【Ai-WB2中级篇】Ai-WB2+SPI

[复制链接]
查看2494 | 回复8 | 2024-9-5 22:47:28 | 显示全部楼层 |阅读模式

本帖最后由 浅末哈哈 于 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、电气连线

image.png

由四根线进行连接:

  • CS:SPI设备片选;
  • SCLK:SPI时钟线;
  • MOSI:主机输出从机输入(Master Output Slaver Input)
  • MISO:主机输入从机输出(Master Input Slaver Output)

针对多个从机进行SPI通信的电气连接:

image.png

利用片选CS脚进行选择不同的SPI设备。

2.2、SPI通信部分

2.2.1、SPI中的时钟相位(CPHA)和时钟极性(CPOL)

image.png

因为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,时钟空闲处于高电平,在时钟上升沿进行采样,下降沿进行数据变化。
  1. Mode0,CPOL=0,CPHA=0

image.png

  1. Mode1,CPOL=0,CPHA=1

image.png

  1. Mode2,CPOL=1,CPHA=0

image.png

  1. Mode3,CPOL=1,CPHA=1

image.png

2.2.2、SPI通信

SPI数据通信图解

SPI Working - Data Transfer

由主机产生时钟,当主机产生了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

image.png

因为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

  • 初始化硬件SPI
/**
  * @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);
  • 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);
  • SPI接收数据
/**
  * @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);
  • SPI发送接收数据
/**
  * @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是属于全双工模式,所以能够在发送的同时进行接收。

  • 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);
  • SPI设置片选CS脚
/**
  * @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);
  • 失能SPI
/**
  * @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。

实验连线图:

image.png

因为模组只有一个SPI接口可以进行使用,其他部分是GPIO矩阵映射的,所以我们选择比较集中的这边GPIO进行配置。

  • SPI_SS:GPIO14;
  • SPI_SCLK:GPIO3;
  • SPI_MOSI:GPIO17;
  • SPI_MISO:GPIO12;

W25Q64硬件原理图:

image.png

W25Q64与WB2连接:

  • GPIO14(SPI_SS) – CS
  • GPIO3(SPI_SCLK) – CLK
  • GPIO17(SPI_MOSI) – DO
  • GPIO(SPI_MISO) – DI

实物连接图:

image.png

4.1、代码详解

4.1.1、文件结构

image.png

4.1.2、W25Q64部分

通过查看W25Q64的数据手册,使用 0x9F指令能够读取到模块的ID。 image.png

能够读取到的ID值 0xEF4017image.png

4.1.3 代码部分

  1. 配置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,
 };
  1. 初始化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
 }
  1. 利用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;
 }
  1. 根据读取到的信息进行模块数据串口打印;
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。 image.png

附录

  • demo_spi_flash.h
#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 */

```
  • demo_spi_flash.c
#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);
}
  • main.c
#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)

本帖被以下淘专辑推荐:

回复

使用道具 举报

爱笑 | 2024-9-6 08:44:42 | 显示全部楼层
写的真不错!
用心做好保姆工作
回复 支持 反对

使用道具 举报

bzhou830 | 2024-9-6 08:46:21 | 显示全部楼层
写的真不错!
选择去发光,而不是被照亮
回复 支持 反对

使用道具 举报

大猫的鱼 | 2024-9-6 13:56:25 | 显示全部楼层
不错不错
回复

使用道具 举报

小小鸟 | 2024-10-30 08:49:09 | 显示全部楼层
真详细
回复

使用道具 举报

lazy | 2024-10-31 18:27:43 | 显示全部楼层
本帖最后由 lazy 于 2024-10-31 18:30 编辑

大佬工程能发下吗,我的spi初始化总是卡死不往后执行。
不知道哪里配置的有问题
回复 支持 反对

使用道具 举报

浅末哈哈 | 2024-11-1 08:35:46 | 显示全部楼层
lazy 发表于 2024-10-31 18:27
大佬工程能发下吗,我的spi初始化总是卡死不往后执行。
不知道哪里配置的有问题
...

我的这篇https://bbs.ai-thinker.com/forum ... amp;extra=#pid87306文章结尾有git的地址,你可以下载看一下,我看了你的问题,至少初始化spi部分是没有错的,就是你的创建的task中是用si4432_init作为一个线程,少了一个while(1)和delay函数,这个线程优先级设置的是最高的,其他任务需要执行就需要增加这一部分
回复 支持 反对

使用道具 举报

lazy | 2024-11-1 08:38:55 | 显示全部楼层
浅末哈哈 发表于 2024-11-1 08:35
我的这篇https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45253&page=1&extra=#pid87306文章结 ...

感谢,其实while原来是有的,后来发现没效果就去掉了。
回复 支持 反对

使用道具 举报

ALiaodddddd | 2024-11-3 13:41:25 | 显示全部楼层
厉害呀
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则