【Rust】使用Rust点亮ttf彩屏(st7735)

夕凪マナ 发布于 2024-11-26 195 次阅读


【Rust】使用Rust点亮ttf彩屏(st7735)

1. 依赖添加

在上一节Rust点灯项目中添加屏幕的相关依赖,st7735-lcd和embbed-graphics

cargo add st7735-lcd
cargo add embbed-graphics

添加成功后就可以引入到我们的项目中了

2. 代码编写

2.1 引入模块

打开main.rs,首先引入我们需要用到的模块

use panic_halt as _;

use cortex_m_rt::entry;
use embedded_graphics::{
    image::Image,
    mono_font::{ascii::FONT_10X20, MonoTextStyle},
    pixelcolor::Rgb565,
    prelude::*,
    text::Text,
};
use st7735_lcd::{self, Orientation, ST7735};
use stm32f4xx_hal::{pac, prelude::*, spi::*};
use tinybmp::Bmp;

因为我这块屏幕是1.8寸的spi屏幕,因此从hal库中引入了spi相关模块(后续会尝试点亮iic屏幕和fsmc并口屏),其他模块会在代码使用到的时候详细解释用途

2.2 主函数

2.2.1 初始化

引脚初始化

进入主函数,第一步要做的依旧是获取核心与外设,成功获取到之后就需要对外设进行一些初始化操作了

        // 对使用到的引脚初始化,因为不需要调亮度就没有用BLK
        // * BLK  <--> PA1
        // * DC   <--> PA2
        // * RST  <--> PA3
        // * CS   <--> PA4
        // * SCLK <--> PA5
        // * MOSI <--> PA7
        let gpioa = dp.GPIOA.split();
        let sclk = gpioa.pa5.into_alternate::<5>();
        let mosi = gpioa.pa7.into_alternate::<5>();
        let cs = gpioa.pa4.into_push_pull_output();
        let dc = gpioa.pa2.into_push_pull_output();
        let rst = gpioa.pa3.into_push_pull_output();

这边需要注意的一点是sclk和mosi引脚是用到了复用功能,Rust没办法自动推断出我们需要用到哪个复用功能,因此需要显示指定。

通过查询mcu的数据手册可以找到对应的AF编号,这里以stm32f407vgt6为例,找到数据手册中的alternate function mapping(Table 9)

在数据手册上的第63页,我们可以看到SPI1的复用功能编号是AF5,所以这里填上5就可以了。

SPI初始化

        let spi_mode = Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };
        let spi = dp
            .SPI1
            .spi((sclk, NoMiso::new(), mosi), spi_mode, 14.MHz(), &clocks)
            .init();

上面的spi_mode定义了时钟极性和时钟相位,Polarity决定了在空闲状态下时钟信号是高电平还是低电平;Phase定义了数据是在时钟的第一个跳变沿还是第二个跳变沿被采样。上述配置对应了spi模式0。

然后进行spi的初始化,由于spi屏幕不需要输出数据,因此没有miso引脚,这里需要从spi模块中引入NoMiso,实例化一个结构体作为占位符(使用gpio::NoPin也是可以的,不过NoMiso会更加直观)。

这里的时钟频率一开始使用的是16MHz,结果图片刷出来会有某些地方的像素不对,降低到14MHz之后可以正常显示。(后面再优化一下)

屏幕初始化

        let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap();

        let mut display = ST7735::new(spi_device, dc, rst, true, false, 128, 160);

        display.init(&mut delay).unwrap();
        display
            .set_orientation(&Orientation::PortraitSwapped)
            .unwrap();
        display.clear(Rgb565::BLACK).unwrap();
        display.set_offset(0, 0);

这边使用到了embedded_hal_bus这个crate用来管理总线操作,确保多个spi设备不会冲突。虽然这里就一个设备,但由于st7735-lcd的初始化需要一个实现了SpiDevice特性的结构体,所以也要用embedded_hal_bus转换一下。

之后就是屏幕的初始化配置和清屏,需要注意在Orientation属性使用不同选项时,如Portrait或Landscape,上面实例化st7735结构体时使用的长和宽需要交换,具体根据实际显示效果和需求来修改即可。

2.2.2 hello world!

        let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
        Text::new("hello world!", Point::new(0, 20), style)
            .draw(&mut display)
            .unwrap();

显示可以用embedded-graphics这个库,它提供了多种分辨率的字库,可以绘制图形,显示图片(不同的图片格式可能会需要用到其他的crate支持),功能相当强大。

这里使用了10X20大小的字体,颜色为白色,在屏幕顶部打印"hello world!",由于字体高度为20,在选取position的时候y轴需要偏移20个像素,否则会显示不全。把实例化的Text结构体输出到我们的屏幕上就可以看到显示了。

2.2.3 显示图片

        const BITMAP_DATA: &[u8] = include_bytes!("../img/miko.bmp");
        let bmp = Bmp::from_slice(BITMAP_DATA).unwrap();
        let image = Image::new(&bmp, Point::zero());
        image.draw(&mut display).unwrap();

我使用到的图片格式为bmp,使用tinybmp库可以读取bmp文件并加载到内存中,tinybmp和embedded-graphics库无缝集成,可以很方便地把bmp图片渲染到屏幕上。

使用include_bytes!()把bmp文件读取到一个常量数组中,然后使用tinybmp进行解析,解析到的结果通过embedded-graphics直接渲染到屏幕上。

目前手上没有空闲的tf卡,不然还挺想用这块屏放一下bad apple来着(

3. 总结

点亮这块屏的过程还是有点曲折的,由于Rust嵌入式目前的教程比较少,加之各种crate更新、api变动,相较于使用C语言、CubeMX来说难度也是大了不少(更大的原因也是自己学艺不精,接触Rust的时间比较短)。不过最后能点亮还是很高兴的。

这次使用的是别人的st7735库,后面希望可以尝试着自己移植C语言的ST7735或者别的芯片的驱动库,加深一下自己对于驱动和SPI协议的理解。

3.1 程序源码

#![deny(unsafe_code)]
#![no_main]
#![no_std]

// Halt on panic
use panic_halt as _;

use cortex_m_rt::entry;
use embedded_graphics::{
    image::Image,
    mono_font::{ascii::FONT_10X20, MonoTextStyle},
    pixelcolor::Rgb565,
    prelude::*,
    text::Text,
};
use st7735_lcd::{self, Orientation, ST7735};
use stm32f4xx_hal::{pac, prelude::*, spi::*};
use tinybmp::Bmp;

const BITMAP_DATA: &[u8] = include_bytes!("../img/miko.bmp");

#[allow(clippy::empty_loop)]
#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (
        pac::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {
        // 配置时钟168MHz,这里如果需要也可以进行外设时钟树的配置
        let rcc = dp.RCC.constrain();
        let clocks = rcc
            .cfgr
            .use_hse(8.MHz())
            .sysclk(168.MHz())
            .pclk1(42.MHz())
            .pclk2(84.MHz())
            .freeze();
        let syst = cp.SYST;
        let mut delay = syst.delay(&clocks);

        // 对使用到的引脚初始化
        // * BLK -- PA1
        // * DC  -- PA2
        // * RST -- PA3
        // * CS  -- PA4
        // * SCLK-- PA5
        // * MOSI-- PA7
        let gpioa = dp.GPIOA.split();
        let sclk = gpioa.pa5.into_alternate::<5>();
        let mosi = gpioa.pa7.into_alternate::<5>();
        let cs = gpioa.pa4.into_push_pull_output();
        let dc = gpioa.pa2.into_push_pull_output();
        let rst = gpioa.pa3.into_push_pull_output();

        let spi_mode = Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };
        let spi = dp
            .SPI1
            .spi((sclk, NoMiso::new(), mosi), spi_mode, 14.MHz(), &clocks)
            .init();

        let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap();

        let mut display = ST7735::new(spi_device, dc, rst, true, false, 128, 160);

        display.init(&mut delay).unwrap();
        display
            .set_orientation(&Orientation::PortraitSwapped)
            .unwrap();
        display.clear(Rgb565::BLACK).unwrap();
        display.set_offset(0, 0);

        let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
        Text::new("hello world!", Point::new(0, 20), style)
            .draw(&mut display)
            .unwrap();

        let bmp = Bmp::from_slice(BITMAP_DATA).unwrap();
        let image = Image::new(&bmp, Point::zero());
        image.draw(&mut display).unwrap();

        loop {}
    }
    loop {}
}
此作者没有提供个人介绍
最后更新于 2024-11-26