历史上的今天
返回首页

历史上的今天

今天是:2025年06月14日(星期六)

2020年06月14日 | STM32基于固件库学习笔记(6)使用DMA实现USART1发送数据

2020-06-14 来源:eefocus

DMA简介

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。换而言之就是当外设有数据发送给mcu,此时可以使用DMA接收到用户定义空间(不占用cpu),接收完成在产生中断发给mcu(才占用CPU)反正一样。


当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。


两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对寄存器访问。

在这里插入图片描述

DMA 主要特性

● 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道

● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过

软件来配置。

● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、

中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。

● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。

● 支持循环的缓冲器管理

● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志

逻辑或成为一个单独的中断请求。

● 存储器和存储器间的传输

● 外设和存储器、存储器和外设之间的传输

● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。

● 可编程的数据传输数目:最大为65535


处理的方式

每次DMA传送由3个操作组成:

● 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传

输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

● 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输

时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

● 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

在这里插入图片描述在这里插入图片描述

DMA 的配置步骤

1. 使能DMA时钟

(是必不可少的)


RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAx,ENABLE); //使能 DMA 时钟


2. 初始化DMA通道参数

(具体某个通道,根据上面的图可以知道)

DMA 通道配置参数种类比较繁多,在"stm32f10x_dma.h""stm32f10x_dma.c"都封装好了的;说白了就是配置DMA_InitTypeDef结构体的参数。


//初始化

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);

typedef struct//结构体

{

  uint32_t DMA_PeripheralBaseAddr;//设置DMA传输的外设基地址

  /*要进行串口DMA传输,那么外设基地址为串口接受收发送数据存储器USART1->DR的地址*/

  uint32_t DMA_MemoryBaseAddr;//定义 DMA 内存基地址(我们定义存放DMA传输数据的内存地址)

  uint32_t DMA_DIR;//作为数据传输的目的地还是来源(传输方向)

  /* DMA_DIR_PeripheralDST 外设作为数据传输的目的地

     DMA_DIR_PeripheralSRC 外设作为数据传输的来源*/

  uint32_t DMA_BufferSize;//指定 DMA 通道的 DMA 缓存的大小,单位为数据单位

  uint32_t DMA_PeripheralInc;//设定外设地址寄存器递增与否

  /*DMA_PeripheralInc_Enable 外设地址寄存器递增

    DMA_PeripheralInc_Disable 外设地址寄存器不变*/

  uint32_t DMA_MemoryInc;//设定内存地址寄存器递增与否

  /*DMA_PeripheralInc_Enable 内存地址寄存器递增

    DMA_PeripheralInc_Disable 内存地址寄存器不变*/

  uint32_t DMA_PeripheralDataSize;// 设定了外设数据宽度

  /*DMA_PeripheralDataSize_Byte 数据宽度为 8 位

    DMA_PeripheralDataSize_HalfWord 数据宽度为 16 位

    DMA_PeripheralDataSize_Word 数据宽度为 32 位*/

  uint32_t DMA_MemoryDataSize;//定了外设数据宽度

  /*DMA_MemoryDataSize_Byte 数据宽度为 8 位

    DMA_MemoryDataSize_HalfWord 数据宽度为 16 位

    DMA_MemoryDataSize_Word 数据宽度为 32 位*/

  uint32_t DMA_Mode;//工作模式

  /*DMA_Mode_Circular 工作在循环缓存模式

    DMA_Mode_Normal 工作在正常缓存模式*/

  uint32_t DMA_Priority;//通道 x 的软件优先级

  /*DMA_Priority_VeryHigh DMA 通道 x 拥有非常高优先级

    DMA_Priority_High DMA 通道 x 拥有高优先级

    DMA_Priority_Medium DMA 通道 x 拥有中优先级

    DMA_Priority_Low DMA 通道 x 拥有低优先级*/

  uint32_t DMA_M2M;//使能 DMA 通道的内存到内存传输

  /*DMA_M2M_Enable DMA 通道 x 设置为内存到内存传输

    DMA_M2M_Disable DMA 通道 x 没有设置为内存到内存传输*/

}DMA_InitTypeDef;

//后面完整程序有更清楚配置说明


3 )使能串口DMA发送或接收


USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//发送


4 ) 使能DMA 通道x,启动传输。


DMA_Cmd(DMA_CHx, ENABLE);


注:配置好了1 2 3步骤,在使能MDA通道就可以成功发送或接收一次数据。反而言之决定“DMA_Cmd()”函数的位置就能决定发送数据的时间。

5)查询 DMA 传输状态

就比如在中断的时候,我们要判断相对应的标志位。


FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

/*

DMA_FLAG_GL1  通道 1 全局标志位

DMA_FLAG_TC1  通道 1 传输完成标志位

DMA_FLAG_HT1  通道 1 传输过半标志位

DMA_FLAG_TE1  通道 1 传输错误标志位

DMA_FLAG_GL2  通道 2 全局标志位

DMA_FLAG_TC2  通道 2 传输完成标志位

DMA_FLAG_HT2  通道 2 传输过半标志位

DMA_FLAG_TE2  通道 2 传输错误标志位

DMA_FLAG_GL3  通道 3 全局标志位

DMA_FLAG_TC3  通道 3 传输完成标志位

DMA_FLAG_HT3  通道 3 传输过半标志位

DMA_FLAG_TE3  通道 3 传输错误标志位

DMA_FLAG_GL4  通道 4 全局标志位

DMA_FLAG_TC4  通道 4 传输完成标志位

DMA_FLAG_HT4  通道 4 传输过半标志位

DMA_FLAG_TE4  通道 4 传输错误标志位

DMA_FLAG_GL5  通道 5 全局标志位

DMA_FLAG_TC5  通道 5 传输完成标志位

DMA_FLAG_HT5  通道 5 传输过半标志位

DMA_FLAG_TE5  通道 5 传输错误标志位

DMA_FLAG_GL6  通道 6 全局标志位

DMA_FLAG_TC6  通道 6 传输完成标志位

DMA_FLAG_HT6  通道 6 传输过半标志位

DMA_FLAG_TE6  通道 6 传输错误标志位

DMA_FLAG_GL7  通道 7 全局标志位

DMA_FLAG_TC7  通道 7 传输完成标志位

DMA_FLAG_HT7  通道 7 传输过半标志位

DMA_FLAG_TE7  通道 7 传输错误标志位

*/


获取当前剩余数据量大小的函数:(有时候还是可以用到)

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

实际功能效果图

DMA实现USART1收发送数据程序

通过查资料,多次调试,最总完成了这次任务。

使用USART1中断+MDA实现接收数据,PE4接的按键,按下一次通过MDA发送数据一次到USART1。


#include "stm32f10x.h" 

#include "stdio.h"

#include "string.h"

int fputc(int ch, FILE *f)

{

  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);

  USART_SendData(USART1,(uint8_t)ch);

  return ch;

}


#define UART_RX_LEN 128

/*串口发送DMA缓存*/

uint8_t SendBuff[UART_RX_LEN]={"zxcvbnm,asdfghjkl"};

/*串口接收DMA缓存*/

uint8_t Uart_Rx[UART_RX_LEN] = {0};

uint8_t Data_Receive_Usart=0;

void delay_ms(u16 time)

{    

   u16 i = 0;  

   while(time--)

   {

      i = 12000;  

      while(i--);    

   }

}


//串口初始化

void Usart_Init(void)

  GPIO_InitTypeDef GPIO_ITDef1;

  GPIO_InitTypeDef GPIO_ITDef;

  USART_InitTypeDef  USART_ITDef;

//挂载时钟(复用PA) 串口时钟使能,GPIO 时钟使能,复用时钟使能

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);

//PA9 TXD初始化

  GPIO_ITDef.GPIO_Pin = GPIO_Pin_9;//PA9 TXD

  GPIO_ITDef.GPIO_Mode = GPIO_Mode_AF_PP;////复用推挽输出

  GPIO_ITDef.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOA,&GPIO_ITDef);

//PA10 TXD初始化

  GPIO_ITDef1.GPIO_Pin = GPIO_Pin_10;//PA10 RXD

  GPIO_ITDef1.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

  GPIO_Init(GPIOA,&GPIO_ITDef1);

  

//USART初始化

  USART_ITDef.USART_BaudRate = 115200;//波特率

  USART_ITDef.USART_WordLength = USART_WordLength_8b;//发送数据长度

  USART_ITDef.USART_StopBits = USART_StopBits_1; //一个停止位   

  USART_ITDef.USART_Parity = USART_Parity_No; //无奇偶校验位     

  USART_ITDef.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制

  USART_ITDef.USART_Mode = USART_Mode_Tx| USART_Mode_Rx ;//发送模式 

  USART_Init(USART1,&USART_ITDef);

  /*中断配置*/

  USART_ITConfig(USART1,USART_IT_TC,DISABLE);

  USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);

  USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);

  

   NVIC_InitTypeDef NVIC_InitStructure;

  //配置UART1中断  

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;              //通道设置为串口1中断  

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;      //中断占先等级0  

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;             //中断响应优先级0  

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //打开中断  

  NVIC_Init(&NVIC_InitStructure);

 

  USART_Cmd(USART1, ENABLE);//使能串口

}

void MDA_USART1_Init(void)

{   

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟

  DMA_Cmd(DMA1_Channel4,DISABLE);

  DMA_DeInit(DMA1_Channel4); //将 DMA 的通道 1 寄存器重设为缺省值

  DMA_InitTypeDef DMA_InitStructure;

  

  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);  //DMA外设基地址

  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;  //DMA内存基地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设

  DMA_InitStructure.DMA_BufferSize = UART_RX_LEN;  //DMA通道的DMA缓存的大小

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位

  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常模式

  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x拥有中优先级 

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输

  DMA_Init(DMA1_Channel4, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器

  

  /*DMA1通道5配置接收*/

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

  DMA_DeInit(DMA1_Channel5);

  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);

  //串口的发送和接收stm32都存在同一寄存器的;51和cc2530是发送和接收用单独的寄存器存数据;我们要其他内设使用DMA,就得清楚当前设备在stm32存数据的寄存器。(stm32数据手册都有介绍)

  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart_Rx;

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

  DMA_InitStructure.DMA_BufferSize = UART_RX_LEN;

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

  DMA_Init(DMA1_Channel5,&DMA_InitStructure);

 //主要通过按键来发送数据,所以这里没有使能通道4(一旦使能就将发送一次数据)

/*使能接收通道5*/

  DMA_Cmd(DMA1_Channel5,ENABLE); 

//采用DMA方式发送

  USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //采用DMA方式接收

  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);

    //启动串口  

  USART_Cmd(USART1, ENABLE);

}

/*

 * 启动DMA数据发送功能

 *size表示需要发送的DMA中数据的个数

*/

void uart_dma_send_enabl(uint16_t size)

{

    DMA1_Channel4->CNDTR = (uint16_t)size; 

    DMA_Cmd(DMA1_Channel4, ENABLE);       

}

void USART1_IRQHandler(void)                               

{   

    memset(SendBuff,0,UART_RX_LEN);//清空发送数组

    uint32_t temp = 0;

    uint16_t i = 0;

    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)

    {

        

推荐阅读

史海拾趣

Eska公司的发展小趣事

为了满足全球客户的需求,Eska公司实施了市场拓展和国际化战略。公司在欧洲各大主要城市及美国设立了服务中心,以便为当地客户提供快速可靠的服务。此外,Eska还利用先进的分切设备,为当地客户集中快速地提供载切大小格式灰板的服务。同时,Eska的产品也通过全球代理商、经销商及存货商网络,覆盖到更广泛的市场。

Diodes Incorporated公司的发展小趣事

近年来,Diodes Incorporated的财务表现稳步提升。根据公司公布的财报数据显示,公司归母净利润持续增长,营业收入也保持着稳定的增长态势。这一成绩的取得,得益于公司对产品质量的严格把控、对市场需求的精准把握以及对新技术的持续投入。同时,公司还通过优化生产流程、降低生产成本等措施,进一步提升了盈利能力。

Dalian Dlicap Corporation公司的发展小趣事

2023年12月29日,大连达利凯普科技股份公司在深圳证券交易所创业板正式挂牌上市,股票代码为301566。这一里程碑式的事件标志着公司进入了一个新的发展阶段。上市融资将为达利凯普提供更多的资金支持和发展机遇,公司将继续加大研发投入和市场拓展力度,不断推动技术创新和产业升级。同时,达利凯普也将积极履行社会责任和义务,为电子行业的发展做出更大的贡献。

请注意,这些故事概要基于现有信息整理而成,具体细节可能因时间、环境等因素而有所不同。

Fascomp公司的发展小趣事

在快速发展的过程中,Fascomp始终注重企业文化建设和人才培养。公司倡导“创新、协作、务实、进取”的价值观,鼓励员工积极创新、勇于挑战。同时,公司还注重人才培养和引进,建立了一套完善的人才培养和激励机制。这些举措为公司的发展提供了坚实的人才保障。

Global Power Technology Co., Ltd公司的发展小趣事
通常容声BCD-190型电冰箱的温度调节旋钮位于冷藏室内部或冰箱侧面。根据实际需要,通过旋转温度调节旋钮来设置冷藏室和冷冻室的温度。
Euroquartz公司的发展小趣事

面对不断变化的市场环境和客户需求,Euroquartz始终保持敏锐的洞察力和快速的反应能力。公司不断投入研发力量,推动产品创新和技术升级。同时,Euroquartz也注重与客户的沟通和合作,深入了解市场需求,为客户提供更加专业和贴心的服务。这种持续发展的动力,使Euroquartz在电子行业始终保持领先地位,并为公司的未来发展奠定了坚实的基础。

请注意,由于篇幅限制,以上每个故事都是基于Euroquartz公司的重要事件和事实进行概括和简化的。如果需要更详细的信息或更深入的分析,建议查阅相关报道或公司官方资料。

问答坊 | AI 解惑

电子竞赛,考的是学生?还是老师?

本帖最后由 paulhyde 于 2014-9-15 09:30 编辑 记得去年竞赛的时候,绕有兴趣地拿给一个有项目经验的朋友看, 由于工作繁忙的缘故,那个朋友说:天哪,谁能在这么短的时间内作出这么多东西啊?当时我就给他解释:有很多东西都是大家训练很多遍的 ...…

查看全部问答>

谁有这几个贴片元件的规格书

一块板子上,有几个贴片二极管,标示为: A,F1,F5,K2。黑色,尺寸类似0805 找不到规格书,谁能帮忙一下 先谢谢了…

查看全部问答>

3G远程唤醒新技术可降低3G监控运行费用

前几个月,在网上发了一篇“痛批3G网络监控之三点害处”,引起了很多行业内人士对3G网络热烈讨论,有的朋友认为3G用于监控还不是很成熟,也有朋友对3G用于监控行业还是充满了希望。上次贴子中总结3G网络三个害处分别是:速度慢,不稳定和费用高。就 ...…

查看全部问答>

如何改变镜像的名称"VXWORKS"

Tornado 生成VXWORKS镜像的名称都为“VXWORKS”,有没有可能改成其他的名字?…

查看全部问答>

pcb

大家好:   谁对pcb比较熟,给我点建议或资料吧。先谢谢了。…

查看全部问答>

谁没睡觉的,进来教我ISE的测试模块怎么操作吧~

文件编好了,可是不知道怎么操作,能不能教我下?用QQ远程控制帮我演示下,或者其它。拜托了!…

查看全部问答>

wm5 下 怎么获得另一个程序右软健的菜单句柄啊?

想自己编写程序控制 ppc上 activesync的菜单 CWnd* pWnd=FindWindowW(NULL,L\"ActiveSync\"); HWND hwndMB = SHFindMenuBar (pWnd->m_hWnd); 已经得到menubar的句柄了  下面该怎么写呢 查了MSDN 说是         ...…

查看全部问答>

关于FSMC时序时间计算ADDSTDATAST

关于这两个参数的计算 根据如上条件 是怎么算出来0x00 0x00 0x06的? 下面来掰一下小学计算: 1.( (ADDST + 1) + (DATAST + 1) )*HCLK =MAX(Trc ,Twc); 2.DATAST*HCLK = Twp; 3.DATAST = (Tavqv + Tv + Tsu)/HCLK - ADDST - 4 ;手册上写 ...…

查看全部问答>

旧耳机驱动小喇叭

请问诸位怎样用放大器做个功放去驱动小喇叭  我用的是就耳机做输入???小喇叭的电阻4欧、功率0.4瓦…

查看全部问答>

TI 可以申请DSP学习板吗

TI有没有申请DSP开发板的活动,论坛有没有申请DSP开发板的活动?   如果有这样的好事,就不用话很多钱去买开发板了,   真希望能掉个馅饼,哈哈 …

查看全部问答>