SPI(Serial Peripheral Interface)是一种常用的串行通信接口标准,它提供了一种主从架构的全双工、同步通信方式。以下是对SPI的详细介绍,并附带一个详细的C代码示例,展示如何使用SPI接口操作外部Flash存储器。
SPI详细介绍
1. 工作模式:
主从结构: SPI通信中有一个主设备(通常是微控制器)和一个或多个从设备(如Flash、传感器等)。主设备负责生成同步时钟信号(SCK)并控制数据交换过程,从设备则被动响应主设备的命令。
2. 全双工通信:
双向数据传输: 在同一时刻,SPI主设备可以通过MOSI(Master Output, Slave Input)线向从设备发送数据,同时通过MISO(Master Input, Slave Output)线接收从设备发来的数据。这种全双工特性使得SPI能够在单个时钟周期内完成一次完整的数据交换。
3. 同步时钟:
同步机制: SPI通信基于主设备提供的时钟信号(SCK)进行同步。数据在SCK的上升沿或下降沿(由CPHA参数决定)被采样和移位。这种同步机制确保了数据传输的精确性和稳定性,尤其适用于高速数据传输场景。
4. 可配置的通信参数:
时钟极性(CPOL): 定义SCK信号空闲状态的电平(高或低)。
时钟相位(CPHA): 决定数据采样是在时钟沿的前半周期还是后半周期进行。
数据位宽: 每个数据帧包含的比特数(如8位、16位等),可根据需要进行调整。
波特率: 即通信速度,可通过调整SCK频率来改变。
5. 多从设备支持:
从设备选择: 一个主设备可以连接多个从设备,通常通过额外的选通信号线(SS/CS,Slave Select/Chip Select)来实现。主设备通过单独拉低某个从设备对应的SS线来选定与其进行通信,未被选中的从设备则保持静默状态。
6. 电气特性:
推挽输出: SPI接口通常采用推挽输出结构,可以驱动较长的线路而无需额外的缓冲器,有利于保证信号完整性,尤其是在高速传输时。
**7. ** 优势与特点:
高速: 由于采用全双工同步通信,SPI相对于I2C等半双工异步总线具有更高的数据传输速率。
低功耗: 无须上拉电阻,且外围电路简单,有助于降低系统功耗。
协议灵活性: 信息帧大小可任意调节,适用于各种数据长度的需求。
软件配置便捷: 相较于复杂的并行总线,SPI的配置和编程相对简单。
C代码示例:SPI操作外部Flash
以下是一个使用C语言操作SPI接口连接的外部Flash存储器(假设为SPI Flash)的详细示例。代码基于STM32Cube HAL库编写,具体实现可能会因所使用的MCU型号、开发环境以及SPI Flash芯片型号的不同而有所差异。本示例仅提供一种通用的编程思路,实际使用时请参照相应MCU的SPI库文档和SPI Flash芯片的数据手册进行适配。
#include "stm32f4xx_hal.h"// 定义SPI Flash相关参数
#define SPI_FLASH_CS_PIN GPIO_PIN_1 // 选通信号线(具体端口根据实际硬件配置)
#define SPI_FLASH_PAGE_SIZE 256 // 页大小(单位:字节)
#define SPI_FLASH_CMD_SIZE 4 // 命令字长度(单位:字节)// SPI Flash命令字定义
#define SPI_FLASH_CMD_WRITE_ENABLE 0x06
#define SPI_FLASH_CMD_WRITE_DISABLE 0x04
#define SPI_FLASH_CMD_READ_STATUS_REG 0x05
#define SPI_FLASH_CMD_PAGE_PROGRAM 0x02
#define SPI_FLASH_CMD_SECTOR_ERASE 0xD8
#define SPI_FLASH_CMD_CHIP_ERASE 0xC7
#define SPI_FLASH_CMD_READ_DATA 0x03// 函数声明
void spi_flash_send_cmd(uint8_t cmd, uint32_t addr, uint8_t *data, uint32_t len);
uint8_t spi_flash_read_status(void);
void spi_flash_wait_for_busy(void);// 擦除操作
void spi_flash_erase_sector(uint32_t sector_addr) {spi_flash_send_cmd(SPI_FLASH_CMD_WRITE_ENABLE, 0, NULL, 0); // 启用写使能spi_flash_send_cmd(SPI_FLASH_CMD_SECTOR_ERASE, sector_addr, NULL, 0); // 发送扇区擦除命令spi_flash_wait_for_busy(); // 等待擦除完成spi_flash_send_cmd(SPI_FLASH_CMD_WRITE_DISABLE, 0, NULL, 0); // 关闭写使能
}// 写入操作
void spi_flash_program_page(uint32_t page_addr, uint8_t *data, uint32_t len) {if (len > SPI_FLASH_PAGE_SIZE) {printf("Error: Data length exceeds page size.\n");return;}spi_flash_send_cmd(SPI_FLASH_CMD_WRITE_ENABLE, 0, NULL, 0); // 启用写使能spi_flash_send_cmd(SPI_FLASH_CMD_PAGE_PROGRAM, page_addr, data, len); // 发送页编程命令并写入数据spi_flash_wait_for_busy(); // 等待写入完成spi_flash_send_cmd(SPI_FLASH_CMD_WRITE_DISABLE, 0, NULL, 0); // 关闭写使能
}// 读取操作
void spi_flash_read_data(uint32_t addr, uint8_t *buf, uint32_t len) {spi_flash_send_cmd(SPI_FLASH_CMD_READ_DATA, addr, buf, len); // 发送读数据命令并接收数据
}// SPI Flash命令发送函数
void spi_flash_send_cmd(uint8_t cmd, uint32_t addr, uint8_t *data, uint32_t len) {// 设置选通信号为低电平(使能SPI Flash)HAL_GPIO_WritePin(SPI_FLASH_CS_PORT, SPI_FLASH_CS_PIN, GPIO_PIN_RESET);// 发送命令字HAL_SPI_Transmit(&hspi1, &cmd, SPI_FLASH_CMD_SIZE, HAL_MAX_DELAY);// 如果有地址,则发送地址if (addr != 0) {HAL_SPI_Transmit(&hspi1, (uint8_t *)&addr, sizeof(addr), HAL_MAX_DELAY);}// 如果有数据,则发送数据if (data != NULL && len > 0) {HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY);}// 设置选通信号为高电平(禁能SPI Flash)HAL_GPIO_WritePin(SPI_FLASH_CS_PORT, SPI_FLASH_CS_PIN, GPIO_PIN_SET);
}// 读取SPI Flash状态寄存器
uint8_t spi_flash_read_status(void) {uint8_t status;// 发送读状态寄存器命令spi_flash_send_cmd(SPI_FLASH_CMD_READ_STATUS_REG, 0, NULL, 0);// 接收状态寄存器值HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);return status;
}// 等待SPI Flash忙标志清除
void spi_flash_wait_for_busy(void) {while ((spi_flash_read_status() & 0x01) == 0x01) { // 检查忙位(第0位)HAL_Delay(1); // 延迟一段时间再检查}
}