RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备

RT-Thread (1) 添加外部内存到内存管理

RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60

RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备​​​​​​​

0 概述

前两章我们介绍了RTT添加外部SRAM到内存管理、以及添加ENC28J60网络接口芯片驱动、开启LWIP。本章介绍一下如何在RTT上添加SP3485的驱动程序。添加一个RS485设备到系统中。

0.0 参考资料

RT-Thread开发指南
正点原子STM32F1开发指南HAL库版本

0.1 硬件资源

MCU:STM32F103ZET6
485接口芯片:SP3485
接口电路如下所示:

RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备_第1张图片

0.2 开发环境

开发环境:RT-Thread Studio
RT-Thread:4.0.3

1 启用RTT中STM32 Uart2驱动

SP3485接口芯片的引脚非常简单,总共有四个功能引脚,其中RO、DI分别连接STM32的TX和RX。RE、DE则是控制接口数据流向的,而且两个引脚的有效状态相反,可以直接使用一个引脚控制。

在电路上,我使用STM32的串口2驱动它,同时使用PD7引脚控制数据流向。使用RTT Studio建立STM32的工程,其中已经包含了UART2的驱动程序,我们只需要在board.h中启用即可。即向board.h中添加如下代码:

#define BSP_USING_UART2
#define BSP_UART2_TX_PIN        "PA2"
#define BSP_UART2_RX_PIN        "PA3"

我们在项目中检索一下BSP_USING_UART2,可以发现这个宏定义在如下几个文件中使用了。RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备_第2张图片

其中uart_config.h在【项目/drivers/include/config/】目录下,我们打开该文件找到使用这个宏定义的地方,如下:

#if defined(BSP_USING_UART2)
#ifndef UART2_CONFIG
#define UART2_CONFIG                                                \
    {                                                               \
        .name = "uart2",                                            \
        .Instance = USART2,                                         \
        .irq_type = USART2_IRQn,                                    \
        .tx_pin_name = BSP_UART2_TX_PIN,                            \
        .rx_pin_name = BSP_UART2_RX_PIN,                            \
    }
#endif /* UART2_CONFIG */

可以看到这里定义了UART2的相关参数,其中.tx_pin_name = BSP_UART2_TX_PIN,和.rx_pin_name = BSP_UART2_RX_PIN,则是使用了我们定义在board.h中定义的两个UART2引脚。

可以看到这里定义了UART2的相关参数,其中.tx_pin_name = BSP_UART2_TX_PIN,和.rx_pin_name = BSP_UART2_RX_PIN,则是使用了我们定义在board.h中定义的两个UART2引脚。

2 基于RTT I/O框架编写SP3485的驱动

RT-Thread 提供了一套简单的 I/O 设备模型框架,其结构如下所示:

RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备_第3张图片

应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。我们所要做的就是在RTT的设备框架内编写SP3485的驱动,并将其注册到系统中。

在RTT中,应用程序通过标准的、统一的接口调用设备。这些接口通过设备对象调用,设备对象结构体的定义如下:

struct rt_device
{
    struct rt_object          parent;        /* 内核对象基类 */
    enum rt_device_class_type type;          /* 设备类型 */
    rt_uint16_t               flag;          /* 设备参数 */
    rt_uint16_t               open_flag;     /* 设备打开标志 */
    rt_uint8_t                ref_count;     /* 设备被引用次数 */
    rt_uint8_t                device_id;     /* 设备 ID,0 - 255 */
    /* 数据收发回调函数 */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
    const struct rt_device_ops *ops;    /* 设备操作方法 */
    /* 设备的私有数据 */
    void *user_data;
};
typedef struct rt_device *rt_device_t;

其中,rt_device_ops保存了设备的操作方法。这些操作方法也是我们编写设备驱动程序时所需要实现的。

 
  
struct rt_device_ops
{
    /* common device interface */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)  (rt_device_t dev);
    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);
};

我们通过RTT框架调用设备时,需要调用哪些接口就要在驱动程序中是现这些操作方法。当然这些是根据需要实现的。例如对于一个只读设备来说,是不需要实现write方法的。

2.1 SP3485 操作方法简析

对于任何一个外设,使用它之前都需要对其进行初始化操作,因此init方法是必须的。

我们需要一个控制手段来控制SP3485是否可以发送数据,在这里我们可以通过实现open方法和close方法控制SP3485是否发送数据。另外,我们在使用SP3485时需要控制波特率,这里通过借用open方法的参数实现。

我们通过485总线读写设备的寄存器,那么read和write方法也是必须的。

2.2 SP3485 驱动编写

2.2.1 Init方法编写

在这里我们是将SP3485挂载到STM32的串口二上。所以在初始化SP3485时需要执行挂载这一步。另外,我们使用PD7引脚作为流控引脚,同样要对其进行模式配置。Init方法的原型结构为rt_err_t (*init) (rt_device_t dev)
传入参数 dev,也就是设备对象的结构体;
返回rt_err_t ,也就是错误代码。
这里我们不需要dev设备对象。

//定义SP3485挂载到哪条UART总线上
#define SP3485_UART_BUS "uart2"
#define SP3485_RE_PIN   GET_PIN(D, 7)
rt_err_t DRV_SP3485_Init(rt_device_t dev)
{
    //查找SP3485 挂载目标总线设备
    SP3485_UART = rt_device_find(SP3485_UART_BUS);
    SP3485_Cfg.baud_rate = BAUD_RATE_9600 ;
    SP3485_Cfg.bufsz = 128;
    //当UART打开成功,配置接口
    rt_device_control(SP3485_UART, RT_DEVICE_CTRL_CONFIG, &SP3485_Cfg);
    //打开SP3485 UART总线
    //选择DMA发送和接收方式
    if(rt_device_open(SP3485_UART,RT_DEVICE_FLAG_INT_RX) == RT_EOK)
    {
        //初始化SP3485的流控接口
        rt_pin_mode(SP3485_RE_PIN, PIN_MODE_OUTPUT);
        return RT_EOK;
    }
    else {
        return RT_ERROR;
    }
}

2.2.2 Open和close方法编写

前面讲到,我们要借用open函数中的参数配置SP3485的波特率,open函数的原型为:rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag)。
传入的第一个参数为设备对象,第二个为设备模式标志。原本第二个参数时用于选择以下参数,但是这里我们借用为设置波特率。

#define RT_DEVICE_FLAG_RDONLY       0x001 /* 只读 */
#define RT_DEVICE_FLAG_WRONLY       0x002 /* 只写  */
#define RT_DEVICE_FLAG_RDWR         0x003 /* 读写  */
#define RT_DEVICE_FLAG_REMOVABLE    0x004 /* 可移除  */
#define RT_DEVICE_FLAG_STANDALONE   0x008 /* 独立   */
#define RT_DEVICE_FLAG_SUSPENDED    0x020 /* 挂起  */
#define RT_DEVICE_FLAG_STREAM       0x040 /* 流模式  */
#define RT_DEVICE_FLAG_INT_RX       0x100 /* 中断接收 */
#define RT_DEVICE_FLAG_DMA_RX       0x200 /* DMA 接收 */
#define RT_DEVICE_FLAG_INT_TX       0x400 /* 中断发送 */
#define RT_DEVICE_FLAG_DMA_TX       0x800 /* DMA 发送 */

另外,我们希望通过open和close方法控制是否启用收发,这里我们设置了一个标志位实现。

static rt_uint8_t SP3485_OpenFlag = 0;
rt_err_t DRV_SP3485_Open(rt_device_t dev, rt_uint16_t oflag)
{
    SP3485_Cfg.baud_rate = oflag ;
    rt_device_control(SP3485_UART, RT_DEVICE_CTRL_CONFIG, &SP3485_Cfg);
    //flag置位
    SP3485_OpenFlag = 1;
    return RT_EOK;
}

对应的close方法为:

rt_err_t DRV_SP3485_Close(rt_device_t dev)
{
    SP3485_Cfg.baud_rate = BAUD_RATE_9600 ;
    rt_device_control(SP3485_UART, RT_DEVICE_CTRL_CONFIG, &SP3485_Cfg);
    //flag置位
    SP3485_OpenFlag = 0;
    return RT_EOK;
}

2.2.3 read 和 write方法

我们将SP3485挂载到UART2总线上,因此我们通过调用RTT UART2的读写接口实现SP3485的读写。如下:

rt_size_t DRV_SP3485_Read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    return rt_device_read(SP3485_UART,0,buffer,size);
}
rt_size_t DRV_SP3485_Write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    //流控引脚高电平
    rt_pin_write(SP3485_RE_PIN, 1);
    rt_device_write(SP3485_UART, 0, buffer, size);
    //流控引脚低电平
    rt_pin_write(SP3485_RE_PIN, 0);
    return RT_EOK;
}

2.2.4 创建和注册SP3485设备

我们编写好程序之后需要将其注册到RTT 的I/O设备管理器中。
可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建:

rt_device_t rt_device_create(int type, int attach_size);

静态申明就是我们自己定义一个静态的rt_device对象结构体。动态创建则是通过内存管理申请空间创建设备对象。

而后,我们需要将2.2.1~2.2.3中编写的硬件操作方法添加到创建的设备对象中。

设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问,注册设备的函数如下所示:

rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);

具体程序如下所示:

//SP3485句柄
static rt_device_t SP3485;
int DRV_SP3485_Create(void)
{
    //注册SP3485为字符设备
    SP3485 = rt_device_create(RT_Device_Class_Char,0);
    if (SP3485 != RT_NULL) {
        //设备注册成功,添加设备方法
        SP3485->init = DRV_SP3485_Init;
        SP3485->open = DRV_SP3485_Open;
        SP3485->close = DRV_SP3485_Close;
        SP3485->read = DRV_SP3485_Read;
        SP3485->write = DRV_SP3485_Write;
        rt_device_register(SP3485,"RS485", RT_DEVICE_FLAG_RDWR);
    }
    return 0;
}
INIT_DEVICE_EXPORT(DRV_SP3485_Create);

3 应用程序编写测试

编写main.c如下:

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-09-25     RT-Thread    first version
 */
#include 
#include 
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include 
#include 
char string[16] = "test rs485\r\n";
char rec_buff[32];
rt_uint16_t rec_len;
static rt_device_t RS485_DEV;
int main(void)
{
    int count = 1;
    RS485_DEV = rt_device_find("RS485"); //寻找我们注册到I/O管理器中的485设备
    if(RS485_DEV)
    {
        rt_device_init(RS485_DEV);      //其进行初始化操作
        rt_device_open(RS485_DEV, 9600);    //打开485接口,配置波特率为9600
        while(count++)
        {
            rt_thread_mdelay(1000);
            rt_device_write(RS485_DEV, 0, string, 16);      //通过485接口发送string数据块
            rec_len = rt_device_read(RS485_DEV, -1, rec_buff, 32);  //接收485数据到缓存
            if(rec_len)
            {
                LOG_D("Receive : %s | len:%d",rec_buff,rec_len);
            }
            memset(rec_buff,0x00,32);
        }
    }
    else {
        LOG_E("No RS485 Device Find.");
    }
    return RT_EOK;
}

你可能感兴趣的:(RT-Thread,stm32,物联网)