Skip to content

b1display: Allow set/get of FPS and power mode #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions b1display/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ Options:
Invert screen on/off [possible values: true, false]
--screen-saver [<SCREEN_SAVER>]
Screensaver on/off [possible values: true, false]
--image-bw <IMAGE_BW>
Display black&white image (300x400px)
--fps [<FPS>]
Set/get FPS [possible values: quarter, half, one, two, four, eight, sixteen, thirty-two]
--power-mode [<POWER_MODE>]
Set/get power mode [possible values: low, high]
--animation-fps [<ANIMATION_FPS>]
Set/get animation FPS
--image <IMAGE>
Display a black&white image (300x400px)
--animated-gif <ANIMATED_GIF>
Display an animated black&white GIF (300x400px)
--clear-ram
Clear display RAM
-h, --help
Expand Down
68 changes: 48 additions & 20 deletions b1display/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ use usbd_serial::{SerialPort, USB_CLASS_CDC};

// Used to demonstrate writing formatted strings
use core::fmt::Debug;
//use core::fmt::Write;
//use heapless::String;
use core::fmt::Write;
use heapless::String;

use fl16_inputmodules::control::*;
use fl16_inputmodules::graphics::*;
Expand All @@ -68,7 +68,7 @@ type B1ST7306 = ST7306<
>;

const DEBUG: bool = false;
const SCRNS_DELTA: i32 = 10;
const SCRNS_DELTA: i32 = 5;
const WIDTH: i32 = 300;
const HEIGHT: i32 = 400;
const SIZE: Size = Size::new(WIDTH as u32, HEIGHT as u32);
Expand Down Expand Up @@ -146,6 +146,19 @@ fn main() -> ! {
&embedded_hal::spi::MODE_0,
);

let mut state = B1DIsplayState {
sleeping: SimpleSleepState::Awake,
screen_inverted: false,
screen_on: true,
screensaver: Some(ScreenSaverState::default()),
power_mode: PowerMode::Lpm,
fps_config: FpsConfig {
hpm: HpmFps::ThirtyTwo,
lpm: LpmFps::Two,
},
animation_period: 1_000_000, // 1000ms = 1Hz
};

const INVERTED: bool = false;
const AUTO_PWRDOWN: bool = true;
const TE_ENABLE: bool = true;
Expand All @@ -159,10 +172,7 @@ fn main() -> ! {
INVERTED,
AUTO_PWRDOWN,
TE_ENABLE,
FpsConfig {
hpm: HpmFps::ThirtyTwo,
lpm: LpmFps::One,
},
state.fps_config,
WIDTH as u16,
HEIGHT as u16,
COL_START,
Expand Down Expand Up @@ -211,15 +221,9 @@ fn main() -> ! {

let sleep = pins.sleep.into_pull_down_input();

let mut state = B1DIsplayState {
sleeping: SimpleSleepState::Awake,
screen_inverted: false,
screen_on: true,
screensaver: Some(ScreenSaverState::default()),
};

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

let mut logo_pos = Point::new(LOGO_OFFSET_X, LOGO_OFFSET_Y);

Expand All @@ -229,10 +233,32 @@ fn main() -> ! {
handle_sleep(host_sleeping, &mut state, &mut delay, &mut disp);

// Handle period display updates. Don't do it too often
if timer.get_counter().ticks() > prev_timer + 500_000 {
if timer.get_counter().ticks() > prev_timer + state.animation_period {
prev_timer = timer.get_counter().ticks();

if let Some(ref mut screensaver) = state.screensaver {
let seconds = ticks / (1_000_000 / state.animation_period);
#[allow(clippy::modulo_one)]
let second_decimals = ticks % (1_000_000 / state.animation_period);
Rectangle::new(Point::new(0, 0), Size::new(300, 50))
.into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE))
.draw(&mut disp)
.unwrap();
let mut text: String<32> = String::new();
write!(
&mut text,
"{:>4} Ticks ({:>4}.{} s)",
ticks, seconds, second_decimals
)
.unwrap();
// Uncomment to draw the ticks on the screen
//draw_text(
// &mut disp,
// &text,
// Point::new(0, 0),
//).unwrap();
ticks += 1;

logo_pos = {
let (x, y) = (logo_pos.x, logo_pos.y);
let w = logo_rect.size.width as i32;
Expand Down Expand Up @@ -286,16 +312,16 @@ fn main() -> ! {
(Some(c @ Command::BootloaderReset), _)
| (Some(c @ Command::IsSleeping), _) => {
if let Some(response) =
handle_command(&c, &mut state, logo_rect, &mut disp)
handle_command(&c, &mut state, logo_rect, &mut disp, &mut delay)
{
let _ = serial.write(&response);
};
}
(Some(command), SimpleSleepState::Awake) => {
// While sleeping no command is handled, except waking up
if let Some(response) =
handle_command(&command, &mut state, logo_rect, &mut disp)
{
if let Some(response) = handle_command(
&command, &mut state, logo_rect, &mut disp, &mut delay,
) {
let _ = serial.write(&response);
};
// Must write AFTER writing response, otherwise the
Expand Down Expand Up @@ -352,7 +378,9 @@ fn handle_sleep<SPI, DC, CS, RST, const COLS: usize, const ROWS: usize>(
//disp.on_off(true).unwrap();
disp.sleep_out(delay).unwrap();
// Sleep-in has to go into HPM first, so we'll be in HPM after wake-up as well
disp.switch_mode(delay, PowerMode::Lpm).unwrap();
if state.power_mode == PowerMode::Lpm {
disp.switch_mode(delay, PowerMode::Lpm).unwrap();
}

// Turn screensaver on when resuming from sleep
// TODO Subject to change, but currently I want to avoid burn-in by default
Expand Down
107 changes: 107 additions & 0 deletions control.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class CommandVals(IntEnum):
FlushFramebuffer = 0x17
ClearRam = 0x18
ScreenSaver = 0x19
SetFps = 0x1A
SetPowerMode = 0x1B
Version = 0x20


Expand Down Expand Up @@ -120,6 +122,9 @@ class GameControlVal(IntEnum):

RGB_COLORS = ['white', 'black', 'red', 'green',
'blue', 'cyan', 'yellow', 'purple']
SCREEN_FPS = ['quarter', 'half', 'one', 'two', 'four', 'eight', 'sixteen', 'thirtytwo']
HIGH_FPS_MASK = 0b00010000
LOW_FPS_MASK = 0b00000111

SERIAL_DEV = None

Expand Down Expand Up @@ -196,6 +201,14 @@ def main():
action=argparse.BooleanOptionalAction)
parser.add_argument("--screen-saver", help="Turn on/off screensaver",
action=argparse.BooleanOptionalAction)
parser.add_argument("--set-fps", help="Set screen FPS",
choices=SCREEN_FPS)
parser.add_argument("--set-power-mode", help="Set screen power mode",
choices=['high', 'low'])
parser.add_argument("--get-fps", help="Set screen FPS",
action='store_true')
parser.add_argument("--get-power-mode", help="Set screen power mode",
action='store_true')
parser.add_argument("--b1image", help="On the B1 display, show a PNG or GIF image in black and white only)",
type=argparse.FileType('rb'))

Expand Down Expand Up @@ -282,6 +295,14 @@ def main():
invert_screen_cmd(args.invert_screen)
elif args.screen_saver is not None:
screen_saver_cmd(args.screen_saver)
elif args.set_fps is not None:
set_fps_cmd(args.set_fps)
elif args.set_power_mode is not None:
set_power_mode_cmd(args.set_power_mode)
elif args.get_fps:
get_fps_cmd()
elif args.get_power_mode:
get_power_mode_cmd()
elif args.b1image is not None:
b1image_bl(args.b1image)
elif args.version:
Expand Down Expand Up @@ -1073,6 +1094,92 @@ def screen_saver_cmd(on):
send_command(CommandVals.ScreenSaver, [on])


def set_fps_cmd(mode):
res = send_command(CommandVals.SetFps, with_response=True)
current_fps = res[0]

if mode == 'quarter':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b000
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'half':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b001
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'one':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b010
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'two':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b011
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'four':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b100
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'eight':
fps = current_fps & ~LOW_FPS_MASK
fps |= 0b101
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('low')
elif mode == 'sixteen':
fps = current_fps & ~HIGH_FPS_MASK
fps |= 0b00000000
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('high')
elif mode == 'thirtytwo':
fps = current_fps & ~HIGH_FPS_MASK
fps |= 0b00010000
send_command(CommandVals.SetFps, [fps])
set_power_mode_cmd('high')


def set_power_mode_cmd(mode):
if mode == 'low':
send_command(CommandVals.SetPowerMode, [0])
elif mode == 'high':
send_command(CommandVals.SetPowerMode, [1])
else:
print("Unsupported power mode")
sys.exit(1)

def get_power_mode_cmd():
res = send_command(CommandVals.SetPowerMode, with_response=True)
current_mode = int(res[0])
if current_mode == 0:
print(f"Current Power Mode: Low Power")
elif current_mode == 1:
print(f"Current Power Mode: High Power")

def get_fps_cmd():
res = send_command(CommandVals.SetFps, with_response=True)
current_fps = res[0]
res = send_command(CommandVals.SetPowerMode, with_response=True)
current_mode = int(res[0])

if current_mode == 0:
current_fps &= LOW_FPS_MASK
if current_fps == 0:
fps = 0.25
elif current_fps == 1:
fps = 0.5
else:
fps = 2 ** (current_fps - 2)
elif current_mode == 1:
if current_fps & HIGH_FPS_MASK:
fps = 32
else:
fps = 16

print(f"Current FPS: {fps}")


# 5x6 symbol font. Leaves 2 pixels on each side empty
# We can leave one row empty below and then the display fits 5 of these digits.

Expand Down
Loading