GPIO模拟SPI时序

前言:

因为工作需要用到SPI驱动Flash芯片,且SPI硬件接口无法使用,因此采用GPIO来模拟SPI和Flash建立连接。但因为之前没有接触过如何根据时序图来编写代码,下面详细展示我是如何根据芯片手册来编写代码驱动Flash芯片的。

硬件介绍:

处理芯片:AWR6843ARBGALPQ1
嵌入式平台:CCS
Flash芯片:MX25R1635F

GPIO模拟SPI:

SPI根据CPOL( Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)不同共分为四种工作模式,这导致时序会有一些差异,具体请查找对应的芯片。
MX25R1635F读ID时序

如图是MX25R1635F的相关介绍,其介绍到:

  1. MX25R1635F的支持Mode 0 和Mode 3,这表明CPOL可以自行设置,下文是按照CPOL为0进行设置(即时钟信号线在空闲状态是低电平)。
  2. MX25R1635F的数据变化是发生在时钟的下降沿,那么数据的采样则是在时钟的上升沿,结合CPOL为0和上升沿采样,那么可以得到CPHA为0(即时钟的奇数边沿采样,偶数边沿数据变化)
  3. SPI的启动是片选信号线CS拉低,结束则是CS拉高。
    根据上述信息就可以编写GPIO模拟SPI的驱动代码。具体如下:
    为了简化代码编写,在进行驱动代码之前先进行硬件引脚的定义及引脚控制函数编写,由于不同硬件和不同编程平台(底层驱动函数不一致),因此这边对基础的功能函数仅做介绍,从而方便阅读后续的驱动代码:
static void delay_us (unsigned char length);    //延时函数
static void GPIO_In_Out_Config(unsigned char gpioNum,unsigned char gpiotype);   //IO口输入输出设置
static void SPI_CS(unsigned char gpiovalue);    //配置片选信号线CS的高低电平
static void SPI_SCLK(unsigned char gpiovalue);  //配置时钟信号线SCLK的高低电平
static void SPI_SI(unsigned char gpiovalue);    //配置MOSI信号线的高低电平
static int32_t READ_SO(void);                   //读取MISO信号的电平状态
void init_qspiflash(void);                      //引脚配置,并设置CS、SCLK、SI为输出,SO为输入
#define DummyBytes              0xFF            //Dummy Bytes

SPI启动

根据芯片手册可知,SPI的启动是片选信号从高变成低,因此,启动代码具体如下:

//产生SPI起始信号
static void SPI_Start(void)
{
    SPI_CS(1);
    SPI_SCLK(0);     //由于我选择的是CPOL为0,因此时钟在空闲状态下是低电平
    delay_us(4);
    SPI_CS(0);       //片选信号线拉低,SPI启动
    delay_us(4);
}

SPI关闭

根据芯片手册可知,SPI的关闭是片选信号从低变高,因此,关闭代码具体如下:

//产生SPI起始信号
static void SPI_Stop(void)
{
    SPI_CS(0);
    SPI_SCLK(0);   //由于我选择的是CPOL为0,因此时钟在空闲状态下是低电平
    delay_us(4);
    SPI_CS(1);     //片选信号线拉高,SPI关闭
    delay_us(4);
}

SPI读字节

根据芯片手册,SPI的读字节是在上升沿采样,下降沿产生变化,那么可以将一个字节的读取时序图解释如下:

空闲状态时钟为低->延时->时钟拉高->数据读取1bit->延时->时钟拉低->延时->时钟拉高->数据读取1bit->延时->…->时钟拉低->延时->时钟拉高->数据读取1bit->延时->时钟拉低(读完8个bit后将时钟拉低)

根据上述的时序逻辑,SPI的读字节驱动代码具体如下:

//数据读取
uint8_t SPI_ReadByte(void) {         //SPI读1 Byte,循环8次,每次接收1 Bit;
    uint8_t i = 0;
    uint8_t read_data = 0xFF;
    for(i=0; i<8; i++) {
        read_data = read_data << 1;  //“腾空” read_data最低位,8次循环后,read_data将高位在前;
        SPI_SCLK(0);                  //拉低时钟,即空闲时钟为低电平;
        delay_us(2);
        SPI_SCLK(1);
        if(READ_SO())
        {
           read_data = read_data + 1;
        }
        delay_us(2);
    }
    SPI_SCLK(0);                      //最后SPI读取完后,拉低时钟,进入空闲状态
    return read_data;
}

SPI写字节

和读字节的逻辑基本一致,不过一个是在上升沿的时候改变SI的电平,一个是在上升沿的时候读取SO的电平。
具体的SPI写字节的驱动代码如下:

void SPI_WriteByte(uint8_t data)
{ 
    uint8_t i = 0;
    uint8_t temp = 0;
    for(i=0; i<8; i++) {
        temp = ((data&0x80)==0x80)? 1:0;   //将data最高位保存到temp;
        data = data<<1;                    //data左移一位,将次高位变为最高位,用于下次取最高位;
        SPI_SCLK(0); //CPOL=0              //拉低时钟,即空闲时钟为低电平, CPOL=0;
        SPI_SI(temp);                      //根据temp值,设置MOSI引脚的电平
        delay_us(2);                       //简单延时,可以定时器或延时函数实现
        SPI_SCLK(1); //CPHA=0              //拉高时钟,这样就设置成上升沿数据没有改变,实现在时钟上升沿采样,下降沿数据变化
        delay_us(2);
     }
     SPI_SCLK(0);                          //最后SPI发送完后,拉低时钟,进入空闲状态;
}

读设备ID:

基于上述的基本驱动函数,下面就是根据芯片手册详细的时序逻辑图完成对应的功能,例如MX25R1635F读ID的操作,其具体的时序逻辑就是:
SPI启动->发送读设备ID命令0x90->发送两个DummyBytes->发送读ID地址->读取ManufactureID->读取DeviceID->SPI停止
DummyBytes可以是任意值,关于DummyBytes的具体功能网上查到的是说为了减少中间过程的偏差。

int32_t SPI_Write_Sequence_Debug(uint8_t DebugCommand,uint8_t DebugCommandAddress)
{
    int32_t   retVal = 0;
    uint8_t   ManufactureID;
    uint8_t   DeviceID;
    SPI_Start();
    SPI_WriteByte(DebugCommand);
    SPI_WriteByte(DummyBytes);
    SPI_WriteByte(DummyBytes);
    SPI_WriteByte(DebugCommandAddress);
    DeviceID        =   SPI_ReadByte();
    ManufactureID   =   SPI_ReadByte();
    SPI_Stop();

    retVal++;
    return retVal;
}

SPI_Write_Sequence_Debug(0x90,0x01);   //主程序调用

最终的Debug测试结果如下:成功读取到两个ID分别是0x15,0xC2,具体结果如下:
在这里插入图片描述
至于该款Flash的其他功能也是一样,根据相应的功能时序图进行修改即可。当然如果是Dual Read或者是Quad Read模式则相应的驱动函数需要根据时序图进一步修改才能使用。

欢迎大家批评指正!

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值