2020年04月25日

STM32 FMCを用いたSDRAMの追加設定

今回はSTM32マイコンのFMCを用いたSDRAM設定を紹介します。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了しています。一方で実際にSDRAMを使いこなすためには追加設定が必要です。

追加設定の中でもメモリマップモードで使用する方法について紹介します。メモリマップモードの場合、変数宣言時に領域を指定することで変数をSDRAM上に配置することが可能です。SDRAMと内蔵RAMの差を意識せずに取り扱うことができます。

今回はSDRAMがボード上に既に実装されているSTM32F7 Discoveryを使用しました。


@リンカスクリプト追加
SDRAMの領域をリンカに追加します。プロジェクトファイル直下の***_FLASH.ldファイルをテキストエディタで編集します。Eclipseでそのまま開くと文字コードが異なるため、テキストエディタで編集します。Eclipseで編集する場合はEclipseのファイルプロパティからUTF-8を選択してから編集してください。

61行目付近に赤字の文字列を追加します。STM32F7 Discoveryは128Mbitのメモリを搭載していますが、データ線が16bit分しか配線されていないため、半分の64Mbit、つまり8MByteが使用できます。


/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
SDRAM(rw) : ORIGIN = 0xC0000000, LENGTH = 8M
}


160行目付近に赤字の文字列を追加します。
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM

.sdram (NOLOAD) :
{
. = ALIGN(4);
*(.sdram)
*(.sdram.*)
. = ALIGN(4);
} >SDRAM

Aメモリ初期化コード追加
メモリ初期化コードを追加します。今回はmain.cのPrivate user code部分に下記のdefineと初期化関数を追加しました。下記のコードはSTM32CubeMXのファーム、STM32Cube\Repository\STM32Cube_FW_F7_V***\Drivers\BSP\STM32746G-Discovery内、stm32746g_discovery_sdram.c、stm32746g_discovery_sdram.hを参考にしています。

/* Private user code ---------------------------------------------------------*/

#define SDRAM_DEVICE_ADDR ((uint32_t)0xC0000000)
#define SDRAM_DEVICE_SIZE ((uint32_t)0x800000) /* SDRAM device size in MBytes */
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
#define REFRESH_COUNT ((uint32_t)0x0603) /* SDRAM refresh counter (100Mhz SD clock) */
#define SDRAM_TIMEOUT ((uint32_t)0xFFFF)


void SDRAM_Initialization_sequence(SDRAM_HandleTypeDef *sdramHandle, uint32_t RefreshCount)
{

  __IO uint32_t tmpmrd = 0;
  FMC_SDRAM_CommandTypeDef Command;

  /* Configure a clock configuration enable command */
  Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Insert 100 us minimum delay */
  /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
  HAL_Delay(1);

  /* Configure a PALL (precharge all) command */
  Command.CommandMode = FMC_SDRAM_CMD_PALL;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Configure an Auto Refresh command */
  Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 8;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Program the external memory mode register */
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
  SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
  SDRAM_MODEREG_CAS_LATENCY_2 |\
  SDRAM_MODEREG_OPERATING_MODE_STANDARD |\
  SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = tmpmrd;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Set the refresh rate counter */
  /* Set the device refresh rate */
  HAL_SDRAM_ProgramRefreshRate(sdramHandle, RefreshCount);

}

/* USER CODE END 0 */


Bメモリ初期化関数追加
先ほど、追加したメモリ初期化関数をメモリ関連のペリフェラル初期化後に実行します。STM32CubeMXではFMCを有効化するとペリフェラル初期化コードが自動で生成されます。main.c内のmain関数内、MX_FMC_Init()がメモリ関連のペリフェラル初期化関数に該当します。MX_FMC_Init()内ではメモリタイミング等のFMC関連の設定がHAL_SDRAM_Init()で行われます。FMC関連の設定後に先ほどのメモリ初期化関数を追加します。ユーザーコードを記入する箇所、「USER CODE BEGIN FMC_Init 2」の中に下記のように関数を追加します。

 /* USER CODE BEGIN FMC_Init 2 */
  SDRAM_Initialization_sequence(&hsdram1, REFRESH_COUNT);
 /* USER CODE END FMC_Init 2 */

Cグローバル変数追加
attributeを追加することでSDRAMに変数を配置します。コンパイラがarm-gccの場合は下記のようにattributeを指定します。

uint32_t sdram_data[85400]__attribute__((section(".sdram")));

arm-gccに限定されない場合はコンパイラに応じて対応できるようにifdefを使用して下記のように変数を定義します。

#if defined ( __ICCARM__ ) // !< IAR Compiler
#pragma location=0xC0000000
 static __no_init uint32_t sdram_data[85400];
#elif defined ( __CC_ARM ) //!< Keil Compiler
 uint32_t sdram_data[85400] __attribute__((at(0xC0000000)));
#elif defined ( __GNUC__ ) // !< GNU Compiler
 uint32_t sdram_data[85400]__attribute__((section(".sdram")));
#endif


以上の設定で通常の内蔵RAM上の変数と同じように使用することができます。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了していますが、メモリマップモードといったシームレスにSDRAMを使うためには追加の設定が必要です。なお、SDRAMの初期化タイミング等の設定についてはこちらのサイトで丁寧に説明がされています。また、前回のメモリ領域空き確認方法を設定することでSDRAMの使用率も可視化することができます。合わせて確認してみてください。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年04月18日

メモリ空き領域確認方法

今回はSW4STM32といったarm-gcc環境でビルド時にメモリ領域を確認する方法を紹介します。


簡単なプログラムであれば、FlashやRamの領域を気にする必要はありませんが、大量にメモリを使用する場合はどれくらい既に占有しているのか気になると思います。リンカの設定を少し追加することで詳細情報をビルド時のコンソールに表示することが可能です。

デフォルトではビルド後のコンソール画面に下記のように表示されます。

〜〜〜
directory post-build
Generating hex and Printing size information:
arm-none-eabi-objcopy -O ihex "***.elf" "***.hex"
arm-none-eabi-size "***.elf"
text        data     bss      dec       hex      filename
22604     484     1828    24916   6154    ***.elf
**:**:** Build Finished (took **s.**ms)
〜〜〜

メモリ使用量はtext、data、bssという形で分かれて情報が表示されます。

.text プログラムコード、通常はFlash
.data 初期値を持つ静的変数、通常はRAM
.bss 初期値を持たない静的変数、通常はRAM

ただ、正直、text、data、bssという表示では分かりにくいと思います。リンカの設定を少し追加すると詳細情報をビルド時に表示することが可能です。

プロジェクトの「プロパティ」から「C/C++ Build」、「Settings」、「Tool Settings」、「MUC GCC Linker」のCommand欄に設定を追加します。

Command欄の初期値は
gcc
となっていますが、
-Wl,--print-memory-usage
を追加して
gcc -Wl,--print-memory-usage
にします。



Linker-Setting.jpg
実際に設定した際の設定画面はこんな感じです



この設定状態でビルドすると...
〜〜〜
Memory region  Used Size   Region Size   %age Used
         RAM:              2080 B         320 KB              0.63%
      FLASH:            15284 B            1 MB      1.46%
     SDRAM:          341600 B            8 MB              4.07%

Finished building target: ***.elf

C:*** print-directory post-build
Generating hex and Printing size information:
arm-none-eabi-objcopy -O ihex "***.elf" "***.hex"
arm-none-eabi-size "***.elf"
text        data     bss      dec       hex      filename
15156     120     343568 358844 579bc  ***.elf
**:**:** Build Finished (took **s.**ms)
〜〜〜

という感じでメモリの使用率とサイズが表示され、どれくらいメモリを占有しているのか一目で分かります。なお、通常はSDRAMがなければSDRAMの項目は表示されません。SW4STM32以外でもSTM32CubeIDEといったarm-gcc系であれば同様に使えると思います。大量にメモリを使用する場合にはぜひ、このような設定を有効化してみてください。


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

2020年04月11日

正負昇圧コンバータ回路

オペアンプやAD変換、オーディオアンプといった回路では正負電源が必要になることが多々あります。そのような場合には方法として、昇圧コンバータ回路を使用する方法、仮想GNDを生成する方法(レールスプリッタ、オペアンプ)が考えられます。

仮想GNDを生成する方法では最大数mA~数10mA程度とあまり電流をとれません。一方、昇圧コンバータ回路を使用する方法では回路によって大電流をとれるものの、昇圧系部品のコストとスイッチングノイズが課題となります。MAU109といった絶縁型の昇圧コンバータは部品を減らして実装できますが、スイッチング周波数が100kHz前後とあまり高くありません。100ksps以上のAD変換用途やハイレゾオーディオ用途ではスイッチングノイズの影響を避けられません。電解コンデンサやインダクタ等のノイズフィルタが別途必要になります。スイッチング周波数を外部のノイズフィルタで除く場合、負荷変動によってノイズフィルタが十分機能しない場合があります。ノイズフィルタの他に昇圧させたあとにリニアレギュレータICでノイズリップルを除去するといった対策を行う場合もあります。

絶縁型ではありませんが、スイッチング周波数が高い2ch以上の昇圧回路を内蔵したスイッチングICを使用(例1例2)して正負電源を生成するとスイッチング周波数によるノイズリップルの影響を低減させることができます。スイッチング周波数が1.2MHz前後と非常に高いため、インダクタ等でノイズ除去も容易です。ただ、問題点として2ch以上の昇圧回路を内蔵したスイッチングICは1個800円~1000円近くするものも多く、コストが課題となります。


そこでいろいろ調査すると1chの昇圧回路で正負の電源を生成する回路例がありました。AD変換ICのADAS3023の電源回路として紹介されており、1chの昇圧スイッチングICとしてADP1613が使用されています。ADP1613であれば200円前後で販売されており、2ch以上の昇圧回路を内蔵したスイッチングICに比べてコストが1/4以下です。当該回路の弱点として正側の電圧しかフィードバックしていないため、正負で偏った負荷を与えると電圧変動してしまう点とカップルインダクタが2つ必要な点があります。オペアンプやAD変換、オーディオアンプ用途であれば正負で偏ることは少なく、カップルインダクタのコストアップもスイッチングICのコストアップに比べれば小さいため、メリットは十分ありそうです。

ADP1613_DualOutput.jpg
AD変換IC ADAS3023データシートから抜粋(左側が正負昇圧回路)


1chの昇圧回路で正負の電源を生成回路を実際に実装してみました。


ADP1613_Board.JPG

カップルインダクタのフットプリントpinアサインを間違えたため、ちょっと残念な実装になってしまいましたが、±15V前後の出力を確認することができました。正負対称電圧なら思いつかなくはないですが、1chの昇圧回路で正負の電源を生成するのはなかなか面白いと思いました。フットプリントpinアサインの修正とインダクタ、コンデンサのパラメータを調整してから基板公開や昇圧モジュールとしての販売を検討したいと思います。
posted by Crescent at 00:00| Comment(0) | 電子部品 | このブログの読者になる | 更新情報をチェックする

2020年04月04日

Aliexpressの制御系装置

Aliexpressでは日常品から家電、電子部品など幅広く扱っていますが、その中でも制御系、モーションコントロールの研究を行っている身としてAliexpressで見つけた気になる装置を紹介します。実際に試したことはありません。


大学の実験の授業でも扱ったような反転振り子の装置です。レールの長さもそこそこあるので調整しやすいかもしれません。

リニアでなく、回転版です。

Youtubeで見たことあるような装置です。装置によって抵抗膜式のタッチパネルを使用してボール位置を検出するタイプもありますが、これまOpenMVを使用してボール位置を検出しているようです。

サーカスの玉乗りロボット、所謂ボールボットの中国版です。

こちらもYoutubeで見たことあるロボットですが、ポールが非常に短いです。傘を手の上で立てて遊んだものを制御で自動化したイメージです。

一輪車ロボットです。

こちらもアメリカの大学の研究でみたようなロボットです。フライホイールで平衡を取って制御します。


価格は安くないものの、見た目では正直、レベルが高いです。どれも汎用化された制御ボードに設定ツールが用意されており、コードも公開されているようです。最近の研究をそのまま製品として出せる技術力はさすがだと思いました。実際に買って動くかどうかは分かりませんが、教育などでも活用できそうです。
posted by Crescent at 00:00| Comment(0) | モノづくり | このブログの読者になる | 更新情報をチェックする