Передача порта avr как параметра в функцию
Перейти к содержимому

Передача порта avr как параметра в функцию

  • автор:

Микроконтроллеры AVR

Можно ли в IAR для AVR передать в качестве параметра адрес порта ввода-вывода? Сразу предупреждаю – задача не выдуманная, но описание сильно упрощено. Просьба не предлагать менять схему или сделать принципиально иначе. Хочется именно так. Просьба также не привязываться к терминологии. Итак. Есть интерфейсная микросхема, подключаемая к ножкам порта(ов) ввода-вывода. И есть функция, реализующая обмен с этой микросхемой, для чего дергающая программно ножками порта(ов). Для того, чтобы было просто менять ножки, к которым подключена микросхема, нужно обозвать их мнемониками через #define, и при необходимости переобозвать. Проблема появляется, если таких микросхем больше одной, и каждая из них подключена к своему набору ножек МК. Как изменить функцию так, чтобы можно было одной функцией связываться (последовательно, не одновременно) с несколькими микросхемами? То есть функция должна принимать в качестве параметра адрес порта (собственно, проблема) и номер бита (это просто). Для 24-х ПИКов такое написалось очень просто – там вполне понятная адресация, и с портом можно прекрасно работать через указатели. А вот с AVR…

    • Что-то опять не выходит. Подробнее.-=John=- (673 знак., 16.11.2008 16:30 )
      • Снимаю вопрос. Все работает вот так.-=John=- (237 знак., 16.11.2008 16:38 )
        • «AD5300_Write ( &PORTC, 2, 128 );» Что говорить, очень информативно получилось 🙂 — Vladimir Ljaschko (17.11.2008 14:44 )
          • Да это просто пример — к реальной программе не имеет вообще никакого отношения. — -=John=- (17.11.2008 16:15 )
          • Я что-то такое писал — не жреть. Порт — он sfr, написать-=John=- (67 знак., 11.11.2008 19:05 )
            • Что-то вы меня запутали. Не поленился, расчехлил IAR:Сергей Борщ (1797 знак., 11.11.2008 23:12 )
            • ну пусть будет void func (uint8_t volatile __io *port, uint8_t mask). А В пиках с понятной адресацией — как? — Сергей Борщ (11.11.2008 22:01 )
              • 4.20A не хочет — ругаетсяVit (103 знак., 11.11.2008 22:08 )
                • Хм, а привести __io к простому указателю он дает? Ведь все порты отображаются в АП данных, тока адреса на 0x20 больше. — she (11.11.2008 22:14 )
                  • Ну дык даёт. Мне пофиг 0x20 там или сколько — компилер это умеет разгребать — нафига ему это ещё раз объяснять? — Vit (11.11.2008 22:36 , ссылка)
                    • Сам численное значение модифицирует? Ух ты. Умный, зараза. — she (11.11.2008 22:43 )
                    • тут ничего не напутано? Какой смысл в приведении типа внутри функции? Скорее оно нужно при вызове. — Сергей Борщ (11.11.2008 22:14 )
                      • Из боязни;), так как через __generic не получилось. Я не знаю даже, работает оно или нет.Vit (76 знак., 11.11.2008 22:22 )
                        • если внутри функции приведение не нужно, то на что же ругался компилятор у -=John=-? __generic — вообще неоправданная трата ресурсов в этом случае. Тип параметра известен заранее. — Сергей Борщ (11.11.2008 22:55 )
                          • Понимаю так, что __io это атрибут, порождающий нечто похожее на #define PORTn (*(unsigned char*)(_ADDR+IO_BASE)), где IO_BASE равно известному 0x20, потому у него нет явной типизации, но некоторые действия как с указателями всё-таки применимы.Vit (729 знак., 12.11.2008 01:27 )
                            • Атрибут __io еще меняет порядок чтения/записи байт в 2-х байтовых регистрах. Например позволяет работать с таймером как с int. — Михаил Е. (12.11.2008 11:44 )
                              • Спасибо, интересно — Vit (12.11.2008 12:09 )
                              • Да это вообще ужас.-=John=- (107 знак., 11.11.2008 19:08 )
                                • В этом случае идеально подходит С++ и шаблоны. А на С можно вручную написать 4 функции uint8_t SPI_exchange(uint8_t) и передавать указатель на нужную функцию. Будет и быстрее и меньше по объему. — Сергей Борщ (11.11.2008 22:08 )
                                  • Ну, про 4 функции понятно — сам так с двумя делал. Но это экстенсивный путь развития. И некрасиво. — -=John=- (12.11.2008 08:36 )
                                  • Как объявлен I2CClockA? — Сергей Борщ (11.11.2008 22:09 )
                                    • static volatile unsigned int * I2CClockA; — -=John=- (12.11.2008 08:47 )
                                      • И у IAR также. Можете смело использовать свои макросы. — Сергей Борщ (12.11.2008 11:43 , ссылка)
                                        • Спасибо, посмотрю внимательней на адреса. — -=John=- (12.11.2008 12:39 )
                                        • Да поехать-то оно поехало. Хочется красиво.-=John=- (319 знак., 11.11.2008 19:14 )
                                          • Да понятно. Но обычно с AVR приходится экономить время, возлагая это дело на препроцессор и оптимизацию, ессно. — Vladimir Ljaschko (11.11.2008 19:20 )
                                            • Это неверный способ экономии времени, ИМХО.-=John=- (307 знак., 11.11.2008 19:31 )
                                              • Вопрос спорный. В новом проекте и я переписываю макросы, так и вы переписываете. Кроме того, убойный аргумент — скорость контроллера. Я на STM32 вообще библиотеку использую со структурами, описывающими порты. Там времени вагон. — Vladimir Ljaschko (11.11.2008 19:47 )
                                                • Да, спор беспредметный. Каждый пишет как привык — так по любому быстрее получается. — -=John=- (11.11.2008 19:53 )

                                                Лето 7532 от сотворения мира. При использовании материалов сайта ссылка на caxapу обязательна. Вебмастер
                                                MMI © MMXXIV

                                                Ненастоящая работа с портом через указатель

                                                Любое устройство на микроконтроллере AVR использует порты ввода вывода. Для работы с портами у AVR`ок есть три регистра: PORTx, PINx и DDRx, где x — буква порта, например A, B, C и т.д.
                                                Регистр DDRx — определяет направление выводов микроконтроллера, PINx позволяет читать их состояние, «осязать» внешний мир, а PORTx в зависимости от направления вывода или задает его логический уровень, или подключает подтягивающий резистор.
                                                Выводы микроконтроллера в проекте обычно задают с помощью макроопределений — define`ов. Мы получаем некую «отвязку» от железа и в дальнейшем это позволяет нам переназначать выводы на другие порты. Например, это может выглядеть так.


                                                #define BUT_PIN 3
                                                #define BUT_PORTX PORTB
                                                #
                                                define BUT_DDRX DDRB
                                                #define BUT_PINX PINB

                                                Неудобство такого подхода состоит в том, что для каждого вывода нужно определять три регистра. Бывает, что два (только PORTx и DDRx), но это тоже неудобно, если выводов много. Существует другой подход, позволяющий сократить число макроопределений. Разберемся в чем он заключается.

                                                В микроконтроллер atmega16 регистр PORTB имеет адрес 0x18, а регистры DDRB и PINB — 0x17 и 0x16 соответственно. Тоже самое и с регистрами остальных портов, они тоже расположены друг за другом. Мы можем определить в проекте только один регистр, а к остальным обращаться вычисляя их адрес. За основу можно взять любой из них, главное ничего не напутать. Лучше всего для этих целей использовать макросы. Если отталкиваться от регистров PORTx, то макросы будут выглядеть так.


                                                //это макросы для доступа к регистрам порта
                                                #define PortReg(port) (*(port))
                                                #define DirReg(port) (*((port) - 1))
                                                #define PinReg(port) (*((port) - 2))

                                                Макросы принимают в качестве параметра адрес регистра PORTx. Для взятия адреса регистра используется оператор &. Посмотрим, как можно использовать эти макросы.


                                                //определили вывод мк
                                                #define BUT_PIN 3
                                                #define BUT_PORT PORTB
                                                .
                                                // конфигурируем вывод как вход
                                                DirReg(&BUT_PORT) &= ~(1<//включаем подтягивающий резистор
                                                PortReg(&BUT_PORT) |= (1<
                                                .
                                                //проверяем нажата ли кнопка
                                                if(! (PinReg(&BUT_PORT)&(1 <.
                                                >

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

                                                Компилятор преобразует эти макросы в очень компактный код. Точно такой же, как если бы мы обращались к регистрам используя их имена. И дело в том, что с точки зрения ассемблера это так и есть. Если вы посмотрите ассемблерный код этих примеров, то увидите, что обращение к регистрам осуществляется методом прямой адресации с помощью команд IN, OUT. Поэтому я и озаглавил этот раздел «ненастоящая работа с портом через указатели». Указатели вроде как используются, но на самом деле нет.
                                                Такой подход можно использовать не со всеми микроконтроллерами AVR, потому что в некоторых моделях регистры порта располагаются не по соседним адресам. Как, например, регистры порта F в микроконтроллере ATmega128.

                                                Настоящая работа с портом через указатель

                                                Иногда приходится прибегать к работе с портом, используя настоящий указатель. Для этого создается переменная указатель, которая инициализируется адресом какого-нибудь регистра порта. Делается это следующим образом.


                                                //объявляем указатель на регистр
                                                //обязательно должно присутствовать volatile
                                                volatile uint8_t *portReg;

                                                //инициализация
                                                //передаем адрес регистра PORTB
                                                portReg = &PORTB;

                                                //вывод в порт через указатель
                                                //перед указателем ставиться оператор *
                                                (*portReg) = 0xff;

                                                Также этот указатель можно передавать в функцию.


                                                void OutPort(volatile uint8_t *pReg, uint8_t data)
                                                *pReg = data;
                                                >

                                                .
                                                //записываем в PORTB число 0xff
                                                OutPort(&PORTB, 0xff);

                                                //а здесь уже не нужен оператор &
                                                //так как мы передаем переменную с адресом порта
                                                OutPort(portReg, 0xff);

                                                Работа с портом через указатель открывает большие возможности. Например, мы можем определить структуру, которая будет хранить все настройки пина микроконтроллера и обращаться к выводу, используя эту структуру. Или можем определить структуру виртуального порта содержащую выводы микроконтроллера из разных физических портов.
                                                Все это так, но есть ложка дегтя. Работа с регистрами порта через указатель «тяжеловесна» с точки зрения размера кода и его быстродействия. Чтобы в этом убедиться, достаточно взглянуть на получаемый ассемблерный код. Если эти два фактора не критичны, то такой подход можно использовать, если нет, то придется работать по старинке.
                                                Также п ри работе с портом через указатели, даже операция установки/сброса разряда будет неатомарна. Атомарность операций в этом случае нужно обеспечивать самостоятельно.

                                                Вот небольшой пример, как можно использовать указатели при работе с портом.


                                                //струтура для хранения настроек вывода - номера и порта
                                                typedef struct outputs uint8_t pin;
                                                volatile uint8_t *portReg;
                                                >outputs_t;


                                                //функция инициализации
                                                void OUT_Init(outputs_t *out, uint8_t pin, volatile uint8_t *port, uint8_t level)
                                                //сохраняем настройки в структуру
                                                out->pin = pin;
                                                out->portReg = port;

                                                //конфигурируем вывод на выход
                                                (*(port-1)) |= (1<
                                                //задаем логический уровень
                                                if (level) (*port) |= (1 <>
                                                else (*port) &= ~(1 <>
                                                >

                                                //установить на выходе 1
                                                void OUT_Set(outputs_t *out)
                                                (*(out->portReg)) |= (1 pin);
                                                >

                                                //установить на выходе 0
                                                void OUT_Clear(outputs_t *out)
                                                (*(out->portReg)) &= ~(1 pin);
                                                >

                                                //определили вывод мк
                                                #define OUT1_PIN 4
                                                #define OUT1_PORT PORTB
                                                .
                                                //объявляем переменную для хранения
                                                //настроек пина
                                                outputs_t out1;

                                                //инициализируем ее
                                                OUT_Init(&out1, OUT1_PIN, OUT1_PORT, 0);

                                                //устанавливаем 1 на выводе OUT1_PIN
                                                OUT_Set(&out1);

                                                Еще один пример работы с портом через указатели есть в коде к статье » сенсорная кнопка на микроконтроллере «.

                                                Передача порта avr как параметра в функцию

                                                AVR-port-sticker

                                                Меня часто спрашивают, как управлять ножками 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-input-simply-sch

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

                                                AVR-output-simply-sch

                                                Упрощенная схема порта 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.

                                                AVR-General-Digital-IO

                                                Логическая схема организации порта ввода/вывода (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:

                                                #include < avr/io.h > 

                                                Имя (модель) процессора AVR может быть указано либо прямо в файле Makefile определением переменной DEVICE, или просто в настройках проекта (AVR Studio или Atmel Studio). Вот пример куска Makefile, где задан тип микроконтроллера ATmega32:

                                                F_CPU = 12000000 DEVICE = atmega32 BOOTLOADER_ADDRESS = 0x7000 ..

                                                Вот так настраивается тип микроконтроллера в свойствах проекта AVR Studio 4.19 (меню Project -> Configuration Options):

                                                AVR-Studio419-select-device

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

                                                Atmel-Studio60-select-device

                                                После того, как подключен файл io.h и задан тип микроконтроллера для проекта, можно в коде программы на языке C использовать имена регистров AVR. Через имена регистров осуществляется доступ к портам GPIO микроконтроллера.

                                                [Как работать с портами AVR как с выходами]

                                                DDRD = 0xFF; //настройка всех выводов порта D как выходов PORTD = 0x0F; //вывод в разряды порта D значений 00001111 

                                                Если у нас есть 8-битная переменная i, то мы можем присвоить её значение регистру PORTx, и тем самым установить ножки микроконтроллера в состояние, соответствующее значению переменной i:

                                                1 2 3
                                                uint8_t i=0x54; 
                                                PORTD = i;

                                                Здесь показана работа с портом D, но Вы точно так же можете работать и с портами A, B, C, если будете использовать соответствующие имена регистров (DDRA, PORTA, DDRB, PORTB и т. п.).

                                                [Как работать с портами AVR как со входами]

                                                Вот как можно прочитать логические уровни из порта D в переменную i:

                                                DDRD = 0; //настройка всех выводов порта D как входов i = PIND; //прочитать все 8 ножек порта 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 2 3 4 5 6
                                                DDRD=0; // Сброс всех битов в 0 (00000000). DDRD |= (1  1)|(1  3)|(1  5)|(1  7); // Использование операции сдвига // битов // чтобы установить биты 1, 3, 5, 7 // в лог. 1. Остальные разряды // останутся в состоянии лог. 0 

                                                Теперь мы можем вывести любое значение в разряды ножек порта (выходы) 1, 3, 5 и 7. Вот как можно установить эти разряды в лог. 1:

                                                PORTD |= (1  1)|(1  3)|(1  5)|(1  7);

                                                А так можно сбросить эти ножки в лог. 0:

                                                PORTD &= ~((1  1)|(1  3)|(1  5)|(1  7));

                                                Можно также использовать имена битов, соответствующие их номерам разрядов. Вот например, как можно работать с разрядами порта D через имена:

                                                1 2 3 4
                                                PORTD |= (1  PD5)|(1  PD7); // установка битов 5 и 7 PORTD &= ~((1  PD5)|(1  PD7)); // сброс битов 5 и 7 PORTD |= (1  PD1); // установка бита 1 PORTD &= ~ (1  PD1); // сброс бита 1 

                                                Можно также для удобства назначать собственные мнемонические имена для разрядов и регистров. Вот как к примеру можно управлять светодиодом, подключенным к разряду 2 порта D:

                                                AVR-LED-connect-example

                                                1 2 3 4 5 6
                                                #define LED PD2 #define LED_ON() PORTD |= (1 #define LED_OFF() PORTD &= ~(1 
                                                LED_ON(); //Зажечь светодиод LED_OFF(); //Погасить светодиод

                                                Здесь макросы LED_ON и LED_OFF заданы для удобного управление одним разрядом порта D. Они позволяют включать и выключать светодиод, подключенный к ножке порта PD2, и делают программу наглядной и простой.

                                                Для составления масок есть также удобные макросы наподобие _BV(n).

                                                [Чтение отдельных разрядов порта AVR]

                                                Чтение отдельных разрядов порта (ножек микроконтроллера) AVR также осуществляется очень просто. К примеру, настройте отдельные ножки (разряды 1 и 3) порта D как входы, и прочитайте их состояние в переменную:

                                                DDRD &=~ ((1  1)|(1  3)); // Очистка битов 1 и 3 регистра направления порта D. i = PIND; // Прочитать состояние всех 8 ножек порта D. 

                                                Теперь Вы можете узнать логическое состояние отдельных разрядов (1 или 3) с помощью использования битовых масок, наложенных на значение переменной.

                                                1 2 3 4 5 6 7 8 9 10
                                                if ((1  PD3) & i) < //Здесь можно выполнить некоторые действия, если PD3==1 .. > else < //Здесь можно выполнить некоторые действия, если PD3==0 .. >

                                                Точно так же можно узнать состояние ножки PD1, если наложить на i маску (1<

                                                1 2 3 4 5 6 7 8 9 10
                                                if ((1  PD1) & i) < //Здесь можно выполнить некоторые действия, если PD1==1 .. > else < //Здесь можно выполнить некоторые действия, если PD1==0 .. >

                                                Второй способ — сдвинуть i вправо нужное число раз (для нашего примера 1 или 3 раза), и затем проверить значение младшего разряда i.

                                                1 2 3 4 5 6 7 8 9 10 11
                                                i >> =3; if (1 & i) < //Здесь можно выполнить некоторые действия, если PD3==1 .. > else < //Здесь можно выполнить некоторые действия, если PD3==0 .. >

                                                Есть также удобные библиотечные макросы наподобие bit_is_set() или bit_is_clear(), имеющихся в файле sfr_defs.h, которые упрощают задачу проверки отдельных бит.

                                                [Бит PUD]

                                                Имеется также специальная возможность отключения всех внутренних нагрузочных резисторов AVR на всех портах сразу, если установить бит PUD (аббревиатура расшифровывается Pull-Up Disable) в регистре SFIOR (Special Function I/O Register). По умолчанию (после сброса или включения питания) этот бит сброшен, и не оказывает влияние на настройку нагрузочных резисторов.

                                                AVR-SFIOR

                                                Регистр 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, установлен на макетной плате.

                                                avr-usb-mega16-GPIO-using-example

                                                Полный пример кода main.c, который иллюстрирует работу с портами микроконтроллера ATmega32A макетной платы AVR-USB-MEGA16:

                                                1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
                                                #include "avr\io.h" #include "avr\iom8.h" 
                                                int main(void) < DDRB &= ~_BV(3); //Сброс разряда 0 регистра DDRB в лог. 0 (PB3 // настроен как вход). Этот порт используется // для чтения состояния кнопки. Кнопка подключена // к ножке порта PB3 и к земле. PORTB |= _BV(3); //Разрешить нагрузочный резистор на PB3. DDRB |= _BV(0); //Установка разряда 0 регистра DDRB в лог. 1 // (PB0 настроен как выход). PORTB &= ~_BV(0); //Погасить светодиод, подключенный к PB0. while(1) < if (bit_is_clear(PINB, 3)) //Кнопка нажата? < //Да, кнопка нажата! PORTB |= _BV(0); //Зажечь светодиод. loop_until_bit_is_set(PINB, 3); //Светодиод будет гореть, // пока кнопка нажата. PORTB &= ~_BV(0); //Погасить светодиод. > > >

                                                [Как управлять портами микроконтроллера по 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.

                                                [Ссылки]

                                                Передача порта avr как параметра в функцию

                                                Указатель — это переменная, содержащая адрес некоторого элемента данных (переменной, константы, функции, структуры). В языке C указатели тесно связаны с обработкой массивов и строк, которые будут рассмотрены в следующем разделе.

                                                Для объявления переменной как указателя используется оператор *:

                                                Для присвоения адреса некоторой переменной указателю используется опера­тор &, например:

                                                char * p ; // объявляем указатель (способен содержать адрес хранения любого символа);

                                                char c = ‘А’; // объявляем символьную переменную с;

                                                р = & c ; // теперь p содержит адрес хранения символа c .

                                                Для того чтобы извлечь значение переменной, на которую указывает указа­тель, используется оператор разыменования *.

                                                char b; char c = ‘ А ‘;

                                                b = * р ; // теперь b равняется не адресу хранения переменной c , а значению переменной c , то есть ‘А’.

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

                                                char b, char c = ‘ А ‘;

                                                р = & b ; // теперь p содержит адрес хранения ещё не инициализированной переменной b ;

                                                * р = b ; // инициализируем переменную b значением ‘А’.

                                                Применительно к программированию микроконтроллеров, указатели можно использовать, к примеру, для записи данных в порт ввода/вывода. Предположим, регистр данных порта расположен в памяти по адресу 0x16. В этом случае, для записи в него значения 0 xFF можно воспользоваться следующим фрагментом программного кода:

                                                unsigned char * p ; //объявление указателя на символ

                                                p = 0 x 16; // присваиваем указателю адрес порта PORTx

                                                * p = 0 xFF ; // теперь на выводах порта PORTx будут все единицы.

                                                Передача в функции параметров по ссылке.

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

                                                int SumAndDiv ( int * a , int * b ) /* Объявляем функцию, возвращающую сумму от целочисленного деления без остатка и остатка */

                                                int bufA , bufB ; // Объявление целых чисел – bufA и bufB ;

                                                bufA = * a ; // bufA = значению, на которое указывает указатель а;

                                                bufB = * b ; // bufB = значению, на которое указывает указатель b ;

                                                * a = bufA / bufB ; //Указатель a ссылается на результат целочисленного деления без остатка

                                                * b = bufA % bufB ; //Указатель b ссылается на остаток от целочисленного деления

                                                return bufA + bufB ; //Функция возвращает сумму

                                                sum = SumAndDiv(&vl, &v2); //sum = 4; vl = 3; v2 = 1.

                                                В функции SumAndDiv вначале сохраняются в буферных переменных значе­ния, на которые указывают указатели а и b . Затем в переменную, на которую ука­зывает указатель а записывается результат деления переданных в функцию значе­ний без остатка, а в переменную, на которую указывается указатель b — остаток от такого деления. Поскольку с помощью указателей мы напрямую обращались к ячейкам памяти, а не к переменным, то после выхода из функции содержимое этих ячеек остается неизменным. При вызове функции SumAndDiv в нее переда­ются адреса переменных vl и v 2, которым в теле функции соответствуют указате­ли а и b .

                                                Указатели на структуры

                                                На структуры можно создавать указатели точно так же, как и для любого дру­гого типа данных. Пример применения такого указателя:

                                                struct DATE MyBirthday, *dateP;

                                                dateP = SMyBirthday ; // dateP указывает на структуру MyBirthday

                                                В таком случае для доступа к полям структуры через указатель используется onepa тор ->:

                                                Можно также использовать и разыменование указателя:

                                                В последнем примере операция * dateP заключена в скобки , поскольку оператор разыме­нования * имеет меньший приоритет по сравнению с оператором доступа к полю структуры .

                                                Указатели также могут быть полями структуры. Это часто используется для создания в памяти цепочек однотипных структур, когда каждая предыдущая ука­зывает на следующую:

                                                struct DATE * next _ date ; // указатель на другую структуру того же типа

                                                struct DATE datel, date2;

                                                Теперь, к примеру, оператору datel . next _ date -> Year соответствует дос­туп к полю Year структуры date 2 (то есть, это эквивалентно записи date 2. Year ).

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

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