0

PSoC CY8C29466のI2CHWモジュールで通信

PSoC CY8C29466のI2Cモジュールを試した。
I2Cは2本の信号線の上にmaster/slaveの2種類のノードを合計100個以上載せて相互に通信できるプロトコル。動作速度が違う部品同士をより少ない結線数で相互接続させる事を目的として、1992年にPhilipsが作って2004年に特許が切れている。今ではほとんどのマイコンにI2C通信をするモジュールがついているし、外部メモリやセンサーなどもI2Cで値を返す物がある。linuxのkernelにも入っていて(/usr/include/linux/i2c.hとか)マザーボード上の温度センサとかに使われているらしい。

このへんに詳しく書いてある。

特に仕様書pdfはちゃんと読んでみたらかなり勉強になったのでオススメ。
プログラムはCypress公式のUser Module Datasheetのp.39,40ものを参考にした。


■作った物
PSoC Designer 5.0+SP6で作ったプロジェクトまるごとgithubに置いた
git clone git://github.com/shokai/i2c-uart-cy8c29466.git
で取得できる。


3つ並んでいるPSoCマイコンのうち一番下がmasterで、上2つがslaveデバイス。デバイスが接続されている2つの線は2kΩの抵抗(1kΩ2つ)を介してVCCと接続されpull upされている。I2Cの接続構成とプルアップ抵抗に適切なプルアップ抵抗の計算方法が書かれている。電源5Vで100kbpsか400kbpsなら2kΩ〜5kΩが最適値とのこと。
R0015277.JPG


masterに付いているボタンを押すと、2つのslaveにLED点灯命令を送る。ボタンを離すと消灯する。
R0015274.JPG


slave側のボタンは、それぞれmasterの2つのLEDの点灯/消灯に対応している。
R0015275.JPG
R0015276.JPG


masterはslaveをコントロールするだけでなく、I2Cとシリアル通信のブリッジになっている。
slaveからのデータをslaveのアドレス名と共にUARTでパソコンに中継する。また、パソコンからmasterマイコンに’U’か’D’の文字を送る事と、masterは全slaveにLED点灯/消灯を送る。
UARTとRS232Cのレベル変換にはADM3202を使った。


だいたいそういう内容のビデオ




■デバイス設定
master側。
I2C master device setting
パソコンとのシリアル通信で9600bpsを作るためにVC1,VC3を設定している。動作電圧やCPUクロックなどはデフォルト値。

I2C master I2CHW module setting
single masterモードでモジュールを置いた。
100kbpsでPORT1の5番と7番ピンをI2Cに使う。bufferはプログラム中で自分で用意したBYTE配列を使ってもらうように設定。

I2C master UART module setting
シリアル通信で9600bpsが出るようにUARTモジュールを設定。
橋本商会 PSoC – CY8C29466でUART受信割り込みでくわしく書いた。

I2C master Pinout setting
3つのLEDに使うピンは出力をStrongにして3V出るようにした。
タクトスイッチにつなぐPort2の2番ピンは両エッジ入力割り込みを設定しつつ内部でプルアップして、ブレッドボード上でも100Ωの抵抗でプルダウンする。こうするとチャタリングが起きない。


I2C master Pin layout
ピン配置図

I2C master modules connection
アナログブロックは使っていないので上の方だけ。
UARTがPort2の4,5番ピンから出るように引き出した。



slave側。
I2C slave device setting
Global Resourceは初期値のまま


I2C slave I2CHW module setting
I2CHWモジュールををslaveで置いた。速度はmasterに合わせる。
slaveデバイスのアドレスは7bitだが、0x00~0x10までの16個のアドレスはプロトコルに予約されているので、0x11からの112個が使える。
今回はmasterのプログラムで0x11~0x20のデバイスがいるか確認しながら動作するようにしているので、アドレス17~32のどれかを使えばいい。


I2C slave Pinout setting
masterと同じ様にLEDとタクトスイッチ用のピンを設定。LEDは1つだけ。


I2C slave Pin layout


I2C slave modules connection
なにも結線していない


■プログラム
master側
slaveアドレス0x11から0x20までのデバイスに、順にTXバッファを書き込み、RXバッファに読み込む。送受信を待っている間、timeout_countをカウントアップし続けて、閾値以上になるとタイムアウトするようにしている。これで存在するかわからないslaveとやりとりできるし、動作中にslaveデバイスが増えても問題なくネットワークに参加させられる。

UARTの受信やピン入力の検出は割り込みで処理している(橋本商会 PSoC – CY8C29466でUART受信割り込みで書いた)のだが、I2Cはメインループの中で処理している。I/OからのイベントでUARTぐらいなら使ってもいいけど、16個のslaveとのやりとりを割り込みの中でやると多重割り込みが起こりやすいので、各ルーチンからbufferを読み書きしてそれを定期的にslaveと共有するという方式にした。
本番ではdigital blockを1つ消費するだけで使える8bit Timerモジュールで定期的に回すようにすると良いと思う。

main.c
// I2C-UART master
// CY8C29466-24PXI(DIP Package)
// PSoC Designer 5.0 + SP6
// IMAGECRAFT C Compiler
#include <m8c.h>        // part specific constants and macros
#include <I2CHW_1Common.h>
#include <I2CHW_1Mstr.h>
#include "PSoCAPI.h"    // PSoC API definitions for all User Modules
#define _BV(BIT) (1<<BIT)
#define sbi(BYTE,BIT) (BYTE |= _BV(BIT))
#define cbi(BYTE,BIT) (BYTE &= ~_BV(BIT))

#define LED_ON() sbi(PRT2DR, 0) // LED
#define LED_OFF() cbi(PRT2DR, 0)
#define LED2_ON() sbi(PRT1DR, 6)
#define LED2_OFF() cbi(PRT1DR, 6)
#define LED3_ON() sbi(PRT1DR, 4)
#define LED3_OFF() cbi(PRT1DR, 4)

#define BTN_PORT PRT2DR // push button
#define BTN_BIT _BV(2)

#define BUF_SIZE 8
BYTE buf_tx[BUF_SIZE]; // I2C buffer
BYTE buf_rx[BUF_SIZE];
BYTE status; // I2C status
BYTE slave; // slave address
#define I2C_TIMEOUT 128 // 長時間応答が返ってこないslaveデバイスを無視する
BYTE timeout_count, i;

void main(void)
{
    M8C_EnableGInt; // enable global interrupt
    M8C_EnableIntMask(INT_MSK0, INT_MSK0_GPIO);
    UART_1_CmdReset(); // uart init
    UART_1_IntCntl(UART_1_ENABLE_RX_INT); // enable receive interrupt
    UART_1_Start(UART_1_PARITY_NONE);
    LED_ON();
    UART_1_CPutString("start");
    LED_OFF();
    I2CHW_1_Start();
    I2CHW_1_EnableMstr();
    I2CHW_1_EnableInt();
    for(;;){
        for(slave = 0x11; slave < 0x21; slave++){
            I2CHW_1_bWriteBytes(slave, buf_tx, BUF_SIZE, I2CHW_1_CompleteXfer); // master->slave
            timeout_count = 0;
            for(;;){
                if(I2CHW_1_bReadI2CStatus() & I2CHW_WR_COMPLETE ||
                   timeout_count++ > I2C_TIMEOUT) break;
            }
            I2CHW_1_ClrWrStatus();
            
            I2CHW_1_fReadBytes(slave, buf_rx, BUF_SIZE, I2CHW_1_CompleteXfer); // slave->master
            timeout_count = 0;
            for(;;){
                if(I2CHW_1_bReadI2CStatus() & I2CHW_RD_COMPLETE ||
                   timeout_count++ > I2C_TIMEOUT) break;
            }
            I2CHW_1_ClrRdStatus();

            switch(slave){
            case 0x11:
                if(buf_rx[0] == 'u') LED2_OFF();
                else if(buf_rx[0] == 'd') LED2_ON();
                break;
            case 0x12:
                if(buf_rx[0] == 'u') LED3_OFF();
                else if(buf_rx[0] == 'd') LED3_ON();
                break;
            }
            
            while(!(UART_1_bReadTxStatus() & UART_1_TX_BUFFER_EMPTY)); // slaveからの受信データをシリアル通信出力
            UART_1_CPutString("I2C:");
            UART_1_PutSHexByte(slave); // slaveアドレス
            UART_1_CPutString(",");
            UART_1_PutString(buf_rx); // slaveからの受信データ
            UART_1_PutCRLF();
            for(i = 0; i < BUF_SIZE-1; i++) buf_rx[i] = '\0'; // 受信バッファを初期化
        }
    }
}


// UART受信割り込み
#pragma interrupt_handler INT_UART_RX
void INT_UART_RX(void){
    char recv_data;
    recv_data = UART_1_cGetChar(); // read UART
    UART_1_PutChar(recv_data); // echo
    switch(recv_data){
    case 'U':
        LED_ON();
        buf_tx[0] = 'A'; // slaveにLED点灯を指示
        UART_1_CPutString("LED:ON\r\n");
        break;
    case 'D':
        LED_OFF();
        buf_tx[0] = 'B'; // slaveにLED消灯を指示
        UART_1_CPutString("LED:OFF\r\n");
        break;
    }
}

// I/Oピン状態変化割り込み
#pragma interrupt_handler INT_GPIO
void INT_GPIO(void){
  if(BTN_PORT & BTN_BIT){ // ボタンが押されている時
    LED_ON();
    buf_tx[0] = 'A'; // slaveにLED点灯を指示
    UART_1_CPutString("LED:ON\r\n");
  }
  else{
    LED_OFF();
    buf_tx[0] = 'B'; // slaveにLED消灯を指示
    UART_1_CPutString("LED:OFF\r\n");
  }
}




slave側
slaveもmasterと同じ様にglobalにバッファを用意して、そこに適当に現在の状態を読み書きし、定期的にメインループ内でmasterと通信し共有している。この方が素直に書きやすい。
LEDとボタンの状態を結びつける情報を1byte目に置いているが、それ以外の情報をやりとりする場合もシリアライズとかして通信するのではなく2byte目以降を使う方がいい。あくまで必要な変数を定期的に同期させられる、分散オブジェクト風に書いた方がすっきりする(PSoCのstring.hがおかしいという理由もあるけど)


あと、ややこしいのがslaveなのでI2CHW_1_InitWrite関数を呼ぶとmasterからslaveへの書き込みが起こる。受信する。送信ではない。また、I2CHW_1_bReadI2CStatus() での状態チェックも、dataに使っている信号線1本の状態のチェックのためなので呼び出すタイミングがmasterと逆になる。このへんややこしいのであまり触りたくないから、通信は隔離された別ループでやってglobalに置いた共用bufferを同期させるという方法にした。

main.c
// I2C-UART slave
// CY8C29466-24PXI(DIP Package)
// PSoC Designer 5.0 + SP6
// IMAGECRAFT C Compiler

#include <m8c.h>        // part specific constants and macros
#include <I2CHW_1Common.h>
#include "PSoCAPI.h"    // PSoC API definitions for all User Modules
#define _BV(BIT) (1<<BIT)
#define sbi(BYTE,BIT) (BYTE |= _BV(BIT))
#define cbi(BYTE,BIT) (BYTE &= ~_BV(BIT))

#define LED_ON() sbi(PRT2DR, 0) // LED
#define LED_OFF() cbi(PRT2DR, 0)
#define BTN_PORT PRT2DR // push button
#define BTN_BIT _BV(2)

#define BUF_SIZE 8
BYTE buf_rx[BUF_SIZE]; // I2C buffer
BYTE buf_tx[BUF_SIZE] = {'x'};
BYTE status; // I2C status

void main(void)
{
    M8C_EnableGInt;
    M8C_EnableIntMask(INT_MSK0, INT_MSK0_GPIO);
    I2CHW_1_Start();
    I2CHW_1_EnableSlave();
    I2CHW_1_EnableInt();
    for(;;){
        status = I2CHW_1_bReadI2CStatus();
        if(status & I2CHW_WR_COMPLETE){ // master->slave
            I2CHW_1_ClrWrStatus();
            I2CHW_1_InitWrite(buf_rx, BUF_SIZE);
        }
        if(status & I2CHW_RD_COMPLETE){ // slave->master
            I2CHW_1_ClrRdStatus();
            I2CHW_1_InitRamRead(buf_tx, BUF_SIZE);
        }
        if(buf_rx[0] == 'A') LED_ON(); // masterからの指示でLEDの点灯/消灯を切り替え
        else if(buf_rx[0] == 'B') LED_OFF();
    }
}

#pragma interrupt_handler INT_GPIO
void INT_GPIO(void){
    if(BTN_PORT & BTN_BIT){ // ボタンを押している時
        buf_tx[0] = 'd'; // 押下をmasterに通知
    }
    else{
        buf_tx[0] = 'u';
    }
}

0

ShinagawaSeaside

tokyo tyrantのサーバーを起動したり終了したりするrake taskを作った。

名前は、tokyotyrantの周辺のライブラリがmiyazaki resistanceとかそういう名前ばかりだったのでそういう作法なのかなと思って天王洲アイルと迷いつつ品川シーサイドに決めた。


■インストール

sudo gem isntall shinagawaseaside


■使う
Rakefile
require 'rubygems'
require 'shinagawaseaside'

ttdb = [ { :name => 'users', :port => 20010},
         { :name => 'videos',:port => 20011},
         { :name => 'comments', :port => 20012} ]

ShinagawaSeaside::set_tasks(ttdb, :basedir => File.dirname(__FILE__)+'/ttdb')
ShinagawaSeaside::set_tasks するとrake taskが追加される。

Rakefileのあるディレクトリの下に ttdb というディレクトリが作られて、
その中にusers.tch, videos.tch, comments.tch というDBができる。pidはusers.pid, videos.pid, comments.pidの中に入る。


% rake -T
rake ttrestart # restart TokyoTyrant server
rake ttstart # start TokyoTyrant server
rake ttstop # stop TokyoTyrant server


中身はRakeでTokyoTyrant serverを起動/終了 – 橋本詳解と大体同じ。(複数サーバー起動できるようにした)
tokyotyrantをソースからインストールすると一緒に入る ttservctl を参考にした。



タスクの名前は初期値がttstart, ttstopだけど、変更もできる
ShinagawaSeaside::set_tasks(ttdb,
                            :basedir => File.dirname(__FILE__)+'/ttdb',
                            :start => 'start', # set task name
                            :stop => 'stop',
                            :restart => 'restart'
                            )



俺はyamlで設定ファイルを書いてそこから読み込むようにしている。そうするとアプリからも、どのDBがどのportにあるか見つけやすい。

config.yaml
ttdb : 
     - name : users
       port : 23240
     - name : videos
       port : 23241

Rakefile
require 'rubygems'
require 'yaml'
require 'shinagawaseaside'

begin
  conf = YAML::load open(File.dirname(__FILE__)+'/config.yaml')
rescue
  STDERR.puts 'config.yaml load error'
  exit 1
end

ShinagawaSeaside::set_tasks(conf['ttdb'], :basedir => File.dirname(__FILE__)+'/ttdb')



■ソースコード
githubに置いた

0

gemだけで画像をリサイズできるImageResizeを作った

ImageResizeはImageMagickなどの外部プログラムに依存せずに、単体で画像をリサイズできる。ただしJavaの実行環境が必要。


SFCの「革新的ネットサービスの構築」という授業のTAをやっていて、昨日と今日niftyに行って開発合宿をしていた。
そこでいろいろあって、ImageMagickなしで画像のサムネイルを作る方法が無いのか模索していたら、ImageResizeというgemができた。

■インストール

gem install ImageResize


■使う
require 'rubygems'
require 'ImageResize'

# input, output, width, height
Image.resize('big.jpg', 'small.jpg', 40, 40)
これで40×40ピクセルに縮小される。
縦横のアスペクト比が1:1ではない画像の場合、アスペクト比を保ったまま長辺の方にあわせて縮小する。


■ソースコード
githubに置いた。
http://github.com/shokai/ImageResize-ruby


■実装
本体はJava。
標準実行環境にJPEGやGIFやBITMAPなど様々な画像フォーマットの読み書き機能を含んでいる環境というと、Javaと.NETとopenFrameworksしか思いつかなかった。
その中でいちばん色々なマシンにインストールされていそうで、配布が容易な物を選んだらJavaになった。


Javaでの画像の扱いは.NET並に簡単。java.awtとjavax.imageioの下に色々充実しているのでそれらを使えばいい。
ImageResize.java

import javax.imageio.*;
import java.io.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.regex.*;

class ImageResize{

    public ImageResize(){
    }

    public static void main(String args[]){
if(args.length < 4){
    System.out.println("ImageResize in.jpg out.jpg 320 320");
    System.exit(1);
}
ImageResize app = new ImageResize();
if(app.resize(args[0], args[1], Integer.parseInt(args[2]), Integer.parseInt(args[3]))){
    System.out.println(args[1]);
}
    }

    public boolean resize(String fname_in, String fname_out, int max_width, int max_height){
System.out.println(fname_in);
BufferedImage img = null;
try {
    img = ImageIO.read(new File(fname_in));
}
catch (Exception e) {
    e.printStackTrace();
    img = null;
}

int width, height;
if(img.getWidth() < img.getHeight()){
    height = max_height;
    width = img.getWidth() * max_height / img.getHeight();
}
else{
    width = max_width;
    height = img.getHeight() * max_width / img.getWidth();
}
System.out.println(img.getWidth() + "x" +img.getHeight() + " => " + width + "x" + height);

BufferedImage img_resized = new BufferedImage(width, height, img.getType());
AffineTransformOp ato = new AffineTransformOp(AffineTransform.getScaleInstance((double)width / img.getWidth(), 
       (double)height / img.getHeight()), 
      null);
ato.filter(img, img_resized);


String format = Pattern.compile("^.+\\.(.+)$").matcher(fname_out).replaceAll("$1");
boolean result = false;
try {
    result = ImageIO.write(img_resized, format, new File(fname_out));
}
catch (Exception e) {
    e.printStackTrace();
    result = false;
}
return result;
    }
    
}


■参考


今回は使わなかったが、Java Advanced Imaging (JAI) APIというのが標準実行環境には含まれていないけどSunが作っていて、かなり充実しているらしい。
Javaランタイムも速くなってきているらしいしScalaで画像処理とかしてみたい。

0

Google APIを使ったアプリをAndroidマーケットで公開

Androidマーケットでのアプリの公開で書いた方法だけでは、Google Mapsを使っているアプリで地図が表示されなかった。

よく考えたら、MapViewのAPI Keyの指定はアプリのビルド時に使うkeystoreから作ったMD5 finger printから生成されていた。


MD5 fingerprintをAndroidマーケットでのアプリの公開で作ったリリース用の鍵から生成する。

keytool -list -keystore shokai-key


Sign Up for the Android Maps API – Android Maps API – Google Codeに、MD5 fingerprintをコピペして「Generate API Key」を押す


新しくAPI Keyが生成される。


Eclipseで、layout/main.xmlを編集。MapViewにapiKeyを指定
<com.google.android.maps.MapView
    android:id="@+id/mapview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:enabled="true"
    android:clickable="true"
    android:apiKey="your-api-key"
    />


これでEclipseでパッケージ名を右クリックして[Android Tools]→[Export Signed Application Package]でapkを生成して、Marketで公開する。


shokai.org で検索
android market


既にdebug版アプリが入っていれば入れ替わる
android market


AndroidManifest.xmlに基づいてインストール時に警告が出る
android market

地図を使うアプリの作り方については、以前AndroidでGPSロガーでくわしく書いた。

1

Androidマーケットでのアプリの公開

公開用に、AndroidManifest.xmlにminSdkVersionを指定してからコンパイルする。
Android2.1用なので7を指定。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.shokai.test"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
        <activity android:name=".Test"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


公開用apkを作る。
Eclipseでパッケージ名を右クリックして[Android Tools]→[Export Signed Application Package]


1回目は証明書を作る。適当に項目を埋める。
signature key creation


signedなapkができたら、terminalで実機にインストールしてみる。
接続されたデバイス一覧を確認
adb devices
HT04GPL12980にinstall
adb -s HT04GPL12980 install test2.1.apk
既に入っているdebug版を先にアンインストールしないとinstallできない。


http://www.android.com/market/ の下のリンク(http://market.android.com/publish)からAndroid Marketでの公開用にGoogleに年会費25ドル払う。


signedなapkとスクリーンショット等を登録、アップロード。
1分も待たずに即公開されていた。android端末側のマーケットで検索するとすぐ出る。
testapp on android market


Google APIを使ったアプリは専用の証明書か何かが必要らしい。これだけではアプリは起動するけどGoogle Mapが表示されなかった。