Skip to main content

endbasic_rpi/
spi.rs

1// EndBASIC
2// Copyright 2025 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! SPI bus implementation using rppal.
17
18use endbasic_std::spi::{SpiBus, SpiMode};
19use rppal::spi::{self, Bus, SlaveSelect, Spi};
20use std::io::Write;
21use std::path::Path;
22use std::{fs, io};
23
24/// Path to the configuration file containing the maximum SPI transfer size.
25const SPIDEV_BUFSIZ_PATH: &str = "/sys/module/spidev/parameters/bufsiz";
26
27/// Converts an SPI error to an IO error.
28fn spi_error_to_io_error(e: spi::Error) -> io::Error {
29    match e {
30        spi::Error::Io(e) => e,
31        e => io::Error::new(io::ErrorKind::InvalidInput, e.to_string()),
32    }
33}
34
35/// Queries the maximum SPI transfer size from `path`.  If `path` is not provided, defaults to
36/// `SPIDEV_BUFSIZ_PATH`.
37fn query_spi_bufsiz(path: Option<&Path>) -> io::Result<usize> {
38    let path = path.unwrap_or(Path::new(SPIDEV_BUFSIZ_PATH));
39
40    let content = match fs::read_to_string(path) {
41        Ok(content) => content,
42        Err(e) => {
43            return Err(io::Error::new(
44                e.kind(),
45                format!("Failed to read {}: {}", path.display(), e),
46            ));
47        }
48    };
49
50    let content = content.trim_end();
51
52    match content.parse::<usize>() {
53        Ok(i) => Ok(i),
54        Err(e) => Err(io::Error::new(
55            io::ErrorKind::InvalidData,
56            format!("Failed to read {}: invalid content: {}", path.display(), e),
57        )),
58    }
59}
60
61/// An implementation of an `SpiBus` using rppal.
62pub struct RppalSpiBus {
63    spi: Spi,
64    bufsiz: usize,
65}
66
67/// Factory function to open an `RppalSpiBus`.
68pub fn spi_bus_open(bus: u8, slave: u8, clock_hz: u32, mode: SpiMode) -> io::Result<RppalSpiBus> {
69    let bus = match bus {
70        0 => Bus::Spi0,
71        _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Only bus 0 is supported")),
72    };
73
74    let slave = match slave {
75        0 => SlaveSelect::Ss0,
76        _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Only slave 0 is supported")),
77    };
78
79    let mode = match mode {
80        SpiMode::Mode0 => spi::Mode::Mode0,
81        SpiMode::Mode1 => spi::Mode::Mode1,
82        SpiMode::Mode2 => spi::Mode::Mode2,
83        SpiMode::Mode3 => spi::Mode::Mode3,
84    };
85
86    let spi = Spi::new(bus, slave, clock_hz, mode).map_err(spi_error_to_io_error)?;
87
88    let bufsiz = query_spi_bufsiz(None)?;
89
90    Ok(RppalSpiBus { spi, bufsiz })
91}
92
93impl Write for RppalSpiBus {
94    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
95        self.spi.write(buf).map_err(spi_error_to_io_error)
96    }
97
98    fn flush(&mut self) -> io::Result<()> {
99        Ok(())
100    }
101}
102
103impl SpiBus for RppalSpiBus {
104    fn max_size(&self) -> usize {
105        self.bufsiz
106    }
107}