STM32L5を使ってみる3 -OCTO-SPIでQUAD-SPIROMを使おう-
●QUAD-SPI接続NORFLASH-ROMを使いこなせ!
FONTX2ファイルなどの大容量のデータはSTM32等の大規模マイコンと
いえどもフラッシュ容量を非常にひっ迫します。
以前からはそういったデータは外付けのQSPI-ROMに格納し、そこから
アクセスするといった手法が定番となっております。
STM32L5では信号線を最大8本持つクロックドシリアルインターフェース
いわゆる"OCTO-SPI"をもちさらに利便性が高まっております!
このOCTO-SPIを使ってQSPI-ROMと接続し内蔵フラッシュメモリのように
使えるMemoryMappedModeで読み出していこうと思います!
●もはや定番☆WinbondのW25Q128JVSIQ
前々回からの紹介の通り、ねむいさんは入手も容易で十分な容量(16MB)、
高速なクロック(133MHz)でデフォルトでQUAD-SPIモードになっていて
コマンドが省略出来て扱いやすいW25Q128JVSIQを使用しました。
W25Qのシリーズにはいろいろありますが末尾"IQ"のものは上でも
書いた通り最初っからQUAD-SPIモードになっており今から紹介する
メモリマップドモードで投げるコマンドがいくつか省略出来て楽です。
digikeyやmouserで購入の際はうっかり違うやつを買わないように
型番には注意してください。
●OCTO-SPIをMemoryMappedModeで使用するためのコード手順
STM32L5に搭載されているOCTO-SPIはSTM32F7などにあったQUAD-SPI
インターフェースからされに拡張され、読み出しのほかに書き込みも
サポートされています。基本的な設定の流れはQUAD-SPIの頃とほぼ
同じですがOCTO-SPIならではの設定もあるので注意しましょう。
↓まずはI/O設定です。
/**************************************************************************/
/*!
@brief OCTO-SPI GPIO Configuration.
@param None.
@retval None.
*/
/**************************************************************************/
void OSPI_IoInit_If(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/* Initializes the peripherals clock */
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_OSPI;
PeriphClkInit.OspiClockSelection = RCC_OSPICLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
for(;;);
}
/* Peripheral clock enable */
__HAL_RCC_OSPI1_CLK_ENABLE();
/* Alternate GPIO enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* PE12 as OSPI_IO0 */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* PB0 as OSPI_IO1 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* PE14 as OSPI_IO2 */
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* PE15 as OSPI_IO3 */
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* PB10 as OSPI_CLK */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* PA2 as OSPI_NCS */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
これはNUCLEO-L552ZE-Qから出たQUAD-SPIの足に対応したものです。
OCTO-SPIなのに8本全部使わぬぇのかと思いでしょうがこれはNUCLEO
が引き出してる足がそうなってるからそれに従ったわけなのでー
↓お次はOCTO-SPIの設定です。
/**************************************************************************/
/*!
@brief Configure OCTO-SPI as Memory Mapped Mode.
Winbond W25Q128JVSIQ specific setting.
* Address size is 24bit(16MBytes).
* Initially sets QUAD-MODE(not need quadmode command).
* MAX 133MHz CLK.
* Support "XIP",thus suitable for MemoryMappedMode.
@param None.
@retval None.
*/
/**************************************************************************/
void Set_OSPI_MemoryMappedMode(void)
{
OSPI_RegularCmdTypeDef sCommand = {0};
OSPI_MemoryMappedTypeDef sMemMappedCfg = {0};
uint8_t reg_data =0;
/* Initialize OCTO-SPI I/O */
OSPI_IoInit_If();
/* Initialize OCTO-SPI */
hospi.Instance = OCTOSPI1;
hospi.Init.FifoThreshold = 1;
hospi.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE;
hospi.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON;
hospi.Init.DeviceSize = 24; /* 128Mbit=16MByte=2^24 W25Q128JVSIQ */
hospi.Init.ChipSelectHighTime = 2; /* 2ClockCycle(18nSec@110MHz) Need for W25Q128JVSIQ >10nSec@read */
hospi.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE;
hospi.Init.ClockMode = HAL_OSPI_CLOCK_MODE_0;
hospi.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED;
hospi.Init.ClockPrescaler = 2; /* 110MHzMAX/2 = 55MHz(MAX OSPI-CLK:90MHz) */
hospi.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE;
hospi.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE;
hospi.Init.ChipSelectBoundary = 0;
hospi.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED;
hospi.Init.Refresh = 0;
if (HAL_OSPI_Init(&hospi) != HAL_OK)
{
for(;;);
}
/* Enable Reset --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
sCommand.FlashId = HAL_OSPI_FLASH_ID_1;
sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDtrMode = HAL_OSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDtrMode = HAL_OSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytes = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesSize = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesDtrMode = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x66; /* Reset Enable W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_OSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_OSPI_DATA_NONE;
sCommand.DummyCycles = 0;
sCommand.NbData = 0;
if (HAL_OSPI_Command(&hospi, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
/* Reset Device --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
sCommand.FlashId = HAL_OSPI_FLASH_ID_1;
sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDtrMode = HAL_OSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDtrMode = HAL_OSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytes = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesSize = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesDtrMode = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x99; /* Reset W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_OSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_OSPI_DATA_NONE;
sCommand.DummyCycles = 0;
sCommand.NbData = 0;
if (HAL_OSPI_Command(&hospi, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
/* Enter Quad-SPI Mode --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
sCommand.FlashId = HAL_OSPI_FLASH_ID_1;
sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDtrMode = HAL_OSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDtrMode = HAL_OSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytes = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesSize = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesDtrMode = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x31; /* Set Status2 W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_OSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.DummyCycles = 0;
sCommand.NbData = 1;
reg_data = 0x02; /* Enable QuadI/O Mode */
if (HAL_OSPI_Command(&hospi, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
if (HAL_OSPI_Transmit(&hospi, ®_data, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
/* Enter MemoryMappedMode --------------------------- */
/* Read Commands */
sCommand.OperationType = HAL_OSPI_OPTYPE_READ_CFG;
sCommand.FlashId = HAL_OSPI_FLASH_ID_1;
sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDtrMode = HAL_OSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDtrMode = HAL_OSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_4_LINES;
sCommand.AlternateBytes = 0xFF; /* Need for Fast Read QUAD W25Q128JVSIQ */
sCommand.AlternateBytesSize = HAL_OSPI_ALTERNATE_BYTES_8_BITS;
sCommand.AlternateBytesDtrMode = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0xEB; /* Fast Read QUAD W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_OSPI_ADDRESS_4_LINES;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_OSPI_DATA_4_LINES;
sCommand.DummyCycles = 4; /* DUMMY 4Cycle for Fast Read QUAD W25Q128JVSIQ */
sCommand.NbData = 0;
if(HAL_OSPI_Command(&hospi, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
/* Write Commands */
sCommand.OperationType = HAL_OSPI_OPTYPE_WRITE_CFG;
sCommand.FlashId = HAL_OSPI_FLASH_ID_1;
sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDtrMode = HAL_OSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDtrMode = HAL_OSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytes = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesSize = HAL_OSPI_ALTERNATE_BYTES_NONE;
sCommand.AlternateBytesDtrMode = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x32; /* Page Write QUAD W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_OSPI_ADDRESS_1_LINE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_OSPI_DATA_4_LINES;
sCommand.DummyCycles = 0;
sCommand.NbData = 0;
if(HAL_OSPI_Command(&hospi, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}
/* Set OCTO-SPI as MemoryMappedMode */
sMemMappedCfg.TimeOutActivation = HAL_OSPI_TIMEOUT_COUNTER_DISABLE;
sMemMappedCfg.TimeOutPeriod = 0;
if(HAL_OSPI_MemoryMapped(&hospi, &sMemMappedCfg) != HAL_OK)
{
for(;;);
}
}
流れとしてはリセット有効->リセット->クアッドI/Oモード有効->
クアッド高速読み出しコマンド->クアッドセクタ書き込みコマンド
->HALのMemoryMappedMode移行関数呼び出し
となっております。
"クアッドI/Oモード有効"はW25Q128JVSIQ使うなら本来不要ですが
"念のため"突っ込んどきました…一応なくても動作します。
注意点ですがOCTO-SPIは書き込みもMemoryMappedModeが使用可能の為、
クアッド読み出しコマンドの次にセクタ書き込みコマンドの設定が
必須になります!!
なぜそうなっているかというとHALの関数内で書き込みコマンドの
設定まで要求してきやがるからです(別に今回書き込み要らんのに☠)
これも設定しないと先に進めません!
●動作してるところ
MemoryMappedModeで設定したアドレスはSTM32F7のでやった時と同じく
0x90000000からとなりますので過去のリソースが利用できます。
ねむいさんの使い方ではFONTX2ファイルのAnkの方を0x90000000、
Sjis-Kanjiフォントの方は0x90010000から参照しております。
Ankのファイルサイズはどんなにでかくとも65536byte以上にならない
だろうという打算でこのオフセット値を設定してます。
こんな感じでFONTX2(全角&半角)ファイルを仕込んだW25Q128JVSIQから
内蔵フラッシュと変わらない感覚でデータを読み出し表示ができました。
使用したフォントは小夏フォントを12ptのFONTX2に変換したものです。
STM32L5のOCTO-SPIは命令キャッシュだけでデータキャッシュは無い
ですが文字表示や操作感に特にもたついた感じはしてませんね。
デバッガで0x90000000番地を表示してみました。
MemoyMappedMode有効後は内蔵フラッシュと同じようにデバッガで
内部のデータを参照できてますね。
●ところでどうやってQSPI-ROMにデータ書くの?
…今回の記事はこれが最大の難関です…!
OpenOCDではDiscovery系のボードではI/Fの足もQSPI-ROMの品種も
決め打ちのためにQSPI-ROMの読み書きがサポートされていますが
NUCLEO系のやつは足は出ているもののその先に何がつながるかは
わからないのでサポートしているものは何にもありません。
したがってOpenOCDのmmwのコマンドでそれぞれのROMデバイス向けに
上で書いたコードを16進数並べてがむばって定義していくしかないの
ですがねむいさんはそんなことやる気力がないので…
Jtagkey2とSPI-ROM書き込みプログラム"Flashom"にてFONTX2データを
書き込むことにしました。
使用するフォントは12x12pxの小夏フォントです。
AnkはKONATHN12.FNTを、S-JisのKanjiはKONATZN12.FNTを使用します。
上記ファイルについて、私のL5プロジェクトの下記ディレクトリを
参照してください。
./lib/FONTX2/inc/fonts/KONATSU
※小夏フォントについてはTTFファイルで提供されております。
今回はあらかじめFONTX2コンバータでTTF->FONTX2形式に変換
して全角/半角とも使用しております。
まずはSPI-ROMの認識と読み出し。ちゃんと認識されているのを確認。
ついでに読み出したデータは後で使用するのでbin形式で保存します。
JtagKey2(で使用されているFT2232H)のSCKは最大30MHzのスピートなので
配線長はなるべく短くしましょう。5cm以下だとまず問題ないです。
残念ながらげたゲタ基板とNUCLEO基板を合体した状態ではまともに認識
出来なかったのでNUCLEOから5Vだけ拝借してW25Q128JVSIQと一対一で配線
する形になります。なお、CSはプルアップ必須なのですがねむいさんは
念のためCLK除くすべてのI/Oを22kohmでプルアップしてます。
一方CLKはSDMMCと同じく直列終端抵抗を忘れずに!!
ねむいさんはSDMMCと同じ33ohmを直列終端として使用してます。
お次は書き込み用バイナリの作成。
ねむいさんの知る限りではFlashromはアドレスをオフセット指定して
一部の領域だけを書き込むことができず、全領域一気に書き込みしないと
いけないので先に読み出したbinファイルを利用してそこにFONTX2ファイル
を上書きする形で配置しFONTX2を仕込んだROMファイルを作成します。
バイナリ編集プログラム"Stirling"を駆使して0x0000000にANKフォントを
0x00010000にKanjiフォントをそれぞれ上書きモードでコピペ配置します。
オフセットは1バイトでもずれないように注意してください。
Ankは配置されるとこんな感じ
Kanjiはこんなかんじになります。
Stirlingのビットマップ表示だとバイナリイメージはこんな感じです。
まだまだ一杯データ詰め込めますね。
そして書き込み。JtagKey2とFlashromを使用した際は通常のSPIで
書き込むことになりますがW25Q128JVSIQはセクタ書き込み対応なので
16MByteもある容量でも2分もかからず終わっちゃいます。
ねむいさんの公開しているプログラムでは一応作成済みのQSPI-ROM
イメージ(128QVFONT.bin)を添付して簡単な使用手順も書いてますので
酔狂な方はご参考に…。
まぁ私のぶろぐ普段から見られてる方は楽勝だと思います。
そんなわけでSTM32H5からやる予定だったOCTO-SPIでしたがL5でもうまく
使いこなすことができました!
L5でやりたいことは一通り全部やり切ったのでいい感じでSTM32H5へと
繋げて行きたいとおもいます!!
STM32L5を使ってみる2 -FatFsその他の実装-
●いつものやつを実装しよう
前回は144Pin版NUCLEO基板の下駄基板のビジュアルだけ紹介しましたが、
今回はそれを動かすためのファームウエアの要所を紹介していこうと思います。
ねむいさんの"いつもの"ことChaN師のFatFs実装を中心としております。
以下、STM32L5-NUCLEOのFatFs実装例のプロジェクトの要点を掻い摘んで
解説していきたいと思います。
●GCCビルドのためのCortex-M33向けオプション
先ずはmakefileを見ていただいたらお分かりと思いますがCortex-M33は
基本的にCortex-M4Fの進化版と思って頂けたらよいと思います。
GCCコンパイラに与えるCortex-M33コア向けオプションは以下になります。
-mcpu=cortex-m33 -mtune=cortex-m33 -mfix-cmse-cve-2021-35465
-mfix-cmse-cve-2021-35465とかいうのはArmv8-Mコアの脆弱性対策です。
2023年現在の最新のGCCコンパイラはデフォルトでONになっておりいちいち
入れる必要はないかもしれませんが念のため明示してます。
●基本の"き"、タイマーとUARTの実装
STM32に代表されるCortex-M系のコアはほぼすべてにSystickタイマーが
標準装備されております。こちらを1mSecウエイト用のタイマーとして、一方
マイコン周辺機器のタイマー機能を利用してuSecオーダーのタイマーを実装します。
今回はuSec用にはTIM5を利用しました。
以下systick.cより抜粋
/* Making MicroSecond-Order Timer uses general purpose timer! */
/* Enable timer clock */
USEC_TIMx_CLKEN();
/* calculate TIMx(2 or 5) Prescaler clock(APB1) */
if(RCC->CFGR & RCC_CFGR_PPRE1){
if((RCC->CFGR & RCC_CFGR_PPRE1) == RCC_HCLK_DIV2){
cal_usec_divide = SystemCoreClock/2; /* (HCLK(=SYSCLK)*2 */
}
else if((RCC->CFGR & RCC_CFGR_PPRE1) == RCC_HCLK_DIV4){
cal_usec_divide = SystemCoreClock/4; /* (HCLK(=SYSCLK)*4)*2 */
}
else if((RCC->CFGR & RCC_CFGR_PPRE1) == RCC_HCLK_DIV8){
cal_usec_divide = SystemCoreClock/8; /* (HCLK(=SYSCLK)*8)*2 */
}
else if((RCC->CFGR & RCC_CFGR_PPRE1) == RCC_HCLK_DIV16){
cal_usec_divide = SystemCoreClock/16; /* (HCLK(=SYSCLK)*16)*2 */
}
}
/* usec wait timer settings */
TimHandle.Instance = USEC_TIMx;
TimHandle.Init.Period = UINT32_MAX;
TimHandle.Init.Prescaler = ((cal_usec_divide)/USEC_INTERVAL) - 1;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
{
/* Capture error */
while (1);
}
USEC_TIMx->CR1 &= ~(TIM_CR1_UIFREMAP); /* Disable UIF Remap(Must Need!) */
HAL_TIM_Base_Start(&TimHandle);
最近のHALライブラリは"AutoReloadPreload"構造体が追加されているので
移植の際はご注意ください。また,UIFRemapビットにもご注意ください。
UARTについてはNUCLEO-L552ZE-Q基板上ではSTLinkのVCPとはLPUART
なるもので接続されております。一般のUARTとは多少違いがありますが
やることは同じです。LPUART1、GPIOG7,8をTX,RXとします。
一応LPUART以外のポートも使用できるようにしておりますので詳細は
uart.cupport.cを参照願います(丸投げ)
っとその前にLPUART1を使う際はLPUART1へのクロック供給のほかに
"HAL_PWREx_EnableVddIO2()"の実行も忘れないようにしてください。
またリングバッファによるノンブロッキング送受信なのはいつも通り
なのでスムーズにデータのやり取りができると思います。
●SDMMCドライバの実装・FatFsとの結合
STM32L5にはSDMMCがありますのでこれを実装しますが数年前STM32H7に
実装したSDMMCのドライバをスライド移植するだけのお手軽です☆
ぶっちゃけ今回の移植作業で一番楽でした。
一応使用上の注意なのですがSTM32H7と同じくSTM32L5のSDMMCはDDR
モードが使用可能ですがSDMMCに供給するクロックがバイパスされて
いないと使用不可となりますのでご注意ください。
ねむいさんの作例ではデフォルトではHSI48をSDMMCにバイパスなしで
供給しているためDDRで使用することができませんorz
もしDDR対応のeMMCをお持ちの方はコアクロックを110MHzから100MHzで
動かすとHSI48を使わずコアクロックをSDMMCのクロックにするよう
切り替えますので試してみてください。
STM32L5の最大動作クロックは110MHzなんですけどこういう制約が色々
あって100MHzで動かすほうがよっぽど有利なんですよね〜
100MHzとか昨今のマイコンでは遅い部類のクロックですがまぁ初代の
STM32F1とかのMAX72MHz動作と比べたら(消費電力も合間見ると)雲泥の
差ですがすっかり贅沢になってしまいましたね〜
STM32H7の実装でも実はひっそり最高速の480MHz対応してますが
こちらもいずれ紹介しようと思います。
●SPIとシリアル接続TFT-LCD
STM32L5のSPIモジュールも言ったって普通のSPIなので実装は容易
でした…が、DMAのほうはちょっと一癖ありました。
ねむいさんが知ってる従来のDMAではなくってDMAのチャネルがマルチ
プレクスされていて柔軟な設定ができるようになっており、逆に扱いに
苦労しましたがExampleを参考に何とかSPIでDMAを吐けるようになって
おります。以下にDMAMUXの設定を抜粋しておきます。
全体は./lib/display/mcu_depend/display_if_basis.cを参照のこと。
#ifdef USE_DISPLAY_DMA_TRANSFER
/* DMA controller clock enable */
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request LcdDmaHandle */
LcdDmaHandle.Instance = SPILCD_DMA_CHANNEL;
LcdDmaHandle.Init.Request = SPILCD_DMA_REQEST;
LcdDmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH;
LcdDmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
LcdDmaHandle.Init.MemInc = DMA_MINC_ENABLE;
LcdDmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
LcdDmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
LcdDmaHandle.Init.Mode = DMA_NORMAL;
LcdDmaHandle.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&LcdDmaHandle) != HAL_OK)
{
for(;;){
__NOP();
}
}
if (HAL_DMA_ConfigChannelAttributes(&LcdDmaHandle, DMA_CHANNEL_NPRIV) != HAL_OK)
{
for(;;){
__NOP();
}
}
__HAL_LINKDMA(&SpiHandle,hdmatx,LcdDmaHandle);
/* DMA interrupt init */
/* SPILCD_DMA_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SPILCD_DMA_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPILCD_DMA_IRQn);
#endif
また下駄基板ではST7789V2を使用したTFT-LCDモジュールを採用して
おりますがこちらのデータ線はSDIとSDOがマルチプレクスされたSDA
となっており、ST7789V2のデバイスIDを取得したいならばSTM32L5側で
SPIのMOSIとMISOを直結し読み出しの際にMISOの信号を正しく受け取る
MOSIをGPIOの入力にわざわざ切り替えてやる必要があります。
以下にそのコードを示します。
void Display_ChangeSDA_If(uint8_t sda_mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
if(sda_mode == TFT_SDA_READ){
/* Enable CTRL Line GPIO Settings */
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SDI);
GPIO_InitStructure.Pin = CTRL_SDI;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = 0;
HAL_GPIO_Init(DISPLAY_PORT_SDI, &GPIO_InitStructure);
}
else {
/* Enable CTRL Line GPIO Settings */
#if defined(USE_SOFTWARE_SPI)
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SDI);
GPIO_InitStructure.Pin = CTRL_SDI;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = 0;
HAL_GPIO_Init(DISPLAY_PORT_SDI, &GPIO_InitStructure);
#else
/* Connect SPI pins to Alternate Function */
/* Restore SPI MOSI pin configuration */
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SDI);
DISPLAY_PERIF_CLK(ENABLE);
GPIO_InitStructure.Pin = CTRL_SDI;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStructure.Alternate = SRC_SDI;
HAL_GPIO_Init(DISPLAY_PORT_SDI, &GPIO_InitStructure);
#endif
}
}
ちなみにST7789V2側のドライバはデフォルトでは上記の切り替えの
必要なくIDを決め打ちで返すようにしてますのでもの好きな方だけ
SDAマルチプレクス有効にして試してみてください。
SDAマルチプレクスの際はSTM32側のMOSIとMISOをショートしてください。
ST7789V2からIDを読み出す設定でビルドしてデバッガで追っかけてみると
こんな感じにSDAからST7789V2のID(RDID2(0xDB)で0x85が返る)が
読まれてくるのがわかります。
SPI接続で動くモジュールはこんな感じでピン数節約のためにSDIとSDOが
マルチプレクスされてることもあるので覚えておいて損はないでしょう。
というわけでいつものChaN師のファイラー起動からの…
海で働いてるいないさんのpng形式のイラスツをlibpngでデコード!
(LCDのサイズ小さいので画像の一部しか表示できませんが)
libpngのほかにbmp形式はもちろんlibjpegやgiflibでjpg、gif形式
ファイルも表示できますがSTM32L5シリーズはフラッシュメモリ容量が
512kByteしかないのでフォントファイルを乗せつつ画像デコード用の
ライブラリ全部乗せは容量上非常に苦しいです。
そんなときのために容量を食うフォントファイルは外付けのSPI-ROM
にデータを配置してアクセスしたいところですが…
こういう用途に便利なOCTO-SPIという機能がありますが解説が長く
なりそうなので次回に続きます…!
-
免責・連絡先は↑のリンクを
↓SNSもやってます↓
powered by まめわざ- ARM/STM32 (117)
- OpenOCD (27)
- ARM/NxP (34)
- ARM/Cypress (5)
- ARM/Others (3)
- ARM/Raspi (1)
- AVR (13)
- FPGA (4)
- GPS/GNSS (19)
- MISC (80)
- STM8 (2)
- Wirelessなアレ (16)
- おきぱ (1)
- ブラウザベンチマーク (28)
- 日本の自然歩道 (25)
- STM32U0はぢめました
⇒ ねむい (08/07) - STM32U0はぢめました
⇒ ひかわ (07/28) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ ねむい (05/17) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ どじょりん (05/16) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ どじょりん (05/16) - いろいろ試す61(と今年の反省会)
⇒ ねむい (01/02) - いろいろ試す61(と今年の反省会)
⇒ ひかわ (01/02) - いろいろ試す61(と今年の反省会)
⇒ ひかわ (01/01) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ ねむい (12/31) - STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
⇒ ひかわ (12/31)
- 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 (7)
- May 2009 (14)
- January 1970 (1)
Copyright(C) B-Blog project All rights reserved.