1. CHAOSとは?
拡張性に優れて自由に改造できる(ハッカブルな)組込みOSです.フルスクラッチでARM64bitで動作するOSというのが特徴で,現状はRaspberry Pi 3 model bで動作します.またQEMUを使ったエミュレーションも可能です.
CHAOSを使って組込みOSを理解しましょう!
サポートしている機能
- UART (PCと通信)
- GPIO (LEDやモータ,センサ等のInput/Output)
- RNG (乱数生成)
2. 実際に手を動かす
開発環境構築
- 下のリンクからCHAOSをクローンまたはダウンロードしてください.
CHAOSのレポジトリ
- READMEを参照して開発環境を構築してください.
- makeしてビルドに成功すればOKです.
動かしてみよう
- QEMUで動かしてみましょう.
するとQEMUが立ち上がって以下のようなコマンド入力画面が表示されます.
1 2 3 4 5
| tsuru@rz4:~/CHAOS$ make run qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial null -serial stdio
CHAOS started >
|
色々入力してみましょう.
対応コマンドはmain.cに書いてあります.
1 2 3 4 5 6 7 8
| > echo hello hello > on GPIO ON! > off GPIO OFF! > reboot rebooting...
|
入力した文字列はchar型のbuf配列に格納されてstrcmpで対応するコマンドを比較して処理を決めています.
ctrl+c でQEMUを終了できます.
- ラズパイに書き込もう
FAT32でフォーマットされた micro SD をPCに差し込み,ディレクトリ内のファイル群をコピーします.
1 2 3 4
| kernel8.img bootcode.bin config.txt start.elf
|
SDカードスロットに差し込み,ラズパイのGPIO14ピン,15ピン(RX,TX)とGNDをUSBシリアル変換器を使い,USB経由でPCを接続します.その後以下のコマンドでminicomを立ち上げます.
minicomが立ち上がった後にラズパイの電源を入れるとQEMUでエミュレーションした時と同じようにコマンド入力画面が表示されます.
ここでGPIO16ピンとGNDにLEDを接続して以下のコマンドを入力してみましょう.
するとどうでしょうLEDが点灯します!めっちゃ簡単ですね!
消灯する際は
で出来ます.
あとは,乱数を作ったり,再起動したり,シャットダウンできたりします.
1 2 3 4 5 6 7 8 9 10
| CHAOS started > rand 4EBE8815 > rand 523D3704 > reboot rebooting... CHAOS started > shutdown shutdown
|
という感じで自作OS CHAOS の機能を紹介しました.
次はソースコードについて説明します.
- CHAOSのコード読解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| #include "lib.h" #include "uart.h" #include "gpio.h"
void init(void) { uart_init(); }
int putc(unsigned char c) { if (c == '\n') uart_send('\r'); if (c != "") uart_send(c); return 0; }
unsigned char getc(void) { unsigned char c = uart_getc(); c = (c == '\r') ? '\n' : c; putc(c); return c; }
int puts(unsigned char *str) { while (*str) putc(*(str++)); return 0; }
int gets(unsigned char *buf) { int i = 0; unsigned char c; do { c = getc(); if (c == '\n') c = '\0'; buf[i++] = c; } while (c); return i - 1; }
int strcmp(const char *s1, const char *s2) { while (*s1 || *s2) { if (*s1 != *s2) return (*s1 > *s2) ? 1 : -1; s1++; s2++; } return 0; }
int strncmp(const char *s1, const char *s2, int len) { while ((*s1 || *s2) && (len > 0)) { if (*s1 != *s2) return (*s1 > *s2) ? 1 : -1; s1++; s2++; len--; } return 0; }
void rand_init(void) { *RNG_STATUS = 0x40000; *RNG_INT_MASK |= 1; *RNG_CTRL |= 1; while(!((*RNG_STATUS) >> 24)) asm volatile("nop"); }
unsigned int rand(unsigned int min, unsigned int max) { return *RNG_DATA % (max - min) + min; }
void on(unsigned int n) { register unsigned int r; // ON *GPFSEL1 = 0x01 << 18; *GPSET0 = 0x01 << 16;
// UARTできるように切り替える r=*GPFSEL1; r&=~((7<<12)|(7<<15)); // gpio14, gpio15 r|=(2<<12)|(2<<15); // alt5 *GPFSEL1 = r; }
void off(unsigned int n) { register unsigned int r; // OFF *GPFSEL1 = 0x01 << 18; *GPCLR0 = 0x01 << 16;
// UARTできるように切り替える r=*GPFSEL1; r&=~((7<<12)|(7<<15)); // gpio14, gpio15 r|=(2<<12)|(2<<15); // alt5 *GPFSEL1 = r; }
|
main.cで呼び出している関数がlib.cで定義してあります.注目するべきはGPFSEL1とGPSET0と*GPCLR0です.これらはgpio.hで定義されたレジスタです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // RaspberryPi3 Memory Mapped I/O Base Address #define MMIO_BASE 0x3F000000
// GPIO #define GPFSEL0 ((volatile unsigned int*)(MMIO_BASE+0x00200000)) #define GPFSEL1 ((volatile unsigned int*)(MMIO_BASE+0x00200004)) #define GPFSEL2 ((volatile unsigned int*)(MMIO_BASE+0x00200008)) #define GPFSEL3 ((volatile unsigned int*)(MMIO_BASE+0x0020000C)) #define GPFSEL4 ((volatile unsigned int*)(MMIO_BASE+0x00200010)) #define GPFSEL5 ((volatile unsigned int*)(MMIO_BASE+0x00200014)) #define GPSET0 ((volatile unsigned int*)(MMIO_BASE+0x0020001C)) #define GPSET1 ((volatile unsigned int*)(MMIO_BASE+0x00200020)) #define GPCLR0 ((volatile unsigned int*)(MMIO_BASE+0x00200028)) #define GPLEV0 ((volatile unsigned int*)(MMIO_BASE+0x00200034)) #define GPLEV1 ((volatile unsigned int*)(MMIO_BASE+0x00200038)) #define GPEDS0 ((volatile unsigned int*)(MMIO_BASE+0x00200040)) #define GPEDS1 ((volatile unsigned int*)(MMIO_BASE+0x00200044)) #define GPHEN0 ((volatile unsigned int*)(MMIO_BASE+0x00200064)) #define GPHEN1 ((volatile unsigned int*)(MMIO_BASE+0x00200068)) #define GPPUD ((volatile unsigned int*)(MMIO_BASE+0x00200094)) #define GPPUDCLK0 ((volatile unsigned int*)(MMIO_BASE+0x00200098)) #define GPPUDCLK1 ((volatile unsigned int*)(MMIO_BASE+0x0020009C))
// RNG #define RNG_CTRL ((volatile unsigned int*)(MMIO_BASE+0x00104000)) #define RNG_STATUS ((volatile unsigned int*)(MMIO_BASE+0x00104004)) #define RNG_DATA ((volatile unsigned int*)(MMIO_BASE+0x00104008)) #define RNG_INT_MASK ((volatile unsigned int*)(MMIO_BASE+0x00104010))
|
レジスタのアドレスをgpio.hで定義されています.
詳細はラズパイ3に搭載されているBMC2837のデータシートを確認してください.
※実はデータシートのベースアドレスは0x7F000000なんですが現状のラズパイ3では0x3F000000であることに注意してください.
GPFSEL…GPIOの入出力設定するレジスタ
GPSET …対応するGPIOピンをHIGHにする
GPCLR …対応するGPIOピンをLOWにする
ここでlib.cのon()を見てみましょう.
1 2 3 4 5 6 7 8 9 10 11 12
| void on(unsigned int n) { register unsigned int r; // ON *GPFSEL1 = 0x01 << 18; *GPSET0 = 0x01 << 16;
// UARTできるように切り替える r=*GPFSEL1; r&=~((7<<12)|(7<<15)); // gpio14, gpio15 r|=(2<<12)|(2<<15); // alt5 *GPFSEL1 = r; }
|
- *GPFSEL1 = 0x01 << 18; はデータシートP92を参照すると”GPIO16ピンを出力に設定”
- *GPSET0 = 0x01 << 16; はOUTしたいピン番号分(16)左シフトすることで”GPIO16をOUT”
この2つのレジスタの演算だけでラズパイのGPIO操作ができます.
ただ,UARTする際にGPFSEL1を操作するため,UARTできるように書き換えるための演算を次の行から行っています.
3. 終わりに
駆け足になりましたがザックリとCHAOSの概要とGPIOの設定,操作を見ていきました.
レジスタのアドレスを変えたりリンカ・スクリプトを改造することで他のSoCでも動くと思います.ガンガン移植していって楽しむのも通かと思います.
CHAOSはKL-01というライセンスなので自由に改変,再配布可能です.またこんな機能追加したよ!というのがあればどんどんプルリク送ってほしいです!まだまだCHAOSには機能が充分に揃ってないですが,そのぶん改造の幅やいろんな可能性を秘めていると言えます.一緒にCHAOSを盛り上げていきましょう!!