背景
Arduinoで1MbpsでのRS232通信(UART)はArduino標準ライブラリでは対応していません。
また、Ubuntu上やWindows上でFTDI社のUSB-RS232変換器とFTDI社のDX2xxドライバ及び専用ライブラリを用いることでRS232 1Mbps通信は可能ですが(注: SetBaudRate関数の代わりにSetDivisor関数を用いると最大3Mbpsまでの通信が可能です。)、汎用OS上ではタスク切り替えが起こるので厳密にリアルタイム性を保証することはできません。そこで、今回処理能力に余裕のあるNUCLEO-F303K8(72MHz Clock)を用いて安価、コンパクト、リアルタイムな1Mbps RS232受信が可能か検証しました。
目標
NUCLEO-F303K8でRS232通信を1Mbpsから115200bpsのレート変換を行う環境構築及び、
以下のデバック環境を構築します。また、NUCLEO-BOARD, Arduino, FTDI社USB-RS232変換器の複数なデバイスを用いて環境構築することで、様々な状況でのシリアル通信環境構築方法もついでに紹介していこうと思います。
必要なもの(ハードウェア)
NUCLEO-F303K8
処理能力の高くUARTが2ch以上あるNUCLEO-BOARDであれば何でも構いません。ただし、動作検証はF303K8ボードで行っています。)
FT232XSBreakoutボード SwitchScience社 参考リンク
デバック環境においてPCからUSBポートで1Mbps送信を行う用途で利用します。
FTDI社のライブラリを利用するのでFTDI社の変換チップを利用している製品を
使う必要があります。例えば、参考リンクのような製品であればどれでも構いません。
Arduino Uno R3
デバックのためレート変換後の115200bpsのデータ受信を行う用途で使います。
FTDI社USBシリアル変換アダプターをもう追加で利用してデータ受信プログラムを作成することによっても対応できます。
その他
配線のためにピンヘッダ、ピンソケット、ジャンパワイヤ、ブレッドボードなどを
秋月電子などで準備しておくとよいかと思います。また、USB3ポート利用するため
PCのUSBポートが足りない場合はUSBハブを準備してください。
必要なもの(ソフトウェア)
検証環境ではWindows 10 Home 64bitを用いています。
- eclipse CDT リンク
- STM32CubeMX リンク
- SystemWorkBench リンク
- Arduino IDE [リンク] (https://www.arduino.cc/en/Main/Software)
- Microsoft Visuao Studio 2017 (Community Editionで十分)
- FTDI社 DX2xxドライバ [リンク] (https://www.ftdichip.com/Drivers/D2XX.htm)
- インストール方法に関してはここでは詳細に記述しません。ネット上の他の記事を参照してください。
システム図
####信号の流れ
PCにUSB1で接続されたFT232XSBreakoutボード経由で1Mbpsボーレートでデータが
NUCLEO-F303K8に送信されます。そして、NUCLEO-F303K8は1Mbpsで受け取ったデータを
115200bpsのボーレートでArduinoにデータ転送します。Arduinoは受けとったデータを即座に
Serial 1chに書き込まれ、そのデータはUSB3を通じてPC上のシリアルモニタで確認できます(TeratermやArduino IDE付属のシリアルモニタなど使えます)
作業項目
- FT232XSBreakoutボードセットアップ
- NUCLEO-F303KBセットアップ
- Arduinoセットアップ
上記の順番で必要な作業を説明していきます。
FT323XSBreakoutボードセットアップ
1. FTDI社D2XXドライバダウンロード
FTDI社のサイトからD2XX Direct Driversをダウンロードします。
もしも、BreakoutボードをUSBに接続した際に自動でドライバがインストール
されなかった場合は、デバイスマネージャーからこちらのファイルを指定してドライバを
インストールします。
2.Visual Studio 2017プロジェクトの作成
以下FTDI社のアプリケーションノートを参考に作成しています。
- Win32コンソールアプリケーションプロジェクトを作成します。(空のプロジェクト)
プロジェクト名は"RS232_1Mbps_Send"にしています。
- プロジェクトフォルダの中に先ほど展開したディレクトリの中にあるftd2xx.hとftd2xx.lib(64bitでビルドする場合はamd64フォルダ以下のftd2xx.libファイル)
をコピーします。
- 以下のプログラムをmain.cppとして追加します。
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include "ftd2xx.h"
// ライブラリ読み込み
#pragma comment(lib, "ftd2xx.lib")
int main()
{
FT_HANDLE ftHandle;
FT_STATUS ftStatus;
ftStatus = FT_Open(0, &ftHandle);
ftStatus |= FT_SetUSBParameters(ftHandle, 4096, 4096);
ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0);
ftStatus |= FT_SetTimeouts(ftHandle, 5000, 5000);
ftStatus |= FT_SetLatencyTimer(ftHandle, 16);
ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0x11, 0x13);
// 1Mbps通信を行う場合はSetBaudRate関数でなくSetDivisor関数の利用が必要
// 3Mbps/Divisorのボーレートで通信できる。Divisorを0にすると3Mbps通信になる。
ftStatus |= FT_SetDivisor(ftHandle, 3);
ftStatus |= FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1,
FT_PARITY_NONE);
if (ftStatus != FT_OK) printf("ftStatus not ok %d\n", ftStatus); //check for error
else
{
uint64_t cur_count = 0;
while (1) {
DWORD w_data_len = 9;
DWORD data_written;
char data_out[9];
if (cur_count > 0xFFFFFFFFFFFFFFFE) {
cur_count = 0;
}
sprintf_s(data_out, "%lld\n", cur_count);
printf("Current Value: %d\n", cur_count);
ftStatus = FT_Write(ftHandle, data_out, w_data_len, &data_written);
cur_count++;
Sleep(10);
}
}
ftStatus = FT_Close(ftHandle);
return 0;
}
ASCIIコードの10msec毎にインクリメントされた数値の文字列を送信するコードです。ボーレート1Mbpsの設定のためSetDivisor関数を利用しているので
注意してください(SetBaudRateでは1Mbpsの設定できない)
- プログラムのビルド
プログラムのビルドの際には"Debug" "x64"に設定の上ビルドしてください。
NUCLEO-F303KBセットアップ
必要ソフトウェアインストール
eclipse CDT, STM32CubeMX, SystemWorkBench を
ダウンロードしてインストールします。それぞれダウンロードに会員登録が必要なので注意してください。
STM32CubeMXでのプロジェクト生成
- stm32cubemxにてF303K8と入力してボードを検索し新規プロジェクトを作成します。
- "Pinout"タブから"USART1"と"USART2"を"Asynchronous"に設定します。
- "Clock Configuration"タブから"To USART1"クロックを"64"MHzに設定をします。
デフォルト設定の8MHzから64MHzにクロックを向上させることで1Mbpsを超えるボーレートを設定可能になります
- "Configuration"タブの"Connectivity"の欄に"USART1"と"USART2"をそれぞれクリックして、"USART1"のボーレートを"1Mbps", "USART2"のボーレートを"115200bps"に設定します。
USART1設定
注: データ受信による割り込みの有効化のためNVIC Settingsにて割り込みを"Enable"にチェックを入れる必要があります。
注: データ受信による割り込みの有効化のためNVIC Settingsにて割り込みを"Enable"にチェックを入れる必要があります。
- "Project"-> "Setting"で出力先のプロジェクト名("uart_baudrate_converter")や場所を設定します。また、Toolchain/IDEは"SW4STM32"と設定します。
- "Project"->"Generate Code"でコードを自動生成します。
eclipseでのソースコード編集
-
"File"->"Import"->"Existing Projects into Workspace"でCubeMXで生成したプロジェクトを読み込ませます。
-
main.cにコードを追加します。
While文の中に受信したデータを転送するコード
While文の外に受信バッファの定義をします。
割り込み許可設定をしているのでHAL_UART_Receive_IT関数でデータの到着した
タイミングでシリアルデータを取得することが可能です。
また、gUartReadyフラグのグローバル変数を用いて、HAL_UART_Receive_IT関数終了時に実行されるHAL_UART_RxCpltCallback関数内でgUartReadyをSETされるまでwhile(gUartReady != SET)部分でプログラムが待機状態となります。Transmit_IT関数に関しても送信終了まで待機してバッファ内のデータを送信し終わってから、受信待ち状態に入るようにしています。
コード変更部分 (main.c)
- グローバル変数定義
/* USER CODE BEGIN 0 */
__IO ITStatus gUartReady = RESET;
uint8_t gBuffer[9];
/* USER CODE END 0 *
- 無限ループ
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
gUartReady = RESET;
HAL_UART_Receive_IT(&huart1, gBuffer, sizeof(gBuffer));
// Wait for the end of the receive data
while (gUartReady != SET);
gUartReady = RESET;
HAL_UART_Transmit_IT(&huart2, &gBuffer, sizeof(gBuffer));
// Wait for the end of the transmitting data
while (gUartReady != SET);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
- 受信及び送信完了時実行部分
/* USER CODE BEGIN 4 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
gUartReady = SET;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
gUartReady = SET;
}
/* USER CODE END 4 */
自動生成部分も含めたコードを念のため掲載しておきます。
コード全体 (main.c)
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
** This notice applies to any and all portions of this file
* that are not between comment pairs USER CODE BEGIN and
* USER CODE END. Other portions of this file, whether
* inserted by the user or by software development tools
* are owned by their respective copyright owners.
*
* COPYRIGHT(c) 2018 STMicroelectronics
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f3xx_hal.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
__IO ITStatus gUartReady = RESET;
uint8_t gBuffer[9];
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
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_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
gUartReady = RESET;
HAL_UART_Receive_IT(&huart1, gBuffer, sizeof(gBuffer));
// Wait for the end of the receive data
while (gUartReady != SET);
gUartReady = RESET;
HAL_UART_Transmit_IT(&huart2, &gBuffer, sizeof(gBuffer));
// Wait for the end of the transmitting data
while (gUartReady != SET);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInit;
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = 16;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initializes the CPU, AHB and APB busses 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_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Configure the Systick interrupt time
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
/**Configure the Systick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
/* USART1 init function */
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 1000000;
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.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
/* USART2 init function */
static void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
/** Pinout Configuration
*/
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
}
/* USER CODE BEGIN 4 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
gUartReady = SET;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
gUartReady = SET;
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @param file: The file name as string.
* @param line: The line in file as a number.
* @retval None
*/
void _Error_Handler(char *file, int line)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
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,
tex: 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****/
*プログラムの実行
"Project"->"Build Project"でプロジェクトをビルドします。
その後、コンパイルに成功したら緑色の再生ボタンマークの右側の下三角をクリックして
"Run As"-> "Ac6 STM32 C/C++ Application"をクリックしてプログラムを実行できます。
デバッグの場合は虫マークのとこから"Debug As"->"Ac6 STM32 C/C++ Application"
を選択します。
Arduinoセットアップ
- Arduino IDEをダウンロードしてインストールします。
- 以下のプログラムを張り付けてArduino UNOに書き込みます。プロジェクト名は(uart_paththrough)
void setup() {
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
}
void loop() { // run over and over
if (Serial.available()) {
Serial.write(Serial.read());
}
}
デバッグ用に受け取ったデータをそのままシリアルコンソールに表示させる
プログラムです。
最終確認
ここまでで構築した、
PC->FT232XSBreakout->NUCLEO-F303KB->Arduino->PC(シリアル表示)
のデバッグシステムがちゃんと動作するかを確認します。
Visual Studioで作成した1Mbpsの送信プログラムでインクリメントされる値が
ArduinoのSerial Consoleに表示できれば実験成功です。
結果、NUCLEO-F303K8上に1Mbps->115200bpsボーレート変換ソフトウェアを
構築できたことになります。少し工夫すると
標準外ボーレート[1Mbpsや3Mbps]のRS232通信から他の通信方式(I2C, SPI, CAN)等への変換器 (NUCLEO-F303K8はCAN出力ポートを持つのでRS232->CAN変換が可能です(注: CANトランシーバーMCP2551が別途必要です) などが実現できます。
Visual Studio実行
- プロジェクト(RS232_1Mbps_Send)を実行します
eclipse IDE実行
- プロジェクトuart_baudrate_converterを実行します。
デバッグモードで実行するとプログラムの最初の行で一時停止するので"Resume"
ボタンをおして実行状態にしてください。
Arduino IDE実行
- "Sketch"->"Upload"でuart_paththroughプログラムを書き込みます。
COMポートは必ずArduino/Genuino Unoとなっているポートを選んでください
- "Tools"->"Serial Monitor"でシリアルモニタを立ち上げます
ボーレートを"115200bps"に設定し"Show Timestamp"にチェックをいれると
以下のように表示することができます。