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
$(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
--- /dev/null
+/* $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
+/* $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"
+/* $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_
+/* $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)
{
#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)
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);
}
+/* $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);
#include <avr/interrupt.h>
#include "lcd.h"
#include "serial.h"
+#include "delay.h"
#define DATA_OUT PORTD2
#define CLOCK PIND3
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;
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;
++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;
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>
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();
}
}
+SERIAL_SET_CALLBACK(receive)
+
void send_state(void)
{
uint8_t i;
serial_write('.');
}
+TIMER_SET_CALLBACK(1, send_state)
+
uint8_t hexdigit(uint8_t n)
{
n &= 0xF;
+/* $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;
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)
while(!(UCSR0A&(1<<UDRE0))) ;
UDR0 = c;
}
-
-ISR(USART_RX_vect)
-{
- serial_callback(UDR0);
-}
+/* $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
+/* $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;
+ }
}
+/* $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