STM32 F1 串口空闲中断 + DMA实现数据发送

DMA实现数据发送

文章目录

  • DMA实现数据发送
  • 前言
  • 一、DMA
  • 二、代码编写
    • 1.DMA
    • 2.USART
    • 3.main


前言

当你遇到通信数据量大的时候,可以使用 空闲中断 + DMA 的方案来减轻 CPU 的压力。
或者
在进行stm32开发时,有时会遇到这种情况:需要在设备间进行数据传输,由于stm32串口RDR和TDR寄存器都是8位有效的,我们往往需要定义传输协议(如一帧数据中,包含包含帧头、帧ID、数据帧、校验帧等若干8位数据)。我们希望可以一次收到一帧数据,并进行解码操作。利DMA+串口空闲中断可以有效完成上述任务。

一、DMA

1、简介
DMA(直接存储器访问)是一种数据传输方法,利用DMA控制器,将数据直接从一个地址空间复制到另一个地址空间。
DMA在硬件ROM和IO设备间开辟直接传输数据的通道,不需要CPU主控芯片控制,也不需要类似中断处理那种保留现场&恢复现场的操作。这大大减小了CPU的负担。
2、使用场景
DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:

外设→存储器(例:从串口RDR寄存器写入某数据buf)
存储器→外设(例:从某数据buf写入串口TDR寄存器)
存储器→存储器(例:复制某特别大的数据buf)

DMA相关的参数:1 数据的源地址、2 数据传输的目标地址 、3 传输宽度,4 传输多少字节,5 传输模式。

传输宽度是指一次传输数据的的大小,可以为字节(8b)、半字(16b)、字(32b)

传输模式分为正常模式(一次结束)和循环模式

DMA通道

STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
在这里插入图片描述
FIFO介绍:
DMA接收有两种模式,一种为直接模式,另一种为FIFO模式
FIFO为缓存区,大小为32位(16个字节,8个半字,4个字),独立的源和目标传输宽度(字节、半字、字),在单位上分为三种读取FIFO的方式,为(字节、半字、字)每个数据流都有一个独立的 4 字 FIFO,阈值级别可由软件配置为 1/4、1/2、3/4 或满。

二、代码编写

1.DMA

#include "DMA.h"
 
u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度         
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量 
void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    DMA1_MEM_LEN=cndtr;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
    
    DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值
    
    DMA_InitStructure.DMA_BufferSize=cndtr;//DMA通道的DMA缓存的大小(转运的数据量)
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//数据传输方向,从外设读取发送到内存
    DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
    DMA_InitStructure.DMA_MemoryBaseAddr=cmar;//DMA内存基地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度为8位
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址寄存器递增
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//工作在正常模式
    DMA_InitStructure.DMA_PeripheralBaseAddr=cpar;//DMA外设基地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度为8位
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址寄存器不变
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium; //DMA通道 x拥有中优先级 
    DMA_Init(DMA_CHx,&DMA_InitStructure);
    
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);//使能外设的DMA通道,这句可以放在对应的外设里
}
 
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
    DMA_Cmd(DMA_CHx, DISABLE );  //关闭所指示的通道      
     DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//重新设定DMA通道的DMA缓存的大小
     DMA_Cmd(DMA_CHx, ENABLE);  //使能所指示的通道 
}      
 

首先,定义了全局变量DMA1_MEM_LEN用于保存每次数据传输的长度。然后,使用MyDMA_Config函数配置DMA通道的参数,包括DMA缓存大小、数据传输方向、内存地址寄存器递增等。其中,cpar表示外设地址,cmar表示存储器地址,cndtr表示数据传输量。在配置完成后,通过USART_DMACmd函数使能外设的DMA通道。

接着,使用MYDMA_Enable函数开启一次DMA传输。该函数首先关闭所指示的DMA通道,然后重新设定DMA缓存的大小,并使能DMA通道。

#ifndef __DMA_H
#define __DMA_H
 
#include "sys.h"
 
#define rx_buff_maxlen 200//定义接受缓存区最长长度
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);
void MyDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);
 
#endif

2.USART

#include "sys.h"
#include "usart.h"	  
#include "DMA.h"	  
 u8 len,Flag=0;
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	 

#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化		   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明 
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
// 	  
 

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

/*使用microLib的方法*/
 /* 
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (uint8_t) ch);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
   
    return ch;
}
int GetKey (void)  { 

    while (!(USART1->SR & USART_FLAG_RXNE));

    return ((int)(USART1->DR & 0x1FF));
}
*/
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
 // USART_ITConfig(USART1, USART_IT_RXE, ENABLE);//开启串口接受中断
	 USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);    //开启USART1的空闲中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}


void USART1_IRQHandler(void)                 //串口1中断服务程序,当接受完毕后便会触发空闲中断
 {
     u8 clear=0;
     if(USART_GetITStatus(USART1, USART_IT_IDLE) == SET)  //接收中断
      {
        clear=USART1->DR;//清楚中断标志位
        Flag=1;//标志一次接受完毕,在main函数中读取flag来判断是否接收完毕,并在主函数中清零
        len=DMA_GetCurrDataCounter(DMA1_Channel5)-sizeof(rx_buff);//读取剩余未转运的长度
    }
}

	
void MyUSART_SendByte(u8 Byte)
{
    USART_SendData(USART1,Byte);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//防止发送过快,前一个数据还没发送出去就别覆盖的情况
}
 
void MyUSART_SendString(char* str)//发送字符串
{
    u16 i;
    for(i=0;str[i]!='\0';i++)
    {
        MyUSART_SendByte(str[i]);
    }
    
}
#endif

要注意的是这里开启的空闲中断
在这里插入图片描述
这里做中断标志位清0
在这里插入图片描述

#ifndef __USART_H
#define __USART_H
#include "stdio.h"	
#include "sys.h" 
#define USART_REC_LEN  			200  	//定义最大接收字节数 200
#define EN_USART1_RX 			1		//使能(1)/禁止(0)串口1接收

extern u8 len;
extern u8 Flag;
extern char rx_buff[200];
extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 USART_RX_STA;         		//接收状态标记	
void uart_init(u32 bound);
#endif

3.main

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
 #include "DMA.h"
 int main(void)
 {		
 	u16 t;  
	 char rx_buff[200]={'\0'};
	u16 len;	
	u16 times=0;
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	LED_Init();			     //LED端口初始化
	KEY_Init();          //初始化与按键连接的硬件接口
	  MyDMA_Config(DMA1_Channel5,(u32)&USART1->DR,(u32)rx_buff,rx_buff_maxlen);
   MYDMA_Enable(DMA1_Channel5);

 	while(1)
	{
	    	if(Flag)
        {
            Flag=0;
            MyUSART_SendString(rx_buff);
            MyUSART_SendString("\r\n");
            printf("len=%d\r\n",len);
            MYDMA_Enable(DMA1_Channel5);
        }
	}	 
 }


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/191423.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

接口测试快速入门 以飞致云平台为例

飞致云电商API地址系统来自飞致云项目。接口API地址:https://gz.fit2cloud.com/swagger-ui.html 飞致云电商系统接口文档 V1.0:见 有道云笔记 该网站可以做接口测试练习。快速了解如何测试接口,如何做关联 系统基地址:https://g…

通用电气调查网络攻击和数据盗窃指控

通用电气正在调查有关威胁行为者在网络攻击中破坏了公司开发环境并泄露据称被盗数据的指控。 通用电气 (GE) 是一家美国跨国公司,业务涉及电力、可再生能源和航空航天行业。 本月早些时候,一个名为 IntelBroker 的威胁行为者试图在黑客论坛上以 500 美…

Linuxfork,写时拷贝

1.prinf隐藏的缓冲区 1.思考:为什么会有缓冲区的存在? 2.演示及思考? 1).演示缓存区没有存在感 那为什么我们感觉不到缓冲区的存在呢?我们要打印东西直接就打印了呢? 我们用代码演示一下: 比如打开一个main.c,输入内容如下: #include <stdio.h> int main() { …

二十七、微服务案例

目录 一、实现输入搜索功能 1、下载代码&#xff0c;在idea上打开 2、新建RequestParams类&#xff0c;用于接收解析请求 3、在启动类中加入客户端地址Bean&#xff0c;以便实现服务 4、编写搜索方法 5、新建返回分页结果类 6、实现搜索方法 7、编写控制类&#xff0c;…

docker打包前端镜像

文章目录 一、构建镜像二、查看本地镜像三、启动容器四、查看启动的容器五、保存镜像六、读取镜像七、创建镜像八、最后 docker官网 一、构建镜像 -t是给镜像命名&#xff0c;.(点)是基于当前目录的Dockerfile来构建镜像 docker build -t image_web .二、查看本地镜像 docke…

【数据结构】什么是队列?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;队列的定义 &#x1f4cc;队列的抽象数据类型 &#x1f4cc;队列的顺序存储结构 &#x1f4cc;队列的链式存储结构 结语 人生,是一个又一个小小的队列…

极客时间:使用本地小型语言模型运行网页浏览器应用程序。

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【Apache Doris】Manager极致丝滑地运维管理

【Apache Doris】Manager极致丝滑地运维管理 1.标准VS可视化运维管理2. 环境信息2.1.硬件信息2.2.软件信息 3.前置准备3.1.安装包准备3.2.文档手册准备 4.集群初始化4.1.系统参数预设4.2.Manager部署4.3.新集群部署4.4 监控告警4.4.1 监控4.4.2 告警 5. 集群升级5.1 新包准备5.…

大数据Doris(二十九):数据导入(Insert Into)

文章目录 数据导入(Insert Into) 一、​​​​​​​创建导入

Windows开启SQL Server服及1433端口

需求&#xff1a;Windows开启SQL Server服务及1433端口 目前端口没有启动 解决&#xff1a; 打开SQL Server配置管理器&#xff08;winR&#xff09; 各个sqlserver版本在textbox中输入对应的命令如下&#xff1a; SQLServerManager15.msc&#xff08;对于 SQL Server 2019 &am…

Linux7设置ssh秘钥登录并关闭密码登录

说明&#xff1a;场景为windows使用WinScp远程登录linux服务 winscp安装教程&#xff1a;winscp安装及关联putty使用_putty.exe没有找到_cherishSpring的博客-CSDN博客 1.在window上生成公钥和秘钥&#xff0c;操作方式参考以下文章第3点&#xff1a; git关联云效使用教程_云…

【经验分享】开发问题记录总结(持续更新)

目录 工具开发 界面类继承某自定义界面类时&#xff0c;出现布局混乱或者所有控件集中在左上角&#xff1f; 在继承自定义界面之后&#xff0c;以诸如 on_xxx_clicked() 模式设计的槽函数失效了? 使用pugi接口取出文本数据后&#xff0c;为什么该变量无法进行字符串比较&…

深信服超融合一体机提示:内存ECC

PS&#xff1a;此事件分享主要来源于季度巡检时发现的超融合一体机红灯闪烁异常&#xff0c;接入IPMI端口查看日志发现持续提示内存ECC&#xff1b; 因为是只有3.05这一天发现了有这个告警的提示&#xff0c;所以当时清除了日志以后重启了BMC服务就解决了&#xff1b;但是如果清…

Echarts 最简单创建柱状图

设置容器 <div ref"myChart" style"width: 500px; height: 500px;"> </div>mounted() {//document渲染完成this.draw()}draw() {const myChart this.$echarts.init(this.$refs.myChart)//初始化对象myChart.setOption({ //参数配置项title: …

如何在gitlab上使用hooks

参考链接&#xff1a;gitlab git hooks 1. Git Hook 介绍 与许多其他版本控制系统一样&#xff0c;Git 有一种方法可以在发生某些重要操作时&#xff0c;触发自定义脚本&#xff0c;即 Git Hook&#xff08;Git 钩子&#xff09;。 当我们初始化一个项目之后&#xff0c;.git…

windows本地dockr的clickhouse链接本地mysql服务,连接不上

不想看过成的&#xff0c;解决办法在最后面 报错信息&#xff1a; SQL 错误 [1000] [08000]: Poco::Exception. Code: 1000, e.code() 0, Exception: Connections to all replicas failed: test1localhost:3306 as user root (version 21.12.3.32 (official build)) , serve…

基于深度学习的图像超分辨率应用

引言 在使用图片浏览软件显示图片时&#xff0c;为了凸显某个部位&#xff0c;你会放大图片&#xff0c;为了显示整体&#xff0c;你会缩小图片。 你的原始图片大小是固定的&#xff0c;但图像浏览软件既可以最大化到整个屏幕&#xff0c;也可以只占一个区域&#xff0c;这些…

一文带你了解机器翻译的前世今生

引言 我们都知道谷歌翻译&#xff0c;这个网站可以像变魔术一样在100 种不同的人类语言之间进行翻译。它甚至可以在我们的手机和智能手表上使用&#xff1a; 谷歌翻译背后的技术被称为机器翻译。它的出现改变了世界交流方式。 事实证明&#xff0c;在过去几年中&#xff0c;深…

【C/C++】如何不使用 sizeof 求数据类型占用的字节数

实现代码&#xff1a; #include <stdio.h>#define GET_TYPE_SIZE(TYPE) ((char *)(&TYPE 1) - (char *) & TYPE)int main(void) {char a a;short b 0;int c 0;long d 0;long long e 0;float f 0.0;double g 0.0;long double h 0.0;char* i NULL;print…

微机原理_5

一、单项选择题(本大题共15小题,每小题3分,共45分。在每小题给出的四个备选项中,选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 8086微处理器CLK引脚输入时钟信号是由(提供。 A. 8284 B. 8288 C.8287 D. 8289 2.下面4个寄存器中,不能作为间接寻址的寄存器是(…