X-Git-Url: https://www.fi.muni.cz/~kas/git//home/kas/public_html/git/?a=blobdiff_plain;f=firmware%2Fmodbus.c;h=c895c5f30ec71336e08a4820b059b9cfec22f827;hb=087a60aae1ad8d3f96f94601c68910154f884735;hp=937d9d90b0f543775c81cd8507729311a7eff57b;hpb=86f8e5bfd1c753034b9ca876dac7d18720ab8808;p=openparking.git diff --git a/firmware/modbus.c b/firmware/modbus.c old mode 100755 new mode 100644 index 937d9d9..c895c5f --- a/firmware/modbus.c +++ b/firmware/modbus.c @@ -5,83 +5,81 @@ * All bugs by Jan "Yenya" Kasprzak :-) */ +#include #include #include #include #include -#define BUFSIZE 128 // must be a power of two +#include "clock.h" +#include "modbus.h" + +#define BUFSIZE 180 // maximum request size // configure the control pin #define ctl_pin_setup() do { DDRD |= _BV(PD2); } while (0) #define ctl_pin_on() do { PORTD |= _BV(PD2); } while (0) #define ctl_pin_off() do { PORTD &= ~_BV(PD2); } while (0) -#define BUFMASK (BUFSIZE-1) -#if (BUFSIZE & BUFMASK) -#error BUFSIZE must be a power of two -#endif - #if BUFSIZE > 255 typedef uint16_t bufptr_t; #else typedef uint8_t bufptr_t; #endif -#define bufptr_inc(x) ((x + 1) & BUFMASK) - -static volatile bufptr_t rx_bytes, tx_head, tx_tail; -static volatile uint8_t rxbuf[BUFSIZE], txbuf[BUFSIZE]; +static volatile bufptr_t buf_len, tx_ptr; +static volatile uint8_t buffer[BUFSIZE], transmitting; static volatile uint16_t last_rx; -static volatile uint8_t unit_id; +#ifndef mb_unit_id +static uint8_t mb_unit_id; +#endif #define UART_BAUD 9600 #define UBRR_VAL ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1) -#define wait_one_byte() _delay_us(10*1000000/UART_BAUD) -#define get_clock() (TCNT1) -#define CLOCK_SPEED (F_CPU/1024) /* * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without * start- and stopbits. */ -#define TIMEOUT (28*CLOCK_SPEED/UART_BAUD) +#define REQ_TIMEOUT (28*CLOCK_HZ/UART_BAUD) -void modbus_init() -{ - rx_bytes = 0; - tx_head = tx_tail = 0; +uint16_t hold_regs[MB_N_HOLD_REGS]; - unit_id = 42; +#if MB_N_HOLD_REGS_EEPROM > 0 +static uint16_t hold_regs_ee[MB_N_HOLD_REGS_EEPROM] EEMEM = { + 42, + 0, 0, 0, 30, 30, 30, 30, 0, 0, 0, 0, 30, + (1 << 4) | (1 << 11), // LED 1 + 0, // LED 2 +}; + +#endif + +void modbus_init(uint8_t unit) +{ + buf_len = 0; + transmitting = 0; + + if (unit) + mb_unit_id = unit; +#if MB_N_HOLD_REGS_EEPROM > 0 + do { + int i; + for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++) + hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]); + } while (0); +#endif ctl_pin_off(); ctl_pin_setup(); + // Serial port setup UBRR0 = UBRR_VAL; UCSR0A = 0; UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0); UCSR0C = _BV(UCSZ01)|_BV(UCSZ00); } -void rs485_send(char *p) -{ - bufptr_t next; - - if (*p == '\0') - return; - - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - next = bufptr_inc(tx_head); - while (next != tx_tail && *p != '\0') { - txbuf[tx_head] = *p++; - tx_head = next; - next = bufptr_inc(tx_head); - } - ctl_pin_on(); - UCSR0B |= _BV(UDRIE0); - } -} - static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len) { bufptr_t i; @@ -103,94 +101,177 @@ static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len) return crc; } -static void make_exception(uint8_t func, uint8_t code) +static void make_exception(mb_exception code) +{ + buffer[1] |= 0x80; + buffer[2] = code; + buf_len = 3; +} + +#define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1]) +void put_byte(uint8_t byte) +{ + buffer[buf_len++] = byte; +} + +void put_word(uint16_t word) +{ + buffer[buf_len++] = word >> 8; + buffer[buf_len++] = word & 0xFF; +} + +static mb_exception read_holding_regs(uint16_t start, uint16_t len) +{ + if (len > BUFSIZE/2 - 3) + return MB_ILLEGAL_ADDR; + + if (start < MB_HOLD_REGS_BASE + || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS) + return MB_ILLEGAL_ADDR; + + put_byte(2*len); + + start -= MB_HOLD_REGS_BASE; + while(len--) + put_word(hold_regs[start++]); + + return MB_OK; +} + +static mb_exception write_single_reg(uint16_t reg, uint16_t val) { - txbuf[tx_head++] = unit_id; - txbuf[tx_head++] = func | 0x80; - txbuf[tx_head++] = code; + if (reg < MB_HOLD_REGS_BASE + || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS) + return MB_ILLEGAL_ADDR; + + if (!hold_reg_is_valid(reg, val)) + return MB_ILLEGAL_VAL; + + reg -= MB_HOLD_REGS_BASE; + hold_regs[reg] = val; +#if MB_N_HOLD_REGS_EEPROM > 0 + if (reg < MB_N_HOLD_REGS_EEPROM) + eeprom_write_word(&hold_regs_ee[reg], val); +#endif + put_word(reg + MB_HOLD_REGS_BASE); + put_word(val); + + return MB_OK; } -void modbus_poll() +uint8_t modbus_poll() { bufptr_t packet_len; uint16_t crc; + uint8_t rv; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - if (rx_bytes == 0) // nothing received yet - return; + if (transmitting) + return 0; - if (get_clock() - last_rx < TIMEOUT) // still receiving - return; + if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving + return 0; - if (rx_bytes < 4) { // too short - rx_bytes = 0; - return; + if (buf_len < 4) { // too short (or not for us) + buf_len = 0; + return 0; } - if (rxbuf[0] != unit_id) { // not for myself - rx_bytes = 0; - return; - } + transmitting = 1; // disable further reads + packet_len = buf_len; + } + + crc = compute_crc(buffer, packet_len - 2); - if (tx_tail) { // still sending? - rx_bytes = 0; - return; + if ((crc & 0xFF) != buffer[packet_len-2] + || (crc >> 8) != buffer[packet_len-1]) { // bad CRC + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + transmitting = 0; + buf_len = 0; } + return 1; + } - packet_len = rx_bytes; // make a copy + packet_len -= 2; // strip the CRC + buf_len = 2; // keep the first two bytes (unit ID and function) for TX + + rv = MB_ILLEGAL_FUNC; + + switch (buffer[1]) { // function + case 3: + if (packet_len == 6) + rv = read_holding_regs( + get_word(buffer, 2), + get_word(buffer, 4) + ); + break; + case 6: + if (packet_len == 6) + rv = write_single_reg( + get_word(buffer, 2), + get_word(buffer, 4) + ); + break; } + + if (rv) + make_exception(rv); - crc = compute_crc(rxbuf, packet_len - 2); + // append the CRC + crc = compute_crc(buffer, buf_len); + put_byte(crc & 0xFF); + put_byte(crc >> 8); - if ((crc & 0xFF) != rxbuf[packet_len-2] - || (crc >> 8) != rxbuf[packet_len-1]) // bad crc - goto out; + // send out the reply + tx_ptr = 0; + ctl_pin_on(); + UCSR0B |= _BV(UDRIE0); - tx_head = 0; + return 1; +} - switch (rxbuf[1]) { // function - default: - make_exception(rxbuf[1], 1); // illegal function - } +ISR(USART_RX_vect) +{ + uint8_t rx_byte = UDR0; + clock_t now = get_clock(); -send: - if (tx_head) { - crc = compute_crc(txbuf, tx_head); - txbuf[tx_head++] = crc & 0xFF; - txbuf[tx_head++] = crc >> 8; + if (transmitting) // how did we get here? discard it + goto out; - tx_tail = 0; + if (buf_len && buffer[0] != mb_unit_id) // not for us + goto out; - ctl_pin_on(); - UCSR0B |= _BV(UDRIE0); + if (buf_len == BUFSIZE) { // overrun - discard the packet + buffer[0] = 0xFF; + buf_len = 1; + goto out; } -out: - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - rx_bytes = 0; + + if (now - last_rx >= REQ_TIMEOUT) { // new packet; start over + buf_len = 0; } + + // TODO: we can probably calculate the CRC here as well + buffer[buf_len++] = rx_byte; +out: + last_rx = now; } -ISR(USART_RX_vect) +ISR(USART_TX_vect) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - rxbuf[rx_bytes] = UDR0; - if (rx_bytes + 1 < BUFSIZE) // ignore overruns - rx_bytes++; - last_rx = get_clock(); - } + UCSR0B &= ~_BV(TXCIE0); // disable further IRQs + ctl_pin_off(); + buf_len = 0; + transmitting = 0; // enable receiving } ISR(USART_UDRE_vect) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - if (tx_head == tx_tail) { - UCSR0B &= ~_BV(UDRIE0); - tx_tail = tx_head = 0; - wait_one_byte(); // FIXME: too long busy-wait - ctl_pin_off(); - } else { - UDR0 = txbuf[tx_tail]; - tx_tail = bufptr_inc(tx_tail); - } + if (tx_ptr >= buf_len) { + UCSR0A |= _BV(TXC0); // clear the pending TXC flag + UCSR0B |= _BV(TXCIE0); // enable xmit complete irq + UCSR0B &= ~_BV(UDRIE0); // disable ourselves + } else { + UDR0 = buffer[tx_ptr++]; } }