古董万年历升级WiFi授时 STM32+ESP8266

本次小制作 前后花了我10天时间 其中连续爆肝(10点起床凌晨4点左右睡觉)6天半

涉及:

STM32:GPIO、UART3、DMA1、TIM3 操作
ESP8266 :SDK编程(入门级)、WiFi配置、STNP服务器设置、UART、定时器、GPIO
万年历PCB板走线(我手动一个个量的 想明白原理图花了一晚上……
数码管5101A(共阳极)
Ds1302
DHT11

这部分内容 当然得写的高大上一点啦!其实这个东西它也不难,有兴趣想动手的可以试一下,很好操作的,除了有点儿废头发……

但是 毕竟有句俗话说的好!

“遇到困难¥%……&#¥%  !!!!”

在这里插入图片描述
对!没错!自带画面和声音的文字,就是这么diao

我会把整个工程文件都打包附在后面,每部分主要代码也都会在后面说明
想折腾的朋友可以参考一下

故事,还得从一只蝙蝠说起…

家里的万年历是 11年产的康巴丝的万年历,在它服役的这些年中,IC已经坏过一次了,上次在某宝买了同款IC给它强行续了2年的命。这几天突然发现它又罢工了,时间跳回到了11年7月,重复出现3次,以为是IC又坏了,既然这样那就开始动手给它大修一下吧。【后经室友提醒,发现是纽扣电池没电了…并不是IC的问题,不过那都不重要了,好不容易才决定要升级它的

老物件了,没了它,那个地方空空的 其实 在学校的时候,就有想过把这个钟给升级成WiFi联网的,只是回家之后一直动力不足,有时间就打游戏了,这波正好赶上了,正好干TND一波。

先来看一下前后对比

硬件方面

升级之前

黄圈为IC主控
经过研究发现这货是8-12V交流电供电

升级之后

现在它就是一个32主控的万年历啦

  • 5V/0.5A 充电器供电
  • STM32F103ZET6
  • ESP8266-01  8脚
  • Ds1302时钟芯片
  • DHT11温湿度模块  获取温湿度
  • 康巴斯万年历电路板(只用了上面的数码管 数码管由32直接驱动

功能方面

功能对比之前的主控我写的32主控说明
时间每30分钟对时一次
阳历
农历用阳历转农历算法
温度DHT11
湿度x强行安排*
闹钟x个人感觉一个很鸡肋的功能
流水鸟叫x&……*@&#
按键设置x物联网时代了 砍掉
秒闪烁x*LED闪烁会让其他数码管变暗

强行安排*本来是没有湿度功能的,那自然没有湿度显示的位置 这里我让温度湿度循环在原来的温度位置上显示 每10秒换一下 因为我家在南方 湿度常年90%以上 所以湿度和温度很好区分
在这里插入图片描述

那个流水和鸟鸣*声音是万年历的一个功能 见过的人肯定知道 不懂的我也说不明白 就这样吧 反正很鸡贼

这里阳历转农历算法是用的 博客园@离子 前辈的代码 接触过的这部分的人都知道 阳历算法很容易 但是农历比较复杂 不像阳历那样有规律可循 得依赖天文学家的 推算 因此农历这部分是最后完成的 阳历转农历算法在网上找了好几天 终于找到这个 亲测可用

引用过来后 结构体定义仍用hzj定义 以示敬意

附上链接 农历和阳历互转(c语言)-离子

LED闪烁* 不懂电路 全是暴力推挽输出。 让 LED闪烁 示意秒 结合这个电路,算法上我实现了 但是在实际发现GPIO驱动能力不够 会导致数码管亮度变化特别大 于是砍掉了

调用序列图-这里只放个大概 详细的还得结合程序说

去年暑假的时候 自学8266SDK编程 照着XianYu上的卖的8266气象站 照葫芦画瓢 自己做了一个
因此我积累了一点点ESP8266编程基础 这里的8266运行我写的固件 上电自动连接家里的WiFi 连接成功之后 连接SNTP服务器获取时间 之后定时通过UART发送时间

而不是用等待AT指令做32从机

STM32 DS1302 DHT11 ESP8266 SNTP服务器 STM32、ESP8266 上电后同时开始运行 用UART串口通信 初始化 初始化 获取BCD码时间/10sec 获取温湿度/10min 请求时间戳/1sec 返回时间戳 看门狗守护 发送最新时间戳/30min 若超过12小时 没收到正确时间戳 则重启系统 长度校验失败,不写入 写入最新时间 STM32 DS1302 DHT11 ESP8266 SNTP服务器

程序流程图

上面的图是个大概 顺手玩玩CSDN编辑器新功能而已 具体的当然还是得看流程图!

STM32流程图

 *为带注释的项 
Created with Raphaël 2.2.0 STM32 上电 系统初始化 创建 数码管寄存数组 主循环 刷新数码管 中断? 1秒定时器中断 清除中断标志 10秒时间到 读取DS1302时间 BCD时间转十进制 阳历转农历 flag+1 flag奇偶判断 写湿度->数码管寄存数组 更新所有数据 到 数码管寄存器 FLAG0超过24h未置位 重启STM32 * 退出中断 写温度->数码管寄存数组 10分钟时间到 读取DHT11数据 串口接收中断 清除中断标志 时间戳有效 时间戳转阳历 阳历时间转BCD BCD码写入DS1302 DS1302更新成功 置位FLAG0 退出中断 按照寄存数组刷新数码管 yes no yes no yes no yes no yes yes yes no
FLAG0超过24h未置位 重启STM32* 

为什么要有这一句呢 因为在程序实际运行中我发现 接收中断运行到一定次数之后 DMA搬运过来的数据永远都只有一个字节 我也不清楚问题出在哪里 这个可以通过复位解决 那显然不是8266的问题 问题肯定出在接收上 但是我找不到问题出在哪里

于是就写了这个 相当于一个看门狗程序吧

为了保证时间准确的无奈之举

ESP8266流程图

里面大量用到了中断回调函数 但原理与中断无异 所以我流程图简化为中断函数

Created with Raphaël 2.2.0 8266上电 系统初始化 连接家里路由器 入网成功 进入主循环 1秒中断定时 连接SNTP服务器 成功? 获取到网络时间 30分钟到 通过串口发送最新网络时间字符串给STM32 退出中断 延时 yes no yes no yes no yes no

关于中断回调函数与ESP8266编程 我看的是B站上的视频 有兴趣学习的可以看看
同样的附上链接 B站 ESP8266 IoT教程

下面说说我的“研发”流程

[ 硬件 ] 数码管电路研究 PCB走线

拆机发现 这个万年历使用的数码管为5101A  共阳极
在这里插入图片描述
下面是我用表量出来的接线图
在这里插入图片描述
段选:一个框内的表示这些数码管的 阴极 a b c d e f g 是连在一起的 颜色与右下角IC位置引脚对应
:其中月份的十位、天数的十位 比较特殊 只有 1 或者 2 驱动的时候对这部分需要特殊处理

左下角的IC已被拆下
位选:这里相同颜色的框表示这些数码管阳极连在一起

有了以上的工作,再来后续的驱动程序设计,着手进行编程

[ 程序 ] 各硬件驱动 8266通信

STM32与DS1302

程序员这行(我肯定是个假程序员 本科机电工程 在读每天为了头秃的机械加工头疼) 素来有不要重复造轮子这个说法 ,本着这一精神的指导,我迅速找到了普中科技开发板的配套源码中DS1302的驱动部分 移植了一手 三下五除二的调了一上午。。。。

//引脚定义(这个我能保证绝对原创   hhhhh
#define DS1302_RST 		PEout(4)
#define DS1302_IO		PEout(6)
#define DS1302_IO_R		PEin(6)
#define DS1302_CLK		PEout(5)
#define GPIO_Pin_RST    GPIO_Pin_4
#define GPIO_Pin_IO     GPIO_Pin_6
#define GPIO_Pin_CLK    GPIO_Pin_5

u8 ACC=0;
u8 TimeDate[7]={0};
extern hjz osolar;
void DS1302_Init(void){
	GPIO_InitTypeDef  GPIO_InitStructure;								//GPIO结构体
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);	            //使能PE端口时钟

	GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_RST|GPIO_Pin_IO|GPIO_Pin_CLK);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 	        //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 	        //IO口速度为50MHz
	GPIO_Init(GPIOE, &GPIO_InitStructure);					 			//根据设定参数初始化GPIOD
	GPIO_SetBits(GPIOE,GPIO_Pin_RST|GPIO_Pin_IO|GPIO_Pin_CLK);				//PE.12 PE.14 输出高

}

/******************************************************************************
函数名称:DS1302_WriteOneByte
函数功能:向DS1302写入一个字符
入口参数:ucData-数据
返回值:无
备注:无
*******************************************************************************/
void DS1302_WriteOneByte(unsigned char ucData) 
{ 
    unsigned char i;
    ACC = ucData;
    DS1302_RST = 1;
    for(i=8; i>0; i--)
    {
        DS1302_IO = ACC&0x01;         
        DS1302_CLK = 0;
        DS1302_CLK = 1;				//先写入最低位,上升沿写入
        ACC = ACC >> 1; 
    } 
}

/******************************************************************************
函数名称:DS1302_ReadOneByte
函数功能:从DS1302读取一个数据
入口参数:无
返回值:读取的数据
备注:无
*******************************************************************************/
u8 DS1302_ReadOneByte(void) 
{ 
    unsigned char i,temp=0;
	GPIO_InitTypeDef  GPIO_InitStructure;
    DS1302_RST = 1;
    for(i=8; i>0; i--)
    {
        ACC = ACC >>1;         //相当于汇编中的 RRC 
        DS1302_IO = 1;
        DS1302_CLK = 1;
        DS1302_CLK = 0;			   //下降沿读取,先读最低位
			
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_IO;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 		 	//浮空输入
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 	//IO口速度为50MHz
        GPIO_Init(GPIOE, &GPIO_InitStructure);					 			//根据设定参数初始化GPIOE
    
        temp=0;
        temp=GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_IO);
//				temp=DS1302_IO_R;
        ACC = (temp<<7)|ACC;
			
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_IO;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 	//推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 	//IO口速度为50MHz
        GPIO_Init(GPIOE, &GPIO_InitStructure);					 			//根据设定参数初始化GPIOD

    } 
    return(ACC); 
}

/******************************************************************************
函数名称:DS1302_WriteOneByteAtAddr
函数功能:在DS1302的指定位置写入一个数据
入口参数:ucAddr-地址	  ;  ucData-数据
返回值:无
备注:无
*******************************************************************************/
void DS1302_WriteOneByteAtAddr(u8 ucAddr, u8 ucData)
{
	DS1302_RST = 0;
	DS1302_CLK = 0;
	DS1302_RST = 1;
	DS1302_WriteOneByte(ucAddr);      
	DS1302_WriteOneByte(ucData);      
	DS1302_CLK = 1;
	DS1302_RST = 0;
	DS1302_IO = 0;
}

/******************************************************************************
函数名称:DS1302_ReadOneByteAtAddr
函数功能:在指定的位置读取一个数据
入口参数:ucAddr-地址
返回值:读取的数据
备注:无
*******************************************************************************/
u8 DS1302_ReadOneByteAtAddr(u8 ucAddr)
{
	unsigned char ucBackValue;
	DS1302_RST = 0;
	DS1302_CLK = 0;
	DS1302_RST = 1;
	DS1302_WriteOneByte(ucAddr);             
	ucBackValue = DS1302_ReadOneByte();         
	DS1302_CLK = 1;
	DS1302_RST = 0;
	return(ucBackValue);
}

u8 DS1302_Get_Time(void){                 //从DS1302读取时间--转码--存入数组中
    u8 year=0,mon,day,week,hour,min,sec;
    year=DS1302_ReadOneByteAtAddr(ADDR_YEAR|DS1302_READ);     //读取 年
    mon=DS1302_ReadOneByteAtAddr(ADDR_MONTH|DS1302_READ);     //读取 月
    day=DS1302_ReadOneByteAtAddr(ADDR_DAY|DS1302_READ);       //读取 日
    week=DS1302_ReadOneByteAtAddr(ADDR_WEEK|DS1302_READ);     //读取 周
    hour=DS1302_ReadOneByteAtAddr(ADDR_HOUR|DS1302_READ);     //读取 时
    min=DS1302_ReadOneByteAtAddr(ADDR_MIN|DS1302_READ);       //读取 分
    sec=DS1302_ReadOneByteAtAddr(ADDR_SEC|DS1302_READ);       //读取 秒

/********************************************************************/
//BCD转十进制														//
//嗯!这个  原创 +  1													//
    TimeDate[Data_Year]=year/16*10+year%16;         				//	
    TimeDate[Data_Week]=week;										//	
    TimeDate[Data_Mon]=mon/16*10+mon%16;							//	
    TimeDate[Data_Day]=day/16*10+day%16;							//	
    TimeDate[Data_Hour]=hour/16*10+hour%16;							//
    TimeDate[Data_Min]=min/16*10+min%16;							//
    TimeDate[Data_Sec]=sec/16*10+sec%16;							//
      																//
//阳历转阴历函数参数赋值  												//
    osolar.year = TimeDate[Data_Year]+2000;							//
    osolar.month = TimeDate[Data_Mon];								//
    osolar.day = TimeDate[Data_Day];								//
    if(week>=7){													//
        printf("\n\n\t获取时间失败,请检查DS1302连接\r\n\r\n");			//
        return 0;													//
    }																//
/********************************************************************/    
	return 1;
}

/******************************************************************************
函数名称:DS1302_SetInit
函数功能:设置初始化
入口参数:pClk-初始化数组的指针
返回值:无
备注:无
*******************************************************************************/
void DS1302_SetInit(u8 *pClk) 
{
    u8 i;
    u8 ucAddr = 0x80; 
    DS1302_WriteOneByteAtAddr(0x8E,0x00);           /* 控制命令,WP=0,写操作*/
    for(i =7; i>0; i--)
    { 
        DS1302_WriteOneByteAtAddr(ucAddr,*pClk);  /* 秒 分 时 日 月 星期 年 */ 
        pClk++;
        ucAddr +=2;
    }
    DS1302_WriteOneByteAtAddr(0x8E,0x80);           /* 控制命令,WP=1,写保护*/
    printf("\r\n\t已从网络更新时间到本地\r\n");
}

人家的代码 每个函数的说明部分 整的多好 赏心悦目的
我想了想 还是算了吧 不如我的省事儿

STM32与DHT11

这部分的DHT11 代码同样是我 白嫖来的
出处为 技新课堂 ESP8266 IOT教程->时钟温湿计 Demo

本来此处准备放代码的   
但是我感觉一大段一大段的代码   
放这儿大家可能也懒得看
还是不放了吧  有心人自己去工程文件看吧
路径在 
./HARDWARE/dht11.c
ESP8266编程

这里我选择用8脚的ESP01 主要考虑到只需要联网串口功能
用ESP12有点儿浪费
第一次用ESP01编程
发现 烧录 开机 复位都比较麻烦
于是焊了一个电路板 给他烧录用
在这里插入图片描述
实践发现下载烧录的过程中 不能长时间通电 不然芯片会特别烫
温度上升会导致烧录失败

ESP8266获取网络时间

这里因为我之前玩过 所以上手还算轻松

这个部分搞懂他的回调函数是关键在这里插入图片描述
这部分教程来自 B站
附上视频链接:P45 物联网教程_43_SNTP

STM32与8266通信

前面的1302 DHT11 包括8266的网络设置 都是比较轻松的 毕竟都是之前接触过的

但是双机通信这块 是之前没接触过的

想了想 还是用串口通信吧 毕竟这个是最简单了

说一下我的方案
STM32打开串口3的接收中断
STM32打开DMA
8266每30分钟串口发送一次时间戳数据
触发中断
DMA将串口外设搬运到内存
之后对数据有效性进行验证
将有效数据经行处理
最后将数据写入1302 完成网络时间更新

对应电路板线路写驱动程序

这部分主要是对GPIO的操作 定义引脚
在这里插入图片描述
小写a b c d e f g 为段
大写A B C D E F 为位

以往接触到的数码管都是共阴极的 要点亮对应的段只需要拉高电平就行了
这次的数码管是共阳极的
我还是按照以往共阴极的办法定义的 只是最后在GPIO输出的时候 按位取反了

这个是我之前在学校玩数码管的时候
针对共阴极的管子写的
可以供大家参考

#define Num_bit_a 	0x01
#define Num_bit_b 	0x02
#define Num_bit_c 	0x04
#define Num_bit_d 	0x08
#define Num_bit_e 	0x10
#define Num_bit_f 	0x20
#define Num_bit_g 	0x40
#define Num_bit_dp 	0x80

#define Num_0 Num_bit_a|Num_bit_b|Num_bit_c|Num_bit_d|Num_bit_e|Num_bit_f
#define Num_1 Num_bit_b|Num_bit_c
#define Num_2 Num_bit_a|Num_bit_b|Num_bit_g|Num_bit_e|Num_bit_d
#define Num_3 Num_bit_a|Num_bit_b|Num_bit_c|Num_bit_d|Num_bit_g
#define Num_4 Num_bit_b|Num_bit_c|Num_bit_g|Num_bit_f
#define Num_5 Num_bit_a|Num_bit_c|Num_bit_g|Num_bit_f|Num_bit_d
#define Num_6 Num_bit_a|Num_bit_d|Num_bit_c|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_7 Num_bit_a|Num_bit_b|Num_bit_c
#define Num_8 Num_bit_a|Num_bit_b|Num_bit_c|Num_bit_d|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_9 Num_bit_a|Num_bit_b|Num_bit_d|Num_bit_c|Num_bit_f|Num_bit_g
#define Num_A Num_bit_a|Num_bit_b|Num_bit_c|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_b Num_bit_c|Num_bit_d|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_C Num_bit_a|Num_bit_d|Num_bit_e|Num_bit_f
#define Num_d Num_bit_b|Num_bit_c|Num_bit_d|Num_bit_e|Num_bit_g
#define Num_E Num_bit_a|Num_bit_d|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_F Num_bit_a|Num_bit_e|Num_bit_f|Num_bit_g
#define Num_G Num_bit_a|Num_bit_c|Num_bit_d|Num_bit_e|Num_bit_f
#define Num_H Num_bit_b|Num_bit_c|Num_bit_e|Num_bit_f|Num_bit_g

[Again 硬件]电路板引线到GPIO

万事开头难在这里插入图片描述
穿完第一行排线1个小时获取了在这里插入图片描述
两排焊接完,已经是凌晨5点了…
狗命要紧 赶快睡觉

WK 我的驱动程序居然会有问题?!?

第二天一睡醒 立马上电 看看能不能点亮管子!
一堆乱码…
经过无数次的Debug
终于发现了
原来有一个引脚定义错了…

修改完了

上电!

一切顺利!
NICE 兄dei

可是到了夜里 忽然发现…

13月…emmmm
在这里插入图片描述
原来是天数的十位 f段 连接在了 月份 的b c 段上
(农历月份同样存在这个问题)
Debug…好了 这下终于完美了

完美了(除了农历那里空着)

苦于没有好的农历算法 只好把农历先空着
终于
某天
蹲在厕所拉💩的时候
看到了曙光

农历和阳历互转(c语言)-离子

代码拿过来在我的407上跑了一下(电脑没装C语言编译器)
不管是阳历转农历 还是农历转阳历 都没问题

可以的,这个前辈,有操作的呀!

经过又一次的烧录,终于完善了农历部分

可以 我很满意

看着温度从14℃
看着温度从刚开始的14℃到现在的21℃ 春天真的来啦
在这里插入图片描述
全部做完大概10天的功夫

后来我看了一下 某宝上不带农历的万年历 180元 软妹币

“这波操作 我给满分”

后记

我之前几乎没有接触过STM32 在这以前 手里最多的还是晶宏STC
像32这种库开发之前并没有接触过
入门库开发还应该是ESP8266 SDK编程 有了这个基础 我看着原子的32源码 还算是比较轻松
根开发51比 单单就程序问题检测这个 32的一个串口打印 简直甩51太多了

不足

1.因为驱动电路几乎没有 全是硬上拉 一个阳极GPIO连接的数码管数量有差距 所以 不同的数码管之间 亮度会有差距 白天也还能看清 凑合着过吧
2.首次上电温湿度数据为0 ;正常运行中 偶尔会出现几十秒温度数据为0 的情况 之前一直不知道为啥 终于前几天 B站一个UP主 提到DHT11在长时间供电情况下会出现数据读取错误 以及 第一次上电读取错误的情况 虽然知道了问题的解决办法 但是我用的32不像树莓派那样方便热升级 还得把钟拿下来拆开 懒得弄了 先这样吧 影响也不大
3.本该是根网络时间查几秒的,但是上次观察到时间差了整整一分钟 这个是个很反常的情况 按道理来说 每天不停的更新时间 不应该出现这么大的差距 我觉得我的程序都没问题啊 搞不明白 在观察几天看看吧

资料下载

本次所有的代码\用到的芯片手册
我都打包好了
百度网盘容易挂 本来想上传GitHub的 但又考虑到GitHub 可能会出现下载较慢

链接: https://pan.baidu.com/s/137ijNnFuH1303pygXNzzMg 提取码: ycuz

如果挂了 请评论 我会更新

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值