欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32F1笔记(十三)SPI

程序员文章站 2022-06-11 18:49:19
...

SPI:Serial Peripheral interface,串行外围设备接口。

SPI接口一般使用4条线通信:

MISO主设备数据输入,从设备数据输出。

MOSI主设备数据输出,从设备数据输入。

SCLK时钟信号,由主设备产生。

STM32F1笔记(十三)SPI

从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输,这个空字节通常称为dummy_byte。

STM32F1笔记(十三)SPI

SPI总线四种工作方式是为了和不同外设进行数据交换,其输出串行同步时钟极性和相位可以进行配置。

时钟极性CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。

时钟相位CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

SPI主从设备的时钟相位和极性应该一致。

STM32F1笔记(十三)SPI

STM32F1笔记(十三)SPI

 

STM32F1的SPI时钟最多可以到18Mhz。可用DMA。

SPI配置示例

void SPI2_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef SPI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);	
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_SetBits(GPIOB, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI2, &SPI_InitStructure);
 
    SPI_Cmd (SPI2, ENABLE);
	
    SPI2_ReadWriteByte(0xff); 
}  

SPI_Direction:选择半双工、全双工。

SPI_Mode:选择主机模式、从机模式。

SPI_DataSize:选择8位还是16位帧格式传输。

SPI_CPOL:设置时钟极性为高电平还是低电平。

SPI_CPHA:设置时钟相位,选择在时钟的第几个跳变沿数据被采样。

SPI_NSS:设置NSS信号由硬件(NSS管脚)还是软件控制。

SPI_BaudRatePrescaler:设置SPI波特率预分频值。示例选择256即本次SPI传输速度=36M/256=140.625KHz。

SPI_FirstBit:设置数据传输顺序是MSB位在前还是LSB位在前。大部分应用是MSB在前。

SPI_CRCPolynomial:设置CRC校验多项式,提高通信可靠性,大于1即可。

 

标准库里SPI发送一个字节函数为

SPI_I2S_SendData(SPI2, data);

接受一个字节函数为

data = SPI_I2S_ReceiveData(SPI2);

因此需要我们再封装几个函数,SPI写任意字节,读任意字节,读写任意字节。

 

SPI最常见的应用场景是读写FLASH。

正点原子战舰板子上的FLASH为W25Q128。该款FLASH的容量为128Mb也就是16M字节。将16M的容量分为256个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每个扇区4K字节。W25Q128最小擦除单位为一个扇区,也就是每次必须擦除4K字节。这样就需要开辟一个至少4K的缓存区,即单片机需要有4K以上的SRAM才能很好操作。W25Q128最大SPI时钟能达80Mhz。

STM32F1笔记(十三)SPI

 

FLASH拿到手通常先测试能否到它的ID

从W25QXX datasheet  里看到,主机给它发0x90,0x00,0x00,0x00,即可读取16位ID

STM32F1笔记(十三)SPI

u16 W25QXX_ReadID(void)
{
    u16 Temp = 0;

    W25QXX_CS = 0;

    SPI2_ReadWriteByte(0x90);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);

    Temp |= SPI2_ReadWriteByte(0xFF) << 8;
    Temp |= SPI2_ReadWriteByte(0xFF);

    W25QXX_CS = 1;

    return Temp;
} 

这里要注意读ID时MOSI发送的是0xFF,这个就是DUMMY_BYTE,大多DUMMY_BYTE是0xFF,少部分设备会有特殊要求

FLASH读取数据,先发0x03和读取数据的地址起点,然后即可读取。

STM32F1笔记(十三)SPI

代码如下

void W25QXX_Read(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)   
{ 
    u16 i;   			
							    
    W25QXX_CS = 0;

    SPI2_ReadWriteByte(0x03);
    SPI2_ReadWriteByte((u8) ((ReadAddr) >> 16));
    SPI2_ReadWriteByte((u8) ((ReadAddr) >> 8)); 
    SPI2_ReadWriteByte((u8) ReadAddr);

    for(i = 0; i < NumByteToRead; i++)
    { 
        pBuffer[i] = SPI2_ReadWriteByte(0XFF);
    }

    W25QXX_CS = 1;  				    	      
} 

 

W25Q128写使能,要给flash写数据,必须先写使能。

从W25QXX datasheet  里看到,只要发0x06即可写使能。

STM32F1笔记(十三)SPI

void W25QXX_Write_Enable(void)   
{
    W25QXX_CS=0;
    SPI2_ReadWriteByte(0x06);
    W25QXX_CS=1;  	      
} 

 

写使能后还不能立即往FLASH里面写数据,需要将写入数据的FLASH地址擦除。

STM32F1笔记(十三)SPI

void W25QXX_Erase_Sector(u32 Dst_Addr)   
{  	  
    Dst_Addr *= 4096;

    W25QXX_Write_Enable();

    W25QXX_Wait_Busy();

    W25QXX_CS = 0;

    SPI2_ReadWriteByte(0x20);
    SPI2_ReadWriteByte((u8) ((Dst_Addr) >> 16)); 
    SPI2_ReadWriteByte((u8) ((Dst_Addr) >> 8));   
    SPI2_ReadWriteByte((u8) Dst_Addr);

    W25QXX_CS = 1;

    W25QXX_Wait_Busy();
} 

 

FLASH写数据需要考虑跨扇区写入的问题,因为FLASH只提供了往一个页page写数据的命令0x02。

STM32F1笔记(十三)SPI

void W25QXX_Write_Page(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
    u16 i;

    W25QXX_Write_Enable();

    W25QXX_CS = 0;

    SPI2_ReadWriteByte(0x02);

    SPI2_ReadWriteByte((u8) ((WriteAddr) >> 16));
    SPI2_ReadWriteByte((u8) ((WriteAddr) >> 8));
    SPI2_ReadWriteByte((u8) WriteAddr);

    for(i = 0; i < NumByteToWrite; i++)
        SPI2_ReadWriteByte(pBuffer[i]);

    W25QXX_CS = 1;

    W25QXX_Wait_Busy();
} 

void W25QXX_Write_NoCheck(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)   
{ 			 		 
    u16 pageremain;

    pageremain = 256 - WriteAddr % 256;
    if(NumByteToWrite <= pageremain)
        pageremain = NumByteToWrite;

    while(1)
    {
        W25QXX_Write_Page(pBuffer , WriteAddr, pageremain);
        if(NumByteToWrite == pageremain)
            break;
        else
        {
            pBuffer += pageremain;
            WriteAddr += pageremain;	

            NumByteToWrite -= pageremain;
            if(NumByteToWrite > 256)
                pageremain = 256;
            else
                pageremain = NumByteToWrite;
        }
    };	    
} 
 
u8 W25QXX_BUFFER[4096];

void W25QXX_Write(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)   
{
    u32 secpos;
    u16 secoff;
    u16 secremain;
    u16 i;
    u8 * W25QXX_BUF;

    W25QXX_BUF = W25QXX_BUFFER;	     
    secpos = WriteAddr / 4096;  
    secoff = WriteAddr % 4096;
    secremain = 4096 - secoff;  

 	if(NumByteToWrite <= secremain)
        secremain = NumByteToWrite;
	while(1) 
	{	
        W25QXX_Read(W25QXX_BUF, secpos * 4096, 4096);

        for(i = 0; i < secremain; i++)
        {
            if(W25QXX_BUF[secoff + i] != 0XFF)
                break;
        }
        
        if(i < secremain)
        {
            W25QXX_Erase_Sector(secpos);

            for(i = 0; i < secremain; i++)
            {
                W25QXX_BUF[i + secoff] = pBuffer[i];	  
            }

            W25QXX_Write_NoCheck(W25QXX_BUF, secpos * 4096, 4096);

        }
        else
            W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain);

        if(NumByteToWrite == secremain)
            break;
        else
        {
            secpos++;
            secoff = 0;

            pBuffer += secremain;
            WriteAddr += secremain;
            NumByteToWrite -= secremain;

            if(NumByteToWrite > 4096)
                secremain = 4096;
            else
                secremain = NumByteToWrite;
        }	 
    }
}