Update: Software for DDS-controlled QRP SSB handheld transceiver (AD9835, ATmega328P)

Recently I had a lot of new ideas for my QRP SSB handheld radio. When starting to program the first changes to my old software, I found out that the ATmega8 had become too small concerning flash size. The program became too large for 8kb memory size. The only thing I could do was to use another MCU that was PIN-compatible and did not require lots of code changes.

ATMEL has got the ATmega48, ATmega88, ATmega168, ATmega328(P)-series on the market. These are also, like the ATmega8, controllers packed in 28-pin-DIL-package. Above all, they are PIN-compatibel to the ATmega8. I decided to use an ATmega328 to update my radio. This MCU provides 32kB of FLASH memory which is more than enough for my application.

Some slight code alternations also had to be done, because some register names have been changed and the SLEEP-method had to be slightly revised. What I deeply regretted was the fact that I couldn’t use my old YAAP-ISP software anymore because it’s much too old and does not support this controller. But AVRDUDE works fine. These were the main obstacles that I had to deal with. After 2 or 3 hours of programming the software job was done. The ATmega328P does a perfect job in my transceiver. Full code will be presented by the end of this article.

Important: The ATmega328P reaches the customer with the factory setting of an 8MHz rate for its main clock. But there is a fuse that divides this clock rate by the factor of 8. This one is activated by factory also. So, your MCU performs like an old ATmega8 with factory default set if this fuse is not changed. Goto this website and find out the correct fuse settings for you MCU. AVRDUDE needs this command line to achieve the correct settings:

-U lfuse:w:0xe2:m -U hfuse:w:0xd9:m -U efuse:w:0xff:m

If you want to use my software for your own project, I will give you a brief description of how the software works and what features it has got (so far). So, first, here’s the completely revised schematic of the transceiver. You are particulary recommended to have a closer look at the AD9835/ATmega328 VFO unit!

SSB QRP handheld tranceiver for 14MHz/20mtr. Rev. 5 (C) Peter Rachow (DK7IH)
SSB QRP handheld tranceiver for 14MHz/20mtr. Rev. 5 (C) Peter Rachow (DK7IH)

Major modifcations: I now use a debouncing circuit for the 5 switches that the operator uses to control the radio. Some MCU ports have been changed compared with older versions of the radio. Some switches have got multiple functions now depending on which part of the software/menu you are currently using.

Watch this picture:

Controls used for SSB QRP handheld transceiver
Controls used for SSB QRP handheld transceiver

The controls are:

VFO: You can now select from 12 VFOs. Pressing this button switches to the next VFO in rising order. Once you’ve reached the last VFO the run starts with VFOA again.

FUNC: Leads you to a set of submenus where you can

  • store a frequency to the current VFO
  • SCAN the band starting from the current frequency either up or down (tuning step ist the last step that you used when tuning)
  • SCAN the VFOs with possibiliy to skip any VFO during the scan isperformed
  • Put the MCU into SLEEP-mode to reduce noise to a maximum

STEP: Sets the tuning step (1, 5, 10, 50, 100, 500, 1000, 5000 Hz). 10Hz tuning step is restored 1 second after the last tuning is finished.

Tune UP/DOWN lets you alter the frequency according to current tuning step.

Let’s go into the FUNC menu. After pressing this button you are asked if you want to store the current frequency to the current VFO:

Saving a frequency into a VFO storage place

“STOR A?” means that you can save the frequency displayed to VFOA. Just press the “STEP (Y)” button. If you don’t want to to do this, press “FUNC (N)” which is read as “NO”.

Scanning the band

Next question is “SCN QRG?”. “SCN” abbrieviates “SCAN”. If you press now either the UP or DOWN button the scan starts. Tuning step applied is the last step that you’ve set before you entered this function. The reset that is automatically performed to 10Hz tuning shift will not affect the tuning step since it has not been manually set. If you don’t want to scan, just press “FUNC (N)”.

Scanning the VFOs

Afterwards you can go to a scanning procedure thru the VFOs. Answering “SCN VFO?” with “yes” will scan each VFO and leave the receiving frequency set for 4 seconds until the next VFO is switched. If you want a certain VFO to be skipped (e. g. if there is noise or an unwanted station), just press the”VFO”-button while the VFO is active. In the next round this VFO will be ignored.

Working SPLIT-Mode

The software allows you to choose different frequencies for transmit and receive. In other words: You can work split with this radio.

The receiving frequency alway is that of the VFO currently used. The transmit frequency can be selected with this function.

“Split?” must be answered with the “STEP (Y)”-button  if you wish to activate this option. In the next step you must select a VFO that you want to use as transmit frequency. Use UP or DOWN-key to alter the selected VFO. Confirm your choice with the “STEP (Y)”-button. If you wish to abort, press “FUNC (N)”.

If you want to switch off SPLIT-mode enter the FUNC-Menu again. Select “Split” and then, when asked to select the TX VFO, press “FUNC (N)”.

Ultimate noise reduction: Putting the MCU to sleep

Last question is if you want to put the MCU to SLEEPMODE. Even if the MCU does not produce any disturbing noise (at least in my rig) it can be uesful to switch the off completely. Answer the respective question with”STEP (Y)” the MCU is now switched off. Pressing either UP or DOWN tuning button will wake up the MCU again.

So, here’s the latest version of my code. See you soon and thanks for reading. 73 de Peter (DK7IH)

/*****************************************************************/
/*     DDS for QRP SSB Transceiver w. ATMega328 und AD9835       */
/*  ************************************************************ */
/*  MUC:              ATMEL AVR ATmega328, 8 MHz                 */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Author:           Peter Rachow (DK7IH)                       */
/*  Last change:      17 AUG 2015                                */
/*****************************************************************/
/* PORT usage */
//
//OUTPUT
//
// LCD
// RS      = PB6
// E       = PB7
// D4...D7 = PD4..PD7
//
// SPI to AD9835
// FSYNC    = PB0
// SDATA:   = PB1
// SCLK:    = PB2
//
//INPUTS
//
//Vcc-Monitor:    PINC0 (analogue input)
//TX-line monitor PINC2 (detects when radio is on air)
//VFO-Select:     PINC3 push button
//FUNC:           PINC4 push button
//TUN STEP:       PINC5 push button
//TUNE UP:        PIND2 push button
//TUNE DOWN:      PIND3 push button
//
//
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#define F_CPU 8000000
#define MAXSHIFT 7
#define MAXVFO 12
//AD9835 Modes
#define AD9835INIT 1 //AD9835 will be fully initialized with frequency value
// (sets AD9835 into sleep mode for some ms)
#define AD9835UPDATE 0 //AD9835 frequency will be updated because frequency
//has been changed by user
//Timer 1
unsigned long runsecs = 0;
int main(void);
/*******************/
//       SPI
/*******************/
//Port usage
//FSYNC: PB0 (1)
//SCLK: PB1 (2)
//SDATA: PB2 (4)
void spi_start(void);
void spi_send_bit(int);
void spi_send_byte(unsigned int);
void spi_send_word(unsigned int);
void spi_stop(void);
void set_frequency(unsigned long, unsigned long, int);
char *freq_shift_str[MAXSHIFT + 1] = {"  1", "  5", " 10", " 50", "100", "500", " 1k", " 5k"};
/***************/
/* LCD-Display */
/***************/
//Data: PD4..PD7
//E: PC0
//RS: PC1
#define LCD_INST 0x00
#define LCD_DATA 0x01
void lcd_write(char, unsigned char);
void set_rs(char);
void set_e(char);
void lcd_init(void);
void lcd_cls(void);
void lcd_putchar(int, int, unsigned char);
void lcd_putstring(int, int, char*);
int lcd_putnumber(int, int, long, int, int, char, char);
void lcd_display_test(void);
void show_freq(long);
void show_step(int);
void show_vfo(int);
//****************
//      ADC
//****************
#define ADWAITSTATE 3
int get_adc(int);
//EEPROM
void savefreq(int, unsigned long);
unsigned long loadfreq(int vfo_num);
//SLEEPMODE
unsigned long idlesecs = 0;
void save_cur_vfo(int);
int load_cur_vfo(void);
void save_cur_vfo(int vfo_num)
{
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)511, vfo_num);
}
int load_cur_vfo(void)
{
    int lvfo = eeprom_read_byte((uint8_t*)511);
    if(lvfo < 0 || lvfo > MAXVFO)
    {
        return 0;
    }
    else
    {
        return lvfo;
    }
}
void savefreq(int vfo_num, unsigned long num)
{
    unsigned int hiword, loword;
    unsigned char hmsb, lmsb, hlsb, llsb;
    int start_adr = vfo_num * 4;
    cli();
    hiword = num / 65536;
    loword = num - hiword * 65536;
    hmsb = hiword / 256;
    hlsb = hiword - hmsb * 256;
    lmsb = loword / 256;
    llsb = loword - lmsb * 256;
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr, hmsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 1, hlsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 2, lmsb);
    while(!eeprom_is_ready());
    eeprom_write_byte((uint8_t*)start_adr + 3, llsb);
    sei();
}
unsigned long loadfreq(int vfo_num)
{
    unsigned long num2;
    unsigned char hmsb, lmsb, hlsb, llsb;
    int start_adr = vfo_num * 4;
    cli();
    hmsb = eeprom_read_byte((uint8_t*)start_adr);
    hlsb = eeprom_read_byte((uint8_t*)start_adr + 1);
    lmsb = eeprom_read_byte((uint8_t*)start_adr + 2);
    llsb = eeprom_read_byte((uint8_t*)start_adr + 3);
    num2 = (unsigned long) 16777216 * hmsb + 65536 * hlsb + (unsigned int) 256 * lmsb + llsb;
    if(num2 >= 13900000 && num2 <= 14380000)
    {
        return num2;
    }
    else
    {
        return 1415000;
    }
    sei();
}
//************
//    SPI
//************
void spi_start(void)
{
    //FSYNC lo
    PORTB &= ~(1); // Bit PB0 = 0
}
void spi_stop(void)
{
    //FSYNC hi
    PORTB |= 1; // Bit PB0 = 1
}
void spi_send_bit(int sbit)
{
    //Bit set or erase
    if(sbit)
    {
        PORTB |= 2;  //SDATA Bit PB1 set
    }
    else
    {
        PORTB &= ~(2);  //SDATA Bit PB1 erase
    }
    //SCLK hi
    PORTB |= 4;  //Bit PB2 set
    //SCLK lo
    PORTB &= ~(4);  //Bit PB2 erase
}
void spi_send_byte(unsigned int sbyte)
{
    int t1, x = 128;
    for(t1 = 0; t1 < 8; t1++)
    {
        spi_send_bit(sbyte & x);
        x = x >> 1;
    }
    PORTB |= 2;  //PB1 SDATA hi
}
void spi_send_word(unsigned int sbyte)
{
    unsigned int t1, x = 32768;
    for(t1 = 0; t1 < 16; t1++)
    {
        spi_send_bit(sbyte & x);
        x = x >> 1;
    }
    PORTB |= 2; //PB1 SDATA hi
}
/**************************************/
/* LCD routines                       */
/**************************************/
//Port usage at MUC:
//LCD-Data: PD0..PD4
//E: PC0
//RS: PC1
/* Send one byte to LCD */
void lcd_write(char lcdmode, unsigned char value)
{
    int x = 16, t1;
    set_rs(lcdmode);    // RS=0 => command, RS=1 => character
    _delay_ms(3);
    set_e(1);
    /* Hi nibble */
    for(t1 = 0; t1 < 4; t1++)
    {
        if(value & x)
        {
            PORTD |= x;              // Set Bit
        }
        else
        {
            PORTD &= ~(x);          // Erase Bit
        }
        x *= 2;
    }
    set_e(0);
    x = 16;
    set_e(1);
    /* Lo nibble */
    for(t1 = 0; t1 < 4; t1++)
    {
        if((value & 0x0F) * 16 & x)
        {
            PORTD |= x;              // Set bit
        }
        else
        {
            PORTD &= ~(x);          // erase bit
        }
        x *= 2;
    }
    set_e(0);
}
/* RS set */
void set_rs(char status) /* PORT PB6  */
{
    if(status)
    {
        PORTB |= 64;
    }
    else
    {
        PORTB &= ~(64);
    }
}
/* E set */
void set_e(char status)  /* PORT PB7*/
{
    if(status)
    {
        PORTB |= 128;
    }
    else
    {
        PORTB &= ~(128);
    }
}
//Send 1 char to LCD
void lcd_putchar(int row, int col, unsigned char ch)
{
    lcd_write(LCD_INST, col + 128 + row * 0x40);
    lcd_write(LCD_DATA, ch);
}
//Send string to LCD
void lcd_putstring(int row, int col, char *s)
{
    unsigned char t1;
    for(t1 = col; *(s); t1++)
    {
        lcd_putchar(row, t1, *(s++));
    }
}
//Clear LCD
void lcd_cls(void)
{
    lcd_write(LCD_INST, 1);
}
/* LCD-Display init */
void lcd_init(void)
{
    // Basic settings: 2 lines, 5x7 matrix, 4 bit data bus
    lcd_write(LCD_INST, 40);
    // Display on, Cursor off, Blink off
    lcd_write(LCD_INST, 12);
    // Entrymode !cursoincrease + !displayshifted
    lcd_write(LCD_INST, 4);
    lcd_cls();
}
//Write number with given amount on digits to LCD
//set decimal where needed (-1 if not needed)
int lcd_putnumber(int row, int col, long num, int digits, int dec, char orientation, char showplussign)
{
    char cl = col, minusflag = 0;
    unsigned char cdigit[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, digitcnt = 0;
    long t1, t2, n = num, r, x = 1;
    if(num < 0)
    {
        minusflag = 1;
        n *= -1;
    }
    if(digits == -1)
    {
        for(t1 = 1; t1 < 10 && (n / x); t1++)
        {
            x *= 10;
        }
        digits = t1 - 1;
    }
    if(!digits)
    {
        digits = 1;
    }
    for(t1 = digits - 1; t1 >= 0; t1--)
    {
        x = 1;
        for(t2 = 0; t2 < t1; t2++)
        {
            x *= 10;
        }
        r = n / x;
        cdigit[digitcnt++] = r + 48;
        if(t1 == dec)
        {
            cdigit[digitcnt++] = 46;
        }
        n -= r * x;
    }
    digitcnt--;
    t1 = 0;
    /* Output of number to be displayed on LCD */
    switch(orientation)
    {
        case 'l':  //orientiation left
        cl = col;
        if(minusflag)
        {
            lcd_putchar(row, cl++, '-');
            digitcnt++;
        }
        else
        {
            if(showplussign)
            {
                lcd_putchar(row, cl++, '+');
                digitcnt++;
            }
        }
        while(cl <= col + digitcnt)
        lcd_putchar(row, cl++, cdigit[t1++]);
        break;
        case 'r':  //orientiation right
        t1 = digitcnt;
        for(cl = col; t1 >= 0; cl--)
        {
            lcd_putchar(row, cl, cdigit[t1--]);
        }
        if(minusflag)
        {
            lcd_putchar(row, --cl, '-');
        }
    }
    if(dec == -1)
    {
        return digits;
    }
    else
    {
        return digits + 1;
    }
}
//Display frequency line 0 left
void show_freq(long fr)
{
    lcd_putnumber(0, 0, fr / 10, -1, 5, 'l', 0);
}
//Display tuning step line 1 right
void show_step(int st)
{
    lcd_putstring(1, 5, freq_shift_str[st]);
}
//Display VFO info, line 1 left
void show_vfo(int vfo_num)
{
    lcd_putstring(1, 0, "VFO");
    lcd_putchar(1, 3, vfo_num + 65);
}
//Set AD9835 to desired frequency
//2 Modes:
//Full initialization
//or
//simple readadjustment of freq (to be preferred beause of avoiding
//
//Full init sets the AD9835 to short sleep thus generating a short interruption
//in receiving/transmitting
void set_frequency(unsigned long freq, unsigned long ifrequency, int ad9835fullinit)
{
    unsigned long fxtal = 50000450;  //fCrystal in MHz
    double fword0;
    unsigned long fword1;
    unsigned long hiword, loword;
    unsigned char hmsb, lmsb, hlsb, llsb;
    fword0 = (double) (freq - ifrequency) / fxtal;
    fword1 = (unsigned long) (fword0 * 0xFFFFFFFF);
    //Split 32 Bit in 2 * 16 Bit
    hiword = (unsigned long) fword1 / 65536;
    loword = (unsigned long) fword1 - hiword * 65536;
    //Slipt 1st 16 Bit to 2 * 8 Bit
    hmsb = hiword / 256;
    lmsb = hiword - hmsb * 256;
    //Split 2nd 16 Bit to 2 * 8 Bit
    hlsb = loword / 256;
    llsb = loword - hlsb * 256;
    if(ad9835fullinit)
    {
        //init, set AD9835 to sleepmode
        spi_start();
        spi_send_word(0xF800);
        spi_stop();
    }
    //Send frequency (double) word to DDS
    spi_start();
    spi_send_word(0x33 * 0x100 + hmsb);
    spi_stop();
    spi_start();
    spi_send_word(0x22 * 0x100 + lmsb);
    spi_stop();
    spi_start();
    spi_send_word(0x31 * 0x100 + hlsb);
    spi_stop();
    spi_start();
    spi_send_word(0x20 * 0x100 + llsb);
    spi_stop();
    //End of sequence
    spi_start();
    if(ad9835fullinit)
    {
        //AD9835 wake up from sleep
        spi_send_word(0xC000);
    }
    else
    {
        //AD9835 freq data update, no full init
        spi_send_word(0x8000);
    }
    spi_stop();
    //Display frequency
    show_freq(freq);
}
//Interrupt routines
ISR(TIMER1_OVF_vect)          // Timer1 overflow
{
    runsecs++;
    TCNT1 = 57724;
}
ISR(INT0_vect) //INT0 for ATmega being woke up from sleep, can be left
{               //empty but must exist!
}
ISR(INT1_vect)
{
}
//***************************************************
//                      ADC
//***************************************************
//Read a value from ADC
int get_adc(int adcmode)
{
    int adc_val = 0;
    ADMUX = (ADMUX &~(0x1F)) | (adcmode & 0x1F);     // Activate channel "adcmode"
    _delay_ms(ADWAITSTATE);
    ADCSRA |= (1<<ADSC);
    _delay_ms(ADWAITSTATE);
    adc_val = ADCL;
    adc_val += ADCH * 256;
    while(ADCSRA & (1<<ADSC));
    return adc_val;
}
int main()
{
    unsigned long freq1, freq2;  //Frequency in Hz to be generated
    unsigned long vfo[MAXVFO + 1] = {}; //12 VFO-frequencies
    char skip_vfo[MAXVFO + 1];
    unsigned int  cur_freq_shift = 2; //Tuning step in Hz
    unsigned int freq_shift[MAXSHIFT + 1] = {1, 5, 10, 50, 100, 500, 1000, 5000}; //Possible tuning steps for UP/DOWN-keys
    int scan_mode = 0;
    int vfo_cnt = 0;
    unsigned long interfreq = 9832000; //Interfrequency in Hz (depends on filter used)
    unsigned int adc_val;
    unsigned long runsecsold1 = 0;
    unsigned long runsecsold2 = 0;  //Timer control for switching display between VFO and VOLTAGE
    unsigned long runsecsold3 = 0;  //Timer control for resseting tuning step to 10 secs after idel time
    char displayed = 0;
    unsigned long voltage2;
    int t1;
    //Variables for FUNC-Menu
    int exit_func = 0;
    int exit_loop = 0;
    //Tuning step for frequency scan
    int scan_step = 2;
    //Variables for SPLIT-MODE
    int vfo_tx = 0;
    int split_mode = 0;
    int tx_freq_set = 0;
    /* Set ports */
    /* OUTPUT */
    DDRB = 0xC7; //SPI (PB0..PB2) LCD RS and E on PB6 and PB7
    DDRD = 0xF0; //LCD (Data) on PD4...PD7
    /*Input*/
    PORTD = 0x0F; //Pull-Up resistors on
    //PD2 = Tune up
    //PD3 = Tune down
    PORTC = 0x3E; ////Pull-Up resistors on for inputs
    //TX INDICATOR:   PINC2
    //VFO-Select:     PINC3
    //FUNC:           PINC4
    //TUN STEP:       PINC5
    //TUNE UP:        PIND2
    //TUNE DOWN:      PIND3
    //Start LCD
    lcd_init();
    _delay_ms(50);
    //Watchdog off
    WDTCSR = 0;
    WDTCSR = 0;
    //ADC init
    ADMUX = (1<<REFS0);     // Reference = AVCC
    ADCSRA = (1<<ADPS2) | (1<<ADPS1) | (1<<ADEN); //Prescaler 64 and ADC activated
    ADCSRA |= (1<<ADSC);
    while (ADCSRA & (1<<ADSC));
    adc_val = ADCL; //Read one sample vaue and discard
    adc_val += ADCH * 256;
    adc_val = 0;
    //Timer 1
    TCCR1A = 0;                         // normal mode, no PWM
    TCCR1B = (1<<CS12) + (1<<CS10) ;   // start Timer with system clock Prescaler = /1024
    //Trigger des Overflow each sec.
    TIMSK1 = (1<<TOIE1);               // overflow activated.
    TCNT1 = 57724;                     //init value for measuering one second
    //Load VFO frequencies from EEPROM if available
    freq2 = 14180000;
    for(t1 = 0; t1 <= MAXVFO; t1++)
    {
        freq1 = loadfreq(t1);
        if(freq1 > 13000000)
        {
            vfo[t1] = freq1;
        }
        else
        {
            vfo[t1] = freq2;
            freq2 += 10000;
        }
    }
    //Get last VFO used
    vfo_cnt = load_cur_vfo();
    freq1 = vfo[vfo_cnt];
    set_frequency(freq1, interfreq, AD9835INIT);
    lcd_putstring(0, 0, "QRP SSB");
    lcd_putstring(1, 0, "TRX V2.2");
    _delay_ms(600);
    lcd_cls();
    show_freq(freq1);
    show_step(cur_freq_shift);
    show_vfo(vfo_cnt);
    runsecsold2 = runsecs;
    idlesecs = runsecs;
    sei();
    for(;;)
    {
        //KEYS:
        //TX INDICATOR:   PINC2
        //VFO-Select:     PINC3
        //FUNC:           PINC4
        //TUN STEP:       PINC5
        //TUNE UP:        PIND2
        //TUNE DOWN:      PIND3
        //VFO-Select
        if(!(PINC & (1<<PINC3)))
        {
            if(vfo_cnt < MAXVFO)
            {
                vfo_cnt++;
            }
            else
            {
                vfo_cnt = 0;
            }
            //set frequency
            set_frequency(vfo[vfo_cnt], interfreq, AD9835UPDATE);
            lcd_cls();
            freq1 = vfo[vfo_cnt];
            save_cur_vfo(vfo_cnt);
            set_frequency(freq1, interfreq, AD9835UPDATE);
            show_vfo(vfo_cnt);
            //show stepanzeigen
            lcd_putstring(1, 5, "  ");
            show_step(cur_freq_shift);
            idlesecs = runsecs;
            //wait for key release
            while(!(PINC & (1<<PINC3)));
        }
        if(!(PIND & (1<<PIND2))) //TUNE UP
        {
            freq1 += freq_shift[cur_freq_shift];
            set_frequency(freq1, interfreq, AD9835UPDATE);
            runsecsold3 = runsecs;
            idlesecs = runsecs;
            if(scan_mode)
            {
                scan_mode = 0;
                show_step(cur_freq_shift);
            }
        }
        if(!(PIND & (1<<PIND3))) //TUNE DOWN
        {
            freq1 -= freq_shift[cur_freq_shift];
            set_frequency(freq1, interfreq, AD9835UPDATE);
            runsecsold3 = runsecs;
            idlesecs = runsecs;
            if(scan_mode)
            {
                scan_mode = 0;
                show_step(cur_freq_shift);
            }
        }
        //FUNC
        if(!(PINC & (1<<PINC4)))
        {
            scan_mode = 0;
            exit_func = 0;
            while(!(PINC & (1<<PINC4)));
            //STORE FREQ TO VFO
            lcd_cls();
            lcd_putstring(1, 0, "STOR");
            lcd_putchar(1, 5, vfo_cnt + 65);
            lcd_putchar(1, 6, '?');
            show_freq(freq1);
            while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
            if(!(PINC & (1<<PINC5)))
            {
                while(!(PINC & (1<<PINC5)));
                lcd_cls();
                vfo[vfo_cnt] = freq1;
                show_freq(freq1);
                lcd_putstring(1, 0, "Saved");
                lcd_putchar(1, 6, vfo_cnt + 65);
                lcd_putchar(1, 7, '.');
                savefreq(vfo_cnt, freq1);
                _delay_ms(1000);
                lcd_putstring(1, 5, "  ");
                exit_func = 1;
            }
            else
            {
                while(!(PINC & (1<<PINC4)));
            }
            //Scan frequency, trigger with UP/DOWN key, cancel with FUNC-Key
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 0, "SCN QRG?");
                lcd_putstring(1, 0, "DOWN UP");
                //Wait for key
                //UP, DOWN, FUNC
                while((PIND & (1<<PIND2)) && (PIND & (1<<PIND3)) && (PINC & (1<<PINC4)));
                //UP
                if(!(PIND & (1<<PIND2)))
                {
                    scan_mode = 1;
                    exit_func = 1;
                    while(!(PIND & (1<<PIND2)));
                }
                //DOWN
                if(!(PIND & (1<<PIND3)))
                {
                    while(!(PIND & (1<<PIND3)));
                    scan_mode = 2;
                    exit_func = 1;
                }
                //Cancel
                if(!(PINC & (1<<PINC4)))
                {
                    while(!(PINC & (1<<PINC4)));
                    lcd_cls();
                    show_freq(freq1);
                    show_step(cur_freq_shift);
                }
            }
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 0, "SCN VFO?");
                lcd_putstring(1, 0, "  NO YES");
                //Wait for key press Y/N
                //
                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
                //Scan VFOs
                if(!(PINC & (1<<PINC5)))
                {
                    while(!(PINC & (1<<PINC5)));
                    //Set all skip flags to 0
                    for(t1 = 0; t1 <= MAXVFO; t1++)
                    {
                        skip_vfo[t1] = 0;
                    }
                    lcd_cls();
                    lcd_putstring(0, 0, "SCANNING");
                    for(t1 = 0; t1 < 8; t1++)
                    {
                        lcd_putchar(1, t1, '.');
                        _delay_ms(100);
                    }
                    exit_loop = 0;
                    while(!exit_loop)
                    {
                        lcd_cls();
                        for(t1 = 0; t1 <= MAXVFO && !exit_loop; t1++)
                        {
                            if(!skip_vfo[t1]) //Scan only if VFO not in skip list
                            {
                                runsecsold1 = runsecs;
                                lcd_cls();
                                set_frequency(vfo[t1], interfreq, AD9835UPDATE);
                                show_vfo(t1);
                                //Wait 4 seconds
                                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)) && runsecs < runsecsold1 + 4)
                                {
                                    lcd_putchar(1, 4 + runsecs - runsecsold1, '.');
                                    if(!(PINC & (1<<PINC3))) //Skip current VFO
                                    {
                                        while(!PINC & (1<<PINC3));
                                        skip_vfo[t1] = 1;
                                        lcd_putstring(1, 0, "SKIPPED!");
                                        _delay_ms(500);
                                        runsecsold1 -= 4;
                                    }
                                }
                                if(!(PINC & (1<<PINC5))) //Select VFO
                                {
                                    while(!(PINC & (1<<PINC5)));
                                    exit_loop = 1;
                                    vfo_cnt = t1;
                                    freq1 = vfo[vfo_cnt];
                                    set_frequency(freq1, interfreq, AD9835UPDATE);
                                }
                                if(!(PINC & (1<<PINC4))) //Cancel
                                {
                                    while(!(PINC & (1<<PINC4)));
                                    exit_loop = 1;
                                }
                            }
                        }
                    }
                    exit_func = 1;
                }
            }
            if(!exit_func)
            {
                while(!(PINC & (1<<PINC4)));
                if(split_mode)
                {
                    split_mode = 0;
                }
                lcd_cls();
                lcd_putstring(0, 0, "Split?");
                lcd_putstring(1, 0, "  NO YES");
                //Wait for key press Y/N
                //
                while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)));
                //Select VFO for transmit (VFO for receive will be the one currently in use)
                if(!(PINC & (1<<PINC5)))
                {
                    while(!(PINC & (1<<PINC5)));
                    lcd_cls();
                    lcd_putstring(0, 0, "SEL TX");
                    lcd_putstring(1, 0, "VFO");
                    vfo_tx = 0;
                    while((PINC & (1<<PINC5)) && (PINC & (1<<PINC4)))
                    {
                        if(!(PIND & (1<<PIND2))) //++
                        {
                            while(!(PIND & (1<<PIND2)));
                            if(vfo_tx >= MAXVFO)
                            {
                                vfo_tx = 0;
                            }
                            else
                            {
                                vfo_tx++;
                            }
                        }
                        if(!(PIND & (1<<PIND3))) //--
                        {
                            while(!(PIND & (1<<PIND3)));
                            if(vfo_tx <= 0)
                            {
                                vfo_tx = MAXVFO;
                            }
                            else
                            {
                                vfo_tx--;
                            }
                        }
                        show_vfo(vfo_tx);
                    }
                    if(!(PINC & (1<<PINC4))) //CANCEL
                    {
                        while(!(PINC & (1<<PINC4)));
                    }
                    if(!(PINC & (1<<PINC5))) //OK
                    {
                        while(!(PINC & (1<<PINC5)));
                        split_mode = 1;
                        lcd_cls();
                        lcd_putstring(0, 0, "TX VFO=");
                        show_vfo(vfo_tx);
                        _delay_ms(1000);
                        lcd_cls();
                        exit_func = 1;
                    }
                }
            }
            if(!exit_func)
            {
                lcd_cls();
                lcd_putstring(0, 1, "SLEEP?");
                //Wait for key release
                while(!(PINC & (1<<PINC4)));
                //Wait for key
                //FUNC=NO, STEP=YES
                while((PINC & (1<<PINC4)) && (PINC & (1<<PINC5)));
                //FUNC = Cancel
                if(!(PINC & (1<<PINC4)))
                {
                    while(!(PINC & (1<<PINC4)));
                    lcd_cls();
                }
                //Set MUC to SLEEPMODE
                if(!(PINC & (1<<PINC5)))
                {
                    //Tuning step = 10Hz
                    cur_freq_shift = 2;
                    //Show tuning step
                    lcd_putstring(1, 5, "  ");
                    lcd_putstring(1, 5, freq_shift_str[cur_freq_shift]);
                    show_freq(freq1);
                    lcd_putstring(1, 0, "*SLEEP* ");
                    EICRA = 0;
                    EICRA |= (1<<ISC11) | (1<<ISC01);  //Interrupt to wake up device triggered on
                    //falling edge on PD2 and PD3
                    EIMSK |= (1<<INT0) | (1<<INT1);    //Enable external interrupt for INT0 and INT1
                    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
                    sleep_mode();
                    //Wake up, returning from ISR
                    lcd_putstring(1, 0, "        ");
                    show_step(cur_freq_shift);
                    EIMSK &= ~(1<<INT0); //Disable external interrupt for INT0 and INT1
                    EIMSK &= ~(1<<INT1);
                    exit_func = 1;
                }
            }
            lcd_cls();
            show_freq(freq1);
            show_step(cur_freq_shift);
        }
        if(scan_mode)
        {
            show_step(scan_step);
            switch(scan_mode)
            {
                case 1: freq1 += freq_shift[scan_step];
                set_frequency(freq1, interfreq, AD9835UPDATE);
                break;
                case 2: freq1 -= freq_shift[scan_step];
                set_frequency(freq1, interfreq, AD9835UPDATE);
                break;
            }
        }
        //Set Frequency-Shift for tuning
        if(!(PINC & (1<<PINC5))) //Frequency-Shift when tuning
        {
            //runsecsold1 = runsecs;
            runsecsold3 = runsecs;
            if(cur_freq_shift < MAXSHIFT)
            {
                cur_freq_shift++;
            }
            else
            {
                cur_freq_shift = 0;
            }
            while(!(PINC & (1<<PINC5)));
            scan_step = cur_freq_shift;
            lcd_putstring(1, 5, "  ");
            show_step(cur_freq_shift);
            idlesecs = runsecs;
        }
        //Reset shift to 10Hz after idle time (1 sec.)
        if(runsecsold3 + 1 < runsecs && cur_freq_shift != 2)
        {
            cur_freq_shift = 2;
            //Show tuning step
            lcd_putstring(1, 5, "   ");
            show_step(cur_freq_shift);
            runsecsold3 = runsecs;
        }
        //Show current VFO
        if(runsecs > runsecsold2 + 1)
        {
            if(displayed != 2)
            {
                lcd_putstring(1, 0, "     ");
                show_vfo(vfo_cnt);
                displayed = 2;
                runsecsold2 = runsecs;
            }
        }
        //Show battery voltage
        if(runsecs > runsecsold2 + 3 && displayed != 1)
        {
            //Check battery voltage with ADC
            adc_val = get_adc(0);
            voltage2 = (unsigned long) adc_val * 5 * 32 / 1024;
            lcd_putstring(1, 0, "     ");
            lcd_putnumber(1, 0, voltage2, -1, 1, 'l', 0);
            lcd_putstring(1, 4, "V");
            runsecsold2 = runsecs;
            displayed = 1;
        }
        if(split_mode) //SPLIT-MODE
        {
            if(!(PINC & (1<<PINC2))) //TX indicator hi
            {
                if(!tx_freq_set)
                {
                    set_frequency(vfo[vfo_tx], interfreq, AD9835UPDATE);
                    tx_freq_set = 1;
                }
            }
            else
            {
                if(tx_freq_set)
                {
                    set_frequency(vfo[vfo_cnt], interfreq, AD9835UPDATE);
                    tx_freq_set = 0;
                }
            }
        }
    }
    return 0;
}

3 thoughts on “Update: Software for DDS-controlled QRP SSB handheld transceiver (AD9835, ATmega328P)”

  1. Rather than use floating point, I used integer math (binary fixed point) functions to calculate my DDS tuning word. I think this is more compact than using the AVR floating point library, and possibly faster.

    union Frequency {
    unsigned long long integer f_ll;
    unsigned long integer f_l[2];
    }
    union TuningWord{
    unsigned long long integer w_ll;
    unsigned long integer w_l[2];
    }
    Our constant is stored in a union as well:
    union Konst{
    unsigned long long integer K_ll;
    unsigned long integer K[2];
    }

    Konst.K[0] = (unsigned long long integer(2**32) /
    unsigned long long integer F_clock) * unsigned long long integer(2**24);
    Konst.K[1] = 0;

    Frequency.F_l[0] = frequency; //load frequency union
    Frequency.F_l[1] = 0;
    TuningWord.w_ll = Frequency.f_ll * Konst.K_ll; // 32 bit multiplication resulting in 64 bit result

    uningWord.wll = TuningWord.wll << 8;
    unsigned long int dds_word = TuningWord.w_l[1];

    Now divide 32 bit dds_word into 4 bytes to send to the ad9835 as in your code.

    Note that by left shifting the 64 bit result by 8 bits and taking the upper half of this in the union we have in effect right shifted the result by 24 bits.

  2. Hi,
    I’m not familiar with C language. To compile the “DDS for QRP SSB Transceiver w. ATMega328 und AD9835” source code, I’ve installed winavr. But I have not carried “compiling” out. Is there a makefile for this source code? Or is there any other method to compile it?

Leave a Reply

Your email address will not be published. Required fields are marked *