Первый старт с STM32-Discovery [Часть 7 – Система тактирования + RTC таймер]
Сегодня по плану: система тактирования и таймер часов реального времени RTC.
Система тактирования Согласно документации (Reference manual RM0041) в качестве источника тактовых сигналов (SYSCLK – системные тактовые сигналы) могут выступать три источника:
- HSI oscillator clock – внутренний высокочастотный генератор
- HSE oscillator clock – внешний высокочастотный генератор
- PLL clock – ситема ФАПЧ
HSE oscillator clock – внешний высокочастотный генератор
Источником сигналов для HSE генератора может быть как внешний тактовый генератор, так и обычный кварцевый или керамический резонатор.
Для установленного на плате STM32-Discovery микроконтроллера частота внешнего сигнала не должна превышать 24 МГц, в случае если вы используете внешний тактовый генератор, а при использовании кварцевого (керамического) резонатора его частота должна быть от 4 до 24 МГц.
Внешний сигнал может иметь форму пилы, синусоиды или прямоугольных импульсов со скважностью 50%.
HSI oscillator clock – внутренний высокочастотный генератор
HSI генератор представляет собой RC-генератор с частотой 8 МГц, он тактирует интерфейс программирования флеш-памяти, может быть источником тактовых сигналов (SYSCLK), а так же может служить источником опорных сигналов для ФАПЧ (однако при этом его частота делиться на два, т.е. составляет 4 МГц).
Данный генератор проходит калибровку на заводе и производитель гарантирует точность в 1% при температуре 25 градусов Цельсия.
PLL clock – ситема ФАПЧ
Система ФАПЧ (фазовая автоподстройка частоты) грубо говоря производит умножение опорного сигнала (доступны коэффициенты от 2 до 16), однако частота на выходе системы ФАПЧ должна лежать в пределах 16-24 МГц.
Так же имеются два вторичных источника тактовых сигналов:
- LSI RC – внутренний низкочастотный RC-генератор (40 кГц)
- LSE crystal – внешний низкочастотный кварцевый генератор (32 кГц)
От внутреннего низкочастотного RC-генератора тактируется сторожевой таймер, так же от него может тактироваться RTC-таймер, по сигналам которого можно выводить микроконтроллер из спящего режима.
Все эти источники можно независимо включать и выключать, это особенно актуально когда необходимо снизить потребляемую мощность.
После кратенького рассмотрения небольшой скриншот из документации:
Не плохо бы все это дело потрогать руками , к этому мы и переходим.
Работаем с источниками тактовых сигналов
Сразу несколько ссылок где можно почитать о работе с системой тактирования:
ARM. Учебный курс. Тактовый генератор STM32
Программирование STM32. Часть 2. Система тактирования STM32
Сегодня мне интересен внутренний низкочастотный RC-генератор (40 кГц), а так же внешний низкочастотный кварцевый генератор (32 кГц), так как на плате установлен кварц на 32768 Гц.
LSI RC – внутренний низкочастотный RC-генератор (40 кГц)
Для активации генератора достаточно разрешить его работу установкой соответствующего бита и дождаться установки бита готовности:
RCC->CSR |= RCC_CSR_LSION;
while ((RCC->CSR & RCC_CSR_LSIRDY) != RCC_CSR_LSIRDY)
{
}
Для деактивации достаточно сбросить бит разрешения работы:
RCC->CSR &= ~RCC_CSR_LSION;
Не так уж и сложно :)
LSE crystal – внешний низкочастотный кварцевый генератор (32 кГц)
С этим генератором не много сложнее, так как он относится к резервной области (Backup domain) разработчики постарались максимально защитить работу узлов данной области, поэтому первым делом необходимо разрешить тактирование модулей управления питанием и управлением резервной областью:
RCC->APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;
После этого разрешить доступ:
PWR->CR |= PWR_CR_DBP;
И только после этого установить бит разрешения работы и дождаться установки бита готовности:
RCC->BDCR |= RCC_BDCR_LSEON;
while ((RCC->BDCR & RCC_BDCR_LSEON) != RCC_BDCR_LSEON)
{
}
Выключить генератор можно только путем программного сброса:
RCC->BDCR |= RCC_BDCR_BDRST;
RCC->BDCR &= ~RCC_BDCR_BDRST;
Таймер часов реального времени RTC
Пожалуй самый простой таймер в данном микроконтроллере. Данный таймер так же относится к резервной области, следовательно что бы включить его и сконфигурировать нужно снять блокировку и он может работать от резервного источника питания, как правило это батарейка.
Наличие четырех байтного счетного регистра позволяет вести счет без переполнения до 2^32, т.е. при инкрементировании раз в секунду может считать без переполнения до 136 лет (если не ошибся :)).
У прощенная функциональная схема:
Я не зря остановился на двух низкочастотных генераторах, так именно они могу выступать в качестве источника тактовых сигналов (и ещё сигнал внешнего высокочастотного генератора деленного на 128). Поэтому если у вас не установлен внешний кварц используйте внутренний генератор.
Таймер ведет счет линейно следовательно нам необходимо парсить на временные составляющие: секунды, минуты, часы, дни и т.д. Однако ещё стоит вопрос делать ли просто часы-таймер, или же реализовывать полноценный режим с определением: дня недели, месяца, високосного года. Так как я сам ни разу этого не делал (использовал ИМС DS1307 - часы-календарь) решил замахнуться на полноценный режим работы, тем более что есть пример от производителя:
Скачать пример: AN2821 Clock/calendar implementation on the STM32F10xxx microcontroller RTC
Почитать в WiKi о “Вечном календаре”
Я не очень вдавался в алгоритм реализации часов в примере, так как хотелось немного размять мозги после праздников :) поэтому мое видение часов-календаря:
- за точку отсчета принимаем 2000 год, так как цикл составляет 400 лет (почему)
- что бы не усложнять алгоритм работы введем ограничение на время корректной работы до 2099 года, чего я думаю достаточно ;)
- на данный момент думаю не делать ввод времени, т.е. нам пока нужно понять сам принцип работы с RTC и разобраться с алгоритмом часов-календаря.
- для индикации используем двухстрочный ЖКИ.
Расчет дня недели взял из примера:
uint8_t calendar_week_day(uint16_t year, uint8_t month, uint8_t day)
{
uint16_t tmp1, tmp2, tmp3, tmp4, week_day;
if (month < 3)
{
month = month + 12;
year = year - 1;
}
tmp1 = (6 * (month + 1)) / 10;
tmp2 = year / 4;
tmp3 = year / 100;
tmp4 = year / 400;
week_day = day + (2 * month) + tmp1 + year + tmp2 - tmp3 + tmp4 + 1;
week_day = week_day % 7;
return (week_day);
}
Результат число от 0 до 6 [воскресенье, понедельник, … суббота].
Кратко об алгоритме работы часов-календаря.
После старта проверяем активирован ли таймер, если нет преобразуем дату, заносим в счетный регистр и активируем таймер от внешнего генератор, если да просто считываем значение преобразуем и выводим на индикатор.
Конвертор даты в значение счетчика:
uint32_t calendar_coder(calendar_type * calendar)
{
uint32_t tmp;
uint8_t i;
tmp = calendar->year * (365 * 24 * 3600) + (calendar->year / 4) * 24 * 3600;
for (i = 0; i < calendar->month - 1; i++)
{
tmp += 24 * 3600 * month_day[0];
}
tmp += calendar->day * 24 * 3600;
tmp += calendar->hour * 3600;
tmp += calendar->minutes * 60;
return tmp + calendar->seconds;
}
и обратное преобразование:
void calendar_decoder(uint32_t value, calendar_type * calendar)
{
int16_t tmp;
calendar->year = value / (365 * 24 * 3600);
tmp = ((value % (365 * 24 * 3600)) / (24 * 3600)) - (calendar->year / 4);
if ((tmp <= 0) && (calendar->year > 3))
{
calendar->year--;
if (calendar_check_leap(calendar->year + 2000))
{
tmp += 366;
}
else
{
tmp += 365;
}
}
calendar->month = 0;
while (tmp > 0)
{
calendar->day = tmp;
if (calendar->month++ == 2)
{
if (calendar_check_leap(calendar->year + 2000))
tmp -= month_day[calendar->month - 1] + 1;
else
tmp -= month_day[calendar->month - 1];
}
else
{
tmp -= month_day[calendar->month - 1];
}
}
calendar->day_of_the_week = calendar_week_day(2000 + calendar->year,
calendar->month, calendar->day);
calendar->hour = ((value % (365 * 24 * 3600)) % (24 * 3600)) / 3600;
calendar->minutes = (((value % (365 * 24 * 3600)) % (24 * 3600)) % 3600)
/ 60;
calendar->seconds = (((value % (365 * 24 * 3600)) % (24 * 3600)) % 3600)
% 60;
}
Если бы не наличие високосных лет, то было бы попроще :)
//-----------------------------------------------------------------------------
int main(void)
{
calendar_type calendar;
PIN_CONFIGURATION(LED_GREEN);
PIN_CONFIGURATION(LED_BLUE);
PIN_CONFIGURATION(BUTTON);
lcd_2x16_init();
mcu_rtc_init();
while (1)
{
if (PIN_SIGNAL(BUTTON))
{
PIN_OFF(LED_GREEN);
PIN_ON(LED_BLUE);
}
else
{
PIN_ON(LED_GREEN);
PIN_OFF(LED_BLUE);
}
calendar_decoder(((uint32_t)RTC->CNTH << 16) | RTC->CNTL, &calendar);
lcd_2x16_set_position(0, 0);
lcd_2x16_print_hex_xx(0x20);
lcd_2x16_print_dec_xx(calendar.year);
lcd_2x16_print_char('-');
lcd_2x16_print_dec_xx(calendar.month);
lcd_2x16_print_char('-');
lcd_2x16_print_char(week_day[calendar.day_of_the_week] >> 8);
lcd_2x16_print_char(week_day[calendar.day_of_the_week]);
lcd_2x16_print_char('-');
lcd_2x16_print_dec_xx(calendar.day);
lcd_2x16_set_position(1, 0);
lcd_2x16_print_dec_xx(calendar.hour);
lcd_2x16_print_char(':');
lcd_2x16_print_dec_xx(calendar.minutes);
lcd_2x16_print_char('.');
lcd_2x16_print_dec_xx(calendar.seconds);
lcd_2x16_print_string((uint8_t *) &Hello);
}
return 0;
}
Для наглядности выставил предварительный делитель у таймера в 1, тем самым ускорив процесс:
С оптимизацией сильно не заморачивался, если кто захочет доделает.
Я так же немного поправил работу с портами, так как решил оставить в качестве базы заголовочник от производителя stm32f10x.h.
Задавали вопрос по компиляции, достаточно установить ПО, скачать архив с файлами проекта, распаковать в удобное для вас место и в корневой директории проекта выполнить команду make all, с данным проектом результат вывода вот такой:
make all
-----------------------------------------------------------
Compiling: mcu/startup.c
Compiling: main.c
Compiling: mcu/interrupt.c
Compiling: mcu/peripherals/mcu_gpio.c
Compiling: mcu/peripherals/mcu_rcc.c
Compiling: mcu/peripherals/mcu_rtc.c
Compiling: mcu/peripherals/mcu_pwr.c
Compiling: mcu/peripherals/lcd_2x16.c
Compiling: utility/calendar.c
Linking: target.elf
-----------------------------------------------------------
text data bss dec hex filename
2132 0 200 2332 91c target/target.elf</pre>
Увидели ошибку напишите мне, хотя бы коммент ;).
Добавлено 2011-07-02
После разрешения доступа к RTC и Backup регистрам, нужно запретить доступ:
PWR->CR &= ~PWR_CR_DBP;
comments powered by Disqus