SPI通信协议深度解析:从理论到实践(收藏这一篇就够了)
本文深入解析SPI通信协议,涵盖理论原理与实践应用。SPI是一种高速全双工同步串行通信协议,具有简单硬件实现、灵活时钟配置等特点,但也存在引脚需求多、无错误检测等局限。文章详细介绍了SPI的4种时钟模式、数据传输时序和帧格式,并通过STM32 HAL库的代码示例展示了SPI初始化和GPIO配置方法。此外,还对比了SPI与I2C、UART等协议的差异,分析了SPI在多从机连接和电平转换电路设计中的应
SPI通信协议深度解析:从理论到实践
摘要
SPI(Serial Peripheral Interface)是一种高速、全双工、同步的串行通信协议,广泛应用于嵌入式系统和各种外设之间的通信。本文将深入探讨SPI通信的基本原理、工作模式、硬件实现、软件编程、性能优化及实际应用案例,通过丰富的代码示例和实践经验,帮助读者全面掌握SPI通信技术。
第一章:SPI通信协议基础
1.1 SPI通信概述
SPI(Serial Peripheral Interface)是由摩托罗拉公司在1980年代开发的一种同步串行通信接口。它具有以下主要特点:
- 全双工通信:数据可以同时发送和接收
- 高速传输:通常支持高达10Mbps以上的传输速率
- 主从架构:由一个主设备和一个或多个从设备组成
- 同步通信:基于时钟信号进行数据同步
- 简单硬件实现:通常只需要4根信号线
1.2 SPI通信的优势与局限性
优势:
- 高速传输:相比I2C和UART,SPI支持更高的时钟频率
- 全双工操作:可以同时进行数据发送和接收
- 简单的硬件设计:接口简单,易于实现
- 灵活的时钟配置:时钟极性和相位可调
- 无寻址机制:通过片选信号选择从设备
局限性:
- 需要更多引脚:至少需要4根线(全双工)
- 无错误检测:协议本身不包含错误检测机制
- 无应答机制:发送方无法确认接收方是否正确接收
- 距离受限:通常只适合板级通信
1.3 SPI与其他通信协议对比
| 特性 | SPI | I2C | UART |
|---|---|---|---|
| 通信方式 | 全双工 | 半双工 | 全双工 |
| 时钟 | 同步 | 同步 | 异步 |
| 数据线数量 | 3-4 | 2 | 2 |
| 最大速率 | 10+ Mbps | 3.4 Mbps | 1 Mbps |
| 寻址方式 | 硬件片选 | 软件地址 | 无 |
| 拓扑结构 | 点对点/菊花链 | 多主多从 | 点对点 |
| 复杂度 | 低 | 中等 | 低 |
第二章:SPI通信硬件原理
2.1 SPI信号线详解
SPI通信通常使用以下4根信号线:
- SCLK(Serial Clock):时钟信号,由主设备产生
- MOSI(Master Out Slave In):主设备输出,从设备输入
- MISO(Master In Slave Out):主设备输入,从设备输出
- SS/CS(Slave Select/Chip Select):从设备选择信号,低电平有效
扩展信号线:
在某些应用中,可能会使用更多信号线:
- WP(Write Protect):写保护
- HOLD:保持信号,暂停传输
- INT:中断信号
2.2 SPI硬件连接方式
2.2.1 标准单从机连接
/*
* 标准SPI连接示意图
*
* 主设备 从设备
* MOSI -------------> SDI
* MISO <------------- SDO
* SCLK -------------> SCK
* CS -------------> CS
*/
2.2.2 多从机连接方式
/*
* 多从机独立片选连接
*
* 主设备 从设备1 从设备2
* MOSI ------> MOSI ------> MOSI
* MISO <------ MISO <------ MISO
* SCLK ------> SCLK ------> SCLK
* CS1 ------> CS
* CS2 -------------------> CS
*/
2.2.3 菊花链连接
/*
* 菊花链连接方式
*
* 主设备 从设备1 从设备2
* MOSI ------> SDI
* MISO <------ SDO -------> SDI
* SCLK ------> SCK -------> SCK
* CS ------> CS -------> CS
* SDO <------- SDO
*/
2.3 SPI硬件接口电路设计
// SPI接口电平转换电路设计示例
/*
* 3.3V设备与5V设备SPI通信电平转换
*
* 3.3V侧 电平转换芯片 5V侧
* MOSI ------> A1 B1 ------> MOSI
* MISO <------ A2 B2 <------ MISO
* SCLK ------> A3 B3 ------> SCLK
* CS ------> A4 B4 ------> CS
* 3.3V ------> VCCA
* 5V ------> VCCB
* GND ------> GND
*
* 推荐芯片:TXS0108E, SN74LVC8T245
*/
第三章:SPI通信协议详解
3.1 SPI时钟模式
SPI有4种不同的时钟模式,由CPOL(Clock Polarity)和CPHA(Clock Phase)两个参数决定:
| 模式 | CPOL | CPHA | 描述 |
|---|---|---|---|
| 0 | 0 | 0 | 时钟空闲低电平,数据在第一个边沿采样 |
| 1 | 0 | 1 | 时钟空闲低电平,数据在第二个边沿采样 |
| 2 | 1 | 0 | 时钟空闲高电平,数据在第一个边沿采样 |
| 3 | 1 | 1 | 时钟空闲高电平,数据在第二个边沿采样 |
3.2 SPI数据传输时序
// SPI模式0时序详解
/*
* 模式0时序图 (CPOL=0, CPHA=0)
*
* CS : ______/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\______
* SCLK : ______/‾\___/‾\___/‾\___/‾\___/‾\______
* MOSI : ______/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\______
* MISO : ______/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\______
*
* 说明:
* 1. CS下降沿表示通信开始
* 2. SCLK空闲时为低电平
* 3. 数据在SCLK上升沿采样
* 4. 数据在SCLK下降沿变化
*/
3.3 SPI数据帧格式
SPI数据帧通常由8位、16位或32位组成,传输时高位(MSB)在前或低位(LSB)在前。
// 8位数据传输示例 (MSB在前)
/*
* 字节数据:0x53 (二进制:01010011)
*
* 传输顺序:bit7 -> bit0
* MOSI: 0 1 0 1 0 0 1 1
* │ │ │ │ │ │ │ │
* SCLK: \_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾
*
* 时间:t0 t1 t2 t3 t4 t5 t6 t7
*/
3.4 SPI通信流程
-
初始化阶段:
- 配置时钟极性和相位
- 设置时钟频率
- 配置数据位宽
- 初始化GPIO
-
通信开始:
- 拉低片选信号
- 等待从设备准备就绪
-
数据传输:
- 主设备发送数据到MOSI
- 主设备同时接收MISO数据
- 每个时钟周期传输一位数据
-
通信结束:
- 拉高片选信号
- 释放总线
第四章:STM32 SPI接口编程
4.1 STM32 SPI硬件架构
STM32系列微控制器通常包含多个SPI接口,每个SPI接口具有以下特性:
- 支持全双工/半双工通信
- 支持主/从模式
- 可配置的时钟极性和相位
- 可编程的数据位宽(4-16位)
- 硬件CRC计算
- DMA支持
4.2 STM32 HAL库SPI编程
4.2.1 SPI初始化配置
#include "stm32f4xx_hal.h"
SPI_HandleTypeDef hspi1;
void SPI1_Init(void)
{
// SPI实例配置
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // 主模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件片选
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 时钟分频
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB在前
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // TI模式禁用
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // CRC禁用
hspi1.Init.CRCPolynomial = 7; // CRC多项式
// 初始化SPI
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
// GPIO初始化
void SPI1_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
// PA5: SPI1_SCK
// PA6: SPI1_MISO
// PA7: SPI1_MOSI
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; // 复用功能SPI1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 片选引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_4; // PA4作为片选
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始时片选无效
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
4.2.2 基本SPI数据传输函数
// SPI字节传输函数
uint8_t SPI_TransmitReceiveByte(uint8_t txData)
{
uint8_t rxData = 0;
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 传输一个字节
HAL_SPI_TransmitReceive(&hspi1, &txData, &rxData, 1, HAL_MAX_DELAY);
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
return rxData;
}
// SPI多字节传输函数
void SPI_TransmitReceiveBuffer(uint8_t *txBuffer, uint8_t *rxBuffer, uint16_t size)
{
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 传输数据
HAL_SPI_TransmitReceive(&hspi1, txBuffer, rxBuffer, size, HAL_MAX_DELAY);
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
// SPI只发送函数
void SPI_TransmitBuffer(uint8_t *buffer, uint16_t size)
{
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 发送数据
HAL_SPI_Transmit(&hspi1, buffer, size, HAL_MAX_DELAY);
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
// SPI只接收函数
void SPI_ReceiveBuffer(uint8_t *buffer, uint16_t size)
{
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 接收数据
HAL_SPI_Receive(&hspi1, buffer, size, HAL_MAX_DELAY);
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
4.3 SPI中断方式编程
// SPI中断回调函数
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
// SPI1传输完成处理
SPI_TransferComplete = 1;
}
}
// 中断方式传输
void SPI_TransmitReceive_IT(uint8_t *txBuffer, uint8_t *rxBuffer, uint16_t size)
{
// 清除完成标志
SPI_TransferComplete = 0;
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 启动中断传输
HAL_SPI_TransmitReceive_IT(&hspi1, txBuffer, rxBuffer, size);
}
// 等待传输完成
void SPI_WaitForTransferComplete(void)
{
while (!SPI_TransferComplete)
{
// 可以在这里执行其他任务
__NOP();
}
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
4.4 SPI DMA方式编程
// DMA传输配置
void SPI1_DMA_Init(void)
{
// DMA控制器时钟使能
__HAL_RCC_DMA2_CLK_ENABLE();
// DMA发送流配置
hdma_tx.Instance = DMA2_Stream3;
hdma_tx.Init.Channel = DMA_CHANNEL_3;
hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_tx.Init.Mode = DMA_NORMAL;
hdma_tx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_tx);
__HAL_LINKDMA(&hspi1, hdmatx, hdma_tx);
// DMA接收流配置
hdma_rx.Instance = DMA2_Stream0;
hdma_rx.Init.Channel = DMA_CHANNEL_3;
hdma_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_rx.Init.Mode = DMA_NORMAL;
hdma_rx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_rx);
__HAL_LINKDMA(&hspi1, hdmarx, hdma_rx);
}
// DMA传输函数
void SPI_TransmitReceive_DMA(uint8_t *txBuffer, uint8_t *rxBuffer, uint16_t size)
{
// 清除完成标志
SPI_DMA_TransferComplete = 0;
// 拉低片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 启动DMA传输
HAL_SPI_TransmitReceive_DMA(&hspi1, txBuffer, rxBuffer, size);
}
// DMA传输完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
SPI_DMA_TransferComplete = 1;
// 拉高片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
}
第五章:Linux SPI驱动开发
5.1 Linux SPI子系统架构
Linux内核SPI子系统分为三层:
- SPI核心层:提供SPI总线驱动模型
- SPI控制器驱动:平台相关的SPI主机控制器驱动
- SPI设备驱动:具体的SPI外设驱动
5.2 SPI设备树配置
// SPI设备树配置示例
/*
* arch/arm/boot/dts/myboard.dts
*/
&spi1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
cs-gpios = <&gpioa 4 GPIO_ACTIVE_LOW>;
// SPI Flash设备
spiflash: w25q128@0 {
compatible = "winbond,w25q128";
reg = <0>; // 片选0
spi-max-frequency = <50000000>; // 最大50MHz
spi-cpol; // CPOL = 1
spi-cpha; // CPHA = 1
// MTD分区
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x00000000 0x00040000>; // 256KB
};
partition@40000 {
label = "kernel";
reg = <0x00040000 0x00200000>; // 2MB
};
partition@240000 {
label = "rootfs";
reg = <0x00240000 0x00dc0000>; // 14MB
};
};
};
// 另一个SPI设备
adc@1 {
compatible = "ti,ads8341";
reg = <1>; // 片选1
spi-max-frequency = <1000000>; // 最大1MHz
vref-supply = <&vref_reg>;
};
};
5.3 SPI字符设备驱动开发
// SPI设备驱动示例 - 头文件
#ifndef SPI_DEVICE_DRIVER_H
#define SPI_DEVICE_DRIVER_H
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#define DEVICE_NAME "spi_example"
#define CLASS_NAME "spi_class"
#define MAX_DEVICES 1
// 设备数据结构
struct spi_example_dev {
struct spi_device *spi;
struct cdev cdev;
dev_t devno;
struct class *class;
struct device *device;
uint8_t *buffer;
size_t buffer_size;
struct mutex lock;
};
// 设备操作函数
static int spi_example_open(struct inode *inode, struct file *filp);
static int spi_example_release(struct inode *inode, struct file *filp);
static ssize_t spi_example_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos);
static ssize_t spi_example_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos);
static long spi_example_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg);
// SPI传输函数
static int spi_example_transfer(struct spi_device *spi, uint8_t *tx_buf,
uint8_t *rx_buf, size_t len);
#endif // SPI_DEVICE_DRIVER_H
// SPI设备驱动示例 - 源文件
#include "spi_device_driver.h"
// 文件操作结构
static const struct file_operations spi_example_fops = {
.owner = THIS_MODULE,
.open = spi_example_open,
.release = spi_example_release,
.read = spi_example_read,
.write = spi_example_write,
.unlocked_ioctl = spi_example_ioctl,
};
// 全局设备结构
static struct spi_example_dev *spi_example_devices[MAX_DEVICES];
static int spi_example_major = 0;
static struct class *spi_example_class = NULL;
// 打开设备
static int spi_example_open(struct inode *inode, struct file *filp)
{
struct spi_example_dev *dev;
int minor = iminor(inode);
if (minor >= MAX_DEVICES) {
return -ENODEV;
}
dev = spi_example_devices[minor];
if (!dev) {
return -ENODEV;
}
filp->private_data = dev;
return 0;
}
// 关闭设备
static int spi_example_release(struct inode *inode, struct file *filp)
{
return 0;
}
// 读取数据
static ssize_t spi_example_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct spi_example_dev *dev = filp->private_data;
uint8_t *rx_buffer;
ssize_t retval = 0;
if (!dev || !dev->spi) {
return -ENODEV;
}
// 分配接收缓冲区
rx_buffer = kmalloc(count, GFP_KERNEL);
if (!rx_buffer) {
return -ENOMEM;
}
mutex_lock(&dev->lock);
// 执行SPI读取
retval = spi_example_transfer(dev->spi, NULL, rx_buffer, count);
if (retval < 0) {
goto out;
}
// 复制数据到用户空间
if (copy_to_user(buf, rx_buffer, count)) {
retval = -EFAULT;
goto out;
}
retval = count;
*f_pos += count;
out:
mutex_unlock(&dev->lock);
kfree(rx_buffer);
return retval;
}
// 写入数据
static ssize_t spi_example_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct spi_example_dev *dev = filp->private_data;
uint8_t *tx_buffer;
ssize_t retval = 0;
if (!dev || !dev->spi) {
return -ENODEV;
}
// 分配发送缓冲区
tx_buffer = kmalloc(count, GFP_KERNEL);
if (!tx_buffer) {
return -ENOMEM;
}
// 从用户空间复制数据
if (copy_from_user(tx_buffer, buf, count)) {
kfree(tx_buffer);
return -EFAULT;
}
mutex_lock(&dev->lock);
// 执行SPI写入
retval = spi_example_transfer(dev->spi, tx_buffer, NULL, count);
if (retval < 0) {
goto out;
}
retval = count;
*f_pos += count;
out:
mutex_unlock(&dev->lock);
kfree(tx_buffer);
return retval;
}
// IOCTL命令处理
static long spi_example_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct spi_example_dev *dev = filp->private_data;
struct spi_ioc_transfer xfer;
uint8_t *tx_buf = NULL, *rx_buf = NULL;
int retval = 0;
if (!dev || !dev->spi) {
return -ENODEV;
}
switch (cmd) {
case SPI_EXAMPLE_IOCTL_XFER:
if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) {
return -EFAULT;
}
// 分配缓冲区
if (xfer.tx_buf) {
tx_buf = kmalloc(xfer.len, GFP_KERNEL);
if (!tx_buf) {
return -ENOMEM;
}
if (copy_from_user(tx_buf, (void __user *)xfer.tx_buf, xfer.len)) {
kfree(tx_buf);
return -EFAULT;
}
}
if (xfer.rx_buf) {
rx_buf = kmalloc(xfer.len, GFP_KERNEL);
if (!rx_buf) {
kfree(tx_buf);
return -ENOMEM;
}
}
// 执行传输
mutex_lock(&dev->lock);
retval = spi_example_transfer(dev->spi, tx_buf, rx_buf, xfer.len);
mutex_unlock(&dev->lock);
// 复制接收数据回用户空间
if (rx_buf && xfer.rx_buf) {
if (copy_to_user((void __user *)xfer.rx_buf, rx_buf, xfer.len)) {
retval = -EFAULT;
}
}
kfree(tx_buf);
kfree(rx_buf);
break;
default:
return -ENOTTY;
}
return retval;
}
// SPI传输函数
static int spi_example_transfer(struct spi_device *spi, uint8_t *tx_buf,
uint8_t *rx_buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
.delay_usecs = 10,
.speed_hz = spi->max_speed_hz,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
// SPI设备探测函数
static int spi_example_probe(struct spi_device *spi)
{
struct spi_example_dev *dev;
int retval, minor;
// 分配设备结构
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
return -ENOMEM;
}
// 初始化设备
dev->spi = spi;
mutex_init(&dev->lock);
// 配置SPI设备
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi->max_speed_hz = 1000000; // 1MHz
retval = spi_setup(spi);
if (retval < 0) {
dev_err(&spi->dev, "Failed to setup SPI device\n");
goto error;
}
// 分配设备号
for (minor = 0; minor < MAX_DEVICES; minor++) {
if (!spi_example_devices[minor]) {
break;
}
}
if (minor == MAX_DEVICES) {
retval = -ENODEV;
goto error;
}
dev->devno = MKDEV(spi_example_major, minor);
// 初始化字符设备
cdev_init(&dev->cdev, &spi_example_fops);
dev->cdev.owner = THIS_MODULE;
retval = cdev_add(&dev->cdev, dev->devno, 1);
if (retval) {
dev_err(&spi->dev, "Failed to add character device\n");
goto error;
}
// 创建设备节点
dev->device = device_create(spi_example_class, &spi->dev,
dev->devno, NULL, "spi_example%d", minor);
if (IS_ERR(dev->device)) {
retval = PTR_ERR(dev->device);
goto error_cdev;
}
spi_example_devices[minor] = dev;
spi_set_drvdata(spi, dev);
dev_info(&spi->dev, "SPI example device registered\n");
return 0;
error_cdev:
cdev_del(&dev->cdev);
error:
kfree(dev);
return retval;
}
// SPI设备移除函数
static int spi_example_remove(struct spi_device *spi)
{
struct spi_example_dev *dev = spi_get_drvdata(spi);
int minor;
if (!dev) {
return -ENODEV;
}
// 查找设备索引
for (minor = 0; minor < MAX_DEVICES; minor++) {
if (spi_example_devices[minor] == dev) {
break;
}
}
if (minor < MAX_DEVICES) {
spi_example_devices[minor] = NULL;
}
// 销毁设备
if (dev->device) {
device_destroy(spi_example_class, dev->devno);
}
cdev_del(&dev->cdev);
kfree(dev);
dev_info(&spi->dev, "SPI example device removed\n");
return 0;
}
// SPI设备ID表
static const struct spi_device_id spi_example_ids[] = {
{ "spi-example", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, spi_example_ids);
// SPI设备匹配表
static const struct of_device_id spi_example_of_match[] = {
{ .compatible = "example,spi-device" },
{ }
};
MODULE_DEVICE_TABLE(of, spi_example_of_match);
// SPI驱动结构
static struct spi_driver spi_example_driver = {
.driver = {
.name = "spi_example",
.owner = THIS_MODULE,
.of_match_table = spi_example_of_match,
},
.probe = spi_example_probe,
.remove = spi_example_remove,
.id_table = spi_example_ids,
};
// 模块初始化
static int __init spi_example_init(void)
{
int retval;
// 分配主设备号
retval = alloc_chrdev_region(&spi_example_devices[0]->devno, 0,
MAX_DEVICES, DEVICE_NAME);
if (retval < 0) {
pr_err("Failed to allocate device numbers\n");
return retval;
}
spi_example_major = MAJOR(spi_example_devices[0]->devno);
// 创建设备类
spi_example_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(spi_example_class)) {
retval = PTR_ERR(spi_example_class);
unregister_chrdev_region(spi_example_devices[0]->devno, MAX_DEVICES);
return retval;
}
// 注册SPI驱动
retval = spi_register_driver(&spi_example_driver);
if (retval < 0) {
class_destroy(spi_example_class);
unregister_chrdev_region(spi_example_devices[0]->devno, MAX_DEVICES);
return retval;
}
pr_info("SPI example driver loaded\n");
return 0;
}
// 模块清理
static void __exit spi_example_exit(void)
{
spi_unregister_driver(&spi_example_driver);
class_destroy(spi_example_class);
unregister_chrdev_region(spi_example_devices[0]->devno, MAX_DEVICES);
pr_info("SPI example driver unloaded\n");
}
module_init(spi_example_init);
module_exit(spi_example_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("SPI Example Device Driver");
MODULE_LICENSE("GPL");
5.4 SPI用户空间编程
// SPI用户空间测试程序
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
// SPI设备路径
#define SPI_DEVICE "/dev/spidev0.0"
// SPI配置结构
static uint8_t mode = SPI_MODE_0;
static uint8_t bits = 8;
static uint32_t speed = 1000000; // 1MHz
static uint16_t delay = 10;
// SPI传输函数
static int spi_transfer(int fd, uint8_t *tx, uint8_t *rx, size_t len)
{
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}
// 配置SPI参数
static int spi_config(int fd)
{
int ret;
// 设置SPI模式
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1) {
perror("Can't set SPI mode");
return -1;
}
// 设置数据位宽
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1) {
perror("Can't set bits per word");
return -1;
}
// 设置时钟速度
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1) {
perror("Can't set max speed");
return -1;
}
return 0;
}
// 读取SPI参数
static void spi_print_config(int fd)
{
uint8_t mode_r, bits_r;
uint32_t speed_r;
ioctl(fd, SPI_IOC_RD_MODE, &mode_r);
ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits_r);
ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed_r);
printf("SPI configuration:\n");
printf(" Mode: %d\n", mode_r);
printf(" Bits per word: %d\n", bits_r);
printf(" Max speed: %d Hz\n", speed_r);
}
// 主函数
int main(int argc, char *argv[])
{
int fd;
uint8_t tx_buffer[32], rx_buffer[32];
int ret, i;
// 打开SPI设备
fd = open(SPI_DEVICE, O_RDWR);
if (fd < 0) {
perror("Can't open SPI device");
return -1;
}
// 配置SPI
if (spi_config(fd) < 0) {
close(fd);
return -1;
}
// 打印配置
spi_print_config(fd);
// 准备测试数据
memset(tx_buffer, 0, sizeof(tx_buffer));
memset(rx_buffer, 0, sizeof(rx_buffer));
for (i = 0; i < sizeof(tx_buffer); i++) {
tx_buffer[i] = i;
}
// 执行SPI传输
printf("\nSending data: ");
for (i = 0; i < 16; i++) {
printf("%02X ", tx_buffer[i]);
}
printf("\n");
ret = spi_transfer(fd, tx_buffer, rx_buffer, sizeof(tx_buffer));
if (ret < 0) {
perror("SPI transfer failed");
close(fd);
return -1;
}
// 打印接收到的数据
printf("Received data: ");
for (i = 0; i < 16; i++) {
printf("%02X ", rx_buffer[i]);
}
printf("\n");
// 关闭设备
close(fd);
return 0;
}
第六章:SPI高级应用与优化
6.1 SPI性能优化技巧
6.1.1 时钟频率优化
// 动态时钟调整
void optimize_spi_clock(SPI_HandleTypeDef *hspi, uint32_t desired_freq)
{
uint32_t apb_clock = HAL_RCC_GetPCLK2Freq(); // 获取APB时钟
uint32_t prescaler = 0;
// 计算最佳分频系数
if (desired_freq >= apb_clock / 2) {
prescaler = SPI_BAUDRATEPRESCALER_2;
} else if (desired_freq >= apb_clock / 4) {
prescaler = SPI_BAUDRATEPRESCALER_4;
} else if (desired_freq >= apb_clock / 8) {
prescaler = SPI_BAUDRATEPRESCALER_8;
} else if (desired_freq >= apb_clock / 16) {
prescaler = SPI_BAUDRATEPRESCALER_16;
} else if (desired_freq >= apb_clock / 32) {
prescaler = SPI_BAUDRATEPRESCALER_32;
} else if (desired_freq >= apb_clock / 64) {
prescaler = SPI_BAUDRATEPRESCALER_64;
} else if (desired_freq >= apb_clock / 128) {
prescaler = SPI_BAUDRATEPRESCALER_128;
} else {
prescaler = SPI_BAUDRATEPRESCALER_256;
}
// 更新SPI配置
hspi->Init.BaudRatePrescaler = prescaler;
HAL_SPI_Init(hspi);
// 计算实际频率
uint32_t actual_freq = apb_clock / (1 << (prescaler >> 3));
printf("SPI Clock: Desired=%luHz, Actual=%luHz\n",
desired_freq, actual_freq);
}
6.1.2 DMA双缓冲技术
// DMA双缓冲实现
#define BUFFER_SIZE 1024
typedef struct {
uint8_t buffer1[BUFFER_SIZE];
uint8_t buffer2[BUFFER_SIZE];
uint8_t *current_tx;
uint8_t *current_rx;
volatile uint8_t buffer_ready;
size_t data_size;
} DoubleBuffer;
DoubleBuffer dma_buffer;
void init_double_buffer(void)
{
memset(&dma_buffer, 0, sizeof(DoubleBuffer));
dma_buffer.current_tx = dma_buffer.buffer1;
dma_buffer.current_rx = dma_buffer.buffer1;
dma_buffer.buffer_ready = 0;
}
void spi_dma_double_buffer_transfer(SPI_HandleTypeDef *hspi,
uint8_t *data, size_t size)
{
static uint8_t active_buffer = 0;
// 等待当前传输完成
while (dma_buffer.buffer_ready == 0) {
// 可以在这里处理其他任务
}
// 准备下一个缓冲区
uint8_t *next_tx = (active_buffer == 0) ?
dma_buffer.buffer1 : dma_buffer.buffer2;
memcpy(next_tx, data, size);
dma_buffer.data_size = size;
dma_buffer.buffer_ready = 0;
// 启动DMA传输
if (active_buffer == 0) {
HAL_SPI_TransmitReceive_DMA(hspi,
dma_buffer.buffer1,
dma_buffer.buffer1,
size);
active_buffer = 1;
} else {
HAL_SPI_TransmitReceive_DMA(hspi,
dma_buffer.buffer2,
dma_buffer.buffer2,
size);
active_buffer = 0;
}
}
// DMA传输完成回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
dma_buffer.buffer_ready = 1;
// 处理接收到的数据
process_received_data(dma_buffer.current_rx, dma_buffer.data_size);
// 切换缓冲区
dma_buffer.current_rx = (dma_buffer.current_rx == dma_buffer.buffer1) ?
dma_buffer.buffer2 : dma_buffer.buffer1;
}
6.2 SPI错误处理与调试
6.2.1 错误检测与恢复
// SPI错误处理
typedef enum {
SPI_ERROR_NONE = 0,
SPI_ERROR_TIMEOUT,
SPI_ERROR_CRC,
SPI_ERROR_OVERRUN,
SPI_ERROR_FRAME,
SPI_ERROR_DMA,
SPI_ERROR_BUSY
} SPI_ErrorType;
typedef struct {
uint32_t total_transfers;
uint32_t error_count;
uint32_t timeout_count;
uint32_t crc_error_count;
SPI_ErrorType last_error;
} SPI_ErrorStats;
SPI_ErrorStats spi_stats;
void spi_error_handler(SPI_HandleTypeDef *hspi, SPI_ErrorType error)
{
spi_stats.error_count++;
spi_stats.last_error = error;
switch (error) {
case SPI_ERROR_TIMEOUT:
spi_stats.timeout_count++;
printf("SPI Timeout Error\n");
break;
case SPI_ERROR_CRC:
spi_stats.crc_error_count++;
printf("SPI CRC Error\n");
break;
case SPI_ERROR_OVERRUN:
printf("SPI Overrun Error\n");
HAL_SPI_Abort(hspi);
HAL_SPI_Init(hspi);
break;
case SPI_ERROR_FRAME:
printf("SPI Frame Error\n");
break;
default:
printf("SPI Unknown Error\n");
break;
}
// 记录调试信息
log_spi_error(hspi, error);
// 尝试恢复
spi_recover(hspi);
}
void spi_recover(SPI_HandleTypeDef *hspi)
{
// 1. 停止当前传输
HAL_SPI_Abort(hspi);
// 2. 重新初始化SPI
HAL_SPI_DeInit(hspi);
HAL_SPI_Init(hspi);
// 3. 重置DMA(如果使用)
if (hspi->hdmatx) {
HAL_DMA_DeInit(hspi->hdmatx);
HAL_DMA_Init(hspi->hdmatx);
}
if (hspi->hdmarx) {
HAL_DMA_DeInit(hspi->hdmarx);
HAL_DMA_Init(hspi->hdmarx);
}
// 4. 重新配置SPI
__HAL_SPI_ENABLE(hspi);
printf("SPI recovered successfully\n");
}
// 带错误处理的SPI传输
HAL_StatusTypeDef spi_transfer_with_retry(SPI_HandleTypeDef *hspi,
uint8_t *tx_data,
uint8_t *rx_data,
uint16_t size,
uint32_t timeout,
uint8_t max_retries)
{
HAL_StatusTypeDef status;
uint8_t retry_count = 0;
do {
status = HAL_SPI_TransmitReceive(hspi, tx_data, rx_data, size, timeout);
if (status == HAL_OK) {
spi_stats.total_transfers++;
return HAL_OK;
}
retry_count++;
// 处理特定错误
if (status == HAL_TIMEOUT) {
spi_error_handler(hspi, SPI_ERROR_TIMEOUT);
} else if (status == HAL_ERROR) {
// 检查具体错误标志
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR)) {
spi_error_handler(hspi, SPI_ERROR_CRC);
__HAL_SPI_CLEAR_CRCERRFLAG(hspi);
} else if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_OVR)) {
spi_error_handler(hspi, SPI_ERROR_OVERRUN);
__HAL_SPI_CLEAR_OVRFLAG(hspi);
}
}
// 短暂延时后重试
HAL_Delay(10);
} while (retry_count < max_retries);
return status;
}
6.2.2 SPI调试工具
// SPI调试信息收集
typedef struct {
uint32_t timestamp;
uint8_t tx_data[16];
uint8_t rx_data[16];
uint16_t data_size;
SPI_ErrorType error;
uint32_t clock_freq;
uint8_t mode;
} SPI_DebugEntry;
#define DEBUG_BUFFER_SIZE 100
SPI_DebugEntry debug_buffer[DEBUG_BUFFER_SIZE];
uint32_t debug_index = 0;
void log_spi_transaction(uint8_t *tx_data, uint8_t *rx_data,
uint16_t size, SPI_ErrorType error)
{
uint32_t index = debug_index % DEBUG_BUFFER_SIZE;
debug_buffer[index].timestamp = HAL_GetTick();
debug_buffer[index].data_size = size;
debug_buffer[index].error = error;
debug_buffer[index].clock_freq = hspi1.Instance->CR1 & SPI_CR1_BR;
debug_buffer[index].mode = (hspi1.Instance->CR1 & (SPI_CR1_CPOL | SPI_CR1_CPHA)) >> 1;
// 记录数据(最多16字节)
uint16_t copy_size = (size > 16) ? 16 : size;
memcpy(debug_buffer[index].tx_data, tx_data, copy_size);
memcpy(debug_buffer[index].rx_data, rx_data, copy_size);
debug_index++;
}
void print_spi_debug_info(void)
{
printf("=== SPI Debug Information ===\n");
printf("Total transfers: %lu\n", spi_stats.total_transfers);
printf("Error count: %lu\n", spi_stats.error_count);
printf("Timeout count: %lu\n", spi_stats.timeout_count);
printf("CRC error count: %lu\n", spi_stats.crc_error_count);
printf("\nLast %d transactions:\n",
(debug_index > DEBUG_BUFFER_SIZE) ? DEBUG_BUFFER_SIZE : debug_index);
uint32_t start = (debug_index > DEBUG_BUFFER_SIZE) ?
debug_index - DEBUG_BUFFER_SIZE : 0;
for (uint32_t i = start; i < debug_index; i++) {
uint32_t idx = i % DEBUG_BUFFER_SIZE;
printf("[%lu] Time: %lums, Size: %u, Error: %d\n",
i, debug_buffer[idx].timestamp,
debug_buffer[idx].data_size,
debug_buffer[idx].error);
if (debug_buffer[idx].data_size > 0) {
printf(" TX: ");
for (uint16_t j = 0; j < debug_buffer[idx].data_size && j < 16; j++) {
printf("%02X ", debug_buffer[idx].tx_data[j]);
}
printf("\n RX: ");
for (uint16_t j = 0; j < debug_buffer[idx].data_size && j < 16; j++) {
printf("%02X ", debug_buffer[idx].rx_data[j]);
}
printf("\n");
}
}
}
// 实时SPI信号监控(需要逻辑分析仪或示波器接口)
void monitor_spi_signals(void)
{
// 配置GPIO为输入模式,用于监控
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 监控引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK, MISO, MOSI
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 采样SPI信号
uint32_t samples[1000];
for (int i = 0; i < 1000; i++) {
samples[i] = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) << 2 | // SCK
HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) << 1 | // MISO
HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7); // MOSI
HAL_Delay(1); // 1ms采样间隔
}
// 分析信号
analyze_spi_waveform(samples, 1000);
}
6.3 SPI在物联网中的应用
6.3.1 SPI连接传感器
// 温湿度传感器DHT12 SPI接口
#define DHT12_READ_CMD 0x00
#define DHT12_WRITE_CMD 0x80
typedef struct {
float temperature;
float humidity;
uint8_t checksum;
} DHT12_Data;
HAL_StatusTypeDef dht12_read_sensor(SPI_HandleTypeDef *hspi, DHT12_Data *data)
{
uint8_t tx_buffer[5] = {0};
uint8_t rx_buffer[5] = {0};
// 发送读取命令
tx_buffer[0] = DHT12_READ_CMD;
// 选择传感器
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 传输数据
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(hspi, tx_buffer,
rx_buffer, 5, 100);
// 取消选择
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
if (status == HAL_OK) {
// 解析数据
data->humidity = rx_buffer[1] + rx_buffer[2] * 0.1;
data->temperature = rx_buffer[3] + rx_buffer[4] * 0.1;
// 验证校验和
uint8_t checksum = rx_buffer[1] + rx_buffer[2] + rx_buffer[3] + rx_buffer[4];
data->checksum = (checksum == rx_buffer[0]);
if (!data->checksum) {
return HAL_ERROR;
}
}
return status;
}
// 批量读取传感器数据
void read_multiple_sensors(void)
{
DHT12_Data sensor_data[10];
uint32_t timestamps[10];
for (int i = 0; i < 10; i++) {
timestamps[i] = HAL_GetTick();
if (dht12_read_sensor(&hspi1, &sensor_data[i]) == HAL_OK) {
printf("Sensor %d: Temp=%.1fC, Hum=%.1f%%\n",
i, sensor_data[i].temperature, sensor_data[i].humidity);
// 上传到云平台
upload_to_cloud(sensor_data[i], timestamps[i]);
} else {
printf("Sensor %d: Read failed\n", i);
}
HAL_Delay(2000); // 2秒间隔
}
// 数据分析
analyze_sensor_data(sensor_data, 10);
}
6.3.2 SPI连接无线模块
// NRF24L01+无线模块SPI驱动
#define NRF24_CMD_R_REGISTER 0x00
#define NRF24_CMD_W_REGISTER 0x20
#define NRF24_CMD_R_RX_PAYLOAD 0x61
#define NRF24_CMD_W_TX_PAYLOAD 0xA0
#define NRF24_CMD_FLUSH_TX 0xE1
#define NRF24_CMD_FLUSH_RX 0xE2
typedef struct {
SPI_HandleTypeDef *spi;
GPIO_TypeDef *ce_port;
uint16_t ce_pin;
GPIO_TypeDef *csn_port;
uint16_t csn_pin;
uint8_t channel;
uint8_t payload_size;
} NRF24_Handle;
// NRF24L01+初始化
HAL_StatusTypeDef nrf24_init(NRF24_Handle *nrf)
{
uint8_t config = 0;
// 1. 配置CE和CSN引脚
nrf24_csn_low(nrf);
// 2. 写入配置寄存器
config = (1 << 1) | // PRX: 接收模式
(1 << 0); // PWR_UP: 上电
nrf24_write_register(nrf, 0x00, &config, 1);
// 3. 启用自动应答
uint8_t en_aa = 0x01; // 启用数据通道0自动应答
nrf24_write_register(nrf, 0x01, &en_aa, 1);
// 4. 启用接收地址
uint8_t en_rxaddr = 0x01; // 启用数据通道0
nrf24_write_register(nrf, 0x02, &en_rxaddr, 1);
// 5. 设置地址宽度
uint8_t setup_aw = 0x03; // 5字节地址
nrf24_write_register(nrf, 0x03, &setup_aw, 1);
// 6. 设置重传
uint8_t setup_retr = (0x01 << 4) | 0x0F; // 重传延迟250us,重试15次
nrf24_write_register(nrf, 0x04, &setup_retr, 1);
// 7. 设置射频通道
nrf24_write_register(nrf, 0x05, &nrf->channel, 1);
// 8. 设置射频参数
uint8_t rf_setup = (0x01 << 5) | // RF_PWR: 0dBm
(0x01 << 3) | // RF_DR_HIGH: 1Mbps
(0x00 << 2); // LNA_HCURR: 默认
nrf24_write_register(nrf, 0x06, &rf_setup, 1);
nrf24_csn_high(nrf);
// 9. 清空中断
uint8_t status = 0;
nrf24_get_status(nrf, &status);
return HAL_OK;
}
// SPI读写函数
uint8_t nrf24_read_register(NRF24_Handle *nrf, uint8_t reg,
uint8_t *data, uint8_t len)
{
uint8_t status;
uint8_t tx_buffer[32], rx_buffer[32];
tx_buffer[0] = NRF24_CMD_R_REGISTER | reg;
memset(&tx_buffer[1], 0xFF, len);
nrf24_csn_low(nrf);
HAL_SPI_TransmitReceive(nrf->spi, tx_buffer, rx_buffer, len + 1, 100);
nrf24_csn_high(nrf);
status = rx_buffer[0];
memcpy(data, &rx_buffer[1], len);
return status;
}
uint8_t nrf24_write_register(NRF24_Handle *nrf, uint8_t reg,
uint8_t *data, uint8_t len)
{
uint8_t status;
uint8_t tx_buffer[32], rx_buffer[32];
tx_buffer[0] = NRF24_CMD_W_REGISTER | reg;
memcpy(&tx_buffer[1], data, len);
nrf24_csn_low(nrf);
HAL_SPI_TransmitReceive(nrf->spi, tx_buffer, rx_buffer, len + 1, 100);
nrf24_csn_high(nrf);
status = rx_buffer[0];
return status;
}
// 发送数据
uint8_t nrf24_send_packet(NRF24_Handle *nrf, uint8_t *data, uint8_t len)
{
uint8_t status;
uint8_t tx_buffer[32];
// 切换到发送模式
nrf24_ce_low(nrf);
// 写入发送数据
tx_buffer[0] = NRF24_CMD_W_TX_PAYLOAD;
memcpy(&tx_buffer[1], data, len);
nrf24_csn_low(nrf);
HAL_SPI_Transmit(nrf->spi, tx_buffer, len + 1, 100);
nrf24_csn_high(nrf);
// 启动发送
nrf24_ce_high(nrf);
HAL_Delay(1); // 至少10us
nrf24_ce_low(nrf);
// 等待发送完成
HAL_Delay(10);
// 获取状态
nrf24_get_status(nrf, &status);
// 清空中断标志
if (status & (1 << 5)) { // TX_DS
nrf24_write_register(nrf, 0x07, &status, 1);
}
return status;
}
// 接收数据
uint8_t nrf24_receive_packet(NRF24_Handle *nrf, uint8_t *data, uint8_t *len)
{
uint8_t status;
uint8_t rx_buffer[32];
// 获取状态
nrf24_get_status(nrf, &status);
if (status & (1 << 6)) { // RX_DR
// 读取数据
rx_buffer[0] = NRF24_CMD_R_RX_PAYLOAD;
nrf24_csn_low(nrf);
HAL_SPI_TransmitReceive(nrf->spi, rx_buffer, rx_buffer,
nrf->payload_size + 1, 100);
nrf24_csn_high(nrf);
memcpy(data, &rx_buffer[1], nrf->payload_size);
*len = nrf->payload_size;
// 清空中断标志
nrf24_write_register(nrf, 0x07, &status, 1);
return 1;
}
return 0;
}
第七章:SPI通信实战案例
7.1 基于SPI的OLED显示屏驱动
// SSD1306 OLED显示屏SPI驱动
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_PAGES (OLED_HEIGHT / 8)
// SSD1306命令定义
#define OLED_CMD_SET_CONTRAST 0x81
#define OLED_CMD_DISPLAY_ALL_ON_RESUME 0xA4
#define OLED_CMD_DISPLAY_ALL_ON 0xA5
#define OLED_CMD_NORMAL_DISPLAY 0xA6
#define OLED_CMD_INVERT_DISPLAY 0xA7
#define OLED_CMD_DISPLAY_OFF 0xAE
#define OLED_CMD_DISPLAY_ON 0xAF
#define OLED_CMD_SET_DISPLAY_OFFSET 0xD3
#define OLED_CMD_SET_COM_PINS 0xDA
#define OLED_CMD_SET_VCOM_DETECT 0xDB
#define OLED_CMD_SET_DISPLAY_CLOCK_DIV 0xD5
#define OLED_CMD_SET_PRECHARGE 0xD9
#define OLED_CMD_SET_MULTIPLEX 0xA8
#define OLED_CMD_SET_LOW_COLUMN 0x00
#define OLED_CMD_SET_HIGH_COLUMN 0x10
#define OLED_CMD_SET_START_LINE 0x40
#define OLED_CMD_MEMORY_MODE 0x20
#define OLED_CMD_COLUMN_ADDR 0x21
#define OLED_CMD_PAGE_ADDR 0x22
#define OLED_CMD_COM_SCAN_INC 0xC0
#define OLED_CMD_COM_SCAN_DEC 0xC8
#define OLED_CMD_SEG_REMAP 0xA0
#define OLED_CMD_CHARGE_PUMP 0x8D
typedef struct {
SPI_HandleTypeDef *spi;
GPIO_TypeDef *dc_port; // 数据/命令选择
uint16_t dc_pin;
GPIO_TypeDef *res_port; // 复位
uint16_t res_pin;
GPIO_TypeDef *cs_port; // 片选
uint16_t cs_pin;
uint8_t buffer[OLED_PAGES][OLED_WIDTH];
} OLED_Handle;
// OLED初始化
void oled_init(OLED_Handle *oled)
{
// 硬件复位
HAL_GPIO_WritePin(oled->res_port, oled->res_pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(oled->res_port, oled->res_pin, GPIO_PIN_SET);
HAL_Delay(10);
// 发送初始化命令序列
oled_send_command(oled, OLED_CMD_DISPLAY_OFF);
oled_send_command(oled, OLED_CMD_SET_DISPLAY_CLOCK_DIV);
oled_send_command(oled, 0x80); // 建议值
oled_send_command(oled, OLED_CMD_SET_MULTIPLEX);
oled_send_command(oled, 0x3F); // 64-1
oled_send_command(oled, OLED_CMD_SET_DISPLAY_OFFSET);
oled_send_command(oled, 0x00); // 无偏移
oled_send_command(oled, OLED_CMD_SET_START_LINE | 0x00);
oled_send_command(oled, OLED_CMD_CHARGE_PUMP);
oled_send_command(oled, 0x14); // 启用电荷泵
oled_send_command(oled, OLED_CMD_MEMORY_MODE);
oled_send_command(oled, 0x00); // 水平寻址模式
oled_send_command(oled, OLED_CMD_SEG_REMAP | 0x01); // 列地址127映射到SEG0
oled_send_command(oled, OLED_CMD_COM_SCAN_DEC); // 扫描方向
oled_send_command(oled, OLED_CMD_SET_COM_PINS);
oled_send_command(oled, 0x12); // 引脚配置
oled_send_command(oled, OLED_CMD_SET_CONTRAST);
oled_send_command(oled, 0x7F); // 对比度
oled_send_command(oled, OLED_CMD_SET_PRECHARGE);
oled_send_command(oled, 0xF1); // 预充电周期
oled_send_command(oled, OLED_CMD_SET_VCOM_DETECT);
oled_send_command(oled, 0x40); // VCOMH电平
oled_send_command(oled, OLED_CMD_DISPLAY_ALL_ON_RESUME);
oled_send_command(oled, OLED_CMD_NORMAL_DISPLAY);
oled_send_command(oled, OLED_CMD_DISPLAY_ON);
// 清屏
oled_clear(oled);
oled_update(oled);
}
// 发送命令
void oled_send_command(OLED_Handle *oled, uint8_t cmd)
{
HAL_GPIO_WritePin(oled->dc_port, oled->dc_pin, GPIO_PIN_RESET); // 命令模式
HAL_GPIO_WritePin(oled->cs_port, oled->cs_pin, GPIO_PIN_RESET); // 选择OLED
HAL_SPI_Transmit(oled->spi, &cmd, 1, 100);
HAL_GPIO_WritePin(oled->cs_port, oled->cs_pin, GPIO_PIN_SET); // 取消选择
}
// 发送数据
void oled_send_data(OLED_Handle *oled, uint8_t *data, uint16_t size)
{
HAL_GPIO_WritePin(oled->dc_port, oled->dc_pin, GPIO_PIN_SET); // 数据模式
HAL_GPIO_WritePin(oled->cs_port, oled->cs_pin, GPIO_PIN_RESET); // 选择OLED
HAL_SPI_Transmit(oled->spi, data, size, 1000);
HAL_GPIO_WritePin(oled->cs_port, oled->cs_pin, GPIO_PIN_SET); // 取消选择
}
// 清屏
void oled_clear(OLED_Handle *oled)
{
memset(oled->buffer, 0, sizeof(oled->buffer));
}
// 更新显示
void oled_update(OLED_Handle *oled)
{
for (uint8_t page = 0; page < OLED_PAGES; page++) {
// 设置页地址
oled_send_command(oled, 0xB0 + page); // 设置页起始地址
oled_send_command(oled, OLED_CMD_SET_LOW_COLUMN); // 设置列低地址
oled_send_command(oled, OLED_CMD_SET_HIGH_COLUMN); // 设置列高地址
// 发送该页数据
oled_send_data(oled, oled->buffer[page], OLED_WIDTH);
}
}
// 绘制像素
void oled_draw_pixel(OLED_Handle *oled, uint8_t x, uint8_t y, uint8_t color)
{
if (x >= OLED_WIDTH || y >= OLED_HEIGHT) {
return;
}
uint8_t page = y / 8;
uint8_t bit = y % 8;
if (color) {
oled->buffer[page][x] |= (1 << bit);
} else {
oled->buffer[page][x] &= ~(1 << bit);
}
}
// 绘制直线
void oled_draw_line(OLED_Handle *oled, uint8_t x0, uint8_t y0,
uint8_t x1, uint8_t y1, uint8_t color)
{
int16_t dx = abs(x1 - x0);
int16_t dy = abs(y1 - y0);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
int16_t e2;
while (1) {
oled_draw_pixel(oled, x0, y0, color);
if (x0 == x1 && y0 == y1) {
break;
}
e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
// 绘制矩形
void oled_draw_rectangle(OLED_Handle *oled, uint8_t x, uint8_t y,
uint8_t width, uint8_t height, uint8_t color)
{
// 上边
oled_draw_line(oled, x, y, x + width - 1, y, color);
// 下边
oled_draw_line(oled, x, y + height - 1, x + width - 1, y + height - 1, color);
// 左边
oled_draw_line(oled, x, y, x, y + height - 1, color);
// 右边
oled_draw_line(oled, x + width - 1, y, x + width - 1, y + height - 1, color);
}
// 绘制填充矩形
void oled_fill_rectangle(OLED_Handle *oled, uint8_t x, uint8_t y,
uint8_t width, uint8_t height, uint8_t color)
{
for (uint8_t i = 0; i < height; i++) {
oled_draw_line(oled, x, y + i, x + width - 1, y + i, color);
}
}
// 绘制圆形
void oled_draw_circle(OLED_Handle *oled, uint8_t x0, uint8_t y0,
uint8_t radius, uint8_t color)
{
int16_t f = 1 - radius;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * radius;
int16_t x = 0;
int16_t y = radius;
oled_draw_pixel(oled, x0, y0 + radius, color);
oled_draw_pixel(oled, x0, y0 - radius, color);
oled_draw_pixel(oled, x0 + radius, y0, color);
oled_draw_pixel(oled, x0 - radius, y0, color);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
oled_draw_pixel(oled, x0 + x, y0 + y, color);
oled_draw_pixel(oled, x0 - x, y0 + y, color);
oled_draw_pixel(oled, x0 + x, y0 - y, color);
oled_draw_pixel(oled, x0 - x, y0 - y, color);
oled_draw_pixel(oled, x0 + y, y0 + x, color);
oled_draw_pixel(oled, x0 - y, y0 + x, color);
oled_draw_pixel(oled, x0 + y, y0 - x, color);
oled_draw_pixel(oled, x0 - y, y0 - x, color);
}
}
// 显示字符
void oled_draw_char(OLED_Handle *oled, uint8_t x, uint8_t y,
char ch, const uint8_t *font, uint8_t color)
{
uint8_t font_width = font[0];
uint8_t font_height = font[1];
uint8_t bytes_per_char = font[2];
// 计算字符在字体数据中的位置
uint16_t index = 3 + (ch - 32) * bytes_per_char;
for (uint8_t i = 0; i < font_width; i++) {
uint8_t column_data = font[index + i];
for (uint8_t j = 0; j < font_height; j++) {
if (column_data & (1 << j)) {
oled_draw_pixel(oled, x + i, y + j, color);
}
}
}
}
// 显示字符串
void oled_draw_string(OLED_Handle *oled, uint8_t x, uint8_t y,
const char *str, const uint8_t *font, uint8_t color)
{
uint8_t font_width = font[0];
uint8_t cursor_x = x;
while (*str) {
oled_draw_char(oled, cursor_x, y, *str, font, color);
cursor_x += font_width + 1; // 字符宽度加间距
str++;
// 检查是否超出屏幕宽度
if (cursor_x + font_width >= OLED_WIDTH) {
break;
}
}
}
// 显示位图
void oled_draw_bitmap(OLED_Handle *oled, uint8_t x, uint8_t y,
const uint8_t *bitmap, uint8_t width, uint8_t height,
uint8_t color)
{
for (uint8_t j = 0; j < height; j++) {
for (uint8_t i = 0; i < width; i++) {
if (bitmap[j * width + i]) {
oled_draw_pixel(oled, x + i, y + j, color);
}
}
}
}
// 示例:创建简单UI
void oled_create_ui(OLED_Handle *oled)
{
// 清屏
oled_clear(oled);
// 绘制边框
oled_draw_rectangle(oled, 0, 0, OLED_WIDTH, OLED_HEIGHT, 1);
// 绘制标题栏
oled_fill_rectangle(oled, 1, 1, OLED_WIDTH - 2, 12, 1);
// 显示标题(反色显示)
const uint8_t font_6x8[] = {6, 8, 6, /* 字体数据 */};
oled_draw_string(oled, 10, 3, "SPI OLED Demo", font_6x8, 0);
// 绘制分割线
oled_draw_line(oled, 1, 15, OLED_WIDTH - 2, 15, 1);
// 显示传感器数据
oled_draw_string(oled, 5, 20, "Temperature: 25.5C", font_6x8, 1);
oled_draw_string(oled, 5, 30, "Humidity: 60.0%", font_6x8, 1);
oled_draw_string(oled, 5, 40, "Pressure: 1013 hPa", font_6x8, 1);
// 绘制电池图标
oled_draw_rectangle(oled, OLED_WIDTH - 25, 3, 20, 8, 1);
oled_fill_rectangle(oled, OLED_WIDTH - 24, 4, 15, 6, 1); // 电池电量75%
oled_fill_rectangle(oled, OLED_WIDTH - 5, 5, 2, 4, 1); // 电池正极
// 绘制信号强度指示
for (int i = 0; i < 4; i++) {
oled_fill_rectangle(oled, OLED_WIDTH - 40 + i * 3,
10 - i * 2, 2, i * 2 + 2, 1);
}
// 更新显示
oled_update(oled);
}
// 动画示例:进度条
void oled_show_progress_bar(OLED_Handle *oled, uint8_t progress)
{
uint8_t bar_width = 100;
uint8_t bar_height = 8;
uint8_t bar_x = (OLED_WIDTH - bar_width) / 2;
uint8_t bar_y = (OLED_HEIGHT - bar_height) / 2;
// 清除进度条区域
oled_fill_rectangle(oled, bar_x - 1, bar_y - 1,
bar_width + 2, bar_height + 2, 0);
// 绘制外框
oled_draw_rectangle(oled, bar_x, bar_y, bar_width, bar_height, 1);
// 绘制进度
uint8_t fill_width = (progress * bar_width) / 100;
if (fill_width > 0) {
oled_fill_rectangle(oled, bar_x + 1, bar_y + 1,
fill_width - 2, bar_height - 2, 1);
}
// 显示百分比
char progress_str[10];
sprintf(progress_str, "%d%%", progress);
const uint8_t font_6x8[] = {6, 8, 6, /* 字体数据 */};
oled_draw_string(oled, bar_x + bar_width + 5, bar_y,
progress_str, font_6x8, 1);
oled_update(oled);
}
// 动画示例:滚动文本
void oled_scroll_text(OLED_Handle *oled, const char *text,
uint8_t speed_ms)
{
uint8_t text_width = strlen(text) * 7; // 假设每个字符6像素宽+1像素间距
uint8_t start_x = OLED_WIDTH;
while (start_x > -text_width) {
// 清除显示区域
oled_fill_rectangle(oled, 0, 20, OLED_WIDTH, 8, 0);
// 绘制文本
const uint8_t font_6x8[] = {6, 8, 6, /* 字体数据 */};
oled_draw_string(oled, start_x, 20, text, font_6x8, 1);
// 更新显示
oled_update(oled);
// 延迟
HAL_Delay(speed_ms);
// 移动位置
start_x--;
}
}
7.2 SPI Flash存储器操作
// W25Qxx系列SPI Flash驱动
#define W25Q_PAGE_SIZE 256
#define W25Q_SECTOR_SIZE 4096
#define W25Q_BLOCK_SIZE 65536
// W25Q指令集
#define W25Q_CMD_WRITE_ENABLE 0x06
#define W25Q_CMD_WRITE_DISABLE 0x04
#define W25Q_CMD_READ_STATUS_REG1 0x05
#define W25Q_CMD_WRITE_STATUS_REG1 0x01
#define W25Q_CMD_READ_DATA 0x03
#define W25Q_CMD_FAST_READ 0x0B
#define W25Q_CMD_PAGE_PROGRAM 0x02
#define W25Q_CMD_SECTOR_ERASE 0x20
#define W25Q_CMD_BLOCK_ERASE_32K 0x52
#define W25Q_CMD_BLOCK_ERASE_64K 0xD8
#define W25Q_CMD_CHIP_ERASE 0xC7
#define W25Q_CMD_POWER_DOWN 0xB9
#define W25Q_CMD_RELEASE_POWER_DOWN 0xAB
#define W25Q_CMD_READ_MANUFACTURER_ID 0x90
#define W25Q_CMD_READ_JEDEC_ID 0x9F
typedef struct {
SPI_HandleTypeDef *spi;
GPIO_TypeDef *cs_port;
uint16_t cs_pin;
uint32_t capacity; // 容量(字节)
uint16_t page_size; // 页大小
uint16_t sector_size; // 扇区大小
uint32_t sector_count; // 扇区数量
uint8_t manufacturer_id;
uint8_t memory_type;
uint8_t capacity_id;
} W25Q_Handle;
// 等待Flash就绪
static void w25q_wait_busy(W25Q_Handle *flash)
{
uint8_t status;
do {
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
uint8_t cmd = W25Q_CMD_READ_STATUS_REG1;
HAL_SPI_Transmit(flash->spi, &cmd, 1, 100);
HAL_SPI_Receive(flash->spi, &status, 1, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
// 检查BUSY位(bit0)
} while (status & 0x01);
}
// 写使能
static void w25q_write_enable(W25Q_Handle *flash)
{
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
uint8_t cmd = W25Q_CMD_WRITE_ENABLE;
HAL_SPI_Transmit(flash->spi, &cmd, 1, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
}
// 读取制造商和设备ID
void w25q_read_id(W25Q_Handle *flash)
{
uint8_t tx_buffer[4] = {W25Q_CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00};
uint8_t rx_buffer[4];
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(flash->spi, tx_buffer, rx_buffer, 4, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
flash->manufacturer_id = rx_buffer[1];
flash->memory_type = rx_buffer[2];
flash->capacity_id = rx_buffer[3];
// 根据ID确定容量
switch (flash->capacity_id) {
case 0x15: // W25Q16
flash->capacity = 2 * 1024 * 1024; // 2MB
break;
case 0x16: // W25Q32
flash->capacity = 4 * 1024 * 1024; // 4MB
break;
case 0x17: // W25Q64
flash->capacity = 8 * 1024 * 1024; // 8MB
break;
case 0x18: // W25Q128
flash->capacity = 16 * 1024 * 1024; // 16MB
break;
default:
flash->capacity = 0;
break;
}
flash->page_size = W25Q_PAGE_SIZE;
flash->sector_size = W25Q_SECTOR_SIZE;
flash->sector_count = flash->capacity / flash->sector_size;
}
// 读取数据
void w25q_read(W25Q_Handle *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t tx_buffer[4];
tx_buffer[0] = W25Q_CMD_READ_DATA;
tx_buffer[1] = (addr >> 16) & 0xFF;
tx_buffer[2] = (addr >> 8) & 0xFF;
tx_buffer[3] = addr & 0xFF;
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(flash->spi, tx_buffer, 4, 100);
HAL_SPI_Receive(flash->spi, data, len, 1000);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
}
// 快速读取
void w25q_fast_read(W25Q_Handle *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t tx_buffer[5];
tx_buffer[0] = W25Q_CMD_FAST_READ;
tx_buffer[1] = (addr >> 16) & 0xFF;
tx_buffer[2] = (addr >> 8) & 0xFF;
tx_buffer[3] = addr & 0xFF;
tx_buffer[4] = 0x00; // 虚字节
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(flash->spi, tx_buffer, 5, 100);
HAL_SPI_Receive(flash->spi, data, len, 1000);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
}
// 页编程(写入)
void w25q_page_program(W25Q_Handle *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t tx_buffer[4];
// 检查地址是否页对齐
if ((addr % flash->page_size) + len > flash->page_size) {
// 跨页写入需要分多次
uint32_t remaining = len;
uint32_t current_addr = addr;
uint8_t *current_data = data;
while (remaining > 0) {
uint32_t write_len = flash->page_size - (current_addr % flash->page_size);
if (write_len > remaining) {
write_len = remaining;
}
w25q_page_program(flash, current_addr, current_data, write_len);
current_addr += write_len;
current_data += write_len;
remaining -= write_len;
}
return;
}
// 写使能
w25q_write_enable(flash);
// 发送页编程命令和地址
tx_buffer[0] = W25Q_CMD_PAGE_PROGRAM;
tx_buffer[1] = (addr >> 16) & 0xFF;
tx_buffer[2] = (addr >> 8) & 0xFF;
tx_buffer[3] = addr & 0xFF;
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(flash->spi, tx_buffer, 4, 100);
HAL_SPI_Transmit(flash->spi, data, len, 1000);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
// 等待写入完成
w25q_wait_busy(flash);
}
// 扇区擦除
void w25q_sector_erase(W25Q_Handle *flash, uint32_t sector)
{
uint32_t addr = sector * flash->sector_size;
uint8_t tx_buffer[4];
// 写使能
w25q_write_enable(flash);
// 发送扇区擦除命令和地址
tx_buffer[0] = W25Q_CMD_SECTOR_ERASE;
tx_buffer[1] = (addr >> 16) & 0xFF;
tx_buffer[2] = (addr >> 8) & 0xFF;
tx_buffer[3] = addr & 0xFF;
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(flash->spi, tx_buffer, 4, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
// 等待擦除完成
w25q_wait_busy(flash);
}
// 块擦除
void w25q_block_erase(W25Q_Handle *flash, uint32_t block, uint8_t block_size)
{
uint32_t addr = block * block_size;
uint8_t tx_buffer[4];
uint8_t cmd;
// 选择擦除命令
if (block_size == 32 * 1024) {
cmd = W25Q_CMD_BLOCK_ERASE_32K;
} else if (block_size == 64 * 1024) {
cmd = W25Q_CMD_BLOCK_ERASE_64K;
} else {
return; // 不支持的块大小
}
// 写使能
w25q_write_enable(flash);
// 发送块擦除命令和地址
tx_buffer[0] = cmd;
tx_buffer[1] = (addr >> 16) & 0xFF;
tx_buffer[2] = (addr >> 8) & 0xFF;
tx_buffer[3] = addr & 0xFF;
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(flash->spi, tx_buffer, 4, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
// 等待擦除完成
w25q_wait_busy(flash);
}
// 芯片擦除
void w25q_chip_erase(W25Q_Handle *flash)
{
// 写使能
w25q_write_enable(flash);
// 发送芯片擦除命令
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_RESET);
uint8_t cmd = W25Q_CMD_CHIP_ERASE;
HAL_SPI_Transmit(flash->spi, &cmd, 1, 100);
HAL_GPIO_WritePin(flash->cs_port, flash->cs_pin, GPIO_PIN_SET);
// 等待擦除完成(需要较长时间)
w25q_wait_busy(flash);
}
// 文件系统支持
typedef struct {
W25Q_Handle *flash;
uint32_t fat_start_sector;
uint32_t data_start_sector;
uint32_t total_sectors;
uint32_t sectors_per_cluster;
uint8_t *fat_cache;
} W25Q_FAT;
// 初始化FAT文件系统
int w25q_fat_init(W25Q_FAT *fat, W25Q_Handle *flash)
{
// 分配FAT缓存
fat->fat_cache = malloc(flash->sector_size);
if (!fat->fat_cache) {
return -1;
}
fat->flash = flash;
fat->total_sectors = flash->sector_count;
// 简单FAT配置
fat->sectors_per_cluster = 1; // 每簇1个扇区
fat->fat_start_sector = 0; // FAT从扇区0开始
fat->data_start_sector = 2; // 数据区从扇区2开始
// 初始化FAT表
w25q_read(flash, 0, fat->fat_cache, flash->sector_size);
// 检查是否已经格式化
if (fat->fat_cache[0] != 0xF8 || fat->fat_cache[1] != 0xFF) {
// 未格式化,进行格式化
w25q_fat_format(fat);
}
return 0;
}
// 格式化FAT
void w25q_fat_format(W25Q_FAT *fat)
{
// 擦除前几个扇区
for (uint32_t i = 0; i < 10; i++) {
w25q_sector_erase(fat->flash, i);
}
// 创建引导扇区
uint8_t boot_sector[512] = {0};
boot_sector[0] = 0xEB; // 跳转指令
boot_sector[1] = 0x3C;
boot_sector[2] = 0x90;
memcpy(boot_sector + 3, "MSDOS5.0", 8); // OEM名称
// BPB参数
boot_sector[11] = 0x00; // 每扇区字节数低字节
boot_sector[12] = 0x02; // 每扇区512字节高字节
boot_sector[13] = 0x01; // 每簇扇区数
boot_sector[14] = 0x01; // 保留扇区数低字节
boot_sector[15] = 0x00; // 保留扇区数高字节
boot_sector[16] = 0x01; // FAT数量
// 写入引导扇区
w25q_page_program(fat->flash, 0, boot_sector, 512);
// 初始化FAT表
memset(fat->fat_cache, 0, fat->flash->sector_size);
fat->fat_cache[0] = 0xF8; // 介质描述符
fat->fat_cache[1] = 0xFF;
fat->fat_cache[2] = 0xFF;
fat->fat_cache[3] = 0xFF;
// 写入FAT表
w25q_page_program(fat->flash, 512, fat->fat_cache, fat->flash->sector_size);
}
// 文件操作
typedef struct {
W25Q_FAT *fat;
uint32_t first_cluster;
uint32_t current_cluster;
uint32_t file_size;
uint32_t position;
uint8_t mode; // 'r' 读, 'w' 写, 'a' 追加
} W25Q_File;
// 打开文件
int w25q_fopen(W25Q_File *file, W25Q_FAT *fat, const char *filename, const char *mode)
{
// 简化的文件打开实现
// 在实际应用中,需要搜索目录项
file->fat = fat;
file->mode = mode[0];
file->position = 0;
if (file->mode == 'r') {
// 查找文件
// 这里简化实现,假设文件从簇2开始
file->first_cluster = 2;
file->current_cluster = 2;
// 读取文件大小等信息
// 简化为固定大小
file->file_size = 1024;
} else if (file->mode == 'w' || file->mode == 'a') {
// 创建新文件或追加
// 分配起始簇
file->first_cluster = 2; // 简化
file->current_cluster = 2;
file->file_size = 0;
if (file->mode == 'a') {
// 移动到文件末尾
file->position = file->file_size;
}
}
return 0;
}
// 读取文件
size_t w25q_fread(void *ptr, size_t size, size_t count, W25Q_File *file)
{
size_t total_bytes = size * count;
size_t bytes_to_read = total_bytes;
if (file->position + bytes_to_read > file->file_size) {
bytes_to_read = file->file_size - file->position;
}
if (bytes_to_read == 0) {
return 0;
}
// 计算读取地址
uint32_t sector = file->fat->data_start_sector +
(file->current_cluster - 2) * file->fat->sectors_per_cluster;
uint32_t addr = sector * file->fat->flash->sector_size +
(file->position % (file->fat->sectors_per_cluster * file->fat->flash->sector_size));
// 读取数据
w25q_read(file->fat->flash, addr, ptr, bytes_to_read);
// 更新位置
file->position += bytes_to_read;
return bytes_to_read / size;
}
// 写入文件
size_t w25q_fwrite(const void *ptr, size_t size, size_t count, W25Q_File *file)
{
size_t total_bytes = size * count;
// 计算写入地址
uint32_t sector = file->fat->data_start_sector +
(file->current_cluster - 2) * file->fat->sectors_per_cluster;
uint32_t addr = sector * file->fat->flash->sector_size +
(file->position % (file->fat->sectors_per_cluster * file->fat->flash->sector_size));
// 写入数据
w25q_page_program(file->fat->flash, addr, (uint8_t *)ptr, total_bytes);
// 更新位置和文件大小
file->position += total_bytes;
if (file->position > file->file_size) {
file->file_size = file->position;
}
return count;
}
// 关闭文件
int w25q_fclose(W25Q_File *file)
{
// 更新文件大小等信息到目录项
// 简化实现
return 0;
}
// 示例:使用SPI Flash存储传感器数据
void store_sensor_data(W25Q_Handle *flash, float temperature,
float humidity, uint32_t timestamp)
{
static uint32_t write_address = 0x1000; // 起始地址
static uint16_t record_count = 0;
// 数据记录结构
typedef struct {
uint32_t timestamp;
float temperature;
float humidity;
uint16_t checksum;
} SensorRecord;
SensorRecord record;
record.timestamp = timestamp;
record.temperature = temperature;
record.humidity = humidity;
// 计算校验和
uint8_t *data = (uint8_t *)&record;
uint16_t checksum = 0;
for (int i = 0; i < sizeof(record) - sizeof(uint16_t); i++) {
checksum += data[i];
}
record.checksum = checksum;
// 检查是否需要擦除新扇区
if ((write_address % flash->sector_size) == 0) {
uint32_t sector = write_address / flash->sector_size;
w25q_sector_erase(flash, sector);
}
// 写入数据
w25q_page_program(flash, write_address, (uint8_t *)&record, sizeof(record));
// 更新地址
write_address += sizeof(record);
record_count++;
// 如果到达存储末尾,循环覆盖
if (write_address >= 0x10000) { // 限制在64KB范围内
write_address = 0x1000;
record_count = 0;
}
printf("Stored record %d at 0x%08lX\n", record_count, write_address - sizeof(record));
}
// 示例:读取存储的传感器数据
void read_sensor_data(W25Q_Handle *flash, uint32_t start_address, uint16_t count)
{
typedef struct {
uint32_t timestamp;
float temperature;
float humidity;
uint16_t checksum;
} SensorRecord;
SensorRecord record;
uint32_t address = start_address;
printf("\n=== Sensor Data Records ===\n");
for (int i = 0; i < count; i++) {
// 读取记录
w25q_read(flash, address, (uint8_t *)&record, sizeof(record));
// 验证校验和
uint8_t *data = (uint8_t *)&record;
uint16_t checksum = 0;
for (int j = 0; j < sizeof(record) - sizeof(uint16_t); j++) {
checksum += data[j];
}
if (checksum == record.checksum) {
// 转换时间戳为可读格式
time_t t = record.timestamp;
struct tm *tm_info = localtime(&t);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%04d] %s - Temp: %.1fC, Hum: %.1f%%\n",
i, time_str, record.temperature, record.humidity);
} else {
printf("[%04d] Checksum error!\n", i);
}
address += sizeof(record);
// 检查是否到达存储末尾
if (address >= 0x10000) {
address = 0x1000;
}
}
}
第八章:SPI通信的未来发展趋势
8.1 高速SPI协议演进
随着物联网、人工智能和5G技术的发展,SPI协议也在不断演进:
-
Quad-SPI (QSPI) 和 Octal-SPI:
- 支持4线和8线并行传输
- 时钟频率可达200MHz以上
- 带宽提升4-8倍
-
Serial Memory Interface (SMI):
- 专门为存储器设计
- 支持DDR(双倍数据率)
- 更低的功耗
-
SPI Express:
- 类似PCIe的包交换协议
- 支持多设备并行通信
- 高级错误检测和恢复
8.2 SPI在边缘计算中的应用
// 边缘AI加速器SPI接口示例
#define AI_ACCELERATOR_CMD_INFER 0x10
#define AI_ACCELERATOR_CMD_LOAD_MODEL 0x11
#define AI_ACCELERATOR_CMD_GET_RESULT 0x12
typedef struct {
SPI_HandleTypeDef *spi;
GPIO_TypeDef *cs_port;
uint16_t cs_pin;
uint8_t model_id;
uint32_t inference_time;
} AI_Accelerator;
// AI推理请求
int ai_accelerator_infer(AI_Accelerator *accel, float *input_data,
uint32_t input_size, float *output_data,
uint32_t output_size)
{
uint8_t tx_buffer[256];
uint8_t rx_buffer[256];
uint32_t total_size = input_size * sizeof(float);
uint32_t chunk_size = 64; // 每次传输64字节
// 发送推理命令
tx_buffer[0] = AI_ACCELERATOR_CMD_INFER;
tx_buffer[1] = accel->model_id;
tx_buffer[2] = (total_size >> 8) & 0xFF;
tx_buffer[3] = total_size & 0xFF;
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(accel->spi, tx_buffer, 4, 100);
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_SET);
// 分块发送输入数据
uint8_t *input_bytes = (uint8_t *)input_data;
for (uint32_t i = 0; i < total_size; i += chunk_size) {
uint32_t current_chunk = (total_size - i) < chunk_size ?
(total_size - i) : chunk_size;
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(accel->spi, &input_bytes[i], current_chunk, 100);
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_SET);
HAL_Delay(1); // 短延时
}
// 等待推理完成
uint32_t start_time = HAL_GetTick();
uint8_t status = 0;
do {
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_RESET);
tx_buffer[0] = AI_ACCELERATOR_CMD_GET_RESULT;
HAL_SPI_TransmitReceive(accel->spi, tx_buffer, rx_buffer, 2, 100);
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_SET);
status = rx_buffer[1];
if (HAL_GetTick() - start_time > 1000) { // 1秒超时
return -1; // 超时错误
}
} while (status != 0x01); // 等待推理完成标志
// 读取推理结果
uint32_t output_bytes = output_size * sizeof(float);
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(accel->spi, tx_buffer, 1, 100); // 发送读取结果命令
HAL_SPI_Receive(accel->spi, (uint8_t *)output_data, output_bytes, 100);
HAL_GPIO_WritePin(accel->cs_port, accel->cs_pin, GPIO_PIN_SET);
// 记录推理时间
accel->inference_time = HAL_GetTick() - start_time;
return 0;
}
8.3 SPI安全增强
随着物联网安全需求的增加,SPI通信也需要加强安全防护:
-
加密SPI通信:
// SPI数据加密传输 void spi_encrypted_transfer(SPI_HandleTypeDef *hspi, uint8_t *plaintext, uint8_t *ciphertext, uint16_t size, uint8_t *key) { // 1. 加密数据 aes128_encrypt(plaintext, ciphertext, size, key); // 2. 添加消息认证码(MAC) uint8_t mac[16]; hmac_sha256(ciphertext, size, key, mac); // 3. 传输加密数据和MAC uint8_t tx_buffer[size + 16]; memcpy(tx_buffer, ciphertext, size); memcpy(tx_buffer + size, mac, 16); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi, tx_buffer, size + 16, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } -
安全启动和固件验证:
// 安全启动验证 int secure_boot_verify(SPI_HandleTypeDef *hspi, uint32_t firmware_address) { uint8_t signature[256]; uint8_t public_key[256]; uint8_t firmware_hash[32]; // 1. 读取固件签名 w25q_read(flash, firmware_address - 256, signature, 256); // 2. 计算固件哈希 sha256_compute(firmware_address, firmware_size, firmware_hash); // 3. 使用公钥验证签名 if (rsa_verify(firmware_hash, signature, public_key) != 0) { // 签名验证失败 return -1; } // 4. 验证通过,执行固件 return 0; }
第九章:总结与最佳实践
9.1 SPI通信最佳实践总结
-
硬件设计最佳实践:
- 保持信号线尽可能短
- 使用适当的阻抗匹配
- 添加适当的终端电阻
- 避免信号线交叉
-
软件设计最佳实践:
- 实现完善的错误处理
- 使用DMA提高效率
- 添加超时机制
- 实现重试逻辑
-
性能优化建议:
- 根据设备能力调整时钟频率
- 使用批量传输减少开销
- 合理使用中断和DMA
- 优化缓冲区管理
9.2 常见问题与解决方案
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 数据传输错误 | 时钟模式不匹配 | 检查CPOL和CPHA设置 |
| 通信速度慢 | 时钟频率设置过低 | 适当提高时钟频率 |
| 数据丢失 | 缓冲区溢出 | 增加缓冲区大小或使用DMA |
| 设备无响应 | 片选信号问题 | 检查片选信号时序 |
| 干扰问题 | 信号线过长或未屏蔽 | 缩短信号线,增加屏蔽 |
9.3 未来学习建议
-
深入学习相关协议:
- I2C、UART、CAN等通信协议
- USB、Ethernet等高速协议
-
掌握高级调试技巧:
- 逻辑分析仪使用
- 示波器信号分析
- 协议分析软件
-
了解行业最新发展:
- 关注芯片厂商最新SPI技术
- 学习新兴通信标准
- 参与开源硬件项目
附录
A. SPI相关资源
-
官方文档:
- STM32 SPI参考手册
- Linux SPI子系统文档
- 各芯片厂商数据手册
-
开发工具:
- STM32CubeMX
- Logic Analyzer(Saleae)
- Wireshark(网络协议分析)
-
开源项目:
- Linux内核SPI驱动
- FreeRTOS SPI组件
- Arduino SPI库
B. 术语表
| 术语 | 解释 |
|---|---|
| CPOL | 时钟极性 |
| CPHA | 时钟相位 |
| MOSI | 主出从入 |
| MISO | 主入从出 |
| SCLK | 串行时钟 |
| SS/CS | 从机选择/芯片选择 |
| FIFO | 先进先出缓冲区 |
| DMA | 直接内存访问 |
C. 常见SPI设备列表
| 设备类型 | 常见型号 | 通信速率 |
|---|---|---|
| Flash存储器 | W25Qxx, AT45DBxx | 10-104MHz |
| 显示屏 | SSD1306, ILI9341 | 10-66MHz |
| 传感器 | MPU6050, BME280 | 1-10MHz |
| ADC/DAC | MCP3008, MAX5216 | 1-20MHz |
| 无线模块 | NRF24L01, CC1101 | 0.25-10MHz |
版权声明:本文为技术分享文章,欢迎转载,但请注明出处。文中代码示例仅供参考,实际使用时请根据具体需求进行修改和测试。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)