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

文章目录

  • 前言
  • 一、SPI协议的特点
    • 1. 优点
    • 2. 缺点
    • 3. 结构
  • 二、SPI协议分析
    • 1. 模式概念理解
    • 2. 通信过程分析
    • 3. SPI个人协议理解
    • 4、使用SPI协议操作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);     // 0xABSPI_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 简单一些。

单片机外设篇——SPI协议相关推荐

  1. 51单片机外设篇:点阵式LCD

    什么是LCD LCD(Liquid Crystal Display)俗称液晶. 液晶是一种材料,液晶这种材料具有一种特点:可以在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因此我们可以在整个液 ...

  2. 51单片机外设篇:蜂鸣器

    蜂鸣器 蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音.报警音等提示信号. 蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器: 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发 ...

  3. 51单片机外设篇:数码管

    数码管简介 LED数码管:数码管是一种简单.廉价的显示器,是由多个发光二极管封装在一起组成"8"字型的器件.比如红绿灯. 单个数码管: 多个数码管: 这些引脚由对应的寄存器控制着, ...

  4. 外设篇:SD卡等外存设备

    主流的外存设备介绍 内存和外存的区别:一般是把RAM(random access memory,随机访问存储器,特点是任意字节读写,掉电丢失)叫内存,把ROM(read only memory,只读存 ...

  5. 单片机软件模拟SPI接口—加深理解SPI总线协议

    单片机软件模拟SPI接口-加深理解SPI总线协议   SPI(Serial Peripheral Interfacer 串行外设接口)是摩托罗拉公司推出的一种同步串行通讯接口,用于微处理器臌控制器和外 ...

  6. SPI的模拟应用——OLED以及时钟模块的应用(一)SPI协议介绍及利用协议实现两机通信(51单片机)

    链接:https://pan.baidu.com/s/1g8jkENjO8v4eXq0bN0acEw?pwd=45c8  提取码:45c8 目录 一. 什么是SPI 1-1 SPI简介 1-2 SPI ...

  7. [经验] PROTEUS仿真学习笔记05 (SPI 协议 外设)——2014_7_15

    SPI 的概念 *************** 对初学者来说,SPI 应该比 I2C 难一些,原因: 1.C51 用SPI 的资料不多,要到STM32 等更高级MCU 资料才会多: 2.SPI 的资料 ...

  8. 【单片机外设学习之SPI】

    stm32外设学习之spi 物理层 SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK.MOSI.MISO,片选线为CS(或者NSS). CS: 片选信号线, 当有多个SPI 从设备与SPI ...

  9. 基于STM32 + 超详细对新手全面解析讲解SPI协议(附源码)

    前言        本次我们学习一下STM32的一个基本外设 --- SPI,全程参考手册讲解,讲述SPI的工作模式和作用,让大家快速掌握和了解SPI通讯协议.本篇博客大部分是自己收集和整理,借鉴了很 ...

  10. 嵌入式硬件入门——Flash Memory(W25Q64+SPI协议)

    Flash存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失. 文章目录 Flash Fla ...

最新文章

  1. 苹果运行内存比较_决定手机流畅度到底是看CPU还是运行内存,你知道么?
  2. python dbscan 如何确定eps参数_如何选择eps和minPts(DBSCAN算法的两个参数)以获得有效结果?...
  3. 福昕PDF阅读器的图章妙用
  4. 170802、Elasticsearch5.2.2 安装问题记录
  5. 泛型集合 无序泛型 c#
  6. Struts2 ( 二 )
  7. 单体应用微服务改造实践
  8. Yeelink初步体验
  9. python-多线程共享内存
  10. 基于JSP的数据库增删改查实现
  11. 读Thinking in Java(1~4)
  12. windbg拦截驱动加载
  13. tomcat热部署(springboot项目)
  14. python fabs和abs_python – abs()vs fabs()速度差异和fabs()
  15. XPO 的三篇介绍文章。
  16. 方差分析——单因素方差分析
  17. 中秋节活动征集!!!
  18. 基于微信小程序的民宿酒店预订系统
  19. at命令、crontab命令
  20. IMP-00017 Oracle数据库imp命令导入时1659错误处理

热门文章

  1. 网络营销应该怎样做?巧用换IP软件
  2. 2004-2020历年美赛优秀论文资源
  3. vim下替换字符串命令
  4. Git之基于图形界面工具TortoiseGit(乌龟git)增删改查本地仓库以及建立远程仓库,同步本地仓库至远程仓库github
  5. suse linux 忘记root密码,SUSE linux 忘记root密码
  6. wordpress字体_如何在WordPress中使用网络字体
  7. JSTL核心标签超详细
  8. 四种大数据分析方法 ,大数据学习入门必须掌握!
  9. SwiftyJSON的基本用法
  10. 计算机网络放大器的作用,运算放大器