手把手教你用STM32F103的HAL库GPIO模拟SPI驱动MAX31865(附完整代码)
2026/6/2 10:25:27 网站建设 项目流程

手把手教你用STM32F103的HAL库GPIO模拟SPI驱动MAX31865(附完整代码)

在嵌入式开发中,SPI通信协议因其高速、全双工的特性被广泛应用于传感器、存储设备等外设的连接。然而,当硬件SPI资源紧张或需要更灵活的时序控制时,GPIO模拟SPI便成为了一种实用的替代方案。本文将详细介绍如何使用STM32F103的HAL库通过GPIO模拟SPI驱动MAX31865温度传感器,从硬件连接到软件实现,一步步带你完成整个项目。

1. 硬件准备与连接

1.1 所需材料清单

  • STM32F103开发板(如Blue Pill)
  • MAX31865模块(支持PT100/PT1000)
  • PT1000温度传感器
  • 杜邦线若干
  • 逻辑分析仪(可选,用于调试)

1.2 硬件连接示意图

MAX31865与STM32F103的连接方式如下:

MAX31865引脚STM32F103引脚功能说明
VIN3.3V电源正极
GNDGND地线
CSPA4片选信号
SCLKPA5时钟信号
SDIPA7数据输入
SDOPA6数据输出

提示:PT1000的三根线分别连接到MAX31865的RTD+、RTD-和REF-引脚,具体接线方式取决于使用的2线、3线还是4线制。

2. GPIO模拟SPI原理与实现

2.1 SPI通信基础

SPI协议包含以下关键信号:

  • SCLK:时钟信号,由主机产生
  • MOSI:主机输出,从机输入
  • MISO:主机输入,从机输出
  • CS:片选信号,低电平有效

在GPIO模拟SPI时,我们需要通过软件控制GPIO的电平变化来模拟这些信号。

2.2 HAL库GPIO配置

首先配置相关GPIO引脚:

// 在main.c的GPIO初始化部分添加以下代码 GPIO_InitTypeDef GPIO_InitStruct = {0}; // 配置CS、SCLK、MOSI为输出 GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置MISO为输入 GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

2.3 模拟SPI时序实现

MAX31865采用SPI模式1(CPOL=0,CPHA=1),即:

  • 时钟空闲时为低电平
  • 数据在时钟上升沿采样

下面是基本的读写函数实现:

// 向MAX31865写入一个字节 void SPI_WriteByte(uint8_t data) { for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // SCLK低 // 设置MOSI电平 if(data & 0x80) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); } HAL_Delay(1); // 适当延时保证时序 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // SCLK高 data <<= 1; HAL_Delay(1); } } // 从MAX31865读取一个字节 uint8_t SPI_ReadByte(void) { uint8_t data = 0; for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // SCLK低 HAL_Delay(1); data <<= 1; if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) { data |= 0x01; } HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // SCLK高 HAL_Delay(1); } return data; }

3. MAX31865驱动开发

3.1 寄存器定义与配置

MAX31865有几个关键寄存器需要配置:

// 寄存器地址定义 #define MAX31865_CONFIG_REG 0x00 #define MAX31865_RTD_MSB_REG 0x01 #define MAX31865_RTD_LSB_REG 0x02 // 配置寄存器位定义 #define MAX31865_CONFIG_BIAS 0x80 #define MAX31865_CONFIG_MODE_AUTO 0x40 #define MAX31865_CONFIG_3WIRE 0x10 #define MAX31865_CONFIG_FILTER_50HZ 0x01

3.2 初始化函数实现

初始化时需要根据实际硬件连接配置MAX31865:

void MAX31865_Init(void) { uint8_t config = MAX31865_CONFIG_BIAS | // 开启偏置电压 MAX31865_CONFIG_MODE_AUTO | // 自动转换模式 MAX31865_CONFIG_FILTER_50HZ; // 50Hz滤波器 // 根据接线方式选择2/3/4线制 // config |= MAX31865_CONFIG_3WIRE; // 如果是3线制则取消注释 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS低 SPI_WriteByte(0x80); // 写入配置寄存器 SPI_WriteByte(config); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS高 }

3.3 温度读取与计算

MAX31865返回的是RTD电阻值,需要转换为温度:

float MAX31865_ReadTemp(void) { uint16_t rtd_raw; float resistance, temp; // 读取RTD值 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); SPI_WriteByte(0x01); // 读取RTD MSB uint8_t msb = SPI_ReadByte(); SPI_WriteByte(0x02); // 读取RTD LSB uint8_t lsb = SPI_ReadByte(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); rtd_raw = ((msb << 8) | lsb) >> 1; // 去掉故障位 // 计算电阻值(假设使用PT1000,参考电阻Rref=430Ω) resistance = (float)rtd_raw * 430.0f / 32768.0f; // 将电阻转换为温度(简化计算) temp = (resistance - 1000.0f) / 3.85f; return temp; }

4. 调试技巧与常见问题

4.1 使用逻辑分析仪调试

当通信不正常时,逻辑分析仪是强大的调试工具。连接逻辑分析仪后,你应该能看到类似以下的波形:

  • CS信号在通信期间保持低电平
  • SCLK有规律的时钟脉冲
  • MOSI/MISO上有数据变化

4.2 常见问题排查

  1. 无数据返回

    • 检查电源和地线连接
    • 确认CS信号是否正确拉低
    • 验证SPI时序是否符合MAX31865要求
  2. 温度值不正确

    • 检查PT1000接线是否正确
    • 确认配置寄存器设置(特别是线制选择)
    • 验证参考电阻Rref的值是否与实际硬件匹配
  3. 通信不稳定

    • 尝试降低SPI时钟速度
    • 检查是否有信号干扰
    • 确保所有连接线牢固

4.3 性能优化建议

  • 根据实际需求调整SPI时钟速度
  • 可以去掉HAL_Delay(),改用更精确的定时器控制时序
  • 实现DMA传输以提高效率(如果使用硬件SPI)

5. 完整代码示例

以下是整合后的完整驱动代码:

// max31865.h #ifndef __MAX31865_H #define __MAX31865_H #include "stm32f1xx_hal.h" // 引脚定义 #define MAX31865_CS_PIN GPIO_PIN_4 #define MAX31865_SCLK_PIN GPIO_PIN_5 #define MAX31865_MISO_PIN GPIO_PIN_6 #define MAX31865_MOSI_PIN GPIO_PIN_7 #define MAX31865_PORT GPIOA // 函数声明 void MAX31865_Init(void); float MAX31865_ReadTemp(void); #endif
// max31865.c #include "max31865.h" // SPI写一个字节 static void SPI_WriteByte(uint8_t data) { for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_SCLK_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_MOSI_PIN, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_SCLK_PIN, GPIO_PIN_SET); data <<= 1; } } // SPI读一个字节 static uint8_t SPI_ReadByte(void) { uint8_t data = 0; for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_SCLK_PIN, GPIO_PIN_RESET); data <<= 1; if(HAL_GPIO_ReadPin(MAX31865_PORT, MAX31865_MISO_PIN)) { data |= 0x01; } HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_SCLK_PIN, GPIO_PIN_SET); } return data; } // MAX31865初始化 void MAX31865_Init(void) { uint8_t config = 0x80 | 0x40 | 0x01; // BIAS ON, Auto, 50Hz HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_CS_PIN, GPIO_PIN_RESET); SPI_WriteByte(0x80); // 写配置寄存器 SPI_WriteByte(config); HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_CS_PIN, GPIO_PIN_SET); } // 读取温度 float MAX31865_ReadTemp(void) { uint16_t rtd_raw; HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_CS_PIN, GPIO_PIN_RESET); SPI_WriteByte(0x01); uint8_t msb = SPI_ReadByte(); SPI_WriteByte(0x02); uint8_t lsb = SPI_ReadByte(); HAL_GPIO_WritePin(MAX31865_PORT, MAX31865_CS_PIN, GPIO_PIN_SET); rtd_raw = ((msb << 8) | lsb) >> 1; float resistance = (float)rtd_raw * 430.0f / 32768.0f; return (resistance - 1000.0f) / 3.85f; }

在实际项目中,我发现GPIO模拟SPI虽然灵活,但在高频率通信时稳定性不如硬件SPI。当需要更高采样率时,可以考虑使用STM32的硬件SPI外设,或者优化GPIO模拟的实现方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询