LoginSignup
1
0

More than 3 years have passed since last update.

Arduino UNOのI2CのSCL周波数を極端に低くしたいとき

Last updated at Posted at 2020-08-16

参考: https://elchika.com/article/baebfc34-dc91-4146-82ee-50a5714af578/

Arduino UNO (ATmega328P)のI2CペリフェラルをWireライブラリーで駆動する場合、SCL周波数はWire.setClock()函数で設定する。設定しない場合のデフォルト値は100 kHzである。

Arduino UNO (ATmega328P)のSCL周波数SCLfreqは、データシートによればTWBR[7:0]TWSR[1:0] (=TWPS)という2つのレジスタ値から次式で求まる。

SCLfreq = F_CPU / (16 + 2 * TWBR * 4^TWPS)
(TWBRは0~255、TWPSは0~3)

したがってSCL周波数は、(F_CPU / 16) ~ (F_CPU / 32656)の範囲で設定できる。しかしWire.setClock()函数はTWSR[1:0]0b00に固定してあるため、(F_CPU / 16) ~ (F_CPU / 526)の範囲でしか設定できない(F_CPU = 16 MHzとすればSCLfreq = 1 MHz ~ 約30 kHz)。

分周比を526よりも高くしたい場合は、Wire.setClock()函数は使わずに、TWBR[7:0]TWSR[1:0]の各レジスタ値を直接書き換える。

Arduino UNO (ATmega328P)のI2CのSCL周波数を極端に低くしたいときのサンプル

たとえば下のように、所望のSCL周波数からTWPS値(=TWSR[1:0])とTWBR値とを求めて設定する。これで32656分周(F_CPU = 16 MHzとすればSCLfreq = 約490 Hz)まで下げられる。できるというだけであって、あまり意味はない。

確認のためのサンプルファイル(.ino)

set_SCL_freq.hをインクルードし、set_SCL_Clock()函数でSCL周波数を設定する。ここでは500 Hzに設定してみる。

#include <Wire.h>
#include "set_SCL_freq.h" // これをインクルードする。

void setup() {
  Wire.begin();
  //Wire.setClock(100000UL);
  set_SCL_Clock(16000000UL, 500); // 引数は(F_CPU, 所望のSCL周波数)
}

void loop() {
  Wire.beginTransmission(8);
  Wire.endTransmission();
  delay(500);
}

ヘッダファイル

set_SCL_freq.h
#ifndef SET_SCL_FREQ_H
#define SET_SCL_FREQ_H

//#include <avr/io.h>

#define MAX(a, b)                (((a) > (b)) ? (a) : (b))
#define MIN(a, b)                (((a) < (b)) ? (a) : (b))
#define CONSTRAIN(val, min, max) (MIN((MAX((val), (min))), (max)))
#define SQUARE(a)                ((a) * (a))

// TWSR[1:0]、TWBR[7:0]の各値から、F_CPUが何分周されるのかを求めるマクロ。
#define DIV_REG(twps, twbr)  (((twbr) * (1 << ((twps) * 2)) + 8) * 2)

const uint8_t TWPS_MAX = 3;
const uint8_t TWBR_MAX = 255;

const uint16_t DIV_MIN = DIV_REG(0, 0);               // 最小16分周まで可
const uint16_t DIV_MAX = DIV_REG(TWPS_MAX, TWBR_MAX); // 最大32656分周まで可

const uint16_t DIV_MAX0 = DIV_REG(0, TWBR_MAX); //  526。TWPS = 0のときの最大分周比。
const uint16_t DIV_MAX1 = DIV_REG(1, TWBR_MAX); // 2056。TWPS = 1のときの最大分周比。
const uint16_t DIV_MAX2 = DIV_REG(2, TWBR_MAX); // 8176。TWPS = 2のときの最大分周比。

// F_CPUと所望のSCL周波数との比を求める函数。
uint16_t calc_div(uint32_t f_cpu, uint32_t scl_freq){
  uint32_t div = f_cpu / scl_freq;
  return (uint16_t)(CONSTRAIN(div, DIV_MIN, DIV_MAX));
}

// TWSR[1:0] (=TWPS)の値を求める函数。
uint8_t calc_twps(uint16_t div){
  uint8_t twps;
  if     (div <= DIV_MAX0){twps = 0;} // F_CPUとSCL周波数との比が     ~  526の場合
  else if(div <= DIV_MAX1){twps = 1;} // F_CPUとSCL周波数との比が 528 ~ 2056の場合
  else if(div <= DIV_MAX2){twps = 2;} // F_CPUとSCL周波数との比が2064 ~ 8176の場合
  else                    {twps = 3;} // F_CPUとSCL周波数との比が8208 ~     の場合
  return twps;
}

// TWBR[7:0]の値を求める函数。
uint8_t calc_twbr(uint32_t f_cpu, uint32_t scl_freq, uint8_t twps){
  scl_freq = CONSTRAIN(scl_freq, f_cpu / DIV_MAX, f_cpu / DIV_MIN);
  return (uint8_t)((f_cpu - (scl_freq * 16)) / ((scl_freq * SQUARE(1 << twps)) * 2));
}

// TWSR[1:0] (=TWPS)、TWBR[7:0]を書き換えるための函数。
void set_SCL_Clock(uint32_t f_cpu, uint32_t scl_freq){
  uint16_t div = calc_div(f_cpu, scl_freq); // 所望の分周比を求めて、
  uint8_t twps = calc_twps(div);            // TWSR[1:0] (=TWPS)の値を求めて、
  TWSR = (TWSR & ~0b11) | twps;             // その値でTWSR[1:0]を書き換えて、
  TWBR = calc_twbr(f_cpu, scl_freq, twps);  // TWBRレジスタも書き換える。
}

#endif

実行結果

SCLを観測している。

scope_3.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0