]> mj.ucw.cz Git - home-hw.git/blobdiff - test-modbus/modbus.c
Modbus: Genericized debugging
[home-hw.git] / test-modbus / modbus.c
index 24a2589eb5e1770e6af7a51b32b0dc4d532df380..c6336460d307d1a8fe814e77b3159409ddaeb113 100644 (file)
@@ -1,11 +1,73 @@
+/*
+ *     Generic MODBUS Library for STM32
+ *
+ *     (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
 #include "util.h"
 #include "modbus.h"
 
+#include <stddef.h>
+#include <string.h>
+
 #include <libopencm3/cm3/nvic.h>
 #include <libopencm3/stm32/gpio.h>
 #include <libopencm3/stm32/usart.h>
 #include <libopencm3/stm32/timer.h>
 
+/*** Configuration ***/
+
+// You should set the following parameters in config.h
+
+// USART (pins are expected to be configured by the caller)
+// #define MODBUS_USART USART2
+// #define MODBUS_NVIC_USART_IRQ NVIC_USART2_IRQ
+// #define MODBUS_USART_ISR usart2_isr
+
+// GPIO pin for transmitter enable (pins is expected to be configured by the caller)
+// #define MODBUS_TXEN_GPIO_PORT GPIOA
+// #define MODBUS_TXEN_GPIO_PIN GPIO1
+
+// Timer
+// #define MODBUS_TIMER TIM2
+// #define MODBUS_NVIC_TIMER_IRQ NVIC_TIM2_IRQ
+// #define MODBUS_TIMER_ISR tim2_isr
+
+// Slave address we are responding at
+// #define MODBUS_OUR_ADDRESS 42
+
+// Baud rate
+#ifndef MODBUS_BAUD_RATE
+#define MODBUS_BAUD_RATE 19200
+#endif
+
+// CPU clock frequency
+// #define CPU_CLOCK_MHZ 72
+
+// Receive buffer size (standard specifies 256 bytes, you can make it shorter if necessary)
+#ifndef MODBUS_RX_BUFSIZE
+#define MODBUS_RX_BUFSIZE 256
+#endif
+
+// Transmit buffer size (standard specifies 256 bytes, you can make it shorter if necessary)
+#ifndef MODBUS_TX_BUFSIZE
+#define MODBUS_TX_BUFSIZE 256
+#endif
+
+// Receive timeout in microseconds
+#ifndef MODBUS_RX_TIMEOUT
+#if MODBUS_BAUD_RATE <= 19200
+// For low baud rates, the standard specifies timeout of 1.5 character times
+// (1 character = start bit + 8 data bits + parity bit + stop bit = 11 bits)
+#define MODBUS_RX_TIMEOUT (1000000*11*3/2/MODBUS_BAUD_RATE)
+#else
+// For high rates, the timeout is fixed to 750 μs
+#define MODBUS_RX_TIMEOUT 750
+#endif
+#endif
+
+/*** State ***/
+
 enum mb_state {
        STATE_RX,
        STATE_RX_DONE,
@@ -15,10 +77,7 @@ enum mb_state {
        STATE_TX_DONE,
 };
 
-#define RX_BUFSIZE 256
-#define TX_BUFSIZE 256
-
-static byte rx_buf[RX_BUFSIZE];
+static byte rx_buf[MODBUS_RX_BUFSIZE];
 static u16 rx_size;
 static byte rx_bad;
 static byte state;             // STATE_xxx
@@ -26,151 +85,148 @@ static byte state;                // STATE_xxx
 static byte *rx_frame;
 static byte *rx_frame_end;
 
-static byte tx_buf[TX_BUFSIZE];
+static byte tx_buf[MODBUS_TX_BUFSIZE];
 static u16 tx_size;
 static u16 tx_pos;
 
-#define MB_OUR_ADDRESS 42
+static bool check_frame(void);
+static void process_frame(void);
 
-static void UNUSED xx_write_char(uint c)       // FIXME
-{
-       usart_set_mode(USART2, USART_MODE_TX);
-       gpio_set(GPIOA, GPIO1);
-       usart_send_blocking(USART2, c);
-       while (!usart_get_flag(USART2, USART_SR_TC))
-               ;
-       gpio_clear(GPIOA, GPIO1);
-       usart_set_mode(USART2, USART_MODE_RX);
-}
+/*** Low-level layer ***/
 
 static void rx_init(void)
 {
        state = STATE_RX;
        rx_size = 0;
        rx_bad = 0;
-       usart_set_mode(USART2, USART_MODE_RX);
-       usart_enable_rx_interrupt(USART2);
+       usart_set_mode(MODBUS_USART, USART_MODE_RX);
+       usart_enable_rx_interrupt(MODBUS_USART);
 }
 
 static void rx_done(void)
 {
        state = STATE_RX_DONE;
-       usart_disable_rx_interrupt(USART2);
+       usart_disable_rx_interrupt(MODBUS_USART);
 }
 
 static void tx_init(void)
 {
        state = STATE_TX;
        tx_pos = 0;
-       gpio_set(GPIOA, GPIO1);
-       usart_set_mode(USART2, USART_MODE_TX);
-       usart_enable_tx_interrupt(USART2);
+       gpio_set(MODBUS_TXEN_GPIO_PORT, MODBUS_TXEN_GPIO_PIN);
+       usart_set_mode(MODBUS_USART, USART_MODE_TX);
+       usart_enable_tx_interrupt(MODBUS_USART);
 }
 
 static void tx_done(void)
 {
        state = STATE_TX_DONE;
-       // usart_disable_tx_interrupt(USART2);          // Already done by irq handler
-       gpio_clear(GPIOA, GPIO1);
+       // usart_disable_tx_interrupt(MODBUS_USART);            // Already done by irq handler
+       gpio_clear(MODBUS_TXEN_GPIO_PORT, MODBUS_TXEN_GPIO_PIN);
 }
 
 void modbus_init(void)
 {
-       timer_set_prescaler(TIM2, 71);          // 1 tick = 1 μs
-       timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
-       timer_update_on_overflow(TIM2);
-       timer_disable_preload(TIM2);
-       timer_one_shot_mode(TIM2);
-       timer_enable_irq(TIM2, TIM_DIER_UIE);
-       nvic_enable_irq(NVIC_TIM2_IRQ);
-
-       gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_USART2_RX);
-       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART2_TX);
-
-       gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO1);
-       gpio_clear(GPIOA, GPIO1);
-
-       usart_set_baudrate(USART2, 19200);
-       usart_set_databits(USART2, 8);
-       usart_set_stopbits(USART2, USART_STOPBITS_1);
-       usart_set_parity(USART2, USART_PARITY_NONE);
-       // usart_set_parity(USART2, USART_PARITY_EVEN); // FIXME
-       usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
+       timer_set_prescaler(MODBUS_TIMER, CPU_CLOCK_MHZ-1);     // 1 tick = 1 μs
+       timer_set_mode(MODBUS_TIMER, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
+       timer_update_on_overflow(MODBUS_TIMER);
+       timer_disable_preload(MODBUS_TIMER);
+       timer_one_shot_mode(MODBUS_TIMER);
+       timer_enable_irq(MODBUS_TIMER, TIM_DIER_UIE);
+       nvic_enable_irq(MODBUS_NVIC_TIMER_IRQ);
 
-       rx_init();
+       gpio_clear(MODBUS_TXEN_GPIO_PORT, MODBUS_TXEN_GPIO_PIN);
 
-       nvic_enable_irq(NVIC_USART2_IRQ);
-       usart_enable(USART2);
+       usart_set_baudrate(MODBUS_USART, MODBUS_BAUD_RATE);
+       usart_set_databits(MODBUS_USART, 9);
+       usart_set_stopbits(MODBUS_USART, USART_STOPBITS_1);
+       usart_set_parity(MODBUS_USART, USART_PARITY_EVEN);
+       usart_set_flow_control(MODBUS_USART, USART_FLOWCONTROL_NONE);
 
-#if 0
-       u32 xxx = USART_CR1(USART2);
-       usart_set_mode(USART2, USART_MODE_TX);
-       gpio_set(GPIOA, GPIO1);
-       debug_printf("%08x\n", xxx);
-       xx_write_char('\n');
-#endif
+       rx_init();
+
+       nvic_enable_irq(MODBUS_NVIC_USART_IRQ);
+       usart_enable(MODBUS_USART);
 }
 
-void usart2_isr(void)
+void MODBUS_USART_ISR(void)
 {
-       u32 status = USART_SR(USART2);
-
-       // FIXME: Optimize
+       u32 status = USART_SR(MODBUS_USART);
 
        if (status & USART_SR_RXNE) {
-               uint ch = usart_recv(USART2);
+               uint ch = usart_recv(MODBUS_USART);
                if (state == STATE_RX) {
-#if 0
                        if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) {
                                rx_bad = 1;
-                       } else
-#endif
-                       if (rx_size < RX_BUFSIZE) {
+                       } else if (rx_size < MODBUS_RX_BUFSIZE) {
                                rx_buf[rx_size++] = ch;
                        } else {
                                // Frame too long
                                rx_bad = 2;
                        }
-                       timer_set_period(TIM2, 7500);   // 0.75 ms timeout for end of frame (FIXME: right value?)
-                       timer_generate_event(TIM2, TIM_EGR_UG);
-                       timer_enable_counter(TIM2);
+                       timer_set_period(MODBUS_TIMER, MODBUS_RX_TIMEOUT);
+                       timer_generate_event(MODBUS_TIMER, TIM_EGR_UG);
+                       timer_enable_counter(MODBUS_TIMER);
                }
        }
 
        if (state == STATE_TX) {
                if (status & USART_SR_TXE) {
                        if (tx_pos < tx_size) {
-                               usart_send(USART2, tx_buf[tx_pos++]);
+                               usart_send(MODBUS_USART, tx_buf[tx_pos++]);
                        } else {
                                // The transmitter is double-buffered, so at this moment, it is transmitting
                                // the last byte of the frame. Wait until transfer is completed.
-                               usart_disable_tx_interrupt(USART2);
-                               USART_CR1(USART2) |= USART_CR1_TCIE;
+                               usart_disable_tx_interrupt(MODBUS_USART);
+                               USART_CR1(MODBUS_USART) |= USART_CR1_TCIE;
                                state = STATE_TX_LAST;
                        }
                }
-       }
-
-       if (state == STATE_TX_LAST) {
+       } else if (state == STATE_TX_LAST) {
                if (status & USART_SR_TC) {
                        // Transfer of the last byte is complete. Release the bus.
-                       USART_CR1(USART2) &= ~USART_CR1_TCIE;
+                       USART_CR1(MODBUS_USART) &= ~USART_CR1_TCIE;
                        tx_done();
                        rx_init();
                }
        }
 }
 
-void tim2_isr(void)
+void MODBUS_TIMER_ISR(void)
 {
-       if (TIM_SR(TIM2) & TIM_SR_UIF) {
-               TIM_SR(TIM2) &= ~TIM_SR_UIF;
+       if (TIM_SR(MODBUS_TIMER) & TIM_SR_UIF) {
+               TIM_SR(MODBUS_TIMER) &= ~TIM_SR_UIF;
                if (state == STATE_RX)
                        rx_done();
        }
 }
 
-// CRC tables
+void modbus_loop(void)
+{
+       if (state != STATE_RX_DONE)
+               return;
+       state = STATE_PROCESSING;
+
+       if (!check_frame()) {
+               rx_init();
+               return;
+       }
+
+       if (rx_buf[0] == MODBUS_OUR_ADDRESS) {
+               // Frame addressed to us: process and reply
+               process_frame();
+               tx_init();
+       } else if (rx_buf[0] == 0x00) {
+               // Broadcast frame: process, but do not reply
+               process_frame();
+               rx_init();
+       } else {
+               // Somebody else's frame: discard
+               rx_init();
+       }
+}
+
+/** CRC ***/
 
 static const byte crc_hi[] = {
        0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
@@ -243,6 +299,8 @@ static u16 crc16(byte *buf, u16 len)
        return (hi << 8 | lo);
 }
 
+/*** High-level layer ***/
+
 static bool check_frame(void)
 {
        if (rx_bad) {
@@ -326,7 +384,7 @@ static void write_u16(u16 v)
 static bool body_fits(uint body_len)
 {
        // body_len excludes slave address, function code, and CRC
-       return (2 + body_len + 2 <= TX_BUFSIZE);
+       return (2 + body_len + 2 <= MODBUS_TX_BUFSIZE);
 }
 
 static void report_error(byte code)
@@ -508,12 +566,88 @@ static void func_read_write_multiple_registers(void)
                modbus_get_holding_register(read_start + i);
 }
 
+static void func_encapsulated_interface_transport(void)
+{
+       if (read_remains() < 3 ||
+           read_byte() != 0x0e)
+               return report_error(ERR_ILLEGAL_DATA_VALUE);
+
+       byte action = read_byte();
+       byte id = read_byte();
+
+       byte range_min, range_max;
+       switch (action) {
+               case 1:
+                       // Streaming access to basic identification
+                       range_min = MODBUS_ID_VENDOR_NAME;
+                       range_max = MODBUS_ID_MAJOR_MINOR_REVISION;
+                       break;
+               case 2:
+                       // Streaming access to regular identification
+                       range_min = MODBUS_ID_VENDOR_URL;
+                       range_max = MODBUS_ID_USER_APP_NAME;
+                       break;
+               case 4:
+                       // Individual access
+                       if (id >= MODBUS_ID_MAX || !modbus_id_strings[id])
+                               return report_error(ERR_ILLEGAL_DATA_ADDRESS);
+                       range_min = range_max = id;
+                       break;
+               default:
+                       return report_error(ERR_ILLEGAL_DATA_VALUE);
+       }
+
+       if (action != 4) {
+               if (id < range_min || id > range_max)
+                       id = range_min;
+       }
+
+       write_byte(0x0e);       // Repeat a part of the request
+       write_byte(action);
+
+       // Conformity level
+       if (modbus_id_strings[MODBUS_ID_VENDOR_URL] ||
+           modbus_id_strings[MODBUS_ID_PRODUCT_NAME] ||
+           modbus_id_strings[MODBUS_ID_USER_APP_NAME])
+               write_byte(0x82);       // Regular identification, both stream and individual access supported
+       else
+               write_byte(0x81);       // Basic identification only
+
+       u16 more_follows_at = tx_size;
+       write_byte(0);          // More follows: so far not
+       write_byte(0);          // Next object ID: so far none
+       write_byte(0);          // Number of objects
+
+       for (id = range_min; id <= range_max; id++) {
+               if (modbus_id_strings[id]) {
+                       byte len = strlen(modbus_id_strings[id]);
+                       byte remains = MODBUS_TX_BUFSIZE - 4 - tx_size; // 2 for CRC, 2 for object header
+                       if (len > remains) {
+                               // If it is the only object, cut it
+                               if (!tx_buf[more_follows_at + 2])
+                                       len = remains;
+                               else {
+                                       // More follows, report the next ID
+                                       tx_buf[more_follows_at] = 0xff;
+                                       tx_buf[more_follows_at + 1] = id;
+                                       break;
+                               }
+                       }
+                       tx_buf[more_follows_at + 2] ++;
+                       write_byte(id);
+                       write_byte(len);
+                       memcpy(tx_buf + tx_size, modbus_id_strings[id], len);
+                       tx_size += len;
+               }
+       }
+}
+
 static void process_frame(void)
 {
        byte func = read_byte();
 
        // Prepare reply frame
-       tx_buf[0] = MB_OUR_ADDRESS;
+       tx_buf[0] = MODBUS_OUR_ADDRESS;
        tx_buf[1] = rx_buf[1];
        tx_size = 2;
 
@@ -548,6 +682,9 @@ static void process_frame(void)
                case FUNC_READ_WRITE_MULTIPLE_REGISTERS:
                        func_read_write_multiple_registers();
                        break;
+               case FUNC_ENCAPSULATED_INTERFACE_TRANSPORT:
+                       func_encapsulated_interface_transport();
+                       break;
                default:
                        report_error(ERR_ILLEGAL_FUNCTION);
        }
@@ -555,81 +692,3 @@ static void process_frame(void)
        // Finish reply frame
        write_u16(crc16(tx_buf, tx_size));
 }
-
-void modbus_loop(void)
-{
-       if (state != STATE_RX_DONE) {
-               // gpio_toggle(GPIOC, GPIO13);
-               return;
-       }
-       state = STATE_PROCESSING;
-       gpio_toggle(GPIOC, GPIO13);
-
-       if (!check_frame()) {
-               rx_init();
-               return;
-       }
-
-       if (rx_buf[0] == MB_OUR_ADDRESS) {
-               // Frame addressed to us: process and reply
-               process_frame();
-               tx_init();
-       } else if (rx_buf[0] == 0x00) {
-               // Broadcast frame: process, but do not reply
-               process_frame();
-               rx_init();
-       } else {
-               // Somebody else's frame: discard
-               rx_init();
-       }
-}
-
-/*** Callbacks ***/
-
-bool modbus_check_discrete_input(u16 addr UNUSED)
-{
-       return false;
-}
-
-bool modbus_get_discrete_input(u16 addr UNUSED)
-{
-       return false;
-}
-
-bool modbus_check_coil(u16 addr UNUSED)
-{
-       return false;
-}
-
-bool modbus_get_coil(u16 addr UNUSED)
-{
-       return false;
-}
-
-void modbus_set_coil(u16 addr UNUSED, bool value UNUSED)
-{
-}
-
-bool modbus_check_input_register(u16 addr UNUSED)
-{
-       return false;
-}
-
-u16 modbus_get_input_register(u16 addr UNUSED)
-{
-       return 0;
-}
-
-bool modbus_check_holding_register(u16 addr UNUSED)
-{
-       return (addr == 0);
-}
-
-u16 modbus_get_holding_register(u16 addr UNUSED)
-{
-       return 0xbeef;
-}
-
-void modbus_set_holding_register(u16 addr UNUSED, u16 value UNUSED)
-{
-}