STM32系のtips¶
Boot pinの取り扱い¶
- ちゃんとpullup/downしておかないとハマる
- BOOT0/BOOT1の選択表は下記
- http://miqn.net/introduction/48.html
- http://stm32f4.web.fc2.com/RAMboot.html
- https://arcadiaplus.jp/news/64
sprintfとかでfloatを使うとき¶
HAL API Refence¶
Referenses¶
printfの出力先をuartにする¶
eclipseで一括インデント¶
ctrl + i
- Eclipseで一括インデント | ハックノート
STM32 HAL ADCの基礎の基礎¶
- DMAで連続変換するときの鍵
- clockはsystem clock devidedなものを入れると楽.Async clockは別途記述が必要.
- continuous conversion enableにしてDMA continuous request enableにしておくとDMA割り込みが入りまくることに注意.ADCが終わるたびにDMA走る.
- これを防ぐにはcontinuous conversion disableとして普通にタイマ割り込みでADCを回すのが良さそう.DMA continuous requestはenableのまま.
- DMAはcircularを選ぶ
- DMAのmemory はincrementにしておく
- DMA continuous request enable
- 上記をやっておかないと連続変換されなかったり,DMAに連続的に変換結果が入らずにハマる.
- もちろん理解して変更すればいいが,基本上記でいいかなぁという気持ち.
- code examples
constexpr adcChannels = 3; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
Error_Handler();
これで逐次clockに応じてADCが動作しDMAで adcData[]
に変換結果が転送される.
変換完了後にcallbackしたい場合は下記等が便利.変換完了時にcallbackされる.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *AdcHandle){
//do something
}
- キャリブレーションが必要な場合は下記を呼び出すことでADCのキャリブレーションが可能らしい. しかしこれは通常起動時に実施済みであるため明示的には不要の場合が多いが,長時間駆動する機器の場合定期的にキャリブレーションを実施することにより精度の高い測定が可能となるよう.
if(HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK)
Error_Handler();
- タイマ割り込みにする場合
- CubeMXでADCのconfigを下記のようにする.ADCのチャネル,DMA転送など基本的なconfigはできている前提で
ADC_Regular_ConversionMode
->External Trigger Conversion Source
をTimer1 Trigger Out event
に.
- CubeMXでADCのconfigを下記のようにする.ADCのチャネル,DMA転送など基本的なconfigはできている前提で
- さらにCubeMXでTIM Timer1のconfigをする.プリスケーラやカウンタ値等のタイマ割り込みを発生させるconfigは書けている前提で
Trigger Output(TRGO) Parameters
->Trigger Event Selection
をUpdate Event
に.
これでタイマ割り込みでADCがトリガされるはず.
- ADCのキャネルとデータの入り方
ch1, ch2, ch3, ch4, ch5, ch6, ch7
を有効にしていたとして
constexpr adcChannels = 8; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
Error_Handler();
このようなコードを書いた場合,変換結果は
ch1: adcData[0];
ch2: adcData[1];
ch3: adcData[2];
ch4: adcData[3];
ch5: adcData[4];
ch6: adcData[5];
ch7: adcData[6];
ch8: adcData[7];
に対応して格納される.
ch1
, ch3
, ch5
, ch7
を有効(ch2
, ch4
, ch6
は無効)にしていたとして
constexpr adcChannels = 4; //number of using ADC channels
static uint16_t adcData[adcChannels];
if (HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, adcChannels) != HAL_OK)
Error_Handler();
このようなコードを書くと
ch1: adcData[0];
ch3: adcData[1];
ch5: adcData[2];
ch7: adcData[3];
仮にここで
constexpr adcChannels = 3; //number of using ADC channels
としてしまった場合,書くchの変換結果が同一の添字(配列index)で取得できなくなる.
具体的にはリングバッファのようにずれてしまう.
したがってHAL_ADC_Start_DMA()
に渡すbufはchに対し十分余裕を持ちlengthは有効化しているadcチャネルと同一値とすることが大変重要である.
有効化しているadcチャネル数よりも少なくても多くてもずれが生じる.
この状態で気づかずに変換結果を取得すると逐次値が変動してハマる.ノイズのよう見見えてしまうことがあるかもしれない.
念の為adcの各chが同一の添字で同じものが取り続けられているかをserial(uart)等を利用し事前に確認しておくことが望ましい.
- ちなみにプロトタイプはこんな感じになっている:
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
STM32 HAL 外部割り込みの基礎の基礎¶
- 外部割り込みをするには
- GPIOをEXTI Interrupt modeにする.
- NVICで該当のEXTI Interruptをenableにする.
- これを忘れるとEXTIされないので注意.ハマりがち.
- 外部割り込みに限らずなんだけれども,割り込みの優先順位は適切につける.
- 例えば外部割り込みの中で`HAL_Delay()`を呼んだりしたいときは`Time base: System tick timer`より外部割り込みの優先順位を下げないと処理が戻ってこなかったりする.
実際のコードをみて割り込みの流れをみる
./Core/Src/stm32f0xx_it.c
/******************************************************************************/
/* STM32F0xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f0xx.s). */
/******************************************************************************/
/**
* @brief This function handles EXTI line 0 and 1 interrupts.
*/
void EXTI0_1_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_1_IRQn 0 */
/* USER CODE END EXTI0_1_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
/* USER CODE BEGIN EXTI0_1_IRQn 1 */
/* USER CODE END EXTI0_1_IRQn 1 */
}
- 複数のEXTIピンを定義した場合でも同じ
HAL_GPIO_EXTI_IRQHandler()
がcallされる.
/******************************************************************************/
/* STM32F0xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f0xx.s). */
/******************************************************************************/
/**
* @brief This function handles EXTI line 0 and 1 interrupts.
*/
void EXTI0_1_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_1_IRQn 0 */
/* USER CODE END EXTI0_1_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
/* USER CODE BEGIN EXTI0_1_IRQn 1 */
/* USER CODE END EXTI0_1_IRQn 1 */
}
/**
* @brief This function handles EXTI line 2 and 3 interrupts.
*/
void EXTI2_3_IRQHandler(void)
{
/* USER CODE BEGIN EXTI2_3_IRQn 0 */
/* USER CODE END EXTI2_3_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
/* USER CODE BEGIN EXTI2_3_IRQn 1 */
/* USER CODE END EXTI2_3_IRQn 1 */
}
HAL_GPIO_EXTI_IRQHandler()
がcallされると割り込みビットの処理が実行され,HAL_GPIO_EXTI_Callback()
がcallされる.- これがユーザが実際に定義することを想定しているcallback functionである.
stm32f0xx_hal_gpio.c
ですでに__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
という形で定義されているが,__weak
指定子がついているので,ユーザがvoid HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
を別で定義した場合それが優先される.
./Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
/**
* @brief EXTI line detection callback.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
- ユーザは実際に
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
を定義して利用することができる.- 引数として
GPIO_PIN
が渡されるので,これを見てどのピンのEXTI割り込みかを判断する.
- 引数として
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
HAL_Delay(1);
printf("EXTI callback called!\r\n");
if (GPIO_Pin == GPIO_PIN_3)
printf("gpio3 pushed!\r\n");
if (GPIO_Pin == GPIO_PIN_1)
printf("gpio1 released!\r\n");
}
- STM32 external interrupt and tick timer - Programmer Soughte
- EXTI内部で
HAL_Delay()
を叩くとすべての処理が停止することがあった. - これは割り込み優先度がすべて
0
となっていたため. HAL_Delay()
はsystick
を利用するためこの割り込みの優先度をあげ,EXTI
割り込みの優先度を下げる必要がある.- これにより無限ループによる処理停止が引き起こされない.
- EXTI内部で
STM32 HAL タイマ割り込みの基礎の基礎¶
- タイマ割り込みを使えるようにする.
- CubeMXで利用したいタイマを有効にする.
- プリスケーラ,カウンタを適切に計算してセット.
- 基本的にtimer clock sourceはinternal clockならAPB系がつかわれるが,どの系でくるかはdatasheet要確認.
- auto preloadをenableにしておくと楽.
- overしたときのcnt resetを自動でやってくれる.
- タイマ割り込みをenableにする.
- 忘れるとハマる.
- どこかで
HAL_TIM_Base_Start_IT(&htimx);
をcallしてtimer interruptをenableにする.
コードを見ていく
./Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim.c
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
uint32_t tmpsmcr;
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance));
/* Enable the TIM Update interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
/* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
__HAL_TIM_ENABLE(htim);
}
/* Return function status */
return HAL_OK;
}
- 基本的に
main.cpp
のstatic void MX_TIMx_Init(void)
あたりの最後で呼び出せばいいと思う.- もちろんmain関数の頭らへん(
MX_TIM3_Init()
が呼ばれたあと)で呼んでももちろん動くはず. - ex.
- もちろんmain関数の頭らへん(
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 39999;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 199;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END TIM3_Init 2 */
}
main.cpp
にcallbackを書く- 複数あるときでも引数で渡される
&htimx
を見て比較して分岐する. ./Core/Src/main.cpp
- 複数あるときでも引数で渡される
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim == &htim3)
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_0);
if(htim == &htim14)
printf("htim14 callback!\r\n");
}
- 大まかな流れと実装.
./Core/Src/stm32f0xx_it.c
- それぞれの割り込みハンドラがここにいる.
- タイマの割り込みハンドラは共通の
HAL_TIM_IRQHandler(&htimx)
をcallする.
/**
* @brief This function handles TIM3 global interrupt.
*/
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
/* USER CODE END TIM3_IRQn 1 */
}
./Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim.c
HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
は共通のタイマハンドラ.- ここでタイマの各種イベントに応じてcallback先を選んでいる.
- タイマの時間切れ(elasped)の場合update eventとなり,タイマ共通の
HAL_TIM_PeriodElapsedCallback(htim)
がcallされるが,htimはそれぞれのIRQHandlerで引数として渡したタイマのオブジェクト(address)が入っているのでhtimを見ることでどのタイマによって発生したイベントかを判断できる.
/**
* @}
*/
/** @defgroup TIM_Exported_Functions_Group7 TIM IRQ handler management
* @brief TIM IRQ handler management
*
@verbatim
==============================================================================
##### IRQ handler management #####
==============================================================================
[..]
This section provides Timer IRQ handler function.
@endverbatim
* @{
*/
/**
* @brief This function handles TIM interrupts requests.
* @param htim TIM handle
* @retval None
*/
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
...(snip)...
/* TIM Update event */
if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->PeriodElapsedCallback(htim);
#else
HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
...(snip)...
}
STM32 HAL PWMの基礎の基礎¶
- PWMを出すには
- CubeMXで利用したいタイマを選択.channelの設定でどのピンをPWM出力として使うかを指定.
- 普通のタイマの設定と同様にプリスケーラ,カウンタを設定.
- cubeMXでgenerateされるコードはconfigまでは入るがそのままではPWMがstartしない.
HAL_TIM_PWM_Start();
をcallする必要がある.MX_TIMx_Init()
の最後で呼んでおくと良さそう.- 別にmainで利用時にstartするのでも問題ない.
コードを見ていく
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 9;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 799;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 100;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sConfigOC.Pulse = (uint32_t)htim1.Init.Period*0.4;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
- 例えばこんな関数を作っておくとdutyをtimer/channelごとに更新できる.
void setPWM(TIM_HandleTypeDef *htim, uint32_t Channel, float duty)
{
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = (uint32_t)htim1.Init.Period*duty;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(htim, &sConfigOC, Channel) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_PWM_Start(htim, Channel);
}
- 相補PWMするときは
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
をcallしなければ動かない
STM32 HAL GPIOの基礎の基礎¶
- tips: GPIOのlabelはただのエイリアス.
./Core/Inc/main.h
#define LCD_RS_Pin GPIO_PIN_6
#define LCD_RS_GPIO_Port GPIOA
- エイリアスの先はちゃんとdriverのheaderで定義されている.なのでlabelをつけたからといって
GPIOA
やGPIO_PIN_X
などで指定できなくなるというわけではない../Drivers/STM32F0xx_HAL_Driver/Inc/stm32f0xx_hal_gpio.h
...(snip)...
/* Exported constants --------------------------------------------------------*/
/** @defgroup GPIO_Exported_Constants GPIO Exported Constants
* @{
*/
/** @defgroup GPIO_pins GPIO pins
* @{
*/
#define GPIO_PIN_0 ((uint16_t)0x0001U) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002U) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004U) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008U) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010U) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020U) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040U) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080U) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100U) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200U) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400U) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800U) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000U) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000U) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000U) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000U) /* Pin 15 selected */
#define GPIO_PIN_All ((uint16_t)0xFFFFU) /* All pins selected */
...(snip)...
STM32でSWCLK, SWDIOをGPIOとしてoutputに使ったとき.¶
- いつもどおりflashからbootすると,起動直後にGPIOがoutになってしまってそのままでは書き込めなくなる.
- いったんBOOT0をHighにしてシステムメモリor RAM起動にするとから書き込むと書き込める.
- BOOT0の状態BOOT1=Highならシステムメモリ,LowならRAM boot.
- BOOT0=Lowだとflash bootだからね.
- ST Community