]> www.fi.muni.cz Git - openparking.git/blob - firmware/modbus.c
2b2a255bedc15d8523d30a9b5d8d518421de5331
[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 128     // must be a power of two
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 #define BUFMASK (BUFSIZE-1)
25 #if (BUFSIZE & BUFMASK)
26 #error BUFSIZE must be a power of two
27 #endif
28
29 #if BUFSIZE > 255
30 typedef uint16_t bufptr_t;
31 #else
32 typedef uint8_t  bufptr_t;
33 #endif
34
35 #define bufptr_inc(x)   ((x + 1) & BUFMASK)
36
37 static volatile bufptr_t rx_bytes, tx_head, tx_tail;
38 static volatile uint8_t rxbuf[BUFSIZE], txbuf[BUFSIZE];
39 static volatile uint16_t last_rx;
40 #ifndef mb_unit_id
41 static uint8_t mb_unit_id;
42 #endif
43
44 #define UART_BAUD       9600
45 #define UBRR_VAL        ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
46
47 /*
48  * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
49  * start- and stopbits.
50  */
51 #define REQ_TIMEOUT             (28*CLOCK_HZ/UART_BAUD)
52
53 uint16_t hold_regs[MB_N_HOLD_REGS];
54
55 #if MB_N_HOLD_REGS_EEPROM > 0
56 static uint16_t hold_regs_ee[MB_N_HOLD_REGS_EEPROM] EEMEM = {
57         42, 
58         0, 0, 0, 30, 30, 30, 30, 0, 0, 0, 0, 30,
59         (1 << 4) | (1 << 11), // LED 1
60         0, // LED 2
61 };
62
63 #endif
64
65 void modbus_init(uint8_t unit)
66 {
67         rx_bytes = 0;
68         tx_head = tx_tail = 0;
69
70         if (unit)
71                 mb_unit_id = unit;
72 #if MB_N_HOLD_REGS_EEPROM > 0
73         do {
74                 int i;
75                 for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++)
76                         hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]);
77         } while (0);
78 #endif
79
80         ctl_pin_off();
81         ctl_pin_setup();
82
83         UBRR0 = UBRR_VAL;
84         UCSR0A = 0;
85         UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
86         UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
87 }
88
89 void rs485_send(char *p)
90 {
91         bufptr_t next;
92
93         if (*p == '\0')
94                 return;
95
96         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
97                 next = bufptr_inc(tx_head);
98                 while (next != tx_tail && *p != '\0') {
99                         txbuf[tx_head] = *p++;
100                         tx_head = next;
101                         next = bufptr_inc(tx_head);
102                 }
103                 ctl_pin_on();
104                 UCSR0B |= _BV(UDRIE0);
105         }
106 }
107
108 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
109 {
110         bufptr_t i;
111         uint16_t crc = 0xFFFF;
112
113         for (i = 0; i < len; i++) {
114                 uint8_t j;
115                 crc ^= (uint16_t)(buf[i]);
116                 for(j = 0; j < 8; j++) {
117                         if (crc & 0x0001) {
118                                 crc >>= 1;
119                                 crc ^= 0xA001;
120                         } else {
121                                 crc >>= 1;
122                         }
123                 }
124         }
125
126         return crc;
127 }
128
129 static void make_exception(mb_exception code)
130 {
131         txbuf[1] |= 0x80;
132         txbuf[2] = code;
133         tx_head = 3;
134 }
135
136 #define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1])
137 void put_byte(uint8_t byte)
138 {
139         txbuf[tx_head++] = byte;
140 }
141
142 void put_word(uint16_t word)
143 {
144         txbuf[tx_head++] = word >> 8;
145         txbuf[tx_head++] = word & 0xFF;
146 }
147
148 static mb_exception read_holding_regs(uint16_t start, uint16_t len)
149 {
150         if (len > BUFSIZE/2 - 3)
151                 return MB_ILLEGAL_ADDR;
152
153         if (start < MB_HOLD_REGS_BASE
154                 || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
155                 return MB_ILLEGAL_ADDR;
156
157         put_byte(2*len);
158
159         start -= MB_HOLD_REGS_BASE;
160         while(len--)
161                 put_word(hold_regs[start++]);
162
163         return MB_OK;
164 }
165
166 static mb_exception write_single_reg(uint16_t reg, uint16_t val)
167 {
168         if (reg < MB_HOLD_REGS_BASE
169                 || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
170                 return MB_ILLEGAL_ADDR;
171
172         if (!hold_reg_is_valid(reg, val))
173                 return MB_ILLEGAL_VAL;
174
175         reg -= MB_HOLD_REGS_BASE;
176         hold_regs[reg] = val;
177 #if MB_N_HOLD_REGS_EEPROM > 0
178         if (reg < MB_N_HOLD_REGS_EEPROM)
179                 eeprom_write_word(&hold_regs_ee[reg], val);
180 #endif
181         put_word(reg + MB_HOLD_REGS_BASE);
182         put_word(val);
183
184         return MB_OK;
185 }
186
187 void modbus_poll()
188 {
189         bufptr_t packet_len;
190         uint16_t crc;
191         uint8_t rv;
192
193         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
194                 if (rx_bytes == 0) // nothing received yet
195                         return;
196
197                 if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving
198                         return;
199
200                 if (rx_bytes < 4) { // too short
201                         rx_bytes = 0;
202                         return;
203                 }
204
205                 if (rxbuf[0] != mb_unit_id) { // not for myself
206                         rx_bytes = 0;
207                         return;
208                 }
209
210                 if (tx_tail) { // still sending?
211                         rx_bytes = 0;
212                         return;
213                 }
214
215                 packet_len = rx_bytes; // make a copy
216         }
217
218         crc = compute_crc(rxbuf, packet_len - 2);
219
220         if ((crc & 0xFF) != rxbuf[packet_len-2]
221                 || (crc >> 8) != rxbuf[packet_len-1]) // bad crc
222                 goto out;
223
224         txbuf[0] = rxbuf[0]; // not mb_unit_id in case it gets changed
225         txbuf[1] = rxbuf[1];
226         tx_head = 2;
227
228         rv = MB_OK;
229         switch (rxbuf[1]) { // function
230         case 3:
231                 rv = read_holding_regs(get_word(rxbuf, 2), get_word(rxbuf, 4));
232                 break;
233         case 6:
234                 rv = write_single_reg(get_word(rxbuf, 2), get_word(rxbuf, 4));
235                 break;
236         default:
237                 make_exception(MB_ILLEGAL_FUNC); // illegal function
238         }
239         
240         if (rv)
241                 make_exception(rv);
242 send:
243         if (tx_head) {
244                 crc = compute_crc(txbuf, tx_head);
245                 txbuf[tx_head++] = crc & 0xFF;
246                 txbuf[tx_head++] = crc >> 8;
247
248                 tx_tail = 0;
249
250                 ctl_pin_on();
251                 UCSR0B |= _BV(UDRIE0);
252         }
253 out:
254         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
255                 rx_bytes = 0;
256         }
257 }
258
259 ISR(USART_RX_vect)
260 {
261         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
262                 rxbuf[rx_bytes] = UDR0;
263                 if (rx_bytes + 1 < BUFSIZE) // ignore overruns
264                         rx_bytes++;
265                 last_rx = get_clock();
266         }
267 }
268
269 ISR(USART_TX_vect)
270 {
271         UCSR0B &= ~_BV(TXCIE0); // disable further IRQs
272         ctl_pin_off();
273 }
274
275 ISR(USART_UDRE_vect)
276 {
277         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
278                 if (tx_head == tx_tail) {
279                         UCSR0B |= _BV(TXCIE0); // enable xmit complete irq
280                         UCSR0B &= ~_BV(UDRIE0);
281                         tx_tail = tx_head = 0;
282                 } else {
283                         UDR0 = txbuf[tx_tail];
284                         tx_tail = bufptr_inc(tx_tail);
285                 }
286         }
287 }