在SPI总线上挂载多个设备时,主要通过片选(CS)信号来区分不同的设备。每个设备都有一个独立的片选引脚,当片选信号为有效电平时,该设备被选中并与SPI主机进行通信。
以下是实现同一SPI总线挂载两个设备的步骤和注意事项:
硬件连接:
共享SPI总线:所有设备的SCK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)分别连接到主机的同一个SPI端口的相应引脚。
独立片选:每个设备有一个独立的片选(CS)引脚,连接到主机的不同GPIO引脚。
软件控制:
在通信前,将目标设备的片选引脚拉低(有效电平),其他设备的片选保持高电平。
通信完成后,将片选引脚拉高,释放设备。
注意事项:
在切换设备时,确保当前通信已经完成,并且片选信号有足够的时间间隔,防止设备冲突。
注意SPI设备的时钟极性和相位(CPOL和CPHA)设置,同一总线上的设备必须使用相同的SPI模式,否则可能无法正常通信。如果设备模式不同,则需要在通信前重新配置SPI主机的模式,但这会降低通信效率。
示例代码(使用HAL库):
假设有两个设备:设备1和设备2,它们的片选引脚分别连接到GPIO_PIN_1和GPIO_PIN_2。
首先,初始化SPI和GPIO:
// SPI初始化
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_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
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();
}
// 初始化片选GPIO
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始状态:两个片选都置高
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
然后,在通信时选择相应的设备:
// 与设备1通信
void device1_transfer(uint8_t *tx_data, uint8_t *rx_data, uint16_t size) {
// 选中设备1
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
// 进行SPI传输
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, size, HAL_MAX_DELAY);
// 释放设备1
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
// 与设备2通信
void device2_transfer(uint8_t *tx_data, uint8_t *rx_data, uint16_t size) {
// 选中设备2
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
// 进行SPI传输
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, size, HAL_MAX_DELAY);
// 释放设备2
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
}
注意:在实际应用中,可能需要根据设备的时序要求调整片选信号的有效时间和SPI时钟速度。
如果两个设备具有不同的SPI模式(CPOL/CPHA),则需要在切换设备时重新配置SPI外设。但这会带来性能损失,因为重新配置SPI需要时间。因此,尽量选择相同SPI模式的设备挂在同一总线上。
重新配置SPI模式的示例:
void switch_spi_mode(SPI_HandleTypeDef *hspi, uint32_t mode) {
// 先停止SPI
HAL_SPI_DeInit(hspi);
// 修改模式
hspi->Init.CLKPolarity = (mode & 0x02) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW;
hspi->Init.CLKPhase = (mode & 0x01) ? SPI_PHASE_2EDGE : SPI_PHASE_1EDGE;
// 重新初始化SPI
HAL_SPI_Init(hspi);
}
然后在切换设备时调用此函数(如果设备模式不同)。
但是,请注意,频繁切换SPI模式可能会影响通信效率和稳定性,因此不推荐在同一总线上使用不同模式的设备。
总结:同一SPI总线挂载多个设备的关键是使用独立的片选信号,并确保在通信过程中只有一个设备被选中。同时,注意所有设备必须支持相同的SPI模式,否则需要动态切换SPI模式,但这会增加复杂性和时间开销。
在SPI总线上挂载多个设备是常见需求,主要通过片选(CS)信号来区分不同设备。以下是详细实现方案:
SPI主机 <---> 多个从设备
MOSI ----> 所有设备的MOSI
MISO ----> 所有设备的MISO
SCK ----> 所有设备的SCK
CS1 ----> 设备1的CS
CS2 ----> 设备2的CS
CSn ----> 设备n的CS
// STM32为例的GPIO定义
#define SPI_DEVICE1_CS_PIN GPIO_PIN_4
#define SPI_DEVICE1_CS_PORT GPIOA
#define SPI_DEVICE2_CS_PIN GPIO_PIN_5
#define SPI_DEVICE2_CS_PORT GPIOA
#define SPI_DEVICE3_CS_PIN GPIO_PIN_6
#define SPI_DEVICE3_CS_PORT GPIOA
typedef struct {
GPIO_TypeDef* cs_port;
uint16_t cs_pin;
SPI_HandleTypeDef* hspi;
uint32_t timeout;
} spi_device_t;
// 设备实例定义
spi_device_t sd_card = {
.cs_port = SPI_DEVICE1_CS_PORT,
.cs_pin = SPI_DEVICE1_CS_PIN,
.hspi = &hspi1,
.timeout = 1000
};
spi_device_t flash_chip = {
.cs_port = SPI_DEVICE2_CS_PORT,
.cs_pin = SPI_DEVICE2_CS_PIN,
.hspi = &hspi1,
.timeout = 1000
};
spi_device_t sensor = {
.cs_port = SPI_DEVICE3_CS_PORT,
.cs_pin = SPI_DEVICE3_CS_PIN,
.hspi = &hspi1,
.timeout = 100
};
void spi_select_device(spi_device_t* device) {
// 先取消所有设备的片选
HAL_GPIO_WritePin(SPI_DEVICE1_CS_PORT, SPI_DEVICE1_CS_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(SPI_DEVICE2_CS_PORT, SPI_DEVICE2_CS_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(SPI_DEVICE3_CS_PORT, SPI_DEVICE3_CS_PIN, GPIO_PIN_SET);
// 短暂延时确保信号稳定
HAL_Delay(1);
// 选中指定设备
HAL_GPIO_WritePin(device->cs_port, device->cs_pin, GPIO_PIN_RESET);
// 短暂延时确保设备准备好
HAL_Delay(1);
}
void spi_deselect_device(spi_device_t* device) {
HAL_GPIO_WritePin(device->cs_port, device->cs_pin, GPIO_PIN_SET);
}
HAL_StatusTypeDef spi_transfer(spi_device_t* device,
uint8_t* tx_data,
uint8_t* rx_data,
uint16_t size) {
HAL_StatusTypeDef status;
// 选择设备
spi_select_device(device);
// 执行SPI传输
if(tx_data && rx_data) {
// 全双工模式
status = HAL_SPI_TransmitReceive(device->hspi, tx_data, rx_data, size, device->timeout);
} else if(tx_data) {
// 只发送模式
status = HAL_SPI_Transmit(device->hspi, tx_data, size, device->timeout);
} else if(rx_data) {
// 只接收模式(通常需要发送dummy数据)
status = HAL_SPI_Receive(device->hspi, rx_data, size, device->timeout);
} else {
status = HAL_ERROR;
}
// 取消设备选择
spi_deselect_device(device);
return status;
}
HAL_StatusTypeDef sd_card_read(spi_device_t* sd_device,
uint32_t sector,
uint8_t* buffer) {
uint8_t cmd[6];
uint8_t response;
// 构造读命令
cmd[0] = 0x51; // READ_MULTIPLE_BLOCK
cmd[1] = (sector >> 24) & 0xFF;
cmd[2] = (sector >> 16) & 0xFF;
cmd[3] = (sector >> 8) & 0xFF;
cmd[4] = sector & 0xFF;
cmd[5] = 0xFF; // CRC
spi_select_device(sd_device);
// 发送命令
HAL_SPI_Transmit(sd_device->hspi, cmd, 6, sd_device->timeout);
// 等待数据令牌
do {
HAL_SPI_Receive(sd_device->hspi, &response, 1, sd_device->timeout);
} while(response != 0xFE);
// 读取数据
HAL_SPI_Receive(sd_device->hspi, buffer, 512, sd_device->timeout);
// 读取CRC(忽略)
uint8_t crc[2];
HAL_SPI_Receive(sd_device->hspi, crc, 2, sd_device->timeout);
spi_deselect_device(sd_device);
return HAL_OK;
}
HAL_StatusTypeDef flash_read_id(spi_device_t* flash_device, uint8_t* id) {
uint8_t cmd = 0x9F; // READ_ID命令
spi_select_device(flash_device);
// 发送命令
HAL_SPI_Transmit(flash_device->hspi, &cmd, 1, flash_device->timeout);
// 读取ID(通常3字节)
HAL_SPI_Receive(flash_device->hspi, id, 3, flash_device->timeout);
spi_deselect_device(flash_device);
return HAL_OK;
}
#include "cmsis_os.h"
osMutexId_t spi_bus_mutex;
void spi_bus_init(void) {
spi_bus_mutex = osMutexNew(NULL);
}
HAL_StatusTypeDef spi_transfer_thread_safe(spi_device_t* device,
uint8_t* tx_data,
uint8_t* rx_data,
uint16_t size) {
// 获取总线使用权
if(osMutexAcquire(spi_bus_mutex, device->timeout) != osOK) {
return HAL_TIMEOUT;
}
HAL_StatusTypeDef status = spi_transfer(device, tx_data, rx_data, size);
// 释放总线
osMutexRelease(spi_bus_mutex);
return status;
}
typedef enum {
SPI_MODE_0 = 0,
SPI_MODE_1,
SPI_MODE_2,
SPI_MODE_3
} spi_mode_t;
HAL_StatusTypeDef spi_reconfigure_for_device(spi_device_t* device,
spi_mode_t mode,
uint32_t prescaler) {
// 保存当前配置
static SPI_HandleTypeDef* last_hspi = NULL;
static uint32_t last_prescaler;
static spi_mode_t last_mode;
if(last_hspi != device->hspi) {
// 设备切换,需要重新配置
HAL_SPI_DeInit(device->hspi);
device->hspi->Init.CLKPolarity = (mode & 0x02) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW;
device->hspi->Init.CLKPhase = (mode & 0x01) ? SPI_PHASE_2EDGE : SPI_PHASE_1EDGE;
device->hspi->Init.BaudRatePrescaler = prescaler;
if(HAL_SPI_Init(device->hspi) != HAL_OK) {
return HAL_ERROR;
}
last_hspi = device->hspi;
last_prescaler = prescaler;
last_mode = mode;
}
return HAL_OK;
}
CS信号管理:确保任何时候只有一个设备被选中
时序要求:不同设备可能有不同的建立/保持时间要求
电平兼容:确保所有设备逻辑电平兼容
总线负载:设备数量过多可能影响信号质量
布线考虑:SCK线尽量短,避免信号反射
这种架构可以轻松扩展到更多设备,只需添加额外的CS引脚和设备配置即可。
上一篇:SD卡SPI初始化失败排查指南
下一篇:SDNAND通信流程
电话:176-6539-0767
Q Q:135-0379-986
邮箱:1350379986@qq.com
地址:深圳市南山区后海大道1021号C座