历史上的今天
返回首页

历史上的今天

今天是:2024年12月08日(星期日)

正在发生

2020年12月08日 | STM32——SPI接口

2020-12-08 来源:elecfans

一、SPI协议【SerialPeripheral Interface】

        串行外围设备接口,是一种高速全双工的通信总线。在ADC/LCD等与MCU间通信。

1、SPI信号线

        SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS 、SCK、MOSI、MISO。

(1)SS(SlaveSelect):片选信号线,当有多个 SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。

(2)SCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。

(3)MOSI (Master Output, Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。

(4)MISO(Master Input, Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。

2、SPI模式

根据 SPI 时钟极性(CPOL)和时钟相位(CPHA) 配置的不同,分为 4 种 SPI 模式。时钟极性是指 SPI 通信设备处于空闲状态时(也可以认为这是 SPI 通信开始时,即SS 为低电平时),SCK 信号线的电平信号。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时则相反。时钟相位是指数据采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的奇数边沿被采样。当 CPHA=1 时,数据线在 SCK 的偶数边沿采样。

首先,由主机把片选信号线SS 拉低,意为主机输出,在SS 被拉低的时刻,SCK 分为两种情况,若我们设置为 CPOL=0,则 SCK 时序在这个时刻为低电平,若设置为 CPOL=1,则 SCK 在这个时刻为高电平。采样时刻都是在 SCK 的奇数边沿(注意奇数边沿有时为下降沿,有时为上升沿)。

CPHA=1时,数据信号的采样时刻为偶数边沿。

 

二、SPI特性及架构

(1)单次传输可选择为 8 或 16 位。

(2)波特率预分频系数(最大为 fPCLK/2) 。

(3)时钟极性(CPOL)和相位(CPHA)可编程设置 。

(4)数据顺序的传输顺序可进行编程选择,MSB 在前或 LSB 在前。

(5)可触发中断的专用发送和接收标志。

(6)可以使用 DMA 进行数据传输操作。

1、SPI架构

MISO 数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。

当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 MOSI 数据线。

SCK 的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。

控制寄存器 CR1 掌管着主控制电路,STM32 的 SPI 模块的协议设置(时钟极性、相位等)就是由它来制定的。而控制寄存器 CR2 则用于设置各种中断使能。

最后为 NSS 引脚,这个引脚扮演着 SPI 协议中的SS 片选信号线的角色,如果我们把 NSS 引脚配置为硬件自动控制,SPI 模块能够自动判别它能否成为 SPI 的主机,或自动进入 SPI 从机模式。但实际上我们用得更多的是由软件控制某些 GPIO 引脚单独作为SS信号,这个 GPIO 引脚可以随便选择。

 

三、SPI接口读取Flash

        各信号线相应连接到 Flash(型号 :W25X16/W25Q16)的 CS、CLK、DO 和 DIO 线,实现SPI 通信,对 Flash进行读写,其中 W25X16 和 W25Q16 在程序上不同的地方是 FLASH 的ID 不一样。

        读取 Flash 的 ID 信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果。

        不同的设备都会相应的有不同的指令,如 EEPROM 中会把第一个数据解释为存储矩阵的地址(实质就是指令)。而 Flash 则定义了更多的指令,有写指令、读指令、读ID 指令等。

SPI-FLASH通信:

(1)配置 I/O端口,使能 GPIO。

(2)根据将要进行通信器件的 SPI模式,配置 STM32的 SPI,使能 SPI时钟。

(3)配置好 SPI后,根据各种 Flash定义的命令控制对它的读写。

注意在写操作前要先进行存储扇区的擦除操作,擦除操作前也要先发出“写使能”命令

1、 main.c

int main(void)

{

  /* 配置串口 1 为:115200 8-N-1 */

  USART1_Config();

  printf("rn 这是一个 2M 串行 flash(W25X16)实验 rn");

 

  /* 2M 串行 flash W25Q16 初始化 */

  SPI_FLASH_Init();

 

  /* Get SPI Flash Device ID */

  DeviceID = SPI_FLASH_ReadDeviceID();

 

  Delay( 200 );

 

  /* Get SPI Flash ID */

  FlashID = SPI_FLASH_ReadID();

 

  printf("rn FlashID is 0x%X, Manufacturer Device ID is 0x%Xrn",  FlashID, DeviceID);

 

  /* Check the SPI Flash ID */

  if (FlashID == sFLASH_ID)   /* #define sFLASH_ID 0xEF3015 */

  {

    printf("rn 检测到串行 flash W25X16 !rn");

 

    SPI_FLASH_SectorErase(FLASH_SectorToErase);

    SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress,  BufferSize);

    printf("rn 写入的数据为:%s rt", Tx_Buffer);

 

    SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);

    printf("rn 读出的数据为:%s rn", Tx_Buffer);

    /* 检查写入的数据与读出的数据是否相等 */

    TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);

 

    if ( PASSED == TransferStatus1 )

    {

      printf("rn 2M 串行 flash(W25X16)测试成功!nr");

    }

    else

    {

      printf("rn 2M 串行 flash(W25X16)测试失败!nr");

    }

  }// if (FlashID == sFLASH_ID)

  else

  {

    printf("rn 获取不到 W25X16 ID!nr");

 

  }

 

  SPI_Flash_PowerDown();

  while (1);

}

(1)调用 USART1Confi g() 初始化串口。


(2)调用 SPI_FLASH_Init() 初始化 SPI 模块。


(3)调用 SPI_FLASH_ReadDeviceID() 读取 Flash 器件生产厂商的 ID 信息。


(4)调用 SPI_FLASH_ReadID() 读取 Flash 器件的设备 ID 信息


(5)若读取得的ID正确, 则调用 SPI_FLASH_SectorErase()把 Flash 的内 容擦除,擦除后调用SPI_FLASH_BufferWrite() 向Flash 写入数据,然后再调用SPI_FLASH_BufferRead()从刚刚写入的地址中读出数据。最后调用 Buffercmp() 函数对写入的数据与读取的数据进行比较,若写入的数据与读出的数据相同,则把标志变量TransferStatus1 赋值为 PASSED(自定义的枚举变量)。


(6)最后调用 SPI_Flash_PowerDown()函数关闭 Flash 设备的电源,因为数据写入到Flash 后并不会因断电而丢失,我们在使用它时才重新开启 Flash 的电源


 


2、SPI初始化


#define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)

#define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)

 

void SPI_FLASH_Init(void)

{

  SPI_InitTypeDef  SPI_InitStructure;

  GPIO_InitTypeDef GPIO_InitStructure;

 

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

 

  /* SCK */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  /* MISO */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  /* MOS */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  /* CS  */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  SPI_FLASH_CS_HIGH();

 

  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;

  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;

  SPI_InitStructure.SPI_CRCPolynomial = 7;

  SPI_Init(SPI1, &SPI_InitStructure);

 

  SPI_Cmd(SPI1, ENABLE);

}

(1)SPI_Mode :主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave),这两个模式的最大区别为 SPI 的 SCK 信号线的时序,SCK 的时序是由通信中的主机产生的。若被配置为从机模式,STM32 的 SPI 模块将接受外来的 SCK 信号。

(2)SPI_DataSize : SPI 每次通信的数据大小(称为数据帧)为 8 位还是 16 位。


(3)SPI_CPOL 和 SPI_CPHA :配置SPI的时钟极性(CPOL)和时钟相位CPHA),这两个配置影响到 SPI 的通信模式,该设置要符合将要互相通信的设备的要求。CPOL 分别可以取 SPI_CPOL_High(SPI 通信空闲时 SCK 为高电平)和SPI_CPOL_Low(SPI 通信空闲时 SCK 为低电平)。CPHA 则可以取 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 和 SPI_CPHA_2Edge(在 SCK偶数边沿采集数据)。


(4)SPI_NSS :配置NSS引脚的使用模式,硬件模式(SPI_NSS_Hard)与软件模式(SPI_NSS_Soft),在硬件模式中的 SPI 片选信号由硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。如果外界条件允许,硬件模式还会自动将 STM32 的 SPI 设置为主机。我们使用软件模式,向这个成员赋值为 SPI_NSS_Soft。


(5)SPI_BaudRatePrescaler:本成员设置波特率分频值,分频后的时钟即为 SPI 的 SCK信号线的时钟频率。这个成员参数可设置为 f PCLK 的 2、4、6、8、16、32、64、128、256 分频。赋值为 SPI_BaudRatePrescaler_4,即 f PCLK 的 4 分频。


(6)SPI_FirstBit:所有串行的通信协议都会有 MSB 先行(高位数据在前)还是 LSB先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。据 Flash 的通信时序,我们向这个成员赋值为MSB先行(SPI_FirstBit_MSB)。


(7)SPI_CRCPolynomial:这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式)来计算 CRC 的值。由于本实验的 Flash 不支持 CRC校验,所以我们向这个结构体成员赋值为 7 实际上是没有意义的。


配置完这些结构体成员后,我们要调用 SPI_Init() 函数把这些参数写入寄存器中,实现SPI 的初始化,然后调用 SPI_Cmd() 来使能 SPI1。


 


2、读FLASH的ID


#define Dummy_Byte   0xFF

 

u8 SPI_FLASH_SendByte(u8 byte)

{

  // 等待发送数据寄存器清空

  while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);

 

  SPI_I2S_SendData(SPI1, byte); // 向从机发送数据

  while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET); // 等待接收数据寄存器非空

 

  return SPI_I2S_ReceiveData(SPI1); // 获取接收寄存器中的数据

}

u32 SPI_FLASH_ReadDeviceID(void)

{

  u32 Temp = 0;

 

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_DeviceID);

  SPI_FLASH_SendByte(Dummy_Byte);

  SPI_FLASH_SendByte(Dummy_Byte);

  SPI_FLASH_SendByte(Dummy_Byte);

  Temp = SPI_FLASH_SendByte(Dummy_Byte);

  SPI_FLASH_CS_HIGH();

 

  return Temp;

}

3、读取厂商ID


u32 SPI_FLASH_ReadID(void)

{

  u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

 

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_JedecDeviceID); // 0x9F

  Temp0 = SPI_FLASH_SendByte(Dummy_Byte);

  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);

  SPI_FLASH_CS_HIGH();

 

  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;

  return Temp;

}

4、擦除FLASH内容


void SPI_FLASH_WriteEnable(void)

{

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_WriteEnable); // 06H

  SPI_FLASH_CS_HIGH();

}

 

void SPI_FLASH_WaitForWriteEnd(void)

{

  u8 FLASH_Status = 0;

 

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_ReadStatusReg); // 05H

  do

  {

    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);

  }

  while ((FLASH_Status & WIP_Flag) == SET);

  SPI_FLASH_CS_HIGH();

}

 

void SPI_FLASH_SectorErase(u32 SectorAddr)

{

  SPI_FLASH_WriteEnable();

  SPI_FLASH_WaitForWriteEnd();

 

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_SectorErase); // 20H

  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);

  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);

  SPI_FLASH_SendByte(SectorAddr & 0xFF);

  SPI_FLASH_CS_HIGH();

 

  SPI_FLASH_WaitForWriteEnd();

}

5、向Flash写数据——分页


void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)

{

  SPI_FLASH_WriteEnable();

 

  SPI_FLASH_CS_LOW();

  SPI_FLASH_SendByte(W25X_PageProgram); // 02H

  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);

  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);

  SPI_FLASH_SendByte(WriteAddr & 0xFF);

 

  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)

  {

    NumByteToWrite = SPI_FLASH_PerWritePageSize;

  }

 

  while (NumByteToWrite--)

  {

    SPI_FLASH_SendByte(*pBuffer);

    pBuffer++;

  }

  SPI_FLASH_CS_HIGH();

 

  SPI_FLASH_WaitForWriteEnd();

}

 

 

void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)

推荐阅读

史海拾趣

ECM [ECM Electronics Limited.]公司的发展小趣事

ECM Electronics Limited.自创立之初,就注重技术创新。公司投入大量研发资金,专注于开发高效、节能的电子产品。通过不断的技术迭代和产品创新,ECM成功推出了一系列具有竞争力的产品,逐渐在市场上建立了良好的口碑。同时,公司还积极与高校和科研机构合作,引进先进技术,为公司的持续发展提供了强大的技术支撑。

Faraday Electronics Inc公司的发展小趣事

在Faraday Electronics Inc的发展历程中,企业文化的塑造起到了至关重要的作用。公司一直注重培养员工的创新精神和团队合作精神,鼓励员工积极参与技术创新和市场拓展。同时,公司还注重营造积极向上的工作氛围和企业文化,让员工在工作中感受到归属感和成就感。这种积极向上的企业文化不仅激发了员工的工作热情和创新精神,还为公司的发展提供了源源不断的动力。

请注意,以上故事均为虚构,仅用于展示电子行业公司可能的发展路径和故事类型。

益升华(Essentra)公司的发展小趣事

为了提高生产效率和降低成本,益升华(Essentra)公司不断优化供应链。公司与多家优质供应商建立了长期稳定的合作关系,确保原材料的质量和供应的稳定性。同时,公司还引入先进的生产设备和工艺,提高生产自动化水平,降低人工成本。此外,益升华(Essentra)公司还建立了完善的物流体系,确保产品能够及时送达客户手中。这些措施的实施,使公司的竞争力得到了显著提升。

Conflux公司的发展小趣事

Conflux公司自创立之初,便专注于区块链技术的研发与应用。在电子支付领域,公司团队成功研发出了一种高效、安全的分布式账本技术,这一技术突破迅速吸引了业界的关注。随后,Conflux与多家金融机构合作,推出了基于区块链技术的电子支付解决方案,不仅提高了交易速度,还大大降低了交易成本。这一系列的创新举措使Conflux在电子支付领域崭露头角,奠定了其在行业内的领先地位。

Avic公司的发展小趣事

随着Avic公司在航空电子技术领域的不断发展和创新,其产品在国内外市场上的竞争力日益增强。公司成功拓展了国内外市场,市场份额稳步增长。这不仅体现了公司在航空电子领域的实力,也为中国航空工业的整体发展做出了积极贡献。

这五个故事虽然不能完全涵盖Avic公司在电子行业发展的所有细节,但希望能通过概括性的描述,为您展现该公司在这一领域的重要发展历程和取得的成就。需要注意的是,这些故事是基于公开资料和已知事实进行描述的,旨在客观呈现事实,而非进行主观评价。

ETRI公司的发展小趣事

作为一家具有全球视野的研究机构,ETRI也积极拓展海外合作。他们与金陵华软投资集团(CSC)共同签署了战略合作备忘录,双方将在新技术对接、投资服务、资源共享、信息交流等方面实现合作。这一合作不仅有助于ETRI拓展海外市场,也将为双方带来更多的发展机遇。

问答坊 | AI 解惑

产品规划、产品开发的概念和计划阶段的重要性

在柯维博士的《高效能人士7个习惯》中,他提出一个有趣的观点:任何创造实际是经过2个层次,一次是“心智的创造”,另一次才是“实际的创造”。其中心智的创造尤为重要,因为它是创新的源头和起始,难度更大,更为难能可贵。心理学家马斯洛也指出, ...…

查看全部问答>

wince里怎样显示bmp图片,像PC里缩略图那样显示??

如题:我想做一个程序,显示bmp图片,最好像在PC里显示缩略图那样显示bmp图片!!!…

查看全部问答>

synaptic是通过共享能存来开关触摸板的?

是和哪个进程共享内存来修改触摸板的呢  是X Window吗?如果我不想通过共享能存的方式应该怎么修改触摸板?…

查看全部问答>

LINUX vivi移植内核时的配置设置

安装VIVBOOTLOADER时,那个配置文件应该怎么设置,好象就是 文件路径,有视频最好了 谢谢!…

查看全部问答>

pic12f683输出问题

#include<pic.h>void main(void){ANSEL=0x00;TRISIO=0x00000000; GPIO=0xff;GIE=1;while(1){}}问题:   为啥我的单片机管脚没有输出?…

查看全部问答>

求助关于视频采集卡

我们公司现在使用的一种PCI视频采集卡,使用SA7111A做AD转换=>>EP2C8T144暂存和控制转移数据=>>PLX9054,在FPGA芯片里写了一个FIFO,此视频采集卡在使用时会占用很高的CPU使用率,我的电脑是赛扬2.93Ghz的cpu,每次能占用40%到50%,当使用 ...…

查看全部问答>

RT-Thread MIPS分支连载:MIPS32的异常与中断

本篇说的是MIPS的异常与中断。异常与中断可能很多人不是太明白这两者的含义:异常,是指计算机在执行程序的过程中,当出现异常情况或特殊请求时,计算机停止现行程序的运行,转而对这些异常情况或特殊请求的处理。中断,是异常的一种,通常指的是由 ...…

查看全部问答>

【问TI】LM3S系列基本上是10位ADC,与别的厂家的12位的ADC差点那?优越在什么地方?

LM3S系列基本上是10位ADC,与别的厂家的12位的ADC差点那?优越在什么地方?看了个帖子说LM3S虽然ADC是10位的,但是精度非常高。专家给介绍下!…

查看全部问答>

LM3S系列的ROM RUN和RAM RUN

试了两款LM3S8962和LM3S9B96,问题都一样。LM3S系列能够将程序下载到内存,然后修改PC指针,在内存中运行,这样调试程序下载速度比较快,也减少了flash的读写次数,这是背景。除了一些大的程序,我都是在内存中调试,简称RAM RUN,大家可以翻翻我以 ...…

查看全部问答>

2011仪器器件清单分析(全)——决战2011全国大学生电子设计竞赛

本帖最后由 paulhyde 于 2014-9-15 09:27 编辑 决战2011全国大学生电子设计竞赛——仪器器件清单分析 格式好乱……原文可以在附件中下载!另外,有同学转载我没有意见,但烦请注明出处! 2009 2011 1.基本仪器清单 20MHz普通示波器(双通道, ...…

查看全部问答>