アーカイブ
IOIO for Androidのアナログ入出力を試した
IOIOの10bit ADコンバータと10kHz PWM出力を試した。
今回のコード https://github.com/shokai/android-ioio-study/tree/master/AnalogInOut
Androidの画面上のシークバーを操作すると、PWMによる擬似的なアナログ出力でLEDの明るさを調整できる。
ブレッドボード上のCdSで明るさを計測して、Androidの画面上のシークバーの長さで表示したりした。

動画
■準備
まず久しぶりに秋葉原に行ったら秋月が改装工事中だったので、他の店を見て回った。IOIOの開発者が使っているピンヘッダを千石で発見したので買った。

このピンヘッダ、みっしりと敷き詰めるように装着できる。いつも使ってるメスピンヘッダは隣のピンにぶつかるので敷き詰められない。

これでブレッドボードに線を引き出す準備が整いました
■回路を作る
ブレッドボードに5Vか3.3Vと、GNDを引き出す。
4番ピンでデジタル入力を試すために、タクトスイッチを通して5Vを流し込む。IOIO内部でGNDにプルダウンするので直結で良い。ただし、Read This Before Connectingに書いてある通り5Vを入れて良いピンは決まっているのでよくわからなかったらブレッドボードに出すのは3.3Vにしておいた方が良い。
3番ピンをLEDに接続する。PWMを試すため。
アナログ入力は40番ピン周辺に16個ある。45番ピンにCdSをつけ、10kΩで分圧した。

■プログラムを書く
書いた。
AnalogInOutActivity.java
1からAndroidプロジェクトでIOIOを使う方法は前の記事を参考にされたし
このへんのドキュメント参考になる。
- Wiki: 'Digital I/O' for ytai's IOIO – Codaset
- Wiki: 'Analog Input' for ytai's IOIO – Codaset
- Wiki: 'PWM Output' for ytai's IOIO – Codaset
重要なのはAnalogInOutActivity.javaの49行目あたり。
class IOIOThread extends AbstractIOIOActivity.IOIOThread{
private DigitalOutput led;
private DigitalInput btn;
private PwmOutput pwm;
private AnalogInput ain;
protected void setup() throws ConnectionLostException{
led = this.ioio_.openDigitalOutput(0, true);
btn = this.ioio_.openDigitalInput(4, Mode.PULL_DOWN);
pwm = this.ioio_.openPwmOutput(3, 1000); // 1000hz
ain = this.ioio_.openAnalogInput(45);
}
protected void loop() throws ConnectionLostException{
try{
led.write(!btnLed.isChecked());
if(btn.read()) seekBarDigitalIn.setProgress(1);
else seekBarDigitalIn.setProgress(0);
pwm.setDutyCycle((float)seekBarPwm.getProgress() / seekBarPwm.getMax());
seekBarAnalogIn.setProgress((int) (ain.read() * seekBarAnalogIn.getMax()));
sleep(10);
} catch (InterruptedException e) {
}
}
}
DigitalInputはMode.PULL_DOWNにしておく。PwmOutputは1kHzに設定した(最大10kHz)
PWMもAnlogInputも、0〜1の間のfloat値で入出力値を扱う。Androidの画面のSeekBarは最大値を1000にしておいたので、IOIO上での値を1000倍してintにcastすればSeekBarに表示できる。
デジタル入力の状態をAndroidの画面上に表示する時、IOIOThreadからUI Threadを操作するとデッドロックが起きてアプリが停止するが、なぜかSeekBarはデッドロックが起きないので長さ1の小さなSeekBarを作ってデジタル値を表現するように工夫した。
ほんとうは「android.os.Handlerクラスのpost()を使え」、とyoggyさんが言っていたので今度やってみます。
PSoC – CY8C29466の14ビットADコンバータを内蔵マルチプレクサで切り替えて複数ピンで使う
14ビットのADコンバータ1つと、AMUX4(4チャンネルアナログマルチプレクサ)を組み合わせると複数のピンからAD変換が使える。
というかPSoC Designer4.xでADCINC12を使っていた頃は、ADCINC12を複数置くことができたのにPSoC Designer5.0では1つしか配置できなくて困った。
でもよく考えたらマルチプレクサ使う方が自然だな。
アナログブロックの接続
マルチプレクサを切り替えるとPort0の0,2,4,6番ピンに接続できる

ピン配置
右上のADC以外は関係ない

PGAの設定
1.0倍で、AMUXがあるアナログマルチプレクサ1に接続する。

ADCINCの設定
Negative Inputのソースは一応指定するが、GainをDisconnectedにすると使わないようにできる。

AMUX4の設定
Interconnect Viewには表示されないけど、配置はされてる。アナログマルチプレクサ1として設定する。

AMUX4の他にも8もあるので、まあたくさん増やせる。
UARTのTX8で、14bitADCINCの値を送る。
TX8だけを使う設定は橋本商会 ≫ PSoC – TX8モジュールに書いた
void wait(int n){
while(n--);
}
// AMUX4_PORT0_0 => 0x00
// AMUX4_PORT0_2 => 0x01
// AMUX4_PORT0_4 => 0x02
// AMUX4_PORT0_6 => 0x03
int get_adc(BYTE amux_channel){
AMUX4_InputSelect(amux_channel);
wait(10);
ADCINC_GetSamples(0);
while(!ADCINC_fIsDataAvailable());
return ADCINC_iClearFlagGetData();
}
int ad;
BYTE ad_pin;
char buf[6];
void main(void){
M8C_EnableGInt;
AMUX4_Start();
PGA_1_Start(PGA_1_HIGHPOWER);
ADCINC_Start(ADCINC_HIGHPOWER);
TX8_Start(TX8_PARITY_NONE);
for(;;){
for(ad_pin = 0; ad_pin < 4; ad_pin++){
ad = get_adc(ad_pin);
TX8_PutChar(ad_pin+'0');
TX8_CPutString(":");
TX8_PutString(intToStr(ad,buf));
TX8_PutCRLF();
}
}
}
AMUX4を切り替えた直後にAD変換を開始するのがなんとなく嫌だった(物理的に回路が切り替わっているわけだし)ので、一瞬waitを入れてからAD変換するようにした。string.hのitoa関数がおかしいので代わりを作った
// intの桁数を返す
char getDigit(int n){
char i;
i = 0;
while(n>0){
n /= 10;
i++;
}
return i;
}
// int->String変換
// char buf[6]
char *intToStr(int n, char *buf){ // 変換する数、作業領域
int i, digit;
digit = getDigit(n); // 桁数
for(i = digit-1; i >= 0; i--){ // intは最大5桁
buf[i] = n%10+'0';
n /= 10;
}
buf[digit] = '\0'; // 行末
return buf;
}
mbedで取得したセンサーの値をsinatra+tokyocabinetで保存する
まずセンサーのデータを時系列で溜めたり、取り出したりするためのwebサービスをsinatra+TokyoCabinetで作っておいた。
そこにmbedのADコンバータでCdSの明るさを計測、LEDを点灯/消灯 と mbedにEthernetを接続しtwitterにpostするを合体させ、約10秒間隔でセンサーの値をhttp-postしまくった。
一度web APIが出ている場所にマイコンの情報も保存しておけば、あとでマッシュアップできて便利になる。このAPIの下にmbedでもgreasemonkeyスクリプトでも何個でもぶら下げられる。
mbedからwedataに保存しても良かったんだけど、あえて自分で作ってみた。
■mbedで明るさを計測してhttp-postする
作った回路。CdSで明るさを取得し、LEDで明るさを表示する。mbedのRD+,RD-,TD+,TD-ピンにトランス入りEthernetジャックを接続し、LANケーブルを刺した。

プログラム。CdSに入る光量が少なくなると緑色のLEDが光る。http-postはTickerで定期的に呼び出したかったが、Ticker内でHTTPClient.hの関数を使うとmainに戻ってこなくなるので仕方なくmain内で20ループ毎(約10秒毎)にHTTPClient.post()するようにした。stdは使えるけど、pthread等のスレッド機能は無いみたいなのでこれは痛い。
sensor_http_post | mbed
#include "mbed.h"せっかくC++が使えて、mbed.hのAPIはC++っぽいのに、HTTPClient.hはそうでもないみたいだ。
#include "HTTPClient.h"
#include <string>
using namespace std;
Serial pc(USBTX, USBRX);
DigitalOut led1(LED1); // blink
DigitalOut led2(LED2); // blink when http-post
DigitalOut led3(p11); // represent ADC value
AnalogIn adc(p15);
float ain;
HTTPClient http; // use DHCP
const string api_uri = "http://shokai.mag.keio.ac.jp/sensor-storage/shokai/cds/";
const string mbed_name = "shokai-mbed01";
const string sensor_name = "CdS";
void blink_led(DigitalOut led, int num){
for(int i=0; i < num*2; i++){
led = !led;
wait(0.1);
}
}
void post_sensor_value(){
blink_led(led2, 2);
char query[256] = "";
char result[4096] = ""; // 4kb
sprintf(query, "name=%s&%s=%f", mbed_name.c_str(), sensor_name.c_str(), (float)ain);
pc.printf("post:%s => %s\n", query, api_uri.c_str());
http.post(api_uri.c_str(), query, result, 4096);
pc.printf("%s\n", result);
blink_led(led2, 2);
}
int main(void){
led1 = 1;
int count = 0;
while(1) {
ain = adc;
pc.printf("sensor:%f\n", float(ain));
led1 = !led1; // blink LED
if(ain > 0.1) led3 = 0; // represent CdS value
else led3 = 1;
if(count > 20){
post_sensor_value();
count = 0;
}
count++;
wait(0.5);
}
}
mbedのローカルのストレージがfopenで読めるので、そこに設定ファイルを置けるようにしたい。yamlやjsonをparseするライブラリを作りたい。
動作はシリアル通信で見れる。表示にはserialterm.rbを使った。
postすると返り値がjsonで来る。

■tokyocabinetとsinatraでデータを溜めるAPIを作る
できたもの
sensor-storage API
githubでソースを全部公開しておいた。
shokai's sensor-storage at master – GitHub
http://shokai.mag.keio.ac.jp/sensor-storage/ にhttp-postした内容を、json stringにエンコードして時刻_マイクロ秒をキーにしてTokyoCabinetに保存する。
keyの _ の左側が時刻になっているので後で使う時に便利。
http://shokai.mag.keio.ac.jp/sensor-storage/keys で保存されているkeyの一覧がJSON形式で見れる。
http://shokai.mag.keio.ac.jp/sensor-storage/recentで最近10件のkeyとvalueが取れる。他にもrecent/件数や、last(最後に入れたデータ)、count(データ件数)などのAPIがある。
今回のmbedでCdSの値を保存しているのは http://shokai.mag.keio.ac.jp/sensor-storage/shokai/cds/recent になる。shokai/cds/last , shokai/cds/recent , shokai/cds/count などで色々データが取り出せる。
/ 以下に適当に文字列を付けると新しくDBを作るようになっている。もしデータをpostしてみたいなら、mbedの方のコードは/shokai/cds/をハードコーディングしているので、そこを適当に変えてから使ってくれるとうれしい
あまりにも大きいデータは入らない。
スクリプト自体はわりとシンプル。sinatra使って1ファイルに書いてある。
server/main.rb at master from shokai's sensor-storage – GitHub
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'sinatra'
require 'sinatra/reloader' if development?
require 'rack'
require 'json'
require 'tokyocabinet'
include TokyoCabinet
@@dbdir = 'db'
def db_open(dbname='/')
dbname = dbname.to_s.gsub(/\//, '_')
@hdb = HDB.new
Dir.mkdir(@@dbdir) if !File.exists?(@@dbdir)
@hdb.open("#{@@dbdir}/_#{dbname}.tch", HDB::OWRITER|HDB::OCREAT)
end
after do
@hdb.close if @hdb
end
get '*/' do
path = "../" * params[:splat].first.count('/')
redirect "#{path}./readme"
end
get '*/keys' do
db_open(params[:splat])
@hdb.keys.reverse[0...10000].to_json
end
get '/dbs' do
Dir::glob("#{@@dbdir}/*.tch").map{|i|
name = i.to_s.scan(/.*\/(.+)\.tch/).first.to_s.gsub(/_/,'/')
tmp = name.split(//u)
tmp.shift
tmp.to_s + "/"
}.uniq.delete_if{|i|
i =~ /\/\//
}.sort.to_json
end
get '*/count' do
db_open(params[:splat])
{"count", @hdb.rnum}.to_json
end
get '*/last' do
db_open(params[:splat])
key = @hdb.keys.last
{key, @hdb[key]}.to_json
end
get '*/recent' do
db_open(params[:splat])
@hdb.keys.reverse[0...10].map{|k| {k, @hdb[k]} }.to_json
end
get '*/recent/:num' do
db_open(params[:splat])
num = [params[:num].to_i, 1000].min
@hdb.keys.reverse[0...num].map{|k| {k, @hdb[k]}}.to_json
end
get '*/:tc_key' do
db_open(params[:splat])
key = params[:tc_key]
{key, @hdb[key]}.to_json
end
post '*/' do
db_open(params[:splat])
now = Time.now
key = "#{now.to_i}_#{now.usec}"
params.delete(:splat.to_s)
@hdb.put(key, params.to_json)
{key, @hdb[key]}.to_json
end
delete '*/:tc_key' do
key = params[:tc_key]
v = @hdb[params[:tc_key]]
@hdb.out(params[:tc_key])
{key,v}.to_json
end
githubのmiscディレクトリ以下にテスト用にhttp postしたりするためのスクリプトがある。mbedが無い時の動作チェックにどうぞ。
サーバーはthinで10個preforkさせておいて、それぞれ別のportで起動させてある。apache2のmod_proxy_balancerでそこにproxyして、80番ポートのサブディレクトリ下にあるように見せている。
- 橋本商会 apache2のサブディレクトリをthinで起動してるsinatraにプロキシする
- ubuntu8.04にtokyocabinetインストール – 橋本詳解
- Macにtokyocabinetインストール – 橋本詳解
- 開発時に自動リロードする – 橋本詳解
- shotgunでコード自動リロードを有効にする – 橋本詳解
mbedのADコンバータでCdSの明るさを計測、LEDを点灯/消灯
mbedのAnalogInとDigitalOutとSerial通信の機能を使った。

CdSを手で隠すとLEDが光るようにした。
CdSは光に反応して抵抗値が変わるセンサー。mbedに6ポートあるADコンバータでCdSの電圧を計測すると、明るさがわかる。

プログラムはcds_led_serial | mbedにpublishした。
mbedのアカウントを持っている人ならすぐに右上のImport program into Compilerというリンクからいじれる。
動作確認用に基板上のLED1は0.3秒間隔で点滅させつつ、p15のADCの値を見てブレッドボード上のLED2を点灯/消灯させるコード
#include "mbed.h"C++はboostばかり使っていたので、printfを久しぶりに使った。
Serial pc(USBTX, USBRX);
DigitalOut led1(LED1); // on board LED
DigitalOut led2(p11);
AnalogIn adc(p15);
float ain;
int main() {
while(1) {
led1 = !led1; // blink LED
ain = adc;
if(ain < 0.4) led2 = 1;
else led2 = 0;
pc.printf("%f\r\n", (float)ain);
wait(0.3);
}
}
他のサンプルコードを読んでるとたまに上の方でusing std;している物もあるので、mbedでstdは使えるんだろうな多分。
ADコンバータのレジスタへの操作はAnalogInのオブジェクトを作った時点で隠蔽されてるけど、変数ainとして作ったAnalogInのオブジェクトを読む毎にAD変換が行われるんだろうか??
たぶん行われる気がするので、今回は一度値を別のint型変数に代入するようにした。
回路図。今回使った小さいCdSは、光の状態で変動するがだいたい1kΩ前後の抵抗値を持っている。
GNDとmbedのADコンバータ(15番ピン)の間に同じく1kΩ程度の抵抗を入れて分圧し、電圧を計測した。

mbedはSerialライブラリでパソコンと通信できる。MacやLinuxなら特にドライバ等必要なしに、/dev/tty*にusbmodemとして認識されているはず。windows用にはCOMポートとして認識できるドライバが公開されている。
シリアル通信をモニタしてみると、CdSからの電圧を計測している15番ピンは0.1〜0.6の間の値を出力している事がわかった。
mbedのプログラム中で、ADCが0.4以下の値の時にブレッドボード上の緑色のLED(led2)を点灯させ、逆の時は消灯するようにした。

シリアル通信のモニタにはruby-serialportで作ったterminalを使っている
CY8C29466のADCINC12
PSoCの12ビットAD変換器を使う。
関連、PGAについても:s.h.log: PSoC Designer – CY8C29466の6bit ADコンバータ




最近のコメント