Skip to content

RS485

RS485简介

通信接口的远程称重数据采集方法,在要求通信距离为几十米到上千米时,广泛采用RS-485串行总线标准。RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。

串口映射

启用RS485需要映射为串口设备,当前例子映射为"uart0",需要关闭系统中"uart0"的menuconfig相关配置,操作路径如下:

Windows环境:利用RT-Thread官方env工具直接在代码根目录下执行menuconfig进入配置。

Linux环境:在代码根目录下执行scons --menuconfig进入配置。

RT-Thread Components -> Network -> AT commands -> Enable AT commands server

关闭"uart1"配置路径如下:

RT-Thread Kernel -> Kernel Device Object -> Using console for rt_kprintf

访问RS485

访问RS485,库中提供了以下接口:

函数 描述
rt_device_find() 查找设备
rt_device_control() 控制设备
rt_device_open() 打开设备
rt_device_read() 读取数据
rt_device_write() 写入数据
rt_device_set_rx_indicate() 设置接收回调函数
rt_device_set_tx_complete() 设置发送完成回调函数
rt_device_close() 关闭设备

查找设备

查找RS485设备,函数原型如下所示:

rt_device_t rt_device_find(const char* name);

  • 参数

    [IN]name:RS485设备名称,目前注册到的系统中的RS485设备名称为"uart0"或"uart1"。

  • 返回值

    RT_NULL:失败,设备不存在。

    非RT_NULL:成功,该返回值是操作RS485设备的句柄,在后续的接口中作为函数入参。

控制设备

控制RS485设备,函数原型如下所示:

rt_err_t rt_device_control(rt_device_t dev, int cmd, void* arg);

  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值

    [IN]cmd:命令控制字,在这里主要支持以下宏定义:

    // 宏定义所在文件相对路径:
    // rt-thread/include/rtdef.h
    #define RT_DEVICE_CTRL_CONFIG       0x03    // 设置配置
    

    [INOUT]arg:对应结构体为struct serial_configure,

    // 结构体定义所在路径为:
    // rt-thread\components\drivers\include\drivers\serial.h
    struct serial_configure
    {
        rt_uint32_t baud_rate;          // 波特率
    
        rt_uint32_t data_bits   : 4;    // 数据位
        rt_uint32_t stop_bits   : 2;    // 停止位
        rt_uint32_t parity      : 2;    // 奇偶校验
        rt_uint32_t bit_order   : 1;    // 大小端
        rt_uint32_t invert      : 1;    // 反转
        rt_uint32_t bufsz       : 16;   // buf大小
        rt_uint32_t reserved    : 6;    // 保留
    };
    
    // 关于RT-Thread定义的宏包括如下:
    #define BAUD_RATE_2400                  2400
    #define BAUD_RATE_4800                  4800
    #define BAUD_RATE_9600                  9600
    #define BAUD_RATE_19200                 19200
    #define BAUD_RATE_38400                 38400
    #define BAUD_RATE_57600                 57600
    #define BAUD_RATE_115200                115200
    #define BAUD_RATE_230400                230400
    #define BAUD_RATE_460800                460800
    #define BAUD_RATE_600000                600000
    #define BAUD_RATE_750000                750000
    #define BAUD_RATE_921600                921600
    #define BAUD_RATE_2000000               2000000
    #define BAUD_RATE_3000000               3000000
    
    #define DATA_BITS_5                     5
    #define DATA_BITS_6                     6
    #define DATA_BITS_7                     7
    #define DATA_BITS_8                     8
    #define DATA_BITS_9                     9
    
    #define STOP_BITS_1                     0
    #define STOP_BITS_2                     1
    #define STOP_BITS_3                     2
    #define STOP_BITS_4                     3
    
    #ifdef _WIN32
    #include <windows.h>
    #else
    #define PARITY_NONE                     0
    #define PARITY_ODD                      1
    #define PARITY_EVEN                     2
    #endif
    
    #define BIT_ORDER_LSB                   0
    #define BIT_ORDER_MSB                   1
    
    #define NRZ_NORMAL                      0       /* Non Return to Zero : normal mode */
    #define NRZ_INVERTED                    1       /* Non Return to Zero : inverted mode */
    
    #ifndef RT_SERIAL_RB_BUFSZ
    #define RT_SERIAL_RB_BUFSZ              64
    #endif
    
    #define RT_SERIAL_EVENT_RX_IND          0x01    /* Rx indication */
    #define RT_SERIAL_EVENT_TX_DONE         0x02    /* Tx complete   */
    #define RT_SERIAL_EVENT_RX_DMADONE      0x03    /* Rx DMA transfer done */
    #define RT_SERIAL_EVENT_TX_DMADONE      0x04    /* Tx DMA transfer done */
    #define RT_SERIAL_EVENT_RX_TIMEOUT      0x05    /* Rx timeout    */
    
    #define RT_SERIAL_DMA_RX                0x01
    #define RT_SERIAL_DMA_TX                0x02
    
    #define RT_SERIAL_RX_INT                0x01
    #define RT_SERIAL_TX_INT                0x02
    
    #define RT_SERIAL_ERR_OVERRUN           0x01
    #define RT_SERIAL_ERR_FRAMING           0x02
    #define RT_SERIAL_ERR_PARITY            0x03
    
    #define RT_SERIAL_TX_DATAQUEUE_SIZE     2048
    #define RT_SERIAL_TX_DATAQUEUE_LWM      30
    
  • 返回值

    0,正常

    其他:错误,RT-Thread定义的错误类型。

打开设备

打开RS485设备,函数原型如下所示:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag);
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

    [IN]oflag:打开设备模式标记,RS485设备驱动框架支持如下模式,需要多个模式同时使用,可用 "|",如:RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,支持的定义值如下:

    #define RT_DEVICE_FLAG_RDONLY       0x001   // 只读
    #define RT_DEVICE_FLAG_WRONLY       0x002   // 只写
    #define RT_DEVICE_FLAG_RDWR         0x003   // 读写
    
    #define RT_DEVICE_FLAG_INT_RX       0x100   // 接收中断模式
    #define RT_DEVICE_FLAG_INT_TX       0x400   // 发送中断模式
    
    #define RT_DEVICE_FLAG_STREAM       0x040   // 流模式
    
  • 返回值

    0:正常。

    其他:错误,RT-Thread定义的错误类型。

读取数据

RS485读取数据,函数原型如下所示:

rt_size_t rt_device_read(rt_device_t dev, 
                         rt_off_t pos, 
                         const void* buffer, 
                         rt_size_t size);
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

    [IN]pos:读取数据偏移,这里没有使用。

    [IN]buffer:读取数据的指针。

    [IN]size:读取数据的长度,不能超过buffer指针指向的空间的长度。

  • 返回值

    0:读取失败。

    其他:实际读取的RS485消息大小。

注:由于RS485是半双工通信,将有一个GPIO脚设置为低电平时才能进行数据接收,高电平时进行数据发送。

写入数据

使用RS485设备(写入)发送数据,函数原型如下所示:

rt_size_t rt_device_write(rt_device_t dev, 
                          rt_off_t pos, 
                          void* buffer, 
                          rt_size_t size);
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

    [IN]pos:发送数据偏移,这里没有使用。

    [IN]buffer:发送数据的指针。

    [IN]size:发送数据的长度,buffer指针指向数据内容的长度。

  • 返回值

    0:发送失败。

    其他:实际发送的RS485消息大小。

注:由于RS485是半双工通信,将有一个GPIO脚设置为低电平时才能进行数据接收,高电平时进行数据发送。

设置接收回调函数

可以通过如下函数来设置数据接收指示,当串口收到数据时,通知上层应用线程有数据到达,函数原型如下所示:

rt_err_t rt_device_set_rx_indicate(
    rt_device_t dev, 
    rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

    [IN]rx_ind:回调函数指针。

    ​ (回调函数)dev:设备句柄,与前面dev是同一个。

    ​ (回调函数)size:缓冲区数据大小。

  • 返回值

    成功:0

设置发送完成回调函数

在调用 rt_device_write() 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示 :

rt_err_t rt_device_set_tx_complete(
    rt_device_t dev,
    rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

    [IN]tx_done:回调函数指针。

    ​ (回调函数)dev:设备句柄,与前面dev是同一个。

    ​ (回调函数)buffer:缓冲区数据。

  • 返回值

    成功:0

调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由设备驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。

关闭设备

当设备完成操作后,可以关闭设备,函数原型如下:

rt_err_t rt_device_close(rt_device_t dev);
  • 参数

    [IN]dev:RS485设备句柄,查找设备函数的返回值。

  • 返回值

    0,正常

    其他:错误,RT-Thread定义的错误类型。

注:若设备已经完全关闭,不能重复关闭设备。

RS485使用示例

使用示例需要配置menuconfig打开配置(默认关闭),路径如下:

wiota APP -> open rs485, default close(_RS485_APP_)

以下提供RS485测试实例,测试步骤如下:

  1. 根据RS485设备名称,获取设备句柄。
  2. 设置RS485配置。
  3. 打开RS485。
  4. 发送数据。
  5. 接收数据。
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#if defined(RT_USING_SERIAL) && defined(_RS485_APP_)

//#define USE_UART_RINGBUFF

#define RS485_NAME "uart0"
#define RS485_PIN 17
#define RS485_BAUD_RATE 9600
#define RS485_BUFFER_SIZE 4096
#define RS485_PRIORITY 7
#define RS485_TIMESLICE 5
#define RS485_STACK_SIZE 640

#define RS485_TX()    rt_pin_write(RS485_PIN, PIN_HIGH)
#define RS485_RX()    rt_pin_write(RS485_PIN, PIN_LOW)

/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;

#ifdef USE_UART_RINGBUFF
static struct rt_ringbuffer* uart_ringbuffer = RT_NULL;
#endif

/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
#ifdef USE_UART_RINGBUFF
    uint8_t uart_recv_buf[64];
    uint32_t recv_count = 0;

    if (size == 0)
    {
        return 0;
    }

    while (recv_count < size)
    {
        uint16_t data_len = 0;
        if ((recv_count + 64) < size)
        {
            data_len = 64;
        }
        else
        {
            data_len = size - recv_count;
        }
        data_len = rt_device_read(dev, 0, uart_recv_buf, data_len);
        if (data_len > 0)
        {
            if (uart_ringbuffer != RT_NULL)
            {
                rt_ringbuffer_put(uart_ringbuffer, (const rt_uint8_t*)uart_recv_buf, data_len);
            }
            recv_count += data_len;
        }
        else
        {
            break;
        }
    }
#endif
    /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    rt_sem_release(&rx_sem);

    return RT_EOK;
}

static void rs485_thread_entry(void *parameter)
{
    char rx_buf[64];

    while (1)
    {
        uint8_t data_len = 0;

        /* 阻塞等待接收信号量,等到信号量后再次读取数据 */
        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
        rt_memset(rx_buf, 0x00, 64);
#ifdef USE_UART_RINGBUFF
        data_len = rt_ringbuffer_get(uart_ringbuffer, (uint8_t*)rx_buf, 64);
#else
        data_len = rt_device_read(serial, 0, rx_buf, 64);
#endif
        if (data_len > 0)
        {
            // rt_kprintf("[RS485]%s\n", rx_buf);
            RS485_TX();
            rt_device_write(serial, 0, rx_buf, data_len);
            rt_device_control(serial, RT_DEVICE_CTRL_WAIT_TX_DONE, RT_NULL);
            RS485_RX();
        }
    }
}

int rs485_app_sample(void)
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];
    char *str = "hello RT-Thread!\r\n";
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;  /* 初始化配置参数 */

    // rt_kprintf("rs485_test start!!\r\n");
    rt_strncpy(uart_name, RS485_NAME, RT_NAME_MAX);

    rt_pin_mode(RS485_PIN, PIN_MODE_OUTPUT);

    /* 查找系统中的串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }

    /* step2:修改串口配置参数 */
    config.baud_rate = RS485_BAUD_RATE;     // 修改波特率为 9600
    // config.data_bits = DATA_BITS_8;         // 数据位 8
    // config.stop_bits = STOP_BITS_1;         // 停止位 1
    // config.bufsz     = 128;                 // 修改缓冲区 buff size 为 128
    // config.parity    = PARITY_NONE;         // 无奇偶校验位

    /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
    rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);

#ifdef USE_UART_RINGBUFF
    uart_ringbuffer = rt_ringbuffer_create(2048);
    if (uart_ringbuffer == RT_NULL)
    {
        rt_kprintf("rt_ringbuffer_create fail!!\r\n");
        return RT_ERROR;
    }
#endif

    /* 初始化信号量 */
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以中断接收及轮询发送模式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_input);

    RS485_TX();
    /* 发送字符串 */
    rt_device_write(serial, 0, str, rt_strlen(str));
    rt_device_control(serial, RT_DEVICE_CTRL_WAIT_TX_DONE, RT_NULL);
    RS485_RX();

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", 
                                          rs485_thread_entry, 
                                          RT_NULL, 
                                          RS485_STACK_SIZE, 
                                          RS485_PRIORITY, 
                                          RS485_TIMESLICE);
    /* 创建成功则启动线程 */
    if (RT_NULL == thread)
    {
        ret = RT_ERROR;
    }
    else
    {
        rt_thread_startup(thread);
    }

    return ret;
}

#endif
Back to top