当前位置: 首页 > news >正文

14、nRF52xx蓝牙学习(串口 UART 和 UARTE 外设应用)

一、UART 功能描述
串口 UART 也称为通用异步收发器。是各种处理器中常用了通信接口,在 nRF52 芯片中, UART
具有以下特点:
● 全双工操作
● 自动流控
● 奇偶校验产生第 9 位数据
串口 UART 的数据发送与接收流程 :
◆硬件配置:
根据 PSELRXD、PSELCTS、PSELRTS 和 PSELTXD 寄存器的配置可以相应的将 RXD、CTS(发送清除、 低有效)、TXD、RTS(发送请求、低有效)映射到物理的引脚上。如果这些寄存器的任意一个设为 0xffffffff,相关的 UART 信号就不会连接到任务物理引脚上。这四个寄存器及其配置只能在 UART 使能时可用,可以在芯片为系统 ON 模式时保持。为了在系统处于 OFF 关闭模式时通过 UART 确保引 脚上的信号电平正确,必须按照 GPIO 外设中的说明在 GPIO 外设中配置引脚。如下表 7.1 所示:
◆UART 发送:
如图 7.1 所示,通过触发 STARTTX 任务启动 UART 传输序列。然后通过写入 TXD 寄存器来发
送字节。成功发送一个字节后,UART 将产生一个 TXDRDY 事件,之后可以将一个新字节写入 TXD 寄 存器。通过触发 STOPTX 任务立即停止 UART 传输序列。如果启用了流量控制,则在 CTS 取消激活时 将自动暂停传输,并在再次激活 CTS 时恢复。在 CTS 被停用时正在传输的字节将在传输暂停之前被 完全传输。
◆UART 接收:
通过触发 STARTRX 任务启动 UART 接收序列。UART 接收器连接了一个 FIFO,能够在数据被覆盖 之前存储六个传入的 RXD 字节。 通过读取 RXD 寄存器从该 FIFO 中提取字节。当从 FIFO 中提取一 个字节时,FIFO 中待处理的新字节将被移动到 RXD 寄存器。 每次将新字节移入 RXD 寄存器时,UART 都会产生 RXDRDY 事件。
当启用流量控制时,如果接收器 FIFO 中只有 4 个字节的空间时,UART 将禁用 RTS 信号。启用
流量控制状态下在重写数据之前,发送器能够在 RTS 信号激活之后发送多达四个字节。因此为防止 覆盖 FIFO 中的数据,对应的 UART 发送器必须确保在 RTS 线停用后在四个字节时间内停止发送数据。
当 FIFO 清空时,首先再次激活 RTS 信号;CPU 读取 FIFO 中的所有字节后,接收器通过 STOPRX 任务 停止时,RTS 信号也将被禁用;
为防止输入数据丢失,必须在每次 RXDRDY 事件后读取 RXD 寄存器一次。为了确保 CPU 可以通
过 RXDRDY 事件寄存器检测所有输入的 RXDRDY 事件,必须在读取 RXD 寄存器之前清零 RXDRDY 事件 寄存器。这样做的原因是允许 UART 将新字节写入 RXD 寄存器,在 CPU 读取(清空)RXD 寄存器后立 即生成新事件。
◆UART 挂起:
UART 串口可以通过触发 SUPSPEND 寄存器任务来挂起。SUPSPEND 将会影响 UART 发送器和 UART 接收器,设置后使发生器停止发送,接收器停止接收。在 UART 挂起后,通过相应的触发 STARTTX 和 STARTRX 就可以重新开启发送和接收。
当触发 SUPSPEND 任务时,UART 接收器和触发 STOPRX 任务一样的工作。
在触发 SUPSPEND 任务后,正在进行的 TXD 字节传输将在 UART 挂起前完成。
◆错误条件:
在一个错误帧出现的情况下,如果再此帧中没有检测到有效的停止位会产生一个 ERROR 事件。
另外,在中断时,如果 RXD 保持低电平超过一个数据帧长度时,也会产生一个 ERROR 事件。
◆流量控制:
nRF52 芯片的 UART 可以分为带流量控制和不带流量控制两种方式。不带流量控制时,不需要
连接 CTS RTS 两个管脚,可以视为两个管脚一值有效。
带流量控制时, RTS 引脚作为输出,由 UART 硬件模块自动控制。与接收寄存器的多级硬件缓
Buff 协调工作。比如在硬件缓冲已经接收满了 6 个字节的时, RTS 引脚就输出高电平的终止信号,
当缓冲中的数据都被读出后回复有效信号(低电平)。
CTS 作为输入由外部输入。当 CTS 有效时(低电平)模块可以发送,当为无效时,模块自动
暂停发送,并在 CTS 恢复有效时继续发送。
那么当 uart 模块的 rts cts 交叉相接。 如果发送方发送太快,当接收方的接收硬件 buff 已经
存满了字节后,接收方自动无效 RTS 信号,表示不能接收了。因为接收方 RTS 与发送方 CTS 相接,
使得发送方的 CTS 也编程无效信号,于是发送方自动停止发送。这样就保证了接收方不会接收溢出。
流量控制也就是体现在这里。
二、UARTE 功能介绍
UARTE 就是带有 EasyDMA 的通用异步接收器 / 发送器 UART 。提供快速、全双工、异步的串
行通信,内置流量控制( CTS RTS )支持硬件,速率高达 1 Mbps 。这里列出的是 UARTE 的主要功能:
• 全双工操作
• 自动硬件流控制
• 生成 9 位数据带奇偶校验
• EasyDMA
• 波特率高达 1 Mbps
• 在支持的事务之间返回 IDLE (使用 HW 流控制时)
• 一个停止位
• 最低有效位(LSB )优先
用于每个 UART 接口的 GPIO 可以从设备上的任何 GPIO 来选择并且独立地为可配置的。
这使得能够在器件的引脚和有效地利用电路板空间和信号路由很大的灵活性。 UARTE 的内部结构
如下所示:
对内部寄存器的说明如下表   所示:
UARTE 实现 EasyDMA 读取和写入,并存入 RAM。如果 TXD.PTR RXD.PTR 没有指向
数据 RAM 区, EasyDMA 传递可能导致 HardFault RAM 损坏。
.PTR .MAXCNT 寄存器是双缓冲的。他们可以在收到 RXSTARTED / TXSTARTED 事件后 立即进行更新,并在接下来的 RX / TX 传送准备。所述 ENDRX / ENDTX 事件表示 EasyDMA 已
完成访问分别在 RAM 中的 RX / TX 缓冲器。
UARTE 发送:
一个 UARTE 的发送的第一个步骤是存储的字节到发送缓冲器和配置 EasyDMA 。这个过程是
通过写起始地址到指针 TXD.PTR ,并在 RAM 缓冲器放置 TXD.MAXCNT 大小的字节数来实现的。
串口 UARTE 的发送是通过触发 STARTTX 任务开始,之后的每个字节在 TXD 线上发送时,会产
生一个 TXDRDY 事件。当在 TXD 缓冲器中的所有字节(在 TXD.MAXCNT 寄存器中指定数目)
已被传送时, UARTE 传输将自动结束,并且将产生一个 ENDTX 事件。
通过触发 STOPTX 任务来停止 UARTE 发送序列 ,当 UARTE 发射机已经停止,将产生一个
TXSTOPPED 事件。如果在 UARTE 发射机已经停下来但尚未产生 ENDTX 事件时, UARTE
明确产生一个 ENDTX 事件,即使在 TXD 缓冲区中的所有字节( TXD.MAXCNT 寄存器中指定)
还没有被发送。
如果启用了流量控制,则在 CTS 取消激活时将自动暂停传输,并在再次激活 CTS 时恢复。在
CTS 被停用时正在传输的字节将在传输暂停之前被完全传输。
UARTE 接收:
通过触发 STARTRX 任务启动 UARTE 接收器。 UARTE 接收器使用 EasyDMA 将输入数据存储
RAM 中的 RX 缓冲区中。 RX 缓冲区位于 RXD.PTR 寄存器中指定的地址。 RXD.PTR 寄存器是双 缓冲的,可以在生成 RXSTARTED 事件后立即更新并为下一个 STARTRX 任务做好准备。 RX 缓冲 区的大小在 RXD.MAXCNT 寄存器中指定, UARTE 在填充 RX 缓冲区时将生成 ENDRX 事件。
对于通过 RXD 线接收的每个字节,都将生成 RXDRDY 事件。在将相应的数据传输到数据 RAM
之前,可能会发生此事件。在 ENDRX 事件之后可以查询 RXD.AMOUNT 寄存器,以查看自上一次 ENDRX 事件以来有多少新字节已传输到 RAM 中的 RX 缓冲区。
通过触发 STOPRX 任务来停止 UARTE 接收器。 UARTE 停止时会生成 RXTO 事件。 UARTE
将确保在生成 RXTO 事件之前生成即将发生的 ENDRX 事件。 这意味着 UARTE 将保证在 RXTO
之后不会生成 ENDRX 事件,除非重新启动 UARTE 或在生成 RXTO 事件后发出 FLUSHRX 命令。
重要提示:如果在 UARTE 接收器停止时尚未生成 ENDRX 事件,这意味着 RX FIFO 中的所有
待处理内容都已移至 RX 缓冲区,则 UARTE 将显式生成 ENDRX 事件,即使 RX 缓冲区未满。 在 这种情况下,将在生成 RXTO 事件之前生成 ENDRX 事件。
为了能够知道实际接收到 RX 缓冲区的字节数, CPU 可以在 ENDRX 事件或 RXTO 事件之后读
RXD.AMOUNT 寄存器。只要在 RTS 信号被禁用后立即连续发送, UARTE 就可以在 STOPRX 任 务被触发后接收最多四个字节。这是可能的,因为在 RTS 取消激活后, UARTE 能够在一段延长的 时间内接收字节,该时间等于在配置的波特率上发送 4 个字节所需的时间。生成 RXTO 事件后,内 部 RX FIFO 可能仍包含数据,要将此数据移至 RAM ,必须触发 FLUSHRX 任务。
为确保此数据不会覆盖 RX 缓冲区中的数据,应在 FLUSHRX 任务被触发之前清空 RX 缓冲区
或更新 RXD.PTR 。为确保 RX FIFO 中的所有数据都移至 RX 缓冲区, RXD.MAXCNT 寄存器必须
设置为 RXD.MAXCNT> 4 ,通过 STOPRX 强制停止的 UARTE 接收。即使 RX FIFO 为空或 RX 缓 冲区未填满,UARTE 也会在完成 FLUSHRX 任务后生成 ENDRX 事件。 为了能够知道在这种情况 下实际接收到 RX 缓冲区的字节数, CPU 可以在 ENDRX 事件之后读取 RXD.AMOUNT 寄存器。
如果启用了 HW 流量控制,当接收器通过 STOPRX 任务停止或 UARTE 只能在其内部 RX FIFO
中接收 4 个字节时, RTS 信号将被禁用。禁用流量控制后, UARTE 将以与启用流量控制时相同的
方式运行,但不会使用 RTS 线路。这意味着当 UARTE 达到只能在其内部 RX FIFO 中接收四个字节 的点时,不会产生任何信号。内部 RX FIFO 填满时接收的数据将丢失。 UARTE 接收器将处于最低 活动水平,并在停止时消耗最少的能量,即在通过 STARTRX 启动之前或通过 STOPRX 停止并且已 生成 RXTO 事件之后。
实例1:串口 printf 输出
硬件连接方面,通过高质量芯片 CH340T 把串口信号转换成 usb 输出 , TXD-- 接端口 P0.09
RXD-- 接端口 P0.11 CTS-- 接端口 P0.08 RTS-- 接端口 P0.10 端,其中数据口 TXD RXD 分别 接 1K 电阻上拉,提高驱动能力,如下图   所示
在代码文件中,实验建立了一个演示历程,我们还是采用分层思想,直接通过官方提供的组
件库进行编程。打开文件夹中的 uart 工程,添加 UART 的组件库工程如下图   所示:
红色框框内的文件是需要在建立串口工程时进行添加的库文件,对这几个添加的库
文件说明如下表   所示:
如上表   所示: nrf_drv_uart.c 文件和 app_uart_fifi.c 文件是官方编写好的驱动库文件。这两个
文件在后面编写带协议栈的 BLE 应用时,是可以直接用于其中配置串口功能的。因此理解这两个文 件提供的 API 函数是利用组件编写串口的必由之路。
app_uart.h 文 件 中 提 供 了 两 个 关 键 的 uart 的 初 始 化 函 数 APP_UART_FIFO_INIT
APP_UART_INIT ,一个是带 FIFO 缓冲的初始化串口函数,一个是不带 FIFO 缓冲的初始化函数, 一般情况下使用带软件缓冲的 FIF0 的函数,减小数据溢出错误的发生几率。
下面学习几个库函数:
(1) APP_UART_FIFO_INIT
#define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \do                                                                                             \{                                                                                              \app_uart_buffers_t buffers;                                                                \static uint8_t     rx_buf[RX_BUF_SIZE];                                                    \static uint8_t     tx_buf[TX_BUF_SIZE];                                                    \\buffers.rx_buf      = rx_buf;                                                              \buffers.rx_buf_size = sizeof (rx_buf);                                                     \buffers.tx_buf      = tx_buf;                                                              \buffers.tx_buf_size = sizeof (tx_buf);                                                     \ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO);                  \} while (0)

代码理解:

代码概述 此代码定义了一个名为 APP_UART_FIFO_INIT 的宏,其用途是初始化一个基于 FIFO(先进先出)机制的 UART(通用异步收发传输器)通信。借助这个宏,你能够轻松地设置 UART 通信所需的参数,包含接收缓冲区、发送缓冲区、事件处理函数以及中断优先级等。

宏定义参数

• P_COMM_PARAMS:指向 UART 通信参数结构体的指针,该结构体里包含了波特率、数据位、停止位等 UART 通信所需的基本参数。


结构体定义如下 :

typedef struct
{uint32_t                rx_pin_no;    /**< RX pin number. */uint32_t                tx_pin_no;    /**< TX pin number. */uint32_t                rts_pin_no;   /**< RTS pin number, only used if flow control is enabled. */uint32_t                cts_pin_no;   /**< CTS pin number, only used if flow control is enabled. */app_uart_flow_control_t flow_control; /**< Flow control setting, if flow control is used, the system will use low power UART mode, based on CTS signal. */bool                    use_parity;   /**< Even parity if TRUE, no parity if FALSE. */uint32_t                baud_rate;    /**< Baud rate configuration. */
} app_uart_comm_params_t;

结构体解析:用于配置 UART(通用异步收发传输器)通信参数。下面解释结构体中每个成员的作用:

// 定义一个名为 app_uart_comm_params_t 的结构体,用于配置 UART 通信参数
typedef struct
{// RX 引脚编号,用于接收数据uint32_t                rx_pin_no;    /**< RX pin number. */// TX 引脚编号,用于发送数据uint32_t                tx_pin_no;    /**< TX pin number. */// RTS(请求发送)引脚编号,仅在启用流控制时使用uint32_t                rts_pin_no;   /**< RTS pin number, only used if flow control is enabled. */// CTS(清除发送)引脚编号,仅在启用流控制时使用uint32_t                cts_pin_no;   /**< CTS pin number, only used if flow control is enabled. */// 流控制设置,如果使用流控制,系统将基于 CTS 信号使用低功耗 UART 模式app_uart_flow_control_t flow_control; /**< Flow control setting, if flow control is used, the system will use low power UART mode, based on CTS signal. */// 是否使用奇偶校验,TRUE 表示使用偶校验,FALSE 表示不使用奇偶校验bool                    use_parity;   /**< Even parity if TRUE, no parity if FALSE. */// 波特率配置,用于设置数据传输的速率uint32_t                baud_rate;    /**< Baud rate configuration. */
} app_uart_comm_params_t;

其中:app_uart_flow_control_t(控制流)是一个枚举类型,其定义如下:

typedef enum
{
    APP_UART_FLOW_CONTROL_DISABLED, /**< UART Hw Flow Control is disabled. */
    APP_UART_FLOW_CONTROL_ENABLED,  /**< Standard UART Hw Flow Control is enabled. */
} app_uart_flow_control_t;

// 定义 app_uart_flow_control_t 枚举类型,用于表示 UART 硬件流控制状态
typedef enum
{
    // UART 硬件流控制禁用
    APP_UART_FLOW_CONTROL_DISABLED, /**< UART Hw Flow Control is disabled. */
    // 标准 UART 硬件流控制启用
    APP_UART_FLOW_CONTROL_ENABLED,  /**< Standard UART Hw Flow Control is enabled. */
} app_uart_flow_control_t;


再回到开始的宏参数:

• RX_BUF_SIZE:接收缓冲区的大小,也就是用于存储从 UART 接收到的数据的缓冲区大小。 • TX_BUF_SIZE:发送缓冲区的大小,即用于存储要通过 UART 发送出去的数据的缓冲区大小。 • EVT_HANDLER:UART 事件处理函数的指针,当 UART 发生特定事件(像数据接收、发送完成等)时,会调用这个函数。

• IRQ_PRIO:UART 中断的优先级,用于设置 UART 中断在系统中的优先级。

• ERR_CODE:用于存储 UART 初始化的错误码,若初始化成功,该变量会被赋值为 0;若失败,则会被赋值为相应的错误码。  


代码详细解析

#define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \
    do                                                                                             \
    {                                                                                              \
        app_uart_buffers_t buffers;                                                                \
        static uint8_t     rx_buf[RX_BUF_SIZE];                                                    \
        static uint8_t     tx_buf[TX_BUF_SIZE];                                                    \
                                                                                                   \
        buffers.rx_buf      = rx_buf;                                                              \
        buffers.rx_buf_size = sizeof (rx_buf);                                                     \
        buffers.tx_buf      = tx_buf;                                                              \
        buffers.tx_buf_size = sizeof (tx_buf);                                                     \
        ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO);                  \
    } while (0)



     1.  定义缓冲区和变量:

app_uart_buffers_t buffers;:定义一个 app_uart_buffers_t 类型的结构体变量 buffers,该结构体用于存储接收和发送缓冲区的信息。


app_uart_buffers_t 类型的结构体定义如下 :

typedef struct
{
    uint8_t * rx_buf;      /**< Pointer to the RX buffer. */
    uint32_t  rx_buf_size; /**< Size of the RX buffer. */
    uint8_t * tx_buf;      /**< Pointer to the TX buffer. */
    uint32_t  tx_buf_size; /**< Size of the TX buffer. */
} app_uart_buffers_t;


static uint8_t rx_buf[RX_BUF_SIZE];:定义一个静态的接收缓冲区数组 rx_buf,其大小由 RX_BUF_SIZE 决定。

static uint8_t tx_buf[TX_BUF_SIZE];:定义一个静态的发送缓冲区数组 tx_buf,其大小由 TX_BUF_SIZE 决定。  

2.  初始化缓冲区信息:

buffers.rx_buf = rx_buf;:把 rx_buf 数组的地址赋值给 buffers 结构体的 rx_buf 成员。

buffers.rx_buf_size = sizeof (rx_buf);:把 rx_buf 数组的大小赋值给 buffers 结构体的 rx_buf_size 成员。

buffers.tx_buf = tx_buf;:把 tx_buf 数组的地址赋值给 buffers 结构体的 tx_buf 成员。

buffers.tx_buf_size = sizeof (tx_buf);:把 tx_buf 数组的大小赋值给 buffers 结构体的 tx_buf_size 成员。  

3  调用 UART 初始化函数:

ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO);:调用 app_uart_init 函数来初始化 UART 通信,将初始化的错误码存储在 ERR_CODE 变量中。

   下面是一个简单的使用示例:


 #include <stdio.h>

// 假设的 UART 通信参数结构体
typedef struct {
    int baud_rate;
    // 其他参数...
} app_uart_comm_params_t;

// 假设的 UART 缓冲区结构体
typedef struct {
    uint8_t *rx_buf;
    size_t  rx_buf_size;
    uint8_t *tx_buf;
    size_t  tx_buf_size;
} app_uart_buffers_t;

// 假设的 UART 初始化函数
int app_uart_init(app_uart_comm_params_t *p_comm_params, app_uart_buffers_t *p_buffers, void (*evt_handler)(void), int irq_prio) {
    // 实际的初始化代码
    return 0; // 假设初始化成功
}

// 假设的 UART 事件处理函数
void uart_evt_handler(void) {
    // 处理 UART 事件
}

// 定义宏
#define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \
    do                                                                                             \
    {                                                                                              \
        app_uart_buffers_t buffers;                                                                \
        static uint8_t     rx_buf[RX_BUF_SIZE];                                                    \
        static uint8_t     tx_buf[TX_BUF_SIZE];                                                    \
                                                                                                   \
        buffers.rx_buf      = rx_buf;                                                              \
        buffers.rx_buf_size = sizeof (rx_buf);                                                     \
        buffers.tx_buf      = tx_buf;                                                              \
        buffers.tx_buf_size = sizeof (tx_buf);                                                     \
        ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO);                  \
    } while (0)

int main() {
    app_uart_comm_params_t comm_params = {115200}; // 波特率设置为 115200
    int err_code;

    APP_UART_FIFO_INIT(&comm_params, 256, 256, uart_evt_handler, 1, err_code);

    if (err_code == 0) {
        printf("UART initialization successful.\n");
    } else {
        printf("UART initialization failed with error code: %d\n", err_code);
    }

    return 0;
}



    在这个示例中,我们定义了一个简单的 UART 通信系统,并且使用 APP_UART_FIFO_INIT 宏来初始化 UART 通信。若初始化成功,会输出相应的提示信息;若失败,则会输出错误码。

(2)app_uart_init函数学习

uint32_t app_uart_init(const app_uart_comm_params_t * p_comm_params,app_uart_buffers_t *     p_buffers,app_uart_event_handler_t event_handler,app_irq_priority_t       irq_priority)
{uint32_t err_code;m_event_handler = event_handler;if (p_buffers == NULL){return NRF_ERROR_INVALID_PARAM;}// Configure buffer RX buffer.err_code = app_fifo_init(&m_rx_fifo, p_buffers->rx_buf, p_buffers->rx_buf_size);VERIFY_SUCCESS(err_code);// Configure buffer TX buffer.err_code = app_fifo_init(&m_tx_fifo, p_buffers->tx_buf, p_buffers->tx_buf_size);VERIFY_SUCCESS(err_code);nrf_drv_uart_config_t config = NRF_DRV_UART_DEFAULT_CONFIG;config.baudrate = (nrf_uart_baudrate_t)p_comm_params->baud_rate;config.hwfc = (p_comm_params->flow_control == APP_UART_FLOW_CONTROL_DISABLED) ?NRF_UART_HWFC_DISABLED : NRF_UART_HWFC_ENABLED;config.interrupt_priority = irq_priority;config.parity = p_comm_params->use_parity ? NRF_UART_PARITY_INCLUDED : NRF_UART_PARITY_EXCLUDED;config.pselcts = p_comm_params->cts_pin_no;config.pselrts = p_comm_params->rts_pin_no;config.pselrxd = p_comm_params->rx_pin_no;config.pseltxd = p_comm_params->tx_pin_no;err_code = nrf_drv_uart_init(&app_uart_inst, &config, uart_event_handler);VERIFY_SUCCESS(err_code);m_rx_ovf = false;// Turn on receiver if RX pin is connectedif (p_comm_params->rx_pin_no != UART_PIN_DISCONNECTED){return nrf_drv_uart_rx(&app_uart_inst, rx_buffer,1);}else{return NRF_SUCCESS;}
}

(a)app_uart_init(const app_uart_comm_params_t * p_comm_params,
                                                   app_uart_buffers_t *     p_buffers,
                                        app_uart_event_handler_t event_handler,
                                                       app_irq_priority_t       irq_priority)
{


 * 此函数用于初始化 UART 通信,配置通信参数、缓冲区、事件处理函数和中断优先级。
 *
 * @param p_comm_params 指向 app_uart_comm_params_t 结构体的指针,包含 UART 通信参数,如引脚编号、流控制设置、奇偶校验和波特率等。
 * @param p_buffers 指向 app_uart_buffers_t 结构体的指针,该结构体应包含用于 UART 数据收发的缓冲区。
 * @param event_handler 指向 UART 事件处理函数的指针,当 UART 发生特定事件(如接收数据、发送完成等)时会调用此函数。
 * @param irq_priority UART 中断的优先级,用于确定 UART 中断在系统中的处理顺序。
 *
 * @return 函数返回值类型未给出,一般可能返回一个状态码,用于表示初始化是否成功。
 */

由于不清楚 app_uart_buffers_t、app_uart_event_handler_t 和 app_irq_priority_t 的具体定义,下面给出一个简单的模拟实现:

 #include <stdio.h>
#include <stdint.h>

// 假设 app_uart_flow_control_t 定义如下
typedef enum
{
    APP_UART_FLOW_CONTROL_DISABLED, /**< UART Hw Flow Control is disabled. */
    APP_UART_FLOW_CONTROL_ENABLED,  /**< Standard UART Hw Flow Control is enabled. */
} app_uart_flow_control_t;

// 假设 app_uart_comm_params_t 定义如下
typedef struct
{
    uint32_t                rx_pin_no;    /**< RX pin number. */
    uint32_t                tx_pin_no;    /**< TX pin number. */
    uint32_t                rts_pin_no;   /**< RTS pin number, only used if flow control is enabled. */
    uint32_t                cts_pin_no;   /**< CTS pin number, only used if flow control is enabled. */
    app_uart_flow_control_t flow_control; /**< Flow control setting, if flow control is used, the system will use low power UART mode, based on CTS signal. */
    bool                    use_parity;   /**< Even parity if TRUE, no parity if FALSE. */
    uint32_t                baud_rate;    /**< Baud rate configuration. */
} app_uart_comm_params_t;

// 假设 app_uart_buffers_t 定义如下
typedef struct
{
    uint8_t *rx_buffer;
    uint8_t *tx_buffer;
    size_t rx_buffer_size;
    size_t tx_buffer_size;
} app_uart_buffers_t;

// 假设 app_uart_event_handler_t 是一个函数指针类型
typedef void (*app_uart_event_handler_t)(void);

// 假设 app_irq_priority_t 是一个枚举类型
typedef enum
{
    APP_IRQ_PRIORITY_LOW,
    APP_IRQ_PRIORITY_MEDIUM,
    APP_IRQ_PRIORITY_HIGH
} app_irq_priority_t;

// 模拟实现 app_uart_init 函数
int app_uart_init(const app_uart_comm_params_t * p_comm_params,
                  app_uart_buffers_t *     p_buffers,
                  app_uart_event_handler_t event_handler,
                  app_irq_priority_t       irq_priority)
{
    if (p_comm_params == NULL || p_buffers == NULL || event_handler == NULL)
    {
        return -1; // 输入参数无效
    }

    // 模拟配置 UART 通信参数
    printf("Initializing UART with the following parameters:\n");
    printf("RX Pin: %u\n", p_comm_params->rx_pin_no);
    printf("TX Pin: %u\n", p_comm_params->tx_pin_no);
    printf("Flow Control: %s\n", p_comm_params->flow_control == APP_UART_FLOW_CONTROL_ENABLED ? "Enabled" : "Disabled");
    printf("Parity: %s\n", p_comm_params->use_parity ? "Even" : "None");
    printf("Baud Rate: %u\n", p_comm_params->baud_rate);

    // 模拟配置缓冲区
    printf("RX Buffer Size: %zu\n", p_buffers->rx_buffer_size);
    printf("TX Buffer Size: %zu\n", p_buffers->tx_buffer_size);

    // 模拟设置事件处理函数
    // 这里可以保存 event_handler 指针,在 UART 事件发生时调用
    printf("Event handler set.\n");

    // 模拟设置中断优先级
    printf("Interrupt priority set to ");
    switch (irq_priority)
    {
        case APP_IRQ_PRIORITY_LOW:
            printf("LOW\n");
            break;
        case APP_IRQ_PRIORITY_MEDIUM:
            printf("MEDIUM\n");
            break;
        case APP_IRQ_PRIORITY_HIGH:
            printf("HIGH\n");
            break;
        default:
            printf("UNKNOWN\n");
            break;
    }

    return 0; // 初始化成功
}


使用示例

  int main()
{
    app_uart_comm_params_t comm_params = {
        .rx_pin_no = 1,
        .tx_pin_no = 2,
        .rts_pin_no = 3,
        .cts_pin_no = 4,
        .flow_control = APP_UART_FLOW_CONTROL_ENABLED,
        .use_parity = false,
        .baud_rate = 115200
    };

    uint8_t rx_buffer[100];
    uint8_t tx_buffer[100];
    app_uart_buffers_t buffers = {
        .rx_buffer = rx_buffer,
        .tx_buffer = tx_buffer,
        .rx_buffer_size = sizeof(rx_buffer),
        .tx_buffer_size = sizeof(tx_buffer)
    };

    void dummy_event_handler()
    {
        // 处理 UART 事件的代码
    }

    app_irq_priority_t irq_priority = APP_IRQ_PRIORITY_MEDIUM;

    int result = app_uart_init(&comm_params, &buffers, dummy_event_handler, irq_priority);
    if (result == 0)
    {
        printf("UART initialization successful.\n");
    }
    else
    {
        printf("UART initialization failed.\n");
    }

    return 0;
}

上述代码中,首先为 app_uart_init 函数声明添加了详细注释,然后模拟实现了该函数,最后给出了一个使用示例,展示了如何调用 app_uart_init 函数进行 UART 初始化。


(b)app_fifo_init函数

uint32_t app_fifo_init(app_fifo_t * p_fifo, uint8_t * p_buf, uint16_t buf_size)
{// Check buffer for null pointer.if (p_buf == NULL){return NRF_ERROR_NULL;}// Check that the buffer size is a power of two.if (!IS_POWER_OF_TWO(buf_size)){return NRF_ERROR_INVALID_LENGTH;}p_fifo->p_buf         = p_buf;p_fifo->buf_size_mask = buf_size - 1;p_fifo->read_pos      = 0;p_fifo->write_pos     = 0;return NRF_SUCCESS;
}

app_fifo_t是一个结构体:

typedef struct
{
    uint8_t *          p_buf;           /**< Pointer to FIFO buffer memory.                      */
    uint16_t           buf_size_mask;   /**< Read/write index mask. Also used for size checking. */
    volatile uint32_t  read_pos;        /**< Next read position in the FIFO buffer.              */
    volatile uint32_t  write_pos;       /**< Next write position in the FIFO buffer.             */
} app_fifo_t;


1.uint8_t * p_buf:这是一个指向 uint8_t 类型的指针,其作用是指向 FIFO 缓冲区的内存起始地址。

2. uint16_t buf_size_mask:这是一个 uint16_t 类型的变量,作为读写索引掩码使用,同时也用于检查缓冲区的大小。

3. volatile uint32_t read_pos:这是一个 volatile uint32_t 类型的变量,代表 FIFO 缓冲区里下一次读取的位置。

4. volatile uint32_t write_pos:这是一个 volatile uint32_t 类型的变量,代表 FIFO 缓冲区里下一次写入的位置。


以下是使用这个结构体的简单示例代码:

 #include <stdio.h>
#include <stdint.h>

typedef struct
{
    uint8_t *          p_buf;           /**< Pointer to FIFO buffer memory.                      */
    uint16_t           buf_size_mask;   /**< Read/write index mask. Also used for size checking. */
    volatile uint32_t  read_pos;        /**< Next read position in the FIFO buffer.              */
    volatile uint32_t  write_pos;       /**< Next write position in the FIFO buffer.             */
} app_fifo_t;

#define BUFFER_SIZE 16

// 初始化 FIFO
void fifo_init(app_fifo_t *fifo, uint8_t *buffer) {
    fifo->p_buf = buffer;
    fifo->buf_size_mask = BUFFER_SIZE - 1;
    fifo->read_pos = 0;
    fifo->write_pos = 0;
}

// 向 FIFO 写入数据
void fifo_write(app_fifo_t *fifo, uint8_t data) {
    fifo->p_buf[fifo->write_pos & fifo->buf_size_mask] = data;
    fifo->write_pos++;
}

// 从 FIFO 读取数据
uint8_t fifo_read(app_fifo_t *fifo) {
    uint8_t data = fifo->p_buf[fifo->read_pos & fifo->buf_size_mask];
    fifo->read_pos++;
    return data;
}

int main() {
    uint8_t buffer[BUFFER_SIZE];
    app_fifo_t fifo;

    fifo_init(&fifo, buffer);

    // 写入数据
    fifo_write(&fifo, 10);
    fifo_write(&fifo, 20);

    // 读取数据
    uint8_t data1 = fifo_read(&fifo);
    uint8_t data2 = fifo_read(&fifo);

    printf("Read data: %d, %d\n", data1, data2);

    return 0;
}


   在这个示例中,我们实现了 FIFO 的初始化、写入和读取功能。你可以根据需求对代码进行修改和扩展。


#define IS_POWER_OF_TWO(A) ( ((A) != 0) && ((((A) - 1) & (A)) == 0) )


(c)nrf_drv_uart_config_t结构体

typedef struct
{uint32_t            pseltxd;            ///< TXD pin number.uint32_t            pselrxd;            ///< RXD pin number.uint32_t            pselcts;            ///< CTS pin number.uint32_t            pselrts;            ///< RTS pin number.void *              p_context;          ///< Context passed to interrupt handler.nrf_uart_hwfc_t     hwfc;               ///< Flow control configuration.nrf_uart_parity_t   parity;             ///< Parity configuration.nrf_uart_baudrate_t baudrate;           ///< Baudrate.uint8_t             interrupt_priority; ///< Interrupt priority.
#if defined(NRF_DRV_UART_WITH_UARTE) && defined(NRF_DRV_UART_WITH_UART)bool                use_easy_dma;
#endif
} nrf_drv_uart_config_t;

(d)NRF_DRV_UART_DEFAULT_CONFIG宏

#define NRF_DRV_UART_DEFAULT_CONFIG                                          \
{                                                                            \.pseltxd            = NRF_UART_PSEL_DISCONNECTED,                        \.pselrxd            = NRF_UART_PSEL_DISCONNECTED,                        \.pselcts            = NRF_UART_PSEL_DISCONNECTED,                        \.pselrts            = NRF_UART_PSEL_DISCONNECTED,                        \.p_context          = NULL,                                              \.hwfc               = (nrf_uart_hwfc_t)UART_DEFAULT_CONFIG_HWFC,         \.parity             = (nrf_uart_parity_t)UART_DEFAULT_CONFIG_PARITY,     \.baudrate           = (nrf_uart_baudrate_t)UART_DEFAULT_CONFIG_BAUDRATE, \.interrupt_priority = UART_DEFAULT_CONFIG_IRQ_PRIORITY,                  \NRF_DRV_UART_DEFAULT_CONFIG_USE_EASY_DMA                                 \
}

函数总体解析:

uint32_t app_uart_init(const app_uart_comm_params_t * p_comm_params,app_uart_buffers_t *     p_buffers,app_uart_event_handler_t event_handler,app_irq_priority_t       irq_priority)
{uint32_t err_code;// 保存事件处理函数m_event_handler = event_handler;// 检查 p_buffers 是否为 NULL,如果为 NULL 则返回错误码if (p_buffers == NULL){return NRF_ERROR_INVALID_PARAM;}// 配置接收 FIFO 缓冲区err_code = app_fifo_init(&m_rx_fifo, p_buffers->rx_buf, p_buffers->rx_buf_size);// 检查初始化是否成功,如果失败则返回错误码VERIFY_SUCCESS(err_code);// 配置发送 FIFO 缓冲区err_code = app_fifo_init(&m_tx_fifo, p_buffers->tx_buf, p_buffers->tx_buf_size);// 检查初始化是否成功,如果失败则返回错误码VERIFY_SUCCESS(err_code);// 使用默认配置初始化 UART 配置结构体nrf_drv_uart_config_t config = NRF_DRV_UART_DEFAULT_CONFIG;// 设置波特率config.baudrate = (nrf_uart_baudrate_t)p_comm_params->baud_rate;// 根据传入的流控制参数设置硬件流控制config.hwfc = (p_comm_params->flow_control == APP_UART_FLOW_CONTROL_DISABLED) ?NRF_UART_HWFC_DISABLED : NRF_UART_HWFC_ENABLED;// 设置中断优先级config.interrupt_priority = irq_priority;// 根据传入的奇偶校验参数设置奇偶校验config.parity = p_comm_params->use_parity ? NRF_UART_PARITY_INCLUDED : NRF_UART_PARITY_EXCLUDED;// 设置 CTS 引脚号config.pselcts = p_comm_params->cts_pin_no;// 设置 RTS 引脚号config.pselrts = p_comm_params->rts_pin_no;// 设置 RX 引脚号config.pselrxd = p_comm_params->rx_pin_no;// 设置 TX 引脚号config.pseltxd = p_comm_params->tx_pin_no;// 初始化 UART 驱动err_code = nrf_drv_uart_init(&app_uart_inst, &config, uart_event_handler);// 检查初始化是否成功,如果失败则返回错误码VERIFY_SUCCESS(err_code);// 初始化接收溢出标志m_rx_ovf = false;// 如果 RX 引脚已连接,则开启接收功能if (p_comm_params->rx_pin_no != UART_PIN_DISCONNECTED){return nrf_drv_uart_rx(&app_uart_inst, rx_buffer,1);}else{return NRF_SUCCESS;}
}

代码逻辑总结

1. 参数检查:检查 p_buffers 是否为 NULL,若为 NULL 则返回错误码。

2. FIFO 缓冲区配置:对接收和发送的 FIFO 缓冲区进行初始化。

3. UART 配置:依据传入的参数对 UART 的配置结构体进行设置。

4. UART 驱动初始化:对 UART 驱动进行初始化,并且注册事件处理函数。

5. 接收功能开启:若 RX 引脚已连接,就开启接收功能。



工程建立后,在工程编译之前,还需要在 sdk_config.h 文件中配置使能下面几个功能:
外设共用源的使能、新串口驱动使能、老板串口驱动使能、串口 FIFO 缓冲库使能、串口驱动
库使能、错误检测使能、重定位库使能。可以直接打开 sdk_config.h 文件中的 configuration Wizard 向导进行勾选,如下图 所示。如果 sdk_config.h 文件中没有相关配置,可以拷贝历程中的库使能 的对应源码到配置文件中。
本例使用 printf() 函数是格式化输出函数 , 一般用于向标准输出设备按规定格式输出信息。本格
式的输出是 c 语言中产生格式化输出的函数(在 stdio.h 中定义)。用于向终端(显示器、控制台
等)输出字符。格式控制由要输出的文字和数据格式说明组成。要输出的文字除了可以使用字母、
数字、空格和一些数字符号以外,还可以使用一些转义字符表示特殊的含义。因此,使用之前除了
要使能重定向以外,还需要在 options for Target 选项卡中,选择 Target 后,勾选 Use MicroLIB 库,
如下图 所示:
那么主函数就是十分的简单了,直接调用我们串口的驱动库函数进行配置,输出采用 printf 函数
打印输出,代码如下所示:
/******************** (C) COPYRIGHT 2023 青风电子 ********************* 文件名  :main* 出品论坛 :www.qfv8.com        * 实验平台:青云nRF52xx蓝牙开发板* 描述    :串口输出* 作者    :青风* 店铺    :qfv5.taobao.com
**********************************************************************/#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif//#define ENABLE_LOOPBACK_TEST  /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */#define MAX_TEST_DATA_BYTES     (15U)                /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256                         /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256                         /**< UART RX buffer size. */void uart_error_handle(app_uart_evt_t * p_event)
{if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR){APP_ERROR_HANDLER(p_event->data.error_communication);}else if (p_event->evt_type == APP_UART_FIFO_ERROR){APP_ERROR_HANDLER(p_event->data.error_code);}
}#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLED/*** @brief Function for main application entry.*/
int main(void)
{uint32_t err_code;const app_uart_comm_params_t comm_params ={RX_PIN_NUMBER,TX_PIN_NUMBER,RTS_PIN_NUMBER,CTS_PIN_NUMBER,UART_HWFC,false,
#if defined (UART_PRESENT)NRF_UART_BAUDRATE_115200
#elseNRF_UARTE_BAUDRATE_115200
#endif};APP_UART_FIFO_INIT(&comm_params,UART_RX_BUF_SIZE,UART_TX_BUF_SIZE,uart_error_handle,APP_IRQ_PRIORITY_LOWEST,err_code);APP_ERROR_CHECK(err_code);while (1){printf(" 2023.3.1 青风!\r\n");nrf_delay_ms(500);}}/** @} */

(1)app_uart_evt_t结构体定义如下:

typedef struct
{app_uart_evt_type_t evt_type; /**< Type of event. */union{uint32_t error_communication; /**< Field used if evt_type is: APP_UART_COMMUNICATION_ERROR. This field contains the value in the ERRORSRC register for the UART peripheral. The UART_ERRORSRC_x defines from nrf5x_bitfields.h can be used to parse the error code. See also the \nRFXX Series Reference Manual for specification. */uint32_t error_code;          /**< Field used if evt_type is: NRF_ERROR_x. Additional status/error code if the error event type is APP_UART_FIFO_ERROR. This error code refer to errors defined in nrf_error.h. */uint8_t  value;               /**< Field used if evt_type is: NRF_ERROR_x. Additional status/error code if the error event type is APP_UART_FIFO_ERROR. This error code refer to errors defined in nrf_error.h. */} data;
} app_uart_evt_t;

(2)app_uart_evt_type_t枚举类型:

typedef enum
{APP_UART_DATA_READY,          /**< An event indicating that UART data has been received. The data is available in the FIFO and can be fetched using @ref app_uart_get. */APP_UART_FIFO_ERROR,          /**< An error in the FIFO module used by the app_uart module has occured. The FIFO error code is stored in app_uart_evt_t.data.error_code field. */APP_UART_COMMUNICATION_ERROR, /**< An communication error has occured during reception. The error is stored in app_uart_evt_t.data.error_communication field. */APP_UART_TX_EMPTY,            /**< An event indicating that UART has completed transmission of all available data in the TX FIFO. */APP_UART_DATA,                /**< An event indicating that UART data has been received, and data is present in data field. This event is only used when no FIFO is configured. */
} app_uart_evt_type_t;

(3)APP_ERROR_HANDLER宏

#define APP_ERROR_HANDLER(ERR_CODE)                                    \do                                                                 \{                                                                  \app_error_handler_bare((ERR_CODE));                            \} while (0)

(4)app_error_handler_bare函数

void app_error_handler_bare(ret_code_t error_code)
{error_info_t error_info ={.line_num    = 0,.p_file_name = NULL,.err_code    = error_code,};app_error_fault_handler(NRF_FAULT_ID_SDK_ERROR, 0, (uint32_t)(&error_info));UNUSED_VARIABLE(error_info);
}

(5)error_info_t结构体

typedef struct
{uint32_t        line_num;    /**< The line number where the error occurred. */uint8_t const * p_file_name; /**< The file in which the error occurred. */uint32_t        err_code;    /**< The error code representing the error that occurred. */
} error_info_t;


http://www.mrgr.cn/news/98323.html

相关文章:

  • 【数据结构_4】顺序表
  • linux多线(进)程编程——(6)共享内存
  • 【前端工程化】-【vue2-ele项目升级】
  • 深度学习ResNet模型提取影响特征
  • 【数据结构_6下篇】有关链表的oj题
  • C语言打印的坑
  • 【玩转全栈】—— Django 连接 vue3 保姆级教程,前后端分离式项目2025年4月最新!!!
  • 个人博客系统后端 - 注册登录功能实现指南
  • 行星际激波在日球层中的传播:Propagation of Interplanetary Shocks in the Heliosphere (第二部分)
  • linux多线(进)程编程——(5)虚拟内存与内存映射
  • 【Java学习笔记】Java第一课,梦开始的地方!!!
  • centos7系统搭建nagios监控
  • SQL 解析 with as dual sysdate level
  • 剑指Offer(数据结构与算法面试题精讲)C++版——day9
  • Day30笔记-综合项目: 购物车
  • CMD命令行笔记
  • Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(三)
  • 【随手笔记】QT避坑一(串口readyRead信号不产生)
  • 【3GPP核心网】【5G】精讲5G系统的策略和计费控制框架
  • Linux:39内核与用户--信号-lesson28(待)-未完多个子进程处