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 =====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.
void CCP_INIT() {
setup_ccp1(CCP_CAPTURE_RE);
setup_ccp2(CCP_CAPTURE_RE);
enable_interrupts( INT_CCP1 );
enable_interrupts( INT_CCP2 );
}
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 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
/* 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 );
}
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!
No comments:
Post a Comment