STM32のI2Cはよくわからん

I2Cで1バイトのレジスタリードをするとき、通常は

START → スレーブアドレス(W)送信(W/ACK) → サブアドレス送信(W/ACK) → START(リスタート) → スレーブアドレス(R)送信(W/ACK) → レジスタデータ読み出し(withoutACK) → STOP

とするが、

START → スレーブアドレス(W)送信(W/ACK) → サブアドレス送信(W/ACK) → STOP → START → スレーブアドレス(R)送信(W/ACK) → レジスタデータ読み出し(withoutACK) → STOP

としないとデータが出てこないへんなデバイスがあった(デバイスメーカ自らこのようにしてくれと公言)

で、これを STM32の I2C で使おうとしたら、、、レジスタデータ読み出しまではできるようだが、そのあとなんかへんなシーケンスにはまってしまうみたいで、以後動かなくなってしまう。
(SDAがLのままになってしまい、それでI2Cモジュールは他からI2Cバスを占有されてると思ってしまうみたい?)

I2CモジュールをいったんDISABLEにしたり、SWRSTビットがあるのでそれでソフトウェアリセットしようとしてみても、できない。
しょうがないので、I2Cモジュールを使うのをあきらめて、ソフト駆動でI2Cして逃げるはめになってしまった・・・。

よくわからんなーSTM32のI2Cモジュールは。

STM32のI2CのSMBAL(SMBus Alert)

STM32のI2CモジュールのSMBAL(SMBus Alert)信号線は、CR1 SMBUS bitを1にしてSMBusモードにしたときだけ使われるのかと思っていたら、
どうも、CR1 SMBUS bit=0のI2Cモードでも、I2CをPEするとI2Cモジュール用に切り替えられてしまって、他の用途に使えないみたい?

それに気づかずSMBALの端子を別用途に使ってしまっていたので、しかたなくソフトでI2C駆動するハメになってしまった…

ARM用GCCと64bitINTと8バイト境界とFAULT

CoreSourcery G++ 4.5.1 (GCC 4.3.2ベース)で、64bit int値を使うとどんなコードが出るのか見てみた。CPUはSTM32(Cortex-M3)。

; volatile uint64_t a,b;

	push	{r4, r5, r6, lr}
	ldr	r6, .L5+8

; a = 0;
	movs	r0, #0
	movs	r1, #0
	strd	r0, [r6]

; a++;
	movs	r0, #1
	ldrd	r2, [r6]
	movs	r1, #0
	adds	r2, r2, r0
	adc	r3, r3, r1
	strd	r2, [r6]

; a = a + 2;
	movs	r4, #2
	ldrd	r2, [r6]
	movs	r5, #0
	adds	r2, r2, r4
	adc	r3, r3, r5
	strd	r2, [r6]

こんなふうにLDRD/STRD命令を使って、割とコンパクトなコードになってるし、なにより、ロード/ストアが atomic だ(だよね・・・? →後述。アトミックじゃなかった)。
(某社オリジナルコア32bitCPU用のGCCでは、64bit値使ったら、いちいちライブラリをコールしてやがったので、不安になって、チェックしたのです…)

しかし、LDRD/STRD命令は、8バイト境界でないところに使ってしまうと、ARMコアがFAULTするので注意が必要だ。

Cortex-M3(ARM v7-Mアーキテクチャ)は、奇数アドレスからの16bit/32bitデータアクセスが出来る(サイクルは余計にかかるが。なお、ARM7は出来ない)ので、油断していた・・・すこし、ハマった・・・。

注:アンアラインドデータアクセスは単一ロード/ストア命令(LDR/STR)でしか利用できない。コンパイラによっては単純なポインタアクセスでLDM/STM命令を使ってくれたりしてFAULTするので(KEIL…)、この機構に期待しない方が安全だ・・・。

・・・Cortex-M3でも、LDRD/STRD命令は、アトミックってことでいいんだろうか?

マニュアルには、LDM/STMは割り込み中断・再開できるのでレイテンシが改善してると書いてあるけど、LDRD/STRDは割り込まれないんだろうか?
・・・マニュアルに書いてあるのはLDM/STM, PUSH/POPについてだけだから、LDRD/STRDは大丈夫なんじゃないかと思うんだけど・・・。

→うーんでも http://www.yokogawa-digital.com/arm/support/faq/index.php?VER-B-09 に 「ARM v7M 及び v7EM のコアに於いて、LDRD/STRD 命令はシングルコピーの最小単位(atomic)ではありません」[SDCOMP-14812]って書かれててるなあ…アトミックじゃないのかなあ。

→Cortex-M3 テクニカルマニュアルの ETMの項に、 ETMCANCEL ポートの説明として、
現在実行されているオペコードがキャンセルされました。割り込みされたオペコードは、この実行コンテキストに復帰したときに再始動または続行されます。これには、次のオペコードが含まれます。
LDR/STR
LDRD/STRD
LDM/STM
U/SMULL
MLA
U/SDIV
MSR
CPSID
とあるな…。じゃあ、LDRD/STRDもアトミックじゃないのか…。がっかり。

USBデバイス作成時はMicrosoft OS ディスクリプタに注意

人に指摘してもらって気がついたのだが、Windows XP SP1以降のWindowsは、USBデバイスから、Microsoft OS Descriptor なるものを取得しようとするらしい。

USB に関する FAQ: 中級レベル (microsoft.com)
Microsoft OS ディスクリプター (microsoft.com)

具体的には Windowsは index 0xEE で String Descriptor を取得しようとしてくる。

そんなindexのStringDescriptorは持ってません、と無視してしまえば問題はないのだが、たとえば、STM32の、最初期(2007年頃)のUSBサンプルコードでは、

こんな String_Descriptor配列を

ONE_DESCRIPTOR String_Descriptor[4] =
  {
    {(u8*)Joystick_StringLangID, JOYSTICK_SIZ_STRING_LANGID},
    {(u8*)Joystick_StringVendor, JOYSTICK_SIZ_STRING_VENDOR},
    {(u8*)Joystick_StringProduct, JOYSTICK_SIZ_STRING_PRODUCT},
    {(u8*)Joystick_StringSerial, JOYSTICK_SIZ_STRING_SERIAL}
  };
u8 *Joystick_GetStringDescriptor(u16 Length)
{
  u8 wValue0 = pInformation->USBwValue0;
  return Standard_GetDescriptorData(Length, &String_Descriptor[wValue0]);
}

こうやってindexノーチェックで引いて返信していた。

危ないのでindexはきちんとチェックしましょう・・・。

STM32 ライブラリ xxx_StructInit() は必ず行う

STM32のライブラリでSPIを使ったが、どうも動作が不安定なので悩んでいた。

初期化は

SPI_InitTypeDef SPI_InitStructure;

// 2線・全二重モード
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

// スレーブモード
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
// 8bitモード
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
// クロック極性: 通常時L
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
// クロックフェーズ: 1エッジ
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
// SPI_NSS_Hard: NSSピンはマスターからのCSとして使用しそれにより自動イネーブル
// SPI_NSS_Soft: 手動(SPI_CR1->SSIビットをNSSとして使用)
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
// MSBから送受信
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
// CRCは使用しないがデフォルトの多項式 7 を設定
SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

こんなコード。SPI_InitTypeDef 構造体メンバ中、 SPI_BaudRatePrescaler のみ、なにも設定していないが、スレーブモードなので、設定しなくていいと思っていた。

でも、SPI_Init() のソースを見ると、構造体メンバの値を単純にorしたものを、SPI_CR1レジスタに設定していた。

tmpreg |= (uint16_t)((uint32_t)SPI_InitStruct->SPI_Direction | SPI_InitStruct->SPI_Mode |
SPI_InitStruct->SPI_DataSize | SPI_InitStruct->SPI_CPOL |
SPI_InitStruct->SPI_CPHA | SPI_InitStruct->SPI_NSS |
SPI_InitStruct->SPI_BaudRatePrescaler | SPI_InitStruct->SPI_FirstBit);
/* Write to SPIx CR1 */
SPIx->CR1 = tmpreg;

構造体メンバに1つでも未初期化のものがあると、めちゃくちゃな値がCR1レジスタ全体に設定されてしまうのだった…それで不安定だったのかorz。

debug時はassert_param()でパラメータチェックが入るからいいのだが、ズボラにズボラを重ねていきなりreleaseで作っていると当然ノーチェック。

構造体メンバはまず

SPI_StructInit(&SPI_InitStructure);

しておけば、必要な構造体メンバの初期化がされるので、必ず行うべきなのだった…。