RT-Thread中已经有不少Modbus相关在线软件包,但总体应用起来还是相对复杂,所以在RT-Thread基于AT32单片机的485应用开发(二)的基础上实现了一个极简Modbus从机,支持Modbus功能码01,02,03,04,05,06,15,16。
在具体项目中只要实现下面几个函数就可以直接用了,不过需要注意多线程访问变量加锁问题。
rt_weak rt_err_t mb_write_coil_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak rt_err_t mb_write_holding_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak unsigned short mb_read_coil_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_di_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_holding_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_input_cb(unsigned short addr){return 0;}
只需要在项目中增加两个文件,就可以实现基于RS485的ModbusRTU设备。
1. mb_core.c
#include <rtthread.h>
static unsigned int MB_COIL_NUM = 16;
static unsigned int MB_DI_NUM = 16;
static unsigned int MB_HOLDING_NUM = 32;
static unsigned int MB_INPUT_NUM = 32;
static unsigned char DEV_ADDR = 1;
rt_weak rt_err_t mb_write_coil_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak rt_err_t mb_write_holding_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak unsigned short mb_read_coil_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_di_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_holding_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_input_cb(unsigned short addr){return 0;}
/* 高位字节的 CRC 值 */
static unsigned char auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 };
/* 低位字节的 CRC 值 */
static char auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8,
0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5,
0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33,
0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E,
0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3,
0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5,
0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8,
0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4,
0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F,
0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A,
0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82,
0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 };
static unsigned short CRC16_ModBus(unsigned char *puchMsg, unsigned char usDataLen)
{
unsigned char uchCRCHi = 0xFF; /* CRC 的高字节初始化 */
unsigned char uchCRCLo = 0xFF; /* CRC 的低字节初始化 */
unsigned char uIndex; /* CRC 查询表索引 */
while (usDataLen--) /* 完成整个报文缓冲区 */
{
uIndex = uchCRCLo ^ *puchMsg++; /* 计算 CRC */
uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex];
uchCRCHi = auchCRCLo[uIndex];
}
return (uchCRCHi << 8 | uchCRCLo);
}
static int frame_available(unsigned char *buf, int len)
{
int data_len = 0, num;
if (len < 5)
return 0;
if (buf[1] == 1 || buf[1] == 2 || buf[1] == 3 || buf[1] == 4 || buf[1] == 5 || buf[1] == 6)
{
data_len = 4;
}
else if (buf[1] == 15)
{
num = ((buf[4] << 8) | buf[5]);
data_len = 4 + 1 + (num + 7) / 8;
}
else if (buf[1] == 16)
{
num = ((buf[4] << 8) | buf[5]);
data_len = 4 + 1 + num * 2;
}
else if (buf[1] & 0x80)
{
data_len = 1;
}
else
{
return -1;
}
if (len < data_len + 4)
return 0;
unsigned short crc = CRC16_ModBus(buf, 2 + data_len);
if (crc == (buf[2 + data_len] | ((buf[2 + data_len + 1]) << 8)))
{
return 1;
}
return -1;
}
void mb_slave_init(unsigned char slave_addr, int coil_num, int di_num, int holding_num, int input_num)
{
MB_COIL_NUM = coil_num;
MB_DI_NUM = di_num;
MB_HOLDING_NUM = holding_num;
MB_INPUT_NUM = input_num;
DEV_ADDR = slave_addr;
}
int mb_slave_process(unsigned char *rx_buf, int rx_len, unsigned char *tx_buf)
{
unsigned char error_code = 0;
unsigned short num, addr, val, ind = 0, crc16, bytes;
int ret = frame_available(rx_buf, rx_len);
//frame error
if (ret <= 0)
return ret;
//valid addr
if (rx_buf[0] != DEV_ADDR)
return 0;
//process rtu cmd
switch (rx_buf[1])
{
case 1: //read coil
case 2: //read di
addr = (rx_buf[2] << 8) | rx_buf[3];
num = (rx_buf[4] << 8) | rx_buf[5];
if (num < 1 || num > 0x7d0)
{
error_code = 3;
break;
}
if (addr + num > (rx_buf[1] == 1 ? MB_COIL_NUM : MB_DI_NUM))
{
error_code = 2;
break;
}
tx_buf[ind++] = DEV_ADDR;
tx_buf[ind++] = rx_buf[1];
bytes = (num + 7) / 8;
tx_buf[ind++] = bytes;
for (int i = 0; i < bytes; i++)
{
tx_buf[ind + i] = 0;
}
for (int i = 0; i < num; i++)
{
if (rx_buf[1] == 1)//coil
{
val = mb_read_coil_cb(i + addr);
if (val)
tx_buf[ind + i / 8] |= 1 << (i % 8);
}
else//di
{
val = mb_read_di_cb(i + addr);
if (val)
tx_buf[ind + i / 8] |= 1 << (i % 8);
}
}
ind += bytes;
crc16 = CRC16_ModBus(tx_buf, ind);
tx_buf[ind++] = crc16;
tx_buf[ind++] = crc16 >> 8;
break;
case 3: //read holding
case 4: //read input
addr = (rx_buf[2] << 8) | rx_buf[3];
num = (rx_buf[4] << 8) | rx_buf[5];
if (num < 1 || num > 125)
{
error_code = 3;
break;
}
if (addr + num > (rx_buf[1] == 3 ? MB_HOLDING_NUM : MB_INPUT_NUM))
{
error_code = 2;
break;
}
tx_buf[ind++] = DEV_ADDR;
tx_buf[ind++] = rx_buf[1];
tx_buf[ind++] = num * 2;
for (int i = 0; i < num; i++)
{
if (rx_buf[1] == 3)//holding
{
val = mb_read_holding_cb(i + addr);
tx_buf[ind++] = val >> 8;
tx_buf[ind++] = val;
}
else//input
{
val = mb_read_input_cb(i + addr);
tx_buf[ind++] = val >> 8;
tx_buf[ind++] = val;
}
}
crc16 = CRC16_ModBus(tx_buf, ind);
tx_buf[ind++] = crc16;
tx_buf[ind++] = crc16 >> 8;
break;
case 5: //write single coil
case 6: //write single reg
addr = (rx_buf[2] << 8) | rx_buf[3];
val = (rx_buf[4] << 8) | rx_buf[5];
if (val != 0x0 && val != 0xff00 && rx_buf[1] == 5)
{
error_code = 3;
break;
}
if (addr> (rx_buf[1] == 5 ? MB_COIL_NUM : MB_HOLDING_NUM))
{
error_code = 2;
break;
}
for (int i = 0; i < 8; i++)
{
tx_buf[i] = rx_buf[i];
}
if (rx_buf[1] == 5)
{
if (0 != mb_write_coil_cb(addr, val))
error_code = 4;
}
else
{
if (0 != mb_write_holding_cb(addr, val))
error_code = 4;
}
ind = 8;
break;
case 15: //wrtie multi coils
addr = (rx_buf[2] << 8) | rx_buf[3];
num = (rx_buf[4] << 8) | rx_buf[5];
if (num < 1 || num > 0x7b || rx_buf[6] != (num + 7) / 8)
{
error_code = 3;
break;
}
if (addr + num > MB_COIL_NUM)
{
error_code = 2;
break;
}
for (ind = 0; ind < 6; ind++)
{
tx_buf[ind] = rx_buf[ind];
}
for (int i = 0; i < num; i++)
{
val = rx_buf[7 + i / 8] & (1 << (i % 8));
if (mb_write_coil_cb(addr + i, val) != 0)
error_code = 4;
}
crc16 = CRC16_ModBus(tx_buf, ind);
tx_buf[ind++] = crc16;
tx_buf[ind++] = crc16 >> 8;
break;
case 16: //write multi regs
addr = (rx_buf[2] << 8) | rx_buf[3];
num = (rx_buf[4] << 8) | rx_buf[5];
if (num < 1 || num > 0x7b || rx_buf[6] != num * 2)
{
error_code = 3;
break;
}
if (addr + num > MB_HOLDING_NUM)
{
error_code = 2;
break;
}
for (ind = 0; ind < 6; ind++)
{
tx_buf[ind] = rx_buf[ind];
}
for (int i = 0; i < num; i++)
{
val = (rx_buf[7 + 2 * i] << 8) | rx_buf[7 + 2 * i + 1];
if (0 != mb_write_holding_cb(addr + i, val))
error_code = 4;
}
crc16 = CRC16_ModBus(tx_buf, ind);
tx_buf[ind++] = crc16;
tx_buf[ind++] = crc16 >> 8;
break;
default:
if (rx_buf[1] & 0x80)
return 0;
else
error_code = 1;
break;
}
if (error_code != 0)
{
ind = 0;
tx_buf[ind++] = DEV_ADDR;
tx_buf[ind++] = rx_buf[1] | 0x80;
tx_buf[ind++] = error_code;
crc16 = CRC16_ModBus(tx_buf, ind);
tx_buf[ind++] = crc16;
tx_buf[ind++] = crc16 >> 8;
}
return ind;
}
2. rs_485_test.c
#include <rtthread.h>
#include <rtdevice.h>
/* 串口设备句柄 */
static rt_device_t serial;
/* 485控制引脚 */
static rt_base_t rs485_ctrl_pin = -1;
/* timeout receive */
static int serial_read_frame(rt_device_t dev, uint8_t *buf, int max_len, uint32_t idle_ms, int timeout_ms)
{
int rx_len = 0, rc;
uint32_t idle_time, timeout_time, cur_tick, last_tick;
timeout_time = rt_tick_from_millisecond(timeout_ms);
idle_time = rt_tick_from_millisecond(idle_ms);
cur_tick = rt_tick_get();
while ((rt_tick_get() - last_tick < idle_time && rx_len < max_len) || rx_len <= 0)
{
rc = rt_device_read(dev, rx_len, buf, max_len - rx_len);
if (rc > 0)
{
rx_len += rc;
last_tick = rt_tick_get();
}
else
{
rt_thread_mdelay(1);
}
if (rt_tick_get() - cur_tick > timeout_time && rx_len <= 0)
break;
}
return rx_len;
}
/* transmit with auto 485 pin ctrl */
static void serial_write_frame_rs485(rt_device_t dev, uint8_t *buf, int len, int bitrate, int ctrl_pin)
{
int ms = len * 10 * 1000 / bitrate + 2;
rt_pin_write(rs485_ctrl_pin, 1);
rt_hw_us_delay(10);
rt_device_write(dev, 0, buf, len);
rt_thread_mdelay(ms);
rt_pin_write(rs485_ctrl_pin, 0);
}
static void serial_thread_entry(void *parameter)
{
extern void mb_slave_init(unsigned char slave_addr, int coil_num, int di_num, int holding_num, int input_num);
extern int mb_slave_process(unsigned char *rx_buf, int rx_len, unsigned char *tx_buf);
rt_uint32_t rx_len, tx_len;
static unsigned char rx_buf[260];
static unsigned char tx_buf[260];
mb_slave_init(1,16,16,32,32);
while (1)
{
rx_len = serial_read_frame(serial, rx_buf, 260, 10, 1000);
if (rx_len <= 0)
continue;
tx_len = mb_slave_process(rx_buf, rx_len, tx_buf);
if(tx_len>0)
serial_write_frame_rs485(serial, rx_buf, rx_len, 115200, rs485_ctrl_pin);
/* 打印数据 */
rx_buf[rx_len] = '\0';
rt_kprintf("rx_len = %d\n", rx_len);
}
}
static int uart_485_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX] = "uart4";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
rt_kprintf("uart_name = %s\n", uart_name);
if (rt_strcmp(uart_name,"uart3") == 0)
{
rs485_ctrl_pin = rt_pin_get("PE.15");
rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
rt_pin_write(rs485_ctrl_pin, 0);
}
else if (rt_strcmp(uart_name,"uart4") == 0)
{
rs485_ctrl_pin = rt_pin_get("PA.15");
rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
rt_pin_write(rs485_ctrl_pin, 0);
}
else
{
return RT_ERROR;
}
/* 查找串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 以 DMA 接收及轮询发送方式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_RX_NON_BLOCKING | RT_DEVICE_FLAG_TX_NON_BLOCKING);
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_485_sample, uart device rs485 sample);
经实验测试,总体性能还错。对于简单Modbus设备,基本可以满足要求。