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==0init_spi1_nss1();#elseGPIOA->MODER|=2<<8;		//PA4功能复用GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZGPIOA->PUPDR|=1<<8;		//PA4上拉输出GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1#endifGPIOA->MODER|=2<<10;		//PA5功能复用GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZGPIOA->PUPDR|=1<<10;		//PA3上拉输出GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1GPIOA->MODER|=2<<12;		//PA6功能复用GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZGPIOA->PUPDR|=1<<12;		//PA6上拉输出GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1GPIOA->MODER|=2<<14;		//PA7功能复用GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZGPIOA->PUPDR|=1<<14;		//PA7上拉输出GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1SPI1->CR1&=~(1<<10);		//全双工模式#if	SPI1_NSSMODE==0SPI2->CR1|=1<<9;	//软件控制nssSPI2->CR1|=1<<8;	//选择芯片上的引脚#elseSPI1->CR2|=1<<2;		//硬件控制NSS引脚#endifSPI1->CR1|=1<<2;	//作为SPI主机#if	SPI1_DATALENGTH==8SPI1->CR1&=~(1<<11);	//数据长度为8位#elseSPI1->CR1|=(1<<11);		//数据长度为16位#endif#if SPI1_DMA_TX_EN==1SPI1->CR2|=1<<1;	//开启DMA传输#elseSPI1->CR2&=~(1<<1);	//开启DMA传输#endifSPI1->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后停止DMADMA2_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;   //通道3DMA2_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#elseSPI1->CR1|=(1<<6);	//开启SPI#endifwhile((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;#elseSPI1->CR1&=~(1<<6);	//关闭SPI#endifbreak;case SPI1_WOMODE:#if SPI1_NSSMODE==0#elseSPI1->CR1|=(1<<6);	//开启SPI#endifSPI1->CR1&=~(1<<15);	//清除模式设置SPI1->CR1&=~(1<<10);	//清除模式设置while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#if SPI1_DMA_TX_EN==1while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#elsewhile((SPI1->SR&1<<1)==0);	//等待发送缓冲为空SPI1->DR=SPI1Data;	//发送数据while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#endif#if SPI1_NSSMODE==0#else#endifwhile((SPI1->SR&1<<7)==1);	//等待总线空闲SPI1->CR1&=~(1<<6);	//关闭SPIbreak;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==0init_spi1_nss1();#elseGPIOA->MODER|=2<<8;		//PA4功能复用GPIOA->OSPEEDR|=2<<8;	//端口速度50MHZGPIOA->PUPDR|=1<<8;		//PA4上拉输出GPIOA->AFR[0]|=5<<16;			//功能复用到SPI1#endifGPIOA->MODER|=2<<10;		//PA5功能复用GPIOA->OSPEEDR|=2<<10;	//端口速度50MHZGPIOA->PUPDR|=1<<10;		//PA3上拉输出GPIOA->AFR[0]|=5<<20;			//功能复用到SPI1GPIOA->MODER|=2<<12;		//PA6功能复用GPIOA->OSPEEDR|=2<<12;	//端口速度50MHZGPIOA->PUPDR|=1<<12;		//PA6上拉输出GPIOA->AFR[0]|=5<<24;			//功能复用到SPI1GPIOA->MODER|=2<<14;		//PA7功能复用GPIOA->OSPEEDR|=2<<14;	//端口速度50MHZGPIOA->PUPDR|=1<<14;		//PA7上拉输出GPIOA->AFR[0]|=5<<28;			//功能复用到SPI1SPI1->CR1&=~(1<<10);		//全双工模式#if	SPI1_NSSMODE==0SPI2->CR1|=1<<9;	//软件控制nssSPI2->CR1|=1<<8;	//选择芯片上的引脚#elseSPI1->CR2|=1<<2;		//硬件控制NSS引脚#endifSPI1->CR1|=1<<2;	//作为SPI主机#if	SPI1_DATALENGTH==8SPI1->CR1&=~(1<<11);	//数据长度为8位#elseSPI1->CR1|=(1<<11);		//数据长度为16位#endif#if SPI1_DMA_TX_EN==1SPI1->CR2|=1<<1;	//开启DMA传输#elseSPI1->CR2&=~(1<<1);	//开启DMA传输#endifSPI1->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#elseSPI1->CR1|=(1<<6);	//开启SPI#endifwhile((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;#elseSPI1->CR1&=~(1<<6);	//关闭SPI#endifbreak;case SPI1_WOMODE:#if SPI1_NSSMODE==0#elseSPI1->CR1|=(1<<6);	//开启SPI#endifSPI1->CR1&=~(1<<15);	//清除模式设置SPI1->CR1&=~(1<<10);	//清除模式设置while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#if SPI1_DMA_TX_EN==1while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#elsewhile((SPI1->SR&1<<1)==0);	//等待发送缓冲为空SPI1->DR=SPI1Data;	//发送数据while((SPI1->SR&1<<1)==0);	//等待发送缓冲为空#endif#if SPI1_NSSMODE==0#else#endifwhile((SPI1->SR&1<<7)==1);	//等待总线空闲SPI1->CR1&=~(1<<6);	//关闭SPIbreak;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后停止DMADMA2_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;   //通道3DMA2_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有乒乓功能,嗯?难道这样一来速率还能在高?那么还是老样子有问题评论区见,我们下篇文章见。

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

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

相关文章

【动手学深度学习】5.2 参数管理(个人向笔记+代码注释)

之前的课程中&#xff0c;我们只是通过深度学习框架完成训练的工作&#xff0c;而忽略了操作参数的具体细节。所以我们我们介绍的内容有&#xff1a; 访问参数&#xff0c;用于调试&#xff0c;诊断和可视化参数初始化在不同的模型组件间共享参数 下面是一个有单隐藏层的多层感…

如何把视频变成自己的原创?提升视频原创度的7个技巧

在短视频平台发布作品时&#xff0c;时常因为原创问题&#xff0c;而被限流。如何在海量视频中脱颖而出&#xff0c;让自己的作品具有独特性和原创性&#xff0c;是每位创作者都需要思考的问题。本文将详细介绍如何通过一系列前期准备和后期处理技巧&#xff0c;将视频素材转化…

模版进阶 非类型模版参数

一.模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将该参数当成常量来使用。 #i…

乌班图基础设施安装之Mysql8.0+Redis6.X安装

简介&#xff1a;云服务器基础设施安装之 Mysql8.0Redis6.X 安装 Docker安装 # 按照依赖 yum install -y yum-utils device-mapper-persistent data lvm2 Docker Mirror 从去年开始. hub.docker.com[1] 在国内的访问速度极慢. 当时大家主要还是依赖国内的一些镜像源: 如中科…

操作系统 | 学习笔记 | 王道 | 4.3 文件系统

4.3 文件系统 4.3.1 文件系统结构 文件系统(File system)提供高效和便捷的磁盘访问&#xff0c;以便允许存储、定位、提取数据。 用一个例子来辅助记忆文件系统的层次结构&#xff1a; 假设某用户请求删除文件"D:/工作目录/学生信息.xIsx"的最后100条记录。 用户需…

在 Windows 11 安卓子系统中安装 APK 的操作指南

这个软件好像不可以在纯android系统中使用&#xff08;不知道是缺了什么&#xff09;&#xff0c;其他对于android的虚拟机要不缺少必要功能组件&#xff0c;要不性能过于低下。本方法致力于在带有谷歌框架WSA中运行该APK 在 Windows 11 安卓子系统中安装 APK 的操作指南 本指…

消息摘要算法

算法特点 a) 消息摘要算法/单向散列函数/哈希函数 b) 不同长度的输入&#xff0c;产生固定长度的输出 c) 散列后的密文不可逆 d) 散列后的结果唯一 e) 哈希碰撞 f) 一般用于校验数据完整性、签名sign 由于密文不可逆&#xff0c;所以服务端也无法解密 想要验证&#xf…

前端 | Uncaught (in promise) undefined

前端 | Uncaught (in promise) undefined 最近开发运行前端项目时&#xff0c;经常预计控制台报错 &#xff0c;如下图&#xff1a; 这里我总结下&#xff0c;这种报错的场景和原因&#xff0c;并通过实际代码案例帮助小伙伴更好理解下 。 文章目录 前端 | Uncaught (in promi…

若依前端后打成一个JAR包部署

客户需要将项目前后端作为一个整体打包成jar&#xff0c;不使用nginx方式转发。使用框架是若依前后端分离&#xff0c;后端springboot&#xff0c;前端vue&#xff0c;目的就是把vue打入jar。 一、前端修改 ruoyi-ui/src/router/index.js文件 &#xff0c;将 mode: ‘history’…

vue-jsonp的使用和腾讯地图当前经纬度和位置详情的获取

1.下载&#xff1a; npm install –save vue-jsonp2.main.js中引入&#xff1a; //腾讯逆地址解析会用到jsonp import {VueJsonp} from vue-jsonp; Vue.use(VueJsonp);3.腾讯地图中使用 uniapp中获取*经纬度*和通过经纬度获取当前**位置详情** //获取当前经纬度 getLocation…

职场上的人情世故你知多少

1.发微信找人帮忙&#xff0c;半天不回&#xff0c;那基本没戏了&#xff0c;不要再打扰了&#xff0c;懂得都懂。 2.能力越大&#xff0c;事情越多&#xff0c;要懂得张弛有度&#xff0c;不要把自己全抛出去&#xff0c;给自己留点余地&#xff0c;毕竟你不知道别人如何暗地…

Windows电脑本地安装AI文生音乐软件结合内网穿透远程访问制作

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天和大家分享一下在Windows系统电脑上本地快速部署一个文字生成音乐的AI创作服务MusicGPT&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问使用进行AI音…

光伏“地图导航”:光照、政策、电价一目了然

当代的快节奏生活中&#xff0c;地图导航的精准定位技术如同一盏照明灯&#xff0c;为我们照亮了前方的道路。许多光伏人纷纷反映&#xff0c;如果光伏也有这样的地图就好了&#xff0c;能够结合各种建设光伏的因素快速完成选址&#xff0c;能够极大地提高效率。今天小编就来分…

PAT甲级-1013 Battle Over Cities

题目 题目大意 给定一个城市图&#xff0c;如果攻陷一个城市&#xff0c;该城市连接的所有路都要被销毁。要求计算出连通剩余的城市最少需要修建几条路。该图有n个顶点&#xff0c;m条边&#xff0c;k个重点城市。分别求出每个重点城市被攻陷&#xff0c;连通剩余城市需要修建…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

【GESP】C++一级练习BCQM3037,简单计算,国庆七天乐收官

又回到了简单计算的题目&#xff0c;继续巩固练习。 题解详见&#xff1a;https://www.coderli.com/gesp-1-bcqm3037/ 【GESP】C一级练习BCQM3037&#xff0c;简单计算&#xff0c;国庆七天乐收官 | OneCoder又回到了简单计算的题目&#xff0c;继续巩固练习。https://www.cod…

内网渗透-隧道代理转发

文章目录 前言环境搭建工具清单工具使用Frp命令执行实验 Lcx命令执行实验 reGeorg命令执行实验Proxifier ew(EarthWorm)正向代理命令执行实验 反向代理命令执行实验SocksCap netsh命令执行 pingtunnel命令执行实验 ngrok命令执行&&实验 cs命令执行实验 前言 本文章介绍…

10/11

一、ARM课程大纲 二、ARM课程学习的目的 2.1 为了找到一个薪资水平达标的工作&#xff08;单片机岗位、驱动开发岗位&#xff09; 应用层(APP) 在用户层调用驱动层封装好的API接口&#xff0c;编写对应的API接口 ----------------------------------------------------…

Redis:通用命令 数据类型

Redis&#xff1a;通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis&#xff0c;在Redis中&#xff0c;key的类型都是字符串&#xff0c;而value有多种类型&#xff0c;每种类型都有自己的操作命…

pytorch 与 pytorch lightning, pytorch geometric 各个版本之间的关系

主要参考 官方的给出的意见&#xff1b; 1. pytorch 与 pytorch lightning 各个版本之间的关系 lightning 主要可以 适配多个版本的 torch; https://lightning.ai/docs/pytorch/latest/versioning.html#compatibility-matrix&#xff1b; 2. pytorch 与 pytorch geometric 各…