Skip to content

Commit a3e5e1d

Browse files
committed
b1display: Add scrensaver
Appears on power-on and also when resuming from sleep. Any drawing will disable it. Signed-off-by: Daniel Schaefer <[email protected]>
1 parent 8fada03 commit a3e5e1d

File tree

6 files changed

+172
-45
lines changed

6 files changed

+172
-45
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

b1display/src/main.rs

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ use usb_device::{class_prelude::*, prelude::*};
4343
use usbd_serial::{SerialPort, USB_CLASS_CDC};
4444

4545
// Used to demonstrate writing formatted strings
46-
use core::fmt::{Debug, Write};
47-
use heapless::String;
46+
use core::fmt::Debug;
47+
//use core::fmt::Write;
48+
//use heapless::String;
4849

4950
use lotus_inputmodules::control::*;
5051
use lotus_inputmodules::graphics::*;
@@ -66,6 +67,12 @@ type B1ST7306 = ST7306<
6667
200,
6768
>;
6869

70+
const DEBUG: bool = false;
71+
const SCRNS_DELTA: i32 = 10;
72+
const WIDTH: i32 = 300;
73+
const HEIGHT: i32 = 400;
74+
const SIZE: Size = Size::new(WIDTH as u32, HEIGHT as u32);
75+
6976
#[entry]
7077
fn main() -> ! {
7178
let mut pac = pac::Peripherals::take().unwrap();
@@ -128,7 +135,6 @@ fn main() -> ! {
128135
let spi = bsp::hal::Spi::<_, _, 8>::new(pac.SPI0);
129136
// Display control pins
130137
let dc = pins.dc.into_push_pull_output();
131-
//let mut lcd_led = pins.backlight.into_push_pull_output();
132138
let mut cs = pins.cs.into_push_pull_output();
133139
cs.set_low().unwrap();
134140
let rst = pins.rstb.into_push_pull_output();
@@ -157,68 +163,109 @@ fn main() -> ! {
157163
hpm: HpmFps::ThirtyTwo,
158164
lpm: LpmFps::One,
159165
},
160-
300,
161-
400,
166+
WIDTH as u16,
167+
HEIGHT as u16,
162168
COL_START,
163169
ROW_START,
164170
);
165171
disp.init(&mut delay).unwrap();
166172

173+
// Clear display, might have garbage in display memory
167174
// TODO: Seems broken
168175
//disp.clear(Rgb565::WHITE).unwrap();
169-
Rectangle::new(Point::new(0, 0), Size::new(300, 400))
176+
Rectangle::new(Point::new(0, 0), SIZE)
170177
.into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE))
171178
.draw(&mut disp)
172179
.unwrap();
173180

174-
let logo_rect = draw_logo(&mut disp).unwrap();
175-
Rectangle::new(Point::new(10, 10), Size::new(10, 10))
176-
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
177-
.draw(&mut disp)
178-
.unwrap();
179-
Rectangle::new(Point::new(20, 20), Size::new(10, 10))
180-
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
181-
.draw(&mut disp)
182-
.unwrap();
183-
Rectangle::new(Point::new(30, 30), Size::new(10, 10))
184-
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
185-
.draw(&mut disp)
181+
let logo_rect = draw_logo(&mut disp, Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y)).unwrap();
182+
if DEBUG {
183+
Rectangle::new(Point::new(10, 10), Size::new(10, 10))
184+
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
185+
.draw(&mut disp)
186+
.unwrap();
187+
Rectangle::new(Point::new(20, 20), Size::new(10, 10))
188+
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
189+
.draw(&mut disp)
190+
.unwrap();
191+
Rectangle::new(Point::new(30, 30), Size::new(10, 10))
192+
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
193+
.draw(&mut disp)
194+
.unwrap();
195+
Rectangle::new(Point::new(40, 40), Size::new(10, 10))
196+
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
197+
.draw(&mut disp)
198+
.unwrap();
199+
Rectangle::new(Point::new(50, 50), Size::new(10, 10))
200+
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
201+
.draw(&mut disp)
202+
.unwrap();
203+
draw_text(
204+
&mut disp,
205+
"Framework",
206+
Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y + logo_rect.size.height as i32),
207+
)
186208
.unwrap();
187-
Rectangle::new(Point::new(40, 40), Size::new(10, 10))
188-
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
189-
.draw(&mut disp)
190-
.unwrap();
191-
Rectangle::new(Point::new(50, 50), Size::new(10, 10))
192-
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
193-
.draw(&mut disp)
194-
.unwrap();
195-
draw_text(
196-
&mut disp,
197-
"Framework",
198-
Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y + logo_rect.size.height as i32),
199-
)
200-
.unwrap();
209+
}
210+
disp.flush().unwrap();
201211

202212
let sleep = pins.sleep.into_pull_down_input();
203213

204214
let mut state = B1DIsplayState {
205215
sleeping: SimpleSleepState::Awake,
206216
screen_inverted: false,
207217
screen_on: true,
218+
screensaver: Some(ScreenSaverState::default()),
208219
};
209220

210221
let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
211222
let mut prev_timer = timer.get_counter().ticks();
212223

224+
let mut logo_pos = Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y);
225+
213226
loop {
214227
// Go to sleep if the host is sleeping
215228
let host_sleeping = sleep.is_low().unwrap();
216229
handle_sleep(host_sleeping, &mut state, &mut delay, &mut disp);
217230

218231
// Handle period display updates. Don't do it too often
219-
if timer.get_counter().ticks() > prev_timer + 20_000 {
220-
// TODO: Update display
232+
if timer.get_counter().ticks() > prev_timer + 500_000 {
221233
prev_timer = timer.get_counter().ticks();
234+
235+
if let Some(ref mut screensaver) = state.screensaver {
236+
logo_pos = {
237+
let (x, y) = (logo_pos.x, logo_pos.y);
238+
let w = logo_rect.size.width as i32;
239+
let h = logo_rect.size.height as i32;
240+
241+
// Bounce off the walls
242+
if x <= 0 || x + w >= WIDTH {
243+
screensaver.rightwards *= -1;
244+
}
245+
if y <= 0 || y + h >= HEIGHT {
246+
screensaver.downwards *= -1;
247+
}
248+
249+
Point::new(
250+
x + screensaver.rightwards * SCRNS_DELTA,
251+
y + screensaver.downwards * SCRNS_DELTA,
252+
)
253+
};
254+
// Draw a border around the new logo, to clear previously drawn adjacent logos
255+
let style = PrimitiveStyleBuilder::new()
256+
.stroke_color(Rgb565::WHITE)
257+
.stroke_width(2 * SCRNS_DELTA as u32)
258+
.build();
259+
Rectangle::new(
260+
logo_pos - Point::new(SCRNS_DELTA, SCRNS_DELTA),
261+
logo_rect.size + Size::new(2 * SCRNS_DELTA as u32, 2 * SCRNS_DELTA as u32),
262+
)
263+
.into_styled(style)
264+
.draw(&mut disp)
265+
.unwrap();
266+
draw_logo(&mut disp, logo_pos).unwrap();
267+
disp.flush().unwrap();
268+
}
222269
}
223270

224271
// Check for new data
@@ -253,14 +300,14 @@ fn main() -> ! {
253300
};
254301
// Must write AFTER writing response, otherwise the
255302
// client interprets this debug message as the response
256-
let mut text: String<64> = String::new();
257-
write!(
258-
&mut text,
259-
"Handled command {}:{}:{}:{}\r\n",
260-
buf[0], buf[1], buf[2], buf[3]
261-
)
262-
.unwrap();
263-
let _ = serial.write(text.as_bytes());
303+
//let mut text: String<64> = String::new();
304+
//write!(
305+
// &mut text,
306+
// "Handled command {}:{}:{}:{}\r\n",
307+
// buf[0], buf[1], buf[2], buf[3]
308+
//)
309+
//.unwrap();
310+
//let _ = serial.write(text.as_bytes());
264311
}
265312
_ => {}
266313
}
@@ -307,6 +354,10 @@ fn handle_sleep<SPI, DC, CS, RST, const COLS: usize, const ROWS: usize>(
307354
// Sleep-in has to go into HPM first, so we'll be in HPM after wake-up as well
308355
disp.switch_mode(delay, PowerMode::Lpm).unwrap();
309356

357+
// Turn screensaver on when resuming from sleep
358+
// TODO Subject to change, but currently I want to avoid burn-in by default
359+
state.screensaver = Some(ScreenSaverState::default());
360+
310361
// TODO: Power display controller back on
311362
}
312363
}

inputmodule-control/src/b1display.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ pub struct B1DisplaySubcommand {
4141
#[arg(long)]
4242
pub invert_screen: Option<Option<bool>>,
4343

44+
/// Screensaver on/off
45+
#[arg(long)]
46+
pub screen_saver: Option<Option<bool>>,
47+
4448
/// Display black&white image (300x400px)
4549
#[arg(long)]
4650
pub image_bw: Option<String>,

inputmodule-control/src/inputmodule.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ enum Command {
3939
SetPixelColumn = 0x16,
4040
FlushFramebuffer = 0x17,
4141
ClearRam = 0x18,
42+
ScreenSaver = 0x19,
4243
Version = 0x20,
4344
}
4445

@@ -260,6 +261,9 @@ pub fn serial_commands(args: &crate::ClapCli) {
260261
if let Some(invert_screen) = b1display_args.invert_screen {
261262
invert_screen_cmd(serialdev, invert_screen);
262263
}
264+
if let Some(screensaver_on) = b1display_args.screen_saver {
265+
screensaver_cmd(serialdev, screensaver_on);
266+
}
263267
if let Some(image_path) = &b1display_args.image_bw {
264268
b1display_bw_image_cmd(serialdev, image_path);
265269
}
@@ -739,6 +743,26 @@ fn invert_screen_cmd(serialdev: &str, arg: Option<bool>) {
739743
}
740744
}
741745

746+
fn screensaver_cmd(serialdev: &str, arg: Option<bool>) {
747+
let mut port = serialport::new(serialdev, 115_200)
748+
.timeout(SERIAL_TIMEOUT)
749+
.open()
750+
.expect("Failed to open port");
751+
752+
if let Some(display_on) = arg {
753+
simple_cmd_port(&mut port, Command::ScreenSaver, &[display_on as u8]);
754+
} else {
755+
simple_cmd_port(&mut port, Command::ScreenSaver, &[]);
756+
757+
let mut response: Vec<u8> = vec![0; 32];
758+
port.read_exact(response.as_mut_slice())
759+
.expect("Found no data!");
760+
761+
let on = response[0] == 1;
762+
println!("Currently on: {on}");
763+
}
764+
}
765+
742766
fn set_color_cmd(serialdev: &str, color: Color) {
743767
let args = match color {
744768
Color::White => &[0xFF, 0xFF, 0xFF],

lotus-inputmodules/src/control.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub enum CommandVals {
5858
SetPixelColumn = 0x16,
5959
FlushFramebuffer = 0x17,
6060
ClearRam = 0x18,
61+
ScreenSaver = 0x19,
6162
Version = 0x20,
6263
}
6364

@@ -159,6 +160,8 @@ pub enum Command {
159160
SetPixelColumn(usize, [u8; 50]),
160161
FlushFramebuffer,
161162
ClearRam,
163+
ScreenSaver(bool),
164+
GetScreenSaver,
162165
_Unknown,
163166
}
164167

@@ -176,11 +179,27 @@ pub struct C1MinimalState {
176179
pub brightness: u8,
177180
}
178181

182+
#[derive(Copy, Clone)]
183+
pub struct ScreenSaverState {
184+
pub rightwards: i32,
185+
pub downwards: i32,
186+
}
187+
188+
impl Default for ScreenSaverState {
189+
fn default() -> Self {
190+
Self {
191+
rightwards: 1,
192+
downwards: 1,
193+
}
194+
}
195+
}
196+
179197
#[cfg(feature = "b1display")]
180198
pub struct B1DIsplayState {
181199
pub sleeping: SimpleSleepState,
182200
pub screen_inverted: bool,
183201
pub screen_on: bool,
202+
pub screensaver: Option<ScreenSaverState>,
184203
}
185204

186205
pub fn parse_command(count: usize, buf: &[u8]) -> Option<Command> {
@@ -364,6 +383,11 @@ pub fn parse_module_command(count: usize, buf: &[u8]) -> Option<Command> {
364383
}
365384
Some(CommandVals::FlushFramebuffer) => Some(Command::FlushFramebuffer),
366385
Some(CommandVals::ClearRam) => Some(Command::ClearRam),
386+
Some(CommandVals::ScreenSaver) => Some(if let Some(on) = arg {
387+
Command::ScreenSaver(on == 1)
388+
} else {
389+
Command::GetScreenSaver
390+
}),
367391
_ => None,
368392
}
369393
} else {
@@ -526,6 +550,9 @@ where
526550
}
527551
Command::Panic => panic!("Ahhh"),
528552
Command::SetText(text) => {
553+
// Turn screensaver off, when drawing something
554+
state.screensaver = None;
555+
529556
clear_text(
530557
disp,
531558
Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y + logo_rect.size.height as i32),
@@ -539,6 +566,7 @@ where
539566
Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y + logo_rect.size.height as i32),
540567
)
541568
.unwrap();
569+
disp.flush().unwrap();
542570
None
543571
}
544572
Command::DisplayOn(on) => {
@@ -562,6 +590,9 @@ where
562590
Some(response)
563591
}
564592
Command::SetPixelColumn(column, pixel_bytes) => {
593+
// Turn screensaver off, when drawing something
594+
state.screensaver = None;
595+
565596
let mut pixels: [bool; 400] = [false; 400];
566597
for (i, byte) in pixel_bytes.iter().enumerate() {
567598
pixels[8 * i] = byte & 0b00000001 != 0;
@@ -590,9 +621,26 @@ where
590621
None
591622
}
592623
Command::ClearRam => {
624+
// Turn screensaver off, when drawing something
625+
state.screensaver = None;
626+
593627
disp.clear_ram().unwrap();
594628
None
595629
}
630+
Command::ScreenSaver(on) => {
631+
state.screensaver = match (*on, state.screensaver) {
632+
(true, Some(x)) => Some(x),
633+
(true, None) => Some(ScreenSaverState::default()),
634+
(false, Some(_)) => None,
635+
(false, None) => None,
636+
};
637+
None
638+
}
639+
Command::GetScreenSaver => {
640+
let mut response: [u8; 32] = [0; 32];
641+
response[0] = state.screensaver.is_some() as u8;
642+
Some(response)
643+
}
596644
_ => handle_generic_command(command),
597645
}
598646
}

lotus-inputmodules/src/graphics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ where
4242
Ok(())
4343
}
4444

45-
pub fn draw_logo<D>(target: &mut D) -> Result<Rectangle, D::Error>
45+
pub fn draw_logo<D>(target: &mut D, offset: Point) -> Result<Rectangle, D::Error>
4646
where
4747
D: DrawTarget<Color = Rgb565>,
4848
{
4949
let bmp: Bmp<Rgb565> = Bmp::from_slice(include_bytes!("../assets/logo.bmp")).unwrap();
50-
let image = Image::new(&bmp, Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y));
50+
let image = Image::new(&bmp, offset);
5151
image.draw(target)?;
5252

5353
Ok(image.bounding_box())

0 commit comments

Comments
 (0)