разработка и программирование электронных устройств

Beaglebone Black: Управление портами GPIO на языке C

Для одноплатного компьютера Beaglebone Black со встроенной операционной системой Linux существует множество способов управления портами ввода/вывода GPIO на различных языках программирования, но все же язык С является для Unix -подобных операционных систем самым родным языком.

В прошлой статье мы рассмотрели пример управления светодиодами с помощью виртуальной файловой системы. Таким же образом можно управлять портами GPIO.
В этом случае используется директория /sys/class/gpio .

Но сначала разберемся с использованием GPIO в виртуальной файловой системе из командной строки.
Перейдите в директорию /sys/class/gpio и просмотрите содержимое этого каталога :

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

root@beaglebone:/sys/class/gpio/echo 38 > /sys/class/gpio/export

После этого появиться новый подкаталог gpio38 с файловой структурой :

root@beaglebone:/sys/class/gpio/cd gpio38
root@beaglebone:/sys/class/gpio/gpio38/ls -al

Будем использовать линию gpio38 как выход :

echo high > /sys/class/gpio/gpio38/direction

Установка логического уровня на линии порта :

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 <stdio.h>
#include <stdlib.h>
#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;  
}

Компилируем программу и проверяем как она работает :

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() выглядит следующим образом :

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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
 
#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 :

gcc led.c -o led
./led on
./led off

В сети интернет можно найти библиотеки на языках С и C++ для удобного обращения к портам GPIO . Вы можете использовать одну из этих библиотек, например BBBIOlib.

5 Comments to Beaglebone Black: Управление портами GPIO на языке C

  1. mishastik's Gravatar mishastik
    22 августа 2014 at 14:27 | Permalink

    Здрасти! Извените если задам не совсем умный вопрос, но в статье написано в пояснении ко второму листингу на С, что инициализация GPIO1 выполнена в первой программе… имеются ввиду строки с 9 по 33 ? (я так понимаю что там выполнена настройка направления порта) Так?
    И ещё. Во второй программе есть такие строки » gpio1[ GPIO_DATAOUT/4 ]…. » Почему базовый адрес регистра GPIO_DATAOUT делится на 4 ?
    Я совсем недавно занялся освоением линукса на BBB и для меня некоторые вещи в диковинку )))
    Спасибо!

  2. mishastik's Gravatar mishastik
    26 августа 2014 at 14:43 | Permalink

    регистр да, 32-разрядный… . сагласно мануалу на камень 0x13C – это не базовый адрес регистра , а его смещение относительно базового адреса порта… и именно его надо использовать для записи. а не делёное на 4…. да, регистры порта можно рассматривать как массив, расположенный по адресу порта, но это ни как не объясняет деление на 4 я не понимаю причём тут разрядность. половину жизни пишу код для контроллеров и впервые вижу такое.

  3. mishastik's Gravatar mishastik
    26 августа 2014 at 15:11 | Permalink

    аааа…. кажется понял…. gpio1 это int указатель, а GPIO_DATAOUT фактически адрес первого байта регистра, соответственно индекс регистра в массиве будет в 4 раза меньше… так?
    и ещё вопрос… почему при вызове mmap первый и второй параметры соответственно NULL и 0×1000 ? спасибо!

  4. mishastik's Gravatar mishastik
    26 августа 2014 at 15:48 | Permalink

    упс. всё, вопрос снят )))) нарыл в мануале…

Leave a Reply

You must be logged in to post a comment.