stm32FのDMA Circular mode を使ったUART受信

stm32FのUARTでシリアルデータを受信するのにDMAのcircularモードを使うとDMAだけでデータをリングバッファに読み込むことが出来ます。
リングバッファからの読み出し関数は、DMAのポインタ(huart_cobs->hdmarx->Instance->CNDTR)をリングバッファへの書き込みポインタとして、読み出しポインタで書き込みポインタを追いかけるようにするだけです。

この手法は割り込みン数を使わずにデータをリングバッファに取り込めるのでとても簡単で効率が良いのですがSTM32CubeMXのHALライブラリを使っているとエラーが起きた時に勝手に送受信を止められてしまうという困った現象が起きます。

解決法を探していると同じことをやっている人がいて、「受信エラーはCRCチェックで対処するからHALライブラリのエラーハンドリングは邪魔なのでこうやってやめさせたよ。」という回答をみつけました :-P (下のトピック)。

Topic: ‘Best’ way to load UART data to ring buffer with STM32/HAL
このトピックの投稿者が「この方法がとてもクールだと思わないか?」と言ってるのに対して、別の人が「自分もそう思う、だけどエラーハンドリングには注意した方がいいよ。」とアドバイスを書いています。

受信部のソースコード : スマートにまとめられているので参考に転載しておきます。

/*
* The STM32 makes receiving chars into a large circular buffer simple
* and requires no CPU time. The UART receiver DMA must be setup as CIRCULAR.
*/
#define CIRC_BUF_SZ       64  /* must be power of two */
static uint8_t rx_dma_circ_buf[CIRC_BUF_SZ];
static UART_HandleTypeDef *huart_cobs;
static uint32_t rd_ptr;

#define DMA_WRITE_PTR ( (CIRC_BUF_SZ - huart_cobs->hdmarx->Instance->CNDTR) & (CIRC_BUF_SZ - 1) )

void msgrx_init(UART_HandleTypeDef *huart)
{
    huart_cobs = huart;
    HAL_UART_Receive_DMA(huart_cobs, rx_dma_circ_buf, CIRC_BUF_SZ);
    rd_ptr = 0;
}

static bool msgrx_circ_buf_is_empty(void) {
    if(rd_ptr == DMA_WRITE_PTR) {
        return true;
    }
    return false;
}

static uint8_t msgrx_circ_buf_get(void) {
    uint8_t c = 0;
    if(rd_ptr != DMA_WRITE_PTR) {
        c = rx_dma_circ_buf[rd_ptr++];
        rd_ptr &= (CIRC_BUF_SZ - 1);
    }
    return c;
}

 

エラーへの対処 : HALの余計なお世話を無効にする方法、これが私の探していた答えです。

/* These uart interrupts halt any ongoing transfer if an error occurs, disable them */
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_ERR);

JDEでSTM32CubeMXが生成したプロジェクトを読み込む

STM32CubeMXは組み込み開発で最も面倒で退屈なハードウェアの初期化部分をGUIを使って設定して初期化済みのプログラムテンプレートを生成してくれるツールです。
統合開発環境JDEはSTM32CubeMXが生成したプロジェクトをインポートすることが出来るようになりました。

ここではSTM32CubeMXが生成したプロジェクトをベースにJDEのプロジェクトを作る手順を簡単に説明します。

 

1.STM32CubeMXで作成したプロジェクトを出力するためにProject → Settingsを開きます。

2.Project Settings画面のToolchain/IDE設定でTrueSTUDIOを選び GenerateUnderRootのチェックを外します。
他の項目はそのままでOKです。

3.Project → Generate Codeをクリックしてコードを生成します。

4.Codeが生成されたらそのままメッセージを閉じます。

プロジェクト保存場所に次のようなフォルダとファイルが作成されているはずです。

5.JDEを開き新規プロジェクトを作成します
プロジェクトを作成する場所はSTM32CubeMXでプロジェクトを保存したフォルダにして下さい。

6.JDEのCpudef(CPU定義)はSTM32CubeMXのHALライブラリと同じものを選んでください。

7.CPU定義ファイルだけのプロジェクトが作成されます。

8.TrueStudioプロジェクトインポートメニューを開きます。

9.インポート元としてTrueSTUDIOプロジェクトを指定して、開くをクリックします

10.プロジェクトがインポートされ、ライブラリとソースファイルを含むプロジェクトツリーが表示されます。

プロジェクトのシンボル定義でプロジェクトの構築に必要なのは上の2行です、赤枠で囲んだ部分はコンパイルエラーの原因になるので削除してください。

11.プロジェクトは構築可能になっているはずです、再構築ボタンをクリックしてコンパイルしてみます。

12.下のコンパイルメッセージに「コンパイルとリンクが終わりました」と表示されればテストOKです。

13.STM32CubeMXが生成したプロジェクトは次のような構成になっています。

./Drivers/の下にあるファイルはHALライブラリで通常は変更する必要ありません。
./Src/の下にあるファイルを修正、追加してプログラムを作っていきます。

例えばmain.cは次のような内容です
/*  USER CODE BEGIN … */ と/* USER CODE END …*/で囲まれた部分はSTM32CubeMXで設定を変更してプロジェクトを再生成しても変更されずに残りますのでこの部分にユーザーコードを追加していきます。

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"
// ※ ここに追加したコードはSTM32CubeMXでプロジェクトを再生成すると失われる
/* USER CODE BEGIN Includes */
// ※ ここに書いたコードはSTM32CubeMXの影響を受けない
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim5;

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_TIM5_Init(void); 
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);


/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

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();

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

/* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_TIM5_Init();

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1)
 {
 /* USER CODE END WHILE */

/* USER CODE BEGIN 3 */

}
 /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{
  .. 
 この後に初期化関数が続きます。

小型DCモーターのPWM制御

マイコンカーラリーでは4個のDCモーターをPWMで制御しています。
回転センサを付けてフィードバック制御をすればDCモーターの速度をきちんと制御出来るため制御の話を検索しても殆どこのような「ちゃんとした速度制御の話」しか見つかりません。

ところがマイコンカーラリーは仕様上の制限で4つのDCモーターとひとつのエンコーダで速度制御をおこなうシステムになっています。
直進の場合は4つのDCモーターを同じ条件で駆動すればそれなりにきちんとした速度制御が出来そうですが,カーブではステアリング用のサーボだけでなく左右の前輪のモータートルクを変えてやらないとスムーズなコーナリングが出来ません。
それでステアリングの舵角に応じた左右のPWM比を決めるテーブルを作って対処していますが、その調整が結構難しいらしいのです。

その話を聞いてPWMの値とトルク、回転数の関係について情報を検索してみたところ興味深い記事が2件見つかりました。

1.アニキの極秘開発記 : モータのPWM駆動周波数

ロボトレースなどを手掛けている人のブログです。
ロボトレースはマイコンカーラリーと似た競技ですがロボットのレギュレーションにサイズの制限だけのため、マイコンカーラリーより高度な制御ができるマイコンを使うことが出来ます。
興味を惹かれたのは”PWMの駆動周波数は電気的時定数より低く、機械的時定数より高い値に設定すると低速域でもスムーズな動作でトルクを得ることができる”という件でした。
ではどのくらいの速度がその領域に入るのかという疑問については次のサイトにヒントがありました。

2.写真で見る工作室 : DCモーター制御

工作好きの人が書いているサイトで、実際に小型DCモータを駆動する基板を作って駆動方式別にPWM周期と回転数の関係をプロットしたグラフを公開されています。

上の2つのサイトで得た情報をまとめると次のようになります。

まずモータ駆動のブリッジ回路には次の3つの駆動方式があります。

A.電流継続モード(ブレーキモード)

ブリッジ回路電圧OFF時にモーターの回生電流(発電電流)が流れるループを作り電流が継続して流れるモードです(下の参考図)。

この場合モータのインダクタンスが大きい、あるいはPWM周波数が高いほど流れる電流の変動が小さくなり定電圧駆動の挙動と近くなり、一般的なモーター制御で使われてます。

無負荷の場合PWMデューティと回転数の関係は下図のようにリニアになっています。

ブレーキモードでのPWMデューティ(縦軸1024=100%)と回転数(横軸)のグラフ
写真で見る工作室 : DCモーター制御より

B.電流不連続モード(フリーモード)

ブリッジ回路電圧OFF時に回生電流が流れないようにスイッチをOFFにして、電流が断続的に流れるようにしたモードです。
使われることは少ないですがマイコンカーラリーでは制御特性の違いを活かして使い分けられたりしています。

フリーモードでのPWMデューティ(縦軸1024=100%)と回転数(横軸)のグラフ
写真で見る工作室 : DCモーター制御より

デューティ20%(200/1024)を超えないとモーターが回転を始めず、「フリーモードは効率が悪い」と言われているのを裏付けるグラフになっています。
一方、低回転数でもある程度の電流が流れているということになるのでPWM周期を下げた時と同じく「低速域でもスムーズな動作でトルクを得ることができる」という状態になっているのではないかと思われます。

C.電圧逆転モード(正式名称ではありません)

ブリッジ回路に加える電圧を交互に切り替える方式です。
デューティ比50%の時電流が0になりデューティ比によってモーターの回転方向を変えることが出来ます。
大昔、私がDCモータ駆動のマイクロマウスを初めて作った時になかなかモーター制御がうまくいかず最終的にこのモードで何とか姿勢制御が出来た記憶があります。
その経験も併せて考えて、この方式はフリーモードよりさらに効率が悪いがオープンループで使うときに低速域でトルクが確保できるモードと思われます。

フリーモードでのPWMデューティ(縦軸1024=100%)と回転数(横軸)のグラフ
写真で見る工作室 : DCモーター制御より