Its to add some meat to the skeleton. Serial/UART driver code would be the minimalistic and necessary code to enable debugging. So, lets create a simplistic UART driver. There are two UART instances on FE310 SoC.
Quick note, above constants are declared as usize (size dependent on target architecturewhat iswha ti) its better to declare above constants as usize instead of u32, though both are correct above.
Lets create a struct for Uart. Its public to be visible outside to be instantiated from execs binary package.
pub struct Uart {
base_address: usize, // Base address of UART register block
}One of the differentiating and nicest things about rust than C is its ability to build/implement functions on struct. So, lets build functions with impl block in rust.
Function
from_base_addressto create a Uart Instance with passed in baseinitfunction to configure/initialize uart settings like baud rate, start bit count, stop bit count, interrupts etcsend_byteto transmit a bytereceive_byteto receive a byteis_data_readyto check if any receivable byte in fifois_tx_readyto check if space available in tx fifo
impl Uart {
// Get one of the instance with base address
pub fn from_base_address (base_address: usize) -> Self {
Uart { base_address }
}
// Initialize the UART with desired baud_rate
// May be should provide a enum with supported baud rates
pub fn init(&self, baud_rate: u32) {
unsafe {
if baud_rate != 115200 {
panic!("Invalid baud");
}
let div_register = (self.base_address + 0x18) as *mut u32;
div_register.write_volatile(138); /* Baud 115200 at tlclk 16 Mhz */
// At reset/default, number of stop bits in txctrl is 1
// No tx/rx watermark interrupts
/* Enable tx */
let tx_ctrl = (self.base_address + 0x8) as *mut u32;
tx_ctrl.write_volatile(tx_ctrl.read_volatile() | 0x1 as u32);
/* Enable rx */
let rx_ctrl = (self.base_address + 0xC) as *mut u32;
rx_ctrl.write_volatile(rx_ctrl.read_volatile() | 0x1 as u32);
}
}
/// Send a byte
/// Writing to txdata register, enqueues the character
/// to transmit FIFO if FIFO is able to accept new entries.
/// Reading from txdata returns the currrent value of the full flag
/// and zero in the data field.
///
/// ```
/// --------------------------
/// | FULL | RESERVED | DATA |
/// --------------------------
/// | 31 | 30 : 8 | [7:0]|
/// --------------------------
/// ```
///
/// `Full` flag indicates whether the FIFO is able to accept new entries.
/// When `full` flag is set, writes are ignored.
/// RISC-V `amoor.w` instruction can be used to both read the status
/// and attempt to enqueue data with a non-zero return value
/// indicating the character was NOT accepted.
pub fn send_byte(&self, byte: u8) {
unsafe {
// Write to the UART data register
let data_register = self.base_address as *mut u8;
data_register.write_volatile(byte);
}
}
/// Receive a byte
/// Reading the rxdata register dequeues the data from the receive FIFO
/// and returns the value in the data field. The 'empty` flag indicates if
/// fifo is empty and when set 'data` field dont have valid data.
/// Writes to `rxdata` register are ignored.
///
/// ```
/// --------------------------
/// | EMPTY | RESERVED | DATA |
/// --------------------------
/// | 31 | [30: 8] | [7:0]|
/// --------------------------
/// ```
pub fn receive_byte(&self) -> u8 {
unsafe {
// Read from the UART data register
let data_register = (self.base_address +0x04) as *mut u8;
data_register.read_volatile()
}
}
// Check if data is available to read
pub fn is_data_ready(&self) -> bool {
unsafe {
// Check rx data register
let status_register = (self.base_address + 0x04) as *mut u32;
status_register.read_volatile() & 0x8000_0000 == 0 // #31 empty
}
}
// Check if TX is ready to transmit data
pub fn is_tx_ready(&self) -> bool {
unsafe {
let status_register = (self.base_address ) as *mut u32; // Example offset
status_register.read_volatile() & 0x8000_0000 == 0 // #31 full
}
}
}Now its time to put it all into library. Though, UART code can be put into lib.rs I prefer it as separate module for readability and maintainability.
So, I put the driver code into uart.rs and expose as public module uart with pub mod uart; in lib.rs . Its public to be called from execs binary crate (from outside libs crate).
cat libs/src/lib.rs
#![no_std]
#![no_main]
use core::arch::global_asm;
global_asm!(include_str!("boot.S"));
pub mod uart;
bootstrap-ws $
These changes are released as v0.3.0 and compare with previous release should show the changes.