STM32CubeIDEでST-Link-V2が接続できない事象の対処方法

ここには簡潔に記載することにする。

【原因】

STM32CubeIDEのCode Generator(コードジェネレーター)で出力された以下、__HAL_AFIO_REMAP_SWJ_DISABLE(); のコードである。
__HAL_AFIO_REMAP_SWJ_DISABLE()は、JTAG-DPとSW-DPをDisabledにする設定なので ST-Link等のデバッガーが接続できなくなって当然である。

main()
 +- HAL_Init()
 |   +- HAL_MspInit()
 |       +- __HAL_AFIO_REMAP_SWJ_DISABLE() ★これが原因
 |

【対処】

HAL_MspInit()でコールしている__HAL_AFIO_REMAP_SWJ_DISABLE()を削除すれば良いのだが、ST-Linkが接続できないので、STM32 ST-LINK Utilityを使用してFLASHメモリに書き込んだプログラムを削除する必要がある。

【ST-LINK Utilityで接続する方法】

前提条件として、基板にRESETスイッチが必要である。(RESETスイッチが無い場合、基板に通電した状態でマイコン端子のNRSTをGNDに落とした状態がRESETスイッチON状態、GNDから離せばスイッチOFF状態ですが、場合によっては抵抗(10KΩ)を付けないと基板が死にます。RESETサブ基板を作りましょう)
手順①:ST-LINK Utilityを起動する。※ST社サイトからダウンロードできます。
手順②:基板のRESETスイッチを押し続ける。
手順③:ST-LINK Utilityの[Connect to the target]ボタンをクリックする。
手順④:基板のRESETスイッチを離す。
※接続できない場合、手順④の実施タイミングにより接続できない時があるので、手順②からやり直しましょう。
手順⑤:書き込んだセクターだけをメニューの[Erase sector]で消去する。
※[Full chip erase]ボタンでFLASHメモリの全領域を消去しても良いが、DFU(プログラムダウンローダー)まで消去される。私はDFUを使用せず、ST-Linkでプログラムを書き込んでいるので全領域消去してます。
※Erase sectorを調べるには、プログラムが書き込まれているアドレス範囲を調べる必要がある。debugまたはreleaseフォルダからxxxx.mapファイルを検索して、エディタで開いて確認しましょう。
手順⑥:[Disconnect from the target]ボタンをクリックする。

【対処完了】

これで、STM32CubeIDEでST-Link-V2が接続できます。原因を排除したプログラムでデバッグ再開できます。

STM32CubeMX

年末年始休暇を利用して、STM32F103C8でI2CデバイスのMCP23017をPB6,PB7(I2C1 SCL,SDA)に接続した回路をブレッドボードに実装してみた。
そして、STM32CubeMX(CubeF1)にて初期化部分のコードを生成したところバグがあることが分かった。
開発環境の各バージョンは次の通りである。
・STM32FCubeMX Ver5.0.0
・STM32CubeF1 Ver1.7.0
・IAR社 EWARM 7.50.3

STM32CubeMXで自動生成された下記コードでは、I2C SCLにクロックが出力されない事が分かった。

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hi2c->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Peripheral clock enable */   ★★I2CペリフェラルへのクロックEnableは、
    __HAL_RCC_I2C1_CLK_ENABLE();    ★★HAL_GPIO_Init()より前に行わないとI2Cが正常に動作しない
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }

}

私は、GPIOBのクロックの直後に移動した。

__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_I2C1_CLK_ENABLE();

ちなみに、MCP23017のdevice addressは0x20であるが、I2C APIのパラメータに渡すときは、左にシフトして(0x40)渡すことが、API仕様に書いてあります。

#include "stm32f1xx.h"

/** i2cスレーブアドレス */
#define SLAVE_ADDR  0x40
/** i2cデバイスハンドル */
I2C_HandleTypeDef   MCP23017_handle;

void MCP23017_init(void)
{
    uint8_t rw_buff[2];

    /* Initialize for I2C */
    MCP23017_handle.Instance = I2C1;
    MCP23017_handle.Init.ClockSpeed = 100000;
    MCP23017_handle.Init.DutyCycle = I2C_DUTYCYCLE_2;
    MCP23017_handle.Init.OwnAddress1 = 0;
    MCP23017_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    MCP23017_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    MCP23017_handle.Init.OwnAddress2 = 0;
    MCP23017_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    MCP23017_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if( HAL_I2C_Init( &MCP23017_handle ) != HAL_OK )
    {
        Error_Handler();
    }

    /** I2Cデバイスからの読み出し(デバッグ用) */
    rw_buff[0] = 0x05;
    if( HAL_I2C_Master_Receive( &MCP23017_handle, SLAVE_ADDR, rw_buff, 1, HAL_MAX_DELAY ) != HAL_OK ){
//  if( HAL_I2C_Master_Transmit( &MCP23017_handle, SLAVE_ADDR, rw_buff, 1, HAL_MAX_DELAY ) != HAL_OK ){
//  if( HAL_I2C_Master_Transmit_IT( &MCP23017_handle, SLAVE_ADDR, rw_buff, 1 ) != HAL_OK ){
        Error_Handler();
    }
}

STM32 内蔵Flashメモリ データ保存 Fault

STM32のようなワンチップマイコンの内蔵Flashメモリにデータを書き込む際は、注意する事があります。
Flashメモリは、一般的にpage, block, deviceという単位ブロックで表現されてます。
Flash消去(Erase), 書き込み(Program)の実行中は、同じblockの読み出し(Read)が出来ません。FAULTします。
出来ることなら消去と書き込み中は、割り込み禁止にするのが良いのですが、システム的にそうする事で他のタスクに影響が出るため、割り込み禁止しない場合、動作する可能性のプログラム(関数や割り込みハンドラ)にram_function属性を付けて、RAM動作するように配置する必要があります。
逆の表現すると、データ通信中に希にデータを取りこぼす事象の場合、割り込み禁止状態が影響している場合がよくあります。どちらも見つけにくいbugです。
設計フェーズで懸念していると見つけやすいですよ。

STM32F103C8T6 マイコン

STM32F103C8T6 が載った基板を購入したので、まずはST社からSTM32CubeF1をダウンロードして開発環境を整えてみます。
流用できるプロジェクトファイルは、STM32F103RB-Nucleoでイケます。main関数は基板固有コードばかりなので空関数にしますが、
使用するソースファイルやヘッダファイル、コンパイルオプションは、まずは同じでOKです。後から精査しましょう。

STM32CubeF1内のDrivers/CMSIS/Device/ST/STM32F1xx/Include/stm32f1xx.hに使用するマイコン名を#defineするように記載されていますが、私はコンパイルオプション(-Dプリプロセッサ定義)でSTM32F103xBを指定します。(何故なら敢えてオリジナルファイルを変更しなくても対応可能な方法だから、ソースの変更管理しなくて済みます。
私の統合開発環境(IDE)は、IAR社のEWARMを使用しているから、iarフォルダ下にプロジェクトファイルがあります。このプロジェクトファイルをEWARMで開けば、必要なソースファイルが分かりますが、ココにも載せておきます。本サイトのSTM32F303K8でも取り上げています。

---STM32Cube_F1 フォルダ構成---
STM32Cube_FW_F1_V1.6.0
+Drivers
 +CMSIS
 |+Device
 ||+ST
 || +STM32F1xx
 ||  +Include   :includeパスにセットするフォルダ
 ||  +Source
 ||   +Templates   :以下(2)にコピーされている
 ||    +iar
 |+Include
 +STM32F1xx_HAL_Driver
  +Inc           :includeパスにセットするフォルダ
  +Src           :以下(1)参照
+Projects
 +STM32F103RB-Nucleo
  +Templates_LL
   +EWARM       :IAR用ファイルのフォルダ
   +Inc         :includeパスにセットするフォルダ
   +Src         :以下(3)参照
+Middlewares
 +Third_Party
  +FreeRTOS      :リアルタイムOS


---(1)Drivers\STM32F1xx_HAL_Driver\Src---
stm32f1xx_hal_XXXX.c  :HALドライバ
stm32f1xx_ll_XXXX.c   :LLドライバ

---(2)Projects\STM32F103RB-Nucleo\Templates_LL\EWARM---
startup_stm32f103xb.s  :ベクタテーブル、リセットハンドラ
stm32f103xb_flash.icf  :IARリンカの設定ファイル

---(3)Projects\STM32F103RB-Nucleo\Templates_LL\Src---
main.c
stm32f1xx_it.c     :割り込みハンドラ(無くても良い。各ハンドラはweakでstartup_stm32f103xb.sに定義済み)
system_stm32f1xx.c :boot初期処理

外部クロックHSE(8MHz)を用いて動作させるために必要なファイル一覧を以下に記載します。
ちなみにCMSISのLLドライバを使用しました。

---STM32Cube_F1 外部クロックで動作させるために必要なファイル一覧---
main.c
startup_stm32f103xb.s
system_stm32f1xx.c
(stm32_assert.h)
stm32f103x8.icf
stm32f1xx_ll_gpio.c
stm32f1xx_ll_rcc.c

STM32マイコンは、Power ONで内部クロックHSI(8MHz)により動作します。従って、ベクタテーブルのリセットハンドラさえあればmain()迄実行され、main(){while(1);}だけの空転関数でも動作します。
しかし、次のmain()ではFreeRTOSを盛り込み、外部クロックHSE(8MHz)を用いて動作するように設定した実装例も載せておきます。省電力のため動作クロックはSYSCLK 48MHz、HCLK 24MHzに落としてます。

---main.c---
#include "stm32f1xx_ll_rcc.h"
#include "stm32f1xx_ll_system.h"
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_bus.h"
#include "FreeRTOS.h"	//FreeRTOS用
#include "task.h"	//FreeRTOS用

void SystemClock_Config(void);
void createOsResource(void);	//FreeRTOS用

/* テストピン(LED1)定義 */
#define LED1_GPIO		GPIOC
#define LED1_PIN		LL_GPIO_PIN_13

/**
 * @brief   Main program
 * @param   None
 * @retval  None
 */
int main(void)
{
	/* setup system clocks */
	SystemClock_Config();

	/* テストピン(LED1)をEnable */
	LL_APB2_GRP1_EnableClock( LL_APB2_GRP1_PERIPH_GPIOC );
#if 0	// LL_GPIO_Init()にBUGあり
	{
	LL_GPIO_InitTypeDef  pin_cfg;
		/* ポートCをEnableにする */
		LL_GPIO_DeInit(LED1_GPIO);

		/* テストピン(LED1)をセットアップする */
		pin_cfg.Pin		= LED1_PIN;
		pin_cfg.Mode		= LL_GPIO_MODE_OUTPUT;
		pin_cfg.Speed		= LL_GPIO_SPEED_FREQ_LOW;
		pin_cfg.OutputType	= LL_GPIO_OUTPUT_PUSHPULL;
		pin_cfg.Pull		= LL_GPIO_PULL_UP;
		LL_GPIO_Init(LED1_GPIO, &pin_cfg);
	}
#else
	/* Pin Mode configuration */
	LL_GPIO_SetPinMode( LED1_GPIO, LED1_PIN, LL_GPIO_MODE_OUTPUT );
	/* Pull-up Pull down resistor configuration*/
	LL_GPIO_SetPinPull( LED1_GPIO, LED1_PIN, LL_GPIO_PULL_UP );
	/* Speed mode configuration */
	LL_GPIO_SetPinSpeed( LED1_GPIO, LED1_PIN, LL_GPIO_SPEED_FREQ_LOW );
	/* Output mode configuration*/
	LL_GPIO_SetPinOutputType( LED1_GPIO, LED1_PIN, LL_GPIO_OUTPUT_PUSHPULL );
#endif

	/* OSリソース生成 */
	createOsResource();	//FreeRTOS用

	/* Start the scheduler */
	vTaskStartScheduler();	//FreeRTOS用

	while(1);
}

/**
 * @brief   System Clock Configuration
 *          The system Clock is configured as follow : 
 *             System Clock source            = PLL (HSE)
 *             SYSCLK(Hz)                     = 48000000
 *             HCLK(Hz)                       = 24000000
 *             AHB Prescaler                  = 2
 *             APB1 Prescaler                 = 1
 *             APB2 Prescaler                 = 1
 *             HSE Frequency(Hz)              = 8000000
 *             PLL_MUL                        = 6
 *             Flash Latency(WS)              = 1
 * @param   None
 * @retval  None
 */
void SystemClock_Config(void)
{
#if 1
//	LL_APB2_GRP1_EnableClock( LL_APB2_GRP1_PERIPH_GPIOD );
//	LL_RCC_EnableIT_HSERDY(); 
	LL_RCC_HSE_EnableCSS(); 
	/* Enable HSE oscillator */
//	LL_RCC_HSE_EnableBypass();
	LL_RCC_HSE_Enable();
	while( LL_RCC_HSE_IsReady() != 1 );
#endif
	/* Set FLASH latency */
	LL_FLASH_SetLatency( LL_FLASH_LATENCY_2 );

	/* Set PLLSRC & PLLMUL */
#if 1
	LL_RCC_PLL_ConfigDomain_SYS( LL_RCC_PLLSOURCE_HSE_DIV_1, LL_RCC_PLL_MUL_6 );
#else
	LL_RCC_PLL_ConfigDomain_SYS( LL_RCC_PLLSOURCE_HSI_DIV_2, LL_RCC_PLL_MUL_12 );
#endif
	LL_RCC_PLL_Enable();
	while( LL_RCC_PLL_IsReady() != 1 );

	/* Set AHB prescaler */
	LL_RCC_SetAHBPrescaler( LL_RCC_SYSCLK_DIV_2 );
	/* Select SYSCLK by PLLCLK */
	LL_RCC_SetSysClkSource( LL_RCC_SYS_CLKSOURCE_PLL );
	while( LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL );

	/* Set APB1 & APB2 prescaler */
	LL_RCC_SetAPB1Prescaler( LL_RCC_APB1_DIV_1 );
	LL_RCC_SetAPB2Prescaler( LL_RCC_APB2_DIV_1 );

	/* Set SYSTICK to 10ms */
	SysTick_Config( 48000000 / 100 );

	/* Set CMSIS variable */
	SystemCoreClock = 48000000;
}

/**
  * @brief  TICKフック of FreeRTOS
  * @param  None
  * @retval None
  */
#define TICK_HOOKCNT  20		//= 200ms=20 * os tick=10ms
int16_t   iHookCnt = TICK_HOOKCNT;
void vApplicationTickHook( void )
{
	if(--iHookCnt <= 0){
		iHookCnt = TICK_HOOKCNT;
		/* テストピン(LED1)にトグル出力 */
		LL_GPIO_TogglePin(LED1_GPIO, LED1_PIN);
	}
}

/**
  * @brief  空タスク
  * @param  None
  * @retval None
  */
void XXXXTask( void * pvParameters )
{
	for( ;; );
}

/**
  * @brief  create FreeRTOS resorce.
  *         タスクやメッセージキューを生成する
  * @param  None
  * @retval None
  */
#define	XXXXTask_STACK_SIZE		configMINIMAL_STACK_SIZE
void createOsResource(void)
{
	static uint8_t ucParameterToPass;
	TaskHandle_t xHandle = NULL;

	// Create the task, storing the handle.
	xTaskCreate( XXXXTask, "TSKNAME", XXXXTask_STACK_SIZE, &ucParameterToPass, tskIDLE_PRIORITY, &xHandle );
	configASSERT( xHandle );
}

FreeRTOSを盛り込むにあたり、startup_stm32f103xb.sの実装例も載せておきます。

        PUBWEAK SVC_Handler
	EXTERN  vPortSVCHandler		;☆追加
        SECTION .text:CODE:REORDER:NOROOT(1)
SVC_Handler
        B vPortSVCHandler		;☆変更

        PUBWEAK DebugMon_Handler
        SECTION .text:CODE:REORDER:NOROOT(1)
DebugMon_Handler
        B DebugMon_Handler

        PUBWEAK PendSV_Handler
	EXTERN  xPortPendSVHandler	;☆追加
        SECTION .text:CODE:REORDER:NOROOT(1)
PendSV_Handler
        B xPortPendSVHandler		;☆変更

        PUBWEAK SysTick_Handler
	EXTERN  xPortSysTickHandler	;☆追加
        SECTION .text:CODE:REORDER:NOROOT(1)
SysTick_Handler
        B xPortSysTickHandler		;☆変更

IAR社のEWARM

[ブログ]
EWARMのバージョンが8.11.1になっていたので使用させてもらったところ、アイコンが新しくなっり64bitOS対応版がありました。

少し使用した感想ですが、ビルドオプションが増えてたので、STのサンプルプロジェクトをダウンロードしようとしたのですがエラーが出てできななった(現在2017/8/15はダウンロード可能)。
デバッグ時のレジスタ窓から各コントローラのレジスタ一覧が無くなってて機能ダウン。レジスタ定義を入手すれば表示されそうだけど手間なので、旧バージョンv7.50でデバッグした(笑)

STM32F303K8のブートプログラム

[コンテンツ]
STM32F303K8内蔵クロック(HSI 8MHz)だけを使用するのであれば、ブートプログラムは IARのEWARMの場合はSTM32CubeF3 内のProjectsフォルダ下を利用すればmain関数まで実行できるようになります。
必要なファイルは以下の通り。
・startup_stm32f303x8.s :無修正で利用
・system_stm32f3xx.c :無修正で利用
・stm32f3xx_it.c :無修正で利用
・main.c :main関数内は空でも良い
・stm32f3xx_it.h
・stm32_assert.h
・main.h

次のステップとしては、クロックアップさせる事になると思います。
STM32CubeF3では、HALまたはLLペリフェラルドライバを提供しています。
HALドライバは、少々ソフトウェア的な作りとなっており、該当MCU以外の処理ロジックが含まれていて理解し辛いです。
LLドライバは、該当MCU以外の処理ロジックはコンパイルフラグにより排除できる形となっています。

ローカルな実装では、動作クロックはPLLを用いて48MHzにしました。
以下のようにmain関数でLLドライバを使用してます。

#include "stm32f3xx_ll.h"

int main(void)
{
	/* クロック設定の確認のためにクロックをMCOポート(18pin:PA8)に出力する */
	LL_RCC_ConfigMCO( LL_RCC_MCO1SOURCE_PLLCLK, LL_RCC_MCO1_DIV_1 );

	/* Configure the system clock to 48 MHz */
	SystemClock_Config();

	/* Infinite loop */
	while (1);
}

次のステップでFreeRTOSを実装します。
STM32CubeF3に含まれているver9.0とオフィシャル ver7.6の消費リソース具合を調べようと思ってます。
組み込みシステムで使用するRTOSの機能は、セマフォとメッセージがあれば事足りるからです。