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}