Skip to content

Commit e103406

Browse files
author
Piet Geursen
committed
Add to api of spi to avoid heavy baudrate calculation, allow checking
for transfer completion.
1 parent 8f161c1 commit e103406

File tree

1 file changed

+100
-30
lines changed

1 file changed

+100
-30
lines changed

rp2040-hal/src/spi.rs

Lines changed: 100 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
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

138208
impl<D: SpiDevice, const DS: u8> Spi<Disabled, D, DS> {

0 commit comments

Comments
 (0)