]> www.fi.muni.cz Git - openparking.git/blob - firmware/modbus.c
Preliminary modbus rtu implementation.
[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/io.h>
9 #include <avr/interrupt.h>
10 #include <util/atomic.h>
11 #include <util/delay.h>
12
13 #define BUFSIZE 128     // must be a power of two
14
15 // configure the control pin
16 #define ctl_pin_setup() do { DDRD  |=  _BV(PD2); } while (0)
17 #define ctl_pin_on()    do { PORTD |=  _BV(PD2); } while (0)
18 #define ctl_pin_off()   do { PORTD &= ~_BV(PD2); } while (0)
19
20 #define BUFMASK (BUFSIZE-1)
21 #if (BUFSIZE & BUFMASK)
22 #error BUFSIZE must be a power of two
23 #endif
24
25 #if BUFSIZE > 255
26 typedef uint16_t bufptr_t;
27 #else
28 typedef uint8_t  bufptr_t;
29 #endif
30
31 #define bufptr_inc(x)   ((x + 1) & BUFMASK)
32
33 static volatile bufptr_t rx_bytes, tx_head, tx_tail;
34 static volatile uint8_t rxbuf[BUFSIZE], txbuf[BUFSIZE];
35 static volatile uint16_t last_rx;
36 static volatile uint8_t unit_id;
37
38 #define UART_BAUD       9600
39 #define UBRR_VAL        ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
40 #define wait_one_byte() _delay_us(10*1000000/UART_BAUD)
41
42 #define get_clock()     (TCNT1)
43 #define CLOCK_SPEED     (F_CPU/1024)
44 /*
45  * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
46  * start- and stopbits.
47  */
48 #define TIMEOUT         (28*CLOCK_SPEED/UART_BAUD)
49
50 void modbus_init()
51 {
52         rx_bytes = 0;
53         tx_head = tx_tail = 0;
54
55         unit_id = 42;
56
57         ctl_pin_off();
58         ctl_pin_setup();
59
60         UBRR0 = UBRR_VAL;
61         UCSR0A = 0;
62         UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
63         UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
64 }
65
66 void rs485_send(char *p)
67 {
68         bufptr_t next;
69
70         if (*p == '\0')
71                 return;
72
73         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
74                 next = bufptr_inc(tx_head);
75                 while (next != tx_tail && *p != '\0') {
76                         txbuf[tx_head] = *p++;
77                         tx_head = next;
78                         next = bufptr_inc(tx_head);
79                 }
80                 ctl_pin_on();
81                 UCSR0B |= _BV(UDRIE0);
82         }
83 }
84
85 static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
86 {
87         bufptr_t i;
88         uint16_t crc = 0xFFFF;
89
90         for (i = 0; i < len; i++) {
91                 uint8_t j;
92                 crc ^= (uint16_t)(buf[i]);
93                 for(j = 0; j < 8; j++) {
94                         if (crc & 0x0001) {
95                                 crc >>= 1;
96                                 crc ^= 0xA001;
97                         } else {
98                                 crc >>= 1;
99                         }
100                 }
101         }
102
103         return crc;
104 }
105
106 static void make_exception(uint8_t func, uint8_t code)
107 {
108         txbuf[tx_head++] = unit_id;
109         txbuf[tx_head++] = func | 0x80;
110         txbuf[tx_head++] = code;
111 }
112
113 void modbus_poll()
114 {
115         bufptr_t packet_len;
116         uint16_t crc;
117
118         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
119                 if (rx_bytes == 0) // nothing received yet
120                         return;
121
122                 if (get_clock() - last_rx < TIMEOUT) // still receiving
123                         return;
124
125                 if (rx_bytes < 4) { // too short
126                         rx_bytes = 0;
127                         return;
128                 }
129
130                 if (rxbuf[0] != unit_id) { // not for myself
131                         rx_bytes = 0;
132                         return;
133                 }
134
135                 if (tx_tail) { // still sending?
136                         rx_bytes = 0;
137                         return;
138                 }
139
140                 packet_len = rx_bytes; // make a copy
141         }
142
143         crc = compute_crc(rxbuf, packet_len - 2);
144
145         if ((crc & 0xFF) != rxbuf[packet_len-2]
146                 || (crc >> 8) != rxbuf[packet_len-1]) // bad crc
147                 goto out;
148
149         tx_head = 0;
150
151         switch (rxbuf[1]) { // function
152         default:
153                 make_exception(rxbuf[1], 1); // illegal function
154         }
155
156 send:
157         if (tx_head) {
158                 crc = compute_crc(txbuf, tx_head);
159                 txbuf[tx_head++] = crc & 0xFF;
160                 txbuf[tx_head++] = crc >> 8;
161
162                 tx_tail = 0;
163
164                 ctl_pin_on();
165                 UCSR0B |= _BV(UDRIE0);
166         }
167 out:
168         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
169                 rx_bytes = 0;
170         }
171 }
172
173 ISR(USART_RX_vect)
174 {
175         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
176                 rxbuf[rx_bytes] = UDR0;
177                 if (rx_bytes + 1 < BUFSIZE) // ignore overruns
178                         rx_bytes++;
179                 last_rx = get_clock();
180         }
181 }
182
183 ISR(USART_UDRE_vect)
184 {
185         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
186                 if (tx_head == tx_tail) {
187                         UCSR0B &= ~_BV(UDRIE0);
188                         tx_tail = tx_head = 0;
189                         wait_one_byte(); // FIXME: too long busy-wait
190                         ctl_pin_off();
191                 } else {
192                         UDR0 = txbuf[tx_tail];
193                         tx_tail = bufptr_inc(tx_tail);
194                 }
195         }
196 }