USB-CDC と R8A66597 取り扱いマニュアル(初心者向け)
対象: SH-3 コア + R8A66597 互換 USB IP を内蔵した組込み機器(ビッグエンディアン)
0. このマニュアルのゴール
PC とデバイスを USB 仮想シリアルポート (CDC-ACM) でつなぎ、文字をやり取りできるようにする。
そのために必要な「USB の基礎」と「R8A66597 という USB コントローラの叩き方」を順に説明します。
code:_
(COMポートに見える) (USB SIE) (このソフト)
1. 用語ミニ辞典(最初に読む)
ホスト: USB を仕切る側 = PC。トークンを出して通信を開始する
ファンクション/デバイス: 仕切られる側 = 今回の対象機器
SIE: Serial Interface Engine。USB の電気・プロトコル処理を自動でやるハード。R8A66597 の中身
エンドポイント (EP): 通信の出入口。番号で区別。EP0 は必ず存在する「制御用」
パイプ: R8A66597 用語で「EP に紐づくバッファ+設定」。EP1〜EP9 を PIPE1〜9 で扱う
ディスクリプタ: デバイスの自己紹介データ(構造体)。PC が列挙時に読み取る
列挙 (Enumeration): 接続直後に PC がデバイス情報を読み、アドレスや構成を決める一連の流れ
CDC-ACM: Communication Device Class / Abstract Control Model。USB を仮想COMポートに見せるクラス
トークン: ホストが出す「これから IN(読む)/OUT(書く)/SETUP するよ」の合図
USB の3種類のやり取り(今回使うもの)
コントロール転送 (EP0): 列挙やクラス要求。SETUP→DATA→STATUS の3段階。
バルク転送 (EP1/EP2): CDC のデータ本体(PC↔デバイスの文字列)。大量・確実。
インタラプト転送 (EP3): CDC の通知用(今回はディスクリプタ上だけ存在。実使用は最小)。
2. R8A66597 とは / このSoCでの位置づけ
R8A66597 はルネサスの USB2.0 Hi-Speed 対応 ホスト/ファンクション コントローラ IP。
本機はこの IP を 0xA4480000 に統合したカスタム SoC。
全レジスタ 16ビットアクセス(例外: PLLCR のみ32ビット)
ベースアドレス USBF_BASE = 0xA4480000
CPU は ビッグエンディアン → これが後述の BIGEND ビットで効いてくる
⚠️ 注意: この USBF は SH-3 純正の USBF (0xA4000240) ではなく R8A66597 互換 IP。
よってルネサスの R8A66597 データシート / サンプルコードがそのまま参考になる。
3. 全体の流れ(これさえ掴めばOK)
code:_
電源ON
│
├─ (A) クロック/PLL を立ち上げる … SIEを動かす土台
├─ (B) SYSCFG0 で USB有効・HS有効化 … USBコア起動
├─ (C) EP0(DCP) を初期化 … 制御転送の準備
├─ (D) D+ プルアップ ON (DPRPU) … 「つなぎました」とPCに通知 ← ここで列挙開始
│
└─ ループ:
├─ バスリセット検出 (DVST) … PCがリセットを掛ける/HS判定
├─ SETUP受信 (VALID) → SETUP処理 … ディスクリプタ要求等に応答
├─ ステージ変化 (CTRT) → データ/ステータス処理
└─ バルクEP(PIPE1/2)で文字列を送受信
起動処理がこの (A)〜(D)、メインループが SETUP/ステージ変化の処理を担います。
4. R8A66597 レジスタ全マップ(ベース USBF_BASE=0xA4480000, 全16bit。PLLCRのみ32bit)
code:c
SYSCFG0 @ 0x00 // USB全体設定
// bit0 USBE =0x0001 : USB有効
// bit4 DPRPU =0x0010 : D+プルアップ(接続通知) ← 最後にONで列挙開始
// bit7 HSE =0x0080 : Hi-Speed有効
// bit10 SCKE =0x0400 : クロック供給有効
SYSCFG1 @ 0x02 // bit8 LPSME=0x0100(低消費) / bit12 CNTFLG=0x1000(遷移中,0まで待つ)
DVSTCTR0 @ 0x08 // bit4 UACT=0x0010(USB動作有効,INに応答するため必須)
// bits2:0 RHST=現在速度 (3=HS,2=FS) CFIFO @ 0x14 // EP0データ読み書きポート(16bit)
CFIFOSEL @ 0x20 // bit5 ISEL =0x0020 : FIFO方向(1=IN書込) ★
// bit8 BIGEND=0x0100 : データポートのバイト順(1=BE) ★
// bit10 MBW =0x0400 : バス幅(1=16bit) ★
// bit15 RCNT =0x8000 : Read Count Mode(書込時0に保つ)
CFIFOCTR @ 0x22 // bit13 FRDY=0x2000(PID=NAK+BCLR後に1) / bit14 BCLR=0x4000 / bit15 BVAL=0x8000
INTSTS0 @ 0x40 // bit3 VALID=0x0008(SETUP有効) / bit11 CTRT=0x0800(ステージ変化)
// bit12 DVST=0x1000(状態変化) / bits6:4 DVSQ(R/O,0x10=Default) // bits2:0 CTSQ(R/O,制御ステージ) ※書込はwrite-0-to-clear USBREQ @ 0x54 // SETUP bmRequestType(下位)+bRequest(上位)
USBVAL @ 0x56 // SETUP wValue
USBINDX @ 0x58 // SETUP wIndex
USBLENG @ 0x5A // SETUP wLength
DCPCFG @ 0x5C // bit4 DIR=0x0010 : 方向(1=IN送信 / 0=OUT受信)
DCPMAXP @ 0x5E // EP0最大パケット = 64
DCPCTR @ 0x60 // PID: 0x0000=NAK / 0x0001=BUF / 0x0002=STALL
// bit2 CCPL=0x0004(STATUS ZLP完了)
// bit7 SQSET=0x0080(→DATA1, PID_BUFと同時書き0x0081必須)
// bit8 SQCLR=0x0100(→DATA0)
PIPESEL @ 0x64 // 操作対象パイプ選択
PIPECFG @ 0x68 // パイプ設定(種別/方向/EP番号)
PIPEBUF @ 0x6A // パイプ バッファ割当
PIPEMAXP @ 0x6C // パイプ最大パケット
PIPE1CTR @ 0x70 // PIPE1制御(EP1 OUT, PC→Dev受信)
PIPE2CTR @ 0x72 // PIPE2制御(EP2 IN, Dev→PC送信)
PLLCR @ 0x100 // PLL制御 (★32bitアクセス)
// バルクパイプ設定例:
// PIPE1(EP1 OUT): PIPECFG=0x4001, PIPEBUF=0x1C04
// PIPE2(EP2 IN ): PIPECFG=0x4012, PIPEBUF=0x1C0C
// PIPEMAXP = HSなら512 / FSなら64
5. 初期化シーケンス(main() 解説)
(A) クロック/PHY/PLL
code:c
SYSCFG0 = 0x0000u; // 一旦クリア
PLLCR |= 4u; delay; PLLCR &= ~4u; // PLL初期化ストローブ(bit2パルス)
PLLCR = 6u; PLLCR = 4u; // PLL段階起動 (ROM FUN_80012AA4と同手順)
PLLCR = 0x24u; PLLCR = 0x20u;
PLL は「USB の 480MHz/48MHz クロックを作る」心臓部。本体ファームウェアの起動手順を移植したもの。順番と待ち時間が重要。
(B) USB コア有効化
code:c
SYSCFG1 = 0x0001u;
SYSCFG0 |= SYSCFG0_SCKE; // クロック供給ON (bit10)
SYSCFG0 |= SYSCFG0_USBE; // USB有効 (bit0)
SYSCFG0 &= 0xA78Fu; // 余分なビット整理
SYSCFG0 |= SYSCFG0_HSE; // Hi-Speed有効 (bit7)
PLLCR |= 8u; // PLL最終有効化(bit3)
(C) EP0 (DCP) 準備
code:c
DCPCFG = 0x0000u; // 方向OUTで初期化
DCPMAXP = EP0_MPS; // = 64 (HS規格でEP0は64必須)
DCPCTR = DCPCTR_SQSET; // シーケンスビットをDATA1に
cfifo_sel_in(); // FIFOをIN方向へ
CFIFOCTR = CFIFOCTR_BCLR; // バッファクリア
(D) 接続通知 → 列挙開始
code:c
INTSTS0 = INTSTS0_CLR(INTSTS0_DVST|INTSTS0_CTRT|INTSTS0_VALID); // 割込みクリア
SYSCFG0 |= SYSCFG0_DPRPU; // D+プルアップON ← PCが「挿された」と認識し列挙開始
💡 DPRPU が「USBケーブルを挿した」スイッチ。ここを最後にONにすることで、準備完了後に列挙を始められる。
6. コントロール転送の3ステージ(EP0)
PC が「デバイス情報ちょうだい」と要求するのがコントロール転送。3段階で進みます。
code:_
SETUP PC→Dev 8バイトの要求(GET_DESCRIPTOR等) … INTSTS0.VALID で検知 ↓
DATA 方向はSETUP内容次第。ディスクリプタ等を分割送信 … ep0_in_pump() ↓
STATUS 0バイト(ZLP)で完了確認 … ep0_zlp() SETUP の読み取り
code:c
uint16_t req=USBREQ, val=USBVAL, len=USBLENG;
uint8_t bmType=(uint8_t)(req&0xFF), bReq=(uint8_t)(req>>8);
bmType==0x80 && bReq==0x06 = GET_DESCRIPTOR。wValueの上位で種類を判別:
1=Device, 2/7=Configuration, 3=String, 6=Device Qualifier
bmType==0x00 && bReq==0x05 = SET_ADDRESS (R8A66597はSIEが自動処理 → ZLPだけ返す)
bmType==0x00 && bReq==0x09 = SET_CONFIGURATION
bmType==0x21 && bReq==0x22 = SET_CONTROL_LINE_STATE (CDC: ターミナル接続)
bmType==0x21 && bReq==0x20 = SET_LINE_CODING (CDC: ボーレート等)
データ送信のキモ (ep0_in_pump)
1. cfifo_sel_in() で FIFO を IN(書き込み)方向に
2. DCPCTR = PID_NAK で一旦止める
3. CFIFOCTR = BCLR → FRDY=1 を待つ
4. CFIFO へデータをバイト書き込み
5. CFIFOCTR = BVAL (バッファ確定) → DCPCTR |= PID_BUF (送信許可)
6. 64バイト超は分割して繰り返し
7. ⚠️ ハマりどころ 6選(実機で苦労した点)
実装で実際に踏んだ罠。ここが本マニュアルで一番価値のある部分。
(1) CFIFOSEL の ISEL/MBW ビット位置
code:c
#define CFIFOSEL_ISEL 0x0020u // bit5 = FIFO方向(1:IN書き込み) ← 正 #define CFIFOSEL_MBW 0x0400u // bit10 = メモリバス幅(1:16bit) ← 正 旧定義 ISEL=0x0400 / MBW=0x0100 は誤りで、FRDY が永遠に 0 になる原因だった。
ルネサス提供の R8A66597 サンプルコードのビット定義で正しい値を確認。
教訓: 互換 IP はメーカ提供のビット定義ヘッダを正とせよ。
(2) BIGEND ビット(エンディアン)
このSoCはビッグエンディアン。FIFO データポートのバイト順を CFIFOSEL.BIGEND(bit8) で合わせる。
BIGEND=0 → D7:0先頭(リトル向け) / BIGEND=1 → D15:8先頭(ビッグ向け) 本FWは BIGEND=1 を使用。BIGEND=0 だと dev_desc のバイト順が逆転する。
ただし通常の16bitレジスタ(USBREQ等)は外部ENDIAN端子でビッグ固定 → そのまま正しく読める。
影響を受けるのは FIFO データポートだけ。
(3) UACT を立てないと IN に応答しない
code:c
#define DVSTCTR0_UACT 0x0010u // bit4 リセット後デフォルト0。SETUP は受信できるが、IN トークンへの応答には UACT=1 が必須。
「SETUP は来るのにデータが返らない」ときはここを疑う。
(4) DCPCTR の SQSET 同時書き込み
code:c
// ✗ DCPCTR = PID_BUF; // bit6(SQMON)を0で上書きしてしまう(SoC固有)
// ○ DCPCTR = SQSET | PID_BUF; // = 0x0081 で同時発行
シーケンスビット(DATA0/1トグル)が壊れるとデータが化ける/再送ループになる。
(5) CFIFOCTR の操作順序
データシート §2.8.16-18:
BVAL は FRDY=1 のときだけ有効
BCLR は PID=NAK にしてから発行
FRDY は PID=NAK + BCLR の後に 1 になる
順番を守らないと FIFO が固まる。
(6) INTSTS0 は write-0-to-clear
code:c
#define INTSTS0_CLR(bits) ((uint16_t)(~(uint16_t)(bits))) // クリアしたいビットを0で書く INTSTS0 = INTSTS0_CLR(INTSTS0_CTRT); // CTRTだけクリア、他は1で保持
1 を書いてもクリアされない。VBSTS/DVSQ/CTSQ は読み取り専用。
8. CDC ディスクリプタの構造
CDC-ACM は「制御IF(EP3 interrupt) + データIF(EP1 OUT / EP2 IN bulk)」の2インターフェイス構成。
code:_
Device記述子 (bDeviceClass=0xEF Misc, IADで束ねる)
└ Configuration記述子 (wTotalLength=75)
├ IAD (Interface Association: IF0+IF1をCDC機能として束ねる)
├ IF0: 通信制御 (Class=0x02 CDC, SubClass=0x02 ACM)
│ ├ Header機能記述子 (CDC v1.10)
│ ├ Call Management機能記述子
│ ├ ACM機能記述子 (ライン制御サポート)
│ ├ Union機能記述子 (Master=IF0, Slave=IF1)
│ └ EP3 IN (interrupt, 8B, 通知用)
└ IF1: データ (Class=0x0A CDC-Data)
├ EP2 IN (bulk, 512B HS) … デバイス→PC
└ EP1 OUT (bulk, 512B HS) … PC→デバイス
PC は IAD と Union 記述子を見て「IF0 と IF1 が1つの仮想COMポート」と理解する。
CDC-ACM はこの定型構造を守れば Windows/Linux 標準ドライバが自動で COM ポートを作る。
9. バルク転送(CDC データ本体)
列挙が終わったら、文字列のやり取りは EP1(OUT)/EP2(IN) のバルクパイプで行う。
パイプ設定
code:c
PIPESEL = 1u; // PIPE1 を選択 (EP1 OUT = PC→Dev受信)
PIPECFG = 0x4001u; // bulk / OUT / EPnum=1
PIPEBUF = 0x1C04u; // バッファ割り当て
PIPEMAXP = (HS?512:64); // 最大パケット
PIPE1CTR = SQCLR; PIPE1CTR = PID_BUF; // 受信許可
PIPESEL = 2u; // PIPE2 (EP2 IN = Dev→PC送信)
PIPECFG = 0x4012u; // bulk / IN / EPnum=2
PIPEBUF = 0x1C0Cu;
PIPEMAXP = (HS?512:64);
PIPE2CTR = SQCLR; PIPE2CTR = PID_NAK; // 送信は要求時にBUFへ
送受信の考え方
受信(PC→Dev): PIPE1 を BUF にしておき、FRDY が立ったら CFIFO(PIPE1選択)から読む → line_rx() へ。
送信(Dev→PC): 送るデータを CFIFO(PIPE2選択)へ書き、BVAL→PID_BUF。out_flush() がこれ。
HS(Hi-Speed) ならバルク最大 512B、FS なら 64B。g_hs_mode で切替(RHST=DVSTCTR02:0==3 が HS)。 10. まとめ・チェックリスト
新しく USB-CDC を立ち上げるとき:
PLL → SYSCFG0(SCKE/USBE/HSE) の順でコア起動できているか
EP0: DCPMAXP=64, SQSET 同時書き込みしているか
最後に DPRPU=1 で接続通知しているか
CFIFOSEL の ISEL=0x20 / MBW=0x400 / BIGEND を正しく設定したか
UACT=1 にして IN へ応答できる状態か
INTSTS0 は write-0-to-clear で操作しているか
ディスクリプタは IAD+Union を含む CDC-ACM 定型か
バルクパイプ PIPE1(OUT)/PIPE2(IN) を設定したか
困ったら: ルネサスの R8A66597 データシート と R8A66597 向けサンプルコード(公式公開)を正典として参照する。
互換 IP なので、これらのビット定義・手順がそのまま使える。
11. 電源・GPIO・クロック レジスタ(USB電源 + 共通基盤)
⚠️ 以下の GPIO/電源/クロックの多くは 本機固有で公式データシートに無い。
本体ファームウェア(ROM)の逆アセンブル解析が唯一の根拠。
11.1 クロック / CPG(USB-PHY 関連)
code:c
// CPG拡張ブロック 0xA40A0000 (本機独自)
0xA40A0004 (8bit) CPG_PHYCTL // bit5(0x20)=USB-PHY enable / bit1(0x02)=SDHIクロック(→SD編)
0xA40A0008 (16bit) CPG_PHYCONN // USB-PHY接続設定値 = 0xA5E0 を書く
0xFFFFFEC0 (8bit) PHY_CLK_STAT // bit0 がトグル=PHYクロック安定の指標(14回トグル待つ)
0xFFFFFF80 (16bit) FRQCR // bit1(0x0002): USBクロック分周。初期化中に一時クリア
// USBF内のPLL: PLLCR @ USBF_BASE+0x100 (32bit) … §5(B)参照
11.2 GPIO ポート(SH-3 系 物理アドレス)
code:c
// 制御レジスタ PxCR (16bit, 1ピン2bit=機能/方向選択)。通常ROMが起動時設定済み
PACR 0xA4000100 PBCR 0xA4000102 PCCR 0xA4000104 PDCR 0xA4000106
PECR 0xA4000108 PFCR 0xA400010A PGCR 0xA400010C PHCR 0xA400010E
PJCR 0xA4000110 PKCR 0xA4000112 PLCR 0xA4000114 SCPCR 0xA4000116 PMCR 0xA4000118
// データレジスタ PxDR (8bit, 1ピン1bit) ★実際の読み書きはこちら
PADR 0xA4000120 PBDR 0xA4000122 PCDR 0xA4000124 PDDR 0xA4000126
PEDR 0xA4000128 PFDR 0xA400012A PGDR 0xA400012C PHDR 0xA400012E
PJDR 0xA4000130 PKDR 0xA4000132 PLDR 0xA4000134 SCPDR 0xA4000136 PMDR 0xA4000138
PNDR 0xA400013A PODR 0xA400013C PPDR 0xA400013E PS0DR..PS7DR 0xA4000140..014E
// GPIO操作 = read-modify-write
PGDR |= (1u<<n); // bit n = HIGH
PGDR &= ~(1u<<n); // bit n = LOW
11.3 USB 電源 / バックライト ピン
code:c
PG4 = PGDR bit4 (0xA400012C, 0x10) // USB電源 enable (正論理: 1=ON) #define PG4_USB_PWR 0x10 PF4 = PFDR bit4 (0xA400012A, 0x10) // USB電源シーケンス補助
PG7 = PGDR bit7 (0xA400012C, 0x80) // LCDバックライト (正論理: 1=点灯) #define PG7_BACKLIGHT 0x80 0xA4080000 (16bit) // システム/バス設定(D+プルアップ前準備, bits7:4) 11.4 USB 電源 投入シーケンス(本体ファームウェアの電源ルーチン準拠)
code:c
// 1) システム設定整理
*(volatile uint16_t*)0xA4080000UL &= 0xFF0Fu; // bits7:4クリア // 2) USB電源ON: PF4=0 → PG4=1
PFDR &= (uint8_t)~0x10u; // PF4=0
PGDR |= 0x10u; // PG4=1 = USB電源ON
// 3) USB-PHY初期化
*(volatile uint8_t *)0xA40A0004UL &= (uint8_t)~0x20u; // PHY enable一旦クリア
*(volatile uint16_t*)0xA40A0008UL = 0xA5E0u; // PHY接続設定
*(volatile uint16_t*)0xFFFFFF80UL &= (uint16_t)~0x0002u; // FRQCR bit1クリア
delay_loops(10000u);
*(volatile uint8_t *)0xA40A0004UL |= 0x20u; // PHY enable
// 4) PHYクロック安定待ち: 0xFFFFFEC0 bit0 が14回トグルするまで
{ uint8_t prev=*(volatile uint8_t*)0xFFFFFEC0UL&1u; int n=0;
while(n<14){ uint8_t c=*(volatile uint8_t*)0xFFFFFEC0UL&1u; if(c!=prev){n++;prev=c;} } }
// 5) USBコア起動(PLL→SYSCFG0) … §5(A)(B)
// 6) D+プルアップ前準備 → 接続通知
*(volatile uint16_t*)0xA4080000UL =
(*(volatile uint16_t*)0xA4080000UL & 0xFF0Fu) | 0x00D0u;
SYSCFG0 |= SYSCFG0_DPRPU; // ここで列挙開始
11.5 USB 電源 切断(逆順)
code:c
SYSCFG0 &= ~SYSCFG0_DPRPU; // D+プルアップOFF(切断通知)
*(volatile uint8_t*)0xA40A0004UL &= ~0x20u; // PHY OFF
PGDR &= (uint8_t)~0x10u; // PG4=0 = USB電源OFF
11.6 操作パターン(USB編・落とし穴回避)
code:c
// (A) INTSTS0 は write-0-to-clear
INTSTS0 = (uint16_t)~CLEAR_BITS; // クリアするビットを0, 他を1
// (B) DCPCTR の SQSET は PID_BUF と同時書き
DCPCTR = 0x0080u | 0x0001u; // SQSET|PID_BUF = 0x0081
// (C) CFIFOCTR 順序: PID=NAK → BCLR → FRDY待ち → CFIFO書込 → BVAL → PID_BUF
// (D) FIFOデータポートのみ BIGEND(CFIFOSEL bit8)でバイト順制御。
// 通常16bitレジスタ(USBREQ等)は外部端子でBE固定 → そのまま読める。
// (E) WDTキー書込(ROMの較正遅延): WTCNT(0xFFFFFF84)=0x5A00|val / WTCSR(0xFFFFFF86)=0xA500|val
// 本実装はビジーループ(約16Mloop/秒)で代用している
11.7 確定度メモ
実機動作で確定: USB列挙一式, DPRPU, PHY enable(0xA40A0004 bit5), UACT, STS2/INTSTS0ビット。
本体ファームウェア解析で確認(高信頼): USB電源シーケンス(PG4/PF4/0xA5E0/FRQCR), 電源ルーチン。
要実機確認(SH-3系からの推定): 一部ポートの PxCR 機能割当, スタンバイ/拡張クロック制御等(本書では深入りしない)。
本マニュアルは実機検証と本体ファームウェアの解析に基づく。