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(田代・・!?)という方が同じ問題に
直面した際の解決策を提示してくれました。
以下コメント引用します
Tassilo H.
11 years ago
Hello Majerle,
first thank you for providing these libb, which helped a lot to get started on the STM32F407.
But I have found a problem that will only show under certain circumstances when using DMA transfers with the SDIO interface,
but then will lead to corrupted files.
The issue comes from the way the DMA is set up (which probably comes from the original code from STM). In the functions
void SD_LowLevel_DMA_TxConfig (uint32_t *BufferSRC, uint32_t BufferSize) and
void SD_LowLevel_DMA_RxConfig (uint32_t *BufferSRC, uint32_t BufferSize)
DMA is set up to use a memory access width of "word" and a DMA burst of 4 transfers.
This leads to certain limitations for the data buffer when a sector is read or written to the SD-card:
The obvious limitation is that now a 2-byte (word-aligned) address is required for the sector buffer.
Normally, this is the case with the memory layout of fatfs, but there is a problem when reading files
in chunks of 512 bytes or more with the f_read/f_write functions to a buffer address that is not word aligned,
because fatfs will then try to do the sector reads/writes directly to or from this address
(bypassing its internal sector buffer), which fails (gives 1 missing byte at block start/end) .
The second (even more tricky) limitation is, that according to the data sheet each DMA burst (of 2*4 = 8 bytes in our case)
must not cross a 1k address boundary (I stumbled over this when a file read from the SD card was missing 2 bytes at
the beginning when read at one place of my code, but was ok at a second place).
To be sure of this, each buffer that is used for file access now should be 8-bytes aligned, which becomes a bit impractical.
So I suggest to change the following lines in the above mentiond functions in fatfs_sd_sdio.c:
// from SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
// to
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// from
// SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
// to
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
// from SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
// to
SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
This now allows arbitrarily aligned buffers by doing single-byte memory accesses.
One drawback is though that this could lead to performance issues or DMA FIFO overflow
when further heavy DMA load from other peripherals is going on during the SD-Card read.
I hope this is useful,
best regards,
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区切りにしたことと、バースト
長さがシングルになってしまったことからバスアクセスの効率が1/16に落ちる
ため転送速度の極端低下や何かしらの弊害あるかもなので次回にみっちり
動作検証して考察してみます。
また、STM32F4/F2だけではなく、STM32F1系のSDIOや、はたまた
LPC1788/4088等のMCIでも4バイト区切りでDMA転送を行うものは同じ
不具合がでますのでこっちも同じく検証してできれば対策まで待っていきたい
ところです。
20150409追:
全ての品種で効果がある根本対策を講じました!

免責・連絡先は↑のリンクを
↓SNSもやってます↓
powered by まめわざ
- ARM/STM32 (119)
- OpenOCD (27)
- ARM/NxP (34)
- ARM/Cypress (5)
- ARM/Others (3)
- ARM/Raspi (1)
- AVR (13)
- FPGA (4)
- GPS/GNSS (20)
- MISC (86)
- SDCard_Rumors (1)
- STM8 (2)
- Wirelessなアレ (16)
- おきぱ (1)
- ブラウザベンチマーク (29)
- 日本の自然歩道 (27)
- GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
⇒ Kenji Arai (05/29) - GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
⇒ ねむい (05/26) - GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
⇒ Kenji Arai (05/24) - 中部北陸自然歩道を往く -砺波平野の県境を駆け抜ける!-
⇒ ねむい (12/18) - 中部北陸自然歩道を往く -砺波平野の県境を駆け抜ける!-
⇒ ひかわ (12/15) - STM32U0はぢめました
⇒ ねむい (08/07) - STM32U0はぢめました
⇒ ひかわ (07/28) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ ねむい (05/17) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ どじょりん (05/16) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ どじょりん (05/16)
- October 2025 (1)
- September 2025 (1)
- August 2025 (1)
- July 2025 (1)
- June 2025 (1)
- May 2025 (1)
- April 2025 (1)
- March 2025 (1)
- February 2025 (1)
- January 2025 (1)
- December 2024 (2)
- November 2024 (1)
- October 2024 (1)
- September 2024 (1)
- August 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (1)
- April 2024 (1)
- March 2024 (1)
- February 2024 (2)
- January 2024 (1)
- December 2023 (4)
- November 2023 (2)
- October 2023 (2)
- September 2023 (1)
- August 2023 (2)
- July 2023 (1)
- June 2023 (2)
- May 2023 (3)
- April 2023 (1)
- March 2023 (1)
- February 2023 (1)
- January 2023 (1)
- December 2022 (2)
- November 2022 (1)
- October 2022 (1)
- September 2022 (1)
- August 2022 (1)
- July 2022 (1)
- June 2022 (1)
- May 2022 (1)
- April 2022 (1)
- March 2022 (1)
- February 2022 (1)
- January 2022 (1)
- December 2021 (2)
- November 2021 (2)
- October 2021 (1)
- September 2021 (1)
- August 2021 (1)
- July 2021 (1)
- June 2021 (1)
- May 2021 (1)
- April 2021 (1)
- March 2021 (1)
- February 2021 (1)
- January 2021 (1)
- December 2020 (3)
- November 2020 (1)
- October 2020 (1)
- September 2020 (1)
- August 2020 (1)
- July 2020 (1)
- June 2020 (2)
- May 2020 (1)
- April 2020 (1)
- March 2020 (1)
- February 2020 (1)
- January 2020 (1)
- December 2019 (3)
- November 2019 (1)
- October 2019 (1)
- September 2019 (2)
- August 2019 (1)
- July 2019 (1)
- June 2019 (1)
- May 2019 (1)
- April 2019 (1)
- March 2019 (1)
- February 2019 (1)
- January 2019 (1)
- December 2018 (3)
- November 2018 (2)
- October 2018 (1)
- September 2018 (1)
- August 2018 (1)
- July 2018 (1)
- June 2018 (1)
- May 2018 (1)
- April 2018 (2)
- March 2018 (1)
- February 2018 (1)
- January 2018 (1)
- December 2017 (2)
- November 2017 (2)
- October 2017 (1)
- September 2017 (1)
- August 2017 (1)
- July 2017 (1)
- June 2017 (1)
- May 2017 (1)
- April 2017 (1)
- March 2017 (2)
- February 2017 (2)
- January 2017 (2)
- December 2016 (7)
- November 2016 (2)
- October 2016 (2)
- September 2016 (1)
- August 2016 (1)
- July 2016 (1)
- June 2016 (1)
- May 2016 (2)
- April 2016 (1)
- March 2016 (2)
- February 2016 (1)
- January 2016 (1)
- December 2015 (3)
- November 2015 (1)
- October 2015 (3)
- September 2015 (2)
- August 2015 (2)
- July 2015 (3)
- June 2015 (3)
- May 2015 (4)
- April 2015 (2)
- March 2015 (4)
- February 2015 (1)
- January 2015 (3)
- December 2014 (3)
- November 2014 (2)
- October 2014 (1)
- September 2014 (2)
- August 2014 (2)
- July 2014 (3)
- June 2014 (2)
- May 2014 (1)
- April 2014 (1)
- March 2014 (4)
- February 2014 (4)
- January 2014 (3)
- December 2013 (5)
- November 2013 (4)
- October 2013 (3)
- September 2013 (2)
- August 2013 (2)
- July 2013 (2)
- June 2013 (3)
- May 2013 (2)
- April 2013 (2)
- March 2013 (2)
- February 2013 (2)
- January 2013 (3)
- December 2012 (4)
- November 2012 (2)
- October 2012 (2)
- September 2012 (4)
- August 2012 (1)
- July 2012 (3)
- June 2012 (2)
- May 2012 (3)
- April 2012 (3)
- March 2012 (2)
- February 2012 (3)
- January 2012 (3)
- December 2011 (5)
- November 2011 (3)
- October 2011 (2)
- September 2011 (2)
- August 2011 (2)
- July 2011 (2)
- June 2011 (2)
- May 2011 (2)
- April 2011 (2)
- March 2011 (2)
- February 2011 (2)
- January 2011 (3)
- December 2010 (7)
- November 2010 (1)
- October 2010 (1)
- September 2010 (1)
- August 2010 (3)
- July 2010 (4)
- May 2010 (1)
- April 2010 (2)
- March 2010 (2)
- February 2010 (2)
- January 2010 (3)
- December 2009 (3)
- November 2009 (8)
- October 2009 (7)
- September 2009 (5)
- August 2009 (4)
- July 2009 (6)
- June 2009 (6)
- May 2009 (14)
- January 1970 (1)
Copyright(C) B-Blog project All rights reserved.