第一节、SPI通信原理与相关概念:
1.SPI总线的特点:
SPI(serial peripheral interface)串行外设接口的缩写,它是Motorola公司推出的一种同步串行接口技术,是一种高速的,全双工的,同步的通信总线。
SPI是支持全双工通信, 通信简单,数据传输速率快,非差分的,一主多从的通信模式。
所谓为非差分与差分的概念是指在远距离传输时,是否通过比较两个根的电平的差来判断是传据是高还是低,一般远距离传输都是使用差分总线,近距离的都是非差分总线。SPI是非差分总线,R485就是一种差分总线。了解即可。
缺点:无应答机制确认是否接收到数据,所以与I2C总线协议比较在数据的可靠性上有一点差距,但是速度快啊。
与I2c相比:
2.三根共享线:SCK,MOSI,MISO,及1个或多个片选线:
1. 常规SPI模式:
2.一个片选的菊花链模式:
3.SPI数据传输方式:
SPI 主设备和从设备都有一个串行移位寄存器,主设备通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。
SPI控制器硬件框图:
4.SPI的采样模式:由CPOL(时钟极性)与 CPHA(时钟相位)
CPOL决定空闲时的电平的高低,CPHA决定采样的时机
当:CPOL(时钟极性) = 0 时,即空闲时电平为低,第一个边沿意味为由低到高的上升边沿。
当:CPOL(时钟极性) = 1时,即空闲时电平为高,第一个边沿意味为由高到低的下降边沿。
当:CPHA(时钟相位) = 0时,即第一个边沿采样,下一个边沿传输。
当:CPHA(时钟相位) = 1时,即第一个边沿传输,下一个边沿采样。
所以就组合了四种模式:Mode0(CPOL=0, CPHA = 0), Model1(CPOL=0, CPHA = 1), Model2(CPOL=1,CPHA=0), Model3(CPOL=1,CPHA=1);
比如:模式0时序:
5.使用CubeMX快速配置SPI控制器:
要点:1.配置时钟的波特率,2.配置采样模式,3.配置传输高位优先还是低位优先,4.配置CS一般使用一个单独的GPIO作为选择。5.使用SPI功能。
第二节、SPI总线接口驱动HAL库API:
1. 阻塞方式:
2.非阻塞方式:
传输完成或出错的回调函数:
3.DMA方式:
添加DMA方式:
第三节、W25Q64Flash数据手册简读:
1.W25Q64芯片简述:
1. 引脚说明及功能:
2.Flash存储管理:
2.1 读数据:Read
读取数据,非常简单,发出读取指令 + 某个位置就可以读取数据了。
2.2 写数据:Write
擦除最小单元是扇区,写入最小单位是页,数据写入的过程也叫页编程。
每次擦除与写入都会有一定的时间的延时,可以使用轮询状态寄存器,来获取相应的状态,也可以直接使用延时函数。
每次擦除或写入都要解锁写使能。否则无法擦除或写入。
写入数据的过程可以分解为:
2.擦除扇区:
3.获取擦除是否完成的状态:
4.写入数据(页编程)
第四节、阻塞方式实现Flash的数据存取驱动:
1. 获取w25q64设备制造端id:
#include "spi_w25q64_driver.h"
//获取制造端ID与设备ID:
uint8_t getManufacturer_id()
{
//1.选择设备:相应cs片选拉低为低电平:
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_RESET);
//获取的逻辑在此构建:
//2.发送一个读取制造商ID的指令:(阻塞方式发送)
//获取制造商id的指令:0x9f,得到将是0xef;
uint8_t get_mid_cmd = 0x9f;
HAL_SPI_Transmit(&hspi1, &get_mid_cmd, 1, portMAX_DELAY);
//读取制造商id:
uint8_t mid_id = 0;
HAL_SPI_Receive(&hspi1,&mid_id,1, portMAX_DELAY);
//结束时,再拉高为高电平,取消选择:
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_SET);
return mid_id;
}
测试:
#include "uart_app.h"
#include "uart_driver.h"
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "mpu6050_driver.h"
#include "spi_w25q64_driver.h"
extern struct i2c_mpu6050 mpu6050;
//uart测试任务回调函数:
void uart_task_function(void* arg)
{
const char* str = "Welcome uart_test,Please input CMD: \n";
//uart_send_by_dma((void*)str,strlen(str));
printf("%s",str);
printf("%d \n",1000);
float f = 3.1415926;
printf("%.2f \n",f);
//char buf[128] = {0};
//初始化mpu6050:
mpu6050_init(&mpu6050);
// float roll_x, pitch_y, yaw_z;
// roll_x = pitch_y = yaw_z = 0;
uint8_t val = 0;
while (true)
{
// /* 测试uart串口收发数据 */
val = getManufacturer_id();
printf("%#x \n",val);
vTaskDelay(1000);
}
}
2.写入数据与读取数据的驱动:
#include "spi_w25q64_driver.h"
//获取制造端ID与设备ID:
uint8_t getManufacturer_id()
{
//1.选择设备:相应cs片选拉低为低电平:
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_RESET);
//获取的逻辑在此构建:
//2.发送一个读取制造商ID的指令:(阻塞方式发送)
//获取制造商id的指令:0x9f,得到将是0xef;
uint8_t get_mid_cmd = 0x9f;
HAL_SPI_Transmit(&hspi1, &get_mid_cmd, 1, portMAX_DELAY);
//读取制造商id:
uint8_t mid_id = 0;
HAL_SPI_Receive(&hspi1,&mid_id,1, portMAX_DELAY);
//结束时,再拉高为高电平,取消选择:
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9, GPIO_PIN_SET);
return mid_id;
}
//向指定的扇区位置写入一页数据:
int w25q64_write(uint32_t sector_addr, uint8_t* buf, uint16_t bufLen)
{
//1.写使能:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
uint8_t write_enable = 0x06;
HAL_SPI_Transmit(&hspi1, &write_enable, 1, portMAX_DELAY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
//2.扇区擦除
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
uint8_t erase_sector[4] = {0x20,};
erase_sector[1] = (sector_addr >> 16) & 0xff;
erase_sector[2] = (sector_addr >> 8) & 0xff;
erase_sector[3] = (sector_addr >> 0) & 0xff;
HAL_SPI_Transmit(&hspi1, erase_sector, 4, portMAX_DELAY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
//3.是否完成了擦除:获取相应的状态。s0bit是否为0,0代表擦除完成
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
uint8_t status_1_reg = 0x05;
HAL_SPI_Transmit(&hspi1, &status_1_reg, 1, portMAX_DELAY);
uint8_t status = 0;
do
{
HAL_SPI_Receive(&hspi1, &status, 1, portMAX_DELAY);
} while (status & 0x01);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
//4. 写使能
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &write_enable, 1, portMAX_DELAY);
//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
//5. 页编程
//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
uint8_t page_cmd[4] = {
0x02,
};
page_cmd[1] = (sector_addr >> 16) & 0xff;
page_cmd[2] = (sector_addr >> 8) & 0xff;
page_cmd[3] = (sector_addr >> 0) & 0xff;
HAL_SPI_Transmit(&hspi1,page_cmd, 4, portMAX_DELAY);
HAL_SPI_Transmit(&hspi1, buf, bufLen, portMAX_DELAY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
return 0;
}
//从指定扇区位置读取数据:
int w25q64_read(uint32_t sector_addr, uint8_t* buf, uint16_t bufLen)
{
//片选:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
//读数据指令:
uint8_t read_cmd[4] = {
0x03,
};
read_cmd[1] = (sector_addr >> 16) & 0xff;
read_cmd[2] = (sector_addr >> 8) & 0xff;
read_cmd[3] = (sector_addr >> 0) & 0xff;
HAL_SPI_Transmit(&hspi1,read_cmd, 4, portMAX_DELAY);
HAL_SPI_Receive(&hspi1, buf, bufLen, portMAX_DELAY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
return 0;
}
第一次无法接到数据的问题,把波特率提高即可解决。
应用测试:
#include "uart_app.h"
#include "uart_driver.h"
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "mpu6050_driver.h"
#include "spi_w25q64_driver.h"
extern struct i2c_mpu6050 mpu6050;
//uart测试任务回调函数:
void uart_task_function(void* arg)
{
const char* str = "Welcome uart_test,Please input CMD: \n";
//uart_send_by_dma((void*)str,strlen(str));
printf("%s",str);
printf("%d \n",1000);
float f = 3.1415926;
printf("%.2f \n",f);
mpu6050_init(&mpu6050);
//1.向w25q64某扇区写入数据:
const char* hello = "Hello World W25Q64 Chip";
w25q64_write(0x00, (uint8_t*)hello, strlen(hello) + 1);
char buf[64] = {0};
w25q64_read(0x00, (uint8_t*)buf, sizeof(buf));
printf("buf data = %s\n",buf);
while (true)
{
memset(buf, 0, sizeof(buf));
w25q64_read(0x00, (uint8_t*)buf, sizeof(buf));
printf("buf data = %s\n",buf);
vTaskDelay(1000);
}
}