Add lss file for cpp_obj program
[avr_serial_lcd.git] / serial_lcd_cpp.cpp
1 /*****************************************************************************\r
2 ** FILE IDENTIFICATION\r
3 **\r
4 **   Name:          serial_lcd.cpp\r
5 **   Purpose:       Serial Backpack firmware with C++ methods (no member variables)\r
6 **   Programmer:    Kevin Rosenberg <kevin@rosenberg.net> (AVR Freaks member kmr)\r
7 **   Date Started:  Dec 2007 (based on Jan 2, 2007 release by C.W. Livinston)\r
8 **\r
9 **   Copyright (c) 2007-2008 by Kevin Rosenberg. All rights reserved.\r
10 **\r
11 ** Compilers supported:\r
12 **   - WinAVR-20071221\r
13 **   - IAR AVR 4.30\r
14 **\r
15 ** LICENSE\r
16 **   See accompaning LICENSE file\r
17 **\r
18 ** CHANGES\r
19 **   See accompaning CHANGES file for modifications log from original\r
20 ******************************************************************************/\r
21 \r
22 \r
23 #include "serial_lcd.h"\r
24 \r
25 // register variables\r
26 REGISTER_VAR(unsigned char ledPwmCount, "r4", 15);\r
27 REGISTER_VAR(unsigned char sUartRxHead, "r5", 14);\r
28 REGISTER_VAR(unsigned char sUartRxTail, "r6", 13);\r
29 REGISTER_VAR(unsigned char ledPwmCycling, "r7", 12);\r
30 \r
31 #define ledPwmPattern GPIOR0\r
32 #define ledStatus GPIOR1\r
33 #define BIT_led_on REGISTER_BIT(GPIOR1,0)\r
34 \r
35 //// Classes with only static methods\r
36 \r
37 class Watchdog {\r
38 public:\r
39   INLINE_FUNC_DECLARE(static void init(void)) {\r
40     reset();\r
41     WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
42     WDTCSR = (1<<WDE)|(1<<WDP3)|(1<<WDP0); // 1024K cycles, 8.0sec\r
43   }\r
44   INLINE_FUNC_DECLARE(static void off(void)) {\r
45     cli();\r
46     reset();\r
47     MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags\r
48     WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
49     WDTCSR &= ~(1<<WDE); // watchdog timer off\r
50     sei();\r
51   }\r
52   \r
53   INLINE_FUNC_DECLARE(static void on(void)) {\r
54     cli();\r
55     reset();\r
56     WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
57     WDTCSR &= ~(1<<WDCE);\r
58     sei();\r
59   }\r
60   \r
61   INLINE_FUNC_DECLARE(static void reset(void)) { \r
62     wdt_reset(); \r
63   }\r
64 };\r
65   \r
66 class SleepMode {\r
67 public:\r
68   INLINE_FUNC_DECLARE(static void initIdleMode(void)) {\r
69     MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode\r
70   }\r
71   INLINE_FUNC_DECLARE(static void enterSleep()) {\r
72     sleep_enable();\r
73     sleep_cpu();\r
74     sleep_disable();\r
75   }\r
76 };\r
77 \r
78 class Delay {\r
79 public:\r
80   INLINE_FUNC_DECLARE(static void millisec(double ms)) {\r
81     _delay_ms(ms);\r
82   }\r
83   INLINE_FUNC_DECLARE(static void microsec(double ms)) {\r
84     _delay_us(ms);\r
85   }\r
86 };\r
87 \r
88 \r
89 //// Classes with non-static methods\r
90 \r
91 #define USE_LED_PWM_IO_MEMBERS 0\r
92 \r
93 class LedPwm {\r
94 private:\r
95   static const prog_uint8_t ledPwmPatterns[];\r
96   static const unsigned char LED_BRIGHTNESS_LEVELS = 8;\r
97 \r
98 #if USE_LED_PWM_IO_MEMBERS\r
99   // LED backlight control pin\r
100   static volatile unsigned char * const m_LedDirPtr;\r
101   static volatile unsigned char * const m_LedPortPtr;\r
102   static const unsigned char m_LedPin = LED_PIN;\r
103 #endif\r
104 \r
105 public:\r
106   INLINE_FUNC_DECLARE(void init(void)) {\r
107     // setup PWM to run at 1.25ms per interrupt. \r
108     // This allows 8 levels of brightness at minimum of 100Hz flicker rate.\r
109     TCCR0A = (1<<WGM01); // CTC mode\r
110     TCCR0B = 0; // timer off\r
111     OCR0A = 72; // 1.25ms with CLK/256 prescaler @ 14.7456MHz\r
112     TIMSK = (1<<OCIE0A); // Turn on timer0 COMPA intr (all other timer intr off)\r
113 #if USE_LED_PWM_IO_MEMBERS\r
114     *m_LedPortPtr &= ~(1<<m_LedPin); // Ensure LED is off during initialization\r
115     *m_LedDirPtr |= (1<<m_LedPin); // Ensure LED is output port\r
116 #else\r
117     LED_PORT &= ~(1<<LED_PIN); // Ensure LED is off during initialization\r
118     LED_DIR |= (1<<LED_PIN); // Ensure LED is output port\r
119 #endif\r
120     BIT_led_on = 0;  // note that LED is off\r
121     ledPwmPattern = 0xFF;  // maximum brightness\r
122   }\r
123 \r
124   INLINE_FUNC_DECLARE(void stop(void)) {\r
125     TCCR0B = 0;\r
126   }\r
127   INLINE_FUNC_DECLARE(void start(void)) {\r
128     TCCR0B = (1<<CS02);\r
129   }\r
130 \r
131   INLINE_FUNC_DECLARE(void switchOff(void)) {\r
132     lampOff();\r
133     BIT_led_on = 0;\r
134     stop();\r
135   }\r
136 \r
137   INLINE_FUNC_DECLARE(void switchOn(void)) {\r
138     BIT_led_on = 1;\r
139     if (ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM\r
140       stop();\r
141       lampOn();\r
142     } else {\r
143       start();\r
144     }\r
145   }\r
146 \r
147   INLINE_FUNC_DECLARE(void setBrightness(unsigned char brightness)) {\r
148     if (brightness == 0) { // turn backlight off for 0 brightness\r
149       if (BIT_led_on) {\r
150         stop();\r
151         ledPwmPattern = 0;\r
152         ledPwmCycling = 0;\r
153         lampOff();\r
154       }\r
155       return;\r
156     }\r
157 \r
158     unsigned char ledPwmPos = (brightness * (LED_BRIGHTNESS_LEVELS-1) + 127) >> 8; \r
159     // Below is probably not required, but ensures we don't exceed array\r
160     if (ledPwmPos > LED_BRIGHTNESS_LEVELS - 1)\r
161       ledPwmPos = LED_BRIGHTNESS_LEVELS - 1;\r
162     ledPwmPattern = pwmPattern(ledPwmPos);\r
163     ledPwmCount = 0;\r
164     ledPwmCycling = ledPwmPattern;\r
165     if (ledPwmPos >= LED_BRIGHTNESS_LEVELS-1) { // maximum brightness\r
166       // don't need PWM to continuously on\r
167       if (BIT_led_on) {\r
168         stop();\r
169         lampOn();\r
170       }\r
171     } else {\r
172       if (BIT_led_on) {\r
173         start();\r
174       }\r
175     }\r
176   }\r
177 \r
178   INLINE_FUNC_DECLARE(void cyclePwm(void)) {\r
179     if (ledPwmCycling & 0x01) {   // Set current LED state based on cycling variable\r
180       lampOn();\r
181     } else {\r
182       lampOff();\r
183     }\r
184 \r
185     // Update cycling variable for next interrupt\r
186     if (ledPwmCount >= LED_BRIGHTNESS_LEVELS-1) {\r
187       ledPwmCount = 0;\r
188       ledPwmCycling = ledPwmPattern;\r
189     } else {\r
190       ledPwmCount++;\r
191       ledPwmCycling >>= 1;\r
192     }\r
193   }\r
194 \r
195   INLINE_FUNC_DECLARE(void lampOn(void)) {\r
196 #if USE_LED_PWM_IO_MEMBERS\r
197     *m_LedPortPtr |= (1<<m_LedPin);\r
198 #else\r
199     LED_PORT |= (1<<LED_PIN);\r
200 #endif\r
201   };\r
202 \r
203   INLINE_FUNC_DECLARE(void lampOff(void)) {\r
204 #if USE_LED_PWM_IO_MEMBERS\r
205     *m_LedPortPtr &= ~(1<<m_LedPin);;\r
206 #else\r
207     LED_PORT &= ~(1<<LED_PIN);;\r
208 #endif\r
209   }\r
210 \r
211   INLINE_FUNC_DECLARE(unsigned char pwmPattern(unsigned char pos)) {\r
212     return PGM_READ_BYTE (&ledPwmPatterns[pos]);\r
213   }\r
214 };\r
215 \r
216 #if USE_LED_PWM_IO_MEMBERS\r
217 volatile unsigned char * const LedPwm::m_LedDirPtr = &LED_DIR;\r
218 volatile unsigned char * const LedPwm::m_LedPortPtr = &LED_PORT;\r
219 #endif\r
220 \r
221 // PWM Patterns (8-brightness levels)\r
222 const prog_uint8_t LedPwm::ledPwmPatterns[] = {\r
223   0x10, // 0b00010000  0\r
224   0x11, // 0b00010001  1\r
225   0x4A, // 0b01001010  2\r
226   0x55, // 0b01010101  3\r
227   0x5D, // 0b01011101  4\r
228   0xEE, // 0b11101110  5\r
229   0x7F, // 0b11110111  6\r
230   0xFF, // 0b11111111  7\r
231 };\r
232 \r
233 \r
234 class Lcd {\r
235 private:\r
236   // Turn on power to the display, no cursor\r
237   static const unsigned char m_PWR_ON = 0x0C;\r
238   // Clear display command\r
239   static const unsigned char m_CLR_DSP = 0x01;\r
240   // Set 8 data bits, 4 display lines\r
241   static const unsigned char m_LINE_8x4 = 0x38;\r
242   // Set 8 data bits\r
243   static const unsigned char m_DATA_8 = 0x30;\r
244 \r
245 public:\r
246   INLINE_FUNC_DECLARE(void init(void)) {\r
247     // Set BAUD_J2:BAUD_J1 PULL-UPS active\r
248     LCD_CONTROL_PORT = (1<<BAUD_J2) | (1<<BAUD_J1);\r
249     // Set LCD_control BAUD_J2:BAUD_J1 to inputs        \r
250     LCD_CONTROL_DIR = 0xF2;\r
251     \r
252     Delay::millisec(15);\r
253     \r
254     LCD_CONTROL_PORT |= (1<<LCD_RS); // Set LCD_RS HIGH\r
255     \r
256     // Initialize the AVR controller I/O\r
257     LCD_DATA_DIR = 0xFF; // Set LCD_DATA_PORT as all outputs\r
258     LCD_DATA_PORT = 0x00; // Set LCD_DATA_PORT to logic low\r
259     \r
260     // Initialize the LCD controller\r
261     LCD_CONTROL_PORT &= ~(1<<LCD_RS);\r
262     \r
263     putChar (m_DATA_8); \r
264     Delay::millisec(4.1);\r
265     \r
266     putChar (m_DATA_8);\r
267     Delay::microsec(100);\r
268     \r
269     putChar (m_DATA_8);\r
270     LCD_CONTROL_PORT |= (1<<LCD_RS);\r
271     \r
272     // The display will be out into 8 bit data mode here\r
273     putCmd (m_LINE_8x4); // Set 8 bit data, 4 display lines\r
274     putCmd (m_PWR_ON); // Power up the display\r
275     putCmd (m_CLR_DSP); // Power up the display\r
276   }\r
277 \r
278   void putChar(unsigned char ch);\r
279   void putCmd(unsigned char ch);\r
280 \r
281 private:\r
282   unsigned char busyWait(void);\r
283 };\r
284 \r
285 \r
286 // round to nearest integer\r
287 \r
288 class Uart {\r
289 private:\r
290 // Circular buffer for UART RX\r
291   //NO_INIT_DECLARE(volatile unsigned char sUartRxBuf[UART_RX_BUFFER_SIZE]);\r
292   static const prog_uint8_t BaudLookupTable[];\r
293 \r
294   static const unsigned char UART_RX_BUFFER_SIZE = 48;\r
295   volatile unsigned char m_UartRxBuf[UART_RX_BUFFER_SIZE];\r
296 \r
297   INLINE_FUNC_DECLARE(static unsigned char baud()) {\r
298     unsigned char BaudSelectJumpersValue;\r
299   \r
300     // Get BAUD rate jumper settings       \r
301     BaudSelectJumpersValue  = BAUD_PIN_REG;\r
302     // Mask off unwanted bits\r
303     BaudSelectJumpersValue &= (1<<BAUD_J2) | (1<<BAUD_J1);\r
304     // BAUD_J2 = PD.3, BAUD_J1 = PD.2\r
305     // This is two bits too far to the left and the array index will\r
306     // increment by 4, instead of 1.\r
307     // Shift BAUD_J2 & BAUD_J1 right by two bit positions for proper array indexing\r
308     BaudSelectJumpersValue = (BaudSelectJumpersValue >> BAUD_J1);\r
309     \r
310     return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);\r
311   }\r
312 \r
313 public:\r
314   INLINE_FUNC_DECLARE(void init(void)) {\r
315     // Initialize UART0 \r
316     UCSRB = 0; // Disable while setting baud rate\r
317     UCSRA = 0;\r
318     UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 bit data\r
319     UBRRL = baud();\r
320     UBRRH = 0; // Set baud rate hi\r
321     UCSRB = (1<<RXEN)|(1<<RXCIE); // RXEN = Enable\r
322 \r
323     // Init circular buffer pointers\r
324     sUartRxHead = 0;\r
325     sUartRxTail = 0;\r
326 \r
327 #if USE_CTS\r
328   CTS_PORT |= (1<<CTS_PIN);  // bring signal high\r
329   CTS_DIR |= (1<<CTS_PIN);   // CTS is active low\r
330 #endif\r
331   }\r
332 \r
333   INLINE_FUNC_DECLARE(unsigned char storeChar(unsigned char rx)) {\r
334     // Calculate next buffer position.\r
335     unsigned char tmphead = sUartRxHead;\r
336     if (tmphead == UART_RX_BUFFER_SIZE-1)\r
337       tmphead = 0;\r
338     else\r
339       tmphead++;\r
340     \r
341     // store in buffer if there is room\r
342     if (tmphead != sUartRxTail) {\r
343       sUartRxHead = tmphead;         // Store new index.\r
344       m_UartRxBuf[tmphead] = rx;\r
345     } else {\r
346       // no room to store character -- data loss\r
347 #if USE_CTS\r
348       CTS_PORT &= ~(1<<CTS_PIN);\r
349 #endif\r
350       return 0;\r
351     }\r
352     \r
353 #if USE_CTS\r
354     // check if buffer is now full, if so switch CTS to inactive\r
355     if (tmphead == UART_RX_BUFFER_SIZE-1)\r
356       tmphead = 0;\r
357     else\r
358       tmphead++;\r
359     // CTS active if still room in buffer\r
360     if (tmphead != sUartRxTail) {\r
361       CTS_PORT |= (1<<CTS_PIN);\r
362     } else {\r
363       // no room left in buffer, so make CTS inactive\r
364       CTS_PORT &= ~(1<<CTS_PIN);\r
365     }\r
366 #endif \r
367     return 1;\r
368   }\r
369 \r
370   INLINE_FUNC_DECLARE(unsigned char charAvail(void)) {\r
371     unsigned char tail = sUartRxTail; // explicitly set order of volatile access\r
372     return (tail != sUartRxHead) ? 1 : 0;\r
373   }\r
374 \r
375   unsigned char waitRxChar (void);\r
376 };\r
377   \r
378 #define UART_UBRR(baud) ((F_CPU+(8*(unsigned long) (baud)))/ (16*((unsigned long) (baud))) - 1)\r
379 const prog_uint8_t Uart::BaudLookupTable[] =\r
380   {UART_UBRR(115200), UART_UBRR(38400), UART_UBRR(19200), UART_UBRR(9600)};\r
381 \r
382 unsigned char Uart::waitRxChar (void) {\r
383   // waits for next RX character, then return it\r
384   unsigned char tail;\r
385   do {\r
386     tail = sUartRxTail; // explicitly set order of volatile variable access\r
387     Watchdog::reset();\r
388   } while (sUartRxHead == tail); // while buffer is empty\r
389 \r
390 #if USE_CTS\r
391   // turn off interrupts so that if UART char ready to be stored, then storage\r
392   // will occur after CTS pin is set active. This allows UART ISR to set\r
393   //  CTS inactive if byte fills buffer after we remove current byte\r
394   cli();\r
395 #endif\r
396 \r
397   // increment tail position \r
398   if (tail == UART_RX_BUFFER_SIZE-1)\r
399     sUartRxTail = 0;\r
400   else\r
401     sUartRxTail++;\r
402 \r
403 #if USE_CTS\r
404   CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte\r
405   sei();   // Allow UART ISR to read character and change potentially change CTS\r
406 #endif\r
407 \r
408   return m_UartRxBuf[sUartRxTail];\r
409 }\r
410 \r
411 LedPwm gLed;\r
412 Lcd gLcd;\r
413 Uart gUart;\r
414 \r
415 \r
416 class SerialCommandProcessor {\r
417 private:\r
418 // ASCII control code to set brightness level\r
419 // Next byte is brightness ranging from 0 (no backlight) to 255 (max backlight)\r
420   static const unsigned char m_LED_SET_BRIGHTNESS = 0xFB;\r
421 // ASCII control code turns on LED\r
422   static const unsigned char m_LED_SW_ON = 0xFC;\r
423 // ASCII control code turns off LED\r
424   static const unsigned char m_LED_SW_OFF = 0xFD;\r
425 // Character generator RAM command\r
426   static const unsigned char m_REG_MODE = 0xFE;\r
427 \r
428 public:\r
429   INLINE_FUNC_DECLARE(static void processChar(unsigned char ch)) {\r
430     // avoid use of switch statement as ImageCraft and GCC produce signifcantly\r
431     // more code for a switch statement than a sequence of if/else if\r
432     if (ch == m_LED_SW_OFF) {\r
433       gLed.switchOff();\r
434     } else if (ch == m_LED_SW_ON) {\r
435       gLed.switchOn();\r
436     } else if (ch == m_LED_SET_BRIGHTNESS) {\r
437       // read next byte which will be brightness\r
438       gLed.setBrightness(gUart.waitRxChar());\r
439     } else if (ch == m_REG_MODE) {\r
440       gLcd.putCmd (gUart.waitRxChar()); // Send LCD command character\r
441     } else {\r
442       gLcd.putChar (ch); // Send LCD data character\r
443     }\r
444   }\r
445 };\r
446 \r
447 \r
448 MAIN_FUNC() {\r
449   MCUSR = 0; // clear all reset flags\r
450   Watchdog::init();\r
451 \r
452   SleepMode::initIdleMode();\r
453 \r
454   gLed.init();\r
455   gLcd.init();\r
456   gUart.init();   // Initialize the AVR USART   \r
457 \r
458   sei();\r
459   while (1) {\r
460     if (gUart.charAvail()) {\r
461       SerialCommandProcessor::processChar (gUart.waitRxChar());\r
462     } else {\r
463       // No characters waiting in RX buffer\r
464 \r
465       Watchdog::off();\r
466       SleepMode::enterSleep();\r
467       Watchdog::on();\r
468     }\r
469   }\r
470   MAIN_FUNC_LAST();\r
471 }\r
472 \r
473 void Lcd::putCmd (unsigned char Cmd) {\r
474   LCD_CONTROL_PORT &= ~(1<<LCD_RS);\r
475   busyWait();\r
476   LCD_DATA_PORT = Cmd;  \r
477   NOP();\r
478   LCD_CONTROL_PORT |= (1<<LCD_E);\r
479   ENABLE_WAIT();\r
480   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
481   NOP();\r
482   LCD_CONTROL_PORT |= (1<<LCD_RS);\r
483 }\r
484 \r
485 void Lcd::putChar (unsigned char c) {\r
486   LCD_CONTROL_PORT &= ~(1<<LCD_RS);\r
487   busyWait();\r
488   LCD_CONTROL_PORT |= (1<<LCD_RS);\r
489   LCD_DATA_PORT = c;\r
490   NOP();\r
491   LCD_CONTROL_PORT |= (1<<LCD_E);\r
492   ENABLE_WAIT();\r
493   LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
494 }\r
495 \r
496 unsigned char Lcd::busyWait (void) {\r
497   unsigned char LCDStatus; \r
498   LCD_DATA_DIR = 0x00; // Set LCD data port to inputs\r
499   LCD_CONTROL_PORT |= (1<<LCD_RW);\r
500   NOP();\r
501   do {\r
502     Watchdog::reset();\r
503     LCD_CONTROL_PORT |= (1<<LCD_E);     \r
504     ENABLE_WAIT();\r
505     LCDStatus = LCD_DATA_PIN_REG;\r
506     LCD_CONTROL_PORT &= ~(1<<LCD_E);\r
507   } while (LCDStatus & (1<<LCD_BUSY));\r
508   LCD_CONTROL_PORT &= ~(1<<LCD_RW);     \r
509   LCD_DATA_DIR = 0xFF; // Set LCD data port to default output state\r
510   return (LCDStatus);\r
511 }                \r
512 \r
513 \r
514 ISR(USART_RX_vect)\r
515 {\r
516   gUart.storeChar(UDR);\r
517 }\r
518 \r
519 ISR(TIMER0_COMPA_vect)\r
520 {  \r
521   sei(); // Okay to allow USART interrupts while controlling LED PWM\r
522 \r
523   gLed.cyclePwm();\r
524 }\r