Compare commits

..

No commits in common. "3cc0d6aaada2e38066d743c7194e0f1c420129c4" and "347b0c6477633ccee2f498ca9badfbe01d988389" have entirely different histories.

10 changed files with 87 additions and 619 deletions

View file

@ -1,13 +0,0 @@
[workspace]
resolver = "2"
members = ["firmware"]
[profile.release]
debug = 2
lto = true
opt-level = 'z'
[profile.dev]
debug = 2
lto = true
opt-level = "z"

View file

View file

@ -763,12 +763,9 @@ dependencies = [
"embedded-io",
"embedded-io-async",
"embedded-storage",
"fixed",
"heapless",
"mipidsi",
"rgb",
"smart-leds",
"tinybmp",
"panic-probe",
]
[[package]]
@ -873,6 +870,16 @@ dependencies = [
"syn",
]
[[package]]
name = "panic-probe"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
dependencies = [
"cortex-m",
"defmt",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@ -1076,9 +1083,6 @@ name = "rgb"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
dependencies = [
"bytemuck",
]
[[package]]
name = "rp-pac"
@ -1263,15 +1267,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tinybmp"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6"
dependencies = [
"embedded-graphics",
]
[[package]]
name = "typenum"
version = "1.19.0"

View file

@ -3,6 +3,7 @@ edition = "2024"
name = "led-mask"
version = "0.1.0"
authors = ["Adam Gausmann <adam@gaussian.dev>"]
resolver = "2"
[[bin]]
name = "led-mask"
@ -12,6 +13,7 @@ bench = false
[dependencies]
defmt = "1.0"
defmt-rtt = "1.1"
panic-probe = { version = "1.0", features = ["print-defmt"] }
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
@ -46,7 +48,13 @@ mipidsi = "0.9.0"
embedded-hal-bus = "0.3.0"
embassy-futures = "0.1.2"
heapless = "0.8.0"
rgb = "0.8.52"
smart-leds = "0.4.0"
fixed = "1.29.0"
tinybmp = "0.6.0"
[profile.release]
debug = 2
lto = true
opt-level = 'z'
[profile.dev]
debug = 2
lto = true
opt-level = "z"

View file

@ -1,183 +0,0 @@
use embassy_time::Duration;
use embedded_graphics::{pixelcolor::Rgb888, prelude::*, primitives::Rectangle};
use smart_leds::hsv::{Hsv, hsv2rgb};
use tinybmp::Bmp;
pub struct Hal {
image_data: Bmp<'static, Rgb888>,
}
impl Hal {
pub fn new() -> Self {
Self {
image_data: Bmp::from_slice(include_bytes!("assets/hal.bmp")).unwrap(),
}
}
}
impl Drawable for Hal {
type Color = Rgb888;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
self.image_data.draw(target)
}
}
pub struct HappyEyes {
image_data: Bmp<'static, Rgb888>,
}
impl HappyEyes {
pub fn new() -> Self {
Self {
image_data: Bmp::from_slice(include_bytes!("assets/eye1.bmp")).unwrap(),
}
}
pub fn draw_colored<D: DrawTarget<Color = Rgb888>>(
&self,
target: &mut D,
color: Rgb888,
) -> Result<(), D::Error> {
self.draw(&mut ColorMapFilter {
inner_target: target,
filter: |c| mix(c, color),
})
}
}
impl Drawable for HappyEyes {
type Color = Rgb888;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
self.image_data.draw(target)
}
}
pub struct Rainbow {
pub rate: Duration,
pub width: u32,
pub elapsed: Duration,
}
impl Rainbow {
fn map(&self, x: i32) -> Rgb888 {
let hue = ((x as f32 / self.width as f32
+ self.elapsed.as_micros() as f32 / self.rate.as_micros() as f32)
* 256.0) as u32 as u8;
let color = hsv2rgb(Hsv {
hue,
sat: 255,
val: 255,
});
Rgb888::new(color.r, color.g, color.b)
}
}
impl Drawable for Rainbow {
type Color = Rgb888;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
let bb = target.bounding_box();
for dx in 0..bb.size.width {
let color = self.map(dx as i32);
target.fill_solid(
&Rectangle::new(
bb.top_left + Point::new(dx as i32, 0),
Size::new(1, bb.size.height),
),
color,
)?;
}
Ok(())
}
}
pub struct RainbowFilter<'a, D> {
pub inner_target: &'a mut D,
pub rainbow: &'a Rainbow,
}
impl<'a, D: DrawTarget<Color = Rgb888>> DrawTarget for RainbowFilter<'a, D> {
type Color = D::Color;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
self.inner_target.draw_iter(
pixels
.into_iter()
.map(|Pixel(point, color)| Pixel(point, mix(color, self.rainbow.map(point.x)))),
)
}
}
impl<'a, D: Dimensions> Dimensions for RainbowFilter<'a, D> {
fn bounding_box(&self) -> Rectangle {
self.inner_target.bounding_box()
}
}
pub struct ColorMapFilter<'a, D, F>
where
F: FnMut(Rgb888) -> Rgb888,
{
pub inner_target: &'a mut D,
pub filter: F,
}
impl<'a, D, F> DrawTarget for ColorMapFilter<'a, D, F>
where
D: DrawTarget<Color = Rgb888>,
F: FnMut(Rgb888) -> Rgb888,
{
type Color = D::Color;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
self.inner_target.draw_iter(
pixels
.into_iter()
.map(|Pixel(point, color)| Pixel(point, (self.filter)(color))),
)
}
}
impl<'a, D: Dimensions, F> Dimensions for ColorMapFilter<'a, D, F>
where
F: FnMut(Rgb888) -> Rgb888,
{
fn bounding_box(&self) -> Rectangle {
self.inner_target.bounding_box()
}
}
fn mix_u8(a: u8, b: u8) -> u8 {
(a as u16 * b as u16 / 255) as u8
}
fn mix(a: Rgb888, b: Rgb888) -> Rgb888 {
Rgb888::new(
mix_u8(a.r(), b.r()),
mix_u8(a.g(), b.g()),
mix_u8(a.b(), b.b()),
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,101 +0,0 @@
use core::convert::Infallible;
use embassy_rp::{peripherals::PIO0, pio_programs::ws2812::PioWs2812};
use embedded_graphics::{
Pixel,
pixelcolor::Rgb888,
prelude::{DrawTarget, OriginDimensions, Point, RgbColor, Size},
};
use rgb::RGB8;
// Sourced from the `smart-leds` crate
const GAMMA8: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14,
14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27,
27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72,
73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104,
105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137,
138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220,
223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255,
];
pub fn gamma(color: Rgb888) -> Rgb888 {
Rgb888::new(
GAMMA8[color.r() as usize],
GAMMA8[color.g() as usize],
GAMMA8[color.b() as usize],
)
}
pub struct Serpentine {
pub size: Size,
}
impl Serpentine {
fn map(&self, mut p: Point) -> Option<usize> {
if p.x < 0 || p.x as u32 >= self.size.width || p.y < 0 || p.y as u32 >= self.size.height {
return None;
}
if p.y % 2 == 0 {
p.x = self.size.width as i32 - 1 - p.x;
}
Some(p.y as usize * self.size.width as usize + p.x as usize)
}
}
pub struct LedMatrix<'d> {
buffer: [RGB8; 320],
layout: Serpentine,
pio: PioWs2812<'d, PIO0, 0, 320>,
}
impl<'d> LedMatrix<'d> {
pub fn new(layout: Serpentine, pio: PioWs2812<'d, PIO0, 0, 320>) -> Self {
Self {
buffer: [RGB8::default(); 320],
layout,
pio,
}
}
pub async fn sync(&mut self) {
self.pio.write(&self.buffer).await;
}
}
impl<'d> DrawTarget for LedMatrix<'d> {
type Color = Rgb888;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
if let Some(x) = self.layout.map(point).and_then(|i| self.buffer.get_mut(i)) {
let color = gamma(color);
// Map to 0-64 for brightness limiting
*x = RGB8::new(
color.r().div_ceil(2),
color.g().div_ceil(2),
color.b().div_ceil(2),
);
}
}
Ok(())
}
}
impl<'d> OriginDimensions for LedMatrix<'d> {
fn size(&self) -> Size {
self.layout.size
}
}

View file

@ -1,61 +1,40 @@
#![no_std]
#![no_main]
mod art;
mod led_matrix;
use core::panic::PanicInfo;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_futures::select::{select_array, select4};
use embassy_rp::gpio::{Input, Level, Output, Pull};
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::Pio;
use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
use embassy_rp::pwm::{self, Pwm};
use embassy_rp::rom_data::reset_to_usb_boot;
use embassy_rp::spi::{self, Blocking, Spi};
use embassy_rp::spinlock_mutex::SpinlockRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
use embedded_graphics::pixelcolor::Rgb888;
use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size, WebColors};
use embassy_time::{Delay, Timer};
use embedded_hal_bus::spi::ExclusiveDevice;
use heapless::String;
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::Orientation;
use {defmt_rtt as _, panic_probe as _};
use crate::art::{ColorMapFilter, Hal, HappyEyes, Rainbow, RainbowFilter};
use crate::led_matrix::{LedMatrix, Serpentine};
use embassy_rp::spi;
use embassy_rp::spi::{Blocking, Spi};
use embedded_graphics::image::{Image, ImageRawLE};
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::mono_font::ascii::FONT_10X20;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use embedded_graphics::text::Text;
use defmt_rtt as _;
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO0>;
});
use {defmt_rtt as _, panic_probe as _};
const DISPLAY_FREQ: u32 = 64_000_000;
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
async fn main(_spawner: Spawner) -> ! {
let p = embassy_rp::init(Default::default());
let buttons = Buttons {
f1: Input::new(p.PIN_11, Pull::Up),
f2: Input::new(p.PIN_14, Pull::Up),
f3: Input::new(p.PIN_9, Pull::Up),
f4: Input::new(p.PIN_3, Pull::Up),
a: Input::new(p.PIN_12, Pull::Up),
b: Input::new(p.PIN_13, Pull::Up),
// x = f2
y: Input::new(p.PIN_15, Pull::Up),
};
// display SPI
let mut display_buffer = [0u8; 512];
let mut _display = {
let mut display = {
let mut config = spi::Config::default();
config.frequency = DISPLAY_FREQ;
config.phase = spi::Phase::CaptureOnSecondTransition;
@ -78,8 +57,17 @@ async fn main(spawner: Spawner) -> ! {
.init(&mut Delay)
.expect("display init")
};
// Blank display - unused at the moment
let _display_backlight = Output::new(p.PIN_20, Level::Low);
let _display_backlight = Output::new(p.PIN_20, Level::High);
let raw_image_data = ImageRawLE::new(include_bytes!("assets/ferris.raw"), 86);
let ferris = Image::new(&raw_image_data, Point::new(0, 0));
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN);
let hello_text = Text::new(
"Hello embedded_graphics \n + embassy + RP2040!",
Point::new(0, 68),
style,
);
// setup RGB LED
let mut rg_config = pwm::Config::default();
@ -90,7 +78,7 @@ async fn main(spawner: Spawner) -> ! {
let mut rg_pwm = Pwm::new_output_ab(p.PWM_SLICE3, p.PIN_6, p.PIN_7, rg_config.clone());
let mut b_pwm = Pwm::new_output_a(p.PWM_SLICE4, p.PIN_8, b_config.clone());
let mut _set_led = |r: u8, g: u8, b: u8| {
let mut set_led = |r: u8, g: u8, b: u8| {
rg_config.compare_a = 0x101 * (255 - r as u16);
rg_config.compare_b = 0x101 * (255 - g as u16);
b_config.compare_a = 0x101 * (255 - b as u16);
@ -98,278 +86,52 @@ async fn main(spawner: Spawner) -> ! {
b_pwm.set_config(&b_config);
};
let mut led_pio = Pio::new(p.PIO0, Irqs);
let led_program = PioWs2812Program::new(&mut led_pio.common);
let led_surface = LedMatrix::new(
Serpentine {
size: Size::new(40, 8),
},
PioWs2812::new(
&mut led_pio.common,
led_pio.sm0,
p.DMA_CH0,
p.PIN_10,
&led_program,
),
);
spawner.spawn(led_task(led_surface)).ok();
spawner.spawn(controller_task(buttons)).ok();
// Setup buttons
let _btn_a = Input::new(p.PIN_12, Pull::Up);
let _btn_b = Input::new(p.PIN_13, Pull::Up);
// let _btn_x = Input::new(p.PIN_14, Pull::Up);
let _btn_y = Input::new(p.PIN_15, Pull::Up);
// go to sleep
loop {
Timer::after_secs(1).await
}
let mut btn_f1 = Input::new(p.PIN_11, Pull::Up);
let mut btn_f2 = Input::new(p.PIN_14, Pull::Up);
let mut btn_f3 = Input::new(p.PIN_9, Pull::Up);
let mut btn_f4 = Input::new(p.PIN_3, Pull::Up);
let mut render = |f_keys: [bool; 4]| {
display.clear(Rgb565::BLACK).ok();
ferris.draw(&mut display).ok();
hello_text.draw(&mut display).ok();
let mut f_key_string: String<16> = String::new();
for (f, s) in f_keys.into_iter().zip(["1", "2", "3", "4"]) {
f_key_string.push_str(f.then_some(s).unwrap_or(" ")).ok();
}
struct Buttons {
f1: Input<'static>,
f2: Input<'static>,
f3: Input<'static>,
f4: Input<'static>,
a: Input<'static>,
b: Input<'static>,
// x = f2
y: Input<'static>,
}
impl Buttons {
fn read(&self) -> ButtonStates {
ButtonStates {
f1: self.f1.is_low(),
f2: self.f2.is_low(),
f3: self.f3.is_low(),
f4: self.f4.is_low(),
a: self.a.is_low(),
b: self.b.is_low(),
y: self.y.is_low(),
}
}
}
#[derive(Clone, Copy, PartialEq)]
struct ButtonStates {
f1: bool,
f2: bool,
f3: bool,
f4: bool,
a: bool,
b: bool,
// x = f2
y: bool,
}
impl ButtonStates {
fn chord(&self) -> u8 {
(self.f1 as u8) + ((self.f2 as u8) << 1) + ((self.f3 as u8) << 2) + ((self.f4 as u8) << 3)
}
}
#[derive(Clone, Copy, Default)]
struct LedConfig {
pattern: LedPattern,
hal: HalConfig,
happy: HappyConfig,
}
#[derive(Clone, Copy, PartialEq, Default)]
enum LedPattern {
#[default]
Blank,
Hal,
Happy,
}
#[derive(Clone, Copy, PartialEq, Default)]
enum HalConfig {
#[default]
Red,
Blue,
}
impl HalConfig {
fn next(self) -> Self {
match self {
HalConfig::Red => HalConfig::Blue,
HalConfig::Blue => HalConfig::Red,
}
}
}
#[derive(Clone, Copy, PartialEq, Default)]
enum HappyConfig {
Green,
Blue,
#[default]
Purple,
Pink,
RainbowSlow,
RainbowFast,
}
impl HappyConfig {
fn next(self) -> Self {
match self {
HappyConfig::Green => HappyConfig::Blue,
HappyConfig::Blue => HappyConfig::Purple,
HappyConfig::Purple => HappyConfig::Pink,
HappyConfig::Pink => HappyConfig::RainbowSlow,
HappyConfig::RainbowSlow => HappyConfig::RainbowFast,
HappyConfig::RainbowFast => HappyConfig::Green,
}
}
}
static LED_CONFIG_SIGNAL: Signal<SpinlockRawMutex<0>, LedConfig> = Signal::new();
#[embassy_executor::task]
async fn controller_task(buttons: Buttons) {
let mut scan_ticker = Ticker::every(Duration::from_millis(1));
let mut chord_debounce = 0;
let mut stable_chord = 0;
let mut pending_chord = 0;
let mut led_config = LedConfig::default();
loop {
let state = buttons.read();
let new_chord = state.chord();
if state.y {
reset_to_usb_boot(0, 0);
}
if new_chord != pending_chord {
// Track the most recent chord key state.
pending_chord = new_chord;
chord_debounce = 10;
} else if chord_debounce > 0 {
// Wait several scans to make sure it's a stable reading.
chord_debounce -= 1;
} else if (!stable_chord & pending_chord) != 0 {
// Once it's stable, if new keys were pressed compared to
// stable_chord, replace stable_chord.
stable_chord = pending_chord;
} else if pending_chord == 0 && stable_chord != 0 {
// If a stable 0 (all keys released) is read, then process
// stable_chord as an input.
match stable_chord {
0x1 => {
// blank display
led_config.pattern = LedPattern::Blank;
LED_CONFIG_SIGNAL.signal(led_config);
}
0x2 => {
// happy
if led_config.pattern == LedPattern::Happy {
led_config.happy = led_config.happy.next();
} else {
led_config.pattern = LedPattern::Happy;
}
LED_CONFIG_SIGNAL.signal(led_config);
}
0x4 => {
// hal
if led_config.pattern == LedPattern::Hal {
led_config.hal = led_config.hal.next();
} else {
led_config.pattern = LedPattern::Hal;
}
LED_CONFIG_SIGNAL.signal(led_config);
}
_ => {
// unknown chord
}
}
stable_chord = 0;
}
scan_ticker.next().await;
}
}
#[embassy_executor::task]
async fn led_task(mut led_surface: LedMatrix<'static>) {
let hal = Hal::new();
let eyes = HappyEyes::new();
let mut rainbow_slow = Rainbow {
rate: Duration::from_secs(10),
width: 300,
elapsed: Duration::from_secs(0),
};
let mut rainbow_fast = Rainbow {
rate: Duration::from_secs(1),
width: 80,
elapsed: Duration::from_secs(0),
Text::new(&f_key_string, Point::new(0, 108), style)
.draw(&mut display)
.ok();
};
let mut ticker = Ticker::every(Duration::from_hz(30));
let start = Instant::now();
let mut led_config = LedConfig::default();
let mut last_state = None;
loop {
let elapsed = start.elapsed();
rainbow_slow.elapsed = elapsed;
rainbow_fast.elapsed = elapsed;
if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() {
led_config = new_config;
let state = [
btn_f1.is_low(),
btn_f2.is_low(),
btn_f3.is_low(),
btn_f4.is_low(),
];
if Some(state) != last_state {
render(state);
last_state = Some(state);
continue;
}
led_surface.clear(Rgb888::BLACK).ok();
match led_config.pattern {
LedPattern::Blank => {}
LedPattern::Hal => match led_config.hal {
HalConfig::Red => {
hal.draw(&mut led_surface).ok();
}
HalConfig::Blue => {
hal.draw(&mut ColorMapFilter {
inner_target: &mut led_surface,
filter: |color| Rgb888::new(color.b(), color.g(), color.r() / 2),
})
.ok();
}
},
LedPattern::Happy => match led_config.happy {
HappyConfig::Green => {
eyes.draw_colored(&mut led_surface, Rgb888::GREEN).ok();
}
HappyConfig::Blue => {
eyes.draw_colored(&mut led_surface, Rgb888::BLUE).ok();
}
HappyConfig::Purple => {
eyes.draw_colored(&mut led_surface, Rgb888::new(172, 20, 172))
.ok();
}
HappyConfig::Pink => {
eyes.draw_colored(&mut led_surface, Rgb888::CSS_HOT_PINK)
.ok();
}
HappyConfig::RainbowSlow => {
eyes.draw(&mut RainbowFilter {
inner_target: &mut led_surface,
rainbow: &rainbow_slow,
})
.ok();
}
HappyConfig::RainbowFast => {
eyes.draw(&mut RainbowFilter {
inner_target: &mut led_surface,
rainbow: &rainbow_fast,
})
.ok();
}
},
}
led_surface.sync().await;
ticker.next().await;
select_array([
btn_f1.wait_for_any_edge(),
btn_f2.wait_for_any_edge(),
btn_f3.wait_for_any_edge(),
btn_f4.wait_for_any_edge(),
])
.await;
}
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
reset_to_usb_boot(0, 0);
loop {}
}