SPI协议

SPI协议,多用于ADC、DA、LCD等设备与MCU之间,要求通信速率要求较高的场合,它相比于I2C来说速度快的多。一般只需要4根线,分别是MISO、MOSI、SCK、CS线等,但是,有可能只用3根。对于SPI通信协议的详细描述我之前有博客已经有说明了,有需要可以了解一下。这里重点是使用软件模拟SPI协议,实验基于野火开发板指南者F103VET6、Flash芯片W25Q64、这里模拟的时序是SPI的模式3(CPOL=1,CPHA=1),原因有两点原因:1、模式3的SCK空闲电平为高,有高电平向低电平翻转较为容易和快,2、模式3在偶数边沿采样,防止第一个信号没采到。

首先,对于软件模拟SPI的GPIO初始化,可以参考SPI的GPIO初始化的配置,我们不用使用复用功能就行,使用普通推挽输出和浮空输入。

void SPI_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);
    /*SPI CS GPIO Confgi*/
    GPIO_InitStruct.GPIO_Mode    =    GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin    =    EEPROM_SPI_CS_PIN;
    GPIO_InitStruct.GPIO_Speed    =    GPIO_Speed_50MHz;
    GPIO_Init(EEPROM_SPI_CS_PORT,&GPIO_InitStruct);

    /*SPI SCK GPIO Config*/
    GPIO_InitStruct.GPIO_Mode    =    GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin    =    EEPROM_SPI_CLK_PIN;
    GPIO_InitStruct.GPIO_Speed    =    GPIO_Speed_50MHz;
    GPIO_Init(EEPROM_SPI_CLK_PORT,&GPIO_InitStruct);

    /*MISO GPIO Config*/
    GPIO_InitStruct.GPIO_Mode    =    GPIO_Mode_IN_FLOATING;
    GPIO_InitStruct.GPIO_Pin    =    EEPROM_SPI_MISO_PIN;
    GPIO_Init(EEPROM_SPI_MISO_PORT,&GPIO_InitStruct);

    /*MOSI GPIO Config*/
  GPIO_InitStruct.GPIO_Mode    =    GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin    =    EEPROM_SPI_MOSI_PIN;
    GPIO_InitStruct.GPIO_Speed    =    GPIO_Speed_50MHz;
    GPIO_Init(EEPROM_SPI_MOSI_PORT,&GPIO_InitStruct);

    EEPROM_SPI_CS_HIGH();
    EEPROM_SPI_CLK_HIGH();
}

接下来就是最为关键的发送和接收数据函数了,其中注意,使用SPI时,发送和接收其实是同一个函数,通过判断RXNE来确认发送结束,此时也接收完数据,接收数据同样要主机产生时序,时序通过主机发送数据产生,所以会发送无用的Dummy数据。软件模拟不需要,直接接收自己本身会产生时序,不用发送DUMMY数据。Delay函数随便设置,不要小于手册的时间即可。

软件模拟发送数据

void SPI_SendData(uint8_t data)
{
    uint8_t cnt;

    for(cnt=0;cnt<8;cnt++)
    {
        EEPROM_SPI_CLK_LOW();//拉低CLK
        SPI_Delay(10);//这个延时时间任意,但要大于芯片数据手册上的(纳秒级的)
        if(data &0x80)
        {
            EEPROM_SPI_MOSI_HIGH();
        }
        else
        {
            EEPROM_SPI_MOSI_LOW();
        }
        data <<= 1;
        //SPI_Delay(10);
        EEPROM_SPI_CLK_HIGH();//拉高CLK
        SPI_Delay(10);
    }

}

延时函数

static void SPI_Delay(__IO uint32_t count)
{
    uint32_t i;
    for(i=0;i<count;i++)
    {
        uint8_t uc =12;
        while(uc--);
    }

}

软件模拟接收数据

uint8_t SPI_ReadData(void)
{
    uint8_t i = 0;
    uint8_t value=0;
    for(i=0;i<8;i++)
    {
        EEPROM_SPI_CLK_LOW();
        SPI_Delay(10);
        value<<=1;
        if(EEPROM_SPI_MISO())
        {
            value |= 0x01;
        }
        EEPROM_SPI_CLK_HIGH();
        SPI_Delay(10);
    }
    return value;
}

等待擦除完成

void SPI_WaitErase(void)
{
    uint8_t status = 0x01;
    EEPROM_SPI_CS_LOW();
    SPI_SendData(0x05);
    do
    {
        status = SPI_ReadData();
    }
    while((status&0x01) == 1);
    EEPROM_SPI_CS_HIGH();
}

擦除扇区(Flash存储器的一个特征就是写入之前需要擦除,只能把1改为0,不能把0改写为1)

void SPI_EraseSector(uint32_t addr)
{
        EEPROM_SPI_CS_LOW();
        SPI_SendData(0x06);
        EEPROM_SPI_CS_HIGH();
        EEPROM_SPI_CS_LOW();
        SPI_SendData(0x20);
        SPI_SendData((uint8_t)(addr&0xFF0000)>>16);
        SPI_SendData((addr&0xFF00)>>8);
        SPI_SendData((addr&0xFF));
        EEPROM_SPI_CS_HIGH();
        SPI_WaitErase();
}

检验设备,和前面的程序是否正常,正常才继续后面的页写入和读取数据 ID=0XEF4017

uint32_t SPI_CheckDevice(void)
{
    uint8_t temp0,temp1,temp2;
    uint32_t Device_ID=0;
    EEPROM_SPI_CS_LOW();
    SPI_SendData(0x9F);
    temp0 = SPI_ReadData();
    temp1 = SPI_ReadData();
    temp2 = SPI_ReadData(); 
    EEPROM_SPI_CS_HIGH();
    Device_ID = (temp0<<16) | (temp1<<8) | (temp2);
    return Device_ID;
}

读取数据

void SPI_BufferRead(uint8_t *pBuffer,uint32_t addr,uint32_t numToRead)
{
    EEPROM_SPI_CS_LOW();
    SPI_SendData(0x03);
    SPI_SendData((uint8_t)(addr&0xFF0000)>>16);
    SPI_SendData((addr&0xFF00)>>8);
    SPI_SendData((addr&0xFF));
    while(numToRead--)
    {
        *pBuffer = SPI_ReadData();
        pBuffer++;
    }
    EEPROM_SPI_CS_HIGH();
}

页写入

void SPI_PageWrite(uint8_t *pBuffer,uint32_t addr,uint32_t numToWrite)
{
    EEPROM_SPI_CS_LOW();
    SPI_SendData(0x06);
    EEPROM_SPI_CS_HIGH();
    EEPROM_SPI_CS_LOW();
    SPI_SendData(0x02);
    SPI_SendData((addr&0xFF0000)>>16);
    SPI_SendData((addr&0xFF00)>>8);
    SPI_SendData((addr&0xFF));

    if(numToWrite>256)
    {
            printf("\r\n页写入最大为256字节\r\n");
            numToWrite = 256 ;
    }
    while(numToWrite--)
    {
        SPI_SendData(*pBuffer);
        pBuffer++;
    }
    EEPROM_SPI_CS_HIGH();
    SPI_WaitErase();
}

任意大小写入

void SPI_BufferWrite(uint8_t *pBuffer,uint32_t addr,uint32_t numToWrite)
{
    uint32_t count=0,numPage=0,numSingle=0,Addr=0;

    Addr = addr%Page_Size;
    count = Page_Size - Addr;
    numPage = numToWrite/Page_Size;
    numSingle = numToWrite%Page_Size;

    if(Addr == 0)
    {
        if(numPage==0)
        {
            SPI_PageWrite(pBuffer,addr,numToWrite);

        }
        else
        {
            while(numPage--)
            {
                SPI_PageWrite(pBuffer,addr,Page_Size);
                pBuffer+=Page_Size;
                addr+=Page_Size;
            }
            SPI_PageWrite(pBuffer,addr,numSingle);


        }
    }

    else
    {
        if(numPage==0)
        {
            if(numSingle>count)
            {
                SPI_PageWrite(pBuffer,addr,count);
                pBuffer+=count;
                addr+=count;
                SPI_PageWrite(pBuffer,addr,(numSingle-count));
            }
            else
            {
                SPI_PageWrite(pBuffer,addr,numToWrite);

            }
        }
        else
        {
            numToWrite -= count;
            numPage = numToWrite/Page_Size;
            numSingle    =    numToWrite%Page_Size;
            SPI_PageWrite(pBuffer,addr,count);
            addr+=count;
            pBuffer+=count;
            while(numPage--)
            {
                SPI_PageWrite(pBuffer,addr,Page_Size);
                pBuffer+=Page_Size;
                addr+=Page_Size;

            }
            if(numSingle!=0)
            {
                SPI_PageWrite(pBuffer,addr,numSingle);

            }


        }

    }
    SPI_WaitErase();
}

主函数和读写测试

#include "stm32f10x.h"
#include "bsp_usart.h"
#include "bsp_soft_spi.h"

uint8_t Write_pBuffer[]={"使用野火STM32开发板的软件模拟SPI总算成功了!"};
uint8_t Read_pBuffer[30]={0};
uint8_t pBuffer_Cmp(uint8_t *buffer1,uint8_t *buffer2,uint32_t num)
{
        while(num--)
        {
            if(*buffer1!=*buffer2)
            {
                return 0;
            }
            buffer1++;
            buffer2++;
        }
        return 1;

}

void SPI_Test(void)
{
    uint32_t Device_ID = 0;
    Device_ID = SPI_CheckDevice();
    printf("ID=0x%x\r\n",Device_ID);
    if(Device_ID == 0xEF4017)
    {
        printf("SPI FLASH设备正常!\r\n");
        SPI_EraseSector(0x001000);
        SPI_BufferWrite(Write_pBuffer,0x001000,(sizeof(Write_pBuffer)/sizeof(*(Write_pBuffer))-1));
        SPI_BufferRead(Read_pBuffer,0x001000,(sizeof(Write_pBuffer)/sizeof(*(Write_pBuffer))-1));
        if(pBuffer_Cmp(Write_pBuffer,Read_pBuffer,(sizeof(Write_pBuffer)/sizeof(*(Write_pBuffer))-1)) == 1)
        {    

            printf("软件模拟SPI的读写数据一致!\r\n");
            printf("\r\n 读出的数据为:%s \r\n", Read_pBuffer);

        }
        else
        {
            printf("软件模拟SPI的读写数据不一致!\r\n");
        }

    }
    else
    {
        printf("SPI FLASH设备异常!\r\n");
    }
}

int main(void)
{

    USART_Config();
    SPI_GPIO_Config();
  /* Output a message on Hyperterminal using printf function */
  printf("\n\r软件模拟SPI实验测试!\n\r");
    SPI_Test();
  while (1)
  {
  }
}