Shujima Blog

Apple製品,技術系の話をするブログ

Arduino DueのPWMデューティをより細かく変える(analogWriteResolution)

環境

Arduino DueのUnoに比べてはるかに柔軟に決められます.

その一つがanalogWriteResolution()関数です.

プログラム

void setup() {
pinMode(11,OUTPUT);
analogWriteResolution(12);
}

void loop() {
analogWrite(11,2047);
}

analogWriteResolution()関数のおかげで,PWMのデューティを12ビットで決めることができるようになります.12ビットの符号なし整数がとる値は0〜4095ですので,上記では2047を指定することでデューティを50%に設定しています.

その他

また,上記に加えて,PWMの周波数も変更できます(こちらはプログラム中ではなく,IDEの設定を変更する必要がありますが...).

masa-flyu.hatenablog.com

 

Arduino Due で高分解能なエンコーダを読み取ってみた

f:id:masa_flyu:20180729020326j:plain

概要

1周512[PPR](2048[counts/rev])のエンコーダを読み取って,シリアル通信で出力してみました.結論から言えば,150000[counts/s]くらいまでなら読み取れそうです.ただし,他になんの処理も挟んでいない上,1個だけの場合ですので,実際に使う場合には,10分の1くらいに抑えるのが無難じゃないでしょうか.

単位はこちらに準拠:エンコーダのPPR、CPR、LPRとは何ですか? | CUI Inc

環境

※カタログには512カウントとありますが1回転あたりのパルス数(PPR)が512です.なので,これ以降では512[PPR]=2048[Cycles/rev]として話を進めます.

高いですが,ごく普通の高分解能エンコーダです.A相B相の+側をArduinoのdigitalに,-側をGNDにつないで,電源として5Vを加えています.

Arduino DueのIOは3.3V入力ですが,エンコーダの5V出力を直接つなぎました.推奨しません.

プログラム

以下のプログラムは以下のURLにあるタイマ割り込みプログラムを使用しています.

DUEでのタイマー割り込みメモ

//このプログラムは Arduino DUE 用です

//エンコーダピン番号
#define ENC_A 2
#define ENC_B 3

void itr_A(); //割り込み関数
void itr_B(); //割り込み関数
void TC3_Handler();
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t mSec);

long count=0;
long recentcount = 0;

void setup() {
  //エンコーダの2つのピンを入力に
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);

  //シリアル通信(USB)の設定
  Serial.begin(115200);
  
  //入力ピンが変化した時に割り込みを入れる
  attachInterrupt(ENC_A, itr_A, CHANGE);  //Aピンが変化した時itr_A()を呼び出す
  attachInterrupt(ENC_B, itr_B, CHANGE);  //Bピンが変化した時itr_B()を呼び出す

  //タイマー(シリアル通信用)
  //http://jtakao.web.fc2.com/elec/due_timerinterrupt/index.html
  NVIC_SetPriority((IRQn_Type)TC3_IRQn,1);  //TC3タイマーの優先順位を1に設定(普通は0なので優先度を下げたことになる)。エンコーダ読み取りを優先させるため。
  startTimer(TC1, 0, TC3_IRQn, 100);  //TC3タイマーを100msecに設定

}


void loop() {
  //ループでは何もしない(割り込みが呼ばれるだけ)
}


/**********割り込み***************/

//Aピン割り込み関数
void itr_A(){
  count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;
}

//Bピン割り込み関数
void itr_B(){
  count += digitalRead(ENC_A) == digitalRead(ENC_B) ? 1 : -1;  
}

//タイマー割り込み関数 100msecごとに呼び出される
void TC3_Handler() {
  TC_GetStatus(TC1, 0);  //時間のカウントを0に戻す
  Serial.print(count);  //エンコーダのカウントをシリアル送信  
  Serial.print("[counts]\t\t");  
  Serial.print( (count - recentcount) * 10);
  Serial.println("[counts/s]");
  recentcount = count;
}


/**********タイマー用関数***************/

//http://jtakao.web.fc2.com/elec/due_timerinterrupt/index.htmlからのコピペ
//タイマーを使いやすくしてくれているらしい
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t mSec) {
  pmc_enable_periph_clk((uint32_t)irq);
  TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1);
  uint32_t rc = (VARIANT_MCK/2/1000)*mSec;
  TC_SetRC(tc, channel, rc);
  TC_Start(tc, channel);
  tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
  tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
  NVIC_EnableIRQ(irq);
}

プログラム解説

タイマー

DUEでのタイマー割り込みメモ startTimer()関数はこちらのサイトをコピペして,タイマー割り込みを用いています.

setup()内でstartTimer(TC1, 0, TC3_IRQn, 100);のように呼び出すことで,TC1ブロックの0番=TC3ユニットのタイマを用いて100msをカウントしています.カウント毎にシリアルを送っていてはエンコーダIOピン割り込みの時間が長くなってしまい,次の割り込みに間に合わなくなるので,100msに1回だけタイマーでシリアルを送るようにしました. この割り込みが反応するとTC3_Handler()関数が呼び出されます.

また,NVIC_SetPriority((IRQn_Type)TC3_IRQn,1);の1行を加えることで,タイマー割り込みの優先順位をエンコーダより下げているので,エンコーダIOピン割り込みには影響しないはずです.

エンコーダIOピン割り込み

attachInterrupt()関数を用いて,IOピン割り込みを入れています.その際,A相ピンとB相ピンで割り込み先の関数を変えています. A相が0または1から1または0に変化するとitr_A()が,B相が変化するとitr_B()が実行されます.

方向の判断・加減算

ここが一番難解でしょう.

エンコーダのA相,B相の波形のずれを利用して回転方向を判断します.

正回転の場合にカウントを増加,負回転の場合にカウントを減少させます.

A相を例にとって説明します.

  count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;

A相,B相の波形を示します. f:id:masa_flyu:20180729013801p:plain 縦軸はそれぞれの相の出力(0または5V),横軸は回転角度です. 正回転であれば横軸の右へ進み,負回転であれば横軸の左へ進むものとします.

18行目でA相の割り込みを以下のように設定しています.

  attachInterrupt(ENC_A, itr_A, CHANGE);  //Aピンが変化した時itr_A()を呼び出す

第3引数をCHANGEに設定することで,A相の入力になんらか変化が生じたときに割り込みが生じるように設定しています.

つまり,A相の割り込みが発生するのはA相に変化が生じる赤い線の場所になります. f:id:masa_flyu:20180729013833p:plain

割り込みが生じたすぐあとは,正回転であれば赤線の右側,負回転であれば赤線の左側にいるはずです.回転方向を判別してカウントを+1または-1するためには,左右のどちらにいるかをA相B相の値から判断しなければなりません. 赤線の左右でのA相B相の値を書き込みました. f:id:masa_flyu:20180729013952p:plain このとき,A相とB相の値が一致しているものを青,不一致のものを黄色で表しました.すると見事に,赤線の左は青,右は黄色に塗り分けられました. つまり, A=Bのとき-1,A≠Bのとき1のように判別してカウントすることができるわけです. f:id:masa_flyu:20180729014203p:plain それをC言語で表したものが

count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;

です,三項演算子を使って,A==Bのとき-1を,A!=Bのとき1をcountに加算しています.

なお,B相のときは青と黄色の関係が真逆になるので,1と-1を反転しています.f:id:masa_flyu:20180827150756p:plain

測定結果

上記のプログラムを走らせた状態で,Arduinoのシリアルモニタを見ながらエンコーダを動かしてみました.下図は高速に往復させた時の値の変化です.

f:id:masa_flyu:20180729015203p:plain

最大で150000[counts/s]くらいの値が確認できますね.

一定回転を高速に往復させ続けたところ,150000[counts/s]程度であれば理論上のカウント数とおよそ一致しました. より高速に回転させたところ,最大で200000[counts/s]くらいを計測しましたが,その際には,累積のカウント数が理論上の値と大幅にずれてしまいました. 正直,計測方法がとても雑なので,自信を持っては言えないのですが,150000[counts/s]までなら測定できそうです. わりと実用的じゃないでしょうか?少なくともArduinoUnoよりはるかに実用的です.

今後の展望

以上のプログラムは,Arduino標準の関数を用いて実装しました.しかしながら,マイコンのレジスタを直接操作するなどすれば,より早い実装が可能かもしれません.

Arduino DueのDACで正弦波(sin)を出してみた

環境

プログラム

double i=0.0;
void setup() {
analogWriteResolution(12);
}

void loop() {
analogWrite(DAC0, 2048 + 2048 * sin(i));
i+=0.01;
}

実験結果

実際にDACから出力して,オシロで観察してみます.

f:id:masa_flyu:20180728233505j:plain
およそ24Hz (5ms/div)
非常に滑らかな正弦波となりました.±45度が直線すぎる気もしますが...

iの増加量を0.1に増やすと,

f:id:masa_flyu:20180728233934j:plain
およそ240Hz (1ms/div)

などとなりました.段差が見えてきていますね.

ちなみに電圧ですが,共に0.5V/divで表示していますので,およそ2.1V (ピークtoピーク)となります. 3.3Vに比べて低くなってしまうようです.

電圧についてはこちらで検証されていました.

playfulchappy.blogspot.com

Arduino DueのPWM周波数を変える mac版(variant.h編集)

こちらを参考にmacでやろうとして戸惑ったので.

http://anbalab.com/summary/duepwm

環境

variant.hのディレクトリ

ここにありました!

~/Library/Arduino15/packages/arduino/hardware/sam/1.6.12/variants/arduino_due_x/

※~は/Users/<ログインユーザ名>/を指す省略表記です ※Arduino15,1.6.12の数字はバージョンにより異なります.自分のパッケージのバージョンを確認してください

1.6.12の場合は上のディレクトリをファインダーでShift+Command+Gで出てくる「フォルダの場所を入力」機能でジャンプできます.

f:id:masa_flyu:20180728222632j:plain

PWMの周波数を変える手順

variant.hを開きます. 220行目あたりをみますと,

//以下はvariant.hの220行目から

/*
 * PWM
 */
#define PWM_INTERFACE      PWM
#define PWM_INTERFACE_ID   ID_PWM
//#define PWM_FREQUENCY        1000
#define PWM_FREQUENCY      4000
#define PWM_MAX_DUTY_CYCLE 255
#define PWM_MIN_DUTY_CYCLE 0
#define PWM_RESOLUTION     8

/*
 * TC
 */
#define TC_INTERFACE        TC0
#define TC_INTERFACE_ID     ID_TC0
//#define TC_FREQUENCY        1000
#define TC_FREQUENCY        4000
#define TC_MAX_DUTY_CYCLE   255
#define TC_MIN_DUTY_CYCLE   0
#define TC_RESOLUTION      8

のようにdefine定義が連なっています.

このうち,PWM_FREQUENCYTC_FREQUENCYを変えたいPWM周波数[Hz]に書き換えます. 私の場合は226行目と236行目でした. 念の為,書き換える前のコードをコメントアウトして残しました.

あとはこれを保存さえすれば,再起動などをしなくても,書き込みなおすだけで,周波数が変更されます.

ちなみに,PWMのデューティもより細かく変更できます

masa-flyu.hatenablog.com

どこまで周波数を高くできるか

このクソみたいなプログラムで実験してみました.

//このプログラムは Arduino DUE 用です
void setup() {
  analogWrite(11,100);
}

void loop() {
}

今回,PWMを変更する目的は,モータを動かすときの雑音が気になったからです. 可聴域の上限である20kHzまで上げれたら御の字でしょう.

というわけで実験開始

続きを読む

USB-CをMagSafe化するケーブルはやめるべき

たとえばこんなの.

Macを壊したくないならお勧めできません.

実例

2018-02-22 買ったばかりのMacBook Proを一発でロジックボード交換に追いやった恐ろしいアイテムabflyblog.wordpress.com

タイトルからして恐ろしいですね.

USB-C界隈で有名なはんぺん氏のブログにこちらの製品のレビューが載っています.

hanpenblog.com

危険な理由

これは推測にすぎませんが,原因はGNDピンが飛び出していないことと考えています.

GNDが最初に接触しない

MagSafeをはじめとするあらゆるコネクタはGND(接地線)が最初に接触するように設計されています. これは,接続する両者の基準電位を取るためです. より簡単にいえば,静電気をあらかじめ逃しておくためです.

上記で槍玉に挙げた製品を見る限り,Vcc,GND,CC×2ピンが全て同じボゴピン(端子)で接続されるように見えます. このうちCCピンはその用途から,内部は半導体の入力に直結していると考えられます(抵抗くらいはあるかも?).半導体の入力は静電気に極めて弱く,たとえ弱くても静電気を一度浴びせれば簡単に壊れてしまいます. これを防ぐためにボゴピンの高さに変化をつけ,GNDが確実に最初に接触するようにすべきです.

周囲もGNDなので安全?

ただし,この製品の場合は周囲の磁石の部分のGNDとして接続されているように推測されます(GND1ピンに対してVcc2ピンなので).つまり,この部分が最初に当たればCCに過大な電圧が加わることはないと考えれます. コネクタが完全にまっすぐ挿入された場合,CCピンが最初に接触する可能性があり,端子が死にます. コネクタが斜めから挿入された場合には,GNDの磁石が最初に接触することになるため,静電気が理由での故障はなくなると考えられます.

もちろん確証はありませんし,あってるかどうかも自信がありませんが, どうしても使いたい人は斜めから挿入すべきではないでしょうか.

他にも問題はある

はんぺん氏が指摘しているように,超強力な磁石が起因の問題の可能性もあります. また,どうせ安物のボゴピンを用いているので,勢いよく接続した瞬間のチャタリングもかなり大きいと予想されます.これも問題になる恐れはあります.

例外...?

ちょっと調べてたらこんな記事を見つけました

weekly.ascii.jp

あれ,これはちゃんとしてそう...?

左右端4ピンだけより大きなボゴピンを使っています.

これらがGNDなら,確実にGNDが接触する設計のようです.

Amazon含めネットショップでは見つかりませんでした.

問題を認識しての販売中止でしょうか.

当ブログをご利用いただく際には免責事項をお読みください。