Первый старт с STM32-Discovery [Часть 7 – Система тактирования + RTC таймер]

11 Января 2011 К комментариям

Сегодня по плану: система тактирования и таймер часов реального времени RTC.

image

Система тактирования Согласно документации (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-таймер, по сигналам которого можно выводить микроконтроллер из спящего режима.

Все эти источники можно независимо включать и выключать, это особенно актуально когда необходимо снизить потребляемую мощность.

После кратенького рассмотрения небольшой скриншот из документации:

image

Не плохо бы все это дело потрогать руками Улыбка, к этому мы и переходим.

Работаем с источниками тактовых сигналов

Сразу несколько ссылок где можно почитать о работе с системой тактирования:

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 лет (если не ошибся :)).

У прощенная функциональная схема:

image

Я не зря остановился на двух низкочастотных генераторах, так именно они могу выступать в качестве источника тактовых сигналов (и ещё сигнал внешнего высокочастотного генератора деленного на 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-01-02_rtc_calendar

2011-01-02_rtc_clock

Увидели ошибку напишите мне, хотя бы коммент ;).


Добавлено 2011-07-02

После разрешения доступа к RTC и Backup регистрам, нужно запретить доступ:

PWR->CR &= ~PWR_CR_DBP;


comments powered by Disqus