《嵌入式-STM32開發(fā)指南》第二部分 基礎(chǔ)篇 - 第7章DMA(HAL庫)

標(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)算處理魁蒜,這是一般的處理方法。

圖1 DMA數(shù)據(jù)傳輸示意圖

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)閉析砸。

表1 各個通道的DMA1通道
圖2 DMA1請求映射
表2 各個通道的DMA2通道
圖3 DMA2請求映射

從外設(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)框圖。

圖4 STM32F1 DMA框圖

我們可以看到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中妓笙。

圖5 STM32F1不使用DMA工作工作過程

如果使用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的參與。

圖6 STM32F1使用DMA工作工作過程

在發(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

圖7 RCC配置

RCC和以前一樣贡茅,使用設(shè)置高速外部時鐘HSE秘蛇。

2.配置串口

本文使用USART1,配置如下顶考。設(shè)置MODE為異步通信(Asynchronous)赁还。基礎(chǔ)參數(shù):波特率為115200 Bits/s驹沿。傳輸數(shù)據(jù)長度為8 Bit艘策。奇偶檢驗(yàn)無,停止位1 接收和發(fā)送都使能渊季。

圖8 USART配置

設(shè)置GPIO引腳自動設(shè)置 USART1_RX/USART_TX朋蔫,默認(rèn)即可。

圖9 USART1 GPIO配置

另外還要使能USART中斷却汉,在NVIC Settings 一欄使能接收中斷驯妄。

圖10 USART1 中斷使能

根據(jù)DMA通道預(yù)覽可以知道,我們用的USART1 的TX RX 分別對應(yīng)DMA1 的通道4和通道5病涨。

圖11 USART1 DMA配置

點(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捞烟。

圖12 DMA配置界面

注意:上圖顯示了我們剛才添加的串口外設(shè),如果你是在DMA設(shè)置界面添加DMA 而沒有開啟對應(yīng)外設(shè)的話 当船,默認(rèn)為MENTOMEN题画,也只有此選擇。如下圖所示德频。

圖13 DMA add界面

4.時鐘源設(shè)置
筆者使用外部8M時鐘源苍息,DMA1掛在AHP上的,USART1是掛在APB2上的壹置。具體配置如下竞思。

圖14 時鐘配置

其他默認(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ī)接收。

圖15 USART1 發(fā)送數(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不停閃爍灾搏。

圖16 USART1 發(fā)送數(shù)據(jù)實(shí)驗(yàn)現(xiàn)象


【注】在本例中串口是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ā)送,如下圖所示吮铭。

圖17DMA+USART1接收數(shù)據(jù)


這樣就完了嗎时迫,我們換個數(shù)據(jù)測試下。

圖18 DMA+USART1接收數(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é)果。

圖19 DMA+USART1接收數(shù)據(jù)

當(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 */
}

好了,編譯下吊宋,重新將程序下載到板子里。

圖20 DMA+USART1接收數(shù)據(jù)


現(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)容秧骑。

USART_CRT寄存器

這是串口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的簡書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末士修,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子樱衷,更是在濱河造成了極大的恐慌棋嘲,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩桂,死亡現(xiàn)場離奇詭異沸移,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門雹锣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來网沾,“玉大人,你說我怎么就攤上這事蕊爵』愿纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵攒射,是天一觀的道長醋旦。 經(jīng)常有香客問我,道長会放,這世上最難降的妖魔是什么饲齐? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮鸦概,結(jié)果婚禮上箩张,老公的妹妹穿的比我還像新娘。我一直安慰自己窗市,他們只是感情好先慷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咨察,像睡著了一般论熙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摄狱,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天脓诡,我揣著相機(jī)與錄音,去河邊找鬼媒役。 笑死祝谚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酣衷。 我是一名探鬼主播交惯,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼穿仪!你這毒婦竟也來了席爽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啊片,失蹤者是張志新(化名)和其女友劉穎只锻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體紫谷,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡齐饮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年捐寥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沈矿。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡上真,死狀恐怖咬腋,靈堂內(nèi)的尸體忽然破棺而出羹膳,到底是詐尸還是另有隱情,我是刑警寧澤根竿,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布陵像,位于F島的核電站,受9級特大地震影響寇壳,放射性物質(zhì)發(fā)生泄漏醒颖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一壳炎、第九天 我趴在偏房一處隱蔽的房頂上張望泞歉。 院中可真熱鬧,春花似錦匿辩、人聲如沸腰耙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挺庞。三九已至,卻和暖如春稼病,著一層夾襖步出監(jiān)牢的瞬間选侨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工然走, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留援制,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓芍瑞,卻偏偏與公主長得像晨仑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啄巧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355