]> git.tdb.fi Git - model-railway-devices.git/commitdiff
Support multiple timers (currently 0 and 1)
authorMikko Rasa <tdb@tdb.fi>
Wed, 27 Oct 2010 16:14:17 +0000 (16:14 +0000)
committerMikko Rasa <tdb@tdb.fi>
Wed, 27 Oct 2010 16:14:17 +0000 (16:14 +0000)
Provide functions for setting timers in µs as well as Hz
Rewrite interrupt callbacks as macros that directly create the ISR
Add an async mode for LCD
Fix DDRB in s88w-t

12 files changed:
firmware/Makefile
firmware/delay.h [new file with mode: 0644]
firmware/eeprom.c
firmware/eeprom.h
firmware/lcd.c
firmware/lcd.h
firmware/s88w-r.c
firmware/s88w-t.c
firmware/serial.c
firmware/serial.h
firmware/timer.c
firmware/timer.h

index c17d9919ecf938b8bf4608768ae5dbeddcf7fc3a..3f4f4c6c9e637c36c7cee4b7a0ac39c3ffac5201 100644 (file)
@@ -3,7 +3,8 @@
 MCU := atmega328p
 CLOCK := 16000000
 CC := avr-gcc
-CFLAGS := -Wall -Os -ffunction-sections -fdata-sections -mmcu=$(MCU) -DF_CPU=$(CLOCK)
+FEATURES := LCD_SHIFTREG LCD_ASYNC
+CFLAGS := -Wall -Os -ffunction-sections -fdata-sections -mmcu=$(MCU) -DF_CPU=$(CLOCK) $(patsubst %,-D%,$(FEATURES))
 LDFLAGS := -Os -Wl,--gc-sections -mmcu=$(MCU)
 AVRDUDE := avrdude
 OBJCOPY := avr-objcopy
@@ -42,4 +43,4 @@ clean:
        $(RM) *.o
 
 s88w-t.elf: serial.o timer.o eeprom.o
-s88w-r.elf: serial.o lcd.o
+s88w-r.elf: serial.o lcd.o timer.o
diff --git a/firmware/delay.h b/firmware/delay.h
new file mode 100644 (file)
index 0000000..1798cec
--- /dev/null
@@ -0,0 +1,52 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#ifndef DELAY_H_
+#define DELAY_H_
+
+static inline void __attribute__((always_inline)) delay_loop8(uint8_t count)
+{
+       __asm__ volatile (
+               "1: dec %0" "\n\t"
+               "brne 1b"
+               : "=r" (count)
+               : "0" (count)
+       );
+}
+
+static inline void __attribute__((always_inline)) delay_loop16(uint16_t count)
+{
+       __asm__ volatile (
+               "1: sbiw %0, 1" "\n\t"
+               "brne 1b"
+               : "=r" (count)
+               : "0" (count)
+       );
+}
+
+static inline void __attribute__((always_inline)) delay_us(uint16_t us)
+{
+       uint16_t clocks = F_CPU/1000000*us;
+       if(clocks<768)
+               delay_loop8(clocks/3);
+       else
+               delay_loop16(clocks/4);
+}
+
+static inline void __attribute__((always_inline)) delay_ms(uint16_t ms)
+{
+       if(ms<0x40000/(F_CPU/1000))
+               delay_loop16(F_CPU/1000*ms/4);
+       else
+       {
+               uint16_t i = ms*10;
+               while(--i)
+                       delay_loop16(F_CPU/40000);
+       }
+}
+
+#endif
index 9027ed02fbf2809f35136b5bbd5dfc278dd8371e..b9010f0f275d7dd5b1ccf0d67d12a000d3dbd426 100644 (file)
@@ -1,3 +1,10 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #include <avr/io.h>
 #include "eeprom.h"
 
index 903ce960b703eb7ed595c13a707a4a8dfb5f9b49..97527d67e5dacda89915be9b70e93b1329c890d4 100644 (file)
@@ -1,3 +1,10 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #ifndef EEPROM_H_
 #define EEPROM_H_
 
index 8c722d8d29e7e9150d83c76ee9eb63f04e22fb2a..6ba00d54a913c01ba6f2aac24a05f708a40c2a7e 100644 (file)
@@ -1,27 +1,70 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+
+ATMega pinout (with LCD_SHIFTREG):
+B0 - HD44780 RS
+B1 - HD44780 clock
+B2 - shift reg data
+B3 - shift reg clock
+
+ATMega pinout (without LCD_SHIFTREG):
+D4 - HD44780 RS
+D5 - HD44780 clock
+D6 - HD44780 data 0
+D7 - HD44780 data 1
+B0 - HD44780 data 2
+B1 - HD44780 data 3
+B2 - HD44780 data 4
+B3 - HD44780 data 5
+B4 - HD44780 data 6
+B5 - HD44780 data 7
+
+In both cases, HD44780 R/W needs to be connected to ground.
+*/
 #include <avr/io.h>
-#include <util/delay.h>
+#include "delay.h"
+#include "lcd.h"
+#include "timer.h"
+
+#ifdef LCD_SHIFTREG
+#define CLOCK_PORT PORTB
+#define CLOCK_BIT 0x02
+#define REGSEL_PORT PORTB
+#define REGSEL_BIT 0x01
+#else
+#define CLOCK_PORT PORTD
+#define CLOCK_BIT 0x20
+#define REGSEL_PORT PORTD
+#define REGSEL_BIT 0x10
+#endif
 
-#define USE_SHIFTREG 1
+#ifndef LCD_BUFSIZE
+#define LCD_BUFSIZE 32
+#endif
 
 #define NOP() __asm__("nop")
 
-void lcd_init(void);
-void lcd_on(void);
-void lcd_clear(void);
-void lcd_goto(uint8_t);
-void lcd_write(uint8_t);
+#ifdef LCD_ASYNC
+static volatile uint8_t lcd_busy = 0;
+static uint8_t lcd_buffer[LCD_BUFSIZE];
+static volatile uint8_t lcd_buf_head = 0;
+static volatile uint8_t lcd_buf_tail = 0;
+#endif
 
-void lcd_clock(void)
+static void lcd_clock(void)
 {
-       _delay_us(1);
-       PORTB |= 0x02;
-       _delay_us(1);
-       PORTB &= ~0x02;
+       delay_us(1);
+       CLOCK_PORT |= CLOCK_BIT;
+       delay_us(1);
+       CLOCK_PORT &= ~CLOCK_BIT;
 }
 
-void lcd_set_data(uint8_t d)
+static void lcd_set_data(uint8_t d)
 {
-#ifdef USE_SHIFTREG
+#ifdef LCD_SHIFTREG
        uint8_t i;
        for(i=0; i<8; ++i)
        {
@@ -37,32 +80,100 @@ void lcd_set_data(uint8_t d)
 #endif
 }
 
-void lcd_command(uint8_t c)
+static void lcd_command(uint8_t c)
 {
-       PORTB &= ~0x01;
+       REGSEL_PORT &= ~REGSEL_BIT;
        lcd_set_data(c);
        lcd_clock();
 }
 
+static void lcd_delay(uint16_t us)
+{
+#ifdef LCD_ASYNC
+       lcd_busy = 1;
+       timer_start_us(0, us);
+#else
+       delay_us(us);
+#endif
+}
+
+#ifdef LCD_ASYNC
+static void lcd_wait(void)
+{
+       while(lcd_busy) ;
+}
+
+static void lcd_buffer_push(uint8_t byte)
+{
+       while((lcd_buf_head+1)%sizeof(lcd_buffer)==lcd_buf_tail) ;
+       lcd_buffer[lcd_buf_head] = byte;
+       lcd_buf_head = (lcd_buf_head+1)%sizeof(lcd_buffer);
+}
+
+static uint8_t lcd_buffer_pop(void)
+{
+       uint8_t byte = lcd_buffer[lcd_buf_tail];
+       lcd_buf_tail = (lcd_buf_tail+1)%sizeof(lcd_buffer);
+       return byte;
+}
+
+static void lcd_timer(void)
+{
+       if(lcd_busy)
+       {
+               if(lcd_buf_head!=lcd_buf_tail)
+               {
+                       uint8_t byte = lcd_buffer_pop();
+                       if(byte==0xFF)
+                       {
+                               REGSEL_PORT |= REGSEL_BIT;
+                               byte = lcd_buffer_pop();
+                       }
+                       else if(byte>=0x80)
+                               REGSEL_PORT &= ~REGSEL_BIT;
+                       else
+                               REGSEL_PORT |= REGSEL_BIT;
+                       lcd_set_data(byte);
+                       lcd_clock();
+                       lcd_delay(50);
+               }
+               else
+               {
+                       lcd_busy = 0;
+                       timer_stop(0);
+               }
+       }
+}
+
+TIMER_SET_CALLBACK(0, lcd_timer)
+#endif
+
 void lcd_init(void)
 {
+#ifdef LCD_SHIFTREG
        DDRB |= 0x0F;
        PORTB &= ~0x0F;
-       _delay_ms(20);
-       lcd_command(0x38);
-       _delay_us(50);
-}
+#else
+       DDRD |= 0xF0;
+       PORTD &= ~0xF0;
+       DDRB |= 0x3F;
+       PORTB &= ~0x3F;
+#endif
 
-void lcd_on(void)
-{
+       delay_ms(20);
+       lcd_command(0x38);
+       delay_us(50);
        lcd_command(0x0C);
-       _delay_us(50);
+       delay_us(50);
 }
 
 void lcd_clear(void)
 {
+#ifdef LCD_ASYNC
+       lcd_wait();
+#endif
        lcd_command(0x01);
-       _delay_us(1500);
+       lcd_delay(1700);
 }
 
 void lcd_gotoxy(uint8_t x, uint8_t y)
@@ -70,14 +181,33 @@ void lcd_gotoxy(uint8_t x, uint8_t y)
        uint8_t a = x+10*y;
        if(y&1)
                a += 54;
+
+#ifdef LCD_ASYNC
+       if(lcd_busy)
+       {
+               lcd_buffer_push(a|0x80);
+               return;
+       }
+#endif
+
        lcd_command(a|0x80);
-       _delay_us(50);
+       lcd_delay(50);
 }
 
 void lcd_write(uint8_t c)
 {
-       PORTB |= 0x01;
+#ifdef LCD_ASYNC
+       if(lcd_busy)
+       {
+               if(c>=0x80)
+                       lcd_buffer_push(0xFF);
+               lcd_buffer_push(c);
+               return;
+       }
+#endif
+
+       REGSEL_PORT |= REGSEL_BIT;
        lcd_set_data(c);
        lcd_clock();
-       _delay_us(50);
+       lcd_delay(50);
 }
index 16d64d5c0ed20a8c8681b7fae880ce59bac5b0f1..2d414dcbf646aeace407f4f2a1da42607b4a27f5 100644 (file)
@@ -1,8 +1,14 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #ifndef LCD_H_
 #define LCD_H_
 
 void lcd_init(void);
-void lcd_on(void);
 void lcd_clear(void);
 void lcd_gotoxy(uint8_t, uint8_t);
 void lcd_write(uint8_t);
index f64445d81c5a33f12b8f82ef1cae2ea21c7fba5d..dbd1ef175d9a7ea95279dc5bccfe85fe12c557f5 100644 (file)
@@ -27,6 +27,7 @@ D5 - S88 RESET
 #include <avr/interrupt.h>
 #include "lcd.h"
 #include "serial.h"
+#include "delay.h"
 
 #define DATA_OUT PORTD2
 #define CLOCK    PIND3
@@ -51,20 +52,22 @@ int main()
        uint8_t bits = 0;
        uint8_t n_bits = 8;
        uint8_t offset = 0;
+       uint8_t i;
 
-       DDRD = 0x06;  // 00000110
-       PIND = 0xC0;  // 11000000
-       DDRB = 0x20;  // 00100000
-       PINB = 0x1F;  // 00011111
+       DDRD = 0x06;   // 00000110
+       PORTD = 0xC0;  // 11000000
+       DDRB = 0x20;   // 00100000
+       PORTB = 0x1F;  // 00011111
 
        serial_init(9600);
-       serial_set_callback(receive);
        lcd_init();
-       lcd_on();
-       lcd_clear();
 
        sei();
 
+       lcd_clear();
+       for(i=0; i<20; ++i)
+               lcd_write('0');
+
        while(1)
        {
                uint8_t d_pins;
@@ -140,13 +143,11 @@ void receive(uint8_t c)
                                        input[j/2] = (input[j/2]&~(0xF<<shift)) | (bits<<shift);
                                        latch[j/2] = input[j/2];
                                }
-                       }
 
-                       lcd_gotoxy(0, 0);
-                       for(i=10; i--;)
-                       {
-                               lcd_write(hexdigit(input[i]>>4));
-                               lcd_write(hexdigit(input[i]));
+                               lcd_gotoxy(19-offset-nibbles, 0);
+                               for(i=0; i<=nibbles; ++i)
+                                       lcd_write(rx_buf[3+i]);
+                               log_pos |= 0x80;
                        }
                }
                rx_fill = 0xFF;
@@ -164,10 +165,13 @@ void receive(uint8_t c)
        ++log_pos;
        if(log_pos>=60)
                log_pos = 0;
-       lcd_gotoxy(log_pos%20, 1+log_pos/20);
+       if(log_pos%20==0)
+               lcd_gotoxy(log_pos%20, 1+log_pos/20);
        lcd_write(255);
 }
 
+SERIAL_SET_CALLBACK(receive)
+
 uint8_t hexdigit(uint8_t n)
 {
        n &= 0xF;
index e394d603032fe07b14047f554abc633afb451c7a..70c646c19984fb5583899d6a2b1a45c6ecab83b7 100644 (file)
@@ -17,9 +17,21 @@ D6 - input 5
 D7 - input 6
 B0 - input 7
 B1 - input 8
+B2 - input 9
+B3 - input 10
+B4 - input 11
+B5 - input 12
 
 Inputs are pulled high by internal pull-up resistors.  Connect to GND to
 activate.
+
+The module can be configured by sending a string of form ":Shhh." over the
+serial port, where hhh is a hex number.  Lower two bits indicate the number of
+inputs (00=4, 01=8, 10=12, 11=16) and upper ten bits indicate offset of lowest
+input in multiples of 4 bits.  At the moment there are no provisions for
+having 16 physical inputs.
+
+Example: ":S016." would configure the module to have 12 inputs at offset 20.
 */
 
 #include <avr/io.h>
@@ -52,13 +64,11 @@ int main()
 
        DDRD = 0x02;   // 00000010
        PORTD = 0xFC;  // 11111100
-       DDRB = 0x20;   // 00000000
+       DDRB = 0x00;   // 00000000
        PORTB = 0x3F;  // 00111111
 
        serial_init(9600);
-       serial_set_callback(receive);
-       timer_init(1, 2);
-       timer_set_callback(send_state);
+       timer_start_hz(1, 1, 2);
 
        sei();
 
@@ -123,6 +133,8 @@ void receive(uint8_t c)
        }
 }
 
+SERIAL_SET_CALLBACK(receive)
+
 void send_state(void)
 {
        uint8_t i;
@@ -135,6 +147,8 @@ void send_state(void)
        serial_write('.');
 }
 
+TIMER_SET_CALLBACK(1, send_state)
+
 uint8_t hexdigit(uint8_t n)
 {
        n &= 0xF;
index b7219ae337381b724c0bcd0877185bbf2f8dda69..be3b13a925946015c096b103f0b849e2793acbc0 100644 (file)
@@ -1,11 +1,15 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #include <avr/io.h>
-#include <avr/interrupt.h>
 #include "serial.h"
 
 #define BIT(n) (1<<(n))
 
-static SerialCallback *serial_callback = 0;
-
 void serial_init(uint16_t baud)
 {
        DDRD = (DDRD&~0x03) | 0x02;
@@ -15,13 +19,7 @@ void serial_init(uint16_t baud)
        UBRR0H = baud>>8;
        UBRR0L = baud;
        UCSR0C = BIT(UCSZ00) | BIT(UCSZ01);  // 8N1
-       UCSR0B = BIT(RXEN0) | BIT(TXEN0);
-}
-
-void serial_set_callback(SerialCallback *cb)
-{
-       serial_callback = cb;
-       UCSR0B |= BIT(RXCIE0);
+       UCSR0B = BIT(RXEN0) | BIT(TXEN0) | BIT(RXCIE0);
 }
 
 void serial_write(uint8_t c)
@@ -29,8 +27,3 @@ void serial_write(uint8_t c)
        while(!(UCSR0A&(1<<UDRE0))) ;
        UDR0 = c;
 }
-
-ISR(USART_RX_vect)
-{
-       serial_callback(UDR0);
-}
index e62b0a9df7137ce313ba15420de7a8308e1d8821..696c20e8f37c28d0cf1617873f5c6ca972ecb97b 100644 (file)
@@ -1,10 +1,23 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #ifndef SERIAL_H_
 #define SERIAL_H_
 
-typedef void SerialCallback(uint8_t);
+#include <avr/interrupt.h>
+
+#define SERIAL_SET_CALLBACK(f) \
+       ISR(USART_RX_vect) \
+       { \
+               char c = UDR0; \
+               f(c); \
+       }
 
 void serial_init(uint16_t);
-void serial_set_callback(SerialCallback *);
 void serial_write(uint8_t);
 
 #endif
index 043dff8a4037389afbc0bb894548753fbb0881f1..13db1e8672708289631ec5957146101d1c09049e 100644 (file)
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #include <avr/io.h>
-#include <avr/interrupt.h>
 #include "timer.h"
 
 #define BIT(n) (1<<(n))
 
-static TimerCallback *timer_callback;
-
-void timer_init(uint16_t freq_p, uint8_t freq_q)
+static void timer_start(uint8_t num, uint32_t period)
 {
-       uint32_t period = F_CPU*freq_q/freq_p;
        uint8_t cs;
 
-       if(period<0x10000)
-       {
-               cs = BIT(CS10);
-       }
-       else if(period<0x80000)
-       {
-               cs = BIT(CS11);
-               period /= 8;
-       }
-       else if(period<0x400000)
-       {
-               cs = BIT(CS11) | BIT(CS10);
-               period /= 64;
-       }
-       else if(period<0x1000000)
+       if(num==0)
        {
-               cs = BIT(CS12);
-               period /= 256;
+               if(period<0x100)
+               {
+                       cs = BIT(CS00);
+               }
+               else if(period<0x800)
+               {
+                       cs = BIT(CS01);
+                       period /= 8;
+               }
+               else if(period<0x4000)
+               {
+                       cs = BIT(CS01) | BIT(CS00);
+                       period /= 64;
+               }
+               else if(period<0x10000)
+               {
+                       cs = BIT(CS02);
+                       period /= 256;
+               }
+               else
+               {
+                       cs = BIT(CS02) | BIT(CS00);
+                       period /= 1024;
+                       if(period>0xFF)
+                               period = 0xFF;
+               }
+               TCCR0A = BIT(WGM01);
+               TCCR0B = cs;
+               OCR0A = period;
+               TIMSK0 = BIT(OCIE0A);
        }
-       else
+       if(num==1)
        {
-               cs = BIT(CS12) | BIT(CS10);
-               period /= 1024;
-               if(period>0xFFFF)
-                       period = 0xFFFF;
+               if(period<0x10000)
+               {
+                       cs = BIT(CS10);
+               }
+               else if(period<0x80000)
+               {
+                       cs = BIT(CS11);
+                       period /= 8;
+               }
+               else if(period<0x400000)
+               {
+                       cs = BIT(CS11) | BIT(CS10);
+                       period /= 64;
+               }
+               else if(period<0x1000000)
+               {
+                       cs = BIT(CS12);
+                       period /= 256;
+               }
+               else
+               {
+                       cs = BIT(CS12) | BIT(CS10);
+                       period /= 1024;
+                       if(period>0xFFFF)
+                               period = 0xFFFF;
+               }
+               TCCR1A = 0;
+               TCCR1B = BIT(WGM12) | cs;
+               OCR1AH = period>>8;
+               OCR1AL = period;
+               TIMSK1 = BIT(OCIE1A);
        }
-       TCCR1A = 0;
-       TCCR1B = BIT(WGM12) | cs;
-       OCR1AH = period>>8;
-       OCR1AL = period;
 }
 
-void timer_set_callback(TimerCallback *cb)
+void timer_start_hz(uint8_t num, uint32_t freq_p, uint8_t freq_q)
+{
+       timer_start(num, F_CPU*freq_q/freq_p);
+}
+
+void timer_start_us(uint8_t num, uint32_t us)
 {
-       timer_callback = cb;
-       TIMSK1 |= BIT(OCIE1A);
+       timer_start(num, F_CPU/1000000*us);
 }
 
-ISR(TIMER1_COMPA_vect)
+void timer_stop(uint8_t num)
 {
-       timer_callback();
+       if(num==0)
+       {
+               TCCR0B = 0;
+               TIMSK0 = 0;
+       }
+       else if(num==1)
+       {
+               TCCR1B = 0;
+               TIMSK1 = 0;
+       }
 }
index fa2cd863ae93f18220da8fc07785079091cda232..2fbda8e8582a5e96195cda269ddedc532e2a1afb 100644 (file)
@@ -1,9 +1,23 @@
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010  Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
 #ifndef TIMER_H_
 #define TIMER_H_
 
-typedef void TimerCallback(void);
+#include <avr/interrupt.h>
+
+#define TIMER_SET_CALLBACK(n, f) \
+       ISR(TIMER ## n ## _COMPA_vect) \
+       { \
+               f(); \
+       }
 
-void timer_init(uint16_t, uint8_t);
-void timer_set_callback(TimerCallback *);
+void timer_start_hz(uint8_t, uint32_t, uint8_t);
+void timer_start_us(uint8_t, uint32_t);
+void timer_stop(uint8_t);
 
 #endif