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