Raspberry Pi. Работаем с GPIO на С - Часть 2

Это продолжение предыдущей статьи о работе с GPIO Raspberry Pi. В данной статье речь пойдет о том, как работать с GPIO на языке C, и как добиться от GPIO максимальной скорости работы.

Максимальная частота GPIO, которую удалось получить в предыдущей статье с помощью Python составила ~27 кГц. Надо сказать, такого быстродействия вполне достаточно для большинства задач, требующих частого переключения управляющего сигнала (например, управление  динамической индикацией, вроде бегущих строк). Тем не менее, не исключены ситуации, когда от логических выводов Raspberry Pi может потребоваться большее быстродействие (ну например, чтобы принимать сигналы от пульта дистанционного управления нужно регистрировать сигналы с фото-датчика, несущая частота которого составляет 30-56 кГц). Как бы там ни было, интересно узнать предельные возможности GPIO и увидеть, насколько большой прирост производительности может дать C. Забегая вперед, скажу, что полученные результаты меня приятно удивили. Черновая редакция.

Исходное состояние


Исходное состояние мини-ПК такое же, как и в предыдущей статье: установлена и настроена ОС Raspbian, подключено два кабеля (питание и Ethernet), IP-адрес Raspberry  - 192.168.1.35. Работа с Raspberry Pi также велась удаленно через терминал посредством SSH, хотя принципиального значения это не имеет. Как настроить SSH доступ описано тут. Все эксперименты проводились с тем же самым выводом общего назначения GPIO 7 (он же P1-26).

Лабораторная "установка" и оборудование


Для наблюдения сигнала на GPIO 7 использовался цифровой осциллограф Tektronix TDS1002B. Привожу фото осциллографа и всей установки (собственно, в предыдущей статье я использовал такое же оборудование, в прошлый раз я его не сфотографировал, поэтому выкладываю тут).


Примечание: Эксперименты проводились как с проводом (черно-красный на фото), так и без него (щупы подключены непосредственно к GPIO). Длина провода на полученные результаты не повлияла. Результаты измерений см. ниже.

Как работать с GPIO на C


Один из вариантов работы с GPIO на языке C - это использование библиотеки bcm2835, документацию и последнюю версию которой можно скачать здесь.

0) Скачиваем и устанавливаем библиотеку "bcm2835"

В терминале Raspberry Pi набираем:
     wget www.open.com.au/mikem/bcm2835/bcm2835-1.21.tar.gz   - скачиваем библиотеку
     tar zxvf bcm2835-1.21.tar.gz       - распаковываем архив, появится папка bcm2835-1.21/
     cd bcm2835-1.xx                    - заходим в папку, компилируем и устанавливаем библиотеку:
     ./configure                                  
     make                                         
     sudo make check                      
     sudo make install                          

Примечание: для последующей работы рекомендую использовать консольный файловый менеджер mc.

1)

Характеристики GPIO


1. Обычный режим
Для проверки максимальной скорости GPIO Raspberry использовался следующий код:


Исходный код
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <bcm2835.h>

#define PIN RPI_GPIO_P1_26

int main(int argc, char **argv, char **envp) {
        int i;

        if (!bcm2835_init())
                exit(EXIT_FAILURE);

        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);

        for(i = 0; i < 5000; i++) {
                bcm2835_gpio_write(PIN, HIGH);
                bcm2835_gpio_write(PIN, LOW);
        }
        bcm2835_close();
        return (EXIT_SUCCESS);
}


Что получилось в результате:

Raspberry GPIO на C - обычный режим Raspberry GPIO на C - обычный режим

Минимальная длительность импульса - 100 нс, частота переключения - 5 МГц!

2. Алгоритм планирования - SCHED_FIFO, sched_priority = 1


Исходный код
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <bcm2835.h>

#define PIN RPI_GPIO_P1_26

int main(int argc, char **argv, char **envp) {
        int i;
        struct sched_param sp;

        if (!bcm2835_init())
                exit(EXIT_FAILURE);

        sp.sched_priority = 1;
        if ( sched_setscheduler( 0, SCHED_FIFO, &sp ) !=0 )
                 fprintf(stderr, "%s:%i sched_setscheduler(): %s\n", __FILE__, __LINE__, strerror(errno));

        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);

        for(i = 0; i < 5000; i++) {
                bcm2835_gpio_write(PIN, HIGH);
                bcm2835_gpio_write(PIN, LOW);
        }
        bcm2835_close();
        return (EXIT_SUCCESS);
}


Результаты:

Raspberry GPIO на C - режим SCHED_FIFO, sched_priority=1 Raspberry GPIO на C - режим SCHED_FIFO, sched_priority=1

Результат - без изменений. Как и в первом случае, при многократном повторении эксперимента иногда возникают задержки (просадка на левом графике), причина которых - многозадачность Linux.

3. Алгоритм планирования - SCHED_FIFO, sched_priority = MAX


Исходный код
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <bcm2835.h>

#define PIN RPI_GPIO_P1_26

int main(int argc, char **argv, char **envp) {
        int i;
        struct sched_param sp;

        if (!bcm2835_init())
                exit(EXIT_FAILURE);
     
        sp.sched_priority = sched_get_priority_max(SCHED_FIFO);
        if ( sched_setscheduler( 0, SCHED_FIFO, &sp ) !=0 )
                fprintf(stderr, "%s:%i sched_setscheduler(): %s\n", __FILE__, __LINE__, strerror(errno));

        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);

        for(i = 0; i < 1000; i++) {
                bcm2835_gpio_write(PIN, HIGH);
                bcm2835_gpio_write(PIN, LOW);
        }
        bcm2835_close();
        return (EXIT_SUCCESS);
}


Результат такой же - частота переключения не изменилась. Как и в прошлый раз, с максимальным приоритетом иногда могут возникать задержки:

Raspberry GPIO на C - режим SCHED_FIFO, sched_priority=MAX Raspberry GPIO на C - режим SCHED_FIFO, sched_priority=MAX

Выводы


Сведем все результаты в общую таблицу:
Показатель
Python (RPi.GPIO)
обычный режим
SCHED_FIFO, priority = 1
SCHED_FIFO, priority = MAX
Максимальная частота переключения
27 кГц
5 МГц
5 МГц
5 МГц
Минимальная длительность импульса
18-20 мкс
100 нс
100 нс
100 нс

Таким образом, по сравнению с Python'ом C позволил получить приблизительно 185-ти кратный прирост скорости. Максимальная частота переключения, которую можно программно "выжать" из логических выводов GPIO - 5 МГц (длительность импульса - 100 нс). При этом следует иметь ввиду следующее:

  • добавление кода в цикл переключения может заметно увеличить длительность пребывания вывода в состояниях "1" и "0";
  • полученные показатели могут быть нестабильны, поскольку Linux не является ОС реального времени. 

Многократное повторение опытов показало, что поведение GPIO и появление "задержек" особо не зависит от алгоритма планирования и приоритета процесса. На данном этапе можно предположить, что положительный эффект от использования режима SCHED_FIFO может быть виден при большой загрузке процессора Raspberry, когда на нем выполняется ряд задач одновременно.

Поэтому, если через GPIO нужно управлять устройством, крайне критичным к режиму реального времени - таки придется использовать дополнительный микроконтроллер. Если же подключаемое устройство требует "скоростного" сигнала, но допускает появление задержек в несколько микросекунд - то вполне успешно можно использовать GPIO Raspberry без дополнительного контроллера.


PS. Благодарность анонимному комментатору из предыдущей статьи за идею с SCHED_FIFO и предоставленный пример кода.

8 комментариев:

  1. Спасибо за статью! :)
    собственно такие результаты я и ожидал, просто хотелось увидеть цифры.
    вы правы, SCHED_FIFO будет играть роль когда CPU будет сильно подгружен.
    sched_priority влиять и не должен, у вас нет RT процессов
    а вот нестабильность сорее всего не из-за ОС, а что-то гораздо более низкоуровневое, возможно обработка прерываний

    аноним. :)

    ОтветитьУдалить
  2. Спасибо за исследование, цифры радуют)

    ОтветитьУдалить
  3. Скажите что надо установить, что-бы скомпилировать исходники, и как запустить потом на исполнение, или дайте ссылку где об этом почитать.

    Спасибо за статью.
    Виталий.

    ОтветитьУдалить
    Ответы
    1. Для компиляции ничего ставить не нужно - в Линуксе уже есть все необходимое. Компилятор - gcc. Если исходник написан в файле gpio.c, а исполняемый файл мы хотим назвать rasp_fifo, то строка для компиляции будет выглядеть так:

      gcc gpio.c -o rasp_fifo -l bcm2835

      Буквально это значит следующее: запустить компилятор gcc, откомпилировать gpio.c и назвать исполняемый файл rasp_fifo, при компиляции использовать библиотеку bcm2835.

      После этого появится исполняемый файл rasp_fifo, для его запуска:

      sudo ./rasp_fifo

      Удалить
  4. Можно раскрыть от чего программа на СИ получилась настолько "быстрой" и хотелось бы узнать, на сколько будет эффективней программа на си по отношению к аналогичной написанной в Python? Планируется написать программу с графической оболочкой для управления шаговыми двигателями по заложенному алгоритму. Возможно, если у Raspberry не хватит производительности, придется использовать ее как внешнее периферийного устройства, а программу ставить на основной PC.

    ОтветитьУдалить
  5. Как изменится скорость при отказе от ОС?
    Поможет ли повысить частоту использование ПДП

    ОтветитьУдалить