マイクロソフト スカルプト エルゴノミック マウス L6V-00013 : ワイヤレス 快適操作 4方向スクロール 右手用 BlueTrack USB接続 ( ブラック ) Windows Mac Android 対応 - マイクロソフト

マイクロソフト

4.0 / 5.0

SteelSeries ゲーミングマウスパッド ブラック 小型 ノンスリップラバーベース 25cm×21cm×0.2cm QcK mini 63005 - SteelSeries

SteelSeries

5.0 / 5.0

会社のマウスとマウスパッドを買い替えた。前まで SteelSeries QcK (mini じゃないやつ) を使ってたけど、普段全面を使うことはまずないので mini にしてみた。

回路図の 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 モードに入る。