見出し画像

RustでSPIデータ受信―有識者レビューとクレート化 (連載15)

前回書いたサンプルプログラムや記事を、有識者の方にチェックいただきました。

そのフィードバックの内容について書きます。

さらに、加速度センサADXL345制御部をクレート化しておこうと思います。

1. 有識者レビュー結果

1.1 引数型は意味に即した型を使用すること

例えば加速度センサADXL345のレジスタに書き込む以下の関数について、見てみます。

pub fn regwrite(regaddr: u32, writeval: u32)

--------- [指摘内容]---------
意味に即した型を使用するのが慣習的です。例えば、ADXL345のレジスタアドレスと値はそれぞれ8ビット幅なので、u8が引数型になります。
------------------------------

ここで筆者がなぜu32にしたかというと、これはBCM2711チップの内蔵I/Oである SPIのFIFOレジスタに書き込むデータであり、そのFIFOレジスタのサイズが32ビットだからです。
Cargoパッケージ「bcm2711_pac」に含まれるライブラリクレートに以下のように定義されています。

register_bitfields! {u32,
    pub FIFO [
        /// Read from RX FIFO or write to TX FIFO
        ///
        /// *DMA Mode (`DMAEN` set):* If `TA` is clear, the first 32-bit write
        /// to this register will control `SPIDLEN` and `SPICS`. Subsequent
        /// reads and writes will be taken as four-byte data words to be read
        /// or written to the FIFOs.
        ///
        /// *Poll/Interrupt Mode (`DMAEN` clear, `TA` set):* Writes to the
        /// register write bytes to the TX FIFO. Reads from the register read
        /// bytes from the RX FIFO.
        DATA OFFSET(0) NUMBITS(32) [],
    ]
}

u32ですね。

すなわち、筆者はアクセス先のレジスタサイズと同じサイズ(u32)にしました。
しかしそうではなく、使い道に即した型にすることが慣習だとの事でした。

今回はFIFO32ビット分すべて使用するわけではありません。
理由はADXL345のレジスタアドレスが8ビット、値も8ビットだからです。
8ビット分押し出せば済みます。

したがって、ここはu8とします。

しかしそうすると、問題が出てきます。
先述の通り、FIFOのレジスタは32ビットアクセスです。

これについても、アドバイス頂きました。

--------- [指摘内容]---------
u8 -> u32は無損失変換なので、“.into()” で変換できます。
例:
   spi::FIFO::DATA.val(writeval.into()));
------------------------------

ふむふむ。
“.into()” は、組み込みではいろいろ出てきそうですね。
要チェックです。

同じことは受信関数にも言えます。

pub fn regread(regval: u32) -> i32 {

引数はu8にしましょう。
戻り値はi32、このままで。

pub fn regread(regval: u8) -> i32 {

そしてFIFOのアクセスも同様に“.into()” で変換。

//(3)FIFOに送信データを書く(送信データ:ライト対象の加速度センサのレジスタ値)
spi_regs().fifo.write(spi::FIFO::DATA.val(regval.into()));

これで完了。

1.2 型変換

受信部の最後、符号付に変換したり型変換したりしている部分があります。

retdata = i32::try_from(retdata_l | (retdata_h << 8)).unwrap();
if (retdata & 0x8000) != 0x0000 {
    retdata = (!retdata & 0xFFFF) + 1;
    retdata = 0 - retdata;
}

ここ、なんと一行で書けてしまいました!

[ポイント1]
FIFOから読んできたretdata_lとretdata_h, u32だがu8として扱い、さらにそれらをfrom_le_bytesを用いて下位側から詰めていく。
参考:https://doc.rust-lang.org/std/primitive.u32.html#method.from_le_bytes

[ポイント2]
i16 -> i32は無損失変換なので、“.into()” または “u32::from( … )” で変換できる。

したがって、以下の一行になりました。

retdata =i32::from(i16::from_le_bytes([retdata_l as u8, retdata_h as u8]));

1.3 return不要

受信関数の最後のreturnは不要です。
(と、過去の記事に書きました。自分で。。。)

https://note.com/yn_2022/n/nf64d5d2b3547#d3706e78-c097-4c12-be4d-941c174e2f63

「Rustでは、return文がなくても、最後の式の値が戻り値になります。」

retdata = i32::from(i16::from_le_bytes([retdata_l as u8, retdata_h as u8]));
return retdata;

という事で、returnを削除し、式にします。

i32::from(i16::from_le_bytes([retdata_l as u8, retdata_h as u8]));

ビルドエラーが出ました。

セミコロンがあると、「式」ではないそうです。
なので、セミコロンは削除し、無事、ビルドが通りました。

[注] 2/14補足
・ブロック中の最後のステートメントの後に式がある場合、それがブロックの値になる
・セミコロン終端された式は「式ステートメント」という扱いになり、ステートメントになるため、リターン値を返すにはその後に式が必要。

ソースコードは最後にまとめて書きます。

2.クレート化

連載8でクレート化を試してみました。

https://note.com/yn_2022/n/nf9851aff3f18

この経験を用いて、加速度センサADXL345制御部をクレート化してみます。

今、以下二つのモジュールがあります。

mod spi_gpio_control
mod spi_control

これら、mod spi_controlに統一し、そしてクレート「spi_adxl345」を作ることにします。

作成の方法は、連載8で書いたことと何ら変わらないので、省略します。

3.動かしてみる

変わらず動きました。

4.ソースコード

ソースコードはこうなりました。
ADXL345センサ制御部をクレート化したので、すっきりしました。

4.1 メイン部

use itron::{task::delay, time::duration};

#[no_mangle]
pub extern "C" fn slo_main() {
    const BW_RATE: u8 = 0x2c;
    const BW_RATE_VAL: u8 = 0x0b;
    const DATA_FORMAT: u8 = 0x31;
    const DATA_FORMAT_VAL: u8 = 0x0B; //(4 mg/LSB +-16g)
    const POWER_CTL: u8 = 0x2D;
    const POWER_CTL_VAL: u8 = 0x08;

    println!("Starting SPI control.");

    spi_adxl345::init();

    //ADXL345_init
    spi_adxl345::regwrite(BW_RATE, BW_RATE_VAL);
    spi_adxl345::regwrite(DATA_FORMAT, DATA_FORMAT_VAL);
    spi_adxl345::regwrite(POWER_CTL, POWER_CTL_VAL);

	loop{
    //連続複数リードを行う。-> 0xc0とORをとる。
	 //0.0392266=(4/1000*9.80665)

		let x_axis = spi_adxl345::regread(0xc0 | 0x32) as f32 * 0.0392266;
		let y_axis = spi_adxl345::regread(0xc0 | 0x34) as f32 * 0.0392266;
		let z_axis = spi_adxl345::regread(0xc0 | 0x36) as f32 * 0.0392266;

		println!("x_axis:{} y_axis:{} z_axis:{}", x_axis, y_axis, z_axis);

		delay(duration!(ms: 500)).unwrap(); //500ms待つ
	}
}

4.2 クレート部

use bcm2711_pac::gpio;
use bcm2711_pac::spi;
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};

fn gpio_regs() -> &'static gpio::Registers {
    // Safety: SOLID for RaPi4B provides an identity mapping in this area, and we don't alter
    // the mapping
    unsafe { &*(gpio::BASE.to_arm_pa().unwrap() as usize as *const gpio::Registers) }
}

fn spi_regs() -> &'static spi::Registers {
    // Safety: SOLID for RaPi4B provides an identity mapping in this area, and we don't alter
    // the mapping
    unsafe { &*(spi::BASE_SPI0.to_arm_pa().unwrap() as usize as *const spi::Registers) }
}

fn gpio_init() {
    for pin in 8..=11 {
        gpio_regs().gpfsel[pin / gpio::GPFSEL::PINS_PER_REGISTER].modify(
            gpio::GPFSEL::pin(pin % gpio::GPFSEL::PINS_PER_REGISTER).val(gpio::GPFSEL::ALT0),
        );
    }
}

pub fn init() {
    const CLKSET: u32 = 0x00cb;

    gpio_init();

    //SPIのCLKレジスタにSPI周波数を設定
    spi_regs().clk.write(spi::CLK::CDIV.val(CLKSET));

    //CS : 00 = Chip select 0  -> CS.bit0 and 1
    //CPOL: Clock Polarity 1 = Rest state of clock = High -> CS.bit3
    //CPHA: Clock Phase 1 = First SCLK transition at beginning of data bit. -> CS.bit2
    spi_regs().cs.modify(spi::CS::CS::ChipSelect0);
    spi_regs().cs.modify(spi::CS::CPOL::RestStateIsHigh);
    spi_regs()
        .cs
        .modify(spi::CS::CPHA::FirstSclkTransitionAtBeginningOFDataBit);
}

pub fn regwrite(regaddr: u8, writeval: u8) {
    //(1)CSレジスタのTAビットを1にする。
    spi_regs().cs.modify(spi::CS::TA::SET);

    //(2)FIFOに送信データを書く(送信データ:ライト対象の加速度センサのレジスタ値)
    spi_regs().fifo.write(spi::FIFO::DATA.val(regaddr.into()));

    //(3)CSレジスタのTXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::TXD) {}

    //(4)FIFOに送信データを書く(送信データ:先ほどのレジスタへのライト値)
    spi_regs().fifo.write(spi::FIFO::DATA.val(writeval.into()));

    //(5)CSレジスタのDONEビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::DONE) {}

    //(6)CSレジスタのCLEARビットに0x01を書いてFIFOクリア
    spi_regs().cs.modify(spi::CS::CLEAR_TX::SET);

    //(7)CSレジスタのTAビットを0にして送信完了。
    spi_regs().cs.modify(spi::CS::TA::CLEAR);
}

//TODO: エラー処理は今回は考えず、後で考える
pub fn regread(regval: u8) -> i32 {
    let retdata_h: u32;
    let retdata_l: u32;

    //(1)CSレジスタのTAビットを1にする。
    spi_regs().cs.modify(spi::CS::TA::SET);

    //(2)CSレジスタのCLEARビットに0x03を書いてTX-FIFO, RX-FIFOクリア
    spi_regs().cs.modify(spi::CS::CLEAR_TX::SET + spi::CS::CLEAR_RX::SET);

    //(3)FIFOに送信データを書く(送信データ:ライト対象の加速度センサのレジスタ値)
    spi_regs().fifo.write(spi::FIFO::DATA.val(regval.into()));

    //(4)CSレジスタのTXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::TXD) {}

    //(5)CSレジスタのRXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::RXD) {}

    //(6)CSレジスタのCLEARビットに0x02を書いてRX-FIFOクリア
    spi_regs().cs.modify(spi::CS::CLEAR_RX::SET);

    //(7)FIFOに送信データを書く(送信データ:押し出すだけなのでダミーで0)
    spi_regs().fifo.write(spi::FIFO::DATA.val(0x00));

    //(8)CSレジスタのTXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::TXD) {}

    //(9)CSレジスタのRXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::RXD) {}

    //(10)待ち終わったらFIFOリード
    retdata_l = spi_regs().fifo.read(spi::FIFO::DATA);

    //(11)FIFOに送信データを書く(送信データ:押し出すだけなのでダミーで0)
    spi_regs().fifo.write(spi::FIFO::DATA.val(0x00));

    //(12)CSレジスタのTXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::TXD) {}

    //(13)CSレジスタのRXDビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::RXD) {}

    //(14)待ち終わったらFIFOリード
    retdata_h = spi_regs().fifo.read(spi::FIFO::DATA);

    //(15)CSレジスタのDONEビットが1になるまで待つ
    while !spi_regs().cs.is_set(spi::CS::DONE) {}

    //(16)CSレジスタのCLEARビットに0x03を書いてTX-FIFO, RX-FIFOクリア
    spi_regs().cs.modify(spi::CS::CLEAR_TX::SET + spi::CS::CLEAR_RX::SET);

    //(17)CSレジスタのTAビットを0にして送受信完了。
    spi_regs().cs.modify(spi::CS::TA::CLEAR);
		
	i32::from(i16::from_le_bytes([retdata_l as u8, retdata_h as u8]))
}

今回はここまで。

今回、クレートのインタフェースを、
・init()関数
・regwrite()関数
・regread()関数
としましたが、次回はspi_adxl345への制御を完全に隠蔽してしまうべく変更をしたいと思います。
・init()関数:spi_adxl345への初期化を完全に終わらせる
・current_acceleration()関数:X, Y, Z軸の加速度値を取得する(Main関数で計算不要にする)

それが終わったらSPIのことは一旦忘れて、Linuxのネットワーク機能をSOLID-OSから使用することを試してみます。

この記事が気に入ったらサポートをしてみませんか?