RT-Thread基于AT32单片机的485应用开发(三)Modbus从机

news/2024/12/22 19:56:34/

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 errorif (ret <= 0)return ret;//valid addrif (rx_buf[0] != DEV_ADDR)return 0;//process rtu cmdswitch (rx_buf[1]){case 1: //read coilcase 2: //read diaddr = (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 holdingcase 4: //read inputaddr = (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 coilcase 6: //write single regaddr = (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 coilsaddr = (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 regsaddr = (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;elseerror_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设备,基本可以满足要求。


http://www.ppmy.cn/news/1298713.html

相关文章

Python - 深夜数据结构与算法之 Two-Ended BFS

目录 一.引言 二.双向 BFS 简介 1.双向遍历示例 2.搜索模版回顾 三.经典算法实战 1.Word-Ladder [127] 2.Min-Gen-Mutation [433] 四.总结 一.引言 DFS、BFS 是常见的初级搜索方式&#xff0c;为了提高搜索效率&#xff0c;衍生了剪枝、双向 BFS 以及 A* 即启发式搜索…

C++:多态究竟是什么?为何能成为面向对象的重要手段之一?

C&#xff1a;多态究竟是什么&#xff1f;为何能成为面向对象的重要手段之一&#xff1f; 前言一、多态的概念二、多态的定义及实现2.1 多态的构成条件2. 2 虚函数2.3 虚函数的重写2.3.1 虚函数重写的例外1&#xff1a;协变(基类与派生类虚函数返回值类型不同)2.3.2 虚函数重写…

Git、TortoiseGit进阶

1.安装Git、TortoiseGit和汉化包 Git官网: Git TortoiseGit和汉化包: Download – TortoiseGit – Windows Shell Interface to Git 2.常用命令 创建仓库命令 git init初始化仓库git clone拷贝一份远程仓库,也就是下载一个项目。提交与修改 git add添加文件到暂存区git…

C++系列十六:枚举

枚举 一、C枚举基础 在C中&#xff0c;枚举&#xff08;Enumeration&#xff09;是一种用户定义的数据类型&#xff0c;它包含一组整数值&#xff0c;每个值都与一个标识符关联。通过使用枚举&#xff0c;我们可以使代码更加清晰易懂&#xff0c;避免使用魔术数字或字符串。 …

elementui dialog 回车时却刷新整个页面

到处都是坑&#xff0c;这个坑填完另一个坑还在等你。。。坑坑相连&#xff0c;坑坑不同。。。 使用el-dialog弹出一个表单&#xff0c;当我无意间敲到回车键时&#xff0c;整个页面被刷新了&#xff0c;又是一脸的懵逼。。。 经过查找文档发现解决方案为上述截图标记。。。 e…

海康摄像头配置移动侦测

需求背景 当有人进入到规划的区域内就会自动报警&#xff0c;目前配置的是语音播报闪光灯闪烁。 操作教程 1、打开配置界面 登录萤石云平台-设备管理-设备配置-立即配置 2、报警语音配置 默认进入的是配置界面&#xff0c;不要在这里配置。 不要在【配置】-【事件】-【事…

智能合约笔记

前言&#xff1a; 首先了解下为什么会出现智能合约&#xff0c;打个比方现在有两个人A和B打赌明天会不会下雨&#xff0c;每个人赌注100元&#xff0c;如果第二天下雨则A拿走200元&#xff0c;否则B拿走200元&#xff0c;这样就有一个问题&#xff0c;赌注要到第二天才能见效&…

【java】项目部署liunx服务器的简单步骤

在Linux服务器上部署Java项目通常涉及到一系列步骤&#xff0c;下面是一个基本的部署流程&#xff0c;具体步骤可能会根据项目和服务器环境的不同而有所调整&#xff1a; 1. 准备工作&#xff1a; 1.1 安装Java环境&#xff1a; 在Linux服务器上安装Java运行环境&#xff0c;…