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 介绍

  1. app_pwm_init():PWM 初始化,声明回调函数

在这里插入图片描述

  1. 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。那么工程目录树添加入下图。
在这里插入图片描述

应用实例编写

这里的实例程序可以如下步骤实现:

  1. 首先需要设置一个脉冲输入,使用 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);  

}
  1. 设置好的 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);
	
}

  1. 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);
}
  1. 主函数调用之前初始化函数,同时需要使用串口输出计数值,因此还需要对串口进行配置;当 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 触发一次计数。所以捕获值要用串口输出两次才发生一个变化。如下图所示:

在这里插入图片描述

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值