Path: Home => AVR-EN => Applications => R/2R-DAC => Sine generators => Variable sine m324
DAC8 Tutorial for learning avr assembler language of
AVR-single-chip-processors AT90S, ATtiny, ATmega, ATxmega

of ATMEL using practical examples.

Variable frequency sine generator m324

Variable frequency sine wave generator with ATmega324

If you need a variable frequency sine wave generator you can build that. It has the following properties:
  1. 1x8 LCD for frequency display in Hz,
  2. two potentiometers to regulate the resolution and the delay separately,
  3. active key to update the adjusted parameters, no automatic adjustment,
  4. 8-bit R/2R network,
  5. selectible and configurable crystal,
  6. either 256, 128, 64, 32, 16 or 8 resolution steps,
  7. large 16-bit delay counter, usable for very low frequencies,
  8. very fast algorithm (minimum cycle length = 17 cycles).


Schematic of the sine generator with ATmega324 This is what it needs.

The generator has The ATmega324 was selected because it is one of the rare AVR types that have
  1. two complete ports with 8 bits each available, one for the LCD's bi-directional data bus and one for the R/2R network, and
  2. the option to be clocked with an external crystal, and
  3. it has ADC channels for measuring the potentiometers, and
  4. it provides an external INT that can be configured to interrupt on falling edges solely (INT2).
The PA sub-type of the ATmega324 was selected because it is cheaper than other sub-types, at least at my dealer.

The LC network for filtering the higher rectangle frequencies produced by the R/2R network depends from the frequency range that the generator shall provide. If you want to operate the generator with smaller resolutions, such as 8 or 16, to achieve higher frequencies or a finer resolution, the clipping frequency has to be smaller than your desired highest frequency, muliplied with the smallest resolution.

I built my one with L=3.3mH and C=150nF, the output capacitor is 470nF.

PCB layout

Copperside of the sinegenerator m324pa Component side of the sinegenerator m324pa This is the PCB layout. Shown are half size pictures, clicking on these displays the originals (download with right-click and Save file as ...).

Red dots in the component picture are 0.8mm holes, violett ones are 1.0mm. The total size is 80-by-100mm (half Euro size). Note that I used a coil with 22µH instead of the 10 that is listed in the schematic.

If you have Linux, you can work with tgif on the originals. The copper side and all components in color layers are here, the component plan is here.

Note that earlier versions of the schematic had the LCD control lines on different port-pins.


When starting the controller up, it generates the selected frequency that is to be defined in the software. If the key on PB2 is pressed, the two variable resistors attached to ADC0 and ADC1 determine

Start-up frequency

The start-up frequency has to be defined in the source code. Selecting the start-up frequency, you can either
  1. define the desired frequency (in mHz) and leave the selection of the resolution and the delay constant for the Assembler, or
  2. the frequency and the resolution (256/128/64/32/16/8), for which the Assembler calculates the delay constant, or
  3. the frequency and the delay constant (0 to 65535, 0 is 65536!), for which the Assembler determines the necessary resolution, or
  4. define the resolution and the delay constant.
This means entering the desired combination in the header of the assembler source file, in the section Adjustable constants. For all cases the Assembler determines the actual effective frequency (in mHz), so you can decide whether the accuracy is good enough. Just inspect the frequency at the end of the assembler listing in the section Symbol list, if you use gavrasm or avr_sim for assembling, other assemblers unfortunately find this kind of information unnecessary, so you'll have to use hyper-intelligent work-arounds to get this type of information.

Note that the names of the constants cFreq, cFrequency, cDel, cDelay, cRes and cResol have been chosen willingly, even though they often have the same value in the short and in the longer constant name. The longer names keep what the user actually has defined, while cRes and cDel are the results of calculations, if that is necessary. As in a 2-pass-Assembler the user input will be lost if those names would be used in calculations, I decided to introduce this convention in this section.

Changing the frequency with the potentiometers

If the user presses the key, the sine loop is immediately stopped and the voltages on the two input pins ADC0 and ADC1 are measured. The voltage on ADC0 linearly determines the resolution: the AD result is divided into six sections, by first dividing the result by 4 and then continuiesly subtracting 43 and shifting an adder left, as long as no carry happens. The adder starts with 1.

The conversion of the ADC1 result to delay values is a little bit different. The AD result is added to the address of the DelayTable: and the delay values are read from this table.

Frequency vs. poti angle This shows the frequency range in the base case (8 bit R/2R resolution). The frequencies generated are derinved from a table in the source code. By manipulating that table any characteristic can be implemented.

The new values come immediately in effect, if the debouncing protection time of 250 ms has been absolved without additional external interrupts.

Frequency display

LCD pin assignments This is the small LCD. The pins are shown from the upper (display) side. The pins are in two rows and in distances of 2.54 mm per pin, the PCB layout below is designed for that format.

As I have foreseen a very small LCD with 8 characters on one single line, calculation and display of the frequency is a little bit tricky.

Calculating the frequency

The calculation is as follows. First, the clock frequency, multiplied with 1,000 (for getting the result in mHz) and divided by 256 is placed in a 64-bit register area. The adder, that determines resolution, is shifted to the right and, as long as no carry occurs, multiplies the 64-bit register area repeatedly by two. The divident is then complete, its lower 32 bits hold the divident and its upper 32 bits are for dividing.

The delay constant is written to a 32-bit wide register area. It is multiplied by 4 and 13 is added. This will be the divisor.

The 64-bit divident representing the multiplied clock frequency is then divided by the 32-bit divisor to yield a 32-bit result. The result is the frequency in mHz.

The 32-bit integer is then converted to decimal ASCII, with the decimal dot behind the Hz. This conversion uses a space in SRAM, examples look like this:
0001234567.123 ; All three decimal digits are non-zero
0001234567.120 ; The last decimal digit is zero
0001234567.100 ; The last two decimal digits are zero
0001234567.000 ; All three decimal digits are zero
0123456789.123 ; The number is larger than 99,999

Formatting the displayed frequency

In the first step all zero digits in the mHz are removed. That means that the second number in the above list will be shorter by one digit, the third by two digits. If all mHz digits are zero, those are removed together with the preceding decimal dot. Removal is done by shifting all digits one, two or four positions to the right and inserting ASCII zeroes to the left.

Then all preceding ASCII zeroes are replaced with blanks. If the first non-blanked digit is too far to the left (the number has more than eight digits) digits of the mHz behind the decimal dot are removed (removed digits from 5 and above round the preceeding number up).

The eight digits are then finally written to the LCD.

Testing the hardware

Test software For diverse purposes I have developed a software which allows to separately test the components of the sine generator. The source code for that is available here. If the LCD shall also be tested, the include routines in have to be placed to the same folder.

in the section Debugging tests of the source code you can determine which tests should be performed (the respective test is set to Yes, only one test procedure can be active, none or more than one force errors when assembling):
  1. testR2R: On the R2R port the number is placed that is determined with the parameter testR2ROut.
  2. testSine: The two parameters testSineRes and testSineDly are used to generate a sine on the R/2R port. Using 128 for testSineRes and 28 for testSineDly brings a perfect 1.000 kHz sine to the R/2R network.
  3. testKey: The attached key is read. If open, the R/2R voltages is set to 3.0 Volt, when closed it is 2.0 Volt.
  4. testAdc0, testAdc1: This measures the voltage on the two ADC inputs and adjusts the R/2R output to the measured voltages.
  5. testPortC: The port C (data port to the LCD) is made output and all pins are pulled high.
  6. testLcd: The LCD is initialized and the text "SineM324" is displayed.
The version includes further debug switches that allow to debug the source code with the simulator avr_sim. For the final version set all those switches to No.

A hint: When I tested the hardware, the LCD did not work at all. After many hours of searching I found the reason: the pins of the LCD are very small, and the socket expected larger ones. So at least one of the 14 pins had no contact. I replaced the plug with a better one, and it worked. Never use unsuited sockets, rather transfer them to the trash can.

The software for the sine generator

The software is, in assembler source format, available here. Download it, edit the respective parameters in the section Adjustable constants, download the and copy it into the same folder. By either defining cOpAmp741 or cOpAmpCA3140 select the curve form. If you use a different opamp, you'll have to use the sheet SineTable in LibreOffice Calc file here to make your own sine table and insert it and the end of the source code, adding it with .ifdef cMyOwnOpAmp and .endif. Place the activator with .equ cMyOwnOpAmp = Yes to the adjustment section and disable the other two with semicolons.

Fuses of the ATmega324 Then re-assemble and burn the hex code to the flash's memory. Do not forget to change the following fuses:
  1. Disable JTAG, otherwise the LCD will not work properly.
  2. Disable CLKDIV8, otherwise your sine will be too slow.
  3. Select an external crystal as clock source, otherwise it will be too slow.

Resolution = 256, Delay = 1 This is what the generator produces with a resolution of 256 and a delay of 1: a sine wave with 3.676 kHz.

Resolution = 256, Frequency = 1000 Hz This is what results from a resolution of 256 and a desired frequency of 1,000 Hz. Unfortunately the integer math produces a frequency of 961.538 Hz as the nearest possible. Not very accurate.

Resolution = 8, Frequency = 1000 Hz This is the result if you reduce the resolution down to 8. The frequency is nearer to 1,000 Hz, but the sine looks rather angled.

To the top of that page

©2020 by