Узнайте, как напрямую запрограммировать BCM2835 на Raspberry Pi, используя его регистры для выполнения таких задач, как запись на вывод GPIO или использование дополнительных возможностей, таких как SPI.

Обзор

Это шестая статья из серии, посвященной Программированию GPIO на Raspberry Pi 3B+. Это введение в управление периферийными устройствами GPIO путем прямого взаимодействия с соответствующими регистрами на BCM2835 SoC (система на кристалле). Это введение, поскольку оно охватывает только одну из возможностей, используя базовую возможность GPIO, как описано в Разделе 6 руководства по периферийным устройствам BCM2835 ARM. Это исключает поддержку BCM2835 таких протоколов, как последовательный периферийный интерфейс (SPI), широтно-импульсная модуляция (PWM) и межинтегральная схема (IC2), которые накладываются на определенные возможности протокола, используя базовые контакты, сопоставленные с данным протоколом (например, GPIO). контакты 8–11 связаны с протоколом SPI и имеют поведение, которое можно активировать в зависимости от этого протокола). Тем не менее, использование базовой функциональности уровня контактов охватывает те же концепции, которые необходимы для использования расширенных возможностей протокола, таких как SPI. Эта статья в сочетании с Руководством по периферийным устройствам BCM2835 ARM должна предоставить достаточно информации для управления устройствами, которые управляются с помощью этих возможностей.

Конечно, существует множество библиотек, таких как библиотеки C BCM2835 и WiringPi, библиотеки Python pgpio и RPi.GPIO, а библиотека Go go-rpio делает это намного проще. И хотя эти библиотеки обычно больше подходят для большинства проектов, может быть полезно понять, как работает SoC BCM2835 и как эти библиотеки взаимодействуют с BCM2835. Этот фон также полезен, если вы читаете код библиотеки и/или хотите внести свой вклад в эти проекты или даже написать свою собственную библиотеку.

В этой статье предполагается, что вы уже знакомы с программированием BCM2835 и GPIO на Raspberry Pi. Если вы этого не сделаете, вы можете рассмотреть возможность попробовать проекты, описанные в некоторых из моих других статей GPIO. Самая простая статья из серии — Raspberry Pi GPIO в Go и C — мигающий светодиод. Он демонстрирует те же возможности, что и эта статья, мигание светодиода, но также демонстрирует использование библиотек WiringPi и go-rpio Go. Это хорошее введение в использование GPIO на Raspberry Pi, но оно охватывает только основные концепции GPIO и способы подключения макетной платы к выходам GPIO Raspberry Pi.

Будут затронуты следующие темы:

  1. Предварительные условия — описывает оборудование и библиотеки, которые вам понадобятся для этой статьи.
  2. Введение в периферийные устройства GPIO BCM2835 — содержит обзор архитектуры и возможностей BCM2835.
  3. Использование платы BCM2835 для управления светодиодом через контакты GPIO – подробные сведения, включая код, для управления светодиодом с помощью регистров GPIO BCM2835.
  4. Резюме — краткое изложение важных понятий, рассмотренных в этой статье.
  5. Ссылки — содержит список ссылок, которые я считаю полезными, некоторые из которых использовались при создании этой статьи.

Предпосылки

Если у вас его нет, вам понадобится Raspberry Pi. Я использовал Raspberry Pi 3B+ с растянутой версией ОС Raspbian. См. Как настроить новый Raspberry Pi с нуля, если у вас еще нет Raspberry Pi и вам нужна помощь в его настройке.

Далее вам понадобится макет, несколько перемычек, резистор на 220 Ом и светодиод. Вам также следует подумать о приобретении 40-контактного разъема «мама-мама с адаптером T-типа» для подключения выходов GPIO к макетной плате. Вы можете использовать только перемычки, но адаптер упростит задачу и поможет предотвратить повреждение контактов GPIO на Raspberry Pi. Если вы решите не покупать 40-контактный кабель с адаптером T-типа, вам нужно будет купить перемычки типа «папа-мама. Однако покупка всех этих вещей по отдельности будет стоить дороже, чем комплект. Вот простой комплект, в котором есть все вышеперечисленное. Если вы планируете следовать этой серии, я рекомендую купить Sunfounder Raspberry Pi Ultimate Starter Kit.

Вам понадобятся некоторые базовые знания программирования на C, а также знакомство с входом в терминал Raspberry Pi или в графический интерфейс рабочего стола, который поставляется с некоторыми версиями ОС. В зависимости от выбранного вами подхода вам может потребоваться подключить клавиатуру и монитор к Raspberry Pi. Я просто SSH в Pi. Вам понадобится знакомство с тем, как использовать такие редакторы, как Vi или nano. Вам также потребуется базовое знакомство с командной строкой Linux.

Наконец, вам нужно будет клонировать или разветвить мой репозиторий GPIO, так как он содержит базовый код библиотеки, используемый для доступа к возможностям GPIO на BCM2835. Как упоминалось в других разделах, код этой библиотеки основан на библиотеке C BCM2835, разработанной Майком Макколи.

Введение в периферийные устройства BCM2835 ARM

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

Это не исчерпывающее описание BCM2835. Единственная возможность, которая будет подробно описана и находится в центре внимания этой статьи, — это взаимодействие с функциями ввода-вывода GPIO, а именно определение функции вывода, а также установка и очистка значения вывода (например, установка его на ВЫСОКИЙ и НИЗКИЙ), специально запрограммировав BCM2835 GPIO для мигания светодиода.

Существует несколько схем нумерации контактов. Raspberry Pi имеет физические номера контактов, то есть номера, напечатанные на плате Pi. BCM2835 имеет схему нумерации, называемую нумерацией GPIO. Она отличается от физической схемы нумерации Pi и несовместима с ней. Наконец, некоторые библиотеки, такие как библиотека C WiringPi, имеют собственные схемы нумерации, несовместимые со схемами Pi и BCM2835. В этой статье используется исключительно схема нумерации BCM2835. Существует хорошая доступная схема выводов, которая отображает эти 3 схемы нумерации. Если вывод поддерживает определенную функцию ввода-вывода, как описано ниже, это также обозначено на этой схеме выводов.

Небольшое замечание по терминологии… Термин «периферийные устройства» используется в заголовке и в некоторых частях этой статьи. Для меня термин «периферийные устройства» является чем-то неправильным. Позволь мне объяснить. По сути, возможности GPIO BCM2835 доступны через набор физических контактов на устройстве. Некоторые из этих контактов обеспечивают питание и заземление, необходимые для управления внешними устройствами, такими как светодиоды, датчики и двигатели. Другие контакты могут управляться программно, чтобы принимать входные данные от этих внешних устройств или выводить их на них. На более высоком уровне подмножества контактов GPIO обеспечивают поддержку полных протоколов, которые реализуют более сложные функции, такие как управление скоростью и направлением двигателя и управление светодиодными дисплеями, которые отображают текст, как на дорожных знаках. Эти основные и более сложные возможности управляются путем установки функции булавки. Вход и выход — это 2 функции, которые можно настроить. Есть множество других, которые будут покрыты. Настройка функции вывода описывает, как BCM2835 взаимодействует с устройством или периферийным устройством. По этой причине я предпочитаю использовать термин «функция ввода/вывода». Я думаю, что это более точно описывает концепцию, чем термин «периферийные устройства». Для меня «периферия» — это внешние устройства. Термин Функции ввода-вывода впервые используется в названии следующего раздела.

Функции ввода/вывода

BCM2835 поддерживает множество функций ввода/вывода. Они являются предметом Руководства по периферийным устройствам BCM2835 ARM. В этом разделе подробно описаны лишь некоторые из этих функций. Есть и другие источники, такие как Википедия, которые могут предоставить информацию о других.

Основные функции ввода-вывода, поддерживаемые BCM2835:

  • GPIO
  • СПИ
  • ШИМ
  • ВСПОМОГАТЕЛЬНЫЙ
  • BSC
  • прямой доступ к памяти
  • Контроллер внешних средств массовой информации
  • прерывания
  • Аудио (аудио PCM/I2s)
  • Системный таймер
  • UART

Поскольку у меня есть непосредственный опыт работы с первыми тремя функциями ввода-вывода, GPIO, SPI и PWM, в этом разделе основное внимание будет уделено им. Это обеспечит достаточный контекст, чтобы получить общее представление о том, на что способен BCM2835, а также использовать информацию в разделах «Адресация» и «Регистры» ниже, чтобы узнать и использовать оставшиеся функции ввода-вывода.

GPIO (ввод/вывод общего назначения)

GPIO означает ввод-вывод общего назначения. В руководстве BCM2835 ARM Peripherals термин GPIO относится к наиболее общему или базовому уровню управления физическим выводом. Это то, что используется для управления функцией контакта, например, для его установки в качестве входа или выхода, или для более продвинутых функций ввода-вывода, таких как SPI и PWM. Он также используется для установки или получения значения вывода. Значения концептуально называются ВЫСОКИМ и НИЗКИМ, но они представлены напряжением или его отсутствием, проходящим через контакт. Существуют и другие типы настроек пинов. Одна из этих настроек используется для управления обнаружением изменения состояния на нарастающем или спадающем фронте изменения напряжения на выводе. Другая настройка управляет так называемыми подтягивающими/подтягивающими резисторами, прикрепленными к каждому контакту. Эти резисторы используются для явного управления значением вывода, 1 или 0, когда напряжение на выводе находится в неопределенном состоянии. В этом руководстве есть достаточно хорошее объяснение того, зачем нужны подтягивающие и подтягивающие резисторы.

Код, связанный с этой статьей, вызывает мигание светодиода. Достижение этого включает в себя следующие шаги:

  1. Установите функцию вывода на вывод, т.е. на него будет производиться запись для отправки сигнала на светодиод.
  2. Напишите LOW на контакт, чтобы светодиод загорелся.
  3. Пауза
  4. Напишите HIGH на контакте, чтобы светодиод погас.
  5. Повторите шаги со 2 по 4.

Для этого используются функции ввода-вывода GPIO. Точнее, он использует регистры GPIO для установки функции вывода (например, выхода) и для управления поведением периферийного устройства, в данном случае светодиода.

SPI (последовательный периферийный интерфейс)

SPI используется для последовательной отправки данных на периферийное устройство, которое может принимать или запрашивать данные параллельно. Это полезно, потому что относительно большой набор параллельных входов может быть записан с использованием всего 3 контактов GPIO, не включая питание (VCC) и землю (GND). Если бы SPI не использовался, для каждого параллельного входа потребовался бы один контакт GPIO. Это может легко быть запретительным, поскольку булавки являются ограниченным ресурсом. Давайте посмотрим, как это работает на примере.

На приведенной выше диаграмме показан матричный светодиодный дисплей MAX7219. Он обычно используется для отображения произвольных форм, таких как буквы, цифры и смайлики. Он управляет матрицей светодиодов 8x8. Для управления светодиодом требуется 1 контакт, исключая питание или землю. Светодиодная матрица 8x8 имеет 64 светодиода. Это число значительно превышает 26 или 40 контактов GPIO, доступных на стандартном Raspberry Pi. Как объясняется ниже, для использования SPI требуется значительно меньше контактов GPIO.

MAX7219 имеет следующие входные контакты:

  1. DIN — это вывод последовательного ввода данных.
  2. CS — это обычно называется выводом выбора микросхемы (CS) или выводом включения микросхемы (CE).
  3. CLK — этот контакт подключается к контакту синхронизации на Raspberry Pi, который синхронизирует передачу данных между Raspberry Pi и MAX7219.

Есть еще 2 вывода, которые не используются для управления MAX7219.

  1. VCC — это контакт питания. Он подключается к источнику питания на Raspberry Pi, обычно к источнику 3,3 В.
  2. GND — это контакт заземления. Он подключается к заземляющему контакту Raspberry Pi.

Для подключения Raspberry Pi к MAX7219 требуется как минимум 3 контакта GPIO, в частности, к контактам DIN, CS и CLK. Raspberry Pi может управлять всеми 64 светодиодами экономичным (с точки зрения выводов) способом, используя только эти три контакта. Обычный подход заключается в использовании выводов Raspberry Pi, которые могут управлять выводами DIN, CS и CLK. Эти 3 вывода называются:

  1. (SPI)MOSI — расшифровывается как Master Out Slave In. Вывод MOSI будет подключен к выводу DIN MAX7219 и используется для отправки сигнала последовательных данных на MAX7219 (или любое периферийное устройство SPI).
  2. (SPI)SCLK — расшифровываются как SPI Clock. Этот вывод будет подключен к выводу CLK MAX7219. Он используется в качестве источника тактового сигнала, используемого для синхронизации передачи данных между BCM2835 и MAX7219 (или любым периферийным устройством SPI). Передача данных будет происходить, когда и вывод MOSI, и вывод SCLK установлены в состояние HIGH.
  3. (SPI)CS или (SPI)CE — расшифровываются как Chip Select или Chip Enable. В любом случае установка вывода в LOW заставляет MAX7219 принимать данные, как описано в предыдущем пункте.

Я неохотно использую термины «хозяин» и «раб». Однако эти термины используются во всех документах, которые я читал по SPI. Я буду продолжать использовать их, чтобы избежать путаницы.

Для возможности SPI на Raspberry Pi может потребоваться до 5 контактов GPIO. 2 дополнительных контакта:

  1. (SPI)MISO — расшифровывается как Master In Slave Out. Этот вывод позволяет MAX7219 (или любому периферийному устройству SPI) отправлять данные обратно в BCM2835.
  2. (SPI)CS или (SPI)CE — это второй контакт выбора/включения чипа. Наличие второго контакта CS/CE позволяет BCM2835 управлять двумя периферийными устройствами SPI. Как указано выше, когда на выводе CS/CE установлено значение LOW, периферийное устройство SPI будет принимать данные от BCM2835. Таким образом, установив один из выводов CS/CE в состояние HIGH, а другой — в LOW, мы можем контролировать, какое ведомое устройство может получать и отправлять данные. В статье Использование нескольких ведомых устройств SPI с Raspberry Pi содержится дополнительная информация о том, как используются 2 контакта CE/CS. На следующей схеме показано, как это делается:

Основной интерфейс SPI на BCM2835, SPI0, реализован на контактах 7–11 GPIO. Контакты 7 и 8 — это 2 контакта CE/CS, доступные на BCM2835. Контакт 9 — MISO, 10 — MOSI, а 11 — часы (SPICLK/SCLK). BCM2835 имеет 2 вспомогательных интерфейса SPI, SPI1 (также известный как AUX_SPI0) и SPI2 (также известный как AUX_SPI1). В Руководстве по периферийным устройствам BCM2835 ARM SPI1 доступен на контактах 16–21, а SPI2 — на контактах 35–39. Эти вспомогательные интерфейсы доступны через функцию AUX I/O.

На следующей диаграмме показана плата расширения GPIO, часто используемая для подключения контактов GPIO BCM2835 к макетной плате. Контакты помечены номерами контактов GPIO или функцией ввода-вывода (например, SPI), которую они поддерживают. Обратите внимание, что контакты SPI помечены на плате с использованием тех же терминов, что и выше:

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

В моей статье под названием Raspberry Pi GPIO — использование SPI для отображения произвольных шаблонов на матричном дисплее MAX7219 подробно рассказывается о функции ввода-вывода SPI и о том, как ее программировать. Подобно этой статье, она также включает подробное описание того, как напрямую запрограммировать BCM2835, используя соответствующие регистры на BCM2835, но для SPI, а не для общих функций ввода-вывода GPIO. Прочитав эту статью, вы получите более глубокое понимание того, как использовать регистры BCM2835 для программирования функций ввода-вывода, поддерживаемых BCM2835.

ШИМ (широтно-импульсная модуляция)

ШИМ используется для преобразования цифрового сигнала, подобного сигналу, создаваемому BCM2835, в смоделированный аналоговый сигнал. Он моделируется в том смысле, что это не настоящий аналоговый сигнал. Это по-прежнему цифровой сигнал, но он циклически повторяется так быстро, что, как и видео, кажется аналоговым сигналом. Многие периферийные устройства, такие как электродвигатели, светодиоды с регулируемой яркостью и цветные светодиоды, требуют аналогового сигнала. Как и в случае с SPI, здесь есть специальные выводы, которые можно активировать для использования функции ввода-вывода ШИМ. Это контакты 12, 13, 18 и 19 GPIO. С помощью этих контактов BCM2835 поддерживает так называемую аппаратную ШИМ. Функциональность ШИМ встроена в BCM2835. Он реализует аппаратную тактовую петлю, которая управляет частотой моделируемого аналогового сигнала. Можно использовать другие контакты GPIO для ШИМ, это называется программной ШИМ. Однако в программном PWM тактовый цикл записывается в коде, который работает на процессоре Raspberry Pi. Поскольку этот ЦП делает много других вещей, невозможно создать надежный тактовый сигнал. Это может привести к некоторым нежелательным побочным эффектам, таким как мерцание светодиода вместо тусклого светодиода. См. мои статьи Raspberry Pi GPIO в Go и C — RGB LED и Широтно-импульсная модуляция для чайников для получения дополнительной информации о ШИМ, а также примеров использования ШИМ.

Адресация

Доступ ко всем функциям ввода-вывода GPIO осуществляется через регистры. Эти регистры расположены по разным смещениям во встроенной памяти. Для доступа к регистрам требуется знание того, как работает адресация на BCM2835 и на Raspberry Pi. Вот диаграмма из Руководства по периферийным устройствам ARM BCM2835, раздел 1.2.1, показывающая, как память отображается из физического адреса Pi в адреса BCM2835.

Raspberry Pi сопоставляет память BCM2835 с физическим адресом Pi 0x2000 0000. Это показано в среднем столбце с надписью Физические адреса ARM. Заштрихованная серым цветом область, обозначенная как периферийные устройства ввода-вывода, — это расположение в физической памяти Pi периферийных устройств BCM2835 (например, контактов GPIO). Двигаясь влево, вы увидите золотую коробку с надписью VC/ARM MMU. Это блок управления памятью, который отвечает за сопоставление физической памяти Pi с памятью BCM2835. Следуя по строке от раздела Периферийные устройства ввода-вывода среднего столбца через MMU VC/ARM к крайнему левому столбцу под названием Адреса шины ЦП VC, вы ведете к разделу Периферийные устройства ввода-вывода на адресной шине ЦП BCM2835. . Его адрес 0x7E00 0000.

Адреса периферийных устройств ввода/вывода могут быть доступны приложению одним из двух способов. Традиционный способ — через устройство /dev/mem, смонтированное в файловой системе. Для доступа к этому устройству требуются привилегии root. Более новый метод стал доступен, начиная с Raspberry Pi 2. Этот метод получает доступ к адресам периферийных устройств ввода-вывода через /dev/gpio. Преимущество использования этого метода заключается в том, что он не требует root-доступа. Недостатком использования этого метода является то, что можно получить доступ только к функциям ввода-вывода GPIO BCM2835. Это означает, что расширенные возможности, такие как PWM и SPI, недоступны. Доступ к периферийным устройствам ввода-вывода с помощью /dev/mem также требует немного больше работы, а именно сопоставления физической памяти из /dev/mem с виртуальной памятью приложения. Это будет рассмотрено более подробно позже.

Существует третий способ взаимодействия с регистрами GPIO. В случае SPI есть 2 устройства (обычно доступны на многих моделях Raspberry Pi), /dev/spidev0.0 и /dev/spidev0.1 через ioctl(). Этот метод не будет рассматриваться в этой статье.

Регистры

Как упоминалось выше, указание функции ввода-вывода вывода или набора выводов выполняется с помощью регистров BCM2835. Каждая функция ввода-вывода имеет соответствующий набор регистров, расположенных по смещению в адресном пространстве периферийного устройства ввода-вывода BCM2835. Например, набор регистров GPIO начинается с адреса шины ЦП 0x7E20 0000. Набор регистров SPI начинается со смещения адреса шины ЦП 0x7E20 4000. Набор регистров ШИМ начинается со смещения 0x7E20 C000. И так далее, каждая функция ввода-вывода имеет собственный набор регистров для управления ее поведением. Тем не менее, все они работают по одним и тем же принципам. А именно, найдите смещение начала набора регистров каждой функции ввода-вывода, а затем установите (или получите) соответствующие биты по соответствующим смещениям для управления поведением. Чтобы сделать этот пример более наглядным и выделить дополнительные детали, давайте подробно рассмотрим, как управлять выводом GPIO для мигания светодиода.

Как упоминалось ранее, регистр GPIO начинается с адреса шины 0x7e2000 0000. Раздел 6.1 Руководства по периферийным устройствам BCM2835 ARM, начинающийся на странице 90, содержит подробную информацию о регистрах GPIO. Чтобы мигать светодиодом, нам сначала нужно указать функцию вывода, выход в этом случае, а затем переключить значение светодиода между ВЫСОКИМ и НИЗКИМ.

Укажите функцию контакта

Во-первых, необходимо установить функцию ввода/вывода вывода. Функциональность булавки определяется одной из 8 альтернативных функций. Для кодирования каждой функции требуется 3 бита, 000–111. Эти биты содержатся в так называемом регистре выбора функции GPIO. Каждый регистр имеет длину 32 бита. Таким образом, каждый 32-битный регистр может управлять 10 выводами с 2 оставшимися битами. Для управления всеми выводами GPIO имеется 6 регистров выбора функций. BCM2835 имеет возможность управлять до 54 контактов, если они поддерживаются, через регистры выбора функций (на Raspberry Pi 3B+ доступно только 40 контактов). Для поддержки 54 контактов с 10 контактами на регистр требуется 6 регистров. Первый регистр выбора функции расположен по адресу 0x7E20 0000, нулевое смещение от начала набора регистров GPIO. Регистр выбора 6-й функции начинается со смещения 0x7E20 0014. 8 альтернативных функций:

  • Вход — представлен битовым шаблоном 000
  • Выход — представлен битовым шаблоном 001
  • и Альтернативные функции с 0 по 5, представленные битовыми комбинациями с 010 по 111 соответственно.

В этом примере давайте используем контакт 17 в качестве выходного контакта. Изучая руководство по периферийным устройствам BCM2835 ARM, мы видим, что контакт 17 находится в регистре выбора функции 1, также известном как GPFSEL1. GPFSEL1 находится по смещению 0x7E20 0004. Смещение выбора функции контакта 17 в регистре находится в битах 21-23. Напомним, что выходная битовая комбинация 001, поэтому нам нужно поместить эту битовую комбинацию в биты 21-23. В разделе о кодировании ниже мы увидим, как это на самом деле достигается.

Установите значение булавки

В этой статье предполагается, что контакт 17 подключен к клемме заземления светодиода (катоду). Это означает, что нам нужно установить контакт на НИЗКИЙ уровень, чтобы ток протекал от входа питания к аноду через землю светодиода на контакте 17 и зажег светодиод. Установка вывода в положение HIGH приведет к выключению светодиода. Переключение между LOW и HIGH приведет к миганию светодиода, что нам и нужно.

Значения выводов GPIO контролируются через регистр набора выводов GPIO. Есть 2 регистра набора выводов GPIO. Контакт 17 находится в первом регистре, GPIO Pin Output Set 0, AKA GPSSET0, со смещением регистра GPIO 0x7E20 002C. Вывод 17 имеет битовое смещение 17 в пределах GPSSET0. Установка бита 17 в 1 устанавливает регистр в HIGH. Регистры набора выводов GPIO могут устанавливать вывод только в ВЫСОКИЙ, установка контакта в 0 не имеет никакого эффекта. Но помните, нам нужно установить вывод на НИЗКИЙ уровень. Поскольку установка вывода в GPSSET0 не приведет к тому, что вывод будет установлен в LOW, как мы можем этого добиться? Оказывается, есть еще один набор регистров GPIO, который называется регистрами очистки выводов GPIO. Они используются для установки значения вывода на LOW. Контакт 17 находится в регистре очистки вывода вывода GPIO 0, также известном как GPCLR0, начиная со смещения 0x7E20 0028. Его позиция в регистре также находится в 17-м бите. Установка 17-го бита в 1 установит вывод в LOW. Итак, нам нужно сдвинуть влево 1 в бит 17, чтобы включить светодиод. Мы будем использовать GPSSET0, чтобы выключить светодиод. Как и в случае определения функции вывода, как это достигается, будет описано далее в этой статье. Используя по очереди регистры GPCLR0 и GPSSET0, мы можем заставить светодиод мигать.

Другие настройки GPIO

Набор регистров GPIO может использоваться для управления многими другими функциями вывода GPIO в дополнение к выбору функции и установке/очистке значения вывода. Его также можно использовать для чтения значения вывода. Как обсуждалось в разделе функций ввода-вывода выше, другие вещи, которыми можно управлять, включают указание того, как должны обнаруживаться изменения состояния (обнаружение нарастающего/спадающего фронта) и как установить напряжение на выводе, когда он находится в неопределенном состоянии (вытягивание). повышающие/понижающие резисторы). Обсуждение этих других регистров является сложной темой и выходит за рамки этой статьи.

Настройка и код

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

Макетная плата должна быть подключена, как показано на приведенной выше схеме. Обратите внимание, что (изогнутый) длинный вывод светодиода, анод, подключен к контакту питания 3,3 В через резистор, а катод или земля подключены к контакту 17 GPIO. Если вы не знакомы с макетными платами и макетными схемами этот учебник по макету должен быть полезен.

В этом разделе сначала будет описана основная программа, которая мигает светодиодом. После этого подробно рассказывается о том, как работает код, взаимодействующий с BCM2835, для управления функциями ввода-вывода.

Весь код этой статьи можно найти в моем репозитории gpio, в каталоге ledblink.

Управление светодиодом — основная программа

Код файла, содержащего этот код, можно найти по адресу gpio/ledblink/blinkinglednolib.c.

Этот первый фрагмент кода показывает пару важных вещей:

  1. Строка 1 дает команду на компиляцию и компоновку программы.
  2. В строке 3 содержится включенная директива, необходимая для импорта функций BCM2835, которые напрямую взаимодействуют с BCM2835.
  3. Строка 8 определяет вывод BCM GPIO, который используется для управления светодиодом. Он соответствует приведенной выше схеме подключения.

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

Этот фрагмент показывает начало основной функции.

Строка 3 регистрирует обработчик прерывания для сигнала SIGINT (т. е. Ctrl-C).

Строки 5–10 инициализируют функциональные возможности BCM2835 GPIO. Вкратце, он находит смещение и длину периферийных устройств GPIO в основной памяти и сопоставляет их с /dev/mem, чтобы к ним можно было безопасно получить доступ в программе. Затем он устанавливает переменные для смещения различных регистров возможностей (например, GPIO или SPI). Если инициализация не удалась, программа завершает работу. Функция bcm_init() будет более подробно описана ниже.

Строка 13 задает LEDPIN функцию вывода GPIO. Это позволяет программе записывать значения, ВЫСОКИЕ или НИЗКИЕ, на вывод. Функция bcm_gpio_fsel() будет более подробно описана ниже.

Этот фрагмент реализует основной функционал программы. Строки 2–10 включают и выключают светодиод.

Строки 5 и 8 устанавливают значение LEDPIN, вызывая bcm_gpio_write() и устанавливая значение вывода либо на LOW, чтобы включить светодиод, либо на HIGH, чтобы выключить светодиод. LOW выключает светодиод, потому что вывод, соединяющий контакт 17 GPIO BCM со светодиодом, подключен к контакту заземления светодиода. Чтобы ток протекал, вывод должен быть установлен на 0 вольт. Аналогично, HIGH выключает светодиод. bcm_gpio_write() будет более подробно описан ниже.

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

Как указано в комментарии, строка 13 освобождает ресурсы GPIO, полученные с помощью bcm_init(). Это также вернет систему GPIO в известное состояние. bcm_close() будет более подробно описан ниже.

Этот фрагмент определяет функцию обработчика прерывания. Строки 3 и 4 сначала выключают светодиод, а затем освобождают ресурсы GPIO, полученные с помощью bcm_init().

Управление светодиодом — взаимодействие с BCM2835

В этом разделе описывается, как код взаимодействует с периферийными устройствами GPIO и управляет ими, в частности, устанавливая значения различных регистров, связанных с функциональностью ввода-вывода BCM2835. Установка значений регистров используется для управления периферийными устройствами GPIO, а значения регистров чтения позволяют получить доступ к текущему состоянию связанных периферийных устройств GPIO.

Код этого раздела можно найти в bcmfuncs.c в моем репозитории gpio. Заголовочный файл можно найти в bcmfuncs.h.

bcm_init()

В приведенном выше разделе о регистрах упоминается, что все операции с периферийными устройствами BCM2835 выполняются путем манипулирования регистрами, связанными с этим типом периферийных устройств. Назначение bcm_init() — определить смещения адресов для всех типов регистров, например, GPIO, PWM, SPI и т. д., чтобы можно было управлять связанными с ними периферийными устройствами.

Этот фрагмент показывает начало функции bcm_init().

Строка 10 открывает файл BCM_RPI2_DT_FILENAME. Как указано в комментарии, rb в вызове fopen() открывает бинарный файл для чтения. BCM_RPI2_DT_FILENAME определяется в соответствующем заголовочном файле bcmfuncs.h. Его значение равно /proc/device-tree/soc/ranges. Как указано в Linux и дереве устройств,

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

Одна из частей информации, которую содержит дерево устройств и которая важна для наших целей, — это расположение и длина раздела I/O Peripherals в физическом адресном пространстве BCM2835. Вот шестнадцатеричный дамп содержимого /proc/device-tree/soc/ranges на Raspberry Pi 3B+:

pi@pi-node1:/ $ hexdump -C /proc/device-tree/soc/ranges
00000000  7e 00 00 00 3f 00 00 00  01 00 00 00 40 00 00 00  |~...?.......@...|
00000010  40 00 00 00 00 00 10 00                           |@.......|
00000018

Мы вернемся к этому в следующем фрагменте кода. Согласно devicetree.org в v0.4-rc1 спецификации Devicetree, свойство soc/ranges определяется следующим образом:

Свойство ranges предоставляет средства определения сопоставления или преобразования между адресным пространством шины (дочернее адресное пространство) и адресным пространством родительского узла шины (родительское адресное пространство). Формат значения свойства ranges представляет собой произвольное количество триплетов (дочерний-адрес-шины, родительский-адрес-шины, длина).

В случае адресного блока периферийных устройств ввода-вывода, как описано в разделе Адресация выше, дочерний-адрес-шины относится к Периферийным устройствам ввода-вывода адресный блок в пространстве памяти Адреса шины ЦП VC. "parent-bus-address" относится к блоку адреса "I/O Peripherals" в блоке "Физические адреса ARM". А "длина" относится к фактической длине адресного блока "Периферийные устройства ввода-вывода". Raspberry Pi 3B+ имеет 32-битное адресное пространство, поэтому каждое из значений в тройке составляет 32 бита или 4 байта.

Следующий фрагмент кода находит адрес родительской шины и длину блока периферийных устройств ввода-вывода, используя данные из /proc/device-tree/soc/ranges. Напомним, что адрес родительской шины — это вторая запись в тройке, которая также определяет адрес и длину дочерней шины. Строки с 1 по 4 сдвигают содержимое адреса родительской шины, байты по buf смещениям с 4 по 7, в base_address. 3F сдвигается в старший байт base_address. Остальные ячейки buf также сдвигаются в base_address слева (наиболее значащий байт) вправо (наименее значащий байт). После завершения переключения base_address будет установлено на 0x3F000000. Взглянув на диаграмму в разделе Адресация выше, вы заметите, что она не соответствуетбазовому адресу 0x20000000 в адресном блоке I/O Peripherals в Физические адреса ARM. Это связано с тем, что диаграмма в разделе Адресация предназначена для Raspberry Pi 1. Это смещение отличается в моделях Raspberry Pi 2 и 3. И оно снова отличается в модели Raspberry Pi 4.

Двигаясь дальше, аналогично base_address, строки с 6 по 9 получают длину путем сдвига от buf смещений с 8 по 11 к peri-size. Для Raspberry Pi 3B+ 0x01000000 — это ожидаемая длина блока адреса.

Следующий фрагмент кода завершает получение информации об адресации от /proc/device-tree/soc/ranges. В частности, он проверяет, что «дочерний-адрес-шины» и «родитель-шина-адрес» имеют ожидаемые значения. Напомним, что «child-bus-address» — это первое значение в триплете адресов/длин, buf смещает от 0 до 3. Строки с 1 по 6 делают следующее:

  1. Строки с 1 по 4 подтверждают, что первые 4 байта buf, адрес дочерней шины, имеют значение 0x7E000000, как определено в «Периферийные устройства ввода-вывода»адресный блок в пространстве памяти «Адреса шины VC CPU».
  2. Строка 5 проверяет, соответствует ли адрес родительской шины, base_address, одному из ожидаемых базовых адресов для моделей Raspberry Pi с 1 по 4.

Строки 7 и 8 затем задают базовый адрес и длину адресного блока I/O Peripherals для переменных, которые будут использоваться в остальной части программы. Важной особенностью этой строки является то, что она приводит исходные переменные к типам, ожидаемым целевыми переменными.

Следующий фрагмент сопоставляет адресный блок «Периферийные устройства ввода-вывода» BCM2835 с пространством памяти «Физические адреса ARM» из /dev/mem, чтобы его можно было безопасно использовать в программе.

Строка 1 сначала проверяет, работает ли пользователь, запускающий программу, как root (пользователь root или sudo). ). В противном случае устройство /dev/mem использовать нельзя. /dev/mem обеспечивает неограниченный доступ к памяти ЦП. Поскольку это явно опасная вещь, ее использование ограничено root. Как вы помните из раздела Адресация, есть альтернатива /dev/mem, /dev/gpio. Это будет показано позже в этом разделе.

Строка 4 открывает /dev/mem для подготовки к операции сопоставления.

Строка 13 присваивает переменной bcm_peripherals смещение и длину, bcm_peripherals_base и bcm-peripherals_size, адресного блока I/O Peripherals. Это сопоставление эффективно ограничивает доступ программы к адресному блоку I/O Peripherals физической памяти.

Следующий фрагмент кода вычисляет смещения для различных типов регистров (например, GPIO, PWM, SPI и т. д.). Каждое смещение относится к значению bcm_peripherals, которое было результатом операции mapmem() в предыдущем фрагменте. Таким образом, чтобы получить адрес набора регистров, подобного набору регистров GPIO, мы должны добавить BCM_GPIO_BASE к базовому адресу bcm_peripherals. Но вы, возможно, заметили, что код делит эти смещения на 4 перед добавлением их к bcm_peripherals. Эти смещения, например, BCM_GPIO_BASE, указанные как целые числа, указывают на конкретные адреса байтов, т. е. на 8-битные границы. bcm_peripherals указывается как uint32_t*, указатель. Вы не можете просто наивно добавить целое число к указателю. Вы можете вспомнить, что арифметика указателя работает с переменной-указателем на основе размера типа связанной переменной. Для unint32_t их размер составляет 32 бита или 4 байта. Добавление 1 к указателю uint32_t приведет к тому, что этот указатель будет указывать на следующий uint32_t, который начинается через 4 байта от текущей позиции. Наивное добавление смещений, таких как BCM_GPIO_BASE к bcm_peripherals, приведет к тому, что bcm_peripherals будет указывать на позицию в памяти, которая находится на 4 uint32_tс от текущей позиции, а не на 1 uint32_t от текущей позиции, что нам и нужно. Вот почему каждое смещение типа регистра, такое как BCM_GPIO_BASE, делится на 4 перед добавлением его к bcm_peripherals.

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

bcm_fsel()

bcm_fsel отвечает за настройку функции ввода-вывода, связанной с данным выводом. Всего доступно 8 функций. Один определяет, что связанный контакт должен быть установлен как входной контакт, что означает, что он будет считываться. Другая функция определяет связанный контакт как выходной контакт, что означает, что на этот контакт будет производиться запись. Остальные 6 называются «альтернативными функциями» и им даются такие имена, как «альтернативная функция 0». Функция ввода-вывода, назначенная для различных альтернативных функций, отличается для разных контактов GPIO. Например, установка вывода 17 BCM GPIO на альтернативную функцию 4 определяет его функцию ввода-вывода как SPI. На самом деле он определяет вывод как специфическое подмножество функций SPI, называемое включением микросхемы или выбором микросхемы, но это тема для следующей статьи. Напомним, что в main() выше функция устанавливается на BCM_GPIO_FSEL_OUTP, что определяет контакт 17 как выходной.

В этой функции происходит довольно много, хотя она довольно короткая.

Строка 1 определяет функцию как принимающую два параметра: pin и mode. Совершенно очевидно, что pin контакт, функция которого должна быть назначена. mode — это функция ввода-вывода, связанная с pin. mode на самом деле является битовой комбинацией, которая должна быть назначена конкретному смещению регистра. Назначения битовой комбинации определены в Техническом описании периферийных устройств ввода-вывода BCM2835 в разделе 6, GPIO. Шаблоны определяются следующим образом:

000 = GPIO Pin X is an input
001 = GPIO Pin X is an output
100 = GPIO Pin X takes alternate function 0
101 = GPIO Pin X takes alternate function 1
110 = GPIO Pin X takes alternate function 2
111 = GPIO Pin X takes alternate function 3
011 = GPIO Pin X takes alternate function 4
010 = GPIO Pin X takes alternate function 5

Для понимания остальной части функции требуется дополнительная информация. Во-первых, Техническое описание периферийных устройств ввода-вывода BCM2835, раздел 6, показывает, что в общей сложности 54 контакта GPIO адресуются через регистры выбора функций (GPFSEL0-GPFSEL5). Каждый регистр выбора функции имеет длину 32 бита. Поскольку каждый из приведенных выше шаблонов выбора функции имеет длину 3 бита, каждый регистр выбора функции может указывать функцию ввода/вывода для 10 контактов с 2 оставшимися битами. Десять контактов на регистр и в общей сложности 54 контакта объясняют, почему существует 6 регистров выбора функций, пронумерованных от 0 до 5. Приведенные ниже математические вычисления получены из этой информации.

Строка 3 определяет смещение регистра paddr битов, которые будут установлены в соответствии с mode. BCM_GPFSEL0 — это базовое смещение в байтах регистров выбора функции. Вот объяснение арифметики, выполненной в этой строке.

  1. Обратите внимание, что выполняются арифметические операции с указателями. Напомним, что результат арифметики указателя основан на типе целевой переменной (см. раздел bcm_init() выше). Поскольку uint32_t имеют длину 4 байта, BCM_GPFSEL0 необходимо разделить на 4, чтобы вычисление получилось правильным. Отсюда ... paddr = bcm_gpio + BCM_GPFSEL0/4 ....
  2. Кроме того, напомню, что каждый регистр содержит информацию о выборе функции для 10 контактов. Для данного вывода нам нужно определить, какой регистр выбора функции, от GPFSEL0 до GPFSEL5, определяет функцию ввода-вывода для данного вывода. В C результат целочисленного деления, который приводит к дроби, будет округлен в меньшую сторону. Итак, если мы разделим номер контакта на 10, (pin/10), мы получим смещение для правильного регистра выбора функции. Таким образом, контакт 9 приведет к 9/10, что равно 0, что означает, что местоположение выбора функции контакта 9 находится в GPFSEL0. Аналогично, контакт 17, 17/10 = 1, регистр выбора функции — GPFSEL1. И так далее.

Взятые вместе, уравнение paddr = bcm_gpio + BCM_GPFSEL0/4 + (pin/10) приводит к смещению регистра выбора функции, соответствующему заданному pin. Для контакта 17 это приведет к тому, что paddr будет логически указывать на GPFSEL1 по адресу 0x7E20 0004, где 7E будет адресом шины, 20004 будет смещением регистров GPIO плюс смещение GPFSEL1, 4, от начала набора регистров GPIO. Я говорю логически, потому что bcm_gpio — это смещение от адреса, возвращенного в результате операции mmap(). mmap() возвращает указатель на виртуальную память процесса, а 0x7E20 0004 — это адрес в пространстве Адреса шины ЦП VC BCM2835.

Строка 4 вычисляет положение в регистре выбора функции для заданного значения выбора функции вывода. Поскольку мы будем использовать сдвиг битов для установки значения выбора функции вывода, это местоположение становится количеством битов для SHIFT значения выбора функции вывода, как указано в параметре mode. При построении вычислений нам сначала нужно найти логическое расположение вывода, то есть, какая 3-битная ячейка в регистре выбора функции (напомним, что каждое значение выбора функции имеет длину 3 бита). Расчет для этого дается shift = (pin % 10) .... Для контакта 17 (pin % 17) = 7. Таким образом, 3-битная ячейка вывода 17 расположена по 7-му 3-битному смещению. Далее нам нужно найти фактическое смещение битов в регистре. Поскольку каждое значение выбора функции имеет длину 3 бита, граница каждого вывода кратна 3, следовательно, полный расчет shift = (pin % 10) * 3. Для контакта 17 это приводит к (17%10)*3, что равно 7*3, что приводит к абсолютному битовому смещению 21. Обращаясь к Техническому описанию периферийных устройств ввода-вывода BCM2835, раздел 6 на стр. 92, мы можем подтвердить, что смещение вывода 17 в регистре GPFSEL0 равно в битовых позициях с 21 по 23.

Теперь давайте посмотрим на строку 5. При установке подмножества битов в заданное значение мы хотим сохранить значения окружающих битов. Для этого используется маска. Маска содержит бит(ы), которые находятся в позиции бита в целевом значении, которое мы хотим изменить. Например, в битовом шаблоне 0101 1111, если мы хотим установить значение бита 6 от 1 до 0, нам нужно только определить битовую последовательность с 1 установленным битом. Чтобы создать наиболее общее решение, мы просто установим младший значащий бит (биты). Поскольку нам нужен только один набор битов, мы должны определить маску как шестнадцатеричное число 0x1, которое определяет битовый шаблон 0000 0001. Далее нам нужно сдвинуть этот битовый шаблон так, чтобы бит 1 был перемещен в правильную позицию. В нашем примере, поскольку мы хотим изменить бит 6, мы должны сдвинуть шаблон маски на 6 бит влево, например, newMask = 0000 0001 << 6. Это приводит к тому, что newMask равняется 0100 00000, что помещает бит 1 в позицию 6, как мы и хотели. Возможно, мы могли бы сразу определить маску как 0100 0000, но это не привело бы к общему решению, которое работало бы для любой маски, необходимой для установки бита(ов) в произвольную битовую позицию, например, биты 21-23.

Как указано в bcmfuncs.h, значение BCM_GPIO_FSEL_MASK равно 0x7 или 0000 0111, это связано с тем, что каждый шаблон выбора функции GPIO имеет длину 3 бита. Так что это полезный битовый шаблон для маскировки 3-битных последовательностей. Если мы сдвинем этот шаблон на SHIFT на 21, как вычислено в строке 4 выше, мы получим 32-битный шаблон, который выглядит так: 0000 0000 1110 0000 0000 0000 0000 0000. Маска теперь находится в битовых позициях 21-23, что является расположением шаблона выбора функции вывода 17 в регистре выбора функции GPFSEL1.

Таким образом, строка 5 создает маску, необходимую для установки шаблона выбора 3-битной функции, как указано в параметре mode в предоставленном параметре pin.

Строка 6 создает новое 3-битное значение, которое будет помещено в регистр GPFSEL1. Напомним, что GPFSEL1 — это 32-битный регистр. Чтобы использовать 3-битное значение, такое как mode, для установки 3-битной последовательности в произвольной позиции, например, биты 21-23, мы создаем маску значения, которая устанавливает биты в желаемой позиции в желаемое значение. Как и в случае с маской выше, наиболее общее решение указывает, что эти биты должны быть установлены, начиная с наименее значащей позиции. Допустим, в нашем примере мы хотим, чтобы контакт 17 был установлен на альтернативную функцию 1. Глядя на приведенные выше битовые комбинации, мы видим, что 3-битное значение для альтернативной функции 1 равно 101. Параметр mode будет содержать это значение. Чтобы установить вывод 17 на альтернативную функцию 1, нам нужно сдвинуть битовую комбинацию mode на 0000 0101 на 21 бит влево. Как и в строке 5, в строке 6 это делается в общем виде, value = mode << shift. Учитывая наше значение mode и вычисленное значение shift, мы получаем 32-битное value из 0000 0000 1010 0000 0000 0000 0000 0000.

Итак, теперь у нас есть 4 переменные, необходимые для вычисления нового значения регистра GPFSEL1: paddr, mode, mask и value.

Строка 7 вызывает функцию bcm_peri_set_bits() для завершения этой операции. Эта операция выполняется в отдельной функции, так как существуют другие значения регистров, требующие установки, как мы увидим позже.

bcm_peri_set_bits()

bcm_peri_set_bits() — это функция, которая устанавливает 32-битную последовательность v в новое значение value в позиции paddr, используя mask, как обсуждалось выше. Как и в случае с bcm_fsel(), эта функция делает довольно много всего за несколько строк кода.

Строка 3 использует bcm_peri_read() для чтения 32-битного значения v, расположенного по адресу paddr. Подробнее об этом позже.

Строка 4 устанавливает для v новое значение. Напомним, что при использовании маски можно установить подмножество битов, оставив остальные биты без изменений. Строка 4 делает это за 3 шага. Сначала выполняется побитовая операция И, &, между текущим значением v и дополнением к маске. Взяв пример из предыдущего раздела для BCM_GPIO_FSEL_MASK, его битовое значение, для краткости только битовые позиции 16 и 23, инвертируется с ... 1110 0000 ... на ... 0001 1111 .... Когда между дополненными mask и v как в (v & ~mask) происходит побитовое И, изменяются только биты 21-23 исходного значения v, а биты 21-23 сбрасываются на 0s. Остальные биты неизменны. Если биты 16-23 v изначально были установлены на ... 0100 1010 ..., новое значение битов 16-23 теперь будет выглядеть как ... 0000 1010 .... В нашем примере для контакта 17 его биты выбора функции были сброшены в нули. Обратите внимание, что биты в позициях с 16 по 20 не изменились.

Следующая операция (value & mask) сохраняет только значения битов в позициях 21-23 нового значения value, устанавливая остальные биты в 0. Биты 16-23 value равны ... 1010 0000 ..., что указывает на то, что контакт 17 должен быть установлен на альтернативную функцию 1. Результатом операции ... 1010 0000 ... & ... 1110 0000 ... является ... 1010 0000 ..., что и ожидается. Операция указана как (value & mask), потому что мы не можем заранее сказать, что только биты 21-23 должны быть сброшены в value. И value, и mask могут иметь биты, установленные в других позициях.

Собрав все это вместе, третий и последний шаг, (v & ~mask) | (value & mask) будет ИЛИ 2 результата, ... 0000 1010 ... | ... 1010 0000 ... что приведет к сбросу v на ... 1010 1010 .... Помните, что были изменены только биты 21-23. Это показано здесь битами 16-20, сохраняющими свои предыдущие значения. Точно так же другие биты v останутся без изменений.

Строка 5 устанавливает 32 бита с paddr на v через bcm_peri_write(). Об этом также позже.

bcm_peri_read()

bcm_peri_read() будет считывать 32 бита, начиная с paddr, и возвращать их вызывающей стороне в виде 32-битного значения. В отличие от bcm_fsel() и bcm_peri_set_bits() эта функция достаточно проста.

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

Строка 5 просто устанавливает возвращаемое значение ret для содержимого, расположенного по адресу paddr.

bcm_peri_write()

bcm_peri_write() запишет 32 бита, начиная с paddr, в значение, содержащееся в value. Как и bcm_peri_read(), эта функция довольно проста.

Строки 3 и 5 синхронизируют доступ (блокировку) к 32 битам, начиная с paddr.

Строка 4 устанавливает 32 бита, расположенные по адресу paddr, в value.

bcm_gpio_write()

bcm_gpio_write() записывает 32-битное value в указанное pin. Он использует 2 вспомогательные функции, bcm_gpio_set() и bcm_gpio_clr(). Параметр on используется для указания, следует ли установить или сбросить value для pin. Существует набор из 4 регистров GPIO, GPSET0 и GPSSET1, а также GPCLR0 и GPCLR1, которые определяют, является ли значение контакта ВЫСОКИМ или НИЗКИМ. ВЫСОКИЙ соответствует GPSSETn. НИЗКИЙ соответствует GPCLRN. Когда значение on равно 1, для регистра GPSSETn, связанного со значением pin, устанавливается значение 1. Если для on установлено значение 0, то для регистра GPCLRn, связанного со значением pin, устанавливается значение 1. Значения GPSSETn используются только при Функция ввода/вывода установлена ​​на ВЫХОД. Значения GPLRn также используются только тогда, когда функция ввода-вывода вывода установлена ​​на ВЫХОД. Значения 0 в этих регистрах игнорируются. Напомним, что BCM2835 позволяет указывать функции GPIO для 54 контактов (вместо ожидаемых 40). Поскольку для каждого вывода требуется 1 бит, чтобы указать, должен ли он быть установлен или очищен, всего требуется 54 бита. Это объясняет, почему для установки и очистки необходимы два 32-битных регистра.

Использование этих регистров более подробно описано в разделе 6, стр. 95, Информации о периферийных устройствах ввода-вывода BCM2835.

bcm_gpio_set() и bcm_gpio_clr()

bcm_gpio_set() и bcm_gpio_clr() - близнецы по функциональности. Они отличаются только тем, с какими регистрами они работают. *set() работает с регистрами GPSETn, а *clr() работает с регистрами GPLRn. Они отвечают за установку соответствующего смещения битов в соответствующем регистре для предоставленного аргумента pin. В этом разделе описывается только bcm_gpio_set(). За исключением используемого набора регистров, их реализации идентичны. На самом деле, добавив еще один параметр для указания начального смещения, их можно было бы свернуть в одну функцию.

В bcm_gpio_set() строка 3 вычисляет адрес paddr целевого регистра. bcm_gpio — начальное смещение регистров GPIO. BCM_GPSET0 — начальное смещение регистров установки и очистки. Напомним из обсуждения в разделе bcm_init() выше, что арифметика указателя используется для определения смещения памяти, используемого для данной операции. Вот почему в этой операции BCM_GPSET0 делится на 4. pin/32 используется для вычисления того, какой из регистров GP*n должен использоваться для данного pin. Напомним, что целочисленное деление всегда округляется в меньшую сторону. Таким образом, результатом pin 17/32 является 0, что указывает на то, что будет использоваться первый регистр GP*n. Это соответствует руководству по периферийным устройствам BCM2835 ARM. И 0 является правильным в расчете paddr из-за использования целочисленной арифметики.

Строка 4 вычисляет, какой размер shift необходим для установки правильного бита, связанного с pin в регистре GP*n. В нашем примере с контактом 17 17%32 равно 17, что соответствует 17-му биту в GPSSET0. Опять же, согласно руководству BCM2835 ARM Peripherals, это правильное смещение в правильном регистре.

Строка 5 затем использует bcm_peri_write() для сдвига 1, shift битов влево, чтобы записать правильное смещение в правильный регистр. Из расчета в строке 4 1 сдвигается на 17 бит влево.

bcm_close()

bcm_close освобождает все ресурсы и сбрасывает все смещения GPIO до их исходных значений, в данном случае MAP_FAILED. Что касается MAP_FAILED, это значение предотвращает дальнейшие операции с периферийными регистрами BCM2835.

Строка 3 освобождает память, зарезервированную операцией mapmem() в bcm_init().

Строки 4–14 сбрасывают все смещения регистров до значений по умолчанию.

Краткое содержание

В этой статье подробно рассматривается, как напрямую взаимодействовать с регистрами периферийных устройств BCM2835 без использования сторонней библиотеки, чтобы заставить светодиод мигать. Сначала был представлен обзор основных концепций периферийных устройств ARM BCM2835, включая типы функций ввода-вывода, которые работают на внешних устройствах/периферийных устройствах, как работает адресация BCM2835 в качестве предварительного условия для описания того, как взаимодействовать с регистрами BCM2835 для управления внешними устройствами ( как светодиод). Затем он описал лабораторную установку, необходимую для управления светодиодом. Затем он представил код вместе с пояснениями того, что делает код, используемый основной программой. В завершение был представлен код с пояснениями, который напрямую взаимодействует с регистрами BCM2835, связанными с различными типами периферийных устройств.

К этому моменту вы должны иметь хорошее представление о том, как напрямую запрограммировать BCM2835 для управления различными типами периферийных устройств, используя различные функции ввода-вывода, поддерживаемые BCM2835. Хотя детали низкого уровня для конкретной функции ввода-вывода, например, SPI, могут отличаться, механизм программирования BCM2835 одинаков.

Следите за моей предстоящей статьей под названием «Raspberry Pi GPIO — использование SPI для отображения буквенно-цифровых символов на модуле матричного дисплея MAX7219», которая, среди прочего, продемонстрирует, как управлять матричным светодиодным дисплеем, используя только низкоуровневый доступ. к регистрам BCM2835. Это хорошее дополнение к этой статье, поскольку в нем будет представлен новый набор периферийных регистров, используемых для программирования функции ввода-вывода SPI.

Отзывы об этой статье приветствуются.

Рекомендации