基於GD32F30x微控制器_定時器輸出比較模式輸出方波(DMA方式)
一、工具
1、GD32F30x系列微控制器;
2、編譯環境:KEIL
二、需求分析
如下圖所示,現要求控制微控制器同時輸出3路方波,並且每個方波的高低電平持續的時長是可調整的,因為對時長有著嚴格的要求,這就需要在方波開始輸出後就不能再通過軟體進行干預,完全交給微控制器自己去完成。通過觀察要輸出方波的特點,除了LED_PWM波具備PWM波形的特點,其它兩個與PWM波形有著很大的不同,於是乎想使用微控制器的定時器的PWM模式輸出剩餘兩種波形很顯然行不通。而微控制器定時器另一種比較靈活的輸出方波的模式就是輸出比較模式,當然這種模式也包含PWM模式。
通過以上分析,波形的輸出方式似乎找到了方法,至於能否實現還需要通過程式碼實現並除錯去驗證。那麼還有一個需求也與以上輸出的波形有關,那就是能夠每次在t4時間段內進行電壓採集,電壓的變化也是受以上三個方波影響的(至於電壓的採集方法我會在另一篇文章中介紹),為了保證採集的電壓穩定,可以適當的在t4時間段內多次採集。熟悉微控制器定時器的知道,想要每次都能夠在t4時間段內採集電壓,需要觸發中斷或者事件來實現,而能夠觸發中斷或者事件點除了t4的開始和t4的結束也就是電平發生轉換時,在t4中間是沒法觸發中斷或者事件的,而如果只在t4的開始和t4的結束區採集電壓,很可能採集到的電壓並非所需要的電壓。
為了實現在t4時間內採集多次電壓,我就想到了定時器的另一個還沒有用的通道,如果讓它也輸出一個波形,並且使這個波形的電平能夠在t4時間段內發生多次轉換,那麼我就可以通過觸發中斷的方式進行多次電壓採集。理想的方式如下圖紫色波形所示(當然也是我已經實現的結果,實際程式是不會輸出紫色波形的,你現在看到的是我通過一個普通的IO引腳在中斷中反轉得到的),黃色波形是上圖MEA_S2的波形。
雖然我使用另一個定時器的輸出比較通道輸出一個波形用於採集電壓,但這個波形並不佔用引腳資源,算是一個抽象出來的波形。如下圖所示,通過對使用者手冊的解讀以及實際驗證,發現是可以遮蔽通道輸出的。
在相關的暫存器中我們也可以找到相關的控制位,比如通道0,如下圖所示,只要我們把該位置0就不要擔心該引腳被用作其它功能了。
分析完上述需求後,下面就開始程式碼的實現。
三、程式碼實現
1、相關變數和引數定義,這裡需要注意所使用定時器的基地址和相關通道的偏移地址,具體值可在使用者手冊中查到(這裡我用的是定時器4)。
通道0的比較暫存器偏移地址:
通道1的比較暫存器偏移地址:
通道2的比較暫存器偏移地址:
通道3的比較暫存器偏移地址:
/* 定時器4通道比較暫存器地址 */ #define TIMER4_CH0CV (TIMER4+0x34) /* 通道0 */ #define TIMER4_CH1CV (TIMER4+0x38) /* 通道1 */ #defineTIMER4_CH2CV (TIMER4+0x3C) /* 通道2 */ #define TIMER4_CH3CV (TIMER4+0x40) /* 通道3 */ /* 定時器比較暫存器裝載值,由DMA去實現裝載 */ uint16_t ch0cv_value[] = {1000, 1500, 2000, 2500, 3000, 3500, 10000}; /* 電壓採集訊號 */ uint16_t ch1cv_value[] = {880, 10000, 0}; /* LED_PWM */ uint16_t ch2cv_value[] = {4000, 8000, 10000}; /* S2 */ uint16_t ch3cv_value[] = {893, 906, 10000}; /* S1 */
2、定時器4輸出引腳配置,因為值輸出三路波形,這裡只配置三個GPIO引腳
/** * @brief 定時器4輸出比較通道引腳初始化 * @retval none * @author Mr.W * @date 2020-10-22 */ static void bsp_timer4_gpio_cfg(void) { /* 開啟引腳時鐘 */ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_AF); /*configure PA1(TIMER4 CH1) PA2(TIMER4 CH2) PA3(TIMER4 CH3) as alternate function*/ gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1); gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3); }
3、這裡把DMA的四個定時器4通道都進行了配置,有一路通道雖然不需要通過引腳輸出,但也要配置。
/** * @brief DMA配置(每個DMA通道只能同時開啟一個外設請求) * @retval none * @author Mr.W * @date 2020-10-22 */ static void bsp_dma_config(void) { dma_parameter_struct dma_init_struct; /* enable DMA clock */ rcu_periph_clock_enable(RCU_DMA1); /* initialize DMA channel */ dma_deinit(DMA1,DMA_CH4); /* DMA channel4 initialize */ dma_init_struct.periph_addr = (uint32_t)TIMER4_CH0CV; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.memory_addr = (uint32_t)ch0cv_value; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.number = 7; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA1,DMA_CH4,&dma_init_struct); /* 使能迴圈模式 */ dma_circulation_enable(DMA1,DMA_CH4); /* enable DMA channel4 */ dma_channel_enable(DMA1,DMA_CH4); /* initialize DMA channel */ dma_deinit(DMA1,DMA_CH3); /* DMA channel3 initialize */ dma_init_struct.periph_addr = (uint32_t)TIMER4_CH1CV; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.memory_addr = (uint32_t)ch1cv_value; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.number = 3; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA1,DMA_CH3,&dma_init_struct); /* 使能迴圈模式 */ dma_circulation_enable(DMA1,DMA_CH3); /* enable DMA channel3 */ dma_channel_enable(DMA1,DMA_CH3); /* initialize DMA channel */ dma_deinit(DMA1,DMA_CH1); /* DMA channel1 initialize */ dma_init_struct.periph_addr = (uint32_t)TIMER4_CH2CV; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.memory_addr = (uint32_t)ch2cv_value; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.number = 3; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA1,DMA_CH1,&dma_init_struct); /* 使能迴圈模式 */ dma_circulation_enable(DMA1,DMA_CH1); /* enable DMA channel1 */ dma_channel_enable(DMA1,DMA_CH1); /* initialize DMA channel */ dma_deinit(DMA1,DMA_CH0); /* DMA channel0 initialize */ dma_init_struct.periph_addr = (uint32_t)TIMER4_CH3CV; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.memory_addr = (uint32_t)ch3cv_value; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.number = 3; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA1,DMA_CH0,&dma_init_struct); /* 使能迴圈模式 */ dma_circulation_enable(DMA1,DMA_CH0); /* enable DMA channel0 */ dma_channel_enable(DMA1,DMA_CH0); }
4、定時器4功能配置,這裡也是配置了四個通道;不進行波形輸出的那一路通道要設定成禁止,但是因為要通過它採集電壓,所以要開啟對應的中斷功能。
/** * @brief 定時器4配置 * @retval none * @author Mr.W * @date 2020-10-22 */ static void bsp_timer4_cfg(void) { /* --------------------------------------------------------------------------- TIMER1 configuration: output compare toggle mode: 定時器頻率 Frequency = systemcoreclock/((prescaler+1) * (period+1)) 10Hz = 120000000/(1200 * 10000) ----------------------------------------------------------------------------*/ timer_oc_parameter_struct timer_ocintpara; timer_parameter_struct timer_initpara; rcu_periph_clock_enable(RCU_TIMER4); timer_deinit(TIMER4); /* TIMER1 configuration */ timer_initpara.prescaler = 1199; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 9999; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(TIMER4, &timer_initpara); /* Configuration in OC TOGGLE mode */ timer_ocintpara.outputstate = TIMER_CCX_ENABLE; timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_HIGH; timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; timer_channel_output_config(TIMER4, TIMER_CH_1, &timer_ocintpara); timer_channel_output_config(TIMER4, TIMER_CH_2, &timer_ocintpara); timer_channel_output_config(TIMER4, TIMER_CH_3, &timer_ocintpara); timer_ocintpara.outputstate = TIMER_CCX_DISABLE; timer_channel_output_config(TIMER4, TIMER_CH_0, &timer_ocintpara); /* CH10*/ timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_0, 0); timer_channel_output_mode_config(TIMER4, TIMER_CH_0, TIMER_OC_MODE_TOGGLE); timer_channel_output_shadow_config(TIMER4, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE); /* clear channel 0 interrupt bit */ timer_interrupt_flag_clear(TIMER4, TIMER_INT_FLAG_CH0); /* channel 0 interrupt enable */ timer_interrupt_enable(TIMER4, TIMER_INT_CH0); /* CH1 */ timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_1, 0); timer_channel_output_mode_config(TIMER4, TIMER_CH_1, TIMER_OC_MODE_TOGGLE); timer_channel_output_shadow_config(TIMER4, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE); /* CH2 */ timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_2, 0); timer_channel_output_mode_config(TIMER4, TIMER_CH_2, TIMER_OC_MODE_TOGGLE); timer_channel_output_shadow_config(TIMER4, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE); /* CH3 */ timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_3, 0); timer_channel_output_mode_config(TIMER4, TIMER_CH_3, TIMER_OC_MODE_TOGGLE); timer_channel_output_shadow_config(TIMER4, TIMER_CH_3, TIMER_OC_SHADOW_DISABLE); /* TIMER1 update DMA request enable */ timer_dma_enable(TIMER4, TIMER_DMA_CH0D); timer_dma_enable(TIMER4, TIMER_DMA_CH1D); timer_dma_enable(TIMER4, TIMER_DMA_CH2D); timer_dma_enable(TIMER4, TIMER_DMA_CH3D); /* auto-reload preload enable */ timer_auto_reload_shadow_enable(TIMER4); timer_enable(TIMER4); }
5、定時器4初始化,開啟並設定中斷優先順序,並設定預設的輸出頻率為44Hz。
/** * @brief 定時器4初始化 * @retval none * @author Mr.W * @date 2020-10-22 */ void bsp_timer4_init(void) { nvic_irq_enable(TIMER4_IRQn, 4, 0); bsp_timer4_gpio_cfg(); bsp_dma_config(); bsp_timer4_cfg(); bsp_timer4_freq_change(44); }
6、輸出頻率設定,這裡單獨實現一個函式是為了方便以後其它地方對頻率進行更改。
/** * @brief 定時器4頻率改變 * @param frequency 要改變的頻率值,單位Hz * @retval none * @author Mr.W * @date 2020-10-22 */ void bsp_timer4_freq_change(uint32_t frequency) { uint16_t prescaler; /* 通過設定的定時器頻率計算出預分頻值 */ prescaler = (SystemCoreClock/10000)/frequency - 1; /* 更新預分頻值 */ timer_prescaler_config(TIMER4, prescaler, TIMER_PSC_RELOAD_NOW); }
7、中斷服務函式,中斷服務函式中對定時器4通道1的比較暫存器進行了判斷,如果符合要求就進行一次電壓的採集,我這裡共採集了6次,同時對應前面示波器顯示的波形。
/** * @brief This function handles TIMER4 interrupt request. * @param None * @retval None */ void TIMER4_IRQHandler(void) {if(SET == timer_interrupt_flag_get(TIMER4, TIMER_INT_FLAG_CH0)) { /* clear channel 0 interrupt bit */ timer_interrupt_flag_clear(TIMER4, TIMER_INT_FLAG_CH0); /* read channel 0 capture value */ if(TIMER_CH0CV(TIMER4) == ch0cv_value[1]) { g_global_data.adc_mea[0] = adc01_get_mea_adc_value(); g_global_data.adc_ref[0] = adc01_get_ref_adc_value(); } else if(TIMER_CH0CV(TIMER4) == ch0cv_value[2]) { g_global_data.adc_mea[1] = adc01_get_mea_adc_value(); g_global_data.adc_ref[1] = adc01_get_ref_adc_value(); } else if(TIMER_CH0CV(TIMER4) == ch0cv_value[3]) { g_global_data.adc_mea[2] = adc01_get_mea_adc_value(); g_global_data.adc_ref[2] = adc01_get_ref_adc_value(); } else if(TIMER_CH0CV(TIMER4) == ch0cv_value[4]) { g_global_data.adc_mea[3] = adc01_get_mea_adc_value(); g_global_data.adc_ref[3] = adc01_get_ref_adc_value(); } else if(TIMER_CH0CV(TIMER4) == ch0cv_value[5]) { g_global_data.adc_mea[4] = adc01_get_mea_adc_value(); g_global_data.adc_ref[4] = adc01_get_ref_adc_value(); } else if(TIMER_CH0CV(TIMER4) == ch0cv_value[6]) { g_global_data.adc_mea[5] = adc01_get_mea_adc_value(); g_global_data.adc_ref[5] = adc01_get_ref_adc_value(); } } }
#endif