Introduction
In a previous article I presented a simple library to control a stepper motor with the Arduino UNO board. In this article I demonstrate another aspect of the Atmel Micro-controller that powers the Arduino board: its 16 bits timer. The Atmel chip integrates a very powerful 16 bits timer that can be used for many purposes. One of the features is that it can be used to generate a wave form on PIN9 and 10 of the Arduino board and trigger an internal interruption on different conditions.
In order to understand all the possibilities of the 16 bits Timer of the Arduino I would advise you to read the specifications of the Atmel Chip.
The following demo shows how to configure the 16 bits Timer to generate a square wave signal on the PIN9/10 and send some command to control the frequency of the wave.
The 16 bits timer of the Atmega 328
The 16 bits timer of the Atmel chip can be used to generate a square wave signal on the PIN9/10 of the Arduino board and raise an interruption every time the counter reaches a given value. We set the PIN signal to change every time the counter value is reached.
The period between two interruption is given by the following formula, it is also the half period of the output signal on PIN9/10:
Poc = 2 * N * (1 + OCR) / Fclk
where:
- N is the a counter divider (prescalar)
- OCR is the counter value
- Fclk is the clock frequency of the chip (16Mhz for the Arduino UNO)
For a given period (Poc) the value of the counter is given by the following formula:
OCR = (Poc * Fclk) / (2 * N) - 1
In this example I have chosen N = 256, so the formula for the counter is:
OCR = Poc * 31250 - 1, with Poc expressed in seconds.
The counter can be programmed using few registers:
- Output Compare Registers (OCR1A/B) to set the counting value (16 bits)
- Timer/Counter Control Registers (TCCR1A/B/C) to program the counter (8 bits)
- Timer Interrupt Mask Register (TIMSK1) to control the interruption
Programming the Timer/Counter:
TCCR1A - Timer/Counter1 Control Register A
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
COM1A1 | COM1A0 | COM1B1 | COM1B0 | - | - | WGM11 | WGM12 |
0 | 1 | 0 | 1 | | | 0 | 0 |
TCCR1B - Timer/Counter1 Control Register B
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 |
0 | 0 | | 0 | 1 | 1 | 0 | 0 |
TCCR1C - Timer/Counter1 Control Register C
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FOC1A | FOC1B | - | - | - | - | - | - |
0 | 1 | | | | | | |
Compare Output Mode, non-PWM is set to Toggle OC1A/OC1B on Compare Match. Waveform generation is set to CTC, with TOP value compare to OCR1A and update of OCR1x is immediate.
Clock Select bits are set to 100 which divides the clock by 256 and finally the Force Output Compare for channel B is set to 1 so the state of PIN10 is always the opposite of PIN9.
The TimerCounter1 library is a very simple one, it is not intended to provide a complete Timer library but just to set-up the wave generator mode for the demo. This code was taken from the CmdrArduino library which uses the Arduino to create a DCC control station for model rail road.
void TimerCounter1::setup_wave_generator(unsigned int counter)
{
DDRB |= (1<<DDB1) | (1<<DDB2);
TCCR1A = (0<<COM1A1) | (1<<COM1A0) | (0<<COM1B1) | (1<<COM1B0) | (0<<WGM11) | (0<<WGM10);
TCCR1B = (0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (1<<WGM12) | (1<<CS12) | (0<<CS11) |
(0<<CS10);
OCR1A = OCR1B = counter;
TCNT1 = 0;
TCCR1C |= (1<<FOC1B);
}
void TimerCounter1::enable_match_interrupt()
{
TIMSK1 |= (1<<OCIE1A);
}
The most important library in this simple demo is the Command library, it processes the command bytes sent to the sketch from the serial line to execute a command. The Command
class is an abstract class that must be implemented represent an actual set of command. In this demo the PC application sends simple commands to control the period of the wave generator.
namespace core
{
class Command
{
protected:
static const byte MAX_DATA = 20;
byte commandData[MAX_DATA];
bool ready;
virtual int extractCmd(byte* cmd);
virtual bool executeCmd(void) = 0;
private:
int idx;
public:
Command() : idx(0), ready(false)
{
}
bool add(byte item);
bool isReady(void);
bool getCommand(byte* command);
void clear(void);
bool execute(void);
static const byte START = 0xF0;
static const byte END = 0xFF;
};
}
This class has one pure virtual method that must be implemented by any class that inherits from it. This method interprets the command bytes that are given to the class and execute the corresponding command.
This class works in the following manner:
- The main application receives bytes on the serial port
- Each byte has to be added to the command when received using the
add()
method - The application must check the
isReady()
method which returns true when a command is ready
A command is represented by an array of bytes that starts with 0xF0 and ends with 0xFF. The bytes in between those markers are the command. A command can contain a 0xF0 value but it can't contain a 0xFF as 0xFF is interpreted as a end of command. This is a limitation but bare in mind that this is just a simple demo ... Another limit is that the command buffer is 20 bytes including the START and END delimiter.
bool core::Command::add(byte item)
{
bool ret = true;
if (idx == 0 && item == START)
{
commandData[idx++] = item;
}
else if (item == END && idx > 0 && idx < MAX_DATA)
{
commandData[idx++] = item;
ready = true;
}
else if (!ready && idx > 0 && idx < MAX_DATA)
{
commandData[idx++] = item;
}
else
{
clear();
ret = false;
}
return ret;
}
int core::Command::extractCmd(byte* cmd)
{
int ret = -1;
byte* ptrCmd = commandData;
if (*(ptrCmd++) == START)
{
ret = 0;
while(*ptrCmd != END && ret < (MAX_DATA - 1))
{
*(cmd++) = *(ptrCmd++);
++ret;
}
}
return ret;
}
An improvement to the process would be to replace the polling in isReady()
with a callback that would be invoked when a command is ready.
The application defines the following commands:
- 'a': sets the period to 1s
- 'b': sets the period to 1/2s
- 'c': sets the period to 1/4s
- 'p<bcd period>': sets the period to a value given in BCD. (Binary Code Decimal)
The code of the class that processes those commands is given below.
class TimerCTC_Cmd : public Command
{
private:
static const byte PERIOD_CMD = 'p';
static const byte ONE_SEC_CMD = 'a';
static const byte HALF_SEC_CMD = 'b';
static const byte QUATER_SEC_CMD = 'c';
unsigned int period;
public:
TimerCTC_Cmd()
{
}
unsigned int getPeriod();
private:
void ProcessPeriodCommand(byte* cmd);
protected:
virtual bool executeCmd(void);
};
bool TimerCTC_Cmd::executeCmd(void)
{
bool ret = false;
byte command[10];
int cmdLen = 1;
cmdLen = extractCmd(command);
if (cmdLen != -1)
{
switch (*command)
{
case ONE_SEC_CMD: {
period = 31249;
ret = true;
break;
}
case HALF_SEC_CMD: {
period = 15624;
ret = true;
break;
}
case QUATER_SEC_CMD: {
period = 7812;
ret = true;
break;
}
case PERIOD_CMD: {
ProcessPeriodCommand(command);
ret = true;
break;
}
}
}
return ret;
}
void TimerCTC_Cmd::ProcessPeriodCommand(byte* cmd)
{
period = -1;
if (cmd[0] == 'p')
{
Converter::BCDValue bcd;
for (int n = 0; n < sizeof(bcd.value); bcd.value[n] = cmd[1 + n++]);
period = Converter::BCD2bin(bcd);
}
}
Demo application: Send a command to the Arduino sketch to change the period of the wave generator
Hardware configuration
In order to run this sketch and see something you'll have to wire the following components
- PIN9: a LED and its limiting resistor (220 to 470 ohms)
- PIN10: a LED and its limiting resistor
- PIN13: a LED or use the test LED of the board
The sketch running in the Arduino is a very simple application that receives bytes sent on the serial port and pass them to the TimerCTC_Cmd
class to process them. The LEDs on PIN9 and PIN10 are going to blink alternately at the given frequency. When PIN9 is high, PIN10 will be low and son on.
It contains as well a ISR (Interrupt Service Routine) that is called by the Timer1 every time the counter is matching the predefined value of OCR1A. This ISR is going to change this value according the command sent to the TimerCTC_Cmd
using the method getPeriod()
.
The code is given below.
#include <TimerCounter1.h>
#include <command.h>
#include <Converter.h>
#include "TimerCTC_Cmd.h"
#define HALF_SECOND 15624
#define QUATER_SECOND 7812
#define ONE_SECOND 31249
#define BAUD_RATE 38400
volatile unsigned int cntr;
volatile unsigned int currcntr;
volatile byte state;
ISR(TIMER1_COMPA_vect)
{
static volatile unsigned long counter = 0;
if (!(PINB & (1 << PINB1)))
{
if (cntr != currcntr)
{
OCR1A = OCR1B = cntr;
currcntr = cntr;
}
}
if (counter % 2 == 0)
{
state = 1 - state;
digitalWrite(13, state);
}
++counter;
}
TimerCTC_Cmd command;
void setup() {
Serial.begin(BAUD_RATE);
TimerCounter1::setup_wave_generator(HALF_SECOND);
TimerCounter1::enable_match_interrupt();
cntr = HALF_SECOND;
currcntr = HALF_SECOND;
pinMode(13, OUTPUT);
state = 1;
}
void loop() {
if (Serial.available() > 0)
{
int inByte = Serial.read();
command.add(inByte);
if (command.isReady())
{
if (command.execute())
{
cntr = command.getPeriod();
}
command.clear();
}
}
}
In order to send commands to the Arduino we need a simple application. A .NET application can easily do the job.
This application connects to the Arduino opening a serial channel on the COM port of the Arduino. By default the half period of the wave generator is set to 1/2 second. You can adjust this value with the buttons or the slider control or the numeric text box. The value you type is not in ms but is the actual value written to the OCR1A counter.
The following extension converts a ushort
value into its BCD equivalent.
public static class UintExtension
{
public static byte[] ToBCD(this ushort value)
{
byte[] bcdNumber = new byte[3];
for (int n = 0; n < 3; n++)
{
bcdNumber[n] = (byte)(value % 10);
value /= 10;
bcdNumber[n] |= (byte)(value % 10 << 4);
value /= 10;
}
return bcdNumber;
}
}
Conclusion and Point of Interest
This is the second article I write about the Arduino. In this one I explored the usage of the Timer1 to generate a waveform and also communication with a PC. This is because I have in mind to use the CmdrArduino library, understand how it works and extend it to build a DCC control station that can receive commands from a PC.
This articles demonstrates two main features of the board, how powerful the built-in timer are and how easily you can communicate with the board.