Контроллер антенного переключателя на 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 мА максимальных для трансформатора.

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

Добавить комментарий

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