From: Mikko Rasa Date: Wed, 27 Oct 2010 16:14:17 +0000 (+0000) Subject: Support multiple timers (currently 0 and 1) X-Git-Url: http://git.tdb.fi/?p=model-railway-devices.git;a=commitdiff_plain;h=238cd5e5bd9c6fa86ae85f5eb7a8ce00158f2f7c Support multiple timers (currently 0 and 1) 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 --- diff --git a/firmware/Makefile b/firmware/Makefile index c17d991..3f4f4c6 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -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 index 0000000..1798cec --- /dev/null +++ b/firmware/delay.h @@ -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 diff --git a/firmware/eeprom.c b/firmware/eeprom.c index 9027ed0..b9010f0 100644 --- a/firmware/eeprom.c +++ b/firmware/eeprom.c @@ -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 #include "eeprom.h" diff --git a/firmware/eeprom.h b/firmware/eeprom.h index 903ce96..97527d6 100644 --- a/firmware/eeprom.h +++ b/firmware/eeprom.h @@ -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_ diff --git a/firmware/lcd.c b/firmware/lcd.c index 8c722d8..6ba00d5 100644 --- a/firmware/lcd.c +++ b/firmware/lcd.c @@ -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 -#include +#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); } diff --git a/firmware/lcd.h b/firmware/lcd.h index 16d64d5..2d414dc 100644 --- a/firmware/lcd.h +++ b/firmware/lcd.h @@ -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); diff --git a/firmware/s88w-r.c b/firmware/s88w-r.c index f64445d..dbd1ef1 100644 --- a/firmware/s88w-r.c +++ b/firmware/s88w-r.c @@ -27,6 +27,7 @@ D5 - S88 RESET #include #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<>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; diff --git a/firmware/s88w-t.c b/firmware/s88w-t.c index e394d60..70c646c 100644 --- a/firmware/s88w-t.c +++ b/firmware/s88w-t.c @@ -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 @@ -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; diff --git a/firmware/serial.c b/firmware/serial.c index b7219ae..be3b13a 100644 --- a/firmware/serial.c +++ b/firmware/serial.c @@ -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 -#include #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< + +#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 diff --git a/firmware/timer.c b/firmware/timer.c index 043dff8..13db1e8 100644 --- a/firmware/timer.c +++ b/firmware/timer.c @@ -1,55 +1,108 @@ +/* $Id$ + +This file is part of the MSP Märklin suite +Copyright © 2010 Mikkosoft Productions, Mikko Rasa +Distributed under the GPL +*/ + #include -#include #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; + } } diff --git a/firmware/timer.h b/firmware/timer.h index fa2cd86..2fbda8e 100644 --- a/firmware/timer.h +++ b/firmware/timer.h @@ -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 + +#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