ATtiny26 Geiger counter schematic and code

This is the Geiger counter project described in the previous post (dealing with the ATtiny26 board and LP2950 regulator).

Most of the circuit was taken from Tom Napier’s article in issue #184 of Circuit Cellar (Nov. 2005).

In the HV generator two components are critical: the 1N4937 fast recovery diode (I used NTE552) and the STX13005 transistor (or similar), which has a 700 V collector-emitter breakdown voltage. I got free samples from STMicroelectronics. The inductor should be a high quality wave wound dust core choke. The output voltage is set by the maximum current, which is detected by the 20 ohm resistor in the emitter lead of the STX13005 (lower value=higher voltage). I measured 510 V from my version of the circuit using a 100 Mohm input resistance DVM. This is the recommended voltage and remains approximately constant over the input voltage range of 5-9 V. The LND712 tube will work fine in this circuit, but you should replace the 4.7M anode resistor with 10M. I added an HV shutoff circuit to save power, and redesigned the HV filter circuit and the INT0 input circuit to reduce capacitatively coupled noise from the power supply.


The rest of the circuit consists of an 8x2 LCD, with DB4-7 attached to PA4-7; RS and E are attached to PB0 and 1. Two pushbuttons are attached to PA1 and PA2 (pullups on). RS232 output is bit-banged and sent to PA0 at 9600 baud. PA0 is connected to RX on an FTDI USB to serial chip on a breakout board, which also powers the entire circuit. I used a 4.194304 MHz crystal, which conveniently divides
to 1 second.

Hope this is useful!
Jim

MAIN PROGRAM

// Geiger Counter
// ATtiny26 version
// Jim Remington, sjames_remington at yahoo.com
// March 2009

/* pin assignments
PA0	- 	TXD
PA1	-	Sw1
PA2	-	Sw2
PA3 -
PA4-7	LCD data
PB0-1	LCD control
PB3-	HV/LED on/off
PB4-5`	Xtal
PB6-	Count in (Int0)
PB7-	Reset
*/



#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include <string.h>

#define F_CPU 4194304UL
#include <util/delay.h>

#define BAUD 9600

#define ARRAY_UPPERBOUND(array)   ((sizeof(array) / sizeof(array[0])) - 1)
#define LCD_clear  	LCDSendCommand( LCD_Clear );
  
typedef void (*FuncPtr)(void);

void MAIN_TopMenu(void);
void Options(void);
void Start(void);
void SetPer(void);
void SetRS(void);
//void SetHV(void);
void SetCycle(void);
void DisplayClock(void);

uint8_t getkey(void);

void Delay1ms(uint8_t loops);
void Delay10ms(int loops);



	// Setup menu options
const char	SetPerStr[] 	        PROGMEM = " Period";
const char  SetCycleStr[]			PROGMEM = " Cycle";
const char	SetStartStr[]   	    PROGMEM = " Start";

const char*   SetFunctionNames[]	PROGMEM = {SetPerStr, SetCycleStr, SetStartStr};
const FuncPtr SetFunctionPtrs[]		PROGMEM = {SetPer, SetCycle, Start};

	// Messages and Settings
const char	Version[]				PROGMEM = "Cntr 1.0";
const char	Total[]					PROGMEM = " Total";
const char 	PeriodOpts[5][7]		PROGMEM = {"10 sec"," 1 min","10 min","  1 hr"," 10 hr"};
const char  CycleOpts[2][7]			PROGMEM = {"1-shot", "Repeat"};
const char	RTC_zero[]				PROGMEM = "000000";
const unsigned int	PeriodInSeconds[]			PROGMEM	= {10,60,600,3600,36000};

//globals (some others in LCD_Joystick)

static volatile uint8_t	PeriodVal=4;	//default count period = 10 seconds (2nd to last digit in RTC_buffer)
static volatile uint8_t	HV=0;			//HV on/off = auto (0) or continuously on (1)
static volatile uint8_t	RS232=1;		//RS232 on/off (1 = on, 0 = off)
static volatile uint8_t Cycle=0;		//recycle count = continuous (1) or 1-shot (0)
static volatile uint16_t event_count;	//global counter for geiger tube
static volatile char display_buf[9];

#include "lcd.c"
#include "RTC99.c"
#include "suart.c"

#define KEY_MENU  (1<<PA2)
#define KEY_ENTER (1<<PA3)


int main(void)
{   

static uint8_t CurrFunc = 0, key = 0;

	PORTA |= (1<<PA2)|(1<<PA3);		//turn on pullups for switches    

	DDRB |= (1<<PB3);				//enable HV output
	PORTB |= (1<<PB3);				//HV off 
	Delay10ms(50);  		

	TX_init();				//test RS232 timing
/*
	DDRA |= (1<<PA2);	//pa2 output for synch
	PORTA &=~(1<<PA2); //zero
	_delay_us(10);
	while(1) {
	PORTA |= (1<<PA2);	//one to synch scope
	TX_putc('P');		// send a 'U' 1010101010
	PORTA &= ~(1<<PA2);  //turn off synch
	_delay_us(10);
	}
*/
	LCD_Init();
	sei();    

	LCD_SendCommand(LCD_Clear);			//Show version number
	LCD_puts_f(Version);

// display default menu selection

	LCD_GotoXY(1,1);
	LCD_puts_f((char*)pgm_read_word(&SetFunctionNames[CurrFunc]));

//  setup menu

	for (;;)
	{
	key = getkey();
		if (key)
		{

		if (key == KEY_MENU)
            (CurrFunc == ARRAY_UPPERBOUND(SetFunctionPtrs))? CurrFunc = 0 : CurrFunc++;
		else if (key == KEY_ENTER)
			((FuncPtr)pgm_read_word(&SetFunctionPtrs[CurrFunc]))(); // Execute associated subroutine

// print text associated with currently selected function

		LCD_SendCommand(LCD_Clear);			//Show version number
		LCD_puts_f(Version);
		LCD_GotoXY(1,1);
		LCD_puts_f((char*)pgm_read_word(&SetFunctionNames[CurrFunc]));
       }
   }
	return 0;
}


void SetPer(void)
{
	static uint8_t CurrPer=0;
	char key;
	
// print text associated with currently selected period option
		LCD_SendCommand(LCD_Clear);
		LCD_puts_f(SetPerStr);
		LCD_GotoXY(1,1);
		LCD_puts_f(PeriodOpts[CurrPer]);

	for (;;)
	{
    key = getkey();
     if (key)
      { 

         if (key == KEY_MENU)
           (CurrPer == ARRAY_UPPERBOUND(PeriodOpts))? CurrPer = 0 : CurrPer++;
         else if (key == KEY_ENTER)
         	{
			PeriodVal=4-CurrPer;
			return;
			} 		//time base digit in RTC_buffer
 
// print text associated with currently selected period option
		LCD_GotoXY(1,1);
		LCD_puts_f(PeriodOpts[CurrPer]);
       }
	}
}
/*
 NAME:      | Start
 PURPOSE:   | Measure and display counts on PORTB.6
 ARGUMENTS: | None
 RETURNS:   | None
*/
void SetCycle(void)
{
	static uint8_t CurrCycle=0,key;
	
// print text associated with currently selected Cycle option (default=continuous)

		LCD_SendCommand(LCD_Clear);
		LCD_puts_f(SetCycleStr);
		LCD_GotoXY(1,1);
		LCD_puts_f(CycleOpts[CurrCycle]);

	for (;;)
	{
    key = getkey();
     if (key)
      { 
         if (key == KEY_MENU)
           (CurrCycle == ARRAY_UPPERBOUND(CycleOpts))? CurrCycle = 0 : CurrCycle++;
         else if (key == KEY_ENTER)
           {Cycle=CurrCycle; return;} 		//global Cycle flag,  1 = continously repeat

// print text associated with currently selected Cycle option
		LCD_GotoXY(1,1);
		LCD_puts_f(CycleOpts[CurrCycle]);
       }
	}
}

/*
** Interrupt INT0 hander
*/
ISR (INT0_vect)
{
	event_count++;
}

/*
** Start (and continue) the count
*/

void Start(void)
{
		char test,key,test2;

		if(RS232) 
		{
//	write RS232 header
		TX_puts("Period= ");
		TX_puts(utoa(pgm_read_word(&PeriodInSeconds[4-PeriodVal]),(char*)&display_buf,10));
		TX_putc(0x0D);TX_putc(0x0A); //dos line terminator
		}

		PORTB &= ~(1<<PB3);					//HV on
		Delay10ms(10);						//wait for HV to rise

		MCUCR |= (1<<ISC01);				//interrupt on INT0 falling edge
		GIMSK |= (1<<INT0);					//enable INT0

		do
		{
			RTC_Init();
			event_count = 0;
			event_secs = 0;
			test=RTC_buffer[PeriodVal];
			key=0;

			while (test==RTC_buffer[PeriodVal]) 
				{
 					test2=RTC_buffer[5]; 	//get secconds
					LCD_SendCommand(LCD_Clear);

					display_buf[0]='>'; //Counting...
	   				utoa(event_count,(char*)&display_buf[1],10);	//convert to ascii
					LCD_puts((char*)&display_buf); 				// and display
	   				utoa(event_secs,(char*)&display_buf,10);	//seconds count to ascii
					strcat((char*)&display_buf,"s");
					LCD_GotoXY(1,1);
					LCD_puts((char*)&display_buf); 				// and display

					while (test2==RTC_buffer[5])		// hang until seconds digit changes
						{
						key=getkey();
						if (key) break;				//break on button push
						}
					test2=RTC_buffer[5];
					if (key) break;
					}
			if(key) break;

			if(RS232) 
				{	//send count to serial port 
				TX_puts(utoa(event_count,(char*)&display_buf,10)); 
				TX_putc(0x0D);	TX_putc(0x0A);  //line term 
				}
// 		show total

			LCD_SendCommand(LCD_Clear);
			LCD_puts_f(Total);
			utoa(event_count,(char*)&display_buf[0],10);	//convert to ascii
			LCD_GotoXY(1,1);
			LCD_puts((char*)&display_buf); 				// and display
			Delay10ms(300);
		
		} while (Cycle);

//		one shot only

		if(!HV) PORTB |= (1<<PB3);		//turn HV off if "Auto"
		GIMSK &= ~(1<<INT0);			//INT0 interrupts off
		LCD_GotoXY(7,1);
		LCD_SendData('.');
		LCD_GotoXY(7,1);
		while(!getkey());							// wait for button press

		return;
}


/*
**	getkey -- returns key(s) pressed, debounced.
*/

#define BUTTONS ((1<<PA2)|(1<<PA3))

uint8_t getkey(void) 
{
	char key;
	key = ~PINA & BUTTONS;			//Read Port A, nonzero if either PA2 or PA3 grounded
	if (!key) return 0;						//none pressed
	
	Delay10ms(2);							// debounce the press
									//reread Port A, nonzero if same key pressed
	if (!(~PINA & key)) return 0;			//none pressed

	do
	{
		while ((~PINA & key));			// wait for button to be released
		Delay10ms(1);					// debounce the release
	}
	while ((~PINA & key));				// if still not released, loop
	return key;
}

/*
 NAME:      | Delay10ms
 PURPOSE:   | Delays for specified blocks of 10 milliseconds
 ARGUMENTS: | Number of blocks of 10ms to delay
 RETURNS:   | None
*/
void Delay10ms(int loops)
{
 
   while (loops--)
     _delay_ms(10);
}

[/code]

SUPPORT ROUTINES
[code]/*****************************************************************************
**   Initial Author(s)...: ATMEL Norway
**   Rewritten for global BCD display buffer (RTC_buffer[])
**   the digits of which provide system-wide timers for:
**   1 second, 10 seconds, 1 minute, 10 minutes, 1 hour, 10 hours
**   Jim Remington sjames_remington at yahoo.com
**   March 2009
**   ATtiny26 version, counts to 99 hours 59 min, 59 sec
******************************************************************************/

//#include <avr/io.h>
//#include <avr/interrupt.h>
//#include <util/delay.h>

volatile char RTC_buffer[] = "000000"; 	//global BCD clock buffer
volatile unsigned int event_secs=0;

/******************************************************************************
*
*   Function name:  RTC_init
*
*   returns:        none
*
*   parameters:     none
*
*   Purpose:        Setup and start Timer/Counter 1 for one second interrupt @ 4.096 MHz.
*
*******************************************************************************/

void RTC_Init(void)
{

    cli();					// disable global interrupts
    TCNT1 = 0;              // clear TCNT1
    TCCR1A = 0;			// select precaler:4.1943 MHz / 16384 = 256 Hz
 	TCCR1B = 0x0F;
	TIFR   = (1<<TOV1);		// Clear Timer1 OVF interrupt flag
    TIMSK = (1<<TOIE1);	// enable Timer1 overflow interrupt

	strcpy_P((char*)&RTC_buffer[0],RTC_zero);	//initialize clock to zeros

    sei();					// enable global interrupt
}

/*****************************************************************************
*
*   Function name : ShowClock
*
*****************************************************************************/
/*
void ShowClock(void)
{

	LCD_SendCommand(LCD_Clear);
	LCD_puts((char*)&RTC_buffer[0]);
}
*/

/*
** handle Timer 1 overflow interrupt (1 per second)
*/

ISR (TIMER1_OVF1_vect)
{
	uint8_t i;
	char t[6];

	for (i=0;i<6;i++) t[i]=RTC_buffer[i];	//make a copy, so digits don't change more than once on rollover

    t[5]++;		// increment second
	event_secs++;

    if (t[5] > '9')
	{
		t[5]='0';	// increment ten seconds
		t[4]++;
	 	if ( t[4] > '5')
		{
			t[4]='0';
			t[3]++;			// increment minutes
    		if (t[3] > '9')
    		{
				t[3]='0';
				t[2]++;		// increment ten minutes
        
		 		if (t[2] > '5')
				{
					t[2]='0';
					t[1]++;			// increment hours
 	     			if (t[1] > '9')
					{
						t[1]='0';
						t[0]++;		// increment ten hours
						if (t[0] > '9') t[0]='0';
    				}
				}
			}
		}
	}
	for (i=0;i<6;i++) RTC_buffer[i]=t[i];  //copy updated time
}

// suart.c
// TX = PORTA.0
// 8N1
#define BAUD 9600
#define US_BIT 1000000UL/BAUD
// microseconds per bit      


void TX_init(void)
{
   DDRA |= 1;               //PORTA.0 = output
   PORTA |=1;            //TX idle
}

void TX_putc(unsigned char c)
{
   char i,t;
   t=c;                  //copy character to send
   cli();                  //disable interrupts

   PORTA &= ~1;            //start bit
   _delay_us(US_BIT);        //0.41667ms for 2400 baud

    for (i=0; i<8; i++) 
   {
   
   if ( t & 0x01 ) PORTA |= 1;   // If the LSB of char=1, set logic 1 on TX_PIN
   else PORTA &=~1;         // Otherwise: send 0

   _delay_us(US_BIT);

   t >>= 1;               // shift TX buffer right
   }
    
   PORTA |=1;               //send stop bit
   _delay_us(US_BIT);
   sei();                     //enable int
}

void TX_puts(char *data )
{
  while(*data != 0) TX_putc( *data++ );
}

// lcd.c
/*
** LCD Routines with bargraph display, heavily modified from Pascal Stang's avrlib
** Original LCD routines heavily modified from examples posted on Pololu user forum
** sjames_remington /at/ yahoo.com
** ATtiny26 4 MHz version March 2009
** PORTA 4,5,6,7 = DB 4-7
** PORTB 0,1 = E, RS.   R/W is grounded
*/

//#define F_CPU 4000000UL 

#define RS   PB1
#define E   PB0

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>

//char TextBuffer[10];         //global text buffer

/*
 
 The required delays included here are generously in accord with the LCD data sheet, and should be 
 independent of the clock speed as long as F_CPU is set correctly (>= 1 MHz)

 Jim Remington, sjames_remington at yahoo dot com

*/

//Function prototypes

void LCD_Init(void);
void LCD_SendData(char);
void LCD_SendCommand(char command);
void LCD_puts(char *string);
void LCD_puts_f(const char *string);
void LCD_GotoXY(unsigned char x,unsigned char y);

// useful defines (LCD commands)

#define LCD_Clear 0x01
#define LCD_Line1 0x80
#define LCD_Line2 0xC0

/*
Send lower 4 bits of data byte to display
*/
void   LCD_SendNibble(char data )
{
   PORTA &= ~(0xF0);         //clear LCD bus bits 0-4
   PORTA |= ((data & 0x0F)<<4);   //or in data
   _delay_us(5);
   PORTB |= (1<<E);      //E = 1
   _delay_us(5);         //required minimum 1 us delay
   PORTB &= ~(1<<E);      //E = 0
   _delay_ms(0.5);         // someone suggested 4.1 ms here, this worked.
}

/*
Send a character to the LCD display
*/

void   LCD_SendData(char data )

{
   PORTB |= (1<<RS);      //RS = 1;
   LCD_SendNibble( data >> 4 );
   LCD_SendNibble( data );
   _delay_us(100);         //38 us typically needed to complete this action
}

/*
Send a command to display. Required delay time depends on the command and the 
LCD controller clock frequency -- here assumed to be the minimum 190 kHz
*/
void   LCD_SendCommand(char command )

{
   PORTB &= ~(1<<RS);      // RS = 0
   LCD_SendNibble(command >> 4 );
   LCD_SendNibble(command);
   _delay_ms(2);         //maximum required is 2.1 ms for "clear display"
}

// print a string constant

void   LCD_puts(char *str )
{
    while (*str != 0) LCD_SendData( *str++ );
}

void LCD_puts_f(const char *FlashData)
{
// Print data from program memory

   strcpy_P((char*)&TextBuffer[0], FlashData);
   LCD_puts(TextBuffer);
}
// set print position to (x,y) where y=line number (0 or 1), x = character position 0, 1, etc.

void LCD_GotoXY(unsigned char x, unsigned char y)
{
   volatile unsigned char ddram_addr;
   ddram_addr=0x80;         //initialize data ram address to 0 (default)
   if (y) ddram_addr=0xC0;     // else start print at line 2, DDRAM address 0x40
   LCD_SendCommand(ddram_addr+ (x&0x7F) );
}
/*
Initialize the LCD Display, timing requirements taken from datasheet
Set PORTA 4-7 to DB 4-7
Set PORTB 0,1 to E, RS

Sends required start-up sequence to set 4 bit interface, 2 lines, 5x8 characters and clear display
*/
   
void   LCD_Init( void )
{
   DDRA |= 0xF0;               //data bus bits = PB4-7 output
   DDRB |= (1<<RS)|(1<<E);         //RS, E output
   PORTB &= ~( (1<<RS)|(1<<E) );   // E=0,RS=0
   _delay_ms(10);         //required startup sequence from power-on (see datasheet)
   _delay_ms(10);         // 2x10 because max delay is 16 ms at 16 MHz
   LCD_SendNibble(0x03);   //set interface=8 bits
   _delay_ms(10);         //wait at least 5 ms   
   LCD_SendNibble(0x03);   //set interface=8 bits
   _delay_ms(1);         //wait at least 100 us
   LCD_SendNibble(0x03);   //set interface=8 bits
   _delay_ms(1);         //wait at least 100 us
   LCD_SendNibble(0x02);   //set interface=4 bits
   _delay_ms(1);         //delays after this are built into SendCommand
   LCD_SendCommand(0x28);   //set interface=4 bits, 2 lines, 5x8 characters
   LCD_SendCommand(0x08);   //display off, cursor off, blink off 
   LCD_SendCommand(0x01);   //clear display
   LCD_SendCommand(0x06);   //entry mode set, cursor shifts right after character rcvd.
   LCD_SendCommand(0x0D);   //0b01DCB  D=1:Display on, C=1:cursor on, B=1:Blink on
// the above commands move cursor due to autoinc. So clear display again:
//   LCD_SendCommand( LCD_Clear );
}
1 Like

Thanks for posting, Jim! Do you know what frequency is produced by the 555 timer circuit? I am wondering whether the switching could be done by the AVR instead.

Paul:

The 555 oscillates at about 4 kHz, but the important point is that the pulse on time is controlled by the inductor and emitter resistor (which sets the maximum current), which in turn sets the high voltage value. The inductor on time turns out to be about 30 microseconds at Vcc = 9V. The uP could certainly generate such a pulse but the interrupts for the counter and real time clock would complicate the timing or, the pulse generation code could interfere with the basic particle counting.

At the moment, I’m out of memory. However, I’m thinking that the next version might dispense with the pushbuttons and the interactive parameter setup. Instead, it would simply accept commands from a PC over the serial line. That would be much simpler to program and would also free up a lot of space for other functions. I might experiment. On the other hand, the CCFL inverters sold by All Electronics for $6.95 work down to 1.5 V and when powered at 3.6 V, generate about 250VAC at 50 kHz (consuming 15 mA). This is an obvious way to go if you don’t mind the extra current draw (see attached)!

I would suggest adding some sort of regulation, such as a 1 Meg resistor in series with a string of Zener diodes, to set the output at the recommended 500 V.


Cheers, Jim

Okay, so you are injecting a constant average current; did you have to adjust that resistor value to match the other components? I’m also curious about whether the 500 V level drops when there are a lot of counts, and what the pulse length is - it looks like the output RC constant is about 10 us? Does that set your dead time, or is the tube a limiting factor?

Anyway, this all looks very fun, and I am definitely going to have to try building my own Geiger counter soon. I would like to try doing it without the 555, using a PWM output and analog feedback to set the output voltage, if possible, and maybe a hardware counter on the AVR for the counting. There are plenty of uses for one of these around the house: checking for radon, measuring the potassium content of bananas, analyzing granite countertops, etc…

-Paul

Paul:

These things are indeed fun! Another interesting source of radioactivity is “uranium glass” or “vaseline glass”, which is yellow fluorescent due to added uranium and thus weakly radioactive. There are also the famous radioactive orange glazed ceramics that were popular a couple of decades ago. For a very hot source of alpha radiation, you can take apart a smoke detector to get at the polonium emitter. Keep in mind that it is extremely dangerous to inhale or ingest polononium, so everything should be handled while wearing gloves and a good air filter mask.

In the circuits above the HV supply is unregulated, so the output depends on the average current draw by the Geiger tube. For the LND7313 tube, the pulse is about 200 usec long, with a peak current of about 30 uA. At 1000 counts/sec (a pretty hot source), I calculate that this corresponds to an average current draw of 3 uA or a drop of 30V across the 10 Mohm resistor. That is acceptable.

In my vicinity the average background is about 1 count/second, but you should see how that shoots up when you go up over 5,000 feet elevation! Cosmic radiation at 30,000 feet is about 100 times as high as at sea level.

For a PWM HV source example, Nuts & Volts had an article a while ago showing how to make an LED flashlight with a 100 uH inductor and an ATtiny13 running 13 bytes of code:
nutsvolts.texterity.com/nutsvolts/200802/?pg=33

It would be easy to adapt this to power a Geiger counter.
Jim

Hi, sorry this is a little bit off topic, but I was wondering if you could refer me to a program that will write an electrical schematic such as the on you have in your post.

Thanks, Jacy

Sorry about the delayed response!

The schematic was generated using the program Capture from the free PSPICE 9.1 student version. The package can be downloaded from various places on the web. It is a bit cumbersome (parts selection is awkward) but with a little practise, it is OK.

In order to produce images such as that posted, I select the entire schematic within Capture, “copy”, then paste into some other drawing program such as Freehand or Powerpoint and finally write out a .gif file.

Jim

If you want something really easy and intuitive to use, I would recommend the free software from ExpressPCB. It’s actually two programs, Express PCB for doing circuit board layout, and Express SCH for schematic diagrams. It’s sort of the MS Paint of circuit design. For example, I used Express SCH to make these schematics: 1 | 2. It uses a proprietary file format for ordering circuit boards from the company, but I’ve never actually ordered anything from them (although I have printed out circuit board designs from Express PCB and tried to use them as transfers for etching).

-Adam

Hello to everybody, very nice and clever project.
I have a problem with the AVR code. I can’t compile the AVR code with AVR Studio and WINAVR (I’m newbie in AVR programming and I’m sure that I’m doing something wrong). I need the .hex file to burn into the microcontroller.
Any help would be acceptable.

Thank you in advance,

Dimitris

Hello,

I have seen on the schematic a choice of 100uH inductor and the setting of frequency and duty cycle of the TLC555.
What is the reason of the choice of the different values of this design?
Can anyone help me to understand?

Thanks a lot!

detube78

The design for the high voltage circuit was originally done by Tom Napier and published (I think) in Nuts&Volts. There might be a reference in his Circuit Cellar article referred to above.

Basically, you know how much energy can be stored in the inductance, from the current (1/2)Li^2. The maximum inductor current is controlled by the ~22 ohm resistor to ground, which triggers a transistor to switch off the 555 thus controlling the duty cycle.

Then you need to know what the current draw of the Geiger tube will be, assuming some maximum count rate. The average Geiger tube current*500 V gives you the power draw. The switching rate of the 555 is set to produce a bit more than that power draw (energy stored in inductor/time).

Thanks for the reply!

I would like to use this HV at the semiconductor radiation detector and i would like to set the HV between 0 to 1500Volts from the 4,5V. This is the reason of my question. I need to choose the good frequency with associated duty cycle and inductance. I have found the MOSFET transistor. I think after that using voltage multiplier with capacitance and diode. The current of charge is about 100uA.

Hi…Guys.

I want to build a handheld spectroscopy instruments using semiconductor CZT detector. The detector will be biased with negative HV ~ 500V and signal will be extracted from the cathode.

There are many references on positive HV design but how about negative HV? Can this circuit be modified to produce a negative HV?

Thanks

To make a negative HV supply, just reverse the diodes in the circuit below. Also, most HV modules that can be salvaged from flash cameras have negative outputs.

The circuit that I showed in the original post for the Geiger counter supply cannot easily be modified for negative output.