RT-Thread (1) 添加外部内存到内存管理
RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60
RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备
前两章我们介绍了RTT添加外部SRAM到内存管理、以及添加ENC28J60网络接口芯片驱动、开启LWIP。本章介绍一下如何在RTT上添加SP3485的驱动程序。添加一个RS485设备到系统中。
RT-Thread开发指南
正点原子STM32F1开发指南HAL库版本
MCU:STM32F103ZET6
485接口芯片:SP3485
接口电路如下所示:
开发环境:RT-Thread Studio
RT-Thread:4.0.3
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,可以发现这个宏定义在如下几个文件中使用了。
其中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引脚。
RT-Thread 提供了一套简单的 I/O 设备模型框架,其结构如下所示:
应用程序通过 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方法的。
对于任何一个外设,使用它之前都需要对其进行初始化操作,因此init方法是必须的。
我们需要一个控制手段来控制SP3485是否可以发送数据,在这里我们可以通过实现open方法和close方法控制SP3485是否发送数据。另外,我们在使用SP3485时需要控制波特率,这里通过借用open方法的参数实现。
我们通过485总线读写设备的寄存器,那么read和write方法也是必须的。
在这里我们是将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;
}
}
前面讲到,我们要借用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;
}
我们将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;
}
我们编写好程序之后需要将其注册到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);
编写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;
}