本記事について
Raspberry Pi用のC/C++開発環境のお手軽な作り方についてまとめます。C/C++開発にはクロスコンパイル環境を構築するのが一般的ですが、小規模なプロジェクトではかける手間に対してメリットが少ないと思っています。本記事では、できるだけお手軽に環境を整えることを目標としました。
もちろん、大規模なプロジェクトになったり、仕事で長期間1つのプロジェクトに取り組む場合はクロス環境の方がいいと思います。クロス開発環境の構築はこちらをご参考にしてみてください。
全てPi上でやる | 一般的なクロス開発環境 | 本記事の手法 | |
---|---|---|---|
Coding | Pi | ホストPC | ホストPC |
Build | Pi | ホストPC | Pi |
Debug | Pi | ホストPC | ホストPC/Pi |
メリット | 環境構築が不要 | 開発効率がいい | 環境構築が簡単。デバッグも少しやりやすい |
デメリット | 開発効率が悪い | 環境構築に時間がかかる ARM Toolchainのインストールが必要 |
規模が大きくなるとビルドに時間がかかる デバッグにGUIを使えない |
本記事でできるようにすること
とにかくお手軽に、ラズパイ用にC/C++の開発環境を整えます
- ホストPC上でコーディング
- ラズパイとのソースファイルの同期(ftp/rsync)
- ホストPCからデバッグ
環境
- Raspberry Pi (IPアドレスなどは適宜読み替えてください)
- IPアドレスは192.168.1.88
- SSHは有効済み
- ユーザー名 = pi
- パスワード = rasp
- Host PC
- どんな環境/OSでも大丈夫なはず。Windows10で確認
- 本記事ではエディタとしてVisual Studio Code (VSCode)を使用
- 他の類似エディタ(SublimeTextやAtom)でも同様のことはできるはずです
- ssh用のターミナルソフト (TeraTermとかmsysとか。Mac/Linuxなら標準ターミナルでOK)
対象とするソースコード
ホスト側で下記のようなシンプルなプロジェクトを用意する
MyCProject/
- main.cpp
#include <stdio.h>
int main()
{
printf("Hello World\n");
for(int i = 0; i < 10; i++) {
printf("i = %d\n", i);
}
printf("Done\n");
return 0;
}
コーディングとファイルの同期 (on Host PC)
出来ればいいこと
以下2つのことが出来ればどういうやり方でも良いです。
- ホストPCでコーディング
- ホストPC -> Raspberry Piへファイルのアップロード
方法1
- 好きなエディタでコーディングする
- SFTPソフト(File Zilla)などでRaspberry Piにアップロードする
- または、sftpコマンドかrsyncコマンドでアップロードする
方法2: Visual Studio Codeなどのエディタを使う
エディタとアドインソフト(エクステンション機能)を使用する。
- Visual Studio Codeにftp用のエクステンション(例えば、sftp by liximomo)をインストールする
- プロジェクト用のフォルダ(MyCProjectフォルダ)を開く
- Ctrl-Shift-pでコマンドパレットを開いてから、
SFTP: Config
を開く - sftp.jsonを以下のように編集する
{
"host": "192.168.1.88",
"port": 22,
"username": "pi",
"password": "rasp",
"protocol": "sftp",
"agent": null,
"privateKeyPath": null,
"passphrase": null,
"passive": false,
"interactiveAuth": false,
"remotePath": "/home/pi/MyCProject",
"uploadOnSave": true,
"syncMode": "update",
"watcher": {
"files": false,
"autoUpload": false,
"autoDelete": false
},
"ignore": [
"**/.vscode/**",
"**/.git/**",
"**/.DS_Store"
]
}
- VSCodeの左側のエクスプローラーエリアの適当なところで右クリック ->
SFTP: Upload
をクリック - Raspberry Pi側にコードがアップロードされる
- 設定で
uploadOnSave
をtrueにしているため、この後は保存するたびに自動的にアップロードされる。(がっつり実装するときは一時的にfalseにしておいたほうがいいかもしれません) - 新規ファイル作成時は手動で
SFTP: Upload
が必要
Visual Studio Codeには他にもftp用エクステンションが複数あるので、好きなのをご使用ください。自前のコマンドでftpアップロードしてもいいです。
ビルドと実行 (on Raspberry Pi)
ホストPCからsshでラズパイにログインして、コンパイル、実行してみる。gccの-g
オプションはデバッグするときには必ずつけてください。
HOST> ssh pi@192.168.1.88
PI> cd MyCProject
PI> gcc -g3 main.cpp
PI> ./a.out
デバッグ (on Host PC)
ビルドしたものが一発で期待通りに動けばいいのですが、通常はそうはいきません。printfデバッグをする場合は、ひたすらコーディングと実行を繰り返せばいいです。デバッグを効率的にやるにはデバッガを使用すべきです。ステップ実行や実行中の変数の状態を見ることが出来ます。ちなみに、小見出しを「on Host PC」としていますが実行自体はラズパイ上で行われます。デバッグ操作をホストPC側からできるという意味です。
gdbでデバッグする (1)
ホストPCからsshでラズパイにログインして、デバッグする。とりあえず、main関数の先頭にブレークポイントを張ってからステップ実行する例
HOST> ssh pi@192.168.1.88
PI> cd MyCProject
PI> gdb ./a.out
(gdb) b main
(gdb) run
(gdb) n
(gdb) n
gdbでデバッグする (2)
上記方法で最低限のデバッグが出来るのですが、表示される情報が貧相でやりづらいです。見た目も格好良くありません。gdb用の設定ファイルを変えることで格好いい見た目にすることが出来ます。GDB dashboardというものを使います。方法は設定ファイルをホームディレクトリにコピーするだけです。gdb起動直後はあまり通常の場合と変わりませんが、ブレークするといろいろな情報を表示してくれる画面に代わります。ファイルの配置方法と、ブレーク後に変数iを見てみる例
PI> cd
PI> wget -P ~ git.io/.gdbinit
PI> cd MyCProject
PI> gdb ./a.out
>>> b main
>>> run
>>> p i
>>> p i = 9
gdbでデバッグする (3)
上記方法だとデバッガの詳細出力もコンソール入力も全て同じターミナルになります。複数ターミナルでやると便利です。例えば、ターミナル1でgdbコマンドを入力して、ターミナル2でデバッガの出力をすることが出来ます。まずはターミナルを2つ立ち上げてどちらもラズパイにssh接続します。そしてttyコマンドでターミナル名を取得して出力先を切り替えます。具体的には以下のようにします(2つのターミナルのコマンドをまとめて書いています)
PI_1> tty # ターミナル1でターミナル名を確認
/dev/pts/1
PI_0> gdb ./a.out # ターミナル0でgdb起動
>>> dashboard -output /dev/pts/1 # 出力先を↑で確認したターミナルに切り替える
>>> b main
>>> run
これで/dev/pts/1のターミナルにデバッガ出力が表示されるはずです。
Visual Studio Codeからリモートデバッグする (未)
GDBを使用してホストPC側からデバッグできるようになりました。GDB dashboardのおかげでだいぶ使いやすくはなったのですが、基本的にはコマンドベースです。GUIからできるようにします。
が、、、 お手軽には無理でした。
ラズパイ側にgdbserverをインストールして実行。ホストPC側ではVSCodeにNative Debug(by webfreak)エクステンションを使用して、ホスト側から実行するところまでは行けたのですが、ブレークポイントを張ったりするのが出来ませんでした。VSCodeをgdbのフロントエンドとして使うことで行けると思ったのですが、ARM用のツールチェインが必要そうです。(バイナリが違うから当然といえば当然なのですが、Cの関数と行数レベルでブレークさせたりはできると思ったんですけどね)
お手軽にやるにはこれくらいが限界のようです。
⇒ できた (https://qiita.com/take-iwiw/items/5b20558f8ab3f27ca4a4 )
おまけ
ビルド用のmakefileとディレクトリ構成一式
これまでの例ではコンパイルはコマンドラインから直接gccで行いましたが、ソースコード数が増えたりした場合にはmakeを使用した方が便利です。以下のような構成をmakeするためのmakefile例です。全部入りでまとめて書きたかったので、サブモジュールとして静的ライブラリ(*.a)と動的ライブラリ(.so)を両方使用しています。使わない場合は適宜サブモジュールのディレクトリを削除してください。中規模なプロジェクトならこれで対応できると思います。もう少しスマートな書き方があるとは思います。
実行するときはライブラリパスを指定してあげる必要があるので、下記コマンドになります。
LD_LIBRARY_PATH=. ./a.exe
/ComplexCProject
- main.cpp, aaa.cpp
- makefile
- makefile_config
- subStaticModuleA/
- subA1.cpp, subA2.cpp
- makefile
- subDynamicModuleB/
- subB1.cpp, subB2.cpp
- makefile
## environment
#PLATFORM = WIN_MINGW
PLATFORM = LINUX
BINARY = DEBUG
#BINARY = RELEASE
ifeq ($(PLATFORM), WIN_MINGW)
# for win
MAKE = mingw32-make
#DEL = del
DEL = rm -f
CC = g++
AR = ar
else
# for linux
MAKE = make
DEL = rm -f
CC = g++
AR = ar
endif
ifeq ($(BINARY), DEBUG)
CFLAGS = -g3 -O0 -Wall
else
CFLAGS = -O3 -Wall
endif
LDFLAGS =
LIBS = -lm
OBJS = $(SRCS:.cpp=.o)
#OBJS = *.o
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $<
.cpp.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $<
include makefile_config
TARGET = a.out
SRCS = main.cpp aaa.cpp
SUBDIRS = subStaticModuleA subDynamicModuleB
INCLUDES += -I./ -IsubStaticModuleA -IsubDynamicModuleB
SUBS = subStaticModuleA/subModuleA.a
LIBS += -L./ -lsubModuleB
all: sub_target $(TARGET)
$(TARGET): $(OBJS) $(SUBS)
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(SUBS) $(LIBS)
.PHONY: sub_target
sub_target:
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif
.PHONY: clean
clean:
$(DEL) *.o *.a *.exe *.dll *.so *.out
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif
include ../makefile_config
TARGET = subModuleA.a
SRCS = subA1.cpp subA2.cpp
INCLUDES = -I./
SUBS =
SUBDIRS =
all: sub_target $(TARGET)
$(TARGET): $(OBJS) $(SUBS)
$(AR) r $@ $(OBJS) $(SUBS)
.PHONY: sub_target
sub_target:
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif
.PHONY: clean
clean:
$(DEL) *.o *.a *.exe *.dll
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif
include ../makefile_config
TARGET = ../libsubModuleB.so
SRCS = subB1.cpp subB2.cpp
INCLUDES = -I./
SUBS =
SUBDIRS =
all: sub_target $(TARGET)
$(TARGET): $(OBJS) $(SUBS)
$(CC) $(LDFLAGS) -shared -fPIC -o $@ $(OBJS) $(SUBS)
.PHONY: sub_target
sub_target:
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) && cd ../ &&) cd ./
endif
.PHONY: clean
clean:
$(DEL) *.o *.a *.exe *.dll
ifdef SUBDIRS
$(foreach subdir,$(SUBDIRS),cd $(subdir) && $(MAKE) clean && cd ../ &&) cd ./
endif