G4TGJ AVR Radio Library

This library is a set of source files for AVR processors that provide functionality of use in amateur radio applications, although many of the files could also be used in non-radio systems.

These files have been tested on the ATTiny85, ATtiny817, ATtiny3216 and ATMega328P. They should work with most tinyAVR 1-series, mega and xmega devices.

When adding these files to your project in Atmel Studio you should add them as links rather than copying them over. This way you can easily update the library when I issue new versions. You should add as links the C files that you need so that they are compiled. You can also link to the header files but this does not bring them into the compilation. For the compiler to find them you need to add ../../../TARL and .. to Project Properties/Toolchain/C Compiler/Directories.

The source code is available at TARL. A good way to learn how to use the library is to look at three projects which use the library: Serial817, TATC and FreqGen5351. You can, of course, use these projects as the basis for your project - feel free to clone or fork. Just remember that if you distribute your program in binary form you must also make the source code available under the GPL. I would welcome pull requests with any modifications you may make.

Index

Drivers

config.h

All source files include config.h. It is used to customise the library for specific hardware, e.g. IO ports, I2C address, frequency ranges. It also contains the definition of the bool type and the values true and false.

Example config.h structure
/*
 * config.h
 *
 * Configuration settings for the whole project
 *
 */


#ifndef CONFIG_H_
#define CONFIG_H_

#include <avr/io.h>

// General definitions
typedef uint8_t bool;
#define true 1
#define false 0

#endif /* CONFIG_H_ */

config.h is not part of the library but should be provided as part of your project. For each driver I have listed the definitions required with example values - these will obviously have to match your requirements and hardware.

main.c and main.h

Your program requires a main.c (although you can name it anything you like) which contains main() which should call the init function for each driver you are using and then it should start a continuous loop. This will continually call into those drivers that require to do regular processing (e.g. morseScanPaddles() in the morse keyer. You obviously have to include your own application code here too.

Some drivers include main.h which defines the functions that the driver calls to feed back into the main program e.g. the morse keyer will call keyDown() whenever it is sending a dot or a dash.

io.c and io.h

Many of the drivers require an I/O driver e.g. to read the state of a switch. The functions should be declared in io.h and implemented in io.c. Again, for each driver I have listed the required functions.

CAT

CAT control simulating the Yaesu FT450D. Requires the serial driver.

Requires the main application to implement a number of functions for setting VFO frequency, RIT etc.

Files

cat.h

Header file.

cat.c

Implementation.

Functions

void catInit()

Initialise CAT control.

void catControl()

Called from the main loop to do CAT control.

Example config.h definitions for the CAT driver
// Time between scans of the CAT interface
#define CAT_CHARACTER_DELAY 10

Required functions to provide in main.c and main.h:

void setVFOFrequency(uint8_t vfo, uint32_t freq)

Sets a VFO frequency.

Parameters
  • vfo – VFO number

  • freq – Frequency

uint32_t getVFOFreq(uint8_t vfo)

Returns a VFO’s frequency.

Parameters
  • vfo – VFO number

uint32_t getCurrentVFOFreq()

Returns the current VFO’s frequency.

uint32_t getOtherVFOFreq()

Returns the other VFO’s frequency. e.g. if the current VFO is A, then return B’s frequency.

int16_t getCurrentVFOOffset()

Returns the current VFO’s offset.

bool getCurrentVFORIT()

Returns the current VFO’s RIT setting.

bool getCurrentVFOXIT()

Returns the current VFO’s XIT setting.

int16_t getOtherVFOOffset()

Returns the other VFO’s offset.

bool getOtherVFORIT()

Returns the other VFO’s RIT setting.

bool getOtherVFOXIT()

Returns the other VFO’s XIT setting.

uint8_t getCurrentVFO()

Returns the current VFO.

void setCurrentVFO(uint8_t vfo)

Sets the current VFO.

Parameters
  • vfo – VFO number

void setCurrentVFORIT(bool bRIT)

Set the current VFO in or out of RIT.

Parameters
  • bRIT – true if going into RIT

bool getVFOSplit()

Return true if in split VFO mode.

void setVFOSplit(bool bSplit)

Sets split VFO mode.

Parameters
  • bSplit – true if setting split

bool getTransmitting()

Return true if currently transmitting.

void vfoSwap()

Swap the two VFOs.

void vfoEqual()

Set the VFOs to be equal.

void setCurrentVFOOffset(int16_t rit)

Set the current VFO’s offset.

Parameters
  • rit – RIT value to set

void setCWReverse(bool bCWReverse)

Sets CW reverse mode i.e. swaps sidebands.

Parameters
  • bCWReverse – true if setting CW reverse

Display

Display driver. Requires the LCD driver.

You must specify the width and height of the display in config.h.

If you are short of flash space then defining DISPLAY_DISABLE_SCROLLING will save some space. Obviously this disables scrolling and displaySplitLine() is not available.

Files

display.h

Header file.

display.c

Implementation.

Functions

void displayInit()

Initialise the display module.

void displayText(uint8_t line, char *text, bool bReplace)

Display text on the specified line replacing the existing text or scrolling from the right.

Parameters
  • [in] line: Line number

  • [in] text: Pointer to the text to display

  • [in] bReplace: If true then replace the existing line, otherwise scroll the text in

void displayCursor(uint8_t col, uint8_t line, enum eCursorState state)

Set the cursor position and state (off, underline or blink).

Parameters
  • [in] col: Column number

  • [in] line: Line number

  • [in] state: cursorOff, cursorUnderline or cursorBlink

void displaySplitLine(uint8_t col, uint8_t line)

Split a line at the given column. This means text will scroll up to that point.

Parameters
  • [in] col: Column number

  • [in] line: Line number

enum eCursorState

Possible cursor states.

Values:

enumerator cursorOff
enumerator cursorUnderline
enumerator cursorBlink
Example config.h definitions for the display driver
// The size of the LCD screen
#define LCD_WIDTH 16
#define LCD_HEIGHT 2

// Define to disable scrolling
#define DISPLAY_DISABLE_SCROLLING

EEPROM

EEPROM driver.

Files

eeprom.h

Header file.

eeprom.c

Implementation.

Functions

void eepromInit()

Initialise the EEPROM driver.

uint8_t eepromRead(uint16_t uiAddress)

Read a byte from the EEPROM.

Return

The byte read from the address

Parameters
  • [in] uiAddress: EEPROM address

void eepromWrite(uint16_t uiAddress, uint8_t ucData)

Write a byte to the EEPROM.

Parameters
  • [in] uiAddress: EEPROM address

  • [in] ucData: Data to write

I2C

Implements the Philips I2C (Inter Integrated Circuit) bus. Some manufacturers call it TWI (Two Wire Interface), presumably to avoid trademark infringement.

Currently only the master side is implemented. Multi-master buses are not supported.

Files

i2c.h

Header file.

i2c.c

Implementation.

Pulls in the required source file for the device. Supports ATMega, tinyAVR 1-series and devices that use the Universal Serial Interface (USI) to provide I2C (such as the ATTiny85).

Functions

void i2cInit()

Initialise the I2C driver.

Must be called before any other I2C functions.

uint8_t i2cWriteRegister(uint8_t addr, uint8_t reg, uint8_t data)

Write to an 8 bit register over I2C.

Parameters
  • [in] addr: I2C address

  • [in] reg: Register address

  • [in] data: Data to write to register

uint8_t i2cReadRegister(uint8_t addr, uint8_t reg, uint8_t *data)

Read from an 8 bit register over I2C.

Parameters
  • [in] addr: I2C address

  • [in] reg: Register address

  • [out] data: Pointer to data location to write contents of register to

LCD

Low level LCD display driver. Normally you would use the display driver rather than the LCD driver.

Files

lcd.h

Header file.

lcd.c

Implementation. Requires either lcd_i2c.c or lcd_port.c. Alternatively, you can use lcd_if.c and define either LCD_PORT or LCD_I2C to target code at either type of display.

lcd_i2c.c

Use this with LCD displays connected over I2C.

lcd_port.c

Use with LCD displays directly connected to the IO ports on the AVR controller.

lcd_if.c

Pulls in either lcd_i2c.c (if LCD_I2C is defined) or lcd_port.c (if LCD_PORT is defined).

Functions

void lcdInit()

Initialise the LCD driver.

void lcdBegin(uint8_t cols, uint8_t rows)

Set the number of rows and columns.

Parameters
  • [in] cols: Number of columns

  • [in] rows: Number of rows

void lcdClear()

Clear the LCD screen.

void lcdHome()

Move the cursor back to the first location.

void lcdDisplayOff()

Turn off the display.

void lcdDisplayOn()

Turn on the display.

void lcdBlinkOff()

Stop the cursor blinking.

void lcdBlinkOn()

Start the cursor blinking.

void lcdCursorOff()

Turn off the cursor.

void lcdCcursorOn()

Turn on the cursor.

void lcdScrollDisplayLeft()

Scroll the display left.

void lcdScrollDisplayRight()

Scroll the display right.

void lcdScrollLeftToRight()

Set the scroll direction to left to right.

void lcdScrollRightToLeft()

Set the scroll direction to right to left.

void lcdAutoscrollOn()

Set the display to automatically scroll.

void lcdAutoscrollOff()

Turn off automatic scrolling.

void lcdSetCursor(uint8_t col, uint8_t row)

Set the cursor position.

Parameters
  • [in] col: Column number

  • [in] row: Row number

void lcdPrint(const char *string)

Print text on the LCD screen.

Parameters
  • [in] string: Pointer to the null terminated string

void lcdBacklight(bool bOn)

Turn the backlight on or off

This function is only available with the I2C interface unless separately implemented

Parameters
  • [in] bOn: True to turn on backlight

Example config.h definitions for lcd_if.c
// Use the I2C version of the LCD driver
#define LCD_I2C
Example config.h definitions for the LCD I2C driver
// Address of the LCD display
#define LCD_I2C_ADDRESS 0x27
Example config.h definitions for the LCD port driver
// LCD Port definitions
#define LCD_ENABLE_PORT PORTB
#define LCD_ENABLE_DDR  DDRB
#define LCD_ENABLE_PIN  PB3
#define LCD_RS_PORT     PORTB
#define LCD_RS_DDR      DDRB
#define LCD_RS_PIN      PB4
#define LCD_DATA_PORT_0 PORTD
#define LCD_DATA_DDR_0  DDRD
#define LCD_DATA_PIN_0  PD7
#define LCD_DATA_PORT_1  PORTD
#define LCD_DATA_DDR_1  DDRD
#define LCD_DATA_PIN_1  PD4
#define LCD_DATA_PORT_2  PORTD
#define LCD_DATA_DDR_2  DDRD
#define LCD_DATA_PIN_2  PD3
#define LCD_DATA_PORT_3  PORTD
#define LCD_DATA_DDR_3  DDRD
#define LCD_DATA_PIN_3  PD2

Millis

Timer providing a count in milliseconds and delays in milliseconds or microseconds. The AVR clock speed (F_CPU) must be specified in config.h as this is needed for correct timing. For the tinyAVR 1-series the clock divider must also be specified. This is because the TCA timer can be used as the source for other timers.

Files

millis.h

Header file.

millis.c

Implementation.

Functions

void millisInit(void)

Initialise the millisecond timer.

uint32_t millis()

Return the number of milliseconds since the box started. Wraps after approx 49 days.

Return

Number of milliseconds

void delay(uint16_t ms)

Delay a number of milliseconds.

Parameters
  • [in] ms: Number of milliseconds to wait

delayMicroseconds(us)

Delay a number of microseconds.

Parameters
  • [in] us: Number of microseconds to wait

Example config.h definitions for the millis driver
// CPU clock speed
// This must match the speed set in the AVR's fuses
#define F_CPU 20000000UL

// Clock divider (for tinyAVR 1-series only)
// This is used by the millisecond clock on TCA0
#define CLOCK_DIV 1

Morse

Morse keyer. Supports Iambic A, Iambic B and Ultimatic.

Files

morse.h

Header file.

morse.c

Implementation.

Functions

void morseInit()

Initialise the morse module.

uint8_t morseGetWpm()

Get the morse speed in words per minute.

Return

Morse speed in WPM

void morseSetWpm(uint8_t wpm)

Set the morse speed in words per minute.

Parameters
  • [in] wpm: Morse speed in wpm

bool morseScanPaddles(void)

Scan the dot and dash paddles. Called from the main loop.

The return value can be used to decide whether or not to go into a low power mode - useful for a battery powered keyer without an off switch.

Return

true if active

Return

false if idle

void morseSetTuneMode(bool bTune)

Switch in or out of tune mode. i.e. continuous transmission for tuning ATU etc.

Parameters
  • [in] bTune: True to set tune mode, False to leave tune mode

bool morseInTuneMode()

Returns whether or not in tune mode.

Return

true if in tune mode

enum eMorseKeyerMode morseGetKeyerMode()

Get the keyer mode.

Return

The keyer mode

void morseSetKeyerMode(enum eMorseKeyerMode)

Set the keyer mode.

Parameters
  • [in] eMorseKeyerMode: The keyer mode

enum eMorseKeyerMode

Morse keyer mode.

Values:

enumerator morseKeyerIambicA = 0
enumerator morseKeyerIambicB
enumerator morseKeyerUltimatic
enumerator MORSE_NUM_KEYER_MODES
Example io.c definitions for the morse keyer
void ioInit()
{
        // Initialise morse output
        MORSE_OUTPUT_DDR_REG |= (1<<MORSE_OUTPUT_PIN);

        // Paddle dot and dash pins as inputs with pull-ups
        MORSE_PADDLE_DOT_PORT_REG |= (1<<MORSE_PADDLE_DOT_PIN);
        MORSE_PADDLE_DASH_PIN_REG |= (1<<MORSE_PADDLE_DASH_PIN);
}

// Functions to read morse paddle inputs
bool ioReadDotPaddle()
{
        return !(MORSE_PADDLE_DOT_PIN_REG & (1<<MORSE_PADDLE_DOT_PIN));
}

bool ioReadDashPaddle()
{
        return !(MORSE_PADDLE_DASH_PIN_REG & (1<<MORSE_PADDLE_DASH_PIN));
}

Required functions to provide in main.c and main.h:

void displayMorse(char *text)

Sends the characters being sent by the keyer to the application. Can be displayed on a screen, for example.

Parameters
  • text – Pointer to characters to display

void keyDown(bool bDown)

Sends the key state to the application so that the keyer can key the transmitter.

Parameters
  • bDown – true if the key is down, false if it is up

Oscillator

Oscillator driver. Allows the setting of individual output frequencies and enabling quadrature mode. Currently only supports the Si5351A chip with 3 outputs. CLK0 and CLK1 use PLL A and CLK2 uses PLL B.

Files

osc.h

Header file.

si5351a.c

Implementation for the Si5351A.

Functions

bool oscInit(void)

Initialise the oscillator.

Return

true if successful

Return

false if unable to talk to it or it doesn’t initialise properly

void oscSetFrequency(uint8_t clock, uint32_t frequency, int8_t q)

Set the clock to the given frequency with optional quadrature.

quadrature is only used for clock 1 - it is ignored for the others.

+ve is CLK1 leads CLK0 by 90 degrees -ve is CLK1 lags CLK0 by 90 degrees

0 is no quadrature i.e. set the frequency as normal.

When quadrature is set for clock 1 then it is set to the same frequency as clock 0.

Parameters
  • [in] clock: Clock output to set

  • [in] frequency: Frequency in hertz

  • [in] q: Quadrature mode

void oscClockEnable(uint8_t clock, bool bEnable)

Enable/disable a clock output.

Parameters
  • [in] clock: Clock output to control

  • [in] bEnable: true to enable, false to disable

void oscSetXtalFrequency(uint32_t xtal_freq)

Set the crystal frequency.

Parameters
  • [in] xtal_freq: Crystal frequency (in hertz)

Example config.h definitions for the oscillator driver
// Oscillator chip definitions
// I2C address
#define SI5351A_I2C_ADDRESS 0x60

// Transmit and receive clocks. Receive uses 2 clocks for quadrature.
#define NUM_CLOCKS 3
#define RX_CLOCK_A 0
#define RX_CLOCK_B 1
#define TX_CLOCK   2

// The minimum and maximum crystal frequencies in the setting menu
// Have to allow for adjusting above or below actual valid crystal range
#define MIN_XTAL_FREQUENCY 24000000
#define MAX_XTAL_FREQUENCY 28000000

// The si5351a default crystal frequency and load capacitance
#define DEFAULT_XTAL_FREQ       27000000
#define SI_XTAL_LOAD_CAP SI_XTAL_LOAD_10PF

Pushbutton

Pushbutton driver. Debounces the button detecting short and long presses.

Files

pushbutton.h

Header file.

pushbutton.c

Implementation.

Functions

void debouncePushbutton(bool bDown, bool *pbShortPress, bool *pbLongPress, uint16_t debounceTime, uint16_t longPressTime, struct sDebounceState *pDebounceState)

Debounce a pushbutton

Parameters
  • [in] bDown: True if the pushbutton is down as read from the hardware

  • [out] pbShortPress: Pointer to boolean set to true if the pushbutton has been pressed for a short time

  • [out] pbLongPress: Pointer to boolean set to true if the pushbutton has been pressed for a long time or null if not used

  • [in] debounceTime: Minimum time pushbutton must be down for it to be a short press

  • [in] longPressTime: Minimum time pushbutton must be down for it to be a long press

  • [in] pDebounceState: Pointer to structure holding state machine state

Example use of the pushbutton driver
// Debounce times in ms
#define DEBOUNCE_TIME 200
#define LONG_PRESS_TIME 1000

// Keep track of pushbutton debounce state
static struct sDebounceState debounceState;

// Read and debounce the pushbutton
bool shortPress, longPress;
debouncePushbutton( readButton(), &shortPress, &longPress, DEBOUNCE_TIME, LONG_PRESS_TIME, &debounceState );
Example io.c definitions for the pushbutton driver
bool readButton()
{
        return !(BUTTON_PIN_REG & (1<<BUTTON_PIN));
}

// Configure all the I/O we need
void ioInit()
{
        BUTTON_PORT_REG |= (1<<BUTTON_PIN);
}

Rotary

Rotary driver. Handles a rotary control with pushbutton. Decodes the quadrature signal for rotation and debounces the button detecting short and long presses.

Requires the pushbutton driver.

Files

rotary.h

Header file.

rotary.c

Implementation.

Functions

void readRotary(bool *pbCW, bool *pbCCW, bool *pbShortPress, bool *pbLongPress)

Read the rotary control and its push button

Parameters
  • [out] pbCW: Pointer to boolean set to true if the control has been moved clockwise

  • [out] pbCCW: Pointer to boolean set to true if the control has been moved counter clockwise

  • [out] pbShortPress: Pointer to boolean set to true if the pushbutton has been pressed for a short time

  • [out] pbLongPress: Pointer to boolean set to true if the pushbutton has been pressed for a long time

Example config.h definitions for the rotary driver
// Time for debouncing the rotary pushbutton (ms)
#define ROTARY_BUTTON_DEBOUNCE_TIME 100

// Time for the rotary pushbutton press to be a long press (ms)
#define ROTARY_LONG_PRESS_TIME 250
Example io.c definitions for the rotary driver
void ioReadRotary( bool *pbA, bool *pbB, bool *pbSw )
{
        *pbA =  !(ROTARY_ENCODER_A_PIN_REG & (1<<ROTARY_ENCODER_A_PIN));
        *pbB = !(ROTARY_ENCODER_B_PIN_REG & (1<<ROTARY_ENCODER_B_PIN));
        *pbSw = !(ROTARY_ENCODER_SW_PIN_REG & (1<<ROTARY_ENCODER_SW_PIN));
}

// Configure all the I/O we need
void ioInit()
{
        ROTARY_ENCODER_A_PORT_REG |= (1<<ROTARY_ENCODER_A_PIN);
        ROTARY_ENCODER_B_PORT_REG |= (1<<ROTARY_ENCODER_B_PIN);
        ROTARY_ENCODER_SW_PORT_REG |= (1<<ROTARY_ENCODER_SW_PIN);
}

Serial

Serial driver for chips, such as the ATMega328 or ATtiny817, that have a USART. Interrupt driven with transmit and receive buffers.

Files

serial.h

Header file.

serial.c

Implementation.

Functions

void serialInit(uint32_t baud)

Initialise the serial driver.

Parameters
  • [in] baud: Baud rate

void serialTransmit(uint8_t data)

Transmit a byte over the serial line.

Parameters
  • [in] data: The byte to send

void serialTXString(char *string)

Transmit a null terminated string over the serial line.

Parameters
  • [in] string: Pointer to the string to send

uint8_t serialReceive(void)

Receive a byte from the serial line.

If the receive buffer is empty then NUL is returned.

Return

The byte received, or NUL.

Example config.h definitions for the serial driver
// // Serial port buffer lengths
// Lengths should be a power of 2 for efficiency
#define SERIAL_RX_BUF_LEN 32
#define SERIAL_TX_BUF_LEN 64