第9章 SPI介绍及基础使用

第九章 SPI介绍及基础使用

1. F28P550的硬件I2C

TMS320F28P550中的串行外设接口 (SPI) 外设支持以下主要特性:

  • SPIPOCI:SPI 外设输出/控制器输入引脚
  • SPIPICO:SPI 外设输入/控制器输出引脚
  • SPIPTE:SPI 外设发送使能引脚
  • SPICLK:SPI 串行时钟引脚
  • 两种工作模式:控制器和外设
  • 波特率:125 个不同的可编程速率。可采用的最大波特率受限于 SPI 引脚上使用的 I/O 缓冲器的最大速度。
  • 数据字长度:1 至 16 数据位
  • 四种时钟方案(由时钟极性和时钟相位的位控制)包含:
    • 无相位延迟的下降沿:SPICLK 高电平有效。SPI 在 SPICLK 信号的下降沿上发送数据,在 SPICLK 信号的 上升沿上接收数据。
    • 有相位延迟的下降沿:SPICLK 高电平有效。SPI 在 SPICLK 信号下降沿提前半个周期发送数据,在 SPICLK 信号的下降沿上接收数据。
    • 无相位延迟的上升沿:SPICLK 低电平无效。SPI 在 SPICLK 信号的上升沿上发送数据,在 SPICLK 信号的 下降沿上接收数据。
    • 有相位延迟的上升沿:SPICLK 低电平无效。SPI 在 SPICLK 信号上升沿的半个周期之前发送数据,而在 SPICLK 信号的上升沿上接收数据。
  • 同时接收和发送操作(可在软件中禁用发送功能)
  • 发送器和接收器操作通过中断驱动或轮询算法完成
  • 16 级发送/接收 FIFO
  • DMA 支持
  • 高速模式
  • 延迟的发送控制
  • 3 线 SPI 模式
  • 在带有两个 SPI 模块的器件上实现数字音频接口接收模式的 SPIPTE 反转

使用硬件SPI的优势:

支持中断和DMA: 硬件SPI可以与中断控制器和DMA控制器配合使用,实现数据的高效处理和传输。

硬件缓冲区: 硬件SPI具有内部缓冲区,可以在主机和外设之间进行数据中转,提高数据的传输效率。

高速传输: 硬件SPI使用硬件模块进行数据传输,速度通常比软件实现的SPI更快。

2. W25Q32介绍

W25Q32是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q32芯片容量为32 Mbit(4 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,比如W25Q16、W25Q64、W25Q128等。通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。 W25Q32闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB。

W25Q32存储芯片,其引脚的说明,见下表。

CLK 从外部获取时间,为输入输出功能提供时钟
DI 标准SPI使用单向的DI,来串行的写入指令,地址,或者数据到FLASH中,在时钟的上升沿。
DO 标准SPI使用单向的DO,来从处于下降边沿时钟的设备,读取数据或者状态。
WP 防止状态寄存器被写入
HOLD 当它有效时允许设备暂停,低电平:DO引脚高阻态,DI CLK引脚的信号被忽略。高电平:设备重新开始,当多个设备共享相同的SPI信 号的时候该功能可能会被用到
CS CS高电平的时候其他引脚成高阻态,处于低电平的时候,可以读写数据

它与开发板的连接如下:

开发板(主机) W25Q32(从机) 说明
GPIO0 CS(NSS) 片选线
GPIO3 CLK 时钟线
GPIO1 DO(IO1)(MISO) 主机输入从机输出线
GPIO2 DI(IO0)(MOSI) 主机输出从机输入线
GND GND 电源线
VCC 3V3 电源线

发板上默认已经为大家贴好了该存储芯片,大家只需要了解连接的是哪一个引脚即可。

需要注意的是,我们使用的是硬件SPI方式驱动W25Q32,因此我们需要确定我们设置的引脚是否有硬件SPI外设接口。在数据手册中,GPIO0 ~ GPIO3 可以复用为SPIA的4根通信线。

这里需要注意的是PICO,表示的是外设输入控制器输出,即对应SPI中的MOSI。POCI表示外设输出控制器输入,即对应MISO。

3. 软件设计

3.1 SPI配置

打开工程下的 .syscfg 文件。找到 SPI 选项开始配置:

  • 在SPI外设的配置中,重新命名叫 FLASH_SPI;

  • 设置传输协议为 mode 0;

  • 设置SPI模式为主机模式,即控制器模式;

  • 设置通信速率为 1MHz;

  • 设置参数数据位长度为 8位;

  • 设置引脚配置自定义,关闭 PTE 的配置,然后设置各引脚。

这里需要注意,由于大多数的SPI协议中,整个时序里在发送接收时片选是要一直拉低的,而SPI外设的片选在每次发送和接收完一帧后会拉高,所以CS片选线需要用MCU的IO口独立控制,没有办法使用SPI外设的CS管脚。这里使用GPIO的方式(软件方式)去控制CS引脚的输出。

新建一个GPIO。命名为FLASH_CS,引脚选择我们现在接入模块CS的引脚GPIO0。其配置如下:

3.2 W25Q32驱动

#ifndef _BSP_W25QXX_H__
#define _BSP_W25QXX_H__

#include "board.h"

//CS引脚的输出控制
//x=0时输出低电平
//x=1时输出高电平
#define SPI_CS(x)  ( (x) ? GPIO_writePin(FLASH_CS,1) : GPIO_writePin(FLASH_CS,0) )

uint8_t spi_read_write_byte(uint8_t dat);//SPI读写一个字节
uint16_t w25qxx_read_id(void);//读取W25QXX的ID
void w25qxx_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);      //W25QXX写数据
void w25qxx_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length);//W25QXX读数据

#endif

#include "bsp_w25qxx.h"
#include "spi.h"

uint8_t spi_read_write_byte(uint8_t dat)
{
        uint8_t data = 0;

        // Transmit data
        SPI_writeDataNonBlocking(FLASH_SPI_BASE, dat<<8);

        // Block until data is received and then return it
        data = SPI_readDataBlockingNonFIFO(FLASH_SPI_BASE);

        return data;
}

//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//读取设备ID
uint16_t w25qxx_read_id(void)
{
    uint16_t  temp = 0;
    //将CS端拉低为低电平
    SPI_CS(0);
    //发送指令90h
    spi_read_write_byte(0x90);//发送读取ID命令
    //发送地址  000000H
    spi_read_write_byte(0x00);
    spi_read_write_byte(0x00);
    spi_read_write_byte(0x00);

    //接收数据
    //接收制造商ID
    temp |= spi_read_write_byte(0xFF)<<8;
    //接收设备ID
    temp |= spi_read_write_byte(0xFF);
    //恢复CS端为高电平
    SPI_CS(1);
    //返回ID
    return temp;
}

//发送写使能
void w25qxx_write_enable(void)
{
    //拉低CS端为低电平
    SPI_CS(0);
    //发送指令06h
    spi_read_write_byte(0x06);
    //拉高CS端为高电平
    SPI_CS(1);
}
//器件忙判断
void w25qxx_wait_busy(void)
{
    unsigned char byte = 0;
    do
     {
        //拉低CS端为低电平
        SPI_CS(0);
        //发送指令05h
        spi_read_write_byte(0x05);
        //接收状态寄存器值
        byte = spi_read_write_byte(0Xff);
        //恢复CS端为高电平
        SPI_CS(1);
     //判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
     }while( ( byte & 0x01 ) == 1 );
}

void w25qxx_erase_sector(uint32_t addr)
{
        //计算扇区号,一个扇区4KB=4096
        addr *= 4096;
        w25qxx_write_enable();  //写使能
        w25qxx_wait_busy();     //判断忙,如果忙则一直等待
        //拉低CS端为低电平
        SPI_CS(0);
        //发送指令20h
        spi_read_write_byte(0x20);
        //发送24位扇区地址的高8位
        spi_read_write_byte((uint8_t)((addr)>>16));
        //发送24位扇区地址的中8位
        spi_read_write_byte((uint8_t)((addr)>>8));
        //发送24位扇区地址的低8位
        spi_read_write_byte((uint8_t)addr);
        //恢复CS端为高电平
        SPI_CS(1);
        //等待擦除完成
        w25qxx_wait_busy();
}

void w25qxx_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
    unsigned int i = 0;
    //擦除扇区数据
    w25qxx_erase_sector(addr/4096);
    //写使能
    w25qxx_write_enable();
    //忙检测
    w25qxx_wait_busy();
    //写入数据
    //拉低CS端为低电平
    SPI_CS(0);
    //发送指令02h
    spi_read_write_byte(0x02);
    //发送写入的24位地址中的高8位
    spi_read_write_byte((uint8_t)((addr)>>16));
    //发送写入的24位地址中的中8位
    spi_read_write_byte((uint8_t)((addr)>>8));
    //发送写入的24位地址中的低8位
    spi_read_write_byte((uint8_t)addr);
    //根据写入的字节长度连续写入数据buffer
    for(i=0;i<numbyte;i++)
    {
        spi_read_write_byte(buffer[i]);
    }
    //恢复CS端为高电平
    SPI_CS(1);
    //忙检测
    w25qxx_wait_busy();
}

void w25qxx_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
        uint16_t i;
        //拉低CS端为低电平
        SPI_CS(0);
        //发送指令03h
        spi_read_write_byte(0x03);
        //发送24位读取数据地址的高8位
        spi_read_write_byte((uint8_t)((read_addr)>>16));
        //发送24位读取数据地址的中8位
        spi_read_write_byte((uint8_t)((read_addr)>>8));
        //发送24位读取数据地址的低8位
        spi_read_write_byte((uint8_t)read_addr);
        //根据读取长度读取出地址保存到buffer中
        for(i=0;i<read_length;i++)
        {
            buffer[i]= spi_read_write_byte(0XFF);
        }
        //恢复CS端为高电平
        SPI_CS(1);
}

3.3 主函数测试

#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "c2000ware_libraries.h"
#include "bsp_w25qxx.h"
#include <stdio.h>
#include <string.h>

void main(void)
{

    Device_init();
    Device_initGPIO();
    Interrupt_initModule();
    Interrupt_initVectorTable();
    Board_init();
    C2000Ware_libraries_init();
    EINT;
    ERTM;

    int flash_id=0;
    char read_write_buff[10] = {0};
    char read_write_buff2[10] = {0};

    //读取器件ID
    flash_id = w25qxx_read_id();
    DEVICE_DELAY_US(500000);
    printf("flash_id=%X\r\n",flash_id);

    //往0地址写入5个字节数据,分别是"12345"
    w25qxx_write("12345", 0x00, 5);
    DEVICE_DELAY_US(500000);

    //读取0地址的5个字节数据到buff
    w25qxx_read(read_write_buff, 0x00, 5);
    //通过CIO输出
    printf("%s\r\n",read_write_buff);
    DEVICE_DELAY_US(500000);

    /* 字符串测试 */
    w25qxx_write("hello", 0x01, sizeof(read_write_buff2));
    DEVICE_DELAY_US(500000);
    w25qxx_read(read_write_buff2, 0x01, sizeof(read_write_buff2));
    printf("%s\r\n",read_write_buff2);
    DEVICE_DELAY_US(500000);

    while(1);
}

posted @ 2025-07-29 22:34  hazy1k  阅读(64)  评论(0)    收藏  举报