赞
踩
使用STM32+ESP32开发一个电子时钟,拥有时钟显示,报警,自动对时等功能的电子时钟
硬件原理图如下,仅供参考,如有误,提示提出:
硬件资源:MCU、ESP32、usb、温度传感器、按键、蜂鸣器、VFD屏,晶振电路,复位电路。
友情提示,各位在焊接时,一定检查是否有虚焊,否则会像我一样,焊接第一版时,发现不能下载程序,一直怀疑是硬件问题,其实是晶振电路中,MCU其中一个引脚没有焊接好导致的,谨记!!!
硬件焊接后的实物如图:
实物背面如下,由于器件没有到全,所有没有焊接esp32和蜂鸣器
正面就是一个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的固定值。
/* 引脚宏定义,置高或者置低 */
/** DA */
#define clrDA() GPIO_ResetBits(VFD_DA_PORT, VFD_DA_PIN)
#define setDA() GPIO_SetBits (VFD_DA_PORT, VFD_DA_PIN)
/** CP */
#define clrCP() GPIO_ResetBits(VFD_CP_PORT, VFD_CP_PIN)
#define setCP() GPIO_SetBits (VFD_CP_PORT, VFD_CP_PIN)
/** #CS */
#define clrCS() GPIO_ResetBits(VFD_CS_PORT, VFD_CS_PIN)
#define setCS() GPIO_SetBits (VFD_CS_PORT, VFD_CS_PIN)
/** High voltage switch operation */
#define clrHON() GPIO_ResetBits(VFD_HON_PORT, VFD_HON_PIN)
#define setHON() GPIO_SetBits (VFD_HON_PORT, VFD_HON_PIN)
/** #RST */
#define clrRST() GPIO_ResetBits(VFD_RST_PORT, VFD_RST_PIN)
#define setRST() GPIO_SetBits (VFD_RST_PORT, VFD_RST_PIN)
/* VFD命令 */
/** VFD 8-MD-06INKM CMD */
#define Write_DCRAM_CMD 0x20 /* Write Data Control RAM Command */
#define Write_CGRAM_CMD 0x40 /* Write Character Generator RAM Command */
#define Write_ADRAM_CMD 0x60 /* Write Additional Data RAM Command */
#define Write_URAM_CMD 0x80 /* Write Univeral Data RAM Command */
#define Set_Timing_CMD 0xE0 /* Set Display Timming Command */
#define Set_Dimming_CMD 0xE4 /* Write Brightness Control Data Command */
#define Light_Normal_CMD 0xE8 /* Display Light Normal Operation */
#define Light_Off_CMD 0xEA /* Set All Display Light Off */
#define Light_On_CMD 0xE9 /* Set All Display Light On */
#define Standby_Off_CMD 0xEC /* Standby Mode Off, Normal Operation Mode */
#define Standby_On_CMD 0xED /* Standby Mode On, Save Power */
/* 举个简单的例子:设置VFD亮度 其余的命令可以仿照这来*/
void VFD_Set_Brightness(uint8_t u8Bright) {
clrCS();
/* Send brightness setting command */
VFD_Send_Data(Set_Dimming_CMD);
/* Send brightness value */
VFD_Send_Data(u8Bright);
setCS();
}
将所有命令全部准备好后就可以进行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();
数字显示还是比较简单,只需传入两个参数,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
*/
void VFD_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();
}
通过时分秒几个变量,自加加就可以动态显示时间了,给个很简单的例子,不要像这样写,很不规范,只是为了演示这个效果
/* 主循环里面实时更新时分秒这三个变量,当然还是得初始化一个值*/
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');
}
为什么要说以下按键呢?它的型号是:SLLB510100,图片如下:
它可以往左和往右拨动,但是会自动回正那种,也可以往下按。所有,采用这种结构,我们可以做一个比较好玩的功能呢。
1.在菜单模式下,往左和往右切换菜单,按下为确认。
2.在设置时间模式下,往左或者往右为切换时间,按下为确认设置。往左和往左不回正,为快速设置
它的驱动方式也很简单,可以把它想象成普通按键就行了,可以扫描触发,也可以中断触发。给个简单的例子:
/* 功能就是,通过扫描每个按键引脚对应的IO口,看看是否被执行,如果是就显示相应内容 */
#define READ_PUSH PAin(0)
#define READ_CCW PAin(1)
#define READ_CW PAin(2)
if(READ_PUSH == 0)
{
VFD_Dis_Char(0, (1) + '0');
}
if(READ_CCW == 0)
{
VFD_Dis_Char(1, (2) + '0');
}
采用wifi模块获取时间,初始化部分就不用再说了,往期文章说过很多,相关链接:wifi模块,请自行参考初始化部分
这里给出初始化程序:
void ESP8266_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"); else S1201_WriteStr(0,"WIFI_OK ");
state = ESP8266_SendCmd("AT+CIPMUX=0","OK",300);
}
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) return 1;
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;
return 1;
}
if(3 == NetTime.li) return 2;
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;
return 0;
}
u8 getTime(void)
{
u8 temp=1;
u8 timeOut=100;
while(temp&&timeOut--)
{
temp = getTimeFromNTPServer();
}
if(0 == timeOut)
return 1;
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;
return 0;
}
首先感谢大家看到这里,简单总结一下
注意:上述操作,就是一个简单的wifi时钟设计,由于esp32和蜂鸣器器件没有到,只是做了一些简单的操作,但是硬件没有问题。我也是第一次使用VFD屏幕,偶然在bilibili刷到VFD屏幕,就很感兴趣,所有做了这么个设计。也可以扩展其他功能,比如
1.将蜂鸣器换成语音播报
2.可以把时钟作为一个桌面摆件,通过usb通信控制电脑关机,设置电脑音量,电脑屏幕亮度等
3.可以开发上位机,远程操作时钟功能
最后再次感谢大家阅览!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。