]> www.fi.muni.cz Git - openparking.git/blob - firmware/modbus.c
modbus.c: new default config
[openparking.git] / firmware / modbus.c
1 /*
2  * Loosely modelled after AVR-RS485 by Yoshinori Kohyama (http://algobit.jp/),
3  * available at https://github.com/kohyama/AVR-RS485/
4  *
5  * All bugs by Jan "Yenya" Kasprzak <kas@fi.muni.cz> :-)
6  */
7
8 #include <avr/eeprom.h>
9 #include <avr/io.h>
10 #include <avr/interrupt.h>
11 #include <util/atomic.h>
12 #include <util/delay.h>
13
14 #include "clock.h"
15 #include "modbus.h"
16
17 #define BUFSIZE 180 // maximum request size
18
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)
23
24 #if BUFSIZE > 255
25 typedef uint16_t bufptr_t;
26 #else
27 typedef uint8_t  bufptr_t;
28 #endif
29
30 static volatile bufptr_t buf_len, tx_ptr;
31 static volatile uint8_t buffer[BUFSIZE], transmitting;
32 static volatile uint16_t last_rx;
33 #ifndef mb_unit_id
34 static uint8_t mb_unit_id;
35 #endif
36
37 #define UART_BAUD       9600
38 #define UBRR_VAL        ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
39
40 /*
41  * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
42  * start- and stopbits.
43  */
44 #define REQ_TIMEOUT             (28*CLOCK_HZ/UART_BAUD)
45
46 uint16_t hold_regs[MB_N_HOLD_REGS];
47
48 #if MB_N_HOLD_REGS_EEPROM > 0
49 static uint16_t hold_regs_ee[MB_N_HOLD_REGS_EEPROM] EEMEM = {
50         99,
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,
54         0x3E0, // LED 1
55         0x01F, // LED 2
56 };
57
58 #endif
59
60 void modbus_init(uint8_t unit)
61 {
62         buf_len = 0;
63         transmitting = 0;
64
65         if (unit)
66                 mb_unit_id = unit;
67 #if MB_N_HOLD_REGS_EEPROM > 0
68         do {
69                 int i;
70                 for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++)
71                         hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]);
72         } while (0);
73 #endif
74
75         ctl_pin_off();
76         ctl_pin_setup();
77
78         // Serial port setup
79         UBRR0 = UBRR_VAL;
80         UCSR0A = 0;
81         UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
82         UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
83 }
84
85 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
86 {
87         bufptr_t i;
88         uint16_t crc = 0xFFFF;
89
90         for (i = 0; i < len; i++) {
91                 uint8_t j;
92                 crc ^= (uint16_t)(buf[i]);
93                 for(j = 0; j < 8; j++) {
94                         if (crc & 0x0001) {
95                                 crc >>= 1;
96                                 crc ^= 0xA001;
97                         } else {
98                                 crc >>= 1;
99                         }
100                 }
101         }
102
103         return crc;
104 }
105
106 static void make_exception(mb_exception code)
107 {
108         buffer[1] |= 0x80;
109         buffer[2] = code;
110         buf_len = 3;
111 }
112
113 #define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1])
114 void put_byte(uint8_t byte)
115 {
116         buffer[buf_len++] = byte;
117 }
118
119 void put_word(uint16_t word)
120 {
121         buffer[buf_len++] = word >> 8;
122         buffer[buf_len++] = word & 0xFF;
123 }
124
125 static mb_exception read_holding_regs(uint16_t start, uint16_t len)
126 {
127         if (len > BUFSIZE/2 - 3)
128                 return MB_ILLEGAL_ADDR;
129
130         if (start < MB_HOLD_REGS_BASE
131                 || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
132                 return MB_ILLEGAL_ADDR;
133
134         put_byte(2*len);
135
136         start -= MB_HOLD_REGS_BASE;
137         while(len--)
138                 put_word(hold_regs[start++]);
139
140         return MB_OK;
141 }
142
143 static mb_exception write_single_reg(uint16_t reg, uint16_t val)
144 {
145         if (reg < MB_HOLD_REGS_BASE
146                 || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
147                 return MB_ILLEGAL_ADDR;
148
149         if (!hold_reg_is_valid(reg, val))
150                 return MB_ILLEGAL_VAL;
151
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);
157 #endif
158         put_word(reg + MB_HOLD_REGS_BASE);
159         put_word(val);
160
161         return MB_OK;
162 }
163
164 uint8_t modbus_poll()
165 {
166         bufptr_t packet_len;
167         uint16_t crc;
168         uint8_t rv;
169
170         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
171                 if (transmitting)
172                         return 0;
173
174                 if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving
175                         return 0;
176
177                 if (buf_len < 4) { // too short (or not for us)
178                         buf_len = 0;
179                         return 0;
180                 }
181
182                 transmitting = 1; // disable further reads
183                 packet_len = buf_len;
184         }
185
186         crc = compute_crc(buffer, packet_len - 2);
187
188         if ((crc & 0xFF) != buffer[packet_len-2]
189                 || (crc >> 8) != buffer[packet_len-1]) { // bad CRC
190                 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
191                         transmitting = 0;
192                         buf_len = 0;
193                 }
194                 return 1;
195         }
196
197         packet_len -= 2; // strip the CRC
198         buf_len = 2; // keep the first two bytes (unit ID and function) for TX
199
200         rv = MB_ILLEGAL_FUNC;
201
202         switch (buffer[1]) { // function
203         case 3:
204                 if (packet_len == 6)
205                         rv = read_holding_regs(
206                                 get_word(buffer, 2),
207                                 get_word(buffer, 4)
208                         );
209                 break;
210         case 6:
211                 if (packet_len == 6)
212                         rv = write_single_reg(
213                                 get_word(buffer, 2),
214                                 get_word(buffer, 4)
215                         );
216                 break;
217         }
218         
219         if (rv)
220                 make_exception(rv);
221
222         // append the CRC
223         crc = compute_crc(buffer, buf_len);
224         put_byte(crc & 0xFF);
225         put_byte(crc >> 8);
226
227         // send out the reply
228         tx_ptr = 0;
229         ctl_pin_on();
230         UCSR0B |= _BV(UDRIE0);
231
232         return 1;
233 }
234
235 ISR(USART_RX_vect)
236 {
237         uint8_t rx_byte = UDR0;
238         clock_t now = get_clock();
239
240         if (transmitting) // how did we get here? discard it
241                 goto out;
242
243         if (buf_len && buffer[0] != mb_unit_id) // not for us
244                 goto out;
245
246         if (buf_len == BUFSIZE) { // overrun - discard the packet
247                 buffer[0] = 0xFF;
248                 buf_len = 1;
249                 goto out;
250         }
251
252         if (now - last_rx >= REQ_TIMEOUT) { // new packet; start over
253                 buf_len = 0;
254         }
255
256         // TODO: we can probably calculate the CRC here as well
257         buffer[buf_len++] = rx_byte;
258 out:
259         last_rx = now;
260 }
261
262 ISR(USART_TX_vect)
263 {
264         UCSR0B &= ~_BV(TXCIE0); // disable further IRQs
265         ctl_pin_off();
266         buf_len = 0;
267         transmitting = 0; // enable receiving
268 }
269
270 ISR(USART_UDRE_vect)
271 {
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
276         } else {
277                 UDR0 = buffer[tx_ptr++];
278         }
279 }