rust: Use buffered UART1 in term-server and weather

This commit is contained in:
Wladimir J. van der Laan 2019-08-16 15:04:15 +00:00
parent 1118d708b1
commit 213ca1d50e
15 changed files with 318 additions and 90 deletions

View File

@ -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

View File

@ -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" }

View File

@ -0,0 +1,4 @@
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Ttrap.x",
]

2
rust/buffered-uart/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

View File

@ -0,0 +1,12 @@
[package]
name = "buffered-uart"
version = "0.1.0"
authors = ["W.J. van der Laan <laanwj@protonmail.com>"]
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" }

View File

@ -0,0 +1,2 @@
# `buffered-uart`

View File

@ -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");
}

View File

@ -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);
}

View File

@ -0,0 +1,2 @@
/* set our own trap handler */
trap_handler = my_trap_handler;

View File

@ -0,0 +1,4 @@
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Ttrap.x",
]

View File

@ -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" }

View File

@ -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<u8>,
{
tx: &'a mut TX,
}
impl<'a, TX> WriteAdapter<'a, TX>
where
TX: serial::Write<u8>,
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<u8>,
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();

View File

@ -0,0 +1,4 @@
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Ttrap.x",
]

View File

@ -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" }

View File

@ -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<u8>,
{
tx: &'a mut TX,
}
impl<'a, TX> WriteAdapter<'a, TX>
where
TX: serial::Write<u8>,
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<u8>,
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();