Gpio error 0025 что это

GPIO для чайников (часть 1) Что делать, когда нечего делать? Попробовать что-нибудь новое! Если вы приобрели Raspberry Pi просто ради любопытства, не отдавая себе отчёта в том, для чего он конкретно вам нужен, то наверняка с каждым днём вам становится всё труднее найти для него применение. Вы уже с ним вдоволь наигрались. Попробовали установку […]

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

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

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

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

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

Во-первых, нужно найти светодиод:

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

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

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

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

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

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

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

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

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

Так выглядит схема в сборе:

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

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

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

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

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

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

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

Мы же пока установим версию 1.17. Запускаем окно терминала и вводим туда команду: wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.17.tar.gz

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

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

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

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

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

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

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

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

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

Сохраняем нашу программу ctrl-o и выходим из текстового редактора ctrl-x. Теперь, если вы введёте команду ls, то увидите только что созданный файл GPIO-test.c. Чтобы этот файл превратился в работающую программу, его нужно скомпилировать. Пишем: gcc -o GPIO-test GPIO-test.c -lrt -lbcm2835 в этой строке: gcc- это имя компилятора; -o GPIO-test GPIO-test.c эта команда компилятору говорит о том, что требуется создать исполняемый файл с именем GPIO-test из текстового файла GPIO-test.c; -l (латинская л маленькая) bcm2835 говорит компилятору о том, что все неизвестные ему функции в нашей программе, он может найти в установленной библиотеке bcm2835. Если компилятор не выдал никаких сообщений, то значит, всё у нас получилось. Если сейчас дать команду ls, то мы увидим, что в директории появился ещё один файл GPIO-test, причём он отмечен зелёным цветом. Это говорит о том, что файл является исполняемой программой. Осталось нам его запустить, но перед этим ещё раз проверяем нашу схему со светодиодом, чтобы всё было собрано правильно и подключено к контактам Р1_01 и Р1_03 разъёма GPIO. Если ошибок не обнаружено, запускаем программу: sudo ./GPIO-test После этого светодиод должен загореться ровно на 1 секунду и погаснуть. Если всё так и произошло, то я вас поздравляю! Вы только что при помощи Raspberry Pi передали через порт GPIO команды светодиоду: включиться, гореть 1 секунду и выключиться.

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

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

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

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

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

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

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

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

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

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

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

b>return 0; — Выход из программы с кодом 0.

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

1. Инициализируем GPIO;

2. Устанавливаем режим для выбранного порта на Вывод;

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

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

Источник

Приехала из Китая флешка, якобы, на 32Гб. Реальных 6,7Гб.
Ни одна из прог не определяет чип и память:

GetFlashInfo:

ЦитироватьVolume: H:
Controller: ChipsBank
Possible Memory Chip(s): Not available
Flash ID: ADDE14AB 424A
Flash CE: 1
Firmware Date: 2015-04-27
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 33554432000 Bytes
Windows Disk Capacity:  33553379328 Bytes
Internal Tags: DGA7-8P3L
File System: NTFS
Relative Offset: 1024 KB
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: A0EF-07-99
Microsoft Windows 10 x64 Build 10586
Program Version: 8.4.0.588

ChipEasy_EN_V1.5.6.6

ЦитироватьLogical drive   : H:            Capacity:  31.2G
Device ID       : VID = 048D     PID = 1234
Device SN       : _
Device version  : 5.00

Device vendor   : General
Device model    : UDisk
Protocol        : USB2.0
Max power       : 100mA

Partition type  :                Device active   :
Aligned state   : Misaligned

Controller      : Chipsbank
Controller model: CBMeneral
FW Date         : UDis.  .

ChipGenius_v4_00_1024

ЦитироватьDescription: [H:]Запоминающее устройство для USB(General UDisk)
Device Type:  Mass Storage Device

Protocal Version: USB 2.00
Current Speed: High Speed
Max Current: 100mA

USB Device ID: VID = 048D PID = 1234
Serial Number: _

Device Vendor: General
Device Name: UDisk
Device Revision: 0100

Manufacturer: General
Product Model: UDisk
Product Revision: 5.00

Controller Part-Number: Unknown

Флешку вскрыл, контроллер бескорпусной, на чипе памяти всего три буквы JVC.
Если нужно, сфоткаю и выложу.

Что можете посоветовать для начала?


Попробуйте посмотреть отчёт GetFlashInfo, после передергивания флешки в USB-порту и суйте его в другой порт, может что измениться в данных…

Сначала подумал, может флешка зависла, а потом всё же нашел, что существует флеш-память такая, у вас что-то типа Hynix H27QCG8D2F5R 16nm MLC

16нм технология и то что чип не распознался, значит скорее всего, что это что-то очень новое.

Попробуйте последние две утилиты, может распознает флеш-диск.
http://www.usbdev.ru/files/chipsbank/cbm2099umptool/
http://www.usbdev.ru/files/chipsbank/cbm2099aptool/


Это я еще вчера пробовал, чип вроде как CBM2099, но уверенности нету.
CBM209X UMPToolV7000(2015-09-22) вообще не увидел флешку.
APToolV6009(2015-03-24) выдал GPIO Error.(0025)
APToolV6009(2015-06-15) и APToolV7000(2015-08-25) не распознали память «Unknown_flash,CE:1,ADDE14AB424AADDE Flash ID unknown.(0011)»

Как ещё можно считать инфу или фотя бы ID памяти, чтоб самому поискать в нете?


Так ID-памяти вы как раз считали и я в предыдущем посте указал что это за память (но у вас видимо даунгрейд вариант — мусорный)! А вот раз такая ошибка, следовательно флеш-память утилита не знает (наверное) и ничего с этим не сделать в них.

Я бы отложил и подождал, думаю что в течении полугода, решение в виде софта, будет доступно в сети.


А каким образом вы поняли какая память? Где в моих отчётах это написано?
Я нуб в этом, пока :D


Так вот она же — ADDE14AB 424A
Просто в текущую версию GetFlashInfo, не заложена ассоциация с Hynix H27QCG8D2F5R 16nm MLC.
Автору я уже отписал, в следующей версии, будет добавлено наверное.



Есть похожий пациент:
Controller: ChipsBank
Possible Memory Chip(s): Not available
Flash ID: 98DE88A3 7251
Flash CE: 1
Firmware Date: 2015-04-27
VID: 048D
PID: 1234

APTool V7000 (2015-08-25) на неё говорит: «Flash ID unknown«

Может, ему помочь, руками прописать FlashType ? Но — какой ?
Как зная FlashId определить FlashType ?

Или просто расслабиться, и ждать новой версии APTool ?


avkiev, а зачем его определять то? Там же в меню ручного выбора флеш рядом с названием имеется его FID, который вам известен (98DE88A37251)! Так по нему и ищите!

В утилите кажись он не заложен, но сам он исправен, т.к. такая микросхема существует, типа — Toshiba TC58TEG6TGLTA00 (15nm TLC)


Хмм… Может, мы про разные утилиты говорим ?
В моей версии APTool V7000 (2015-08-25) нет такого FlashType — «Toshiba TC58TEG6TGLTA00 (15nm TLC)»
и нет такого FID — 98DE88A37251

Как заставить APTool работать с этой флешкой ?


А я где-то сказал, что он там есть? Я говорю, что он там должен быть, если бы утилита поддерживала бы флеш!
Если вы не смогли найти в списке этот FID, то утилита его не поддерживает.


Ждать следующую версию, я правильно понял ?


Как я понял да, ждать придётся.
К примеру, в SMI-шных утилитах, такая флеш-память появилась ровно год назад.


Всё нормально, APTool_V7000(2016.04.11) перебил флешку на 6,7 гига, проверка H2tеstw прошла без ошибок.
Спасибо bigstan за помощь.

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


Добрый день! Подскажите, как Вы узнали модель микросхемы по Flash ID: 98DE88A3 7251 ?


SergiuS, ничего не нужно узнавать (т.к. не имеет никакого значения) и указывать в настройках тоже не стоит! Поддержка флеш-памяти проверяется поиском символов 98DE88A37251 в списке флеш (там в скобках указанно). А ещё проще нажать кнопку старт и сразу получить ответ, подходит или нет.


FD 8.8:

ЦитироватьVolume: E:
Controller: ChipsBank
Possible Memory Chip(s):
  Toshiba TC58NVG2S0FTA00
Memory Type: SLC
Flash ID: 98DC9026 7616
Flash CE: 1
Firmware Date: 2016-10-15
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 2097152000000 Bytes
Windows Disk Capacity:  2097134174208 Bytes
Internal Tags: DGA7-8F4J
File System: EXFAT
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: 6480-16-99
Microsoft Windows 7 SP1 x64 Build 7601

CG 4.17v:

ЦитироватьDescription: [E:]Çàïîìèíàþùåå óñòðîéñòâî äëÿ USB(General UDisk)
Device Type:        Mass Storage Device

Protocal Version: USB 2.00
Current Speed: High Speed
Max Current: 100mA

USB Device ID: VID = 048D PID = 1234
Serial Number: _

Device Vendor: General
Device Name: UDisk
Device Revision: 0100

Manufacturer: General
Product Model: UDisk
Product Revision: 5.00

Controller Vendor: SiliconGo [former Kingstor]
Controller Part-Number: p
Flash ID code:      70000500 — 4CE/Single Channel [MLC]

Видит только APToolV7000(2016-06-28) но ничего зделать не могу так как выдает ошибку «GPIO Error.(0025)».Подскажите плз как зделать реальный вес!? Ато h2testw проверяет 2ТБ 82 часа, както много времени и рисковано. Пробувал наугад менять вес через MyDisk и результат показал что там только 499мб (менял на 32Гб и через тест ). Какие будут варианты, помогите плз!?



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


korosta, разобрать и посмотреть, что на USB-контроллере написано нельзя? Монолитная флешка?


флешка монолитная, но немноо поковырвшись вытянул главную штукенсию. Там тупо 1 контролер (даже припоя не видно) и юсб резьем. На контролере написано только 512 и я так понял что ето 512 мб. но h2testw больше как  247 мб не записывает:) както так.


Данным софтом, да ещё при условии, что это монолит, вообще не вижу смысла, что-то пытаться сделать. Мы даже не знаем, Chipsbank там установлен или нет.
ИМХО — 200 метров, лучше выкинуть, перспектив я особо не вижу.


монолит красивый. оставил как брелок:)


ЕвгенКамчатка

  • гость
  • Записан

Всем привет.

Пришла флешка из китая, 32 ГБ.
H2test выдавала ошибку чтения на 22 гб.,
после некоторых (каких именно сам не понял) манипуляция флешка стала неформатируемой (Windows не удается завершить форматирование).

ЦитироватьVolume: H:
Controller: ChipsBank
Possible Memory Chip(s): Not available
Flash ID: 454C98A3 7651
Flash CE: 1
Firmware Date: 2016-10-15
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 32086425600 Bytes
Windows Disk Capacity:  0 Bytes
Internal Tags: DGA7-SF93
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: 782D-01-99
Microsoft Windows 8.1 x64 Build 9600
Program Version: 8.8.0.601

ЦитироватьLogical drive   : H:            Capacity:  0.0G
Device ID       : VID = 048D     PID = 1234
Device SN       : _
Device version  : 5.00

Device vendor   : General
Device model    : UDisk
Protocol        : USB2.0
Max power       : 100mA

Partition type  :                Device active   : no
Aligned state   : 0 KB, Have been Aligned

Controller      : Chipsbank
Controller model: CBMeneral
FW Date         : UDis.  .

UMPTool (CBM209X_V7000(2017-03-31)) не определяет

APTool (V7000(16-06-28) выдает это: «GPIO Error.(0025)»

Чем её можно пошевелить?


ЕвгенКамчатка, что там насчёт разобрать и посмотреть маркировки?


У меня, судя по всему, аналогичный контроллер. Штук десять программ от chipsbank перепробовал — даже не находят флешку.

ЦитироватьVolume: F:
Controller: ChipsBank
Possible Memory Chip(s):
  Intel 29F128G08CBEBB
  Intel 29F16B08LCMF3
  Intel 29F16B08ACMF1
Memory Type: MLC
Flash ID: 8984643C A5
Flash CE: 1
Firmware Date: 2016-10-15
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 134217728000 Bytes
Windows Disk Capacity:  34347155456 Bytes
Internal Tags: DGA7-8F4J
File System: FAT32
Relative Offset: 1024 KB
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: DD3B-01-99
Microsoft Windows 7 SP1 x64 Build 7601

ЦитироватьProtocal Version: USB 2.00
Current Speed: High Speed
Max Current: 100mA

USB Device ID: VID = 048D PID = 1234
Serial Number: _

Device Vendor: General
Device Name: UDisk
Device Revision: 0100

Manufacturer: General
Product Model: UDisk
Product Revision: 5.00

Controller Vendor: SiliconGo [former Kingstor]
Controller Part-Number: p
Flash ID code:      70000500 — 4CE/Single Channel [MLC]

ЦитироватьLogical drive   : F:            Capacity:  32.0G
Device ID       : VID = 048D     PID = 1234
Device SN       : _
Device version  : 5.00

Device vendor   : General
Device model    : UDisk
Protocol        : USB2.0
Max power       : 100mA

Partition type  : FAT32          Device active   : OK
Aligned state   : 1024 KB, Have been Aligned

Controller      : Chipsbank
Controller model: CBMeneral
FW Date         : UDis.  .

Дата прошивки 2016-10-15 у всех неопределяемых контроллеров в этой теме. Контроллер на плате в виде капли — соответственно маркировки никакой нет.


dimagsv, да я так и знал, что ничего путного от осмотра микросхемы не будет. У всех ситуация похожа, что наталкивает на мысли, о которых я уже где-то на форуме писал.

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


Прочёл тему http://www.usbdev.ru/f/index.php?topic=1778.0. Там одна из флешек с такой же особенностью как и у меня и у других авторов этой темы. Дата микропрограммы 2016-10-15 и не определяется утилитами APTool и UMPTool. Там помог перевод флешки в тестовый режим. Мне тоже это помогло. Закоротил ножки памяти, как указано в той теме на рисунке, вставил в usb, разомкнул ножки памяти. Утилита UMPTool CBM209X_V7000 (2017-03-31) сразу же определила флешку. Прошилось без проблем. Теперь флешка определяется утилитой UMPTool без необходимости перевода её в тестовый режим. Вот что теперь показывает usbflashinfo:

ЦитироватьVolume: D:
Controller: ChipsBank CBM2099E
Possible Memory Chip(s):
  Intel 29F128G08CBEBB
  Intel 29F16B08LCMF3
  Intel 29F16B08ACMF1
Memory Type: MLC
Flash ID: 8984643C A5
Flash CE: 1
Firmware Date: 2016-10-15
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 13463715840 Bytes
Windows Disk Capacity:  13450559488 Bytes
Internal Tags: DGA7-8F4L
File System: FAT32
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: 1039-02-99
Microsoft Windows 10 x64 Build 15063
Program Version: 8.8.0.601


Сергей 15101972

  • гость
  • Записан

Здравствуйте была флешка на 64 гб ,но показывала видео минуты 3 .               
Volume: I:
Controller: ChipsBank
Possible Memory Chip(s):
  Samsung K9F1208U0C
Memory Type: SLC
Flash ID: EC765A3F 74EC
Flash CE: 1
Firmware Date: 2016-10-15
VID: 048D
PID: 1234
Manufacturer: General
Product: UDisk           
Query Vendor ID: General
Query Product ID: UDisk           
Query Product Revision: 5.00
Physical Disk Capacity: 67108864000 Bytes
Windows Disk Capacity:  67087826944 Bytes
Internal Tags: DGA7-8F4J
File System: FAT32
Relative Offset: 31 KB
USB Version: 2.00
Declared Power: 100 mA
ContMeas ID: ECB3-04-99    после программы APToolV7100(2018-01-19) стала 63 мб — это нормально???!!!                                                                               


Сергей 15101972, угу, можете сами загуглить первые 4 байта вашего FID (EC765A3F).


  • USBDev.ru Forum

  • Choose UFD Controller [Выберите модель контроллера USB-флешки ]

  • ChipsBank Chips [Ремонт поддельных и подарочных флешек на контроллерах от ChipsBank]

  • Непонятная флешка на CHIPSBANK определить чип / CBM 2x9xx + ADDE14AB 424A

Быстрый ответ

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

Итак, давайте посмотрим на блок — схему порта ввода-вывода контроллера

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

Итак, помните в прошлом уроке мы настраивали чем у нас будет ножка контроллера — Входом, Выходом. Но это не все параметры. Ещё ножка может быть сконфигурирована как аналоговый входвыход для допустим работы с АЦП, или с ЦАП, у кого он есть на «борту»))

Давайте рассмотрим все режимы работы
1)Input floating — по простому это вход безо всяких подтяжек (Hi-Z состояние, плавающий). По простому вход у нас ни к чему не подключён (привет помехи))))
2)Input pull-up — режим входа, в котором он чрез подтягивающий резистор подключён к питанию (номинал резистора несколько десятков килоОм)

3)Input-pull-down — режим входа, в котором он чрез подтягивающий резистор подключён к земле (массе) (номинал резистора несколько десятков килоОм)

4)Analog — режим работы, который включаем если желаем работать с АЦП или ЦАП

5)Output open-drain with pull-up or pull-down capability — выход с «открытым коллектором»

6)Output push-pull with pull-up or pull-down capability — самый используемый режим, в котором наш пин может выдавать как лог. ноль так и лог. единицу (это будут работать те самые полевые тарнзисторы о которых писал выше)

7)Alternate function push-pull with pull-up or pull-down capability — альтернативная функция (двухтактный вывод)

8)Alternate function open-drain with pull-up or pull-down capability — альтернативная функция (открытый
коллектор)

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

Вот смотрите, кусочек кода, который отвечает за настройку параметров выхода

GPIO_InitStruct.Pin = GPIO_PIN_0; — данная строчка кода указывает что конфигурировать мы будем ножку 0
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; — Данная строчка указывает что режим работы — Вход
У данной строчки могут быть вот такие параметры
GPIO_MODE_INPUT
GPIO_MODE_OUTPUT_PP
GPIO_MODE_OUTPUT_OD
GPIO_MODE_AF_PP
GPIO_MODE_AF_OD

Следующая строчка GPIO_InitStruct.Pull = GPIO_PULLDOWN; — Указывает что у нас подтяжка к массе. У данной строчки ещё могут быть вот такие варианты
GPIO_NOPULL
GPIO_PULLUP
GPIO_PULLDOWN

Ну и последняя строчка указывает нам с каким портом нашего контроллера мы вообще только что разговаривали) — HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

Здесь мы рассмотрели настройку нашего пина к которому подключена кнопка. PA0.

А давайте теперь рассмотрим настройку нашего порта, куда подключены светодиоды
/*Configure GPIO pins : PD12 PD13 */
GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13; — эта строчка указывает какие пины настраиваем
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; — эта строка указыввает нам режим работы — ВЫХОД двухтактный (push-pull) . Возможные варианты конфигурации описаны чуть выше.
GPIO_InitStruct.Pull = GPIO_PULLUP; — данная штука указывает что включена подтяжка к питанию
Возможные варианты конфигурации описаны чуть выше.
GPIO_InitStruct.Speed = GPIO_SPEED_LOW; — данная строчка настраивает скорость работы выхода
Возможны вот такие варианты
GPIO_SPEED_LOW — низкая скорость 2MHz
GPIO_SPEED_MEDIUM — средняя скорость 25MHz
GPIO_SPEED_FAST — повышеная скорость 50MHz
GPIO_SPEED_HIGH -высокая скорость до 100MHz

Также, чтобы работал наш порт, и мы что то могли с ним делать — нам нужно включить тактирование порта. Так как мы создаём проект в CubeMX, то он за нас это всё делает, но на будущее, мало ли, может кто то захочет использовать старые библиотеки- не забывайте подавать тактирование на нужные вам порты.
В нашем случае тактирование наших портов включается вот таким образом
/* GPIO Ports Clock Enable */
__GPIOA_CLK_ENABLE();
__GPIOD_CLK_ENABLE();

Если поищем дальше, что обозначают эти строки то вот что найдём. Функция включения тактирования нашего порта A.
#define __HAL_RCC_GPIOA_CLK_ENABLE() do {
__IO uint32_t tmpreg;
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
/* Delay after an RCC peripheral clock enabling */
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
UNUSED(tmpreg);
} while(0)

Ну а теперь код, с помощью которого мы управляем нашими пинами.
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET); — сбрасывает пин в НОЛЬ
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET); — устанавливает пин в ЕДИНИЦУ
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9); — изменяет состояние пина на противоположное. Если было 0, то станет единица, и наоборот.

Ну и добавлю сюда ещё одну функцию — функция задержки Delay. Мы её часто использовали в CAVR, и тут она тоже есть. Задаётся она в милисекундах и выглядит вот так — HAL_Delay(100);
Это означает задержка в 100 милисекунд.

Можете теперь поиграться светодиодами на плате, выставив задержку и используя HAL_GPIO_TogglePin. Ваши светодиоды будут по очереди перемигиваться.

Жмём палец вверх, и читаем, читаем, читаем мануалы, и уже в голове придумываем что мы сделаем на STM32! STM — мечты сбываются)))

Ну и не забываем про хорошую музычку, да погромче! Пока писал — наслаждался вот этим шедевральным концертом. С ним как то и светодиоды по другому перемигиваются))

dotnet libgpiod

Когда заходит речь про программирование на C# .NET для одноплатных компьютеров, то разговоры крутятся только в основном вокруг Raspberry Pi на Windows IoT. А как же Banana/Orange/Rock/Nano Pi, Odroid, Pine64 и другие китайские одноплатные компьютеры работающие на Linux? Так давайте это исправим, установим .NET 5 на Banana Pi BPI-M64 (ARM64) и Cubietruck (ARM32), и будем управлять контактами GPIO из C# в Linux. В первой части серии постов, подключим светодиод и кнопку для отработки прерываний и рассмотрим библиотеку Libgpiod (спойлер, библиотеку так же можно использовать в C++, Python) для доступа к контактам GPIO.

Предисловие

Управление светодиодом и получение событий от кнопки будет реализовано через библиотеку Libgpiod, которая не является частью платформы .NET. Данная библиотека предоставляет доступ к GPIO из любого языка программирования, требуется лишь написание класса обертки.

Данный пост применим не только к платам Banana Pi BPI-M64 и Cubietruck, но и другим, основанных на процессоре ARM архитектуры armv71(32-bit) и aarch64 (64-bit).  На Banana Pi BPI-M64 (ARM64) и Cubietruck (ARM32) установлена ОС — Armbian версии 21.02.1, основанная на Ubuntu 18.04.5 LTS (Bionic Beaver), ядро Linux 5.10.12. uname: Linux bananapim64 5.10.12-sunxi64 #21.02.1 SMP Wed Feb 3 20:42:58 CET 2021 aarch64 aarch64 aarch64 GNU/Linux

Armbian — это самый популярный дистрибутив Linux, предназначенный для одноплатных компьютеров построенных на ARM процессоре, список поддерживаемых плат огромен: Orange Pi, Banana Pi, Odroid, Olimex, Cubietruck, Roseapple Pi, Pine64, NanoPi и др. Дистрибутив Armbain основан на Debian и Ubuntu. Из большого перечня поддерживаемых одноплатных компьютеров можно выбрать то решение, которое лучше всего походит для вашего IoT проекта, от максимально энергоэффективных до высокопроизводительных плат с NPU. И на базе всех этих одноплатных компьютеров, вы сможете реализовать свое решения на платформе .NET и работать с периферийными устройствами из кода на C#.

Что такое GPIO

GPIO (general-purpose input/output) — интерфейс ввода/вывода общего назначения. GPIO подключены напрямую к «процессору» SoC (System-on-a-Chip — Система на кристалле), и неправильное использование может вывести его из строя. Большинство одноплатных компьютеров, кроме обычных двунаправленных Input/Output портов, имеют один или более интерфейсов: UART, SPI, I²C/TWI, PWM (ШИМ), но не имеют ADC (АЦП). GPIO — порты обычно могут быть сконфигурированны на ввод или вывод (Input/Output), состояние по умолчанию обычно INPUT.

Некоторые «GPIO»-порты — являются просто питающими портами 3.3V, 5V и GND, они не связаны с SoC и не могут использоваться как либо еще.

Порты с альтернативной функцией — могут быть мультиплексированны с одним из соответствующих ему интерфейсов.

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

Работа с контактами GPIO осуществляется через виртуальную файловую систему sysfs. стандартный интерфейс для работы с контактами sysfs впервые появился с версии ядра 2.6.26, в Linux. Работа с GPIO проходит через каталог /sys/class/gpio путём обращения к файлам-устройствам.

К портам GPIO подключаются:

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

Для программирования GPIO существует несколько способов обращения:

  • Посредством файл-устройства GPIO;
  • Используя языки программирования:
    • Через прямое обращение к регистрам чипа;
    • Используя уже готовые библиотеки (libgpiod).

Одноплатный компьютер Banana Pi BPI-M64

Banana Pi BPI-M64 — это 64-битный четырехъядерный мини-одноплатный компьютер, поставляемый как решение с открытым исходном кодом. Ядром системы является процессор Allwinner A64 с 4-мя ядрами Cortex-A53 с частотой 1.2 ГГц. На плате размещено 2 ГБ DDR3 SDRAM 733МГц оперативной памяти и 8 ГБ eMMC.

На плате размещен 40-контактный совместимый с Raspberry Pi разъем, который содержит: GPIO (x28), Power (+5V, +3.3V and GND), UART, I2C, SPI. И 40-контактный интерфейс MIPI DSI.

dotnet libgpiod
Banana Pi BPI-M64 и 40-контактный разъем типа Raspberry Pi 3

Наличие 40-контактного разъема типа Raspberry Pi 3 GPIO, существенно облегчает подключение датчиков из-за совпадение назначение контактов с  Raspberry Pi 3. Не приходится гадать к какому контакту подключать тот или иной датчик. Указанные в посте датчики (светодиод и кнопка) подключенные к Banana Pi BPI-M64, можно подключать на те же самые контакты другого одноплатного компьютера, на котором тоже есть 40-контактный разъем, типа Raspberry Pi 3 (или к самой Raspberry Pi 3, разницы нет никакой). Единственное, необходимо изменить номера контактов (линий, ножка процессора) в программном коде, т.к. они зависят от используемого процессора. Но легко определяются но названию контакта. Плата Cubietruck (ARM32) приведена для проверки совместимости и работы кода на 32-разрядных ARM процессорах.

Banana Pi BPI-M64 GPIO Header Position
Позиция [1] 3V3 power соответствует позиции на плате со стрелочкой

Формула для вычисления номера GPIOXX
Для обращение к контактам из C# кода необходимо знать порядковый номер (линия, порт) физической ножки процессора SoC(для Allwinner). Эти данные в спецификациях отсутствую, т.к. порядковый номер получаем путем простого расчета. Например, из схемы возьмем 32-контакт на  разъеме типа Raspberry Pi. Название контакта PB7, для получения номера контакта на процессоре произведем расчет по формуле:
(позиция буквы в алфавите — 1) * 32 + позиция вывода. Первая буква не учитывается т.к. P — PORT, позиция буквы B в алфавите = 2, получаем (2-1) * 32 + 7 = 39. Физический номер контакта PB7 является номер 39. У каждого разработчика SoC может быть свой алгоритм расчета номера контактов, должен быть описан в Datasheet к процессору.

Banana Pi BPI-M64 GPIOXX
Контакт «PB7» на процессоре Allwiner A64, номер ножки — 39

Библиотеки .NET IoT

До того как напишем первую программу на C# по управления GPIO, необходимо рассмотреть пространство имен входящих в dotnet/iot. Все используемые библиотеки добавляются через Nuget пакеты. Подробно рассмотрим драйвера для получения доступа к контактам GPIO одноплатного компьютера. Код на C# взаимодействует с GPIO через специальный драйвер, который является абстракцией доступа к GPIO и позволяет переносить исходный код от одного одноплатного компьютера к другому, без изменений.

Пространства имен .NET IoT:

  • System.Device.Gpio. Пакет System.Device.Gpio поддерживает множество протоколов для взаимодействия с низкоуровневыми аппаратными интерфейсами:
    • General-purpose I/O (GPIO);
    • Inter-Integrated Circuit (I2C);
    • Serial Peripheral Interface (SPI);
    • Pulse Width Modulation (PWM);
    • Serial port.

  • Iot.Device.Bindings. Пакет Iot.Device.Bindings содержит:
    • Драйвера и обертки над System.Device.Gpio для различных устройств которые упрощают разработку приложений;
    • Дополнительные драйвера поддерживаемые сообществом (community-supported).

dotnet IoT Library
Стек библиотек .NET IoT

Рассмотрим первую программу типа Hello World, мигание светодиода (Blink an LED):

using System;
using System.Device.Gpio;
using System.Threading;

Console.WriteLine("Blinking LED. Press Ctrl+C to end.");
int pin = 18;
using var controller = new GpioController();
controller.OpenPin(pin, PinMode.Output);
bool ledOn = true;
while (true)
{
    controller.Write(pin, ((ledOn) ? PinValue.High : PinValue.Low));
    Thread.Sleep(1000);
    ledOn = !ledOn;
}

Разбор кода:

  • using System.Device.Gpio — пространство имен для использования контроллера GpioController доступа к аппаратным ресурсам;
  • using var controller = new GpioController() — создает экземпляр контроллера для управления контактами GPIO;
  • controller.OpenPin(pin, PinMode.Output) — инициализирует контакт pin = 18 на вывод, к 18 контакту подключен светодиод;
  • controller.Write(pin, ((ledOn)? PinValue.High: PinValue.Low)) — если ledOn принимает значение True, то PinValue.High присваивает высокое значение 18 контакту и светодиод загорается. На 18 контакт подается  напряжение в 3.3V. Если ledOn принимает значение False, то PinValue.Low присваивает низкое значение контакту 18 и светодиод гаснет. На 18 контакт подается напряжение в 0V (или минимальное пороговое для значения «0», может быть немного выше 0V).

Далее остается компиляция под ARM архитектуру: dotnet publish -r linux-arm или dotnet publish -r linux-arm64. Но так работает просто только для Raspberry Pi. При использование одноплатных компьютерах отличных от Raspberry Pi необходимо при инициализации GpioController выбирать драйвер доступа к GPIO.

Драйвера доступа к GPIO из .NET

Классы драйверов доступа к GPIO находятся в пространстве имен System.Device.Gpio.Drivers. Доступны  следующие драйвера-классы:

  • HummingBoardDriver — GPIO драйвер для платы HummingBoard на процессоре NXP i.MX 6 Arm Cortex A9;
  • LibGpiodDriver — этот драйвер использует библиотеку Libgpiod для получения доступа к портам GPIO, заменяет драйвер SysFsDriver. Библиотека Libgpiod может быть установлена на Linux и Armbian, не является аппаратно-зависимой, что позволяет ее использовать для различных одноплатных компьютерах ARM32 и ARM64;
  • RaspberryPi3Driver — GPIO драйвер для одноплатных компьютеров Raspberry Pi 3 или 4;
  • SysFsDriver — GPIO драйвер работающий поверх интерфейса SysFs для Linux и Unux систем, предоставляет существенно меньше возможностей, чем драйвер LibGpiodDriver, но не требует установки библиотеки Libgpiod. Тот случай, когда хочется просто попробовать помигать светодиодом из C# без дополнительных действий;
  • UnixDriver — базовый стандартный класс доступа к GPIO для Unix систем;
  • Windows10Driver — GPIO драйвер для ОС Windows 10 IoT. Из поддерживаемых плат только Raspberry Pi, весьма ограниченное применение.

В данном посте будет рассматриваться доступ к GPIO через драйвер LibGpiodDriver. Драйвер SysFsDriver базируется на устаревшем методе работы с GPIO через виртуальную файловую систему SysFs. Для решений IoT, SysFs не подходит по трем серьезным причинам:

  • Низкая скорость работы I/O;
  • Есть проблемы с безопасной работой с GPIO при совместном доступе;
  • При контейнеризации приложения на C# в контейнер придется пробрасывать много путей из файловой системы Linux, что создается дополнительные сложности. При использование библиотеки Libgpiod этого не требуется.

Библиотека Libgpiod предназначена для работы с GPIO не только из .NET кода, но и из Python, C++, и т.д. Поэтому ниже изложенная инструкция по установке библиотеки Libgpiod позволит разработчикам на Python реализовывать подобную функциональность, как и на C#. В состав пакета Libgpiod входят утилиты для работы с GPIO. До создание программы на C#, поработаем с датчиками через эти утилиты.

Схема подключения светодиода (LED) и кнопки

Подключать светодиод и кнопку будем на 40-контактный разъем совместимый с Raspberry Pi 3. Светодиод будет подключен на №33 контакт разъема, название контакта «PB4», номер линии — 36. Кнопка будет подключен на №35 контакт разъема, название контакта «PB6», номер линии — 38. Необходимо обратить внимание на поддержку прерывания на контакте «PB6» для кнопки. Поддержка прерывания необходима для исключения постоянного опроса линии с помощью CPU. На контакте «PB6» доступно прерывание «PB_EINT6», поэтому кнопку к этому контакту и подключим. Например, соседний контакт «PL12» не имеет прерывание, поэтому подключать кнопку к нему кнопку не будем. Если вы подключаете кнопку и резистор напрямую, то не забывайте в цепь добавить резистор для сопротивления для избежания выгорания порта!

libgpiod Armbian
Схема подключения светодиода (LED) и кнопки к 40-контактному разъему совместимый с Raspberry Pi 3

libgpiod Armbian
Схема назначения контактов к которым подключается светодиод (LED) и кнопка

Интерфейс GPIO ядра Linux

GPIO (General-Purpose Input/Output) является одним из наиболее часто используемых периферийных устройств во встраиваемых системах (embedded system) Linux.

Во внутренней архитектуре ядро Linux реализует доступ к GPIO через модель производитель/потребитель. Существуют драйверы, которые предоставляют доступ к линиям GPIO (драйверы контроллеров GPIO) и драйверы, которые используют линии GPIO (клавиатура, сенсорный экран, датчики и т. д.).

В ядре Linux система gpiolib занимается регистрацией и распределением GPIO. Эта структура доступна через API как для драйверов устройств, работающих в пространстве ядра (kernel space), так и для приложений пользовательского пространства (user space).

libgpiod Armbian
Схема работы gpiolib

Старый путь: использование виртуальной файловой системы sysfs для доступа к GPIO

До версии ядра Linux 4.7 для управления GPIO в пользовательском пространстве использовался интерфейс sysfs. Линии GPIO были доступны при экспорте по пути /sys/class/gpio. Так, например, для подачи сигнала «0» или «1» на линию GPIO, необходимо:

  1. Определить номер линии (или номер ножки процессора) GPIO;
  2. Экспортировать номер GPIO, записав его номер в /sys/class/gpio/export;
  3. Конфигурировать линию GPIO как вывод, указав это в /sys/class/gpio/gpioX/direction;
  4. Установить значение «1» или «0» для линии GPIO /sys/class/gpio/gpioX/value;

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

# echo 36 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio36/direction
# echo 1 > /sys/class/gpio/gpio36/value

Этот подход очень простой как и интерфейс sysfs, он неплохо работает, но имеет некоторые недостатки:

  1. Экспорт линии GPIO не связан с процессом, поэтому если процесс использующий линию GPIO аварийно завершит свою работу, то эта линия GPIO так и останется  экспортированной;
  2. Учитываю первый пункт возможен совместный доступ к одной и той же линии GPIO, что приведет к проблеме совместного доступа. Процесс не может «узнать» у ОС используется ли та или иная линия GPIO в настоящий момент;
  3. Для каждой линии GPIO приходится выполнять множество операций open()/read()/write()/close(), а так же указывать параметры (export, direction, value, и т.д.) используя методы работы с файлами. Это усложняет программный код;
  4. Невозможно включить/выключить сразу несколько линий GPIO одним вызовом;
  5. Процесс опроса для перехвата событий (прерываний от линий GPIO) ненадежен;
  6. Нет единого интерфейса (API) для конфигурирования линий GPIO;
  7. Номера, присвоенные линиям GPIO непостоянны, их приходится каждый раз экспортировать;
  8. Низкая скорость работы с линиями GPIO;

Новый путь: интерфейс chardev

Начиная с ядра Linux версии 4.8 интерфейс GPIO sysfs объявлен как «deprecated» и не рекомендуется к использованию. На замену sysfs появился новый API, основанный на символьных устройствах для доступа к линиям GPIO из пользовательского пространства.

Каждый контроллер GPIO (gpiochip) будет иметь символьное устройство в разделе /dev, и мы можем использовать файловые операции (open(), read(), write(), ioctl(), poll(), close()) для управления и взаимодействия с линиями GPIO. контроллеры GPIO доступны по путям /dev/gpiochipN или /sys/bus/gpiochipN, где N — порядковый номер чипа. Просмотр доступных контроллеров GPIO (gpiochip) на Banana Pi BPI-M64:

root@bananapim64:~# ls /dev/gpiochip*
/dev/gpiochip0  /dev/gpiochip1  /dev/gpiochip2

libgpiod Armbian
Стек работы библиотеки libgpiod

Несмотря на то, что новый API предотвращает управление линиями  GPIO с помощью стандартных инструментов командной строки, таких как echo и cat, он обладает весомыми преимуществами по сравнению с интерфейсом sysfs, а именно:

  • Выделение линий GPIO связано с процессом, который он его использует. При завершение процесса, так же в случае аварийного завершения, линии GPIO используемые процессом освобождаются автоматически;
  • Дополнительно, можно всегда определить какой процесс в данное время использует определенную линию GPIO;
  • Можно одновременно читать и писать в несколько линий GPIO одновременно;
  • Контроллеры GPIO и линии GPIO можно найти по названию;
  • Можно настроить состояние вывода контакта (open-source, open-drain и т. д.);
  • Процесс опроса для перехвата событий (прерывания от линий GPIO) надежен.

Библиотека libgpiod и инструменты управления GPIO

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

Libgpiod (Library General Purpose Input/Output device) предоставляет набор API для вызова из своих программ и несколько утилит для управления линиями GPIO из пользовательского режима.

В состав libgpiod входят следующие утилиты:

  • gpiodetect — выведет список всех чипов GPIO, их метки и количество линий;
  • gpioinfo — выведет информацию о линиях GPIO конкретного контроллера GPIO. В таблице вывода по колонкам будет указано: номер линии, название контакта, направление ввода/вывода, текущее состояние;
  • gpioget — считает текущее состояние линии GPIO;
  • gpioset — установит значение для линии GPIO;
  • gpiofind — выполняет поиск контроллера GPIO и линии по имени;
  • gpiomon — осуществляет мониторинг состояния линии GPIO и выводит значение при изменение состояния.

Например, следующая программа написанная на C использует libgpiod для чтения строки GPIO:

void main() {
	struct gpiod_chip *chip;
	struct gpiod_line *line;
	int req, value;

	chip = gpiod_chip_open("/dev/gpiochip0");
	if (!chip)
		return -1;

	line = gpiod_chip_get_line(chip, 3);
	if (!line) {
		gpiod_chip_close(chip);
		return -1;
	}

	req = gpiod_line_request_input(line, "gpio_state");
	if (req) {
		gpiod_chip_close(chip);
		return -1;
	}

	value = gpiod_line_get_value(line);

	printf("GPIO value is: %dn", value);

	gpiod_chip_close(chip);
}

Библиотеку можно вызывать так же и из кода на C++, Python, C#, и т.д.

Для управления линиями GPIO из терминала необходимо использовать инструменты командной строки, предоставляемые libgpiod. Библиотеку libgpiod и инструменты управления GPIO можно установить скомпилировать из исходного текста и установить.

Установка библиотеки libgpiod и инструментов управления GPIO

Репозитарий библиотеки libgpiod доступ по адресу libgpiod/libgpiod.git. В разделе Download опубликованы релизы библиотеки. На 28.04.2021 последний релиз: v1.6.3.

Библиотеку libgpiod можно установить из репозитария дистрибутива, но скорее всего будет доступна старая версия. Установка libgpiod:

$ sudo apt-get update
$ sudo apt-get install -y libgpiod-dev gpiod

Для установки последней актуальной версии необходимо выполнить скрипт установки, который возьмет последнюю версию библиотеки из исходного репозитария. В строке вызова скрипта установки setup-libgpiod-arm64.sh, в качестве первого параметра указать номер версии библиотеки (например: 1.6.3), второй параметр (необязательный) — папка установки скрипта. По умолчанию библиотека установится по пути: /usr/share/libgpiod.

Скрипт установки из исходного текста библиотеки libgpiod и утилит для ARM32/ARM64:

$ cd ~/
$ sudo apt-get update
$ sudo apt-get install -y curl 
$ curl -SL --output setup-libgpiod-armv7-and-arm64.sh https://raw.githubusercontent.com/devdotnetorg/dotnet-libgpiod-linux/master/setup-libgpiod-armv7-and-arm64.sh
$ chmod +x setup-libgpiod-armv7-and-arm64.sh
$ sudo ./setup-libgpiod-armv7-and-arm64.sh 1.6.3

Для удаления библиотеки выполнить скрипт: remove-libgpiod-armv7-and-arm64.sh

Если по итогу выполнения скрипта появится надпись «Successfully», то значит библиотека и утилиты успешно установлены. Дополнительно для проверки, можно вызвать команду с выводом номера версии библиотеки:

root@bananapim64:~# gpiodetect -v
gpiodetect (libgpiod) v1.6.3
Copyright (C) 2017-2018 Bartosz Golaszewski
License: LGPLv2.1
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Инструменты библиотеки libgpiod

Команда gpiodetect выведет список всех чипов GPIO, их метки и количество линий. Результат выполнения команды:

root@bananapim64:~# gpiodetect
gpiochip0 [1f02c00.pinctrl] (32 lines)
gpiochip1 [1c20800.pinctrl] (256 lines)
gpiochip2 [axp20x-gpio] (2 lines)

gpiochip0 и gpiochip1, это чипы входящие в состав SoC Allwinner A64. gpiochip1 имеет выход на 40-контактный разъем совместимый с Raspberry Pi. Чип gpiochip2 — отдельная микросхема управления электропитанием axp209 подключенная по интерфейсу I2C.

Для вывод справки к вызываемой команде необходимо добавлять параметр «—help». Вызов справки для команды gpiodetect. Результат выполнения команды:

root@bananapim64:~# gpiodetect --help
Usage: gpiodetect [OPTIONS]

List all GPIO chips, print their labels and number of GPIO lines.

Options:
  -h, --help:           display this message and exit
  -v, --version:        display the version and exit

Команда gpioinfo выведет информацию о линиях GPIO конкретного контроллера GPIO (или всех контроллеров GPIO, если они не указаны). Результат выполнения команды:

root@bananapim64:~# gpioinfo 1
gpiochip1 - 256 lines:
        line   0:      unnamed       unused   input  active-high
...
        line  64:      unnamed         "dc"  output  active-high [used]
...
        line  68:      unnamed "backlightlcdtft" output active-high [used]
...
        line  96:      unnamed   "spi0 CS0"  output   active-low [used]
        line  97:      unnamed       unused   input  active-high
        line  98:      unnamed       unused   input  active-high
        line  99:      unnamed       unused   input  active-high
        line 100:      unnamed      "reset"  output   active-low [used]
...
        line 120:      unnamed "bananapi-m64:red:pwr" output active-high [used]
...
        line 254:      unnamed       unused   input  active-high
        line 255:      unnamed       unused   input  active-high

В таблице по колонкам указано: номер линии, название контакта, направление ввода/вывода, текущее состояние. Сейчас к Banana Pi BPI-M64 подключен LCD экран ILI9341 на SPI интерфейсе, для подключения используется вариант с управляемой подсветкой, файл DTS sun50i-a64-spi-ili9341-backlight-on-off.dts. В DTS файле контакт «PC4» GPIO68 обозначен для управления подсветкой, название «backlightlcdtft». Соответственно в выводе команды, указан номер линии 68, название «backlightlcdtft», направление — вывод, текущее состояние — active-high (включено).

Команда gpioset установит значение для линии GPIO. Например, следующая команда попытается выключить подсветку на LCD ILI9341. Команда: gpioset 1 68=0, где 1 — gpiochip1, 68 — номер линии(контакта), 0 — логическое значение, может быть «0» или «1». Результат выполнения команды:

root@bananapim64:~# gpioset 1 68=0
gpioset: error setting the GPIO line values: Device or resource busy
root@bananapim64:~#

В результате мы получим ошибку — линия занята, т.к. данная линия занята драйвером «gpio-backlight».

Попробуем включить светодиод на линии 36, название «PB4», номер контакта на 40-контактном разъеме (совместимый с Raspberry Pi) — №33. Результат выполнения команды:

root@bananapim64:~# gpioset 1 36=1

В результате выполнения команды, светодиод включится.

Команда gpioget считывает текущее состояние линии GPIO. Результат выполнения команды:

root@bananapim64:~# gpioget 1 36
1

Получили значение «1», т.к. до этого включили светодиод командой gpioset.

Команда gpiomon будет осуществлять мониторинг состояния линии GPIO и выводить значение при изменение состояния. Будем мониторить состояние кнопки, которая подключена на линию 38, название «PB4», номер контакта на 40-контактном разъеме (совместимый с Raspberry Pi) №35. Команда: gpiomon 1 38, где 1 — gpiochip1, 38 — номер линии (контакта). Результат выполнения команды:

root@bananapim64:~# gpiomon 1 38
event:  RISING EDGE offset: 38 timestamp: [     122.943878429]
event: FALLING EDGE offset: 38 timestamp: [     132.286218099]
event:  RISING EDGE offset: 38 timestamp: [     137.639045559]
event: FALLING EDGE offset: 38 timestamp: [     138.917400584]

Кнопка несколько раз нажималась. RISING — повышение, изменение напряжения с 0V до 3.3V, кнопка нажата и удерживается состояние. FALLING — понижение, изменение напряжения с 3.3V до 0V, происходит отпускание кнопки, и кнопка переходит в состояние «не нажата».

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

Установка .NET 5.0 для ARM

Одно из лучших нововведений в .NET 5.0 стало увеличение производительности для архитектуры ARM64. Поэтому переход на новую версию не только увеличит производительность решения на базе ARM64, но и увеличит время автономной работы в случае  использования аккумуляторной батареи.

Определение архитектуры ARM32 и ARM64 для SoC

.NET 5 устанавливается на одноплатный компьютер в соответствие с архитектурой SoC:

  • ARM32, ARMv7, aarch32, armhf — 32-разрядная архитектура ARM. Первые процессоры ARM для встраиваемых систем разрабатывались именно на этой архитектуре. По заявлению компании ARM Holding, в 2022 поддержка 32-битных платформ прекратится, и будет поддерживаться только 64-битная архитектура. Это означает, что компания не будет поддерживать разработку ПО для 32-битных систем. Если конечный производитель устройства пожелает установить 32-битную ОС, то ему придется самостоятельно заняться портированием драйверов с 64-битной архитектуры на 32-битную.
  • ARM64, ARMv8, aarch64 — 64-разрядная архитектура ARM. Ядра Cortex-A53 и Cortex-A57, поддерживающие ARMv8, были представлены компанией ARM Holding 30 октября 2012 года.

Плата Banana Pi BPI-M64 построена на основе процессора Allwinner A64, содержит в себе 64-битные ядра Cortex-A53, поэтому поддерживает 64-разрядные приложения. Для платы Banana Pi BPI-M64 используется 64-разрядный образ ОС Armbian, поэтому на плату будем устанавливать .NET для 64-разрядных систем ARM.

Плата Cubietruck построена на основе процессора Allwinner A20 содержит в себе 32-битные ядра Cortex-A7, поэтому поддерживает только 32-разрядные приложения. Соответственно на плату устанавливается .NET для 32-разрядных систем.

Если вы не знаете какую версию .NET установить на одноплатный компьютер, то необходимо выполнить команду для получения информации об архитектуре системы: uname -m.

Выполним команду на  Banana Pi BPI-M64:

root@bananapim64:~# uname -m
aarch64

Строка aarch64 говорит о 64-разрядной архитектуре ARM64, ARMv8, aarch64, поэтому установка .NET для 64-х разрядных ARM систем.

Выполним команду на  Cubietruck:

root@cubietruck:~# uname -m
armv7l

Строка armv7l говорит о 32-разрядной архитектуре ARM32, ARMv7, aarch32, armhf, поэтому установка .NET для 32-разрядных ARM систем.

Редакции .NET 5.0 на ARM

.NET 5.0 можно устанавливать в трех редакциях:

  • .NET Runtime — содержит только компоненты, необходимые для запуска консольного приложения.
  • ASP.NET Core Runtime — предназначен для запуска ASP.NET Core приложений, так же включает в себя .NET Runtime для запуска консольных приложений.
  • SDK — включает в себя .NET Runtime, ASP.NET Core Runtime и .NET Desktop Runtime. Позволяет кроме запуска приложений, компилировать исходный код на языках C# 9.0, F# 5.0, Visual Basic 15.9.

Для запуска .NET программ достаточно установки редакции .NET Runtime, т.к. компиляция проекта будет на компьютере x86.

Загрузить .NET с сайта Microsoft можно по ссылке Download .NET 5.0.

Установка .NET Runtime

На странице Download .NET 5.0. можно узнать текущую актуальную версию .NET. В первой колонке Release information будет указана версия: v5.0.5 Released 2021-04-06. Версия номер: 5.0.5. В случае выхода более новый версии .NET, ниже в скрипте в строке export DOTNET_VERSION=5.0.5, нужно будет заменить номер версии на последний. Выполним скрипт установки, в зависимости от разрядности системы ARM32 (Cubietruck) или ARM64(Banana Pi BPI-M64):

ARM64

$ cd ~/
$ apt-get update && apt-get install -y curl
$ export DOTNET_VERSION=5.0.5
$ curl -SL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz 
&& mkdir -p /usr/share/dotnet 
&& tar -ozxf dotnet.tar.gz -C /usr/share/dotnet 
&& rm dotnet.tar.gz
$ ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

ARM32

$ cd ~/
$ apt-get update && apt-get install -y curl
$ export DOTNET_VERSION=5.0.5
$ curl -SL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm.tar.gz 
&& mkdir -p /usr/share/dotnet 
&& tar -ozxf dotnet.tar.gz -C /usr/share/dotnet 
&& rm dotnet.tar.gz
$ ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

Проверим запуск .NET, командой (результат одинаков для Banana Pi BPI-M64 и Cubietruck): dotnet —info

root@bananapim64:~# dotnet --info
Host (useful for support):
  Version: 5.0.5
  Commit:  2f740adc14
.NET SDKs installed:
  No SDKs were found.
.NET runtimes installed:
  Microsoft.NETCore.App 5.0.5 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

.NET установлен в системе, для запуска приложений в Linux необходимо воспользоваться командой: dotnet ConsoleApp1.dll

Обновление .NET 5.0

При выходе новых версий .NET необходимо сделать следующее:

  1. Удалить папку /usr/share/dotnet/
  2. Выполнить скрипт установки, указав новую версию .NET в строке export: DOTNET_VERSION=5.0.5. Номер последней версии .NET можно посмотреть на странице Download .NET 5.0. Строку скрипта создания символической ссылки выполнять не надо: ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

Удаленная отладка приложения на .NET 5.0 в Visual Studio Code для ARM

Удаленная отладка в Visual Studio Code позволяет в интерактивном режиме видеть ошибки и просматривать состояние переменных, без необходимости постоянного ручного переноса приложения на одноплатный компьютер, что существенно облегчает разработку. Бинарные файлы копируются в автоматическом режиме с помощью утилиты Rsync. Для работы с GPIO, настройка удаленной отладки не является обязательной задачей. Более подробно можно почитать в публикации Удаленная отладка приложения на .NET 5.0 в Visual Studio Code для ARM на примере Banana Pi BPI-M64 и Cubietruck (Armbian, Linux).

Создание первого приложения для управления (вкл/выкл светодиода) GPIO на C#, аналог утилиты gpioset

Поздравляю тебя %habrauser%! Мы уже подходим к финалу, осталось буквально чуть-чуть. Разрабатывать и компилировать приложение будем на x86 компьютере в в Visual Studio Code. Находясь в этой точке,  подразумевается, что на одноплатном компьютере уже установлена платформа .NET 5 и библиотека Libgpiod, а на компьютере x86 .NET 5 и Visual Studio Code. Итак приступаем:

Шаг 1 — Создание приложения dotnet-gpioset

Действия выполняются на x86 компьютере. В командной строке создаем проект с названием dotnet-gpioset: dotnet new console -o dotnet-gpioset, где dotnet-gpioset — название нового проекта. Результат выполнения команды:

D:AntonProjects>dotnet new console -o dotnet-gpioset
Getting ready...
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on dotnet-gpiosetdotnet-gpioset.csproj...
  Определение проектов для восстановления...
  Восстановлен D:AntonProjectsdotnet-gpiosetdotnet-gpioset.csproj (за 68 ms).
Restore succeeded.

После выполнения команды будет создана папка Projectsdotnet-gpioset, в этой папке будет расположен наш проект: папка — obj, файл программы — Program.cs и файл проекта — dotnet-gpioset.csproj.

Шаг 2 — Установка расширения C# for Visual Studio Code (powered by OmniSharp) для Visual Studio Code

Запустим Visual Studio Code и установим расширение C# for Visual Studio Code (powered by OmniSharp), для возможности работы с кодом на C#. Для этого нажмем на закладке: 1. Extensions, затем 2. в поле ввода напишем название расширения C# for Visual Studio Code, выберем пункт 3. C# for Visual Studio Code (powered by OmniSharp). 4. Перейдем на страницу описание расширения и нажмем на кнопку Install.

.NET Visual Studio Code ARM
C# for Visual Studio Code (powered by OmniSharp)

После установки можно выполнить настройку расширения.

.NET Visual Studio Code ARM
Настройка расширения C# for Visual Studio Code

После установки расширения, перезапустим Visual Studio Code.

Шаг 3 — Открытие проекта в Visual Studio Code и добавление NuGet пакетов

Откроем проект в Visual Studio Code. Меню: File =>Open Folder, и выберем папку с проектом Projectsdotnet-gpioset

dotnet libgpiod
Проект в Visual Studio Code

Откроем файл dotnet-gpioset.csproj, убедимся что версия .NET выставлена верно, должно быть следующее содержание:

dotnet libgpiod
Содержание файла dotnet-gpioset.csproj

NuGet пакеты можно добавить через командную строку или расширение NuGet Package Manager. Установим данное расширение, и добавим пакеты: Iot.Device.Bindings и System.Device.Gpio. Для этого нажмем комбинацию Ctrl+Shift+P, затем в поле введем: Nuget, выберем Nuget Packet Managet: Add Package.

dotnet libgpiod
Запуск расширения NuGet Package Manager

В поле ввода укажем название пакета Iot.Device.Bindings, нажмем Enter, затем выберем версию 1.4.0 и нажмем Enter. Так же сделать и для пакета System.Device.Gpio. В результате добавление пакетов, содержимое файла dotnet-gpioset.csproj должно быть следующим:

dotnet libgpiod
Содержание файла dotnet-gpioset.csproj

Шаг 4 — Добавление обработки аргументов в код

Утилита dotnet-gpioset как и оригинальная gpioset будет принимать на вход точно такие же аргументы. Вызов: dotnet-gpioset 1 36=1, включит светодиод на gpiochipX — 1, номер линии — 36, значение — 1. В режиме отладки будут заданы значения  по умолчанию int_gpiochip=1, int_pin=36, pin_value = PinValue.High. Подключим пространство имен  System.Device.Gpio для использование структуры PinValue.

Обработка входящих аргументов:

static void Main(string[] args)
{
  //run: dotnet-gpioset 1 36=1
  //-----------------------------------------------                        
  int? int_gpiochip=null,int_pin=null;
  PinValue? pin_value=null;
  
  #if DEBUG
    Console.WriteLine("Debug version");
    int_gpiochip=1;
    int_pin=36;
    pin_value = PinValue.High;
  #endif
	
  if (args.Length==2)
    {
      //Read args
      if (int.TryParse(args[0], out int output)) int_gpiochip = output;
      Regex r = new Regex(@"d+=d+");//36=1
      if (r.IsMatch(args[1])) //check: 36=1
        {
          var i = args[1].Split("=");
          if (int.TryParse(i[0], out output)) int_pin = output;
          if (int.TryParse(i[1], out output))
            {
              pin_value=(output != 0) ? PinValue.High : PinValue.Low;                             
            }
        }  
    }	
  Console.WriteLine($"Args gpiochip={int_gpiochip}, pin={int_pin}, value={pin_value}");
  //next code
  Console.WriteLine("Hello World!");
}

Запускаем выполнение кода для проверки, меню Run => Start Debugging, все работает отлично!

Загружено "C:Program FilesdotnetsharedMicrosoft.NETCore.App5.0.5System.Text.Encoding.Extensions.dll". Загрузка символов пропущена. Модуль оптимизирован, включен параметр отладчика "Только мой код".
Debug version
Args gpiochip=1, pin=36, value=High
Hello World!
Программа "[8528] dotnet-gpioset.dll" завершилась с кодом 0 (0x0).

Шаг 5 — Добавление контроллера управления GPIO c драйвером LibGpiodDriver

Для управления GPIO необходимо создать объект GpioController и указать драйвер LibGpiodDriver, для этого добавим пространство имен System.Device.Gpio.Drivers.

Добавление контроллера:

//next code
GpioController controller;
var drvGpio = new LibGpiodDriver(int_gpiochip.Value);            
controller = new GpioController(PinNumberingScheme.Logical, drvGpio);

Описание кода:

  • GpioController — класс контроллера для управления контактами GPIO;
  • LibGpiodDriver(int_gpiochip.Value) — драйвер обертки библиотеки Libgpiod, в качестве аргумента указываем номер gpiochip;
  • GpioController(PinNumberingScheme.Logical, drvGpio) — инициализация контроллера, PinNumberingScheme.Logical — формат указания контактов. Есть два варианта, по названию контакта или по его номеру. Но т.к. названия контактов не заданы, то обращение будет только по номеру.

Шаг 6 — Управление контактом GPIO

Добавление кода для задания значения контакту:

//set value            
if(!controller.IsPinOpen(int_pin.Value))
  {
    controller.OpenPin(int_pin.Value,PinMode.Output);
    controller.Write(int_pin.Value,pin_value.Value);                    
  } 

Описание кода:

  • controller.IsPinOpen — проверка открытия контакта, может быть занят или недоступен;
  • controller.OpenPin — открытие контакта и задание ему режима работы, PinMode.Output на вывод;
  • controller.Write(int_pin.Value,pin_value.Value) — выставление контакту int_pin значение pin_value.

Шаг 7 — Публикация для архитектуры ARM

Открыть командную строку, и перейти в папку Projectsdotnet-gpioset.

Для ARM32 выполнить команду:

  • параметр —runtime — задает архитектуру выполнения программы (берется из списка  Runtime Identifiers (RIDs));
  • параметр —self-contained — указывает на необходимость добавление в каталог всех зависимых сборок .NET, при выставление значение в False, копируются только дополнительные сборки не входящие в .NET Runtime (в данном случае будут скопированы сборки из дополнительных NuGet пакетов).

dotnet publish dotnet-gpioset.csproj --configuration Release --runtime linux-arm --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: Projectsdotnet-gpiosetbinReleasenet5.0linux-armpublish.

Для ARM64 выполнить команду:

dotnet publish dotnet-gpioset.csproj --configuration Release --runtime linux-arm64 --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: Projectsdotnet-gpiosetbinReleasenet5.0linux-arm64publish.

Шаг 8 — Перенос папки publish

Содержимое папки publish необходимо перенести в домашний каталог Linux пользователя на одноплатном компьютере. Это можно сделать используя терминал MobaXterm.

Шаг 9 — Запуск dotnet-gpioset на одноплатном компьютере

Содержимое папки publish было скопировано в папку /root/publish-dotnet-gpioset. Исполняемым файлом будет файл с расширением *.dll. В самом начале, светодиод был подключен на контакт №33, 40-контактного разъема совместимого с Raspberry P, название контакта «PB4», номер линии — 36. Поэтому в качестве аргумента номера контакта указываем — 36. Для запуска программы необходимо выполнить команду:

dotnet dotnet-gpioset.dll 1 36=1

Результат выполнения команды:

root@bananapim64:~# cd /root/publish-dotnet-gpioset
root@bananapim64:~/publish-dotnet-gpioset# dotnet dotnet-gpioset.dll 1 36=1
Args gpiochip=1, pin=36, value=High
OK

Светодиод включился!

Проект доступен на GitHub dotnet-gpioset.

Создание приложения обработки прерывания от кнопки

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

Светодиод подключен контакту с номером — 36. Кнопка подключена на контакт с номером — 38. Итак приступаем:

Шаг 1 — Создание приложения dotnet-led-button

Действия выполняются на x86 компьютере. В командной строке создаем проект с названием dotnet-led-button: dotnet new console -o dotnet-led-button, где dotnet-led-button — название нового проекта.

D:AntonProjects>dotnet new console -o dotnet-led-button
Getting ready...
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on dotnet-led-buttondotnet-led-button.csproj...
  Определение проектов для восстановления...
  Восстановлен D:AntonProjectsdotnet-led-buttondotnet-led-button.csproj (за
76 ms).
Restore succeeded.

После выполнения команды будет создана папка с файлами проекта Projectsdotnet-led-button.

Шаг 2 — Открытие проекта в Visual Studio Code и добавление NuGet пакетов

Точно так же, как и в предыдущем проекте добавим Nuget пакеты: Iot.Device.Bindings и System.Device.Gpio.

Шаг 3 — Добавление контроллера управления GPIO c драйвером LibGpiodDriver

Добавим контроллер для управления GPIO, и выставим режим работы контактов:

private const int GPIOCHIP = 1;
private const int LED_PIN = 36;
private const int BUTTON_PIN = 38;       
private static PinValue ledPinValue = PinValue.Low;     
static void Main(string[] args)
{                        
  GpioController controller;
  var drvGpio = new LibGpiodDriver(GPIOCHIP);
  controller = new GpioController(PinNumberingScheme.Logical, drvGpio);
  //set value
  if(!controller.IsPinOpen(LED_PIN)&&!controller.IsPinOpen(BUTTON_PIN))
    {
      controller.OpenPin(LED_PIN,PinMode.Output);
      controller.OpenPin(BUTTON_PIN,PinMode.Input);
    }
  controller.Write(LED_PIN,ledPinValue); //LED OFF

Описание кода:

  • controller.OpenPin(LED_PIN,PinMode.Output) — открывает контакт светодиода, и выставляет режим работы на — вывод;
  • controller.OpenPin(BUTTON_PIN,PinMode.Input) — открывает контакт кнопки, и выставляет режим работы на — ввод (сигнал поступает от кнопки.

Шаг 4 — Добавление обработки прерывания кнопки

Обработка прерывания реализуется путем добавление Callback на изменение состояние контакта. Callback регистрируется в контроллере GPIO:

controller.RegisterCallbackForPinValueChangedEvent(BUTTON_PIN,PinEventTypes.Rising,(o, e) =>
  {
    ledPinValue=!ledPinValue;
    controller.Write(LED_PIN,ledPinValue);
    Console.WriteLine($"Press button, LED={ledPinValue}");        
  });

Описание кода:

  • RegisterCallbackForPinValueChangedEvent — регистрация Callback на контакт BUTTON_PIN, будет срабатывать при нажатие на кнопку — Rising. Так же доступно срабатывание на событие отпускание кнопки.

Шаг 5 — Публикация для архитектуры ARM

Открыть командную строку, и перейти в папку Projectsdotnet-led-button.

Для ARM32 выполнить команду:

dotnet publish dotnet-led-button.csproj --configuration Release --runtime linux-arm --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: Projectsdotnet-led-buttonbinReleasenet5.0linux-armpublish.

Для ARM64 выполнить команду:

dotnet publish dotnet-led-button.csproj --configuration Release --runtime linux-arm64 --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: Projectsdotnet-led-buttonbinReleasenet5.0linux-arm64publish.

Шаг 6 — Перенос папки publish

Содержимое папки publish необходимо перенести в домашний каталог Linux пользователя на одноплатном компьютере.

Шаг 7 — Запуск dotnet-led-button на одноплатном компьютере

Содержимое папки publish было скопировано в папку /root/publish-dotnet-led-button. Для запуска программы необходимо выполнить команду:

dotnet dotnet-led-button.dll

Результат выполнения команды:

root@bananapim64:~/publish-dotnet-led-button# dotnet dotnet-led-button.dll
CTRL+C to interrupt the read operation:
Press any key, or 'X' to quit, or CTRL+C to interrupt the read operation:
Press button, LED=Low
Press button, LED=High
Press button, LED=Low
Press button, LED=High
Press button, LED=Low

Кнопка работает!

Проект доступен на GitHub dotnet-led-button.

Теперь поговорим о скорости

Замеры скорости управления GPIO на Banana Pi BPI-M64 не проводились из-за отсутствия осциллографа. Но не так давно, пользователь ZhangGaoxing опубликовал результаты замеров скорости на Orange Pi Zero: ОС Armbian buster, ядро Linux 5.10.16, .NET 5.0.3. Тест заключался в быстром переключение контакта GPIO с «0» на «1» и наоборот, по сути осуществлялась генерация сигнала ШИМ (в Arduino аналог SoftPWM). Чем больше частота, тем быстрее переключатся контакт. Для замера был разработан проект SunxiGpioDriver.GpioSpeed. ZhangGaoxing для доступа к контактам разработал драйвер SunxiDriver, который напрямую обращается к регистрам памяти для управления GPIO. Код этого драйвера так же можно адаптировать к любой плате, путем изменения адресов регистров памяти из datasheet к процессору. Минус такого подхода заключается в отсутствие контроля к GPIO со стороны ОС, можно «влезть» в контакт используемой ОС и вызвать сбой работы.

Таблица замеров:

Результаты подтвердили, что самым медленным интерфейсом является SysFs, и его не стоит использовать для серьезных проектов. wiringOP является С оберткой доступа к GPIO. Непосредственно управление GPIO из C кода существенно быстрее, чем из приложения на .NET, разница скорости в ~13 раз. Это и есть плата за Runtime.

Итог

Управлять контактами GPIO в C# оказалось не сложнее чем на Arduino. В отличие от Arduino в нашем распоряжение Linux с поддержкой полноценной графики, звуком, и большими возможностями подключения различной периферии. В далеком 2014 году с хабровчанином prostosergik был спор о целесообразности использовании Raspberry Pi в качестве школьного звонка. Мною был реализован подобный функционал на C# .NET Micro Framework, отладочная плата FEZ Domino. С того времени многое что изменилось. Сейчас вариант использования для подобных индивидуальных задач, одноплатных компьютеров на Linux более оправдан, чем использование микроконтроллера. Первое существенное изменение это — .NET теперь работает на Linux нативно. Второе — появились библиотеки которые упрощают и скрывают под капотом все сложную работу. Третье — цена, сейчас одноплатный компьютер с 256 Мб ОЗУ, Ethernet и Wi-Fi в известном китайском магазине можно приобрести за 18$. За такие деньги МК, с поддержкой полноценного Web-интерфейса и шифрования сетевого трафика, вряд ли найдешь. Платформа .NET IoT позволяет работать с GPIO на достаточно высоком уровне абстракции, что существенно снижает порог вхождения. В результате любой разработчик .NET платформы, может с легкостью реализовать свое решение для IoT не вдаваясь в детали как это работает внутри. Установка платформы .NET и библиотеки Libgpiod было приведено для понимания, как это работает, но такой подход не является самым удобным. Гораздо удобнее все разворачивать в Docker контейнере, тем более это mainstream для Linux. В продолжении посмотрим как упаковывать приложение на C# вместе с .NET 5 и Libgpiod в один контейнер, для дальнейшей удобной дистрибьюции нашего решения потенциальному клиенту, задействуем LCD для вывода информации из .NET кода.


На правах рекламы

Прямо сейчас вы можете заказать мощные серверы, которые используют новейшие процессоры AMD Epyc. Гибкие тарифы — от 1 ядра CPU до безумных 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe.

Подписывайтесь на наш чат в Telegram.

After releasing some projects with the ESP32-CAM, some readers reported issues when trying to use the ESP32-CAM. This guide is a compilation with the most common errors when using the ESP32-CAM and how to fix them.

ESP32-CAM Troubleshooting Guide: Most Common Problems Fixed

We’ve released the following projects with the ESP32-CAM:

  • Video Streaming, Face Detection and Face Recognition
  • ESP32 IP CAM – Video Streaming (Home Assistant and Node-RED)
  • Take Photo and Save to MicroSD Card
  • PIR Motion Detector with Photo Capture
  • Take Photo, Save to SPIFFS and Display in Web Server

Note: some of our readers reported errors when trying to follow the ESP32-CAM project with Home Assistant. We’ve modified some lines on the code, so most of the problems related with that project should be fixed.

Please note that we couldn’t reproduce some of the errors on our end. However, we’ve gathered all the information given by our readers to get answers to the most common issues.

If you have a different problem or a different solution to these issues, you can share your tips by writing a comment below.

Most common errors:

  1. Failed to connect to ESP32: Timed out waiting for packet header
  2. Camera init failed with error 0x20001 or similar
  3. Brownout detector or Guru meditation error
  4. Sketch too big error – Wrong partition scheme selected
  5. Board at COMX is not available – COM Port Not Selected
  6. Psram error: GPIO isr service is not installed
  7. Weak Wi-Fi Signal
  8. No IP Address in Arduino IDE Serial Monitor
  9. Can’t open web server
  10. The image lags/shows lots of latency
  11. esp_camera_fb_get(): Failed to get the frame on time!

1. Failed to connect to ESP32: Timed out waiting for packet header

ESP32-CAM Failed to connect to ESP32: Timed out waiting for packet header

This error means that the ESP32-CAM is not in flashing mode or it is not connected properly to the FTDI programmer.

Double-check the steps to upload code

Double-check that you’ve followed the exact steps to put your ESP32-CAM in flashing mode. Failing to complete one of the steps may result in that error. Here’s the steps you need to follow:

Connect the ESP32-CAM board to your computer using an FTDI programmer. Follow the next schematic diagram:

Important: GPIO 0 needs to be connected to GND so that you’re able to upload code.

Many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make sure the jumper is in the right place to select 5V.

Important: GPIO 0 needs to be connected to GND so that you’re able to upload code.

ESP32-CAM FTDI Programmer
GND GND
5V VCC (5V)
U0R TX
U0T RX
GPIO 0 GND

To upload the code, follow the next steps:

1) Go to Tools Board and select AI-Thinker ESP32-CAM.

2) Go to Tools Port and select the COM port the ESP32 is connected to.

3) Then, click the upload button to upload the code.

4) When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button.

After a few seconds, the code should be successfully uploaded to your board.

GPIO 0 must be connected to GND

Important: if you can’t upload the code, double-check that GPIO 0 is connected to GND and that you selected the right settings in the Tools menu. You should also press the on-board Reset button to restart your ESP32 in flashing mode. Also, check that you have the FTDI programmer jumper cap set to 5V.

Check the FTDI programmer you are using

One of our readers reported the following: “found out that you can program the board with a USB-to-TTL module model CP2102 and that the CH340 model does NOT work“. This is the FTDI programmer we’re using.

Power the ESP32-CAM with 5V

Some of our readers reported that they could only upload code when the ESP32 was powered with 5V. So, power the ESP32-CAM with 5V.

FTDI Programmer 5V

Measure the output voltage of your FTDI programmer (VCC and GND) using a Multimeter to ensure it’s providing 5V to your ESP32-CAM.

FTDI Programmer output 5V multimeter

2. Camera init failed with error 0x20001 or similar

ESP32-CAM Camera init failed with error 0x20001 or similar

If you get this exact error, it means that your camera OVX is not connected properly to your ESP32 board or you have the wrong pin assignment in the code.

Sometimes, unplugging and plugging the FTDI programmer multiple times or restart the board multiple times, might solve the issue.

Camera not connected properly

The camera has a tiny connector and you must ensure it’s connected in the the right away and with a secure fit, otherwise it will fail to establish a connection.

Wrong pin assignment in the code

When you get this error, it might also mean that you didn’t select the right board in the define section or the pin definition is wrong for your board.

Make sure you select the right camera module in your projects. You just need to uncomment the right camera module and comment all the others:

//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_M5STACK_PSRAM
#define CAMERA_MODEL_AI_THINKER

In this example, we’re using the CAMERA_MODEL_AI_THINKER, so it’s the one that is enabled. Otherwise, it will fail the pin assignment and the camera will fail to init.

There are many esp32-cam boards being released (“fake boards”) that the wiring between the ESP32 and the OV camera might be different, so selecting the camera module, might not be enough. You might need to check each gpio declaration with your board pinout.

For example, M5Stack board without PSRAM has a different pin assignment than the M5STACK with PSRAM (defined on the code by default). So, you need to change the pin definition in the code accordingly to the board pinout.

Not enough power through USB source

If you’re powering your ESP32 through a USB port on your computer, it might not be supplying enough power.

Faulty FTDI programmer

Some readers also reported this problem was solved by replacing their actual FTDI programmer with this one.

The camera/connector is broken

If you get this error, it might also mean that your camera or the camera ribbon is broken. If that is the case, you may get a new OV2640 camera probe.

3. Brownout detector or Guru meditation error

When you open your Arduino IDE Serial monitor and the error message “Brownout detector was triggered” is constantly being printed over and over again. It means that there’s some sort of hardware problem.

It’s often related to one of the following issues:

  • Poor quality USB cable;
  • USB cable is too long;
  • Board with some defect (bad solder joints);
  • Bad computer USB port;
  • Or not enough power provided by the computer USB port.

Solution: 

  • try a different shorter USB cable (with data wires)
  • use a different computer USB port or use a USB hub with an external power supply
  • some readers reported that when powering the ESP32-CAM with 5V, the issue was fixed.

Also, follow the suggestions described in issue 2.

4. Sketch too big error – Wrong partition scheme selected

ESP32-CAM Sketch too big error Wrong partition scheme selected

When you get the following error:

Sketch too big; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing it.
Error compiling for board ESP32 Dev Module.

It means that you haven’t selected the right partition scheme. Make sure you select the right partition scheme. In your Arduino IDE, go to Tools > Partition Scheme, select “Huge APP (3MB No OTA)“.

5. Board at COMX is not available – COM Port Not Selected

ESP32-CAM COM Port Not Selected

If you get the following error or similar:

serial.serialutil.SerialException: could not open port 'COM8': WindowsError(2, 'The system cannot find the file specified.')
Failed to execute script esptool
the selected serial port Failed to execute script esptool
 does not exist or your board is not connected
Board at COM8 is not available

It means that you haven’t selected the COM port in the Tools menu. In your Arduino IDE, go to Tools > Port and select the COM port the ESP32 is connected to.

It might also mean that the ESP32-CAM is not establishing a serial connection with your computer or it is not properly connected to the USB connector.

6. Psram error: GPIO isr service is not installed

ESP32-CAM Psram error GPIO isr service is not installed

You are using a board without PSRAM and you get the following error or similar:

E (161) gpio: gpio_isr_handler_remove(380): GPIO isr service is not installed, call gpio_install_isr_service() first
Camera init failed with error 0x101

when the board was initialized with the following settings:

config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;

Adding the following fixes the issues (it lowers the image resolution so it won’t need so much space to store images. However, as a result, you cannot get some high resolution formats due to the limited memory):

if(psramFound()){
  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = 10;
  config.fb_count = 2;
} else {
  config.frame_size = FRAMESIZE_SVGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;
}

Note: face recognition and detection doesn’t work with boards without PSRAM. However, you can still use all the other functionalities of the board. For example, although you can’t use the face recognition and detection features of this project (ESP32-CAM Video Streaming and Face Recognition with Arduino IDE), you can still play with the example and explore the board features as long as you have the right pin assignment in the code.

7. Weak Wi-Fi Signal

Some readers reported that after powering the ESP32-CAM with 5V, they’ve gotten a more stable Wi-Fi signal. You can read this dedicated guide to learn how to connect an external antenna to the ESP32-CAM and extend Wi-Fi coverage.

The ESP32-CAM has the option to use either the built-in antenna or an external antenna. If your ESP32-CAM AI-Thinker has no Wi-Fi connection or poor connection, it might have the external antenna enabled. If you connect an external antenna to the connector, it should work fine.

Check if the jumper 0K resistor by the antenna connector is in the proper position for the desired antenna. There are 3 little white squares laid out like a “<” with the middle position being common.

ESP32-CAM external and built-in antenna
Photo courtesy of Helmut Schoenborn

The following photo shows a closer look at that area. You can clearly see a small 0K resistor connecting to the built-in antenna.

esp32-cam connected to built-in antenna
Photo courtesy of Helmut Schoenborn

With board turned so the the PCB antenna is up:

  • To use the PCB antenna, the resistor must be on the top position, like this: /
  • For the antenna connector, the resistor must be on the bottom position, like this:

So, to enable the on-board antenna:

  • Unsolder the resistor that goes to the antenna, it’s in this position
  • And solder together the two connections to enable the on-board antenna.

8. No IP Address in Arduino IDE Serial Monitor

f you just see dots printed in the serial monitor (……), it means that your ESP32-CAM is not establishing a Wi-Fi connection with your router.

Double-check your network credentials

You need to make sure that you’ve typed your exact network credentials (SSID and password) in the following variables:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Select the right baud rate in the Arduino IDE Serial Monitor

If you don’t select the right baud rate in the Arduino IDE Serial Monitor, you won’t get your board IP address or you’ll just get garbage on the screen.

Make sure you select the right baud rate. In our examples with the ESP32-CAM, we use 115200 baud rate.

Reset the board multiple times

You might also need to press the ESP32-CAM on-board RESET button multiple times to restart your ESP and print the IP address during boot.

RX and TX swapped

Double-check the connections between your ESP32 board and the FTDI programmer. RX goes to TX and TX goes to RX. If these connections are swapped, the ESP32-CAM is not able to establish a serial communication with your computer.

Wi-Fi Range

If the router is far away from your ESP32 board, it might not be able to catch the Wi-Fi signal. Ensure that your ESP32-CAM is fairly close to your router.

9. Can’t open web server

If the ESP32-CAM is printing the IP address in your Arduino IDE Serial Monitor, but when you try to open the web server in your web browser you see a blank screen, it usually means that you are trying to access the ESP32-CAM web server with multiple web browser tabs.

At the moment, these ESP32-CAM sketches only work with one client connected at a time.

10. The image lags/shows lots of latency

Having some latency is normal for such a small and cheap camera. Some readers have suggested the following to reduce latency:

  • Power the ESP32-CAM with a standalone 5V power supply
  • Reduce the frame size with the following in your code:
    config.frame_size = FRAMESIZE_SVGA or config.frame_size = FRAMESIZE_VGA
  • Use an external antenna.

11. esp_camera_fb_get(): Failed to get the frame on time!

We’ve personally never faced this issue. However, many readers are getting this error with their ESP32-CAM boards.

One of our readers (Fibula) suggested the following to solve this issue:

“Im using the ESP32-CAM Module 2MP OV2640 Camera sensor Module Type-C USB module from Aliexpress. Although not mentioned, It doesn’t have the extra PSRAM the other M5 models do, and the camera has one changed IO pin.

See here: https://github.com/m5stack/m5stack-cam-psram/blob/master/README.md and scroll down to Interface Comparison.

The CameraWebServer Arduino example we’re probably all using doesn’t have this ESP32-CAM model defined.

You need to add it yourself in the main tab add:

#define CAMERA_MODEL_M5STACK_NO_PSRAM

And in the camera_pins.h tab add the following:

#elif defined(CAMERA_MODEL_M5STACK_NO_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21

And you’re good to go.

Also note that the max resolution of the bare ESP32-CAM Module is XGA 1024×768, I assume also because of the lack of PSRAM. “

We hope this suggestion solves your issue. Let us know in the comments section.

Using larger microSD card sizes

According to he datasheet, the ESP32-CAM should only supports 4GB microSD cards.

However, we’ve tested with 16GB microSD card and it works well.

You might not be able to store more than 4GB, even though you have 16GB. We haven’t tested storing more than 4GB, so we’re not sure about this.

Are these projects compatible with M5Stack board?

Yes, the M5Stack ESP32 board is compatible with out projects. However, you must check your camera pinout to ensure you have the right assignment in the code.

You can check the M5Stack camera connections here.

How to set a fixed the IP Address

To set a static/fixed IP address, you can follow the next tutorial:

  • ESP32 Static/Fixed IP Address

Setting ESP32-CAM as Access Point (AP)

You can set your ESP32-CAM as an Access Point (AP). This means you are able to connect to your ESP32-CAM directly without having to connect to your router. You can use the following code to set your video streaming web server as an Access Point:

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/
  
  IMPORTANT!!! 
   - Select Board "AI Thinker ESP32-CAM"
   - GPIO 0 must be connected to GND to upload a sketch
   - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h"  //disable brownout problems
#include "esp_http_server.h"

// Replace with your network credentials
const char* ssid     = "ESP32-Access-Point";
const char* password = "123456789";

#define PART_BOUNDARY "123456789000000000000987654321"

// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM

// Not tested with this model
//#define CAMERA_MODEL_WROVER_KIT

#if defined(CAMERA_MODEL_WROVER_KIT)
  #define PWDN_GPIO_NUM    -1
  #define RESET_GPIO_NUM   -1
  #define XCLK_GPIO_NUM    21
  #define SIOD_GPIO_NUM    26
  #define SIOC_GPIO_NUM    27
  
  #define Y9_GPIO_NUM      35
  #define Y8_GPIO_NUM      34
  #define Y7_GPIO_NUM      39
  #define Y6_GPIO_NUM      36
  #define Y5_GPIO_NUM      19
  #define Y4_GPIO_NUM      18
  #define Y3_GPIO_NUM       5
  #define Y2_GPIO_NUM       4
  #define VSYNC_GPIO_NUM   25
  #define HREF_GPIO_NUM    23
  #define PCLK_GPIO_NUM    22

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       32
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       17
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_AI_THINKER)
  #define PWDN_GPIO_NUM     32
  #define RESET_GPIO_NUM    -1
  #define XCLK_GPIO_NUM      0
  #define SIOD_GPIO_NUM     26
  #define SIOC_GPIO_NUM     27
  
  #define Y9_GPIO_NUM       35
  #define Y8_GPIO_NUM       34
  #define Y7_GPIO_NUM       39
  #define Y6_GPIO_NUM       36
  #define Y5_GPIO_NUM       21
  #define Y4_GPIO_NUM       19
  #define Y3_GPIO_NUM       18
  #define Y2_GPIO_NUM        5
  #define VSYNC_GPIO_NUM    25
  #define HREF_GPIO_NUM     23
  #define PCLK_GPIO_NUM     22
#else
  #error "Camera model not selected"
#endif

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "rn--" PART_BOUNDARY "rn";
static const char* _STREAM_PART = "Content-Type: image/jpegrnContent-Length: %urnrn";

httpd_handle_t stream_httpd = NULL;

static esp_err_t stream_handler(httpd_req_t *req){
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK){
    return res;
  }

  while(true){
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400){
        if(fb->format != PIXFORMAT_JPEG){
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted){
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
          }
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
        }
      }
    }
    if(res == ESP_OK){
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if(fb){
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK){
      break;
    }
    //Serial.printf("MJPG: %uBn",(uint32_t)(_jpg_buf_len));
  }
  return res;
}

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;

  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
  
  //Serial.printf("Starting web server on port: '%d'n", config.server_port);
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("Camera Stream Ready! Connect to the ESP32 AP and go to: http://");
  Serial.println(IP);
  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  delay(1);
}

View raw code

To better understand how it works, you can read the next tutorial:

  • ESP32 Access Point (AP) for Web Server

Wrapping Up

We hope you’ve found this troubleshooting guide useful and you were able to make your ESP32-CAM work with our projects.

If you have any other issues or suggestions on how to fix them, please post a comment below.

If you like this project, you may also like other projects with the ESP32-CAM:

  • ESP32-CAM AI-Thinker Pinout Guide: GPIOs Usage Explained
  • Video Streaming, Face Detection and Face Recognition
  • Build ESP32-CAM Projects (eBook)
  • Read all our ESP32-CAM Projects, Tutorials and Guides

Thank you for reading.

P.S. It is very difficult to understand what’s wrong with your project when we can’t reproduce the error on our end. However, if you post the error, there might be other readers with the same issue/solution, so we encourage you to interact in the comment’s section.

Environment

  • Module or chip used: ESP32-WROOM-32
  • IDF version : v4.2.1
  • Build System: idf.py
  • Compiler version : 8.4.0
  • Operating System: Windows
  • (Windows only) environment type: ESP Command Prompt
  • Using an IDE?: Yes, VSCode IDE
  • Power Supply: USB

Problem Description

//Detailed problem description goes here.
I have on going project which has binary size of 1457 KB.
When I try to gpio_set_level(GPIO_NUM_16,1) after initializing everything properly, that GPIO 16 it is not set to High.

Expected Behavior

When I call for gpio_set_level(GPIO_NUM_16,1). It has to set to High.

Actual Behavior

It is not Set to High. But when I call it in While loop without any delay or any other function it is set to high.

Steps to reproduce

I have tried to modify the gpio example code. it works perfectly. but when I tried to add it to my project it is not working.
I even tried to disable entire program logic. and just used the same code as like as example. But result is same.

And I tried to call the gpio_set_level in a loop which makes it work

This is happening only for the GPIO_NUM_16 and GPIO_NUM_25. other GPIOs are working perfectly.

Code to reproduce this issue

// the code should be wrapped in the ```cpp tag so that it will be displayed better.
#include "esp_log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

#define GPIO_OUTPUT_IO_0    16
#define GPIO_OUTPUT_IO_1    25
#define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
#define GPIO_INPUT_IO_0     4
#define GPIO_INPUT_IO_1     5
#define GPIO_INPUT_PIN_SEL  ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))
#define ESP_INTR_FLAG_DEFAULT 0

#define HIGH 1
#define LOW 0

void app_main(void)
{
    gpio_config_t io_conf;
    //disable interrupt
    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
    //set as output mode
    io_conf.mode = GPIO_MODE_OUTPUT;
    //bit mask of the pins that you want to set,e.g.GPIO18/19
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    //disable pull-down mode
    io_conf.pull_down_en = 0;
    //disable pull-up mode
    io_conf.pull_up_en = 0;
    //configure GPIO with the given settings
    gpio_config(&io_conf);

    //interrupt of rising edge
    io_conf.intr_type = GPIO_PIN_INTR_POSEDGE;
    //bit mask of the pins, use GPIO4/5 here
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode    
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);

    //change gpio intrrupt type for one pin
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);

    //create a queue to handle gpio event from isr
    // gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    // //start gpio task
    // xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    // gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
    // //hook isr handler for specific gpio pin
    // gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);

    //remove isr handler for gpio number.
    // gpio_isr_handler_remove(GPIO_INPUT_IO_0);
    // //hook isr handler for specific gpio pin again
    // gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);

    int cnt = 0;
    gpio_set_level(GPIO_OUTPUT_IO_0,HIGH);
    gpio_set_level(GPIO_OUTPUT_IO_1,HIGH);
    // while(1) {
    //     printf("cnt: %dn", cnt++);
    //     vTaskDelay(1000 / portTICK_RATE_MS);
    //     gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);
    //     gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);
    // }
}

// If your code is longer than 30 lines, GIST is preferred.

Debug Logs

entry 0x40080710
I (29) boot: ESP-IDF v4.2 2nd stage bootloader
I (29) boot: compile time 17:10:29
I (29) boot: chip revision: 1
I (31) qio_mode: Enabling default flash chip QIO
I (36) boot.esp32: SPI Speed      : 40MHz
I (41) boot.esp32: SPI Mode       : QIO
I (46) boot.esp32: SPI Flash Size : 4MB
I (50) boot: Enabling RNG early entropy source...
I (56) boot: Partition Table:
I (59) boot: ## Label            Usage          Type ST Offset   Length
I (66) boot:  0 nvs              WiFi data        01 02 00009000 00004000
I (74) boot:  1 otadata          OTA data         01 00 0000d000 00002000
I (81) boot:  2 phy_init         RF data          01 01 0000f000 00001000
I (89) boot:  3 ota_0            OTA app          00 10 00010000 001c2000
I (96) boot:  4 ota_1            OTA app          00 11 001e0000 001c2000
I (104) boot:  5 storage          Unknown data     01 82 003a2000 00040000
I (111) boot:  6 nvs_key          NVS keys         01 04 003e2000 00001000
I (119) boot: End of partition table
I (123) boot: No factory image, trying OTA 0
I (128) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x46c80 (289920) map
I (232) esp_image: segment 1: paddr=0x00056ca8 vaddr=0x3ffbdb60 size=0x04140 ( 16704) load
I (238) esp_image: segment 2: paddr=0x0005adf0 vaddr=0x40080000 size=0x00404 (  1028) load

I (239) esp_image: segment 3: paddr=0x0005b1fc vaddr=0x40080404 size=0x04e1c ( 19996) load
I (256) esp_image: segment 4: paddr=0x00060020 vaddr=0x400d0020 size=0xf9804 (1021956) map
0x400d0020: _stext at ??:?

I (592) esp_image: segment 5: paddr=0x0015982c vaddr=0x40085220 size=0x19314 (103188) load
0x40085220: coex_classic_bt_release$part$0 at ld_fm.c:?

I (632) esp_image: segment 6: paddr=0x00172b48 vaddr=0x400c0000 size=0x00064 (   100) load
I (648) boot: Loaded app from partition at offset 0x10000
I (682) boot: Set actual ota_seq=1 in otadata[0]
I (682) boot: Disabling RNG early entropy source...
I (682) cpu_start: Pro cpu up.
I (685) cpu_start: Application information:
I (690) cpu_start: Project name:     ABCD
I (695) cpu_start: App version:      40569bf-dirty
I (700) cpu_start: Compile time:     May 10 2021 17:10:17
I (706) cpu_start: ELF file SHA256:  471e1d12ceecb37a...
I (712) cpu_start: ESP-IDF:          v4.2
I (717) cpu_start: Starting app cpu, entry point is 0x40081890

I (0) cpu_start: App cpu up.
D (727) memory_layout: Checking 11 reserved memory ranges:
D (733) memory_layout: Reserved memory range 0x3ffae000 - 0x3ffae6e0
D (739) memory_layout: Reserved memory range 0x3ffae6e0 - 0x3ffaff10
D (746) memory_layout: Reserved memory range 0x3ffb0000 - 0x3ffb6388
D (752) memory_layout: Reserved memory range 0x3ffb8000 - 0x3ffb9a20
D (758) memory_layout: Reserved memory range 0x3ffbdb28 - 0x3ffbdb5c
D (765) memory_layout: Reserved memory range 0x3ffbdb60 - 0x3ffcd7b0
D (771) memory_layout: Reserved memory range 0x3ffe0000 - 0x3ffe0440
D (778) memory_layout: Reserved memory range 0x3ffe3f20 - 0x3ffe4350
D (784) memory_layout: Reserved memory range 0x40070000 - 0x40078000
D (791) memory_layout: Reserved memory range 0x40078000 - 0x40080000

D (797) memory_layout: Reserved memory range 0x40080000 - 0x4009e534

D (803) memory_layout: Building list of available memory regions:
D (810) memory_layout: Available memory region 0x3ffaff10 - 0x3ffb0000
D (816) memory_layout: Available memory region 0x3ffb6388 - 0x3ffb8000
D (823) memory_layout: Available memory region 0x3ffb9a20 - 0x3ffbdb28
D (829) memory_layout: Available memory region 0x3ffcd7b0 - 0x3ffce000
D (836) memory_layout: Available memory region 0x3ffce000 - 0x3ffd0000
D (842) memory_layout: Available memory region 0x3ffd0000 - 0x3ffd2000
D (849) memory_layout: Available memory region 0x3ffd2000 - 0x3ffd4000
D (856) memory_layout: Available memory region 0x3ffd4000 - 0x3ffd6000
D (862) memory_layout: Available memory region 0x3ffd6000 - 0x3ffd8000
D (869) memory_layout: Available memory region 0x3ffd8000 - 0x3ffda000
D (875) memory_layout: Available memory region 0x3ffda000 - 0x3ffdc000
D (882) memory_layout: Available memory region 0x3ffdc000 - 0x3ffde000
D (889) memory_layout: Available memory region 0x3ffde000 - 0x3ffe0000
D (895) memory_layout: Available memory region 0x3ffe0440 - 0x3ffe3f20
D (902) memory_layout: Available memory region 0x3ffe4350 - 0x3ffe8000
D (908) memory_layout: Available memory region 0x3ffe8000 - 0x3fff0000
D (915) memory_layout: Available memory region 0x3fff0000 - 0x3fff8000
D (922) memory_layout: Available memory region 0x3fff8000 - 0x3fffc000
D (928) memory_layout: Available memory region 0x3fffc000 - 0x40000000
D (935) memory_layout: Available memory region 0x4009e534 - 0x400a0000
I (941) heap_init: Initializing. RAM available for dynamic allocation:
D (949) heap_init: New heap initialised at 0x3ffaff10
I (954) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
D (960) heap_init: New heap initialised at 0x3ffb6388
I (965) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM
D (971) heap_init: New heap initialised at 0x3ffb9a20
I (976) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM
D (982) heap_init: New heap initialised at 0x3ffcd7b0
I (987) heap_init: At 3FFCD7B0 len 00012850 (74 KiB): DRAM
I (994) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (1000) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
D (1006) heap_init: New heap initialised at 0x4009e534
I (1012) heap_init: At 4009E534 len 00001ACC (6 KiB): IRAM
I (1018) cpu_start: Pro cpu start user code
D (1023) clk: waiting for 32k oscillator to start up
D (1059) clk: RTC_SLOW_CLK calibration value: 11414464
D (1065) intr_alloc: Connected src 46 to int 2 (cpu 0)
D (1065) intr_alloc: Connected src 17 to int 3 (cpu 0)
D (1066) intr_alloc: Connected src 24 to int 9 (cpu 0)
D (1071) FLASH_HAL: extra_dummy: 1
D (1074) spi_flash: trying chip: issi
D (1078) spi_flash: trying chip: gd
D (1082) spi_flash: trying chip: mxic
D (1085) spi_flash: trying chip: generic
I (1089) spi_flash: detected chip: generic
I (1094) spi_flash: flash io: qio
D (1098) chip_generic: set_io_mode: status before 0x200
I (1103) cpu_start: Starting scheduler on PRO CPU.
D (0) intr_alloc: Connected src 25 to int 2 (cpu 1)
I (0) cpu_start: Starting scheduler on APP CPU.
D (1119) heap_init: New heap initialised at 0x3ffe0440
D (1129) heap_init: New heap initialised at 0x3ffe4350
D (1139) intr_alloc: Connected src 16 to int 12 (cpu 0)
D (1139) APP: [APP] StartuP..
D (1139) APP: [APP] Free memory: 206936 bytes
D (1149) APP: [APP] IDF version: v4.2
D (1149) partition: Loading the partition table
D (1169) event: running task for loop 0x3ffbd778
D (1169) event: created task for loop 0x3ffbd778
D (1169) event: created event loop 0x3ffbd778
D (1169) APP: Heaps Are Valid
D (1179) SLEEP: Not a deep sleep reset

D (1179) history: Initializing SPIFFS
I (1209) history: Partition size: total: 233681, used: 502
D (1209) ADC: ADC Calibrartion Type 0

I (1209) gpio: GPIO[15]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1219) gpio: GPIO[16]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1229) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1239) gpio: GPIO[19]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1249) gpio: GPIO[21]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1259) gpio: GPIO[22]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1269) gpio: GPIO[23]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1269) gpio: GPIO[12]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (1279) gpio: GPIO[18]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
D (1289) intr_alloc: Connected src 22 to int 13 (cpu 0)
D (1299) intr_alloc: Connected src 35 to int 17 (cpu 0)
D (1299) intr_alloc: Connected src 49 to int 18 (cpu 0)

Добрый день, уважаемый читатель! В этой статье обсудим методы работы со встроенными портами ввода-вывода GPIO в цифровом режиме.

Если вы создавали скетчи для Arduino IDE, то наверное, знаете, как осуществляется работа с GPIO для Arduino:

pinMode(10, OUTPUT); // Настраиваем PIN10 на выход
digitalWrite(10, HIGH); // Записываем в PIN10 высокий уровень

В данном случае pinMode настраивает порт ввода-вывода на режим “выход”, а digitalWrite служит для записи в ранее настроенный порт логической единицы (или нуля). Это унифицированные функции Arduino, которые “внешне” не зависят от аппаратной платформы, а вот их внутренняя реализация будет зависеть от того, какой микроконтроллер вы используете. Это позволяет сравнительно легко переносить код с одного микроконтроллера на другой без адаптации (на самом деле это не всегда прокатывает, но тем не менее, разработчики платформы Arduino к этому стремятся).

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

Давайте вспомним, какие выводы можно использовать на ESP32 (я буду рассматривать линейки ESP32-WROOM или ESP32-WROVER).

Чип ESP32 имеет 34 физических контакта GPIO. Каждая контактная площадка может использоваться как вход/выход общего назначения (GPIO) или может быть подключена к внутреннему периферийному сигналу. Мультиплексоры IO_MUX, RTC IO_MUX и матрица GPIO отвечают за маршрутизацию сигналов от периферийных устройств к контактам GPIO. Вместе эти системы обеспечивают гибко настраиваемый ввод-вывод.

Порты ввода-вывода для ESP32-DevKitC V4

Порты ввода-вывода для ESP32-DevKitC V4

  • На ввод и вывод для ESP32-WROOM можно смело использовать следующие 18 выводов GPIO: 4, 14, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33. Все указанные выводы имеют встроенные программно-подключаемые резисторы подтяжки 45 кОм (в документации это называется “слабая подтяжка”). Я буду называть эти порты “универсальными”, для простоты понимания. Некоторые из них можно назначить для использования различных интерфейсов: I2C, SPI и т.д. Примечания: для ESP32-WROVER выводы 16 и 17 использовать нельзя.
  • Выводы GPIO 34, 35, 36, 39 можно использовать только на ввод, и у них отсутствуют встроенные резисторы слабой подтяжки. На эти же выводы выведен канал ADC1.
  • Можно ещё использовать GPIO 0, 2, 5, 12, 15, но с учетом того, что их нельзя подтягивать ни к питанию, ни к земле при старте микроконтроллера.

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

Хочу обратить ваше внимание только на то, что, согласно спецификации, выводы GPIO допускают ток аж до 40 mA “высокого” уровня и до 28 mA “низкого” уровня. Это позволяет управлять различными слаботочными устройствами типа светодиодов напрямую, без применения коммутирующих транзисторов, нужно только учитывать напряжение на выводе не более 3.3В.

Настройка порта GPIO

Для работы с портами GPIO необходимо подключить модуль “driver/gpio.h”:

# include "driver/gpio.h"

Как и в Arduino IDE, прежде чем начинать работу с GPIO, его нужно настроить (сконфигурировать). Каждый “универсальный” порт можно настроить:

  • направление: на вход или выход
  • слабая подтяжка (45 КОм) к +3,3В или к “земле”
  • прерывание (если необходимо)

ESP-IDF предлагает два метода конфигурации:

  • Пакетная конфигурация сразу нескольких портов: с помощью функции gpio_config (const gpio_config_t* pGPIOConfig). Эта функция позволяет за один вызов настроить все параметры для нескольких выбранных портов: направление, подтяжки, прерывания. Соответственно это чуть более быстрый способ.
  • С помощью набора функций gpio_set_directiongpio_set_pull_mode, и т.д. Это более “детализированные” функции и работают они только для одного выбранного порта. Я чаще использую именно этот способ, скорее всего “по привычке” (так как они более похожи на способ из Arduino IDE).

Выбор вывода для работы в режиме GPIO

Прежде всего необходимо настроить GPIO для использования в режиме ввода-вывода (так как GPIO на ESP32 могут быть использованы для разных целей с помощью мультиплексора выводов IO_MUX). Разработчики ESP32 не гарантируют, что после аппаратного сброса микроконтроллера все его выводы установятся в режим ввода-вывода. Поэтому операцию перевода нужных выводов в режим GPIO желательно делать всегда.

Сделать это можно с помощью функции:

esp_err_t gpio_reset_pin(gpio_num_t gpio_num)

где:

  • gpio_num – номер вывода GPIO

Эта функция настраивает IOMUX для этого вывода на работу с GPIO, включает встроенную слабую подтяжку и отключает режимы работы на вход и на выход (то есть устанавливается режим GPIO_MODE_DISABLE – см. следующий раздел).

Для этой же цели можно воспользоваться другой функцией, которая просто перенастраивает IOMUX для этого вывода на работу с GPIO: 

void gpio_pad_select_gpio(uint8_t gpio_num)

где:

  • gpio_num – номер вывода GPIO

Режимы работы портов ввода-вывода

ESP32 поддерживает несколько режимов GPIO:

  • GPIO_MODE_DISABLE – порт отключён
  • GPIO_MODE_INPUT – порт работает только на вход
  • GPIO_MODE_OUTPUT – порт работает только на выход
  • GPIO_MODE_OUTPUT_OD – порт работает только на выход в режиме “открытый коллектор” (open-drain)
  • GPIO_MODE_INPUT_OUTPUT_OD – порт может работать одновременно и на вход и на выход с открытым коллектором
  • GPIO_MODE_INPUT_OUTPUT – порт может работать одновременно и на вход и на выход

В режимах с открытым коллектором (OD) микроконтроллер управляет только низким логическим уровнем, при установке на GPIO логической единицы вывод отключается и остается “болтаться в воздухе”. Этот режим удобно использовать для датчиков типа DHT11-22 или для управления светодиодами “по низкому уровню” (катодом к выводу, анодом к +3,3В).

Для выбора режима порта используйте функцию

esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode)

где:

  • gpio_num – идентификатор GPIO
  • mode – режим работы

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

Встроенная подтяжка

Большинство портов имеют встроенные резисторы слабой подтяжки. Чтобы их задействовать, существует несколько функций.

Универсальная функция:

esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull)

где:

  • gpio_num – идентификатор GPIO
  • pull – режим подтяжки

Режим gpio_pull_mode_t может принимать одно из нескольких значений:

  • GPIO_PULLUP_ONLY – подтяжка к питанию +3,3В
  • GPIO_PULLDOWN_ONLY – подтяжка к “земле”
  • GPIO_PULLUP_PULLDOWN – подтяжка одновременно к питанию +3,3В и “земле”
  • GPIO_FLOATING – подтяжка отключена

Однако можно использовать и несколько более простых функций:

Какой метод использовать – выбирайте на свой вкус.

Настройка допустимого выходного тока

Для GPIO, работающих на выход, ESP32 позволяет задать максимальный ток через выход. Это может быть полезно в некоторых случаях, например можно ограничить ток через светодиод без использования резистора. Для этого воспользуйтесь функцией:

esp_err_t gpio_set_drive_capability (gpio_num_t gpio_num , gpio_drive_cap_t strength)

где:

  • gpio_num – идентификатор GPIO
  • strength – максимально допустимый ток

Режим gpio_drive_cap_t может принимать одно из нескольких значений:

  • GPIO_DRIVE_CAP_0 – слабый, до ~5мА
  • GPIO_DRIVE_CAP_1 – сильнее, до ~10мА
  • GPIO_DRIVE_CAP_2 – средний (по умолчанию), до ~20мА
  • GPIO_DRIVE_CAP_3 – максимальный, до ~40мА

Как видите, по умолчанию ток высокого уровня ограничен на уровне 20мА. В большинстве случаев вызывать данную функцию при настройке порта не требуется. Но если ваше устройство на ESP32 требует большего тока (например при управлении мощным биполярным транзистором), то вы можете столкнуться с “неправильным” поведением.

Запись логического уровня в GPIO

Для записи данных в выходной порт необходимо воспользоваться функцией:

esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)

где:

  • gpio_num – идентификатор GPIO
  • level – логический уровень, 0 (низкий) или 1 (высокий)

Ничего сложного, всё предельно просто.

Чтение логического уровня из GPIO

Для чтения данных из порта необходимо воспользоваться другой функцией:

uint32_t gpio_get_level(gpio_num_t gpio_num)

где:

  • gpio_num – идентификатор GPIO

Если GPIO не настроен для ввода (или ввода и вывода), возвращаемое значение всегда равно 0.

Практическое использование GPIO в режиме вывода – мигаем светодиодом

Допустим, мы хотим использовать светодиод, подключенный к одному из выводов через токоограничительный резистор, например это GPIO12. Классика жанра.

Источник: Яндекс Картинки

Источник: Яндекс Картинки

Светодиод можно подключить двумя способами:

  • С управлением по высокому уровню. Анод через резистор к GPIO, катод – к общему проводу. В этом случае следует использовать режим GPIO_MODE_OUTPUT.
  • С управлением по низкому уровню. Анод к +3,3В, катод через резистор к GPIO. В этом случае лучше использовать режим GPIO_MODE_OUTPUT_OD.

Для ESP32 наверное оптимальнее использовать первый способ, так как ток GPIO для высокого уровня может достигать до 40мА, а для низкого – только 28мА. Хотя для светодиода вполне достаточно 10мА, в крайнем случае – 20мА (для старых советских светодиодов зеленого цвета).

Настройка вывода будет выглядеть так:

Для мигания светодиодов создадим задачу. Функция задачи для мигания светодиодом будет выглядеть примерно так:

Использование GPIO для пробуждения микроконтроллера

Если вы используете в своих разработках режим глубокого сна (например при питании от батарей), то вы можете использовать вывод GPIO для пробуждения микроконтроллера по внешнему сигналу. Задействовать данную функцию можно с помощью функции:

esp_err_t gpio_wakeup_enable (gpio_num_t gpio_num, gpio_int_type_t intr_type)

где:

  • gpio_num – идентификатор GPIO
  • intr_type – логический уровень на GPIO для пробуждения. Можно использовать только GPIO_INTR_LOW_LEVEL или GPIO_INTR_HIGH_LEVEL.

Отключить пробуждение можно с помощью функции gpio_wakeup_disable (gpio_num_t gpio_num).

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

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

Полезные ссылки

  1. ESP-IDF — GPIO and RTC GPIO.
  2. Пример на GitHub

В прошлом уроке нам удалось попробовать использование порта ввода-вывода на вход.

Только работали мы с кнопкой, отслеживая её состояние в неком цикле, что накладывает ряд ограничений на работу всей программы. Для этого и придумано следить за состоянием портов именно посредством прерываний. Как это всё работает, мы уже неоднократно видели, например в аналогичном уроке для ESR8266 и уроках для работы с другими контроллерами.

Мы знаем также, что внешние прерывания обрабатываются разные, вернее они работают по разным событиям (нарастание,  спад и т.д.). Есть также универсальные прерывания, которые отслеживают и то и это. ESP32 в этом плане также не исключение и в нём это всё есть. И не только это. В технической документации (Technical Reference Manual) есть описание регистра GPIO_PINn_REG, в котором есть битовое поле, отвечающее за настройку типов прерываний для порта ввода-вывода

Вот описание установки значения в данном поле:

GPIO_PINn_INT_TYPE Interrupt type selection: (R/W) 0: GPIO interrupt disable; 1: rising edge trigger; 2: falling edge trigger; 3: any edge trigger; 4: low level trigger; 5: high level trigger.

Третий тип установки отслеживает и фронт и спад сигнала.

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

Так как мы будем использовать не один тип изменения сигнала на ножке, давайте подключим две кнопки, одну по-прежнему к GPIO4, а другую — к GPIO5

Проект мы за основу возьмём из прошлого урока с именем BUTTON01 и дадим ему имя EXTI01.

Откроем наш новый проект в Espressif IDE и внесём некоторые изменения в файл конфигурации Kconfig.projbuild.

Во второй пункт мы добавим номер кнопки, так как их будет два

config BUTTON_GPIO_1

  int "Button GPIO1 number"

  range 0 48

  default 4

  help

      GPIO number Button 1.

Ниже добавим ещё один пункт для второй кнопки

config BUTTON_GPIO_2

  int "Button GPIO2 number"

  range 0 48

  default 5

  help

      GPIO number Button 2.

Обратите внимание, что номер порта по умолчанию тоже будет другой.

Попробуем собрать проект, и сконфигурировать его. При сборке скорей всего будет ошибка на изменение имени переменной, не обращаем внимание на это

Из тела функции app_main файла main.c удалим весь код, кроме бесконечного цикла, останется вот это

void app_main(void)

{

  while (1) {

  }

}

Теперь у нас, конечно же, код соберётся.

Добавим в данную функцию объявление переменой для счётчика циклов

void app_main(void)

{

  int cnt = 0;

Сегодня мы не будем для изменения каждого параметра порта ввода-вывода использовать отдельную функцию, а будем использовать для этого специальную структуру.

Поэтому создадим переменную типа такой структуры

  int cnt = 0;

  gpio_config_t io_conf = {};

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

#include «sdkconfig.h»

//————————————————

#define GPIO_OUTPUT_PIN_SEL  (1ULL<<CONFIG_BLINK_GPIO)

#define GPIO_INPUT_PIN_SEL  ((1ULL<<CONFIG_BUTTON_GPIO_1) | (1ULL<<CONFIG_BUTTON_GPIO_2))

#define ESP_INTR_FLAG_DEFAULT 0

//————————————————

Вернёмся в функцию app_main и проинициализируем поля переменной структуры настройками для ножки, работающей на выход

  gpio_config_t io_conf = {};

  io_conf.intr_type = GPIO_INTR_DISABLE;

  io_conf.mode = GPIO_MODE_OUTPUT;

  io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;

  io_conf.pull_down_en = 0;

  io_conf.pull_up_en = 0;

Применим данные настройки с помощью специальной функции

  io_conf.pull_up_en = 0;

  gpio_config(&io_conf);

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

  gpio_config(&io_conf);

  io_conf.intr_type = GPIO_INTR_POSEDGE;

  io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;

  io_conf.mode = GPIO_MODE_INPUT;

  io_conf.pull_up_en = 1;

  gpio_config(&io_conf);

Мы хотим, чтобы типы прерываний были разные, а у нас присвоился один и тот же тип для обеих ножек при помощи присвоения значения перечислимого типа GPIO_INTR_POSEDGE полю intr_type.

Мы можем определённой ножке порта применить другой тип при помощи функции. Так и поступим

  gpio_config(&io_conf);

  gpio_set_intr_type(CONFIG_BUTTON_GPIO_1, GPIO_INTR_ANYEDGE);

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

Добавим глобальную переменную для очереди

#define ESP_INTR_FLAG_DEFAULT 0

//————————————————

static xQueueHandle gpio_evt_queue = NULL;

Также подключим библиотеку для работы с очередями

#include «freertos/task.h»

#include «freertos/queue.h»

Создадим очередь в app_main

  gpio_set_intr_type(CONFIG_BUTTON_GPIO_1, GPIO_INTR_ANYEDGE);

  gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));

Выше функции app_main добавим функцию задачи, которая будет выводить в порт информацию по событиям прерываний

//————————————————

static void task1(void* arg)

{

    uint32_t io_num;

    for(;;) {

        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {

            printf(«GPIO[%d] intr, val: %dn», io_num, gpio_get_level(io_num));

        }

    }

}

//————————————————

Здесь из очереди забирается номер ножки порта и выводится в терминал, а также заодно выводится состояние данной ножки.

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

  gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));

  xTaskCreate(task1, «task1», 2048, NULL, 10, NULL);

Включим прерывания

  xTaskCreate(task1, «task1», 2048, NULL, 10, NULL);

  gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);

Мы можем назначить один и тот же обработчик прерывания на оба входа. Добавим функцию для него выше функции task1

//————————————————

static void IRAM_ATTR gpio_isr_handler(void* arg)

{

    uint32_t gpio_num = (uint32_t) arg;

    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);

}

//————————————————

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

В функции app_main объявим наш обработчик для каждой ножки

  gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);

  gpio_isr_handler_add(CONFIG_BUTTON_GPIO_1, gpio_isr_handler, (void*) CONFIG_BUTTON_GPIO_1);

  gpio_isr_handler_add(CONFIG_BUTTON_GPIO_2, gpio_isr_handler, (void*) CONFIG_BUTTON_GPIO_2);

В бесконечном цикле выведем значение счётчика, который будет считать каждый цикл, заодно проинкрементировав его, и установим значение ножки светодиода в зависимости от чётности счётчика

  while (1) {

    printf(«cnt: %dn», cnt++);

    gpio_set_level(CONFIG_BLINK_GPIO, cnt % 2);

    vTaskDelay(1000 / portTICK_RATE_MS);

  }

Ну вот, в принципе, и всё. Соберём код, прошьём контроллер и запустим терминал. Терминал мы сегодня запустим прямо в среде Espressif IDE. Если у кого он ещё не добавлен в окно ввода-вывода, то делается это посредством выбора пункта меню Window->Show View->Terminal

Запускаем терминал, настраиваем порт и видим как наращивается наш счётчик. В такт с этим мигает светодиод на плате

Нажмём на кнопку 1 и, так как кнопка прижимает ножку на общий провод, то у нас в терминале отобразится уровень 0

Отожмём кнопку, разорвав цепь, и увидим, что на ножке у нас теперь уровень 1

Также мы в сообщении видим номер ножки порта.

Нажмём теперь кнопку 2

И в данном случае мы не получим никакого сообщения, так как в случае нажатия кнопки у нас происходит спад, а ножка 5 у нас настроена только на отслеживание фронта

Отожмём кнопку и вот результат

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

Всем спасибо за внимание!

Данная статья в Дзен.

Предыдущий урок Программирование МК ESP32 Следующий урок

Исходный код

Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

ESP32 Name

Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)

ESP32 Name


Post Views:
3 454

Понравилась статья? Поделить с друзьями:
  • Gpgkeys http fetch error 1 unsupported protocol
  • Gpg не найдено данных формата openpgp как исправить
  • Gpg keyserver receive failed keyserver error
  • Gpg keyserver receive failed general error
  • Gpg error termux