bl602_sdk/
lib.rs

1//!  Rust Wrapper for BL602 IoT SDK. See "The RISC-V BL602 Book" <https://lupyuen.github.io/articles/book>
2#![no_std]  //  Use the Rust Core Library instead of the Rust Standard Library, which is not compatible with embedded systems
3
4use bl602_macros::safe_wrap;
5use result::*;
6
7///////////////////////////////////////////////////////////////////////////////
8//  Wrapper for BL602 HAL generated by `bindgen` and `safe_wrap`
9
10/// ADC HAL for BL602. See <https://lupyuen.github.io/articles/book#adc-on-bl602>
11#[allow(non_camel_case_types)]
12#[allow(non_snake_case)]
13pub mod adc;
14
15/// DMA HAL for BL602. See <https://lupyuen.github.io/articles/book#dma-on-bl602>
16#[allow(non_camel_case_types)]
17#[allow(non_snake_case)]
18pub mod dma;
19
20/// GPIO HAL for BL602. See <https://lupyuen.github.io/articles/book#gpio-on-bl602>
21#[allow(non_camel_case_types)]
22#[allow(non_snake_case)]
23pub mod gpio;
24
25/// I2C HAL for BL602. See <https://lupyuen.github.io/articles/book#i2c-on-bl602>
26#[allow(non_camel_case_types)]
27#[allow(non_snake_case)]
28pub mod i2c;
29
30/// PWM HAL for BL602. See <https://lupyuen.github.io/articles/book#pwm-on-bl602>
31#[allow(non_camel_case_types)]
32#[allow(non_snake_case)]
33#[allow(non_upper_case_globals)]
34pub mod pwm;
35
36/// SPI HAL for BL602. See <https://lupyuen.github.io/articles/book#spi-on-bl602>
37#[allow(non_camel_case_types)]
38#[allow(non_snake_case)]
39pub mod spi;
40
41/// UART HAL for BL602. See <https://lupyuen.github.io/articles/book#uart-on-bl602>
42#[allow(non_camel_case_types)]
43#[allow(non_snake_case)]
44#[allow(non_upper_case_globals)]
45pub mod uart;
46
47/// WiFi HAL for BL602. See <https://lupyuen.github.io/articles/book#wifi-on-bl602>
48#[allow(non_camel_case_types)]
49#[allow(non_snake_case)]
50#[allow(non_upper_case_globals)]
51pub mod wifi;
52
53//  Import the Rust Core Library
54use core::{
55    str::FromStr,      //  For converting `str` to `String`
56};
57
58///////////////////////////////////////////////////////////////////////////////
59//  Wrapper for NimBLE Porting Layer
60//  TODO: Generate with `bindgen` and `safe_wrap`
61
62/// Print a message to the serial console.
63/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration
64pub fn puts(s: &str) -> i32 {  //  `&str` is a reference to a string slice, similar to `const char *` in C
65
66    extern "C" {  //  Import C Function
67        /// Print a message to the serial console (from C stdio library)
68        fn puts(s: *const u8) -> i32;
69    }
70
71    //  Convert `str` to `String`, which similar to `char [64]` in C
72    let mut s_with_null = String::from_str(s)  //  `mut` because we will modify it
73        .expect("puts conversion failed");     //  If it exceeds 64 chars, halt with an error
74    
75    //  Terminate the string with null, since we will be passing to C
76    s_with_null.push('\0')
77        .expect("puts overflow");  //  If we exceed 64 chars, halt with an error
78
79    //  Convert the null-terminated string to a pointer
80    let p = s_with_null.as_str().as_ptr();
81
82    //  Call the C function
83    unsafe {  //  Flag this code as unsafe because we're calling a C function
84        puts(p)
85    }
86
87    //  No semicolon `;` here, so the value returned by the C function will be passed to our caller
88}
89
90/// Convert milliseconds to system ticks.
91/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration:
92/// `ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms)`
93pub fn time_ms_to_ticks32(
94    ms: u32  //  Number of milliseconds
95) -> u32 {   //  Returns the number of ticks (uint32_t)
96    extern "C" {        //  Import C Function
97        /// Convert milliseconds to system ticks (from NimBLE Porting Layer)
98        fn ble_npl_time_ms_to_ticks32(ms: u32) -> u32;
99    }
100
101    //  Call the C function
102    unsafe {  //  Flag this code as unsafe because we're calling a C function
103        ble_npl_time_ms_to_ticks32(ms)
104    }
105
106    //  No semicolon `;` here, so the value returned by the C function will be passed to our caller
107}
108
109/// Sleep for the specified number of system ticks.
110/// TODO: Auto-generate this wrapper with `bindgen` from the C declaration:
111/// `void ble_npl_time_delay(ble_npl_time_t ticks)`
112pub fn time_delay(
113    ticks: u32  //  Number of ticks to sleep
114) {
115    extern "C" {  //  Import C Function
116        /// Sleep for the specified number of system ticks (from NimBLE Porting Layer)
117        fn ble_npl_time_delay(ticks: u32);
118    }
119
120    //  Call the C function
121    unsafe {  //  Flag this code as unsafe because we're calling a C function
122        ble_npl_time_delay(ticks);
123    }
124}
125
126/// Limit Strings to 64 chars, similar to `char[64]` in C
127pub type String = heapless::String::<64>;
128
129///////////////////////////////////////////////////////////////////////////////
130//  Wrapper Types
131
132/// HAL return type and error codes
133pub mod result {
134
135    /// Common return type for BL602 HAL.  If no error, returns `Ok(val)` where val has type T.
136    /// Upon error, returns `Err(err)` where err is the BlError error code.
137    pub type BlResult<T> = ::core::result::Result<T, BlError>;
138
139    /// Error codes for BL602 HAL
140    #[repr(i32)]
141    #[derive(PartialEq)]
142    #[allow(non_camel_case_types)]    //  Allow type names to have non-camel case
143    pub enum BlError {
144        /// Error code 0 means no error
145        SYS_EOK         = 0,
146        /// HAL returned an unknown error code
147        SYS_UNKNOWN     = -1,
148        /// HAL returned a null pointer
149        SYS_NULLPOINTER = -2,
150    }
151
152    /// Cast `BlError` to `i32`
153    impl From<BlError> for i32 {
154        /// Cast `BlError` to `i32`
155        fn from(err: BlError) -> Self {
156            err as i32
157        }
158    }
159
160    /// Cast `i32` to `BlError`
161    impl From<i32> for BlError {
162        /// Cast `i32` to `BlError`
163        fn from(num: i32) -> Self {
164            unsafe { 
165                ::core::mem::transmute::
166                    <i32, BlError>
167                    (num)
168            }  
169        }
170    }
171
172    /// Cast `()` to `BlError`
173    impl From<()> for BlError {
174        /// Cast `()` to `BlError`
175        fn from(_: ()) -> Self {
176            BlError::SYS_UNKNOWN
177        }
178    }
179
180    /// Implement formatted output for BlError
181    impl core::fmt::Debug for BlError {
182        fn fmt(&self, _fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
183            //  TODO
184            Ok(())
185        }
186    }
187}
188
189/// Represents a null-terminated string, suitable for passing to C APIs as `* const char`.
190/// The string can be a null-terminated byte string created in Rust, or a pointer to a null-terminated string returned by C.
191/// Pointer may be null.
192#[derive(Clone, Copy)]  //  Strn may be copied
193pub struct Strn<'a> {
194    /// Either a byte string terminated with null, or a pointer to a null-terminated string
195    pub rep: StrnRep<'a>
196}
197
198/// Either a byte string or a string pointer
199#[derive(Clone, Copy)]  //  StrnRep may be copied
200#[repr(u8)]
201pub enum StrnRep<'a> {
202    /// Byte string terminated with null
203    ByteStr(&'a [u8]),
204    /// Pointer to a null-terminated string
205    CStr(*const u8),
206}
207
208impl<'a> Strn<'a> {
209    /// Create a new `Strn` with a byte string. Fail if the last byte is not zero.
210    /// ```
211    /// Strn::new(b"network\0")
212    /// strn!("network")
213    /// ```
214    pub fn new(bs: &[u8]) -> Strn {
215        assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
216        Strn { 
217            rep: StrnRep::ByteStr(bs)
218        }
219    }
220
221    /// Create a new `Strn` with a null-terminated string pointer returned by C.
222    pub fn from_cstr(cstr: *const u8) -> Strn<'a> {
223        Strn { 
224            rep: StrnRep::CStr(cstr)
225        }
226    }
227
228    /// Return a pointer to the string
229    pub fn as_ptr(&self) -> *const u8 {
230        match self.rep {
231            StrnRep::ByteStr(bs) => { bs.as_ptr() }
232            StrnRep::CStr(cstr)  => { cstr }
233        }
234    }
235
236    /// Return the length of the string, excluding the terminating null. For safety, we limit to 128.
237    pub fn len(&self) -> usize {
238        match self.rep {
239            StrnRep::ByteStr(bs) => { 
240                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
241                bs.len() - 1  //  Don't count the terminating null.
242            }
243            StrnRep::CStr(cstr)  => { 
244                //  Look for the null termination.
245                if cstr.is_null() { return 0; }
246                for len in 0..127 {
247                    let ptr: *const u8 =  ((cstr as u32) + len) as *const u8;
248                    if unsafe { *ptr } == 0 { return len as usize; }                    
249                }
250                assert!(false, "big strn");  //  String too long
251                return 128 as usize;
252            }
253        }
254    }
255
256    /// Return true if the string is empty
257    pub fn is_empty(&self) -> bool {
258        self.len() == 0
259    }
260
261    /// Return the byte string as a null-terminated `* const char` C-style string.
262    /// Fail if the last byte is not zero.
263    pub fn as_cstr(&self) -> *const u8 {
264        match self.rep {
265            StrnRep::ByteStr(bs) => { 
266                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
267                bs.as_ptr() as *const u8
268            }
269            StrnRep::CStr(cstr)  => { cstr }
270        }
271    }
272
273    /// Return the byte string.
274    /// Fail if the last byte is not zero.
275    pub fn as_bytestr(&self) -> &'a [u8] {
276        match self.rep {
277            StrnRep::ByteStr(bs) => {                
278                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
279                &bs
280            }
281            StrnRep::CStr(_cstr)  => { 
282                assert!(false, "strn cstr");  //  Not implemented
283                b"\0"
284            }
285        }
286    }
287
288    /// Fail if the last byte is not zero.
289    pub fn validate(&self) {
290        match self.rep {
291            StrnRep::ByteStr(bs) => {         
292                assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
293            }
294            StrnRep::CStr(_cstr)  => {}
295        }
296    }
297
298    /// Fail if the last byte is not zero.
299    pub fn validate_bytestr(bs: &'static [u8]) {
300        assert_eq!(bs.last(), Some(&0u8), "no null");  //  Last byte must be 0.
301    }
302}
303
304///  Allow threads to share Strn, since it is static.
305unsafe impl<'a> Send for Strn<'a> {}
306
307///  Allow threads to share Strn, since it is static.
308unsafe impl<'a> Sync for Strn<'a> {}
309
310///  Declare a `void *` pointer that will be passed to C functions
311pub type Ptr = *mut ::cty::c_void;
312
313///////////////////////////////////////////////////////////////////////////////
314//  Test Functions
315
316/// For Testing: This function will be called by the BL602 command-line interface
317#[no_mangle]              //  Don't mangle the function name
318extern "C" fn test_rust(  //  Declare `extern "C"` because it will be called by BL602 firmware
319    _result: *mut u8,        //  Result to be returned to command-line interface (char *)
320    _len:  i32,              //  Length of command line (int)
321    _argc: i32,              //  Number of command line args (int)
322    _argv: *const *const u8  //  Array of command line args (char **)
323) {
324    //  Show a message on the serial console
325    puts("Hello from Rust!");
326
327    //  PineCone Blue LED is connected on BL602 GPIO 11
328    const LED_GPIO: u8 = 11;  //  `u8` is 8-bit unsigned integer
329
330    //  Configure the LED GPIO for output (instead of input)
331    gpio::enable_output(LED_GPIO, 0, 0)   //  No pullup, no pulldown
332        .expect("GPIO enable output failed");  //  Halt on error
333
334    //  Blink the LED 5 times
335    for i in 0..10 {  //  Iterates 10 times from 0 to 9 (`..` excludes 10)
336
337        //  Toggle the LED GPIO between 0 (on) and 1 (off)
338        gpio::output_set(  //  Set the GPIO output (from BL602 GPIO HAL)
339            LED_GPIO,           //  GPIO pin number
340            i % 2               //  0 for low, 1 for high
341        ).expect("GPIO output failed");  //  Halt on error
342
343        //  Sleep 1 second
344        time_delay(                   //  Sleep by number of ticks (from NimBLE Porting Layer)
345            time_ms_to_ticks32(1000)  //  Convert 1,000 milliseconds to ticks (from NimBLE Porting Layer)
346        );
347    }
348
349    //  Return to the BL602 command-line interface
350}