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>
17 #define BUFSIZE 180 // maximum request size
19 // configure the control pin
20 #define ctl_pin_setup() do { DDRD |= _BV(PD2); } while (0)
21 #define ctl_pin_on() do { PORTD |= _BV(PD2); } while (0)
22 #define ctl_pin_off() do { PORTD &= ~_BV(PD2); } while (0)
25 typedef uint16_t bufptr_t;
27 typedef uint8_t bufptr_t;
30 static volatile bufptr_t buf_len, tx_ptr;
31 static volatile uint8_t buffer[BUFSIZE], transmitting;
32 static volatile uint16_t last_rx;
34 static uint8_t mb_unit_id;
37 #define UART_BAUD 9600
38 #define UBRR_VAL ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
41 * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
42 * start- and stopbits.
44 #define REQ_TIMEOUT (28*CLOCK_HZ/UART_BAUD)
46 uint16_t hold_regs[MB_N_HOLD_REGS];
48 #if MB_N_HOLD_REGS_EEPROM > 0
49 static uint16_t hold_regs_ee[MB_N_HOLD_REGS_EEPROM] EEMEM = {
51 //0 1 2 3 4 5 6 7 8 9 10 11
52 //1 2 3 4 5 6 7 8 9 10 11 12
53 220, 220, 220, 220, 220, 220, 220, 220, 220, 220, 0, 0,
60 void modbus_init(uint8_t unit)
67 #if MB_N_HOLD_REGS_EEPROM > 0
70 for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++)
71 hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]);
81 UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
82 UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
85 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
88 uint16_t crc = 0xFFFF;
90 for (i = 0; i < len; i++) {
92 crc ^= (uint16_t)(buf[i]);
93 for(j = 0; j < 8; j++) {
106 static void make_exception(mb_exception code)
113 #define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1])
114 void put_byte(uint8_t byte)
116 buffer[buf_len++] = byte;
119 void put_word(uint16_t word)
121 buffer[buf_len++] = word >> 8;
122 buffer[buf_len++] = word & 0xFF;
125 static mb_exception read_holding_regs(uint16_t start, uint16_t len)
127 if (len > BUFSIZE/2 - 3)
128 return MB_ILLEGAL_ADDR;
130 if (start < MB_HOLD_REGS_BASE
131 || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
132 return MB_ILLEGAL_ADDR;
136 start -= MB_HOLD_REGS_BASE;
138 put_word(hold_regs[start++]);
143 static mb_exception write_single_reg(uint16_t reg, uint16_t val)
145 if (reg < MB_HOLD_REGS_BASE
146 || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
147 return MB_ILLEGAL_ADDR;
149 if (!hold_reg_is_valid(reg, val))
150 return MB_ILLEGAL_VAL;
152 reg -= MB_HOLD_REGS_BASE;
153 hold_regs[reg] = val;
154 #if MB_N_HOLD_REGS_EEPROM > 0
155 if (reg < MB_N_HOLD_REGS_EEPROM)
156 eeprom_write_word(&hold_regs_ee[reg], val);
158 put_word(reg + MB_HOLD_REGS_BASE);
164 uint8_t modbus_poll()
170 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
174 if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving
177 if (buf_len < 4) { // too short (or not for us)
182 transmitting = 1; // disable further reads
183 packet_len = buf_len;
186 crc = compute_crc(buffer, packet_len - 2);
188 if ((crc & 0xFF) != buffer[packet_len-2]
189 || (crc >> 8) != buffer[packet_len-1]) { // bad CRC
190 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
197 packet_len -= 2; // strip the CRC
198 buf_len = 2; // keep the first two bytes (unit ID and function) for TX
200 rv = MB_ILLEGAL_FUNC;
202 switch (buffer[1]) { // function
205 rv = read_holding_regs(
212 rv = write_single_reg(
223 crc = compute_crc(buffer, buf_len);
224 put_byte(crc & 0xFF);
227 // send out the reply
230 UCSR0B |= _BV(UDRIE0);
237 uint8_t rx_byte = UDR0;
238 clock_t now = get_clock();
240 if (transmitting) // how did we get here? discard it
243 if (buf_len && buffer[0] != mb_unit_id) // not for us
246 if (buf_len == BUFSIZE) { // overrun - discard the packet
252 if (now - last_rx >= REQ_TIMEOUT) { // new packet; start over
256 // TODO: we can probably calculate the CRC here as well
257 buffer[buf_len++] = rx_byte;
264 UCSR0B &= ~_BV(TXCIE0); // disable further IRQs
267 transmitting = 0; // enable receiving
272 if (tx_ptr >= buf_len) {
273 UCSR0A |= _BV(TXC0); // clear the pending TXC flag
274 UCSR0B |= _BV(TXCIE0); // enable xmit complete irq
275 UCSR0B &= ~_BV(UDRIE0); // disable ourselves
277 UDR0 = buffer[tx_ptr++];