シェルスクリプトマガジン

特別企画 Raspberry Piを100%活用しよう 拡大版(Vol.84記載)

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、前回に引き続き、ラズパイと連携して使用するマイコンボードを扱います。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図8 I★2★CアドレスとGPIOピン番号の設定(ADRS2040U_i2c.h)

 // ADRS2040U I2Cアドレス
 #define I2C0_SLAVE_ADDR 0x41
 
 // I2Cで使うGPIO
 #define GPIO_SDA0 0
 #define GPIO_SCK0 1

図9 I2CとI2C Slaveライブラリの初期化

#include <stdio.h>
#include <pico/stdlib.h>
#include "hardware/i2c.h" ← hardware_i2cライブラリのヘッダーファイル
#include "pico/i2c_slave.h" ← I2C Slaveライブラリのヘッダーファイル
(略)
#include "ADRS2040U_i2c.h"
(略)
static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) ← I★★2C割り込みコールバック関数
{
(略)
}

void i2c_setup(void)
{
    i2c_init(i2c0, 100 * 1000); ← I★2★Cコントローラの初期化(通信速度100Kビット/秒)
    gpio_set_function(GPIO_SDA0, GPIO_FUNC_I2C); ← I★2★Cで利用するGPIOピンの設定
    gpio_set_function(GPIO_SCK0, GPIO_FUNC_I2C);
    // ADRS2040Uでは基板上でプルアップされているので
    // プルアップを無効化する
    gpio_disable_pulls(GPIO_SDA0);
    gpio_disable_pulls(GPIO_SCK0);
(略)
    // I2Cスレーブ初期化
    i2c_slave_init(i2c0, I2C0_SLAVE_ADDR, &i2c_slave_handler);
}

図10 コールバック関数「i2c_slave_handler()」

static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event)
{
(略) 
    switch(event) {
        // I2Cデータ受信
        case I2C_SLAVE_RECEIVE:
(略)
            break;
        
        // I2Cデータ要求
        case I2C_SLAVE_REQUEST:
(略)
            break;
        // STOP or RESTARTコンデション
        case I2C_SLAVE_FINISH:
            break;
        
        default:
            break;
    }
}

図11 ADCの初期化で呼び出す関数

adc_init();
adc_gpio_init(26);
adc_select_input(0);

図12 adc_fifo_setup()関数の引数パラメータ

adc_fifo_setup(
    en,
    dreq_en,
    dreq_threash,
    error_in_fifo,
    byte_shift
);

図13 ADCの受信FIFOバッファ割り込み処理のサンプルコード

uint16_t adc_buffer[1024];
static int p = 0;
(略)
// ADC FIFO割り込み関数
void adc_interrupt(void)
{
    // FIFOが空になるまでデータをadc_bufferに読み取る
    while(! adc_fifo_is_empty()) {
        adc_buffer[p++] = adc_fifo_get() & 0xFFF;
    }
}

void main(void)
{
(略)
    adc_fifo_setup(true, false, 1, true, false);
(略)
    // 排他的割り込みの設定
    irq_set_exclusive_handler(ADC_IRQ_FIFO, adc_interrupt);
    // 割り込みの有効化
    irq_set_enabled(ADC_IRQ_FIFO, true);
    // フリーランスタート
    adc_run(true);
(略)
}

図14 サンプリングレート設定のサンプルコード

int sample_rate = 1000;  // 1000 サンプリング/秒

adc_clk = clock_get_hz(clk_adc);
adc_set_clkdiv(adc_clk/sample_rate);

図15 リングバッファのサンプルコード

uint16_t buffer[0x100];
int write_pointer, read_pointer;

write_pointer = read_pointer = 0;

// バッファの読み出し
data = buffer[(read_pointer++) & 0xFF];
// バッファへの書き込み
buffer[(write_pointer++) & 0xFF] = data;

図16 クリティカルセクションの保護

mutex_enter_blocking(&my_mutex);

ここがクリティカルセクション

mutex_exit(&my_mutex);

図17 サンプルファームウエアのレジスタ番号定義(ADRS2040U_i2c.h)

// I2Cレジスタ
enum ADRS2040_CMD {
    ADRS2040_CMD_INVALID,       // 無効
    ADRS2040_CMD_ADC_START,     // ADCフリーラン開始
    ADRS2040_CMD_ADC_STOP,      // ADCフリーラン停止
    ADRS2040_CMD_SET_RATE,      // サンプリングレート設定
    ADRS2040_CMD_GET_COUNT,     // バッファデータ数
    ADRS2040_CMD_GET_VALUE,      // ADCデータ読み取り
};

図18 I2Cスレーブ動作時に使えるFIFOバッファ読み書き関数

// 1バイト読みだし
i2c_read_byte_raw(i2c_inst_t *);
// 指定バイト数の読み出し
i2c_read_raw_blocking(i2c_inst_t *, uint8_t *, size_t);
// 1バイト書き込み
i2c_write_byte_raw(i2c_inst_t *, uint8_t);
// 指定バイト数の書き込み
i2c_write_raw_blocking(i2c_inst_t *, uint8_t *, size_t);

図19 サンプルファームウエアのi2c_slave_handler()関数とその関連個所(main.cpp)

// ADCドライバ
ADC_Driver adcd;


typedef union {
    uint16_t d;
    uint8_t  b[2];
} I2C_WORD_t;


static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event)
{
    static uint8_t ADRS2040U_cmd = ADRS2040_CMD_INVALID;

    switch(event) {
        // I2Cデータ受信
        case I2C_SLAVE_RECEIVE:
            if(ADRS2040U_cmd == ADRS2040_CMD_INVALID) {
                ADRS2040U_cmd = i2c_read_byte_raw(i2c);
            }
            if(ADRS2040U_cmd == ADRS2040_CMD_ADC_START) {
                DEBUG_PRINT("ADC START\n");
                adcd.run(true);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;
            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_ADC_STOP) {
                DEBUG_PRINT("ADC STOP\n");
                adcd.run(false);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;

            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_SET_RATE) {
                I2C_WORD_t rate;
                i2c_read_raw_blocking(i2c, rate.b, sizeof(I2C_WORD_t));
                DEBUG_PRINT("Rate = %d\n", rate.d * 10);
                adcd.set_sample_rate(rate.d * 10);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;
            }
            break;
        
        // I2Cデータ要求
        case I2C_SLAVE_REQUEST:
            I2C_WORD_t sdata;
            sdata.d = 0;

            if(ADRS2040U_cmd == ADRS2040_CMD_GET_COUNT) {
                sdata.d = adcd.count();
            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_GET_VALUE) {
                int value = adcd.get_value();

                if( value >= 0) {
                    sdata.d = value & 0xFFF;
                }
                else {
                    sdata.d = 0xFFFF;
                }
            }
            i2c_write_raw_blocking(i2c, sdata.b, sizeof(I2C_WORD_t));
            ADRS2040U_cmd = ADRS2040_CMD_INVALID;

            break;
        // STOP or RESTARTコンデション
        case I2C_SLAVE_FINISH:
            break;
        
        default:
            break;
    }
}
(略)
int main(void)
{
    stdio_init_all();
    i2c_setup();
    while (true)
    {
        ;
    }
}

図20 受信FIFOバッファからデータを取り出すための前処理

i2c_hw_t *i2chw = i2c_get_hw(i2c0);
uint32_t datacmd = i2chw->data_cmd; // FIFOからデータを取り出す
uintu_t value = datacmd & I2C_IC_DATA_CMD_DAT_BITS; // 下位8ビットがデータ本体
if(datacmd & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) {
    // I2Cアドレスに続く最初の書き込みバイト
    // つまりレジスタ番号
}

図22 ADCへアクセスするサンプルプログラム(adcsample.py)

from smbus2 import SMBus

ADRS2040_ADDR=0x41
ADRS2040_CMD_INVALID      = 0
ADRS2040_CMD_ADC_START    = 1
ADRS2040_CMD_ADC_STOP     = 2
ADRS2040_CMD_SET_RATE     = 3
ADRS2040_CMD_GET_COUNT    = 4
ADRS2040_CMD_GET_VALUE    = 5

with SMBus(1) as i2c:
    # 500 spsで初期化・スタート
    self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50)
    self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START)

    while True:
        nod = 0
        nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT)
        for i in range(nod):
            value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE)
            print(value)

図23 ADCから取得したデータをグラフ化するサンプルプログラム(adctest.py)

from smbus2 import SMBus
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys
import time

# 定数
ADRS2040_ADDR=0x41
ADRS2040_CMD_INVALID      = 0
ADRS2040_CMD_ADC_START    = 1
ADRS2040_CMD_ADC_STOP     = 2
ADRS2040_CMD_SET_RATE     = 3
ADRS2040_CMD_GET_COUNT    = 4
ADRS2040_CMD_GET_VALUE    = 5

class ADCGraph:
    def __init__(self):
        # グラフウィンドウ
        self.win = pg.GraphicsWindow()
        self.win.setWindowTitle('ADC Input')
        self.plt = self.win.addPlot()
        self.plt.setYRange(0,1024)
        self.curve = self.plt.plot(pen=(0, 0, 255))

        # グラフデータを用意
        self.data = np.zeros(100)

        self.i2c = SMBus(1)
        # 500 spsで初期化・スタート
        self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50)
        self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        # 10msごとに更新
        self.timer.start(10)

    # グラフ更新
    def update(self):
        nod = 0
        nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT )
        for i in range(nod):
            value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE)
            self.data = np.delete(self.data, 0)
            self.data = np.append(self.data, value)
        self.curve.setData(self.data)

if __name__ == "__main__":
    graphWin = ADCGraph()

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()