Содержание
Сравнение sysfs и uapi
С помощью нового драйвера рассмотрим отличия между двумя системами с точки зрения пользователя.
uapi
uapi
uapi
uapi
Polling on events
В документации ядра для sysfs указано использовать EPOLLPRI и EPOLLERR (или exceptfds для select), это в принципе характерно для любого вызова sysfs_notify необязательно именно для подсистемы gpio.
Для uapi достаточно EPOLLIN.
Читаем мы событие с временной меткой и типом GPIOEVENT_EVENT_RISING_EDGE или GPIOEVENT_EVENT_FALLING_EDGE.
EPOLLET для uapi работает согласно документации на epoll.
labels
Имя контакта gpioN к которому так все привыкли, вообще говоря, каноническим не является, а используется если контакту не было присвоено имя, например в Device Tree.
Попробуем gpio-mockup с опцией gpio_mockup_named_lines:
Как мы видим имя контакта приобрело вид gpio_chip_label-gpio_offset, но это справедливо только для драйвера gpio-mockup.
Способа «угадать» заранее, существует ли имя для контакта, не используя uapi не представляется возможным, а поиск экспортированной «именованной» линии затруднен, если имя заранее не известно (опять же если известно, то нам для однозначной идентификации необходимо имя и смещение на известном gpiochip).
uapi
Интерфейс uapi позволяет нам без инициализации видеть имена линий:
С соответствующим файлом device tree (пример взят из документации ядра):
Мы бы видели имя в struct gpioline_info для каждого контакта, к сожалению, мало кто именует контакты, даже для распостраненных SBC.
Теперь перечислим преимущества недоступные старому интерфейсу.
Основным преимуществом я считаю временную метку, которая присваивается событию в верхней половине обработчика прерывания. Что незаменимо для приложений, которым важным является точность измерения времени между событиями.
Если позволяет драйвер устройства, линия дополнительно может быть сконфигурирована как открытый коллектор (GPIOLINE_FLAG_OPEN_DRAIN) или открытый эммитер (GPIOLINE_FLAG_OPEN_SOURCE), данное нововведение как раз может быть легко перенесено в sysfs, но этого не будет так как Линус Ваерли против.
Так же новый api позволяет присваивать каждому контакту пользовательские ярлыки при инициализации в struct gpiohandle_request поле consumer_label.
И в заключение позволяет «читать» и «писать» сразу группу состояний для контактов.
Заключение по сравнению
Субъективно, uapi выглядит более громоздким, чем sysfs, но не стоит забывать, что сравнивали мы управление через стандартные утилиты GNU cat и echo и C код, если сравнивать C код для каждого из интерфейсов, получится приблизительно тоже самое по сложности и объему.
Важным моментом является, что в случае использование sysfs линия остается инициализированной пока пользователь не попросит обратного или до перезагрузки. uapi освобождает линию сразу после закрытия файлового дескриптора.
Создание веб-сервера
Теперь давайте настроим веб-сервер. Файл Node.js откроет запрошенный файл и вернет содержимое файла, а если что-то пойдет не так, он выдаст ошибку 404.
Создайте файл, набрав nano webserver.js и вставьте в него приведенный ниже код.
var Gpio = require('onoff').Gpio; //require onoff to control GPIO var LEDPin = new Gpio(4, 'out'); //declare GPIO4 an output var fs = require('fs'); //require filesystem to read html files var http = require('http').createServer(function handler(req, res) { //create server fs.readFile(__dirname + '/index.html', function (err, data) { //read html file if (err) { res.writeHead(500); return res.end('Error loading socket.io.html'); } res.writeHead(200); res.end(data); }); }); var io = require('socket.io')(http) //require socket.io module and pass the http object http.listen(8080); //listen to port 8080 io.sockets.on('connection', function (socket) {// WebSocket Connection var buttonState = 0; //variable to store button state socket.on('state', function (data) { //get button state from client buttonState = data; if (buttonState != LEDPin.readSync()) { //Change LED state if button state is changed LEDPin.writeSync(buttonState); //turn LED on or off } }); });
Мы создали как веб-сервер, так и HTML-файлы, поэтому пришло время запустить веб-сервер и управлять выводом GPIO Raspberry Pi.
Введите следующую команду в терминале, чтобы запустить веб-сервер:
node webserver.js
Затем перейдите в браузер и откройте веб-страницу, используя :8080
В моем случае это: 192.168.4.1:8080
На экране должны появиться две кнопки, и когда вы нажмете эти кнопки, светодиод, подключенный к GPIO4 Raspberry Pi, включится или выключится.
10.2. Input devices¶
Reading a button press in RPi.GPIO:
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) if not GPIO.input(4): print("button is pressed")
Reading a button press in GPIO Zero:
from gpiozero import Button btn = Button(4) if btn.is_pressed print("button is pressed")
Note that in the RPi.GPIO example, the button is set up with the option
which means “pull-up”, and therefore when the button is not
pressed, the pin is high. When the button is pressed, the pin goes low, so the
condition requires negation (). If the button was configured as
pull-down, the logic is reversed and the condition would become :
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(4, GPIO.IN, GPIO.PUD_DOWN) if GPIO.input(4): print("button is pressed")
In GPIO Zero, the default configuration for a button is pull-up, but this can
be configured at initialization, and the rest of the code stays the same:
from gpiozero import Button btn = Button(4, pull_up=False) if btn.is_pressed print("button is pressed")
RPi.GPIO also supports blocking edge detection.
Wait for a pull-up button to be pressed in RPi.GPIO:
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) GPIO.wait_for_edge(4, GPIO.FALLING): print("button was pressed")
The equivalent in GPIO Zero:
from gpiozero import Buttons btn = Button(4) btn.wait_for_press() print("button was pressed")
Again, if the button is pulled down, the logic is reversed. Instead of waiting
for a falling edge, we’re waiting for a rising edge:
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) GPIO.wait_for_edge(4, GPIO.FALLING): print("button was pressed")
Again, in GPIO Zero, the only difference is in the initialization:
from gpiozero import Buttons btn = Button(4, pull_up=False) btn.wait_for_press() print("button was pressed")
RPi.GPIO has threaded callbacks. You create a function (which must take one
argument), and pass it in to , along with the pin number
and the edge direction:
import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) def pressed(pin): print("button was pressed") def released(pin): print("button was released") GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) GPIO.add_event_detect(4, GPIO.FALLING, pressed) GPIO.add_event_detect(4, GPIO.RISING, released)
In GPIO Zero, you assign the and
properties to set up callbacks on those actions:
from gpiozero import Buttons def pressed(): print("button was pressed") def released(): print("button was released") btn = Button(4) btn.when_pressed = pressed btn.when_released = released
is also provided, where the length of time considered
a “hold” is configurable.
The callback functions don’t have to take any arguments, but if they take one,
the button object is passed in, allowing you to determine which button called
the function.
is the base class for input devices, and can be used in a
similar way to input devices in RPi.GPIO.
GPIO usage from user space
The GPIOs can be accessed from the sysfs. Refer to the Linux kernel documentation at Documentation/gpio/sysfs.txt.
Calculating the Linux GPIO number of a GPIO pin
For each GPIO controller entry on the device tree Linux creates an entry /sys/class/gpio/gpiochipN, where N is an integer number starting at 0, with the following read-only attributes:
- base: same as N, the first GPIO managed by this chip
- label: provided for diagnostics (not always unique)
- ngpio: the number of GPIOs this controller manages (from N to N + ngpio — 1)
GPIOs on the ConnectCore 6 system-on-module
Every GPIO port of the i.MX6 CPU is a different GPIO controller and thus has its own /sys/class/gpio/gpiochipN entry on the sysfs. The Dialog DA9063 PMIC chip also contains a GPIO controller and has its own gpiochipN entry.
On the default ConnectCore 6 system-on-module device tree, the i.MX6 CPU’s seven GPIO ports are probed first:
- PORT1: /sys/class/gpio/gpiochip0
- PORT2: /sys/class/gpio/gpiochip32
- PORT3: /sys/class/gpio/gpiochip64
- PORT4: /sys/class/gpio/gpiochip96
- PORT5: /sys/class/gpio/gpiochip128
- PORT6: /sys/class/gpio/gpiochip160
- PORT7: /sys/class/gpio/gpiochip192
The PMIC is probed second:
PMIC: /sys/class/gpio/gpiochip240
The Linux GPIO number for a certain GPIO pin can be determined by adding the GPIO pin index to the port base index. For instance:
- i.MX6 GPIO2_4 (port 2, pin 4) is: 32 + 4 = 36
- PMIC GPIO3 is: 240 + 3 = 243
Since the i.MX6 CPU has seven ports, all of which have 32 pins, the following formula also applies for i.MX6 CPU GPIOs (without requiring the user to know the GPIO base of each port):
LinuxGPIO_num = (<imx6_gpio_port> — 1) * 32 + <imx6_gpio_pin>
For example, i.MX6 GPIO2_4 (port 2, pin 4) translates to: (2 — 1) * 32 + 4 = 36
Note If your platform’s device tree defines additional GPIO controllers, the gpiochipN assigned to the i.MX6 and PMIC may be different, depending on the order in which Linux probes the various drivers.
Example write from sysfs
The ConnectCore 6 SBC contains three LEDs (GPIOs 34, 35, 36). To turn the LED connected to GPIO 34 on and off:
- Request the GPIO:
- Configure the GPIO as output:
- Turn on the LED by setting the GPIO high:
- Turn off the LED by setting the GPIO low:
Example application for sysfs access
Digi Embedded Yocto provides the example application gpio_sysfs_test for accessing the GPIOs via sysfs thorugh the package dey-examples-gpio-sysfs.
The gpio_sysfs_test application configures an input pin (preferably a push button) and output pin (preferably an LED) and toggles the output on each press of the push button. It also configures the input as interrupt to toggle the output on interrupt events.
Syntax
root@ccimx6sbc:~# gpio_sysfs_test Usage: gpio-sysfs-test <gpio_in> Where gpio_in is a pushbutton and gpio_out an optional LED
Note The ConnectCore 6 SBC does not have a push button connected to a GPIO. To run this test application on the ConnectCore 6 SBC you can use any of the GPIOs available at the GPIO expansion connector as input, and any of the user LEDs on the board as output.
Member Function Documentation
|
inline |
Set the edge mode for ISR
- Parameters
-
mode The edge mode to set
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Sets a callback to be called when pin value changes
- Parameters
-
mode The edge mode to set fptr Function pointer to function to be called when interrupt is triggered args Arguments passed to the interrupt handler (fptr)
- Returns
- Result of operation
Here is the call graph for this function:
Here is the caller graph for this function:
|
inline |
Exits callback — this call will not kill the isr thread immediately but only when it is out of it’s critical section
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Change Gpio mode
- Parameters
-
mode The mode to change the gpio into
- Returns
- Result of operation
Here is the call graph for this function:
Here is the caller graph for this function:
|
inline |
Change Gpio direction
- Parameters
-
dir The direction to change the gpio into
- Returns
- Result of operation
Here is the call graph for this function:
Here is the caller graph for this function:
|
inline |
Read Gpio direction
- Exceptions
-
std::runtime_error in case of failure
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Read value from Gpio
- Returns
- Gpio value
Here is the call graph for this function:
|
inline |
Write value to Gpio
- Parameters
-
value Value to write to Gpio
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Enable use of mmap i/o if available.
- Parameters
-
enable true to use mmap
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Get pin number of Gpio. If raw param is True will return the number as used within sysfs. Invalid will return -1.
- Parameters
-
raw (optional) get the raw gpio number.
- Returns
- Pin number
Here is the call graph for this function:
|
inline |
Change Gpio input mode
- Parameters
-
mode The mode to change the gpio input
- Returns
- Result of operation
Here is the call graph for this function:
|
inline |
Change Gpio output driver mode
- Parameters
-
mode Set output driver mode
- Returns
- Result of operation
Here is the call graph for this function:
Features
There are lots of GPIO modules available for node.js. Why use this one?
Performance
Most alternative GPIO modules use the slower file system interface.
How much faster? Here is a simple
test which calculates
how long it takes to switch a pin on and off 1 million times:
- rpi-gpio (using ): seconds
- rpio (using ): seconds
So rpio can be anywhere up to 1000x faster than the alternatives.
Hardware support
While provides a simple interface to GPIO, not all hardware features are
supported, and it’s not always possible to handle certain types of hardware,
especially when employing an asynchronous model. Using the
interface means rpio can support a lot more functionality:
-
rpio supports sub-millisecond access, with features to support multiple
reads/writes directly with hardware rather than being delayed by the event
loop. -
Output pins can be configured with a default state prior to being enabled,
required by some devices and not possible to configure via . -
Internal pullup/pulldown registers can be configured.
-
Hardware i²c, PWM, and SPI functions are supported.
Simple programming
rpio tries to make it simple to program devices, rather than having to jump
through hoops to support an asynchronous workflow. Some parts of rpio block,
but that is intentional in order to provide a simpler interface, as well as
being able to support time-sensitive devices.
The aim is to provide an interface familiar to Unix programmers, with the
performance to match.
Writing from CPU to GPIO
The most common way to get data out on the GPIO port is using the CPU to send
the data. Let’s do some measurements how the Pis perform here.
Direct Output Loop to GPIO
In this simplest way to control the output, we essentially
just write to the GPIO set and clear register in a tight loop:
// Pseudocode for (;;) { *gpio_set_register = (1<<TOGGLE_PIN); *gpio_clr_register = (1<<TOGGLE_PIN); }
Result
The resulting output wave on the Raspberry Pi 1 of 22.7Mhz, the Raspberry Pi 2
reaches 41.7Mhz and the Raspberry Pi 3 65.8 Mhz.
Raspberry Pi 1 | Raspberry Pi 2 | Raspberry Pi 3 | Raspberry Pi 4 |
---|---|---|---|
(about 131Mhz) |
The limited resolution in the 100ns range of the scope did not read the frequency correctly
for the Pi 3 (so it only shows 58.8Mhz above) but if we zoom in, we see the 65.8Mhz
Reading Word from memory, write masked set/clr
The most common way you’d probably send data to GPIO: you have an array of
32 bit data representing the bits to be written to GPIO and a mask that
defines which are the relevant bits in your application.
// Pseudocode uint32_t data; // Words to be written to GPIO const uint32_t mask = ...; // The GPIO pins used in the program. const uint32_t *start = data; const uint32_t *end = start + 256; for (const uint32_t *it = start; it < end; ++it) { if (( *it & mask) != ) *gpio_set_register = *it & mask; if ((~*it & mask) != ) *gpio_clr_register = ~*it & mask; }
Result
Raspberry Pi 2 and Pi 3 are unimpressed and output in the same speed as writing
directly, Raspberry Pi 1 takes a performance hit and drops to 14.7Mhz:
Raspberry Pi 1 | Raspberry Pi 2 | Raspberry Pi 3 | Raspberry Pi 4 |
---|---|---|---|
(about 131Mhz) |
Reading prepared set/clr from memory
This would be a bit more unusal way to prepare and write data: break out the
set and clr bits beforehand and store in memory before writing them to GPIO.
It uses twice as much memory per operation.
It does help the Raspberry Pi 1 to be as fast as possible writing from memory
though, while there is no additional advantage for the Raspberry Pi 2 or 3.
Primarily, this is a good preparation to understand the way we have to send data with DMA.
// Pseudocode struct GPIOData { uint32_t set; uint32_t clr; }; struct GPIOData data; // Preprocessed set/clr to be written to GPIO const struct GPIOData *start = data; const struct GPIOData *end = start + 256; for (const struct GPIOData *it = start; it < end; ++it) { *gpio_set_register = it->set; *gpio_clr_register = it->clr; }
Result
The Raspberry Pi 2 and Pi 3 have the same high speed as in the previous examples, but
Raspberry Pi 1 can digest the prepared data faster and gets up to 20.8Mhz
out of this (compared to the 14.7Mhz we got with masked writing):
Raspberry Pi 1 | Raspberry Pi 2 | Raspberry Pi 3 | Raspberry Pi 4 |
---|---|---|---|
(about 83Mhz) |
Reading prepared set/clr from UNCACHED memory
This next example is not useful in real life, but it helps to better
understand the performance impact of accessing memory that does not go
through a cache (L1 or L2).
The DMA subsystem, which we are going to explore in the next examples, has to
read from physical memory, as it cannot use the caches (or can it ? Somewhere
I read that it can make at least use of L2 cache ?).
The example is the same as before: reading pre-processed set/clr values from
memory and writing them to GPIO. Only the type of memory is different.
Result
The speed is significantly reduced — it is very slow to read from uncached
memory (a testament of how fast CPUs are these days or slow DRAM actually
is).
One interesting finding is, that the Raspberry Pi 2 and Pi 3 both are actually significantly
slower than the Raspberry Pi 1. Maybe the makers were relying more on various
caches and choose to equip the machine with slower memory to keep the price while
increasing memory ? At least the Pi 3 is faster than the 2, so the relative order there is
preserved.
Raspberry Pi 1 | Raspberry Pi 2 | Raspberry Pi 3 | Raspberry Pi 4 |
---|---|---|---|
(about 2.7Mhz) |
Распиновка DSI разъема дисплея
Display Serial Interface (DSI) — спецификация Mobile Industry Processor Interface (MIPI) Alliance. направленная на снижение затрат на дисплейную подсистему в мобильных устройствах. В основном она ориентирована на LCD и тому подобные технологии дисплея. Спецификация определяет последовательную шину и протокол связи между хостом (источник изображения) и устройством (получателем изображения).
Pin | Назначение |
---|---|
1 | DISP_GND |
2 | DISP_D1_N |
3 | DISP_D1_P |
4 | DISP_GND |
5 | DISP_CK_N |
6 | DISP_CK_P |
7 | DISP_GND |
8 | DISP_D0_N |
9 | DISP_D0_P |
10 | DISP_GND |
11 | DISP_SCL |
12 | DISP_SDA |
13 | DISP_GND |
14 | DISP_3V3 |
15 | DISP_3V3 |
4.4. Pin factories¶
An alternative (or additional) method of configuring gpiozero objects to use
remote pins is to create instances of
objects, and use them when
instantiating device objects. For example, with no environment variables set:
from gpiozero import LED from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep factory = PiGPIOFactory(host='192.168.1.3') led = LED(17, pin_factory=factory) while True led.on() sleep(1) led.off() sleep(1)
This allows devices on multiple Raspberry Pis to be used in the same script:
from gpiozero import LED from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep factory3 = PiGPIOFactory(host='192.168.1.3') factory4 = PiGPIOFactory(host='192.168.1.4') led_1 = LED(17, pin_factory=factory3) led_2 = LED(17, pin_factory=factory4) while True led_1.on() led_2.off() sleep(1) led_1.off() led_2.on() sleep(1)
You can, of course, continue to create gpiozero device objects as normal, and
create others using remote pins. For example, if run on a Raspberry Pi, the
following script will flash an LED on the controller Pi, and also on another Pi
on the network:
from gpiozero import LED from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep remote_factory = PiGPIOFactory(host='192.168.1.3') led_1 = LED(17) # local pin led_2 = LED(17, pin_factory=remote_factory) # remote pin while True led_1.on() led_2.off() sleep(1) led_1.off() led_2.on() sleep(1)
Alternatively, when run with the environment variables
set, the following
script will behave exactly the same as the previous one:
from gpiozero import LED from gpiozero.pins.rpigpio import RPiGPIOFactory from time import sleep local_factory = RPiGPIOFactory() led_1 = LED(17, pin_factory=local_factory) # local pin led_2 = LED(17) # remote pin while True led_1.on() led_2.off() sleep(1) led_1.off() led_2.on() sleep(1)
Of course, multiple IP addresses can be used:
from gpiozero import LED from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep factory3 = PiGPIOFactory(host='192.168.1.3') factory4 = PiGPIOFactory(host='192.168.1.4') led_1 = LED(17) # local pin led_2 = LED(17, pin_factory=factory3) # remote pin on one pi led_3 = LED(17, pin_factory=factory4) # remote pin on another pi while True led_1.on() led_2.off() led_3.on() sleep(1) led_1.off() led_2.on() led_3.off() sleep(1)
Note that these examples use the class, which takes a pin
argument to initialise. Some classes, particularly those representing HATs and
other add-on boards, do not require their pin numbers to be specified. However,
it is still possible to use remote pins with these devices, either using
environment variables, or the pin_factory keyword argument:
import gpiozero from gpiozero import TrafficHat from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep gpiozero.Device.pin_factory = PiGPIOFactory(host='192.168.1.3') th = TrafficHat() # traffic hat on 192.168.1.3 using remote pins
This also allows you to swap between two IP addresses and create instances of
multiple HATs connected to different Pis:
import gpiozero from gpiozero import TrafficHat from gpiozero.pins.pigpio import PiGPIOFactory from time import sleep remote_factory = PiGPIOFactory(host='192.168.1.3') th_1 = TrafficHat() # traffic hat using local pins th_2 = TrafficHat(pin_factory=remote_factory) # traffic hat on 192.168.1.3 using remote pins
You could even use a HAT which is not supported by GPIO Zero (such as the
Sense HAT) on one Pi, and use remote pins to control another over the
network:
from gpiozero import MotionSensor from gpiozero.pins.pigpio import PiGPIOFactory from sense_hat import SenseHat remote_factory = PiGPIOFactory(host='192.198.1.4') pir = MotionSensor(4, pin_factory=remote_factory) # remote motion sensor sense = SenseHat() # local sense hat while True pir.wait_for_motion() sense.show_message(sense.temperature)
Особенности GPIO «Малины»
В первую очередь необходимо рассмотреть ключевые особенности этого интерфейса. И самое главное в GPIO Raspberry Pi – это pings (пины). Именно они используются для связи одноплатника с периферией.
В совокупности у «Малины» есть 26 GPIO (портов), однако самих элементов больше:
- 2 из них отвечают за подачу напряжения в 5 Вольт;
- 2 – 3,3 Вольта;
- 8 применяются для заземления;
- 2 используются исключительно для подключения расширений.
Все они выстроены в 2 ряда. Если расположить плату горизонтально и так, чтобы интерфейс оказался вверху, то к первой паре элементов можно подключать (запитывать) устройства, требующие напряжения в 5 Вольт. Снизу, в свою очередь, находится 1 на 3,3, а второй такой же располагается в этом же ряду, примерно посередине – между 22 и 10 портами.
Чтобы ознакомиться с распиновкой GPIO Raspberry следует изучить схему. Её рекомендуется загрузить на компьютер, чтобы при необходимости можно было быстро обратиться к данной информации – заучить сразу расположение всех элементов не получится.