单片机外设篇——SPI协议

提示:若转载,请备注来源,谢谢!

文章目录


前言

题目上写的是单片机,其实不管你的板子上不上系统(FreeRtos、Linux),协议都是不变的。题外话:工作过程中,一直在移植别人写好的SPI协议,然后和外设的芯片(例如:Flash芯片、NFC芯片等)进行通信,但是都没有往底层深入的看,下午照着代码看了三个多小时,写这篇博客作为总结。

一、SPI协议的特点

SPI (Serial Peripheral Interface),是串行外围设备接口,通过这几个接口(一般4个接口,有片选、时钟、输入、输出)出来的数据遵循一定的规则,我们把这个规则叫做协议,所以就是SPI协议,可以进行高速、全双工、同步的通信。现在越来越多的外设芯片集成了这种通信协议,常见的有FLASH、AD转换器,NFC芯片等。

1. 优点

  • 支持全双工,信号完整性好;

  • 支持高速(100MHz以上);

  • 协议支持字长不限于8bits,可根据应用特点灵活选择消息字长,(高位先行还是低位先行,需要看外设芯片的手册,主要是保证两个 SPI通讯设备之间使用同样的协定);

  • 硬件连接简单;

2. 缺点

  • 相比IIC多两根线,有4根线;

  • 没有寻址机制,只能靠片选选择不同设备。意思就是发送数据前,要先通过IO拉低设备片选信号,然后在发送数据,操作完成后将片选信号拉高;

  • 没有从设备接受ACK,主设备对于发送成功与否不得而知;

  • 典型应用只支持单主控;

  • 相比RS232 RS485和CAN总线,SPI传输距离短,局限于PCB板子;

3. 结构

  • 信号定义如下:
    SCK: Serial Clock 串行时钟
    MOSI : Master Output, Slave Input 主发从收信号
    MISO: Master Input, Slave Output 主收从发信号
    SS/CS : Slave Select 片选信号

在这里插入图片描述

二、SPI协议分析

1. 模式概念理解

首先要知道时钟极性 CPOL”和“时钟相位 CPHA的概念,概念自行百度,根据CPOL 及 CPHA 的不同状态,SPI 分成了四种模式,若你写软SPI协议的话,一定要知道这四种模式,使用硬SPI协议的话,根据外设芯片,在初始化时,配置MCU的寄存器即可。四种模式如下:

在这里插入图片描述

例如:W25Q64这款FLSH芯片,既支持模式0,也支持模式3,所以在MCU初始化SPI时,就可以选择这两种模式中的一种。

在这里插入图片描述

2. 通信过程分析

这是一张野火STM32F103手册上的图片,我们参考这种图片来分析通信过程

在这里插入图片描述

  • (1) 拉低NSS信号线,产生起始信号(图中没有画出);(需要软件操作)
  • (2) 把要发送的数据写入到“数据寄存器 DR”中,该数据会被存储到发送缓冲区;(需要软件操作)
  • (3) 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则把数据一位一位地存储进接收缓冲区中;(我们不用管,单片机会自动帮我们完成!)
    *(4) 当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置 1,表示传输完一帧,接收缓冲区非空;(需要软件操作,因为我们要做状态查询,通常是while死循环来保证数据被发送或接收)
  • (5) 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE 标志位”为 1时,通过读取“数据寄存器 DR”可以获取接收缓冲区中的内容;
  • (6) 拉高 NSS信号线,产生结束信号(需要软件操作)

3. SPI个人协议理解

其实,对于任何一种MCU支持的协议来说,我们要做的就3步:

  • 1、初始化
  • 2、发送数据
  • 3、接收数据
    不过,spi协议在发送和接收数据前要拉低片选信号而已。对MCU操作来说,每款MCU的厂家给出的寄存器是不一样,在编写发送或接收函数时,每个MCU的编写函数是不一样的。这里,分析两家的,拿到一款芯片后,可以参考厂家demo编写,这才是最正确的,千万不要傻乎乎的字节从头到尾写。

第一家,意法半导体的STM32F103芯片。因为之前说过,SPI协议没有从设备发送ACK,所以主设备对于发送成功与否不得而知,但是可以知道数据buff是否发送完成,简单来说,数据发送成不成功我不知道,但是我知道数据发没发完。每个厂家设计的不一样,STM32检测buff是否发送完成依据接收缓冲区(没有写出错,是接收缓冲区)不为空(这样设计感觉挺奇怪的,没办法厂家就是这样设计的)

在这里插入图片描述

  • 1)发送之前,先检测TXE,若发送缓冲区位空,则将数据写入发送数据寄存器;
  • 2)等待数据发送完成(若RXNE为非空,则表示发送完成);
// 发送函数
u8 SPI_FLASH_SendByte(u8 byte)
{
    SPITimeout = SPIT_FLAG_TIMEOUT;

    /* 等待发送缓冲区为空,TXE事件 */
    while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE) == RESET)
    {
        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
    }

    /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
    SPI_I2S_SendData(FLASH_SPIx, byte);   // 将一个字节的数据写入spi数据寄存器

    SPITimeout = SPIT_FLAG_TIMEOUT;

    /* 判断发送buff的数据是否完成,等待接收缓冲区非空,RXNE事件 */
    while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE) == RESET)
    {
        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
    }

    /* 读取数据寄存器 */
    return SPI_I2S_ReceiveData(FLASH_SPIx );
}
// 接收函数
u8 SPI_FLASH_ReadByte(void)
{
	// 通过写的方式,来读数据,感觉挺奇怪的
    return (SPI_FLASH_SendByte(Dummy_Byte));	//Dummy_Byte为任意字节,无意义,但是必须要写,一般我们写0XFF
}
  • 第二家,国内HUA芯片, 这款芯片就有专门的发送完成和是否接受到数据的状态寄存器,发送和接收逻辑符合我们通常的认知。写这两个函数的时候需要参考厂家demo。
  • 在这里插入图片描述

// 发送函数
void Spim0SendData(UINT8 *data_buf, UINT16 len)
{    
	UINT16 *phalfword = (UINT16*)data_buf;
	UINT32 *pword = (UINT32*)data_buf;
	
	Spim0ClrFifo();		//清空发送缓冲区
    Spim0RecAutorcvDis();  	// 禁用自动接收
	Spim0TransStart();		// 开始发送
	Spim0ClrStatus(SPIM0_TXEND);	// 清空发送完成寄存器
	
	while(len)
    {        
     	if(len >= 8)
        {
			/*send 8 Byte data*/
			for (UINT8 i = 0; i < 8; i++)
			{
				SPIM0->DR = *data_buf;
				data_buf++;
			}					
			len -= 8;
			wrcnt += 8;
        }
        else if(len >= 4)
        {
			/*send 4 Byte data*/
			for (UINT8 i = 0; i < 4; i++)
			{
				SPIM0->DR = *data_buf;
				data_buf++;
			}					
			len -= 4;
			wrcnt += 4;
        } 		
		else
		{
			for (UINT8 i = 0; i < len; i++)
			{
				SPIM0->DR = *data_buf;
				data_buf++;
				len--;
			}					
		}

		while(!(Spim0GetStatus() & SPIM0_TXEND)); 
		Spim0ClrStatus(SPIM0_TXEND);
	}
	Spim0TransStop();
}
// 接收函数
void Spim0RecvData( UINT8 *data_buf, UINT16 rev_len)
{   
	UINT16 *phalfword = (UINT16*)data_buf;
	UINT32 *pword = (UINT32*)data_buf;
	
	Spim0SetClk(rev_len & 0x3ff);/*set rx frames,the maxlen is 0x3ff bytes*/
	Spim0ClrFifo();
	Spim0RecAutorcvEn();/*only receive mode en*/	  
    Spim0TransStart();
	
		while(rev_len != 0)
		{
			if(rev_len >= 4)
			{	    			             	
				/*receive 4 byte data*/
				while(!(Spim0GetStatus() & SPIM0_RXHF));
				*data_buf++ = SPIM0->DR;
				*data_buf++ = SPIM0->DR;
				*data_buf++ = SPIM0->DR;
				*data_buf++ = SPIM0->DR;
				rev_len -= 4;			
			}        
			else
			{			           
				while(!(Spim0GetStatus() & SPIM0_RXNE));
				for(; rev_len>0; rev_len--)
				{
					*data_buf++ = SPIM0->DR;
				}
			}
		}
	
	Spim0TransStop();    
} 

4、使用SPI协议操作SPI外设芯片

需要先看外设芯片的数据手册,例如W25Q64 flash芯片的操作指令为,(下图中括号的数据为接收的数据):

在这里插入图片描述


举个简单的例子,使用stm32读flash的设备ID:

u32 SPI_FLASH_ReadDeviceID(void)
{
    u32 Temp = 0;

    /* Select the FLASH: Chip Select low */
    SPI_FLASH_CS_LOW();

    /* Send "RDID " instruction */
    SPI_FLASH_SendByte(W25X_DeviceID);		// 0xAB
    SPI_FLASH_SendByte(Dummy_Byte);
    SPI_FLASH_SendByte(Dummy_Byte);
    SPI_FLASH_SendByte(Dummy_Byte);

    /* Read a byte from the FLASH */
    Temp = SPI_FLASH_ReadByte();	// 等价于 Temp = SPI_FLASH_SendByte(Dummy_Byte);

    /* Deselect the FLASH: Chip Select high */
    SPI_FLASH_CS_HIGH();

    return Temp;
}

总结

  • 1、SPI协议主要写的就是发送和接收函数,发送和接收的数据需要看外设芯片的数据手册;
  • 2、若MCU支持硬SPI协议,那我们一般用硬spi协议,若用软的,移植的时候不好移植,因为你不知道你的外设芯片支持哪种spi模式。如果MCU不支持SPI,现在你又需要SPI,这时就可以写个软的SPI协议。不过现在芯片一般都支持硬SPI了,除非为了节省成本,你的芯片很Low很Low。
  • 软spi协议很简单,关于波特率,你不需要太过关系,只要不超过外设芯片的波特率就可以,至于具体是多少Hz,如果不追求速度的话,没有太大的关系,可以先调通spi,然后在调速。
  • 软SPI协议如下(模式0): 可以看到,先操作的是数据IO,然后在操作SCK的IO。

    在这里插入图片描述


    请务必参考上面的时序图,来看下面软spi模式0对应的代码,不然不知道原由:
// spi发送函数
void SpiByteWrite(unsigned char dat) 
{ 
    unsigned char mask; 
    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位移出 
    { 
        if ((mask&dat) != 0) //首先输出该位数据 
            Set_MOSI_IO(1); 	// IO拉高
        else 
            Set_MOSI_IO(0); 	// IO拉低
        Set_SPI_CK(1);       //然后拉高时钟,数据采样,IO拉高
        Set_SPI_CK(0);       //再拉低时钟,完成一个位的操作 ,IO拉低
    } 
     Set_MOSI_IO(1);            //最后确保释放 IO 引脚,IO拉高
} 

// spi总线上读取一个字节  
unsigned char DS1302ByteRead() 
{ 
    unsigned char mask; 
    unsigned char dat = 0; 
     
    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位读取 
    { 
        if (Get_MISO_IO!= 0)  //首先读取此时的 IO 引脚,并设置 dat 中的对应位 
        { 
            dat |= mask; 
        } 
        Set_SPI_CK(1);       //然后拉高时钟,数据采样,IO拉高
        Set_SPI_CK(0);       //再拉低时钟,完成一个位的操作 ,IO拉低
    } 
    return dat;              //最后返回读到的字节数据 
}
  • 若其他模式,参考下面的图片,相信你也能自己写出对应的软SPI协议。

在时序上,SPI 比 I2C 简单多,没有了起始、停止和应答,和UART一样, SPI 在通信的时候,只负责通信,不管是否通信成功,而 I2C 却要通过应答信息来获取通信成功失败的信息,所以相对来说,UART 和 SPI 的时序都要比 I2C 简单一些。

在这里插入图片描述

在这里插入图片描述

原文地址:https://blog.csdn.net/zcc_123/article/details/113446933

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


  译序:JWMediaPlayer是开源的网页使用的Flash播放器。本文采摘于JWPlayer的官方文档,讲解了JWPlayer对于RTMP的使用方法,我们可以从JWPlayer客户端的角度来了解RTMP协议。以下是官方原文:      简介    RTMP(RealTimeMessagingProtocol
    Flash编程原理都是只能将1写为0,而不能将0写成1.所以在Flash编程之前,必须将对应的块擦除,而擦除的过程就是将所有位都写为1的过程,块内的所有字节变为0xFF.因此可以说,编程是将相应位写0的过程,而擦除是将相应位写1的过程,两者的执行过程完全相反.一、Nor和NandFlash
 上传setenvgatewayip192.168.1.1;setenvserverip192.168.1.7;setenvipaddr192.168.1.156;mw.b0x820000000xff0x1000000sfprobe0sfread0x8200000000x1000000tftp0x82000000test.bin0x1000000 下载mw.b82000000ff1000000tftp82000000test.bi
Error:FlashDownloadFailed-"Cortex-M3"出现一般有两种情况:1.SWD模式下,Debug菜单中,Reset菜单选项(Autodetect/HWreset/sysresetReq/Vectreset)默认是AutoDetect,改成SysResetReq即可。2.Jtag模式下,主要是芯片大小选错。Flash->ConfigureFalshTools配置窗口,切换到“Utilities"
jPlayer是一个用于控制和播放mp3文件的jQuery插件。它在后台使用Flash来播放mp3文件,前台播放器外观完全可以使用XHML/CSS自定义。支持:有一点比较好的是,在支持html5的浏览器上会使用html5的标签audio或者video,而不支持的浏览器上使用swf来播放选择需要播放的Mp3文件。播放、暂停
#ifndef__FONTUPD_H__#define__FONTUPD_H__#include"sys.h" //字库信息结构体定义33字节__packedtypedefstruct{u8fontok;//字库存在标志,0XAA,字库正常;其他,字库不存在u32ugbkaddr;//unigbk的地址u32ugbksize;//unigbk的大小u32f12addr;//gbk12地址u32g
ROM(ReadOnlyMemory)和RAM(RandomAccessMemory)指的都是半导体存储器。ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,但是访问速度快。典型的RAM就是计算机的内存。RAM有两大类,一种称为静态RAM(StaticRAM/SRAM),SRAM速度非常快,是目前读写最快的存储
JSpc端和移动端实现复制到剪贴板功能实现在网页上复制文本到剪切板,一般是使用JS+Flash结合的方法,网上有很多相关文章介绍。随着HTML5技术的发展,Flash已经在很多场合不适用了,甚至被屏蔽。本文介绍的一款JS插件,实现了纯JS方法复制文本到剪切板。插件名是Clipboard.js,该插件不依
例子:R0=1R1=1R2=10R3=e000ed10R12=0LR=fffffff9(中断返回值)PC=0PSR=60000013或60000016或60000036(Z、C、EXCEPT_NUM:RTC_WKUP_IRQn、EXTI0_IRQn、USART2_IRQn)BFAR=e000ed38(不关心)CFSR=20000(INVSTATE:Invalidstateusagefault thePCvaluestackedf
 内存接口概念首先来分析下操作GPIO控制器和操作UART控制器两者的区别如图是S3C2440是个片上系统,有GPIO控制器(接有GPIO管脚),有串口控制器(接有TXDRXD引脚)配置GPIO控制器相应的寄存器,即可让引脚输出高低电平;配置UART控制器相应的寄存器,即可让引脚输出波形。前者相对简单,类
小编导语:    近几年来,网页游戏成为了游戏界关注的焦点,由于其制作简单,成本低并且收益率较高,因此成为了众多游戏厂商追逐的对象,但是除了商家夸张的炒作宣传外,很少有页游佳作出现。然而,随着Unity3D游戏引擎的出现,网页游戏的3D化成了页游冲出重围的杀手锏,那么在flash网页游戏称
1.指定数组到特定的Flash单元#pragmalocation=0x000FFF00 __rootconstcharFlash_config[]={0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0
继续研究发现,计算机的固件真的很有趣。参考了一些重要的资料,比如http://donovan6000.blogspot.com/2013/06/insyde-bios-modding-advanced-and-power-tabs.html等,对于IDA的使用也了解了一些。最后,总结一下目前看来可行性的方案:0.基础知识储备,包括UEFIBIOS的概念,InsydeBIOS的
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>navigator对象<itle></head><body><buttononclick="checkFlash()">检测</button>
修改网上流传的flash-marker.js(function(global,factory){typeofexports==='object'&&typeofmodule!=='undefined'?module.exports=factory():typeofdefine==='function'&&define.amd?define(factory
shareObject本地缓存存储位置:win7系统用户到C:\Users\[你的用户名]\AppData\Roaming\Macromedia\FlashPlayer\#SharedObjects\XP或2003用户到:C:\DocumentsandSettings\用户名\ApplicationData\Macromedia\FlashPlayer\#SharedObjects\ ---------------------作者:iteye_
安装谷歌浏览器之后经常遇到Flash崩溃或者浏览器在浏览Flash内容时卡死的情况。在网上查找资料大多都认为应该是浏览器自带的Flash插件工作模式引起的问题,解决方法如下:首先在地址栏输入chrome://plugins/显示浏览器使用的插件。点击右上角的详细信息,可以看到Flash插件为进程外
之前一直使用的W25Q16spiflash都没问题,换了一款W25Q80后发现工作不正常,经过测试,初步定位到问题在于初始化SPI后是否将CS拉高。于是又去查看了一下原厂代码:发现原厂的代码初始化SPI接口时是专门拉高CS的。结论:网上很多代码初始化SPI接口时没有专门拉高CS,对某些型号可能确实
======================================================NANDFlash最小存储单元:写数据操作:通过对控制闸(ControlGate)施加高电压,然后允许源极(SOURCE)和汲极(RRAIN)间的N信道(N-Channel)流入电子,等到电流够强,电子获得足够能量时,便会越过浮置闸(FloatingGate)底下的二氧化硅层(S
安装CnarioPlayer3.8.1.156或其他版本时,有时会出现如下提示:Warning4154.AdobeFlashPlayer13...notcorrectlyinstalled:请前往AdobeFlash网站,并选择下图示的版本下载安装: