clion开发threadx+gd32f425驱动(串口驱动)

前言

  1. 本次使用的os为threadx,版本为6.4
  2. 芯片使用的是gd32f425,开发库为GD官方提供的标准库
  3. 开发环境使用的使用clion
  4. 参考驱动程序为arm硬汉的stm32f4 串口驱动程序编写而成
  5. 目前驱动只做了串口0的中断收发,如需其他串口请自行添加,根据实际情况可以在此基础上增加dma和空闲中断的方式

串口驱动

头文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 24-8-18     shchl   first version
 */

#ifndef TX_GD32F425_DRV_USART_H
#define TX_GD32F425_DRV_USART_H

#define UART0_FIFO_EN (1)


/* 串口设备结构体 */
typedef struct uart_struct {
    uint32_t uart_periph;        /* STM32内部串口设备指针 */
    uint8_t *pTxBuf;            /* 发送缓冲区 */
    uint8_t *pRxBuf;            /* 接收缓冲区 */
    uint16_t usTxBufSize;       /* 发送缓冲区大小 */
    uint16_t usRxBufSize;       /* 接收缓冲区大小 */
    __IO uint16_t usTxWrite;    /* 发送缓冲区写指针 */
    __IO uint16_t usTxRead;     /* 发送缓冲区读指针 */
    __IO uint16_t usTxCount;    /* 等待发送的数据个数 */

    __IO uint16_t usRxWrite;    /* 接收缓冲区写指针 */
    __IO uint16_t usRxRead;        /* 接收缓冲区读指针 */
    __IO uint16_t usRxCount;    /* 还未读取的新数据个数 */
    void (*RS485Init)(struct uart_struct *uart);  /* 485 io 引脚初始化 */
    void (*SendBefore)(struct uart_struct *uart);    /* 开始发送之前的回调函数指针(主要用于RS485切换到发送模式) */
    void (*SendOver)(struct uart_struct *uart);    /* 发送完毕的回调函数指针(主要用于RS485将发送模式切换为接收模式) */
    void (*ReceiveNew)(struct uart_struct *uart, uint8_t _byte);    /* 串口收到数据的回调函数指针 */
    uint8_t Sending;            /* 正在发送中 */
} uart_t;

#ifdef UART0_FIFO_EN
#define UART0_BAUD          115200
#define UART0_TX_BUF_SIZE 1024
#define UART0_RX_BUF_SIZE 1024
#endif


void drv_usart_callback_set(uint32_t uart_periph,
                            void (*pRS485Init)(uart_t *uart),
                            void (*pSendBefore)(uart_t *uart),
                            void (*pSendOver)(uart_t *uart),
                            void (*pReceiveNew)(uart_t *uart, uint8_t _byte)
);

void drv_usart_init(void);

uart_t *uart_get_by_periph(uint32_t uart_periph);

void uart_send(uint32_t uart_periph, uint8_t *_ucaBuf, uint16_t _usLen);

void uart_send_block(uint32_t uart_periph, uint8_t *_ucaBuf, uint16_t _usLen);

uint8_t uart_get_byte(uint32_t uart_periph, uint8_t *_pByte);

uint16_t uart_get_bytes(uint32_t uart_periph, uint8_t *_pBuf, uint16_t _usLen);


#endif //TX_GD32F425_DRV_USART_H


源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 24-8-18     shchl   first version
 */
#include "board.h"
#include "drv_usart.h"

#define USART0_TX_CLK RCU_GPIOA
#define USART0_TX_PORT GPIOA
#define USART0_TX_PIN GPIO_PIN_9
#define USART0_TX_AF  GPIO_AF_7
#define USART0_RX_CLK RCU_GPIOA
#define USART0_RX_PORT GPIOA
#define USART0_RX_PIN GPIO_PIN_10
#define USART0_RX_AF  GPIO_AF_7

static void usart_gpio_init(uint32_t tx_port, uint32_t tx_pin, uint32_t tx_af,
                            uint32_t rx_port, uint32_t rx_pin, uint32_t rx_af) {

    /* configure the USART0 TX pin and USART0 RX pin */
    gpio_af_set(tx_port, rx_af, rx_pin);
    gpio_af_set(rx_port, tx_af, tx_pin);
    /* configure USART0 TX as alternate function push-pull */
    gpio_mode_set(tx_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, tx_pin);
    gpio_output_options_set(tx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, tx_pin);
    /* configure USART0 RX as alternate function push-pull */
    gpio_mode_set(rx_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, rx_pin);
    gpio_output_options_set(rx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, rx_pin);

}


/* 定义每个串口结构体变量 */
#if UART0_FIFO_EN == 1
static uart_t g_tUart0 = {0};
static uint8_t g_TxBuf0[UART0_TX_BUF_SIZE];        /* 发送缓冲区 */
static uint8_t g_RxBuf0[UART0_RX_BUF_SIZE];        /* 接收缓冲区 */
#endif

static void usart_var_init(void);

static void usart_hard_init(void);

static void usart_rs485_init(void);

static void uart_real_send(uart_t *_pUart, const uint8_t *_ucaBuf, uint16_t _usLen);

static uint8_t uart_real_get_byte(uart_t *_pUart, uint8_t *_pByte);

static uint16_t uart_real_get_bytes(uart_t *_pUart, uint8_t *_pucaBuf, uint16_t _usLen);

/**
 * @brief 设置USART回调函数
 *
 * 本函数用于为指定的USART外设设置相应的回调函数,这些回调函数用于在数据发送、接收等操作前后执行特定的操作。
 * 主要用于实现RS485通信的初始化和数据传输过程中的控制。
 *
 * @param uart_periph UART外设标识符,用于指定要设置的USART外设
 * @param pRS485Init RS485初始化回调函数指针,用于在UART初始化时调用
 * @param pSendBefore 发送数据前的回调函数指针,用于在发送数据前调用
 * @param pSendOver 发送数据完成后的回调函数指针,用于在发送数据完成后调用
 * @param pReceiveNew 接收到新数据后的回调函数指针,用于在接收到新数据后调用
 */
void drv_usart_callback_set(uint32_t uart_periph,
                            void (*pRS485Init)(uart_t *uart),
                            void (*pSendBefore)(uart_t *uart),
                            void (*pSendOver)(uart_t *uart),
                            void (*pReceiveNew)(uart_t *uart, uint8_t _byte)
) {
    // 定义一个指向uart_t结构体的指针
    uart_t *pUart;

    // 根据UART外设标识符获取对应的uart_t结构体指针
    pUart = uart_get_by_periph(uart_periph);
    // 如果获取的指针为空,直接返回,不进行数据发送
    if (pUart == 0) {
        return;
    }
    // 设置uart_t结构体中的各个回调函数指针
    pUart->RS485Init = pRS485Init;
    pUart->SendBefore = pSendBefore;
    pUart->SendOver = pSendOver;
    pUart->ReceiveNew = pReceiveNew;
}


void drv_usart_init(void) {
    /* 初始化串口变量 */
    usart_var_init();
    /* 配置串口的硬件参数(波特率等) */
    usart_hard_init();

    usart_rs485_init();
}

uart_t *uart_get_by_periph(uint32_t uart_periph) {
#if UART0_FIFO_EN == 1
    if (uart_periph == USART0) return &g_tUart0;
#endif
    return NULL;
}


/**
 * 通过UART外设发送数据。
 *
 * @param uart_periph UART外设标识符,用于选择具体的UART外设。
 * @param _ucaBuf 指向发送缓冲区的指针,存储待发送的数据。
 * @param _usLen 发送数据的长度,单位为字节。
 *
 * 该函数首先根据UART外设标识符获取对应的uart_t结构体指针。
 * 如果获取到的指针为0(空指针),则直接返回,不进行数据发送。
 * 如果指针不为空,且该UART外设在发送前需要执行特定的操作(如RS485的发送模式设置),
 * 则调用对应的函数完成这些操作。
 * 最后调用uart_real_send函数实现数据的实际发送。
 */
void uart_send(uint32_t uart_periph, uint8_t *_ucaBuf, uint16_t _usLen) {
    uart_t *pUart;

    // 根据UART外设标识符获取对应的uart_t结构体指针
    pUart = uart_get_by_periph(uart_periph);
    // 如果获取的指针为空,直接返回,不进行数据发送
    if (pUart == 0) {
        return;
    }
    // 如果该UART外设在发送前需要执行特定的操作,则调用对应的函数完成这些操作
    if (pUart->SendBefore != 0) {
        pUart->SendBefore(pUart);        /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */
    }
    // 调用uart_real_send函数实现数据的实际发送
    uart_real_send(pUart, _ucaBuf, _usLen);
}


/**
 * @brief 通过UART外设发送数据块
 *
 * 本函数通过指定的UART外设发送一个数据块。首先,它根据外设地址获取UART实例。
 * 如果存在有效的UART实例,则开始发送数据。如果UART实例配置了发送前的回调函数,
 * 则调用该回调函数,这通常用于处理如RS485通信的发送模式设置等预发送操作。
 * 数据发送通过循环逐一发送数据块中的每个字节,并等待每个字节发送完成。
 * 最后,如果配置了发送完成后的回调函数,则调用该回调函数,以处理发送完成后的操作。
 *
 * @param uart_periph UART外设地址,标识要使用的UART外设
 * @param _ucaBuf 指向要发送的数据块的指针
 * @param _usLen 要发送的数据块的长度(字节数)
 */
void uart_send_block(uint32_t uart_periph, uint8_t *_ucaBuf, uint16_t _usLen) {
    // 获取指定UART外设的UART实例
    uart_t *pUart;
    pUart = uart_get_by_periph(uart_periph);
    // 如果没有找到有效的UART实例,直接返回
    if (pUart == 0) {
        return;
    }
    // 如果UART实例配置了发送前的回调函数,则调用该函数
    if (pUart->SendBefore != 0) {
        pUart->SendBefore(pUart);        /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */
    }
    // 循环发送数据块中的每个字节
    for (int i = 0; i < _usLen; ++i) {
        // 发送一个字节数据,并等待发送缓冲区为空
        usart_data_transmit(uart_periph, _ucaBuf[i]);
        while (!usart_flag_get(uart_periph, USART_FLAG_TBE));
    }
    // 如果配置了发送完成后的回调函数,则调用该函数
    if (pUart->SendOver) {
        pUart->SendOver(pUart);
    }

}


uint8_t uart_get_byte(uint32_t uart_periph, uint8_t *_pByte) {
    uart_t *pUart;
    pUart = uart_get_by_periph(uart_periph);
    if (pUart == 0) {
        return 0;
    }
    return uart_real_get_byte(pUart, _pByte);
}

uint16_t uart_get_bytes(uint32_t uart_periph, uint8_t *_pBuf, uint16_t _usLen) {
    uart_t *pUart;
    pUart = uart_get_by_periph(uart_periph);
    if (pUart == 0) {
        return 0;
    }

    return uart_real_get_bytes(pUart, _pBuf, _usLen);

}

static void usart_var_init(void) {

#if UART0_FIFO_EN == 1
    g_tUart0.uart_periph = USART0;              /*  串口设备 */
    g_tUart0.pTxBuf = g_TxBuf0;                    /* 发送缓冲区指针 */
    g_tUart0.pRxBuf = g_RxBuf0;                    /* 接收缓冲区指针 */
    g_tUart0.usTxBufSize = UART0_TX_BUF_SIZE;    /* 发送缓冲区大小 */
    g_tUart0.usRxBufSize = UART0_RX_BUF_SIZE;    /* 接收缓冲区大小 */
#endif


}


static void usart_hard_init(void) {
#if UART0_FIFO_EN == 1         /* 串口0 */
    /* USART interrupt configuration */
    nvic_irq_enable(USART0_IRQn, 0, 0);
    /* enable GPIO clock */
    rcu_periph_clock_enable(USART0_TX_CLK);
    rcu_periph_clock_enable(USART0_RX_CLK);
    /* enable USART clock */
    rcu_periph_clock_enable(RCU_USART0);
    usart_gpio_init(USART0_TX_PORT, USART0_TX_PIN, USART0_TX_AF,
                    USART0_RX_PORT, USART0_RX_PIN, USART0_RX_AF);
    /* USART configure */
    usart_deinit(USART0);
    usart_baudrate_set(USART0, UART0_BAUD);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);

    // 开启接收中断
    usart_interrupt_enable(USART0, USART_INT_RBNE);
#endif


}


static void usart_rs485_init(void) {
#if UART0_FIFO_EN == 1        /* 串口0 */
    if (g_tUart0.RS485Init) g_tUart0.RS485Init(&g_tUart0);
#endif
}


/**
 * @brief 实际发送UART数据的函数
 *
 * 该函数用于向指定的UART设备发送数据。它将数据逐字节地填充到UART的发送缓冲区中,
 * 并通过中断机制来完成实际的数据发送。当发送缓冲区已满时,函数会进入等待状态,
 * 直到缓冲区有可用空间为止。
 *
 * @param _pUart 指向UART设备结构体的指针,包含UART操作所需的所有配置信息。
 * @param _ucaBuf 指向待发送数据缓冲区的指针。
 * @param _usLen 待发送的数据长度,单位为字节。
 */
static void uart_real_send(uart_t *_pUart, const uint8_t *_ucaBuf, uint16_t _usLen) {
    uint16_t i;
    BSP_DEF_CRITICAL_SECTION
    for (i = 0; i < _usLen; i++) {
        // 等待发送缓冲区有足够的空间
        while (1) {
            __IO uint16_t usCount;

            BSP_ENTER_CRITICAL;
            usCount = _pUart->usTxCount;
            BSP_EXIT_CRITICAL;

            // 如果发送缓冲区未满,则可以继续发送
            if (usCount < _pUart->usTxBufSize) {
                break;
            } else if (usCount == _pUart->usTxBufSize) { // 发送缓冲区已满
                if (RESET == usart_interrupt_flag_get(_pUart->uart_periph, USART_INT_FLAG_TBE)) {
                    usart_interrupt_enable(_pUart->uart_periph, USART_INT_TBE);
                }
            }
        }

        // 将新数据填入发送缓冲区
        _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i];

        BSP_ENTER_CRITICAL;
        if (++_pUart->usTxWrite >= _pUart->usTxBufSize) {
            _pUart->usTxWrite = 0; // 循环缓冲区写指针
        }
        _pUart->usTxCount++; // 更新发送计数器
        BSP_EXIT_CRITICAL;
    }
    usart_interrupt_enable(_pUart->uart_periph, USART_INT_TBE); // 使能发送中断(缓冲区空)
}


/**
 * 从UART接收缓冲区中获取一个字节的数据
 *
 * 此函数用于从串口的接收缓冲区中取出一个字节的数据返回给调用者
 * 如果缓冲区为空,则返回0;否则,返回取出的数据,并更新缓冲区读取索引
 *
 * @param _pUart UART设备的结构体指针,指向包含接收缓冲区信息的结构体
 * @param _pByte 用于存储从UART接收缓冲区中取出的数据的指针
 * @return 如果缓冲区为空,返回0;否则,返回1表示成功取出一个字节的数据
 */
static uint8_t uart_real_get_byte(uart_t *_pUart, uint8_t *_pByte) {

    uint16_t usCount;
    BSP_DEF_CRITICAL_SECTION
    /* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */
    BSP_ENTER_CRITICAL;
    usCount = _pUart->usRxCount;
    BSP_EXIT_CRITICAL;

    /* 如果读和写索引相同,则返回0 */
    //if (_pUart->usRxRead == usRxWrite)
    if (usCount == 0)    /* 已经没有数据 */
    {
        return 0;
    } else {
        *_pByte = _pUart->pRxBuf[_pUart->usRxRead];        /* 从串口接收FIFO取1个数据 */

        /* 改写FIFO读索引 */
        BSP_ENTER_CRITICAL;
        if (++_pUart->usRxRead >= _pUart->usRxBufSize) {
            _pUart->usRxRead = 0;
        }
        _pUart->usRxCount--;
        BSP_EXIT_CRITICAL;
        return 1;
    }

}

static uint16_t uart_real_get_bytes(uart_t *_pUart, uint8_t *_pucaBuf, uint16_t _usLen) {
    // 检查缓冲区长度是否为0
    if (_usLen == 0) {
        return 0;
    }

    // 假设有一个方法可以用来从硬件读取数据到缓冲区
    // 这里使用一个循环来模拟读取过程
    uint16_t bytes_read = 0; // 已读取的字节数

    // 循环读取直到达到请求的长度或没有更多数据可读
    while (bytes_read < _usLen) {
        // 假设 uart_read_byte 是一个函数,它尝试从硬件读取一个字节
        // 如果成功,返回读取的字节;如果失败,返回一个特殊值(例如 -1)
        uint8_t byte;

        // 如果读取成功
        if (uart_real_get_byte(_pUart, &byte) > 0) {
            // 将读取的字节存储到缓冲区中
            _pucaBuf[bytes_read] = byte;
            // 更新已读取的字节数
            bytes_read++;
        } else {
            // 如果无法读取更多的数据,则退出循环
            break;
        }
    }

    // 返回实际读取的字节数
    return bytes_read;
}


static void UartIRQ(uart_t *_pUart);

/*!
    \brief      this function handles USART0 exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void USART0_IRQHandler(void) {

    UartIRQ(&g_tUart0);

}


static void UartIRQ(uart_t *_pUart) {
    BSP_DEF_CRITICAL_SECTION
    BSP_ENTER_CRITICAL
    if ((RESET != usart_interrupt_flag_get(_pUart->uart_periph, USART_INT_FLAG_RBNE)) &&
        (RESET != usart_flag_get(_pUart->uart_periph, USART_FLAG_RBNE))) {
        // 如果串口接收FIFO中还有数据,则继续接收,否则直接退出循环,避免频繁进入中断
        // 如果数据量不多时,可以采用此方案,数据量多的情况不建议此方案, 因为此方案会占用CPU中断时间
        while (1) {
            /* 从串口接收数据寄存器读取数据存放到接收FIFO */
            uint8_t ch;
            ch = usart_data_receive(_pUart->uart_periph);
            _pUart->pRxBuf[_pUart->usRxWrite] = ch;
            if (++_pUart->usRxWrite >= _pUart->usRxBufSize) {
                _pUart->usRxWrite = 0;
            }
            if (_pUart->usRxCount < _pUart->usRxBufSize) {
                _pUart->usRxCount++;
            }
            /* 回调函数,通知应用程序收到新数据,一般是发送1个消息或者设置一个标记 */
            {
                if (_pUart->ReceiveNew) {
                    _pUart->ReceiveNew(_pUart, ch); /* 比如,交给MODBUS解码程序处理字节流 */
                }
            }


            if (RESET == usart_interrupt_flag_get(_pUart->uart_periph, USART_INT_FLAG_RBNE) ||
                RESET == usart_flag_get(_pUart->uart_periph, USART_FLAG_RBNE)) {
                break;
            }

        }

    }

    if (/*(RESET != usart_flag_get(_pUart->uart_periph, USART_FLAG_TBE)) &&*/
            (RESET != usart_interrupt_flag_get(_pUart->uart_periph, USART_INT_FLAG_TBE))) {

        if (_pUart->usTxCount == 0) {
            /* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后1个数据还未真正发送完毕)*/
            usart_interrupt_disable(_pUart->uart_periph, USART_INT_TBE);
            /* 使能数据发送完毕中断 */
            usart_interrupt_enable(_pUart->uart_periph, USART_INT_TC);
        } else {
            _pUart->Sending = 1;
            /* 从发送FIFO取1个字节写入串口发送数据寄存器 */
            usart_data_transmit(_pUart->uart_periph, _pUart->pTxBuf[_pUart->usTxRead]);
            if (++_pUart->usTxRead >= _pUart->usTxBufSize) {
                _pUart->usTxRead = 0;
            }
            _pUart->usTxCount--;
        }


    }
    if ((RESET != usart_interrupt_flag_get(_pUart->uart_periph, USART_INT_FLAG_TC)) &&
        (RESET != usart_flag_get(_pUart->uart_periph, USART_FLAG_TC))) {
        if (_pUart->usTxCount == 0) {
            /* 如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断 */
            usart_interrupt_disable(_pUart->uart_periph, USART_INT_TC);
            /* 回调函数, 一般用来处理RS485通信,将RS485芯片设置为接收模式,避免抢占总线 */
            if (_pUart->SendOver) {
                _pUart->SendOver(_pUart);
            }
            _pUart->Sending = 0;
        } else {
            /* 正常情况下,不会进入此分支 */
            /* 如果发送FIFO的数据还未完毕,则从发送FIFO取1个数据写入发送数据寄存器 */
            usart_data_transmit(_pUart->uart_periph, _pUart->pTxBuf[_pUart->usTxRead]);
            if (++_pUart->usTxRead >= _pUart->usTxBufSize) {
                _pUart->usTxRead = 0;
            }
            _pUart->usTxCount--;
        }
    }
    BSP_EXIT_CRITICAL
}

测试

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 24-8-18     shchl   first version
 */

#include "tx_task.h"

// Allocate stack memory statically.
TX_THREAD my_thread;
static UCHAR my_thread_stack[4096] = {0};

VOID my_thread_entry(ULONG thread_input);

VOID tx_application_define(VOID *first_unused_memory) {


    ULONG status;


    // Create a new thread.
    status = tx_thread_create(&my_thread,
                              "MyThread",
                              my_thread_entry,
                              (ULONG) 0,
                              my_thread_stack,
                              4096, 2, 2,
                              TX_NO_TIME_SLICE,
                              TX_AUTO_START);

    // Check for errors in the thread creation.
    if (status != TX_SUCCESS) {
        // Handle error.
        log_printf("Thread creation failed with status: %d\n", status);
    }
}




// Thread entry function.
VOID my_thread_entry(ULONG thread_input) {
    // Thread code goes here.
    uint8_t data[128];
    while (1) {
        // Example task.
        uint16_t len = uart_get_bytes(USART0, data, 128);
        if (len > 0) {
            uart_send(USART0, data, len);
        }

        tx_thread_sleep(10);

    }
}

测试结果(ok)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

詩不诉卿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值