Home

BlinkTalksToPC Project

Project Description

This 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 Setup

If 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.

blinkScratch.jpg

Project Software

We 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 Setup

We 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 Transmit

Transmit 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 Receive

The 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;
	}

}