久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 3255|回復: 0
收起左側

STM32串口DMA很難?帶你一步步學會。ㄖv解+代碼)

  [復制鏈接]
ID:736383 發表于 2020-5-15 01:40 | 顯示全部樓層 |閱讀模式
串口DMA

DMA利用好無疑會讓串口使用起來更加高效,同時CPU還能處理自己的事情,但是DMA的使用卻讓代碼變得更加的復雜,也使串口配置變得更加麻煩!麻煩???也許只是學習的方式不對,代碼是變長了,但是思路清晰的話,也就是在原來串口加了點東西而已。本文讓你輕輕松松學會串口DMA!定長數據傳輸與不定長數據傳輸都教會你!通過此文,希望能讓更多人了解和會使用串口DMA,希望大家多多支持!

一、串口DMA的配置

本文針對串口2(USART2)如何進行DMA傳輸進行講解,如果采用其他串口,注意要修改相應端口配置以及DMA通道。

1、串口的DMA請求映像

通過我之前的博文《 STM32 | DMA配置和使用如此簡單(超詳細)》可以知道,串口2(USART2)的接收(USART2_RX)和發送(USART2_TX)分別在DMA1控制器的通道6和通道7。下圖為DMA1控制器的請求映像圖。

2、資源說明

STM32F1中,USART2使用的是PA2(USART2_TX)和PA3(USART2_RX),為了方便閱讀,使用到的資源列在下表。

     外設           GPIO口      DMA請求映像通道         備注

USART2_TX        PA2             DMA1通道7        RAM->USART2的數據傳輸

USART2_RX        PA3             DMA1通道6        USART2->RAM的數據傳

3、DMA初始化配置

USART2的DMA配置在《 STM32 | DMA配置和使用如此簡單(超詳細)》中分別講了庫函數版和寄存器版兩種配置,我這里直接搬用,不理解的可以看看《 STM32 | DMA配置和使用如此簡單(超詳細)》這篇文章。要注意的是庫函數最大優勢就是便于閱讀,所以在DMA初始化配置中我們使用庫函數。

首先,我們要先定義三個緩沖區(作全局定義),一個發送緩沖區,兩個接收緩沖區,兩個接收緩沖區是為了做雙緩沖區,目的是為了防止后一次傳輸的數據覆蓋前一次傳輸的數據,并且留出足夠的時間讓CPU處理緩沖區數據。雙緩沖在串口DMA中有著很重要的意義并起著很大的作用!

  1. //USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在頭文件進行了宏定義,分別指USART2最大發送長度和最大接收長度
  2. u8 USART2_TX_BUF[USART2_MAX_TX_LEN];          //發送緩沖,最大USART2_MAX_TX_LEN字節
  3. u8 u1rxbuf[USART2_MAX_RX_LEN];                        //發送數據緩沖區1
  4. u8 u2rxbuf[USART2_MAX_RX_LEN];                        //發送數據緩沖區2
  5. u8 witchbuf=0;                                                      //標記當前使用的是哪個緩沖區,0:使用u1rxbuf;1:使用u2rxbuf
  6. u8 USART2_TX_FLAG=0;                                        //USART2發送標志,啟動發送時置1
  7. u8 USART2_RX_FLAG=0;                                        //USART2接收標志,啟動接收時置
復制代碼
       要說明的是,實際上發送緩沖區可能用不上,因為我們要發送的數據內容和大小都不一定每次都是固定的,可以是變化的,我們只需重新指派新的發送緩沖區地址即可,但是在初始化中我們還是要指定一個發送緩沖區地址。
下面是DMA1_USART2的初始化函數

  1. void DMA1_USART2_Init(void)
  2. {
  3.         DMA_InitTypeDef DMA1_Init;
  4.         NVIC_InitTypeDef NVIC_InitStructure;
  5.         
  6.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);                            //使能DMA1時鐘

  7.         //DMA_USART2_RX  USART2->RAM的數據傳輸
  8.         DMA_DeInit(DMA1_Channel6);                                                                    //將DMA的通道6寄存器重設為缺省值
  9.         DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                       //啟動傳輸前裝入實際RAM地址
  10.         DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf;                                       //設置接收緩沖區首地址
  11.         DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC;                                          //數據傳輸方向,從外設讀取到內存
  12.         DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN;                                    //DMA通道的DMA緩存的大小
  13.         DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                //外設地址寄存器不變
  14.         DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                                        //內存地址寄存器遞增
  15.         DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                        //數據寬度為8位
  16.         DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                                        //數據寬度為8位
  17.         DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                                        //工作在正常模式
  18.         DMA1_Init.DMA_Priority = DMA_Priority_High;                                                         //DMA通道 x擁有高優先級
  19.         DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                                        //DMA通道x沒有設置為內存到內存傳輸
  20.          
  21.         DMA_Init(DMA1_Channel6,&DMA1_Init);                                                                         //對DMA通道6進行初始化
  22.         
  23.         //DMA_USART2_TX  RAM->USART2的數據傳輸
  24.         DMA_DeInit(DMA1_Channel7);                                                                                                //將DMA的通道7寄存器重設為缺省值
  25.         DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                                        //啟動傳輸前裝入實際RAM地址
  26.         DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF;                              //設置發送緩沖區首地址
  27.         DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST;                                                                 //數據傳輸方向,從內存發送到外設
  28.         DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN;                                                        //DMA通道的DMA緩存的大小
  29.         DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                //外設地址寄存器不變
  30.         DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                                        //內存地址寄存器遞增
  31.         DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                        //數據寬度為8位
  32.         DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                                        //數據寬度為8位
  33.         DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                                        //工作在正常模式
  34.         DMA1_Init.DMA_Priority = DMA_Priority_High;                                                         //DMA通道 x擁有高優先級
  35.         DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                                        //DMA通道x沒有設置為內存到內存傳輸

  36.         DMA_Init(DMA1_Channel7,&DMA1_Init);                                                                         //對DMA通道7進行初始化
  37.         
  38.         //DMA1通道6 NVIC 配置
  39.         NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;                                //NVIC通道設置
  40.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;                                //搶占優先級
  41.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                                                //子優先級
  42.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                        //IRQ通道使能
  43.         NVIC_Init(&NVIC_InitStructure);                                                                                        //根據指定的參數初始化NVIC寄存器

  44.         //DMA1通道7 NVIC 配置
  45.         NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;                                //NVIC通道設置
  46.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;                                //搶占優先級
  47.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                                                //子優先級
  48.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                       //IRQ通道使能
  49.         NVIC_Init(&NVIC_InitStructure);                                                                                        //根據指定的參數初始化NVIC寄存器

  50.         DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);                                                        //開USART2 Rx DMA中斷
  51.         DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);                                                        //開USART2 Tx DMA中斷

  52.         DMA_Cmd(DMA1_Channel6,ENABLE);                                                                           //使DMA通道6停止工作
  53.         DMA_Cmd(DMA1_Channel7,DISABLE);                                                                           //使DMA通道7停止工作
  54.          
  55.         USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);                                                //開啟串口DMA發送
  56.         USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);                                                //開啟串口DMA接收
  57. }
復制代碼
相應配置的講解在《 STM32 | DMA配置和使用如此簡單(超詳細)》中講解過,這里不再敘述,要注意的是,我們采用中斷方式進行DMA傳輸。正常情況下,我們傳輸完數據要進行相應的處理,那我們怎么知道什么時候數據傳輸完成了呢?這時候就借助DMA傳輸完成中斷。既然打開了中斷,那一定要注意中斷優先級,這里特別指出串口的中斷優先級應低于串口DMA通道的中斷優先級。

4、串口配置

前面已經完成了DMA的配置,而DMA是不能單獨使用的,所以不要忘了配置要用到的串口USART2。

  1. void Initial_UART2(unsigned long baudrate)
  2. {
  3.         //GPIO端口設置
  4.          GPIO_InitTypeDef GPIO_InitStructure;
  5.         USART_InitTypeDef USART_InitStructure;
  6.         NVIC_InitTypeDef NVIC_InitStructure;
  7.         
  8.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE);        //使能USART2,GPIOA時鐘
  9.         
  10.         //USART2_TX   GPIOA.2初始化
  11.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                                                                                //PA.2
  12.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                                                        //復用推挽輸出
  13.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                                                //GPIO速率50MHz
  14.         GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                        //初始化GPIOA.2
  15.         
  16.         //USART2_RX          GPIOA.3初始化
  17.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                                                                                //PA.3
  18.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                                                        //浮空輸入
  19.         GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                        //初始化GPIOA.3
  20.          
  21.         //USART 初始化設置
  22.         USART_InitStructure.USART_BaudRate = baudrate;                                                                        //串口波特率
  23.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;                                                //字長為8位數據格式
  24.         USART_InitStructure.USART_StopBits = USART_StopBits_1;                                                        //一個停止位
  25.         USART_InitStructure.USART_Parity = USART_Parity_No ;                                                        //無奇偶校驗位
  26.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;        //無硬件數據流控制
  27.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                                        //收發模式
  28.         USART_Init(USART2, &USART_InitStructure);                                                                                 //初始化串口2
  29.         
  30.         //中斷開啟設置
  31.         USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                                                                        //開啟檢測串口空閑狀態中斷
  32.         USART_ClearFlag(USART2,USART_FLAG_TC);                                                                                        //清除USART2標志位
  33.         
  34.         USART_Cmd(USART2, ENABLE);                                                                                                                //使能串口2
  35.         
  36.         NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;                                                                //NVIC通道設置
  37.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;                                                //搶占優先級
  38.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                                                                //響應優先級
  39.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                        //IRQ通道使能
  40.         NVIC_Init(&NVIC_InitStructure);                                                                                                        //根據指定的參數初始化NVIC寄存器
  41.         
  42.         DMA1_USART2_Init();                                                                                                                                //DMA1_USART2初始化
  43. }
復制代碼

可以注意到,我這里定義的串口中斷搶占優先級低于DMA中斷的搶占優先級。細心點可以看到,我開啟了串口空閑中斷。為什么呢?因為通常情況下我們是不知道接收數據的長度的,這樣我們是沒有辦法利用DMA傳輸完成標志位來判斷是否完成接收,所以我們這里采用串口空閑中斷來判斷數據是否接收完成,接收完成了會進入串口空閑中斷。串口配置的最后進行了串口2(USART2)DMA的初始化,這樣直接初始化串口也直接包括了串口DMA的初始化,主函數初始化只需一步即可。

二、串口DMA的使用

前面我們已經完成了串口的配置及DMA的配置,接下來講講怎么使用。


  • 發送數據:沒什么特殊的,我們只需指定DMA外設緩沖區首地址以及數據長度(即設置發送數據的地址)并開啟DMA1通道7即可。
  • 接收數據:接收串口來數據有兩種,一種是不定長數據(也就是每次接收到數據的長度都不一樣),另一種是定長數據(接收到的數據每次都相同)。對于定長數據我們只需檢測DMA傳輸完成標志位即可;而對于不定長數據,可以通過串口的空閑中斷來判斷數據是否接收完成。所以,接收完不定長的數據后,需要重新賦值計數值,這樣才可以進行下一次數據傳輸。

詳細的使用下文進行介紹。

1、發送數據

發送數據上有兩種形式,一種是以數組的形式發送,此情況下要知道數組有效元素的個數;另一種就是類似“printf”的形式,此形式可以基于第一種情況稍作修改。

(1)數組的形式發送數據

使用串口DMA發送數據,默認情況下我們要關閉DMA1通道7(即串口2的DMA發送通道),因為一旦開啟通道7就會開始發送數據,沒有要發送數據時自然是要關閉的。

發送數據步驟如下:


  • 判斷上一次發送數據是否完成,未完成就等待數據發送完成。有兩種方法,一種是直接判斷DMA傳輸完成標志位,另一種判斷我們自己定義全局變量USART2_TX_FLAG是否置1(即上一次發送未完成)。我更喜歡使用第二種方法。
  • 指定發送緩沖區首地址,也就是待發送數據的地址。
  • 指定待發送數據長度。
  • 開啟DMA通道7啟動DMA發送。
  • 產生DMA通道7傳輸完成中斷,清除中斷標志位。
  • 關閉DMA通道7,并清除USART2_TX_FLAG(清0)。

接下來給出串口DMA發送數據的代碼

  1. //DMA 發送應用源碼
  2. void DMA_USART2_Tx_Data(u8 *buffer, u32 size)
  3. {
  4.         while(USART2_TX_FLAG);                                                //等待上一次發送完成(USART2_TX_FLAG為1即還在發送數據)
  5.         USART2_TX_FLAG=1;                                                        //USART2發送標志(啟動發送)
  6.         DMA1_Channel7->CMAR  = (uint32_t)buffer;        //設置要發送的數據地址
  7.         DMA1_Channel7->CNDTR = size;                            //設置要發送的字節數目
  8.         DMA_Cmd(DMA1_Channel7, ENABLE);                                //開始DMA發送
  9. }

  10. //DMA1通道7中斷
  11. void DMA1_Channel7_IRQHandler(void)
  12. {
  13.         if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET)        //DMA接收完成標志
  14.         {
  15.                 DMA_ClearITPendingBit(DMA1_IT_TC7);         //清除中斷標志
  16.                 USART_ClearFlag(USART2,USART_FLAG_TC);        //清除串口2的標志位
  17.                 DMA_Cmd(DMA1_Channel7, DISABLE );           //關閉USART2 TX DMA1 所指示的通道
  18.                 USART2_TX_FLAG=0;                                                //USART2發送標志(關閉)
  19. }
  20. }
復制代碼

至此,串口DMA發送數據完成。細心點可以發現我這里居然直接配置寄存器,因為這樣簡單快捷,不用像庫函數那樣重新初始化DMA的配置。寄存器使用請查看《 STM32 | DMA配置和使用如此簡單(超詳細)》的寄存器那塊的內容。

(2)類似printf形式發送數據

上述學習,使用DMA_USART2_Tx_Data();函數可以很方便的發送一個數組的數據,但是有時候我們還是喜歡使用printf();,今天講串口DMA,當然要講到位,讓串口DMA也能實現強大的printf();功能。

—————————————————————。。。未完。。。————————————————————

論壇看起來太不舒服了,有的格式沒辦法設置,而且代碼對其很麻煩,直接復制過來卻不對齊,反正我是看著不舒服的,如果喜歡還是去CSDN博客看吧!

原文已在CSDN發布,博文閱讀起來更舒服,CSDN博文鏈接:

STM32 | 串口DMA很難?其實就是如此簡單。ǔ敿、附代碼)

https://blog.csdn.net/weixin_44524484/article/details/106029682


以下為原文目錄,一步一步教會你串口DMA!


評分

參與人數 2黑幣 +80 收起 理由
arther + 30 很給力!
admin + 50 共享資料的黑幣獎勵!

查看全部評分

回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 秋霞在线一区二区 | 精品二三区 | 天天天操操操 | 欧美精产国品一二三区 | 伊人狠狠干 | 国产精品久久一区 | 国产成人网 | 精品国产免费人成在线观看 | 91精品国产自产精品男人的天堂 | 色吧综合网 | 日日操视频 | 日韩在线一区二区三区 | 亚洲国产精品久久 | 精品一区二区三区四区外站 | 久久精品小视频 | 亚洲人在线播放 | 亚洲精品成人网 | 色视频在线免费观看 | 久久亚洲国产精品 | 日韩欧美三区 | 操久久 | 亚洲三区在线观看 | 国产精品久久久久久亚洲调教 | 天天操天天摸天天干 | 在线观看视频一区 | 91视频久久 | 在线观看av网站 | 欧美日韩综合视频 | 午夜精品久久久久久久久久久久 | 欧州一区二区三区 | 亚洲高清av在线 | 天天色天天射天天干 | 中文字幕视频在线免费 | 四虎永久免费地址 | k8久久久一区二区三区 | 色婷婷精品久久二区二区蜜臂av | 国产成在线观看免费视频 | 亚洲精品综合一区二区 | 国产真实精品久久二三区 | 久久久久久国产精品久久 | 最新中文字幕第一页视频 |