+/*****************************************************************************\r
+** FILE IDENTIFICATION\r
+**\r
+** Name: serial_lcd_obj.cpp\r
+** Purpose: Serial backback firmware with C++ objects\r
+** Programmer: Kevin Rosenberg <kevin@rosenberg.net> (AVR Freaks member kmr)\r
+** Date Started: Dec 2007\r
+**\r
+** Copyright (c) 2007-2008 by Kevin Rosenberg. All rights reserved.\r
+**\r
+** Compilers supported:\r
+** - WinAVR-20071221\r
+** - IAR AVR 4.30\r
+**\r
+** LICENSE\r
+** See accompaning LICENSE file\r
+**\r
+** CHANGES\r
+** See accompaning CHANGES file for modifications log from original\r
+******************************************************************************/\r
+\r
+\r
+#include "serial_lcd.h"\r
+\r
+//// Classes with only static methods\r
+\r
+class Watchdog {\r
+public:\r
+ INLINE_FUNC_DECLARE(static void init(void)) {\r
+ reset();\r
+ WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
+ WDTCSR = (1<<WDE)|(1<<WDP3)|(1<<WDP0); // 1024K cycles, 8.0sec\r
+ }\r
+ INLINE_FUNC_DECLARE(static void off(void)) {\r
+ cli();\r
+ reset();\r
+ MCUSR &= ~(1<<WDRF); // clear any watchdog interrupt flags\r
+ WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
+ WDTCSR &= ~(1<<WDE); // watchdog timer off\r
+ sei();\r
+ }\r
+ \r
+ INLINE_FUNC_DECLARE(static void on(void)) {\r
+ cli();\r
+ reset();\r
+ WDTCSR |= (1<<WDCE)|(1<<WDE); // start timed sequence (keep old prescaler)\r
+ WDTCSR &= ~(1<<WDCE);\r
+ sei();\r
+ }\r
+ \r
+ INLINE_FUNC_DECLARE(static void reset(void)) { \r
+ wdt_reset(); \r
+ }\r
+};\r
+\r
+\r
+class SleepMode {\r
+public:\r
+ INLINE_FUNC_DECLARE(static void initIdleMode(void)) {\r
+ MCUCR &= ~((1<<SM1)|(1<<SM0)); // use idle sleep mode\r
+ }\r
+ INLINE_FUNC_DECLARE(static void enterSleep()) {\r
+ sleep_enable();\r
+ sleep_cpu();\r
+ sleep_disable();\r
+ }\r
+};\r
+\r
+class Delay {\r
+public:\r
+ INLINE_FUNC_DECLARE(static void millisec(double ms)) {\r
+ _delay_ms(ms);\r
+ }\r
+ INLINE_FUNC_DECLARE(static void microsec(double ms)) {\r
+ _delay_us(ms);\r
+ }\r
+};\r
+\r
+\r
+//// Classes with non-static functions\r
+\r
+class LedPwm {\r
+private:\r
+ static const unsigned char m_LED_BRIGHTNESS_LEVELS = 8;\r
+ static const prog_uint8_t m_ledPwmPatterns[];\r
+\r
+ volatile unsigned char m_ledPwmCount;\r
+ volatile unsigned char m_ledPwmCycling;\r
+ unsigned char m_ledPwmPattern;\r
+ unsigned char m_BIT_led_on;\r
+ volatile unsigned char * m_LedPort;\r
+ volatile unsigned char * m_LedDir;\r
+ unsigned char m_LedPin;\r
+\r
+public:\r
+ INLINE_FUNC_DECLARE(void init(volatile unsigned char* port, \r
+ volatile unsigned char* dir, \r
+ unsigned char pin)) {\r
+ m_LedPort = port;\r
+ m_LedDir = dir;\r
+ m_LedPin = pin;\r
+\r
+ // setup PWM to run at 1.25ms per interrupt. \r
+ // This allows 8 levels of brightness at minimum of 100Hz flicker rate.\r
+ TCCR0A = (1<<WGM01); // CTC mode\r
+ TCCR0B = 0; // timer off\r
+ OCR0A = 72; // 1.25ms with CLK/256 prescaler @ 14.7456MHz\r
+ TIMSK = (1<<OCIE0A); // Turn on timer0 COMPA intr (all other timer intr off)\r
+ *m_LedPort &= ~(1<<m_LedPin); // Ensure LED is off during initialization\r
+ *m_LedDir |= (1<<m_LedPin); // Ensure LED is output port\r
+ m_BIT_led_on = 0; // note that LED is off\r
+ m_ledPwmPattern = 0xFF; // maximum brightness\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void stop(void)) {\r
+ TCCR0B = 0;\r
+ }\r
+ INLINE_FUNC_DECLARE(void start(void)) {\r
+ TCCR0B = (1<<CS02);\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void switchOff(void)) {\r
+ lampOff();\r
+ m_BIT_led_on = 0;\r
+ stop();\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void switchOn(void)) {\r
+ m_BIT_led_on = 1;\r
+ if (m_ledPwmPattern == 0xFF) { // maximum brightness, no need for PWM\r
+ stop();\r
+ lampOn();\r
+ } else {\r
+ start();\r
+ }\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void setBrightness(unsigned char brightness)) {\r
+ if (brightness == 0) { // turn backlight off for 0 brightness\r
+ if (m_BIT_led_on) {\r
+ stop();\r
+ m_ledPwmPattern = 0;\r
+ m_ledPwmCycling = 0;\r
+ lampOff();\r
+ }\r
+ return;\r
+ }\r
+\r
+ unsigned char ledPwmPos = (brightness * (m_LED_BRIGHTNESS_LEVELS-1) + 127) >> 8; \r
+ // Below is probably not required, but ensures we don't exceed array\r
+ if (ledPwmPos > m_LED_BRIGHTNESS_LEVELS - 1)\r
+ ledPwmPos = m_LED_BRIGHTNESS_LEVELS - 1;\r
+ m_ledPwmPattern = pwmPattern(ledPwmPos);\r
+ m_ledPwmCount = 0;\r
+ m_ledPwmCycling = m_ledPwmPattern;\r
+ if (ledPwmPos >= m_LED_BRIGHTNESS_LEVELS-1) { // maximum brightness\r
+ // don't need PWM to continuously on\r
+ if (m_BIT_led_on) {\r
+ stop();\r
+ lampOn();\r
+ }\r
+ } else {\r
+ if (m_BIT_led_on) {\r
+ start();\r
+ }\r
+ }\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void cyclePwm(void)) {\r
+ if (m_ledPwmCycling & 0x01) { // Set current LED state based on cycling variable\r
+ lampOn();\r
+ } else {\r
+ lampOff();\r
+ }\r
+\r
+ // Update cycling variable for next interrupt\r
+ if (m_ledPwmCount >= m_LED_BRIGHTNESS_LEVELS-1) {\r
+ m_ledPwmCount = 0;\r
+ m_ledPwmCycling = m_ledPwmPattern;\r
+ } else {\r
+ m_ledPwmCount++;\r
+ m_ledPwmCycling >>= 1;\r
+ }\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void lampOn(void)) {\r
+ *m_LedPort |= (1<<m_LedPin);\r
+ };\r
+\r
+ INLINE_FUNC_DECLARE(void lampOff(void)) {\r
+ *m_LedPort &= ~(1<<m_LedPin);;\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(unsigned char pwmPattern(unsigned char pos)) {\r
+ return PGM_READ_BYTE (&m_ledPwmPatterns[pos]);\r
+ }\r
+};\r
+\r
+// PWM Patterns (8-brightness levels)\r
+const prog_uint8_t LedPwm::m_ledPwmPatterns[] = {\r
+ 0x10, // 0b00010000 0\r
+ 0x11, // 0b00010001 1\r
+ 0x4A, // 0b01001010 2\r
+ 0x55, // 0b01010101 3\r
+ 0x5D, // 0b01011101 4\r
+ 0xEE, // 0b11101110 5\r
+ 0x7F, // 0b11110111 6\r
+ 0xFF, // 0b11111111 7\r
+};\r
+\r
+\r
+class Lcd {\r
+private:\r
+ // Turn on power to the display, no cursor\r
+ static const unsigned char m_PWR_ON = 0x0C;\r
+ // Clear display command\r
+ static const unsigned char m_CLR_DSP = 0x01;\r
+ // Set 8 data bits, 4 display lines\r
+ static const unsigned char m_LINE_8x4 = 0x38;\r
+ // Set 8 data bits\r
+ static const unsigned char m_DATA_8 = 0x30;\r
+\r
+ volatile unsigned char * m_ctrlPort;\r
+ volatile unsigned char * m_ctrlDir;\r
+ volatile unsigned char * m_dataPort;\r
+ volatile unsigned char * m_dataDir;\r
+ volatile unsigned char * m_dataPinReg;\r
+ unsigned char m_rsPin;\r
+ unsigned char m_rwPin;\r
+ unsigned char m_ePin;\r
+\r
+ unsigned char busyWait(void);\r
+\r
+public:\r
+ INLINE_FUNC_DECLARE(void init(volatile unsigned char* ctrlPort, \r
+ volatile unsigned char* ctrlDir, \r
+ volatile unsigned char* dataPort,\r
+ volatile unsigned char* dataDir,\r
+ volatile unsigned char* dataPinReg,\r
+ unsigned char rsPin,\r
+ unsigned char rwPin,\r
+ unsigned char ePin)) {\r
+ m_ctrlPort = ctrlPort;\r
+ m_ctrlDir = ctrlDir;\r
+ m_dataPort = dataPort;\r
+ m_dataDir = dataDir;\r
+ m_dataPinReg = dataPinReg;\r
+ m_rsPin = rsPin;\r
+ m_rwPin = rwPin;\r
+ m_ePin = ePin;\r
+\r
+ *m_ctrlPort &= ~((1<<m_rsPin)|(1<<m_rwPin)|(1<<m_ePin)); // set low\r
+ *m_ctrlDir |= ((1<<m_rsPin)|(1<<m_rwPin)|(1<<m_ePin)); // set as output\r
+ \r
+ Delay::millisec(15);\r
+ \r
+ *m_ctrlPort |= (1<<LCD_RS); // Set LCD_RS HIGH\r
+ \r
+ // Initialize the AVR controller I/O\r
+ *m_dataDir = 0xFF; // Set LCD_DATA_OUT as all outputs\r
+ *m_dataPort = 0x00; // Set LCD_DATA_OUT to logic low\r
+ \r
+ // Initialize the LCD controller\r
+ *m_ctrlPort &= ~(1<<LCD_RS);\r
+ \r
+ putChar (m_DATA_8); \r
+ Delay::millisec(4.1);\r
+ \r
+ putChar (m_DATA_8);\r
+ Delay::microsec(100);\r
+ \r
+ putChar (m_DATA_8);\r
+ *m_ctrlPort |= (1<<LCD_RS);\r
+ \r
+ // The display will be out into 8 bit data mode here\r
+ putCmd (m_LINE_8x4); // Set 8 bit data, 4 display lines\r
+ putCmd (m_PWR_ON); // Power up the display\r
+ putCmd (m_CLR_DSP); // Power up the display\r
+ }\r
+\r
+ void putChar(unsigned char ch);\r
+ void putCmd(unsigned char ch);\r
+\r
+};\r
+\r
+void Lcd::putCmd (unsigned char Cmd) {\r
+ *m_ctrlPort &= ~(1<<m_rsPin);\r
+ busyWait();\r
+ *m_dataPort = Cmd; \r
+ NOP();\r
+ *m_ctrlPort |= (1<<m_ePin);\r
+ ENABLE_WAIT();\r
+ *m_ctrlPort &= ~(1<<m_ePin);\r
+ NOP();\r
+ *m_ctrlPort |= (1<<m_rsPin);\r
+}\r
+\r
+void Lcd::putChar (unsigned char c) {\r
+ *m_ctrlPort &= ~(1<<m_rsPin);\r
+ busyWait();\r
+ *m_ctrlPort |= (1<<m_rsPin);\r
+ *m_dataPort = c;\r
+ NOP();\r
+ *m_ctrlPort |= (1<<m_ePin);\r
+ ENABLE_WAIT();\r
+ *m_ctrlPort &= ~(1<<m_ePin);\r
+}\r
+\r
+unsigned char Lcd::busyWait (void) {\r
+ unsigned char LCDStatus; \r
+ *m_dataDir = 0x00; // Set LCD data port to inputs\r
+ *m_ctrlPort |= (1<<m_rwPin);\r
+ NOP();\r
+ do {\r
+ Watchdog::reset();\r
+ *m_ctrlPort |= (1<<m_ePin); \r
+ ENABLE_WAIT();\r
+ LCDStatus = *m_dataPinReg;\r
+ *m_ctrlPort &= ~(1<<m_ePin);\r
+ } while (LCDStatus & (1<<LCD_BUSY));\r
+ *m_ctrlPort &= ~(1<<m_rwPin); \r
+ *m_dataDir = 0xFF; // Set LCD data port to default output state\r
+ return (LCDStatus);\r
+} \r
+\r
+\r
+\r
+class Uart {\r
+private:\r
+ // Circular buffer for UART RX\r
+ static const unsigned char UART_RX_BUFFER_SIZE = 48;\r
+ volatile unsigned char m_UartRxBuf[UART_RX_BUFFER_SIZE];\r
+\r
+ volatile unsigned char m_UartRxHead;\r
+ volatile unsigned char m_UartRxTail;\r
+\r
+public:\r
+ static const unsigned char UART_PARITY_NONE = 0;\r
+ static const unsigned char UART_PARITY_EVEN = 1;\r
+ static const unsigned char UART_PARITY_ODD = 2;\r
+ \r
+ INLINE_FUNC_DECLARE(void init(void)) {\r
+ // Default initialization is 8-bit, parity none\r
+ UCSRB = 0;\r
+ UCSRA = 0;\r
+ UCSRC = (1<<UCSZ1) | (1<<UCSZ0); // 8 bit data\r
+\r
+ // Init circular buffer pointers\r
+ m_UartRxHead = 0;\r
+ m_UartRxTail = 0;\r
+\r
+#if USE_CTS\r
+ CTS_PORT |= (1<<CTS_PIN); // bring signal high\r
+ CTS_DIR |= (1<<CTS_PIN); // CTS is active low\r
+#endif\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void setBaudDivisor(unsigned int baud_divisor)) {\r
+ UBRRL = baud_divisor & 0xFF;\r
+ UBRRH = baud_divisor >> 8;\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void rxEnable(void)) {\r
+ UCSRB |= (1<<RXEN);\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void rxInterruptEnable(void)) {\r
+ UCSRB |= (1<<RXCIE);\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void txEnable(void)) {\r
+ UCSRB |= (1<<TXEN);\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void txInterruptEnable(void)) {\r
+ UCSRB |= (1<<TXCIE);\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(void setParity(unsigned char mode)) {\r
+ unsigned char reg = UCSRC & ~((1<<UPM0)|(1<<UPM1));\r
+ if (mode == UART_PARITY_EVEN)\r
+ reg |= (1<<UPM1);\r
+ else if (mode == UART_PARITY_ODD)\r
+ reg |= (1<<UPM0)|(1<<UPM1);\r
+ UCSRC = reg;\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(unsigned char storeChar(unsigned char rx)) {\r
+ // Calculate next buffer position.\r
+ unsigned char tmphead = m_UartRxHead;\r
+ if (tmphead == UART_RX_BUFFER_SIZE-1)\r
+ tmphead = 0;\r
+ else\r
+ tmphead++;\r
+ \r
+ // store in buffer if there is room\r
+ if (tmphead != m_UartRxTail) {\r
+ m_UartRxHead = tmphead; // Store new index.\r
+ m_UartRxBuf[tmphead] = rx;\r
+ } else {\r
+ // no room to store character -- data loss\r
+#if USE_CTS\r
+ CTS_PORT &= ~(1<<CTS_PIN);\r
+#endif\r
+ return 0;\r
+ }\r
+ \r
+#if USE_CTS\r
+ // check if buffer is now full, if so switch CTS to inactive\r
+ if (tmphead == UART_RX_BUFFER_SIZE-1)\r
+ tmphead = 0;\r
+ else\r
+ tmphead++;\r
+ // CTS active if still room in buffer\r
+ if (tmphead != m_UartRxTail) {\r
+ CTS_PORT |= (1<<CTS_PIN);\r
+ } else {\r
+ // no room left in buffer, so make CTS inactive\r
+ CTS_PORT &= ~(1<<CTS_PIN);\r
+ }\r
+#endif \r
+ return 1;\r
+ }\r
+\r
+ INLINE_FUNC_DECLARE(unsigned char charAvail(void)) {\r
+ unsigned char tail = m_UartRxTail; // explicitly set order of volatile access\r
+ return (tail != m_UartRxHead) ? 1 : 0;\r
+ }\r
+\r
+ unsigned char waitRxChar (void);\r
+};\r
+ \r
+unsigned char Uart::waitRxChar (void) {\r
+ // waits for next RX character, then return it\r
+ unsigned char tail;\r
+ do {\r
+ tail = m_UartRxTail; // explicitly set order of volatile variable access\r
+ Watchdog::reset();\r
+ } while (m_UartRxHead == tail); // while buffer is empty\r
+\r
+#if USE_CTS\r
+ // turn off interrupts so that if UART char ready to be stored, then storage\r
+ // will occur after CTS pin is set active. This allows UART ISR to set\r
+ // CTS inactive if byte fills buffer after we remove current byte\r
+ cli();\r
+#endif\r
+\r
+ // increment tail position \r
+ if (tail == UART_RX_BUFFER_SIZE-1)\r
+ m_UartRxTail = 0;\r
+ else\r
+ m_UartRxTail++;\r
+\r
+#if USE_CTS\r
+ CTS_PORT |= (1<<CTS_PIN); // Ensure CTS is active since just removed a byte\r
+ sei(); // Allow UART ISR to read character and change potentially change CTS\r
+#endif\r
+\r
+ return m_UartRxBuf[m_UartRxTail];\r
+}\r
+\r
+// Round to nearest integer\r
+#define UART_UBRR(baud) ((F_CPU+(8*(unsigned long) (baud)))/ (16*((unsigned long) (baud))) - 1)\r
+const prog_uint8_t BaudLookupTable[] =\r
+ {UART_UBRR(115200), UART_UBRR(38400), UART_UBRR(19200), UART_UBRR(9600)};\r
+\r
+\r
+INLINE_FUNC_DECLARE(static unsigned char GetBaudDivisor());\r
+static inline unsigned char GetBaudDivisor() {\r
+ // Set LCD_control BAUD_J2:BAUD_J1 to inputs \r
+ BAUD_DIR &= ~((1<<BAUD_J2) | (1<<BAUD_J1));\r
+ // Set BAUD_J2:BAUD_J1 PULL-UPS active\r
+ BAUD_PORT |= (1<<BAUD_J2) | (1<<BAUD_J1);\r
+\r
+ // Get BAUD rate jumper settings \r
+ unsigned char BaudSelectJumpersValue = BAUD_PIN_REG;\r
+ // Mask off unwanted bits\r
+ BaudSelectJumpersValue &= (1<<BAUD_J2) | (1<<BAUD_J1);\r
+ // BAUD_J2 = PD.3, BAUD_J1 = PD.2\r
+ // This is two bits too far to the left and the array index will\r
+ // increment by 4, instead of 1.\r
+ // Shift BAUD_J2 & BAUD_J1 right by two bit positions for proper array indexing\r
+ BaudSelectJumpersValue = (BaudSelectJumpersValue >> BAUD_J1);\r
+ \r
+ return PGM_READ_BYTE (&BaudLookupTable[BaudSelectJumpersValue]);\r
+}\r
+\r
+\r
+Lcd gLcd;\r
+LedPwm gLed;\r
+Uart gUart;\r
+\r
+// This class has only static methods\r
+class SerialCommandProcessor {\r
+private:\r
+ // ASCII control code to set brightness level\r
+ // Next byte is brightness ranging from 0 (no backlight) to 255 (maximum)\r
+ static const unsigned char m_LED_SET_BRIGHTNESS = 0xFB;\r
+ // ASCII control code turns on LED\r
+ static const unsigned char m_LED_SW_ON = 0xFC;\r
+ // ASCII control code turns off LED\r
+ static const unsigned char m_LED_SW_OFF = 0xFD;\r
+ // Character generator RAM command\r
+ static const unsigned char m_REG_MODE = 0xFE;\r
+\r
+public:\r
+ INLINE_FUNC_DECLARE(static void processChar(unsigned char ch)) {\r
+ // avoid use of switch statement as ImageCraft and GCC produce signifcantly\r
+ // more code for a switch statement than a sequence of if/else if\r
+ if (ch == m_LED_SW_OFF) {\r
+ gLed.switchOff();\r
+ } else if (ch == m_LED_SW_ON) {\r
+ gLed.switchOn();\r
+ } else if (ch == m_LED_SET_BRIGHTNESS) {\r
+ // read next byte which will be brightness\r
+ gLed.setBrightness(gUart.waitRxChar());\r
+ } else if (ch == m_REG_MODE) {\r
+ gLcd.putCmd (gUart.waitRxChar()); // Send LCD command character\r
+ } else {\r
+ gLcd.putChar (ch); // Send LCD data character\r
+ }\r
+ }\r
+};\r
+\r
+\r
+MAIN_FUNC() {\r
+ MCUSR = 0; // clear all reset flags\r
+ Watchdog::init();\r
+\r
+ SleepMode::initIdleMode();\r
+\r
+ gUart.init(); // Initialize the AVR USART \r
+ gUart.setBaudDivisor(GetBaudDivisor());\r
+ gUart.rxEnable();\r
+ gUart.rxInterruptEnable();\r
+ gLed.init(&LED_PORT, &LED_DIR, LED_PIN);\r
+ gLcd.init(&LCD_CONTROL_PORT, &LCD_CONTROL_DIR, &LCD_DATA_PORT,\r
+ &LCD_DATA_DIR, &LCD_DATA_PIN_REG, LCD_RS, LCD_RW, LCD_E);\r
+\r
+ sei();\r
+ while (1) {\r
+ if (gUart.charAvail()) {\r
+ SerialCommandProcessor::processChar (gUart.waitRxChar());\r
+ } else { // No characters waiting in RX buffer\r
+ Watchdog::off();\r
+ SleepMode::enterSleep();\r
+ Watchdog::on();\r
+ }\r
+ }\r
+ MAIN_FUNC_LAST();\r
+}\r
+\r
+\r
+ISR(USART_RX_vect) {\r
+ gUart.storeChar(UDR);\r
+}\r
+\r
+ISR(TIMER0_COMPA_vect) {\r
+ sei(); // Okay to allow USART interrupts while controlling LED PWM\r
+ gLed.cyclePwm();\r
+}\r