文章目錄
一. STM32的DMA PWM原理
最開始疑惑過STM32如何才能實現精確數量的脈衝輸出從而控制步進電機,直到作WS2812B燈珠的驅動程序時才知道原來有DMA-PWM模式。使用DMA輸出PWM能夠精確控制脈衝數量,且能夠精確控制脈衝週期與佔空比,更重要的是使用DMA傳輸不消耗CPU資源。因而乎上網搜索資源與教程,遺憾的是網上的教程要麼語焉不詳,要麼代碼不全,要麼只講表層不講原理。秉承本身動手豐衣足食的古訓,因而去翻閱參考手冊,從DMA章節看到定時器章節,結合代碼實戰,總算搞清些端倪,也分享一下在此過程當中遇到的問題。javascript
1. DMA簡介
官方釋義:DMA,全稱爲: Direct Memory Access,即直接存儲器訪問。 DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場的過程,經過硬件爲 RAM 與 I/O 設備開闢一條直接傳送數據的通路, 能使 CPU 的效率大爲提升。下面這張圖是網上流行的摘自參考手冊上的DMA框圖。java
STM32F1系列有兩個DMA,分別有7個通道和5個通道(Channel)。F7系列每一個DMA分別有8個數據流(StreamX),每一個數據流對應8個通道(CHannel),這裏稍微區分一下兩個系列的表述,F1所說的Channel應該對應F7中的Stream。例如DMA1的通道對應表以下。STM32的ADC、SPI、IIS、USART、IIC、TIM、DAC等數據傳輸外設均可以設置爲DMA方式傳輸,在手動配置的時候查表選擇通道便可,固然若是用Cubemx工具的話就會自動選擇了。
DMA傳輸有什麼好處?舉個例子,使用HAL_UART_Transmit()和HAL_UART_Transmit_DMA(),前者使用普通模式,CPU會進入執行函數,直到數據傳輸完成退出,而後才執行下一條指令。後者使用DMA傳輸,DMA啓動傳輸以後CPU就無論了,直接往下執行其餘指令。CPU幹什麼呢?只須要處理DMA傳輸完成、半傳輸完成、傳輸錯誤等中斷,或者經過查詢寄存器檢查DMA傳輸到啥狀況了。是否是瞬間快起來了!
函數
2. DMA方式輸出PWM是怎麼回事
使用DMA傳輸數據很好理解,爲何DMA能夠控制PWM脈衝數量和佔空比呢?這裏咱們迴歸本質,在DMA控制PWM輸出的過程當中,DMA依然傳輸的是數據,只不過它送過去的是比較值,即TIMx_CCRx的值,這個值不用多解釋了,和自動重裝載寄存器(TIMx_ARR)的值分別決定週期和佔空比。看一下手冊中定時器的DMA連續傳送模式的解釋。
注意黃色部分,什麼是更新事件?回顧一下,在向上計數模式下當計數到自動重裝載值就會發生更新事件(溢出)。也就是說每單個PWM波結束後就會自動將比較值設置成DMA傳輸來的數據。以本例設置的週期1ms爲例,設置send_Buf[] = {10,20,30,…,100},最終的波形就是高電平時間分別爲10,20,30,…100us的十個方波。
很簡單有木有!!
工具
3. HAL庫DMA配置PWM的幾個函數
說實話使用HAL庫仍是有點彎彎繞,不少操做層層封裝,可能用寄存器幾句代碼的事情到了HAL庫要調用好幾個函數轉幾道彎,但這也是大勢所趨吧,將底層都封裝起來,讓用戶專一於應用程序。最近用Cubemx自動生成代碼的感受就是,真香!!測試
搬運stm32F7xx_hal_tim.h中的函數定義,如下分別是以阻塞模式、中斷模式、DMA模式啓動和中止PWM。ui
/* Blocking mode: Polling */ HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel); HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel); /* Non-Blocking mode: Interrupt */ HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel); HAL_StatusTypeDef HAL_TIM_PWM_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel); /* Non-Blocking mode: DMA */ HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length); HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel);
如下是中斷回調函數的聲明,這裏咱們只關注void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
每次PWM輸出完成以後調用這個函數,在中斷裏面咱們須要調用HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel)
中止DMA傳輸,不然它不會本身中止的。
spa
/* Callback in non blocking modes (Interrupt and DMA) *************************/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim); void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim); void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim); void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim); void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);
二. STM32CubeMx配置 DMA PWM
以STM32F1和F7系列板子爲例進行測試,通過測試二者配置基本是同樣的,結果也是同樣,因此這裏以F1爲例講解。debug
如圖,新建基於STM32F103ZET6的工程,先進行時鐘配置,系統時鐘設定到最大72MHz。
而後設置定時器,我使用的是T2,四個通道都選上了,根據須要來便可。分頻係數設爲71,即72分頻,pwm頻率1MHz,自動重裝載值爲1000,獲得週期爲1ms.
接下來設置DMA,如圖,四個通道DMA都選上了,這裏CH2和CH4共用了一個通道,暫且無論它。
能夠看到此時DMA中斷已經開啓
此外若是使用ST-Link下載程序,注意圖示這個地方設置debug模式爲Serial Wire,否則會出現ST-Link只能下載一次程序的狀況。
到此設置完畢,點擊GENERATE CODE便可。
指針
三. 波形調試過程分析
打開工程,能夠看到TIM的初始化和DMA的初始化函數,這裏在main函數中調用HAL_TIM_PWM_Start函數就能夠正常輸出連續波形了。調試
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1)
調用__HAL_TIM_SET_COMPARE函數能夠改變佔空比
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200);
如設置成200,則高電平時間爲200us,佔空比爲200/1000。
由於咱們要使用DMA方式,在main函數中定義一個發送數據緩衝區
#define NUM 21 uint32_t send_Buf[NUM] = { 0};
在main函數中增長如下代碼
for (i = 0; i < NUM; i++) { send_Buf[i] = 20 * (i + 1); } while (1) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(200); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(200); HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM); }
添加以下函數
// PWM DMA 完成回調函數 void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_3); }
這就是第一部分講的須要在回調函數中調用HAL_TIM_PWM_Stop_DMA
函數中止PWM輸出。
理論上講到這裏應該如咱們所願輸出指望波形了,也就是佔空比遞增的21個波。但遺憾的是個人波是這樣的:
三個問題:
(1)數據只有一半;
(2)波的週期變成了正常的兩倍(2ms)
(3)最後一個數據跑到最前邊了。
生氣ing…
因而開始調bug,第一個問題發現了,因爲HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);
函數中的發送數據指針是指向32位的,個人send_Buf也是定義的32位,可是DMA傳輸個人設置是半字16位,如圖示。
也就是從uint32_t *pData開始指針每移一位,地址偏移兩個字節。這樣就能解釋上面的第一二問題,由於但願傳輸的數據是{sendBuf[0],sendBuf[1], …,sendBuf[20]},實際傳輸的數據倒是:
sendBuf[0]低16位
sendBuf[0]高16位(即0)
…
sendBuf[9]低16位
sendBuf[9]高16位(即0)
sendBuf[10]低16位
接下來修改傳輸字寬爲Word(32)位,或者把send_Buf改成uint16_t型,經測試結果都對了,如圖所示。因此提醒朋友們,
DMA傳輸位寬和定義的緩衝區位寬必定要一致!!
DMA傳輸位寬和定義的緩衝區位寬必定要一致!!
DMA傳輸位寬和定義的緩衝區位寬必定要一致!!
問題3依然存在,因而把最後一個數據改成0試試,
添加send_Buf[NUM - 1] = 0;
波形正常了。
緣由尚不清楚,是否是由於DMA傳輸的起始和結束有什麼不穩定因素,既然如此,那就每次在正常數據後面補一個或者多個0就好了。不影響使用。
至此DMA控制PWM輸出成功,下一篇用HAL-DMA-PWM點亮WS2812燈珠。