本帖最后由 HonestQiao 于 2022-12-6 11:47 编辑
#申请原创# 一、想法起源
之前使用ESP32+OLED(SSD1306)只做过基于NTP对时的互联网时钟,无需RTC,也不需要对时,效果非常好。MM32L0136C7P开发板上自带了SLCD,其上有两个数字显示区域,用来做时钟显示,再合适不过了。
在官方最新的演示代码中,只有SLCD各笔画的统一显示测试。
后经过坛友分享,拿到了MM32L0130_LibSamples_V020_1201.zip,其中有SCLD数字和单位符号显示的实例。
经过一番研究后,将ESP8266与MM32L0136C7P相结合,实现了如下的电子时钟:
二、实现规划
查找资料的时候,看到有大佬在MM32上,实现了ESP8266的透传,非常的厉害。
不过我这个应用,就是简单的进行NTP对时即可。
所以NTP对时这个部分,就让ESP8266自己完成就好了。
ESP8266对时后,通过串口,每秒钟,自动上报一次当前时间数据。
MM32L0136C7P开发板则通过串口,来获取ESP8266提供的时间信息。
为了确保处理效率更好,经过研究学习例子,最终使用中断的方式,来进行数据的接收,效率更高。
最后,为了整体显示效果更好,把MM32L0136C7P开发板上的4个LED都利用起来了,根据时间信息中当前秒的个位数,来决定LED的显示,这样就活了起来。
具体规的功能:
1. 由ESP8266直接联网,进行NTP对时,然后通过串口发送时间信息,发送数据的格式为:Tyyyy-mm-dd HH:MM:SS XX,其中XX为CRC8校验码
2. MM32L0136C7P开发板通过串口获取时间信息,进行解析,然后显示到SLCD屏幕上。
3. MM32L0136C7P开发板取秒数中的第0、1、2、3bit位,该n位为1,则点亮对应的LDn
三、硬件物料
1. ESP8266-S1
ESP32一般都要大一点点,而ESP8266-01则较为较小,所示使用了一块ESP8266-01来提供网络对时功能。
为了方便烧录,我还有一个ESP8266-01的底座:
把ESP8266-01插上底座,就可以直接接到电脑上,进行固件下载烧录了。
另外,ESP8266-01正常运行时,需要CH_PD高电平使能,通常是直接VCC:
所以,直接在ESP8266-01上面,在VCC和CH_PD引脚上,焊了一个跳线,刚好用MM32L0136C7P开发板上的UART1的跳线帽:
2、MM32L0136C7P开发板
参考原理图,以及演示代码,使用UART1来进行和ESP8266-01进行通讯,所对应的引脚选用PA9、PA10
为了稳固,把ESP8266-01开发板,直接用胶枪给沾到了MM32L0136C7P开发板右上角:
具体接线,就在ESP8266-01背后偷偷进行了:
不过MM32L0136C7P开发板的接线只能在正面进行。
四、软件设计
软件部分,分为两个部分,一个是ESP8266上面的NTP对时和事件信息提供,一个是MM32L0136C7P开发板上的主逻辑。
1. ESP8266部分
简单起见,使用了Arduino IDE进行开发,且还提供了相应的实例。
具体代码如下:
/*
* ESP8266_NTP_Time_Clock.ino
* NTP Time Clock at ESP8266
*
*/
#include <TimeLib.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiUdp.h>
#define DEBUG_MODE 0
ESP8266WiFiMulti wifiMulti;
// WiFi connect timeout per AP. Increase when connecting takes longer.
const uint32_t connectTimeoutMs = 5000;
const char ssid[] = "OpenBSD"; // your network SSID (name)
const char pass[] = "********"; // your network password
// NTP Servers:
static const char ntpServerName[] = "ntp.aliyun.com";
// TimeZone
const int timeZone = 8; // CST +8
WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
static const uint8_t crc_table[] = {
0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31,
0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9,
0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1,
0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe,
0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16,
0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80,
0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8,
0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10,
0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f,
0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7,
0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef,
0xfa, 0xfd, 0xf4, 0xf3
};
time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);
uint8_t crc8(uint8_t *p, uint8_t len);
void setup()
{
WiFi.persistent(false);
Serial.begin(115200);
while (!Serial) ; // Needed for Leonardo only
delay(250);
Serial.println("I:ESP8266 NTP Time Clock");
Serial.print("I:Connecting to ");
Serial.println(ssid);
// WiFi.begin(ssid, pass);
// while (WiFi.status() != WL_CONNECTED) {
// delay(500);
// Serial.print(".");
// }
WiFi.mode(WIFI_STA);
wifiMulti.addAP("OpenBSD", "13581882013");
while (wifiMulti.run(connectTimeoutMs) != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("OK");
Serial.print("I:IP address is ");
Serial.println(WiFi.localIP());
Serial.println("I:Starting UDP");
Udp.begin(localPort);
Serial.print("I:Local port: ");
Serial.println(Udp.localPort());
Serial.println("I:waiting for sync");
setSyncProvider(getNtpTime);
setSyncInterval(300);
}
time_t prevDisplay = 0; // when the digital clock was displayed
void loop()
{
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
digitalClockDisplay();
}
}
}
uint8_t crc8(char *p, uint8_t len)
{
uint16_t i;
uint16_t crc = 0x0;
while (len--) {
i = (crc ^ *p++) & 0xFF;
crc = (crc_table[i] ^ (crc << 8)) & 0xFF;
}
return crc & 0xFF;
}
void digitalClockDisplay()
{
// digital clock display of the time
// Serial.print(hour());
// printDigits(minute());
// printDigits(second());
// Serial.print(" ");
// Serial.print(day());
// Serial.print(".");
// Serial.print(month());
// Serial.print(".");
// Serial.print(year());
// Serial.println();
char timeStr[25] = {0};
sprintf(timeStr, "T:%04d-%02d-%02d %02d:%02d:%02d ", year(), month(), day(), hour(), minute(), second());
// Serial.println(timeStr);
uint8_t crc = crc8(timeStr, strlen(timeStr));
sprintf(timeStr, "%s%02X", timeStr, crc);
Serial.println(timeStr);
}
void printDigits(int digits)
{
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("I:Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print("I:");
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("I:Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("E:No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
该部分的代码,参考了Arduino IDE的8266例子中的:
在此基础上,进行了一些小的修改:
- 将WiFi连接部分,修改为了WiFiMul,这样就可以同时设置多组WiFi连接,更方便使用。
- 添加了查表计算CRC8的代码,以便接收端收到后进行校验,确保数据准确。不过,MM32L0136C7P不分,我没有使用到,大家有需要可以在接收后进行校验。
- 修改了信息的输出,正常信息使用I:开头,错误信息使用E:开头,时间信息使用T:开头,方便接收端进行解析
将以上代码烧录到ESP8266,然后打开串口终端,就可以收到启动的信息,以及时间信息:
测试验证完成,就可以连到MM32L0136C7P开发板上使用了。
2. MM32L0136C7P开发板
该部分的代码,参考了SLCD_ShowOnStop、UART_Tx_DMA_Interrupt_Rx_Interrupt。
UART_Tx_DMA_Interrupt_Rx_Interrupt实例,提供了中断收发数据的演示,基本不用修改,连引脚定义都是对应的,直接就可以使用了。
SLCD_ShowOnStop,则提供了显示数字和单位符号的演示,其中关键的调用如下:
- LCD_Clear():清屏
- LCD_DisplayNumber1(索引, '字符', 小数点标志):大号数字区域显示
- LCD_DisplayNumber2(索引, '字符', 小数点标志):小号数字区域显示
- LCD_DisplayUnit(索引, 显示标志):单位符号显示
大号数字显示:LCD_DisplayNumber1(索引, '字符', 小数点标志)
索引对应如下:
字符对应如下:
从上表中可知,除了0-9,还能显示一部分字母,但是不全,例如没有K,因为没法显示区分。
小数点标志,就表示是否显示小数点,0-不显示,1-显示
小号数字显示:LCD_DisplayNumber2(索引, '字符', 小数点标志)
索引对应如下:
小号区域的显示字符,演示代码只有0-9。当然,可以根据自己的需要进行扩展。
小数点标志和大号区域相同。
单位符号显示:LCD_DisplayUnit(索引, 显示标志)
索引有10个,对应下图:
{"S1 ", "S2 ", "S3 ", "S4 ", "S9 ", "T1 ", "W1 ", "C1 ", "C2 ", "C3 "};
时钟部分,要显示的:,使用的就是C2、C3,索引为8、9
最终,main.c的代码如下:
////////////////////////////////////////////////////////////////////////////////
/// [url=home.php?mod=space&uid=288409]@file[/url] main.c
/// [url=home.php?mod=space&uid=187600]@author[/url] AE TEAM
/// [url=home.php?mod=space&uid=247401]@brief[/url] THIS FILE PROVIDES ALL THE SYSTEM FUNCTIONS.
////////////////////////////////////////////////////////////////////////////////
// Define to prevent recursive inclusion
#define _MAIN_C_
// Files includes
#include "delay.h"
#include "slcd.h"
#include "led.h"
#include "uart_txdma_rx_interrupt.h"
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup MM32_Example_Layer
/// @{
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup MAIN
/// @{
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup MAIN_Exported_Constants
/// @{
#define GET_BIT(x, bit) ((x & (1 << bit)) >> bit) /* 获取第bit位 */
#define APP_TITLE "I:MM32 NTP Net Clock\r\n"
u8 gSendBuf[] = "I:yyyy-mm-dd HH:MM:SS\r\n";
////////////////////////////////////////////////////////////////////////////////
/// [url=home.php?mod=space&uid=247401]@brief[/url] This function is main entrance.
/// @param None.
/// @retval 0.
////////////////////////////////////////////////////////////////////////////////
s32 main(void)
{
u8 len;
u16 times = 0;
u16 year;
u8 month,day,hour,minute,second;
u8 crc;
u8 buf[25] = {0};
DELAY_Init();
LED_Init();
DELAY_Ms(100);
//slcd_test();
slcd_init();
UART1_NVIC_Init(115200);
NVIC_Configure(DMA1_Channel2_3_IRQn, 2, 0);
UART1_SendString(APP_TITLE);
DMA_NVIC_Send_Config(DMA1_Channel2, (u32)&UART1->TDR, (u32)gSendBuf, sizeof(gSendBuf)/sizeof(gSendBuf[0]));
LCD_Clear();
LCD_DisplayNumber2(0, '2', 0);
LCD_DisplayNumber2(1, '0', 0);
LCD_DisplayNumber2(2, '2', 0);
LCD_DisplayNumber2(3, '2', 0);
LCD_DisplayNumber1(0, '-', 0);
LCD_DisplayNumber1(1, 'C', 0);
LCD_DisplayNumber1(2, 'l', 0);
LCD_DisplayNumber1(3, 'o', 0);
LCD_DisplayNumber1(4, 'c', 0);
LCD_DisplayNumber1(5, 'L', 0);
DELAY_Ms(5000);
while(1) {
if(gUartRxSta & 0x8000) {
//receive data length
len = gUartRxSta & 0x3fff;
if(len==0x1a && gUartRxBuf[0]=='T' && gUartRxBuf[1]==':') {
sscanf(gUartRxBuf, "T:%04d-%02d-%02d %02d:%02d:%02d %2X", &year, &month, &day, &hour, &minute, &second, &crc);
sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d\r\n", year, month, day, hour, minute, second);
UART1_SendString(buf);
LCD_DisplayNumber2(0, ((month/10)%10)?('0'+((month/10)%10)):' ', 0);
LCD_DisplayNumber2(1, '0'+((month/1)%10), 0);
LCD_DisplayNumber2(2, ((day/10)%10)?('0'+((day/10)%10)):'-', 0);
LCD_DisplayNumber2(3, '0'+((day/1)%10), 0);
LCD_DisplayNumber1(0, ((hour/10)%10)?('0'+((hour/10)%10)):' ', 0);
LCD_DisplayNumber1(1, '0'+((hour/1)%10), 0);
LCD_DisplayNumber1(2, '0'+((minute/10)%10), 0);
LCD_DisplayNumber1(3, '0'+((minute/1)%10), 0);
LCD_DisplayNumber1(4, '0'+((second/10)%10), 0);
LCD_DisplayNumber1(5, '0'+((second/1)%10), 0);
LCD_DisplayUnit(8,1);
LCD_DisplayUnit(9,1);
LED1_OFF();
LED2_OFF();
LED3_OFF();
LED4_OFF();
if(GET_BIT(second, 0)) {
LED1_ON();
}
if(GET_BIT(second, 1)) {
LED2_ON();
}
if(GET_BIT(second, 2)) {
LED3_ON();
}
if(GET_BIT(second, 3)) {
LED4_ON();
}
} else {
DMA_NVIC_Send_Config(DMA1_Channel2, (u32)&UART1->TDR, (u32)gUartRxBuf, len);
}
gUartRxSta = 0;
}
else {
times++;
if(times % 5000 == 0) {
UART1_SendString(APP_TITLE);
}
if(times % 500 == 0) UART1_SendString("I:Please input Data, End with Enter\r\n");
if(times % 50 == 0){
LED1_TOGGLE();
LED2_TOGGLE();
LED3_TOGGLE();
LED4_TOGGLE();
}
DELAY_Ms(100);
}
}
}
/// @}
/// @}
/// @}
其中的逻辑也不复杂,具体如下:
- 初始化LED、SLCD、串口以及中断和DMA设置
- LCD清屏,然后显示”2022 - ClocL“,没有K,只好用大L代替了
- 然后就是检测接收状态了,收到了,且符合时间字符串的格式,则解析其中的年月日时分秒,然后显示到SLCD,并根据秒来确定对应的LED显示,实际上就是对应1、2、4、8
另外,还有下面两个地方的代码,需要修改:
修改其中LED对应引脚的配置,以对应MM32L0136C7P开发板。
五、实际效果
将以上MM32L0136C7P开发板的代码,使用Keil编译下载后,就可以看到具体效果了:
【以下播放,加快了速度】
六、代码分享
1. 官方演示代码:
MM32L0130_LibSamples_V020_1201.zip
(5.49 MB)
2. 本应用代码:https://gitee.com/honestqiao/MM32_SLCD_NTP_Net_CLock
要使用本应用的代码,请先下载官方演示代码,并解压,然后将 MM32_SLCD_NTP_Net_CLock 目录放置到 MM32L0130_LibSamples_V020/MM32L0130_Samples/SLCD下,具体如下:
红色框柱的为关键代码,如果编译的时候提示找不到对应的文件,请自己添加一下到工程中。
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
|