Rust embedded. Gpio.

Rust embedded. Stopwatch.
Rust embedded. Spi и embedded-graphics.
Rust embedded. Gpio.
Rust embedded. Сиквел.
Rust embedded.

Сегодня речь про gpio и rust. Gpio основной интерфейс для старта.  Сегодня мы поморгаем светодиодами на отладочной плате stm32f429i-disco и сделаем это двумя способами.

Интерфейс ввода/вывода общего назначения (англ. general-purpose input/output, GPIO) — интерфейс для связи между компонентами компьютерной системы, к примеру микропроцессором и различными периферийными устройствами. Контакты GPIO могут выступать как в роли входа, так и в роли выхода — это, как правило, конфигурируется. GPIO контакты часто группируются в порты.

На микроконтроллерах серии STM32 порты общего назначения обозначаются как: GPIOA, GPIOB и т.д. На схеме микроконтроллера соответствующие портам выводы обозначаются PA0-PA15, PB0-PB15 и т.д. Порты 16- разрядные, у каждого порта 16 выводов. Режим и состояние каждого вывода могут быть установлены отдельно, независимо от других выводов. Каждый вывод может использоваться в режиме:

    • Input floating – вход без подтягивающего резистора, порт никуда не подключен;
    • Input pull-up – вход с подтягивающим резистором, порт подключен к питанию микроконтроллера;
    • Input pull-down – вход с подтягивающим резистором, подключенным к общему сигналу (GND);
    • Analog – аналоговый вход;
    • Output open-drain – выход с открытым стоком. Выход или подключен к “0” или в воздухе болтается (например, для внешней подтяжки вверх);
    • Output push-pull – обычный активный выход. При низком логическом уровне напряжение на выводе равно 0, при высоком – напряжение близко к напряжению питания микроконтроллера, обычно + 3 В;
    • Alternate function push-pull – альтернативная функция вывода в обычном (активном) режиме;
    • Alternate function open-drain – альтернативная функция вывода в режиме открытый сток;

Первый пример. Простое baremetal firmware. Baremetal – это небольшой код, инициализирующий ядро/ядра, и передающий управление пользовательской программе, которую по какой-то причине требуется запустить без нормальной ОС и без ОСРВ. На отладочной плате у нас присутствуют два пользовательских светодиода LD3 и LD2, и им соответствующие пины микроконтроллера PG13 и PG14.

cargo-features = ["default-run"]

[package]
authors = ["Roman Shulenkov postmaster@mcu.by"]
categories = ["embedded", "no-std"]
name = "stm32f429i_disco"
version = "0.1.0"
edition = "2018"

[dependencies]
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
panic-halt = "0.2.0"

[dependencies.stm32f4]
version = "0.10.0"
features = ["stm32f429", "rt"]

[dependencies.stm32f4xx-hal]
version = "0.7"
features = ["rt", "stm32f429"]
#![deny(unsafe_code)]
#![no_main]
#![no_std]

// Halt on panic
#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964
extern crate panic_halt; // panic handler

use cortex_m;
use cortex_m_rt::entry;
use stm32f4xx_hal as hal;

use crate::hal::{prelude::*, stm32};

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (
        stm32::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {
        // Set up the LEDs. On the stm32f429i-disco they are connected to pin PG13 and PG14.
        let gpiog = dp.GPIOG.split();
        let mut led3 = gpiog.pg13.into_push_pull_output();
        let mut led4 = gpiog.pg14.into_push_pull_output();

        // Set up the system clock. We want to run at 180MHz for this one.
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(180.mhz()).freeze();

        // Create a delay abstraction based on SysTick
        let mut delay = hal::delay::Delay::new(cp.SYST, clocks);

        loop {
            // On for 1s, off for 1s.
            led3.set_high().unwrap();
            led4.set_low().unwrap();
            delay.delay_ms(1000_u32);
            
            led3.set_low().unwrap();
            led4.set_high().unwrap();
            delay.delay_ms(1000_u32);
        }
    }

    loop {}
}

Второй пример по функционалу полностью как первый пример, только мы воспользуемся фреймворком RTMF. RTMF – это среда параллелизма для построения систем реального времени.

Возможности RTFM:

  • Задачи – единица конкуренции. Задачи могут запускаться по событию (в ответ на асинхронный стимул) или вызываться программно по желанию.
  • Передача сообщений между задачами. А именно, сообщения можно передавать программным задачам в момент вызова.
  • Очередь таймера. Программные задачи можно планировать на запуск в определенный момент в будущем. Это свойство можно использовать, чтобы реализовывать периодические задачи.
  • Поддержка приоритетов задач, и таким образом, вытесняющей многозадачности.
  • Эффективное, свободное от гонок данных разделение памяти через хорошо разграниченные критические секции на основе приоритетов.
  • Выполнение без взаимной блокировки задач, гарантированное на этапе компиляции. Это более сильная гарантия, чем предоставляемая стандартной абстракцией Mutex.
  • Минимальные затраты на диспетчеризацию. Диспетчер задач имеет минимальный след; основная часть работы по диспетчеризации делается аппаратно.
  • Высокоэффективное использование памяти: Все задачи используют общий стек вызовов и нет сильной зависимости от динамического распределителя памяти.
  • Все устройства Cortex-M полностью поддерживаются.
  • Эта модель задач поддается известному анализу методом WCET (наихудшего времени исполнения) и техникам анализа диспетчеризации (хотя разработчики еще не разработали дружественных инструментов для этого).

Все программы на RTFM используют атрибут app (#[app(..)]). Этот атрибут нужно применять к const-элементам, содержащим элементы. Атрибут app имеет обязательный аргумент device, в качестве значения которому передается путь. Этот путь должен указывать на библиотеку устройства, сгенерированную с помощью svd2rust v0.14.x. Атрибут app развернется в удобную точку входа, поэтому нет необходимости использовать атрибут cortex_m_rt::entry.

init
Внутри псевдо-модуля атрибут app ожидает запуска функции инициализации, обозначенную атрибутом init. Эта функция должна иметь сигнатуру [unsafe] fn().

Эта функция инициализации будет первой частью запускаемого приложения. Функция init запустится с отключенными прерываниями и будет иметь эксклюзивный доступ к периферии Cortex-M и специфичной для устройства периферии через переменные core и device, которые внедряются в область видимости init атрибутом app. Не вся периферия Cortex-M доступна в core, потому что рантайм RTFM принимает владение частью из неё.

idle
Функция, помеченная атрибутом idle может присутствовать в псевдо-модуле опционально. Эта функция используется как специальная задача ожидания и должна иметь сигнатуру [unsafe] fn() – > !.

Когда она присутствует, рантайм запустит задачу idle после init. В отличие от init, idle запустится с включенными прерываниями и не может завершиться, поэтому будет работать бесконечно.

cargo-features = ["default-run"]

[package]
authors = ["Roman Shulenkov postmaster@mcu.by"]
categories = ["embedded", "no-std"]
name = "stm32f429i_disco"
version = "0.1.0"
edition = "2018"

[dependencies]
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
panic-halt = "0.2.0"
panic-semihosting = "0.5.3"
cortex-m-semihosting = "0.3.5"
cortex-m-rtfm = "0.5.1"

[dependencies.stm32f4]
version = "0.10.0"
features = ["stm32f429", "rt"]

[dependencies.stm32f4xx-hal]
version = "0.7"
features = ["rt", "stm32f429"]
#![no_main]
#![no_std]

use panic_semihosting as _;
use rtfm::app;

use cortex_m;
use stm32f4xx_hal as hal;
use crate::hal::{prelude::*, stm32};
use stm32f4::stm32f429;

#[app(device = stm32f429)]
const APP: () = {        

    struct Resources {
        led3: stm32f4xx_hal::gpio::gpiog::PG13<stm32f4xx_hal::gpio::Output<stm32f4xx_hal::gpio::PushPull>>,
        led4: stm32f4xx_hal::gpio::gpiog::PG14<stm32f4xx_hal::gpio::Output<stm32f4xx_hal::gpio::PushPull>>,
        delay: stm32f4xx_hal::delay::Delay,
    }

    #[init]
    fn init(_cx: init::Context) -> init::LateResources{    

        let dp =  stm32::Peripherals::take().unwrap();
        let cp =  cortex_m::peripheral::Peripherals::take().unwrap();
 
        let gpiog = dp.GPIOG.split();
         
        let led3 = gpiog.pg13.into_push_pull_output();
        let led4 = gpiog.pg14.into_push_pull_output();

        // Set up the system clock. We want to run at 180MHz for this one.
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(180.mhz()).freeze();

        // Create a delay abstraction based on SysTick
        let delay = hal::delay::Delay::new(cp.SYST, clocks);

        init::LateResources {
            led3,
            led4,
            delay,
        } 
    }

    #[idle (resources = [led3, led4, delay])] 
    fn idle(_cx: idle::Context) -> ! {

        let  led3: &mut stm32f4xx_hal::gpio::gpiog::PG13<stm32f4xx_hal::gpio::Output<stm32f4xx_hal::gpio::PushPull>> = _cx.resources.led3;
        let  led4: &mut stm32f4xx_hal::gpio::gpiog::PG14<stm32f4xx_hal::gpio::Output<stm32f4xx_hal::gpio::PushPull>> = _cx.resources.led4;
        let  delay: &mut stm32f4xx_hal::delay::Delay = _cx.resources.delay;

        loop {
            // On for 1s, off for 1s.
            led3.set_high().unwrap();
            led4.set_low().unwrap();
            delay.delay_ms(1000_u32);
            
            led3.set_low().unwrap();
            led4.set_high().unwrap();
            delay.delay_ms(1000_u32);
        }
    }    
};