【STM32】使用DMA+SPI传输数据
DMA(Direct Memory Access):直接存储器访问
一些简单的动作,例如复制或发送,就可以不透过CPU,从而减轻CPU负担
由于本人使用的是正点原子开发板,部分代码取自里面的范例
本篇内容大纲
【1】DMA初步了解
【2】导入相关的库
【3】代码流程
【1】DMA初步了解
DMA可以设定三种传输方式:『外设到存储器』『存储器到外设』『存储器到存储器』(第三种方式仅DMA2能执行)
本篇测试的是『存储器到外设』,下面继续介绍DMA
STM32F4有两个DMA控制器(DMA1、DMA2)
每个控制器有8个数据流(Stream)
然后,每个数据流又有8个通道(Channel)
下面两张张表格,来说明『DMA控制器』『数据流』『通道』所对应的DMA请求映射(request mapping)
以下这图是针对STM32F4的,其他芯片,例如STM32F1,应该要找各自的说明书,也许表格会有出入
找出我想实现的功能,例如我想用串口1的发送(USART1_TX),在DMA2里面,『Stream = 7』『Channel = 4』 就是我们要的了
/* ------------- 题外话 ------------- */
也许你会发现,为什么会有两个一样的,例如DMA1表格里,【Stream0、Channel0】【Stream2、Channel0】对应的都是SPI3_RX
在网上问人后,对方是和我说,这是解决唯一拥有的情况,例如只有DMA2有SDIO功能,如果你同时又要使用SPI,那么可以用DMA1来配合SPI
/* -------------------------------- */
【2】导入相关的库
因为本篇测试的是『存储器到外设』
先看看有没有所需外设的文件,例如stm32f4xx_usart.c,没有的话参考下面的图片来导入,以本篇来说,需要导入的外设是stm32f4xx_spi.c
接下来,由于我们要使用DMA,所以也要导入stm32f4xx_dma.c
导入完成后,我们先打开 stm32f4xx_dma.h 这个头文件,可以看到一些设定的函数,例如初始化之类的
要设置时,基本上所要调用的函数就在这里了,而下方红框是中断和标志相关的函数
为什么说基本上?那是因为还有一小部分的设定,要在别的地方找
假设我们要使用USART(上面已经添加库了:stm32f4xx_usart.c)
找一下stm32f4xx_USART.h这个头文件,通过搜寻dmacmd,就会找到使能函数(USART_DMACmd)
因为本篇使用SPI,但由于我懒得改图了,只要找到stm32f4xx_SPI.h这个头文件
就会发现关于DMA的函数,void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState)
【3】代码流程
在main里,大致的流程是这样的
(1)首先初始化外设,这里以SPI为例(spi3_init)
(2)执行DMA的初始化(MYDMA_Config)
(3)在你需要执行传送数据的地方,执行数据的传送,这里是直接写在while(1)里面了
(4)做完一次的DMA,要把相关的标志清0
int main(void)
{
SPI3_Init(); // 串口初始化
MYDMA_Config(DMA1_Stream5,DMA_Channel_0,(u32)&SPI3->DR,(u32)SendBuff,SEND_BUF_SIZE); // DMA初始化
while(1)
{
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE); // 使能DMA发送
MYDMA_Enable(DMA1_Stream5,SEND_BUF_SIZE); // 执行一次的DMA发送
if(DMA_GetFlagStatus(DMA1_Stream5,DMA_FLAG_TCIF5)!=RESET)) //等待DMA传输完成
DMA_ClearFlag(DMA1_Stream5,DMA_FLAG_TCIF5); // 清除标志
}
}
先不要在意里面的参数,下面会详解,DMA的使用,大致的流程就是这样
下面详解这5个函数的内容,判断式就不解释了
SPI3_Init()
void SPI3_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//使能SPI3时钟 // PC10:SPI3_SCK
// PC11:SPI3_MISO
// PC12:SPI3_MOSI
// PB3:SPI1_SCK、SPI3_SCK
// PB4:SPI1_MISO、SPI3_MISO
// PB5:SPI1_MOSI、SPI3_MOSI
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12;//PC10~12复用功能输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化 GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_SPI3); //PC10复用为 SPI3
GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_SPI3); //PC11复用为 SPI3
GPIO_PinAFConfig(GPIOC,GPIO_PinSource12,GPIO_AF_SPI3); //PC12复用为 SPI3 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为8
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI3, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 SPI_Cmd(SPI3, ENABLE); //使能SPI外设
}
SPI一开始要使能的就是时钟
其次,找到SPI能复用的引脚,这里用的是SPI3,PC10、11、12
后续一堆的SPI_InitStructure开头的,就是在做SPI相关的初始化
这部分就不详解了,SPI的知识网上有很多介绍的,例如什么是CPOL,什么又是CPHA,这些都是重点
倒数第二行执行SPI_Init来初始化
最后一行使能外设
MYDMA_Config(DMA1_Stream5,DMA_Channel_0,(u32)&SPI3->DR,(u32)SendBuff,SEND_BUF_SIZE)
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{ DMA_InitTypeDef DMA_InitStructure; if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}
else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx); while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置 /* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
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_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
}
这个函数需要给5个参数
(1)DMA数据流,参照文章一开始的表格,这里使用的是SPI3_TX,对应的是DMA1的数据流5(DMA1_Stream5)
(2)通道,参照文章一开始的表格,这里使用的是SPI3_TX,对应的是DMA1的数据流5的通道0(DMA_Channel_0)
(3)外设地址,使用的是SPI发送(SPI3->DR)
(4)存储器地址,自己定义的一个变量
#define SEND_BUF_SIZE 500
u8 SendBuff[SEND_BUF_SIZE];
(5)传输的数据量,第4点的宏定义,当然,也可以看你要传多少
函数的内容差不多也就那样,都是一些初始化的设定,也就传输方式、优先级、单次传输还是循环之类的
while(1)之前的两个初始化介绍完了,接下来就是while(1)内部的几个函数
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE)
结果到头来,还是要截这张图。。。这个是库函数,不是我自己写的
参数1:SPI3,因为我测试用的就是SPI3
参数2:发送或是接收,我是发送,所以是SPI_I2S_DMAReq_Tx
参数3:使能请求
MYDMA_Enable(DMA1_Stream5,SEND_BUF_SIZE)
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{ DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输 while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置 DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量 DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}
参数1:哪个DMA控制器的哪个数据流,这里是DMA1数据流5(DMA1_Stream5)
参数2:数据量
DMA_ClearFlag(DMA1_Stream5,DMA_FLAG_TCIF5)
参数1:哪个DMA控制器的哪个数据流,这里是DMA1数据流5(DMA1_Stream5)
参数2:图片的1068行,说明了可以用0~7的数据流,我使用的是数据流5,所以要清除的也是数据流5(DMA_FLAG_TCIF5)
第1063~1067行的解释
DMA_FLAG_TCIFx:『数据流x』传输完成标志
DMA_FLAG_HTIFx:『数据流x』半传输完成标志
DMA_FLAG_TEIFx:『数据流x』传输错误标志
DMA_FLAG_DMEIFx:『数据流x』直接模式错误标志
DMA_FLAG_FEIFx:『数据流x』FIFO错误标志
选定自己需要的来清除即可
然后就能实现DMA+SPI了
观看的人,如果能帮到你,这是我的荣幸
【STM32】使用DMA+SPI传输数据的更多相关文章
- STM32学习笔记——SPI串行通讯(向原子哥学习)
一.SPI 简介 SPI是 Serial Peripheral interface 的缩写,就是串行外围设备接口.SPI 接口主要应用在 EEPROM, FLASH,实时时钟,AD 转换器,还有数 ...
- STM32之DMA+ADC
借用小甲鱼的经典:各位互联网的广大网友们.大家早上中午晚上好..(打下小广告,因为小甲鱼的视频真的很不错).每次看小甲鱼的视频自学都是比较轻松愉快的..我在想,如果小甲鱼出STM32的视频,我会一集不 ...
- STM32之DMA
一.DMA简介 1.DMA简介 DMA(Direct Memory Access:直接内存存取)是一种可以大大减轻CPU工作量的数据转移方式. CPU有转移数据.计算.控制程序转移等很多功能,但其实转 ...
- STM32之DMA实例
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zouleideboke/article/details/75092558 DMA简介: DMA(Di ...
- STM32使用DMA发送串口数据
1.概述 上一篇文章<STM32使用DMA接收串口数据>讲解了如何使用DMA接收数据,使用DMA外设和串口外设,使用的中断是串口空闲中断.本篇文章主要讲解使用DMA发送数据,不会讲解基础的 ...
- STM32的DMA
什么是DMA?其全称是:Direct Memory Access:根据ST公司提供的相关信息,DMA是STM32中一个独立与Cortex-M3内核的模块,有点类似与ADC.PWM.TIMER等模块:主 ...
- STM32 串口DMA方式接收(转)
STM32 是一款基于ARM Cortex-M3内核的32位MCU,主频最高可达72M.最近因为要在车机上集成TPMS功能, 便开始着手STM32的开发工作,STM32F10x系列共有5个串口(USA ...
- STM32 基DMA的DAC波形发生器
DAC是STM32系列的一个基本外设,可以将数字信号转化成模拟信号,这次我将使用DAC来输出一个特定波形. 首先确定工作方法,由于我目前在做的简易示波器在输出波形的同时还需要显示输入信号,所以不能占用 ...
- STM32 UART DMA实现未知数据长度接收
串口通信是经常使用到的功能,在STM32中UART具有DMA功能,并且收发都可以使用DMA,使用DMA发送基本上大家不会遇到什么问题,因为发送的时候会告知DMA发送的数据长度,DMA按照发送的长度直接 ...
随机推荐
- Django(73)django-debug-toolbar调试工具
介绍 Django框架的调试工具栏使用django-debug-toolbar库,是一组可配置的面板,显示有关当前请求/响应的各种调试信息,点击时,显示有关面板内容的更多详细信息. 应用 1. 安装 ...
- 重装系统——联想window 10
大四了,读了四年大学,唉,混的,啥也不会,工作也找不到,真的不知道这大学四年到底干了什么.专业是计算机方向的,但居然,不敢,也不会装电脑系统,大学四年的文件都是乱放的,更那个的是,有些软件卸载不完全, ...
- TDSQL | 在整个技术解决方案中HTAP对应的混合交易以及分析系统应该如何实现?
从主交易到传输,到插件式解决方案,每个厂商对HTAP的理解和实验方式都有自己的独到解法,在未来整个数据解决方案当中都会往HTAP中去牵引.那么在整个技术解决方案中HTAP对应的混合交易以及分析系统应该 ...
- redis异常:(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk.
(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on d ...
- Spring IOC&DI 控制反转和依赖注入
控制反转(Inversion of Control,缩写为IOC),它是把你设计好的对象交给spring控制,而不再需要你去手动 new Object(); 网上对于IOC的解释很多,对程序员而言,大 ...
- Django 小实例S1 简易学生选课管理系统 12 CSS样式完善
Django 小实例S1 简易学生选课管理系统 第12节--CSS样式完善 点击查看教程总目录 作者自我介绍:b站小UP主,时常直播编程+红警三,python1对1辅导老师. 课程模块的逻辑代码到这里 ...
- MySQL 1064 错误
ERROR 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MySQL s ...
- SpringCloud升级之路2020.0.x版-40. spock 单元测试封装的 WebClient(上)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们来测试下前面封装好的 WebClient,这里开始,我们使用 spock 编写 gro ...
- [hdu7065]Yinyang
将一个格子看作一个节点,相邻(有公共边)的同色格子之间连边,那么由前两个条件即要求图恰被分为两个非空连通块(由于$n,m\ge 3$,显然不能不使用某种颜色) 下面,来分析图中的简单环,其对应于网格图 ...
- [bzoj3670]动物园
首先计算出s数组,s表示可以重复的前缀等于后缀的个数,显然有s[i]=s[next[i]]+1,因为有且仅有next的next满足这个条件. 然后直接暴力枚举所有next,直到它小于i的一半,这个时间 ...