This page last updated on Thu, 19 Jan 2017 00:45:43 +0000

(Making it all work together)
Dieter (Diz) Gentzow -- W8DIZ                                   

Part-3 of the Frequency Counter project will bring together all the things we learned in Parts 1 and 2.
For Part-3, you will need a built prototype of the freq counter, either from your own parts or purchase the 
Freq Counter Development Kit offered at the end of this article.  You should also have reviewed the internals
of the ATmel AVR microprocessor, part number ATtiny2313-20, and the articles referenced in the addendum files
noted at the end of this article.  We will write assembly code on a Linux or Windows PC, assemble the code,
download the code to the target prototype hardware and hopefully get the target hardware to function as intended.
If you do not have access to the first two articles of this project, you may download them from the links
in the notes section of this article.  All references to computers will be for the Linux Mint operating
system, but conversion to Windows is easy.  Any reference to the GEDIT text editor can be read as NOTEPAD
or your favorite TEXT editor for Windows.  The AVRA assembler and AVRDUDE are both available for Linux and Windows.
OK, here we go:

If you have not installed the AVRA assembler on your PC, then you need to install it now.  For Windows,
go to and download and install version 1.2.3 filename
and for Linux Mint, follow the installation proceedure listed in the addendum at
Now that you have AVRA installed, let's test the program.  Create a directory on your Desktop called qq-fc-project
by right-clicking the Desktop and selecting "Create Folder".
Inside this new directory, create a sub-directory (Folder) called led-code.  Start GEDIT and save the file
as led.asm in the led-code subdirectory.  Enter the follow code/text exactly as shown using GEDIT:
;cd Desktop/qq-fc-project/led-code/
.include ""
Save the program by holding down the ALT key and typing "F", then release the keys and type "S".
The first 2 lines of the code are a comment lines, indicated by the starting semicolon.
The third line tells the AVRA assembler to insert(include) a file into the source code.
The fourth line is the OP-CODE for "no operation" which is usually used as a place holder or to literally waste CPU time.
Now we are ready to assemble the code.  For Linux users, open the TERMINAL program
by clicking on Menu and then Terminal under the System column.
When the Terminal program pops up on the screen (in Linux), it is running from your default directory,
a sub-directory of the /home directory.  Let's cut and paste the command in line 2 of your source code
into the Terminal program. Highlight "cd Desktop/qq-fc-project/led-code/" with your mouse;
hold down the CONTROL key and hit the "C" key to copy the line of code into your "buffer".
Now paste the command into the Terminal program by holding down the 
CONTROL & SHIFT keys and type "V".  This should make the terminal command line look like:
username@username-desktop ~ $ cd Desktop/qq-fc-project/led-code/
Now hit the enter key to execute the command.  The new command line should look like:
username@username-desktop ~/Desktop/qq-fc-project/led-code $ 
Now we are ready to assemble the source code into usable "machine code" for the target hardware.
At the new command line, type avra led.asm and hit the Enter key.  Note the avra program name must be entered in lower-case.
If all went well, your source code should have compiled with "no errors" and "1 warnings".  The warning is telling you that
the avra program could not find the ".DEVICE definition" which is part of the file.
To fix this, download the include file and place the file into your led-code subdirectory.  Instruction are in the addendum.
Let's compile the source code again by executing the last command in the terminal program; hit the UP-Arrow on your keyboard.
This should display the last command on the Terminal command line; now hit the Enter key.  With the include file placed into
the led-code sub-directory, the source code should compile with no errors and no warnings.  So what did we accomplish?
Not much except that we verified the assembler is working and that we are now ready to write some serious code.

We are now going to "learn by doing" a simple example that employs the most important functions of the ARV microcontroller, the INTERRUPTS.

From Wikipedia: "In computing, an interrupt is an asynchronous signal indicating the need for attention
or a synchronous event in software indicating the need for a change in execution.
A hardware interrupt causes the processor to save its state of execution and begin execution of an interrupt handler."

In order to make an LED blink for an exact time period, we must first have an accurate clock source.
Our project uses a 20.48 MHz crystal that can be trimmed to oscillate at exactly 24,480,000 Hertz.
The 24.48 MHz oscillator signal is used as the heartbeat of the ATtiny2313 CPU.
Inside the CPU is a divide by 1024 circuit that can be activated through software instructions that produces a 50 uSecond time period.
Also inside the CPU is an 8 bit timer overflo interrupt that we activate by initializing(presetting) the 8 bit timer to (256-200) or 56.
After the timer counts up to 256 (a total of 200 times 50 uS), it triggers the timer0 overflow interrupt,
thus the timer0 overflow interrupt is activated every 10 milliseconds.
Now all we need to do is to turn on the LED after we count to 900 mS and turn off the LED after another 100 mS, giving us a 10 LED percent duty cycle.
Note that the firmware included in the FC Kit causes the LED to bling ON for 1 sec and OFF for 1 sec. The code below was changed after the chips were programmed.
Download the led2.asm code into the led-code sub-directory per instructions in the addendum.  Here is the code with extensive comments:
;led2.asm - demo program to blink an LED once every second
;open Terminal and move to active directory : cd Desktop/qq-fc-project/led-code/
;to assemble code in the directory that holds led2.asm : avra led2.asm
;to write fuse bits : avrdude -P usb -p t2313 -c avrispv2 -U lfuse:w:0xFF:m
;to upload code to the target : avrdude -P usb -p t2313 -c avrispv2 -U led2.hex

.include "" ; include file that defines the variables in ALL CAPS

.equ Led = PIND4 ; The Led is connected to PORTD PIN4
.equ LedMask = 0b00010000 ; Bit 4 ($10) mask for the Led

.def temp1 =r16 ; work register
.def temp2 =r17 ; work register
.def delay =r18 ; register that holds the timeout delay value

.cseg ;following is code and goes into ROM space
.org $000 ; this is the start of the Interrupt Vector table
  rjmp Reset ; on power-up, program starts to execute at location $000 
  reti ; IRQ0 - not used
  reti ; IRQ1 - not used
  reti ; Timer1 Capture - not used
  reti ; Timer1 Compare - not used
  reti ; Timer1 Overflow - not used
  rjmp Tim0_Ovf ; Timer0 Overflow Interrupt Vector
  reti ; UART Receive - not used
  reti ; UART empty - not used
  reti ; UART Transmit - not used
  reti ; Analog comparator - not used
  reti ; Pin Change - not used
  reti ; Timer1 Compare B - not used
  reti ; Timer0 Compare A - not used
  reti ; Timer0 Compare B - not used
  reti ; USI Start - not used
  reti ; USI Overflow - not used
  reti ; EEPROM Ready - not used
  reti ; Watchdog Overflow - not used

Reset: ;Reset Interrupt Vector points here. 
  ldi temp1,low(RAMEND)
  out SPL,temp1  ; Set stack pointer to last RAM loc
  ldi temp1,high(RAMEND)
  out SPH,temp1  ; Set stack pointer, 12 bits wide

  ldi temp1,LedMask
  out DDRD,temp1  ;make the Led pin an output

  ldi temp1,0b00000101 ;set timer0 prescaler to 1024
  out TCCR0,temp1  ;using 20.48 XTAL = 50uS
  ldi temp1,0b00000010
  out TIMSK,temp1  ;enable TIMER0 overflow interrupts
  ldi delay,10 ; init the delay to 100 mSec
  sei ;enable all Interrupts (global interrupt enable)

Menu: ; main program - loops here forever
  rjmp Menu

  ldi temp1,256-200 ;10 ms using a 20.48 xtal
  out TCNT0,temp1 ;set for next overflow
  dec delay ; decrement the delay counter
  brne Tim0_Ovf_Exit ; if it is NOT ZERO, then exit the interrupt routine
  in temp1,PORTD ; get the current Led Port Value
  ldi temp2,LedMask ; load the Led bit
  eor temp1,temp2 ; exclusive or the Led bitmask with the PORTD value
  brne Tim0_Ovf_1 ; if it is NOT ZERO, then branch and turn off Led
  ldi delay,10 ; reset the delay counter to 100 mSec
  cbi PORTD,Led ; turn Led on
  rjmp Tim0_Ovf_Exit ; exit the Interrupt routine
ldi delay,90 ; reset the delay counter to 900 mSec
  sbi PORTD,Led ; turn Led off
  reti ; return from Interrupt
To reiterate the above code in plain English:  First we include the source file that contains many definitions.
Next, we define the labels and register variables that we will use.  Then we fill in the Interrupt Vector table.
followed by the main code starting with "Reset:" where we place the stack pointer at the top of static memory, aka RAM.
We then enable one of the I/O lines (PORTD Pin-4) to be an output pin to control the LED.  
Next we set the prescaler to divide the 20.48 MHz clock by 1024 to produce 50 uSec timer counts in the CPU.
We then enable the TIMER0 overflow interrupt and then initialize the delay timer to 100 mSec and then we
enable the global interrupt flag as a master interrupt enable switch for the system.
After that, the CPU runs in a tight loop doing a relative jump to "Menu:" forever.
The first time that the TIMER0 overflow interrupt is activated, the "Tim0_Ovf:" interrupt routine sets the timeout
for the next interrupt by loading the "TCNT0" register with 56.
When TIMER0, an 8 bit timer, counts up to 256 (every 50 uSec.), we get another interrupt.
Next in the interrupt code, we decrement the "delay" variable register.
If the register has counter down to zero (0) then we need to test the status of the LED
and determine if we need to turn it on or off, else we exit the interrup routine.
The Led OFF time and the Led ON time are both independently controlled.

You may disconnect the LCD from the target hardware for this demonstration; the LCD is not used.
To upload the machine code file (led2.hex) to the target hardware, we need interface hardware and software.
The hardware recommended is the AVRISP-II referenced in Addendum Part-1.  There is also a software fix
pertaining to the AVRISP in the Addendum Part-2.  See the reference at the end of this document under notes.
To upload the machine code to the target, we also need a "PROGRAMMER" or software that talks between the AVRISP and the PC, 
We will be using the free program called AVRDUDE, referenced in Addendum Part-1.
Connect the AVRISP module between a PC USB port and the 6 pin header on the target hardware.
Make sure that the 6 pin header connection has the red wire toward the edge of the target PCB or away from the CPU.
Start the Terminal program and move to the working directory by running the command: "cd Desktop/qq-fc-project/led-code/"
Make sure the response is "username@username-desktop ~/Desktop/qq-fc-project/led-code $", verifying that you are in the correct directory.
Next, apply power to the target hardware. Then issue the commmand: "avrdude -P usb -p t2313 -c avrispv2 -U led2.hex" and take a deep breath.
If all is working correctly, this should upload the led2.hex file machine code to the target hardware and start executing the code, once loaded.
The Led should start to blink, ON for 100 mSec and OFF for 900 mSec.
If you can not get the hardware to blink the Led, review the preceeding instruction and find the fault.
Should you get stuck, email me me. My email is listed at the end of this article.

OK...congratulations are in order (I think), assuming that you managed to control the Led.
You may wish to "play" with the code and try different ON/OFF times for the Led.

Connected the LCD to the Freq Counter PCB (target hardware) for this exercise.
If you have not done so, download the frequency counter source code "fc.asm" per instructions in Addendum Part-3.
Place the source code into a directory (folder) called "fc-code" under the "qq-fc-project" folder on your Desktop.
In a new Terminal window, execute "cd Desktop/qq-fc-project/fc-code/" and compile the source code by running the
avra program in a Terminal window, "avra fc.asm".  Apply power to the target board and then upload the machine file
to the target by executing "avrdude -P usb -p t2313 -c avrispv2 -U fc.hex".
Your display should look something like this:

Pix source

Open the source file fc.asm in GEDIT as a reference to the following review.  To best follow this discussion,
you should enable the line numbers in GEDIT.  In GEDIT, click on "Edit", "Preferences" and enable Line Numbers.
then Close the Preferences window.
The Freq Counter accuracy display resolution is dependent upon two parameters: The gate time  and the prescaler.
The prescaler is set in hardware by selecting the desired jumper on the 74HC4040 binary counter.  For this 
project, we selected divide by 4. Since the maximum frequency that the basic CPU can measure is about 9 MHz,
the divide by 4 circuit enables us to measure up to about 36 MHz.  If we set the gate time (measurement period)
to one second, then frequency measurements would have a four hertz display resolution, because when the CPU counts
the pulses for one second, we need to multiply the count by 4 to display the correct frequency.  A gate time of
one second is a bit long for most frequency counter applications so we use a gate time of one half second.
This results in doubling our display resolution to 8 Hertz because we need to multiply the count result by 2.
The gate time and prescaler could be controlled with jumpers of switches, but for simplicity we will stick
to a prescale of divide by 4 and a gate time of one half second. The ASM code from lines 48 thru 66 define 
the FC gate time and prescaler, hence the display resolution.
Lines 68-71 define if you want a frequency offset, but this code is not fully implemented.  There will be
an update in the Addendum Part-3.
The rest of the code is split between frequency counting and the LCD display.  The LCD can be controlled
using either 4 data bits or 8 data bits.  We use 4 data bits, so every command or character sent to the 
display requires two writes to the LCD, one nibble (4 bits) at a time.  Before we send data to the LCD,
we read the BUSY bit from the LCD.  This is the fasted way to control the LCD.  Many LCD interfaces
do not read the BUSY bit, but rely on worst case timing requirements to control the LCD.
Frequency counting is controlled by the Timer-0 interrupt routine (TIM0_OVF:) at line 476.
The timer is configured to interrupt every 5 milliseconds and accumulates the number of pulses counted
on PIN-9 (PD5) of the CPU.  The 16 bit count is captured in lines 499 & 500 and added to accumulator
registers in lines 502 to 525.  After 100 interrupts (100 * 5 mSec or half a second) the result
is converted to human readable characters (lines 537-580) and displayed on the LCD (lines 354-418).
Line 599 is where you can include your callsign.
Change "URCALL" to your callsign.  Make sure that you have exactly 16 characters inside the quotes, 
else you may have some display problems.  All data is broken into 16 bit boundaries
so we need an even number of characters for the text display storage.  Note the zeros at the end of the text line.
Zero is interpreted as the text string terminating character.

NEXT ARTICLE:  Designing and Producing the PCB - Printed Circuit Board

Dieter (Diz) Gentzow, W8DIZ, aka WB8QYY before April 2000, has been a licensed ham since 1973.  
Past employment include Honeywell as an Industrial Sales & Systems Engineer,
AC Nielson, Electrical Engineer designing black boxes that monitor TV viewing habits and 
a handful of other hardware and/or software jobs.  
Currently semi-retired, living in sunny Palm Harbor, Florida.  
You can contact Diz, W8DIZ via eMail at and SKYPE ID = "w8diz_".

Addendums and/or corrections to this project are available at
Frequency Counter Development Kits are available from
Part-1 of this project is available at
Part-2 of this project is available at