// // Yet another LCD example. // // This project ended up being postponed, etc, etc. so it did not // get the final polish. There may have even been a change or two // that didn't get updated. So, look at this for ideas, but proceed // with caution. I usually only publish stuff that has been fielded // and tested, but I was asked about this approach, so here it is. // // This program implements a software UART using the PIC's timers. // This was tricky to do in C and at 9600 Baud but it worked. // It also talks to the LCD and looks at some switches. Again, just // an example of C code on a low-end PIC. // // Code is for the PIC 16C55 processor using CCS C compiler. // // I've published a couple of similar programs, including LCD.ASM // which is a software UART receiver, but in assembler. #include <16c55.h> #define PIN_LCDPOWER PIN_B1 #define PIN_RX PIN_B2 #define PIN_TX PIN_B3 #define PIN_SWITCH1 PIN_B4 #define PIN_SWITCH2 PIN_B5 #define PIN_SWITCH3 PIN_B6 #define PIN_SWITCH4 PIN_B7 #define PIN_INVOPT PIN_B7 #define PIN_LCDRS PIN_B4 #define PIN_LCDE PIN_B4 // Timebase // // This program uses the TMR0 for all timing. In general, we let TMR0 // increment freely, and use delta values to implement intervals. The // SERIAL I/O uses TMR0 values fairly directly. Switch debouncing is // on a longer timescale, so a system of counters is used for debounce. // // TMR0 is an 8-bit register location. The way we will use it, it is // continuously incrementing. An optional prescaler can be applied // to the TMR0 by setting OPTION register bits. We choose the 1:4 // TMR0 prescaler. The OPTION Register is: // // bit 7-6: Unimplemented // 5: T0CS. Timer0 Clock Source Select, // 0 = Internal CLK (what we want), // 1 = Transition on T0CLKI pin (default) // 4: T0SE: Timer0 Source Edge Select (N/A) // 3: PSA: Prescaler Assignment bit, // 0 - Timer0 (what we want) // 1 - WDT (default) // 2-0: Prescaler Rate Select bits (for Timer0), // 000 - 1:2 (<--- What we want) // 001 - 1:4 // 010 - 1:8 // etc. // // We want to set the OPTION Register to: // // xx0x0000 or just 00000000 or 0x00 // // Crystal is 4Mhz. PIC Immiedately divides this by 4. // Our TMR0 Prescaler is 1:2, therefore the TMR0 Tick rate is: // // 4,000,000 / 4 / 2 = 2 us // // TMR0 will Wrap every 2 us * 256 = 0.512 ms // // 9600 Baud, means each bit is 1/9600 sec = 104.167 us // // So, one bit time is about 52 TMR0 ticks. // // If you sampled at deltas of 26,52,52,52,52,52,52,52,52 where the last // 52 is the 8th bit, that would be at time 884 us. // For a perfect 9600 baud character, the true center of the 8th bit would // be at t = 8/9600 + 1/(2*9600) = 885.417 us. // The error is 1.417 us / 104.167 = 1.36 percent. Not bad! // // #define HALF_BIT 26 #define FULL_BIT 52 // DEBOUNCE_TIME is in units of TMR0 Wraps. 1 TMR0 wrap is 0.512 ms. // So, t = DEBOUNCE_TIME * 0.512ms. // For 200ms, DEBOUNCE_TIME = 200 ms / 0.512 ms = 391. // This requires a PIC long int.. #define DEBOUNCE_TIME 400 //#use fixed_io(a_outputs=PIN_A5) #use fixed_io(b_outputs=PIN_B0, PIN_B1, PIN_B2, PIN_B3, PIN_B4, PIN_B5, PIN_B6, PIN_B7) //#use fixed_io(c_outputs=PIN_C0, PIN_C2, PIN_C3, PIN_C4, PIN_C5, PIN_C6) #BYTE PORTB = 6 #BYTE TMR0 = 1 #define TRUE 1 #define FALSE 0 // ************** Receiver Variables #define RXSTATE_IDLE 0 #define RXSTATE_CHECKSTART 1 #define RXSTATE_GETBITS 2 #define RXSTATE_WAITSTOP 3 int nRXState; short int bRXActive; // 0 if RX is IDLE, 1 if currently receiving. int nRXBits; int nRXStartTime; int nNextRXTime; int cRXData; short int bRXReady; // ************** Timebase Variables int nLastTMR0; int nWrapCnt; // ************** Transmitter Variables int bTXActive; int nTXBits; int cTXData; int nNextTXTime; // ************** Switch Input Variables long int lSWBounce; short int bSwitchesQuiet; // ************** LCD Variables // short int bCommandMode; #define ESCAPE_CHAR 0xFE void SendLCDCommand (char c); void SendLCDData (char c); main () { // PIC Initialization // // 1) Set OPTION register with prescalers.. // Software initialization; Set flags and initial states. bRXActive = FALSE; nRXState = RXSTATE_IDLE; bRXActive = FALSE; bRXReady = FALSE; nLastTMR0 = TMR0; nWrapCnt = 0;; bTXActive = FALSE; bSwitchesQuiet = TRUE; bCommandMode = FALSE; // Hardware Initialization. output_low (PIN_LCDRS); // Commands mean RS bit is LO output_low (PIN_LCDE); // Pulse LCD's E pin. output_high (PIN_LCDPOWER); // Initialize LCD. Delay for a little bit, first.. // delay_ms (50); SendLCDCommand (0x38); // 2 Line Format SendLCDCommand (0x0C); // SendLCDCommand (0x03); // SendLCDCommand (0x01); // Clear SendLCDCommand (0x06); // Autoinc SendLCDCommand (0x02); // Home // Display initial LCD message SendLCDData ('L'); SendLCDData ('C'); SendLCDData ('D'); // Enter our main loop. // for (;;) { // Be very prepared to detect a Start Bit if (!bRXActive && !input(PIN_RX)) { nRXStartTime = TMR0; // Record TMR0 time as soon as possible! bRXActive = TRUE; } // Update COMM state machine if (bRXActive) { switch (nRXState) { case RXSTATE_IDLE: if (!input(PIN_RX)) { // We got a start edge nNextRXTime = nRXStartTime + HALF_BIT; nRXState = RXSTATE_CHECKSTART; } break; case RXSTATE_CHECKSTART: if (TMR0 >= nNextRXTime) { if (!input(PIN_RX)) { // It is still LOW. Good. Get data. nNextRXTime += FULL_BIT; nRXState = RXSTATE_GETBITS; nRXBits = 0; } else { // Hmmm False start. Go back to IDLE. nRXState = RXSTATE_IDLE; } } break; case RXSTATE_GETBITS: if (TMR0 >= nNextRXTime) { cRXData = cRXData >> 1; if (nRXBits == 9) { // That was the STOP bit, we're done. nRXState = RXSTATE_IDLE; bRXReady = TRUE; bRXActive = FALSE; } else { if (input(PIN_RX)) cRXData |= 0x80; else cRXData &= 0x80; } nRXBits++; } break; } } // Be very prepared to detect a Start Bit if (!bRXActive && !input(PIN_RX)) { nRXStartTime = TMR0; // Record TMR0 time as soon as possible! bRXActive = TRUE; } // Talk to LCD if (bRXReady) { if (bCommandMode) { // Output as a Command SendLCDCommand (cRXData); bCommandMode = FALSE; // Always reset back to Data mode. } else { // We're in Data mode. Is this the ESCAPE char? if (cRXData == ESCAPE_CHAR) { // Yes! Set command mode but don't do anything until // next char, which is the command code. bCommandMode = TRUE; } else { // No. Just Data. SendLCDData (cRXData); } } // Clear flag. bRXReady = FALSE; } // Be very prepared to detect a Start Bit if (!bRXActive && !input(PIN_RX)) { nRXStartTime = TMR0; // Record TMR0 time as soon as possible! bRXActive = TRUE; } // Maintain the timebase if (TMR0 < nLastTMR0) { // Wrap! if (!nWrapCnt++) { // Shorttick hit zero!! Decrement counters if (lSWBounce) lSWBounce--; } } nLastTMR0 = TMR0; // Be very prepared to detect a Start Bit if (!bRXActive && !input(PIN_RX)) { nRXStartTime = TMR0; // Record TMR0 time as soon as possible! bRXActive = TRUE; } // Maintain switches. Assume only one switch at a time! if (bSwitchesQuiet) { if (!input(PIN_SWITCH1)) { cTXData = 'A'; } else if (!input(PIN_SWITCH2)) { cTXData = 'B'; } else if (!input(PIN_SWITCH3)) { cTXData = 'C'; } else if (!input(PIN_SWITCH4)) { cTXData = 'D'; } else { cTXData = 0; } if (cTXData) { // Keypress! lSWBounce = DEBOUNCE_TIME; bTXActive = TRUE; // Go ahead and put out the START bit and note the time nNextTXTime = TMR0 + FULL_BIT; nTXBits = 0; output_low (PIN_TX); bSwitchesQuiet = FALSE; } } else { // We are debouncing if (!lSWBounce) { // Debounce interval is over! Declare switches quiet bSwitchesQuiet = TRUE; } } // Be very prepared to detect a Start Bit if (!bRXActive && !input(PIN_RX)) { nRXStartTime = TMR0; // Record TMR0 time as soon as possible! bRXActive = TRUE; } // Transmit Machine if (bTXActive) { if (TMR0 == nNextTXTime) { if (nTXBits == 10) { // That's it bTXActive = FALSE; } else if (nTXBits == 9) { // STOP Bit output_high (PIN_TX); } // Output a normal data bit else { if (cTXData & 1) { // Data bit is '1' if (input(PIN_INVOPT)) output_low (PIN_TX); else output_high (PIN_TX); } else { // Data bit is '0' if (input(PIN_INVOPT)) output_high (PIN_TX); else output_low (PIN_TX); } } cTXData >>= 1; nNextTXTime += FULL_BIT; nTXBits++; } } } } void SendLCDCommand (char c) { output_low (PIN_LCDRS); // Commands mean RS bit is LO PORTB = c; // OK. Data is on the LCD data pins output_high (PIN_LCDE); /// Pulse LCD's E pin. output_low (PIN_LCDE); /// Pulse LCD's E pin. } void SendLCDData (char c) { output_high (PIN_LCDRS);// Commands mean RS bit is HI PORTB = c; // OK. Data is on the LCD data pins output_high (PIN_LCDE); /// Pulse LCD's E pin. output_low (PIN_LCDE); /// Pulse LCD's E pin. }