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

参考: 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に設定してみる。         

```cpp
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/7fc89f94-fc36-c3a1-4119-64f25dacd267.png)