Qiita Advent calendar2021の記事です。
Spresenseの関係会社に勤務していますが、業務外の個人活動の紹介です。
当記事は会社と関係ありません。
誤った基板上のピン接続により故障する可能性があります。
自己責任でお願いします。
Wi-Fi Add-onボード iS110BでUDP通信するサンプルを自作できたので、NPT時刻取得を試してみました。
###事前準備
いきなり外のNTPサーバにつなげるのは怖かったので、自分のPCでNTPサーバの機能を動くようにしました。
こちらを参考にしました。
###スケッチの作成
12/15に公開したUDP通信用のサンプルをさらに改造しました。
ここでNTPの時刻取得のためのパケットはこちらを参考にしました。
http://arms22.blog91.fc2.com/blog-entry-445.html
AppFunc.cppの変更箇所だけ抜粋
/*-------------------------------------------------------------------------*
Constants:
-------------------------------------------------------------------------*/
// RTC.hを追加
#include <RTC.h>
...
/*---------------------------------------------------------------------------*
App_UDPClient_Test
---------------------------------------------------------------------------*/
// 部分的に追加・修正
void App_UDPClient_Test(void)
{
ATCMD_RESP_E resp;
// char server_cid = 0;
uint8_t server_cid = 0;
bool served = false;
// ATCMD_NetworkStatus networkStatus;
uint32_t timer = 0;
AtCmd_Init();
App_InitModule();
App_ConnectAP();
while (1)
{
if (!served)
{
ATCMD_NetworkStatus networkStatus;
resp = ATCMD_RESP_UNMATCH;
ConsoleLog( "Start UDP Client");
resp = AtCmd_NCUDP( (char *)UDPSRVR_IP, (char *)UDPSRVR_PORT, (char *)LocalPort, &server_cid); // Create UDP Client; AT+NCUDP=<Dest-Address>,<Port>[<,Src-Port>]
ConsolePrintf( "server_cid: %d \r\n", server_cid);
AtCmd_CID();
if (resp != ATCMD_RESP_OK)
{
ConsoleLog( "No Connect!" );
delay(2000);
continue;
}
if (server_cid == ATCMD_INVALID_CID)
{
ConsoleLog( "No CID!" );
delay(2000);
continue;
}
do
{
resp = AtCmd_NSTAT(&networkStatus); // AT+NSTAT=?
} while (ATCMD_RESP_OK != resp);
ConsoleLog( "Connected" );
ConsolePrintf("IP: %d.%d.%d.%d\r\n\r\n",
networkStatus.addr.ipv4[0], networkStatus.addr.ipv4[1], networkStatus.addr.ipv4[2], networkStatus.addr.ipv4[3]);
AtCmd_Time();
served = true;
}
else
{
ConsoleLog( "Start to send UDP Data");
// Prepare for the next chunck of incoming data
WiFi_InitESCBuffer();
// Start the infinite loop to send the data
// http://arms22.blog91.fc2.com/blog-entry-445.html
// set all bytes in the buffer to 0
memset(UDP_Data, 0x00, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
UDP_Data[0] = B11100011; //0x1b; // ; // 00(no announce) + 100(version4) + 011(client) // 0b11100011 LI, Version, Mode
UDP_Data[1] = 0; //0x05; // not sync // Stratum, or type of clock // 0
UDP_Data[2] = 6; // 2^6 = 64 sec // 6 Polling Interval 2^6 = 64sec
UDP_Data[3] = 0xEC; // 0xE9; // 0xEC Peer Clock Precision 256 - EC = -20 -> 2^-20 ~ 0.9us
// 8 bytes of zero for Root Delay & Root Dispersion CP_Data[4] ~ UDP_Data[11] = 0x00
//
UDP_Data[12] = 49; // Reference ID 49 4E 49 52
UDP_Data[13] = 0x4E;
UDP_Data[14] = 49;
UDP_Data[15] = 52;
// for (int i = 0; i < NTP_PACKET_SIZE; i++)
// {
// ConsolePrintf("%02x ", UDP_Data[i]);
// }
ConsolePrintf("\r\n");
while ( 1 )
{
// ConsolePrintf( "send at cid: %d \r\n", server_cid);
AtCmd_SendBulkData(server_cid, UDP_Data, NTP_PACKET_SIZE);
// ConsolePrintf( "before GetGPIO37\r\n");
resp = AtCmd_RecvResponse(); // Description: Wait for a response after sending a command. Keep parsing the data until a response is found.
// ConsolePrintf( "response after sending a command: %d vs ref: %d\r\n", resp, ATCMD_RESP_BULK_DATA_RX);
// ConsolePrintf( "after GetGPIO37\r\n");
if ( ATCMD_RESP_BULK_DATA_RX == resp )
{
// ConsolePrintf( "Command sent successfully. \r\n\n");
if ( Check_CID( server_cid ) )
{
// ConsolePrintf( "CID check succeeded\r\nReceive %d byte: \r\n", ESCBufferCnt - 1);
// ConsolePrintf("Transmitted Data: ");
// for (int i = 0; i < NTP_PACKET_SIZE; i++)
// {
// ConsolePrintf("%02x ", UDP_Data[i]);
// }
// Serial.println("");
// ConsolePrintf("Receive Data: ");
// Receive Dataは1バイト目から開始 ESCBufferは0にサイズが入っている つまり1つずれている
// for (int i = 1; i < ESCBufferCnt; i++)
// {
// ConsolePrintf("%02x ", ESCBuffer[i]);
// }
// Serial.println("");
// 時刻情報は40, 41, 42, 43バイト目のデータ
// ESCBufferは1つずれている
unsigned long highWord = word(ESCBuffer[40+1], ESCBuffer[41+1]);
unsigned long lowWord = word(ESCBuffer[42+1], ESCBuffer[43+1]);
// ConsolePrintf("time data: %02x %02x %02x %02x \r\n\n", ESCBuffer[40], ESCBuffer[41], ESCBuffer[42], ESCBuffer[43]);
// NTPタイムスタンプ 小数部は切り捨て
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Serial.print("Seconds since Jan 1 1900 = " );
// Serial.println(secsSince1900);
// NTPタイムスタンプをUNIXタイムに変換
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears + 3600 * 9;
// Serial.print("Unix time = ");
// Serial.println(epoch);
// 雰囲気で代入した
RtcTime now1 = epoch;
Serial.print("JST is ");
Serial.print(now1.year());
Serial.print('/');
Serial.print(now1.month());
Serial.print('/');
Serial.print(now1.day());
Serial.print(' ');
Serial.print(now1.hour());
Serial.print(':');
Serial.print(now1.minute());
Serial.print(':');
Serial.println(now1.second());
Serial.println("parsed");
Serial.println("");
}
WiFi_InitESCBuffer();
delay(1000);
}
...
送信バッファのUDP_Dataと異なり、受信バッファのESCBufferは先頭1バイトが受信データの長さを表すので、変換するときに注意が必要でした。
###実行時の様子
https://youtu.be/xOePFX-oOBs
Khabane lameさんのように言うとESP32だと1行で済むよねという内容ですが、NTPの勉強になってよかったです。
https://wak-tech.com/archives/833
受信した時刻を送信するパケットに反映させたり、iS110Bに時刻の情報をセットしたりすると面白そうです。