2 * Loosely modelled after AVR-RS485 by Yoshinori Kohyama (http://algobit.jp/),
3 * available at https://github.com/kohyama/AVR-RS485/
5 * All bugs by Jan "Yenya" Kasprzak <kas@fi.muni.cz> :-)
8 #include <avr/eeprom.h>
10 #include <avr/interrupt.h>
11 #include <util/atomic.h>
12 #include <util/delay.h>
16 #define BUFSIZE 128 // must be a power of two
18 // configure the control pin
19 #define ctl_pin_setup() do { DDRD |= _BV(PD2); } while (0)
20 #define ctl_pin_on() do { PORTD |= _BV(PD2); } while (0)
21 #define ctl_pin_off() do { PORTD &= ~_BV(PD2); } while (0)
23 #define BUFMASK (BUFSIZE-1)
24 #if (BUFSIZE & BUFMASK)
25 #error BUFSIZE must be a power of two
29 typedef uint16_t bufptr_t;
31 typedef uint8_t bufptr_t;
34 #define bufptr_inc(x) ((x + 1) & BUFMASK)
36 static volatile bufptr_t rx_bytes, tx_head, tx_tail;
37 static volatile uint8_t rxbuf[BUFSIZE], txbuf[BUFSIZE];
38 static volatile uint16_t last_rx;
40 static uint8_t mb_unit_id;
43 #define UART_BAUD 9600
44 #define UBRR_VAL ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
45 #define wait_one_byte() _delay_us(10*1000000/UART_BAUD)
47 #define get_clock() (TCNT1)
48 #define CLOCK_SPEED (F_CPU/1024)
50 * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
51 * start- and stopbits.
53 #define TIMEOUT (28*CLOCK_SPEED/UART_BAUD)
55 uint16_t hold_regs[MB_N_HOLD_REGS];
57 #if MB_N_HOLD_REGS_EEPROM > 0
58 static uint16_t hold_regs_ee[MB_N_HOLD_REGS_EEPROM] EEMEM = {
60 0, 0, 0, 30, 30, 30, 30, 0, 0, 0, 0, 30,
61 (1 << 4) | (1 << 11), // LED 1
67 void modbus_init(uint8_t unit)
70 tx_head = tx_tail = 0;
74 #if MB_N_HOLD_REGS_EEPROM > 0
77 for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++)
78 hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]);
87 UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
88 UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
91 void rs485_send(char *p)
98 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
99 next = bufptr_inc(tx_head);
100 while (next != tx_tail && *p != '\0') {
101 txbuf[tx_head] = *p++;
103 next = bufptr_inc(tx_head);
106 UCSR0B |= _BV(UDRIE0);
110 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
113 uint16_t crc = 0xFFFF;
115 for (i = 0; i < len; i++) {
117 crc ^= (uint16_t)(buf[i]);
118 for(j = 0; j < 8; j++) {
131 static void make_exception(mb_exception code)
138 #define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1])
139 void put_byte(uint8_t byte)
141 txbuf[tx_head++] = byte;
144 void put_word(uint16_t word)
146 txbuf[tx_head++] = word >> 8;
147 txbuf[tx_head++] = word & 0xFF;
150 static mb_exception read_holding_regs(uint16_t start, uint16_t len)
152 if (len > BUFSIZE/2 - 3)
153 return MB_ILLEGAL_ADDR;
155 if (start < MB_HOLD_REGS_BASE
156 || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
157 return MB_ILLEGAL_ADDR;
161 start -= MB_HOLD_REGS_BASE;
163 put_word(hold_regs[start++]);
168 static mb_exception write_single_reg(uint16_t reg, uint16_t val)
170 if (reg < MB_HOLD_REGS_BASE
171 || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
172 return MB_ILLEGAL_ADDR;
174 if (!hold_reg_is_valid(reg, val))
175 return MB_ILLEGAL_VAL;
177 reg -= MB_HOLD_REGS_BASE;
178 hold_regs[reg] = val;
179 #if MB_N_HOLD_REGS_EEPROM > 0
180 if (reg < MB_N_HOLD_REGS_EEPROM)
181 eeprom_write_word(&hold_regs_ee[reg], val);
183 put_word(reg + MB_HOLD_REGS_BASE);
195 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
196 if (rx_bytes == 0) // nothing received yet
199 if (get_clock() - last_rx < TIMEOUT) // still receiving
202 if (rx_bytes < 4) { // too short
207 if (rxbuf[0] != mb_unit_id) { // not for myself
212 if (tx_tail) { // still sending?
217 packet_len = rx_bytes; // make a copy
220 crc = compute_crc(rxbuf, packet_len - 2);
222 if ((crc & 0xFF) != rxbuf[packet_len-2]
223 || (crc >> 8) != rxbuf[packet_len-1]) // bad crc
226 txbuf[0] = rxbuf[0]; // not mb_unit_id in case it gets changed
231 switch (rxbuf[1]) { // function
233 rv = read_holding_regs(get_word(rxbuf, 2), get_word(rxbuf, 4));
236 rv = write_single_reg(get_word(rxbuf, 2), get_word(rxbuf, 4));
239 make_exception(MB_ILLEGAL_FUNC); // illegal function
246 crc = compute_crc(txbuf, tx_head);
247 txbuf[tx_head++] = crc & 0xFF;
248 txbuf[tx_head++] = crc >> 8;
253 UCSR0B |= _BV(UDRIE0);
256 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
263 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
264 rxbuf[rx_bytes] = UDR0;
265 if (rx_bytes + 1 < BUFSIZE) // ignore overruns
267 last_rx = get_clock();
273 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
274 if (tx_head == tx_tail) {
275 UCSR0B &= ~_BV(UDRIE0);
276 tx_tail = tx_head = 0;
277 wait_one_byte(); // FIXME: too long busy-wait
280 UDR0 = txbuf[tx_tail];
281 tx_tail = bufptr_inc(tx_tail);