Первый результат

Однажды один мой друг спросил, на чем бы я сделал таймер обратного отсчета, чтобы на телевизоре показывал большие цифры. Понятно, что можно подключить ноутбук / iPad / Android и написать приложение, только ноутбук — громоздко, а написанием мобильных приложений ни друг, ни я никогда не занимались.

И тут я вспомнил, что видел в сети проекты тв-терминалов на микроконтроллере AVR. В голове сразу появилась идея объединить маленькие символы в большие и мы решили попробовать. Как-то само собой получилось, что основную работу пришлось делать мне.

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

Найдено было много проектов, но оказалось, что большинство из них критериям не особо соответствуют. Впоследствии стало ясно, что главное — понять принцип формирования видеосигнала, а дальше дело пойдет. Но на данном этапе безусловным фаворитом стал проект Максима Ибрагимова «Простой VGA/видео адаптер», он и лег в основу моей поделки. Однако, в процессе работы от него осталась только структура, реализацию пришлось переделать практически полностью.

Дополнительной задачей, которую я практически сам себе придумал, стало задание начального времени с ИК-пульта.

В качестве основного контроллера я решил использовать ATMega168, работающий на 20МГц. Аппаратная часть формирователя видеосигнала выглядит так:

схема формирователя видеосигнала

Начал я с того, что выкинул из проекта все, что касается VGA, так как его делать не планировал. Попутно изучал стандарты кодирования видеосигнала, наиболее доступной мне показалась картинка с сайта Мартина Хиннера:

image.

По этой картинке делал генератор сигнала синхронизации.

В основе генератора — Timer1 в режиме fastPWM. Дополнительно глобальной переменной организован счетчик синхроимпульсов. По каждому прерыванию переполнения таймера происходит проверка номера синхроимпульса на ключевое значение, изменение длительности следующего синхроимпульса и период следующего синхроимпульса (полная строка / половина строки). Если не требуется изменений, делаются стандартные действия — увеличивается счетчик синхроимпульсов, изменяются другие переменные.

#define
// 2. System definitions

#define Timer_WholeLine	F_CPU/15625		//One PAL line 64us
#define Timer_HalfLine	Timer_WholeLine/2	//Half PAL line = 32us
#define Timer_ShortSync Timer_WholeLine/32	//2us
#define Timer_LongSync	Timer_ShortSync*15	//30us
#define Timer_NormalSync Timer_WholeLine/16	//4us
#define Timer_blank	Timer_WholeLine/8		//8us

//Global definitions for render PAL

#define PAL_FPS	50

#define pal_first_visible_line1 40
#define pal_last_visible_line1	290 //pal_first_visible_line1+pal_row_count*pal_symbol_height

#define horiz_shift_delay 15


Инициализация таймера (фрагмент функции)
// Initialize Sync for PAL
synccount = 1;	
VIDEO_DDR |= (1<<SYNC_PIN);
OCR1B = Timer_LongSync;
TCCR1A = (1<<COM1B1)|(1<<COM1B0)|(0<<WGM10)|(1<<WGM11);	//Fast PWM,Set OC1B on Compare Match,
														// clear OC1B at BOTTOM (inverting mode)  
TCCR1B = (1<<WGM12)|(1<<WGM13)|(1<<CS10);				//full speed;TOP = ICR1
ICR1 = Timer_HalfLine;					//Начинаем с двух прерываний на строку.
TIMSK1 = (1<<OCIE1B);				//enable interrupt from
row_render=0;
y_line_render=0;


Генератор синхросигнала
//генератор синхросигнала
volatile unsigned int synccount;		//  счетчик импульсов синхронизации

EMPTY_INTERRUPT (TIMER1_COMPB_vect);

void MakeSync(void)
{
  switch (synccount)
  {
  case 5://++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
	Sync=Timer_ShortSync;
	synccount++;
	break;
  case 10://++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	ICR1 = Timer_WholeLine;
	Sync= Timer_NormalSync;
	synccount++;
	break;
  case 315://++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	ICR1 = Timer_HalfLine;
	Sync= Timer_ShortSync;
	synccount++;
	break;
  case 321://++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	Sync=Timer_LongSync;
	synccount=1;
	framecount++;
	linecount = 0;
	break;
  default://++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	synccount++;
	video_enable_flg = ((synccount>pal_first_visible_line1)&&(synccount<pal_last_visible_line1));
	break;
  }
}


сигнал кадровой синхронизации стандарта PAL

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

Вывод видеосигнала организован через SPI, работающий на максимальной частоте, равной половине частоты тактового сигнала.

#define
#define SPI_PORT	PORTB
#define SPI_DDR		DDRB
#define MOSI	PORTB3
#define MISO	PORTB4
#define SCK		PORTB5

//Вывод видео
#define VIDEO_PORT	SPI_PORT
#define VIDEO_DDR	SPI_DDR
#define VIDEO_PIN 	MOSI

#define VIDEO_OFF DDRB=0b00100100; 
#define VIDEO_ON DDRB=0b00101100;


Инициализация SPI (фрагмент)
//Set SPI PORT DDR bits
VIDEO_DDR |= (1<<MOSI)|(1<<SCK)|(0<<MISO);
SPSR = 1 << SPI2X;
SPCR = (1 << SPE) | (1 << MSTR); //SPI enable as master ,FREQ = fclk/2


Сам процесс вывода осуществляется в каждой строке функцией DrawString, которой в качестве параметров передается указатель на массив цифр для вывода, указатель на используемый шрифт и количество выводимых символов. Также при выводе используются глобальные переменные номера выводимой строки в каждом шрифте и номера символа. Внутри каждого символа, в цикле с количеством итераций, равному ширине данного символа в байтах, эти байты шрифта передаются в регистр SPDR.

Кроме того, аппаратная реализация SPI в контроллере AVR не может передавать несколько байт данных подряд. После каждого байта один бит пропускается, из-за чего возникают разрывы на изображении.

разрывы при передаче через SPI
Маленькое пояснение
Даже немного не так. Выход MOSI остается в высоком уровне после передачи байта, а на этой фотке выход видео включен через инвертор 74НС04, а байты шрифтов инвертируются перед выдачей, поэтому разрывы черные. Без инвертора получаются белые вертикальные полоски.


Чтобы победить этот недостаток, пришлось воспользоваться трюком, предложенным в проекте TellyMate, который заключается в переключении ножки вывода видео в высокоимпедансное состояние, когда нужно, и таким образом повторять последний бит в выводимом байте. Эта часть функции очень критична по времени и отказ от ассемблера привел к необходимости использовать бубен найти хитрое решение.

Функция вывода строки
inline void DrawString (unsigned char *str_buffer[], struct FONT_INFO *font, unsigned char str_symbols)
{
unsigned char symbol_width;
		
unsigned char i;
unsigned char * _ptr;
unsigned char * _ptr1;
			
y_line_render++;
//Set pointer for render line (display buffer)
_ptr = &str_buffer[row_render * str_symbols];
		
unsigned char j;
register unsigned char _S;
unsigned char _S1;
		
//Cycle for render line
i = str_symbols;
while(i--)
    {
        symbol_width = font->width[(* _ptr)];
        //Set pointer for render line (character generator)
        _ptr1 = &font->bitmap[font->offset[* _ptr]+y_line_render*symbol_width];
		
	_S1 = 0;					//предыдущий байт
	_S = pgm_read_byte(_ptr1); //текущий байт
	_ptr1++;

	j=symbol_width;					//вывод одного символа
	while (1)
	{
		if (_S1 & 0b1)
		{
			goto matr;
		}
		VIDEO_OFF;
matr:	NOP;
		SPDR = _S;
		VIDEO_ON;
		_S1 = _S;
		_S = pgm_read_byte(_ptr1++);	
		NOP;	
		NOP;
		if (!--j) break;
	}
	_ptr++;
	VIDEO_OFF;			
    }
}


После того, как изображение было получено, стало ясно, что ни о каком приеме и разборе ИК-посылок с пульта не может идти речи, просто не хватит скорости, поэтому оставил прием команд по UART. Приемом ИК займется другой микроконтроллер.

Также добавил второй буфер, который нужен для отображения часов. Соответственно, шрифтов будет тоже два. Структура файла шрифта состоит из собственно, битмапов символов, константы высоты шрифта и массивов смещений каждого символа и ширины каждого символа.

Также имеется структура, описывающая шрифт, для более простого доступа из программы.

Шрифт
// Character bitmaps for Digital-7 Mono 120pt
const unsigned char PROGMEM Digital7_Bitmaps[] =
{
	// @0 '0' (71 pixels wide)
	0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x80, //                #############################################   #
	0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xE0, //               ###############################################   ###
	0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF0, //              ###############################################   #####
	0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF8, //             ################################################   ######
...
...
}

const unsigned char Digital7_Height = 105;

const unsigned char Digital7_Width[] =
{
	9, 		/* 0 */
	9, 		/* 1 */
	9, 		/* 2 */
	9, 		/* 3 */
	9, 		/* 4 */
	9, 		/* 5 */
	9, 		/* 6 */
	9, 		/* 7 */
	9, 		/* 8 */
	9, 		/* 9 */
	3 		/* : */
};

const unsigned int Digital7_Offset[] =
{
	0	, 		/* 0 */
	945, 		/* 1 */
	1890, 		/* 2 */
	2835, 		/* 3 */
	3780, 		/* 4 */
	4725, 		/* 5 */
	5670, 		/* 6 */
	6615, 		/* 7 */
	7560, 		/* 8 */
	8505, 		/* 9 */
	9450 		/* : */
};



Шрифты генерировал программой DotFactory.

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

Прием по UART
unsigned char clock_left;
bool clock_set;

volatile unsigned char MinTens, MinOnes;
volatile unsigned char SecTens, SecOnes;

static void pal_terminal_handle(void)
{
	unsigned char received_symbol = 0;
	// Parser received symbols from UART
	while(UCSR0A & (1<<RXC0))
	{
		received_symbol = UDR0;
		if (received_symbol=='#')
			{
				clock_left=5;
				clock_set = true;
			}
		if ((received_symbol>0x2F)&&(received_symbol<0x3A))
			{
				if (clock_set)
					{
						time_array[5-clock_left] = received_symbol - 0x30;
						clock_left--;
						if (clock_left==3)
							{
								clock_left--;
							}
						if (clock_left==0)
							{
								time_array[6] = 0;
								time_array[7] = 0;
								clock_set = false;
							}
					}
				else
					{
						if ((pause==0)||_Stop)
						{
							MinTens = 0;
						}
						else
						{
							MinTens = MinOnes;
						}
						MinOnes = received_symbol - 0x30;
						SecTens = 0;
						SecOnes = 0;
						pause = 4;
						_Stop = false;
						
						str_array[0] = MinTens;
						str_array[1] = MinOnes;
						str_array[2] = 0x0A;
						str_array[3] = SecTens;
						str_array[4] = SecOnes;
					}
				//time_array[] = {1, 2, 10, 5, 5};
				
			}
	}
}


Функция Main();
volatile bool _Stop;

struct FONT_INFO
{
	unsigned char height;
	unsigned char * bitmap;
	unsigned int * offset;
	unsigned char * width;
} Digital7, comdot;

int main(void)
{	
    avr_init();
	
	//fonts
	Digital7.bitmap = &Digital7_Bitmaps;
	Digital7.height = Digital7_Height;
	Digital7.offset = &Digital7_Offset;
	Digital7.width = &Digital7_Width;
	
	comdot.bitmap = &comdotshadow_Bitmaps;
	comdot.height = comdotshadow_Height;
	comdot.offset = &comdotshadow_Offset;
	comdot.width = &comdotshadow_Width;

	MinTens = 0;
	MinOnes = 0;
	SecTens = 0;
	SecOnes = 0;
	
	str_array[0] = MinTens;
	str_array[1] = MinOnes;
	str_array[2] = 0x0A;
	str_array[3] = SecTens;
	str_array[4] = SecOnes;
	
	unsigned char *semicolon = &time_array[2];
	sei();
	
    while (1) 
    {
		sleep_mode();
		MakeSync();

		if (UCSR0A & (1<<RXC0))
		{
			//Parse received symbol
			pal_terminal_handle();
			//Can easealy add here RX polling buffer
			//to avoid display flickering
			continue;
		}
		//Check visible field
		if(video_enable_flg)
			{
				linecount++;
				//OK, visible
				//Main render routine
#define firstline	36
#define secondline  200
				//To make horizontal shift rendered image
				unsigned char k;
				for (k=horiz_shift_delay; k>0; k--)
					{
						NOP;
					}
				if ((linecount == firstline)||(linecount == secondline))
					{
						row_render = 0;
						y_line_render = 0;
					}
	
				if ((linecount> firstline) && (linecount< firstline+(Digital7.height)))
					{
						DrawString(&str_array, &Digital7, 5);	
					}
				if ((linecount> secondline) && (linecount< secondline+(comdot.height)))
					{
						DrawString(&time_array, &comdot, 5);
					}
								
			}
		else
		{
		//Not visible
		//Can do something else..	
		//You can add here your own handlers..
// 			VIDEO_OFF;
			if (framecount==PAL_FPS)
				{
				framecount=0;
				//=========================================
				if (*semicolon== 11)
					{
						*semicolon=10;
					}
				else
					{
						*semicolon=11;
					}
				if (++time_array[7] == 10)
					{
						framecount = 1;// коррекция секунд
						time_array[7]=0;
						if (++time_array[6]==6)
							{
								framecount = 3; // коррекция секунд
								time_array[6]=0;
								if (++time_array[4]==10)
									{	
										time_array[4]=0;
										if (++time_array[3]==6)
											{
												time_array[3]=0;
												if ((++time_array[1]==4) && (time_array[0]==2))
													{
														time_array[0]=0;
														time_array[1]=0;
													}
												if (time_array[1]== 9)
													{
														time_array[1]=0;
														time_array[0]++;
													}
											}		
									}
							}
					}
				
				//=========================================
				if ((pause==0)&&(_Stop==false))
					{								
						if ((SecOnes--)==0)
						{
							SecOnes=9;
							if ((SecTens--) == 0)
							{
								SecTens = 5;
								if ((MinOnes--) == 0)
								{
									MinOnes = 9;
									if (MinTens == 0)
									{
										_Stop = true;
									}
									else
									{
										MinTens--;
									}
								}	
							}	
						}
					if (!_Stop)
						{
						str_array[0] = MinTens;
						str_array[1] = MinOnes;
						str_array[2] = 0x0A;
						str_array[3] = SecTens;
						str_array[4] = SecOnes;	
						}

					}
				else
					{
						pause--;
					}

				}
		}
		
		
    }
}


В качестве контроллера, декодирующего ИК-пульт и отправляющего команды по UART, я взял ATTiny45. Поскольку у него нет аппаратного UART, на просторах интернета была найдена очень компактная функция программного UART, работающего только на отправку, а также простая функция чтения команд с пульта (без декодирования).

Все это было быстренько собрано в кучу и откомпилировано. Коды кнопок пульта жестко прошиты в коде. Дополнительно сделал мигание светодиода при приеме команды.

Приемник ИК и UART
/*
* Tiny85_UART.c
*
* Created: 19.04.2016 21:22:52
* Author: Antonio
*/

#include <avr/io.h>
#include «dbg_putchar.h»
#include <avr/interrupt.h>
//#include <stdlib.h>
#include <stdbool.h>

// пороговое значение для сравнения длинн импульсов и пауз
static const char IrPulseThershold = 9;// 1024/8000 * 9 = 1.152 msec
// определяет таймаут приема посылки
// и ограничивает максимальную длину импульса и паузы
static const uint8_t TimerReloadValue = 100;
static const uint8_t TimerClock = (1 << CS02) | (1 << CS00);// 8 MHz / 1024

volatile unsigned char blink = 0;

#define blink_delay 3;

volatile struct ir_t
{
// флаг начала приема полылки
uint8_t rx_started;
// принятый код
uint32_t code,
// буфер приёма
rx_buffer;
} ir;

static void ir_start_timer()
{

TCNT0 = 0;
TCCR0B = TimerClock;
}

// когда таймер переполнится, считаем, что посылка принята
// копируем принятый код из буфера
// сбрасываем флаги и останавливаем таймер
ISR(TIMER0_OVF_vect)
{
ir.code = ir.rx_buffer;
ir.rx_buffer = 0;
ir.rx_started = 0;
if(ir.code == 0)
TCCR0B = 0;
TCNT0 = TimerReloadValue;
}

ISR(TIMER1_OVF_vect)
{
if (blink==0)
{
OCR1B = 0;
}
else
{
OCR1B = 200;
blink--;
}
}

// внешнее прерывание по фронту и спаду
ISR(INT0_vect)
{
uint8_t delta;
if(ir.rx_started)
{
// если длительность импульса/паузы больше пороговой
// сдвигаем в буфер единицу иначе ноль.
delta = TCNT0 — TimerReloadValue;
ir.rx_buffer <<= 1;
if(delta > IrPulseThershold) ir.rx_buffer |= 1;
}
else{
ir.rx_started = 1;
ir_start_timer();
}
TCNT0 = TimerReloadValue;
}

void dbg_puts(char *s)
{
while(*s) dbg_putchar(*s++);
}

int main(void)
{

GIMSK |= _BV(INT0);
MCUCR |= (1 << ISC00) | (0 <<ISC01);
TIMSK = (1 << TOIE0)|(1<<TOIE1);
ir_start_timer();

dbg_tx_init();

DDRB|=_BV(PB4);

TCCR1 |= (1<<CS13)|(1<<CS12)|(0<<CS11)|(0<<CS10);
GTCCR |= (1<<COM1B1)|(0<<COM1B0)|(1<<PWM1B);
OCR1C = 255;
OCR1B = 0;
blink=0;
sei();

//dbg_puts(&HelloWorld);
while (1)
{
// если ir.code не ноль, значит мы приняли новую комманду
if(ir.code)
{
// конвертируем код в строку
//ultoa(ir.code, buf, 16);
// dbg_puts(buf); //и выводим в порт
//==================================================================
switch (ir.code)
{
case 0x2880822a: blink=blink_delay; dbg_putchar('1'); break;
case 0x8280282a: blink=blink_delay; dbg_putchar('2'); break;
case 0x8a0020aa: blink=blink_delay; dbg_putchar('3'); break;
case 0x0a00a0aa: blink=blink_delay; dbg_putchar('4'); break;
case 0x0280a82a: blink=blink_delay; dbg_putchar('5'); break;
case 0x2a888022: blink=blink_delay; dbg_putchar('6'); break;
case 0x0200a8aa: blink=blink_delay; dbg_putchar('7'); break;
case 0x0a80a02a: blink=blink_delay; dbg_putchar('8'); break;
case 0x22888822: blink=blink_delay; dbg_putchar('9'); break;
case 0x20888a22: blink=blink_delay; dbg_putchar('0'); break;
case 0x0008aaa2: blink=blink_delay; dbg_putchar('O'); break;
case 0x280882a2: blink=blink_delay; dbg_putchar('U'); break;
case 0x8880222a: blink=blink_delay; dbg_putchar('D'); break;
case 0x0808a2a2: blink=blink_delay; dbg_putchar('L'); break;
case 0xa0080aa2: blink=blink_delay; dbg_putchar('R'); break;
case 0x20088aa2: blink=blink_delay; dbg_putchar('*'); break;
case 0x220888a2: blink=blink_delay; dbg_putchar('#'); break;
default: break;
}
ir.code = 0;
//===================================================================

}
}
}

Итоговая схема получилась такая:

Схема таймера

Первую версию собрал на макетной плате с использованием кусков оргстекла в качестве корпуса.

сборка

Блок питания купил самый простой на 12В 500мА в местном магазине.

Пультик заказывал на ebay.

сборка

Вот результат:

полученное изображение

Таймер используется для информирования говорящего с кафедры об отведенном времени.

использование таймера

В планах — переделать на stm32, уместить в один контроллер, оформить в корпус покрасивее.

Спасибо за внимание.
Поделиться с друзьями
-->

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


  1. impetus
    24.05.2016 14:03
    +6

    И, хотя сама работа отличная и полезная, и автору респект, как за изделие, так и за нормальное его описание — определить теперь где хабр где ГТ уже само НЛО наверное не в состоянии


    1. safari2012
      24.05.2016 17:32
      +3

      думаю как-то так:
      arduino — geektimes
      arv (чистый) — habr


      1. abrakada
        24.05.2016 20:41

        Думаю что нет, на ардуино полно весьма приличных проектов и решений. Прошли те времена, когда только светодиодом там мигали.


      1. custos
        24.05.2016 21:36

        Тем не менее, раздел DIY на Geektimes, и не понятно что делать, когда DIY касается микроконтроллера, поскольку хаб о его программировании на Habrahabr. Кто бы разъяснил...


    1. Joric
      24.05.2016 19:22
      +2

      Да сразу было понятно, что разделение это абсолютно бессмысленное (аудитория там одна и та же), недавняя смерть «мегамозга» это только потдвердила. Я думаю, скоро гигтаймс сольется с хабром (или наоборот, пофигу). Имхо, НЛО — дура!


      1. Janom
        24.05.2016 23:37

        Ну, тут неоднозначно… Я рад, что всякий мусор, который ежедневно, тоннами сваливают всем известные персонажи, стало проще фильтровать, персонажи переехали на гт, а сюда все больше в гости заходят. И в тоже время, как-то не хватает нормального DIY (не диодом конечно мигать, но просто прикольные железки с достойным описанием как она работает, будь-то хоть сколько оригинально, а не перепост любой ссылки с первой страницы гугла, по правильному запросу) и науч-поп хабов, типа Космонавтика…
        Я бы оставил как было изначально — все на хабре, но дал пользователям возможность настроить фильтр ленты как ему удобно.


  1. ToSHiC
    24.05.2016 17:51

    Кстати, на вашей картинке, которая показывает эффект девятого бита, отлично бы помог трюк от того же TellyMate с дублированием восьмого бита. Мне кажется, она стала бы намного лучше, став лишь немного более кривой на наклонных переходах цвета.


    1. antonluba
      24.05.2016 18:14

      Прикольно


  1. lorc
    24.05.2016 18:33

    Я когда-то делал нечто похожее на AT91SAM7S, только для VGA. Хорошо что там был DMA и более вменяемый SPI.
    Но мне пришлось выделить под frame buffer бОльшую часть доступной RAM, потому что в frame buffer пришлось хранить не только само изображение, но и невидимые части (все четыре porch'а). Только таким образом удалось избежать срывов синхронизации.


    1. antonluba
      24.05.2016 19:04

      Простым решением было бы переписать вывод видео на STM8. Он по функциям почти аналогичен, но SPI непрерывный. Я даже получил лицензию на компилятор COSMIC.
      Но потом понял, что лучше сразу сделать на STM32. На данный момент сделал вывод синхросигнала и немного исследовал SPI, DMA и контроллер прерываний.
      Выглядит так, что тайминги можно выдерживать с помощью таймера синхронизации (с его 4 каналами), один канал вырабатывает сами синхроимпульсы, второй — настроен на backporch и пинает DMA на вывод строки. Или запускает второй таймер, который, в свою очередь, несколько раз пинает DMA c периодом, равным ширине каждого символа в строке.
      В деталях еще не продумал, периферия очень мощная, изучаю потихоньку.


      1. lorc
        24.05.2016 19:28

        Да, чем хороши STM32, так это развитой периферией. Она там намного лучше чем в тех же AT91SAM7x, и уж явно намного круче AVRок. И это при сопоставимой с AVRками цене.
        Думаю, на STM32 выйдет реализовать вменяемый frame buffer. А если бы там было три SPI или один quad SPI, то можно было бы сделать даже трехбитный цвет.


        1. antonluba
          24.05.2016 19:47

          Цвет я бы сделал 8-битный, внешним кодеком, даже заказал пару микросхем.
          Можно так же через DMA выводить байты в порт, к которому подключен простой R2R ЦАП.
          Только для хранения цветного изображения нужно гораздо больший объем флеша, тут бы сжатие не помешало какое-нибудь быстрое.
          Другой вариант — сделать мультиплексор и подавать черно-белый сигнал на разные входы кодера цвета.

          Только в практическом устройстве это вряд ли нужно.


        1. antonluba
          24.05.2016 23:29

          И кстати, frame buffer как таковой в этом таймере отсутствует.

          Есть буфер, где хранятся значения символов — 5 байт верхней строки и 8 — нижней (только второе двоеточие и секунды не отображаются, они нужны для хода часов).

          Для верхней строки символы могут принимать значения цифр от 0 до 9 и двоеточия. Для нижней строки добавляется пустое двоеточие, то есть символ такого же размера, как двоеточие, но пустой, для гашения двоеточия. Битмапы этих символов хранятся во флеше (см. описание шрифтов).

          На каждом цикле вывода видео-строки есть вложенный цикл вывода линии текущего символа. В этом цикле по цифре в буфере и номеру линии (y-координата символа) вычисляется начальный адрес, с которого начинается вывод, а дальше просто в SPI кидаются байты, пока вся ширина символа не выведется.

          Таким образом расход ОЗУ вообще минимален, а флеш забит огромным шрифтом.


  1. andrewsh
    24.05.2016 19:01

    Совет: используйте GIMP и формат XBM для графики. Очень помогает (я делал похожий проект на AVR, потом на STM32).


  1. mmMike
    25.05.2016 07:03

    Ностальгия…
    Как то подобное и на рассыпухе (обычная K155 серии «логика» + ПЗУ) один мой знакомый делал. Вот это был мазохизм.

    Еще один кусочек который можно добавить — это передатчик. Хоть на одном транзисторе (на метровый диапазон).
    Тогда даже кабеля к телевизору не надо. В радиусе 10-20 метров нормально сигнал ловится.


  1. Armleo
    25.05.2016 14:39

    Рекомендую еще попробовать сделать то же самое на FPGA. Опыта и удовольствия получите в разы больше. Кстати, я тоже делал похожую штуку только под VGA. Внешний ПЛИС генерировал синхроимпульсы, и выводил данные с SRAM чипа, а МК на порчах записовал.


    1. antonluba
      25.05.2016 19:38

      Обязательно попробую. Я даже заказал пару микросхем sram по 128 кБайт каждая, флешек от биосов у меня и так полно, есть кодеры цвета, есть макетка на Altera MAX2 и Циклоне 1.

      Хочу еще с какой-нибудь AVR с интерфейсом внешней памяти поиграться.

      Времени только мало.


      1. Armleo
        26.05.2016 16:11

        Можно информацию о SRAM чипе? А что за макетки Altera MAX II и Cyclone I? самодельные?

        «Хочу еще с какой-нибудь AVR с интерфейсом внешней памяти поиграться.»
        Не совсем понял. Что вы имеете ввиду? чипы памяти подключить к AVR или с интерфесом внутри AVR поиграться? (видимо пора спать).


        1. antonluba
          26.05.2016 20:35

          Память взял первую попавшуюся.
          Макетка MAX2

          Макетки с циклоном такие, как у меня, уже не продаются. Вот здесь описание.

          К интерфейсу внешней памяти (а это шина адреса и шина данных) хочу подключить плис и сделать какое-нибудь внешнее устройство. Тот же видеоадаптер или вывод звука.


          1. Armleo
            28.05.2016 09:43

            Насколько мне известно в AVR8 нет апаратной шины адреса или данных.
            Лучше сделать все в плисе. Ядро аврок есть, возможно придется доделать, но они рабочие. С внутренним ядром можно всякое творить, например много много pwm или вывести его вообще.

            Чип от итачи не рекомендую поскольку они не дадут вывести тру 640 на 480 при 60 фпс 8 бит цвет. У них скорость ~14MHz, а с кешом это 14МегаБайт в секунду а надо 18 МегаБайт с кешом или 25 МегаБайт без кеша. Можно конечно уменшить разрешение, скажем 320 на 240 с 60 фпс, но это не ахти.
            В MAX II авр не поместить, но VGA поместится.

            Поидее можно вывести видеопамять на внутренний авр. Только там 64кб макс, а с перефирией 60кб максимум.


            1. Armleo
              28.05.2016 09:56

              Эх забыл сказать. У чипов итачи ножки 5V и питание соответсвенно. А у плис только 3.3V. Будут проблемы с уровнями.