RX マイコンのクロック設定
- RX マイコンのクロック設定はかなり柔軟ではあるものの、設定が出来ない組み合わせも多々ある。
- その場合、現状の実装では、微妙なクロックで動作していても、その異変を検出するのが難しくなってしまう。
- そこで、C++11 から追加された「コンパイル時アサート static_assert」を使い、コンパイル時に、不整合を検出して、エラーで停止するようにした。
- constexpr を組み合わせて、コンパイル時に複雑な条件を検出できる。
- static_assert はコンパイル時に評価されるので、実時間に影響を与えない。
※何故、今まで使わなかったのか・・
clock_profile.hpp
- 以前、Makefile で、各クロック周波数を外部から与えていたが、最近は、clock_profile クラスで定義するようにした。
- この定義は、外部から簡単に参照出来るようにしてある。
RX72T の場合:
namespace device {
class clock_profile {
public:
#ifdef USE_USB
static constexpr bool TURN_USB = true; ///< USB を使う場合「true」
static constexpr uint32_t BASE = 16'000'000; ///< 外部接続クリスタル
static constexpr uint32_t PLL_BASE = 192'000'000; ///< PLL ベースクロック(最大200MHz)
static constexpr uint32_t ICLK = 192'000'000; ///< ICLK 周波数(最大200MHz)
static constexpr uint32_t PCLKA = 96'000'000; ///< PCLKA 周波数(最大120MHz)
static constexpr uint32_t PCLKB = 48'000'000; ///< PCLKB 周波数(最大60MHz)
static constexpr uint32_t PCLKC = 192'000'000; ///< PCLKC 周波数(最大200MHz)
static constexpr uint32_t PCLKD = 48'000'000; ///< PCLKD 周波数(最大60MHz)
static constexpr uint32_t FCLK = 48'000'000; ///< FCLK 周波数(最大60MHz)
static constexpr uint32_t BCLK = 48'000'000; ///< BCLK 周波数(最大60MHz)
#else
static constexpr bool TURN_USB = false; ///< USB を利用しない場合「false」
static constexpr uint32_t BASE = 16'000'000; ///< 外部接続クリスタル
static constexpr uint32_t PLL_BASE = 200'000'000; ///< PLL ベースクロック
static constexpr uint32_t ICLK = 200'000'000; ///< ICLK 周波数
static constexpr uint32_t PCLKA = 100'000'000; ///< PCLKA 周波数
static constexpr uint32_t PCLKB = 50'000'000; ///< PCLKB 周波数
static constexpr uint32_t PCLKC = 200'000'000; ///< PCLKC 周波数
static constexpr uint32_t PCLKD = 50'000'000; ///< PCLKD 周波数
static constexpr uint32_t FCLK = 50'000'000; ///< FCLK 周波数
static constexpr uint32_t BCLK = 50'000'000; ///< BCLK 周波数
#endif
};
}
- 上記のように、実際に設定する周波数を、整数で定義してある。
- RX72T の場合、USB を使う場合と最大周波数で動作させる場合で、異なったプロファイルを使う。
- 水晶発振子の場合、8MHz~24MHzまでを使う。
- 細かい周波数の場合は多少注意を要する。
- この定数は、CMT、MTU、SCI など、タイミングデバイスで、周期を計算する場合に参照される。
system_io::boost_master_clock()
- 各クロックを切り替える関数では、設定出来ない場合にコンパイルを止める。
- 分周して余りが出る場合もコンパイルエラーとなる。
- PLL_BASE は、外部接続クリスタルなどから、PLL で、内部クロックを生成する。
- PLL_BASE は 0.5 倍単位で、10 倍から 30 倍まで生成出来る。
ベース周波数のチェック
static constexpr bool check_base_clock_() noexcept
{
bool ok = true;
if(OSC_TYPE_ == OSC_TYPE::XTAL) {
if(clock_profile::BASE < 8'000'000 || clock_profile::BASE > 24'000'000) ok = false;
} else if(OSC_TYPE_ == OSC_TYPE::EXT) {
#if defined(SIG_RX72N) || defined(SIG_RX72M)
if(clock_profile::BASE > 30'000'000) ok = false;
#else
if(clock_profile::BASE > 24'000'000) ok = false;
#endif
} else if(OSC_TYPE_ == OSC_TYPE::HOCO) { // 16MHz, 18MHz, 20MHz
if(clock_profile::BASE != 16'000'000 && clock_profile::BASE != 18'000'000 && clock_profile::BASE != 20'000'000) ok = false;
}
return ok;
}
...
static_assert(check_base_clock_(), "BASE out of range.");
- RX72N、RX72M では、外部入力クロック時に 30MHz までを許容している。
- それ以外は 24MHz
- 内臓高速オシレータは、16MHz、18MHz、20MHz を選択出来る。
PLL ベースクロック
- PLL ベースクロックは、BASE クロックを内部 PLL で0.5倍単位で逓倍する。
- 10 倍~30 倍の設定が可能
static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) >= 20, "PLL-base clock divider underflow.");
static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) <= 60, "PLL-base clock divider overflow.");
static_assert((clock_profile::PLL_BASE * 2 % clock_profile::BASE) == 0, "PLL-base clock can't divided.");
各モジュール分周器
- 各モジュール分周器は、1, 1/2,1/4,1/8,1/16,1/32,1/64 を設定出来る。
- RX72N、RX72M では、バスクロック(BCLK)だけ 1/3 を設定出来る。
static constexpr uint8_t clock_div_(uint32_t clk) noexcept
{
uint8_t div = 0;
while(clk < clock_profile::PLL_BASE) {
++div;
clk <<= 1;
}
if(div > 0b0110) div = 0b111;
return div;
}
static constexpr bool check_clock_div_(uint32_t clk) noexcept
{
auto div = clock_div_(clk);
if(div > 0b0110) {
return false; // overflow
}
if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
return false; // 割り切れない周期
}
return true;
}
static constexpr uint8_t clock_div_bus_(uint32_t clk) noexcept
{
#if defined(SIG_RX72N) || defined(SIG_RX72M)
if((clock_profile::PLL_BASE - (clk * 3)) < 3) { // 1/3 設定の検出
return 0b1001;
}
#endif
return clock_div_(clk);
}
static constexpr bool check_clock_div_bus_(uint32_t clk) noexcept
{
auto div = clock_div_bus_(clk);
if((div & 0b0111) > 0b0110) {
return false; // overflow
}
if(div == 0b1001) { // 1/3
return true;
} else {
if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
return false;
} else {
return true;
}
}
}
...
static_assert(check_clock_div_(clock_profile::FCLK), "FCLK can't divided.");
static_assert(check_clock_div_(clock_profile::ICLK), "ICLK can't divided.");
static_assert(check_clock_div_bus_(clock_profile::BCLK), "BCLK can't divided.");
static_assert(check_clock_div_(clock_profile::PCLKA), "PCLKA can't divided.");
static_assert(check_clock_div_(clock_profile::PCLKB), "PCLKB can't divided.");
static_assert(check_clock_div_(clock_profile::PCLKC), "PCLKC can't divided.");
static_assert(check_clock_div_(clock_profile::PCLKD), "PCLKD can't divided.");
USB クロック
static constexpr uint32_t usb_div_() noexcept
{
if(clock_profile::TURN_USB) {
if((clock_profile::PLL_BASE % 48'000'000) != 0) return 0; // 割り切れない場合
return (clock_profile::PLL_BASE / 48'000'000);
} else { // USB を使わない場合は、常に「2」(リセット時の値)を返す
return 0b0001 + 1;
}
}
...
{
static_assert(usb_div_() >= 2 && usb_div_() <= 5, "USB Clock can't divided.");
// 1/2, 1/3, 1/4, 1/5
device::SYSTEM::SCKCR2.UCK = usb_div_() - 1;
}
まとめ
- C++11 以降に追加された「static_assert」だが、今まで、何故か使っていなかったのか?(もったいない)
- この少しの改造で、clock_profile で、無効な設定を書いても、コンパイル時に検出して、止める事が可能となった。
- constexpr を使い、コンパイル時に計算や分岐も可能となるので、複雑な条件であってもコンパイル時に検出が可能となる。