Для одноплатного компьютера Beaglebone Black со встроенной операционной системой Linux существует множество способов управления портами ввода/вывода GPIO на различных языках программирования, но все же язык С является для Unix -подобных операционных систем самым родным языком.
В прошлой статье мы рассмотрели пример управления светодиодами с помощью виртуальной файловой системы. Таким же образом можно управлять портами GPIO.
В этом случае используется директория /sys/class/gpio .
Но сначала разберемся с использованием GPIO в виртуальной файловой системе из командной строки.
Перейдите в директорию /sys/class/gpio и просмотрите содержимое этого каталога :
1 2 |
root@beaglebone:/ cd /sys/class/gpio root@beaglebone:/sys/class/gpio/ ls -al |
Результат будет выглядеть приблизительно так :
Обратите внимание на названия портов gpiochip0, gpiochip32, gpiochip64, gpiochip96.
Это все те-же доступные для устройства порты GPIO0 – GPIO3, только нумерация линий портов у них идет от 0 до 127.
Не все выводы портов будут нам доступны, поскольку некоторые задействованы в схеме BeagleBone Black. Какие именно порты GPIO можно использовать в своих целях вы можете прочитать в документе Beagle Bone Black System Reference Manual соответствующей версии платы. Собственно это выводы микропроцессора AM3359, выведенные на два боковых разъема платы.
Делаем экспорт используемых линий GPIO . Например линия 6 порта GPIO1, которая в моей версии платы выведена на контакт 3 разъема P8.
Посчитаем ее номер , учитывая сквозную нумерацию линий : 1 * 32 + 6 = 38
1 |
root@beaglebone:/sys/class/gpio/echo 38 > /sys/class/gpio/export |
После этого появиться новый подкаталог gpio38 с файловой структурой :
1 2 |
root@beaglebone:/sys/class/gpio/cd gpio38 root@beaglebone:/sys/class/gpio/gpio38/ls -al |
Будем использовать линию gpio38 как выход :
1 |
echo high > /sys/class/gpio/gpio38/direction |
Установка логического уровня на линии порта :
1 2 |
echo 1 > /sys/class/gpio/gpio38/value echo 0 > /sys/class/gpio/gpio38/value |
Оформим все выше изложенное в виде программы на языке С :
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* gpio38.c */ #include #include #define GPIO_PATH "/sys/class/gpio" #define GPIO38_PATH “/sys/class/gpio/gpio38” int main( int argc, char * argv[] ) { FILE * fp; if (argc!= 2) { printf(“wrong number of arguments\n”); return EXIT_FAILURE; } fp = fopen( GPIO_PATH"/export","w"); if ( fp == NULL ) { printf(“can not open file\n”); return EXIT_FAILURE; } fprintf( fp, "38"); fclose( fp ); fp = fopen( GPIO38_PATH”/direction”, “w”); if ( fp == NULL ) { printf(“can not open file\n”); return EXIT_FAILURE; } fprintf( fp, “high”); fclose(fp); fp = fopen( GPIO38_PATH”/value”, “w”); if ( fp == NULL ) { printf(“can not open file\n”); return EXIT_FAILURE; } fprint( fp, argv[1] ); fclose(fp); return EXIT_SUCCESS; } |
Компилируем программу и проверяем как она работает :
1 2 3 |
gcc gpio38.c -o gpio38 ./gpio38 1 ./gpio38 0 |
Управление портами через виртуальную файловую систему (ВФС) имеет ограничения по скорости, поэтому написать программу на основе ВФС для высокоскоростного переключения портов (например вывод на TFT LCD экран ) вряд ли получиться.
Для более серьезной работы с платой необходимо иметь под рукой документацию на используемый процессор AM3359.
Загрузить Reference Manual на семейство процессоров AM335x можно с сайта производителя данного устройства Texas Instruments.
Порты GPIO организованы так же , как и у большинства микроконтроллеров ARM . Для каждого порта в пространстве памяти периферийных устройств есть базовый адрес, относительно которого размещены абсолютно одинаковые для каждого порта GPIO
регистры.
Базовые адреса портов GPIO можно узнать в разделе 2 справочного руководства , где описана карта памяти микропроцессора (лично для меня более приемлемо называть его микроконтроллером, но раз на сайте производителя называют микропроцессором, то как говорится «жираф большой , ему видней»(С) В.Высоцкий).
Обращаться к регистрам GPIO будем через указатели языка С .
Доступ к карте памяти мы получим благодаря использованию драйвера, расположенного в /dev/mem . Для этого нам понадобиться всего два системных вызова Linux — open() и mmap() .
Системный вызов open() традиционно для ОС Linux открывает устройство. Что касается системного вызова mmap() , то с его помощью можно выполнить отображение устройства или файла на память, говоря на жаргоне — «замапить» устройство или файл.
Пользоваться системным вызовом достаточно просто, необходимо передать в функцию начальный адрес , размер отображаемых данных, режим защиты, тип отображаемого объекта, файловый дескриптор, смещение файла. Функция в результате своей работы вернет указатель на отображенные данные. После этого через данный указатель можно получить доступ к любому байту из отображенных данных, записанные значения будут автоматически выведены в файл(или устройство).
Прототип системного вызова mmap() выглядит следующим образом :
1 |
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset); |
- start – начальный адрес
- length – размер отображаемых данных
- prot – режим защиты
- flags – тип отображаемого объекта
- fd – дескриптор файла
- offset – смещение внутри файла
При возникновении ошибки традиционно для системных вызовов Linux mmap() возвращает значение ошибки -1 . Код ошибки можно прочитать из глобальной переменной errno сразу после вызова mmap() .
В случае успешного завершения системный вызов mmap() возвращает адрес отображенных данных.
Теперь выполним моргание светодиодом , используя прямое управление портом GPIO , к которому подключен соответствующий светодиод.
Смотрим в принципиальную электрическую схему платы BeagleBone Black.
Нужно сначала посмотреть ревизию платы, для каждой ревизии принципиальная схема отличается. У моей платы BeagleBone Black была старая ревизия A5B.
Из принципиальной схемы мы видим, что пользовательский светодиод usr0 подключен к линии 21 порта GPIO1.
Из руководства пользователя определяем базовый адрес порта GPIO1, который равен 0x4804C000. В описании регистров GPIO находим базовый адрес регистра GPIO_DATAOUT, который равен 0x13C.
Теперь настало время написать простую программу, выполняющую мерцание светодиодом usr0 непосредственно через управление линией GPIO1_21. Инициализацию порта в этой простой программе мы выполнять не станем, поскольку она уже выполнена для светодиода usr0 .
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/* led.c */ #include #include #include #include #include #include #include #include #define GPIO1_BASE 0x4804C000 #define GPIO_DATAOUT 0x13C int main(int argc, char * argv[] ) { volatile uint32_t * gpio1; int fp; if ( argc != 2 ) { printf("wrong number of arguments\n"); return EXIT_FAILURE; } fp = open( "/dev/mem", O_RDWR | O_SYNC ); if ( fp < 0 ) { printf("can not open mem\n"); return EXIT_FAILURE; } gpio1 = (uint32_t *)mmap( NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fp, GPIO1_BASE ); if ( strcmp( (const char *)argv[1], "off" ) == 0 ) { gpio1[ GPIO_DATAOUT/4 ] &= ~( 1 << 21 ); } else if ( strcmp( (const char *)argv[1],"on") == 0 ) { gpio1[ GPIO_DATAOUT/4 ] |= ( 1 << 21 ); } else { printf("unknown argument\n"); close(fp); return EXIT_FAILURE; } close(fp); return EXIT_SUCCESS; } |
Скомпилируем программу, затем поочередно включим и выключим светодиод usr0 :
1 2 3 |
gcc led.c -o led ./led on ./led off |
В сети интернет можно найти библиотеки на языках С и C++ для удобного обращения к портам GPIO . Вы можете использовать одну из этих библиотек, например BBBIOlib.
Viewed 133773 times by 16111 viewers
Comments
Здрасти! Извените если задам не совсем умный вопрос, но в статье написано в пояснении ко второму листингу на С, что инициализация GPIO1 выполнена в первой программе… имеются ввиду строки с 9 по 33 ? (я так понимаю что там выполнена настройка направления порта) Так?
И ещё. Во второй программе есть такие строки » gpio1[ GPIO_DATAOUT/4 ]…. » Почему базовый адрес регистра GPIO_DATAOUT делится на 4 ?
Я совсем недавно занялся освоением линукса на BBB и для меня некоторые вещи в диковинку )))
Спасибо!
Вы имеете ввиду вот эти строки ?
Если да, то инициализация GPIO1_21 выполняется при запуске ОС , поскольку на этой линии находится светодиод, линия настраивается как выход GPIO.
Совершенно верно
Потому что регистры 32-битные ( 4 байта ). Например , адреса первых трех регистров 0x00, 0x04, 0x08 , соответственно это 0-вой , 1-ый и 2-ой элементы массива uint32_t
регистр да, 32-разрядный… . сагласно мануалу на камень 0x13C — это не базовый адрес регистра , а его смещение относительно базового адреса порта… и именно его надо использовать для записи. а не делёное на 4…. да, регистры порта можно рассматривать как массив, расположенный по адресу порта, но это ни как не объясняет деление на 4 я не понимаю причём тут разрядность. половину жизни пишу код для контроллеров и впервые вижу такое.
аааа…. кажется понял…. gpio1 это int указатель, а GPIO_DATAOUT фактически адрес первого байта регистра, соответственно индекс регистра в массиве будет в 4 раза меньше… так?
и ещё вопрос… почему при вызове mmap первый и второй параметры соответственно NULL и 0x1000 ? спасибо!
упс. всё, вопрос снят )))) нарыл в мануале…