標(biāo)準(zhǔn)庫3.5實(shí)現(xiàn):
《嵌入式-STM32開發(fā)指南》第二部分 基礎(chǔ)篇 - 第7章 DMA
7.1 DMA工作原理
7.1.1 DMA介紹
DMA (Direct Memory Access孩哑,直接存儲器存刃否)察净,是一種可以大大減輕 CPU 工作量的數(shù)據(jù)存取方式,DMA傳輸將數(shù)據(jù)從一個地址空間復(fù)制到另一個地址空間费坊,提供在外設(shè)和存儲器之間或者存儲器和存儲器之間的高速數(shù)據(jù)傳輸,因而被廣泛地使用旬痹。早在 8086 的應(yīng)用中就已經(jīng)有 Intel 的 8237 這種典型的 DMA 控制器附井,而 STM32 的 DMA 則是以類似外設(shè)的形式添加到 Cortex 內(nèi)核之外的。
在硬件系統(tǒng)中两残,主要由 CPU(內(nèi)核)永毅、外設(shè)、內(nèi)存(SRAM)人弓、總線等結(jié)構(gòu)組成沼死,系統(tǒng)運(yùn)作的核心就是CPU,CPU無時不刻的在處理著大量的事務(wù)崔赌,但有些事情卻沒有那么重要意蛀,比方說數(shù)據(jù)的復(fù)制和存儲數(shù)據(jù),數(shù)據(jù)經(jīng)常要在內(nèi)存與外設(shè)之間轉(zhuǎn)移健芭,或從外設(shè) A 轉(zhuǎn)移到外設(shè) B浸间。例如:當(dāng) CPU 需要處理由 ADC 外設(shè)采集回來的數(shù)據(jù)時,CPU 首先要把數(shù)據(jù)從 ADC外設(shè)的寄存器讀取到內(nèi)存中(變量)吟榴,然后進(jìn)行運(yùn)算處理魁蒜,這是一般的處理方法。
DMA數(shù)據(jù)傳輸主要涉及三種情況的數(shù)據(jù)傳輸,但本質(zhì)上是一樣的兜看,都是從內(nèi)存的某一區(qū)域傳輸?shù)絻?nèi)存的另一區(qū)域(外設(shè)的數(shù)據(jù)寄存器本質(zhì)上就是內(nèi)存的一個存儲單元)锥咸。三種情況的數(shù)據(jù)傳輸分別時:外設(shè)到內(nèi)存、內(nèi)存到外設(shè)细移、內(nèi)存到內(nèi)存搏予。
7.1.2 STM32F1 的DMA主要特征
STM32F1有兩個DMA控制器,一共有12個通道(DMA1有7個通道弧轧,DMA2有5個通道)雪侥,每個通道專門用來管理來自于一個或多個外設(shè)對存儲器訪問的請求。還有一個仲裁器來協(xié)調(diào)各個DMA請求的優(yōu)先權(quán)精绎。
● 12個獨(dú)立的可配置的通道(請求):DMA1有7個通道速缨,DMA2有5個通道
● 每個通道都直接連接專用的硬件DMA請求,每個通道都同樣支持軟件觸發(fā)代乃。這些功能通過軟件來配置旬牲。
● 在同一個DMA模塊上,多個請求間的優(yōu)先權(quán)可以通過軟件編程設(shè)置(共有四級:很高搁吓、高原茅、中等和低),優(yōu)先權(quán)設(shè)置相等時由硬件決定(請求0優(yōu)先于請求1堕仔,依此類推) 擂橘。
● 獨(dú)立數(shù)據(jù)源和目標(biāo)數(shù)據(jù)區(qū)的傳輸寬度(字節(jié)、半字摩骨、全字)贝室,模擬打包和拆包的過程。源和目標(biāo)地址必須按數(shù)據(jù)傳輸寬度對齊仿吞。
● 支持循環(huán)的緩沖器管理
● 每個通道都有3個事件標(biāo)志(DMA半傳輸滑频、DMA傳輸完成和DMA傳輸出錯),這3個事件標(biāo)志邏輯或成為一個單獨(dú)的中斷請求唤冈。
● 存儲器和存儲器間的傳輸
● 外設(shè)和存儲器峡迷、存儲器和外設(shè)之間的傳輸
● 閃存、SRAM你虹、外設(shè)的SRAM绘搞、APB1 、APB2和AHB外設(shè)均可作為訪問的源和目標(biāo)傅物。
● 可編程的數(shù)據(jù)傳輸數(shù)目:最大為65535
7.1.3 STM32F1 的DMA請求映像
從外設(shè)(TIMx[x=1夯辖、 2、 3董饰、 4]蒿褂、 ADC1圆米、 SPI1、 SPI/I2S2啄栓、 I2Cx[x=1娄帖、 2]和USARTx[x=1、 2昙楚、 3])產(chǎn)生的7個請求近速,通過邏輯或輸入到DMA1控制器,這意味著同時只能有一個請求有效堪旧。參見下圖的DMA1請求映像削葱。
外設(shè)的DMA請求,可以通過設(shè)置相應(yīng)外設(shè)寄存器中的控制位淳梦,被獨(dú)立地開啟或關(guān)閉析砸。
從外設(shè)(TIMx[5、 6谭跨、 7干厚、 8]李滴、 ADC3螃宙、 SPI/I2S3、 UART4所坯、 DAC通道1谆扎、 2和SDIO)產(chǎn)生的5個請求,經(jīng)邏輯或輸入到DMA2控制器芹助,這意味著同時只能有一個請求有效堂湖。參見上圖的DMA2請求映像。
要使用 DMA状土,需要確定一系列的控制參數(shù)无蜂,如外設(shè)數(shù)據(jù)的地址、內(nèi)存地址蒙谓、傳輸方向等斥季,在開啟 DMA 傳輸前還要先發(fā)出 DMA 請求。外設(shè)的DMA請求累驮,可以通過設(shè)置相應(yīng)外設(shè)寄存器中的DMA控制位酣倾,被獨(dú)立地開啟或關(guān)閉。
注意: DMA2控制器及相關(guān)請求僅存在于大容量產(chǎn)品和互聯(lián)型產(chǎn)品中谤专。
7.1.4 STM32F1 的DMA工作過程
如圖4所示為躁锡,STM32F1的DMA的系統(tǒng)框圖。
我們可以看到STM32內(nèi)核置侍,存儲器映之,外設(shè)及DMA的連接拦焚,這些硬件最終通過各種各樣的線連接到總線矩陣中,硬件結(jié)構(gòu)之間的數(shù)據(jù)轉(zhuǎn)移都經(jīng)過總線矩陣的協(xié)調(diào)惕医,使各個外設(shè)和諧的使用總線來傳輸數(shù)據(jù)耕漱。
如果不使用DMA,CPU傳輸數(shù)據(jù)還要以內(nèi)核作為中轉(zhuǎn)站抬伺,比如要將USART1的數(shù)據(jù)轉(zhuǎn)移到到SRAM中螟够,這個過程是這樣的:
第一步:內(nèi)核通過DCode經(jīng)過總線矩陣協(xié)調(diào),從獲取AHB存儲的外設(shè)USART1的數(shù)據(jù)峡钓。
第二步:內(nèi)核再通過DCode經(jīng)過總線矩陣協(xié)調(diào)把數(shù)據(jù)存放到內(nèi)存SRAM中妓笙。
如果使用DMA的話,數(shù)據(jù)傳輸需要以下步驟:
1.DMA傳輸時外設(shè)對DMA控制器發(fā)出請求能岩。DMA控制器收到請求寞宫,觸發(fā)DMA工作。
2.DMA控制器從AHB外設(shè)獲取USART1的數(shù)據(jù)拉鹃,存儲到DMA通道中
3.DMA控制器的DMA總線與總線矩陣協(xié)調(diào)辈赋,使用AHB把外設(shè)USART1的數(shù)據(jù)經(jīng)由DMA通道存放到
SRAM中,這個數(shù)據(jù)的傳輸過程中膏燕,完全不需要內(nèi)核的參與钥屈,也就是不需要CPU的參與。
在發(fā)生一個事件后坝辫,外設(shè)向DMA控制器發(fā)送一個請求信號篷就。DMA控制器根據(jù)通道的優(yōu)先權(quán)處理請求。當(dāng)DMA控制器開始訪問發(fā)出請求的外設(shè)時近忙,DMA控制器立即發(fā)送給它一個應(yīng)答信號竭业。當(dāng)從DMA控制器得到應(yīng)答信號時,外設(shè)立即釋放它的請求及舍。一旦外設(shè)釋放了這個請求未辆,DMA控制器同時撤銷應(yīng)答信號。DMA傳輸結(jié)束锯玛,如果有更多的請求時咐柜,外設(shè)可以啟動下一個周期。
總之更振,每次DMA傳送由3個操作組成:
1.從外設(shè)數(shù)據(jù)寄存器或者從當(dāng)前外設(shè)/存儲器地址寄存器指示的存儲器地址取數(shù)據(jù)炕桨,第一次傳輸時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設(shè)基地址或存儲器單元;
2.存數(shù)據(jù)到外設(shè)數(shù)據(jù)寄存器或者當(dāng)前外設(shè)/存儲器地址寄存器指示的存儲器地址肯腕,第一次傳輸時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設(shè)基地址或存儲器單元献宫;
3.執(zhí)行一次DMA_CNDTRx寄存器的遞減操作,該寄存器包含未完成的操作數(shù)目实撒。
好了姊途,接下來我們看看如何實(shí)現(xiàn)DMA進(jìn)行數(shù)據(jù)傳輸涉瘾。
7.2 STM32Cube生成工程
和以前一樣,還是使用GPIO流程燈的工程進(jìn)行修改捷兰,當(dāng)然也可重新新建工程立叛。主要配置項(xiàng)如下。
1.配置RCC
RCC和以前一樣贡茅,使用設(shè)置高速外部時鐘HSE秘蛇。
2.配置串口
本文使用USART1,配置如下顶考。設(shè)置MODE為異步通信(Asynchronous)赁还。基礎(chǔ)參數(shù):波特率為115200 Bits/s驹沿。傳輸數(shù)據(jù)長度為8 Bit艘策。奇偶檢驗(yàn)無,停止位1 接收和發(fā)送都使能渊季。
設(shè)置GPIO引腳自動設(shè)置 USART1_RX/USART_TX朋蔫,默認(rèn)即可。
另外還要使能USART中斷却汉,在NVIC Settings 一欄使能接收中斷驯妄。
根據(jù)DMA通道預(yù)覽可以知道,我們用的USART1 的TX RX 分別對應(yīng)DMA1 的通道4和通道5病涨。
點(diǎn)擊DMASettings 點(diǎn)擊 Add 添加通道富玷,選擇USART_RX USART_TX 傳輸速率設(shè)置為中速,DMA傳輸模式為正常模式,DMA內(nèi)存地址自增楷拳,每次增加一個Byte(字節(jié))坦弟。
【注1】DMA傳輸方式
方法1:DMA_Mode_Normal,正常模式狸膏,
當(dāng)一次DMA數(shù)據(jù)傳輸完后,停止DMA傳送 ,也就是只傳輸一次
方法2:DMA_Mode_Circular 囊颅,循環(huán)傳輸模式
當(dāng)傳輸結(jié)束時,硬件自動會將傳輸數(shù)據(jù)量寄存器進(jìn)行重裝傅瞻,進(jìn)行下一輪的數(shù)據(jù)傳輸踢代。也就是多次傳輸模式。
Channel DMA傳輸通道設(shè)置
- DMA1 : DMA1 Channel 0~DMA1 Channel 7
- DMA2: DMA2 Channel 1~DMA1 Channel 5
【注2】DMA的傳輸方向
DMA的傳輸方向在前文也已經(jīng)說過了嗅骄,對應(yīng)于STM32cubeMX的三種傳輸配置如下:外設(shè)到內(nèi)存 Peripheral To Memory胳挎,內(nèi)存到外設(shè) Memory To Peripheral,內(nèi)存到內(nèi)存 Memory To Memory溺森。
【注3】指針遞增模式
Src Memory 表示外設(shè)地址寄存器
功能:設(shè)置傳輸數(shù)據(jù)的時候外設(shè)地址是不變還是遞增慕爬。如果設(shè)置 為遞增窑眯,那么下一次傳輸?shù)臅r候地址加 Data Width個字節(jié),
Dst Memory 表示內(nèi)存地址寄存器
功能:設(shè)置傳輸數(shù)據(jù)時候內(nèi)存地址是否遞增医窿。如果設(shè)置 為遞增磅甩,那么下一次傳輸?shù)臅r候地址加 Data Width個字節(jié),這個Src Memory一樣姥卢,只不過針對的是內(nèi)存卷要。
串口發(fā)送數(shù)據(jù)是將數(shù)據(jù)不斷存進(jìn)固定外設(shè)地址串口的發(fā)送數(shù)據(jù)寄存器(USARTx_TDR)。所以外設(shè)的地址是不遞增独榴。而內(nèi)存儲器存儲的是要發(fā)送的數(shù)據(jù)却妨,所以地址指針要遞增,保證數(shù)據(jù)依次被發(fā)出括眠。串口數(shù)據(jù)發(fā)送寄存器只能存儲8bit彪标,每次發(fā)送一個字節(jié),所以數(shù)據(jù)長度選擇Byte掷豺。
3.DMA配置
右側(cè)點(diǎn)擊System Core 點(diǎn)擊DMA捞烟。
注意:上圖顯示了我們剛才添加的串口外設(shè),如果你是在DMA設(shè)置界面添加DMA 而沒有開啟對應(yīng)外設(shè)的話 当船,默認(rèn)為MENTOMEN题画,也只有此選擇。如下圖所示德频。
4.時鐘源設(shè)置
筆者使用外部8M時鐘源苍息,DMA1掛在AHP上的,USART1是掛在APB2上的壹置。具體配置如下竞思。
其他默認(rèn)即可,然后點(diǎn)擊GENERATE CODE 創(chuàng)建工程
7.3 DMA具體代碼分析
7.3.1 USART+ DMA數(shù)據(jù)發(fā)送
在具體實(shí)現(xiàn)實(shí)現(xiàn)代碼之前钞护,先看看USART1+DMA發(fā)送數(shù)據(jù)流程盖喷,如下圖示所示。我們直接通過DMA通道將要發(fā)送的數(shù)據(jù)放入串口發(fā)送寄存器难咕,數(shù)據(jù)經(jīng)過串口發(fā)送到數(shù)據(jù)接收設(shè)備课梳,筆者使用的是上位機(jī)接收。
在分析代碼之前余佃,先看看USART+ DMA數(shù)據(jù)發(fā)送是如何實(shí)現(xiàn)的暮刃,打開工程,我們新建一個變量爆土。
uint8_t sendBuff[] = "USART test by DMA\r\n";
然后在man.c中的主循環(huán)添加以下代碼:
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)sendBuff, sizeof(sendBuff));
HAL_Delay(1000);
然后編譯椭懊,將程序編譯好后下載到板子中,通過串口助手可以看到在接收區(qū)有數(shù)據(jù)不斷的打印輸出雾消,同時LED1不停閃爍灾搏。
【注】在本例中串口是DMA操作的挫望,而LED的閃爍是CPU控制,請讀者朋友注意狂窑。
下面講講代碼媳板,流程圖見上圖14。通過DMA+USART數(shù)據(jù)發(fā)送和直接使用USART整體流程編程差不多泉哈,主要在于串口的初始化配置不同蛉幸,數(shù)據(jù)發(fā)送函數(shù)不同〈曰蓿可以和上一章比較奕纫。DMA+USART流程如下:
1.初始化硬件,配置時鐘
2.GPIO初始化(僅僅LED)烫沙,DMA初始化匹层,串口初始化參數(shù)配置(USART的RX和TX);
3.填充數(shù)據(jù)锌蓄,發(fā)送數(shù)據(jù)升筏。
這里主要關(guān)注DMA初始化,串口初始化參數(shù)配置瘸爽,以下函數(shù)都是STM32cudeMX自動生成的您访,函數(shù)如下:
MX_DMA_Init();
MX_USART1_UART_Init();
先看MX_DMA_Init()函數(shù),函數(shù)原型如下:
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel4_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
/* DMA1_Channel5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}
很簡單剪决,主要打開DMA1的通道4和5的中斷開關(guān)灵汪,也就是串口的RX和TX。
再看看MX_USART1_UART_Init()函數(shù)柑潦。函數(shù)原型如下:
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
前面就不說了享言,在上一章已經(jīng)交接過了,這里重點(diǎn)講解HAL_UART_Init()函數(shù)
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
/* Check the UART handle allocation */
if (huart == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
{
/* The hardware flow control is available only for USART1, USART2 and USART3 */
assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
}
else
{
assert_param(IS_UART_INSTANCE(huart->Instance));
}
assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
#if defined(USART_CR1_OVER8)
assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
#endif /* USART_CR1_OVER8 */
if (huart->gState == HAL_UART_STATE_RESET)
{
/* Allocate lock resource and initialize it */
huart->Lock = HAL_UNLOCKED;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
UART_InitCallbacksToDefault(huart);
if (huart->MspInitCallback == NULL)
{
huart->MspInitCallback = HAL_UART_MspInit;
}
/* Init the low level hardware */
huart->MspInitCallback(huart);
#else
/* Init the low level hardware : GPIO, CLOCK */
HAL_UART_MspInit(huart);
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
}
huart->gState = HAL_UART_STATE_BUSY;
/* Disable the peripheral */
__HAL_UART_DISABLE(huart);
/* Set the UART Communication parameters */
UART_SetConfig(huart);
/* In asynchronous mode, the following bits must be kept cleared:
- LINEN and CLKEN bits in the USART_CR2 register,
- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));
/* Enable the peripheral */
__HAL_UART_ENABLE(huart);
/* Initialize the UART state */
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_READY;
huart->RxState = HAL_UART_STATE_READY;
return HAL_OK;
}
重點(diǎn)關(guān)注HAL_UART_MspInit()函數(shù)妒茬,
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 DMA Init */
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_NORMAL;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(huart,hdmatx,hdma_usart1_tx);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
這里就是DMA的USART初始化担锤。有個重要的結(jié)構(gòu)體:DMA_HandleTypeDef蔚晨。
typedef struct __DMA_HandleTypeDef
{
DMA_Channel_TypeDef *Instance; /*!< Register base address */
DMA_InitTypeDef Init; /*!< DMA communication parameters */
HAL_LockTypeDef Lock; /*!< DMA locking object */
HAL_DMA_StateTypeDef State; /*!< DMA transfer state */
void *Parent; /*!< Parent object state */
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete callback */
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback */
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer error callback */
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer abort callback */
__IO uint32_t ErrorCode; /*!< DMA Error code */
DMA_TypeDef *DmaBaseAddress; /*!< DMA Channel Base Address */
uint32_t ChannelIndex; /*!< DMA Channel Index */
} DMA_HandleTypeDef;
? Instance:DMA通道配置乍钻,也就是寄存器基地址;
? Init:也是一個結(jié)構(gòu)體铭腕,主要初始化DMA通訊參數(shù)银择,后面會有講解;
? Lock:對資源操作增加操作鎖累舷;
? State:DMA的傳輸狀態(tài)浩考,通過設(shè)置不同的狀態(tài)設(shè)置,實(shí)現(xiàn)傳輸方式被盈;
? 剩余幾個參數(shù)是回調(diào)的指針和變量析孽,用于實(shí)現(xiàn)回調(diào)函數(shù)搭伤。
再看看DMA_InitTypeDef結(jié)構(gòu)體,其原型如下袜瞬;
typedef struct
{
uint32_t Direction; /*!< Specifies if the data will be transferred from memory to peripheral,
from memory to memory or from peripheral to memory.
This parameter can be a value of @ref DMA_Data_transfer_direction */
uint32_t PeriphInc; /*!< Specifies whether the Peripheral address register should be incremented or not.
This parameter can be a value of @ref DMA_Peripheral_incremented_mode */
uint32_t MemInc; /*!< Specifies whether the memory address register should be incremented or not.
This parameter can be a value of @ref DMA_Memory_incremented_mode */
uint32_t PeriphDataAlignment; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_Peripheral_data_size */
uint32_t MemDataAlignment; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_Memory_data_size */
uint32_t Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_mode
@note The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
uint32_t Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_Priority_level */
} DMA_InitTypeDef;
? Direction:DMA傳輸方向怜俐,有三種,外設(shè)到存儲器邓尤,存儲器到外設(shè)拍鲤,存儲器到存儲器,根據(jù)工程要求來配置汞扎,這里選擇DMA_MEMORY_TO_PERIPH參數(shù)季稳;
? PeriphInc:配置外設(shè)地址寄存器是否自動增加,這里配置為不遞增澈魄;
? MemInc:配置內(nèi)存地址自動增加景鼠,一般都是使能自動增加;
? PeriphDataAlignment:外設(shè)數(shù)據(jù)長度痹扇,分別有字節(jié)莲蜘、字、半字帘营,這里配置為字票渠。
? MemDataAlignment:內(nèi)存數(shù)據(jù)長度,和外設(shè)的類似芬迄;
? Mode:配置傳輸模式问顷,這里是常規(guī)模式。
? Priority:優(yōu)先權(quán)禀梳,筆者配置的為中優(yōu)先級杜窄。
以上的結(jié)構(gòu)體配置參數(shù)就是通過STM32cudeMX配置得到的,和前文是的配置是一一對應(yīng)的算途。DMA+USART1的數(shù)據(jù)發(fā)送就到這里了塞耕,如果還有什么迷惑,請讀者朋友結(jié)合USART1 發(fā)送數(shù)據(jù)流程與代碼在仔細(xì)琢磨吧嘴瓤。
7.3.2 USART+ DMA數(shù)據(jù)接收
使用USART+DMA數(shù)據(jù)接收不需要再配置STM32cubeMX扫外,只是在發(fā)送的基礎(chǔ)上添加代碼即可。下面還是先將如何USART+ DMA數(shù)據(jù)接收實(shí)現(xiàn)廓脆。
在main.c添加如下變量:
uint8_t recvBuff; //接收數(shù)據(jù)緩存
在MX_USART1_UART_Init()函數(shù)添加以下代碼:
//DMA接收初始化函數(shù)筛谚,此句一定要加,不加接收不到第一次傳進(jìn)來的實(shí)數(shù)據(jù)停忿,是空的驾讲,且此時接收到的數(shù)據(jù)長度為緩存器的數(shù)據(jù)長度
HAL_UART_Receive_DMA(&huart1, &recvBuff, 1);
然后添加回調(diào)函數(shù)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
HAL_UART_Receive_DMA(&huart1, &recvBuff, 1);
HAL_UART_Transmit_DMA(&huart1, &recvBuff, 1);
}
然后將程序編譯好后下載到板子中,發(fā)送一個數(shù)據(jù)我們設(shè)置連續(xù)發(fā)送,如下圖所示吮铭。
這樣就完了嗎时迫,我們換個數(shù)據(jù)測試下。
有的朋友說谓晌,是我設(shè)置的接收數(shù)據(jù)是一個字符别垮,換一下就好了,那么你可以是試試換成2個或者多個試試扎谎。我這里就不展示了碳想,接下來筆者將帶領(lǐng)大家實(shí)現(xiàn)任意字符串接收并輸出。這里有種最簡單的方式毁靶,就是使用DMA+USART1接收數(shù)據(jù)胧奔,通過USART1的普通方式發(fā)送數(shù)據(jù)。
也就是將
HAL_UART_Transmit_DMA(&huart1, &recvBuff, 1);
換成
HAL_UART_Transmit(&huart1,&recvBuff,1,0);
然后編譯预吆,下載程序龙填,看下結(jié)果。
當(dāng)然拐叉,只要是實(shí)現(xiàn)了岩遗,怎樣都可以,筆者這章講的是DMA凤瘦,那么接收發(fā)送都得用吧宿礁,來吧,展示蔬芥!
在main.c中添加以下變量:
uint8_t recvBuff[BUFFER_SIZE] ; //接收數(shù)據(jù)緩存數(shù)組
volatile uint8_t recvLength = 0; //接收一幀數(shù)據(jù)的長度
volatile uint8_t recvDndFlag = 0; //一幀數(shù)據(jù)接收完成標(biāo)志
在函數(shù)添加以下代碼:
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);
在main.h中添加以下宏定義與變量:
#define BUFFER_SIZE 16
extern uint8_t recvBuff[BUFFER_SIZE]; //接收數(shù)據(jù)緩存
extern volatile uint8_t recvLength; //接收一幀數(shù)據(jù)的長度
extern volatile uint8_t recvDndFlag; //一幀數(shù)據(jù)接收完成標(biāo)志
接下來就是重點(diǎn)梆靖,我們使用IDLE 接收空閑中斷+DMA接收數(shù)據(jù),然后發(fā)送數(shù)據(jù)笔诵。STM32的IDLE的中斷產(chǎn)生條件:在串口無數(shù)據(jù)接收的情況下返吻,不會產(chǎn)生,當(dāng)清除IDLE標(biāo)志位后乎婿,必須有接收到第一個數(shù)據(jù)后测僵,才開始觸發(fā),一但接收的數(shù)據(jù)斷流谢翎,沒有接收到數(shù)據(jù)捍靠,即產(chǎn)生IDLE中斷。我們修改USART1_IRQHandler()函數(shù):
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t tmpFlag = 0;
uint32_t temp;
tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //獲取IDLE標(biāo)志位
if((tmpFlag != RESET))//idle標(biāo)志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標(biāo)志位
HAL_UART_DMAStop(&huart1); //
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸?shù)臄?shù)據(jù)個數(shù)
recvLength = BUFFER_SIZE - temp; //總計(jì)數(shù)減去未傳輸?shù)臄?shù)據(jù)個數(shù)岳服,得到已經(jīng)接收的數(shù)據(jù)個數(shù)
recvDndFlag = 1; // 接受完成標(biāo)志位置1
HAL_UART_Transmit_DMA(&huart1, recvBuff, recvLength);
recvLength = 0;//清除計(jì)數(shù)
recvDndFlag = 0;//清除接收結(jié)束標(biāo)志位
memset(recvBuff,0,recvLength);
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打開DMA接收剂公,不然只能接收一次數(shù)據(jù)
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
好了,編譯下吊宋,重新將程序下載到板子里。
現(xiàn)在就完美了,當(dāng)然在實(shí)際工程中璃搜,最基本的就是要實(shí)現(xiàn)功能拖吼,然后再談效率。最后再總結(jié)下USART+ DMA數(shù)據(jù)接收過程这吻,和USART中斷接收流程差不多吊档,只是這里增加了DMA,流程圖我看的畫了唾糯。這里重點(diǎn)將講解STM32 的IDLE中斷怠硼,也就是STM32的接收不定長度字節(jié)數(shù)據(jù)的方法。由于STM32單片機(jī)帶IDLE中斷移怯,所以利用這個中斷香璃,可以接收不定長字節(jié)的數(shù)據(jù)。
IDLE中斷什么時候發(fā)生舟误?IDLE就是串口收到一幀數(shù)據(jù)后葡秒,發(fā)生的中斷。什么是一幀數(shù)據(jù)呢嵌溢?比如說給單片機(jī)一次發(fā)來1個字節(jié)眯牧,或者一次發(fā)來8個字節(jié),這些一次發(fā)來的數(shù)據(jù)赖草,就稱為一幀數(shù)據(jù)学少,也可以叫做一包數(shù)據(jù)。關(guān)于STM32F1的IDLE可以看參考手冊的27.6.4節(jié)的內(nèi)容秧骑。
這是串口CR1寄存器旱易,其中,對bit4寫1開啟IDLE中斷,對bit5寫1開啟接收數(shù)據(jù)中斷腿堤。(注意:不同系列的STM32阀坏,對應(yīng)的寄存器位可能不同,清理中斷標(biāo)志位的方法也不同笆檀,具看參考手冊)
關(guān)于更多IDLE中斷忌堂,有時間再講吧,今天就到這里了酗洒。
代碼獲取方式
1.關(guān)注公眾號[嵌入式實(shí)驗(yàn)樓]
2.在公眾號回復(fù)關(guān)鍵詞[STM32F1]獲取資料
歡迎訪問我的網(wǎng)站:
BruceOu的嗶哩嗶哩
BruceOu的主頁
BruceOu的博客
BruceOu的CSDN博客
BruceOu的簡書