@@ -78,61 +78,131 @@ impl<S: State, D: SpiDevice, const DS: u8> Spi<S, D, DS> {
7878 /// Set baudrate based on peripheral clock
7979 ///
8080 /// Typically the peripheral clock is set to 125_000_000
81+ ///
82+ /// Note that this takes ~100us on rp2040 at runtime. If that's too slow for you, see
83+ /// [calc_spi_clock_divider_settings_for_baudrate]
8184 pub fn set_baudrate < F : Into < HertzU32 > , B : Into < HertzU32 > > (
8285 & mut self ,
8386 peri_frequency : F ,
8487 baudrate : B ,
8588 ) -> HertzU32 {
8689 let freq_in = peri_frequency. into ( ) . to_Hz ( ) ;
8790 let baudrate = baudrate. into ( ) . to_Hz ( ) ;
88- let mut prescale: u8 = u8:: MAX ;
89- let mut postdiv: u8 = 0 ;
90-
91- // Find smallest prescale value which puts output frequency in range of
92- // post-divide. Prescale is an even number from 2 to 254 inclusive.
93- for prescale_option in ( 2u32 ..=254 ) . step_by ( 2 ) {
94- // We need to use an saturating_mul here because with a high baudrate certain invalid prescale
95- // values might not fit in u32. However we can be sure those values exeed the max sys_clk frequency
96- // So clamping a u32::MAX is fine here...
97- if freq_in < ( ( prescale_option + 2 ) * 256 ) . saturating_mul ( baudrate) {
98- prescale = prescale_option as u8 ;
99- break ;
100- }
101- }
91+
92+ let settings = calc_spi_clock_divider_settings_for_baudrate ( freq_in, baudrate) ;
10293
10394 // We might not find a prescale value that lowers the clock freq enough, so we leave it at max
104- debug_assert_ne ! ( prescale, u8 :: MAX ) ;
105-
106- // Find largest post-divide which makes output <= baudrate. Post-divide is
107- // an integer in the range 0 to 255 inclusive.
108- for postdiv_option in ( 1 ..=255u8 ) . rev ( ) {
109- if freq_in / ( prescale as u32 * postdiv_option as u32 ) > baudrate {
110- postdiv = postdiv_option;
111- break ;
112- }
113- }
95+ debug_assert_ne ! ( settings. prescale, u8 :: MAX ) ;
96+
97+ self . set_baudrate_from_settings ( & settings) ;
98+
99+ // Return the frequency we were able to achieve
100+ use fugit:: RateExtU32 ;
101+ ( freq_in / ( settings. prescale as u32 * ( 1 + settings. postdiv as u32 ) ) ) . Hz ( )
102+ }
114103
104+ /// Set the baudrate using a previously calculated [SpiClockDividerSettings]
105+ pub fn set_baudrate_from_settings ( & mut self , settings : & SpiClockDividerSettings ) {
115106 self . device
116107 . sspcpsr
117- . write ( |w| unsafe { w. cpsdvsr ( ) . bits ( prescale) } ) ;
108+ . write ( |w| unsafe { w. cpsdvsr ( ) . bits ( settings . prescale ) } ) ;
118109 self . device
119110 . sspcr0
120- . modify ( |_, w| unsafe { w. scr ( ) . bits ( postdiv) } ) ;
121-
122- // Return the frequency we were able to achieve
123- use fugit:: RateExtU32 ;
124- ( freq_in / ( prescale as u32 * ( 1 + postdiv as u32 ) ) ) . Hz ( )
111+ . modify ( |_, w| unsafe { w. scr ( ) . bits ( settings. postdiv ) } ) ;
125112 }
126113
127114 /// Set the mode
115+ ///
116+ /// Momentarily disables / enables the device so be careful of truncating ongoing transfers.
128117 pub fn set_mode ( & mut self , mode : Mode ) {
118+ // disable the device
119+ self . device . sspcr1 . modify ( |_, w| w. sse ( ) . clear_bit ( ) ) ;
120+
121+ // Set the polarity and phase
129122 self . device . sspcr0 . modify ( |_, w| {
130123 w. spo ( )
131124 . bit ( mode. polarity == Polarity :: IdleHigh )
132125 . sph ( )
133126 . bit ( mode. phase == Phase :: CaptureOnSecondTransition )
134127 } ) ;
128+
129+ // enable the device
130+ self . device . sspcr1 . modify ( |_, w| w. sse ( ) . set_bit ( ) ) ;
131+ }
132+
133+ /// Checks if all transmission buffers are empty.
134+ ///
135+ /// Useful when you need to wait to de-assert a chipselect or reconfigure the device after sending some bytes.
136+ pub fn complete_transfer ( & mut self ) -> Result < ( ) , nb:: Error < Infallible > > {
137+ if self . device . sspsr . read ( ) . bsy ( ) . bit ( ) {
138+ return Err ( nb:: Error :: WouldBlock ) ;
139+ } else {
140+ Ok ( ( ) )
141+ }
142+ }
143+ }
144+
145+ /// Clock divider settings
146+ pub struct SpiClockDividerSettings {
147+ /// The prescaler for writing to sspcpsr
148+ pub prescale : u8 ,
149+ /// The postdiv for writing to sspcr0
150+ pub postdiv : u8 ,
151+ }
152+
153+ /// Calculate the prescale and post divider settings for a required baudrate that can then be
154+ /// passed to [Spi::set_baudrate_from_settings]
155+ ///
156+ /// This calculation takes ~100us on rp2040 at runtime and is used by [set_baudrate] every time
157+ /// it's called. That might not be acceptable in
158+ /// situations where you need to change the baudrate often.
159+ ///
160+ /// Note that this is a const function so you can use it in a static context if you don't change your
161+ /// peripheral clock frequency at runtime.
162+ ///
163+ /// If you do change your peripheral clock at runtime you can store the [SpiClockDividerSettings] and only re-calculate it
164+ /// when the peripheral clock frequency changes.
165+ pub const fn calc_spi_clock_divider_settings_for_baudrate (
166+ peri_frequency_hz : u32 ,
167+ baudrate_hz : u32 ,
168+ ) -> SpiClockDividerSettings {
169+ let mut prescale: u8 = u8:: MAX ;
170+ let mut postdiv: u8 = 0 ;
171+
172+ // Find smallest prescale value which puts output frequency in range of
173+ // post-divide. Prescale is an even number from 2 to 254 inclusive.
174+ let mut prescale_option: u32 = 0 ;
175+ loop {
176+ prescale_option += 2 ;
177+ if prescale_option >= 254 {
178+ break ;
179+ }
180+
181+ // We need to use a saturating_mul here because with a high baudrate certain invalid prescale
182+ // values might not fit in u32. However we can be sure those values exeed the max sys_clk frequency
183+ // So clamping a u32::MAX is fine here...
184+ if peri_frequency_hz < ( ( prescale_option + 2 ) * 256 ) . saturating_mul ( baudrate_hz) {
185+ prescale = prescale_option as u8 ;
186+ break ;
187+ }
188+ }
189+
190+ // Find largest post-divide which makes output <= baudrate. Post-divide is
191+ // an integer in the range 0 to 255 inclusive.
192+ let mut postdiv_option = 255u8 ;
193+ loop {
194+ if peri_frequency_hz / ( prescale as u32 * postdiv_option as u32 ) > baudrate_hz {
195+ postdiv = postdiv_option;
196+ break ;
197+ }
198+
199+ postdiv_option -= 1 ;
200+ if postdiv_option < 1 {
201+ break ;
202+ }
135203 }
204+
205+ SpiClockDividerSettings { prescale, postdiv }
136206}
137207
138208impl < D : SpiDevice , const DS : u8 > Spi < Disabled , D , DS > {
0 commit comments