nRF52832学习记录(六、PPI 与定时器综合应用 PWM 输入捕获 )

本文详细介绍了如何在nRF52832微控制器上利用定时器、GPIOTE任务模式和PPI通道实现软件PWM,以及如何通过PPI引用输入捕获事件来计数PWM周期。通过示例代码展示了如何配置定时器、GPIOTE和PPI,以及如何动态调整PWM的占空比。同时,还展示了如何利用PPI通道来触发定时器的计数任务,从而实现对输入电平变化的捕获。
摘要由CSDN通过智能技术生成

一、定时器实现软件PWM

实现一个软件PWM,实际上是通过控制一个IO口的输出电平变化实现的,在nRF52832中,我们使用GPIOTE 的 任务模式 TASK_OUT,设置为翻转来实现的,一个周期需要2次翻转,第一次翻转是确定PWM的占空比,第二次翻转是确定PWM的周期:
在这里插入图片描述
利用PPI和定时器来实现这个PWM,就需要2路PPI来实现一个PWM输出,一路控制占空比,一路控制周期,定时器的话因为有6个CC寄存器,所以只需要使用一个即可,但是这样实现的PWM占空比是固定的,就是定时器CC寄存器的值固定了,占空比和周期就固定了,程序中是否可以随时更改,等下测试下,下面给出程序示例:

软件PWM实现(寄存器版本):

...
/*定时器初始化*/
void timer0_init(void)
	{
	/*
	2^4   SysClk/2^PRESCALER  16分频成1M时钟源
	1000 000HZ
	计1S需要1000 000次,计1ms需要1000次	
	*/
	NRF_TIMER0->PRESCALER  = 4;     //
	NRF_TIMER0->MODE = TIMER_MODE_MODE_Timer;    // 0 :timer模式
	NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_32Bit;    //3 : 32bit
    
    NRF_TIMER0->CC[0] =200;      //第一次翻转时间
	NRF_TIMER0->CC[1] = 5000;    //cc[1]这个作为周期
	
   
	NRF_TIMER0->CC[2] =5000;    //定时短的先翻转,定时长的后翻转,所以不用刻意定下前后顺序
	NRF_TIMER0->CC[3] =4000;    //
	/*
	// /* Bit 1 : Shortcut between CC[1] event and the CLEAR task. */
	// #define TIMER_SHORTS_COMPARE1_CLEAR_Pos (1UL) /*!< Position of COMPARE1_CLEAR field. */
	// #define TIMER_SHORTS_COMPARE1_CLEAR_Msk (0x1UL << TIMER_SHORTS_COMPARE1_CLEAR_Pos) /*!< Bit mask of COMPARE1_CLEAR field. */
	// #define TIMER_SHORTS_COMPARE1_CLEAR_Disabled (0UL) /*!< Shortcut disabled. */
	// #define TIMER_SHORTS_COMPARE1_CLEAR_Enabled (1UL) /*!< Shortcut enabled. */
	
	// /* Bit 0 : Shortcut between CC[0] event and the CLEAR task. */
	// #define TIMER_SHORTS_COMPARE0_CLEAR_Pos (0UL) /*!< Position of COMPARE0_CLEAR field. */
	// #define TIMER_SHORTS_COMPARE0_CLEAR_Msk (0x1UL << TIMER_SHORTS_COMPARE0_CLEAR_Pos) /*!< Bit mask of COMPARE0_CLEAR field. */
	// #define TIMER_SHORTS_COMPARE0_CLEAR_Disabled (0UL) /*!< Shortcut disabled. */
	// #define TIMER_SHORTS_COMPARE0_CLEAR_Enabled (1UL) /*!< Shortcut enabled. */
	*/
	NRF_TIMER0->SHORTS  = (TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);
    //NRF_TIMER0->SHORTS = 1<<1;      //这里清除到cc寄存器中最大的那个就可以,因为要保证一个完整的周期计数,\
					   				 所以短时间的cc寄存器触发了,也得继续计时等到计时到最大时间的cc寄存器值,\
					   				 才可以完成一个完整周期,再清0计数   
    NRF_TIMER0->TASKS_START = 1;    //启动timer
}
//这里是2路PWM,我们定时器用了4个定时器时间,2个定时时间实现一路PWM
void gpiote_init(void){
    NRF_GPIOTE->CONFIG[0] = ( GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos )        //作为task模式
                         | ( LED_0 << GPIOTE_CONFIG_PSEL_Pos) //设置PWM输出引脚
                         | ( GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos )     //设置task为翻转PWM引脚的电平
                         | ( GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);     //初始输出电平为高
	NRF_GPIOTE->CONFIG[1] = ( 3 << 0 )        //作为task模式
                         | ( 18 << 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]);
       
    NRF_PPI->CHEN = 0x0f; // 使能通道 0 1 2 3  低4位1111
}

int main(void){
    int i = 0;
    gpiote_init();
    ppi_set();
    timer0_init();
    while(1){
		for(i = 0; i< 50;i++){
			NRF_TIMER0->CC[0] = i*100; //改变占空比,这是一个DEMO
			nrf_delay_ms(25);
		}		
	}		
}

软件PWM实现(库函数版本):

/**@brief Macro for creating a PWM instance. 
#define APP_PWM_INSTANCE(name, num)                                           \
    const nrf_drv_timer_t m_pwm_##name##_timer = NRF_DRV_TIMER_INSTANCE(num); \
    app_pwm_cb_t m_pwm_##name##_cb;                                           \
    lint -e{545}                                                         \
    const app_pwm_t name = {                                                  \
        .p_cb    = &m_pwm_##name##_cb,                                        \
        .p_timer = &m_pwm_##name##_timer,                                     \
    }
*/    
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;  
    /* 
    /**@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                                                \
	    }
	 . 
	  使用默认配置 初始化 软件PWM ,2个通道,5000us,LED1,LED2,极性APP_PWM_POLARITY_ACTIVE_LOW
    */
    app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_2CH(5000L, LED_1, LED_2);
    
    /* 为了看个现象,两个灯状态相反,所以改变了一个PWM输出极性 */
    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(1)
    {
        for (uint8_t i = 0; i < 40; ++i)
        {
            value = (i < 20) ? (i * 5) : (100 - (i - 20) * 5);
            
            ready_flag = false;
            /* 
            need 1:
            ret_code_t app_pwm_channel_duty_set(app_pwm_t const * const p_instance,
                                  uint8_t channel, app_pwm_duty_t duty)
			{
			    uint32_t ticks = ((uint32_t)app_pwm_cycle_ticks_get(p_instance) * (uint32_t)duty) / 100UL;
			    return app_pwm_channel_duty_ticks_set(p_instance, channel, ticks);
			}
			
			need 2:
			ret_code_t app_pwm_channel_duty_ticks_set(app_pwm_t const * const p_instance,
                                          uint8_t           channel,
                                          uint16_t          ticks)
			{
			    app_pwm_cb_t         * p_cb    = p_instance->p_cb;
			    app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel];
			
			    ASSERT(channel < APP_PWM_CHANNELS_PER_INSTANCE);
			    ASSERT(p_ch_cb->initialized == APP_PWM_CHANNEL_INITIALIZED);
			
			    if (p_cb->state != NRFX_DRV_STATE_POWERED_ON)
			    {
			        return NRF_ERROR_INVALID_STATE;
			    }
			    if (ticks == p_ch_cb->pulsewidth)
			    {
			        if (p_cb->p_ready_callback)
			        {
			            p_cb->p_ready_callback(p_instance->p_timer->instance_id);
			        }
			        return NRF_SUCCESS;     // No action required.
			    }
			    if (app_pwm_busy_check(p_instance))
			    {
			        return NRF_ERROR_BUSY;  // PPI channels for synchronization are still in use.
			    }
			
			    m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_CHANGING;
			
			    // Set new value.
			
			    pwm_transition(p_instance, channel, ticks);
			
			    if (p_instance->p_cb->p_ready_callback)
			    {
			        //PWM ready interrupt handler will be called after one full period.
			        m_pwm_ready_counter[p_instance->p_timer->instance_id][channel] = 2;
			        pwm_irq_enable(p_instance);
			    }
			    return NRF_SUCCESS;
			}
            设置占空比 - 不停设置直到PWM准备好.
            app_pwm_channel_duty_set(&PWM1, 0, value)
            第二个参数是通道????
             */
            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);
        }
    }  
}

app_pwm_channel_duty_set(&PWM1, 0, value) 第二个参数是通道??? 这个怎么理解呢?好像是PPI通道,内部是通过PPI实现的 ,最终找到了下面这个结构体:

/**
     * @brief PWM channel instance
     *
     * This structure holds all data needed by a single PWM channel.
     */
    typedef struct
    {
        uint32_t           gpio_pin;        //!< Pin that is used by this PWM channel.
        uint32_t           pulsewidth;      //!< The copy of duty currently set (in ticks).
        nrf_ppi_channel_t  ppi_channels[2]; //!< PPI channels used by the PWM channel to clear and set the output.
        app_pwm_polarity_t polarity;        //!< The active state of the pin.
        uint8_t            initialized;     //!< The internal information if the selected channel was initialized.
    } app_pwm_channel_cb_t;

二、PPI 引用输入捕获

下面的例子用软件实现了一个PWM输出,然后通过GPTPTE 的电平变换事件,捕获变化的次数,用到了定时器,PPI,串口,PWM等功能:

nrf_ppi_channel_t my_ppi_channel=NRF_PPI_CHANNEL0 ;
//定义Timer0的驱动程序实例。驱动程序实例的ID对应Timer的ID,如NRF_DRV_TIMER_INSTANCE(0)对应Timer0
const nrf_drv_timer_t timer0 = NRF_DRV_TIMER_INSTANCE(0);
//IO口
#define INPUT  2
#define OUTPUT  3

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

/*PWM部分和上面的例程一样*/
void PWM_OUT(uint32_t value)
{
	ret_code_t err_code;
	/* PWM, 周期0.5S, 通过 OUTPUT 管脚输出. */
	app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(500000L, OUTPUT);
	
	/* 初始化和使能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);  
}

//设置管脚为gpiote输入 作为事件
static void gpio_init(void)
{
    ret_code_t err_code;
    //GPIOE驱动初始化
    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);  
    /*
	  Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect high-to-low transition.
	  默认是高电平,event 事件 是 由 高到低,
	  所以采集的是第一次变化的事件
	  采集就是占空比变化的次数	  
		*/
    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);
}

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

static void ppi_init(void)
{
    uint32_t err_code = NRF_SUCCESS;
    
    err_code = nrf_drv_ppi_init();
    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);
    /*
    一旦有了GPIOTE事件,
    就触发计数器+1的任务
    */
    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);	
}

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,
	  NRF_UART_BAUDRATE_115200
	  NRF_UARTE_BAUDRATE_115200
	};
	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);
	/*
	Enable constant latency mode 
	PPI模式下最好启用这个持续供电模式
	*/
	NRF_POWER->TASKS_CONSTLAT = 1;
	//启动定时器
	nrf_drv_timer_enable(&timer0);		
	while (1)
	{
		/*PWM周期为0.5S,所以计数器的值每1S会增加2次*/
		printf("Current cout: %d", (int)nrf_drv_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL1));
		...      
	}
}
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矜辰所致

您们的鼓励是我奋斗的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值