NRF52832学习笔记(41)——添加串口库libuarte

一、背景

由于板子上不支持硬件流控,在使用 app_uart_fifo 库接收串口大数据时,频繁报 APP_UART_COMMUNICATION_ERROR 错误,多次重新初始化后,串口也不再产生中断了。查看官方论坛后决定使用串口异步库 libuarte

二、简介

Libuarte 是一个 UARTE 库,由以下层组成:

  • nrf_libuarte_drv:一个低级 UARTE 驱动程序,具有扩展功能,如连续计算接收的字节、双缓冲、启动和停止接收器的可选事件,以及可在接收器启动和停止时触发的可选任务。
  • nrf_libuarte_async:适用于接收和传输异步数据包的库。它管理接收缓冲区并实现接收器非活动超时。库正在使用 nrf_libuarte。它旨在用于典型的 UART 用例,其中交易对手异步发送可变长度的数据包。在这种情况下,用户会在数据包边界(即超时)和 DMA 缓冲区已满时收到事件。

2.1 nrf_libuarte_drv

nrf_libuarte_drv 使用 EasyDMA 双缓冲功能和 PPI 连接来确保可靠的接收。连续接收由 STARTRX 任务建立,连接到 ENDRX 事件。STARTRX 任务启动后,将生成 RXSTARTED 事件。

在 RXSTARTED 事件中:

  • EasyDMA 已配置为传输,并且可以配置为下一次传输。
  • Configuration registers 被锁存。

使用这种方法时,系统延迟取决于所使用的缓冲区的大小。如果缓冲区足够大,系统可以确保完全接收,而无需流控制,延迟为数毫秒。这个最小延迟可以覆盖 SoftDevice 或任何其他更高优先级的中断和 flash 操作(包括 flash 页面擦除)。

此外,计数器模式下的专用 TIMER 外设用于跟踪接收到的字节数。

nrf_libuarte_drv 可以配置硬件事件来启动和停止接收器。例如,此选项可用于构建具有请求和响应引脚的低功耗 UART 协议,该协议仅在传输期间启用接收器。所有硬件任务和事件都与 PPI 连接,这允许不依赖于中断处理时间的自主操作。

nrf_libuarte_drv 正在使用以下硬件资源:

  • UARTE 实例
  • TIMER 实例
  • PPI 通道:
    • 如果未使用可选任务和事件,则至少 2 个 PPI 通道(在最多 255 次 EasyDMA 传输的设备上为 3 个)。
    • 如果使用可选任务和事件,则最多 9 个 PPI 通道和 2 个 PPI 组。

2.2 nrf_libuarte_async

nrf_libuarte_async 库构建在 lib_libuarte_drv 驱动程序之上。它实现了接收器非活动超时,这会导致一个包含接收数据量的事件。

该库具有用于接收的专用缓冲区,并处理来自驱动程序 (NRF_LIBUARTE_DRV_EVT_RX_BUF_REQ) 的缓冲区请求事件。

您可以使用以下选项之一实现接收器不活动超时,具体取决于可用资源:

  • 专用的 TIMER 外围设备
  • 专用 RTC 外设
  • 一个 app_timer 实例

TIMER/RTC 外设

TIMER 或 RTC 外设与 TIMER/RTC 外设中的字节边界事件 (RXDRDY) 和任务 CLEAR 之间的 PPI 连接一起使用。

TIMER/RTC 的配置方式是在 compare 事件上触发中断。当任务 CLEAR 未按时触发时,将触发中断。

configurable 中的超时和 resolution 等于所用外设的 tick length。

app_timer实例

除了 TIMER/RTC 外设 之外,还可以使用 app_timer 实例进行超时实现。此方法需要按指定的时间间隔定期生成 app_timer 事件。

超时时,app_timer 事件处理程序检查收到的字节数是否已更改。根据此检查,处理程序报告数据包边界。

TIMER/RTC 外设相比,超时分辨率较低,等于可以设置的最短app_timer超时。

nrf_libuarte_async 正在使用以下硬件资源:

  • UARTE 实例
  • TIMER 实例
  • 2 或 3 个 PPI 通道
  • 如果 TIMER 或 RTC 外围设备用于接收器非活动超时,则使用以下附加资源:
    • RTC 或 TIMER 实例
    • 2 个 PPI 通道

三、参考工程

libuarte 在 SDK v16.0 中得到了改进,它也从 Experimental 中移出并取代了串行库。

SDK\examples\peripheral\libuarte 中找到示例工程

四、添加组件库

基于 ble_app_uart 的工程,在 nRF_Drivers 文件夹和 nRF_Libraries 文件夹确认以下组件库是否存在,不存在则添加。

在 ble_app_uart 的工程中后三个文件没有,需要添加:

  1. 添加 nrfx_ppi.cnrfx_rtc.cnrfx_timer.c

    位于 SDK\modules\nrfx\drivers\src

  2. 添加 nrfx_libuarte_async.cnrfx_libuarte_drv.c

    位于 SDK\components\libraries\libuarte

  3. 添加 nrfx_queue.c

    位于 SDK\components\libraries\queue

  4. 添加上述编译文件路径

五、SDK配置

点击 sdk_config.h 文件

选择 Configuration Wizard

nRF_Drivers 中勾选PPI、RTC、TIMER相关选项

修改时钟配置,由于要移植的工程是ble_app_uart,其中蓝牙协议栈SoftDevice使用了RTC0和TIMER0,app_timer模块使用了RTC1,所以我们用于串口超时的时钟有RTC2、TIMER1和TIMER2等等。

nRF_Libraries 中勾选QUEUE相关选项

添加 NRF_LIBUARTE_ASYNC_WITH_APP_TIMERnrf_libuarte_drv 并勾选

cpp 复制代码
// <q> NRF_LIBUARTE_ASYNC_WITH_APP_TIMER  - nrf_libuarte_async - libUARTE_async library
 

#ifndef NRF_LIBUARTE_ASYNC_WITH_APP_TIMER
#define NRF_LIBUARTE_ASYNC_WITH_APP_TIMER 1
#endif

// <h> nrf_libuarte_drv - libUARTE library

//==========================================================
// <q> NRF_LIBUARTE_DRV_HWFC_ENABLED  - Enable HWFC support in the driver
 

#ifndef NRF_LIBUARTE_DRV_HWFC_ENABLED
#define NRF_LIBUARTE_DRV_HWFC_ENABLED 0
#endif

// <q> NRF_LIBUARTE_DRV_UARTE0  - UARTE0 instance
 

#ifndef NRF_LIBUARTE_DRV_UARTE0
#define NRF_LIBUARTE_DRV_UARTE0 1
#endif

// <q> NRF_LIBUARTE_DRV_UARTE1  - UARTE1 instance
 

#ifndef NRF_LIBUARTE_DRV_UARTE1
#define NRF_LIBUARTE_DRV_UARTE1 0
#endif

// </h> 
//==========================================================

六、使用例子

1)添加头文件

cpp 复制代码
#include "nrf_libuarte_async.h"

2)添加全局变量(SDK16.0以上 中 ble_peripheral 的 ble_app_uart 工程)

cpp 复制代码
// 使用TIMER2作为计数,不使用RTC和TIMER用作超时(如果前两个都不使用,则使用app_timer作为超时)
NRF_LIBUARTE_ASYNC_DEFINE(libuarte, 0, 2, NRF_LIBUARTE_PERIPHERAL_NOT_USED, NRF_LIBUARTE_PERIPHERAL_NOT_USED, 255, 3);
// 使用TIMER2作为计数,使用RTC2用作超时
//NRF_LIBUARTE_ASYNC_DEFINE(libuarte, 0, 2, 2, NRF_LIBUARTE_PERIPHERAL_NOT_USED, 255, 3);

static uint8_t text[] = "UART example started.\r\n Loopback:\r\n";
static uint8_t text_size = sizeof(text);
static volatile bool m_loopback_phase;

typedef struct {
    uint8_t * p_data;
    uint32_t length;
} buffer_t;

NRF_QUEUE_DEF(buffer_t, m_buf_queue, 10, NRF_QUEUE_MODE_NO_OVERFLOW);

第3,4,5参数分别是TIMER接口用作libuarte字节统计,RTC接口用作超时,TIMER接口用作超时,二者选其一即可,如果都不使用,则使用app_timer作为超时。

其中蓝牙协议栈SoftDevice使用了RTC0和TIMER0,app_timer模块使用了RTC1,所以我们用于串口超时的时钟有RTC2、TIMER1和TIMER2等等。

3)修改main函数

这里由于串口超时使用的是app_timer,而非RTC或TIMER,所以这里将uart_init()往后放。

cpp 复制代码
/**@brief Application main function.
 */
int main(void)
{
    bool erase_bonds;

    // Initialize.
//    uart_init();
    log_init();
    timers_init();
    uart_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();

    // Start execution.
//    printf("\r\nUART started.\r\n");
    NRF_LOG_INFO("Debug logging for UART over RTT started.");
    advertising_start();

    // Enter main loop.
    for (;;)
    {
        idle_state_handle();
    }
}

4)添加串口初始化函数

这里由于串口超时使用的是app_timer,而非RTC或TIMER,所以这里将串口中断优先级调整的app_timer优先级高,修改为 APP_IRQ_PRIORITY_LOW_MID 及以上。

cpp 复制代码
/**@brief  Function for initializing the UART module.
 */
/**@snippet [UART Initialization] */
static void uart_init(void)
{
    ret_code_t err_code;
    nrf_libuarte_async_config_t nrf_libuarte_async_config = {
            .tx_pin     = TX_PIN_NUMBER,
            .rx_pin     = RX_PIN_NUMBER,
            .baudrate   = NRF_UARTE_BAUDRATE_115200,
            .parity     = NRF_UARTE_PARITY_EXCLUDED,
            .hwfc       = NRF_UARTE_HWFC_DISABLED,
            .timeout_us = 100,
            .int_prio   = APP_IRQ_PRIORITY_LOW_MID
    };

    err_code = nrf_libuarte_async_init(&libuarte, &nrf_libuarte_async_config, uart_event_handler, (void *)&libuarte);
    APP_ERROR_CHECK(err_code);

    nrf_libuarte_async_enable(&libuarte);

    err_code = nrf_libuarte_async_tx(&libuarte, text, text_size);
    APP_ERROR_CHECK(err_code);
}
/**@snippet [UART Initialization] */

5)添加串口中断处理函数

每当 nrf_libuarte_drv 设置 EasyDMA 接收缓冲区时,都会生成缓冲区请求事件 (NRF_LIBUARTE_DRV_EVT_RX_BUF_REQ)。在这种情况下,请确保应用程序使用 nrf_libuarte_drv_rx_buf_rsp 进行响应并提供新的缓冲区。新的接收缓冲区必须在刚刚开始的传输结束之前提供,否则 UARTE 将开始覆盖活动缓冲区,因为接收会自主重新启动。

如果设备不支持 16 位长的 EasyDMA 传输,则使用 EasyDMA 双缓冲功能以及 ENDTX 事件和 STARTTX 任务之间的 PPI 连接来确保连续传输。传输完成后,将生成 NRF_LIBUARTE_DRV_EVT_TX_DONE 事件。

cpp 复制代码
void uart_event_handler(void * context, nrf_libuarte_async_evt_t * p_evt)
{
    nrf_libuarte_async_t * p_libuarte = (nrf_libuarte_async_t *)context;
    ret_code_t ret;

    switch (p_evt->type)
    {
        case NRF_LIBUARTE_ASYNC_EVT_ERROR:
            bsp_board_led_invert(0);
            break;
        case NRF_LIBUARTE_ASYNC_EVT_RX_DATA:
            ret = nrf_libuarte_async_tx(p_libuarte,p_evt->data.rxtx.p_data, p_evt->data.rxtx.length);
            if (ret == NRF_ERROR_BUSY)
            {
                buffer_t buf = {
                    .p_data = p_evt->data.rxtx.p_data,
                    .length = p_evt->data.rxtx.length,
                };

                ret = nrf_queue_push(&m_buf_queue, &buf);
                APP_ERROR_CHECK(ret);
            }
            else
            {
                APP_ERROR_CHECK(ret);
            }
            bsp_board_led_invert(1);
            m_loopback_phase = true;
            break;
        case NRF_LIBUARTE_ASYNC_EVT_TX_DONE:
            if (m_loopback_phase)
            {
                nrf_libuarte_async_rx_free(p_libuarte, p_evt->data.rxtx.p_data, p_evt->data.rxtx.length);
                if (!nrf_queue_is_empty(&m_buf_queue))
                {
                    buffer_t buf;
                    ret = nrf_queue_pop(&m_buf_queue, &buf);
                    APP_ERROR_CHECK(ret);
                    UNUSED_RETURN_VALUE(nrf_libuarte_async_tx(p_libuarte, buf.p_data, buf.length));
                }
            }
            bsp_board_led_invert(2);
            break;
        default:
            break;
    }
}

6)串口接收

每次完成 EasyDMA 传输时,都会生成 NRF_LIBUARTE_DRV_EVT_RX_DATA 事件。事件结构包含指向数据和接收的数据量的指针。处理数据时,必须通过调用 nrf_libuarte_async_rx_free 用于释放接收到的缓冲区数据的函数。

上面串口中断处理函数是为了串口回环打印测试,实际应用可改为以下逻辑:

复制代码
    case NRF_LIBUARTE_ASYNC_EVT_RX_DATA: {
            在此自定义串口处理(event->data.rxtx.p_data, event->data.rxtx.length)
            nrf_libuarte_async_rx_free(libuarte, event->data.rxtx.p_data, event->data.rxtx.length);
        } break;

7)串口发送
nrf_libuarte_async_tx 用于启动传输。

在传输完成时调用具有 NRF_LIBUARTE_ASYNC_EVT_TX_DONE 事件的事件处理程序。

七、工程代码

基于 nRF5_SDK_17.1.0_ddde560,仅用于移植文件添加参考。

百度网盘: https://pan.baidu.com/s/1yu34SDNoBl7Uy-WV18UO8A?pwd=999m 提取码: 999m


• 由 Leung 写于 2024 年 11 月 4 日

• 参考:nRF5 SDK v17.1.0: Libuarte - advanced UARTE driver (nordicsemi.com)

nrf52840蓝牙开发之Libuarte外设移植

相关推荐
babytiger4 天前
ble扫描相关的问题,蓝牙 MAC 是否可以确定厂商?
蓝牙·ble
蓝天居士7 天前
RS485在Linux内核(驱动)及全志T113平台上的实现(7)
串口·rs485·设备驱动
蓝天居士8 天前
RS485在Linux内核(驱动)及全志T113平台上的实现(4)
串口·rs485·设备驱动
蓝天居士9 天前
RS485在Linux内核(驱动)及全志T113平台上的实现(5)
串口·rs485·设备驱动
dozenyaoyida16 天前
BLE传输WiFi列表的问题分析
网络·经验分享·物联网·wifi·中文乱码·json解析·ble
Darkershadow19 天前
蓝牙学习之发送 Mesh Provisioning Service advertising
学习·蓝牙·ble·mesh
非鱼䲆鱻䲜20 天前
esp32基于中断+FIFO+事件队列的uart
单片机·嵌入式硬件·esp32·uart
Industio_触觉智能22 天前
触觉智能RV1126B核心板配置USB复合设备(下)
串口·acm·开发板·usb·rv1126b·ums·usb存储
wotaifuzao1 个月前
STM32多协议网关-FreeRTOS事件驱动架构实战
stm32·嵌入式硬件·can·freertos·uart·modbus·spi
Darkershadow1 个月前
蓝牙学习之Time Set
python·学习·蓝牙·ble·mesh