]> www.fi.muni.cz Git - openparking.git/blob - firmware/modbus.c
00a9370a05bdaa872ce14b38f95de35d194d275b
[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 (buf_len == 0) // nothing received yet
173                         return 0;
174
175                 if (get_clock() - last_rx < REQ_TIMEOUT) // still receiving
176                         return 0;
177
178                 if (buf_len < 4) { // too short
179                         buf_len = 0;
180                         return 0;
181                 }
182
183                 if (buffer[0] != mb_unit_id) { // not for myself
184                         buf_len = 0;
185                         return 0;
186                 }
187
188                 transmitting = 1; // disable further reads
189                 packet_len = buf_len;
190         }
191
192         crc = compute_crc(buffer, packet_len - 2);
193
194         if ((crc & 0xFF) != buffer[packet_len-2]
195                 || (crc >> 8) != buffer[packet_len-1]) { // bad CRC
196                 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
197                         transmitting = 0;
198                         buf_len = 0;
199                 }
200                 return 1;
201         }
202
203         packet_len -= 2; // strip the CRC
204         buf_len = 2; // keep the first two bytes (unit ID and function) for TX
205
206         rv = MB_ILLEGAL_FUNC;
207
208         switch (buffer[1]) { // function
209         case 3:
210                 if (packet_len == 5)
211                         rv = read_holding_regs(
212                                 get_word(buffer, 2),
213                                 get_word(buffer, 4)
214                         );
215                 break;
216         case 6:
217                 if (packet_len == 5)
218                         rv = write_single_reg(
219                                 get_word(buffer, 2),
220                                 get_word(buffer, 4)
221                         );
222                 break;
223         }
224         
225         if (rv)
226                 make_exception(rv);
227
228         // append the CRC
229         crc = compute_crc(buffer, buf_len);
230         put_byte(crc & 0xFF);
231         put_byte(crc >> 8);
232
233         // send out the reply
234         tx_ptr = 0;
235         ctl_pin_on();
236         UCSR0B |= _BV(UDRIE0);
237
238         return 1;
239 }
240
241 ISR(USART_RX_vect)
242 {
243         uint8_t rx_byte = UDR0;
244
245         if (transmitting) // discard it
246                 return;
247
248         buffer[buf_len] = rx_byte;
249
250         if (buf_len + 1 < BUFSIZE) // ignore overruns
251                 buf_len++;
252
253         last_rx = get_clock();
254 }
255
256 ISR(USART_TX_vect)
257 {
258         UCSR0B &= ~_BV(TXCIE0); // disable further IRQs
259         ctl_pin_off();
260         buf_len = 0;
261         transmitting = 0; // enable receiving
262 }
263
264 ISR(USART_UDRE_vect)
265 {
266         if (tx_ptr+1 >= buf_len) {
267                 UCSR0B |= _BV(TXCIE0); // enable xmit complete irq
268                 UCSR0B &= ~_BV(UDRIE0); // disable ourselves
269         } else {
270                 UDR0 = buffer[tx_ptr++];
271         }
272 }