# bl616-74hc595 **Repository Path**: jrobot_Q_Q/bl616-74hc595 ## Basic Information - **Project Name**: bl616-74hc595 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-03 - **Last Updated**: 2024-06-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # M61-32SU开发板 74hc595芯片数码管驱动 翻箱倒柜找到了多年以前买的8段8位数码管,想找商家要原理图资料没想到已经把我拉黑了,还是可以看到背后的芯片是74hc595的芯片,最后还是决定点亮它 ## 数码管 ![数码管](image/README/数码管.png) 如图所示是德飞莱的数码管8段(带小数点)8位 该模块是有两个74hc595芯片级联,和两个4位数码管组成,每个数码管的段引脚全都连接在同一个74hc595芯片上,每个数码管的公共引脚全都接在另一个75hc595芯片上,这样就可以通过一个74hc595控制选择数码管的段,另一个74hc595就可以控制选择那一位数码管 ### 数码管原理图 数码管一般有7段和8段之分,8段的多了一个小数点,每个段都是一个发光led,分为共阴极和共阳极,共阴极是每个段的负极连接在一起,共阳极是每个段的正极连接在一起,原理图如下所示 ![数码管原理图](image/README/数码管原理图.jpg) 以共阴极数码管为例要想点亮a段就需要把数码管的a脚接高电平,公共端接GND就可以点亮,如果要显示数字 1,则需要把b和c接高电平,其它接低电平,公共端同样接GND即可点亮b和c段显示为数字1 ### 存在的问题 一位的数码管都有8个段引脚,要想控制8位的岂不是要64个GPIO才可以控制吗?单片机的GPIO资源是很宝贵的,也不会只控制几位的数码管就浪费掉那么多的GPIO资源 解决问题也很简单,既然是不想浪费那么多io口,那么找到可以利用少量的io口分别控制多个输出的芯片就可以了,74hc595正是这样的芯片 ## 74hc595芯片 74hc595是一个8位的高速的CMOS芯片,支持TTL电平控制,把输入的串行数据转成并行数据,有以下特点 - 8bit串行输入 - 8bit串行输出或并行输出 - 内部含有存储寄存器控制3态输出 - 支持清空位移寄存器 - 支持多级串联 芯片引脚如下 ![74hc595芯片引脚](image/README/74hc595芯片引脚.png) DS引脚是串行输入引脚,Q0~Q7为并行输出0~7代表0~7bit位,Q7S引脚为溢出输出引脚,当串行输入的数据超过8bit是,高位就会从Q7S输出,利用这个特性可以把Q7S和另一个74hc595芯片的DS引脚就做到级联控制更多的引脚 具体的引脚功能如下 ![595引脚功能](image/README/595引脚功能.png) ### 工作原理 ![595芯片工作原理](image/README/595芯片工作原理.gif) 当SHCP处于上升沿时,会从DS引脚读取1bit数据放入移位寄存器(位移寄存器的值会一位一位往前移动),当STCP处于上升沿时会把位移寄存器中的值存储到锁存器中,如果此时OE引脚是低电平就会根据锁存器里面的值更新Q0~Q7引脚的状态 ### 8位数码管 ![8位数码管原理图](image/README/8位数码管原理图.png) 通过前面的了解的知识可以看到该模块由两个74hc595级联来控制的8位8段共阴极数码管 其中第一个74hc595连接的是数码管的8个段引脚,另一个74hc595连接的是每个数码管的公共引负脚8位数码管也就有8个 **结论:** 第一个595芯片控制显示数码管的段,第二个595芯片控制的哪一位的数码管,DIO输入引脚输入16bit数据,高8bit是位选功能,控制哪个位显示,低8bit是段选功能,控制数码管的哪个段显示,根据前面的595芯片介绍DIO输入是先输入高位 ## 代码 模块需要控制的有移位寄存器时序输入、锁存器时序输入以及串口数据输入一共三个,定义dev结构体如下 ```c typedef struct { //串口数据 uint8_t pin_ser; //移位寄存器时序 uint8_t pin_clk; //锁存器时序 uint8_t pin_st; //设置pin状态 int (*set_dev_data)(uint8_t pin, uint8_t data); //延时函数 void (*delay_fun)(uint32_t us); //初始化gpio void (*gpio_init)(uint8_t pin, uint32_t cfgset); }digit_dev_t; ``` 另外还定义了两组数据,分别是控制段码的数据和控制位码的数据 根据上面的8位数码管模块的原理图可以得到数码管的a-h段引脚分别接到595芯片的Q0~Q7引脚,也就是1个字节的第一位控制a,第二位控制b。。。第8位控制h段,如果要显示1需要点亮b和c两段即可,因为是共阴极数码管,所以对应的b和c要是高电平,也就是段a-h对应的数据分别为0000 0110,转成16进制为0x06 显然我们可以的到控制段显示的数据为 ```c unsigned char digit_tab[] = { 0x3f,//0 0x06,//1 0x5b,//2 0x4f,//3 0x66,//4 0x6d,//5 0x7d,//6 0x07,//7 0x7f,//8 0x6f,//9 0x77,//a 0x7c,//b 0x39,//c 0x5e,//d 0x79,//e 0x71,//f 0x00,//全关 }; ``` 位码是控制哪一位数码管被点亮,因为是共阴极,所以需要把对应的电平拉低即可点亮,显然可以得到位码数据如下: ```c unsigned char digit_choose_bit[] = { 0xfe,0xfd,0xfb,0xf7, 0xef,0xdf,0xbf,0x7f, }; ``` 定义595对应的函数有下面三个 ```c //初始化设备 void hc595_init(digit_dev_t* dev); /** * * @brief 动态刷新显示,需要循环调用 * @param dev * @param number * 整数模式:12345678 * 小数模式:1234.5678 * 负数模式:-1234567 * :-1234.567 */ void hc595_desplay(digit_dev_t* dev, double number); //发送高八位,第八位数据 void hc595_send_byte2(digit_dev_t* dev, unsigned char dataH, unsigned char dataL); ``` 74hc595.c实现代码如下 ```c #include "74hc595.h" #include unsigned char digit_tab[] = { 0x3f,//0 0x06,//1 0x5b,//2 0x4f,//3 0x66,//4 0x6d,//5 0x7d,//6 0x07,//7 0x7f,//8 0x6f,//9 0x77,//a 0x7c,//b 0x39,//c 0x5e,//d 0x79,//e 0x71,//f 0x00,//全关 }; unsigned char digit_choose_bit[] = { 0xfe,0xfd,0xfb,0xf7, 0xef,0xdf,0xbf,0x7f, }; #define PIN_RESET(dev) \ do{ \ dev->set_dev_data(dev->pin_clk,0); \ dev->set_dev_data(dev->pin_st,0); \ }while(0) #define ST_REFRESH(dev) \ do{ \ dev->set_dev_data(dev->pin_st,0); \ dev->set_dev_data(dev->pin_st,1); \ }while(0) void hc595_init(digit_dev_t* dev) { dev->gpio_init(dev->pin_clk, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0); dev->gpio_init(dev->pin_ser, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0); dev->gpio_init(dev->pin_st, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0); PIN_RESET(dev); } /** * @brief 发送 一字节数据到595串口引脚 * @param dev * @param data */ static void hc595_send_char_impl(digit_dev_t* dev, unsigned char data) { for (int i = 0; i < 8;i++) { //从高位写入 if (data >= 0x80) { dev->set_dev_data(dev->pin_ser, 1); } else { dev->set_dev_data(dev->pin_ser, 0); } //上升沿触发写入 dev->set_dev_data(dev->pin_clk, 0); dev->set_dev_data(dev->pin_clk, 1); data <<= 1; } } /** * @brief 计算小数点位数 * @param number * @return size_t */ static size_t deimal_size(double number) { size_t size = 0; while ((number - (int)number) != 0) { number *= 10; size++; } return size; } void hc595_send_byte2(digit_dev_t* dev, unsigned char dataH, unsigned char dataL) { PIN_RESET(dev); hc595_send_char_impl(dev, dataH); hc595_send_char_impl(dev, dataL); ST_REFRESH(dev); PIN_RESET(dev); } /** * @brief 更新buf到锁存器中 * @param dev * @param buf */ static void hc595_refresh_buf(digit_dev_t* dev, unsigned char* buf) { static int index = 0; for (;index < 8;index++) { //消隐 hc595_send_byte2(dev, 0xff, 0x00); hc595_send_byte2(dev, digit_choose_bit[index], buf[index]); dev->delay_fun(2000); } index = 0; } void hc595_desplay(digit_dev_t* dev, double number) { PIN_RESET(dev); //默认0x00不显示 unsigned char buf[8] = { 0x00 }; if (number > 99999999 || number < -9999999) { printf("show range: -9999999 ~ 99999999"); for (int i = 0; i < 8;i++) { buf[i] = MINUS; } //显示-------- hc595_refresh_buf(dev, buf); return; } size_t size = deimal_size(number); if (size != 0) { //把number处理成整数,小数点写入buf中再处理 number = number * pow(10, size); } long integer = number; uint8_t is_negative = integer < 0; //负数要去相反数,避免后面计算每一位的余数为负数 if (is_negative) { integer = -integer; } int i = 0; for (; integer != 0;i++) { buf[7 - i] = digit_tab[integer % 10]; //处理小数点 if (size != 0 && i == size) { buf[7 - i] = DP_WRAP(buf[7 - i]); } integer /= 10; } //如果是负数在buf最前面加上负号 if (is_negative) { buf[7 - i] = MINUS; } hc595_refresh_buf(dev, buf); } ``` main代码中调用 ```c #include "board.h" #include "log.h" #include #include "bflb_gpio.h" #include "bflb_mtimer.h" #include "bflb_timer.h" #include "74hc595.h" #define PIN_CLK GPIO_PIN_13 #define PIN_SER GPIO_PIN_10 #define PIN_ST GPIO_PIN_19 struct bflb_device_s* gpio; struct bflb_device_s* timer0; void delay_us(uint32_t us) { bflb_mtimer_delay_us(us); } int gpio_set_data(uint8_t pin, uint8_t data) { if (data) { bflb_gpio_set(gpio, pin); } else { bflb_gpio_reset(gpio, pin); } return 0; } int gpio_init(uint8_t pin, uint32_t cfgset) { bflb_gpio_init(gpio, pin, cfgset); } void timer0_isr(int argc, void* argv) { bool status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_0); if (status) { bflb_timer_compint_clear(timer0, TIMER_COMP_ID_0); digit_dev_t* dev = (digit_dev_t*)argv; hc595_desplay(dev, -1234.567); } } int main(void) { board_init(); printf("74hc595 start!\n"); gpio = bflb_device_get_by_name("gpio"); digit_dev_t dev = { .pin_clk = PIN_CLK, .pin_ser = PIN_SER, .pin_st = PIN_ST, .delay_fun = delay_us, .set_dev_data = gpio_set_data, .gpio_init = gpio_init, }; hc595_init(&dev); struct bflb_timer_config_s cfg; cfg.counter_mode = TIMER_COUNTER_MODE_PROLOAD; cfg.clock_source = TIMER_CLKSRC_XTAL; cfg.clock_div = 39; cfg.trigger_comp_id = TIMER_COMP_ID_0; cfg.comp0_val = 1000; cfg.preload_val = 0; timer0 = bflb_device_get_by_name("timer0"); bflb_irq_attach(timer0->irq_num, timer0_isr, &dev); bflb_irq_enable(timer0->irq_num); bflb_timer_init(timer0, &cfg); bflb_timer_start(timer0); while (1) { bflb_mtimer_delay_ms(1000); } return 0; } ``` ### 点亮效果 ![点亮效果](image/README/点亮效果.png)