用STM32F103的LL库驱动74hc595三位数码管

一、首先,用STM32CbueMX来生成时钟代码,当然不止三位数码管都可以

      1、新建工程什么的应该都会,来到重点配置环节,时钟记得配置为72M

       2、最终选择LL库,然后生成代码。                        

    3、打开main.c代码,在此位置整段复制以下代码即可,

// 共阳数码管段码表(0-9)
const uint8_t SEGMENT_CODE[] = 
{
    0xC
<think>我们已知: 1. 数码管型号:5461BS-1,这是一个四位共阳极数码管。 2. 要显示的数字:507(注意,四位数码管,所以应该是0507?或者我们要考虑显示为507,即第一位不显示或显示空格?但通常四位数码管,如果要显示507,我们一般会显示为“507 ”,即最后一位空,或者显示为“0507”?这里用户要求显示507,我们可以显示为“507 ”,即第一位显示5,第二位显示0,第三位显示7,第四位不显示。或者根据实际需求,也可以显示为“507”在右边,左边空,即第一位空,然后显示507。这里我们按常规,显示为“507 ”,即第四位空。但用户没有明确,我们按显示三位数字,第四位不显示(即消隐)来处理。 由于是共阳极数码管,所以公共端(位选)给高电平,段选给低电平,对应的段就会亮。 接线方案: 我们使用两个74HC595芯片级联来控制数码管。74HC595是串行输入并行输出的移位寄存器,可以节省IO口。 级联方式:第一个595的QH'(第9脚)连接到第二个595的SER(第14脚)。 STM32F103C8T6通过三个IO口控制两个74HC595: - SCK(移位时钟):接两个595的SCK(第11脚) - RCK(锁存时钟):接两个595的RCK(第12脚) - DATA(数据线):接第一个595的SER(第14脚) 两个74HC595级联后,共有16位输出。我们使用8位数码管,但这里我们只有四位数码管,所以实际上我们只需要8位(段选)和4位(位选)共12位。但是我们的模块是8位数码管模块,所以它可能用了两个595来控制8位数码管的8个段选和8个位选?但注意,通常两个595级联,第一个595控制段选(8位),第二个595控制位选(8位),这样8位数码管就可以独立控制。 但是我们的数码管是四位的,所以我们可以这样分配: - 第一个595的8位输出(Q0~Q7)分别接数码管的a,b,c,d,e,f,g,dp(即段选) - 第二个595的8位输出中,我们只用低4位(Q0~Q3)分别接四位共阳极数码管的位选(COM1~COM4),高4位不用。 注意:共阳极数码管,位选为高电平时选中该位。 显示原理: 我们采用动态扫描,每次只点亮一位数码管,依次点亮四位,利用人眼视觉暂留效果。 步骤: 1. 初始化STM32的IO口(配置为推挽输出)。 2. 编写向74HC595发送一个字节(8位)数据的函数,由于级联,我们需要发送两个字节(先发送位选数据,再发送段选数据?注意:在移位时,先发送的数据会进入第二个595,后发送的数据在第一个595。所以如果我们先发送位选数据(第二个595),再发送段选数据(第一个595),那么在移位完成后,第一个595的数据对应段选,第二个595的数据对应位选。但是这样在移位时,我们发送的第一个字节(位选)会被移到第二个595,而第二个字节(段选)会留在第一个595。然后锁存,两个595同时输出。 因此,我们发送数据的顺序是:先发送段选数据(第二个字节),再发送位选数据(第一个字节)?不对,应该是先发送要进入第二个595的数据(即位选),再发送要进入第一个595的数据(段选)。这样在移位过程中,位选数据被移到了第二个595,段选数据在第一个595。所以发送顺序是:先发送位选数据(第二个595),再发送段选数据(第一个595)。但是这样我们发送两个字节时,先发送的位选数据在移位过程中会被推到第二个595,后发送的段选数据留在第一个595。然后锁存,输出。 但是,我们通常希望发送的数据结构是: 第一个字节(高字节):段选(第一个595的输出) 第二个字节(低字节):位选(第二个595的输出) 然而,在移位时,先移入的是位选(因为位选在第二个595,需要先移进去),然后移入段选。所以当我们发送时,应该先发送段选数据(因为段选要进入第一个595,而第一个595靠近输出,所以后发送的段选数据会占据第一个595),再发送位选数据(先发送的位选数据会通过第一个595移到第二个595)。但这样不符合我们的数据结构。 实际上,级联时,数据先进入第一个595,然后从第一个595的QH'串出到第二个595的SER。所以,如果我们先发送位选数据(即第二个595要接收的数据),再发送段选数据(第一个595要接收的数据),那么在移位过程中,先移入段选数据(最后会进入第一个595),再移入位选数据(会进入第二个595)。这样,当移位完成后,第一个595中存放的是段选数据,第二个595中存放的是位选数据。然后锁存输出。 因此,发送顺序应该是:先发送段选数据(8位),再发送位选数据(8位)。这样,段选数据先进入,然后被位选数据推到了第一个595,而位选数据在第二个595。但是这样不对,因为先发送的数据会先进入移位寄存器,然后被后发送的数据推出去。所以实际上,先发送的段选数据会被推到级联的第二个595,而后发送的位选数据留在第一个595。这不符合我们的要求。 所以,我们需要调整:我们想要第一个595输出段选,第二个595输出位选。那么我们应该先发送位选数据(因为位选数据要去第二个595),再发送段选数据(段选数据留在第一个595)。这样,在发送时,先发送位选数据(8位),再发送段选数据(8位)。在移位过程中,先移入位选数据,然后移入段选数据,位选数据被推入第二个595,段选数据留在第一个595。 因此,发送顺序是:先发送位选数据(第二个595的数据),再发送段选数据(第一个595的数据)。这样,当我们发送完16位后,第一个595中存储的是段选数据,第二个595中存储的是位选数据。 但是,我们在程序中定义两个字节: - seg_data: 段选数据(8位) - bit_data: 位选数据(8位,但我们只用低4位) 然后发送时,先发送bit_data,再发送seg_data。 注意:由于级联,我们发送16位数据,先发送bit_data(高位字节),再发送seg_data(低位字节)?不对,应该是先发送的bit_data会进入移位寄存器的最前端,然后被后面的数据推出去到第二个595。所以,我们发送的顺序应该是:先发送bit_data,再发送seg_data。 具体发送函数: - 拉低SCK(移位时钟) - 从高位到低位发送bit_data(8位) - 从高位到低位发送seg_data(8位) - 拉高SCK(移位时钟)?不对,应该是每发送一位,产生一个上升沿移位。具体步骤: for (i=0; i<8; i++) { // 先发送seg_data的最高位(因为seg_data是第二个字节,但我们要后发送它,所以先发送bit_data) // 但实际上,我们这里先发送bit_data } 所以,我们分别发送两个字节,先发送bit_data(从高位到低位),再发送seg_data(从高位到低位)。这样,bit_data的每一位先被移入,然后seg_data的每一位被移入。当全部移入后,bit_data被推到了第二个595,seg_data在第一个595。 3. 动态显示:分别显示每一位,每一位显示一定时间(比如1ms),然后切换到下一位。 段码表(共阳极,注意:共阳极是低电平点亮,所以段码表是低电平有效的): 0: 0xC0 (1100 0000) -> a,b,c,d,e,f灭?不对,共阳极,给低电平点亮。所以0的段码应该是点亮a,b,c,d,e,f,不点亮g和dp,即1100 0000(二进制)-> 0xC0 但是注意:我们模块的段选线连接顺序。通常数码管的段顺序是:a,b,c,d,e,f,g,dp,对应74HC595的Q0~Q7。所以: 0: a,b,c,d,e,f亮 -> 0b11000000 -> 0xC0 1: b,c亮 -> 0b11111001 -> 0xF9 2: a,b,d,e,g亮 -> 0b10100100 -> 0xA4 3: a,b,c,d,g亮 -> 0b10110000 -> 0xB0 4: b,c,f,g亮 -> 0b10011001 -> 0x99 5: a,c,d,f,g亮 -> 0b10010010 -> 0x92 6: a,c,d,e,f,g亮 -> 0b10000010 -> 0x82 7: a,b,c亮 -> 0b11111000 -> 0xF8 8: 全部亮 -> 0b10000000 -> 0x80 9: a,b,c,d,f,g亮 -> 0b10010000 -> 0x90 其他:比如空格(不显示)则段码全1(0xFF) 注意:这里我们使用的是共阳极,所以段选为0表示亮,1表示灭。 位选:我们使用低4位,分别对应数码管的第1位到第4位(从左到右)。例如,要显示第一位,则位选数据为0b00000001(即0x01),第二位0x02,第三位0x04,第四位0x08。注意:因为我们的位选是共阳极,所以给高电平选中。 但是,我们使用两个595,第二个595的输出Q0~Q7,我们只用Q0~Q3(低4位)作为位选,所以位选数据我们只使用低4位,高4位设为0(不选中其他位)。 要显示“507 ”(第四位空),那么: 第一位:5 -> 段码0x92(二进制10010010) 第二位:0 -> 段码0xC0 第三位:7 -> 段码0xF8 第四位:空 -> 段码0xFF 动态扫描时,我们依次显示每一位: 第一步:发送位选数据(0x01,即选中第一位),段选数据(0x92),然后锁存输出,延时1ms。 第二步:发送位选数据(0x02),段选数据(0xC0),延时1ms。 第三步:发送位选数据(0x04),段选数据(0xF8),延时1ms。 第四步:发送位选数据(0x08),段选数据(0xFF),延时1ms。 注意:由于人眼视觉暂留,刷新频率要大于50Hz,所以四位数码管,每位的显示时间1ms,则一周期4ms,刷新频率250Hz,满足要求。 程序框架: 1. 定义引脚: #define DATA_PIN GPIO_Pin_0 // 假设使用GPIOA的0号引脚 #define SCK_PIN GPIO_Pin_1 #define RCK_PIN GPIO_Pin_2 2. 初始化GPIO:配置为推挽输出,初始化时全部置低。 3. 写一个函数:void HC595_Send_Data(uint8_t bit_data, uint8_t seg_data) 这个函数先发送bit_data(8位),再发送seg_data(8位),然后产生一个锁存信号。 4. 定义段码表:const uint8_t seg_code[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; // 0~9的段码 5. 主循环中,不断扫描四位: uint8_t display_digit[4] = {5,0,7,10}; // 10表示空,我们定义空用0xFF,即段码表中没有,我们单独处理 uint8_t bit_select[4] = {0x01, 0x02, 0x04, 0x08}; // 位选 for (int i=0; i<4; i++) { uint8_t seg; if (display_digit[i] == 10) // 假设10代表空 seg = 0xFF; else seg = seg_code[display_digit[i]]; HC595_Send_Data(bit_select[i], seg); delay_ms(1); // 延时1ms } 注意:在发送数据后,需要锁存,将移位寄存器的数据输出到锁存器。锁存信号:在发送完16位数据后,拉高RCK(锁存时钟)一个脉冲(先低再高,然后拉低)。 具体发送函数HC595_Send_Data的实现: void HC595_Send_Data(uint8_t bit_data, uint8_t seg_data) { // 先发送bit_data(8位),从高位到低位 // 再发送seg_data(8位),从高位到低位 uint8_t i; // 拉低RCK,在发送过程中保持低电平 GPIO_ResetBits(GPIOA, RCK_PIN); // 先发送bit_data(第二个595的数据) for (i=0; i<8; i++) { GPIO_ResetBits(GPIOA, SCK_PIN); // 时钟拉低 // 判断最高位,然后设置数据线 if (bit_data & 0x80) GPIO_SetBits(GPIOA, DATA_PIN); else GPIO_ResetBits(GPIOA, DATA_PIN); bit_data <<= 1; // 左移一位 // 产生一个上升沿,移位 GPIO_SetBits(GPIOA, SCK_PIN); // 这里可以加一个小的延时,或者不加,因为STM32速度很快 } // 再发送seg_data(第一个595的数据) for (i=0; i<8; i++) { GPIO_ResetBits(GPIOA, SCK_PIN); if (seg_data & 0x80) GPIO_SetBits(GPIOA, DATA_PIN); else GPIO_ResetBits(GPIOA, DATA_PIN); seg_data <<= 1; GPIO_SetBits(GPIOA, SCK_PIN); } // 发送完毕,产生锁存信号:拉高RCK,再拉低 GPIO_SetBits(GPIOA, RCK_PIN); // 延时一下 delay_us(10); GPIO_ResetBits(GPIOA, RCK_PIN); } 注意:由于我们发送的是两个字节,先发送bit_data(位选)到第二个595,再发送seg_data(段选)到第一个595,所以锁存后,两个595同时输出。 但是,在动态扫描时,由于我们每次只点亮一位,所以位选数据每次只选中一位。而段选数据则是对应该位的数字。 延时函数:可以使用SysTick或者简单的循环延时。 以上是程序的基本思路。 但是,我们还需要考虑硬件连接: - 数码管的段选线(a,b,c,d,e,f,g,dp)是否按照顺序连接到了第一个595的Q0~Q7(注意:Q0对应a,Q1对应b,...,Q7对应dp)?这个需要根据实际硬件连接调整段码表。如果连接顺序不同,段码表需要调整。 另外,位选线:我们假设位选线是低4位(第二个595的Q0~Q3)分别接数码管的第1位到第4位。注意:共阳极,所以位选高电平选中。 如果实际连接不同,需要调整。 由于用户没有提供具体的硬件连接图,我们按照常见的连接方式编写程序。用户需要根据实际连接调整。 最后,我们给出完整的代码框架(基于标准外设)。 注意:这里我们使用GPIOA的0、1、2引脚分别作为DATA、SCK、RCK。 初始化代码: 在main函数之前: 定义引脚和段码表,以及发送函数。 我们使用简单的延时函数(循环延时)。 完整代码:</think>以下是基于STM32F103C8T6驱动5461BS-1四位数码管显示"507"的完整程序代码及解析。5461BS-1是共阳极四位数码管,采用动态扫描方式驱动,通过两个级联的74HC595芯片实现控制(节省IO资源)[^1][^2]。 --- ### 硬件连接 | STM32引脚 | 74HC595引脚 | 功能 | |-----------|-------------|------------| | PA0 | SER (14) | 串行数据 | | PA1 | SRCLK (11) | 移位时钟 | | PA2 | RCLK (12) | 锁存时钟 | | VCC | VCC | 电源 | | GND | GND | 地线 | 数码管段选(a-g,dp)连接第一个74HC595的Q0-Q7,位选(COM1-COM4)连接第二个74HC595的Q0-Q3。 --- ### 程序代码(Keil MDK环境) ```c #include "stm32f10x.h" // 引脚定义 #define DATA_PIN GPIO_Pin_0 // PA0 #define SCK_PIN GPIO_Pin_1 // PA1 #define RCK_PIN GPIO_Pin_2 // PA2 #define GPIO_PORT GPIOA // 共阳极数码管段码表 (0-9) const uint8_t SEG_CODE[10] = { 0xC0, // 0 (11000000) 0xF9, // 1 (11111001) 0xA4, // 2 (10100100) 0xB0, // 3 (10110000) 0x99, // 4 (10011001) 0x92, // 5 (10010010) <- 需要显示的数字 0x82, // 6 (10000010) 0xF8, // 7 (11111000) <- 需要显示的数字 0x80, // 8 (10000000) 0x90 // 9 (10010000) }; // 位选控制 (1<<0:COM1, 1<<1:COM2, ...) const uint8_t DIGIT_SELECT[4] = {0x01, 0x02, 0x04, 0x08}; // 向74HC595发送16位数据 (高位在前) void HC595_SendData(uint16_t data) { GPIO_ResetBits(GPIO_PORT, RCK_PIN); // 锁存时钟拉低 for (uint8_t i = 0; i < 16; i++) { GPIO_ResetBits(GPIO_PORT, SCK_PIN); // 移位时钟拉低 // 发送当前位 (高位先发) if (data & 0x8000) GPIO_SetBits(GPIO_PORT, DATA_PIN); else GPIO_ResetBits(GPIO_PORT, DATA_PIN); data <<= 1; // 移位准备下一位 GPIO_SetBits(GPIO_PORT, SCK_PIN); // 移位时钟上升沿 } GPIO_SetBits(GPIO_PORT, RCK_PIN); // 锁存时钟上升沿(输出数据) } // 初始化GPIO void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = DATA_PIN | SCK_PIN | RCK_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIO_PORT, &GPIO_InitStruct); // 初始状态置低 GPIO_ResetBits(GPIO_PORT, DATA_PIN | SCK_PIN | RCK_PIN); } // 动态显示数字 void DisplayNumber(uint16_t num) { uint8_t digits[4]; // 分解数字:digits[0]=5, digits[1]=0, digits[2]=7, digits[3]=空 digits[0] = num / 100; // 百位: 5 digits[1] = (num / 10) % 10; // 十位: 0 digits[2] = num % 10; // 个位: 7 digits[3] = 10; // 第四位不显示(自定义值) for (uint8_t i = 0; i < 4; i++) { uint8_t seg = (digits[i] <= 9) ? SEG_CODE[digits[i]] : 0xFF; // 不显示时全关 uint16_t output = (DIGIT_SELECT[i] << 8) | seg; // 高8位:位选, 低8位:段选 HC595_SendData(output); Delay_ms(3); // 每位数码管显示3ms(动态扫描) } } // 简单延时函数 void Delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 8000; i++); } int main(void) { GPIO_Config(); // 初始化GPIO while (1) { DisplayNumber(507); // 持续显示507 } } ``` --- ### 关键代码解析 1. **动态扫描原理** 通过快速切换位选信号(每次使能1位数码管),依次显示"5"→"0"→"7"→空,利用视觉暂留效应形成连续显示[^2]。 2. **74HC595级联控制** - 发送16位数据:高8位控制位选(第二个595),低8位控制段选(第一个595) - 示例数据包:`0x01 0x92` 表示在COM1显示数字5(位选0x01 + 段选0x92) 3. **显示数据格式** ```math \text{输出数据} = (\text{位选编码} \ll 8) \lor \text{段选编码} ``` 例如显示十位数字"0": - 位选:`0x02` (COM2) - 段选:`0xC0` (数字0) - 输出:`0x0200 | 0x00C0 = 0x02C0` --- ### 注意事项 1. **消隐处理** 第四位设置为`digits[3]=10`,段码返回`0xFF`(所有段关闭),实现该位数码管完全关闭。 2. **刷新频率** 每位数码管显示3ms,4位总周期12ms(≈83Hz),高于人眼闪烁临界值(50Hz)[^2]。 3. **硬件适配** 若实际段码顺序(a-g,dp)与定义不符,需调整`SEG_CODE`数组的二进制位顺序。 4. **电流限制** 共阳极数码管需在VCC串联限流电阻(每段100-220Ω),防止过流损坏。 --- ### 优化建议 1. 使用定时器中断实现扫描,避免`Delay_ms`占用CPU 2. 添加小数点控制(修改段码最高位) 3. 实现滚动显示效果(通过移位缓冲区)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值