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