diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-04-04 21:32:27 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-04-04 21:32:27 +0000 |
| commit | 6c35a1769d9bce27805ed6dcbb09d1306be6e452 (patch) | |
| tree | 3fba5b2ae92677744f7da246ecd4bd96fa2d48f7 | |
| parent | 067e422863674762c0ee20178f3671ce16a5986c (diff) | |
| parent | 6b2e15e318c05a66f17575cde4987353a52108a4 (diff) | |
Merge pull request #2697 from eZioPan/stm32-cordic
stm32 CORDIC driver
| -rw-r--r-- | embassy-stm32/build.rs | 9 | ||||
| -rw-r--r-- | embassy-stm32/src/cordic/enums.rs | 71 | ||||
| -rw-r--r-- | embassy-stm32/src/cordic/errors.rs | 144 | ||||
| -rw-r--r-- | embassy-stm32/src/cordic/mod.rs | 617 | ||||
| -rw-r--r-- | embassy-stm32/src/cordic/sealed.rs | 116 | ||||
| -rw-r--r-- | embassy-stm32/src/cordic/utils.rs | 62 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 5 | ||||
| -rw-r--r-- | examples/stm32h5/src/bin/cordic.rs | 78 | ||||
| -rw-r--r-- | tests/stm32/Cargo.toml | 15 | ||||
| -rw-r--r-- | tests/stm32/gen_test.py | 2 | ||||
| -rw-r--r-- | tests/stm32/src/bin/cordic.rs | 135 |
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))] | ||
| 5 | pub 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)] | ||
| 21 | pub 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))] | ||
| 44 | pub 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)] | ||
| 59 | pub enum AccessCount { | ||
| 60 | #[default] | ||
| 61 | One, | ||
| 62 | Two, | ||
| 63 | } | ||
| 64 | |||
| 65 | /// CORDIC argument/result data width | ||
| 66 | #[allow(missing_docs)] | ||
| 67 | #[derive(Clone, Copy)] | ||
| 68 | pub 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 @@ | |||
| 1 | use super::{Function, Scale}; | ||
| 2 | |||
| 3 | /// Error for [Cordic](super::Cordic) | ||
| 4 | #[derive(Debug)] | ||
| 5 | pub 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 | |||
| 18 | impl From<ConfigError> for CordicError { | ||
| 19 | fn from(value: ConfigError) -> Self { | ||
| 20 | Self::ConfigError(value) | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | impl From<NumberOutOfRange> for CordicError { | ||
| 25 | fn from(value: NumberOutOfRange) -> Self { | ||
| 26 | Self::NumberOutOfRange(value) | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | impl From<ArgError> for CordicError { | ||
| 31 | fn from(value: ArgError) -> Self { | ||
| 32 | Self::ArgError(value) | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | impl 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)] | ||
| 54 | pub struct ConfigError { | ||
| 55 | pub(super) func: Function, | ||
| 56 | pub(super) scale_range: [u8; 2], | ||
| 57 | } | ||
| 58 | |||
| 59 | #[cfg(feature = "defmt")] | ||
| 60 | impl 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)] | ||
| 80 | pub enum NumberOutOfRange { | ||
| 81 | BelowLowerBound, | ||
| 82 | AboveUpperBound, | ||
| 83 | } | ||
| 84 | |||
| 85 | #[cfg(feature = "defmt")] | ||
| 86 | impl 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)] | ||
| 100 | pub 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")] | ||
| 109 | impl 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))] | ||
| 141 | pub(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 | |||
| 3 | use embassy_hal_internal::drop::OnDrop; | ||
| 4 | use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; | ||
| 5 | |||
| 6 | use crate::{dma, peripherals}; | ||
| 7 | |||
| 8 | mod enums; | ||
| 9 | pub use enums::*; | ||
| 10 | |||
| 11 | mod errors; | ||
| 12 | pub use errors::*; | ||
| 13 | |||
| 14 | mod sealed; | ||
| 15 | use self::sealed::SealedInstance; | ||
| 16 | |||
| 17 | pub mod utils; | ||
| 18 | |||
| 19 | /// CORDIC driver | ||
| 20 | pub struct Cordic<'d, T: Instance> { | ||
| 21 | peri: PeripheralRef<'d, T>, | ||
| 22 | config: Config, | ||
| 23 | } | ||
| 24 | |||
| 25 | /// CORDIC instance trait | ||
| 26 | #[allow(private_bounds)] | ||
| 27 | pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral {} | ||
| 28 | |||
| 29 | /// CORDIC configuration | ||
| 30 | #[derive(Debug)] | ||
| 31 | pub struct Config { | ||
| 32 | function: Function, | ||
| 33 | precision: Precision, | ||
| 34 | scale: Scale, | ||
| 35 | } | ||
| 36 | |||
| 37 | impl 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 | ||
| 82 | impl<'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 | |||
| 147 | impl<'d, T: Instance> Drop for Cordic<'d, T> { | ||
| 148 | fn drop(&mut self) { | ||
| 149 | T::disable(); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | // q1.31 related | ||
| 154 | impl<'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 | ||
| 356 | impl<'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 | |||
| 469 | macro_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 | |||
| 600 | check_arg_value!(check_f64_arg1, check_f64_arg2, &f64); | ||
| 601 | check_arg_value!(check_f32_arg1, check_f32_arg2, &f32); | ||
| 602 | |||
| 603 | foreach_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 | |||
| 616 | dma_trait!(WriteDma, Instance); | ||
| 617 | dma_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 @@ | |||
| 1 | use super::*; | ||
| 2 | use crate::pac::cordic::vals; | ||
| 3 | |||
| 4 | /// Cordic instance | ||
| 5 | pub(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 | ||
| 2 | use super::errors::NumberOutOfRange; | ||
| 3 | |||
| 4 | macro_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 | |||
| 44 | floating_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 | |||
| 54 | floating_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; | |||
| 32 | pub mod adc; | 32 | pub mod adc; |
| 33 | #[cfg(can)] | 33 | #[cfg(can)] |
| 34 | pub mod can; | 34 | pub mod can; |
| 35 | // FIXME: Cordic driver cause stm32u5a5zj crash | ||
| 36 | #[cfg(all(cordic, not(any(stm32u5a5, stm32u5a9))))] | ||
| 37 | pub mod cordic; | ||
| 35 | #[cfg(crc)] | 38 | #[cfg(crc)] |
| 36 | pub mod crc; | 39 | pub 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 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::cordic::{self, utils}; | ||
| 7 | use {defmt_rtt as _, panic_probe as _}; | ||
| 8 | |||
| 9 | #[embassy_executor::main] | ||
| 10 | async 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 | |||
| 14 | stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] | 14 | stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] |
| 15 | stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] | 15 | stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] |
| 16 | stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] | 16 | stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] |
| 17 | stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan"] | 17 | stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"] |
| 18 | stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng", "hash"] | 18 | stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng", "hash", "cordic"] |
| 19 | stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] | 19 | stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] |
| 20 | stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] | 20 | stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] |
| 21 | stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng", "fdcan"] | 21 | stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng", "fdcan"] |
| @@ -25,8 +25,8 @@ stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"] | |||
| 25 | stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] | 25 | stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] |
| 26 | stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] | 26 | stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] |
| 27 | stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] | 27 | stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] |
| 28 | stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng", "hash"] | 28 | stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng", "hash", "cordic"] |
| 29 | stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng", "hash"] | 29 | stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash |
| 30 | stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] | 30 | stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] |
| 31 | stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng", "hash"] | 31 | stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng", "hash"] |
| 32 | stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] | 32 | stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] |
| @@ -48,6 +48,7 @@ embassy-stm32-wpan = [] | |||
| 48 | not-gpdma = [] | 48 | not-gpdma = [] |
| 49 | dac = [] | 49 | dac = [] |
| 50 | ucpd = [] | 50 | ucpd = [] |
| 51 | cordic = ["dep:num-traits"] | ||
| 51 | 52 | ||
| 52 | cm0 = ["portable-atomic/unsafe-assume-single-core"] | 53 | cm0 = ["portable-atomic/unsafe-assume-single-core"] |
| 53 | 54 | ||
| @@ -83,6 +84,7 @@ chrono = { version = "^0.4", default-features = false, optional = true} | |||
| 83 | sha2 = { version = "0.10.8", default-features = false } | 84 | sha2 = { version = "0.10.8", default-features = false } |
| 84 | hmac = "0.12.1" | 85 | hmac = "0.12.1" |
| 85 | aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } | 86 | aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } |
| 87 | num-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" | |||
| 92 | required-features = [ "can",] | 94 | required-features = [ "can",] |
| 93 | 95 | ||
| 94 | [[bin]] | 96 | [[bin]] |
| 97 | name = "cordic" | ||
| 98 | path = "src/bin/cordic.rs" | ||
| 99 | required-features = [ "rng", "cordic",] | ||
| 100 | |||
| 101 | [[bin]] | ||
| 95 | name = "cryp" | 102 | name = "cryp" |
| 96 | path = "src/bin/cryp.rs" | 103 | path = "src/bin/cryp.rs" |
| 97 | required-features = [ "cryp",] | 104 | required-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"] | ||
| 9 | mod common; | ||
| 10 | use common::*; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_stm32::cordic::utils; | ||
| 13 | use embassy_stm32::{bind_interrupts, cordic, peripherals, rng}; | ||
| 14 | use num_traits::Float; | ||
| 15 | use {defmt_rtt as _, panic_probe as _}; | ||
| 16 | |||
| 17 | bind_interrupts!(struct Irqs { | ||
| 18 | RNG => rng::InterruptHandler<peripherals::RNG>; | ||
| 19 | }); | ||
| 20 | |||
| 21 | /* input value control, can be changed */ | ||
| 22 | |||
| 23 | const INPUT_U32_COUNT: usize = 9; | ||
| 24 | const 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. | ||
| 28 | const OUTPUT_LENGTH: usize = (INPUT_U32_COUNT - 1) * 2; | ||
| 29 | |||
| 30 | #[embassy_executor::main] | ||
| 31 | async 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 | } | ||
