完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、简介
SPI是串行外设接口(Serial Peripheral lnterface)的缩写。SPI是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,如NRF24L01、VS1053、SD卡等。 (1)速度:串口的通信一般也就是115200bps,但是SPI的通信速度可以达到10Mbps,接近快了一百倍,所以在配置的时候需要注意,一般不超过10Mbps。 (2)同步:采用同步方式(Synchronous)传输数据,主设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输。—简单地说,发送数据的时候,必须同时接收到数据,由时钟控制。 (3)全双工:发送数据的时候能够接收数据,接收数据的时候能够发送数据,即可以同时双向通信。 二、 spi四种模式 SPI的相位(CPHA)和极性(CPOL)都可以为0或1,对应的4种组合构成了SPI的4种模式 Mode 0: CPOL=0, CPHA=0 Mode 1 :CPOL=0, CPHA=1 Mode 2 :CPOL=1, CPHA=0 Mode 3 :CPOL=1, CPHA=1 时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)三、spi四线 SPI FLASH 一般用于存储LCD显示屏所要显示的图片,视频数据。 四根线:SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。 或者说是MOSI、MISO、SCLK、CS四根,CS和SS是一样的,只是表示方法不同。 SCLK:串行时钟线,用于数据的同步。 MOSI:主机输出数据,从机接收数据。 MISO:主机接收数据,从机输出数据。 SS/CS:控制从机是否工作,往往是低电平有效,低电平选中从设备。 就比如STM32和LCD屏,使用SPI进行通信,单片机就是主机,LCD就是从机,单片机主机发送数据给从机LCD。 其中的SCLK、MOSI、MISO三根线是可以复用连接的,唯独片选信号线CS是要连接到器件N,这也代表了多个从设备接在一起 的时候,被片选的设备才可以进行工作,只能多选一,当其中一个设备被选中CS引脚为低电平的时候,其他引脚一定要设置为高电平,避免传输数据的时候发生紊乱。 四、编程 SPI读取W25Q128 (1)读取设备ID 1.根据设备数据手册读时序图,根据固件库参考手册编写函数 (1)GPIO初始化、SPI初始化 (2)SPI write初始化(主机发送数据的时候,从机会返回数据给单片机) (3)读取ID,读取id的目的是验证自己的写数据函数是否正确。 如果出现可上可下的梯形时序图,那么表示可以是任意数据,下图简单示例一下,学会看图。 读从设备的ID,Read Manufacturer / Device lD (90h),读取厂商的ID,90h是命令,具体得看从器件的数据手册,一般你Ctrl+F搜索ID就可以找得到,通信开始拉低片选信号,结束时候拉高。 大概意思: 读取设备lD指令,该指令是通过驱动CS引脚拉低,并移动指令90h后跟一个0x00的24位地址来启动的。最高有效位(MSB)优先。初始化、发送字节数据、读取ID的部分代码 static GPIO_InitTypeDef GPIO_InitStructure; static SPI_InitTypeDef SPI_InitStructure; #define W25QXX_SS PBout(14) void w25qxx_init(void) { /*!< Enable the SPI clock,使能SPI1硬件时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /*!< Enable GPIO clocks,使能GPIOB硬件时钟 */ RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOB, ENABLE); //SPI1端口配置 PB3 PB4 PB5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOB,&GPIO_InitStructure); /*!< Connect SPI1 pins to AF3 AF4 AF5 */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1); //初始化片选引脚 PB14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //输出功能 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //速度50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //推挽复用输出 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //上拉 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB,&GPIO_InitStructure); //由于M4芯片还没有真正配置好,先不让外部SPI设备工作 W25QXX_SS = 1; /*!< SPI configuration ,SPI的配置*/ //设置SPI为双线双向全双工通信 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //配置M4工作在主机模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI的发送和接收都是8位数据位 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //串口时钟线(SCLK)空闲的时候为高电平,这里电平的设置要根据通信的外围设备有关系的 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行时钟的第二跳变沿进行数据采样,即采用了模式3 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //很多时候基于多设备通信,片选引脚都设置为软件控制 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //SPI通信时钟 = 84MHz/16=5.25MHz //最高有效位优先,根据通信的外围设备有关系的 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); /*!< Enable the sFLASH_SPI ,使能SPI1硬件*/ SPI_Cmd(SPI1, ENABLE); } //发送一个字节数据 uint8_t SPI1_SendByte(uint8_t byte) { /*!< Loop while DR register in not emplty */ while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); /*!< Send byte through the SPI1 peripheral */ SPI_I2S_SendData(SPI1, byte); /*!< Wait to receive a byte */ while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); /*!< Return the byte read from the SPI bus */ return SPI_I2S_ReceiveData(SPI1); } //读取厂家ID数据 uint16_t w25qxx_read_id(void) { uint16_t id=0; //片选引脚为低电平 W25QXX_SS = 0; //发送0x90指令 SPI1_SendByte(0x90); //发送24bit地址,全都是0 SPI1_SendByte(0x00); SPI1_SendByte(0x00); SPI1_SendByte(0x00); //读取厂商ID,填写任意参数,十六位数据,先将获取的放在高八位 id = SPI1_SendByte(0xFF)<<8; //读取设备ID,填写任意参数,读取第八位数据 id |= SPI1_SendByte(0xFF); //片选引脚为高电平 W25QXX_SS = 1; //打印出ID return id; } (2)读取指定地址的数据 英文大概意思: (1)读取数据指令允许从内存中顺序地读取一个或多个数据字节。 参数:addr是24位地址,*pbuf需要用户在外部定义数组来存储返回的数据,len是数组的长度 void w25qxx_read_data(uint32_t addr,uint8_t *pbuf,uint32_t len) { W25QXX_SS=0;//拉低片选信号 SPI1_SendByte(0x03);//发送读数据指令 SPI1_SendByte((addr>>16)&0xFF);//发送读取数据的地址,24位,一次一个字节 SPI1_SendByte((addr>>8)&0xFF);//总共三次 SPI1_SendByte((addr>>0)&0xFF); while(len--) { *pbuf++ = SPI1_SendByte(0XFF);//返回读取的数据 } W25QXX_SS=1;//拉高片选信号 } 调用: int main(void) { uint16_t id=0; uint8_t buf[64]; uint32_t i=0; //系统定时器初始化,时钟源来自HCLK,且进行8分频, SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //串口1,波特率115200bps,开启接收中断 USART1_Init(115200); //w25qxx初始化 w25qxx_init(); //读取ID id = w25qxx_read_id(); printf("w25qxx id=%X rn",id); //读取数据,从0地址开始读取,连续读取64字节 w25qxx_read_data(0,buf,64); printf("w25qxx read addr from 0 data is:rn"); for(i=0; i<64; i++) { printf("%02X ",buf); } printf("rn");//打印完回车换行 while(1) } (3)扇区擦除(这部分较为繁琐) 英文的大概意思: (1)扇区擦除都是以4KB为单位,擦除后,所有数据都变为0xFF。写使能: //解除写保护 void w25qxx_write_enable(void) { //片选引脚为低电平 W25QXX_SS = 0; //发送0x06指令 SPI1_SendByte(0x06); //片选引脚为高电平 W25QXX_SS = 1; } 写失能 //开启写保护 void w25qxx_write_disable(void) { //片选引脚为低电平 W25QXX_SS = 0; //发送0x04指令 SPI1_SendByte(0x04); //片选引脚为高电平 W25QXX_SS = 1; } 寄存器 //读状态寄存器1 uint8_t w25qxx_read_status1(void) { uint8_t status; //片选引脚为低电平 W25QXX_SS = 0; //发送0x05指令 SPI1_SendByte(0x05); //读取状态寄存器1的值 status = SPI1_SendByte(0xFF); //片选引脚为高电平 W25QXX_SS = 1; return status; } 将上述结合起来,进行扇区擦除 void w25qxx_erase_sector(uint32_t addr) { uint8_t status; //解除写保护 w25qxx_write_enable(); //延时1ms,让W25Q128能够识别到CS引脚电平的变化 delay_ms(1); //片选引脚为低电平 W25QXX_SS = 0; //发送0x20指令 SPI1_SendByte(0x20); //发送24bit地址 SPI1_SendByte((addr>>16)&0xFF); SPI1_SendByte((addr>>8)&0xFF); SPI1_SendByte( addr&0xFF); //片选引脚为高电平 W25QXX_SS = 1; while(1) { //读取状态寄存器1 status= w25qxx_read_status1(); //若BUSY位为0,则跳出循环 if((status & 0x01) == 0) break; } //开启写保护 w25qxx_write_disable(); } 调用 int main(void) { uint16_t id=0; uint8_t buf[64]; uint32_t i=0; SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //串口1,波特率115200bps,开启接收中断 USART1_Init(115200); //w25qxx初始化 w25qxx_init(); //读取ID id = w25qxx_read_id(); printf("w25qxx id=%X rn",id); //进行扇区擦除 printf("w25qxx erase from 0 rn"); w25qxx_erase_sector(0); //读取数据,从0地址开始读取,连续读取64字节 w25qxx_read_data(0,buf,64); printf("w25qxx read addr from 0 data is:rn"); for(i=0; i<64; i++) { printf("%02X ",buf); } printf("rn"); while(1) } (4)页写 和扇区擦除过程有点像 void w25qxx_write_data(uint32_t addr,uint8_t *pbuf,uint32_t len) { uint8_t status; //解除写保护 w25qxx_write_enable(); //延时1ms,让W25Q128能够识别到CS引脚电平的变化 delay_ms(1); //片选引脚为低电平 W25QXX_SS = 0; //发送0x02指令 SPI1_SendByte(0x02); //发送24bit地址 SPI1_SendByte((addr>>16)&0xFF); SPI1_SendByte((addr>>8)&0xFF); SPI1_SendByte( addr&0xFF); //写入数据 while(len--) SPI1_SendByte(*pbuf++); //片选引脚为高电平 W25QXX_SS = 1; while(1) { //读取状态寄存器1 status= w25qxx_read_status1(); //若BUSY位为0,则跳出循环 if((status & 0x01) == 0) break; } //开启写保护 w25qxx_write_disable(); } 调用的时候需要添加头文件,string.h,在主函数中进行调用。 uint8_t SPI1_SendByte(uint8_t byte) (5)下一篇文章写使用普通的IO口来模拟SPI时序,替代发送一个字节数据函数: uint8_t SPI1_SendByte(uint8_t byte) |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1627 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1550 浏览 1 评论
984 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1601 浏览 2 评论
1867浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
650浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
518浏览 3评论
536浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
506浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-24 18:17 , Processed in 0.724980 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号