みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

PlatformIO nRF52840-DKをmbedで動かす

はじめに

nRF52840-DK(pca10056)をPlatoformIOで動かそうとしたところ、 デフォルトのframework(arduino)ではGPIOのピン割り当てが思ったようにならなかった。

そこで、frameworkをmbedに設定して試してみた。

プロジェクト作成

まずはプロジェクトを作成する。

$ pio init -b nrf52840_dk

platformio.iniを次の内容に修正する。

[env:nrf52840_dk]
platform = nordicnrf52
board = nrf52840_dk
framework = mbed

このように一度platformio.iniを出力してからframeworkを書き換える方法もあるが、下記のようにオプションを与えることで出力時点でframeworkを設定することもできる。

$ pio init -b nrf52840_dk -O "framework=mbed" 

objects_cryptocell.hでエラー

nRF52840-DKのmbed環境では、デフォルトの設定だと、ビルド時に次のようなエラーが発生する。

/home/mickey/.platformio/packages/framework-mbed/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/objects.h:58:10: fatal error: objects_cryptocell.h: No such file or directory

****************************************************************************
* Looking for objects_cryptocell.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:objects_cryptocell.h"
* Web  > https://platformio.org/lib/search?query=header:objects_cryptocell.h
*
****************************************************************************

 #include "objects_cryptocell.h"
          ^~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

platform-nordicnrf52のissue(Missing objects_cryptocell.h)によるとbuild_flags = -DPIO_FRAMEWORK_MBED_RTOS_PRESENTによって回避できるとのこと。

次のようにすることで、プロジェクト作成時に設定することができる。

$ pio init -b nrf52840_dk -O "framework=mbed" -O"build_flags = -DPIO_FRAMEWORK_MBED_RTOS_PRESENT"

Lチカ

src/main.cppを次の内容で作成する。

#include "mbed.h"
 
DigitalOut myled(LED1);
 
int main() {
    while(1) {
        myled = 1;
        wait(0.25);
        myled = 0;
        wait(0.25);
    }
}

次のコマンドでビルドし、ボードへ書き込む。

$ pio run -t upload

LEDがチカチカした。

まとめ

nRF52840-DKのmbed環境ではbuild_flags = -DPIO_FRAMEWORK_MBED_RTOS_PRESENTが必要。

Ubuntu 18.04にaptでLLVM-9をインストール

はじめに

LLVM 9環境をUbuntu 18.04で構築する。

google先生に聞いても手動で設定する方法ばかり見つかったので、aptでインストールする方法を調べた。

2020/1/8 修正

リポジトリの設定

$ sudo apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main"

パッケージのインストール

$ sudo apt install clang-9 clang-tools-9 clang-tidy-9 clang-format-9 lldb-9 lld-9 

シンボリックリンクの作成

Ubuntuでは複数のバージョンがインストールされている場合に、どのバージョンをアクティブにするかを管理するためのalternativesパッケージが存在する。

追加リポジトリでインストールしたclang-9はalternativesには対応していないようで、 /usr/bin/clangの実行時にclang-9を使用したい場合、シンボリックリンクを手動で切り替える必要がある。

$ sudo ln -s -f /usr/bin/clang-9 /usr/bin/clang
$ sudo ln -s -f /usr/bin/clang++-9 /usr/bin/clang++

素敵なスクリプトがあったので、これを使用する。

これを保存し、実行権限をつけて実行する。

$ wget https://gist.githubusercontent.com/junkdog/70231d6953592cd6f27def59fe19e50d/raw/92f0e73d2558402b7316021c1ab408b30e534de6/update-alternatives-clang.sh
$ chmod +x update-alternatives-clang.sh
$ sudo ./update-alternatives-clang.sh 9 50

その後、update-alternativesclangllvm-configを設定する。

clang

$ update-alternatives --config clang
alternative clang (/usr/bin/clang を提供) には 2 個の選択肢があります。

  選択肢    パス            優先度  状態
------------------------------------------------------------
  0            /usr/bin/clang-9   50        自動モード
  1            /usr/bin/clang-8   50        手動モード
* 2            /usr/bin/clang-9   50        手動モード

現在の選択 [*] を保持するには <Enter>、さもなければ選択肢の番号のキーを押してください: 2

llvm-config

$ update-alternatives --config llvm-config
alternative llvm-config (/usr/bin/llvm-config を提供) には 2 個の選択肢があります。

  選択肢    パス                  優先度  状態
------------------------------------------------------------
  0            /usr/bin/llvm-config-8   200       自動モード
  1            /usr/bin/llvm-config-8   200       手動モード
* 2            /usr/bin/llvm-config-9   50        手動モード

現在の選択 [*] を保持するには <Enter>、さもなければ選択肢の番号のキーを押してください: 2

まとめ

http://apt.llvm.org/bionicにはllvm-toolchain-bionic-9が存在するので、 リポジトリの追加とaptコマンドでインストール可能。

alterntivesの自動設定はされないので、必要に応じてシンボリックリンクリンクを貼り替える必要がある。 素敵なスクリプトのお世話になる。

NuttXをnRF52840-DKで動かす

はじめに

NuttXがnRF52840-DKに対応したっぽいので動かしてみる。

ARMのツールチェインの設定方法についてはここを参照。

J-Linkツールの設定方法についてはここを参照。

NuttXの作成

ソースの取得

$ git clone https://bitbucket.org/nuttx/nuttx.git
$ git clone https://bitbucket.org/nuttx/apps.git

コンフィグレーション

USB経由でシェルを使用する場合は次のようにする。

$ cd nuttx
$ tools/configure.sh -l nrf52840-dk:nsh

ビルド

次のコマンドでビルドする。

$ make -j $(nproc)

書き込み

upload.jlink

nuttxディレクトリ直下にupload.jlinkを次の内容で作成する。

h
loadbin nuttx.bin,0x0
r
q

書き込み

$ JLinkExe -device nRF52840_xxAA -speed 4000 -if swd -autoconnect 1 -CommanderScript upload.jlink 

実行

nRF52840-DKはボードとPCをUSBケーブルで接続すると、Linux上でシリアルコンソールが/dev/ttyACM0として見えるようになる。 これをminicomなどで開くことでnshというシェルが使用できる。

$ minicom -D /dev/ttyACM0
NuttShell (NSH) NuttX-8.2
nsh>

helpコマンドを実行すると、実行可能なコマンドを表示することができる。

nsh> help
help usage:  help [-v] [<cmd>]

  [         cd        echo      hexdump   mkfatfs   mw        sh        uname
  ?         cp        exec      kill      mkrd      pwd       sleep     umount
  basename  cmp       exit      ls        mh        rm        test      unset
  break     dirname   false     mb        mount     rmdir     time      usleep
  cat       dd        help      mkdir     mv        set       true      xd
nsh>

Hello worldサンプルの有効化

nRF52840-DKのNuttXのコンフィグレーションは最低限の設定しか有効化されていないので、 Hello worldのサンプルを有効化してみる。

コンフィグレーションを変更するには下記のコマンドを実行し、メニュー画面でコンフィグレーションを設定する。

$ make menuconfig

Hello worldのサンプルを有効化するには下記のコンフィグレーションを有効化する必要がある。

  • CONFIG_BUILTIN
  • CONFIG_EXAMPLES_HELLO
  • CONFIG_NSH_BUILTIN_APPS

メニュー画面で「/」を押すことでコンフィグレーション項目を検索することができる。 また、検索結果の左側にある「(1)」のような番号の数字を入力すると、そのメニューへジャンプできる。

コンフィグレーションを変更したら、リビルドしてイメージを書き込んで再度nshを開く。

nsh> help
help usage:  help [-v] [<cmd>]

  [         cd        echo      hexdump   mkfatfs   mw        sh        uname
  ?         cp        exec      kill      mkrd      pwd       sleep     umount
  basename  cmp       exit      ls        mh        rm        test      unset
  break     dirname   false     mb        mount     rmdir     time      usleep
  cat       dd        help      mkdir     mv        set       true      xd

Builtin Apps:
  hello  nsh

helpコマンドの結果にBuiltin Apps:としてhelloが追加されていればOK。

nsh> hello
Hello, World!!

世界に向かってご挨拶。

まとめ

しばらく見なかった間にtools/configure/shのコンフィグ指定が変わっていた。(nrf52840-dk/nsh -> nrf52840-dk:nshなど)

NuttXのビルド自体は問題なくできたが、ビルトインアプリケーションとしてHello worldのサンプルをイメージに組み込む際、CONFIG_BUILTINCONFIG_NSH_BUILTIN_APPSを一緒に有効化する必要があることがわからず、 しばらくハマった。

以前使用した時のターゲットであるSTM32F4Discovery向けの設定では、デフォルトでここらへんが有効になっていたので気づかなかった。

Linux経験者がこの手のボードで何か作りたい場合は、NuttXは使いやすいと思う。

使用したターゲットはSTM32F4Discoveryだけど、以前にこんなのも書いたので、NuttXに興味が出た方は是非!!(宣伝が露骨)

Longan nanoをUbuntu 18.04のPlatformIOでハロワ

はじめに

Lチカが動いたのでシリアルでHello worldしてみる。

このハロワは失業と関係ない

今時の人たちはHello worldを「ハロワ」と略すらしい。

当時在籍した会社が潰れてハローワークにお世話になった経験がある筆者は、このワードからは悲壮感しか無い。

プロジェクトの作成

戯言はともかく、プロジェクトを作成する。

$ mkdir -p ~/pio/longan_hello
$ cd ~/pio/longan_hello
$ pio init -b sipeed-longan-nano

USART0のピン

Longan nanoで一番簡単に使用できそうなシリアルポートはUSART0で、 これらは基板に引き出されている。

f:id:mickey_happygolucky:20191221065223p:plain
基板

Type-Cポートを左にした時に右側に見える「T0」と「R0」のシルクがあるピンがそれぞれ次のようになっている。

pin 機能
T0 PA9/USART0_TX
R0 PA10/USART0_RX

今回は出力だけなのでT0のみ使用する。

プログラムの作成

platformio.ini

Lチカではframeworkをarduinoとしたが、今回はデフォルトのgd32vf103-sdkを使用する。

ファームウェアの書き込みにDFUを使用するので、最終的には次のようになる。

[env:sipeed-longan-nano]
platform = gd32v
board = sipeed-longan-nano
framework = gd32vf103-sdk

upload_protocol = dfu

シリアル送信までの流れ

まず、Longan nanoのUSART0を使用できるようにする必要がある。

それには次のような処理が必要となる。

  1. GPIOAのクロック有効化
  2. USART0のクロック有効化
  3. PA9ピンのAlternative Function設定
  4. USART0の有効化

初期化処理は次のようにする。

    // 1. GPIOAのクロック有効化
    rcu_periph_clock_enable(RCU_GPIOA);

    // 2. USART0のクロック有効化
    rcu_periph_clock_enable(RCU_USART0);

    // 3. PA9の設定
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

    // 4. USART0の有効化
    usart_enable(USART0);

その後USART0の設定を行い、送信シーケンスに入る。

USARTの送信シーケンス

マニュアルからUSARTの送信シーケンスを抜粋する。

f:id:mickey_happygolucky:20191223065413p:plain
送信シーケンス

送信関数として実装するのはこの7〜10まで。

要約すると、次のようになる。

  1. TBEを待つ
  2. USART_DATAレジスタに送信データを書く
  3. データ数分1と2を繰り返す
  4. TC=1を待つ

図にはTCはcleared by softwareとあるので、明示的にクリアする必要があるかもしれない。

それらを踏まえて次のようにする。

static void putc(unsigned char c) {
    // 1. TBEを待つ
    while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET) 
        ;

    // 2. USART_DATAレジスタにデータを書く
    usart_data_transmit(USART0, c);
}

static void puts(const char* s)
{
    // 3. 送信データ数分1と2を繰り返す
    while (*s != '\0') {
        if (*s == '\n')
            putc('\r');
        putc(*s++);
    }

    // 4. TC=1を待つ
    while (usart_flag_get(USART0, USART_FLAG_TC) == RESET) 
        ;

    // おまけ TCをクリアする。
    usart_flag_clear(USART0, USART_FLAG_TC);
}

サンプルコードではTBE待ちとDATA書き込みが逆になっているが、とりあえずマニュアルにならった。

main.c

これらを踏まえて最終的なプログラムは次のようになる。

#include "gd32vf103.h"

static void putc(unsigned char c) {
    while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET) 
        ;
    usart_data_transmit(USART0, c);
}

static void puts(const char* s)
{
    while (*s != '\0') {
        if (*s == '\n')
            putc('\r');
        putc(*s++);
    }
    while (usart_flag_get(USART0, USART_FLAG_TC) == RESET) 
        ;
    usart_flag_clear(USART0, USART_FLAG_TC);
}

int main(int argc, char* argv[])
{
    rcu_periph_clock_enable(RCU_USART0);
    rcu_periph_clock_enable(RCU_GPIOA);
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

    usart_deinit(USART0);
    usart_enable(USART0);
    usart_baudrate_set(USART0, 115200);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

    puts("Hello ");
    puts("World\n");

    while (1);

    return 0;
}

ビルドと書き込み

Longan nanoをDFUモードでPCと接続し、次のコマンドでファームウェアを書き込む。

$ pio run -t upload

ビルドが必要な場合は、ビルドした後にファームウェアを書き込む。

dfu-util: dfuse_download: libusb_control_transfer returned -1
*** [upload] Error 74

前回同様このエラーは無視する。

実行

USB-シリアル変換ケーブルでLongan nanoとPCを接続し、minicomなど端末を開く。

Longan nanoのリセットボタンを押して、次のように表示されれば成功。

minicom へようこそ 2.7.1

オプション: I18n 
コンパイルされた日時は:  Aug 13 2017, 15:25:34.
ポート /dev/ttyUSB0, 21:32:17

CTRL-@ Z を押すと、説明画面になります。

Hello World

ただ、Longan nanoのピン電圧は3.3Vで、筆者が普段使っている変換ケーブルが5Vのみの対応だったので、 出力が確認できずしばらくハマった。

ロジアナで出力を確認したりオシロで電圧を測ってみたりで原因はわかったが、こういう作業も組み込みの楽しみだと思う。

まとめ

最近ではHello worldのことを「ハロワ」という。

USART0を使う間にGPIOAとUSART0のクロック有効化が必要だが、それを忘れていてしばらくハマった。

ピン設定からUSART0設定は特に癖はない。

USART0のピン電圧は3.3V。

ということで、無事にHello worldができた。

今回は、gd32vf103-sdkを使用したので、レジスタのアドレスなどは特に意識しなくてもプログラムできた。

参考

Longan_GD32VF_examplesのgdv32v_lcd

Longan nanoをUbuntu 18.04のPlatformIOでLチカ

はじめに

安価なRISC-Vボードとして国内でも比較的入手が簡単なLongan nanoを入手した。 開発環境としては当ブログでもおなじみのPlatformIOに対応している。

国内で入手が容易なせいか日本語の情報も多いが、Windows環境での動作報告記事が多いので、ここではUbuntu 18.04上でPlatformIOで動かした事例を紹介する。

PlatformIO

PlatformIOはpipコマンドでインストールするが、環境を汚さないためにuserインストールする。

$ pip install platformio --user

環境変数を通すために~/.bashrcに下記を追加する。

PATH="${HOME}/.local/bin:${PATH}"

端末を開き直すか次のコマンドを実行し、環境変数を読み直す。

$ source ~/.bashrc

whichコマンドで次のようになればOK。

~$ which platformio
/home/xxxxx/.local/bin/platformio

プロジェクトの作成

とりあえず作業用ディレクトリを次のように作成する。

$ mkdir -p ~/pio/longan
$ cd ~/pio/longan

PlatformIOでLongan nanoのボード名を確認する。

$ pio boards | grep longan
sipeed-longan-nano  GD32VF103CBT6  108MHz       128KB    32KB   Sipeed Longan Nano

次にlongan向けのプロジェクトを作成する。

$ pio init -b sipeed-longan-nano
$ ls
include  lib  platformio.ini  src  test

これでプロジェクトの作成は完了

プログラムの作成

main.cpp

src/main.cppを次の内容で作成する。

#include <Arduino.h>

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

一般的なArduinoのLチカのコード。

platformio.ini

Arduinoのライブラリを使用する場合、frameworkをarduinoに設定する必要がある。

また、ファームウェアの書き込みにdfuを使用するので、platformio.iniを次の内容に編集する。

[env:sipeed-longan-nano]
platform = gd32v
board = sipeed-longan-nano
framework = arduino
upload_protocol = dfu

udevルールの編集

DFUを使用してファームウェアを書き込むには、Longan nanoをDFUモードで接続する必要がある。

Ubuntu 18.04というか、LinuxマシンにLongan nanoをDFUモードで接続するためにはまずudevルールを追加する。

/etc/udev/rules.d/99-platformio-udev.rulesに次の内容を追加する。

# Longan Nano
ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", MODE="0666" 

次のコマンドでudevルールを読み込ませる。

$ sudo udevadm trigger

Longan nanoをDFUで接続

Longan nanoをDFUモードで接続するには、BOOT0ボタンを押しながらUSBケーブルを接続する。

基板をケースに入れていない場合はシルクで確認できるが、基板のType-Cコネクタを左にした時に下側にあるボタンがBOOT0ボタンとなる。 反対側のボタンはリセット。

f:id:mickey_happygolucky:20191221065223p:plain
基板

DFUモードで接続できた場合、基板のBOOT0側の赤いLEDだけが光っている状態になるのでおそらくわかるが、確実に確認したい場合はdmesgコマンドを使用し、次のメッセージが表示されていることを確認する。

[26877.865188] usb 1-2: new full-speed USB device number 8 using xhci_hcd
[26878.201184] usb 1-2: device descriptor read/64, error -71
[26878.459003] usb 1-2: New USB device found, idVendor=28e9, idProduct=0189, bcdDevice=10.00
[26878.459008] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[26878.459012] usb 1-2: Product: GD32 0x418 DFU Bootloade
[26878.459015] usb 1-2: Manufacturer: GDMicroelectronics
[26878.459017] usb 1-2: SerialNumber: 䌳䩂

これでファームウェアの書き込みが可能となる。

ビルドと書き込み

次のコマンドでファームウェアをビルドする。

$ pio run

次のコマンドで書き込む。

$ pio run -t upload

次のように「dfu-util: dfuse_download: libusb_control_transfer returned -1」というエラーが表示される。

Opening DFU capable USB device...
ID 28e9:0189
Run-time device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuERROR, status = 10
dfuERROR, clearing status
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 011a
Device returned transfer size 2048
GD32 flash memory access detected
Device model: GD32VF103CB
Memory segment (0x08000000 - 0801ffff)(rew)
Erase size 1024, page count 128
Downloading to address = 0x08000000, size = 8268

Download    [                         ]   0%            0 bytes
Download    [                         ]   0%            0 bytes
Download    [======                   ]  24%         2048 bytes
Download    [============             ]  49%         4096 bytes
Download    [==================       ]  74%         6144 bytes
Download    [======================== ]  99%         8192 bytes
Download    [=========================] 100%         8268 bytes
Download done.
File downloaded successfully
dfu-util: dfuse_download: libusb_control_transfer returned -1
*** [upload] Error 74
================== [FAILED] Took 2.52 seconds ==================

ここによれば、

That message is unrelated. I recall reading that this was a failure (or perhaps a permissions problem) for a post-transfer operation, I think related to a post-transfer reset, but I forget where I read it. It doesn’t affect the actual flashing of the firmware, so far as I can tell.

とのことで書き込み結果には関係ないらしい。

まとめ

Longan nanoの開発環境をUbuntu 18.04上に構築し、無事に動かすことができた。

PlatformIOなので基本的にはWindowsmacOSでもほぼ変わらない手順で動くとは思うが、デバイスを認識させる手順などがおそらく異なる。

面白そうな基板なのでもう少し遊びたい。

Yocto Project 3.0(Zeus)でラズベリーパイ4を動かす

はじめに

技適が通り、やっと国内で流通し始めたラズベリーパイ4が手に入ったので、Yocto Projectで動かしてみる。 発熱がすごいらしいのでファン付きのケースも同時に購入した。

構築手順

ソース取得

依存関係がいつの間にか改善されている。最小限の構成であればmeta-openembeddedは不要となったようだ。

$ mkdir -p rpi-zeus/layers
$ cd rpi-zeus/layers
$ git clone git://git.yoctoproject.org/poky.git -b zeus
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b zeus
$ cd ../

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

レイヤ追加

こちらもmeta-raspberrypiのみの追加で行ける。

$ bitbake-layers add-layer ../layers/meta-raspberrypi

local.confの修正

MACHINEをraspberrypi4に設定し、UARTを有効化する。

MACHINE = "raspberrypi4"
DL_DIR ?= "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# connman
IMAGE_INSTALL_append = " connman \
                 connman-client \
"

ビルド

core-image-baseをビルドする。

$ bitbake core-image-base

書き込み

ddやEtcherで書き込む

ddの場合は次のようにする。

$ sudo dd if=tmp/deploy/images/raspberrypi3/core-image-base-raspberrypi3.rpi-sdimg of=/dev/sdX bs=100M 

/dev/sdXはSDのデバイスに適宜読み替え。

実行

zeusが起動した。

# cat /etc/os-release
ID="poky"
NAME="Poky (Yocto Project Reference Distro)"
VERSION="3.0.1 (zeus)"
VERSION_ID="3.0.1"
PRETTY_NAME="Poky (Yocto Project Reference Distro) 3.0.1 (zeus)"

メモリもきちんと4G見えているようだ。

# free
              total        used        free      shared  buff/cache   available
Mem:        3999780       86908     3871760        8796       41112     3849068
Swap:             0           0           0

きちんと動かしてないけど、USB3.0バイスも認識している様子。

~# lsusb
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 0480:0200 Toshiba America Inc External Disk
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

# lsusb -t
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=dwc_otg/1p, 480M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
    |__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M

USB3.0のHDDを接続したらきちんとBus 02の下にHDDが入った。

まとめ

大体問題なく動作しているようだ。

ベアメタルのHiFIve1 Rev.BでHello world

はじめに

前回ベアメタルでLチカは動いたので、今度はシリアルを動かす。

ホストPCの環境はUbuntu 18.04

UARTのポート

HiFive1 Rev.BをPCに接続すると/dev/ttyACM0/dev/ttyACM1が見えるようになる。 この内/dev/ttyACM0デバッグコンソールとして使える。

f:id:mickey_happygolucky:20191114074445p:plain

この図のSegger J-Link OBのUART 0FE310のUART0に接続されている。そのためFE310のUART0の内容がttyACM0でも見ることができる。

筆者環境ではSegger J-Link OBを実装しているMK22Fは意外と調子が悪いので、UARTのボーレートなどのパラメータが正しくてもminicomにゴミしか出ないことが多い。

なので、MK22Fに入るまでのUART0が直接見れるJ2コネクタのD0/D1にUSB-シリアル変換ケーブルを接続するほうが確実性は高い。

UART1

意外とわかりづらいのがFE310のUART1。

Segger J-Link OBのUARTには接続されていないのでttyACM1とは無関係。

f:id:mickey_happygolucky:20191114074453p:plain

GPIO_18がUART1_RX、GPIO_23がUART1_TXに割り当てられている。

回路図によるとArduino互換ヘッダのD2(DIG2)がGPIO_18(UART1_RX)、D7(DIG7)がGPIO_23(UART1_TX)に割り当たっている。

f:id:mickey_happygolucky:20191114074502p:plain

だがしかし。筆者の手元のボードではUART1に出力するとD2から波形が出る。

マニュアルなのか配線なのかよくわからないが、とりあえずTXとRXは逆転していそう。 今回はHello worldということでTXしか使用しないので実際のRXがどうなっているかは未確認。

リンカスクリプト

linker.ldは変更なし。

アセンブリ

utils.Sレジスタを読み出すためのget32を追加。

    .global start
start:
    lui sp, 0x80004
    jal main
    j .
    
    .global dummy
dummy:
    ret

    .global put32
put32:
    sw x11,(x10)
    ret

    .global get32
get32:
    lw x10, (x10)
    ret

プログラム

プログラムはすべてmain.cに記述。

定義部

マクロとグローバル変数の定義

void put32(unsigned int, unsigned int);
unsigned int get32(unsigned int);

#define GPIOBASE         0x10012000
#define GPIO_OUTPUT_EN     (GPIOBASE+0x08)
#define GPIO_PORT      (GPIOBASE+0x0c)
#define GPIO_IOF_EN        (GPIOBASE+0x38)
#define GPIO_IOF_SEL   (GPIOBASE+0x3C)

#define IOF_UART0_RX   (1<<16)
#define IOF_UART0_TX   (1<<17)
#define IOF_UART1_RX   (1<<18)
#define IOF_UART1_TX   (1<<23)

#define UART0BASE      0x10013000
#define UART1BASE      0x10023000

#define UART_TXDATA        (0x00)
#define UART_TXCTRL        (0x08)
#define UART_DIV       (0x18)

#define TXCTRL_TXEN        (1)
#define TXCTRL_NSTOP   (1<<1)

#define HFCLK          (16000000) //HF clock is 16MHz
#define LFCLK          (32768)    //LF clock is 32KHz

unsigned int uart = 0;

UART初期化

後からUART0/1両対応にしたので不細工。

int setup_uart(int port, int baudrate)
{
    unsigned int iof;
    switch (port) {
    case 0:
        uart = UART0BASE;
        iof = IOF_UART0_RX|IOF_UART0_TX;
        break;
    case 1:
        uart = UART1BASE;
        iof = IOF_UART1_RX|IOF_UART1_TX;
        break;
    default:
        return -1;
    }

    unsigned int ra;
    ra = get32(GPIO_IOF_SEL);
    ra &= ~iof;
    put32(GPIO_IOF_SEL, ra);

    ra = get32(GPIO_IOF_EN);
    ra |= iof;
    put32(GPIO_IOF_EN, ra);

    unsigned div = HFCLK/baudrate-1;
    put32(uart+UART_DIV, div);

    put32(uart+UART_TXCTRL, TXCTRL_NSTOP|TXCTRL_TXEN);
    return 0;
}

シリアル出力

TX queueがFullかであれば待ってTXDATAへ書き込み。

void putc(char c)
{
    /* wait if queue is full. */
    while (1) {
        if ((get32(uart+UART_TXDATA) & 0x80000000) == 0)
            break;
    }
    put32(uart+UART_TXDATA, c);
}

void puts(const char* s)
{
    do {
        putc(*s++);
    } while (*s != '\0');
}

1文字ずつputcを呼ぶのはだるいので文字列対応。

こんにちわ

int main(int argc, char *argv[])
{
    setup_uart(0, 115200);
    puts("hello world !!\r\n");
    return 0;
}

Makefile

Makefileは少し改良した

PROGRAM=hello
RISCVGNU = riscv32-unknown-elf

AOPS = -march=rv32imac
COPS = -march=rv32imac -Wall -O0 -g  -nostdlib -nostartfiles -ffreestanding 
#COPS = -march=rv32imac -Wall -O2  -nostdlib -nostartfiles -ffreestanding

all : $(PROGRAM).hex

clean :
    rm -f *.o
    rm -f *.elf
    rm -f *.hex
    rm -f *.list
    rm -f *~

utils.o : utils.S
    $(RISCVGNU)-as $(AOPS) utils.S -o utils.o

main.o : main.c
    $(RISCVGNU)-gcc $(COPS) -c main.c -o main.o

$(PROGRAM).hex : linker.ld utils.o main.o 
    $(RISCVGNU)-ld utils.o main.o -T linker.ld -o $(PROGRAM).elf
    $(RISCVGNU)-objdump -D $(PROGRAM).elf > $(PROGRAM).list
    $(RISCVGNU)-objcopy $(PROGRAM).elf -O ihex $(PROGRAM).hex

flash : $(PROGRAM).hex
    JLinkExe -device FE310 -speed 1000 -if JTAG -jtagconf -1,-1 -autoconnect 1 -CommanderScript "upload.jlink"

gdb : $(PROGRAM).hex
    JLinkGDBServer -device FE310 -endian little -speed 1000 &
    riscv32-unknown-elf-gdb $(PROGRAM).elf

プログラム名の変数かとgdbの追加。

これによってmake gdbデバッグを開始できる。

J-Link OBの調子によってgdbの開始に失敗することがある。

実行

make flashで書き込み。

minicomでUART0を開いておくと、次のように表示される。

Bench Clock Reset Complete

ATE0-->ATE0
OK
AT+BLEINIT=0-->OK
AT+CWMODE=0-->OK

hello world !!

hello world !!の前にブートローダによっていくつか表示がある。

main関数で呼び出しているsetup_uartの第1引数を1にするとUART1に変更することができる。

まとめ

UARTを使用してHello worldを作成した。

ttyACM0はUART0の入出力をUSBケーブルで見れるようになるが、若干不安定。

UART1はUSBのttyACM1とは無関係。J2のD2/D7でアクセス可能だが、TX/RXが逆転しているように見える。

HiFive1 Rev.Bはまだハードがこなれていない感じ。

参照

図は下記からの抜粋