STM32CubeMX系列|SPI总线

SPI总线

1. 简介

1.1 SPI总线介绍

SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图

在这里插入图片描述
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。

SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO/MOSI/SCLK),1条为SPI片选信号线(CS),它们的作用如下:

  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生
  • CS:从设备片选信号,由主设备控制

SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:

  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

在这里插入图片描述

1.2 W25QXX芯片介绍

W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。
下表是W25QXX的常用命令表

在这里插入图片描述

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

  • D1指示灯
  • K_UP和K_DOWN按键
  • USART1
  • SPI
  • W25Q64

在这里插入图片描述

3. 软件设计

3.1 STM32CubeMX设置
  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式
  • PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚)

在这里插入图片描述

  • 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制

在这里插入图片描述

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM编程
  • 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
void MX_SPI2_Init(void){
    
    
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;	//设置为主模式
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;	//双线模式
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;	//	8位数据长度
  hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;	//串行同步时钟空闲状态为高电平
  hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;	//第二个跳变沿采样
  hspi2.Init.NSS = SPI_NSS_SOFT;	//NSS软件控制
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;	//分配因子256
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;	//MSB先行
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;	//关闭TI模式
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;	//关闭硬件CRC校验
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK){
    
    
    Error_Handler();
  }
}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
    
    
  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(spiHandle->Instance==SPI2){
    
    
  __HAL_RCC_SPI2_CLK_ENABLE();  
  __HAL_RCC_GPIOB_CLK_ENABLE();
  /**SPI2 GPIO Configuration    
  PB13     ------> SPI2_SCK
  PB14     ------> SPI2_MISO
  PB15     ------> SPI2_MOSI */
  GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  }
}
//这里仅介绍几个重要的函数
void W25QXX_Init(void){
    
    
	W25Qx_Disable();
	W25QXX_TYPE = W25QXX_ReadID();	//读取芯片ID
	printf("FLASH ID:%X\r\n",W25QXX_TYPE);
	if(W25QXX_TYPE == 0xc816)
		printf("FLASH TYPE:W25Q64\r\n");
}

uint16_t W25QXX_ReadID(void){
    
    
	uint16_t ID;
	uint8_t id[2]={
    
    0};
	uint8_t cmd[4] = {
    
    W25X_ManufactDeviceID,0x00,0x00,0x00};	//读取ID命令	
	W25Qx_Enable();		//使能器件
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);
	HAL_SPI_Receive(&hspi2,id,2,1000);
	W25Qx_Disable();	//取消片选	
	ID = (((uint16_t)id[0])<<8)|id[1];
	return ID;
}

void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    
    
	uint8_t cmd[4] = {
    
    0};
	cmd[0] = W25X_ReadData;		//读取命令
	cmd[1] = ((uint8_t)(ReadAddr>>16));
	cmd[2] = ((uint8_t)(ReadAddr>>8));
	cmd[3] = ((uint8_t)ReadAddr);

	W25Qx_Enable();		//使能器件
	HAL_SPI_Transmit(&hspi2,cmd,4,1000);
	HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
	W25Qx_Disable();	//取消片选	
}

void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
    
    
	uint32_t secpos;
	uint16_t secoff;
	uint16_t secremain;
	uint16_t i;
	uint8_t *W25QXX_BUF;
	
	W25QXX_BUF = W25QXX_BUFFER;
	secpos = WriteAddr/4096;	//扇区地址
	secpos = WriteAddr%4096;	//在扇区里的偏移
	secremain = 4096-secoff;	//扇区剩余空间大小
	printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
	if(NumByteToWrite <= secremain)  //不大于4K字节
		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++;		//扇区地址增1
			secoff = 0;		//偏移位置为0		
			pBuffer += secremain;	//指针偏移
			WriteAddr += secremain;	//写地址偏移
			NumByteToWrite -= secremain;	//字节数递减
			if(NumByteToWrite > 4096)		
				secremain = 4096;			//下个扇区还没是写不完
			else
				secremain = NumByteToWrite;	//下个扇区可以写完了
		}
	}
}
  • 在main.c文件下编写SPI测试代码
/* USER CODE BEGIN PV */
uint8_t wData[0x100];
uint8_t rData[0x100];
uint32_t i;
/* USER CODE END PV */
void SystemClock_Config(void);

int main(void){
    
    
  /* USER CODE BEGIN 1 */
  uint8_t key;
  /* USER CODE END 1 */
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  W25QXX_Init();
  for(i=0;i<0x100;i++){
    
    
  	wData[i] = i;
  	rData[i] = 0;
  }	
  /* USER CODE END 2 */
  while (1){
    
    
    key = KEY_Scan(0);
	if(key == KEY_UP_PRES){
    
    
		printf("KEY_UP_PRES write data...\r\n");
		W25QXX_Erase_Sector(0);
		W25QXX_Write(wData,0,256);
			
	}
			
	if(key == KEY_DOWN_PRES){
    
    
	printf("KEY_DOWN_PRES read data...\r\n");
		W25QXX_Read(rData,0,256);
		for(i=0;i<256;i++){
    
    
			printf("0x%02X ",rData[i]);
		}
	}
		
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
	HAL_Delay(200);
    }
}

4. 下载验证

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

在这里插入图片描述

关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:

玩转STM32CubeMX | SPI总线

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/108821717