Basic C AVR
Basic C AVR
platforms like 8051, AVR and PICs. While programming microcontrollers in C most of the
time we have to deal with registers. Most common tasks are setting and clearing bits in a
register and check whether a bit is 0 or 1 in a given register. So here I will give detail on
those topics, it will help you if you are new to embedded programming in C and if you get
confused when you see some codes.
A Register
A register is simply a collection of some bits (mostly 8 bits in case of 8bit MCUs). Either
each different bit in a register has some purpose or the register as a whole holds a value.
Registers serves as connection between a CPU and a Peripheral device (like ADC or
TIMER). By modifying the register the CPU is actually instructing the PERIPHERAL to do
something or it is configuring it in some way. And by reading a register, the CPU can know
the state of peripheral or read associated data.
Fig.: CPU Reading from Peripheral Register
Binary Numbers in C
When you write a=110; in C it means you are setting the value of variable"a" to "one
hundred and ten" (in decimal). Many time in embedded programming we are not interested
in the value of a variable but the state of each bits in the variable. Like when you want to set
the bits of a register (MYREG) to a bit pattern like 10010111 (binary). Then you cannot write
MYREG=10010111. Because compiler will interpret 10010111 as decimal. To specify a
binary number in C program you have to prefix it with 0b (zero followed by b). So if you
write
MYREG=0b10010111;
HEX Numbers in C
In same way if you prefix a number by 0x (a zero followed by x) then compiler interpret it
like a HEX number. So
MYREG=MYREG | 0b00100000;
The above code will SET bit 5 to 1 leaving all other bits unchanged. What the above code
does is that it ORs each Bit of MYREG with each bit of 0b00100000 and store the value back
in MYREG. If you know how logical OR works then you will get it.
MYREG|=0b00100000;
Now lets come to practical usage. In practice each bit has got a name according to its
work/function. Say our BIT (the 5th bit) has got name ENABLE, and what it does is clear by
its name,when we set it to 1 it enables the peripheral and when cleared (0) it disables it. So
the right way to set it is.
MYREG|=(1<<ENABLE);
The << is called left shift operator. It shifts the bits of LHS variable left by the amount on its
RHS variable. If you write
b=1<<3;
then, 1 whose binary value is 00000001 is shifted 3 places to left which results in 00001000
MYREG|=(1<<ENABLE);
will result in
MYREG|=(1<<5);
MYREG|=(0b00100000);
Now a beginner would ask "What's the Advantage ?". And once you know it you would
realize that advantage is immense!
MYREG&=~(1<<ENABLE);
This will clear (i.e. set to value 0) a given bit (identified by name ENABLE) in a register
called MYREG. This operation will not affect any other bits of register except ENABLE.
So now you know how you can selectively clear any bit in any given register. If you want to
clear more than one bit at a time you can write like this
//This will clear bits ENABLE,FAST_MODE and BUSY, leaving all other bits
untouched
MYREG&=(~((1<<ENABLE)|(1<<FAST_MODE)|(1<<BUSY)));
//This will set bits ENABLE,FAST_MODE and BUSY, leaving all other bits
untouched
MYREG|=((1<<ENABLE)|(1<<FAST_MODE)|(1<<BUSY));
Testing The Status of a Bit.
Till now we were modifying the registers either setting or clearing bits. Now we will learn
how can be know that a specific bit is 0 or 1. To Know if a bit is 0 or 1 we AND it with a
AND MASK. Suppose if we want to check bit 5 of a register MYREG then the AND MASK
would be 0b00100000. If we AND this value with the current value of MYREG then result
will be non-zero only if the 5th bit in MYREG is '1' else the result will be '0'.
So now you know the basic operation on bits, they are widely used in firmware programming
and will help you understand other codes on my web site. And Please don't forget to post
your comment regarding any doubts, or reporting errors in the above article, or simply to tell
how you liked the stuff.
On a compare match we can configure the timer to reset it self to 0. This is called CTC -
Clear Timer on Compare match.
The compare feature is not present in the 8 bit TIMER0 of the ATmega8 so we will use the
TIMER1 which is a 16 Bit timer. First we need to setup the timer's prescaler as described in
the Timer0 tutorial. Please see this tutorial for a basic introduction of TIMERs.
The TIMER1 has two compare units so it has two output compare register OC1A and OC1B.
The '1' in the name signifies that they are for timer '1'.
In this tutorial we will create a standard time base which will be useful for many projects
requiring timing such as clocks,timers,stopwatches etc. For this we will configure the timer to
generate an Compare match every millisecond and in the ISR we will increment a variable
clock_millisecond. In this way we will have a accurate time base which we can use for
computing time in seconds,minutes and hours.
References
For learning about the basic idea of peripherals and their use with AVRs please see this
tutorial.
This are used to configure the action for the event when the timer has detected a "match". As
i told earlier the timer can be used to automatically set,clear or toggle the associated Output
compare pin this feature can be configured from here. The table below shows the possible
combinations.
The OC1A pin is the Pin15 on ATmega8 and Pin19 on ATmega16/32. As you may guess that
we don't need any pin toggling or any thing for this project so we go for the first option i.e.
Normal Port Operation
As I have told you that the TIMER1 has two compare unit, the COM1B1/COM1B0 are
used exactly in same way but for the channel B.
These combined with WGM12 and WGM13 found in TCCR1B are used for selecting proper
mode of operation. WGM= Waveform Generation Mode.
Bit 7 6 5 4 3 2 1 0
Name ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10
InitialValue 0 0 0 0 0 0 0 0
WGM13 - WGM12 - WGM11 - WGM10 are used to select the proper mode of operation.
Please refer to the datasheet for complete combinations that can be used. We need the CTC
mode i.e. clear timer on match so we set them as follows
WGM13=0
WGM12=1
WGM11=0
WGM10=0
The CS12,CS11,CS10
These are used for selecting the prescalar value for generating clock for the timer. I have
already discussed them on TIMER0 tutorial. I will select the prescalar division factor as 64.
As the crystal we are using is of 16MHz so dividing this by 64 we get the timer clock as
F(timer)=16000000/64 = 250000Hz
TCCR1B=(1<<WGM12)|(1<<CS11)|(1<<CS10);
Bit 7 6 5 4 3 2 1 0
Name OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 - TOIE0
InitialValue 0 0 0 0 0 0 0 0
Of all these bits 5,4,3,2 are for TIMER1 and we are interested in the OCIE1A which is
Output Compare Interrupt Enable 1 A. To enable this interrupt we write
TIMSK|=(1<<OCIE1A);
After enabling the interrupt we also need to enable interrupt globally by using the function
sei();
This function is part of AVR-GCC interrupt system and enables the interrupt globally.
Actually this translate in one machine code so there is no function call overhead.
As in serial transmission only one wire is used for data transfer. Its logic level changes
according to bit being transmitted (0 or 1). But a serial communication need some way of
synchronization. If you don't understand what I mean by "synchronization" then don't worry
just read on it will become clear.
The animation below shows you how a serial transmission would look like (if you can see
electricity).
Can you make out what data is coming? No because you are not synchronized. You need a
way to know when a new byte start and when a bit ends and new bit start. Suppose the line is
low for some time that means a '0' but how many zeros? If we send data like 00001111 then
line is first low for some time and high after that. Then how we know it is four '0's and four
'1's?
Now if we add another line called the clock line to synchronize you then it will become very
easy. You need to note down the value of data line only when you see the "clock line"
high. Lets understand this with help of an animation.
Now you can see how the "clock" line helps you in "synchronizing" the incoming data. In this
way many serial busses like SPI and I2C works. But USART is different in USART there is
no clock line. So it is called UART - Universal Asynchronous Receiver Transmitter. In
USART a start bit and stop bits are used to synchronize the incoming data the.
RS232
In RS232 there are two data lines RX and TX. TX is the wire in which data is sent out to
other device. RX is the line in which other device put the data it need to sent to the device.
One more thing about RS232. We know that a HIGH =+5v and LOW=0v in TTL / MCU
circuits but in RS232 a HIGH=-12V and LOW=+12V. Ya this is bit weird but it increases
the range and reliability of data transfer. Now you must be wondering how to interface this to
MCUs who understand only 0 and 5v? But you will be very happy to know that there is a
very popular IC which can do this for you! It is MAX232 from Maxim Semiconductors. I
will show you how to make a level converter using MAX232 in next tutorial.
1200
2400
4800
9600
19200
38400
57600
115200
... etc
For our example for discussion of protocol we chose the speed as 9600bps(bits per second).
As we are sending 9600 bits per second one bits takes 1/9600 seconds or 0.000104 sec or 104
uS (microsecond= 10^-6 sec).
To transmit a single byte we need to extra bits they are START BIT AND STOP BIT(more
about them latter). Thus to send a byte a total of ten bits are required so we are sending 960
bytes per second.
Note: The number of stop bits can be one or two (for simplicity we will be using single stop
bit)
There is one more bit the parity bit but again for simplicity we would not be using it)
Transmission
1. When there is no transmission the TX line sits HIGH (-12V See above para) ( STOP
CONDITION )
2. When the device needs to send data it pulls the TX line low for 104uS (This is the
start bit which is always 0)
3. then it send each bits with duration = 104uS
4. Finally it sets TX lines to HIGH for at least 104uS (This is stop bits and is always 1). I
said "at least" because after you send the stop bit you can either start new
transmission by sending a start bit or you let the TX line remain HIGH till next
transmission begin in this case the last bit is more than 104uS.
Fig- Data Transmission on RS232 line.
Reception
1. The receiving device is waiting for the start bit i.e. the RX line to go LOW (+12V see
above para).
2. When it gets start bit it waits for half bit time i.e. 104/2 = 51uS now it is in middle of
start bit it reads it again to make sure it is a valid start bit not a spike.
3. Then it waits for 104uS and now it is in middle of first bit it now reads the value of
RX line.
4. In same way it reads all 8 bits
5. Now the receiver has the data.
Fig- How the Receiver receives the data on RS232 RX li
Hello and welcome back. Continuing our discussion on RS232 serial communication in this
part we will make a RS232 level converter. In the last tutorial we saw that how RS232 level
signals differs from normal logic signals. So to interface RS232 level signals to our MCUs
we need a "Level converter". And in this tutorial we will make one.
What a level converter will do is to convert RS232 level signals (HIGH=-12V LOW=+12V)
from PC to TTL level signal (HIGH=+5V LOW=0V) to be fed to MCU and also the
opposite.
Fig - Working of RS232 level converter
As RS232 is such a common protocol there is a dedicated IC designed for this purpose of
"Level Conversion". This IC is MAX232 from Maxim. By using charge pumps it generates
high voltages(12V) and negative voltages(-12V).
Fig - Stuffs required for RS232 level converter.
Now having all the stuffs in our working table lets begin.
The Schematic
Fig - Schematic for RS232 level converter.
Assembly
Assemble the circuit according to the schematic on a small piece of general purpose PCB.
Take out two wires for the power supply(5V) and two wire that connects to the MCUs
RX/TX lines. Connect a DB9 female connector with longer wires because it connects to your
PC. But don't make it too long keep it within 1.5 meter to 2 meter that will be enough.
Take care while connecting the DB9 connector wire must be connected to proper pins as
shown above. To help you the connector has pin numbering on it.
Now the connector can be easily connected to your PC's COM port (Serial Port).
Fig - Fully assembled level converter.
Testing
It is always better to check each module separately. So we will test our converter to see if its
working fine. For testing we will use a Hyperterminal a Windows software that can be
quickly used to open COM ports and send and receive textual data. Right now you don't need
a MCU or any MCU programming. The theory of testing is that we will connect output
(RX/TX) together so any data written to COM port enters our circuit get converted to TTL
level and loops back and enter MAX232 get converted to RS232 level and enters COM port,
that's it.
Fig - Loop back testing.
1. Enter the name of connection say "testing" and select any icon for it.
Fig - Hyperterminal Main Window
2. Select your COM port in the "Connect Using Drop Down List". Note your PC might be
having more than one COM port but commonly only one is available outside the rest are
connected internally to modems etc.
Fig - Select COM port.
3) Select
Data Bits = 8
Parity = None
Stop bits = 1
4) Now the HyperTerminal is ready. Make sure that Hyperterminal has the input focus and
type something on the keyboard, they should echo on screen. Now disconnect the RX from
TX and do the same. This time you should not see any thing on screen. Now your circuit is
ready and working correctly.
If the characters are not echoing to screen your circuit is not working as expected. Check
your connections and try changing the COM port in Hyperterminal.
Welcome to the third part of my RS232 serial communication tutorial. Till now we saw the
basics of RS232 communication and made our level converter. Now its time to understand
the USART of AVR microcontroller and write the code to initialize the USART and use it to
send and receive data.
Like many microcontrollers AVR also has a dedicated hardware for serial communication
this part is called the USART - Universal Synchronous Asynchronous Receiver Transmitter.
This special hardware make your life as programmer easier. You just have to supply the data
you need to transmit and it will do the rest. As you saw serial communication occurs at
standard speeds of 9600,19200 bps etc and this speeds are slow compared to the AVR CPUs
speed. The advantage of hardware USART is that you just need to write the data to one of the
registers of USART and your done, you are free to do other things while USART is
transmitting the byte.
Also the USART automatically senses the start of transmission of RX line and then inputs the
whole byte and when it has the byte it informs you(CPU) to read that data from one of its
registers.
The USART of AVR is very versatile and can be setup for various different mode as required
by your application. In this tutorial I will show you how to configure the USART in a most
common configuration and simply send and receive data. Later on I will give you my library
of USART that can further ease you work. It will be little complicated (but more useful) as it
will have a FIFO buffer and will use interrupt to buffer incoming data so that you are free to
anything in your main() code and read the data only when you need. All data is stored into a
nice FIFO(first in first out queue) in the RAM by the ISR.
UDR - USART Data Register : Actually this is not one but two register but when you
read it you will get the data stored in receive buffer and when you write data to it goes
into the transmitters buffer. This important to remember it.
UCSRA - USART Control and status Register A : As the name suggests it is used to
configure the USART and it also stores some status about the USART. There are two
more of this kind the UCSRB and UCSRC.
UBRRH and UBRRH : This is the USART Baud rate register, it is 16BIT wide so
UBRRH is the High Byte and UBRRL is Low byte. But as we are using C language it
is directly available as UBRR and compiler manages the 16BIT access.
So the connection of AVR and its internal USART can be visualized as follows.
Fig- AVR USART registers.
Registers Explained
In order to write programs that uses the USART you need to understand what each register's
importance. The scheme behind using the AVR USART is same as with any other internal
peripheral (say ADC). So if you are new to this topic please see this tutorial, it shows you the
basic idea of using peripherals.
I am not going to repeat what is already there in the datasheets, I will just tell about what is
required for a quick startup. The datasheets of AVR provides you with all the details of every
bit of every register so please refer to it for detailed info. Note bit names with RED
background are of our interest here.
*****************************************
Bit No 7 6 5 4 3 2 1 0
Name RXC TXC UDRE FE DOR PE U2X MPCM
Initial Val 0 0 1 0 0 0 0 0
RXC this bit is set when the USART has completed receiving a byte from the host (may be
your PC) and the program should read it from UDR
TXC This bit is set (1) when the USART has completed transmitting a byte to the host and
your program can write new data to USART via UDR
UCSRB: USART Control and Status Register B
*****************************************
Bit No 7 6 5 4 3 2 1 0
Name RXCIE TXCIE UDRIE RXEN TXEN UCSZ2 RXB8 TXB8
Initial Val 0 0 0 0 0 0 0 0
RXCIE: Receive Complete Interrupt Enable - When this bit is written one the the
associated interrupt is enabled.
TXCIE: Transmit Complete Interrupt Enable - When this bit is written one the the
associated interrupt is enabled.
RXEN: Receiver Enable - When you write this bit to 1 the USART receiver is enabled. The
normal port functionality of RX pin will be overridden. So you see that the associated I/O
pin now switch to its secondary function,i.e. RX for USART.
For our first example we won't be using interrupts so we set UCSRB as follows
UCSRB=(1<<RXEN)|(1<<TXEN);
*****************************************
Bit No 7 6 5 4 3 2 1 0
Name URSEL UMSEL UPM1 UPM0 USBS UCSZ1 UCSZ0 UCPOL
Initial Val 0 0 0 0 0 0 0 0
IMPORTANT : The UCSRC and the UBRRH (discussed below) register shares same
address so to determine which register user want to write is decided with the 7th(last) bit of
data if its 1 then the data is written to UCSRC else it goes to UBRRH. This seventh bit is
called the
UMSEL: USART Mode Select - This bit selects between asynchronous and synchronous
mode. As asynchronous mode is more popular with USART we will be using that.
UMSEL Mode
0 Asynchronous
1 Synchronous
USBS: USART Stop Bit Select - This bit selects the number of stop bits in the data transfer.
UCSZ: USART Character size - These three bits (one in the UCSRB) selects the number of
bits of data that is transmited in each frame. Normally the unit of data in MCU is 8BIT (C
type "char") and this is most widely used so we will go for this. Otherwise you can select
5,6,7,8 or 9 bit frames!
UCSRC=(1<<URSEL)|(3<<UCSZ0);
*********************************
This is the USART Baud rate register, it is 16BIT wide so UBRRH is the High Byte and
UBRRL is Low byte. But as we are using C language it is directly available as UBRR and
compiler manages the 16BIT access. This register is used by the USART to generate the data
transmission at specified speed (say 9600Bps). To know about baud rate see the previous
tutorial. UBRR value is calculated according to following formula.
Baud Rate is the required communication speed say 19200 bps (see previous tutorial for more
info).
Example:
For above configuration our UBRR value comes to be 51.083 so we have to set
UBRR=51;
in our initialization section. Note UBRR can hold only integer value. So it is better to use the
baud rates that give UBRR value that are purely integer or very close to it. So if your UBRR
value comes to be 7.68 and you decided to use UBRR=8 then it has high error percentage,
and communication is unreliable!
Initialization of USART
Before using the USART it must be initialized properly according to need. Having the
knowledge of RS232 communication and Internal USART of AVR you can do that easily.
We will create a function that will initialize the USART for us.
#include <avr/io.h>
#include <inttypes.h>
*/
UCSRC=(1<<URSEL)|(3<<UCSZ0);
Now we have a function that initializes the USART with a given UBRR value.