【DIY教程】作一个永远准时的WiFi时钟

新手请勿跳过的琐碎简介

  系统时间是单片机系统中常常用到的要素,通常来讲,采用RTC时钟能够获取较准确的时间。可是,你们若是有过使用街边买的便宜电子表的经验,就会知道,若是不进行对时,电子表用着用着就不许了,一年产生的偏差在十几秒到几十秒之间。这是由于电子表的精度依赖于所使用的晶振,通常低端的电子产品里使用的晶振,其精度多在20ppm、10ppm(百万分之一)这两档上。对于20ppm的晶振,理论上一年的最大偏差为31S,同时,受到温度变化、电容是否匹配等因素的影响,实际偏差可能大于这个值。同理,单片机RTC时钟的精度依赖于RTC晶振,使用时间长了以后,精度大几率不会让人满意。html

  那么,有没有啥解决办法呢?python

  有一种你们都很容易想到的办法是——氪金。毕竟,氪金带来力量是广泛规律,这一点在电子设计领域表现得格外突出。既然偏差源于晶振,那提升晶振精度不就行了嘛。的确,若是愿意花钱,那么你可使用高精度的温补晶振——顾名思义,这种晶振具备温度补偿功能。并且,做为具有补偿功能的高端产品,自己的精度通常也很不错,0.1ppm的一抓一大把,若是使用这样的晶振,理论最大年偏差为1.55S,这还要啥自行车啊。git

  不过,0.1ppm的温补晶振,价格通常在50RMB以上。编程

  因此咱们来看下一个方案吧。json

  这几年物联网之类的概念仍是炒的挺热的,相关产品也出货很多,其中乐鑫的ESP8266这款芯片能够说是个划时代的东西(指价格),将单片机系统接入网络的成本一会儿降到了7RMB之内,接入网络能够给单片机系统带来许多强大的功能,好比获取网络时间、获取本身帐户的B站粉丝数、获取天气信息甚至在线播放badapple等等。网络

  因此,第二个为单片机系统提供准确时间的方案就是,经过ESP系列或相似的具备WiFi功能的芯片,获取网络时间,而后发送给主控单片机进行显示。app

  若是有电子设计领域的熟手路过,看到这可能会笑出声来。由于,其实ESP系列的芯片自己就是一块单片机,并且其性能在常见32位单片机里算是至关不错的,引脚数量也很多,电子爱好者我的轻度使用彻底足够。函数

  可是,外挂芯片方案也是有着本身的优点的——WiFi模块和主控MCU相对分离,在程序设计上能够较为容易地处理系统的层次结构。并且,毕竟有不少人只熟悉5一、STM32啥的工具

  琐碎的话就说到这里,立刻开始动手作吧!oop

材料清单

  1. ESP8266-01S模块 × 1
  2. ESP8266下载器(基于CP2104)× 1
  3. STM32F103C8T6核心板 × 1
  4. 0.96‘ OLED屏(IIC接口)× 1

    总的材料花费在30左右

如何获取网络时间

  准确的网络时间通常经过NTP(Network Time Protocol)来获取,具体的实现流程可能稍显复杂,并且这些流程每每是相对固定的,没什么意思(毕竟能修改的东西才好玩嘛),为此,笔者决定选用比较简单的方式来给你们作个示范。

  咱们链接WiFi是经过ESP8266实现的,一般你们给ESP8266写程序的方式有这些:

  1. 采用官方SDK进行开发(基于C/C++)
  2. 烧录micropython固件之后写micropython
  3. 使用Arduino IDE

  从设计意图就能知道,在咱们想偷懒的时候该选择哪个——SDK面向的主要是是使用其产品进行深度开发的工程师;micropython主要是为了给软件开发者玩硬件提供便利;而Arduino是为了给非专业人士提供控制硬件进行交互的傻瓜化方法,因此就选它了。

  首先去下载Arduino,官网选个最新的版本便可,连接在这:

https://www.arduino.cc/en/Mai...

  标有“ZIP file for non admin install”字样的是解压后直接能够运行的版本。

  下载后打开,而后从菜单栏依次选择“文件 -> 首选项 ”,在“附加开发板管理网址”中填入http://arduino.esp8266.com/st...,确认。

  而后从菜单栏依次选择“工具 -> 开发板 -> 开发板管理器”,稍稍等待一会,而后在搜索框中输入esp8266,选择对应的库进行安装,下面是安装好的效果:

P2.jpg

  为了使用NTP,咱们须要再额外安装一些库(站在大神的肩膀上XD)。打开“工具 -> 管理库”,输入NTPClient,安装名字彻底匹配的那个库。以后,以相同方式安装“ArduinoJson”这个库。另外,为了使用WiFi功能,还须要ESP8266WiFi和WiFiUdp这两个库,不过这些是软件自带的直接使用就行。

链接WiFi

  要链接WiFi,最简单的方式是在程序里预先配置好SSID和密码后直接链接,具体以下:

const char *ssid     = "your SSID";
const char *password = "your password";

  另外顺便说一下,Arduino的程序结构基本就是setup和loop这两个函数,setup相似于咱们日常使用的初始化函数,loop相似于单片机编程经常使用的while(1)循环。初始化操做通常放在setup函数里面(好比链接WiFi),以下:

WiFi.begin(ssid, password);

  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }

经过NTP获取GMT时间

  学过C语言的同窗应该都知道“Unix时间”,用time函数就能够获取,其数值表示自1970年01月01日 0:00:00至当前所通过的秒数,时间标准为GMT时间。咱们经过NTP获取的就是这样的数值(借助NTPClient库)。

timeClient.update();
  epoch_time = timeClient.getEpochTime(); //epoch_time为预先定义的32位无符号数

转换时间数据格式

  直接获取的时间数据不太适合人类理解,通常人应该不能一眼从“1585144726”这样的数据解读出当前的时间吧?因此,把它转换成通常的时间格式是颇有必要的。

  使用Arduino的时候就该偷懒嘛,此次仍是用现成代码解决问题,笔者使用了一位网友的代码来进行时间格式的转换,地址:https://blog.csdn.net/mill_li...

  数据转换结果将被存入以下的结构体:

typedef struct
{
    unsigned short nYear;
    unsigned char nMonth;
    unsigned char nDay;
    unsigned char nHour;
    unsigned char nMin;
    unsigned char nSec;
    unsigned char DayIndex; /* 0 = Sunday */
} mytime_struct;

数据打包发送

  若是咱们仅仅是想作个能显示时间的时钟的话,只要发送一个时间数据,而后在主控单片机那边解析出时分秒什么的就能够了。可是,若是考虑到要方便后期添加各类诸如天气显示之类的功能的话,就有必要选用一种灵活的数据交换格式了。

  笔者在这里选择选择json。

  JSON是一种简洁、轻量的、基于文本的数据交换格式,使用json交换数据时,能够避免考虑一些大端小端之类的问题。关于json的格式,能够看这里:https://www.cnblogs.com/mcgra...

  咱们以前下载的ArduinoJson库能够帮助咱们完成打包Json字符串和经过串口发送它的工做。部分代码以下:

StaticJsonDocument<200> doc; //建立Json对象
//添加关键字和对应的值
doc["year"] = 0;
doc["mon"]  = 0;
···
//每次接收完网络时间并完成格式转换后,更新Json对象中的数据
doc["year"] = my_time.nYear;
doc["mon"]  = my_time.nMonth;
doc["day"]  = my_time.nDay;
···

serializeJson(doc, Serial);//经过串口发送根据Json对象制造的Json字符串

  发送完数据之后就没ESP8266什么事了,这部分的完整代码在这里:https://gitee.com/multicolore...

  接下来,咱们能够在主控单片机上接收Json字符串、解析数据而且进行显示了,笔者这里选用STM3二、Keil,显示用0.96寸、IIC接口的OLED屏。

STM32接收JSON字符串

安装Jansson库

  在比较正经的单片机开发中,单片机接收字符串通常经过cjson来实现,不过须要本身移植一下,在Keil下进行开发时,可使用官方提供的Jansson这个库来对Json进行处理。

  使用库前得先去官网把pack下下来,地址:http://www2.keil.com/mdk5/par...

  下载完双击安装便可。

  以后打开一个Keil的工程,在如图位置单击打开运行时环境管理窗口

P1.jpg

  而后勾选下图位置:

P3.jpg

  以后点击确认便可。

Jansson库API简单说明

  Jansson这个库提供了一下用于处理Json的API,这里咱们用到如下几个:

//从Json字符串建立Json对象
json_t *json_loads(const char *input, size_t flags, json_error_t *error)

//解析Json对象,获取数据
int json_unpack(json_t *root, const char *fmt, ...)

//删除Json对象
json_delete(json_t *object)

  json_delete的使用最为简单,调用的时候把json对象的名称传入就行。

  json_loads的第一个参数是指定的字符串首地址(也就是它的名称),第二个参数经常使用“JSON_ENCODE_ANY”,第三个是要求预先建立的一个json错误对象,用于一些错误提示信息的存储。

  json_unpack使用的时候稍稍复杂些,须要填入一个相似于{s:i,s:i,s:i}这样的列表,这里s表明字符串,i表明int型变量,同时,能够指定解析获得的数据的存储位置,以下:

json_unpack(epoch_time_raw,"{s:i,s:i,s:i,s:i,s:i,s:i,s:i}", \
"year",&year,"mon",&mon,"day",&day,"day_index",&day_index,  \
"hour",&hour,"min",&min,"sec",&sec);

其余

  串口接收的程序是基于原子的代码改的,没有作什么工做,并且相关的讲解也比较多,这里就不赘述了。奉上上粗糙的代码一份,招待不周,还请原谅。

  地址:https://gitee.com/multicolore...

(测试代码基于STM32F103C8T6,V3.5版本库函数)

相关文章
相关标签/搜索