[C#]lock文で排他をとる仕組みがC#4.0で変更された件

C#では、オブジェクトの排他を取るためにlock文があります。

lock( obj ) {
    body;
}



これは、C#4.0の場合は以下のコードと等価です。

bool lockWasTaken = false;
var temp = obj;
try {
    Monitor.Enter(temp, ref lockWasTaken);
    {
        body;
    }
} finally {
    if (lockWasTaken) {
        Monitor.Exit(temp);
    }
}




わざわざ「C#4.0の場合は」と注意書きしたのは、C#3.0以前では下記のコードと等価だったからです。

var temp = obj;
Monitor.Enter(temp);
try {
    body;
} finally {
    Monitor.Exit(temp);
}



どちらでも一緒のように見えますが、後者のコードだとMonitor.Enter(temp);とtryの間に、コンパイラが内部的にIL命令を挿入しており、かつ、そこで例外が発生した時に、Lockの解除が行われないというリスクを抱えています。ロックの解除が行われないと、結果としてデッドロックの原因になってしまいます。

C# 4.0では、この問題を解消するために等価となるコードが変更されました。


と、ここまでlock()と、”等価となるコード”と今まで書いていましたが…
実はlock()文は、上記コードのsyntax sugerなので、完全に同一のものです(コンパイル前に置換されると分かりやすいです)。



また余談ですが、Monitor.Enter()メソッドは、”スレッドに対して”排他を提供します。
ですので、下記のコードではデッドロックは発生しません。
要は、単一スレッドなら、同一オブジェクトを複数回Monitor.Enter()を行えるという事です。

SomeObj obj;
 
void MainMethod( void ) {
    lock( obj ) {
        SubMethod();
    }
}
 
void SubMethod( void ) {
    lock( obj ) {
        DoSomething();
    }
}



並行コンピューティング技法 (実践マルチコア/マルチスレッドプログラミング)

抵抗のカラーコードと、虹色の関係

電子回路で使用される抵抗は、抵抗値を示すためのカラーコードが有ります。
一目で見て分かるように、このカラーコードは虹を元にしています。

ですが虹は7色、カラーコードは10種類。


色と値がどのようにマッピングされているか気になったので、確認してみました。


カラーコードの1から7が基本的に虹の色と対応しているのですが、赤の後に茶が入るのと、藍色が無いのが相違点でした。藍色は、青と近くて紛らわしいから除かれたのでしょうか…

また、足りない分の追加として、無彩色の黒、灰、白が加えられています。


覚え方の語呂合わせは、以下のものが有名です。

0 黒: 黒い礼服
1 茶: お茶を一杯、小林一茶
2 赤: 赤い人参
3 橙: みかんは橙
4 黄: 岸恵子、きしめん
5 緑: みどりご、五月みどり
6 青: 青虫、青二才のろくでなし、青緑青
7 紫: 紫式(7)部
8 灰: ハイヤー
9 白: 釧路、しろくま、ホワイトクリスマス





海外ではどの様な覚え方をするのだろうか思い調べてみました。

語呂合わせのパターンです。
お国柄が出るので、日本人にはピンと来ないものも多いです。
特に、”A blue bug has six legs”の”blue bug”は、調べても意味が良く分かりませんでした…

Black is nothing -- zero.       黒は無
One brown penny.                     ペニー硬貨は茶色
A red heart has two parts.           心臓は2つの部位がある
Three oranges.                       Three oranges(オペラのタイトルより)
A yellow dog has four legs.          黄色い犬(のら犬)には4つの足がある
A green $5 bill.                     5ドル紙幣は緑色
A blue bug has six legs.             青い虫は6つ足
Seven violent seas.                  紫の、七つの海
An 80-year-old man has grey hair.    80歳の老人はグレーの髪
A white cat has nine lives.          猫に九生あり(白猫は9回生まれ変わるという迷信)


How to Give a Resistor Color Code for the Value Enteredより



基本的には海外も虹色がベースです。

Alternatively, most of the colors are those from the traditional rainbow. 
Black is 0 (as in 'nothing')
Brown is 1
then Red through Violet
and finally Gray and White are 8 and 9.


How to Remember Electrical Resistor Color Codes: 5 stepsより

4501328304
たのしくできるブレッドボード電子工作

[PHP]文字列リテラルを、シングルクォートとダブルクォートで括った時の速度差

PHPでは文字列リテラルを書くとき、文字列をシングルクォート、又は、ダブルクォートで括ることが出来ます。
両者の違いは、”{}”で書いた変数の展開を行うかどうかと、エスケープシーケンスを処理するかです。

<?php
$a = 10;
echo "hello world:{$a}\n";
echo 'hello world:{$a}\n';



実行結果

C:\>php test.php
hello world:10
hello world:{$a}\n



ここで疑問が1つ出てきます。

上記の様に変数やエスケープシーケンス処理が必要な場合は、ダブルクォートで括る必要があります。
ですが、単なる文字列リテラルを指定したい場合、どちらの方がパフォーマンス的に良いのでしょうか?


直感では、余計な処理が必要なダブルクォートのほうが遅そうな気がします。
気になったので、論より証拠ということでベンチマークを取ってみました。


テストに使ったコードは以下の通りです。
echo文のクォートを変えて、パフォーマンス測定を行ってみます。

<?php
$stdout= fopen( 'php://stdout', 'w' ); 
$stderr = fopen( 'php://stderr', 'w' ); 
 
 
$startTime = microtime(TRUE);
 
for ( $loop = 0; $loop < 1000; $loop++ ) {
	for ( $loop2 = 0; $loop2 < 1000; $loop2++ ) {
		echo 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
	}
}
$endTime = microtime(TRUE);
 
$elapsedTimeMsec = ( $endTime - $startTime ) * 1000;
 
fprintf( $stderr, "実行時間: %.4fミリ秒", $elapsedTimeMsec );




実行環境はこんな感じ。

C:\>php -version
PHP 5.3.8 (cli) (built: Aug 23 2011 11:50:20)
Copyright (c) 1997-2011 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies





実行結果は以下の通りです。それぞれ3回づつ計測しました。

シングルクォートの場合

C:\>php test.php > NUL
実行時間: 2639.4989ミリ秒
 
C:\>php test.php > NUL
実行時間: 2602.2980ミリ秒
 
C:\>php test.php > NUL
実行時間: 2630.3949ミリ秒




ダブルクォートの場合

C:\>php test.php > NUL
実行時間: 2681.6361ミリ秒
 
C:\>php test.php > NUL
実行時間: 2641.7520ミリ秒
 
C:\>php test.php > NUL
実行時間: 2612.1821ミリ秒




グラフで比較するまでも無く、結果は一目瞭然で両者の差はほとんど有りませんでした。
直感とは異なり、速度差は無いので、パフォーマンスの観点で見るとどっちでも良いみたいです。

“elasped time”と”latency”の違い

よくコンピュータのパフォーマンスについて書かれている記事を見ると、”elasped time”(経過時間)や”latency”(遅延)という言葉が出てくる場合があります。

この二つの言葉の違いは何でしょうか?



elasped timeは、有る処理を依頼してから、応答が全部返ってくるまでの時間です。
ターンアラウンドタイム(turn around time)とも呼ばれています。

一方、latencyは、有る処理を依頼してから最初の応答が返ってくるまでの時間です。
こちらは、レスポンスタイム(response time)とも呼ばれています。

両者を絵で表現すると、以下の関係になります。



latencyの”最初の応答”というのは、複数のメッセージが返ってくるようなサービスの場合、最初のメッセージの取得が完了するまでという意味で使われる場合もあるし、最初の1byteを取得するまでという意味の場合が有ります。
厳密なパフォーマンス測定を行う場合は、何を指しているか定義を確認する必要があります。

[PHP]標準エラー出力、標準出力に文字列を出力する

PHPで標準エラー出力に文字を出すには、ファイル”php://stdout”をオープンすればOKです。

<?php
$stdout= fopen( 'php://stdout', 'w' );
fwrite( $stdout, "hello world to stdout\n" );
 
$stderr = fopen( 'php://stderr', 'w' );
fwrite( $stderr, "hello world to stderr\n" );




実行結果

C:\>php test.php > stdout.txt 2> stderr.txt
 
C:\>more stdout.txt
hello world to stdout
 
C:\>more stderr.txt
hello world to stderr


キーエンスのPLC(シーケンサ)、KV10~90やKZ-A500で使うPC接続ケーブルの仕様

キーエンスが以前出していたシーケンサのKV10~90やKZ-A500についての備忘録メモです。


KZ-A500は、PCとの接続端子がモジュラーコネクタになっています。
純正品を使用する場合は、以下のケーブルを買う必要があります。(接続先PCの構成により異なります)

DOS/V
    OP-26487    モジュラーケーブル
    OP-26486    モジュラー,RS-232C変換(D-Sub9)
 
PC9801
    OP-26487    モジュラーケーブル
    OP-26485    モジュラー,RS-232C変換(D-Sub25)
 
PC9801
    OP-26487    モジュラーケーブル
    OP-26482    モジュラー,RS-232C変換(D-Sub14)




このうち、OP-26487は普通のストレートケーブルです。
OP-26486は、RS-232Cとの変換となっており、以下の結線で自作できます。

RJ-11  D-SUB9ピン
-----  ----------
Pin 3  Pin 3(TXD)
Pin 4  Pin 5(GND)
Pin 5  Pin 2(RXD)


上記に加えてD-SUB9ピン側は、Pin4とPin6(DTR-DSR)、Pin7とPin8(RTS-CTS)を、それぞれショートさせます。


通信設定は,以下の通りです。

9800 baud 
8 bit
偶数パリティ
ストップビット1

[C# 5.0]メソッドの呼び元メソッドを調べる方法

C# 5.0では、関数の引数に属性(attribute)を追加する事で、関数の呼び元の情報を取得が可能です。
これはC言語で言う__LINE__,__FILE__マクロ相当のものです。

C#の4.0以前でもリフレクションや、System.Diagnostics.StackTraceを使えば取得は出来たのですが、5.0からは取得が簡単になりました。

※C#4.0でSystem.Diagnostics.StackTraceを使う場合の例はこちら
 → [C#]関数の呼び出し元のメソッド名を知る方法


という訳でサンプルコードです。

class Test
{
    static void Main(string[] args)
    {
        ShowCaller();
    }
 
    static void ShowCaller([CallerLineNumber] int     line = 0,
                            [CallerFilePath]   string path = "",
                            [CallerMemberName] string memberName = "") {
        Console.WriteLine("line:{0}, file:{1}, member:{2}", line, path, memberName );
    }
}



呼び元情報の取得は、メソッドの省略可能引数として実装します。そうすると実行時に.Netのランタイム側が自動でパラメータに値をセットしてくれます。

呼び元情報の取得は、ログの出力や例外ハンドラなどで使用すると調査・デバッグに便利です。

内部的には、CallerLineNumber,CallerFilePath,CallerMemberNameの情報はコンパイル時にバイナリに埋め込まれます。ですのでプログラム実行時にオーバーヘッドは発生しません。

[C#]string,char,byteの相互変換

文字を表現する各種データ型の変換方法です。
Stringから他の型に変換する場合は、文字コードの指定が必要となります。

string -> char


string str = "hello world";
 
//文字列をcharの配列に変換する
char[] charArray = str.ToCharArray();
 
//文字列を、1文字づつcharとして処理する
foreach (char c in str) {
    Console.WriteLine( c );
}
 
//文字列のn文字目をcharとして取得する
int n = 5;
char c = str[n];




char -> string


char c = "あ";
string s = c.ToString();




string -> byte


byte[] bytesArray = xxx;
 
// SJISのbyte配列をstringに変換
str = System.Text.Encoding.GetEncoding( 932 ).GetString( bytesArray );
 
// UTF-8のbyte配列をstringに変換
str = System.Text.Encoding.UTF8.GetString( bytesArray );





byte -> string


string str = "hello world";
byte[] bytesArray;
 
// stringをSJISのbyte配列に変換
byte[] bytesArray = System.Text.Encoding.GetEncoding( 932 ).GetBytes( str );
 
// stringをUTF-8のbyte配列に変換
byte[] bytesArray = System.Text.Encoding.UTF8.GetBytes( str );





byte -> char


byte[] bytesArray = ...;
char[] charArray  = System.Text.Encoding.GetEncoding( 932 ).GetString( bytesArray ).ToCharArray();





char -> byte


char c = "あ";
byte b = Convert.ToByte( c );



プログラマのための文字コード技術入門
文字コード「超」研究

[VisualStudioでμITRON]HelloWorldプログラムを作成する



前回はμITRONの実装のひとつであるHOS(Hyper Operating System)をダウンロードし、
カーネルのライブラリを作成しました。
Visual Studioで実行可能なμItronの環境を構築する

今回はこのライブラリを使用して、μITRON環境下でHelloWorldを表示させるプログラムを作成します。


今回作成するプロジェクトのフォルダ構成


作業に入る前に、今回の説明の前提を説明しておきます。

まず、前回書いたカーネルのライブラリは準備が出来ているものとします。
Visual Studioで実行可能なμItronの環境を構築する

今回作成するプロジェクトは、以下のフォルダに作成します。

C:\HOS\HOSProj\HelloWorld



また、前回使用したカーネルのソースはC:\HOS\hos-v4a以下に配置します。


HOS自体は、好きなフォルダにプロジェクトを作成する事が出来ますが、上記と異なるフォルダで作業する場合は、以降の説明を読み替えてください。



プロジェクトを作成する

まずは新規作成のプロジェクトから、新しいプロジェクトを作成します。


Visual C++のWin32を選択し、コンソールアプリケーションでプロジェクトを作成します。


アプリケーションの設定画面では、以下の設定を行います。
プリコンパイル済みヘッダーからチェックを外して、完了ボタンをクリックします。


この状態でF5キーを押して、デバッグ実行が行える事を確認しておきます。
黒いコンソールの画面が一瞬表示された後、閉じればOKです。



以下のフォルダより2つのファイルを、今回作成するプロジェクトのフォルダにコピーします。前回も説明しましたが、hosv4a.libがカーネルのライブラリで、h4acfg.exeがシステム定義のコンフィグ作成プログラムです。

コピー元フォルダ
    C:\HOS\hos-v4a\kernel\build\win\win32\vc2010\Debug
 
ファイル
    hosv4a.lib
    h4acfg.exe




2012/10/21追記:
上記2ファイルに加えてvc100.pdbも一緒にコピーしてください。
vc100.pdbが無くてもコンパイル時にwarningが出るだけで動作に支障は有りませんが、.pdb(デバッグ情報ファイル)があるとデバッグ実行時に便利です。



次に、サンプルフォルダから以下の4ファイルをコピーします。
これらのファイルはHOSが周期的にカーネル処理を行うための関数です。

コピー元フォルダ
    C:\HOS\hos-v4a\sample\win\win32
 
ファイル
    ostimer.c
    ostimer.h
    wintimer.c
    wintimer.h




それぞれのソースで各2つの関数が定義されており、処理概要は以下の通りです。
(中身については今のところ理解する必要はありません。)

ostimer.c
    void OsTimer_Initialize(VP_INT exinf)
            -> カーネル周期実行の初期化
               内部でWinTimer_Start()関数を使用し、HOSの初期処理でコールされる
 
    void OsTimer_IrqHandler(void)
            -> カーネル周期実行の処理
               HOSの周期実行タスクとしてコールされる
 
 
wintimer.c(ostimer.cの内部使用関数)
    void WinTimer_Start(INHNO inhno, int iInterval)
            -> 内部でWinTimer_Threadスレッドを起動している
 
    DWORD WINAPI WinTimer_Thread(LPVOID param)
            -> 一定周期毎にItronの割り込み処理をコールしている



上記の計6つのファイルコピーが終わると、以下の状態になります。




追加した*.c/*.libをプロジェクトに追加します。





追加後、以下の様な表示になればOKです。




次にHOSカーネルのincludeフォルダをパスに追加します。
プロジェクトのプロパティを選択します。


構成プロパティ->C/C++を選択し、追加のインクルードディレクトリに以下のフォルダを追加します。

C:\HOS\hos-v4a\kernel\include





ここで再度コンパイルしてみます。
kernel_cfg.cが有りませんというエラーが出ればOKです。




※もしここで”kernel.hが有りません”エラーになる場合は、includeディレクトリの指定を再確認してください。




システムコンフィギュレーションのファイルを作成する

次は、先ほどエラーが出たkernel_cfg.cファイルを作成します。
このkernel_cifg.cとkernel_id.hというファイルは、システムコンフィグレーションファイルと呼ばれています。

システムコンフィグレーションファイル
    カーネル構成・初期化ファイル
        kernel_cfg.c
 
    ID自動割付け結果ヘッダファイル
        kernel_id.h



これらのファイルの元ネタは、system.cfgというファイルです。
system.cfgからは、以下のようにPreConfigure/Configure処理を経て作成されます。



PreConfigure処理は、C言語のプリプロセッサ機能を使用します。
VisualStudioのCコンパイラはcl.exeなので、今回はこれを利用します。PreConfigureを事前に行う事で、プログラムが使用するヘッダのdefine値を使用することが可能になります。(今回はPreConfigure処理自体は行いますが、プリプロセッサ命令を書いていないので何もしません)


次にConfigure処理を行います。
これは、カーネルの初期化パラメータや初期化処理を行うC言語プログラムを生成するコードジェネレータです。ヘッダファイルはタスクID等、連番をつける必要があるID値の付番処理を行っています。別にエディタを使って手作業でkernel_cfg.c/kernel.hを作っても良いのですが、管理の手間を省くためμITRONでは上記の処理を設けています。


という訳で、まずは以下の内容で元ネタとなるsystem.cfgを作成します。

/* μITRONコンフィグ定義(HOS) */
 
INCLUDE("\"ostimer.h\"");
INCLUDE("\"HelloWorld.h\"");
 
/* カーネルの設定 */
KERNEL_HEP_MEM(4096, NULL);
KERNEL_INT_STK(1024, NULL);
KERNEL_TIM_TIC(10, 1);
KERNEL_MAX_TSKID(8);
KERNEL_MAX_SEMID(8);
KERNEL_MAX_MBXID(8);
KERNEL_MAX_MPFID(8);
KERNEL_MAX_CYCID(8);
 
/* OSタイマの設定 */
ATT_INI({TA_HLNG, 0, OsTimer_Initialize});
 
/* タスクの登録 */
CRE_TSK(TSK_TASK1, {TA_HLNG|TA_ACT, 1, Task1, 1, 1024, NULL});



中身の詳細は、まだ分からなくても良いですが、以下の3点が行われている事を気に留めておいてください。

INCLUDE -> HelloWorld.hを指定
ATT_INI -> OsTimer_Initialize()関数を定義
CRE_TSK -> Task1()関数を定義





システムコンフィギュレーションのファイルを処理する

system.cfgを作成したら、次は実際にPreConfigure/Configure処理を行います。

スタートメニューより、Visual Studioコマンドプロンプトを実行します。



コマンドプロンプトで以下のコマンドを実行します。

cd /D C:\HOS\HOSProj\HelloWorld
cl /E system.cfg > system.i
h4acfg.exe





cl.exeを/Eオプションで実行するとプリプロセッサだけを実行させることが出来ます。”cl.exe /E”の結果は標準出力に表示されるのでリダイレクトでsystem.iファイルに出力しています。
Configureプログラムのh4acfg.exeはデフォルトでsystem.iファイルを処理するためオプションは無しです。

プログラムを実行したらkernel_cfg.c/kernel_id.hファイルが出来上がることを確認します。
kernel_cfg.cを,Visual Studioのプロジェクトに追加して、再度F5キーでビルドします。

すると、次はHelloWorld.hが無いというエラーになるはずです。



これは、kernel_cfg.cで定義されている#include “HelloWorld.h”文に対するエラーです。
この文が生成されたのは、system.cfgで以下の文を記述しており、これを元にConfigureがコード生成したからです。

INCLUDE("\"HelloWorld.h\"");



上記の作業で事前の準備は完了です。次は実際にプログラムを作成します。



プログラムを作成する

次は実際のHOSカーネルの呼び出しと、HOSで動作するタスクの定義を行います。

まずはプロジェクト作成時に自動生成されたHelloWorld.cppです。
HOSでは基本的にC++ではなくC言語で開発するので、拡張子をcpp->cに変更します。
(ソリューションエクスプローラでファイルを選択後F2キーを押せば変更できます)

HelloWorld.cを以下の内容に変更します。

#include "kernel.h"
#include "kernel_id.h"
#include "HelloWorld.h"
#include "stdafx.h"
#pragma comment(lib, "winmm.lib")   // winmm.libを使用する
 
int _tmain(int argc, _TCHAR* argv[])
{
    // HOSの起動
    vsta_knl();
    return 0;
}
 
void Task1(VP_INT exinf)
{
    // タスク1の処理
    printf( "hello world\n" );
}


#pragma命令は、wintimer.cで使用するtimeSetEvent()関数の為です。
(このpragmaが必要な理由が知りたい人はこちらを参考にしてください。)


次に、以下の内容でHelloWorld.hを作成します。

#ifndef _HELLOWORLD_H_
#define _HELLOWORLD_H_
 
void Task1(VP_INT exinf);
 
#endif



これでビルドすると、エラーがなくなるはずです。


※以下のエラーが出る場合は、HelloWorld.cppをHelloWorld.cにリネームし忘れてないか確認して下さい

LNK2001: 外部シンボル "_Task1" は未解決です。   
C:\HOS\HOSProj\HelloWorld\kernel_cfg.obj






動作を確認する

プログラムを実行し、以下のようにコンソールにHello Worldの文字が表示されれば成功です。




補足:Configure作業を自動化する

今回の説明では,作業手順を確認するためPreConfigure,Configureを手作業で実行しましたが、この方法だと開発時に作業漏れが発生することがあり不便です。
そこで、以下の作業をしておくとPreConfigure,Configureをビルドプロセスに組み込むことが出来ます。

プロジェクトからプロジェクトのプロパティを選択します。


構成プロパティ->ビルドイベント->ビルド前イベントから、コマンドラインの編集を選択します。


コマンドラインのウィンドウに、以下のコマンドを登録します。

pushd $(ProjectDir)
echo  Start PreConfigure -----------------------
cl /E system.cfg > system.i
echo  End   PreConfigure -----------------------
 
echo  Start Configure -----------------------
h4acfg.exe
echo  End   Configure -----------------------
popd





設定を行った後、ビルドを行うと自動でPreConfigure,Configureが行われます。
PreConfigure,Configureにエラーが無いかは、出力ウィンドウで確認できます。
下記は、エラーが無い場合の出力例です。

1>------ すべてのリビルド開始: プロジェクト: HelloWorld, 構成: Debug Win32 ------
1>   Start PreConfigure -----------------------
1>  Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
1>  Copyright (C) Microsoft Corporation.  All rights reserved.
1>  
1>  system.cfg
1>   End   PreConfigure -----------------------
1>   Start Configure -----------------------
1>   End   Configure -----------------------
1>  wintimer.c
1>  ostimer.c
1>  kernel_cfg.c
1>  HelloWorld.c
1>  コードを生成中...
1>  stdafx.cpp
1>  HelloWorld.vcxproj -> C:\HOS\HOSProj\HelloWorld\Debug\HelloWorld.exe
========== すべてリビルド: 1 正常終了、0 失敗、0 スキップ ==========





ここまでの作業で、非常に簡単なプログラムですが、vsta_knl()でHOSのカーネルを起動させ、カーネルからsystem.cfgで登録したタスク”Task1″がコールされるという、OS環境下でのプログラムの実行が出来ました。

次回は、μITRONにおけるタスクの考え方と、タスクの生成/終了処理を確認します。

radikaで,あまみFMを再生できるようにする方法



radikaではサイマル放送の聴取や録音が出来るのですが、あまみFMについては設定が間違っており番組登録しても聴く事が出来ません。


これは、radika.exeと同じフォルダに有るsimulradiostations.xmlをエディタで編集する事で対応できます。

simulradiostations.xmlに有る以下の記述を…

<Prefecture name="鹿児島県">
    <Station>
        <Name>あまみFM</Name>
        <Municipality>奄美市</Municipality>
        <TimeTable>24時間</TimeTable>
        <Logo>http://www.simulradio.jp/data/65.jpg</Logo>
        <Stream>rtsp://wms013.shibapon.net/AmamiFM</Stream>
        <SampleRate>48000</SampleRate>
        <WebSite>http://www.npo-d.org/</WebSite>
        <Twitter></Twitter>
        <Description></Description>
    </Station>
</Prefecture>


このように変更します。(streamタグの中身を書き換えます)

<Prefecture name="鹿児島県">
    <Station>
        <Name>あまみFM</Name>
        <Municipality>奄美市</Municipality>
        <TimeTable>24時間</TimeTable>
        <Logo>http://www.simulradio.jp/data/65.jpg</Logo>
        <Stream>rtsp://hdv.nkansai.tv/amami</Stream>
        <SampleRate>48000</SampleRate>
        <WebSite>http://www.npo-d.org/</WebSite>
        <Twitter></Twitter>
        <Description></Description>
    </Station>
</Prefecture>





既にあまみFMがラジオ局一覧に登録されている場合は、一旦右クリックメニューから局の削除を行います。


その後再度、ツールのラジオ局設定ウィザードから登録してください。


これで、あまみFMを聴く事が出来ます。



ギター弾き語り 元ちとせ ハイヌミカゼ

「未解決の外部シンボル __imp__timeSetEvent@20 が関数 xxxx で参照されました。」エラーの対処法

Win32APIのタイマーを使用したプログラムをコンパイルすると以下のエラーが出る場合があります。

未解決の外部シンボル __imp__timeSetEvent@20 が関数 xxxx で参照されました。




英語環境だと以下のメッセージになります。

error LNK2019: unresolved external symbol __imp__timeSetEvent@20 referenced in function xxxx



これは、timeSetEvent()関数がライブラリwinmm.libで定義されているのですが、ライブラリがプロジェクトに登録されていない事が原因です。



対処法は、プログラムの先頭に以下のpragma命令を書きます。
これによってwinmm.libがリンクされるのでエラーが出なくなります。

#pragma comment(lib, "winmm.lib")	// winmm.libを使用する






pragma命令による指定ではなくプロジェクトの設定でwinmm.libを登録する事も可能です。
プロジェクトのプロパティより、構成プロパティ->リンカ->入力にある、追加の依存ファイルでwinmm.libを指定してください。
指定するライブラリは、手元の環境だと以下のフォルダにありました。

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib\x64



Visual Studio 2010 スタートアップガイド

Visual Studioで実行可能なμItronの環境を構築する



国内で利用されている組み込み向けのOSとして有名なものに、μItronというものが有ります。
μItronを使用すると、非常に省資源な環境(数kByteのRAM環境など)でも、マルチタスクのプログラミングを行う事ができます。

組み込み向けのプログラムを行う場合、実機でμItronを動作させるとタスク切り替えのタイミング等が実際の動作環境と全く同じとなります。一方で、組み込み向けの開発環境(IDE)はPC向けの環境と比べると見劣りする場合も多くまたprintfデバッグを行うにも制限が多いため開発・デバッグに苦労します。

この為、実際の製品開発では実機の方がよいのですが、初学者の勉強目的にはちょっと面倒です。
そこで、今回はWindowsでVisual Studioを使用してμItronの実行環境を構築してみます。


構築した環境はWindows上で動作し、Visual Studioによるデバッグ機能も利用できます。


使用するμItronの選定


無償で利用可能なμItronは幾つか有りますが、今回はルネサスのH8マイコン向けに作成されたHOSを使用する事にします。

HOSのメリットは以下の点です。

1.ソースコードが読みやすい

HOSのソースは比較的コメントが多く、プログラムも平易な書き方を去れているため読みやすいです。
また、サービスコール(Unixで言うシステムコールの事)単位でファイルが分割されており、ソースツリーから見たい関数を探すのも簡単です。

2.ライセンスの自由度が高い

以下は、HOSのライセンスから利用条件の項目を抜粋したものです。

3. 利用条件
  利用にあたり著作権情報の改竄を行う事を禁じます。
  上記を守る限り、商用/非商用を問わず、自由に利用することが出来ます。
  利用にあたり著作権者への報告義務もありません。



HOSは著作権情報だけ残しておけば、コードをかなり自由に使用することが出来ます。他にソースが入手できるμITRONの実装には、TOPPERS/JSPが有りますが、こちらは利用の仕方によっては報告義務が課される場合が有ります。


2.環境を作るのが楽

実はHOSのソースにはVisualStudio2008,2010用のプロジェクトファイルが入ってます。
なので、プロジェクトの環境構築に掛かる手間がかなり削減できます。


μItronカーネルのライブラリ作成


Linux等のOSでは、カーネルとユーザプログラムは別の実行ファイルになりますが、HOSでは単一のexeにOS機能が同梱される形となります。
HOSをH8マイコン上で動作させる場合、OSのサービスコール呼び出しは、内部では単なる関数呼び出しの形で実装されています。

この為、まずはOSのカーネル機能部分をlibファイルにします。
別にライブラリを作成せず、プログラムを作るたびに毎回カーネルの全*.cファイルをプロジェクトに追加しても良いのですが、管理が煩雑になるので、一旦libファイルを作成しておくと便利です。


まず、下記のサイトから最新のソースをダウンロードします。
Hyper Operating System(ITRON仕様OS)


ダウンロードするのは、hos-v4advanceです。
今回の記事では、hos-v4a-20110331.zipを使用しました。


ダウンロードしたzipを展開して、以下のプロジェクトを開きます。

hos-v4a\kernel\build\win\win32\vc2010\hosv4a.sln





ソリューションの中には、h4acfgと、hosv4aという2つのプロジェクトが登録されています。

h4acfgは、itronのコンフィギュレータプログラムで、プログラムを作成する時に使用します。
hosv4aが実際のカーネルソースになり、ビルドするとカーネルのライブラリが生成されます。

両方のプロジェクトのビルドを行うと、Debugフォルダに以下のファイルが作成されます。

h4acfg.exe
hosv4a.lib





これでカーネルのライブラリが作成できました。
次回は、作成したライブラリを使用し、μItron環境下で動作するHelloWorldプログラムを作成するための手順を説明します。
 [VisualStudioでμITRON]HelloWorldプログラムを作成する

[C言語]合成抵抗の計算、分圧回路の計算を行うプログラム

抵抗を直列、並列につないだ時の抵抗値を計算する

#include <stdio.h>
 
int main()
{
	int r1;
	int r2;
 
	printf( "抵抗値1を入力してください>" );
	scanf( "%d", &r1 );
 
	printf( "抵抗値2を入力してください>" );
	scanf( "%d", &r2 );
 
 
	int serial   = r1 + r2;
	int parallel = (r1 * r2) / ( r1 + r2 );
 
	printf( "直列接続時の合成抵抗: %dΩ\n", serial );
	printf( "並列接続時の合成抵抗: %dΩ\n", parallel );
 
	return 0;
}





抵抗による電圧の分圧値を計算する

#include <stdio.h>
 
int main()
	double ans;
	int voltage;
	int r1;
	int r2;
 
	printf( "電圧を入力してください(V)>" );
	scanf( "%d", &voltage );
 
	printf( "抵抗値1を入力してください(電源側)>" );
	scanf( "%d", &r1 );
 
	printf( "抵抗値2を入力してください(GND側 )>" );
	scanf( "%d", &r2 );
 
 
	ans = (double)( voltage * r2 ) / ( r1 + r2 );
 
	printf( "中間の電圧は: %.1fV\n", ans );
 
	return 0;
}


[対処法]D8045 : C ファイル ‘xxx.c’ を /clr オプションと共にコンパイルできません のエラーが出た時にすること

Visual Studioで、拡張子が*.cのc++プログラムをコンパイルする時、以下のエラーが出る事があります。

D8045 : C ファイル 'xxx.c'/clr オプションと共にコンパイルできません






この場合は、以下の手順でコンパイルオプションを変更するとエラーを回避できる場合があります。

メニューバーの、プロジェクト->xxxのプロパティを選択します。


構成プロパティ->C/C++->詳細設定を選択します。
右側に表示されたコンパイル言語の選択を、「規定」から「C++コードとしてコンパイル」を選択します。


これで、.cファイルを.cppと見なし、C++のコンパイラが走るようになります。

[RPR-220]反射型フォトセンサを使ってみる



今回は、ローム製のRPR-220という製品を使用して、反射型フォトセンサの動作を確認します。
反射型フォトセンサというのは、LEDで照らした明かりを床などに反射させて、隣にあるセンサー(フォトトランジスタ)で受光することで対象の状態を確認するものです。

反射型フォトセンサは、フォトリフレクタや、フォトインタラプタと呼ばれる場合も有り、フォトセンサの”フォト”は光(photon)が語源です。



反射型フォトセンサの利用場面


反射型フォトセンサは、非接触で物体の有無や位置を検知したり、反射率の違いから色(白/黒)を検知できるという特徴を持っています。

ホビーユースだと、ライントレーサで床に書かれた線を認識させるのに利用する事例が多いです。

ライントレースカーは黒い床に白い線(逆の場合も有り)が書かれたコースを車が自立走行するものです。
床が白い場合は照らした光が反射するのでセンサーが反応し、黒い場合は光が吸収されてしまうのでセンサーが反応しないという仕組みでラインを認識します。

ライントレーサ(左側にセンサーが有ります)

http://www.mcr.gr.jp/amcr/mcr_history2012_100.pdf より

ライントレーサが走るコース(この例では中央が白くなっている)

http://www.mcr.gr.jp/amcr/mcr_history2012_100.pdf より

トレースカーが動作している様子です。


また、民生用ではコピー機や自動販売機の内部で、機械の特定の場所に紙(コピー用紙/紙幣)が有るかを非接触でチェックするなどの用途、他にはエンコーダ、計測器、搬送設備、医療機器などで使用されているようです。



RPR-220の特徴と外観


反射型フォトセンサは、沢山のものが有りますが、今回はローム製のRPR-220という製品を使用します。
RPR-220のデータシートはこちらです。


RPR-220は、価格が安く1個130~150円ぐらいで購入できます。
また、秋月やマルツなどのパーツショップでも販売されており、入手性も良いです。



比較的小型で、ブレッドボードやユニバーサル基板で扱う場合でも場所をとりません。
4本の足が有るのですが、ユニバーサル基板では4マス分しかスペースを取らずに配置できます。




一方高さ方向は6.5mmあり、ちょっと高めです。


RPR-220は、反射型フォトセンサの中では比較的離れた場所の検知が可能で、対象物と6mm離れた状態が最も感度が良いです。また、5mmから10mmの範囲なら、6mmの時と比べて90%以上の感度が有るので、距離に関してはそれ程シビアではないです。

データシートより

発光はGaAsの赤外線LEDを使用しているため、光っている状態が肉眼では確認できません。
反射型のフォトセンサはその仕組み上、外光の影響を受けやすいのですが、RPR-220はセンサーに可視光の遮断フィルタが入っており赤外線のみを認識するので、外光の影響をあまり受けないです。



RPR-220の発光部を確認する


フォトセンサは発光部と受光部があるのは説明したとおりですが、まずは発光部のみの確認を行います。
確認は今回3.3vの電源で行いました。(5Vでも勿論動作するのですが、今回は後でPIC24Fと組み合わせて使用したかったため、あえて3.3Vを使っています)

発光部は赤外線LEDで、データシートを見ると最大定格は50mAまで流せることが分かります。
また順電圧降下は1.34Vです。

赤外線LEDといえども普通のLEDなので、電流制限の抵抗を入れる必要があります。
3.3V駆動で定格の1/3(16mA)ぐらい流す場合の抵抗値を計算してみます。

LEDが1.3V分の電圧を持っていくので、抵抗に掛かるのは3.3-1.3 = 2Vです。
オームの法則よりR=E/Iなので、以下の式より120Ωとなります。

2(V)/0.01666(A)=120Ω


120オームは12*10^1なので、カラーコードは121の”茶赤茶※”となります。
※ちなみに5Vの電源で動作確認する場合は、(5-1.3)/0.01666 = 220Ωぐらいが適切です。

LEDの出力を上げればセンサーの出力も良くなりますが、消費電力が増すので注意が必要です。



次にピン配置を確認しておきます。

上から見たときに、ガワに欠けが無くて、白っぽく見える側が赤外線LEDです。


赤外線LEDは足の長さが異なっており、長いほうがアノード(プラス側)で短いほうがカソード(マイナス側)です。




スペックを確認したら、実際にブレッドボードで確認してみます。

まず、刺し方ですが、以下のように各ピンが同じ列に入らないように注意してください。形状が四角なので慣れていても油断していると間違えがちです。


以下は、悪い例です。これではアノードとカソードがショートします。


先ほど計算した120Ωの抵抗をつけて、回路を電源を繋ぎます。
RPR-220のピンは、右から順にアノード、カソード、コレクタ、エミッタです。

電源を入れても、発光しているのは赤外線なので肉眼では確認できません。


このような場合は、携帯電話のカメラを使用すると良いです。
大抵の携帯のカメラは赤外線を写すことが出来るので、発光しているかどうかを確認できます。但し、携帯も機種によっては赤外線が移らないようにフィルタが入っている場合もあります。フィルタがあるかは赤外線リモコンの発光部を撮ってみると分かります。iPhoneの場合は背面のカメラは赤外線防止フィルタが有るのでダメですが、自分撮り用のフロントカメラならイケるらしいです。


カメラ越しに見ると以下の様に紫っぽい色で発光しているのが分かります。


真上から見ると、さらに良く分かりますね。



RPR-220の受光部を確認する

次に、受光部の振る舞いを確認するために、センサー側の回路を作ります。

センサーはフォトトランジスタになっており、E(エミッタ)と、C(コレクタ)のピンが出ています。

フォトトランジスタは基本的に普通のトランジスタと同じですが、ベースに電流を流す代わりに光を当てる事でエミッタとコレクタの間に電流が流れるようになります。光が入ってこない場合(=床が黒い場合)はCとEの端子が切断され、光が入ってくる場合(床が白い場合)にC-Eの端子がつながるような振る舞いをします。

通常はコレクタをVccに接続し、エミッタを抵抗経由でGNDに接続します。抵抗はセンサーの感度を微調整するために可変抵抗にします。可変抵抗のみだと抵抗を完全に絞ったときに0Ωとなり大電流が流れてしまう為、固定抵抗を直列で繋ぎます。(大電流が流れるとフォトダイオードが壊れてしまいます)

センサー側の回路を作った状態です。

固定の抵抗は、2.2kΩ、可変抵抗は10kΩにしました。
センサーの状態を確認するため、固定抵抗と可変抵抗の間からLEDを繋いでます。


センサーの前にモノが無い場合(又は黒い紙をかざした時)はLEDが光りませんが…


白い紙をかざすと、赤外線が紙に反射してセンサーが反応するため、LEDが点灯します。
(可変抵抗はMAXにしておき、徐々に絞ってみてください)


実際にやってみるとわかるのですが、センサーがアナログなので紙の近づける距離や紙の色によって、LEDの明るさは緩やかに(アナログ的に) 変化します。


RPR-220のセンサー出力をマイコンで扱いやすくする


前述の回路で、反射型フォトセンサは使用できているのですが、センサが拾った値をマイコンに取り込む時、アナログ的な出力変化だと扱いが面倒です。アナログ入力が有るマイコンならアナログ値のまま取り込むという選択肢も有りますが、A/Dコンバータはデータを読み込む(=入力電圧を確定する)のに時間が掛かるため、センサー値を高速に判断したい場合は不向きです。

この為、センサの出力をデジタル化するために、ロジックICを挟み込みます。
ロジックICは74HC04のインバータでも良いのですが、これだとセンサーの出力がICの閾値付近になった場合High/Lowがバタつくので、シュミットトリガ付きの74HC14を使用したほうがマイコンのソフト側の処理が楽チンです。

という訳で、74HC14のICを追加した回路です。


先ほどの回路のLEDがあったところをICに入力させ、出力側をLEDにしています。


ICをかませた後のLEDを確認すると分かるのですが、LEDのON/OFFがデジタルになるのではっきりします。74HC14の出力をマイコンに入れれば良いです。

[PIC24]UART機能を使用してRS-232C通信を行う

PIC24でUART機能を使用するサンプルです。
とりあえず動いたので、メモ代わりにアップしておきます。

設定は以下の通り。

PIC24FJ32GA002で確認
UART1を使用
送受信処理はポーリングベース(割り込みは使わない)
 
9600bps
8bit
ノンパリティ
ストップ 1bit
 
TxDのピン: RP5
RxDのピン: RP6



PICからの出力はFT2232経由でPCに取り込みます。

#include <p24FJ32GA002.h>
#include "uart.h"
#include "PPS.h"
 
#define UART_CLK8MHZ_9600BPS 25
 
//--------------------------------------
// コンフィグレーションビットの設定
//--------------------------------------
_CONFIG2(   POSCMOD_NONE  &       // Primary oscillator disabled
            IOL1WAY_OFF   &       // IOLOCK may be changed via unlocking seq
            OSCIOFNC_ON   &       // OSC2/CLKO/RC15 functions as port I/O (RC15)
            FCKSM_CSDCMD  &       // Clock switching and Fail-Safe Clock Monitor are disabled
            FNOSC_FRC     &       // Fast RC Oscillator (FRC)
            WUTSEL_LEG            // Legacy Wake-up Timer
        );
 
_CONFIG1 (  FWDTEN_OFF &          // Watchdog Timer is disabled
            ICS_PGx1   &          // Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1
            COE_ON     &          // Reset Into Clip On Emulation Mode
            BKBUG_ON   &          // Device resets into Debug mode
            GWRP_OFF   &          // Writes to program memory are allowed
            GCP_OFF    &          // Code protection is disabled
            JTAGEN_OFF            // JTAG port is disabled
         );
 
 
int main( void )
{   
    volatile int recvCount = 0; // 受信文字数
    volatile int errCount  = 0; // 受信エラー回数
    volatile int waitCount = 0; // 送信待ち回数
 
 
    //------------------------------------
    // クロックの定義
    //------------------------------------
    OSCCONbits.COSC  = 0; // CPUクロックにFast RC Oscillator (FRC)を使用
    CLKDIVbits.DOZE  = 0; // クロックのレート1:1(postscaler)
    CLKDIVbits.RCDIV = 0; // 8Mhz(prescaler)
 
 
    //------------------------------------
    // RPピンの役割マッピング設定
    //------------------------------------                              
    iPPSOutput( OUT_PIN_PPS_RP5, OUT_FN_PPS_U1TX ); // RP5を UART1のTXDとして使用
    iPPSInput(  IN_FN_PPS_U1RX,  IN_PIN_PPS_RP6  ); // RP6を UART1のRXDとして使用
 
 
    //----------------------
    // UART1のオープン
    //----------------------
    CloseUART1();
 
    unsigned int config1 =  UART_EN &               // UART有効
                            UART_IDLE_CON &         // アイドル時も動作させる
                            UART_DIS_WAKE &         // スリープ時はWakeupさせない
                            UART_IrDA_DISABLE &
                            UART_DIS_LOOPBACK &     // ループバックさせない
                            UART_NO_PAR_8BIT &      // パリティなし8bit
                            UART_1STOPBIT &         // ストップビット1
                            UART_MODE_SIMPLEX  &    // フローコントロール無し
                            UART_UEN_00 &
                            UART_UXRX_IDLE_ONE &    // U1RXのアイドルを1にする
                            UART_BRGH_SIXTEEN &     // 高速転送を無効
                            UART_DIS_ABAUD;         // ボーレートの自動検出を無効
 
    unsigned int config2 =  UART_INT_TX_BUF_EMPTY &  // TXDバッファが空のとき割り込みを発生
                            UART_IrDA_POL_INV_ZERO & // U1TXアイドルを0とする
                            UART_TX_ENABLE &         // TXDを有効(同時にRXDも有効になる
                            UART_INT_RX_CHAR &       // RXD受信時に割り込みを発生
                            UART_ADR_DETECT_DIS & 
                            UART_RX_OVERRUN_CLEAR;  
 
    OpenUART1(config1, config2, UART_CLK8MHZ_9600BPS );
 
    ConfigIntUART1( UART_RX_INT_DIS & UART_TX_INT_DIS ); 
 
 
 
    //-----------------------------
    // シリアル送受信の無限ループ
    // 一定周期で文字を送信します
    //-----------------------------
    while(1) {
        volatile unsigned int recvText;
        volatile long int loop;
 
        //-------------------------
        // 定期的に文字を送信する
        //-------------------------
 
        // 1byte書き込み
        U1TXREG = 'a';
 
        //バッファが空になるまで待つ(TRMT:bit8)
        while ( U1STAbits.TRMT == 0 ) {
            waitCount++;
        }
 
        // ちょっと待つ     
        for ( loop = 0; loop < 10000; loop++) { 
            /* nop */;
        }
    } 
 
    return 0;
}



今回、Microchip PIC24FのPeripheral Libraryを使用してみました。


RPピンの役割マッピング設定の…

    iPPSOutput( OUT_PIN_PPS_RP5, OUT_FN_PPS_U1TX ); // RP5を UART1のTXDとして使用



は、以下のコードと等価です。

    RPOR2bits.RP5R = 3;






また、OpenUART1()は、以下のコードと同じです。

    // I/Oレジスタを直接指定する場合
    U1MODEbits.UARTEN = 1;
    U1MODEbits.USIDL  = 0;
    U1MODEbits.IREN   = 0;
    U1MODEbits.RTSMD  = 1;
    U1MODEbits.UEN    = 0;
    U1MODEbits.WAKE   = 0;
    U1MODEbits.LPBACK = 0;
    U1MODEbits.ABAUD  = 0;
    U1MODEbits.RXINV  = 0;
    U1MODEbits.BRGH   = 0;  
    U1MODEbits.PDSEL  = 0;
    U1MODEbits.STSEL  = 0;
 
    U1STAbits.UTXISEL1 = 0;
    U1STAbits.UTXINV  = 0;
    U1STAbits.UTXBRK  = 0;
    U1STAbits.UTXEN   = 1;
    U1STAbits.URXISEL = 0;
    U1STAbits.ADDEN   = 0;
 
    U1BRG = UART_CLK8MHZ_9600BPS;





実際の文字送信ループは、以下のコードに変更すると渡された文字のエコーバックが行えます

    while(1) {
        //---------------------------------------
        // 受信エラーが発生していないかチェック     
        //---------------------------------------
        if ( U1STAbits.PERR == 1 || U1STAbits.FERR == 1 || U1STAbits.OERR == 1 ) {
            errCount++;
        }
 
        //-----------------------------         
        // バッファから文字を受信する
        //-----------------------------         
        if ( U1STAbits.URXDA == 1 ) {
            recvText = U1RXREG;
            recvCount++;
 
            //-----------------------------         
            // エコーバックする         
            //-----------------------------         
            U1TXREG = recvText;
 
            //バッファが空になるまで待つ
            while ( U1STAbits.TRMT == 0 ) {
                waitCount++;
            }   
        }
    }



送受信処理は、byte単位での送信に加えて、文字列単位のgetsUART1()/putsUART1() も使用できるようです。

getsUART1(
    unsigned int length, 
    unsigned int * buffer, 
    unsigned int uart_data_wait
);
 
putsUART1(
    unsigned int * buffer
);



[PIC,C30]#define USE_AND_OR の意味

PICでC30コンパイラを使用したプログラムサンプルを見ると、以下のdefineをしている場合があります。

#define USE_AND_OR



このdefineは、ライブラリの下位互換のために使用されています。



PIC24Fのperipheral libraryでI/Oレジスタのフラグ設定方法が、v3.00から変わった事に伴う対応です。

ver3.00までは、複数のフラグを設定する時ORで結合していました。

ConfigIntUART2(UART_RX_INT_EN | UART_RX_INT_PR6 | UART_TX_INT_EN | UART_TX_INT_PR6);



これが、ver3.01以降ではANDに変わりました。

ConfigIntUART2(UART_RX_INT_EN & UART_RX_INT_PR6 & UART_TX_INT_EN & UART_TX_INT_PR6);




ver3.01以降でver3.00のプログラムを流用する場合は、USE_AND_ORをdefineしておくと、今までどおりorで結合させる事が出来る様になります。


このdefineが、どの様に使われているかuart.hを見ると、以下の様な定義がされています。

#ifndef USE_AND_OR /* Format for AND_OR based bit setting */
/* defines for UxMODE register */
#define UART_EN                 0xFFFF/* Module enable */
#define UART_DIS                0x7FFF/* Module disable */
...
 
#else /* Format for backward compatibility (AND based bit setting). */
 
/* defines for UxMODE register */
#define UART_EN                 0x8000 /* Module enable */
#define UART_DIS                0x0000 /* Module disable */
...
 
#endif


ヘッダを見ても、USE_AND_ORがdefineされていると該当のビットだけが立っており(例えばUART_ENが0x8000でdefineされている)、orで結合できることが確認できます。

PIC24で出力コンペアを使用して,指定した周波数で矩形波を出力する

PIC24Fには、タイマモジュールを使用した出力コンペア機能があります。
出力コンペア機能には、以下の3つのモードがあります。

1.シングル比較一致モード
 
2.デュアル比較一致モード
        単一出力パルスモード
        連続出力パルスモード
 
3.単純パルス幅変調モード
        フォルト保護入力付き
        フォルト保護入力なし



今回はこの中で、2.デュアル比較一致モードの連続出力パルスモード機能を使用してみます。
この機能を使用すると、任意の周波数/デューティー比で矩形波を出力でき、矩形波の出力によってスピーカから音を出したり、モータのPWM制御を行えます。

今回の動作確認にはPIC24FJ32GA002を使用しました。



作業の流れ

出力コンペアを使用した矩形波出力のプログラムは、以下の手順で設定を行います。

コンフィグレーションビットの指定
クロックの定義
Timer2の設定
RPピンの役割マッピング設定
出力コンペアモジュールの設定



出力コンペア機能を使用するためには、どのピンで波形の出力を行うかをRPピンの役割マッピングで定義する必要があります。また、コンペア対象となる値をタイマーで作成するため、タイマーモジュールも使用します。タイマーのカウントスピードはCPUのクロック周波数にも依存するため、クロックやコンフィグの定義も確認しておきます。

それでは、各ステップの具体的な作業を順に確認していきます。


コンフィグレーションビットの指定

出力コンペア機能の使用有無に限らず、PICのプログラムを作成する場合は、PICの振る舞いを決めるためにコンフィグレーションビットの設定が必要です。

今回は以下の設定にしました。

#include <p24FJ32GA002.h>
 
//--------------------------------------
// コンフィグレーションビットの設定
//--------------------------------------
_CONFIG2(   POSCMOD_NONE  &       // Primary oscillator disabled
            IOL1WAY_OFF   &       // IOLOCK may be changed via unlocking seq
            OSCIOFNC_ON   &       // OSC2/CLKO/RC15 functions as port I/O (RC15)
            FCKSM_CSDCMD  &       // Clock switching and Fail-Safe Clock Monitor are disabled
            FNOSC_FRC     &       // Fast RC Oscillator (FRC)
            WUTSEL_LEG            // Legacy Wake-up Timer
        );
 
_CONFIG1 (  FWDTEN_OFF &          // Watchdog Timer is disabled
            ICS_PGx1   &          // Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1
            COE_ON     &          // Reset Into Clip On Emulation Mode
            BKBUG_ON   &          // Device resets into Debug mode
            GWRP_OFF   &          // Writes to program memory are allowed
            GCP_OFF    &          // Code protection is disabled
            JTAGEN_OFF            // JTAG port is disabled
         );



コンフィグレーションビットでは、FNOSC_FRCで内部オシレータを使用させています。
ほかの定義は、今回のプログラムの本質ではないので説明は省略します。


クロックの定義

次にクロックの定義をします。
今回は、PIC内蔵のオシレータを8MHzで使用します。

    //------------------------------------
    // クロックの定義
    //------------------------------------
    OSCCONbits.COSC  = 0; // CPUクロックにFast RC Oscillator (FRC)を使用
    CLKDIVbits.DOZE  = 0; // クロックのレート1:1(postscaler)
    CLKDIVbits.RCDIV = 0; // 8Mhz(prescaler)
    // OSCTUNbits.TUN = 0; // クロックの誤差微調整 0:調整なし
    // OSCCONbits.SOSCEN = 1;// セカンダリオシレータ(32kHz)を有効にする



内部オシレータを使用するには、_CONFIG2()でFNOSC_FRCの定義が必要です。
今回は使用しませんが、オシレータの個体差によるずれを微調整するためのレジスタとしてOSCTUN.TUNが有ります。

Timer2の設定

出力コンペアモジュールは内部的にタイマモジュール(TMRn)を使用します。
このタイマーの周期にしたがって波形が出力されるため、この値は出力される周波数に関係します。

    //------------------------------------
    // Timer2の設定
    //------------------------------------
    PR2             = 4000; // Timer2の最大値(この値でTMR2を0にリセットする)
    T2CONbits.TCKPS = 0;    // Timer2 プリスケーラ 0:1/1, 1:1/8, 2:1/64
    T2CONbits.TON   = 1;    // Timer2 ON



出力コンペアを使用する場合、元にするタイマはTimer2かTimer3のどちらかが使用できますが、今回はTimer2を使うことにします。タイマモジュールも幾つかの動作モードが有りますが、今回は16bitタイマのモードを分周せずに使う事にします

16bitモードなので、最大では0~65535までカウントさせる事が出来ますが、今回は説明を簡単にさせるために、4000までカウントしたらリセットさせる事にします。
タイマーの最大値はPR2レジスタにセットします。

値をセットした後、T2CONレジスタのTONを1にするとカウンタが動作します。
デバッガやシミュレータで、ステップ実行させてTMR2レジスタを観察すれば値が増えていく事がわかります。

RPピンの役割マッピング設定

タイマが用意できたら、次は波形を出力させるピンを決めます。
PIC24FJxxGA002では、出力コンペア結果の出力先をRP0~15のピンに割り当てる事が可能です。

今回は、RP5のピンに割り当てる事にしました。

    //------------------------------------
    // RPピンの役割マッピング設定
    //------------------------------------
    RPOR2bits.RP5R = 18;   // RP5ピンの機能を, 18:OC1に割り当てる
//  RPOR3bits.RP6R = 19;   // RP6ピンの機能を, 19:OC2に割り当てる


RPxの出力ピン機能割り当てはRPORxレジスタにFunctionCodeをセットすることで設定します。
RP5の機能設定はRPOR2レジスタのRP5R、出力コンペア1(OC1)のFunctionCodeは18なので、上記のコードとなります。

コメントアウトされている行では、 RP6ピンの機能を19:OC2に割り当てています。
PIC24FJxxGA002では、出力コンペアモジュールが5個内蔵されているので、18:OC1~22:OC5のFunctionCodeが指定できます。



出力コンペアモジュールの設定


次は、いよいよ出力コンペアモジュールの設定です。
ここまでの設定でOC1,Timer2モジュールを使用する事にしたので、以下の様な設定になります。

    //------------------------------------
    // 出力コンペアモジュールの設定
    //------------------------------------
    OC1CONbits.OCTSEL = 0;      // 0:Timer2 Clock 1:Timer3
    OC1CONbits.OCM    = 5;      // 5:ダブル比較連続パルス(OC1RでHigh,OC1RSでLow)
    OC1R              = PR2;    // Highになるタイミング
    OC1RS             = PR2/2;  // Low になるタイミング


出力コンペアモジュールの1番目を使用するので、設定するレジスタは、OC1CON、OC1R、OC1RSになります。
またこのモジュールが使用するタイマーはTimer2なので、OC1CONbits.OCTSELに0:Timer2を指定します。


ここまでの設定で、出力される波形の周波数、On/Offの比率(デューティー比)が決まります。
周波数は、以下の式で求まります。

周期: (PRx+1) * 1/クロック周波数 * Timer2の分周 * 2
      = (4000+1) * 1/8Mhz * 1 * 2 
      ≒ 1/1000


という訳で周期が1mSecとなり、1kHzの波形が出力されます。
周波数は、式を見て分かるとおり、PRの値・Timer2の分周・CPUクロックに応じて決まります。


矩形波がOn/OffするタイミングはOC1R,OC1RSの値で決まります。
今回は、OC1RをPR2と同じ値、OC1RSがPR2の半分に設定したので、TMR2レジスタの値が0~2000の時はLow、2001~4000の時はHighというDuty比50%の波形になります。



サンプルプログラム

という訳で、今回作成したサンプルのプログラムです。

#include <p24FJ32GA002.h>
 
//--------------------------------------
// コンフィグレーションビットの設定
//--------------------------------------
_CONFIG2(   POSCMOD_NONE  &       // Primary oscillator disabled
            IOL1WAY_OFF   &       // IOLOCK may be changed via unlocking seq
            OSCIOFNC_ON   &       // OSC2/CLKO/RC15 functions as port I/O (RC15)
            FCKSM_CSDCMD  &       // Clock switching and Fail-Safe Clock Monitor are disabled
            FNOSC_FRC     &       // Fast RC Oscillator (FRC)
            WUTSEL_LEG            // Legacy Wake-up Timer
        );
 
_CONFIG1 (  FWDTEN_OFF &          // Watchdog Timer is disabled
            ICS_PGx1   &          // Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1
            COE_ON     &          // Reset Into Clip On Emulation Mode
            BKBUG_ON   &          // Device resets into Debug mode
            GWRP_OFF   &          // Writes to program memory are allowed
            GCP_OFF    &          // Code protection is disabled
            JTAGEN_OFF            // JTAG port is disabled
         );
 
int main( void )
{
    //------------------------------------
    // クロックの定義
    //------------------------------------
    OSCCONbits.COSC  = 0; // CPUクロックにFast RC Oscillator (FRC)を使用
    CLKDIVbits.DOZE  = 0; // クロックのレート1:1(postscaler)
    CLKDIVbits.RCDIV = 0; // 8Mhz(prescaler)
 
    // OSCTUNbits.TUN = 0; // クロックの誤差微調整 0:調整なし
    // OSCCONbits.SOSCEN = 1;// セカンダリオシレータ(32kHz)を有効にする
 
    //------------------------------------
    // Timer2の設定
    //------------------------------------
    PR2             = 4000; // Timer2の最大値(この値でTMR2を0にリセットする)
    T2CONbits.TCKPS = 0;    // Timer2 プリスケーラ 0:1/1, 1:1/8, 2:1/64
    T2CONbits.TON   = 1;    // Timer2 ON
 
    //------------------------------------
    // RPピンの役割マッピング設定
    //------------------------------------
    RPOR2bits.RP5R = 18;   // RP5ピンの機能を, 18:OC1に割り当てる
//  RPOR3bits.RP6R = 19;   // RP6ピンの機能を, 19:OC2に割り当てる
 
 
    //------------------------------------
    // 出力コンペアモジュールの設定
    //------------------------------------
    // OC1(出力コンペア1)をオープンする(OC1CONの値、OC1RSの値,OC1Rの値)
    //  周期: (PRx+1) * 1/クロック周波数 * 2
    //        = (4000+1) * 1/8Mhz * 2 
    //         ≒ 1/1000    -> なので1kHzの波形が出るはず
    OC1CONbits.OCTSEL = 0;      // 0:Timer2 Clock 1:Timer3
    OC1CONbits.OCM    = 5;      // 5:ダブル比較連続パルス(OC1RでHigh,OC1RSでLow)
    OC1R            = PR2;      // Highになるタイミング
    OC1RS           = PR2/2;    // Low になるタイミング
 
 
    while( 1 ) {
        /* nop */;
    }    
 
    return 0;
}



このプログラムを実行させると、RP5のピンから1kHzの波形が出力されます。
本当に波形が出ているかは、オシロスコープなどを使用しないと分からないのですが、1kHzだったらスピーカを繋ぐことで確認する事ができます。


手元にあった8オーム,0.5Wの小さなスピーカを直結してみましたが、十分な音量で聞くことが出来ました。1kHzの音がどれくらいの高さかは、以下のwavファイルで確認できます。
1kHz.wav
500Hz.wav
440Hz.wav

PR1レジスタの値を変えると周期(音の高さ)を変えることが出来ます。
PR2=4000 -> 8000に変更すると周期が2倍になるので、500Hzになります。
500Hzはドレミで言うと、”シ”(B3)の音に近いです。

応用:スピーカーを使ってメロディを奏でてみる


今回作成したプログラムでは、決められた周期の波形を出すものでした。
これを、タイミングにあわせてPR1レジスタの値を変えていくことで音楽を奏でる事が出来ます。

試しに”カエルの歌”を演奏させるプログラムを作成してみました。

#include <p24FJ32GA002.h>
 
// 各音階のdefine定義
#define R    0
#define C3   1
#define CS3  2
#define D3   3
#define DS3  4
#define E3   5
#define F3   6
#define FS3  7
#define G3   8
#define GS3  9
#define A3   10
#define AS3  11
#define B3   12
#define C4   13
#define CS4  14
#define D4   15
#define DS4  16
#define E4   17
#define F4   18
#define FS4  19
#define G4   20
#define GS4  21
#define A4   22
#define AS4  23
#define B4   24
#define C5   25
#define CS5  26
#define D5   27
#define DS5  28
#define E5   29
#define F5   30
#define FS5  31
#define G5   32
#define GS5  33
#define A5   34
#define AS5  35
#define B5   36
//--------------------------------------
// コンフィグレーションビットの設定
//--------------------------------------
_CONFIG2(       POSCMOD_NONE  &       // Primary oscillator disabled
            IOL1WAY_OFF   &       // IOLOCK may be changed via unlocking seq
            OSCIOFNC_ON   &       // OSC2/CLKO/RC15 functions as port I/O (RC15)
            FCKSM_CSDCMD  &       // Clock switching and Fail-Safe Clock Monitor are disabled
            FNOSC_FRC     &       // Fast RC Oscillator (FRC)
            WUTSEL_LEG            // Legacy Wake-up Timer
        );
 
_CONFIG1 (  FWDTEN_OFF &          // Watchdog Timer is disabled
            ICS_PGx1   &          // Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1
            COE_ON     &          // Reset Into Clip On Emulation Mode
            BKBUG_ON   &          // Device resets into Debug mode
            GWRP_OFF   &          // Writes to program memory are allowed
            GCP_OFF    &          // Code protection is disabled
            JTAGEN_OFF            // JTAG port is disabled
         );
 
int main( void )
{
    volatile unsigned long int loop;
 
    // 各音階に応じたPR2レジスタの値
    int pr2data[] = { 0, 15289, 14431, 13621, 12857, 12135, 11454, 10811, 10204, 9632, 9091, 8581, 8099, 7645, 7216, 6811, 6428, 6068, 5727, 5406, 5102, 4816, 4545, 4290, 4050, 3822, 3608, 3405, 3214, 3034, 2863, 2703, 2551, 2408, 2273, 2145, 2025, };
 
    // 奏でるメロディ
    int melody[]  = { C3, D3, E3, F3, E3, D3, C3, R,  
                      E3, F3, G3, A3, G3, F3, E3, R, 
                      C3, R, C3, R, C3, R, C3, R, 
                      C3,C3, D3,D3, E3,E3, F3,F3, E3, E3, D3, D3, C3, C3, R, R, R  };
 
    //------------------------------------
    // クロックの定義
    //------------------------------------
    OSCCONbits.COSC  = 0; // CPUクロックにFast RC Oscillator (FRC)を使用
    CLKDIVbits.DOZE  = 0; // クロックのレート1:1(postscaler)
    CLKDIVbits.RCDIV = 0; // 8Mhz(prescaler)
 
    //------------------------------------
    // Timer2の設定
    //------------------------------------
    PR2   = 4000;   // この値でTMR2を0クリアする
    T2CONbits.TCKPS = 0;    // Timer2 プリスケーラ 0:1/1, 1:1/8, 2:1/64
    T2CONbits.TON   = 1;    // Timer2 ON
 
    //------------------------------------
    // RPピンの役割マッピング設定
    //------------------------------------
    RPOR2bits.RP5R = 18;   // RP5ピンの機能を, 18:OC1に割り当てる
 
    //------------------------------------
    // 出力コンペアの設定
    //------------------------------------
    // OC1(出力コンペア1)をオープンする
    // 周期: (PRx+1) * 1/クロック周波数 * 2
    OC1CONbits.OCTSEL = 0;      // 0:Timer2 Clock 1:Timer3
    OC1CONbits.OCM    = 6;      // 5:コンペアごとにOC1を反転 5:ダブル比較連続パルス(OC1RでHigh,OC1RSでLow)
    OC1R            = PR2;      // Highになるタイミング
    OC1RS           = PR2/2;    // Low になるタイミング
 
 
    //-------------------
    // 音楽を鳴らす
    //-------------------
    while( 1 ) {
        int loop2;
        for ( loop2 = 0; loop2 < sizeof( melody ) / sizeof( melody[0] ); loop2++ ) {
            // この値でTMR2を0クリアする-> 周波数を決める
            PR2   = pr2data[ melody[loop2] ];
 
            if ( melody[loop2] == 0 ) {
                // R:休符のときは音を止める
                PR2 = 0;
            } else {
                OC1R            = PR2;    // Highになるタイミング
                OC1RS           = PR2/2;  // Low になるタイミング
            }
 
            // 指定した音をこの時間鳴らす(テンポの設定)
            for ( loop = 0; loop < 130000; loop++ ) {
                /* nop */;
            }
 
            // 小さな無音時間(区切り)を作る
            PR2 = 0;
            for ( loop = 0; loop < 100; loop++ ) {
                /* nop */;
            }
 
        }
    }
 
    return 0;
}



pr2dataの配列に、各音階に応じたPR2の値をセットしています。
値は近似値で誤差が有るので、ちょっと音痴なメロディーになっているかもしれません。
こんな感じで再生されます。 → kaeru.wma

演奏する曲は、melodyの配列で指定してます。
簡単なサンプルなので音の長さは指定できませんが、値を色々変えてみて遊んでみてください(ループの130000を可変にすることで、音の長さを調整できます)。

また、PR2に設定するデータ指定を配列からではなく、外部に接続したスイッチからにすれば電子オルガンを作ることも出来ます。

[PIC]PICの資料にある”R-M-W”の意味

PICについて書かれた資料や書籍を見ると、出力ピンの説明で”R-M-W”と書かれている場合があります。
このR-M-Wは、Read,Modify,Writeの略です。

PICでは出力ピンの特定ビットのみ(例えばPortBのRB0)を変更する場合でも、一旦PortBの全ビットに紐付くピンの電圧を読み取った後(Read)、プログラムで指定した値に書き換えられ(Modify)、PortBの全ビットを出力する(Write)という振る舞いをします。

これは、アセンブラだとBSET,BCLRなどの特定ビットを操作する命令で起きます。


プログラマは、データを書き込んでいるだけのつもりでもPICの回路上では一旦現状の値を読み取っています。ですので、外部回路で出力ピンの電圧が変わるようになっていると一度セットしたはずの値が勝手に変わってしまう為、注意が必要です。

R-M-W機能に伴う問題

“外部回路で出力ピンの電圧が変わるよう”な回路というのは、例えば以下の様なものです。

出力ピンにノイズ除去等の目的でバイパスコンデンサが入っていると、PIC側で出力をLow->Highに変更しても、直ぐに電圧が+5Vにはあがってくれません。これは、コンデンサに電荷が蓄積される間の遅延が発生するためです。

RB0の出力ピンにパスコンが入っている場合、下記のコードだと問題が発生する場合があります。

PORTB |= 0x01;  // RB0をHighにする (RB0にパスコンが入っていると仮定します)
PORTB |= 0x02;  // その後RB1をHighにする


2行目のRB1をHighにする際に、R-M-Wの機能でRB0の値が一旦Readされます。
この際、パスコンによる作用でRB0の値はまだLowだったら、RB1をWriteする時に意図せずRB0がLow(=0)にされてしまいます。



PIC18,PIC24シリーズ以降でのR-M-W問題の対策

PIC16シリーズでは、ピンの書き込みにR-M-Wの仕組みを使用しているため、前述の問題が発生しましたが、後継のPIC18,PIC24シリーズでは、対策が採られています。

出力ピンの直前に,マイコン内部で”LATx: Data Latch register”というものが加えられ、自分が直近に出力した値がラッチメモリに記憶されるようになりました。これにより、R-M-Wが行われても、Readするデータが実際のピンの電圧ではなく記憶したラッチ情報になるので、意図しないデータの変化を防ぐようになっています。

[C言語基礎]switch文で偶数・奇数の判定を行う方法

int型の変数に入っている値をswitch文で偶奇判定するプログラムです。

int a = ...;
switch( a % 2 ) {
    case 0:
        printf( "偶数です\n" );
        break;
    case 1:
        printf( "奇数です\n" );
        break;
}



入力された値が、偶数か奇数かを判定するには2で割った余りを見れば良く、余りが無ければ偶数で有れば偶数となります。C言語で2で割った余りを求めるには、剰余演算子の”%”を使用します。


ちなみに、上記の例はswitchでしたが、偶奇の判定は2分岐なのでifで書いたほうがすっきりします。

int a = ...;
if ( a % 2 == 0 ) {
    printf( "偶数です\n" );
} else {
    printf( "奇数です\n" );
}




ifの条件式はC言語の仕様上、”0の時はfalse,0以外の時trueと見なす”というルールです。
なので、上記コードはさらに簡略化できます。

int a = ...;
if ( a % 2 ) {
    /* 条件式がtrueになる -> 2で割った余りが0ではない -> 奇数である */
    printf( "奇数です\n" );
} else {
    printf( "偶数です\n" );
}




4798101036
Cの絵本―C言語が好きになる9つの扉