Pages

Tuesday, 19 August 2014

How It Works #2: Geiger Counter (in collaboration with my friend)

Introduction

A fellow friend called Andrew from my previous university is an avid physic enthusiast. He possesses all sort of weird exotic contraptions and nerdy stuff. I believe we first met when I returned a book ("A Brief History of Time" by Stephen Hawking) to him. That was when I found out that we share similar interests, especially astronomy.

About a year ago, he showed me one of the new stuff he bought online.

GMC-200

It was a Geiger-Muller tube encased in a plastic box. Contrary to what the box says, it is not actually a counter. Technically, it is a detector. When a radiation is present nearby, the small speaker inside will make a clicking sound. The detector outputs an analogue pulse that, after running through an oscilloscope, decays in a matter of several microseconds. This is an important factor that I will discuss later.

Picture courtesy of my friend. The screen was malfunctioning.

He told me that his intention was to combine this detector with a counter circuit he bought in a local shop to show the number of counts detected. After several trials (which I will not elaborate as it is not the focus of this post), we found out that the counter was not sensitive enough to detect the pulse output from the detector. From the manual, the counter can only detect signals of at least 30ms long, which is roughly 33 counts per second, or Hertz. The duration is to avoid false reading. The pulse as shown above decays too fast for the counter to detect, hence a new solution is needed.

Enter Microcontroller

My friend briefed me on his idea of the overall structure.


There are several components in the system, namely:
  • Display : To display the information from the counter (counts, counts per minute, etc)
  • Timer : To implement a countdown time to count for a specific duration 
  • Counter : To count the inputs from Geiger detectors
  • Geiger Detector: Geiger-Muller tubes for detecting ionizing radiation

He didn't study much about electronic and has no experience in programming, so naturally his suggestion was to use discrete electronic components such as 555 timer and transistors to build the system. Based on my experience, I told him that the majority of the system can be squeezed into one small silicon package.

Prerequisite Checklist

In order to implement all the features, there are several things that is required.

1. Timers

Timers are very useful in many programming projects. They provide accurate timing for many purposes; their accuracy is determined by the clock crystal (internal or external), which is also highly accurate. They can be used for countdown/countup timer, button scanning/debouncing, pulse-width modulation, etc. In this case, timers are essential to implement the timer components.

2. Display

Display can be simple LEDs, 7 segment display, LCD, graphical LCD, etc. Display is used to, well, display the information from the counter. They are simple to implement once you have a proper software driver.

3. CCP Module(Capture-Compare-PWM)

One of the many features of Microchip's microcontrollers is the CCP module. The module provides 3 different function: capturing inputs, comparing inputs with something else, and outputting PWM. In this project, we are using it for capturing the inputs from GM tubes.

Now that we know what we need for the project, we can start writing the code. This post assumes that you have basic knowledge of C programming and will skip most of the fundamental stuff.


Main ingredients:
Original recipe makes 1 Geiger Counter
1 x Microchip PIC16F1938 with an internal clock of 16MHz (optionally, replace with an external crystal)
1 x 16x2 alphanumeric LCD module
3 x pushbuttons
Miscellaneous passive electronic components

Real-time Clock (aka Countdown Timer)

To be honest, I didn't come up with this RTC function. I found it in a forum when I was working on my final year project for my Bachelor's Degree.

The basic idea of the RTC (called zero-drift RTC by the original author) is that it uses Timer and interrupts to measure time. When a timer module is enabled, it counts up every instruction cycle, to a maximum of 2^(timer bit) - 1 counts before resetting (overflow). Instruction cycle is the amount of real time the MCU requires to execute an instruction. For Microchip MCU, one instruction cycle is 4 crystal clock cycles.

In this project, Timer1 (Timer1 is 16-bit) is used for the RTC. It is best explained with an example.
setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
The above statement initialises Timer1 by telling it to use the internal 16MHz clock and a prescale of 1. Prescale simply means how many instruction cycles is required before the timer counts up.

16MHz clock frequency = 1/16M
                      = 62.5ns
1 instruction cycles = 4 clock cycles
                     = 0.25us
Therefore, timer counts up every 0.25us.
16-bit = 2^16 = 65536 counts
65536 x 0.25us = 16.384ms
Therefore, timer overflows every 16.384ms.
In 1 second, the timer will have overflowed approximately 61 times.

Based on this information, the RTC can be easily implemented.

Initialise a 32-bit integer variable (eg. ticker) to the instruction frequency (eg. 4000000). Every time the timer overflows, the program is interrupted to perform the following ISR (interrupt service routine). Ignore the time, rst_count, and geiger_pause for the time being:

//===== TIMER1 interrupt routine =====
#INT_TIMER1
void TIMER1_rtc_isr() {
   
    ticker -= 65536;
    if( ticker < 65536 ) {
        ticker += INST_FREQ;
        // A second has passed
        second++;
        time--;
        if(rst_count)  rst_count--;
        if(geiger_pause)  output_toggle(pin_a3);
    }
}

Decrement ticker by 65536 (which is the number of counts per interrupt). If ticker is less than 65536, it is safe to point out that about 61 overflows have occurred ( 4000000 - (61*65536) = 2304). From the above calculation, 61 overflows roughly equal to 1 second, hence it is also safe to deduce that 1 second has passed. When this occur, ticker is topped up with the instruction frequency (4M) and the process repeats for as long as necessary. Using this method, the RTC will not be subjected to drift as it will recalibrate itself.

When the user inputs an arbitrary amount of time for the countdown timer, the program will translate that into amount of seconds and store it in the time variable. Every time a second has passed, time is decremented by 1. When it reaches 0, we know that the user-specified time has passed, thereby stopping the Geiger counter.

With just a simple interrupt routine, the countdown timer is successfully implemented. Next, we look at input capturing.

CCP Module (aka Input Capturing)

As mentioned, PIC16F1938 includes several CCP modules. Before using this module, the initial idea for input capturing was to sample the input ports at a specific time interval. This is why I wrote in the introduction that the decay rate of the GMC-200 output pulse is an important factor. Based on the oscilloscope data, I programmed the MCU to sample the input ports at about 16ms interval (using Timer1). Essentially it means the program check the inputs every 16ms for any logic HIGH. If there is one, increment variables accordingly.

After revising the code and some research, I learned about CCP module. It provides a convenient tool to capture inputs. Two CCP modules were programmed to capture an input on rising edge:
//===== CCP module initialisation =====
void CCP_INIT() {
    setup_ccp1(CCP_CAPTURE_RE);
    setup_ccp2(CCP_CAPTURE_RE);
    enable_interrupts( INT_CCP1 );
    enable_interrupts( INT_CCP2 );
}
When a rising edge is detected on any CCP pins, the program is interrupted and perform any necessary tasks, which in this project means incrementing tube A and tube B counts accordingly.

My friend wanted to implement two modes: coincidence and non-coincidence. For non-coincidence, the input capturing is not overly complicated. However, for coincidence, it is trickier. After rounds of discussion, we concluded that for two readings to be considered as coincident, the time interval between them has to be less than 50us.

In order to do this, a separate timer is needed.
//===== TIMER2 setup =====
/* Timer2 is used for inserting a buffer between coincident events */
void TIMER2_INIT() {
    setup_timer_2( T2_DIV_BY_1, 0xA0, 1);
    enable_interrupts( INT_TIMER2 );
}
Timer2 is 8-bit (256 counts). Prescale is set to 1, the maximum count is set to 160 (0xA0), and 1 overflow is required for interruption. Therefore Timer2 interrupts every
0.25us x 160 = 40us
In coincidence mode, every time an input is detected, Timer2 is reset to 0 and a variable called count_buffer is kept HIGH. After 40us, Timer2 interrupts and count_buffer is reset to LOW. Coincidence count will only increment if another input is detected within the 40us buffer.

There are several more features that I didn't talk about, such as buttons handling, menus handling, user-interface and customised countdown time period. Those are not the focus of this post and I might write about it in other post in the future.

Conclusion


The final prototype
Final schematic

It took me about 2 weeks to debug and finish writing this program. Some of the features were done before this (RTC, user-interface, buttons handling from my FYP) so I had plenty of experience in writing the code. The new feature I learned from this is the CCP module, which was briefly visited by me in a separate project to learn its basic.

Overall, this has been a fun project to work on as it gives me a purpose for all my fiddling in programming MCU. Last but not least, I would like to express my gratitude to my friend Andrew for lending me a GMC-200 for debugging and all the nerdy discussion.

The C code for the project, for anyone interested, is available here.

Happy programming!

Tuesday, 13 May 2014

How It Works #1: How does the PIC16F887 calculator work?

Introduction

When I first decided to put up this video showing a simple calculator made from PIC16F887, I did not expect a lot of viewership and comments. The first reason is that the programming language I used, CCS C, is not very popular. The second reason is that, compared to other videos on YouTube, my project can be considered child's play. In the months following the upload, I had received numerous people asking for the code. Whatever their purpose for requesting the code is, be it for school assignments or hobby projects, I decided to share it publicly after some editing to include sharing policy, clear comments, etc.

In this long (please bear with me) blog post, I will attempt to explain how the calculator works. I will assume you have at least basic knowledge of general C programming syntax and how to compile and program a PIC in CCS C.

Getting Information before Calculating

In order to perform calculation, the microcontroller (MCU) needs to gather some data. The simplest arithmetic calculations, i.e. addition, subtraction, multiplication, and division, require 3 things:
  1. First Argument
  2. Second Argument
  3. Operator
For addition and multiplication, the order of the arguments is not important, but for subtraction and division, it is important. Therefore, the MCU must know which one is the first and which one is the second. To understand this, first you need to know how the LCD module works.

The LCD has a cursor that points to a specific coordinate at any given time. After initializing, the cursor is moved to (1,1) using the following code
lcd_gotoxy(1,1);
Coordinate (1,1) is upper left corner of the screen. After initialization, the MCU will scan for any key press using
k = kbd_getc();
This function is defined in the flex_keypad_AE.c file. It is repeated in a while loop. When a key press is detected, the MCU will then check which key is pressed. Any key will just print out the corresponding number or symbol, except the "*" key and the "=" key. When "*" key is detected, it will initiate a clear screen function. There is nothing to talk about.

The real "magic" happens when "=" key is pressed. When detected, the MCU will start a chain of instructions.

The first step is (obviously) printing the "=" character, and the cursor is reset back to (1,1) via variables definition
posX = 1;
posY
= 1;
Another variable called index is also reset to 0. I will explain what this variable is later.

After the cursor reset, the LCD starts to read the numbers on the screen in a do-while loop. What this means is the LCD will keep reading as long as a condition is satisfied. In this case, the condition is a check of a flag called calcF (calculation finished). This flag checks whether the calculation is done. Hence, as long as the calculation is not done, the LCD will keep reading.

Typically, numbers are separated by an operator, for example
24+275=
Reading from the left, if the LCD detected any numbers, it will keep scanning to the right. At some point the cursor will point to the operator (if there is one). Note the underline in the statement below.
24+275=
The LCD will send the current cursor coordinate to the MCU to record the operator position in a variable called opPos. This coordinate will serve as the separation point between the two arguments. After that, the LCD will keep scanning to the right again, until it detected the "=" character. At this point, we can safely say that the cursor has reached the end of the mathematical statement and the argument separation process can be started.

NB: Weight (Index)

Roman numeric system has a very odd feature. In words and sentences, the alphabets are arranged from left to right; but in numbers, the weight of numbers increases from right to left. This simply means the number "4" in 540 and 1400 carries different weight (10 and 100 respectively). For this reason and simplicity, the argument is read from right to left. It is easier to understand if you look at the following calculations, using the statement above as example here:
275 = 5 + (7 x 10) + (2 x 100)

Determining the Second Arguments

Starting from the "=" sign, the cursor moves left. The weight is increased by a multiplication of 10, as shown by the statement
index++;
But there is a problem: the LCD returns any data it reads in char type. In programming, it is impossible to perform arithmetic using this type. Therefore the data is typecasted into int type and stored in a buffer.
bufBc = lcd_getc(posX,posY); bufB = (int)bufBc;
There is also another problem here: char 4 does not correspond to int 4. The LCD modules uses ASCII table to translate numbers into alphabets, numbers, symbols, etc.
Click to enlarge in a new window
Click to enlarge in a separate window
Luckily, or rather, intentionally, the ASCII table is designed in such a way that the numbers are shifted by hex 30. For example, in order to display the number "4", the MCU will send a hexadecimal 34 to the LCD.  Therefore to store the actual number, a hex 30 is subtracted from the data before multiplying the weight (in the power of 10s).
bufB = ( (bufB-0x30) * pow(10,index-1) ) + 0.1;
The 0.1 at the end of the statement is required due to the way computers perform logarithmic calculations. At the moment, I cannot remember where I got that information, but rest assure that without it, the result will be very slightly different from the actual number (i,e, 0.9999999...).

The result of bufB is stored in a variable called B, which, technically, means second argument.
B += bufB
This is done in a cumulative way, i.e. 5 + 70 + 200 + ... , as shown in the example in NB above. All the steps above are repeated in a loop until the cursor reaches the operator coordinate opPos.
while( posX > opPos )
Once the cursor reaches the operator (if there is one), we can safely say that the second argument is completely captured.

To Operate or Not to Operate?

Now you may be asking, "What happens if there is no operator? What if the statement is just '275='? "

It may sound weird, but it does not really matter whether there is an operator. Right after the second argument capture, the final result is immediately set to the second argument.
result = B
The next step is to check whether the cursor is at position 0.
if ( posX != 0 )
If it is, then it means there is no operation and the loop closes. The final result is simply the second argument.
If it is not, then it means there is an operator and another argument, hence the loop continues.

Now that the cursor is at the operator position, it is the best time to determine the operator type. This is simply done by storing the char data read by the LCD at this point.
operator = lcd_getc(posX,posY);
After that, the program continues to capture the first argument in a similar fashion to the second argument capturing process.
bufAc = lcd_getc(posX,posY);
bufA = (int)bufAc;
bufA = ( (bufA-0x30) * pow(10,index-1) ) + 0.1;
A += bufA;
All the steps above are repeated in a loop until the cursor reaches the origin.
while( posX >= 1 )

Calculate!

Now that the program has captured the first argument, second argument, and operator type, it has enough information to perform the actual calculation. Remember that, in the previous step, we stored the operator type in a variable? This is the time to put that information to use. By determining the operator type, the corresponding arithmetic calculation can be performed.

Addition is simply A + B : result = A + B;
Subtraction is simply A - B : result = A - B; (remember, the order of the arguments is important! A - B does not equal to B - A)
Multiplication is simply A x B : result = A * B;

In the case of division, additional steps are required. All the other operations are done using integers, i.e. numbers without decimal points; but in division, decimal is often required. Hence, to correctly perform division, the arguments need to be typecasted into floating points.
resultF = (float)A / (float)B;
After the calculation is done, the final step is to print the result. Note that there are two different variables for result: result and resultF. This difference is important to change the result display format via a variable called divFlag (division flag). It is often not necessary to print the result in decimal format if only integers are involved, such as in addition, subtraction, and multiplication.

Once the difference is determined, the final result can be printed, conveniently at the second line of the LCD.
lcd_gotoxy(1,2);
printf(lcd_putc, "%s", string);
Also, the calcF flag is checked to indicate that the calculation is finished, thus ending the main loop. Before ending, all the variables are reset to initial values to prepare for the next calculation.

Any key press after the loop ends will initiate a clear screen, providing an empty screen for the next calculation.

Conclusion

While this program may look simple, it was one of the proudest moment in my short life as an embedded system programmer. I am not an expert in this, that is why completing this task is considered a milestone for me. As shown in the YouTube video description, this project was actually a cancelled assignment when I was studying in a university back in 2010. This calculator only supports 2 arguments and basic arithmetic operations. Complex operations such as trigonometry and logarithm are being considered.

If you are still reading at this point, I sincerely thank you and hope you enjoyed reading the working principle behind my simple calculator. If you have any feedback, question, or suggestion, please do not hesitate to leave a comment.

Happy programming!