I2C / SCL周波数を設定する

データシートによればSCL周波数はTWBR[7:0]TWSR[1:0] (= TWPS)の各レジスタ値から次式で求まる。

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

この式をTWBRについて解くと次のように変形できる。
TWBR == F_CPU / (2 * SCL_freq * (2^TWPS)^2) - 8 / (2^TWPS)^2

F_CPUについて解くと次のように変形できる。
F_CPU == 2 * SCL_freq * TWBR * (2^TWPS)^2 + 16 * SCL_freq

結局F_CPUを基準とする分周比DIVは下式で求まる。
DIV == 2 * (TWBR * 4^TWPS + 8)

f:id:ti-nspire:20200814134400p:plain:w700

こんな感じにすればF_CPUとSCL周波数とからTWBR[7:0]TWSR[1:0] (= TWPS)の各値が求まる。

#include <iostream>
using namespace std;

// TWSR[1:0] (== TWPS)、TWBR[7:0]の各値から、F_CPUを基準とする分周比を求める函数。
uint16_t calc_div_reg(uint8_t twps, uint8_t twbr){
    return 2 * (twbr * (1 << (twps * 2)) + 8) ;
}

// F_CPUとSCL周波数とから、F_CPUを基準とする分周比を求める函数。
uint32_t calc_div(uint32_t f_cpu, uint32_t scl_freq){
    return f_cpu / scl_freq;
}

// TWSR[1:0] (== TWPS)の値を求める函数。
uint8_t calc_twps(uint32_t div){
    uint8_t twps;
    if     (div <= calc_div_reg(0,255)){twps = 0;} //      ~   526
    else if(div <= calc_div_reg(1,255)){twps = 1;} //  528 ~  2056
    else if(div <= calc_div_reg(2,255)){twps = 2;} // 2064 ~  8176
    else                               {twps = 3;} // 8208 ~
    return twps;
}

// TWBR[7:0]の値を求める函数。
uint8_t calc_twbr(uint32_t f_cpu, uint32_t scl_freq, uint8_t twps){
    uint32_t twbr = f_cpu / (2 * scl_freq * (1<<twps) * (1<<twps)) - 8 / ((1<<twps) * (1<<twps));
    return (twbr > 255) ? 255 : (uint8_t)twbr;
}

// TWSR[1:0] (== TWPS)、TWBR[7:0]を書き換えるための函数。
uint8_t TWSR, TWBR; // ★★確認用。あとで消す。★★★★★★
void set_SCL_Clock(uint32_t f_cpu=16000000UL, uint32_t scl_freq=100000UL){
    uint8_t twps = calc_twps(calc_div(f_cpu, scl_freq)); // TWSR[1:0] (== TWPS)の値を求めて、
    TWSR &= ~0b11;                                       // いったんTWSR[1:0]を消して、
    TWSR |= twps;                                        // 求まったTWPS値をTWSR[1:0]に書き込んで、
    TWBR = calc_twbr(f_cpu, scl_freq, twps);             // TWBRレジスタも書き換える。
    printf("TWPS: %d\n", TWSR & 0b11); // ★★確認用。あとで消す。★★★★★★
    printf("TWBR: %d\n", TWBR);        // ★★確認用。あとで消す。★★★★★★
}


int main() {
    set_SCL_Clock(16000000UL, 500); // (F_CPU, SCL周波数);

    return 0;
}

実行結果:
f:id:ti-nspire:20200814130317p:plain