目录
一、SPI简介
二、硬件基础
三、SPI基本的通讯单元
0.SPI移位寄存器工作原理:
1.起始条件:
2.终止条件:
3.交换一个字节:
四、软件SPI读写STM32F103ZET6板载W25Q16的ID号
1.硬件前提:
2.软件部分:
1.初始化引脚:
2. 发送9h指令读取ID号。
3.打印在OLED屏幕上:
4.现象:
SPI的英文全称为Serial Peripheral Interface,是一种通用数据总线。较为重要的端口为MOSI(主机输出,从机输入)、MISO(主机输入,从机输出)、SCL(总线时钟)、CS(片选信号)。
如下图所示,主机的MOSI与从机的SDI(Slave Data Input)相连,主机的MISO与从机的SDO(Slave Data Output),主机片选信号CS与从机片选相连(Chip Select)通常低电平有效,时钟总线相连一起。根据输入输出特性,在软件SPI中,将MOSI、SCL、CS配置为推挽输出、将MISO配置成上拉输入即可。
SPI主机与从机通讯实际上时两者的移位寄存器交换数据的过程,在时钟信号SCL的推动下,主机发送一位,从机也发送一位,两者互换数据,交换8次之后,便完成了一个字节的传输。
代码前提:
void MySPI_W_CS(uint8_t value)
{
GPIO_WriteBit(SPI_PORT,SPI_CS_PIN,(BitAction)value);
}
void MySPI_W_SCL(uint8_t value)
{
GPIO_WriteBit(SPI_PORT,SPI_SCL_PIN,(BitAction)value);
}
void MySPI_W_MOSI(uint8_t value)
{
GPIO_WriteBit(SPI_PORT,SPI_MOSI_PIN,(BitAction)value);
}
uint8_t MySPI_R_MISO(void )
{
return GPIO_ReadInputDataBit(SPI_PORT,SPI_MISO_PIN);;
}
片选信号由高至低,即选中从机芯片通讯的过程。
void MySPI_Start(void )
{
MySPI_W_CS(0);
}
片选信号由低至高,即不选从机芯片的过程。
void MySPI_Stop(void )
{
MySPI_W_CS(1);
}
介绍一种最常用的通讯模式:CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
uint8_t MySPI_SwapData(uint8_t Data)
{
uint8_t temp=0x00;
for (int i = 0; i < 8; i++)
{
MySPI_W_MOSI(Data & (0x80>>i));
MySPI_W_SCL(1);
if(MySPI_R_MISO()==1)
{
temp|=(0x80>>i);
}
MySPI_W_SCL(0);
}
return temp;
}
多数商家所售STM32F103ZET6自带一块支持SPI通讯的W25Q16,如下图:
原理图如下:观察可知,所接的PB12、13、14、15也为板上ZET6班上对应的硬件SPI资源,本文中我们使用软件时序模拟SPI。
本文中我们读出该设备ID号 :查看手册可知:MID为EF(十六进制),DID为4015(十六进制)。
void MySPI_Init(void )
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=SPI_CS_PIN|SPI_SCL_PIN|SPI_MOSI_PIN;
GPIO_Init(SPI_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=SPI_MISO_PIN;
GPIO_Init(SPI_PORT,&GPIO_InitStructure);
MySPI_W_CS(1);
MySPI_W_SCL(0);
}
void W25Q16_Init(void )
{
MySPI_Init();
}
void W25Q16_Read_ID(uint8_t* MID,uint16_t* DID)
{
MySPI_Start();
MySPI_SwapData(0x9F);
*MID=MySPI_SwapData(0xFF);
*DID=MySPI_SwapData(0xFF);
*DID<<=8;
*DID|=MySPI_SwapData(0xFF);
MySPI_Stop();
}
#include "main.h"
static int i=0;
int main(void)
{
uint8_t MID=0;
uint16_t DID=0;
uart_init(115200);
delay_init();
OLED_Init();
OLED_Clear();
W25Q16_Init();
W25Q16_Read_ID(&MID,&DID);
while(1)
{
for (; i < 1; ++i) {
OLED_Init();
OLED_Clear();
}
OLED_ShowString(1,0,"MID:",16);
OLED_ShowString(1,2,"DID:",16);
OLED_ShowHexNum(40,0,MID,2);
OLED_ShowHexNum(40,2,DID,4);
}
return 0;
}
符合最终的现象,正确读出设备ID号!