製作モジュール一覧

ラベル UART の投稿を表示しています。 すべての投稿を表示
ラベル UART の投稿を表示しています。 すべての投稿を表示

2019年3月13日水曜日

STM32: UARTでprintfを使う(浮動小数点型あり)

STM32のUARTでprintf関数を使ってみました。

参考にした記事
「ガレスタさんのDIY日記」さんの「STM32でUARTをやってみる6(float型printfをUART経由で出力)
「@take-iwiw」さんの」「STM32F4 Discovery BoardとCube MXの環境構築、Lチカからprintfポーティングまで
「滴了庵日録」さんの「CubeMX環境でprintfを使う

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/UART_printf_test1

UART自体の基本的な使い方は「STM32CubeとTrueSTUDIOでUART通信」にあります。

ソースコード(一部)

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

<stdio.h>をインクルードします。

/* USER CODE BEGIN PV */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
 return ch;
}
/* USER CODE END PV */

int __io_putchar()で、HAL_UART_Transmit()をラッピングするコードです。これによってprintf()内部でHAL_UART_Transmit()が呼ばれるようになります。


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  const char msg[] = "CNT:";
  int cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */
 float fv = cnt * 0.01f;
 printf("%s\t%d\t%f\r\n", msg, cnt, fv);
 cnt++;
 fv += 0.01;
 HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

メインループ内で、printf()を呼び出しています。文字列、整数、浮動小数点数を使っています。

<追記:2019.03.13>

ソースコードの「fv += 0.01;」は無駄な演算です。

</追記>


Puttyで受信している様子


メモ:


printf()で浮動少数点数を使うと、「-u _printf_float」せよと、IDEでアラートが表示されます。[MCU GCC Linker]-[Miscellaneous]の[Linder flags]で「-u _printf_float」を指定します。


STM32CubeMX Ver.5.1.0では、Generate Codeすると「syscalls.c」は<projectフォルダ>/Srcに入っています。

2019年2月6日水曜日

STM32CubeとTrueSTUDIOでUART通信

Arduinoやmbedで最初にやったことはLチカ、その次はUART通信(Serial)でした。

TrueSTUDIOとSTM32CubeでもUART通信をやってみました。TureSTUDIOはDebuggerが統合されているのでデバッグにはそっちを使えばいいという話もありますが、ロギングやらなんやかやでUARTも必要になりそうです。

環境は

  • Nucleo-F446RE
  • STM32CubeMX Version 5.0.1
  • STM32Cube FW_F4 V1.23.0
  • TrueSTDUIO Version: 9.2.0
  • Windows10 + Putty0.7.0

参考にしたドキュメントは
ST公式の「UM1718 User manual STM32CubeMX for STM32 configuration and initialization C code generation」の「11  Tutorial 4 - Example of UART communications with an STM32L053xx Nucleo board」です。Nucleo-F446RE用に一部読み替えています。

STM32CubeMXでCコードの初期化


STM32CubeMXで「New Project」 - 「Board Selector」を選択。「NUCLEO-F446RE」を選択し、「Start Project」


デフォルトモードで初期化。「Initialize all peripherals with their default Mode?」で「Yes」を選択


「SYS」 - 「Debug」 - 「Serial Wire」が選択されているのを確認


「Timers」 - 「TIM2」 - 「Clock Source」 「Internal Clock」を選択


※TIM2は周期的にTimer割り込みを発生するものでUART通信に必須ではありません。

「Connectivity」 - 「USART2」 - 「Mode」 - 「Asynchronouse」が選択されているのを確認


「Pinout」 - 「Pinout view」で下記のピンがアサインされているのを確認

  • PA13 TMS (SWD:SWDIO)
  • PA14 TCK (SWD:SWCLK)
  • PA2  USART_TX
  • PA3  USART_RX


「Clock Configuration」 デフォルトのまま


※ドキュメント(UM1718)ではHCLKが2.097MHzになっていますが、F446のデフォルトの84MHzでテストしました。

「Pinout & Configuration」タブの「USART2」 - 「Parameter Setting」で

  • 「Baud Rate」を「9600 Bits/s」に設定
  • 「Data Direction」で「Receive & Transmit」が選択されているのを確認


「TIM2」 - 「Parameter Setting」で

  • 「Prescaler」を「16000」に設定
  • 「Counter Period」を「1000」に設定


「TIM2」 - 「NVIC Settings」で

  • 「TIM2 global interrupt」の「Enabled」をチェック


「Project Manager」 - 「Project」 - 「Toolchain / IDE」で「TrueSTUDIO」を選択


「Code Gengerator」 「HAL Settings Set all free pins as analog」をチェック


「Generate Code」して、「Open Project」を選択


TrueSTUDIOでコードの追加・実行


TrueSTUDIOが立ち上がるので、C/C++パースペクティブでソースに以下のコードを追加。

C/C++パースペクティブでツールバーの「デバッグ(虫のアイコン)」をクリック → デバッグパースペクティブでツールバーの「再開( [|>] みたいなアイコン)」でプログラムが実行されます。

Puttyの設定


Puttyに以下のような文字列が表示されました。


TIM2の「Counter Period」を「10000」に増やすとPutty上でゆっくりと表示されるようになりました。

HCLKが84MHz、プリスケーラが16000なので
84MHz / 16000 = 5.25kHz
Counterが1000の場合5.25Hzなので割り込み周期は約190ms
Counterが10000の場合0.525Hzなので割り込み周期は約1.9s

メモ:


STM32CubeのHALの使い方の習得がそこそこ大変そうですが、飼い馴らせば強力そうです。

SPI、I2C、I2S、FPUあたりの使い方から探っていくつもりです。音絡みがメインなのでCMSIS DSPも使ってみたいですが1年ぐらいかかるかな。

2016年10月2日日曜日

Arduino同士でSerial通信(UART)して最大速度を調べる。

配線図

信号線はRXとTXの2本だけ。UART通信ではRX-RX、TX-TXではなく、RX-TX、TX-RXという風に接続する。

SerialでPCと通信できなくなるので、8個のLEDを状態表示用として接続した。


Arduinoのスケッチ


TX側
Serial_Write_Test.ino

#define BAUD_RATE  (460800)

byte sendChar = 0;

void setup() {
  Serial.begin(BAUD_RATE);
}

void loop() {
  if (Serial.availableForWrite() > 0) {
    Serial.write(sendChar);
    sendChar++;
  }
}

Serial.begin()でUART通信速度(BAUD_RATE)を設定。RX側でも同じ値を指定する。送信データはBYTE型をインクリメント。

RX側
Serial_Read_Test.ino

#define BAUD_RATE  (460800)

byte prevRv = 0;
byte errorCnt = 0;

void dispNum(int num) {
  digitalWrite(2, (num & 0x01));
  digitalWrite(3, (num & 0x02));
  digitalWrite(4, (num & 0x04));
  digitalWrite(5, (num & 0x08));
  digitalWrite(6, (num & 0x10));
  digitalWrite(7, (num & 0x20));
  digitalWrite(8, (num & 0x40));
  digitalWrite(9, (num & 0x80));
}

void setup() {
  // LED Check
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
 
  for (int i = 0; i < 5; i++) {
      digitalWrite(2, HIGH);
      digitalWrite(3, HIGH);
      digitalWrite(4, HIGH);
      digitalWrite(5, HIGH);
      digitalWrite(6, HIGH);
      digitalWrite(7, HIGH);
      digitalWrite(8, HIGH);
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(2, LOW);
      digitalWrite(3, LOW);
      digitalWrite(4, LOW);
      digitalWrite(5, LOW);
      digitalWrite(6, LOW);
      digitalWrite(7, LOW);
      digitalWrite(8, LOW);
      digitalWrite(9, LOW);
      delay(100);
  }  
  Serial.begin(BAUD_RATE);
}

void loop() {
  if (Serial.available() > 0) {
      int rv = Serial.read();
      prevRv++;
      if (rv != prevRv) {
        errorCnt++;
        dispNum(errorCnt);
      }
      prevRv = rv;
      digitalWrite(10, !digitalRead(10));
  }
}

PORTDを全部使えればもっと簡単になるが、UARTでPD0、PD1を使うのでめんどくさい書き方になってしまった(@@;

RX側のDispNum()で8本のLEDでBYTE型のデータを表示できるようにした。受信データをそのまま表示しても早すぎて視認できないので、受信データが1ずつインクリメントされているかどうかを監視して、値が異なったらエラーカウント(errorCnt)をインクリメントしてLEDに表示。

結果

115200 OK
230400 OK
460800 NG
961600 NG

230400bpsまではLEDの点灯状態が変化しないが、460800bps以上ではLEDが全点灯する(最上位ビットはちらつく程度に点滅)

エラーなく送受信できるのは230400bps程度までだろう。

通信波形

230400bps(OK)

ch1:RX側D10 ch2:RX側D1

ch1のD10は1byte分受信したときにH/Lを切り替えている。ch2はUARTの通信波形。

460800bps(NG)

メモ:


公式のAruduino Uno Rev.3とaitendoのびんぼうでいいのを使った。シリアルポートの割り当ては以下の様に。


公式のArduino Unoは「Arduino Uno(COM3)」、びんぼうでいいのは「USB-SERIAL CH340(COM6)」と表示されている。(公式同士だとややこしいかも?)

Arduino IDEで書き込みするときにArduino同士のUART通信の配線をいったん外さないと書き込みがうまくいかないので注意。

2016年6月16日木曜日

ArduinoとNucleoでPCとのSerial(UART)通信の最高速度を調べてみる。

SPIやI2Cの通信状態を解析しようと思うと補足した信号をPCに送ってから見やすいように整形・グラフ化するのが良さそうだ。

Arduinoでは、I2Cはデフォルトで100kbps、SPIはよく覚えていないが数Mhzぐらいだった気がする。

ArduinoのSerialはデフォルトが9600bps(9.6kbps)なので、I2Cと比べても明らかに遅い。

PCとの通信にはUSB HIDも使えそうなのでWebで調べてみたがUARTより特に速い感じでもない。

SDカードに一回保存して…とか、TCP-IP/Wi-fiを使ってみるとか一瞬思ったがかなりめんどくさそう。

なのでPCとArduino Uno/NucleoとのSerial通信をどれぐらいの速度でできるか調べてみた。


Arduino Uno


最高1Mbpsで送信できたが、PC(putty)で受信すると取りこぼしが結構ある。

Arduinoのスケッチ(SerialSpeedTest.ino)

#define BAUD_RATE 1000000
#define LOOP_N    1000
#define SEND_CHR  (0x55)

void setup() {
  Serial.begin(BAUD_RATE);
  Serial.println("***Serial Speed Test Start***");
}

void loop() {
  char strBuffer[80];

  Serial.print("\r\n\nBaud Rate: ");
  Serial.println(BAUD_RATE);
  
  unsigned long startTime = micros();
  for (int i = 0; i < LOOP_N; i++) {
    Serial.write(SEND_CHR);
  }
  unsigned long duration = micros() - startTime;
  unsigned long bps = (unsigned long)(8 * 1000000.0f * (float)LOOP_N  / (float)duration);
  
  Serial.print("\r\n");
  sprintf(strBuffer, "\r\n%lu bits/second\r\n", bps);
  Serial.print(strBuffer);
}


1Mbpsを指定した場合はおおよそ1Mbpsで出力できた。


ch1:D0(RX) ch2:D1(TX)

送信するバイトは #define SEND_CHR で指定した0x55なので、ビットにすると0b01010101になる。したがってオシロの表示値の499.3KHzの倍の998.6kHzが送信レートになる。

puttyの受信のようす

’U'がいっぱい並んでいるが、0x55をASCIIコードで文字にすると'U'になる。

ボーレートが1Mbpsで実際送信できたのは800kbps弱。しかも時々読みこぼしてしまうようだ。

Nucleo F401RE


main.cpp(Nucleo_Serial_Speed_test)

#include "mbed.h"

#define BAUD_RATE 2000000
#define LOOP_N    1000
#define SEND_CHR  (0x55)

Serial pc(SERIAL_TX, SERIAL_RX);
Timer timer;

int main()
{
    pc.baud(BAUD_RATE);
    pc.printf("***Serial Speed Test Start***");
   
    timer.start();
   
    while(1) {
        pc.printf("\r\n\nBaud Rate: %d\r\n", BAUD_RATE);
       
        timer.reset();

        for (int i = 0; i < LOOP_N; i++) {
            pc.putc(SEND_CHR);
        }
       
        uint32_t bps = (uint32_t)(8.0f * (float)LOOP_N / timer.read());
       
        pc.printf("\r\n\n%u bits/secon\r\n", bps);
    }
}


表示値が1MHzなので、同じく倍にして2Mbps。Arduino互換のD0、D1には信号が出ていなくて、分割できるようになっている通信用(?)の基板のRX、TXと書いてあるところでUART信号を捕捉できた。

F401REをmbedで使うと2Mbpsが最大のようだ。それ以上を指定すると信号が出ないか、めちゃくちゃ遅くなってしまう(1kHz以下)

メモ:


Nucelo F401REは最大84MHz駆動となっているが、ほんとにそのクロックで動作してるのだろうか?mbedだとクロックの計測方法がわからない。


Nucleoのボード上ではX3と書いてあるところにクリスタルを刺すような表示になっているが、実装されていない。

PSoC 5LPでもやりたかったが、UARTコンポーネントのinternal clockだと921,600bpsまでしか指定できない。External Clockを指定すればもっとSpeedを上げられそうだがまた気が向いたらやるかも(DataSheetには4Mbpsまで、みたいな記述がある)

Arduinoで1Mbpsはムリそう、Nucleo F401REで2Mbps。←データの取りこぼしがあるかどうかは要確認。

I2Cなら2信号、SPIでもSCKとMOSIに限れば2信号なので、ランレングス圧縮とかして通信コストを下げてやれば、なんちゃってLogic Analyzerはなんとか出来そうもするがPC側のプログラムを作るのもしんどそうだなあ(^q^;

フルスピードのUSBを使うにはどうすればいいんじゃろ。