BlinkTalksToPC ProjectProject DescriptionThis is a follow-on to the BlinkFromScratch program. BlinkTalksToPC adds serial communications to the same setup. In handling input from the PC it will be a good example of using interrupts rather than polling.Every second, when the pin 13 LED is flipped, the microcontroller will report the status of the two LEDs to HyperTerm running on the PC. If you enter a 'y' or an 'n' in HyperTerm, the microcontroller will ensure that the pin 12 LED is on or off respectively. Project Hardware SetupIf you think this looks like the BlinkFromScratch hardware setup, you're right. It is the same jpg. The difference is how we are using the serial interface. In BlinkFromScratch I used the P3 adapter only to download the program into the ATmega168. This time we will use that same adapter to talk with HyperTerm on the PC.
Project SoftwareWe will only look at WinAVR code, specifically the code used to access the PC. This will be high lighted in red. We probably could have used some built in functions like printf to do this, but we are trying to understand it at the bits and register level. 1. USART SetupWe need to have a number of variables set up for our communication with the PC. We have 5 standard messages so we won't have to construct any on the fly. We also have the queue and 2 counters that will be used during transmit. But the most difficult thing to set up is the registers. We just clear out UCSR0A, but UCSR0B enables/disables transmit and receive, as well as, the transmit and receive interrupts. If we are going to use interrupt handlers rather than polling they must be enabled. Which bit is which is specified in the code comments. UCSR0C gives more information about the connection, parity, stop bits and so forth - also detailed in the comments. UBRR0 (both high and low) is the register that describes the baud rate for this transaction. The formula is (sys clock / (16 * baud rate)) - 1. I have done the calculations for both 9600 baud and 19200 baud. I set up the register for 9600 baud. At the PC end you need to set up HyperTerm to match. 2. USART TransmitTransmit will require one method (or function) and one ISR (interrupt service routine). Any time we want to send something to the PC we will call the sendmsg function passing it a message. That function will send a CRLF (carriage return line feed), initialize the counters and fill the queue with the message. It then sends the first character - puts in UDR0. Notice that sendcntr is initialized to 1 since we have sent that first character. After this it is up to the ISR to send the rest of the message. Every time a character is tranmitted across the wire, the transmit interrupt fires. So ISR(USART_TX_vect) grabs the next character from the queue and puts it in UDR0. When that's send the interrupt fires again and the ISR does its thing again, until the queue is empty. Most of the messages are being sent from the timer's ISR. I have added a flag so it knows the status of the LEDs. At the end of the ONE_SEC if statement it uses a switch statement to determine which message to send. 3. USART ReceiveThe receive side is all interrupt driven. No polling here. When an incoming character lands in UDR0 a receive interrupt is fired. ISR(USART_RX_vect) handles the interrupt, pulls in the character and checks to see what it is. If it is a 'y' (lower or upper case) it ensures bit 4 of Port B is on. If it is a 'n' (lower or upper case) it ensures bit 4 of Port B is off. WinAVR Example
//TIMER0 interrupt routine
#include < inttypes.h >
#include < avr/io.h >
#include < avr/interrupt.h >
#include < avr/sleep.h >
#define ONE_SEC 100
void initPorts (void);
void initTimer (void);
void initUSART (void);
void sendmsg (char *s);
ISR (TIMER0_OVF_vect);
ISR (USART_TX_vect);
ISR (USART_RX_vect);
static unsigned int time_count; // counts overflows
unsigned char qcntr = 0, sendcntr = 0;
unsigned char queue[50];
char msgHello[] = {"Welcome to BlinkTalksToPC"};
char msg0[] = {"LED 13 is off, LED 12 is off"};
char msg1[] = {"LED 13 is on, LED 12 is off"};
char msg2[] = {"LED 13 is off, LED 12 is on"};
char msg3[] = {"LED 13 is on, LED 12 is on"};
/* * * * * * * * * * * * * * * * * * * * *
main will call the initialization for ports and timer,
then sit in a loop waiting for the timer interrupt.
* * * * * * * * * * * * * * * * * * * * */
int main (void) {
initPorts();
initTimer();
initUSART();
sendmsg(msgHello);
while (1) {
; // do nothing
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * *
We will put the LEDs on BBB pins 12 and 13 --> PortB 4:5
We will put the push button on pin 8 --> PortB 0 and set it low
* * * * * * * * * * * * * * * * * * * * */
void initPorts (void) {
DDRB = 0x30; //set two middle bits (4,5) for output
PORTB |= _BV(5); // blink LED starts on
}
/* * * * * * * * * * * * * * * * * * * * *
We will set the timer up for 1 second counts.
The ATmega168 has a 16 MHz resonator.
Use prescalar to divide it by 1024 giving 15625 Hz
Set TCNT0 to overflow 100 times a second by starting it at 100.
I guess the output compare register needs to be set, but we are in normal mode.
Unmask the Timer 0 overflow interrupt and enable interrupts
* * * * * * * * * * * * * * * * * * * * */
void initTimer (void) {
TCCR0B = 0x05; // set TIMER0 prescalar to clk/1024
TCNT0 = 100; // set TCNT0 to start at 100 giving 156 counts to overflow
OCR0A = 0x00; // output compare register initialized but not used.
TIMSK0 = 0x01; // unmask TIMER0 overflow interrupt
sei(); // enable interrupts
time_count = 0; // init to no overflows
}
/* * * * * * * * * * * * * * * * * * * * *
We will send an initial message to the PC via USART
To calculate the UBRR:
(sys clock / (16 * baud rate)) - 1
(16MHz / (16 * 9600 Hz)) -1 = 103 = 0x67
(16MHz / (16 * 19200 Hz)) -1 = 51 = 0x33
For UCSR0B 0xd8 1101 1000
bit 7 is receive interrupt enable
bit 6 is transmit interrupt enable
bit 4 is receive enable
bit 3 is transmit enable
For UCSR0C 0x86 1000 0110
bit 7 is always set to 1
bit 6 0 is asynch
bits 5:4 00 = no parity
bit 3 0 = 1 stop bit
bits2:0 010 = 7 bits
* * * * * * * * * * * * * * * * * * * * */
void initUSART (void) {
UCSR0A = 0x00; // initialize control reg.
UCSR0B = 0xd8; // enable transmitter, receiver
//and transmit and receive interrupts
UCSR0C = 0x86; // asynch, no parity, 1 stop bit, 8 data bits
UBRR0H = 0x00; // set up baud rate for 9600 top of reg
UBRR0L = 0x67; // set up baud rate for 9600 bottom of reg
}
/* * * * * * * * * * * * * * * * * * * * *
Every overflow interrupt increment the counter.
If we have reached the 1 second count, do the things.
* * * * * * * * * * * * * * * * * * * * */
ISR (TIMER0_OVF_vect) {
unsigned char msgFlag = 0;
TCNT0 = 100; //set for timeout
++time_count; // increment the loop counter
if (time_count == ONE_SEC) { // have we reached the 1 second count?
if (PORTB & _BV(5)) { // is output bit 5 on??
PORTB &= ~_BV(5); // clear bit, turn LED off
} else {
PORTB |= _BV(5); // set bit, turn LED on
msgFlag += 1;
}
time_count = 0; // reset the loop counter
if ((PINB & _BV(0)) == 0) { // is input bit 0 low, button pushed??
if (PORTB & _BV(4)) {
PORTB &= ~_BV(4); // clear bit, turn LED off
} else {
PORTB |= _BV(4); // set bit, turn LED on
}
}
PORTB |= _BV(0); // reset the push button high, not pushed.
if (PORTB & _BV(4)) {
msgFlag += 2;
}
switch (msgFlag) {
case 0: sendmsg(msg0); break;
case 1: sendmsg(msg1); break;
case 2: sendmsg(msg2); break;
case 3: sendmsg(msg3); break;
}
}
}
/* * * * * * * * * * * * * * * * * * * * *
Okay somebody wants me to send a message.
Set up the counters and send the first character.
Let the ISR send the rest of the message.
* * * * * * * * * * * * * * * * * * * * */
void sendmsg (char *s) {
qcntr = 0;
sendcntr = 1;
queue[qcntr++] = 0x0d; // load queue with CR
queue[qcntr++] = 0x0a; // load queue with LF
while (*s) queue[qcntr++] = *s++; // move chars from msg string to queue
UDR0 = queue[0]; // push first char into send buff
}
/* * * * * * * * * * * * * * * * * * * * *
Hey, there's still something to transmit.
Keep sending it until the counters are equal.
* * * * * * * * * * * * * * * * * * * * */
ISR (USART_TX_vect) {
if (qcntr != sendcntr) UDR0 = queue[sendcntr++]; // move next char to send buffer
}
/* * * * * * * * * * * * * * * * * * * * *
Hey, the PC sent something. Check to see if it is a 'y' or 'n'.
If so set the LED on or off, don't worry about case.
If not, forget it.
* * * * * * * * * * * * * * * * * * * * */
ISR (USART_RX_vect) {
unsigned char ch;
ch = UDR0;
switch (ch) {
case 'y': case 'Y':
PORTB |= _BV(4); // set bit, turn LED on
break;
case 'n': case 'N':
PORTB &= ~_BV(4); // clear bit, turn LED off
break;
default:
break;
}
}
|