STM32F103C8T6标准库实现SPI通信控制三块从芯片代码实现

代码实现:

以下是使用STM32F103C8T6的标准库实现SPI通信控制三块从STM32芯片的代码,仅供参考

- 包含头文件、端口定义、函数声明

#include "stm32f10x.h"

// 定义SPI引脚和端口
#define SPI_GPIO_PORT       GPIOA
#define SPI_SCK_PIN         GPIO_Pin_5
#define SPI_MISO_PIN        GPIO_Pin_6
#define SPI_MOSI_PIN        GPIO_Pin_7
#define SPI_NSS_PIN         GPIO_Pin_4

// 定义从设备数量
#define SLAVE_COUNT 3

// 定义从设备选择引脚
GPIO_TypeDef* NSS_Port[SLAVE_COUNT] = {GPIOA, GPIOB, GPIOC};
uint16_t NSS_Pin[SLAVE_COUNT] = {SPI_NSS_PIN, SPI_NSS_PIN, SPI_NSS_PIN};

// 定义超时时间计数最大值(这里假设为一个相对合理的值,可根据实际情况调整)
#define TIMEOUT_COUNT_MAX 10000

// SPI配置函数,用于初始化SPI相关的硬件设置和参数配置
void SPI_Config(void);

// 根据传入的从设备索引,选中对应的从设备,即将其片选引脚电平拉低
void SPI_SelectSlave(uint8_t slaveIndex);

// 根据传入的从设备索引,取消选中对应的从设备,即将其片选引脚电平拉高
void SPI_DeselectSlave(uint8_t slaveIndex);

// 用于实现SPI数据的发送和接收操作,具备超时处理机制,若通信超时返回错误码,否则返回接收到的数据
uint8_t SPI_TransmitReceive(uint8_t data);

- 主函数

int main(void)
{
    // 初始化系统时钟,为整个芯片的正常运行提供合适的时钟配置,此函数一般由芯片厂商提供标准实现
    SystemInit();

    // 调用SPI配置函数,完成SPI模块的初始化工作,使其能正常进行SPI通信
    SPI_Config();

    // 主循环,程序会一直在此循环内运行,不断地与各个从设备进行SPI通信交互
    while (1)
    {
        for (uint8_t i = 0; i < SLAVE_COUNT; i++)
        {
            // 选择从设备,为与指定从设备进行SPI通信做准备
            SPI_SelectSlave(i);

            // 发送数据并接收响应,添加超时处理,若通信出现异常(超时)会返回错误码,正常则返回从设备响应的数据
            uint8_t receivedData = SPI_TransmitReceive(0xAA);
            if (receivedData == 0xFF)  // 假设0xFF作为错误码,根据实际可调整,用于判断通信是否出错
            {
                // 这里可以添加针对通信错误的处理代码,比如打印错误信息等,示例为通过串口打印错误信息(需提前配置好串口功能)
                   printf("SPI communication error with slave %d!\n", i);
            }
            else
            {
                // 正常接收到数据后的处理逻辑,这里只是简单示例,可以按需完善,示例为通过串口打印接收到的数据(需提前配置好串口功能)
                   printf("Received data from slave %d: 0x%X\n", i, receivedData);
            }

            // 取消选择从设备,结束与当前从设备的SPI通信交互
            SPI_DeselectSlave(i);

            // 处理接收到的数据(这里只是简单的延时,可替换为实际处理逻辑),模拟对接收到的数据进行一些操作
            for (volatile int j = 0; j < 100000; j++);
        }
    }
}

- SPI_Config函数实现了对SPI模块的完整配置,
包括使能相关时钟、配置SPI引脚功能、设置SPI通信参数以及最终使能SPI模块等操作

void SPI_Config(void)
{
    // 使能GPIO和SPI时钟,确保后续对GPIO引脚以及SPI模块的操作能够正常进行
    // RCC_APB2PeriphClockCmd用于使能APB2总线上的外设时钟,这里使能GPIOA端口(SPI相关引脚所在端口)和复用功能IO(AFIO)的时钟
    // RCC_APB1PeriphClockCmd用于使能APB1总线上的外设时钟,这里使能SPI1模块的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI1, ENABLE);

    // 配置SPI引脚,将SPI的SCK、MISO、MOSI引脚设置为复用推挽输出模式,以便它们能作为SPI功能引脚正常使用,并指定引脚速度为50MHz
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = SPI_SCK_PIN | SPI_MISO_PIN | SPI_MOSI_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置速度为50MHz
    GPIO_Init(SPI_GPIO_PORT, &GPIO_InitStructure);

    // 配置NSS引脚为推挽输出模式,同样速度设为50MHz,并初始状态通过GPIO_SetBits函数将其设置为高电平,意味着一开始不选中任何从设备
    GPIO_InitStructure.GPIO_Pin = SPI_NSS_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置速度为50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, SPI_NSS_PIN);

    // 配置SPI参数,使用SPI_InitTypeDef结构体来详细设置SPI的各项通信参数
    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 双线全双工模式,主设备和从设备可同时收发数据
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 将STM32配置为SPI主设备
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 每次传输的数据长度为8位
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性为低电平空闲,即空闲时SCLK时钟线是低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 数据在时钟信号的第一个边沿(结合SPI_CPOL为低电平的情况,就是上升沿)捕获数据
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 采用软件管理片选信号,可通过代码灵活控制各个从设备的片选引脚电平
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; // 设置SPI的波特率预分频系数,决定SPI通信速度,这里选择16分频,可根据实际需求调整
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 数据传输从最高位(MSB)开始
    SPI_InitStructure.SPI_CRCPolynomial = 7; // 配置CRC校验多项式,在部分应用中可能用到CRC校验,这里进行了简单设置
    SPI_Init(SPI1, &SPI_InitStructure);

    // 使能SPI1模块,使其开始工作,准备进行SPI通信
    SPI_Cmd(SPI1, ENABLE);
}

// SPI_SelectSlave函数根据传入的从设备索引(slaveIndex),判断索引是否在合法范围内(小于SLAVE_COUNT),
// 如果合法,则通过GPIO_ResetBits函数将对应的从设备片选引脚电平拉低,从而选中该从设备,使其可以与主设备进行SPI通信

void SPI_SelectSlave(uint8_t slaveIndex)
{
    if (slaveIndex < SLAVE_COUNT)
    {
        // 设置对应的NSS引脚低电平以选中从设备
        GPIO_ResetBits(NSS_Port[slaveIndex], NSS_Pin[slaveIndex]);
    }
}

// SPI_DeselectSlave函数与SPI_SelectSlave函数相对应,根据传入的从设备索引,
// 在索引合法的情况下,通过GPIO_SetBits函数将对应的从设备片选引脚电平拉高,取消对该从设备的选择,结束与该从设备的SPI通信

void SPI_DeselectSlave(uint8_t slaveIndex)
{
    if (slaveIndex < SLAVE_COUNT)
    {
        // 设置对应的NSS引脚高电平以取消选中从设备
        GPIO_SetBits(NSS_Port[slaveIndex], NSS_Pin[slaveIndex]);
    }
}

// SPI_TransmitReceive函数用于实现SPI数据的发送和接收功能,同时具备超时处理机制,以应对从设备无响应等异常情况
// 它先等待发送缓冲区空(通过检查SPI_I2S_FLAG_TXE标志位),若超时未空则返回错误码;发送数据后,再等待接收缓冲区非空(通过检查SPI_I2S_FLAG_RXNE标志位),若超时仍为空则返回错误码,正常则返回接收到的数据

uint8_t SPI_TransmitReceive(uint8_t data)
{
    uint32_t timeoutCount = 0;
    // 等待TXE标志位被置位(发送缓冲区空),只有发送缓冲区空了才能往里面写入新的数据进行发送
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
    {
        if (timeoutCount++ > TIMEOUT_COUNT_MAX)
        {
            return 0xFF;  // 超时返回错误码,表示发送缓冲区一直没空,通信异常
        }
    }
    // 发送数据,将传入的数据写入SPI的数据寄存器,SPI模块会按照配置的参数将数据发送出去
    SPI_I2S_SendData(SPI1, data);

    timeoutCount = 0;
    // 等待RXNE标志位被置位(接收缓冲区非空),只有接收缓冲区有数据了才能读取到从设备返回的数据
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
    {
        if (timeoutCount++ > TIMEOUT_COUNT_MAX)
        {
            return 0xFF;  // 超时返回错误码,表示接收缓冲区一直无数据,通信异常
        }
    }
    // 读取接收到的数据,从SPI的数据寄存器中获取从设备返回的数据,并作为函数的返回值返回
    return SPI_I2S_ReceiveData(SPI1);
}

代码说明:

  1. 头文件:包含必要的头文件 stm32f10x.h
  2. 宏定义:定义了SPI引脚、端口以及从设备数量和选择引脚。
  3. 函数声明:声明了配置SPI、选择/取消选择从设备以及传输接收数据的函数。
  4. 主函数:初始化系统时钟和SPI配置,然后在主循环中依次选择每个从设备进行数据传输。
  5. SPI配置函数:配置SPI引脚、时钟、模式等参数,并使能SPI。
  6. 选择/取消选择从设备函数:通过设置对应的NSS引脚来选中或取消选中从设备。
  7. 传输接收函数:发送一个字节的数据并接收从设备的响应。

硬件连接:

  1. SPI引脚连接
    • 时钟线(SCLK):主STM32的SPI时钟引脚(在代码中定义为SPI_SCK_PIN,即GPIOAGPIO_Pin_5)需要连接到三块从STM32芯片的SPI时钟输入引脚。这样主芯片才能通过这个时钟信号来同步数据传输,确保所有从芯片都按照相同的节奏接收和发送数据。
    • 主设备输出/从设备输入线(MOSI):主STM32的MOSI引脚(代码中的SPI_MOSI_PIN,即GPIOAGPIO_Pin_7)要连接到三块从STM32芯片的SPI数据输入引脚。通过这条线,主芯片可以将数据发送给从芯片。
    • 主设备输入/从设备输出线(MISO):主STM32的MISO引脚(SPI_MISO_PINGPIOAGPIO_Pin_6)应连接到三块从STM32芯片的SPI数据输出引脚。这使得从芯片能够将数据返回给主芯片。
  2. 片选线(NSS)连接
    • 在代码中有三个从设备的片选引脚定义,主STM32通过这些引脚来选择与哪一个从芯片进行通信。
    • 对于第一个从芯片,主STM32的NSS_Port[0](在代码中初始化为GPIOA)的NSS_Pin[0](在代码中为SPI_NSS_PIN,即GPIOAGPIO_Pin_4)连接到从芯片1的片选引脚。
    • 对于第二个从芯片,主STM32的NSS_Port[1](代码中为GPIOB)的NSS_Pin[1]SPI_NSS_PIN)连接到从芯片2的片选引脚。
    • 对于第三个从芯片,主STM32的NSS_Port[2](代码中为GPIOC)的NSS_Pin[2]SPI_NSS_PIN)连接到从芯片3的片选引脚。这样主芯片可以通过控制这些片选引脚的电平来单独选中或取消选中某个从芯片,实现与特定从芯片的通信。
  3. 电源和接地连接
    • 所有的STM32芯片(主芯片和三块从芯片)都需要连接到合适的电源和接地引脚。一般来说,要连接到稳定的电源供应(如3.3V电源),并且要确保所有的接地引脚都连接到同一个地平面,以保证信号的正确参考电位,避免电气干扰和故障。
  4. 硬件连接注意事项
    • 布线规则:在连接SPI引脚时,尽量使连线短且直,减少信号传输过程中的寄生电感和电容。对于SCLK、MOSI和MISO这三条高速信号传输线,最好采用等长布线,以减少信号的skew(信号延迟差异),确保数据传输的同步性。
    • 隔离和抗干扰措施:如果系统环境存在较多的电磁干扰,可以考虑在SPI信号线上添加合适的滤波电容或者磁珠来抑制高频干扰。同时,在PCB布局上,要将SPI电路与可能产生干扰的电路(如高频振荡器、大功率驱动电路等)保持一定的距离,或者采用隔离措施(如光耦隔离,不过这会增加系统复杂度和成本,需要根据实际情况权衡)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值