در قسمت قبلی با راهاندازی ارتباط I2C آشنا شدیم و در آموزشهای قبلی، در چند بخش از تایمرها استفاده کردیم. Real Time Clock یا همان RTC عنصری است که از آن برای اندازهگیری دقیق زمان واقعی استفاده میشود. ممکن است این سؤال پیش بیاید که این عمل بدون استفاده از RTC نیز امکانپذیر است. چه نیازی به استفاده از این واحد داریم؟ که در جواب باید گفت استفاده از RTC مزایایی دارد که ازجمله آنها میتوان به موارد زیر اشاره کرد:
- مصرف توان پایین (در کاربردهایی که از باتری یا منابع محدود برای توان استفاده میشود اهمیت دارد).
- باعث خالی بودن سیستم جهت انجام عملیات مهم با وابستگی زمانی دقیق میشود.
- بعضیاوقات از روشهای دیگر دقیقتر است.
در این بخش میخواهیم واحد RTC داخلی میکرو را راهاندازی کنیم و علاوه بر اندازهگیری تاریخ و ساعت، یک هشدار یا Alarm تنظیم کنیم. با سیسوگ همراه باشید.
ایجاد پروژه
مثل قبل بخشهای دیباگ و USART را تنظیم میکنیم. در این پروژه برای بخش کلاک هر دو بخش HSE و LSE را فعال میکنیم. زیرا میخواهیم برای کلاک RTC از کریستال فرکانس پایین استفاده کنیم. توجه کنید که اگر روی بورد BluePill شما این کریستال تعبیه نشده است از هدر بورد چیپ STM32F103RET6 استفاده کنید.
مرحله بعدی تنظیم کلاک میکرو و انتخاب منبع کلاک برای واحد RTC است، طبق شکل زیر LSE را بهعنوان منبع کلاک انتخاب میکنیم.
حالا باید به سراغ واحد RTC برویم. مانند شکلهای زیر تنظیم این بخش را انجام میدهیم و وقفه را برای آن فعال میکنیم.
بقیهی مراحل را مانند قبل انجام میدهیم و وارد بخش برنامهنویسی میشویم.
نوشتن کد برنامه
مثل پروژه قبل، در ابتدا کتابخانه stdio را به برنامه اضافه کرده و توابع ریدایرکت به USART را مینویسیم و تنظیمات مربوط به آن را انجام میدهیم. سپس ساختارها و متغیرهای مورد نیاز برای تاریخ و ساعت را تعریف میکنیم؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | struct time_t { uint8_t sec; uint8_t min; uint8_t hour; }; struct time_t Time; struct time_t Alarm; struct date_t { uint8_t month; uint8_t day; uint16_t year; }; struct date_t Date; uint8_t EndOfMonth[12]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; uint32_t TimeCounter = 0; |
اکنون باید توابع موردنیازمان برای تنظیم و همچنین بهروزرسانی تاریخ و ساعت، و نیز تنظیم هشدار را تعریف کنیم. این توابع را بهصورت زیر مینویسیم:
1 2 3 4 5 6 | void DATE_Config( uint8_t fMonth, uint8_t fDay, uint16_t fYear) { Date.month = fMonth; Date.day = fDay; Date.year = fYear; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void DATE_Update(void) { if ((Time.hour == 00) & (Time.min == 00) & (Time.sec == 00)) { if(Date.day == EndOfMonth[Date.month -1]) { Date.day = 1U; Date.month += 1U; } else { Date.day = Date.day + 0x1U; } if (Date.month == 13) { Date.month = 1U; Date.year += 1U; } } } |
1 2 3 4 5 6 7 | void TIME_Update(void) { TimeCounter = LL_RTC_TIME_Get(RTC); Time.hour = (TimeCounter/3600) % 24; Time.min = (TimeCounter % 3600) / 60; Time.sec = (TimeCounter % 3600) % 60; } |
1 2 3 4 5 6 7 8 9 10 | void TIME_Config(uint8_t fHour, uint8_t fMin, uint8_t fSec) { Time.hour = fHour; Time.min = fMin; Time.sec = fSec; LL_RTC_TIME_Set(RTC,((Time.hour * 3600) + (Time.min * 60) + Time.sec)); } |
1 2 3 4 5 6 7 8 9 10 | void ALARM_Config(uint8_t fHour, uint8_t fMin, uint8_t fSec) { Alarm.hour = fHour; Alarm.min = fMin; Alarm.sec = fSec; LL_RTC_ALARM_Set(RTC,((Alarm.hour * 3600) + (Alarm.min * 60) + Alarm.sec)); } |
حالا باید درون int main و قبل از حلقه while(1) تنظیم واحد RTC را انجام دهیم؛
1 2 3 4 5 6 7 8 9 10 11 12 | LL_RTC_DisableWriteProtection(RTC); LL_RTC_EnterInitMode(RTC); DATE_Config(12, 13, 2021); TIME_Config(11, 59, 59); ALARM_Config(12, 00, 05); LL_RTC_EnableIT_ALR(RTC); LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_17); LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_17); LL_RTC_ExitInitMode(RTC); LL_RTC_EnableWriteProtection(RTC); |
تنها کاری که برای آمادهسازی واحد RTC باقیمانده است، تنظیم Prescaler برای شمارش ثانیه است. همانطور که گفتیم برای این واحد از LSE بهعنوان منبع کلاک استفاده کردهایم. بنابراین فرکانس کلاک RTC برابر با 32.768KHz است. برای اینکه شمارش ثانیه بهدرستی انجام شود، باید مقدار Prescaler را برابر با معادل هگز 32768 یعنی 00008000 قرار دهیم؛
1 2 3 | RTC_InitStruct.AsynchPrescaler = 0x00008000U; LL_RTC_Init(RTC, &RTC_InitStruct); LL_RTC_SetAsynchPrescaler(RTC, 0x00008000U); |
اکنون میتوانیم زمان را اندازهگیری کنیم. کافی است در حلقه while(1) از توابع بهروزرسانی زمان که نوشتیم استفاده کنیم. برای اطمینان از درستی عملکرد، زمان و تاریخ را به پورت سریال میفرستیم؛
1 2 3 4 5 6 7 8 | for(int i=0; i<10; i++) { TIME_Update(); DATE_Update(); printf("Time: %.2d:%.2d:%.2d\r\n", Time.hour, Time.min, Time.sec); LL_mDelay(998); } printf("Date: %.2d/%.2d/%.4d\r\n", Date.month, Date.day, Date.year); |
در کدهای قبلی زمان هشدار را تنظیم کردهایم. برای روشن کردن یک LED در زمان وقوع وقفه، در فایل stm32f1xx_it.c و در بدنه تابع مربوط به وقفه RTC کد زیر را مینویسیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* USER CODE BEGIN RTC_Alarm_IRQn 0 */ if (LL_RTC_IsEnabledIT_ALR(RTC) == 1) { LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_13); /* Clear the RTC Second interrupt */ LL_RTC_ClearFlag_ALR(RTC); /* Wait until last write operation on RTC registers has finished */ LL_RTC_WaitForSynchro(RTC); } /* Clear the EXTI's Flag for RTC Alarm */ LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_17); /* USER CODE END RTC_Alarm_IRQn 0 */ |
پس از رسیدن به زمان تعیینشده برای هشدار، LED روشن خواهد شد. در قسمت بعدی در مورد کالیبره کردن RTC صحبت خواهیم کرد.
من از آلارم استفاده نکردم و توی برنامه اضافش نکردم و اینتراپت ها هم خاموش کردم.(مثل تنظیمات عکس).
و تمام برنامه دقیقا همونی هست که شما نوشتید.
زمان که تا زمانی که توی حلقه while هست بدرستی کار میکنم.
تاریخ هم همینطور.
اما با رست کردم میکرو، همچی صفر میشه و ذخیره نمیمونه.
کریستال 32 و 12Mhz روی بورد موجود هست.
باطری بکاپ هم بدرستی وصل شده.
توی توابع HAL تاریخ ذخیره نمیشد. اما ساعت مشکلی نداشت.
اما توی LL پروسه کلاً به ایراد برخورد.
عرض سلام و ادب خدمت نویسنده و تیم آموزش 30سوگ و سپاس از زحمتی که کشیدید، مطلب بسیار آموزنده بود.
با دو مشکل در برنامه فوق مواجه شدم که اولیش رفع شد ولی دومیش هنوز نه:
1- با ورود به لحظه آپدیت تاریخ یعنی 00:00:00 تاریخ بصورت نادرست پرش میکرد که با “یک بار اجرا کردن کد” مشکل حل شد، چون کد چند بار آپدیت میخورد تا ثانیه اول سپری شه.
2- شما در بدنه تابع اصلی و قبل از حلقه while(1) از 12 خط کد برای تنظیمات اولیه واحد زمان، تاریخ و آلارم استفاده کردید.
در خط 5 از همون دستورات فوق الذکر شما مقدار تابع TIME_Config رو به TIME_Config(23:59:00) تغییر بدید، سپس ALARM_Config رو هم به ALARM_Config(00:00:10) یا ALARM_Config(00:01:00) یا هر مقدار دیگر تغییر بدید، من برای اینکه سریعتر نتیجه برنامه رو مشاهده کنید 5 ثانیه یا یک دقیقه بعد رو پیشنهاد دادم.
الان برنامه در قسمت آلارم فعال نمیشه!!!!!
بررسی کردم دیدم وقتی برنامه وارد آپدیت تاریخ میشه یعنی از 23:59:59 به 00:00:00 میره آلارم کار نمیکنه اما در حالتی بجز این به راحتی کار میکنه.
(جهت اطلاع خوانندگان برای تنظیم مجدد ساعت، تاریخ و آلارم کافیه همون 12 خط کد رو بصورت 1 بار اجرا در حلقه while بیارید و بجای عدد ثابت در توابع از متغیر ها یا اعداد مورد نظرتون استفاده کنید)
اما اینکه با ورود به روز جدید دیگه آلارم کار نمیکنه معظله!!!
ممنون از مهندس ایرانپاک بخاطر مطلبشون در مورد RTC
یک انتقاد و یک سوال داشتم از ایشون.
انتقادم در مورد اینکه تابع DATE_Update به هیچ عنوان بهینه نیست و ممکنه در صورت بروز ثانیه ای خطا عمل نکنه، توصیه من استفاده از اختلاف ساعت حال و قبل هست برای تشخیص و آپدیت تاریخ.
سوالم در این مورد هست که اگه بین کد هامون بخوایم دوباره آلارم رو تنظیم کنیم چیکار باید بکنیم؟ به طور مثال من میخوام بعد از اولین آلارم اون رو برای 5 دقیقه بعد نیز دوباره تنظیم کنم و منتظر وقفه اون باشم. این کار با استفاده از تابعی که نوشتید عمل نکرد. من فکر کردم مربوط به تابع LL_RTC_EnableWriteProtection(RTC); هست ولی حتی با غیرفعال کردن این مورد نیز عمل نکرد.
سلام دوست عزیز، خوشحالم که این مطلب مورد استفاده شما قرار گرفته.
در مورد اول باید بگم که متوجه حالت رخ داد خطایی که فرمودین نمیشم، اگه ممکنه شرح بدین که توی چه حالتی ممکنه این خطای ثانیه رخ بده.
در مورد دوم، آیا بعد از ست کردن دوباره آلارم وقفه رو فعال میکنین؟
کدتون رو به چه صورت نوشتین؟