dev_soft_rtc.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * Copyright (c) 2006-2025 RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date Author Notes
  8. * 2018-01-30 armink the first version
  9. * 2025-10-30 dongly fix timespec/timeval error, optimize soft_rtc implementation
  10. */
  11. #include <sys/time.h>
  12. #include <rtthread.h>
  13. #include <rtdevice.h>
  14. #ifdef RT_USING_CLOCK_TIME
  15. #include <drivers/clock_time.h>
  16. #endif
  17. #ifdef RT_USING_SOFT_RTC
  18. /* 2018-01-30 14:44:50 = RTC_TIME_INIT(2018, 1, 30, 14, 44, 50) */
  19. #define RTC_TIME_INIT(year, month, day, hour, minute, second) \
  20. { \
  21. .tm_year = year - 1900, \
  22. .tm_mon = month - 1, \
  23. .tm_mday = day, \
  24. .tm_hour = hour, \
  25. .tm_min = minute, \
  26. .tm_sec = second, \
  27. }
  28. #ifndef SOFT_RTC_TIME_DEFAULT
  29. #define SOFT_RTC_TIME_DEFAULT RTC_TIME_INIT(2018, 1, 1, 0, 0, 0)
  30. #endif
  31. #ifndef RTC_AUTO_SYNC_FIRST_DELAY
  32. #define RTC_AUTO_SYNC_FIRST_DELAY 25
  33. #endif
  34. #ifndef RTC_AUTO_SYNC_PERIOD
  35. #define RTC_AUTO_SYNC_PERIOD 3600
  36. #endif
  37. static struct rt_work rtc_sync_work;
  38. static struct rt_device soft_rtc_dev;
  39. static RT_DEFINE_SPINLOCK(_spinlock);
  40. /* RTC time baseline for calculation */
  41. static struct timespec base_ts = { 0 };
  42. #ifdef RT_USING_CLOCK_TIME
  43. static struct timespec base_clocktime_ts = { 0 };
  44. #else
  45. static rt_tick_t base_tick;
  46. #endif
  47. #ifdef RT_USING_ALARM
  48. static struct rt_rtc_wkalarm wkalarm;
  49. static struct rt_timer alarm_time;
  50. /**
  51. * @brief Alarm timeout callback function
  52. * @param param User data passed to alarm update function
  53. * @return None
  54. *
  55. * This function is called when the alarm timer expires and updates
  56. * the alarm system.
  57. */
  58. static void alarm_timeout(void *param)
  59. {
  60. rt_alarm_update(param, 1);
  61. }
  62. /**
  63. * @brief Update soft RTC alarm status
  64. * @param palarm Pointer to alarm configuration structure
  65. * @return None
  66. *
  67. * This function updates the alarm timer based on the alarm enable status.
  68. * When enabled, it starts a 1-second period timer for alarm detection.
  69. * When disabled, it stops the timer.
  70. */
  71. static void soft_rtc_alarm_update(struct rt_rtc_wkalarm *palarm)
  72. {
  73. rt_tick_t next_tick;
  74. if (palarm->enable)
  75. {
  76. next_tick = RT_TICK_PER_SECOND;
  77. rt_timer_control(&alarm_time, RT_TIMER_CTRL_SET_TIME, &next_tick);
  78. rt_timer_start(&alarm_time);
  79. }
  80. else
  81. {
  82. rt_timer_stop(&alarm_time);
  83. }
  84. }
  85. #endif
  86. /**
  87. * @brief Set RTC time baseline
  88. * @param ts Pointer to timestamp to set as baseline
  89. * @return None
  90. *
  91. * This function sets a new time baseline for the soft RTC. All subsequent
  92. * time calculations will be based on this baseline. It records both the
  93. * time value and the corresponding system tick or high-precision time.
  94. * Also updates alarm status if alarms are enabled.
  95. */
  96. static void set_rtc_time(struct timespec *ts)
  97. {
  98. rt_base_t level = rt_spin_lock_irqsave(&_spinlock);
  99. base_ts.tv_sec = ts->tv_sec;
  100. base_ts.tv_nsec = ts->tv_nsec;
  101. #ifdef RT_USING_CLOCK_TIME
  102. rt_clock_time_boottime_ns(&base_clocktime_ts);
  103. #else
  104. base_tick = rt_tick_get();
  105. #endif
  106. rt_spin_unlock_irqrestore(&_spinlock, level);
  107. #ifdef RT_USING_ALARM
  108. soft_rtc_alarm_update(&wkalarm);
  109. #endif
  110. }
  111. /**
  112. * @brief Get current RTC time
  113. * @param ts Output parameter to store the retrieved timestamp
  114. * @return None
  115. *
  116. * This function calculates the current time based on the stored baseline
  117. * and the elapsed system tick or high-precision time. It handles both
  118. * nanosecond overflow and underflow to ensure accurate time representation.
  119. * The calculation is thread-safe using spinlock protection.
  120. */
  121. static void get_rtc_time(struct timespec *ts)
  122. {
  123. rt_base_t level;
  124. if (!ts)
  125. return;
  126. level = rt_spin_lock_irqsave(&_spinlock);
  127. #ifdef RT_USING_CLOCK_TIME
  128. struct timespec current_ts;
  129. rt_clock_time_boottime_ns(&current_ts);
  130. ts->tv_sec = base_ts.tv_sec + (current_ts.tv_sec - base_clocktime_ts.tv_sec);
  131. ts->tv_nsec = base_ts.tv_nsec + (current_ts.tv_nsec - base_clocktime_ts.tv_nsec);
  132. #else
  133. rt_tick_t tick = rt_tick_get_delta(base_tick);
  134. ts->tv_sec = base_ts.tv_sec + tick / RT_TICK_PER_SECOND;
  135. ts->tv_nsec = base_ts.tv_nsec + ((tick % RT_TICK_PER_SECOND) * (1000000000UL / RT_TICK_PER_SECOND));
  136. #endif
  137. /* Handle nanosecond overflow/underflow */
  138. if (ts->tv_nsec >= 1000000000L)
  139. {
  140. ts->tv_sec++;
  141. ts->tv_nsec -= 1000000000L;
  142. }
  143. if (ts->tv_nsec < 0)
  144. {
  145. ts->tv_sec--;
  146. ts->tv_nsec += 1000000000L;
  147. }
  148. rt_spin_unlock_irqrestore(&_spinlock, level);
  149. }
  150. /**
  151. * @brief RTC device control function
  152. * @param dev Pointer to RTC device
  153. * @param cmd Control command (RT_DEVICE_CTRL_RTC_*)
  154. * @param args Command arguments (varies by command)
  155. * @return rt_err_t RT_EOK on success, -RT_EINVAL on error
  156. *
  157. * This function handles various RTC control commands including:
  158. * - RT_DEVICE_CTRL_RTC_GET_TIME: Get current time as time_t
  159. * - RT_DEVICE_CTRL_RTC_SET_TIME: Set time from time_t
  160. * - RT_DEVICE_CTRL_RTC_GET_ALARM: Get alarm configuration
  161. * - RT_DEVICE_CTRL_RTC_SET_ALARM: Set alarm configuration
  162. * - RT_DEVICE_CTRL_RTC_GET_TIMEVAL: Get time as timeval
  163. * - RT_DEVICE_CTRL_RTC_SET_TIMEVAL: Set time from timeval
  164. * - RT_DEVICE_CTRL_RTC_GET_TIMESPEC: Get time as timespec
  165. * - RT_DEVICE_CTRL_RTC_SET_TIMESPEC: Set time from timespec
  166. * - RT_DEVICE_CTRL_RTC_GET_TIMERES: Get timer resolution
  167. */
  168. static rt_err_t soft_rtc_control(rt_device_t dev, int cmd, void *args)
  169. {
  170. time_t *t;
  171. struct timeval *tv;
  172. struct timespec *ts;
  173. struct timespec ts_temp;
  174. rt_base_t level;
  175. RT_ASSERT(dev != RT_NULL);
  176. if (!args)
  177. return -RT_EINVAL;
  178. rt_memset(&ts_temp, 0, sizeof(ts_temp));
  179. switch (cmd)
  180. {
  181. case RT_DEVICE_CTRL_RTC_GET_TIME:
  182. {
  183. t = (time_t *)args;
  184. get_rtc_time(&ts_temp);
  185. *t = ts_temp.tv_sec;
  186. break;
  187. }
  188. case RT_DEVICE_CTRL_RTC_SET_TIME:
  189. {
  190. t = (time_t *)args;
  191. ts_temp.tv_sec = *t;
  192. set_rtc_time(&ts_temp);
  193. break;
  194. }
  195. #ifdef RT_USING_ALARM
  196. case RT_DEVICE_CTRL_RTC_GET_ALARM:
  197. *((struct rt_rtc_wkalarm *)args) = wkalarm;
  198. break;
  199. case RT_DEVICE_CTRL_RTC_SET_ALARM:
  200. wkalarm = *((struct rt_rtc_wkalarm *)args);
  201. soft_rtc_alarm_update(&wkalarm);
  202. break;
  203. #endif
  204. case RT_DEVICE_CTRL_RTC_GET_TIMEVAL:
  205. {
  206. tv = (struct timeval *)args;
  207. get_rtc_time(&ts_temp);
  208. tv->tv_sec = ts_temp.tv_sec;
  209. tv->tv_usec = ts_temp.tv_nsec / 1000;
  210. break;
  211. }
  212. case RT_DEVICE_CTRL_RTC_SET_TIMEVAL:
  213. {
  214. tv = (struct timeval *)args;
  215. ts_temp.tv_sec = tv->tv_sec;
  216. ts_temp.tv_nsec = tv->tv_usec * 1000;
  217. set_rtc_time(&ts_temp);
  218. break;
  219. }
  220. case RT_DEVICE_CTRL_RTC_GET_TIMESPEC:
  221. {
  222. ts = (struct timespec *)args;
  223. get_rtc_time(ts);
  224. break;
  225. }
  226. case RT_DEVICE_CTRL_RTC_SET_TIMESPEC:
  227. {
  228. ts = (struct timespec *)args;
  229. set_rtc_time(ts);
  230. break;
  231. }
  232. case RT_DEVICE_CTRL_RTC_GET_TIMERES:
  233. {
  234. ts = (struct timespec *)args;
  235. level = rt_spin_lock_irqsave(&_spinlock);
  236. ts->tv_sec = 0;
  237. #ifdef RT_USING_CLOCK_TIME
  238. ts->tv_nsec = (rt_clock_time_getres() / RT_CLOCK_TIME_RESMUL);
  239. #else
  240. ts->tv_nsec = (1000UL * 1000 * 1000) / RT_TICK_PER_SECOND;
  241. #endif
  242. rt_spin_unlock_irqrestore(&_spinlock, level);
  243. break;
  244. }
  245. default:
  246. return -RT_EINVAL;
  247. }
  248. return RT_EOK;
  249. }
  250. #ifdef RT_USING_DEVICE_OPS
  251. const static struct rt_device_ops soft_rtc_ops = {
  252. RT_NULL,
  253. RT_NULL,
  254. RT_NULL,
  255. RT_NULL,
  256. RT_NULL,
  257. soft_rtc_control
  258. };
  259. #endif
  260. /**
  261. * @brief Soft RTC device initialization
  262. * @return int 0 on success
  263. *
  264. * This function initializes the soft RTC device, registers it to the system,
  265. * and sets the default time. It ensures only one RTC device named "rtc"
  266. * exists in the system and configures the device operations.
  267. * The initialization is performed only once.
  268. */
  269. static int rt_soft_rtc_init(void)
  270. {
  271. static rt_bool_t init_ok = RT_FALSE;
  272. struct tm time_new = SOFT_RTC_TIME_DEFAULT;
  273. if (init_ok)
  274. {
  275. return 0;
  276. }
  277. #if defined(RT_USING_SOFT_RTC) && defined(BSP_USING_ONCHIP_RTC)
  278. #warning "Please note: Currently only one RTC device is allowed in the system, and the name is "rtc"."
  279. #endif
  280. RT_ASSERT(!rt_device_find("rtc"));
  281. #ifdef RT_USING_ALARM
  282. rt_timer_init(&alarm_time,
  283. "alarm",
  284. alarm_timeout,
  285. &soft_rtc_dev,
  286. 0,
  287. RT_TIMER_FLAG_SOFT_TIMER | RT_TIMER_FLAG_ONE_SHOT);
  288. #endif
  289. #ifdef RT_USING_CLOCK_TIME
  290. rt_clock_time_boottime_ns(&base_clocktime_ts);
  291. #else
  292. base_tick = rt_tick_get();
  293. #endif
  294. base_ts.tv_sec = timegm(&time_new);
  295. base_ts.tv_nsec = 0;
  296. soft_rtc_dev.type = RT_Device_Class_RTC;
  297. /* Register RTC device */
  298. #ifdef RT_USING_DEVICE_OPS
  299. soft_rtc_dev.ops = &soft_rtc_ops;
  300. #else
  301. soft_rtc_dev.init = RT_NULL;
  302. soft_rtc_dev.open = RT_NULL;
  303. soft_rtc_dev.close = RT_NULL;
  304. soft_rtc_dev.read = RT_NULL;
  305. soft_rtc_dev.write = RT_NULL;
  306. soft_rtc_dev.control = soft_rtc_control;
  307. #endif
  308. /* No private data */
  309. soft_rtc_dev.user_data = RT_NULL;
  310. rt_device_register(&soft_rtc_dev, "rtc", RT_DEVICE_FLAG_RDWR);
  311. init_ok = RT_TRUE;
  312. return 0;
  313. }
  314. INIT_DEVICE_EXPORT(rt_soft_rtc_init);
  315. #ifdef RT_USING_SYSTEM_WORKQUEUE
  316. /**
  317. * @brief Soft RTC time synchronization
  318. * @return rt_err_t RT_EOK on success
  319. *
  320. * This function retrieves the current RTC time and resets the time baseline.
  321. * It's used to synchronize the soft RTC time with an external time source.
  322. */
  323. rt_err_t rt_soft_rtc_sync(void)
  324. {
  325. time_t time = 0;
  326. rt_device_control(&soft_rtc_dev, RT_DEVICE_CTRL_RTC_GET_TIME, &time);
  327. struct timespec ts = { time, 0 };
  328. /*
  329. * Intentionally reset the soft RTC baseline to the current time.
  330. * This operation updates alarm status and synchronizes the soft RTC
  331. * with the external time source, discarding any accumulated drift.
  332. * This is the intended behavior for synchronization in this context.
  333. */
  334. set_rtc_time(&ts);
  335. return RT_EOK;
  336. }
  337. /**
  338. * @brief RTC sync work function
  339. * @param work Pointer to work item
  340. * @param work_data Work data (unused)
  341. * @return None
  342. *
  343. * This function is executed periodically to maintain soft RTC time accuracy.
  344. * It performs synchronization and schedules the next sync task.
  345. */
  346. static void rtc_sync_work_func(struct rt_work *work, void *work_data)
  347. {
  348. rt_soft_rtc_sync();
  349. rt_work_submit(work, rt_tick_from_millisecond(RTC_AUTO_SYNC_PERIOD * 1000));
  350. }
  351. /**
  352. * @brief Set soft RTC time source
  353. * @param name Name of the time source device
  354. * @return rt_err_t RT_EOK on success
  355. *
  356. * This function configures the soft RTC to use a specific time source
  357. * and starts the periodic synchronization mechanism. The time source
  358. * device must exist before calling this function.
  359. */
  360. rt_err_t rt_soft_rtc_set_source(const char *name)
  361. {
  362. RT_ASSERT(name != RT_NULL);
  363. RT_ASSERT(rt_device_find(name));
  364. rt_work_init(&rtc_sync_work, rtc_sync_work_func, RT_NULL);
  365. rt_work_submit(&rtc_sync_work, rt_tick_from_millisecond(RTC_AUTO_SYNC_FIRST_DELAY * 1000));
  366. return RT_EOK;
  367. }
  368. #ifdef FINSH_USING_MSH
  369. #include <finsh.h>
  370. /**
  371. * @brief RTC sync command handler
  372. * @param argc Argument count
  373. * @param argv Argument array
  374. * @return None
  375. *
  376. * MSH command that manually triggers RTC time synchronization and displays
  377. * the current time information. Usage: rtc_sync
  378. */
  379. static void cmd_rtc_sync(int argc, char **argv)
  380. {
  381. struct timeval tv = { 0 };
  382. struct timezone tz = { 0 };
  383. time_t now = (time_t)0;
  384. rt_soft_rtc_sync();
  385. gettimeofday(&tv, &tz);
  386. now = tv.tv_sec;
  387. rt_kprintf("local time: %.*s", 25, ctime(&now));
  388. rt_kprintf("timestamps: %ld\n", (long)tv.tv_sec);
  389. }
  390. MSH_CMD_EXPORT_ALIAS(cmd_rtc_sync, rtc_sync, Update time by soft rtc);
  391. #endif
  392. #endif /* RT_USING_SYSTEM_WORKQUEUE */
  393. #endif /* RT_USING_SOFT_RTC */