SPI介绍
SPI:串行外部设备接口 --- 通信接口
特点:高速同步串行全双工 --- 40MHz
场景:存储器 OLED 无线通信 传感器
硬件连线:
SCL 时钟线 --------------------- 只能由主机提供
MOSI 发送线 ---------------------- 主机输出从机输入
MISO 接收线 ---------------------- 主机输入从机输出
CS 片选线 ---------------------- 开始/停止信号
SPI通信
特点:
数据位: 8 /16位 //取决于从器件支持
MSB 在前/LSB 在前 //取决于从器件支持
传输过程:
1、MCU选中芯片 //拉低片选
2、传输数据
3、MCU释放芯片 //拉高片选
即 数据帧 : 片选拉低 + 数据位 + 片选拉高
SPI模式
SPI有四种标准模式,其主要区别体现:
什么时候输出数据,什么时候采样数据
不同模式由以下两个参数决定:
时钟极性CPOL:在空闲状态是,SCK的默认电平
时钟相位CPHA:决定数据在哪个边沿采样 //可以理解为时钟格式
CPHA = 0 时钟线的第一个跳变沿数据线可以采样
CPHA = 1 时钟线的第二个跳变沿数据线可以采样
两位参数可以组成4种模式,常用的是0模式和3模式
SPI通信传输,双方必须在相同模式
以主机视角:
边沿输出数据:即将发送移位寄存器的最高位的数据输出ODR
边沿采样数据:即将读取IDR数据移到接收移位寄存器的最低位
以下为模式0的时序图,进行分析
数据的第一个位bit1在第一个上升沿之前,即SCK在低电平时
就已经将bit1的数据输出到ODR了
接下来的时序
上升沿到来,读取数据
下降沿到来,输出数据
SPI外设
以下为STM32F4系列的SPI资源
SPI数据
SPI发送数据和接收数据的规则:
发送一个位数据一定会接收到一位数据
接收一个位数据之前一定要发送一位数据 //必须主机主动发送数据
所以,发送函数和接收函数要写成一个函数.
注意:
要发送有意义的数据,同时会接受到无意义的数据,接收数据不用理会
要接受有意义的数据,同时需要发送无意义的数据,发送数据随意即可
SPI代码
/***************************************
*函数名 :spi1_init
*函数功能 :spi所用IO口初始化配置函数
*函数参数 :无
*函数返回值 :无
*函数描述 :SCK------PA5 复用输出
MOSI-----PA7 复用推挽输出
MISO-----PA6 复用输入
****************************************/
void spi1_init(void)
{
/*IO口配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<0);
//端口模式配置
GPIOA->MODER &= ~((3<<10) | (3<<14) | (3<<12));
GPIOA->MODER |= ((2<<10) | (2<<14) | (2<<12));
//端口输出类型
GPIOA->OTYPER &= ~((1<<5) | (1<<7));
//端口输出速度
GPIOA->OSPEEDR &= ~((3<<10) | (3<<14));
GPIOA->OSPEEDR |= ((2<<10) | (2<<14));
//无上下拉
GPIOA->PUPDR &= ~((3<<10) | (3<<14) | (3<<12));
//SPI1与IO口复用关系
GPIOA->AFR[0] &= ~((0xf<<20) | (0xf<<24) | (0xf<<28));
GPIOA->AFR[0] |= ((5<<20) | (5<<24) |(5<<28));
/*SPI控制器配置*/
//SPI1时钟使能
RCC->APB2ENR |= (1<<12);
//CR1
SPI1->CR1 &= ~(1<<15); //双线单向
SPI1->CR1 &= ~(1<<11); //使用8位数据格式
SPI1->CR1 &= ~(1<<10); //全双工模式
SPI1->CR1 |= (1<<9); //SSM置1 软件片选(NSS)控制
SPI1->CR1 |= (1<<8); //SSI置1 禁止软件从设备,即做主机
SPI1->CR1 &= ~(1<<7); //MSB高位先行
SPI1->CR1 &= ~(7<<3); //2分频
SPI1->CR1 |= (1<<2); //主机模式
SPI1->CR1 &= ~(1<<1); //CPOL=0 空闲状态下, SCK保持低电平
SPI1->CR1 &= ~(1<<0); //CPHA=0 数据采集从第一个时钟边沿开始采样
//CR2
SPI1->CR2 &= ~(1<<4); //使用Motorrla模式
//使能SPI
SPI1->CR1 |= (1<<6);
}
/***************************************
*函数名 :spi1_byte
*函数功能 :spi1收发一字节函数
*函数参数 :u8 data
*函数返回值 :u8
*函数描述 :0,0模式
****************************************/
u8 spi1_byte(u8 data)
{
u8 val;
//等待之前的数据发送完成
while(!(SPI1->SR & (1<<1)));
//把要发送的数据给数据寄存器
SPI1->DR = data;
//等待接收数据完成
while(!(SPI1->SR & (1<<0)));
//把数据寄存器的值给到一个变量
val = SPI1->DR;
return val;
}
W25Q64介绍
W25Q64是一款Flash类型存储芯片.
内存大小64Mbit ==8Mbyte
通信接口是标准SPI, 支持 (0,0) 和 (1,1) 模式 MSB
手动擦除数据(写数据前要擦除空间)
不允许跨页写
存储结构:
存储空间是8M字节
内存区域划分: 块 扇区 页
块 :一共有128块,每块有16扇区
扇区:一个扇区有16页
页 :一页有256byte
写入地址:
十六进制: 0~0x7F F F FF 范围
某块 的 某扇区 的 某页 的 某个字节
XX X X XX
如写入 0x12 3 4 56 即18号块的3号扇区的4号页的86号字节
W25Q64框图
W25Q64指令
0x06 写使能
0x05 读控制及状态寄存器
0x01 写控制及状态寄存器
0x02 页写操作
0x03 连续读数据操作
0x20 扇区擦除0xD8 块区擦除
0xC7 芯片擦除
W25Q64页写
根据以上指令结合W25Q64手册说明,即可封装相应的函数
以下为对W25Q64的页写函数
其中写使能和等待写周期完成函数的如何编写可以通过手册查看
/***************************************
*函数名 :w25q64_page_write
*函数功能 :页写功能
*函数参数 :u32 inner_addr 要写入的起始地址
u16 len 写入数据的长度
u8 *data 要写人数据的首地址
*函数返回值 :无
*函数描述 :0x02
****************************************/
void w25q64_page_write(u32 inner_addr,u16 len,u8 *data)
{
write_enable(); //写使能
//片选拉低
W25Q64_CS_L;
spi1_byte(0x02); //发送指令
//发送地址
spi1_byte((u8)(inner_addr>>16));
spi1_byte((u8)(inner_addr>>8));
spi1_byte((u8)(inner_addr));
//循环发送数据
while(len)
{
spi1_byte(*data);
data++;
len--;
}//片选拉高
W25Q64_CS_H;
//等待写入完成
wait_busy(); //等待BUSY位清零
}
通过页写函数,还可以封装成自由连续写函数
其算法和前篇《STM32 IIC通信 开漏模式 & AT24C02》 AT24C02连续写函数相同