nRF51822使用Timer制作4路PWM波详解

Date2015.5.8 Author:杨  QQ1209758756 <[email protected]>


一、            pwm简介

PWM英文名叫Pulse Width Modulation,中文名叫脉宽调制。那它到底是什么呢?其实它是由定时器产生的,比普通的定时器多了一个比较寄存器。PWM里面有一个词叫占空比,即一个周期内,高电平持续时间与周期的比值。如下图:

nRF51822使用Timer制作4路PWM波详解_第1张图片

占空比(dutycycle) = t/T。

PWM用途:控制电机调速,控制蜂鸣器播放音乐,控制led灯亮度等

 

二、            Timer,PPI,GPIOTE之间的关系

由Timer产生一个事件,PPI捕获这个事件并把这个事件转化为任务传递给GPIOTE,由GPIOTE模块执行最终指定操作(该操作后面会讲到):

nRF51822使用Timer制作4路PWM波详解_第2张图片


三、Timer定时器

要产生PWM波,需要将定时器产生的信号通过指定的引脚输出。定时器有两种模式,即计数器模式和定时器模式,要产生PWM波,自然要选择定时器模式,然而定时器模式里面也有一个计数器寄存器,即Counter。定时器模式还有一个捕获/比较寄存器,即CC寄存器。nRF51822的Timer中的Counter是递增的方式计数,当Counter的计数值与CC寄存器中的值相等时,就会产生一个事件。

nRF51822里面有三个定时器,因为TIMER0被CPU占用,所以只能使用TIMER1和TIMER2来做4路PWM波。使用TIMER1的CC[0]和CC[1]分别控制一路PWM波的频率和占空比,CC[2]和CC[3]分别控制第二路PWM波的频率和占空比。TIMER2类推,这样就可以做出4路PWM波。以下是第一路PWM波的timer init函数, 其他三路的timer init函数可以由此类推,eg:

    static voidtimer1_cc01_init(void)  //第一路PWM波的timer init函数

{

        NRF_CLOCK->EVENTS_HFCLKSTARTED  = 0;

        NRF_CLOCK->TASKS_HFCLKSTART     = 1;

 

        while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)

      {

      } 

       NRF_TIMER1->MODE =TIMER_MODE_MODE_Timer;

NRF_TIMER1->BITMOD=TIMER_BITMODE_BITMODE_16Bit<<TIMER_BITMODE_BITMODE_Pos;  //counter与指定cc寄存器的16位进行比较

       NRF_TIMER1->PRESCALER   = 9;  //9分频,每一个tick32us

       NRF_TIMER1->TASKS_CLEAR = 1; 

       NRF_TIMER1->CC[0]=256;    //控制周期,周期相当于256*32us

       NRF_TIMER1->CC[1]= 1;    //控制占空比

 NRF_TIMER1->SHORTS=(TIMER_SHORTS_COMPARE0_CLEAR_Enabled<<TIMER_SHORTS_COMPARE0_CLEAR_Pos);

   

       NRF_TIMER1->INTENSET= (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos);    //使能中断,中断发生时就会产生COMPARE事件,就会即把compare寄存器置1

       NVIC_EnableIRQ(TIMER1_IRQn);

       NVIC_SetPriority(TIMER1_IRQn,APP_IRQ_PRIORITY_HIGH); 

      

}

 

void TIMER1_IRQHandler(void)   //定时器的中断处理函数;Timer2的中断处理函数类似,这里不再给出。

{   

     //第一路PWM

//cc0 controlperiod, cc1 controls the duty cycle

         NRF_TIMER1->EVENTS_COMPARE[0]= 0;   //Compare寄存器清零

         NRF_TIMER1->CC[1] = duty_cycle1_01;       //每次timer中断都会重新获取CC[1]的值作为占空比

      

        //第二路PWM

          //cc2 control period, cc3 controls the duty cycle

        NRF_TIMER1->EVENTS_COMPARE[2] = 0;

        NRF_TIMER1->CC[3] = duty_cycle1_23;  

}

 

四、ProgrammablePeripheral Interconnect (PPI)

nRF51822的寄存器分为三类:

Task寄存器:外设可以执行的task;

Event寄存器:外设带有的event;

普通寄存器;

Task寄存器和Event寄存器在PPI中的使用非常重要,例如,在PPI中,设置EEP寄存器为某个外设A的Event寄存器地址,TEP寄存器设为另外一个外设Task寄存器地址,那么当外设A的event发生时可以直接触发外设B的Task,而不经过CPU。

eg:

staticvoid ppi_init(void)   //初始化PPI模块,设置EEP寄存器和TEP寄存器

{

       // Configure PPI channel 01 to togglePWM_OUTPUT_PIN on every TIMER1 COMPARE match.

    NRF_PPI->CH[0].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];

    NRF_PPI->CH[0].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[0]; 

    NRF_PPI->CH[1].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[1];

    NRF_PPI->CH[1].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[0];

    

        // Configure PPI channel 23 to toggle PWM_OUTPUT_PIN on every TIMER1COMPARE match.

       NRF_PPI->CH[2].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[2];

    NRF_PPI->CH[2].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[1]; 

    NRF_PPI->CH[3].EEP =(uint32_t)&NRF_TIMER1->EVENTS_COMPARE[3];

    NRF_PPI->CH[3].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[1];

      

        // Configure PPI channel 45 to toggle PWM_OUTPUT_PIN on every TIMER2COMPARE match.

       NRF_PPI->CH[4].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0];

    NRF_PPI->CH[4].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[2]; 

    NRF_PPI->CH[5].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[1];

    NRF_PPI->CH[5].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[2];

      

        // Configure PPI channel 67 to toggle PWM_OUTPUT_PIN on every TIMER2COMPARE match.

       NRF_PPI->CH[6].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[2];

    NRF_PPI->CH[6].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[3]; 

    NRF_PPI->CH[7].EEP =(uint32_t)&NRF_TIMER2->EVENTS_COMPARE[3];

    NRF_PPI->CH[7].TEP =(uint32_t)&NRF_GPIOTE->TASKS_OUT[3]; 

             

       // Enable PPI channels 0-7.

    NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled<< PPI_CHEN_CH0_Pos)

                    | (PPI_CHEN_CH1_Enabled<< PPI_CHEN_CH1_Pos)

                       | (PPI_CHEN_CH2_Enabled<< PPI_CHEN_CH2_Pos)

                    | (PPI_CHEN_CH3_Enabled<< PPI_CHEN_CH3_Pos)

                                   |(PPI_CHEN_CH4_Enabled << PPI_CHEN_CH4_Pos)

                                   |(PPI_CHEN_CH5_Enabled << PPI_CHEN_CH5_Pos)

                                   |(PPI_CHEN_CH6_Enabled << PPI_CHEN_CH6_Pos)

                                   |(PPI_CHEN_CH7_Enabled << PPI_CHEN_CH7_Pos);

}

 

这里NRF_PPI->CH[n]是指PPI通道。

PPI由两个端点寄存器组成,即Event End-Point (EEP)Task End-Point (TEP)。一个外设任务通过与任务相关的任务寄存器连接到TEP;同样的,一个外设事件通过与事件相关的事件寄存器连接到EEP

NRF_PPI->CH[0].EEP对应NRF_PPI->CH[0].TEP,以此类推。

EVENT_COMPARE[0]对应CC[0],以此类推。

TASK_OUT[n]指定任务输出通道。

 

五、GPIOTasks and Events (gpiote)

GPIOTE模块也是设计成减少CPU占用的Task Event模式,使得事件不经过CPU直接得到响应。

Event引脚触发源:上升沿,下降沿等;

Task引脚操作方式:置位,清零,翻转;

Event和Task之间可以通过PPI连接在一起。

 

一旦把某个引脚分配给Task(OUT[n])或Event(IN[n]),那么该引脚只能被GPIOTE模块写操作,而普通的gpio写入无效。

当GPIOTE通道被配置用于操作一个任务引脚n,那么该引脚n的初值需要在CONFIG[n]寄存器的OUTINIT区域中设定。

eg:

 

staticvoid gpiote_init(void)    //初始化GPIOTE模块,设置4PWM信号输出引脚

{

    APP_GPIOTE_INIT(APP_GPIOTE_MAX_USERS);

      

    // Configure PWM_OUTPUT_PIN_NUMBER as anoutput.

   nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER0);   

       nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER1);

       nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER2);

       nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER3);

   

    // Configure GPIOTE channel 0 to toggle thePWM pin state

       // @note Only one GPIOTE task can beconnected to an output pin.

       nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0      , \

                           NRF_GPIOTE_POLARITY_TOGGLE,NRF_GPIOTE_INITIAL_VALUE_HIGH);

       nrf_gpiote_task_config(1,PWM_OUTPUT_PIN_NUMBER1      , \

                          NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);  

       nrf_gpiote_task_config(2,PWM_OUTPUT_PIN_NUMBER2      , \

                           NRF_GPIOTE_POLARITY_TOGGLE,NRF_GPIOTE_INITIAL_VALUE_HIGH);

       nrf_gpiote_task_config(3,PWM_OUTPUT_PIN_NUMBER3      , \

                          NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);

       

}

 

重要函数static__INLINE void nrf_gpiote_task_config(uint32_t channel_number, uint32_tpin_number, nrf_gpiote_polarity_t polarity, nrf_gpiote_outinit_t initial_value)

channel_number:指定GPIOTE的通道[0:3],并配置成输出通道,与PPI中的TASK_OUT[n]对应;

pin_number:指定pin管脚,在GPIOTE中使用,也就是最终被pwm控制的管脚;

polarity:指定在GPIOTE中的极性,当任务信号到达就会执行该动作;

initial_value:指定pin管脚的初始值。

 

 

六、            总结

 

问题一通过pwm波控制灯的亮度范围是全灭到全亮,但是现在做出来的pwm波不能使灯全灭或者全亮。比如我的周期用了256,占空比的范围只能是1到255,这样的话通过占空比是不能控制灯全灭或者全亮。

 

解决方法如果占空比为0,或者为256,就会在同一时间触发两个事件,如果占空比为0,即CC[1]=0,那么当计数器超过CC[0]的值(256)时,就会自动置零并从零开始重新计数,而且会产生一个事件,当计数器置零时,CC[1]的值也为零,所以CC[1]也会产生一个事件,所以同一时间会产生两个事件,分别由CC[0]CC[1]产生(这部分是个人理解)。
   
所以这样是不能控制灯全灭或者全亮。所以有以下办法能控制灯全亮或者全灭:

要设置该PWM引脚为低或者高,可以重新初始化这个引脚的GPIOTE nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_HIGH);设置为高或者低,然后把控制他的PPI disable掉。

 

或者还有一种方法,设置高配置为:

nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER,NRF_GPIOTE_POLARITY_LOTOHI, NRF_GPIOTE_INITIAL_VALUE_HIGH)

设置低配置为nrf_gpiote_task_config(0, PWM_OUTPUT_PIN_NUMBER,NRF_GPIOTE_POLARITY_HITOLO,NRF_GPIOTE_INITIAL_VALUE_LOW)

可以试试哪种方法比较好用,这里给出后面这种方法:

for (; ;)

{

     power_manage();

        

     if (duty_cycle1_01 == 255)

     {

             nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0,  \

                                   NRF_GPIOTE_POLARITY_LOTOHI,NRF_GPIOTE_INITIAL_VALUE_HIGH); //当占空比为255时,灯最暗(但是微亮),把引脚拉高,就会全灭

         // nrf_gpiote_task_config(0,PWM_OUTPUT_PIN_NUMBER0,  \

                                        NRF_GPIOTE_POLARITY_HITOLO,NRF_GPIOTE_INITIAL_VALUE_LOW);   //当占空比为1时,灯最亮(但不是全亮),把引脚拉低,就会全亮

     }

……

 

 


你可能感兴趣的:(timer,ppi,PWM,蓝牙4.0,gpiote)