+/*
+ * Generic MODBUS Library for STM32
+ *
+ * (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
+#include "config.h"
#include "util.h"
#include "modbus.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,
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
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 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);
+ 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);
- gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO1);
- gpio_clear(GPIOA, GPIO1);
+ gpio_clear(MODBUS_TXEN_GPIO_PORT, MODBUS_TXEN_GPIO_PIN);
- usart_set_baudrate(USART2, 19200);
- usart_set_databits(USART2, 9);
- usart_set_stopbits(USART2, USART_STOPBITS_1);
- // usart_set_parity(USART2, USART_PARITY_NONE);
- usart_set_parity(USART2, USART_PARITY_EVEN);
- usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
+ 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);
rx_init();
- nvic_enable_irq(NVIC_USART2_IRQ);
- usart_enable(USART2);
+ 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);
+ 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 (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) {
rx_bad = 1;
- } else 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;
}
}
} 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();
}
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)
for (id = range_min; id <= range_max; id++) {
if (modbus_id_strings[id]) {
byte len = strlen(modbus_id_strings[id]);
- byte remains = TX_BUFSIZE - 4 - tx_size; // 2 for CRC, 2 for object header
+ 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])
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;
return;
}
- if (rx_buf[0] == MB_OUR_ADDRESS) {
+ if (rx_buf[0] == MODBUS_OUR_ADDRESS) {
// Frame addressed to us: process and reply
process_frame();
tx_init();