The Geiger-Muller tube (GM tube) is a commonly used radiation detector, primarily used to detect and measure various types of radiation, such as alpha particles, beta particles, and gamma rays. Its structure is simple, consisting of a metal outer shell
(modern Geiger tubes are made of glass tubes coated with a metal film) and an internal gas-filled conduit. Typically, the metal outer shell is cylindrical, usually made of metal or alloy, possessing good conductivity and shielding properties to prevent external interference. The gas filled inside is usually a low-pressure inert gas, such as a mixture of nitrogen and argon. This gas has good ionization and energy transfer properties, producing an effective ionization effect. The

working principle of the Geiger tube is based on the gas ionization effect. The Geiger tube has
a simple structure, typically consisting of a thin metal wire fixed in the center of a metal cylinder. A DC voltage, usually greater than 300V, is applied between the wire and the outer wall. The container is filled with a gas for generating ionization (such as argon) and a gas for quenching (such as butane).

When ions pass through a Geiger tube, they excite a gas atom, causing ionization. The excited electrons, attracted by the positive voltage of the metal wire, collide with other gas molecules, causing avalanche ionization. This process also triggers a larger-scale ionization avalanche through the emitted ultraviolet photons, ultimately forming a current pulse consisting of up to 10 billion electrons. Because of their large mass, the ionized positively charged particles move slowly. After the electron avalanche, they remain in the center of the Geiger tube, forming a positive electron cloud that disrupts the electric field within the tube, hindering further avalanche. The avalanche current is quickly terminated by the combined action of the current-limiting resistor and the quenching gas. After a recovery dead time of 100-500 microseconds, the Geiger tube returns to normal, ready for the next particle detection. In short, a pulse is generated when an ion is detected passing through the Geiger tube.
The main circuit design
needs to achieve the following basic functions:
handheld portability;
buzzer alert;
radiation detection display; and
a charging/boost circuit .
Since it's handheld, it requires battery power.
Under normal circumstances, it's powered by the battery voltage BAT+. When a Type-C port is connected, VCCIN has power, which charges the battery, and the RED charging indicator light illuminates. VOUT is the 5V output voltage boosted from the battery voltage. Please refer to the TP5400 datasheet for details.

The Geiger diode operating circuit
detects radiation, requiring a boost circuit to ensure the Geiger diode functions properly. According to the Geiger transistor manual, the operating voltage should be at least 280V, with 380V recommended.

Q2/Q4 in the schematic need to be high-voltage rated, and R60 uses a 3296-packaged potentiometer for output voltage adjustment. A 4000Hz passive buzzer is used for the

buzzer and display
. The screen is a 1.69-inch SPI 16-bit color screen. (Screen purchase link: 1.69-inch TFT color high-definition IPS LCD bare screen 240*280 SPI interface ST7789 driver (select soldering type)).

The
schematic also includes reserved sections for NB-IoT and 2.4G wireless communication; these modules are not essential and can be omitted or removed. The most important part of
the main software design
code is the second detection and interrupt function. The Geiger transistor detection pin PE6 is set to external interrupt falling edge trigger mode (when a pulse is measured on the Geiger transistor, Q3 conducts, making PE6 low).
// Get the count value
uint32_t get_Geiger_count(void)
{
return Geiger_count;
}
// Clear the count value
void set_Geiger_count(uint32_t count)
{
Geiger_count = count;
}
// External interrupt service function
void EXTI5_9_IRQHandler(void)
{
// Falling edge detected
if(exti_interrupt_flag_get(EXTI_6) == SET)
{
// Determine PE6 is low
if( gpio_input_bit_get(GEIGER_GPIO_PORT, GEIGER_GPIO_PIN) == RESET )
{
// Buzzer sounds
beep(100);
// Increment the count value
Geiger_count++;
}
// Clear the interrupt flag
exti_interrupt_flag_clear(EXTI_6);
}
Every
second, the current count value is compared to the previous count value. If there is a difference, the difference is recorded; this difference is the count value for that second. The difference is stored in the array `COUNTS_buff`. This difference is accumulated 10 times, so `COUNTS_buff` contains 10 measurement results from the past 10 seconds. All numbers in the `COUNTS_buff` array are summed and multiplied by 6 to display the CPM value (CPM = count rate per minute) on the screen.
The dose equivalent, expressed in µSv/hr, is calculated by dividing the CPM value by 2^10 (K) (this value can be found in the literature).
// One second elapsed
if( get_sec_timer_flag() == 1 )
{
// Clear the second flag
set_sec_timer_flag(0);
// Disable the timer
timer_disable(BSP_TIMER);
// Get the current Geiger counter value
new_geiger = get_Geiger_count();
// If the current counter value is different from the previous counter value
if( new_geiger != again_geiger )
{
//保存当前与之前的计数差
COUNTS_buff[t] = new_geiger - again_geiger;
//更新之前计数值
again_geiger = new_geiger;
}
else
{
//如果没有计数变化
COUNTS_buff[t] = 0;
}
//累加10次
for( i = 0; i < 10; i++ )
{
TenSecCPS = TenSecCPS + COUNTS_buff[i];
}
//*6得到CPM
CPM = 6 * TenSecCPS;
//计算瞬时值
CPM = CPM / K;
//清除CPS
TenSecCPS = 0;
//累加数组限制
t++ ;
if (t > 9) { t = 0 ;}
//保存瞬时值
CPM_buff[AVGCPM_num] = CPM;
//累加60次
for( i = 0; i < 60; i++ )
{
AVGCPM = AVGCPM + CPM_buff[i];
}
//换算平均值
AVGCPM = AVGCPM/60.0;
//平均值累加数组限制
AVGCPM_num++;
if( AVGCPM_num >= 60 ) AVGCPM_num = 0;
//显示瞬时危险程度
//警告
if( CPM >= 0.5 && CPM < 1.0 )
{
new_level=1;
beep_open(50);//控制蜂鸣器鸣叫频率一般
}
//危险
else if( CPM >= 1.0 )
{
new_level=2;
beep_open(100);//控制蜂鸣器鸣叫频率加快
}
else//正常
{
new_level=0;
beep_close();//关闭蜂鸣器
}
//如果危险等级与之前不一样
if( new_level != again_level )
{
//更新之前的危险等级
again_level = new_level;
//更新瞬时值背景色参数
widget_moment_frame.color = color_buf[new_level];
widget_moment_value.color = color_buf[new_level];
widget_moment_units.color = color_buf[new_level];
widget_moment_slogan.color = color_buf[new_level];
tli_show_button(widget_moment_frame.x,widget_moment_frame.y,widget_moment_frame.w,widget_moment_frame.h,widget_moment_frame.value,widget_moment_frame.color);
LCD_ShowChinese(widget_moment_slogan.x,widget_moment_slogan.y,widget_moment_slogan.string,WHITE,widget_moment_slogan.color,widget_moment_slogan.value,0);
LCD_ShowString(widget_moment_units.x,widget_moment_units.y,(const uint8_t*)widget_moment_units.string,WHITE,widget_moment_units.color,widget_moment_units.value,0);
}
//Show instantaneous value
sprintf((char*)widget_moment_value.string,"%.4f", CPM );
LCD_ShowString(widget_moment_value.x,widget_moment_value.y,(const uint8_t*)widget_moment_value.string,WHITE,widget_moment_value.color,widget_moment_value.value,0);
//Show average
sprintf((char*)widget_average_value.string,"%.4f", AVGCPM );
LCD_ShowString(widget_average_value.x,widget_average_value.y,(const uint8_t*)widget_average_value.string,WHITE,widget_average_value.color,widget_average_value.value,0);
//Show the cumulative value
sprintf((char*)widget_aggregate_value.string,"%d", new_geiger );
LCD_ShowString(280/2-(strlen((const char*)widget_aggregate_value.string)*(3*8)/2),widget_aggregate_value.y,(const uint8_t*)widget_aggregate_value.string,WHITE,widget_aggregate_value.color,widget_aggregate_value.value,0);
//Turn on the timer
timer_counter_value_config(BSP_TIMER,0);
timer_enable(BSP_TIMER);
}