Skip to content
  • Категории
  • Главная
  • Документация
  • Последние
  • Пользователи
Collapse
R

RomanNV5

@RomanNV5
Сводка
Сообщения
6
Темы
6
Группы
0
Подписчики
0
Подписки
0

Сообщения

Последние Лучшие сообщения Спорные

  • GPIO для чайников (часть 6)
    R RomanNV5

    Создаём простейшее интернет-радио.

    Для начала составим себе техническое задание, чтобы чётко представлять, что мы хотим получить. Нам нужна программа, которая бы проигрывала аудиопотоки интернет-радиостанций. Программа должна автоматически запускаться при включении RPI, программа должна предоставлять возможность переключаться между радиостанциями, управление программой должно осуществляться без участия клавиатуры и мыши. Управление громкостью предусматривать не будем, т.к. с этим могут отлично справиться подключённые к RPI колонки, или наушники с регулятором громкости.

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

    Нам нужна программа, которая бы проигрывала аудиопотоки интернет-радиостанций. Программа должна автоматически запускаться при включении RPI, программа должна предоставлять возможность переключаться между радиостанциями, управление программой должно осуществляться без участия клавиатуры и мыши. Управление громкостью предусматривать не будем, т.к. с этим могут отлично справиться подключённые к RPI колонки, или наушники с регулятором громкости.

    Теперь набросаем общий алгоритм работы данной программы, т.е. последовательность действий:

    1. Старт программы (инициализация переменных, присваиваем счётчику проигрываемой радиостанции 1)
    2. Начинаем проигрывать радиостанцию, на которую указывает счётчик
    3. Проверяем, нажата ли кнопка (если кнопка не нажата, то переходим к пункту 3)
    4. Если кнопка нажата, то увеличиваем счётчик радиостанций на 1, завершаем проигрывание текущей радиостанции и переходим к пункту 2.
      Всё. Как видно из алгоритма, наша программа будет работать бесконечно. Выход из неё не предусмотрен. Но, т.к. в данном случае предполагается, что мы RPI будем использовать исключительно как интрнет-радиоприёмник, то в принципе выход и не нужен. Включать мы его будем включением в розетку источника питания, соответственно и выключать будем выключением БП из розетки. В прочем, если захочется, нам ничего не стоит сделать это в будущем.

    Теперь обдумаем детали пройдясь по алгоритму.

    По 1 пункту вопросов нет. Там всё ясно. В этом пункте нам, как минимум, нужно будет объявить и инициализировать переменную счётчика проигрываемой радиостанции и составить список этих самых радиостанций, т.е. плей-лист. Где взять адреса потоков интернет-радиостанций? Ну я просто вбил запрос в Гугль, и из него взял первые попавшиеся 4 ссылки свободного доступа. Вот они:

    http://listen2.myradio24.com:9000/8304
    http://89.208.99.16:8088/zvezda_128
    http://imgradio.pro/MegaRadio
    http://imgradio.pro/RusHit

    Разумеется, вы можете взять любое количество, любых ссылок на любы интернет-радиостанции, которые нравятся лично вам.

    По 2 пункту. Главный вопрос- как, или чем проигрывать радиостанции. Ответ на этот вопрос тоже простой- нужно либо написать программу- проигрыватель, либо взять уже готовую. Я, например, в программировании чайник. И написать аудиоплеер с нуля, для меня задача крайне трудновыполнимая. Поэтому я в решении данного вопроса выберу второй вариант. В стандартную комплектацию ОС Raspbian входит прекрасный консольный медиаплеер- omxplayer. Он является многофункциональной и законченной программой. Думаю, для решения нашей задачи трудно найти что либо более подходящее, чем omxplayer. Формат запуска этого медиплеера такой: omxplayer <имя, или адрес проигрываемого файла>. Например, чтобы проиграть первый аудиопоток из нашего списка, можно набрать в терминале

    omxplayer http://listen2.myradio24.com:9000/8304
    

    Через пару секунд плеер подключится к данному потоку и в наушниках, вы услышите музыку. Чтобы прекратить воспроизведение и выйти из программы, в том же терминале нужно нажать клавишу q. Опций у этого плеера довольно много. С ними вы можете ознакомиться из хелпа к самому плееру. Но для нашего случая нам достаточно знать как этот плеер можно запустить и как его можно выключить. Мы это знаем, а значит с пунктом 2 мы тоже разобрались.

    По пункту 3.Этот пункт для нас не составляет никаких проблем. Кнопку мы подключать научились. Нужно лишь выбрать порт, к которому мы её подключим. Выберем порт, который уже имеет подтяжку к +3,3в, а именно Р1-05. Ну и Р1-06 GND.

    По пункту 4. Тоже никаких вопросов. Этот пункт решается программно без проблем.

    Вроде бы можно приступать к реализации. Но тут скрываются ещё две проблемки, невидимые на первый взгляд, но очень существенные. Во-первых, вспомните нашу программу из второй части цикла статей. Мы там писали программу мигающего светодиода и завершали программу по нажатию кнопки. Там наш светодиод мигал 1 раз в секунду, потом происходил опрос состояния кнопки. И чтобы гарантированно выйти из программы, нам приходилось держать кнопку нажатой не менее 1 секунды, чтобы дождаться окончания цикла мигания светодиода. Иначе мы рисковали тем, что программа не заметит нажатие кнопки и не отреагирует на него. В нашем случае эта проблема встаёт наиболее остро. Ведь все команды в программе выполняются последовательно, и если мы запустим omxplayer на воспроизведение, то очередь до опроса состояния кнопки дойдёт лишь после завершения работы запущенного плеера, а значит никогда! Т.к. плеер наш должен выключиться только после нажатия на эту самую кнопку. Значит, нам нужно найти какой-то механизм, позволяющий опрашивать состояние кнопки даже во время запущенной другой программы. Во-вторых, нам нужно как-то из нашей программы запустить другую независимую программу (собственно сам omxplayer), да ещё и найти возможность управлять им, подменяя нажатие клавиш на клавиатуре нажатием нашей кнопки.

    Для решения первой проблемы стоит обратить внимание на то, что Linux- многозадачная ОС. Мы видим, что при запущенном LXDE по нему прекрасно бегает курсор мышки, можем запустить браузер, текстовый редактор, треминал… И всё это вместе прекрасно сосуществует и работает. Нам нет необходимости закрывать LXDE, чтобы передвинуть курсор мышки на новое место. Значит, нам в программе нужно сделать тоже самое. Мы должны организовать работу плеера и опрос клавиатуры в разных задачах, но работающих одновременно. В Linux существует множество способов организации многозадачности. Для нашего случая, я посчитал, что наиболее оптимальным решением будет организация двух параллельных процессов. Вопрос этот достаточно обширен и не очевиден для лёгкого понимания. Поэтому, чтобы разобраться с ним, надо обратиться к литературе. Наберите в гугле «Linux процессы» и изучите найденные там статьи. Если же попытаться объяснить работу процессов в двух словах, то происходит это примерно так. Когда мы запускаем какую-то программу, то она запускается в отдельном процессе, который организует операционная система. Далее, в нашей программе мы можем создать несколько копий этого процесса. Основной процесс называется родительский, а созданные в программе другие процессы (порождённые) называются дочерними. Дочерние процессы подчиняются родительскому и наследуют его свойства. Так же из родительского процесса мы можем управлять дочерними (командовать), дочерние же процессы могут только просить родительский процесс о чём либо, либо рассказывать ему о своём состоянии. После создания родительского процесса мы можем запустить в нём любую другую программу. Причём программы в родительском и дочерних процессах будут выполняться одновременно, пользуясь многозадачностью, предоставляемую Linux-ом. Вот и мы поступим так же. Создадим параллельный процесс. В родительском процессе будем следить за состоянием кнопки, а в дочернем процессе запустим медиаплеер. Тепрь наша программа в целом сможет одновременно проигрывать поток и следить за нажатием кнопки, а так же отдавать команды этому медиаплееру.

    Для решения второй проблемы мы используем функцию execlp(). Эта функция принадлежит целому семейству похожих функций. Все они позволяют запускать из своего приложения другие, но требуют различных параметров. Подробности использования данной функции можно легко найти через Гугл. А чтобы запущенный медиаплеер воспринимал нажатие нашей кнопки, как команду с клавиатуры, мы будем запускать его не в обычном терминале, а в псевдотерминале. Псевдотерминал, это такой терминал, который вроде бы есть, но на самом деле его нет, поэтому мы легко сможем ему скормить всё, что угодно, выдавая это за команды с клавиатуры. При чём об этом будет беспокоится сам Linux, а не мы. Для запуска процесса в псевдотерминале существует функция forkpty(), а для записи «всего, чего угодно» в этот псевдотерминал мы используем функцию write().

    И так, чтобы полностью осознать то, как это всё вместе будет работать, вам нужно самостоятельно через гугл найти описание процессов в Linux, работу функций forkpty(), execlp() и попытаться разобраться.

    Теперь вроде всё готово для написания нашей программы. У меня получилась такая:

    Упражнение к 5-й части статей "GPIO для чайников."

    // Интернет-радио для Raspberry Pi
    //
    // Переключение между станциями осуществляется
    // кратковременным нажатием на кнопку.
    // Кнопку управления подключить к Р1-05 и Р1-06
    //
    // Компиляция:
    // gcc -o radio radio.c -lutil -lrt -lbcm2835
    //
    // Запуск: sudo ./radio
    // или автозапуск при помощи скрипта.
    
     
    #include                    // Библиотека для управления GPIO
    
    
     
    
    // Следующие 3 библиотеки необходимы для организации работы параллельного процесса
    
    #include 
    #include 
    #include 
    
    #define PIN_IN RPI_GPIO_P1_05    // Определяем порт GPIO, к которому будет подключена кнопка
    #define STATIONS_MAX_NUM 4   // Количество станций в нашем плейлисте
     
    // Это наш маленький типа плэйлист
    // Между кавычек вставляем URL потока вещания станции        
    
    char *station1={"http://listen2.myradio24.com:9000/8304", NULL};
    char *station2={"http://89.208.99.16:8088/zvezda_128", NULL};
    char *station3={"http://imgradio.pro/MegaRadio", NULL};
    char *station4={"http://imgradio.pro/RusHit", NULL};
    
     
    // Функция запускает omxplayer в псевдотерминале
    // и заставляет проигрывать заданный поток
    // Параметр функции **omx_arg содержит текстовую строку с URL потока
    // Это дочерний процесс
    
    void do_child (char **omx_arg)
    {
                if (execlp("omxplayer", "omxplayer", *omx_arg) < 0)
                {
                      perror("exec omxplayer");
                      exit(1);
                }
    }
    
    // В основном процессе мы будем опрашивать состояние кнопки
    // и обрабатывать её нажатие, передавая команды
    // omxplayer-у, работающему в дочернем процессе.
    // Входными параметрами функции являются:
    // fd- дескриптор файл;
    // p- ID организованного процесса;
    // n- порядковый номер проигрываемой станции.
    // На выходе функция возвращает порядковый номер следующей станции.
    
    
     
    int do_parent (int fd, pid_t p, int n)
    {
    
        while(bcm2835_gpio_lev(PIN_IN)){}   // Просто ждём, пока не будет нажата кнопка управления
        bcm2835_delay(500);                                // Эта задержка в 500мс нужна для фиксации нажатия кнопки
        char r;                                            
        write(fd, "q", 1);                             // Здесь мы посылаем omxplayer-у эквивалент нажатия на клавишу "q" клавиатуры
    
                                                               // что заставляет плеер завершить работу
    
        while (read(fd, &r, 1) > 0) { write(1, &r, 1); }       // Всё, что ниже, нужно для корректного завершения процесса
        waitpid(p, 0, 0);
        close(fd);                                        // Всё, плеер выключен, процесс завершён. Можно начинать всё заново с новой станцией
        ++n;                                    // Увеличиваем порядковый номер проигрываемой станции на 1
        if (n>STATIONS_MAX_NUM) n=1;         // Проверяем, если мы проиграли все станции в плей-листе, то устанавливаем номер снова на 1
        return(n);                                        // Выходим из функции и возвращаем номер очередной станции
    }
    
     
    // Функция проигрывания очередной станции
    // Входной параметр n- порядковый номер проигрываемой станции.
    // Если добавили в плей-лист ещё станций, то и здесь нужно увеличить
    // количество строк case
    
    void play_station(int n)
    {
            switch (n)
            {
                    case 1: do_child(station1);
                    case 2: do_child(station2);
                    case 3: do_child(station3);
                    case 4: do_child(station4);
            }
    }
    
    
    // Основная программа
    int main()
    {
    
                int fd;              // файловый дескриптор
                int n=1;            // порядковый номер станции
    
                if (!bcm2835_init())    // Инициализация GPIO
                return 1;       //Завершение программы, если инициализация не удалась
    
                bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT);
    
                while(n)           // Бесконечный цикл. Выход не предусмотрен.
                {
                            pid_t p = forkpty(&fd, 0, 0, 0); // Запускаем параллельный процесс в псевдотерминале
                            switch (p)
                            {
                                       case 0:  play_station(n);          // Если всё получилось, запускаем плеер в созданном процессе
                                       case -1: perror("forkpty");      // Иначе аварийное завершение.
                                       exit(EXIT_FAILURE);
    
                                       default: break;
    
                            }
                    n=do_parent(fd, p, n); // А в основном процессе начинаем следить за кнопкой.
                }
    }
    

    Ну вот, как то так!

    Теперь эту программку нужно сохранить под именем radio.c, скомпилировать и можно запускать. Команда для компиляции написана в шапке программы. Перед запуском не забудьте подключить кнопку к пинам Р1-05 и Р1-06. Если нет ошибок, то после запуска программы вы должны услышать музыку, а каждое нажатие на кнопку должно переключать медиаплеер на следующую станцию. Когда наиграетесь, завершить выполнение программы можно нажатием на Ctrl-c. А чтобы наверняка прибить все запущенные процессы, можно ввести команду sudo killall radio.

    Основная часть работы сделана. Осталось добавить нашу программу в автозапуск, чтобы она автоматически стартовала после загрузки системы. Сделать это нетрудно.

    Создаём файл autostart.sh в директории /etc/init.d/

    sudo nano /etc/init.d/autostart.sh
    
    1. В этом файле прописываем скрипт запуска нашего интернет-радио:
    sudo /home/pi/Myprog/radio
    exit 0
    

    обратите внимание на то, что нужно прописать полный путь до запускаемого файла. В моём случае файл radio находится в папке /Myprog/.

    1. Сохраняем файл и закрываем редактор nano. Теперь дадим права на исполнение нашему скрипту:
    sudo chmod 755  /etc/init.d/autostart.sh
    
    1. Ну и добавим наш скрипт в автозапуск:
    sudo update-rc.d autostart.sh default
    

    Если всё сделано верно, то RPI ответит на это сообщением:update-rc.d: using dependency based boot sequencing

    Это всё. Теперь можно перегрузить RPI. После перезагрузки мы должны услышать трансляцию аудиопотока первой по списку интернет-радиостанции. Если всё так, то выключаем RPI и выдёргиваем из него все провода (если у вас нет Wi-Fi адаптера, то Ethernet кабель придётся оставить). Прячем наш RPI в изящную коробочку, выводим на панель нашу кнопочку, подключаем колонки и ставим на холодильник готовое устройство, наслаждаемся проделанной работой под музычку, играемую нашим RPI-интернет-радио. Остаётся теперь заказать ещё один RPI, чтобы можно было и дальше проводить различные эксперименты и создавать различные устройства.

    Если же вы не планируете пожизненно использовать данный RPI под радио, то когда наиграетесь, можно убрать наш скрипт из автозагрузки командой:

    sudo update-rc.d autostart.sh remove
    

    После этого можно вообще удалить наш скрипт autostart.sh.

    На этом закончим. Желающие могут легко модифицировать программу под свои нужды. Например, можно добавить ещё пару кнопок и привязать к ним управление громкостью, или выключение. Для этого достаточно последовательно опрашивать порты на которые подключены кнопки и при нажатии на них передавать функцией write() символы, или коды клавиш управления omxplayer-ом.

    ПС. Я допускаю, что с точки зрения продвинутого программиста моя программа далека от идеала, и всё можно сделать более грамотно и возможно проще. Но я не профессиональный программист и мои знания, и опыт в программировании крайне малы. Но раз программа работает, и делает то, что от неё требовалось, значит можно считать, что задача выполнена.


  • GPIO для чайников (часть 5)
    R RomanNV5

    Снова возвращаемся к кнопкам и создаём первое полезное приложение.

    Во второй части статей «GPIO для чайников» мы подключали кнопку между двумя портами. Один порт устанавливали на «вывод» и в состояние «1», а другим портом читали эту «1» через кнопку. При отпущенной кнопке на втором порту читался «0», а при нажатой- «1».
    917b22bc-3308-4ccd-88a7-df5e6778142e-image.png
    Всё вроде бы работало. Но недавно у такого способа подключения обнаружился один неприятный недостаток. Такое подключение кнопки оказалось неустойчивым к помехам. Когда я свою RPI подключил к сети при помощи Wi-Fi USB донгла, то все программы, в которых использовалось такое включение кнопки, просто сошли с ума. RPI детектировал нажатие кнопки даже тогда, когда к ней никто не притрагивался. Виной всему оказался длинный провод, которым была подключена кнопка к RPI. Работающий Wi-Fi адаптер наводил на нём потенциал, с уровнем достаточным для детектирования «1» на порте GPIO. Такое поведение кнопки никуда не годится.

    Нужно принимать меры по помехозащищённости. В данном случае поможет использование экранированного провода до кнопки, либо даже простое уменьшение длины проводов, соединяющих кнопку с RPI. Но можно поступить проще. Раз наш RPI реагирует на помехи при отсутствии сигнала на входе, то разумным решением будет перевести порт, которым мы читаем состояние кнопки в состояние логической «1». Раз на нём всегда будет «1», то уже никакая помеха не сможет этот порт перевести в «0». А значит, наш порт станет невосприимчивым к помехам.

    Давайте подключим нашу кнопку по такой схеме:

    096ae400-849c-4ba5-bbf1-78517a531dd4-image.png

    Если вы использовали кнопку от системного блока с коннектором BLS, то достаточно его просто развернуть на 90 градусов. Теперь наша кнопка подключена к порту Р1-05 и к пину «GND» (земля, или 0 по русски).

    Ну и напишем такую простенькую программу button1.c

    // Проверка работоспособности кнопки, подключённой
    // к порту Р1_05 и GND. 
    // Компиляция: gcc -o button1 button1.c -lbcm2835 -lrt
    // Исполнение: sudo ./button1
    
    #include <stdio.h>
    #include <bcm2835.h>
    #define PIN_IN RPI_GPIO_P1_05  
    //#define PIN_IN RPI_V2_GPIO_P1_05  // Для RPI ревизии v2 раскомментировать эту строку и удалить предыдущую
    int main()
    {
      if (!bcm2835_init())   // Инициализация GPIO
      return 1;              //Завершение программы, если инициализация не удалась
       
    bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT); // Порт на ввод
    printf(&quot;Ждём нажатия на кнопку.\n&quot;);
    while(bcm2835_gpio_lev(PIN_IN))   // Повторяем все действия, заключённые в скобки {} пока не будет нажата кнопка
            {}
            printf("Кнопочка нажата!\n");
    
            return (bcm2835_close ());     // Выход из программы
    }
    

    Компилируем и запускаем. Убеждаемся, что наша кнопка, включённая таким образом, действительно работает. Можно после запуска программы повертеть провод перед Wi-Fi донглом и убедиться, что RPI никак на него не реагирует, а реагирует только на нажатую кнопку.

    А теперь разберёмся, как это работает.

    Если мы зайдём на страницу http://elinux.org/RPi_Low-level_peripherals то в таблице «Header Pinout,bottom row:» мы увидим такое описание портов:
    Screenshot from 2024-12-24 05-26-51.png
    Мы видим, что в описании порта Р1-05 написано «1K8 pull up resistor». Это значит, что в RPI вывод этого порта подключён к шине +3,3в через резистор сопротивлением 1,8кОм. Это называется подтяжкой. А раз этот порт подключён к +3,3в, то это значит, что в любом состоянии на входе этого порта будет присутствовать логическая «1». Если мы теперь с учётом этой информации перерисуем схему подключения нашей кнопки, то она у нас будет выглядеть вот так:

    89966b10-70c4-4e68-b618-3b622bb938bf-image.png

    Теперь мы видим, что если кнопка не нажата, то с порта мы будем считывать «1». А когда мы нажмём на кнопку, то мы замкнём вход порта с «землёй», а значит, получим на входе в порт логический «0». Т.е. нам теперь, чтобы отследить нажатие на кнопочку, нужно просто ждать, когда на порте появится «0». Собственно чем наша выше написанная программа и занимается.

    Цикл while(bcm2835_gpio_lev(PIN_IN)){} повторяется до тех пор, пока функция bcm2835_gpio_lev(PIN_IN) не вернёт в программу «0». Мы эту функцию уже использовали раньше в наших программах, только там был ещё оператор инвертирования результата, в виде восклицательного знака перед функцией, что означало, что мы ждём возвращения «1». Тут же мы ждём возвращения «0», по этому от инверсии мы избавились.

    При замкнутой кнопке у нас через резистор течёт небольшой ток I=U/R=3,3/1800=1,8мА. Ток этот очень маленький, что практически никак не скажется на потребляемой RPI мощности. Как видим, это очень удачный способ подключения кнопки к RPI. Мало того, что он очень помехоустойчивый (ведь помеха может навести в проводе ток, но вот избавить провод от тока она не в состоянии), так ещё мы освободили целый порт для любых других нужд. Одна кнопка- один порт.

    Но, разумеется, это совершенно не значит, что наша первая схема подключения кнопки оказалась никуда негодной. Например, подключить матричную клавиатуру можно лишь первым способом. Тогда например, для подключения клавиатуры 4х4 кнопки нам понадобится всего 8 портов. Если же мы попытаемся подключить такую клавиатуру по нашему сегодняшнему способу, то мы займём 16 портов GPIO. И тем не менее, для всех остальных случаев, нашу сегодняшнюю схему подключения можно считать оптимальной.

    Теперь мы знаем, что благодаря наличию подтяжки на порту Р1-05, мы можем легко использовать этот порт для подключения кнопки. Так же из таблицы следует, что подтяжкой обладает ещё и порт Р1-03. И мы можем точно так же подключить к нему ещё одну кнопочку. Возникает резонный вопрос: «А если я хочу подключить, скажем, 3 кнопочки, а портов с подтяжкой всего 2, как быть?». Ответ прост- можно выполнить подтяжку любого порта самостоятельно, впаяв дополнительный резистор на своей плате и соединив им любой другой порт GPIO с +3,3в. К стати, сопротивление этого резистора не обязано быть именно 1,8кОм. Схема будет прекрасно работать и с резистором в 10кОм. Значит нам можно поставить в цепь подтяжки порта любой резистор сопротивлением от 1,8 кОм и до 10-12кОм. Но не торопитесь бежать за паяльником. У RPI маленькие секреты ещё не закончились. Попробуем обойтись без самодельной подтяжки.

    Для начала предлагаю провести маленький эксперимент. Давайте подключим нашу кнопку к портам Р1-13 и Р1-14. Р1-13, это обычный порт GPIO, а Р1-14, это GND. Собственно у нас ничего не изменилось. Всё так же наша кнопка подключена между портом GPIO и GND, за исключением того, что этот порт не имеет резистора подтяжки. Изменим нашу программу button1.c заменив определение порта Р1-05 на Р1-13. Откомпилируем программу и запустим её. Мы видим, что она не работает должным образом. Программа считает, что кнопка нажата, хотя мы к ней и не притрагивались. А всё потому, что отсутствует подтяжка порта и на нём всё время находится «0». А по логике работы нашей программы, наличие «0» на порту говорит о нажатии кнопки.

    Пришло время вспомнить о том, что я говорил в первой части статей «GPIO для чайников». А говорил я о том, что внутри процессора BCM2835

    порты GPIO имеют возможность подключения внутренними резисторами подтяжки как к +3,3в, так и к 0 (или GND). Так почему бы нам не воспользоваться этим чудесным свойством в своих корыстных целях? Для воплощения нашей задумки нам достаточно лишь включить подтяжку порта Р1-13 к +3,3в. Тогда наша схема подключения кнопки автоматически превратится в такую:

    f287c1b1-3dcf-426e-840e-eb5ec7fff32d-image.png

    По сути, наша схема ничуть не изменилась. Только подтягивающий резистор переместился внутрь нашего процессора BCM2835.

    Для включения подтягивающего резистора существует функция

    void bcm2835_gpio_set_pud (uint8_tpin, uint8_t pud)
    

    где:
    pin - номер порта, к которому мы применяем данную функцию;
    pud - управляющая команда, включающая нужный нам режим подтяжки (0- подтяжка отключена, 1- подтяжка к GND, 2- подтяжка к +3,3в).

    Ну что ж, добавим эту функцию в нашу программу. Теперь она должна выглядеть так:

    // Проверка работоспособности кнопки, подключённой
    // к порту Р1_13 и GND. 
    // Компиляция: gcc -o button2 button2.c -lbcm2835 -lrt
    // Исполнение: sudo ./button2
    
    #include 'stdio.h'
    #include 'bcm2835.h'
    
    #define PIN_IN RPI_GPIO_P1_13  
    //#define PIN_IN RPI_V2_GPIO_P1_13  // Для RPI ревизии v2 раскомментировать эту строку и удалить предыдущую
    
    int main()
    {
            if (!bcm2835_init())   // Инициализация GPIO
            return 1;              //Завершение программы, если инициализация не удалась
    
            bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT); // Порт на ввод
    
            bcm2835_gpio_set_pud(PIN_IN, 2); // Включаем подтяжку порта к +3,3в
    
            printf(&quot;Ждём нажатия на кнопку.\n&quot;);
    
            while(bcm2835_gpio_lev(PIN_IN))   // Повторяем все действия, заключённые в скобки {} пока не будет нажата кнопка
            {}
    
            printf(&quot;Кнопочка нажата!\n&quot;);
    
            bcm2835_gpio_set_pud(PIN_IN, 0); // Отключаем подтяжку порта
    
            return (bcm2835_close ());     // Выход из программы
    
    }
    

    Сохраним нашу программу под именем button2.c , скомпилируем и запустим. Всё, теперь наша программа адекватно реагирует на нажатие кнопки.

    Ну вот, теперь вы знаете ещё полтора способа подключения кнопки к RPI.

    Настало время испытать наши знания в деле. На этот раз мы не будем играть с моргающими светодиодами. Наших знаний уже вполне достаточно, чтобы превратить RPI в самостоятельное, работающее, законченное устройство, которое сможет выполнять уже полезную работу. Предлагаю превратить наш RPI в интрнет-радио! Причём это «радио» должно быть автономным, т.е. обходиться без клавиатуры, мыши и монитора. А управлять мы им будем при помощи кнопочек, которые мы только что научились подключать к RPI.

    Некоторые пояснения.

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

    Так, на сегодняшний день доступна библиотека версии 1.16.

    Если вы установили себе библиотеку более новой версии, чем 1.8 (установка которой описана в первой статье "GPIO для чайников"), то у вас может возникнуть проблема с компиляцией написанных программ.

    Например при компиляции может вылезти сообщение:

    /usr/local/lib/libbcm2835.a(bcm2835.o): In function `bcm2835_delayMicroseconds':
    /home/pi/bcm2835-1.15/src/bcm2835.c:350: undefined reference to `clock_gettime'
    /home/pi/bcm2835-1.15/src/bcm2835.c:360: undefined reference to `clock_gettime'
    collect2: ld returned 1 exit status
    

    Чтобы обойти эту проблемму, достаточно при компиляции добавить опцию -l rt

    Т.е., если раньше мы компилировали нашу программу button командой:

    gcc -o button button.c -l bcm2835
    

    то теперь нужно писать так:

    gcc -o button button.c -lbcm2835 -lrt
    

    Просто автор библиотеки в более новых версиях модифицировал функцию bcm2835_delayMicroseconds, которая теперь учитывает скорость выполнения команд процессором и ей требуется обращение к real-time функциям.

    Если вы владелец Raspberry PI Version2:

    Для ревизии платы Raspberry PI v2 необходимо изменять определения портов GPIO.

    Например, если мы для ревизии v1 писали определение для порта Р1_03 так:

    #define PIN RPI_GPIO_P1_03
    

    то для ревизии v2 эта строка должна выглядеть вот так:

    #define PIN RPI_V2_GPIO_P1_03
    

    Т.е. для определения любых портов ревизии v2 нужно добавлять в запись "_V2" между "RPI" и "_GPIO"

    В прочем, это необходимо только тогда, когда вы используете в своих проектах порты с номерами: Р1-03, Р1-05 и Р1-13.

    Для других портов это не имеет никакого значения, какое определение вы используете, т.к. остальные порты GPIO совпадают в обеих ревизиях.

    Зато в ревизии v2 вывели дополнительно 4 новых порта на разъём Р5. Этих портов нет в ревизии v1 вобще. Обладатели же ревизии v2 могут использовать эти порты абсолютно так же, как и прочие. Номера этих портов: Р5-03, Р5-04, Р5-05 и Р5-06.

    Соответственно псевдонимами этих портов будут:

    RPI_V2_GPIO_P5_03
    RPI_V2_GPIO_P5_04  
    RPI_V2_GPIO_P5_05  
    RPI_V2_GPIO_P5_06
    

  • GPIO для чайников (часть 4)
    R RomanNV5

    -Почему в кинотеатрах так медленно гаснет свет?
    -Потому, что киномеханик очень медленно вынимает вилку из розетки.

    Знакомимся с широтно-импульсной модуляцией.

    Ранее мы научились с помощью изменения состояния порта GPIO управлять светодиодом. Мы научились управлять длительностью и частотой импульсов, благодаря чему получили различные световые эффекты. Убедились в том, что если изменять состояние порта со звуковой частотой, то можно получать различные звуки, освоили частотную модуляцию…

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

    Проведите эксперимент. Измените нашу программу blink.c так, чтобы светодиод загорался и гас 200 раз в секунду, с частотой 200 Гц. Для этого достаточно изменить параметры функции delay(). Чтобы узнать, какие задержки нужно ввести, достаточно рассчитать период колебания Т. Т=1/f. А т.к. f у нас равна 200Гц, то Т=1/200=0,005 секунды, или 5 миллисекунд. Вот за эти 5 миллисекунд мы должны успеть включить светодиод и выключить его 1 раз. Так, как 5 на 2 не делится нацело, примем время свечения светодиода в 2 мС, а время несвечения в 3мС. 2+3=5, т.е. полный период одного колебания так и останется 5мС. Теперь изменим программу: заменим delay(500), на delay(2) и delay(3) для горящего и не горящего светодиода соответственно.

    Скомпилируем программу и запустим. Если у вас всё ещё в схеме установлен динамик, то вы услышите низкий звук, а если динамик заменить светодиодом, то вы увидите непрерывно горящий светодиод. На самом деле светодиод конечно моргает, но делает он это на столько быстро, что глаз уже не замечает это моргание и воспринимает его как непрерывное свечение. Но светит диод вроде бы не так ярко, как он у нас горел раньше. Можете для сравнения запустить нашу самую первую программу, где светодиод горел постоянно, и сравнить яркость светодиода в обоих случаях. Давайте разберёмся, почему так происходит и как это можно использовать.

    Помните, в самой первой части мы рассчитывали токоограничивающий резистор для питания светодиода? Мы знаем, что у светодиода есть рабочий ток, при котором он светится наиболее ярко. Если этот ток уменьшать, то яркость светодиода будет тоже уменьшаться. А когда мы начинаем быстро включать и выключать светодиод, то его яркость свечения становится зависимой от среднего тока (Iср) за период колебания. Для импульсного (П-образного) сигнала, который мы генерируем на выходе порта GPIO, средний ток будет пропорционален отношению t1 к t2. А именно: Iср=Iн x t1/t2, где Iн- номинальный ток светодиода, который мы благодаря резистору установили в 10мА. При номинальном токе светодиод светится наиболее ярко. А в нашем случае Iср=10 х 2/3 = 6,7мА. Мы видим, что ток стал меньше, поэтому и светодиод стал гореть менее ярко. В этой формуле отношение t1/t2 называется коэффициентом заполнения импульса D.

    Чем этот коэффициент больше, тем больше будет среднее значение тока. Мы можем изменять этот коэффициент от 0 до 1, или от 0% до 100%. А значит, мы можем и менять средний ток в этих пределах. Получается, что таким способом мы можем регулировать яркость светодиода от максимальной, до полностью выключенного! И хотя напряжение на выводе нашего порта по-прежнему может быть лишь либо +3,3в, либо 0в, ток в нашей схеме может изменяться. И изменением этого тока мы легко можем управлять нашей Малинкой. Вот такой способ управления и называется Широтно-Импульсной модуляцией, или просто ШИМ. В английском языке это звучит как PWM, или Pulse-Width Modulation. ШИМ, это импульсный сигнал постоянной частоты с переменным коэффициентом заполнения. Используется и такое определение, как Импульсный сигнал постоянной частоты с переменной скважностью. Скважность S, это величина обратная коэффициенту заполнения и характеризует отношение периода импульса T к его длительности t1.

    S=T/t1 = 1/D.

    a83d5a0a-4c97-4112-a564-38288a13fc42-image.png

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

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

    //
    dimmer.c
    // Программа плавно изменяет яркость светодиода
    // Светодиод подключён к порту Р1_03#include #define PIN RPI_GPIO_P1_03
    int main()
    {    
    if (!bcm2835_init()) return 1;       
    
    bcm2835_gpio_fsel(PIN,BCM2835_GPIO_FSEL_OUTP);  
     //Устанавливаем порт Р1_03 на выводunsigned int t_on, t_off;
     // t_on продолжительность включённого состояния= t1, а t_off- выключенного =t2
    
    int d = 100, i, j, flag=0; // d- коэффициент заполнения в процентах, i и j, вспомогательные переменные для организации циклов, flag- если =0 светодиод затухает, если =1 разгорается
    
    int a=10; // количество полных рабочих циклов 
    while (a)
    {
        for (j=100; j!=0; j--) //изменяем коэффициент заполнения от 100% до 0%
        {
            t_on=50*d; //находим t1
            t_off=50*(100-d);  //находим t2
            if (flag==0) d=d-1; // если светодиод затухает, уменьшаем коэффициент заполнения
            if (flag==1) d=d+1; // если светодиод разгорается, увеличиваем коэффициент заполнения
    
            for (i=10; i!=0; i--) //передаём 10 импульсов на светодиод с рассчитанными параметрами t1 и t2        
             {             
                bcm2835_gpio_write(PIN, LOW);              
                delayMicroseconds(t_on);              
                bcm2835_gpio_write(PIN, HIGH);              
                delayMicroseconds(t_off);         
             }
    
        if (d==0) flag=1; // если светодиод выключен, начинаем его включать
        if (d==100) flag=0; // если светодиод достиг максимума свечения, начинаем его гасить 
        }
    
    a--;
    }
    return (!bcm2835_close ()); // Выход из программы
    }
    

    Сохраняем программу под именем dimmer.c, компилируем и запускаем.

    Как видите, теперь наш светодиод медленно гаснет и медленно разгорается. Вот так и работает ШИМ. Широтно-импульсная модуляция используется во многих областях. Это и управление яркостью свечения ламп и светодиодов, управление сервоприводами, регулирование напряжения в импульсных источниках питания (которые например, стоят в вашем компьютере), в цифро-аналоговых и аналого-цифровых преобразователях и т.д. К стати, если вернуться к нашей схеме с динамиком, то при помощи ШИМ можно управлять громкостью сигнала, а изменяя частоту - его тоном.

    Помните старый анекдот из предисловия к этой части, о киномеханике, медленно вытягивающим вилку из розетки? Теперь то мы знаем, что этому киномеханику, чтобы плавно погасить свет, нужно наоборот очень быстро вставлять и вытаскивать вилку из розетки.

    На этом мы и закончим данный урок. Остаётся лишь добавить, что ШИМ настолько часто используется в различных приложениях, что производители процессорного оборудования часто встраивают ШИМ-контроллер непосредственно в процессор. Т.е. вы процессору задаёте параметры требуемого вам сигнала, а процессор уже сам, без вашей помощи выдаёт нужный вам сигнал. При этом, нисколько не тратя программных ресурсов на генерацию этого сигнала. Bcm2835 тоже имеет встроенный аппаратный ШИМ. И этот ШИМ является альтернативной функцией порта GPIO 18, или P1-12. Чтобы воспользоваться аппаратными ШИМ мы должны установить порт P1-12 в режим ALT5 и задать процессору параметры.

    Но это уже совсем другая история…


  • GPIO для чайников (часть 3)
    R RomanNV5

    Отдаём команды компьютеру. Играем музыку. Разбираемся в частотной модуляции цифровым сигналом.

    Мы научились передавать на порт GPIO последовательность единиц и нулей, другими словами мы передавали последовательность бит. Эта последовательность бит управляла включением светодиода. Казалось бы, если любой бит может иметь только два состояния- «1» и «0», то об аналоговом сигнале с цифрового выхода не может быть и речи. Теоретически это так, практически это тоже так, но фактически… Не смотря на то, что уровень сигнала на выходе порта мы не можем изменять так, как нам хочется, в некоторых случаях мы цифровым сигналом можем имитировать аналоговый. Сегодняшняя часть цикла статей «GPIO для чайников» будет подготовительной для понимания этого процесса.В программе мигающего светодиода мы последовательно включали светодиод на 500 миллисекунд и на 500 миллисекунд его выключали. Давайте посмотрим, что получится, если мы будем его включать на 200 миллисекунд, а выключать на 800 миллисекунд. Можете самостоятельно отредактировать созданную ранее программу button.c изменив временные промежутки в функции bcm2835_delay(). А можете набить предлагаемую мной программку, снабжённую простейшим интерфейсом общения с пользователем.

    // count.c
    
    // Программа включает светодиод требуемое количество раз.
    // Светодиод подключен к порту Р1_03
    // Компиляция: gcc -o count count.c -lrt -lbcm283
    // Запуск: sudo ./count
    
    #include <stdio.h> 
    #include  <bcm2835.h>
    
    #define PIN RPI_GPIO_P1_03
    
    int main()
    {
        if (!bcm2835_init())
           return 1;
     
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);
        printf("Сколько раз моргнуть?\n");
        int i;
        scanf("%d",&i);
     
        while (i)
        {
            bcm2835_gpio_write(PIN, LOW);
            bcm2835_delay(200);
     
            bcm2835_gpio_write(PIN, HIGH);
            bcm2835_delay(800);
            i--;
        }
        return (!bcm2835_close ());
    }
    

    Сохраняем её под именем count.c и компилируем.

    Далее я не буду расписывать процесс сохранения, компиляции и запуска программ. Надеюсь, вы уже запомнили, как это делается?

    Введённая нами программа задаёт нам вопрос: «Сколько раз моргнуть?». Дождавшись ответа (нужно ввести любое целое число и нажать Enter), светодиод мигает требуемое количество раз и программа завершается. Т.е. наша с вами программа уже не просто тихонько выполняет последовательность действий, а общается с нами и исполняет наши требования. Но главное в этой программе не это. Мы здесь изменили интервалы включения и выключения светодиода. И как видите, это вылилось в некий визуальный эффект. Теперь создаётся впечатление, что светодиод вспыхивает, а не мигает. Вот так вот, простым изменением временных задержек мы получили новый визуальный эффект. Попробуйте самостоятельно поиграть с временными задержками и посмотрите, к чему это приведёт. Попробуйте задать время включения большее, чем продолжительность в выключенном состоянии. Следите за тем, чтобы общая сумма задержки была в 1000 миллисекунд. Чтобы было удобнее наблюдать результат изменения в поведении светодиода.

    Коротко о новом в этой программе.

    #include <stdio.h> - мы подключили новую библиотеку. В этой библиотеке содержатся базовые функции ввода-вывода. Такие, как вывод на монитор и ввод с клавиатуры. Эта библиотека является комплектной библиотекой компилятора, поэтому нет необходимости при компиляции задавать компилятору опцию –lstdio. Компилятор и без нас разберётся, что её нужно подключить и где её искать.

    printf("Сколько раз моргнуть?\n"); - Данная функция просто выводит на экран монитора надпись, заключённую в кавычки. \n – это служебный символ перевода строки.

    scanf("%d",&i); - данная функция ожидает ввода с клавиатуры. Когда ввод осуществлён, она помещает полученное значение в адресное пространство, выделенное для ранее объявленной переменной i. После выполнения этой функции у нас переменная i будет равна введённому числу. В кавычках мы задаём формат вводимого значения. В данном случае %d говорит о том, что мы ожидаем ввода десятичного числа.

    Далее мы запускаем цикл с миганием до тех пор, пока переменная i не станет равна 0. Для этого мы после каждого моргания уменьшаем значение i на единицу функцией декремента i--. Эта функция эквивалентна записи i=i-1. Т.е. переменной i присваивается значение, уменьшенное на единицу.

    Ну а остальное в этой программе вам уже знакомо.

    Следующий параметр, который мы можем изменить в передаваемых через порт битах, это общая скорость передачи. Предлагаю вам самостоятельно поэкспериментировать с этим параметром. Для этого достаточно уменьшить, или увеличить временные интервалы для включения и выключения светодиода. Например, поставьте временные задержки в 250 миллисекунд и посмотрите, что получится. Светодиод станет мигать в 2 раза быстрее. Как это можно применить? Как вы думаете, что получится, если отправлять последовательность единиц и нулей 200 раз в секунду? Ответ очевиден- мы уже не заметим, что светодиод моргает. Но если мы чего-то не видим, мы это можем услышать! Предлагаю на время заменить наш светодиод динамиком. Можно вместо него подключить наушники, или компьютерные колонки. Кроме этого крайне желательно заменить и резистор, на более высокоомный, например сопротивлением в 1кОм. Это делается потому, что динамик является индуктивной нагрузкой, и в момент переключения порта с «1» в «0» он может сгенерировать бросок тока, способный повредить процессор в Raspberry. Поэтому мы должны защититься от подобной ситуации резистором с большим сопротивлением.

    У нас должна получиться вот такая схема:

    e6b6477c-e272-47f5-bb37-ade9195757c8-image.png

    Классический звуковой сигнал представляет из себя механические гармонические (синусоидальные) колебания звуковой частоты f. Частоты звучания нот можно легко найти в интернете. Например нота До первой октавы имеет частоту 261,63Гц. Нота До следующей октавы будет иметь частоту в 2 раза большую. Давайте попробуем проиграть гамму от ноты До 3-й октавы, до ноты До 2-й октавы.

    При гармонических колебаниях диффузор в динамике отклоняется взад и вперёд с частотой, равной частоте сигнала. А раз мы можем изменять частоту передачи нулей и единиц через порт, значит мы так же сможем раскачивать диффузор с нужной нам частотой и извлекать звук. Вот так это будет выглядеть:

    37bac572-a80d-4783-aeab-490281878971-image.png

    На первом графике представлен вид синусоидального гармонического колебания, а на втором графике мы пытаемся его сымитировать цифровым сигналом.

    Значит, нам нужно для каждой ноты рассчитать временные интервалы t1 и t2, равные периоду колебания T делённому на 2. А период Т равен 1 делённая на частоту f. В интернете находим, что частота ноты До 3-й октавы равна 1046,5Гц. Значит период колебания равен Т=1/f=1/1046,5=0,000956 секунды, или 956 микросекунды. Получаем: t1=t2=Т/2=956/2=478 мкС. Теперь нам нужно найти периоды t1 и t2 для всех остальных нот до ноты До 2-й октавы. Я их посчитал на калькуляторе, но разумеется, ничто не мешает нам возложить этот расчёт на процессор компьютера. При расчёте округляем результат до целого значения. Теперь напишем программку, которая все наши ноты проиграет. У меня получилось вот такая:

    // music.c
    // Играем гамму
    // Динамик подключён к порту Р1_03 через резистор 1кОм
    // Компиляция: gcc -o music music.c -lrt -lbcm2835
    // Запуск: sudo ./music
    
    #include  <bcm2835.h>
    #define PIN RPI_GPIO_P1_03
    
    int main()
    {
        unsigned int tone[8] = {956, 851, 758, 716, 638, 568, 506, 478}; // Создаём массив с рассчитанными периодами для каждой ноты
    
        if (!bcm2835_init())    // Инициализация GPIO
             return 1;                //Завершение программы, если инициализация не удалась
    
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);     //Устанавливаем порт Р1_03 на вывод
    
        int i=8, j;             // Объявляем переменные. i- количество нот, j- продолжительность звучания ноты. 
    
        while (i)  // Играем ноты пока i не равно 0
        {
             bcm2835_delay(200);      // Просто пауза перед проигрыванием каждой ноты
    
             for (j=600; j!=0; j--)   // 600 раз повторяем колебания для каждой ноты (примерно 1 секунда звучания)
             {
                   bcm2835_gpio_write(PIN, LOW);
                   delayMicroseconds(tone[i-1]);
                   bcm2835_gpio_write(PIN, HIGH);
                   delayMicroseconds(tone[i-1]);
             }
             i--;
        }
        return (!bcm2835_close ());  // Выход из программы
    }
    

    Сохраняем, компилируем и запускаем. Слушаем нашу музыку, исполненную всего лишь 1 портом GPIO.

    Вот так, изменяя частоту передачи единиц и нулей через порт, мы получили возможность воспроизводить звук и освоили принцип частотной модуляции, а также научились этой модуляцией пользоваться . Да-да, именно это и называется Frequency modulation. Теперь, знакомые с языком Си, могут попробовать написать программу простенького пианино, играющую ноты в зависимости от нажатой клавиши на клавиатуре. Или попробовать переложить какую-нибудь мелодию на нашу программу. Можно попробовать создавать простенькие звуковые эффекты. Кстати, именно таким вот способом выводили звук первые игровые компьютеры, приставки, сотовые телефоны. Причём добивались даже имитации четырёхголосой полифонии, и даже воспроизведения человеческой речи! Но для этого нужно не только хорошо знать язык программирования, но и уверенно владеть высшей математикой, чтобы знать, как складывать гармонические сигналы и перенести это на дискретные колебания.

    В нашей программе появилась новая функция bcm2835_delayMicroseconds(). Эта функция аналогична ранее использованной нами функции bcm2835_delay(), за исключением того, что выполняет задержку в микросекундах. Эта функция тоже из библиотеки bcm2835. Ну а со всей программой в целом, думаю вы разберётесь без проблем, если уже начали изучать Си.

    На сегодня это всё. В следующей части мы разберёмся в очень важной и часто используемой Широтно-Импульсной Модуляции. И поможет нам в этом наш старый знакомый- светодиод.


  • GPIO для чайников (Часть 2)
    R RomanNV5

    В этой части мы научимся читать из порта GPIO.

    В первой части мы научились включать и выключать наш светодиод. Это самое элементарное и простое, на что способен порт GPIO. Но даже это самое простое уже можно использовать в своих целях. За примером далеко ходить ненужно. Например, на плате Raspberry светодиоды управляются точно так же, через подключение к GPIO. Просто на разъём GPIO выведены не все доступные порты. Часть этих портов используется самим Raspbery для своих нужд. Моргает светодиодами, общается с SD-картой, управляет адаптером Ethernet и т.д.

    Пойдём дальше. Что получится, если мы будем повторять последовательность включения и выключения? Давайте попробуем! Слегка подправим нашу написанную программу. Откроем, написанную нами ранее программу:

    nano GPIO-test.c
    

    Сохраним её под новым именем:<>ctrl+o
    и введём blink.c

    Теперь отредактируем её до следующего вида (или просто удалим всё написанное ранее и вставим текст отсюда):

    // blink.c
    // Программа мигает светодиодом 1 раз в секунду.
    // Светодиод подключён к порту Р1_03
    // Компиляция: gcc -o blink blink.c -lrt -lbcm2835
    // Запуск: sudo ./blink
    
    #include   <bcm2835.h>
    #define PIN RPI_GPIO_P1_03 
    //#define PIN RPI_V2_GPIO_P1_03  //для плат RPi rev. V2
    int main() {
        if (!bcm2835_init())         // Инициализация GPIO
            return 1;                //Завершение программы, если инициализация не удалась
    
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);          //Устанавливаем порт Р1_03 на вывод
    
        while(1)   // Повторяем все действия, заключённые в скобки {} бесконечное число раз
        {
            bcm2835_gpio_write(PIN, LOW);   // Устанавливаем порт в 0, светодиод горит
            bcm2835_delay(500);             // Ждём 500 милисекунд
            bcm2835_gpio_write(PIN, HIGH);  // Устанавливаем порт в 1, светодиод не горит
            bcm2835_delay(500);             // Ждём 500 милисекунд
        }
        return 0;     // Выход из программы
    }
    

    Проверяем на наличие ошибок и сохраняемся: Ctrl+o, Ctrl+x

    Компилируем:

    gcc -o blink blink.c -lrt -lbcm2835
    

    И запускаем:

    sudo ./blink
    

    Ну вот, теперь наш светодиод мигает. В эту программу мы внесли следующие изменения:

    • Во-первых, изменили временные промежутки включённого и выключенного состояния. Теперь наш светодиод 500 миллисекунд горит и 500 миллисекунд не горит. Т.е. на всё уходит ровно 1 секунда.
    • Во-вторых, мы заключили функции включения и выключения светодиода в бесконечный цикл. Оператор while(1) будет выполнять всё, что заключено в следующую за while пару скобок {} до тех пор, пока параметром while будет 1. А раз мы эту единичку туда прописали, то она там будет всегда, а следовательно и команды включения-выключения будут тоже выполняться бесконечно.

    Посмотрели, порадовались своим успехам… А светодиод всё ещё мигает. И будет мигать до тех пор, пока не выдернете источник питания из розетки. А ещё можно прекратить выполнение программы «варварским» способом- нажать Ctrl+c. Только если нажать неудачно, то программа завершится, а светодиод продолжит гореть. Непорядок. Нужно это дело исправлять.

    Ставим перед собой задачу:

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

    Т.е. после выхода из программы светодиод однозначно бы гас.

    Приступим.
    Для того, чтобы завершить выполнение программы в любое время, мы подключим к нашему Raspberry кнопочку. Пока она не нажата, светодиод будет мигать. Когда нам надоест смотреть на мигающий светодиод, мы нажимаем на кнопочку и наша программа завершается с выключенным светодиодом. Чтобы это реализовать, нам нужно выделить для кнопочки ещё один порт GPIO. Причём этот порт нужно настроить не на Вывод, а на Ввод (или другими словами не на запись, а на чтение). Этот порт будет следить за состоянием подключённой к нему кнопки, и как только на нём появится напряжение (уровень HIGH), наша программа должна завершиться. Выберем порт для этой цели. Пусть будет Р1_07.

    Так же нам нужен источник сигнала для кнопки, за состояние которой мы будем следить. На этом источнике должна постоянно находиться логическая единица. Можно в качестве такого источника использовать присутствующие на Р1_01 +3,3в. Но к нему неудобно подключаться, он уже использован для питания светодиода. Мы поступим проще. Помните, я в первой части говорил, что процессор bcm2835 умеет подключать свои порты к 0, либо к +3,3в? Так вот, если мы подключим порт процессора к +3,3В посредством установки этого порта в высокое состояние HIGH, то этот порт и можно будет использовать в качестве источника сигнала "1" для кнопки. Выделим под это дело порт P1_05, который находится по соседству с Р1_07. В результате у нас должна получиться вот такая схема:
    Описание
    Самые внимательные могут вспомнить, что в предыдущей первой части я говорил, что мы должны всегда следить за тем, чтобы не перегрузить порт по току, и должны устанавливать токоограничивающие резисторы. А в данном случае кнопка подключена безо всего. А так, как кнопка практически не имеет никакого сопротивления, то замкнув кнопкой +3,3В с 0 мы получим короткое замыкание и сожжём нашу Малинку… Но этого не произойдёт. И вот почему. Во-первых, разработчики процессора решили снять часть ответственности с нас и сделали так, что процессор подключает свои порты к +3,3в и 0 через микроскопические резисторы, установленные в самом процессоре. Поэтому короткого замыкания не произойдёт. Ток потечёт через внутренние резисторы. Во-вторых, так же в первой части я говорил, что процессор может вообще никуда не подключать порт. Так вот. Если мы настраиваем порт на Ввод (чтение), то это как раз тот самый случай. Процессор будет читать данные с этого порта, поэтому ему нет необходимости подключать его к источнику питания, или 0. В этом режиме порт имеет достаточно высокое сопротивление. Настолько высокое, что мы в принципе можем безбоязненно подать на него прямиком 3,3 вольта от источника питания, и с ним ничего не случится. Но всё равно, если вы подключаете кнопку напрямую от источника питания, то лучше подстраховаться от всяческих бросков напряжения и поставить небольшой токоограничивающий резистор. Например тот же в 330Ом (не менее 100Ом). На будущее запомним следующие правила:

    • Мы можем спокойно подключать напрямую (без резисторов) к портам GPIO любые входы и выходы микросхем, работающих от того же источника питания, что и наш Raspberry, и имеющих 3-вольтовую логику. Под это же правило попадает и наше подключение кнопки между 2 портами процессора;
    • Если подключаемое устройство имеет 3-вольтовую логику, но работает от другого источника питания, то нужно подстраховаться и подключать это устройство через ограничительные резисторы сопротивлением 100-330 Ом.
    • Если подключаемое устройство работает на 5-вольтовой логике, то мы обязательно должны делать согласование уровней. На GPIO Raspberry нельзя подключать напряжение выше 3,3В! По этому о непосредственном подключении не может быть и речи!

    Ну и информация для общего развития. Даже в режиме чтения порт может быть подключён к +3,3В, или 0 через внутренний резистор. Это называется подтяжкой.

    Теперь давайте соберём схему. В принципе у нас она собрана, осталось только подключить к портам Р1_05 и Р1_07 кнопку. Кнопку можно взять абсолютно любую, хоть выключатель от электролампочки. Я взял кнопку Reset из старого системного блока:
    d1061a65-91d3-49b8-8e12-1b74f8ff31e3-image.png
    В крайнем случае, можно просто использовать 2 проводочка из разъёма и просто замыкать их между собой.Подключаем всё к RPi таким образом:
    561b519e-019c-4c43-9114-ac7921b5c838-image.png
    Откроем нашу программу blink.c и добавим в неё несколько строк. В результате она должна будет выглядеть вот так:

    // button.c
    // Программа мигает светодиодом 1 раз в секунду
    // и завершается при нажатии на кнопку.
    // Светодиод подключён к порту Р1_03
    // Кнопка подключена к портам Р1_05 и Р1_07
    // Компиляция: gcc -o button button.c -lrt -lbcm2835
    // Запуск: sudo ./button
    
    #include  <bcm2835.h>
    
    #define PIN RPI_GPIO_P1_03       // Определяем порт для подключения светодиода
    #define PIN_OUT RPI_GPIO_P1_05   // Определяем порт для записи
    #define PIN_IN RPI_GPIO_P1_07    // Определяем порт для чтения
    
    int main()
    {
        if (!bcm2835_init())         // Инициализация GPIO
            return 1;                // Завершение программы, если инициализация не удалась
    
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);          // Устанавливаем порт PIN на вывод
        bcm2835_gpio_fsel(PIN_OUT, BCM2835_GPIO_FSEL_OUTP);      // Устанавливаем порт PIN_OUT на вывод
        bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT);       // Устанавливаем порт PIN_IN на ввод
        bcm2835_gpio_pud(PIN_IN, 1);                             // Включаем подтяжку порта PIN_IN к "0"
    
        bcm2835_gpio_write(PIN_OUT, HIGH);                       // Устанавливаем порт PIN_OUT в состояние "1"  
    
        while(!bcm2835_gpio_lev(PIN_IN))    // Повторяем все действия, заключённые в скобки {} пока не будет нажата кнопка
        {
            bcm2835_gpio_write(PIN, LOW);   // Устанавливаем порт в 0, светодиод горит
            bcm2835_delay(500);             // Ждём 500 милисекунд
            bcm2835_gpio_write(PIN, HIGH);  // Устанавливаем порт в 1, светодиод не горит
            bcm2835_delay(500);             // Ждём 500 милисекунд
        }
    
        bcm2835_gpio_pud(PIN_IN, 0);   // Отключаем подтяжку порта PIN_IN к "0"
        return (bcm2835_close ());     // Выход из программы
    }
    

    роверяем программу на ошибки и сохраняем под новым именем:

    Ctrl+o button.c Ctrl+x
    

    Компилируем и запускаем:

    gcc -o button button.c -lrt -lbcm283
    sudo ./button
    

    Светодиод начинает мигать, как и раньше. Теперь нажмём нашу кнопку (нажатие должно быть продолжительным).

    Программа должна завершиться при выключенном светодиоде. Сейчас разберёмся в том, что мы добавили в программу и как оно работает.

    Первое, что мы сделали, это закрепили свои названия за выбранными портами.

    #define PIN_OUT RPI_GPIO_P1_05
    #define PIN_IN RPI_GPIO_P1_07
    

    Порт Р1_05, который служит источником питания для нашей кнопки, мы обозвали PIN_OUT.\nПорт Р1_07, который следит за состоянием нашей кнопки, мы обозвали PIN_IN.

    Не забываем, что для платы RPi версии rev.V2, необходимо добавить в определение портов V2. Раньше я помещал эти строки в код закомментированными, здесь и далее я этого делать больше не буду, следите за этим пожалуйста сами.

    Следующее изменение касается установки новых портов в нужные нам режимы.

    bcm2835_gpio_fsel(PIN_OUT, BCM2835_GPIO_FSEL_OUTP);
    bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT);
    

    Т.е. установили PIN_OUT в режим Вывода (он нам даёт +3,3В для кнопки), а PIN_IN установили в режим Ввода (он будет следить за состоянием кнопки).

    bcm2835_gpio_pud(PIN_IN, 1);
    

    Этой строкой мы подключили порт PIN_IN через внутренний резистор процессора к "0" (или GND), применили подтяжку порта к 0. Это позволит повысить помехоустойчивость опроса состояния кнопки. В более ранней версии этой статьи этого сделано небыло. И хотя всё работало, но при подключении RPi к сети по Wi-Fi возникали ошибки чтения из-за того, что Wi-Fi донгл наводил в проводниках кнопки потенциал, достаточный для распознавания на порте PIN_IN логической единицы, хотя кнопка была ещё не нажата. В 5-й части цикла статей я ещё коснусь этого вопроса.

    Далее, мы установили порт PIN_OUT в высокое состояние:

    bcm2835_gpio_write(PIN_OUT, HIGH);
    

    Теперь на выходе порта Р1_05 у нас присутствует логическая единица в виде напряжения в +3,3В.

    И ещё мы изменили содержимое функции while()

    while(!bcm2835_gpio_lev(PIN_IN)) ;
    

    Мы поместили в условие выполнения цикла функцию чтения состояния порта PIN_IN.

    Функция bcm2835_gpio_lev() возвращает значение состояния выбранного порта, т.е. читает его состояние. В нашем случае она читает состояние порта PIN_IN, который в соответствии с нашим определением является портом Р1_07. Если на ножке этого порта присутствует логическая единица, или по другому +3,3В, то эта функция возвращает 1 (помните, в нашей предыдущей программе какраз на этом месте и стояла 1?). А если на ножке порта логический ноль (или напряжение 0В), то эта функция и возвращает 0. Восклицательный знак перед этой функцией обозначает инверсию результата. В булевой математике он соответствует функции «НЕ». Т.е. !1=0, а !0=1. Другими словами изменяет результат на противоположный. Вот и получается, что наш светодиод будет мигать до тех пор, пока результатом чтения состояния порта PIN_IN будет НЕ единица, а собственно Ноль. А НЕ единица будет до тех пор, пока мы не нажмём кнопочку. Как мы только нажмём на кнопку, то логическая единичка с порта PIN_OUT придёт на порт PIN_IN и функция чтения состояния этого порт возвратит единичку. Цикл прервётся и программа завершится. А так, как последним состоянием светодиода в этом цикле было «выключено», то программа завершится именно с выключенным светодиодом. Так же стоит отметить, что чтение порта PIN_IN в нашей программе выполняется 1 раз в секунду. Именно поэтому нажатие кнопки должно быть продолжительным, чтобы гарантировано уложиться в эту 1 секунду и попасть на чтение порта. Разумеется, есть масса способов обойти это неудобство, но пока мы не будем заострять на этом внимание, чтобы не усложнять программу и разобраться в сути чтения информации с порта.

    Ну и появилась ещё одна новая функция, которой мы ранее не пользовались.

    bcm2835_close ()
    

    Эта функция закрывает библиотеку, освобождает выделенную под использование GPIO память и приводит состояние портов GPIO к исходному.

    Другими словами корректно завершает работу с портами.

    Мы её поместили в return потому, что эта функция возвращает значение 1, если всё прошло успешно, и 0, если возникли какие-то проблемы.

    Этой функцией нужно пользоваться всегда при завершении работы с портами. Это, как правило хорошего тона - уходя говорить: «до свидания». Так что эту функцию можно смело ставить и во все ранее написанные нами программы.

    Ну вот, сегодня мы научились читать из порта. И хотя мы читали состояние лишь одной кнопки, но именно подобным образом можно прочитать и последовательность бит от любого другого устройства, подключённого к этому порту и передающего ему какую либо информацию. Но самое главное- это принцип. И мы в нём сегодня разобрались.

    Но на этом мы пока не будем разбирать нашу схему со светодиодом. Он нам поможет разобраться ещё в нескольких интересных и нужных вещах. И об этом речь пойдёт в следующих частях серии «GPIO для чайников».

    А пока продолжайте самостоятельно изучать язык программирования Си. Ну и задание для самостоятельного выполнения- разработайте программу, которая бы включала светодиод при нажатой кнопке и выключала при отпущенной.


  • GPIO для чайников (часть 1)
    R RomanNV5

    Что делать, когда нечего делать? Попробовать что-нибудь новое!

    Если вы приобрели Raspberry Pi просто ради любопытства, не отдавая себе отчёта в том, для чего он конкретно вам нужен, то наверняка с каждым днём вам становится всё труднее найти для него применение. Вы уже с ним вдоволь наигрались. Попробовали установку разных операционных систем, послушали музыку, посмотрели видео, попробовали поиграть и порисовать… И наверняка с огорчением для себя сделали вывод - «Всё ж таки Raspberry Pi мало годится для использования в качестве настольного компьютера». Слишком он уж медленный и задумчивый, по сравнению с обычным компьютером. И вроде бы ничего серьезного с ним сделать нельзя. Остаётся лишь найти ему применение в качестве либо медиацентра, либо простенького интернет-сервера, который не страшно оставлять включённым круглые сутки…

    Но всё ж таки Raspberry Pi может делать одну вещь гораздо более эффективнее, чем любой домашний компьютер- он может управлять внешними устройствами. Устройства могут быть абсолютно любыми, от обычной лампочки, до беспилотного летательного аппарата. В данном случае, область применения Raspberry ограничена лишь вашей фантазией и знаниями. И если вы никогда и ничего подобного не делали, но это вас заинтересовало, то эта статья для вас. И так, начнём.

    Чтобы общаться с любыми внешними устройствами и управлять ими, Raspberry Pi имеет на борту интерфейс, называемый GPIO. Это аббревиатура от General Purpose Input Output. А по-русски, это низкоуровневый интерфейс ввода-вывода прямого управления. На плате Raspberry он находится в углу, в виде гребёнки из 26 штырьков, рядом с видеовыходом. Т.е. через этот интерфейс Raspberry может слушать и отдавать команды любому внешнему устройству, например беспилотнику. Но сегодня мы беспилотник строить не будем, начнём с обычной лампочки, а точнее светодиода, который и исполнит роль подопытной лампочки. Наша задача - заставить светодиод, подключённый к Raspberry включаться и выключаться по его команде. Кроме того, дабы убедиться, что эти включения происходят вполне осознано и так, как мы этого хотим, а не благодаря каким-то глюкам в недрах процессора, мы привнесём в нашу программу элемент общения с нами. Т.е. отстроим чёткую иерархию- Raspberry управляет светодиодом, а самим Raspberry управляем мы. Теперь надо подготовиться и раздобыть где-то несколько вещей.

    Во-первых, нужно найти светодиод:
    Описание
    Его можно достать из старой сломанной игрушки, из зажигалки с фонариком, попросить у знакомого радиоэлектронщика, в конце концов, просто купить.

    Во-вторых, понадобятся проводочки любые и парочка коннекторов BLS:

    3dd06136-9e54-4354-8b7e-6e3643ebb6b2-image.png

    Такие коннекторы можно вытащить из старого системного блока вместе с проводами, или попросить у знакомого компьютерщика, или тоже купить. Они прекрасно подходят для подключения к разъёму на Raspberry.

    Начнём с планирования используемых портов. Порт- это грубо говоря штырёк на разъёме. Так, как штырьков там много (26), то и портов тоже много. А чтобы в них не запутаться, то каждому порту присвоен свой номер и обозначение. Следует заметить, что не все штырьки в этом разъёме являются портами. Некоторые штырьки подключены к источникам напряжения, а некоторые вообще никуда не подключены (По секрету, на самом деле они всё-же подключены, но ими пользоваться нельзя, можно убить свою Малинку. Поэтому лучше вобще их не трогайте).

    Вот собственно как эти порты расположены на плате:

    8e6708cd-e96f-4798-abf6-1bf39390f2d0-image.png

    Чтобы светодиод зажёгся, нам нужно его подключить к источнику питания. Выбираем для питания светодиода Р1-01, верхний по рисунку штырёк, на котором присутствуетнапряжение 3,3в. Для управления светодиодом нам понадобится один порт GPIO. Можно выбрать любой. Но если у вас есть разъём BLS, то удобнее в данном случае использовать порт, который выведен на штырёк P1-03 и называется GPIO 0. В таком случае мы, воспользовавшись одним разъёмом, сможем подключить наш светодиод. И так, мы будем подключать светодиод между ножками разъёма P1-01 и Р1-03. С вывода Р1-01 мы берём +3,3в для питания светодиода, а вывод Р1-03 будет тем самым управляющим выводом порта GPIO. Все эти порты физически находятся внутри центрального процессора Raspberry Pi, который называется BCM2835. Этот процессор может подключать любой порт к источнику напряжения 3,3в, а может подключить порт к 0 питания (а может вообще никуда не подключать, но об этом позже). Эти переключения он делает в соответствии с поданной командой. Значит, когда порт будет подключён к напряжению +3,3в, наш светодиод гореть не будет, т.к. току некуда идти. А когда процессор подключит порт к 0, то наш светодиод загорится, т.к. ток побежит от +3,3в к 0 через светодиод. Значит наша программа должна будет отдавать соответствующие команды процессору в соответствии с нашим желанием.

    Маленькое, но важное. На самом деле, мы не должны подключать светодиод напрямую между источником питания +3,3в и выводом порта. Это нельзя делать по двум причинам. Причина первая: любой светодиод нормально работает при определённом токе. Если через светодиод потечёт большой ток (а выход +3,3в способен отдать до 50мА), то светодиод сгорит. Если маленький ток, то светодиод будет гореть слишком слабо, либо вообще не будет светиться. Для большинства обычных светодиодов рабочий ток находится в пределах 10-20мА. Отсюда вытекает и вторая причина (хотя в данном случае она несущественна). Если мы пропустим большой ток через порт GPIO, то этим самым мы уничтожим процессор и Raspberry- умрёт. Поэтому, мы должны следить, чтобы через порт не протекал ток больше допустимого. Примем для себя ограничение в 16мА, так мы точно не сожжем процессор. Как этого добиться? Очень просто! Нам нужно последовательно со светодиодомвключить токоограничивающий резистор. И сейчас мы его рассчитаем.

    Примем для светодиода рабочий ток в 10мА. Убеждаемся в том, что выбранный нами ток не превышает предельно допустимый ток для порта в 16мА. Теперь зная напряжение питания 3,3в и рабочий ток 10мА, мы можем по закону Ома рассчитать необходимое нам сопротивление. R=U/I=3,3/0,01=330Ом. Значит нам нужно найти резистор с сопротивлением 330Ом. А точнее- сопротивлением не менее 330Ом. Больше- можно. Светодиод будет заметно светиться и при сопротивлении 1000 Ом, или 1кОм. В общем наша задача- найти резистор с сопротивлением от 330 Ом до 1кОм. Если вы его нашли, то можно собрать вот такую схему:

    444ab967-0a7a-4e93-9e53-9a236407a8ca-image.png

    Схему лучше собрать на макетной плате. Лично мне, для экспериментов, мой сын дал на прокат свой конструктор «Знаток».

    Так выглядит схема в сборе:
    ea82f975-6f7f-492c-aa4a-1d5d14455871-image.png

    Так мы подключаемся к Raspberry:

    510a47c7-8497-4281-add2-e5d0cdf735dc-image.png

    А вот общий план всей конструкции:

    8c3e0fb8-0692-41d2-a2a2-b328431683f9-image.png

    В крайнем случае, можно просто скрутить выводы элементов. Но в этом случае нужно следить за тем, чтобы оголённые ножки элементов случайно не попали на контактные площадки Raspberry. Это может убить его. Так же стоит обратить внимание на то, что светодиод должен подключаться Анодом к + источника питания, т.е. в нашем случае это Р1-01. Как найти на светодиоде Анод? Очень просто! Достаньте из любого ДУ батарейку на 1,5В и подключите к ней ваш светодиод. Если он не зажёгся, поменяйте выводы местами. Если зажёгся- то на + батарейки и будет Анод светодиода.

    e7aa01a9-b7bf-48d4-b6b7-1772aad22eec-image.png

    Если вы собрали схему, то отложите пока её в сторонку. Теперь мы займёмся второй частью задачи - написанием программы управления светодиодом. Писать эту программу мы будем на языке Си.

    Почему на именно на Си? Просто по тому, что я других языков не знаю, а раз вы читаете эту статью, то скорее всего вы тоже немного знаете о программировании и радиоэлектронике, а значит, вам всё равно с какого языка начинать.

    Обычно изучение языков программирования начинают с написания программы «Hello World!», но мы же круче «тех» чайников, поэтому мы начнём сразу с низкоуровневой работы с периферией. Тем более, что это не намного сложнее ХеллоуВорлда. 😉 Что для этого нужно? Нужен любой текстовый редактор, в котором мы будем набирать программу. В Raspbian есть отлично подходящий для этого редактор “nano”. Ещё нужен компилятор, это программа, которая осуществляет перевод написанной нами программы с человечески понятного языка на язык, понятный компьютеру. Т.е. делает из нашей программы исполняемый файл, который мы впоследствии и запустим на Raspberry. Эта штука тоже у нас есть, называется gcc. Этот компилятор поставляется в комплекте со всеми Линуксами и уже готов к работе.

    Как видите,всё необходимое у нас уже есть. Хотя нет. Одной вещи все-таки у нас не хватает. Её мы возьмем из интернета. Речь идёт о библиотеке функций управления портами GPIO на Raspberry, специально написанно добрым человеком для того, чтобы наша программа по своей простоте могла бы соперничать с «Хеллоуворлдом» и нам самим бы не пришлось ломать голову, изучая техническую документацию на процессор и протоколы работы с его внутренностями. Сама библиотека состоит из заголовочного файла, в котором обозначены все имена функций со структурами переменных и файла библиотеки самих функций. Эту библиотеку нужно скачать и установить, чтобы компилятор мог с ней работать. Библиотека называется bcm2835-1.17. Последние цифры в названии библиотеки, обозначают её версию. А так, как библиотека постоянно обновляется автором, то версии будут меняться. на сегодняшний день доступна версия 1.17. Узнать о номере последней версии можно по адресу: http://www.open.com.au/mikem/bcm2835/index.html По этой же ссылке вы можете ознакомиться со всеми функциями, которые присутствуют в этой библиотеке.

    Мы же пока установим версию 1.17. Запускаем окно терминала и вводим туда команду:

    wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.17.tar.gz
    

    Библиотека быстренько скачивается. Чтобы её установить, нужно сначала её разархивировать. Это делается следующей командой:

    tar zxvf bcm2835-1.17.tar.gz
    

    Теперь перейдём в директорию, куда эта библиотека развернулась:

    cd bcm2835-1.17
    

    Ну и инсталлируем её:

    ./configure make
    sudo make check
    sudo make install
    

    Всё, теперь эта библиотека у нас есть в наличии, она установлена, и мы, и компилятор можем ей пользоваться в своих интересах. Начинаем писать программу. Возвращаемся в домашнюю директорию: cd ..

    Тут можно создать папочку для наших экспериментов с любым именем, например myprog:

    mkdir myprog
    

    Перейдём в эту папку:

    cd myprog
    

    И начинаем писать нашу программу: nanoGPIO-test.c

    Эта команда запускает текстовый редактор nano, который создаёт текстовый файл GPIO-test.c.Теперь можете набрать в нём следующую программу (можно просто скопировать и вставить):

    //GPIO-test.c
    // Программа включает на 1 секунду светодиод,
    // подключённый к порту Р1_03
    // Компиляция командой gcc -o GPIO-test GPIO-test.c -lrt -lbcm2835
    #include <bcm2835.h>
    #define PIN RPI_GPIO_P1_03      // Для RPi ревизии v1
    //#define PIN RPI_V2_GPIO_P1_03 // Для RPi ревизии v2
    int main()
    {
        if (!bcm2835_init())        // Инициализация GPIO
            return 1;               //Завершение программы, если инициализация не удалась
    
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);          //Устанавливаем порт Р1_03 на вывод
        bcm2835_gpio_write(PIN, LOW);                            // Устанавливаем порт в 0, светодиод  горит
        bcm2835_delay(1000);                                             // Ждём 1000 милисекунд
        bcm2835_gpio_write(PIN, HIGH);                           // Устанавливаем порт в 1, светодиод не горит
        return 0;                                                // Выход из программы
    }
    

    Обратите внимание на строки #define. Их в программе 2 и одна из них закомментирована. Одна строка для ревизии RPi v1, вторая для RPi v2. Если у вас v1, то всё оставьте как есть. Если у вас RPi v2, то первую строку с #define удалите, а со второй уберите символ комментария //.В будущем, во всех остальных программах, просто добавляйте V2 между RPI и GPIO в определении портов, если ваша плата RPi v2.

    Сохраняем нашу программу ctrl-o и выходим из текстового редактора ctrl-x. Теперь, если вы введёте команду ls, то увидите только что созданный файл GPIO-test.c. Чтобы этот файл превратился в работающую программу, его нужно скомпилировать. Пишем:

    gcc -o GPIO-test GPIO-test.c -lrt -lbcm2835
    

    в этой строке:

    • gcc это имя компилятора;
    • -o GPIO-test GPIO-test.c эта команда компилятору говорит о том, что требуется создать исполняемый файл с именем GPIO-test из текстового файла GPIO-test.c;
    • -l (латинская л маленькая) bcm2835 говорит компилятору о том, что все неизвестные ему функции в нашей программе, он может найти в установленной библиотеке bcm2835.

    Если компилятор не выдал никаких сообщений, то значит, всё у нас получилось. Если сейчас дать команду ls, то мы увидим, что в директории появился ещё один файл GPIO-test, причём он отмечен зелёным цветом. Это говорит о том, что файл является исполняемой программой. Осталось нам его запустить, но перед этим ещё раз проверяем нашу схему со светодиодом, чтобы всё было собрано правильно и подключено к контактам Р1_01 и Р1_03 разъёма GPIO. Если ошибок не обнаружено, запускаем программу:

    sudo ./GPIO-test
    

    После этого светодиод должен загореться ровно на 1 секунду и погаснуть. Если всё так и произошло, то я вас поздравляю! Вы только что при помощи Raspberry Pi передали через порт GPIO команды светодиоду: включиться, гореть 1 секунду и выключиться.

    Теперь о том, что делает каждая строка в нашей программе.

    • Все надписи после двойного слеша // являются коментариями и никак не влияют на выполнение программы.

    • #include <bcm2835.h> -эта строка говорит компилятору, что в программе используется заголовочный файл bcm2835.h. В этом файле находятся все описания функций и идентификаторы портов GPIO.

    • #define PIN RPI_GPIO_P1_03 - здесь мы говорим компилятору, что везде в программе, где он увидит идентификатор PIN, ему нужно выполнить замену его на идентификатор RPI_GPIO_P1_03 . Это сделано для того, чтобы мы могли при желании быстро изменить номер подключаемого порта. Для этого достаточно изменить только эту строку, а не выискивать по всей программе, где мы этот идентификатор использовали.

    • int main() это начало нашей программы, обозначение главной функции в Си.

    • if (!bcm2835_init()) - эта часть пытается инициализировать GPIO и если это не получилось,

    • return 1; то аварийно завершает программу и передаёт на выходе код 1.

    • bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP); - Эта функция устанавливает для нашего порта Р1_03 режим на вывод. Т.е. говорит процессору, что этот порт будет использован для управления внешним устройством.

    • bcm2835_gpio_write(PIN, LOW); - устанавливаем порт Р1_03 в низкое состояние, т.е. процессор его подключает к 0. После этого светодиод загорается.

    • bcm2835_delay(1000); - Эта функция просто ждёт 1000 милисекунд, или ровно 1 секунду. Всё это время у нас горит светодиод.

    • bcm2835_gpio_write(PIN, HIGH); - устанавливаем порт Р1_03 в высокое состояние, т.е. процессор его подключает к +3,3в. При этом светодиод гаснет.

    • return 0; - Выход из программы с кодом 0.

    Т.е. алгоритм работы с портом GPIO в режиме записи, т.е. вывода, выглядит следующим образом:

      1. Инициализируем GPIO;
      1. Устанавливаем режим для выбранного порта на Вывод;
      1. Теперь можем управлять этим портом, устанавливая его в высокое, или низкое состояние. Соответственно на этом порте будет пристутствовать либо +3,3В, либо 0В. Что соответствует логической 1 и логическому 0 соответственно.

    На этом на сегодня закончим. В следующей части научим наш светодиод загораться более полезным образом, а так же научимся портами GPIO не только отдавать команды другим устройством, но и слушать их.

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

  • 1 / 1
  • Войти

  • Нет учётной записи? Зарегистрироваться

  • Login or register to search.
  • Первое сообщение
    Последнее сообщение
0
  • Категории
  • Главная
  • Документация
  • Последние
  • Пользователи