Использование периферии в модулях

Изучив отдельно настройку и использование периферии микроконтроллера, можно её использовать для работы с устройством. Для примера возьмем дисплей и кнопку. Для работы с дисплеем будем использовать уже написанный модуль. А для кнопки напишем свой, чтобы немного понять как использовать hal со стороны модуля.

Добавьте модуль ssd1306 в зависимости созданного проекта. Например, это можно сделать добавив ssd1306 = "0.5.1" в раздел dependencies файла Cargo.toml. Таким же образом добавьте модули embedded-graphics = "0.6.2" и heapless = "0.6.1".

Модуль (драйвер) ssd1306 отвечает за общение с дисплеем. Модуль embedded-graphics предоставляет функционал для работы с графикой. Рисование фигур, картинок, текста разных стилей. Вы легко сможете поменять драйвер дисплея, сильно не меняя код отвечающий за рисование графики. Модуль heapless предоставляет различные структуры данных которые не используют динамическое выделение памяти.

Перед написанием самой программы, создадим наш учебный модуль для кнопки. В папке с проектом, запустите в терминале:

cargo new button --lib

И в зависимости проекта добавьте button = {path = "./button"}. В зависимости созданного модуля добавьте embedded-hal = "0.2.4".

После того как все зависимости добавлены, можно писать код. Напишите следующий код в файле src/lib.rs модуля button:


#![allow(unused)]
fn main() {
// Файл с основным кодом модуля

// Отключение стандартной библиотеки
#![no_std]

// Импорт трейта для пина входа
use embedded_hal::digital::v2::InputPin;

///
/// Мини реализация кнопки
///
pub struct Button<PIN>
where
    PIN: InputPin,
{
    button_pin: PIN,
    value: u32,
}

impl<PIN, PinE> Button<PIN>
where
    PIN: InputPin<Error = PinE>,
{
    ///
    /// Создает новый экземпляр драйвера
    ///
    /// # Аргументы
    ///
    /// * `button_pin` - пин к которому подключена кнопка
    ///
    pub fn new(button_pin: PIN) -> Self {
        Self { button_pin, value: 0 }
    }

    ///
    /// Считывает состояние и возвращает кол-во нажатий
    ///
    pub fn tick(&mut self) -> Result<u32, PinE> {
        if self.button_pin.is_low()? {
            self.value += 1;
        }

        Ok(self.value)
    }

    ///
    /// Освобождаем пин кнопки
    ///
    pub fn release(self) -> PIN {
        self.button_pin
    }
}
}

Написанный выше код имеет:

  • Функцию new для создания нового экземпляра драйвера. Функция принимает все что, реализует функционал InputPin. В документации по embedded-hal содержится больше информации об использовании различной периферии в модулях. Документация по модулю embedded-hal доступна на docs.rs.
  • Функцию tick, где получаем значение пина к которому подключена кнопка, если кнопка нажата, то увеличиваем внутреннюю переменную на 1. Возвращает кол-во нажатий.
  • Функцию release для освобождения переданной драйверу периферии. У большинства драйверов для работы с каким-либо устройством, имеется похожая функция.

Напишем следующий код в файле самого проекта:


#![allow(unused)]
fn main() {
// Разделение GPIO на отдельные пины
let port0 = gpio::p0::Parts::new(periph.P0);
// Создание "задержки"
let mut delay = delay::Delay::new(core_periph.SYST);
// Макрос который переименовывает пины микроконтроллера в названия пинов на плате
let pins = crabik_board::rename_pins!(port0);

// Подготовка пинов
let scl = pins.d9.into_floating_input().degrade();
let sda = pins.d10.into_floating_input().degrade();

// Подготовка к работе TWI мастера
let i2c = twim::Twim::new(periph.TWIM0, twim::Pins { scl, sda }, twim::Frequency::K400);

// Создание экземпляра кнопки и передача пина к которому подключена кнопка
let mut button = button::Button::new(pins.a0.into_pullup_input());

// Прослойка отправки команд для дисплея через I2C
// Это нужно т.к. контроллер дисплея может работать как по I2C так и по SPI
let interface = I2CDIBuilder::new().init(i2c);
// Соединение и подготовка к работе дисплея
let mut display: GraphicsMode<_, _> = Builder::new().connect(interface).into();
display.init().expect("Не удалось подготовить к работе дисплей");

// Создание стиля текста
let text_style = TextStyleBuilder::new(Font6x8)
    .text_color(BinaryColor::On)
    .background_color(BinaryColor::Off)
    .build();

// Создание фиксированного размера строки, для работы без динамического выделения памяти
let mut text = heapless::String::<consts::U20>::new();

loop {
    // Отчищаем строку от предыдущего значения
    text.clear();

    // Записываем новое значение в строку
    write!(
        &mut text,
        "Сlicks: {}",
        button.tick().expect("Не удалось получить значение нажатий")
    )
    .expect("Не удалось записать данные");

    // Отрисовываем текст
    Text::new(text.as_str(), Point::zero())
        .into_styled(text_style)
        .draw(&mut display)
        .expect("Не удалось отрисовать на дисплее");

    // Передаем все что отрисовали на дисплей
    display.flush().expect("Не удалось обновить дисплей");

    delay.delay_ms(100u32);
}
}

В показанном выше примере мы выводим на дисплей кол-во нажатий кнопки.