国产低成本单片机中运行Rust GUI

Slint 的介绍

Slint 是一个声明式 GUI 开源框架,该框架完全用 Rust 开发。用户的开发方式类似 Qt 的 QML,同时支持 Rust/C++/Js语言的绑定。Slint 正在积极开发中,目前已支持多平台运行,如嵌入式、Windows、Mac、Web 、Android 等。

Slint 的优点

  • 开发简单,Slint 申明式开发界面,轻松绑定不同语言的业务逻辑.
  • 跨平台, 完全支持嵌入式、桌面、移动端运行
  • 多语言,完美支持 C++/Rust/Js语言,用户可选择自己擅长的语言
  • 快速设计,桌面端轻松仿真,便捷的代码提示
  • 固件体积小,相比 Qt 臃肿的框架,Slint 能在小容量的单片机中也能轻松运行

移植到Swm341单片机

开发平台

目前所有代码都在 Mac 下编译和下载。

开发板使用 SWM341RET6 单片机,64KRAM+512KFlash,LCD 分辨率大小为 800*480.

编写 Swm341 的 Rust 外设驱动库

Swm341 单片机目前官方只提供 C 的库接口,并不能直接运行 Slint,因为 Slint 由 Rust 开发,因此笔者需要自己实现一套 Rust 的外设驱动库。Swm341 Rust版本的外设驱动框架如下:

~/mywork/synwit/synwit_hal_common  ‹18e6b51*› $ tree -d -L 2
.
├── examples
└── src
    ├── can
    ├── cmp
    ├── cordic
    ├── crc
    ├── dac
    ├── div
    ├── dma
    ├── dma2d
    ├── dwt
    ├── flash
    ├── gpio
    ├── i2c
    ├── jpeg
    ├── lcd
    ├── opa
    ├── rtc
    ├── saradc
    ├── sdio
    ├── sdram
    ├── spi
    ├── syscon
    ├── timer
    ├── usb
    └── util
移植 Lcd 接口到 Slint

Slint 提供便利的 trait 引导接口的适配,接口适配如下:

#[derive(Debug)]
enum Error {}

#[derive(Clone, Copy)]
struct MyLcd<
    Bl: GpioFun,
    const PIN: u8,
    const WIDTH: usize = DISPLAY_WIDTH,
    const HEIGHT: usize = DISPLAY_HEIGTH,
> {
    back_light_io: AnyPin,
    lcd: Lcd,
}

impl
    MyLcd
{
    pub fn new() -> Result {
        let mut back_light_io = AnyPin::::new();
        let _ = back_light_io.set_high();
        let config: LcdConfig = LcdConfig::default().size(WIDTH as u16, HEIGHT as u16);
        let lcd = Lcd::new(config);
        Ok(Self { back_light_io, lcd })
    }

    pub fn start(&self) {
        self.lcd.start();
    }

    pub fn set_display_addr(&self, addr: u32) {
        let layer_config = LayerConfig::default().set_source(addr);
        Lcd::layer_init(Layer::Layer0, layer_config);
    }

    pub fn width(&self) -> u16 {
        WIDTH as u16
    }

    pub fn height(&self) -> u16 {
        HEIGHT as u16
    }

    fn set_pixel(&mut self, x: u16, y: u16, color: Rgb565) {
        let fb = unsafe { &mut *core::ptr::addr_of_mut!(FB1) };
        fb[y as usize][x as usize] = color;
    }
}

impl OriginDimensions
    for MyLcd
{
    fn size(&self) -> embedded_graphics_core::prelude::Size {
        Size::new(WIDTH as u32, HEIGHT as u32)
    }
}

impl DrawTarget
    for MyLcd
{
    type Color = Rgb565;
    type Error = Error;

    fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator>,
    {
        let bb = self.bounding_box();
        pixels
            .into_iter()
            .filter(|Pixel(pos, _color)| bb.contains(*pos))
            .for_each(|Pixel(pos, color)| self.set_pixel(pos.x as u16, pos.y as u16, color));

        Ok(())
    }
}
界面开发

界面开发非常简单,直接使用 Slint 标记语法开发 GUI界面即可,由于例程简单,因此无需 Rust 后台逻辑处理交互。

import { AboutSlint, VerticalBox, CheckBox,Switch, HorizontalBox } from "std-widgets.slint";

export component MyButton inherits Rectangle {
    in-out property text <=> txt.text;
    callback clicked <=> touch.clicked;
    border-radius: root.height / 2;
    border-width: 1px;
    border-color: root.background.darker(25%);
    background: touch.pressed ? #6b8282 : touch.has-hover ? #6c616c :  #456;
    height: txt.preferred-height * 1.33;
    min-width: txt.preferred-width + 20px;
    txt := Text {
        x: (parent.width - self.width)/2 + (touch.pressed ? 2px : 0);
        y: (parent.height - self.height)/2 + (touch.pressed ? 1px : 0);
        color: touch.pressed ? #fff : #eee;
    }
    touch := TouchArea { }
}

export component MySlider inherits Rectangle {
    in-out property maximum: 100;
    in-out property minimum: 0;
    in-out property value;

    min-height: 24px;
    min-width: 100px;
    horizontal-stretch: 1;
    vertical-stretch: 0;

    border-radius: root.height/2;
    background: touch.pressed ? #eee: #ddd;
    border-width: 1px;
    border-color: root.background.darker(25%);

    handle := Rectangle {
        width: self.height;
        height: parent.height;
        border-width: 3px;
        border-radius: self.height / 2;
        background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
        border-color: self.background.darker(15%);
        x: (root.width - handle.width) * (root.value - root.minimum)/(root.maximum - root.minimum);

        touch := TouchArea {
            moved => {
                if (self.enabled && self.pressed) {
                    root.value = max(root.minimum, min(root.maximum,
                        root.value + (self.mouse-x - self.pressed-x) * (root.maximum - root.minimum) / root.width));
                }
            }
        }
    }
}


export component MainWindow inherits Window {
    width: 800px;
    height: 480px;

    VerticalBox {
        alignment: LayoutAlignment.center;

        Text {
            text: "Hello SWM341 Slint!";
            font-size: 30px;
            horizontal-alignment: center;
        }
        Text {
            text: "Author: EmbeddedTeckTalk";
            font-size: 12px;
            horizontal-alignment: center;
        }
        AboutSlint {
            preferred-height: 200px;
        }

        HorizontalLayout {
            alignment: center;
            VerticalLayout {
                alignment: center;
                MyButton {
                    height: 45px;
                    width: 80px;
                    text: "My Button";
                }
            }
            CheckBox {
                text: "CheckBox";
                checked: true;
            }

            Switch {

            }

            Rectangle {
                height: 40px;
                width: 40px;
                border-radius: 20px;
                background: red;
                Text {
                    text: "Red";
                }
            }

            Rectangle {
                height: 40px;
                width: 40px;
                border-radius: 20px;
                background: green;
                Text {
                    text: "Green";
                }
            }

            Rectangle {
                height: 40px;
                width: 40px;
                border-radius: 20px;
                background: blue;
                Text {
                    text: "Blue";
                }
            }
        }

        VerticalBox {
            alignment: LayoutAlignment.center;
            slider := MySlider {
                maximum: 100;
                value: 49;
            }
            Text {
                text: "Slider value: \{round(slider.value)}";
                horizontal-alignment: center;
            }
        }
    }
}
运行效果

MCU显示与仿真效果几乎一致,目前触摸接口暂未移植,后续继续更新。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 8
收藏 9
关注 10
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧