На нашем сайте мы продолжаем серию обучающих статей про плату Raspberry Pi Pico и в данной статье мы рассмотрим управление двумя светодиодами с помощью многопоточной обработки данных в двухъядерном режиме в плате Raspberry Pi Pico на языке программирования MicroPython. Ранее на нашем сайте мы также рассматривали программирование микроконтроллера ESP32 в двухъядерном режиме.
Как известно, микропроцессор RP2040, являющийся "сердцем" платы Raspberry Pi Pico, имеет два ядра. Если вы мало знакомы с многопоточной обработкой данных, то просто представьте себе свой домашний компьютер на операционной системе Windows, который может выполнять одновременно сразу несколько задач. Все это происходит благодаря тому, что его операционная система (ОС) поддерживает многопоточную обработку данных. Но для нашей платы Raspberry Pi Pico у нас нет ОС, которая могла бы управлять циклами центрального процессора (CPU) – она может поддерживать (обрабатывать) только один поток данных на ядро (в нашей ситуации мы имеем два ядра).
Необходимые компоненты
- Плата Raspberry Pi Pico (купить на AliExpress).
- Резистор 330 Ом – 2 шт. (купить на AliExpress).
- Светодиод – 2 шт. (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Схема проекта
Схема проекта для демонстрации двухъядерного режима в плате Raspberry Pi Pico представлена на следующем рисунке.
Как видите, схема очень проста. В ней необходимо просто подключить 2 светодиода с помощью токоограничивающих резисторов 330 Ом к контактам GPIO15 и GPIO16 платы Raspberry Pi Pico. Контакты земли (ground pins) обоих светодиодов подключены к контакту земли платы.
Объяснение программы для многопоточной обработки данных в Raspberry Pi Pico в двухъядерном режиме
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Вначале вам необходимо скачать каталог с обучающими программами для платы Raspberry Pi Pico по следующей ссылке с GitHub. В скачанном каталоге откройте подкаталог “T9_DualCore_Program_PIco”, внутри которого вы найдете подкаталог “Codes”. Из данного подкаталога откройте с помощью редактора Thonny файл “main.py”. Рассмотрим содержимое данного файла.
Первым делом в программе нам необходимо импортировать класс Pin() из библиотеки machine.py – он будет использоваться для управления контактами ввода/вывода платы Raspberry Pi Pico. Библиотека “utime” будет использоваться для доступа к внутренним часам платы (организации задержек в программе), а библиотека “_thread” будет использоваться для функций многопоточной обработки данных. Библиотека “_thread” была разработана сообществом raspberry pi pico.
1 2 3 |
from machine import Pin import utime import _thread |
Далее в программе мы инициализируем контакты GPIO15 и GPIO16 платы – мы сконфигурируем их для работы в качестве цифровых выходов (OUPUT) с помощью функций “Pin(16, machine.Pin.OUT)” и “Pin(15, machine.Pin.OUT)”. Функция “_thread.allocate_lock()” будет использоваться в качестве семафорного затвора (semaphore lock) для обоих обрабатываемых потоков данных. Если вы хотите узнать больше подробностей об этой функции, то можете обратиться к документации библиотеки “_thread” (на английском языке).
1 2 3 |
led1 = Pin(16, machine.Pin.OUT) led2 = Pin(15, machine.Pin.OUT) sLock = _thread.allocate_lock() |
Затем мы будем использовать функцию “CoreTask()” для работы с одиночным потоком на другом ядре. В цикле while внутри данной функции мы будем использовать семафорный затвор чтобы удерживать поток до тех пор, пока он не завершится. После этого мы будем включать 2-й светодиод (led2 high) и выводить необходимые инструкции в выходной терминал редактора Thonny. Затем мы будем высвобождать семафорный затвор когда поток завершится. Функция “_thread.start_new_thread(CoreTask, ())” будет начинать поток – в этой функции в качестве первого параметра передается имя функции. В нашем случае этой функцией будет “CoreTask”. Больше никаких параметров в функцию мы передавать не будем.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def CoreTask(): while True: sLock.acquire() print("Entered into the second Thred") utime.sleep(1) led2.high() print("Led 2 turned on") utime.sleep(2) led2.low() print("Led 2 turned off") utime.sleep(1) print("Exiting from the 2nd Thread") utime.sleep(1) sLock.release() _thread.start_new_thread(CoreTask, ()) |
А в основном цикле программы while мы аналогичным образом будем использовать другой семафорный затвор, поэтому основной поток будет исполняться до тех пор пока он не будет завершен. Этот поток будет переключать первый светодиод (led1) и затем высвобождать семафорный затвор.
1 2 3 4 5 6 7 8 9 10 11 |
while True: # We acquire the semaphore lock sLock.acquire() print("Entered into the main Thred") led1.toggle() utime.sleep(0.15) print("Led 1 started to toggle.") print("Exiting from the main Thread") utime.sleep(1) # We release the semaphore lock sLock.release() |
В Thonny IDE откройте файл “main.py” и чтобы начать его сохранение нажмите на клавиатуре комбинацию клавиш “ctrl+shift+s”. Перед этим убедитесь в том, что ваша плата Raspberry Pi Pico подключена к компьютеру. Во время сохранения кода программы у вас на экране появится всплывающее окно, показанное на рисунке ниже. В нем вы должны выбрать Raspberry Pi Pico, ввести имя файла “main.py” и сохранить его. Эта процедура позволит вашей программе в дальнейшем запускаться на плате всегда, когда на нее подано питание.
Когда вы запустите эту программу на плате Raspberry Pi Pico вы увидите, что первый светодиод (led1), подключенный к контакту GPIO16 платы, будет переключать свое состояние с интервалом 1,15 секунды. А второй светодиод (led2), подключенный к контакту GPIO15 платы, будет мигать с интервалом 2 секунды. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы на MicroPython
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import machine import utime import _thread led1 = machine.Pin(16, machine.Pin.OUT) led2 = machine.Pin(15, machine.Pin.OUT) sLock = _thread.allocate_lock() def CoreTask(): while True: sLock.acquire() print("Entered into the second Thred") # входим во второй поток utime.sleep(1) led2.high() print("Led 2 turned on") utime.sleep(2) led2.low() print("Led 2 turned off") utime.sleep(1) print("Exiting from the 2nd Thread") utime.sleep(1) sLock.release() _thread.start_new_thread(CoreTask, ()) while True: # We acquire the semaphore lock (получаем контроль над семафорным затвором) sLock.acquire() print("Entered into the main Thred") # входим в главный поток led1.toggle() utime.sleep(0.15) print("Led 1 started to toggle.") utime.sleep(1) print("Exiting from the main Thread") # выходим из главного потока utime.sleep(1) # We release the semaphore lock (освобождаем семафорный затвор) sLock.release() |
Или я дурак, или не работает так. У меня эта программа работает последовательно, а не параллельно! Изменение задержек это явно показывает. И в видео видно что переключаются светодиоды последовательно.
Пойду в C++, там вроде есть чё.
Станислав, ну а как они должны переключаться? Вся фишка двухъядерного режима и заключается в том чтобы заставить их переключаться независимо друг от друга
Ну так они зависимо переключаются! Читай внимательнее.
Прочитайте внимательнее объяснение работы программы. Мы функцию _thread.allocate_lock() используем для переключения между потоками данных, а в каждом из этих потоков светодиоды последовательно включаются/выключаются друг за другом, но с разными задержками в каждом из потоков. Но потоки то разные.
Возможно что это действительно 2 разных потока, но я ждал их независимого, параллельного выполнения, а получил то-же самое что без переключателя потоков.
Да, я удалил переключатель и посмотрел.
Сожалею что статья не оправдала ваших ожиданий. А вам нужна многозадачность именно на плате Raspberry Pi Pico? Не удобнее ли для вашей задачи использовать модуль ESP32, обладающий схожими характеристиками и ценой? И для него есть полноценная статья про его программирование в двухъядерном режиме.