目录

一、SPI介绍

二、SPI接口框图

三、SPI优缺点

四、SPI工作原理总结

五、时序图

六、SPI程序编写过程

七、W25Q12xx的原理及应用

7.1 分析W25Q128指令

7.2 擦除扇区:

7.3 部分常用设备读取指令:


一、SPI介绍

SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

正是简单易用的特性,如NRF24L01、VS1053、SD卡等皆集成了这种通信协议


二、SPI接口框图


三、SPI优缺点

SPI接口是在CPU和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,低位在前,高位在后,为全双工通信,数据传输速度总体来说比I2C总线要快,速度可达到几Mbps。

信号线少,协议简单,相对数据速率高。

缺点:没有指定的流控制,没有应答机制确认是否接收到数据


四、SPI工作原理总结

  • 硬件上为4根线。
  • 主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
  • 串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
  • 外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

多个设备使用SPI的应用举例


五、时序图

时序为 SPI_CR1 寄存器中的 LSBFIRST 位复位时的时序

SPI_CPHA的值将会影响SPI_CPOL的值


六、SPI程序编写过程

//①使能SPIx和IO口时钟
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();//②初始化IO口为复用功能
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//③设置引脚复用映射:
GPIO_PinAFConfig();//②初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);//③使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);//④SPI传输数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;//⑦查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
SPI.c:
#include "spi.h"//以下是SPI模块的初始化代码,配置成主机模式
//SPI口初始化
//这里针是对SPI1的初始化
void SPI1_Init(void)
{    GPIO_InitTypeDef  GPIO_InitStructure;SPI_InitTypeDef  SPI_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1时钟//GPIOFB3,4,5初始化设置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5复用功能输出  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4复用为 SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5复用为 SPI1//这里只针对SPI口初始化RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;      //设置SPI工作模式:设置为主SPISPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;       //设置SPI的数据大小:SPI发送接收8位帧结构SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;       //串行同步时钟的空闲状态为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  //串行同步时钟的第二个跳变沿(上升或下降)数据被采样SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;     //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;     //定义波特率预分频的值:波特率预分频值为256SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始SPI_InitStructure.SPI_CRCPolynomial = 7;  //CRC值计算的多项式SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输
}
//SPI1速度设置函数
//SPI速度=fAPB2/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2时钟一般为84Mhz:
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性SPI1->CR1&=0XFFC7;//位3-5清零,用来设置波特率SPI1->CR1|=SPI_BaudRatePrescaler;   //设置SPI1速度 SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{                    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空  SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个byte  数据while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一个byte  return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据   }

七、W25Q12xx的原理及应用

W25Q128将16M的容量分为256个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。

W25Q128的擦写周期多达10W次,具有20年的数据保存期限,支持电压为2.7~3.6V,W25Q128支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可以到80Mhz(双输出时相当于160Mhz,四输出时相当于320M),更多的W25Q128的介绍,请参考W25Q128的DATASHEET。

W25Q12xx可根据原理图查看,使用的是SPI总线通信协议

比如原子的原理图

7.1 分析W25Q128指令

可参考W25Qxx的数据手册,这里列出W25Q128部分指令:

比如读取设备的ID的指令:0x90000000

7.2 擦除扇区:

7.3 部分常用设备读取指令:

0x90FFFFFF

读取厂商ID

0x20xxxxxx

擦除扇区地址 

0x05FFFFFF

  读取flash状态   

0x02|addr|data

写入数据
0x03 读取数据

每次操作前要使能写操作,且给一个低电平。结束时要给其为高电平,再失能写操作。也就是通过操作片选引脚来确定是否使能或失能。

W25Q12xx.c:
#include "w25qxx.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"    u16 W25QXX_TYPE=W25Q128;   //默认是W25Q128//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector //初始化SPI FLASH的IO口
void W25QXX_Init(void)
{ GPIO_InitTypeDef  GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//使能GPIOG时钟//GPIOB14GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//PB14GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//PG7GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化GPIO_SetBits(GPIOG,GPIO_Pin_7);//PG7输出1,防止NRF干扰SPI FLASH的通信 W25QXX_CS=1;          //SPI FLASH不选中SPI1_Init();                  //初始化SPISPI1_SetSpeed(SPI_BaudRatePrescaler_2);     //设置为42M时钟,高速模式 W25QXX_TYPE=W25QXX_ReadID();   //读取FLASH ID.
}  //读取W25QXX的状态寄存器
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
u8 W25QXX_ReadSR(void)
{  u8 byte=0;   W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_ReadStatusReg);    //发送读取状态寄存器命令    byte=SPI1_ReadWriteByte(0Xff);             //读取一个字节  W25QXX_CS=1;                            //取消片选     return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(u8 sr)
{   W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_WriteStatusReg);   //发送写取状态寄存器命令    SPI1_ReadWriteByte(sr);               //写入一个字节  W25QXX_CS=1;                            //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_WriteEnable);      //发送写使能  W25QXX_CS=1;                            //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{  W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_WriteDisable);     //发送写禁止指令    W25QXX_CS=1;                            //取消片选
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
u16 W25QXX_ReadID(void)
{u16 Temp = 0;   W25QXX_CS=0;                 SPI1_ReadWriteByte(0x90);//发送读取ID命令     SPI1_ReadWriteByte(0x00);       SPI1_ReadWriteByte(0x00);       SPI1_ReadWriteByte(0x00);                  Temp|=SPI1_ReadWriteByte(0xFF)<<8;  Temp|=SPI1_ReadWriteByte(0xFF);   W25QXX_CS=1;                  return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{ u16 i;                                            W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_ReadData);         //发送读取命令   SPI1_ReadWriteByte((u8)((ReadAddr)>>16));  //发送24bit地址    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));   SPI1_ReadWriteByte((u8)ReadAddr);   for(i=0;i<NumByteToRead;i++){ pBuffer[i]=SPI1_ReadWriteByte(0XFF);   //循环读数  }W25QXX_CS=1;
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u16 i;  W25QXX_Write_Enable();                  //SET WEL W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_PageProgram);      //发送写页命令   SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址    SPI1_ReadWriteByte((u8)((WriteAddr)>>8));   SPI1_ReadWriteByte((u8)WriteAddr);   for(i=0;i<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);//循环写数  W25QXX_CS=1;                            //取消片选 W25QXX_Wait_Busy();                    //等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{                    u16 pageremain;       pageremain=256-WriteAddr%256; //单页剩余的字节数                if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){      W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain;  NumByteToWrite-=pageremain;              //减去已经写入了的字节数if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节else pageremain=NumByteToWrite;       //不够256个字节了}};
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{ u32 secpos;u16 secoff;u16 secremain;     u16 i;    u8 * W25QXX_BUF;     W25QXX_BUF=W25QXX_BUFFER;         secpos=WriteAddr/4096;//扇区地址  secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小   //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节while(1) {   W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除     }if(i<secremain)//需要擦除{W25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;i<secremain;i++)     //复制{W25QXX_BUF[i+secoff]=pBuffer[i];    }W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.                   if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff=0;//偏移位置为0     pBuffer+=secremain;  //指针偏移WriteAddr+=secremain;//写地址偏移       NumByteToWrite-=secremain;              //字节数递减if(NumByteToWrite>4096)secremain=4096;   //下一个扇区还是写不完else secremain=NumByteToWrite;         //下一个扇区可以写完了}    };
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{                                   W25QXX_Write_Enable();                  //SET WEL W25QXX_Wait_Busy();   W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_ChipErase);        //发送片擦除命令  W25QXX_CS=1;                            //取消片选               W25QXX_Wait_Busy();                      //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{  //监视falsh擦除情况,测试用   printf("fe:%x\r\n",Dst_Addr);    Dst_Addr*=4096;W25QXX_Write_Enable();                  //SET WEL      W25QXX_Wait_Busy();   W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_SectorErase);      //发送扇区擦除指令 SPI1_ReadWriteByte((u8)((Dst_Addr)>>16));  //发送24bit地址    SPI1_ReadWriteByte((u8)((Dst_Addr)>>8));   SPI1_ReadWriteByte((u8)Dst_Addr);  W25QXX_CS=1;                            //取消片选            W25QXX_Wait_Busy();                      //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{   while((W25QXX_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{ W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_PowerDown);        //发送掉电命令  W25QXX_CS=1;                            //取消片选              delay_us(3);                               //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{  W25QXX_CS=0;                            //使能器件   SPI1_ReadWriteByte(W25X_ReleasePowerDown);   //  send W25X_PowerDown command 0xAB    W25QXX_CS=1;                            //取消片选             delay_us(3);                               //等待TRES1
}

W25Q12xx.h:#ifndef __W25QXX_H
#define __W25QXX_H
#include "sys.h"  //W25X系列/Q系列芯片列表
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16
//W25Q128 ID  0XEF17
#define W25Q80  0XEF13
#define W25Q16  0XEF14
#define W25Q32  0XEF15
#define W25Q64  0XEF16
#define W25Q128 0XEF17extern u16 W25QXX_TYPE;                   //定义W25QXX芯片型号         #define  W25QXX_CS       PBout(14)       //W25QXX的片选信号
//
//指令表
#define W25X_WriteEnable        0x06
#define W25X_WriteDisable       0x04
#define W25X_ReadStatusReg      0x05
#define W25X_WriteStatusReg     0x01
#define W25X_ReadData           0x03
#define W25X_FastReadData       0x0B
#define W25X_FastReadDual       0x3B
#define W25X_PageProgram        0x02
#define W25X_BlockErase         0xD8
#define W25X_SectorErase        0x20
#define W25X_ChipErase          0xC7
#define W25X_PowerDown          0xB9
#define W25X_ReleasePowerDown   0xAB
#define W25X_DeviceID           0xAB
#define W25X_ManufactDeviceID   0x90
#define W25X_JedecDeviceID      0x9F void W25QXX_Init(void);
u16  W25QXX_ReadID(void);               //读取FLASH ID
u8   W25QXX_ReadSR(void);               //读取状态寄存器
void W25QXX_Write_SR(u8 sr);            //写状态寄存器
void W25QXX_Write_Enable(void);         //写使能
void W25QXX_Write_Disable(void);        //写保护
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //读取flash
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);           //整片擦除
void W25QXX_Erase_Sector(u32 Dst_Addr); //扇区擦除
void W25QXX_Wait_Busy(void);            //等待空闲
void W25QXX_PowerDown(void);            //进入掉电模式
void W25QXX_WAKEUP(void);               //唤醒#endif

main.c:

main.c:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "spi.h"
#include "w25qxx.h"
#include "key.h"    //要写入到W25Q16的字符串数组
const u8 TEXT_Buffer[]={"Explorer STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)     int main(void)
{ u8 key;u16 i=0;u8 datatemp[SIZE];    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2delay_init(168);    //初始化延时函数uart_init(115200);    //初始化串口波特率为115200LED_Init();                    //初始化LED KEY_Init();                //按键初始化  W25QXX_Init();         //W25QXX初始化 while(W25QXX_ReadID()!=W25Q128)    //检测不到W25Q128{printf("W25Q128 Check Failed!");delay_ms(500);printf("Please Check!      ");delay_ms(500);LED0=!LED0;        //DS0闪烁}while(1){key=KEY_Scan(0);if(key==KEY1_PRES)//KEY1按下,写入24C02{printf("Start Write W25Q128...."); //从倒数第100个地址处开始,写入SIZE长度的数据  W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);printf("W25Q128 Write Finished!");  //提示传送完成}if(key==KEY0_PRES)//KEY0按下,读取字符串并显示{printf("Start Read W25Q128.... ");//从倒数第100个地址处开始,读出SIZE个字节W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);                    printf("The Data Readed Is: \r\n ");//提示传送完成printf("%s\r\n ",datatemp);}       }
}

STM32的SPI的原理与使用(W25Q128附代码)相关推荐

  1. 基于stm32、spi协议的Fatfs文件系统移植(附完整代码下载)

    开发环境:Window 7 32bit 开发工具:Keil uVision4 硬件:stm32f103vct6 目录 1.硬件设计: 2.软件设计 1.SPI收发数据 2.向SD卡发送的命令格式: 3 ...

  2. 对联智能生成的原理(学习笔记附代码实现与详解)

    文章均从个人微信公众号" AI牛逼顿"转载,文末扫码,欢迎关注! 过年的脚步越来越近,是不是该给家里贴上一副对联呢?除了买买买,有没有想过自己动手写出一副对联?来吧,撸起袖子加油干 ...

  3. 一文读懂舵机工作原理并运用(附代码)

    杂谈 自己拿到这一模块是也挺迷茫的,后来看了一些资料,也渐渐积累了些自己的理解,很多博文并没有将舵机讲明白,至少你待把PWM与角度如何换算讲清楚吧,所以笔者写这篇博文供大家学习掌握. 如果你拿到一个舵 ...

  4. 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)

    前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...

  5. 从新建工程开始使用C++开发单片机(以STM32为例):六、C++输入输出流(附代码)

    经过前面几篇文章的铺垫,完成了C语言接口层的GPIO.外部中断.串口.delay等接口,现在可以正式进入C++驱动层的文章.当然C语言接口层的还远没有完成,在以后的文章中还会继续更新. 本文将会介绍一 ...

  6. 灰色预测原理及实例(附代码)

    灰色预测 引言 古人说:"凡事预则立,不预则废."办任何事情之前,必须先调查研究,摸清情况,深思熟虑,有科学的预见,周密的计划,这样才能达到预期的成功. 所谓预测,就是人们根据可获 ...

  7. 孤立森林异常检测算法原理和实战(附代码)

    孤立森林(isolation Forest)算法,2008年由刘飞.周志华等提出,算法不借助类似距离.密度等指标去描述样本与其他样本的差异,而是直接去刻画所谓的疏离程度(isolation),因此该算 ...

  8. 多任务学习模型ESMM原理与实现(附代码)

    来源:DataFunTalk 本文约2500字,建议阅读5分钟 文章基于 Multi-Task Learning (MTL) 的思路,提出一种名为ESMM的CVR预估模型. [ 导读 ] 本文介绍的是 ...

  9. sklearn SVM原理与实现(附代码)

    1.SVM简介 支持向量机(Support Vector Machine, SVM)是一类按监督学习(supervised learning)方式对数据进行二元分类(binary classifica ...

最新文章

  1. 常考数据结构与算法:表达式求值
  2. 从C语言的角度重构数据结构系列(七)-数据结构堆知识求解数据流中的第K大元素
  3. mysql查看数据库和表的占用空间大小
  4. dotNET:怎样处理程序中的异常(实战篇)?
  5. docker sonarqube:7.7-community
  6. 07_支持向量机3_统计学习方法
  7. mysqls为node.js而编写的sql语句生成插件 crud for mysql.
  8. Spring MVC异常处理详解
  9. ADO.NET编程(4)根据条件查询DataTable的值
  10. “康园圈--互联网+校园平台“项目之拓展手机客户端
  11. 【训练题55:尺取 + 高阶等差】Another String | HDU7015 | 杭电多校五 04题
  12. 三星c5怎么改系统语言,三星c5驱动|三星c5手机驱动下载 v1.5.55.0 官方版 - 比克尔下载...
  13. CLRvia3读书笔记
  14. 利用U盘制作虚拟软驱加载raid驱动
  15. 数字转为汉语中人民币的大写
  16. Payoneer(P卡)怎么免费申请Joom账号?
  17. 中银国际证券java面试_Re: 【offer求比较】深圳关内老师vs中银国际证券后  - 找工作啦(Job)版 - 北大未名BBS...
  18. 『R语言Python』建模前的准备:连续型与离散型变量探索,离散型变量转为虚拟变量
  19. Python计算:sympy解数学方程
  20. 悲观的人更容易获得好的感觉

热门文章

  1. 也谈如何举办一场成功的技术讲座?
  2. 机器学习-近9年双色球开奖数据的频繁项集
  3. python什么语句提前结束循环_在循环语句中,__________语句的作用是提前结束本层循环。...
  4. mobiscroll实现年月日分别选择
  5. linux机器中毒,Centos系统中毒(sfewfesfs)处理过程记录
  6. VBA学习笔记:1、用msxml v6.0网抓
  7. 隆重推荐【SQLServer】127个SQL server热门资料汇总
  8. 前端页面插件集成-Markdown编辑器
  9. VLC 视频pause后seek画面概率性卡主问题分析记录
  10. 乔布斯居然是这样面试我的,你能挺到哪一步?