Category tech.

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 にバッテリー表示をつける

STM32 には出荷時点でブートローダーが入っていて、様々な方法ですぐ書きこめるようになっている。ブートローダーは、書き換えできない「システムメモリ」と呼ばれる領域に入っている。

ユーザーコードからでも、このシステムメモリにジャンプすればブートローダーのモードに入れる。入れるのだけど、このブートローダーに入る方法について AN2606 にはこう書かれている

In addition to patterns described above, user can execute bootloader by performing a jump to system memory from user code. Before jumping to bootloader user must:
• Disable all peripheral clocks
• Disable used PLL
• Disable interrupts
• Clear pending interrupts
System memory boot mode can be exited by getting out from bootloader activation condition and generating hardware reset or using Go command to execute user code.

https://www.st.com/content/ccc/resource/technical/document/application_note/b9/9b/16/3a/12/1e/40/0c/CD00167594.pdf/files/CD00167594.pdf/jcr:content/translations/en.CD00167594.pdf

つまりいろんなものを初期状態に戻さなければいけない。これは、まぁめんどうくさい……

同様の事例をググってみると、マジックコードをメモリに書きこんで (普通のグローバル変数でも良いし、メモリを節約したいなら、どうせリセットするのだし適当なアドレスに書きこんでいい)、システムリセットを起こし、(メモリは初期化されないので) マジックコードを検出して、リセット直後のあらゆるペリフェラルが初期化される前にブートローダ(システムメモリ)へジャンプをかけるという方法がひっかかる。

リセット直後にジャンプするほうがあきらかに楽。

ChibiOS では

board.c の __early_init() というのがスタック初期化直後に ChibiOS のブートストラップコードのアセンブリから呼ばれてくるので、ここにジャンプコードを実装してやる。

以下のようになった。いろいろ試したあげく、結局ほぼstackoverflowの内容と同じだけど、どうしてもうまくいかなかったため調べていたら、どうも __enable_irq() が必要ということがわかった。

STM32F072xB_SYSTEM_MEMORY の位置には初期スタックポインタのアドレスが入っているはず、、なんだけどリセットからくると(?)ダメみたいなので、定数値を入れている。STM32F072xB_SYSTEM_MEMORY+4 が実際のジャンプ先。

// board.c
void __early_init(void) {
  if ( *((unsigned long *)BOOT_FROM_SYTEM_MEMORY_MAGIC_ADDRESS) == BOOT_FROM_SYTEM_MEMORY_MAGIC ) {
    // require irq
    __enable_irq();
    *((unsigned long *)BOOT_FROM_SYTEM_MEMORY_MAGIC_ADDRESS) = 0;
    // remap memory. unneeded for F072?
    // RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
    // SYSCFG->CFGR1 = 0x01;
    __set_MSP(SYSTEM_BOOT_MSP); 
    ( (void (*)(void)) (*((uint32_t *)(STM32F072xB_SYSTEM_MEMORY+4))) )();
  }

  //si5351_setup();
  stm32_clock_init();
}

定数はこのようになっている。STM32F072xB_SYSTEM_MEMORY は型番によって違うので調べる必要がある。リファレンスマニュアルに書いてある。

// board.h
#define STM32F072xB_SYSTEM_MEMORY 0x1FFFC800
#define BOOT_FROM_SYTEM_MEMORY_MAGIC_ADDRESS 0x20003FF0
#define BOOT_FROM_SYTEM_MEMORY_MAGIC 0xDEADBEEF
#define SYSTEM_BOOT_MSP 0x20002250

実際のジャンプするコードはこう。メモリにフラグを書いてリセットしているだけ。

  *((unsigned long *)BOOT_FROM_SYTEM_MEMORY_MAGIC_ADDRESS) = BOOT_FROM_SYTEM_MEMORY_MAGIC;
  NVIC_SystemReset();

ref

  1. トップ
  2. tech
  3. STM32F072 で、ユーザーコードから DFU モードに入る。