数码管基础知识参考:
实现功能
1、上电后,数码管每隔50ms计数;
2、触摸按键1调节数码管亮度。
原理图
根据原理图很容易就能看出来,左边的是段选信号,右边的是位选信号。
对应的驱动电路如下:
用的是芯片TM1602,输入端接到了PC0/PC1/PC2
编程的重点在于理解TM1602芯片。
我们在编程时,需要将TM1602的属性和操作进行封装,然后再使用。
MX配置
配置PC0/PC1/PC2三个端口为输出模式即可。
别忘了标签名。
程序编写
本文的程序编写并不涉及到新的HAL库内容,而是对芯片TM1602的封装。
有助于对底层HAL库封装的理解。
TM1602数据手册
具体自行查看数据手册,此处仅放置关键内容。
TM1620是一种LED(发光二极管显示器)驱动控制专用IC,内部集成有MCU数字接口、数据锁存器、LED驱动等电路。
手册里大部分内容还可以理解,但是关于“显示寄存器地址”表,表示很难理解:
这啥意思?
这里我不明白的是为什么要每个地址都要写两次。。。。还非得写00HL和00HU,直接整个8位一起写00H不行吗?
横向表示显示寄存器的地址,也就是位选所对应的地址。数码管位选端和哪个GRID绑定了,对应数码管就和哪个显存对应了,后面就会从对应显存里拿数据来显示。
封装
新建两个文件display.h和display.c,用于封装数码管操作。
display.h
#ifndef _DISPLAY_H_ #define _DISPLAY_H_ #include <stdint.h> //显示模式 typedef enum { DI4_SEG10 = 0x00, DI5_SEG9 = 0x01, DI6_SEG8 = 0x02 } display_mode; //数据读写模式 typedef enum { DATA_MODE = 0x40, AUTO_ADDR_INC = 0x40, FIXED_ADDR = 0x44, NORMAL_MODE = 0x40, TEST_MODE = 0x48 } wr_mode; //显示控制 typedef enum { LIGHTNESS_1 = 0x88, LIGHTNESS_2 = 0x89, LIGHTNESS_3 = 0x8A, LIGHTNESS_4 = 0x8B, LIGHTNESS_5 = 0x8C, LIGHTNESS_6 = 0x8D, LIGHTNESS_7 = 0x8E, LIGHTNESS_8 = 0x8F, DIS_DISPLAY = 0x80, EN_DISPLAY = 0x88 } display_ctrl_mode; //地址设置 typedef enum { GRID1 = 0xC0, GRID1_EX = 0xC1, GRID2 = 0xC2, GRID2_EX = 0xC3, GRID3 = 0xC4, GRID3_EX = 0xC5, GRID4 = 0xC6, GRID4_EX = 0xC7, GRID5 = 0xC8, GRID5_EX = 0xC9, GRID6 = 0xCA, GRID6_EX = 0xCB } grid_sel; //输出哪个数字,默认不带小数点 typedef enum { ZERO = 0x3F, ONE = 0x06, TWO = 0x5B, THREE = 0x4F, FOUR = 0x66, FIVE = 0x6D, SIX = 0x7D, SEVEN = 0x07, EIGHT = 0x7F, NINE = 0x6F } display_num; //是否输出小数点 typedef enum { NO, YES } is_point; typedef struct { display_ctrl_mode lightness; void (*display)(grid_sel, display_num, is_point); void (*changeLightness)(void); void (*disDisplay)(void); } display_haldler; extern display_haldler displayHaldler; #endif
display.c
#include "myapplication.h" #define STB_SET HAL_GPIO_WritePin(TM1602_STB_GPIO_Port, TM1602_STB_Pin, GPIO_PIN_SET); #define STB_RESET HAL_GPIO_WritePin(TM1602_STB_GPIO_Port, TM1602_STB_Pin, GPIO_PIN_RESET); #define CLK_SET HAL_GPIO_WritePin(TM1602_CLK_GPIO_Port, TM1602_CLK_Pin, GPIO_PIN_SET); #define CLK_RESET HAL_GPIO_WritePin(TM1602_CLK_GPIO_Port, TM1602_CLK_Pin, GPIO_PIN_RESET); #define DIN_WRITE(x) HAL_GPIO_WritePin(TM1602_DIN_GPIO_Port, TM1602_DIN_Pin, (x)); static void Display(grid_sel gridSel, display_num displayNum, is_point isPoint); static void ChangeLightness(void); static void DisDisplay(void); display_haldler displayHaldler = { LIGHTNESS_1, Display, ChangeLightness, DisDisplay }; //最底层时序操作 static void Display_Write(uint8_t dataWrited) { uint8_t i = 0; STB_RESET; for(i; i<8; i++) { CLK_RESET; DIN_WRITE(dataWrited & 0x01); CLK_SET; dataWrited >>= 1; } } static void Display_Init(void) { Display_Write(DI6_SEG8); STB_SET; Display_Write(FIXED_ADDR); STB_SET; } static void Display(grid_sel gridSel, display_num displayNum, is_point isPoint) { Display_Init(); Display_Write(gridSel); if(isPoint == YES) { Display_Write(displayNum + 0x80); } else { Display_Write(displayNum); } STB_SET; Display_Write(EN_DISPLAY); STB_SET; } static void ChangeLightness(void) { if(displayHaldler.lightness > LIGHTNESS_8) { displayHaldler.lightness = LIGHTNESS_1; } Display_Write((displayHaldler.lightness)++); } static void DisDisplay(void) { Display_Write(DIS_DISPLAY); }
完成了TM1602的底层封装,以及数码管的操作封装。
封装时一定要注意严格按照时序操作。
比如说上面红框中的地方,并不是每个字节传输完成后就拉高STB,所以不能在每次写字节函数中拉高STB,而是要把拉高STB的操作留给外层。要注意,拉高的时机,有的地方是一个字节后就拉高,有的地方是两个字节或者多个字节后再拉高。当然,字节传输函数中可以拉低STB,相比放外面更方便些。
如果要实现多个数码管同时显示,可以通过扫描实现,结合位选操作即可。
如果要不同数码管各显示个十百千万,可通过整除和取余来实现,比如:
//数码管显示计数值 Display.Disp(Disp_NUM_1,Cnt%10, Disp_DP_OFF); //个位 Display.Disp(Disp_NUM_2,Cnt/10%10, Disp_DP_OFF); //十位 Display.Disp(Disp_NUM_3,Cnt/100%10, Disp_DP_OFF); //百位 Display.Disp(Disp_NUM_4,Cnt/1000%10, Disp_DP_OFF); //千位 Display.Disp(Disp_NUM_5,Cnt/10000%10,Disp_DP_OFF); //万位 Display.Disp(Disp_NUM_6,Cnt/100000, Disp_DP_OFF); //十万位
时间关系,不再赘述。后面有时间再完善。