STM32CubeIDE 3 (UART without DMA)

デバッグ用に1つだけ送信だけのUARTを使います。

方針 1

デバッグ用に1つだけ送信だけのUARTを使うためだけに余計なことはしません。

NUCLEO G474REには最初からLPUART1が使えるようになっています。
これがおそらくピン(PA2/PA3)とつながっていません。
USB経由で出力することになり、VCPを使うことになり、STが公式で配布しているVCP DriverはWindows用のみなので、Ubuntuだと実現不可か死ぬほど面倒かのどちらかになりそうです。
Clockも調整が必要になる可能性も高いので避けたいです。

最終的には電源はUSBではない予定で、開発中はモバイルバッテリーを使うことが多くて、TX/RX/GNDを外に出してFTDI UART-USBでRaspberry Pi 4で受けると簡単です。
Raspberry Pi 4で2つUARTを受けるはよくやっています。Raspberry Pi 3でやると実現不可か死ぬほど面倒かのどちらかになると思います。

UART4よりも、USART1の方が動く可能性が高そうと考えます。

方針 1 : USART1を使う。

方針 2

サンプルにprintfがありますが、printfは、やりません。

組み込みでは、printfが使えるように時間を使うよりも、sprintfやsnprintfに慣れた方が仕事が早いと考えるからです。

方針 2 : printfは無視する。

方針 3

CPUに無駄な苦労をさせないためにDMAを使います。
しかし、DMAはHAL Driverに昔から問題があり、直っていないこともある、ということなので、上手くいかなかったら、すぐやめます。

最終的にはUARTは不要なので。

方針 3 : UART + DMAは実現させたいが、上手くいかなかったらすぐに止める。

UARTの設定

USART1を選択して、ModeをAsynchronousにします。

001

RX用とTX用のDMAをAddします。基本的にハードウェア構成の変更は面倒なので、先にここで完成形にしておきます。
どちらもModeをCircularにします。

002

NVICに自動的にチェックが入っています。
NVICはARMの安いSoCの割り込みコントローラーでよく使われています。

003

GPIO SettingsからTX/RXをpullupします。

設定するためのインターフェースが見えません。

004

かなり上に引っ張ってきて、やっと見えるようになります。これはひどい。

005

Project Managerを確認しておきます。Advanced Settingsを確認します。
DMA_InitがUSART1_UART_Initよりも上にあることを確認します。
これが逆だと問題が起きることがあるそうです。

006

これをクリックします。
ハードウェア構成から、ソフトウェアを生成させます。

007

Core >> Src >> main.c

選択して、F2を押します。main.cppに変更します。これで、gccではなくg++が使われるようになります。
最初にC++って選択したじゃん、って思うのですが。

現状、必ずmain.cを生成すると思います。(2021.12.03)

008

自分の場合

ソフトウェアを編集するときに、IDEを使わずにviを使う人のための設定。


$ cd
$ ln -s STM32CubeIDE/workspace_1.7.0 ide

名前(上記ではide)はなんでもいいのですが、これでcd i <tab>で開発環境にすぐに行けるようになります。

関数の定義に飛べるようにします。
インストールされていないときにctagsを実行すると、Ubuntuが
sudo apt install exuberant-ctags
sudo apt install universal-ctags
のどっちかをやってくださいと教えてくれます。自分が使う範囲ではexuberant-ctagsで問題になることはないですがuniversal-ctagsの方が良いらしいです。
下記の操作だとexuberant-ctagsがインストールされます。


$ cd ~/ide/g474re_sensor
$ ctags -R (怒られる)
$ sudo apt install ctags
$ ctags -R

ソフトウェア開発を始めるにあたり

最初にライセンスを確認します。

STM32CubeG4/License.md

さすがにGPLではないですね。一応、安心しておきます。

どこから漁るか

本家STがソフトウェアを公開していますので、ここから漁ります。

STMicroelectronics/STM32CubeG4

Examples uses only the HAL and BSP drivers (Middleware not used)
Examples_LL uses only the LL drivers (HAL and Middleware not used)
Examples_MIX uses only HAL, BSP and LL drivers (Middleware are not used)
Applications intends to demonstrate the product performance and how to use the different Middleware stacks available
Demonstrations aims to integrate and run the maximum of peripherals and Middleware stacks to showcase the product features and performance

情報元。HALとLLとBSPの関係が図で描かれています。

STM32CubeG4GettingStarted.pdf
STM32CubeProjectsList.html

よくやるやり方としてDemonstrations、Applicationsにあれば最良ですが、STM32の場合はHALかLLみたいです。Examples、Examples_LL、Examples_MIX、の順でしょうか。

UART without DMA

ソースコードです。ほぼサンプルコードのままです。

main.cpp

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef hlpuart1;
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

/* USER CODE BEGIN PV */

char uart_message[256] = {0};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_LPUART1_UART_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_LPUART1_UART_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    sprintf( uart_message, "\r\n ----- UART TEST ----- \r\n" );
    HAL_UART_Transmit(&huart1, (uint8_t *)uart_message, 64, 5000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV4;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the peripherals clocks
  */
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_LPUART1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief LPUART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_LPUART1_UART_Init(void)
{

  /* USER CODE BEGIN LPUART1_Init 0 */

  /* USER CODE END LPUART1_Init 0 */

  /* USER CODE BEGIN LPUART1_Init 1 */

  /* USER CODE END LPUART1_Init 1 */
  hlpuart1.Instance = LPUART1;
  hlpuart1.Init.BaudRate = 115200;
  hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
  hlpuart1.Init.StopBits = UART_STOPBITS_1;
  hlpuart1.Init.Parity = UART_PARITY_NONE;
  hlpuart1.Init.Mode = UART_MODE_TX_RX;
  hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  hlpuart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  hlpuart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  hlpuart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&hlpuart1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetTxFifoThreshold(&hlpuart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetRxFifoThreshold(&hlpuart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_DisableFifoMode(&hlpuart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN LPUART1_Init 2 */

  /* USER CODE END LPUART1_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMAMUX1_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel2_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LD2_Pin */
  GPIO_InitStruct.Pin = LD2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

変更点は下記です。4行のみです。

#include <stdio.h>
char uart_message[256] = {0};
    sprintf( uart_message, "\r\n ----- UART TEST ----- \r\n" );
    HAL_UART_Transmit(&huart1, (uint8_t *)uart_message, 64, 5000);

sprintfを使うためにstdio.hをincludeしています。
HAL_UART_TransmitがSTが提供しているHALライブラリです。

注意点は下記です。Renesasも同様に書く場所を指定してきますが、STのは慣れるまで時間がかかる気がします。

/* USER CODE BEGIN XXXXX */
ここに自分のソースコードを書く。
/* USER CODE END XXXXX */

C++を使うとき、小賢しいですが下記で負担を減らすことができます。

cd Core/Src
cp main.cpp main.c
mv main.cpp ~/main.cpp.backup
アイコンをクリックしてソースコード生成
(左ペインのSrcを選択してF5)
左ペインのmain.cを選択してF2
Ctrl + ‘b’か、Runでコンパイルする。

Program Download

USBでMacとNUCLEO G474REを接続します。

[Run]をクリックします。

余計なものを選択していて上手くいかない場合などは、下記を選択します。
Binaries >> g474re_sensor.elf

下記のポップアップが出てきます。

009

初回はST-LINKのFirmwareのupdateをしろ、と出てくるでしょう。
Yes

Open in update mode
Upgrade

010

Ubuntu 20.04.3だと怒られます。
STのCommunityに怒られなくする方法が書いてあります。

How can i fix “Could not determine GDB version using command: ” error ?


$ sudo apt install libncurses5*
[Y/n] Y

成功 !!

Release Buildでも結構デバッガーが止まるのがSTM32

Debug Buildは実行速度が遅いので、Release Buildにします。

[Project] >> [Properties] >> [C/C++ Build]

Configuration

必ず[Manage Configuraitons]をクリックしてください。そして、Releaseを選択してSet Activeをクリックしてください。そうしないと設定が保存されません。
結構色々なところで設定が保存されませんので注意が必要です。

011
012

検証環境としてRaspberry Pi 3 Model B+を使う

ディスプレイが用意されている場合、最も手っ取り早いであろうRaspberry Pi 3を使います。
物理的なワークスペースが必要です。マウスとキーボードも安物でも用意しておく必要があります。

UARTでは簡単すぎて不要ですが、I2CやSPIとなるとロジックアナライザーは必須です。このとき、信号を横取りするためにブレッドボードも必須です。
オシロスコープもあると良いです。

USART1

PC4 : TX
PC5 : RX

TX/RX/GNDを正しく接続します。
NUCLEO G474REのTXと、Raspberry Pi 3 Model B+のRX、
NUCLEO G474REのRXと、Raspberry Pi 3 Model B+のTX、
NUCLEO G474REのGNDと、Raspberry Pi 3 Model B+のGND、
を接続します。

uart.py

import time
import serial

uart = serial.Serial('/dev/ttyAMA0', 115200, timeout=0.5)

while True:
    # time.sleep(0.01)
    # time.sleep(0.02)
    # time.sleep(0.03)
    # time.sleep(0.05)
    # time.sleep(0.1)
    time.sleep(0.2)
    print( uart.readline().decode('utf-8') )
    # print( uart.readline() )
    # print( uart.read() )

‘utf-8’ codec can’t decode byteと怒られる場合もありますが、何回かやっていると通るはずです。

予備は大事

Raspberry Pi 3 Model B+ですが、昨日は何の問題もなく動いていたのに、今日は起動しない、ということが何回かありました。
電源ONにして赤のLEDは点灯するけど、緑のLEDが全く点灯しないのでOSが起動していないとわかります。

Raspberry Pi 3は2つ以上、SDカードは3枚以上もっておくと安心できます。SDカードは同じものにしておきましょう。カードリーダーも2個以上用意しておきましょう。

Macだと、dfで確認して、クローン元のSDカードが/dev/disk2s1で、クローン先のSDカードが/dev/disk3s1とします。
% df
/dev/disk2s1 43233 22041 21192 51% 0 0 100% /Volumes/boot
/dev/disk3s1 258095 53978 204117 21% 0 0 100% /Volumes/boot 1

アンマウントします。

diskutil umount /dev/disk2
or
diskutil umount /dev/disk2s1

diskutil umount /dev/disk3
or
diskutil umount /dev/disk3s1

raw diskの方が速いとのことです。

% sudo dd if=/dev/rdisk2 of=/dev/rdisk3 bs=1m

128GBのSDカードで30分強くらいでした。クローン元だと起動しませんが、クローン先だと起動します。

接触不良か、クローン(コピー)はできるけどRaspberry Pi 3が起動できないくらいの破損か、どちらにしても勘弁してほしいです。

置き換える

通信が確立して、ロジックアナライザーが絶対に不要となったらFTDI UART-USBに置き換えてしまいましょう。

LPUART1を使おうとしたとき、ロジックアナライザーで見ると波形が全く動かないはずです。見える化は大事。

まとめ

瞬殺ですね。

これが瞬殺でなかったら、もうSTの製品は買いません。

参考

電子工作専科

Logic analyzer

入門用に3000円未満で色々と出来ます。
凄い使いやすいというものではないです。

USBケーブルが断線しやすいので、最初から別の市販品を使用した方が時間の無駄が無くて良いです。

USBロジックアナライザ – 24 MHz/8チャンネル


コメントを残すために、Twitter OAuthを必要としています。ご了承ねがいます。
コメントは、Twitterに影響しません。

Twitter OAuth