nRF51822使用Timer製作4路PWM波詳解

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


一、            pwm簡介

PWM英文名叫Pulse Width Modulation,中文名叫脈寬調製。那它到底是什麼呢?其實它是由定時器產生的,比普通的定時器多了一個比較寄存器。PWM裏面有一個詞叫佔空比,即一個週期內,高電平持續時間與週期的比值。如下圖:


佔空比(dutycycle) = t/T。

PWM用途:控制電機調速,控制蜂鳴器播放音樂,控制led燈亮度等

 

二、            Timer,PPI,GPIOTE之間的關係

由Timer產生一個事件,PPI捕獲這個事件並把這個事件轉化爲任務傳遞給GPIOTE,由GPIOTE模塊執行最終指定操作(該操作後面會講到):



三、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時,燈最亮(但不是全亮),把引腳拉低,就會全亮

     }

……

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章