Версия 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 — без понятия) было высказано несколько замечаний относительно некоторых технических моментов:
- Отсутствие контроля CRC в пересылаемых по USART данных;
- Отсутствие подтверждения выполнения команд микроконтроллером;
- Постоянная запись в 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. Вариантов несколько:
- Программный. Так называемый wear leveling — равномерное использование ВСЕХ ячеек EEPROM памяти, то есть циклическая запись по очереди (при поиске или использовать ID записи, или затирать до FF прошлое значение после записи и по нему находить последнюю запись);
- Программно-аппаратный. Сохранение значения включенной антенны в момент выключения питания. Для этого устанавливается супервизор, который отслеживает напряжение на общей шине питания, при срабатывании выдает логический «0» на порт INT0 микроконтроллера (PD2), тем самым вызывая внешнее прерывание, которое в свою очередь сохраняет данные. Чтобы поддерживать достаточное напряжение в момент записи настроек, параллельно шине питания микроконтроллера устанавливается электролитический конденсатор соответствующей емкости (подбирается). Естественно, отключение резервного «источника питания» от остальной части схемы обеспечивает MOSFET с как можно меньшим сопротивлением открытого канала (чтобы не было падения напряжения при штатном питании от общей шины USB).
- Скорее аппаратный, чем программный — поставить внешнюю микросхему FRAM или EEPROM.
- Радикальный. Отказаться вообще от сохранения переключаемых антенн, при подаче питания не включать вообще ничего — все антенны будут заземлены.
Вариант 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 мА максимальных для трансформатора.
Фото на различных этапах конструирования — ниже в галерее. Стоит обратить внимание, что некоторые узлы подвергались неоднократной модификации, поэтому на фото немного не соответствуют действительности. Тем не менее, корпус закрыт, работа проверена. Можно ставить на полку, чтоб пылилась))