cpp
复制代码
/**
******************************************************************************
* @file seg_display.h
* @brief 74HC595驱动的4位7段数码管模块的驱动头文件
* @author Gemini
* @date 2025-07-26
* @version 3.0 (Final Clean)
* @note 本驱动为非阻塞式,依赖一个定时器中断进行动态扫描刷新。
* - 依赖正确的GPIO速度配置(Low Speed)来确保时序。
* - 提供高级字符串显示接口,支持小数点和对齐。
* - 初始化函数可自动配置定时器,具备跨平台可移植性。
******************************************************************************
*/
#ifndef __SEG_DISPLAY_H
#define __SEG_DISPLAY_H
#include "main.h"
#include <stdbool.h>
/* --------------------------- 公共宏定义 --------------------------- */
#define SEG_DIGITS 4 // 数码管的位数
#define SEG_BLANK 16 // 用于熄灭数码管的索引
#define SEG_DASH 17 // 用于显示'-'的索引
/* --------------------------- 公共数据类型 --------------------------- */
/**
* @brief 对齐方式枚举
*/
typedef enum {
SEG_ALIGN_LEFT, // 左对齐
SEG_ALIGN_RIGHT // 右对齐
} SEG_Align_t;
/**
* @brief 定义数码管一位的显示数据结构
*/
typedef struct {
uint8_t num; // 要显示的数字/字符索引
bool show_dp; // 是否显示该位的小数点
} SEG_Digit_t;
/* --------------------------- 公共函数原型 --------------------------- */
/**
* @brief 初始化数码管驱动并自动配置定时器
* @param htim: 用于驱动动态扫描的定时器句柄指针 (例如 &htim7).
* @param refresh_freq_hz: 每一位数码管的刷新频率 (单位: Hz). 推荐值为 400-800.
* @retval None
*/
void SEG_Init(TIM_HandleTypeDef *htim, uint32_t refresh_freq_hz);
/**
* @brief 高级接口:显示一个字符串(支持整数和小数)
* @param str: 要显示的字符串,例如 "12.34", "99.", "0.1", "5"。
* @param alignment: 对齐方式 (SEG_ALIGN_LEFT 或 SEG_ALIGN_RIGHT).
* @retval None
*/
void SEG_ShowString(const char* str, SEG_Align_t alignment);
/**
* @brief 清空所有数码管(全部熄灭)
* @param None
* @retval None
*/
void SEG_Clear(void);
/**
* @brief 数码管定时器中断服务函数 (需要在全局回调中调用)
* @param htim: 触发中断的定时器句柄指针.
* @retval None
*/
void SEG_Timer_ISR(TIM_HandleTypeDef *htim);
#endif /* __SEG_DISPLAY_H */
cpp
复制代码
/**
******************************************************************************
* @file seg_display.c
* @brief 74HC595驱动的4位7段数码管模块的驱动源文件
* @author Gemini
* @date 2025-07-26
* @version 3.5 (Negative Number Support)
* @note v3.5: 增强了SEG_ShowString函数,使其能够正确解析和显示负数。
******************************************************************************
*/
#include "seg_display.h"
#include <string.h>
/* --------------------------- 私有静态常量 --------------------------- */
// 共阳极数码管段码表 "0123456789AbCdEF" "熄灭" "-"
static const uint8_t LED_table[18] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E,
0xFF, 0xBF
};
// 位选码表 (0x01对应最右边一位)
static const uint8_t wei_table[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
/* --------------------------- 私有静态变量 --------------------------- */
static TIM_HandleTypeDef* p_display_timer;
static volatile SEG_Digit_t display_data[SEG_DIGITS];
/* --------------------------- 私有函数原型 --------------------------- */
static void SEG_SendByte(uint8_t byte);
static void SEG_Latch(void);
static void SEG_SetBuffer(const SEG_Digit_t* buffer);
/* --------------------------- 公共函数实现 --------------------------- */
void SEG_Init(TIM_HandleTypeDef *htim, uint32_t refresh_freq_hz)
{
p_display_timer = htim;
uint32_t timer_clock_frequency = HAL_RCC_GetPCLK1Freq();
if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1)
{
timer_clock_frequency *= 2;
}
uint32_t period_value = 19;
uint32_t prescaler_value = (timer_clock_frequency / (refresh_freq_hz * (period_value + 1))) - 1;
htim->Instance = p_display_timer->Instance;
htim->Init.Prescaler = prescaler_value;
htim->Init.CounterMode = TIM_COUNTERMODE_UP;
htim->Init.Period = period_value;
htim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(htim) != HAL_OK)
{
Error_Handler();
}
HAL_GPIO_WritePin(SEG_SCLK_GPIO_Port, SEG_SCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SEG_RCLK_GPIO_Port, SEG_RCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SEG_DIO_GPIO_Port, SEG_DIO_Pin, GPIO_PIN_RESET);
SEG_Clear();
HAL_TIM_Base_Start_IT(p_display_timer);
}
void SEG_Clear(void)
{
for(int i = 0; i < SEG_DIGITS; i++)
{
display_data[i].num = SEG_BLANK;
display_data[i].show_dp = false;
}
}
void SEG_ShowString(const char* str, SEG_Align_t alignment)
{
// 1. 初始化
SEG_Digit_t parsed_digits[SEG_DIGITS];
int digit_count = 0;
bool is_negative = false;
const char* p_str = str;
// 2. 检查负号
if (*p_str == '-') {
is_negative = true;
p_str++; // 指针跳过负号
}
// 3. 解析数字和小数点
for (int i = 0; p_str[i] != '\0' && digit_count < SEG_DIGITS; i++)
{
if (p_str[i] >= '0' && p_str[i] <= '9')
{
parsed_digits[digit_count].num = p_str[i] - '0';
parsed_digits[digit_count].show_dp = (p_str[i+1] == '.');
if (parsed_digits[digit_count].show_dp) {
i++; // 跳过小数点
}
digit_count++;
}
}
// 4. 创建最终显示缓冲区并清空
SEG_Digit_t final_buffer[SEG_DIGITS];
for(int i = 0; i < SEG_DIGITS; i++) {
final_buffer[i].num = SEG_BLANK;
final_buffer[i].show_dp = false;
}
// 5. 计算总字符数和起始物理位置
int total_chars = digit_count + (is_negative ? 1 : 0);
int start_pos; // 物理位置 (0=最右边)
if (alignment == SEG_ALIGN_LEFT) {
start_pos = SEG_DIGITS - 1;
} else { // SEG_ALIGN_RIGHT
start_pos = total_chars - 1;
}
// 6. 填充最终缓冲区
int current_pos = start_pos;
// 如果是负数,先放置负号
if (is_negative) {
if (current_pos >= 0 && current_pos < SEG_DIGITS) {
final_buffer[current_pos].num = SEG_DASH;
final_buffer[current_pos].show_dp = false;
}
current_pos--;
}
// 接着放置数字
for (int i = 0; i < digit_count; i++) {
if (current_pos >= 0 && current_pos < SEG_DIGITS) {
final_buffer[current_pos] = parsed_digits[i];
}
current_pos--;
}
// 7. 将准备好的缓冲区内容设置到驱动
SEG_SetBuffer(final_buffer);
}
void SEG_Timer_ISR(TIM_HandleTypeDef *htim)
{
if (htim->Instance == p_display_timer->Instance)
{
static uint8_t digit_index = 0; // 0 = 最右边一位
uint8_t char_index = display_data[digit_index].num;
bool dp_state = display_data[digit_index].show_dp;
uint8_t segment_data = LED_table[char_index];
if (dp_state)
{
segment_data &= 0x7F;
}
uint8_t digit_select_data = wei_table[digit_index];
SEG_SendByte(segment_data);
SEG_SendByte(digit_select_data);
SEG_Latch();
digit_index = (digit_index + 1) % SEG_DIGITS;
}
}
/* --------------------------- 私有函数实现 --------------------------- */
static void SEG_SetBuffer(const SEG_Digit_t* buffer)
{
// 使用 memcpy 优化缓冲区复制。
memcpy((void*)display_data, buffer, sizeof(display_data));
}
static void SEG_SendByte(uint8_t byte)
{
for (int i = 0; i < 8; i++)
{
HAL_GPIO_WritePin(SEG_DIO_GPIO_Port, SEG_DIO_Pin, (byte & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
byte <<= 1;
HAL_GPIO_WritePin(SEG_SCLK_GPIO_Port, SEG_SCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SEG_SCLK_GPIO_Port, SEG_SCLK_Pin, GPIO_PIN_SET);
}
}
static void SEG_Latch(void)
{
HAL_GPIO_WritePin(SEG_RCLK_GPIO_Port, SEG_RCLK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SEG_RCLK_GPIO_Port, SEG_RCLK_Pin, GPIO_PIN_SET);
}