ATmega328でSC16IS750を使う

i2cインタフェース



ATmega328にはUARTポートが1組しかありません。
Software Serialライブラリも有りますが、57,600bpsが限界です。
今回、i2c/SPIインタフェースのUARTモジュールを入手しました。
これを使えばUARTを増設することができます。

メインチップはSC16IS750で、それ以外に14.7456Mhzのクリスタルと5V→3.3Vのレギュレータ (MIC5219-3.3YM5)が実装 されています。
SC16IS750は送信用、受信用それぞれに64ByteのFIFOバッファを持っているので、この範囲ならば取りこぼしは起こらないです。
Schematicがこ ちらに公開されています。


SC16IS7XXには以下のバリエーションが有ります。
モデル UARTチャネル数 プログラマブルI/Oピン SPI Clock Speed
SC16IS740 Single 無し(※) 4 Mbit/s
SC16IS741 Single 無し(※) 4 Mbit/s
SC16IS750 Single 有り 4 Mbit/s
SC16IS752 Dual 有り 4 Mbit/s
SC16IS760 Single 有り 15 Mbit/s
SC16IS762 Dual 有り 15 Mbit/s

(※)
SC16IS740ではInput Latch機能を有効・無効にできますが、SC16IS741ではこの機能がありません。
SC16IS740/750/760のデータシートの8.20にInput Latch機能のことが書かれています。
正確には理解できないのでそのまま記載します。

0 =入力値はラッチされません。
入力を変更すると、割り込みが生成されます。
入力レジスタの読み取りは割り込みを中断します。
入力レジスタが読み取られる前に入力ピンが初期論理状態に戻った場合、
その後、割り込みがクリアされます。

1 =入力値はラッチされます。
入力の変更により割り込みが生成され、入力論理値が対応する入力状態レジスタ(IOState)のビットにロードされます。
IOStateレジスタの読み取りは、割り込みを中断します。
入力レジスタが読み取られる前に入力ピンが初期論理状態に戻った場合、
その後、割り込みはクリアされず、IOStateレジスタの対応するビットは、割り込みを開始する論理値を保持します。



Githubを探したところ、こちらの ライブラリを見つけました。
そこでまずはi2cで使ってみます。
i2cで使う場合の結線は以下の様になります。
SC16IS750 ATmega328 備考
i2c-SPI 5V i2c/SPIの切り替え
A0-CS 5V i2cアドレスの選択(※)
A1-SI 5V i2cアドレスの選択(※)
NC-SO N/C i2cの時は使わない
SCL-SCK SCL PullUpする必要が有る
SDA-VSS SDA PullUpする必要が有る
/IRQ N/C どこにも繋がっていない
RESET N/C 基板上でPullUpされている
VIN 5V
GND GND

SC16IS750の動作電圧は3.5Vか2.5Vです。
SC16IS750のVddにはモジュール上のレギュレータで5V→3.3Vに変換して給電していますが、
デー タシートの13. Static characteristicsにHIGH-level input voltageは5.5Vと書かれていますので、
5VのAtmega328と繋いでも、i2cのレベルシフトは不要です。

※これらのピンはi2cアドレスの選択に使われます。
アドレスの範囲はデー タシートの10.3章に記載されています。
A0とA1をVddとした場合、i2cアドレスは0x90となります。
このライブラリでは「SC16IS750_ADDRESS_AA」の定数で与えます。
i2cスキャナーでスキャンすると0x90(1001-0000)は1ビット右にシフトした0x48(0100-1000)として検出されます。




TXとRXをクロスで結線しライブラリに付属するI2CSELFTESTを動かしてみました。
このサンプルはエラーが無いと何も表示を行いません。





このライブラリではフレーミングを、データ長=8ビット、パリティなし、STOPビット=1ビットで設定します。
SetLine()でフレーミングをレジスターに書き込みますが、残念ながらPrivate関数なのでこの関数をユーザが使うことはできません。
フレーミングを変更する場合は、ライブラリソースを直接変更して、SetLine()の引数を変更する必要があります。

SetLine(uint8_t data_length, uint8_t parity_select, uint8_t stop_length )

data_length
データ長(5〜8)

parity_select
0:No parity
1:Odd parity
2:Even parity
3:Forced '1' stick parity
4:Forced '0' stick parity

stop_length
STOPビット長。1または2



最大通信速度はクリスタルの周波数で決まります。
14.7456Mhzのクリスタルが使われている場合、14,745,600/16 = 921,600bpsですが、Arduino UNOと通信する場合、Arduino UNO側が115,200bpsまでしか保証していません。
何とか230,400bpsで通信できますが、完全に規格外です。
115,200bpsで通信できれば十分です。
#include <Wire.h>
#include <SC16IS750.h>
#include <string.h>
#include <SPI.h>

SC16IS750 i2cuart = SC16IS750(SC16IS750_PROTOCOL_I2C,SC16IS750_ADDRESS_AA);

//Connect TX and RX with a wire and run this sketch

//#define baudrate 57600
//#define baudrate 115200
#define baudrate 230400
//#define baudrate 460800
//#define baudrate 921600

void setup()
{
  Serial.begin(115200);

  Serial.println("start i2cuart");
  // UART to Serial Bridge Initialization
  i2cuart.begin(baudrate);               //baudrate setting
  if (i2cuart.ping()!=1) {
      Serial.println("device not found");
      while(1);
  } else {
      Serial.println("device found");
  }
  Serial.print("start serial communication. baudrate = ");
  Serial.println(baudrate);
}

void loop()
{
  static char buffer[64] = {0};
  static int index = 0;

  if (i2cuart.available() > 0){
    // read the incoming byte:
    char c = i2cuart.read();

#if 0
    Serial.print("c=");
    if (c < 0x20) {
      Serial.print(" ");
    } else {
      Serial.print(c);
    }
    Serial.print(" 0x");
    Serial.println(c,HEX);
#endif

    if (c == 0x0d) {
     
    } else if (c == 0x0a) {
      Serial.print("[");
      Serial.print(buffer);
      Serial.println("]");
      index = 0;
    } else {
      buffer[index++] = c;
      buffer[index] = 0;
    }
  }
}

通信速度はクリスタルの周波数を分周して使うので、通信速度によっては誤差が生じる可能性が有ります。
分周の仕方はデータシートに記載されていますが、例えば、3.072MHzのクリスタルを使って、
3600bpsの通信速度を設定する場合、クリスタルの周波数を53.3333で分周する必要がありますが、
この様な値はレジスターに設定できなので、レジスターには54を設定します。
その結果、実際の通信速度は3555bpsになります。
この計算は以下の式で求めることができます。
#include <stdio.h>
void main() {
  long crystal_freq = 3072000; // 3,072,000MHz
  long prescaler = 1;
  long baudrate = 3600;
  long divisor1 = crystal_freq/prescaler;
  long divisor2 = baudrate*16;
  double divisorf = (double)divisor1/(double)divisor2;
  long divisor = divisorf + 0.999;
  printf("divisor1=%ld divisor2=%ld divisor=%ld divisorf=%f\n",
    divisor1, divisor2, divisor, divisorf);

  long actual_baudrate = (divisor1/divisor)/16;
  printf("actual_baudrate=%ld\n", actual_baudrate);
}

続く...