AVR小ネタたち

今回はAVRマイコンにまつわることをいろいろ試していきます。


●avr-gccが14になっていた
月日の流れは速いものでGCC14が少し前にリリースされていましたが
avr-gccにも適用されております。

Windows向けのものはzakkemble氏の私家ビルド版が利用可能なもの
となっております。正確なバージョンはGCC14.1.0とのこと。

zakkemble氏のビルドはAVR-LibCも2.2.0にアップしており最新の
AVRにも対応しております。もうmicrochipのサイトからDFP拾ってきて
あれやこれややらなくてよくなったので
めっちゃ楽です!

今回のGCC14の目玉はAVR64DD28とかのAVR-Dx,AVR-Ex系でフラッシュ
メモリ領域が64kBを超える品種においてPROGMEMなしで32kByteまで
constデータを配置できるようになったことです


以前ねむいさんはリンカスクリプトを弄って32kByte分のrodataを
データ領域にマッピングする技
をやっていましたがGCC14はデフォルトで
そうするように変更されております。

これでAVR64DD28やAVR128DB28とかでも32kByteまでならPROGMEM
無しでもconstデータを置きまくれることになります…!やった!

しかし…

ねむいさんが過去に公開しているAVR-Dx向けのプロジェクトは無理くそ
リンカスクリプトをいじってマッピングしている奴とぶつかってしまい
GCC14でまともにビルドできなくなってしまいました。
そこで今回後方互換性を保ちつつGCC14でもビルドできるようにmakefile
に仕込みを行いました。

#============================================================================
# Define programs and commands.
SHELL = sh
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
REMOVEDIR = rm -rf
COPY = cp
WINSHELL = cmd
# Detect GCC version
GCCVERSION:= $(shell $(CC) -dumpversion)
dot:=.
empty:=
space:= $(empty) $(empty)
GCCVERSION:= $(subst $(dot),$(space), $(GCCVERSION))
GCCMAJOR:= $(word 1, $(GCCVERSION))
GCCMINOR:= $(word 2, $(GCCVERSION))


こういやってGCCのメジャーバージョンをmakefile内で取得して…

# If GCC 14+ Add -mrodata-in-ram to get backward compatibility on AVR uC
ifeq (1,$(shell expr $(GCCMAJOR) ¥>= 14))
ifneq ($(MCU),avr32da28)
ifeq ($(USE_SECTOR_MIRROR),SECTOR_MIRRORED)
CFLAGS += -mrodata-in-ram
endif
endif
endif

GCC14でビルドする場合はCFLAGSにrodataのマッピングを無効化する
"-mrodata-in-ram"を付与してGCC13以前と同じようにビルドするように
しております…
せっかく便利な機能ができたのにわざわざ以前の状態に戻すような愚行
をしちゃってますがまぁ時代の変遷の仇花ということで…

ちなみにmakefile中のGCCバージョン取得はこちらの方の記事を参考に
させていただきました!

まぁ新規にプロジェクト作る際は"-mrodata-in-ram"の仕込みは不要な
ようにしたいと思います。
そんなわけでAVR-Dx向けのプロジェクトもGCC14に対応してみましたので
興味ある方は覗いてみてください。

またGCC14では"-fanalyzer"なるオプションも追加されています。
これを有効にすると以下のように問題のある部分を解説してくれます。
Compiling: src/xprintf_avr.c
avr-gcc -c -mmcu=avr128da48 -std=gnu99 -g -gdwarf-2 -Os -mrelax -mcall-prologues -fno-strict-aliasing -funsigned-char -funsigned-bitfields -ffunction-sections -fdata-sections -fno-common -fanalyzer -fno-split-wide-types -fno-tree-scev-cprop -fpack-struct -fshort-enums -Wall -Wstrict-prototypes --param=min-pagesize=0 -Wa,-adhlns=src/xprintf_avr.lst -DCURIOSITY_NANO -DMPU_SUBMODEL=¥"avr128da48¥" -DAPP_VERSION=¥"W.I.P.¥" -DF_CPU=24000000UL -DSECTOR_SEL=UL -I. -I ./inc -I ./lib/ff -MD src/xprintf_avr.c -o src/xprintf_avr.o
src/xprintf_avr.c: In function 'xvprintf':
src/xprintf_avr.c:131:27: warning: 'va_arg' expected 'char *' but received 'long unsigned int' for variadic argument 1 of 'arp' [CWE-686] [-Wanalyzer-va-arg-type-mismatch]
131 | p = va_arg(arp, char*);
| ^
'put_dump': events 1-2
|
| 247 | void put_dump (
| | ^~~~~~~~
| | |
| | (1) entry to 'put_dump'
|......
| 260 | xprintf(PFSTR("%08lX "), addr); /* address */
| | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| | |
| | (2) calling 'xprintf' from 'put_dump' with 1 variadic argument
|
+--> 'xprintf': events 3-4
|
| 187 | void xprintf ( /* Put a formatted string to the default device */
| | ^~~~~~~
| | |
| | (3) entry to 'xprintf'
|......
| 196 | xvprintf(fmt, arp);
| | ~~~~~~~~~~~~~~~~~~
| | |
| | (4) calling 'xvprintf' from 'xprintf'
|
+--> 'xvprintf': events 5-8
|
| 96 | void xvprintf (
| | ^~~~~~~~
| | |
| | (5) entry to 'xvprintf'
|......
| 108 | if (!c) break; /* End of format? */
| | ~
| | |
| | (6) following 'false' branch (when '__result != 0')...
| 109 | if (c != '%') { /* Pass through it if not a % sequense */
| | ~
| | |
| | (7) ...to here
| | (8) following 'false' branch (when '__result == 37')...
|
'xvprintf': event 9
|
| 113 | c = PGM_READ_BYTE(fmt++); /* Get first char of the sequense */
src/xprintf_avr.c:113:21: note: in expansion of macro 'PGM_READ_BYTE'
| 113 | c = PGM_READ_BYTE(fmt++); /* Get first char of the sequense */
| | ^~~~~~~~~~~~~
|
'xvprintf': event 10
|
| 114 | if (c == '0') { /* Flag: '0' padded */
| | ^
| | |
| | (10) following 'true' branch (when '__result == 48')...
|
'xvprintf': event 11
|
| 115 | f = 1; c = PGM_READ_BYTE(fmt++);
src/xprintf_avr.c:115:36: note: in expansion of macro 'PGM_READ_BYTE'
| 115 | f = 1; c = PGM_READ_BYTE(fmt++);
| | ^~~~~~~~~~~~~
|
'xvprintf': events 12-15
|
| 121 | for (w = 0; c >= '0' && c <= '9'; c = PGM_READ_BYTE(fmt++)) /* Minimum width */
| | ~~~~~~~~~^~~~~~~~~~~
| | |
| | (12) following 'false' branch...
| 122 | w = w * 10 + c - '0';
| 123 | if (c == 'l' || c == 'L') { /* Prefix: Size is long int */
| | ~
| | |
| | (13) ...to here
|......
| 126 | if (!c) break; /* End of format? */
| | ~
| | |
| | (14) following 'false' branch (when 'c != 0')...
|......
| 129 | switch (d) { /* Type is... */
| | ~~~~~~
| | |
| | (15) ...to here
|
'xvprintf': event 16
|
| 131 | p = va_arg(arp, char*);
| | ^
| | |
| | (16) 'va_arg' expected 'char *' but received 'long unsigned int' for variadic argument 1 of 'arp'
|

とこんな感じに問題の個所を詳細に解説してくれます。


●AVR-EAシリーズを使ってみる

STM32U0マイコン買ったついでに買ったのですがAVR64EA28も買ってみました。
AVR-Dxシリーズとの最大の違いは内部レギュレータが無いので電圧により動作
可能なクロック上限(MAX20MHz)があることです。
どちらかというとAVRTiny-0/1/2シリーズに近いかもしれませんね。

そんなわけでAVR64EA28を使用したプロジェクトもサクっと作ってみましたので
紹介します。1秒ごとにLEDを点滅させつつ内部温度計の現在温度をUARTで送出
するいたってシンプルな構成です。まぁ基本の基本ですね。


ところでAVR-EAとAVR-DDはヒューズ設定でUPDIやRESETピンをGPIOとして使用
できますがGPIO化した状態でUPDIを活性化させるためにはRESETに高電圧(HV)を
印加する必要があります。なおRESETピンの絶対最大定格は9Vのため、ATTINY202
の時みたいに気軽に12Vを印可してしまうと気軽ににます・・・(AVR-DDも同じ)。

じゃぁ何V加えたらいいのかという話ですがデータシートのどこを見ても私の
脳みそでは適切な電圧はわからず(もちろん9Vもあかんと思います)、UPDIと
RESETピンは無理にGPIOとして使用するのはやめようと思いまし、た!

AVR-EA/EBシリーズに詳しいaskn氏はHVを9Vよりちょっと低い8.2V
されているようですね。

20240624追:
AVR64DD28のデータシートが2024年2月付で更新されているのに今更
気づいたのですがRESET高電圧の定義がようやく明確に記されておりました…。

Typ値7.5Vですって…!!
何故こんな重要な情報を何年も公表せず放置していやがったのでしょうか(ピキピキ
AVR-Exシリーズも多分同じ感じでしょうからRESET高電圧は7.5Vくらいを
印加すればうまくいくと思いますがやっぱりUPDIとRESETピンは無理に
GPIOとして使用するのはやめようと思いまし、た!

20240624追:
AVR-EBシリーズもRESET高電圧のTyp値が7.5Vでした…(2023年秋更新)
AVR-EAのデータシートもさっさと更新してほしいんですけぉ!!


avrdude v7.3リリース!!
☝で書いたAVR-EAシリーズのプログラムができます!!!(HV除く)
最新のパッチを当てたWindows用バイナリを公開しておりますので
どしどしご利用ください!!!!!



●avr-gccのdelay関数の変遷
avr-gccではmSec,uSec単位を待つのに便利な_delay_ms(),_delay_us()
という非常に便利な関数があります。おそらくもっともポピュラーな
関数ですがこの関数もいろんな変遷をたどっています。

2005年くらい
_delay_ms()は262.14mSec/F_CPU(MHz)まで設定可能。
引数はdoubleなので実数も可能。
F_CPU=8000000(8MHz)の場合、32.7675mSecまでは保証される。

_delay_us()は768uSec/F_CPU(MHz)まで設定可能。
引数はdoubleなので実数も可能。


2007年くらい
_delay_ms()は262.14mSec/F_CPU(MHz)まで設定可能
それを超えると1/10mSecに精度が落ちるがF_CPUの設定にかかわらず
6.5535Secまで設定可能。

_delay_us()は768uSec/F_CPU(MHz)まで設定可能。
それを超えると_delay_ms()が呼び出される。

2010年前半くらい
__builtin_avr_delay_cycles()が追加された。
__HAS_DELAY_CYCLEを1にして最適化オプションを-O1以上にすると
__builtin_avr_delay_cycles()が呼び出される。
この場合_delay_ms()は4294967.295mSec/F_CPU(MHz)まで設定が可能になる。
F_CPU=8000000(8MHz)の場合、536870.911875mSecまでは保証される。
それ以上の値を設定した場合0mSecになる。
_delay_us()も4294967.295uSec/F_CPU(MHz)まで設定が可能になる。
それ以上の値を設定した場合0uSecになる。

また、__HAS_DELAY_CYCLEを0にするもしくは最適化オプションを-O0に
した場合は△汎韻犬砲覆襦

13年位前にXMEGA触ったときにこれに引っかかってしまってましたね。
※今では不要な対処法ですが

2010年後半くらい
コンパイル時に__DELAY_ROUND_DOWN__,__DELAY_ROUND_CLOSEST__の
スイッチ追加。またの機能は__DELAY_BACKWARD_COMPATIBLE__を
コンパイラに渡すと△汎韻犬砲覆襪茲Δ吠儿后

2011年くらい
い鵬辰┐"-fno-hosted"と"-ffreestanding"がコンパイルオプションに
あった場合△汎韻犬砲覆襪茲Δ吠儿后


2013年くらい
△_delay_us()の_delay_ms()を呼び出す仕組みにエンバグされてしまう!

2020年くらい
Δ離丱阿茲Δ笋修正される(が、GCC13まで取り込まれていなかった)


なんと今回のAVR-GCC14くらいまで10年くらいバグが放置されていたの
ですが特殊な使い方をしない限りは誰も踏まなかったと思いますので
たぶん大丈夫だと思いました!
ねむいさんはおもいくそ踏んでしまいました!!1!!!

ちなみに具体的なコードの変遷はこちらで見ることができます。



というわけでごちゃごちゃと書きましたが2024年現在GCC14を使用する
__builtin_avr_delay_cycles()呼び出す方式でdelayしたい場合は…

・"-fno-hosted"と"-ffreestanding"をコンパイルオプションに加えない
・最適化オプションは-O1以上にする

でおっけーです。簡単に見える関数もしっかり中身理解してないと
思わぬところでハマってしまうというお話でした。


今回の修正を加えたねむいさんのAVR系のプロジェクトを更新しましたので
興味がある方はご参考に…
*ATTINY202
*AVR-Dx
*AVR-EA
*ATXMEGA128A1/A1U

STM32H5を使ってみる6 -秋月販売記念!SDMMCとFatFsでSDカードを使う-

秋月さんからついにNUCLEO-H563ZIが販売となりました!!!
これで皆様も気軽にSTM32H5をいじることができるでしょう〜!

ちなみにSTM32H5に対応したOpenOCDはねむいさんのぶろぐで公開
しておりますので
どしどしご利用ください!!!!
そうだね宣伝だね!!

あ、それと買った人チップリビジョンちゃんと確認してくださいね…
Zならはずれです…


●STM32H5のSDMMC
そんなわけで今回はSTM32H5のSDMMC機能を利用してFatFsを移植し
SDカードやMMC/eMMCからデータの読み書きを行うところまでを
紹介します。


STM32H5のSDMMCにはSTM32H7と同じくIDMAというGPDMAから独立した
DMAが存在しており、GPDMAと干渉することなく運用が可能です。


また、STM32H5は外部メモリ(FSMC/OCTO-SPI)にしかDキャッシュが
かかわってこないためキャッシュコントロールの面倒さもありません。

●STM32H5では公式にFatFsの移植例がないが…
STM32H5以降はMicrosoft Azure RTOSに入れ込んでおりFatFsではなく
FILEXなるソフトウエアライブラリに置き変わっておりました。

ねむいさんは慣れ親しんだFatFs以外の選択肢はないのでFILEXはガン
無視のザ・シカトで移植に挑みました。


移植についてですがSTM32F7のころから慣れ親しんだいわば"枯れた"
ペリフェラルなのでHALの構造も酷似しているためSTM32H7やSTM32L5の
移植例からI/Oとクロック設定以外はほぼスライド移植で簡単にできて
しまいました!

f_readの結合関数SD_read()はこんな感じです。

DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;
uint32_t timer = SysTick->VAL + SD_DATATIMEOUT;

/* first ensure the SDCard is ready for a new operation */
while((SD_GetCardState() == SD_TRANSFER_BUSY))
{
if(timer < SysTick->VAL)
return RES_NOTRDY;
}

#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)

if((uintptr_t)buff & 0x3) /* Check 4Byte Alignment */
{ /* Unaligned Buffer Address Case (Slower) */
for (unsigned int secNum = 0; secNum < count ; secNum++){

if(SD_ReadBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1)!= MSD_OK)
{
MSG_PRINTF("Read error on unaligned buffer¥n");
res = RES_ERROR;
}

memcpy(buff+secNum*SECTOR_SIZE, dmabuf, SECTOR_SIZE);
}
} else {
/* Aligned Buffer Address Case (Faster) */
if(SD_ReadBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Read error on DMA¥n");
res = RES_ERROR;
}
}
#else
if(SD_ReadBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Read error on polling¥n");
res = RES_ERROR;
}
#endif
return res;
}


f_writeの結合関数SD_write()はこんな感じです。
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
uint32_t timer;


#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)
if((uintptr_t)buff & 0x3) /* Check 4Byte Alignment */
{ /* Unaligned Buffer Address Case (Slower) */
for (unsigned int secNum = 0; secNum < count; secNum++){

memcpy(dmabuf, buff+(SECTOR_SIZE*secNum), SECTOR_SIZE);

if(SD_WriteBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1) != MSD_OK)
{
MSG_PRINTF("Write error on unaligned buffer¥n");
res = RES_ERROR;
}
}
} else {
if(SD_WriteBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Write error on DMA¥n");
res = RES_ERROR;
}
}
#else
if(SD_WriteBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Write error on polling¥n");
res = RES_ERROR;
}

#endif

/* ensure the SDCard is ready for a next operation */
timer = SysTick->VAL + SD_DATATIMEOUT;
res = RES_ERROR; /* Timeout */
/* block until SDIO IP is ready or a timeout occur */
while(timer > SysTick->VAL)
{
if(SD_GetCardState() == SD_TRANSFER_OK)
{
res = RES_OK;
break;
}
}

return res;
}

キャッシュコントロールがないのですっきりですね。
全体的なソースの詳細についてはねむいさんのSTM32H5プロジェクト内の
下記ディレクトリのファイルを参照してください。
./lib/ff/sdmmc_stm32h5.c
./lib/ff/sdmmc_stm32h5.h


●直線リードのパフォーマンスはどんな感じか

STM32H5の最大クロックは250MHz取れるのでSDMMCのクロック周波数も
250/5=50MHzとハイスピードの規格いっぱいの50MHzでぶん回すことが
可能です。

直線の読み出しスピードは19MBytes/Sec出てますね。
こんだけあれば十分でしょう。


ちなみにeMMCでは3.3VでDDRモードに対応しており、STM32H5のSDMMCも
DDRに対応しているのでSDカードを超えた早い読み出しが可能です!!

●SMART取得機能も搭載

ねむいさんはSMARTが取れる産業用/工業用SDカードが大好きですが
STM32H5でももちろんSMARTの取得を可能としてます!
試される際は上記のコードをコメントアウトしてくださいね〜

SMART対応のSDカードについては下記記事もご参照ください!
DELKIN製SDカード
TRANSCEND製microSDカード



そんなわけでかなりやっけつ気味にSTM32H5へのSDMMCを使ったFatFs
の移植を紹介しましたがそれらを組み込んだ総合的な成果物は昨年
からすでにおきぱで公開しておりますので
どしどし参考にしてください!

STM32H5を使ってみる5 -普通のSPIをDMAと交えて使う-

20240516追:
秋月さんよりNUCLEO-H563ZI販売開始です!!
20240516追:



初めてSTM32H5を触った時も述べましたが、L5と比べてSPIやDMAの扱いが
ちょっと癖があって違います。今回はその使い方の一例を紹介します。
なお、ねむいさんのSTM32H5のプロジェクトではSPIをシリアル接続の
TFT-LCD(ST7789V2コントローラIC)
で使用しております。


●コード解説(基本設定編)
以下はねむいさんのSTM32H563ZI-NUCLEO向けのプロジェクト中
./lib/display/mcu_depend/src/display_if_basis.cを解説していきます。
./lib/display/mcu_depend/inc/display_if_basis.hも参照のこと。

Display_IoInit_If()内でハードウエアSPIからDMAの設定までやっちゃって
ますが少しずつ分解していきます。

まずはI/Oの設定です。

	GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

/* Enable CTRL line GPIO settings */
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_RES);
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_CS);
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_DC);
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SCK);
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SDI);
DISPLAY_GPIOCLK_EN(DISPLAY_CLK_SDO);

GPIO_InitStructure.Pin = CTRL_RES;
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_RES, &GPIO_InitStructure);

GPIO_InitStructure.Pin = CTRL_CS;
HAL_GPIO_Init(DISPLAY_PORT_CS, &GPIO_InitStructure);
GPIO_InitStructure.Pin = CTRL_DC;
HAL_GPIO_Init(DISPLAY_PORT_DC, &GPIO_InitStructure);

/* Set peripheral clock */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI1;
PeriphClkInitStruct.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PLL1Q;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
for(;;){
__NOP();
}
}
DISPLAY_PERIF_CLK(ENABLE);

/* Connect SPI pins to aletenate function */
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

/* SPI SCK pin configuration */
GPIO_InitStructure.Pin = CTRL_SCK;
GPIO_InitStructure.Alternate = SRC_SCK;
HAL_GPIO_Init(DISPLAY_PORT_SCK, &GPIO_InitStructure);

/* SPI MOSI pin configuration */
GPIO_InitStructure.Pin = CTRL_SDI;
GPIO_InitStructure.Alternate = SRC_SDI;
HAL_GPIO_Init(DISPLAY_PORT_SDI, &GPIO_InitStructure);

/* SPI MISO pin configuration */
GPIO_InitStructure.Pin = CTRL_SDO;
GPIO_InitStructure.Alternate = SRC_SDO;
HAL_GPIO_Init(DISPLAY_PORT_SDO, &GPIO_InitStructure);

SCK,MOSI,MISOはAlternateFunctionとして、RESET・CS・DCはGPIOとして
設定します。

お次はSPIモジュールの設定です。
	/* SPI configuration */
SpiHandle.Instance = LCD_SPI;
SpiHandle.Init.Mode = SPI_MODE_MASTER;
SpiHandle.Init.Direction = SPI_DIRECTION_2LINES;
SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
SpiHandle.Init.CLKPolarity = SPI_POLARITY_HIGH;
SpiHandle.Init.CLKPhase = SPI_PHASE_2EDGE;
SpiHandle.Init.NSS = SPI_NSS_SOFT;
SpiHandle.Init.BaudRatePrescaler = SPI_BaudPrescale;
SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
SpiHandle.Init.TIMode = SPI_TIMODE_DISABLE;
SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
SpiHandle.Init.CRCPolynomial = 0x7;
SpiHandle.Init.CRCLength = SPI_CRC_LENGTH_8BIT;
SpiHandle.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
SpiHandle.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
SpiHandle.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
SpiHandle.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
SpiHandle.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
SpiHandle.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
SpiHandle.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* avoid glitch */
SpiHandle.Init.IOSwap = SPI_IO_SWAP_DISABLE;
SpiHandle.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
SpiHandle.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
if(HAL_SPI_Init(&SpiHandle) != HAL_OK)
{
/* Capture error */
while (1);
}

/* Enable SPI module */
LCD_SPI->CR1 |= SPI_CR1_SPE;
LCD_SPI->CR1 |= SPI_CR1_CSTART;

HAL_SPI_Init()が完了したらCR1レジスタのSPEとCSTARTビットを1にして
SPIモジュールをスタート状態にさせるのがミソです。

設定中で
SpiHandle.Init.BaudRatePrescaler 		= SPI_BaudPrescale;

ですが
#define SPI_BaudPrescale	SPI_BAUDRATEPRESCALER_4 /* PLL1Q CLK 100MHz/4 = 25MHz */

とdefineしております。SPIのクロックは25MHzとしてます。
ST7789V2の許容クロックを大幅に逸脱してる気がしますが気のせいです!
20240502追:
ST7789V2のSCLのクロック最大値ちゃんと調べたらなんと60MHzでした!!!
全然余裕でしたすみません!!!!!
20240502追:


そしてDMAの設定です…これが一番苦労しました。
	/* DMA controller clock enable */
__HAL_RCC_GPDMA1_CLK_ENABLE();

/* DMA interrupt init */
/* SPILCD_DMA_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SPILCD_DMA_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(SPILCD_DMA_IRQn);

/* Configure DMA request LcdDmaHandle */
LcdDmaHandle.Instance = SPILCD_DMA_CHANNEL;
LcdDmaHandle.Init.Request = SPILCD_DMA_REQEST;
LcdDmaHandle.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
LcdDmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH;
LcdDmaHandle.Init.SrcInc = DMA_SINC_INCREMENTED;
LcdDmaHandle.Init.DestInc = DMA_DINC_FIXED;
LcdDmaHandle.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
LcdDmaHandle.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
LcdDmaHandle.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
LcdDmaHandle.Init.SrcBurstLength = 1;
LcdDmaHandle.Init.DestBurstLength = 1;
LcdDmaHandle.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
LcdDmaHandle.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
LcdDmaHandle.Init.Mode = DMA_NORMAL;
if (HAL_DMA_Init(&LcdDmaHandle) != HAL_OK)
{
for(;;){
__NOP();
}
}
__HAL_LINKDMA(&SpiHandle,hdmatx,LcdDmaHandle);

if (HAL_DMA_ConfigChannelAttributes(&LcdDmaHandle, DMA_CHANNEL_NPRIV) != HAL_OK)
{
for(;;){
__NOP();
}
}

/* SPI interrupt init */
/* SPILCD_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SPILCD_IRQn, 3, 1);
HAL_NVIC_EnableIRQ(SPILCD_IRQn);

今回はTFT-LCDに向けてひたすらデータをバイト単位でぶん投げるので
    LcdDmaHandle.Init.Direction 			= DMA_MEMORY_TO_PERIPH;
LcdDmaHandle.Init.SrcInc = DMA_SINC_INCREMENTED;
LcdDmaHandle.Init.DestInc = DMA_DINC_FIXED;
LcdDmaHandle.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
LcdDmaHandle.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;

の部分が重要です。

そしてなんでかDMAのほかにSPIの割り込みも要求してくるので
DMAの設定の最後にSPIの割り込みも必ず有効にします。

●コード解説(基本データのやり取り編)
SPI接続のTFT-LCDの基本は8bitのデータをやり取りします。
で、STM32H5では8bitデータのやり取りするのに素直にTDRにデータを
放り込んでもまともに送出されないのでちょっと細工する必要があります。

まずは8bit単位でデータをやり取りするためのdefine
#define SPIx_TDR8			*(__IO uint8_t *) ((uint32_t)LCD_SPI+0x20)
#define SPIx_RDR8 *(__IO uint8_t *) ((uint32_t)LCD_SPI+0x30)


☝を利用した送信関数はこちら
inline void SendSPI(uint8_t dat)
{
/* Send byte through the SPI peripheral */
SPIx_TDR8 = dat;

/* Wait to receive a byte */
while (!(LCD_SPI->SR & (SPI_FLAG_RXP)));

/* Drain Rx FIFO */
SPIx_RDR8;
}

inline void SendSPI16(uint16_t dat)
{
SendSPI((uint8_t)(dat>>8));

SendSPI((uint8_t)dat);
}

STM32CubeH5のHALドライバの送信関数は異常なまでにオーバーヘッドが大きい
ですが中身を分解して簡略化すると送信はここまでシンプルになります。

さらにST7789V2向けには上記関数を利用してCMD,DATA,GRAM書き込み用に拵えます。
/**************************************************************************/
/*!
Write LCD Command.
*/
/**************************************************************************/
inline void ST7789V2_wr_cmd(uint8_t cmd)
{
ST7789V2_DC_CLR(); /* DC=L */
DISPLAY_ASSART_CS(); /* CS=L */

SendSPI(cmd);

DISPLAY_NEGATE_CS(); /* CS=H */
ST7789V2_DC_SET(); /* DC=H */
}

/**************************************************************************/
/*!
Write LCD Data and GRAM.
*/
/**************************************************************************/
inline void ST7789V2_wr_dat(uint8_t dat)
{
DISPLAY_ASSART_CS(); /* CS=L */

SendSPI(dat);

DISPLAY_NEGATE_CS(); /* CS=H */
}

/**************************************************************************/
/*!
Write LCD GRAM.
*/
/**************************************************************************/
inline void ST7789V2_wr_gram(uint16_t gram)
{
DISPLAY_ASSART_CS(); /* CS=L */

SendSPI16(gram);

DISPLAY_NEGATE_CS(); /* CS=H */
}



一応Readも実装しておりますが以前も解説しておりますので今回は割愛します。


●コード解説(DMAでぶん投げる編)
これが一番難しいです…HALドライバの作例はLinkListを使用した複雑な
DMA転送しかコード例がなくてシンプルなDMAのコード例がなく作りこみに
非常に苦労しました…が、何とかなりました。

まずはHALライブラリのDMA,SPIのIRQハンドラを定義します。
/* DMA IRQ Handler */
void SPILCD_DMA_HANDLER(void)
{
HAL_DMA_IRQHandler(&LcdDmaHandle);
}
/* SPI IRQ Handler */
void SPILCD_IRQHandler(void)
{
HAL_SPI_IRQHandler(&SpiHandle);
}


そんでもって今回はSend専用なのでSPIの割り込みでTx完了のコールバック
関数を定義します。
/* SPI-DMA IRQ Callback */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == LCD_SPI){
wTransferState = TRANSFER_COMPLETE;
}
}

STM32L5の場合はSPIの割り込みが必要なかったのですが、H5では割り込み
必須になっていやがります…!なお、STM32H7も同じくDMA転送にSPIの
割り込み設定が必須です
(使ってなかったから今まで知らんかった…)。

実際のDMA転送関数はこちらになります。
/* DMA transaction */
void lcd_dma_transfer(const uint8_t *buff,unsigned int count)
{
/* Disable SPI to change register */
LCD_SPI->CR1 &= ~(SPI_CR1_SPE);

/* Start DMA */
if (HAL_SPI_Transmit_DMA(&SpiHandle, buff, count) != HAL_OK)
{
for(;;){
__NOP();
}
}

/* Wait transfer complete */
while (wTransferState == TRANSFER_WAIT) {};

/* Restore transfer state */
wTransferState = TRANSFER_WAIT;

/* Restore nomal transfer settings */
LCD_SPI->CR2 = 0; /* Restore TSIZE */
LCD_SPI->CFG2 &= ~(SPI_CFG2_COMM_Msk); /* Restore FULL duplex mode */
LCD_SPI->CR1 |= SPI_CR1_SPE; /* Re-enable SPI module */
LCD_SPI->CR1 |= SPI_CR1_CSTART; /* Re-start SPI Trasfer ready */
}

転送の手順は
SPIモジュールをDisable
HAL_SPI_Transmit_DMAを実行
SPIのコールバック関数が転送完了を捕まえるまで待つ
ぜ,療樵の準備を設定
となります。

特にい硫宍コードに相当する後処理は超重要です!!
/* Restore nomal transfer settings */
LCD_SPI->CR2 = 0; /* Restore TSIZE */
LCD_SPI->CFG2 &= ~(SPI_CFG2_COMM_Msk); /* Restore FULL duplex mode */
LCD_SPI->CR1 |= SPI_CR1_SPE; /* Re-enable SPI module */
LCD_SPI->CR1 |= SPI_CR1_CSTART; /* Re-start SPI Trasfer ready */

これをやっておかないと通常転送ができなくなるので必ず
後処理はは忘れずに!!どれが欠けても動かなくなります。





こんなわけでかなり癖があるSTM32H5のSPIをDMAと交えて使用してみました。
今回は8bitSPI接続のTFT-LCD接続に特化したコードの紹介となりましたが、
繋がるSPIデバイスの特性よって設定はかなり変わりますのでそれぞれで
試行錯誤が必要となるでしょう…
…正直OCTO-SPIやSDMMCとかより動かすのしんどかった…。

STM32H5を使ってみる4 -STM32L5の時みたいにOCTO-SPIを使ってみる-

20240516追:
秋月さんよりNUCLEO-H563ZI販売開始です!!
20240516追:



つい最近、STM32H7シリーズにSTM32H7R/Sというかなり尖った品種
追加されましたが思い出したようにSTM32H5を触っていこうと思います。

20240321追:
STM32U0シリーズなんてのも出現してました…!!
20240321追:


今回もSTM32H563ZI-NUCLEOを使用したFatFsの移植デモのソースコード
使用して解説していきます。


●STM32L5から進化したSTM32H5のOCTO-SPI
STM32H5のOCTO-SPIはポピュラーなQUADSPI-ROMだけではなく、最大
16bitなクロックドシリアルROM/PSRAMなんかも接続可能で、もちろん
メモリマップドなアクセスが可能となっております。
シリアルなのに16bitもパラレルってなんなんだよと常に感じてますが
時代は常に進化していっているなと同じく感じております(混乱)。
sirius506さんはそんなOCTO-SPIを完全に使いこなしDOOMというゲームを
移植されており
、ほんとに感心してしまいました…

ねむいさんは組み込み系界隈の中では雑魚キャラに過ぎないのでそんな
高度なことはできず、基本中の基本のQSPI-ROMを接続してメモリ
マップドモードで読み出し専用で動かすことからやってきます。

OCTO-SPIについてはST公式のAN5050もしっかり熟読してください。



●STM32H5のOCTO-SPIをメモリマップドモードで動かすコード
移植に当たってはSTM32L5のやつをそのままスライド移植…というわけには
簡単にいかず、L5のOCTO-SPIのコードから少し改変が必要でした。



その前に、STM32H563ZI-NUCLEOでOCTO-SPIを使う際はSB70をジャンパして
PE2(COTO-SPI IO2)を外部に引き出せられるようにするのを忘れないで
くださいね。

それではSTM32H5の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_HCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
for(;;);
}

/* Peripheral clock enable */
__HAL_RCC_OSPI1_CLK_ENABLE();

/* Alternate GPIO enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();

/* PD11 as OSPI_IO0 */
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPI1;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

/* PD12 as OSPI_IO1 */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPI1;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

/* PE2 as OSPI_IO2(Need SB70 SolderBridge) */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPI1;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

/* PD13 as OSPI_IO3 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPI1;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

/* PB2 as OSPI_CLK */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/* PG6 as OSPI_NCS */
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPI1;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}



お次は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)
{
XSPI_RegularCmdTypeDef sCommand = {0};
XSPI_MemoryMappedTypeDef sMemMappedCfg = {0};
uint8_t reg_data =0;

/* Initialize OCTO-SPI I/O */
OSPI_IoInit_If();

/* Initialize OCTO-SPI */
hxspi.Instance = OCTOSPI1;
hxspi.Init.FifoThresholdByte = 1;
hxspi.Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
hxspi.Init.MemoryType = HAL_XSPI_MEMTYPE_MICRON;
hxspi.Init.MemorySize = 23; /* 128Mbit=16MByte=2^(23+1) W25Q128JVSIQ */
hxspi.Init.ChipSelectHighTimeCycle = 4; /* 4ClockCycle(16nSec@250MHzMAX) Need for W25Q128JVSIQ >10nSec@read */
hxspi.Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
hxspi.Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
hxspi.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
hxspi.Init.ClockPrescaler = 1; /* 250MHzMAX/(1+1) = 125MHz (Allowed SDR Clock is 150MHz@3.3V) */
hxspi.Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_HALFCYCLE;
hxspi.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
hxspi.Init.ChipSelectBoundary = 0;
hxspi.Init.DelayBlockBypass = HAL_XSPI_DELAY_BLOCK_BYPASS;
hxspi.Init.Refresh = 0;
if (HAL_XSPI_Init(&hxspi) != HAL_OK)
{
for(;;);
}

/* Enable Reset --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
sCommand.IOSelect = HAL_XSPI_SELECT_IO_3_0;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytes = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesWidth = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesDTRMode = HAL_XSPI_ALT_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x66; /* Reset Enable W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_XSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_XSPI_DATA_NONE;
sCommand.DataLength = 0;
sCommand.DummyCycles = 0;

if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

/* Reset Device --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
sCommand.IOSelect = HAL_XSPI_SELECT_IO_3_0;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytes = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesWidth = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesDTRMode = HAL_XSPI_ALT_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x99; /* Reset W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_XSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_XSPI_DATA_NONE;
sCommand.DataLength = 0;
sCommand.DummyCycles = 0;

if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

/* Enter Quad-SPI Mode --------------------------- */
/* Common Commands */
sCommand.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
sCommand.IOSelect = HAL_XSPI_SELECT_IO_3_0;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytes = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesWidth = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesDTRMode = HAL_XSPI_ALT_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x31; /* Set Status2 W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_XSPI_ADDRESS_NONE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.DataLength = 1;
sCommand.DummyCycles = 0;
reg_data = 0x02; /* Enable QuadI/O Mode */

if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

if (HAL_XSPI_Transmit(&hxspi, ®_data, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

/* Enter MemoryMappedMode --------------------------- */
/* Read Commands */
sCommand.OperationType = HAL_XSPI_OPTYPE_READ_CFG;
sCommand.IOSelect = HAL_XSPI_SELECT_IO_3_0;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_4_LINES;
sCommand.AlternateBytes = 0xFF; /* Need for Fast Read QUAD W25Q128JVSIQ */
sCommand.AlternateBytesWidth = HAL_XSPI_ALT_BYTES_8_BITS;
sCommand.AlternateBytesDTRMode = HAL_XSPI_ALT_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0xEB; /* Fast Read QUAD W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_XSPI_ADDRESS_4_LINES;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_XSPI_DATA_4_LINES;
sCommand.DataLength = 0;
sCommand.DummyCycles = 4; /* DUMMY 4Cycle for Fast Read QUAD W25Q128JVSIQ */

if(HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

/* Write Commands */
sCommand.OperationType = HAL_XSPI_OPTYPE_WRITE_CFG;
sCommand.IOSelect = HAL_XSPI_SELECT_IO_3_0;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytes = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesWidth = HAL_XSPI_ALT_BYTES_NONE;
sCommand.AlternateBytesDTRMode = HAL_XSPI_ALT_BYTES_DTR_DISABLE;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
/* Instruction */
sCommand.Instruction = 0x32; /* Page Write QUAD W25Q128JVSIQ */
/* Address */
sCommand.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
sCommand.Address = 0;
/* Data */
sCommand.DataMode = HAL_XSPI_DATA_4_LINES;
sCommand.DataLength = 0;
sCommand.DummyCycles = 0;

if(HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
for(;;);
}

/* Set OCTO-SPI as MemoryMappedMode */
sMemMappedCfg.TimeOutActivation = HAL_XSPI_TIMEOUT_COUNTER_DISABLE;
sMemMappedCfg.TimeoutPeriodClock = 0;
if(HAL_XSPI_MemoryMapped(&hxspi, &sMemMappedCfg) != HAL_OK)
{
for(;;);
}

}


STM32H5のOCTO-SPIのHALはL5と微妙に違っており"HAL_OSPI"だったのが
"HAL_XSPI"に変わってたりして移植の際めっちゃ鬱陶しかったのですが
基本的な設定の流れはL5の時とそこまで変わっておりません。

特筆すべきはSTM32H5は250MHzで動作するのでW25Q128JVSIQ(最大133MHz)
に対して125MHzという過激なスピードのクロックが供給可能で非常に高速な
動作が可能です!しかもデータキャッシュもついてます!

ちなみにSTM32H5のOCTO-SPIの最大供給クロックは150MHz(3.3V時)まで
可能となっております。

●実際に動かしてみる
ていうかポリウレタン被覆銅線でQSPI-ROMへ配線引き伸ばしてほんとに
125MHzで動いてンのかと自分でも訝しんでいましたので実際にクロックの
波形を測定してみました。
使用したオシロはPicoscope5444D MSOですそうだね宣伝だね。


Picoscope5444D MSOのアナログ帯域200MHzなのに125MHzの高速信号測るの
どうなのとお思いでしょうが測定限界を大幅に超えていますがETSモードで
測定した限りではきっちり125MHzのクロックが出ていることがわかります。


ねむいさんはQSPI-ROMをFONTX2を格納するのに使っていますがこんな感じで
文字崩れも起こさずきれいに表示できております!

手配線の限界に挑んだ感じですがまぁSCLKは一応33ohmで直列終端
しているのでそれが効いて安定して動作しているのでしょう〜。



●おまけ・STM32L5とのパフォーマンス比較
せっかく下駄基板作ってハード的に同じ条件が揃えてるのでSTM32L5と
STM32H5のパフォーマンス比較とかやってみました。
同一のpngファイルでlibpngを使ったデコード時間を比較してみました。

☝STM32L5の場合(動作クロック:110MHz)

☝STM32H5の場合(動作クロック:250MHz)
う〜むやはり圧倒的に違いますね〜というかねむいさんがSTM32H5の
パワーを持て余しててまだまだ機能の一握りも使えてないので頑張って
使いこなしていこうと思います!これからも頑張るぞい!

STM32C0を使ってみる4 -8PinのSTM32C0を使いこなせ!-

今回は秋月電子さんから販売された8ピンのSTM32C011J4M7を使いこなします。
STM32G0の時と違ってGPIOの設定が厳密になっているのでそこら辺も掘り込んで
詳しく紹介します。

●ねむいさん的STM32C011やっけつ回路

今回紹介する8ピンSTM32C011の回路図はG0の頃と同じ感じです。
8ピンSTM32G0をそのまま差し替えて使用できるようにしてみました。
もちろん1-Wireも使えます!

ちなみに最低限のやっけつ回路なので真似される方は電源ラインは
適宜強化願います。


●GPIOのリマップとマルチプレクス

さて、8ピンのSTM32C011はひとつのピンに多重にGPIOや周辺機能が
割り当てられています。


ねむいさんは8ピンのSTM32G0と同じ回路でI2Cを動作させたかったの
ですが、STM32C011のI2Cは1つしか存在しないためSTM32G0では可能
だったI2C2が使用できずI2C1のみとなっております。
その結果PA11,PA12をそのまま使用することができないので"リマップ"
を行う必要があります。



SYSCFGのCFGR1レジスタをいじってGPIOのリマップを行うことによりI2C1
(PA9,PA10)がようやく使用可能となります。


実際はHALライブラリのRemap関数が提供されているのでそれを使用しております。


また、STM32G0ではガバガバだったGPIOの設定もC0では厳密になっており、
例えば8Pin版STM32C0でPC14を選択して使用したい場合はSYSCFGのCFGR3
レジスタのPINMUXビットをいじる必要があります(ついでにPA9とPA10も)。


また、20Pin版STM32C011F4P6では他のI/Oポートと重複がなくPINMUXをいじる
必要がないため、上で提示したコードのようにパッケージレジスタを参照し、
PINMUXをいじるか否かを判定しております。


てわけで改めてSTM32C011J4M7を使ってI2Cデバイス(秋月OLEDSHT40)を
使った温湿度計を稼働させてみました!楽勝ですね☆

秋月から8pinSTM32G0がなくなった今、8ピンのSTM32C011J4M7がSTM32への
入門の定番となっていくでしょう!

今回紹介したプロジェクトはSTM32C0-NUCELOのプロジェクトと合体させて
公開しております!どしどしご活用ください!



●STM32C011J4M7のフラッシュ容量は実際32kByteもある?
STM32C011J4M7のフラッシュ容量は公称では16kByteとなっております。
しかし実際には32kByteもあるようです…マジかよ…
実際に以下の方々が32kByteあることを確認をしております。
しっぽいいんちょ様
amanoya3様


ねむいさんもSTM32C0-NucleoからSWDを引き出してSTM32C011に接続し、
STM32CubeProgrammerで確認してみました!


…ッッマジだ…32kByteってなってる…


わざと16kByteを超える30kByte超えのバイナリを作って書き込んで
みました…書き込める…ちゃんとベリファイも通る…!!!


もちろんちゃんと動いてるし…!


てわけで秋月で売っているSTM32C011J4M7は16kByteではなく実際は
32kByteもあることがわかりましたがこのおまけ容量に頼った設計を
しないように!ちゃんと16kByteの範疇で遊びましょう!





なお、OpenOCDでは16kByte以上を書き込むことができず、16kByte
以降の領域は書き込まれていないためベリファイが通りません。


なんでかというとOpenOCDのSTM32C0のフラッシュ書き込みドライバは
STM32C0に仕込んであるフラッシュ容量レジスタを参照して厳密に書き込める
容量を決定しているためです。

逆に言うとSTM32CubeProgrammerのほうがガバッガバなドンブリ設計…
STマイクロ公式がそれでいいのか!?


ちなみに超セコい技ですがCubeProgrammerで予め16kByte以降の領域を
書き込んでおくとOpenOCDでもベリファイが通りプログラムも正常に
動作します。全然意味ないですがー!

このおまけ容量の使い方としてはどうしてもプログラムサイズが
増えがちなデバッグ時に一時的な使い方として用いるのがよいです。
まぁロットが変わっておまけの16kByte分が無くなったら出来ない
技なので当たり引けたらラッキー的な感じで遊んでいきましょ〜

STM32C0を使ってみる3 -STM32C011秋月発売記念-


少し前の話ですがSTM32C0シリーズが秋月電子よりついに販売されました!!


8pinのSTM32C011J4M7


20pinのSTM32C011F4P6です!!


ROM容量はROM16kbyte,RAMが6kbyteとSTM32シリーズの中では
非常に小ぶりな品種となります。


●ピン配置について注意


まず注意点として20Pinの品種は同じく秋月で販売されているSTM32L010F4P6
ピン互換性がないのでご注意ください。



また、8pinのSTM32C011J4M7はかつて秋月で販売されていたSTM32G031J6M6
ほぼピン互換となっております…

が!


ねむいさんの使い方ではI/Oのリマップを行わないと一部の機能…I2Cが
使用できないのでご注意ください。こちらについては次回にみっちりと解説
させていただきます!



●実際に使ってみた

そんなわけで秋月OLED秋月販売のSHT408pinSTM32C011でサクッと温湿度
表示してみました…!

長らくの間ねむいさんのドットマトリクスOLEDドライバが秋月OLEDに対応して
なかったの気づいてませんでしたが誰も気づいてなかったから多分大丈夫です…

すみません次回からまじめにやります…!!


今回はさわりだけ紹介しましたが、次回はI/Oのリマップ機能とかも使いこなす
方法も解説していこうと思います!

20240206追:
既存のSTM32C031-NUCLEO向けプロジェクトにSTM32C011J4M7向けの
やつを合体させたの先行公開します!!!!!

20240206追:

Go to top of page