@@ -0,0 +1,11 @@
|
|||||||
|
[build]
|
||||||
|
target = "riscv32imac-unknown-none-elf"
|
||||||
|
|
||||||
|
[target.riscv32imac-unknown-none-elf]
|
||||||
|
runner = "espflash flash --monitor"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
ESP_LOG = "info"
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["alloc", "core"]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"WebFetch(domain:crates.io)",
|
||||||
|
"WebFetch(domain:docs.esp-rs.org)",
|
||||||
|
"WebFetch(domain:github.com)",
|
||||||
|
"WebFetch(domain:raw.githubusercontent.com)",
|
||||||
|
"WebFetch(domain:docs.rs)",
|
||||||
|
"WebFetch(domain:docs.espressif.com)",
|
||||||
|
"WebFetch(domain:esp32.implrust.com)",
|
||||||
|
"WebSearch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Base image
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV LC_ALL=C.UTF-8
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
ARG CONTAINER_USER=esp
|
||||||
|
ARG CONTAINER_GROUP=esp
|
||||||
|
ARG NIGHTLY_VERSION=nightly-2025-11-01
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
curl unzip git pkg-config gcc \
|
||||||
|
libssl-dev libusb-1.0-0 libusb-1.0-0-dev libudev-dev \
|
||||||
|
sudo \
|
||||||
|
&& apt-get clean -y \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN adduser --disabled-password --gecos "" ${CONTAINER_USER}
|
||||||
|
|
||||||
|
# Allow esp to sync the dialout group GID at container start
|
||||||
|
RUN echo "${CONTAINER_USER} ALL=(root) NOPASSWD: /usr/sbin/groupadd, /usr/sbin/groupmod, /usr/sbin/usermod, /usr/bin/chmod" \
|
||||||
|
>> /etc/sudoers.d/esp-dialout
|
||||||
|
USER ${CONTAINER_USER}
|
||||||
|
WORKDIR /home/${CONTAINER_USER}
|
||||||
|
|
||||||
|
# Install rustup + nightly toolchain with RISC-V target and rust-src component
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \
|
||||||
|
--default-toolchain ${NIGHTLY_VERSION} -y --profile minimal \
|
||||||
|
--component rust-src,clippy,rustfmt \
|
||||||
|
--target riscv32imac-unknown-none-elf
|
||||||
|
|
||||||
|
ENV PATH=${PATH}:$HOME/.cargo/bin
|
||||||
|
|
||||||
|
# Install espflash (flash + monitor tool)
|
||||||
|
RUN ARCH=$($HOME/.cargo/bin/rustup show | grep "Default host" | sed -e 's/.* //') && \
|
||||||
|
curl -L "https://github.com/esp-rs/espflash/releases/latest/download/espflash-${ARCH}.zip" \
|
||||||
|
-o "${HOME}/.cargo/bin/espflash.zip" && \
|
||||||
|
unzip "${HOME}/.cargo/bin/espflash.zip" -d "${HOME}/.cargo/bin/" && \
|
||||||
|
rm "${HOME}/.cargo/bin/espflash.zip" && \
|
||||||
|
chmod u+x "${HOME}/.cargo/bin/espflash"
|
||||||
|
|
||||||
|
# Install probe-rs (on-chip debugger, flashing, RTT)
|
||||||
|
RUN $HOME/.cargo/bin/cargo install probe-rs-tools --locked
|
||||||
|
|
||||||
|
CMD ["/bin/bash"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "esp32c6-dev",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"NIGHTLY_VERSION": "nightly-2025-11-01"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Privileged container to access /dev
|
||||||
|
"privileged": true,
|
||||||
|
// Mount USB bus so espflash can reach the connected board
|
||||||
|
"mounts": [
|
||||||
|
"source=/dev/bus/usb/,target=/dev/bus/usb/,type=bind"
|
||||||
|
],
|
||||||
|
"runArgs": [
|
||||||
|
"--device=/dev/ttyACM0",
|
||||||
|
"--device=/dev/ttyACM1"
|
||||||
|
],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"settings": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnSaveMode": "modifications",
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/target/**": true
|
||||||
|
},
|
||||||
|
"rust-analyzer.check.command": "clippy",
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"rust-lang.rust-analyzer",
|
||||||
|
"tamasfe.even-better-toml",
|
||||||
|
"mutantdino.resourcemonitor",
|
||||||
|
"probe-rs.probe-rs-debugger"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "DIALOUT_GID=$(stat -c '%g' /dev/ttyACM0 2>/dev/null || stat -c '%g' /dev/ttyACM1 2>/dev/null || echo 20) && (getent group dialout > /dev/null && sudo groupmod -g $DIALOUT_GID dialout || sudo groupadd -g $DIALOUT_GID dialout) && sudo usermod -aG dialout esp && USB_GID=$(stat -c '%g' /dev/bus/usb/*/* 2>/dev/null | sort -u | head -1 || echo 46) && (getent group plugdev > /dev/null && sudo groupmod -g $USB_GID plugdev || sudo groupadd -g $USB_GID plugdev) && sudo usermod -aG plugdev esp || true",
|
||||||
|
"postStartCommand": "sudo chmod a+rw /dev/bus/usb/*/* 2>/dev/null; sudo chmod a+rw /dev/ttyACM* 2>/dev/null; true",
|
||||||
|
"remoteUser": "esp",
|
||||||
|
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
|
||||||
|
"workspaceFolder": "/workspace"
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
*.rs.bk
|
||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "probe-rs-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug ESP32-C6",
|
||||||
|
"chip": "esp32c6",
|
||||||
|
"flashingConfig": { "flashingEnabled": true },
|
||||||
|
"coreConfigs": [{
|
||||||
|
"programBinary": "target/riscv32imac-unknown-none-elf/debug/esp32c6-display"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+1404
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
|||||||
|
[package]
|
||||||
|
name = "esp32c6-display"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# ESP32-C6 hardware abstraction layer
|
||||||
|
esp-hal = { version = "1.0.0", features = ["esp32c6", "unstable"] }
|
||||||
|
# Panic handler + backtrace over serial
|
||||||
|
esp-backtrace = { version = "0.18", features = ["esp32c6", "panic-handler", "println"] }
|
||||||
|
# Serial output / log backend
|
||||||
|
esp-println = { version = "0.16", features = ["esp32c6", "log-04"] }
|
||||||
|
# Heap allocator
|
||||||
|
esp-alloc = "0.9"
|
||||||
|
# Logging facade
|
||||||
|
log = "0.4"
|
||||||
|
# ESP-IDF app descriptor required by the on-chip bootloader (and probe-rs)
|
||||||
|
esp-bootloader-esp-idf = { version = "0.2", features = ["esp32c6"] }
|
||||||
|
|
||||||
|
# embedded-hal 1.0 traits + bus utilities
|
||||||
|
embedded-hal = "1.0"
|
||||||
|
embedded-hal-bus = "0.2"
|
||||||
|
|
||||||
|
# MIPI DSI display driver (supports ST7789)
|
||||||
|
mipidsi = "0.10"
|
||||||
|
|
||||||
|
# 2D graphics primitives and text rendering
|
||||||
|
embedded-graphics = "0.8"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
debug = 2
|
||||||
|
debug-assertions = false
|
||||||
|
incremental = false
|
||||||
|
opt-level = "s"
|
||||||
|
lto = "fat"
|
||||||
|
overflow-checks = false
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 318 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
components = ["rust-src"]
|
||||||
|
targets = ["riscv32imac-unknown-none-elf"]
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
use embedded_graphics::pixelcolor::Rgb565;
|
||||||
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use mipidsi::{
|
||||||
|
ConfigurationError,
|
||||||
|
dcs::{
|
||||||
|
BitsPerPixel, ExitSleepMode, InterfaceExt, PixelFormat, SetAddressMode, SetDisplayOn,
|
||||||
|
SetInvertMode, SetPixelFormat, SetTearingEffect,
|
||||||
|
},
|
||||||
|
interface::{Interface, InterfaceKind},
|
||||||
|
models::{Model, ModelInitError},
|
||||||
|
options::{ColorInversion, ModelOptions, TearingEffect},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// JD9853 display driver in Rgb565 color mode.
|
||||||
|
///
|
||||||
|
/// The JD9853 has a 240-column internal framebuffer but the visible panel is
|
||||||
|
/// 172×320, starting at column offset 34. Configure the [`Builder`] as:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// Builder::new(JD9853, di)
|
||||||
|
/// .display_size(172, 320)
|
||||||
|
/// .display_offset(34, 0)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This panel requires colour inversion; the driver always enables it.
|
||||||
|
pub struct JD9853;
|
||||||
|
|
||||||
|
impl Model for JD9853 {
|
||||||
|
type ColorFormat = Rgb565;
|
||||||
|
|
||||||
|
// Full internal framebuffer of the JD9853 controller.
|
||||||
|
// Visible area is 172×320 at column offset 34 – set in the Builder.
|
||||||
|
const FRAMEBUFFER_SIZE: (u16, u16) = (240, 320);
|
||||||
|
|
||||||
|
fn init<DELAY, DI>(
|
||||||
|
&mut self,
|
||||||
|
di: &mut DI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
options: &ModelOptions,
|
||||||
|
) -> Result<SetAddressMode, ModelInitError<DI::Error>>
|
||||||
|
where
|
||||||
|
DELAY: DelayNs,
|
||||||
|
DI: Interface,
|
||||||
|
{
|
||||||
|
if !matches!(
|
||||||
|
DI::KIND,
|
||||||
|
InterfaceKind::Serial4Line
|
||||||
|
| InterfaceKind::Parallel8Bit
|
||||||
|
| InterfaceKind::Parallel16Bit
|
||||||
|
) {
|
||||||
|
return Err(ModelInitError::InvalidConfiguration(
|
||||||
|
ConfigurationError::UnsupportedInterface,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let madctl = SetAddressMode::from(options);
|
||||||
|
|
||||||
|
// --- Exit sleep and wait for internal oscillator ---
|
||||||
|
di.write_command(ExitSleepMode)?;
|
||||||
|
delay.delay_us(120_000);
|
||||||
|
|
||||||
|
// --- Unlock manufacturer commands ---
|
||||||
|
di.write_raw(0xDF, &[0x98, 0x53])?;
|
||||||
|
|
||||||
|
// --- Bank 0: panel / power / gamma settings ---
|
||||||
|
di.write_raw(0xB2, &[0x23])?;
|
||||||
|
di.write_raw(0xB7, &[0x00, 0x47, 0x00, 0x6F])?;
|
||||||
|
di.write_raw(0xBB, &[0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0])?;
|
||||||
|
di.write_raw(0xC0, &[0x44, 0xA4])?;
|
||||||
|
di.write_raw(0xC1, &[0x16])?;
|
||||||
|
di.write_raw(0xC3, &[0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77])?;
|
||||||
|
di.write_raw(
|
||||||
|
0xC4,
|
||||||
|
&[
|
||||||
|
0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
// Gamma table (positive + negative, 16 bytes each)
|
||||||
|
di.write_raw(
|
||||||
|
0xC8,
|
||||||
|
&[
|
||||||
|
0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
|
||||||
|
0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
|
||||||
|
0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
|
||||||
|
0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
di.write_raw(0xD0, &[0x04, 0x06, 0x6B, 0x0F, 0x00])?;
|
||||||
|
di.write_raw(0xD7, &[0x00, 0x30])?;
|
||||||
|
di.write_raw(0xE6, &[0x14])?;
|
||||||
|
|
||||||
|
// --- Bank 1: power settings ---
|
||||||
|
di.write_raw(0xDE, &[0x01])?;
|
||||||
|
di.write_raw(0xB7, &[0x03, 0x13, 0xEF, 0x35, 0x35])?;
|
||||||
|
di.write_raw(0xC1, &[0x14, 0x15, 0xC0])?;
|
||||||
|
di.write_raw(0xC2, &[0x06, 0x3A])?;
|
||||||
|
di.write_raw(0xC4, &[0x72, 0x12])?;
|
||||||
|
di.write_raw(0xBE, &[0x00])?;
|
||||||
|
|
||||||
|
// --- Bank 2: internal settings (first pass) ---
|
||||||
|
di.write_raw(0xDE, &[0x02])?;
|
||||||
|
di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
|
||||||
|
di.write_raw(0xE5, &[0x01, 0x02, 0x00])?;
|
||||||
|
|
||||||
|
// --- Return to bank 0 ---
|
||||||
|
di.write_raw(0xDE, &[0x00])?;
|
||||||
|
|
||||||
|
// --- Standard DCS: tearing effect + pixel format ---
|
||||||
|
di.write_command(SetTearingEffect::new(TearingEffect::Vertical))?;
|
||||||
|
|
||||||
|
let pf = PixelFormat::with_all(BitsPerPixel::from_rgb_color::<Self::ColorFormat>());
|
||||||
|
di.write_command(SetPixelFormat::new(pf))?;
|
||||||
|
|
||||||
|
// Seed the column / page address window (mipidsi will update this before
|
||||||
|
// each draw, but the second bank-2 block below requires these to have been
|
||||||
|
// written first to latch internal timing registers).
|
||||||
|
di.write_raw(0x2A, &[0x00, 0x22, 0x00, 0xCD])?; // cols 34..205
|
||||||
|
di.write_raw(0x2B, &[0x00, 0x00, 0x01, 0x3F])?; // rows 0..319
|
||||||
|
|
||||||
|
// --- Bank 2: internal settings (second pass, after window init) ---
|
||||||
|
di.write_raw(0xDE, &[0x02])?;
|
||||||
|
di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
|
||||||
|
di.write_raw(0xDE, &[0x00])?;
|
||||||
|
|
||||||
|
// --- Address mode + colour inversion ---
|
||||||
|
di.write_command(madctl)?;
|
||||||
|
|
||||||
|
// This panel always requires colour inversion to display correct colours.
|
||||||
|
di.write_command(SetInvertMode::new(ColorInversion::Inverted))?;
|
||||||
|
|
||||||
|
// 10 ms settle time before display-on (matches reference init ordering).
|
||||||
|
delay.delay_us(10_000);
|
||||||
|
|
||||||
|
di.write_command(SetDisplayOn)?;
|
||||||
|
|
||||||
|
Ok(madctl)
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use esp_backtrace as _;
|
||||||
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
|
use esp_hal::{
|
||||||
|
delay::Delay,
|
||||||
|
gpio::{Level, Output, OutputConfig},
|
||||||
|
main,
|
||||||
|
spi::master::{Config, Spi},
|
||||||
|
time::Rate,
|
||||||
|
};
|
||||||
|
use esp_println::println;
|
||||||
|
|
||||||
|
mod jd9853;
|
||||||
|
|
||||||
|
use jd9853::JD9853;
|
||||||
|
use mipidsi::{interface::SpiInterface, Builder};
|
||||||
|
|
||||||
|
use embedded_graphics::{
|
||||||
|
mono_font::{ascii::FONT_10X20, MonoTextStyle},
|
||||||
|
pixelcolor::Rgb565,
|
||||||
|
prelude::*,
|
||||||
|
primitives::{PrimitiveStyleBuilder, Rectangle},
|
||||||
|
text::Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: verify pin assignments against your board schematic
|
||||||
|
//
|
||||||
|
// Typical wiring for a 1.47" JD9853 breakout on ESP32-C6:
|
||||||
|
//
|
||||||
|
// Display pin │ ESP32-C6 GPIO
|
||||||
|
// ─────────────┼───────────────
|
||||||
|
// SCL / SCK │ GPIO6
|
||||||
|
// SDA / MOSI │ GPIO7
|
||||||
|
// CS │ GPIO10
|
||||||
|
// DC │ GPIO4
|
||||||
|
// RST │ GPIO5
|
||||||
|
// BLK / BL │ GPIO22 (set high to enable backlight)
|
||||||
|
//
|
||||||
|
const LCD_W: u16 = 172;
|
||||||
|
const LCD_H: u16 = 320;
|
||||||
|
|
||||||
|
#[main]
|
||||||
|
fn main() -> ! {
|
||||||
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||||
|
|
||||||
|
// 72 KB heap for mipidsi internal buffers, alloc, etc.
|
||||||
|
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||||
|
|
||||||
|
println!("ESP32-C6 display demo starting…");
|
||||||
|
|
||||||
|
// --- SPI bus -----------------------------------------------------------
|
||||||
|
let spi = Spi::new(
|
||||||
|
peripherals.SPI2,
|
||||||
|
Config::default()
|
||||||
|
.with_frequency(Rate::from_mhz(40))
|
||||||
|
.with_mode(esp_hal::spi::Mode::_0),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.with_sck(peripherals.GPIO1)
|
||||||
|
.with_mosi(peripherals.GPIO2);
|
||||||
|
|
||||||
|
// Manual chip-select via GPIO
|
||||||
|
let cs = Output::new(peripherals.GPIO14, Level::High, OutputConfig::default());
|
||||||
|
|
||||||
|
// Wrap bus + CS into an embedded-hal SpiDevice
|
||||||
|
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new(spi, cs, Delay::new()).unwrap();
|
||||||
|
|
||||||
|
// --- Display -----------------------------------------------------------
|
||||||
|
let dc = Output::new(peripherals.GPIO15, Level::Low, OutputConfig::default());
|
||||||
|
let rst = Output::new(peripherals.GPIO22, Level::High, OutputConfig::default());
|
||||||
|
|
||||||
|
// Backlight — drive high to enable
|
||||||
|
let _bl = Output::new(peripherals.GPIO23, Level::High, OutputConfig::default());
|
||||||
|
|
||||||
|
// mipidsi 0.10 requires a small scratch buffer owned by the interface
|
||||||
|
let mut buf = [0u8; 512];
|
||||||
|
let di = SpiInterface::new(spi_device, dc, &mut buf);
|
||||||
|
|
||||||
|
// JD9853 has a 240-wide internal framebuffer; the 172-pixel panel starts at column 34.
|
||||||
|
let mut display = Builder::new(JD9853, di)
|
||||||
|
.reset_pin(rst)
|
||||||
|
.display_size(LCD_W, LCD_H)
|
||||||
|
.display_offset(34, 0)
|
||||||
|
.init(&mut Delay::new())
|
||||||
|
.expect("display init failed");
|
||||||
|
|
||||||
|
// --- Drawing -----------------------------------------------------------
|
||||||
|
display.clear(Rgb565::BLACK).unwrap();
|
||||||
|
|
||||||
|
// Blue rectangle as background banner
|
||||||
|
let bg_style = PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(Rgb565::new(0, 0, 20))
|
||||||
|
.build();
|
||||||
|
Rectangle::new(Point::new(0, 0), Size::new(LCD_W as u32, 40))
|
||||||
|
.into_styled(bg_style)
|
||||||
|
.draw(&mut display)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// White text
|
||||||
|
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
||||||
|
Text::new("ESP32-C6", Point::new(10, 28), text_style)
|
||||||
|
.draw(&mut display)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("Display ready.");
|
||||||
|
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user