STM32CubeIDE 6 (I2C without DMA)

SeeedのBMI088ボードから加速度と角速度を取得します。

I2Cの設定

I2C1を選択して、I2CをI2Cにします。
I2CはSDAを下げた(=Start Condition)奴がマスターです。CPUがI2Cのレジスタを叩いて、「行ってこい。」と命令します。

BMI088の最大が400kHzなので、Fast Modeとします。
I2C Speed ModeをFast Modeにします。
そうすると、自動的にI2C Speed Frequencyが400KHzになります。

001

RX用とTX用のDMAをAddします。DMACを使うときの準備をしておきます。

Point !!!!!
どっちもModeはNormalニャ !! Circularだとなんか上手くいかないニャ !!
balloon

002

NVICに自動的にチェックが入っています。

003

こちらもDMACを使うときの準備をしておきます。

Point !!!!!
event interruptのチェックも必須ニャ !! このチェックが無いとBUSYからREADYに戻らないニャ !!
balloon

004

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

Seeed BMI088ボードは下記の回路図からプルアップしているように見えるのですが、通信できなくなる場合があり、この設定をすることにより通信が安定しました。
Arduino互換でない方のGPIOで3.0V-3.1Vを実測してます。

ロジックアナライザーで横取りすると、不安定になるときもありました。Standard Modeのときはずっと安定していたと思います。
ジャンパーワイヤー1本なら安定してジャンパーワイヤー2本なら不安定なのか、ロジックアナライザーで電圧が下がっているのか、なかなか大変です。

Grove六軸加速度計およびジャイロ(BMI088) – Grove 6-Axis Accelerometer&Gyroscope
Eagle Files

005

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

006

C++を使うときの小賢しいやつをやります。

cd Core/Src
cp main.cpp main.c
mv main.cpp ~/main.cpp.backup

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

007

(左ペインのSrcを選択してF5)
左ペインのmain.cを選択してF2
Ctrl + ‘b’か、Runでコンパイルする。

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

最初にライセンスを確認します。
SeeedがArduino用のソースコードを公開してくれています。これを(NUCLEO) G474REに移植します。

Grove_6Axis_Accelerometer_And_Gyroscope_BMI088/LICENSE

こちらもGPLではないですね。

ソースコードを利用する

ソースコードは下記です。

Seeed-Studio/Grove_6Axis_Accelerometer_And_Gyroscope_BMI088

BMI090Lも持っているのですが、Boschのホームページから消えています。(2021.12.03)

半導体不足が改善したら復活するのかもしれませんが、入手できるBMI088を使います。
レベルシフタも入っていて、3.3Vでも5Vでも気にせずに使えるSeeedのこのボードは使いやすいと思います。

加速度用のI2Cスレーブのアドレスは何もしなければ0x18なのですが、Seeedは0x19にしています。
角速度用のI2Cスレーブのアドレスは何もしなければ0x68なのですが、Seeedは0x69にしています。

ここからBMI008.hとBMI088.cppをVMwareのUbuntuに持ってくるのですが、MacからのDrag & Dropが上手くいくときと上手くいかないときがあります。
上手くいかないときは、UbuntuのFirefoxで「seeed github bmi088」で検索すると早いでしょう。
zipをダウンロードしてもいいし、git cloneしてもいいです。
$ git clone https://github.com/Seeed-Studio/Grove_6Axis_Accelerometer_And_Gyroscope_BMI088/

cp BMI088.cpp ~/ide/g474re_sensor/Core/Src/
cp BMI088.h ~/ide/g474re_sensor/Core/Inc/

STM32CubeIDEでCtrl + ‘b’とすると、追加した2つのファイルが自動的に読み込まれて、Arduino.hが無いと怒られます。

このあたりの操作はpushd/popdを使うと楽でしょう。

I2C HAL Driver

STのサンプルコードを見ます。

I2C_TwoBoards_ComPolling

サンプルのスレーブアドレスは10bit。
普通は7bit。

Arduinoの真似をしたままだと最初からNACKが返ってきます。全く通信できません。
ロジックアナライザーで確認したら、0x19のアドレスが0xCになっていました。

0x19 = 0b11001
0xC = 0b1100

16bitや8bitのスレーブアドレスが格納されたビット列の最下位1bitを削ってくれてやがります。
ロジックアナライザーが必須です。

すぐに気付きましたが、これは波形を見ないと解決できないと思います。
0xCがアドレスのスレーブなんて今回のI2Cバスの中には居ないわけです。

なので、SeeedのArduinoのソースコードを下記のように書き換える必要がありました。

if () {
    addr = accel_addr << 1;
}
else {
    addr = gyro_addr << 1;
}

7bitのアドレスを1bit上にビットシフトして、HALの中かハードの中かわかりませんが1bit削らせてあげます。

STのサンプルコードのアドレスは0x30Fですが、マスターは(0x30F >> 1)に投げて、スレーブは自分の住所を(0x30F >> 1)だと思っていると思います。

「10bitなんてキレッキレのカッティングエッジなことやってないで、普通に7bitでやってスレーブスキャンとか客が喜ぶサンプルつくれよ。」と思います。

I2C without DMA (main.cpp)

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>
#include <string.h>
#include "BMI088.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 ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
DMA_HandleTypeDef hdma_i2c1_rx;
DMA_HandleTypeDef hdma_i2c1_tx;

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};

float ax = 0.0f, ay = 0.0f, az = 0.0f;
float gx = 0.0f, gy = 0.0f, gz = 0.0f;

int error_type = NO_ERROR;

/* 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);
static void MX_I2C1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

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

asm(".global _printf_float");

/* 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();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */

    for (;;)
    {
        if (bmi088.isConnection())
        {
            bmi088.initialize();
            break;
        }

        HAL_Delay(100);
        sprintf( uart_message, "\r\n initialize fail and retry \r\n" );
        p_( uart_message );
    }

    HAL_Delay(100);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

    while (1)
    {
        // sprintf( uart_message, "\r\n ----- UART DMA TEST ----- \r\n" );
        // HAL_UART_Transmit_DMA(&huart1, (uint8_t *)&uart_message, 64);

        bmi088.getAcceleration(&ax, &ay, &az);
        HAL_Delay(10);
        bmi088.getGyroscope(&gx, &gy, &gz);

        sprintf( uart_message, "\r\n ax=%4.3f, ay=%4.3f, az=%4.3f, gx=%4.3f, gy=%4.3f, gz=%4.3f \r\n",
                 ax, ay, az, gx, gy, gz );
        p_( uart_message );
        HAL_Delay(10);

    /* 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
                              |RCC_PERIPHCLK_I2C1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_PCLK1;
  PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.Timing = 0x10802D9B;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analogue filter
  */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Digital filter
  */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @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);
  /* DMA1_Channel3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_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 */

void
p_( const char * char_array )
{
    /// without DMA

    // if ( HAL_UART_Transmit(&huart1, (uint8_t *)uart_message, strlen(uart_message), 5000) != HAL_OK )
    // {
    //     error_type = UART_ERROR;
    //     Error_Handler();
    // }

    /// with DMA

    if (HAL_UART_GetState(&huart1) == HAL_UART_STATE_READY)
    {
        if ( HAL_UART_Transmit_DMA(&huart1, (uint8_t *)uart_message, 128) != HAL_OK )
        {
            error_type = UART_ERROR;
            Error_Handler();
        }
    }
    else
    {
    }
}

void
i2c_write( uint16_t addr, uint8_t *tx_buf, uint16_t len )
{
    /// without DMA

    while (HAL_I2C_Master_Transmit(&hi2c1, addr, tx_buf, len, 10000) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
        {
            error_type = I2C_ERROR;
            Error_Handler();
        }
    }
}

void
i2c_read( uint16_t addr, uint8_t *rx_buf, uint16_t len )
{
    /// without DMA

    while (HAL_I2C_Master_Receive(&hi2c1, addr, rx_buf, len, 10000) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
        {
            error_type = I2C_ERROR;
            Error_Handler();
        }
    }
}

/* 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)
  // {
  // }

    switch ( error_type )
    {
        case UART_ERROR:
            sprintf( uart_message, "\r\n ERROR:UART \r\n" );
            p_( uart_message );
            break;
        case I2C_ERROR:
            sprintf( uart_message, "\r\n ERROR:I2C \r\n" );
            p_( uart_message );
            break;
        case SPI_ERROR:
            sprintf( uart_message, "\r\n ERROR:SPI \r\n" );
            p_( uart_message );
            break;
        default:
            break;
    }

  /* 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****/

変更点は下記です。

UARTでstdio.hをincludeしましたが、BMI088.hを追加します。string.hはstrlen用です。

#include <stdio.h>
#include <string.h>
#include "BMI088.h"

加速度と角速度を格納する変数です。
普段はグローバル変数は避けるのですが、この環境だとなんか感覚が麻痺します。

簡易ですがError_Handlerの中の実装もしますので、その準備をします。無名enumでサクッと実装します。

float ax = 0.0f, ay = 0.0f, az = 0.0f;
float gx = 0.0f, gy = 0.0f, gz = 0.0f;

int error_type = NO_ERROR;

Seeedのサンプルコードを移植します。
I2Cスレーブが接続していることを確認して、やりたいことのためのパラメーター設定をします。

/* USER CODE BEGIN 2 */

    for (;;)
    {
        if (bmi088.isConnection())
        {
            bmi088.initialize();
            break;
        }
        HAL_Delay(100);
        sprintf( uart_message, "\r\n initialize fail and retry \r\n" );
        p_( uart_message );
    }
    HAL_Delay(100);

  /* USER CODE END 2 */

BMI088.cppでBMI088クラスがインスタンスされています。
BMI088.hでbmi088がextern宣言されています。

加速度と角速度を取得して、表示させています。

        bmi088.getAcceleration(&ax, &ay, &az);
        HAL_Delay(10);
        bmi088.getGyroscope(&gx, &gy, &gz);

        sprintf( uart_message, "\r\n ax=%4.3f, ay=%4.3f, az=%4.3f, gx=%4.3f, gy=%4.3f, gz=%4.3f \r\n",
                 ax, ay, az, gx, gy, gz );
        p_( uart_message );
        HAL_Delay(10);

表示用のラッパー関数です。
DMAなしでも動きます。DMAで動かなくなったら、すぐにDMAなしに切り替えましょう。

elseの中は、LEDか他のUARTを使う必要があります。
elseの中に入る場合はあるので、その時はUSART1の出力がスキップされるかたちとなります。

void
p_( const char * char_array )
{
    /// without DMA

    // if ( HAL_UART_Transmit(&huart1, (uint8_t *)uart_message, strlen(uart_message), 5000) != HAL_OK )
    // {
    //     error_type = UART_ERROR;
    //     Error_Handler();
    // }

    /// with DMA

    if (HAL_UART_GetState(&huart1) == HAL_UART_STATE_READY)
    {
        if ( HAL_UART_Transmit_DMA(&huart1, (uint8_t *)uart_message, 128) != HAL_OK )
        {
            error_type = UART_ERROR;
            Error_Handler();
        }
    }
    else
    {
    }
}

I2C writeのラッパー関数です。
hi2c1をextern宣言するよりは、ラッパー関数を作成した方が良いでしょう。

void
i2c_write( uint16_t addr, uint8_t *tx_buf, uint16_t len )
{
    /// without DMA

    while (HAL_I2C_Master_Transmit(&hi2c1, addr, tx_buf, len, 10000) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
        {
            error_type = I2C_ERROR;
            Error_Handler();
        }
    }
}

I2C readのラッパー関数です。

void
i2c_read( uint16_t addr, uint8_t *rx_buf, uint16_t len )
{
    /// without DMA
    while (HAL_I2C_Master_Receive(&hi2c1, addr, rx_buf, len, 10000) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
        {
            error_type = I2C_ERROR;
            Error_Handler();
        }
    }
}

Error_Handlerの中を少し実装します。
まずは、無限待ちではなく、エラー表示にさせます。

完成に近づくにつれて、リセット呼び出しなど仕様を決める必要があります。

    switch ( error_type )
    {
        case UART_ERROR:
            sprintf( uart_message, "\r\n ERROR:UART \r\n" );
            p_( uart_message );
            break;
        case I2C_ERROR:
            sprintf( uart_message, "\r\n ERROR:I2C \r\n" );
            p_( uart_message );
            break;
        case SPI_ERROR:
            sprintf( uart_message, "\r\n ERROR:SPI \r\n" );
            p_( uart_message );
            break;
        default:
            break;
    }

I2C without DMA (BMI088.cpp)

BMI088.cpp

/*
    A library for Grove - 6-Axis Accelerometer&Gyroscope(BMI088)

    Copyright (c) 2018 seeed technology co., ltd.
    Author      : Wayen Weng
    Create Time : June 2018
    Change Log  :

    The MIT License (MIT)

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/

#include "BMI088.h"

BMI088::BMI088(void)
{
    devAddrAcc = BMI088_ACC_ADDRESS;
    devAddrGyro = BMI088_GYRO_ADDRESS;
}

void BMI088::initialize(void)
{
    setAccScaleRange(RANGE_6G);
    setAccOutputDataRate(ODR_100);
    setAccPoweMode(ACC_ACTIVE);

    setGyroScaleRange(RANGE_2000);
    setGyroOutputDataRate(ODR_2000_BW_532);
    setGyroPoweMode(GYRO_NORMAL);
}

bool BMI088::isConnection(void)
{
#ifdef USE_BMI090L
    return ((getAccID() == 0x1A) && (getGyroID() == 0x0F));
#else
    return ((getAccID() == 0x1E) && (getGyroID() == 0x0F));
#endif
}

void BMI088::resetAcc(void)
{
    write8(ACC, BMI088_ACC_SOFT_RESET, 0xB6);
}

void BMI088::resetGyro(void)
{
    write8(GYRO, BMI088_GYRO_SOFT_RESET, 0xB6);
}

uint8_t BMI088::getAccID(void)
{
    return read8(ACC, BMI088_GYRO_CHIP_ID);
}

uint8_t BMI088::getGyroID(void)
{
    return read8(GYRO, BMI088_GYRO_CHIP_ID);
}

void BMI088::setAccPoweMode(acc_power_type_t mode)
{
    if (mode == ACC_ACTIVE) {
        write8(ACC, BMI088_ACC_PWR_CTRl, 0x04);
        write8(ACC, BMI088_ACC_PWR_CONF, 0x00);
    } else if (mode == ACC_SUSPEND) {
        write8(ACC, BMI088_ACC_PWR_CONF, 0x03);
        write8(ACC, BMI088_ACC_PWR_CTRl, 0x00);
    }
}

void BMI088::setGyroPoweMode(gyro_power_type_t mode)
{
    if (mode == GYRO_NORMAL) {
        write8(GYRO, BMI088_GYRO_LPM_1, (uint8_t)GYRO_NORMAL);
    } else if (mode == GYRO_SUSPEND) {
        write8(GYRO, BMI088_GYRO_LPM_1, (uint8_t)GYRO_SUSPEND);
    } else if (mode == GYRO_DEEP_SUSPEND) {
        write8(GYRO, BMI088_GYRO_LPM_1, (uint8_t)GYRO_DEEP_SUSPEND);
    }
}

void BMI088::setAccScaleRange(acc_scale_type_t range)
{
    if (range == RANGE_3G) {
        accRange = 3000;
    } else if (range == RANGE_6G) {
        accRange = 6000;
    } else if (range == RANGE_12G) {
        accRange = 12000;
    } else if (range == RANGE_24G) {
        accRange = 24000;
    }

    write8(ACC, BMI088_ACC_RANGE, (uint8_t)range);
}

void BMI088::setAccOutputDataRate(acc_odr_type_t odr)
{
    uint8_t data = 0;

    data = read8(ACC, BMI088_ACC_CONF);
    data = data & 0xf0;
    data = data | (uint8_t)odr;

    write8(ACC, BMI088_ACC_CONF, data);
}

void BMI088::setGyroScaleRange(gyro_scale_type_t range)
{
    if (range == RANGE_2000) {
        gyroRange = 2000;
    } else if (range == RANGE_1000) {
        gyroRange = 1000;
    } else if (range == RANGE_500) {
        gyroRange = 500;
    } else if (range == RANGE_250) {
        gyroRange = 250;
    } else if (range == RANGE_125) {
        gyroRange = 125;
    }

    write8(GYRO, BMI088_GYRO_RANGE, (uint8_t)range);
}

void BMI088::setGyroOutputDataRate(gyro_odr_type_t odr)
{
    write8(GYRO, BMI088_GYRO_BAND_WIDTH, (uint8_t)odr);
}

void BMI088::getAcceleration(float* x, float* y, float* z)
{
    uint8_t buf[6] = {0};
    uint16_t ax = 0, ay = 0, az = 0;
    float value = 0;

    read(ACC, BMI088_ACC_X_LSB, buf, 6);

    ax = buf[0] | (buf[1] << 8);
    ay = buf[2] | (buf[3] << 8);
    az = buf[4] | (buf[5] << 8);

    value = (int16_t)ax;
    *x = accRange * value / 32768;

    value = (int16_t)ay;
    *y = accRange * value / 32768;

    value = (int16_t)az;
    *z = accRange * value / 32768;
}

float BMI088::getAccelerationX(void)
{
    uint16_t ax = 0;
    float value = 0;

    ax = read16(ACC, BMI088_ACC_X_LSB);

    value = (int16_t)ax;
    value = accRange * value / 32768;

    return value;
}

float BMI088::getAccelerationY(void)
{
    uint16_t ay = 0;
    float value = 0;

    ay = read16(ACC, BMI088_ACC_Y_LSB);

    value = (int16_t)ay;
    value = accRange * value / 32768;

    return value;
}

float BMI088::getAccelerationZ(void)
{
    uint16_t az = 0;
    float value = 0;

    az = read16(ACC, BMI088_ACC_Z_LSB);

    value = (int16_t)az;
    value = accRange * value / 32768;

    return value;
}

void BMI088::getGyroscope(float* x, float* y, float* z)
{
    uint8_t buf[6] = {0};
    uint16_t gx = 0, gy = 0, gz = 0;
    float value = 0;

    read(GYRO, BMI088_GYRO_RATE_X_LSB, buf, 6);

    gx = buf[0] | (buf[1] << 8);
    gy = buf[2] | (buf[3] << 8);
    gz = buf[4] | (buf[5] << 8);

    value = (int16_t)gx;
    *x = gyroRange * value / 32768;

    value = (int16_t)gy;
    *y = gyroRange * value / 32768;

    value = (int16_t)gz;
    *z = gyroRange * value / 32768;
}

float BMI088::getGyroscopeX(void)
{
    uint16_t gx = 0;
    float value = 0;

    gx = read16(GYRO, BMI088_GYRO_RATE_X_LSB);

    value = (int16_t)gx;
    value = gyroRange * value / 32768;

    return value;
}

float BMI088::getGyroscopeY(void)
{
    uint16_t gy = 0;
    float value = 0;

    gy = read16(GYRO, BMI088_GYRO_RATE_Y_LSB);

    value = (int16_t)gy;
    value = gyroRange * value / 32768;

    return value;
}

float BMI088::getGyroscopeZ(void)
{
    uint16_t gz = 0;
    float value = 0;

    gz = read16(GYRO, BMI088_GYRO_RATE_Z_LSB);

    value = (int16_t)gz;
    value = gyroRange * value / 32768;

    return value;
}

int16_t BMI088::getTemperature(void)
{
    uint16_t data = 0;

    data = read16Be(ACC, BMI088_ACC_TEMP_MSB);
    data = data >> 5;

    if (data > 1023) {
        data = data - 2048;
    }

    return (int16_t)(data / 8 + 23);
}

void BMI088::write8(device_type_t dev, uint8_t reg, uint8_t val)
{
    uint8_t addr = 0;
    uint8_t tx_buf[4] = {0};

    tx_buf[0] = reg; tx_buf[1] = val;

    if (dev) {
        addr = devAddrGyro << 1;
    } else {
        addr = devAddrAcc << 1;
    }

    // Wire.beginTransmission(addr);
    // Wire.write(reg);
    // Wire.write(val);
    // Wire.endTransmission();

    i2c_write( addr, tx_buf, 2 );
}

uint8_t BMI088::read8(device_type_t dev, uint8_t reg)
{
    uint16_t addr = 0;
    uint8_t rx_buf[4] = {0};

    if (dev) {
        addr = devAddrGyro << 1;
    } else {
        addr = devAddrAcc << 1;
    }

    // Wire.beginTransmission(addr);
    // Wire.write(reg);
    // Wire.endTransmission();

    i2c_write( addr, &reg, 1 );

    // Wire.requestFrom(addr, 1);
    // while (Wire.available()) {
    //     data = Wire.read();
    // }

    i2c_read( addr, rx_buf, 1 );

    return rx_buf[0];
}

// uint16_t BMI088::read16(device_type_t dev, uint8_t reg)
// {
//     uint8_t addr = 0;
//     uint16_t msb = 0, lsb = 0;
//
//     if (dev) {
//         addr = devAddrGyro;
//     } else {
//         addr = devAddrAcc;
//     }
//
//     Wire.beginTransmission(addr);
//     Wire.write(reg);
//     Wire.endTransmission();
//
//     Wire.requestFrom(addr, 2);
//     while (Wire.available()) {
//         lsb = Wire.read();
//         msb = Wire.read();
//     }
//
//     return (lsb | (msb << 8));
// }

// uint16_t BMI088::read16Be(device_type_t dev, uint8_t reg)
// {
//     uint8_t addr = 0;
//     uint16_t msb = 0, lsb = 0;
//
//     if (dev) {
//         addr = devAddrGyro;
//     } else {
//         addr = devAddrAcc;
//     }
//
//     Wire.beginTransmission(addr);
//     Wire.write(reg);
//     Wire.endTransmission();
//
//     Wire.requestFrom(addr, 2);
//     while (Wire.available()) {
//         msb = Wire.read();
//         lsb = Wire.read();
//     }
//
//     return (lsb | (msb << 8));
// }

// uint32_t BMI088::read24(device_type_t dev, uint8_t reg)
// {
//     uint8_t addr = 0;
//     uint32_t hsb = 0, msb = 0, lsb = 0;
//
//     if (dev) {
//         addr = devAddrGyro;
//     } else {
//         addr = devAddrAcc;
//     }
//
//     Wire.beginTransmission(addr);
//     Wire.write(reg);
//     Wire.endTransmission();
//
//     Wire.requestFrom(addr, 3);
//     while (Wire.available()) {
//         lsb = Wire.read();
//         msb = Wire.read();
//         hsb = Wire.read();
//     }
//
//     return (lsb | (msb << 8) | (hsb << 16));
// }

void BMI088::read(device_type_t dev, uint8_t reg, uint8_t* rx_buf, uint16_t len)
{
    uint16_t addr = 0;

    if (dev) {
        addr = devAddrGyro << 1;
    } else {
        addr = devAddrAcc << 1;
    }

    // Wire.beginTransmission(addr);
    // Wire.write(reg);
    // Wire.endTransmission();

    i2c_write( addr, &reg, 1 );

    // Wire.requestFrom(addr, len);
    // while (Wire.available()) {
    //     for (uint16_t i = 0; i < len; i ++) {
    //         buf[i] = Wire.read();
    //     }
    // }

    i2c_read( addr, rx_buf, len );
}

BMI088 bmi088;

write8/read8/readのみを書き換えます。読んでください。

read16/read16Be/read24は、Xだけ取得、Yだけ取得、Zだけ取得、あとは気温の取得のときのインターフェースを用意してくれているかたちになります。
最初は無視して構いません。
どれかを多く取りたいとか、どれかは不要など、やることが確定すれば使うことになると思います。
Wireでコンパイルエラーになってしまうので、コメントアウトします。

BMI090L用の準備が入っていますが無視してください。

絶対に守らないとならないのは下記です。

  • スレーブアドレスを1ビットシフトしておくこと
  • write8 = start [slave addr] [regster] [value] stop

BMI088のDatasheetではreadのときにrepeat start conditionでやれ、と書いていますが、途中にstop conditionが入っても大丈夫です。つまり、repeat start conditionでなくても期待の動作になります。

守らなくてもいいことは下記です。

  • readのときのrepeat start condition

Program Download

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

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

BMI088とNUCLEO G474REを接続する

I2C1

PA15 : SCL
PB7 : SDA

NUCLEO G474REのSCLと、BMI088のSCL、
NUCLEO G474REのSDAと、BMI088のSDA、
NUCLEO G474REの3V3と、BMI088のVCC、
NUCLEO G474REのGNDと、BMI088のGND、
を接続します。

SeeedのBMI088ボードは5Vでもいいのですが、ここでは3V3を接続するとしています。

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

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と怒られる場合もありますが、何回かやっていると通るはずです。

100msだとリアルタイム目視確認は厳しいですが、200msだと出来ます。
IMUセンサーは10msとか20msでもリアルタイム目視確認できます。

まとめ

ソフトウェア屋さんもロジックアナライザーは必須と思います。ソフトウェアが間違っている、配線接続が間違っている、まで気にしていると作業が終わりません。

期待しない動作が発現、即、見える化。

広告

IT開発関連書とビジネス書が豊富な翔泳社の通販『SEshop』
さくらのレンタルサーバ
ムームードメイン
Oisix(おいしっくす)
らでぃっしゅぼーや
珈琲きゃろっと
エプソムソルト




«       »