一、工具
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 */ #define TIMER4_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 */
順便說一下,上面比較寄存器的裝載值設置是有技巧的,這是我在調試中發現的,如果有不對的地方望指出。10000是我的滿度值,這一塊在我的使用STM32定時器的DMA方式輸出方波那篇文章中有介紹,不懂的可以去了解下。https://www.cnblogs.com/wenhao-Web/p/13820320.html
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次,同時對應前面示波器顯示的波形(細心的會發現我判斷通道比較寄存器的值並不是從第一個開始比較的,這也是我在調試中發現的,如果從第一個開始比較會在當前值的前一次電平的轉換時觸發中斷,與實際不符,這也是我困惑的地方,我猜測可能是DMA或者定時器緩沖區導致的滯后性造成的,如有不對的忘指出)。
/** * @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