Проект інфо-панелі оповіщення про аварії (Частина 2)

    Вітаю всіх.
У першої частини була розглянута концепція самої інфо-панелі, яку я збираю для офісу (де, нагадаю, утруднений нормальний моніторинг за деякими серверами, в тому числі, через топології мережі — сіра підмережа з частиною важливого обладнання, якому не потрібне вихід в інтернет, але від його роботи залежить працездатність послуг телефонії, зокрема, TDM і E1 ).
Тепер накопичилося достатньо матеріалу для продовження теми.
 
Ось так виглядають індикатори:
 
 
У закінченні проекту буде приведений весь код з поясненнями.
Код поширюється по ліцензії WTFPL .
За інформацію за ліцензією, а так само за код для Ethernet модуля ENC28J60 ще одне спасибі Lifelover .
 
Проект ще не закінчений, далі буде продовження.
 
На жаль, на поточний момент пристрій ще не зібрані, і довелося досить багато чекати (при розробці зіткнувся з неприємними труднощами як при виготовленні плат, так і при закупівлі деяких расходников).
Чи готові модулі індикації (2 з 3), хоча працювати можна і тільки з ними. Частково зібрана «бібліотека» роботи з LED-дисплеєм.
Поки немає інтерфейсу взаємодії між двома контролерами (тут питання до хабровчанам, як зробити краще — сам схиляюся до реалізації через UART).
 
Кого зацікавив — ласкаво просимо під кат.
Попередження: Багато фото.
 
 
На поточний момент добре працює тільки модуль, що відповідає за виведення інформації на дисплей.
Реалізована динамічна індикація (частота оновлення порядку 125Hz). Модуль управляється з контролера ATmega48 (на мекете — ATmega1284p ). Обидва працюють на тактовій частоті 12МГц і харчуванні 3.3V (для сумісності з microSD картою і Ethernet модулем). Харчування модуля індикації — має становити 5V 0 ~ 2.2А/модуль, тобто трохи більше 10W при повному заповненні екрана.
При типовій роботі (наприклад, режим годин / календаря) пристрій буде споживати мінімум потужності).
На даний момент питання харчування актуальний, але схиляюся до варіанту:
48V (PoE) -> 5V (Fly-Back) -> 3.3V (LDO).
Ланцюг живлення 3.3V за розрахунками не повинна споживати більше 0.5А струму (спільна робота Ethernet модуля (~ 150-250мА) і 2 контролерів (навряд чи більше 20мА), SD карти (не заміряв, але навряд чи більше 50мА), годинника реального часу (DS1307), хоча останні планую живити від 5V через фільтри харчування).
 
Зараз зібрані два модулі іднікаціі, то ж фото, що в заголовку, але без кадрування. Фото нижче.
 Прихований текст Два модуля індикації заробили разом.
 
Загальний концепт роботи пристрою був дещо переглянутий з першого поста. Тепер воно буде складатися з двох процесорів, як водиться, Master (ATMefa1284p) і Slave (ATMega48). Плата ЦП ще не готова (тому поки не повністю обрана перефирія і не хочеться переробляти все по кілька разів). Зараз залишилися деякі питання по звуку, наприклад, чи варто взагалі морочитися з відтворенням потокового аудіо (8bit, ~ 22kHz) прямо з мережі, або простіше відкинути ідею? Варіант з читанням звукових файлів з SD карти — принадний, але тут виникає проблема управління ними, а так само (можливо, завантаження їх на карту прямо з мережі). Можливо, зупинюся на другому варіанті.
Майстер відповідатиме за підтримку мережі, звук (можливо), а так само (можливо) найпростіший telnet доступ.
Пізніше постараюся реалізувати обнолвеніе ПО по мережі (якщо вистачить прямоти рук), або з SD-карти (швидше, uSD в режимі SPI). Руки не доходять зібрати макетке.
 
Плати індикаторів трохи раніше
 Прихований текст
 
Знаю, досить страшно.
Таких буде 3.
Плата електроніки — в процесі… поки не виходить витравити. Фоторезист змивається при прояві.
Фото чергової невдачі: (Тепер Positiv 20). Схоже, цей метод мені ще не дається.
 Прихований текст
Найкраще, що вдалося зробити на поточний момент… до нещастя, теж пошкоджена. Патьоки і нерівний шар резиста зробили свою підступну справу.
Найчастіше виходить щось подібне:
 
ПН-ВЩ-50 трирічної витримки — це… дуже сумно.
Спочатку планувалася одна плата з перехідниками… надалі від перехідників вирішено було повністю відмовитися і перейти на щось більш зручне. Вибрав монтажну плату з проводками. Грубувато, але проблему вирішує.
Плата електроніки в зборі
 Прихований текст
Вже припаяна сама плата, масив тразністорних ключів, зсувні регістри.
 
Навіть всі рядки і стовпці працюють.
 
У процесі налагодження.
З якоїсь причини рядок виводиться з помилками. (Застарілий)
 
 upd 2014-05-11 : Вдалося перемогти «драйвер» дисплея, отримавши м'який скролл рядки по всьому «екрану».
На даний день невелику перемогу можна подивитися тут .
Прошу вибачення за посилання на ВК, але іншого місця для заливки відео поки не знайшов. Якщо хто підкаже — буду вдячний.
 Готується друга плата індикатора — вже зібрані всі компоненти, залишилося спаяти самі матриці.
Код… поки не терпить критики, тому буде допілівать. Хоча вже зараз можна довільно змінювати розмірність екрану і це не вплине на його працездатність (в розумних межах).
Використання RAM — мінімально. Тільки під буфер тексту (або графіки, але на 1 кадр), і кілька пенеменних. Цілком укладаюся в доступну RAM в 512 байт.
 upd 2014-06-02 : Чи готові два індикатори, код масштабується на індикатори в пару рухів — змінити один дефайн і пересобрать проект. Фото було вище.
Код управління працює, але поки не заточений під обробку двома контролерами. На макетної платі працює тільки на одному контролері, другий поки чекає своєї черги. На 48-й Меге немає JTAG'а, тому налагоджувати на залозі буде значно складніше. Намагаюся виправити всі баги ще до збірки плати контролерів.
 
Виникло питання, чи можна оновити ПЗ «сусіднього» контролера з flash накопичувача. Найзручніше (з того, що легко підключити — microSD / SPI, але доведеться писати свій завантажувач). Зараз обмірковую варіант прошивки Slave'а через стантарнтий ISP інтерфейс, підключений до Master'у (доведеться виводити роз'єми для програмування на обидва контролера, а так само джампери, щоб Master міг оновлювати ПО «драйвера екрану».
 
Трохи коду:
 led.h
/*
Модуль управления LED матрицами для AlarmPanel.
*/
// Поле: (3*9)*8
// Хранить будем 27*8 = 216 байт.

#define MATRIX_COUNT	16			// Количество индикаторов
#define ARRAY_SIZE	MATRIX_COUNT * 8	// Суммарный объём памяти для массива.
#define VisibleChars	ARRAY_SIZE / 6

#define LED_MODE_OFF		0
#define LED_MODE_TXT_SCROLL	1
#define LED_MODE_TXT_NO_SCROLL	2
#define LED_MODE_IMG		3

#define LED_MODE_MAX LED_MODE_IMG

void LED_Reset(void);
	// Сбрасывает все индикаторы. Должна вызываться, когда нужно погасить индикацию.
void LED_PushBits(unsigned char DataByte, unsigned char Bits);
	// Проталкивает байт в строку.
void LED_FirstLine(void);
	// Сброс и выбор первой строки.
void LED_NextLine(void);
	// Подготовка новой строки (переключение вертикальной защёлки)
void LED_Sync(void);
	// А эта "интересная" команда - ни что иное, как дерганье пином "Latch" на всех сдвиговых регистрах. Позволяет избежать "дребезга".
//char HEX(unsigned char dataNibble);
//	// Преобразование числа в HEX формат (принимаются значения [0..15], возвращается номер символа).
void LED_DrawChar(uint8_t CharN, uint8_t LineNum, /*uint8_t SkipCols,*/ uint8_t ShowCols);
	// Вывод символа на дисплей. При этом можно пропустить несколько столбцов. (НомерСимвола, НомерСтроки, ПропускСтолбцов)
void LED_DrawString(void);
	// Регенерация строки. Должно выполняться каждую 1ms (1000/8 = 125Hz), в крайнем случае, каждые 2мс (500/8 = 62.5Hz)
//void LED_NewString(alarm_packet_t *data);
	// Получение новой строки из сети. Должна получать ссылку на память, откуда забирать, и количество, которое забирать.
void LED_DrawImg(void);
	// Вывод изображения. При этом не будут работать никакие переменные, отвечающие за обработку строк. Только номер текущей строки и счётчик.
	// В этот раз будем считать, что нужно вывести 8 бит из байта (то есть, ВЕСЬ).
void LED_Poll(void);
	// Собственно, "обработчик", который будет вызываться каждый тик таймера.
	// Да, он сам решит, что делать.
	
// Знакогенератор.
// Вырезан - занимает ОЧЕНЬ много места в посте.

 led.c
#include "led.h"
//#define TextScrollMS 50
uint8_t DataArray[ARRAY_SIZE];		// Массив символов.
uint8_t CurrMode;			// Текущий режим отображения: {0=Выключено, 1=Текст со скроллом, 2=Текст без скролла, 3=Графика}
uint8_t CurrLength;			// Текущий размер массива (актуально для текста).
uint8_t CurrLine;			// Текущая строка
int16_t CurrSkipChars;			// Количество прокручиваемых символов
// Пропускать можно более чем 127 символов (ибо буфер строки больше 128-ми), так что... int16.
uint8_t CurrColSkip;			// Позиция сдвига внутри символа (дополнительная прокрутка)

volatile uint8_t TextScrollDelayMS;
volatile uint16_t LastMS;

void LED_SetMode(uint8_t NewMode)
{
	if (NewMode <= LED_MODE_MAX)
	{ CurrMode = NewMode;
	} else { CurrMode = LED_MODE_OFF; };
};

void LED_Poll(void){
	if (CurrMode == LED_MODE_OFF) { return; };
	if (CurrMode == LED_MODE_TXT_SCROLL) { LED_DrawString(); };
	if (CurrMode == LED_MODE_TXT_NO_SCROLL) { LED_DrawString(); };
	if (CurrMode == LED_MODE_IMG) { LED_DrawImg(); };
};

void LED_Reset(void) {
	char t;
	CurrLine = 0;
	CurrSkipChars = -VisibleChars;
	LED_PORT |= (1<<LED_OE);			// Переводим выходы всех регистров из Z-состояния
	LED_PORT |= (1<<LED_DATA_V);			// Поднимаем пин данных.
	for (t=0;t<8;t++)
	{
		LED_PORT |= (1<<LED_C_V);		// Проталкиваем 0 в вертикальный регистр
		LED_PORT &= ~(1<<LED_C_V);		// Повторяем, пока он не закончится.
	};
	LED_PORT &= ~(1<<LED_OE);			// Z-состояние убираем..
};

void LED_PushBits(unsigned char DataByte, unsigned char Bits) {
	while (Bits > 0)				// Для оставшихся битов
	{
		Bits--;					// Отсчитываем 1 колонку
		if (DataByte & 0x80)			// Если старший бит = 1 (после сдвигов)
		{
			LED_PORT |= (1<<LED_DATA_H);	// Поднимаем пин "Data"
		} else {
			LED_PORT &= ~(1<<LED_DATA_H);	// Иначе - опускаем его
		};
		LED_PORT |= (1<<LED_C_H);		// И дёргаем такт для горизонтальной синхронизации
		LED_PORT &= ~(1<<LED_C_H);		// Проталкиваем 1 бит в горизонтальные регистры.
		DataByte <<= 1;				// Сдвигаем оставшиеся биты влево на 1.
	};						// Повторяем, пока не закончатся колонки.
};

void LED_FirstLine(void) {
	LED_PORT &= ~(1<<LED_DATA_V);			// Опускаем пин данных
	LED_PORT |= (1<<LED_C_V);			// Дёргаем тактирование
	LED_PORT &= ~(1<<LED_C_V);			// Опускаем такт.
	LED_PORT |= (1<<LED_DATA_V);			// Поднимаем пин обратно
//	LED_Sync();					// Синхронизация
};

void LED_NextLine(void) {
	LED_PORT |= (1<<LED_C_V);			// Проталкиваем бит в вертикальный регистр
	LED_PORT &= ~(1<<LED_C_V);			// ОДИН раз.

	CurrLine++;					// Следующая строка
	CurrLine &= 0x07;				// Только 7 строк
							// Если произошёл переход на новую строку, то
	if (CurrLine==0)
	{
		LED_FirstLine();
		// Если прошло времени больше, чем нужно ждать для скролла, то
		if (((ticks_count - LastMS) > TextScrollDelayMS)|(ticks_count < LastMS))
		{
			LastMS = ticks_count;		// Запоминаем новое время
			if (CurrMode == LED_MODE_TXT_SCROLL)
			{
				CurrColSkip--;			// Прокручиваем на 1 колонку
				if (CurrColSkip == 255)		// Целочисленное переполнение, ибо меньше нуля..
				{
					CurrColSkip = 5;	// Теперь пропускаем все 5/6 столбцов...
					CurrSkipChars++;	// И добавляем 1 к пропущенным байтам из буфера.
				};
								// Если мы выползли ЗА буфер
				if (CurrSkipChars > CurrLength)
				{
								// Начинаем с самого начала (из-за правой стороны)
					CurrSkipChars = -VisibleChars;
				};
			} else {
				CurrColSkip = 0;
				CurrSkipChars = 0;
			};
		};
	};
};

void LED_Sync(void) {
	// Заготовка
	LED_PORT |= (1<<LED_OE);			// Переводим выходы всех регистров в Z-состояние
	LED_PORT |= (1<<LED_LATCH);			// Защёлкиваем данные
	LED_PORT &= ~(1<<LED_LATCH);			// Хватит защёлкивать. Опускаем пин.
	LED_PORT &= ~(1<<LED_OE);			// Разрешаем выводам работать. Возможно предосторожность изляшняя.
};

void LED_DrawChar(uint8_t CharN, uint8_t LineNum, /*uint8_t SkipCols,*/ uint8_t ShowCols)
{
	unsigned char temp = 0;
	unsigned char cols;
	cols = ShowCols;				// Количество отображаемых колонок
	if (cols == 0)
	{
		return;
	};
	if (CharN == 168) {				// Ё
		temp = pgm_read_byte(&CharsRu[65][LineNum]);
	};
	if (CharN == 184) {				// ё
		temp = pgm_read_byte(&CharsRu[66][LineNum]);
	};
	if (CharN > 191) {				// Русские символы
		temp = pgm_read_byte(&CharsRu[CharN-192][LineNum]);
	};
	if ((CharN > 32)&(CharN <= 126)) {		// Английские символы и некоторые знаки
		temp = pgm_read_byte(&Chars[CharN-32][LineNum]);
	};

	// Экономия RAM. Было бы быстрее из памяти, но так всего +1 такт (формально, 1 такт.... вероятно, больше).
	LED_PushBits(temp, cols);			// Оставшееся выводим на экран
};

void LED_DrawString(void)
{
	int16_t First;					// Первый видимый символ
	int16_t Last;					// Последний выводимый символ
	int16_t SymbolsSkip;				// Количество пропускаемых символов
							// Так же работает, как количество добавляемых символов.
	uint8_t i;					// Переменная "i" - более 40 лет на рынке счётчиков!
	uint8_t EndedWithChar;				// Флаг, так его растак... Но без него никак.
	SymbolsSkip = CurrSkipChars-1;			// Копируем количество пропускаемых символов
	while (SymbolsSkip < 0) {
		SymbolsSkip ++;				// Пока количество не сравняется с 0
		LED_PushBits(0, 6);			// Проталкиваем нули (гасим строку).
	};
							// Нужное количество знакомест пропущено.
							// Вычисляем ПЕРВЫЙ символ, который нужно отобразить.
	if (CurrSkipChars <= 0)				// Если нужно добавить пустых знакомест
	{
		First = -1;				// Начинаем показ с самого первого символа.
	} else {
		First = CurrSkipChars-2;		// Иначе - количество пропускаемых символов-2
							// Чтобы самые дальние символы не исчезали перед покиданием
							// индикатора.
	};
	Last = First + CurrSkipChars + VisibleChars;	// Вычисляем последний символ.
	// Если последний символ всё же больше нуля (в случае полностью сдвинутой строки)
	if (Last > First)
	{
		// Попытка вывести на экран больше, чем есть в массиве
		if (Last > CurrLength-1)
		{
			// Получаем правильную позицию последнего символа.
			Last = CurrLength-1;
			// Теперь нужно вычислить, сколько необходимо пустого пространства.
			// Вычисляем, как (Общая длинна видимых символов) + Текущий сдвиг - Последний видимый символ - 1
			// -1 - ибо нумеруем, как нормальные люди, с нуля.
			SymbolsSkip = VisibleChars + CurrSkipChars - Last - 1;
		};
		if (SymbolsSkip > 0)
			{ EndedWithChar = 0; } else { EndedWithChar = 1; };
		// Конструкция выше - это проверка, закончится ли строка символом из массива, или нет.
		// Если нужно вывести более 1 символа...
		for (i = First+1; i < Last; i++)
		{
			// Выводим символы, которые можем без обработки.
			LED_DrawChar(DataArray[i], CurrLine, 6);
		};
		// Выводим последний... По идее, правильно.
		if (EndedWithChar)
		{
			LED_DrawChar(DataArray[Last], CurrLine, 6-CurrColSkip);
		} else {
			LED_DrawChar(DataArray[Last], CurrLine, 6);
		};

		// Если нужно дополнить пробелами в конце..
		if (SymbolsSkip > 0)
		{
			// В количестве чуть МЕНЕЕ чем нужно пропустить
			// целых символов,...
			for (i=1;i<SymbolsSkip;i++)
			{
				// ...выводим пустые знакоместа.
				LED_PushBits(0, 6);
			};
			// Если что-то остаётся - выводим заполнение.
			LED_PushBits(0, 6-CurrColSkip);
		};
	};
	LED_Sync();					// Синхронизируем.
	LED_NextLine();					// И переводим строку.
};

void LED_DrawImg(void)
{
	uint8_t i;					// Счётчик
	// Диапазон значений - от Номера текущей строки*8 (адрес первого байта)
	// и до следующей строки.
	for (i = CurrLine*MATRIX_COUNT; i<CurrLine*(MATRIX_COUNT+1); i++)
	{
		// Выводим символ на дисплей... Полностью, все 8 бит.
		LED_PushBits(DataArray[i], 8);
	};
	// Фактически, при этом будет выведено только MATRIX_COUNT байт.
	// Довольно быстрая процедура.
	LED_Sync();					// Синхронизируем.
	LED_NextLine();					// И переводим строку.
};

    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.