以前在单片机学习中,驱动数码管是直接将数码管连接单片机,在单片机上需要编写很复杂的程序,并且对数码管需要实时扫描。在实际项目开发中,一般使用数码管专用控制芯片来驱动数码管,提高开发效率。
TM1640是一种LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU数字接口、数据锁存器、LED驱动等电路。主要应用于电子产品LED显示屏驱动。采用SOP28封装形式。TM1640只是数码管驱动芯片的一种,还有很多其他的驱动芯片,具体芯片选择需要根据项目需求及预算来决定。
可驱动16位数码管。
有共阴极与共阳极两种电路图,根据开发板上的数码管实际连接来决定使用哪个。
这两个引脚分别与单片机的两个GPIO相连。
8位数码管
这八个共阴极的LED灯相当于一个数码管,可以当成一个数码管来处理。
驱动代码如下:
main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include "stm32f10x.h" #include "sys.h" #include "delay.h" #include "rtc.h" #include "TM1640.h" int main (void) { u8 c=0x01; RCC_Configuration(); //系统时钟初始化 RTC_Config(); //RTC初始化 TM1640_Init(); //TM1640初始化 while(1) { if(RTC_Get()==0) //读出RTC时间 { TM1640_display(0,rday/10); //天 TM1640_display(1,rday%10+10); //+10是为了显示后面的小数点 TM1640_display(2,rhour/10); //时 TM1640_display(3,rhour%10+10); TM1640_display(4,rmin/10); //分 TM1640_display(5,rmin%10+10); TM1640_display(6,rsec/10); //秒 TM1640_display(7,rsec%10); TM1640_led(c); //与TM1640连接的8个LED全亮 c<<=1; //数据左移 流水灯 if(c==0x00)c=0x01; //8个灯显示完后重新开始 delay_ms(125); //延时 } } } |
TM1640.c函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | #include "TM1640.h" #include "delay.h" #define DEL 1 //宏定义 通信速率(默认为1,如不能通信可加大数值) //地址模式的设置 //#define TM1640MEDO_ADD 0x40 //宏定义 自动加一模式 #define TM1640MEDO_ADD 0x44 //宏定义 固定地址模式(推荐) //显示亮度的设置 //#define TM1640MEDO_DISPLAY 0x88 //宏定义 亮度 最小 //#define TM1640MEDO_DISPLAY 0x89 //宏定义 亮度 //#define TM1640MEDO_DISPLAY 0x8a //宏定义 亮度 //#define TM1640MEDO_DISPLAY 0x8b //宏定义 亮度 #define TM1640MEDO_DISPLAY 0x8c //宏定义 亮度(推荐) //#define TM1640MEDO_DISPLAY 0x8d //宏定义 亮度 //#define TM1640MEDO_DISPLAY 0x8f //宏定义 亮度 最大 #define TM1640MEDO_DISPLAY_OFF 0x80 //宏定义 亮度 关 //通信时序 启始(基础GPIO操作)(低层) void TM1640_start() { GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1 GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1 delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0 delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0 delay_us(DEL); } //通信时序 结束(基础GPIO操作)(低层) void TM1640_stop() { GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0 GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1 delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1 delay_us(DEL); } //写数据(低层) void TM1640_write(u8 date) { u8 i; u8 aa; aa=date; GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0 GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0 for(i=0;i<8;i++) { GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0 delay_us(DEL); if(aa&0x01) { GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1 delay_us(DEL); } else { GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0 delay_us(DEL); } GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1 delay_us(DEL); aa=aa>>1; } GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(0)); //接口输出0 GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(0)); //接口输出0 } //TM1640接口初始化 void TM1640_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitStructure.GPIO_Pin = TM1640_DIN | TM1640_SCLK; //选择端口号(0~15或all) GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz) GPIO_Init(TM1640_GPIOPORT, &GPIO_InitStructure); GPIO_WriteBit(TM1640_GPIOPORT,TM1640_DIN,(BitAction)(1)); //接口输出高电平1 GPIO_WriteBit(TM1640_GPIOPORT,TM1640_SCLK,(BitAction)(1)); //接口输出高电平1 TM1640_start(); TM1640_write(TM1640MEDO_ADD); //设置数据,0x40,0x44分别对应地址自动加一和固定地址模式 TM1640_stop(); TM1640_start(); TM1640_write(TM1640MEDO_DISPLAY); //控制显示,开显示,0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f分别对应脉冲宽度为: //------------------1/16, 2/16, 4/16, 10/16, 11/16, 12/16, 13/16, 14/16 //0x80关显示 TM1640_stop(); } //固定地址模式的显示输出8个LED控制 void TM1640_led(u8 date) { TM1640_start(); TM1640_write(TM1640_LEDPORT); //传显示数据对应的地址 TM1640_write(date); //传1BYTE显示数据 TM1640_stop(); } //固定地址模式的显示输出 void TM1640_display(u8 address,u8 date) { const u8 buff[21]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0x00};//数字0~9及0~9加点显示段码表 //--------------- 0 1 2 3 4 5 6 7 8 9 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 无 TM1640_start(); TM1640_write(0xC0+address); //传显示数据对应的地址 TM1640_write(buff[date]); //传1BYTE显示数据 TM1640_stop(); } //地址自动加一模式的显示输出 void TM1640_display_add(u8 address,u8 date) { u8 i; const u8 buff[21]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0x00};//数字0~9及0~9加点显示段码表 //--------------- 0 1 2 3 4 5 6 7 8 9 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 无 TM1640_start(); TM1640_write(0xC0+address); //设置起始地址 for(i=0;i<16;i++) { TM1640_write(buff[date]); } TM1640_stop(); } |
TM1640.h文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #ifndef __TM1640_H #define __TM1640_H #include "sys.h" #define TM1640_GPIOPORT GPIOA //定义IO接口 #define TM1640_DIN GPIO_Pin_12 //定义IO接口 #define TM1640_SCLK GPIO_Pin_11 //定义IO接口 #define TM1640_LEDPORT 0xC8 //定义IO接口 void TM1640_Init(void);//初始化 void TM1640_led(u8 date);// void TM1640_display(u8 address,u8 date);// void TM1640_display_add(u8 address,u8 date);// #endif |
rtc.c函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | /* //时间读写与设置说明// 1,在mani函数开头放入RTC_Config();就可以使能时钟了。 在RTC_Config();函数中自带判断是不是首次使用RTC 2,使用 RTC_Get();读出时间。读出的数据存放在: 年 ryear (16位) 月 rmon (以下都是8位) 日 rday 时 rhour 分 rmin 秒 rsec 周 rweek 3,使用 RTC_Set(4位年,2位月,2位日,2位时,2位分,2位秒); 写入时间。例如:RTC_Get(2017,08,06,21,34,00); 其他函数都是帮助如上3个函数的,不需要调用。 注意要使用RTC_Get和RTC_Set的返回值,为0时表示读写正确。 */ #include "sys.h" #include "rtc.h" //以下2条全局变量--用于RTC时间的读取 u16 ryear; //4位年 u8 rmon,rday,rhour,rmin,rsec,rweek;//2位月日时分秒周 //首次启用RTC的设置 void RTC_First_Config(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR和BKP的时钟(from APB1) PWR_BackupAccessCmd(ENABLE);//后备域解锁 BKP_DeInit();//备份寄存器模块复位 RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振开启 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待稳定 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC时钟源配置成LSE(外部低速晶振32.768KHZ) RCC_RTCCLKCmd(ENABLE);//RTC开启 RTC_WaitForSynchro();//开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器 RTC_WaitForLastTask();//读写寄存器前,要确定上一个操作已经结束 RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) RTC_WaitForLastTask();//等待寄存器写入完成 //当不使用RTC秒中断,可以屏蔽下面2条 // RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断 // RTC_WaitForLastTask();//等待写入完成 } //实时时钟初始化 void RTC_Config(void) { //在BKP的后备寄存器1中,存了一个特殊字符0xA5A5 //第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//判断寄存数据是否丢失 RTC_First_Config();//重新配置RTC BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5 }else{ //若后备寄存器没有掉电,则无需重新配置RTC //这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型 if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){ //这是上电复位 } else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){ //这是外部RST管脚复位 } RCC_ClearFlag();//清除RCC中复位标志 //虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行 //但是每次上电后,还是要使能RTCCLK RCC_RTCCLKCmd(ENABLE);//使能RTCCLK RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步 //当不使用RTC秒中断,可以屏蔽下面2条 // RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断 // RTC_WaitForLastTask();//等待操作完成 } #ifdef RTCClockOutput_Enable RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); BKP_TamperPinCmd(DISABLE); BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock); #endif } //RTC时钟1秒触发中断函数(名称固定不可修改) void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET){ } RTC_ClearITPendingBit(RTC_IT_SEC); RTC_WaitForLastTask(); } //闹钟中断处理(启用时必须调高其优先级) void RTCAlarm_IRQHandler(void) { if(RTC_GetITStatus(RTC_IT_ALR) != RESET){ } RTC_ClearITPendingBit(RTC_IT_ALR); RTC_WaitForLastTask(); } //判断是否是闰年函数 //月份 1 2 3 4 5 6 7 8 9 10 11 12 //闰年 31 29 31 30 31 30 31 31 30 31 30 31 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31 //输入:年份 //输出:该年份是不是闰年.1,是.0,不是 u8 Is_Leap_Year(u16 year) { if(year%4==0){ //必须能被4整除 if(year%100==0){ if(year%400==0)return 1;//如果以00结尾,还要能被400整除 else return 0; }else return 1; }else return 0; } //设置时钟 //把输入的时钟转换为秒钟 //以1970年1月1日为基准 //1970~2099年为合法年份 //月份数据表 u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表 //写入当前时间(1970~2099年有效) u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) { u16 t; u32 seccount=0; if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099 for(t=1970;t<syear;t++){ //把所有年份的秒钟相加 if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数 else seccount+=31536000; //平年的秒钟数 } smon-=1; for(t=0;t<smon;t++){ //把前面月份的秒钟数相加 seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加 if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数 } seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 seccount+=(u32)hour*3600;//小时秒钟数 seccount+=(u32)min*60; //分钟秒钟数 seccount+=sec;//最后的秒钟加上去 RTC_First_Config(); //重新初始化时钟 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5 RTC_SetCounter(seccount);//把换算好的计数器值写入 RTC_WaitForLastTask(); //等待写入完成 return 0; //返回值:0,成功;其他:错误代码. } //读出当前时间值 ,返回值:0,成功;其他:错误代码. u8 RTC_Get(void) { static u16 daycnt=0; u32 timecount=0; u32 temp=0; u16 temp1=0; timecount=RTC_GetCounter(); temp=timecount/86400; //得到天数(秒钟数对应的) if(daycnt!=temp){//超过一天了 daycnt=temp; temp1=1970; //从1970年开始 while(temp>=365){ if(Is_Leap_Year(temp1)){//是闰年 if(temp>=366)temp-=366;//闰年的秒钟数 else {temp1++;break;} } else temp-=365; //平年 temp1++; } ryear=temp1;//得到年份 temp1=0; while(temp>=28){//超过了一个月 if(Is_Leap_Year(ryear)&&temp1==1){//当年是不是闰年/2月份 if(temp>=29)temp-=29;//闰年的秒钟数 else break; }else{ if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年 else break; } temp1++; } rmon=temp1+1;//得到月份 rday=temp+1; //得到日期 } temp=timecount%86400; //得到秒钟数 rhour=temp/3600; //小时 rmin=(temp%3600)/60; //分钟 rsec=(temp%3600)%60; //秒钟 rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期 return 0; } //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用 u8 RTC_Get_Week(u16 year,u8 month,u8 day) { u16 temp2; u8 yearH,yearL; yearH=year/100; yearL=year%100; // 如果为21世纪,年份数加100 if (yearH>19)yearL+=100; // 所过闰年数只算1900年之后的 temp2=yearL+yearL/4; temp2=temp2%7; temp2=temp2+day+table_week[month-1]; if (yearL%4==0&&month<3)temp2--; return(temp2%7); //返回星期值 } |
rtc.h文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #ifndef __RTC_H #define __RTC_H #include "sys.h" //全局变量的声明,在rtc.c文件中定义 //以下2条是使用extern语句声明全局变量 //注意:这里不能给变量赋值 extern u16 ryear; extern u8 rmon,rday,rhour,rmin,rsec,rweek; void RTC_First_Config(void);//首次启用RTC的设置 void RTC_Config(void);//实时时钟初始化 u8 Is_Leap_Year(u16 year);//判断是否是闰年函数 u8 RTC_Get(void);//读出当前时间值 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//写入当前时间 u8 RTC_Get_Week(u16 year,u8 month,u8 day);//按年月日计算星期 #endif |