Raspberry pi で AP のレジスタをC言語で直接変更してモーターを PWM で操作する

目的

PWM でDCモータを操作します。 また、フルブリッジのモータードライバICを使います。

注意点

Linuxシステムコールを含む(mmap関数)ので、root 権限が必要なため sudo コマンドを利用する必要があります。 プログラム名を sample.c とするならば、

 $ gcc sample.c
 $ sudo ./a.out

のように実行してください。

モータドライバIC

TA7291P を使いました。

モータードライバー TA7291P (2個入): 半導体 秋月電子通商 電子部品 ネット通販

端子記号 端子番号 端子説明
GND 1 GND
OUT1 2 出力端子
NC 3
Vref 4 制御電源端子
IN1 5 入力端子
IN2 6 入力端子
VCC 7 ロジック側電源端子
VS 8 出力側電源端子
NC 9
OUT2 10 出力端子

回路の概要

本当は RaspberryPi 3 を使っています。

f:id:katogiso-tech:20171112200721p:plain

main() プログラム

正転と逆転をGPIO20/21 を使って切り替えています。

void main()
{
  volatile unsigned *addr;
  int count;

  //--------------------------
  // GPIO
  gpio = io_mapping(GPIO_BASE);

  // set GPIO18 mode to ALT5(PWM0)
  *(gpio + 0x4/4) = (*(gpio + 0x4/4) & ~(0x7 << 24 )) | (0b010 << 24);

  // set GPIO20/21 mode to Output
  *(gpio + 0x8/4) = (*(gpio + 0x8/4) & ~(0x7 << 0 )) | (0b001 << 0);
  *(gpio + 0x8/4) = (*(gpio + 0x8/4) & ~(0x7 << 3 )) | (0b001 << 3);

  //--------------------------
  // Clock
  clk  = io_mapping(CLK_BASE);
  // add the offset address of pwm clk manager
  clk += 0xA0/4;
  // disable
  *clk = CLK_PASSWD + (0x1<<5);
  // divided by 192
  *(clk+0x4/4) = CLK_PASSWD + (192 << 12);
  // choose oscillator(19.2MHz) as source
  *clk = CLK_PASSWD + 0x1 + (0x1 << 4);

  //--------------------------
  // PWM
  pwm  = io_mapping(PWM_BASE);
  *pwm = 0; // Disable
  usleep(10);

  *(pwm + 0x10/4) = 8; // set RNG
  *(pwm + 0x14/4) = 6; // set DAT
  *pwm = 0x1;  // Enable

  //--------------------------
  // drive motor
  for( count = 0; count < 2; count++ ){
    // set
    if( count % 2 ){
      *(gpio + 0x1C/4) = 0x1 << 20;
    } else {
      *(gpio + 0x1C/4) = 0x1 << 21;
    }
    sleep(3); 

    // clear
    *(gpio + 0x28/4) = 0x1 << 21;
    *(gpio + 0x28/4) = 0x1 << 20;
    sleep(3); 
  }

 *pwm = 0; // Disable
  usleep(10);
}

電圧、電流波形

ChannelA が電圧(PWM)で、ChannelB は電流波形です。 また、電流波形は1Ωの抵抗両端で測定しました。

f:id:katogiso-tech:20171112201431p:plain

Raspberry pi で AP のレジスタをC言語で直接変更して PWM を操作する

やること

Raspberry Pi 用の特別なライブラリとドライバを利用せずにPWMをC言語で操作する。

注意点

Linuxシステムコールを含む(mmap関数)ので、root 権限が必要なため sudo コマンドを利用する必要があります。 プログラム名を sample.c とするならば、

 $ gcc sample.c
 $ sudo ./a.out

のように実行してください。

main()

IC 18pin (RaspberryPi の端子だと 12pin) をALT5モードにして、PWMを発生させる。 PWM のサブモードは

  1. ある期間(M, RNG)内で、バランスよく N(N, DAT) 回 Highとなる
    • サイトによっては balanced mode と記載あり
    • 仕様書にはPWM algorithm
  2. ある期間(M, RNG)内で、最初に N(N, DAT) 回 Highとなる
    • サイトによっては legacy mode と記載あり
    • 仕様書には M/S transmission

の2つあります。 正式な名称は不明で、完全な仕様書を読むと記載があるのかもしれません。

実際の波形

上記のモードだけを切り替えた波形を参考に添付します。 他の設定値は同じです。

balanced mode

f:id:katogiso-tech:20171112170346p:plain

legacy mode

f:id:katogiso-tech:20171112170350p:plain

サンプルコードではクロックのソースを Oscillator(19.2MHz)にして これをもとに 192 分周して100kHzにしています。 また、PWM algorithm( balanced mode ) です。

void main()
{

  //------------------------------------
  // set GPIO18 mode to ALT5(PWM0)
  //------------------------------------
  gpio = io_mapping(GPIO_BASE);
  *(gpio + 0x4/4) = (*(gpio + 0x4/4) & ~(0x7 << 24 )) | (0b010 << 24);

  //------------------------------------
  // set source clock 
  //------------------------------------
  clk  = io_mapping(CLK_BASE);
  clk += 0xA0/4;
  // Oscillator 19.2MHz
  *clk         = CLK_PASSWD + (0x1<<5);
  *(clk+0x4/4) = CLK_PASSWD + (192 << 12);
  *clk         = CLK_PASSWD + 0x1 + (0x1 << 4);

  //------------------------------------
  // set pwm
  //------------------------------------
  pwm = io_mapping(PWM_BASE);
  *pwm = 0;   // Disable
  usleep(10); // wait for stable 

  *(pwm + 0x10/4) = 4; // set RNG
  *(pwm + 0x14/4) = 2; // set DAT

  *pwm = 0x1; // Enable 
}

物理アドレスのアクセス用マッピング

過去記事を参照してください。

katogiso-tech.hatenablog.com

使っているレジスタ

Clock 関連

ADDRESS Name Description Size Read/Write
0x3F10_1000 CM_PWMCTL PWM Clock Control 32 R/W
0x3F10_1004 CM_PWMDIV PWM Clock Divisors 32 R/W
CM_PWMCTL

名前は類推で、記載を見つけられず WiringPi ソースコードから該当アドレスを確認しました。 ただし、CM_GPnCTL レジスタの記述があり、レジスタの操作は仕様書が参考になります。

Clock 系のレジスタへの書き込みはbit[31:24]に0x5a を同時に書き込む必要があります。

使っている bit field は

  • SRC bit[3:0]

    • 0 : GND
    • 1 : oscillator
    • 4 : PLL
    • 他にも色々ある
  • ENAB bit[4]

    • 1 : Start
    • 0 : Stop
  • KILL bit[5]

    • 1 : stop and reset
    • 0 : no action

です。

CM_PWMDIV

名前は類推で、記載を見つけられず WiringPi ソースコードから該当アドレスを確認しました。 ただし、CM_GPnDIV レジスタの記述があり、レジスタの操作は仕様書が参考になります。

クロックの分周を決めるレジスタです。

使っている bit field は

  • DIVI bit[23:12]
    • Integer part of divisor

PWM 設定関連

ADDRESS Name Description Size Read/Write
0x3F20_C000 CTL PWM Channel1 Range 32 R/W
0x3F20_C010 RNG1 PWM Channel1 Range 32 R/W
0x3F20_C014 DAT1 PWM Channel1 Data 32 R/W

CTL

仕様書からの抜粋です。 初期値はどれも 0x0 で、 bit0(Enable) と bit7(PWMのモード切り替え) を使えばほぼ大丈夫なはず。

bit# Name Description Read/Write
7 MSEN1 Channel 1 M/S Enable
0: PWM algorithm is used
1: M/S transmission is used.
RW
4 POLA1 Channel 1 Polarity
0 : 0=low 1=high
1: 1=low 0=high
RW
1 MODE1 Channel 1 Mode
0: PWM mode
1: Serialiser mode
RW
0 PWEN1 Channel 1 Enable
0: Channel is disabled
1: Channel is enabled RW 0x0
RW

RNGn

PWM の期間をこのレジスタ値で分割します。 32 未満の値で設定してください。

DATn

RNGn で設定した期間のうちHiとする回数を設定できます。

全容

RaspberryPi pwm sample

Raspberry pi で AP のレジスタをC言語で直接変更して GPIO を操作する

やること

Raspberry Pi 用の特別なライブラリとドライバを利用せずにGPIOをC言語で操作する。

その他の普通の手段

注意点

Linuxシステムコールを含む(mmap関数)ので、root 権限が必要なため sudo コマンドを利用する必要があります。 プログラム名を sample.c とするならば、

 $ gcc sample.c
 $ sudo ./a.out

のように実行してください。

main()

IC 20pin (RaspberryPi の端子だと 38pin) を操作して、High/Low をずっと繰り返す。

アドレスをいちいち addr/4 していますが、仕様書に記載されている byte アドレスを 32bit(4byte)アドレスに変換しているためです。 C言語の int は今回 32bitなので、アドレスを保持しているポインタに 1 を加算すると 32bit 分ポインタがずれます。

void main()
{
  int count;
  volatile unsigned *addr;

  gpio = io_mapping(GPIO_BASE);

  // set GPIO20 to output mode ( bit[2:0] ). offset is 0x8( byte address ) .
  *( gpio + 0x8/4 ) = (*( gpio + 0x8/4 ) & ~(0x7)) | (0x1 << 0);

  for( count = 0 ; ; count++){
    usleep(1000); 
    addr = ( count % 2 ) ? gpio + 0x1C/4 :  gpio + 0x28/4 ;
    *addr = 0x1 << 20;
  }
}

物理アドレスのアクセス用マッピング

過去記事を参照してください。

katogiso-tech.hatenablog.com

使っているレジスタ

ADDRESS Name Description Size Read/Write
0x3F20_0008 GPFSEL2 GPIO Function Select 2 32 R/W
0x3F20_001C GPSET0 GPIO Pin Output Set 0 32 W
0x3F20_0028 GPCLR0 GPIO Pin Output Clear 0 32 W

GPFSELn

SoC の端子は色々な機能を切り替えられるようになっていて、 RaspberryPi も同じようになっている。 今回は output モードに切り替えていて、GPFSEL2 の bit[2:0] に 1 を書き込んでいます。

GPSETn

Write Only のレジスタで、1 の書き込みで対応するGPIOが High になる。

GPCLRn

Write Only のレジスタで、1 の書き込みで対応するGPIOが Low になる。

その他

レジスタの値をそのまま反映する GPLEVn もあるが、他のGPIOの値を含めてレジスタReadして対応するbitだけ変更する必要がり(Read modified write)、少し手数が増えて複雑となります。

全容

RaspberryPi gpio sample

Raspberry pi で AP のレジスタを操作する前に

Linux からのレジスタ操作

仮想アドレス

OS上の通常のプログラムはハードウェアのアドレス(物理アドレス)からは隔離されていて、 仮想的なアドレス空間上で動作しています。 その仮想アドレスがOSとHW(MMU)で変換されて、一部が物理アドレスです。

そのため、AP の仕様書に記載されている物理アドレスへアクセスするにはひと工夫が必要です。

mmap()

Linuxシステムコールmmap()を使って、物理アドレスへアクセスすることができます。

Man page of MMAP

今回は SRAM の開始アドレスが仕様書から 0x0000_0000 であることがわかるので、 これを基準にしてGPIOなどへアクセスします。

mmap() の概要

物理アドレスをプログラムの仮想アドレス上に割り当てることができます。 (これ以外の目的に使うこともあります。) ただし、ファイルシステムや、HW の制約により Block/Pase size に気をつける必要があります。 Block/Page size は予め決められたアドレスマップ上の区切りのことです。

サンプルコード

volatile unsigned * io_mapping(int base_addr)
{
    char *gpio_mem, *gpio_map;
    
    if (!mem_fd) {
      mem_fd = open("/dev/mem", O_RDWR|O_SYNC);
      check((mem_fd < 0), "can't open /dev/mem \n");
    }
    
    gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1));
    check( ( gpio_mem == NULL), "allocation error \n");

    // Make sure pointer is on 4K boundary
    if ((unsigned long) gpio_mem % PAGE_SIZE)
      gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);
    
    // Now map it
    gpio_map = (char *)mmap((caddr_t)gpio_mem, 
                            BLOCK_SIZE, 
                            PROT_READ|PROT_WRITE, 
                            MAP_SHARED|MAP_FIXED, 
                            mem_fd, 
                            base_addr );

    check( ((long)gpio_map < 0), "mmap error \n");

    return (volatile unsigned *)gpio_map;
}

io_mapping() は、 SRAM のアドレスを基準にして、アクセスしたい領域のベースアドレスをオフセットにして 物理アドレスの割当用に確保した仮想アドレス空間gpio_memを戻り値とします。

Raspberry pi の AP に関して

Broadcom 2835

Raspberry pi のAP(CPU)公式ドキュメント

場所が少しわかりにくいのでリンクを記載します。 Top ベージからは、"Help" > "Documentation" > "Hardware" > "Raspberrypi" です。

www.raspberrypi.org

ここから "BCM2835" > "Peripheral specification" にあります。

このドキュメントの課題

  1. 情報が不完全で必要としている情報も欠落している
  2. 誤記もある
  3. AVR のドキュメントと比較すると随分とわかりにくい

レジスタのアドレスなど参考になるサンプルコード

wiring Pi

wiringpi.com

このライブラリの作者はいろいろなFAQ/Forumで回答者としてアドバイスされています。 細かいことを Google 検索するとかなりの確率でお世話になる。

調べてわかったこと(備忘録)

Peripheral Base Address ( Physical )

上記公式ドキュメントと

https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf

で確認できます。

PWM pin

BCM2835 で GPIO18 を使って、モードは ALT5 にする。

PWM モード

2つのモードがある。

PWM algorithm

単位期間内のHigh/Low Cycle をバランスよく配置する。 DCモータの駆動のように平均電圧をなんとかするだけなら、こちらで使用した方が良いかもしれない。

MS algorithm

単位期間内のHigh/Low cycle が各々固まっている。 単位期間内のHigh期間が終わってから、Low 期間となる。 サーボモータの回転角を PWM 波形で制御する場合はこちらが制御しやすいだろうと思う。

Clock source

PWM のカウンタクロックは主に下記の通りとなる。 ただし、PLL の周波数はOSで設定されている通りなので、 下記周波数は調査する必要がある。

  • Oscillator = 19.2MHz
  • PLLA = 500MHz?

BME280 with C-lang and Raspberry Pi

目的

Raspberry Pi 上の Linux から i2c バス経由で、 BME280からデータを C言語のプログラムで取得する。

この記事の続きです。 katogiso-tech.hatenablog.com

BME280 センサ

秋月電子のモジュールキットを利用する。

akizukidenshi.com

使い方

 $ rake
 $ chmod 776 ./bme280
 $ ./bme280

ソースコード

参考文献

以下の情報を参考にしています。

Bosch Sensortec ソースコード

猫ぱーんち! Raspberry Pi でI2C: C言語プログラミング

今回のソースコード

bme280.git

概要

詳細は後日記載する予定です。

Bosch のサンプルプログラムを全面的に整理して、 Raspberry Pi 上の Linux で動かしました。

BME280 を Forced Mode で動かして、気温、湿度、気圧データを取得しています。 main.c を見るとおおよその流れがわかると思います。

openBD (https://openbd.jp/) 書影

前置き

ここで言及するのは日本の書籍情報を取得できる API であるところの openBD です。JAVAのライブラリの方ではありません。

画像(書影)を表示する。

ソース

<div>
  <img id="output"></img>
</div>


<script>
function clickGet(){
  genRequest( uri, function(resp){
    var obj = JSON.parse(resp);
    var src = obj[0]["onix"]["CollateralDetail"]["SupportingResource"]["ResourceVersion"][0]["ResourceLink"];
    var alt = obj[0]["onix"]["DescriptiveDetail"]["TitleDetail"]["TitleElement"]["TitleText"]["content"];

    var img_tag = document.getElementById("output");
    img_tag.setAttribute('src', src);
    img_tag.setAttribute('alt', alt);
  });
}

function genRequest( uri, callback){
  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function(){
    if (this.readyState==4 && this.status==200) {
      callback(this.response);
    }
  };

  xhr.responseType = 'json';
  xhr.open('GET',uri,true);
  xhr.send();
}

var uri = "https://api.openbd.jp/v1/get?isbn=4873113636?pretty";

</script>

脱"変なリンク"

openBD に書影が存在すれば気軽に画像がブログなどに表示できる。 それが第三者の販売サイトとは独立に利用できるのが良い。