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 // Use avr_compat register variables
\r
99 REGISTER_VAR(unsigned char ledPwmCount, "r4", 15);
\r
100 REGISTER_VAR(unsigned char sUartRxHead, "r5", 14);
\r
101 REGISTER_VAR(unsigned char sUartRxTail, "r6", 13);
\r
102 REGISTER_VAR(unsigned char ledPwmCycling, "r7", 12);
\r
105 #define ledPwmPattern GPIOR0
\r
106 #if defined(REGISTER_BIT)
\r
107 #define BIT_led_on REGISTER_BIT(GPIOR1,0)
\r
108 #define IS_BACKLIGHT_ON() BIT_led_on
\r
109 #define BACKLIGHT_OFF() BIT_led_on = 0
\r
110 #define BACKLIGHT_ON() BIT_led_on = 1
\r
112 #define IS_BACKLIGHT_ON() (GPIOR1 & 0x01)
\r
113 #define BACKLIGHT_OFF() GPIOR1 &= ~0x01
\r
114 #define BACKLIGHT_ON() GPIOR1 |= 0x01
\r
117 // Function declarations
\r
118 void LcdWriteCmd (unsigned char);
\r
119 void LcdWriteData (unsigned char);
\r
120 unsigned char LcdBusyWait (void);
\r
121 static unsigned char WaitRxChar (void);
\r
123 // ImageCraft/Codevision don't support inline functions, so use preprocessor
\r
124 #if defined(__IMAGECRAFT__) || defined(__CODEVISIONAVR__)
\r
125 #define LedTimerStop() TCCR0B = 0
\r
126 // Start with 256 prescaler
\r
127 #define LedTimerStart() TCCR0B = (1<<CS02)
\r
130 // Using a platform that has proper inline functions
\r
131 INLINE_FUNC_DECLARE(static void LedTimerStop (void));
\r
132 static inline void LedTimerStop (void) {
\r
136 INLINE_FUNC_DECLARE(static void LedTimerStart (void));
\r
137 static inline void LedTimerStart (void) {
\r
138 TCCR0B = (1<<CS02); // Start with 256 prescaler
\r
142 INLINE_FUNC_DECLARE(static void LedPwmInit (void));
\r
143 static inline void LedPwmInit (void) {
\r
144 // setup PWM to run at 1.25ms per interrupt.
\r
145 // This allows 8 levels of brightness at minimum of 100Hz flicker rate.
\r
146 TCCR0A = (1<<WGM01); // CTC mode
\r
147 TCCR0B = 0; // timer off
\r
148 OCR0A = 72; // 1.25ms with CLK/256 prescaler @ 14.7456MHz
\r
149 TIMSK = (1<<OCIE0A); // Turn on timer0 COMPA intr (all other timer intr off)
\r
150 LED_PORT &= ~(1<<LED_PIN); // Ensure LED is off during initialization
\r
151 LED_DIR |= (1<<LED_PIN); // Ensure LED is output direction
\r
152 BACKLIGHT_OFF(); // note that LED is off
\r
153 ledPwmPattern = 0xFF; // maximum brightness
\r
157 INLINE_FUNC_DECLARE(static void LedPwmSetBrightness (unsigned char brightness));
\r
158 static inline void LedPwmSetBrightness (unsigned char brightness) {
\r
159 unsigned char ledPwmPos;
\r
161 if (brightness == 0) { // turn backlight off for 0 brightness
\r
162 if (IS_BACKLIGHT_ON()) {
\r
166 LED_PORT &= ~(1<<LED_PIN);
\r
171 ledPwmPos = (brightness * (LED_BRIGHTNESS_LEVELS-1) + (unsigned int) 127) >> 8;
\r
172 // Below is probably not required, but ensures we don't exceed array
\r
173 if (ledPwmPos > LED_BRIGHTNESS_LEVELS - 1)
\r
174 ledPwmPos = LED_BRIGHTNESS_LEVELS - 1;
\r
176 ledPwmPattern = PGM_READ_BYTE (&ledPwmPatterns[ledPwmPos]);
\r
178 ledPwmCycling = ledPwmPattern;
\r
179 if (ledPwmPos >= LED_BRIGHTNESS_LEVELS-1) { // maximum brightness
\r
180 // don't need PWM for continuously on
\r
181 if (IS_BACKLIGHT_ON()) {
\r
183 LED_PORT |= (1<<LED_PIN);
\r
186 if (IS_BACKLIGHT_ON()) {
\r
192 INLINE_FUNC_DECLARE(static void LedPwmSwitchOff (void));
\r
193 static inline void LedPwmSwitchOff (void) {
\r
194 LED_PORT &= ~(1<<LED_PIN);
\r
199 INLINE_FUNC_DECLARE(static void LedPwmSwitchOn (void));
\r
200 static inline void LedPwmSwitchOn (void) {
\r
202 if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM
\r
204 LED_PORT |= (1<<LED_PIN); // keep LED on at all times
\r
210 // Actual baud rate = 9600 BAUD, (0.0% error) @ 14.7456MHz
\r
211 // Actual baud rate = 19.2K BAUD, (0.0% error) @ 14.7456MHz
\r
212 // Actual baud rate = 38.4K BAUD, (0.0% error) @ 14.7456MHz
\r
213 // Actual baud rate = 115.2K BAUD, (0.0% error) @ 14.7456MHz
\r
214 #define BAUD_4800 191
\r
215 #define BAUD_9600 95
\r
216 #define BAUD_14400 63
\r
217 #define BAUD_19200 47
\r
218 #define BAUD_28800 31
\r
219 #define BAUD_38400 23
\r
220 #define BAUD_57600 15
\r
221 #define BAUD_76800 11
\r
222 #define BAUD_115200 7
\r
224 FLASH_DECLARE(static const unsigned char BaudLookupTable[])
\r
225 = {BAUD_115200, BAUD_38400, BAUD_19200, BAUD_9600};
\r
227 INLINE_FUNC_DECLARE(static unsigned char GetUsartBaud (void));
\r
228 static inline unsigned char GetUsartBaud (void) {
\r
229 // Get BAUD rate jumper settings
\r
230 unsigned char BaudSelectJumpersValue = BAUD_PIN_REG;
\r
231 // Mask off unwanted bits
\r
232 BaudSelectJumpersValue &= (1<<BAUD_J2) | (1<<BAUD_J1);
\r
233 // BAUD_J2 = PD.3, BAUD_J1 = PD.2
\r
234 // This is two bits too far to the left and the array index will
\r
235 // increment by 4, instead of 1.
\r
236 // Shift BAUD_J2 & BAUD_J1 right by two bit positions for proper array indexing
\r
237 BaudSelectJumpersValue = (BaudSelectJumpersValue >> BAUD_J1);
\r
239 return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);
\r
243 INLINE_FUNC_DECLARE(static void UsartInit (void));
\r
244 static inline void UsartInit(void) {
\r
245 // Initialize USART
\r
246 UCSRB = 0; // Disable while setting baud rate
\r
248 UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 bit data
\r
249 UBRRL = GetUsartBaud (); // Set baud rate
\r
250 UBRRH = 0; // Set baud rate hi
\r
251 UCSRB = (1<<RXEN)|(1<<RXCIE); // RXEN = Enable
\r
252 sUartRxHead = 0; // register variable, need to explicitly zero
\r
253 sUartRxTail = 0; // register variable, need to explicitly zero
\r
256 CTS_PORT |= (1<<CTS_PIN); // bring signal high
\r
257 CTS_DIR |= (1<<CTS_PIN); // CTS is active low
\r
261 #if defined(__IMAGECRAFT__)
\r
262 // Clock cycle = 67nS @ 14.7456MHz
\r
263 // Delay resolution ~ 1uS @ 14.7456MHz
\r
264 // So this function is only accurate at near above frequency
\r
265 void _delay_us (unsigned int d) {
\r
272 INLINE_FUNC_DECLARE(static void LcdInit (void));
\r
273 static inline void LcdInit (void) {
\r
274 // Set BAUD_J2:BAUD_J1 PULL-UPS active
\r
275 LCD_CONTROL_PORT = (1<<BAUD_J2) | (1<<BAUD_J1);
\r
276 // Set LCD_control BAUD_J2:BAUD_J1 to inputs
\r
277 LCD_CONTROL_DIR = 0xF2;
\r
280 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH
\r
282 // Initialize the LCD Data AVR I/O
\r
283 LCD_DATA_DIR = 0xFF; // Set LCD_DATA_PORT as all outputs
\r
284 LCD_DATA_PORT = 0x00; // Set LCD_DATA_PORT to logic low
\r
286 // Initialize the LCD controller
\r
287 LCD_CONTROL_PORT &= ~(1<<LCD_RS);
\r
288 LcdWriteData (DATA_8);
\r
291 LcdWriteData (DATA_8);
\r
294 LcdWriteData (DATA_8);
\r
295 LCD_CONTROL_PORT |= (1<<LCD_RS);
\r
297 // The display will be out into 8 bit data mode here
\r
298 LcdWriteCmd (LCD_8_Bit | LCD_4_Line); // Set 8 bit data, 4 display lines
\r
299 LcdWriteCmd (LCD_ON); // Power up the display
\r
300 LcdWriteCmd (LCD_CLR); // Power up the display
\r
304 MCUSR = 0; // clear all reset flags
\r
307 #if defined(__CODEVISIONAVR__)
\r
310 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
311 WDTCSR = (1<<WDE)|(1<<WDP3)|(1<<WDP0); // 1024K cycles, 8.0sec
\r
312 #if defined(__CODEVISIONAVR__) && defined(_OPTIMIZE_SIZE_)
\r
315 MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode
\r
320 // Initialize the AVR USART
\r
325 unsigned char tail = sUartRxTail; // explicitly set order of volatile access
\r
326 if (tail != sUartRxHead) { // Check if UART RX buffer has a character
\r
327 unsigned char rx_byte = WaitRxChar();
\r
329 // avoid use of switch statement as ImageCraft and GCC produce signifcantly
\r
330 // more code for a switch statement than a sequence of if/else if
\r
331 if (rx_byte == LED_SW_OFF) {
\r
333 } else if (rx_byte == LED_SW_ON) {
\r
335 } else if (rx_byte == LED_SET_BRIGHTNESS) {
\r
336 rx_byte = WaitRxChar(); // read next byte which will be brightness
\r
337 LedPwmSetBrightness(rx_byte);
\r
338 } else if (rx_byte == REG_MODE) {
\r
339 LcdWriteCmd (WaitRxChar()); // Send LCD command character
\r
341 LcdWriteData (rx_byte); // Send LCD data character
\r
344 // No characters waiting in RX buffer
\r
348 MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags
\r
350 #if defined(__CODEVISIONAVR__)
\r
353 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
354 WDTCSR &= ~(1<<WDE); // watchdog timer off
\r
355 #if defined(__CODEVISIONAVR__) && defined(_OPTIMIZE_SIZE_)
\r
368 #if defined(__CODEVISIONAVR__)
\r
371 WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)
\r
372 WDTCSR &= ~(1<<WDCE);
\r
373 #if defined(__CODEVISIONAVR__) && defined(_OPTIMIZE_SIZE_)
\r
383 void LcdWriteCmd (unsigned char Cmd) {
\r
385 LCD_DATA_PORT = Cmd; // BusyWait leaves RS low in "Register mode"
\r
387 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
389 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
391 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character mode"
\r
394 void LcdWriteData (unsigned char c) {
\r
396 LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character Mode"
\r
399 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
401 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
404 unsigned char LcdBusyWait (void) {
\r
405 unsigned char LCDStatus;
\r
406 LCD_DATA_DIR = 0x00; // Set LCD data port to inputs
\r
407 LCD_CONTROL_PORT &= ~(1<<LCD_RS); // Set display to "Register mode"
\r
408 LCD_CONTROL_PORT |= (1<<LCD_RW); // Put the display in the read mode
\r
412 LCD_CONTROL_PORT |= (1<<LCD_E);
\r
414 LCDStatus = LCD_DATA_PIN_REG;
\r
415 LCD_CONTROL_PORT &= ~(1<<LCD_E);
\r
416 } while (LCDStatus & (1<<LCD_BUSY));
\r
417 LCD_CONTROL_PORT &= ~(1<<LCD_RW); // Put display in write mode
\r
418 LCD_DATA_DIR = 0xFF; // Set LCD data port to outputs
\r
419 return (LCDStatus);
\r
422 #if defined(__IMAGECRAFT__)
\r
423 #pragma interrupt_handler usart_rx_handler:iv_USART0_RXC
\r
424 void usart_rx_handler(void)
\r
429 unsigned char rx, tmphead;
\r
431 if (UCSRA & (1<<FE)) {
\r
432 // framing error. Currrently, this is silently ignored
\r
433 // real applications may wish to output information to LCD to indicate
\r
434 // erroroneous byte received
\r
437 #ifdef HANDLE_DATA_OVER_RUN_ERROR
\r
438 if (UCSRA & (1<<DOR)) {
\r
439 // some applications may benefit from addind error notification for serial port data overruns
\r
444 // Calculate next buffer position.
\r
445 tmphead = sUartRxHead;
\r
446 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
450 // store in buffer if there is room
\r
451 if (tmphead != sUartRxTail) {
\r
452 sUartRxHead = tmphead; // Store new index.
\r
453 sUartRxBuf[tmphead] = rx;
\r
456 // check if buffer is now full, if so switch CTS to inactive
\r
457 if (tmphead == UART_RX_BUFFER_SIZE-1)
\r
461 // CTS active if still room in buffer
\r
462 if (tmphead != sUartRxTail) {
\r
463 CTS_PORT |= (1<<CTS_PIN);
\r
465 // no room left in buffer, so make CTS inactive
\r
466 CTS_PORT &= ~(1<<CTS_PIN);
\r
471 static unsigned char WaitRxChar (void) {
\r
472 // waits for next RX character, then return it
\r
473 unsigned char tail;
\r
475 tail = sUartRxTail; // explicitly set order of volatile variable access
\r
477 } while (sUartRxHead == tail); // while buffer is empty
\r
480 // turn off interrupts so that if UART char ready to be stored, then storage
\r
481 // will occur after CTS pin is set active. This allows UART ISR to set
\r
482 // CTS inactive if byte fills buffer after we remove current byte
\r
486 // increment tail position
\r
487 if (tail == UART_RX_BUFFER_SIZE-1)
\r
493 CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte
\r
494 sei(); // Allow UART ISR to read character and change potentially change CTS
\r
497 return sUartRxBuf[sUartRxTail];
\r
500 #if defined(__IMAGECRAFT__)
\r
501 #pragma interrupt_handler timer0_compa_handler:iv_TIMER0_COMPA
\r
502 void timer0_compa_handler(void)
\r
504 ISR(TIMER0_COMPA_vect)
\r
507 sei(); // Okay to allow USART interrupts while controlling LED PWM
\r
509 // Set current LED state based on cycling variable
\r
510 if (ledPwmCycling & 0x01) {
\r
511 LED_PORT |= (1<<LED_PIN);
\r
513 LED_PORT &= ~(1<<LED_PIN);
\r
516 // Update cycling variable for next interrupt
\r
517 if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {
\r
519 ledPwmCycling = ledPwmPattern;
\r
522 ledPwmCycling >>= 1;
\r