STM32HAL库学习笔记八——SPI通信

HAL库快速部署SPI通信

本文主要记录如何使用STMCubeMX快速部署SPI通信。


文章目录

  • HAL库快速部署SPI通信
  • SPI简介
  • FLASH简介
  • HAL库配置读写串行FLASH
    • 一、CubeMX配置
    • 二、手写代码
      • 1.宏定义与全局变量
      • 2.读写函数
      • 3.等待函数
      • 4.写使能/失能
      • 5.扇区擦除
      • 6.页写入
      • 7.读函数
      • 8.读取厂商设备ID
      • 9.printf重定向
      • 10.测试函数
  • 总结


SPI简介

SPI是一种串行同步高速的全双工通信方式。

SPI的物理层一般由四条数据线组成:片选信号线CS/NSS,时钟信号线SCK,主写从读信号线MOSI,主读从写信号线MISO。
STM32HAL库学习笔记八——SPI通信_第1张图片

SPI的协议层主要包括起始信号、停止信号、数据有效性、通信模式四个部分。
与I2C通信不同,SPI的起始停止信号由片选信号线控制,而I2C由时钟线为高电平时数据线电平的变化来控制。
数据有效性部分,SPI在时钟信号电平变化时,即上升沿或者下降沿进行数据的触发(数据变化)或者采样(认定此时数据有效),前一个边沿触发,后一个边沿就进行采样,具体采样和触发谁先谁后要依据通信模式的配置;I2C相对要简单一些,在时钟线为高电平时数据有效,在低电平时进行数据电平变换。
对于通信模式,引入CPOL和CPHA的概念,CPOL和CPHA分别被称为时钟极性和时钟相位,CPOL为0表示SPI通信未开始时SCK时钟信号为低电平,为1则是高电平;CPHA为0表示数据采样时刻是SCK的奇数边沿,为1则是SCK的偶数边沿。
CPOL与CPHA两两组合形成4种模式,对于FLASH芯片支持模式0和模式3,即CPOL和CPHA都为0或者都为1的模式。这样一来,模式0就表示片选信号触发通信开始后,时钟线第一个上升沿进行数据采样,之后的下降沿数据触发,第二个上升沿再采样,依次类推。
STM32HAL库学习笔记八——SPI通信_第2张图片
而SPI没有应答信号的设置,也不需要在通信前先发送从机地址查找从机,用片选信号线就能解决。由于SPI是全双工通信,因此也不需要设置读写位。


FLASH简介

FLASH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U 盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而 EEPROM 可以单个字节擦写。

STM32f103VET6单片机使用的是型号为W25Q64的FLASH芯片,是一种使用 SPI 通讯协议的 NOR FLASH存储器(NOR Flash是一种非易失闪存技术,是Intel在1988年创建。是市场上两种主要的非易失闪存技术之一),存储容量64Mbit,即8MBytes。它的CS/CLK/DIO/DO 引 脚 分 别 连 接 到 了 STM32 对 应 的 SPI 引 脚NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚是一个普通的 GPIO,不是 SPI 的专用NSS 引脚,所以程序中我们要使用软件控制片选信号,对于STM32f103VET6单片机片选信号由PC0引脚控制。
STM32HAL库学习笔记八——SPI通信_第3张图片
FLASH芯片在接收到主机发来的信号时会解析信号内容,根据指令表执行不同的命令。
本次实验用到的W25Q64指令表如下(详细的可以查阅W25Q64芯片手册获取相关信息):

指令 指令编码 指令解释
写使能 06h 开启FLASH芯片的写入
写禁止 04h 禁止FLASH芯片的写入
读状态寄存器 05h 判断FLASH读取工作的状态
写状态寄存器 01h 判断FLASH写入工作的状态
03h 开始读取FLASH中数据
页写入 02h 一次最多写入256个字节
扇区擦除 20h 擦除一个扇区的数据(4KB)
厂商设备信息 09h 获取厂商和设备编号

HAL库配置读写串行FLASH

一、CubeMX配置

1.新建工程,常规配置调试模式、时钟树、项目环境;
2.选择PB0、PB5、PC0配置为GPIO_Out,初始为高电平,PC0为片选信号;
3.选择SPI1,配置模式为全双工主模式,硬件片选关闭,软件片选打开,选择数据大小,MSB/LSB模式。
值得注意的是,分频系数选择4,虽然SPI1的最大波特率能取到36MBits/s,但这里是被限制了。

STM32HAL库学习笔记八——SPI通信_第4张图片
4.配置好USART1用于串口调试。
5.GENERATE CODE

二、手写代码

1.宏定义与全局变量

#define CS_enable HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_RESET)		//片选信号使能
#define CS_disable HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_SET)		//片选信号失能

/***FLASH指令集***/
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_PageProgram 0x02
#define W25X_SectorErase 0x20
#define W25X_ManufactDeviceID 0x90

uint8_t devid[2];
uint8_t sendbuf[8] = {1,36,4,7,8,9,41,2};
uint8_t revbuff[8];

2.读写函数

HAL库有自带函数,我们对它们进行封装方便后续使用。

写函数

HAL_StatusTypeDef SPI_Write(uint8_t* pbuff, uint16_t size)
{
    return HAL_SPI_Transmit(&hspi1, pbuff, size, 100);
}

读函数

HAL_StatusTypeDef SPI_Read(uint8_t* pbuff, uint16_t size)
{
   return HAL_SPI_Receive(&hspi1, pbuff, size, 100);
}

同时读写函数:注意这里的size是读写字节数总和,SPI读写可以同时进行,依据时序进行,因此size是时序中读写字节数的总和。

HAL_StatusTypeDef SPI_WriteAndRead(uint8_t *pwritebuff,uint8_t* preceivebuff,uint8_t size)
{
	
	return HAL_SPI_TransmitReceive(&hspi1,pwritebuff,preceivebuff,size,100);
	
}

3.等待函数

FLASH的读写速度有限,如果读写未完成是不会接收MCU的指令的,而读写状态寄存器可以一直访问,因此读取状态寄存器的busy位可以获取FLASH当前的读写状态。
下图分别为FLASH的状态寄存器结构和时序图:
STM32HAL库学习笔记八——SPI通信_第5张图片
STM32HAL库学习笔记八——SPI通信_第6张图片

uint8_t FLASH_WriteStatus(void)
{
	uint8_t cmd[4] = {W25X_WriteStatusReg,0x00,0x00,0x00};
	uint8_t busy;
	CS_enable;
	SPI_Write(cmd,4);
	SPI_Read(&busy,1);
	CS_disable;
	return busy;
}

uint8_t FLASH_ReadStatus(void)
{
	uint8_t cmd[4] = {W25X_ReadStatusReg,0x00,0x00,0x00};
	uint8_t busy;
	CS_enable;
	SPI_Write(cmd,4);
	SPI_Read(&busy,1);
	CS_disable;
	return busy;
}

设计等待函数,判断FLASH是否处于BUSY状态。使用等待函数时,要不要等待就看不同指令在不在一个时序里,每个时序开始前和结束后都检查一下。等待函数的位置很重要,因为等待函数本身也会进行SPI通信,如果不当会导致片选信号线重复开启,提早关闭。

void Wait_WriteBusy(void)
{
	while((FLASH_WriteStatus()&0x01) == 0x01);
}

void Wait_ReadBusy(void)
{
	while((FLASH_ReadStatus()&0x01) == 0x01);
}

4.写使能/失能

FLASH写入是受保护的,需要输入指令允许写入。下图为写入使能的时序图,失能同理。

STM32HAL库学习笔记八——SPI通信_第7张图片

void FLASH_WriteEnable(void)
{
	uint8_t cmd = W25X_WriteEnable;
	Wait_WriteBusy();
	CS_enable;
	SPI_Write(&cmd,1);
	CS_disable;
	Wait_WriteBusy();
}

void FLASH_WriteDisable(void)
{
	uint8_t cmd = W25X_WriteDisable;
	Wait_WriteBusy();
	CS_enable;	
	SPI_Write(&cmd,1);
	CS_disable;
	Wait_WriteBusy();
}

5.扇区擦除

Flash的数据位可以由1变为0,但不能由0变为1。所以在向 Flash 写数据之前,必须要先进行擦除操作,把该扇区所有的数据变为 0xFF。擦除的最小单位是扇区。
扇区擦除的时序图如下:
STM32HAL库学习笔记八——SPI通信_第8张图片

void FLASH_SectorErase(uint32_t erase_add)
{
	uint8_t cmd[4];
	cmd[0] = W25X_SectorErase;
	cmd[1] = (uint8_t)(erase_add >> 16);
	cmd[2] = (uint8_t)(erase_add >> 8);
	cmd[3] = (uint8_t)(erase_add);
	FLASH_WriteEnable();
	Wait_WriteBusy();
	CS_enable;		
	SPI_Write(cmd,4);	
	CS_disable;
	Wait_WriteBusy();
}

6.页写入

页缓冲区大小为256B,因此页写入最多写入256B,开启写使能后发送页写入指令开始写入。
与I2C类似, 在进行部分写的时候如果超过当前页的地址范围,就会回到该页开头覆盖写。
页写入时序图如下:
STM32HAL库学习笔记八——SPI通信_第9张图片

void FLASH_PageWrite(uint8_t *pbuff,uint32_t write_add,uint16_t size)
{
	uint8_t cmd[4];
	cmd[0] = W25X_PageProgram;
	cmd[1] = (uint8_t)(write_add >> 16);
	cmd[2] = (uint8_t)(write_add >> 8);
	cmd[3] = (uint8_t)(write_add);
	FLASH_WriteEnable();
	Wait_WriteBusy();
	CS_enable;
	SPI_Write(cmd,4);
	SPI_Write(pbuff,size);
	CS_disable;
	Wait_WriteBusy();
}

7.读函数

FLASH读取可以从指定地址后读取多个字节的数据。读取时序图如下:
STM32HAL库学习笔记八——SPI通信_第10张图片

void FLASH_Read(uint8_t *pbuff,uint32_t read_add,uint16_t size)
{
	uint8_t cmd[4];
	cmd[0] = W25X_ReadData;
	cmd[1] = (uint8_t)(read_add >> 16);
	cmd[2] = (uint8_t)(read_add >> 8);
	cmd[3] = (uint8_t)(read_add);
	Wait_ReadBusy();
	Wait_WriteBusy();
	CS_enable;
	SPI_Write(cmd,4);
	SPI_Read(pbuff,size);
	CS_disable;
	Wait_ReadBusy();
}

8.读取厂商设备ID

使用指令可以分别读取到厂商ID和设备ID,时序图如下:
STM32HAL库学习笔记八——SPI通信_第11张图片

void FLASH_ManufacturerID(uint8_t* devbuf)
{
	uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};
	Wait_WriteBusy();
	Wait_ReadBusy();
	CS_enable;
	SPI_Write(cmd,4);
	SPI_Read(devbuf,2);
	CS_disable;
	Wait_ReadBusy();
}

9.printf重定向

为了方便打印输出,对printf进行重定向。注意使用时要勾选上keil里的微库use MicroLib

int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}

int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}

10.测试函数

编写完必要的函数后,设计实验测试STM32的FLASH读写过程。

/*
便于打印的函数
*/
void Byte_print(uint8_t* pbuff)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		printf("0x%x",pbuff[i]);
		printf("\t");
	}
	printf("\r\n");
}
/*打印厂商设备信息*/
printf("==test begin==\r\n");
FLASH_ManufacturerID(devid);
printf("%x",devid[0]);
printf("%x\r\n",devid[1]);
/*扇区擦除*/
printf("==erase==\r\n");
FLASH_SectorErase(0);	
FLASH_Read(revbuff,0,8);
Byte_print(revbuff);
/*输入输出测试*/
printf("==write data==\r\n");
FLASH_PageWrite(sendbuf,0,8);
FLASH_Read(revbuff,0,8);
Byte_print(revbuff);
/*再次擦除*/
printf("==erase==\r\n");
FLASH_SectorErase(0);	
FLASH_Read(revbuff,0,8);
Byte_print(revbuff);

测试打印结果如图所示:
STM32HAL库学习笔记八——SPI通信_第12张图片


总结

总的来说我认为SPI部署有以下几个比较重要的点:
1.FLASH片选信号使用软件控制,要注意与硬件控制加以区别;
2.SPI的启停由片选信号控制,SPI的时钟线更多的起到数据同步的作用,因此实际传输时参考芯片的传输时序很重要,在不同的芯片操作指令之后跟随的内容是不一样的;
3.注意判断状态寄存器,FLASH读写较慢,没有执行完命令不能进行下一个操作;
4.擦除完也需要加入延时,立刻执行写命令写不进去。

你可能感兴趣的:(笔记,stm32,单片机,学习)