Jump to content
43oh

V0JT4

Members
  • Content Count

    32
  • Joined

  • Last visited

  • Days Won

    2

Reputation Activity

  1. Like
    V0JT4 got a reaction from zeke in I2C USCI_B0 Slave?   
    Check out USCI I2C documentation, you have to shift device ID >>1, it takes only 7 low bits unlike what you see on the bus. That's the reason why your slave doesn't send ACK.
  2. Like
    V0JT4 reacted to MSP430Andy in Poor Man's Command Line Shell   
    I needed to serially input data in my "2553" LaunchPad using the hardware Uart.
    I hacked together some code which has Printf(), a receive ring buffer, Uart routines,
    and the case/switch Poor Man's Command Line Shell. You need to criss-cross the tx/rx on the LP
    header and use the terminal program in CCS 4.1. Please Note: CCS 5 does not have the terminal program installed ! :evil:
    Note: GRACE was used to setup the USCI_A0 hw UART at 9600 8 N 1
    Enjoy
     
    Edited. 1/27/12 Ti took out the terminal program in CCS v 5 but you can add it back into the software.
     
    http://processors.wiki.ti.com/index.php/How_to_install_the_terminal_plugin_in_CCSv5
     

    /*********************************************************************************/ /*! THIS SOFTWARE IS PROVIDED BY THE THE HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. IF YOU USE THIS SOFTWARE AND YOUR HOUSE BURNS DOWN AND YOUR WIFE AND CHILDREN BECOMES HOMELESS AND WORST OF ALL YOUR DOG DIES, DON'T HOLD ME RESPONSIBLE! YOU CAN USE THIS CODE BUT PLEASE GIVE CREDIT TO THOSE WHO CONTRIBUTED TO IT. PLEASE INCLUDE ALL COPYWRITES HEADERS/FOOTERS IN YOUR SOFTWARE. */ /*********************************************************************************/ /*********************************************************************************/ /*! Author: MSP430Andy Description: LP_2553_Cmd_Line - command line shell or interpreter. Program Revision: 1.0b (beta) Date: 2/1/12 PC Platform: Windows PC/SP3 Development platform: LaunchPad revision 2 firmware. PC compiler used: Eclipse with CCS 4.1 & 5 <--- Missing terminal program in 5 !!!!! Configuration setup I/O generated by: GRACE Device used: MSP430G2553 Pin DIP Software Module: USCI_A0 at 9600, 8 N 1 Bugs: 1. Syntax warnings in function printf()? - routine works but there were no errors in CCS 4.1? Code size: 2553 Device: 16K flash, 512 bytes RAM Flash used: xxx k RAM used: xxx bytes (without printf_Test() Program status OK */ /*********************************************************************************/ /*********************************************************************************/ /*! [includes] */ /*********************************************************************************/ #include // GRACE Include #include // Support Include #include // for printF #include /*********************************************************************************/ /*! [Defines] */ /*********************************************************************************/ // Red LED - on board on LaunchPad #define RED_LED_OFF() P1OUT &= ~BIT0 #define RED_LED_ON() P1OUT |= BIT0 #define RED_LED_FLIP() P1OUT ^= BIT0 // Green LED - on board on LaunchPad #define GRN_LED_OFF() P1OUT &= ~BIT6 #define GRN_LED_ON() P1OUT |= BIT6 #define GRN_LED_FLIP() P1OUT ^= BIT6 #define TX_RX_DIAG 0 // [1 = Toggles LP LEDs rx = grn & tx = red] [0 = all off] #define SYNC_BYTE 0x7E // 127 dec #define ESCAPE_BYTE 0x7D // 128 dec /*********************************************************************************/ /*! [Rx Ring Buffer] */ /*********************************************************************************/ //Ring Buffer #define RING_BUFFER_SIZE 32 char ring_buffer[RING_BUFFER_SIZE]; // usci_A0 rx ring buffer /* Size of RX ring buffer */ #define RING_BUFFER_MASK ( RING_BUFFER_SIZE - 1) #if ( RING_BUFFER_SIZE & RING_BUFFER_MASK ) #error RX ring buffer size is not a power of 2 #endif // Ring Buffer Global Variables int ring_buffer_data_size = 0; // number of chars in buffer int ring_buffer_read_pointer = 0; // indice number of last read char int ring_buffer_write_pointer = 0; // indice number of last written char /*********************************************************************************/ /*! [Printf] */ /*********************************************************************************/ //Printf Functions void printf_putc (unsigned char c); void printf_puts(char *s); void printf(char *, ...); void printf_Test(void); /*********************************************************************************/ /*! [Rx Receive Callback] */ /*********************************************************************************/ // Callbacks static uint8_t dummy_callback( uint8_t ); // default ring buffer callback // Holds pointers to all callback functions for USCI_AO rx buffer static uint8_t (*uart_rx_callback)( uint8_t ) = dummy_callback; /*********************************************************************************/ /*! [Function Prototypes] */ /*********************************************************************************/ // rx ring buffer prototypes int ring_buffer_full(void); int ring_buffer_empty(void); void ring_buffer_push_char(char c); // to ring buffer unsigned char ring_buffer_pull_char(void); // from ring buffer <------<<<< for rx void flush_ring_buffer(void); // clear ring buffer with spaces 0x20 // usci_a0 uart prototypes void uart_put_char( uint8_t ); // <-------<<<<< for tx void uart_write_buffer( uint8_t*, uint16_t ); void uart_write_buffer_escaped( uint8_t*, uint16_t ); uint8_t hex_to_string( uint8_t* , uint8_t*, uint8_t ); // cmd line prototypes void cmd_show_help(void); void poll_cmd(void); /*********************************************************************************/ /*! [Main] */ /*********************************************************************************/ void main(void) { CSL_init(); // Activate Grace-generated configuration __enable_interrupt(); // Set global interrupt enable flush_ring_buffer(); // clear receive ring buffer with spaces 0x20 //_________________________________________________________________________________ RED_LED_OFF(); GRN_LED_OFF(); //printf_Test(); cmd_show_help(); while(1){ poll_cmd(); } } // end of main /*********************************************************************************/ /*! [uART_USCI_A0_RX_ISR] */ /*********************************************************************************/ void UART_USCI_A0_RX_ISR(void){ // Process incoming byte from USART if( IFG2 & UCA0RXIFG ) { // Call rx callback function (default - dummy) or // ring_buffer_callback if selected if( uart_rx_callback( UCA0RXBUF ) ) { // If function returns something nonzero, wakeup the processor //__bic_SR_register_on_exit(LPM1_bits); _nop(); } } } /*********************************************************************************/ /*! [user Application Functions] */ /*********************************************************************************/ /*********************************************************************************/ /*! [uART Functions] */ /*********************************************************************************/ /*********************************************************************************/ /*! @fn uart_put_char( uint8_t character ) @brief transmit single character */ /*********************************************************************************/ void uart_put_char( uint8_t character ) { while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = character; if (TX_RX_DIAG){ RED_LED_FLIP(); } } /*********************************************************************************/ /*! @fn uart_write_buffer( uint8_t character ) @brief transmit whole buffer */ /*********************************************************************************/ void uart_write_buffer( uint8_t* buffer, uint16_t length ) { uint16_t buffer_index; for( buffer_index = 0; buffer_index < length; buffer_index++ ) { while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = buffer[buffer_index]; } } /*********************************************************************************/ /*! @fn uart_write_buffer_escaped( uint8_t character ) @brief transmit whole buffer while escaping characters The uart_write_buffer_escaped() is more for sending whole packets through the serial peripheral to the computer. Normally you just send a stream of bytes. Unfortunately, there is no way for the computer to know when a stream of data begins or ends. This function uses the 0x7E byte as a start and end of packets. This way, the receiver knows when to begin and end capturing data. If your data contains a 0x7E, that might confuse the receiver, so you must escape it by preceding it with a 0x7D and modifying it. The receiver must be aware of this to process it accordingly. */ /*********************************************************************************/ void uart_write_buffer_escaped( uint8_t* buffer, uint16_t length ) { uint16_t buffer_index; while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = SYNC_BYTE; for( buffer_index = 0; buffer_index < length; buffer_index++ ) { if( (buffer[buffer_index] == SYNC_BYTE) | (buffer[buffer_index] == ESCAPE_BYTE) ) { while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = ESCAPE_BYTE; while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = buffer[buffer_index] ^ 0x20; } else { while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = buffer[buffer_index]; } } while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = SYNC_BYTE; } /*********************************************************************************/ /*! @fn void dummy_callback( uint8_t rx_char ) @brief empty function works as default callback */ /*********************************************************************************/ static uint8_t dummy_callback( uint8_t rx_char ) { if (TX_RX_DIAG){ GRN_LED_FLIP(); } //uart_put_char(rx_char); // turnaround echo to tx <----- for test only ring_buffer_push_char(rx_char); // Put/push char into ring buffer return 0; } /*********************************************************************************/ /*! @fn uint8_t hex_to_string( uint8_t* buffer_out, uint8_t* buffer_in, uint8_t buffer_in_size ) @brief DEBUG function used to convert hex values to [hex]string format The hex_to_string function was just for debugging. When you want to display a value, you usually use printf(). Unfortunately, printf takes a lot of cycles to format the data, and if you're doing time-sensitive stuff, you can't afford the delay. In cases where you don't have full debugging capability, you might want to print out the values of some registers or variables. the hex_to_string() function converts a value to a hexadecimal string. */ /*********************************************************************************/ uint8_t hex_to_string( uint8_t* buffer_out, uint8_t* buffer_in, uint8_t buffer_in_size ) { static const uint8_t hex_char[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; uint8_t counter = 0; while( counter < buffer_in_size * 2 ) { buffer_out[counter] = hex_char[((buffer_in[(counter>>1)]>>4) & 0xF)]; counter++; buffer_out[counter] = hex_char[(buffer_in[(counter>>1)] & 0xF)]; counter++; } // Terminate string with null character buffer_out[counter++] = 0; return counter; } /*********************************************************************************/ /*! [Printf Functions] */ /*********************************************************************************/ /*********************************************************************************/ /*! @fn void printf_putc (unsigned char c) @brief put char into tx buffer */ /*********************************************************************************/ void printf_putc (unsigned char c) { _nop(); while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready? UCA0TXBUF = c; // TX <-- c } /*********************************************************************************/ /*! @fn void printf_puts(char *s) { while(*s) printf_putc(*s++); @brief put string chars into into tx buffer */ /*********************************************************************************/ void printf_puts(char *s) { while(*s) printf_putc(*s++); } /*********************************************************************************/ /*! @fn @brief */ /*********************************************************************************/ static const unsigned long dv[] = { // 4294967296 // 32 bit unsigned max 1000000000, // +0 100000000, // +1 10000000, // +2 1000000, // +3 100000, // +4 // 65535 // 16 bit unsigned max 10000, // +5 1000, // +6 100, // +7 10, // +8 1, // +9 }; /*********************************************************************************/ /*! @fn @brief */ /*********************************************************************************/ static void xtoa(unsigned long x, const unsigned long *dp) { char c; unsigned long d; if(x) { while(x < *dp) ++dp; do { d = *dp++; c = '0'; while(x >= d) ++c, x -= d; printf_putc(c); } while(!(d & 1)); } else printf_putc('0'); } /*********************************************************************************/ /*! @fn @brief */ /*********************************************************************************/ static void puth(unsigned n) { static const char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; printf_putc(hex[n & 15]); } /***********************************************************************************/ /*! @fn @brief */ /***********************************************************************************/ void printf(char *format, ...) { char c; int i; long n; va_list a; va_start(a, format); while(c = *format++) { if(c == '%') { switch(c = *format++) { case 's': // String printf_puts(va_arg(a, char*)); break; case 'c': // Char printf_putc(va_arg(a, char)); break; case 'i': // 16 bit Integer case 'u': // 16 bit Unsigned i = va_arg(a, int); if(c == 'i' && i < 0) i = -i, printf_putc('-'); xtoa((unsigned)i, dv + 5); break; case 'l': // 32 bit Long case 'n': // 32 bit uNsigned loNg n = va_arg(a, long); if(c == 'l' && n < 0) n = -n, printf_putc('-'); xtoa((unsigned long)n, dv); break; case 'x': // 16 bit heXadecimal i = va_arg(a, int); puth(i >> 12); puth(i >> 8); puth(i >> 4); puth(i); break; case 0: return; default: goto bad_fmt; } } else bad_fmt: printf_putc(c); } va_end(a); } /***********************************************************************************/ /*! @fn @brief */ /***********************************************************************************/ void printf_Test(void){ /* ================= Printf() for 2553 ================= There are 7 format specifiers: %c - Character %s - String %i - signed Integer (16 bit) %u - Unsigned integer (16 bit) %l - signed Long (32 bit) %n - uNsigned loNg (32 bit) %x - heXadecimal (16 bit) Field width, floating point and other standard printf() features are not supported. */ const char *s = "test"; const char c = 'X'; const int i = -12345; const unsigned u = 12345; const long int l = -1234567890; const long unsigned n = 1234567890; const unsigned x = 0xABCD ; //hex printf("%s", "\r\n*** printf() test ***\r\n"); printf("String %s\r\n", s); printf("Char %c\r\n", c); printf("Integer %i\r\n", i); printf("Unsigned %u\r\n", u); printf("Long %l\r\n", l); printf("uNsigned loNg %n\r\n", n); printf("heX %x\r\n", x); printf("multiple args %s %c %i %u %l %n %x\r\n", s, c, i, u, l, n, x); printf("\r\n*** Done ***\r\n"); // printf_Test result .... /* *** printf() test *** String test Char X Integer -12345 Unsigned 12345 Long -1234567890 uNsigned loNg 1234567890 heX ABCD multiple args test X -12345 12345 -1234567890 1234567890 ABCD *** Done *** */ } /*********************************************************************************/ /*! [Ring Buffer Functions] */ /*********************************************************************************/ /*********************************************************************************/ /*! @fn void ring_buffer_push_char(char c) @brief adds a char */ /*********************************************************************************/ void ring_buffer_push_char(char c) { // increase ring_buffer_write_pointer, check if at end of array if (++ring_buffer_write_pointer >= RING_BUFFER_SIZE) ring_buffer_write_pointer = 0; ring_buffer[ring_buffer_write_pointer] = c; ring_buffer_data_size++; } /*********************************************************************************/ /*! @fn int ring_buffer_full(void) @brief returns 1 if buffer is full, 0 if buffer is not full */ /*********************************************************************************/ int ring_buffer_full(void) { return ring_buffer_read_pointer == ring_buffer_write_pointer && ring_buffer_data_size == RING_BUFFER_SIZE; } /*********************************************************************************/ /*! @fn int ring_buffer_empty(void) @brief returns 1 if buffer is empty, 0 if buffer is not empty */ /*********************************************************************************/ int ring_buffer_empty(void) { return ring_buffer_read_pointer == ring_buffer_write_pointer && ring_buffer_data_size == 0; } /*********************************************************************************/ /*! @fn void ring_buffer_pull_char(void) @brief pull char from queue */ /*********************************************************************************/ unsigned char ring_buffer_pull_char(void) { char rx; // added - also above unsigned char if (++ring_buffer_read_pointer >= RING_BUFFER_SIZE) ring_buffer_read_pointer = 0; rx = ring_buffer[ring_buffer_read_pointer]; // added // enter space on place of read char so we can see it is removed ring_buffer[ring_buffer_read_pointer] = 0x20; ring_buffer_data_size--; return rx; // added } /*********************************************************************************/ /*! @fn void flush_ring_buffer(void) @brief flushes ring buffer */ /*********************************************************************************/ void flush_ring_buffer(void){ // make sure there are no random chars in array, all spaces int i; for (i = 0; i < RING_BUFFER_SIZE; i++) ring_buffer[i] = 0x20; // spaces } /*********************************************************************************/ /*! [Command Line Functions] */ /*********************************************************************************/ /*********************************************************************************/ /*! @fn void cmd_show_help(void) @brief show cmd line help menu */ /*********************************************************************************/ void cmd_show_help(void) { const char *m0 = "<<--COMMAND LINE MENU-->>"; const char *m1 = "[A] LP Red LED ON"; const char *m2 = "[b] LP Red LED OFF"; const char *m3 = "[C] LP Green LED ON"; const char *m4 = "[D] LP Green LED OFF"; const char *m5 = "[E] Command #5 Spare Text"; const char *m6 = "[F] Command #6 Spare Text"; const char *m7 = "[G] Command #7 Spare Text"; const char *m8 = "[?] Show Help"; const char *prompt = "Cmd >> "; printf("\r\n"); printf("\r\n"); printf("%s\r\n",m0); printf("\r\n"); printf("%s\r\n",m1); printf("%s\r\n",m2); printf("%s\r\n",m3); printf("%s\r\n",m4); printf("%s\r\n",m5); printf("%s\r\n",m6); printf("%s\r\n",m7); printf("%s\r\n",m8); printf("\r\n"); printf("%s",prompt); } /*********************************************************************************/ /*! @fn @brief */ /*********************************************************************************/ void poll_cmd(void) { while(1){ char x; if (ring_buffer_data_size > 0){ x = ring_buffer_pull_char(); uart_put_char(x); // return/echo cmd char to uart screen } switch (x){ case 'A': case 'a': _nop(); RED_LED_ON(); _nop(); cmd_show_help(); break; case 'B': case 'b': _nop(); RED_LED_OFF(); _nop(); cmd_show_help(); break; case 'C': case 'c': GRN_LED_ON(); cmd_show_help(); break; case 'D': case 'd': GRN_LED_OFF(); cmd_show_help(); break; case 'E': case 'e': cmd_show_help(); break; case 'F': case 'f': cmd_show_help(); break; case 'G': case 'g': cmd_show_help(); break; case '?': cmd_show_help(); break; //default: } // end of case x = 0x20; // clear case char _nop(); } // end of while } //================================================================================= //================================================================================= // The End
  3. Like
    V0JT4 got a reaction from roadrunner84 in Universal Ripple Control Receiver   
    The goal of my project is to build ripple control receiver with some additional functions, main request was to receive massages and sent them via UART but current plan includes many more functions:
    - receive and decode messages
    - 4 relays triggered by certain message content
    - message logging to EEPROM with date and time from RTC
    - configuration will be stored on RTC DS1338 user memory
    - simple CLI via UART
    - 4 buttons and 20x4 character LCD interface
    - listing recorded messages with filtering
     
    Ripple control messages are transmitted to electricity distribution network, usually at 110kV or 22kV level and pass transformers down to end user 230V mains line. It is used to signal low and high tariff to electricity meters and high non-critical loads to optimize power distribution. The signal uses low modulation frequency, most common in Czech Republic is 216.7Hz. One message takes about 63 seconds to transmit. This allows one transmitter to cover large area. So far I'm getting around 200 messages a day with first receiver prototype.
     
    I plan to use these components:
    - MSP430G2553
    - modified old receiver ZPA FMX-100 for signal extraction from mains, opto-coupled connection
    - AT24C512 I2C EEPROM, 64Kx8 bit
    - DS1338 I2C RTC
    - 20x4 character LCD HD44780 with shift register 74HCT164
    - ULN2803 for relay switching with shift register 74HC595
    - USB to UART module with FTDI or CP2102 chip, opto-coupled UART
     
    Does someone have good experience with DS1338 or would you recommend other RTC? I need 3.3V operation and battery backup, square wave output is welcomed.
  4. Like
    V0JT4 got a reaction from roadrunner84 in Universal Ripple Control Receiver   
    So far I have built two versions of simple message logger based on LaunchPad, first uses almost unmodified FMX-100 and the next one dropped top board and required some wire cutting on bottom board. I needed to use capacitor to smoothen the signal, 10uF with 2kOhm resistor to LED and opto coupler works just fine. So far I have around 100 hours recorded. Now it's time to concentrate on software part.
     
    Old receiver ZPA FMX-100:

    Inside:

    Message format, time is in secconds:

    Message recorded on scope:

    Logger V1:

    Logger V2:

    Receiver schema V0.8:

    Receiver board V0.8:

    Log sample, time is counted from system start just to get picture how often messages come:

    A1000B00000100P___0____________ +314:11 m:s A1000B01000000P_____1__________ +320:9 m:s A1001B10000000P___1____1_____1_ +324:12 m:s A1000B00110010P_______1_1___1__ +325:32 m:s A1000B00000100P_________0______ +326:52 m:s A0001B11000000P_______1_1___1__ +328:11 m:s A0010B10000000P______00________ +336:9 m:s A1010B11000100P00____00____0__0 +337:28 m:s A1000B00100000P____000_________ +338:47 m:s A1000B00001001P_0___010________ +340:8 m:s A1000B00010000P____0_______0__0 +341:27 m:s
  5. Like
    V0JT4 got a reaction from timotet in Universal Ripple Control Receiver   
    So far I have built two versions of simple message logger based on LaunchPad, first uses almost unmodified FMX-100 and the next one dropped top board and required some wire cutting on bottom board. I needed to use capacitor to smoothen the signal, 10uF with 2kOhm resistor to LED and opto coupler works just fine. So far I have around 100 hours recorded. Now it's time to concentrate on software part.
     
    Old receiver ZPA FMX-100:

    Inside:

    Message format, time is in secconds:

    Message recorded on scope:

    Logger V1:

    Logger V2:

    Receiver schema V0.8:

    Receiver board V0.8:

    Log sample, time is counted from system start just to get picture how often messages come:

    A1000B00000100P___0____________ +314:11 m:s A1000B01000000P_____1__________ +320:9 m:s A1001B10000000P___1____1_____1_ +324:12 m:s A1000B00110010P_______1_1___1__ +325:32 m:s A1000B00000100P_________0______ +326:52 m:s A0001B11000000P_______1_1___1__ +328:11 m:s A0010B10000000P______00________ +336:9 m:s A1010B11000100P00____00____0__0 +337:28 m:s A1000B00100000P____000_________ +338:47 m:s A1000B00001001P_0___010________ +340:8 m:s A1000B00010000P____0_______0__0 +341:27 m:s
  6. Like
    V0JT4 got a reaction from oPossum in Universal Ripple Control Receiver   
    So far I have built two versions of simple message logger based on LaunchPad, first uses almost unmodified FMX-100 and the next one dropped top board and required some wire cutting on bottom board. I needed to use capacitor to smoothen the signal, 10uF with 2kOhm resistor to LED and opto coupler works just fine. So far I have around 100 hours recorded. Now it's time to concentrate on software part.
     
    Old receiver ZPA FMX-100:

    Inside:

    Message format, time is in secconds:

    Message recorded on scope:

    Logger V1:

    Logger V2:

    Receiver schema V0.8:

    Receiver board V0.8:

    Log sample, time is counted from system start just to get picture how often messages come:

    A1000B00000100P___0____________ +314:11 m:s A1000B01000000P_____1__________ +320:9 m:s A1001B10000000P___1____1_____1_ +324:12 m:s A1000B00110010P_______1_1___1__ +325:32 m:s A1000B00000100P_________0______ +326:52 m:s A0001B11000000P_______1_1___1__ +328:11 m:s A0010B10000000P______00________ +336:9 m:s A1010B11000100P00____00____0__0 +337:28 m:s A1000B00100000P____000_________ +338:47 m:s A1000B00001001P_0___010________ +340:8 m:s A1000B00010000P____0_______0__0 +341:27 m:s
  7. Like
    V0JT4 reacted to turd in MSP430FG4618 and 24LC512   
    Here's an example using an MSP430G2553 and a 24LC256.
    It's not perfect but it works.
     
    It first writes "HELLO WORLD" too 0x0000 then reads it back and sends it out the UART at 9600.
    I hope it helps
     

    //#include //For mspgcc //#include //For mspgcc #include "msp430g2553.h" //Address word + "HELLO WORLD" unsigned char txdata[14] = {0x00, 0x00, 0x00, 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44}; unsigned char rxdata[12]; unsigned char tx_byte_count; unsigned char rx_byte_count; unsigned char tx_byte_counter; unsigned char rx_byte_counter; unsigned char i; unsigned char tx_rx; void i2c_tx(unsigned char tx_count); void i2c_rx(unsigned char rx_count); void main(void) { WDTCTL = WDTPW + WDTHOLD; //Stop WDT BCSCTL1 = CALBC1_1MHZ; //Set DCO to 1MHz DCOCTL = CALDCO_1MHZ; P1SEL = BIT1 + BIT2; //Set RXD and TXD P1SEL2 = BIT1 + BIT2; UCA0CTL1 |= UCSSEL_2; //Have USCI use SMCLK AKA 1MHz main CLK UCA0BR0 = 104; //Baud = 9600 UCA0BR1 = 0; UCA0MCTL = UCBRS_1; //Modulation UCA0CTL1 &= ~UCSWRST; //Start USCI P1SEL |= BIT6 + BIT7; //Set I2C pins P1SEL2|= BIT6 + BIT7; UCB0CTL1 |= UCSWRST; //Enable SW reset UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; //I2C Master, synchronous mode UCB0CTL1 = UCSSEL_2 + UCSWRST; //Use SMCLK, keep SW reset UCB0BR0 = 12; //fSCL = SMCLK/12 = ~100kHz UCB0BR1 = 0; UCB0I2CSA = 0b1010000; //Slave Address UCB0CTL1 &= ~UCSWRST; //Clear SW reset, resume operation IE2 |= UCB0TXIE; //Enable TX interrupt IE2 |= UCB0RXIE; //Enable RX interrupt __delay_cycles(20000); //Just a start up delay i2c_tx(13); //i2c TX 13 bytes(Address word + "HELLO WORLD" __delay_cycles(20000); //Allow 24LC256 to write data i2c_tx(2); //i2c TX address i2c_rx(12); //i2c RX data for(i = 1; i < 12; i++) { while(!(IFG2 & UCA0TXIFG)); UCA0TXBUF = rxdata[i]; //UART TX data } __bis_SR_register(CPUOFF + GIE); //Wait for a reset } void i2c_tx(unsigned char tx_count) { tx_rx = 0; tx_byte_count = tx_count + 1; tx_byte_counter = tx_count; // Load TX byte counter UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition __bis_SR_register(CPUOFF + GIE); // Enter LPM0 w/ interrupts // Remain in LPM0 until all data is TX'd } void i2c_rx(unsigned char rx_count) { tx_rx = 1; rx_byte_count = rx_count + 1; rx_byte_counter = rx_count; // Load RX byte counter UCB0CTL1 &= ~UCTR; // I2C RX UCB0CTL1 |= UCTXSTT; // I2C start condition __bis_SR_register(CPUOFF + GIE); // Enter LPM0 w/ interrupts // Remain in LPM0 until all data is RX'd } //interrupt(USCIAB0TX_VECTOR) USCIAB0TX_ISR(void) #pragma vector = USCIAB0TX_VECTOR __interrupt void USCIAB0TX_ISR(void) //For mspgcc { if(tx_rx == 0) { if (tx_byte_counter > 0) //Check TX byte counter { UCB0TXBUF = txdata[tx_byte_count - tx_byte_counter]; // Load TX buffer tx_byte_counter--; //Decrement TX byte counter } else if(tx_byte_counter == 0) { UCB0CTL1 |= UCTXSTP; //I2C stop condition while (UCB0CTL1 & UCTXSTP); //Ensure stop condition got sent IFG2 &= ~UCB0TXIFG; //Clear USCI_B0 TX int flag __bic_SR_register_on_exit(CPUOFF); //Exit LPM0 } } else if(tx_rx == 1) { if (rx_byte_counter > 0) //Check RX byte counter { rxdata[rx_byte_count - rx_byte_counter] = UCB0RXBUF; rx_byte_counter--; //Decrement RX byte counter } else if(rx_byte_counter == 0) { UCB0CTL1 |= UCTXSTP; // I2C stop condition while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent rxdata[rx_byte_count - (rx_byte_counter + 1)] = UCB0RXBUF; rxdata[rx_byte_count - (rx_byte_counter + 1)] = UCB0RXBUF; IFG2 &= ~UCB0RXIFG; // Clear USCI_B0 RX int flag __bic_SR_register_on_exit(CPUOFF); // Exit LPM0 } } }
    It hasn't been tested with CCS or IAR just mspgcc.
     



  8. Like
    V0JT4 reacted to cde in i2c Explorer   
    NOTE: This was built using IAR, for an msp430 with the USI peripheral. It will need editing to work on USCI peripherals or if compiled in CCS or GCC.
    Updated 12/12/12: http://forum.43oh.com/topic/126-i2c-explorer/?p=25918
     

    I like i2c, and have one main project I want to create based on turning a scrolling led display into a computer/net/micro controller accessible one (it has an i2c eeprom, and its original mc is still in use).

    Having gotten the launchpad, it was one step closer to completion. The next step was getting i2c protocol working and such. With the builtin USI of the launchpad, and Joby's SPI explorer (which uses NJ's uart to boot), I was able to make an i2c Explorer.

    It uses the UART code without modification (except that I am using IAR instead of mspgcc or CCS) and modified Joby's command processing, with the i2c USI built from scratch.

    There are a handful of commands.
    [- i2c START
    bx or 0bx- Transmit Binary number
    hx or 0x- Transmit Hex number
    x- Transmit Decimal number
    r read with Acknowledge (Ack)
    n read with No-Acknowledge (Nack)
    s Search for i2c Slaves
    ]- i2c STOP

    Nearly the same commands as the buspirate/busninja/spi-explorer. I broke out the nack read because it was easier, allows more control, and not all i2c slaves want/expect a nack on the last read.

    It is not completely refined. Still some stuff that needs to be removed/polished (Slave search should only output on responded addresses, instead of needing someone to search through all 255 address tests responses.)

    Bus speed is non-standard as far as I can see (USI Clock is SMCLK at 1mhz, divided at 128 as per TI's USI application note. 1mhz/128 = 7812hz = ~8khz???) This needs to be fixed, and a TI introduction to USI/USCI powerpoint shows that the bus can be driven at 500khz at 1mhz clock. Probably just needs the correct divider selection (Divide clock by 2 for 500k (400k i2c speed) or by 8 for 125k (100k i2c speed).

    This is beta/preliminary release. Only been tested with a PCF8574. Code clocks in at 1920 bytes, with no optimization (Standard IAR project with no defaults changed). UART at 9600 like normal, pin 1.7 is sda, pin 1.6 is scl (can't be changed due to USI so remove the p1.6 led jumper) and p1.0 is a status light.

    Will be testing with faster speed and more i2c devices soon.
    i2c.zip
  9. Like
    V0JT4 reacted to oPossum in DIY Mini Launchpad   
    Make use of the second MCU (and female headers) included with the Launchpad 50 mm x 50 mm board size - low cost from Seeed & iTead Mostly through hole - anyone can build this Populate with components as needed Mounting holes for secure attachment Power, P1.6 & P1.0 LEDs LED enable jumpers Reset & P1.3 switch Coaxial power jack and/or terminal block Optional Microchip MCP1700/1702 voltage regulator 6 pin power/program/debug/serial connector UART line swap jumper Prototype with Launchpad, build permanent project with this.
    lp_mini.brd
    lp_mini.sch

  10. Like
    V0JT4 reacted to pine in New chips shipping with the LaunchPad?   
    Some pics for the comparison, on the Left is the classic LP, the right is the new LP
     

     
     
    And the back...

     
    The box of the new LP

  11. Like
    V0JT4 reacted to oPossum in Tiny printf() - C version   
    This is a tiny printf() function that can be used with the chips that come with the Launchpad. Code size is about 640 bytes with CCS.
     
    There are 7 format specifiers:
    %c - Character
    %s - String
    %i - signed Integer (16 bit)
    %u - Unsigned integer (16 bit)
    %l - signed Long (32 bit)
    %n - uNsigned loNg (32 bit)
    %x - heXadecimal (16 bit)
     
    Field width, floating point and other standard printf() features are not supported.
     
    printf() code

    #include "msp430g2231.h" #include "stdarg.h" void putc(unsigned); void puts(char *); static const unsigned long dv[] = { // 4294967296 // 32 bit unsigned max 1000000000, // +0 100000000, // +1 10000000, // +2 1000000, // +3 100000, // +4 // 65535 // 16 bit unsigned max 10000, // +5 1000, // +6 100, // +7 10, // +8 1, // +9 }; static void xtoa(unsigned long x, const unsigned long *dp) { char c; unsigned long d; if(x) { while(x < *dp) ++dp; do { d = *dp++; c = '0'; while(x >= d) ++c, x -= d; putc(c); } while(!(d & 1)); } else putc('0'); } static void puth(unsigned n) { static const char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; putc(hex[n & 15]); } void printf(char *format, ...) { char c; int i; long n; va_list a; va_start(a, format); while(c = *format++) { if(c == '%') { switch(c = *format++) { case 's': // String puts(va_arg(a, char*)); break; case 'c': // Char putc(va_arg(a, char)); break; case 'i': // 16 bit Integer case 'u': // 16 bit Unsigned i = va_arg(a, int); if(c == 'i' && i < 0) i = -i, putc('-'); xtoa((unsigned)i, dv + 5); break; case 'l': // 32 bit Long case 'n': // 32 bit uNsigned loNg n = va_arg(a, long); if(c == 'l' && n < 0) n = -n, putc('-'); xtoa((unsigned long)n, dv); break; case 'x': // 16 bit heXadecimal i = va_arg(a, int); puth(i >> 12); puth(i >> 8); puth(i >> 4); puth(i); break; case 0: return; default: goto bad_fmt; } } else bad_fmt: putc(c); } va_end(a); }
     
    test code

    #include "msp430g2231.h" void serial_setup(unsigned out_mask, unsigned in_mask, unsigned duration); void printf(char *, ...); void main(void) { char *s; char c; int i; unsigned u; long int l; long unsigned n; unsigned x; // Disable watchdog WDTCTL = WDTPW + WDTHOLD; // Use 1 MHz DCO factory calibration DCOCTL = 0; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; // Setup the serial port // Serial out: P1.1 (BIT1) // Serial in: P1.2 (BIT2) // Bit rate: 9600 (CPU freq / bit rate) serial_setup(BIT1, BIT2, 1000000 / 9600); printf("%s", "\r\n*** printf() test ***\r\n"); s = "test"; c = 'X'; i = -12345; u = 12345; l = -1234567890; n = 1234567890; x = 0xABCD; printf("String %s\r\n", s); printf("Char %c\r\n", c); printf("Integer %i\r\n", i); printf("Unsigned %u\r\n", u); printf("Long %l\r\n", l); printf("uNsigned loNg %n\r\n", n); printf("heX %x\r\n", x); printf("multiple args %s %c %i %u %l %n %x\r\n", s, c, i, u, l, n, x); printf("\r\n*** Done ***\r\n"); for(;; }

  12. Like
    V0JT4 reacted to oPossum in Dallas/Maxim One Wire Library   
    Here is a basic library for 1-wire communication. It works with CPU frequency of 1 MHz or higher. Carefully crafted assembly code is used to allow operation over a wide CPU clock frequency range.
     
    There are only two functions. One for setup and the other for all communication tasks.
     

    // one_wire.h void one_wire_setup(volatile unsigned char *dir, volatile unsigned char *in, unsigned bitmask, unsigned mhz); int owex(int data, unsigned bits);
     
    The library is initialized by a call to one_wire_setup(). The arguments specify the I/O pin and clock frequency.
    An example for P1.4 at 16 MHz...
     
    one_wire_setup(&P1DIR, &P1IN, 0x10, 16);
     
    The setup function can be called repeatedly to allow communication on more than one I/O pin.
     
    The owex() [One Wire EXchange] function does bus reset, tx and rx.
     
    To reset the bus and detect if any devices are present, just call it with a bit count of zero....
     
    p = owex(0, 0);
     
    The returned value will be -1 (0xFFFF) if any devices are detected, or 0 if none are detected.
     
    To send data...
     
    owex(0xCC, 8); // Skip ROM command
     
    The returned value should match the data sent, if it does not then there is a bus problem.
     
    To receive data, use -1 (0xFFFF) for the tx data value...
     
    d = owex(-1, 8); // Get 8 bits
     
    1 to 16 bits can be sent/received at once.
     
    This is the core code...

    .def owex ; int owex(int data, unsigned bit_count) .def owdport .def owiport .def owbit .def owtiming .bss owdport, 2 ; Direction port .bss owiport, 2 ; Input port .bss owbit, 2 ; Port bitmask .bss owtiming, 0 ; Timing array .bss owda, 2 ; 28 Zero .bss owdb, 2 ; 33 .bss owdc, 2 ; 39 .bss owdd, 2 ; 7 One .bss owde, 2 ; 8 .bss owdf, 2 ; 85 .bss owdg, 2 ; 500 Reset .bss owdh, 2 ; 50 .bss owdi, 2 ; 450 owex ; tst R13 ; Reset? jeq owrst ; Yes... push R11 ; Save R11, R13 push R13 ; clr R13 ; Clear bit count ; owloop ; --- Tx/Rx bit loop rra R12 ; Get tx bit jc owone ; If one... ; ; - Send and verify zero mov &owda, R15 ; nop ; mov &owdport, R14 ; Bus low bis.b &owbit, 0(R14) ; ; call #owdelay ; Delay 28 us ; mov &owiport, R14 ; Sample bus mov.b @R14, R14 ; bit &owbit, R14 ; rrc R11 ; ; mov &owdb, R15 ; Delay 33 us call #owdelay ; ; mov &owdc, R15 ; mov &owdport, R14 ; Bus open bic.b &owbit, 0(R14) ; ; jmp ownext ; Delay 39 us ; owone ; - Send one and read bit mov &owdd, R15 ; tst R15 ; mov &owdport, R14 ; Bus low bis.b &owbit, 0(R14) ; ; jn owoneo ; Delay 7 us owdlyd ; sub #8, R15 ; nop ; jc owdlyd ; subc R15, PC ; nop ; nop ; nop ; owoneo ; bic.b &owbit, 0(R14) ; Bus open ; jc owones ; Delay 8 us mov &owde, R15 ; owdlye ; sub #8, R15 ; nop ; jc owdlye ; subc R15, PC ; nop ; nop ; nop ; owones ; mov &owiport, R14 ; Sample bus mov.b @R14, R14 ; bit &owbit, R14 ; rrc R11 ; ; mov &owdf, R15 ; Delay 85 us ownext ; call #owdelay ; ; - Next bit inc R13 ; Increment bit count cmp R13, 0(SP) ; Compare bit count jne owloop ; Loop if not done... owrxa ; - Align rx data cmp #16, R13 ; Rx data aligned? jeq owrex ; Yes.. rra R11 ; Shift in a zero bit inc R13 ; Inc bit count jmp owrxa ; Next bit... owrex ; mov R11, R12 ; Get rx data to R12 pop R13 ; Restore R11, R13 pop R11 ; ret ; Return ; ; owrst ; - Reset and presence detect mov &owdport, R14 ; Bus low bis.b &owbit, 0(R14) ; ; mov &owdg, R15 ; Delay 500 us call #owdelay ; ; bic.b &owbit, 0(R14) ; Bus open ; mov &owdh, R15 ; Delay 50 us call #owdelay ; ; mov &owiport, R14 ; Sample bus mov.b @R14, R14 ; bit &owbit, R14 ; subc R12, R12 ; ; mov &owdi, R15 ; Delay 450 us ;jmp owdelay ; and return ; owdelay ; sub #8, R15 ; nop ; jc owdelay ; subc R15, PC ; nop ; nop ; nop ; ret ; ; .end ;
     
    Demo program to read DS1820/1822 temperature sensor is included in zip file.
    one_wire.zip
  13. Like
    V0JT4 got a reaction from RobG in [ ENDED ] June 2011 - 43oh Project of the Month Contest   
    My entry: Slot Cars Lap Counter/Timer
    Finally it works the way I like it.

    EDIT 21.6.: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
  14. Like
    V0JT4 got a reaction from bluehash in Slot Cars Lap Counter/Timer   
    Good news everyone! I have a new code version (see first post), now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).

  15. Like
    V0JT4 reacted to bluehash in Slot Cars Lap Counter/Timer   
    You got Hackaday'ed!
  16. Like
    V0JT4 got a reaction from RobG in Slot Cars Lap Counter/Timer   
    Firs post was updated with some more info, final code and video showing it in action.


  17. Like
    V0JT4 got a reaction from gordon in Slot Cars Lap Counter/Timer   
    Here is my first project with LaunchPad - Slot Cars Lap Counter/Timer. I wanted to build the lap counter for quite a long time, originally from BCD counter ICs and 7 segment LED display. Some time passed and my friend told me about LaunchPad, it looked like a great opportunity to start with MCUs and make some old ideas reality.


    Main idea was to use character LCD instead of 7seg LED as it gives you much more space to display stuff and I had one lying (from old computer connected to LPT with LCDSmartie). I used great RobG's project and slightly modified it to my needs. As I usually build only two lane circuits, I used custom characters feature of HD44780 to display large digits.

    Car is detected by magnetic Reed switch (extracted from old keyboard) placed under each lane. I needed to use capacitor parallel to switch, otherwise many false interrupts were generated (one car triggering both lanes,...). TimerA is used to track time with interrupt generated 100 times per second. Long and short button press is distinguish by WDT in timer mode with accumulator variable and threshold. Buzzer is connected to TimerA output 1 in PWM mode.

    The construction was fairly simple. I used hot melt glue to attach Reed switch under each lane. You just have to be very careful bending the Reed switch contacts, it's very fragile piece of glass and mine are maybe 30 years old, extracted from computer keyboard.

    Software implements 3 modes, standard N lap race, time attack limited by N laps and free practice with no time or lap limit. You can see it in action in video posted below. It's possible to pause/resume race (short press) and restart race (long press). Buzzer signals start, end and resume of race. To change race mode just hit reset button. I wasn't able to fit in 2K limit by 200B, so I had to use MSP430G2553 I received from great TI sample program. I think without large digits it would easily fit MSP430G2211, maybe even in 4 lane modification.



    EDIT: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
    #include <msp430g2553.h> #include "lcd.h" //#define LANE_4 // uncomment to enable 4 lane mode #ifdef BIG_DIGITS // big digits enabled in lcd.h #undef LANE_4 // are incompatible with 4 lanes #endif // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #ifdef LANE_4 #define SENS2 BIT6 #define SENS3 BIT7 #endif #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s #ifdef LANE_4 // lane count adjustment #define LANES 4 static const u8 SENS[] = {SENS0, SENS1, SENS2, SENS3}; #else #define LANES 2 static const u8 SENS[] = {SENS0, SENS1}; #endif u8 hold = 0; // Long button press counter u8 lap[LANES]; // Lap number u16 time[LANES]; // Lap time in 1/100 s u16 best[LANES]; // Best Lap time u8 race; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_READY 0x30 #define STATE_START 0x40 #define STATE_PAUSE 0x50 #define STATE_RACE 0x60 #define STATE_FINISH 0x70 #define STATE_END 0x80 #define WIN1_MASK 0x03 // Race result bits u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else #ifdef LANE_4 static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', ' ', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', '0', '0', ' ', 'E', ' ', '0', '0', ' ', '0', '0', ' ', 'T', ' ', '-', '-', '.', '-', '-'}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', '.', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif /* LANE_4 */ #endif /* BIG_DIGITS */ void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 #ifdef LANE_4 P1OUT = SENS0 | SENS1 | SENS2 | SENS3; // pullup P1REN = SENS0 | SENS1 | SENS2 | SENS3; // internal resistor P1IES = BUTTON | SENS0 | SENS1 | SENS2 | SENS3; // Hi/lo edge #else P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge #endif P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer #ifdef LANE_4 P2DIR = BUZZER; // P2.6 in 4 lane mode P2SEL = BUZZER; // BUZZER TimeA output #else P1DIR = BUZZER; // P1.6 in 4 lane mode P1SEL = BUZZER; // BUZZER TimeA output #endif CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id; // print updated values for (id = 0; id < LANES; ++id) { if (P1IE & SENS[id]) { // hold after lap tostr(time[id]); // print time printT(id, 7); } if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print best printT(id, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id, 2); } } if (state == STATE_READY) { // draw init race screen lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); } if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting #ifdef LANE_4 P1IFG &= ~(SENS0 | SENS1 | SENS2 | SENS3); P1IE |= SENS0 | SENS1 | SENS2 | SENS3; #else P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; #endif } // race ended if ((state & ~WIN1_MASK) == STATE_FINISH) { #ifdef BIG_DIGITS lcdJump((state & WIN1_MASK) << 1, 1); // mark winner on LCD #else lcdJump(state & WIN1_MASK, 1); // mark winner on LCD #endif lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { u8 id; for (id = 0; id < LANES; ++id) { best[id] = 9999; lap[id] = 0; time[id] = 0; } #ifdef LANE_4 race = SENS0 | SENS1 | SENS2 | SENS3; #else race = SENS0 | SENS1; #endif } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif break; case STATE_READY: // start race case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdClear(); lcdString("Set number of laps:"); lcdJump(1, 11); lcdData('_'); #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(1, 11); lcdData(' '); lcdJump(1, 10); lcdData('_'); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_READY; raceLaps |= LAP_MARK; // for easier lap[id] comparison in newLap initData(); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif lcdJump(r, c); lcdDataArray(bufferT, 5); } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow lap[id] |= LAP_MARK; // mark for LCD update if (time[id] < best[id]) { // test best lap time best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (state >= STATE_FINISH) { race &= ~SENS[id]; // race mode looser finish } if (lap[id] == raceLaps) { race &= ~SENS[id]; if (mode == MODE_RACE && state == STATE_RACE) { state = STATE_FINISH | id; // race mode winner finish } else if (mode == MODE_TIME) { if (++state == (STATE_RACE + LANES)) { // time mode all finished u8 bestId = 0; for (id = 1; id < LANES; ++id) { // find best time if (best[id] < best[bestId]) bestId = id; } state = STATE_FINISH | bestId; } } } } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { u8 id; for (id = 0; id < LANES; ++id) { if (race & SENS[id]) { time[id]++; // increment time counter if (time[id] == MIN_LAP_TIME) { P1IFG &= ~SENS[id]; // SENS IFG cleared P1IE |= SENS[id]; // SENS interrupt enabled } else if (time[0] == 10000) time[id] = 0; // 4 digit overflow } } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 id; u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT } else { // SENS for (id = 0; id < LANES; ++id) { if (x & SENS[id]) { P1IE &= ~SENS[id]; // SENS interrupt disabled P1IFG &= ~SENS[id]; // SENS IFG cleared newLap(id); return; } } P1IFG = 0; } }
    Final version code:
    EDIT: Now with possibility to disable large digits (comment #define BIG_DIGITS in lcd.h) so it can fit in 2K FLASH devices.
    EDIT2: Deprecated, replaced by new 2/4 lane version
    #include <msp430g2553.h> #include "lcd.h" // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; u8 hold = 0; // Long button press counter u8 lap[2]; // Lap number u16 time[2]; // Lap time in 1/100 s u16 best[2]; // Best Lap time u8 race[2]; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_PAUSE 0x30 #define STATE_START 0x40 #define STATE_RACE 0x50 #define STATE_FINISH 0x60 #define STATE_END 0x70 #define WIN1_MARK 0x02 // Race result bit, set if car 1 wins u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer P1DIR = BUZZER; P1SEL = BUZZER; // BUZZER TimeA output CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id = 0; if (P1IE & SENS0) { // hold after lap tostr(time[0]); // print time printT(0, 7); } if (P1IE & SENS1) { // hold after lap tostr(time[1]); // print time printT(2, 7); } // print updated values REPEAT: if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print time printT(id << 1, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id << 1, 2); } id = !id; // to update both cars 0/1 if (id) goto REPEAT; if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; } // race ended if ((state & ~WIN1_MARK) == STATE_FINISH) { lcdJump(state & WIN1_MARK, 1); // mark winner in LCD lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } longDelay(); } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { best[0] = 9999; best[1] = 9999; lap[0] = 0; lap[1] = 0; time[0] = 0; time[1] = 0; race[0] = 1; race[1] = 1; } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD printL(1, 10); break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; P1IE &= ~(SENS0 | SENS1); break; case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode lcdClear(); if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdString("Set number of laps:"); printL(1, 10); lcdJump(3, 11); lcdData(0x2D); break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(3, 11); lcdData(' '); lcdJump(3, 10); lcdData(0x2D); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; P1IE &= ~(SENS0 | SENS1); case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_PAUSE; initData(); lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { lcdJump(r, c); lcdDataArray(bufferT, 5); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow if (time[id] best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (mode == MODE_RACE && (race[0] ^ race[1])) { race[id] = 0; // race mode looser finish } if (lap[id] == raceLaps) { race[id] = 0; if (mode == MODE_RACE && (race[0] ^ race[1])) { state = STATE_FINISH; // race mode winner finish if (id) state |= WIN1_MARK; } else if (mode == MODE_TIME && !(race[0] | race[1])) { state = STATE_FINISH; // time mode both finished if (best[0] > best[1]) { // time mode select winner state |= WIN1_MARK; } } } lap[id] |= LAP_MARK; // mark for LCD update } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { if (race[0]) { time[0]++; // increment time counter if (time[0] == MIN_LAP_TIME) { P1IFG &= ~SENS0; // SENS1 IFG cleared P1IE |= SENS0; // SENS1 interrupt enabled } else if (time[0] == 10000) time[0] = 0; // 4 digit overflow } if (race[1]) { time[1]++; // increment time counter if (time[1] == MIN_LAP_TIME) { P1IFG &= ~SENS1; // SENS2 IFG cleared P1IE |= SENS1; // SENS1 interrupt enabled } else if (time[1] == 10000) time[1] = 0; // 4 digit overflow } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT }// SENS1 else if (x & SENS0) { P1IE &= ~SENS0; // SENS1 interrupt disabled P1IFG &= ~SENS0; // SENS1 IFG cleared newLap(0); }// SENS2 else if (x & SENS1) { P1IE &= ~SENS1; // SENS2 interrupt disabled P1IFG &= ~SENS1; // SENS2 IFG cleared newLap(1); } else P1IFG = 0; }  


    Modified RobG's LCD display code with large digits:
    lcd.h
    // Pin allocation, modify to connected pins! #define LCD_D BIT2 #define LCD_C BIT1 #define LCD_E BIT0 #define BIG_DIGITS // uncomment to enable two line digits, takes ~100B #ifdef BIG_DIGITS // Dual Line digits 0-9 // Top part static const char numT[] = {0x07,0x00,0x01,0x01,0x06,0x03,0x03,0x01,0x05,0x07}; // Bottom part static const char numB[] = {0x06,0x00,0x04,0x02,0x00,0x02,0x05,0x00,0x05,0x02}; #endif void lcdInit(void); void lcdString(const char text[]); void lcdDataArray(const char data[], char length); void lcdClear(); void lcdJump(char row, char colum); void lcdSend(char data, char registerSelect); #define lcdData(data) lcdSend(data, 1) #define lcdCommand(command) lcdSend(command, 0) // commands #define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00  

    lcd.c
    #include "msp430g2553.h" #include "lcd.h" // Starts of lines for 4x20 LCD static const char Line[] = {0, 0x40, 20, 0x40 + 20}; #ifdef BIG_DIGITS // Definition of custom chars for dual line digits static const char NUMdata[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; #endif void lcdInit(void) { _delay_cycles(100000); // initialize pins P1OUT &= ~(LCD_C | LCD_D); P1OUT |= LCD_E; P1DIR |= LCD_E | LCD_C | LCD_D; // initialize display lcdCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS); lcdCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); lcdClear(); #ifdef BIG_DIGITS lcdCommand(LCD_ENTRYMODESET | LCD_ENTRYLEFT); // define 8 custom characters lcdCommand(LCD_SETCGRAMADDR); lcdDataArray(NUMdata, 64); // go to first position lcdCommand(LCD_SETDDRAMADDR); #endif } // Display String ended by 0x00 void lcdString(const char text[]) { int i; for (i = 0; text[i] != 0; i++) { lcdData(text[i]); } } // Send byte array void lcdDataArray(const char data[], char length) { char i; for (i = 0; i < length; i++) { lcdData(data[i]); } } // Bitbang one LDE instruction/data void lcdSend(char data, char registerSelect) { char i; for (i = 0; i < 8; i++) { (data & BIT0) ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); data >>= 1; P1OUT |= LCD_C; P1OUT &= ~LCD_C; } registerSelect ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); P1OUT &= ~LCD_E; P1OUT |= LCD_E; } // Clear display and go to first position void lcdClear() { lcdCommand(LCD_CLEARDISPLAY); _delay_cycles(2000); } // Go to defined position void lcdJump(char row, char colum) { lcdCommand(LCD_SETDDRAMADDR + Line[row] + colum); }  
     




    http://www.youtube.com/watch?v=Fwz0Ui2cySw

    http://www.youtube.com/watch?v=YNLD608ZsAQ


    [id]){>[id]){>
  18. Like
    V0JT4 got a reaction from Rickta59 in Slot Cars Lap Counter/Timer   
    Here is my first project with LaunchPad - Slot Cars Lap Counter/Timer. I wanted to build the lap counter for quite a long time, originally from BCD counter ICs and 7 segment LED display. Some time passed and my friend told me about LaunchPad, it looked like a great opportunity to start with MCUs and make some old ideas reality.


    Main idea was to use character LCD instead of 7seg LED as it gives you much more space to display stuff and I had one lying (from old computer connected to LPT with LCDSmartie). I used great RobG's project and slightly modified it to my needs. As I usually build only two lane circuits, I used custom characters feature of HD44780 to display large digits.

    Car is detected by magnetic Reed switch (extracted from old keyboard) placed under each lane. I needed to use capacitor parallel to switch, otherwise many false interrupts were generated (one car triggering both lanes,...). TimerA is used to track time with interrupt generated 100 times per second. Long and short button press is distinguish by WDT in timer mode with accumulator variable and threshold. Buzzer is connected to TimerA output 1 in PWM mode.

    The construction was fairly simple. I used hot melt glue to attach Reed switch under each lane. You just have to be very careful bending the Reed switch contacts, it's very fragile piece of glass and mine are maybe 30 years old, extracted from computer keyboard.

    Software implements 3 modes, standard N lap race, time attack limited by N laps and free practice with no time or lap limit. You can see it in action in video posted below. It's possible to pause/resume race (short press) and restart race (long press). Buzzer signals start, end and resume of race. To change race mode just hit reset button. I wasn't able to fit in 2K limit by 200B, so I had to use MSP430G2553 I received from great TI sample program. I think without large digits it would easily fit MSP430G2211, maybe even in 4 lane modification.



    EDIT: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
    #include <msp430g2553.h> #include "lcd.h" //#define LANE_4 // uncomment to enable 4 lane mode #ifdef BIG_DIGITS // big digits enabled in lcd.h #undef LANE_4 // are incompatible with 4 lanes #endif // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #ifdef LANE_4 #define SENS2 BIT6 #define SENS3 BIT7 #endif #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s #ifdef LANE_4 // lane count adjustment #define LANES 4 static const u8 SENS[] = {SENS0, SENS1, SENS2, SENS3}; #else #define LANES 2 static const u8 SENS[] = {SENS0, SENS1}; #endif u8 hold = 0; // Long button press counter u8 lap[LANES]; // Lap number u16 time[LANES]; // Lap time in 1/100 s u16 best[LANES]; // Best Lap time u8 race; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_READY 0x30 #define STATE_START 0x40 #define STATE_PAUSE 0x50 #define STATE_RACE 0x60 #define STATE_FINISH 0x70 #define STATE_END 0x80 #define WIN1_MASK 0x03 // Race result bits u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else #ifdef LANE_4 static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', ' ', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', '0', '0', ' ', 'E', ' ', '0', '0', ' ', '0', '0', ' ', 'T', ' ', '-', '-', '.', '-', '-'}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', '.', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif /* LANE_4 */ #endif /* BIG_DIGITS */ void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 #ifdef LANE_4 P1OUT = SENS0 | SENS1 | SENS2 | SENS3; // pullup P1REN = SENS0 | SENS1 | SENS2 | SENS3; // internal resistor P1IES = BUTTON | SENS0 | SENS1 | SENS2 | SENS3; // Hi/lo edge #else P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge #endif P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer #ifdef LANE_4 P2DIR = BUZZER; // P2.6 in 4 lane mode P2SEL = BUZZER; // BUZZER TimeA output #else P1DIR = BUZZER; // P1.6 in 4 lane mode P1SEL = BUZZER; // BUZZER TimeA output #endif CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id; // print updated values for (id = 0; id < LANES; ++id) { if (P1IE & SENS[id]) { // hold after lap tostr(time[id]); // print time printT(id, 7); } if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print best printT(id, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id, 2); } } if (state == STATE_READY) { // draw init race screen lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); } if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting #ifdef LANE_4 P1IFG &= ~(SENS0 | SENS1 | SENS2 | SENS3); P1IE |= SENS0 | SENS1 | SENS2 | SENS3; #else P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; #endif } // race ended if ((state & ~WIN1_MASK) == STATE_FINISH) { #ifdef BIG_DIGITS lcdJump((state & WIN1_MASK) << 1, 1); // mark winner on LCD #else lcdJump(state & WIN1_MASK, 1); // mark winner on LCD #endif lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { u8 id; for (id = 0; id < LANES; ++id) { best[id] = 9999; lap[id] = 0; time[id] = 0; } #ifdef LANE_4 race = SENS0 | SENS1 | SENS2 | SENS3; #else race = SENS0 | SENS1; #endif } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif break; case STATE_READY: // start race case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdClear(); lcdString("Set number of laps:"); lcdJump(1, 11); lcdData('_'); #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(1, 11); lcdData(' '); lcdJump(1, 10); lcdData('_'); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_READY; raceLaps |= LAP_MARK; // for easier lap[id] comparison in newLap initData(); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif lcdJump(r, c); lcdDataArray(bufferT, 5); } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow lap[id] |= LAP_MARK; // mark for LCD update if (time[id] < best[id]) { // test best lap time best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (state >= STATE_FINISH) { race &= ~SENS[id]; // race mode looser finish } if (lap[id] == raceLaps) { race &= ~SENS[id]; if (mode == MODE_RACE && state == STATE_RACE) { state = STATE_FINISH | id; // race mode winner finish } else if (mode == MODE_TIME) { if (++state == (STATE_RACE + LANES)) { // time mode all finished u8 bestId = 0; for (id = 1; id < LANES; ++id) { // find best time if (best[id] < best[bestId]) bestId = id; } state = STATE_FINISH | bestId; } } } } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { u8 id; for (id = 0; id < LANES; ++id) { if (race & SENS[id]) { time[id]++; // increment time counter if (time[id] == MIN_LAP_TIME) { P1IFG &= ~SENS[id]; // SENS IFG cleared P1IE |= SENS[id]; // SENS interrupt enabled } else if (time[0] == 10000) time[id] = 0; // 4 digit overflow } } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 id; u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT } else { // SENS for (id = 0; id < LANES; ++id) { if (x & SENS[id]) { P1IE &= ~SENS[id]; // SENS interrupt disabled P1IFG &= ~SENS[id]; // SENS IFG cleared newLap(id); return; } } P1IFG = 0; } }
    Final version code:
    EDIT: Now with possibility to disable large digits (comment #define BIG_DIGITS in lcd.h) so it can fit in 2K FLASH devices.
    EDIT2: Deprecated, replaced by new 2/4 lane version
    #include <msp430g2553.h> #include "lcd.h" // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; u8 hold = 0; // Long button press counter u8 lap[2]; // Lap number u16 time[2]; // Lap time in 1/100 s u16 best[2]; // Best Lap time u8 race[2]; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_PAUSE 0x30 #define STATE_START 0x40 #define STATE_RACE 0x50 #define STATE_FINISH 0x60 #define STATE_END 0x70 #define WIN1_MARK 0x02 // Race result bit, set if car 1 wins u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer P1DIR = BUZZER; P1SEL = BUZZER; // BUZZER TimeA output CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id = 0; if (P1IE & SENS0) { // hold after lap tostr(time[0]); // print time printT(0, 7); } if (P1IE & SENS1) { // hold after lap tostr(time[1]); // print time printT(2, 7); } // print updated values REPEAT: if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print time printT(id << 1, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id << 1, 2); } id = !id; // to update both cars 0/1 if (id) goto REPEAT; if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; } // race ended if ((state & ~WIN1_MARK) == STATE_FINISH) { lcdJump(state & WIN1_MARK, 1); // mark winner in LCD lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } longDelay(); } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { best[0] = 9999; best[1] = 9999; lap[0] = 0; lap[1] = 0; time[0] = 0; time[1] = 0; race[0] = 1; race[1] = 1; } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD printL(1, 10); break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; P1IE &= ~(SENS0 | SENS1); break; case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode lcdClear(); if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdString("Set number of laps:"); printL(1, 10); lcdJump(3, 11); lcdData(0x2D); break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(3, 11); lcdData(' '); lcdJump(3, 10); lcdData(0x2D); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; P1IE &= ~(SENS0 | SENS1); case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_PAUSE; initData(); lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { lcdJump(r, c); lcdDataArray(bufferT, 5); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow if (time[id] best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (mode == MODE_RACE && (race[0] ^ race[1])) { race[id] = 0; // race mode looser finish } if (lap[id] == raceLaps) { race[id] = 0; if (mode == MODE_RACE && (race[0] ^ race[1])) { state = STATE_FINISH; // race mode winner finish if (id) state |= WIN1_MARK; } else if (mode == MODE_TIME && !(race[0] | race[1])) { state = STATE_FINISH; // time mode both finished if (best[0] > best[1]) { // time mode select winner state |= WIN1_MARK; } } } lap[id] |= LAP_MARK; // mark for LCD update } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { if (race[0]) { time[0]++; // increment time counter if (time[0] == MIN_LAP_TIME) { P1IFG &= ~SENS0; // SENS1 IFG cleared P1IE |= SENS0; // SENS1 interrupt enabled } else if (time[0] == 10000) time[0] = 0; // 4 digit overflow } if (race[1]) { time[1]++; // increment time counter if (time[1] == MIN_LAP_TIME) { P1IFG &= ~SENS1; // SENS2 IFG cleared P1IE |= SENS1; // SENS1 interrupt enabled } else if (time[1] == 10000) time[1] = 0; // 4 digit overflow } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT }// SENS1 else if (x & SENS0) { P1IE &= ~SENS0; // SENS1 interrupt disabled P1IFG &= ~SENS0; // SENS1 IFG cleared newLap(0); }// SENS2 else if (x & SENS1) { P1IE &= ~SENS1; // SENS2 interrupt disabled P1IFG &= ~SENS1; // SENS2 IFG cleared newLap(1); } else P1IFG = 0; }  


    Modified RobG's LCD display code with large digits:
    lcd.h
    // Pin allocation, modify to connected pins! #define LCD_D BIT2 #define LCD_C BIT1 #define LCD_E BIT0 #define BIG_DIGITS // uncomment to enable two line digits, takes ~100B #ifdef BIG_DIGITS // Dual Line digits 0-9 // Top part static const char numT[] = {0x07,0x00,0x01,0x01,0x06,0x03,0x03,0x01,0x05,0x07}; // Bottom part static const char numB[] = {0x06,0x00,0x04,0x02,0x00,0x02,0x05,0x00,0x05,0x02}; #endif void lcdInit(void); void lcdString(const char text[]); void lcdDataArray(const char data[], char length); void lcdClear(); void lcdJump(char row, char colum); void lcdSend(char data, char registerSelect); #define lcdData(data) lcdSend(data, 1) #define lcdCommand(command) lcdSend(command, 0) // commands #define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00  

    lcd.c
    #include "msp430g2553.h" #include "lcd.h" // Starts of lines for 4x20 LCD static const char Line[] = {0, 0x40, 20, 0x40 + 20}; #ifdef BIG_DIGITS // Definition of custom chars for dual line digits static const char NUMdata[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; #endif void lcdInit(void) { _delay_cycles(100000); // initialize pins P1OUT &= ~(LCD_C | LCD_D); P1OUT |= LCD_E; P1DIR |= LCD_E | LCD_C | LCD_D; // initialize display lcdCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS); lcdCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); lcdClear(); #ifdef BIG_DIGITS lcdCommand(LCD_ENTRYMODESET | LCD_ENTRYLEFT); // define 8 custom characters lcdCommand(LCD_SETCGRAMADDR); lcdDataArray(NUMdata, 64); // go to first position lcdCommand(LCD_SETDDRAMADDR); #endif } // Display String ended by 0x00 void lcdString(const char text[]) { int i; for (i = 0; text[i] != 0; i++) { lcdData(text[i]); } } // Send byte array void lcdDataArray(const char data[], char length) { char i; for (i = 0; i < length; i++) { lcdData(data[i]); } } // Bitbang one LDE instruction/data void lcdSend(char data, char registerSelect) { char i; for (i = 0; i < 8; i++) { (data & BIT0) ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); data >>= 1; P1OUT |= LCD_C; P1OUT &= ~LCD_C; } registerSelect ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); P1OUT &= ~LCD_E; P1OUT |= LCD_E; } // Clear display and go to first position void lcdClear() { lcdCommand(LCD_CLEARDISPLAY); _delay_cycles(2000); } // Go to defined position void lcdJump(char row, char colum) { lcdCommand(LCD_SETDDRAMADDR + Line[row] + colum); }  
     




    http://www.youtube.com/watch?v=Fwz0Ui2cySw

    http://www.youtube.com/watch?v=YNLD608ZsAQ


    [id]){>[id]){>
  19. Like
    V0JT4 got a reaction from bluehash in Slot Cars Lap Counter/Timer   
    Here is my first project with LaunchPad - Slot Cars Lap Counter/Timer. I wanted to build the lap counter for quite a long time, originally from BCD counter ICs and 7 segment LED display. Some time passed and my friend told me about LaunchPad, it looked like a great opportunity to start with MCUs and make some old ideas reality.


    Main idea was to use character LCD instead of 7seg LED as it gives you much more space to display stuff and I had one lying (from old computer connected to LPT with LCDSmartie). I used great RobG's project and slightly modified it to my needs. As I usually build only two lane circuits, I used custom characters feature of HD44780 to display large digits.

    Car is detected by magnetic Reed switch (extracted from old keyboard) placed under each lane. I needed to use capacitor parallel to switch, otherwise many false interrupts were generated (one car triggering both lanes,...). TimerA is used to track time with interrupt generated 100 times per second. Long and short button press is distinguish by WDT in timer mode with accumulator variable and threshold. Buzzer is connected to TimerA output 1 in PWM mode.

    The construction was fairly simple. I used hot melt glue to attach Reed switch under each lane. You just have to be very careful bending the Reed switch contacts, it's very fragile piece of glass and mine are maybe 30 years old, extracted from computer keyboard.

    Software implements 3 modes, standard N lap race, time attack limited by N laps and free practice with no time or lap limit. You can see it in action in video posted below. It's possible to pause/resume race (short press) and restart race (long press). Buzzer signals start, end and resume of race. To change race mode just hit reset button. I wasn't able to fit in 2K limit by 200B, so I had to use MSP430G2553 I received from great TI sample program. I think without large digits it would easily fit MSP430G2211, maybe even in 4 lane modification.



    EDIT: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
    #include <msp430g2553.h> #include "lcd.h" //#define LANE_4 // uncomment to enable 4 lane mode #ifdef BIG_DIGITS // big digits enabled in lcd.h #undef LANE_4 // are incompatible with 4 lanes #endif // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #ifdef LANE_4 #define SENS2 BIT6 #define SENS3 BIT7 #endif #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s #ifdef LANE_4 // lane count adjustment #define LANES 4 static const u8 SENS[] = {SENS0, SENS1, SENS2, SENS3}; #else #define LANES 2 static const u8 SENS[] = {SENS0, SENS1}; #endif u8 hold = 0; // Long button press counter u8 lap[LANES]; // Lap number u16 time[LANES]; // Lap time in 1/100 s u16 best[LANES]; // Best Lap time u8 race; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_READY 0x30 #define STATE_START 0x40 #define STATE_PAUSE 0x50 #define STATE_RACE 0x60 #define STATE_FINISH 0x70 #define STATE_END 0x80 #define WIN1_MASK 0x03 // Race result bits u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else #ifdef LANE_4 static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', ' ', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', '0', '0', ' ', 'E', ' ', '0', '0', ' ', '0', '0', ' ', 'T', ' ', '-', '-', '.', '-', '-'}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', '.', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif /* LANE_4 */ #endif /* BIG_DIGITS */ void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 #ifdef LANE_4 P1OUT = SENS0 | SENS1 | SENS2 | SENS3; // pullup P1REN = SENS0 | SENS1 | SENS2 | SENS3; // internal resistor P1IES = BUTTON | SENS0 | SENS1 | SENS2 | SENS3; // Hi/lo edge #else P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge #endif P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer #ifdef LANE_4 P2DIR = BUZZER; // P2.6 in 4 lane mode P2SEL = BUZZER; // BUZZER TimeA output #else P1DIR = BUZZER; // P1.6 in 4 lane mode P1SEL = BUZZER; // BUZZER TimeA output #endif CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id; // print updated values for (id = 0; id < LANES; ++id) { if (P1IE & SENS[id]) { // hold after lap tostr(time[id]); // print time printT(id, 7); } if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print best printT(id, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id, 2); } } if (state == STATE_READY) { // draw init race screen lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); } if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting #ifdef LANE_4 P1IFG &= ~(SENS0 | SENS1 | SENS2 | SENS3); P1IE |= SENS0 | SENS1 | SENS2 | SENS3; #else P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; #endif } // race ended if ((state & ~WIN1_MASK) == STATE_FINISH) { #ifdef BIG_DIGITS lcdJump((state & WIN1_MASK) << 1, 1); // mark winner on LCD #else lcdJump(state & WIN1_MASK, 1); // mark winner on LCD #endif lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { u8 id; for (id = 0; id < LANES; ++id) { best[id] = 9999; lap[id] = 0; time[id] = 0; } #ifdef LANE_4 race = SENS0 | SENS1 | SENS2 | SENS3; #else race = SENS0 | SENS1; #endif } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif break; case STATE_READY: // start race case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdClear(); lcdString("Set number of laps:"); lcdJump(1, 11); lcdData('_'); #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(1, 11); lcdData(' '); lcdJump(1, 10); lcdData('_'); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_READY; raceLaps |= LAP_MARK; // for easier lap[id] comparison in newLap initData(); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif lcdJump(r, c); lcdDataArray(bufferT, 5); } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow lap[id] |= LAP_MARK; // mark for LCD update if (time[id] < best[id]) { // test best lap time best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (state >= STATE_FINISH) { race &= ~SENS[id]; // race mode looser finish } if (lap[id] == raceLaps) { race &= ~SENS[id]; if (mode == MODE_RACE && state == STATE_RACE) { state = STATE_FINISH | id; // race mode winner finish } else if (mode == MODE_TIME) { if (++state == (STATE_RACE + LANES)) { // time mode all finished u8 bestId = 0; for (id = 1; id < LANES; ++id) { // find best time if (best[id] < best[bestId]) bestId = id; } state = STATE_FINISH | bestId; } } } } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { u8 id; for (id = 0; id < LANES; ++id) { if (race & SENS[id]) { time[id]++; // increment time counter if (time[id] == MIN_LAP_TIME) { P1IFG &= ~SENS[id]; // SENS IFG cleared P1IE |= SENS[id]; // SENS interrupt enabled } else if (time[0] == 10000) time[id] = 0; // 4 digit overflow } } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 id; u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT } else { // SENS for (id = 0; id < LANES; ++id) { if (x & SENS[id]) { P1IE &= ~SENS[id]; // SENS interrupt disabled P1IFG &= ~SENS[id]; // SENS IFG cleared newLap(id); return; } } P1IFG = 0; } }
    Final version code:
    EDIT: Now with possibility to disable large digits (comment #define BIG_DIGITS in lcd.h) so it can fit in 2K FLASH devices.
    EDIT2: Deprecated, replaced by new 2/4 lane version
    #include <msp430g2553.h> #include "lcd.h" // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; u8 hold = 0; // Long button press counter u8 lap[2]; // Lap number u16 time[2]; // Lap time in 1/100 s u16 best[2]; // Best Lap time u8 race[2]; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_PAUSE 0x30 #define STATE_START 0x40 #define STATE_RACE 0x50 #define STATE_FINISH 0x60 #define STATE_END 0x70 #define WIN1_MARK 0x02 // Race result bit, set if car 1 wins u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer P1DIR = BUZZER; P1SEL = BUZZER; // BUZZER TimeA output CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id = 0; if (P1IE & SENS0) { // hold after lap tostr(time[0]); // print time printT(0, 7); } if (P1IE & SENS1) { // hold after lap tostr(time[1]); // print time printT(2, 7); } // print updated values REPEAT: if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print time printT(id << 1, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id << 1, 2); } id = !id; // to update both cars 0/1 if (id) goto REPEAT; if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; } // race ended if ((state & ~WIN1_MARK) == STATE_FINISH) { lcdJump(state & WIN1_MARK, 1); // mark winner in LCD lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } longDelay(); } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { best[0] = 9999; best[1] = 9999; lap[0] = 0; lap[1] = 0; time[0] = 0; time[1] = 0; race[0] = 1; race[1] = 1; } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD printL(1, 10); break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; P1IE &= ~(SENS0 | SENS1); break; case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode lcdClear(); if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdString("Set number of laps:"); printL(1, 10); lcdJump(3, 11); lcdData(0x2D); break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(3, 11); lcdData(' '); lcdJump(3, 10); lcdData(0x2D); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; P1IE &= ~(SENS0 | SENS1); case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_PAUSE; initData(); lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { lcdJump(r, c); lcdDataArray(bufferT, 5); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow if (time[id] best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (mode == MODE_RACE && (race[0] ^ race[1])) { race[id] = 0; // race mode looser finish } if (lap[id] == raceLaps) { race[id] = 0; if (mode == MODE_RACE && (race[0] ^ race[1])) { state = STATE_FINISH; // race mode winner finish if (id) state |= WIN1_MARK; } else if (mode == MODE_TIME && !(race[0] | race[1])) { state = STATE_FINISH; // time mode both finished if (best[0] > best[1]) { // time mode select winner state |= WIN1_MARK; } } } lap[id] |= LAP_MARK; // mark for LCD update } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { if (race[0]) { time[0]++; // increment time counter if (time[0] == MIN_LAP_TIME) { P1IFG &= ~SENS0; // SENS1 IFG cleared P1IE |= SENS0; // SENS1 interrupt enabled } else if (time[0] == 10000) time[0] = 0; // 4 digit overflow } if (race[1]) { time[1]++; // increment time counter if (time[1] == MIN_LAP_TIME) { P1IFG &= ~SENS1; // SENS2 IFG cleared P1IE |= SENS1; // SENS1 interrupt enabled } else if (time[1] == 10000) time[1] = 0; // 4 digit overflow } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT }// SENS1 else if (x & SENS0) { P1IE &= ~SENS0; // SENS1 interrupt disabled P1IFG &= ~SENS0; // SENS1 IFG cleared newLap(0); }// SENS2 else if (x & SENS1) { P1IE &= ~SENS1; // SENS2 interrupt disabled P1IFG &= ~SENS1; // SENS2 IFG cleared newLap(1); } else P1IFG = 0; }  


    Modified RobG's LCD display code with large digits:
    lcd.h
    // Pin allocation, modify to connected pins! #define LCD_D BIT2 #define LCD_C BIT1 #define LCD_E BIT0 #define BIG_DIGITS // uncomment to enable two line digits, takes ~100B #ifdef BIG_DIGITS // Dual Line digits 0-9 // Top part static const char numT[] = {0x07,0x00,0x01,0x01,0x06,0x03,0x03,0x01,0x05,0x07}; // Bottom part static const char numB[] = {0x06,0x00,0x04,0x02,0x00,0x02,0x05,0x00,0x05,0x02}; #endif void lcdInit(void); void lcdString(const char text[]); void lcdDataArray(const char data[], char length); void lcdClear(); void lcdJump(char row, char colum); void lcdSend(char data, char registerSelect); #define lcdData(data) lcdSend(data, 1) #define lcdCommand(command) lcdSend(command, 0) // commands #define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00  

    lcd.c
    #include "msp430g2553.h" #include "lcd.h" // Starts of lines for 4x20 LCD static const char Line[] = {0, 0x40, 20, 0x40 + 20}; #ifdef BIG_DIGITS // Definition of custom chars for dual line digits static const char NUMdata[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; #endif void lcdInit(void) { _delay_cycles(100000); // initialize pins P1OUT &= ~(LCD_C | LCD_D); P1OUT |= LCD_E; P1DIR |= LCD_E | LCD_C | LCD_D; // initialize display lcdCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS); lcdCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); lcdClear(); #ifdef BIG_DIGITS lcdCommand(LCD_ENTRYMODESET | LCD_ENTRYLEFT); // define 8 custom characters lcdCommand(LCD_SETCGRAMADDR); lcdDataArray(NUMdata, 64); // go to first position lcdCommand(LCD_SETDDRAMADDR); #endif } // Display String ended by 0x00 void lcdString(const char text[]) { int i; for (i = 0; text[i] != 0; i++) { lcdData(text[i]); } } // Send byte array void lcdDataArray(const char data[], char length) { char i; for (i = 0; i < length; i++) { lcdData(data[i]); } } // Bitbang one LDE instruction/data void lcdSend(char data, char registerSelect) { char i; for (i = 0; i < 8; i++) { (data & BIT0) ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); data >>= 1; P1OUT |= LCD_C; P1OUT &= ~LCD_C; } registerSelect ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); P1OUT &= ~LCD_E; P1OUT |= LCD_E; } // Clear display and go to first position void lcdClear() { lcdCommand(LCD_CLEARDISPLAY); _delay_cycles(2000); } // Go to defined position void lcdJump(char row, char colum) { lcdCommand(LCD_SETDDRAMADDR + Line[row] + colum); }  
     




    http://www.youtube.com/watch?v=Fwz0Ui2cySw

    http://www.youtube.com/watch?v=YNLD608ZsAQ


    [id]){>[id]){>
  20. Like
    V0JT4 got a reaction from gwdeveloper in Slot Cars Lap Counter/Timer   
    Here is my first project with LaunchPad - Slot Cars Lap Counter/Timer. I wanted to build the lap counter for quite a long time, originally from BCD counter ICs and 7 segment LED display. Some time passed and my friend told me about LaunchPad, it looked like a great opportunity to start with MCUs and make some old ideas reality.


    Main idea was to use character LCD instead of 7seg LED as it gives you much more space to display stuff and I had one lying (from old computer connected to LPT with LCDSmartie). I used great RobG's project and slightly modified it to my needs. As I usually build only two lane circuits, I used custom characters feature of HD44780 to display large digits.

    Car is detected by magnetic Reed switch (extracted from old keyboard) placed under each lane. I needed to use capacitor parallel to switch, otherwise many false interrupts were generated (one car triggering both lanes,...). TimerA is used to track time with interrupt generated 100 times per second. Long and short button press is distinguish by WDT in timer mode with accumulator variable and threshold. Buzzer is connected to TimerA output 1 in PWM mode.

    The construction was fairly simple. I used hot melt glue to attach Reed switch under each lane. You just have to be very careful bending the Reed switch contacts, it's very fragile piece of glass and mine are maybe 30 years old, extracted from computer keyboard.

    Software implements 3 modes, standard N lap race, time attack limited by N laps and free practice with no time or lap limit. You can see it in action in video posted below. It's possible to pause/resume race (short press) and restart race (long press). Buzzer signals start, end and resume of race. To change race mode just hit reset button. I wasn't able to fit in 2K limit by 200B, so I had to use MSP430G2553 I received from great TI sample program. I think without large digits it would easily fit MSP430G2211, maybe even in 4 lane modification.



    EDIT: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
    #include <msp430g2553.h> #include "lcd.h" //#define LANE_4 // uncomment to enable 4 lane mode #ifdef BIG_DIGITS // big digits enabled in lcd.h #undef LANE_4 // are incompatible with 4 lanes #endif // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #ifdef LANE_4 #define SENS2 BIT6 #define SENS3 BIT7 #endif #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s #ifdef LANE_4 // lane count adjustment #define LANES 4 static const u8 SENS[] = {SENS0, SENS1, SENS2, SENS3}; #else #define LANES 2 static const u8 SENS[] = {SENS0, SENS1}; #endif u8 hold = 0; // Long button press counter u8 lap[LANES]; // Lap number u16 time[LANES]; // Lap time in 1/100 s u16 best[LANES]; // Best Lap time u8 race; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_READY 0x30 #define STATE_START 0x40 #define STATE_PAUSE 0x50 #define STATE_RACE 0x60 #define STATE_FINISH 0x70 #define STATE_END 0x80 #define WIN1_MASK 0x03 // Race result bits u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else #ifdef LANE_4 static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', ' ', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', '0', '0', ' ', 'E', ' ', '0', '0', ' ', '0', '0', ' ', 'T', ' ', '-', '-', '.', '-', '-'}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', '.', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif /* LANE_4 */ #endif /* BIG_DIGITS */ void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 #ifdef LANE_4 P1OUT = SENS0 | SENS1 | SENS2 | SENS3; // pullup P1REN = SENS0 | SENS1 | SENS2 | SENS3; // internal resistor P1IES = BUTTON | SENS0 | SENS1 | SENS2 | SENS3; // Hi/lo edge #else P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge #endif P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer #ifdef LANE_4 P2DIR = BUZZER; // P2.6 in 4 lane mode P2SEL = BUZZER; // BUZZER TimeA output #else P1DIR = BUZZER; // P1.6 in 4 lane mode P1SEL = BUZZER; // BUZZER TimeA output #endif CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id; // print updated values for (id = 0; id < LANES; ++id) { if (P1IE & SENS[id]) { // hold after lap tostr(time[id]); // print time printT(id, 7); } if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print best printT(id, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id, 2); } } if (state == STATE_READY) { // draw init race screen lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); } if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting #ifdef LANE_4 P1IFG &= ~(SENS0 | SENS1 | SENS2 | SENS3); P1IE |= SENS0 | SENS1 | SENS2 | SENS3; #else P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; #endif } // race ended if ((state & ~WIN1_MASK) == STATE_FINISH) { #ifdef BIG_DIGITS lcdJump((state & WIN1_MASK) << 1, 1); // mark winner on LCD #else lcdJump(state & WIN1_MASK, 1); // mark winner on LCD #endif lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { u8 id; for (id = 0; id < LANES; ++id) { best[id] = 9999; lap[id] = 0; time[id] = 0; } #ifdef LANE_4 race = SENS0 | SENS1 | SENS2 | SENS3; #else race = SENS0 | SENS1; #endif } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif break; case STATE_READY: // start race case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdClear(); lcdString("Set number of laps:"); lcdJump(1, 11); lcdData('_'); #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(1, 11); lcdData(' '); lcdJump(1, 10); lcdData('_'); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_READY; raceLaps |= LAP_MARK; // for easier lap[id] comparison in newLap initData(); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif lcdJump(r, c); lcdDataArray(bufferT, 5); } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow lap[id] |= LAP_MARK; // mark for LCD update if (time[id] < best[id]) { // test best lap time best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (state >= STATE_FINISH) { race &= ~SENS[id]; // race mode looser finish } if (lap[id] == raceLaps) { race &= ~SENS[id]; if (mode == MODE_RACE && state == STATE_RACE) { state = STATE_FINISH | id; // race mode winner finish } else if (mode == MODE_TIME) { if (++state == (STATE_RACE + LANES)) { // time mode all finished u8 bestId = 0; for (id = 1; id < LANES; ++id) { // find best time if (best[id] < best[bestId]) bestId = id; } state = STATE_FINISH | bestId; } } } } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { u8 id; for (id = 0; id < LANES; ++id) { if (race & SENS[id]) { time[id]++; // increment time counter if (time[id] == MIN_LAP_TIME) { P1IFG &= ~SENS[id]; // SENS IFG cleared P1IE |= SENS[id]; // SENS interrupt enabled } else if (time[0] == 10000) time[id] = 0; // 4 digit overflow } } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 id; u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT } else { // SENS for (id = 0; id < LANES; ++id) { if (x & SENS[id]) { P1IE &= ~SENS[id]; // SENS interrupt disabled P1IFG &= ~SENS[id]; // SENS IFG cleared newLap(id); return; } } P1IFG = 0; } }
    Final version code:
    EDIT: Now with possibility to disable large digits (comment #define BIG_DIGITS in lcd.h) so it can fit in 2K FLASH devices.
    EDIT2: Deprecated, replaced by new 2/4 lane version
    #include <msp430g2553.h> #include "lcd.h" // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; u8 hold = 0; // Long button press counter u8 lap[2]; // Lap number u16 time[2]; // Lap time in 1/100 s u16 best[2]; // Best Lap time u8 race[2]; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_PAUSE 0x30 #define STATE_START 0x40 #define STATE_RACE 0x50 #define STATE_FINISH 0x60 #define STATE_END 0x70 #define WIN1_MARK 0x02 // Race result bit, set if car 1 wins u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer P1DIR = BUZZER; P1SEL = BUZZER; // BUZZER TimeA output CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id = 0; if (P1IE & SENS0) { // hold after lap tostr(time[0]); // print time printT(0, 7); } if (P1IE & SENS1) { // hold after lap tostr(time[1]); // print time printT(2, 7); } // print updated values REPEAT: if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print time printT(id << 1, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id << 1, 2); } id = !id; // to update both cars 0/1 if (id) goto REPEAT; if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; } // race ended if ((state & ~WIN1_MARK) == STATE_FINISH) { lcdJump(state & WIN1_MARK, 1); // mark winner in LCD lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } longDelay(); } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { best[0] = 9999; best[1] = 9999; lap[0] = 0; lap[1] = 0; time[0] = 0; time[1] = 0; race[0] = 1; race[1] = 1; } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD printL(1, 10); break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; P1IE &= ~(SENS0 | SENS1); break; case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode lcdClear(); if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdString("Set number of laps:"); printL(1, 10); lcdJump(3, 11); lcdData(0x2D); break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(3, 11); lcdData(' '); lcdJump(3, 10); lcdData(0x2D); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; P1IE &= ~(SENS0 | SENS1); case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_PAUSE; initData(); lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { lcdJump(r, c); lcdDataArray(bufferT, 5); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow if (time[id] best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (mode == MODE_RACE && (race[0] ^ race[1])) { race[id] = 0; // race mode looser finish } if (lap[id] == raceLaps) { race[id] = 0; if (mode == MODE_RACE && (race[0] ^ race[1])) { state = STATE_FINISH; // race mode winner finish if (id) state |= WIN1_MARK; } else if (mode == MODE_TIME && !(race[0] | race[1])) { state = STATE_FINISH; // time mode both finished if (best[0] > best[1]) { // time mode select winner state |= WIN1_MARK; } } } lap[id] |= LAP_MARK; // mark for LCD update } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { if (race[0]) { time[0]++; // increment time counter if (time[0] == MIN_LAP_TIME) { P1IFG &= ~SENS0; // SENS1 IFG cleared P1IE |= SENS0; // SENS1 interrupt enabled } else if (time[0] == 10000) time[0] = 0; // 4 digit overflow } if (race[1]) { time[1]++; // increment time counter if (time[1] == MIN_LAP_TIME) { P1IFG &= ~SENS1; // SENS2 IFG cleared P1IE |= SENS1; // SENS1 interrupt enabled } else if (time[1] == 10000) time[1] = 0; // 4 digit overflow } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT }// SENS1 else if (x & SENS0) { P1IE &= ~SENS0; // SENS1 interrupt disabled P1IFG &= ~SENS0; // SENS1 IFG cleared newLap(0); }// SENS2 else if (x & SENS1) { P1IE &= ~SENS1; // SENS2 interrupt disabled P1IFG &= ~SENS1; // SENS2 IFG cleared newLap(1); } else P1IFG = 0; }  


    Modified RobG's LCD display code with large digits:
    lcd.h
    // Pin allocation, modify to connected pins! #define LCD_D BIT2 #define LCD_C BIT1 #define LCD_E BIT0 #define BIG_DIGITS // uncomment to enable two line digits, takes ~100B #ifdef BIG_DIGITS // Dual Line digits 0-9 // Top part static const char numT[] = {0x07,0x00,0x01,0x01,0x06,0x03,0x03,0x01,0x05,0x07}; // Bottom part static const char numB[] = {0x06,0x00,0x04,0x02,0x00,0x02,0x05,0x00,0x05,0x02}; #endif void lcdInit(void); void lcdString(const char text[]); void lcdDataArray(const char data[], char length); void lcdClear(); void lcdJump(char row, char colum); void lcdSend(char data, char registerSelect); #define lcdData(data) lcdSend(data, 1) #define lcdCommand(command) lcdSend(command, 0) // commands #define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00  

    lcd.c
    #include "msp430g2553.h" #include "lcd.h" // Starts of lines for 4x20 LCD static const char Line[] = {0, 0x40, 20, 0x40 + 20}; #ifdef BIG_DIGITS // Definition of custom chars for dual line digits static const char NUMdata[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; #endif void lcdInit(void) { _delay_cycles(100000); // initialize pins P1OUT &= ~(LCD_C | LCD_D); P1OUT |= LCD_E; P1DIR |= LCD_E | LCD_C | LCD_D; // initialize display lcdCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS); lcdCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); lcdClear(); #ifdef BIG_DIGITS lcdCommand(LCD_ENTRYMODESET | LCD_ENTRYLEFT); // define 8 custom characters lcdCommand(LCD_SETCGRAMADDR); lcdDataArray(NUMdata, 64); // go to first position lcdCommand(LCD_SETDDRAMADDR); #endif } // Display String ended by 0x00 void lcdString(const char text[]) { int i; for (i = 0; text[i] != 0; i++) { lcdData(text[i]); } } // Send byte array void lcdDataArray(const char data[], char length) { char i; for (i = 0; i < length; i++) { lcdData(data[i]); } } // Bitbang one LDE instruction/data void lcdSend(char data, char registerSelect) { char i; for (i = 0; i < 8; i++) { (data & BIT0) ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); data >>= 1; P1OUT |= LCD_C; P1OUT &= ~LCD_C; } registerSelect ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); P1OUT &= ~LCD_E; P1OUT |= LCD_E; } // Clear display and go to first position void lcdClear() { lcdCommand(LCD_CLEARDISPLAY); _delay_cycles(2000); } // Go to defined position void lcdJump(char row, char colum) { lcdCommand(LCD_SETDDRAMADDR + Line[row] + colum); }  
     




    http://www.youtube.com/watch?v=Fwz0Ui2cySw

    http://www.youtube.com/watch?v=YNLD608ZsAQ


    [id]){>[id]){>
  21. Like
    V0JT4 got a reaction from RobG in Slot Cars Lap Counter/Timer   
    Here is my first project with LaunchPad - Slot Cars Lap Counter/Timer. I wanted to build the lap counter for quite a long time, originally from BCD counter ICs and 7 segment LED display. Some time passed and my friend told me about LaunchPad, it looked like a great opportunity to start with MCUs and make some old ideas reality.


    Main idea was to use character LCD instead of 7seg LED as it gives you much more space to display stuff and I had one lying (from old computer connected to LPT with LCDSmartie). I used great RobG's project and slightly modified it to my needs. As I usually build only two lane circuits, I used custom characters feature of HD44780 to display large digits.

    Car is detected by magnetic Reed switch (extracted from old keyboard) placed under each lane. I needed to use capacitor parallel to switch, otherwise many false interrupts were generated (one car triggering both lanes,...). TimerA is used to track time with interrupt generated 100 times per second. Long and short button press is distinguish by WDT in timer mode with accumulator variable and threshold. Buzzer is connected to TimerA output 1 in PWM mode.

    The construction was fairly simple. I used hot melt glue to attach Reed switch under each lane. You just have to be very careful bending the Reed switch contacts, it's very fragile piece of glass and mine are maybe 30 years old, extracted from computer keyboard.

    Software implements 3 modes, standard N lap race, time attack limited by N laps and free practice with no time or lap limit. You can see it in action in video posted below. It's possible to pause/resume race (short press) and restart race (long press). Buzzer signals start, end and resume of race. To change race mode just hit reset button. I wasn't able to fit in 2K limit by 200B, so I had to use MSP430G2553 I received from great TI sample program. I think without large digits it would easily fit MSP430G2211, maybe even in 4 lane modification.



    EDIT: Good news everyone! I have a new code version, now with possibility to enable big digits or 4 lanes. Without big digits it fits in 2K FLASH devices supplied with LaunchPad in both 2 and 4 lanes modes. In 4 lanes mode buzzer needs to be placed on P2.6 (XIN).
    #include <msp430g2553.h> #include "lcd.h" //#define LANE_4 // uncomment to enable 4 lane mode #ifdef BIG_DIGITS // big digits enabled in lcd.h #undef LANE_4 // are incompatible with 4 lanes #endif // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #ifdef LANE_4 #define SENS2 BIT6 #define SENS3 BIT7 #endif #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s #ifdef LANE_4 // lane count adjustment #define LANES 4 static const u8 SENS[] = {SENS0, SENS1, SENS2, SENS3}; #else #define LANES 2 static const u8 SENS[] = {SENS0, SENS1}; #endif u8 hold = 0; // Long button press counter u8 lap[LANES]; // Lap number u16 time[LANES]; // Lap time in 1/100 s u16 best[LANES]; // Best Lap time u8 race; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_READY 0x30 #define STATE_START 0x40 #define STATE_PAUSE 0x50 #define STATE_RACE 0x60 #define STATE_FINISH 0x70 #define STATE_END 0x80 #define WIN1_MASK 0x03 // Race result bits u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else #ifdef LANE_4 static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', ' ', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', '0', '0', ' ', 'E', ' ', '0', '0', ' ', '0', '0', ' ', 'T', ' ', '-', '-', '.', '-', '-'}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', '0', '0', ' ', 'I', ' ', '0', '0', '.', '0', '0', ' ', 'E', ' ', '-', '-', '.', '-', '-', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif /* LANE_4 */ #endif /* BIG_DIGITS */ void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 #ifdef LANE_4 P1OUT = SENS0 | SENS1 | SENS2 | SENS3; // pullup P1REN = SENS0 | SENS1 | SENS2 | SENS3; // internal resistor P1IES = BUTTON | SENS0 | SENS1 | SENS2 | SENS3; // Hi/lo edge #else P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge #endif P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer #ifdef LANE_4 P2DIR = BUZZER; // P2.6 in 4 lane mode P2SEL = BUZZER; // BUZZER TimeA output #else P1DIR = BUZZER; // P1.6 in 4 lane mode P1SEL = BUZZER; // BUZZER TimeA output #endif CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id; // print updated values for (id = 0; id < LANES; ++id) { if (P1IE & SENS[id]) { // hold after lap tostr(time[id]); // print time printT(id, 7); } if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print best printT(id, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id, 2); } } if (state == STATE_READY) { // draw init race screen lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); } if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting #ifdef LANE_4 P1IFG &= ~(SENS0 | SENS1 | SENS2 | SENS3); P1IE |= SENS0 | SENS1 | SENS2 | SENS3; #else P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; #endif } // race ended if ((state & ~WIN1_MASK) == STATE_FINISH) { #ifdef BIG_DIGITS lcdJump((state & WIN1_MASK) << 1, 1); // mark winner on LCD #else lcdJump(state & WIN1_MASK, 1); // mark winner on LCD #endif lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { u8 id; for (id = 0; id < LANES; ++id) { best[id] = 9999; lap[id] = 0; time[id] = 0; } #ifdef LANE_4 race = SENS0 | SENS1 | SENS2 | SENS3; #else race = SENS0 | SENS1; #endif } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif break; case STATE_READY: // start race case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdClear(); lcdString("Set number of laps:"); lcdJump(1, 11); lcdData('_'); #ifdef BIG_DIGITS printL(1, 10); #else printL(2, 10); #endif break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(1, 11); lcdData(' '); lcdJump(1, 10); lcdData('_'); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; #ifdef LANE_4 P1IE &= ~(SENS0 | SENS1 | SENS2 | SENS3); #else P1IE &= ~(SENS0 | SENS1); #endif case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_READY; raceLaps |= LAP_MARK; // for easier lap[id] comparison in newLap initData(); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { #ifdef BIG_DIGITS r <<= 1; lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif lcdJump(r, c); lcdDataArray(bufferT, 5); } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow lap[id] |= LAP_MARK; // mark for LCD update if (time[id] < best[id]) { // test best lap time best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (state >= STATE_FINISH) { race &= ~SENS[id]; // race mode looser finish } if (lap[id] == raceLaps) { race &= ~SENS[id]; if (mode == MODE_RACE && state == STATE_RACE) { state = STATE_FINISH | id; // race mode winner finish } else if (mode == MODE_TIME) { if (++state == (STATE_RACE + LANES)) { // time mode all finished u8 bestId = 0; for (id = 1; id < LANES; ++id) { // find best time if (best[id] < best[bestId]) bestId = id; } state = STATE_FINISH | bestId; } } } } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { u8 id; for (id = 0; id < LANES; ++id) { if (race & SENS[id]) { time[id]++; // increment time counter if (time[id] == MIN_LAP_TIME) { P1IFG &= ~SENS[id]; // SENS IFG cleared P1IE |= SENS[id]; // SENS interrupt enabled } else if (time[0] == 10000) time[id] = 0; // 4 digit overflow } } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 id; u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT } else { // SENS for (id = 0; id < LANES; ++id) { if (x & SENS[id]) { P1IE &= ~SENS[id]; // SENS interrupt disabled P1IFG &= ~SENS[id]; // SENS IFG cleared newLap(id); return; } } P1IFG = 0; } }
    Final version code:
    EDIT: Now with possibility to disable large digits (comment #define BIG_DIGITS in lcd.h) so it can fit in 2K FLASH devices.
    EDIT2: Deprecated, replaced by new 2/4 lane version
    #include <msp430g2553.h> #include "lcd.h" // Pin allocation #define BUTTON BIT3 #define SENS0 BIT4 #define SENS1 BIT5 #define BUZZER BIT6 #define LONG_PRESS 10 // N x 30 ms #define MIN_LAP_TIME 100 // N x 1/100 s // Type definition for simplicity typedef unsigned char u8; typedef char s8; typedef unsigned int u16; typedef int s16; u8 hold = 0; // Long button press counter u8 lap[2]; // Lap number u16 time[2]; // Lap time in 1/100 s u16 best[2]; // Best Lap time u8 race[2]; // Car did not finish race u8 raceLaps = 0; // Number of laps of race #ifdef BIG_DIGITS u8 bufferT[] = {0, 0, ' ', 0x07, 0x07}; // Number to display top part u8 bufferB[] = {0, 0, '.', 0x06, 0x06}; // Number to display bottom part #else u8 bufferT[] = {0, 0, '.', '0', '0'}; // Number to display #endif #define BEST_MARK 0x8000 // best updated flag, needs redraw #define LAP_MARK 0x80 // lap updated flag, needs redraw // Application states #define STATE_MAIN 0x00 #define STATE_SETUP1 0x10 #define STATE_SETUP2 0x20 #define STATE_PAUSE 0x30 #define STATE_START 0x40 #define STATE_RACE 0x50 #define STATE_FINISH 0x60 #define STATE_END 0x70 #define WIN1_MARK 0x02 // Race result bit, set if car 1 wins u8 state = STATE_MAIN; // Race modes #define MODE_RACE 0x01 // first to finish wins #define MODE_TIME 0x02 // best lap time wins #define MODE_PRAC 0x03 // infinite practice with best time tracking u8 mode = MODE_RACE; // Race initial screen #ifdef BIG_DIGITS static const char CScreen[] = {'L', ' ', 0x07, 0x07, ' ', 'T', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'B', ' ', '_', '_', ' ', '_', '_', 'P', ' ', 0x07, 0x07, ' ', 'M', ' ', 0x07, 0x07, ' ', 0x07, 0x07, ' ', 'S', ' ', '_', '_', ' ', '_', '_', 'A', ' ', 0x06, 0x06, ' ', 'I', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'E', ' ', ' ', ' ', '.', ' ', ' ', 'S', ' ', 0x06, 0x06, ' ', 'E', ' ', 0x06, 0x06, '.', 0x06, 0x06, ' ', 'T', ' ', ' ', ' ', '.', ' ', ' '}; #else static const char CScreen[] = {'L', ' ', '0', '0', ' ', 'T', ' ', '0', '0', '.', '0', '0', ' ', 'B', ' ', '-', '-', '.', '-', '-', 'P', ' ', '0', '0', ' ', 'M', ' ', '0', '0', '.', '0', '0', ' ', 'S', ' ', '-', '-', '.', '-', '-', 'A', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', 'S', ' ', ' ', ' ', ' ', 'E', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'T', ' ', ' ', ' ', ' ', ' ', ' '}; #endif void longDelay(void); void initData(void); void buttonShort(void); void buttonLong(void); void tostr(u16 i); void printL(u8 r, u8 c); void printT(u8 r, u8 c); void newLap(u8 id); // handles all race LCD updates to avoid char placement errors void main(void) { // try to set DCO to 1 MHz if (CALBC1_1MHZ != 0xFF || CALDCO_1MHZ != 0xFF) { BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation } // initialize peripheries // watchdog WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT IE1 = WDTIE; // WDT IE // gpio P1 P1OUT = SENS0 | SENS1; // pullup P1REN = SENS0 | SENS1; // internal resistor P1IES = BUTTON | SENS0 | SENS1; // Hi/lo edge P1IFG = 0; // IFG cleared P1IE = BUTTON; // BUTTON interrupt enabled // timerA CCR0 = 9994 - 1; // 1/100s, DCO 1MHz calibration = 244x4096 TACTL = TASSEL_2 + MC_1; // SMCLK, upmode //buzzer P1DIR = BUZZER; P1SEL = BUZZER; // BUZZER TimeA output CCTL1 = OUTMOD_7; // CCR1 reset/set CCR1 = 0; // Buzzer off // lcd lcdInit(); _enable_interrupts(); // global interupt enable // Setup initial screen lcdString("Select Mode: Time Attack ~ Lap Race Practice"); initData(); while (1) { u8 id = 0; if (P1IE & SENS0) { // hold after lap tostr(time[0]); // print time printT(0, 7); } if (P1IE & SENS1) { // hold after lap tostr(time[1]); // print time printT(2, 7); } // print updated values REPEAT: if (best[id] & BEST_MARK) { best[id] &= ~BEST_MARK; tostr(best[id]); // print time printT(id << 1, 15); } if (lap[id] & LAP_MARK) { lap[id] &= ~LAP_MARK; tostr(lap[id]); // print lap count printL(id << 1, 2); } id = !id; // to update both cars 0/1 if (id) goto REPEAT; if (state == STATE_START) { // Sound countdown CCR0 = 4000; // higher frequency for (id = 8; id > 1; id--) { // 3 short+1 long CCR1 ^= 2000; longDelay(); if (id & 1)longDelay(); } longDelay(); CCR1 = 0; CCR0 = 9994 - 1; // resume 1/100s state = STATE_RACE; CCTL0 = CCIE; // start time counting P1IFG &= ~(SENS0 | SENS1); P1IE |= SENS0 | SENS1; } // race ended if ((state & ~WIN1_MARK) == STATE_FINISH) { lcdJump(state & WIN1_MARK, 1); // mark winner in LCD lcdData(0xFF); CCR1 = 4997; // short beep longDelay(); CCR1 = 0; state = STATE_END; } longDelay(); } } // Delay ~0.2s void longDelay(void) { _delay_cycles(190000); } // Set initial values void initData(void) { best[0] = 9999; best[1] = 9999; lap[0] = 0; lap[1] = 0; time[0] = 0; time[1] = 0; race[0] = 1; race[1] = 1; } // Short button press void buttonShort(void) { switch (state) { case STATE_MAIN: lcdJump(mode, 0); lcdData(' '); switch (mode) { // change race mode case MODE_RACE: mode = MODE_TIME; break; case MODE_TIME: mode = MODE_PRAC; break; case MODE_PRAC: mode = MODE_RACE; break; default: break; } lcdJump(mode, 0); lcdData(0x7E); break; case STATE_SETUP1: raceLaps++; // increase 1st digit if (raceLaps == 10) // overflow raceLaps = 0; LPRINT: tostr(raceLaps); // update LCD printL(1, 10); break; case STATE_SETUP2: raceLaps += 10; // increase 2nd digit if (raceLaps >= 100) // overflow raceLaps -= 100; goto LPRINT; //break; case STATE_RACE: // pause race state = STATE_PAUSE; CCTL0 = 0; P1IE &= ~(SENS0 | SENS1); break; case STATE_PAUSE: // resume race state = STATE_START; break; default: break; } } // Long button press void buttonLong() { switch (state) { case STATE_MAIN: // confirm race mode lcdClear(); if (mode == MODE_PRAC) { // start practice raceLaps = 127; goto CPRINT; } state = STATE_SETUP1; lcdString("Set number of laps:"); printL(1, 10); lcdJump(3, 11); lcdData(0x2D); break; case STATE_SETUP1: // confirm 1st digit state = STATE_SETUP2; lcdJump(3, 11); lcdData(' '); lcdJump(3, 10); lcdData(0x2D); break; case STATE_PAUSE: // restart race case STATE_RACE: case STATE_END: CCTL0 &= ~CCIE; P1IE &= ~(SENS0 | SENS1); case STATE_SETUP2: // confirm 2nd digit and prepare race CPRINT : state = STATE_PAUSE; initData(); lcdCommand(LCD_SETDDRAMADDR); lcdDataArray(CScreen, 80); break; default: break; } } // Print 2 digit number (lap count) void printL(u8 r, u8 c) { lcdJump(r, c); lcdData(bufferT[3]); lcdData(bufferT[4]); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdData(bufferB[3]); lcdData(bufferB[4]); #endif } // Print 2.2 digit number (time/best) void printT(u8 r, u8 c) { lcdJump(r, c); lcdDataArray(bufferT, 5); #ifdef BIG_DIGITS lcdJump(r + 1, c); lcdDataArray(bufferB, 5); #endif } // Convert Interger to 2.2 or 2 digit 2 line size String void tostr(u16 i) { bufferT[4] = i % 10; i /= 10; bufferT[3] = i % 10; i /= 10; bufferT[1] = i % 10; i /= 10; bufferT[0] = i % 10; #ifdef BIG_DIGITS bufferB[4] = numB[bufferT[4]]; bufferT[4] = numT[bufferT[4]]; bufferB[3] = numB[bufferT[3]]; bufferT[3] = numT[bufferT[3]]; bufferB[1] = numB[bufferT[1]]; bufferT[1] = numT[bufferT[1]]; bufferB[0] = numB[bufferT[0]]; bufferT[0] = numT[bufferT[0]]; #else bufferT[4] += '0'; bufferT[3] += '0'; bufferT[1] += '0'; bufferT[0] += '0'; #endif } // New lap service routine void newLap(u8 id) { lap[id]++; // increment lap counter if (lap[id] == 100) lap[id] = 0; // 2 digit overflow if (time[id] best[id] = time[id] | BEST_MARK; // mark for LCD update } time[id] = 0; // reset time counter if (mode == MODE_RACE && (race[0] ^ race[1])) { race[id] = 0; // race mode looser finish } if (lap[id] == raceLaps) { race[id] = 0; if (mode == MODE_RACE && (race[0] ^ race[1])) { state = STATE_FINISH; // race mode winner finish if (id) state |= WIN1_MARK; } else if (mode == MODE_TIME && !(race[0] | race[1])) { state = STATE_FINISH; // time mode both finished if (best[0] > best[1]) { // time mode select winner state |= WIN1_MARK; } } } lap[id] |= LAP_MARK; // mark for LCD update } // Timer A0 interrupt service routine #ifdef TIMER0_A0_VECTOR #pragma vector=TIMER0_A0_VECTOR #else #pragma vector=TIMERA0_VECTOR #endif __interrupt void Timer_A(void) { if (race[0]) { time[0]++; // increment time counter if (time[0] == MIN_LAP_TIME) { P1IFG &= ~SENS0; // SENS1 IFG cleared P1IE |= SENS0; // SENS1 interrupt enabled } else if (time[0] == 10000) time[0] = 0; // 4 digit overflow } if (race[1]) { time[1]++; // increment time counter if (time[1] == MIN_LAP_TIME) { P1IFG &= ~SENS1; // SENS2 IFG cleared P1IE |= SENS1; // SENS1 interrupt enabled } else if (time[1] == 10000) time[1] = 0; // 4 digit overflow } } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if (hold == LONG_PRESS) { buttonLong(); } else if (P1IN & BUTTON) { buttonShort(); } else { // Button still pressed hold++; return; } P1IE |= BUTTON; // BUTTON interrupt enabled WDTCTL = WDTPW | WDTHOLD | WDTTMSEL | WDTCNTCL; // Stop+Clear+TimerMode WDT hold = 0; } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { u8 x = P1IFG & P1IE; // Mask enabled intrrupt flags // BUTTON if (x & BUTTON) { P1IE &= ~BUTTON; // BUTTON interrupt disabled P1IFG &= ~BUTTON; // BUTTON IFG cleared WDTCTL = WDTPW | WDTTMSEL; // Start+TimerMode WDT }// SENS1 else if (x & SENS0) { P1IE &= ~SENS0; // SENS1 interrupt disabled P1IFG &= ~SENS0; // SENS1 IFG cleared newLap(0); }// SENS2 else if (x & SENS1) { P1IE &= ~SENS1; // SENS2 interrupt disabled P1IFG &= ~SENS1; // SENS2 IFG cleared newLap(1); } else P1IFG = 0; }  


    Modified RobG's LCD display code with large digits:
    lcd.h
    // Pin allocation, modify to connected pins! #define LCD_D BIT2 #define LCD_C BIT1 #define LCD_E BIT0 #define BIG_DIGITS // uncomment to enable two line digits, takes ~100B #ifdef BIG_DIGITS // Dual Line digits 0-9 // Top part static const char numT[] = {0x07,0x00,0x01,0x01,0x06,0x03,0x03,0x01,0x05,0x07}; // Bottom part static const char numB[] = {0x06,0x00,0x04,0x02,0x00,0x02,0x05,0x00,0x05,0x02}; #endif void lcdInit(void); void lcdString(const char text[]); void lcdDataArray(const char data[], char length); void lcdClear(); void lcdJump(char row, char colum); void lcdSend(char data, char registerSelect); #define lcdData(data) lcdSend(data, 1) #define lcdCommand(command) lcdSend(command, 0) // commands #define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00  

    lcd.c
    #include "msp430g2553.h" #include "lcd.h" // Starts of lines for 4x20 LCD static const char Line[] = {0, 0x40, 20, 0x40 + 20}; #ifdef BIG_DIGITS // Definition of custom chars for dual line digits static const char NUMdata[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; #endif void lcdInit(void) { _delay_cycles(100000); // initialize pins P1OUT &= ~(LCD_C | LCD_D); P1OUT |= LCD_E; P1DIR |= LCD_E | LCD_C | LCD_D; // initialize display lcdCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS); lcdCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); lcdClear(); #ifdef BIG_DIGITS lcdCommand(LCD_ENTRYMODESET | LCD_ENTRYLEFT); // define 8 custom characters lcdCommand(LCD_SETCGRAMADDR); lcdDataArray(NUMdata, 64); // go to first position lcdCommand(LCD_SETDDRAMADDR); #endif } // Display String ended by 0x00 void lcdString(const char text[]) { int i; for (i = 0; text[i] != 0; i++) { lcdData(text[i]); } } // Send byte array void lcdDataArray(const char data[], char length) { char i; for (i = 0; i < length; i++) { lcdData(data[i]); } } // Bitbang one LDE instruction/data void lcdSend(char data, char registerSelect) { char i; for (i = 0; i < 8; i++) { (data & BIT0) ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); data >>= 1; P1OUT |= LCD_C; P1OUT &= ~LCD_C; } registerSelect ? (P1OUT |= LCD_D) : (P1OUT &= ~LCD_D); P1OUT &= ~LCD_E; P1OUT |= LCD_E; } // Clear display and go to first position void lcdClear() { lcdCommand(LCD_CLEARDISPLAY); _delay_cycles(2000); } // Go to defined position void lcdJump(char row, char colum) { lcdCommand(LCD_SETDDRAMADDR + Line[row] + colum); }  
     




    http://www.youtube.com/watch?v=Fwz0Ui2cySw

    http://www.youtube.com/watch?v=YNLD608ZsAQ


    [id]){>[id]){>
  22. Like
    V0JT4 reacted to RobG in Using 3 wires to control parallel LCD display   
    I have seen this done other ways, but I didn't want to deal with 7 pins, or 4 bits, etc., so I did it my way.
    You send 8 bits to shift register, then set data out to whatever RS should be, and finally pulse E, that simple.
    I will add my code later, once I clean it up.
    BTW, if you are interested, the display is LMB162 and was purchased from ebay for $7.99 with free S/H to US of A (it took a week to get to NC.)
    It requires 3.3V Vcc with 5V BL, that's why I like it (well, I don't like the fact that I need separate power for BL.)
     


     

     
    My next step is to reduce number of required pins to 2 by adding missing pulse detector (555 I think will do well here.)

×
×
  • Create New...