STM32F1笔记(十三)SPI

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

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

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

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

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

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

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

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

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

扫描二维码关注公众号,回复: 6084598 查看本文章

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。

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

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

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和读取数据的地址起点,然后即可读取。

代码如下

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即可写使能。

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

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

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。

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;
        }	 
    }
}

猜你喜欢

转载自blog.csdn.net/Dr_Haven/article/details/82689116