一、项目背景
1.1 本专栏背景:
(1)STM32 CUBE MX 软件的选用:
对于毕业以后很久没有碰过STM32代码编写的码农来说,如果按照传统的HAL库编写单片机程序费时费力。哪怕是仅仅用裸机跑功能,不仅仅需要重新阅读数据手册和开发板厂家的教程,而且还基于厂家例程改写引脚、外设参数等等。出于项目周期、任务达成两方面考量,为快速完成可供芯片功能、性能测试用途的STM32 demo工程代码,可考虑使用STM32 CUBE MX 软件进行快速代码生成,配合开发板相关手册、网上教程进行排障。(STM32CUBE MX官方下载地址)
(2)RTOS的应用
基于项目多任务实时处理的需求,在项目中添加FREE_RTOS操作系统。借助STM32 CUBE MX 软件和操作系统配套官方配件包(配件包官方下载地址)进行快速移植、配置。
一方面,在公司做项目不会有充裕的时间像在学校那样慢慢啃视频教程或者教程书籍,主要的特点是快速完成一个demo工程实现基本的功能如点灯,在此基础上增加新的功能和需求,遇到新的问题通过论坛或者请教他人解决;另一方面,之前学生时代简单上手STM32工程进行实验,没有深究其中的众多机制(书到用时方恨少啊…),故在此整理从0开始完成功能开发的过程,不仅供日后编者自己查阅,也供有类似需求的开发者参考,抛砖引玉,如有误请大佬指正。
如果读者参考本文的操作遇到其他问题或者对文中的操作有疑问和建议欢迎在评论区与我交流。
文中所用的软件大多通过ST官网或者野火/正点原子等开发板厂家可以获得,我尽量将软件贴在文章中免费下载,如有需要的读者可以联系我免费获取(编者自己也是厌倦了各种会员和关注微信下载的套路,故所有资源无门槛无套路联系我即可获得,愿与各位一起进步!)
(无广)需要相关芯片资料可通过该网站查找下载半导小芯。
1.2 本项目背景
现有一项目,需使用F429芯片通过SPI通信协议对某一芯片(称为B芯片)进行读/写操作,编者的任务是编写该单片机控制代码实现对B芯片的通信、控制。SPI通信协议是在嵌入式开发中常见、常用的通信协议。掌握单片机的SPI控制代码快速编写和debug是一项基本技能。(编者上周被同事吐槽说在学校学习的基本功不扎实…)
本测试demo的调试即使借助STM32Cube MX、参考了野火等网上例程,自己仍然花了数天时间定位问题。我认为这些努力是值得的:
(1)在编写驱动中去加深对SPI通信机制、芯片工作原理以及HAL标准接口的理解;
(2)尽管有的人觉得不需要掌握STM32Cube MX也能轻松编写基于HAL库的FreeRTOS工程,但是认为掌握STM32Cube MX能够使你在没有模板和代码时,仅仅借助自动生成和数据手册编写基本的芯片驱动。
二、本次开发的环境准备
2.1 硬件环境
本次开发使用F103开发板以及配套的JTAG debugger。开发板通过debugger和USB串口线连接电脑,开发板上主控芯片F103VET6通过四个引脚连接W25Q64芯片。连接示意图如下

关于SPI协议和FLASH芯片,网上讲解的资料有很多,在此不再赘述。
2.2 软件环境
如本专栏之前文章所述,使用STM32 Cube MX进行代码生成,使用Keil进行代码修改和编译。串口调试工具选择具有基本功能的即可,无特别限制。
本文章代码参考野火指南者开发板中的例程,所需的代码会在文中展示。
三、开发过程
3.1 生成代码
初次生成代码的基本操作参考编者文章串口工程的快速生成
在完成以上操作基础上,增加以下操作:
配置SPI1相关的引脚以及通信参数。
3.1.1 配置SPI1的时钟线、数据线

开发注意事项:
(1)开发者在拿到一个没有接触过的芯片时,如果有芯片厂家或是其他渠道获得的参考例程,请仔细按照例程配置Parameter Settings中的各项参数,或者是在生成代码后,找到spi.c以及spi.h,对SPI外设初始化参数进行仔细核对,以下是野火提供的驱动代码
void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
在main.c中需要调用void MX_SPI1_Init(void)函数对外设进行初始化。
(2)如果开发者只有芯片手册(比如你没有参考代码,手上只有W25Q64芯片手册),需要重点关注几方面:芯片所支持的读写频率、芯片与单片机的硬件连接、芯片手册上提供的读写协议及其时序。在开发中遇到问题请务必再三检查以上的参数是否配置正确,其中读写频率是容易踩坑的参数,网上有部分文章和视频有提到,部分芯片在进行SPI通信时,即时配置通信波特率稍稍低于理论最高工作频率但仍在理论许可范围内,可能会出现错误,所以当出现类似问题时可以尝试降低通信波特率。
3.1.2 配置SPI的片选信号线
前面提到开发板上的片选信号线CS引脚对应单片机的PC0引脚,在可视化配置界面中找到PA4引脚并配置为输出模式。

在system core->GPIO中配置输出引脚的参数

3.1.3 创建SPI线程,任务函数为设置为弱定义。

3.1.4 最后检查工程的时钟树和系统嘀嗒定时器
3.2 在keil中编写W25Q64芯片的驱动文件
3.2.1 从简单的FLASH ID读取开始
在工程目录中创建BSP文件夹,在文件夹中创建bsp_spi_flash.c和bsp_spi_flash.h文件。为了方便修改,将SPI线程的任务函数编写在驱动中,所以freertos.c需要引用驱动头文件:
#include "bsp_spi_flash.h"
首先在前者中添加如下任务函数,目的是进行简单的FLASH ID读取:
#include "bsp_spi_flash.h"
typedef enum {
FAILED = 0, PASSED = !FAILED} TestStatus;
__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;
static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
/**
* @brief 使用SPI发送一个字节的数据
* @param byte:要发送的数据
* @retval 返回接收到的数据
*/
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待发送缓冲区为空,TXE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
WRITE_REG(hspi1.Instance->DR, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收缓冲区非空,RXNE事件 */
while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
/* 读取数据寄存器,获取接收缓冲区数据 */
return READ_REG(hspi1.Instance->DR);
}
/**
* @brief 读取FLASH ID
* @param 无
* @retval FLASH ID
*/
uint32_t SPI_FLASH_ReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* 开始通讯:CS低电平 */
SPI_FLASH_CS_LOW();
/* 发送JEDEC指令,读取ID */
SPI_FLASH_SendByte(W25X_JedecDeviceID);
/* 读取一个字节数据 */
Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
/* 停止通讯:CS高电平 */
SPI_FLASH_CS_HIGH();
/*把数据组合起来,作为函数的返回值*/
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
/**
* @brief 读取FLASH Device ID
* @param 无
* @retval FLASH Device ID
*/
uint32_t SPI_FLASH_ReadDeviceID(void)
{
uint32_t Temp = 0;
/* Select the FLASH: Chip Select low */
SPI_FLASH_CS_LOW();
/* Send "RDID " instruction */
SPI_FLASH_SendByte(W25X_DeviceID);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */
Temp = SPI_FLASH_SendByte(Dummy_Byte);
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH();
return Temp;
}
void SPI_Flash_PowerDown(void)
{
/* 选择 FLASH: CS 低 */
SPI_FLASH_CS_LOW();
/* 发送 掉电 命令 */
SPI_FLASH_SendByte(W25X_PowerDown);
/* 停止信号 FLASH: CS 高 */
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待超时回调函数
* @param None.
* @retval None.
*/
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 等待超时后的处理,输出错误信息 */
FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
void StartTask_SPI(void const * argument)
{
/* USER CODE BEGIN StartTask_SPI */
printf("\r\nthis is a 16M flash(W25Q64)SPI TEST \r\n");
/* 获取 Flash Device ID */
DeviceID = SPI_FLASH_ReadDeviceID();
osDelay(200);
/* 获取 SPI Flash ID */
FlashID = SPI_FLASH_ReadID();
printf("\r\nFlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n", FlashID, De

最低0.47元/天 解锁文章
1341

被折叠的 条评论
为什么被折叠?



