I2C 自分が理解するための試み

 No.13以後のものは正式のI2C通信例と考えています。それまでのものは基準を持ちませんでしたのでまちがいがあります。データシートたよりの模索の過程に過ぎません。ご注意下さい。

22 室温計 MAXIM DS7505センサ使用     2011.03.06
21 遊び心のI2Cマスターボード(TeraTerm制御)     2011.02.04
20 4桁7セグメントLED表示スレーブ(Tiny2313)-- 新方式     2011.01.24
19 I2C通信のシーケンスに思うこと     2011.01.20
18 自作機器におけるI2C通信の実験 (改題しました)    2010.12.21
17 MAXIM DS3231 RTCを用いた単純時計 mega88 Tiny2313     2010.12.14
16 16桁×2 LCD表示スレーブ(Tiny2313)     2010.06.22
15 4桁7セグメントLED表示スレーブ(Tiny2313) 苦闘中 → 強引に解決  
14 wsNak中尾氏プログラムのトレースと一部変更 mega88--mega88 1バイト送受信 2010.05.26
13 RTC-8564の読み取り mega88 TeraTermへ出力 2010.05.14
  -------------------------------
12 mega48-tiny2313(USI)7segLED表示 TeraTerm入力を表示 2007.09.10
11 mega48-tiny2313(USI)LCD表示 TeraTerm入力を表示 2007.09.07
10 tiny2313のUSI機能 その3 割込でフラグ、処理はメイン 2007.09.04
9 tiny2313のUSI機能 その2 割込を使わない場合 2007.09.02
8 tiny2313のUSI機能 2007.08.31
7 mega48−tiny2313LCD表示 2007.08.24
6 TWIラーニングメモ 2007.08.24
5 TWIによるLED点灯実験(mega48−mega48) 2007.08.17
4 TWI説明を読む(mega48データシート日本語版) 2007.08.15
3 サブセットで液晶表示・LED表示・スイッチ読み取り GPIO使用 2007.08.12
2 超サブセットで液晶表示 機能の一部だけでデータの流し込み 2007.07.20
1 プロローグ 2007.07.20












22 室温計 MAXIM DS7505センサ使用     
 MAXIMの温度センサICをWEBで知り、サンプルをいただきました。0〜75℃の範囲で±0.5℃の精度を有するものだそうです。アナログ出力のものでは実装の方法や比較電圧で値が変わりますが、このICではI2C出力ですから実装による誤差は入らないと考えました。電源電圧も1.7〜3.7Vですから電源の安定性も問題にならないでしょう。
市販の温度計は、特にデジタル表示のものは、同じものを店頭で比べても2℃以上の差がある場合が多いようです。どれを信じたらよいのか迷います。もし、額面どおりの±0.5℃なら日常使用には問題がありません。


mega168Pをマスタに室温計としてまとめました。

なお、このICは電源は〜3.7Vと低電圧ですが、IOは5Vトレラントとなっていますので、マスタはこれまでどおりのVcc5Vとして、DS7505センサのVccだけ抵抗で分圧して3.2Vとしています。消費電流が750μAと少ないので抵抗分割で問題はないと思います。

実装は写真のとおりですが、左の1号機モジュールは種々の実験の結果ハンダが汚くなっています。右側の2号機モジュールでは熱慣性を小さくするためにリン青銅板に糊付けして細いUEWで結線しました。(しかし、あまり変わりは無いようです)

回路図は省略しますが、結線は概ね次のとおりです(最近は自分でわからなくなるので概要をプログラムのコメントとして書くことにしています)。それをここに再掲します。
  MCU=ATmega168p  RC-8MHz (FCPUは makefile で指定)
  fuse -fL11100010 -fH11011111 -fx00000001   
  
  01 reset          →sw              28 SCK(PC5)       →I2C
  02 RXD(PD0)       →LED7a           27 SDA(PC4)       →I2C
  03 TXD(PD1)       →LED7b           26 PC3
  04 PD2            →LED7c           25 PC2
  05 PD3            →LED7d           24 PC1                  
  06 PD4            →LED7e           23 PC0            →F-sw
  07 Vcc                              22 GND
  08 GND                              21 AREF
  09 XTL1(PB6)                        20 AVCC
  10 XTL2(PB7)                        19 PB5(SCK)       
  11 PD5            →LED7f           18 PB4(MISO)      
  12 PD6            →LED7g           17 PB3(MOSI)      
  13 PD7            →LED7dp          16 PB2            
  14 PB0            →D1位           15 PB1           →D 10位 
  
  LED接続  dp    g    f    e    d    c    b    a   
           PD7   PD6  PD5  PD4  PD3  PD2  PD1  PD1  Lで点灯
           
           デジット 10位=PB1  1位=PB0 
  
  注:Vccは5Vだがセンサはmax3.7Vのため Vcc--470Ω--470Ω--470Ω--GND で2/3に分圧供給(0.1uFパスコン)。
      Vccのパスコンは10uFセラミックを使用。
  7セグLEDの電流制限抵抗は 120Ω、 桁ドライブ(アノード)は 2SA1015(ベース抵抗=1.5k)。
 I2C SDAとSCLのプルアップ抵抗は2.7k。クロックは100kHz。
このセンサICの出力温度値は2バイトのデータで、1バイト目のMSBがセ氏温度の+−を示し、残りの7ビットで整数値を転送します。
2バイト目のMSBから順に 1/2℃、1/4℃、1/8℃、1/16℃(残り4ビットはゼロ)が入っています。しかし、工場出荷時は0.5℃単位の符号を含めて9ビット出力に設定されています。

プログラムでは、F-swを押した状態でリセットすると1/16℃まで使用できるようにEEPROMを書き換える事にしました。電源を投入する毎に無駄にEEPROMに書くことを避けています。書き換え寿命も考えました。
このICは内部アドレスをひとたび設定すると電源を切っても記憶し続けます。内部アドレス0番地が温度読み取り番地で、データビット数変更は1番地です。1番地に変更を書き込んだ後、内部アドレスを0番地に戻しておかないと温度データが読みとれません。マニュアルに書かれていますが注意が必要です。

LEDの表示は日常的にはセ氏の整数値で十分ですが、小数点を利用して0.5℃ビットが立っているときは点灯することにしました。「20.」と小数点が点灯していれば20.5℃以上30℃未満ということになり0.5℃単位で知ることができます。

また、表示に少し遊び心を加えて、F-swを押すとトグルでセ氏温度の1位数値、小数点、0.1℃単位の値 を示すようにしました。「20」の時にswを押すと「0.4」などと表示されて 20.4℃ であることがわかります。

我が家に1本だけある200℃の水銀棒状温度計をこのICに接近させてしばらく机上に置いているのですが、ともに23.5℃を示しなぜか一致しています。数十センチ離れたところに秋月で購入の小型デジタル温度計があるのですがこれは19.5℃を示しています。もっともこのデジタル温度計は2℃程度は低く表示するのではないかと思っていたものです。
厳密に温度を測ろうとしても冷暖房中は足下と上体では室温がかなり変わるので意味は少ないかもしれません。
余談ですが、その昔に職場はガスストーブでした。真冬に室温がある程度上がると天井扇をまわしてみなさんに笑われたのですがめげることなく続けていると5年目に私が回さなくてもスイッチを入れてくれるようになりました。暖かい空気は上に上がって、天井扇が足下にそれを送ってくれることを体得する時間は丸4年であることがわかりました。因みに温度差は足下と天井では15℃に及ぶことがありました。

もとに戻って、NSのLM35に比べると精神衛生上良い温度センサではないかと思うところです。

ファイル一式



21 遊び心のI2Cマスターボード(TeraTerm制御)     2011.02.04
 #18の実験用I2Cマスターボードをteratermからの制御をメインにするとPCに接続する必要はありますが、種々のテストがしやすいのではないかと考えて作り直すことにしました。
入力はUARTで行いますからスイッチはリセットswだけにします。表示は無いのは寂しいですからジャンクの1.5桁7セグLEDをプログラム番号のインジケータに、赤色LEDをメニュー実行中の合図につけようかと思います。
ポートの割付ですが、
  PB 0 LED7a         PC 0 (ADC)         PD 0 RXD
     1 LED7b            1                  1 TXD
     2 LED7c            2                  2 (INT)
     3 LED7d            3                  3
     4 LED7e            4 SDA              4
     5 LED7f            5 SCL              5 LED-red
     6 Xtal             6 RST              6 LED7g
     7 Xtal                                7 LED7-1
赤字の部分が専用に取られますのでLEDは分割したポートに割り付ける事になります。

配置を発泡プラスチックの上に適当にしてみました。

後で変更はありますが、まずはこのような感じで行こうと思います。

アバウトに基板に移して、ユニバーサル基板の切れ端をガイドに穴あけします。(右手にドリル、左手にカメラ=はじめての経験)


穴開け完了後に事務用PPテープ(?)を全面に貼り付けて、切り抜きました。裏面の黒いテープは2mm幅のサンハヤトのテープです。

やや熱めのお湯に冷凍パックに入れたエッチング液に浸して8分ほどエッチングしました。細かなミスはプラスチックカッターで修正しています。

ここまで描いたのは初めてですがまずまず使えそうです。UEWで配線しますからパターンではなくランドを描いているだけです。AVRの再利用のために脚にはランドを描いていません。UEWを巻き付けてハンダ付けします。

配線を完了しました。きれいな結線ではありませんが(恥を忍んで)裏側も公開しておきます。ここまでエッチングしたのは初めてですが、カッターで溝を切るよりはらくに作業ができたと思います。今回の半田鏝は超普及品の23Wで、鏝先は\260で最近購入した2φ程度で先端が斜めに切り落とされたもの(切り口は楕円型)を使用しました。鏝先のハンダメッキが円柱部分までありましたのでアルミ箔を巻いて先端の切り口部分だけにハンダが乗るようにしています。(これがいいたかった)

下のアップは黄色枠の部分ですが、LEDの抵抗に秋月のチップ抵抗1608サイズを計画してうまく行くかどうか心配していた部分です。思いの外らくに作業ができました。
ISP端子がありませんが、超簡易アダプタを被せて、リセットは手で押しながら書き込む予定です。
→不勉強でした。 -r(デバイス情報読み出し)や-rf(fuse設定読み出し)、-f(fuse設定)、-e(消去)など簡単なものはリセットボタンの手押しでも可能ですが、大量のプログラムデータの書き込みは適当なリセットのコントロールが必要なようです。手押しリセットでは書き込むことができません。ISP端子を追加しました。

TeraTaermの操作選択画面:
テスト用のルーチンを複数書き込んだときに、時間が経つと操作を忘れてしまいます。そこで、マスターボードのリセットボタンを押せばAVRからメニューメッセージを TeraTaermに送り、番号で選択できるようにしてみました。
次はRTCを選んだときのサブメニュー表示が出たところです。
-- menu --
1 teraterm通信確認
2 RTC操作
3 EEPROM 24LC64 読み書き
4 mega88読み書き
5 tiny2313読み書き
6 7セグ4桁表示

RTC操作
  1 時刻表示  2 分未満四捨五入  3 時刻合わせ
mega328のフラッシュは消費しますが量的に問題はありません。余分なことを考えずに操作できるから快適です。
マスターボードのプログラムは以前のものと本質的に変わりは無いのですが現在ゴミ掃除中で整理できればアップします。→ゴミ掃除終了しました。
プログラム一式







20 4桁7セグメントLED表示スレーブ(Tiny2313)-- 新方式     2011.01.24
 #18に書い た16バイト読み書き方式により、#19のW/Rシーケンスに基づいたユニットをコンパクトにまとめてみました。
ソフトウエ ア的には4桁のバイナリデータと小数点位置を示す1桁バイナリを受け取るだけです。




プログラムは次のとおりです。 改造のライブラリ"i2c_slv2313.h"を使っているのでデータの転送は割り込み処理で行われます。その結果プログラムは簡単になりました。
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "i2c_slv2313.h"

static const uint8_t TWI_slaveAddress = 0x20;  // 7ビットで指定する(W=0x40,R=0x41)


int main(void){// ********************* main ***********************************************************
  uint8_t i;
  prog_uint8_t s[] = {                                 //  unsigned char __attribute__((__progmem__)) s[] = {
    192, 249, 164, 176, 153, 146, 130, 248, 128, 152   // 0 1 2 3 4 5 6 7 8 9
  };
  
  usiTwiSlaveInit( TWI_slaveAddress );  // I2C Slave initialization

  for(i=0;i<16;i++){t[i]=i+17;}   // リセット時の仮表示用
  
  DDRD=0xff;                                     // PORTB出力モード  PORTD=0xff;
  DDRB|=0b00011111;
  DDRA=0xff;                                     // PORTB出力モード  PORTA=0xff;

  // main loop
  while(1) {
    uint8_t i;
    sei();

     PORTB=0b11110111; PORTD=s[t[0]]; if(t[4]==1){PORTA=0b11111101;} _delay_ms(2);//PORTA=255;
     PORTD=0xff;PORTA=255;_delay_ms(0.1);
     PORTB=0b11111011; PORTD=s[t[1]]; if(t[4]==2){PORTA=0b11111101;} _delay_ms(2);//PORTA=255;
     PORTD=0xff;PORTA=255;_delay_ms(0.1);
     PORTB=0b11111101; PORTD=s[t[2]]; if(t[4]==3){PORTA=0b11111101;} _delay_ms(2);//PORTA=255;
     PORTD=0xff;PORTA=255;_delay_ms(0.1);
     PORTB=0b11111110; PORTD=s[t[3]]; if(t[4]==4){PORTA=0b11111101;} _delay_ms(2);//PORTA=255;
     PORTD=0xff;PORTA=255;_delay_ms(0.1);
  }
}
現在はこれを使う予定はありませんが、I2C仕様のマスタから4桁表示の必要があれば簡単に使えるものと思っています。
ファイル一式




19 I2C通信のシーケンスに思うこと      2011.01.20
 一般にI2Cのread/writeシーケンスは次の手順で行われます。
    書き込みシーケンス                 読み取りシーケンス
  スタート・コンディション発行             スタート・コンディション発行   
  コントロールバイト送信(writeアドレス)        コントロールバイト送信(readアドレス)
  内部アドレス(H)送信                  内部アドレス(H)送信
  内部アドレス(L)送信                  内部アドレス(L)送信
  第1データ(バイト)送信                  リピート・コンディション発行(AVRではs・cと同じ)  
  第2データ(バイト)送信                   第1データ(バイト)受信(ACK応答)
  最終データ(バイト)送信                  第2データ(バイト)受信(ACK応答)
  ストップ・コンディション発行                 最終データ(バイト)受信(NACK応答)
                               ストップ・コンディション発行
ここで問題を感じたのは赤字で書いたR/W開始番地の指定です。容量の大きなEEPROMは内部アドレス(H)の指定が必要ですが、RTCではこれを持たないので送信するとデータが一つずれることになります。また、これらの専用ICでは内部開始アドレス指定が決まっていますが、自分でAVRを用いてスレーブを作るときは開始アドレスに関係しない(開始アドレス固定など)方法も可能です。
しかし、これを自由に決めてしまうとマスタのプログラムがケースによって異なったものにする必要が生じて煩わしくなります。
その解決の方法として、マスタからは上記の表にあげた方法ですべてをアクセスすることにします。一つは内部アドレス(H)を持たないときは0xffを指定してマスタのルーチンで送らないことにすること、もう一つは自作のスレーブで内部アドレス指定が不要なときは内部アドレス指定データを無効にすることにしました。
下記の 「18 自作機器におけるI2C通信の実験」からはこの方式で統一するつもりですが、それより以前の実験プログラムではそこまで配慮していませんのでスレーブのプログラムを修正する必要が生じています。



18 自作機器におけるI2C通信の実験 (改題しました)     2010.12.21 2011.01.14
 I2C通信で表示器(出力装置)やセンサ(入力装置)をメインボードに接続できると各種の実験が能率よくできます。
そこで次のような計画を考えてみました。なお、製作の自由度からArduinoではなくwinavrで行います。
全体の機能は私の実験に見合った単純化したものに限定して開発をしやすくする予定です。

 @ マスター機能はメインボード1台に限り、周辺からマスターが立ちあがる事はないとします。
 A スレーブからの割り込みが必要なときは専用の結線をします。
 B スレーブ1はマキシムのRTCとmicrochipのEEPROMの読み書きとします。
   I2C通信システムがメーカーで書き込まれているものとして採用しました。→完成
 C スレーブ2はTiny2313mcuとの通信です。
   書き込みは過去の実験でまちがいがないと考えます。手軽に使えるmcuとして採用しました。
   →書き込みは完成。読み取りはできていません。
 D スレーブ3はmega88系mcuとの通信です。→完成
このような感じで実験を進めます。
通信の形態をしてはRTCやEEPROMに見られるように、スレーブのメモリ(SRAM上の変数)に内部アドレスを与え、マスターから読み書きができることを目標にします。一部の作例のようにマスタからコマンドを発行してそれに応答する形式は考えていません。
スレーブのデータ領域は最大32バイト程度を考えます。

とりあえずハードウエアの姿です。一部未配線です。

試行錯誤の結果、tiny2313の通信には失敗しましたので2313の実装をはずし、mega88を実装しています。

簡単な回路図は次のとおりです。



メインボード(マスター機能)について:
 ・MCUは手持ちのATmega328を使います。メモリに余裕があるので採用しました。クロックは 16MHz です。
 ・teratermでPCと結び、文字列の受け渡し=動作指示と結果報告ができるようにします。
 ・LEDアレイ(8個)をつけます。バイトデータ表示とエラーインジケータとして使います。
 ・プログラム分岐などに備えてタクトSWを2個つけます。
 ・リセットスイッチを用意します。
 ・電源電圧は5Vとします。  ・I2Cコネクタは4芯( SCK SDA Vcc GND )として、2回路用意します。

ATmega328回りの結線は次のようになっています。
  MCU=ATmega328p  Xtal-16MHz I2Cクロックは 100kHz
  fuse -fL11000111 -fH11010001 -fx11111100   c7 d1 fc  eeprom=save BOD=4.3V
  
  01 reset                                 28 SCK(PC5)       →I2C(pull up 2k7)
  02 RXD(PD0)       →TXD                  27 SDA(PC4)       →I2C(pull up 2k7)
  03 TXD(PD1)       →RXD                  26 PC3
  04 PD2                                   25 PC2
  05 PD3                                   24 PC1            →sw1
  06 PD4            →LED4                 23 PC0            →sw0
  07 Vcc                                   22 GND
  08 GND                                   21 AREF
  09 XTL1(PB6)      →Xtal                 20 AVCC
  10 XTL2(PB7)      →Xtal                 19 PB5(SCK)
  11 PD5            →LED5                 18 PB4(MISO)
  12 PD6            →LED6                 17 PB3(MOSI)      →LED3
  13 PD7            →LED7                 16 PB2            →LED2
  14 PB0            →LED0                 15 PB1            →LED1
  
  LED接続  bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit1    
           PD7  PD6  PD5  PD4  PB3  PB2  PB1  PB0     
  MSBとLSBの配線を間違った(逆になっている)ため、
  表示サブルーチンで変換しています。
写真のようにスレーブボードは電源を含めて4本の線で繋がりますが、自作コネクタ(真鍮線とICソケット)で芋蔓式に繋がるようになっています。

メインボードの実験メニュー(ソフト)の選択:
リセットスイッチでメニュー選択ルーチンに入り、sw0で1〜15のメニューを決めます。sw0を押す毎にLEDアレイが2進数でメニュー番号を表示します。
sw1を押すとその番号のメニューに決定して最上位ビットが点灯します。写真では110が点灯していますので6番メニューを示し、かつMSBが点灯していますからそのメニューを実行していることを示しています。

現在のメニューは、(後日、メニューが増えたらここに追加します)
 1番 teratermとの通信確認。 19200bps (cp2102使用のUARTコンバータでTXDとRXDのみ接続)。
    PCからの送信に対して、送信文字列+"test" の文字を返します。
 2番 簡易時刻表示。 I2CでRTCを読み取り、24時制で hh:mm:ss を1秒ごとにteratermへ送り出します。
 3番 分未満四捨五入機能。 sw1を押すと四捨五入されます。1秒レベルの微調整に使います。
    RTCに書き込んでいます。
 4番 簡易時刻合わせ。 24時制で、teratermから hhmm の4桁を送るとその時刻にRTCを設定します。
    設定後には2または3番メニューを呼ばないと結果は分かりません(手抜きです)。
    (現在は必要がないので 年月日などはセットしていません)
 5番 EEPROM 24LC64 を実装しました。プログラムの固定文字5バイトを書き込んで、のち、読み出して
    teratermに表示できました。
 6番 スレーブボード3のtiny2313への書き出し/読み込みテストルーチンです。2313の特定の変数に
    16バイト(固定)の任意のデータを書き込み、その後読み出します。
    スレーブでは順次LEDアレイに繰り返し表示します(確認用)。→後述
 7番 スレーブボード2のtiny2313データの読み出しテストルーチンを書く予定です。→現在は失敗に終わっています。
 8番 スレーブボード2のmega88データ書き込みテストルーチンです。mega88の内部アドレス 0〜31番地の
    32バイトに書き込みができます。→完成。
 9番 スレーブボード2のmega88データ読み取りテストルーチンです。上記の32バイトを読むことができます。
    →完成。

マスターからのI2C通信方法(ソフトウエア):
wsNAK氏のプログラムを元に共通部分をソースファイル i2c_mstr.c とヘッダファイル i2c_mstr.h にしたところプログラムが見通しよく書けることになりました。
このライブラリが正しいものかどうかは自分ではわかりませんが、問題なく動作して、かつ、簡単に記述できますから満足しています。
マスターからの通信は上述のとおり、スレーブの内部アドレスを指定してそこから必要バイト数(最大32バイト)を読み書きする方法をだけを考えます。スレーブにコマンドを送る事は考えません。
その結果、書き込みまたは読み取りのサブルーチンが簡略化できて読み書きのための手続きは極めて簡単に書けることになりました。
メインプログラム中の扱いは、
  マスターの初期化に I2CMsInit(); を実行する。
  書き込みでは i2c_write_d(0x20,255,0,5); を実行すると変数 d[0]〜d[n-1] のデータが書き込まれる。
    引数は 第1=スレーブアドレス(8ビット偶数)
         第2=スレーブ内部アドレスHIGH(HIGHが無いとき=rtcなど=では255とする)
         第3=スレーブ内部アドレスLOW
         第4=書き込みバイト数
   読み取りでは i2c_read_d(0x20,255,0,5); を実行すると変数 d[0]〜d[n-1] にデータが読み込まれる。
         引数は書き込み時に同じです。第4引数は読み出しバイト数になります。

この結果、スレーブの内部データテーブルさえわかっていれば極めて単純に取り扱えることがわかりました。多くを覚えるのが苦手になってきている折から私にはうれしい結果です。

スレーブボード1(RTC,EEPROM)について:
RTCはMAXIMのDN3231です。内部アドレスの一部分だけの読み書きが可能ですからマスター機能から問題なくアクセスできました。
ボタン型リチウム電池CR2025でバックアップしています。

EEPROMは 24LC64 です。容量から内部アドレスはHIGH,LOWの2バイトを指定する必要があります。
書き込み時には時間がかかりますから1バイト書く毎に5msのウエイトを入れています。さらに最後に10msのウエイトを入れました。
ライブラリのI2C書き込みルーチンに埋め込みましたのでウエイトが不必要なRTCなどの書き込みでもウエイトがかかりますが、通常の使用では1回の書き込みでせいぜい0.1〜0.2秒ですから実用的には問題はありません。
EEPROMはI2Cの勉強のために購入しました(値段も安かった)が、音楽やグラフィックに興味が無いので使途は決まっていません。

スレーブボード2について:
Tiny2313とmega88の送受信実験用ボードとして考えましたが、tiny2313はうまく動作しないので実装をはずし、mega88専用に変更しています。
モニタ用にLEDをつけました。入力装置としてDIPスイッチを付けていますが実験では使用していません。
Tiny2313への書き込みはすでに7セグメントLED表示器で実験済みなので実用になるかと思いますが、読み取りに失敗していますのでここでは報告しないことにします。
スレーブのプログラムもマスタ同様にライブラリに分割しました。wsNaK中尾氏のプログラムが原典ですが割り込み方式に変更して自己流に書き換えています。
ライブラリで定義している d[0]〜d[32] の変数にマスタから割り込み処理で読み書きするためにスレーブプログラムではデータを準備することとデータを読みとることだけで使うことができます。I2Cに関しては初期設定サブルーチンを実行することくらいです。
tiny2313よりIOポートが多いのでより複雑な表示装置を駆動することや、ADCを持つことからセンサ入力装置としての利用が考えられます。

tiny2313の読み取りが失敗に終わったことは残念ですが、mega_x8では使えることがわかりましたので初期の目的は達成することができました。
なお、通信の互換性を確立するためにマスタにArduino-IDEを使ったものも試しましたが問題なく動いています。

公開に絶えない内容ですがソースファイルをここに置きます。18i2cprjt.zip



ところで、UART通信に使う受信文字列変数をグローバルで volatile char recbuf[20]; と宣言して、他方mainプログラムからUARTに送り出す文字列変数をmainの中で char s[20]; と定義して、  strcpy(s,recbuf); と書くと、
   warning: passing argument 2 of 'strcpy' discards qualifiers from pointer target type
という警告が出ます。どのように処理するのが正しいのかわからずに困っています。
原始的にビット毎に代入すると警告は出ません。


Tiny2313スレーブに再挑戦(スレーブボード3について:)
 2313をスレーブとして読み書きできる方法を探したくて、専用のボードを作りました。

回路図はありませんが、I2C回路、リセットスイッチ以外にはLEDアレイをつけただけです。
LEDアレイは (MSB) PA0 PD6 PD5 PD4 PD3 PD2 PD1 PD0 (LSB) として High で点灯しています。
I2CのSCK、SDAがPORTBにあるのでテスト段階ではPORTBを使わないようにしました。

前述のDonald R. Blake氏作TWIドライバ(usiTwiSlave.h と usiTwiSlave.c)を使った過去の学習では、プログラムを一部変更してマスターからの書き込みは成功しているのですが(7セグ4桁LED及びLCD表示器)、読み出しは失敗に終わっていました。
「usiTwiTransmitByte関数」でデータを渡すだけなのですがうまく実行できませんでした。数多くの試行錯誤の後にスレーブプログラムが他の作業中に読み出しが行われるとハングしてしまうことが判明しました。delayはもちろんのこと、簡単なfor文でも支障をきたします。ほぼ待ち時間無く連続でマスターからの呼び出しに対応すれば正常にデータを渡すことができました。
しかし、これでは実用的なプログラムを作れませんから、ライブラリを変更して割り込みで対応するようにしたのですが、専用のバッファに16バイトのデータを送る方法が見つからず(C原語の勉強不足)強引に16バイトの引数を持つ関数を作ってバッファに送ることができました。
これを使うと待ち時間があっても動作するのですが、通信とLED点灯だけでフラッシュが1200バイトほどとSRAMが50%以上使われます。これでは自由なスレーブプログラムが書けなくなります。

そこで、ライブラリを強引に分解してスレーブプログラムを作ってみました。リードバッファ、ライトバッファ、スレーブプログラムの配列変数の各16バイトを共通の配列1つのみとして、16バイトに固定して、常にこの16バイトの先頭から16バイトを読み書きするものと簡略化して、割り込みで処理するとマスターからの通常読み書きの方法で通信が可能であって、メインプログラムに待ち時間があっても問題なく動作する事が確認できました。
このままでも良いのですが、プログラムが見にくいので自己流にライブラリを分離してみました。ライブラリの書き方がわかりませんので常識を逸脱している可能性がありますが、これをincludeして書きますと見やすく簡単なものとなりました。また、プログラム量も650バイト程度に収まり、SRAMの消費も少なくなっています。
繰り返しになりますが、マスタから見ると内部アドレスが0x00から始まる16バイトの通信領域があって、通常はこの16バイトを一括して送受することができるということになります。(内部アドレス指定は無視されます)
スレーブプログラムでは、この領域は t[0]〜[[15] となりますからマスタからの読み書きがあるという前提でこれらのデータの読み書きをすることになります。
通信に必要な手続きは、
 @ スレーブアドレスを定めること(7ビット)
 A 初期化関数 usiTwiSlaveInit( TWI_slaveAddress ); を実行するだけです。

これを使った応用プログラムはまだですが、16バイトの通信ができればIO装置としてかなりのものが考えられるのではないかと思っています。
ここにプログラム一式を置きます。(不備な点がありますがご容赦下さい)







17 MAXIM DS3231 RTCを用いた単純時計   2010.12.14
 MAXIM DS3231 RTCのサンプルをいただきました。高精度でカレンダー、アラーム機能もも持っている優れものです。本来ならそれらの機能を生かした時計を考えるべきでしょうが、大量生産の恐ろしさでカレンダー、温度計つき液晶表示電波時計(目覚まし時計)が\980〜\880で買える時代になっています。液晶表示ですべての機能を活用しても市販の時計にかないません。
そこで、精度の測定を兼ねてLED表示の単純時計を考えました。アナログ時計の秒針が動くのはまだ辛抱できるのですがデジタル時計の秒表示やドットの点滅は落ち着いた気分には合いませんから、時間と分だけを表示する単純時計を視認性のよいLEDで作ることにしました。


回路図をここに置きます。

このRTCモジュールはI2Cで読みとる事になっています。4桁のLED表示には通常12本の信号線が必要ですが、表示部のモジュール化と未だ理解が進んでいないI2Cの勉強のためにLEDもI2Cのモジュールにすることを考えました。 以前と違ってArduinoライブラリによるI2Cプログラムの作製ができますので動作確認はArduinoスケッチで行えますから確実に進めることになっています。今回もRTCモジュールの書き込み/読み出し確認とLEDモジュールへの書き出し確認はArduinoライブラリによる動作で前もってしました。

まずはこのページにある「15 4桁7セグメントLED表示スレーブ(Tiny2313)」の再製作です。LEDは時と分の間が少し空くように配置しました(完成後もう少し広い方が良いと思いました)。Arduinoプログラムで実行すると、書き出し時にI2Cではスレーブの内部アドレスを送ることになっていますが私の作はその情報を0番地のデータとして受けとってしまいます。I2Cの規則に違反するので内部アドレス指定の受信バイトは無視するようにプログラムを変更しました。
また、時刻あわせなどのために時または分を示していることを表示するように明表示と暗表示ができるように考えました。LEDの数値はバイナリで受け取るのですが、MSBは「0」で通常の表示とし、「1」では暗く表示する機能を付けました。同時に時の10位が0の時は見にくいので非表示としています。プログラム
LED表示スレーブが機能することを確認して、いよいよマスターのプログラムに取りかかります。

同じくこのページの「13 RTC-8564の読み取り」でwsNak中尾氏のプログラムを頂戴していますが、その時はかなり不理解のまま動いた結果になっています。今回はもう少し自分にわかるように読み返しながら実験を続けました。
よく読んでみるとプログラムのコメントに書いたように意外と単純に取り扱いができるようです。Arduinoに似た扱いで通信ができます。
  I2C取り扱いの手順
    // ライト・シーケンス
    I2CStart();             // スタート・コンディション
    I2CWrite(0xd0)          // CB(W) Adrs=0xd0(d0+0) R/W=0
    I2CWrite(0xXX);         // ROM Adrs(H)  Hは不要
    I2CWrite(0x00);         // ROM Adrs(L)
    I2CWrite(0xXX);         // 0番地書き込みデータ
    I2CWrite(0xXX);         // 1番地書き込みデータ
    I2CStop();              // ストップ・コンディション発行
    

    //リード・シーケンス (読み出す内部番地を書き込んで、リピートする)      -------  流れ  ----------------------
    I2CStart();             // スタート・コンディション                      |スタートコンディション発行           |
    I2CWrite(0xd0)          // CB(W) Adrs=0xd0(d0+0) R/W=0                   |コントロールバイト(書き込み)       |
    I2CWrite(0x00)          // ROM Adrs(L)指定=読み出しポイント00           |ROMアドレス書き込み(読み出し起点)  |
    I2CStart();             // リピート(スタート)コンディション            |リピートコンディション発行           |
    I2CWrite(0xd1)          // CB(R) Adrs=0xd1(d0+1) R/W=1                   |コントロールバイト(読み出し)       |
    dat1 = I2CRead(0);      // バイト・リード(ACK応答)                       |読み出し(継続)  自動インクリメント |
    dat1 = I2CRead(1);      // (最終)バイト・リード(NOACK応答)               |読み出し(最終)                     |
    I2CStop();              // ストップ・コンディション発行                  |ストップコンディションで終了         |
                                       --------------------------------------
その結果プログラムはこのような形になりました。
時分がわかればよいだけですが、その他のデータには適当に数値を書き込んでいます。RTCモジュールは24時制とam/pmの区別がありますが、単純にするために24時制で扱っています。LEDに表示するときに12時制に変更しています。
秒単位で正確に時刻あわせをしたいので時分表示に加えて分秒表示にかえられるようにsw1を設定しました。同時に四捨五入で0秒にセットできる機能も付けました。進み遅れは少ないようですからこの機能で秒未満の誤差に合わせる事ができます。sw4を短押しすると四捨五入されます。
また、めったに使わないのですが時と分もスタンドアロンで時刻あわせができるようにしました。
 sw4長押し→時が明るく表示される→sw3で時up/sw4で時down→sw4短押し→分が明るく表示される→
 sw3で分up/sw4で分down→sw4短押し→計時機能に戻る
となります。
なお、sw1を押した状態でリセットするとRTCモジュールが初期化されます。

これで初期の目的は達したのですが、マスターのプログラムには define文とサブルーチンが多くて煩雑な感じがします。これを foo.h と foo.c の別ファイルにできないものかと試行したところ簡単にコンパイルに成功してプログラムは簡素に書くことができました(\rtc1_h_test\rtc_master1.c)。

このように簡単に .hファイル と .cファイル を作ってもよいものかと疑問にさえ思います。c言語に詳しい方のお教えを願いたいところです。

この形で使えるものならばmega88系のmcuではI2C通信が少ない負担で実行できるのではないかと思います。
今後考えられる機能をI2C通信のモジュール化を行えば、かなり自由な組み合わせで試行ができるのではないでしょうか。楽しみが少し増えました。
ゴミもありますがファイル一式をここに置きます。




16 16桁×2 LCD表示スレーブ(Tiny2313)   2010.06.22
 前の項(15 4桁7セグメントLED表示スレーブ(Tiny2313))でDonald R. Blake氏のドライバを使うと簡単な記述でスレーブができることを知りましたのでLCD表示器にも適用することを考えました。
機能は単純に考えて、16桁×2の表示器に表示器が内蔵するキャラクタを表示することを目的としました。
1行目と2行目を区別するために最初の文字で行を判断することにして、1なら1行目で1以外は2行目表示とします。その1文字に続いて16桁のバイトデータ(キャラクタ)を受信してそのまま表示することにします。画面クリアのコマンドやカラム指定のコマンドは使わないことにします。既定バイト数のデータを送受信することで単純化しています。



回路図は省略しますが、
 PB7にI2CのSCK PB5にSDA (マスタで4.7k pull up)、
 PB0〜PB3 に LCDのD4〜D7、
 PD5にLCDのRS PD6にLCDのEN をつなぎ、
 reset端子に10kpull up抵抗とリセットスイッチを付けています。
 電源の5Vは信号線とともにマスタから取り込みます。

ここにソフトウエア一式を置きます。マスタはArduino-IDEで書いています。

メインルーチンの主要部は次のとおりです。

int main (void)
{
    uint8_t cnt,ichar,i,j;
    uint8_t s[18];
  
    DDRD=0xff;                                     // PORTD出力モード  
    DDRB=0xff;                                     // PORTB出力モード  
    DDRA=0xff;

    usiTwiSlaveInit( TWI_slaveAddress );            // I2C Slave initialization
    sei();
    lcd_init();                                     //ここでLCDポートの方向が設定される

    while(1){ //  _delay_ms(500);
        if( usiTwiDataInReceiveBuffer() ) {
            for(uint8_t k=1;k<18;k++){s[k]=usiTwiReceiveByte00(k);}  // 17文字の読み込み
            if(s[1]==0x31){lcd_gotopos(0,0);}else{lcd_gotopos(0,1);}  // 行番号 1以外は2行目に
            for(i=2;i<18;i++){ lcd_putch(s[i]);}                      // 2文字目以降の16文字を表示
        }
    }
}
ここでも usiTwiReceiveByte() の使用法がわかりませんが、付け加えたusiTwiReceiveByte00()で受信データを読みとっています。
7セグメントLED表示と同様に通信形態の単純化とI2Sドライバによって簡単にプログラムを書くことができました。
送信部からは 1バイト+16バイト の文字データを送るとそのまま表示されます。

tiny2313の基板をLCDに付けておけば 2本の信号線+電源線 で使えるので便利だと思いますが、LCDは6本の信号線で使え、かつ、LEDのような点灯サポートはいらないのでLEDほどのメリットは無いかもしれません。

TWI(I2C)機能を持つAVRについて、このtiny2313用のUSIを利用したドライバのようなドライバがあればマスタも簡単に書けると思いますが、まだ見つけ得ていませんのでArduino-IDEで書くことにしています。




15 4桁7セグメントLED表示スレーブ(Tiny2313) 苦闘中→強引に解決     
 理解が困難な I2C ですが別項の様にTWI機能を持つAVRとArduino-IDEの組み合わせでは簡単な記述で4桁7セグメントLED表示スレーブを作ることができました。
これがtiny2313で実現できるとさらに楽しくなる(実用になる)と考えて、以前に暗中模索した方法を試してみましたが、過去に試したものは割れ鍋に綴じ蓋で、送受共にごまかしで通信できていたようです。

 今回Arduino-IDEを使って正式に 4桁数値+小数点位置 の送信部ができましたので確かめたのですが通信はできていませんでした。
chuckさんがTWIドライバを使って巧妙なスレーブを作っておられますが、スキルの不足でプログラムを解読できません。機能が少なくても良いから自分に理解できる方法を試しているのですが成功には至っていません。

送信部のハードとソフトは別項のArduino-IDEで書いたものを使っています。
送信内容は 4バイトのバイナリデータと1バイトの小数点位置バイナリ の5バイトです。

試作した受信部は Tiny2313 内部RC発振8MHz で PD0〜PD6をセグメントドライブにあて、PA0を小数点に、PB0〜PB3を桁ドライブに割り当てています。
桁はPNPトランジスタ経由でドライブしていますので桁ドライブ、セグメントドライブ共にLで点灯します。
chuckさんが使っておられるTWIドライバ(Donald R. Blake氏作 usiTwiSlave.h と usiTwiSlave.c)を用いて、考えました。(原典を探しましたが、chuckさんのサイト以外では見つけることができませんでした)
無限ループの中でダイナミック表示を行い、割り込みでデータを更新する考えです。5バイト目の小数点位置のバイナリデータで小数点の点灯をしています。
/**********************************************************************************
  4桁7セグメントLED表示スレーブ(Tiny2313)

*********************************************************************************** */

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include "usiTwiSlave.h"

static const uint8_t TWI_slaveAddress = 0x20;    //0x40,0x41

volatile uint8_t d[6];
volatile uint8_t data,cnt=5;            
volatile uint8_t ichar;

/*   メインルーチン  ************************************************ */
int main (void) {
  unsigned char __attribute__((progmem)) s[] = {
    192, 249, 164, 176, 153, 146, 130, 248, 128, 152
  };
  uint8_t temp;

  DDRD=0xff;                                     // PORTD出力モード  
  DDRB=0xff;                                     // PORTB出力モード  
  DDRA=0xff;
  
  // I2C Slave initialization
  usiTwiSlaveInit( TWI_slaveAddress );

  data=1; cnt=5; ichar=0;
   
  sei();
  USISR=0xE0;                             // SIF OIF PFをクリア カウンタ0  USI開始

  for(;;) {
   if( usiTwiDataInReceiveBuffer() ) {
     temp = usiTwiReceiveByte();
   }
   
     d[5]= temp;     d[2]= temp;            //  通信の確認用
     PORTB=0b11110111; PORTD=s[d[1]]; if(d[5]==1){PORTA=0b11111101;} _delay_ms(0.01);PORTA=255;
     PORTB=0b11111011; PORTD=s[d[2]]; if(d[5]==2){PORTA=0b11111101;} _delay_ms(0.01);PORTA=255;
     PORTB=0b11111101; PORTD=s[d[3]]; if(d[5]==3){PORTA=0b11111101;} _delay_ms(0.01);PORTA=255;
     PORTB=0b11111110; PORTD=s[d[4]]; if(d[5]==4){PORTA=0b11111101;} _delay_ms(0.01);PORTA=255;
  }
}
TWIドライバで提供されている赤字の部分を仮にデータとして読み込んでいますが、送信側で 12.34 と 98.76 を交互に送っていますがマスターの指示LEDの点滅に同期して 2桁目が 2と3、小数点が2桁目と3桁目、に変化します。受信データの最後が小数点を表す 0x02、0x03であることと一致していますのでデータの送受が行われていると考えて良いと思います。
しかし、どうすれば 第1バイトから第4バイトのデータが得られるのかわからず困っています。
(ドライバを弄って return rxBuf[ rxTail ]; の [ ]内を他の数値に変えてみましたが、時々おかしな挙動をします。)
ここさえ読み出せれば、tiny2313のスレーブはできることになるのですが、思うようには動いてくれません。



(2010.06.04) 強引に解決 受信データがTWIドライバの rxBuf[] に取り込まれているようなのですが、不勉強でexternでも読み出すことができません。
usiTwiReceiveByte()関数の戻り値で最終読み取りデータが帰ってくるようなので、強引にrxBuf[]を読みとるルーチンを作製しました。
  usiTwiSlave.h に uint8_t usiTwiReceiveByte00( uint8_t ); をプロトタイプ宣言して、
  usiTwiSlave.c に uint8_t usiTwiReceiveByte00( uint8_t,n) をusiTwiReceiveByte()を変更して作りました。

   uint8_t usiTwiReceiveByte00(uint8_t n){
     return rxBuf[n];                         
   }                                               

これを使って 1桁目 usiTwiReceiveByte00(1) から 5桁目 usiTwiReceiveByte00(5) をデータとして取り込んでみました。
実行するとスレーブをリセットしたときは期待通りの表示なのですが2つ目からは崩れます。どうやらゴミが残っているようです。
データが更新される前には 開始条件 による割り込みがありますからこのルーチンでバッファをクリアしてみてはどうかと考え、usiTwiSlave.c の ISR( USI_START_VECTOR ){ の最初に flushTwiBuffers(); をコピーしました。

Arduino-IDEで作ったマスタから5バイトデータを送って4桁LED表示をするtiny2313の方法が何とか成功しました。常道ではない方法ですが初期の目的は達しました。今後必要なときはブラックボックスとして使えるのではないかと思っています。
送信形式は 1桁目バイナリ〜4桁目バイナリ+小数点位置バイナリ を送ることにしています。
"123.4”を送信するときは 0x01 0x02 0x03 0x04 0x03 を送ることになります(備忘録です)。

プログラム と TWIドライバ(usiTwiSlave.h usiTwiSlave.c サーバーの関係で.hは.txtに変えています)です。
TWIドライバの原典は探し得ておらず、chuckさんのページの i2cui_FS03_20090919.zip から頂戴しています。





14 wsNak中尾氏プログラムのトレースと一部変更   mega88--mega88 1バイト送受信 2010.05.26 
 私には理解困難な I2C です。WEB上にも参考プログラムがほとんどないように感じていますが、swNak中尾氏が「エレキジャック」に掲載しているプログラムのトレースをしました。
「AVRのI2C通信プログラミング(8)TWI使用のI2Cスレーブ その4」回路図スレーブプログラムマスタプログラム が原典です。

ハードウエア:
回路図を元にそれぞれをブレッドボードで構成しました。
マスタは12MHz水晶、リセットスイッチ、SCL(28番ピン)とSDA(27番ピン)に4.7kプルアップ抵抗、PD4とPD5にLED(H点灯)、を接続しています。
スレーブは12MHz水晶、リセットスイッチ、PD4とPD5にLED(H点灯)、を接続しています。
それぞれのSCL同士、SDA同士、を接続しています。

通信の概要:
マスタは1バイトのカウンタ(変数)を準備し、0x00をセットしてこの値をスレーブに送ります。(事前に開始条件とスレーブアドレスを送っています)
マスタはスレーブからの1バイト読み取りを待ちます。同時に、カウンタ値の0,1ビットでLEDを点灯します(上位はマスクしています)。
スレーブは1バイトのデータを受信すると0,1ビットでLEDを点灯します(上位はマスクしています)。
スレーブは受信したデータ1バイトをマスタに返します。
マスタはスレーブから送られたデータ(その前にマスタが送ったデータと同じもの)をインクリメントして次のデータとして送信します。
これをエンドレスに続けます。
結果として、マスタとスレーブのLEDが同期して2進2桁のカウント動作を行います。マスタが送信の間隔に約1秒のインターバルを作っています。

プログラムの実行:
添付のファイルにはhexファイルが含まれていますので、水晶のクロックは原典は20MHzで製作は12MHzのために時間的にはずれができると予想されましたが、そのまま実行してみました。
結果として期待通りの動作となりました。

再コンパイル:
コンパイル環境が私が常用しているものとは少し違うようなので、.cファイルだけを取り出し、クロックの違いを通信クロックが100kHzになるように修正してから、常用のmakefileを使ってWINAVRでコンパイルしました。問題なくコンパイルが進み、動作結果も正常でした。

プログラムについて感じたこと:
まず、「プロの作品」と感じました。自己流ではSFRの設定で直接定数を書くのですが、定数を定義してそれを使っています。内容がわかることと変更が容易になることがわかります。しかし、I2Cの理解が困難な私には二重三重の定義となって理解がさらに難しくなったようです。修行が不足しています。
I2Cには多くの手続きが必要ですが、通信を確立するための手続きが多くてアプリケーションによって変更しなければならない部分はわずかのように感じました。
それならば、プログラム上は多くあってもブラックボックスと考えて使用できるようです。(難しいところはおいしいところだけいつも頂戴しています)
自分流に書き換えた方がわかりやすいところは自分流に変えることにしました。

プログラムの変更・7セグメント2桁表示に:
せっかくのプログラムですから、LED点滅だけで終わるのは惜しいものです。過去に暗中模索したようにI2Cの私の用途は、その一つに、LED多桁表示器に送ることにあります。
その一歩として2桁LEDに送られた1バイトデータを16進表示することを考えました。ハードウエアは手元の基板を流用しています。mega168、12MHz水晶クロックで2桁のLED表示器がついたものです。幸いなことにSCL、SDA端子は無接続に変更できるものだったのでそのまま使うことにしました。
マスタのプログラム スレーブのプログラム

プログラムはLEDの点滅に使っているデータを7セグメントに送ればできると考えたのですが、原典ではメインルーチンは通信のポーリングだけに使われていて、受信のサブルーチンの中でLEDの点滅をしています。これだけならどこに置いてもon,offくらいはできるのですが、7セグメントではダイナミックドライブで常に書き続ける必要があります。点灯中はウエイトルーチンを置かないと明るさが確保できず、それを置くとポーリングに支障がでます。

表示ルーチンは目で見るだけですから少々割り込みが入っても支障はありません。ウエイトを使いながらダイナミック表示をしていて、データ受信の時だけ割り込みで処理すればリアルタイムのデータが表示されます。
メインルーチンに唯一あった受信ルーチンを割り込みに変更しました。割り込みルーチンのなかでデータをグローバル変数に置き換える過程でコンパイラの最適化にあって少々悩みましたがvolatileを付けることで無事解消しました。
16進2桁のカウントアップが正常に表示されるようになりました。

プログラムに思うこと:
一つは、このサンプルは1バイトの転送です。が、多くのケースでは複数バイトのデータを送ることになります。2バイトなら1バイト目はACKで送り2バイト目はNACKで送ればできると思うのですが、2バイトと100バイトでは違いがありませんが1バイトと2バイトではまったく違うことを考えなければなりません。無料公開のサンプルではそこまで気を配れないのかもしれませんが、できれば1バイトではなく2バイト送信のプログラムとしてくれた方が初心者には親切です。
同様に、二つ目として、ポーリングでなく割り込み受信が普通だと思います。割り込みでサンプルをもらえたら助かります。
確かにLピカはできますが、発展の方向性も望みたいものです。高望みでしょうか。



*********************** この項 終わり ***********************************




13 RTC-8564の読み取り   TeraTermへ出力 
 しばらくさわっていないとすっかり忘れています。今回は正規のI2Cデバイスを購入しましたので、今までと違ったアプローチをしてみました。
別項でArduinoの実験もしていますが、そこでWEBに公開されているプログラム(スケッチ)を頂戴して読み取り・表示をしてみましたので、通常のwinavrでもと試みました。

ハードウエアは秋月のリアルタイムクロックRTC-8564NBで8ピンディップの基板に装着されているものです。これを次のようにブレッドボード上に組み立てました。
 1番ピン
 2番ピン 560Ω−LED−GND
 3番ピン 
 4番ピン GND
 5番ピン (SDA)−mega88/27番ピンSDA (4.7kプルアップ)
 6番ピン (SCK)−mega88/28番ピンSCK (4.7kプルアップ)
 7番ピン
 8番ピン −−AダイオードC−Vcc
       −220Ω−スーパーC5.6V1F−GND

時刻設定が瞬間の電源断で蒸発するのが残念なので1Fのスーパーキャパシタでバックアップしています。
mega88は12MHzの水晶によるクロックです。

プログラムはwsNak中尾氏のAVRのI2C制御 アーカイブのものを頂戴して変更しました。
時刻設定はプログラムで決め打ちしています。したがって設定後は #IF 0 でコンパイルからはずし、読み取りだけにして再書き込みしています。

プログラムの仕様は大まかなところが理解できた、というところに留まっていますが便利なサブルーチンをそのまま利用させていただいているのでデバイスの読み取りを書き込みは比較的理解しやすい状態でした。
もともとはEEPROMの書き込み/読み出しプログラムなのでディレイが入っていますが、RTCの場合には必要がないのかもしれません。
プログラムはとりあえず動いた、という状況で間違いがあるかもしれません。

実験したもう一つの理由は、Arduino-IDEで書いたほぼ同じ機能のもの(今回のものに比べてアラーム時刻関係の読み出しが付加されている)が、5,462バイトのプログラムであったのでWINAVRで直接書くとどれほどの差がでるのか知りたかったことなのです。結果は 2,044バイトでした。実行速度はArduino-IDEの方が速いようですがArduino-IDEではプログラムが大きくなると感じていたのは事実のようです。
Arduino-IDEで包まれて状況のよくわからないプログラムと、十分な理解ができないまま使っているひとさまのCプログラムは甲乙付けがたいところがありますが、趣味の世界ではプログラムが小さくて済む、また、日頃使っているWINAVR直接の方が良いのではないかと感じた結果となりました。

*********************** この項 終わり ***********************************




12 mega48-tiny2313(USI)7segLED表示   TeraTerm入力を表示 
 前述のLCD表示では組み込みのコントローラがありますのでスレーブからはデータを渡すだけで表示できます。しかし、LEDではダイナミック点灯で常にLEDをドライブし続けねばなりません。そのとき、I2C通信との時間的なものがどうなるか調べたくて実験しました。

tiny2313のIO端子は、内蔵の発振子使用で、ポートAが2、Bが8、Dが7の計17ですがI2CでポートBの5番と7番が使われますので残りの15となります。そこで、7エレメントLEDは8桁として、小数点は表示できませんが7つのセグメントをドライブすることにしました。勿論小数点を入れて、7桁にすることもできます。

ハードウエア:LEDはアノードコモンの2桁のものを4個つなぎました。各セグメントは手持ちの120Ωの電流制限抵抗を経てポートDに接続しています。やや電流が多いようですが8桁点灯でデューティが小さくなるのでよかろうと考えました。他方アノードは7セグメント分で電流が大きくなりますからPNPトランジスタ2SA1048でドライブします。ベースには1.5kの抵抗を入れています。ベース・エミッタ間の抵抗は入れていません。このトランジスタはmax150mAと小さいのですが、部品取りジャンク基板から多量に持ち合わせがあることと小型で実装しやすいので使いました。新品なら1015といったところでしょうか。最終的に全電源電流が80mA程度になりました。LEDとドライブトランジスタ、抵抗を一つにまとめ、I2Cスレーブのtiny2313は「10 tiny2313のUSI機能 その3」で使ったものにポート出力取り出し用のICソケット(これに自作端子を挿入してコネクタ代わりとします)を付けたものを使いました。
アノード制御はMSBから順にA1,A0,B6,B4,B3,B2,B1,B0のポートを割り当てています。混合ですから桁を指定するときには一工夫必要です。



ソフトウエア:I2C通信はtiny2313のUSI機能で受けます。割り込みは開始条件検出と14ビットカウンタオーバーフロー(データレジスタのシフト完了)の2つを使っています。割り込みルーチンはともにフラグを立てる程度として実際の処理はメインルーチンでしています。
以前の受信ルーチンにLEDの表示ルーチンを付加したのですが通信はできませんでした。表示のウエイト時間は10msとしたのですがこれが長すぎるようで、順に短くしたところ1μsで何とか通信と表示ができるようになったのですが時々失敗します。どうやら表示中に通信が入るのがまずいようです。そこで8桁の表示を連続でするのではなく、1桁表示しては割り込み処理をチェックすることにしますと割り込みがスムーズに入るためか順調に表示するようになりました。「1桁表示」「開始条件処理」「データ受信処理(1バイト)」を順にチェックして処理しています。

この装置はLEDのテストのために作りましたので、送信部はLCD用のものをそのまま使っています。したがって、17文字送らないと通信できないとか、最初の文字は行指定用であるから表示しないなどの不都合が残っています。実際に数値を送るなら4バイトバイナリデータで送って、t2313でデコードするのがスマートだと思います。ROMの使用量が1000バイト強ですから可能だと思います。

スレーブのプログラムをここに置きます。マスタはLCD表示と同じものです。

LCDに比べると文字種、文字量が制約されますが、LCDにない迫力がありますね。嵩は高くなりますが周波数カウンタなどに使えば見やすいものができると思います。 あとで使い回しができるように今回は表示部だけをユニットにしました。でも、配線は蜘蛛の巣状態です。半田付けは好きな方ですがコネクタを含めると満足するだけはありました(笑。  

*********************** この項 終わり ***********************************


11 mega48-tiny2313(USI)LCD表示   TeraTerm入力を表示 
 I2C実験の当初の目的が、「PCから送った文字データをマスタが受信してI2C経由でスレーブに送りLCDに表示する」ことにありましたから、今までの事柄と重複する点は多いのですがまとめの意味を兼ねて書きます。

マスタはUART経由でPCからデータを受けます。PCの通信ソフトは例によってTeraTermを使い、FT232RLを使ったコンバータでUSBのCOMポートから送っています。自作のコンバータはICの出力そのものですから正論理のTTLレベル(実際は3.3V)でMCUにつながります。PCから受けたデータはTWI機能を使ってスレーブに送ります。横着をしてスレーブが開始条件を受け付けないときの処理はしていませんので起動に失敗すればリセットでやり直しになります。スレーブから正しいACK応答がないと通信を続行しません。PCから17文字を受け取るとすぐに通信します。このとき第1カラムが"2"の時はLCDの2行目に表示して、"2"以外の時は1行目に表示します。したがって、LCDに表示されるのは第2カラム以降になります。

スレーブはtiny2313のUSI機能を使っています。開始条件の検出とデータレジスタシフト完了は割り込みを使いますが、フラグをセットするだけでメインで処理をしています。停止条件の検出が(不勉強で)できていませんので、固定長の17文字を受信するとフラグをクリアして開始条件を待つことにしています。
LCDへデータを転送中に次の開始条件が入ると正常動作はできなくなりますが、今は手入力ですから不都合はありません。シリアルの通信ですから、またいろいろな処理も含んでいますからPCから送信後、一瞬遅れて表示するのがよくわかります。

実験装置の写真と回路の構成です。クリックで大きくなります。
 

マスタのプログラムスレーブのプログラムです。lcd.hとlcd.cは「7 mega48−tiny2313LCD表示」の項を参考にしてください。

つでに、今回の実験のために作ったLEDアレイです。

左はデジットで見つけたアレイを細工したもの、抵抗がLEDチップのしたに入っているのでスマートです。
中央は、3mmφLEDをピッチが合うようにヤスリで削り、無指向性にするためにヤスリで削って四角になっています。
右はジャンク部品から取り出したLEDチップです。箱に入った感じでレンズがないので見やすいLEDです。
いずれも、ICソケットやブレッドボードに挿して使います。Hで点灯するようにしてあります。

実験用電源は9Vスイッチング電源を3端子レギュレータで5Vにしたものを使っていますが、電源SWはAC100Vにあり、出力にはかなり大きなコンデンサが入っています。そのため、今回の実験では電源再投入ではうまくリセットできない場合がありました。電源の立ち上がりが極めて遅いのだと思います。実験ボードの電源端子でOFF,ONをしますと正常にリセットされます。電源装置の出力側にもう一つスイッチを付ける必要があります。が、場所がありません....

この装置・プログラムは目的のLCD表示はできるのですが、実用には極めて危険です。異常が発生したときの回復ルーチンをまったく持っていないからです。スレーブの用意が遅れても機能しません。UARTが17文字の途中で途絶えると機能がストップしてしまいます。エラートラップを十分にするには膨大な努力が必要でしょう。この点がアマチュアの遊びかもしれません。その昔、MS-DOS上で簡単な数値処理をするプログラムをCで600行ほど書いたことがありますが、他の人に使ってもらうためにエラートラップを付けると倍以上のサイズになったのを思い出します。
今はもう、遊びの世界にとどめて置きましょう。

これらの実験がUSIを調べてみようという人の参考になれば幸いです。

*********************** この項 終わり ***********************************


10 tiny2313のUSI機能 その3   割込でフラグ、処理はメイン 
 割り込みの一般的な方法である割り込みでフラグを立てて、メインルーチンの無限ループの中でフラグをチェックして処理をする方法を模索してみました。

開始条件を割り込みで検出すると、フラグを0→1に変えて、かつ、オーバーフロー(OF)割り込みを許可して、USIに処理を渡して割り込みルーチンを終わります。

はじめのデータ(アドレスですが)をデータレジスタにラッチするとOF割り込みが発生しますので、データを変数に取り込んで、フラグを2にします。 USISRをクリアしてUSIに渡してルーチンを終わります。

メインでフラグをチェックして、フラグが2であればアドレスをラッチしているはずだから自身のアドレスと比較します。

自身宛が確定したら、ACKを応答しますが、このときにOF割り込みが生じると困るので前もって禁止しておきます。次はデータ受信ですから、受信文字列の初期化をして、OF割り込みを許可してUSIに渡します。

次にOF割り込みが入るとデータ受信のはずですから、フラグを3にして(実際は横着してインクリメントしているだけ)USIに渡します。

メインがフラグ3(3以上)を検出するとデータ処理に移ります。OF割り込みでデータが受信されていると思うのですが実際はもう一度データレジスタに取り込まないとデータが得られませんでした。(理由はわかりません。誤って理解しているかも) データを文字列に取り込み、文字位置をインクリメントして、USIに戻します。

合計20文字を受信したら、割り込みを禁止して置いてLEDに順次表示させます。

20文字受信後はフラグを0にしますから、初期条件割り込みだけを受け付ける状態になります。

送信側では、送信ミスを避けるため、表示のインターバルを考えた送信を行います。

送信側はアドレスを指定した後"0123456789ABCDEFGHIJ"を送っているだけです。ただし、正しいACKを受け取らないとエラーとなってあとのデータが続きません。
どうしたら正しい応答が返るものかその方法がわかりません。TWIの送信仕様だとアドレスで$18、データで$28が正常なのですが、$20、$30のNACKや$38の不正、$00のバスエラーなどが返りました。考えられることをしらみつぶしに書いては実行してプログラムができてきました。
上記の下線部は、どうしてもデータが$00になるのでまさかと思いつつも試したところです。(以外に、無知が原因かな?)

かなりの時間に亘ってUSIと格闘してきましたが、まだまだ理解できていないようです。tiny2313にTWIがあったら、金輪際使わないぞ!と言うところです。
そのプログラムです

スレーブに液晶表示(2行×16字)させるだけなら、割り込みを使わないポーリング方式がすっきりしているかもしれません。
*********************** この項 終わり ***********************************


9 tiny2313のUSI機能 その2   割込を使わない場合 
 割込の利用はポーリングの手間を省きますが、一般処理に優先して実行されますから、ある意味でメインルーチンを邪魔することになります。割込ルーチンが大きいと支障が多いことを教えていただきましたので、この項では割込を使わないスレーブの実験をしました。

 スレーブの用途としては、今のところではLCD表示と7セグメントLEDの表示くらいしか浮かびません。割込を使うかポーリングでするかは、そのケースのプログラムを書いてみて決めることになるかと思います。
 割込を使わなくても、開始条件やカウンタオーバーフローはフラグを判断すればわかりますし、また、シフトレジスタと4ビットカウンタの利用で、100%ソフトに比べるとかなり簡単になっています。USI機能は、うまくセットできれば値打ちがありますが、不具合が発生しますと分析装置を持たない素人には厳しい環境でもあります。

 マスタは前述のmega48のTWIを利用したものです。TWIシステムでは送信毎にその結果をステータスレジスタに取り込んで、返り値が正しいときのみ次の動作をする様になっています。例えば、 開始条件に続いてスレーブのアドレスを送ります。このときスレーブがアドレスを受け取ったあとACKを正しく応答するとマスタのコンディションコードに$18が返されます。同様に1バイトデータを送信してスレーブが正しいACKを応答すると$28になります。
ここ何日か、何週間かのUSI地獄ではこの応答を見ることが最大の問題でした。今日も、割込を使わないスレーブを実験していましたが、どう考えても正しいと思われるところで正しい返り値が表示されません。データバスを出力にして、データにLを用意して、カウンタを14にセットして送り出すのですが、どうしても$18が見られません。かなりの時間を費やしてから、「どうにでもなれ」との感覚でカウンタに13をセットしましたら、正しい返り値が表示されました。なぜ13なのか、まったくわかりません。続けてのデータ受信ではカウンタは14にセットしないと正しい結果が得られません。動いたら良いのですがすっきりしません。シフトレジスタが遅れるのならデータ受信のACK応答でも同じだと思うのですがよくわかりません。

今回の実験では、"0123456789ABCDEFGHIJ"を送って、スレーブのバッファに20文字を取り込み、通信終了後に20文字のコードをLEDアレイに表示するものです。
マスタは20文字を送った後停止条件をバスに乗せます。他方スレーブでは停止条件の検出がわからない(不勉強)ので、20文字をカウントしたところで通信終了としています。
一応、実験基板で通信が確認できましたので、実験に使用したスレーブプログラムを報告します。USI機能の利用でプログラムが簡単になっているのがわかると思います。この使用法はスレーブのLCD表示を目的にします。

*********************** この項 終わり ***********************************


8 tiny2313のUSI機能   mega48マスタTWIからの受信 
 mega48をマスタとして、TWIでスレーブにデータを送信する条件で、tiny2313の受信に関するUSI機能の使い方がおぼろげにわかってきましたのでここにメモを置きます。

まず、関係するレジスタは次のとおりです。
USIDR: データレジスタ。受信データがここに入る。バッファがないのですぐに取り込むこと。また、出力データ(ACKだけ)もここにセットする。
USISR: ステータスレジスタ。
bit7: USISIF 開始条件割込有効。1を書いてクリアすると開始条件保持を解放して(SCL=Lをやめて)開始条件割込を受け付ける状態になる。USIを実行する時に1を書く。1を書いて開始条件を待つ。USICRのUSISIEに1が書かれていると開始条件で割込が起こる。
bit6: USIOIF カウンタオーバーフローフラグ。1を書くとSCLのLが解放されてUSIが実行され、USIDRがシフトレジスタとなる。USICRのUSIOIEに1が書かれているとオーバーフローで割込が起こる。このときSCLはLを保持する(マスタに待ったをかける)。転送するときは、したがって、1を書く。1を書くと直ちに始まる。
bit5: USIPF 停止条件フラグ。停止条件検出で1になるが割込は関係しない。1を書くとクリアされる。(このbitは未研究)
bit4: USIDC 出力衝突検出。(マルチマスタ用、未研究)
bit3〜0:4ビットカウンタ。クロックの両エッジでカウントアップする。データ受信時は0をセット。8ビットで16カウントするとオーバーフローフラグを立てる。 ACKを送信するときは1クロックだから、14にセットしておくと1クロックでオーバーフローになる。
USICR: コントロールレジスタ。
bit7: USISIE 開始条件割込許可。1を書くと検出時に割込が起こる。
bit6: USIOIE カウンタオーバーフロー割込許可。1を書くと検出時に割込が起こる。
bit5〜4: USIWM1,0 動作種別。 I2Cの時は 11(オーバーフローでSCL=L)、10(OFでもSCLは解放)
bit3〜2: USICS1,0 データ&カウンタセレクタ。10でSCLクロック、立ち上がりラッチを選択。
bit1: USICK ソフトウエアクロック。(使用していない)=0
bit0: USITC クロック値切り換え。(使用していない。未研究)=0

割込ルーチンは次のようになります。
  // ********* usi initialise ******
  USICR=0xF8;                      // スタート割込可 カウンタOF割込可 WM=10 CS=10 clk=0 TC=0
  DDR_SDA_IN;PORTB&=~_BV(5);       // SDAポート入力指定
  DDRB&=~_BV(7)                    // SCLポート入力指定
  USISR=0xE0;                      // SIF OIF PFをクリア カウンタ0
このように初期設定すると、最後の行によってUSI動作が実行されます。このとき、次の割込ルーチンを用意しますと開始条件を検出すると割込が発生します。
ISR(USI_START_vect) // ********************* スタートコンディション 割込 ***********************
{
  USISR=0xE0;       // SIF OIF PFをクリア カウンタ0 USI動作を実行させる
  f_int=1;          // フラグは開始条件を受信したので1とする
  i2=0;             // 文字列番地0 連続バイト受信態勢の初期化
}
開始条件の次に来るのはスレーブアドレスですからデータを受けて確認します。
ISR(USI_OVERFLOW_vect) // ********************* オーバーフロー 割込 ***********************
{
  USICR=0xB8;          // OIE禁止(オーバーフロー割込禁止)
  data=USIDR;          // 受信データを保存
  switch(f_int){                        // フラグにより処理を判断
  case 1:                               // スタートコンディションを受け取った直後の時
    if(data==slv_address){              // スレーブアドレス+Wと一致したら
      DDR_SDA_OUT;                      // ACKの準備  SDAを出力にして(以下5行がACKを返す処理)
      USIDR=0;                          // データにLを用意して
      USISR=0xEE;                       // カウンタ14
      while(!(USISR & _BV(USIOIF))){;}  // 転送を待って
      DDR_SDA_IN;                       // データバスをHiZに戻す
      f_int=2;                          // 自身宛のアドレスWが指定されたから、フラグをセットして、次はデータ受信だ
    }
    else {                              // 自身宛でないから何もしない
    }
    break;
  case 2:                               // レシーバー指定されているから(フラグ2)、取り入れた8ビットは受信データだ
    DDR_SDA_OUT;                        // ACKの準備  SDAを出力にして(以下5行がACKを返す処理)
    USIDR=0;                            // ACKのLを送るために
    USISR=0xEE;                         // カウンタ14
    while(!(USISR & _BV(USIOIF))){;}    // 転送を待って
    DDR_SDA_IN;                         // データバスをHiZに戻す
    
    recvdata[i2]=data;
    i2++;                               // 文字列位置インクリメント
    if(i2>2){f_int=0;}                  // 受信文字数が終わっているから(この場合は3文字としている)次は開始条件を待つ
    break;
  }                                     // switch文の終わり
  USISR=0xE0;                           // SIF OIF PFをクリア カウンタ0
  USICR=0xF8;                           // OIF許可
}
このルーチンで3文字を受信することができました(LEDで確認しました)。
USI利用のポイントは、データレジスタとコントロールレジスタを設定して、ステータスレジスタのフラグクリアで実行に入ることのようです。TWIのコントロールレジスタと似ています。

ACK応答は手続きを書かねばなりませんが、割込が使えることとシフトレジスタが自動運転されることで、オールソフトのプログラムよりは格段にすっきりします。マスタの場合はクロックの生成を考えねばなりませんが、スレーブですと上記のように便利な機能だと思います。tiny2313をLCDやLED表示のスレーブとするには十分ではないかと思います。停止条件の検出はまだ勉強していませんが、固定長のデータ通信はできますので、LCD表示の実験へと進める予定です。

難解と思われたUSIもなんとかその姿を見ることができてきました。
想像の部分が多くありますので、間違いがありましたらお知らせいただけるようお願いします。

(追記)
SDA,SCL出力は日本語データシートのp90の図60. 多用途シリアル インターフェース構成図でオープンドレイン出力と記載されています。
上記プログラムで『(以下5行がACKを返す処理)』の部分が2回でてきます。これを1つの関数にまとめると正しい動作をしなくなります。MCUクロックが8MHzで、SCLが100kHzですから十分余裕はあると思うのですが、理由はわかりません。同じように、LEDモニタに書き込みをしてもその後は正しい動作をしません。F_CPUを速くすれば変わるのかもしれませんが実験できていません。
(追記2)
割込ルーチンを他の資料に基づいて実験してみましたが、割込ルーチンが大きいこと、その中に待機ルーチンを含んでいることから好ましいプログラムでないことを教えていただきました。また、ブレッドボードに組んだ実験回路が蜘蛛の巣状態ですから、新しく万能基板に同じ回路を作ってみました。ところが、同じ反応を示さないのです。その原因を調べるべきですが、数時間のトライで挫折しています。ブレッドボード回路で上記のルーチンが正常に動くことは確認しています。通信となると、微妙なノイズやデバイスの特性のかたよりなど関係するのかもしれません。
そこでもう一つのチャレンジ、USI機能を利用した割込を使わない回路の実験へ方向転換しました。やがてはI2Cでスレーブに何をさせるか、で方法が決まると思います。
*********************** この項 終わり ***********************************


7 mega48−tiny2313LCD表示   TWIと自作ソフトの通信 
 マスタにmega48を使ってTWI機能を使った送信を行い、LCDを表示するスレーブにはTWI機能がないtiny2313を使った実験をしました。このスレーブはすでに自作ソフトのI2C通信に成功しているもので、基本的には自作ソフトで問題はないのですがACK応答が完全でないために動かなかったものです。LED表示実験ボードで自作ソフトの問題点を解消しましたのでそれに沿って変更したものです。

デモプログラムでは、マスタに用意した5種類の文字列をスレーブに連続して送っています。停止条件を検知できないので、20文字の固定長通信としました。実用時には1文字目をコードとして扱いLCDの制御に使うつもりです。

右端がプログラム解析用に準備したLED出力スレーブボードです。簡単なボードですがバイト値が直接見えるのは便利なものです。左端のマスタのコンディションコード表示用LEDも大きな効果がありました。

システムの構成(回路)は次のとおりです。


プログラムはつぎのとおりです。 マスタ用  スレーブ用  lcd.h(lcd.txtになっています) lcd.c (なおlcd.hとlcd.cはポートとF_CPUで変えています)

割込はありませんが、周波数カウンタや温度計などの表示には実用上使えると思います。
*********************** この項 終わり ***********************************


6 TWIラーニングメモ 
 I2C(TWI)は2線で通信が行える魅力的なインターフェイスです。以前に実験したSPIは理解しやすかったのですが、スレーブ1つで4線、2つになるとマスタのポートが5個占有されます。AVRの少ないIOをカバーしたいのが本音なので、マスタのIOは少ない方がありがたいわけです。最終的には入出力双方向の通信ができる2つ程度のスレーブを考えています。データ量は少ないので速度は問題にしません。できればスレーブは割込で処理をしたいのですが、他の機能と共存できればポーリングでも実用になるでしょう。ここで、今までの流れと今後の方向を考えてみました。

1.ソフトウエアによる構成:
 tiny2313が使えると良いと思いデータシートを調べるとI2Cができるように書いてあります。読み進めましたが具体的な方法がわかりません。そこで、過去のトラ技を思い出し、クロック一つから手作りの「超サブセットで液晶表示」が試行錯誤の後にできあがりました。SDAとSCLの関係を描きながらタイミングを計ってクロックとデータのバスコントロールを考えました。この流れからI2Cの構造がわかってきました。
2.自作ソフトの拡張:
 この考えをさらに広げて2つのスレーブとバスを繋ぎ、マスタからの要請でスレーブのポート入力の読み取りまでできるようにしたのが二つ目の「サブセットで液晶表示・LED表示・スイッチ読み取り」です。割込を使わずに任意のIOポートで構成できます。時間的に厳しくなければ液晶表示やLED点灯・スイッチ入力には実用になるのではないかと思います。ただ、送受信が身内ですから、なあなあでごまかしている部分があるように思うので、I2C通信と呼ぶには引け目を感じます。なお、クロックを内蔵発振器で済ませる(ポートに余裕ができる)8MHzとして、SCLを通常の100kHzとするとSCLの一つのH(またはL)の間に40ステップの実行ができる勘定になりますので、前後に10ステップ程度のmcu処理が入ってもSCLが延びるだけで、かつ、マスタのクロックに同期だから問題はなかろうと考えました。
3.正規のTWI機能を使う:
 原理が理解できたので、より高度なハードを含むmega48のTWIを読み返しました。こちらは専用のレジスタに命令やデータを書くと自動的に送受信してくれるようです。マスタ機能はデータシートにかなり詳しく書かれています。スレーブは説明が雑ですから、想像で構成してみました。これが「TWIによるLED点灯実験(mega48−mega48)」です。1バイトだけの送信は割合簡単にできました。多少のごまかしはありますが、ループでインクリメンタルデータを送ることに成功しました。このモジュールを使えば十分実用になると感じました。しかし、mega48をスレーブに使う気持ちは余りありません。
4.TWIをマスタに、tiny2313GPIOをスレーブに:
 この組み合わせが成功すると自作ソフトが市民権を得ることになり、tiny2313が使えることになります。上記3でTWIの送信方法が確立しましたので自作ソフトのLED表示スレーブを結合してみました。勿論動きません。
マスタが1データを送りスレーブが正しく受信しているのですがソフトのACKに間違いがあるらしく送信を続行してくれません。思案のあげくデバグ機能として8桁LEDアレイを作り、マスタに返されるコンディションコードを表示させることにしました。ここに至るまで簡単ではありませんが、このモニタは大正解でした。何もしないNACKを返すと正しいコードが得られますが、ACKを返すと不合理な$30が表示されます。ACKルーチンを虱潰しに調べて、そのうち正しいコード$28が返るルーチンを発見しました。複数バイトのデータも正しく送受できます。目に見えるようにディレイを置いて複数のデータを送りますとデータに応じたLEDの点灯が見えるわけですが、同時に返されるステータスコードもマスタ側のLEDアレイで見ることができます。ほっと一息、眺めていました。割込はありませんが、tiny2313が市民権を得られそうです。
5.TWIマスタ、tiny2313スレーブLCD表示:
 自作ソフトの修正場所がわかりましたので、前述のLCD表示器をTWIのスレーブとして使えるように変更できました。例によって割込はありませんが正常に使えそうです。ただし、通信終了が検出できないので固定長送受とします。
6.tiny2313スレーブのスイッチ入力:
 割込無しのマスタ読み込みを考えています。
7.tiny2313スレーブの割込動作:
 USI機能を使ってSTARTコンディションだけ割込で検出できないだろうか。(swNakさんはしておられる)
8.swNak-IOエクスパンダとの通信:
 現在正常動作していないので...
*********************** この項 終わり ***********************************


5 TWIによるLED点灯実験(mega48−mega48)前項4の実装
 前項でTWIの機能をデータシートから読みましたが実際に動くものかどうか実験してみました。
実験回路は下図のとおりです。スレーブのLEDは出力確認用でマスタのLEDはデバグ時にエラー表示として用意しました。

結果的には概ねデータシート読み取りのとおりで、できたプログラムはこのようになりました。前項で調べた結果をコメントでそのまま残しています。 数ヶ所にタイプミスがありましたので修正しました。
 マスタ用プログラム スレーブ用プログラム

tiny2313はTWI機能を持たない(サブセットのUSI)ためソフトウエアでカバーしなければならないところが多いようですが、mega48はTWI機能を持ちますのでハードウエアに頼れるところがずいぶんあるようです。たとえば、スレーブに設定したときにTWARレジスタにスレーブアドレスを一度書き込めばハードウエアがバスを監視していて自分が呼び出されたときにフラグが立つ(イネーブルにしておくと割込がかかる)機能があります。tiny2313に比べるとずいぶん処理が簡単になるようです。

前回調べたプログラムどおりに順次実行したところ、初期の目的どおりに1バイトのデータが送受され、正しい値のLED出力が得られました。
しかし、調子に乗ってループを作り連続して1バイトを送信すると(1バイト毎に開始条件からはじめる)意味不明な点灯をします。
試行錯誤の結果得た結論は(間違っているかもしれませんが)、
1 マスタで停止条件を出しても、TWINTを有効にすれば、スレーブの受信結果は「停止条件」を示さずに、「データ受信」コードが返ります。スレーブが直前のデータ受信でNACKを返すか、または、マスタでTWSTOpと同時にTWENableを無効にしておくと、受信側では正しい「停止条件受け取り」コードが返るようです。
2 スレーブが次の受信を開始条件ではじめるなら、前回の終わりにTWIを無効にして、あらためて受信態勢を作るのも一つの方法と思います。

マスタからスレーブへ1バイトのデータを送るだけ、という最も簡単な実験だけですが時間を見ていろいろなケースも調べようと考えています。プログラムを基本から勉強していないのでわかりにくいのですが関数やマクロを作っていけば使いやすい環境ができるのではないでしょうか。
PICはハードウエアが充実しているそうですが、tiny2313でそれを求めるのは酷でしょう。16F84にはUARTさえなかったと思います。

TWI(I2C)はEEPROMなどのデバイスを目的に使われることが多いようです。そのためにマスタとして使うケースは多く見かけるのですが、スレーブとしてAVRを使う例は極めて少ないので、まだ適切な例に出会っていません。
*********************** この項 終わり ***********************************


4 TWI説明を読む(mega48データシート日本語版)自分の理解を助けるためのメモ書き
 12Cは専用機能を使うとIO端子選択の自由度は減りますが、割込処理なども使えて便利だと思います。何もわからない中でかじれるところだけからでもと考えてソフトウエアI2Cを試みましたが、AVRの機能も調べてみようとデータシートを読みました。その中で理解できそうなところを自分の言葉になおして書いてみました。

 TWI使用法
 バイト志向で、割込が基本になっています。1バイトの送受信・START送信などのTWI処理のあと割込フラグが立ちます。グローバル割込が許可されsei()、TWCRレジスタのTWENビットが許可されていれば、割込フラグ(TWCRレジスタのTWINTビット)が立てば割込ルーチンにジャンプできます。

関係レジスタ:
TWBR TWI ビット レート レジスタ (TWI Bit Rate Register)
TWCR TWI制御レジスタ (TWI Control Register)
TWSR TWI ステータス レジスタ (TWI Status Register)
TWDR TWI データ レジスタ (TWI Data Register)
TWAR TWI (スレーブ) アドレス レジスタ (TWI (Slave) Address Register)
TWAMR TWI (スレーブ) アドレス マスク レジスタ (TWI (Slave) Address Mask Register)
この中で、コントロール、ステータス、データのレジスタの使い方がポイントになります。

重要事項:(特にTWINTフラグ)
・TWIの処理が終わるとTWCRのTWINTフラグがセット(1)されます。SCLバスはTWINTフラグがプログラムでクリアされるまで、Lが続きます。通信関係はサスペンドになります。
・プログラムはTWINTのセット(1)を待って、次の処理に入ります。すなわち、TWINTのセット(1)を確認して、次の処理のためにレジスタを書き換えます。
・TWI関係レジスタの書き込みで次の命令を決めますが、このときTWCRを最後に書きます。このTWCRのTWINTは自動的にクリアされることはなく、TWINTに1を書き込んでプログラムでクリアします。1を書き込むとクリアされて(0)になることに注意します。TWINTが(1)の間は実行が止まり、(0)にすると次の処理が始まります。

マスタから1バイトのデータをスレーブに送る手順は次のようになります。(マスタの手順です)
@開示条件を送ります。バスの信号はSCLがHのままでSDAが ̄\_立ち下がります。
 TWCRに、開始・イネーブル・INTのビットを書き込みます。
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN) ;
 TWINT=1が書かれ、クリアされますので、直ちに実行されます。送信が完了するとTWINTがセットされます。

A処理完了のTWINTフラグが立つまでプログラムは待ちます。
  while(!(TWCR & _BV(TWINT))) ;

B開始条件が正常に送られたかどうかTWSRを検査します。TWSRの2:0には他のビットが同居しているのでマスクします。
 エラーがあれば別のルーチンでの処理を考えます。
  if((TWSR & 0xF8) != START){ERROR();}
 正常なら、TWDRにスレーブアドレス+Wを書いて、TWCRに送信命令と実行開始のためにTWINTに1を書きます。
(すぐに実行されます)
  TWDR = SLA_W;
  TWCR = _BV(TWINT) | _BV(TWEN) ;

C送信が完了するのをTWINTフラグを見ながら待ちます。
  while(!(TWCR & _BV(TWINT))) ;

DTWSRコードで、ACKが返っているか調べて、異常ならエラールーチンを呼びます。
  if((TWSR & 0xF8) != TW_MT_SLA_ACK){ERROR();}
 正常なら次のデータ送信のためにTWDRにデータを書いて、TWCRに送信命令とTWINTクリアを書きます。実行されます。
  TWDR = data;
  TWCR = _BV(TWINT) | _BV(TWEN) ;

E送信完了をTWINTフラグを見ながら待ちます。
  while(!(TWCR & _BV(TWINT))) ;

FTWSR状態コードでACKが返っているかを見ます。返っていなかったらエラー処理を呼びます。
  if((TWSR & 0xF8) != TW_MT_DATA_ACK){ERROR();}
 正常なら停止条件を送るために、TWCRに、TWINT・停止・イネーブルビット(1)を書いて実行します。
  TWCR = _BV(TWINT) | _BV(TWSTO | _BV(TWEN) ;
 停止条件送信後はTWINTはセットされません。したがって、クリア操作は不要です。
(以上で1バイトのデータをマスタからスレーブに送信できます。プログラムでは、定義ファイルの設定が必要です。)
  #include <util/twi.h>

スレーブが1バイトを受信する手順は次のとおりです。(説明がなく想像部分が多くあります。 ???多し)
@スレーブを初期化するために、スレーブアドレスレジスタにアドレスを書きます。
 ビット0は一斉呼び出しに応じないので0を書きます。
 TWCRにはTWENとTWEA(ACK応答用)のビット立て(1)、TWSTAとTWSTOは(0)とします。
  TWAR = SLA + 0 ; //SLAは0を付加して8ビットにしたもの
  TWCR = _BV(TWEN) | _BV(TWEA) ;

A上記で受信態勢が完了して、自身のアドレス+Wを受信するとTWINTがセット(1)され、
 @でACK応答を採用しているとTWSRに、正常ならば、TW_SR_SLA_ACK ($60)が書かれる。
  while(!(TWCR & _BV(TWINT))) ;  //割込を使わないとき
  if((TWSR & 0xF8) != TW_SR_SLA_ACK){ERROR();}

Bデータを1バイト受信します。
 TWCRにTWEN、TWEA、TWINTのビットを立ててデータを待ちます。
 受信完了を検知したらデータを変数に代入します。
  while(!(TWCR & _BV(TWINT))) ;  //割込を使わないとき
  if((TWSR & 0xF8) != TW_SR_DATA_ACK){ERROR();} //$80
  data = TWDR ;
C次のデータの受信準備をします。
 TWCRにTWEN、TWEA、TWINTのビットを立ててデータを待ちます。
 受信完了を検知したらデータを変数に代入します。
 終了条件のチェックをします。終了条件ならTwi_stopルーチンへ。
  while(!(TWCR & _BV(TWINT))) ;  //割込を使わないとき
  if((TWSR & 0xF8) == TW_SR_STOP){Twi_stop();} //$80
  if((TWSR & 0xF8) != (TW_SR_DATA_ACK || TW_SR_STOP)){ERROR();} //$80,0xA0
  data = TWDR ;
DSTOPでないとき(データの受信を続けるとき)はCを繰り返します。
 

*********************** この項 終わり ***********************************



3 サブセットで液晶表示・LED表示・スイッチ読み取り
 マスターとしてmega48を配置し、2個のtiny2313をスレーブとして1つにはLCD表示器を、他の1つには入力スイッチとLEDを接続したものをセットとして、マスターから 任意の文字列をLCDに表示すること、スイッチのon/offを読みとること、1バイトデータをLEDに表示すること、を目指した実験が何とか初期の目的を果たしましたので まとめます。今は、実験装置も頭の中もスパゲティ状態です。

I2CバスのIOは専用機能ではなく一般のIO(GPIO)を使っています。LCDスレーブは他の関係でPB5とPB7を使っていますが、SW/LEDスレーブでは内蔵発振器を使いますから PAに割り当てました。マスタを含めてどのIOでも使用可能です。SW/LEDは4ビットに省略しています(8,7ビットが可能です)。
通信を簡略にするために、マスタは1台に限り、スレーブは複数接続の実験としました。割込は使っていませんのでスレーブのポーリングにより通信開始を監視しています。

ソフトウエアのメモ
1.クロックはソフトで発生していますが、HLそれぞれに5μsほどのウエイトをかけて、約100kHzとしています。マスタの基準クロックは9.22MHzのセラロックを 使用しています。
2.I2Cのバスラインは抵抗プルアップしたものをオープンドレイン/オープンコレクタでドライブしてHとLの衝突を避けています。プルアップ抵抗が大きいとL→Hの 立ち上がりが鈍りますので2.7kを使っています。10kでは立ち上がりが遅れますので読み取りにディレイが必要になります。
3.AVRのポートはオープンドレインではありませんので、H出力時はDDRxで入力設定としてHiZを実現しています。(内部プルアップが必要→原因は不明です)
4.通信の各部分は関数で扱いますが、関数の呼び出し及び終了はクロック(SCL)Lのタイミングにします。データ転送時はデータ(SDA)はSCLがLの時遷移します。
5.スタートコンディション、ストップコンディション、リスタートコンディションはSDA遷移の前後に5msを取っています。(これは専用ICの中にはこの程度の時間を 必要とするものがあると読んだことに起因しています。長いと便利なこともあります。)
6.データの読み書きは、マスタでは、
 書き込み: SDAをセットして、SCLをHにする。5μs待って、SCLをLにして、5μs後に次に進む。
 読み取り: SCLをHにして(5μs後)SDAを読みとる。SCLをLにして(5μs後)次に進む。
7.スレーブ側の読み書きは、マスタのSCLに同期して行う必要がありますので、
 読み取り: SCLの終了を待って[ while(SCL==L); ](一呼吸置いて)、SDAを読みます。読み取り後、SCLがHのうちは待ち続けます。
 書き込み: SDAをセットしてSCLの終了を待ちます。続いてSCLの終了を待ちます。
8.AKNとNAKの書き込みについては、8ビットデータを受信した後、AKNはデータ書き込み(送信)状態にしてLを送ります。NAKはHiZのままにしておきます。
9.受信の終了は、スレーブの場合、ストップコンディションの受信によりますが、そのためのポーリングが面倒ですからSCL=Hが長時間続けば終了と判断します。
10. 液晶に送るデータは、グローバル変数領域に設定した20バイトのcharを送ることにします。液晶は16桁ですが、将来にコントロールコードを埋め込むつもりです。
11. 自分の頭を整理するために次の語を使うことにします。
 マスタがスレーブに送るとき・・・write
 マスタがスレーブから受けるとき・・・read
 スレーブがマスタから受けるとき・・・recv
 スレーブがマスタへ送るとき・・・send

プログラムはここに置きます。マスタ用  スレーブ液晶用lcd.h lcd.c)  スレーブLED/SW用 (注意:サーバーの関係で.hファイルは置けませんので、lcd.txtに変えています。使うなら.hにファイル名を変更してください)

このデモプログラムは、マスタの持つ文字列をLCDスレーブに送ること、LED/SWスレーブからSW情報を読みとること、LEDを点滅すること、の機能を入れてみました。

評価
以上の機能で初期の実験目的は成功したと思っています。マスタが1基に限られている、スレーブにマスタ機能がない、割込を使っていないためポーリングの能率が悪い、またそのためにタイムラグが大きくなる、スタートコンディションは必ず読まれるものとして書かれている(安全対策がない)、など不満もありますが、辛抱すればIO端子が増えたことのメリットはあるでしょう。6桁の7セグLEDがメインボート離れたところにあるときなど極めて効果があると思います。入力スレーブを付けたときはポーリングの間隔が問題になるかもしれませんが、例えばロータリーエンコーダの設定とモニタをスレーブにさせて、決定した値をマスタに送る時には多少のタイムラグは問題にならないかと思います。
汎用IOの利用は、AVRによく見られる8ビットが連続して使えないことの対策になるのではないでしょうか。tiny2313が入手しやすい現状では利用の方法が考えられると思います。

実験中の様子。回路図はかけないかも...
                         <感想をいただければ幸甚です>


*********************** この項 終わり ***********************************



2 超サブセットで液晶表示
 専用レジスタを使用しての複雑なプログラムは設定が一つでも間違えると部分的な動作も正常にしてくれません。以前のSDカードの読み出しでも同じ状態になり、 結局ソフトウエアオンリーでチマチマと書き進み読み書きに成功しています。今回も方針を変えてこの方法で試みることにしました。トラ技06年6月号のI2C記事では EEPROMとの通信にGPIOを使ったソフト制御が書かれています。

I2Cの通信規則は多くありますが、まず実現できるところだけを取り出すことにしました。そのため動作を極端に簡略化して、
 マスタは1つ、スレーブも1つ。
 マスタは1つしかないスレーブに対して、開始条件とスレーブアドレス+Wを発行する。
 スレーブはポーリングでデータとクロックを監視する。開始条件に続いて自分のアドレスを受け取ると、データ受信の準備をする。
 マスタはスレーブの応答に関係なく、1バイトのデータ+1クロックを16バイト分送り出す。
 スレーブは1バイト受信すると配列に収め、クロックにあわせてとりあえずACKのデータLを送る。これを16回繰り返す。
 スレーブはこれで一つの通信が終わったと判断して、終了条件無しで、読み取りルーチンを終わり、LCDに文字列として表示した後、ポーリング動作に入る。
 開始条件はクロックHのままデータをHからLに変える。長目が良さそうなので、変化の前後に5msのディレイを入れている。
 データ、クロックは平坦部を適当に付けるためにディレイを入れて、およそ100kHzになるようにしている。
 レベルLはLを出力しているが、レベルHはポートを入力にしてハイZの状態にしている。外部抵抗によるプルアップでHにしている(衝突を避けている)。
10 1ビットのデータ送信では、クロックLでデータをセット(変更)して、クロックをHにする。クロックLで次のビット値に変える。受信側では、クロックLでは 待ち続け、クロックHになると極短いディレイの後にデータを読み込む、クロックHの間はそのまま待ち続け、次のクロックLの動作に続く。

以上の動作で、定数としてセットした16文字を送受して、スレーブのLCDに表示することに成功しました。

ハードウエアの回路図は省略しますが、
マスタ:mega48、Vcc=5V、9.22MHzセラロック、データポート=PB0、クロックポート=PB1、PB0とPB1は10kでプルアップ、リセットスイッチ付き、(波形が鈍るので プルアップ抵抗を2.7kに変更しました。)
スレーブ:tiny2313、Vcc=5V、内蔵8MHz、データポート=PB5、クロックポート=PB7、LCDはPB0〜3とPD5,6、リセットスイッチ付き、
です。
両方を交互にプログラムしましたから、その都度プログラマを繋ぎ変えています。



マスタのプログラム と スレーブのプログラム です。 (やっと動いた、というところで整理できていません。 →少し整理しました)
文字列を送って、スレーブのLCDに表示する−−−この目的だけなら実用になると思います。


1 プロローグ
 mega168のUSBコンバータ付きボードの基板とデバイス一式をsenshuさんからいただいて、wsNakさんのプリント基板を作らせていただきました。 mega168はプログラムメモリが多く、SRAMも1kBありますからかなりのものが作られると思います。ただ、このシリーズはIOが少なく、その上に実用的には連続した 8ビットが使えない状態です。そこで、通信によりTiny2313にデータを送って、2313に表示機能を任せればmega168の数少ないポートを有効に使えると考えました。

以前に、同じ考えから8桁LEDをmega8にSPI通信で送って表示したことがありましたが、今回はバスが2本で済むI2Cを企てました。I2Cはデータとクロックの 2本のバスでオープンコレクタのOR接続を使っていくつものデバイスを接続できます。例えばSPIで2つのスレーブをつなぐと、MISO,MOSI,SCK,CS1,CS2の5個のポートを 必要としますが、I2Cなら2個のポートで行えます。mega168にはI2C(atmelではTWIという)のハードウエアが組み込まれているようですし、tiny2313には簡略型のUSI機能 が準備されています。2つのデバイスとも自由なポートが選べないのですが、I2Cを理解するためにも暗中模索をはじめることにしました。
マスターの例は少しあるのですが、記述が難しいこと(プロの仕事)、スレーブはほとんどの例がプログラムが完成されているEEPROMのようで、マスターとスレーブの 動作を比較しながら勉強できる例を見つけることはできませんでした。

データシートを見つめながら部分のプログラムを、レジスタを調べながら書いてみるのですが一向に進展しません。このあたりが素人の独学のしんどいところです。 数十回は書いてみたでしょうか、1つしかないmega168がかわいそうになって、急遽mega48のブレッドボードで予備実験することに変更しました。


TOPページへもどる