STM32F103驱动8*8点阵屏:从点亮一个点到显示0-9数字的保姆级教程 STM32F103驱动8*8点阵屏从点亮一个点到显示0-9数字的保姆级教程第一次接触嵌入式开发时看到那些闪烁的LED点阵屏总有种莫名的兴奋。作为STM32的初学者你可能已经点亮过单个LED但面对8*8点阵屏时64个LED的组合让人既期待又忐忑。本文将带你从最基础的原理开始一步步实现数字0-9的动态显示过程中不仅会解释硬件连接细节还会深入剖析动态扫描的底层机制。1. 硬件准备与连接1.1 认识8*8点阵屏1088AS是一款常见的共阴极8*8LED点阵模块内部结构可以看作8行×8列的矩阵。每个LED的阳极连接列线阴极连接行线。当某一行被拉低接地同时某一列被拉高供电时对应位置的LED就会点亮。关键参数工作电压通常2.1-2.5V红色LED最大电流单点20mA整屏建议不超过160mA引脚排列双排16针行和列交替分布1.2 STM32F103ZE引脚分配我们使用GPIOA控制行阴极GPIOB控制列阳极。具体连接如下点阵引脚STM32引脚功能行1PA0阴极控制行2PA1阴极控制.........行8PA7阴极控制列1PB0阳极控制列2PB1阳极控制.........列8PB7阳极控制注意PB3和PB4默认是JTAG功能需要重映射为普通IO口才能使用。1.3 硬件连接实操准备杜邦线和面包板按照上表连接点阵屏和STM32检查所有连接是否牢固为STM32和点阵屏分别供电注意电压匹配// 硬件初始化关键代码示例 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); // 禁用JTAG释放PB3/PB42. 软件环境配置2.1 开发工具准备推荐使用Keil MDK作为开发环境安装Keil MDK-ARM最新版本安装STM32F1系列设备支持包新建工程选择STM32F103ZE器件配置调试工具如ST-Link2.2 工程文件结构建议采用模块化编程创建以下文件Project/ ├── Core/ │ ├── Inc/ │ │ └── led_matrix.h │ └── Src/ │ └── led_matrix.c ├── Drivers/ └── main.c2.3 时钟配置STM32F103ZE默认使用内部8MHz RC振荡器为获得更好的稳定性建议配置为外部晶振void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; // 配置HSE振荡器 RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; HAL_RCC_OscConfig(RCC_OscInitStruct); // 配置时钟树 RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2); }3. 基础驱动实现3.1 点亮单个LED理解点阵屏控制原理的最佳方式是从点亮一个LED开始设置对应行为低电平导通设置对应列为高电平供电保持一段时间后关闭void light_single_led(uint8_t row, uint8_t col) { // 所有行先置高关闭 GPIO_SetBits(GPIOA, 0xFF); // 所有列置低关闭 GPIO_ResetBits(GPIOB, 0xFF); // 点亮指定LED GPIO_ResetBits(GPIOA, 1 row); // 选中行 GPIO_SetBits(GPIOB, 1 col); // 选中列 HAL_Delay(100); // 保持点亮状态 // 关闭所有LED GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); }3.2 逐行扫描原理动态显示的核心是视觉暂留效应。通过快速逐行刷新人眼会看到稳定的图像选中第1行显示该行所有需要点亮的列保持极短时间通常1-5ms关闭所有LED移动到下一行重复过程完成8行后循环void row_scan_demo(void) { uint8_t row_patterns[8] { 0b00000001, // 第1行只有最右侧LED亮 0b00000010, // 第2行 0b00000100, // ... 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000 // 第8行只有最左侧LED亮 }; while(1) { for(int row 0; row 8; row) { // 关闭所有行和列 GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); // 设置当前行 GPIO_ResetBits(GPIOA, 1 row); // 设置当前行对应的列模式 GPIO_SetBits(GPIOB, row_patterns[row]); HAL_Delay(2); // 每行显示2ms } } }4. 数字显示实现4.1 数字编码方案每个数字可以表示为8×8的点阵图案。我们使用字节数组存储每个数字的列数据const uint8_t digit_patterns[10][8] { {0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00, 0x00}, // 0 {0x00, 0x21, 0x7F, 0x01, 0x00, 0x00, 0x00, 0x00}, // 1 {0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00, 0x00}, // 2 {0x22, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, 0x00}, // 3 {0x0C, 0x14, 0x24, 0x7F, 0x04, 0x00, 0x00, 0x00}, // 4 {0x72, 0x51, 0x51, 0x51, 0x4E, 0x00, 0x00, 0x00}, // 5 {0x3E, 0x49, 0x49, 0x49, 0x26, 0x00, 0x00, 0x00}, // 6 {0x40, 0x40, 0x4F, 0x70, 0x00, 0x00, 0x00, 0x00}, // 7 {0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, 0x00}, // 8 {0x32, 0x49, 0x49, 0x49, 0x3E, 0x00, 0x00, 0x00} // 9 };4.2 静态数字显示实现单个数字的静态显示void show_digit(uint8_t num) { if(num 9) return; while(1) { for(int row 0; row 8; row) { GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); GPIO_ResetBits(GPIOA, 1 row); GPIO_SetBits(GPIOB, digit_patterns[num][row]); HAL_Delay(2); } } }4.3 动态数字切换实现数字0-9的自动切换显示void show_digits_sequence(void) { uint8_t current_digit 0; uint32_t last_change 0; const uint32_t change_interval 1000; // 1秒切换一次 while(1) { uint32_t now HAL_GetTick(); // 检查是否需要切换数字 if(now - last_change change_interval) { current_digit (current_digit 1) % 10; last_change now; } // 显示当前数字 for(int row 0; row 8; row) { GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); GPIO_ResetBits(GPIOA, 1 row); GPIO_SetBits(GPIOB, digit_patterns[current_digit][row]); HAL_Delay(2); } } }5. 高级功能实现5.1 平滑滚动效果实现数字从左到右平滑滚动的效果void scrolling_digits(void) { uint8_t scroll_buffer[16]; // 两倍宽度缓冲区 uint8_t scroll_pos 0; while(1) { // 填充缓冲区当前数字在左侧下一个数字在右侧 for(int i 0; i 8; i) { scroll_buffer[i] digit_patterns[current_digit][i]; scroll_buffer[i8] digit_patterns[(current_digit1)%10][i]; } // 显示当前滚动位置 for(int frame 0; frame 8; frame) { for(int row 0; row 8; row) { GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); GPIO_ResetBits(GPIOA, 1 row); GPIO_SetBits(GPIOB, (scroll_buffer[row] frame) | (scroll_buffer[row8] (8-frame))); HAL_Delay(2); } } current_digit (current_digit 1) % 10; HAL_Delay(500); // 数字间停留 } }5.2 亮度控制技术通过PWM调节LED亮度配置TIM2或TIM3为PWM输出模式将列控制连接到PWM通道调节占空比控制亮度void pwm_init(void) { TIM_HandleTypeDef htim; TIM_OC_InitTypeDef sConfigOC {0}; htim.Instance TIM2; htim.Init.Prescaler 0; htim.Init.CounterMode TIM_COUNTERMODE_UP; htim.Init.Period 255; // 8位分辨率 htim.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 128; // 50%占空比 sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim, TIM_CHANNEL_1); }5.3 多数字显示扩展通过增加硬件和优化算法可以扩展显示更多内容级联多个8×8点阵模块使用移位寄存器如74HC595扩展IO口实现左右滚动长文本显示// 使用74HC595驱动多块点阵的示例代码 void shift_out(uint8_t data) { for(int i 0; i 8; i) { HAL_GPIO_WritePin(GPIOB, DATA_PIN, (data (1 (7-i))) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, CLOCK_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, CLOCK_PIN, GPIO_PIN_RESET); } HAL_GPIO_WritePin(GPIOB, LATCH_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, LATCH_PIN, GPIO_PIN_RESET); }6. 常见问题与调试技巧6.1 LED亮度不均匀现象不同位置的LED亮度差异明显解决方案检查限流电阻是否合适确保扫描时间均匀考虑使用恒流驱动芯片6.2 显示闪烁严重现象显示内容明显闪烁解决方法增加刷新频率建议100Hz优化代码结构减少扫描间隔使用定时器中断控制刷新// 使用定时器中断的示例 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t current_row 0; GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); GPIO_ResetBits(GPIOA, 1 current_row); GPIO_SetBits(GPIOB, digit_patterns[current_digit][current_row]); current_row (current_row 1) % 8; }6.3 部分LED不亮排查步骤单独测试每个LED是否正常检查对应引脚连接确认GPIO配置正确测量引脚输出电压提示使用万用表二极管测试档可以快速检查单个LED是否正常。6.4 功耗优化建议降低扫描电流通过增大限流电阻使用PWM调节整体亮度在不需要显示时关闭所有LED考虑使用低功耗模式void enter_low_power_mode(void) { // 关闭所有LED GPIO_SetBits(GPIOA, 0xFF); GPIO_ResetBits(GPIOB, 0xFF); // 配置为低功耗模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); }