nRF52832——定时器和 PPI 的联合应用
PPI 之定时器计数
PPI 定时器计数寄存器编程
PPI 不仅仅适用于 GPIOTE 的事件触发,还可以用于其他事件来触发任务。其中定时器的应用较为广泛。这里演示几种定时器和 PPI 的综合应用。
代码如下:
static void ppi_init(void)
{
// 配置PPIT通道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);
// 配置PPIT通道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);
}
配置定时器 0 ,将定时器 0 作为开启和关闭的任务:
void timer0_init(void)
{
NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // 设置定时器0为计数器模式
NRF_TIMER0->PRESCALER = 9;//设置定时器0的分频
NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_16Bit; // 设置定时器0的位宽
}
配置定时器1 和定时器 2。
/**
初始化定时器1
*/
static void timer1_init(void)
{
// 配置定时器1每2秒触发一次
// SysClk = 16 Mhz
// BITMODE = 16 bit
// PRESCALER = 9
// 触发时间=0xFFFF/(SysClk/2^PRESCALER)= 65535/31250 = 2.097 sec
//位宽
NRF_TIMER1->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);
//定时器分频值
NRF_TIMER1->PRESCALER = 9;
//手动清零
NRF_TIMER1->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos);
// 触发比较中断事件
NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;
NRF_TIMER1->CC[0] = 0xFFFFUL; // 设置比较寄存器
}
/** 初始化定时器2
*/
static void timer2_init(void)
{
// 设置定时器2每一秒触发一次.
// SysClk = 16Mhz
// BITMODE = 16 bit
// PRESCALER = 9
// 触发时间=0x7FFF/(SysClk/2^PRESCALER)= 32767/31250 = 1.048 sec */
NRF_TIMER2->BITMODE = (TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos);
NRF_TIMER2->PRESCALER = 9;
NRF_TIMER2->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos);
// 触发比较中断事件
NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;
NRF_TIMER2->CC[0] = 0x7FFFUL; // 设置比较寄存器
}
主函数的功能就是启动定时器 1 和定时器 2 开始运行。定时器 1 每 2 秒会触发一次比较事件,触发关闭定时器 0 任务,定时器 2 每 1 秒会触发一次比较事件,触发启动定时器 0 任务。当同时出 现启动和关闭定时器操作的时候,以关闭定时器为准。因此定时器 0,会出现 1,3,5…秒启动计数, 2,4,6…秒关闭计数的状态。那么定时器 0 计数的时候会出现每隔 1s 停止计数 1 次的现象,计数值会 保持 1s 不变化。具体代码如下所示:
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code;
uint32_t timVal = 0;
timer0_init(); //定时器0的初始化
timer1_init(); //定时器1初始化
timer2_init(); //定时器2初始化
ppi_init(); //PPI的配置
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);
//启动定时器
NRF_TIMER1->TASKS_START = 1;
NRF_TIMER2->TASKS_START = 1;
while (1)
{
printf(" 2019.5.1 hello world!\r\n");
/* 计数器加1 */
NRF_TIMER0->TASKS_COUNT = 1;
NRF_TIMER0->TASKS_CAPTURE[0] = 1;
//获取计数值
timVal = NRF_TIMER0->CC[0];
//串口打印计数值
printf("conut value: %d\r\n", timVal);
nrf_delay_ms(1048);
}
}
把该例子程序编译后下载到 nrf52832 开发板内。连接开发板串口,打开串口调试助手,设置波特率为 115200,数据位为 8,停止位为 1,如下图所示。这时候串口调试助手输出的计数器的计数值会保持 1s 不变:
PPI 定时器计数库函数方式
组件库的应用涉及到了定时器、 PPI 和 UARTE 的库函数 API 调用。库函数调用方式和配置,和前面定时器、PPI 和 UARTE 的章节相同。具体的搭建工程如下图:
然后添加工程路径,这里就不再讲述,可以参考前面几章的内容。同时需要在 sdk_config.h 配置文件的 configuration wizard 配置导航卡中看见如下图:
整体代码如下:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "nrf_drv_timer.h"
#include "nrf_drv_ppi.h"
//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */
#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
nrf_ppi_channel_t my_ppi_channel=NRF_PPI_CHANNEL0 ;
nrf_ppi_channel_t my_ppi_channel2=NRF_PPI_CHANNEL1;
//定义Timer0的驱动程序实例。驱动程序实例的ID对应Timer的ID,如NRF_DRV_TIMER_INSTANCE(0)对应Timer0
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);
//Timer事件回调函数
void my_timer_event_handler(nrf_timer_event_t event_type, void* p_context)
{
}
void uart_error_handle(app_uart_evt_t * p_event)
{
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_communication);
}
else if (p_event->evt_type == APP_UART_FIFO_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_code);
}
}
#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLED
void timer0_init(void)
{
// NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // Set the timer in counter Mode.
// NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_24Bit; // 24-bit mode.
uint32_t err_code = NRF_SUCCESS;
//定义定时器配置结构体,并使用默认配置参数初始化结构体
nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
//Timer0配置为计数模式
timer_cfg.mode = NRF_TIMER_MODE_COUNTER;
//初始化定时器,定时器工作于计数模式时,没有事件,所以无需回调函数
err_code = nrfx_timer_init(&timer0, &timer_cfg, my_timer_event_handler);
//err_code = nrfx_timer_init(&TIMER_COUNTER, &timer_cfg, NULL);
APP_ERROR_CHECK(err_code);
}
/** @brief 初始化PPI外设.
*/
static void ppi_init(void)
{
uint32_t err_code = NRF_SUCCESS;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
APP_ERROR_CHECK(err_code);
// Configure 2nd available PPI channel to start timer0 counter at TIMER2 COMPARE[0] match, which is every odd number of seconds.
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);
}
static void timer1_init(void)
{
uint32_t time_ticks1;
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);
}
static 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);
}
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code;
uint32_t timVal = 0;
//
timer0_init();
timer1_init();
timer2_init();
ppi_init();
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);
// NRF_TIMER0->TASKS_START=1;
//启动定时器
// nrfx_timer_enable(&timer0);
nrf_drv_timer_enable(&timer0);
while (1)
{
printf(" 2019.5.1 青风!\r\n");
// /* 计数器加1 */
// NRF_TIMER0->TASKS_COUNT = 1;
// NRF_TIMER0->TASKS_CAPTURE[0] = 1;
// //获取计数值
// timVal = NRF_TIMER0->CC[0];
// //定时器计数值加1
nrfx_timer_increment(&timer0);
//获取计数值
timVal = nrfx_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL0);
//串口打印计数值
printf("conut value: %d\r\n", timVal);
nrf_delay_ms(1048);
}
}
把该例子程序编译后下载到青风 nrf52832 开发板内。连接开发板串口,打开串口调试助手,设置波特率为 115200,数据位为 8,停止位为 1,如下图所示。这时候串口调试助手输出的计数器的计数值会保持 1s 不变:
定时器与 PPI 软件 PWM
软件 PWM 寄存器方式
工程结构如下:
根据前面讲解的原理,PPI 的结构其实非常简单的。PPI 实际上提供了一种直连的机制,这种机制可以把一个外设发生的事件(event)来触发另一个外设的任务(task),整个过程不需要 CPU 进行参与。 因此一个任务(task)通过 PPI 通道和事件(event)进行互连。PPI 通道由两个终点寄存器组成,分别为:事件终点寄存器 (EEP) 和任务终点寄存器 (TEP)。可以把外设任务(task)通过任务寄存器的地址与任务终点寄存器(TEP)进行赋值。同理,也可以把外设事件通过事件(event)寄存器的地址与事件终点寄存器(EEP) 进行赋值。 PPI 输出软件 PWM 实际上是由定时器来触发的 GPIOTE 的输出电平变化实现的。那么 PPI 的作用就是连接定时器作为任务(task),触发的 GPIOTE 的输出作为事件(event)。例子中,我们编写两路 PWM 输出,所以需要使用 4 路 PPI 通道。每两路 PPI 通道产生一个 PWM 输出,其中一路作为占空比的输出控制,一路作为 PWM 周期的控制。整个过程 CPU 不参与其中。
设置 PPI 通道:
void ppi_set(void){
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 = 0x0f;
}
配置定时器,通过定时器作为事件 EEP,定时器触发 COMPARE[n],如下:
void timer0_init(void)
{
NRF_TIMER0->PRESCALER = 4; //2^4 16分频成1M时钟源
NRF_TIMER0->MODE = 0; //timer模式
NRF_TIMER0->BITMODE = 3; //32bit
NRF_TIMER0->CC[1] = 5000; //cc[1]的值等于是1s,这里相当于方波的周期为1s
NRF_TIMER0->CC[0] =100; //调节占空比,这里设置为0.5
NRF_TIMER0->CC[2] =5000; //cc[1]的值等于是1s,这里相当于方波的周期为1s
NRF_TIMER0->CC[3] =4900; //调节占空比,这里设置为0.5
NRF_TIMER0->SHORTS = 1<<1; //设置到计数到cc1中的值时 自动清0 重新开始计数
NRF_TIMER0->TASKS_START = 1; //启动timer
}
配置 GPIOTE,使用 GPIOTE 通道控制 LED 灯。
//电平不停翻转,长度可变,可以配置周期和占空比,1个输出PWM端口需要两个定时器控制
void gpiote_init(void){
NRF_GPIOTE->CONFIG[0] = ( 3 << 0 ) //作为task模式
| ( PWM_OUT1 << 8) //设置PWM输出引脚
| ( 3 << 16 ) //设置task为翻转PWM引脚的电平
| ( 1 << 20); //初始输出电平为高
NRF_GPIOTE->CONFIG[1] = ( 3 << 0 ) //作为task模式
| ( PWM_OUT2 << 8) //设置PWM输出引脚
| ( 3 << 16 ) //设置task为翻转PWM引脚的电平
| ( 1 << 20); //初始输出电平为高
}
整体代码如下:
#include "nrf52.h"
#include "stdio.h"
#include "led.h"
#include "nrf_gpio.h"
#define PWM_OUT1 LED_0//高电平时间越短,灯的亮度越亮
#define PWM_OUT2 LED_1
void timer0_init(void)
{
NRF_TIMER0->PRESCALER = 4; //2^4 16分频成1M时钟源
NRF_TIMER0->MODE = 0; //timer模式
NRF_TIMER0->BITMODE = 3; //32bit
NRF_TIMER0->CC[1] = 5000; //cc[1]的值等于是1s,这里相当于方波的周期为1s
NRF_TIMER0->CC[0] =100; //调节占空比,这里设置为0.5
NRF_TIMER0->CC[2] =5000; //cc[1]的值等于是1s,这里相当于方波的周期为1s
NRF_TIMER0->CC[3] =4900; //调节占空比,这里设置为0.5
NRF_TIMER0->SHORTS = 1<<1; //设置到计数到cc1中的值时 自动清0 重新开始计数
NRF_TIMER0->TASKS_START = 1; //启动timer
}
//电平不停翻转,长度可变,可以配置周期和占空比,1个输出PWM端口需要两个定时器控制
void gpiote_init(void){
NRF_GPIOTE->CONFIG[0] = ( 3 << 0 ) //作为task模式
| ( PWM_OUT1 << 8) //设置PWM输出引脚
| ( 3 << 16 ) //设置task为翻转PWM引脚的电平
| ( 1 << 20); //初始输出电平为高
NRF_GPIOTE->CONFIG[1] = ( 3 << 0 ) //作为task模式
| ( PWM_OUT2 << 8) //设置PWM输出引脚
| ( 3 << 16 ) //设置task为翻转PWM引脚的电平
| ( 1 << 20); //初始输出电平为高
}
void ppi_set(void){
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 = 0x0f;
}
int main(void){
gpiote_init();
ppi_set();
timer0_init();
while(1);
}
把该例子程序编译后下载到 nrf52832 开发板内。PWM 波就会通过 IO 管脚输出到 LED1 灯 和 LED2 灯上,通过两个 PWM 波输出对比,不同占空比的 LED1 和 LED2 灯亮度不同。
用示波器测试 PWM 的波形如下图:
软件 PWM 库函数方式
工程结构如下:
工程中需要添加的函数文件如下:
文件添加到工程后,需要添加对应文件的路径。在 Options for Target 选项卡的 C/C++中,点击 include paths 路径选项中添加硬件驱动库的文件路径,如下图
工程搭建完毕后,首先我们需要来修改 sdk_config.h 配置文件,库函数的使用是需要对库功能进行使能的,因此需要在 sdk_config.h 配置文件中,设置对应模块的使能选项。关于定时器的配代码选项较多,我们不就一一展开,大家可以直接把对应的配置代码复制到自己建立的工程中的 sdk_config.h 文件里。如果复制代码后,在 sdk_config.h 配置文件的 configuration wizard 配置导航卡中看见如下图几个参数选项被勾选,表明配置修改成功
库函数 API 介绍
app_pwm_init()
:PWM 初始化,声明回调函数
app_pwm_channel_duty_set()
用于设置软件 PWM 占空比的值。
这两个函数是实现 PWM 输出的关键了,首先来看下如何配置 app_pwm_init 函数,该函数主要需要配置第二个参数:app_pwm_config_t const * const p_config,这个参数是一个结构体形式,官方给出了单 PWM 输出和双 PWM 输出的定义:
/**@brief PWM instance structure. */
typedef struct
{
app_pwm_cb_t *p_cb; //!< Pointer to control block internals.
nrf_drv_timer_t const * const p_timer; //!< Timer used by this PWM instance.
} app_pwm_t;
/**@brief PWM configuration structure used for initialization. */
typedef struct
{
uint32_t pins[APP_PWM_CHANNELS_PER_INSTANCE]; //!< Pins configured as PWM output.
app_pwm_polarity_t pin_polarity[APP_PWM_CHANNELS_PER_INSTANCE]; //!< Polarity of active state on pin.
uint32_t num_of_channels; //!< Number of channels that can be used.
uint32_t period_us; //!< PWM signal output period to configure (in microseconds).
} app_pwm_config_t;
/**@brief PWM instance default configuration (1 channel). */
#define APP_PWM_DEFAULT_CONFIG_1CH(period_in_us, pin) \
{ \
.pins = {pin, APP_PWM_NOPIN}, \
.pin_polarity = {APP_PWM_POLARITY_ACTIVE_LOW, APP_PWM_POLARITY_ACTIVE_LOW}, \
.num_of_channels = 1, \
.period_us = period_in_us \
}
/**@brief PWM instance default configuration (2 channels). */
#define APP_PWM_DEFAULT_CONFIG_2CH(period_in_us, pin0, pin1) \
{ \
.pins = {pin0, pin1}, \
.pin_polarity = {APP_PWM_POLARITY_ACTIVE_LOW, APP_PWM_POLARITY_ACTIVE_LOW}, \
.num_of_channels = 2, \
.period_us = period_in_us \
}
整体代码如下:
#include <stdbool.h>
#include <stdint.h>
#include "nrf.h"
#include "app_error.h"
#include "bsp.h"
#include "nrf_delay.h"
#include "app_pwm.h"
APP_PWM_INSTANCE(PWM1,1); // 创建一个使用定时器1产生PWM波的实例
static volatile bool ready_flag; // 使用一个标志位表示PWM状态
void pwm_ready_callback(uint32_t pwm_id) // PWM回调功能
{
ready_flag = true;
}
int main(void)
{
ret_code_t err_code;
/* 2-个频道的 PWM, 200Hz(5000us=5ms), 通过 开发板LED 管脚输出. */
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);//使能PWM
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);
}
}
}
由于在代码中设置的管脚极性相反,输出 PWM 占空比相反,所以观察两个 LED 灯的亮度变化正好相反。一个不停的变亮,到达最亮后开始反方向变暗,一个不停变暗,到达熄灭后开始反方向不停变量。用示波器测试 PWM 的波形如下图
PPI 之输入捕获
原理分析
本实验通过 PPI 来实现定时器的输入捕获功能。本例的 PPI 的功能就是通过一端的 GPIOTE 的输入脉冲信号作为事件 evet,触发 PPI 另一端的定时器捕获功能作为任务 task,并且把定时器捕获到的脉冲计数值通过串口输出,过程如下图。 而输入脉冲信号则通过上一节的软件 PWM 来提供。因此综合了 GPIOTE、定时器、软件 PWM 的多项功能的综合性应用。
那么工程文件中需要调用几个关键的组件库:PPI 的组件库、定时器的组件库、GPIOTE 的组件库、串口的组件库。然后更加这几个组件库来编写主函数 main。那么工程目录树添加入下图。
应用实例编写
这里的实例程序可以如下步骤实现:
- 首先需要设置一个脉冲输入,使用 PWM 方式输出信号,为了方便串口输出观察,我们把 PWM 的频率定为 5HZ。PWM 波的设置在上一节已经讲过,这里不再累述,具体代码如下,我们这里只 需要一路 PWM 输出:
//设置一个脉冲输入,才有PWM方式输入
void PWM_OUT(uint32_t value)
{
ret_code_t err_code;
/* 2-个频道的 PWM, 200Hz(5000us=5ms), 通过 开发板LED 管脚输出. */
app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_2CH(2000000L, OUTPUT, 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);//使能PWM
while (app_pwm_channel_duty_set(&PWM1, 0, value) == NRF_ERROR_BUSY);
}
- 设置好的 PWM 脉冲信号需要被采样,采样管脚可以使用 gpiote 输入作为事件来触发 PPI, 因此这一步来设置 gpiote 输入,具体代码如下:
//设置管脚为gpiote输入 作为事件
static void gpio_init(void)
{
ret_code_t err_code;
//GPIOE驱动初始化
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
//配置设置GPIOE输入参数
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(1);//采集就是占空比变化的次数
in_config.pull = NRF_GPIO_PIN_PULLUP;
//GPIOE输入初始化,设置触发输入中断
err_code = nrf_drv_gpiote_in_init(INPUT, &in_config, NULL);
APP_ERROR_CHECK(err_code);
//设置GPIOE输入事件使能
nrf_drv_gpiote_in_event_enable(INPUT, 1);
}
3.然后配置 PPI 外设。设置 PPI 的输入捕获,以 GPIOTE 输入作为 event 事件,以定时器计数器计数作为 task 任务。捕获到的脉冲个数通过计数器计数,具体代码如下:
/** @brief 初始化PPI外设.
*/
static void ppi_init(void)
{
uint32_t err_code = NRF_SUCCESS;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
APP_ERROR_CHECK(err_code);
// Configure 2nd available PPI channel to start timer0 counter at TIMER2 COMPARE[0] match, which is every odd number of seconds.
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_gpiote_in_event_addr_get(INPUT),
nrf_drv_timer_task_address_get(&timer0, NRF_TIMER_TASK_COUNT));
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_enable(my_ppi_channel);
APP_ERROR_CHECK(err_code);
}
- PPI 设置好后,做为 TASK 的定时器也需要进行配置,对使用的定时器 0 进行初始化,配置为计数模式。关于定时器的初始化前面章节有具体介绍,这里就不累述,具体代码如下:
void timer0_init(void)
{
// NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // Set the timer in counter Mode.
// NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_24Bit; // 24-bit mode.
uint32_t err_code = NRF_SUCCESS;
//定义定时器配置结构体,并使用默认配置参数初始化结构体
nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
//Timer0配置为计数模式
timer_cfg.mode = NRF_TIMER_MODE_COUNTER;
//初始化定时器,定时器工作于计数模式时,没有事件,所以无需回调函数
err_code = nrfx_timer_init(&timer0, &timer_cfg, my_timer_event_handler);
//err_code = nrfx_timer_init(&TIMER_COUNTER, &timer_cfg, NULL);
APP_ERROR_CHECK(err_code);
}
- 主函数调用之前初始化函数,同时需要使用串口输出计数值,因此还需要对串口进行配置;当 GPIOTE 输入事件通过 PPI 触发了定时器计数任务时,定时器开始计数;主函数中调用定时器比较器捕获函数来捕获计数值:
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code;
timer0_init(); // Timer used to blink the LEDs.
gpio_init();
ppi_init(); // PPI to redirect the event to timer start/stop tasks.
PWM_OUT(10);
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);
NRF_POWER->TASKS_CONSTLAT = 1;
//启动定时器
nrf_drv_timer_enable(&timer0);
while (1)
{
printf(" 2019.5.1 青风!\r\n");
printf("Current cout: %d", (int)nrf_drv_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL1));
nrf_delay_ms(1000);
}
}
编译程序后下载到 nrf52832 开发板内。同时把 IO 口 P0.02 和 P0.03 用杜邦线短接。打开串口调试助手,选择开发板串口端号,设置波特率为 115200,数据位为 8,停止位为 1。由于 while 循环中设置了 1s 的延迟,而脉冲信号为 2s 一个周期,一个下降沿通过 PPI 触发一次计数。所以捕获值要用串口输出两次才发生一个变化。如下图所示: