当前位置: 首页>新闻资讯>技术问答

SPI同一总线如何挂载两个设备

SD NAND-贴片式TF卡-贴片式SD卡-免费测试2025-11-103

在SPI总线上挂载多个设备时,主要通过片选(CS)信号来区分不同的设备。每个设备都有一个独立的片选引脚,当片选信号为有效电平时,该设备被选中并与SPI主机进行通信。

以下是实现同一SPI总线挂载两个设备的步骤和注意事项:

  1. 硬件连接:

    • 共享SPI总线:所有设备的SCK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)分别连接到主机的同一个SPI端口的相应引脚。

    • 独立片选:每个设备有一个独立的片选(CS)引脚,连接到主机的不同GPIO引脚。

  2. 软件控制:

    • 在通信前,将目标设备的片选引脚拉低(有效电平),其他设备的片选保持高电平。

    • 通信完成后,将片选引脚拉高,释放设备。

  3. 注意事项:

    • 在切换设备时,确保当前通信已经完成,并且片选信号有足够的时间间隔,防止设备冲突。

    • 注意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时钟速度。

  1. 如果两个设备具有不同的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同一总线挂载多个设备的实现方法

在SPI总线上挂载多个设备是常见需求,主要通过片选(CS)信号来区分不同设备。以下是详细实现方案:

1. 基本硬件连接

SPI主机 <---> 多个从设备
MOSI  ----> 所有设备的MOSI
MISO  ----> 所有设备的MISO  
SCK   ----> 所有设备的SCK
CS1   ----> 设备1的CS
CS2   ----> 设备2的CS
CSn   ----> 设备n的CS

2. 实际接线示例

// 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

3.软件实现

设备管理结构体

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
};

4. 片选控制函数

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);
}

5. 通用SPI传输函数

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;
}

设备特定操作示例

6. SD卡操作

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;
}

7. Flash芯片操作

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;
}

高级管理技巧

8. 设备互斥访问

#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;
}

9. 动态设备配置

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;
}

重要注意事项

  1. CS信号管理:确保任何时候只有一个设备被选中

  2. 时序要求:不同设备可能有不同的建立/保持时间要求

  3. 电平兼容:确保所有设备逻辑电平兼容

  4. 总线负载:设备数量过多可能影响信号质量

  5. 布线考虑:SCK线尽量短,避免信号反射

这种架构可以轻松扩展到更多设备,只需添加额外的CS引脚和设备配置即可。

热门标签:SD NAND FLASH 贴片式TF卡 贴片式SD卡 SD FLASH NAND FLASH


SD NAND-贴片式TF卡-贴片式SD卡-免费测试

深圳市芯存者科技有限公司

联系我们

电话:176-6539-0767

Q Q:135-0379-986

邮箱:1350379986@qq.com

地址:深圳市南山区后海大道1021号C座

商务咨询
商务咨询
技术支持
技术支持