Skip to content

Commit 2e08d94

Browse files
committed
adc: iio: hardware: use iio buffering for the powerboard ADC
This means the STM32/baseboard ADC and the powerboard ADC can share most of their setup routine. The main difference is that the powerboard ADC uses a software trigger (iio-trig-hrtimer) instead of a hardware timer like the STM32 ADC does. The software trigger has to first be created via the configs, making new_powerboard() a bit different from new_stm32(). Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
1 parent 5edbbb5 commit 2e08d94

File tree

1 file changed

+60
-136
lines changed

1 file changed

+60
-136
lines changed

src/adc/iio/hardware.rs

Lines changed: 60 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1717

1818
use std::convert::{TryFrom, TryInto};
19+
use std::fs::create_dir;
1920
use std::io::Read;
21+
use std::path::Path;
2022
use std::sync::atomic::{AtomicU16, AtomicU64, Ordering};
2123
use std::sync::Mutex;
2224
use std::thread;
23-
use std::thread::{sleep, JoinHandle};
25+
use std::thread::JoinHandle;
2426
use std::time::{Duration, Instant};
2527

2628
use anyhow::{anyhow, Context, Error, Result};
@@ -102,6 +104,8 @@ const CHANNELS_PWR: &[ChannelDesc] = &[
102104
},
103105
];
104106

107+
const TRIGGER_HR_PWR_DIR: &str = "/sys/kernel/config/iio/triggers/hrtimer/tacd-pwr";
108+
105109
const TIMESTAMP_ERROR: u64 = u64::MAX;
106110

107111
#[derive(Clone, Copy)]
@@ -243,43 +247,50 @@ pub struct IioThread {
243247
}
244248

245249
impl IioThread {
246-
fn adc_setup_stm32() -> Result<(Vec<Channel>, Buffer)> {
250+
fn adc_setup(
251+
adc_name: &str,
252+
trigger_name: &str,
253+
sample_rate: i64,
254+
channel_descs: &[ChannelDesc],
255+
buffer_len: usize,
256+
) -> Result<(Vec<Channel>, Buffer)> {
247257
let ctx = industrial_io::Context::new()?;
248258

249259
debug!("IIO devices:");
250260
for dev in ctx.devices() {
251261
debug!(" * {}", &dev.name().unwrap_or_default());
252262
}
253263

254-
let stm32_adc = ctx
255-
.find_device("48003000.adc:adc@0")
256-
.ok_or(anyhow!("Could not find STM32 ADC"))?;
264+
let adc = ctx
265+
.find_device(adc_name)
266+
.ok_or(anyhow!("Could not find ADC: {}", adc_name))?;
257267

258-
if let Err(err) = stm32_adc.attr_write_bool("buffer/enable", false) {
259-
warn!("Failed to disable STM32 ADC buffer: {}", err);
268+
if let Err(err) = adc.attr_write_bool("buffer/enable", false) {
269+
warn!("Failed to disable {} ADC buffer: {}", adc_name, err);
260270
}
261271

262-
let stm32_channels: Vec<Channel> = CHANNELS_STM32
272+
let channels: Vec<Channel> = channel_descs
263273
.iter()
264274
.map(|ChannelDesc { kernel_name, .. }| {
265-
let ch = stm32_adc
275+
let ch = adc
266276
.find_channel(kernel_name, false)
267-
.unwrap_or_else(|| panic!("Failed to open iio channel {}", kernel_name));
277+
.unwrap_or_else(|| panic!("Failed to open kernel channel {}", kernel_name));
268278

269279
ch.enable();
270280
ch
271281
})
272282
.collect();
273283

274284
let trig = ctx
275-
.find_device("tim4_trgo")
276-
.ok_or(anyhow!("Could not find STM32 Timer 4 trigger"))?;
277-
trig.attr_write_int("sampling_frequency", 1024)?;
285+
.find_device(trigger_name)
286+
.ok_or(anyhow!("Could not find IIO trigger: {}", trigger_name))?;
287+
288+
trig.attr_write_int("sampling_frequency", sample_rate)?;
278289

279-
stm32_adc.set_trigger(&trig)?;
290+
adc.set_trigger(&trig)?;
280291
ctx.set_timeout_ms(1000)?;
281292

282-
let stm32_buf = stm32_adc.create_buffer(128, false)?;
293+
let buf = adc.create_buffer(buffer_len, false)?;
283294

284295
set_thread_priority_and_policy(
285296
thread_native_id(),
@@ -288,10 +299,17 @@ impl IioThread {
288299
)
289300
.map_err(|e| anyhow!("Failed to set realtime thread priority: {e:?}"))?;
290301

291-
Ok((stm32_channels, stm32_buf))
302+
Ok((channels, buf))
292303
}
293304

294-
pub async fn new_stm32() -> Result<Arc<Self>> {
305+
async fn new(
306+
thread_name: &str,
307+
adc_name: &'static str,
308+
trigger_name: &'static str,
309+
sample_rate: i64,
310+
channel_descs: &'static [ChannelDesc],
311+
buffer_len: usize,
312+
) -> Result<Arc<Self>> {
295313
// Some of the adc thread setup can only happen _in_ the adc thread,
296314
// like setting the priority or some iio setup, as not all structs
297315
// are Send.
@@ -303,16 +321,23 @@ impl IioThread {
303321

304322
// Spawn a high priority thread that updates the atomic values in `thread`.
305323
let join = thread::Builder::new()
306-
.name("tacd stm32 iio".to_string())
324+
.name(format!("tacd {thread_name} iio"))
307325
.spawn(move || {
308-
let (thread, channels, mut buf) = match Self::adc_setup_stm32() {
326+
let adc_setup_res = Self::adc_setup(
327+
adc_name,
328+
trigger_name,
329+
sample_rate,
330+
channel_descs,
331+
buffer_len,
332+
);
333+
let (thread, channels, mut buf) = match adc_setup_res {
309334
Ok((channels, buf)) => {
310335
let thread = Arc::new(Self {
311336
ref_instant: Instant::now(),
312337
timestamp: AtomicU64::new(TIMESTAMP_ERROR),
313338
values: channels.iter().map(|_| AtomicU16::new(0)).collect(),
314339
join: Mutex::new(None),
315-
channel_descs: CHANNELS_STM32,
340+
channel_descs,
316341
});
317342

318343
(thread, channels, buf)
@@ -332,7 +357,7 @@ impl IioThread {
332357
if let Err(e) = buf.refill() {
333358
thread.timestamp.store(TIMESTAMP_ERROR, Ordering::Relaxed);
334359

335-
error!("Failed to refill stm32 ADC buffer: {}", e);
360+
error!("Failed to refill {} ADC buffer: {}", adc_name, e);
336361

337362
// If the ADC has not yet produced any values we still have the
338363
// queue at hand that signals readiness to the main thread.
@@ -377,127 +402,26 @@ impl IioThread {
377402
Ok(thread)
378403
}
379404

380-
fn adc_setup_powerboard() -> Result<Vec<Channel>> {
381-
let ctx = industrial_io::Context::new()?;
382-
383-
debug!("IIO devices:");
384-
for dev in ctx.devices() {
385-
debug!(" * {}", &dev.name().unwrap_or_default());
386-
}
387-
388-
let pwr_adc = ctx
389-
.find_device("lmp92064")
390-
.ok_or(anyhow!("Could not find Powerboard ADC"))?;
391-
392-
ctx.set_timeout_ms(1000)?;
393-
394-
let pwr_channels: Vec<Channel> = CHANNELS_PWR
395-
.iter()
396-
.map(|ChannelDesc { kernel_name, .. }| {
397-
pwr_adc
398-
.find_channel(kernel_name, false)
399-
.unwrap_or_else(|| panic!("Failed to open iio channel {}", kernel_name))
400-
})
401-
.collect();
402-
403-
set_thread_priority_and_policy(
404-
thread_native_id(),
405-
ThreadPriority::Crossplatform(ThreadPriorityValue::try_from(10).unwrap()),
406-
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo),
405+
pub async fn new_stm32() -> Result<Arc<Self>> {
406+
Self::new(
407+
"stm32",
408+
"48003000.adc:adc@0",
409+
"tim4_trgo",
410+
1024,
411+
CHANNELS_STM32,
412+
128,
407413
)
408-
.map_err(|e| anyhow!("Failed to set realtime thread priority: {e:?}"))?;
409-
410-
Ok(pwr_channels)
414+
.await
411415
}
412416

413417
pub async fn new_powerboard() -> Result<Arc<Self>> {
414-
// Some of the adc thread setup can only happen _in_ the adc thread,
415-
// like setting the priority or some iio setup, as not all structs
416-
// are Send.
417-
// We do however not want to return from new() before we know that the
418-
// setup was sucessful.
419-
// This is why we create Self inside the thread and send it back
420-
// to the calling thread via a queue.
421-
let (thread_res_tx, mut thread_res_rx) = bounded(1);
422-
423-
// Spawn a high priority thread that updates the atomic values in `thread`.
424-
let join = thread::Builder::new()
425-
.name("tacd powerboard iio".to_string())
426-
.spawn(move || {
427-
let (thread, channels) = match Self::adc_setup_powerboard() {
428-
Ok(channels) => {
429-
let thread = Arc::new(Self {
430-
ref_instant: Instant::now(),
431-
timestamp: AtomicU64::new(TIMESTAMP_ERROR),
432-
values: channels.iter().map(|_| AtomicU16::new(0)).collect(),
433-
join: Mutex::new(None),
434-
channel_descs: CHANNELS_PWR,
435-
});
418+
let hr_trigger_path = Path::new(TRIGGER_HR_PWR_DIR);
436419

437-
(thread, channels)
438-
}
439-
Err(e) => {
440-
thread_res_tx.try_send(Err(e)).unwrap();
441-
return;
442-
}
443-
};
444-
445-
let thread_weak = Arc::downgrade(&thread);
446-
let mut signal_ready = Some((thread, thread_res_tx));
447-
448-
// Stop running as soon as the last reference to this Arc<IioThread>
449-
// is dropped (e.g. the weak reference can no longer be upgraded).
450-
while let Some(thread) = thread_weak.upgrade() {
451-
// Use the sysfs based interface to get the values from the
452-
// power board ADC at a slow sampling rate.
453-
let voltage = channels[0].attr_read_int("raw");
454-
let current = channels[1].attr_read_int("raw");
455-
456-
let (voltage, current) = match (voltage, current) {
457-
(Ok(v), Ok(c)) => (v, c),
458-
(Err(e), _) | (_, Err(e)) => {
459-
thread.timestamp.store(TIMESTAMP_ERROR, Ordering::Relaxed);
460-
461-
error!("Failed to read Powerboard ADC: {}", e);
462-
463-
// If the ADC has not yet produced any values we still have the
464-
// queue at hand that signals readiness to the main thread.
465-
// This gives us a chance to return an Err from new().
466-
// If the queue was already used just print an error instead.
467-
if let Some((_, tx)) = signal_ready.take() {
468-
tx.try_send(Err(Error::new(e))).unwrap();
469-
}
470-
471-
break;
472-
}
473-
};
474-
475-
thread.values[0].store(voltage as u16, Ordering::Relaxed);
476-
thread.values[1].store(current as u16, Ordering::Relaxed);
477-
478-
let ts: u64 = Instant::now()
479-
.checked_duration_since(thread.ref_instant)
480-
.unwrap()
481-
.as_nanos()
482-
.try_into()
483-
.unwrap();
484-
485-
thread.timestamp.store(ts, Ordering::Release);
486-
487-
// Now that we know that the ADC actually works and we have
488-
// initial values: return a handle to it.
489-
if let Some((content, tx)) = signal_ready.take() {
490-
tx.try_send(Ok(content)).unwrap();
491-
}
492-
493-
sleep(Duration::from_millis(50));
494-
}
495-
})?;
496-
497-
let thread = thread_res_rx.next().await.unwrap()?;
498-
*thread.join.lock().unwrap() = Some(join);
420+
if !hr_trigger_path.is_dir() {
421+
create_dir(hr_trigger_path).unwrap();
422+
}
499423

500-
Ok(thread)
424+
Self::new("powerboard", "lmp92064", "tacd-pwr", 20, CHANNELS_PWR, 1).await
501425
}
502426

503427
/// Use the channel names defined at the top of the file to get a reference

0 commit comments

Comments
 (0)