【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 {}
}
Comments NOTHING