久久久国产精品视频袁燕,99re久久精品国产,亚洲欧美日韩国产综合v,天天躁夜夜躁狠狠久久,激情五月婷婷激情五月婷婷

  • 5
    回復(fù)
  • 收藏
  • 點(diǎn)贊
  • 分享
  • 發(fā)新帖

STM32F1/F7使用HAL庫DMA方式輸出PWM詳解(輸出精確數(shù)量且可調(diào)周期與占空比)

核心提示:文章目錄一. STM32的DMA PWM原理1. DMA簡介2. DMA方式輸出PWM是怎么回事3. HAL庫DMA配置PWM的幾個(gè)函數(shù)二. STM32CubeMx配置 DMA PWM三. 波形調(diào)試過程分析一. STM32的DMA PWM原理最開始疑惑過STM32如何才能實(shí)現(xiàn)精確數(shù)量的脈沖輸出從而控制步進(jìn)電機(jī),直到做WS2812B燈珠的驅(qū)動(dòng)程序時(shí)才知道原來有DMA-PWM模式。使用DMA輸出PWM可以精確控制脈沖數(shù)量,且可以精確控制脈沖周期與占空比,更重要的是使用DMA傳輸不消耗CPU資源。于是乎

文章目錄

一. STM32的DMA PWM原理1. DMA簡介2. DMA方式輸出PWM是怎么回事3. HAL庫DMA配置PWM的幾個(gè)函數(shù)二. STM32CubeMx配置 DMA PWM三. 波形調(diào)試過程分析

一. STM32的DMA PWM原理

最開始疑惑過STM32如何才能實(shí)現(xiàn)精確數(shù)量的脈沖輸出從而控制步進(jìn)電機(jī),直到做WS2812B燈珠的驅(qū)動(dòng)程序時(shí)才知道原來有DMA-PWM模式。使用DMA輸出PWM可以精確控制脈沖數(shù)量,且可以精確控制脈沖周期與占空比,更重要的是使用DMA傳輸不消耗CPU資源。于是乎上網(wǎng)搜索資源與教程,遺憾的是網(wǎng)上的教程要么語焉不詳,要么代碼不全,要么只講表層不講原理。秉承自己動(dòng)手豐衣足食的古訓(xùn),于是去翻閱參考手冊,從DMA章節(jié)看到定時(shí)器章節(jié),結(jié)合代碼實(shí)戰(zhàn),總算搞清些端倪,也分享一下在此過程中遇到的問題。

1. DMA簡介

官方釋義:DMA,全稱為: Direct Memory Access,即直接存儲(chǔ)器訪問。 DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復(fù)現(xiàn)場的過程,通過硬件為 RAM 與 I/O 設(shè)備開辟一條直接傳送數(shù)據(jù)的通路, 能使 CPU 的效率大為提高。下面這張圖是網(wǎng)上流行的摘自參考手冊上的DMA框圖。

STM32F1系列有兩個(gè)DMA,分別有7個(gè)通道和5個(gè)通道(Channel)。F7系列每個(gè)DMA分別有8個(gè)數(shù)據(jù)流(StreamX),每個(gè)數(shù)據(jù)流對(duì)應(yīng)8個(gè)通道(CHannel),這里稍微區(qū)分一下兩個(gè)系列的表述,F(xiàn)1所說的Channel應(yīng)該對(duì)應(yīng)F7中的Stream。例如DMA1的通道對(duì)應(yīng)表如下。STM32的ADC、SPI、IIS、USART、IIC、TIM、DAC等數(shù)據(jù)傳輸外設(shè)都可以設(shè)置為DMA方式傳輸,在手動(dòng)配置的時(shí)候查表選擇通道即可,當(dāng)然如果用Cubemx工具的話就會(huì)自動(dòng)選擇了。

DMA傳輸有什么好處?舉個(gè)例子,使用HAL_UART_Transmit()和HAL_UART_Transmit_DMA(),前者使用普通模式,CPU會(huì)進(jìn)入執(zhí)行函數(shù),直到數(shù)據(jù)傳輸完成退出,然后才執(zhí)行下一條指令。后者使用DMA傳輸,DMA啟動(dòng)傳輸之后CPU就不管了,直接往下執(zhí)行其他指令。CPU干什么呢?只需要處理DMA傳輸完成、半傳輸完成、傳輸錯(cuò)誤等中斷,或者通過查詢寄存器檢查DMA傳輸?shù)缴肚闆r了。是不是瞬間快起來了!

2. DMA方式輸出PWM是怎么回事

使用DMA傳輸數(shù)據(jù)很好理解,為什么DMA可以控制PWM脈沖數(shù)量和占空比呢?這里我們回歸本質(zhì),在DMA控制PWM輸出的過程中,DMA依然傳輸?shù)氖菙?shù)據(jù),只不過它送過去的是比較值,即TIMx_CCRx的值,這個(gè)值不用多解釋了,和自動(dòng)重裝載寄存器(TIMx_ARR)的值分別決定周期和占空比??匆幌率謨灾卸〞r(shí)器的DMA連續(xù)傳送模式的解釋。

注意黃色部分,什么是更新事件?回顧一下,在向上計(jì)數(shù)模式下當(dāng)計(jì)數(shù)到自動(dòng)重裝載值就會(huì)發(fā)生更新事件(溢出)。也就是說每單個(gè)PWM波結(jié)束后就會(huì)自動(dòng)將比較值設(shè)置成DMA傳輸來的數(shù)據(jù)。以本例設(shè)置的周期1ms為例,設(shè)置send_Buf[] = {10,20,30,…,100},最終的波形就是高電平時(shí)間分別為10,20,30,…100us的十個(gè)方波。很簡單有木有??!

3. HAL庫DMA配置PWM的幾個(gè)函數(shù)

說實(shí)話使用HAL庫還是有點(diǎn)彎彎繞,很多操作層層封裝,可能用寄存器幾句代碼的事情到了HAL庫要調(diào)用好幾個(gè)函數(shù)轉(zhuǎn)幾道彎,但這也是大勢所趨吧,將底層都封裝起來,讓用戶專注于應(yīng)用程序。最近用Cubemx自動(dòng)生成代碼的感覺就是,真香??!

搬運(yùn)stm32F7xx_hal_tim.h中的函數(shù)定義,以下分別是以阻塞模式、中斷模式、DMA模式啟動(dòng)和停止PWM。


HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);

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);

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);

以下是中斷回調(diào)函數(shù)的聲明,這里我們只關(guān)注void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);每次PWM輸出完成之后調(diào)用這個(gè)函數(shù),在中斷里面我們需要調(diào)用HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel)停止DMA傳輸,否則它不會(huì)自己停止的。


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系列板子為例進(jìn)行測試,經(jīng)過測試兩者配置基本是一樣的,結(jié)果也是一樣,所以這里以F1為例講解。

如圖,新建基于STM32F103ZET6的工程,先進(jìn)行時(shí)鐘配置,系統(tǒng)時(shí)鐘設(shè)定到最大72MHz。

然后設(shè)置定時(shí)器,我使用的是T2,四個(gè)通道都選上了,根據(jù)需要來即可。分頻系數(shù)設(shè)為71,即72分頻,pwm頻率1MHz,自動(dòng)重裝載值為1000,得到周期為1ms.

接下來設(shè)置DMA,如圖,四個(gè)通道DMA都選上了,這里CH2和CH4共用了一個(gè)通道,暫且不管它。

可以看到此時(shí)DMA中斷已經(jīng)開啟

此外如果使用ST-Link下載程序,注意圖示這個(gè)地方設(shè)置debug模式為Serial Wire,不然會(huì)出現(xiàn)ST-Link只能下載一次程序的情況。

到此設(shè)置完畢,點(diǎn)擊GENERATE CODE即可。

三. 波形調(diào)試過程分析

打開工程,可以看到TIM的初始化和DMA的初始化函數(shù),這里在main函數(shù)中調(diào)用HAL_TIM_PWM_Start函數(shù)就可以正常輸出連續(xù)波形了。

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1)

調(diào)用__HAL_TIM_SET_COMPARE函數(shù)可以改變占空比

__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,200);

如設(shè)置成200,則高電平時(shí)間為200us,占空比為200/1000。

因?yàn)槲覀円褂肈MA方式,在main函數(shù)中定義一個(gè)發(fā)送數(shù)據(jù)緩沖區(qū)

#define NUM 21
uint32_t send_Buf[NUM] = { 0};

在main函數(shù)中增加以下代碼

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);
}

添加如下函數(shù)

// PWM DMA 完成回調(diào)函數(shù)
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{ 
	HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_3);
}

這就是第一部分講的需要在回調(diào)函數(shù)中調(diào)用HAL_TIM_PWM_Stop_DMA函數(shù)停止PWM輸出。理論上講到這里應(yīng)該如我們所愿輸出期望波形了,也就是占空比遞增的21個(gè)波。但遺憾的是我的波是這樣的:

三個(gè)問題:(1)數(shù)據(jù)只有一半;(2)波的周期變成了正常的兩倍(2ms)(3)最后一個(gè)數(shù)據(jù)跑到最前邊了。

生氣ing…

于是開始調(diào)bug,第一個(gè)問題發(fā)現(xiàn)了,由于HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);函數(shù)中的發(fā)送數(shù)據(jù)指針是指向32位的,我的send_Buf也是定義的32位,但是DMA傳輸我的設(shè)置是半字16位,如圖示。

也就是從uint32_t *pData開始指針每移一位,地址偏移兩個(gè)字節(jié)。這樣就能解釋上面的第一二問題,因?yàn)橄M麄鬏數(shù)臄?shù)據(jù)是{sendBuf[0],sendBuf[1], …,sendBuf[20]},實(shí)際傳輸?shù)臄?shù)據(jù)卻是:sendBuf[0]低16位sendBuf[0]高16位(即0)…sendBuf[9]低16位sendBuf[9]高16位(即0)sendBuf[10]低16位

接下來修改傳輸字寬為Word(32)位,或者把send_Buf改為uint16_t型,經(jīng)測試結(jié)果都對(duì)了,如圖所示。所以提醒朋友們,DMA傳輸位寬和定義的緩沖區(qū)位寬一定要一致!!DMA傳輸位寬和定義的緩沖區(qū)位寬一定要一致??!DMA傳輸位寬和定義的緩沖區(qū)位寬一定要一致!!

問題3依然存在,于是把最后一個(gè)數(shù)據(jù)改為0試試,添加send_Buf[NUM - 1] = 0;波形正常了。

原因尚不清楚,是不是因?yàn)镈MA傳輸?shù)钠鹗己徒Y(jié)束有什么不穩(wěn)定因素,既然如此,那就每次在正常數(shù)據(jù)后面補(bǔ)一個(gè)或者多個(gè)0就行了。不影響使用。

至此DMA控制PWM輸出成功,下一篇用HAL-DMA-PWM點(diǎn)亮WS2812燈珠。

全部回復(fù)(5)
正序查看
倒序查看
ruohan
LV.9
2
2022-05-18 09:33

繼續(xù)啊,

0
回復(fù)
ruohan
LV.9
3
2022-05-20 10:08
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM);

這句是不是可以不用
(uint32_t*)send_Buf強(qiáng)制轉(zhuǎn)換啊,,,數(shù)組本身就是指針類型的,,
0
回復(fù)
2022-05-25 14:59
@ruohan
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM);這句是不是可以不用(uint32_t*)send_Buf強(qiáng)制轉(zhuǎn)換啊,,,數(shù)組本身就是指針類型的,,

可以的,這個(gè)我實(shí)測了,最后一個(gè)數(shù)據(jù)跑到最前邊了,沒找到問題

0
回復(fù)
ruohan
LV.9
5
2022-05-26 08:00
@lihui710884923
可以的,這個(gè)我實(shí)測了,最后一個(gè)數(shù)據(jù)跑到最前邊了,沒找到問題
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM+1);
發(fā)的數(shù)改成NUM+1也可以把最后一個(gè)調(diào)整回來,
原因是不是最后一個(gè)數(shù)配給定時(shí)器后,中斷把
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_3);這個(gè)指令執(zhí)行了,造成最后一個(gè)沒有發(fā)出來,而經(jīng)過WHILE(1)循環(huán)執(zhí)行HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM);的時(shí)候,就把之前的那個(gè)比較寄存器的值開始跑了,,,,
0
回復(fù)
2022-07-22 13:39
@ruohan
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM+1);發(fā)的數(shù)改成NUM+1也可以把最后一個(gè)調(diào)整回來,原因是不是最后一個(gè)數(shù)配給定時(shí)器后,中斷把HAL_TIM_PWM_Stop_DMA(&htim2,TIM_CHANNEL_3);這個(gè)指令執(zhí)行了,造成最后一個(gè)沒有發(fā)出來,而經(jīng)過WHILE(1)循環(huán)執(zhí)行HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_3,(uint32_t*)send_Buf,NUM);的時(shí)候,就把之前的那個(gè)比較寄存器的值開始跑了,,,,

具體不太清楚,不應(yīng)該有這個(gè)bug啊

 

0
回復(fù)
發(fā)