最近有个项目,需要通过NRF52832的IO实时输出不同频率的方波来驱动蜂鸣器,但是因为NRF52832自带的硬件PWM初始化时就决定了频率,动态可调的只有占空比,如果需要调整频率需要重新初始化PWM,故选择了通过硬件Timer通过PPI与GPIO连接来输出方波的方式达成这个目地.
首先,介绍下NRF52的PPI:
1.概念
PPI是可编程外设互连(Programmable Peripheral Interconnect)的缩写
2.作用
提供一个硬件通道,将不同外设的事件和任务"连接"在一起,当事件产生时硬件自动触发事件"连接"的任务
3.优点
PPI机制的设计,使得被"连接"的任务由硬件自动触发完成,省去了原本CPU需要参与的步骤。这一方面降低了CPU的负荷,另一方面保证了产生事件到执行任务的实时性。
4.实现原理
1)nRF52832共有32个通道(编号为0~31),其中,有12个通道(通道20~31)已经被预编程,剩余20个通道(通道0~19)是用户可编程的;
2)每个PPI通道由3个端点寄存器组成,1个EEP(Event End-Point:事件端点)和2个TEP(Task End-Point:任务端点,次级任务端点)寄存器;
3)任务端点和次级任务端点同时被触发;
4)使用PPI连接外设事件和外设任务的时候,将外设事件寄存器的地址写入到PPI通道的EEP,将外设任务寄存器的地址写入到PPI通道的TEP,最后使能该PPI通道实现事件和任务间的连接;
5.PPI组
nRF52832共用6个PPI组CHG[0]~CHG[5],PPI通道可以加入PPI组中集中管理,实现同时使能或禁用PPI通道的功能。
注意:当一个PPI通道同时属于两个PPI组时,如果使能一个PPI组禁用另一个PPI组,则使能作用优先执行。
这个功能需要include的几个头文件:
- nrf_drv_timer.h
- nrf_drv_ppi.h
- nrf_drv_gpiote.h
相关的初始化代码:
const nrf_drv_timer_t TIMER_Audio = NRF_DRV_TIMER_INSTANCE(3);
nrf_ppi_channel_t PpiChannel;
uint32_t CompareEvtAddr;
uint32_t GpioteTaskAddr;
ret_code_t ErrCode = NRF_SUCCESS;
// 配置Timer
nrf_drv_timer_config_t TimerCfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
TimerCfg.frequency = NRF_TIMER_FREQ_16MHz;
TimerCfg.mode = NRF_TIMER_MODE_TIMER;
TimerCfg.bit_width = NRF_TIMER_BIT_WIDTH_16;
TimerCfg.interrupt_priority = APP_IRQ_PRIORITY_LOWEST;
// 初始化Timer
ErrCode = nrf_drv_timer_init(&TIMER_Audio, &TimerCfg, timer_audio_event_handler);
APP_ERROR_CHECK(ErrCode);
// 将IO配置成输出任务为翻转IO,默认低电平
nrf_drv_gpiote_out_config_t gpiote_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(false);
ErrCode = nrf_drv_gpiote_out_init(SINGAL_DIN_PIN, &gpiote_config);
APP_ERROR_CHECK(ErrCode);
// 申请一个PPI通道
ErrCode = nrf_drv_ppi_channel_alloc(&PpiChannel);
APP_ERROR_CHECK(ErrCode);
// 获取Timer的事件地址
CompareEvtAddr = nrf_drv_timer_event_address_get(&TIMER_Audio, NRF_TIMER_EVENT_COMPARE0);
// 获取刚刚设置的那个PIN的输出任务地址
GpioteTaskAddr = nrf_drv_gpiote_out_task_addr_get(SINGAL_DIN_PIN);
// 通过申请的PPI通道连接刚刚的事件地址与任务地址
ErrCode = nrf_drv_ppi_channel_assign(PpiChannel, CompareEvtAddr, GpioteTaskAddr);
APP_ERROR_CHECK(ErrCode);
// 使能PPI通道
ErrCode = nrf_drv_ppi_channel_enable(PpiChannel);
APP_ERROR_CHECK(ErrCode);
// 使能GPIOTE的输出任务
nrf_drv_gpiote_out_task_enable(SINGAL_DIN_PIN);
在播放音频过程中如需要修改频率,则可以调用以下函数:
void audio_tone(uint16_t usFreq)
{
if (usFreq)
{
// 设置Timer的比较值,设置不使能中断
nrf_drv_timer_extended_compare(&TIMER_Audio, NRF_TIMER_CC_CHANNEL0, nrf_drv_timer_us_to_ticks(&TIMER_Audio, (1000 * 1000 / usFreq / 2)),NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
// 使能定时器
if (nrf_drv_timer_is_enabled(&TIMER_Audio) == false)
{
nrf_drv_timer_enable(&TIMER_Audio);
}
}
else
{
// 停止PWM
nrf_drv_timer_disable(&TIMER_Audio);
}
}
Comments | NOTHING