Используйте платформу Nano RP2040 Connect для создания электронных гаджетов, метеостанций, роботов и других изобретений. Плата программируется на языке C++ или MicroPython и отлично подойдёт как начинающим мейкерам, так и опытным разработчикам.
Рассмотрим программирование Nano RP2040 Connect на языке C++ в среде Arduino IDE на компьютере под управлением Windows.
Рассмотрим несколько примеров программирования Nano RP2040 Connect на C++ через Arduino IDE.
Библиотеки, которые используют в своём коде программируемый ввод-вывод PIO, используют нумерацию Распиновка.
микроконтроллера RP2040 вместо . Подробности по нумерацию читайте в разделеДля стабильной работы примеров обновите все сторонние библиотеки до последней версии в менеджере библиотек.
Для начала мигнём встроенным светодиодом
на пине.// Даём имя встроенному светодиоду на 13 пине constexpr uint8_t LED_ARDUINO_PIN = 13; void setup() { // Настраиваем пин со светодиодом в режим выхода pinMode(LED_PIN, OUTPUT); } void loop() { // Зажигаем светодиод digitalWrite(LED_PIN, HIGH); // Ждём 1 секунду delay(1000); // Гасим светодиод digitalWrite(LED_PIN, LOW); // Ждём 1 секунду delay(1000); }
После прошивки скетча светодиод начнёт мигать раз в секунду.
Главная фишка Nano RP2040 — это микроконтроллер, который поддерживает возможность программируемого ввода-вывода через блоки PIO, на которых можно реализовать произвольный интерфейс. В следующем примере заставим Arduino рулить светодиодами WS2812.
Для работы примера скачайте и установите библиотеку Adafruit NeoPixel.
// Библиотека для работы со светодиодами WS2812 #include <Adafruit_NeoPixel.h> // Номер пина, к которому подключена матрица WS2812 // Библиотека <Adafruit_NeoPixel.h> использует блок PIO, // который в свою очередь использует нумерацию // микроконтроллера RP2040, а не Arduino. constexpr uint8_t PIN_GPIO_WS2812 = 25; // Количество светодиодов в матрице constexpr uint8_t LED_COUNT = 16; // Создаём объект для работы со светодиодной матрицей Adafruit_NeoPixel matrix = Adafruit_NeoPixel(LED_COUNT, PIN_GPIO_WS2812, NEO_GRB + NEO_KHZ800); void setup() { // Инициализация матрицы matrix.begin(); // Устанавливаем яркость светодиодов // Диапазон значений от 0 до 255 matrix.setBrightness(40); } void loop() { // Заполняем матрицу по сегментам «бегущий огонь» красного цвета colorWipe(matrix.Color(255, 0, 0), 100); // Заполняем матрицу по сегментам «бегущий огонь» зелёного цвета colorWipe(matrix.Color(0, 255, 0), 100); // Заполняем матрицу по сегментам «бегущий огонь» синего цвета colorWipe(matrix.Color(0, 0, 255), 100); // Гасим матрицу по сегментам «бегущая тень» colorWipe(matrix.Color(0, 0, 0), 100); } // Функция заполнения каждого сегмента void colorWipe(uint32_t c, uint8_t wait) { for (uint16_t i = 0; i < matrix.numPixels(); i++) { // Заполняем текущий сегмент выбранным цветом matrix.setPixelColor(i, c); matrix.show(); // Ждём delay(wait); } }
После прошивки управляющей платформы вы увидите заполнение по очереди каждого светодиода матрицы из красного, зелёного и синего цветов.
А теперь попробуем подружить плату с дисплеем и отобразить простой текст.
Для работы примера скачайте и установите библиотеку TroykaTextLCD.
// Библиотека для работы с дисплеем #include <TroykaTextLCD.h> // Номер пина, к которому подключена подсветка дисплея constexpr uint8_t PIN_ARDUINO_LCD_BACKLIGHT = A3; // I²C-адрес дисплея constexpr uint8_t LCD_SLAVE_ADDRESS = 0x3E; // Создаём объект для работы с дисплеем // передаём ему объект I²C, I²C-адрес и пин подсветки TroykaTextLCD lcd(&Wire, LCD_SLAVE_ADDRESS, PIN_ARDUINO_LCD_BACKLIGHT); void setup() { // Устанавливаем количество столбцов и строк экрана lcd.begin(16, 2); // Устанавливаем контрастность в диапазоне от 0 до 63 lcd.setContrast(45); // Устанавливаем яркость в диапазоне от 0 до 255 lcd.setBrightness(255); // Устанавливаем курсор в колонку 0, строку 0 lcd.setCursor(0, 0); // Печатаем первую строку lcd.print("Hello, world!"); // Устанавливаем курсор в колонку 0, строку 1 lcd.setCursor(0, 1); // Печатаем вторую строку lcd.print("Arduino RP2040"); } void loop() { }
После прошивки управляющей платформы на дисплее отобразится приветствующий текст.
Пришло время пошуметь, будем считывать звук со встроенного микрофона и выводить полученные данные в Serial Plotter.
Для работы примера скачайте и установите библиотеку PDM.
// Библиотека для работы с микрофоном #include <PDM.h> // Количество аудио каналов constexpr uint8_t channels = 1; // Частота дискретизации constexpr uint32_t frequency = 16000; // Буфер для хранения семплов short sampleBuffer[512]; // Счётчик пришедших семплов volatile int samplesRead; // Границы верхнего и нижнего диапазона выводимого семпла // Для наглядной визуализации звуковой волны в Serial Plotter constexpr int32_t sampleLimitUpper = 3000; constexpr int32_t sampleLimitLower = -3000; void setup() { // Открываем Serial-порт Serial.begin(115200); while (!Serial); // Выполняем конфигурацию PDM.onReceive(onPDMdata); // Устанавливаем усиление сигнала PDM.setGain(1); // Инициализируем PDM if (!PDM.begin(channels, frequency)) { Serial.println("Failed to start PDM!"); while (1); } } void loop() { // Ожидаем семплы для считывания if (samplesRead) { // Печатаем семплы в Serial-порт for (int i = 0; i < samplesRead; i++) { Serial.print(sampleLimitLower); Serial.print("\t"); Serial.print(sampleBuffer[i]); Serial.print("\t"); Serial.println(sampleLimitUpper); } // сбрасываем счётчик семплов samplesRead = 0; } } // Функция для обработки данных с микрофона void onPDMdata() { // Запрашиваем количество пришедших байтов int bytesAvailable = PDM.available(); // Считываем данные в буфер PDM.read(sampleBuffer, bytesAvailable); // 16 бит, 2 байта на семпл samplesRead = bytesAvailable / 2; }
После прошивки скетча запустите Serial Plotter. А затем попробуйте пошуметь — например, хлопнуть в ладоши или включить музыку.
В продолжение приведём пример отображения платы в пространстве в виде самолёта, а для этого нам поможет встроенный IMU-сенсор. Для запуска примера необходимо прошить платформу Nano RP2040 кодом ниже и настроить графическую среду Processing.
Для работы примера скачайте и установите библиотеку LSM6DSOX.
// Библиотека для работы фильтра Madgwick #include <TroykaIMU.h> // Библиотека для работы IMU-сенсора LSM6DSOXTR #include <Arduino_LSM6DSOX.h> // Создаём объект для фильтра Madgwick Madgwick filter; // Переменные для данных с гироскопа и акселерометра float gx, gy, gz, ax, ay, az; // Переменные для хранения самолётных углов ориентации float yaw, pitch, roll; // Переменная для хранения частоты выборок фильтра float sampleRate = 100; void setup() { // Открываем последовательный порт Serial.begin(9600); // Выводим сообщение об неудачной инициализации IMU if (!IMU.begin()) { Serial.println("Failed to initialize IMU!"); } // Выводим сообщение об удачной инициализации IMU Serial.println("Success to initialize IMU"); // Инициализируем фильтр filter.begin(); } void loop() { // Запоминаем текущее время unsigned long startMillis = millis(); // Считываем данные с акселерометра в единицах G if (IMU.accelerationAvailable()) { IMU.readAcceleration(ax, ay, az); } // Считываем данные с гироскопа в градусах if (IMU.gyroscopeAvailable()) { IMU.readGyroscope(gx, gy, gz); } // Переводим показания гироскопа изградусов в радианы в секунду gx *= DEG_TO_RAD; gy *= DEG_TO_RAD; gz *= DEG_TO_RAD; // Устанавливаем частоту фильтра filter.setFrequency(sampleRate); // Обновляем входные данные в фильтр filter.update(gx, gy, gz, ax, ay, az); if (Serial.available() > 0) { int val = Serial.read(); // Если пришёл символ 's' if (val == 's') { float q0, q1, q2, q3; filter.readQuaternion(q0, q1, q2, q3); // Выводим кватернион в serial-порт Serial.print(q0); Serial.print(","); Serial.print(q1); Serial.print(","); Serial.print(q2); Serial.print(","); Serial.println(q3); } } // Вычисляем затраченное время на обработку данных unsigned long deltaMillis = millis() - startMillis; // Вычисляем частоту обработки фильтра sampleRate = 1000 / deltaMillis; }
import processing.serial.*; import toxi.geom.*; import toxi.processing.*; // NOTE: requires ToxicLibs to be installed in order to run properly. // 1. Download from http://toxiclibs.org/downloads // 2. Extract into [userdir]/Processing/libraries // (location may be different on Mac/Linux) // 3. Run and bask in awesomeness // The serial port Serial port; String message; float[] q = new float[4]; Quaternion quat = new Quaternion(1, 0, 0, 0); // New line character in ASCII final char newLine = '\n'; String [] massQ = new String [4]; float[] ypr = new float[3]; void setup() { // Size form 400x400 size(400, 400, P3D); // Open serial port // Replace "COM7" with the COM port on which your arduino is connected port = new Serial(this, "COM7", 9600); } void draw() { // Read and parse incoming serial message serialEvent(); // Set background to black background(0); printQuaternions(); printYawPitchRoll(); // Set position to centre translate(width / 2, height / 2); // Begin object pushMatrix(); float[] axis = quat.toAxisAngle(); rotate(axis[0], axis[2], axis[3], axis[1]); // Draw main body in red drawBody(); // Draw front-facing tip in blue drawCylinder(); // Draw Triangles drawTriangles(); // Draw Quads drawQuads(); // End of object popMatrix(); // Send character 's' to Arduino port.write('s'); } void serialEvent() { // Read from port until new line (ASCII code 13) message = port.readStringUntil(newLine); if (message != null) { // Split message by commas and store in String array massQ = split(message, ","); q[0] = float(massQ[0]); q[1] = float(massQ[1]); q[2] = float(massQ[2]); q[3] = float(massQ[3]); } // Print values to console print(q[0]); print("\t"); print(q[1]); print("\t"); print(q[2]); print("\t"); print(q[3]); println("\t"); // Set our toxilibs quaternion to new data quat.set(q[0], q[1], q[2], q[3]); } void drawCylinder() { float topRadius = 0; float bottomRadius = 20; float tall = 20; int sides = 8; // Begin object pushMatrix(); translate(0, 0, -120); rotateX(PI/2); fill(0, 0, 255, 200); float angle = 0; float angleIncrement = TWO_PI / sides; beginShape(QUAD_STRIP); for (int i = 0; i < sides + 1; ++i) { vertex(topRadius * cos(angle), 0, topRadius * sin(angle)); vertex(bottomRadius * cos(angle), tall, bottomRadius * sin(angle)); angle += angleIncrement; } endShape(); // if it is not a cone, draw the circular top cap if (topRadius != 0) { angle = 0; beginShape(TRIANGLE_FAN); // Center point vertex(0, 0, 0); for (int i = 0; i < sides + 1; i++) { vertex(topRadius * cos(angle), 0, topRadius * sin(angle)); angle += angleIncrement; } endShape(); } // If it is not a cone, draw the circular bottom cap if (bottomRadius != 0) { angle = 0; beginShape(TRIANGLE_FAN); // Center point vertex(0, tall, 0); for (int i = 0; i < sides + 1; i++) { vertex(bottomRadius * cos(angle), tall, bottomRadius * sin(angle)); angle += angleIncrement; } endShape(); } popMatrix(); } void drawBody() { fill(255, 0, 0, 200); box(10, 10, 200); } void drawTriangles() { // Draw wings and tail fin in green fill(0, 255, 0, 200); beginShape(TRIANGLES); // Wing top layer vertex(-100, 2, 30); vertex(0, 2, -80); vertex(100, 2, 30); // Wing bottom layer vertex(-100, -2, 30); vertex(0, -2, -80); vertex(100, -2, 30); // Tail left layer vertex(-2, 0, 98); vertex(-2, -30, 98); vertex(-2, 0, 70); // Tail right layer vertex( 2, 0, 98); vertex( 2, -30, 98); vertex( 2, 0, 70); endShape(); } void drawQuads() { beginShape(QUADS); vertex(-100, 2, 30); vertex(-100, -2, 30); vertex( 0, -2, -80); vertex( 0, 2, -80); vertex( 100, 2, 30); vertex( 100, -2, 30); vertex( 0, -2, -80); vertex( 0, 2, -80); vertex(-100, 2, 30); vertex(-100, -2, 30); vertex(100, -2, 30); vertex(100, 2, 30); vertex(-2, 0, 98); vertex(2, 0, 98); vertex(2, -30, 98); vertex(-2, -30, 98); vertex(-2, 0, 98); vertex(2, 0, 98); vertex(2, 0, 70); vertex(-2, 0, 70); vertex(-2, -30, 98); vertex(2, -30, 98); vertex(2, 0, 70); vertex(-2, 0, 70); endShape(); } void printQuaternions() { // Set text mode to shape textMode(SHAPE); textSize(13); fill(255, 255, 255); text("Quaternions:", 20, 20, 10); text(q[0], 20, 40, 10); text(q[1], 20, 60, 10); text(q[2], 20, 80, 10); text(q[3], 20, 100, 10); } void printYawPitchRoll() { // Calculate yaw/pitch/roll angles ypr[0] = atan2(2*q[1]*q[2] - 2*q[0]*q[3], 2*q[0]*q[0] + 2*q[1]*q[1] - 1) * 57.2; ypr[1] = atan2(2 * q[2] * q[3] - 2 * q[0] * q[1], 2 * q[0] * q[0] + 2 * q[3] * q[3] - 1) * 57.2; ypr[2] = -atan2(2 * (q[0] * q[2] - q[1] * q[3]), 1 - 2 * (q[2] * q[2] + q[1] *q[1])) * 57.2; text("Yaw:", 150, 20, 10); text(ypr[0], 150, 40, 10); text("Pitch:", 220, 20, 10); text(ypr[1], 220, 40, 10); text("Roll:", 290, 20, 10); text(ypr[2], 290, 40, 10); }
При запуске визуализации на Processing откроется окно с графическим отображением платы в виде самолёта. Самолёт на экране будет повторять перемещения IMU-сенсора в пространстве.
Приведём пример, как соединить две платы по беспроводной сети.
// Библиотека для общения устройств по BLE #include <ArduinoBLE.h> // Номер пина, к которому подключена кнопка constexpr uint8_t PIN_ARDUINO_BUTTON = 19; // Переменная, для хранения состояния светодиода boolean ledSwitch; // BLE LED Service BLEService LEDService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central BLEByteCharacteristic LEDCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify | BLEWrite); void setup() { // Открываем Serial-порт Serial.begin(9600); // Ожидаем подключение по USB // Если не используете отладку, удалите строку while (!Serial); // Кнопку в режим входа pinMode(PIN_ARDUINO_BUTTON, INPUT); // Инициализируем BLE if (!BLE.begin()) { Serial.println("Starting BLE failed!"); } // Устанавливаем локальное имя BLE.setLocalName("Button Device LED"); // Устанавливаем службу UUID BLE.setAdvertisedService(LEDService); // Добавляем характеристику LEDService.addCharacteristic(LEDCharacteristic); // Добавляем сервис BLE.addService(LEDService); // Запускаем BLE.advertise(); Serial.println("BLE LED Peripheral, waiting for connections…"); } void loop() { // Слушаем все устройства BLE BLEDevice central = BLE.central(); // Если Central Device подключилось if (central) { Serial.print("Connected to central: "); // Выводим MAC-адрес подключенного устройства Serial.println(central.address()); // Пока Central Device подключено к переферийному while (central.connected()) { // Считываем состояние кнопки bool buttonState = digitalRead(PIN_ARDUINO_BUTTON); // Если кнопка нажата if (buttonState == LOW) { ledSwitch = !ledSwitch; delay(500); // Если светодиод не горит, включаем его // Если светодиод горит, выключаем его if (ledSwitch) { Serial.println("ON"); LEDCharacteristic.writeValue((byte)0x01); } else { LEDCharacteristic.writeValue((byte)0x00); Serial.println("OFF"); } } } // Если Central Device отключилось // Выводим сообщение в Serial-порт Serial.print(F("Disconnected from central: ")); Serial.println(central.address()); } }
// Библиотека для общения устройств по BLE #include <ArduinoBLE.h> // Номер пина, к которому подключен светодиод constexpr uint8_t PIN_ARDUINO_LED = 14; void setup() { // Открываем Serial-порт Serial.begin(9600); // Ожидаем подключение по USB // Если не используете отладку, удалите строку while (!Serial); // Светодиод в режим выхода pinMode(PIN_ARDUINO_LED, OUTPUT); // Инициализируем BLE BLE.begin(); Serial.println("BLE Central - LED control"); // Сканируем все устройства LED BLE peripherals BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214"); } void loop() { // Проверяем было ли найдено Peripheral Device BLEDevice peripheral = BLE.available(); // Если Peripheral Device найдено, выводим информацию в Serial-порт if (peripheral) { Serial.print("Found "); Serial.print(peripheral.address()); Serial.print(" '"); Serial.print(peripheral.localName()); Serial.print("' "); Serial.print(peripheral.advertisedServiceUuid()); Serial.println(); // Если имя Peripheral Device не содержит фразу «LED» // Выходим из функции if (peripheral.localName().indexOf("LED") < 0) { Serial.println("No 'LED' in name"); return; } // Останавливаем сканирование BLE.stopScan(); controlLed(peripheral); // Если Peripheral Device отключено, продолжаем сканирование BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214"); } } void controlLed(BLEDevice peripheral) { // Подключаемся к Peripheral Device Serial.println("Connecting ..."); if (peripheral.connect()) { Serial.println("Connected"); } else { Serial.println("Failed to connect!"); return; } // Сканируем свойства Peripheral Device Serial.println("Discovering attributes ..."); if (peripheral.discoverAttributes()) { Serial.println("Attributes discovered"); } else { Serial.println("Attribute discovery failed!"); peripheral.disconnect(); return; } // Получаем текущее состояние светодиода BLECharacteristic LEDCharacteristic = peripheral.characteristic( "19b10001-e8f2-537e-4f6c-d104768a1214"); if (!LEDCharacteristic) { Serial.println("Peripheral does not have LED characteristic!"); peripheral.disconnect(); return; } while (peripheral.connected()) { if (LEDCharacteristic.canRead()) { byte value = LEDCharacteristic.read(); LEDCharacteristic.readValue(value); if (value == 0x01) { Serial.println("ON"); digitalWrite(PIN_ARDUINO_LED, HIGH); } else if (value == 0x00) { digitalWrite(PIN_ARDUINO_LED, LOW); Serial.println("OFF"); } } delay(500); } Serial.println("Peripheral disconnected"); }
Рассмотрим программирование Nano RP2040 Connect на языке MycroPython на компьютере под управлением Windows.
Рассмотрим несколько примеров программирования Nano RP2040 Connect на MicroPython.
Интерпретатор Python используют нумерацию Распиновка.
микроконтроллера RP2040 вместо . Подробности про нумерацию читайте в разделеДля начала мигнём встроенным светодиодом
на плате Nano RP2040. В коде используем нумерацию .# Библиотека для работы с пинами ввода-вывода from machine import Pin # Библиотека для работы с временем import time # Даём имя встроенному светодиоду # на 6 пине нумерации GPIO LED_GPIO_PIN = 6 # Светодиод в режим выхода на 6 пине led = Pin(6, Pin.OUT) while True: # Зажигаем светодиод led.value(1) # Ждём 1 секунду time.sleep(1) # Гасим светодиод led.value(0) # Ждём 1 секунду time.sleep(1)
После прошивки скетча, светодиод начнёт мигать раз в секунду.
Arduino Nano RP2040 Connect поддерживает два метода загрузки: штатный режим и режим загрузчика.
Платформа загружается с внешней Flash-памяти, распаянной на плате Arduino Nano RP2040 Connect. В диспетчере устройств OS Windows плата отображается как виртуальный COM-порт с именем . Режим служит для загрузки пользовательских программ через Arduino IDE, Thonny Python и друг сред разработки.
Активация режима происходит простым подключением платы по USB.
Платформа загружается с внутренней памяти микроконтроллера RP2040. В диспетчере устройств OS Windows плата отображается как съёмный накопитель с именем
. Режим служит для загрузки прошивки в формате простым перемещением файла с одного носителя на другой. Активация режима:
Платформа Arduino Nano RP2040 Connect выполнена на одноименном чипе RP2040 от компании Raspberry Pi Foundation. Кристалл содержит двухъядерный процессор на архитектуре ARM Cortex M0+ с тактовой частотой до 133 МГц. На RP2040 также расположились часы реального времени, датчик температуры и SRAM-память на 264 КБ. А Flash-память на 16 МБ расположилась на плате отдельной микросхемой.
Для хранения программ и сопутствующих статичных ресурсов на плате распаяна внешняя Flash-память AT25SF128A-MHB-T объёмом 16 МБ.
За беспроводную передачу данных отвечает чип U-blox NINA-W102 со встроенным чипом ESP32 для обмена данными по воздуху в диапазоне 2,4 ГГц по Wi-Fi и Bluetooth. Для работы с модулем используйте библиотеку WiFiNINA.
Инерционный модуль на чипе LSM6DSOXTR трёхосевой акселерометр и трёхосевой гироскоп для распознавания движений и моушен-функций с жестами. Для работы с модулем используйте библиотеку ArduinoLSM6DSOX.
Встроенный цифровой микрофон MP34DT05 пригодится для распознавания коротких голосовых команд или записи звука. Для работы с модулем используйте библиотеку PDM.
Криптографический сопроцессор Microchip ATECC608A интегрирует протокол безопасности ECDH (Elliptic Curve Diffie Hellman) в сверхзащищённый метод, обеспечивающий согласование ключей для шифрования / дешифрования, наряду с ECDSA (алгоритм цифровой подписи эллиптической кривой) для проверки подлинности с подписью для Интернета вещей (IoT), включая домашнюю автоматизацию, промышленные сети, медицинские услуги, аутентификацию аксессуаров и расходных материалов.
Понижающий DC-DC преобразователь MP2322 обеспечивает питание микроконтроллера RP2040 и другой логики на плате. Диапазон входного напряжения от 5 до 18 вольт. Выходное напряжение 3,3 В с максимальным выходным током 1 А.
Имя светодиода | Назначение |
---|---|
ON | Индикатор питания платформы. |
L | Пользовательский светодиод, подключенный к пину | микроконтроллера RP2040. При задании значения высокого уровня светодиод включается, при низком – выключается. Для управления светодиодом в Arduino IDE используйте нумерацию Arduino — пин или определение . А в среде Thonny Python используйте нумерацию портов микроконтроллера PR2040 — пин .
RGB | Пользовательский RGB-светодиод с общим анодом. Катоды красного, зелёного и синего цвета выведены на пины WiFiNINA и встроенные определения , и . | , и беспроводного модуля NINA-W102. При задании значения высокого уровня светодиоды выключаются, при низком – включаются. Для управления RGB-светодиодом в Arduino IDE используйте библиотеку
Разъём USB Micro предназначен для прошивки и питания платформы. Для подключения к ПК понадобится кабель USB (A — Micro USB).
Кнопка предназначена для ручного сброса прошивки — аналог кнопки
обычного компьютера.