Аналоговые часы на STM32F429I-DISCO (теперь с TS)

Пусть тут полежит. Может кому и пригодится. На форумах тема часто возникает, некоторые на этом даже деньги делают.



Используется стандартная периферия и драйвера к STM32F429I-DISCO.

Настроим светодиоды на плате:


void RCC_Configuration (void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
	
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOG, &GPIO_InitStructure);
}


Инициализируем RTC:

void RTC_Configuration (void)
{
	RTC_TimeTypeDef RTC_TimeStructure;
	RTC_InitTypeDef RTC_InitStructure;
	RTC_DateTypeDef RTC_DateStructure;
	
	uint8_t RTC_Error = 0;
	
	LCD_DisplayStringLine(1, (uint8_t*)"Starting Init RTC");
	
	if (RTC_ReadBackupRegister(RTC_BKP_DR0) != 0x32F2)
		{
			LCD_DisplayStringLine(20, (uint8_t*)"RTC After COLD Start");
			
			RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR, ENABLE);
			PWR_BackupAccessCmd(ENABLE);
			RCC_LSEConfig(RCC_LSE_ON);
			while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){};
			RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
			
			//RCC_LSICmd(ENABLE);
			//while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET) {};
			//RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

			RCC_RTCCLKCmd(ENABLE);
			RTC_WaitForSynchro();

			RTC_InitStructure.RTC_AsynchPrediv = 0x7F;
			RTC_InitStructure.RTC_SynchPrediv = 0xFF;
			RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;
				
			if (RTC_Init(&RTC_InitStructure) == ERROR)
				{
					LCD_DisplayStringLine(40, (uint8_t*)"RTC Prescaler Config failed");
				}
		
			RTC_TimeStructure.RTC_H12 = RTC_H12_AM;
			RTC_TimeStructure.RTC_Hours = 14;
			RTC_TimeStructure.RTC_Minutes = 9;
			RTC_TimeStructure.RTC_Seconds = 50;
				
			if(RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure) == ERROR)
				{
					LCD_DisplayStringLine(60, (uint8_t*)"RTC Set Time failed");
					RTC_Error = 1;
				} 
				else
				{
					LCD_DisplayStringLine(60, (uint8_t*)"RTC Set Time success");
				}
				
			RTC_DateStructure.RTC_WeekDay = RTC_Weekday_Sunday;
			RTC_DateStructure.RTC_Date = 11;
			RTC_DateStructure.RTC_Month = RTC_Month_September;
			RTC_DateStructure.RTC_Year = 14;
				
			if(RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure) == ERROR)
				{
					LCD_DisplayStringLine(80, (uint8_t*)"RTC Set Date failed");
					RTC_Error = 1;
				} 
				else
				{
					LCD_DisplayStringLine(80, (uint8_t*)"RTC Set Date success");
				}
			
			if (!RTC_Error)
				{
					RTC_WriteBackupRegister(RTC_BKP_DR0, 0x32F2);
				}
		}
	else
		{

			LCD_DisplayStringLine(20, (uint8_t*)"RTC After HOT Start");
			
			RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
			PWR_BackupAccessCmd(ENABLE);
			RTC_WaitForSynchro();
			RTC_ClearFlag(RTC_FLAG_ALRAF);
			EXTI_ClearITPendingBit(EXTI_Line17);
		}
}

Код стандартный, честно вытянутый из примеров ST. В инициализации устанавливаем время и дату «по-умолчанию».


void PrintTimeDate (void) 
{
	uint8_t LCDTime[9] = "00:00:00";
	uint8_t LCDDate[11] = "00:00:0000";

	RTC_GetTime(RTC_Format_BIN, &RTC_TimeStruct);
	RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct);
		
	sprintf ((char*)LCDTime, "%0.2d:%0.2d:%0.2d", RTC_TimeStruct.RTC_Hours, RTC_TimeStruct.RTC_Minutes, RTC_TimeStruct.RTC_Seconds);
	sprintf ((char*)LCDDate, "%0.2d:%0.2d:%0.2d", RTC_DateStruct.RTC_Date, RTC_DateStruct.RTC_Month, (2000 + RTC_DateStruct.RTC_Year));
		
	LCD_DisplayStringLineColumn(LCD_PIXEL_HEIGHT/2-60, LCD_PIXEL_WIDTH/2-30, 8, LCDTime);

}


Считываем время дату из RTC, переводим их в текстовый формат и выводим в центр часов. LCD_PIXEL_HEIGHT = 320, LCD_PIXEL_WIDTH = 240.
Дальше, рисуем часы. Вся работа основывается на 2-х формулах, нахождение 2-х катетов при известной гипотенузе и угле. С помощью них находятся x и у часовых, минутных и секундных отметок.


	ClockX = LCD_PIXEL_WIDTH/2;
	ClockY = LCD_PIXEL_HEIGHT/2-45;
	ClockRadius = LCD_PIXEL_WIDTH/2-15;


Задается центр часов и максимальный радиус. На нем будут располагаться часовые отметки.


        halfPi = ((asin(1))*2)/2;
	
	PiH = ((asin(1))*2)/6;;
	PiM = ((asin(1))*2)/30;
	PiHalf = ((asin(1))*2)/180;


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


LCD_DrawCircle((uint16_t) ClockX, (uint16_t) ClockY, 1);
		
		for (angle = 0; angle < 12; angle = angle + 1)
		{
			modf ((ClockX + ClockRadius * cos(PiH*angle - halfPi)), &Nx);
			modf ((ClockY + ClockRadius * sin(PiH*angle - halfPi)), &Ny);
			LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 3);
		}
		
		for (angle = 0; angle < 60; angle = angle + 1)
		{
			modf ((ClockX + (ClockRadius - 23) * cos(PiM*angle - halfPi)), &Nx);
			modf ((ClockY + (ClockRadius - 23) * sin(PiM*angle - halfPi)), &Ny);
			LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 1);
		}
		
		for (angle = 0; angle < 60; angle = angle + 1)
		{
			modf ((ClockX + (ClockRadius - 45) * cos(PiM*angle - halfPi)), &Nx);
			modf ((ClockY + (ClockRadius - 45) * sin(PiM*angle - halfPi)), &Ny);
			LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 0);
		}


Рисуем центр часов, потом часовые, минутные и секундные отметки в виде окружностей. Радиус меняем для каждого типа.


                LCD_SetTextColor(LCD_COLOR_BLACK);
		modf ((ClockX + (ClockRadius - 45) * cos(PiM*(RTC_TimeStruct.RTC_Seconds-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 45) * sin(PiM*(RTC_TimeStruct.RTC_Seconds-1) - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 3);		
		LCD_SetTextColor(LCD_COLOR_CYAN);
		modf ((ClockX + (ClockRadius - 45) * cos(PiM*RTC_TimeStruct.RTC_Seconds - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 45) * sin(PiM*RTC_TimeStruct.RTC_Seconds - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 3);
		LCD_SetTextColor(LCD_COLOR_GREY);
		modf ((ClockX + (ClockRadius - 45) * cos(PiM*(RTC_TimeStruct.RTC_Seconds-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 45) * sin(PiM*(RTC_TimeStruct.RTC_Seconds-1) - halfPi)), &Ny);
		LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 0);
		
		LCD_SetTextColor(LCD_COLOR_BLACK);
		modf ((ClockX + (ClockRadius - 23) * cos(PiM*(RTC_TimeStruct.RTC_Minutes-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 23) * sin(PiM*(RTC_TimeStruct.RTC_Minutes-1) - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 5);
		LCD_SetTextColor(LCD_COLOR_CYAN);
		modf ((ClockX + (ClockRadius - 23) * cos(PiM*RTC_TimeStruct.RTC_Minutes - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 23) * sin(PiM*RTC_TimeStruct.RTC_Minutes - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 5);
		LCD_SetTextColor(LCD_COLOR_GREY);
		modf ((ClockX + (ClockRadius - 23) * cos(PiM*(RTC_TimeStruct.RTC_Minutes-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 23) * sin(PiM*(RTC_TimeStruct.RTC_Minutes-1) - halfPi)), &Ny);
		LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 1);
		
		LCD_SetTextColor(LCD_COLOR_BLACK);
		modf ((ClockX + (ClockRadius - 0) * cos(PiHalf*(RTC_TimeStruct.RTC_Hours*30+RTC_TimeStruct.RTC_Minutes/2-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 0) * sin(PiHalf*(RTC_TimeStruct.RTC_Hours*30+RTC_TimeStruct.RTC_Minutes/2-1) - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 6);
		LCD_SetTextColor(LCD_COLOR_CYAN);
		modf ((ClockX + (ClockRadius - 0) * cos(PiHalf*(RTC_TimeStruct.RTC_Hours*30+RTC_TimeStruct.RTC_Minutes/2) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 0) * sin(PiHalf*(RTC_TimeStruct.RTC_Hours*30+RTC_TimeStruct.RTC_Minutes/2) - halfPi)), &Ny);
		LCD_DrawFullCircle((uint16_t) Nx, (uint16_t) Ny, 6);
		LCD_SetTextColor(LCD_COLOR_GREY);
		modf ((ClockX + (ClockRadius - 0) * cos(PiH*(RTC_TimeStruct.RTC_Hours-1) - halfPi)), &Nx);
		modf ((ClockY + (ClockRadius - 0) * sin(PiH*(RTC_TimeStruct.RTC_Hours-1) - halfPi)), &Ny);
		LCD_DrawCircle((uint16_t) Nx, (uint16_t) Ny, 3);



Стрелок нет, вместо них цветные окружности увеличенного радиуса. Если нужны стрелки, просто строим линию от центра часов до вычисленного центра окружности. Каждая отрисовка в 3 этапа, стираем окружность на предыдущей временной отметке, восстанавливаем отметку, рисуем на новом месте.


void ReadTS (void) 
{
	uint16_t KeyDelay = 200;
		
		tState = IOE_TP_GetState();
			
			if (tState->TouchDetected)
			{
				if ((tState->Y <= LCD_PIXEL_HEIGHT/2-14+15) && (tState->Y >= LCD_PIXEL_HEIGHT/2-14))
				{
					if ((tState->X >= LCD_PIXEL_WIDTH/2-32) && (tState->X <= LCD_PIXEL_WIDTH/2-32+12))
					{
						RTC_TimeStruct.RTC_Hours = RTC_TimeStruct.RTC_Hours+1;
						if (RTC_TimeStruct.RTC_Hours > 23)
						{
							RTC_TimeStruct.RTC_Hours = 0;
						}
						RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct);
						
						LCD_Clear(LCD_COLOR_BLACK);
						Delay_MS (KeyDelay);
					}
					if ((tState->X >= LCD_PIXEL_WIDTH/2-32) && (tState->X <= LCD_PIXEL_WIDTH/2-32+23+12))
					{
						RTC_TimeStruct.RTC_Minutes = RTC_TimeStruct.RTC_Minutes+1;
						if (RTC_TimeStruct.RTC_Minutes > 59)
						{
							RTC_TimeStruct.RTC_Minutes = 0;
						}
						RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct);
						
						LCD_Clear(LCD_COLOR_BLACK);
						Delay_MS (KeyDelay);
					}
					if ((tState->X >= LCD_PIXEL_WIDTH/2-32) && (tState->X <= LCD_PIXEL_WIDTH/2-32+47+12))
					{
						RTC_TimeStruct.RTC_Seconds = 0;
			
						RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct);
						
						LCD_Clear(LCD_COLOR_BLACK);
						Delay_MS (KeyDelay);
					}
				}
			}
}	


Простой обработчик тача. Определяются нажатия, если нажатие происходит в области электронных часов (отдельно часы, минуты и секунды), происходит увеличение часов, минут на 1, секунды сбрасываются в 0.


LCD_Init();
LCD_LayerInit();
LCD_SetLayer(LCD_BACKGROUND_LAYER);
LCD_SetTransparency(0);
LCD_SetLayer(LCD_FOREGROUND_LAYER);
LTDC_ReloadConfig(LTDC_IMReload);
LTDC_Cmd(ENABLE);
LCD_Clear(LCD_COLOR_BLACK);
LCD_SetTextColor(LCD_COLOR_GREY);
LCD_SetBackColor(LCD_COLOR_BLACK);
LCD_SetFont(&Font8x12);
if (IOE_Config())
{
		LCD_Clear(LCD_COLOR_RED);
		LCD_SetFont(&Font12x12);
		LCD_DisplayStringLineColumn(120, 25, 12, (uint8_t*) "!!! ERROR TS !!!");
		while (1) {};
}


Инициализируем LCD и TS.

Все. Запускаем бесконечный цикл:


while (1)
	{
		AnalogClock ();
		PrintTimeDate ();
                ReadTS ();
                GPIOG->ODR ^= GPIO_Pin_14;
		Delay_MS (100);
	}


К топику прикреплю архив с проектом для KEIL-a. Там, правда, все под RTOS-ом, но, все тоже самое.

PS. Вопрос по RTC-у. У меня на плате нет внешнего часового кварца. Соответствующие перемычки с обратной стороны открыты. Но, Инициализация LSE происходит и часы тикают. Проверено на 2-х платах с одной закупочной партии. Как так?

  • +6
  • 15 сентября 2014, 22:00
  • lexanet
  • 1
Файлы в топике: stm32f429.zip

Комментарии (17)

RSS свернуть / развернуть
Там есть LSI для RTC видимо он как то и и тикает, он вроде 37 (толи 40 kHz?), так что даже при делителе на 32768 можно не заметить
0
Совершенно справедливо замечено. Выложенный код бессистемно закоментирован и автором не проверен.
На кейпаде вместо цифр выводятся qwerty итп.
-1
Приветствую, уважаемый Алексей!
Очень понравилась Ваша реалезация аналоговых часов на STM32F429!
Хочу повторить Ваш проект! Я скомпилил, то, что в архиве, без ошибок, но тачСкрин не работает(((
Помогите, пожалуйста!
PS: Я уже собрал настенные часы из матрицы от ноутбука 12" на Андройд стике — работает хорошо, но слишком спецефично, хотелось бы упрастить, используя STM32F429.
0
Соревнование по забиванию гвоздей микроскопом продолжается :-))
+3
ну а если микроскоп дешевле, или доступнее, или унифицированнее, или его использование оправдано с точки зрения временного пользования не по назначению, или в качестве обучения, то почему бы и нет?
+2
На то она и отладка, чтобы на ней учиться :-)
0
Можешь выложить Реальное видео «Соревнований по забиванию гвоздей микроскопом»?
кто то Думает на этой платке… но пока никто не Квакал


кто то делает разные мелкие осцилографы
www.youtube.com/results?search_query=STM32F429I-DISCO

кто то как тс — аналоговые часы без стрелок но с цифрами
0
Дохловата она для кваки, квака рассчитана на наличие 3D ускорения.
0
Позвольте с Вами не согласиться!
На AVR32 7000-й серии, ЕМНИП, таки квакали! Так что и М4 ему должно хватить, с его-то FPU. Тем паче что и десктопные камни на момент выхода Quake I имели производительность, сопоставимую с таковой у героя статьи (шел даже на всяких К5 и Цырикс, фризил, но шел). А вот 3D-ускорителей в те времена можно сказать и не было, как класса… По крайней мере, для потребительского рынка.
0
На AVR32 7000-й серии, ЕМНИП, таки квакали!
Линк?
А вот 3D-ускорителей в те времена можно сказать и не было, как класса…
Были, Voodoo, например, да и первые nVidia тогда примерно вышли. А требовать топовое железо — традиция для игр id.
Не вижу, правда, инфы о требованиях оригинального квейка, но вышедший на его движке годом позже Half-Life хотел, ЕМНИП, 266МГц пенек и 3D ускоритель (был и софтверный режим, но, помнится, на разрешениях выше 640х480 атлон гигагерцовый его уже не тянул).
0
У мну в гугли под нумером 1 сцылко
di-halt.livejournal.com/364062.html?thread=5933342
оригинал на тытрубе
www.youtube.com/watch?v=vvOyixL8BRM

Ща будет многабукаф…

Выход Quake как раз и породил всплеск развития 3D-ускорителей. Тот же 3DFx Voodoo вышел где-то на полгода позже, со своим API Glide. Позже iD запилили версию под Glide для Voodoo, еще позже — QuakeGL под OpenGL. nVidia же выпустила свой первый ускоритель Riva 128 практически одновременно с Voodoo 2.
Одно несомненно — Quake был эпохален! Толчок к качественно новому развитию индустрии игр на ПК, отправная точка.
0
У мну в гугли под нумером 1 сцылко
Неплохо. Хотя и заметно тормозит даже в QVGA.

По остальному — есть что почитать в плане описания технологической стороны квейка?

HL был написан под D3D, потому и требовал наличие ускорителя
Вообще, он был мультирендерным — SW, D3D, OGL (не помню насчет Glide). По крайней мере те версии, с которых началось мое знакомство с оным.
0
Тормозит, но работает! Делалось-то поди не игры ради, а с целью доказать саму возможность от нефиг делать… )))
Насчет почитать — ничего нету ( Знаю только, что был для Q1 запилен свой аналог Си с блэкджеком и шлюхами компилятором и отладчиком — QuakeC, доступный в свободном доступе на момент выхода самой игры. С 1999 сорцы движка также выложены в сеть (ща на github есть), только без графики.
Блин, как давно это было! Какой же я старый (((
0
HL был написан под D3D, потому и требовал наличие ускорителя, в оригинальном же Quake все обрабатывалось исключительно силами ЦП, минимум — Pentium и 16МБ оперативы.
0
Можно на ней и радио послушать (правда, не в этой стране — здесь ДВ/СВ вещание прекратили)
armradio.weaksignals.com/
0
Лучше уж тогда WEB-радио. Опять-таки, производительности хватит и на программное декодирование МР3, и на всякие «улучшайзеры» звука еще останется. Если уж говорить про эфирное радио/ТВ, то можно, например, запилить телетекст для старого «Рубина» или «Горизонта»… ЕМНИП, подмешивание в видеосигнал когда-то давным-давно делали на «дохлой» Меге32 корнелльские студенты.
0
Я таки грешным делом подумал, что аффтар при помощи лобзика и надфиля изготовил часы механические используя STM32F429I-DISCO как основной материал :)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.