STM32学习笔记之SPI_DMA寄存器级操作

发布者:紫菜包饭最新更新时间:2016-06-15 来源: eefocus关键字:STM32  SPI  DMA寄存器 手机看文章 扫描二维码
随时随地手机看文章

一、实验目标

学会配置STM32的SPI寄存器和DMA寄存器,实现STM32的SPI1与SPI2通信功能,每次发送一字节数据,并可多次发送,如果接收的数据正确,则点亮LED灯。

二、实验目的

加入DMA的SPI通信相对于普通SPI通信有什么好处?ST给SPI加了DMA功能出于什么目的?我觉得这是很重要的一个问题,一直边学习边想。以下是我的看法:

减少CPU负荷?我想这应该是DMA最主要的功能,可是对于SPI通信来说,其实大部分时候我们需要根据发送的指令->目标器件的应答来决定下一个指令,所以此时CPU还是需要一直等待每次通信的结束。而且像SD卡的操作,是一个顺序流的指令操作过程,用中断也不容易控制。那到底加入了DMA有什么好处?仔细查看了STM32F10xxx的用户手册,发现这么一行字“连续和非连续传输:当在主模式下发送数据时,如果软件足够快,能够在检测到每次TXE的上升沿(或TXE中断),并立即在正在进行的传输结束之前写入SPI_DR寄存器,则能够实现连续的通信;此时,在每个数据项的传输之间的SPI时钟保持连续,同时BSY位不会被清除。如果软件不够快,则会导致不连续的通信;这时,在每个数据传输之间会被清除”以及




    		    STM32学习笔记之SPI_DMA寄存器级操作

也就是说如果连续传输而不使用DMA的话,需要CPU不停检测TXE并很快地置入SPI->DR的值,对于复杂程序的话这是很难达到的,而如果使用DMA,就可以轻易实现连续传输,CPU只需等待其完成就好。我想到的一个应用就是在写SD卡的时候,每次写一个块512字节,就可以用到,能提高SD卡的写入数据速率。

其次还可以降低功耗,记得数字集成电路老师说过一句话“软件上降低数字电路功耗的一个方法就是减少电平转换。”那么连续通信的时候,像SPI的BSY电平转换会大大减少!

最后一点,虽然效果不大,就是如果不是用DMA,那么CPU的工作就是搬运工,把SPI->DR的内容搬到内存存储起来,而如果使用DMA,就省略了这个环节!

我想,为什么实现同一个功能,有的执行起来很流畅,有的却很卡,应该和这些小细节的减载有关吧。

这次先把SPI基本通信写出来,然后再写SPI的连续通信,并看能不能用到SD卡读写上。

三、SPI&DMA分析

1、这里先说明一下SPI的全双工通信(高手略过哈)

SPI全双工通信的特点:一边发送一边接收,硬件上只有一个SPI->DR寄存器和两个缓冲器(发送缓冲器和接收缓冲器),主模式(从模式类似):SPI->DR会先读发送缓冲器,并通过MOSI管脚(Master output Slave Input)一位一位地发送出去,在发送的过程中,SPI->DR的数据会左移(如果是高位先发送),并且会从MISO(Master input Slave output)读入数据填补SPI->DR左移后的空缺。传完8比特后,SPI->DR再把数据并行写入接收缓冲寄存器。所以,SPI1与SPI2的通信过程如下:




    		    STM32学习笔记之SPI_DMA寄存器级操作

配置SPI寄存器的时候,需要注意以下几点:

(1)nss的配置:如果是单主单从,使用nss软件管理,除了用MSTR配置主从设备,还要设置SSM和SSI,只有在SSM位为1时,SSI位才有意义。

(2)主从设备的数据帧格式,时钟沿读写模式要一致;

(3)SPI的寄存器也需要开启DMA使能;

(4)SPI虽然可以发送16bit数据,可是只支持8bitDMA!

2、再说一下DMA

DMA——Direct Memory Access,直接内存存取,作用是独立于CPU,直接建立内存与外设的通信通道。

SPI的DMA操作,就是在SPI->TXE为1时,会向对应的DMA通道发出请求,DMA通道会发出应答信号,SPI收到应答信号后撤销请求信号,DMA撤销应答信号,并把内存值置入发送缓冲,SPI传送开始。接收过程与上面类似。

DMA配置的部分说明:

(1)需要使能RCC寄存器的SPI和DMA时钟,至于辅助时钟,查过网上的讨论,有人说一些外设如果没有开启辅助时钟会不能用,但SPI不需要;

(2)DMA的存储器地址(memorybaseaddr):即变量地址。我们在程序中定义的每个变量,都有对应的内存地址,你想把SPI的接收发送数据存在哪个变量,就将对应变量的地址赋给DMA存储器地址寄存器。如u8 SPI1_TX_Buff的地址是(u32)&SPI1_TX_Buff;u8 SPI1_TX_Buff[512]的地址是(u32)SPI1_TX_Buff。

(3)DMA循环模式:有些资料会译为DMA的循环缓存模式,我觉得不太准确,这里循环的意思是指DMA的传输数量计数器会重置初值,由于DMA每传一个数据,传输数量计数器减一,只有在传输数量计数器的值不为零时,才会响应请求。在循环模式下,当传输计数器的值减为0后,会重新装载;而内存(缓存)地址则不管循环非循环模式,都会在每次传输完成后重置为基地址。所以,如果我们把DMA设置会正常模式,那么在下次传输前,只需对DMA的传输数量计数器重新写入就行。

循环模式一般用于数据更新,比如ADC采用需要不停更新数据。

(4)DMA的外设地址:正点原子的串口DMA实验中,在写外设地址时,都会用一个变量缓存再写入,否则程序就运行不正确,他也不知道为什么,而ST库函数的example中对于外设地址也都是重新define的,所以外设地址最好还是采用#define SPI1_DR_Addr ( (u32)0x4001300C )定义的好。

至于外设地址,可以先从STM32的用户手册“2.3存储器映像”得到起始地址+对应外设所在目录的“寄存器地址映像”标识的偏移地址。例如:从“2.3存储器映像”得到SPI1起始地址0x40013000,从SPI所在目录的“寄存器地址映像”得到SPI->DR的偏移量为0x0C,那么SPI1_DR_Addr就是0x4001300C;

(5)DMA通道开启顺序:按照下图的数字序号依次开启,才能确保数据正确发送。比如①的SPI2_TX_Buff对应的是DMA通道5.




    		    STM32学习笔记之SPI_DMA寄存器级操作

(6)正常模式的第二次发送:DMA发送的时候只需使能DMA就可以开始传送,但是第二次传送之前,需要进行以下步骤:

1、关闭DMA通道;

2、清除DMA传输完成标志以及重置CNDTR传输数量计数器;

3、开启DMA通道,等待传输完成。

四、实验结果

利用SPI1和SPI2进行两次数据传输,并比较SPI1_RX与SPI2_TX,SPI2_RX与SPI1_TX,数据相同点亮LED灯。

在某个论坛看到有人说把SPI的速度设置为2分频传输数据不正确,分析原因是DMA反应不过来。我也试了一下,传输正常,数据正确。(SPI传输速率是用JLINK仿真查看寄存器的)

哦,对了,期间还吃过一个亏,害我调了好久,就是下面的语句:

while( ( DMA1->ISR & (1<<17) ) == 0 ) ; //等待通道5传输完成

我写成:

while( DMA1->ISR & (1<<17) == 0 ) ; //等待通道5传输完成

由于“==”的优先级比“&”高,所以会先执行“(1<<17) == 0”,结果是0,再与上DMA1->ISR,那么相当于while直接跳过了,读不到数据!很低级的错误!所以提醒后来者,看起来可加可不加的括号,还是要加上去的好!

还有一个问题,一直在想DMA传输,那么硬件怎么认为一次传输的结束而停止以及怎样才能开启新一次的传输。我觉得最关键就是DMA的传输数量计数器以及DMA的传输完成标志。只要DMA的计数器不为零,就能响应请求传输,此时就算传输完成标志置位,也能进行DMA响应,只不过你不知道什么时候完成罢了。所以每次传输开始前,程序需要清除标志位并检测到该标志位置位,才知道一次传输是否完成!

续:终于把SPI的DMA弄完了,实现了连续发送和读取的功能,DMA开辟512字节的数组作为内存存储数据(所以连续发送最大的数据量也是512,当然可以在宏定义里面更改),通过num控制要写入或读入的数据量,源代码中有3个函数,一个函数是读写一体的,一个函数是只发送模式,一个函数是只接收模式,都通过测试。唯一的缺陷就是没有进行错误检测,特别说明一下,我把清标志位是放在函数前面而不是函数后面,就是想函数执行完,标志位依然还在,我们可以以此来判断是否有错误。在这里和大家分享一下小经验。

(1)怎么测试?最好的测试方法我觉得就是双机通讯了,由于实验室资源比较好,所以我得以有两个STM32(非MiniSTM32,用的是AG嵌入式开发板)进行测试,所以以上代码都是通过双击测试的,不过我只整理了SPI1主机源代码,需要的自己稍微改一下就可以,程序中有注释!

(2)用双机测试的时候,刚开始我没有共地,导致数据可以接收,但是数据错误!所以紧记,当你使用两个器件通讯或交互时,一定要先检查两个器件是否共地,甚至共源!

(3)如果只有一个STM32其实也可以测试,就是把MISO和MOSI短接,但这个测试方法,用来测试SPI1_ReceiveSendByte(u16 num)就比较方便,用来测试只发送和只接收模式就需要改一下函数咯。

(4)弄了这么久的SPI_DMA,也不知道用处大不大,总之弄完了,呵呵,也算比较了解SPI总线和DMA了,接下来想试试原子哥的新的SD卡函数,原来AG嵌入式开发板也是移植原子哥的旧版,也是有些卡初始化失败,我还以为是我的卡有问题呢?还有就是文件系统,前阵子只弄了基本的读写,准备把FATFS文件系统写得完善一点~

最后,附上源代码。(使用的不是MiniSTM32,所以大家在测试时只需要改一下LED驱动。)
第一个源代码是基础的,实现一个字节在SPI1&SPI2的传送;
第二个是函数化的代码咯,发送随意数量的8bit数据,数量小于512;

关键字:STM32  SPI  DMA寄存器 引用地址:STM32学习笔记之SPI_DMA寄存器级操作

上一篇:stm32 DMA数据搬运 操作寄存器+库函数
下一篇:STM32定时器的预装载寄存器与影子寄存器之间的关系

推荐阅读最新更新时间:2024-03-16 14:57

STM32单片机学习总结之中断EXTI (External interrupt)
学习内容: EXTI (External interrupt) 就是指外部中断,通过GPIO 检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后,再返回到中断之前的代码中执行。 1、能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表管理起来,编号为0~15 的称为内核异常,而16 以上的则称为外部中断(外,相对内核而言),这个表就称为中断向量表。把编号从-3 至6 的中断向量定义为系统异常,编号为负 的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号7 开始的为外部中断,
[单片机]
<font color='red'>STM32</font>单片机学习总结之中断EXTI (External interrupt)
STM32串口的问题
今天在使用USART模块,遇到了一些问题并解决了,于是发贴共享。 问题描述: 在使用USART做串口通讯时,我只把接收中断打开,并设置抢占优先级为最低一个级别,而接收中断上一个优先级处理事情比较多,可能占用了2ms时间。当我使用9600波特率往下位机发送数据,速度非常快,就是一直按回车发!问题就出来,不到1分钟时间,通讯没有反应了。USART配置代码如下: void uart_config(void) { USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = UART_GetBaud(BaudRate); USART_
[单片机]
STM32下串口的使用
STM32下关于串口的固件库写得相当好了,以下只是本文对串口库的一点点封装: /*HKY_uart.h*/ #ifndef _HKY_UART_H_ #define _HKY_UART_H_ #include stm32f10x_lib.h //#include platform_config.h #define GPIO_RTSPin GPIO_Pin_1 #define GPIO_CTSPin GPIO_Pin_0 #define GPIO_TxPin GPIO_Pin_2 #define GPIO_RxPin GPIO_Pin_3 void USART2_Config
[单片机]
atmega48 spi编程代码
#include windows.h #include tchar.h #include winio.h #include stdlib.h #include malloc.h #include memory.h #include tchar.h #include stdio.h #include conio.h #pragma comment( lib, winio.lib ) #include resource.h # define TIMER_ACCURACY 1 HWND hwnd; HANDLE hfile; unsigned long ddd=0; char * send(char * sss);
[单片机]
STM32 RTC时钟源LSE
一开始,所有实验都是在神舟板上去完成,根本就没有发现RTC的问题。直到我们自己画板来后调试时,才发现STM32 RTC的外部时钟源存在问题。 这也算是STM32的一个鸡肋,对于LSE外部晶振太过于苛刻,手册上要求使用6pf,这个规格的晶振市场上太少,鱼龙混杂,中招的高手菜鸟不在少数。我们自己的板也是如此,几经波折,反反复复尝试使用不同的规格的晶振,替换外部的电容,电阻都没有能让这个32.768K的LSE起振。但是又需要有RTC来提供时间,考虑的方法主要有2种,第一采用外部RTC时钟芯片,如DS1302。第二是使用内部其它的时钟源来提供RTC时钟。毫无疑问,目前板已经制好,添加时钟芯片肯定造成板上布局更改,还得重新打板,这里采用了第二
[单片机]
<font color='red'>STM32</font> RTC时钟源LSE
STM32之外部中断例程
外部中断的基本步骤如下: 1.设置好相应的时钟; 2.设置相应的中断; 3.IO口初始化; 4.把相应的IO口设置为中断线路(要在设置外部中断之前)并初始化; 5.在选择的中断通道的响应函数中中断函数。
[单片机]
<font color='red'>STM32</font>之外部中断例程
STM32固件库文件分析
1-汇编编写的启动文件 startup_stm32f10x_hd.s:设置堆栈指针、设置PC指针、初始化中断向量表、配置系统时钟、对用C库函数_main最终去到C的世界 2-时钟配置文件 system_stm32f10x.c:把外部时钟HSE=8M,经过PLL倍频为72M。 3-外设相关的 stm32f10x.h:实现了内核之外的外设的寄存器映射 xxx:GPIO、USRAT、I2C、SPI、FSMC stm32f10x_xx.c:外设的驱动函数库文件 stm32f10x_xx.h:存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明 4-内核相关的 CMSIS - Cortex 微控制器软件接
[单片机]
STM32单片机I/O的工作模式
最近有个朋友在设计低功耗设备,用的是STM32的主控,他知道我做过很多类似的超低功耗项目,于是向我咨询了一些问题,其中就包括I/O口的几种工作模式。今天我就详细的来总结一下这几种工作模式,让大家在以后的设计中知其然也知其所以然。 先说说GPIO 在聊这8种工作模式之前,我想先说一下GPIO的概念—通用输入输出GeneralPurposeInputOutput简称GPIO,就是芯片引脚可以通过它们输出高、低电平,也可以通过他们输入、读取引脚的电压、电平状态。 下面的8种工作模式我将围绕下图进行分析介绍,读懂这一张图大家基本就可以完全理解STM32的GPIO了。希望大家仔细的看一看。 四种不同的输出模式 推挽输出:该模式下引脚
[单片机]
<font color='red'>STM32</font>单片机I/O的工作模式
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved