当前位置: 首页 > news >正文

STM32即插即用HAL库驱动系列——4位串行数码管显示

使用优信电子的四位串行数码管,595驱动。

实现功能:

  1. 高级功能封装,调用函数,参数填入要显示的字符串,即可自动解析并显示。
  2. 支持左对齐、右对齐切换。
  3. 支持小数点、负数显示。
  4. 与cubemx设置完全解耦,无需参数设置,真正即插即用。

驱动部分

.h文件

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

.c文件

/********************************************************************************* @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_RIGHTstart_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);
}

使用示例:

在main.c: USER CODE BEGIN 2到USER CODE END 3部分:

/* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM7_Init();/* USER CODE BEGIN 2 */// 运行数码管初始化SEG_Init(&htim7,500);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */float tmp=-0.1f;for(int i=0;i<10000;++i){tmp+=(float)0.01;sprintf(display_string_buffer, "%.2f", tmp);SEG_ShowString(display_string_buffer,SEG_ALIGN_RIGHT);HAL_Delay(400);}}/* USER CODE END 3 */
}

在main.c: USER CODE BEGIN 4到USER CODE END 4部分:

/* USER CODE BEGIN 4 *//*** @brief  定时器周期溢出回调函数* @param  htim: 定时器句柄指针* @retval None*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{// 判断中断是否来自于我们用于数码管刷新的TIM7if (htim->Instance == TIM7){// 如果是,则调用我们驱动中写好的中断服务程序SEG_Timer_ISR(htim);}
}/* USER CODE END 4 */

使用说明:

  • 记得在main.c里面include对应驱动的.h;
  • 同时,cubemx里面开一个tim计时器,任意计时器,任意参数均可,建议用tim7这种基础计时器,比较省资源;
  • seg_init的参数,第一个是你驱动数码管的定时器,第二个是你的目标刷新率,即数码管刷新速率,单位Hz。
http://www.dtcms.com/a/327845.html

相关文章:

  • Pandas数据处理与分析实战:Pandas数据处理与分析入门-选择与过滤
  • uniapp -- 小程序处理与设备通讯 GBK/GB2312 编码问题。
  • 记一次 .NET 某汽车控制焊接软件 卡死分析
  • 腾讯云terraform学习教程
  • 传输线的效应
  • 【MAUI】在 .NET MAUI 中实现全局异常捕获的完整指南
  • 五、Nginx、RabbitMQ和Redis在Linux中的安装和部署
  • DAY41 简单CNN
  • PostgreSQL——数据查询
  • PyCharm Community 2024.2.3.exe 安装教程(详细步骤,附安装包下载)
  • Docker守护进程安全加固在香港VPS环境的操作标准
  • vue3使用插槽写一个自定义瀑布列表
  • 海康视觉相机驱动软件参数配置
  • 用 Docker 安装并启动 MySQL:从零到实战的完整指南
  • vivo Pulsar 万亿级消息处理实践(2)-从0到1建设 Pulsar 指标监控链路
  • 人工智能与金融:金融行业的革新
  • 计算机网络摘星题库800题笔记 第3章 数据链路层
  • linux Phy驱动开发之mido总线介绍
  • 打印流水号标签
  • 三防手机和防爆手机的本质区别是什么?
  • INSAR数据处理---ENVI5.6(Sarscape)
  • 【从零开始java学习|第三篇】变量与数据类型的关联
  • 秋招笔记-8.9
  • 【网络运维】Linux和自动化: Ansible基础实践
  • SynAdapt:通过合成连续思维链实现大语言模型的自适应推理
  • 机器学习第十课之TF-IDF算法(红楼梦文本分析)
  • 服务器节点技术解析:从架构原理到家庭实践的全维度指南
  • 文件IO函数实现
  • 异或和查询
  • 【报错处理】mount: /boot/efi: unknown filesystem type ‘LVM2_member‘.