软件SPI(STM32笔记)

发布时间:2026/8/14 13:32:07
软件SPI(STM32笔记) 目录简介连接图移位示意图SPI时序基本单元传输字节软件模拟SPI通信代码简介SPISerial Peripheral Interface是由Motorola公司开发的一种通用数据总线四根通信线SCKSerial Clock、MOSIMaster Output Slave Input、MISOMaster Input Slave Output、SSSlave Select 同步全双工支持总线挂载多设备一主多从连接图通常SPI通过四个引脚与外部设备相连SCK时钟引脚MISO主设备输入/从设备输出引脚。该引脚在从模式下发送数据在主模式下接收数据。MOSI主设备输出/从设备输入引脚。该引脚在主模式下发送数据在从模式下接收数据。NSS从设备选择。这是一个可选的引脚用来选择主/从设备。主机想指定谁就把对应的SS线拉低就好了所有SPI设备的SCK、MOSI、MISO分别连在一起主机另外引出多条SS控制线分别接到各从机的SS引脚输出引脚配置为推挽输出输入引脚配置为浮空或上拉输入。因为MOSI输出那他就是输出输入引脚就是输入不会出现冲突。但是主机的MISO为输入但是从机却有很多个输出如果三个从机都是推挽输出的话这样会导致冲突当从机未被选中的时候那么从机的SS引脚为高电平那么协议就规定未被选中的从机的MISO为高阻态。高阻态就是代表引脚断开不输出任何电平。在SS为低电平时从机的MISO才被允许变为推挽输出移位示意图主机通过MISO接收从机的数据后放入移位寄存器的左边。主机通过MOSI发送数据到从机的移位寄存器中的左边。两者都是高位先行。每来一个时钟移位寄存器就向左移动一位。SCL上升沿主机和从机都会对数据进行一个左移发送将数据发送至图中的引脚连线上电路中是输入寄存器。随后下降沿主机和从机会对数据进行一个接收。基础是交换一个字节SPI时序基本单元起始条件SS从高电平切换到低电平终止条件SS从低电平切换到高电平传输字节SPI有四种模式他们由时钟极性CPOL和时钟相位CPHA共同决定模式0CPOL0空闲状态时SCK为低电平CPHA0SCK第一个边沿移入数据第二个边沿移出数据模式1CPOL0空闲状态时SCK为低电平CPHA1SCK第一个边沿移出数据第二个边沿移入数据模式2CPOL1空闲状态时SCK为高电平CPHA0SCK第一个边沿移入数据第二个边沿移出数据模式3CPOL1空闲状态时SCK为高电平CPHA1SCK第一个边沿移出数据第二个边沿移入数据模式0模式1模式2是模式0的SCK的波形取反。同理模式3也是模式1的SCK波形取反补充IIC一般传输数据是寄存器地址读写数据。而SPI是指令码读写数据。在从机中对应的会定义一个指令集。当我们需要发送什么指令时就可以在起始后的第一个字节发送指令集里面的数据。一般指令集在从机的芯片手册里都会定义好相应的指令集。什么指令对应什么功能什么指令后面跟什么数据都会定义好。SPI没有应答位。每次就是起始传数据传数据传数据停止。本文是通过W25Q64展示SPI通信的每次写入数据时先擦除再写入。擦除时不能指定某个字节擦除要擦除最小单元的区域。最小单元是一个扇区4096个字节软件模拟SPI通信代码#include stm32f10x.h // Device header void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); } //输出引脚 void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); } //输入引脚 uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); } void MySPI_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); MySPI_W_SS(1);//给高电平说明此时没有选中从设备 //使用的模式0所以默认SCK为低电平 MySPI_W_SCK(0); } /* * SPI三个基本时序 * 1.起始信号 * 2.终止信号 * 3.交换一个字节 */ //起始信号 void MySPI_Start(void) { //初始化时已经默认SS为高电平了 MySPI_W_SS(0); } //终止信号 void MySPI_Stop(void) { //此时SS已经是低电平 MySPI_W_SS(1); } /* 交换一个字节.参数ByteSend是发送的字节 * 此处使用的是SPI的模式0 * 先移出数据第一个上升沿移入 */ uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t ByteReceive 0x00; //用于接收 for(uint8_t i 0;i 8;i ) { MySPI_W_MOSI(ByteSend (0x80 i)); //下降沿主机移出数据 MySPI_W_SCK(1); //SCK上升沿主机移入数据 if(MySPI_R_MISO() ! 0){ByteReceive | (0x80 i);} //从机移入数据会自己进行 MySPI_W_SCK(0); //下降沿要移出下一位数据 } return ByteReceive; } //uint8_t MySPI_SwapByte(uint8_t ByteSend) //{ // uint8_t ByteReceive 0x00; //用于接收 // // for(uint8_t i 0;i 8;i ) // { // MySPI_W_MOSI(ByteSend 0x80); //下降沿主机移出数据 // ByteSend 1; // MySPI_W_SCK(1); //SCK上升沿主机移入数据 // if(MySPI_R_MISO() ! 0){ByteSend | 0x01;} //从机移入数据会自己进行 // MySPI_W_SCK(0); //下降沿要移出下一位数据 // } // // return ByteSend; //}W25Q64模块#include stm32f10x.h // Device header #include MYSPI.h #include W25Q64_INS.h void W25Q64_Init(void) { MySPI_Init(); } void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); MySPI_SwapByte(W25Q64_JEDEC_ID); //这一次是说明我要读ID号下一次交换就是返回ID号 *MID MySPI_SwapByte(W25Q64_DUMMY_BYTE); //这次返回厂商ID *DID MySPI_SwapByte(W25Q64_DUMMY_BYTE); //这次返回设备ID的高八位 *DID 8; *DID | MySPI_SwapByte(W25Q64_DUMMY_BYTE); //这次返回设备ID的低八位 } /*写使能*/ //W25Q64规定SPI起始的第一个都是指令码 void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } void W25Q64_WaitBusy(void) { uint32_t Timeout 100000; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); // while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) 0x01) 0x01) //发送0xFF接收数据 { Timeout --; if(Timeout 0) break; } MySPI_Stop(); } //页编程函数 void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t count) { W25Q64_WriteEnable(); uint16_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_PAGE_PROGRAM); MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); for(i 0; i count ; i ) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy(); } void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); MySPI_Stop(); W25Q64_WaitBusy(); } void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_DATA); MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); for(i 0;i Count ;i ) { DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }