在上篇RT-Thread基于AT32单片机的485应用开发(一)中实现了RS485收发,但总觉得效率不高,函数封装也不完善。考虑到RS485总线应用都是主从式结构,比如工业领域常用的Modbus协议,都是以帧为单位进行收发,本次测试对收发函数进行了封装,并对RS485的收发控制引脚根据波特率进行了自动延时控制,降低了CPU负载。
本例中收发全部采用DMA的NON_BLOCKING方式,把接收一帧数据和发送一帧数据进行了函数封装。
测试代码如下:
#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)
{
rt_uint32_t rx_len;
static unsigned char rx_buf[256];
while(1){
rx_len = serial_read_frame(serial, rx_buf, 255, 10, 1000);
if(rx_len<=0)
continue;
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);
接收函数,返回收到的字节数:
int serial_read_frame(rt_device_t dev, uint8_t *buf, int max_len, uint32_t idle_ms, int timeout_ms)
idle_ms : 收到最后一个字节数据后空闲的毫秒数
timeout_ms : 如果在这个时间内没有收到数据,则返回0;-1代表一直等待直到收到数据。
发送函数
void serial_write_frame_rs485(rt_device_t dev, uint8_t *buf, int len, int bitrate, int ctrl_pin)
bitrate : 波特率,用于计算实际需要发送的时间
ctrl_pin :485收发控制引脚号
实际测试结果:
在编辑文字的约6分钟内,总共收发了31270个字节,没有发生错误。
在此基础上,后续又实现了一个极简ModbusRTU从机,核心代码不到300行,支持01、02、03、04、05、06、15、16功能码。