一、RTT基础配置
1.新建工程: 选择合适的芯片进行工程创建,并编译工程确保工程文件可以正常运行。
2.添加引脚: 在board.h
中添加串口所需要的引脚,以及RS485通信时的控制引脚。本次使用的是UART3,控制引脚是PB14和PB15。
#define BSP_USING_UART3
#define BSP_UART3_TX_PIN "PD8"
#define BSP_UART3_RX_PIN "PD9"
#define BSP_UART3_RE "PB15"
#define BSP_UART3_DE "PB14"
3.添加变量: 在drv_uasrt.h -> struct stm32_uart_config
结构体中添加和RS485相关的变量,主要包含RD、DE名称、端口号、引脚,以及串口名称。
/* stm32 config class */
struct stm32_uart_config
{
const char *name;
USART_TypeDef *Instance;
IRQn_Type irq_type;
struct dma_config *dma_rx;
struct dma_config *dma_tx;
const char *tx_pin_name;
const char *rx_pin_name;
/* 新增变量 */
const char *re_pin_name; // RS485的RE名称
const char *de_pin_name; // RS485的DE名称
GPIO_TypeDef *re_port; // RE端口号
GPIO_TypeDef *de_port; // DE端口号
uint16_t re_pin; // RE引脚
uint16_t de_pin; // DE引脚
char is485; // RS485
};
4.添加初始值: 在uart_config.h -> #define UART3_CONFIG
宏定义中添加初始值。
#if defined(BSP_USING_UART3)
#ifndef UART3_CONFIG
#define UART3_CONFIG \
{ \
.name = "uart3", \
.Instance = USART3, \
.irq_type = USART3_IRQn, \
.tx_pin_name = BSP_UART3_TX_PIN, \
.rx_pin_name = BSP_UART3_RX_PIN, \
/* 新增初始值 */
.is485 = 1, \
.re_pin_name = BSP_UART3_RE, \
.de_pin_name = BSP_UART3_DE, \
}
#endif /* UART3_CONFIG */
5.添加初始化: 在drv_usart.c -> stm32_gpio_configure
函数中增加GPIO引脚的初始化代码,主要是RS485控制线初始化,并且设置为默认接收模式。
// 串口GPIO引脚初始化
static rt_err_t stm32_gpio_configure(struct stm32_uart_config *config)
{
int uart_num = 0;
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_TypeDef *tx_port;
GPIO_TypeDef *rx_port;
uint16_t tx_pin;
uint16_t rx_pin;
// 获取串口编号
uart_num = config->name[4] - '0';
// 解析GPIO 端口和引脚编号
get_pin_by_name(config->rx_pin_name, &rx_port, &rx_pin);
get_pin_by_name(config->tx_pin_name, &tx_port, &tx_pin);
/* gpio ports clock enable */
stm32_gpio_clk_enable(tx_port);
if (tx_port != rx_port)
{
stm32_gpio_clk_enable(rx_port);
}
/* rx pin initialize */
GPIO_InitStruct.Pin = tx_pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
// 指定复用设备
#if defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || \
defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G4) || \
defined(SOC_SERIES_STM32L1) || defined(SOC_SERIES_STM32L4)
#define GPIO_AF7 ((uint8_t)0x07)
#define GPIO_AF8 ((uint8_t)0x08)
/* uart1-3 -> AF7, uart4-8 -> AF8 */
if (uart_num <= 3)
{
GPIO_InitStruct.Alternate = GPIO_AF7;
}
else
{
GPIO_InitStruct.Alternate = GPIO_AF8;
}
#endif
HAL_GPIO_Init(tx_port, &GPIO_InitStruct);
/* rx pin initialize */
GPIO_InitStruct.Pin = rx_pin;
HAL_GPIO_Init(rx_port, &GPIO_InitStruct);
// RS485控制线初始化
if (config->is485)
{
// 解析GPIO 端口和引脚编号
get_pin_by_name(config->de_pin_name, &(config->de_port), &(config->de_pin));
get_pin_by_name(config->re_pin_name, &(config->re_port), &(config->re_pin));
// 使能控制线时钟
if (config->de_port != rx_port || config->re_port != rx_port)
{
stm32_gpio_clk_enable(config->de_port);
stm32_gpio_clk_enable(config->re_port);
}
// 控制线初始化
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pin = config->de_pin;
HAL_GPIO_Init(config->de_port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = config->re_pin;
HAL_GPIO_Init(config->re_port, &GPIO_InitStruct);
// 设置控制线默认电平为低电平,接收模式
HAL_GPIO_WritePin(config->de_port, config->de_pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(config->re_port, config->re_pin, GPIO_PIN_RESET);
}
return RT_EOK;
}
6.发送进行模式切换 在drv_usart.c -> stm32_putc
函数中增加接收和发送模式的切换代码,在发送时设置成发送模式,等待发送完成,之后再设置成接收模式。
static int stm32_putc(struct rt_serial_device *serial, char c)
{
struct stm32_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct stm32_uart, serial);
UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32WL) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \
|| defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) || defined(SOC_SERIES_STM32L5) \
|| defined(SOC_SERIES_STM32G4) || defined(SOC_SERIES_STM32MP1) || defined(SOC_SERIES_STM32WB) || defined(SOC_SERIES_STM32F3) \
|| defined(SOC_SERIES_STM32U5)
uart->handle.Instance->TDR = c;
#else
// 发送数据时,设置成发送模式
if(uart->config->is485)
{
HAL_GPIO_WritePin(uart->config->de_port,uart->config->de_pin, GPIO_PIN_RESET);
}
uart->handle.Instance->DR = c;
#endif
// 等带发送完成
while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
// 发送完成后,设置成接收模式
if(uart->config->is485)
{
HAL_GPIO_WritePin(uart->config->de_port,uart->config->de_pin, GPIO_PIN_SET);
}
return 1;
}
二、注册RS485设备
1.注册设备介绍
设备注册函数:以下结构体中的函数需要具体实现,用于设备在查找之后进行设备的操作
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); // 设备控制函数
void *user_data; // 设备用户数据
(1)设备初始化函数
/**
* @brief 设备初始化接口
* @return
*/
rt_err_t rs485_usart_init(rt_device_t dev)
{
g_rs485.queue = rt_malloc(QUEUE_SIZE);
// 查找创建的设备
g_rs485.dev = (rt_device_t) rt_device_find(RS485_DEV_NAME);
RT_ASSERT(g_rs485.dev != RT_NULL);
// 配置创建设备的参数
rt_device_control(g_rs485.dev, RT_DEVICE_CTRL_CONFIG, (void *)&g_user_data);
return RT_EOK;
}
INIT_ENV_EXPORT(rs485_usart_init);
(2)设备打开函数
/**
* @brief 设备打开接口
* @return
*/
rt_err_t rs485_usart_open(rt_device_t dev, rt_uint16_t oflag)
{
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
rt_device_open(user->dev, RT_DEVICE_FLAG_INT_RX);
return RT_EOK;
}
(3)设备关闭函数
/**
* @brief 设备关闭接口
* @return
*/
rt_err_t rs485_usart_close(rt_device_t dev)
{
return RT_EOK;
}
(4)设备读取函数
/**
* @brief 设备读取接口
* @return
*/
rt_size_t rs485_usart_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
size = rt_device_read(user->dev, 0, buffer, 1);
return size;
}
(5)设备写入函数
/**
* @brief 设备写入接口
* @return
*/
rt_size_t rs485_usart_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
if (g_user_data.name == RT_NULL)
return 0;
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
user->dev->write(user->dev, pos, buffer, size);
return 1;
}
(6)设备控制函数
/**
* @brief 设备控制接口
* @return
*/
rt_err_t rs485_usart_control(rt_device_t dev, int cmd, void *args)
{
int ret = -RT_ERROR;
struct sram_device *sdcard;
RT_ASSERT(dev != 0);
sdcard = (struct sram_device *)dev;
switch (cmd)
{
// 设置互斥锁
case RT_DEVICE_CTRL_LOCK:
lock(sdcard);
ret = RT_EOK;
break;
// 解除互斥锁
case RT_DEVICE_CTRL_UNLOCK:
unlock(sdcard);
ret = RT_EOK;
break;
default:
ret = -RT_ERROR;
break;
}
return ret;
}
(7)RS485设备注册
/**
* @brief RS485设备注册
* @return 0:成功 -1:失败
*/
static int RS485_Device_Register(void)
{
rt_err_t ret;
// 开辟一个存储空间
g_rs485.dev = rt_malloc(sizeof(struct rt_device));
rt_memset(g_rs485.dev, 0, sizeof(struct rt_device));
// 串口3参数初始化
usart3_param_init();
// 结构体初始化
g_rs485.dev->type = RT_Device_Class_Char; // 设备类型
g_rs485.dev->init = rs485_usart_init; // 设备初始化接口
g_rs485.dev->open = rs485_usart_open; // 设备打开接口
g_rs485.dev->close = rs485_usart_close; // 设备关闭接口
g_rs485.dev->read = rs485_usart_read; // 设备读取接口
g_rs485.dev->write = rs485_usart_write; // 设备写入接口
g_rs485.dev->control = rs485_usart_control; // 设备控制接口
g_rs485.dev->user_data = &g_user_data; // 设备数据
// 设备注册
ret = rt_device_register(g_rs485.dev, RS485_DEV_NAME, RT_DEVICE_FLAG_RDWR);
return ret;
}
INIT_DEVICE_EXPORT(RS485_Device_Register);
2.设备使用
正常注册完设备之后,可以直接调用函数的接口来进行数据的收发,以及对设备进行控制。
/**
* @brief RS485接收线程
* @param param
*/
void rs485_rx_thread_entry(void *param)
{
char ch;
g_rs485.qpush_index = 0;
rt_err_t result;
rt_uint16_t CRC_Value = 0;
while (1)
{
if (g_rs485.qpush_index >= QUEUE_SIZE)
g_rs485.qpush_index = 0;
// 信号量设置超时时间
result = rt_sem_take(g_rs485.rx_sem, 1000);
if (result == RT_EOK)
{
g_rs485.dev->read(g_user_data.dev, 0, &ch, 1);
g_rs485.queue[g_rs485.qpush_index++] = ch;
// 接收数据超过三个
if (g_rs485.qpush_index > 3)
{
// CRC校验
CRC_Value = CRC_Check(g_rs485.queue, (g_rs485.qpush_index - 2));
// 校验合格才会处理数据
if ((g_rs485.queue[g_rs485.qpush_index - 1] == ((char) CRC_Value))
&& (g_rs485.queue[g_rs485.qpush_index - 2] == (char) (CRC_Value >> 8)))
{
g_rs485.dev->write(RT_NULL, 0, g_rs485.queue, g_rs485.qpush_index);
g_rs485.qpush_index = 0;
}
}
}
else
{
g_rs485.qpush_index = 0;
}
}
}
3.设备访问
(1)查询函数:应用程序根据设备名称获取设备句柄,进而可以操作设备。
rt_device_t rt_device_find(const char* name);
参数 | 描述 |
---|---|
name | 设备名称 |
返回 | - - |
---|---|
设备句柄 | 查找到对应设备将返回相应的设备句柄 |
RT_NULL | 没有找到相应的设备对象 |
(2)初始化设备:获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作。当一个设备已经初始化成功后,调用这个接口将不再重复做初始化 0。
rt_err_t rt_device_init(rt_device_t dev);
参数 | 描述 |
---|---|
dev | 设备句柄 |
返回 | — — |
---|---|
RT_EOK | 设备初始化成功 |
错误码 | 设备初始化失败 |
(3)打开设备:通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 | 描述 |
---|---|
dev | 设备句柄 |
oflags | 设备打开模式标志 |
返回 | — — |
---|---|
RT_EOK | 设备打开成功 |
-RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 |
其他错误码 | 设备打开失败 |
oflags 支持以下的参数:
#define RT_DEVICE_OFLAG_CLOSE 0x000 /* 设备已经关闭(内部使用)*/
#define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只读方式打开设备 */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只写方式打开设备 */
#define RT_DEVICE_OFLAG_RDWR 0x003 /* 以读写方式打开设备 */
#define RT_DEVICE_OFLAG_OPEN 0x008 /* 设备已经打开(内部使用)*/
#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 发送模式打开 */
注:如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。
(4)关闭设备:不需要再对设备进行操作则可以关闭设备。
rt_err_t rt_device_close(rt_device_t dev);
参数 | 描述 |
---|---|
dev | 设备句柄 |
返回 | — — |
---|---|
RT_EOK | 关闭设备成功 |
-RT_ERROR | 设备已经完全关闭,不能重复关闭设备 |
其他错误码 | 关闭设备失败 |
注:关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。
(5)控制设备:通过命令控制字,应用程序也可以对设备进行控制
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数 | 描述 |
---|---|
dev | 设备句柄 |
cmd | 命令控制字,这个参数通常与设备驱动程序相关 |
arg | 控制的参数 |
返回 | — — |
---|---|
RT_EOK | 函数执行成功 |
-RT_ENOSYS | 执行失败,dev 为空 |
其他错误码 | 执行失败 |
参数 cmd 的通用设备命令可取如下宏定义:
#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */
#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */
#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */
#define RT_DEVICE_CTRL_SET_INT 0x10 /* 设置中断 */
#define RT_DEVICE_CTRL_CLR_INT 0x11 /* 清中断 */
#define RT_DEVICE_CTRL_GET_INT 0x12 /* 获取中断状态 */
(6)读设备:应用程序从设备中读取数据,调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 读取数据偏移量 |
buffer | 内存缓冲区指针,读取的数据将会被保存在缓冲区中 |
size | 读取数据的大小 |
返回 | — — |
---|---|
读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位 |
0 | 需要读取当前线程的 errno 来判断错误状态 |
(7)写设备:向设备中写入数据,调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 写入数据偏移量 |
buffer | 内存缓冲区指针,放置要写入的数据 |
size | 写入数据的大小 |
返回 | — — |
---|---|
写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位 |
0 | 需要读取当前线程的 errno 来判断错误状态 |
(8)数据接收回调:当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达。
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));
参数 | 描述 |
---|---|
dev | 设备句柄 |
rx_ind | 回调函数指针 |
返回 | — — |
---|---|
RT_EOK | 设置成功 |
- 该函数的回调函数由调用者提供。当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在 size 参数中传递给上层应用。上层应用线程应在收到指示后,立刻从设备中读取数据。
(9)数据发送回调:应用程序调用 rt_device_write()
写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。
参数 | 描述 |
---|---|
dev | 设备句柄 |
tx_done | 回调函数指针 |
返回 | — — |
---|---|
RT_EOK | 设置成功 |
- 调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。
三、收发函数以及校验
1.接收函数线程:在一个线程中进行数据的接收,通过接收中断来实现。
g_rs485.dev->read(g_user_data.dev, 0, &ch, 1); // 接收一个字节的数据
2.发送函数:将接收到的数据通过发送函数,进行发送。
g_rs485.dev->write(RT_NULL, 0, g_rs485.queue, g_rs485.qpush_index); // 将数据发送出去
3.串口接收中断:通过使用信号量,当接收到数据时,释放信号量,进行数据接收。
/**
* @brief 串口接收回调函数
*/
static rt_err_t rx_callback(rt_device_t dev, rt_size_t size)
{
/*串口接收到数据后,马上释放信号量,以便读取数据的线程运行*/
rt_sem_release(g_rs485.rx_sem);
return RT_EOK;
}
4.CRC校验:通过函数实现ModbusCRC16数据校验码
/**
* @brief CRC校验码生成
* @param CRC_Ptr->数组指针,LEN->长度
* @retval CRC校验值
*/
rt_uint16_t CRC_Check(uint8_t *CRC_Ptr, uint8_t LEN)
{
rt_uint16_t CRC_Value = 0;
rt_uint8_t i = 0;
rt_uint8_t j = 0;
CRC_Value = 0xffff;
// LEN为数组长度
for (i = 0; i < LEN; i++)
{
CRC_Value ^= *(CRC_Ptr + i);
for (j = 0; j < 8; j++)
{
if (CRC_Value & 0x00001)
CRC_Value = (CRC_Value >> 1) ^ 0xA001;
else
CRC_Value = (CRC_Value >> 1);
}
}
// 交换高低字节
CRC_Value = ((CRC_Value >> 8) + (CRC_Value << 8));
return CRC_Value;
}
四、完整代码
1.rs485.h
#ifndef APPLICATIONS_RS485_H_
#define APPLICATIONS_RS485_H_
#include <rtthread.h>
#include <drv_usart.h>
#include <rtdef.h>
/**====================================================###### 宏定义 ######==================================================*/
#define RS485_DEV_NAME "RS485"
#define RS485_UART_DEV_NAME "uart3"
#define QUEUE_SIZE 100 // 接收字节大小
#define RT_DEVICE_CTRL_LOCK 0x13
#define RT_DEVICE_CTRL_UNLOCK 0x14
// 串口初始化参数
#define RT_RS485_SERIAL_CONFIG \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
RT_SERIAL_FLOWCONTROL_NONE, /* Off flowcontrol */\
0 \
}
typedef struct
{
rt_device_t dev; // 设备名称
rt_uint32_t timout_us; // 超时时间
const char *name; // 设备名字
} rs485_user_t;
typedef struct
{
rt_device_t dev; // 注册设备
rt_sem_t rx_sem; // 信号量
rt_thread_t thread; // RS485线程
rt_uint8_t qpush_index; // 队列序号
rt_uint8_t *queue; // 队列
} rs485_dev_t;
struct sram_device
{
struct rt_device parent;
struct rt_mutex lock;
};
rs485_dev_t g_rs485; // 注册设备结构体
rs485_user_t g_user_data; // 用户数据结构体
#define lock(x) rt_mutex_take(&x->lock, RT_WAITING_FOREVER)
#define unlock(x) rt_mutex_release(&x->lock)
/**====================================================####### END #######=================================================*/
#endif /* APPLICATIONS_RS485_H_ */
2.rs485.c
#include "rs485.h"
#include <rtdevice.h>
struct serial_configure rs485_config = RT_RS485_SERIAL_CONFIG; /* 初始化串口配置参数 */
/*=====================================================### 静态函数调用 ###==================================================*/
/**
* @brief CRC校验码生成
* @param CRC_Ptr->数组指针,LEN->长度
* @retval CRC校验值
*/
rt_uint16_t CRC_Check(uint8_t *CRC_Ptr, uint8_t LEN)
{
rt_uint16_t CRC_Value = 0;
rt_uint8_t i = 0;
rt_uint8_t j = 0;
CRC_Value = 0xffff;
// LEN为数组长度
for (i = 0; i < LEN; i++)
{
CRC_Value ^= *(CRC_Ptr + i);
for (j = 0; j < 8; j++)
{
if (CRC_Value & 0x00001)
CRC_Value = (CRC_Value >> 1) ^ 0xA001;
else
CRC_Value = (CRC_Value >> 1);
}
}
// 交换高低字节
CRC_Value = ((CRC_Value >> 8) + (CRC_Value << 8));
return CRC_Value;
}
/**
* @brief 串口接收回调函数
*/
static rt_err_t rx_callback(rt_device_t dev, rt_size_t size)
{
/*串口接收到数据后,马上释放信号量,以便读取数据的线程运行*/
rt_sem_release(g_rs485.rx_sem);
return RT_EOK;
}
/**
* @brief RS485接收线程
* @param param
*/
void rs485_rx_thread_entry(void *param)
{
char ch;
g_rs485.qpush_index = 0;
rt_err_t result;
rt_uint16_t CRC_Value = 0;
while (1)
{
if (g_rs485.qpush_index >= QUEUE_SIZE)
g_rs485.qpush_index = 0;
// 信号量设置超时时间
result = rt_sem_take(g_rs485.rx_sem, 1000);
if (result == RT_EOK)
{
g_rs485.dev->read(g_user_data.dev, 0, &ch, 1);
g_rs485.queue[g_rs485.qpush_index++] = ch;
// 接收数据超过三个
if (g_rs485.qpush_index > 3)
{
// CRC校验
CRC_Value = CRC_Check(g_rs485.queue, (g_rs485.qpush_index - 2));
// 校验合格才会处理数据
if ((g_rs485.queue[g_rs485.qpush_index - 1] == ((char) CRC_Value))
&& (g_rs485.queue[g_rs485.qpush_index - 2] == (char) (CRC_Value >> 8)))
{
g_rs485.dev->write(RT_NULL, 0, g_rs485.queue, g_rs485.qpush_index);
g_rs485.qpush_index = 0;
}
}
}
else
{
g_rs485.qpush_index = 0;
}
}
}
/*=====================================================####### END #######=================================================*/
/*=====================================================##### 设备注册 #####==================================================*/
/**
* @brief 设备初始化接口
* @return
*/
rt_err_t rs485_usart_init(rt_device_t dev)
{
g_rs485.queue = rt_malloc(QUEUE_SIZE);
// 查找创建的设备
g_rs485.dev = (rt_device_t) rt_device_find(RS485_DEV_NAME);
RT_ASSERT(g_rs485.dev != RT_NULL);
// 配置创建设备的参数
rt_device_control(g_rs485.dev, RT_DEVICE_CTRL_CONFIG, (void *)&g_user_data);
return RT_EOK;
}
INIT_ENV_EXPORT(rs485_usart_init);
/**
* @brief 设备打开接口
* @return
*/
rt_err_t rs485_usart_open(rt_device_t dev, rt_uint16_t oflag)
{
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
rt_device_open(user->dev, RT_DEVICE_FLAG_INT_RX);
return RT_EOK;
}
/**
* @brief 设备关闭接口
* @return
*/
rt_err_t rs485_usart_close(rt_device_t dev)
{
return RT_EOK;
}
/**
* @brief 设备读取接口
* @return
*/
rt_size_t rs485_usart_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
size = rt_device_read(user->dev, 0, buffer, 1);
return size;
}
/**
* @brief 设备写入接口
* @return
*/
rt_size_t rs485_usart_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
if (g_user_data.name == RT_NULL)
return 0;
rs485_user_t *user = (rs485_user_t *)g_rs485.dev->user_data;
user->dev->write(user->dev, pos, buffer, size);
return 1;
}
/**
* @brief 设备控制接口
* @return
*/
rt_err_t rs485_usart_control(rt_device_t dev, int cmd, void *args)
{
int ret = -RT_ERROR;
struct sram_device *sdcard;
RT_ASSERT(dev != 0);
sdcard = (struct sram_device *)dev;
switch (cmd)
{
// 设置互斥锁
case RT_DEVICE_CTRL_LOCK:
lock(sdcard);
ret = RT_EOK;
break;
// 解除互斥锁
case RT_DEVICE_CTRL_UNLOCK:
unlock(sdcard);
ret = RT_EOK;
break;
default:
ret = -RT_ERROR;
break;
}
return ret;
}
/**
* @brief 串口3参数初始化
*/
void usart3_param_init(void)
{
rt_err_t ret;
// 查找串口设备获取设备句柄
rt_device_t serial_dev = (rt_device_t) rt_device_find(RS485_UART_DEV_NAME);
RT_ASSERT(serial_dev != RT_NULL);
// 打开串口,可读可写|接收中断方式打开
ret = rt_device_open(serial_dev, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
RT_ASSERT(ret == RT_EOK);
// 配置串口参数
rt_device_control(serial_dev, RT_DEVICE_CTRL_CONFIG, (void *) &rs485_config);
// 设置接收中断函数
rt_device_set_rx_indicate(serial_dev, rx_callback);
// 创建信号量
g_rs485.rx_sem = rt_sem_create("rs485_sem", 0, RT_IPC_FLAG_PRIO);
// 初始化用户数据
g_user_data.name = RS485_DEV_NAME;
g_user_data.timout_us = 1;
g_user_data.dev = serial_dev;
// 创建接收线程
g_rs485.thread = rt_thread_create("rs485_rx", rs485_rx_thread_entry, serial_dev, 1024, 16, 20);
if (g_rs485.thread == RT_NULL)
{
rt_kprintf("rt_thread_create failedread_th...\n");
}
else
{
// 启动线程
rt_thread_startup(g_rs485.thread);
}
}
/**
* @brief RS485设备注册
* @return 0:成功 -1:失败
*/
static int RS485_Device_Register(void)
{
rt_err_t ret;
// 开辟一个存储空间
g_rs485.dev = rt_malloc(sizeof(struct rt_device));
rt_memset(g_rs485.dev, 0, sizeof(struct rt_device));
// 串口3参数初始化
usart3_param_init();
// 结构体初始化
g_rs485.dev->type = RT_Device_Class_Char; // 设备类型
g_rs485.dev->init = rs485_usart_init; // 设备初始化接口
g_rs485.dev->open = rs485_usart_open; // 设备打开接口
g_rs485.dev->close = rs485_usart_close; // 设备关闭接口
g_rs485.dev->read = rs485_usart_read; // 设备读取接口
g_rs485.dev->write = rs485_usart_write; // 设备写入接口
g_rs485.dev->control = rs485_usart_control; // 设备控制接口
g_rs485.dev->user_data = &g_user_data; // 设备数据
// 设备注册
ret = rt_device_register(g_rs485.dev, RS485_DEV_NAME, RT_DEVICE_FLAG_RDWR);
return ret;
}
INIT_DEVICE_EXPORT(RS485_Device_Register);
/*=====================================================####### END #######=================================================*/
五、测试现象
通过发送ModBus的指令,来测试程序是否可以使用,并且串口可以自动加CRC16校验: