STM32F4シリーズを使ってみる13 - FatFsとSDカード再考その2(SDIO基本編) -

※今回の記事の内容は時勢/判明した事実に即して逐次変更していきます。
 この記事ではDMAでよくある間違いをやらかしています。
 次回の記事も必ず参照してください!


STM32にはSDIOというSD/MMCカードと高速でデータをやり取りができる機能が
あります。今回は前回触れたハードウエアの接続の内容を踏まえ、ソフト的な話を
展開していきます。さらにSTM32F4に限らず他品種のSDIO相当の機能を
使用できるマイコンについても考察を重ねていきます。


●STM32のSDIOでFatFsる
SDカードSDIOは簡易な解説がSDアソシエーションのサイトで
公開されております。この"簡易な解説"が曲者で本当に知りたい細かい部分の事柄を
知るためには会費を払って会員になる必要があります。そんなわけで
ねむいさんみたいな金も地位も技術もないホビイストにとってはMCUベンダから
提供されたSDIOモジュールアクセス用のコードさんぷるをありがたく
頂いて自分のプロジェクトに適した形に組み込んでいくことになります。

話は少しFatFsのほうに戻りますが、FatFsとMCU固有コードを結合させる
ためにはFatFs自身が必要とする下部レイヤと呼ばれる関数群をこさえて
やる必要があります。
このうちdisk_ioctl(),fat_gettime(),disk_write()はFatFsのオプション設定で
無効状態にできるのでdisk_initialize(),disk_status(),disk_read()の3つの関数は
最低限作りこんであげないといけません。

かつてSTマイクロより提供されていたSTM32F4xx_DSP_StdPeriph_Libには
FatFsと親和性が良いSDIOのSD/MMCカードW/Rサンプルライブラリがあり、
私はこれをベースにSDIOドライバを実装していきました(現在配布されている
STM32F4Cube通称"HALライブラリ"のFatFsドライバもこのサンプルライブラリと
ほぼ同じ実装です)。
私のSDIOドライバはリソース使用状況に応じてDMAとFIFOのポーリングと
両方使い分けることが可能です。以下に実際に私のSDIOドライバで使用している
要点、特に初期化時のSDIO_CK(=実際にSDカードに掛かるクロック)の設定を
重点的にかいつまんで解説していきます。
以下おきぱのSTM32F4向けのFatFs実装例内にあるsdio_stm32f4.c,sdio_stm32f4.h
を見ながら読み進んでください。


SD_Error SD_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
SD_Error errorstatus = SD_OK;

/* SDIO Peripheral Low Level Init */
/* GPIOC and GPIOD Periph clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | SD_DETECT_GPIO_CLK, ENABLE);

GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);

/* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/* Configure PD.02 CMD line */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Configure PC.12 pin: CLK pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/*!< Configure SD_SPI_DETECT_PIN pin: SD Card detect pin */
#ifdef SDIO_INS_DETECT
GPIO_InitStructure.GPIO_Pin = SD_DETECT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(SD_DETECT_GPIO_PORT, &GPIO_InitStructure);
#endif

/*
*NVIC_PriorityGroup_0: 0 Pre-emption priorities, 16 Sub-priorities
*NVIC_PriorityGroup_1: 2 Pre-emption priorities, 8 Sub-priorities
*NVIC_PriorityGroup_2: 4 Pre-emption priorities, 4 Sub-priorities
*NVIC_PriorityGroup_3: 8 Pre-emption priorities, 2 Sub-priorities
*NVIC_PriorityGroup_4: 16 Pre-emption priorities, 0 Sub-priorities
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

/* Enable the SDIO Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);

/* Enable the SDIO APB2 Clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);

#if defined(SD_DMA_MODE)
/* Enable the DMA Clock */
RCC_AHB1PeriphClockCmd(SD_SDIO_DMA_CLK, ENABLE);
/* Initialize SDDMA Structure */
SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL;
SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
SDDMA_InitStructure.DMA_Memory0BaseAddr = 0;
SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
SDDMA_InitStructure.DMA_BufferSize = 0;
SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
#endif
/* End of LowLevel Init */


SDIO_DeInit();

errorstatus = SD_PowerON();

if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}

errorstatus = SD_InitializeCards();

if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}

/*!< Configure the SDIO peripheral */
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
/*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);

/*----------------- Read CSD/CID MSD registers ------------------*/
if (errorstatus == SD_OK)
{
errorstatus = SD_GetCardInfo(&SDCardInfo);
}

/*----------------- Select Card --------------------------------*/
if (errorstatus == SD_OK)
{
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
}

/*----------------- Enable SDC 4BitMode --------------------------------*/
if (errorstatus == SD_OK)
{
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
}

#ifdef SD_HS_MODE
/*----------------- Enable HighSpeedMode --------------------------------*/
#warning "Enable SD High Speed mode!"
if (errorstatus == SD_OK)
{
errorstatus = SD_HighSpeed();
}
#endif

return(errorstatus);
}

最初の要となるSD_Init内の関数です。DMAを使用するモードでもFIFOポーリング
でも割り込みは使用します。CLK以外のGPIOは一応プルアップしていますが
これに頼らず必ず外付け抵抗でプルアップをしてください(前回参照)。
DMAモードの場合はここでDMA用の構造体の設定も完了させておきます。
構造体変数はグローバル化してDMAをキックする際に最低限の変数の設定に
とどめるようにして少しでもオーバーヘッドを減らすようにしております。

また、構造体変数の保持用にCCM領域を積極的に使用するようにして速度向上に
努めています。ここで注意点ですがCCM領域に置いた変数はdata領域のように
スタートアップで初期化しておくようにはしていないのでプログラム内で
明示的に初期化するようにしましょう。

その後はSTマイクロ提供のサンプルのとおりにSDカード初期化のための関数を
実行していきます。デフォルトでは先ずSDIOCLKから作られるSDIO_CKを
400kHz以下、データ幅は1-bitモードでカードの初期化を実行し、成功が
返ってきたらクロックをSDNomalModeがサポートする上限付近まで上げて
データ幅も4-bitに拡張するコマンドを発行して高速なデータのやり取りの
準備を行います。

現在流通しているSDカードはほぼすべての品種でSDNomalModeの最高値25MHzを
サポートしていますので上げる場合はその周波数近辺まで一気にあげられます。
このときのクロックですが、STM32F4ではUSB,RNG,そしてSDIO用の固定値
クロック生成用のPLLが存在して柔軟に対応可能となっています。
F4系で一般的な168MHz動作の時はUSBで必須の48MHzが生成され、同じくこれは
SDIOでも都合よい値となっています。

そしてSDNomalModeの時は規格上SDIO_CKの値は25MHzが上限値なので上記の
コードのようにSDIOモジュール内にあるクロックディバイダを通して48MHzを
2分周した24MHzを生成するような設定にします。過去に私はこの時点で
クロックディバイダを通さず48MHzをモロに引き出して無理やり動作させて
おりましたがこのやり方は完全に誤りで本当はSDカードの規格に存在する
SDHighSpeedModeに切り替えたうえでクロックを48MHzに上げてやる必要が
あることを知りました。

SD_Error SD_HighSpeed (void)
{
SD_Error errorstatus = SD_OK;
uint32_t scr[2] = {0, 0};
uint32_t SD_SPEC = 0 ;
uint8_t hs[64] = {0} ;
uint32_t count = 0, *tempbuff = (uint32_t *)hs;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0;

SDIO->DCTRL = 0x0;

/*!< Get SCR Register */
errorstatus = FindSCR(RCA, scr);

if (errorstatus != SD_OK)
{
return(errorstatus);
}

/* Test the Version supported by the card*/
SD_SPEC = (scr[1] & 0x01000000)||(scr[1] & 0x02000000);

if (SD_SPEC != SD_ALLZERO)
{
/* Set Block Size for Card */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)64;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = 64;
SDIO_DataInitStructure.SDIO_DataBlockSize = SDIO_DataBlockSize_64b ;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);

/*!< Send CMD6 switch mode */
SDIO_CmdInitStructure.SDIO_Argument = 0x80FFFF01;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_HS_SWITCH;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_HS_SWITCH);

if (errorstatus != SD_OK)
{
return(errorstatus);
}
while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
{
for (count = 0; count < 8; count++)
{
*(tempbuff + count) = SDIO_ReadData();
}
tempbuff += 8;
}
}

if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
errorstatus = SD_RX_OVERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
count = SD_DATATIMEOUT;
while ((SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) && (count > 0))
{
*tempbuff = SDIO_ReadData();
tempbuff++;
count--;
}

/*!< Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS);

/* Test if the switch mode HS is ok */
if ((hs[13]& 0x2)==0x2)
{
/*!< Configure the SDIO peripheral */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
#if defined (STM32F40_41xxx)
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Falling; /* This is a baddest work around for STM32F40x and STM32F41x */
#else
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
#endif
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Enable; /* Set Direct Clock(48MHz) */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_4b;
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
errorstatus=SD_OK; /* Enter SDHighSpeedMode */
}
else
{
errorstatus=SD_OK; /* Still SDNomalMode */
/*errorstatus=SD_UNSUPPORTED_FEATURE ;*/
}
}
return(errorstatus);
}

STマイクロのサンプルにはどこからも参照されていないSD_HighSpeed()なる
関数がとあるバージョンから唐突に追加されてフルの規格を入手しないと
わからなかったSDHighSpeedModeへの切り替え方が判明しております。
そんなわけでこの関数を利用して正規にSDIOのクロックを48MHzでドライブ
したい と こ ろ で す が !


残念ながらSTM32F405/7系のSDIOモジュールには上記のエラッタが存在し、
上記の関数を実行してもハードウエア的に48MHzで安定動作せしめることは
できません。

実際にRev.AのSTM32F407が乗った紅牛板で実験してみたのですがクロック
ディバイダを無効にして48MHz直結の場合は動作しないカードが殆どで動作する
カードは極めて限定的なものでした。しかもreadはかろうじて成功するものの
writeは殆ど失敗してディレクトリまで破壊されることが判明したので一時は
お蔵入りにしていました(このエラッタが明記されたのは2012年の秋以降で、
2011年当時はまだ判明していませんでした)。

てわけでワークアラウンドを講じるとSTM32F405/7系では動作が保証された
クロック上限値は37.5MHzとなり、しかもPLLの分周値を変えてこの周波数に
設定すると今度はUSBで必要な48MHzが作られなくなってしまうので利用が
限定的になってしまいます。
したがって潰しが効くようにコーディングするとSDNomalModeのまま、
SDIO_CKも24MHzのままで使用するべしとなってしまうのです。一応STM32F2/F4
にはGPIOの高速動作を安定化させる"compensation cell"なるものがあり
(こちらのテクニカルノートの75pに解説があります)、有効にしてみたのですが
改善はありませんでした。

一方STM32F42x/43x系では動作周波数が180MHzまで拡張されたせいかGPIOの
絶対的速度も強化され、168MHz動作時クロックバイパスをEnableにして
48MHzのクロックで動かしてもRead,Writeとも安定して動作可能です。
180MHzの時はSDIO_CKは52MHzまで増加しますがこの状態でも全く問題は
ありません。そんなわけで後で紹介する裏ワザを使わずどうしても48MHzの
SDHighSpeedModeで動かしたい場合は405/407系から42x/43x系にお引越し
する必要があります。
ほぼピン互換なのでボードの改造は不要で単純な交換でパワーアップできます!


…と言いたい と こ ろ で す が(こんなんばっかだ)
このSTM32F42x/43x系も1MB以上のフラッシュ領域を使っている時PA12の
電圧レベルが変化すると動作がおかしくなるという意味不明かつかなり
致命的な不具合が存在しますorz。このやばい不具合、rev3でFMCとともに
ようやく修正されたようですがPA12はUSBのD+に相当するためエラッタ
持ちのチップはUSB機能を殺すかもしくはフラッシュの1MB以上の領域を
使用しないという2択を迫られることになりますorz

/*!< Configure the SDIO peripheral */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
#if defined (STM32F40_41xxx)
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Falling; /* This is a baddest work around for STM32F40x and STM32F41x */
#else
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
#endif
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Enable; /* Set Direct Clock(48MHz) */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_4b;
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
errorstatus=SD_OK; /* Enter SDHighSpeedMode */

すみません話がそれましたがSTM32F405/407系でどうしても48MHzでSDIOを
安定して使用したい場合の裏技があります。そもそも安定しない理由がI/Oの
変化速度が規定に追い付けずデータが1ビットシフトして取り込まれている
せいなのです。ここでSDIO_CKのクロック送出エッジをNegativeに変えることに
よってクロックは遅れて送出されるがデータの取り込みタイミングは変わらない
状態となるためビットシフトせずうまい具合に取り込んでくれるようになります。
これはクロックバイパスEnable時の直結でも有効(実際はディバイダを噛まさ
ないでもSDIOCLKがそのまま出ているのではなくゲーティングされてSDIO_CKが
生成されている事を示している)で安定して読み書きができるようになります。

この送出エッジをNegativeにすることはデータの破損を招く事になるとして
errataとして設定が禁じられています。ですが48MHz動作だとこの構成の方が
都合がよくなります。おきぱにあるSTM32F4のSDIOドライバは405/407系向け
限定でSDHighSpeedModeを使う際は敢えてこの技を行っています。
もちろん禁を破った動かし方ですので物好きな人だけ試してみてください。

DRESULT disk_read(uint8_t drv,uint8_t *buff,uint32_t sector,unsigned int count)
{
switch (drv)
{
case SDIO_DRIVE:
{
Status = SD_OK;

if(count==1){
Status = SD_ReadBlock((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE);
}
else{
Status = SD_ReadMultiBlocks((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE
,count);
}

if (Status == SD_OK) return RES_OK;
else return RES_ERROR;
}
}
return RES_PARERR;
}

だらだらとクロック設定の話ばっかり続けてしまいましたがdisk_read()は
かなり単純明快です。SD_ReadBlock()及びSD_ReadMultiBlock()をセクタ数に
応じて呼び出すだけです。SD_ReadMultiBlock()単体だけでも動作には問題
ないですがセクタ数1の時はオーバーヘッドの方が大きくなるので分けた方が
いいです。

DRESULT disk_write(uint8_t drv,const uint8_t *buff,uint32_t sector,unsigned int count)
{
switch (drv)
{
case SDIO_DRIVE:
{
Status = SD_OK;

if(count==1){
Status = SD_WriteBlock((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE);
}
else{
Status = SD_WriteMultiBlocks((uint8_t*)(buff),
((uint64_t)(sector)*SECTOR_SIZE),
SECTOR_SIZE
,count);
}

if (Status == SD_OK) return RES_OK;
else return RES_ERROR;
}
}
return RES_PARERR;
}
ちなみにWriteの時も同じような感じで上記のようになります。


SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK;
#if defined (SD_POLLING_MODE)
uint32_t count = 0, *tempbuff = (uint32_t *)readbuff;
#endif

TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0;

SDIO->DCTRL = 0x0;

#if defined(SD_DMA_MODE)
/* Ready to DMA Before Any SDIO Commands! */
SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
SDIO_DMACmd(ENABLE);
#endif

if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
ReadAddr /= 512;
}

/* Set Block Size for Card */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);

if (SD_OK != errorstatus)
{
return(errorstatus);
}

SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);

/*!< Send CMD17 READ_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);

if (errorstatus != SD_OK)
{
return(errorstatus);
}

#if defined (SD_POLLING_MODE)
/*!< In case of single block transfer, no need of stop transfer at all.*/
/*!< Polling mode */
while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
{
for (count = 0; count < 8; count++)
{
*(tempbuff + count) = SDIO_ReadData();
}
tempbuff += 8;
}
}

if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
errorstatus = SD_RX_OVERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
count = SD_DATATIMEOUT;
while ((SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) && (count > 0))
{
*tempbuff = SDIO_ReadData();
tempbuff++;
count--;
}

/*!< Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS);

#elif defined(SD_DMA_MODE)
/* Check if the Transfer is finished */
Status = SD_WaitReadOperation();

/* Wait until end of DMA transfer */
while(SD_GetStatus() != SD_TRANSFER_OK);
if (TransferError != SD_OK)
{
return(TransferError);
}
#endif

return(errorstatus);
}

SD_ReadBlock()及びSD_ReadMultiBlock()の関数は#defineの設定によってDMAか
ポーリングかを分けています。上記コードはSD_ReadBlock()の例ですがブロック/
マルチブロック転送にDMAを使う場合はDMAのキックを関数の冒頭、SDIOコマンド
送出前に行っておかないとラスト数バイトの転送漏れが生じてしまいますので必ず
ここで行ってください。
これはSTM32F1のSDIOモジュールのころに議論にあった問題でSTマイクロの
フォーラムで指摘され講じられた対策
がその後のF2/F4の公式のドライバにも
反映されています。コメントでも述べましたがDMAを行う際は転送に使用する
メモリのアドレスを4で割り切れる数、つまり4バイトの境界に合わせてください。
GCCにおいては静的にメモリを確保する際に" __attribute__ ((aligned (4)))"の
修飾子を付けます。これをやっておかないと転送がコケて失敗します。
さらにブロック転送の際の転送するサイズを512バイト単位で行ってください。
これらをちゃんとやっておかないと(ry
ちなみにDMAを使用しない場合のFIFOのポーリング版でも転送速度の低下は
ほとんどありません。使用するリソース/ペリフェラルの優先度と相談して
DMAを使うか否かを決めてください。

また、Writeの際も同(ry

DSTATUS disk_status(uint8_t drv)
{
switch (drv)
{
case SDIO_DRIVE:
{
Status = SD_GetCardInfo(&SDCardInfo);

if (Status != SD_OK)
return STA_NOINIT;
else
return 0x00;
}
}

return STA_NOINIT;
}

disk_status()の実装は一番シンプルです。
SD_GetCardInfo()を実行するだけです。


さて、LPC4088などの多品種のマイコンにも触れたかったのですがそろそろこの
ぶろぐの一記事の文字制限に引っかかりそうなので実際に動かしてみたところは
次回に紹介させていただきます
・・・なんか内容をまとめるどころかどんどん内容が発散していくorz


20150227追:
まぐろ様よりコメントにてSDIOでDMAで4の倍数のバイト数でない数をf_write()で
書き込んだ際にデータが1〜3バイトずれて記録されるという不具合報告を
受けました。いただいた情報を元にこちらでも再現したのでさらに追ってみると
DMA初期化時のメモリ側インクリメント設定が4(バイト)となっていたためか4で
割り切れない数を書きこんだ際に次の転送でずれてしまうと最終的に推測しました。

というわけで対策ですが一番手っ取り早いのは細かいデータを記録していく際は
ポーリングモードで使用することです。どうしてもDMAしたい!場合について私も
もうちょっと調べてみました。するとChaN氏のページからも紹介されている
STM32F4のプロジェクトを専門に取り扱っているtilz0R氏のサイト中のコメントにてTassilo(田代・・!?)という方が同じ問題に
直面した際の解決策を提示してくれました。
私のプロジェクトでいうとsdio_stm32f4.cの291行目以降を以下のように変更します。
変更前:
SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;


変更後:
SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;

上記の対策で不具合は発生しなくなることを確認しました。
太字の部分が変更点です。ねむいさんてっきりメモリアドレスもサイズも
32bit(=4Byte)であるべきと思い込んでいましたのでこんな罠があったの
知らなかったとはちょっと恥ずかしいですorz
しかしながらメモリアクセスを4Byteから1Byte区切りにしたことによる
転送速度の低下や何かしらの弊害あるかもなので次回にみっちり動作検証
して考察してみましょう。

また、STM32F4/F2だけではなく、STM32F1系のSDIOや、はたまた
LPC1788/4088等のMCIでも4バイト区切りでDMA転送を行うものは同じ
不具合がでますのでこっちも同じく検証してできれば対策まで待っていきたい
ところです。
20150409追:
全ての品種で効果がある根本対策を講じました!

Comments

こんにちは。いつも大変お世話になっています。
この記事と前回の記事を見て、STM32F4DiscoveryでSDIOを使ったSDへの書き込みにチャレンジしてみました。
書き込みはできたのですが、よくわからない問題が発生して困っています。

症状は、マルチブロック書き込みの時にデータが破損することがあるというものです。
・書き込みバイト数が512以下では発生しない?
・必ずブロックの切れ目で破損する?
・書き込み開始位置が4の倍数なら発生しない?
・破損するときは、ブロックの先頭が1-3バイト消滅し、次のブロックとの間に同じバイト数挿入される? 挿入されるデータは直前のもの。
・再現性がある。書き込み量が同じなら、データ内容に関わらず全く同じ位置が破損する。

DMAでのみ発生し、ポーリングでは発生しませんでした。
SDカードの種類の変更、書き込み速度の変更、書き込み元の変更などを行いましたが、改善しませんでした。
シングルブロック関数をマルチブロック関数に置き換えても、512バイト以下の挙動は変わりませんでした。
プログラムは、ねむいさんのsdio_stm32f4のバージョン10をそのまま使わせて頂いています。
SDへの書き込みに必要なこと以外はしていません。

DMA転送が正常にできていないような気がするのですが、対処方法が全くわかりません。
ご助力頂ければ幸いです。

  • まぐろ
  • 2015/02/22 6:18 PM

まぐろさまはじめまして、ねむいです。

手持ちのSTM32F407Z RevAのSDIOが使える基板でSDSC,SDHCそれぞれ
数種類ずつカードを変えてマルチブロックライトを伴うファイル書き込み操作を
してみましたが私の環境では書き込んだデータの破損は再現しませんでした。

まぐろ様の書きこみから上位関数は使用せずSD_WriteBlocks()やSD_Write
MultiBlocks()を直接使用していると見受けましたがこれらの関数は使用に
際していくつかの制限があります。
SDカードの場合通常はブロックサイズが512バイトとなりますのでSDIOの
転送の際は512バイト単位で行わなければならず、さらにDMA転送の際に
使用するメモリのアドレスは4バイトの境界に合わせておかないと転送が
正常に完了しません。

まぐろ様の環境でwrite動作でデータが破損した際DMA転送時に何らかの
エラーが発生してエラービットが立っているはずなのでデバッガで動作を
追いかけてどのようなエラーとなっているか確認してみてください。

返信ありがとうございます。

確認してみたところ、正常時でもエラー時でも、転送時に必ずFIFO error flag(FEIF)がtransfer complete flag(TCIF)と同時に立っていました。
失敗時と成功時で挙動が変わらなかったため、よくわからない状態のままです。

下位関数は直接使用せず、上位関数(FatFS)のみを使っています。
関数は以下の順番で実行しています。(ソースを書いたら投稿できなかったため、関数名だけ書いています。)

f_open
f_write //38バイトのヘッダ書き込み
f_sync
while(1){
f_write //4096バイトのデータを書き込み続ける
f_sync


書き込んでいる4096バイトのデータは、確認しやすいように
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ¥r¥n
の64バイトを連結して4096バイトにしたものにしています。FLASHから読んでもRAMから読んでも変わりませんでした。
ヘッダを書き込まない場合や、ヘッダが4の倍数バイトの場合はデータの破損は起こりません。
f_writeやf_syncは成功しており、DMAやSDIO割り込みでも上記以外のエラーは発生していません。
よろしくお願いします。

  • まぐろ
  • 2015/02/26 5:16 PM



ねむいです。こんばんは。

いただいた情報を元にこちらでも再現を確認しました。
詳細と暫定対策は記事の最後の方に追記しましたのでご参照願います。

でもSDIOでDMAを使い、チマチマ書き込みしまくる私のGNSSロガー
でそういうデータが異常になる不具合は発生しなかったのはなぜ
なんだろう????たまたま4バイト区切りのデータ塊しか
やり取りしてなかったからかしら・・・・
せっかくなのでこちらにもメスを入れてみることにします。

なにはともあれまぐろ様、ご協力ありがとうございました。

ねむいです。こんにちは。
STM32F1系でも検証してみましたがF1系ではSDIOとDMAの構成ではF2/F4系と
違ってバーストサイズを柔軟に可変できる機構が存在せず常にWord(4バイト)単位
でしかデータをやり取りができないのがわかりました。無理やりByte単位で転送
してもUNDERRUNエラーになり失敗してしまいます。

したがってF1系の場合は4バイト区切りで書き込みをするかポーリングかの
二択となります。

こんにちは。返信が遅くなり申し訳ありません。

こちらでも正常動作を確認しました。
まさかメモリ側だけBYTEにするだけでいいとは・・・。
速度も問題ないと思います。

F1系で使う場合は、4バイト区切りにするか、諦めてSPIにすることにします。
DMA転送の待ち時間で他の処理を済ませたいので、ポーリングは使いたくないのです。

早速の調査と対応ありがとうございました。記事の続きも楽しみにしています!

  • まぐろ
  • 2015/03/03 11:01 AM

ねむいです。こんばんは。

F1系やNxPのMCIも対策行ってみましたので参考にしてください。
nemuisan.blog.bai.ne.jp/?eid=213939

Post a Comment








Go to top of page