Category tech.

しばらく放っておいてどうセットアップしたか忘れたので、https://www.armbian.com/nanopi-neo-2/ armbian を改めて入れてみる。

FriendlyARM のイメージも結構更新されていそうだけど、どっちがいいか不明。しかし FriendlyARM のイメージはいまいちデキが良い感じがしなかったので、armbian Bionic server (Ubuntu系) を入れてみる。

SDカード書きこみ

Etcher で焼くだけ

初回起動

ping 192.168.0.255 とかして arp -a して 02:01:a9 からはじまる Mac アドレスがそれ。

https://docs.armbian.com によると、デフォルトで ssh が起動しており、root 、 1234 で入れるらしい。

ssh root@192.168.0.xx

すると、初回設定としてパスワード変更とユーザ追加をさせられる。やったら一回 logout して、作ったユーザーでログインしなおす。

前後して

 ssh-copy-id  cho45@192.168.0.17

とかやっておく。

初回設定

sudo armbian-config

して Personal -> Timezone の設定と、 System -> Hardware -> i2c0 にチェックを入れて再起動。

NanoHat OLED

i2c

検出できるか確認しとく。

$ sudo apt install i2c-tools
$ sudo i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

i2c グループに自分を追加しておく

$ sudo usermod -a -G i2c
# 一回ログアウトすること
$ i2cdetect -y 0

gpio

armbian もデフォルトでは root にしか gpio へのアクセス権限がなく、gpio グループなども設定されていない。ので自分でやる必要がある.

sudo groupadd gpio
sudo usermod -aG gpio cho45
$ sudo vim /etc/udev/rules.d/80-gpio.rules
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chown -R root:gpio /sys/devices/platform/soc/*pinctrl/gpio*'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chmod -R ug+rw /sys/devices/platform/soc/*pinctrl/gpio*'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chown -R root:gpio /sys/class/gpio'"
SUBSYSTEM=="gpio", PROGRAM="/bin/sh -c '/bin/chmod -R ug+rw /sys/class/gpio'"
sudo udevadm control --reload-rules
sudo udevadm test --action=change /sys/devices/platform/soc/1c20800.pinctrl/gpiochip1/gpio/gpio0
$ ls -altr /sys/class/gpio/
total 0
-rw-rw----  1 root gpio 4096 Sep 15 15:45 export
drwxrwxr-x  2 root gpio    0 Sep 15 15:45 .
drwxr-xr-x 51 root root    0 Sep 15 15:45 ..
lrwxrwxrwx  1 root gpio    0 Sep 15 15:45 gpio0 -> ../../devices/platform/soc/1c20800.pinctrl/gpiochip1/gpio/gpio0
-rw-rw----  1 root gpio 4096 Sep 15 15:45 unexport
lrwxrwxrwx  1 root gpio    0 Sep 15 15:45 gpio3 -> ../../devices/platform/soc/1c20800.pinctrl/gpiochip1/gpio/gpio3
lrwxrwxrwx  1 root gpio    0 Sep 15 15:45 gpiochip352 -> ../../devices/platform/soc/1f02c00.pinctrl/gpio/gpiochip352
lrwxrwxrwx  1 root gpio    0 Sep 15 15:45 gpiochip0 -> ../../devices/platform/soc/1c20800.pinctrl/gpio/gpiochip0
lrwxrwxrwx  1 root gpio    0 Sep 15 15:45 gpio2 -> ../../devices/platform/soc/1c20800.pinctrl/gpiochip1/gpio/gpio2

node

nodejs をいれる。apt でいれると気持ちわるいことになるので公式パッケージをいれるほうがよい。

$ cd ~/
$ mkdir app
$ cd app
$ wget https://nodejs.org/dist/v10.16.3/node-v10.16.3-linux-arm64.tar.xz
$ tar xvf node-v10.16.3-linux-arm64.tar.xz
$ ln -s  node-v10.16.3-linux-arm64 node
$ cd ~/bin
$ ln -s ~/node/bin/* .
$ sudo apt install pkg-config libcairo2-dev
$ git clone git@github.com:cho45/nanohat-oled-nodejs.git
$ cd nanohat-oled-nodejs
$ npm install

動作確認

$ node index.js

systemd 化

$ ./install.sh

備考: ssh ログイン時のメッセージ

$ ls  /etc/update-motd.d

にある。/etc/update-motd.d/30-armbian-sysinfo が特に有用

  1. トップ
  2. tech
  3. NanoPi NEO2 + NanoHAT OLED に armbian

PCB上にピンヘッダ(未実装)がある。

  • SWDIO
  • SWCLK
  • GND
  • NRST
  • VCC (使わない)

SWD (Serial Wire Debug) 用のもの。ここに ST-Link を繋ぐ。ST-Link といっても中華 ST-Link だけど、こういう感じになる。

VCC 以外を接続する。VCCは普通に電源をオンにして供給する。

事前条件

普通に make して build できる環境にしておく。arm-none-eabi-gcc が入っていればよい。

open-ocd は brew で入るデフォルトではなく、head を入れる必要がある。なぜか texinfo が要求されて死んだので前もって入れたほうがよさそう。

brew install texinfo
brew install open-ocd --HEAD

VSCode

普段は vim を使っているが、CUI デバッガは個人的にはつらいので、こういうときは VSCode を使う。

VSCode を入れたのち Cortex-Debug extension を入れて使う。VSCode を開いて、Extensions から検索して Install するのが最速。

tasks.jsonを書く

make を呼ぶようにしておく

{
    "tasks": [
        {
            "type": "shell",
            "label": "build",
            "command": "make",
            "args": [
            ],
            "options": {
                "cwd": "${workspaceRoot}"
            }
        }
    ],
    "version": "2.0.0"
}

launch.json を書く

stlink を使って stm32f0x をデバッグするので以下のようにする。また、デバッグ前に build するようにする。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "cortex-debug",
            "servertype": "openocd",
            "request": "launch",
            "name": "OpenOCD-Debug",
            "executable": "build/ch.elf",
            "configFiles": [
                "interface/stlink.cfg",
                "target/stm32f0x.cfg"
            ],
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "build",
        }
    ]
}

デバッグ開始する

Debug を開いて Start Debugging (F5) をする。ビルドしたのち、しばらくする (デバイス側にビルドしたファームが転送される) とデバイス側の画面は白くなり、リセットハンドラでブレークするので、適当な場所にブレークポイントを置いて resume する。

svd ファイルを指定する

追加で SVD (System View Description) ファイル (ST のサイトからダウンロードできる を指定しておく。

"svdFile": "./STM32F0x8.svd",

MCU のレジスタがわかりやすく表示される

備考

OpenOCD の cfg の場所

/usr/local/share/openocd/scripts/

にある。結構 deprecated になっているものも置いたままだったりする。stlink.cfg は ST-Link のバージョンに関係なく共通で使えるものになっている。

リソース

ref

  1. トップ
  2. tech
  3. NanoVNA を VSCode + ST-Link + OpenOCD でオンチップデバッグ

回路図の D2 はバッテリーから MCU の VBAT に接続する経路ですが、自分の入手した固体だと未実装でした。せっかくなので、手元にあった適当なダイオードをひとまず半田付けして、ファームウェア側の実装を試してみました。使ったのは汎用小信号用ダイオードの 1N4148 の SMD 版 (SOD-323) ですが、本来はショットキーのほうが良いはずです。

パッチの全体

D2 の実装が必要なことと、中華版のバッテリーが載っているモデルにしか適用できないので (一応 VBAT を見た結果、バッテリが載っていないあるいは D2 がない場合には無視するようにはしてありますが)、オリジナルの master には入れることができないパッチです。自分でパッチを管理する必要があります。

master に入れてもらったので、自分でパッチ管理する必要はなくなりました。ありがたや。D2 を実装したうえで最新ファームを書くだけで有効になります。

adc_vbat_read()

int16_t adc_vbat_read(ADC_TypeDef *adc)
{
#define ADC_FULL_SCALE 3300
#define VBAT_DIODE_VF 500
#define VREFINT_CAL (*((uint16_t*)0x1FFFF7BA))
	float vbat = 0;
	float vrefint = 0;

	ADC->CCR |= ADC_CCR_VREFEN | ADC_CCR_VBATEN;
	// VREFINT == ADC_IN17
	vrefint = adc_single_read(adc, ADC_CHSELR_CHSEL17);
	// VBAT == ADC_IN18
	// VBATEN enables resiter devider circuit. It consume vbat power.
	vbat = adc_single_read(adc, ADC_CHSELR_CHSEL18);
	ADC->CCR &= ~(ADC_CCR_VREFEN | ADC_CCR_VBATEN);

	uint16_t vbat_raw = (ADC_FULL_SCALE * VREFINT_CAL * vbat * 2 / (vrefint * ((1<<12)-1)));
	if (vbat_raw < 100) {
		// maybe D2 is not installed
		return -1;
	}
	
	return vbat_raw + VBAT_DIODE_VF;

}

一応 VREFINT_CAL を使って補正をかけています。面倒なので計算に float 使ってますが、返り値は mV 単位の int16_t です。

ADCサイクル数

既存の adc_single_read() では ADC のサンプリングサイクルが最小になっているのですが、これだと VBAT から内部キャパシタを十分に充電できないようで、ADC で取得できる値がやたら低くく、うまくいきませんでした。ということで ADC_SMPR_SMP_239P5 までサイクル数を増やしました。

どこでバッテリ残量をとるか

そんなに頻繁に残量をとってもしかたないのですが、面倒なので sweep 直後に毎回取得するようにました。

      if (vbat != -1) {
          adc_stop(ADC1);
          vbat = adc_vbat_read(ADC1);
          touch_start_watchdog();
          draw_battery_status();
      }

表示

5x7 のフォントとして残量アイコンを実装しようと最初は考えていましたが、小さすぎるので、自力でモリモリ描くことにしました。

void
draw_battery_status(void)
{
    int w = 10, h = 14;
    int x = 0, y = 0;
    int i, c;
    uint16_t *buf = spi_buffer;
    uint8_t vbati = vbat2bati(vbat);
    uint16_t col = vbati == 0 ? RGB565(0, 255, 0) : RGB565(0, 0, 240);
    memset(spi_buffer, 0, w * h * 2);

    // battery head
    x = 3;
    buf[y * w + x++] = col;
    buf[y * w + x++] = col;
    buf[y * w + x++] = col;
    buf[y * w + x++] = col;

    y++;
    x = 3;
    buf[y * w + x++] = col;
    x++; x++;
    buf[y * w + x++] = col;

    y++;
    x = 1;
    for (i = 0; i < 8; i++)
        buf[y * w + x++] = col;

    for (c = 0; c < 3; c++) {
        y++;
        x = 1;
        buf[y * w + x++] = col;
        x++; x++; x++; x++; x++; x++;
        buf[y * w + x++] = col;

        y++;
        x = 1;
        buf[y * w + x++] = col;
        x++;
        for (i = 0; i < 4; i++)
            buf[y * w + x++] = ( ((c+1) * 25) >= (100 - vbati)) ? col : 0;
        x++;
        buf[y * w + x++] = col;

        y++;
        x = 1;
        buf[y * w + x++] = col;
        x++;
        for (i = 0; i < 4; i++)
            buf[y * w + x++] = ( ((c+1) * 25) >= (100 - vbati)) ? col : 0;
        x++;
        buf[y * w + x++] = col;
    }

    // battery foot
    y++;
    x = 1;
    buf[y * w + x++] = col;
    x++; x++; x++; x++; x++; x++;
    buf[y * w + x++] = col;

    y++;
    x = 1;
    for (i = 0; i < 8; i++)
        buf[y * w + x++] = col;

    ili9341_bulk(0, 1, w, h);
}

vbat2bati は以下のような inline 関数です。閾値がいまいちわからないので、結構雑に設定してます。

// convert vbat [mV] to battery indicator
static inline uint8_t vbat2bati(int16_t vbat)
{
	if (vbat < 3200) return 0;
	if (vbat < 3450) return 25;
	if (vbat < 3700) return 50;
	if (vbat < 3950) return 75;
	return 100;
}
  1. トップ
  2. tech
  3. 中華 NanoVNA にバッテリー表示をつける