Pythonにおけるクラスの使い方とスコープを確認してみる
会社の若い子たちがPhysonで機械学習とかやっているようなので自分も勉強してみることにしました。
基本的な文法はなんとなくわかる気がするので、Pythonにおけるクラスの使い方とスコープから初めてみたいと思います。
自分のお勉強目的の備忘録なのでわかりにくい表現を使うかもしれませんがお許し下さいm(T▽T)m
スコープ確認用のクラス
今回は動作確認用に以下のクラスを準備しました。
# coding: utf-8 class testClass : # publicなクラス変数 class_variable_A = 1 # privateなクラス変数 __class_variable_B = 2 # コンストラクタ def __init__(self) : print( "コンストラクタを呼び出し" ) # publicなインスタンス変数 self.instance_variable_A = 3 # privateなインスタンス変数 self.__instance_variable_B = 4 # デストラクタ def __del__(self) : print( "デストラクタを呼び出し" ) # publicなクラスメソッド @classmethod def class_method_A(cls) : print( "publicなクラスメソッドを呼び出し" ) # privateなクラスメソッド @classmethod def __class_method_B(cls) : print( "privateなクラスメソッドを呼び出し" ) # publicなインタンスメソッド def instance_method_A(self) : print( "publicなインタンスメソッド" ) # privateなインタンスメソッド def __instance_method_B(self) : print( "privateなインタンスメソッド" )
コンストラクタ
インスタンスが作成されるタイミングで呼び出されるメソッドです。
書き方は以下のとおり。「__init__」という関数を定義する感じです。
引数のselfは、インスタンス自身を示す特殊な引数。JavaやC#でいうthis句みたいなもの。
なお、ここではselfという変数名にしていますが別にselfとする必要はありません。
ただ伝統的にselfが使われるようなので従っておきます。
def __init__(self) :
なおクラスからインスタンスを作成するには以下のような感じです。
JavaやC#のようにnew句は必要ないようです。
またself変数も指定する必要はありません。
※以降の説明ではobjという変数はtestClassクラスのインスタンスを指します。
obj = testClass()
デストラクタ
インスタンスが破棄されるタイミングで呼び出されるメソッドです。
書き方は以下のとおり。「__del__」という関数を定義する感じです。
selfはコンストラクタと同じです。
def __del__(self) :
publicなクラス変数
javaやC#でいうpublic staticなフィールド変数みたいなもののようです。
宣言はclass内で普通に変数宣言すればよいようです。
「クラス名.変数名」でアクセス可能です。
print("class_variable_A =", testClass.class_variable_A)
また、インスタンス名.変数名でもアクセス可能です。
print("class_variable_A =", obj.class_variable_A)
privateなクラス変数
javaやC#でいうprivate staticなフィールド変数みたいなもののようです。
宣言はpublicなクラス変数と同様ですが、変数名の前に__を付ける必要があります。
privateとしていますが「クラス名._クラス名__変数名」でアクセスができてしまいます。
print("__class_variable_B =", testClass._testClass__class_variable_B)
また、「インスタンス名._クラス名__変数名」でもアクセス可能です。
print("__class_variable_B =", obj._testClass__class_variable_B)
publicなインスタンス変数
javaやC#でいうpublic なフィールド変数のようです。
コンストラクタやインスタンスメソッドの引数であるselfを使って、「self.変数名」で宣言可能です。
「インスタンス名.変数名」でアクセス可能です。
print("instance_variable_A =", obj.instance_variable_A)
privateなインスタンス変数
javaやC#でいうprivateなフィールド変数のようです。
コンストラクタやインスタンスメソッドの引数であるselfを使って、「self.__変数名」で宣言可能です。
privateとしていますが「インスタンス名._クラス名__変数名」でアクセスができてしまいます。
print("__instance_variable_B =", obj._testClass__instance_variable_B)
publicなクラスメソッド
javaやC#でいうpublic staticなメソッドみたいなものです。
宣言はクラス内で関数宣言した後に@classmethodというアノテーションを付けます。
引数clsはクラス自身を示す特殊な引数です。
@classmethod def class_method_A(cls) :
「クラス名.メソッド名」でアクセス可能です。
testClass.class_method_A()
また、インスタンス名.メソッド名でもアクセス可能です。
obj.class_method_A()
privateなクラスメソッド
javaやC#でいうprivate staticなメソッドみたいなもののようです。
宣言はpublicなクラス変数と同様ですが、メソッド名の前に__を付ける必要があります。
privateとしていますが「クラス名._クラス名__メソッド名」でアクセスができてしまいます。
testClass._testClass__class_method_B()
また、「インスタンス名._クラス名__メソッド名」でもアクセス可能です。
obj._testClass__class_method_B()
publicなインスタンスメソッド
javaやC#でいうpublicなメソッドのようです。
宣言はクラス内で関数宣言します。引数にはコンストラクタ同様にselfを持ちます。
「インスタンス名.変数名」でアクセス可能です。
obj.instance_method_A()
privateなインスタンスメソッド
javaやC#でいうprivateなメソッドみたいなもののようです。
宣言はpublicなインスタンスメソッドと同様ですが、メソッド名の前に__を付ける必要があります。
privateとしていますが「インスタンス名._クラス名__メソッド名」でアクセスができてしまいます。
obj._testClass__instance_method_B()
動作確認
動作確認用に以下のプログラムを用意しました。
# coding: utf-8 from test import testClass # インスタンス化 obj = testClass() # publicなクラス変数にアクセス print("class_variable_A =", testClass.class_variable_A) # クラスからアクセス print("class_variable_A =", obj.class_variable_A) # インスタンスからアクセス # privateなクラス変数にアクセス #print("__class_variable_B =", testClass.__class_variable_B) # クラスからアクセス:エラー print("__class_variable_B =", testClass._testClass__class_variable_B) # クラスからアクセス #print("__class_variable_B =", obj.__class_variable_B) # インスタンスからアクセス:エラー print("__class_variable_B =", obj._testClass__class_variable_B) # インスタンスからアクセス # publicなインスタンス変数にアクセス print("instance_variable_A =", obj.instance_variable_A) # インスタンスからアクセス # privateなインスタンス変数にアクセス #print("__instance_variable_B =", obj.__instance_variable_B) # インスタンスからアクセス:エラー print("__instance_variable_B =", obj._testClass__instance_variable_B) # インスタンスからアクセス # publicなクラスメソッドを呼び出し testClass.class_method_A() # クラスからアクセス obj.class_method_A() # インスタンスからアクセス #privateなクラスメソッドを呼び出し #testClass.__class_method_B() # クラスからアクセス:エラー testClass._testClass__class_method_B() # クラスからアクセス #obj.__class_method_B() # クラスからアクセス:エラー obj._testClass__class_method_B() # クラスからアクセス # publicなインスタンスメソッドを呼び出し obj.instance_method_A() # インスタンスからアクセス # privateなインスタンスメソッドを呼び出し #obj.__instance_method_B() # インスタンスからアクセス:エラー obj._testClass__instance_method_B() # インスタンスからアクセス
実行結果は以下になります。
コンストラクタを呼び出し class_variable_A = 1 class_variable_A = 1 __class_variable_B = 2 __class_variable_B = 2 instance_variable_A = 3 __instance_variable_B = 4 publicなクラスメソッドを呼び出し publicなクラスメソッドを呼び出し privateなクラスメソッドを呼び出し privateなクラスメソッドを呼び出し publicなインタンスメソッド privateなインタンスメソッド デストラクタを呼び出し
Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみる
前回までは、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、Cortex-M4でCMSISライブラリを使わずにSysTickを制御してみたいと思います。
SysTickとは
ARMマイコンが周辺機器とは別に持っている24ビットのシステム・タイマです。
特徴としては以下のとおりです。
- LOADレジスタ値(再ロード値)をVALレジスタ(カウンタ値)に読み込む。
- VALレジスタ値をカウント・ダウンする。
- VALレジスタが0になるとSysTick割り込みを発生させる。
- VALレジスタが0になると1. を実施する。
用途としてはOSのスケジューリングを行うためのシステム時間の計測などに使用されます。
レジスタの調査
今回はCMSISライブラリを使用しないのでSysTickのレジスタを調べる必要があります。
レジスタは、STM32F407VGのプログラミング・マニュアルに記載されています。
STK_CTRLレジスタ
SysTickを制御したり、ステータスを確認したりするレジスタです。
これによると
ビット 0 (カウンタ・イネーブル)を 1 にするとカウンタが開始し、0 にするとカウントが停止するようです。
ビット 1 (SysTick 例外要求の有効化)を 1 にするとカウント・ダウンが0になったとき、SysTick割り込みを発生するようになります。
ビット 2 (クロック・ソース)はクロック・ソースの選択です。1を選択しておくことでプロセッサ・クロックを利用するみたいです。
ビット 16 (COUNTFLAG)はSTK_VALレジスタが0になったときに 1 になり、STK_VALレジスタが更新されたときに 0 になるようです。
つまりカウント・ダウンが完了したときに 0、再ロード値が読み込まれたとき 1 になります。
STK_LOADレジスタ
カウント・ダウンの開始位置を示すレジスタです。
0~23の24ビットでカウント・ダウンさせたい時間を指定するようです。
STK_VALレジスタ
現在のカウント値を示すレジスタです。 STK_LOADレジスタと同様に24ビットでカウンタ値を持ちます。 このレジスタが初期化されるとSTK_CTRLレジスタのCOUNTFLAGもクリアされるようです。
実装してみる
全体を説明すると長くなるので、ここでは部分的に紹介します。
Tagは「20210111」です。
割り込みの設定
SysTick割り込みをハンドルするためにベクタ・テーブルを用意します。
ベクタ・テーブルは以下のような感じにする必要があります。
SysTickは例外番号15のようです。
ベクタ・テーブルは.isr_vectorセクションに配置するようにしています。
ちなみに.text .data .sdata .bss以外の固有セクションに配置するときは、.section .isr_vector,"a",%progbitsのような感じで指定してやる必要があるみたいです。
"a"は配置する、%progbitsはデータという意味らしいです。
.syntax unified .thumb .section .isr_vector,"a",%progbits .align 2 .globl __Vectors __Vectors: .long __main_stack_start /* Main stack pointer (MSP) */ .long Reset_Handler /* Reset Handler */ .long NMI_Handler /* NMI Handler */ .long HardFault_Handler /* Hard Fault Handler */ .long MemManage_Handler /* MPU Fault Handler */ .long BusFault_Handler /* Bus Fault Handler */ .long UsageFault_Handler /* Usage Fault Handler */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long SVC_Handler /* SVCall Handler */ .long DebugMon_Handler /* Debug Monitor Handler */ .long 0 /* Reserved */ .long PendSV_Handler /* PendSV Handler */ .long SysTick_Handler /* SysTick Handler */
リンカ・スクリプトは以下のような感じです。
ベクタ・テーブルはROMの先頭に配置するようにしました。
MEMORY { /* 0x20000000 から 128KB */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x20000 /* 0x08000000 から 1024KB FLASH */ ROM (rx) : ORIGIN = 0x08000000, LENGTH = 0x100000 } SECTIONS { /* The startup code into ROM memory */ /* ベクターテーブル配置用のセクション */ .isr_vector : { . = ALIGN(4); /* ロケーションカウンタから4byteアライン */ KEEP(*(.isr_vector)) /* vectorテーブル */ . = ALIGN(4); /* ロケーションカウンタから4byteアライン */ } >ROM
SysTickドライバ
ドライバは初期化、開始、停止という感じで機能分けするようにしてみました。
#include "iodefine.h" #include "SysTick.h" uint8_t tick_1ms_flag; void SysTick_Handler(void); /* OSタイマドライバ初期化 */ void SysTick_init(void) { REG_STK.VAL = 0; /* カウンタの現在値をクリア */ REG_STK.LOAD = 16000; /* カウントダウン開始位置を設定(1ms = 16MHz ) */ REG_STK.CTRL = 0x6; /* 制御レジスタを設定 */ /* - 2bit : クロックソースに プロセッサ・クロック(AHB)を設定 */ /* - 1bit : SysTick 例外要求を有効 */ /* - 0bit : カウンタ停止 */ tick_1ms_flag = 0; } /* OSタイマ開始 */ void SysTick_start(void) { REG_STK.CTRL |= 0x1; /* カウンタ開始 */ } /* OSタイマ停止 */ void SysTick_stop(void) { REG_STK.CTRL &= ~(0x1); /* カウンタ停止 */ } /* 割り込みハンドラ */ void SysTick_Handler(void) { if ( ( REG_STK.CTRL & 0x00010000UL ) != 0UL ) { tick_1ms_flag = 1; } }
動かしてみる
ユースケースとして以下のようなプログラムを用意しました。
ビジーループ内では、SysTick割り込み(1[ms])があったときに立つtick_1ms_flagを監視させました。
その後、tick_1ms_flagが立った回数をカウントして1[s]を計測するようにしています。
1[s]経過していたら、これまでと同様にLチカさせています。
#include "PIOdriver.h" #include "SysTick.h" #include "type.h" extern uint8_t tick_1ms_flag; int main(void) { uint8_t status = PIO_PORT_GROUP_D_12; uint8_t gpio_lv = PIO_SIGNAL_LV_L; static uint32_t tick_1sec_flag = 0; /* ポートドライバを初期化 */ PIO_init(); /* OSタイマドライバを初期 */ SysTick_init(); /* OSタイマ開始 */ SysTick_start(); while(1) { /* 1msTick割り込みがあった場合 */ if(tick_1ms_flag == 1) { tick_1ms_flag = 0; tick_1sec_flag++; } /* 1sec経過していなかった場合 */ if(tick_1sec_flag != 1000) { continue; } tick_1sec_flag = 0; switch(status) { case PIO_PORT_GROUP_D_12: status = PIO_PORT_GROUP_D_13; break; case PIO_PORT_GROUP_D_13: status = PIO_PORT_GROUP_D_14; break; case PIO_PORT_GROUP_D_14: status = PIO_PORT_GROUP_D_15; break; case PIO_PORT_GROUP_D_15: status = PIO_PORT_GROUP_D_12; break; defualt: break; } /* ポート読み込み */ gpio_lv = PIO_read(status); /* 読み込んだポートの値を反転させて書き込み */ PIO_write(status, ~gpio_lv); } return (0); }
こんな感じで動けば成功です!
Cortex-M4でCMSISライブラリを使わずにGPIO制御してみる(拡張)
前回は、Cortex-M4でCMSISライブラリを使わずにGPIO制御してみました。
今回は、その延長で複数のGPIOを制御してLチカさせます。
内容は前回やったことに毛が生えたような感じですが、今後やりたいことがあり、そのデバッグ目的のためにやります。
User LEDについて
STM32F407VGに搭載されているUser LEDは、red/blue/green/orangeの4種類あります。
いずれもポートグループはDグループのようです。
実装してみる
全体を説明すると長くなるので、ここでは部分的に紹介します。
Tagは「20210110_01」です。
ポートグループのテーブルを作成
今回は、以下のような列挙型とポートグループのテーブルを作成しました。
テーブルにはポートグループのアドレスとポート番号を登録しておくようにします。
このテーブルに列挙型に定義したグループIDを使ってアクセスさせようと思います。
※このように設計しておくことが使いやすくなるかはさておき(・`ω´・)
typedef enum { PIO_PORT_GROUP_D_12 = 0, PIO_PORT_GROUP_D_13, PIO_PORT_GROUP_D_14, PIO_PORT_GROUP_D_15, PIO_PORT_GROUP_NUM } PIO_PORT_GROUP_ID;
typedef struct { uint32_t port_reg_addr; uint8_t port_idx; } PIO_PORTSET; static PIO_PORTSET s_pio_port_config_table[PIO_PORT_GROUP_NUM] = { { REG_GPIO_D_ADDR, 12 }, /* DP12 */ { REG_GPIO_D_ADDR, 13 }, /* DP13 */ { REG_GPIO_D_ADDR, 14 }, /* DP14 */ { REG_GPIO_D_ADDR, 15 } /* DP15*/ };
ポートの初期化
つづいてポートの初期化です。
レジスタのおさらいですがモード選択レジスタは以下のようになっていました。
今回はDPの12, 13, 14, 15を使用したいので、24, 26, 28, 30bitに1を立てればよいようです。
一方でスピード設定レジスタは、高速設定するために24~31bitに1を立てる必要があります。
したがってポートグループのテーブルからポート番号を取り出して、
モード選択レジスタに立てるビットは、 「ポート番号 * 2」、
スピード設定レジスタに立てるビットは、「ポート番号 * 2」と「(ポート番号 * 2) + 1」
という感じで算出するようにしました。(このように設計しておくことがよいかはさておき。)
void PIO_init(void) { ST_REG_GPIO* port_reg; uint8_t port_idx; /* IOポート D にクロックを供給 */ REG_RCC_AHB1ENR |= 0x8; for(int i = 0; i < PIO_PORT_GROUP_NUM; i++) { /* ポートグループのレジスタアドレスとポート番号を取得 */ port_reg = (ST_REG_GPIO*)s_pio_port_config_table[i].port_reg_addr; port_idx = s_pio_port_config_table[i].port_idx; /* ポートモードを汎用出力モードに設定 */ port_reg->MODER |= ( 1 << ( port_idx * 2 ) ); /* ポート出力スピードを高速に設定 */ port_reg->OSPEEDR |= ( ( 1 << ( port_idx * 2 ) ) | ( 1 << ( ( port_idx * 2 ) + 1 ) ) ); } }
ポートの書き込み
書き込み関数は、引数にポートグループのIDを追加しました。
初期化関数と同様にポートグループのIDを使用してレジスタのアドレスとポーと番号を取得するように変更しました。
void PIO_write(PIO_PORT_GROUP_ID port_group_id, uint8_t lv) { ST_REG_GPIO* port_reg; uint8_t port_idx; /* ポートグループのレジスタアドレスとポート番号を取得 */ port_reg = ( ST_REG_GPIO* )s_pio_port_config_table[port_group_id].port_reg_addr; port_idx = s_pio_port_config_table[port_group_id].port_idx; /* Lだった場合 */ if( lv == PIO_SIGNAL_LV_L ) { port_reg->ODR &= ~( 1 << port_idx ); } /* Hだった場合 */ else { port_reg->ODR |= ( 1 << port_idx ); } }
ポートの読み込み
読み込み関数も書き込み関数とほぼ同じ変更です。
uint8_t PIO_read(PIO_PORT_GROUP_ID port_group_id) { ST_REG_GPIO* port_reg; uint8_t port_idx; uint8_t lv; /* ポートグループのレジスタアドレスとポート番号を取得 */ port_reg = (ST_REG_GPIO*)s_pio_port_config_table[port_group_id].port_reg_addr; port_idx = s_pio_port_config_table[port_group_id].port_idx; /* Lだった場合 */ if( ( ( port_reg->ODR >> port_idx ) & 0x1 ) == PIO_SIGNAL_LV_L ) { lv = PIO_SIGNAL_LV_L; } /* Hだった場合 */ else { lv = PIO_SIGNAL_LV_H; } return (lv); }
動かしてみる
ユースケースとしてmain関数を以下のようにしてみました。
このコードでは1[s]毎に点滅させるLEDを選択しています。
int main(void) { uint8_t status = PIO_PORT_GROUP_D_12; uint8_t gpio_lv = PIO_SIGNAL_LV_L; /* ポートドライバを初期化 */ PIO_init(); while(1) { switch(status) { case PIO_PORT_GROUP_D_12: status = PIO_PORT_GROUP_D_13; break; case PIO_PORT_GROUP_D_13: status = PIO_PORT_GROUP_D_14; break; case PIO_PORT_GROUP_D_14: status = PIO_PORT_GROUP_D_15; break; case PIO_PORT_GROUP_D_15: status = PIO_PORT_GROUP_D_12; break; defualt: break; } /* ポート読み込み */ gpio_lv = PIO_read(status); /* 読み込んだポートの値を反転させて書き込み */ PIO_write(status, ~gpio_lv); /* wait 1sのつもり*/ for(int i = 0; i < 16000000; i++) { __asm("nop"); } } return (0); }
このように動けば成功です!
Cortex-M4でCMSISライブラリを使わずにGPIO制御してみる
前回は、eclipse Embedded CDTでSTM32F407VG(Cortex-M4)のベアメタルをデバッグしました。
今回は、CMSISライブラリを使わずにGPIOを制御してUser LED(green)をLチカさせてみたいと思います。
User LED(green)の接続先
まずはUser LED(green)の接続先を探します。
接続先は、STM32F4DISCOVERYのユーザマニュアルに記載されています。
これによるとUser LED(green)は、PD12(ポートグループDの12番)に接続されているようです。
レジスタの調査
今回はCMSISライブラリを使用しないのでPD12を制御するためのレジスタを調べる必要があります。
レジスタは、STM32F407VGのテクニカルリファレンスマニュアルに記載されています。
RCC_AHB1ENRレジスタ
AMRは省電力のために必要なペリフェラル毎にクロックを供給するかどうかを設定できます。
リセット時は大抵の場合、クロックは供給されておらず自分で設定する必要があります。
Cortex-M4にはRCC(たぶんReset Clock Control?)というリセットやクロック制御をするための機能があります。
この機能が持つレジスタにRCC_AHB1ENRというレジスタあり、さらに3bit目にGPIODEN(PDクロックイネーブル)があります。
このbitを1にすれば、PDにクロックが供給されるようです。
GPIOx_MODER
GPIOの制御をするための基本的なレジスタ。
GPIOx_MODERレジスタは、GPIOポートのモードを設定できます。
今回はPD12を制御したいので、このレジスタの24bit目を1に、25bit目を0に(汎用出力モード)にすればよさそうです。
GPIOx_OSPEEDR
あと設定したほうがよさそうなのはGPIOx_OSPEEDRレジスタくらいでしょうか?
このレジスタはIOの出力スピードを設定できます。
これによると24bit目を1に、25bit目を1に(高速)にすればよさそうです。
実装してみる
全体を説明すると長くなるので、ここでは部分的に紹介します。
ソースコード全体はgithubにeclipse Embedded CDTのworkspaceとして読み込めるようにして置いてみました。
自分のお勉強目的でコメントが汚いですがお許しください(>_<)
まずはレジスタの定義です。
GPIO関連のレジスタは構造体を作成してメモリマットIOアドレスにマッピングしてあります。
typedef struct /* GPIOレジスタ構造 */ { uint32_t MODER; uint32_t OTYPER; uint32_t OSPEEDR; uint32_t PUPDR; uint32_t IDR; uint32_t ODR; uint32_t BSRR; uint32_t LCKR; uint32_t AFRL; uint32_t AFRH; } ST_REG_GPIO; #define REG_RCC_AHB1ENR ( *( ( uint32_t* )0x40023830 ) ) #define REG_GPIO_D ( *( ( ST_REG_GPIO* )0x40020C00 ) )
続いてPIO(Port IO)ドライバです。
本当は、read/write関数はポートグループとポート番号を指摘できるようにしてもう少し汎用的に使用できるようにしたかったのですが今回は手抜きです笑
/* PIOドライバ初期化処理 */ void PIO_init(void) { /* IOポート D にクロックを供給 */ REG_RCC_AHB1ENR |= 0x8; /* PD12のポートモードを汎用出力モードに設定 */ REG_GPIO_D.MODER |= ( 1 << 24 ); /* PD12のポート出力スピードを高速に設定 */ REG_GPIO_D.OSPEEDR |= ( ( 1 << 24 ) | ( 1 << 25 ) ); } /* PIOドライバ書き込み機能 */ void PIO_write(uint8_t lv) { /* Lだった場合 */ if( lv == PIO_SIGNAL_LV_L ) { REG_GPIO_D.ODR &= ~( 1 << 12 ); } /* Hだった場合 */ else { REG_GPIO_D.ODR |= ( 1 << 12 ); } } /* PIOドライバ読み込み機能 */ uint8_t PIO_read(void) { uint8_t lv; /* Lだった場合 */ if( ( ( REG_GPIO_D.ODR >> 12 ) & 0x1 ) == PIO_SIGNAL_LV_L ) { lv = PIO_SIGNAL_LV_L; } /* Hだった場合 */ else { lv = PIO_SIGNAL_LV_H; } return (lv); }
最後にエントリポイントです。
LチカはPIO_read()の値を反転させて作っています。
waitでforで強引に実装しました。
int main(void) { /* ポートドライバを初期化 */ PIO_init(); while(1) { /* 読み込んだポートの値を反転させて書き込み */ PIO_write( ~PIO_read() ); /* wait 1sのつもり*/ for(int i = 0; i < 16000000; i++) { __asm("nop"); } } return (0); }
動かしてみる
前回と特に変わりないですがUserLED(green)がチカチカしていれば成功です。
こんな簡単なことでもCMSISライブラリを使うとあっと言う間にできてしまうのに久しぶりにレジスタを調べたりしていると思いのほか時間がかかりました(;´Д`)
eclipse Embedded CDTを使ってAMR用のベアメタルをデバッグしてみる
前回はeclipse Embedded CDTをインストールしてWidnowsだけで動くAMR用のベアメタル開発環境を構築してみました。
今回はeclipse Embedded CDTを使用してAMR用のベアメタルをデバッグしてみました。
備忘録として残します。
プロジェクトを作成する
eclipseを起動してメニューバーより File > New >Projectを選択します。
C Projectを選択してプロジェクトを作成します。
今回はproject typeを STM32F4xx C/C++ Project をにしました。
またTarget processor settingsよりContentを Blinky (blink a led)を選択しました。
(Lチカテンプレートです)
あとはメニューバーより Project > Build All を実行してビルドができることを確認しておきます。
これでプロジェクトの作成は完了です。
デバッガを設定する
続いてDebug Configurationを作成します。
メニューバーより Run > Debug Configurationsを選択するとDebug Configurationsウィンドウが開きます。
その後、Debug Configurationsウィンドウの左サイドメニューの GDB QEMU Debugging をダブルクリックするとDebug Configurationが作成されます。
あとはDebuggerタブより使用したいBorad nameとDevice nameを入力して完了です。
今回はSTM32F407VGというボードを使ってみることにします。
デバッグしてみる
Debug Configurationsウィンドウの Debugボタンを押下するとデバッガが起動します。
あとはメニューバーより Run > Resume を選択すると動き出します。
ボードの絵が出てきてなんかかっちょいいですが、Lチカは黄緑色の四角いテクスチャをテカテカさせることで表現していてなんかしょぼい笑
嫌いではないですけどね!
eclipse Embedded CDTでAMR用のベアメタル開発環境を作ってみる
eclipseを使用したARMの開発環境 eclipse Embedded CDT が便利だという話を小耳にはさみました。
eclipse Embedded CDT はeclipseにクロスツールチェーンやプラグインを追加して使うような開発環境のようです。
今回は、Windows 10 64bitマシンにeclipse Embedded CDTによる開発環境を構築してみました。(・`ω´・)b
eclipse Embedded CDTのインストール
インストール手順は以下を参考にしました。
基本的にはここに書いてあるとおりにすればインストールできました(;´Д`)
Javaをインストール
eclipseを動かすのにJDKが必要です。
ただしJDK15ではQt plug-inが動かないようなので以下からOpenJDK 11 (LTS)をインストールしました。
Node.jsをインストール
ビルドツールのインストールはxPack Package Manager(xpm)を使って行うようです。
またxpmを使うにはNode.jsが必要です。
なので、以下からダウンロードしてインストールします。
インストールが完了したらコマンドプロンプトで以下のコマンドを実行して確認しておきます。
> npm --version 6.14.9
xPack Package Manager(xpm)のインストール
Node.jsがインストールできたら以下のコマンドをコマンドプロンプトで実行してxpmをインストールします。
> npm install --global xpm@latest
インストールが完了したらコマンドプロンプトで以下のコマンドを実行して確認しておきます。
> xpm --version 0.7.1
windows-build-toolsのインストール
xpmがインストールできたら以下のコマンドをコマンドプロンプトで実行してwindows-build-toolsをインストールします。
> xpm install --global @xpack-dev-tools/windows-build-tools@latest
arm-none-eabi-gccのインストール
xpmがインストールできたら以下のコマンドをコマンドプロンプトで実行してarm-none-eabi-gccをインストールします。
> xpm install --global @xpack-dev-tools/arm-none-eabi-gcc@latest
qemu-armのインストール
xpmがインストールできたら以下のコマンドをコマンドプロンプトで実行してqemu-armをインストールします。
> xpm install --global @xpack-dev-tools/qemu-arm@latest
openocdのインストール
xpmがインストールできたら以下のコマンドをコマンドプロンプトで実行してqemu-armをインストールします。
> xpm install --global @xpack-dev-tools/openocd@latest
riscv-none-embed-gccのインストール
xpmがインストールできたら以下のコマンドをコマンドプロンプトで実行してqemu-armをインストールします。
> xpm install --global @xpack-dev-tools/riscv-none-embed-gcc@latest
eclipseのインストール
eclipse本体をインストールします。
以下のサイトからインストーラをダウンロードします。
インストールする際は Eclipse IDE for C/C++ Developers を選択します。
プラグインをインストール
eclipseにCDT(C/C++ Development Tools)のプラグインをインストールします。
eclipseのメニューバーよりhelp > Install New Softwareを選択します。
Addボタンを押してリポジトリを追加します。
リポジトリは「http://gnu-mcu-eclipse.netlify.com/v4-neon-updates/」になります。
Embeded C/C++ Cross Development Toolsを選択してインストールします。
これでインストールは完了です!
参考になれば幸いです。
システムコールを実装してnewlibのprintfを使えるようにする
Raspberry pi のベアメタル開発環境構築の続編です。
以前、Crosstool-NGでRPiのベアメタル開発環境を作るときに、newlibも一緒にインストールしました。
今回は、システムコールを実装してnewlibのprintf()を使えるようにしてみようと思います。
printfがないと簡単なデバッグも面倒ですからね( ^ω^;)
システムコールとは
システムコールとは、OSの機能をアプリケーションが利用するための機構のことです。
https://ja.wikipedia.org/wiki/システムコール
簡単な絵を描いてみました
アプリケーションはOSやハードウェアの機能を使用するためにシステムコールを呼び出しますが、
よりシステムコールを使いやすくするために標準ライブラリ関数がいくつかあります。
printfなどの標準ライブラリ関数も内部ではシステムコールを呼び出してドライバを経て画面出力を行っています。
システムコールがない状態でビルドしてみる
以下はprintf()でhello worldコードです。
#include <stdio.h> int main() { printf("hello world!\n"); return (0); }
これを以前作った開発環境を使ってビルドすると以下のようになります。
システムコールが実装されていないので当然いっぱい怒られます(・´ω`・;)
"undefined reference to”と出ているものが不足しているシステムコールになります。
$ aarch64-rpi3-elf-gcc main.c -o main aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-exit.o): in function `exit': aarch64-rpi3-elf/src/newlib/newlib/libc/stdlib/exit.c:64: undefined reference to `_exit' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-sbrkr.o): in function `_sbrk_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/sbrkr.c:51: undefined reference to `_sbrk' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-writer.o): in function `_write_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/writer.c:49: undefined reference to `_write' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-closer.o): in function `_close_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/closer.c:47: undefined reference to `_close' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-fstatr.o): in function `_fstat_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/fstatr.c:55: undefined reference to `_fstat' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-isattyr.o): in function `_isatty_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/isattyr.c:52: undefined reference to `_isatty' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-lseekr.o): in function `_lseek_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/lseekr.c:49: undefined reference to `_lseek' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-readr.o): in function `_read_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/readr.c:49: undefined reference to `_read' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-abort.o): in function `abort': aarch64-rpi3-elf/src/newlib/newlib/libc/stdlib/abort.c:59: undefined reference to `_exit' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-signalr.o): in function `_kill_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/signalr.c:53: undefined reference to `_kill' aarch64-rpi3-elf/bin/ld.bfd: aarch64-rpi3-elf/lib/libc.a(lib_a-signalr.o): in function `_getpid_r': aarch64-rpi3-elf/src/newlib/newlib/libc/reent/signalr.c:83: undefined reference to `_getpid'
システムコールを実装してみる
以下にnewlibで使うシステムコールの最小限の実装が書かれています。
ここを参考にして、やっつけで実装してみました(^ω^;)
https://www.sourceware.org/newlib/libc.html#Syscalls
#include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <stdint.h> #include "uart.h" double __trunctfdf2( long double A ); caddr_t _sbrk(int incr); int _close(void *reent, int fd); int _fstat(void *reent, int fd, struct stat *pstat); off_t _lseek(void *reent, int fd, off_t pos, int whence); int _read(int file, char* ptr, int len); int _write(int file, char* ptr, int len); void _exit(int code); int _getpid(void); int _isatty(int file); void _kill(int pid, int sig); double __trunctfdf2( long double A ) { return ( -1 ); } register char * stack_ptr asm ("sp"); caddr_t _sbrk(int incr) { extern char _end; /* Defined by the linker */ static char *heap_end; char *prev_heap_end; if (heap_end == 0) { heap_end = &_end; } prev_heap_end = heap_end; if (heap_end + incr > stack_ptr) { _write (1, "Heap and stack collision\n", 25); abort(); } heap_end += incr; return (caddr_t) prev_heap_end; } int _close(void *reent, int fd) { return ( 0 ); } int _fstat(void *reent, int fd, struct stat *pstat) { pstat->st_mode = S_IFCHR; return ( 0 ); } off_t a; off_t _lseek(void *reent, int fd, off_t pos, int whence) { return (a); } int _read(int file, char* ptr, int len) { int r; for ( r =0; r < len; r++ ) { ptr[r] = uart_getc(); } return len ; } int _write(int file, char* ptr, int len) { int r; for ( r =0; r < len; r++) { uart_send(ptr[r]); } return len ; } void _exit(int code) { return; } int _getpid(void) { return ( -1 ); } int _isatty(int file) { return ( -1 ); } void _kill(int pid, int sig) { return; }
必要なシステムコールはいろいろありますが、とりわけ必要なのがsbrk()、 read()、write()です。
これを実装すれば最低限printf()は動くと思います。
sbrk()
sbrk()はヒープとして使えるメモリ領域をOSに確保するように要求するためのコールバックです。
sbrk()は呼び出されるたびに引数で指定したサイズだけヒープ領域を拡張し、
拡張したヒープ領域の先頭アドレスを返せば良いようです。
sbrk()については以下のサイトが参考になりました。
sbrkのシステムコールを直接呼び出す - Kludge Factory様
read()
ファイルを読み込むためのシステムコールです。
本来は、ファイルディスクリプタ(第1引数:file)から最大バイト数(第3引数:len)をバッファ(第2引数:ptr)に読み込むように実装します。
今回は、デバッグ用のUARTから読み込みたいため、ここでUARTドライバ関数を呼び出します。
UARTドライバはRaspberry pi ベアメタルチュートリアルの「03 UART1」にあるサンプルを使用しました。
https://github.com/bztsrc/raspi3-tutorial/tree/master/03_uart1
write()
ファイルに書き込みを行うシステムコールです。
本来はファイルディスクリプタ(第1引数:file)にバッファ(第2引数:ptr)を最大バイト数(第3引数:len)書き込みを行うように実装します。
write()もread()同様、Raspberry pi ベアメタルチュートリアルのUARTドライバを使用してUART出力するようにしてました。
動かしてみる
Raspberry pi ベアメタルチュートリアルの「03 UART1」のフォルダ内にsystemcall.cを配置し、Makefile.gccに以下のように変更しました。
SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) CFLAGS = -Wall -O2 -ffreestanding -Ihome/usr/x-tools/aarch64-rpi3-elf/aarch64-rpi3-elf/sys-include -I/home/usr/x-tools/aarch64-rpi3-elf/lib/gcc/aarch64-rpi3-elf/10.2.0/include all: clean kernel8.img start.o: start.S aarch64-rpi3-elf-gcc $(CFLAGS) -c start.S -o start.o %.o: %.c aarch64-rpi3-elf-gcc $(CFLAGS) -c $< -o $@ kernel8.img: start.o $(OBJS) aarch64-rpi3-elf-ld -nostdlib -nostartfiles start.o $(OBJS) -L/home/usr/x-tools/aarch64-rpi3-elf/aarch64-rpi3-elf/lib -lc -T link.ld -o kernel8.elf aarch64-rpi3-elf-objcopy -O binary kernel8.elf kernel8.img clean: rm kernel8.elf *.o >/dev/null 2>/dev/null || true run: qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial stdio
あとはmain.cを以下のように変更しました。
#include <stdio.h> #include "uart.h" int main() { uart_init(); printf("hello world!\n"); return (0); }
再びビルド、実行して「hello world!」が表示されれば成功です。
$ make clean $ make $ make run qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial stdio VNC server running on 127.0.0.1:5900 hello world!
これで一応、QEMU上で動きましたがいまはかなり手抜きしているので、
最初にuart_init()を呼んでUARTドライバを初期化する必要があります。
また、ドライバの仕様上、printfする際にデリミタとして「\n」をつけてやらないとうまく動きませんでした。
まだまだ使いにくいですね(; ・`ω・´)