bl602-sdk 0.0.6

Rust Wrapper for BL602 IoT SDK
Documentation
//!  Rust Wrapper for BL602 IoT SDK. See "The RISC-V BL602 Book" <https://lupyuen.github.io/articles/book>
#![no_std]  //  Use the Rust Core Library instead of the Rust Standard Library, which is not compatible with embedded systems

use bl602_macros::safe_wrap;
use result::*;

///////////////////////////////////////////////////////////////////////////////
//  Wrapper for BL602 HAL generated by `bindgen` and `safe_wrap`

/// ADC HAL for BL602. See <https://lupyuen.github.io/articles/book#adc-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod adc;

/// DMA HAL for BL602. See <https://lupyuen.github.io/articles/book#dma-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod dma;

/// GPIO HAL for BL602. See <https://lupyuen.github.io/articles/book#gpio-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod gpio;

/// I2C HAL for BL602. See <https://lupyuen.github.io/articles/book#i2c-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod i2c;

/// PWM HAL for BL602. See <https://lupyuen.github.io/articles/book#pwm-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod pwm;

/// SPI HAL for BL602. See <https://lupyuen.github.io/articles/book#spi-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
pub mod spi;

/// UART HAL for BL602. See <https://lupyuen.github.io/articles/book#uart-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod uart;

/// WiFi HAL for BL602. See <https://lupyuen.github.io/articles/book#wifi-on-bl602>
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
pub mod wifi;

//  Import the Rust Core Library
use core::{
    str::FromStr,      //  For converting `str` to `String`
};

///////////////////////////////////////////////////////////////////////////////
//  Wrapper for NimBLE Porting Layer
//  TODO: Generate with `bindgen` and `safe_wrap`

/// Print a message to the serial console.
/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration
pub fn puts(s: &str) -> i32 {  //  `&str` is a reference to a string slice, similar to `const char *` in C

    extern "C" {  //  Import C Function
        /// Print a message to the serial console (from C stdio library)
        fn puts(s: *const u8) -> i32;
    }

    //  Convert `str` to `String`, which similar to `char [64]` in C
    let mut s_with_null = String::from_str(s)  //  `mut` because we will modify it
        .expect("puts conversion failed");     //  If it exceeds 64 chars, halt with an error
    
    //  Terminate the string with null, since we will be passing to C
    s_with_null.push('\0')
        .expect("puts overflow");  //  If we exceed 64 chars, halt with an error

    //  Convert the null-terminated string to a pointer
    let p = s_with_null.as_str().as_ptr();

    //  Call the C function
    unsafe {  //  Flag this code as unsafe because we're calling a C function
        puts(p)
    }

    //  No semicolon `;` here, so the value returned by the C function will be passed to our caller
}

/// Convert milliseconds to system ticks.
/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration:
/// `ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms)`
pub fn time_ms_to_ticks32(
    ms: u32  //  Number of milliseconds
) -> u32 {   //  Returns the number of ticks (uint32_t)
    extern "C" {        //  Import C Function
        /// Convert milliseconds to system ticks (from NimBLE Porting Layer)
        fn ble_npl_time_ms_to_ticks32(ms: u32) -> u32;
    }

    //  Call the C function
    unsafe {  //  Flag this code as unsafe because we're calling a C function
        ble_npl_time_ms_to_ticks32(ms)
    }

    //  No semicolon `;` here, so the value returned by the C function will be passed to our caller
}

/// Sleep for the specified number of system ticks.
/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration:
/// `void ble_npl_time_delay(ble_npl_time_t ticks)`
pub fn time_delay(
    ticks: u32  //  Number of ticks to sleep
) {
    extern "C" {  //  Import C Function
        /// Sleep for the specified number of system ticks (from NimBLE Porting Layer)
        fn ble_npl_time_delay(ticks: u32);
    }

    //  Call the C function
    unsafe {  //  Flag this code as unsafe because we're calling a C function
        ble_npl_time_delay(ticks);
    }
}

/// Limit Strings to 64 chars, similar to `char[64]` in C
pub type String = heapless::String::<64>;

///////////////////////////////////////////////////////////////////////////////
//  Wrapper Types

/// HAL return type and error codes
pub mod result {

    /// Common return type for BL602 HAL.  If no error, returns `Ok(val)` where val has type T.
    /// Upon error, returns `Err(err)` where err is the BlError error code.
    pub type BlResult<T> = ::core::result::Result<T, BlError>;

    /// Error codes for BL602 HAL
    #[repr(i32)]
    #[derive(PartialEq)]
    #[allow(non_camel_case_types)]    //  Allow type names to have non-camel case
    pub enum BlError {
        /// Error code 0 means no error
        SYS_EOK         = 0,
        /// HAL returned an unknown error code
        SYS_UNKNOWN     = -1,
        /// HAL returned a null pointer
        SYS_NULLPOINTER = -2,
    }

    /// Cast `BlError` to `i32`
    impl From<BlError> for i32 {
        /// Cast `BlError` to `i32`
        fn from(err: BlError) -> Self {
            err as i32
        }
    }

    /// Cast `i32` to `BlError`
    impl From<i32> for BlError {
        /// Cast `i32` to `BlError`
        fn from(num: i32) -> Self {
            unsafe { 
                ::core::mem::transmute::
                    <i32, BlError>
                    (num)
            }  
        }
    }

    /// Cast `()` to `BlError`
    impl From<()> for BlError {
        /// Cast `()` to `BlError`
        fn from(_: ()) -> Self {
            BlError::SYS_UNKNOWN
        }
    }

    /// Implement formatted output for BlError
    impl core::fmt::Debug for BlError {
        fn fmt(&self, _fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
            //  TODO
            Ok(())
        }
    }
}

/// Represents a null-terminated string, suitable for passing to C APIs as `* const char`.
/// The string can be a null-terminated byte string created in Rust, or a pointer to a null-terminated string returned by C.
/// Pointer may be null.
#[derive(Clone, Copy)]  //  Strn may be copied
pub struct Strn<'a> {
    /// Either a byte string terminated with null, or a pointer to a null-terminated string
    pub rep: StrnRep<'a>
}

/// Either a byte string or a string pointer
#[derive(Clone, Copy)]  //  StrnRep may be copied
#[repr(u8)]
pub enum StrnRep<'a> {
    /// Byte string terminated with null
    ByteStr(&'a [u8]),
    /// Pointer to a null-terminated string
    CStr(*const u8),
}

impl<'a> Strn<'a> {
    /// Create a new `Strn` with a byte string. Fail if the last byte is not zero.
    /// ```
    /// Strn::new(b"network\0")
    /// strn!("network")
    /// ```
    pub fn new(bs: &[u8]) -> Strn {
        assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
        Strn { 
            rep: StrnRep::ByteStr(bs)
        }
    }

    /// Create a new `Strn` with a null-terminated string pointer returned by C.
    pub fn from_cstr(cstr: *const u8) -> Strn<'a> {
        Strn { 
            rep: StrnRep::CStr(cstr)
        }
    }

    /// Return a pointer to the string
    pub fn as_ptr(&self) -> *const u8 {
        match self.rep {
            StrnRep::ByteStr(bs) => { bs.as_ptr() }
            StrnRep::CStr(cstr)  => { cstr }
        }
    }

    /// Return the length of the string, excluding the terminating null. For safety, we limit to 128.
    pub fn len(&self) -> usize {
        match self.rep {
            StrnRep::ByteStr(bs) => { 
                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
                bs.len() - 1  //  Don't count the terminating null.
            }
            StrnRep::CStr(cstr)  => { 
                //  Look for the null termination.
                if cstr.is_null() { return 0; }
                for len in 0..127 {
                    let ptr: *const u8 =  ((cstr as u32) + len) as *const u8;
                    if unsafe { *ptr } == 0 { return len as usize; }                    
                }
                assert!(false, "big strn");  //  String too long
                return 128 as usize;
            }
        }
    }

    /// Return true if the string is empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Return the byte string as a null-terminated `* const char` C-style string.
    /// Fail if the last byte is not zero.
    pub fn as_cstr(&self) -> *const u8 {
        match self.rep {
            StrnRep::ByteStr(bs) => { 
                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
                bs.as_ptr() as *const u8
            }
            StrnRep::CStr(cstr)  => { cstr }
        }
    }

    /// Return the byte string.
    /// Fail if the last byte is not zero.
    pub fn as_bytestr(&self) -> &'a [u8] {
        match self.rep {
            StrnRep::ByteStr(bs) => {                
                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
                &bs
            }
            StrnRep::CStr(_cstr)  => { 
                assert!(false, "strn cstr");  //  Not implemented
                b"\0"
            }
        }
    }

    /// Fail if the last byte is not zero.
    pub fn validate(&self) {
        match self.rep {
            StrnRep::ByteStr(bs) => {         
                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
            }
            StrnRep::CStr(_cstr)  => {}
        }
    }

    /// Fail if the last byte is not zero.
    pub fn validate_bytestr(bs: &'static [u8]) {
        assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
    }
}

///  Allow threads to share Strn, since it is static.
unsafe impl<'a> Send for Strn<'a> {}

///  Allow threads to share Strn, since it is static.
unsafe impl<'a> Sync for Strn<'a> {}

///  Declare a `void *` pointer that will be passed to C functions
pub type Ptr = *mut ::cty::c_void;

///////////////////////////////////////////////////////////////////////////////
//  Test Functions

/// For Testing: This function will be called by the BL602 command-line interface
#[no_mangle]              //  Don't mangle the function name
extern "C" fn test_rust(  //  Declare `extern "C"` because it will be called by BL602 firmware
    _result: *mut u8,        //  Result to be returned to command-line interface (char *)
    _len:  i32,              //  Length of command line (int)
    _argc: i32,              //  Number of command line args (int)
    _argv: *const *const u8  //  Array of command line args (char **)
) {
    //  Show a message on the serial console
    puts("Hello from Rust!");

    //  PineCone Blue LED is connected on BL602 GPIO 11
    const LED_GPIO: u8 = 11;  //  `u8` is 8-bit unsigned integer

    //  Configure the LED GPIO for output (instead of input)
    gpio::enable_output(LED_GPIO, 0, 0)   //  No pullup, no pulldown
        .expect("GPIO enable output failed");  //  Halt on error

    //  Blink the LED 5 times
    for i in 0..10 {  //  Iterates 10 times from 0 to 9 (`..` excludes 10)

        //  Toggle the LED GPIO between 0 (on) and 1 (off)
        gpio::output_set(  //  Set the GPIO output (from BL602 GPIO HAL)
            LED_GPIO,           //  GPIO pin number
            i % 2               //  0 for low, 1 for high
        ).expect("GPIO output failed");  //  Halt on error

        //  Sleep 1 second
        time_delay(                   //  Sleep by number of ticks (from NimBLE Porting Layer)
            time_ms_to_ticks32(1000)  //  Convert 1,000 milliseconds to ticks (from NimBLE Porting Layer)
        );
    }

    //  Return to the BL602 command-line interface
}