目录
② 不需要CPU参与,PPI的方式即把组的开启关闭当做是一个PPI的任务,通过PPI事件触发
一、开发环境的搭建安装
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 – nRF52820Softdevice命名规则一。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
二、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博客
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
第一种赋值方式
第二种赋值方式(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,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
}
效果实现变周期
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绑定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个都可以?)
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
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;
//初始化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参与的方式
上述是吧按键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事件触发
③ 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]);//配置输出从任务
//使能通道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
}
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的亮度不一样
② 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 时序图
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
#endifSCK = 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
#endifMCK,主时钟,也叫作系统时钟,是采样频率的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],
};
//指定下一个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);
}
}
/** @} */
输出结果
分析
nRF5 SDK v15.3.0: I2S Loopback Example
nRF52832 — 使用nRF52832的I2S播放音频_52832 蓝牙语音_文化人Sugar的博客-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类功放输出的是包含音频信息的调制方波,而非直接的音频波形数据所以示波器无法直接看输出的波形。详见
- 所以我是通过音频采集器用AU来采集
- 方波
- 三角波
- 正弦波
相关文章
原始数据不能用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
}
原始音频波形
生成的
4.7 SAADC
4.7.1 框图
4.7.2 主要配置项
①:采样模式
②:信号增益
③:参考电压
④:采样精度
⑤:工作模式
结合PPI,双Buff,EDMA来运行;
实战经验,Nordic 52832 低功耗模式与唤醒机制 (360doc.com)
STM32Cube MX USB双设备MSC+CDC 实现虚拟U盘+虚拟串口_stm32 usb虚拟串口和u盘_Pzkkkkk的博客-CSDN博客
4.8 读取HT11温度
4.8.1 DHT11单总线时序
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
}