STM32H743-ARM例程18-SPI
目录
- 实验平台
- SPI
- SPI 介绍
- SPI协议
- W25Q128介绍
- STM32CubeMX生成工程
- 实验代码
- 实验现象
实验平台
硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器
软件:最新版本STM32CubeH7固件库,STM32CubeMX v6.10.0,开发板环境MDK v5.35,串口工具putty
SPI
SPI 介绍
SPI是串行外设接口(Serial PeripheralInterface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(数据输入)、MOSI(数据输出)、SCLK(时钟)、CS(片选)。
SPI硬件接口:
MISO :主设备数据输入,从设备数据输出
MOSI :主设备数据输出,从设备数据输入
SCLK :时钟信号,由主设备产生
CS :从设备片选信号,由主设备控制
主设备是通过片选线选择要与之通信的从设备。每个从设备都有一个片选线,当片选线为低电平时,表示该从设备被选中。(也有一些设备以高电平有效,需要根据其数据手册确定)。主设备通过控制时钟线的电平来同步数据传输。时钟线的上升沿和下降沿用于控制数据的传输和采样。SPI的主从接线方式需要对应,主从机设定后身份固定。
SPI协议
根据时钟极性(CPOL)和时钟相位(CPHA)配置的不同,SPI工作模式分为4种。
时钟极性是指SPI通信设备处于空闲状态时(也可以认为这是SPI通信开始时,即SS线为低电平),SCK信号线的电平信号。CPOL=0时,SCK在空闲状态时为低电平,CPOL=1时则相反。
时钟相位是指数据采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的奇数边沿被采样。当CPHA=1时,数据线在SCK的偶数边沿被采样。
以CPHA=0为例讲解SPI时序。
首先,由主机把片选信号NSS拉低,意为主机输出。
在NSS被拉低的时刻,SCK分为两种情况,若我们设置CPOL=0,则SCK时序在这时为低电平,若设置为CPOL=1,则SCK在这个时刻为高电平。
无论CPOL为0还是1,因为我们配置的时钟相位CPHA=0,在采样时刻的时序中我们可以看到,采样时刻都是在SCK的奇数边沿(注意奇数边沿有时为下降沿,有时为上升沿)。
因此,MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,这个信号将会在SCK奇数边沿时被采集,在非采样时刻,MOSI和MISO的有效信号才发生切换。
对于CPHA=1的情况也类似,只是数据信号的采样时刻为偶数边沿。
W25Q128介绍
W25Q128是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q125芯片容量为128Mbit(16MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,比如W25Q16、W25Q64、W25Q32等。通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。 W25Q128闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,将16M的容量分为256个块(Block),每个块的大小为64KB,每个块包含16个扇区,每个扇区的大小为4K字节。
W25Q128 的适用场景非常广泛,几乎涵盖了所有需要可靠存储的嵌入式电子产品:
嵌入式系统:存储微控制器的启动代码、应用程序固件和系统配置文件。
物联网设备:保存传感器数据、设备日志和用于OTA(空中下载)升级的固件包。
消费电子:在智能手表、蓝牙耳机等设备中,用于存放开机动画、提示音等资源文件。
工业控制与汽车电子:存储设备参数、校准数据等,适应恶劣的工作环境。
更多的W25Q128的介绍,请参考W25Q128的DATASHEET。
本章实验对W25Q128操作步骤,初始化后,写使能SPI5,之后进行扇区擦除,等待空闲后即可再次写使能进行页编程,具体如图所示。
STM32CubeMX生成工程
我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示,我们来看配置SPI部分如下图所示:
实验代码
1. 主函数
int main(void)
{uint16_t device_id=0; //Flash内部的IDuint8_t read_buf=0; //用于存放读取缓冲地址uint8_t write_buf=0; //用于存放写入缓冲地址uint16_t i; HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI5_Init();MX_USART6_UART_Init();uart6.initialize(115200);HAL_Delay(5000);//延时5秒,提供使用者打开串口时间uart6.printf("\033[1;32;40m");device_id = W25QXX_ReadID();//读取Flash内部的IDuart6.printf("W25QXX_ReadID:%x\r\n",device_id);W25QXX_Erase_Sector(0);//擦除扇区。在闪存存储器中,写入新数据之前,需要先将要写入的扇区擦除,以确保该扇区中之前的数据被清空uart6.printf("W25QXX_Write_Buffer:0~255\r\n"); for(i=0;i < 256;i++){W25QXX_Page_Program(&write_buf, i, 1); //将数据写入FLASH,参数包括写入数据的缓冲区地址(&write_buf)、写入的地址(i)和写入的数据长度(1)write_buf = write_buf + 1; //写入数据的缓冲区地址递增1}uart6.printf("W25QXX_Read_Buffer:");for(i=0;i < 256;i++){if(i%10==0)uart6.printf("\r\n");W25QXX_Read(&read_buf, i, 1); //从芯片依次读取数据,参数包括读取数据的缓冲区地址(&read_buf)、读入的地址(i)和读入的数据长度(1)uart6.printf("%d\t",read_buf);}uart6.printf("\r\n\r\nFlash Test OK!\r\n");while (1){}}
2. SPI初始化函数
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};if(spiHandle->Instance==SPI5){/** 初始化外设时钟*/PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI5;PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_D2PCLK1;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){Error_Handler();}/*SPI5时钟使能 */__HAL_RCC_SPI5_CLK_ENABLE();__HAL_RCC_GPIOJ_CLK_ENABLE();__HAL_RCC_GPIOH_CLK_ENABLE();/**SPI5 GPIO配置PJ11 ------> SPI5_MISOPJ10 ------> SPI5_MOSIPH6 ------> SPI5_SCK*/GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF5_SPI5;HAL_GPIO_Init(GPIOJ, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_6;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF5_SPI5;HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);HAL_NVIC_SetPriority(SPI5_IRQn, 0, 0);HAL_NVIC_EnableIRQ(SPI5_IRQn);}
}
初始化SPIx,设置SPIx工作模式
void MX_SPI5_Init(void)
{hspi5.Instance = SPI5;hspi5.Init.Mode = SPI_MODE_MASTER;hspi5.Init.Direction = SPI_DIRECTION_2LINES;hspi5.Init.DataSize = SPI_DATASIZE_8BIT;hspi5.Init.CLKPolarity = SPI_POLARITY_LOW;hspi5.Init.CLKPhase = SPI_PHASE_1EDGE;hspi5.Init.NSS = SPI_NSS_SOFT;hspi5.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;hspi5.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi5.Init.TIMode = SPI_TIMODE_DISABLE;hspi5.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi5.Init.CRCPolynomial = 0x0;hspi5.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;hspi5.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;hspi5.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;hspi5.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;hspi5.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;hspi5.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;hspi5.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;hspi5.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;hspi5.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;hspi5.Init.IOSwap = SPI_IO_SWAP_DISABLE;if (HAL_SPI_Init(&hspi5) != HAL_OK){Error_Handler();}}