製作モジュール一覧

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

2019年4月14日日曜日

STM32: u8g2を使おうという試み(未完)

Arduinombedで、中華製のSSD1306のOLEDとu8g2ライブラリを組み合わせてよく使っています。STM32Cubeでも使えないか試してみました。

参考
「Elastic Notes」さんの「u8g2 library usage with STM32 MCU
「olikraus」さんの「Use U8g2 with STM32 #356
「olikraus」さんの「Template for the GPIO and Delay callback

「Elastic Notes」さんの記事がよくできているので、ほとんどそのまま試しました。

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

実行環境

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

配線図


SSD1306 Arduino Header Pin Name Function Lavel
D0 D13 PA5 SPI1 SPI1_SCK
D1 D11 PA7 SPI1 SPI1_MOSI
CS D10 PB6 GPIO SPI1_CS
DC D9 PC7 GPIO OLED_DC
RES D8 PA9 GPIO OLED_RES

一応100uFのOSコンで電源をデカップリングしています。

STM32CubeMXの設定


Clock Configuration
  PLL Source Mux: HSE
  HCLK (MHz): 180

GPIO-GPIO

GPIO-SPI1

SPI1

SW4STM32でコードを追加


GPIOと遅延と、SPI通信の2つのCallback関数を追加

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t u8x8_stm32_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8,
    U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int,
    U8X8_UNUSED void *arg_ptr)
{
  switch (msg)
  {
  case U8X8_MSG_GPIO_AND_DELAY_INIT:
    HAL_Delay(1);
    break;
  case U8X8_MSG_DELAY_MILLI:
    HAL_Delay(arg_int);
    break;
  case U8X8_MSG_DELAY_10MICRO:
    for (uint16_t n = 0; n < 320; n++)
    {
      __NOP();
    }
  break;
  case U8X8_MSG_DELAY_100NANO:
    __NOP();
  break;
  case U8X8_MSG_GPIO_CS:
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, arg_int);
    break;
  case U8X8_MSG_GPIO_DC:
    HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int);
    break;
  case U8X8_MSG_GPIO_RESET:
    HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, arg_int);
    break;
  }
  return 1;
}

uint8_t u8x8_byte_4wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
    void *arg_ptr)
{
  switch (msg)
  {
  case U8X8_MSG_BYTE_SEND:
    HAL_SPI_Transmit(&hspi1, (uint8_t *) arg_ptr, arg_int, 10000);
    break;
  case U8X8_MSG_BYTE_INIT:
    break;
  case U8X8_MSG_BYTE_SET_DC:
    HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int);
    break;
  case U8X8_MSG_BYTE_START_TRANSFER:
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    __NOP();
    break;
  case U8X8_MSG_BYTE_END_TRANSFER:
    __NOP();
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
    break;
  default:
    return 0;
  }
  return 1;
}
/* USER CODE END 0 */

「Elastic Notes」さんは、CSピンがないタイプのSSD1306 OLEDを使われていますが、私の手持ちのものはCSピンがあるタイプなので、Callback関数にCSの処理を追加しています。

u8g2ライブラリの準備


u8g2/csrcをプロジェクトのDriverディレクトリに配置、名前をu8g2に変更。

このパスをインクルードに追加。[Project]->[Properties]->[C/C++ Build]->[Setting]->[Includes]で
../Drivers/u8g2
を追加。

main.cファイルの先頭にu8g2.hを追加。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "u8g2.h"
/* USER CODE END Includes */

グローバル変数u8g2を配置。

/* USER CODE BEGIN PV */
static u8g2_t u8g2;
/* USER CODE END PV */

u8g2をセットアップ。

  /* USER CODE BEGIN 2 */

  u8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay);
  u8g2_InitDisplay(&u8g2);
  u8g2_SetPowerSave(&u8g2, 0);
  /* USER CODE END 2 */

メインループで描画処理

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

    /* USER CODE BEGIN 3 */
    u8g2_FirstPage(&u8g2);
    do
    {
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
      u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");
      u8g2_DrawCircle(&u8g2, 64, 40, 10, U8G2_DRAW_ALL);
    } while (u8g2_NextPage(&u8g2));
  }
  /* USER CODE END 3 */

Buildすると実行可能ファイルが大きすぎてエラーになるので、未使用のコードとデータを除去するオプションを設定。


実行結果


以上で冒頭の画像の通りOLEDに描画されるのですが、なぜだかSCKにオシロのプローブを当てていないと描画されません。SCKにプローブを当てていれば必ずOKというわけでもなく、たまに描画に失敗します。オシロのプローブもx1モードではだめで、x10モードのときにだけ描画されます。どういうこと?

オシロのプローブの仕様から、入力インピーダンス10MΩ・20pFぐらいでGNDにプルダウンすれば動作するんでしょうか?とりあえず1MΩ以上の抵抗は持ち合わせていないので検証できません。

それにしても、あまりにも厳しい条件です。

Arduinoでは似たような条件で特別なことをしなくても問題なく動作します。

参考「温度・湿度・気圧センサーのBME280をArduino Pro Miniで使う。

問題はおそらく違うところにあると思うので、しばらく放置して、気が向いたらまたテストしたいと思います。

メモ:


SPI1のSCKに割り当てているPA5(D13)はNucleo Board上でLEDに接続されている。ただし、Arduinoも同様で、Elastic Notesさんの検証でもSCKはPA5。

SPI通信波形


MOSI

ch1:MOSI(D1) ch2:SCK(D0)

RES

ch1:RES ch2:SCK(D0)

RESはReset時にLow。通常はHigh


DC

ch1:DC ch2:SCK(D0)

DCはWrite Command時にLow。Write Data時にHigh。

CS

ch1:CS ch2:SCK(D0)

CSはSlave選択時にLow。

2019年4月8日月曜日

STM32: I2C接続のLCD:AQM1602を使う(Nucleo-F446RE)

秋月のI2C接続のキャラクタLCDのAQM1602を動作させました。

I2Cについては以下の記事を参考にしました。


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

実行環境

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

配線

SCLとSDAは4.7kΩでPullUpしています。

STM32Cubeの設定


Connectivity
  I2C1
    Mode: I2C
  Prameter Settings
    Master Features
      I2C Speed Mode: Standard Mode
      I2C Clock Speed(Hz): 100000
   
Pinout ViewでPinを割り当て
  PB8: I2C1_SCL
  PB9: I2C1_SDA

SW4STM32でコードを追加(一部抜粋)


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define AQM1602_ADDRESS (0x3e << 1)
/* USER CODE END PD */

AQM1602のI2Cアドレスを指定します。Arduinoは7bit右詰めですが、STM32Cubeは7bitで左詰めなので左シフトしています。

参考「I2C addressing on STM32F4-Discovery

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

// I2C LCD : Akizuki AQM1602

void lcd_cmd(uint8_t x)
{
  uint8_t aTxBuffer[2] = { 0x00, x };
  while (HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)AQM1602_ADDRESS, (uint8_t*)aTxBuffer, 2, 1000) != HAL_OK) {
    if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
    {
      Error_Handler();
    }
  }
}

void lcd_data(uint8_t x)
{
  uint8_t aTxBuffer[2] = { 0x40, x };
  while (HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)AQM1602_ADDRESS, (uint8_t*)aTxBuffer, 2, 1000) != HAL_OK) {
    if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
    {
      Error_Handler();
    }
  }
}

void lcd_puts(const char *s)
{
  while(*s) lcd_data(*s++);
}

// param:
//  contrast: 0 ~ 63 (最初は大きくして調整)
void lcd_init(uint8_t contrast)
{
  // LCD initialize
  lcd_cmd(0x38); // function set
  lcd_cmd(0x39); // function set
  lcd_cmd(0x14); // interval osc
  lcd_cmd(0x70 | (contrast & 15)); // contrast low
  lcd_cmd(0x5c | (contrast >> 4 & 3)); // contrast high / icon / power
  lcd_cmd(0x6c); // follower control
  HAL_Delay(300);
  lcd_cmd(0x38); // function set
  lcd_cmd(0x0c); // display on
  lcd_cmd(0x01); // clear display
  HAL_Delay(2);
}

void lcd_move(uint8_t pos){
  lcd_cmd(0x80 | pos);
}

void lcd_pos(uint8_t raw, uint8_t col) {
  lcd_cmd(0x80 | ((raw & 0x01) << 6) | col);
}

void lcd_clear() {
  lcd_cmd(0x01);
}

/* USER CODE END 0 */

AQM1602の制御関数です。

  /* USER CODE BEGIN 2 */
  lcd_init(32);
  lcd_puts("Hello STM32");
  lcd_pos(1, 0);
  lcd_puts("AQM1602");
  /* USER CODE END 2 */

AQM1602を初期化。


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    char strBuffer[17] = {0};
    sprintf(strBuffer, "CNT=%05d", cnt);
    lcd_pos(1, 0);
    lcd_puts(strBuffer);
    cnt++;
    HAL_Delay(100);
  }
  /* USER CODE END 3 */

メインループではインクリメントした数値の表示とLチカを行っています。

I2C通信のようす

ch1:SDA ch2:SCL

2019年4月2日火曜日

STM32: 内蔵DACをDMA経由で使う(Nucleo-F446RE)

内蔵DACをDMA経由で使ってみました。データをあらかじめ用意できれば、DMAにデータ転送をまかせて、CPUは他の処理が行えます。

参考
「Elastic Notes」さんの「Usage DMA and DAC in STM32CubeMX

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

実行環境

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

4096サンプルのサイン波テーブルを作成して内蔵DACから出力します。

STM32CubeMXの設定


Clock Configuration
  PLL Source Mux: HSE
  HCLK: 180MHz

Pinout & Configuration
  Timers
    TIM6
      Mode: Activatedをチェック
      Counter Period: 220
      Trigger Event Selection: Update Event

180MHz駆動するとAPB1 Timer clocksは90MHzになります。4096サンプルで1波形のサイン波を出力するので、1kHzのサイン波を出力しようとすると
90MHz / prescaler / 4096 = 1kHz
なので
prescaler = 90MHz / (1kHz * 4096) ≒ 22.0
ですが、prescalerを22に設定するとDACから信号が出力されませんでした。100Hzにしようとprescalerを220に設定するとOKでした。

  Analog
    DAC
      Mode: OUT1 Configurationをチェック
      Prameter Settings
        Trigger: Timer 6 Trigger Out event
      NVIC Settings
        Enableをチェック
      DMA Settings
        DMA Request: DAC1を追加
        Mode: Circular
        Increment Address: Memory
        Data Width: Half Word
     

 SW4STM32でコードを追加(一部抜粋)


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

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define SINE_TABLE_SIZE (4096)
/* USER CODE END PD */

/* USER CODE BEGIN PV */
uint16_t sine_wave_array[SINE_TABLE_SIZE];
/* USER CODE END PV */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void generateSineWave(void)
{
  for (int i = 0; i < SINE_TABLE_SIZE; i++) {
      float fv = ((float)i / SINE_TABLE_SIZE) * 2.0f - 1.0f;
      float phi = (float)M_PI * fv;
      sine_wave_array[i] = 4095 * ((sinf(phi) + 1.0f) / 2.0f);
  }
}
/* USER CODE END 0 */

  /* USER CODE BEGIN 2 */
  generateSineWave();
  HAL_TIM_Base_Start(&htim6);
  HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave_array, SINE_TABLE_SIZE, DAC_ALIGN_12B_R);
  /* USER CODE END 2 */

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

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

Memory→DACのデーター転送はTimer駆動(TIM6)のDMAにまかせているので、プログラム(while(1)ループ)では何もしていません。

出力波形

いつものように、サイン波形の最大振幅付近で若干クリップしています。

99.6Hzのサイン波になりました。水晶がクロック源なので少し誤差が大きいような・・・(@@?

Prescaler Prescler(丸め) 誤差
219.7265625 220 0.1244%

期待周期(ms) 実測周期(ms) 誤差
10 10.04 0.4000%

2019年3月30日土曜日

STM32: 内蔵DACを使う(Nucleo-F446RE)

Nucleo-F446REの内蔵DACからSIN波を出力しました。

参考
<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\DAC\DAC_SimpleConversion

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

実行環境

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

STM32CubeMXの設定



STM32CubeMXに[Report]という機能があったので出力してみました。

https://github.com/ryood/STM32Cube_Test/blob/master/SW4STM32/Nucleo-F446_DAC_Test5/Nucleo-F446_DAC_Test5.pdf

SW4STM32でコードを追加(一部抜粋)

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

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  double phi = 0.0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    uint16_t sinv = 4095 * ((sin(phi) + 1.0) / 2.0);
    if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sinv) != HAL_OK)
    {
      Error_Handler();
    }
    if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_12B_R, sinv) != HAL_OK)
    {
      Error_Handler();
    }
    phi += 0.01;
  }
  /* USER CODE END 3 */
}

位相角を0.01ずつ増やしてsinの値を求め、12bit値に変換して2chある内蔵DACから出力しています。

出力結果


ch1:DAC1(PA4) ch2:DAC2(PA5)

DAC2はNucleoボード上でLEDに接続されているため、上側がクリップしています。DAC1も上下の最大振幅付近で若干クリップしています。

2019年3月26日火曜日

STM32: SPI ADCのMCP3208を使う(Nucleo-F446RE)

SPIのマスター側が読み取る必要があるA-DコンバーターのMCP3208を動作させました。

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

実行環境

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

配線


STM32CubeMXの設定


CLOCK Confiiguration

[HCLK]: 128

プリスケーラ-で分周しやすいようにHCLKを128MHz。

[SPI1]-[Parameter Settings]

Clock Prameters
  Prescaler(for Baud Rate): 64

MCP3208はVdd=2.7Vのときクロック周波数は最大1.0MHzなのでそれに合わせてプリスケーラ-を設定。

GPIOの設定

SPI1のNSS(CS)をソフトウェア制御するために、GPIOで[SPI1_CS]を割り当て。

SW4STM32でコードを追加(一部抜粋)


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define MCP3208_START_BIT   0x04
#define MCP3208_MODE_SINGLE 0x02    // Single-ended mode
#define MCP3208_MODE_DIFF   0x00    // Differential mode
/* USER CODE END PD */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#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;
}

uint16_t MCP3208_read_u16(uint8_t channel)
{
  uint8_t aTxBuffer[3];
  uint8_t aRxBuffer[3];

  aTxBuffer[0] = MCP3208_START_BIT | MCP3208_MODE_SINGLE | ((channel & 0x04) >> 2);
  aTxBuffer[1] = (channel & 0x03) << 6;
  aTxBuffer[2] = 0;

  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);

  if(HAL_SPI_TransmitReceive(&hspi1, aTxBuffer, aRxBuffer, 3, 100) != HAL_OK)
  {
    /* Transfer error in transmission process */
    Error_Handler();
  }

  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

  // RXデータの2バイト目の上位4ビットは値が不定のため0x0fでマスキング
  uint16_t conv_result = ((uint16_t)(aRxBuffer[1] & 0x0f) << 8) | (uint16_t)aRxBuffer[2];

  return conv_result;
}

/* USER CODE END 0 */

MCP3208のデータシートを見ると、SPIで8bit単位で送受信すると、RXデータ(12bit)の上位3ビットはHI-Zで不定になり、4ビット目は0になるようです。このためRXデータ(aRxBuffer[])の2バイト目の上位4ビットはプログラムで読み捨てています。


↑DOUTがNucleo-F446側から見るとRXデータになります。


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

    /* USER CODE BEGIN 3 */
    uint16_t adcv[3] = {0};
    for (int i = 0; i < 3; i++) {
      adcv[i] = MCP3208_read_u16(i);
    }
    printf("Read VAL:\t%d\t%d\t%d\r\n", adcv[0], adcv[1], adcv[2]);
  }
  /* USER CODE END 3 */

メインループでMCP3208をポーリングして、値をprintf(UART出力)しています。

SPI信号波形

MISO

ch1:MISO ch2:SCK

MOSI

ch1:MOSI ch2:SCK

<追記:2019.03.29>

MOSIの3バイト目(aTxBuffer[2])は「0」のハズなのに、値が出力されてますね(@@? バグがありそうです。

</追記>

CS

ch1:CS ch2:SCK

Puttyで受信しているようす

メモ

3連POTのch0(黄色)が動作不良。

2019年3月23日土曜日

STM32: SPI DACのMCP4922を使う(Nucleo-F446RE)

SPI DACのMCP4922でノコギリ波を2ch出力しました。

実行環境

  • 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/Nucleo-F446_MCP4922_Test1

STM32CubeMXの設定


MCP4922はMISOが必要ないのでSPIは「Transmit Only Master」でOKです。


GPIOは、SPIのNSS(CS)をソフトウェアで制御するのに1つ、2ch分ラッチして出力するのでLDAC1つで計2本です。


SW4STM32でコードを追加(一部)


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

// param
//   channel: 0, 1
//   val: 0 .. 4095
void MCP4922_Write(uint8_t channel, uint16_t val)
{
 channel = channel & 0x01;
 val = val & 0x0fff;

 uint8_t cmd[2];
 cmd[0] = (channel << 7) | (val >> 8) | 0x30;
 cmd[1] = val & 0xff;

 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, cmd, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
}

/* USER CODE END 0 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_SET);
 MCP4922_Write(0, cnt);
 MCP4922_Write(1, 4095 - cnt);
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_RESET);

 cnt++;
 if (cnt > 4095) {
  cnt = 0;
 }
  }
  /* USER CODE END 3 */

ノコギリ波の非反転、反転を出力しました。

出力結果


ch1:VOUTA ch2:VOUTB

2019年3月22日金曜日

STM32: SPIをPollingとDMAで使う(Nucleo-F446RE)

SPI通信を、ポーリングとDMA経由でテストしました。

参考にした記事

STM32CubeのExample「SPI_FullDuplex_ComPolling」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling

STM32CubeのExample「SPI_FullDuplex_ComDMA」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComDMA

実行環境

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

SPI通信(ポーリング)


STM32CubeのExampleはボードを2枚使って互いに通信させていますが、SPI送信波形を見るだけにして一番シンプルな形でテストしました。

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

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Transmit Only Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

SPI1はデフォルトでは、PB3がSYS_JTDO-SWOに割り当てられていて有効化できませんが、Pinout viewでPB3にSPI1_SCKを割り当ててやれば、有効化できます。

モードは送信のみの「Transmit Only Master」にしました。MISOが割り当てられず、SCKとMOSIの2線です。

CSはソフトウェア制御にしてPinout viewでPB4をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MOSI PB5 D4
CS PB4 D5

クロック設定


SPIクロックをちょうど1MHzにしたかったので、HCLKを2のN乗の128MHzにしました。またクロック源(PLL Source Mux)を内蔵RCのHSIではなく、NucleoボードのST-Link側に乗っている8MHzの水晶から取るために、HSEに切り替えています。

SW4STM32でコードを追加(一部抜粋)

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

    /* USER CODE BEGIN 3 */
 uint8_t data[2] = { 0x55, 0xAA };
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, data, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
  }
  /* USER CODE END 3 */

0x55, 0xAAを送信しています。ビット列にすると「01010101:10101010」になります。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)

SPI通信(DMA)


こちらも送信のみですが、設定は全二重(Full-Duplex Master)にしました。

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

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Full-Duplex Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

Transmit Only Masterの場合より1ピン増えて、MISOが追加されます。CSはPinout viewでPB10をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MISO PB4 D5
MOSI PB5 D4
CS PB10 D6

DMA Settings


SPIとDMAを紐付ける設定です。

[Add]ボタンを押して、[DMA Request]で[SPI1_TX][SPI1_RX]を追加します。

DMA Request Settings
  Mode: Normal

クロックの設定はポーリングの場合と同じです。

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFERSIZE (2)
enum {
 TRANSFER_WAIT,
 TRANSFER_COMPLETE,
 TRANSFER_ERROR
};
/* USER CODE END PD */

/* USER CODE BEGIN PV */
/* transfer state */
__IO uint32_t wTransferState = TRANSFER_WAIT;
/* USER CODE END PV */

DMA転送の状態を示すフラグです。

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

    /* USER CODE BEGIN 3 */
    uint8_t aTxBuffer[BUFFERSIZE] = { 0x55, 0xAA };
 uint8_t aRxBuffer[BUFFERSIZE];
 wTransferState = TRANSFER_WAIT;

    /*##-2- Start the Full Duplex Communication process ########################*/
    /* While the SPI in TransmitReceive process, user can transmit data through
    "aTxBuffer" buffer & receive data through "aRxBuffer" */
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    if(HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)aTxBuffer, (uint8_t *)aRxBuffer, BUFFERSIZE) != HAL_OK)
    {
   /* Transfer error in transmission process */
   Error_Handler();
    }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

    /*##-3- Wait for the end of the transfer ###################################*/
    /*  Before starting a new communication transfer, you must wait the callback call
     to get the transfer complete confirmation or an error detection.
     For simplicity reasons, this example is just waiting till the end of the
     transfer, but application may perform other tasks while transfer operation
     is ongoing. */
    while (wTransferState == TRANSFER_WAIT)
    {
    }

    printf("SPI RX: %d\t%d\r\n", aRxBuffer[0], aRxBuffer[1]);
  }
  /* USER CODE END 3 */

こちらも0x55、0xAAを送信しています。

ソースのコメントにもありますが、DMA転送の完了をWhileループでフラグを見て待機していますが、本来はうまいことやってDMA転送完了待ち中は他の処理をするべきです。

/* USER CODE BEGIN 4 */
/**
  * @brief  TxRx Transfer completed callback.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report end of DMA TxRx transfer, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_COMPLETE;
}

/**
  * @brief  SPI error callbacks.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report transfer error, and you can
  *         add your own implementation.
  * @retval None
  */
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_ERROR;
}
/* USER CODE END 4 */

DMA転送完了の割り込みハンドラです。wTransferStateに状態を代入しているだけです。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)


ch1:MOSI(D4) ch2:SCK(D3)

printf()でUART通信しているので(ブロッキングされる)、SPI通信ごとの間隔が広がっています。

Puttyで受信しているようす

スレーブがつながっていないので受信データは0:0です。

メモ:


NSS(CS)をハードウェア制御できるようですが、マスター側ではソフトウェアでGPIO Writeしてやれば用は済みます。NSSのハードウェア制御の使い方はしばし保留。

2019年3月21日木曜日

STM32: ADCをDMAで使う(Nucleo-F446RE)

ADCの使い方の本丸、DMA経由でA-D Convertするテストをしました。

参考にした記事
STM32CubeのExample「ADC_RegularConversion_DMA」<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\ADC\ADC_RegularConversion_DMA
「ガレスタさんのDIY日記」さんの「STM32でADCをやってみる2(DMAを使ったレギュラ変換)
「JP7FKFの備忘録」さんの「STM32マイコンでADCを使ってみる話

実行環境

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

DMAシングルチャネル


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

DMAを使った1チャネルのみのA-D Convertです。

STM32CubeMXの設定


[Analog]-[ADC1]で、[Mode]の[IN0]をチェックします。PA0がADC1-IN0に割り当てられます。これはNucleoボードのArduinoヘッダのA0に接続されています。


[Parameter Settings]
Continuous Conversion Mode: Enable
DMA Continuous Requests: Enable
End Of Conversion Selection: EOC flag at the end of single channel conversion
ADC_Regular_ConvesionMode
  Rank:1
    Sampling Time: 480Cycles
Sampling Timeはデフォルトでは3 Cyclesになっていますが、3 CyclesのままだとDMAの割り込み処理でCPU時間を消費しつくしてしまい、他の処理が行えません。一番大きい480 Cyclesを指定しました。実用的には、DMAの処理と他の処理との兼ね合いで値を調整することになると思います。


[DMA Settings]
[Add]ボタンを押して[DMA Request]でADC1を選択
Mode: Circular

SW4STM32でコードを追加(一部抜粋)


  /* USER CODE BEGIN 2 */

  printf("before HAL_ADC_Start_DMA()\r\n");

  /*##-3- Start the conversion process #######################################*/
  /* Note: Considering IT occurring after each number of ADC conversions      */
  /*       (IT by DMA end of transfer), select sampling time and ADC clock    */
  /*       with sufficient duration to not create an overhead situation in    */
  /*        IRQHandler. */
  if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  printf("after HAL_ADC_Start_DMA()\r\n");
  /* USER CODE END 2 */

前述の[Sampling Time]が3 Cyclesだと、DMA初期化後
    printf("after HAL_ADC_Start_DMA()\r\n");
に処理が移らず、メッセージが表示されません。

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  /* Turn LED2 on: Transfer process is correct */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
/* USER CODE END 4 */

ADC変換後のDMA転送完了時の割り込み処理です。Nucleoボード上のLEDを点滅させています。点滅の間隔が短すぎて視認できないのでオシロで測定しました。


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

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_SET);
 printf("ADC Value: %d\r\n", uhADCxConvertedValue);
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_RESET);
  }
  /* USER CODE END 3 */

メインループ内で、printf()でADCの読み取り値をUART送信し、その前後で処理時間計測用にGPIOをH/Lさせています。


ch1:D13(LD2) ch2:D2(CHK1)

メインループ処理中(黄色)にも非同期にADCのDMA転送割り込み(赤色)が行われています。

Puttyで受信しているようす


マルチチャンネル


2ch分ADC読み取りを行いました。

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

STM32CubeMXの設定


[Analog]-[ADC1]で、[Mode]の[IN0]、[IN1]をチェックします。PA0がADC1-IN0、PA1がADC1-IN1に割り当てられます。これはNucleoボードのArduinoヘッダのA0、A1に接続されています。

[Parameter Settings]
Continuous Conversion Mode: Enable
DMA Continuous Requests: Enable
End Of Conversion Selection: EOC flag at the end of single channel conversion
Scanmode: Enable
ADC_Regular_ConversionMode
  Number Of Conversion: 2
Number Of Conversionで2以上を指定すると、[Scan Conversion Mode]は[Enable]しか選べなくなります。
Rank 1
  Channel: Channel 0
  Sampling Time: 480Cycles
Rank 2
  Channel: Channel 1
  Sampling Time: 480Cycles
Sampling Timeはひとまず480Cyclesを指定しました。
DMA Settings
  DMA Request: ADC1
  Mode: Circular

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ADC_CHANNEL_N (2)
/* USER CODE END PD */

/* USER CODE BEGIN PV */
__IO uint16_t uhADCxConvertedValue[ADC_CHANNEL_N];
/* USER CODE END PV *

2ch分のバッファを確保しています。

/* USER CODE BEGIN 2 */
  printf("before HAL_ADC_Start_DMA()\r\n");

  /*##-3- Start the conversion process #######################################*/
  /* Note: Considering IT occurring after each number of ADC conversions      */
  /*       (IT by DMA end of transfer), select sampling time and ADC clock    */
  /*       with sufficient duration to not create an overhead situation in    */
  /*        IRQHandler. */
  if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)uhADCxConvertedValue, ADC_CHANNEL_N) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }

  printf("after HAL_ADC_Start_DMA()\r\n");
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_SET);
 printf("ADC Value[0]: %d\t",   uhADCxConvertedValue[0]);
 printf("ADC Value[1]: %d\r\n", uhADCxConvertedValue[1]);
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_RESET);
  }
  /* USER CODE END 3 */

メインループでは、ADCの読み取り値をUART送信しています。


/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  /* Turn LED2 on: Transfer process is correct */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
/* USER CODE END 4 */

DMA転送割り込みハンドラではLEDを点滅させています。


ch1:D13(LD2) ch2:D2(CHK1)

Puttyで受信しているようす