├── BF-018A └── BF-018A.ino ├── BF-018ARev2 ├── BF-018ARev2.ino ├── BF_Pcf8563.cpp ├── BF_Pcf8563.h ├── BF_RtcxNtp.cpp └── BF_RtcxNtp.h ├── BF-018ARev3 ├── BF-018ARev3.ino ├── BF_Pcf8563.cpp ├── BF_Pcf8563.h ├── BF_RtcxNtp.cpp └── BF_RtcxNtp.h ├── BF-018ARev4 ├── BF-018ARev4.ino ├── BF_Pcf8563.cpp ├── BF_Pcf8563.h ├── BF_RtcxNtp.cpp └── BF_RtcxNtp.h ├── BF-018A_M5AtomLite.JPG ├── BF-018A_M5AtomMatrix.JPG ├── BF-018A_back.JPG ├── BF-018A_front.JPG ├── BF-018a_DOCv4.pdf ├── LICENSE ├── PCBV01L02 └── gerber │ └── JLCPCB │ ├── bf-018a_bom.csv │ ├── bf-018a_panel2-top-pos.csv │ └── bf-018al02.zip ├── README.md ├── RTC.jpeg ├── bf-018a_scm.pdf ├── bf-018a_scm_v01l02.pdf ├── wifi_ntp_rtc.jpg ├── wifi_ntp_rtc.xlsx └── wifi_status.jpg /BF-018A/BF-018A.ino: -------------------------------------------------------------------------------- 1 | // copyright 2020 BotanicFields, Inc. 2 | // BF-018A 3 | // JJY Simulator for M5Atom 4 | // 5 | #include 6 | #include 7 | #include 8 | #include // https://github.com/tzapu/WiFiManager 9 | 10 | // initialize Wifimanager 11 | WiFiManager wm; 12 | 13 | // for NTP and time 14 | const int gmt_offset = 3600 * 9; // JST-9 15 | const int daylight = 3600 * 0; // No daylight time 16 | const char* ntp_server = "pool.ntp.org"; 17 | struct tm td; // time of day .. year, month, day, hour, minute, second 18 | struct timeval tv; // time value .. milli-second. micro-second 19 | 20 | // for monitoring 21 | enum ReportItem { kWifi, kNtp, kTco }; 22 | enum ItemStatus { kOn, kOff }; 23 | const int loop_delay_ms = 100; 24 | CRGB led_color = 0x000000; 25 | bool led_enable = true; 26 | 27 | void ReportInit() 28 | { 29 | M5.dis.setBrightness(20); 30 | M5.dis.clear(); 31 | ReportStatus(kWifi, kOff); 32 | ReportStatus(kNtp, kOff); 33 | ReportStatus(kTco, kOff); 34 | } 35 | 36 | void ReportStatus(ReportItem report_item, ItemStatus item_status) 37 | { 38 | CRGB led_color_last = led_color; 39 | switch (report_item) { 40 | case kWifi: 41 | switch (item_status) { 42 | case kOn: led_color.g = 0; break; // red 43 | case kOff: led_color.g = 0x80; break; // red 44 | default: break; 45 | } 46 | break; 47 | case kNtp: 48 | switch (item_status) { 49 | case kOn: led_color.r = 0; break; // green 50 | case kOff: led_color.r = 0x80; break; // green 51 | default: break; 52 | } 53 | break; 54 | case kTco: 55 | switch (item_status) { 56 | case kOn: if (led_enable) led_color.b = 0x80; break; 57 | case kOff: led_color.b = 0; break; 58 | default: break; 59 | } 60 | break; 61 | default: 62 | break; 63 | } 64 | if (led_color != led_color_last) 65 | M5.dis.drawpix(0, led_color); 66 | } 67 | 68 | // PWM for TCO signal 69 | const uint8_t ledc_pin = 22; 70 | const uint8_t ledc_channel = 0; 71 | const double ledc_frequency = 4e4; // 40kHz 72 | const uint8_t ledc_resolution = 10; // 2^10 = 1024 73 | const uint32_t ledc_duty_on = 512; // 50% 74 | const uint32_t ledc_duty_off = 0; // 0 75 | 76 | // Ticker for TCO(Time Code Output) generation 77 | const int ticker_period = 100; // 100ms 78 | const int marker = 0xff; // marker code which TcoValue() returns 79 | int last_usec; // last time in microsecond for statistics of Ticker 80 | Ticker tk; 81 | 82 | // main task of TCO 83 | void TcoGen() 84 | { 85 | if (!getLocalTime(&td)) { 86 | Serial.println("[TCO]Failed to obtain time"); 87 | ReportStatus(kNtp, kOff); 88 | return; 89 | } 90 | ReportStatus(kNtp, kOn); 91 | 92 | gettimeofday(&tv, NULL); 93 | int tv_100ms = tv.tv_usec / 100000; 94 | switch (tv_100ms) { 95 | case 0: Tco000ms(); break; 96 | case 2: Tco200ms(); break; 97 | case 5: Tco500ms(); break; 98 | case 8: Tco800ms(); break; 99 | default: break; 100 | } 101 | 102 | // statistics of tv_usec 103 | int now_usec = tv.tv_usec; 104 | if (now_usec < 100000) 105 | now_usec += 1000000; // 0..99999 --> 100000..199999 106 | int tk_deviation = now_usec - last_usec - 100000; // ticker deviation in micro second 107 | last_usec = tv.tv_usec; 108 | 109 | static int tk_count = 0; 110 | static int tk_max = 0; 111 | static int tk_min = 0; 112 | static double tk_sum = 0.0; 113 | static double tk_sq_sum = 0.0; 114 | ++tk_count; 115 | if (tk_max < tk_deviation) 116 | tk_max = tk_deviation; 117 | if (tk_min > tk_deviation) 118 | tk_min = tk_deviation; 119 | tk_sum += (double)tk_deviation; 120 | tk_sq_sum += (double)tk_deviation * (double)tk_deviation; 121 | 122 | static int tk_distribution[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 123 | if (tk_deviation < -50000) ++tk_distribution[0]; // ~ -50ms 124 | else if (tk_deviation < -5000) ++tk_distribution[1]; // ~ -5ms 125 | else if (tk_deviation < -500) ++tk_distribution[2]; // ~ -0.5ms 126 | else if (tk_deviation < -50) ++tk_distribution[3]; // ~ -0.05ms 127 | else if (tk_deviation < 50) ++tk_distribution[4]; // -0.05 ~ 0.05ms 128 | else if (tk_deviation < 500) ++tk_distribution[5]; // 0.05ms ~ 129 | else if (tk_deviation < 5000) ++tk_distribution[6]; // 0.5ms ~ 130 | else if (tk_deviation < 50000) ++tk_distribution[7]; // 5ms ~ 131 | else ++tk_distribution[8]; // 50ms ~ 132 | 133 | if ((td.tm_sec == 0) && (tv.tv_usec < 100000)) { 134 | for (int i = 0; i < 9; ++i) { 135 | Serial.printf("%d ", tk_distribution[i]); 136 | } 137 | Serial.println(); 138 | 139 | double tk_average = tk_sum / (double)tk_count; 140 | double tk_variance = (tk_sq_sum - tk_sum * tk_sum / (double)tk_count) 141 | / (double)tk_count; 142 | double tk_std_deviation = sqrt(tk_variance); 143 | Serial.printf("n= %d, ave= %.4f sdv= %.4f min= %d max= %d\n", 144 | tk_count, tk_average, tk_std_deviation, tk_min, tk_max); 145 | } 146 | } 147 | 148 | // TCO task at every 0ms 149 | void Tco000ms() 150 | { 151 | TcOn(); 152 | if (td.tm_sec == 0) { 153 | Serial.println(); 154 | Serial.println(&td, "%A %B %d %Y %H:%M:%S "); 155 | } 156 | } 157 | 158 | // TCO task at every 200ms 159 | void Tco200ms() 160 | { 161 | if (TcoValue() == marker) { 162 | TcOff(); 163 | Serial.print(" "); 164 | Serial.print(td.tm_sec); 165 | Serial.print(" "); 166 | } 167 | } 168 | 169 | // TCO task at every 500ms 170 | void Tco500ms() 171 | { 172 | if (TcoValue() != 0) { 173 | TcOff(); 174 | if(TcoValue() != marker) 175 | Serial.print("1"); 176 | } 177 | } 178 | 179 | // TCO task at every 800ms 180 | void Tco800ms() 181 | { 182 | TcOff(); 183 | if (TcoValue() == 0) 184 | Serial.print("0"); 185 | } 186 | 187 | void TcOn() 188 | { 189 | ledcWrite(ledc_channel, ledc_duty_on); 190 | ReportStatus(kTco, kOn); 191 | } 192 | 193 | void TcOff() 194 | { 195 | ledcWrite(ledc_channel, ledc_duty_off); 196 | ReportStatus(kTco, kOff); 197 | } 198 | 199 | // TCO value 200 | // marker, 1:not zero, 0:zero 201 | int TcoValue() 202 | { 203 | int bcd_hour = Int3bcd(td.tm_hour); 204 | int parity_bcd_hour = Parity8(bcd_hour); 205 | 206 | int bcd_minute = Int3bcd(td.tm_min); 207 | int parity_bcd_minute = Parity8(bcd_minute); 208 | 209 | int year = td.tm_year + 1900; 210 | int bcd_year = Int3bcd(year); 211 | 212 | static const int days_of_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 213 | int days = td.tm_mday; 214 | for (int i = 0; i < td.tm_mon; ++i) // td.tm_mon starts from 0 215 | days += days_of_month[i]; 216 | if ((td.tm_mon >= 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) 217 | ++days; 218 | int bcd_days = Int3bcd(days); 219 | 220 | int day_of_week = td.tm_wday; 221 | 222 | int tco; 223 | switch (td.tm_sec) { 224 | case 0: tco = marker; break; 225 | case 1: tco = bcd_minute & 0x40; break; 226 | case 2: tco = bcd_minute & 0x20; break; 227 | case 3: tco = bcd_minute & 0x10; break; 228 | case 4: tco = 0; break; 229 | case 5: tco = bcd_minute & 0x08; break; 230 | case 6: tco = bcd_minute & 0x04; break; 231 | case 7: tco = bcd_minute & 0x02; break; 232 | case 8: tco = bcd_minute & 0x01; break; 233 | case 9: tco = marker; break; 234 | 235 | case 10: tco = 0; break; 236 | case 11: tco = 0; break; 237 | case 12: tco = bcd_hour & 0x20; break; 238 | case 13: tco = bcd_hour & 0x10; break; 239 | case 14: tco = 0; break; 240 | case 15: tco = bcd_hour & 0x08; break; 241 | case 16: tco = bcd_hour & 0x04; break; 242 | case 17: tco = bcd_hour & 0x02; break; 243 | case 18: tco = bcd_hour & 0x01; break; 244 | case 19: tco = marker; break; 245 | 246 | case 20: tco = 0; break; 247 | case 21: tco = 0; break; 248 | case 22: tco = bcd_days & 0x200; break; 249 | case 23: tco = bcd_days & 0x100; break; 250 | case 24: tco = 0; break; 251 | case 25: tco = bcd_days & 0x080; break; 252 | case 26: tco = bcd_days & 0x040; break; 253 | case 27: tco = bcd_days & 0x020; break; 254 | case 28: tco = bcd_days & 0x010; break; 255 | case 29: tco = marker; break; 256 | 257 | case 30: tco = bcd_days & 0x008; break; 258 | case 31: tco = bcd_days & 0x004; break; 259 | case 32: tco = bcd_days & 0x002; break; 260 | case 33: tco = bcd_days & 0x001; break; 261 | case 34: tco = 0; break; 262 | case 35: tco = 0; break; 263 | case 36: tco = parity_bcd_hour; break; 264 | case 37: tco = parity_bcd_minute; break; 265 | case 38: tco = 0; break; 266 | case 39: tco = marker; break; 267 | 268 | case 40: tco = 0; break; 269 | case 41: tco = bcd_year & 0x80; break; 270 | case 42: tco = bcd_year & 0x40; break; 271 | case 43: tco = bcd_year & 0x20; break; 272 | case 44: tco = bcd_year & 0x10; break; 273 | case 45: tco = bcd_year & 0x08; break; 274 | case 46: tco = bcd_year & 0x04; break; 275 | case 47: tco = bcd_year & 0x02; break; 276 | case 48: tco = bcd_year & 0x01; break; 277 | case 49: tco = marker; break; 278 | 279 | case 50: tco = day_of_week & 0x04; break; 280 | case 51: tco = day_of_week & 0x02; break; 281 | case 52: tco = day_of_week & 0x01; break; 282 | case 53: tco = 0; break; 283 | case 54: tco = 0; break; 284 | case 55: tco = 0; break; 285 | case 56: tco = 0; break; 286 | case 57: tco = 0; break; 287 | case 58: tco = 0; break; 288 | case 59: tco = marker; break; 289 | default: tco = 0; break; 290 | } 291 | return tco; 292 | } 293 | 294 | int Int3bcd(int a) 295 | { 296 | return (a % 10) + (a / 10 % 10 * 16) + (a / 100 % 10 * 256); 297 | } 298 | 299 | int Parity8(int a) 300 | { 301 | int pa = a; 302 | for (int i = 1; i < 8; ++i) { 303 | pa += a >> i; 304 | } 305 | return pa % 2; 306 | } 307 | 308 | void setup() 309 | { 310 | M5.begin(true, false, true); 311 | 312 | // for monitoring 313 | ReportInit(); 314 | 315 | // connect Wifi 316 | // wm.resetSettings(); // for testing 317 | if (!wm.autoConnect()) { 318 | Serial.println("Failed to connect"); 319 | ReportStatus(kWifi, kOff); 320 | } 321 | else { 322 | Serial.println("connected...yeey :)"); 323 | ReportStatus(kWifi, kOn); 324 | } 325 | 326 | // start NTP 327 | configTime(gmt_offset, daylight, ntp_server); 328 | while (!getLocalTime(&td)) { 329 | Serial.println("Waiting to obtain time"); 330 | delay(100); 331 | } 332 | Serial.println(&td, "%A %B %d %Y %H:%M:%S"); 333 | ReportStatus(kNtp, kOn); 334 | 335 | // start TCO signal source(40kHz) 336 | ledcSetup(ledc_channel, ledc_frequency, ledc_resolution); 337 | ledcAttachPin(ledc_pin, ledc_channel); 338 | 339 | // wait until middle of 100ms timing. ex. 50ms, 150ms, 250ms,.. 340 | gettimeofday(&tv, NULL); 341 | delayMicroseconds(150000 - tv.tv_usec % 100000); 342 | 343 | // for the first sample of statistics 344 | gettimeofday(&tv, NULL); 345 | last_usec = tv.tv_usec; 346 | 347 | // start Ticker for TCO 348 | tk.attach_ms(ticker_period, TcoGen); 349 | } 350 | 351 | void loop() 352 | { 353 | M5.update(); 354 | 355 | // button: led_pin monitor on/off 356 | if (M5.Btn.wasReleased()) 357 | led_enable = !led_enable; 358 | 359 | // if wifi disconnected, turn on LED-red 360 | if (WiFi.status() == WL_CONNECTED) 361 | ReportStatus(kWifi, kOn); 362 | else 363 | ReportStatus(kWifi, kOff); 364 | 365 | delay(loop_delay_ms); 366 | } 367 | 368 | /* 369 | The MIT License 370 | SPDX short identifier: MIT 371 | 372 | Copyright 2020 BotanicFields, Inc. 373 | 374 | Permission is hereby granted, free of charge, to any person obtaining a copy 375 | of this software and associated documentation files (the "Software"), to deal 376 | in the Software without restriction, including without limitation the rights 377 | to use, copy, modify, merge, publish, distributionribute, sublicense, and/or sell 378 | copies of the Software, and to permit persons to whom the Software is 379 | furnished to do so, subject to the following conditions: 380 | 381 | The above copyright notice and this permission notice shall be included in all 382 | copies or substantial portions of the Software. 383 | 384 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 385 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 386 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 387 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 388 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 389 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 390 | SOFTWARE. 391 | */ 392 | -------------------------------------------------------------------------------- /BF-018ARev2/BF-018ARev2.ino: -------------------------------------------------------------------------------- 1 | // copyright 2022 BotanicFields, Inc. 2 | // BF-018A Rev.2 3 | // JJY Simulator for M5Atom / M5Atom Lite 4 | 5 | #include 6 | #include 7 | #include 8 | #include // https://github.com/tzapu/WiFiManager 9 | #include "BF_Pcf8563.h" 10 | #include "BF_RtcxNtp.h" 11 | 12 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 13 | // for NTP 14 | const char* time_zone = "JST-9"; 15 | const char* ntp_server = "pool.ntp.org"; 16 | bool localtime_valid(false); 17 | bool rtcx_avail(false); 18 | 19 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 20 | // for LED 21 | const int led_on_r(0x80); // PWM 0..0xFF 22 | const int led_on_g(0x80); // PWM 0..0xFF 23 | const int led_on_b(0x80); // PWM 0..0xFF 24 | const unsigned int blink_slow_ms(1000); // blink period 25 | const unsigned int blink_fast_ms( 200); // blink period 26 | 27 | enum led_r_t { 28 | led_r_off, // WL_CONNECTED 29 | led_r_slow, // WL_NO_SSID_AVAIL 30 | led_r_fast, // WL_DISCONNECTED 31 | led_r_on, // WL_IDLE_STATUS, WL_CONNECTION_LOST, etc. 32 | }; 33 | enum led_g_t { 34 | led_g_off, // time valid 35 | led_g_slow, // waiting time valid 36 | led_g_fast, // 37 | led_g_on, // WiFi configuration portal is active 38 | }; 39 | enum led_b_t { 40 | led_b_off, // TCO off 41 | led_b_slow, // 42 | led_b_fast, // 43 | led_b_on, // TCO on 44 | }; 45 | led_r_t led_r(led_r_off); 46 | led_g_t led_g(led_g_off); 47 | led_b_t led_b(led_b_off); 48 | 49 | bool led_enable(true); 50 | void LedShow(); 51 | 52 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 53 | // for WiFi 54 | const int wifi_config_portal_timeout_sec(60); 55 | const unsigned int wifi_retry_interval_ms(60000); 56 | unsigned int wifi_retry_last_ms(0); 57 | const int wifi_retry_max_times(3); 58 | int wifi_retry_times(0); 59 | 60 | wl_status_t wifi_status(WL_NO_SHIELD); 61 | 62 | const char* wl_status_str[] = { 63 | "WL_IDLE_STATUS", // 0 64 | "WL_NO_SSID_AVAIL", // 1 65 | "WL_SCAN_COMPLETED", // 2 66 | "WL_CONNECTED", // 3 67 | "WL_CONNECT_FAILED", // 4 68 | "WL_CONNECTION_LOST", // 5 69 | "WL_DISCONNECTED", // 6 70 | "WL_NO_SHIELD", // 7 <-- 255 71 | "wl_status invalid", // 8 72 | }; 73 | 74 | const char* WlStatus(wl_status_t wl_status) 75 | { 76 | if (wl_status >= 0 && wl_status <= 6) { 77 | return wl_status_str[wl_status]; 78 | } 79 | if (wl_status == 255) { 80 | return wl_status_str[7]; 81 | } 82 | return wl_status_str[8]; 83 | } 84 | 85 | void WifiCheck() 86 | { 87 | wl_status_t wifi_status_new = WiFi.status(); 88 | if (wifi_status != wifi_status_new) { 89 | wifi_status = wifi_status_new; 90 | Serial.printf("[WiFi]%s\n", WlStatus(wifi_status)); 91 | switch (wifi_status) { 92 | case WL_CONNECTED : led_r = led_r_off; break; 93 | case WL_NO_SSID_AVAIL: led_r = led_r_slow; break; 94 | case WL_DISCONNECTED : led_r = led_r_fast; break; 95 | default : led_r = led_r_on; break; // state transition also 96 | } 97 | } 98 | 99 | // retry interval 100 | if (millis() - wifi_retry_last_ms < wifi_retry_interval_ms) { 101 | return; 102 | } 103 | wifi_retry_last_ms = millis(); 104 | 105 | // reboot if wifi connection fails 106 | if (wifi_status == WL_CONNECT_FAILED) { 107 | Serial.print("[WiFi]connect failed: rebooting..\n"); 108 | ESP.restart(); 109 | return; 110 | } 111 | 112 | // let the wifi process do if wifi is not disconnected 113 | if (wifi_status != WL_DISCONNECTED) { 114 | wifi_retry_times = 0; 115 | return; 116 | } 117 | 118 | // reboot if wifi is disconnected for a long time 119 | if (++wifi_retry_times > wifi_retry_max_times) { 120 | Serial.print("[WiFi]disconnect timeout: rebooting..\n"); 121 | ESP.restart(); 122 | return; 123 | } 124 | 125 | // reconnect, and reboot if reconnection fails 126 | Serial.printf("[WiFi]reconnect %d\n", wifi_retry_times); 127 | if (!WiFi.reconnect()) { 128 | Serial.print("[WiFi]reconnect failed: rebooting..\n"); 129 | ESP.restart(); 130 | return; 131 | }; 132 | } 133 | 134 | void WifiConfigModeCallback(WiFiManager *wm) 135 | { 136 | led_g = led_g_on; // green LED indicates configuration portal is active 137 | LedShow(); 138 | } 139 | 140 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 141 | // TCO(Time Code Output) 142 | Ticker tk; 143 | const int ticker_interval_ms(100); // 100ms 144 | const int marker(0xff); // marker code TcoValue() returns 145 | 146 | // PWM for TCO signal 147 | const uint8_t ledc_pin(22); // GPIO22 for TCO 148 | const uint8_t ledc_channel(0); 149 | const uint32_t ledc_frequency(40000); // 40kHz(east), 60kHz(west) 150 | const uint8_t ledc_resolution(8); // 2^8 = 256 151 | const uint32_t ledc_duty_on(128); // 128/256 = 50% 152 | const uint32_t ledc_duty_off(0); // 0 153 | 154 | // real time 155 | struct tm td; // time of day: year, month, day, day of week, hour, minute, second 156 | struct timespec ts; // time spec: second, nano-second 157 | 158 | void TcoInit() 159 | { 160 | // carrier for TCO 161 | uint32_t ledc_freq_get = ledcSetup(ledc_channel, ledc_frequency, ledc_resolution); 162 | Serial.printf("ledc frequency get = %d\n", ledc_freq_get); 163 | ledcAttachPin(ledc_pin, ledc_channel); 164 | 165 | // wait until middle of 100ms timing. ex. 50ms, 150ms, 250ms,.. 166 | clock_gettime(CLOCK_REALTIME, &ts); 167 | delayMicroseconds((150000000 - ts.tv_nsec % 100000000) / 1000); 168 | 169 | // for the first sample of statistics 170 | clock_gettime(CLOCK_REALTIME, &ts); 171 | Serial.printf("ts.tv_nsec = %d\n", ts.tv_nsec); 172 | 173 | // start Ticker for TCO 174 | tk.attach_ms(ticker_interval_ms, TcoGen); 175 | } 176 | 177 | // main task of TCO 178 | void TcoGen() 179 | { 180 | // statistics of ts_nsec 181 | static int tk_count(0); 182 | static int tk_max(0); 183 | static int tk_min(0); 184 | static double tk_sum(0.0); 185 | static double tk_sq_sum(0.0); 186 | static int tk_distribution[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 187 | static int tk_last_nsec(0); 188 | 189 | if (!localtime_valid) { 190 | led_g = led_g_slow; // localtime not valid yet 191 | return; 192 | } 193 | led_g = led_g_off; // localtime valid 194 | 195 | getLocalTime(&td); 196 | clock_gettime(CLOCK_REALTIME, &ts); 197 | int ts_100ms = ts.tv_nsec / 100000000; 198 | switch (ts_100ms) { 199 | case 0: Tco000ms(); break; 200 | case 2: Tco200ms(); break; 201 | case 5: Tco500ms(); break; 202 | case 8: Tco800ms(); break; 203 | default: break; 204 | } 205 | 206 | if (tk_count++ != 0) { 207 | int tk_deviation = ts.tv_nsec - tk_last_nsec; 208 | if (tk_deviation < 0) { 209 | tk_deviation += 1000000000; // 0xx - 9xx ms --> 10xx - 9xx ms 210 | } 211 | tk_deviation -= 100000000; // center 100 ms --> 0 212 | 213 | if (tk_max < tk_deviation) tk_max = tk_deviation; 214 | if (tk_min > tk_deviation) tk_min = tk_deviation; 215 | tk_sum += (double)tk_deviation; 216 | tk_sq_sum += (double)tk_deviation * (double)tk_deviation; 217 | 218 | if (tk_deviation < -50000000) ++tk_distribution[0]; // ~ -50ms 219 | else if (tk_deviation < -5000000) ++tk_distribution[1]; // ~ -5ms 220 | else if (tk_deviation < -500000) ++tk_distribution[2]; // ~ -0.5ms 221 | else if (tk_deviation < -50000) ++tk_distribution[3]; // ~ -0.05ms 222 | else if (tk_deviation < 50000) ++tk_distribution[4]; // -0.05 ~ 0.05ms 223 | else if (tk_deviation < 500000) ++tk_distribution[5]; // 0.05ms ~ 224 | else if (tk_deviation < 5000000) ++tk_distribution[6]; // 0.5ms ~ 225 | else if (tk_deviation < 50000000) ++tk_distribution[7]; // 5ms ~ 226 | else ++tk_distribution[8]; // 50ms ~ 227 | } 228 | tk_last_nsec = ts.tv_nsec; 229 | 230 | if ((td.tm_sec == 0) && (ts.tv_nsec < 100000000)) { 231 | for (int i = 0; i < 9; ++i) { 232 | Serial.printf("%d ", tk_distribution[i]); 233 | } 234 | double tk_average = tk_sum / (double)tk_count; 235 | double tk_variance = (tk_sq_sum - tk_sum * tk_sum / (double)tk_count) / (double)tk_count; 236 | double tk_std_deviation = sqrt(tk_variance); 237 | Serial.printf("\nn= %d, ave= %.4f sdv= %.4f min= %d max= %d\n", tk_count, tk_average, tk_std_deviation, tk_min, tk_max); 238 | } 239 | } 240 | 241 | // TCO task at every 0ms 242 | void Tco000ms() 243 | { 244 | TcOn(); 245 | if (td.tm_sec == 0) { 246 | Serial.print(&td, "\n%A %B %d %Y %H:%M:%S\n"); 247 | } 248 | } 249 | 250 | // TCO task at every 200ms 251 | void Tco200ms() 252 | { 253 | if (TcoValue() == marker) { 254 | TcOff(); 255 | Serial.printf(" %d ", td.tm_sec); 256 | } 257 | } 258 | 259 | // TCO task at every 500ms 260 | void Tco500ms() 261 | { 262 | if (TcoValue() != 0) { 263 | TcOff(); 264 | if(TcoValue() != marker) { 265 | Serial.print("1"); 266 | } 267 | } 268 | } 269 | 270 | // TCO task at every 800ms 271 | void Tco800ms() 272 | { 273 | TcOff(); 274 | if (TcoValue() == 0) { 275 | Serial.print("0"); 276 | } 277 | } 278 | 279 | void TcOn() 280 | { 281 | ledcWrite(ledc_channel, ledc_duty_on); 282 | led_b = led_b_on; 283 | } 284 | 285 | void TcOff() 286 | { 287 | ledcWrite(ledc_channel, ledc_duty_off); 288 | led_b = led_b_off; 289 | } 290 | 291 | // TCO value 292 | // marker, 1:not zero, 0:zero 293 | int TcoValue() 294 | { 295 | const int days_of_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 296 | 297 | int bcd_hour = Int3Bcd(td.tm_hour); 298 | int parity_bcd_hour = Parity8(bcd_hour); 299 | 300 | int bcd_minute = Int3Bcd(td.tm_min); 301 | int parity_bcd_minute = Parity8(bcd_minute); 302 | 303 | int year = td.tm_year + 1900; 304 | int bcd_year = Int3Bcd(year); 305 | 306 | int days = td.tm_mday; 307 | for (int i = 0; i < td.tm_mon; ++i) { // td.tm_mon starts from 0 308 | days += days_of_month[i]; 309 | } 310 | if ((td.tm_mon >= 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) { 311 | ++days; 312 | } 313 | int bcd_days = Int3Bcd(days); 314 | 315 | int day_of_week = td.tm_wday; 316 | 317 | int tco; 318 | switch (td.tm_sec) { 319 | case 0: tco = marker; break; 320 | case 1: tco = bcd_minute & 0x40; break; 321 | case 2: tco = bcd_minute & 0x20; break; 322 | case 3: tco = bcd_minute & 0x10; break; 323 | case 4: tco = 0; break; 324 | case 5: tco = bcd_minute & 0x08; break; 325 | case 6: tco = bcd_minute & 0x04; break; 326 | case 7: tco = bcd_minute & 0x02; break; 327 | case 8: tco = bcd_minute & 0x01; break; 328 | case 9: tco = marker; break; 329 | 330 | case 10: tco = 0; break; 331 | case 11: tco = 0; break; 332 | case 12: tco = bcd_hour & 0x20; break; 333 | case 13: tco = bcd_hour & 0x10; break; 334 | case 14: tco = 0; break; 335 | case 15: tco = bcd_hour & 0x08; break; 336 | case 16: tco = bcd_hour & 0x04; break; 337 | case 17: tco = bcd_hour & 0x02; break; 338 | case 18: tco = bcd_hour & 0x01; break; 339 | case 19: tco = marker; break; 340 | 341 | case 20: tco = 0; break; 342 | case 21: tco = 0; break; 343 | case 22: tco = bcd_days & 0x200; break; 344 | case 23: tco = bcd_days & 0x100; break; 345 | case 24: tco = 0; break; 346 | case 25: tco = bcd_days & 0x080; break; 347 | case 26: tco = bcd_days & 0x040; break; 348 | case 27: tco = bcd_days & 0x020; break; 349 | case 28: tco = bcd_days & 0x010; break; 350 | case 29: tco = marker; break; 351 | 352 | case 30: tco = bcd_days & 0x008; break; 353 | case 31: tco = bcd_days & 0x004; break; 354 | case 32: tco = bcd_days & 0x002; break; 355 | case 33: tco = bcd_days & 0x001; break; 356 | case 34: tco = 0; break; 357 | case 35: tco = 0; break; 358 | case 36: tco = parity_bcd_hour; break; 359 | case 37: tco = parity_bcd_minute; break; 360 | case 38: tco = 0; break; 361 | case 39: tco = marker; break; 362 | 363 | case 40: tco = 0; break; 364 | case 41: tco = bcd_year & 0x80; break; 365 | case 42: tco = bcd_year & 0x40; break; 366 | case 43: tco = bcd_year & 0x20; break; 367 | case 44: tco = bcd_year & 0x10; break; 368 | case 45: tco = bcd_year & 0x08; break; 369 | case 46: tco = bcd_year & 0x04; break; 370 | case 47: tco = bcd_year & 0x02; break; 371 | case 48: tco = bcd_year & 0x01; break; 372 | case 49: tco = marker; break; 373 | 374 | case 50: tco = day_of_week & 0x04; break; 375 | case 51: tco = day_of_week & 0x02; break; 376 | case 52: tco = day_of_week & 0x01; break; 377 | case 53: tco = 0; break; 378 | case 54: tco = 0; break; 379 | case 55: tco = 0; break; 380 | case 56: tco = 0; break; 381 | case 57: tco = 0; break; 382 | case 58: tco = 0; break; 383 | case 59: tco = marker; break; 384 | default: tco = 0; break; 385 | } 386 | return tco; 387 | } 388 | 389 | int Int3Bcd(int a) 390 | { 391 | return (a % 10) + (a / 10 % 10 * 16) + (a / 100 % 10 * 256); 392 | } 393 | 394 | int Parity8(int a) 395 | { 396 | int pa = a; 397 | for (int i = 1; i < 8; ++i) { 398 | pa += a >> i; 399 | } 400 | return pa % 2; 401 | } 402 | 403 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 404 | // LED 405 | void LedShow() 406 | { 407 | if (!led_enable && led_r == led_r_off && led_g == led_g_off) { 408 | M5.dis.drawpix(0, 0); 409 | return; 410 | } 411 | 412 | // LED on 413 | CRGB led(0); 414 | switch (led_r) { 415 | case led_r_on : led.r = led_on_r; break; 416 | case led_r_fast: led.r = LedBlink(blink_fast_ms) ? led_on_r : 0; break; 417 | case led_r_slow: led.r = LedBlink(blink_slow_ms) ? led_on_r : 0; break; 418 | default: break; // led.r = 0 419 | } 420 | switch (led_g) { 421 | case led_g_on : led.g = led_on_g; break; 422 | case led_g_fast: led.g = LedBlink(blink_fast_ms) ? led_on_g : 0; break; 423 | case led_g_slow: led.g = LedBlink(blink_slow_ms) ? led_on_g : 0; break; 424 | default: break; // led.g = 0 425 | } 426 | switch (led_b) { 427 | case led_b_on : led.b = led_on_b; break; 428 | case led_b_fast: led.b = LedBlink(blink_fast_ms) ? led_on_b : 0; break; 429 | case led_b_slow: led.b = LedBlink(blink_slow_ms) ? led_on_b : 0; break; 430 | default: break; // led.b = 0 431 | } 432 | M5.dis.drawpix(0, led); 433 | } 434 | 435 | bool LedBlink(unsigned int period_ms) 436 | { 437 | return millis() / period_ms % 2 != 0; 438 | } 439 | 440 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 441 | // main 442 | const unsigned int loop_period_ms(100); 443 | unsigned int loop_last_ms; 444 | 445 | void setup() 446 | { 447 | // To avoid Wi-Fi issues, force GPIO0 to 0 while CH552 outputs 5V with its internal pullup. 448 | // https://twitter.com/wakwak_koba/status/1553162622479974400 449 | pinMode(0, OUTPUT); 450 | digitalWrite(0, LOW); 451 | 452 | const bool serial_enable(true); 453 | const bool i2c_enable(true); // Wire: SDA=25, SCL=21, Frequency=100kHz 454 | const bool display_enable(true); 455 | M5.begin(serial_enable, !i2c_enable, display_enable); // Wire is not used 456 | 457 | // assign Wire1 to GROVE port of M5Atom to connect RTC 458 | const int scl1(32); // GPIO32 459 | const int sda1(26); // GPIO26 460 | const uint32_t freq1(100000); // 100kHz 461 | Wire1.begin(sda1, scl1, freq1); 462 | if (rtcx.Begin(Wire1) == 0) { 463 | rtcx_avail = true; 464 | if (SetTimeFromRtcx(time_zone)) { 465 | localtime_valid = true; 466 | } 467 | } 468 | if (!localtime_valid) { 469 | Serial.print("RTC not valid: set the localtime temporarily\n"); 470 | td.tm_year = 117; // 2017 > 2016, getLocalTime() returns true 471 | td.tm_mon = 0; // January 472 | td.tm_mday = 1; 473 | td.tm_hour = 0; 474 | td.tm_min = 0; 475 | td.tm_sec = 0; 476 | struct timeval tv = { mktime(&td), 0 }; 477 | settimeofday(&tv, NULL); 478 | } 479 | getLocalTime(&td); 480 | Serial.print(&td, "localtime: %A, %B %d %Y %H:%M:%S\n"); 481 | // print sample: must be < 64 482 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 483 | //localtime: Wednesday, September 11 2021 11:10:46 484 | 485 | // WiFi start 486 | WiFiManager wm; // blocking mode only 487 | 488 | // erase SSID/Key to force rewrite 489 | const int button_pin(39); // the button on M5Atom 490 | if (digitalRead(button_pin) == LOW) { 491 | wm.resetSettings(); 492 | } 493 | 494 | // WiFi connect 495 | wm.setConfigPortalTimeout(wifi_config_portal_timeout_sec); 496 | wm.setAPCallback(WifiConfigModeCallback); 497 | wm.autoConnect(); 498 | WiFi.setSleep(false); // https://macsbug.wordpress.com/2021/05/02/buttona-on-m5stack-does-not-work-properly/ 499 | wifi_retry_last_ms = millis() - wifi_retry_interval_ms; 500 | 501 | // NTP start 502 | NtpBegin(time_zone, ntp_server); 503 | 504 | // TCO start 505 | TcoInit(); 506 | 507 | // clear button of erase SSID/Key 508 | M5.update(); 509 | 510 | // loop control 511 | loop_last_ms = millis(); 512 | } 513 | 514 | void loop() 515 | { 516 | M5.update(); 517 | WifiCheck(); 518 | if (RtcxUpdate(rtcx_avail)) { 519 | localtime_valid = true; // SNTP sync completed 520 | }; 521 | LedShow(); 522 | 523 | // button: TCO monitor on/off 524 | if (M5.Btn.wasReleased()) { 525 | led_enable = !led_enable; 526 | } 527 | 528 | // loop control 529 | unsigned int delay_ms(0); 530 | unsigned int elapse_ms = millis() - loop_last_ms; 531 | if (elapse_ms < loop_period_ms) { 532 | delay_ms = loop_period_ms - elapse_ms; 533 | } 534 | delay(delay_ms); 535 | loop_last_ms = millis(); 536 | // Serial.printf("loop elapse = %dms\n", elapse_ms); // for monitoring elapsed time 537 | } 538 | -------------------------------------------------------------------------------- /BF-018ARev2/BF_Pcf8563.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | 4 | #include 5 | #include "BF_Pcf8563.h" 6 | 7 | int Pcf8563::Begin(TwoWire &wire) 8 | { 9 | m_wire = &wire; 10 | 11 | // clear registers 12 | external_clock_test_mode = false; 13 | source_clock_stoped = false; 14 | power_on_reset_override = true; 15 | if (WriteControl() != 0) return 1; 16 | 17 | clock_out_active =false; 18 | clock_out = fco_32768Hz; 19 | if (WriteClockOut() != 0) return 1; 20 | 21 | alarm_minute_enable = false; alarm_minute = 0; 22 | alarm_hour_enable = false; alarm_hour = 0; 23 | alarm_day_enable = false; alarm_day = 0; 24 | alarm_weekday_enable = false; alarm_weekday = 0; 25 | if (WriteAlarm() != 0) return 1; 26 | 27 | timer_enable = false; 28 | timer_source = fts_4096Hz; 29 | timer = 0; 30 | if (WriteTimer() != 0) return 1; 31 | 32 | timer_interrupt_pulse_mode = false; 33 | alarm_flag_active = false; 34 | timer_flag_active = false; 35 | alarm_interrupt_enable = false; 36 | timer_interrupt_enable = false; 37 | if (WriteInterrupt() != 0) return 1; 38 | 39 | return 0; 40 | } 41 | 42 | int Pcf8563::ReadTime(struct tm *tm_now) 43 | { 44 | if (ReadReg(0x02, 7) != 7) return 1; // ReadReg error 45 | if (m_reg[0x02] & 0x80) return 2; // invalid time 46 | tm_now->tm_sec = Bcd2Int(m_reg[0x02] & 0x7f); 47 | tm_now->tm_min = Bcd2Int(m_reg[0x03] & 0x7f); 48 | tm_now->tm_hour = Bcd2Int(m_reg[0x04] & 0x3f); 49 | tm_now->tm_mday = Bcd2Int(m_reg[0x05] & 0x3f); 50 | tm_now->tm_wday = Bcd2Int(m_reg[0x06] & 0x07); // 0:Sun, 1:Mon, .. 6:Sat 51 | tm_now->tm_mon = Bcd2Int(m_reg[0x07] & 0x1f) - 1; // tm month: 0..11 52 | tm_now->tm_year = Bcd2Int(m_reg[0x08] & 0xff); 53 | if ((m_reg[0x07] & 0x80) != 0) 54 | tm_now->tm_year += 100; // century bit 55 | return 0; 56 | } 57 | 58 | int Pcf8563::WriteTime(struct tm *tm_now) 59 | { 60 | m_reg[0x02] = Int2Bcd(tm_now->tm_sec); 61 | m_reg[0x03] = Int2Bcd(tm_now->tm_min); 62 | m_reg[0x04] = Int2Bcd(tm_now->tm_hour); 63 | m_reg[0x05] = Int2Bcd(tm_now->tm_mday); 64 | m_reg[0x06] = Int2Bcd(tm_now->tm_wday); // 0:Sun, 1:Mon, .. 6:Sat 65 | m_reg[0x07] = Int2Bcd(tm_now->tm_mon + 1); // rtc month: 1..12 66 | m_reg[0x08] = Int2Bcd(tm_now->tm_year % 100); 67 | if (tm_now->tm_year >= 100) 68 | m_reg[0x07] |= 0x80; // century bit 69 | return WriteReg(0x02, 7); 70 | } 71 | 72 | // to enable alarm, give valid value for arguments of minute, hour, day, weekday 73 | int Pcf8563::SetAlarm(int minute, int hour, int day, int weekday) 74 | { 75 | alarm_minute_enable = false; 76 | alarm_hour_enable = false; 77 | alarm_day_enable = false; 78 | alarm_weekday_enable = false; 79 | if (minute >= 0 && minute <= 59) { alarm_minute_enable = true; alarm_minute = minute; } 80 | if (hour >= 0 && hour <= 23) { alarm_hour_enable = true; alarm_hour = hour; } 81 | if (day >= 1 && day <= 31) { alarm_day_enable = true; alarm_day = day; } 82 | if (weekday >= 0 && weekday <= 6) { alarm_weekday_enable = true; alarm_weekday = weekday; } 83 | return WriteAlarm(); 84 | } 85 | 86 | int Pcf8563::DisableAlarm() 87 | { 88 | return SetAlarm(); 89 | } 90 | 91 | // interrupt_en = true: enable, false: disable 92 | int Pcf8563::EnableAlarmInterrupt(bool enable_interrupt, bool flag_keep) 93 | { 94 | if (ReadInterrupt() != 0) return 1; 95 | alarm_flag_active = flag_keep; 96 | alarm_interrupt_enable = enable_interrupt; 97 | return WriteInterrupt(); 98 | } 99 | 100 | int Pcf8563::DisableAlarmInterrupt() 101 | { 102 | return EnableAlarmInterrupt(false); // disable and clear 103 | } 104 | 105 | // Set timer_source and timer to the argument timer_sec as close as possible 106 | // returns actual timer value realized by timer_source and timer, 107 | // return 0.0: timer_sec is out of range, the timer was not set 108 | // return <>0.0: The timer is enabled. 109 | double Pcf8563::SetTimer(double timer_sec) 110 | { 111 | if (timer_sec < 1.0 / 4096.0) return 0.0; // minimum 0.00024414s 112 | if (timer_sec > 255.0 * 60.0) return 0.0; // maximum 15,300s 113 | 114 | if (timer_sec >= 240.0) { // cross over at 240s not 255s 115 | timer_source = fts_1_60th_Hz; 116 | timer = timer_sec / 60.0 + 0.5; // +0.5 for rounding 117 | } 118 | else if (timer_sec > 255.0 / 64.0) { 119 | timer_source = fts_1Hz; 120 | timer = timer_sec + 0.5; // +0.5 for rounding 121 | } 122 | else if (timer_sec > 255.0 / 4096.0) { 123 | timer_source = fts_64Hz; 124 | timer = timer_sec * 64.0 + 0.5; // +0.5 for rounding 125 | } 126 | else { 127 | timer_source = fts_4096Hz; 128 | timer = timer_sec * 4096.0 + 0.5; // +0.5 for rounding 129 | } 130 | timer_enable = true; 131 | if (WriteTimer() != 0) return 0.0; 132 | 133 | switch (timer_source) { 134 | case 0: return timer / 4096.0; break; 135 | case 1: return timer / 64.0; break; 136 | case 2: return timer / 1.0; break; 137 | case 3: return timer * 60.0; break; 138 | default: return 0.0; break; 139 | } 140 | } 141 | 142 | // timer_en = true: enable, false: disable 143 | int Pcf8563::EnableTimer(bool enable_timer) 144 | { 145 | if (ReadReg(0x0e, 1) != 1) return 1; 146 | switch (m_reg[0x0e] & 0x03) { 147 | case 0: timer_source = fts_4096Hz; break; 148 | case 1: timer_source = fts_64Hz; break; 149 | case 2: timer_source = fts_1Hz; break; 150 | case 3: timer_source = fts_1_60th_Hz; break; 151 | default: break; 152 | } 153 | timer_enable = enable_timer; 154 | m_reg[0x0e] = timer_source; 155 | if (timer_enable) 156 | m_reg[0x0e] |= 0x80; 157 | return WriteReg(0x0e, 1); 158 | } 159 | 160 | int Pcf8563::DisableTimer() 161 | { 162 | return EnableTimer(false); 163 | } 164 | 165 | // interrupt_enable = true: enable, false: disable 166 | int Pcf8563::EnableTimerInterrupt(bool enable_interrupt, bool pulse_mode, bool keep_flag) 167 | { 168 | if (ReadInterrupt() != 0) return 1; 169 | timer_interrupt_pulse_mode = pulse_mode; 170 | timer_flag_active = keep_flag; 171 | timer_interrupt_enable = enable_interrupt; 172 | if (WriteInterrupt() != 0) return 1; 173 | return 0; 174 | } 175 | 176 | int Pcf8563::DisableTimerInterrupt() 177 | { 178 | return EnableTimerInterrupt(false); // disable and clear 179 | } 180 | 181 | int Pcf8563::GetInterrupt() 182 | { 183 | if (ReadInterrupt() != 0) return 4; 184 | int flag_got(0); 185 | if (alarm_flag_active) { flag_got |= 0x02; alarm_flag_active = false; } 186 | if (timer_flag_active) { flag_got |= 0x01; timer_flag_active = false; } 187 | if (flag_got != 0) 188 | if (WriteInterrupt() != 0) return 4; 189 | return flag_got; 190 | } 191 | 192 | int Pcf8563::ClockOutForTrimmer(bool enable_clko) // clock out 1Hz 193 | { 194 | if (enable_clko) { 195 | // CLKO(clock out) to adjust trimmer 196 | clock_out = fco_1Hz; 197 | clock_out_active = true; 198 | if (WriteClockOut() != 0) return 1; 199 | } 200 | else { 201 | clock_out_active = false; 202 | if (WriteClockOut() != 0) return 1; 203 | } 204 | return 0; 205 | } 206 | 207 | Pcf8563::Pcf8563() 208 | { 209 | m_reg = new uint8_t[m_reg_size]; 210 | for (int i = 0; i < m_reg_size; ++i) 211 | m_reg[i] = 0; 212 | } 213 | 214 | Pcf8563::~Pcf8563() 215 | { 216 | delete [] m_reg; 217 | } 218 | 219 | int Pcf8563::ReadControl() 220 | { 221 | if (ReadReg(0x00, 1) != 1) return 1; 222 | external_clock_test_mode = m_reg[0x00] & 0x80; 223 | source_clock_stoped = m_reg[0x00] & 0x20; 224 | power_on_reset_override = m_reg[0x00] & 0x08; 225 | return 0; 226 | } 227 | 228 | int Pcf8563::WriteControl() 229 | { 230 | m_reg[0x00] = 0x00; 231 | if (external_clock_test_mode) m_reg[0x00] |= 0x80; 232 | if (source_clock_stoped ) m_reg[0x00] |= 0x20; 233 | if (power_on_reset_override ) m_reg[0x00] |= 0x08; 234 | return WriteReg(0x00, 1); 235 | } 236 | 237 | int Pcf8563::ReadInterrupt() 238 | { 239 | if (ReadReg(0x01, 1) != 1) return 1; 240 | timer_interrupt_pulse_mode = m_reg[0x01] & 0x10; 241 | alarm_flag_active = m_reg[0x01] & 0x08; 242 | timer_flag_active = m_reg[0x01] & 0x04; 243 | alarm_interrupt_enable = m_reg[0x01] & 0x02; 244 | timer_interrupt_enable = m_reg[0x01] & 0x01; 245 | return 0; 246 | } 247 | 248 | int Pcf8563::WriteInterrupt() 249 | { 250 | m_reg[0x01] = 0x00; 251 | if (timer_interrupt_pulse_mode) m_reg[0x01] |= 0x10; 252 | if (alarm_flag_active ) m_reg[0x01] |= 0x08; 253 | if (timer_flag_active ) m_reg[0x01] |= 0x04; 254 | if (alarm_interrupt_enable ) m_reg[0x01] |= 0x02; 255 | if (timer_interrupt_enable ) m_reg[0x01] |= 0x01; 256 | return WriteReg(0x01, 1); 257 | } 258 | 259 | int Pcf8563::ReadAlarm() 260 | { 261 | if (ReadReg(0x09, 4) != 4) return 1; 262 | alarm_minute_enable = (m_reg[0x09] & 0x80) == 0; 263 | alarm_hour_enable = (m_reg[0x0a] & 0x80) == 0; 264 | alarm_day_enable = (m_reg[0x0b] & 0x80) == 0; 265 | alarm_weekday_enable = (m_reg[0x0c] & 0x80) == 0; 266 | alarm_minute = Bcd2Int(m_reg[0x09] & 0x7f); 267 | alarm_hour = Bcd2Int(m_reg[0x0a] & 0x3f); 268 | alarm_day = Bcd2Int(m_reg[0x0b] & 0x3f); 269 | alarm_weekday = Bcd2Int(m_reg[0x0c] & 0x07); 270 | return 0; 271 | } 272 | 273 | int Pcf8563::WriteAlarm() 274 | { 275 | m_reg[0x09] = Int2Bcd(alarm_minute ); 276 | m_reg[0x0a] = Int2Bcd(alarm_hour ); 277 | m_reg[0x0b] = Int2Bcd(alarm_day ); 278 | m_reg[0x0c] = Int2Bcd(alarm_weekday); 279 | if (!alarm_minute_enable ) m_reg[0x09] |= 0x80; 280 | if (!alarm_hour_enable ) m_reg[0x0a] |= 0x80; 281 | if (!alarm_day_enable ) m_reg[0x0b] |= 0x80; 282 | if (!alarm_weekday_enable) m_reg[0x0c] |= 0x80; 283 | return WriteReg(0x09, 4); 284 | } 285 | 286 | int Pcf8563::ReadClockOut() 287 | { 288 | if (ReadReg(0x0d, 1) != 1) return 1; 289 | clock_out_active = m_reg[0x0d] & 0x80; 290 | switch (m_reg[0x0d] & 0x03) { 291 | case 0: clock_out = fco_32768Hz; break; 292 | case 1: clock_out = fco_1024Hz; break; 293 | case 2: clock_out = fco_32Hz; break; 294 | case 3: clock_out = fco_1Hz; break; 295 | default: break; 296 | } 297 | return 0; 298 | } 299 | 300 | int Pcf8563::WriteClockOut() 301 | { 302 | m_reg[0x0d] = clock_out; 303 | if (clock_out_active) 304 | m_reg[0x0d] |= 0x80; 305 | return WriteReg(0x0d, 1); 306 | } 307 | 308 | int Pcf8563::ReadTimer() 309 | { 310 | if (ReadReg(0x0e, 2) != 2) return 1; 311 | timer_enable = m_reg[0x0e] & 0x80; 312 | switch (m_reg[0x0e] & 0x03) { 313 | case 0: timer_source = fts_4096Hz; break; 314 | case 1: timer_source = fts_64Hz; break; 315 | case 2: timer_source = fts_1Hz; break; 316 | case 3: timer_source = fts_1_60th_Hz; break; 317 | default: break; 318 | } 319 | timer = m_reg[0x0f]; 320 | return 0; 321 | } 322 | 323 | int Pcf8563::WriteTimer() 324 | { 325 | m_reg[0x0e] = timer_source; 326 | if (timer_enable) 327 | m_reg[0x0e] |= 0x80; 328 | m_reg[0x0f] = timer; 329 | return WriteReg(0x0e, 2); 330 | } 331 | 332 | int Pcf8563::Int2Bcd(int int_num) 333 | { 334 | return int_num / 10 * 16 + int_num % 10; 335 | } 336 | 337 | int Pcf8563::Bcd2Int(int bcd_num) 338 | { 339 | return bcd_num / 16 * 10 + bcd_num % 16; 340 | } 341 | 342 | size_t Pcf8563::ReadReg(int reg_start, size_t read_length) 343 | { 344 | const bool send_stop(true); 345 | 346 | m_wire->beginTransmission(m_i2c_address); 347 | m_wire->write(reg_start); 348 | if (m_wire->endTransmission(!send_stop) != 0) { 349 | Serial.print("[PCF8563]ERROR ReadReg write\n"); 350 | return 0; // command error 351 | } 352 | m_wire->requestFrom(m_i2c_address, read_length); 353 | size_t i = 0; 354 | while (m_wire->available()) 355 | m_reg[reg_start + i++] = m_wire->read(); 356 | if (i != read_length) 357 | Serial.print("[PCF8563]ERROR ReadReg read\n"); 358 | return i; 359 | } 360 | 361 | int Pcf8563::WriteReg(int reg_start, size_t write_length) 362 | { 363 | m_wire->beginTransmission(m_i2c_address); 364 | m_wire->write(reg_start); 365 | for (int i = 0; i < write_length; ++i) 366 | m_wire->write(m_reg[reg_start + i]); 367 | int return_code = m_wire->endTransmission(); 368 | if (return_code != 0) 369 | Serial.print("[PCF8563]ERROR WriteReg\n"); 370 | return return_code; 371 | } 372 | 373 | // the instance "rtc external" 374 | Pcf8563 rtcx; 375 | -------------------------------------------------------------------------------- /BF-018ARev2/BF_Pcf8563.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | 4 | #pragma once 5 | 6 | #include 7 | #include // for struct tm 8 | 9 | class Pcf8563 { 10 | public: 11 | int Begin(TwoWire &wire); 12 | int ReadTime(struct tm *tm_now); 13 | int WriteTime(struct tm *tm_now); 14 | 15 | int SetAlarm(int miniute = -1, int hour = -1, int day = -1, int weekday = -1); 16 | int DisableAlarm(); 17 | int EnableAlarmInterrupt(bool enable_interrupt = true, bool keep_flag = false); 18 | int DisableAlarmInterrupt(); 19 | 20 | double SetTimer(double timer_sec); 21 | int EnableTimer(bool enable_timer = true); 22 | int DisableTimer(); 23 | int EnableTimerInterrupt(bool enable_interrupt = true, bool pulse_mode = false, bool keep_flag = false); 24 | int DisableTimerInterrupt(); 25 | 26 | int GetInterrupt(); // check interrupt and clear 27 | 28 | int ClockOutForTrimmer(bool enable_clko = true); // clock out 1Hz 29 | 30 | bool external_clock_test_mode; // 00h Control_status_1 bit 7 TEST1 31 | bool source_clock_stoped; // 00h Control_status_1 bit 5 STOP 32 | bool power_on_reset_override; // 00h Control_status_1 bit 3 TESTC 33 | 34 | bool timer_interrupt_pulse_mode; // 01h Control_status_2 bit 4 TI_TP 35 | bool alarm_flag_active; // 01h Control_status_2 bit 3 AF 36 | bool timer_flag_active; // 01h Control_status_2 bit 2 TF 37 | bool alarm_interrupt_enable; // 01h Control_status_2 bit 1 AIE 38 | bool timer_interrupt_enable; // 01h Control_status_2 bit 0 TIE 39 | 40 | bool alarm_minute_enable; // 09h Minute_alarm bit 7 !AE_M 41 | int alarm_minute; // 09h Minute_alarm 0..59 42 | bool alarm_hour_enable; // 0Ah Hour_alarm bit 7 !AE_H 43 | int alarm_hour; // 0Ah Hour_alarm 0..23 44 | bool alarm_day_enable; // 0Bh Day_alarm bit 7 !AE_D 45 | int alarm_day; // 0Bh Day_alarm 1..31 46 | bool alarm_weekday_enable; // 0Ch Weekday_alarm bit 7 !AE_W 47 | int alarm_weekday; // 0Ch Weekday_alarm 0..6 48 | 49 | enum FreqClockOut_t { // 0Dh Clock_out_control FD 0..3 50 | fco_32768Hz, 51 | fco_1024Hz, 52 | fco_32Hz, 53 | fco_1Hz, 54 | }; 55 | 56 | bool clock_out_active; // 0Dh Clock_out_control bit 7 FE 57 | FreqClockOut_t clock_out; // 0Dh Clock_out_control 0..3 58 | 59 | enum FreqTimerSource_t { // 0Eh Timer_control TD 0..3 60 | fts_4096Hz, 61 | fts_64Hz, 62 | fts_1Hz, 63 | fts_1_60th_Hz, // (1/60)Hz 64 | }; 65 | 66 | bool timer_enable; // 0Eh Timer_control bit 7 TE 67 | FreqTimerSource_t timer_source; // 0Eh Timer_control 0..3 68 | int timer; // 0Fh Timer 0..255 69 | 70 | Pcf8563(); 71 | ~Pcf8563(); 72 | 73 | int ReadControl(); 74 | int WriteControl(); 75 | int ReadInterrupt(); 76 | int WriteInterrupt(); 77 | int ReadAlarm(); 78 | int WriteAlarm(); 79 | int ReadClockOut(); 80 | int WriteClockOut(); 81 | int ReadTimer(); 82 | int WriteTimer(); 83 | 84 | private: 85 | const int m_i2c_address = 0x51; 86 | const int m_reg_size = 0x10; 87 | 88 | TwoWire *m_wire; 89 | uint8_t *m_reg; 90 | 91 | size_t ReadReg(int reg_start, size_t read_length); 92 | int WriteReg(int reg_start, size_t write_length); 93 | int Int2Bcd(int int_num); 94 | int Bcd2Int(int bcd_num); 95 | }; 96 | 97 | // an instance "rtc external" 98 | extern Pcf8563 rtcx; 99 | -------------------------------------------------------------------------------- /BF-018ARev2/BF_RtcxNtp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | 4 | #include 5 | #include "BF_Pcf8563.h" 6 | #include "BF_RtcxNtp.h" 7 | 8 | bool sntp_sync_status_complete(false); 9 | 10 | void NtpBegin(const char* time_zone, const char* ntp_server) 11 | { 12 | configTzTime(time_zone, ntp_server); 13 | Serial.printf("NtpBegin: config TZ time = %s\n", time_zone); 14 | Serial.printf("NtpBegin: SNTP sync mode = %d (0:IMMED 1:SMOOTH)\n", sntp_get_sync_mode()); 15 | Serial.printf("NtpBegin: SNTP sync interval = %dms\n", sntp_get_sync_interval()); 16 | sntp_sync_status_complete = false; 17 | sntp_set_time_sync_notification_cb(SntpTimeSyncNotificationCallback); 18 | } 19 | 20 | void SntpTimeSyncNotificationCallback(struct timeval *tv) 21 | { 22 | sntp_sync_status_t sntp_sync_status = sntp_get_sync_status(); 23 | PrintSntpStatus("SNTP callback:", sntp_sync_status); 24 | if (sntp_sync_status == SNTP_SYNC_STATUS_COMPLETED) { 25 | sntp_sync_status_complete = true; 26 | } 27 | } 28 | 29 | bool RtcxUpdate(bool rtcx_avail) 30 | { 31 | if (sntp_sync_status_complete) { 32 | sntp_sync_status_complete = false; 33 | 34 | struct tm tm_sync; 35 | getLocalTime(&tm_sync); 36 | Serial.print(&tm_sync, "SNTP sync: %A, %B %d %Y %H:%M:%S\n"); 37 | // print sample: must be < 64 38 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 39 | //SNTP sync: Wednesday, September 11 2021 11:10:46 40 | 41 | if (rtcx_avail) { 42 | if (rtcx.WriteTime(&tm_sync) == 0) { 43 | Serial.print("RTCx updated\n"); 44 | } 45 | else { 46 | Serial.print("RTCx update failed\n"); 47 | } 48 | } 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status) 55 | { 56 | static const char* sntp_sync_status_str[] = { 57 | "SNTP_SYNC_STATUS_RESET ", // 0 58 | "SNTP_SYNC_STATUS_COMPLETED ", // 1 59 | "SNTP_SYNC_STATUS_IN_PROGRESS ", // 2 60 | "sntp_sync_status invalid ", // 3 61 | }; 62 | int sntp_sync_status_index = 3; 63 | if (sntp_sync_status >= 0 && sntp_sync_status <= 2) { 64 | sntp_sync_status_index = sntp_sync_status; 65 | } 66 | Serial.printf("%s status = %d %s\n", header, sntp_sync_status, sntp_sync_status_str[sntp_sync_status_index]); 67 | } 68 | 69 | bool SetTimeFromRtcx(const char* time_zone) 70 | { 71 | bool rtcx_valid(false); 72 | struct tm tm_init; 73 | 74 | setenv("TZ", time_zone, 1); 75 | tzset(); // assign timezone with setenv for mktime() 76 | 77 | if (rtcx.ReadTime(&tm_init) == 0) { 78 | rtcx_valid = true; 79 | struct timeval tv = { mktime(&tm_init), 0 }; 80 | settimeofday(&tv, NULL); 81 | Serial.print("RTCx valid, the localtime was set\n"); 82 | } else { 83 | Serial.print("RTCx not valid\n"); 84 | } 85 | return rtcx_valid; 86 | } 87 | -------------------------------------------------------------------------------- /BF-018ARev2/BF_RtcxNtp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | 4 | #pragma once 5 | 6 | #include // for struct timeval 7 | #include // for sntp_sync_status, https://github.com/espressif/arduino-esp32 >= 1.0.6 8 | 9 | // examples 10 | // const char* time_zone = "JST-9"; 11 | // const char* ntp_server = "pool.ntp.org"; 12 | void NtpBegin(const char* time_zone, const char* ntp_server); 13 | void SntpTimeSyncNotificationCallback(struct timeval *tv); 14 | bool RtcxUpdate(bool rtcx_avail = true); 15 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status); 16 | bool SetTimeFromRtcx(const char* time_zone); 17 | -------------------------------------------------------------------------------- /BF-018ARev3/BF-018ARev3.ino: -------------------------------------------------------------------------------- 1 | // copyright 2024 BotanicFields, Inc. 2 | // BF-018A Rev.3 3 | // JJY Simulator for ATOM Lite / ATOM Matrix / ATOMS3 Lite 4 | // 5 | #include 6 | #include // https://github.com/FastLED/FastLED 7 | #include 8 | #include 9 | #include // https://github.com/tzapu/WiFiManager 10 | #include "BF_Pcf8563.h" 11 | #include "BF_RtcxNtp.h" 12 | 13 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 14 | // for TCO(Time Code Output) 15 | const int jjy_frequency(40000); // 40kHz(east), 60kHz(west) 16 | struct tm td; // time of day: year, month, day, day of week, hour, minute, second 17 | struct timespec ts; // time spec: second, nano-second 18 | 19 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 20 | // for NTP 21 | const char* time_zone = "JST-9"; 22 | const char* ntp_server = "pool.ntp.org"; 23 | bool rtcx_available(false); 24 | bool localtime_valid(false); 25 | 26 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 27 | // for WiFi 28 | const int wifi_config_portal_timeout_sec(60); 29 | const unsigned int wifi_retry_interval_ms(60000); 30 | unsigned int wifi_retry_last_ms(0); 31 | const int wifi_retry_max_times(3); 32 | int wifi_retry_times(0); 33 | 34 | wl_status_t wifi_status(WL_NO_SHIELD); 35 | const char* wl_status_str[] = { 36 | "WL_IDLE_STATUS", // 0 37 | "WL_NO_SSID_AVAIL", // 1 38 | "WL_SCAN_COMPLETED", // 2 39 | "WL_CONNECTED", // 3 40 | "WL_CONNECT_FAILED", // 4 41 | "WL_CONNECTION_LOST", // 5 42 | "WL_DISCONNECTED", // 6 43 | "WL_NO_SHIELD", // 7 <-- 255 44 | "wl_status invalid", // 8 45 | }; 46 | 47 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 48 | // for FastLED 49 | const int led_pin_atom(27); // GPIO27 for ATOM Lite/Matrix 50 | const int led_pin_atoms3lite(35); // GPIO35 for ATOMS3 Lite 51 | const unsigned char led_num(25); // ATOM Matrix: 25, ATOM lite/ATOMS3 Lite: 1 --> 25 52 | CRGB leds[led_num]; 53 | 54 | const int led_on_r(0x80); // 0..0xFF 55 | const int led_on_g(0x80); // 0..0xFF 56 | const int led_on_b(0x80); // 0..0xFF 57 | const int led_brightness(40); // 0..0xFF 58 | const unsigned int blink_slow_ms(1000); // blink period 59 | const unsigned int blink_fast_ms( 200); // blink period 60 | 61 | enum led_r_t { 62 | led_r_off, // WL_CONNECTED 63 | led_r_slow, // WL_NO_SSID_AVAIL 64 | led_r_fast, // WL_DISCONNECTED 65 | led_r_on, // WL_IDLE_STATUS, WL_CONNECTION_LOST, etc. 66 | }; 67 | enum led_g_t { 68 | led_g_off, // time valid 69 | led_g_slow, // waiting time valid 70 | led_g_fast, // 71 | led_g_on, // WiFi configuration portal is active 72 | }; 73 | enum led_b_t { 74 | led_b_off, // TCO off 75 | led_b_slow, // 76 | led_b_fast, // 77 | led_b_on, // TCO on 78 | }; 79 | led_r_t led_r(led_r_off); 80 | led_g_t led_g(led_g_off); 81 | led_b_t led_b(led_b_off); 82 | 83 | bool led_enable(true); 84 | 85 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 86 | // LED 87 | void LedShow() 88 | { 89 | CRGB led(0); 90 | if (led_enable) { 91 | switch (led_r) { 92 | case led_r_on : led.r = led_on_r; break; 93 | case led_r_fast: led.r = LedBlink(blink_fast_ms) ? led_on_r : 0; break; 94 | case led_r_slow: led.r = LedBlink(blink_slow_ms) ? led_on_r : 0; break; 95 | default: break; // led.r = 0 96 | } 97 | switch (led_g) { 98 | case led_g_on : led.g = led_on_g; break; 99 | case led_g_fast: led.g = LedBlink(blink_fast_ms) ? led_on_g : 0; break; 100 | case led_g_slow: led.g = LedBlink(blink_slow_ms) ? led_on_g : 0; break; 101 | default: break; // led.g = 0 102 | } 103 | switch (led_b) { 104 | case led_b_on : led.b = led_on_b; break; 105 | case led_b_fast: led.b = LedBlink(blink_fast_ms) ? led_on_b : 0; break; 106 | case led_b_slow: led.b = LedBlink(blink_slow_ms) ? led_on_b : 0; break; 107 | default: break; // led.b = 0 108 | } 109 | } 110 | leds[0] = led; 111 | FastLED.show(); 112 | } 113 | 114 | bool LedBlink(unsigned int period_ms) 115 | { 116 | return millis() / period_ms % 2 != 0; 117 | } 118 | 119 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 120 | // WiFi 121 | const char* WlStatus(wl_status_t wl_status) 122 | { 123 | if (wl_status >= 0 && wl_status <= 6) { 124 | return wl_status_str[wl_status]; 125 | } 126 | if (wl_status == 255) { 127 | return wl_status_str[7]; 128 | } 129 | return wl_status_str[8]; 130 | } 131 | 132 | void WifiCheck() 133 | { 134 | wl_status_t wifi_status_new = WiFi.status(); 135 | if (wifi_status != wifi_status_new) { 136 | wifi_status = wifi_status_new; 137 | Serial.printf("[WiFi]%s\n", WlStatus(wifi_status)); 138 | switch (wifi_status) { 139 | case WL_CONNECTED : led_r = led_r_off; break; 140 | case WL_NO_SSID_AVAIL: led_r = led_r_slow; break; 141 | case WL_DISCONNECTED : led_r = led_r_fast; break; 142 | default : led_r = led_r_on; break; // state transition also 143 | } 144 | } 145 | 146 | // retry interval 147 | if (millis() - wifi_retry_last_ms < wifi_retry_interval_ms) { 148 | return; 149 | } 150 | wifi_retry_last_ms = millis(); 151 | 152 | // reboot if wifi connection fails 153 | if (wifi_status == WL_CONNECT_FAILED) { 154 | Serial.print("[WiFi]connect failed: rebooting..\n"); 155 | ESP.restart(); 156 | return; 157 | } 158 | 159 | // let the wifi process do if wifi is not disconnected 160 | if (wifi_status != WL_DISCONNECTED) { 161 | wifi_retry_times = 0; 162 | return; 163 | } 164 | 165 | // reboot if wifi is disconnected for a long time 166 | if (++wifi_retry_times > wifi_retry_max_times) { 167 | Serial.print("[WiFi]disconnect timeout: rebooting..\n"); 168 | ESP.restart(); 169 | return; 170 | } 171 | 172 | // reconnect, and reboot if reconnection fails 173 | Serial.printf("[WiFi]reconnect %d\n", wifi_retry_times); 174 | if (!WiFi.reconnect()) { 175 | Serial.print("[WiFi]reconnect failed: rebooting..\n"); 176 | ESP.restart(); 177 | return; 178 | }; 179 | } 180 | 181 | void WifiConfigModeCallback(WiFiManager *wm) 182 | { 183 | led_g = led_g_on; // green LED indicates configuration portal is active 184 | LedShow(); 185 | } 186 | 187 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 188 | // TCO(Time Code Output) 189 | Ticker tk; 190 | const int ticker_interval_ms(100); // 100ms 191 | const int marker(0xff); // marker code TcoValue() returns 192 | 193 | // PWM for TCO signal 194 | const uint8_t ledc_pin_atom(22); // GPIO22 for ATOM Lite/Matrix 195 | const uint8_t ledc_pin_atoms3(5); // GPIO5 for ATOMS3 Lite 196 | const uint8_t ledc_channel(0); 197 | const uint32_t ledc_frequency(jjy_frequency); 198 | const uint8_t ledc_resolution(8); // 2^8 = 256 199 | const uint32_t ledc_duty_on(128); // 128/256 = 50% 200 | const uint32_t ledc_duty_off(0); // 0 201 | 202 | void TcoInit() 203 | { 204 | // carrier for TCO 205 | uint32_t ledc_freq_get = ledcSetup(ledc_channel, ledc_frequency, ledc_resolution); 206 | Serial.printf("ledc frequency get = %d\n", ledc_freq_get); 207 | 208 | int ledc_pin = ledc_pin_atom; 209 | if (M5.getBoard() == m5::board_t::board_M5AtomS3Lite) { 210 | ledc_pin = ledc_pin_atoms3; 211 | } 212 | Serial.printf("ledc pin = %d\n", ledc_pin); 213 | ledcAttachPin(ledc_pin, ledc_channel); 214 | 215 | // wait until middle of 100ms timing. ex. 50ms, 150ms, 250ms,.. 216 | clock_gettime(CLOCK_REALTIME, &ts); 217 | delayMicroseconds((150000000 - ts.tv_nsec % 100000000) / 1000); 218 | 219 | // for the first sample of statistics 220 | clock_gettime(CLOCK_REALTIME, &ts); 221 | Serial.printf("ts.tv_nsec = %ld\n", ts.tv_nsec); 222 | 223 | // start Ticker for TCO 224 | tk.attach_ms(ticker_interval_ms, TcoGen); 225 | } 226 | 227 | // main task of TCO 228 | void TcoGen() 229 | { 230 | // statistics of ts_nsec 231 | static int tk_count(0); 232 | static int tk_max(0); 233 | static int tk_min(0); 234 | static double tk_sum(0.0); 235 | static double tk_sq_sum(0.0); 236 | static int tk_distribution[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 237 | static int tk_last_nsec(0); 238 | 239 | if (!localtime_valid) { 240 | led_g = led_g_slow; // localtime not valid yet 241 | return; 242 | } 243 | led_g = led_g_off; // localtime valid 244 | 245 | getLocalTime(&td); 246 | clock_gettime(CLOCK_REALTIME, &ts); 247 | int ts_100ms = ts.tv_nsec / 100000000; 248 | switch (ts_100ms) { 249 | case 0: Tco000ms(); break; 250 | case 2: Tco200ms(); break; 251 | case 5: Tco500ms(); break; 252 | case 8: Tco800ms(); break; 253 | default: break; 254 | } 255 | 256 | if (tk_count++ != 0) { 257 | int tk_deviation = ts.tv_nsec - tk_last_nsec; 258 | if (tk_deviation < 0) { 259 | tk_deviation += 1000000000; // 0xx - 9xx ms --> 10xx - 9xx ms 260 | } 261 | tk_deviation -= 100000000; // center 100 ms --> 0 262 | 263 | if (tk_max < tk_deviation) tk_max = tk_deviation; 264 | if (tk_min > tk_deviation) tk_min = tk_deviation; 265 | tk_sum += (double)tk_deviation; 266 | tk_sq_sum += (double)tk_deviation * (double)tk_deviation; 267 | 268 | if (tk_deviation < -50000000) ++tk_distribution[0]; // ~ -50ms 269 | else if (tk_deviation < -5000000) ++tk_distribution[1]; // ~ -5ms 270 | else if (tk_deviation < -500000) ++tk_distribution[2]; // ~ -0.5ms 271 | else if (tk_deviation < -50000) ++tk_distribution[3]; // ~ -0.05ms 272 | else if (tk_deviation < 50000) ++tk_distribution[4]; // -0.05 ~ 0.05ms 273 | else if (tk_deviation < 500000) ++tk_distribution[5]; // 0.05ms ~ 274 | else if (tk_deviation < 5000000) ++tk_distribution[6]; // 0.5ms ~ 275 | else if (tk_deviation < 50000000) ++tk_distribution[7]; // 5ms ~ 276 | else ++tk_distribution[8]; // 50ms ~ 277 | } 278 | tk_last_nsec = ts.tv_nsec; 279 | 280 | if ((td.tm_sec == 0) && (ts.tv_nsec < 100000000)) { 281 | for (int i = 0; i < 9; ++i) { 282 | Serial.printf("%d ", tk_distribution[i]); 283 | } 284 | double tk_average = tk_sum / (double)tk_count; 285 | double tk_variance = (tk_sq_sum - tk_sum * tk_sum / (double)tk_count) / (double)tk_count; 286 | double tk_std_deviation = sqrt(tk_variance); 287 | Serial.printf("\nn= %d, ave= %.4f sdv= %.4f min= %d max= %d\n", tk_count, tk_average, tk_std_deviation, tk_min, tk_max); 288 | } 289 | } 290 | 291 | // TCO task at every 0ms 292 | void Tco000ms() 293 | { 294 | TcOn(); 295 | if (td.tm_sec == 0) { 296 | Serial.print(&td, "\n%A %B %d %Y %H:%M:%S\n"); 297 | } 298 | } 299 | 300 | // TCO task at every 200ms 301 | void Tco200ms() 302 | { 303 | if (TcoValue() == marker) { 304 | TcOff(); 305 | Serial.printf(" %d ", td.tm_sec); 306 | } 307 | } 308 | 309 | // TCO task at every 500ms 310 | void Tco500ms() 311 | { 312 | if (TcoValue() != 0) { 313 | TcOff(); 314 | if(TcoValue() != marker) { 315 | Serial.print("1"); 316 | } 317 | } 318 | } 319 | 320 | // TCO task at every 800ms 321 | void Tco800ms() 322 | { 323 | TcOff(); 324 | if (TcoValue() == 0) { 325 | Serial.print("0"); 326 | } 327 | } 328 | 329 | void TcOn() 330 | { 331 | ledcWrite(ledc_channel, ledc_duty_on); 332 | led_b = led_b_on; 333 | } 334 | 335 | void TcOff() 336 | { 337 | ledcWrite(ledc_channel, ledc_duty_off); 338 | led_b = led_b_off; 339 | } 340 | 341 | // TCO value 342 | // marker, 1:not zero, 0:zero 343 | int TcoValue() 344 | { 345 | const int days_of_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 346 | 347 | int bcd_hour = Int3Bcd(td.tm_hour); 348 | int parity_bcd_hour = Parity8(bcd_hour); 349 | 350 | int bcd_minute = Int3Bcd(td.tm_min); 351 | int parity_bcd_minute = Parity8(bcd_minute); 352 | 353 | int year = td.tm_year + 1900; 354 | int bcd_year = Int3Bcd(year); 355 | 356 | int days = td.tm_mday; 357 | for (int i = 0; i < td.tm_mon; ++i) { // td.tm_mon starts from 0 358 | days += days_of_month[i]; 359 | } 360 | if ((td.tm_mon >= 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) { 361 | ++days; 362 | } 363 | int bcd_days = Int3Bcd(days); 364 | 365 | int day_of_week = td.tm_wday; 366 | 367 | int tco; 368 | switch (td.tm_sec) { 369 | case 0: tco = marker; break; 370 | case 1: tco = bcd_minute & 0x40; break; 371 | case 2: tco = bcd_minute & 0x20; break; 372 | case 3: tco = bcd_minute & 0x10; break; 373 | case 4: tco = 0; break; 374 | case 5: tco = bcd_minute & 0x08; break; 375 | case 6: tco = bcd_minute & 0x04; break; 376 | case 7: tco = bcd_minute & 0x02; break; 377 | case 8: tco = bcd_minute & 0x01; break; 378 | case 9: tco = marker; break; 379 | 380 | case 10: tco = 0; break; 381 | case 11: tco = 0; break; 382 | case 12: tco = bcd_hour & 0x20; break; 383 | case 13: tco = bcd_hour & 0x10; break; 384 | case 14: tco = 0; break; 385 | case 15: tco = bcd_hour & 0x08; break; 386 | case 16: tco = bcd_hour & 0x04; break; 387 | case 17: tco = bcd_hour & 0x02; break; 388 | case 18: tco = bcd_hour & 0x01; break; 389 | case 19: tco = marker; break; 390 | 391 | case 20: tco = 0; break; 392 | case 21: tco = 0; break; 393 | case 22: tco = bcd_days & 0x200; break; 394 | case 23: tco = bcd_days & 0x100; break; 395 | case 24: tco = 0; break; 396 | case 25: tco = bcd_days & 0x080; break; 397 | case 26: tco = bcd_days & 0x040; break; 398 | case 27: tco = bcd_days & 0x020; break; 399 | case 28: tco = bcd_days & 0x010; break; 400 | case 29: tco = marker; break; 401 | 402 | case 30: tco = bcd_days & 0x008; break; 403 | case 31: tco = bcd_days & 0x004; break; 404 | case 32: tco = bcd_days & 0x002; break; 405 | case 33: tco = bcd_days & 0x001; break; 406 | case 34: tco = 0; break; 407 | case 35: tco = 0; break; 408 | case 36: tco = parity_bcd_hour; break; 409 | case 37: tco = parity_bcd_minute; break; 410 | case 38: tco = 0; break; 411 | case 39: tco = marker; break; 412 | 413 | case 40: tco = 0; break; 414 | case 41: tco = bcd_year & 0x80; break; 415 | case 42: tco = bcd_year & 0x40; break; 416 | case 43: tco = bcd_year & 0x20; break; 417 | case 44: tco = bcd_year & 0x10; break; 418 | case 45: tco = bcd_year & 0x08; break; 419 | case 46: tco = bcd_year & 0x04; break; 420 | case 47: tco = bcd_year & 0x02; break; 421 | case 48: tco = bcd_year & 0x01; break; 422 | case 49: tco = marker; break; 423 | 424 | case 50: tco = day_of_week & 0x04; break; 425 | case 51: tco = day_of_week & 0x02; break; 426 | case 52: tco = day_of_week & 0x01; break; 427 | case 53: tco = 0; break; 428 | case 54: tco = 0; break; 429 | case 55: tco = 0; break; 430 | case 56: tco = 0; break; 431 | case 57: tco = 0; break; 432 | case 58: tco = 0; break; 433 | case 59: tco = marker; break; 434 | default: tco = 0; break; 435 | } 436 | return tco; 437 | } 438 | 439 | int Int3Bcd(int a) 440 | { 441 | return (a % 10) + (a / 10 % 10 * 16) + (a / 100 % 10 * 256); 442 | } 443 | 444 | int Parity8(int a) 445 | { 446 | int pa = a; 447 | for (int i = 1; i < 8; ++i) { 448 | pa += a >> i; 449 | } 450 | return pa % 2; 451 | } 452 | 453 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 454 | // main 455 | const unsigned int loop_period_ms(100); 456 | unsigned int loop_last_ms; 457 | const int button_atom(39); // GPIO39 458 | const int button_atoms3(41); // GPIO41 459 | 460 | void setup() 461 | { 462 | if (M5.getBoard() == m5::board_t::board_M5Atom) { 463 | // To avoid Wi-Fi issues, force GPIO0 to 0 while CH552 outputs 5V with its internal pullup. 464 | // https://twitter.com/wakwak_koba/status/1553162622479974400 465 | // https://www.facebook.com/groups/154504605228235/posts/699719300706760/ 466 | pinMode(0, OUTPUT); 467 | digitalWrite(0, LOW); 468 | } 469 | 470 | // M5Unified 471 | auto cfg = M5.config(); 472 | cfg.serial_baudrate = 115200; // default=0. use Serial.print(). 473 | cfg.external_rtc = true; // default=false. use Unit RTC. 474 | M5.begin(cfg); 475 | delay(3000); 476 | Serial.println(); 477 | 478 | // FastLED 479 | #if defined (CONFIG_IDF_TARGET_ESP32S3) // FastLED requires strict constant for the pin number 480 | FastLED.addLeds(leds, led_num); 481 | #else 482 | FastLED.addLeds(leds, led_num); 483 | #endif 484 | FastLED.setBrightness(led_brightness); 485 | for (int i = 0; i < led_num; ++i) { 486 | leds[i] = 0; 487 | } 488 | 489 | // Unit RTC (External) 490 | if (rtcx.Begin(Wire) == 0) { 491 | rtcx_available = true; 492 | if (SetTimeFromRtcx(time_zone)) { 493 | localtime_valid = true; 494 | } 495 | } 496 | if (!localtime_valid) { 497 | Serial.print("RTC not valid: set the localtime temporarily\n"); 498 | td.tm_year = 117; // 2017 > 2016, getLocalTime() returns true 499 | td.tm_mon = 0; // January 500 | td.tm_mday = 1; 501 | td.tm_hour = 0; 502 | td.tm_min = 0; 503 | td.tm_sec = 0; 504 | struct timeval tv = { mktime(&td), 0 }; 505 | settimeofday(&tv, NULL); 506 | } 507 | getLocalTime(&td); 508 | Serial.print(&td, "localtime: %A, %B %d %Y %H:%M:%S\n"); 509 | // print sample: must be < 64 510 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 511 | //localtime: Wednesday, September 11 2021 11:10:46 512 | 513 | // WiFi start 514 | WiFiManager wm; // blocking mode only 515 | 516 | // erase SSID/Key to force rewrite 517 | int button_pin = button_atom; 518 | if (M5.getBoard() == m5::board_t::board_M5AtomS3Lite) { 519 | button_pin = button_atoms3; 520 | } 521 | if (digitalRead(button_pin) == LOW) { 522 | wm.resetSettings(); 523 | } 524 | 525 | // WiFi connect 526 | wm.setConfigPortalTimeout(wifi_config_portal_timeout_sec); 527 | wm.setAPCallback(WifiConfigModeCallback); 528 | wm.autoConnect(); 529 | WiFi.setSleep(false); // https://macsbug.wordpress.com/2021/05/02/buttona-on-m5stack-does-not-work-properly/ 530 | wifi_retry_last_ms = millis() - wifi_retry_interval_ms; 531 | 532 | // NTP start 533 | NtpBegin(time_zone, ntp_server); 534 | 535 | // TCO start 536 | TcoInit(); 537 | 538 | // clear button of erase SSID/Key 539 | M5.update(); 540 | 541 | // loop control 542 | loop_last_ms = millis(); 543 | } 544 | 545 | void loop() 546 | { 547 | M5.update(); 548 | LedShow(); 549 | 550 | WifiCheck(); 551 | if (RtcxUpdate(rtcx_available)) { 552 | localtime_valid = true; // SNTP sync completed 553 | }; 554 | 555 | // button: TCO monitor on/off 556 | if (M5.BtnA.wasReleased()) { 557 | led_enable = !led_enable; 558 | } 559 | 560 | // loop control 561 | unsigned int delay_ms(0); 562 | unsigned int elapse_ms = millis() - loop_last_ms; 563 | if (elapse_ms < loop_period_ms) { 564 | delay_ms = loop_period_ms - elapse_ms; 565 | } 566 | delay(delay_ms); 567 | loop_last_ms = millis(); 568 | // Serial.printf("loop elapse = %dms\n", elapse_ms); // for monitoring elapsed time 569 | } 570 | -------------------------------------------------------------------------------- /BF-018ARev3/BF_Pcf8563.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | // 4 | #include 5 | #include "BF_Pcf8563.h" 6 | 7 | int Pcf8563::Begin(TwoWire &wire) 8 | { 9 | m_wire = &wire; 10 | 11 | // clear registers 12 | external_clock_test_mode = false; 13 | source_clock_stoped = false; 14 | power_on_reset_override = true; 15 | if (WriteControl() != 0) return 1; 16 | 17 | clock_out_active =false; 18 | clock_out = fco_32768Hz; 19 | if (WriteClockOut() != 0) return 1; 20 | 21 | alarm_minute_enable = false; alarm_minute = 0; 22 | alarm_hour_enable = false; alarm_hour = 0; 23 | alarm_day_enable = false; alarm_day = 0; 24 | alarm_weekday_enable = false; alarm_weekday = 0; 25 | if (WriteAlarm() != 0) return 1; 26 | 27 | timer_enable = false; 28 | timer_source = fts_4096Hz; 29 | timer = 0; 30 | if (WriteTimer() != 0) return 1; 31 | 32 | timer_interrupt_pulse_mode = false; 33 | alarm_flag_active = false; 34 | timer_flag_active = false; 35 | alarm_interrupt_enable = false; 36 | timer_interrupt_enable = false; 37 | if (WriteInterrupt() != 0) return 1; 38 | 39 | return 0; 40 | } 41 | 42 | int Pcf8563::ReadTime(struct tm *tm_now) 43 | { 44 | if (ReadReg(0x02, 7) != 7) return 1; // ReadReg error 45 | if (m_reg[0x02] & 0x80) return 2; // invalid time 46 | tm_now->tm_sec = Bcd2Int(m_reg[0x02] & 0x7f); 47 | tm_now->tm_min = Bcd2Int(m_reg[0x03] & 0x7f); 48 | tm_now->tm_hour = Bcd2Int(m_reg[0x04] & 0x3f); 49 | tm_now->tm_mday = Bcd2Int(m_reg[0x05] & 0x3f); 50 | tm_now->tm_wday = Bcd2Int(m_reg[0x06] & 0x07); // 0:Sun, 1:Mon, .. 6:Sat 51 | tm_now->tm_mon = Bcd2Int(m_reg[0x07] & 0x1f) - 1; // tm month: 0..11 52 | tm_now->tm_year = Bcd2Int(m_reg[0x08] & 0xff); 53 | if ((m_reg[0x07] & 0x80) != 0) 54 | tm_now->tm_year += 100; // century bit 55 | return 0; 56 | } 57 | 58 | int Pcf8563::WriteTime(struct tm *tm_now) 59 | { 60 | m_reg[0x02] = Int2Bcd(tm_now->tm_sec); 61 | m_reg[0x03] = Int2Bcd(tm_now->tm_min); 62 | m_reg[0x04] = Int2Bcd(tm_now->tm_hour); 63 | m_reg[0x05] = Int2Bcd(tm_now->tm_mday); 64 | m_reg[0x06] = Int2Bcd(tm_now->tm_wday); // 0:Sun, 1:Mon, .. 6:Sat 65 | m_reg[0x07] = Int2Bcd(tm_now->tm_mon + 1); // rtc month: 1..12 66 | m_reg[0x08] = Int2Bcd(tm_now->tm_year % 100); 67 | if (tm_now->tm_year >= 100) 68 | m_reg[0x07] |= 0x80; // century bit 69 | return WriteReg(0x02, 7); 70 | } 71 | 72 | // to enable alarm, give valid value for arguments of minute, hour, day, weekday 73 | int Pcf8563::SetAlarm(int minute, int hour, int day, int weekday) 74 | { 75 | alarm_minute_enable = false; 76 | alarm_hour_enable = false; 77 | alarm_day_enable = false; 78 | alarm_weekday_enable = false; 79 | if (minute >= 0 && minute <= 59) { alarm_minute_enable = true; alarm_minute = minute; } 80 | if (hour >= 0 && hour <= 23) { alarm_hour_enable = true; alarm_hour = hour; } 81 | if (day >= 1 && day <= 31) { alarm_day_enable = true; alarm_day = day; } 82 | if (weekday >= 0 && weekday <= 6) { alarm_weekday_enable = true; alarm_weekday = weekday; } 83 | return WriteAlarm(); 84 | } 85 | 86 | int Pcf8563::DisableAlarm() 87 | { 88 | return SetAlarm(); 89 | } 90 | 91 | // interrupt_en = true: enable, false: disable 92 | int Pcf8563::EnableAlarmInterrupt(bool enable_interrupt, bool flag_keep) 93 | { 94 | if (ReadInterrupt() != 0) return 1; 95 | alarm_flag_active = flag_keep; 96 | alarm_interrupt_enable = enable_interrupt; 97 | return WriteInterrupt(); 98 | } 99 | 100 | int Pcf8563::DisableAlarmInterrupt() 101 | { 102 | return EnableAlarmInterrupt(false); // disable and clear 103 | } 104 | 105 | // Set timer_source and timer to the argument timer_sec as close as possible 106 | // returns actual timer value realized by timer_source and timer, 107 | // return 0.0: timer_sec is out of range, the timer was not set 108 | // return <>0.0: The timer is enabled. 109 | double Pcf8563::SetTimer(double timer_sec) 110 | { 111 | if (timer_sec < 1.0 / 4096.0) return 0.0; // minimum 0.00024414s 112 | if (timer_sec > 255.0 * 60.0) return 0.0; // maximum 15,300s 113 | 114 | if (timer_sec >= 240.0) { // cross over at 240s not 255s 115 | timer_source = fts_1_60th_Hz; 116 | timer = timer_sec / 60.0 + 0.5; // +0.5 for rounding 117 | } 118 | else if (timer_sec > 255.0 / 64.0) { 119 | timer_source = fts_1Hz; 120 | timer = timer_sec + 0.5; // +0.5 for rounding 121 | } 122 | else if (timer_sec > 255.0 / 4096.0) { 123 | timer_source = fts_64Hz; 124 | timer = timer_sec * 64.0 + 0.5; // +0.5 for rounding 125 | } 126 | else { 127 | timer_source = fts_4096Hz; 128 | timer = timer_sec * 4096.0 + 0.5; // +0.5 for rounding 129 | } 130 | timer_enable = true; 131 | if (WriteTimer() != 0) return 0.0; 132 | 133 | switch (timer_source) { 134 | case 0: return timer / 4096.0; break; 135 | case 1: return timer / 64.0; break; 136 | case 2: return timer / 1.0; break; 137 | case 3: return timer * 60.0; break; 138 | default: return 0.0; break; 139 | } 140 | } 141 | 142 | // timer_en = true: enable, false: disable 143 | int Pcf8563::EnableTimer(bool enable_timer) 144 | { 145 | if (ReadReg(0x0e, 1) != 1) return 1; 146 | switch (m_reg[0x0e] & 0x03) { 147 | case 0: timer_source = fts_4096Hz; break; 148 | case 1: timer_source = fts_64Hz; break; 149 | case 2: timer_source = fts_1Hz; break; 150 | case 3: timer_source = fts_1_60th_Hz; break; 151 | default: break; 152 | } 153 | timer_enable = enable_timer; 154 | m_reg[0x0e] = timer_source; 155 | if (timer_enable) 156 | m_reg[0x0e] |= 0x80; 157 | return WriteReg(0x0e, 1); 158 | } 159 | 160 | int Pcf8563::DisableTimer() 161 | { 162 | return EnableTimer(false); 163 | } 164 | 165 | // interrupt_enable = true: enable, false: disable 166 | int Pcf8563::EnableTimerInterrupt(bool enable_interrupt, bool pulse_mode, bool keep_flag) 167 | { 168 | if (ReadInterrupt() != 0) return 1; 169 | timer_interrupt_pulse_mode = pulse_mode; 170 | timer_flag_active = keep_flag; 171 | timer_interrupt_enable = enable_interrupt; 172 | if (WriteInterrupt() != 0) return 1; 173 | return 0; 174 | } 175 | 176 | int Pcf8563::DisableTimerInterrupt() 177 | { 178 | return EnableTimerInterrupt(false); // disable and clear 179 | } 180 | 181 | int Pcf8563::GetInterrupt() 182 | { 183 | if (ReadInterrupt() != 0) return 4; 184 | int flag_got(0); 185 | if (alarm_flag_active) { flag_got |= 0x02; alarm_flag_active = false; } 186 | if (timer_flag_active) { flag_got |= 0x01; timer_flag_active = false; } 187 | if (flag_got != 0) 188 | if (WriteInterrupt() != 0) return 4; 189 | return flag_got; 190 | } 191 | 192 | int Pcf8563::ClockOutForTrimmer(bool enable_clko) // clock out 1Hz 193 | { 194 | if (enable_clko) { 195 | // CLKO(clock out) to adjust trimmer 196 | clock_out = fco_1Hz; 197 | clock_out_active = true; 198 | if (WriteClockOut() != 0) return 1; 199 | } 200 | else { 201 | clock_out_active = false; 202 | if (WriteClockOut() != 0) return 1; 203 | } 204 | return 0; 205 | } 206 | 207 | Pcf8563::Pcf8563() 208 | { 209 | m_reg = new uint8_t[m_reg_size]; 210 | for (int i = 0; i < m_reg_size; ++i) 211 | m_reg[i] = 0; 212 | } 213 | 214 | Pcf8563::~Pcf8563() 215 | { 216 | delete [] m_reg; 217 | } 218 | 219 | int Pcf8563::ReadControl() 220 | { 221 | if (ReadReg(0x00, 1) != 1) return 1; 222 | external_clock_test_mode = m_reg[0x00] & 0x80; 223 | source_clock_stoped = m_reg[0x00] & 0x20; 224 | power_on_reset_override = m_reg[0x00] & 0x08; 225 | return 0; 226 | } 227 | 228 | int Pcf8563::WriteControl() 229 | { 230 | m_reg[0x00] = 0x00; 231 | if (external_clock_test_mode) m_reg[0x00] |= 0x80; 232 | if (source_clock_stoped ) m_reg[0x00] |= 0x20; 233 | if (power_on_reset_override ) m_reg[0x00] |= 0x08; 234 | return WriteReg(0x00, 1); 235 | } 236 | 237 | int Pcf8563::ReadInterrupt() 238 | { 239 | if (ReadReg(0x01, 1) != 1) return 1; 240 | timer_interrupt_pulse_mode = m_reg[0x01] & 0x10; 241 | alarm_flag_active = m_reg[0x01] & 0x08; 242 | timer_flag_active = m_reg[0x01] & 0x04; 243 | alarm_interrupt_enable = m_reg[0x01] & 0x02; 244 | timer_interrupt_enable = m_reg[0x01] & 0x01; 245 | return 0; 246 | } 247 | 248 | int Pcf8563::WriteInterrupt() 249 | { 250 | m_reg[0x01] = 0x00; 251 | if (timer_interrupt_pulse_mode) m_reg[0x01] |= 0x10; 252 | if (alarm_flag_active ) m_reg[0x01] |= 0x08; 253 | if (timer_flag_active ) m_reg[0x01] |= 0x04; 254 | if (alarm_interrupt_enable ) m_reg[0x01] |= 0x02; 255 | if (timer_interrupt_enable ) m_reg[0x01] |= 0x01; 256 | return WriteReg(0x01, 1); 257 | } 258 | 259 | int Pcf8563::ReadAlarm() 260 | { 261 | if (ReadReg(0x09, 4) != 4) return 1; 262 | alarm_minute_enable = (m_reg[0x09] & 0x80) == 0; 263 | alarm_hour_enable = (m_reg[0x0a] & 0x80) == 0; 264 | alarm_day_enable = (m_reg[0x0b] & 0x80) == 0; 265 | alarm_weekday_enable = (m_reg[0x0c] & 0x80) == 0; 266 | alarm_minute = Bcd2Int(m_reg[0x09] & 0x7f); 267 | alarm_hour = Bcd2Int(m_reg[0x0a] & 0x3f); 268 | alarm_day = Bcd2Int(m_reg[0x0b] & 0x3f); 269 | alarm_weekday = Bcd2Int(m_reg[0x0c] & 0x07); 270 | return 0; 271 | } 272 | 273 | int Pcf8563::WriteAlarm() 274 | { 275 | m_reg[0x09] = Int2Bcd(alarm_minute ); 276 | m_reg[0x0a] = Int2Bcd(alarm_hour ); 277 | m_reg[0x0b] = Int2Bcd(alarm_day ); 278 | m_reg[0x0c] = Int2Bcd(alarm_weekday); 279 | if (!alarm_minute_enable ) m_reg[0x09] |= 0x80; 280 | if (!alarm_hour_enable ) m_reg[0x0a] |= 0x80; 281 | if (!alarm_day_enable ) m_reg[0x0b] |= 0x80; 282 | if (!alarm_weekday_enable) m_reg[0x0c] |= 0x80; 283 | return WriteReg(0x09, 4); 284 | } 285 | 286 | int Pcf8563::ReadClockOut() 287 | { 288 | if (ReadReg(0x0d, 1) != 1) return 1; 289 | clock_out_active = m_reg[0x0d] & 0x80; 290 | switch (m_reg[0x0d] & 0x03) { 291 | case 0: clock_out = fco_32768Hz; break; 292 | case 1: clock_out = fco_1024Hz; break; 293 | case 2: clock_out = fco_32Hz; break; 294 | case 3: clock_out = fco_1Hz; break; 295 | default: break; 296 | } 297 | return 0; 298 | } 299 | 300 | int Pcf8563::WriteClockOut() 301 | { 302 | m_reg[0x0d] = clock_out; 303 | if (clock_out_active) 304 | m_reg[0x0d] |= 0x80; 305 | return WriteReg(0x0d, 1); 306 | } 307 | 308 | int Pcf8563::ReadTimer() 309 | { 310 | if (ReadReg(0x0e, 2) != 2) return 1; 311 | timer_enable = m_reg[0x0e] & 0x80; 312 | switch (m_reg[0x0e] & 0x03) { 313 | case 0: timer_source = fts_4096Hz; break; 314 | case 1: timer_source = fts_64Hz; break; 315 | case 2: timer_source = fts_1Hz; break; 316 | case 3: timer_source = fts_1_60th_Hz; break; 317 | default: break; 318 | } 319 | timer = m_reg[0x0f]; 320 | return 0; 321 | } 322 | 323 | int Pcf8563::WriteTimer() 324 | { 325 | m_reg[0x0e] = timer_source; 326 | if (timer_enable) 327 | m_reg[0x0e] |= 0x80; 328 | m_reg[0x0f] = timer; 329 | return WriteReg(0x0e, 2); 330 | } 331 | 332 | int Pcf8563::Int2Bcd(int int_num) 333 | { 334 | return int_num / 10 * 16 + int_num % 10; 335 | } 336 | 337 | int Pcf8563::Bcd2Int(int bcd_num) 338 | { 339 | return bcd_num / 16 * 10 + bcd_num % 16; 340 | } 341 | 342 | size_t Pcf8563::ReadReg(int reg_start, size_t read_length) 343 | { 344 | const bool send_stop(true); 345 | 346 | m_wire->beginTransmission(m_i2c_address); 347 | m_wire->write(reg_start); 348 | if (m_wire->endTransmission(!send_stop) != 0) { 349 | Serial.print("[PCF8563]ERROR ReadReg write\n"); 350 | return 0; // command error 351 | } 352 | m_wire->requestFrom(m_i2c_address, read_length); 353 | size_t i = 0; 354 | while (m_wire->available()) 355 | m_reg[reg_start + i++] = m_wire->read(); 356 | if (i != read_length) 357 | Serial.print("[PCF8563]ERROR ReadReg read\n"); 358 | return i; 359 | } 360 | 361 | int Pcf8563::WriteReg(int reg_start, size_t write_length) 362 | { 363 | m_wire->beginTransmission(m_i2c_address); 364 | m_wire->write(reg_start); 365 | for (int i = 0; i < write_length; ++i) 366 | m_wire->write(m_reg[reg_start + i]); 367 | int return_code = m_wire->endTransmission(); 368 | if (return_code != 0) 369 | Serial.print("[PCF8563]ERROR WriteReg\n"); 370 | return return_code; 371 | } 372 | 373 | // the instance "rtc external" 374 | Pcf8563 rtcx; 375 | -------------------------------------------------------------------------------- /BF-018ARev3/BF_Pcf8563.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | // 4 | #pragma once 5 | #include 6 | #include // for struct tm 7 | 8 | class Pcf8563 { 9 | public: 10 | int Begin(TwoWire &wire); 11 | int ReadTime(struct tm *tm_now); 12 | int WriteTime(struct tm *tm_now); 13 | 14 | int SetAlarm(int miniute = -1, int hour = -1, int day = -1, int weekday = -1); 15 | int DisableAlarm(); 16 | int EnableAlarmInterrupt(bool enable_interrupt = true, bool keep_flag = false); 17 | int DisableAlarmInterrupt(); 18 | 19 | double SetTimer(double timer_sec); 20 | int EnableTimer(bool enable_timer = true); 21 | int DisableTimer(); 22 | int EnableTimerInterrupt(bool enable_interrupt = true, bool pulse_mode = false, bool keep_flag = false); 23 | int DisableTimerInterrupt(); 24 | 25 | int GetInterrupt(); // check interrupt and clear 26 | 27 | int ClockOutForTrimmer(bool enable_clko = true); // clock out 1Hz 28 | 29 | bool external_clock_test_mode; // 00h Control_status_1 bit 7 TEST1 30 | bool source_clock_stoped; // 00h Control_status_1 bit 5 STOP 31 | bool power_on_reset_override; // 00h Control_status_1 bit 3 TESTC 32 | 33 | bool timer_interrupt_pulse_mode; // 01h Control_status_2 bit 4 TI_TP 34 | bool alarm_flag_active; // 01h Control_status_2 bit 3 AF 35 | bool timer_flag_active; // 01h Control_status_2 bit 2 TF 36 | bool alarm_interrupt_enable; // 01h Control_status_2 bit 1 AIE 37 | bool timer_interrupt_enable; // 01h Control_status_2 bit 0 TIE 38 | 39 | bool alarm_minute_enable; // 09h Minute_alarm bit 7 !AE_M 40 | int alarm_minute; // 09h Minute_alarm 0..59 41 | bool alarm_hour_enable; // 0Ah Hour_alarm bit 7 !AE_H 42 | int alarm_hour; // 0Ah Hour_alarm 0..23 43 | bool alarm_day_enable; // 0Bh Day_alarm bit 7 !AE_D 44 | int alarm_day; // 0Bh Day_alarm 1..31 45 | bool alarm_weekday_enable; // 0Ch Weekday_alarm bit 7 !AE_W 46 | int alarm_weekday; // 0Ch Weekday_alarm 0..6 47 | 48 | enum FreqClockOut_t { // 0Dh Clock_out_control FD 0..3 49 | fco_32768Hz, 50 | fco_1024Hz, 51 | fco_32Hz, 52 | fco_1Hz, 53 | }; 54 | 55 | bool clock_out_active; // 0Dh Clock_out_control bit 7 FE 56 | FreqClockOut_t clock_out; // 0Dh Clock_out_control 0..3 57 | 58 | enum FreqTimerSource_t { // 0Eh Timer_control TD 0..3 59 | fts_4096Hz, 60 | fts_64Hz, 61 | fts_1Hz, 62 | fts_1_60th_Hz, // (1/60)Hz 63 | }; 64 | 65 | bool timer_enable; // 0Eh Timer_control bit 7 TE 66 | FreqTimerSource_t timer_source; // 0Eh Timer_control 0..3 67 | int timer; // 0Fh Timer 0..255 68 | 69 | Pcf8563(); 70 | ~Pcf8563(); 71 | 72 | int ReadControl(); 73 | int WriteControl(); 74 | int ReadInterrupt(); 75 | int WriteInterrupt(); 76 | int ReadAlarm(); 77 | int WriteAlarm(); 78 | int ReadClockOut(); 79 | int WriteClockOut(); 80 | int ReadTimer(); 81 | int WriteTimer(); 82 | 83 | private: 84 | const int m_i2c_address = 0x51; 85 | const int m_reg_size = 0x10; 86 | 87 | TwoWire *m_wire; 88 | uint8_t *m_reg; 89 | 90 | size_t ReadReg(int reg_start, size_t read_length); 91 | int WriteReg(int reg_start, size_t write_length); 92 | int Int2Bcd(int int_num); 93 | int Bcd2Int(int bcd_num); 94 | }; 95 | 96 | // an instance "rtc external" 97 | extern Pcf8563 rtcx; 98 | -------------------------------------------------------------------------------- /BF-018ARev3/BF_RtcxNtp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | // 4 | #include 5 | #include "BF_Pcf8563.h" 6 | #include "BF_RtcxNtp.h" 7 | 8 | bool sntp_sync_status_complete(false); 9 | 10 | void NtpBegin(const char* time_zone, const char* ntp_server) 11 | { 12 | configTzTime(time_zone, ntp_server); 13 | Serial.printf("NtpBegin: config TZ time = %s\n", time_zone); 14 | Serial.printf("NtpBegin: SNTP sync mode = %d (0:IMMED 1:SMOOTH)\n", sntp_get_sync_mode()); 15 | Serial.printf("NtpBegin: SNTP sync interval = %dms\n", sntp_get_sync_interval()); 16 | sntp_sync_status_complete = false; 17 | sntp_set_time_sync_notification_cb(SntpTimeSyncNotificationCallback); 18 | } 19 | 20 | void SntpTimeSyncNotificationCallback(struct timeval *tv) 21 | { 22 | sntp_sync_status_t sntp_sync_status = sntp_get_sync_status(); 23 | PrintSntpStatus("SNTP callback:", sntp_sync_status); 24 | if (sntp_sync_status == SNTP_SYNC_STATUS_COMPLETED) { 25 | sntp_sync_status_complete = true; 26 | } 27 | } 28 | 29 | bool RtcxUpdate(bool rtcx_avail) 30 | { 31 | if (sntp_sync_status_complete) { 32 | sntp_sync_status_complete = false; 33 | 34 | struct tm tm_sync; 35 | getLocalTime(&tm_sync); 36 | Serial.print(&tm_sync, "SNTP sync: %A, %B %d %Y %H:%M:%S\n"); 37 | // print sample: must be < 64 38 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 39 | //SNTP sync: Wednesday, September 11 2021 11:10:46 40 | 41 | if (rtcx_avail) { 42 | if (rtcx.WriteTime(&tm_sync) == 0) { 43 | Serial.print("RTCx updated\n"); 44 | } 45 | else { 46 | Serial.print("RTCx update failed\n"); 47 | } 48 | } 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status) 55 | { 56 | static const char* sntp_sync_status_str[] = { 57 | "SNTP_SYNC_STATUS_RESET ", // 0 58 | "SNTP_SYNC_STATUS_COMPLETED ", // 1 59 | "SNTP_SYNC_STATUS_IN_PROGRESS ", // 2 60 | "sntp_sync_status invalid ", // 3 61 | }; 62 | int sntp_sync_status_index = 3; 63 | if (sntp_sync_status >= 0 && sntp_sync_status <= 2) { 64 | sntp_sync_status_index = sntp_sync_status; 65 | } 66 | Serial.printf("%s status = %d %s\n", header, sntp_sync_status, sntp_sync_status_str[sntp_sync_status_index]); 67 | } 68 | 69 | bool SetTimeFromRtcx(const char* time_zone) 70 | { 71 | bool rtcx_valid(false); 72 | struct tm tm_init; 73 | 74 | setenv("TZ", time_zone, 1); 75 | tzset(); // assign timezone with setenv for mktime() 76 | 77 | if (rtcx.ReadTime(&tm_init) == 0) { 78 | rtcx_valid = true; 79 | struct timeval tv = { mktime(&tm_init), 0 }; 80 | settimeofday(&tv, NULL); 81 | Serial.print("RTCx valid, the localtime was set\n"); 82 | } else { 83 | Serial.print("RTCx not valid\n"); 84 | } 85 | return rtcx_valid; 86 | } 87 | -------------------------------------------------------------------------------- /BF-018ARev3/BF_RtcxNtp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | // 4 | #pragma once 5 | #include // for struct timeval 6 | #include // for sntp_sync_status 7 | 8 | // examples 9 | // const char* time_zone = "JST-9"; 10 | // const char* ntp_server = "pool.ntp.org"; 11 | void NtpBegin(const char* time_zone, const char* ntp_server); 12 | void SntpTimeSyncNotificationCallback(struct timeval *tv); 13 | bool RtcxUpdate(bool rtcx_avail = true); 14 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status); 15 | bool SetTimeFromRtcx(const char* time_zone); 16 | -------------------------------------------------------------------------------- /BF-018ARev4/BF-018ARev4.ino: -------------------------------------------------------------------------------- 1 | // copyright 2025 BotanicFields, Inc. 2 | // BF-018A Rev.4 3 | // JJY Simulator for ATOM Lite / ATOM Matrix / ATOMS3 Lite 4 | // 5 | #include 6 | #include // https://github.com/FastLED/FastLED 7 | #include 8 | #include 9 | #include // https://github.com/tzapu/WiFiManager 10 | #include "BF_Pcf8563.h" 11 | #include "BF_RtcxNtp.h" 12 | 13 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 14 | // for TCO(Time Code Output) 15 | const uint32_t jjy_frequency(40000); // 40kHz(east), 60kHz(west) 16 | struct tm td; // time of day: year, month, day, day of week, hour, minute, second 17 | struct timespec ts; // time spec: second, nano-second 18 | 19 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 20 | // for NTP 21 | const char* time_zone = "JST-9"; 22 | const char* ntp_server = "pool.ntp.org"; 23 | bool rtcx_available(false); 24 | bool localtime_valid(false); 25 | 26 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 27 | // for WiFi 28 | const int wifi_config_portal_timeout_sec(60); 29 | const unsigned int wifi_retry_interval_ms(60000); 30 | unsigned int wifi_retry_last_ms(0); 31 | const int wifi_retry_max_times(3); 32 | int wifi_retry_times(0); 33 | 34 | wl_status_t wifi_status(WL_NO_SHIELD); 35 | const char* wl_status_str[] = { 36 | "WL_IDLE_STATUS", // 0 37 | "WL_NO_SSID_AVAIL", // 1 38 | "WL_SCAN_COMPLETED", // 2 39 | "WL_CONNECTED", // 3 40 | "WL_CONNECT_FAILED", // 4 41 | "WL_CONNECTION_LOST", // 5 42 | "WL_DISCONNECTED", // 6 43 | "WL_NO_SHIELD", // 7 <-- 255 44 | "wl_status invalid", // 8 45 | }; 46 | 47 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 48 | // for FastLED 49 | const int led_pin_atom(27); // GPIO27 for ATOM Lite/Matrix 50 | const int led_pin_atoms3lite(35); // GPIO35 for ATOMS3 Lite 51 | const unsigned char led_num(25); // ATOM Matrix: 25, ATOM lite/ATOMS3 Lite: 1 --> 25 52 | CRGB leds[led_num]; 53 | 54 | const int led_on_r(0x80); // 0..0xFF 55 | const int led_on_g(0x80); // 0..0xFF 56 | const int led_on_b(0x80); // 0..0xFF 57 | const int led_brightness(40); // 0..0xFF 58 | const unsigned int blink_slow_ms(1000); // blink period 59 | const unsigned int blink_fast_ms( 200); // blink period 60 | 61 | enum led_r_t { 62 | led_r_off, // WL_CONNECTED 63 | led_r_slow, // WL_NO_SSID_AVAIL 64 | led_r_fast, // WL_DISCONNECTED 65 | led_r_on, // WL_IDLE_STATUS, WL_CONNECTION_LOST, etc. 66 | }; 67 | enum led_g_t { 68 | led_g_off, // time valid 69 | led_g_slow, // waiting time valid 70 | led_g_fast, // 71 | led_g_on, // WiFi configuration portal is active 72 | }; 73 | enum led_b_t { 74 | led_b_off, // TCO off 75 | led_b_slow, // 76 | led_b_fast, // 77 | led_b_on, // TCO on 78 | }; 79 | led_r_t led_r(led_r_off); 80 | led_g_t led_g(led_g_off); 81 | led_b_t led_b(led_b_off); 82 | 83 | bool led_enable(true); 84 | 85 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 86 | // LED 87 | void LedShow() 88 | { 89 | CRGB led(0); 90 | if (led_enable) { 91 | switch (led_r) { 92 | case led_r_on : led.r = led_on_r; break; 93 | case led_r_fast: led.r = LedBlink(blink_fast_ms) ? led_on_r : 0; break; 94 | case led_r_slow: led.r = LedBlink(blink_slow_ms) ? led_on_r : 0; break; 95 | default: break; // led.r = 0 96 | } 97 | switch (led_g) { 98 | case led_g_on : led.g = led_on_g; break; 99 | case led_g_fast: led.g = LedBlink(blink_fast_ms) ? led_on_g : 0; break; 100 | case led_g_slow: led.g = LedBlink(blink_slow_ms) ? led_on_g : 0; break; 101 | default: break; // led.g = 0 102 | } 103 | switch (led_b) { 104 | case led_b_on : led.b = led_on_b; break; 105 | case led_b_fast: led.b = LedBlink(blink_fast_ms) ? led_on_b : 0; break; 106 | case led_b_slow: led.b = LedBlink(blink_slow_ms) ? led_on_b : 0; break; 107 | default: break; // led.b = 0 108 | } 109 | } 110 | leds[0] = led; 111 | FastLED.show(); 112 | } 113 | 114 | bool LedBlink(unsigned int period_ms) 115 | { 116 | return millis() / period_ms % 2 != 0; 117 | } 118 | 119 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 120 | // WiFi 121 | const char* WlStatus(wl_status_t wl_status) 122 | { 123 | if (wl_status >= 0 && wl_status <= 6) { 124 | return wl_status_str[wl_status]; 125 | } 126 | if (wl_status == 255) { 127 | return wl_status_str[7]; 128 | } 129 | return wl_status_str[8]; 130 | } 131 | 132 | void WifiCheck() 133 | { 134 | wl_status_t wifi_status_new = WiFi.status(); 135 | if (wifi_status != wifi_status_new) { 136 | wifi_status = wifi_status_new; 137 | Serial.printf("[WiFi]%s\n", WlStatus(wifi_status)); 138 | switch (wifi_status) { 139 | case WL_CONNECTED : led_r = led_r_off; break; 140 | case WL_NO_SSID_AVAIL: led_r = led_r_slow; break; 141 | case WL_DISCONNECTED : led_r = led_r_fast; break; 142 | default : led_r = led_r_on; break; // state transition also 143 | } 144 | } 145 | 146 | // retry interval 147 | if (millis() - wifi_retry_last_ms < wifi_retry_interval_ms) { 148 | return; 149 | } 150 | wifi_retry_last_ms = millis(); 151 | 152 | // reboot if wifi connection fails 153 | if (wifi_status == WL_CONNECT_FAILED) { 154 | Serial.print("[WiFi]connect failed: rebooting..\n"); 155 | ESP.restart(); 156 | return; 157 | } 158 | 159 | // let the wifi process do if wifi is not disconnected 160 | if (wifi_status != WL_DISCONNECTED) { 161 | wifi_retry_times = 0; 162 | return; 163 | } 164 | 165 | // reboot if wifi is disconnected for a long time 166 | if (++wifi_retry_times > wifi_retry_max_times) { 167 | Serial.print("[WiFi]disconnect timeout: rebooting..\n"); 168 | ESP.restart(); 169 | return; 170 | } 171 | 172 | // reconnect, and reboot if reconnection fails 173 | Serial.printf("[WiFi]reconnect %d\n", wifi_retry_times); 174 | if (!WiFi.reconnect()) { 175 | Serial.print("[WiFi]reconnect failed: rebooting..\n"); 176 | ESP.restart(); 177 | return; 178 | }; 179 | } 180 | 181 | void WifiConfigModeCallback(WiFiManager *wm) 182 | { 183 | led_g = led_g_on; // green LED indicates configuration portal is active 184 | LedShow(); 185 | } 186 | 187 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 188 | // TCO(Time Code Output) 189 | Ticker tk; 190 | const int ticker_interval_ms(100); // 100ms 191 | const int marker(0xff); // marker code TcoValue() returns 192 | 193 | // PWM for TCO signal 194 | const uint8_t ledc_pin_atom(22); // GPIO22 for ATOM Lite/Matrix 195 | const uint8_t ledc_pin_atoms3(5); // GPIO5 for ATOMS3 Lite 196 | const uint32_t ledc_frequency(jjy_frequency); 197 | const uint8_t ledc_resolution(8); // 2^8 = 256 198 | const uint32_t ledc_duty_on(128); // 128/256 = 50% 199 | const uint32_t ledc_duty_off(0); // 0 200 | uint8_t ledc_pin(ledc_pin_atom); 201 | 202 | void TcoInit() 203 | { 204 | if (M5.getBoard() == m5::board_t::board_M5AtomS3Lite) { 205 | ledc_pin = ledc_pin_atoms3; 206 | } 207 | Serial.printf("ledcAttach result= %d\n", ledcAttach(ledc_pin, ledc_frequency, ledc_resolution)); 208 | Serial.printf("ledcWrite result= %d\n", ledcWrite(ledc_pin, ledc_duty_on)); 209 | Serial.printf("pin= %u, duty= %lu, freq= %lu\n", ledc_pin, ledcRead(ledc_pin), ledcReadFreq(ledc_pin)); 210 | 211 | // wait until middle of 100ms timing. ex. 50ms, 150ms, 250ms,.. 212 | clock_gettime(CLOCK_REALTIME, &ts); 213 | delayMicroseconds((150000000 - ts.tv_nsec % 100000000) / 1000); 214 | 215 | // for the first sample of statistics 216 | clock_gettime(CLOCK_REALTIME, &ts); 217 | Serial.printf("ts.tv_nsec = %ld\n", ts.tv_nsec); 218 | 219 | // start Ticker for TCO 220 | tk.attach_ms(ticker_interval_ms, TcoGen); 221 | } 222 | 223 | // main task of TCO 224 | void TcoGen() 225 | { 226 | // statistics of ts_nsec 227 | static int tk_count(0); 228 | static int tk_max(0); 229 | static int tk_min(0); 230 | static double tk_sum(0.0); 231 | static double tk_sq_sum(0.0); 232 | static int tk_distribution[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 233 | static int tk_last_nsec(0); 234 | 235 | if (!localtime_valid) { 236 | led_g = led_g_slow; // localtime not valid yet 237 | return; 238 | } 239 | led_g = led_g_off; // localtime valid 240 | 241 | getLocalTime(&td); 242 | clock_gettime(CLOCK_REALTIME, &ts); 243 | int ts_100ms = ts.tv_nsec / 100000000; 244 | switch (ts_100ms) { 245 | case 0: Tco000ms(); break; 246 | case 2: Tco200ms(); break; 247 | case 5: Tco500ms(); break; 248 | case 8: Tco800ms(); break; 249 | default: break; 250 | } 251 | 252 | if (tk_count++ != 0) { 253 | int tk_deviation = ts.tv_nsec - tk_last_nsec; 254 | if (tk_deviation < 0) { 255 | tk_deviation += 1000000000; // 0xx - 9xx ms --> 10xx - 9xx ms 256 | } 257 | tk_deviation -= 100000000; // center 100 ms --> 0 258 | 259 | if (tk_max < tk_deviation) tk_max = tk_deviation; 260 | if (tk_min > tk_deviation) tk_min = tk_deviation; 261 | tk_sum += (double)tk_deviation; 262 | tk_sq_sum += (double)tk_deviation * (double)tk_deviation; 263 | 264 | if (tk_deviation < -50000000) ++tk_distribution[0]; // ~ -50ms 265 | else if (tk_deviation < -5000000) ++tk_distribution[1]; // ~ -5ms 266 | else if (tk_deviation < -500000) ++tk_distribution[2]; // ~ -0.5ms 267 | else if (tk_deviation < -50000) ++tk_distribution[3]; // ~ -0.05ms 268 | else if (tk_deviation < 50000) ++tk_distribution[4]; // -0.05 ~ 0.05ms 269 | else if (tk_deviation < 500000) ++tk_distribution[5]; // 0.05ms ~ 270 | else if (tk_deviation < 5000000) ++tk_distribution[6]; // 0.5ms ~ 271 | else if (tk_deviation < 50000000) ++tk_distribution[7]; // 5ms ~ 272 | else ++tk_distribution[8]; // 50ms ~ 273 | } 274 | tk_last_nsec = ts.tv_nsec; 275 | 276 | if ((td.tm_sec == 0) && (ts.tv_nsec < 100000000)) { 277 | for (int i = 0; i < 9; ++i) { 278 | Serial.printf("%d ", tk_distribution[i]); 279 | } 280 | double tk_average = tk_sum / (double)tk_count; 281 | double tk_variance = (tk_sq_sum - tk_sum * tk_sum / (double)tk_count) / (double)tk_count; 282 | double tk_std_deviation = sqrt(tk_variance); 283 | Serial.printf("\nn= %d, ave= %.4f sdv= %.4f min= %d max= %d\n", tk_count, tk_average, tk_std_deviation, tk_min, tk_max); 284 | } 285 | } 286 | 287 | // TCO task at every 0ms 288 | void Tco000ms() 289 | { 290 | TcOn(); 291 | if (td.tm_sec == 0) { 292 | Serial.print(&td, "\n%A %B %d %Y %H:%M:%S\n"); 293 | } 294 | } 295 | 296 | // TCO task at every 200ms 297 | void Tco200ms() 298 | { 299 | if (TcoValue() == marker) { 300 | TcOff(); 301 | Serial.printf(" %d ", td.tm_sec); 302 | } 303 | } 304 | 305 | // TCO task at every 500ms 306 | void Tco500ms() 307 | { 308 | if (TcoValue() != 0) { 309 | TcOff(); 310 | if(TcoValue() != marker) { 311 | Serial.print("1"); 312 | } 313 | } 314 | } 315 | 316 | // TCO task at every 800ms 317 | void Tco800ms() 318 | { 319 | TcOff(); 320 | if (TcoValue() == 0) { 321 | Serial.print("0"); 322 | } 323 | } 324 | 325 | void TcOn() 326 | { 327 | ledcWrite(ledc_pin, ledc_duty_on); 328 | led_b = led_b_on; 329 | } 330 | 331 | void TcOff() 332 | { 333 | ledcWrite(ledc_pin, ledc_duty_off); 334 | led_b = led_b_off; 335 | } 336 | 337 | // TCO value 338 | // marker, 1:not zero, 0:zero 339 | int TcoValue() 340 | { 341 | const int days_of_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 342 | 343 | int bcd_hour = Int3Bcd(td.tm_hour); 344 | int parity_bcd_hour = Parity8(bcd_hour); 345 | 346 | int bcd_minute = Int3Bcd(td.tm_min); 347 | int parity_bcd_minute = Parity8(bcd_minute); 348 | 349 | int year = td.tm_year + 1900; 350 | int bcd_year = Int3Bcd(year); 351 | 352 | int days = td.tm_mday; 353 | for (int i = 0; i < td.tm_mon; ++i) { // td.tm_mon starts from 0 354 | days += days_of_month[i]; 355 | } 356 | if ((td.tm_mon >= 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) { 357 | ++days; 358 | } 359 | int bcd_days = Int3Bcd(days); 360 | 361 | int day_of_week = td.tm_wday; 362 | 363 | int tco; 364 | switch (td.tm_sec) { 365 | case 0: tco = marker; break; 366 | case 1: tco = bcd_minute & 0x40; break; 367 | case 2: tco = bcd_minute & 0x20; break; 368 | case 3: tco = bcd_minute & 0x10; break; 369 | case 4: tco = 0; break; 370 | case 5: tco = bcd_minute & 0x08; break; 371 | case 6: tco = bcd_minute & 0x04; break; 372 | case 7: tco = bcd_minute & 0x02; break; 373 | case 8: tco = bcd_minute & 0x01; break; 374 | case 9: tco = marker; break; 375 | 376 | case 10: tco = 0; break; 377 | case 11: tco = 0; break; 378 | case 12: tco = bcd_hour & 0x20; break; 379 | case 13: tco = bcd_hour & 0x10; break; 380 | case 14: tco = 0; break; 381 | case 15: tco = bcd_hour & 0x08; break; 382 | case 16: tco = bcd_hour & 0x04; break; 383 | case 17: tco = bcd_hour & 0x02; break; 384 | case 18: tco = bcd_hour & 0x01; break; 385 | case 19: tco = marker; break; 386 | 387 | case 20: tco = 0; break; 388 | case 21: tco = 0; break; 389 | case 22: tco = bcd_days & 0x200; break; 390 | case 23: tco = bcd_days & 0x100; break; 391 | case 24: tco = 0; break; 392 | case 25: tco = bcd_days & 0x080; break; 393 | case 26: tco = bcd_days & 0x040; break; 394 | case 27: tco = bcd_days & 0x020; break; 395 | case 28: tco = bcd_days & 0x010; break; 396 | case 29: tco = marker; break; 397 | 398 | case 30: tco = bcd_days & 0x008; break; 399 | case 31: tco = bcd_days & 0x004; break; 400 | case 32: tco = bcd_days & 0x002; break; 401 | case 33: tco = bcd_days & 0x001; break; 402 | case 34: tco = 0; break; 403 | case 35: tco = 0; break; 404 | case 36: tco = parity_bcd_hour; break; 405 | case 37: tco = parity_bcd_minute; break; 406 | case 38: tco = 0; break; 407 | case 39: tco = marker; break; 408 | 409 | case 40: tco = 0; break; 410 | case 41: tco = bcd_year & 0x80; break; 411 | case 42: tco = bcd_year & 0x40; break; 412 | case 43: tco = bcd_year & 0x20; break; 413 | case 44: tco = bcd_year & 0x10; break; 414 | case 45: tco = bcd_year & 0x08; break; 415 | case 46: tco = bcd_year & 0x04; break; 416 | case 47: tco = bcd_year & 0x02; break; 417 | case 48: tco = bcd_year & 0x01; break; 418 | case 49: tco = marker; break; 419 | 420 | case 50: tco = day_of_week & 0x04; break; 421 | case 51: tco = day_of_week & 0x02; break; 422 | case 52: tco = day_of_week & 0x01; break; 423 | case 53: tco = 0; break; 424 | case 54: tco = 0; break; 425 | case 55: tco = 0; break; 426 | case 56: tco = 0; break; 427 | case 57: tco = 0; break; 428 | case 58: tco = 0; break; 429 | case 59: tco = marker; break; 430 | default: tco = 0; break; 431 | } 432 | return tco; 433 | } 434 | 435 | int Int3Bcd(int a) 436 | { 437 | return (a % 10) + (a / 10 % 10 * 16) + (a / 100 % 10 * 256); 438 | } 439 | 440 | int Parity8(int a) 441 | { 442 | int pa = a; 443 | for (int i = 1; i < 8; ++i) { 444 | pa += a >> i; 445 | } 446 | return pa % 2; 447 | } 448 | 449 | //..:....1....:....2....:....3....:....4....:....5....:....6....:....7.. 450 | // main 451 | const unsigned int loop_period_ms(100); 452 | unsigned int loop_last_ms; 453 | const int button_atom(39); // GPIO39 454 | const int button_atoms3(41); // GPIO41 455 | 456 | void setup() 457 | { 458 | if (M5.getBoard() == m5::board_t::board_M5Atom) { 459 | // To avoid Wi-Fi issues, force GPIO0 to 0 while CH552 outputs 5V with its internal pullup. 460 | // https://twitter.com/wakwak_koba/status/1553162622479974400 461 | // https://www.facebook.com/groups/154504605228235/posts/699719300706760/ 462 | pinMode(0, OUTPUT); 463 | digitalWrite(0, LOW); 464 | } 465 | 466 | // M5Unified 467 | auto cfg = M5.config(); 468 | cfg.serial_baudrate = 115200; // default=0. use Serial.print(). 469 | cfg.external_rtc = true; // default=false. use Unit RTC. 470 | M5.begin(cfg); 471 | delay(3000); 472 | Serial.println(); 473 | 474 | // FastLED 475 | #if defined (CONFIG_IDF_TARGET_ESP32S3) // FastLED requires strict constant for the pin number 476 | FastLED.addLeds(leds, led_num); 477 | Serial.println("FastLED add LEDs for ATOMS3 Lite"); 478 | #else 479 | FastLED.addLeds(leds, led_num); 480 | Serial.println("FastLED add LEDs for M5Atom Lite/Matrix"); 481 | #endif 482 | FastLED.setBrightness(led_brightness); 483 | for (int i = 0; i < led_num; ++i) { 484 | leds[i] = 0; 485 | } 486 | 487 | // Unit RTC (External) 488 | if (rtcx.Begin(Wire) == 0) { 489 | rtcx_available = true; 490 | if (SetTimeFromRtcx(time_zone)) { 491 | localtime_valid = true; 492 | } 493 | } 494 | if (!localtime_valid) { 495 | Serial.print("RTC not valid: set the localtime temporarily\n"); 496 | td.tm_year = 117; // 2017 > 2016, getLocalTime() returns true 497 | td.tm_mon = 0; // January 498 | td.tm_mday = 1; 499 | td.tm_hour = 0; 500 | td.tm_min = 0; 501 | td.tm_sec = 0; 502 | struct timeval tv = { mktime(&td), 0 }; 503 | settimeofday(&tv, NULL); 504 | } 505 | getLocalTime(&td); 506 | Serial.print(&td, "localtime: %A, %B %d %Y %H:%M:%S\n"); 507 | // print sample: must be < 64 508 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 509 | //localtime: Wednesday, September 11 2021 11:10:46 510 | 511 | // WiFi start 512 | WiFiManager wm; // blocking mode only 513 | 514 | // erase SSID/Key to force rewrite 515 | int button_pin = button_atom; 516 | if (M5.getBoard() == m5::board_t::board_M5AtomS3Lite) { 517 | button_pin = button_atoms3; 518 | } 519 | if (digitalRead(button_pin) == LOW) { 520 | wm.resetSettings(); 521 | } 522 | 523 | // WiFi connect 524 | wm.setConfigPortalTimeout(wifi_config_portal_timeout_sec); 525 | wm.setAPCallback(WifiConfigModeCallback); 526 | wm.autoConnect(); 527 | WiFi.setSleep(false); // https://macsbug.wordpress.com/2021/05/02/buttona-on-m5stack-does-not-work-properly/ 528 | wifi_retry_last_ms = millis() - wifi_retry_interval_ms; 529 | 530 | // NTP start 531 | NtpBegin(time_zone, ntp_server); 532 | 533 | // TCO start 534 | TcoInit(); 535 | 536 | // clear button of erase SSID/Key 537 | M5.update(); 538 | 539 | // loop control 540 | loop_last_ms = millis(); 541 | } 542 | 543 | void loop() 544 | { 545 | M5.update(); 546 | LedShow(); 547 | 548 | WifiCheck(); 549 | if (RtcxUpdate(rtcx_available)) { 550 | localtime_valid = true; // SNTP sync completed 551 | }; 552 | 553 | // button: TCO monitor on/off 554 | if (M5.BtnA.wasReleased()) { 555 | led_enable = !led_enable; 556 | } 557 | 558 | // loop control 559 | unsigned int delay_ms(0); 560 | unsigned int elapse_ms = millis() - loop_last_ms; 561 | if (elapse_ms < loop_period_ms) { 562 | delay_ms = loop_period_ms - elapse_ms; 563 | } 564 | delay(delay_ms); 565 | loop_last_ms = millis(); 566 | // Serial.printf("loop elapse = %dms\n", elapse_ms); // for monitoring elapsed time 567 | } 568 | -------------------------------------------------------------------------------- /BF-018ARev4/BF_Pcf8563.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | // 4 | #include 5 | #include "BF_Pcf8563.h" 6 | 7 | int Pcf8563::Begin(TwoWire &wire) 8 | { 9 | m_wire = &wire; 10 | 11 | // clear registers 12 | external_clock_test_mode = false; 13 | source_clock_stoped = false; 14 | power_on_reset_override = true; 15 | if (WriteControl() != 0) return 1; 16 | 17 | clock_out_active =false; 18 | clock_out = fco_32768Hz; 19 | if (WriteClockOut() != 0) return 1; 20 | 21 | alarm_minute_enable = false; alarm_minute = 0; 22 | alarm_hour_enable = false; alarm_hour = 0; 23 | alarm_day_enable = false; alarm_day = 0; 24 | alarm_weekday_enable = false; alarm_weekday = 0; 25 | if (WriteAlarm() != 0) return 1; 26 | 27 | timer_enable = false; 28 | timer_source = fts_4096Hz; 29 | timer = 0; 30 | if (WriteTimer() != 0) return 1; 31 | 32 | timer_interrupt_pulse_mode = false; 33 | alarm_flag_active = false; 34 | timer_flag_active = false; 35 | alarm_interrupt_enable = false; 36 | timer_interrupt_enable = false; 37 | if (WriteInterrupt() != 0) return 1; 38 | 39 | return 0; 40 | } 41 | 42 | int Pcf8563::ReadTime(struct tm *tm_now) 43 | { 44 | if (ReadReg(0x02, 7) != 7) return 1; // ReadReg error 45 | if (m_reg[0x02] & 0x80) return 2; // invalid time 46 | tm_now->tm_sec = Bcd2Int(m_reg[0x02] & 0x7f); 47 | tm_now->tm_min = Bcd2Int(m_reg[0x03] & 0x7f); 48 | tm_now->tm_hour = Bcd2Int(m_reg[0x04] & 0x3f); 49 | tm_now->tm_mday = Bcd2Int(m_reg[0x05] & 0x3f); 50 | tm_now->tm_wday = Bcd2Int(m_reg[0x06] & 0x07); // 0:Sun, 1:Mon, .. 6:Sat 51 | tm_now->tm_mon = Bcd2Int(m_reg[0x07] & 0x1f) - 1; // tm month: 0..11 52 | tm_now->tm_year = Bcd2Int(m_reg[0x08] & 0xff); 53 | if ((m_reg[0x07] & 0x80) != 0) 54 | tm_now->tm_year += 100; // century bit 55 | return 0; 56 | } 57 | 58 | int Pcf8563::WriteTime(struct tm *tm_now) 59 | { 60 | m_reg[0x02] = Int2Bcd(tm_now->tm_sec); 61 | m_reg[0x03] = Int2Bcd(tm_now->tm_min); 62 | m_reg[0x04] = Int2Bcd(tm_now->tm_hour); 63 | m_reg[0x05] = Int2Bcd(tm_now->tm_mday); 64 | m_reg[0x06] = Int2Bcd(tm_now->tm_wday); // 0:Sun, 1:Mon, .. 6:Sat 65 | m_reg[0x07] = Int2Bcd(tm_now->tm_mon + 1); // rtc month: 1..12 66 | m_reg[0x08] = Int2Bcd(tm_now->tm_year % 100); 67 | if (tm_now->tm_year >= 100) 68 | m_reg[0x07] |= 0x80; // century bit 69 | return WriteReg(0x02, 7); 70 | } 71 | 72 | // to enable alarm, give valid value for arguments of minute, hour, day, weekday 73 | int Pcf8563::SetAlarm(int minute, int hour, int day, int weekday) 74 | { 75 | alarm_minute_enable = false; 76 | alarm_hour_enable = false; 77 | alarm_day_enable = false; 78 | alarm_weekday_enable = false; 79 | if (minute >= 0 && minute <= 59) { alarm_minute_enable = true; alarm_minute = minute; } 80 | if (hour >= 0 && hour <= 23) { alarm_hour_enable = true; alarm_hour = hour; } 81 | if (day >= 1 && day <= 31) { alarm_day_enable = true; alarm_day = day; } 82 | if (weekday >= 0 && weekday <= 6) { alarm_weekday_enable = true; alarm_weekday = weekday; } 83 | return WriteAlarm(); 84 | } 85 | 86 | int Pcf8563::DisableAlarm() 87 | { 88 | return SetAlarm(); 89 | } 90 | 91 | // interrupt_en = true: enable, false: disable 92 | int Pcf8563::EnableAlarmInterrupt(bool enable_interrupt, bool flag_keep) 93 | { 94 | if (ReadInterrupt() != 0) return 1; 95 | alarm_flag_active = flag_keep; 96 | alarm_interrupt_enable = enable_interrupt; 97 | return WriteInterrupt(); 98 | } 99 | 100 | int Pcf8563::DisableAlarmInterrupt() 101 | { 102 | return EnableAlarmInterrupt(false); // disable and clear 103 | } 104 | 105 | // Set timer_source and timer to the argument timer_sec as close as possible 106 | // returns actual timer value realized by timer_source and timer, 107 | // return 0.0: timer_sec is out of range, the timer was not set 108 | // return <>0.0: The timer is enabled. 109 | double Pcf8563::SetTimer(double timer_sec) 110 | { 111 | if (timer_sec < 1.0 / 4096.0) return 0.0; // minimum 0.00024414s 112 | if (timer_sec > 255.0 * 60.0) return 0.0; // maximum 15,300s 113 | 114 | if (timer_sec >= 240.0) { // cross over at 240s not 255s 115 | timer_source = fts_1_60th_Hz; 116 | timer = timer_sec / 60.0 + 0.5; // +0.5 for rounding 117 | } 118 | else if (timer_sec > 255.0 / 64.0) { 119 | timer_source = fts_1Hz; 120 | timer = timer_sec + 0.5; // +0.5 for rounding 121 | } 122 | else if (timer_sec > 255.0 / 4096.0) { 123 | timer_source = fts_64Hz; 124 | timer = timer_sec * 64.0 + 0.5; // +0.5 for rounding 125 | } 126 | else { 127 | timer_source = fts_4096Hz; 128 | timer = timer_sec * 4096.0 + 0.5; // +0.5 for rounding 129 | } 130 | timer_enable = true; 131 | if (WriteTimer() != 0) return 0.0; 132 | 133 | switch (timer_source) { 134 | case 0: return timer / 4096.0; break; 135 | case 1: return timer / 64.0; break; 136 | case 2: return timer / 1.0; break; 137 | case 3: return timer * 60.0; break; 138 | default: return 0.0; break; 139 | } 140 | } 141 | 142 | // timer_en = true: enable, false: disable 143 | int Pcf8563::EnableTimer(bool enable_timer) 144 | { 145 | if (ReadReg(0x0e, 1) != 1) return 1; 146 | switch (m_reg[0x0e] & 0x03) { 147 | case 0: timer_source = fts_4096Hz; break; 148 | case 1: timer_source = fts_64Hz; break; 149 | case 2: timer_source = fts_1Hz; break; 150 | case 3: timer_source = fts_1_60th_Hz; break; 151 | default: break; 152 | } 153 | timer_enable = enable_timer; 154 | m_reg[0x0e] = timer_source; 155 | if (timer_enable) 156 | m_reg[0x0e] |= 0x80; 157 | return WriteReg(0x0e, 1); 158 | } 159 | 160 | int Pcf8563::DisableTimer() 161 | { 162 | return EnableTimer(false); 163 | } 164 | 165 | // interrupt_enable = true: enable, false: disable 166 | int Pcf8563::EnableTimerInterrupt(bool enable_interrupt, bool pulse_mode, bool keep_flag) 167 | { 168 | if (ReadInterrupt() != 0) return 1; 169 | timer_interrupt_pulse_mode = pulse_mode; 170 | timer_flag_active = keep_flag; 171 | timer_interrupt_enable = enable_interrupt; 172 | if (WriteInterrupt() != 0) return 1; 173 | return 0; 174 | } 175 | 176 | int Pcf8563::DisableTimerInterrupt() 177 | { 178 | return EnableTimerInterrupt(false); // disable and clear 179 | } 180 | 181 | int Pcf8563::GetInterrupt() 182 | { 183 | if (ReadInterrupt() != 0) return 4; 184 | int flag_got(0); 185 | if (alarm_flag_active) { flag_got |= 0x02; alarm_flag_active = false; } 186 | if (timer_flag_active) { flag_got |= 0x01; timer_flag_active = false; } 187 | if (flag_got != 0) 188 | if (WriteInterrupt() != 0) return 4; 189 | return flag_got; 190 | } 191 | 192 | int Pcf8563::ClockOutForTrimmer(bool enable_clko) // clock out 1Hz 193 | { 194 | if (enable_clko) { 195 | // CLKO(clock out) to adjust trimmer 196 | clock_out = fco_1Hz; 197 | clock_out_active = true; 198 | if (WriteClockOut() != 0) return 1; 199 | } 200 | else { 201 | clock_out_active = false; 202 | if (WriteClockOut() != 0) return 1; 203 | } 204 | return 0; 205 | } 206 | 207 | Pcf8563::Pcf8563() 208 | { 209 | m_reg = new uint8_t[m_reg_size]; 210 | for (int i = 0; i < m_reg_size; ++i) 211 | m_reg[i] = 0; 212 | } 213 | 214 | Pcf8563::~Pcf8563() 215 | { 216 | delete [] m_reg; 217 | } 218 | 219 | int Pcf8563::ReadControl() 220 | { 221 | if (ReadReg(0x00, 1) != 1) return 1; 222 | external_clock_test_mode = m_reg[0x00] & 0x80; 223 | source_clock_stoped = m_reg[0x00] & 0x20; 224 | power_on_reset_override = m_reg[0x00] & 0x08; 225 | return 0; 226 | } 227 | 228 | int Pcf8563::WriteControl() 229 | { 230 | m_reg[0x00] = 0x00; 231 | if (external_clock_test_mode) m_reg[0x00] |= 0x80; 232 | if (source_clock_stoped ) m_reg[0x00] |= 0x20; 233 | if (power_on_reset_override ) m_reg[0x00] |= 0x08; 234 | return WriteReg(0x00, 1); 235 | } 236 | 237 | int Pcf8563::ReadInterrupt() 238 | { 239 | if (ReadReg(0x01, 1) != 1) return 1; 240 | timer_interrupt_pulse_mode = m_reg[0x01] & 0x10; 241 | alarm_flag_active = m_reg[0x01] & 0x08; 242 | timer_flag_active = m_reg[0x01] & 0x04; 243 | alarm_interrupt_enable = m_reg[0x01] & 0x02; 244 | timer_interrupt_enable = m_reg[0x01] & 0x01; 245 | return 0; 246 | } 247 | 248 | int Pcf8563::WriteInterrupt() 249 | { 250 | m_reg[0x01] = 0x00; 251 | if (timer_interrupt_pulse_mode) m_reg[0x01] |= 0x10; 252 | if (alarm_flag_active ) m_reg[0x01] |= 0x08; 253 | if (timer_flag_active ) m_reg[0x01] |= 0x04; 254 | if (alarm_interrupt_enable ) m_reg[0x01] |= 0x02; 255 | if (timer_interrupt_enable ) m_reg[0x01] |= 0x01; 256 | return WriteReg(0x01, 1); 257 | } 258 | 259 | int Pcf8563::ReadAlarm() 260 | { 261 | if (ReadReg(0x09, 4) != 4) return 1; 262 | alarm_minute_enable = (m_reg[0x09] & 0x80) == 0; 263 | alarm_hour_enable = (m_reg[0x0a] & 0x80) == 0; 264 | alarm_day_enable = (m_reg[0x0b] & 0x80) == 0; 265 | alarm_weekday_enable = (m_reg[0x0c] & 0x80) == 0; 266 | alarm_minute = Bcd2Int(m_reg[0x09] & 0x7f); 267 | alarm_hour = Bcd2Int(m_reg[0x0a] & 0x3f); 268 | alarm_day = Bcd2Int(m_reg[0x0b] & 0x3f); 269 | alarm_weekday = Bcd2Int(m_reg[0x0c] & 0x07); 270 | return 0; 271 | } 272 | 273 | int Pcf8563::WriteAlarm() 274 | { 275 | m_reg[0x09] = Int2Bcd(alarm_minute ); 276 | m_reg[0x0a] = Int2Bcd(alarm_hour ); 277 | m_reg[0x0b] = Int2Bcd(alarm_day ); 278 | m_reg[0x0c] = Int2Bcd(alarm_weekday); 279 | if (!alarm_minute_enable ) m_reg[0x09] |= 0x80; 280 | if (!alarm_hour_enable ) m_reg[0x0a] |= 0x80; 281 | if (!alarm_day_enable ) m_reg[0x0b] |= 0x80; 282 | if (!alarm_weekday_enable) m_reg[0x0c] |= 0x80; 283 | return WriteReg(0x09, 4); 284 | } 285 | 286 | int Pcf8563::ReadClockOut() 287 | { 288 | if (ReadReg(0x0d, 1) != 1) return 1; 289 | clock_out_active = m_reg[0x0d] & 0x80; 290 | switch (m_reg[0x0d] & 0x03) { 291 | case 0: clock_out = fco_32768Hz; break; 292 | case 1: clock_out = fco_1024Hz; break; 293 | case 2: clock_out = fco_32Hz; break; 294 | case 3: clock_out = fco_1Hz; break; 295 | default: break; 296 | } 297 | return 0; 298 | } 299 | 300 | int Pcf8563::WriteClockOut() 301 | { 302 | m_reg[0x0d] = clock_out; 303 | if (clock_out_active) 304 | m_reg[0x0d] |= 0x80; 305 | return WriteReg(0x0d, 1); 306 | } 307 | 308 | int Pcf8563::ReadTimer() 309 | { 310 | if (ReadReg(0x0e, 2) != 2) return 1; 311 | timer_enable = m_reg[0x0e] & 0x80; 312 | switch (m_reg[0x0e] & 0x03) { 313 | case 0: timer_source = fts_4096Hz; break; 314 | case 1: timer_source = fts_64Hz; break; 315 | case 2: timer_source = fts_1Hz; break; 316 | case 3: timer_source = fts_1_60th_Hz; break; 317 | default: break; 318 | } 319 | timer = m_reg[0x0f]; 320 | return 0; 321 | } 322 | 323 | int Pcf8563::WriteTimer() 324 | { 325 | m_reg[0x0e] = timer_source; 326 | if (timer_enable) 327 | m_reg[0x0e] |= 0x80; 328 | m_reg[0x0f] = timer; 329 | return WriteReg(0x0e, 2); 330 | } 331 | 332 | int Pcf8563::Int2Bcd(int int_num) 333 | { 334 | return int_num / 10 * 16 + int_num % 10; 335 | } 336 | 337 | int Pcf8563::Bcd2Int(int bcd_num) 338 | { 339 | return bcd_num / 16 * 10 + bcd_num % 16; 340 | } 341 | 342 | size_t Pcf8563::ReadReg(int reg_start, size_t read_length) 343 | { 344 | const bool send_stop(true); 345 | 346 | m_wire->beginTransmission(m_i2c_address); 347 | m_wire->write(reg_start); 348 | if (m_wire->endTransmission(!send_stop) != 0) { 349 | Serial.print("[PCF8563]ERROR ReadReg write\n"); 350 | return 0; // command error 351 | } 352 | m_wire->requestFrom(m_i2c_address, read_length); 353 | size_t i = 0; 354 | while (m_wire->available()) 355 | m_reg[reg_start + i++] = m_wire->read(); 356 | if (i != read_length) 357 | Serial.print("[PCF8563]ERROR ReadReg read\n"); 358 | return i; 359 | } 360 | 361 | int Pcf8563::WriteReg(int reg_start, size_t write_length) 362 | { 363 | m_wire->beginTransmission(m_i2c_address); 364 | m_wire->write(reg_start); 365 | for (int i = 0; i < write_length; ++i) 366 | m_wire->write(m_reg[reg_start + i]); 367 | int return_code = m_wire->endTransmission(); 368 | if (return_code != 0) 369 | Serial.print("[PCF8563]ERROR WriteReg\n"); 370 | return return_code; 371 | } 372 | 373 | // the instance "rtc external" 374 | Pcf8563 rtcx; 375 | -------------------------------------------------------------------------------- /BF-018ARev4/BF_Pcf8563.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // PCF8563 RTC for M5 Series 3 | // 4 | #pragma once 5 | #include 6 | #include // for struct tm 7 | 8 | class Pcf8563 { 9 | public: 10 | int Begin(TwoWire &wire); 11 | int ReadTime(struct tm *tm_now); 12 | int WriteTime(struct tm *tm_now); 13 | 14 | int SetAlarm(int miniute = -1, int hour = -1, int day = -1, int weekday = -1); 15 | int DisableAlarm(); 16 | int EnableAlarmInterrupt(bool enable_interrupt = true, bool keep_flag = false); 17 | int DisableAlarmInterrupt(); 18 | 19 | double SetTimer(double timer_sec); 20 | int EnableTimer(bool enable_timer = true); 21 | int DisableTimer(); 22 | int EnableTimerInterrupt(bool enable_interrupt = true, bool pulse_mode = false, bool keep_flag = false); 23 | int DisableTimerInterrupt(); 24 | 25 | int GetInterrupt(); // check interrupt and clear 26 | 27 | int ClockOutForTrimmer(bool enable_clko = true); // clock out 1Hz 28 | 29 | bool external_clock_test_mode; // 00h Control_status_1 bit 7 TEST1 30 | bool source_clock_stoped; // 00h Control_status_1 bit 5 STOP 31 | bool power_on_reset_override; // 00h Control_status_1 bit 3 TESTC 32 | 33 | bool timer_interrupt_pulse_mode; // 01h Control_status_2 bit 4 TI_TP 34 | bool alarm_flag_active; // 01h Control_status_2 bit 3 AF 35 | bool timer_flag_active; // 01h Control_status_2 bit 2 TF 36 | bool alarm_interrupt_enable; // 01h Control_status_2 bit 1 AIE 37 | bool timer_interrupt_enable; // 01h Control_status_2 bit 0 TIE 38 | 39 | bool alarm_minute_enable; // 09h Minute_alarm bit 7 !AE_M 40 | int alarm_minute; // 09h Minute_alarm 0..59 41 | bool alarm_hour_enable; // 0Ah Hour_alarm bit 7 !AE_H 42 | int alarm_hour; // 0Ah Hour_alarm 0..23 43 | bool alarm_day_enable; // 0Bh Day_alarm bit 7 !AE_D 44 | int alarm_day; // 0Bh Day_alarm 1..31 45 | bool alarm_weekday_enable; // 0Ch Weekday_alarm bit 7 !AE_W 46 | int alarm_weekday; // 0Ch Weekday_alarm 0..6 47 | 48 | enum FreqClockOut_t { // 0Dh Clock_out_control FD 0..3 49 | fco_32768Hz, 50 | fco_1024Hz, 51 | fco_32Hz, 52 | fco_1Hz, 53 | }; 54 | 55 | bool clock_out_active; // 0Dh Clock_out_control bit 7 FE 56 | FreqClockOut_t clock_out; // 0Dh Clock_out_control 0..3 57 | 58 | enum FreqTimerSource_t { // 0Eh Timer_control TD 0..3 59 | fts_4096Hz, 60 | fts_64Hz, 61 | fts_1Hz, 62 | fts_1_60th_Hz, // (1/60)Hz 63 | }; 64 | 65 | bool timer_enable; // 0Eh Timer_control bit 7 TE 66 | FreqTimerSource_t timer_source; // 0Eh Timer_control 0..3 67 | int timer; // 0Fh Timer 0..255 68 | 69 | Pcf8563(); 70 | ~Pcf8563(); 71 | 72 | int ReadControl(); 73 | int WriteControl(); 74 | int ReadInterrupt(); 75 | int WriteInterrupt(); 76 | int ReadAlarm(); 77 | int WriteAlarm(); 78 | int ReadClockOut(); 79 | int WriteClockOut(); 80 | int ReadTimer(); 81 | int WriteTimer(); 82 | 83 | private: 84 | const int m_i2c_address = 0x51; 85 | const int m_reg_size = 0x10; 86 | 87 | TwoWire *m_wire; 88 | uint8_t *m_reg; 89 | 90 | size_t ReadReg(int reg_start, size_t read_length); 91 | int WriteReg(int reg_start, size_t write_length); 92 | int Int2Bcd(int int_num); 93 | int Bcd2Int(int bcd_num); 94 | }; 95 | 96 | // an instance "rtc external" 97 | extern Pcf8563 rtcx; 98 | -------------------------------------------------------------------------------- /BF-018ARev4/BF_RtcxNtp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | // 4 | #include 5 | #include "BF_Pcf8563.h" 6 | #include "BF_RtcxNtp.h" 7 | 8 | bool sntp_sync_status_complete(false); 9 | 10 | void NtpBegin(const char* time_zone, const char* ntp_server) 11 | { 12 | configTzTime(time_zone, ntp_server); 13 | Serial.printf("NtpBegin: config TZ time = %s\n", time_zone); 14 | Serial.printf("NtpBegin: SNTP sync mode = %d (0:IMMED 1:SMOOTH)\n", sntp_get_sync_mode()); 15 | Serial.printf("NtpBegin: SNTP sync interval = %dms\n", sntp_get_sync_interval()); 16 | sntp_sync_status_complete = false; 17 | sntp_set_time_sync_notification_cb(SntpTimeSyncNotificationCallback); 18 | } 19 | 20 | void SntpTimeSyncNotificationCallback(struct timeval *tv) 21 | { 22 | sntp_sync_status_t sntp_sync_status = sntp_get_sync_status(); 23 | PrintSntpStatus("SNTP callback:", sntp_sync_status); 24 | if (sntp_sync_status == SNTP_SYNC_STATUS_COMPLETED) { 25 | sntp_sync_status_complete = true; 26 | } 27 | } 28 | 29 | bool RtcxUpdate(bool rtcx_avail) 30 | { 31 | if (sntp_sync_status_complete) { 32 | sntp_sync_status_complete = false; 33 | 34 | struct tm tm_sync; 35 | getLocalTime(&tm_sync); 36 | Serial.print(&tm_sync, "SNTP sync: %A, %B %d %Y %H:%M:%S\n"); 37 | // print sample: must be < 64 38 | //....:....1....:....2....:....3....:....4....:....5....:....6.... 39 | //SNTP sync: Wednesday, September 11 2021 11:10:46 40 | 41 | if (rtcx_avail) { 42 | if (rtcx.WriteTime(&tm_sync) == 0) { 43 | Serial.print("RTCx updated\n"); 44 | } 45 | else { 46 | Serial.print("RTCx update failed\n"); 47 | } 48 | } 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status) 55 | { 56 | static const char* sntp_sync_status_str[] = { 57 | "SNTP_SYNC_STATUS_RESET ", // 0 58 | "SNTP_SYNC_STATUS_COMPLETED ", // 1 59 | "SNTP_SYNC_STATUS_IN_PROGRESS ", // 2 60 | "sntp_sync_status invalid ", // 3 61 | }; 62 | int sntp_sync_status_index = 3; 63 | if (sntp_sync_status >= 0 && sntp_sync_status <= 2) { 64 | sntp_sync_status_index = sntp_sync_status; 65 | } 66 | Serial.printf("%s status = %d %s\n", header, sntp_sync_status, sntp_sync_status_str[sntp_sync_status_index]); 67 | } 68 | 69 | bool SetTimeFromRtcx(const char* time_zone) 70 | { 71 | bool rtcx_valid(false); 72 | struct tm tm_init; 73 | 74 | setenv("TZ", time_zone, 1); 75 | tzset(); // assign timezone with setenv for mktime() 76 | 77 | if (rtcx.ReadTime(&tm_init) == 0) { 78 | rtcx_valid = true; 79 | struct timeval tv = { mktime(&tm_init), 0 }; 80 | settimeofday(&tv, NULL); 81 | Serial.print("RTCx valid, the localtime was set\n"); 82 | } else { 83 | Serial.print("RTCx not valid\n"); 84 | } 85 | return rtcx_valid; 86 | } 87 | -------------------------------------------------------------------------------- /BF-018ARev4/BF_RtcxNtp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 BotanicFields, Inc. 2 | // for RTCx(PCf8563) and NTP 3 | // 4 | #pragma once 5 | #include // for struct timeval 6 | #include // for sntp_sync_status 7 | 8 | // examples 9 | // const char* time_zone = "JST-9"; 10 | // const char* ntp_server = "pool.ntp.org"; 11 | void NtpBegin(const char* time_zone, const char* ntp_server); 12 | void SntpTimeSyncNotificationCallback(struct timeval *tv); 13 | bool RtcxUpdate(bool rtcx_avail = true); 14 | void PrintSntpStatus(const char* header, sntp_sync_status_t sntp_sync_status); 15 | bool SetTimeFromRtcx(const char* time_zone); 16 | -------------------------------------------------------------------------------- /BF-018A_M5AtomLite.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/BF-018A_M5AtomLite.JPG -------------------------------------------------------------------------------- /BF-018A_M5AtomMatrix.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/BF-018A_M5AtomMatrix.JPG -------------------------------------------------------------------------------- /BF-018A_back.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/BF-018A_back.JPG -------------------------------------------------------------------------------- /BF-018A_front.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/BF-018A_front.JPG -------------------------------------------------------------------------------- /BF-018a_DOCv4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/BF-018a_DOCv4.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 BotanicFields, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PCBV01L02/gerber/JLCPCB/bf-018a_bom.csv: -------------------------------------------------------------------------------- 1 | Comment,Designator,Footprint,LCSC 2 | 100u,C1,Capacitor_SMD:CP_Elec_6.3x7.7,C176674 3 | 100n,"C2,C8",Capacitor_SMD:C_0603_1608Metric,C14663 4 | 10n,"C3,C4,C5,C7",Capacitor_SMD:C_0603_1608Metric,C57112 5 | 10u,C6,Capacitor_SMD:C_0603_1608Metric,C19702 6 | 1k,"R1,R2",Resistor_SMD:R_0603_1608Metric,C21190 7 | 51k,"R3,R4,R6",Resistor_SMD:R_0603_1608Metric,C23196 8 | 10k,"R5,R7",Resistor_SMD:R_0603_1608Metric,C25804 9 | 510,R8,Resistor_SMD:R_1206_3216Metric,C17991 10 | MCP6022,U1,Package_SO:SOP-8_3.9x4.9mm_P1.27mm,C57639 11 | -------------------------------------------------------------------------------- /PCBV01L02/gerber/JLCPCB/bf-018a_panel2-top-pos.csv: -------------------------------------------------------------------------------- 1 | Designator,Val,Package,Mid X,Mid Y,Rotation,Layer 2 | C1,100u,CP_Elec_6.3x7.7,61.595,38.895,270,top 3 | C1,100u,CP_Elec_6.3x7.7,61.595,78.265,270,top 4 | C2,100n,C_0603_1608Metric,47.625,41.91,0,top 5 | C2,100n,C_0603_1608Metric,47.625,81.28,0,top 6 | C3,10n,C_0603_1608Metric,55.88,73.66,180,top 7 | C3,10n,C_0603_1608Metric,55.88,34.29,180,top 8 | C4,10n,C_0603_1608Metric,55.88,36.195,180,top 9 | C4,10n,C_0603_1608Metric,55.88,75.565,180,top 10 | C5,10n,C_0603_1608Metric,55.88,38.1,0,top 11 | C5,10n,C_0603_1608Metric,55.88,77.47,0,top 12 | C6,10u,C_0603_1608Metric,46.99,73.025,180,top 13 | C6,10u,C_0603_1608Metric,46.99,33.655,180,top 14 | C7,10n,C_0603_1608Metric,55.88,40.005,180,top 15 | C7,10n,C_0603_1608Metric,55.88,79.375,180,top 16 | C8,100n,C_0603_1608Metric,47.752,23.876,90,top 17 | C8,100n,C_0603_1608Metric,47.752,63.246,90,top 18 | R1,1k,R_0603_1608Metric,52.07,34.29,180,top 19 | R1,1k,R_0603_1608Metric,52.07,73.66,180,top 20 | R2,1k,R_0603_1608Metric,52.07,75.565,180,top 21 | R2,1k,R_0603_1608Metric,52.07,36.195,180,top 22 | R3,51k,R_0603_1608Metric,46.99,67.31,0,top 23 | R3,51k,R_0603_1608Metric,46.99,27.94,0,top 24 | R4,51k,R_0603_1608Metric,46.99,69.215,180,top 25 | R4,51k,R_0603_1608Metric,46.99,29.845,180,top 26 | R5,10k,R_0603_1608Metric,46.99,31.75,0,top 27 | R5,10k,R_0603_1608Metric,46.99,71.12,0,top 28 | R6,51k,R_0603_1608Metric,52.07,79.375,180,top 29 | R6,51k,R_0603_1608Metric,52.07,40.005,180,top 30 | R7,10k,R_0603_1608Metric,52.07,38.1,180,top 31 | R7,10k,R_0603_1608Metric,52.07,77.47,180,top 32 | R8,510,R_1206_3216Metric,45.72,63.878,90,top 33 | R8,510,R_1206_3216Metric,45.72,24.508,90,top 34 | U1,MCP6022,SOP-8_3.9x4.9mm_P1.27mm,45.72,38.1,270,top 35 | U1,MCP6022,SOP-8_3.9x4.9mm_P1.27mm,45.72,77.47,270,top 36 | -------------------------------------------------------------------------------- /PCBV01L02/gerber/JLCPCB/bf-018al02.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/PCBV01L02/gerber/JLCPCB/bf-018al02.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JJY Simulator for M5Atom 2 | ## ATOMS3 Lite, ATOM Lite/Matrixで動作する標準電波(JJY)シミュレータ 3 | ※ ATOMS3(無印)には対応しておりません。 4 | 5 | ### 2025/8/15 修正 6 | - Rev.4を追加しました。M5Stack 3.x(Boards Manager)に対応しました。Rev.4はATOMS3 Liteで使用いただけます。 7 | - BF-018ARev3.inoを修正しました。M5Atom Lite/Matrixの判定を改善しました。 8 | ### 2025/3/03 修正 9 | - BF-018ARev3.ino を修正しました。M5Unifiedの仕様変更に対応しcfg.serial_baudrateの指定を追加しました。 10 | ### 2024/3/20 修正 11 | - BF-018ARev3.ino を修正しました。コンパイラ警告レベルをデフォルトよりも上げると、Serial.printf()の%d指定でエラーを検出していました。 12 | ### 2024/3/12 修正 13 | - BF-018ARev3にアップデートしました。 14 | 15 | ### Rev.4 16 | - フォルダ: BF-018ARev4 17 | - Boards Managerで、M5Stack 3.xを選択してください。 18 | - ATOMS3 Liteで使用いただけます。 19 | - M5Atom Lite/Matrixは、現状LEDが点灯しない不具合がありますので、Rev.3をお使いください。 20 | 21 | 動作確認状況: 22 | 23 | | tool | item | 2025/8/15 | 24 | |:-:|:-:|:-:| 25 | |Application| Arduino-IDE | 2.3.6 | 26 | |Boards Manager| M5Stack by M5Stack official | 3.2.2 | 27 | |Library Manager| M5Unified by M5Stack | 0.2.7 | 28 | |Library Manager| M5GFX by M5Stack | 0.2.9 | 29 | |Library Manager| FastLED by Daniel Garcia | 3.10.1 | 30 | |Library Manager| WiFiManager by tzapu | 2.0.17 | 31 | 32 | ### Rev.3 33 | - フォルダ: BF-018ARev3 34 | - Boards Managerで、M5Stack 2.xを選択してください。 35 | - Library Managerで、M5Unified 0.2.5, M5GFX 0.2.6を選択してください。 36 | - M5Atom Lite/Matrix, ATOMS3 Liteで使用いいただけます。 37 | 38 | 動作確認状況: 39 | 40 | | tool | item | 2025/8/15 | 41 | |:-:|:-:|:-:| 42 | |Application| Arduino-IDE | 2.3.6 | 43 | |Boards Manager| M5Stack by M5Stack official | 2.1.4 | 44 | |Library Manager| M5Unified by M5Stack | 0.2.5 | 45 | |Library Manager| M5GFX by M5Stack | 0.2.6 | 46 | |Library Manager| FastLED by Daniel Garcia | 3.10.1 | 47 | |Library Manager| WiFiManager by tzapu | 2.0.17 | 48 | 49 | ### Rev.2 50 | - フォルダ: BF-018ARev2 51 | - Rev.3以降をご使用ください。 52 | 53 | ### Rev.1(無印) 54 | - フォルダ: BF-018A 55 | - Rev.3以降をご使用ください。 56 | 57 | ## 1. 概要 58 |  M5Atomで電波時計のためのJJY信号もどきを生成します。JJY信号が届かないところにある電波時計の時刻合わせができます。Wifi経由のNTPで時刻を取得し、GPIOからJJY信号を出力します。 59 | 60 | 参考: Qiita [標準電波 JJY もどきを M5StickC / M5Atom の Ticker で生成する](https://qiita.com/BotanicFields/items/a78c80f947388caf0d36) 61 | 62 | ## 2. ソフトウェア 63 |  開発環境やライブラリの情報は、各Rev.の欄を参照ください。 64 | 65 | ## 3. ハードウェア 66 |  JJY信号の送信にはアンテナが必要です。GPIO22とGND間に1kΩ程度の抵抗を途中に挟んで1m程度の電線を接続して実験できます。電線を電波時計の至近距離に這わせると電波時計が電線からの磁界を受信してくれます。 67 | 68 | - 電線と1kΩの抵抗による動作例(M5StickC) 69 | Youtube [JJY Simulator by M5StickC for a radio controlled clock](https://youtu.be/S_t3g5wqyh8) 70 | 71 | ### 3.1 プリント基板 72 |  M5Atomを搭載可能なプリント基板を製作しました。 73 | 74 | - [回路図](./bf-018a_scm.pdf) 75 | 76 | - プリント基板 77 | [PCB_front] 78 | 79 | 80 | - M5Atom Liteを搭載 81 | [PCB_with_M5AtomLite] 82 | 83 | 84 | - M5Atom Matrixを搭載 85 | [PCB_with_M5AtomMatrix] 86 | 87 | 88 | ## 4. 動作 89 | 90 | - 電源投入またはリセット後、まずWifi接続の動作に入ります 91 | - Wifi接続が完了後、NTPで日時を取得し、標準信号の送出を開始します 92 | - JJY信号オンを内蔵LEDでモニターできます。M5Atom Matrixでは、0番のLEDのみを使用しています 93 | Rev1の動作(Rev2は「8. Rev.2変更内容」を参照): 94 | >青: JJY信号の送出中を示します 95 | 赤: NTPによる時刻取得に失敗すると点灯します 96 | 黄: Wifi接続が切れると点灯します(赤+緑) 97 | 98 | 動作例 99 | - YouTube [BF-018A: JJY Antenna for M5Atom](https://www.youtube.com/watch?v=4Soobp9k9r0) 100 | 101 | ## 5. Wifi 接続 102 |  tzapu/WiFiManagerを使用しています。使い方は、tzapu/WiFiManagerの説明を参照ください。 103 | 104 | 参考: GitHub [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager) 105 | 106 | ### 大まかな流れ: 107 | > (1) M5Atomが、まずは前回接続したアクセスポイントに接続を試みる 108 | (2) 接続できない場合、M5Atom自身がアクセスポイントとなりスマートホン等からの接続を待つ 109 | >> (2-1) スマートホン等で、アクセスポイントとなったM5Atomに接続する 110 | ※ SSID: Chip IDに基づく名前、password/key: なし 111 | (2-2) ブラウザでIPアドレス192.168.4.1を開く 112 | (2-3) M5Atomが接続すべきSSID/keyを入力する 113 | 114 | >(3) M5Atomが指定されたSSID/keyでWi-Fiに接続する 115 | ## 5. LCD 116 |  M5Atom版では外付けLCDを使用していません。 117 | 118 | ## 6. LED 119 |  ボタンでLEDによるモニターをオン・オフできます。 120 | 121 | ## 7. シリアルモニタ 122 |  動作の状況を監視できます。 123 | 124 | ## 8. その他 125 | 126 | ### (1) WiFi接続についてのリカバリ処理 127 |  WiFiのステータスに応じたリカバリ処理を追加しました。リカバリ処理には5分、10分、あるいはWiFiアクセスポイントが回復するまでなど、長時間を要する場合があります。放置して、いつの間にか正常動作をしていることを目指しています。リカバリ処理の内容は試行錯誤に基づくもので、必ずしもベストではありません。アドバイスなどありましたら是非お知らせください。 128 | 129 | 参考: Qiita [ESP32のWiFiおよびtzapu/WiFiManagerにおいて回復処理を試行錯誤した](https://qiita.com/BotanicFields/items/8a73101a8bfe51e57f67) 130 | 131 | ### (2) RTCの利用 132 |  電源投入時やリセット時においてWiFiが一時的に接続できないときでも、RTCの時刻に基づいてJJY信号を発信します。このための準備として、NTPの時刻同期のタイミングでRTCの時刻をアップデートします。RTCは、直近のアップデートに基づいた現在時刻を保持しています。M5AtomのGROVEポートにRTCユニットを接続して利用できます。RTCユニットを20cmのGROVEケーブルで接続した場合は動作が不安定になり、10cmのケーブルで安定した経験があります。 133 | 134 | 利用可能なRTCユニットの例 135 | - [M5Stack用HYM8563搭載 リアルタイムクロック(RTC)ユニット](https://www.switch-science.com/catalog/7482/) 136 | - [M5 用リアルタイムクロック (RTC) 基板](https://www.switch-science.com/catalog/7170/) 137 | 138 | 参考: Qiita [ESP32 において NTP の時刻同期を捕まえて RTC を更新する](https://qiita.com/BotanicFields/items/f1e28af5a63e4ccf7023) 139 | 140 | [RTCモジュール] 141 | 142 | 143 | ### (3) SSID/Keyを消去する操作 144 |  接続先のWiFiアクセスポイントを変更する場合、SSID/Keyの設定を変更します。古いアクセスポイントが撤去済の場合、WiFiManagerのconfigration portalが自動的に起動します。古いアクセスポイントが稼働中の場合、まずEEPROMに保存されたSSID/Keyの消去することで新規設定ができます。EEPROMのSSID/Keyを消去するには、ボタンを押しながらリセットボタンを押しLEDが緑色に点灯するまでボタンを押し続けます。"configuration portal"が起動しています。 145 | 146 | ### (4) 40kHzを使用 147 |  JJY信号は、福島県の送信所から40kHz、佐賀県の送信所から60kHzで送信されています。PWM周波数は40kHzとしています。ESP32では60KHzぴったりの信号を生成できませんが、かなり近い周波数で生成できます。東日本の場合、疑似JJY信号を60kHzとすることで、福島からの信号に同期したのか、疑似信号に同期したのかを区別することができます。疑似信号を60kHzに設定するには、BF-018ARevN.inoのjjy_frequencyを60000に修正ください。 148 | 149 | ``` 150 | // for TCO(Time Code Output) 151 | const uint32_t jjy_frequency(40000); // 40kHz(east), 60kHz(west) 152 | ``` 153 | 154 | 参考: Qiita [ESP32においてLEDC(LED PWM Controller)に設定する分解能をExcelシートで検討する](https://qiita.com/BotanicFields/items/e74c449c0bef0820fcd1) 155 | 156 | ### (5) LED表示 157 | 158 | - WiFiの接続状態を表示 159 | WL_CONNECTED以外の場合、赤LEDの点灯・点滅で表示 160 | - config portalの起動時、緑LEDを点灯 161 | - NTP同期待ち時、緑LEDを点滅 162 | 163 | 詳細: [JJYシミュレータの状態表示] 164 | 165 | ### (6) M5AtomのWi-Fi接続が不安定となる問題への対策 166 | 167 |  M5AtomにはWi-Fiの出力が低下し接続が不安定になる問題があります。USBシリアル変換用のCH552のファームウェア改善による対策が進んでいると期待されますが、未対策の既出荷品も多い状況です。対策としてGPIO0にLOWを出力する処理をsetup()の冒頭に追加しました。 168 | 169 | #### 参考情報 170 | 171 | - [twitter @wakwak_koba 午前8:36 2022年7月30日](https://twitter.com/wakwak_koba/status/1553162622479974400) 172 | - [M5Atom CH552 FW更新](https://togetter.com/li/1807623) 173 | - [Bugs and measures for M5Stack ATOM](https://macsbug.wordpress.com/2021/10/10/bugs-and-measures-for-m5stack-atom/) 174 | 175 | 176 | 以上 -------------------------------------------------------------------------------- /RTC.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/RTC.jpeg -------------------------------------------------------------------------------- /bf-018a_scm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/bf-018a_scm.pdf -------------------------------------------------------------------------------- /bf-018a_scm_v01l02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/bf-018a_scm_v01l02.pdf -------------------------------------------------------------------------------- /wifi_ntp_rtc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/wifi_ntp_rtc.jpg -------------------------------------------------------------------------------- /wifi_ntp_rtc.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/wifi_ntp_rtc.xlsx -------------------------------------------------------------------------------- /wifi_status.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botanicfields/BF-018A/8e05453023f18dde504e54e31cebd4f58c3ca292/wifi_status.jpg --------------------------------------------------------------------------------