diff --git a/README.md b/README.md index b1fa35e..f5a5158 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Maix Go / K210 stuff ===================== +Some demo projects (mostly in rust) for the Maix Go. + Building the C projects ----------------------- @@ -52,7 +54,7 @@ Running ELF ----------- There is no need anymore to convert to raw binary, as ELF executables can be executed directly on -the device (no flashing) using a recent checkout of [kflash](https://github.com/kendryte/kflash.py) +the device (without flashing) using a recent checkout of [kflash](https://github.com/kendryte/kflash.py) ```bash kflash.py -t -s -p /dev/ttyUSB1 -B goE "${ELF_NAME}" @@ -64,7 +66,8 @@ and run code on the device through JTAG and OpenOCD, but I have never got this t Currently, rust generates ELF executables based at address `0xffffffff80000000` instead of the expected `0x80000000`, to work around lack of medany memory -model support in LLVM. To make this work with kflash I had to patch the +model support in LLVM (this has ben fixed but hasn't reached stable yet at the +time of writing). To make this work with kflash I had to patch the following: ```patch diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 755a751..03a0b57 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -18,4 +18,4 @@ members = [ [patch.crates-io] k210-hal = { git = "https://github.com/riscv-rust/k210-hal.git", rev = "b83e843c19a2f0bc4eb7f56322ae844818709298" } -k210-pac = { git = "https://github.com/riscv-rust/k210-pac.git", rev = "91b421e17729b549566271a66ba19ce6fc205178" } +k210-pac = { git = "https://github.com/riscv-rust/k210-pac.git", rev = "dc1acb925b0ee45b7c5fefb6897dc8cf3c32838d" } diff --git a/rust/buffered-uart/.cargo/config b/rust/buffered-uart/.cargo/config new file mode 100644 index 0000000..27904bc --- /dev/null +++ b/rust/buffered-uart/.cargo/config @@ -0,0 +1,4 @@ +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-C", "link-arg=-Ttrap.x", +] diff --git a/rust/buffered-uart/.gitignore b/rust/buffered-uart/.gitignore new file mode 100644 index 0000000..f0e3bca --- /dev/null +++ b/rust/buffered-uart/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk \ No newline at end of file diff --git a/rust/buffered-uart/Cargo.toml b/rust/buffered-uart/Cargo.toml new file mode 100644 index 0000000..390e6a4 --- /dev/null +++ b/rust/buffered-uart/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "buffered-uart" +version = "0.1.0" +authors = ["W.J. van der Laan "] +edition = "2018" + +[dependencies] +bare-metal = "0.2.0" +riscv-rt = "0.6" +k210-hal = "0.1.0" +riscv = { version = "0.5", features = ["inline-asm"] } +k210-shared = { path = "../k210-shared" } diff --git a/rust/buffered-uart/README.md b/rust/buffered-uart/README.md new file mode 100644 index 0000000..b713d28 --- /dev/null +++ b/rust/buffered-uart/README.md @@ -0,0 +1,2 @@ +# `buffered-uart` + diff --git a/rust/buffered-uart/build.rs b/rust/buffered-uart/build.rs new file mode 100644 index 0000000..0d3e283 --- /dev/null +++ b/rust/buffered-uart/build.rs @@ -0,0 +1,13 @@ +use std::{env, fs}; +use std::path::PathBuf; +use std::io::Write; + +fn main() { + // Put the linker script somewhere the linker can find it + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_dir.display()); + + fs::File::create(out_dir.join("trap.x")).unwrap() + .write_all(include_bytes!("trap.x")).unwrap(); + println!("cargo:rerun-if-changed=trap.x"); +} diff --git a/rust/buffered-uart/src/lib.rs b/rust/buffered-uart/src/lib.rs new file mode 100644 index 0000000..12899ad --- /dev/null +++ b/rust/buffered-uart/src/lib.rs @@ -0,0 +1,238 @@ +#![no_std] +#![allow(dead_code)] +/** Buffered UART, using interrupts — currently only receiving is buffered because this is most + * important, avoiding loss of data when the FIFO fills up. Buffered sending is slightly less + * interesting without a fully fledged scheduling OS. + */ +// Yep, this is an awful hack, many things are hardcoded that should not be, just a proof of concept… +use bare_metal::Nr; +use core::sync::atomic::{AtomicUsize, Ordering}; +use k210_hal::pac; +use k210_shared::soc::sysctl; +use pac::interrupt::Interrupt; +use riscv::register::{mcause, mhartid, mie, mip, mstatus}; + +const UART_BUFSIZE: usize = 4096; +/** UART ring buffer */ +struct UartInstance { + buf: [u8; UART_BUFSIZE], + /** writing happens at head */ + head: AtomicUsize, + /** reading happens at tail, until tail==head */ + tail: AtomicUsize, +} + +static mut UART1_INSTANCE_RECV: UartInstance = UartInstance { + buf: [0; UART_BUFSIZE], + head: AtomicUsize::new(0), + tail: AtomicUsize::new(0), +}; + +/** UART IIR interrupt reason */ +const UART_INTERRUPT_SEND: u8 = 0x02; +const UART_INTERRUPT_RECEIVE: u8 = 0x04; +const UART_INTERRUPT_CHARACTER_TIMEOUT: u8 = 0x0C; + +/** Receive FIFO trigger */ +const UART_RECEIVE_FIFO_1: u32 = 0; +const UART_RECEIVE_FIFO_4: u32 = 1; +const UART_RECEIVE_FIFO_8: u32 = 2; +const UART_RECEIVE_FIFO_14: u32 = 3; + +/** Send FIFO trigger */ +const UART_SEND_FIFO_0: u32 = 0; +const UART_SEND_FIFO_2: u32 = 1; +const UART_SEND_FIFO_4: u32 = 2; +const UART_SEND_FIFO_8: u32 = 3; + +const UART_IER_ERBFI: u32 = 1; + +/** Handle UARTx interrupt */ +fn interrupt_uart1() { + unsafe { + let uart = pac::UART1::ptr(); + let irecv = &mut UART1_INSTANCE_RECV; + + match ((*uart).fcr_iir.read().bits() & 0xf) as u8 { + UART_INTERRUPT_RECEIVE | UART_INTERRUPT_CHARACTER_TIMEOUT => { + // Read recv FIFO into receive ringbuffer + let mut head = irecv.head.load(Ordering::SeqCst); + while ((*uart).lsr.read().bits() & 1) != 0 { + irecv.buf[head] = ((*uart).rbr_dll_thr.read().bits() & 0xff) as u8; + head += 1; + if head == UART_BUFSIZE { + head = 0; + } + } + irecv.head.store(head, Ordering::SeqCst); + } + UART_INTERRUPT_SEND => { + // TODO + } + _ => {} + } + } +} + +/** Global trap handler */ +#[no_mangle] +fn my_trap_handler() { + let hartid = mhartid::read(); + let cause = mcause::read().cause(); + match cause { + // PLIC interrupts + mcause::Trap::Interrupt(mcause::Interrupt::MachineExternal) => { + if mip::read().mext() { + unsafe { + let plic = pac::PLIC::ptr(); + let target = &(*plic).targets[hartid * 2]; + let int_num = target.claim.read().bits(); + let int = Interrupt::try_from(int_num as u8).unwrap(); + + // Does this really need the 'disable other interrupts, change threshold' dance + // as done in handle_irq_m_ext in plic.c? + match int { + Interrupt::UART1 => interrupt_uart1(), + // We'll get a spurious UARTHS interrupt, ignore it + Interrupt::UARTHS => {} + _ => { + panic!( + "unknown machineexternal {:?} on {}, int {:?}", + cause, hartid, int + ); + } + } + + // Perform IRQ complete + target.claim.write(|w| w.bits(int_num)); + } + } + } + _ => { + panic!("unknown trap {:?}", cause); + } + } +} + +/** Enable or disable a PLIC interrupt for the current core */ +fn plic_irq_enable(interrupt: Interrupt, enabled: bool) { + let targetid = mhartid::read() * 2; + let irq_nr = interrupt.nr(); + unsafe { + let plic = pac::PLIC::ptr(); + let bit = 1 << ((irq_nr as u32) % 32); + if enabled { + (*plic).target_enables[targetid].enable[(irq_nr as usize) / 32] + .modify(|r, w| w.bits(r.bits() | bit)); + } else { + (*plic).target_enables[targetid].enable[(irq_nr as usize) / 32] + .modify(|r, w| w.bits(r.bits() & !bit)); + } + } +} + +/** Set interrupt priority (0-7) */ +fn plic_set_priority(interrupt: Interrupt, priority: u32) { + let irq_nr = interrupt.nr(); + unsafe { + let plic = pac::PLIC::ptr(); + (*plic).priority[irq_nr as usize].write(|w| w.bits(priority)); + } +} + +/** Initialize UART */ +fn uart_init(baud_rate: u32) { + let uart = pac::UART1::ptr(); + sysctl::clock_enable(sysctl::clock::UART1); + sysctl::reset(sysctl::reset::UART1); + + // Hardcode these for now: + let data_width = 8; // 8 data bits + let stopbit_val = 0; // 1 stop bit + let parity_val = 0; // No parity + let divisor = sysctl::clock_get_freq(sysctl::clock::APB0) / baud_rate; + let dlh = ((divisor >> 12) & 0xff) as u8; + let dll = ((divisor >> 4) & 0xff) as u8; + let dlf = (divisor & 0xf) as u8; + unsafe { + // Set Divisor Latch Access Bit (enables DLL DLH) to set baudrate + (*uart).lcr.write(|w| w.bits(1 << 7)); + (*uart).dlh_ier.write(|w| w.bits(dlh.into())); + (*uart).rbr_dll_thr.write(|w| w.bits(dll.into())); + (*uart).dlf.write(|w| w.bits(dlf.into())); + // Clear Divisor Latch Access Bit after setting baudrate + (*uart) + .lcr + .write(|w| w.bits((data_width - 5) | (stopbit_val << 2) | (parity_val << 3))); + // Write IER + (*uart).dlh_ier.write(|w| w.bits(0x80)); /* THRE */ + // Write FCT + (*uart) + .fcr_iir + .write(|w| w.bits(UART_RECEIVE_FIFO_4 << 6 | UART_SEND_FIFO_8 << 4 | 0x1 << 3 | 0x1)); + } +} + +/** Enable or disable UART interrupt */ +fn uart_enable_intr(recv: bool) { + unsafe { + let uart = pac::UART1::ptr(); + if recv { + (*uart) + .dlh_ier + .modify(|r, w| w.bits(r.bits() | UART_IER_ERBFI)); + plic_set_priority(Interrupt::UART1, 6); + plic_irq_enable(Interrupt::UART1, true); + } else { + (*uart) + .dlh_ier + .modify(|r, w| w.bits(r.bits() & !UART_IER_ERBFI)); + plic_set_priority(Interrupt::UART1, 0); + plic_irq_enable(Interrupt::UART1, false); + } + } +} + +/** Send data to UART */ +pub fn send(s: &[u8]) { + let uart = pac::UART1::ptr(); + for &c in s { + unsafe { + while ((*uart).lsr.read().bits() & (1 << 5)) != 0 {} + (*uart).rbr_dll_thr.write(|w| w.bits(c.into())); + } + } +} + +/** Receive data from UART (non-blocking, returns number of bytes received) */ +pub fn recv(s: &mut [u8]) -> usize { + let irecv = unsafe { &mut UART1_INSTANCE_RECV }; + let head = irecv.head.load(Ordering::SeqCst); + let mut tail = irecv.tail.load(Ordering::SeqCst); + let mut ptr = 0; + while ptr < s.len() && tail != head { + s[ptr] = irecv.buf[tail]; + tail += 1; + if tail == UART_BUFSIZE { + tail = 0; + } + ptr += 1; + } + irecv.tail.store(tail, Ordering::SeqCst); + ptr +} + +/** Initialize interrupts and buffered UART handling */ +pub fn init() { + unsafe { + // Enable interrupts in general + mstatus::set_mie(); + // Set the Machine-Software bit in MIE + mie::set_msoft(); + // Set the Machine-External bit in MIE + mie::set_mext(); + } + + uart_init(115_200); + uart_enable_intr(true); +} diff --git a/rust/buffered-uart/trap.x b/rust/buffered-uart/trap.x new file mode 100644 index 0000000..d58bc44 --- /dev/null +++ b/rust/buffered-uart/trap.x @@ -0,0 +1,2 @@ +/* set our own trap handler */ +trap_handler = my_trap_handler; diff --git a/rust/term-server/.cargo/config b/rust/term-server/.cargo/config new file mode 100644 index 0000000..27904bc --- /dev/null +++ b/rust/term-server/.cargo/config @@ -0,0 +1,4 @@ +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-C", "link-arg=-Ttrap.x", +] diff --git a/rust/term-server/Cargo.toml b/rust/term-server/Cargo.toml index 7c49438..c7ff666 100644 --- a/rust/term-server/Cargo.toml +++ b/rust/term-server/Cargo.toml @@ -13,3 +13,4 @@ riscv = "0.5" k210-shared = { path = "../k210-shared" } k210-console = { path = "../k210-console" } esp8266at = { path = "../../util/esp8266at", default-features = false } +buffered-uart = { path = "../buffered-uart" } diff --git a/rust/term-server/src/main.rs b/rust/term-server/src/main.rs index 7724a79..23335d5 100644 --- a/rust/term-server/src/main.rs +++ b/rust/term-server/src/main.rs @@ -5,7 +5,6 @@ #![no_main] use core::str; -use embedded_hal::serial; use esp8266at::handler::{NetworkEvent, SerialNetworkHandler}; use esp8266at::response::{parse, ParseResult}; use esp8266at::traits; @@ -21,42 +20,27 @@ use k210_shared::soc::gpiohs; use k210_shared::soc::sleep::usleep; use k210_shared::soc::spi::SPIExt; use k210_shared::soc::sysctl; -use nb::block; -use riscv::register::mcycle; use riscv_rt::entry; use k210_console::console::{Console, ScreenImage, DISP_HEIGHT, DISP_WIDTH, DISP_PIXELS}; +use buffered_uart; mod config; const DEFAULT_BAUD: u32 = 115_200; const TIMEOUT: usize = 390_000_000 * 40 / 115200; -struct WriteAdapter<'a, TX> -where - TX: serial::Write, -{ - tx: &'a mut TX, -} -impl<'a, TX> WriteAdapter<'a, TX> -where - TX: serial::Write, - TX::Error: core::fmt::Debug, -{ - fn new(tx: &'a mut TX) -> Self { - Self { tx } +struct WriteAdapter; + +impl WriteAdapter { + fn new() -> Self { + Self { } } } -impl<'a, TX> traits::Write for WriteAdapter<'a, TX> -where - TX: serial::Write, - TX::Error: core::fmt::Debug, -{ - type Error = TX::Error; +impl traits::Write for WriteAdapter { + type Error = (); fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { - for ch in buf { - block!(self.tx.write(*ch))?; - } + buffered_uart::send(buf); Ok(()) } } @@ -97,14 +81,15 @@ fn main() -> ! { // Configure UART1 (→WIFI) sysctl::clock_enable(sysctl::clock::UART1); sysctl::reset(sysctl::reset::UART1); + fpioa::set_function(io::WIFI_RX, fpioa::function::UART1_TX); + fpioa::set_function(io::WIFI_TX, fpioa::function::UART1_RX); fpioa::set_function(io::WIFI_EN, fpioa::function::GPIOHS8); fpioa::set_io_pull(io::WIFI_EN, fpioa::pull::DOWN); gpiohs::set_pin(8, true); gpiohs::set_direction(8, gpio::direction::OUTPUT); - let wifi_serial = p.UART1.configure((p.pins.pin7, p.pins.pin6), DEFAULT_BAUD.bps(), &clocks); - let (mut wtx, mut wrx) = wifi_serial.split(); - let mut wa = WriteAdapter::new(&mut wtx); + buffered_uart::init(); + let mut wa = WriteAdapter::new(); let mut sh = SerialNetworkHandler::new(&mut wa, config::APNAME.as_bytes(), config::APPASS.as_bytes()); // LCD ini @@ -133,21 +118,7 @@ fn main() -> ! { } // Receive into buffer - let mut lastrecv = mcycle::read(); - while ofs < serial_buf.len() { - // Read until we stop receiving for a certain duration - // This is a hack around the fact that in the time that the parser runs, - // more than one FIFO full of characters can be received so characters could be - // lost. The right way would be to receive in an interrupt handler, but, - // we don't have that yet. - if let Ok(ch) = wrx.read() { - serial_buf[ofs] = ch; - ofs += 1; - lastrecv = mcycle::read(); - } else if (mcycle::read().wrapping_sub(lastrecv)) >= TIMEOUT { - break; - } - } + ofs += buffered_uart::recv(&mut serial_buf[ofs..]); //writeln!(debug, "ofs: {} received {} chars {:?}", ofs0, ofs - ofs0, // &serial_buf[ofs0..ofs]).unwrap(); diff --git a/rust/weather/.cargo/config b/rust/weather/.cargo/config new file mode 100644 index 0000000..27904bc --- /dev/null +++ b/rust/weather/.cargo/config @@ -0,0 +1,4 @@ +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-C", "link-arg=-Ttrap.x", +] diff --git a/rust/weather/Cargo.toml b/rust/weather/Cargo.toml index 218eed8..1ecff4c 100644 --- a/rust/weather/Cargo.toml +++ b/rust/weather/Cargo.toml @@ -13,3 +13,4 @@ riscv = "0.5" k210-shared = { path = "../k210-shared" } k210-console = { path = "../k210-console" } esp8266at = { path = "../../util/esp8266at", default-features = false } +buffered-uart = { path = "../buffered-uart" } diff --git a/rust/weather/src/main.rs b/rust/weather/src/main.rs index 92c9094..7e76ef4 100644 --- a/rust/weather/src/main.rs +++ b/rust/weather/src/main.rs @@ -5,7 +5,6 @@ #![no_main] use core::str; -use embedded_hal::serial; use esp8266at::handler::{NetworkEvent, SerialNetworkHandler}; use esp8266at::response::{parse, ConnectionType, ParseResult}; use esp8266at::traits::{self, Write}; @@ -21,42 +20,27 @@ use k210_shared::soc::gpiohs; use k210_shared::soc::sleep::usleep; use k210_shared::soc::spi::SPIExt; use k210_shared::soc::sysctl; -use nb::block; -use riscv::register::mcycle; use riscv_rt::entry; use k210_console::console::{Console, ScreenImage, DISP_HEIGHT, DISP_WIDTH, DISP_PIXELS}; +use buffered_uart; mod config; const DEFAULT_BAUD: u32 = 115_200; const TIMEOUT: usize = 390_000_000 * 40 / 115200; -struct WriteAdapter<'a, TX> -where - TX: serial::Write, -{ - tx: &'a mut TX, -} -impl<'a, TX> WriteAdapter<'a, TX> -where - TX: serial::Write, - TX::Error: core::fmt::Debug, -{ - fn new(tx: &'a mut TX) -> Self { - Self { tx } +struct WriteAdapter; + +impl WriteAdapter { + fn new() -> Self { + Self { } } } -impl<'a, TX> traits::Write for WriteAdapter<'a, TX> -where - TX: serial::Write, - TX::Error: core::fmt::Debug, -{ - type Error = TX::Error; +impl traits::Write for WriteAdapter { + type Error = (); fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { - for ch in buf { - block!(self.tx.write(*ch))?; - } + buffered_uart::send(buf); Ok(()) } } @@ -97,17 +81,18 @@ fn main() -> ! { // Configure UART1 (→WIFI) sysctl::clock_enable(sysctl::clock::UART1); sysctl::reset(sysctl::reset::UART1); + fpioa::set_function(io::WIFI_RX, fpioa::function::UART1_TX); + fpioa::set_function(io::WIFI_TX, fpioa::function::UART1_RX); fpioa::set_function(io::WIFI_EN, fpioa::function::GPIOHS8); fpioa::set_io_pull(io::WIFI_EN, fpioa::pull::DOWN); gpiohs::set_pin(8, true); gpiohs::set_direction(8, gpio::direction::OUTPUT); - let wifi_serial = p.UART1.configure((p.pins.pin7, p.pins.pin6), DEFAULT_BAUD.bps(), &clocks); - let (mut wtx, mut wrx) = wifi_serial.split(); - let mut wa = WriteAdapter::new(&mut wtx); + buffered_uart::init(); + let mut wa = WriteAdapter::new(); let mut sh = SerialNetworkHandler::new(&mut wa, config::APNAME.as_bytes(), config::APPASS.as_bytes()); - // LCD ini + // LCD init let dmac = p.DMAC.configure(); let spi = p.SPI0.constrain(); let mut lcd = LCD::new(spi, &dmac, dma_channel::CHANNEL0); @@ -145,21 +130,7 @@ fn main() -> ! { } // Receive into buffer - let mut lastrecv = mcycle::read(); - while ofs < serial_buf.len() { - // Read until we stop receiving for a certain duration - // This is a hack around the fact that in the time that the parser runs, - // more than one FIFO full of characters can be received so characters could be - // lost. The right way would be to receive in an interrupt handler, but, - // we don't have that yet. - if let Ok(ch) = wrx.read() { - serial_buf[ofs] = ch; - ofs += 1; - lastrecv = mcycle::read(); - } else if (mcycle::read().wrapping_sub(lastrecv)) >= TIMEOUT { - break; - } - } + ofs += buffered_uart::recv(&mut serial_buf[ofs..]); //writeln!(debug, "ofs: {} received {} chars {:?}", ofs0, ofs - ofs0, // &serial_buf[ofs0..ofs]).unwrap();