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

news2024/11/27 12:36:59

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);
        }
	}	 
 }


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字符串原地旋转

记录一下做的练习题 字符串原地旋转:五 三 mat [[1,2,3],[3,4,5],[4,5,6]] tag0 total 0 for i in mat:total total i[tag]tag 1 print(total) 四 X [[12,7,3],[4,5,6],[7,8,9]] Y [[5,8,1],[6,7,3],[4,5,9]] res [[0,0,0],[0,0,0],[0,0,0]] for i in rang…

2024年天津天狮学院市场营销专业《管理学》考试大纲

2024年天津天狮学院专升本市场营销专业高职升本入学考试《管理学》考试大纲 一、考试性质 《管理学》专业课程考试是天津天狮学院市场营销专业高职升本入学考试的必考科 目之一,其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《管理学》考…

STM32 默认时钟更改 +debug调试

STM32时钟 文章目录 STM32时钟前言一、修改系统时钟二、DEBUG 前言 为什么我们要改STM32的时钟呢,打个比方在做SPI驱动的时候,需要16M的时钟,但是stm32默认是72的分频分不出来,这个时候我们就要改系统时钟了,那么怎么…

人工智能基础_机器学习050_对比sigmoid函数和softmax函数的区别_两种分类器算法的区别---人工智能工作笔记0090

可以看到最上面是softmax的函数对吧,但是如果当k = 2 那么这个时候softmax的函数就可以退化为sigmoid函数,也就是 逻辑斯蒂回归了对吧 我们来看一下推导过程,可以看到上面是softmax的函数 可以看到k=2 表示,只有两个类别对吧,两个类别的分类不就是sigmoid函数嘛对吧,所以说 …

算法设计与分析(贪心法)

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…

Git基础命令,一篇搞懂!(命令行模式,无IDEA)

1.概述 Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库…

城市生命线丨桥梁结构健康监测系统的作用

在城市建设当中,有非常多的城市基本建设,建设当中,桥梁作为不可忽视的一环,也需要有很多桥梁建设的智能监测系统,在这个桥梁结构健康监测系统中,桥梁的各个数值都能被监测得到。 WITBEE万宾使用城市生命线智…

【记录】有关接口响应很快,但是在页面渲染的时候发现很慢的问题

请求数据返回的时候,接口响应的速度是很快的,但是数据量很大,导致返回的报文体很多兆,如果服务器的带宽不够大的话,会有些慢,我这边的例子是3m的数据平均需要大概5~10秒的时间。 思路:开启压缩…

【教学类-06-12】20231126 (二)三位数 如何让加减乘除题目从小到大排序(以0-110之间加法为例,做正序排列用)

结果展示 背景需求: 二位数:去0 三位数(需要排除很多0) 解决思路 一、把数字改成三位数 二、对数组内的题目,8种可能性进行去“0”处理 1、十位数(去百位数0)十位数(去百位数0&am…

百家号MCN是什么?百家号MCN禁止拉子账号怎么解决?

在当今数字化时代,社交媒体平台已成为人们获取信息、分享观点和创作内容的重要渠道之一。百家号作为百度旗下的自媒体平台,吸引了众多创作者和机构入驻,以分享优质内容并获得收益。在百家号上,MCN矩阵扮演着重要的角色&#xff0c…

LeetCode Hot100 543.二叉树的直径

题目: 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 方法:灵神 代码: ​…

哈希思想的应用

目录 1.位图 位图的实现 题目变形一 题目变形二 题目变形三 总结: 2.布隆过滤器 概念 布隆过滤器的实现 3.哈希切割的思想 1.位图 哈希表和位图是数据结构中常用的两种技术。哈希表是一种数据结构,通过哈希函数把数据和位置进行映射&#xff0c…

Alfred v5.1.4(mac快速启动)

Mac效率办公软件哪个好?Alfred是一款Mac电脑上的快速启动和工作流自动化工具,它可以帮助用户快速访问文件、应用程序、web搜索和系统工具,提高工作效率。以下是Alfred的特点: 快速启动:用户可以通过Alfred快速启动应用…

【Linux】bash 终端指令

进程 $ ps aux | grep pwd work 63317 0.0 0.0 51192 612 pts/9 S 14:22 0:00 grep /home/work/search/1000000.dyenv-user-diaoyan-baiseCliPlus-baisePlus-195522.diaoyan.yq/ala-ac/output_root端口 查看本机端口开放情况 $ netstat -tln | grep 3200 tcp…

Arduino驱动温湿度气压光照传感器模块

目录 一、简介二、原理图三、使用方法四、实验现象 一、简介 点击图片购买 HTU21D特性:HTU21D基于法国Humirel公司高性能的湿度感应元件制成,传感器输出标准IIC格式。同时具有很高的温度精度和湿度精度。HTU21专为低功耗小体积应用设计,具有很…

3. 迷宫问题

题目 迷宫有一个入口,一个出口。一个人从入口走进迷宫,目标是找到出口。阴影部分和迷宫的外框为墙,每一步走一格,每格有四个可走的方向,探索顺序为地图方向:南(下)、东(右…

【Spring整合MyBatis】Spring整合MyBatis的具体方法

在前面写的博客中,介绍了MyBatis通过配置方式和通过注解方式写的方法: 【Spring集成MyBatis】MyBatis诞生及代码快速入门(非注解开发)【Spring集成MyBatis】MyBatis的Dao层实现(基于配置,非注解开发&#…

【libGDX】立方体手动旋转

1 前言 本文主要介绍使用 libGDX 绘制立方体,并实现手动触摸事件控制立方体旋转。 为方便控制触摸旋转,并提高渲染性能,我们通过改变相机的位置和姿态实现立方体旋转效果。 读者如果对 libGDX 不太熟悉,请回顾以下内容。 使用Me…

Re55:读论文 Entities as Experts: Sparse Memory Access with Entity Supervision

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称:Entities as Experts: Sparse Memory Access with Entity Supervision 模型名称:Entities as Experts (EaE) ArXiv网址:https://arxiv.org/abs/2004.07202 本文…

轻松实现文件按数量平均分类,高效整理并自动新建文件夹保存“

你是否曾经因为文件数量过多,整理起来繁琐而感到烦恼?是否曾经为了新建文件夹而手动一个一个进行创建,费时又费力?现在,我们的智能文件管理工具将为你解决这些问题! 首先第一步,我们要进入文件…