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 并勾选

// <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)添加头文件

#include "nrf_libuarte_async.h"

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

// 使用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()往后放。

/**@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 及以上。

/**@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 事件。

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外设移植

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容