nRF52833-peripheral

目录

一、开发环境的搭建安装

二、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:时钟框图 

nRF52833-peripheral_第1张图片

2.2:总线框图(部分)

nRF52833-peripheral_第2张图片

 其中 :

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

2.3 USB框图

nRF52833-peripheral_第3张图片

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

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

USB 为什么一般选择48MHz

2.4 引脚、时钟

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

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

nRF52833-peripheral_第4张图片

2.5 sdk_config.h配置文件 

nRF52833-peripheral_第5张图片

// nRF_Drivers

//

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

nRF52833-peripheral_第6张图片

// GPIOTE_ENABLED - nrf_drv_gpiote - GPIOTE peripheral driver - legacy layer

//

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

nRF52833-peripheral_第7张图片

#ifndef GPIOTE_ENABLED

#define GPIOTE_ENABLED 1

#endif

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

nRF52833-peripheral_第8张图片

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

nRF52833-peripheral_第9张图片

// 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

// GPIOTE_CONFIG_IRQ_PRIORITY  - Interrupt priority

// 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

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

nRF52833-peripheral_第10张图片

// GPIOTE_CONFIG_IRQ_PRIORITY  - Interrupt priority

// <0=> 0 (highest)

// <1=> 1

// <2=> 2

// <3=> 3

// <4=> 4

// <5=> 5

// <6=> 6

// <7=> 7

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

nRF52833-peripheral_第11张图片

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

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

三、SDK中提供52833的例程

nRF52833-peripheral_第12张图片

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

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

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

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

四、外设使用

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

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

nRF52833-peripheral_第13张图片

4.1 PWM

 蓝牙芯片nRF52832之PWM的使用

NRF52832 PWM 占空比调整详解

nRF52833-peripheral_第14张图片  

nRF52833-peripheral_第15张图片 第一种赋值方式

nRF52833-peripheral_第16张图片

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

nRF52833-peripheral_第17张图片

 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,011
			uint8_t channel    = my_demo1_phase >> 1;
        bool    down       = my_demo1_phase & 1; //0(递增),1(递减),0,1,0,1
        bool    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 
}

效果实现变周期 

nRF52833-peripheral_第18张图片

4.2 定时器
4.2.1 定时器框图

nRF52833-peripheral_第19张图片nRF52833-peripheral_第20张图片

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

nRF52833-peripheral_第21张图片

nRF52833-peripheral_第22张图片

nRF52833-peripheral_第23张图片

 4.2.2 寄存器法配置

配置ms级的定时器 

nRF52833-peripheral_第24张图片

nRF52833-peripheral_第25张图片

 4.2.3 库函数方式
#include 
#include 
#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:当按键按下会产生一个相应的中断,并在中断处理事件

nRF52833-peripheral_第26张图片

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口通用一个通道)

nRF52833-peripheral_第27张图片

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绑定POTR
	nrf_drv_gpiote_in_init(BUTTON_1,&in_config,in_pin_handeer);
	nrf_drv_gpiote_in_event_enable(BUTTON_1,true);
	//配置按键1绑定POTR
	nrf_drv_gpiote_in_init(BUTTON_2,&in_config,in_pin_handeer);
	nrf_drv_gpiote_in_event_enable(BUTTON_2,true);
	//配置按键2绑定POTR
	nrf_drv_gpiote_in_init(BUTTON_3,&in_config,in_pin_handeer);
	nrf_drv_gpiote_in_event_enable(BUTTON_3,true);
	//配置按键3绑定POTR
	nrf_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个都可以?)

nRF52833-peripheral_第28张图片nRF52833-peripheral_第29张图片 nRF52833-peripheral_第30张图片

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);
	
	

	
#endif
	

    while (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食用

使用的基本配置流程

nRF52833-peripheral_第31张图片

4.4.1 普通PPI的配置

eg :按键触发LED寄存器版

nRF52833-peripheral_第32张图片

 库函数

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;
	//初始化GPIOTE
	err_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参与的方式 

nRF52833-peripheral_第33张图片nRF52833-peripheral_第34张图片

nRF52833-peripheral_第35张图片

 上述是吧按键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);
			//使能GROUP
			err_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);
			//使能GROUP
			err_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;
	//初始化GPIOTE
	err_code = nrf_drv_gpiote_init();
	APP_ERROR_CHECK(err_code);
	
	//配置LED端口翻转输出任务		按键1---LED0
	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);
	
	
	//配置LED端口翻转输出任务		按键2---LED1
	nrf_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通道1
	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的频道	PPI通道2
	err_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事件触发

nRF52833-peripheral_第36张图片

 ③ 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<CONFIG[1] = 
	(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
	|(LED_1<CONFIG[2] =
	(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
	|(LED_2<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]);//配置输出从任务
	
	//使能通道0
	NRF_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;
	//初始化GPIOTE
	err_code = nrf_drv_gpiote_init();
	APP_ERROR_CHECK(err_code);
	
	//配置LED端口翻转输出任务	led1
	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);
	
	//配置LED端口翻转输出任务	led2
	nrf_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-fork
	err_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 = 16bit
	NRF_TIMER1->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);
	//定时器分频值 PRESCALER = 9
	NRF_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 sec
	NRF_TIMER1 ->CC[0] = 0xFFFFUL;
}
void timer2_init(void)
{
	//定时器2配置为定时模式,定时1秒
	
	//位宽 BITMODE = 16bit
	NRF_TIMER2->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);
	//定时器分频值 PRESCALER = 9
	NRF_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、1
	NRF_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
#else
          NRF_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_TEST
	
	int32_t timval;
    timer0_init();
	timer1_init();
	timer2_init();
	ppi_init();
	  
	//启动定时器
	 NRF_TIMER1 ->TASKS_START = 1;
	 NRF_TIMER2 ->TASKS_START = 1;
    while (true)
    {
       //计数器加1
		NRF_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
#else
          NRF_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_TEST
	
	int32_t timval;
    timer0_init();
	timer1_init();
	timer2_init();
	ppi_init();
	//启动定时器0 
	nrf_drv_timer_enable(&timer0);
    while (true)
    {
       //计数器加1
		nrfx_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

}

 nRF52833-peripheral_第37张图片

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;	//32bit
	 
	 NRF_TIMER0->CC[1] = 5000;	//cc[1]的值等于5ms,这里相当于方波的周期为5ms
	 NRF_TIMER0->CC[0] = 100;	//cc[0]为占空比值
	 
	 NRF_TIMER0->CC[2] = 5000;	//cc[2]的值等于5ms,这里相当于方波的周期为5ms
	 NRF_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 与1
 void 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    1111
	NRF_PPI->CHENSET = 0xf;
}
 
int main(void)
{
	timer0_init();
	gpoite_init();
	ppi_set();

    /* Toggle LEDs. */
    while (true)
    {

    }
}

现象 LED1与LED2的亮度不一样

nRF52833-peripheral_第38张图片

 ② 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;
	//初始和使能PWM
	err_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 时序图

nRF52833-peripheral_第39张图片

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

nRF52833-peripheral_第40张图片

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

nRF52833-peripheral_第41张图片

.mck_pin      = NRFX_I2S_CONFIG_MCK_PIN,                      \

// 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口输出)并观察

nRF52833-peripheral_第42张图片

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

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

 

nRF52833-peripheral_第43张图片

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

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

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

nRF52833-peripheral_第44张图片

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

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

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

nRF52833-peripheral_第45张图片

\配置为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,  

nRF52833-peripheral_第46张图片

4.6.3 官方例程分析

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

加了分析注释的源代码


#include 
#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],
        };
		//指定下一个buff
        APP_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);
    }
}

/** @} */

 输出结果

nRF52833-peripheral_第47张图片

 分析

nRF52833-peripheral_第48张图片

 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 transmission
  NRF_I2S->CONFIG.TXEN = (I2S_CONFIG_TXEN_TXEN_ENABLE << I2S_CONFIG_TXEN_TXEN_Pos);
  
  // Enable MCK generator
  NRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos);
  
  // MCKFREQ = 4 MHz
  NRF_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 aligned
  NRF_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 = I2S
  NRF_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 pins
  NRF_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 pointer
  NRF_I2S->TXD.PTR = (uint32_t)&square_table[0];
  NRF_I2S->RXTXD.MAXCNT = sizeof(square_table) / sizeof(uint32_t);
  
  // Start transmitting I2S data
  NRF_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类功放输出的是包含音频信息的调制方波,而非直接的音频波形数据所以示波器无法直接看输出的波形。详见

nRF52833-peripheral_第49张图片

  • 所以我是通过音频采集器用AU来采集nRF52833-peripheral_第50张图片
  • 方波nRF52833-peripheral_第51张图片
  • 三角波nRF52833-peripheral_第52张图片
  • 正弦波 nRF52833-peripheral_第53张图片

相关文章

可生成采样率对照表

生成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 1

    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_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 
}

 原始音频波形

nRF52833-peripheral_第54张图片

 生成的

nRF52833-peripheral_第55张图片

4.7 SAADC
4.7.1 框图

nRF52833-peripheral_第56张图片

4.7.2 主要配置项 

①:采样模式

②:信号增益

③:参考电压

④:采样精度

⑤:工作模式

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

nRF52832学习记录

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

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

4.8 读取HT11温度
4.8.1 DHT11单总线时序

nRF52833-peripheral_第57张图片

 nRF52833-peripheral_第58张图片

 nRF52833-peripheral_第59张图片

4.8.2 DHT11驱动代码
#include "dht11.h"
#include "nrf_delay.h"

/*
*开始信号
*开始信号输出后从机会回应一个应答信号
*/
void dht_Rst(void)	   
{                 
	dht_IO_OUT(); 		//IO口配置为输出
    dht_data_OUT_0; 	//输出0	
    nrf_delay_ms(25);	//输出25ms   
    dht_data_OUT_1; 	//输出1	
	nrf_delay_us(30);	//输出30us
}
/*
*检测应答信号
*正确应答返回0错误应答返回1
*/
uint8_t dht_Check(void) 	   
{   
	uint8_t time=0;
	dht_IO_IN();		//输入模式	 
  while (dht_data_IN&&time<100)	//输入电平为低且时机小于100us为应答信号
	{
		time++;
		nrf_delay_us(1);
	} 
	if(time>=100)				//时间大于100us应答错误	
	{
		printf("error\r\n");
		return 1;
	}
	else time=0;
 while (!dht_data_IN&&time<100)//输入电平为高且时机小于100us为数据开始传输
	{
		time++;
		nrf_delay_us(1);
 
	}
	if(time>=100)
		{
		printf("error  2\r\n");
		return 1;
	}
	return 0;					//数据开始传输
}
/*
*读取一个bit的数据
*/
uint8_t dht_Read_Bit(void) 			 
{
 	uint8_t time=0;
	while(dht_data_IN&&time<100)	//数据输的开始位为低电平(50us)
	{
		time++;
		nrf_delay_us(1);
	}
	time=0;
	while(!dht_data_IN&&time<100)	//开始读取高电平时间,判断数据为1还是0
	{
		time++;
		nrf_delay_us(1);
	}
	nrf_delay_us(40);				//延迟70毫秒后若还为高电平则为1否则为0
	if(dht_data_IN)return 1;
	else return 0;		   
}
/*
*读取一个字节的数据
*/
uint8_t dht_Read_Byte(void)    
{        
    uint8_t i,dat;
    dat=0;
	for (i=0;i<8;i++) 
	{
   		dat<<=1; 
	    dat|=dht_Read_Bit();
    }						    
    return dat;
}
uint8_t dht_Read_Data(uint8_t *temp,uint8_t *humi)    
{        
 	uint8_t dat[5];
	uint8_t i;
	dht_Rst();
	if(dht_Check()==0)
	{
		for(i=0;i<5;i++)
		{
			dat[i]=dht_Read_Byte();
		}
		if((dat[0]+dat[1]+dat[2]+dat[3])==dat[4]) //校验
		{
			*humi=dat[0];			//温度整数位
			*temp=dat[2];			//湿度整数位
		}
	}else return 1;
	return 0;	    
}

uint8_t dht_Init(void)
{
	dht_Rst(); //开始信号
	return dht_Check(); //检测是否有响应
}
 
4.8.3 主程序
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
#else
          NRF_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_TEST
	
	int32_t timval;
    timer0_init();
	timer1_init();
	timer2_init();
	ppi_init();
	//启动定时器0 
	nrf_drv_timer_enable(&timer0);
	 
	 while(dht_Init())
	 {
		nrf_delay_ms(1000);
	 }
	 printf("succed\r\n");
	 uint8_t temp;
  	 uint8_t humi;
	 
	
    while (true)
    {
//       //计数器加1
//		nrfx_timer_increment(&timer0);
//		//获取计数值
//		timval = nrfx_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL0);
//		printf("count value:%d\r\n",timval);
		bsp_board_led_invert(1);
		dht_Read_Data(&temp,&humi);
		printf("t:%d  h:%d\r\n",temp,humi);
		nrf_delay_ms(1048);
    }
#else

    // This part of the example is just for testing the loopback .
    while (true)
    {
        uart_loopback_test();
    }
#endif

}

你可能感兴趣的:(嵌入式硬件,单片机)