本帖最后由 浅末哈哈 于 2024-9-13 22:09 编辑
本帖最后由 浅末哈哈 于 2024-9-13 22:06 编辑
本帖最后由 浅末哈哈 于 2024-9-6 08:19 编辑
本帖最后由 浅末哈哈 于 2024-9-5 22:47 编辑
本帖最后由 浅末哈哈 于 2024-9-5 22:43 编辑
本帖最后由 浅末哈哈 于 2024-9-5 22:35 编辑
本帖最后由 浅末哈哈 于 2024-9-5 22:34 编辑
本帖最后由 浅末哈哈 于 2024-9-5 22:27 编辑
本帖最后由 浅末哈哈 于 2024-9-5 21:56 编辑
本帖最后由 浅末哈哈 于 2024-9-2 21:19 编辑
1、介绍
SPI 是英语Serial Peripheral interface的缩写,是一个主机和一个或多个从机的主从模式。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
同时,根据需求和应用场景不同还会由Dual SPI,Qual SPI改进Standard SPI的版本。
2、原理
2.1、电气连线
由四根线进行连接:
CS
:SPI设备片选;
SCLK
:SPI时钟线;
MOSI
:主机输出从机输入(Master Output Slaver Input)
MISO
:主机输入从机输出(Master Input Slaver Output)
针对多个从机进行SPI通信的电气连接:
利用片选CS脚进行选择不同的SPI设备。
2.2、SPI通信部分
2.2.1、SPI中的时钟相位(CPHA)和时钟极性(CPOL)
因为SPI是有主从模式,所以对于主从两个设备来说,通信时钟(SCLK)必须要保持一致,所以引入时钟极性和时钟相位的概念。
所谓时钟极性和时钟相位所指的就是SCLK的特性,通过设置这两个值保证主从设备时钟的特性一致,这样才能保证SPI能够正常通信。
- CPHA:时钟相位。表示SCLK的边沿,当CPHA=0,表示第一个边沿,CPHA=1,表示第二个边沿;
- CPOL:时钟极性。表示SCLK在空闲时段(IDLE)是高电平还是低电平。当CPOL=0,idle是低电平,CPOL=1,idle是高电平;
所以就能够组成四种模式
CPOL |
CPHA |
Mode |
0 |
0 |
mode0,时钟线空闲是出于低电平,在时钟上升沿进行采样,下降沿进行数据变化。 |
0 |
1 |
mode1,时钟线空闲处于低电平,在时钟下降沿进行采样,上升沿进行数据变化。 |
1 |
0 |
mode2,时钟空闲处于高电平,在时钟下降沿进行采样,上升沿进行数据变化。 |
1 |
1 |
mode3,时钟空闲处于高电平,在时钟上升沿进行采样,下降沿进行数据变化。 |
Mode0,CPOL=0,CPHA=0
Mode1,CPOL=0,CPHA=1
Mode2,CPOL=1,CPHA=0
Mode3,CPOL=1,CPHA=1
2.2.2、SPI通信
SPI数据通信图解
由主机产生时钟,当主机产生了8个时钟,主机寄存器中的数据A0 - A7会通过MOSI发送到从机的寄存器buff中,同一时间内,从机寄存器中的数据B0 - B7通过MISO发送给主机。
3、Ai-WB2-32S-Kit上的SPI
芯片内置一个 SPI,可以配置为主机模式或者从机模式,SPI模块时钟是 BCLK,具有以下特性:
- 主机模式下,时钟频率最高为 48 MHz
- 从机模式下,允许主机最大的时钟频率为 32 MHz
- 每帧的位宽可以配置为 8 位/ 16 位/ 24 位/ 32 位
- SPI 具有独立的收发 FIFO,FIFO 深度固定为 4 帧(即,如果帧的位宽是 8bit,FIFO 的深度是 4 字节),支持 DMA 功能。
3.0、WB2硬件Pins
因为WB2的引脚都能进行映射,所以只需根据图选择需要的SPI引脚进行配置。
3.1、SPI设备定义
通过查看 ..\Ai-Thinker-WB2\components\platform\hosal\include\hosal_spi.h
能注意到WB2的SPI主要是使用两个结构体进行配置:
/**
* @brief Define spi config args
*/
typedef struct {
uint8_t mode; /**< spi communication mode */
uint8_t dma_enable; /**< enable dma tansmission or not */
uint8_t polar_phase; /**< spi polar and phase */
uint32_t freq; /**< communication frequency Hz */
uint8_t pin_clk; /**< spi clk pin */
uint8_t pin_mosi; /**< spi mosi pin */
uint8_t pin_miso; /**< spi miso pin */
} hosal_spi_config_t;
/**
* @brief Define spi dev handle
*/
typedef struct {
uint8_t port; /**< spi port */
hosal_spi_config_t config; /**< spi config */
hosal_spi_irq_t cb; /**< spi interrupt callback */
void *p_arg; /**< arg pass to callback */
void *priv; /**< priv data */
} hosal_spi_dev_t;
其中 hosal_spi_config_t
是配置WB2上的硬件接口,hosal_spi_dev_t
是声明一个spi的设备。
hosal_spi_config_t
参数信息:
名称 |
备注 |
mode |
SPI的模式,设置SPI的主从模式。 |
dma_enable |
SPI的DMA使能设置。 |
polar_phase |
设置时钟极性和时钟相位。 |
freq |
设置SPI的时钟频率。 |
pin_clk |
设置SPI的时钟线。 |
pin_mosi |
设置SPI的MOSI数据线。 |
pin_miso |
设置SPI的MISO数据线。 |
hosal_spi_dev_t
参数信息:
名称 |
备注 |
port |
SPI端口。 |
config |
SPI硬件设置结构体。 |
cb |
SPI中断回调函数。 |
p_arg |
传给SPI中断回调函数的参数。 |
priv |
SPI设备私有信息。 |
3.2、API
/**
* @brief Initialises the SPI interface for a given SPI device
*
* @param[in] spi the spi device
*
* @return
* - 0 : on success
* - other : error
*/
int hosal_spi_init(hosal_spi_dev_t *spi);
/**
* @brief Spi send
*
* @param[in] spi the spi device
* @param[in] data spi send data
* @param[in] size spi send data size
* @param[in] timeout timeout in milisecond, set this value to HAL_WAIT_FOREVER
* if you want to wait forever
*
* @return
* - 0 : on success
* - other : error
*/
int hosal_spi_send(hosal_spi_dev_t *spi, const uint8_t *data, uint16_t size, uint32_t timeout);
/**
* @brief Spi recv
*
* @param[in] spi the spi device
* @param[out] data spi recv data
* @param[in] size spi recv data size
* @param[in] timeout timeout in milisecond, set this value to HAL_WAIT_FOREVER
* if you want to wait forever
*
* @return
* - 0 : success
* - other : error
*/
int hosal_spi_recv(hosal_spi_dev_t *spi, uint8_t *data, uint16_t size, uint32_t timeout);
/**
* @brief spi send data and recv
*
* @param[in] spi the spi device
* @param[in] tx_data spi send data
* @param[out] rx_data spi recv data
* @param[in] size spi data to be sent and recived
* @param[in] timeout timeout in milisecond, set this value to HAL_WAIT_FOREVER
* if you want to wait forever
*
* @return
* - 0 : success
* - other : error
*/
int hosal_spi_send_recv(hosal_spi_dev_t *spi, uint8_t *tx_data, uint8_t *rx_data, uint16_t size, uint32_t timeout);
注意,因为SPI是属于全双工模式,所以能够在发送的同时进行接收。
/**
* @brief set spi irq callback
*
* @param spi the spi device
* @param pfn callback function
* @param p_arg callback function parameter
*
* @return
* - 0 : success
* - othe : error
*/
int hosal_spi_irq_callback_set(hosal_spi_dev_t *spi, hosal_spi_irq_t pfn, void *p_arg);
/**
* @brief spi software set cs pin high/low only for master device
*
* @param[in] pin cs pin
* @param[in] value 0 or 1
*
* @return
* - 0 : success
* - other : error
*/
int hosal_spi_set_cs(uint8_t pin, uint8_t value);
/**
* @brief De-initialises a SPI interface
*
*
* @param[in] spi the SPI device to be de-initialised
*
* @return
* - 0 : success
* - other : error
*/
int hosal_spi_finalize(hosal_spi_dev_t *spi);
4、SPI实验
实验器材:W25Q64模块1个,Ai-WB2-32S-Kit 1个
实验目的:通过WB2上的SPI读取W25Q64的ID。
实验连线图:
因为模组只有一个SPI接口可以进行使用,其他部分是GPIO矩阵映射的,所以我们选择比较集中的这边GPIO进行配置。
SPI_SS
:GPIO14;
SPI_SCLK
:GPIO3;
SPI_MOSI
:GPIO17;
SPI_MISO
:GPIO12;
W25Q64硬件原理图:
W25Q64与WB2连接:
- GPIO14(SPI_SS) – CS
- GPIO3(SPI_SCLK) – CLK
- GPIO17(SPI_MOSI) – DO
- GPIO(SPI_MISO) – DI
实物连接图:
4.1、代码详解
4.1.1、文件结构
4.1.2、W25Q64部分
通过查看W25Q64的数据手册,使用 0x9F
指令能够读取到模块的ID。
能够读取到的ID值 0xEF4017
:
4.1.3 代码部分
- 配置WB2上的SPI,设置能与W25Q64模块通信的时钟极性和时钟相位,确保能够启动并与模块正常通信;
static hosal_spi_dev_t flash_w25q64 = {
.config = {
.dma_enable = 0, //disable dma
.freq = 2000000, //speed:2M
.mode = HOSAL_SPI_MODE_MASTER, //spi主设备
.pin_clk = SPI_FLASH_SCLK, //SCLK Pin 设置
.pin_miso = SPI_FLAHS_MISO, //MISO Pin 设置
.pin_mosi = SPI_FLASH_MOSI, // MOSI Pin 设置
.polar_phase = 0, //spi mode0
},
.cb = NULL,
.port = 0,
};
- 初始化SPI和片选CS;
void bsp_w25q64_init(void)
{
bl_gpio_enable_output(SPI_FLAHS_CS_PIN, 0, 0); //配置片选CS Pin
bl_gpio_output_set(SPI_FLAHS_CS_PIN, 1); //将CS Pin拉高
hosal_spi_init(&flash_w25q64); //初始化SPI
}
- 利用SPI与Flash模块通信
static int bsp_spi_flash_write(uint8_t *data, uint32_t size)
{
int ret;
ret = hosal_spi_send(&flash_w25q64, data, size, W25QX_TIMEOUT);
return ret;
}
static int bsp_spi_flash_read(uint8_t *data, uint32_t size)
{
int ret;
ret = hosal_spi_recv(&flash_w25q64, data, size, W25QX_TIMEOUT);
return ret;
}
uint32_t bsp_w25q64_read_id(void)
{
uint8_t cmd = W25X_JEDEC_DEVICE_ID;
uint8_t id[3] = {0};
uint32_t device_id;
CS_0; //CS拉低选中模块
bsp_spi_flash_write(&cmd, 1); //发送cmd
bsp_spi_flash_read(id, 3); //接收id数据
CS_1; //模块拉高,禁用模块
device_id = (id[0] << 16) | (id[1] << 8) | id[2];
return device_id;
}
- 根据读取到的信息进行模块数据串口打印;
void bsp_flash_set_info(void)
{
uint32_t id = bsp_w25q64_read_id();
flash_dev.device_id = id;
switch (id)
{
case W25Q64_ID:
strcpy(flash_dev.device_name, "W25Q64");
flash_dev.total_size = 8; /* 总容量 = 8M */
flash_dev.sector_size = 4 * 1024; /* 页面大小 = 4K */
break;
default:
strcpy(flash_dev.device_name, "Unknow Flash");
flash_dev.total_size = 2;
flash_dev.sector_size = 4 * 1024;
break;
}
printf("Chip Name:%s, Chip ID:0x%08X\r\n", flash_dev.device_name, flash_dev.device_id);
printf("Chip total size:%d M, Chip Sector Size:%d\r\n", flash_dev.total_size, flash_dev.sector_size);
}
4.2、实验现象
通过烧录,在串口调试助手上能够看到模块的ID。
附录
#ifndef DEMO_SPI_FLASH_H
#define DEMO_SPI_FLASH_H
#include <hosal_spi.h>
#include <bl_gpio.h>
#define SPI_FLAHS_CS_PIN 14
#define SPI_FLASH_SCLK 3
#define SPI_FLASH_MOSI 17 //D0
#define SPI_FLAHS_MISO 12 //D1
#define CS_0 bl_gpio_output_set(SPI_FLAHS_CS_PIN, 0)
#define CS_1 bl_gpio_output_set(SPI_FLAHS_CS_PIN, 1)
#define W25X_WRITE_ENABLE 0x06
#define W25X_WRITE_DISABLE 0x04
#define W25X_READ_STATUS_REG 0x05
#define W25X_WRITE_STATUS_REG 0x01
#define W25X_READ_DATA 0x03
#define W25X_FAST_READ_DATA 0x0B
#define W25X_FAST_READ_DUAL 0x3B
#define W25X_PAGE_PROGRAM 0x02
#define W25X_BLOCK_ERASE 0xD8
#define W25X_SECTOR_ERASE 0x20
#define W25X_CHIP_ERASE 0xC7
#define W25X_POWER_DOWN 0xB9
#define W25X_DEVICE_ID 0xAB
#define W25X_MANBUFAT_DEVICE_ID 0x90
#define W25X_JEDEC_DEVICE_ID 0x9F
#define WIP_FLAG 0x01 /* Write In Progress (WIP) flag */
#define Dummy_Byte 0xFF
#define W25QX_TIMEOUT 100
enum
{
W25Q64_ID = 0xEF4017U,
};
typedef struct
{
char device_name[20];
uint32_t device_id;
uint32_t total_size;
uint32_t sector_size;
}flash_info_t;
void bsp_w25q64_init(void);
uint32_t bsp_w25q64_read_id(void);
void bsp_flash_set_info(void);
#endif /* endif DEMO_SPI_FLASH_H */
```
#include "demo_spi_flash.h"
#include <string.h>
#include <stdio.h>
#include <hosal_spi.h>
#include <bl_gpio.h>
static hosal_spi_dev_t flash_w25q64 = {
.config = {
.dma_enable = 0,
.freq = 2000000,
.mode = HOSAL_SPI_MODE_MASTER,
.pin_clk = SPI_FLASH_SCLK,
.pin_miso = SPI_FLAHS_MISO,
.pin_mosi = SPI_FLASH_MOSI,
.polar_phase = 0,
},
.cb = NULL,
.port = 0,
};
static int bsp_spi_flash_write(uint8_t *data, uint32_t size)
{
int ret;
ret = hosal_spi_send(&flash_w25q64, data, size, W25QX_TIMEOUT);
return ret;
}
static int bsp_spi_flash_read(uint8_t *data, uint32_t size)
{
int ret;
ret = hosal_spi_recv(&flash_w25q64, data, size, W25QX_TIMEOUT);
return ret;
}
flash_info_t flash_dev;
uint32_t bsp_w25q64_read_id(void)
{
uint8_t cmd = W25X_JEDEC_DEVICE_ID;
uint8_t id[3] = {0};
uint32_t device_id;
CS_0;
bsp_spi_flash_write(&cmd, 1);
bsp_spi_flash_read(id, 3);
CS_1;
device_id = (id[0] << 16) | (id[1] << 8) | id[2];
return device_id;
}
void bsp_w25q64_init(void)
{
bl_gpio_enable_output(SPI_FLAHS_CS_PIN, 0, 0);
bl_gpio_output_set(SPI_FLAHS_CS_PIN, 1);
hosal_spi_init(&flash_w25q64);
}
void bsp_flash_set_info(void)
{
uint32_t id = bsp_w25q64_read_id();
flash_dev.device_id = id;
switch (id)
{
case W25Q64_ID:
strcpy(flash_dev.device_name, "W25Q64");
flash_dev.total_size = 8; /* 总容量 = 8M */
flash_dev.sector_size = 4 * 1024; /* 页面大小 = 4K */
break;
default:
strcpy(flash_dev.device_name, "Unknow Flash");
flash_dev.total_size = 2;
flash_dev.sector_size = 4 * 1024;
break;
}
printf("Chip Name:%s, Chip ID:0x%08X\r\n", flash_dev.device_name, flash_dev.device_id);
printf("Chip total size:%d M, Chip Sector Size:%d\r\n", flash_dev.total_size, flash_dev.sector_size);
}
#include <stdio.h>
#include <hosal_spi.h>
#include <bl_gpio.h>
#include "demo_spi_flash.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
void demo_flash(void)
{
bsp_w25q64_init();
bsp_flash_set_info();
}
uint8_t str[] = {"init done\r\n"};
int main(void)
{
demo_flash();
return 0;
}
参考资料
下一篇
【Ai-WB2高级篇】+ Flash编程原理 - Ai-WB2系列 - 物联网开发者社区-安信可论坛 - Powered by Discuz! (ai-thinker.com)