仅需一个 HAL 库函数:轻松实现 STM32 的 SPI 编程(以 Flash 25Q128 为例)
主要介绍如何用HAL_SPI_TransmitReceive()函数实现对W25Q128 Flash存储器ID的读取。先介绍SPI是一种高速且简单的同步串行接口技术,由四根线((MOSI、MISO、SCLK和SS/CS))组成。接着介绍Flash ,它是串行闪存芯片,能提供更大存储容量。还提到STM32 HAL库简化了SPI编程,以正点原子精英V2开发板为例,给出开发环境及函数原型和参数。最后展示读取ID的代码示例,通过发送命令、接收数据并判断状态来获取ID。
一、SPI介绍
百科上解释,SPI(Serial Peripheral Interface一一串行外设接口)总线是Motorola公司推出的一种同步串行接口技术。SPI通讯其实就是设备之间互相“唠嗑”、传数据的一种方式。就好比在餐厅里,顾客、服务员和厨房之间得交流,才能顺利地把饭吃上。有些在电子设备里,主机和从机就靠着SPI通讯。广泛应用:常用于传感器、存储芯片(如W25Q64)、显示器等外设与主控芯片之间的通信。
1.为啥要用SPI?
高速传输:比其他一些通信协议(如I2C)更快。
简单直接:只需要4根线就能实现设备间的通信。
2.SPI的基本组成
四根线搞定,SPI通信只需要四根线,分别是:
MOSI (Master Out Slave In):主机发送数据给从机。
MISO (Master In Slave Out):主机接收从机的数据。
SCLK (Serial Clock):时钟信号,控制数据传输的节奏。
SS/CS (Slave Select/Chip Select):选择要通信的从设备。
3.餐馆点餐吃饭类比SPI
为了让你更好地理解这些概念,我们用一个大家都熟悉的场景- 餐馆点餐吃饭.
顾客点菜(MOSI):你告诉服务员你想点什么菜(发送命令和地址)。
服务员送餐(MISO):服务员将你点的菜送到桌上(返回数据)。
服务流程时间表(SCK):餐厅有一套严格的时间表,确保每个步骤都能按时完成(时钟信号控制数据传输节奏)。
顾客呼叫特定服务员(SS/CS):当你按下餐桌上的呼叫按钮时,只有负责这张桌子的服务员会过来服务(选择从设备进行通信)。
4.SPI时序图
主机通过拉低 CS 选中从机,SCK 提供时钟信号确定节奏。主机经 MOSI 发数据,从机经 MISO 回传。按时钟信号在特定边沿收发数据,完成一次通讯后主机拉高 CS 结束。
二、 Flash 介绍
1.什么是Flash串行闪存芯片?
Flash串行闪存芯片是一种非易失性存储器,即使在断电的情况下也能保存数据。它们通过 SPI(Serial Peripheral Interface) 接口与主控设备进行通信。相比于并行闪存,串行闪存具有引脚数少、封装小、成本低等优点。W25Q64和W25Q128都是常见的串行闪存芯片。它就是单片机的 “小仓库”,可以把程序、数据啥的存进去,要用的时候再取出来,就像咱们把东西存进仓库,要用的时候再去拿。
2.为啥需要Flash?
尽管单片机有内置Flash,但外置提供更大存储容量,适合大数据存储,大多数单片机的内置 Flash 容量相对较小(例如,STM32F103C8T6 只有 64 KB 或 128 KB)。而 W25Q64 提供了 8 MB 的存储空间。它还能分离存储,保护主程序代码,避免干扰和误操作,提升系统稳定性和灵活性。
3.Flash的类比
Flash可类比为电脑硬盘与U盘。电脑硬盘长期存储操作系统、软件、文档等多样数据,Flash在嵌入式系统里能存程序代码、传感器数据。Flash类似一个巨大的字节数组,支持按地址进行数据增删改查操作(CRUD:Create, Read, Update, Delete)。增(写入)数据需按页对齐,删(擦除)以扇区或块为单位,改(更新)需先擦除再写入,查(读取)则直接按地址读取。
4.Flash芯片举例
4.1 W25Q64 是由 Winbond(华邦电子) 生产的一款 SPI NOR Flash 存储器。
W25Q64 容量为 64 兆比特(Megabit, Mbit),W25Q的"64"是Mbit ,换算后为 8 兆字节(MB),即 8×1024×1024 字节(Byte),选型的时候要注意的。包含 128 个块、2048 个扇区或 32768 个页。
4.1 NM25Q128EVB容量
NM25Q128EVB容量为 128 兆比特(Mbit),W25Q的"128"是Mbit ,换算后为 16 兆字节(MB),即 16×1024×1024 字节(Byte),是 W25Q64 容量的两倍,因此它包含 256 个块、4096 个扇区或 65536 个页。读 ID(Manufacturer ID)的命令是0x90,其默认值是0x52.
5.命令统计
W25Q64和NM25Q128EVB都通用的.
操作 |
命令 |
读 ID |
0x90 |
读数据 |
0x03 |
写使能 |
0x06 |
读状态 |
0x05或0x35 或 0x15 |
擦除扇区 |
0x20 |
三、SPI编程
1.STM32 HAL库
以前SPI编程可麻烦了,各种底层操作让人眼花缭乱。但有了HAL库函数就不一样了,它就像贴心的餐厅服务生,对服务流程门儿清。你去餐厅点餐,只要告诉服务生想吃啥,后面的备餐、上菜他都能搞定。同样,在SPI通信里,你只要跟HAL库说清楚数据发送和接收的需求,像初始化、参数设置这些复杂细节,它都帮你处理,你就能专心搞数据传输。通过SPI接口,STM32和Flash之间的数据传输就跟顾客和服务生交流一样,简单又高效 。
2.开发环境
硬件:正点原子精英版 V2 STM32F103开发板
单片机:STM32F103ZET6
Keil版本:5.32
STM32CubeMX版本:6.9.2
STM32Cube MCU Packges版本:STM32F1xx_DFP.2.4.1
串口:USART1(PA9,PA10)
SPI2:
PB12 SPI2_CS
PB13 SPI2_SCK
PB14 SPI2_MISO
PB15 SPI2_MOSI
Flash型号:NM25Q128EVB
3.一个 HAL 库函数搞定 SPI 编程
函数原型
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)//同时收发数据
参数 |
说明 |
SPI_HandleTypeDef *hspi |
哪个SPI控制器 |
uint8_t *pTxData |
要发送的数据的 buffer |
uint8_t *pRxData |
存储接收到的数据是 buffer |
uint16_t Size |
数据个数 |
uint32_t Timeout |
超时时间,单位是 Tick,一般是 1ms |
返回值 |
HAL_OK:成功 HAL_ERROR:错误 HAL_BUSY:总线忙 HAL_TIMEOUT:超时 |
4.配置STM32CubeMX
4.1.启动STM32CubeMX,新建STM32CubeMX项目:
4.2.选择MCU:在软件中选择你的STM32型号-STM32F103ZET6。
4.3.选择时钟源:
4.4.配置时钟:
4.5.使能Debug功能:Serial Wire
4.6.HAL库时基选择:SysTick
4.7.USART1配置:选择异步模式,使能中断。
4.8.开启SPI2:
4.9.配置工程参数:在Project标签页中,配置项目名称和位置,选择工具链MDK-ARM。 4.10.生成代码:在Code Generator标签页中,配置工程外设文件与HAL库,勾选头文件.c和.h文件分开,然后点击Project > Generate Code生成代码。
5.代码示例
具体源码请查看附件.
/**
* @brief 读取W25Q128 Flash存储器的设备ID
* @note 使用HAL库函数 HAL_SPI_TransmitReceive 来发送读取ID命令并接收响应
* @retval 返回制造商ID(Manufacturer ID)
*/
int SPIFlash_25QX_ReadID(void)
{
// 发送缓冲区,包含读取设备ID的命令 0x9F 和一个填充字节 0xFF
uint8_t txbuf[2] = {0x9F, 0xff};
// 接收缓冲区,用于存储从W25Q128接收到的设备ID信息
uint8_t rxbuf[2] = {0, 0};
// 打开片选引脚(CS/SS),告诉W25Q128我们即将开始通信
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // 拉低片选引脚
// 发送读取设备ID的命令,并接收设备ID信息
// 参数解释:
// &hspi2: SPI句柄
// txbuf: 发送缓冲区
// rxbuf: 接收缓冲区
// 2: 发送和接收的数据长度(以字节为单位)
// 10: 超时时间(毫秒)
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi2, txbuf, rxbuf, 2, 10);
// 关闭片选引脚(CS/SS),结束与W25Q128的通信
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // 拉高片选引脚
// 检查传输状态是否成功
if (status == HAL_OK)
{
// 返回制造商ID(Manufacturer ID),位于rxbuf[1]
return rxbuf[1];
}
else
{
// 如果传输失败,返回错误码 -1
return -1;
}
}
6.运行结果
编译烧写后,打开串口助手,接收到数据0x52,与芯片手册的一致.
六、总结
本文不仅涵盖了SPI和Flash存储器的基础知识,还展示了如何利用HAL库简化SPI编程,为嵌入式开发人员提供了一个清晰且实用的指南。希望这篇文章能帮助你轻松上手STM32的SPI编程,并在实际项目中应用自如。仅供参考,有任何问题,欢迎在评论区留言讨论!
作者:jmlinux