]> www.fi.muni.cz Git - openparking.git/commitdiff
Preliminary modbus rtu implementation.
authorJan "Yenya" Kasprzak <kas@fi.muni.cz>
Wed, 20 May 2015 23:27:06 +0000 (01:27 +0200)
committerJan "Yenya" Kasprzak <kas@fi.muni.cz>
Wed, 20 May 2015 23:28:32 +0000 (01:28 +0200)
firmware/Makefile
firmware/firmware.c
firmware/modbus.c [new file with mode: 0755]
firmware/modbus.h [new file with mode: 0755]

index 40ec933077a1ae7a717edf4bb4fb6268c11d0f48..9cfa5279c7355de6bb0f0238d1d470d965f5e9ef 100644 (file)
@@ -1,6 +1,5 @@
-
 PROGRAM=firmware
-SRC=firmware.c rs485.c
+SRC=firmware.c modbus.c
 OBJ=$(SRC:.c=.o)
 
 MCU=atmega328p
index 67508e62e8e0ce0ae09e9d93ddcf1634ed403e4f..9b925210d48cbceee072333ea204e34a77cbfaab 100755 (executable)
@@ -2,7 +2,7 @@
 #include <avr/interrupt.h>
 #include <util/delay.h>
 #include <stdio.h>
-#include "rs485.h"
+#include "modbus.h"
 
 #define TIMEOUT 0x2FF
 
@@ -104,9 +104,7 @@ static void led_set(uint8_t led, uint8_t state)
 
 int main()
 {
-       char obuf[120];
-
-       rs485_init();
+       modbus_init();
 
        // output pins
        DDRD |= _BV(PD7); // Trig D
@@ -123,15 +121,8 @@ int main()
        sei();
 
        while(1) {
-               do_measurements();
-
-               sprintf(obuf, "%3d %3d %3d %3d %3d %3d %3d %3d %3d %3d %3d %3d\r\n",
-                       distances[0], distances[1], distances[2],
-                       distances[3], distances[4], distances[5],
-                       distances[6], distances[7], distances[8],
-                       distances[9], distances[10], distances[11]);
-
-               rs485_send(obuf);
+               // do_measurements();
+               modbus_poll();
                led_set(0,
                        distances[4] > 100 || distances[11] > 100);
        }
diff --git a/firmware/modbus.c b/firmware/modbus.c
new file mode 100755 (executable)
index 0000000..937d9d9
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Loosely modelled after AVR-RS485 by Yoshinori Kohyama (http://algobit.jp/),
+ * available at https://github.com/kohyama/AVR-RS485/
+ *
+ * All bugs by Jan "Yenya" Kasprzak <kas@fi.muni.cz> :-)
+ */
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/atomic.h>
+#include <util/delay.h>
+
+#define BUFSIZE 128    // must be a power of two
+
+// configure the control pin
+#define ctl_pin_setup()        do { DDRD  |=  _BV(PD2); } while (0)
+#define ctl_pin_on()   do { PORTD |=  _BV(PD2); } while (0)
+#define ctl_pin_off()  do { PORTD &= ~_BV(PD2); } while (0)
+
+#define BUFMASK (BUFSIZE-1)
+#if (BUFSIZE & BUFMASK)
+#error BUFSIZE must be a power of two
+#endif
+
+#if BUFSIZE > 255
+typedef uint16_t bufptr_t;
+#else
+typedef uint8_t  bufptr_t;
+#endif
+
+#define bufptr_inc(x)  ((x + 1) & BUFMASK)
+
+static volatile bufptr_t rx_bytes, tx_head, tx_tail;
+static volatile uint8_t rxbuf[BUFSIZE], txbuf[BUFSIZE];
+static volatile uint16_t last_rx;
+static volatile uint8_t unit_id;
+
+#define UART_BAUD      9600
+#define UBRR_VAL        ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
+#define wait_one_byte()        _delay_us(10*1000000/UART_BAUD)
+
+#define get_clock()    (TCNT1)
+#define CLOCK_SPEED    (F_CPU/1024)
+/*
+ * According to Wikipedia, it is indeed 28 bits = 3.5 bytes without
+ * start- and stopbits.
+ */
+#define TIMEOUT                (28*CLOCK_SPEED/UART_BAUD)
+
+void modbus_init()
+{
+       rx_bytes = 0;
+       tx_head = tx_tail = 0;
+
+       unit_id = 42;
+
+       ctl_pin_off();
+       ctl_pin_setup();
+
+       UBRR0 = UBRR_VAL;
+       UCSR0A = 0;
+       UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
+       UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
+}
+
+void rs485_send(char *p)
+{
+       bufptr_t next;
+
+       if (*p == '\0')
+               return;
+
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+               next = bufptr_inc(tx_head);
+               while (next != tx_tail && *p != '\0') {
+                       txbuf[tx_head] = *p++;
+                       tx_head = next;
+                       next = bufptr_inc(tx_head);
+               }
+               ctl_pin_on();
+               UCSR0B |= _BV(UDRIE0);
+       }
+}
+
+static uint16_t compute_crc(volatile uint8_t *buf, bufptr_t len)
+{
+       bufptr_t i;
+       uint16_t crc = 0xFFFF;
+
+       for (i = 0; i < len; i++) {
+               uint8_t j;
+               crc ^= (uint16_t)(buf[i]);
+               for(j = 0; j < 8; j++) {
+                       if (crc & 0x0001) {
+                               crc >>= 1;
+                               crc ^= 0xA001;
+                       } else {
+                               crc >>= 1;
+                       }
+               }
+       }
+
+       return crc;
+}
+
+static void make_exception(uint8_t func, uint8_t code)
+{
+       txbuf[tx_head++] = unit_id;
+       txbuf[tx_head++] = func | 0x80;
+       txbuf[tx_head++] = code;
+}
+
+void modbus_poll()
+{
+       bufptr_t packet_len;
+       uint16_t crc;
+
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+               if (rx_bytes == 0) // nothing received yet
+                       return;
+
+               if (get_clock() - last_rx < TIMEOUT) // still receiving
+                       return;
+
+               if (rx_bytes < 4) { // too short
+                       rx_bytes = 0;
+                       return;
+               }
+
+               if (rxbuf[0] != unit_id) { // not for myself
+                       rx_bytes = 0;
+                       return;
+               }
+
+               if (tx_tail) { // still sending?
+                       rx_bytes = 0;
+                       return;
+               }
+
+               packet_len = rx_bytes; // make a copy
+       }
+
+       crc = compute_crc(rxbuf, packet_len - 2);
+
+       if ((crc & 0xFF) != rxbuf[packet_len-2]
+               || (crc >> 8) != rxbuf[packet_len-1]) // bad crc
+               goto out;
+
+       tx_head = 0;
+
+       switch (rxbuf[1]) { // function
+       default:
+               make_exception(rxbuf[1], 1); // illegal function
+       }
+
+send:
+       if (tx_head) {
+               crc = compute_crc(txbuf, tx_head);
+               txbuf[tx_head++] = crc & 0xFF;
+               txbuf[tx_head++] = crc >> 8;
+
+               tx_tail = 0;
+
+               ctl_pin_on();
+               UCSR0B |= _BV(UDRIE0);
+       }
+out:
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+               rx_bytes = 0;
+       }
+}
+
+ISR(USART_RX_vect)
+{
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+               rxbuf[rx_bytes] = UDR0;
+               if (rx_bytes + 1 < BUFSIZE) // ignore overruns
+                       rx_bytes++;
+               last_rx = get_clock();
+       }
+}
+
+ISR(USART_UDRE_vect)
+{
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+               if (tx_head == tx_tail) {
+                       UCSR0B &= ~_BV(UDRIE0);
+                       tx_tail = tx_head = 0;
+                       wait_one_byte(); // FIXME: too long busy-wait
+                       ctl_pin_off();
+               } else {
+                       UDR0 = txbuf[tx_tail];
+                       tx_tail = bufptr_inc(tx_tail);
+               }
+       }
+}
diff --git a/firmware/modbus.h b/firmware/modbus.h
new file mode 100755 (executable)
index 0000000..4218317
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef MODBUS_H__
+#define MODBUS_H__ 1
+
+/*
+ * Loosely modelled after AVR-RS485 by Yoshinori Kohyama (http://algobit.jp/),
+ * available at https://github.com/kohyama/AVR-RS485/
+ *
+ * All bugs by Jan "Yenya" Kasprzak <kas@fi.muni.cz> :-)
+ */
+
+void modbus_init();
+void modbus_poll();
+
+#endif /* MODBUS_H__ */