Add lss file for cpp_obj program
[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 and 5.10\r
15 **   - Imagecraft AVR 7.16\r
16 **   - Codevision AVR 2.02.06\r
17 **\r
18 ** LICENSE\r
19 **   See accompaning LICENSE file\r
20 **\r
21 ** CHANGES\r
22 **   See accompaning CHANGES file for modifications log from original\r
23 ******************************************************************************/\r
24 \r
25 #include "serial_lcd.h"\r
26 \r
27 #if defined(__GNUC__)\r
28 //FUSES = {\r
29 // .low = SUT1,\r
30     //  .high = (unsigned char) (DWEN & WDTON & RSTDISBL & BODLEVEL1 & BODLEVEL2),\r
31     //  .extended = EFUSE_DEFAULT,\r
32     //};\r
33 #endif\r
34 \r
35 // Number of PWM brightness levels supported\r
36 #define LED_BRIGHTNESS_LEVELS 8\r
37 \r
38 ////// LCD Commands ///////\r
39 // Turn on power to the display, no cursor\r
40 #define LCD_ON  0x0C\r
41 // Clear display command\r
42 #define LCD_CLR 0x01\r
43 // Set 4 data bits\r
44 #define LCD_4_Bit 0x20\r
45 // Set 8 data bits\r
46 #define LCD_8_Bit 0x30\r
47 // Set number of lines\r
48 #define LCD_4_Line 0x08\r
49 // Set 8 data bits\r
50 #define DATA_8  0x30\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
57 \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
68 \r
69 // Circular buffer for UART RX\r
70 #define UART_RX_BUFFER_SIZE 48\r
71 #if defined(__IMAGECRAFT__)\r
72 #pragma data:noinit\r
73 volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE];\r
74 #pragma data:data\r
75 #else\r
76 NO_INIT_DECLARE(volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE]);\r
77 #endif\r
78 \r
79 // PWM Patterns (8-brightness levels)\r
80 static FLASH_DECLARE(const unsigned char ledPwmPatterns[]) =\r
81 {\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
90 };\r
91 \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
96 \r
97 // Use avr_compat register variables\r
98 #else\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
103 #endif\r
104 \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
111 #else\r
112 #define IS_BACKLIGHT_ON() (GPIOR1 & 0x01)\r
113 #define BACKLIGHT_OFF() GPIOR1 &= ~0x01\r
114 #define BACKLIGHT_ON() GPIOR1 |= 0x01\r
115 #endif\r
116 \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
122 \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
128 \r
129 #else\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
133   TCCR0B = 0;\r
134 }\r
135 \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
139 }\r
140 #endif\r
141 \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
154 }\r
155 \r
156 \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
160 \r
161   if (brightness == 0) { // turn backlight off for 0 brightness\r
162     if (IS_BACKLIGHT_ON()) {\r
163       LedTimerStop();\r
164       ledPwmPattern = 0;\r
165       ledPwmCycling = 0;\r
166       LED_PORT &= ~(1<<LED_PIN);\r
167     }\r
168     return;\r
169   }\r
170 \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
175 \r
176   ledPwmPattern = PGM_READ_BYTE (&ledPwmPatterns[ledPwmPos]);\r
177   ledPwmCount = 0;\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
182       LedTimerStop();\r
183       LED_PORT |= (1<<LED_PIN);\r
184     }\r
185   } else {\r
186     if (IS_BACKLIGHT_ON()) {\r
187       LedTimerStart();\r
188     }\r
189   }\r
190 }\r
191 \r
192 INLINE_FUNC_DECLARE(static void LedPwmSwitchOff (void));\r
193 static inline void LedPwmSwitchOff (void) {\r
194   LED_PORT &= ~(1<<LED_PIN);\r
195   BACKLIGHT_OFF();\r
196   LedTimerStop();\r
197 }\r
198 \r
199 INLINE_FUNC_DECLARE(static void LedPwmSwitchOn (void));\r
200 static inline void LedPwmSwitchOn (void) {\r
201   BACKLIGHT_ON();\r
202   if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM\r
203     LedTimerStop();\r
204     LED_PORT |= (1<<LED_PIN); // keep LED on at all times\r
205   } else {\r
206     LedTimerStart();\r
207   }\r
208 }\r
209 \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
223 \r
224 FLASH_DECLARE(static const unsigned char BaudLookupTable[])\r
225 = {BAUD_115200, BAUD_38400, BAUD_19200, BAUD_9600};\r
226 \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
238 \r
239   return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);\r
240 }\r
241 \r
242 \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
247   UCSRA = 0;\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
254 \r
255 #if USE_CTS\r
256   CTS_PORT |= (1<<CTS_PIN);  // bring signal high\r
257   CTS_DIR |= (1<<CTS_PIN);   // CTS is active low\r
258 #endif\r
259 }\r
260 \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
266   while (d-- != 0);\r
267   asm("nop");\r
268 }\r
269 #endif\r
270 \r
271 \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
278 \r
279   _delay_us(15000);\r
280   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH\r
281 \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
285 \r
286   // Initialize the LCD controller\r
287   LCD_CONTROL_PORT &= ~(1<<LCD_RS);\r
288   LcdWriteData (DATA_8);\r
289 \r
290   _delay_us(4100);\r
291   LcdWriteData (DATA_8);\r
292 \r
293   _delay_us(100);\r
294   LcdWriteData (DATA_8);\r
295   LCD_CONTROL_PORT |= (1<<LCD_RS);\r
296 \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
301 }\r
302 \r
303 MAIN_FUNC() {\r
304   MCUSR = 0; // clear all reset flags\r
305 \r
306   wdt_reset();\r
307 #if defined(__CODEVISIONAVR__)\r
308 #pragma optsize-\r
309 #endif\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
313 #pragma optsize+\r
314 #endif\r
315   MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode\r
316 \r
317   LedPwmInit ();\r
318   LcdInit ();\r
319 \r
320   // Initialize the AVR USART\r
321   UsartInit ();\r
322 \r
323   sei();\r
324   while (1) {\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
328 \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
332         LedPwmSwitchOff();\r
333       } else if (rx_byte == LED_SW_ON) {\r
334         LedPwmSwitchOn();\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
340       } else {\r
341         LcdWriteData (rx_byte); // Send LCD data character\r
342       }\r
343     } else {\r
344       // No characters waiting in RX buffer\r
345 \r
346       cli();\r
347       wdt_reset();\r
348       MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags\r
349 \r
350 #if defined(__CODEVISIONAVR__)\r
351 #pragma optsize-\r
352 #endif\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
356 #pragma optsize+\r
357 #endif\r
358 \r
359       sei();\r
360 \r
361       sleep_enable();\r
362       sleep_cpu();\r
363       sleep_disable();\r
364 \r
365       cli();\r
366       wdt_reset();\r
367 \r
368 #if defined(__CODEVISIONAVR__)\r
369 #pragma optsize-\r
370 #endif\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
374 #pragma optsize+\r
375 #endif\r
376 \r
377       sei();\r
378     }\r
379   }\r
380   MAIN_FUNC_LAST();\r
381 }\r
382 \r
383 void LcdWriteCmd (unsigned char Cmd) {\r
384   LcdBusyWait();\r
385   LCD_DATA_PORT = Cmd;  // BusyWait leaves RS low in "Register mode"\r
386   NOP();\r
387   LCD_CONTROL_PORT |= (1<<LCD_E);\r
388   ENABLE_WAIT();\r
389   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
390   NOP();\r
391   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character mode"\r
392 }\r
393 \r
394 void LcdWriteData (unsigned char c) {\r
395   LcdBusyWait();\r
396   LCD_CONTROL_PORT |= (1<<LCD_RS); // Set display to "Character Mode"\r
397   LCD_DATA_PORT = c;\r
398   NOP();\r
399   LCD_CONTROL_PORT |= (1<<LCD_E);\r
400   ENABLE_WAIT();\r
401   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
402 }\r
403 \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
409   NOP();\r
410   do {\r
411     wdt_reset();\r
412     LCD_CONTROL_PORT |= (1<<LCD_E);\r
413     ENABLE_WAIT();\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
420 }\r
421 \r
422 INLINE_FUNC_DECLARE(static void UartStoreRx (uint8_t rx));\r
423 static inline void UartStoreRx (uint8_t rx) {\r
424   unsigned char tmphead;\r
425 \r
426   if (UCSRA & (1<<FE)) {\r
427     // framing error. Currrently, this is silently ignored\r
428     // real applications may wish to output information to LCD to indicate\r
429     // erroroneous byte received\r
430     return;\r
431   }\r
432 #ifdef HANDLE_DATA_OVER_RUN_ERROR\r
433   if (UCSRA & (1<<DOR)) {\r
434     // some applications may benefit from addind error notification for serial port data overruns\r
435   }\r
436 #endif\r
437 \r
438   // Calculate next buffer position.\r
439   tmphead = sUartRxHead;\r
440   if (tmphead == UART_RX_BUFFER_SIZE-1)\r
441     tmphead = 0;\r
442   else\r
443     tmphead++;\r
444   // store in buffer if there is room\r
445   if (tmphead != sUartRxTail) {\r
446     sUartRxHead = tmphead;         // Store new index.\r
447     sUartRxBuf[tmphead] = rx;\r
448   }\r
449 #if USE_CTS\r
450   // check if buffer is now full, if so switch CTS to inactive\r
451   if (tmphead == UART_RX_BUFFER_SIZE-1)\r
452     tmphead = 0;\r
453   else\r
454     tmphead++;\r
455   // CTS active if still room in buffer\r
456   if (tmphead != sUartRxTail) {\r
457     CTS_PORT |= (1<<CTS_PIN);\r
458   } else {\r
459     // no room left in buffer, so make CTS inactive\r
460     CTS_PORT &= ~(1<<CTS_PIN);\r
461   }\r
462 #endif\r
463 }\r
464 \r
465 #if defined(__IMAGECRAFT__)\r
466 #pragma interrupt_handler usart_rx_handler:iv_USART0_RXC\r
467 void usart_rx_handler(void)\r
468 #else\r
469 ISR(USART_RX_vect)\r
470 #endif\r
471 {\r
472   UartStoreRx(UDR);\r
473 }\r
474 \r
475 static unsigned char WaitRxChar (void) {\r
476   // waits for next RX character, then return it\r
477   unsigned char tail;\r
478   do {\r
479     tail = sUartRxTail; // explicitly set order of volatile variable access\r
480     wdt_reset();\r
481   } while (sUartRxHead == tail); // while buffer is empty\r
482 \r
483 #if USE_CTS\r
484   // turn off interrupts so that if UART char ready to be stored, then storage\r
485   // will occur after CTS pin is set active. This allows UART ISR to set\r
486   // CTS inactive if byte fills buffer after we remove current byte\r
487   cli();\r
488 #endif\r
489 \r
490   // increment tail position\r
491   if (tail == UART_RX_BUFFER_SIZE-1)\r
492     sUartRxTail = 0;\r
493   else\r
494     sUartRxTail++;\r
495 \r
496 #if USE_CTS\r
497   CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte\r
498   sei();   // Allow UART ISR to read character and change potentially change CTS\r
499 #endif\r
500 \r
501   return sUartRxBuf[sUartRxTail];\r
502 }\r
503 \r
504 #if defined(__IMAGECRAFT__)\r
505 #pragma interrupt_handler timer0_compa_handler:iv_TIMER0_COMPA\r
506 void timer0_compa_handler(void)\r
507 #else\r
508   ISR(TIMER0_COMPA_vect)\r
509 #endif\r
510 {\r
511   sei(); // Okay to allow USART interrupts while controlling LED PWM\r
512 \r
513   // Set current LED state based on cycling variable\r
514   if (ledPwmCycling & 0x01) {\r
515     LED_PORT |= (1<<LED_PIN);\r
516   } else {\r
517     LED_PORT &= ~(1<<LED_PIN);\r
518   }\r
519 \r
520   // Update cycling variable for next interrupt\r
521   if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {\r
522     ledPwmCount = 0;\r
523     ledPwmCycling = ledPwmPattern;\r
524   } else {\r
525     ledPwmCount++;\r
526     ledPwmCycling >>= 1;\r
527   }\r
528 }\r