stm32 cube に作成してもらった makefile を使用して c++ のプログラムをコンパイルできるようにする ( memo )

こんにちは。

最近毎日外に出ると蒸し風呂かって言いたくなるくらいの暑さですが、皆様いかがお過ごしでしょうか?

体調管理はしっかり気を付けてやっていきましょう。(自戒)

 

最近、マイクロマウスの迷路のソフトをc++で少し書いていたので、マイコンでもc++が動くようにしたいというところからこの話はスタートしました。

今回の記事では私がnone-eabi-g++,makefile を使用してすべての.c,.cppファイルをコンパイルするまでのやり方を書き残しておこうと思い書くものになります。

所々抜けている点や間違えている点があるかもしれません。ご愛顧でお願いします。また、ご指摘していただけるばと思います。

さて、まずはstm32cube からコードをはいてもらいましょう!

 

そしたら makefile を開いてください。

今回はすべてのファイルを一括でg++でコンパイルをしてしまうということなので

#######################################
# binaries
#######################################
BINPATH =
PREFIX = arm-none-eabi-
CC = $(PREFIX)g++
AS = $(PREFIX)g++ -x assembler-with-cpp
CP = $(PREFIX)objcopy
AR = $(PREFIX)ar
SZ = $(PREFIX)size
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S

のようにしてgccのところをg++に変えてしまいます。

その後、ソース管理のところに

CPP_SOURCES = \

というものを作成してcppソース置き場を作りましょう。

そしたら次にコンパイルオプションの追加を行います。

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall
-fdata-sections -ffunction-sections -std=c++11

std=c++11でコンパイルをしたかったためこのようにしました。必要なもののオプションを通せば大丈夫だと思います。

続いてobjectに追加して

# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(CPP_SOURCES:.cpp=.o)))
vpath %.cpp $(sort $(dir $(CPP_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
 
$(BUILD_DIR)/%.o: %.cpp Makefile | $(BUILD_DIR)
  $(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.cpp=.lst)) $< -o $@

また、c言語と同じように、コンパイルをするときのルール?も決めましょう。

 

ここまでやったらcpp_sourseのところにc++のソースファイルを置いてあげればコンパイルをすることが可能になりました。

以上でmakefileの操作は終了になります。

 

ここから先はまった点になります。

一つ目 printf が使えなくなった。

先日の記事で記載した方法でそのまま使用をしようとしたところ使うことができずになぜかと考えたり調べたりしたところ、

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
void __io_putchar(uint8_t ch) {
HAL_UART_Transmit(&huart1, &ch, 1, 1);
}
 

はだめじゃん、__GNUC__はgccに定義されているものじゃんということになり、

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
void __io_putchar(uint8_t ch) {
HAL_UART_Transmit(&huart1, &ch, 1, 1);
}
#ifdef __cplusplus
}
#endif

のようにc言語としてコンパイルしろというマクロを使うことで使用可能にしました。

 

次にerror_handler()のwarningがたくさんおきました。

それはchar*型の関数の引数は関数内で値の変更ができるから推奨しないよということでした。これにはすべてのhファイル,関数の引数を const char*とすることで解決します。

また、main.hも上記に挙げたマクロが使用されていないため自分で書いておくと安全かもしれないです。

 

最後に

 なにか質問等ございましたらコメントに書いていただければわかる内容であれば答えます。間違いなどがあれば教えていただければ幸いです。

 

 

stm32f405rg flashに書き込み

こんにちは

今回はstm32f405rgtのフラッシュにデータを書き込み、読み込み方を書いていきます。

ここに書いてあるのはあくまで一例なのでそこのところはご了承ください。いつも通りこれで動きましたという紹介のような形で書いていきます。

 

それでは本文に入っていきたいと思います。

今回はSTMicroelectronicsの公式サイトにあるja.DM00031020.pdfから絶対に必要だと思われる場所を一部抜粋します。f:id:sora_siro:20180525111624p:plain

f:id:sora_siro:20180525111735p:plain

今回はこの中のセクタ11を使いたいと思います。

 

これでセクタとフラッシュメモリにどのように書き込みができるか削除ができるか、メモリの番地がわかりました。

 

それではプログラムに入っていきたいと思います。

まずはSTM32F405RGTx_FLASH.ldファイルを開いてください。そこのだいたい40行目を以下のように追記、変更してください。

/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 896K
DATA (rx) : ORIGIN = 0x80E0000, LENGTH = 128k
}

このこうすることでDATAっていうプログラムを書き込まない部分を作ることができます。*1

 

それではプログラムにはいっていきたいと思います。

私が実装したソフトは以下のようになりました。

///////////////////////////////////////////////////////////////////////
// ereaseFlash
// [argument] nothing
// [Substitutiong] nothing
// [return] nothing
///////////////////////////////////////////////////////////////////////
void eraseFlash( void )
{
  FLASH_EraseInitTypeDef erase;
  erase.TypeErase = FLASH_TYPEERASE_SECTORS;  // セクタを選ぶ
  erase.Sector = FLASH_SECTOR_11;       // セクタ11を指定
  erase.NbSectors = 1;    // 消すセクタの数(今回はひとつ)
  erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 3.3Vで消去

  uint32_t pageError = 0;

  HAL_FLASHEx_Erase(&erase, &pageError);  // HAL_FLASHExの関数で消去
}

///////////////////////////////////////////////////////////////////////
// writeFlash
// [argument] address, *data, size
// [Substitutiong] nothing
// [return] nothing
///////////////////////////////////////////////////////////////////////
void writeFlash(uint32_t address, uint8_t *data, uint32_t size )
{
  HAL_FLASH_Unlock();   // フラッシュをアンロック
  eraseFlash();     // セクタ11を消去
  do {
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, address, *data); // 1byteずつフラッシュに書き込む
++address;
++data;
--size;
  } while ( size > 0);

  HAL_FLASH_Lock();   // フラッシュをロック
}

///////////////////////////////////////////////////////////////////////
// loadFlash
// [argument] address, *data, size
// [Substitutiong] struct
// [return] nothing
///////////////////////////////////////////////////////////////////////
void loadFlash(uint32_t address, uint8_t *data, uint32_t size )
{
  memcpy(data, (uint8_t*)address, size); // addressの内容をコピー
 
}

 

私は構造体自体で書き込み・読み込みをするようにしています。

 理由としてはフラシュに書き込みするためには1byteずつ書き込みになるので構造体だと都合がよかったため使いました。

また、書き込む構造体を増やしたいときは次のように改造すればできます。

 

///////////////////////////////////////////////////////////////////////
// writeFlash
// [argument] address, *data, size
// [Substitutiong] nothing
// [return] nothing
///////////////////////////////////////////////////////////////////////
void writeFlash(uint32_t address, uint8_t *data, uint32_t size, uint8_t *data2, uint32_t size2 )
{
  HAL_FLASH_Unlock();   // フラッシュをアンロック
  eraseFlash();     // セクタ11を消去
  do {
    // 1byteずつフラッシュに書き込む
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, address, *data);
++address;
++data;
--size;
  } while (--size > 0);
 
do {
    // 1byteずつフラッシュに書き込む
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, address, *data2);
++address;
++data;
--size;
  } while (size2 > 0 );

  HAL_FLASH_Lock();   // フラッシュをロック
}

///////////////////////////////////////////////////////////////////////
// loadFlash
// [argument] address, *data, size
// [Substitutiong] struct
// [return] nothing
///////////////////////////////////////////////////////////////////////
void loadFlash(uint32_t address, uint8_t *data, uint32_t size, uint8_t *data2, uint32_t size2 )
{
  memcpy(data, (uint8_t*)address, size);
address = address + size;
memcpy(data2, (uint8_t*)address, size2);
address = address + size2;
}

ただ単に引数を増やしてアドレスの更新をしてあげるだけです。

実際に動作確認をした際のソフトは以下のようになりました。*2

 

#include <string.h>
// flash use address ( sector11 )
const uint32_t start_address = 0x80E0000; //sentor11 start address
const uint32_t end_adress = 0x80FFFFF;
 
typedef struct {
int x;
int y;
int dir;
float angle;
float distance;
}POSITION;

POSITION position;

int main()
{
loadFlash( start_address,(uint8_t*)&position, sizeof(POSITION) );
printf("%d,%d,%f,%f\r\n",
position.x, position.y,position.angle, position.distance);

position.x = 100;
position.y = 150;
position.angle = 1.52;
position.distance = 0.18;

writeFlash( start_address,(uint8_t*)&position, sizeof(POSITION) );

}
 

こうしてprintfで構造体positionのメンバの値の変化を一度目の電源を入れたときと二度目で見比べてあげれば書き込まれているかどうかがわかると思います。

 

まとめ

私の勉強不足によりこまかい動作に対しての説明ができず申し訳ないです。

HALの関数の動作に関してはSTMicroelectronicsの公式サイトにある関数をまとめたドキュメントから参照していただければと思います。

 

最後に

知見のあるかたで、ここをこうしたほうがいいということがあればご指摘していただきたいです。よろしくお願いします。

 

更新履歴

2018.5/16

writeFlash関数の中のdo while文の条件式が分かりづらいとの指摘をいただいたので訂正しました。

2018.6/9 

誤字を指摘していただいたため訂正しました。

 

 

 

*1:リンカファイルに関して勉強不足なので間違っているかもしれないです。知見のある方で何か問題があれば指摘をしていただきたいです。

*2:一部抜粋で書きます。

メモ stm32f405rgt,makefileでFPUを使う

arm_mathi.hを使いたいと思い使えるようにしたいということでいろいろと試してみたところできなかったのでTwitterでそのようなことをつぶやいたところosabohさんに教えていただき解決することができました。

ありがとうございました。

 

実装の仕方をここにメモしておきます。

 

まず、stm32cubeのファームウェアからCMISのLibディレクトリを自分のプロジェクトのCMISの直下に置きます。

cubeの初期設定のままなら以下のディレクトリにあると思います。参考までに私は以下のディレクトリにありました。

「C:\Users\alcne\STM32Cube\Repository\STM32Cube_FW_F4_V1.16.0\Drivers\CMSIS」  *1

そしたら、makefileを開き次のように編集します。

# C defines
C_DEFS = \
-DUSE_HAL_DRIVER \
-DARM_MATH_CM4 \
-DSTM32F405xx

 

# libraries
LIBS = -lc -lm -lnosys -larm_cortexM4lf_math
LIBDIR = -L Drivers/CMSIS/Lib/GCC/

 

あとはarm_math.hをよべば使えるはずです。

 

最後に

途中から雑になってしまい申し訳ないです。

知見のあるかたでここをこうしたほうがいいということがあればご指摘していただきたいです。よろしくお願いします。

 

訂正

*1 2018/5/7 F3xxと記載ミスがあったため訂正しました

osabohさん,指摘していただきありがとうございます。

stm32 halライブラリ、makefileでprintf(float型)を使えるようにする!

こんにちは

時が過ぎるのが速すぎて焦っています。もう三月の後半・・・

今日はタイトル通りprintfの実装の仕方を書いていきます。

 

まずはstm32cubeでuartの設定をします。

f:id:sora_siro:20180322104348p:plain

f:id:sora_siro:20180322104400p:plain

こんな感じで・・・baud Rateなどはシリアル通信をするソフトなどとの兼ね合いを付けてください。

そしたらcubeにコードを生成してもらいましょう。

 

次に、syscalls.cをSrc直下においてください。ファイルのリンクを張っておきます。

syscalls.c

この状態でmakeしてもsyscalls.cはコンパイルされません。なのでmakefileに追記する必要があります。2か所あり、一つ目はsource内にsyscalls.cを追記することです。

######################################
# source
######################################
# C sources
C_SOURCES = \
省略
(ex)Src/main.c\
  Src/syscalls.c

2つ目はLDFLAGに-u _printf_floatと追記してください。

LDFLAGS = 省略 -u _printf_float

ここまででひとまずmakefileに追記することはおしまいです。

 

続いて、main.cを開いて下さい。

// includesにこのように書いて下さい。

 
#include <stdio.h>
 

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
void __io_putchar(uint8_t ch) {
HAL_UART_Transmit(&huart1(初期設定で使用したuartを設定してください), &ch, 1, 1);
}

 また、printfを使用する前に(int main()のINIT()のあとにでも)この関数を呼び出しておいて下さい。

setbuf(stdout, NULL);

 

まとめ

マイコンでprintfが最初から実装されていることにありがたみをとても感じました。

マクロ定義の使い方が難しい・・・

 

最後に

知見のある方でこうしたほうがいいのではないかってことがありましたら教えていただきたいです。

stm32f405rgtでspi通信(L3GD20)を使ってみる

早速進めていきたいと思います。

 

使用するもの

L3GD20

STM32F405RGT

 

使用するソフト

visual studio code

cmd( makefile , arm-none-eabi-gcc )

 

まずはstm32cubemxでSPI通信の設定をします。

今回はSPI2を Full-Duplex Masterに設定して、どこか一つのポートをGPIO_OUTPUTに設定します。

f:id:sora_siro:20180224155344p:plain

その後、SPIの設定を変更します。

f:id:sora_siro:20180224160233p:plain

SPIの通信速度によってプリスケーラの値は適宜変更してください。

ここで、L3GD20は10Mhzで通信を行えるので10Mhzで設定しました。

あとはGPIO_PIN_OUTPUTのラベルを付けておくと後でソフトを書くときに楽になるのでつけておくことをお勧めします。

 

ここまで設定をしたらコードを作成してもらいましょう。

これで、cubemxでの作業はおしまいです。

 

そしたらvisual studio codeでソフトを書いていこうと思います。(開発環境については)stm32のローカル環境構築のメモ1,stm32のローカル環境構築のメモ2を参照して頂ければ幸いです。

main.c( spi.c)内に記述していきます。

追加で記述したコードです。

// ジャイロ関連
#define WHO_AM_I 0x0F
#define CTRL_REG1 0x20
#define CTRL_REG4 0x23
#define OUT_Z_L 0x2C
#define OUT_Z_H 0x2D

#define Certain 0xD4
#define POWER_ON 0x0F
#define L3GD20_500dps 0x10
#define L3GD20_2000dps 0x20

#define SETTING 0x80

 

///////////////////////////////////////////////////////////////////////
// spi read 1 byte upper bit ( signed )
// [argument] Register
// [Substitutiong] nothing
// [return] 1byte upper bit data
///////////////////////////////////////////////////////////////////////
int16_t readShiftByte( uint8_t reg )
{
uint8_t address,val;
int16_t ret;
HAL_GPIO_WritePin( gyro_cs_GPIO_Port, gyro_cs_Pin, RESET );
address =( reg|SETTING );
HAL_SPI_Transmit( &hspi2, &address,1,100 );
HAL_SPI_Receive( &hspi2,&val,1,100 );
ret = (int16_t)(val<< 8);
HAL_GPIO_WritePin( gyro_cs_GPIO_Port,gyro_cs_Pin, SET );
return ret;
}

///////////////////////////////////////////////////////////////////////
// spi write 1 byte
// [argument] Register
// [Substitutiong] write data
// [return] nothong
///////////////////////////////////////////////////////////////////////
void writeByte( uint8_t reg, uint8_t val )
{
HAL_GPIO_WritePin( gyro_cs_GPIO_Port, gyro_cs_Pin, RESET );
HAL_SPI_Transmit( &hspi2, &reg,1,100 ); // 書き込みアドレス??��?��???��?��?
HAL_SPI_Transmit( &hspi2, &val,1,100 ); // 書き込み
HAL_GPIO_WritePin( gyro_cs_GPIO_Port,gyro_cs_Pin, SET );
}

///////////////////////////////////////////////////////////////////////
// l3gd20 set up ( spi )
// [argument] nothing
// [Substitutiong] nothing
// [return] nothig
///////////////////////////////////////////////////////////////////////
void setL3GD20( void )
{
uint8_t ret;
ret = readByte( WHO_AM_I );
printf("who am i = 0x%x",ret);
writeByte( CTRL_REG1, POWER_ON );
writeByte( CTRL_REG4,L3GD20_2000dps );
}

///////////////////////////////////////////////////////////////////////
// spi read 1 byte
// [argument] nothing
// [Substitutiong] nothing
// [return] 2000 deg/sec int data
///////////////////////////////////////////////////////////////////////
int getGyroZ( void )
{
int ret;
// *0.070 because you read l3gd20 manual
ret = ( readShiftByte(OUT_Z_H) | readByte(OUT_Z_L) ) * 0.070;
return ret;
}

(インデントが崩れているのは目をつぶっていただきたいです。

やばいインデント警察に捕まってしまう・・・)

そしたら、関数のプロトタイプ宣言などをして、

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_TIM4_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_SPI2_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_ADC3_Init();

/* USER CODE BEGIN 2 */
setbuf(stdout, NULL);
setL3GD20();
/* USER CODE END 2 */

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

/* USER CODE BEGIN 3 */
printf( "gyro z axis %04d\r",getGyroZ() );
} //switch end
} // while end
/* USER CODE END 3 */

}

 とすることでジャイロセンサーの値を2000deg/secでtera termなどで表示することができると思います。この開発環境でprintfを実装する方法は後々このブログに書いていこうと思います。

詳しくはマニュアルやリファレンスマニュアルなどを参考にしてください。

 

最後に

つたない文章を最後まで読んでいただきありがとうございます。

知見のある方でここをこうしたほうがいいとのことがあれば教えていただきたいです。

stm32のローカル環境構築のメモ その2

前回のstm32のローカル環境構築のメモ その1の続きを書いていきたいと思います。前回はローカル環境にコンパイラなどを落としたので今回はそのあとのことを少々書きたいと思います。

ではいきなり始めていきます。

ソフトは visual studio code を使用します。

 

visual studio code (vs code) をインストールしてください。そしたら、vscodeでcmdをたたけるようにしましょう。(ほかのサイトなどを参考にしてください。)

その後、stm32cubeを使用するなり、mbed のサイトからmakefile形式でプロジェクトを作ったらあとはmakeとたたくだけです。

 

ここからははまった点をいくつか紹介します。

 

makeとコマンドを打つ前にbuildのディレクトリを作業スペース直下に作成されていないとエラーが出てしまうことがあります。

次に、stm32cubeで生成してもらったmakefile

binaries

直下の

$(BINPATH)\

というのを消さないとmakeがたたけないことがありました。

 下記のようにするといいです。

#######################################
# binaries
#######################################
BINPATH =
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
AR = $(PREFIX)ar
SZ = $(PREFIX)size
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
 

 

最後に

そのほかにもはまった点などが見つかった場合ここに追記していきたいと考えています。また、ここが間違っているとのことがあれば指摘していただけると幸いです。

 

あけましておめでとうございます。

もう新年があけてから一週間以上たってしまいました・・・。

今後。このブログではロボトレの制御の話とか前回挙げた開発環境の話の続きとかをできたらいいなと思っています。

今年もどうぞよろしくお願いします。