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.rhkb.cn/news/444738.html

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

相关文章

Spring Boot 学习之路 -- Thymeleaf 模板引擎

前言 最近因为业务需要&#xff0c;被拉去研究后端的项目&#xff0c;代码框架基于 Spring Boot&#xff0c;后端对我来说完全小白&#xff0c;需要重新学习研究…出于个人习惯&#xff0c;会以 Blog 文章的方式做一些记录&#xff0c;文章内容基本来源于「 Spring Boot 从入门…

微信小程序-分包加载

一.分包的意义 小程序是由多个页面构成&#xff0c;为了因为代码量多&#xff0c;体积大导致用户打开速度变慢&#xff0c;小程序提供了分包加载数据。 分包加载数据&#xff0c;只有在主包调用分包某一个页面时候才会调用加载分包。即就是按需加载。 整个小程序不能超过20M&a…

golang grpc进阶

protobuf 官方文档 基本数据类型 .proto TypeNotesGo Typedoublefloat64floatfloat32int32使用变长编码&#xff0c;对于负值的效率很低&#xff0c;如果你的域有可能有负值&#xff0c;请使用sint64替代int32uint32使用变长编码uint32uint64使用变长编码uint64sint32使用变长…

滚柱导轨适配技巧与注意事项!

滚柱导轨是一种重要的传动元件&#xff0c;它由滚柱作为滚动体。用于连接机床的运动部件和床身基座&#xff0c;其设计旨在提供高承载能力和高刚度&#xff0c;适用于重型机床和精密仪器&#xff0c;而滚柱导轨的适配方法对于确保机械设备的高精度运行至关重要。 滚柱导轨的适配…

大数据分析案例-基于逻辑回归算法构建抑郁非抑郁推文识别模型

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集

介绍Java

Java简介 Java是一门由Sun公司&#xff08;现被Oracle收购&#xff09;在1995年开发的计算机编程语言&#xff0c;其主力开发人员是James Gosling&#xff0c;被称为Java之父。Java在被命名为“Java”之前&#xff0c;实际上叫做Oak&#xff0c;这个名字源于James Gosling望向…

Unite Barcelona主题演讲回顾:深入了解 Unity 6

本周&#xff0c;来自世界各地的 Unity 开发者齐聚西班牙巴塞罗那&#xff0c;参加 Unite 2024。本次大会的主题演讲持续了一个多小时&#xff0c;涵盖新功能的介绍、开发者成功案例的分享&#xff0c;以及在编辑器中进行的技术演示&#xff0c;重点展示了 Unity 6 在实际项目中…

学习python自动化——pytest单元测试框架

一、什么是pytest 单元测试框架&#xff0c;unittest&#xff08;python自带的&#xff09;&#xff0c;pytest&#xff08;第三方库&#xff09;。 用于编写测试用例、收集用例、执行用例、生成测试结果文件&#xff08;html、xml&#xff09; 1.1、安装pytest pip instal…

Spring AI 介绍与入门使用 -- 一个Java版Langchain

Langchain 是什么&#xff1f; Langchain 是一个Python 的AI开发框架&#xff0c;它集成了模型输入输出、检索、链式调用、内存记忆&#xff08;Memory&#xff09;、Agents以及回调函数等功能模块。通过这些模块的协同工作&#xff0c;它能够支持复杂的对话场景和任务执行流程…

C语言 | Leetcode C语言题解之第460题LFU缓存

题目&#xff1a; 题解&#xff1a; /* 数值链表的节点定义。 */ typedef struct ValueListNode_s {int key;int value;int counter;struct ValueListNode_s *prev;struct ValueListNode_s *next; } ValueListNode;/* 计数链表的节点定义。 其中&#xff0c;head是数值链表的头…

多点低压差分(M-LVDS)线路驱动器和接收器——MS2111

MS2111 是多点低压差分 (M-LVDS) 线路驱动器和接收器。经过 优化&#xff0c;可运行在高达 200Mbps 的信号速率下。所有部件均符合 M LVDS 标准 TIA / EIA-899 。该驱动器的输出支持负载低至 30Ω 的多 点总线。 MS2111 的接收器属于 Type-2 &#xff0c; 可在 -1…

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

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

性能测试工具locust —— Python脚本参数化!

1.1.登录用户参数化 在测试过程中&#xff0c;经常会涉及到需要用不同的用户登录操作&#xff0c;可以采用队列的方式&#xff0c;对登录的用户进行参数化。如果数据要保证不重复&#xff0c;则取完不再放回&#xff1b;如可以重复&#xff0c;则取出后再返回队列。 def lo…

std::future::then的概念和使用方法

std::future::then是 C 中用于异步操作的一种机制&#xff0c;它允许在一个异步任务完成后&#xff0c;接着执行另一个操作&#xff08;即延续操作&#xff09;。以下是关于 std::future::then 的概念和使用方法&#xff1a; 1. 概念&#xff1a; std::future::then 的主要目…

Chrome清除nslookup解析记录 - 强制http访问 - 如何禁止chrome 强制跳转https

步骤&#xff1a; 地址栏输入 chrome://net-internals/#hsts在Delete domain 栏的输入框中输入要http访问的域名&#xff0c;然后点击“delete”按钮最后在Query domain 栏中搜索刚才输入的域名&#xff0c;点击“query”按钮后如果提示“Not found”即可&#xff01; 办法来自…

Java | Leetcode Java题解之第459题重复的子字符串

题目&#xff1a; 题解&#xff1a; class Solution {public boolean repeatedSubstringPattern(String s) {return kmp(s s, s);}public boolean kmp(String query, String pattern) {int n query.length();int m pattern.length();int[] fail new int[m];Arrays.fill(fa…

Hunuan-DiT代码阅读

一 整体架构 该模型是以SD为基础的文生图模型&#xff0c;具体扩散模型原理参考https://zhouyifan.net/2023/07/07/20230330-diffusion-model/&#xff0c;代码地址https://github.com/Tencent/HunyuanDiT&#xff0c;这里介绍 Full-parameter Training 二 输入数据处理 这里…

E系列I/O模块在锂电装备制造系统的应用

为了满足电池生产线对稳定性和生产效率的严苛要求&#xff0c;ZLG致远电子推出高速I/O应用方案&#xff0c;它不仅稳定可靠&#xff0c;而且速度快&#xff0c;能够迅速响应生产需求。 锂电池的生产工艺较为复杂&#xff0c;大致分为三个主要阶段&#xff1a;极片制作、电芯制作…

单点登录Apereo CAS 7.1客户端集成教程

从上一篇部署并成功运行CAS服务端后,我们已经能通过默认的账号密码进行登录。 上篇地址:单点登录Apereo CAS 7.1安装配置教程-CSDN博客 本篇我们将开始对客户端进行集成。 CAS中的客户端,就是指我们实际开发的各个需要登录认证的应用。现在,跟着笔者的步伐,一起探索如何…

springmvc直接访问 上下文路径 302 后路径更改并跳转源码解析

【问题现状】 application.yml 配置如下属性&#xff1a; server:servlet:context-path: /learning直接访问&#xff1a;http://localhost:8888/learning 路径时&#xff0c;会返回302的响应状态&#xff1b;并跳转路径&#xff1a;http://localhost:8888/learning/ (原路径后…