1 /*****************************************************************************
\r
2 ** FILE IDENTIFICATION
\r
4 ** Name: serial_lcd.cpp
\r
5 ** Purpose: USART driven HD44780 LCD Driver
\r
6 ** Programmer: Kevin Rosenberg <kevin@rosenberg.net>
\r
7 ** (AVR Freaks member kmr)
\r
8 ** Date Started: Dec 2007
\r
10 ** Copyright (c) 2007-2008 by Kevin Rosenberg. All rights reserved.
\r
12 ** Compilers supported:
\r
13 ** - WinAVR-20071221 (includes avr-libc 1.6 required for proper _delay_us)
\r
15 ** - Imagecraft AVR 7
\r
18 ** See accompaning LICENSE file
\r
21 ** See accompaning CHANGES file for modifications log from original
\r
22 ******************************************************************************/
\r
24 #include "serial_lcd.h"
\r
26 #if defined(__GNUC__)
\r
29 .high = (unsigned char) (DWEN & WDTON & RSTDISBL & BODLEVEL1 & BODLEVEL2),
\r
30 .extended = EFUSE_DEFAULT,
\r
34 // Number of PWM brightness levels supported
\r
35 #define LED_BRIGHTNESS_LEVELS 8
\r
37 ////// LCD Commands ///////
\r
38 // Turn on power to the display, no cursor
\r
40 // Clear display command
\r
41 #define LCD_CLR 0x01
\r
43 #define LCD_4_Bit 0x20
\r
45 #define LCD_8_Bit 0x30
\r
46 // Set number of lines
\r
47 #define LCD_4_Line 0x08
\r
50 // Set character font
\r
51 #define LCD_Font 0x04
\r
52 // Turn the cursor on
\r
53 #define LCD_CURSOR_ON 0x02
\r
54 // Turn on cursor blink
\r
55 #define LCD_CURSOR_BLINK 0x01
\r
57 ////// Serial command codes ///////
\r
58 // ASCII control code to set brightness level
\r
59 // Next byte is brightness ranging from 0 (no backlight) to 255 (max backlight)
\r
60 #define LED_SET_BRIGHTNESS 0xFB
\r
61 // ASCII control code turns on LED
\r
62 #define LED_SW_ON 0xFC
\r
63 // ASCII control code turns off LED
\r
64 #define LED_SW_OFF 0xFD
\r
65 // Character generator RAM command
\r
66 #define REG_MODE 0xFE
\r
68 // Circular buffer for UART RX
\r
69 #define UART_RX_BUFFER_SIZE 48
\r
70 #if defined(__IMAGECRAFT__)
\r
72 volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE];
\r
75 NO_INIT_DECLARE(volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE]);
\r
78 // PWM Patterns (8-brightness levels)
\r
79 static FLASH_DECLARE(const unsigned char ledPwmPatterns[]) =
\r
81 0x10, // 0b00010000 0
\r
82 0x11, // 0b00010001 1
\r
83 0x4A, // 0b01001010 2
\r
84 0x55, // 0b01010101 3
\r
85 0x5D, // 0b01011101 4
\r
86 0xEE, // 0b11101110 5
\r
87 0x7F, // 0b11110111 6
\r
88 0xFF, // 0b11111111 7
\r
91 // register variables
\r
92 #if defined(__IMAGECRAFT__)
\r
93 #pragma global_register ledPwmCount:20 sUartRxHead:21 sUartRxTail:22 ledPwmCycling:23
\r
94 unsigned char ledPwmCount, sUartRxHead, sUartRxTail, ledPwmCycling;
\r
97 REGISTER_VAR(unsigned char ledPwmCount, "r4", 15);
\r
98 REGISTER_VAR(unsigned char sUartRxHead, "r5", 14);
\r
99 REGISTER_VAR(unsigned char sUartRxTail, "r6", 13);
\r
100 REGISTER_VAR(unsigned char ledPwmCycling, "r7", 12);
\r
103 #define ledPwmPattern GPIOR0
\r
104 #define ledStatus GPIOR1
\r
105 #define BIT_led_on REGISTER_BIT(GPIOR1,0)
\r
107 // Function declarations
\r
108 void LcdWriteCmd (unsigned char);
\r
109 void LcdWriteData (unsigned char);
\r
110 unsigned char LcdBusyWait (void);
\r
111 static unsigned char WaitRxChar (void);
\r
113 // ImageCraft doesn't support inline functions, so use preprocessor
\r
114 #if defined(__IMAGECRAFT__)
\r
115 #define LedTimerStop() TCCR0B = 0
\r
116 // Start with 256 prescaler
\r
117 #define LedTimerStart() TCCR0B = (1<<CS02)
\r
120 // Using a platform that has proper inline functions
\r
121 INLINE_FUNC_DECLARE(static void LedTimerStop (void));
\r
122 static inline void LedTimerStop (void) {
\r
126 INLINE_FUNC_DECLARE(static void LedTimerStart (void));
\r
127 static inline void LedTimerStart (void) {
\r
128 TCCR0B = (1<<CS02); // Start with 256 prescaler
\r
132 INLINE_FUNC_DECLARE(static void LedPwmInit (void));
\r
133 static inline void LedPwmInit (void) {
\r
134 // setup PWM to run at 1.25ms per interrupt.
\r
135 // This allows 8 levels of brightness at minimum of 100Hz flicker rate.
\r
136 TCCR0A = (1<<WGM01); // CTC mode
\r
137 TCCR0B = 0; // timer off
\r
138 OCR0A = 72; // 1.25ms with CLK/256 prescaler @ 14.7456MHz
\r
139 TIMSK = (1<<OCIE0A); // Turn on timer0 COMPA intr (all other timer intr off)
\r
140 LED_PORT &= ~(1<<LED_PIN); // Ensure LED is off during initialization
\r
141 LED_DIR |= (1<<LED_PIN); // Ensure LED is output direction
\r
142 BIT_led_on = 0; // note that LED is off
\r
143 ledPwmPattern = 0xFF; // maximum brightness
\r
147 INLINE_FUNC_DECLARE(static void LedPwmSetBrightness (unsigned char brightness));
\r
148 static inline void LedPwmSetBrightness (unsigned char brightness) {
\r
149 unsigned char ledPwmPos;
\r
151 if (brightness == 0) { // turn backlight off for 0 brightness
\r
156 LED_PORT &= ~(1<<LED_PIN);
\r
161 ledPwmPos = (brightness * (LED_BRIGHTNESS_LEVELS-1) + 127) >> 8;
\r
162 // Below is probably not required, but ensures we don't exceed array
\r
163 if (ledPwmPos > LED_BRIGHTNESS_LEVELS - 1)
\r
164 ledPwmPos = LED_BRIGHTNESS_LEVELS - 1;
\r
166 ledPwmPattern = PGM_READ_BYTE (&ledPwmPatterns[ledPwmPos]);
\r
168 ledPwmCycling = ledPwmPattern;
\r
169 if (ledPwmPos >= LED_BRIGHTNESS_LEVELS-1) { // maximum brightness
\r
170 // don't need PWM for continuously on
\r
173 LED_PORT |= (1<<LED_PIN);
\r
182 INLINE_FUNC_DECLARE(static void LedPwmSwitchOff (void));
\r
183 static inline void LedPwmSwitchOff (void) {
\r
184 LED_PORT &= ~(1<<LED_PIN);
\r
189 INLINE_FUNC_DECLARE(static void LedPwmSwitchOn (void));
\r
190 static inline void LedPwmSwitchOn (void) {
\r
192 if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM
\r
194 LED_PORT |= (1<<LED_PIN); // keep LED on at all times
\r
200 // Actual baud rate = 9600 BAUD, (0.0% error) @ 14.7456MHz
\r
201 // Actual baud rate = 19.2K BAUD, (0.0% error) @ 14.7456MHz
\r
202 // Actual baud rate = 38.4K BAUD, (0.0% error) @ 14.7456MHz
\r
203 // Actual baud rate = 115.2K BAUD, (0.0% error) @ 14.7456MHz
\r
204 #define BAUD_4800 191
\r
205 #define BAUD_9600 95
\r
206 #define BAUD_14400 63
\r
207 #define BAUD_19200 47
\r
208 #define BAUD_28800 31
\r
209 #define BAUD_38400 23
\r
210 #define BAUD_57600 15
\r
211 #define BAUD_76800 11
\r
212 #define BAUD_115200 7
\r
214 FLASH_DECLARE(static const unsigned char BaudLookupTable[])
\r
215 = {BAUD_115200, BAUD_38400, BAUD_19200, BAUD_9600};
\r
217 INLINE_FUNC_DECLARE(static unsigned char GetUsartBaud (void));
\r
218 static inline unsigned char GetUsartBaud (void) {
\r
219 // Get BAUD rate jumper settings
\r
220 unsigned char BaudSelectJumpersValue = BAUD_PIN_REG;
\r
221 // Mask off unwanted bits
\r
222 BaudSelectJumpersValue &= (1<<BAUD_J2) | (1<<BAUD_J1);
\r
223 // BAUD_J2 = PD.3, BAUD_J1 = PD.2
\r
224 // This is two bits too far to the left and the array index will
\r
225 // increment by 4, instead of 1.
\r
226 // Shift BAUD_J2 & BAUD_J1 right by two bit positions for proper array indexing
\r
227 BaudSelectJumpersValue = (BaudSelectJumpersValue >> BAUD_J1);
\r
229 return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);
\r
233 INLINE_FUNC_DECLARE(static void UsartInit (void));
\r
234 static inline void UsartInit(void) {
\r
235 // Initialize USART
\r
236 UCSRB = 0; // Disable while setting baud rate
\r
238 UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 bit data
\r
239 UBRRL = GetUsartBaud (); // Set baud rate
\r
240 UBRRH = 0; // Set baud rate hi
\r
241 UCSRB = (1<<RXEN)|(1<<RXCIE); // RXEN = Enable
\r
242 sUartRxHead = 0; // register variable, need to explicitly zero
\r
243 sUartRxTail = 0; // register variable, need to explicitly zero
\r
246 CTS_PORT |= (1<<CTS_PIN); // bring signal high
\r
247 CTS_DIR |= (1<<CTS_PIN); // CTS is active low
\r
251 #if defined(__IMAGECRAFT__)
\r
252 // Clock cycle = 67nS @ 14.7456MHz
\r
253 // Delay resolution ~ 1uS @ 14.7456MHz
\r
254 // So this function is only accurate at near above frequency
\r
255 void _delay_us (unsigned int d) {
\r
262 INLINE_FUNC_DECLARE(static void LcdInit (void));
\r
263 static inline void LcdInit (void) {
\r
264 // Set BAUD_J2:BAUD_J1 PULL-UPS active
\r
265 LCD_CONTROL_PORT = (1<<BAUD_J2) | (1<<BAUD_J1);
\r
266 // Set LCD_control BAUD_J2:BAUD_J1 to inputs
\r
267 LCD_CONTROL_DIR = 0xF2;
\r
270 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH
\r
272 // Initialize the LCD Data AVR I/O
\r
273 LCD_DATA_DIR = 0xFF; // Set LCD_DATA_PORT as all outputs
\r
274 LCD_DATA_PORT = 0x00; // Set LCD_DATA_PORT to logic low
\r
276 // Initialize the LCD controller
\r
277 LCD_CONTROL_PORT &= ~(1<<LCD_RS);
\r
278 LcdWriteData (DATA_8);
\r
281 LcdWriteData (DATA_8);
\r
284 LcdWriteData (DATA_8);
\r
285 LCD_CONTROL_PORT |= (1<<LCD_RS);
\r
287 // The display will be out into 8 bit data mode here
\r
288 LcdWriteCmd (LCD_8_Bit | LCD_4_Line); // Set 8 bit data, 4 display lines
\r
289 LcdWriteCmd (LCD_ON); // Power up the display
\r
290 LcdWriteCmd (LCD_CLR); // Power up the display
\r
294 MCUSR = 0; // clear all reset flags
\r
297 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
298 WDTCSR = (1<<WDE)|(1<<WDP3)|(1<<WDP0); // 1024K cycles, 8.0sec
\r
299 MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode
\r
304 // Initialize the AVR USART
\r
309 unsigned char tail = sUartRxTail; // explicitly set order of volatile access
\r
310 if (tail != sUartRxHead) { // Check if UART RX buffer has a character
\r
311 unsigned char rx_byte = WaitRxChar();
\r
313 // avoid use of switch statement as ImageCraft and GCC produce signifcantly
\r
314 // more code for a switch statement than a sequence of if/else if
\r
315 if (rx_byte == LED_SW_OFF) {
\r
317 } else if (rx_byte == LED_SW_ON) {
\r
319 } else if (rx_byte == LED_SET_BRIGHTNESS) {
\r
320 rx_byte = WaitRxChar(); // read next byte which will be brightness
\r
321 LedPwmSetBrightness(rx_byte);
\r
322 } else if (rx_byte == REG_MODE) {
\r
323 LcdWriteCmd (WaitRxChar()); // Send LCD command character
\r
325 LcdWriteData (rx_byte); // Send LCD data character
\r
328 // No characters waiting in RX buffer
\r
332 MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags
\r
333 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
334 WDTCSR &= ~(1<<WDE); // watchdog timer off
\r
343 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
344 WDTCSR &= ~(1<<WDCE);
\r
351 void LcdWriteCmd (unsigned char Cmd) {
\r
353 LCD_DATA_PORT = Cmd; // BusyWait leaves RS low in "Register mode"
\r
355 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
357 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
359 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character mode"
\r
362 void LcdWriteData (unsigned char c) {
\r
364 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character Mode"
\r
367 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
369 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
372 unsigned char LcdBusyWait (void) {
\r
373 unsigned char LCDStatus;
\r
374 LCD_DATA_DIR = 0x00; // Set LCD data port to inputs
\r
375 LCD_CONTROL_PORT &= ~(1<<LCD_RS); // Set display to "Register mode"
\r
376 LCD_CONTROL_PORT |= (1<<LCD_RW); // Put the display in the read mode
\r
380 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
382 LCDStatus = LCD_DATA_PIN_REG;
\r
383 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
384 } while (LCDStatus & (1<<LCD_BUSY));
\r
385 LCD_CONTROL_PORT &= ~(1<<LCD_RW); // Put display in write mode
\r
386 LCD_DATA_DIR = 0xFF; // Set LCD data port to outputs
\r
387 return (LCDStatus);
\r
390 #if defined(__IMAGECRAFT__)
\r
391 #pragma interrupt_handler usart_rx_handler:iv_USART0_RXC
\r
392 void usart_rx_handler(void)
\r
397 unsigned char rx = UDR;
\r
398 // Calculate next buffer position.
\r
399 unsigned char tmphead = sUartRxHead;
\r
400 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
404 // store in buffer if there is room
\r
405 if (tmphead != sUartRxTail) {
\r
406 sUartRxHead = tmphead; // Store new index.
\r
407 sUartRxBuf[tmphead] = rx;
\r
410 // check if buffer is now full, if so switch CTS to inactive
\r
411 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
415 // CTS active if still room in buffer
\r
416 if (tmphead != sUartRxTail) {
\r
417 CTS_PORT |= (1<<CTS_PIN);
\r
419 // no room left in buffer, so make CTS inactive
\r
420 CTS_PORT &= ~(1<<CTS_PIN);
\r
425 static unsigned char WaitRxChar (void) {
\r
426 // waits for next RX character, then return it
\r
427 unsigned char tail;
\r
429 tail = sUartRxTail; // explicitly set order of volatile variable access
\r
431 } while (sUartRxHead == tail); // while buffer is empty
\r
434 // turn off interrupts so that if UART char ready to be stored, then storage
\r
435 // will occur after CTS pin is set active. This allows UART ISR to set
\r
436 // CTS inactive if byte fills buffer after we remove current byte
\r
440 // increment tail position
\r
441 if (tail == UART_RX_BUFFER_SIZE-1)
\r
447 CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte
\r
448 sei(); // Allow UART ISR to read character and change potentially change CTS
\r
451 return sUartRxBuf[sUartRxTail];
\r
454 #if defined(__IMAGECRAFT__)
\r
455 #pragma interrupt_handler timer0_compa_handler:iv_TIMER0_COMPA
\r
456 void timer0_compa_handler(void)
\r
458 ISR(TIMER0_COMPA_vect)
\r
461 sei(); // Okay to allow USART interrupts while controlling LED PWM
\r
463 // Set current LED state based on cycling variable
\r
464 if (ledPwmCycling & 0x01) {
\r
465 LED_PORT |= (1<<LED_PIN);
\r
467 LED_PORT &= ~(1<<LED_PIN);
\r
470 // Update cycling variable for next interrupt
\r
471 if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {
\r
473 ledPwmCycling = ledPwmPattern;
\r
476 ledPwmCycling >>= 1;
\r