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