2020年11月21日

STM32 USB CDC応用

以前、STM32 USB CDC注意点について紹介しました。今回はUSBの接続有無確認やCOMポートのオープンといった確認する方法を応用編として紹介します。

■USB接続有無確認方法
ステートを確認することでPC等のホスト側とのUSB接続有無を確認することができます。STM32 HALライブラリのUSB CDC以外でも利用できます。USBデバイスのハンドルのdev_stateから確認できます。dev_state が USBD_STATE_CONFIGURED の場合はUSBがPCと接続されている状態です。例えば下記のような使い方ができます。

if(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED)
{
   //USBが接続されている場合
}
else
{
  //USBが接続されていない場合
}

■COMポートオープン有無
CDCの場合、USB接続の有無だけでなく、COMポートのオープン有無を確認したい場合が多々あると思います。通信で確認する他に間接的ではありますが、CDC_GET_LINE_CODINGの呼び出しイベントから確認できます。電源ON後のUSB認識時とCOMポートオープン時にそれぞれ2回から3回、CDC_GET_LINE_CODINGのイベントが発生します。このイベント発生をカウントすることでCOMポートのオープンを確認することが可能です。

usbd_cdc_if.cファイル内のCDC_Control_FS関数のCDC_GET_LINE_CODING条件を下記の様に書き換えます。必要に応じて下記の様にカウンタとフラグを設定して確認できるようにします。

case CDC_GET_LINE_CODING:
{
  USBD_SetupReqTypedef * req = (USBD_SetupReqTypedef *)pbuf;
  if((req->wValue & 0x0001) != 0)
  {
    comOpenCount++;
    if(comOpenCount>3)IsComPortOpen=true;
  }
}


STM32マイコンのUSB関連のHALライブラリについても、最低限の機能は実装されているため、少しコードを追加することで応用することが可能です。他にも便利な機能等あれば、また紹介したいと思います。
posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする

2020年11月14日

コード移植時のチェック項目

STM32マイコンのHALライブラリは移植性が高く、STM32FxxやSTM32Lxxといったシリーズを超えた場合でもほぼそのままのコードで動かすことができます。シリーズによっては一部の関数で引数が増えていたり、設定が異なっているものもありますが、多くはそのまま移植することができます。ただ、時々、移植してもうまく動かない、同じシリーズでも動作しないといった場合があります。そのような場合はコードの分かりずらい箇所で設定が違っていたり、コード以外の設定が違って動かないということがあります。

今回はコード移植時にうまく動かない場合のチェック項目について紹介します。これまでの経験でよくあるコード移植時の設定漏れをリストにしてみました。

@リンカスクリプト
プロジェクトフォルダ直下にSTM32Fxxxxx_FLASH.ldファイルがあります。メモリ配置を設定するスクリプトでSDRAMやSRAM、密結合メモリ等の設定をしています。また、HeapサイズやStackサイズを定義しています。サイズが異なる場合にメモリを確保できず、エラーとなっている場合があります。

Aスタートアップファイル
プロジェクトフォルダ直下のstartupフォルダにstartup_stm32fxxxxx.sファイルがあります。起動直後に呼び出される処理が記載されています。稀に初期化処理や割込み処理を意図的に変更している場合があります。

Bプリプロセッサ
SW4STM32の場合、プロジェクトのプロパティから[C/C++ Build]→ [Settings]→ [Tool Settings]→ [MCU GCC Compiler]→ [Preprocessor] の定義を確認します。うまく動くプロジェクトと比較して抜けがないか、確認します。コード上で定義(#define)せずにここで定義している場合があります。定義項目が抜けているとコードが部分的に除外され、挙動や動作が異なる場合があります。必要に応じて追加します。

preprocessor.jpg


C割込み
プロジェクト直下のsrcフォルダ内のstm32fxxx_it.cファイルに割込み処理の設定がされています。よくある移植時の設定ミスとして、タイマの割込み関数内に直接、割込み関数が定義されている場合があります。また、割込み設定がされておらず、関数自体がない場合もあります。特にHAL_Delayが動作しない、HAL_Delayで停止してしまう場合はstm32fxxx_it.cファイル内にSysTick_Handler関数が定義されているか確認します。合わせて、SysTick_Handler関数内にHAL_IncTick関数が呼び出されているか確認します。


WinMerge等でプロジェクトフォルダを丸ごと比較できる場合は移植時のミスに気づきやすいですが、フォルダ内の構造が違っていたり、異なるシリーズに移植した際はファイル名が異なるため、単純比較できないということも多々あります。このような場合に上記の項目について確認すると漏れに気づくことが多いです。他にもよくある漏れがあれば追記したいと思います。

posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする

2020年11月07日

STM32 USBマスストレージクラス実装

CubeMXを用いて今回はSTM32マイコンにUSBカードリーダー的な機能(USBマスストレージクラス、MSC)を実装する方法について紹介します。今回はSDカードですが、応用すれば内蔵RAMやNOR Flash、NandFlash等への実装も可能です。今回はSTM32F746-Discoveryボードを用いました。環境はSTM32Cube FW_F7 V1.16.0+SW4STM32です。

1.CubeMX設定
1-1.ボード選択
ボード選択からSTM32F746-Discoveryを選択します。
1-1.jpg

1-2.SDMMC設定
SD4bit BUSを選択します。
SDMMCCLK clock divide factorに5を選択します。
GPIO Settingsで各IOをプルアップに選択します。
その他はデフォルトのままです。

1-2_1.jpg

1-2_2.jpg

1-3.USB_OTG_FS設定
ModeをDevice_Onlyを選択します。
その他はデフォルトのままです。

1-3.jpg


1-4.USB_DEVICE設定
「Class For FS IP」でMass Strage Classを選択します。
その他はデフォルトのままです。

1-4.jpg

1-5. クロック設定
USBのクロックを48MHzに設定します。それ以外は必要に応じて変更してください。

1-5.jpg


1-6.Heap、Stack設定
マウント時に足りなくなる可能性があるため、念のため、HeapとStackを10倍にして確保しました。使い方によってはもう少し減らしても問題ありません。
Heap 0x2000
Stack 0x4000

1-6.jpg

設定が一通り完了したら、コードを生成してプロジェクトにインポートします。

Aコード追加
コード追加が必要な部分は「usbd_strage_if.c」のみです。それ以外は既に初期化等のコードがCubeMXで自動生成されています。

72行目付近に下記のコードを追加します。

/* USER CODE BEGIN PRIVATE_DEFINES */
extern SD_HandleTypeDef hsd1;
#define CAPACITY ((uint32_t)0x80000000U)
/* USER CODE END PRIVATE_DEFINES */


STORAGE_GetCapacity_FS関数を下記のように書き換えます。

int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
  HAL_SD_CardInfoTypeDef info;
  int8_t ret = -1;
  HAL_SD_GetCardInfo(&hsd1, &info);

  *block_num = info.LogBlockNbr - 1;
  *block_size = info.LogBlockSize;
  if(info.LogBlockNbr<CAPACITY)
  {
   *block_num *= 512;
  }
  ret = 0;
  return ret;
  /* USER CODE END 3 */
}

ポイントは if(info.LogBlockNbr<CAPACITY)の部分です。2GB以下のSDカードの場合はif文以降がなくても動作しますが、2GBを超えるSDカードを対応させる場合には必須です。CubeMXで自動生成されるコードはSDカード2GB以下しか対応していないため、容量の条件に応じて対応させています。


STORAGE_Read_FS関数を下記のように書き換えます。

int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
  int8_t ret = -1;
  HAL_SD_ReadBlocks(&hsd1, buf, blk_addr, blk_len, HAL_MAX_DELAY);
  while (HAL_SD_GetCardState(&hsd1) != HAL_SD_CARD_TRANSFER){}
  ret = 0;
  return ret;
  /* USER CODE END 6 */
}

STORAGE_Write_FS関数を下記のように書き換えます。

int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
  int8_t ret = -1;
  HAL_SD_WriteBlocks(&hsd1, buf, blk_addr, blk_len, HAL_MAX_DELAY);
  while (HAL_SD_GetCardState(&hsd1) != HAL_SD_CARD_TRANSFER){}
  ret = 0;
  return ret;
  /* USER CODE END 7 */
}


上記の3つの関数を書き換えてビルドしてから、ファームを書き込みます。CN3にmicroSDカードを挿入し、CN13にmicroUSBをPCに接続するとmicroSDカードを読み書きできることが確認できました。デバイスマネージャからはSTM Product USB Deviceとして認識されます。


2.jpg

今回、書き換えた3つの関数をSDカードアクセス関数から別の関数に置き換えることで内蔵RAMやNOR Flash、NandFlash等をマウントすることが可能です。例えば内蔵RAMをマウントする場合は下記のように設定します。デバイス認識後、フォーマットによりファイルの読み書きができますが、RAMサイズが小さく、大きなファイルは保存できません。また、マイコンのリセットでデータが消えてしまいます。

#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 100
#define STORAGE_BLK_SIZ 0x200
uint8_t buffer[STORAGE_BLK_NBR*STORAGE_BLK_SIZ];
STORAGE_GetCapacity_FS関数はデフォルトのまま
STORAGE_Read_FS関数内に下記を追加
memcpy(buf, &buffer[blk_addr*STORAGE_BLK_SIZ], blk_len*STORAGE_BLK_SIZ);
STORAGE_Write_FS関数内に下記を追加
memcpy(&buffer[blk_addr*STORAGE_BLK_SIZ], buf, blk_len*STORAGE_BLK_SIZ);


今回はCubeMXを用いてSTM32マイコンにUSBカードリーダー的な機能(USBマスストレージクラス、MSC)を実装する方法を紹介しました。今後はNOR Flash、NandFlash等のマウントにも挑戦してみたいと思います。また、FatFSとも組み合わせて、MSCとマイコン側からの読み書きの機能も紹介したいと思います。
posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする

2020年10月24日

ST Acoustic SLライブラリ

STマイクロから提供されているCubeMX群の中にX-CUBE-MEMSMIC1があります。X-CUBE-MEMSMIC1はMEMSマイクに関するサンプルファイルや応用ライブラリが用意されています。例えば、PDM出力をPCM出力に変換するライブラリやFFT処理、USBオーディオ、音の位置検出(Sound Source Localization)、音の向きに応じた増幅(Beamforming)といったライブラリが準備されています。

今回は音の位置検出(Sound Source Localization)、Acoustic SLライブラリについて紹介します。Acoustic SLライブラリを使用して、2つor4つのマイクの情報を入力すると音の方向を角度として出力することができます。マイク2つの場合は-90~90度で検出され、4つの場合は0~360度で検出できます。

Acoustic SLライブラリでは、XCORR cross correlationアルゴリズム、GCC-PHATアルゴリズム、BMPHアルゴリズムが実装されており、3つからアルゴリズムを選択できます。Acoustic SLライブラリの紹介PDFに記載されている各アルゴリズムの違いについて下記に記します。


・XCORR cross correlation
 -時間領域で処理
 -計算処理が軽い
 -角度分解能は低い
   -マイクの距離を離す必要がある

・GCC-PHATアルゴリズム
 -周波数領域で処理
 -XCORRより計算処理が必要
 -角度分解能は比較的高い
 -マイクの距離に影響しない

・BMPHアルゴリズム
 -周波数領域で処理
 -XCORRよりも計算処理が必要だが、PHATよりも軽い
 -マイクの距離に影響しない
 -安定性を高めるために過去データに基づいた処理

ヘルプファイルに記載されているアルゴリズム比較を参考に記します。

comparison.jpg

Acoustic SLライブラリはソースコード形式でなく、*.a形式のライブラリとして提供されており、ライブラリを組み込んで使用します。組み込み方については以前に紹介したこちらを参照してください。また、使用できるSTM32マイコンの中でもM4、M7コアのみ対応と記載されており、STM32シリーズだとF3、F4、F7、G4、H7、L4のみ対応ということになります。

角度検出で使用する関数は下記の通りです。

@メモリ設定関数AcousticSL_getMemorySize
A初期化関数AcousticSL_Init
B閾値設定関数AcousticSL_setConfig
C音データ入力関数AcousticSL_Data_Input
D音源角度取得関数AcousticSL_Process

各関数の設定例を紹介します。

@メモリ設定関数AcousticSL_getMemorySizeとA初期化関数AcousticSL_Init
設定のポイントは下記の通りです。
・音データ配列インクリメント数
 音データの入力時にどのような配列かに応じて数が変わります。
 例えば、4つの音データが1つの配列に順番に入る場合は4つずつポインタを進めるため、4となります。
 int16_t val[512]={マイク1,マイク2,マイク3,マイク4,マイク1,マイク2,マイク3,マイク4,...}

 例えば、マイクごとに配列を準備して音データを入力する場合はポインタを1つずつ進めるため、1となります。
 int16_t mic1[16]={マイク1,マイク1,...}
 int16_t mic2[16]={マイク1,マイク1,...}

・処理までのサンプリング数
 角度検出するためのサンプリング数の設定です。4つのマイク合計でのサンプリング数を設定します。
 なお、サンプリング周波数16kHz、サンプリング数512の場合、AcousticSL_Data_Inputで1回あたり、各マイク16個のデータ(計64個)を入力し、同様のAcousticSL_Data_Inputを32サイクル繰り返すとAcousticSL_Processで角度を得ることができます。
 16個x4chx32cycle=512サンプル

 サンプリング周波数16kHz、サンプリング数256の場合は16cycle、サンプリング周波数16kHz、サンプリング数128の場合は8cycleとなります。 

uint32_t error_value = 0;
AcousticSL_Handler_t SSL_Handler_Instance;
AcousticSL_Config_t SSL_Config_Instance;

SSL_Handler_Instance.channel_number = 4;//マイク数2or4
SSL_Handler_Instance.M12_distance =50;//マイク同士の距離 単位mm
SSL_Handler_Instance.M34_distance =50;//マイク同士の距離 単位mm
SSL_Handler_Instance.sampling_frequency = 16000;//サンプリング周波数
SSL_Handler_Instance.algorithm = ACOUSTIC_SL_ALGORITHM_GCCP;//処理アルゴリズム
SSL_Handler_Instance.ptr_M1_channels = 1;//音データ配列インクリメント数
SSL_Handler_Instance.ptr_M2_channels = 1;//音データ配列インクリメント数
SSL_Handler_Instance.ptr_M3_channels = 1;//音データ配列インクリメント数
SSL_Handler_Instance.ptr_M4_channels = 1;//音データ配列インクリメント数
SSL_Handler_Instance.samples_to_process = 512;//処理までのサンプリング数 全マイク合計 32,64,128...
AcousticSL_getMemorySize( &SSL_Handler_Instance);//サンプリング数に応じた必要メモリ領域
SSL_Handler_Instance.pInternalMemory=(uint32_t *)malloc(SSL_Handler_Instance.internal_memory_size);//メモリ確保
if(SSL_Handler_Instance.pInternalMemory == NULL)
{
  printf("SSL Malloc Error\n\r");
}

error_value = AcousticSL_Init( &SSL_Handler_Instance);//初期化
if(error_value != 0)
{
  printf("SSL Config Error\n\r");
}

B閾値設定関数AcousticSL_setConfig
分解能は角度分解能を設定します。分解能は内部的に処理されます。
環境音変化閾値はどれくらいの音の変化で音源検出を実行するかを設定します。0~1000の範囲で設定します。
例えば、テストデータとしてsin波等を与えて検証する際は環境音(ノイズ)がないため、0を設定すると常に角度検出されます。
環境音が大きく、大きな音だけで反応させたい場合は値を大きく設定します。

SSL_Config_Instance.resolution=10;//分解能
SSL_Config_Instance.threshold=24;//環境音変化閾値
error_value = AcousticSL_setConfig(&SSL_Handler_Instance, &SSL_Config_Instance);
if(error_value != 0)
{
  printf("SSL Config Error\n\r");
}

C音データ入力関数AcousticSL_Data_Input
サンプリング周波数16kHz、サンプリング数512の場合、1回のAcousticSL_Data_Inputでは16個x4chデータを入力します。これを32cycle分実行するとサンプリング数512となり、角度データを取得できます。サンプリング数に達していない状態でAcousticSL_Processを実行すると処理が異常終了します。

int16_t val[4][16];
//各チャンネル16個分の音データを変数に入力
res=AcousticSL_Data_Input( (int16_t *)&val[0][0], (int16_t *)&val[1][0],
                                           (int16_t *)&val[2][0], (int16_t *)&val[3][0],
                                           &SSL_Handler_Instance );

resが1の場合は処理に必要なサンプリング数に達したため、AcousticSL_Processで角度データを取得できます。
resが0の場合は処理に必要なサンプリング数に達していないため、サンプリング数に達するまでAcousticSL_Data_Inputを繰り返して音データを入力します。

D音源角度取得関数AcousticSL_Process
AcousticSL_Data_Inputの返数が1であることを確認後にAcousticSL_Processを実行して音源角度情報を取得します。検出された角度は配列の1番目に0~360(マイク2つの場合は-90~90)の範囲でそのまま角度として出力されます。環境音変化閾値に0以外を設定した場合で環境音変化閾値以下の場合は角度検出されず、-100が返されます。

int32_t result[2];
res=AcousticSL_Process((int32_t *)&result, &SSL_Handler_Instance);
if(result[0]==ACOUSTIC_SL_NO_AUDIO_DETECTED)
{
  printf("SoundSource is not detected!::%lo\n\r",result[0]);
}
else
{
  printf("SoundSource is detected!::%lo \n\r",result[0]);
}


マイクの配置は下記の通りに配置します。


mic_array.jpg


X-CUBE-MEMSMIC1ライブラリは様々な応用例が準備されている一方でPDFやヘルプ資料だけでは情報が少なく、サンプルコードや実際の関数を使用しながら調べる必要があります。使いこなすまでに苦労したため、情報としてまとめてみました。


posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする

2020年09月12日

STM32H7とNAND Flashメモリ

以前に紹介したNAND Flashメモリ を搭載したNucleo144 Memory Shieldを用いて、STM32H743と接続した際にH7シリーズ固有の問題に直面したたため、対処方法を少し紹介します。

STM32H7シリーズは非常に高速な上にIキャッシュとDキャッシュを搭載しているため、さらに効率的に処理をすることが可能です。一方でDキャッシュはDMA転送時等にデータ化け(キャッシュの一貫性が崩れる)が生じるため、なかなか厄介です。データ化けが生じる可能性を意識して実装する必要があります。データ化け、キャッシュの一貫性が崩れるとは、例えば、DMA転送の際にメモリからDMAコントローラに書き込んだはずが、キャッシュに溜まっているだけで、DMAコントローラに書き込まれていない、その逆も然りでDMAコントローラのデータを読み出そうとメモリを見てもキャッシュに溜まっているだけでメモリは更新されてないといったことが生じます。

DMA転送時のデータ化けの対処方法についてはこちらのサイトに紹介されています。今回はDMA転送だけでなく、FMCメモリコントローラでも同様のデータ化けが発生することが分かりました。考えてみれば、メモリコマンド指令も同じデータメモリのため、当たり前といえば当たり前ですが、FMCコントローラの場合、データが化けるという現象ではなく、NAND Flashメモリの初期化や書き込みでエラーが発生するという現象でした。データ化けによってメモリコマンドの書き込み、読み込みのデータが化けてエラーとなったようです。HAL_BUSY等のエラーレスポンスからデータ化けと気づくまでに時間を要してしまいました。

NAND Flashメモリの場合、初期化のMPU_Config内に下記のようにEthernetDMAの領域に加えて、NAND Flashメモリで使用する0x80000000以降の領域に対してライトスルーモードに設定します。EthernetDMAの領域の設定についての詳細はこちらに説明があります。

設定の注意点としては領域はMPU_REGION_NUMBER*で順番に設定することです。*が同じだと設定が上書きされてしまいます。

/* Configure the MPU attributes as Device not cacheable for ETH DMA descriptors */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Normal Non Cacheable for LwIP RAM heap which contains the Tx buffers */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30044000;
MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes  for NAND Flash */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x80000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);


今回はNAND Flashメモリについて紹介しましたが、NOR Flash、QSPI等でも同様だと思います。0x80000000以降の領域に対してライトスルーモードに設定することでエラーが発生せずに読み書きできるようになりました。FMCコントローラの場合、データが化けるという現象ではなく、NAND Flashメモリの初期化や書き込みでエラーが発生しました。当初、タイミング信号のパラメータ設定ミスや各サイズの設定ミスを疑いましたが、FMC単体では動作して、様々な他の機能を入れると動作しないため、機能を削って確認すると最終的にDキャッシュと判明しました。Dキャッシュを完全に無効にすると正常に動作しますが、パフォーマンスを考えると必要な領域のみを個別に設定した方が良さそうです。
FMCの設定についてはこちらのPDFに分かりやすく説明されています。
posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする