当前位置:   article > 正文

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

基于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();
}
  • 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

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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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');
}
  • 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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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;
}
  • 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

四、总结

首先感谢大家看到这里,简单总结一下
注意:上述操作,就是一个简单的wifi时钟设计,由于esp32和蜂鸣器器件没有到,只是做了一些简单的操作,但是硬件没有问题。我也是第一次使用VFD屏幕,偶然在bilibili刷到VFD屏幕,就很感兴趣,所有做了这么个设计。也可以扩展其他功能,比如

1.将蜂鸣器换成语音播报
2.可以把时钟作为一个桌面摆件,通过usb通信控制电脑关机,设置电脑音量,电脑屏幕亮度等
3.可以开发上位机,远程操作时钟功能


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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/761976
推荐阅读
相关标签
  

闽ICP备14008679号