
Простой проект для начинающих электронщиков, которые непрочь попрактиковаться в программировании микроконтроллеров серии PIC32MX на Rust. Здесь мы соберем макетную плату со светодиодом, напишем короткую программу, чтобы им помигать, и загрузим эту программу в микроконтроллер, попутно разобрав нюансы работы с контейнерами Rust и программатором.
Я уже давненько хотел вернуться к проектам с микроконтроллерами. В последний раз я связывался с кодом для микроконтроллеров несколько лет назад, когда собирал другу регулятор нагрева на Arduino Nano. Мой друг интересовался программированием. Он приходил ближе к ночи, и мы вместе работали над этим устройством. Тогда у нас возникли проблемы с подачей питания, которая некорректно работала при использовании любого старого блока питания. Как бы то ни было, в итоге мы постепенно утратили интерес к этой затее, так и не доведя ее до этапа тестирования. Может, дело было в том, что мы проводили отладку уже поздно ночью, к тому же не без участия виски, кто знает…
Однако недавно у меня возникло желание опробовать Rust в программировании встраиваемых устройств. В качестве микроконтроллера для задуманного проекта я решил взять модель из семейства PIC32MX. Он имеет достаточное число контактов ввода-вывода, встроенную поддержку USB и может куда больше, чем просто помигать светодиодом.
Дисклеймер: эта статья предназначена для новичков вроде меня, которые желают попрактиковаться с программированием PIC32 при помощи Rust. Это моя первая попытка программирования микроконтроллера не на плате Arduino. У меня довольно большие пробелы в знаниях по этой теме, так что я всегда открыт к рекомендациям и обсуждению. Можете писать мне в темах на Reddit, Twitter или на электронную почту (страница контактов автора).
Можно было найти готовую макетную плату PIC32, но я решил взять голую микросхему. Это решение не столь удобно, зато предоставляет ��ольше возможностей для обучения. Для проекта нам понадобится ряд компонентов и, естественно, микроконтроллер PIC32 (хотя бы один).
Ниже перечислен необходимый минимум, который потребуется для написания “Hello World” (помигать светодиодом).
- макетная плата;
- проволочные перемычки;
- светодиод;
- Программатор PICkitT3;
- PIC32 (в моем случае MX270F256B);
- набор керамических дисковых конденсаторов;
- набор резисторов;
- кнопки и прочее на случай, если вы захотите расширить программу.
Я рекомендую приобрести недорогой стартовый комплект радиолюбителя, в котором есть множество компонентов вроде резисторов, конденсаторов, светодиодов и т.д.
Как сообщает Википедия (англ.):
Микроконтроллер – это небольшой компьютер на базе одной интегральной МОП-микросхемы. Микроконтроллер включает в себя одно или более процессорных ядер, а также память и программируемые периферийные устройства ввода/вывода. Микросхема также зачастую снабжается программной памятью, представленной в виде сегнетоэлектрической RAM, NOR-флэш или OTP ROM, а также небольшим объемом RAM. Микроконтроллеры предназначены для встраиваемых приложений, в отличие от микропроцессоров, используемых в персональных компьютерах или других устройствах, состоящих из различных гибридных микросхем.
Итак, путешествие в кроличью нору началось. Я нашел pic32-rs, контейнер Rust для работы с PIC. После получения своих микросхем мне не терпелось поскорее приступить к делу, но я понятия не имел, с чего начать. Duckduckgo не особо помог мне разобраться. По не ясной причине большинство руководств по PIC оказались устаревшими. Похоже, что интерес к этой серии микроконтроллеров упал с тех пор, как сцену заполонили Arduino и сотни других плат. В итоге же выяснилось, что можно получить немало информации из одной только спецификации для семейства PIC32MX. Помимо этого, в доступе есть руководство для программатора PICkitT3.
Подключение комплектующих
Микроконтроллер, а также кнопку сброса и светодиод я подключил к монтажной плате по приведенной ниже схеме.

Рекомендованный минимум подключений PIC32MX

Таблица распиновки PIC32MX270F256B

Распиновка штекера PICkit3
Я запитал микроконтроллер и реализовал необходимый минимум подключений, но как мне его прошить. Компания Microchip предоставляет версию своих инструментов
MPLAB IDE и MPLAB IPE для Linux. В результате же мне не удалось с их помощью даже проверить правильность подключения всей сборки. Тогда было решено обратиться к опенсорс решениям.Небольшие заминки
Я скопировал
pic32-rs локально и после выполнения предложенных инструкций смог успешно скомпилировать пример blinky. Однако при попытках загрузить hex-файл через MPLAB IPE я продолжал получать следующую ошибку:***************************************************** Connecting to MPLAB PICkit 3... Currently loaded firmware on PICkit 3 Firmware Suite Version.....01.56.09 Firmware type..............PIC32MX Target voltage detected Target device PIC32MX270F256B found. Device ID Revision = A2 Loading code from /home/.../Projects/pic32-rs/examples/blinky/blinky.hex... Warning: /home/.../Projects/pic32-rs/examples/blinky/blinky.hex contains code that is located at addresses that do not exist on the PIC32MX270F256B. Code incompletely loaded starting at 0x9D000000 (0x3D4). 2021-08-20 11:17:58 +0100 - Hex file loaded successfully. 2021-08-20 11:18:06 +0100 - Programming... Device Erased... Programming... The following memory area(s) will be programmed: MPLAB's memory is blank so no programming operation was attempted. 2021-08-20 11:18:10 +0100 - Programming complete
Тогда я решил спросить об этом Kiffie, создателя контейнера, который пояснил:
«Проблема в том, чтоobjcopyсоздает hex-файлы не с физическими, а с виртуальными адресами. Похоже, программатор от Microchip способен обрабатывать hex-файлы только с физическими адресами. У меня такой проблемы не возникало, так как я использую другой инструмент:pic32prog».
Следуя его рекомендации, я смог преобразовать создаваемый компилятором бинарник с помощью
xc32-bin2hex, но загрузить полученный hex-файл мне все равно не удавалось. Оказалось же, что для успешного программирования PIC32 необходимо установить керамический конденсатор 10мкФ на выводе 20 (VCAP). Наконец-то светодиод замигал. Прошивка PIСkit3 под использование с помощью pic32prog
Теперь меня не устраивала необходимость задействовать массивное Java-приложение лишь для загрузки hex-файла после каждого его редактирования. Тогда я начал разбираться с
pic32prog в надежде использовать эту утилиту. Я скопировал репозиторий и скомпилировал инструмент, но он отказался распознавать PIСkitT3, так как для него требовалась другая прошивка. Заглянув в документацию
pic32prog, я нашел раздел Flashing on Linux using pickit-3. Для прошивки программатора нужно либо иметь доступ к машине с Windows, либо запустить VM с Windows. Не стоит беспокоиться, если в процессе прошивки система подвиснет и откажется его распознавать. У меня случилось то же самое, но в итоге программатор работает исправно. Лишний раз повторю, что при использовании этой конфигурации вам не потребуется
MATLAB IDE. Просто скомпилируйте код с помощью Cargo (в проекте есть удобный скрипт build.sh) и запишите его командой pic32prog -p blinky.hex. Опция -p обеспечит подачу питания устройству от PIСkit3, так что дополнительного источника 3.3В вам не потребуется.Что это за hex-числа?
Как видите,
blinky/src/main.rs – это небольшая программа, но ввиду своей неопытности я не до конца понимаю, что здесь происходит. Первым мое внимание привлекло следующее:// Регистры конфигурации PIC32 для PIC32MX1xx и PIC32MX2xx #[cfg(any( feature = "pic32mx1xxfxxxb", feature = "pic32mx2xxfxxxb" ))] #[link_section = ".configsfrs"] #[no_mangle] pub static CONFIGSFRS: [u32; 4] = [ 0x0fffffff, // DEVCFG3 0xfff9ffd9, // DEVCFG2 0xff7fcfd9, // DEVCFG1 0x7ffffffb, // DEVCFG0 ];
Похоже, что это основные биты настройки регистров конфигурации в семействах микросхем pic32mx1xxfxxxb и pic32mx2xxgxxxb. Чтобы хоть как-то разобраться, я преобразовал эти значения в двоичный формат:
0x0fffffff, // DEVCFG3 00001111 11111111 11111111 11111111 0xfff9ffd9, // DEVCFG2 11111111 11111001 11111111 11011001 0xff7fcfd9, // DEVCFG1 11111111 01111111 11001111 11011001 0x7ffffffb, // DEVCFG0 01111111 11111111 11111111 11111011
В документации без проблем можно обнаружить, что
DEVCFG0 имеет возможные значения для всех 32 бит, определенных выше. Например, бит 31 зарезервирован для записи в качестве 0, биты 30-29 должны быть 1, бит 28 указывает на то, включена ли защита кода (1 значит отключена, наш случай). В общем, смысл понятен. Остальную конфигурацию с помощью документации настроить несложно.Далее мы можем использовать одну из возможностей
pic32-hal, а именно pac::Peripherals::take(), для получения опциональной структуры, которая даст нам доступ к периферии, включая отдельные выводы PIC.В программе
blinky мы используем системный тактовый генератор (на PIC32MX270F256B) с частотой 40МГц, с помощью которого управляем встроенным таймером, вводя задержку мигания светодиода. Этот тактовый генератор настраивается с помощью слов конфигурации (через активацию внутреннего RC-генератора 8МГц и PLL).// настройка объекта для управления тактовым генератором let sysclock = 40_000_000_u32.hz(); let clock = Osc::new(p.OSC, sysclock); let mut timer = Delay::new(sysclock);
Когда я связался с создателем контейнера
pic32-rs, то он любезно ответил на несколько моих вопросов о правильной настройке и первом запуске. Он также добавил в пример blinky протоколирование UART, так как это может пригодиться при отладке, если вы соберетесь расширить пример и захотите отправлять вывод/значения с микроконтроллера на ПК.К удобству, у меня под рукой нашелся usb-модуль, который я подключил к своей сборке, соединив его Rx вывод с выводом RB0 AKA PIN 4 у PIC.
Взаимодействие с внешними устройствами реализуется с помощью встроенного в PIC универсального асинхронного приемопередатчика (UART). Важно учитывать, что здесь необходимо использовать одинаковую величину
buad (в нашем случае 115200) и для minicom, и при инициализации Uart2. В противном случае в терминал может выводиться неразбериха. Наблюдательный читатель мог заметить, что в проекте есть пара файлов, а именно
32MX270F256B_procdefs.ld и pic32_common.ld. Для меня они оказались незнакомы, так как до этого из микроконтроллеров я работал только с Arduino. Тогда я снова решил спросить Kiffie, который ответил следующее:Это фрагмент скрипта компоновщика, который в основном определяет структуру памяти микросхемы. Сюда входятdevices.x(часть Peripheral Access Crate, PAC) иpic32_common.ld(основная часть скрипта). В Arduino подробности компоновки могут не быть столь очевидны для пользователя.
Наконец, вот этот простой пример с миганием светодиода (немного отличный от того, что лежит в репозитории
pic32-rs).//! Blinky //! //! Классический пример мигания светодиода для PIC32MX1xx или PIC32MX2xx в 28-контактном корпусе //! Тактовые сигналы генерируются внутренним RC-генератором. //! Поэтому никаких внешних компонентов, кроме конденсатора 10мкФ в Vcap (вывод 20) и светодиода, подключенного через резистор (например, 470Ом) к RB7 (вывод 16),//! не требуется. //! RB0 (вывод 4) используется для вывода текста через UART2. #![no_main] #![no_std] use core::{fmt::Write, panic::PanicInfo}; use embedded_hal::{blocking::delay::DelayMs, digital::v2::*}; use mips_rt::{self, entry}; use pic32_hal::{ clock::Osc, coretimer::Delay, gpio::GpioExt, pac, time::U32Ext, uart::Uart, }; // Регистры конфигурации PIC32 для PIC32MX1xx и PIC32MX2xx #[cfg(any(feature = "pic32mx1xxfxxxb", feature = "pic32mx2xxfxxxb"))] #[link_section = ".configsfrs"] #[no_mangle] pub static CONFIGSFRS: [u32; 4] = [ 0x0fffffff, // DEVCFG3 00001111 11111111 11111111 11111111 0xfff9ffd9, // DEVCFG2 11111111 11111001 11111111 11011001 0xff7fcfd9, // DEVCFG1 11111111 01111111 11001111 11011001 0x7ffffffb, // DEVCFG0 01111111 11111111 11111111 11111011 ]; #[entry] fn main() -> ! { let p = pac::Peripherals::take().unwrap(); let pps = p.PPS; pps.rpb0r.write(|w| unsafe { w.rpb0r().bits(0b0010) }); // U2TX on RPB0 // объект для управления тактовым генератором let sysclock = 40_000_000_u32.hz(); let clock = Osc::new(p.OSC, sysclock); let mut timer = Delay::new(sysclock); let uart = Uart::uart2(p.UART2, &clock, 115200); timer.delay_ms(10u32); let (mut tx, _) = uart.split(); writeln!(tx, "Blinky example\n").unwrap(); let parts = p.PORTB.split(); let mut led = parts.rb7.into_push_pull_output(); let mut on = true; loop { writeln!(tx, "LED status: {}", on).unwrap(); if on { led.set_high().unwrap(); } else { led.set_low().unwrap(); } on = !on; timer.delay_ms(500u32); } } #[panic_handler] fn panic(_panic_info: &PanicInfo<'_>) -> ! { loop {} }
Если вы захотите задействовать функционал Cargo, то понадобиться подредактировать файл
/.cargo/config и runner = "./flash.sh", после чего команда cargo run будет собирать программу и записывать ее на устройство.В дальнейшем я планирую заняться примером программы для USB, ну а пока…
Успехов вам в написании кода!

