目录

一、开发环境的搭建安装

二、NORDIC时钟框图

2.1:时钟框图

2.2:总线框图(部分)

2.3 USB框图

2.4 引脚、时钟

2.5 sdk_config.h配置文件

三、SDK中提供52833的例程

四、外设使用

4.0 外设库函数配置的基本四步骤

4.1 PWM

4.1.1复杂队列

4.1.2 独立模式

4.1.3 带回调函数的独立通道

4.1.4 分组模式

4.1.5 波形加载模式

4.2 定时器

4.2.1 定时器框图

4.2.2 寄存器法配置

4.2.3 库函数方式

4.3 GPIOTE

4.3.1事件模式

4.3.2 任务模式

4.4 PPI

4.4.1 普通PPI的配置

4.4.2  PPI-GROUP的管理

① CPU参与的方式

②  不需要CPU参与,PPI的方式即把组的开启关闭当做是一个PPI的任务,通过PPI事件触发

③ PPI-fork从任务

4.5 PPI-TIMER

4.5.1 精确定时

4.5.2 软件PWM

4.6 I²S

4.6.1 时序图

4.6.2 结构体说明

4.6.3 官方例程分析

4.6.4 通过D类功放生成波形


一、开发环境的搭建安装

Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片平台) - iini - 博客园 (cnblogs.com)

Keil环境下,只是用nordic的外设只需要安装两个包

CMSIS

② Device family pack(安装过程中有可能会报错,不要管它!

③ nordic板子名称和芯片对应关系

pca10040 – nRF52832
        pca10040e – nRF52810
        pca10056 – nRF52840
        pca10056e – nRF52811
        pca10100 – nRF52833
        pca10100e – nRF52820

Softdevice命名规则一。Softdevice包括两种底层协议栈:BLE和ANT,BLE包括两种角色:central(又称master)和peripheral(又称slave),为此需要给这些不同类型的协议栈进行命名区分。协议栈命名格式为Sxyz,其中

  • x – 表示协议栈的类型,1表示BLE协议栈,2表示ANT协议栈,3表示同时支持BLE和ANT
  • y – 表示BLE角色,1表示从设备,2表示主设备,3表示同时支持主设备和从设备
  • z – 表示芯片类型,0表示nRF51系列,2表示nRF52系列
  • 比如S110,表示只支持从设备模式的nRF51 BLE协议栈
  • 比如S130,表示既支持从设备模式又支持主设备模式的nRF51 BLE协议栈
  • 比如S132,表示既支持从设备模式又支持主设备模式的nRF52 BLE协议栈
  • 比如S212,表示nRF52 ANT协议栈
  • 比如S332,表示nRF52既支持BLE协议栈又支持ANT协议栈,而且BLE协议栈既支持从设备模式又支持主设备模式

5)        Softdevice命名规则二。大体上跟命名规则1相同,但是协议栈编号最后2位跟芯片型号一样,比如S140,代表这个协议栈专门用于nRF52840。由于52840 Flash空间很大,没有必要做各种细分的协议栈,S140协议栈是一个大而全的协议栈,包含蓝牙所有功能。

NORDIC BLE SoC 开发环境 – 烧录 - 物联网技术分享

nRF52开发板初步上手 - 中文社区博客 - 中文社区 - Arm Community

北欧半导体信息中心 (nordicsemi.com)

北欧开发区 (nordicsemi.com)

二、NORDIC时钟框图

2.1:时钟框图

2.2:总线框图(部分)

其中 :

Advanced High-performance Bus(AHB) runs at 64MHz, but the Advanced Peripheral Bus(APB) runs at 16MHz.

2.3 USB框图

64 MHz晶体振荡器(HFXO)由32 MHz外部晶振控制,再经过PLL提供48Mhz给USB

NRF52832时钟控制系统_f78fk_liuyu的博客-CSDN博客

USB 为什么一般选择48MHz

2.4 引脚、时钟

Nordic的引脚是可以自由定义的。只有SAADC接口是固定的那几个引脚,
数字引脚,PWM, I2C, UART, I2S都是可以自由定义的。

58233的系统时钟固定在64Mhz不可变

2.5 sdk_config.h配置文件

// <h> nRF_Drivers

// </h>

这一对表示一个名为nRF_Drivers的段落

// <e> GPIOTE_ENABLED - nrf_drv_gpiote - GPIOTE peripheral driver - legacy layer

// </e>

这一对表示一个名为 GPIOTE_ENABLED - nrf_drv_gpiote - GPIOTE peripheral driver - legacy layer的配置组,用来配置GPIOTE_ENABLED。

#ifndef GPIOTE_ENABLED

#define GPIOTE_ENABLED 1

#endif

表示默认配置GPIOTE_ENABLED为1(使能该配置组下的配置选项)

关闭后项目子选项不可修改

// <o> GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS - Number of lower power input pins

#ifndef GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS

#define GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS 1

#endif

表示配置GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS 默认配置为1

// <o> GPIOTE_CONFIG_IRQ_PRIORITY  - Interrupt priority

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice

// <0=> 0 (highest)

// <1=> 1

// <2=> 2

// <3=> 3

// <4=> 4

// <5=> 5

// <6=> 6

// <7=> 7

#ifndef GPIOTE_CONFIG_IRQ_PRIORITY

#define GPIOTE_CONFIG_IRQ_PRIORITY 6

#endif

表示配置GPIOTE_CONFIG_IRQ_PRIORITY 默认配置为6

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice 表示注释说明

// <o> GPIOTE_CONFIG_IRQ_PRIORITY  - Interrupt priority

// <0=> 0 (highest)

// <1=> 1

// <2=> 2

// <3=> 3

// <4=> 4

// <5=> 5

// <6=> 6

// <7=> 7

表示以下拉列表的方式配置

Configuration Wizard Annotations (open-cmsis-pack.github.io)

MDK中configuration wizard的使用_苍穹雄鹰007的博客-CSDN博客

三、SDK中提供52833的例程

可以将部分52840例程在52833上跑(nRF52833 是 nRF52840 的子集)

如果移植不同的板,我应该改变什么?- 北欧问答 - 北欧开发区 - 北欧开发区 (nordicsemi.com)

nRF52833-DK 眨眼问题 - 北欧问答 - 北欧开发区 - 北欧开发区 (nordicsemi.com)

我可以将 PCA 10056 的示例代码用于 PCA 10100 吗?- 北欧问答 - 北欧开发区 - 北欧开发区 (nordicsemi.com)

四、外设使用

4.0 外设库函数配置的基本四步骤

(初始化实例→初始化外设结构体配置→初始化设备→触发)

4.1 PWM

蓝牙芯片nRF52832之PWM的使用

NRF52832 PWM 占空比调整详解

 第一种赋值方式

第二种赋值方式(typedef uint16_t nrf_pwm_values_common_t;)

4.1.1复杂队列
用的是nrf_drv_pwm_complex_playback不是nrf_drv_pwm_simple_playback();/********************seq0 渐变**********************/uint16_t value = 0;uint8_t i;//设置序列0的占空比,这个数组不能在堆栈上分配(因此是“静态的”),他必须在RAM中static nrf_pwm_values_common_t seq0_values[25];for (i = 0;i < 25 ; ++i){value += 25000/25;seq0_values[i] = value;}//设置序列nrf_pwm_sequence_t const seq0 = {.values.p_common  =  seq0_values,.length                     =  NRF_PWM_VALUES_LENGTH(seq0_values),.repeats                 =  0,.end_delay                =  0};/********************seq1 暗灭**********************/static nrf_pwm_values_common_t seq1_values[] ={0,0x8000,0,0x8000,};nrf_pwm_sequence_t const seq1 = {.values.p_common    =  seq1_values,.length                     =  NRF_PWM_VALUES_LENGTH(seq1_values),.repeats                 =  4,.end_delay                =  0};(void)nrf_drv_pwm_complex_playback(&m_pwm0,&seq0,&seq1,1,NRF_DRV_PWM_FLAG_LOOP);

回放 当设置回放后,停止播放(如下为播放3次序列后停止)

(void)nrf_drv_pwm_complex_playback(&m_pwm0,&seq0,&seq1,5,NRF_DRV_PWM_FLAG_STOP);
4.1.2 独立模式

一个PWM模块的四个通道各自一个序列,实现4个LED依次亮灭

static void my_demo(void)
{nrf_drv_pwm_config_t const my_config = {.output_pins = {BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED,},.irq_priority  =  APP_IRQ_PRIORITY_LOWEST,.base_clock         =  NRF_PWM_CLK_1MHz,.count_mode            =  NRF_PWM_MODE_UP,.top_value          =  25000,.load_mode            =  NRF_PWM_LOAD_INDIVIDUAL,.step_mode          =  NRF_PWM_STEP_AUTO};APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0,&my_config,NULL));static nrf_pwm_values_individual_t seq1_values[] ={{0x8000,0,0,0},{0,0x8000,0,0},{0,0,0x8000,0},{0,0,0,0x8000}};nrf_pwm_sequence_t const seq1 = {.values.p_individual   =  seq1_values,.length                     =  NRF_PWM_VALUES_LENGTH(seq1_values),.repeats                 =  10,.end_delay               =  0};(void)nrf_drv_pwm_simple_playback(&m_pwm0,&seq1,1,NRF_DRV_PWM_FLAG_LOOP);
}

4.1.3 带回调函数的独立通道

在回调函数里面对值的修改,该回调函数值得学习借鉴。

/*************************************************************************/static nrf_pwm_values_individual_t my_demo1_seq_values;
static uint8_t                     my_demo1_phase;
static uint16_t const              my_demo1_step = 200;
static uint16_t const              my_demo1_top  = 10000;
static nrf_pwm_sequence_t const    my_demo1_seq ={.values.p_individual = &my_demo1_seq_values,.length              = NRF_PWM_VALUES_LENGTH(my_demo1_seq_values),.repeats             = 0,.end_delay           = 0};static void my_demo1_handler(nrf_drv_pwm_evt_type_t event_type)
{if (event_type == NRF_DRV_PWM_EVT_FINISHED){//my_demol_phase 每次+1 后进行右移00,01,10,11,110,111 变为00(通道0递增),00(通道0递减),01(通道1递增),01(通道1递减),010,010,011,011uint8_t channel    = my_demo1_phase >> 1;bool    down       = my_demo1_phase & 1; //0(递增),1(递减),0,1,0,1bool    next_phase = false;uint16_t * p_channels = (uint16_t *)&my_demo1_seq_values;uint16_t value = p_channels[channel];if (down)        //Decrement{value -= my_demo1_step;if (value == 0){next_phase = true;}}else     //Increase{value += my_demo1_step;if (value >= my_demo1_top){next_phase = true;}}p_channels[channel] = value;if (next_phase){if (++my_demo1_phase >= 2 * NRF_PWM_CHANNEL_COUNT){my_demo1_phase = 0;}}}
}static void my_demo(void)
{#if 1 //独立模式含回调nrf_drv_pwm_config_t const my_config = {.output_pins = {BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED,BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED,},.irq_priority   =  APP_IRQ_PRIORITY_LOWEST,.base_clock         =  NRF_PWM_CLK_1MHz,.count_mode            =  NRF_PWM_MODE_UP,.top_value          =  my_demo1_top,.load_mode         =  NRF_PWM_LOAD_INDIVIDUAL,.step_mode          =  NRF_PWM_STEP_AUTO};APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0,&my_config,my_demo1_handler));//占空比值和极性的初始化my_demo1_seq_values.channel_0 = 0;my_demo1_seq_values.channel_1 = 0;my_demo1_seq_values.channel_2 = 0;my_demo1_seq_values.channel_3 = 0;my_demo1_phase                 =    0;(void)nrf_drv_pwm_simple_playback(&m_pwm0,&my_demo1_seq,1,NRF_DRV_PWM_FLAG_LOOP);}

效果渐亮到渐暗,然后下一个LED

4.1.4 分组模式
static void my_demo(void)
{#if 1 //分组加载模式nrf_drv_pwm_config_t  my_config = {.irq_priority    =  APP_IRQ_PRIORITY_LOWEST,.count_mode         =  NRF_PWM_MODE_UP,.step_mode          =  NRF_PWM_STEP_AUTO};my_config.output_pins[0] = BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED;my_config.output_pins[1] = BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED;my_config.output_pins[2] = BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED;my_config.output_pins[3] = BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED;my_config.base_clock     =  NRF_PWM_CLK_1MHz;my_config.top_value        =  my_demo1_top;my_config.load_mode        =  NRF_PWM_LOAD_GROUPED;APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0,&my_config,NULL));static nrf_pwm_values_grouped_t seq1_values[] ={//组1:灭亮灭亮//组2:亮灭亮灭{0,0x8000},{0x8000,0},{0,0x8000},{0x8000,0}};nrf_pwm_sequence_t const seq1 = {.values.p_grouped =  seq1_values,.length                     =  NRF_PWM_VALUES_LENGTH(seq1_values),.repeats                 =  100,.end_delay              =  0};(void)nrf_drv_pwm_simple_playback(&m_pwm0,&seq1,1,NRF_DRV_PWM_FLAG_LOOP);#endif
}

现象LED1、2为一组 LED3、4为一组

//组1:灭亮灭亮//组2:亮灭亮灭

4.1.5 波形加载模式
static void my_demo(void)
{#if 1 //波形模式nrf_drv_pwm_config_t const my_config = {.output_pins = {BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED,NRF_DRV_PWM_PIN_NOT_USED,NRF_DRV_PWM_PIN_NOT_USED,},.irq_priority   =  APP_IRQ_PRIORITY_LOWEST,.base_clock         =  NRF_PWM_CLK_1MHz,.count_mode            =  NRF_PWM_MODE_UP,//.top_value            =  25000,  顶点值可以不用配置.load_mode         =  NRF_PWM_LOAD_WAVE_FORM,.step_mode           =  NRF_PWM_STEP_AUTO};APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0,&my_config,NULL));m_used |= USED_PWM(0);   //使用PWM0模块//ram中包含占极性、空比、顶点值static nrf_pwm_values_wave_form_t seq1_values[] ={{0,0,0,0x4d09},      //0x4d09周期1{0x8000,0,0,0x4d09},{0,0,0,0x3d09},      //0x3d09周期2{0x8000,0,0,0x3d09},{0,0,0,0x1d09},      //0x1d09周期3{0x8000,0,0,0x1d09},};//设置序列nrf_pwm_sequence_t const seq0 = {.values.p_wave_form    =  seq1_values,.length                     =  NRF_PWM_VALUES_LENGTH(seq1_values),.repeats                 =  0,.end_delay                =  0};(void)nrf_drv_pwm_simple_playback(&m_pwm0,&seq0,1,NRF_DRV_PWM_FLAG_LOOP);#endif
}

效果实现变周期

4.2 定时器

4.2.1 定时器框图

fTIMER是定时器的定时频率即定时周期的倒数,模块会根据这个值来自动选择输入时钟

4.2.2 寄存器法配置

配置ms级的定时器

4.2.3 库函数方式
#include <stdbool.h>
#include <stdint.h>
#include "nrf.h"
#include "nrf_drv_timer.h"
#include "bsp.h"
#include "app_error.h"const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);   //配置哪一个定时器这里配置TIMER0/**
* @brief Handler for timer events.             //中断事件这里为比较事件*/
void timer_led_event_handler(nrf_timer_event_t event_type, void* p_context)
{static uint32_t i;uint32_t led_to_invert = ((i++) % LEDS_NUMBER);switch (event_type){case NRF_TIMER_EVENT_COMPARE0:             //比较事件计时结束bsp_board_led_invert(led_to_invert);break;default://Do nothing.break;}
}/*** @brief Function for main application entry.*/
int main(void)
{uint32_t time_ms = 500; //Time(in miliseconds) between consecutive compare events.//定时器比较事件事件的时间uint32_t time_ticks;uint32_t err_code = NRF_SUCCESS;//Configure all leds on board.bsp_board_init(BSP_INIT_LEDS);//Configure TIMER_LED for generating simple light effect - leds on board will invert his state one after the other.nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;                                         //定时器结构体默认配置err_code = nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer_led_event_handler);                //初始化定时器APP_ERROR_CHECK(err_code);//计算CC寄存器中的值time_ticks = nrf_drv_timer_ms_to_ticks(&TIMER_LED, time_ms);//触发定时器比较nrf_drv_timer_extended_compare(&TIMER_LED, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);//NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK 快捷方式会清除定时器的counter从而从新开始计数nrf_drv_timer_enable(&TIMER_LED);while (1){__WFI();}
}

4.3 GPIOTE

4.3.1事件模式

eg:当按键按下会产生一个相应的中断,并在中断处理事件

void giop_inint_test(void);int main(void)
{giop_inint_test();while (true){// Do Nothing - GPIO can be toggled without software intervention.}
}
void in_pin_handeer(nrf_drv_gpiote_pin_t pin,nrf_gpiote_polarity_t active)
{if(nrf_gpio_pin_read(BUTTON_1) == 0) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_0);}
}
void giop_inint_test(void)
{nrf_gpio_cfg_output(BSP_LED_0);nrf_drv_gpiote_init();nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);in_config.pull = NRF_GPIO_PIN_PULLUP;//设置GPIOTE输入,极性,模式//in_pin_handeer为回调函数,中断函数在nrf_drv_gpiote_in_init内部,发生中断后会调用回调函数nrf_drv_gpiote_in_init(BUTTON_1,&in_config,in_pin_handeer);nrf_drv_gpiote_in_event_enable(BUTTON_1,true);
}

PORT模式应对GPIOTE只能绑定8个通道的问题(可以32个IO口通用一个通道)

void giop_inint_test(void);
int main(void)
{giop_inint_test();while (true){// Do Nothing - GPIO can be toggled without software intervention.}
}
void in_pin_handeer(nrf_drv_gpiote_pin_t pin,nrf_gpiote_polarity_t active)
{if(pin == BUTTON_1) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_0);}else if(pin == BUTTON_2) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_1);}else if(pin == BUTTON_3) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_2);}else if(pin == BUTTON_4) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_3);}
}
void giop_inint_test(void)
{nrf_gpio_cfg_output(BSP_LED_0);nrf_gpio_cfg_output(BSP_LED_1);nrf_gpio_cfg_output(BSP_LED_2);nrf_gpio_cfg_output(BSP_LED_3);nrf_drv_gpiote_init();//配置SENSE模式,选择false为sense配置nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);in_config.pull = NRF_GPIO_PIN_PULLUP;//设置GPIOTE输入,极性,模式//in_pin_handeer为回调函数,中断函数在nrf_drv_gpiote_in_init内部,发生中断后会调用回调函数//配置按键0绑定POTRnrf_drv_gpiote_in_init(BUTTON_1,&in_config,in_pin_handeer);nrf_drv_gpiote_in_event_enable(BUTTON_1,true);//配置按键1绑定POTRnrf_drv_gpiote_in_init(BUTTON_2,&in_config,in_pin_handeer);nrf_drv_gpiote_in_event_enable(BUTTON_2,true);//配置按键2绑定POTRnrf_drv_gpiote_in_init(BUTTON_3,&in_config,in_pin_handeer);nrf_drv_gpiote_in_event_enable(BUTTON_3,true);//配置按键3绑定POTRnrf_drv_gpiote_in_init(BUTTON_4,&in_config,in_pin_handeer);nrf_drv_gpiote_in_event_enable(BUTTON_4,true);
}

4.3.2 任务模式

eg:给LED灯绑定一个任务,当别的方法触发时LED做相应的电平变化(见下下图),可以同时触发多个(32个都可以?)

 

int main(void)
{giop_inint_test();nrf_gpio_cfg_output(BSP_LED_0);//初始化GPIOTE模块nrf_drv_gpiote_init();//定义GPIOTE输出初始化结构体,主要配置为翻转模式nrf_drv_gpiote_out_config_t out_config =  GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//给指定的GPIO口绑定任务nrf_drv_gpiote_out_init(BSP_LED_0,&out_config);//开始任务nrf_drv_gpiote_out_task_enable(BSP_LED_0);#endifwhile (true){//不断触发任务nrf_drv_gpiote_out_task_trigger(BSP_LED_0);// Do Nothing - GPIO can be toggled without software intervention.}
}void in_pin_handeer(nrf_drv_gpiote_pin_t pin,nrf_gpiote_polarity_t active)
{if(pin == BUTTON_1) //按键消抖{//触发任务nrf_drv_gpiote_out_task_trigger(BSP_LED_0);}else if(pin == BUTTON_2) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_1);}else if(pin == BUTTON_3) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_2);}else if(pin == BUTTON_4) //按键消抖{nrf_gpio_pin_toggle(BSP_LED_3);}
}

配合上面的事件模式 ,实现按下按键,触发任务翻转LED

nRF5芯片外设GPIO和GPIOTE介绍

4.4 PPI

①  Programmable peripheral interconnect(PPI):即不通过CPU、中断(GPIOTE是触发中断)由一个事件触发一个任务。

②  共32个通道,可编程的20个通道,12个固定的事件任务对。

③  PPI通道可进行分组,将多个PPI通道分为一组进行统一管理,同时打开或者关闭组中的所以PPI通道,最多可实现6个组。一个事件触发一人组中的任务实现一对多。 统一开启组里面对应的PPI。

④   fork 从任务,即一个事件可以触发两个任务,一个主任务,一个从任务。

⑤   配合GPIOTE食用

使用的基本配置流程

4.4.1 普通PPI的配置

eg :按键触发LED寄存器版

库函数

void my_ppi_init(void);void gpiote_init(void);int main(void)
{gpiote_init();my_ppi_init();while(true);}static nrf_ppi_channel_t my_ppi_channel;void gpiote_init(void)
{ret_code_t err_code;//初始化GPIOTEerr_code = nrf_drv_gpiote_init();APP_ERROR_CHECK(err_code);//配置LED端口翻转输出任务nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//绑定输出端口err_code  = nrf_drv_gpiote_out_init(BSP_LED_0,&out_config);//配置为输出端口任务使能nrf_drv_gpiote_out_task_enable(BSP_LED_0);//配置按键端口高电平变低电平事件nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);in_config.pull = NRF_GPIO_PIN_PULLUP;//绑定输入端口err_code = nrf_drv_gpiote_in_init(BUTTON_1,&in_config,NULL);APP_ERROR_CHECK(err_code);//配置输入事件使能nrf_drv_gpiote_in_event_enable(BUTTON_1,true);}
void my_ppi_init(void)
{ret_code_t err_code;APP_ERROR_CHECK(err_code);//初始化PPI模块err_code = nrf_drv_ppi_init();APP_ERROR_CHECK(err_code);//配置PPI的频道err_code = nrfx_ppi_channel_alloc(&my_ppi_channel);APP_ERROR_CHECK(err_code);//设置PPI通道的EPP(事件)和TEP(任务) 两端对应的端口err_code = nrfx_ppi_channel_assign(my_ppi_channel,nrfx_gpiote_in_event_addr_get(BUTTON_1),nrfx_gpiote_out_task_addr_get(BSP_LED_0));APP_ERROR_CHECK(err_code);//使能PPI通道err_code = nrfx_ppi_channel_enable(my_ppi_channel);APP_ERROR_CHECK(err_code);}

4.4.2  PPI-GROUP的管理

GROUP 组的开启关闭配置

① CPU参与的方式

上述是吧按键1与LED0,按键2与LED1,各设置一组PPI通道0和通道1,再把通道0和通道1绑定的到PPI group0上。但按键3按下使能组(未使能前按下按键1或2无响应),使能后里面两个PPI通道就可以用,按键1按下LED0翻转,按键2按下LED1翻转。按键4按下使能组

库函数

static nrf_ppi_channel_t my_ppi_channel;
static nrf_ppi_channel_t my_ppi_channel_1;
static nrf_ppi_channel_group_t  my_ppi_group;
int main(void)
{nrf_gpio_cfg_output(LED_3); //配置P0.19为输出,驱动led3 nrf_gpio_cfg_output(LED_4);//配置P0.20为输出,驱动led4 nrf_gpio_pin_set(LED_3);//led3初始状态设置为熄灭  nrf_gpio_pin_set(LED_4);//led4初始状态设置为熄灭 nrf_gpio_cfg_input(BUTTON_3,NRF_GPIO_PIN_PULLUP);//配置P0.15为输入  nrf_gpio_cfg_input(BUTTON_4,NRF_GPIO_PIN_PULLUP);//配置P0.16为输入ret_code_t err_code;gpiote_init();my_ppi_init();while(true){//检测按键3是否按下if(nrf_gpio_pin_read(BUTTON_3) == 0){//等待按键释放while(nrf_gpio_pin_read(BUTTON_3) == 0);//使能GROUPerr_code = nrfx_ppi_group_enable(my_ppi_group);//LED2亮LED3灭指示状态nrf_gpio_pin_set(BSP_LED_2);nrf_gpio_pin_clear(BSP_LED_3);}//检测按键4是否按下if(nrf_gpio_pin_read(BUTTON_4) == 0){//等待按键释放while(nrf_gpio_pin_read(BUTTON_4) == 0);//使能GROUPerr_code = nrfx_ppi_group_disable(my_ppi_group);//LED3亮LED2灭指示状态nrf_gpio_pin_set(BSP_LED_3);nrf_gpio_pin_clear(BSP_LED_2);}};}void gpiote_init(void)
{ret_code_t err_code;//初始化GPIOTEerr_code = nrf_drv_gpiote_init();APP_ERROR_CHECK(err_code);//配置LED端口翻转输出任务     按键1---LED0nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//绑定输出端口err_code  = nrf_drv_gpiote_out_init(BSP_LED_0,&out_config);//配置为输出端口任务使能nrf_drv_gpiote_out_task_enable(BSP_LED_0);//配置按键端口高电平变低电平事件nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);in_config.pull = NRF_GPIO_PIN_PULLUP;//绑定输入端口err_code = nrf_drv_gpiote_in_init(BUTTON_1,&in_config,NULL);APP_ERROR_CHECK(err_code);//配置输入事件使能nrf_drv_gpiote_in_event_enable(BUTTON_1,true);//配置LED端口翻转输出任务        按键2---LED1nrf_drv_gpiote_out_config_t out_config_1 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//绑定输出端口err_code  = nrf_drv_gpiote_out_init(BSP_LED_1,&out_config_1);//配置为输出端口任务使能nrf_drv_gpiote_out_task_enable(BSP_LED_1);//配置按键端口高电平变低电平事件nrf_drv_gpiote_in_config_t in_config_1 = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);in_config_1.pull = NRF_GPIO_PIN_PULLUP;//绑定输入端口err_code = nrf_drv_gpiote_in_init(BUTTON_2,&in_config_1,NULL);APP_ERROR_CHECK(err_code);//配置输入事件使能nrf_drv_gpiote_in_event_enable(BUTTON_2,true);}
void my_ppi_init(void)
{ret_code_t err_code;//APP_ERROR_CHECK(err_code);  //此处开启会运行不了!!!!!!!!!!!!!!!!!//初始化PPI模块err_code = nrf_drv_ppi_init();APP_ERROR_CHECK(err_code);//配置PPI的频道 PPI通道1err_code = nrfx_ppi_channel_alloc(&my_ppi_channel);APP_ERROR_CHECK(err_code);//设置PPI通道的EPP(事件)和TEP(任务) 两端对应的端口err_code = nrfx_ppi_channel_assign(my_ppi_channel,nrfx_gpiote_in_event_addr_get(BUTTON_1),nrfx_gpiote_out_task_addr_get(BSP_LED_0));APP_ERROR_CHECK(err_code);/*************************///配置PPI的频道   PPI通道2err_code = nrfx_ppi_channel_alloc(&my_ppi_channel_1);APP_ERROR_CHECK(err_code);//设置PPI通道的EPP(事件)和TEP(任务) 两端对应的端口err_code = nrfx_ppi_channel_assign(my_ppi_channel_1,nrfx_gpiote_in_event_addr_get(BUTTON_2),nrfx_gpiote_out_task_addr_get(BSP_LED_1));APP_ERROR_CHECK(err_code);/*************************///申请一个PPI组err_code = nrfx_ppi_group_alloc(&my_ppi_group);APP_ERROR_CHECK(err_code);//将PPI通道1加入PPI组err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel,my_ppi_group);APP_ERROR_CHECK(err_code);//将PPI通道2加入PPI组err_code = nrfx_ppi_channel_include_in_group(my_ppi_channel_1,my_ppi_group);APP_ERROR_CHECK(err_code);}

②  不需要CPU参与,PPI的方式即把组的开启关闭当做是一个PPI的任务,通过PPI事件触发

③ PPI-fork从任务

eg:按下按键同时翻转LED1、2

寄存器

void gpiote_init(void);
void my_ppi_init(void);
int main(void)
{nrf_gpio_cfg_output(LED_3); //配置P0.19为输出,驱动led3 nrf_gpio_cfg_output(LED_4);//配置P0.20为输出,驱动led4 nrf_gpio_pin_set(LED_3);//led3初始状态设置为熄灭  nrf_gpio_pin_set(LED_4);//led4初始状态设置为熄灭 nrf_gpio_cfg_input(BUTTON_3,NRF_GPIO_PIN_PULLUP);//配置P0.15为输入  nrf_gpio_cfg_input(BUTTON_4,NRF_GPIO_PIN_PULLUP);//配置P0.16为输入ret_code_t err_code;gpiote_init();my_ppi_init();while(true){};}void gpiote_init(void)
{nrf_gpio_cfg_input(BUTTON_1,NRF_GPIO_PIN_PULLUP);//配置一个GPIOTE输入任务NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) //触发极性的设置|(BUTTON_1<<GPIOTE_CONFIG_PSEL_Pos)    //配置事件输入|(GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);    //设置为事件模式//配置一个主任务NRF_GPIOTE->CONFIG[1] = (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)|(LED_1<<GPIOTE_CONFIG_PSEL_Pos)|(GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);//配置fork从任务NRF_GPIOTE->CONFIG[2] =(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)|(LED_2<<GPIOTE_CONFIG_PSEL_Pos)|(GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos);}
void my_ppi_init(void)
{NRF_PPI ->CH[0].EEP = (uint32_t)(&NRF_GPIOTE->EVENTS_IN[0]);//配置输入事件NRF_PPI ->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);//配置输出主任务NRF_PPI ->FORK[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[2]);//配置输出从任务//使能通道0NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos);
}

库函数

void gpiote_init(void);
void my_ppi_init(void);
static nrf_ppi_channel_t my_ppi_channel;
int main(void)
{nrf_gpio_cfg_output(LED_3); //配置P0.19为输出,驱动led3 nrf_gpio_cfg_output(LED_4);//配置P0.20为输出,驱动led4 nrf_gpio_pin_set(LED_3);//led3初始状态设置为熄灭  nrf_gpio_pin_set(LED_4);//led4初始状态设置为熄灭 nrf_gpio_cfg_input(BUTTON_3,NRF_GPIO_PIN_PULLUP);//配置P0.15为输入  nrf_gpio_cfg_input(BUTTON_4,NRF_GPIO_PIN_PULLUP);//配置P0.16为输入ret_code_t err_code;gpiote_init();my_ppi_init();while(true){};}void gpiote_init(void)
{ret_code_t err_code;//初始化GPIOTEerr_code = nrf_drv_gpiote_init();APP_ERROR_CHECK(err_code);//配置LED端口翻转输出任务 led1nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//绑定输出端口err_code  = nrf_drv_gpiote_out_init(BSP_LED_0,&out_config);//配置为输出端口任务使能nrf_drv_gpiote_out_task_enable(BSP_LED_0);//配置LED端口翻转输出任务    led2nrf_drv_gpiote_out_config_t out_config_1 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);//绑定输出端口err_code  = nrf_drv_gpiote_out_init(BSP_LED_1,&out_config_1);//配置为输出端口任务使能nrf_drv_gpiote_out_task_enable(BSP_LED_1);//配置按键端口高电平变低电平事件nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);in_config.pull = NRF_GPIO_PIN_PULLUP;//绑定输入端口err_code = nrf_drv_gpiote_in_init(BUTTON_1,&in_config,NULL);APP_ERROR_CHECK(err_code);//配置输入事件使能nrf_drv_gpiote_in_event_enable(BUTTON_1,true);}
void my_ppi_init(void)
{ret_code_t err_code;//初始化PPI模块err_code = nrf_drv_ppi_init();APP_ERROR_CHECK(err_code);//配置PPI的频道err_code = nrfx_ppi_channel_alloc(&my_ppi_channel);APP_ERROR_CHECK(err_code);//设置PPI通道的EPP(事件)和TEP(任务) 两端对应的端口err_code = nrfx_ppi_channel_assign(my_ppi_channel,nrfx_gpiote_in_event_addr_get(BUTTON_1),nrfx_gpiote_out_task_addr_get(BSP_LED_0));APP_ERROR_CHECK(err_code);//PPI-forkerr_code = nrfx_ppi_channel_fork_assign(my_ppi_channel,nrfx_gpiote_out_task_addr_get(BSP_LED_1));//使能PPI通道err_code = nrfx_ppi_channel_enable(my_ppi_channel);APP_ERROR_CHECK(err_code);
}

4.5 PPI-TIMER

4.5.1 精确定时

通过PPI通道,让定时器1定时1秒后开启定时器0的计数,定时器2定时2秒关闭定时器0的计数。

寄存器

void timer0_init(void)
{//定时器0配置为计数模式NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter;//设置定时器的分频NRF_TIMER0->PRESCALER = 9;//定时器位宽NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
}
void timer1_init(void)
{//定时器1配置为定时模式,定时2秒(第2秒停止启动同时开始此时停止优先)//位宽 BITMODE = 16bitNRF_TIMER1->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);//定时器分频值 PRESCALER = 9NRF_TIMER1 ->PRESCALER = 9;//定时器比较清零计数器模式实现不停的定时,本地任务和事件的快捷方式NRF_TIMER1->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);//定时器模式NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;//触发时间 = 0XFFFF/(SysClk/2^PERSCALER) = 65535/31250 = 2.097 secNRF_TIMER1 ->CC[0] = 0xFFFFUL;
}
void timer2_init(void)
{//定时器2配置为定时模式,定时1秒//位宽 BITMODE = 16bitNRF_TIMER2->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);//定时器分频值 PRESCALER = 9NRF_TIMER2 ->PRESCALER = 9;//定时器比较清零计数器模式实现不停的定时,本地任务和事件的快捷方式NRF_TIMER2->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);//定时器模式NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;//触发时间 = 0XFFFF/(SysClk/2^PERSCALER) = 32767/31250 = 1.048 sec 触发比较事件NRF_TIMER2 ->CC[0] = 0x7FFFUL;
}
void ppi_init(void)
{//配置PPI通道0,事件端为定时器1的比较定时事件,任务端为关闭定时器0;NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_TIMER1->EVENTS_COMPARE[0]);NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_TIMER0->TASKS_STOP);//配置PPI通道1,事件端为定时器2的比较事件,任务端为开启定时器0;NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_TIMER2->EVENTS_COMPARE[0]);NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_TIMER0->TASKS_START);//使能PPI通道0、1NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled<< PPI_CHEN_CH0_Pos) |(PPI_CHEN_CH1_Enabled << PPI_CHEN_CH1_Pos);
}/*** @brief Function for main application entry.*/
int main(void)
{uint32_t err_code;bsp_board_init(BSP_INIT_LEDS);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);#ifndef ENABLE_LOOPBACK_TESTint32_t timval;timer0_init();timer1_init();timer2_init();ppi_init();//启动定时器NRF_TIMER1 ->TASKS_START = 1;NRF_TIMER2 ->TASKS_START = 1;while (true){//计数器加1NRF_TIMER0->TASKS_COUNT = 1;//捕获输出NRF_TIMER0->TASKS_CAPTURE[0] =1;//获取计数值timval = NRF_TIMER0->CC[0];printf("count value:%d\r\n",timval);nrf_delay_ms(1048);}
#else// This part of the example is just for testing the loopback .while (true){uart_loopback_test();}
#endif}

库函数

const nrf_drv_timer_t timer0 = NRF_DRV_TIMER_INSTANCE(0);
const nrf_drv_timer_t timer1 = NRF_DRV_TIMER_INSTANCE(1);
const nrf_drv_timer_t timer2 = NRF_DRV_TIMER_INSTANCE(2);nrf_ppi_channel_t my_ppi_channel=NRF_PPI_CHANNEL0 ;
nrf_ppi_channel_t my_ppi_channel2=NRF_PPI_CHANNEL1;void timer0_init(void)
{uint32_t time_ticks;ret_code_t err_code;nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;timer_cfg.mode = NRF_TIMER_MODE_COUNTER;err_code = nrf_drv_timer_init(&timer0,&timer_cfg,NULL);APP_ERROR_CHECK(err_code);
}
void timer1_init(void)
{uint32_t time_ticks;ret_code_t err_code;nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;err_code = nrf_drv_timer_init(&timer1,&timer_cfg,NULL);APP_ERROR_CHECK(err_code);nrf_drv_timer_extended_compare(&timer1,NRF_TIMER_CC_CHANNEL0,0xFFFFUL,NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,false);nrf_drv_timer_enable(&timer1);}
void timer2_init(void)
{uint32_t time_ticks;ret_code_t err_code;nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;err_code = nrf_drv_timer_init(&timer2,&timer_cfg,NULL);APP_ERROR_CHECK(err_code);nrf_drv_timer_extended_compare(&timer2,NRF_TIMER_CC_CHANNEL0,0x7FFFUL,NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,false);nrf_drv_timer_enable(&timer2);
}
void ppi_init(void)
{ret_code_t err_code;err_code = nrf_drv_ppi_init();APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_alloc(&my_ppi_channel);APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_assign(my_ppi_channel,nrf_drv_timer_event_address_get(&timer2,NRF_TIMER_EVENT_COMPARE0),nrf_drv_timer_task_address_get(&timer0,NRF_TIMER_TASK_START));APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_enable(my_ppi_channel);APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_alloc(&my_ppi_channel2);APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_assign(my_ppi_channel2,nrf_drv_timer_event_address_get(&timer1,NRF_TIMER_EVENT_COMPARE0),nrf_drv_timer_task_address_get(&timer0,NRF_TIMER_TASK_STOP));APP_ERROR_CHECK(err_code);err_code = nrf_drv_ppi_channel_enable(my_ppi_channel2);APP_ERROR_CHECK(err_code);
}/*** @brief Function for main application entry.*/
int main(void)
{uint32_t err_code;bsp_board_init(BSP_INIT_LEDS);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);#ifndef ENABLE_LOOPBACK_TESTint32_t timval;timer0_init();timer1_init();timer2_init();ppi_init();//启动定时器0 nrf_drv_timer_enable(&timer0);while (true){//计数器加1nrfx_timer_increment(&timer0);//获取计数值timval = nrfx_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL0);printf("count value:%d\r\n",timval);nrf_delay_ms(1048);}
#else// This part of the example is just for testing the loopback .while (true){uart_loopback_test();}
#endif}

4.5.2 软件PWM

实现方法通过定时器触发GPIOTE的输出电平变化来实现,定时器作为任务,触发的GPIOTE作为事件。

eg:使用一个定时器通过不同的CC[],编写两路PWM输出,需要4路PPI通道。每两路PPI通道产生一个PWM输出,其中一路作为占空比的输出控制,另外一路作为PWM周期的控制整个过程CPU不参与其中。

① eg:固定周期不同占空比的两路PWM

寄存器

/*** @brief Function for application main entry.*/void timer0_init(void){NRF_TIMER0 ->PRESCALER = 4;    //2^4 16分频成1M时钟源NRF_TIMER0 ->MODE = 0;  //time模式NRF_TIMER0 ->BITMODE = 3;   //32bitNRF_TIMER0->CC[1] = 5000;    //cc[1]的值等于5ms,这里相当于方波的周期为5msNRF_TIMER0->CC[0] = 100;   //cc[0]为占空比值NRF_TIMER0->CC[2] = 5000;   //cc[2]的值等于5ms,这里相当于方波的周期为5msNRF_TIMER0->CC[3] = 4900;  //cc[3]为占空比值NRF_TIMER0->SHORTS = 1<<1;    //设置倒计时到CC1中的值时自动清零重新开始计数;NRF_TIMER0->SHORTS = 1<<2;   //设置倒计时到CC2中的值时自动清零重新开始计数;NRF_TIMER0->TASKS_START = 1;   //开启timer}//电平不停翻转,配置GPIOTE 0 与1void gpoite_init(void){NRF_GPIOTE->CONFIG[0] = ( 3 << 0 )        //作为task模式| ( BSP_LED_0 << 8) //设置PWM输出引脚| ( 3 << 16 )     //设置task为翻转PWM引脚的电平| ( 1 << 20);     //初始输出电平为高NRF_GPIOTE->CONFIG[1] = ( 3 << 0 )        //作为task模式| ( BSP_LED_1 << 8) //设置PWM输出引脚| ( 3 << 16 )     //设置task为翻转PWM引脚的电平| ( 1 << 20);     //初始输出电平为高}
void ppi_set(void)
{//配置每个PPI对应的事件和任务NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[0]);NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[1]);NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);NRF_PPI->CH[2].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[2]);NRF_PPI->CH[2].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);NRF_PPI->CH[3].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[3]);NRF_PPI->CH[3].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);//任务绑定//两个通道的task端绑定的都是翻转电平的task//使能PPI通道 0 和 通道1    1111NRF_PPI->CHENSET = 0xf;
}int main(void)
{timer0_init();gpoite_init();ppi_set();/* Toggle LEDs. */while (true){}
}

现象 LED1与LED2的亮度不一样

② eg:变占空比动态调节LED亮度\

APP_PWM_INSTANCE(PWM1,1);                   // 创建一个使用定时器1产生PWM波的实例
static volatile bool ready_flag;            // 使用一个标志位表示PWM状态void pwm_ready_callback(uint32_t pwm_id)    // PWM callback function
{ready_flag = true;
}int main(void)
{ret_code_t err_code;//配置2个通道的PWM ,200Hz(50000us = 5ms),通过LED0、1管脚输出app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_2CH(5000L,BSP_LED_0,BSP_LED_1);//初始输出电平配置为高pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_HIGH;//初始和使能PWMerr_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);APP_ERROR_CHECK(err_code);app_pwm_enable(&PWM1);uint32_t value;while (true){for (uint8_t i = 0; i < 40; ++i){value = (i < 20) ? (i * 5) : (100 - (i - 20) * 5);ready_flag = false;/* 设置占空比 - 不停设置直到PWM准备好. */while (app_pwm_channel_duty_set(&PWM1, 0, value) == NRF_ERROR_BUSY);/* 等待回调 */while (!ready_flag);APP_ERROR_CHECK(app_pwm_channel_duty_set(&PWM1, 1, value));nrf_delay_ms(25);}}}

4.6 I²S

4.6.1 时序图

4.6.2 结构体说明

#define NRFX_I2S_DEFAULT_CONFIG                                   \
{                                                                 \
    .sck_pin      = NRFX_I2S_CONFIG_SCK_PIN,                      \
    .lrck_pin     = NRFX_I2S_CONFIG_LRCK_PIN,                     \
    .mck_pin      = NRFX_I2S_CONFIG_MCK_PIN,                      \
    .sdout_pin    = NRFX_I2S_CONFIG_SDOUT_PIN,                    \
    .sdin_pin     = NRFX_I2S_CONFIG_SDIN_PIN,                     \
    .irq_priority = NRFX_I2S_CONFIG_IRQ_PRIORITY,                 \
    .mode         = (nrf_i2s_mode_t)NRFX_I2S_CONFIG_MASTER,       \
    .format       = (nrf_i2s_format_t)NRFX_I2S_CONFIG_FORMAT,     \
    .alignment    = (nrf_i2s_align_t)NRFX_I2S_CONFIG_ALIGN,       \
    .sample_width = (nrf_i2s_swidth_t)NRFX_I2S_CONFIG_SWIDTH,     \
    .channels     = (nrf_i2s_channels_t)NRFX_I2S_CONFIG_CHANNELS, \
    .mck_setup    = (nrf_i2s_mck_t)NRFX_I2S_CONFIG_MCK_SETUP,     \
    .ratio        = (nrf_i2s_ratio_t)NRFX_I2S_CONFIG_RATIO,       \
}

NRFX_I2S_CONFIG_LRCK_PIN : 配置LRCK管脚

#define NRFX_I2S_CONFIG_LRCK_PIN 30:系统默认配置为30

LRCK:字段选择信号WS,也叫LRCLK,用于切换左右声道的数据。WS的频率 = 采样频率。

nrf_drv_i2s_config_t config = NRF_DRV_I2S_DEFAULT_CONFIG;
    // In Master mode the MCK frequency and the MCK/LRCK ratio should be
    // set properly in order to achieve desired audio sample rate (which
    // is equivalent to the LRCK frequency).
    // For the following settings we'll get the LRCK frequency equal to
    // 15873 Hz (the closest one to 16 kHz that is possible to achieve).
    config.sdin_pin  = I2S_SDIN_PIN;
    config.sdout_pin = I2S_SDOUT_PIN;
    config.mck_pin = 29;
    config.mck_setup = NRF_I2S_MCK_32MDIV21;
    config.ratio     = NRF_I2S_RATIO_96X;
    config.channels  = NRF_I2S_CHANNELS_STEREO;
    err_code = nrf_drv_i2s_init(&config, data_handler);

LRCK(采样频率)= NRF_I2S_MCK_32MDIV21/NRF_I2S_RATIO_96X = 32Mhz/21/96 = 15873Hz ≈16K

NRFX_I2S_CONFIG_SCK_PIN  :配置SCK的管脚

#define NRFX_I2S_CONFIG_SCK_PIN 31:系统默认配置为31

SCK:也叫位时钟BCLK。对应数字音频的每一位数据,SCK都有1个脉冲。SCK的频率 = 声道数 * 采样频率 * 采样位数。这里的声道数为2

默认配置的为右边声道,16位采样数。
// <0=> Stereo 
// <1=> Left 
// <2=> Right

#ifndef NRFX_I2S_CONFIG_CHANNELS
#define NRFX_I2S_CONFIG_CHANNELS 1
#endif

// <0=> 8 
// <1=> 16 
// <2=> 24

#ifndef NRFX_I2S_CONFIG_SWIDTH
#define NRFX_I2S_CONFIG_SWIDTH 1
#endif

SCK = 2 *16*16 = 512 Khz

.mck_pin      = NRFX_I2S_CONFIG_MCK_PIN,                      \

// <o> NRFX_I2S_CONFIG_MCK_PIN - MCK pin 
#ifndef NRFX_I2S_CONFIG_MCK_PIN
#define NRFX_I2S_CONFIG_MCK_PIN 255
#endif

MCK,主时钟,也叫作系统时钟,是采样频率的256倍、384倍、512倍或者768倍,频率范围再0.256~16MHz。

这里指定为 PIN255  (我给他改成29口输出)并观察

MCK的频率是之前配置的 NRF_I2S_MCK_32MDIV21 对应的是1.52Mhz

或者用 LRCK*CONGIFG  = 16*96=1536Khz = 1.536Mhz

.sdout_pin    = NRFX_I2S_CONFIG_SDOUT_PIN,                    \
    .sdin_pin     = NRFX_I2S_CONFIG_SDIN_PIN,                     \

配置I²S的数据输入输出管脚

这里配置输出为 27管脚可以捕获到有电平变化输出

输入管脚是作为数据输入端没有电平变化

.irq_priority = NRFX_I2S_CONFIG_IRQ_PRIORITY,                 \中断的优先级

.mode         = (nrf_i2s_mode_t)NRFX_I2S_CONFIG_MASTER,       \ 配置为主机模式

\配置为I2S通信他还支左对齐或右对齐格式

.format       = (nrf_i2s_format_t)NRFX_I2S_CONFIG_FORMAT,

\ 数据左对格式上面已经配置为标准的I2S格式所有这里没有起效
 .alignment    = (nrf_i2s_align_t)NRFX_I2S_CONFIG_ALIGN,

\ 配置位宽默认16bits  
.sample_width = (nrf_i2s_swidth_t)NRFX_I2S_CONFIG_SWIDTH,

\默认配置声道为左声道 一个LRCLK周期(1/Fs)包括发送左声道和右声道数据。
.channels     = (nrf_i2s_channels_t)NRFX_I2S_CONFIG_CHANNELS

\MCK的设置
 .mck_setup    = (nrf_i2s_mck_t)NRFX_I2S_CONFIG_MCK_SETUP,

\比特率
.ratio        = (nrf_i2s_ratio_t)NRFX_I2S_CONFIG_RATIO,

4.6.3 官方例程分析

官方提供了一个I2S的回环数据传输,下面分析是如何实现双缓冲的乒乓结构

加了分析注释的源代码


#include <stdio.h>
#include "nrf_drv_i2s.h"
#include "nrf_delay.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "boards.h"#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"#define LED_OK      BSP_BOARD_LED_0
#define LED_ERROR   BSP_BOARD_LED_1#define I2S_DATA_BLOCK_WORDS    512  //一个块的大小
static uint32_t m_buffer_rx[2][I2S_DATA_BLOCK_WORDS];
static uint32_t m_buffer_tx[2][I2S_DATA_BLOCK_WORDS];// Delay time between consecutive I2S transfers performed in the main loop
// (in milliseconds).
#define PAUSE_TIME          500
// Number of blocks of data to be contained in each transfer.
#define BLOCKS_TO_TRANSFER  20      //传多少个块static uint8_t volatile m_blocks_transferred     = 0; //有多少个块被传输
static uint8_t          m_zero_samples_to_ignore = 0;  //开始传输时会先传两个字节全0的数据流,并非我们要发送的数据
static uint16_t         m_sample_value_to_send;
static uint16_t         m_sample_value_expected;
static bool             m_error_encountered;static uint32_t       * volatile mp_block_to_fill  = NULL;
static uint32_t const * volatile mp_block_to_check = NULL;static void prepare_tx_data(uint32_t * p_block)
{// These variables will be both zero only at the very beginning of each// transfer, so we use them as the indication that the re-initialization// should be performed.if (m_blocks_transferred == 0 && m_zero_samples_to_ignore == 0){// Number of initial samples (actually pairs of L/R samples) with zero// values that should be ignored - see the comment in 'check_samples'.m_zero_samples_to_ignore = 2;m_sample_value_to_send   = 0xCAFE;m_sample_value_expected  = 0xCAFE;m_error_encountered      = false;}// [each data word contains two 16-bit samples]uint16_t i;for (i = 0; i < I2S_DATA_BLOCK_WORDS; ++i){uint16_t sample_l = m_sample_value_to_send - 1;uint16_t sample_r = m_sample_value_to_send + 1;++m_sample_value_to_send;uint32_t * p_word = &p_block[i];((uint16_t *)p_word)[0] = sample_l;((uint16_t *)p_word)[1] = sample_r;}
}static bool check_samples(uint32_t const * p_block)
{// [each data word contains two 16-bit samples]uint16_t i;for (i = 0; i < I2S_DATA_BLOCK_WORDS; ++i){uint32_t const * p_word = &p_block[i];uint16_t actual_sample_l = ((uint16_t const *)p_word)[0];uint16_t actual_sample_r = ((uint16_t const *)p_word)[1];// Normally a couple of initial samples sent by the I2S peripheral// will have zero values, because it starts to output the clock// before the actual data is fetched by EasyDMA. As we are dealing// with streaming the initial zero samples can be simply ignored.if (m_zero_samples_to_ignore > 0 &&actual_sample_l == 0 &&actual_sample_r == 0){--m_zero_samples_to_ignore;}else{m_zero_samples_to_ignore = 0;uint16_t expected_sample_l = m_sample_value_expected - 1;uint16_t expected_sample_r = m_sample_value_expected + 1;++m_sample_value_expected;if (actual_sample_l != expected_sample_l ||actual_sample_r != expected_sample_r){NRF_LOG_INFO("%3u: %04x/%04x, expected: %04x/%04x (i: %u)",m_blocks_transferred, actual_sample_l, actual_sample_r,expected_sample_l, expected_sample_r, i);return false;}}}NRF_LOG_INFO("%3u: OK", m_blocks_transferred);return true;
}static void check_rx_data(uint32_t const * p_block)
{++m_blocks_transferred;if (!m_error_encountered){m_error_encountered = !check_samples(p_block);}if (m_error_encountered){bsp_board_led_off(LED_OK);bsp_board_led_invert(LED_ERROR);}else{bsp_board_led_off(LED_ERROR);bsp_board_led_invert(LED_OK);}
}static void data_handler(nrf_drv_i2s_buffers_t const * p_released,uint32_t                      status)
{// 'nrf_drv_i2s_next_buffers_set' is called directly from the handler// each time next buffers are requested, so data corruption is not// expected.ASSERT(p_released);// When the handler is called after the transfer has been stopped// (no next buffers are needed, only the used buffers are to be// released), there is nothing to do.if (!(status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED)){NRF_LOG_INFO("----STOP-----");return;}//p_released 指向结构的指针,该结构指向以前传递给驱动程序的缓冲区的指针,//该驱动程序将不再被它访问(现在可以安全地释放它们或将其用于其他目的,特别是用于传输的下一部分)// First call of this handler occurs right after the transfer is started.// No data has been transferred yet at this point, so there is nothing to// check. Only the buffers for the next part of the transfer should be// provided.if (!p_released->p_rx_buffer){//在开始传输后首次调用处理程序时,此结构中的两个指针均为 NULL,因为此时尚未传输任何数据。//此时p_released = NULL,下一个buff指向m_buffer_tx[1]。NRF_LOG_INFO("----DONE-----");nrf_drv_i2s_buffers_t const next_buffers = {.p_rx_buffer = m_buffer_rx[1],.p_tx_buffer = m_buffer_tx[1],};//指定下一个buffAPP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&next_buffers));mp_block_to_fill = m_buffer_tx[1];}else{if(p_released->p_tx_buffer == m_buffer_tx[0]){NRF_LOG_INFO("-----m_buffer_tx[0]-----");}else if(p_released->p_tx_buffer == m_buffer_tx[1]){NRF_LOG_INFO("-----m_buffer_tx[1]-----");}//在所有连续调用中,指针指定刚刚完成的传输部分中已发送的内容 (TX) 和已接收的内容 (RX)NRF_LOG_INFO("----NEXT-----");mp_block_to_check = p_released->p_rx_buffer;// The driver has just finished accessing the buffers pointed by// 'p_released'. They can be used for the next part of the transfer// that will be scheduled now.//此时m_buffer_tx[0] 传输完毕,p_released指向的是m_buffer_tx[0],下一个buff指向m_buffer_tx[0]。APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(p_released));// The pointer needs to be typecasted here, so that it is possible to// modify the content it is pointing to (it is marked in the structure// as pointing to constant data because the driver is not supposed to// modify the provided data).mp_block_to_fill = (uint32_t *)p_released->p_tx_buffer;}
}void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{bsp_board_leds_on();app_error_save_and_stop(id, pc, info);
}int main(void)
{uint32_t err_code = NRF_SUCCESS;bsp_board_init(BSP_INIT_LEDS);err_code = NRF_LOG_INIT(NULL);APP_ERROR_CHECK(err_code);NRF_LOG_DEFAULT_BACKENDS_INIT();NRF_LOG_INFO("#########################1");nrf_drv_i2s_config_t config = NRF_DRV_I2S_DEFAULT_CONFIG;// In Master mode the MCK frequency and the MCK/LRCK ratio should be// set properly in order to achieve desired audio sample rate (which// is equivalent to the LRCK frequency).// For the following settings we'll get the LRCK frequency equal to// 15873 Hz (the closest one to 16 kHz that is possible to achieve).config.sdin_pin  = I2S_SDIN_PIN;config.sdout_pin = I2S_SDOUT_PIN;config.mck_setup = NRF_I2S_MCK_32MDIV21;config.ratio     = NRF_I2S_RATIO_96X;config.channels  = NRF_I2S_CHANNELS_STEREO;err_code = nrf_drv_i2s_init(&config, data_handler);APP_ERROR_CHECK(err_code);for (;;){NRF_LOG_INFO("#########################2");m_blocks_transferred = 0;mp_block_to_fill  = NULL;mp_block_to_check = NULL;prepare_tx_data(m_buffer_tx[0]);nrf_drv_i2s_buffers_t const initial_buffers = {.p_tx_buffer = m_buffer_tx[0],.p_rx_buffer = m_buffer_rx[0],};err_code = nrf_drv_i2s_start(&initial_buffers, I2S_DATA_BLOCK_WORDS, 0);APP_ERROR_CHECK(err_code);do {// Wait for an event.__WFE();// Clear the event register.__SEV();__WFE();if (mp_block_to_fill){prepare_tx_data(mp_block_to_fill);mp_block_to_fill = NULL;}if (mp_block_to_check){check_rx_data(mp_block_to_check);mp_block_to_check = NULL;}} while (m_blocks_transferred < BLOCKS_TO_TRANSFER);nrf_drv_i2s_stop();NRF_LOG_FLUSH();bsp_board_leds_off();nrf_delay_ms(PAUSE_TIME);}
}/** @} */

 输出结果

 分析

nRF5 SDK v15.3.0: I2S Loopback Example

nRF52832 — 使用nRF52832的I2S播放音频_52832 蓝牙语音_文化人Sugar的博客-CSDN博客

一文搞懂I2S通信总线_不脱发的程序猿的博客-CSDN博客

【通信协议】I2S/IIS总线介绍_iis接口_努力努力再努力~~的博客-CSDN博客

I2S的理解_i2s通信的详细讲解_wholetus的博客-CSDN博客

4.6.4 通过D类功放生成波形

三角波,正弦波,方波数据通过I2S传输到MAX98357生成对应波形。

#define PIN_MCK    (13)
#define PIN_SCK    (14)
#define PIN_LRCK   (15)
#define PIN_SDOUT  (16)
void sin_text();int main(void)
{sin_text();}void sin_text()
{// Sine table (stereo, so every value is duplicated)int16_t sine_table[] = {2047,2447,2831,3185,3498,3750,3939,4056,4095,4056,3939,3758,3495,3185,2831,2447,2047,1647,1263,909,599,344,155,38,0,38,155,344,599,909,1263,1647};int16_t triangle_table[] = {0,256,512,768,1024,1279,1535,1791,2047,2303,2559,2815,3071,3326,3582,3838,4095,3838,3582,3326,3071,2815,2559,2303,2047,1791,1535,1279,1024,768,512,256};int16_t square_table[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,4095,};// Enable transmissionNRF_I2S->CONFIG.TXEN = (I2S_CONFIG_TXEN_TXEN_ENABLE << I2S_CONFIG_TXEN_TXEN_Pos);// Enable MCK generatorNRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos);// MCKFREQ = 4 MHzNRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11  << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;// Ratio = 64 NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_64X << I2S_CONFIG_RATIO_RATIO_Pos;// Master mode, 16Bit, left alignedNRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos;NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16BIT << I2S_CONFIG_SWIDTH_SWIDTH_Pos;NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_LEFT << I2S_CONFIG_ALIGN_ALIGN_Pos;// Format = I2SNRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S << I2S_CONFIG_FORMAT_FORMAT_Pos;// Use stereo NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_STEREO << I2S_CONFIG_CHANNELS_CHANNELS_Pos;// Configure pinsNRF_I2S->PSEL.MCK = (PIN_MCK << I2S_PSEL_MCK_PIN_Pos);NRF_I2S->PSEL.SCK = (PIN_SCK << I2S_PSEL_SCK_PIN_Pos); NRF_I2S->PSEL.LRCK = (PIN_LRCK << I2S_PSEL_LRCK_PIN_Pos); NRF_I2S->PSEL.SDOUT = (PIN_SDOUT << I2S_PSEL_SDOUT_PIN_Pos);NRF_I2S->ENABLE = 1;// Configure data pointerNRF_I2S->TXD.PTR = (uint32_t)&square_table[0];NRF_I2S->RXTXD.MAXCNT = sizeof(square_table) / sizeof(uint32_t);// Start transmitting I2S dataNRF_I2S->TASKS_START = 1;// Since we are not updating the TXD pointer, the sine wave will play over and over again.// The TXD pointer can be updated after the EVENTS_TXPTRUPD arrives.while (1){__WFE();}}
  • D类功放输出的是包含音频信息的调制方波,而非直接的音频波形数据所以示波器无法直接看输出的波形。详见

  • 所以我是通过音频采集器用AU来采集
  • 方波
  • 三角波
  • 正弦波 

相关文章

可生成采样率对照表

生成44.1Khz

可参考案例

原始数据不能用const修饰,因为EDMA只能读取内存中的数据,不能读取FLASH中的

输中使用的uint32_t常量指针类型,不能使用的uint8_t常量指针类型

I2S模块为双缓冲,可以在写入电流的同时准备下一个缓冲器,每个缓冲器的最大大小为8192bytes

通过nRF52840 I2S传输正弦波,使用简单的DMA和MAX98357A编解码器 - 北欧问答 - 北欧开发区 - 北欧开发区 (nordicsemi.com)

4.6.5 通过D类功播放音乐

这个折腾了2天参照例程和资料

  • 截取了一段1S钟16位44.1Khz的WAV音频数据;
  • 用的I2S的双缓冲,这里我设置为10K的缓冲太小了会卡顿。
#include "wav.h" //wav数组#define I2S_DATA_BLOCK_WORDS    10*1024  //buff大小
static uint32_t m_buffer_tx[2][I2S_DATA_BLOCK_WORDS];static uint32_t       * volatile mp_block_to_fill  = NULL;uint32_t length;static void prepare_tx_data(uint32_t * p_block)
{memcpy(p_block,wave_data+length,I2S_DATA_BLOCK_WORDS);length +=I2S_DATA_BLOCK_WORDS;if(length >= 39396) length = 0;
}static void data_handler(nrf_drv_i2s_buffers_t const * p_released,uint32_t                      status)
{ASSERT(p_released);if (!(status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED)){return;}if (!p_released->p_tx_buffer){nrf_drv_i2s_buffers_t const next_buffers = {.p_tx_buffer = m_buffer_tx[1],};APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&next_buffers));mp_block_to_fill = m_buffer_tx[1];}else{APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(p_released));mp_block_to_fill = (uint32_t *)p_released->p_tx_buffer;}
}void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{bsp_board_leds_on();app_error_save_and_stop(id, pc, info);
}
void sin_text();#define PIN_MCK    (13)
#define PIN_SCK    (14)
#define PIN_LRCK   (15)
#define PIN_SDOUT  (16)
#define I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV3 (0x50000000UL)int main(void)
{#if 1uint32_t err_code = NRF_SUCCESS;bsp_board_init(BSP_INIT_LEDS);err_code = NRF_LOG_INIT(NULL);APP_ERROR_CHECK(err_code);NRF_LOG_DEFAULT_BACKENDS_INIT();nrf_drv_i2s_config_t config = NRF_DRV_I2S_DEFAULT_CONFIG;config.sck_pin      = PIN_SCK;config.lrck_pin      = PIN_LRCK;config.sdout_pin    = PIN_SDOUT;config.mode        = NRF_I2S_MODE_MASTER;config.format        = NRF_I2S_FORMAT_I2S;config.alignment      = NRF_I2S_ALIGN_LEFT;config.sample_width = NRF_I2S_SWIDTH_16BIT;config.channels   = NRF_I2S_CHANNELS_LEFT;config.mck_setup   = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV3;config.ratio          = NRF_I2S_RATIO_256X;err_code = nrf_drv_i2s_init(&config, data_handler);APP_ERROR_CHECK(err_code);mp_block_to_fill  = NULL;prepare_tx_data(m_buffer_tx[0]);nrf_drv_i2s_buffers_t const initial_buffers = {.p_tx_buffer = m_buffer_tx[0]};err_code = nrf_drv_i2s_start(&initial_buffers, I2S_DATA_BLOCK_WORDS, 0);APP_ERROR_CHECK(err_code);for (;;){// Wait for an event.__WFE();// Clear the event register.__SEV();__WFE();if (mp_block_to_fill){prepare_tx_data(mp_block_to_fill);mp_block_to_fill = NULL;}}
#endif
}

原始音频波形

生成的

4.7 SAADC

4.7.1 框图

4.7.2 主要配置项

①:采样模式

②:信号增益

③:参考电压

④:采样精度

⑤:工作模式

结合PPI,双Buff,EDMA来运行;

nRF52832学习记录

实战经验,Nordic 52832 低功耗模式与唤醒机制 (360doc.com)

STM32Cube MX USB双设备MSC+CDC 实现虚拟U盘+虚拟串口_stm32 usb虚拟串口和u盘_Pzkkkkk的博客-CSDN博客

nRF52833-peripheral相关推荐

  1. android5.0(Lollipop) BLE Peripheral牛刀小试

    转载请表明作者:http://blog.csdn.net/lansefeiyang08/article/details/46468743 知道Android L对蓝牙对了一些改进,包括添加A2dp s ...

  2. android+5.0+ble,android5.0(Lollipop) BLE Peripheral牛刀小试(示例代码)

    转载请表明作者:http://blog.csdn.net/lansefeiyang08/article/details/46468743 知道Android L对蓝牙对了一些改进.包含加入A2dp s ...

  3. android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser

    android 从4.3系统开始可以连接BLE设备,这个大家都知道了.iOS是从7.0版本开始支持BLE.android 进入5.0时代时,开放了一个新功能,手机可以模拟设备发出BLE广播, 这个新功 ...

  4. android ble status,Android BLE peripheral disconnects with status code BLE_HCI_INSTANT_PASSED(0x28)

    问题 My application is able to connect to the BLE peripheral(which is an OBDII/J1939 device) device su ...

  5. Linux PCI驱动框架分析:(Peripheral Component Interconnect,外部设备互联)

    <DPDK 20.05 | rte_pci_bus思维导图 | 第一版> <linux系统下:IO端口,内存,PCI总线 的 读写(I/O)操作> <Linux指令:ls ...

  6. STM32固件库(Standard Peripheral Libraries )官网下载方法

    首先进入ST官网http://www.stmicroelectronics.com.cn/content/st_com/zh.html  1:选择"产品目录" 2:选择" ...

  7. STM32 Tips:如何从ST官方网站上下载STM32标准外设库(STM32F10x standard peripheral library)

    入手了一块STM32F107VCT6开发板,配置开发环境时需要一个库:STM32F10x标准外设库(STM32F10x standard peripheral library),在网上看到很多初学者和 ...

  8. 低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端

    低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端 Android对外模模式(peripheral)的支持 从Android5.0开始才支持 关键术语和概念 以下是关键BLE术语和 ...

  9. AliOS-Things+ESP32 BLE篇 (1)BLE peripheral

    由于STM32的板子没有自带蓝牙和wifi模组,所以外设方面的demo,我选择放到乐鑫的ESP32模组上.一方面是由于ESP32这块板子有丰富的BT/WIFI的实现例程,还因为乐鑫的这款SOC扩展性很 ...

  10. android BLE Peripheral 模拟 ibeacon 发出ble 广播

    原文地址: https://www.cnblogs.com/CharlesGrant/p/7155211.html Android对外模模式(peripheral)的支持: 从Android 5.0+ ...

最新文章

  1. 启动VIP报CRS-1028/CRS-0223致使VIP状态为UNKNOWN故障分析与解决
  2. NKStartup的参数KData
  3. Winform开发几个常用的开发经验及知识积累(一)
  4. Android 4.0 截屏(Screenshot)代码流程小结
  5. Constructor sap.ui.core.ComponentContainer has been called without new operator
  6. PHP在不同页面间传递Json数据示例代码
  7. 1000道Python题库系列分享17(17道判断题)
  8. 女神相册密码忘记了,我只用Python写了20行代码
  9. 【翻译】在Ext JS 6通用应用程序中使用既共享又特定于视图的代码
  10. Atitit uke签名规范 与防伪鉴别 attilax总结
  11. 【经典算法实现 16】阿克曼函数(非递归实现 代码优化)
  12. CentOS7下载安装JDK1.8
  13. 微信小程序 audio 音频 组件
  14. 求两个数的最小公倍数c语言程序,用C语言求两个数的最大公约数和最小公倍数...
  15. 解决VMware装上Mac后icloud无法激活 【安装QQ发现不能注册Apple ID】
  16. July:海量数据处理
  17. Google Android 原生Rom 下载地址及刷机教程--Factory Images for Nexus and Pixel Devices
  18. linux+ros2 launch文件开机自启动
  19. [COGS2189][HZOI 2015]帕秋莉的超级多项式-NTT-多项式求逆-多项式求ln-多项式开方-多项式求exp-多项式快速幂
  20. Java多维数组是什么,怎么用?

热门文章

  1. Flutter之事件处理
  2. p2p网络摄像头的工作原理
  3. 候选区域(Region proposals )
  4. 阅读作业之The New Methodology——洪虹
  5. NewsRecommendation
  6. 再怎么链上 还不是要结合传统经济的借贷靠谱吗?
  7. 电脑开机密码忘记了怎么办?使用优盘重装系统
  8. 软件测试培训三个月,找到工作了11K,面试总结分享给大家
  9. 元素类型为 mapper 的内容必须匹配 (cache-ref|cache|resultMap*|parameterMap*|sql*|insert*|update*|delete*|selec
  10. Apache 日志分类及作用