【笔记分享】nRF54 PPI 跨域绑定实现方法

发布于:2025-07-06 ⋅ 阅读:(12) ⋅ 点赞:(0)

本专栏由非官方人员 王小小海 所著,其内容主要记录了在开发NCS的过程中遇到的一些问题和解决方法,还有一些应用的例程。作者本人也是在实践应用中遇到的问题,想着把这些问题分享给可能遇到的朋友。仅仅做个人技术交流分享,不做任何商业用途。如有不对之处,请留言,本人及时更改。

本专栏不涉及基础的安装和环境搭建问题,本例程开发使用NCS 3.0.0 以上开发,还请注意!。

本文代码位置

https://gitee.com/seliverwang/nrf54l_ppi_example.git


所有分享内容

笔记分享

  1. 【笔记分享】NCS下radio_test添加FEM
  2. 【笔记分享】5340基于 BLE LBS 自定义网络核固件点亮LED并合并固件
  3. 【笔记分享】5340基于LBS自定义网络核双核DFU实现
  4. 【笔记分享】5340 设置public address 和 random address
  5. 【笔记分享】NCS nRF52/53 添加LVGL组件驱动屏幕
  6. 【笔记分享】VirtualBox Ubuntu22.04 不能使用nrfjprog问题记录
  7. 【笔记分享】5340使用内部负载电容调频偏
  8. 【笔记分享】基于 LE Audio 例程移植到nRF52840上运行思路
  9. 【nRF52/53】【笔记分享】基于 BLE LBS DFU使用内部外部Flash 升级
  10. 【nRF54H20】基础介绍与使用介绍
  11. 【笔记分享】nRF54H20 SPI速率范围记录
  12. 【笔记分享】NCS/Zephyr 使能SPI SD卡方法介绍
  13. 【笔记分享】NCS/Zephyr USB MSC实现方法介绍

应用分享

暂无


前言

nRF54L系列出来了已经快一年了,很多朋友 苦于对 nRF54L的开发,有朋友咨询我能不能分享一下 nRF54L的 PPI绑定教程,之前52能用为什么54不能用?

我看了他发过来的代码,发现其实是因为 nRF54L PPI跨域的问题

作用域介绍

打开 54L的数据手册,看到整体框图就可以知道,哪些外设是在哪些域里面了。不像之前52系列,所有外设不分域,想用PPI连接哪个,就可以连接哪个。

当然需要看是否存在跨域的现象,如果无跨域,那么使用还是跟之前52的相同只不过外设从,PPI名字变成了DPPIC而已。

在这里插入图片描述

那么跨域改怎么实现呢?主要还是需要看懂下面的图,并且结合上面的系统框架图。

在这里插入图片描述

举例解释

想实现 TIMER10的定时器事件,输出到 P1 的一个IO口 , 那么可以看到上面的整体系统框架图,TIMER10在 RADIO PD,而P1在 PERI PD,那么他们之间是无法直接通过PPI对应连接起来,必须通过PPIB11 去连接 PPIB21然后实现桥接的原理。

那么我们梳理一下:

  1. 初始化TIMER10的配置,1ms定时。
  2. 从DPPIC10中申请一个channel,然后绑定到TIMER10的 定时器捕捉事件上。我们假设命名为:dppic10_timer_channel。
  3. 将 dppic10_timer_channel 绑定到 ppib11上,ppib11有2种事件,一种是接受,一种是发送,我们是推送事件肯定就设置推送。
  4. 初始化P1的一个IO口,初始化为GPIOTE(只有GPIOTE才能通过其他事件触发IO口)。
  5. 从DPPIC20中申请一个channel,然后绑定到GPIOTE的 TASK事件上。我们假设命名为:dppic20_gpiote_channel。
  6. 将 dppic20_gpiote_channel 绑定到 ppib21上,我们是接收事件,就设置PPIB21的属性为接收。
  7. 开启 dppic10_timer_channel 和 dppic20_gpiote_channel 这两个通道就可以了。

以上是整个步骤的实现思路,下面让我们来具体实现吧!


操作步骤

步骤一

创建一个任意的工程,我就用 hallo_world例程,编译下来没问题吧?

在这里插入图片描述

步骤二

这是一个啥配置都没有,那么我们开始 开启一些需要的东西。 嫌麻烦的同学直接打开 gitee链接,抄抄抄~~

在添加pj.conf病开启PPI相关宏
#
# Copyright (c) 2019 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

######################################################
# Logging configuration
######################################################
CONFIG_NCS_BOOT_BANNER=n
CONFIG_BOOT_BANNER=n
CONFIG_PRINTK=n

CONFIG_LOG=y
CONFIG_LOG_SPEED=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_BUFFER_SIZE=8192
CONFIG_LOG_BACKEND_UART=y
CONFIG_USE_SEGGER_RTT=n
CONFIG_LOG_BACKEND_RTT=n
CONFIG_RTT_CONSOLE=n

######################################################
# Peripheral configuration
######################################################
# Enable All necessary peripherals

# RGB
CONFIG_GPIO=y

# 通用PPIB使能
CONFIG_NRFX_GPPI=y
CONFIG_NRFX_PPIB00=y
CONFIG_NRFX_PPIB10=y
CONFIG_NRFX_PPIB11=y
CONFIG_NRFX_PPIB21=y

# GPIO2的时钟域
CONFIG_NRFX_DPPI00=y    

# 不要更改, 由于RADIO和DPPI10在同一个时钟域.
CONFIG_NRFX_DPPI10=y

# GRTC的时钟域
# GPIO1的时钟域
CONFIG_NRFX_DPPI20=y

步骤三

编写绑定代码:


static void ppi_timer10_init(void)
{
    NRF_TIMER10->PRESCALER = 4;
    NRF_TIMER10->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
    NRF_TIMER10->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk;
    NRF_TIMER10->CC[0] = 1000;
    NRF_TIMER10->TASKS_CLEAR = 1;
    NRF_TIMER10->TASKS_START = 1;
}

static int timer_diff_domain_init(void)
{
    int err;
    // 跨域绑定流程.
    //
    uint8_t dppi10_timer_channnel;
    uint8_t dppi20_gpiote_channnel;
    uint8_t ppib_addr_ch;
    uint8_t timer_gpio_channel;

    nrfx_dppi_t dppic10 = NRFX_DPPI_INSTANCE(10);
    nrfx_dppi_t dppic20 = NRFX_DPPI_INSTANCE(20);
    nrfx_ppib_interconnect_t ppib11_21 = NRFX_PPIB_INTERCONNECT_INSTANCE(11, 21);

    const nrfx_gpiote_t timer_gpiote = NRFX_GPIOTE_INSTANCE(NRF_DT_GPIOTE_INST(DT_NODELABEL(led1), gpios));
    uint32_t timer_gpio_pin = NRF_DT_GPIOS_TO_PSEL(DT_NODELABEL(led1), gpios);

    // 初始化GPIOTE事件.
    err = nrfx_gpiote_channel_alloc(&timer_gpiote, &timer_gpio_channel);
    if (err != NRFX_SUCCESS)
    {
        return -ENODEV;
    }
    nrf_gpiote_task_configure(timer_gpiote.p_reg, timer_gpio_channel, timer_gpio_pin, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW);
    nrf_gpiote_task_enable(timer_gpiote.p_reg, timer_gpio_channel);

    // 从DPPIC10 申请通道.
    err = nrfx_dppi_channel_alloc(&dppic10, &dppi10_timer_channnel);
    if (err != NRFX_SUCCESS)
    {
        return -ENODEV;
    }

    // 申请新的PPIB通道
    err = nrfx_ppib_channel_alloc(&ppib11_21, &ppib_addr_ch);
    if (err != NRFX_SUCCESS)
    {
        return -ENODEV;
    }

    // 从DPPIC20 申请一个通道.
    err = nrfx_dppi_channel_alloc(&dppic20, &dppi20_gpiote_channnel);
    if (err != NRFX_SUCCESS)
    {
        return -ENODEV;
    }

    // 绑定事件通道.
    NRF_DPPI_ENDPOINT_SETUP(
        nrf_timer_event_address_get(NRF_TIMER10, NRF_TIMER_EVENT_COMPARE0), dppi10_timer_channnel);
    NRF_DPPI_ENDPOINT_SETUP(
        nrf_gpiote_task_address_get(timer_gpiote.p_reg, nrf_gpiote_clr_task_get(timer_gpio_channel)), dppi20_gpiote_channnel);
    NRF_DPPI_ENDPOINT_SETUP(
        nrfx_ppib_send_task_address_get(&ppib11_21.left, ppib_addr_ch), dppi10_timer_channnel);
    NRF_DPPI_ENDPOINT_SETUP(
        nrfx_ppib_receive_event_address_get(&ppib11_21.right, ppib_addr_ch), dppi20_gpiote_channnel);

    // 使能通道.
    nrfx_dppi_channel_enable(&dppic10, dppi10_timer_channnel);
    nrfx_dppi_channel_enable(&dppic20, dppi20_gpiote_channnel);

    return 0;
}


由于我这不需要中断,我直接用寄存器写TIMER10配置了,原理都一样。

核心步骤是:timer_diff_domain_init 函数。其中最需要注意的是,


    nrfx_dppi_t dppic10 = NRFX_DPPI_INSTANCE(10);
    nrfx_dppi_t dppic20 = NRFX_DPPI_INSTANCE(20);
    nrfx_ppib_interconnect_t ppib11_21 = NRFX_PPIB_INTERCONNECT_INSTANCE(11, 21);

    const nrfx_gpiote_t timer_gpiote = NRFX_GPIOTE_INSTANCE(NRF_DT_GPIOTE_INST(DT_NODELABEL(led1), gpios));
    uint32_t timer_gpio_pin = NRF_DT_GPIOS_TO_PSEL(DT_NODELABEL(led1), gpios);

由于开发板的led1是 P1.10,所以对应的是 dppic20,不然会没有作用!

结束语

上面配置和代码,基本上就清晰的描述了 本文的核心目标,可以清晰的看懂跨域绑定的流程,大家可以自己举一反三,我在git仓库放了 grtc 驱动同域的 led3,大家也可以参考学习一下。

有不明白的地方欢迎提问,也厚脸皮要个赞或者关注,谢谢各位啦。


本系列文章大多数是本人遇到和解决过的问题,难有疏忽之处,有什么问题或者不明白的地方,欢迎留言询问!


网站公告

今日签到

点亮在社区的每一天
去签到