【RTT-Studio】详细使用教程二:RS485通信

一、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校验:
在这里插入图片描述

六、其他RS485例程

===》》》RS485使用DMA串行通信进行数据收发
===》》》RS485使用中断进行数据收发

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值