]> www.fi.muni.cz Git - openparking.git/blob - firmware/modbus.c
b726280f9922f9de4abea11a702e0a69ba58af0f
[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 // 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         42, 
51         0, 0, 0, 30, 30, 30, 30, 0, 0, 0, 0, 30,
52         (1 << 4) | (1 << 11), // LED 1
53         0, // LED 2
54 };
55
56 #endif
57
58 void modbus_init(uint8_t unit)
59 {
60         buf_len = 0;
61         transmitting = 0;
62
63         if (unit)
64                 mb_unit_id = unit;
65 #if MB_N_HOLD_REGS_EEPROM > 0
66         do {
67                 int i;
68                 for (i = 0; i < MB_N_HOLD_REGS_EEPROM; i++)
69                         hold_regs[i] = eeprom_read_word(&hold_regs_ee[i]);
70         } while (0);
71 #endif
72
73         ctl_pin_off();
74         ctl_pin_setup();
75
76         // Serial port setup
77         UBRR0 = UBRR_VAL;
78         UCSR0A = 0;
79         UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
80         UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
81 }
82
83 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
84 {
85         bufptr_t i;
86         uint16_t crc = 0xFFFF;
87
88         for (i = 0; i < len; i++) {
89                 uint8_t j;
90                 crc ^= (uint16_t)(buf[i]);
91                 for(j = 0; j < 8; j++) {
92                         if (crc & 0x0001) {
93                                 crc >>= 1;
94                                 crc ^= 0xA001;
95                         } else {
96                                 crc >>= 1;
97                         }
98                 }
99         }
100
101         return crc;
102 }
103
104 static void make_exception(mb_exception code)
105 {
106         buffer[1] |= 0x80;
107         buffer[2] = code;
108         buf_len = 3;
109 }
110
111 #define get_word(ptr, off) (((uint16_t)ptr[off] << 8) | ptr[off+1])
112 void put_byte(uint8_t byte)
113 {
114         buffer[buf_len++] = byte;
115 }
116
117 void put_word(uint16_t word)
118 {
119         buffer[buf_len++] = word >> 8;
120         buffer[buf_len++] = word & 0xFF;
121 }
122
123 static mb_exception read_holding_regs(uint16_t start, uint16_t len)
124 {
125         if (len > BUFSIZE/2 - 3)
126                 return MB_ILLEGAL_ADDR;
127
128         if (start < MB_HOLD_REGS_BASE
129                 || start + len > MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
130                 return MB_ILLEGAL_ADDR;
131
132         put_byte(2*len);
133
134         start -= MB_HOLD_REGS_BASE;
135         while(len--)
136                 put_word(hold_regs[start++]);
137
138         return MB_OK;
139 }
140
141 static mb_exception write_single_reg(uint16_t reg, uint16_t val)
142 {
143         if (reg < MB_HOLD_REGS_BASE
144                 || reg >= MB_HOLD_REGS_BASE + MB_N_HOLD_REGS)
145                 return MB_ILLEGAL_ADDR;
146
147         if (!hold_reg_is_valid(reg, val))
148                 return MB_ILLEGAL_VAL;
149
150         reg -= MB_HOLD_REGS_BASE;
151         hold_regs[reg] = val;
152 #if MB_N_HOLD_REGS_EEPROM > 0
153         if (reg < MB_N_HOLD_REGS_EEPROM)
154                 eeprom_write_word(&hold_regs_ee[reg], val);
155 #endif
156         put_word(reg + MB_HOLD_REGS_BASE);
157         put_word(val);
158
159         return MB_OK;
160 }
161
162 uint8_t modbus_poll()
163 {
164         bufptr_t packet_len;
165         uint16_t crc;
166         uint8_t rv;
167
168         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
169                 if (transmitting)
170                         return 0;
171
172                 if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving
173                         return 0;
174
175                 if (buf_len < 4) { // too short (or not for us)
176                         buf_len = 0;
177                         return 0;
178                 }
179
180                 transmitting = 1; // disable further reads
181                 packet_len = buf_len;
182         }
183
184         crc = compute_crc(buffer, packet_len - 2);
185
186         if ((crc & 0xFF) != buffer[packet_len-2]
187                 || (crc >> 8) != buffer[packet_len-1]) { // bad CRC
188                 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
189                         transmitting = 0;
190                         buf_len = 0;
191                 }
192                 return 1;
193         }
194
195         packet_len -= 2; // strip the CRC
196         buf_len = 2; // keep the first two bytes (unit ID and function) for TX
197
198         rv = MB_ILLEGAL_FUNC;
199
200         switch (buffer[1]) { // function
201         case 3:
202                 if (packet_len == 6)
203                         rv = read_holding_regs(
204                                 get_word(buffer, 2),
205                                 get_word(buffer, 4)
206                         );
207                 break;
208         case 6:
209                 if (packet_len == 6)
210                         rv = write_single_reg(
211                                 get_word(buffer, 2),
212                                 get_word(buffer, 4)
213                         );
214                 break;
215         }
216         
217         if (rv)
218                 make_exception(rv);
219
220         // append the CRC
221         crc = compute_crc(buffer, buf_len);
222         put_byte(crc & 0xFF);
223         put_byte(crc >> 8);
224
225         // send out the reply
226         tx_ptr = 0;
227         ctl_pin_on();
228         UCSR0B |= _BV(UDRIE0);
229
230         return 1;
231 }
232
233 ISR(USART_RX_vect)
234 {
235         uint8_t rx_byte = UDR0;
236         clock_t now = get_clock();
237
238         if (transmitting) // how did we get here? discard it
239                 goto out;
240
241         if (buf_len && buffer[0] != mb_unit_id) // not for us
242                 goto out;
243
244         if (buf_len == BUFSIZE) { // overrun - discard the packet
245                 buffer[0] = 0xFF;
246                 buf_len = 1;
247                 goto out;
248         }
249
250         if (now - last_rx >= REQ_TIMEOUT) { // new packet; start over
251                 buf_len = 0;
252         }
253
254         // TODO: we can probably calculate the CRC here as well
255         buffer[buf_len++] = rx_byte;
256 out:
257         last_rx = now;
258 }
259
260 ISR(USART_TX_vect)
261 {
262         UCSR0B &= ~_BV(TXCIE0); // disable further IRQs
263         ctl_pin_off();
264         buf_len = 0;
265         transmitting = 0; // enable receiving
266 }
267
268 ISR(USART_UDRE_vect)
269 {
270         if (tx_ptr >= buf_len) {
271                 UCSR0A |= _BV(TXC0); // clear the pending TXC flag
272                 UCSR0B |= _BV(TXCIE0); // enable xmit complete irq
273                 UCSR0B &= ~_BV(UDRIE0); // disable ourselves
274         } else {
275                 UDR0 = buffer[tx_ptr++];
276         }
277 }