基于STM32的电子时钟设计!!!


前言

使用STM32+ESP32开发一个电子时钟,拥有时钟显示,报警,自动对时等功能的电子时钟


一、运行环境及硬件参考

  1. MCU:STM32F103
  2. 通信:串口通信,波特率:115200、数据位:8、停止位:1、校验:None
  3. 开发软件:keil mdk
  4. 主要硬件连接:stm32与esp32通过串口相连
  5. 按键: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 */
#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();
}

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
  */
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();
}

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口,看看是否被执行,如果是就显示相应内容 */
#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');
}

3.4 esp32获取时间

采用wifi模块获取时间,初始化部分就不用再说了,往期文章说过很多,相关链接:wifi模块,请自行参考初始化部分

3.4.1 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);
}

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) 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.可以开发上位机,远程操作时钟功能


最后再次感谢大家阅览!!!

### 回答1: STM32电子时钟是一种基于开源原理的电子时钟,可以使用STM32单片机进行控制和实现。由于其采用了开源设计,意味着其硬件和软件设计的原理和代码是可以公开访问且可以自由修改、分发的。 首先,对于STM32电子时钟的硬件设计部分,可以使用开源的硬件开发平台,如Arduino或者Raspberry Pi,以及开源的电路设计软件,如KiCad,实现电路设计。通过这些开源平台和软件,用户可以制作自己的STM32电子时钟,并且可以根据个人需求进行修改和优化。 其次,对于STM32电子时钟的软件编程部分,可以利用开源的集成开发环境,如Arduino IDE或者STM32CubeIDE,编写代码实现各种功能。由于STM32是一种流行的32位单片机,有许多开源的代码库和例程可供参考和使用,用户可以根据自己的需要进行二次开发。另外,在网络上也有许多开源的电子时钟项目可供下载和学习,在此基础上可以进行自己的创新和拓展。 最后,STM32电子时钟开源的优势在于可以充分利用开源社区的力量,获取更多的资源和支持。用户可以在开源社区中与其他开发者交流经验,解决问题,共享代码和设计方案。这样的开源性质使得STM32电子时钟变得更加灵活和可定制,同时也可以为其他开发者提供参考和学习的机会。 总之,STM32电子时钟的开源性质使得其更加灵活、可定制化,并且能够与其他开发者共享和交流。这为电子时钟设计和开发提供了更多的可能性和机会,使得我们可以创造出更具个性化的产品。 ### 回答2: STM32电子时钟开源是指通过公开源代码和相关设计文档,使得其他开发者可以自由地查看、修改和使用STM32电子时钟设计方案。 STM32是一种由意法半导体(STMicroelectronics)公司开发的32位单片机系列产品,具有强大的性能和丰富的外设。电子时钟是一种常见的应用场景,可以用于显示当前时间、日期等功能。 开源的好处是提供了更大的自由度和灵活性,使其他开发者能够基于原始设计进行二次开发、定制和优化。开源设计还可以促进技术交流和共享,加速创新和发展过程。 对于STM32电子时钟的开源,具体包括以下方面: 首先,开源软件部分。通过公开源代码,其他开发者可以了解到整个软件架构、实现细节和算法设计,可以根据自己的需求进行修改和定制。同时,也可以将自己的改进和优化提交回原始代码库,为整个开源社区做出贡献。 其次,开源硬件部分。这部分包括电路设计和PCB布局等方面,通过公开设计文档,开发者可以深入了解硬件架构和接口定义,可以根据需要修改和优化硬件电路。开源硬件还可以使得其他开发者能够快速地制造和生产自己的电子时钟产品。 最后,开源文档和说明。这些文档可以包括用户手册、编程指南、硬件接口说明等,让其他开发者更加便于理解和使用STM32电子时钟设计。 总之,STM32电子时钟的开源可以促进创新和技术进步,共享知识和资源,更好地满足不同用户的需求,推动整个社区的发展。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值