我将针对基于PPG的脉搏波及心率显示项目,详细阐述最适合的代码设计架构,并提供具体的C代码实现。本项目旨在构建一个低成本、易于复刻、可靠、高效且可扩展的嵌入式系统平台。
关注微信公众号,提前获取相关推文

项目概述与需求分析
1. 项目目标:
- 功能性目标:
- 实时采集PPG(光电容积脉搏波描记法)传感器数据。
- 对PPG信号进行预处理、滤波和分析,提取脉搏波形。
- 准确计算心率(BPM - 每分钟心跳次数)。
- 在小型显示屏上清晰显示脉搏波形和实时心率值。
- 通过LED指示灯或屏幕颜色变化,根据心率范围提供视觉反馈。
- 用户友好的操作界面,可能包括一个或多个按键用于启动/停止测量或切换显示模式。
- 非功能性目标:
- 低成本: 采用经济实惠的硬件组件,降低BOM(物料清单)成本。
- 易于复刻: 设计简单明了,代码结构清晰,方便其他开发者理解和复制。
- 可靠性: 系统运行稳定可靠,能够长时间连续工作,数据采集和处理准确。
- 高效性: 代码执行效率高,资源占用少,确保实时性和低功耗。
- 可扩展性: 系统架构应易于扩展,未来可以添加更多功能,例如血氧饱和度(SpO2)测量、数据记录、无线传输等。
- 用户友好性: 操作简单直观,显示信息清晰易懂。
- 低功耗: 尽可能降低功耗,延长电池续航时间(如果使用电池供电)。
2. 硬件选型(低成本和易复刻原则):
- 微控制器 (MCU):
- 选项: STM32F103C8T6 (俗称“蓝 pills”), ESP32-C3, ATmega328P (Arduino Nano)。
- 理由: STM32F103C8T6 性能适中,价格低廉,社区支持广泛,易于获取。ESP32-C3 具有Wi-Fi功能,虽然本项目初期可能不需要,但为未来扩展无线传输功能预留了可能性。ATmega328P 成本更低,但性能稍弱,适合对性能要求不高的简化版本。
- 选定: STM32F103C8T6 作为示例,因为它在性能、成本和易用性之间取得了良好的平衡。
- PPG传感器:
- 选项: MAX30102, MAX30100, SFH 7050, 定制化方案(LED + 光电二极管/三极管)。
- 理由: MAX30102 和 MAX30100 是集成度高的PPG传感器,内置LED和光电探测器,I2C接口,易于使用。SFH 7050 也是类似的集成传感器。定制化方案成本更低,但需要更多的外围电路设计和校准工作。
- 选定: MAX30102,因为它集成度高,性能良好,且相对容易获取和使用。
- 显示屏:
- 选项: 0.96寸或1.3寸 OLED显示屏 (SPI或I2C接口), 128x64 LCD显示屏 (SPI或并行接口)。
- 理由: OLED显示屏对比度高,功耗低,显示效果好,SPI接口易于连接MCU。LCD显示屏成本更低,但显示效果稍逊。
- 选定: 0.96寸 SPI OLED显示屏,兼顾显示效果和易用性。
- 电源:
- 选项: USB供电,锂电池供电。
- 理由: USB供电方便调试和开发。锂电池供电更适合便携式应用。
- 设计: 采用USB供电,同时预留电池供电接口,方便用户根据需求选择。
- 按键:
- 选项: 机械按键,触摸按键。
- 理由: 机械按键成本低,可靠性高,易于实现。触摸按键更美观,但成本稍高,且在嵌入式系统中可能不如机械按键可靠。
- 选定: 机械按键,例如轻触按键,用于启动/停止测量或切换显示模式。
- LED指示灯:
- 选项: 单色LED,RGB LED。
- 理由: 单色LED成本低,用于简单的状态指示。RGB LED可以实现更丰富的颜色指示,例如根据心率范围改变颜色。
- 选定: RGB LED,用于更直观的心率范围指示。
3. 软件需求:
- 驱动程序:
- PPG传感器驱动 (MAX30102)。
- OLED显示屏驱动 (SPI接口)。
- GPIO驱动 (按键,LED)。
- ADC驱动 (如果PPG传感器输出模拟信号,虽然MAX30102是I2C数字接口,这里可以作为通用ADC驱动的示例,或者用于其他可能的模拟传感器扩展)。
- 定时器驱动 (用于采样率控制,心率计算等)。
- I2C/SPI驱动 (用于传感器和显示屏通信)。
- 信号处理算法:
- PPG信号预处理 (滤波,去噪)。
- 脉搏波形检测和提取。
- 心率计算算法。
- 应用程序逻辑:
- 系统初始化。
- 状态管理 (测量状态,显示状态,空闲状态等)。
- 用户界面逻辑 (按键处理,显示内容更新)。
- 心率范围指示逻辑 (LED颜色控制)。
代码设计架构
为了实现可靠、高效、可扩展的系统平台,并遵循模块化和分层设计的原则,我推荐以下代码架构:
1. 分层架构:
- 硬件抽象层 (HAL - Hardware Abstraction Layer):
- 功能: 直接与硬件交互,提供统一的硬件接口,屏蔽底层硬件差异。
- 模块:
hal_gpio.c/h
, hal_adc.c/h
, hal_timer.c/h
, hal_spi.c/h
, hal_i2c.c/h
等。
- 优点: 提高代码的可移植性,方便更换底层硬件平台。
- 设备驱动层 (Device Driver Layer):
- 功能: 基于HAL层,为上层应用提供更高级、更易用的设备接口。
- 模块:
driver_ppg.c/h
(MAX30102驱动), driver_oled.c/h
(OLED显示屏驱动), driver_button.c/h
, driver_led.c/h
等。
- 优点: 将硬件操作细节封装在驱动层,使应用层代码更简洁,专注于业务逻辑。
- 信号处理层 (Signal Processing Layer):
- 功能: 负责PPG信号的滤波、分析和特征提取。
- 模块:
processing_ppg.c/h
(PPG信号处理模块), algorithm_filter.c/h
(滤波算法), algorithm_peak_detection.c/h
(峰值检测算法), algorithm_heart_rate.c/h
(心率计算算法) 等。
- 优点: 将信号处理算法独立出来,方便算法的优化和替换。
- 应用逻辑层 (Application Logic Layer):
- 功能: 实现系统的核心业务逻辑,包括状态管理、用户界面、数据显示等。
- 模块:
app_main.c
(主应用程序), ui_display.c/h
(用户界面显示), state_machine.c/h
(状态机管理), config.c/h
(系统配置) 等。
- 优点: 清晰地组织应用程序逻辑,提高代码的可读性和可维护性。
2. 模块化设计:
- 每个层次和功能都划分为独立的模块,模块之间通过定义良好的接口进行通信。
- 模块内部高内聚,模块之间低耦合。
- 方便代码的复用、测试和维护。
3. 事件驱动或轮询机制:
- 数据采集: 可以使用定时器中断触发ADC采样 (如果使用模拟PPG传感器),或者轮询方式读取数字PPG传感器数据。
- UI更新: 可以定时更新显示屏,或者在数据更新时触发UI更新。
- 按键处理: 可以使用外部中断或轮询方式检测按键事件。
4. 状态机:
- 使用状态机管理系统的不同状态 (例如:初始化状态、测量状态、显示状态、错误状态等)。
- 状态机可以简化程序逻辑,提高系统的可靠性和可维护性。
5. 配置管理:
- 将系统配置参数 (例如:采样率、滤波参数、显示设置等) 集中管理在配置文件中。
- 方便参数的修改和调整,提高系统的灵活性。
C 代码实现 (详细注释)
为了满足3000行代码的要求,我将尽可能详细地实现各个模块,并添加详细的注释。以下代码将以 STM32F103C8T6 微控制器和 MAX30102 PPG传感器 为例进行实现。
1. HAL 层 (Hardware Abstraction Layer)
hal_gpio.h
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #ifndef HAL_GPIO_H #define HAL_GPIO_H
#include <stdint.h>
typedef enum { GPIO_PORT_A, GPIO_PORT_B, GPIO_PORT_C } GPIO_PortTypeDef;
typedef uint16_t GPIO_PinTypeDef;
#define GPIO_PIN_0 (1U << 0) #define GPIO_PIN_1 (1U << 1) #define GPIO_PIN_2 (1U << 2)
typedef struct { GPIO_PortTypeDef Port; GPIO_PinTypeDef Pin; uint32_t Mode; uint32_t Pull; uint32_t Speed; } GPIO_InitTypeDef;
#define GPIO_MODE_INPUT (0x00000000U) #define GPIO_MODE_OUTPUT_PP (0x00000001U) #define GPIO_MODE_OUTPUT_OD (0x00000011U) #define GPIO_MODE_AF_PP (0x00000002U) #define GPIO_MODE_AF_OD (0x00000012U) #define GPIO_MODE_INPUT_PULLUP (0x00000021U) #define GPIO_MODE_INPUT_PULLDOWN (0x00000022U) #define GPIO_MODE_ANALOG (0x00000003U)
#define GPIO_PULL_NONE (0x00000000U) #define GPIO_PULLUP (0x00000001U) #define GPIO_PULLDOWN (0x00000002U)
#define GPIO_SPEED_FREQ_LOW (0x00000000U) #define GPIO_SPEED_FREQ_MEDIUM (0x00000001U) #define GPIO_SPEED_FREQ_HIGH (0x00000002U) #define GPIO_SPEED_FREQ_VERY_HIGH (0x00000003U)
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct);
void HAL_GPIO_WritePin(GPIO_PortTypeDef Port, GPIO_PinTypeDef Pin, uint8_t PinState);
uint8_t HAL_GPIO_ReadPin(GPIO_PortTypeDef Port, GPIO_PinTypeDef Pin);
#endif
|
hal_gpio.c
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include "hal_gpio.h" #include "stm32f1xx_hal.h"
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct) { GPIO_InitTypeDef GPIO_Config;
GPIO_Config.Pin = GPIO_InitStruct->Pin; GPIO_Config.Mode = GPIO_InitStruct->Mode; GPIO_Config.Pull = GPIO_InitStruct->Pull; GPIO_Config.Speed = GPIO_InitStruct->Speed;
GPIO_TypeDef *gpio_port; switch (GPIO_InitStruct->Port) { case GPIO_PORT_A: gpio_port = GPIOA; __HAL_RCC_GPIOA_CLK_ENABLE(); break; case GPIO_PORT_B: gpio_port = GPIOB; __HAL_RCC_GPIOB_CLK_ENABLE(); break; case GPIO_PORT_C: gpio_port = GPIOC; __HAL_RCC_GPIOC_CLK_ENABLE(); break; default: return; }
HAL_GPIO_Init_Ex(gpio_port, &GPIO_Config); }
void HAL_GPIO_WritePin(GPIO_PortTypeDef Port, GPIO_PinTypeDef Pin, uint8_t PinState) { GPIO_TypeDef *gpio_port; switch (Port) { case GPIO_PORT_A: gpio_port = GPIOA; break; case GPIO_PORT_B: gpio_port = GPIOB; break; case GPIO_PORT_C: gpio_port = GPIOC; break; default: return; }
HAL_GPIO_WritePin_Ex(gpio_port, Pin, (GPIO_PinState)PinState); }
uint8_t HAL_GPIO_ReadPin(GPIO_PortTypeDef Port, GPIO_PinTypeDef Pin) { GPIO_TypeDef *gpio_port; switch (Port) { case GPIO_PORT_A: gpio_port = GPIOA; break; case GPIO_PORT_B: gpio_port = GPIOB; break; case GPIO_PORT_C: gpio_port = GPIOC; break; default: return 0; }
return (uint8_t)HAL_GPIO_ReadPin_Ex(gpio_port, Pin); }
|
hal_timer.h
(简化的定时器 HAL):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #ifndef HAL_TIMER_H #define HAL_TIMER_H
#include <stdint.h>
typedef void* TimerHandleTypeDef;
typedef struct { uint32_t Prescaler; uint32_t Period; uint32_t ClockDivision; void (*Callback)(void); } Timer_InitTypeDef;
TimerHandleTypeDef HAL_Timer_Init(Timer_InitTypeDef *Timer_InitStruct);
void HAL_Timer_Start(TimerHandleTypeDef TimerHandle);
void HAL_Timer_Stop(TimerHandleTypeDef TimerHandle);
void HAL_Timer_SetCallback(TimerHandleTypeDef TimerHandle, void (*Callback)(void));
uint32_t HAL_Timer_GetCounter(TimerHandleTypeDef TimerHandle);
#endif
|
hal_timer.c
(简化的定时器 HAL):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include "hal_timer.h" #include "stm32f1xx_hal.h"
TimerHandleTypeDef HAL_Timer_Init(Timer_InitTypeDef *Timer_InitStruct) { TIM_HandleTypeDef *htim = malloc(sizeof(TIM_HandleTypeDef)); if (htim == NULL) { return NULL; }
htim->Instance = TIM2; __HAL_RCC_TIM2_CLK_ENABLE();
htim->Init.Prescaler = Timer_InitStruct->Prescaler; htim->Init.Period = Timer_InitStruct->Period; htim->Init.ClockDivision = TIM_InitStruct->ClockDivision; htim->Init.CounterMode = TIM_COUNTERMODE_UP; htim->Init.RepetitionCounter = 0; htim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(htim) != HAL_OK) { free(htim); return NULL; }
if (Timer_InitStruct->Callback != NULL) { HAL_TIM_IRQHandlerCallbackTypeDef callback_wrapper = Timer_InitStruct->Callback; HAL_TIM_RegisterCallback(htim, HAL_TIM_PERIOD_ELAPSED_CB_ID, callback_wrapper); HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); }
return (TimerHandleTypeDef)htim; }
void HAL_Timer_Start(TimerHandleTypeDef TimerHandle) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef*)TimerHandle; if (htim != NULL) { HAL_TIM_Base_Start_IT(htim); } }
void HAL_Timer_Stop(TimerHandleTypeDef TimerHandle) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef*)TimerHandle; if (htim != NULL) { HAL_TIM_Base_Stop_IT(htim); } }
void HAL_Timer_SetCallback(TimerHandleTypeDef TimerHandle, void (*Callback)(void)) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef*)TimerHandle; if (htim != NULL && Callback != NULL) { HAL_TIM_IRQHandlerCallbackTypeDef callback_wrapper = Callback; HAL_TIM_RegisterCallback(htim, HAL_TIM_PERIOD_ELAPSED_CB_ID, callback_wrapper); } }
uint32_t HAL_Timer_GetCounter(TimerHandleTypeDef TimerHandle) { TIM_HandleTypeDef *htim = (TIM_HandleTypeDef*)TimerHandle; if (htim != NULL) { return HAL_TIM_ReadCount(htim); } return 0; }
void TIM2_IRQHandler(void) { HAL_TIM_IRQHandler((TIM_HandleTypeDef*)htim_tim2); }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { HAL_TIM_IRQHandlerCallbackTypeDef callback = (HAL_TIM_IRQHandlerCallbackTypeDef)htim->PeriodElapsedCallback; if (callback != NULL) { callback(); } } }
|
hal_spi.h
(简化的 SPI HAL):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #ifndef HAL_SPI_H #define HAL_SPI_H
#include <stdint.h>
typedef void* SPI_HandleTypeDef;
typedef struct { uint32_t Mode; uint32_t Direction; uint32_t DataSize; uint32_t ClockPolarity; uint32_t ClockPhase; uint32_t NSS; uint32_t BaudRatePrescaler; uint32_t FirstBit; uint32_t TIMode; uint32_t CRCCalculation; uint32_t CRCPolynomial; } SPI_InitTypeDef;
#define SPI_MODE_MASTER (0x00000000U) #define SPI_MODE_SLAVE (0x00000001U)
#define SPI_DIRECTION_2LINES (0x00000000U) #define SPI_DIRECTION_2LINES_RXONLY (0x00000001U) #define SPI_DIRECTION_1LINE (0x00000002U)
#define SPI_DATASIZE_8BIT (0x00000000U) #define SPI_DATASIZE_16BIT (0x00000001U)
#define SPI_POLARITY_LOW (0x00000000U) #define SPI_POLARITY_HIGH (0x00000001U)
#define SPI_PHASE_1EDGE (0x00000000U) #define SPI_PHASE_2EDGE (0x00000001U)
#define SPI_NSS_SOFT (0x00000000U) #define SPI_NSS_HARD_OUTPUT (0x00000001U) #define SPI_NSS_HARD_INPUT (0x00000002U)
#define SPI_BAUDRATEPRESCALER_2 (0x00000000U) #define SPI_BAUDRATEPRESCALER_4 (0x00000001U) #define SPI_BAUDRATEPRESCALER_8 (0x00000002U) #define SPI_BAUDRATEPRESCALER_16 (0x00000003U)
#define SPI_FIRSTBIT_MSB (0x00000000U) #define SPI_FIRSTBIT_LSB (0x00000001U)
SPI_HandleTypeDef HAL_SPI_Init(SPI_InitTypeDef *SPI_InitStruct);
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
void HAL_SPI_NSS_Control(SPI_HandleTypeDef hspi, uint8_t NSSState);
#endif
|
hal_spi.c
(简化的 SPI HAL):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include "hal_spi.h" #include "stm32f1xx_hal.h" #include "hal_gpio.h"
SPI_HandleTypeDef HAL_SPI_Init(SPI_InitTypeDef *SPI_InitStruct) { SPI_HandleTypeDef hspi = malloc(sizeof(SPI_HandleTypeDef)); if (hspi == NULL) { return NULL; }
hspi->Instance = SPI1; __HAL_RCC_SPI1_CLK_ENABLE();
hspi->Init.Mode = SPI_InitStruct->Mode; hspi->Init.Direction = SPI_InitStruct->Direction; hspi->Init.DataSize = SPI_InitStruct->DataSize; hspi->Init.ClockPolarity = SPI_InitStruct->ClockPolarity; hspi->Init.ClockPhase = SPI_InitStruct->ClockPhase; hspi->Init.NSS = SPI_InitStruct->NSS; hspi->Init.BaudRatePrescaler = SPI_InitStruct->BaudRatePrescaler; hspi->Init.FirstBit = SPI_InitStruct->FirstBit; hspi->Init.TIMode = SPI_InitStruct->TIMode; hspi->Init.CRCCalculation = SPI_InitStruct->CRCCalculation; hspi->Init.CRCPolynomial = SPI_InitStruct->CRCPolynomial;
if (HAL_SPI_Init_Ex(&hspi) != HAL_OK) { free(hspi); return NULL; }
return hspi; }
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout) { return HAL_SPI_Transmit_Ex(&hspi, pData, Size, Timeout); }
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout) { return HAL_SPI_Receive_Ex(&hspi, pData, Size, Timeout); }
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) { return HAL_SPI_TransmitReceive_Ex(&hspi, pTxData, pRxData, Size, Timeout); }
void HAL_SPI_NSS_Control(SPI_HandleTypeDef hspi, uint8_t NSSState) { GPIO_InitTypeDef nss_gpio; nss_gpio.Port = GPIO_PORT_A; nss_gpio.Pin = GPIO_PIN_4; nss_gpio.Mode = GPIO_MODE_OUTPUT_PP; nss_gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(&nss_gpio);
HAL_GPIO_WritePin(GPIO_PORT_A, GPIO_PIN_4, NSSState); }
|
2. 设备驱动层 (Device Driver Layer)
driver_oled.h
(OLED 显示屏驱动头文件):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #ifndef DRIVER_OLED_H #define DRIVER_OLED_H
#include <stdint.h> #include "hal_spi.h"
typedef void* OLED_HandleTypeDef;
OLED_HandleTypeDef DRIVER_OLED_Init(SPI_HandleTypeDef hspi);
void DRIVER_OLED_Clear(OLED_HandleTypeDef oled_handle);
void DRIVER_OLED_DrawChar(OLED_HandleTypeDef oled_handle, uint8_t x, uint8_t y, char chr, uint8_t size, uint8_t color);
void DRIVER_OLED_DrawString(OLED_HandleTypeDef oled_handle, uint8_t x, uint8_t y, const char *str, uint8_t size, uint8_t color);
void DRIVER_OLED_DrawPixel(OLED_HandleTypeDef oled_handle, uint8_t x, uint8_t y, uint8_t color);
#endif
|
driver_oled.c
(OLED 显示屏驱动源文件 - SSD1306 驱动示例):
#include "driver_oled.h"
#include "hal_gpio.h"
#include "hal_delay.h" // 假设有 HAL 延时函数
#define OLED_DC_PIN GPIO_PIN_8 // 数据/命令选择引脚 (DC) - 假设连接到 GPIOA Pin 8
#define OLED_RES_PIN GPIO_PIN_9 // 复位引脚 (RES) - 假设连接到 GPIOA Pin 9
#define OLED_CS_PIN GPIO_PIN_4 // 片选引脚 (CS) - 假设连接到 GPIOA Pin 4 (软件 NSS)
#define OLED_SPI_HANDLE hspi_oled // SPI 句柄 (在初始化时赋值)
static SPI_HandleTypeDef hspi_oled; // 全局 SPI 句柄,用于 OLED 驱动
// 初始化 OLED 引脚 GPIO
static void OLED_GPIO_Init(void) {
GPIO_InitTypeDef gpio_init_struct;
// DC 引脚配置
gpio_init_struct.Port = GPIO_PORT_A;
gpio_init_struct.Pin = OLED_DC_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(&gpio_init_struct);
// RES 引脚配置
gpio_init_struct.Port = GPIO_PORT_A;
gpio_init_struct.Pin = OLED_RES_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(&gpio_init_struct);
// CS 引脚配置 (软件 NSS,HAL_SPI_NSS_Control 函数会配置)
// 在 HAL_SPI_Init 中已经配置了 CS 引脚,这里不需要重复配置
}
// 发送命令到 OLED
static void OLED_SendCommand(uint8_t cmd) {
HAL_GPIO_WritePin(GPIO_PORT_A, OLED_DC_PIN, GPIO_PIN_RESET); // DC=0:发送命令
HAL_SPI_NSS_Control(OLED_SPI_HANDLE, GPIO_PIN_RESET); // CS=0:使能 SPI 从设备
HAL_SPI_Transmit(OLED_SPI_HANDLE, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_NSS_Control(OLED_SPI_HANDLE, GPIO_PIN_SET); // CS=1:禁用 SPI 从设备
}
// 发送数据到 OLED
static void OLED_SendData(uint8_t data) {
HAL_GPIO_WritePin(GPIO_PORT_A, OLED_DC_PIN, GPIO_PIN_SET); // DC=1:发送数据
HAL_SPI_NSS_Control(OLED_SPI_HANDLE, GPIO_PIN_RESET); // CS=0:使能 SPI 从设备
HAL_SPI_Transmit(OLED_SPI_HANDLE, &data, 1, HAL_MAX_DELAY);
HAL_SPI_NSS_Control(OLED_SPI_HANDLE, GPIO_PIN_SET); // CS=1:禁用 SPI 从设备
}
// OLED 初始化序列 (SSD1306 初始化命令)
static void OLED_InitSequence(void) {
HAL_Delay(100); // 上电延时
HAL_GPIO_WritePin(GPIO_PORT_A, OLED_RES_PIN, GPIO_PIN_RESET); // 复位 OLED
HAL_Delay(100);
HAL_GPIO_WritePin(GPIO_PORT_A, OLED_RES_PIN, GPIO_PIN_SET);
HAL_Delay(100);
OLED_SendCommand(0xAE); //display off
OLED_SendCommand(0x20); //set memory addressing mode
OLED_SendCommand(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
OLED_SendCommand(0xB0); //set page start address for page addressing mode,0-7
OLED_SendCommand(0xC8); //Set COM Output Scan Direction
OLED_SendCommand(0x00); //---set low column address
OLED_SendCommand(0x10); //---set high column address
OLED_SendCommand(0x40); //--set start line address - set maps address to display ram
OLED_SendCommand(0x81); //set contrast control register
OLED_SendCommand(0xFF); //亮度调节 0x00~0xff
OLED_SendCommand(0xA1); //set segment re-map 0 to 127 - column address 0 mapped to SEG0
OLED_SendCommand(0xA6); //set normal display
OLED_SendCommand(0xA8); //set multiplex ratio(1 to 64)
OLED_SendCommand(0x3F); //1/64 duty
}