第六章、SPI总线接口设备及驱动

第一节、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);
    }
    
}

 

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值