Программирование микроконтроллера PIC32 с помощью Rust

от автора


Простой проект для начинающих электронщиков, которые непрочь попрактиковаться в программировании микроконтроллеров серии 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, ну а пока…

Успехов вам в написании кода!


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/645253/