aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/build.rs9
-rw-r--r--embassy-stm32/src/cordic/enums.rs71
-rw-r--r--embassy-stm32/src/cordic/errors.rs144
-rw-r--r--embassy-stm32/src/cordic/mod.rs617
-rw-r--r--embassy-stm32/src/cordic/sealed.rs116
-rw-r--r--embassy-stm32/src/cordic/utils.rs62
-rw-r--r--embassy-stm32/src/lib.rs5
-rw-r--r--examples/stm32h5/src/bin/cordic.rs78
-rw-r--r--tests/stm32/Cargo.toml15
-rw-r--r--tests/stm32/gen_test.py2
-rw-r--r--tests/stm32/src/bin/cordic.rs135
11 files changed, 1247 insertions, 7 deletions
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index 15bb8ea62..057c4cee2 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -484,7 +484,7 @@ fn main() {
484 let expr = if let Some(mux) = self.chained_muxes.get(&v.name) { 484 let expr = if let Some(mux) = self.chained_muxes.get(&v.name) {
485 self.gen_mux(mux) 485 self.gen_mux(mux)
486 } else { 486 } else {
487 self.gen_clock(&v.name) 487 self.gen_clock(v.name)
488 }; 488 };
489 match_arms.extend(quote! { 489 match_arms.extend(quote! {
490 crate::pac::rcc::vals::#enum_name::#variant_name => #expr, 490 crate::pac::rcc::vals::#enum_name::#variant_name => #expr,
@@ -1139,11 +1139,18 @@ fn main() {
1139 (("timer", "CH2"), quote!(crate::timer::Ch2Dma)), 1139 (("timer", "CH2"), quote!(crate::timer::Ch2Dma)),
1140 (("timer", "CH3"), quote!(crate::timer::Ch3Dma)), 1140 (("timer", "CH3"), quote!(crate::timer::Ch3Dma)),
1141 (("timer", "CH4"), quote!(crate::timer::Ch4Dma)), 1141 (("timer", "CH4"), quote!(crate::timer::Ch4Dma)),
1142 (("cordic", "WRITE"), quote!(crate::cordic::WriteDma)), // FIXME: stm32u5a crash on Cordic driver
1143 (("cordic", "READ"), quote!(crate::cordic::ReadDma)), // FIXME: stm32u5a crash on Cordic driver
1142 ] 1144 ]
1143 .into(); 1145 .into();
1144 1146
1145 for p in METADATA.peripherals { 1147 for p in METADATA.peripherals {
1146 if let Some(regs) = &p.registers { 1148 if let Some(regs) = &p.registers {
1149 // FIXME: stm32u5a crash on Cordic driver
1150 if chip_name.starts_with("stm32u5a") && regs.kind == "cordic" {
1151 continue;
1152 }
1153
1147 let mut dupe = HashSet::new(); 1154 let mut dupe = HashSet::new();
1148 for ch in p.dma_channels { 1155 for ch in p.dma_channels {
1149 // Some chips have multiple request numbers for the same (peri, signal, channel) combos. 1156 // Some chips have multiple request numbers for the same (peri, signal, channel) combos.
diff --git a/embassy-stm32/src/cordic/enums.rs b/embassy-stm32/src/cordic/enums.rs
new file mode 100644
index 000000000..e8695fac7
--- /dev/null
+++ b/embassy-stm32/src/cordic/enums.rs
@@ -0,0 +1,71 @@
1/// CORDIC function
2#[allow(missing_docs)]
3#[derive(Debug, Clone, Copy)]
4#[cfg_attr(feature = "defmt", derive(defmt::Format))]
5pub enum Function {
6 Cos = 0,
7 Sin,
8 Phase,
9 Modulus,
10 Arctan,
11 Cosh,
12 Sinh,
13 Arctanh,
14 Ln,
15 Sqrt,
16}
17
18/// CORDIC precision
19#[allow(missing_docs)]
20#[derive(Debug, Clone, Copy, Default)]
21pub enum Precision {
22 Iters4 = 1,
23 Iters8,
24 Iters12,
25 Iters16,
26 Iters20,
27 #[default]
28 Iters24, // this value is recommended by Reference Manual
29 Iters28,
30 Iters32,
31 Iters36,
32 Iters40,
33 Iters44,
34 Iters48,
35 Iters52,
36 Iters56,
37 Iters60,
38}
39
40/// CORDIC scale
41#[allow(missing_docs)]
42#[derive(Debug, Clone, Copy, Default, PartialEq)]
43#[cfg_attr(feature = "defmt", derive(defmt::Format))]
44pub enum Scale {
45 #[default]
46 Arg1Res1 = 0,
47 Arg1o2Res2,
48 Arg1o4Res4,
49 Arg1o8Res8,
50 Arg1o16Res16,
51 Arg1o32Res32,
52 Arg1o64Res64,
53 Arg1o128Res128,
54}
55
56/// CORDIC argument/result register access count
57#[allow(missing_docs)]
58#[derive(Clone, Copy, Default)]
59pub enum AccessCount {
60 #[default]
61 One,
62 Two,
63}
64
65/// CORDIC argument/result data width
66#[allow(missing_docs)]
67#[derive(Clone, Copy)]
68pub enum Width {
69 Bits32,
70 Bits16,
71}
diff --git a/embassy-stm32/src/cordic/errors.rs b/embassy-stm32/src/cordic/errors.rs
new file mode 100644
index 000000000..3c70fc9e7
--- /dev/null
+++ b/embassy-stm32/src/cordic/errors.rs
@@ -0,0 +1,144 @@
1use super::{Function, Scale};
2
3/// Error for [Cordic](super::Cordic)
4#[derive(Debug)]
5pub enum CordicError {
6 /// Config error
7 ConfigError(ConfigError),
8 /// Argument length is incorrect
9 ArgumentLengthIncorrect,
10 /// Result buffer length error
11 ResultLengthNotEnough,
12 /// Input value is out of range for Q1.x format
13 NumberOutOfRange(NumberOutOfRange),
14 /// Argument error
15 ArgError(ArgError),
16}
17
18impl From<ConfigError> for CordicError {
19 fn from(value: ConfigError) -> Self {
20 Self::ConfigError(value)
21 }
22}
23
24impl From<NumberOutOfRange> for CordicError {
25 fn from(value: NumberOutOfRange) -> Self {
26 Self::NumberOutOfRange(value)
27 }
28}
29
30impl From<ArgError> for CordicError {
31 fn from(value: ArgError) -> Self {
32 Self::ArgError(value)
33 }
34}
35
36#[cfg(feature = "defmt")]
37impl defmt::Format for CordicError {
38 fn format(&self, fmt: defmt::Formatter) {
39 use CordicError::*;
40
41 match self {
42 ConfigError(e) => defmt::write!(fmt, "{}", e),
43 ResultLengthNotEnough => defmt::write!(fmt, "Output buffer length is not long enough"),
44 ArgumentLengthIncorrect => defmt::write!(fmt, "Argument length incorrect"),
45 NumberOutOfRange(e) => defmt::write!(fmt, "{}", e),
46 ArgError(e) => defmt::write!(fmt, "{}", e),
47 }
48 }
49}
50
51/// Error during parsing [Cordic::Config](super::Config)
52#[allow(dead_code)]
53#[derive(Debug)]
54pub struct ConfigError {
55 pub(super) func: Function,
56 pub(super) scale_range: [u8; 2],
57}
58
59#[cfg(feature = "defmt")]
60impl defmt::Format for ConfigError {
61 fn format(&self, fmt: defmt::Formatter) {
62 defmt::write!(fmt, "For FUNCTION: {},", self.func);
63
64 if self.scale_range[0] == self.scale_range[1] {
65 defmt::write!(fmt, " SCALE value should be {}", self.scale_range[0])
66 } else {
67 defmt::write!(
68 fmt,
69 " SCALE value should be {} <= SCALE <= {}",
70 self.scale_range[0],
71 self.scale_range[1]
72 )
73 }
74 }
75}
76
77/// Input value is out of range for Q1.x format
78#[allow(missing_docs)]
79#[derive(Debug)]
80pub enum NumberOutOfRange {
81 BelowLowerBound,
82 AboveUpperBound,
83}
84
85#[cfg(feature = "defmt")]
86impl defmt::Format for NumberOutOfRange {
87 fn format(&self, fmt: defmt::Formatter) {
88 use NumberOutOfRange::*;
89
90 match self {
91 BelowLowerBound => defmt::write!(fmt, "input value should be equal or greater than -1"),
92 AboveUpperBound => defmt::write!(fmt, "input value should be equal or less than 1"),
93 }
94 }
95}
96
97/// Error on checking input arguments
98#[allow(dead_code)]
99#[derive(Debug)]
100pub struct ArgError {
101 pub(super) func: Function,
102 pub(super) scale: Option<Scale>,
103 pub(super) arg_range: [f32; 2], // only for debug display, f32 is ok
104 pub(super) inclusive_upper_bound: bool,
105 pub(super) arg_type: ArgType,
106}
107
108#[cfg(feature = "defmt")]
109impl defmt::Format for ArgError {
110 fn format(&self, fmt: defmt::Formatter) {
111 defmt::write!(fmt, "For FUNCTION: {},", self.func);
112
113 if let Some(scale) = self.scale {
114 defmt::write!(fmt, " when SCALE is {},", scale);
115 }
116
117 defmt::write!(fmt, " {} should be", self.arg_type);
118
119 if self.inclusive_upper_bound {
120 defmt::write!(
121 fmt,
122 " {} <= {} <= {}",
123 self.arg_range[0],
124 self.arg_type,
125 self.arg_range[1]
126 )
127 } else {
128 defmt::write!(
129 fmt,
130 " {} <= {} < {}",
131 self.arg_range[0],
132 self.arg_type,
133 self.arg_range[1]
134 )
135 };
136 }
137}
138
139#[derive(Debug)]
140#[cfg_attr(feature = "defmt", derive(defmt::Format))]
141pub(super) enum ArgType {
142 Arg1,
143 Arg2,
144}
diff --git a/embassy-stm32/src/cordic/mod.rs b/embassy-stm32/src/cordic/mod.rs
new file mode 100644
index 000000000..6bbc48f2b
--- /dev/null
+++ b/embassy-stm32/src/cordic/mod.rs
@@ -0,0 +1,617 @@
1//! coordinate rotation digital computer (CORDIC)
2
3use embassy_hal_internal::drop::OnDrop;
4use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
5
6use crate::{dma, peripherals};
7
8mod enums;
9pub use enums::*;
10
11mod errors;
12pub use errors::*;
13
14mod sealed;
15use self::sealed::SealedInstance;
16
17pub mod utils;
18
19/// CORDIC driver
20pub struct Cordic<'d, T: Instance> {
21 peri: PeripheralRef<'d, T>,
22 config: Config,
23}
24
25/// CORDIC instance trait
26#[allow(private_bounds)]
27pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral {}
28
29/// CORDIC configuration
30#[derive(Debug)]
31pub struct Config {
32 function: Function,
33 precision: Precision,
34 scale: Scale,
35}
36
37impl Config {
38 /// Create a config for Cordic driver
39 pub fn new(function: Function, precision: Precision, scale: Scale) -> Result<Self, CordicError> {
40 let config = Self {
41 function,
42 precision,
43 scale,
44 };
45
46 config.check_scale()?;
47
48 Ok(config)
49 }
50
51 fn check_scale(&self) -> Result<(), ConfigError> {
52 use Function::*;
53
54 let scale_raw = self.scale as u8;
55
56 let err_range = match self.function {
57 Cos | Sin | Phase | Modulus if !(0..=0).contains(&scale_raw) => Some([0, 0]),
58
59 Arctan if !(0..=7).contains(&scale_raw) => Some([0, 7]),
60
61 Cosh | Sinh | Arctanh if !(1..=1).contains(&scale_raw) => Some([1, 1]),
62
63 Ln if !(1..=4).contains(&scale_raw) => Some([1, 4]),
64
65 Sqrt if !(0..=2).contains(&scale_raw) => Some([0, 2]),
66
67 Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh | Ln | Sqrt => None,
68 };
69
70 if let Some(range) = err_range {
71 Err(ConfigError {
72 func: self.function,
73 scale_range: range,
74 })
75 } else {
76 Ok(())
77 }
78 }
79}
80
81// common method
82impl<'d, T: Instance> Cordic<'d, T> {
83 /// Create a Cordic driver instance
84 ///
85 /// Note:
86 /// If you need a peripheral -> CORDIC -> peripheral mode,
87 /// you may want to set Cordic into [Mode::ZeroOverhead] mode, and add extra arguments with [Self::extra_config]
88 pub fn new(peri: impl Peripheral<P = T> + 'd, config: Config) -> Self {
89 T::enable_and_reset();
90
91 into_ref!(peri);
92
93 let mut instance = Self { peri, config };
94
95 instance.reconfigure();
96
97 instance
98 }
99
100 /// Set a new config for Cordic driver
101 pub fn set_config(&mut self, config: Config) {
102 self.config = config;
103 self.reconfigure();
104 }
105
106 /// Set extra config for data count and data width.
107 pub fn extra_config(&mut self, arg_cnt: AccessCount, arg_width: Width, res_width: Width) {
108 self.peri.set_argument_count(arg_cnt);
109 self.peri.set_data_width(arg_width, res_width);
110 }
111
112 fn clean_rrdy_flag(&mut self) {
113 while self.peri.ready_to_read() {
114 self.peri.read_result();
115 }
116 }
117
118 /// Disable IRQ and DMA, clean RRDY, and set ARG2 to +1 (0x7FFFFFFF)
119 pub fn reconfigure(&mut self) {
120 // reset ARG2 to +1
121 {
122 self.peri.disable_irq();
123 self.peri.disable_read_dma();
124 self.peri.disable_write_dma();
125 self.clean_rrdy_flag();
126
127 self.peri.set_func(Function::Cos);
128 self.peri.set_precision(Precision::Iters4);
129 self.peri.set_scale(Scale::Arg1Res1);
130 self.peri.set_argument_count(AccessCount::Two);
131 self.peri.set_data_width(Width::Bits32, Width::Bits32);
132 self.peri.write_argument(0x0u32);
133 self.peri.write_argument(0x7FFFFFFFu32);
134
135 self.clean_rrdy_flag();
136 }
137
138 self.peri.set_func(self.config.function);
139 self.peri.set_precision(self.config.precision);
140 self.peri.set_scale(self.config.scale);
141
142 // we don't set NRES in here, but to make sure NRES is set each time user call "calc"-ish functions,
143 // since each "calc"-ish functions can have different ARGSIZE and RESSIZE, thus NRES should be change accordingly.
144 }
145}
146
147impl<'d, T: Instance> Drop for Cordic<'d, T> {
148 fn drop(&mut self) {
149 T::disable();
150 }
151}
152
153// q1.31 related
154impl<'d, T: Instance> Cordic<'d, T> {
155 /// Run a blocking CORDIC calculation in q1.31 format
156 ///
157 /// Notice:
158 /// If you set `arg1_only` to `true`, please be sure ARG2 value has been set to desired value before.
159 /// This function won't set ARG2 to +1 before or after each round of calculation.
160 /// If you want to make sure ARG2 is set to +1, consider run [.reconfigure()](Self::reconfigure).
161 pub fn blocking_calc_32bit(
162 &mut self,
163 arg: &[u32],
164 res: &mut [u32],
165 arg1_only: bool,
166 res1_only: bool,
167 ) -> Result<usize, CordicError> {
168 if arg.is_empty() {
169 return Ok(0);
170 }
171
172 let res_cnt = Self::check_arg_res_length_32bit(arg.len(), res.len(), arg1_only, res1_only)?;
173
174 self.peri
175 .set_argument_count(if arg1_only { AccessCount::One } else { AccessCount::Two });
176
177 self.peri
178 .set_result_count(if res1_only { AccessCount::One } else { AccessCount::Two });
179
180 self.peri.set_data_width(Width::Bits32, Width::Bits32);
181
182 let mut cnt = 0;
183
184 match arg1_only {
185 true => {
186 // To use cordic preload function, the first value is special.
187 // It is loaded to CORDIC WDATA register out side of loop
188 let first_value = arg[0];
189
190 // preload 1st value to CORDIC, to start the CORDIC calc
191 self.peri.write_argument(first_value);
192
193 for &arg1 in &arg[1..] {
194 // preload arg1 (for next calc)
195 self.peri.write_argument(arg1);
196
197 // then read current result out
198 res[cnt] = self.peri.read_result();
199 cnt += 1;
200 if !res1_only {
201 res[cnt] = self.peri.read_result();
202 cnt += 1;
203 }
204 }
205
206 // read the last result
207 res[cnt] = self.peri.read_result();
208 cnt += 1;
209 if !res1_only {
210 res[cnt] = self.peri.read_result();
211 // cnt += 1;
212 }
213 }
214 false => {
215 // To use cordic preload function, the first and last value is special.
216 // They are load to CORDIC WDATA register out side of loop
217 let first_value = arg[0];
218 let last_value = arg[arg.len() - 1];
219
220 let paired_args = &arg[1..arg.len() - 1];
221
222 // preload 1st value to CORDIC
223 self.peri.write_argument(first_value);
224
225 for args in paired_args.chunks(2) {
226 let arg2 = args[0];
227 let arg1 = args[1];
228
229 // load arg2 (for current calc) first, to start the CORDIC calc
230 self.peri.write_argument(arg2);
231
232 // preload arg1 (for next calc)
233 self.peri.write_argument(arg1);
234
235 // then read current result out
236 res[cnt] = self.peri.read_result();
237 cnt += 1;
238 if !res1_only {
239 res[cnt] = self.peri.read_result();
240 cnt += 1;
241 }
242 }
243
244 // load last value to CORDIC, and finish the calculation
245 self.peri.write_argument(last_value);
246 res[cnt] = self.peri.read_result();
247 cnt += 1;
248 if !res1_only {
249 res[cnt] = self.peri.read_result();
250 // cnt += 1;
251 }
252 }
253 }
254
255 // at this point cnt should be equal to res_cnt
256
257 Ok(res_cnt)
258 }
259
260 /// Run a async CORDIC calculation in q.1.31 format
261 ///
262 /// Notice:
263 /// If you set `arg1_only` to `true`, please be sure ARG2 value has been set to desired value before.
264 /// This function won't set ARG2 to +1 before or after each round of calculation.
265 /// If you want to make sure ARG2 is set to +1, consider run [.reconfigure()](Self::reconfigure).
266 pub async fn async_calc_32bit(
267 &mut self,
268 write_dma: impl Peripheral<P = impl WriteDma<T>>,
269 read_dma: impl Peripheral<P = impl ReadDma<T>>,
270 arg: &[u32],
271 res: &mut [u32],
272 arg1_only: bool,
273 res1_only: bool,
274 ) -> Result<usize, CordicError> {
275 if arg.is_empty() {
276 return Ok(0);
277 }
278
279 let res_cnt = Self::check_arg_res_length_32bit(arg.len(), res.len(), arg1_only, res1_only)?;
280
281 let active_res_buf = &mut res[..res_cnt];
282
283 into_ref!(write_dma, read_dma);
284
285 self.peri
286 .set_argument_count(if arg1_only { AccessCount::One } else { AccessCount::Two });
287
288 self.peri
289 .set_result_count(if res1_only { AccessCount::One } else { AccessCount::Two });
290
291 self.peri.set_data_width(Width::Bits32, Width::Bits32);
292
293 let write_req = write_dma.request();
294 let read_req = read_dma.request();
295
296 self.peri.enable_write_dma();
297 self.peri.enable_read_dma();
298
299 let _on_drop = OnDrop::new(|| {
300 self.peri.disable_write_dma();
301 self.peri.disable_read_dma();
302 });
303
304 unsafe {
305 let write_transfer = dma::Transfer::new_write(
306 &mut write_dma,
307 write_req,
308 arg,
309 T::regs().wdata().as_ptr() as *mut _,
310 Default::default(),
311 );
312
313 let read_transfer = dma::Transfer::new_read(
314 &mut read_dma,
315 read_req,
316 T::regs().rdata().as_ptr() as *mut _,
317 active_res_buf,
318 Default::default(),
319 );
320
321 embassy_futures::join::join(write_transfer, read_transfer).await;
322 }
323
324 Ok(res_cnt)
325 }
326
327 fn check_arg_res_length_32bit(
328 arg_len: usize,
329 res_len: usize,
330 arg1_only: bool,
331 res1_only: bool,
332 ) -> Result<usize, CordicError> {
333 if !arg1_only && arg_len % 2 != 0 {
334 return Err(CordicError::ArgumentLengthIncorrect);
335 }
336
337 let mut minimal_res_length = arg_len;
338
339 if !res1_only {
340 minimal_res_length *= 2;
341 }
342
343 if !arg1_only {
344 minimal_res_length /= 2
345 }
346
347 if minimal_res_length > res_len {
348 return Err(CordicError::ResultLengthNotEnough);
349 }
350
351 Ok(minimal_res_length)
352 }
353}
354
355// q1.15 related
356impl<'d, T: Instance> Cordic<'d, T> {
357 /// Run a blocking CORDIC calculation in q1.15 format
358 ///
359 /// Notice::
360 /// User will take respond to merge two u16 arguments into one u32 data, and/or split one u32 data into two u16 results.
361 pub fn blocking_calc_16bit(&mut self, arg: &[u32], res: &mut [u32]) -> Result<usize, CordicError> {
362 if arg.is_empty() {
363 return Ok(0);
364 }
365
366 if arg.len() > res.len() {
367 return Err(CordicError::ResultLengthNotEnough);
368 }
369
370 let res_cnt = arg.len();
371
372 // In q1.15 mode, 1 write/read to access 2 arguments/results
373 self.peri.set_argument_count(AccessCount::One);
374 self.peri.set_result_count(AccessCount::One);
375
376 self.peri.set_data_width(Width::Bits16, Width::Bits16);
377
378 // To use cordic preload function, the first value is special.
379 // It is loaded to CORDIC WDATA register out side of loop
380 let first_value = arg[0];
381
382 // preload 1st value to CORDIC, to start the CORDIC calc
383 self.peri.write_argument(first_value);
384
385 let mut cnt = 0;
386
387 for &arg_val in &arg[1..] {
388 // preload arg_val (for next calc)
389 self.peri.write_argument(arg_val);
390
391 // then read current result out
392 res[cnt] = self.peri.read_result();
393 cnt += 1;
394 }
395
396 // read last result out
397 res[cnt] = self.peri.read_result();
398 // cnt += 1;
399
400 Ok(res_cnt)
401 }
402
403 /// Run a async CORDIC calculation in q1.15 format
404 ///
405 /// Notice::
406 /// User will take respond to merge two u16 arguments into one u32 data, and/or split one u32 data into two u16 results.
407 pub async fn async_calc_16bit(
408 &mut self,
409 write_dma: impl Peripheral<P = impl WriteDma<T>>,
410 read_dma: impl Peripheral<P = impl ReadDma<T>>,
411 arg: &[u32],
412 res: &mut [u32],
413 ) -> Result<usize, CordicError> {
414 if arg.is_empty() {
415 return Ok(0);
416 }
417
418 if arg.len() > res.len() {
419 return Err(CordicError::ResultLengthNotEnough);
420 }
421
422 let res_cnt = arg.len();
423
424 let active_res_buf = &mut res[..res_cnt];
425
426 into_ref!(write_dma, read_dma);
427
428 // In q1.15 mode, 1 write/read to access 2 arguments/results
429 self.peri.set_argument_count(AccessCount::One);
430 self.peri.set_result_count(AccessCount::One);
431
432 self.peri.set_data_width(Width::Bits16, Width::Bits16);
433
434 let write_req = write_dma.request();
435 let read_req = read_dma.request();
436
437 self.peri.enable_write_dma();
438 self.peri.enable_read_dma();
439
440 let _on_drop = OnDrop::new(|| {
441 self.peri.disable_write_dma();
442 self.peri.disable_read_dma();
443 });
444
445 unsafe {
446 let write_transfer = dma::Transfer::new_write(
447 &mut write_dma,
448 write_req,
449 arg,
450 T::regs().wdata().as_ptr() as *mut _,
451 Default::default(),
452 );
453
454 let read_transfer = dma::Transfer::new_read(
455 &mut read_dma,
456 read_req,
457 T::regs().rdata().as_ptr() as *mut _,
458 active_res_buf,
459 Default::default(),
460 );
461
462 embassy_futures::join::join(write_transfer, read_transfer).await;
463 }
464
465 Ok(res_cnt)
466 }
467}
468
469macro_rules! check_arg_value {
470 ($func_arg1_name:ident, $func_arg2_name:ident, $float_type:ty) => {
471 impl<'d, T: Instance> Cordic<'d, T> {
472 /// check input value ARG1, SCALE and FUNCTION are compatible with each other
473 pub fn $func_arg1_name(&self, arg: $float_type) -> Result<(), ArgError> {
474 let config = &self.config;
475
476 use Function::*;
477
478 struct Arg1ErrInfo {
479 scale: Option<Scale>,
480 range: [f32; 2], // f32 is ok, it only used in error display
481 inclusive_upper_bound: bool,
482 }
483
484 let err_info = match config.function {
485 Cos | Sin | Phase | Modulus | Arctan if !(-1.0..=1.0).contains(arg) => Some(Arg1ErrInfo {
486 scale: None,
487 range: [-1.0, 1.0],
488 inclusive_upper_bound: true,
489 }),
490
491 Cosh | Sinh if !(-0.559..=0.559).contains(arg) => Some(Arg1ErrInfo {
492 scale: None,
493 range: [-0.559, 0.559],
494 inclusive_upper_bound: true,
495 }),
496
497 Arctanh if !(-0.403..=0.403).contains(arg) => Some(Arg1ErrInfo {
498 scale: None,
499 range: [-0.403, 0.403],
500 inclusive_upper_bound: true,
501 }),
502
503 Ln => match config.scale {
504 Scale::Arg1o2Res2 if !(0.0535..0.5).contains(arg) => Some(Arg1ErrInfo {
505 scale: Some(Scale::Arg1o2Res2),
506 range: [0.0535, 0.5],
507 inclusive_upper_bound: false,
508 }),
509 Scale::Arg1o4Res4 if !(0.25..0.75).contains(arg) => Some(Arg1ErrInfo {
510 scale: Some(Scale::Arg1o4Res4),
511 range: [0.25, 0.75],
512 inclusive_upper_bound: false,
513 }),
514 Scale::Arg1o8Res8 if !(0.375..0.875).contains(arg) => Some(Arg1ErrInfo {
515 scale: Some(Scale::Arg1o8Res8),
516 range: [0.375, 0.875],
517 inclusive_upper_bound: false,
518 }),
519 Scale::Arg1o16Res16 if !(0.4375..0.584).contains(arg) => Some(Arg1ErrInfo {
520 scale: Some(Scale::Arg1o16Res16),
521 range: [0.4375, 0.584],
522 inclusive_upper_bound: false,
523 }),
524
525 Scale::Arg1o2Res2 | Scale::Arg1o4Res4 | Scale::Arg1o8Res8 | Scale::Arg1o16Res16 => None,
526
527 _ => unreachable!(),
528 },
529
530 Sqrt => match config.scale {
531 Scale::Arg1Res1 if !(0.027..0.75).contains(arg) => Some(Arg1ErrInfo {
532 scale: Some(Scale::Arg1Res1),
533 range: [0.027, 0.75],
534 inclusive_upper_bound: false,
535 }),
536 Scale::Arg1o2Res2 if !(0.375..0.875).contains(arg) => Some(Arg1ErrInfo {
537 scale: Some(Scale::Arg1o2Res2),
538 range: [0.375, 0.875],
539 inclusive_upper_bound: false,
540 }),
541 Scale::Arg1o4Res4 if !(0.4375..0.584).contains(arg) => Some(Arg1ErrInfo {
542 scale: Some(Scale::Arg1o4Res4),
543 range: [0.4375, 0.584],
544 inclusive_upper_bound: false,
545 }),
546 Scale::Arg1Res1 | Scale::Arg1o2Res2 | Scale::Arg1o4Res4 => None,
547 _ => unreachable!(),
548 },
549
550 Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh => None,
551 };
552
553 if let Some(err) = err_info {
554 return Err(ArgError {
555 func: config.function,
556 scale: err.scale,
557 arg_range: err.range,
558 inclusive_upper_bound: err.inclusive_upper_bound,
559 arg_type: ArgType::Arg1,
560 });
561 }
562
563 Ok(())
564 }
565
566 /// check input value ARG2 and FUNCTION are compatible with each other
567 pub fn $func_arg2_name(&self, arg: $float_type) -> Result<(), ArgError> {
568 let config = &self.config;
569
570 use Function::*;
571
572 struct Arg2ErrInfo {
573 range: [f32; 2], // f32 is ok, it only used in error display
574 }
575
576 let err_info = match config.function {
577 Cos | Sin if !(0.0..=1.0).contains(arg) => Some(Arg2ErrInfo { range: [0.0, 1.0] }),
578
579 Phase | Modulus if !(-1.0..=1.0).contains(arg) => Some(Arg2ErrInfo { range: [-1.0, 1.0] }),
580
581 Cos | Sin | Phase | Modulus | Arctan | Cosh | Sinh | Arctanh | Ln | Sqrt => None,
582 };
583
584 if let Some(err) = err_info {
585 return Err(ArgError {
586 func: config.function,
587 scale: None,
588 arg_range: err.range,
589 inclusive_upper_bound: true,
590 arg_type: ArgType::Arg2,
591 });
592 }
593
594 Ok(())
595 }
596 }
597 };
598}
599
600check_arg_value!(check_f64_arg1, check_f64_arg2, &f64);
601check_arg_value!(check_f32_arg1, check_f32_arg2, &f32);
602
603foreach_interrupt!(
604 ($inst:ident, cordic, $block:ident, GLOBAL, $irq:ident) => {
605 impl Instance for peripherals::$inst {
606 }
607
608 impl SealedInstance for peripherals::$inst {
609 fn regs() -> crate::pac::cordic::Cordic {
610 crate::pac::$inst
611 }
612 }
613 };
614);
615
616dma_trait!(WriteDma, Instance);
617dma_trait!(ReadDma, Instance);
diff --git a/embassy-stm32/src/cordic/sealed.rs b/embassy-stm32/src/cordic/sealed.rs
new file mode 100644
index 000000000..8f0bd1830
--- /dev/null
+++ b/embassy-stm32/src/cordic/sealed.rs
@@ -0,0 +1,116 @@
1use super::*;
2use crate::pac::cordic::vals;
3
4/// Cordic instance
5pub(super) trait SealedInstance {
6 /// Get access to CORDIC registers
7 fn regs() -> crate::pac::cordic::Cordic;
8
9 /// Set Function value
10 fn set_func(&self, func: Function) {
11 Self::regs()
12 .csr()
13 .modify(|v| v.set_func(vals::Func::from_bits(func as u8)));
14 }
15
16 /// Set Precision value
17 fn set_precision(&self, precision: Precision) {
18 Self::regs()
19 .csr()
20 .modify(|v| v.set_precision(vals::Precision::from_bits(precision as u8)))
21 }
22
23 /// Set Scale value
24 fn set_scale(&self, scale: Scale) {
25 Self::regs()
26 .csr()
27 .modify(|v| v.set_scale(vals::Scale::from_bits(scale as u8)))
28 }
29
30 /// Enable global interrupt
31 fn enable_irq(&self) {
32 Self::regs().csr().modify(|v| v.set_ien(true))
33 }
34
35 /// Disable global interrupt
36 fn disable_irq(&self) {
37 Self::regs().csr().modify(|v| v.set_ien(false))
38 }
39
40 /// Enable Read DMA
41 fn enable_read_dma(&self) {
42 Self::regs().csr().modify(|v| {
43 v.set_dmaren(true);
44 })
45 }
46
47 /// Disable Read DMA
48 fn disable_read_dma(&self) {
49 Self::regs().csr().modify(|v| {
50 v.set_dmaren(false);
51 })
52 }
53
54 /// Enable Write DMA
55 fn enable_write_dma(&self) {
56 Self::regs().csr().modify(|v| {
57 v.set_dmawen(true);
58 })
59 }
60
61 /// Disable Write DMA
62 fn disable_write_dma(&self) {
63 Self::regs().csr().modify(|v| {
64 v.set_dmawen(false);
65 })
66 }
67
68 /// Set NARGS value
69 fn set_argument_count(&self, n: AccessCount) {
70 Self::regs().csr().modify(|v| {
71 v.set_nargs(match n {
72 AccessCount::One => vals::Num::NUM1,
73 AccessCount::Two => vals::Num::NUM2,
74 })
75 })
76 }
77
78 /// Set NRES value
79 fn set_result_count(&self, n: AccessCount) {
80 Self::regs().csr().modify(|v| {
81 v.set_nres(match n {
82 AccessCount::One => vals::Num::NUM1,
83 AccessCount::Two => vals::Num::NUM2,
84 });
85 })
86 }
87
88 /// Set ARGSIZE and RESSIZE value
89 fn set_data_width(&self, arg: Width, res: Width) {
90 Self::regs().csr().modify(|v| {
91 v.set_argsize(match arg {
92 Width::Bits32 => vals::Size::BITS32,
93 Width::Bits16 => vals::Size::BITS16,
94 });
95 v.set_ressize(match res {
96 Width::Bits32 => vals::Size::BITS32,
97 Width::Bits16 => vals::Size::BITS16,
98 })
99 })
100 }
101
102 /// Read RRDY flag
103 fn ready_to_read(&self) -> bool {
104 Self::regs().csr().read().rrdy()
105 }
106
107 /// Write value to WDATA
108 fn write_argument(&self, arg: u32) {
109 Self::regs().wdata().write_value(arg)
110 }
111
112 /// Read value from RDATA
113 fn read_result(&self) -> u32 {
114 Self::regs().rdata().read()
115 }
116}
diff --git a/embassy-stm32/src/cordic/utils.rs b/embassy-stm32/src/cordic/utils.rs
new file mode 100644
index 000000000..008f50270
--- /dev/null
+++ b/embassy-stm32/src/cordic/utils.rs
@@ -0,0 +1,62 @@
1//! Common math utils
2use super::errors::NumberOutOfRange;
3
4macro_rules! floating_fixed_convert {
5 ($f_to_q:ident, $q_to_f:ident, $unsigned_bin_typ:ty, $signed_bin_typ:ty, $float_ty:ty, $offset:literal, $min_positive:literal) => {
6 /// convert float point to fixed point format
7 pub fn $f_to_q(value: $float_ty) -> Result<$unsigned_bin_typ, NumberOutOfRange> {
8 const MIN_POSITIVE: $float_ty = unsafe { core::mem::transmute($min_positive) };
9
10 if value < -1.0 {
11 return Err(NumberOutOfRange::BelowLowerBound)
12 }
13
14 if value > 1.0 {
15 return Err(NumberOutOfRange::AboveUpperBound)
16 }
17
18
19 let value = if 1.0 - MIN_POSITIVE < value && value <= 1.0 {
20 // make a exception for value between (1.0^{-x} , 1.0] float point,
21 // convert it to max representable value of q1.x format
22 (1.0 as $float_ty) - MIN_POSITIVE
23 } else {
24 value
25 };
26
27 // It's necessary to cast the float value to signed integer, before convert it to a unsigned value.
28 // Since value from register is actually a "signed value", a "as" cast will keep original binary format but mark it as a unsigned value for register writing.
29 // see https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast
30 Ok((value * ((1 as $unsigned_bin_typ << $offset) as $float_ty)) as $signed_bin_typ as $unsigned_bin_typ)
31 }
32
33 #[inline(always)]
34 /// convert fixed point to float point format
35 pub fn $q_to_f(value: $unsigned_bin_typ) -> $float_ty {
36 // It's necessary to cast the unsigned integer to signed integer, before convert it to a float value.
37 // Since value from register is actually a "signed value", a "as" cast will keep original binary format but mark it as a signed value.
38 // see https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast
39 (value as $signed_bin_typ as $float_ty) / ((1 as $unsigned_bin_typ << $offset) as $float_ty)
40 }
41 };
42}
43
44floating_fixed_convert!(
45 f64_to_q1_31,
46 q1_31_to_f64,
47 u32,
48 i32,
49 f64,
50 31,
51 0x3E00_0000_0000_0000u64 // binary form of 1f64^(-31)
52);
53
54floating_fixed_convert!(
55 f32_to_q1_15,
56 q1_15_to_f32,
57 u16,
58 i16,
59 f32,
60 15,
61 0x3800_0000u32 // binary form of 1f32^(-15)
62);
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 8b826e5ac..99d8b5036 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -32,6 +32,9 @@ pub mod timer;
32pub mod adc; 32pub mod adc;
33#[cfg(can)] 33#[cfg(can)]
34pub mod can; 34pub mod can;
35// FIXME: Cordic driver cause stm32u5a5zj crash
36#[cfg(all(cordic, not(any(stm32u5a5, stm32u5a9))))]
37pub mod cordic;
35#[cfg(crc)] 38#[cfg(crc)]
36pub mod crc; 39pub mod crc;
37#[cfg(cryp)] 40#[cfg(cryp)]
@@ -244,7 +247,7 @@ pub fn init(config: Config) -> Peripherals {
244 247
245 #[cfg(dbgmcu)] 248 #[cfg(dbgmcu)]
246 crate::pac::DBGMCU.cr().modify(|cr| { 249 crate::pac::DBGMCU.cr().modify(|cr| {
247 #[cfg(any(dbgmcu_h5))] 250 #[cfg(dbgmcu_h5)]
248 { 251 {
249 cr.set_stop(config.enable_debug_during_sleep); 252 cr.set_stop(config.enable_debug_during_sleep);
250 cr.set_standby(config.enable_debug_during_sleep); 253 cr.set_standby(config.enable_debug_during_sleep);
diff --git a/examples/stm32h5/src/bin/cordic.rs b/examples/stm32h5/src/bin/cordic.rs
new file mode 100644
index 000000000..73e873574
--- /dev/null
+++ b/examples/stm32h5/src/bin/cordic.rs
@@ -0,0 +1,78 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_stm32::cordic::{self, utils};
7use {defmt_rtt as _, panic_probe as _};
8
9#[embassy_executor::main]
10async fn main(_spawner: Spawner) {
11 let mut dp = embassy_stm32::init(Default::default());
12
13 let mut cordic = cordic::Cordic::new(
14 &mut dp.CORDIC,
15 unwrap!(cordic::Config::new(
16 cordic::Function::Sin,
17 Default::default(),
18 Default::default(),
19 )),
20 );
21
22 // for output buf, the length is not that strict, larger than minimal required is ok.
23 let mut output_f64 = [0f64; 19];
24 let mut output_u32 = [0u32; 21];
25
26 // tips:
27 // CORDIC peripheral has some strict on input value, you can also use ".check_argX_fXX()" methods
28 // to make sure your input values are compatible with current CORDIC setup.
29 let arg1 = [-1.0, -0.5, 0.0, 0.5, 1.0]; // for trigonometric function, the ARG1 value [-pi, pi] should be map to [-1, 1]
30 let arg2 = [0.5]; // and for Sin function, ARG2 should be in [0, 1]
31
32 let mut input_buf = [0u32; 9];
33
34 // convert input from floating point to fixed point
35 input_buf[0] = unwrap!(utils::f64_to_q1_31(arg1[0]));
36 input_buf[1] = unwrap!(utils::f64_to_q1_31(arg2[0]));
37
38 // If input length is small, blocking mode can be used to minimize overhead.
39 let cnt0 = unwrap!(cordic.blocking_calc_32bit(
40 &input_buf[..2], // input length is strict, since driver use its length to detect calculation count
41 &mut output_u32,
42 false,
43 false
44 ));
45
46 // convert result from fixed point into floating point
47 for (&u32_val, f64_val) in output_u32[..cnt0].iter().zip(output_f64.iter_mut()) {
48 *f64_val = utils::q1_31_to_f64(u32_val);
49 }
50
51 // convert input from floating point to fixed point
52 //
53 // first value from arg1 is used, so truncate to arg1[1..]
54 for (&f64_val, u32_val) in arg1[1..].iter().zip(input_buf.iter_mut()) {
55 *u32_val = unwrap!(utils::f64_to_q1_31(f64_val));
56 }
57
58 // If calculation is a little longer, async mode can make use of DMA, and let core do some other stuff.
59 let cnt1 = unwrap!(
60 cordic
61 .async_calc_32bit(
62 &mut dp.GPDMA1_CH0,
63 &mut dp.GPDMA1_CH1,
64 &input_buf[..arg1.len() - 1], // limit input buf to its actual length
65 &mut output_u32,
66 true,
67 false
68 )
69 .await
70 );
71
72 // convert result from fixed point into floating point
73 for (&u32_val, f64_val) in output_u32[..cnt1].iter().zip(output_f64[cnt0..cnt0 + cnt1].iter_mut()) {
74 *f64_val = utils::q1_31_to_f64(u32_val);
75 }
76
77 println!("result: {}", output_f64[..cnt0 + cnt1]);
78}
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index e42470004..e09083111 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -14,8 +14,8 @@ stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not
14stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] 14stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"]
15stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] 15stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"]
16stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] 16stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"]
17stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan"] 17stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"]
18stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng", "hash"] 18stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng", "hash", "cordic"]
19stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] 19stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"]
20stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] 20stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"]
21stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng", "fdcan"] 21stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng", "fdcan"]
@@ -25,8 +25,8 @@ stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"]
25stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] 25stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"]
26stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] 26stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"]
27stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] 27stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"]
28stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng", "hash"] 28stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng", "hash", "cordic"]
29stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng", "hash"] 29stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash
30stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] 30stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"]
31stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng", "hash"] 31stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng", "hash"]
32stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] 32stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"]
@@ -48,6 +48,7 @@ embassy-stm32-wpan = []
48not-gpdma = [] 48not-gpdma = []
49dac = [] 49dac = []
50ucpd = [] 50ucpd = []
51cordic = ["dep:num-traits"]
51 52
52cm0 = ["portable-atomic/unsafe-assume-single-core"] 53cm0 = ["portable-atomic/unsafe-assume-single-core"]
53 54
@@ -83,6 +84,7 @@ chrono = { version = "^0.4", default-features = false, optional = true}
83sha2 = { version = "0.10.8", default-features = false } 84sha2 = { version = "0.10.8", default-features = false }
84hmac = "0.12.1" 85hmac = "0.12.1"
85aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } 86aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] }
87num-traits = {version="0.2", default-features = false,features = ["libm"], optional = true}
86 88
87# BEGIN TESTS 89# BEGIN TESTS
88# Generated by gen_test.py. DO NOT EDIT. 90# Generated by gen_test.py. DO NOT EDIT.
@@ -92,6 +94,11 @@ path = "src/bin/can.rs"
92required-features = [ "can",] 94required-features = [ "can",]
93 95
94[[bin]] 96[[bin]]
97name = "cordic"
98path = "src/bin/cordic.rs"
99required-features = [ "rng", "cordic",]
100
101[[bin]]
95name = "cryp" 102name = "cryp"
96path = "src/bin/cryp.rs" 103path = "src/bin/cryp.rs"
97required-features = [ "cryp",] 104required-features = [ "cryp",]
diff --git a/tests/stm32/gen_test.py b/tests/stm32/gen_test.py
index 8ff156c0e..daf714376 100644
--- a/tests/stm32/gen_test.py
+++ b/tests/stm32/gen_test.py
@@ -14,7 +14,7 @@ for f in sorted(glob('./src/bin/*.rs')):
14 with open(f, 'r') as f: 14 with open(f, 'r') as f:
15 for line in f: 15 for line in f:
16 if line.startswith('// required-features:'): 16 if line.startswith('// required-features:'):
17 features = line.split(':', 2)[1].strip().split(',') 17 features = [feature.strip() for feature in line.split(':', 2)[1].strip().split(',')]
18 18
19 tests[name] = features 19 tests[name] = features
20 20
diff --git a/tests/stm32/src/bin/cordic.rs b/tests/stm32/src/bin/cordic.rs
new file mode 100644
index 000000000..400e10207
--- /dev/null
+++ b/tests/stm32/src/bin/cordic.rs
@@ -0,0 +1,135 @@
1// required-features: rng, cordic
2
3// Test Cordic driver, with Q1.31 format, Sin function, at 24 iterations (aka PRECISION = 6), using DMA transfer
4
5#![no_std]
6#![no_main]
7
8#[path = "../common.rs"]
9mod common;
10use common::*;
11use embassy_executor::Spawner;
12use embassy_stm32::cordic::utils;
13use embassy_stm32::{bind_interrupts, cordic, peripherals, rng};
14use num_traits::Float;
15use {defmt_rtt as _, panic_probe as _};
16
17bind_interrupts!(struct Irqs {
18 RNG => rng::InterruptHandler<peripherals::RNG>;
19});
20
21/* input value control, can be changed */
22
23const INPUT_U32_COUNT: usize = 9;
24const INPUT_U8_COUNT: usize = 4 * INPUT_U32_COUNT;
25
26// Assume first calculation needs 2 arguments, the reset needs 1 argument.
27// And all calculation generate 2 results.
28const OUTPUT_LENGTH: usize = (INPUT_U32_COUNT - 1) * 2;
29
30#[embassy_executor::main]
31async fn main(_spawner: Spawner) {
32 let dp = embassy_stm32::init(config());
33
34 //
35 // use RNG generate random Q1.31 value
36 //
37 // we don't generate floating-point value, since not all binary value are valid floating-point value,
38 // and Q1.31 only accept a fixed range of value.
39
40 let mut rng = rng::Rng::new(dp.RNG, Irqs);
41
42 let mut input_buf_u8 = [0u8; INPUT_U8_COUNT];
43 defmt::unwrap!(rng.async_fill_bytes(&mut input_buf_u8).await);
44
45 // convert every [u8; 4] to a u32, for a Q1.31 value
46 let mut input_q1_31 = unsafe { core::mem::transmute::<[u8; INPUT_U8_COUNT], [u32; INPUT_U32_COUNT]>(input_buf_u8) };
47
48 // ARG2 for Sin function should be inside [0, 1], set MSB to 0 of a Q1.31 value, will make sure it's no less than 0.
49 input_q1_31[1] &= !(1u32 << 31);
50
51 //
52 // CORDIC calculation
53 //
54
55 let mut output_q1_31 = [0u32; OUTPUT_LENGTH];
56
57 // setup Cordic driver
58 let mut cordic = cordic::Cordic::new(
59 dp.CORDIC,
60 defmt::unwrap!(cordic::Config::new(
61 cordic::Function::Sin,
62 Default::default(),
63 Default::default(),
64 )),
65 );
66
67 #[cfg(feature = "stm32g491re")]
68 let (mut write_dma, mut read_dma) = (dp.DMA1_CH4, dp.DMA1_CH5);
69
70 #[cfg(any(feature = "stm32h563zi", feature = "stm32u585ai", feature = "stm32u5a5zj"))]
71 let (mut write_dma, mut read_dma) = (dp.GPDMA1_CH0, dp.GPDMA1_CH1);
72
73 // calculate first result using blocking mode
74 let cnt0 = defmt::unwrap!(cordic.blocking_calc_32bit(&input_q1_31[..2], &mut output_q1_31, false, false));
75
76 // calculate rest results using async mode
77 let cnt1 = defmt::unwrap!(
78 cordic
79 .async_calc_32bit(
80 &mut write_dma,
81 &mut read_dma,
82 &input_q1_31[2..],
83 &mut output_q1_31[cnt0..],
84 true,
85 false,
86 )
87 .await
88 );
89
90 // all output value length should be the same as our output buffer size
91 defmt::assert_eq!(cnt0 + cnt1, output_q1_31.len());
92
93 let mut cordic_result_f64 = [0.0f64; OUTPUT_LENGTH];
94
95 for (f64_val, u32_val) in cordic_result_f64.iter_mut().zip(output_q1_31) {
96 *f64_val = utils::q1_31_to_f64(u32_val);
97 }
98
99 //
100 // software calculation
101 //
102
103 let mut software_result_f64 = [0.0f64; OUTPUT_LENGTH];
104
105 let arg2 = utils::q1_31_to_f64(input_q1_31[1]);
106
107 for (&arg1, res) in input_q1_31
108 .iter()
109 .enumerate()
110 .filter_map(|(idx, val)| if idx != 1 { Some(val) } else { None })
111 .zip(software_result_f64.chunks_mut(2))
112 {
113 let arg1 = utils::q1_31_to_f64(arg1);
114
115 let (raw_res1, raw_res2) = (arg1 * core::f64::consts::PI).sin_cos();
116 (res[0], res[1]) = (raw_res1 * arg2, raw_res2 * arg2);
117 }
118
119 //
120 // check result are the same
121 //
122
123 for (cordic_res, software_res) in cordic_result_f64[..cnt0 + cnt1]
124 .chunks(2)
125 .zip(software_result_f64.chunks(2))
126 {
127 for (cord_res, soft_res) in cordic_res.iter().zip(software_res.iter()) {
128 // 2.0.powi(-19) is the max residual error for Sin function, in q1.31 format, with 24 iterations (aka PRECISION = 6)
129 defmt::assert!((cord_res - soft_res).abs() <= 2.0.powi(-19));
130 }
131 }
132
133 info!("Test OK");
134 cortex_m::asm::bkpt();
135}