电子时钟设计
前言
使用STM32+ESP32开发一个电子时钟,拥有时钟显示,报警,自动对时等功能的电子时钟
一、运行环境及硬件参考
- MCU:STM32F103
- 通信:串口通信,波特率:115200、数据位:8、停止位:1、校验:None
- 开发软件:keil mdk
- 主要硬件连接:stm32与esp32通过串口相连
- 按键:SLLB510100,显示屏:VFD
二、硬件设计
1.原理图
硬件原理图如下,仅供参考,如有误,提示提出:
硬件资源:MCU、ESP32、usb、温度传感器、按键、蜂鸣器、VFD屏,晶振电路,复位电路。
友情提示,各位在焊接时,一定检查是否有虚焊,否则会像我一样,焊接第一版时,发现不能下载程序,一直怀疑是硬件问题,其实是晶振电路中,MCU其中一个引脚没有焊接好导致的,谨记!!!
2.硬件实物
硬件焊接后的实物如图:
实物背面如下,由于器件没有到全,所有没有焊接esp32和蜂鸣器
正面就是一个vfd屏幕,这里简单搞了个驱动程序,可以看看效果,还是比较不错的
三、软件设计
3.1 VFD驱动原理
VFD显示屏,8位5x7点阵
这里我使用的是SPI控制方式,引出了SPI引脚,默认使能高压电压转换,可以通过EN引脚置低电平关闭。根据使用手册列出以下命令,方便控制程序编写:
命令功能0x20写入数据控制RAM命令0x40写入字符生成器RAM命令0x60写入附加数据RAM命令0x80写入通用数据RAM命令0xE0设置显示计时命令0xE4写入亮度控制数据命令0xE8显示灯正常操作0xEA将所有显示灯设置为关闭0xE9设置所有显示灯亮起0xEC待机模式关闭,正常操作模式0xEC待机模式开启,省电
且给出运行流程图:
这个流程图显示了从接通电源到显示器亮起的基本流程。接通电源后,将2和3中的值设置为所使用的每个VFD的固定值。
3.2 VFD驱动程序
3.2.1 驱动指令编写
/* 引脚宏定义,置高或者置低 *//** DA */#defineclrDA()GPIO_ResetBits(VFD_DA_PORT, VFD_DA_PIN)#definesetDA()GPIO_SetBits(VFD_DA_PORT, VFD_DA_PIN)/** CP */#defineclrCP()GPIO_ResetBits(VFD_CP_PORT, VFD_CP_PIN)#definesetCP()GPIO_SetBits(VFD_CP_PORT, VFD_CP_PIN)/** #CS */#defineclrCS()GPIO_ResetBits(VFD_CS_PORT, VFD_CS_PIN)#definesetCS()GPIO_SetBits(VFD_CS_PORT, VFD_CS_PIN)/** High voltage switch operation */#defineclrHON()GPIO_ResetBits(VFD_HON_PORT, VFD_HON_PIN)#definesetHON()GPIO_SetBits(VFD_HON_PORT, VFD_HON_PIN)/** #RST */#defineclrRST()GPIO_ResetBits(VFD_RST_PORT, VFD_RST_PIN)#definesetRST()GPIO_SetBits(VFD_RST_PORT, VFD_RST_PIN)/* VFD命令 *//** VFD 8-MD-06INKM CMD */#defineWrite_DCRAM_CMD0x20/* Write Data Control RAM Command */#defineWrite_CGRAM_CMD0x40/* Write Character Generator RAM Command */#defineWrite_ADRAM_CMD0x60/* Write Additional Data RAM Command */#defineWrite_URAM_CMD0x80/* Write Univeral Data RAM Command */#defineSet_Timing_CMD0xE0/* Set Display Timming Command */#defineSet_Dimming_CMD0xE4/* Write Brightness Control Data Command */#defineLight_Normal_CMD0xE8/* Display Light Normal Operation */#defineLight_Off_CMD0xEA/* Set All Display Light Off */#defineLight_On_CMD0xE9/* Set All Display Light On */#defineStandby_Off_CMD0xEC/* Standby Mode Off, Normal Operation Mode */#defineStandby_On_CMD0xED/* Standby Mode On, Save Power *//* 举个简单的例子:设置VFD亮度 其余的命令可以仿照这来*/voidVFD_Set_Brightness(uint8_t u8Bright){clrCS();/* Send brightness setting command */VFD_Send_Data(Set_Dimming_CMD);/* Send brightness value */VFD_Send_Data(u8Bright);setCS();}
3.2.2 屏幕初始化
将所有命令全部准备好后就可以进行VFD屏幕驱动了,首先呢,要初始化屏幕,程序如下:
/* Turn on VFD filament and high-voltage power supply, cancel reset */setHON();/* Turn on the filament and operate the high-voltage step-up transformer */setRST();/* 8MD06INKM Init *//* Set Display Timming,Set scan timing */clrCS();VFD_Send_Data(Set_Timing_CMD);VFD_Send_Data(0x07);/* Data, URAM disabled, scanning 1G~8G */setCS();/* Set URAM URAM Disabled*//* Set Dimming Data,Set the default brightness, with a brightness range of:0~240 */clrCS();VFD_Send_Data(Set_Dimming_CMD);VFD_Send_Data(Brightness);setCS();/* Display Light Normal Operation */clrCS();VFD_Send_Data(Light_Normal_CMD);setCS();
3.2.3 显示数字
数字显示还是比较简单,只需传入两个参数,u8Position:0~7.u8Char:ASCII.
/* 显示数字 */VFD_Dis_Char(0,(1)+'0');/**
* @brief Display a character at the specified position in VFD 8MD06INKM.
* @param u8Position:0~7.
* @param u8Char:ASCII.
* @retval None
*/voidVFD_Dis_Char(uint8_t u8Position,uint8_t u8Char){clrCS();/* Set character position */VFD_Send_Data(Write_DCRAM_CMD | u8Position);/* Set display character content */VFD_Send_Data(u8Char);setCS();}
3.2.4 定时显示
通过时分秒几个变量,自加加就可以动态显示时间了,给个很简单的例子,不要像这样写,很不规范,只是为了演示这个效果
/* 主循环里面实时更新时分秒这三个变量,当然还是得初始化一个值*/while(1){
Second ++;VFD_Delay_ms(900);/** 时间计数 */if(Second ==60){
Second =0;
Minute ++;if(Minute ==60){
Minute =0;
Hour ++;if(Hour ==24){
Hour =0;}}}/** 显示时 */VFD_Dis_Char(0,(Hour /10)+'0');VFD_Dis_Char(1,(Hour %10)+'0');/** 显示分 */VFD_Dis_Char(3,(Minute /10)+'0');VFD_Dis_Char(4,(Minute %10)+'0');/** 显示秒 */VFD_Dis_Char(6,(Second /10)+'0');VFD_Dis_Char(7,(Second %10)+'0');}
3.3 按键
为什么要说以下按键呢?它的型号是:SLLB510100,图片如下:
它可以往左和往右拨动,但是会自动回正那种,也可以往下按。所有,采用这种结构,我们可以做一个比较好玩的功能呢。
1.在菜单模式下,往左和往右切换菜单,按下为确认。
2.在设置时间模式下,往左或者往右为切换时间,按下为确认设置。往左和往左不回正,为快速设置
它的驱动方式也很简单,可以把它想象成普通按键就行了,可以扫描触发,也可以中断触发。给个简单的例子:
/* 功能就是,通过扫描每个按键引脚对应的IO口,看看是否被执行,如果是就显示相应内容 */#defineREAD_PUSHPAin(0)#defineREAD_CCWPAin(1)#defineREAD_CWPAin(2)if(READ_PUSH ==0){VFD_Dis_Char(0,(1)+'0');}if(READ_CCW ==0){VFD_Dis_Char(1,(2)+'0');}
3.4 esp32获取时间
采用wifi模块获取时间,初始化部分就不用再说了,往期文章说过很多,相关链接:wifi模块,请自行参考初始化部分
3.4.1 wifi模块初始化
这里给出初始化程序:
voidESP8266_Init(void){
u8 state=0;int j;
USART1_RX_STA =0;memset(USART1_RX_BUF,0,sizeof(USART1_RX_BUF));
state =ESP8266_SendCmd("AT+CWMODE=3","OK",20);
state =ESP8266_SendCmd("AT+RST","OK",20);for(j=0;j<10;j++){S1201_WriteStr(0,"NTP_CALC");ysm(190);}for(j=9;j>=0;j--){S1201_WriteStr(0,"WIFI_CON");ysm(190);}
state =ESP8266_SendCmd("AT+CWJAP=\"nova 5 pro\",\"7104021730114\"","OK",1000);if(!state)S1201_WriteStr(0,"WIFI_ERR");elseS1201_WriteStr(0,"WIFI_OK ");
state =ESP8266_SendCmd("AT+CIPMUX=0","OK",300);}
3.4.2 从服务器获取时间
NTP服务器提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。因为ntp服务器是udp协议,ip:120.25.115.20 端口号:123,格式是接收48个字节,第一个字节以0xa3(版本4) 、0x1b, (版本3)、0x13(版本2) 、0x0b(版本1),返回的数据中带有时间。
u8 getTimeFromNTPServer(void){
u8 packetBuffer[48];
u32 timeOut=0xffffff;
u8 i;
u16 year=1900;
u32 yearSec;U1_Printf("AT+CIPSTART=\"UDP\",\"1.cn.pool.ntp.org\",123\r\n");
USART1_RX_STA =0;memset(USART1_RX_BUF,0,sizeof(USART1_RX_BUF));ysm(100);memset(packetBuffer,0,sizeof(packetBuffer));ESP8266_SendCmd("AT+CIPSEND=48","OK",100);
packetBuffer[0]=0xe3;// LI, Version, Mode
packetBuffer[1]=0;// Stratum, or type of clock
packetBuffer[2]=6;// Polling Interval
packetBuffer[3]=0xEC;// Peer Clock Precision
packetBuffer[12]=49;
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
USART1_RX_STA =0;memset(USART1_RX_BUF,0,sizeof(USART1_RX_BUF));for(i=0;i<48;i++){U1Putchar(packetBuffer[i]);}while(timeOut--){if(USART1_RX_STA&0x80){if((USART1_RX_STA-0x80)>=60){
USART1_RX_STA =0;break;}else{
USART1_RX_STA =0;memset(USART1_RX_BUF,0,sizeof(USART1_RX_BUF));}}}if(0== timeOut)return1;if(0x24== USART1_RX_BUF[38]){
NetTime.li =((u8)USART1_RX_BUF[11]&0xc0)>>6;
NetTime.secTemp =(u8)USART1_RX_BUF[70];
NetTime.secTemp <<=8;
NetTime.secTemp |=(u8)USART1_RX_BUF[71];
NetTime.secTemp <<=8;
NetTime.secTemp |=(u8)USART1_RX_BUF[72];
NetTime.secTemp <<=8;
NetTime.secTemp |=(u8)USART1_RX_BUF[73];
USART1_RX_STA =0;}else{
USART1_RX_STA =0;return1;}if(3== NetTime.li)return2;
NetTime.secTemp +=28800;//UTC/GMT+08:00 8h==2800sec
datetemp = NetTime.secTemp;
datetemp = datetemp/86400;
datetemp +=1;
NetTime.date = datetemp%7;do{if(((0== year%4)&&(0!= year%100))||0==year%400){
yearSec =31622400;}else
yearSec =31536000;if(NetTime.secTemp < yearSec)break;else{
NetTime.secTemp -= yearSec;
year++;}}while(1);// while(1)
NetTime.year = year;if(((0== year%4)&&(0!= year%100))||0==year%400){
month[1]=29;}for(i=0;i<12;i++){if(NetTime.secTemp < month[i]*86400)//There are 86400sec in 1 day;break;else
NetTime.secTemp -= month[i]*86400;}
NetTime.daysInMonth = month[i];
NetTime.month = i;/* 解析数据为时间 */
NetTime.day = NetTime.secTemp/86400+1;
NetTime.secTemp = NetTime.secTemp %86400;
NetTime.hour = NetTime.secTemp/3600;
NetTime.secTemp = NetTime.secTemp%3600;
NetTime.min = NetTime.secTemp/60;
NetTime.sec = NetTime.secTemp%60;return0;}
u8 getTime(void){
u8 temp=1;
u8 timeOut=100;while(temp&&timeOut--){
temp =getTimeFromNTPServer();}if(0== timeOut)return1;
localTime.year = NetTime.year;
localTime.month = NetTime.month;
localTime.day = NetTime.day;
localTime.hour = NetTime.hour;
localTime.min = NetTime.min;
localTime.sec = NetTime.sec;
localTime.date = NetTime.date;
localTime.dateTemp =(u8)datetemp;return0;}
四、总结
首先感谢大家看到这里,简单总结一下
注意:上述操作,就是一个简单的wifi时钟设计,由于esp32和蜂鸣器器件没有到,只是做了一些简单的操作,但是硬件没有问题。我也是第一次使用VFD屏幕,偶然在bilibili刷到VFD屏幕,就很感兴趣,所有做了这么个设计。也可以扩展其他功能,比如
1.将蜂鸣器换成语音播报
2.可以把时钟作为一个桌面摆件,通过usb通信控制电脑关机,设置电脑音量,电脑屏幕亮度等
3.可以开发上位机,远程操作时钟功能
最后再次感谢大家阅览!!!
版权归原作者 ProMonkeyZ 所有, 如有侵权,请联系我们删除。