STM32F407寄存器操作(DMA+SPI)

1.前言

前面看B站中有些小伙伴吐槽F4的SPI+DMA没有硬件可控的CS引脚,那么今天我就来攻破这个问题

我这边暂时没有SPI的从机芯片,并且接收的过程与发送的过程类似,所以这里我就以发送的过程为例了。

2.理论

手册上给出了如下的描述

我们关注一下黑点的两行,这是DMA操作的核心,我们可以理解为TXE与DMA的触发挂钩,这样理解上与程序上都比较好写。

手册上还给出了DMA的触发流程,如下。

我们详细剖析一下TXE与DMA操作关联,可以看到每一次TXE变高,DMA就会进行一次搬运,直到通讯结束,这样一来我们就可以通过等待TXE置位来联动DMA。

除此之外,我们还需监控BSY位,等待TXE=1然后BSY=0后再关闭SPI,进而完成通讯

然后是DMA通道,本次实验我用的是DMA2的数据流3的通道3

3.程序

3.1 SPI初始化

void init_spi1(void)
{
	RCC->AHB1ENR|=1<<1;		//开启PB时钟
	RCC->AHB1ENR|=1<<0;		//开启PA时钟
	RCC->APB2ENR|=1<<12;	//开启SPI1时钟
	
	
	#if	SPI1_NSSMODE==0
	init_spi1_nss1();
	#else
	GPIOA->MODER|=2<<8;		//PA4功能复用
	GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<8;		//PA4上拉输出
	GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1
	#endif
	
	GPIOA->MODER|=2<<10;		//PA5功能复用
	GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<10;		//PA3上拉输出
	GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1
	
	GPIOA->MODER|=2<<12;		//PA6功能复用
	GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<12;		//PA6上拉输出
	GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1
			
	GPIOA->MODER|=2<<14;		//PA7功能复用
	GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<14;		//PA7上拉输出
	GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1
	
	
	SPI1->CR1&=~(1<<10);		//全双工模式
	
	#if	SPI1_NSSMODE==0
	SPI2->CR1|=1<<9;	//软件控制nss
	SPI2->CR1|=1<<8;	//选择芯片上的引脚
	#else
	SPI1->CR2|=1<<2;		//硬件控制NSS引脚
	#endif
	
	SPI1->CR1|=1<<2;	//作为SPI主机
	
	#if	SPI1_DATALENGTH==8
	SPI1->CR1&=~(1<<11);	//数据长度为8位
	#else
	SPI1->CR1|=(1<<11);		//数据长度为16位
	#endif
	
	#if SPI1_DMA_TX_EN==1
	SPI1->CR2|=1<<1;	//开启DMA传输
	#else
	SPI1->CR2&=~(1<<1);	//开启DMA传输
	#endif
	
	SPI1->CR1|=1<<0;	//从第二位开始采集数据
	SPI1->CR1|=1<<1;	//空闲状态下时钟保持高电平
	
	SPI1->CR1|=SPI_SPEED_8<<3;		//APB2上84MHz,8分频
	SPI1->CR1&=~(1<<7);	//先发送MSB,高位先发送
	
	
	SPI1->I2SCFGR&=~(1<<11);	//关闭I2S功能,使用SPI
}

说一下区别吧,很少,就一句话

SPI的CR2的第一位,解释如下

这里注意一下SPI的发送与接收是分开的,我们可以根据需要开启其中的DMA。

3.2 DMA初始化

//初始化DMA2 组3 通道3
//SPI1_TX
void init_DMA2_S3C3(unsigned char *SPIData,unsigned short SPIWEI)
{	
	DMA2_Stream3 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA2_Stream3 ->PAR  = (unsigned int)(&SPI1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA2_Stream3 ->M0AR = (unsigned int)(SPIData);
	
	DMA2_Stream3 ->NDTR = SPIWEI;			// 一次传输数量
	DMA2_Stream3 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA2_Stream3 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA2_Stream3 ->CR &=~(1<<8);	//非循环模式
	DMA2_Stream3 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA2_Stream3 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA2_Stream3 ->CR &= ~(1<<9); //外设非增量模式
	DMA2_Stream3 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA2_Stream3 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA2_Stream3 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA2_Stream3 ->CR &= ~(3<23);   //存储器突发单次传输
	
	DMA2_Stream3 ->CR |= 3<<25;   //通道3
	DMA2_Stream3 ->CR |= 1<<0;    //使能数据流
}

没有什么特别的地方,和存储器去寄存器的操作方式一致。

3.3 发送

unsigned char SPI1_WR(unsigned char SPI1MODE,unsigned char SPI1Data)
{
	unsigned char temp=0;
	switch(SPI1MODE)
	{
		case SPI1_WRMODE:
				
			//清除全部设置
			SPI1->CR1&=~(1<<15);	
			SPI1->CR1&=~(1<<10);
		
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
		
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<0)==0);	//等待接受缓冲为空
			temp=SPI1->DR;		//接受数据
			while((SPI1->SR&1<<7)==1);	//等待发送缓冲为空
			
			#if SPI1_NSSMODE==0
			;
			#else
			SPI1->CR1&=~(1<<6);	//关闭SPI
			#endif
		break;
		
		
		
		case SPI1_WOMODE:
			
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
			SPI1->CR1&=~(1<<15);	//清除模式设置
			SPI1->CR1&=~(1<<10);	//清除模式设置
            while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
		
			#if SPI1_DMA_TX_EN==1
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#else
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#endif
		
			#if SPI1_NSSMODE==0
			#else
			#endif
			
			while((SPI1->SR&1<<7)==1);	//等待总线空闲
			SPI1->CR1&=~(1<<6);	//关闭SPI
		break;
		
		
		case SPI1_ROMODE:
			SPI1->CR1&=~(1<<10);//清除模式设置
			SPI1->CR1|=1<<10;	//半双工模式只读
			temp=SPI1->DR;		//接受数据
		break;
	}
	return temp;
}

这里稍微说说区别

核心在于两个TXE的判断

第一个TXE就是手册上的第一个判断

第二个也就是后面的,但是由于DMA的存在,所以下面无需我们再判断,当一个数据搬运完成,就会重新再次搬运直达搬运完所有数据TXE才会拉高,所以这里我们无需进行循环判断

4.测试

最终程序

spi.c

#include "spi.h"


void init_spi1(void)
{
	RCC->AHB1ENR|=1<<1;		//开启PB时钟
	RCC->AHB1ENR|=1<<0;		//开启PA时钟
	RCC->APB2ENR|=1<<12;	//开启SPI1时钟
	
	
	#if	SPI1_NSSMODE==0
	init_spi1_nss1();
	#else
	GPIOA->MODER|=2<<8;		//PA4功能复用
	GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<8;		//PA4上拉输出
	GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1
	#endif
	
	GPIOA->MODER|=2<<10;		//PA5功能复用
	GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<10;		//PA3上拉输出
	GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1
	
	GPIOA->MODER|=2<<12;		//PA6功能复用
	GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<12;		//PA6上拉输出
	GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1
			
	GPIOA->MODER|=2<<14;		//PA7功能复用
	GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZ
	GPIOA->PUPDR|=1<<14;		//PA7上拉输出
	GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1
	
	
	SPI1->CR1&=~(1<<10);		//全双工模式
	
	#if	SPI1_NSSMODE==0
	SPI2->CR1|=1<<9;	//软件控制nss
	SPI2->CR1|=1<<8;	//选择芯片上的引脚
	#else
	SPI1->CR2|=1<<2;		//硬件控制NSS引脚
	#endif
	
	SPI1->CR1|=1<<2;	//作为SPI主机
	
	#if	SPI1_DATALENGTH==8
	SPI1->CR1&=~(1<<11);	//数据长度为8位
	#else
	SPI1->CR1|=(1<<11);		//数据长度为16位
	#endif
	
	#if SPI1_DMA_TX_EN==1
	SPI1->CR2|=1<<1;	//开启DMA传输
	#else
	SPI1->CR2&=~(1<<1);	//开启DMA传输
	#endif
	
	SPI1->CR1|=1<<0;	//从第二位开始采集数据
	SPI1->CR1|=1<<1;	//空闲状态下时钟保持高电平
	
	SPI1->CR1|=SPI_SPEED_256<<3;		//APB2上84MHz,8分频
	SPI1->CR1&=~(1<<7);	//先发送MSB,高位先发送
	
	
	SPI1->I2SCFGR&=~(1<<11);	//关闭I2S功能,使用SPI
}

unsigned char SPI1_WR(unsigned char SPI1MODE,unsigned char SPI1Data)
{
	unsigned char temp=0;
	switch(SPI1MODE)
	{
		case SPI1_WRMODE:
				
			//清除全部设置
			SPI1->CR1&=~(1<<15);	
			SPI1->CR1&=~(1<<10);
		
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
		
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<0)==0);	//等待接受缓冲为空
			temp=SPI1->DR;		//接受数据
			while((SPI1->SR&1<<7)==1);	//等待发送缓冲为空
			
			#if SPI1_NSSMODE==0
			;
			#else
			SPI1->CR1&=~(1<<6);	//关闭SPI
			#endif
		break;
		
		
		
		case SPI1_WOMODE:
			
			#if SPI1_NSSMODE==0
		
			#else
			SPI1->CR1|=(1<<6);	//开启SPI
			#endif
			SPI1->CR1&=~(1<<15);	//清除模式设置
			SPI1->CR1&=~(1<<10);	//清除模式设置
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
		
			#if SPI1_DMA_TX_EN==1
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#else
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			SPI1->DR=SPI1Data;	//发送数据
			while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空
			#endif
		
			#if SPI1_NSSMODE==0
			#else
			#endif
			
			while((SPI1->SR&1<<7)==1);	//等待总线空闲
			SPI1->CR1&=~(1<<6);	//关闭SPI
		break;
		
		
		case SPI1_ROMODE:
			SPI1->CR1&=~(1<<10);//清除模式设置
			SPI1->CR1|=1<<10;	//半双工模式只读
			temp=SPI1->DR;		//接受数据
		break;
	}
	return temp;
}

spi.h

#ifndef SPI_H__
#define SPI_H__

#include "stm32f4xx.h"

#define SPI_SPEED_2 	0
#define SPI_SPEED_4 	1
#define SPI_SPEED_8 	2
#define SPI_SPEED_16 	3
#define SPI_SPEED_32 	4
#define SPI_SPEED_64 	5
#define SPI_SPEED_128 6
#define SPI_SPEED_256 7

//定义空闲状态下的时钟状态,为1则是高电平,否则是低电平
#define SPI1_CPOL	1
//定义数据长度
#define SPI1_DATALENGTH	8

#define SPI1_NSS1UP			do{GPIOB->ODR|=1<<12;}while(0)
#define SPI1_NSS1DOWN		do{GPIOB->ODR&=~(1<<12);}while(0)

//是否软件管理NSS引脚
//0	软件管理
//1	硬件管理
#define SPI1_NSSMODE	1

//是否开启SPI1发送的DMA功能
//0 关闭
//1 开启
#define SPI1_DMA_TX_EN	1
//是否开启SPI1接收的DMA功能
//0 关闭
//1 开启
#define SPI1_DMA_RX_EN	0

//SPI2通信模式
//0	全双工通信
//1	只发送
//2	只接收
#define SPI1_WRMODE	0
#define SPI1_WOMODE	1
#define SPI1_ROMODE	2



#endif

DMA

//初始化DMA2 组3 通道3
//SPI1_TX
void init_DMA2_S3C3(unsigned char *SPIData,unsigned short SPIWEI)
{	
	DMA2_Stream3 ->CR   = 0;//禁止数据流 ,才能写寄存器 
	
	//外设地址寄存器
	//将所需寄存器的地址放入PAR寄存器
	DMA2_Stream3 ->PAR  = (unsigned int)(&SPI1->DR);
	
	//数据流地址寄存器
	//M1AR仅在双通道模式下有用
	//将数据所在地址给M0AR寄存器
	DMA2_Stream3 ->M0AR = (unsigned int)(SPIData);
	
	DMA2_Stream3 ->NDTR = SPIWEI;			// 一次传输数量
	DMA2_Stream3 ->FCR  = 0x21;		//FIFO所有配置失效
	DMA2_Stream3 ->CR |= 1<< 6;		//储存器到外设模式
	
	//循环模式:
	//当NDTR寄存器减到0时自动重装
	//单次模式(普通模式):
	//NDTR减到0后停止DMA
	DMA2_Stream3 ->CR &=~(1<<8);	//非循环模式
	DMA2_Stream3 ->CR &=~(3<<11);	//外设数据长度:8位
	DMA2_Stream3 ->CR &=~(3<<13);	//存储器数据长度:8位
	
	DMA2_Stream3 ->CR &= ~(1<<9); //外设非增量模式
	DMA2_Stream3 ->CR |= 1<<10;   //存储器增量模式,指针增加,可用于传输数组
	DMA2_Stream3 ->CR |= 1<<16;   //中等优先级
	
	//突发传输
	//DMA占用CPU总线时间,此时CPU无法工作
	//一个节拍:传输多少次32位变量
	//应用场景:从ram里读出字节
	DMA2_Stream3 ->CR &= ~(3<<21);   //外设突发单次传输
	DMA2_Stream3 ->CR &= ~(3<23);   //存储器突发单次传输
	
	DMA2_Stream3 ->CR |= 3<<25;   //通道3
	DMA2_Stream3 ->CR |= 1<<0;    //使能数据流
}

我们在主程序里如何使用呢?首先初始化SPI,然后是DMA,最后触发传输即可。这里我传输5个数据0x01,0x02,0x04,0x01最后一位应该是00

unsigned char spi_test_data[5]={0x01,0x02,0x04,0x01};
init_spi1();//初始化SPI1
init_DMA2_S3C3(spi_test_data,5);//初始化DMA
SPI1_WR(SPI1_WOMODE,5);//发送

可以看到效果拔群啊,CS管脚也没问题。

5.结语

至此完整的SPI完全出来了,手册上说这样的效果可以实现SPI的最高速率,但是我没有测试过。刚刚看手册的时候发现DMA有乒乓功能,嗯?难道这样一来速率还能在高?那么还是老样子有问题评论区见,我们下篇文章见。

### 回答1: STM32F407芯片具有DMA功能和SPI接口,可以实现DMA SPI对Flash的读写。下面是一个简要的描述: 首先,需要确保STM32F407SPI控制器正确配置。配置SPI控制器的模式(主模式/从模式)、数据位宽、时钟极性和相位等参数。确定好SPI的数据输入输出引脚。 接下来,配置DMA控制器,使其能够与SPI控制器进行数据传输。配置DMA的数据传输方向、传输大小、传输通道和传输模式等参数。 然后,将待传输的数据从Flash中读取出来并存储在单独的缓冲区中。可以使用读取函数来实现,例如: ```c uint8_t dataBuffer[256]; uint32_t address = 0x08000000; uint32_t size = 256; FLASH_Read(address, dataBuffer, size); //从Flash中读取数据到缓冲区 ``` 然后,将从Flash读取的数据传输到SPI接口,使用DMA来完成数据传输。可以使用发送函数来实现,例如: ```c SPI_DMA_SendData(dataBuffer, size); //使用DMA传输数据到SPI ``` 需要注意的是,在使用DMA进行SPI数据传输时,将数据写入SPI的数据寄存器后,DMA控制器会自动将数据从缓冲区传输到SPI接口,并在传输完成后产生中断信号,通知传输已完成。 如果需要进行Flash写操作,则需要将要写入的数据存储到缓冲区中,然后再使用DMA将数据传输到SPI接口,最后使用Flash编程函数将数据写入Flash中。 以上是一个简要的描述,实际的代码实现需要根据具体情况进行调整和优化。 ### 回答2: STM32F407实现DMA SPI对Flash的读写可以通过以下步骤实现: 1. 配置SPI接口:首先需要配置SPI接口,包括主从模式、数据位长度、时钟极性和相位、CPOL、CPHA等参数。在SPI控制寄存器中配置这些参数。 2. 配置DMA通道:使用DMA来传输数据,可以提高读写效率。选择一个合适的DMA通道,并设置传输方向、数据宽度和缓冲区地址。 3. 配置Flash:根据Flash的芯片型号和规格,选择合适的操作命令和地址,将其配置到SPI发送缓冲区中。 4. 启动DMA传输:通过设置DMA控制寄存器,启动DMA传输,并等待传输完成的中断或状态标志。 5. 数据传输:在中断或状态标志表明DMA传输完成后,将接收到的数据从SPI接收缓冲区中读取出来,并将其写入Flash或从Flash中读取。 6. 完成操作:根据需求,可以通过判断Flash状态寄存器的标志位,来确认数据读写是否成功。如果成功,可以继续执行其他操作;如果失败,可以进行错误处理。 需要注意的是,Flash的读写操作必须按照其规格和要求进行,包括地址对齐、写保护状态等。另外,还需要根据具体的编程环境和开发板,在程序中选择合适的库函数和API来执行相应的配置和操作。 ### 回答3: STM32F407是一款高性能的单片机,通过DMA(Direct Memory Access)和SPI(Serial Peripheral Interface)可以实现对Flash的读写操作。 首先,我们需要配置SPI接口。在STM32F407中,SPI接口使用4条I/O线来进行通信,即SCK、MISO、MOSI和SS(片选信号)。我们需要将这些线连接到Flash芯片,并在单片机上进行相应的引脚配置。 然后,我们需要配置DMA控制器。DMA可以将数据在存储器和外设之间进行直接传输,提高数据传输效率。在STM32F407中,有多个DMA通道可供选择。我们选择一个合适的通道,并进行相应的配置,包括数据长度、传输方向等。 接下来,我们需要编写读写Flash的代码。读取Flash时,我们可以向Flash芯片发送读取命令,并通过SPI接收到的数据进行存储;写入Flash时,我们将要写入的数据送入DMA缓冲区,并通过SPI发送给Flash芯片。 在读写过程中,DMA控制器将负责将数据从存储器传输到SPI接口(写入Flash)或从SPI接口传输到存储器(读取Flash)。这样,我们可以将主处理器从数据传输中解放出来,提高系统的并发性。 最后,我们需要进行相应的测试和调试,确保读写操作的正确性。可通过读取Flash中的数据验证读取操作的准确性,并通过编写检验程序验证写入操作的准确性。 总之,通过配置SPI接口和DMA控制器,我们可以实现STM32F407对Flash的读写操作。这种方式能够提高数据传输效率,减轻主处理器的负担,从而提高系统的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值