Initial import
[avr_serial_lcd.git] / serial_lcd.c
1 /*****************************************************************************\r
2 ** FILE IDENTIFICATION\r
3 **\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
9 **\r
10 **   Copyright (c) 2007-2008 by Kevin Rosenberg. All rights reserved.\r
11 **\r
12 ** Compilers supported:\r
13 **   - WinAVR-20071221 (includes avr-libc 1.6 required for proper _delay_us)\r
14 **   - IAR AVR 4.30\r
15 **   - Imagecraft AVR 7\r
16 **\r
17 ** LICENSE\r
18 **   See accompaning LICENSE file\r
19 **\r
20 ** CHANGES\r
21 **   See accompaning CHANGES file for modifications log from original\r
22 ******************************************************************************/\r
23 \r
24 #include "serial_lcd.h"\r
25 \r
26 #if defined(__GNUC__)\r
27 FUSES = {\r
28   .low = SUT1, \r
29   .high = (unsigned char) (DWEN & WDTON & RSTDISBL & BODLEVEL1 & BODLEVEL2),\r
30   .extended = EFUSE_DEFAULT,\r
31 };\r
32 #endif\r
33 \r
34 // Number of PWM brightness levels supported\r
35 #define LED_BRIGHTNESS_LEVELS 8\r
36 \r
37 ////// LCD Commands ///////\r
38 // Turn on power to the display, no cursor\r
39 #define LCD_ON  0x0C\r
40 // Clear display command\r
41 #define LCD_CLR 0x01\r
42 // Set 4 data bits \r
43 #define LCD_4_Bit 0x20 \r
44 // Set 8 data bits \r
45 #define LCD_8_Bit 0x30 \r
46 // Set number of lines \r
47 #define LCD_4_Line 0x08 \r
48 // Set 8 data bits\r
49 #define DATA_8  0x30\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
56 \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
67 \r
68 // Circular buffer for UART RX\r
69 #define UART_RX_BUFFER_SIZE 48\r
70 #if defined(__IMAGECRAFT__)\r
71 #pragma data:noinit\r
72 volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE];\r
73 #pragma data:data\r
74 #else\r
75 NO_INIT_DECLARE(volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE]);\r
76 #endif\r
77 \r
78 // PWM Patterns (8-brightness levels)\r
79 static FLASH_DECLARE(const unsigned char ledPwmPatterns[]) =\r
80 {\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
89 };\r
90 \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
95 \r
96 #else\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
101 #endif\r
102 \r
103 #define ledPwmPattern GPIOR0\r
104 #define ledStatus GPIOR1\r
105 #define BIT_led_on REGISTER_BIT(GPIOR1,0)\r
106 \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
112 \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
118 \r
119 #else\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
123   TCCR0B = 0;\r
124 }\r
125 \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
129 }\r
130 #endif\r
131 \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
144 }\r
145 \r
146 \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
150 \r
151   if (brightness == 0) { // turn backlight off for 0 brightness\r
152     if (BIT_led_on) {\r
153       LedTimerStop();\r
154       ledPwmPattern = 0;\r
155       ledPwmCycling = 0;\r
156       LED_PORT &= ~(1<<LED_PIN);\r
157     }\r
158     return;\r
159   }\r
160 \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
165 \r
166   ledPwmPattern = PGM_READ_BYTE (&ledPwmPatterns[ledPwmPos]);\r
167   ledPwmCount = 0;\r
168   ledPwmCycling = ledPwmPattern;\r
169   if (ledPwmPos >= LED_BRIGHTNESS_LEVELS-1) { // maximum brightness\r
170     // don't need PWM for continuously on\r
171     if (BIT_led_on) {\r
172       LedTimerStop();\r
173       LED_PORT |= (1<<LED_PIN);\r
174     }\r
175   } else {\r
176     if (BIT_led_on) {\r
177       LedTimerStart();\r
178     }\r
179   }\r
180 }\r
181 \r
182 INLINE_FUNC_DECLARE(static void LedPwmSwitchOff (void));\r
183 static inline void LedPwmSwitchOff (void) {\r
184   LED_PORT &= ~(1<<LED_PIN);\r
185   BIT_led_on = 0;\r
186   LedTimerStop();\r
187 }\r
188 \r
189 INLINE_FUNC_DECLARE(static void LedPwmSwitchOn (void));\r
190 static inline void LedPwmSwitchOn (void) {\r
191   BIT_led_on = 1;\r
192   if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM\r
193     LedTimerStop();\r
194     LED_PORT |= (1<<LED_PIN); // keep LED on at all times\r
195   } else {\r
196     LedTimerStart();\r
197   }\r
198 }\r
199 \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
213 \r
214 FLASH_DECLARE(static const unsigned char BaudLookupTable[])\r
215 = {BAUD_115200, BAUD_38400, BAUD_19200, BAUD_9600};\r
216 \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
228 \r
229   return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);\r
230 }\r
231 \r
232 \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
237   UCSRA = 0;\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
244 \r
245 #if USE_CTS\r
246   CTS_PORT |= (1<<CTS_PIN);  // bring signal high\r
247   CTS_DIR |= (1<<CTS_PIN);   // CTS is active low\r
248 #endif\r
249 }\r
250 \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
256   while (d-- != 0);\r
257   asm("nop");\r
258 }\r
259 #endif\r
260 \r
261 \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
268 \r
269   _delay_us(15000);\r
270   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH\r
271 \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
275 \r
276   // Initialize the LCD controller\r
277   LCD_CONTROL_PORT &= ~(1<<LCD_RS);\r
278   LcdWriteData (DATA_8);\r
279 \r
280   _delay_us(4100);\r
281   LcdWriteData (DATA_8);\r
282 \r
283   _delay_us(100);\r
284   LcdWriteData (DATA_8);\r
285   LCD_CONTROL_PORT |= (1<<LCD_RS);\r
286 \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
291 }\r
292 \r
293 MAIN_FUNC() {\r
294   MCUSR = 0; // clear all reset flags\r
295 \r
296   wdt_reset();\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
300 \r
301   LedPwmInit ();\r
302   LcdInit ();\r
303 \r
304   // Initialize the AVR USART\r
305   UsartInit ();\r
306 \r
307   sei();\r
308   while (1) {\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
312 \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
316         LedPwmSwitchOff();\r
317       } else if (rx_byte == LED_SW_ON) {\r
318         LedPwmSwitchOn();\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
324       } else {\r
325         LcdWriteData (rx_byte); // Send LCD data character\r
326       }\r
327     } else {\r
328       // No characters waiting in RX buffer\r
329 \r
330       cli();\r
331       wdt_reset();\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
335       sei();\r
336 \r
337       sleep_enable();\r
338       sleep_cpu();\r
339       sleep_disable();\r
340 \r
341       cli();\r
342       wdt_reset();\r
343       WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
344       WDTCSR &= ~(1<<WDCE);\r
345       sei();\r
346     }\r
347   }\r
348   MAIN_FUNC_LAST();\r
349 }\r
350 \r
351 void LcdWriteCmd (unsigned char Cmd) {\r
352   LcdBusyWait();\r
353   LCD_DATA_PORT = Cmd;  // BusyWait leaves RS low in "Register mode"\r
354   NOP();\r
355   LCD_CONTROL_PORT |= (1<<LCD_E);\r
356   ENABLE_WAIT();\r
357   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
358   NOP();\r
359   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character mode"\r
360 }\r
361 \r
362 void LcdWriteData (unsigned char c) {\r
363   LcdBusyWait();\r
364   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character Mode"\r
365   LCD_DATA_PORT = c;\r
366   NOP();\r
367   LCD_CONTROL_PORT |= (1<<LCD_E);\r
368   ENABLE_WAIT();\r
369   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
370 }\r
371 \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
377   NOP();\r
378   do {\r
379     wdt_reset();\r
380     LCD_CONTROL_PORT |= (1<<LCD_E);\r
381     ENABLE_WAIT();\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
388 }\r
389 \r
390 #if defined(__IMAGECRAFT__)\r
391 #pragma interrupt_handler usart_rx_handler:iv_USART0_RXC\r
392 void usart_rx_handler(void)\r
393 #else\r
394 ISR(USART_RX_vect)\r
395 #endif\r
396 {\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
401     tmphead = 0;\r
402   else\r
403     tmphead++;\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
408   }\r
409 #if USE_CTS\r
410   // check if buffer is now full, if so switch CTS to inactive\r
411   if (tmphead == UART_RX_BUFFER_SIZE-1)\r
412     tmphead = 0;\r
413   else\r
414     tmphead++;\r
415   // CTS active if still room in buffer\r
416   if (tmphead != sUartRxTail) {\r
417     CTS_PORT |= (1<<CTS_PIN);\r
418   } else {\r
419     // no room left in buffer, so make CTS inactive\r
420     CTS_PORT &= ~(1<<CTS_PIN);\r
421   }\r
422 #endif\r
423 }\r
424 \r
425 static unsigned char WaitRxChar (void) {\r
426   // waits for next RX character, then return it\r
427   unsigned char tail;\r
428   do {\r
429     tail = sUartRxTail; // explicitly set order of volatile variable access\r
430     wdt_reset();\r
431   } while (sUartRxHead == tail); // while buffer is empty\r
432 \r
433 #if USE_CTS\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
437   cli();\r
438 #endif\r
439 \r
440   // increment tail position\r
441   if (tail == UART_RX_BUFFER_SIZE-1)\r
442     sUartRxTail = 0;\r
443   else\r
444     sUartRxTail++;\r
445 \r
446 #if USE_CTS\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
449 #endif\r
450 \r
451   return sUartRxBuf[sUartRxTail];\r
452 }\r
453 \r
454 #if defined(__IMAGECRAFT__)\r
455 #pragma interrupt_handler timer0_compa_handler:iv_TIMER0_COMPA\r
456 void timer0_compa_handler(void)\r
457 #else\r
458   ISR(TIMER0_COMPA_vect)\r
459 #endif\r
460 {\r
461   sei(); // Okay to allow USART interrupts while controlling LED PWM\r
462 \r
463   // Set current LED state based on cycling variable\r
464   if (ledPwmCycling & 0x01) {\r
465     LED_PORT |= (1<<LED_PIN);\r
466   } else {\r
467     LED_PORT &= ~(1<<LED_PIN);\r
468   }\r
469 \r
470   // Update cycling variable for next interrupt\r
471   if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {\r
472     ledPwmCount = 0;\r
473     ledPwmCycling = ledPwmPattern;\r
474   } else {\r
475     ledPwmCount++;\r
476     ledPwmCycling >>= 1;\r
477   }\r
478 }\r