Контроллер антенного переключателя на 8 антенн

Версия 1.2

…без этой вашей адурины!

Давно возникла необходимость иметь антенный переключатель для КВ антенн, которые хоть и медленно, но начинают появляться. Как правило, антенные переключатели состоят из двух частей: собственно, самого переключателя, и блока управления (контроллера). С первым проблем обычно не возникает, да и со вторым тоже, если бы не одно НО — возможность управления с ПК. Вот тут начинается интересное (кто постоянно читает мой блог — два путя). Первый: сходить на Хабр или на сайт к RW9JD, взять Arduino, всё собрать и прошить, воткнуть разъем от трансивера с Band Data и успокоиться. Ага, щаз..! Во-первых — переключение антенн происходит по команде с трансивера, а не с ПК. Во-вторых — использовать в таком простом проекте эту вашу адурину равносильно доставке единственного мешка картошки (причем рассыпаного по всей площади кузова) на большом грузовике; очень нерациональное использование ресурсов. Поэтому идем по второму пути: берем САПР Proteus, подключаем WinAVR, создаем проект на Attiny2313 и пишем прошивку на чистом Си.

Итак. Реализация контроллера переключателя антенн следующая: к микроконтроллеру Attiny2313 подключено восемь кнопок, управление восемью реле осуществляется через сдвиговый регистр 74HC595, индикация светодиодная (какая ж еще?), связь с ПК по UART либо через преобразователь RS232/UART, либо через преобразователь USB/UART — как в моем случае на  микросхеме FT232; скорость соединения 9600 бит/с. Схема устройства ниже.

Прошивка, как уже говорилось, написана на чистом Си для WinAVR. Исходный код первой версии ниже.

/*****************************************************
Project : Antenna Switch
Version : 1.2
Date    : 23.03.2021
Author  : R4ADX
Company : Unlis
Comments: 

Chip type           : ATtiny2313
Clock frequency     : 11,059200 MHz
Memory model        : Tiny
External SRAM size  : 0
Data Stack size     : 32
*****************************************************/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define SH PD4 								// Тактовый выход 74HC595
#define DS PD3 								// Выход данных 74HC595
#define ST PD2 								// Синхронизация
#define F_CPU 11059200							// Тактовая частота MCU
#define baudrate 9600L							// Скорость передачи USART
#define baud (F_CPU/(16*baudrate)-1)					// 

unsigned char tab[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; // Коды антенных портов
unsigned char count;							// Счетчик для клавиатуры
unsigned char temp;
volatile unsigned char antenna;						// Активный антенный порт
unsigned char buf[2];							// Буфер приема UART
unsigned char addr = 0;
volatile unsigned char enabled;						// Разрешенные антенные порты

/* Функция передачи байта по UART */
void USART_Transmit(unsigned char data) {
	while (!(UCSRA & (1<<UDRE)));
	UDR = data;
}

/* Функция записи в EEPROM */
void EEPROM_write(unsigned int uiAddress, unsigned char ucData) {
	while(EECR & (1<<EEPE)) {}; 					// Ожидание окончания предыдущей записи
	EEAR = uiAddress;						// Установка регистра адреса
	EEDR = ucData;							// Установка регистра данных
	EECR |= (1<<EEMPE);						// Подготовка к записи
	EECR |= (1<<EEPE);						// Начало записи в EEPROM
}

/* Функция чтения из EEPROM */
unsigned char EEPROM_read(unsigned int uiAddress) {
	while(EECR & (1<<EEPE)) {}; 					// Ожидание окончания предыдущей записи
	EEAR = uiAddress; 						// Установка регистра адреса
	EECR |= (1<<EERE); 						// Начало чтения из EEPROM
	return EEDR;							// Возврат регистра данных
}

/* Функция вывода данных в сдвиговый регистр 74HC595 */
void shift_595(antenna) {
	for (count = 0; count < 8; count++) {
		PORTD =(antenna & 0x80) ? PORTD | (1<<DS) : PORTD & ~(1<<DS);
		PORTD |= (1<<SH);
		_delay_us(2);
		PORTD &= ~(1<<SH);
		antenna = antenna <<1;
		}
	PORTD |= (1<<ST);
	_delay_us(2);
	PORTD &= ~(1<<ST);
}

/* Отправка переключенной антенны в UART */
void S_to_PC() {
	USART_Transmit('S');						// Шлем в порт S
	USART_Transmit(antenna);					// Шлем в порт адрес антенны
}

/* Обработчик приемного буфера */
void from_PC(unsigned char *data) {
	switch (data[0]) {
		case 0x49:	USART_Transmit('I');			// I - Инициализация коммутатора программой с ПК. Шлем в порт I 
				USART_Transmit(enabled);		// Шлем в порт байт разрешенных антенн
				S_to_PC();				// Вызываем функцию для отсылки адреса антенны
				break;
		case 0x53:	antenna = data[1];			// S - Переключение антенн с ПК
				shift_595(antenna);			// 	...переключили
				EEPROM_write(0x01, antenna);		// 	...запомнили
				break;
		case 0x57:	enabled = data[1];			// W - Запись разрешенных антенн
				EEPROM_write(0, enabled);
				break;
	}
}

int main(void) {

/* Настройка портов */
	DDRB = 0x00; 							// PortB на вход
	PORTB = 0xFF; 							// Подтяжка
	DDRD |= (1<<SH) | (1<<DS) | (1<<ST); 				// PortD HC595 на выход
  
/* Настройка UART */
	UBRRH = (unsigned char)(baud>>8); 				// Скорость порта
	UBRRL = (unsigned char)baud;					//
	UCSRB = (1<<RXEN) | (1<<TXEN) | (1<<RXCIE); 			// Разрешение прием и передачу через USART. Разрешение прерывания по приему
	UCSRC = (0<<USBS) | (1<<UCSZ0) | (1<<UCSZ1); 			// Режим UART: 8n1 
	enabled = EEPROM_read(0x00);					// Читаем разрешенные антенные порты
	antenna = EEPROM_read(0x01); 					// Последняя включенная антенна
	shift_595(antenna);						// Включаем антенну
	sei();

while(1)
	{
m1:		temp = PINB;  
		for (count = 0; count < 8; count++) {			// Цикл сканирования клавиатуры
			if ((temp&1) == 0) { 				// Проверка младшего бита
			_delay_ms(10); 					// Антидребезг
			while ( PINB != 0xFF ) {};	 		// Ждем отпускания кнопок
			goto m2;
			}
			temp >>= 1; 					// Сдвигаем для проверки следующий бит
		}
		goto m1;						// Идем в начало сканирования клавиатуры
m2:		if ((enabled & (1<<count)) != 0 ) {			// Проверка разрешено ли включать антенный порт
			antenna = tab[count]; 				// Запись значения антенны
			shift_595(antenna);				// Переключаем антенные реле
			S_to_PC();					// Отправляем в UART
			EEPROM_write(0x01, antenna); 			// Запоминаем что включили
		}
	}
}

/* Обработчик вектора прерывания по приему UART */
ISR(USART_RX_vect) {
	buf[addr] = UDR;						// Читаем UART в буфер
	addr++;								// Увеличиваем адрес буфера
	if (addr == 2) {
		from_PC(buf);						// Если заполнился -  вызываем обработчик приемного буфера
		addr = 0;  						// Сбрасываем адрес буфера.
	}
}

 

В начале управляющей программы происходит настройка портов ввода/вывода МК, подготовка USART, загрузка из EEPROM памяти байта разрешенных/запрещенных антенных входов и байта с адресом последней включенной антенной, происходит включение соответствующего антенного входа. Далее выполняется бесконечный цикл опроса клавиатуры, и в этом же цикле — процедура обработки нажатия кнопок, в котором происходит проверка на запрещенный антенный вход, вызов функции записи данных в сдвиговый регистр, сохранение адреса включенной антенны в EEPROM, вызов функции отправки данных о переключении по USART. При приеме байта данных по USART микроконтроллером вызывается прерывание, в котором осуществляется запись принятого байта в кольцевой буфер длиной 2 байта, при заполнении буфера происходит вызов функции его обработчика. Функция обработчика буфера по нулевому байту определяет тип сообщения: при поступлении команды инициализации МК формирует ответ, состоящий из заголовка, байта разрешенных антенн, байта с адресом включенной в данный момент антенны; при поступлении команды переключения антенн байт с адресом антенны записывается в ОЗУ, EEPROM, сдвиговый регистр 74HC595; при поступлении команды записи настроек разрешенных/запрещенных антенных входов происходит запись байта данных в соответствующее место ОЗУ, EEPROM. Формат обмена данными между программой ПК и микроконтроллером представлен в таблице.

Действие МК => ПК ПК => МК
Переключение антенн кнопками блока управления Заголовок. Байт 0: 0x53 (ASCII символ «S»)
Данные. Байт 1: адрес антенны
­
  Не передает данные
Инициализация контроллера программой ПК Заголовок. Байт 0: 0x49 (ASCII символ «I»)
Данные. Байт 1: Любой, но должен быть передан.
Заголовок. Байт 0: 0x49 (ASCII символ «I»)
Данные. Байт 1: байт разрешенных/запрещенных антенных входов
Данные. Байт 2: 0x53 (ASCII символ «S»)
Данные. Байт 3: адрес антенны
 
Разрешение/запрещение антенных входов программой ПК ­­­ Заголовок. Байт 0: 0x57 (ASCII символ «W»)
Данные. Байт 1: набор бит, соответствующих антенным входам.
Не передает данные  
Переключение антенн программой ПК ­ Заголовок. Байт 0: 0x53 (ASCII символ «S»)
Данные. Байт 1: адрес антенны
Не передает данные  

 

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

Программа управления почти написана на C# в среде Visual Studio.

Алгоритм управления следующий. При запуске программы происходит попытка инициализации микроконтроллера путем посылки байта 0x49 (ASCII код — I). При успешной инициализации (первый байт ответа также будет 0x49) отключаются кнопки тех антенн, которые были запрещены настройками, записанными в EEPROM контроллера, а также выбирается кнопка, соответствующая включенной в данный момент антенне. Если инициализация не удалась — все кнопки будут отключены. В статусной строке отображается состояние. При переключении антенн непосредственно с контроллера выбирается RadioButton, соответствующий выбранной антенне; при переключении антенн из программы в микроконтроллер посылается байт управления 0x53 и байт с адресом антенны.  При нажатии меню Settings открывается окно, где можно выбрать порт для подключения, переименовать антенны (названия хранятся в ini файле), а также разрешить/запретить антенные входы.


Версия 1.3

…по прежнему без этой вашей абдурины.

После опубликования исходного кода версии 1.2 (где версии 1.0 и 1.1 — без понятия) было высказано несколько замечаний относительно некоторых технических моментов:

  1. Отсутствие контроля CRC в пересылаемых по USART данных;
  2. Отсутствие подтверждения выполнения команд микроконтроллером;
  3. Постоянная запись в EEPROM значения переключенной антенны, да еще и в одну и ту же ячейку.

Для решения п. 1 размер пересылаемого пакета увеличен до трех байт, где последний байт — контрольная сумма первых двух байтов; использован алгоритм CRC-8/MAXIM (он же Dallas).

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

Действие МК => ПК ПК => МК
Переключение антенн кнопками блока управления Заголовок. Байт 0: 0x53 (ASCII символ «S»)
Данные. Байт 1: адрес антенны
Байт 2: Контрольная сумма CRC
­
  Не передает данные
Инициализация контроллера программой ПК Байт 0: Заголовок. 0x49 (ASCII символ «I»)
Байт 1: Данные.  Любой байт, но должен быть передан.
Байт 2: Контрольная сумма CRC
Байт 0: Заголовок. 0x49 (ASCII символ «I»)
Байт 1: Данные. Байт разрешенных/запрещенных антенных входов
Байт 2: Контрольная сумма CRC
<пауза>
Байт 0: Заголовок. 0x53 (ASCII символ «S»)
Байт 1: Данные. адрес антенны
Байт 2: Контрольная сумма CRC
 
Разрешение/запрещение антенных входов программой ПК ­­­ Байт 0: Заголовок. 0x57 (ASCII символ «W»)
Байт 1: Данные. набор бит, соответствующих антенным входам.
Байт 2: Контрольная сумма CRC
Байт 0: Заголовок. 0x57 (ASCII символ «W»)
Байт 1: Данные. набор бит, соответствующих антенным входам.
Байт 2: Контрольная сумма CRC
 
Переключение антенн программой ПК ­ Байт 0: Заголовок. 0x53 (ASCII символ «S»)
Байт 1: Данные. адрес антенны
Байт 2: Контрольная сумма CRC
Байт 0: Заголовок. 0x53 (ASCII символ «S»)
Байт 1: Данные. адрес антенны
Байт 2: Контрольная сумма CRC
 

Таким образом, любая посылка команд управления с данными (переключение, сохранение настроек) сопровождается дублированием этих данных в ответном сообщении после успешного выполнения команды микроконтроллером. Да еще и с CRC — мыш не проскочить!

Изрядно заставил задуматься п.3. Вариантов несколько:

  1. Программный. Так называемый wear leveling — равномерное использование ВСЕХ ячеек EEPROM памяти, то есть циклическая запись по очереди (при поиске или использовать ID записи, или затирать до FF прошлое значение после записи и по нему находить последнюю запись);
  2. Программно-аппаратный. Сохранение значения включенной антенны в момент выключения питания. Для этого устанавливается супервизор, который отслеживает напряжение на общей шине питания, при срабатывании выдает логический «0» на порт INT0 микроконтроллера (PD2), тем самым вызывая внешнее прерывание, которое в свою очередь сохраняет данные. Чтобы поддерживать достаточное напряжение в момент записи настроек, параллельно шине питания микроконтроллера устанавливается электролитический конденсатор соответствующей емкости (подбирается). Естественно, отключение резервного «источника питания» от остальной части схемы обеспечивает MOSFET с как можно меньшим сопротивлением открытого канала (чтобы не было падения напряжения при штатном питании от общей шины USB).
  3. Скорее аппаратный, чем программный — поставить внешнюю микросхему FRAM или EEPROM.
  4. Радикальный. Отказаться вообще от сохранения переключаемых антенн, при подаче питания не включать вообще ничего — все антенны будут заземлены.

Вариант d. самый простой, но не интересный. Вариант c. не предпочтителен ни разу — писать один байт в специально выделенную микросхему? Жирно что-то… Вариант a. очень интересен, но пока неясен алгоритм его работы. Совсем неясен. Вариант b. имеет огромное количество нюансов: на какое напряжение взять супервизор, чтобы с одной стороны не было ложных срабатываний из-за возможных просадок напряжения, с  другой стороны — чтобы при более низком «допустимом» напряжении питания микроконтроллер устойчиво работал (тактовая частота 11 МГц все-таки)? И какой должна быть емкость конденсатора, чтоб его энергии хватило для записи в EEPROM, а при зарядке он не сжег порт USB?

Легких путей мы не ищем, поэтому схема ниже:

Исходный код этой версии прошивки ниже

/*****************************************************
Project : Antenna Switch
Version : 1.3
Date    : 12.04.2021
Author  : R4ADX
Company : Unlis
Comments: 

Chip type           : ATtiny2313
Clock frequency     : 11,059200 MHz
Memory model        : Tiny
External SRAM size  : 0
Data Stack size     : 32
*****************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define SH PD5 									// Тактовый выход 74HC595
#define DS PD4 									// Выход данных 74HC595
#define ST PD3 									// Синхронизация
#define SPW PD2									// Вход супервизора
#define F_CPU 11059200								// Тактовая частота MCU
#define baudrate 9600L								// Скорость передачи USART
#define baud (F_CPU/(16*baudrate)-1)						// 

unsigned char tab[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; 	// Коды антенных портов
unsigned char count;								// Счетчик для клавиатуры
unsigned char i;
unsigned char temp;
unsigned char antenna;								// Активный антенный порт
unsigned char buf[3];								// Буфер приема UART
unsigned char addr = 0;
unsigned char crc_byte;								// Байт для побитного вычисления CRC
volatile unsigned char enabled;							// Разрешенные антенные порты

/* Функция записи в EEPROM */
void EEPROM_write(unsigned int uiAddress, unsigned char ucData) {
	while(EECR & (1<<EEPE)) {}; 						// Ожидание окончания предыдущей записи
	EEAR = uiAddress;							// Установка регистра адреса
	EEDR = ucData;								// Установка регистра данных
	EECR |= (1<<EEMPE);							// Подготовка к записи
	EECR |= (1<<EEPE);							// Начало записи в EEPROM
}

/* Функция чтения из EEPROM */
unsigned char EEPROM_read(unsigned int uiAddress) {
	while(EECR & (1<<EEPE)) {}; 						// Ожидание окончания предыдущей записи
	EEAR = uiAddress; 							// Установка регистра адреса
	EECR |= (1<<EERE); 							// Начало чтения из EEPROM
	return EEDR;								// Возврат регистра данных
}

/* Функция вывода данных в сдвиговый регистр 74HC595 */
void shift_595(antenna) {
	for (count = 0; count < 8; count++) {
		PORTD =(antenna & 0x80) ? PORTD | (1<<DS) : PORTD & ~(1<<DS);
		PORTD |= (1<<SH);
		_delay_us(2);
		PORTD &= ~(1<<SH);
		antenna = antenna <<1;
		}
	PORTD |= (1<<ST);
	_delay_us(2);
	PORTD &= ~(1<<ST);
}
/* Расчет CRC-8 по алгоритму MAXIM (Dallas) */
unsigned char crc8(unsigned char *buff, unsigned char bsize) {
	unsigned char crc = 0;
	for (count = 0; count < bsize; count++) {
		crc_byte = buff[count];
		for (i = 0; i < 8; i++) { crc = ((crc ^ crc_byte) & 0x01) ? (crc >> 1) ^ 0x8C : (crc >> 1);
		crc_byte >>= 1;
		}
	}
	return crc;
}

/* Функция обработки и отправки данных в USART */
void to_PC(unsigned char head, unsigned char body) {
	unsigned char buffer[3];						// Создадим буфер по образу и подобию нашему
	buffer[0] = head;							// Заголовок пакета
	buffer[1] = body;							// Данные пакета
	buffer[2] = crc8(buffer, sizeof(buffer) - 1);				// Вычислаем CRC буфера, кроме последней ячейки
	for (count = 0; count < sizeof(buffer); count++) {			// Скармливаем буфер в USART
		while(!(UCSRA&(1<<UDRE))){}; 					// Ждем готовность порта 
		UDR = buffer[count];						// Отправляем очередной байт
	}
}

/* Обработчик приемного буфера */
void from_PC(unsigned char *data) {
    unsigned char crc = crc8(buf, sizeof(buf));					// считаем CRC буфера полностью
    if (crc == 0) {								// Если сошлось - 
		switch (data[0]) {						// смотрим что в заголовке
			case 0x49:	to_PC('I', enabled);			// I - Инициализация коммутатора. Посылка разрешенных антенн
						_delay_ms(10);			//
						to_PC('S', antenna);		// Посылка включенной антенны
						break;
			case 0x53:	antenna = data[1];			// S - Переключение антенн с ПК
						shift_595(antenna);		// ...переключили
//						EEPROM_write(0x01, antenna);	// ...запомнили
						to_PC('S', antenna);		// ...отсылаем обратно для контроля
						break;
			case 0x57:	enabled = data[1];			// W - Запись разрешенных антенн
						EEPROM_write(0, enabled);	// в EEPROM
						to_PC('W', enabled);		// Отсылаем обратно для контроля
						break;
		}
    } else {									// Для отладки...
    PORTD |= (1<<PD6);								// При ошибке в CRC	
	_delay_ms(500);								// 
	PORTD &= ~(1<<PD6);							// мигаем светодиодом
    }
}

int main(void) {

/* Настройка портов */
	DDRB = 0x00; 								// PortB на вход
	PORTB = 0xFF; 								// Подтяжка
	DDRD |= (1<<SH) | (1<<DS) | (1<<ST); 					// PortD HC595 на выход
	DDRD &= ~(1<<SPW);							// Вход от супервизора
	DDRD |= (1<<PD6);							// Светодиод для отладки

/* Настройка прерывания INT0 */
	GIMSK |= (1<<INT0);							// Разрешаем внешнее прерывание от INT0
	MCUCR |= (0<<ISC00) | (0<<ISC01); 					// Прерывание по низкому уровню на входе INT0 

/* Настройка UART */ 
	UBRRH = (unsigned char)(baud>>8); 					// Скорость порта
	UBRRL = (unsigned char)baud;						//
	UCSRB = (1<<RXEN) | (1<<TXEN) | (1<<RXCIE); 				// Разрешение прием и передачу через USART. Разрешение прерывания по приему
	UCSRC = (0<<USBS) | (1<<UCSZ0) | (1<<UCSZ1); 				// Режим UART: 8n1 
	enabled = EEPROM_read(0x00);						// Читаем разрешенные антенные порты
	antenna = EEPROM_read(0x01); 						// Последняя включенная антенна
	shift_595(antenna);							// Включаем антенну
	sei();

while(1) {
m1:		temp = PINB;  
		for (count = 0; count < 8; count++) { 				// Цикл сканирования клавиатуры 
			if ((temp&1) == 0) { 					// Проверка младшего бита 
			_delay_ms(10); 						// Антидребезг 
			while ( PINB != 0xFF ) {}; 				// Ждем отпускания кнопок 
			goto m2; 
			} 
			temp >>= 1; 						// Сдвигаем для проверки следующий бит
		}
		goto m1;							// Идем в начало сканирования клавиатуры
m2:		if ((enabled & (1<<count)) != 0 ) { 				// Проверка разрешено ли включать антенный порт 
			antenna = tab[count]; 					// Запись значения антенны 
			shift_595(antenna); 					// Переключаем антенные реле 
			to_PC('S', antenna); 					// Отправляем в UART 
// 			EEPROM_write(0x01, antenna); 				// Запоминаем что включили 
		} 
	} 
}

/* Обработчик вектора прерывания по приему UART */ 
ISR(USART_RX_vect) { 
	buf[addr] = UDR; // Читаем UART в буфер 
	addr++; 								// Увеличиваем адрес буфера 
	if (addr > 2) {
		from_PC(buf);							// Если заполнился -  вызываем обработчик приемного буфера
		addr = 0;  							// Сбрасываем адрес буфера.
	}
}

/* Обработчик вектора прерывания по срабатыванию супервизора */
ISR(INT0_vect) {
	EEPROM_write(0x01, antenna);						// Сохраняемся при пропадании питания
}

 

Программа управления также подвержена модернизации


Версия 1.4

…и снова без этой вашей адурины.

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

Алгоритм прост — в секции инициализации производится поиск ячейки с байтом последней включенной антенны путем перебора всех адресов ячеек (от 1 до 127, нулевая ячейка, как известно, занята под другие нужды) до того момента, пока не будет найдено значение, отличное от 0xFF. Содержимое ячейки считывается в переменную, хранящую в себе текущее значение антенны, адрес же увеличивается на единицу для подготовки к записи нового значения в момент переключения. При переключении предыдущая ячейка EEPROM затирается, в текущую записывается новое значение выбранной антенны, указатель адреса увеличивается на единицу для подготовки к следующей записи.

Исходник в спойлере

/*****************************************************
Project : Antenna Switch
Version : 1.4
Date    : 27.04.2021
Author  : R4ADX
Company : Unlis
Comments: 

Chip type           : ATtiny2313
Clock frequency     : 11,059200 MHz
Memory model        : Tiny
External SRAM size  : 0
Data Stack size     : 32
*****************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define SH PD5 																// Тактовый выход 74HC595
#define DS PD4 																// Выход данных 74HC595
#define ST PD3 																// Синхронизация
#define SPW PD2																// Вход супервизора
#define F_CPU 11059200														// Тактовая частота MCU
#define baudrate 9600L														// Скорость передачи USART
#define baud (F_CPU/(16*baudrate)-1)										// 

unsigned char tab[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; 	// Коды антенных портов
unsigned char count;														// Счетчик для клавиатуры
unsigned char i;
unsigned char temp;
unsigned char antenna;														// Активный антенный порт
unsigned char buf[3];														// Буфер приема UART
unsigned char buf_addr = 0x00;												// Адрес буфера приема
unsigned char ee_addr = 0x01;												// Адрес ячейки EEPROM для сохранения включенной антенны
unsigned char crc_byte;														// Байт для побитного вычисления CRC
volatile unsigned char enabled;												// Разрешенные антенные порты

/* Функция записи в EEPROM */
void EEPROM_write(unsigned int uiAddress, unsigned char ucData) {
	while(EECR & (1<<EEPE)) {}; 											// Ожидание окончания предыдущей записи
	EEAR = uiAddress;														// Установка регистра адреса
	EEDR = ucData;															// Установка регистра данных
	EECR |= (1<<EEMPE);														// Подготовка к записи
	EECR |= (1<<EEPE);														// Начало записи в EEPROM
}

/* Функция чтения из EEPROM */
unsigned char EEPROM_read(unsigned int uiAddress) {
	while(EECR & (1<<EEPE)) {}; 											// Ожидание окончания предыдущей записи
	EEAR = uiAddress; 														// Установка регистра адреса
	EECR |= (1<<EERE); 														// Начало чтения из EEPROM
	return EEDR;															// Возврат регистра данных
}

/* Функция кольцевой записи в EEPROM */
void store(void) {
	unsigned char old_addr;													
	if (ee_addr == 0x01) {													// Если адрес будущей записи пепешел с конца на начало буфера -
		old_addr = 0x7F;													// адрес для затирания старой записи ставим на конец буфера
	} else {																// Если это не переход с конца на начало буфера -
		old_addr = ee_addr - 1;												// просто отнимем единицу
	}
	EEPROM_write(old_addr, 0xFF);											// Затираем ячейку с предыдущей записью
	EEPROM_write(ee_addr, antenna);		 									// Запоминаем что включили
	ee_addr++;																// Адрес для последующей записи
	if (ee_addr == 0x80) ee_addr = 0x01;									// Если дошли до конца - устанавливаем адрес на начало
}

/* Функция вывода данных в сдвиговый регистр 74HC595 */
void shift_595(antenna) {
	for (count = 0; count < 8; count++) {
		PORTD =(antenna & 0x80) ? PORTD | (1<<DS) : PORTD & ~(1<<DS);
		PORTD |= (1<<SH);
		_delay_us(2);
		PORTD &= ~(1<<SH);
		antenna = antenna <<1;
		}
	PORTD |= (1<<ST);
	_delay_us(2);
	PORTD &= ~(1<<ST);
}
/* Расчет CRC-8 по алгоритму MAXIM (Dallas) */
unsigned char crc8(unsigned char *buff, unsigned char bsize) {
	unsigned char crc = 0;
	for (count = 0; count < bsize; count++) {
		crc_byte = buff[count];
		for (i = 0; i < 8; i++) { crc = ((crc ^ crc_byte) & 0x01) ? (crc >> 1) ^ 0x8C : (crc >> 1);
		crc_byte >>= 1;
		}
	}
	return crc;
}

/* Функция обработки и отправки данных в USART */
void to_PC(unsigned char head, unsigned char body) {
	unsigned char buffer[3];												// Создадим буфер по образу и подобию нашему
	buffer[0] = head;														// Заголовок пакета
	buffer[1] = body;														// Данные пакета
	buffer[2] = crc8(buffer, sizeof(buffer) - 1);							// Вычислаем CRC буфера, кроме последней ячейки
	for (count = 0; count < sizeof(buffer); count++) {						// Скармливаем буфер в USART
		while(!(UCSRA&(1<<UDRE))){}; 										// Ждем готовность порта 
		UDR = buffer[count];												// Отправляем очередной байт
	}
}

/* Обработчик приемного буфера */
void from_PC(unsigned char *data) {
    unsigned char crc = crc8(buf, sizeof(buf));								// считаем CRC буфера полностью
    if (crc == 0) {															// Если сошлось - 
		switch (data[0]) {													// смотрим что в заголовке
			case 0x49:	to_PC('I', enabled);								// I - Инициализация коммутатора. Посылка разрешенных антенн
					_delay_ms(10);										//
					to_PC('S', antenna);								// Посылка включенной антенны
					break;
			case 0x53:	antenna = data[1];									// S - Переключение антенн с ПК
					shift_595(antenna);									// 		...переключили
					store();											// 		...запомнили
					to_PC('S', antenna);								// 		...отсылаем обратно для контроля
					break;
			case 0x57:	enabled = data[1];									// W - Запись разрешенных антенн
					EEPROM_write(0x00, enabled);						// в EEPROM
					to_PC('W', enabled);								// Отсылаем обратно для контроля
					break;
		}
    } else {																// Для отладки...
    PORTD |= (1<<PD6);														// При ошибке в CRC	
	_delay_ms(500);															// 
	PORTD &= ~(1<<PD6);														// мигаем светодиодом
    }
}

int main(void) {

/* Настройка портов */
	DDRB = 0x00; 															// PortB на вход
	PORTB = 0xFF; 															// Подтяжка
	DDRD |= (1<<SH) | (1<<DS) | (1<<ST); 									// PortD HC595 на выход
	DDRD |= (1<<PD6); // Светодиод для отладки /* Настройка UART */ UBRRH = (unsigned char)(baud>>8); 										// Скорость порта
	UBRRL = (unsigned char)baud;											//
	UCSRB = (1<<RXEN) | (1<<TXEN) | (1<<RXCIE); 							// Разрешение прием и передачу через USART. Разрешение прерывания по приему
	UCSRC = (0<<USBS) | (1<<UCSZ0) | (1<<UCSZ1); 							// Режим UART: 8n1 
	enabled = EEPROM_read(0x00);											// Читаем разрешенные антенные порты
	do  {																	// Ищем адрес ячейки 
		antenna = EEPROM_read(ee_addr); 									// Читаем значение из EEPROM
		ee_addr++;															// Увеличиваем адрес
		if (ee_addr == 0x80) ee_addr = 0x01;								// Если дошли до конца - устанавливаем адрес на начало
		}																	// !!! Указатель адреса останется на ячейке для СЛЕДУЮЩЕЙ записи !!!
	while (antenna == 0xFF);												// Перебираем ячейки до тех пор, пока будет не FF
	shift_595(antenna);														// Включаем антенну
	sei();

while(1)
	{
m1:		temp = PINB;  
		for (count = 0; count < 8; count++) { 						// Цикл сканирования клавиатуры 
		if ((temp&1) == 0) { 								// Проверка младшего бита 
		_delay_ms(10); 										// Антидребезг
		 while ( PINB != 0xFF ) {}; 								// Ждем отпускания кнопок
		 goto m2; 
		}
 		temp >>= 1; 													// Сдвигаем для проверки следующий бит
		}
		goto m1;															// Идем в начало сканирования клавиатуры
m2:		if ((enabled & (1<<count)) != 0 ) { 				// Проверка разрешено ли включать антенный порт 
		antenna = tab[count];						 // Запись значения антенны 
		shift_595(antenna); 						// Переключаем антенные реле 
		to_PC('S', antenna);						 // Отправляем в UART
		 store(); 								// Записываем в EEPROM 
		} 
		} 
} 
/* Обработчик вектора прерывания по приему UART */ 
ISR(USART_RX_vect) { 
	buf[buf_addr] = UDR; // Читаем UART в буфер
	buf_addr++; // Увеличиваем адрес буфера 
	if (buf_addr > 2) {
		from_PC(buf);														// Если заполнился -  вызываем обработчик приемного буфера
		buf_addr = 0;  														// Сбрасываем адрес буфера.
	}
}

Ссылка для обсуждения https://forum.cqr4a.ru/index.php/topic,5.0.html


UPD

Проект разморожен. Худо-бедно, с костылями, написана управляющая программа на C#, пришлось кое-что выкинуть из прошивки (при подаче команд на контроллер последний ничего не отвечает, увеличена задержка между посылкой «пакетов» байт при инициализации). Проект в VisualStudio в открытом доступе публиковать не буду, но если кто захочет его получить — никаких проблем. Просто выкладывать этот г-нокод стыдно. Скомпилированная программа тут. Net Framework 4.8 обязателен.

Небольшой, но важный, нюанс при программировании контроллера, о котором забыл — при прошивке необходимо инициализировать EEPROM. По-хорошему надо описать это в коде, можно в скомпилированной прошивке указать, можно вообще при программировании записать руками, если ПО программатора позволяет редактировать EEPROM. В любом случае, в любой одной ячейке памяти, кроме нулевой, должен быть установлен байт с единицей в одном из разрядов, например 0x01. Иначе цикл поиска последней включенной антенны (а мы ж ничего не включали еще после прошивки) станет бесконечным. Можно и прошивку переписать, чтоб делало полный круг поиска по EEPROM и успокаивалось, но проще запрограммировать EEPROM.

UPD2. Версия 1.4.1

Наконец-то разведена печатная плата. Для удобства разводки пришлось перекоммутировать некоторые пины контроллера, использующиеся для связи со сдвиговым регистром, сам регистр был отправлен на обратную сторону платы — опять же, из-за удобства разводки. Соответственно в прошивку внесены изменения кто где теперь живет, исходник тут. На пине PD6 микроконтроллера реализовано подключение светодиода для «дебага», например при ошибках CRC в данных от ПК.

Схема ниже, разница — в выводах микросхемы DD3. Блок питания на схеме не показан.

Чертеж печатной платы выполнен традиционно в Sprint-Layout 5. Разводка выполнена не со стороны дорожек, а видом «сквозь плату». На плате разведено место для выпрямительного моста с конденсатором и трех последовательно включенных интегральных стабилизаторов на 24, 12 и 5 вольт. Первые два — для питания реле, последний — для остальной схемы. Если не нужна универсальность — паяем перемычки. Конденсаторы-резисторы типоразмера 0805. На выходах микросхемы драйвера DD4 дополнительно сделаны места для установки шунтирующих конденсаторов на 0,1 мкФ. Файл печатной платы в архиве

Промежуточный итог

Вопреки ожиданиям, контроллер прошился (не забыть прошить EEPROM! и FUSE bits), загрузился. Микросхема FT232 оригинальная, определилась корректно, появилось два устройства  — USB Serial Converter и COM-порт. Программа управления на ПК опросила контроллер, при замыкании соответствующих контактов на плате в программе на ПК сами переключаются кнопки. В обратную сторону пока не тестировалось — нет ни индикации, ни исполнительных устройств.

Итог

Изначально были допущены ошибки в разводке платы (не были подключены две ноги регистра DD3), и неправильно реализованы цепи питания — каскадное включение трех интегральных стабилизаторов явно не лучшее решение. При токе потребления схемы (без реле) ~ 80 мА пришлось выдумывать как отводить от них тепло. Ввиду того, что корпусом послужил алюминиевый кабель-канал, свободного места не было в принципе. Интегральные стабилизаторы пришлось поместить на дно корпуса, под плату, и нарастить выводы проводниками. Трансформатор по мощности также пришелся впритык — потребляемы ток составил 95 мА (вместе с реле) при 100 мА максимальных для трансформатора.

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

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *