roboforum.ru
Вообще говоря это не Си++ это некоторая нашлепка на него от CvAVR.
Правильный Си++ делает так (на примере установки бита, т.е. PORTC.1=1):
PORTx |= 1 << BIT_NUMBER;
PORTx — нужный нам порт (соотв. ему ячейка памяти, если я правильно помню как там что устроено), например PORTC, BIT_NUMBER — номер бита, например 1.
Т.е., например, PORTC |= 0x02;
Re: Передача аргментом ножки порта
dccharacter » 16 авг 2011, 16:38
Передать одним аргументом, наверное, нельзя, а вот двумя — можно. Адрес регистра + маска.
Адрес можно передать как в явном виде (см. даташит), так и скорее всего взять его от имени порта (хотя не уверен — там в хидерах такое понакручено).
unsigned char * addr_ptr;
addr_ptr = &PORTC; //хотя наверное в CvAVR PORTC — это само по себе что-то структуры
some_function (addr_ptr, mask);
Добавлено спустя 51 секунду:
Re: Передача аргментом ножки порта
Hagrael » 16 авг 2011, 18:39
Grem , я начинаю читать книгу "Самоучитель разработчика устройств на микроконтроллерах AVR". Но все-таки интересно, dccharacter , как внутри применять этот код? Если таким образом:
Код: Выделить всё • Развернуть
то у меня выходит ошибка. Ваш код:
=DeaD= , ваш код:
Код: Выделить всё • Развернуть
как я понимаю, поставит на второй с конца бит 1. А если я пишу
Код: Выделить всё • Развернуть
то как тогда эта инструкция расшифруется?
Большое спасибо за быстрые отзывы!
Re: Передача аргментом ножки порта
Виталий » 16 авг 2011, 19:18
Re: Передача аргментом ножки порта
boez » 16 авг 2011, 19:56
Эта вся беда оттого, что в С работы с железом нету как таковой, а людям хочется
Запись PORTC.1 ошибочна с точки зрения С, любой _стандартный_ С компилятор должен выдавать ошибку. Но в некоторых компиляторах, заточенных именно под АВРки, как минимум в CVAVR, такое прокатывает. При этом это уже магия компилятора, соответственно поддержка "указателей на ножку" — тоже вопрос нестандартного расширения языка, насколько я знаю — такого нету.
Что касается все же передачи ножки в функцию — отдельно ссылку на порт можно передать как обычный указатель, поскольку в АВР порты — это ячейки памяти. Т.е. обращение к такому порту будет выглядеть например так:
вызываться это дело должно как SetPinTo1(&PORTA,0) и работать чисто теоретически должно на любом компиляторе, разве что тип указателя может придется подкрутить.
как-то так, на WinAVR (компилятор gcc) компилится, будет ли работать — не проверял, не на чем.
Передача порта avr как параметра в функцию
Всем здравствуйте великодушно.
Пишу я вам в этот мерзопакостный вечер сейчас 19:47 мск с криком о помощи.
Столкнулся я с такой проблемой с таким недопонимаем своего любимого хобби.
В чём же дело можете вы задать вопрос мне , хорошо отвечаю суть вопроса суть моей тревоги вот в чём.
не могу я понять разобраться с использованием аббревиатур допустим( PORTB и PINB.5)
есть к примеру такая функция
void TestPort(port, pinb.5)
<
if(pinb.5)port=0;
else port=1;
>
как такое реализовать? то есть здесь в функцию передаётся два аргумента сам порт port-PORTB и для проверки пина порта на текущее состояние pinb.5-PINB.5
как передать в функцию такие аргументы для долнейщей рабы с ней в самой функции и для присвоения порту значений?
среда CodeVision
Помагите пожалуйста очень надо мне
Вот до чего доводит использование CodeVision
Ваш pinb.5 в CodeVision — какой-то макрос, какой — смотрите.
Номер пина можно задать числом. Не обращая внимания на типы и прочее — что-то типа этого:
Вот до чего доводит использование CodeVision
Ваш pinb.5 в CodeVision — какой-то макрос, какой — смотрите.
Номер пина можно задать числом. Не обращая внимания на типы и прочее — что-то типа этого:
нееее не видел я там таких макросов для этих целей. Там есть хидер #include <tiny2313.h>
вот его часть
sfrb PINB=0x16;
sfrb DDRB=0x17;
sfrb PORTB=0x18;
sfrb PINA=0x19;
sfrb DDRA=0x1a;
sfrb PORTA=0x1b;
sfrb EECR=0x1c;
sfrb EEDR=0x1d;
sfrb EEAR=0x1e;
sfrb PCMSK=0x20;
sfrb WDTCR=0x21;
sfrb TCCR1C=0x22;
sfrb GTCCR=0x23;
sfrb ICR1L=0x24;
sfrb ICR1H=0x25;
объявления в нём.
Но как их использовать в коде программы.
пробовал так для проверки
void TestPort(int PORTB.0)
<
компилятор ругается
Error. missing ‘,’
хотя на это молчит
void TestPort(int PORTB)
<
Так в чём же дело? друзья
Сборка печатных плат от $30 + БЕСПЛАТНАЯ доставка по всему миру + трафарет
Ведущий производитель электрического оборудования компания MORNSUN выпустила серию источников питания на DIN-рейку LI100-20BxxPR3 c выходами на 12, 15, 24 и 48 В. ИП позиционируются для умных домов, а так же используются в составе оборудования для промышленной автоматизации, различных производственных машин, рельсовых систем транспортировки и другого оборудования, работающего в условиях неблагоприятной окружающей среды.
Компания MEAN WELL продолжает активное развитие номенклатуры, осваивая новые направления и обновляя существующую продукцию с учетом возрастающих требований. В настоящий момент в Компэл представлено множество недавно вышедших новинок MEAN WELL.
MEAN WELL выпустил ряд таких новинок как мощные высоковольтные управляемые источники питания, DC/DC-преобразователи со сверхшироким входом (с креплением на DIN-рейку и на шасси), полностью обновил линейку зарядных устройств (ЗУ), DC/AC-преобразователей (инверторов) и ИБП для охранно-пожарных систем. Кроме того, выпущены специальные источники питания с выходным напряжением в виде ШИМ для светодиодных лент и модулей управляемых по DALI2 и 0…10 В, а также другая продукция.
void test(char* port, char pin)
<
if(pin) *port |= 1;
else *port &=
void main(void)
<
DDRB = 1; // выход
PORTB = 1<<5; // подтяжка
while (1)
<
test(&PORTB, PINB.5);
>
>
вот рабочий код:
void test(char* port, char pin)
<
if(pin) *port |= 1;
else *port &=
На мой взгляд было бы правильней:
Поскольку PINB.5 возвращает состояние бита порта.
Но вот тут мне не ясна одна деталь:
Выходит надо писать так:
А автор случаем не с ардуины слезть пытается?
Поскольку вроде бы там есть очень похожая digitalwrite(port, pin).
Проснувшись сегодня утром и ВКЛ. комп был воодушевлён ответами. Всем спасибо за помощь. С таким прекраснейшим настроением я приступил к водным процедурам))))
Задача функции такая. Есть у меня две функции одинаковые они полностью отличаются только они как раз что используют разные PIN и PORT.
первая использует PIND.5 PIND.0 PINB.3 и PORTD.0
вторая использует PIND.0 PIND.5 PINB.4 и PORTD.5
одинаковые PINы используются в разных местах кода.
Так вот для начала я хочу эти две функции объединить в одну, оптимизировать а нужные PINы и PORTы передавать ей в качестве параметров при вызове. или вот думаю можно так реалировать смену PINов в самой функции по команде через параметр, вот как то так:
void TestPort(int command)
<
port; // здесь мы объявили переменные для порта и пина, но какой ставить тип переменных ?
pin;
if(command==1)
<
port = PORTB; // здесь мы присваиваем переменным номер порта и пина в зависимости от команды
pin = PINB.5;
>
if(pin)port=0; // работаем с переменными пора и пина
else port=1;
вот так тоже делать пришла идея но как?
Вообще это устройство у меня стоит в авто уже года три назад я его сам собрал. его назначение это управление стёклами (стеклоподъёмник) и закрывает он их автоматически при постановке на сигнализацию. У меня там две функции есть одна управляет закрытием водительского окна , другая закрытием пассажирского окна, они одинаковые различаются только работой с разными пинами и портом. Поюзав это собственно ручно собранное замечательное устройство не один год и убедившись в его замечательной надёжности и нужности я решил сделать ему upgrade добавить дополнительно новые функции полезные и подключить его к блютузу.
В частности планируется задействовать в алгоритме ещё и открывание окон (щас только закрыванием онных оно отвечает). Не писать же ещё две такие же функции на открывание
поэтому я и решил написать одну функцию и в параметрах при вызове указывать ей какое окно открывать или закрывать. Вот в чём задумка.
Я понимаю некоторые могут подумать и сказать типа пиши четыре функции одинаковых и иди пей пиво светлое, тёмное кому как нравиться на вкус и цвет товарищей нет. Яб мог конечно так сделать еслиб был пивным алкоголиком но я не из них! Мне кажется это логически неправильно так делать. Да и ресурсы у контроллера не безграничные. щас стоит ATTINY2313 новую реализацию планирую реализовать на ATMEGA8 а то чёт ног у 2313 не хватает мне чуть чуть
Неее я с ней дело никогда не имел. Видел только видюхи на ютубе, забавная вещица
Ну вот я вам соотечественники рассказал полностью откровенно ничего не скрывая свой замысел, какие будит идеи? заранее великодушно всем спасибо.
Передача порта в качестве переменной — AVR
Можно ли использовать порт AVR в качестве переменной, которую можно передавать?
Я хотел бы, чтобы светодиод мог принимать любую комбинацию PORT / Pin, поэтому я бы не стал жестко кодировать ее.
Обратите внимание, что порты определены как:
Символ PORTA разрешается в (* (volatile uint8_t *) ((0x02) + 0x20))
Я считаю, что это позволило бы мне сделать что-то вроде следующего, но я не уверен, понадобится ли мне ключевое слово volatile или нет, и будет ли оно работать так, как ожидалось
Наконец, есть ли способ установить порт / вывод как выход из конструктора светодиодов?
Это будет связано с поиском относительного регистра DDR # для данного номера порта.
Могу ли я предположить &DDR # всегда будет &ПОРТ # -1?
Решение
Макросы регистров в основном являются указателями на область памяти, где находится соответствующий регистр, так что да, вы можете использовать uint8_t volatile * , Однако компилятор не будет генерировать наиболее эффективный код таким способом — он будет использовать косвенную адресацию вместо прямой записи.
Это то, что я делаю вместо этого, используя avrlib .
Тогда вы можете использовать led_pin typedef, например
Другие решения
Порты — это не что иное, как адреса ввода / вывода, поэтому все, что вам нужно сделать, это передать адрес порта ввода / вывода вашему конструктору LED:
Почему это работает? PORTA разрешает, как вы уже упоминали, разыменование указателя:
Передача порта avr как параметра в функцию
Любое устройство на микроконтроллере AVR использует порты ввода вывода. Для работы с портами у AVR`ок есть три регистра: PORTx, PINx и DDRx, где x — буква порта, например A, B, C и т.д.
Регистр DDRx — определяет направление выводов микроконтроллера, PINx позволяет читать их состояние, «осязать» внешний мир, а PORTx в зависимости от направления вывода или задает его логический уровень, или подключает подтягивающий резистор.
Выводы микроконтроллера в проекте обычно задают с помощью макроопределений — define`ов. Мы получаем некую «отвязку» от железа и в дальнейшем это позволяет нам переназначать выводы на другие порты. Например, это может выглядеть так.
Неудобство такого подхода состоит в том, что для каждого вывода нужно определять три регистра. Бывает, что два (только PORTx и DDRx), но это тоже неудобно, если выводов много. Существует другой подход, позволяющий сократить число макроопределений. Разберемся в чем он заключается.
В микроконтроллер atmega16 регистр PORTB имеет адрес 0x18, а регистры DDRB и PINB — 0x17 и 0x16 соответственно. Тоже самое и с регистрами остальных портов, они тоже расположены друг за другом. Мы можем определить в проекте только один регистр, а к остальным обращаться вычисляя их адрес. За основу можно взять любой из них, главное ничего не напутать. Лучше всего для этих целей использовать макросы. Если отталкиваться от регистров PORTx, то макросы будут выглядеть так.
Макросы принимают в качестве параметра адрес регистра PORTx. Для взятия адреса регистра используется оператор &. Посмотрим, как можно использовать эти макросы.
Как видите, определять выводы теперь можно намного короче, однако за удобство приходится платить вставкой макросов.
Компилятор преобразует эти макросы в очень компактный код. Точно такой же, как если бы мы обращались к регистрам используя их имена. И дело в том, что с точки зрения ассемблера это так и есть. Если вы посмотрите ассемблерный код этих примеров, то увидите, что обращение к регистрам осуществляется методом прямой адресации с помощью команд IN, OUT. Поэтому я и озаглавил этот раздел «ненастоящая работа с портом через указатели». Указатели вроде как используются, но на самом деле нет.
Такой подход можно использовать не со всеми микроконтроллерами AVR, потому что в некоторых моделях регистры порта располагаются не по соседним адресам. Как, например, регистры порта F в микроконтроллере ATmega128.
Настоящая работа с портом через указатель
Иногда приходится прибегать к работе с портом, используя настоящий указатель. Для этого создается переменная указатель, которая инициализируется адресом какого-нибудь регистра порта. Делается это следующим образом.
Также этот указатель можно передавать в функцию.
Работа с портом через указатель открывает большие возможности. Например, мы можем определить структуру, которая будет хранить все настройки пина микроконтроллера и обращаться к выводу, используя эту структуру. Или можем определить структуру виртуального порта содержащую выводы микроконтроллера из разных физических портов.
Все это так, но есть ложка дегтя. Работа с регистрами порта через указатель «тяжеловесна» с точки зрения размера кода и его быстродействия. Чтобы в этом убедиться, достаточно взглянуть на получаемый ассемблерный код. Если эти два фактора не критичны, то такой подход можно использовать, если нет, то придется работать по старинке.
Также п ри работе с портом через указатели, даже операция установки/сброса разряда будет неатомарна. Атомарность операций в этом случае нужно обеспечивать самостоятельно.
Вот небольшой пример, как можно использовать указатели при работе с портом.
Еще один пример работы с портом через указатели есть в коде к статье » сенсорная кнопка на микроконтроллере «.
Передача порта avr как параметра в функцию
Меня часто спрашивают, как управлять ножками GPIO микроконтроллера AVR, и как читать их состояние. Несмотря на то, что это довольно просто, описано не только в даташите, но и во многих статьях, например [1], поток вопросов не уменьшается.
Все порты AVR (под AVR обычно подразумеваются микроконтроллеры популярных серий megaAVR и tinyAVR компании Atmel, например ATmega32A, примененный в макетной плате AVR-USB-MEGA16) обладают функционалом чтение-модификация-запись (read-modify-write) при работе с выводами микроконтроллера как обычными портами ввода вывода (general purpose I/O ports, GPIO). При этом можно поменять направление (задать что это — вход или выход) для каждой отдельной ножки GPIO (вывода порта AVR). Каждая ножка также имеет симметричный выходной буфер (два CMOS-ключа, один на +, другой на -), который может выдавать выходной ток от плюса питания (VCC, обычно +5V, drive source), или от земли (GND, минус источника питания, sink source). Мощность выходного драйвера более чем достаточна для прямого управления светодиодом (LED). Также для всех выводов портов, настроенных как вход, можно селективно подключить внутренний верхний нагрузочный резистор (pull-up), встроенный прямо в кристалл микроконтроллера. Все выводы имеют защищающие от перенапряжения диоды, подключенные к VCC и GND.

Упрощенная схема порта AVR, настроенного как вход (состояние по умолчанию, DDRxn == 0).

Упрощенная схема порта AVR, настроенного как выход (DDRxn == 1).
Примечания к рисункам:
Pxn — имя ножки порта микроконтроллера, где x буква порта (A, B, C или D), n номер разряда порта (7 .. 0).
Cpin — паразитная емкость порта.
VCC — напряжение питания.
Rpu — отключаемый нагрузочный верхний резистор (pull-up).
PORTxn — бит n регистра PORTx.
PINxn — бит n регистра PINx.
DDRxn — бит n регистра DDRx.
Каждый порт микроконтроллера AVR (обычно имеют имена A, B и иногда C или даже D) имеет 8 разрядов, каждый из которых привязан к определенной ножке корпуса. Каждый порт имеет три специальных регистра DDRx, PORTx и PINx (где x соответствует букве порта A, B, C или D). Назначение регистров:
| DDRx | Настройка разрядов порта x на вход или выход. |
| PORTx | Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход). |
| PINx | Чтение логических уровней разрядов порта x. |
Регистр DDRx выбирает направление работы каждой отдельной ножки порта. Если в разряд регистра DDRx записана лог. 1, то соответствующая ножка будет сконфигурирована как выход. Ноль означает, что порт сконфигурирован как вход (состояние по умолчанию, которое устанавливается после сброса или включения питания). Если в разряд DDRx записан 0, и в соответствующий разряд PORTx записана 1, то порт не только сконфигурирован как вход, но к нему также еще и подключен внутренний верхний нагрузочный резистор (input pull-up). Если в разряд DDRx записан 0, и в соответствующий разряд PORTx также записан 0, то порт сконфигурирован как вход с высоким входным сопротивлением, что соответствует отключенному выходному состоянию (третье состояние), при этом можно для искусственного создания логических уровней подключать внешние нагрузочные резисторы (pull-up верхний на VCC или pull-down нижний на GND).
Если в разряд PORTx записана лог. 1, и в соответствующий разряд DDRx записана лог. 1, то порт сконфигурирован как выход, и на выходе будет лог. 1. Если в разряд PORTx записана лог. 0, и в соответствующий разряд DDRx записана лог. 1, то порт сконфигурирован как выход, и на выходе будет лог. 0. Т. е. биты PORTx управляют состоянием выходного порта, при условии что в соответствующий порту разряд DDRx записана лог. 1.

Логическая схема организации порта ввода/вывода (GPIO) микроконтроллера ATmega32A.
[Предварительная настройка проекта для доступа к GPIO]
Теперь перейдем к языку C — как можно управлять ножками микроконтроллера AVR? Когда Вы используете WinAVR GCC (или соответствующий GCC-тулчейн от Atmel, который устанавливается вместе с AVR Studio или Atmel Studio), то нужно правильно настроить проект, чтобы нормально распознавались имена регистров AVR (имена DDRA, DDRB, PORTA, PORTB, PINA, PINB и другие), и преобразовывались в нужные адреса. Для этого имеется специальный файл заголовка io.h, который находится в папках инсталляции WinAVR (к примеру, это может быть папка c: \ WinAVR-20100110 \ avr \ include \ avr ) или соответствующего тулчейна Atmel (для Atmel Studio это может быть папка c: \ Program Files \ Atmel \ Atmel Studio 6.0 \ extensions \ Atmel \ AVRGCC \ 3.3.2.31 \ AVRToolchain \ avr \ include \ avr ). Для того, чтобы подключить заголовок io.h, нужно в код модуля (например файл main.c) добавить строку с директивой #include, и в настройках проекта правильно указать тип микроконтроллера. Вот как указывается директива #include:
Имя (модель) процессора AVR может быть указано либо прямо в файле Makefile определением переменной DEVICE, или просто в настройках проекта (AVR Studio или Atmel Studio). Вот пример куска Makefile, где задан тип микроконтроллера ATmega32:
Вот так настраивается тип микроконтроллера в свойствах проекта AVR Studio 4.19 (меню Project -> Configuration Options):

Вот так настраивается тип микроконтроллера в свойствах проекта Atmel Studio 6.0 (меню Project -> Properties):

После того, как подключен файл io.h и задан тип микроконтроллера для проекта, можно в коде программы на языке C использовать имена регистров AVR. Через имена регистров осуществляется доступ к портам GPIO микроконтроллера.
[Как работать с портами AVR как с выходами]
Если у нас есть 8-битная переменная i, то мы можем присвоить её значение регистру PORTx, и тем самым установить ножки микроконтроллера в состояние, соответствующее значению переменной i:
Здесь показана работа с портом D, но Вы точно так же можете работать и с портами A, B, C, если будете использовать соответствующие имена регистров (DDRA, PORTA, DDRB, PORTB и т. п.).
[Как работать с портами AVR как со входами]
Вот как можно прочитать логические уровни из порта D в переменную i:
[Как работать с отдельными ножками порта AVR как с выходами]
Есть возможность получить доступ к отдельным ножкам порта AVR. Это позволяет гибко использовать разряды порта для разных применений.
Некоторые из выводов 8-разрядного порта могут быть сконфигурированы и работать как выходы, причем остальные выводы порта могут работать как входы. Т. е. разные разряды одного порта могут выполнять разные функции в зависимости от потребностей пользователя.
Например, нам нужно, чтобы у порта D разряды 0, 2, 4, 6 (PD0, PD2, PD4, PD6) работали как входы (input), и разряды 1, 3, 5, 7 (PD1, PD3, PD5, PD7) работали как выходы (output). Тогда мы можем использовать код наподобие следующего:
Теперь мы можем вывести любое значение в разряды ножек порта (выходы) 1, 3, 5 и 7. Вот как можно установить эти разряды в лог. 1:
А так можно сбросить эти ножки в лог. 0:
Можно также использовать имена битов, соответствующие их номерам разрядов. Вот например, как можно работать с разрядами порта D через имена:
Можно также для удобства назначать собственные мнемонические имена для разрядов и регистров. Вот как к примеру можно управлять светодиодом, подключенным к разряду 2 порта D:

Здесь макросы LED_ON и LED_OFF заданы для удобного управление одним разрядом порта D. Они позволяют включать и выключать светодиод, подключенный к ножке порта PD2, и делают программу наглядной и простой.
Для составления масок есть также удобные макросы наподобие _BV(n).
[Чтение отдельных разрядов порта AVR]
Чтение отдельных разрядов порта (ножек микроконтроллера) AVR также осуществляется очень просто. К примеру, настройте отдельные ножки (разряды 1 и 3) порта D как входы, и прочитайте их состояние в переменную:
Теперь Вы можете узнать логическое состояние отдельных разрядов (1 или 3) с помощью использования битовых масок, наложенных на значение переменной.
Точно так же можно узнать состояние ножки PD1, если наложить на i маску (1<<PD1):
Второй способ — сдвинуть i вправо нужное число раз (для нашего примера 1 или 3 раза), и затем проверить значение младшего разряда i.
Есть также удобные библиотечные макросы наподобие bit_is_set() или bit_is_clear(), имеющихся в файле sfr_defs.h, которые упрощают задачу проверки отдельных бит.
[Бит PUD]
Имеется также специальная возможность отключения всех внутренних нагрузочных резисторов AVR на всех портах сразу, если установить бит PUD (аббревиатура расшифровывается Pull-Up Disable) в регистре SFIOR (Special Function I/O Register). По умолчанию (после сброса или включения питания) этот бит сброшен, и не оказывает влияние на настройку нагрузочных резисторов.

Регистр SFIOR и размещение в нем бита PUD.
Обычно бит PUD не используют (он остается сброшенным) и настраивают подключением pull-up резисторов только через регистры DDRx. В таблице показано, как влияет на настройку порта бит PUD и биты PORTx, DDRx.
| DDxn | PORTxn | PUD (SFIOR) |
I/O | pull-up | Пояснение |
| 0 | 0 | X | Вход | Нет | Третье состояние (отключено, Hi-Z). |
| 0 | 1 | 0 | Вход | Да | Через порт Pxn будет течь ток через нагрузочный резистор, если на землю подключена внешняя цепь (нагрузка). |
| 0 | 1 | 1 | Вход | Нет | Третье состояние (отключено, Hi-Z). |
| 1 | 0 | X | Выход | Нет | Выход, замкнутый на землю (открыт нижний ключ буфера, верхний ключ закрыт). |
| 1 | 1 | X | Выход | Нет | Выход, замкнутый на плюс питания VCC (открыт верхний ключ буфера, нижний ключ закрыт). |
[Практический пример работы с портами GPIO макетной платы AVR-USB-MEGA16]
Микроконтроллер может управлять электрическими сигналами (зажигать светодиоды, пищать динамиком, включать реле), получать сигналы из внешнего мира (например, нажатия кнопок, сигналы с датчиков). Для этого как раз и используются порты GPIO. В этом разделе приведен простейший для понимания пример таких подключений. К макетной плате AVR-USB-MEGA16 подключена кнопка к порту PB3, которую будет читать программа. Светодиод, подключенный к PB0, установлен на макетной плате.
Полный пример кода main.c, который иллюстрирует работу с портами микроконтроллера ATmega32A макетной платы AVR-USB-MEGA16:
[Как управлять портами микроконтроллера по USB]
Мы уже разобрались, как программа микроконтроллера сама может управлять выводами портов — устанавливать в лог. 0 и лог. 1 (что можно использовать для управления лампочками, реле и другими периферийными устройствами), читать их состояние (можно использовать для принятия нажатия от кнопок, снятия информации с датчиков и различать другие события окружающего мира). Но как заставить компьютер передать команду через USB, чтобы микроконтроллер поменял состояние своей ножки/порта, или чтобы можно было считать в компьютер данные с GPIO, аналого-цифрового преобразователя, SPI или любого другого периферийного устройства микроконтроллера?
Ответ очевиден — нужно просто научиться наладить обмен данными между микроконтроллером и компьютером через USB. Если такой обмен будет налажен, то дальше уже дело техники — микроконтроллер сможет интерпретировать переданный ему байт как команду выполнить определенное действие (например, зажечь или погасить светодиод). И наоборот, микроконтроллер сможет передавать через USB какие-то данные, которые программа компьютера будет принимать и распознавать определенным образом.
Итак, вся загвоздка в том, как наладить обмен между микроконтроллером и компьютером через USB. Вопрос выбора практического решения этой задачи может оказаться весьма непрост — из-за того, что на рынке имеется очень много разных микроконтроллеров, и имеется очень много вариантов программных библиотек для обмена данными между устройствами USB. Но если уже имеются какие-то предпочтения, или выбрана модель микроконтроллера или макетная плата, то выбрать уже проще. Здесь я кратко остановлюсь на возможных решениях для обмена данными по USB для макетных плат AVR-USB-MEGA16 и AVR-USB162. Далее для простоты программу для микроконтроллера буду называть firmware (эта программа будет работать как устройство USB HID или USB CDC), а программу на компьютере, которая обменивается данными с устройством USB, буду называть ПО хоста.
AVR-USB-MEGA16, работающая в качестве устройства USB
На этой макетной плате установлен микроконтроллер ATmega32A. Он не имеет на кристалле специально выделенного аппаратного контроллера USB. Поэтому протокол USB обрабатывается программно. В этом случае firmware микроконтроллера строится на основе популярной, многократно испытанной на деле библиотеке V-USB.
Написать firmware устройства USB HID для микроконтроллера на основе V-USB довольно просто, так имеется много хорошо документированных примеров таких устройств с открытым исходным кодом (как в составе самой библиотеки, так и в Интернете). Для ПО хоста V-USB предлагает примеры, написанные с помощью кроссплатформенной (Windows, Mac, Lunux) библиотеки LibUSB.
Для AVR-USB-MEGA16 есть также два готовых решения — [3] и [4], специально предназначенные для доступа (через USB из ПО хоста) не только к GPIO микроконтроллера, но и к его регистрам на чтение и запись. Благодаря этому можно получить полное управления над всеми портами микроконтроллера и его периферийными устройствами — можно управлять ножками и читать их значение, можно читать данные с аналого-цифрового преобразователя, можно пользоваться интерфейсом SPI, UART и проч.
[3] представляет из себя устройство класса USB HID, для которого не нужен драйвер. Однако из-за того, что для ПО хоста используется LibUSB, необходимо на компьютере установить так называемый драйвер фильтра — программную прослойку между LibUSB и периферийным устройством USB.
[4] — это устройство класса USB CDC (виртуальный COM-порт). ПО хоста, работающее с виртуальным COM-портом, можно написать довольно просто, так как есть много примеров программ и библиотек, которые передают и принимают данные через COM-порт. [4] организовано так, что содержит текстовый интерфейс команд, с помощью которых Вы обычной консоли (putty, TerraTerm, HyperTerminal и т. п.) можете управлять ножками микроконтроллера, читать их значение, можете получить доступ ко всем регистрам AVR. [5] содержит готовый пример (исходный код и скомпилированные бинарники, а также драйвер) firmware USB CDC, портированного на макетную плату AVR-USB-MEGA16.
Наладив обмен данными с AVR-USB-MEGA16 с помощью [3, 4, 5], Вы легко сможете управлять портами микроконтроллера через USB.
AVR-USB162, работающая в качестве устройства USB
Макетная плата AVR-USB162 (или её малогабаритный вариант AVR-USB162MU) выполнена на микроконтроллере AT90USB162. Этот микроконтроллер имеет в своем составе для работы с USB специальный аппаратный интерфейс, и для firmware используются уже другие библиотеки. Самые распространенные — LUFA [6] и библиотека Atmel для устройств AVR USB Series2 [7]. С помощью этих библиотек Вы легко сами можете создать собственное устройство USB HID или USB CDC.
Для ПО хоста USB HID можно использовать не только LibUSB, но и многие другие популярные библиотеки [8]. Наладив обмен данными с AVR-USB162 с помощью [7, 8], Вы легко сможете управлять портами микроконтроллера через USB.
Урок 8. Передача данных через UART в AVR микроконтроллерах
Наступила очередь рассказать о довольно интересной теме — о том, как подключить AVR микроконтроллер к компьютеру. Для этого, у микроконтроллеров есть приемопередатчик. У некоторых их, даже два.
Update:10.02.17
Использовать будем UART наиболее простой и распространенный интерфейс. Чтобы понять как он работает, представьте себе что уарт это водопроводный кран, из которого течет вода — байты. Каждая новая капля, «затирает» старую, поэтому главная задача нашей программы успевать забирать данные до того, как придут новые.
Существует два основных способа, это все время в цикле проверять наличие новых данных по флагам или настроить прерывание, по приходу нового байта. Если программа не использует задержки и скорость передачи низкая, то можно использовать первый способ, в остальных случаях, лучше использовать прерывания.
Данные приходят последовательно один байт за другим, поэтому часто UART называют последовательным портом. Если ваша программа достаточно простая, то можно принять/передать один байт и на основании этого выполнить какое то действие. Например если приняли байт 100, то включили светодиод.
Только не стоит путать ASCII символ и байт, например i = ‘1’ i = 1 не одно и тоже. В чем разница можно почитать тут
Если ваша программа подразумевает, нечто более сложное, т.е. прием нескольких байт, то в прерывании по приходу данных складываем их в массив, далее в основном цикле разбираем массив. Стоит понимать, что поток не имеет ни начала, ни конца, он просто льется постоянно, поэтому нужно самому придумать какие то условности которые бы говорили о том, что сейчас пришел первый байт, а сейчас последний. Типовое решение, это использование спецсимволов, либо таймаутов. Пример можно посмотреть тут
Теперь перейдем к железу. У Atmega8 всего один приемопередатчик, PD0 — Rx, receiver (приемник) и PD1 — Tx, transmitter (передатчик).

Аналогичные ножки есть на нашем переходнике FT232. Соединяем микроконтроллер и переходник между собой. В протеусе это будет выглядеть так:

Напишем программу, которая по сигналу от компьютера будет включать светодиод и выключать. В CodeVision создаем новый проект, микроконтроллер atmega8, частота 8МГц (тактирование от кварца), на закладке USART включаем приемник и передатчик.

Обратите внимание, при текущей частоте микроконтроллера и скорости передачи, ошибка составляет 0,2%, т.е. данные могут теряться, но вероятность этого крайне низкая. Порт B настроим как выход — к нему подключим светодиод.
В текущем алгоритме используется простой способ функция getchar() постоянно проверяет не появились ли новые данные. После того как данные пришли — записываем их в переменную data; если пришла единичка, зажигаем светодиод; если 0, то выключаем его. Еще раз обратите внимание ‘1’ означает ASCII символ 1, реально байт будет равен 0x31. Сделано так, потому что большинство терминалов при нажатии кнопки на клавиатуре, отправляет именно символы.
Собираем схемку и прошиваем. Подключаем к компьютеру. Запускаем программу KeTerm или другой терминал. Подсоединяемся к нужному com порту. Шлем единичку — светодиод включается, шлем 0 светодиод выключается.
Урок был бы неполным, если не применить наши знания по C#.

Создадим проект, нарисуем 2 кнопки и последовательный порт
В свойствах последовательного порта не забудьте настроить PortName, он должен соответствовать номеру порта переходника FT232. Добавим события: по клику на 1 кнопку — отсылаем 1, по клику на 2 кнопку — отсылаем 0, при загрузке формы — открытие порта, при выходе из формы — закрытие порта.
private void button1_Click(object sender, EventArgs e) private void button2_Click(object sender, EventArgs e) private void Form1_Load(object sender, EventArgs e) private void Form1_FormClosing(object sender, FormClosingEventArgs e)
И напоследок, видео работы всего этого безобразия)
Update: По просьбе Евгения программа немного изменена, теперь при запуске программа автоматически проверяет все ком порты, если есть активные, то они заносятся в comboBox1.

151 комментарий: Урок 8. Передача данных через UART в AVR микроконтроллерах
Для выделения памяти 2 элементов в массиве использовал конструкцию:
byte[] buf = new byte[2];
Если вдуг кому нибудь понадопбиться.
Делаете успехи 
Спасибо, стараемся.. ) Тот кто ищет всегда найдет )
Добрый день. Скомпилировал Вашу прошивку и залил в контроллер. Программой для пк воспользовался тоже Вашей. Но, увы, ничего не заработало. С чем это может быть связано? usb-uart преобразователь CP2102.
с чем угодно, не правильно подсоединено, не правильные скорости и т.п. земли мк и переходника обязательно соединять
Подскажите, как с Вами можно связаться — Вконтакте и т.д. Буду очень признателен.
у меня вопрос.через UART можно передавать а также принимать с компьютера .Одно временно нельзя.А если мне нужно со схемы в комп принять информацию а потом передать что я должен сделать?
Заранее говорю спасибо.
Если у вас полный дуплекс, то линии Rx,Tx независимые, поэтому прием никак не зависит от передачи.
System.IO.Ports.SerialPort.GetPortNames(); — А это в каком фреймвёрке эта функцияия есть?
Спасибо за статью, с терминалами обменяться данными получилось.
Можно ли сделать так, чтобы программа (для компьютера) отправляла не ASCII код, а просто числа?
красавчик, помоги! пенсионеру. Надо с меги16, в CVAVR отправить unsigned int в C# по UART. Примерно понимаю что надо както разбить на байты, отправить, получить и както склеить.Хоть два кусочка с кодами : разбить-отправить и получить-склеить
знаю язык Cи как мой трёхлетний сосед русский. Ну вот слепил рабочий код
CVAVR:
type : ATmega16A
Program type : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*******************************************************/
// Standard Input/Output functions
#include
#include
#include
unsigned int MSBLSB = 12345;
unsigned char MSB,LSB;
// External Interrupt(s) initialization
// INT2: On
// INT2 Mode: Falling Edge (падающий фронт)
GICR|=(0<<INT1) | (0<<INT0) | (1<<INT2);
MCUCR=(0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (0<<ISC00);
MCUCSR=(0<<ISC2);
GIFR=(0<<INTF1) | (0<<INTF0) | (1<<INTF2);
// USART initialization с прарыванием по приёму
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud Rate: 9600
UCSRA=(0<<RXC) | (0<<TXC) | (0<<UDRE) | (0<<FE) | (0<<DOR) | (0<<UPE) | (0<<U2X) | (0<<MPCM);
UCSRB=(1<<RXCIE) | (0<<TXCIE) | (0<<UDRIE) | (1<<RXEN) | (1<<TXEN) | (0<<UCSZ2) | (0<<RXB8) | (0<<TXB8);
UCSRC=(1<<URSEL) | (0<<UMSEL) | (0<<UPM1) | (0<<UPM0) | (0<<USBS) | (1<<UCSZ1) | (1<<UCSZ0) | (0<<UCPOL);
UBRRH=0x00;
UBRRL=0x19;
//отправляем старший байт
UDR = MSB;
delay_ms(500);
//отправляем младший байт
UDR = LSB;
while (1)
>
__________________________________________________________________________
С#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
// Делегат используется для записи в UI control из потока не-UI
private delegate void LineReceivedEvent(byte размер);
//метод «serialPort1_DataReceived», который будет выполнен в потоке не-UI при поступлении данных в последовательный порт:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
private void LineReceived(byte размер)
else
>
private void Form1_Load(object sender, EventArgs e)
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
>
>
коряво конечно, но работает. подрихтуй пожалуйста. Может кому то пригодится.
