Stm32 абстрагируемся от регистров cmsis при настройке gpio

Сравнение 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:

  1. Request the GPIO:
  1. Configure the GPIO as output:
  1. Turn on the LED by setting the GPIO high:
  1. 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

edge (   mode )
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:

isr (   mode,
void(*)(void *)  fptr,
void *  args 
)
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:

isrExit ( )
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:

mode (   mode )
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:

dir (   dir )
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:

readDir ( )
inline

Read Gpio direction

Exceptions
std::runtime_error in case of failure
Returns
Result of operation

Here is the call graph for this function:

int read ( )
inline

Read value from Gpio

Returns
Gpio value

Here is the call graph for this function:

write ( int  value )
inline

Write value to Gpio

Parameters
value Value to write to Gpio
Returns
Result of operation

Here is the call graph for this function:

useMmap ( bool  enable )
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:

int getPin ( bool  raw = )
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:

inputMode (   mode )
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:

outputMode (   mode )
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 следует изучить схему. Её рекомендуется загрузить на компьютер, чтобы при необходимости можно было быстро обратиться к данной информации – заучить сразу расположение всех элементов не получится.

Оцените статью
Рейтинг автора
5
Материал подготовил
Илья Коршунов
Наш эксперт
Написано статей
134
Добавить комментарий