Своё устройство на микроконтроллере я начал "пилить" еще в начале 2019-го. Тогда я даже не думал, что захочу связать его с компьютером, но устройство постоянно эволюционирует, и вот настала пора. Причём нужно не просто связать, а написать своё фирменное ПО, которое будет управлять устройством через USB. С подобной задачей я столкнулся впервые, а беглый поиск в сети выдал такие результаты, после которых появилось ещё больше вопросов и каша в голове. Про свой опыт решения данной задачи "От" и "До" делюсь в этой статье.

Вступление

О моём устройстве расскажу немного позже, сохраним интригу. Предвидя вопрос типа "В чём смысл этого топика?", отвечу, что простого и законченного решения в сети я так и не нашёл. Под простым и законченным решением я подразумеваю дуэт микроконтроллера (далее МК) с компьютером (далее ПК) и заготовки кода с функцией автоматического коннекта, а также мега-функцией "поморгать светодиодом". Пришлось повозиться и собрать все элементы мозайки в единую картину, которую я решил оформить в статью. Надеюсь, она поможет новичкам в вопросе "Как связать микроконтроллер с компьютером через USB" без лишних заумных слов. Уточню, что программированием занимаюсь для удовольствия, поэтому не претендую на образцово-показательный код. Это всего-лишь мой личный опыт, и вам судить, дорогой читатель, насколько он полезен.

Постановка задачи

  1. Обеспечить двусторонний обмен данными между МК и ПК через USB. Никаких плат и софта типа Arduino в готовом решении мы не используем.

  2. Сделать так, чтобы ПО компьютера автоматически определяло наше устройство при подключении его к любому USB-разъему.

  3. USB-устройство должно отображаться в диспетчере устройств под нашим собственным именем (например, под именем нашей компании).

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

Выполнив это, мы получим огромную власть над нашими устройствами. Однако, есть ограничение - скорость передачи данных будет небольшой. Но отправка несложных команд, текста, или легких файлов будет работать быстро.

Что нужно уметь/иметь

  • Знать C++ хотя бы на начальном уровне.

  • Уметь работать с любым МК (главное, чтобы у него был интерфейс UART).

  • Собственно сам МК (я взял Atmega328, т. к. она используется в моём проекте).

  • Макетная плата и несколько простых радиодеталей для сборки тестовой схемы.

  • Плата USB/UART преобразователя (о ней скажу ниже).

Как вы понимаете, информации предстоит много, и здесь не будет объяснения того, как прошивать МК или какой компилятор выбрать. Скажу только, что для прошивки я использовал Arduino IDE, а для ПО компьютера - Visual Studio C++ 12.

Не совсем USB-устройство

Проект, для которого я делаю канал связи с ПК, называется "Teslafon". Это название музыкальных катушек тесла с микроконтроллерным управлением (описание их работы тут). Пока катушка является автономным устройством и ни к чему не подключается, но недавно мне пришла идея сделать управление молниями с ноутбука. Несмотря на то, что тема специфическая, в сети можно найти примеры даже на неё, но там всё довольно индивидуально и я решил пойти своим путём.

Так как слона лучше есть по частям, я раздробил задачу на несколько подзадач, первая из которых - тема данной статьи. Но не подумайте, что я хочу подключить катушку тесла напрямую к ноутбуку (мой ноутбук мне дорог). Катушка тесла это генератор адских помех и наводок, а значит между ней и ноутбуком должно быть не только существенное расстояние, но и промежуточное устройство-изолятор на несколько мегавольт... Его-то я и хочу подружить с ПК. Вдаваться в подробности катушек тесла не буду, это уже другая история. Просто скажу, что мы сделаем универсальную заготовку USB-устройства, которую вы сможете использовать в своём проекте, а я в своём.

Теория

Сперва я думал, что всё будет проще простого. Возьму библиотеки типа V-USB и libusb, подключу МК напрямую к USB-разъему, быстренько найду готовый пример кода и будет мне счастье. Но "быстренько" не получилось. Оказывается, там много заморочек - например, дополнительная библиотека кушает ресурсы МК, а моя прошивка предполагается довольно ёмкой. Мне хотелось использовать встроенные аппаратные фишки по максимуму, чтобы не занимать память. Кроме того, настройка всех этих библиотек требует опыта. Рабочая частота МК подойдет не абы какая, а та, что задал автор библиотеки. Но реальный шок был тогда, когда я узнал, что для моего устройства нужны PID (Product ID) и VID (Vendor ID), за которые сегодня надо заплатить, на секундочку, 3500$ организации usb.org. Ох, не туда меня завели поиски, не туда... Наконец до меня дошло, что к чему.

Оказалось, вопрос не в том, как передать данные через USB, а как передать данные через виртуальный COM-порт! Передача данных через COM-порт намного проще, хотя это устаревший вид портов. А раз так, то реального COM-порта у нас не будет, только виртуальный. Физически же мы по-прежнему будем использовать привычный USB. Виртуальный COM-порт будет предоставлен драйвером, о котором мы поговорим ниже. А еще к устройству добавится некая микросхема-посредник, которая снимет целый ворох проблем. Взгляните на схему:

Рис. 1 - Связующие звенья между хостом и микроконтроллером
Рис. 1 - Связующие звенья между хостом и микроконтроллером

Между ПО хоста и нашей прошивкой есть целых 4 звена. С аппаратным UART-ом всё понятно, что же с остальными?

Здесь роль посредника будет выполнять микросхема по кличке CP2102, у которой есть также куча аналогов (CH340, PL2303, FT232RL и другие). Её основная задача - преобразование интерфейса UART в USB и обратно. Как это делается, нам не важно, это просто чёрный ящик, имеющий вход и выход. CP2102 используется в некоторых программаторах, возможно на вашем она тоже есть, так как довольно распространена. Изучать распиновку этой микросхемы тоже не будем, потому что есть готовые платки с ней же и со всей обвязкой. И это хорошо, ведь наша задача - чем проще, тем целее нервы. Пример двух платок с CP2102 от компании Silicon Labs:

Рис. 2 - USB/UART преобразователи в разных формфакторах
Рис. 2 - USB/UART преобразователи в разных формфакторах

Купить подобную железку можно, например, здесь. Но чтобы не ждать, можете поискать в местных радиомагазинах, товар не редкий, думаю, найдёте. Далее я начал изучать, как применить этот USB/UART преобразователь. Тут тоже всё обошлось - производитель железки уже позаботился о нас и написал драйвер для контроллера USB (см. рис. 1). Этот драйвер и будет эмулировать виртуальный COM-порт. Вы спросите, а где же брать PID и VID для нашего устройства? Ответ - их предоставляет тот же Silicon Labs бесплатно.

Круто, теперь можно увидеть всю последовательность действий:

  1. Покупаем USB/UART преобразователь и собираем тестовое устройство;

  2. Скачиваем и устанавливаем драйвер;

  3. Пишем ПО хоста;

  4. Пишем прошивку МК;

  5. Тестируем интерфейс.

Макетная плата

Помните, что наше устройство кроме моргания светодиодом ничего пока уметь не будет? Чтобы не было скучно, давайте сделаем три светодиода:

  • Зелёный - будет гореть, пока связь с хостом установлена (не путать с физическим подключением к порту).

  • Красный - загорается или гаснет по нажатию 'r' на клавиатуре.

  • Белый - загорается или гаснет по нажатию 'w' на клавиатуре.

Нажатие 'x' на клавиатуре будет разрывать сеанс передачи данных, гасить зелёный светодиод и завершать работу ПО хоста. Но я забежал немного вперед, вернёмся к схеме устройства:

Рис. 3 - Схема тестового устройства
Рис. 3 - Схема тестового устройства

Думаю, тут всё понятно. Я даже не стал ставить подтягивающий резистор R1, хотя в реальной схеме он нужен. Сейчас нам главное получить минимально рабочую модель. Вот так выглядит макетка:

Рис. 4 - Макетная плата
Рис. 4 - Макетная плата

Кстати, очень удобно использовать витую пару в качестве соединительных проводков, они дешёвые и нарезать их можно разной длины. В жгуте из 5 проводов используются только 4, зелёный провод идёт с контакта DTR преобразователя на макетку и никуда не подключается (оставлен про запас). Atmega328 в корпусе DIP-28 прошла огонь, воду и трубы, но ещё жива - когда-то она была подопытным кролем в создании первой музыкальной катушки тесла. Кварцевый генератор я не ставлю, а использую встроенный на 8 МГц. Для удобства я сделал преобразователь съёмным и слегка подогнул вверх вывод 3.3V, чтобы избежать неправильного подключения.

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

Установка драйверов

Наш USB/UART преобразователь уже вставлен в ПК, открываем диспетчер устройств и видим:

Рис. 5 - USB/UART преобразователь в диспетчере
Рис. 5 - USB/UART преобразователь в диспетчере

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

Программное обеспечение хоста

Итак, схема имеет три светодиода: зелёный, красный и белый. Логика работы будет такая: запускаем программу на ПК, если девайс не подключён, то программа будет ждать, пока мы его не подключим. При подключении автоматически будет осуществлён коннект с устройством и загорится зелёный светодиод. Клавиши "w" и "r" будут управлять белым и красным светодиодом соответственно. При вводе "x" произойдёт разрыв связи с нашим устройством и зелёный светодиод погаснет. Как видите, все очень просто.

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

Создадим класс устройства, но на самом деле это будет скорее класс COM-порта, т. к. в нём будут его настройки, функции подключения/отключения и передачи данных. Начало файла tesladevice.h:

//Файл tesladevice.h

#include <windows.h>
#include <iostream>

class TeslafonDevice
{
private:
	HANDLE hPort;				  //Идентификатор ком-порта (далее просто порта)
	wchar_t portName[7];	//Строка с именем порта
	LPCTSTR ptrPortName;	//Указатель на строку с именем порта
	unsigned int portNum;	//Номер порта
	DCB dcbParams;				//Структура DCB с кучей полей для настройки порта
	COMMTIMEOUTS ctParams; //Структура для определения параметров временных задержек при приеме и передаче данных

Постараюсь максимально комментировать прямо в коде, но некоторые моменты буду выносить отдельно. Так как изначально номер порта мы не знаем, подготовим строку portName из 7 двухбайтовых символов типа wchar_t, где будем перебирать имена в формате "COMxxx". В Windows по умолчанию доступно 256 виртуальных ком-портов, и мы будем перебирать все, пока не найдем своё устройство. Структура dcbParams включает основную массу настроек порта (разбирать её не будем, если интересно, можете загуглить сами). Структура ctParams будет содержать настройки таймаутов, её описание тоже опустим. Далее объявим публичные и приватные функции:

public:
  TeslafonDevice():portNum(0){};  //Конструктор инициализирует номер порта нулём
  bool Connect()    //Ищет наше USB-устройство и подключается к нему
  {
    //...
  }
  bool SendData(char transBuf)    //Отправка одного байта transBuf в порт
  {
    //...
  }
  BOOL GetPortNum()   //Возвращает номер порта, к которому подключено устройство
  {
    //...
  }
  BOOL Close()    //Закрывает порт
  {
    //...
  }
private:
	bool helloFrom()	//Получает приветствие от нашего устройства
	{
		//...
	}
};

//Конец файла tesladevice.h

Разберём каждую из них подробно. Функция Connect() самая большая, она делает всю черновую работу по подключению к устройству. Работает она так: запускается цикл перебора всех 256 возможных портов и пробует подключиться к каждому. Если подключение удалось, устройству посылается приветствие, затем в течение заданного таймаута ожидается приветствие в ответ. Если оно не поступило, значит устройство не наше и связь с ним прекращается. Если приветствие поступило, значит устройство распознало своего "хозяина" и приготовилось к работе (при этом загорается зелёный светодиод). Функция Connect(), видя знакомый ответ, завершается и отдает истину в основную программу. Происходит всё это быстро, так как обычно львиная доля портов из 256 возможных просто не существует. Такой способ идентификации устройства мне кажется надёжным.

//Тело функции Connect():

for(unsigned int comnum = 1; comnum<=256; comnum++)
{
	//Готовим строку с очередным номером порта, swprintf() аналогична sprintf(), но работает с юникодом
	//Примечание 1: выражение sizeof(portName)>>1 равносильно sizeof(portName)/2, но выполняется быстрее;
	//написать просто sizeof(portName) мы не можем, т. к. тут нужно количество символов, а не их суммарный размермер
	//Примечание 2: буква L перед строкой расширяет её до формата юникод
	swprintf(portName, sizeof(portName)>>1, L"COM%d", comnum);
	
	//Присваиваем указателю адрес только что собранной строки
	ptrPortName = portName;
	
	//Попытка подключения к порту (напоминает работу с файлами)
	hPort = ::CreateFile(ptrPortName,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
	
	//Если порт существует, делаем нужные настройки, посылаем приветствие и запрашиваем ответ
	if(hPort != INVALID_HANDLE_VALUE)
	{
		//Заполнение структуры dcbParams
		dcbParams.DCBlength = sizeof(dcbParams);
		if (!GetCommState(hPort, &dcbParams))	//GetCommState() заполняет dcbParams значениями по умолчанию
		{
			//Ошибка GetCommState
		}
		dcbParams.BaudRate = CBR_9600;			//Эти настройки меняем на стандартные
		dcbParams.ByteSize = 8;
		dcbParams.StopBits = ONESTOPBIT;
		dcbParams.Parity = NOPARITY;
		if(!SetCommState(hPort, &dcbParams))	//Установка новых значений
		{
			//Ошибка SetCommState
		}
		//Заполнение структуры ctParams
		if (!GetCommTimeouts(hPort, &ctParams))	//GetCommTimeouts() считывает текущие таймауты в ctParams
		{
			//Ошибка GetCommTimeouts
		}
		ctParams.ReadIntervalTimeout = 2;			//Эти таймауты меняем на свои
		ctParams.ReadTotalTimeoutConstant = 2;
		ctParams.ReadTotalTimeoutMultiplier = 2;
		if (!SetCommTimeouts(hPort, &ctParams))		//Установка новых таймаутов
		{
			//Ошибка SetCommTimeouts
		}
		//Посылаем идентификационный запрос устройству (я отправляю просто символ 't')
		char data = 't';  					
		DWORD dataSize = sizeof(data);
		DWORD dataWritten;
		WriteFile (hPort,&data,dataSize,&dataWritten,NULL);		//"Стучимся" в порт

		if(helloFrom())		//Если ответ от нашего устройства получен
		{
			//Связь установлена, сохраняем номер порта и выходим в основную программу
			portNum = comnum;
			return true;
		}
		//Если ответа нет, закрываем порт и переходим к следующему
		CloseHandle(hPort);
	}
	else
	{
		//Порт не существует
	}
}
return false;	//Все порты проверены, устройство не подключено ни к одному из них

//Конец функции Connect()

В коде я дал исчерпывающие пояснения, останавливаться на них не буду. Единственный вопрос, который может возникнуть - как отреагируют другие устройства (если они подключены) к вмешательству со стороны нашей программы? Скорее всего, никак. Я подключал одновременно несколько разных устройств, никаких проблем не было.

Теперь разберём приватную функцию helloFrom(). Чтобы на 100% отличить наше устройство от чужого, будем принимать от него кодовое слово, которое другим устройствам неизвестно. Я взял слово "teslafon", вы можете придумать своё. Вот как это выглядит в коде:

//Тело функции helloFrom():

DWORD strSize;									//Количество принятых символов от устройства
char strReceived[9] = {0};						//Буфер для приёма этих символов
memset(strReceived, 0, sizeof(strReceived));	//Обнулим буфер на всякий случай
//Попытка прочитать ответ
ReadFile(hPort,&strReceived,sizeof(strReceived),&strSize,0);

if (strSize > 0)			//Eсли что-то принято
{
	if(strcmp(strReceived, "teslafon") == 0)	//Если получено кодовое слово
	{
		return true;		//Связь установлена!
	}
}
return false;				//Иначе нет

//Конец функции helloFrom()

Здесь функция ReadFile() ждёт ответа от устройства в пределах заданных таймаутов, хранящихся в структуре ctParams. Если ответа нет или он не совпадает с ожиданиями, helloFrom() выдаёт false. Далее напишем SendData(), которая вступает в работу после подключения к устройству:

//Тело функции SendData(char transBuf):

DWORD bufSize = sizeof(transBuf);		//Размер буфера для отправки
DWORD dataWritten;    					    //Количество переданных байт
WriteFile(hPort,&transBuf,bufSize,&dataWritten,NULL);	//Отправка данных
return (bufSize == dataWritten);		//Результат отправки

//Конец функции SendData()

Для простоты мы отправляем один байт, но если надо отправить более сложные данные, можно несколько раз вызвать SendData() в цикле. Следующая функция GetPortNum() просто возвращает номер порта, к которому мы подключились. Конечно, можно было сделать переменную portNum публичной, но правила хорошего тона велят закрывать данные класса от всеобщего доступа:

//Тело функции GetPortNum():

return portNum;

//Конец функции GetPortNum()

И последняя функция нашего класса просто завершает работу с портом, возвращая результат операции:

//Тело функции Close():

return CloseHandle(hPort);

//Конец функции Close()

Я решил сэкономить на файле tesladevice.cpp и все тела функций разместил в tesladevice.h. Вы можете разделить их или оставить как есть.

Теперь напишем основное тело нашего хоста. Так как это тестовый проект, я сделал его в консольной программе:

//Файл main.cpp

#include <stdio.h>
#include <tchar.h>
#include "tesladevice.h"

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	cout << "Hi! We find Teslafon Device..." << endl << endl;	//Приветствие
	TeslafonDevice TeslaDev;			//Объект нашего устройства
	while(!TeslaDev.Connect());			//Ждем, пока его не подключат к USB
	cout << "Device (COM" << TeslaDev.GetPortNum() << ") found!" << endl << endl;	//Подключили!
	char transBuf = 0;  				//Байт данных для передачи
	while(1)
	{
		cout << "Enter 'w' or 'r' ('x' to exit):" << endl; //Мигнём белым или красным?
		cin >> transBuf;
		if(TeslaDev.SendData(transBuf))		//Данные отправлены?
		{
			cout << "Data sent: " << transBuf << endl;
		}
		else
		{
			cout << "Failed to send data..." << endl;
		}
		if(transBuf == 'x') break;			//Выход из программы
	}
	TeslaDev.Close();						//Закрываем порт
	cout << "Exit" << endl;
	getchar();
	return 0;
}

//Конец файла main.cpp

На этом работа с хостом закончена. Это всего лишь заготовка, но вполне рабочая, из которой можно слепить что душе угодно. Осталось написать прошивку для USB-устройства и переходить к долгожданным тестам.

Прошивка

Как уже говорилось выше, моё устройство будет работать на Atmega328, а значит и код я буду строчить для неё. Эксперимент проводился на частоте встроенного генератора 8 МГц, с другими частотами я не работал. В коде используются регистры МК напрямую и глобальные переменные, и вообще попахивает хаосом, но повторюсь - это пример, набросок, может быть не самый лучший, но рабочий. Если у вас другой камень, вам всё равно придется изменить инициализирующую часть кода, так что можете его оформить как вам нравится. Вот мой полный скетч для Arduino IDE:

//Файл firmware.ino

#define FREQMC 8000000L			//Рабочая частота МК
#define BAUD 9600L					//Бодрейт по умолчанию
#define BAUDDIV (FREQMC/(16UL*BAUD)-1)	//Манипуляция с делителем
#define HBYTE(x) ((x)>>8)		//Макрос для получения старшего байта
#define LBYTE(x) ((x)& 0xFF)	//Макрос для получения младшего байта

#define GREENPORT	PC3				//Пины светодиодов
#define REDPORT		PC4
#define WHITEPORT	PC5

typedef unsigned char	BYTE;	//Для удобства объявления переменных
BYTE data = 0;					//Глобальный буфер для хранения принятых данных от хоста
BYTE isConnect = 0;					//Глобальный флаг состояния устройства

void UARTInit()					//Инициализация интерфейса передачи данных
{
	UBRR0H = HBYTE(BAUDDIV);
	UBRR0L = LBYTE(BAUDDIV);
	UCSR0B = (1 << RXEN0) | (1 << TXEN0);
	UCSR0C = ((1 << UCSZ00) | (1 << UCSZ01));
}

void byteSend(BYTE data)		//Посылает 1 байт хосту
{
   while (!(UCSR0A & (1 << UDRE0)));
   UDR0 = data;
}

void dataSend(BYTE* data)		//Посылает массив байт хосту
{
   while(*data) byteSend(*data++);
}

BYTE byteReceive()				//Ждет и возвращает данные от хоста
{
	while (!(UCSR0A & (1 << RXC0)));
	return UDR0;
}

void setup()			//Инициализация пинов, интерфейсов и т. д.
{
	PORTC |= (1 << REDPORT)|(1 << WHITEPORT)|(1 << GREENPORT);	//Инициализация пинов светодиодов
	PORTC &= ~(1 << REDPORT);		//Перевод пинов светодиодов в низкий уровень (на всякий случай)
	PORTC &= ~(1 << WHITEPORT);	//
	PORTC &= ~(1 << GREENPORT);	//
	UARTInit();						//Инициализация интерфейса
}

void loop()				//Главный цикл прошивки
{
	data = byteReceive();	//Ожидаем данные от хоста бесконечно, пока они не придут
	
	if(data == 'r')						//Если прилетела буква r
	{
		PORTC ^= (1 << REDPORT);		//Включить/выключить красный 
	}
	else if(data == 'w')					//Если буква w
	{
		PORTC ^= (1 << WHITEPORT);	//Включить/выключить белый
	}
	else if(data == 't' && !isConnect)	//Если буква t и состояние устройства "Отключён"
	{
		dataSend("teslafon");				//Послать хосту заветное слово
		PORTC |= (1 << GREENPORT);	//Включить зелёный
		isConnect = 1;							//Поменять флаг состояния на "Подключён"
	}
	else if(data == 'x' && isConnect)	//Если буква x и состояние устройства "Подключён"
	{
		PORTC &= ~(1 << GREENPORT);	//Выключить зелёный
		isConnect = 0;							//Поменять флаг состояния на "Отключён"
	}
}
//Конец файла firmware.ino

Ну вот, проделана большая работа. Теперь всё компилируем, прошиваем устройство, втыкаем его в ПК и с замиранием сердца запускаем программу...

Тест всего проекта

Если всё сделано правильно, программа выведет это:

Рис. 6 - Результат
Рис. 6 - Результат

Дальше вводим разные буквы и жмём Enter. Результат работы я продемонстрирую на видео:

Если у вас всё это добро не запускается или запускается криво, придется попыхтеть над отладкой. По крайней мере, я проверял эту систему на трёх разных компьютерах и на двух Windows (7 и 10), и результатами остался доволен. Желаю и вам удачного запуска.

Выводы

Статья получилась объёмной, но некоторые детали так и остались не раскрыты. Однако, поставленные задачи выполнены. Только представьте, какие штуки теперь можно делать, имея такой инструмент. Например, можно сделать пульт ДУ для ПК, или обработку разных датчиков умного дома. Можно придумать и более нестандартные и опасные штуки, как в моём случае - управление катушкой тесла. И даже если кто-нибудь скажет, что всё уже давно придумано, я соглашусь. Наверно, есть и более изящные решения задачи, но мне интересно (а порой и необходимо) разобраться во всём самому. Ведь именно так рождается новое. Надеюсь, что дорогой читатель меня поймёт. Удачных всем разработок!

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


  1. Engenigger
    07.09.2021 13:21
    +9

    простого и законченного решения в сети я так и не нашёл. Под простым и законченным решением я подразумеваю дуэт микроконтроллера (далее МК) с компьютером (далее ПК)

    Да неужели? Во всем интернете не нашлось информации как работать с COM-портом (ПК) и UART (МК)?


    1. juramehanik
      07.09.2021 18:32

      Лучше бы конечно автору написать причины, почему он решил работать с морально устаревшим МК, когда на рынке есть куда более современные и дешевые (даже не смотря на ковид) и доступные кристаллы, с куда более простой возможностью реализации USB VCP.


      1. da-nie
        07.09.2021 19:04
        +3

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


        1. Teslafon Автор
          08.09.2021 10:06

          Все верно, за исключением одного - я прекрасно понимаю, что мир обо всем уже давно знает, и моя задача была не в открытии Америки, а резюмировать изученное и поделиться опытом с такими же новичками, как я :) А контроллер я выбрал этот только потому, что он использовался в катушке тесла и я с ним знаком.


          1. Mulfarion
            08.09.2021 12:28

            Так если мир знает уже, может стоило более лучше гуглить? Я когда писал свою программу для управления ардуинкой с компа, почему-то за час нашёл и как передавать в порт команды и как получать и как прошивать ардуино прямо через ПО. Как говорится, было бы желание и голова на плечах, а заново изобретать колесо и переоткрывать америку, ну так себе занятие.


  1. maledog
    07.09.2021 16:23
    +3

    Почему никто никогда в таких руководствах не добавляет контрольную сумму? Где гарантия что последовательность байт не пришла искаженной? Особенно при использовании дешевых преобразователей. А контроль длинны сообщения? А проверку, что оба девайса на линии не начали передавать одновременно?


    1. da-nie
      07.09.2021 17:39

      Это интересный вопрос. Берём какой-нибудь дорогущий мультиметр от Agilent (тысяч за 300). Подключаем. И да, там нет контрольной суммы. Почему…
      Берём at-протокол. Тоже самое. Нет контрольной суммы. Тот же вопрос…


      1. sim2q
        07.09.2021 19:25

        с одной стороны - внутри чипа нет смысла, с другой - протокол асинхронный
        А коллизии быть не должно по причине разных линий tx/rx
        ps на RS485 у меня всё есть)


        1. da-nie
          07.09.2021 19:33

          Линия может быть и внешняя, и помехи никто не отменял.


          1. sim2q
            07.09.2021 19:44

            Пишу комменты с перерывах с кодом. Кто-то наоборот максимально сосредоточен - это не я. Сколько раз таким образом смотрел на ошибку в упор. А вот если сгонять в творческую командировку на кухню или в окно тлен браузера, то внезапно можно что то обнаружить)
            Это к тому, что каждый раз порываюсь в любом комменте развернуть switch/case на все случаи, но понимаю, что всё равно ровно же столько раз всё равно предложат описанное:)

            так что да)


            1. sami777
              08.09.2021 12:42
              +1

              USB аппаратно поддерживает CRC.


          1. RTFM13
            10.09.2021 20:30

            Если переходник usb-uart стоит рядом с МК, то на помехи на линию как правило можно смело забить. Внешняя линия там USB и там контроль целостности данных есть из коробки прозрачно для пользовательских данных.

            Что вы хотите от человека который впервые прикрутил МК к компу через uart?

            Автору, если чуть умерить пыл, можно было обойтись режимом HID. Наверняка в сети полно примеров под нужный МК. Возможно даже от микрочипа.


    1. FGV
      07.09.2021 17:50

      Почему никто никогда в таких руководствах не добавляет контрольную сумму? Где гарантия что последовательность байт не пришла искаженной? Особенно при использовании дешевых преобразователей. А контроль длинны сообщения?

      Для начинающих типичная ошибка :) Кстати можно автора отправить почитать про nmea 0183 например.

      А проверку, что оба девайса на линии не начали передавать одновременно?

      Это ж 232, он дуплексный, ничего страшного не произойдет.


    1. Teslafon Автор
      08.09.2021 05:46

      Да, контрольную сумму я обязательно добавлю, и не только её. Это ведь только заготовка проекта. Спасибо за обратную связь.


    1. nixtonixto
      08.09.2021 10:43

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


    1. HiTechSpoon
      08.09.2021 18:49
      +1

      Тут в комментариях правильно ответили, что у UART есть бит чётности (опциональный) и контроль фрейма по старт и стоп битам, а в протоколе USB есть своя контрольная сумма. Однако на самом деле контрольная сумма в устройствах с таким UART<->USB интерфейсом не нужна потому, что шина данных от МК до преобразователя UART-USB находится внутри устройства, её параметры хорошо контролируется схемотехнически и она достаточно коротка для того, чтобы на неё что-то навелось. Если на этой шине вдруг возникают ошибки, то решать это надо не контрольной суммой, а переделкой схемотехники.


  1. da-nie
    07.09.2021 19:01

    swprintf(portName, sizeof(portName)>>1, L«COM%d», comnum);


    COM-порт с номером от 10 адресуется иначе. А именно,"\\\\.\\COM%d" вместо «COM%d».

    нужны PID (Product ID) и VID (Vendor ID), за которые сегодня надо заплатить, на секундочку, 3500$


    А вы возьмите для своего устройства такие идентификаторы:

    VENDOR_ID 0x0c45
    PRODUCT_ID 0x7401

    Они соответствуют тепловизору Flir One Gen 2. И к компьютеру его, обычно, не подключают (официально — точно нет, поэтому драйвер от производителя никогда не появится в Windows или Linux). Правда, тут появится проблемы накатать драйвер и подписать его. Но, возможно, у вас получится. :)


  1. bigfoot_tmn
    07.09.2021 19:32

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


  1. COKPOWEHEU
    07.09.2021 23:59
    +1

    Предвидя вопрос типа «В чём смысл этого топика?», отвечу, что простого и законченного решения в сети я так и не нашёл. Под простым и законченным решением я подразумеваю дуэт микроконтроллера (далее МК) с компьютером (далее ПК) и заготовки кода с функцией автоматического коннекта, а также мега-функцией «поморгать светодиодом».
    Так в чем смысл топика-то? Информации по подключению контроллера к компьютеру море. Хоть по устаревшему LPT-порту, хоть по чуть менее замшелому COM, хоть по современному USB. Даже по bluetooth, ethernet, wifi народ подключает.
    Собственно сам МК (я взял Atmega328, т. к. она используется в моём проекте).
    Вот с этого стоило начать, потому что я долго пытался угадать что же за контроллер вы изучаете и какой же извращенный способ подключения реализуете (потому что обычные проблем не представляют, см.выше).
    микросхема по кличке CP2102
    Классическое решение, но о проблемах предупредить стоит. Это только переходник USB-UART, соответственно ничего кроме COM-порта вы не получите. Ни аудиоканала, ни HID. Невозможно (в некоторых переходниках таки возможно, но не очень-то поможет) настроить VID:PID, manufacturer/product/serial. То есть если у вас несколько таких переходников, друг от друга вы их программно не отличите.
    Возьму библиотеки типа V-USB и libusb, подключу МК напрямую к USB-разъему, быстренько найду готовый пример кода и будет мне счастье. Но «быстренько» не получилось. Оказывается, там много заморочек — например, дополнительная библиотека кушает ресурсы МК, а моя прошивка предполагается довольно ёмкой.
    vusb занимает всего около 2 кБ флеша. У вас ATmega328 — целых 32кБ! Я бы еще понял если бы не хватало скорости, софтовый USB действительно здорово нагружает ядро. Но только пока идет передача. Если вам надо поменять какую-то настройку примерно раз в вечность, средняя нагрузка будет совсем незначительной. Зато можно изобразить не COM-порт, а HID-устройство, то есть однозначно себя идентифицировать по VID, PID, manufacturer, product, serial и в некоторых случаях даже вывести эти самые manufacturer, product в список оборудования.
    BYTE byteReceive() //Ждет и возвращает данные от хоста
    {
    while (!(UCSR0A & (1 << RXC0)));
    return UDR0;
    }

    Так делать не стоит. А вдруг провод выскочит или переходник сломается? Так и будете висеть в ожидании. Работать с UART стоит по прерываниям или хотя бы по методу опроса.
    if(UCSR0A & (1<RXC0)){/*читаем UDR0, обрабатываем и т.д.*/}else{/*ничего не пришло, выполняем основной код*/}

    Рабочая частота МК подойдет не абы какая, а та, что задал автор библиотеки.
    Вы хоть видели список доступных частот? 12, 12.8, 15, 16, 16.5, 18 и 20 МГц. Вы от чего вообще тактируетесь, что ничего из этого не подходит?
    для моего устройства нужны PID (Product ID) и VID (Vendor ID)
    Если вы собираете устройство со стандартным функционалом (COM-порты, аудио, HID и т.п.), то есть не пытаетесь обрабатывать низкоуровневые запросы, то можно взять VID:PID от чужого устройства с более-менее похожим. Я обычно пользуюсь парой от vusb (16C0:05DF).
    Задача VID:PID в том чтобы производитель железа мог реализовать любой извращенный алгоритм, не вписывающийся в стандарты USB, написать под него драйвер и потом применять его к любому устройству с данной парой VID:PID. Ну и прописать свои инициалы в системе. Мол все, что с этой парой идентификаторов — МОЁ.


  1. Singrana
    08.09.2021 11:46

    А почему бы не взять 32U4? Та же 328, только с USB. И все станет проще


  1. srg27y
    08.09.2021 11:56

    Это действительно больная тема, была для меня 10 лет назад)

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

    Я когда начинал изучать мк, остановился на примере бутлоадера - по сути взял готовый протокол с контрольной суммой, автоопределения скорости и командами чтения/записи ром и епром. Добавил команды работы с рам и вобщемто довольно неплохо получилось. Работает всё такжэ как у автора через усб-уарт и блютуз-уарт, поддерживается много контроллерная система по RS422. Всё работает в полудуплексном режиме, с компа запрос, с мк ответ. Единственно что познее добавил широковещательную команду синхронизацыи времени RTC, на нее никто не отвечает.

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


  1. Andy_Big
    09.09.2021 17:06

    В интернете можно найти всего около 100500 примеров подключения атмег к компьютеру - катастрофически мало. И Вы решили добавить еще один :)

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

    Вот тут интрига. Обычно "драйвер" для такого преобразователя представляет из себя просто .inf-файл.

    Можно установить девайсу PID/VID стандартного USB-COM, тогда в системе он и будет виден как стандартный, и драйвера винда подтянет стандартные.

    Можно установить девайсу PID/VID от какого-нить существующего USB-COM устройства и установить соответствующий драйвер, тогда в системе девайс будет виден как это устройство.

    Можно установить девайсу свои наобум взятые PID/VID, взять .inf от какого-то существующего USB-COM устройства, заменить в нем PID/VID на те, что установили девайсу (заменив заодно и описание на свое), и установить этот .inf как драйвер. Тогда в системе девайс будет виден под своим именем. Но тогда встанет проблема с тем, что драйвер не подписан и винда откажется устанавливать его без бубна.

    Неужели есть еще какой-то способ?


    1. da-nie
      09.09.2021 17:48

      Вот тут интрига. Обычно «драйвер» для такого преобразователя представляет из себя просто .inf-файл.

      Можно установить девайсу свои наобум взятые PID/VID, взять .inf от какого-то существующего USB-COM устройства, заменить в нем PID/VID на те, что установили девайсу (заменив заодно и описание на свое), и установить этот .inf как драйвер. Тогда в системе девайс будет виден под своим именем. Но тогда встанет проблема с тем, что драйвер не подписан и винда откажется устанавливать его без бубна.


      inf-файл — это не драйвер. Драйвер это набор устанавливаемых в систему dll, которые указаны в inf-файле. И вот эти-то dll написать не так уж просто.


      1. Andy_Big
        09.09.2021 18:22
        +1

        Строго говоря — да, .inf — это не драйвер. Но некоторым устройствам достаточно только этого файла, внутри которого имеются ссылки на стандартные dll и sys и свои собственные дескрипторы. Такой «драйвер», к примеру, идет для отладочных плат STM32.
        Про написание собственных бинарников драйверов и их подписание я даже не заикаюсь, такой уровень сложности автору явно не по плечу, поэтому вряд ли он имел в виду этот путь.


        1. da-nie
          09.09.2021 19:12

          Но некоторым устройствам достаточно только этого файла,


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

          и их подписание


          Но, к слову, в XP подписание не требуется. А в системах выше его можно обойти. :)

          написание собственных бинарников драйверов


          Кстати, в DDK есть примеры готовых драйверов для разных устройств. Весьма вероятно что автору подойдёт что-то готовое с незначительной модификацией.


          1. Andy_Big
            09.09.2021 21:53

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

            Стандартный usbcom.sys есть в стандартном комплекте винды еще с версии XP точно, а может и раньше :)

            Но, к слову, в XP подписание не требуется.

            Не буду утверждать точно, но по-моему требуется, по крайней мере в SP3. Но там это достаточно просто отключить. В семерке - уже сложнее, в десятке - надо быть продвинутым пользователем :) Я со своими девайсами пошел изначально по этому пути еще когда XP был самым распространенным. С появлением семерки пользователи уровня "Я тут нажал кнопку и выскочило какое-то окошко" уже начали остро чувствовать эту проблему. А с появлением десятки я взял себя в руки и переделал все на HID - и никаких проблем даже с самыми "тук-тук-войдите" пользователями :)

            Кстати, в DDK есть примеры готовых драйверов для разных устройств. Весьма вероятно что автору подойдёт что-то готовое с незначительной модификацией.

            Установить и настроить DDK - тот еще квест сам по себе, и все равно придется подписывать полученный драйвер :)


            1. da-nie
              10.09.2021 06:59

              Не буду утверждать точно, но по-моему требуется, по крайней мере в SP3.


              Не требуется и в SP3. :)

              Установить и настроить DDK — тот еще квест сам по себе, и все равно придется подписывать полученный драйвер :)


              Я бы так не сказал. Ставится DDK очень просто. А вот разобраться в примерах — вот будет квест. :) Ну а подпись… ну что ж делать…


              1. Andy_Big
                10.09.2021 11:44

                Я еще во времена раннего XP баловался этим DDK, писал сетевой фильтр на уровне драйверов. Помню, что там все было не просто "Запустил установщик и через пару минут можно работать". Прописывание путей в окружение, доустановка библиотек, связывание с VisualStudio, что-то еще... :)


                1. da-nie
                  10.09.2021 16:18

                  Там связывать с Visual Studio совсем не обязательно. Можно легко из командной строки всё собирать.


                  1. Andy_Big
                    10.09.2021 16:20

                    Эт еще сложнее :)