STM32CubeMX系列教程11:串行外设接口SPI(二)
1.新建工程
本章程序在串口printf工程的基础上修改,复制串口printf的工程,修改文件夹名。击STM32F746I.ioc打开STM32cubeMX的工程文件重新配置。SPI1选择全双工主模式,不开启NSS。配置PA7为SPI_MOSI,PA6为SPI_MISO,PA5为SPI_SCK,PA4配置为GPIO输出模式,作为片选信号。
SPI配置中设置数据长度为8bit,MSB先输出分频为64分频,则波特率为1.6875 MBits/s。其他为默认设置。
Motorla格式,CPOL设置为Low,CPHA设置为第一个边沿。不开启CRC检验,NSS为软件控制。
在GPIO管脚配置中设置PA4的用户标签为SPI1_CS。
下面为W25QXX的驱动文件。下载加压并添加进工程中。
在工程目录下新建文件夹BSP,把刚才下载的文件复制进去。
在工程框中,选择工程名按鼠标右键添加组,修改组名称为Drivers/BSP,选择刚才BSP文件夹的路径,添加W25QXX.c文件。
重新编译工程,看是否有错误。
2.W25Qxx驱动函数介绍
在W25QXX.c文件中有很多操作函数,这个只接收几个简单的函数。
/** * @brief Read Manufacture/Device ID. * @param return value address * @retval None */ void BSP_W25Qx_Read_ID(uint8_t *ID) { uint8_t cmd[4] = {READ_ID_CMD,0x00,0x00,0x00}; W25Qx_Enable(); /* Send the read ID command */ HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE); /* Reception of the data */ HAL_SPI_Receive(&hspi1,ID, 2, W25Qx_TIMEOUT_VALUE); W25Qx_Disable(); }
以上为读W25Qxx读ID函数,函数开始先定义一个数组cmd保存读ID命令,其中READ_ID_CMD为读ID命令90H,在W25QXX.h头文件中通过宏定义。数组后三个值为地址000000H。
W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平。在W25QXX.h头文件中可以找到如下宏定义。
在GPIO管脚配置中设置PA4的用户标签为SPI1_CS,所以mxconstants.h头文件中有如下宏定义。
HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE) 为通过SPI将cmd中四个字节的命令发送出去。 然后通过HAL_SPI_Receive(&hspi1,ID, 2, W25Qx_TIMEOUT_VALUE);函数介绍两个字节的数据保存在对应的地址ID中。W25Qx_TIMEOUT_VALUE为超时。其值为1000.
如下为W25QXX读函数。函数开始先将要发送的数据(命令和地址)存储在cmd数组中,然后后通过HAL_SPI_Transmit()函数发送出去,接着通过HAL_SPI_Receive()接收读取的数据。
/** * @brief Reads an amount of data from the QSPI memory. * @param pData: Pointer to data to be read * @param ReadAddr: Read start address * @param Size: Size of data to read * @retval QSPI memory status */ uint8_t BSP_W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size) { uint8_t cmd[4]; /* Configure the command */ cmd[0] = READ_CMD; cmd[1] = (uint8_t)(ReadAddr >> 16); cmd[2] = (uint8_t)(ReadAddr >> 8); cmd[3] = (uint8_t)(ReadAddr); W25Qx_Enable(); /* Send the read ID command */ HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE); /* Reception of the data */ if (HAL_SPI_Receive(&hspi1, pData,Size,W25Qx_TIMEOUT_VALUE) != HAL_OK) { return W25Qx_ERROR; } W25Qx_Disable(); return W25Qx_OK; }
如下为W25Qxx写操作函数,采用页编程指令(02H),每次最多可以写入256字节。所以写函数中将要写入的数据分多次写入W25Qxx中,每次只写256个字节,不断循环直到数据完全写完。写数据前先使能写操作。
/** * @brief Writes an amount of data to the QSPI memory. * @param pData: Pointer to data to be written * @param WriteAddr: Write start address * @param Size: Size of data to write,No more than 256byte. * @retval QSPI memory status */ uint8_t BSP_W25Qx_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size) { uint8_t cmd[4]; uint32_t end_addr, current_size, current_addr; uint32_t tickstart = HAL_GetTick(); /* Calculation of the size between the write address and the end of the page */ current_addr = 0; while (current_addr <= WriteAddr) { current_addr += W25Q128FV_PAGE_SIZE; } current_size = current_addr - WriteAddr; /* Check if the size of the data is less than the remaining place in the page */ if (current_size > Size) { current_size = Size; } /* Initialize the adress variables */ current_addr = WriteAddr; end_addr = WriteAddr + Size; /* Perform the write page by page */ do { /* Configure the command */ cmd[0] = PAGE_PROG_CMD; cmd[1] = (uint8_t)(current_addr >> 16); cmd[2] = (uint8_t)(current_addr >> 8); cmd[3] = (uint8_t)(current_addr); /* Enable write operations */ BSP_W25Qx_WriteEnable(); W25Qx_Enable(); /* Send the command */ if (HAL_SPI_Transmit(&hspi1,cmd, 4, W25Qx_TIMEOUT_VALUE) != HAL_OK) { return W25Qx_ERROR; } /* Transmission of the data */ if (HAL_SPI_Transmit(&hspi1, pData,current_size, W25Qx_TIMEOUT_VALUE) != HAL_OK) { return W25Qx_ERROR; } W25Qx_Disable(); /* Wait the end of Flash writing */ while(BSP_W25Qx_GetStatus() == W25Qx_BUSY); { /* Check for the Timeout */ if((HAL_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE) { return W25Qx_TIMEOUT; } } /* Update the address and size variables for next page programming */ current_addr += current_size; pData += current_size; current_size = ((current_addr + W25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : W25Q128FV_PAGE_SIZE; } while (current_addr < end_addr); return W25Qx_OK; }
扇区擦除函数,和写函数一样,擦除扇区前必先使能写操作。发送扇区擦除指令后不断读取W25Qxx的状态寄存器,判断flash是否为忙状态,如果不为忙则擦除操作完成。
/** * @brief Erases the specified block of the QSPI memory. * @param BlockAddress: Block address to erase * @retval QSPI memory status */ uint8_t BSP_W25Qx_Erase_Block(uint32_t Address) { uint8_t cmd[4]; uint32_t tickstart = HAL_GetTick(); cmd[0] = SECTOR_ERASE_CMD; cmd[1] = (uint8_t)(Address >> 16); cmd[2] = (uint8_t)(Address >> 8); cmd[3] = (uint8_t)(Address); /* Enable write operations */ BSP_W25Qx_WriteEnable(); /*Select the FLASH: Chip Select low */ W25Qx_Enable(); /* Send the read ID command */ HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE); /*Deselect the FLASH: Chip Select high */ W25Qx_Disable(); /* Wait the end of Flash writing */ while(BSP_W25Qx_GetStatus() == W25Qx_BUSY); { /* Check for the Timeout */ if((HAL_GetTick() - tickstart) > W25Q128FV_SECTOR_ERASE_MAX_TIME) { return W25Qx_TIMEOUT; } } return W25Qx_OK; }
3.添加应用程序
在main.c文件中声明变量,rData,wData分别存储读写的数据,ID存储读取的ID值。
/* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ uint8_t wData[0x100]; uint8_t rData[0x100]; uint32_t i; uint8_t ID[2]; /* USER CODE END PV */
在main函数中添加如下测试程序。
/* USER CODE BEGIN 2 */ printf("\r\n SPI-W25Qxxx Example \r\n\r\n"); /*##-1- Read the device ID ########################*/ BSP_W25Qx_Init(); BSP_W25Qx_Read_ID(ID); printf(" W25Qxxx ID is : 0x%02X 0x%02X \r\n\r\n",ID[0],ID[1]); /*##-2- Erase Block ##################################*/ if(BSP_W25Qx_Erase_Block(0) == W25Qx_OK) printf(" SPI Erase Block ok\r\n"); else Error_Handler(); /*##-3- Written to the flash ########################*/ /* fill buffer */ for(i =0;i<0x100;i ++) { wData[i] = i; rData[i] = 0; } if(BSP_W25Qx_Write(wData,0x00,0x100)== W25Qx_OK) printf(" SPI Write ok\r\n"); else Error_Handler(); /*##-4- Read the flash ########################*/ if(BSP_W25Qx_Read(rData,0x00,0x100)== W25Qx_OK) printf(" SPI Read ok\r\n\r\n"); else Error_Handler(); printf("SPI Read Data : \r\n"); for(i =0;i<0x100;i++) printf("0x%02X ",rData[i]); printf("\r\n\r\n"); /*##-5- check date ########################*/ if(memcmp(wData,rData,0x100) == 0 ) printf(" W25Q128FV SPI Test OK\r\n"); else printf(" W25Q128FV SPI Test False\r\n"); /* USER CODE END 2 */
程序中先是初始化W25QXX,即发送使能重启(66H)和使能重启设备(99H)命令初始化flash,然后读取设备ID并输出。第2步中擦除flash,写入数据前必先要擦除内存。然后填充写入数据缓存并写入flahs中。最后读取刚才写入的数据,并检测数据是否正确。
把程序中用的出错处理函数添加在main.c文件后面。
/* USER CODE BEGIN 4 */ /** * @brief This function is executed in case of error occurrence. * @param None * @retval None */ static void Error_Handler(void) { printf("something wrong ....\r\n"); /* User may add here some code to deal with this error */ while(1) { } } /* USER CODE END 4 */
在main.c文件开头添加应用的头文件。
/* USER CODE BEGIN Includes */ #include <string.h> #include "W25QXX.h" /* USER CODE END Includes */</string.h>
将W25QXX DataFlash Board模块插入到Open746I开发板SPI1中,编译程序并下载到开发板。打开串口调试助手。设置波特率为115200。串口助手上会显示如下信息。