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

※今回の記事の内容は時勢/判明した事実に即して逐次変更していきます。


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()なる関数がとある
バージョンから唐突に追加されてフルの規格を入手しないとわからなかったSDHighSpeed
Modeへの切り替え方が判明しております。そんなわけでこの関数を利用して正規に
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追:
全ての品種で効果がある根本対策を講じました!

Go to top of page