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
14 ** - IAR AVR 4.30 and 5.10
\r
15 ** - Imagecraft AVR 7.16
\r
16 ** - Codevision AVR 2.02.06
\r
19 ** See accompaning LICENSE file
\r
22 ** See accompaning CHANGES file for modifications log from original
\r
23 ******************************************************************************/
\r
25 #include "serial_lcd.h"
\r
27 #if defined(__GNUC__)
\r
30 .high = (unsigned char) (DWEN & WDTON & RSTDISBL & BODLEVEL1 & BODLEVEL2),
\r
31 .extended = EFUSE_DEFAULT,
\r
35 // Number of PWM brightness levels supported
\r
36 #define LED_BRIGHTNESS_LEVELS 8
\r
38 ////// LCD Commands ///////
\r
39 // Turn on power to the display, no cursor
\r
41 // Clear display command
\r
42 #define LCD_CLR 0x01
\r
44 #define LCD_4_Bit 0x20
\r
46 #define LCD_8_Bit 0x30
\r
47 // Set number of lines
\r
48 #define LCD_4_Line 0x08
\r
51 // Set character font
\r
52 #define LCD_Font 0x04
\r
53 // Turn the cursor on
\r
54 #define LCD_CURSOR_ON 0x02
\r
55 // Turn on cursor blink
\r
56 #define LCD_CURSOR_BLINK 0x01
\r
58 ////// Serial command codes ///////
\r
59 // ASCII control code to set brightness level
\r
60 // Next byte is brightness ranging from 0 (no backlight) to 255 (max backlight)
\r
61 #define LED_SET_BRIGHTNESS 0xFB
\r
62 // ASCII control code turns on LED
\r
63 #define LED_SW_ON 0xFC
\r
64 // ASCII control code turns off LED
\r
65 #define LED_SW_OFF 0xFD
\r
66 // Character generator RAM command
\r
67 #define REG_MODE 0xFE
\r
69 // Circular buffer for UART RX
\r
70 #define UART_RX_BUFFER_SIZE 48
\r
71 #if defined(__IMAGECRAFT__)
\r
73 volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE];
\r
76 NO_INIT_DECLARE(volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE]);
\r
79 // PWM Patterns (8-brightness levels)
\r
80 static FLASH_DECLARE(const unsigned char ledPwmPatterns[]) =
\r
82 0x10, // 0b00010000 0
\r
83 0x11, // 0b00010001 1
\r
84 0x4A, // 0b01001010 2
\r
85 0x55, // 0b01010101 3
\r
86 0x5D, // 0b01011101 4
\r
87 0xEE, // 0b11101110 5
\r
88 0x7F, // 0b11110111 6
\r
89 0xFF, // 0b11111111 7
\r
92 // register variables
\r
93 #if defined(__IMAGECRAFT__)
\r
94 #pragma global_register ledPwmCount:20 sUartRxHead:21 sUartRxTail:22 ledPwmCycling:23
\r
95 unsigned char ledPwmCount, sUartRxHead, sUartRxTail, ledPwmCycling;
\r
97 #elif defined(__CODEVISIONAVR__)
\r
99 register unsigned char ledPwmCount, sUartRxHead, sUartRxTail, ledPwmCycling;
\r
102 // Use avr_compat register variables
\r
104 REGISTER_VAR(unsigned char ledPwmCount, "r4", 15);
\r
105 REGISTER_VAR(unsigned char sUartRxHead, "r5", 14);
\r
106 REGISTER_VAR(unsigned char sUartRxTail, "r6", 13);
\r
107 REGISTER_VAR(unsigned char ledPwmCycling, "r7", 12);
\r
110 #define ledPwmPattern GPIOR0
\r
111 #if defined(REGISTER_BIT)
\r
112 #define BIT_led_on REGISTER_BIT(GPIOR1,0)
\r
113 #define BACKLIGHT_STATUS() BIT_led_on
\r
114 #define BACKLIGHT_OFF() BIT_led_on = 0
\r
115 #define BACKLIGHT_ON() BIT_led_on = 1
\r
117 #define BACKLIGHT_STATUS() (GPIOR1 & 0x01)
\r
118 #define BACKLIGHT_OFF() GPIOR1 &=~ ~0x01
\r
119 #define BACKLIGHT_ON() GPIOR1 |= 0x01
\r
122 // Function declarations
\r
123 void LcdWriteCmd (unsigned char);
\r
124 void LcdWriteData (unsigned char);
\r
125 unsigned char LcdBusyWait (void);
\r
126 static unsigned char WaitRxChar (void);
\r
128 // ImageCraft/Codevision don't support inline functions, so use preprocessor
\r
129 #if defined(__IMAGECRAFT__) || defined(__CODEVISIONAVR__)
\r
130 #define LedTimerStop() TCCR0B = 0
\r
131 // Start with 256 prescaler
\r
132 #define LedTimerStart() TCCR0B = (1<<CS02)
\r
135 // Using a platform that has proper inline functions
\r
136 INLINE_FUNC_DECLARE(static void LedTimerStop (void));
\r
137 static inline void LedTimerStop (void) {
\r
141 INLINE_FUNC_DECLARE(static void LedTimerStart (void));
\r
142 static inline void LedTimerStart (void) {
\r
143 TCCR0B = (1<<CS02); // Start with 256 prescaler
\r
147 INLINE_FUNC_DECLARE(static void LedPwmInit (void));
\r
148 static inline void LedPwmInit (void) {
\r
149 // setup PWM to run at 1.25ms per interrupt.
\r
150 // This allows 8 levels of brightness at minimum of 100Hz flicker rate.
\r
151 TCCR0A = (1<<WGM01); // CTC mode
\r
152 TCCR0B = 0; // timer off
\r
153 OCR0A = 72; // 1.25ms with CLK/256 prescaler @ 14.7456MHz
\r
154 TIMSK = (1<<OCIE0A); // Turn on timer0 COMPA intr (all other timer intr off)
\r
155 LED_PORT &= ~(1<<LED_PIN); // Ensure LED is off during initialization
\r
156 LED_DIR |= (1<<LED_PIN); // Ensure LED is output direction
\r
157 BACKLIGHT_OFF(); // note that LED is off
\r
158 ledPwmPattern = 0xFF; // maximum brightness
\r
162 INLINE_FUNC_DECLARE(static void LedPwmSetBrightness (unsigned char brightness));
\r
163 static inline void LedPwmSetBrightness (unsigned char brightness) {
\r
164 unsigned char ledPwmPos;
\r
166 if (brightness == 0) { // turn backlight off for 0 brightness
\r
167 if (BACKLIGHT_STATUS()) {
\r
171 LED_PORT &= ~(1<<LED_PIN);
\r
176 ledPwmPos = (brightness * (LED_BRIGHTNESS_LEVELS-1) + (unsigned int) 127) >> 8;
\r
177 // Below is probably not required, but ensures we don't exceed array
\r
178 if (ledPwmPos > LED_BRIGHTNESS_LEVELS - 1)
\r
179 ledPwmPos = LED_BRIGHTNESS_LEVELS - 1;
\r
181 ledPwmPattern = PGM_READ_BYTE (&ledPwmPatterns[ledPwmPos]);
\r
183 ledPwmCycling = ledPwmPattern;
\r
184 if (ledPwmPos >= LED_BRIGHTNESS_LEVELS-1) { // maximum brightness
\r
185 // don't need PWM for continuously on
\r
186 if (BACKLIGHT_STATUS()) {
\r
188 LED_PORT |= (1<<LED_PIN);
\r
191 if (BACKLIGHT_STATUS()) {
\r
197 INLINE_FUNC_DECLARE(static void LedPwmSwitchOff (void));
\r
198 static inline void LedPwmSwitchOff (void) {
\r
199 LED_PORT &= ~(1<<LED_PIN);
\r
204 INLINE_FUNC_DECLARE(static void LedPwmSwitchOn (void));
\r
205 static inline void LedPwmSwitchOn (void) {
\r
207 if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM
\r
209 LED_PORT |= (1<<LED_PIN); // keep LED on at all times
\r
215 // Actual baud rate = 9600 BAUD, (0.0% error) @ 14.7456MHz
\r
216 // Actual baud rate = 19.2K BAUD, (0.0% error) @ 14.7456MHz
\r
217 // Actual baud rate = 38.4K BAUD, (0.0% error) @ 14.7456MHz
\r
218 // Actual baud rate = 115.2K BAUD, (0.0% error) @ 14.7456MHz
\r
219 #define BAUD_4800 191
\r
220 #define BAUD_9600 95
\r
221 #define BAUD_14400 63
\r
222 #define BAUD_19200 47
\r
223 #define BAUD_28800 31
\r
224 #define BAUD_38400 23
\r
225 #define BAUD_57600 15
\r
226 #define BAUD_76800 11
\r
227 #define BAUD_115200 7
\r
229 FLASH_DECLARE(static const unsigned char BaudLookupTable[])
\r
230 = {BAUD_115200, BAUD_38400, BAUD_19200, BAUD_9600};
\r
232 INLINE_FUNC_DECLARE(static unsigned char GetUsartBaud (void));
\r
233 static inline unsigned char GetUsartBaud (void) {
\r
234 // Get BAUD rate jumper settings
\r
235 unsigned char BaudSelectJumpersValue = BAUD_PIN_REG;
\r
236 // Mask off unwanted bits
\r
237 BaudSelectJumpersValue &= (1<<BAUD_J2) | (1<<BAUD_J1);
\r
238 // BAUD_J2 = PD.3, BAUD_J1 = PD.2
\r
239 // This is two bits too far to the left and the array index will
\r
240 // increment by 4, instead of 1.
\r
241 // Shift BAUD_J2 & BAUD_J1 right by two bit positions for proper array indexing
\r
242 BaudSelectJumpersValue = (BaudSelectJumpersValue >> BAUD_J1);
\r
244 return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);
\r
248 INLINE_FUNC_DECLARE(static void UsartInit (void));
\r
249 static inline void UsartInit(void) {
\r
250 // Initialize USART
\r
251 UCSRB = 0; // Disable while setting baud rate
\r
253 UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 bit data
\r
254 UBRRL = GetUsartBaud (); // Set baud rate
\r
255 UBRRH = 0; // Set baud rate hi
\r
256 UCSRB = (1<<RXEN)|(1<<RXCIE); // RXEN = Enable
\r
257 sUartRxHead = 0; // register variable, need to explicitly zero
\r
258 sUartRxTail = 0; // register variable, need to explicitly zero
\r
261 CTS_PORT |= (1<<CTS_PIN); // bring signal high
\r
262 CTS_DIR |= (1<<CTS_PIN); // CTS is active low
\r
266 #if defined(__IMAGECRAFT__) || defined(__CODEVISIONAVR__)
\r
267 // Clock cycle = 67nS @ 14.7456MHz
\r
268 // Delay resolution ~ 1uS @ 14.7456MHz
\r
269 // So this function is only accurate at near above frequency
\r
270 void _delay_us (unsigned int d) {
\r
272 #if defined(__IMAGECRAFT__)
\r
274 #elif defined(__CODEVISIONAVR__)
\r
281 INLINE_FUNC_DECLARE(static void LcdInit (void));
\r
282 static inline void LcdInit (void) {
\r
283 // Set BAUD_J2:BAUD_J1 PULL-UPS active
\r
284 LCD_CONTROL_PORT = (1<<BAUD_J2) | (1<<BAUD_J1);
\r
285 // Set LCD_control BAUD_J2:BAUD_J1 to inputs
\r
286 LCD_CONTROL_DIR = 0xF2;
\r
289 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH
\r
291 // Initialize the LCD Data AVR I/O
\r
292 LCD_DATA_DIR = 0xFF; // Set LCD_DATA_PORT as all outputs
\r
293 LCD_DATA_PORT = 0x00; // Set LCD_DATA_PORT to logic low
\r
295 // Initialize the LCD controller
\r
296 LCD_CONTROL_PORT &= ~(1<<LCD_RS);
\r
297 LcdWriteData (DATA_8);
\r
300 LcdWriteData (DATA_8);
\r
303 LcdWriteData (DATA_8);
\r
304 LCD_CONTROL_PORT |= (1<<LCD_RS);
\r
306 // The display will be out into 8 bit data mode here
\r
307 LcdWriteCmd (LCD_8_Bit | LCD_4_Line); // Set 8 bit data, 4 display lines
\r
308 LcdWriteCmd (LCD_ON); // Power up the display
\r
309 LcdWriteCmd (LCD_CLR); // Power up the display
\r
313 MCUSR = 0; // clear all reset flags
\r
315 #if defined(__CODEVISIONAVR__)
\r
320 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
321 WDTCSR = (1<<WDE)|(1<<WDP3)|(1<<WDP0); // 1024K cycles, 8.0sec
\r
322 MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode
\r
327 // Initialize the AVR USART
\r
330 #if defined(__CODEVISIONAVR__)
\r
336 unsigned char tail = sUartRxTail; // explicitly set order of volatile access
\r
337 if (tail != sUartRxHead) { // Check if UART RX buffer has a character
\r
338 unsigned char rx_byte = WaitRxChar();
\r
340 // avoid use of switch statement as ImageCraft and GCC produce signifcantly
\r
341 // more code for a switch statement than a sequence of if/else if
\r
342 if (rx_byte == LED_SW_OFF) {
\r
344 } else if (rx_byte == LED_SW_ON) {
\r
346 } else if (rx_byte == LED_SET_BRIGHTNESS) {
\r
347 rx_byte = WaitRxChar(); // read next byte which will be brightness
\r
348 LedPwmSetBrightness(rx_byte);
\r
349 } else if (rx_byte == REG_MODE) {
\r
350 LcdWriteCmd (WaitRxChar()); // Send LCD command character
\r
352 LcdWriteData (rx_byte); // Send LCD data character
\r
355 // No characters waiting in RX buffer
\r
357 #if defined(__CODEVISIONAVR__)
\r
362 #if defined(__CODEVISIONAVR__)
\r
367 MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags
\r
368 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
369 WDTCSR &= ~(1<<WDE); // watchdog timer off
\r
370 #if defined(__CODEVISIONAVR__)
\r
377 #if defined(__CODEVISIONAVR__)
\r
384 #if defined(__CODEVISIONAVR__)
\r
389 #if defined(__CODEVISIONAVR__)
\r
394 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
395 WDTCSR &= ~(1<<WDCE);
\r
396 #if defined(__CODEVISIONAVR__)
\r
406 void LcdWriteCmd (unsigned char Cmd) {
\r
408 LCD_DATA_PORT = Cmd; // BusyWait leaves RS low in "Register mode"
\r
409 #if defined(__CODEVISIONAVR__)
\r
414 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
415 #if defined(__CODEVISIONAVR__)
\r
416 #if CONSERVATIVE_ENABLE_DURATION
\r
433 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
434 #if defined(__CODEVISIONAVR__)
\r
439 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character mode"
\r
442 void LcdWriteData (unsigned char c) {
\r
444 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character Mode"
\r
446 #if defined(__CODEVISIONAVR__)
\r
451 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
452 #if defined(__CODEVISIONAVR__)
\r
453 #if CONSERVATIVE_ENABLE_DURATION
\r
470 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
473 unsigned char LcdBusyWait (void) {
\r
474 unsigned char LCDStatus;
\r
475 LCD_DATA_DIR = 0x00; // Set LCD data port to inputs
\r
476 LCD_CONTROL_PORT &= ~(1<<LCD_RS); // Set display to "Register mode"
\r
477 LCD_CONTROL_PORT |= (1<<LCD_RW); // Put the display in the read mode
\r
478 #if defined(__CODEVISIONAVR__)
\r
484 #if defined(__CODEVISIONAVR__)
\r
489 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
490 #if defined(__CODEVISIONAVR__)
\r
491 #if CONSERVATIVE_ENABLE_DURATION
\r
508 LCDStatus = LCD_DATA_PIN_REG;
\r
509 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
510 } while (LCDStatus & (1<<LCD_BUSY));
\r
511 LCD_CONTROL_PORT &= ~(1<<LCD_RW); // Put display in write mode
\r
512 LCD_DATA_DIR = 0xFF; // Set LCD data port to outputs
\r
513 return (LCDStatus);
\r
516 #if defined(__IMAGECRAFT__)
\r
517 #pragma interrupt_handler usart_rx_handler:iv_USART0_RXC
\r
518 void usart_rx_handler(void)
\r
523 unsigned char rx = UDR;
\r
524 // Calculate next buffer position.
\r
525 unsigned char tmphead = sUartRxHead;
\r
526 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
530 // store in buffer if there is room
\r
531 if (tmphead != sUartRxTail) {
\r
532 sUartRxHead = tmphead; // Store new index.
\r
533 sUartRxBuf[tmphead] = rx;
\r
536 // check if buffer is now full, if so switch CTS to inactive
\r
537 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
541 // CTS active if still room in buffer
\r
542 if (tmphead != sUartRxTail) {
\r
543 CTS_PORT |= (1<<CTS_PIN);
\r
545 // no room left in buffer, so make CTS inactive
\r
546 CTS_PORT &= ~(1<<CTS_PIN);
\r
551 static unsigned char WaitRxChar (void) {
\r
552 // waits for next RX character, then return it
\r
553 unsigned char tail;
\r
555 tail = sUartRxTail; // explicitly set order of volatile variable access
\r
556 #if defined(__CODEVISIONAVR__)
\r
561 } while (sUartRxHead == tail); // while buffer is empty
\r
564 // turn off interrupts so that if UART char ready to be stored, then storage
\r
565 // will occur after CTS pin is set active. This allows UART ISR to set
\r
566 // CTS inactive if byte fills buffer after we remove current byte
\r
567 #if defined(__CODEVISIONAVR__)
\r
574 // increment tail position
\r
575 if (tail == UART_RX_BUFFER_SIZE-1)
\r
581 CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte
\r
582 #if defined(__CODEVISIONAVR__)
\r
585 sei(); // Allow UART ISR to read character and change potentially change CTS
\r
589 return sUartRxBuf[sUartRxTail];
\r
592 #if defined(__IMAGECRAFT__)
\r
593 #pragma interrupt_handler timer0_compa_handler:iv_TIMER0_COMPA
\r
594 void timer0_compa_handler(void)
\r
595 #elif defined(__CODEVISIONAVR__)
\r
596 interrupt [TIM0_COMPA] void timer0_compa_handler(void)
\r
598 ISR(TIMER0_COMPA_vect)
\r
601 #if defined(__CODEVISIONAVR__)
\r
604 sei(); // Okay to allow USART interrupts while controlling LED PWM
\r
607 // Set current LED state based on cycling variable
\r
608 if (ledPwmCycling & 0x01) {
\r
609 LED_PORT |= (1<<LED_PIN);
\r
611 LED_PORT &= ~(1<<LED_PIN);
\r
614 // Update cycling variable for next interrupt
\r
615 if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {
\r
617 ledPwmCycling = ledPwmPattern;
\r
620 ledPwmCycling >>= 1;
\r