野火指南者F103开发板之二: 移植 qmk 开源键盘固件
前言
在前文中, 我介绍了移植野火开发板 stm32f103开发板的 dfu bootloader, 接下来要介绍的是如何迁移qmk到开发板上.
这篇文章会涉及修改 qmk 源码,添加 stm32f103 键盘, 配置测试小键盘的布局, 最后通过 stm32duino 的 dfu 模式刷入固件.
一同解释 qmk 的一些基础知识.
后面的文章逐步深入到 RGB 和存储等.
源码仓库 https://github.com/hitsmaxft/qmk-kb-bheznz , 最新的代码在我的qmk-firmware 中, 这一份是手工尽量同步.
开发板
指南者拥有接近 100 个引脚, 这次搭建键盘原型只会用到很小一部分引脚.
在本文这个测试键盘中, 我用的是一个简单的 2x4 微动按钮矩阵用于测试键盘功能.
- 微动键盘: 4x2 8个独立按键, 9个引脚.
每个按钮都是独立引脚, 直接连接到开发板, 还有一个 GND 接地.
初始化键盘 源代码
1 | qmk new-keyboard |
我这里给键盘起的名字是 bheznz
- layout 选最后一个
62. none of the above
, 后面再改 - mcu 选
40. STM32F103
qmk会添加一下文件.
1 | keyboards/bheznz |
keyboard.json 是最新的配置格式, 通过 json 配置大部分键盘核心配置. 但是剩下的定制选项还是需要通过其他文件来完成.
由于后续我还根据按键个数拆分了不同的外设, 发现一个坑, 就是同一个键盘下多个型号, 需要多份 keyboard.json 的时候, 这里的 keyboard.json 会导致报错, 又被我手动重新改名回 info.json.
实际移植中, 我后续手动添加以下文件, 来完成迁移 qmk 的所有功能
1 | keyboards/bheznz/bheznz.h //键盘公共头文件 |
后面需要改的时候再一一说明.
配置键盘布局
移植和构建qmk固件的第一步, 是需要让键盘按键逻辑运作起来.
这部分配置不需要写代码, 只需要修改 json 配置.
我这里选择了开发板上给 led 屏幕留的插槽部分和 gnd, 一共9个引脚.
根据物理的外观, 首先配置好按键在板子上行列情况.
本来独立按钮是没有必要配置行列的, 它并没有行列扫描的逻辑, 直接按一行8列配置就行. 这里只是为了和行列的保持一致, 按照板子上的排列配置了4行2列.
接下来 json 的配置都是keyboard.json
这个文件.
这是键盘上按键的物理布局, 4 rows 2 column (4行2列), 坐标从 (0,0) 到 (3,2).
┌──┐┌──┐
│K1││K2│
└──┘└──┘
┌──┐┌──┐
│K3││K4│
└──┘└──┘
┌──┐┌──┐
│K5││K6│
└──┘└──┘
┌──┐┌──┐
│K7││K8│
└──┘└──┘
1
2
3
4
5
6
7
8
9
10
11
12
连接的 PIN/引脚 按上面, 排列如下
```json
"matrix_pins": {
"direct": [
["D15", "D1"], //0,0|0,1
["E8", "E10"],
["E12", "E14"],
["D8", "D10"]
]
}
考虑到键盘一般是横向的, 内置的 layout 里也只有 2x4/2行4列, 接下来会键盘的物理定义顺时针旋转了 90 度. 原因后面会补充.
计算了一下键盘的按键物理布局和引脚行列编号的对应关系, 是下面这个情况. 列出
1
2
3,0|2,0|1,0|0,0
3,1|2,1|1,1|0,1
接下来需要配置 layout, 也就是键盘上从左到右, 从上到下, 从 0 开始编号至最后一个按钮.
配置中需要声明每个按钮对应的引脚的行列位置, 以及它在键盘中的物理位置.
> matrix: 当前按钮对应的行和列, pin 分成了4列2行, 但做了90度旋转, 所以需要换成实际的pin位置
> x: 按钮左边的偏移量, 从0开始. 所以每个键的x, 其实**左边所有按键的宽度**累加起来.
> y: 同x, 是垂直方面, 上面所有行高度累加, 默认是1. 像分裂键盘就会存在高度超过 1 的按钮.
> w: 键盘正常是 1U , mod 键一般是1.25u, 还有更长的 shift 和 space, 我这里都当成1u, 也就不用特地写上 w 参数了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"layouts": {
"LAYOUT_ortho_2x4": {
"layout": [
{"matrix": [3, 0], "x": 0, "y": 0},
{"matrix": [2, 0], "x": 1, "y": 0},
{"matrix": [1, 0], "x": 2, "y": 0},
{"matrix": [0, 0], "x": 3, "y": 0},
{"matrix": [3, 1], "x": 0, "y": 1},
{"matrix": [2, 1], "x": 1, "y": 1},
{"matrix": [1, 1], "x": 2, "y": 1},
{"matrix": [0, 1], "x": 3, "y": 1}
]
}
}
这里做了按键到pin的换算, 是处于教程的目的.
实际的键盘设计中, 是会存在一些优化手段, 按键的实际位置和 pin 的行列位置是不同的.
这么做也是为了节省引脚. 比如61/64键键盘布局 5x14, 实际上能容纳下70个键, 这就导致存在很多空位.
同过把第 14 列的按键放在其他行的空位上. 这样子 5x13 列的 pin 布局就可以容纳下物理外观是 5x14, 最多65键的键盘.
节省了一个引脚可以用于其他作用.
最终效果如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> qmk info -kb bheznz -l
....
Layouts: LAYOUT_ortho_2x4
Processor: STM32F103
Bootloader: stm32duino
Matrix for "LAYOUT_ortho_2x4":
┌──┐┌──┐┌──┐┌──┐
│3A││2A││1A││0A│
└──┘└──┘└──┘└──┘
┌──┐┌──┐┌──┐┌──┐
│3B││2B││1B││0B│
└──┘└──┘└──┘└──┘
`LAYOUT_ortho_2x4` 是`qmk`源码库中内置的布局名称, `qmk`编译的时候, 会按这个布局生成对应的 c 代码宏指令. 下面通过 `keymap.c` 配置键位功能, 代码中会用到这个生成的宏命令.
以下是我根据需要定制自己的测试 keymap 代码.
title:keymap.c 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
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/*
* ┌───┬───┬───┬───┐
* │ A │ S │ D │ F │
* ├───┼───┼───┼───┤
* │ Z │ X │ C │ V │
* └───┴───┴───┴───┘
*/
[0] = LAYOUT_ortho_2x4(
LT(2, KC_A), KC_S, KC_D, KC_F,
LT(1, KC_Z), KC_X, KC_C, KC_V
),
[1] = LAYOUT_ortho_2x4(
_______, RGB_VAI, RGB_HUI, RGB_HUD,
_______, RGB_VAD, RGB_MOD, RGB_RMOD
),
[2] = LAYOUT_ortho_2x4(
_______, RGB_TOG, _______, _______,
QK_BOOT, QK_DEBUG_TOGGLE, RGB_VAI, RGB_VAD
),
};
## 开发板和 chibios 代码相关
前面只是罗列了开发板的一些引脚信息, 让固件跑起来, 除了配置布局, 还需要正确地配置 qmk 中的开发板配置和引脚信息.
qmk 的 HAL (硬件抽象层) 是 chibios 这个嵌入式框架提供的. 关于开发板的定制, 首先了解一下 chibios 中开发板代码组织方式.
* platforms/chibios/boards 下是qmk提供的内置开发板, f103 的板子只有 `STM32_F103_STM32DUINO`
* lib/chibios/os/hal/boards 下则是 chibios 默认支持的开发板信息, 可以看到 ST Nuclear 的板子比较多
本来我以为需要自己写板子的配置, 但是仔细测试之后发现, qmk 的bootloader选项一旦配置成 `stm32duino`, 会强行指定 `BOARD=STM32_F103_STM32DUINO`.
也就是说, 不能自己定义开发板源码. 所以这里我们也不用特地去关心开发板的代码了, 自定义的行为代码, 都需要放在 keyboard 的源代码中.
所以, 现在确认一下 `keyboard.json` 中, bootloader 是正确地写上 `stm32duino`.
1
"bootloader": "stm32duino",
开发板的准备工作到这里就告一段落了, 我们已经完成了 bootloader 开发板信息是默认的, 键盘的布局和keymap也配置完毕.
接下来还需需要一些改动, 让固件能够正常地工作起来.
### 解决 qmk 每次启动都进入 bootloader
虽然开发板代码不能改, 这里还需要提醒一下, qmk 固件刷了之后, 每次启动都会默认进入 bootloader, 这是开发板源码里一个bug导致的.
先看一下开发板的默认初始化代码
title: platforms/chibios/boards/STM32_F103_STM32DUINO/board.c 1
2
3
4
5
6
7
8
9
10
11
12
/*
* Board-specific initialization code.
*/
void boardInit(void) {
//JTAG-DP Disabled and SW-DP Enabled
AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE;
//Set backup register DR10 to enter bootloader on reset
//我们需要注释掉这句话
BKP->DR10 = RTC_BOOTLOADER_FLAG;
}
`BKP` 是 stm32 的备份寄存器, 指南者板子上自带了纽扣电池, `BKP` 和 `RTC` 时钟寄存器在电池供电的情况下, 即时拔掉外部供电, 也不会丢失.
而 `stm32duino-bootloader` 会读取 `BKP DR10` , 一旦发现这个值是 RTC_BOOTLOADER_FLAG, 会停留在 bootloader 的DFU 模式, 等待刷固件.
> RTC Flag 还有额外集中情况, 待补充.
所以默认的 boardInit 会引起每次上电都进入刷固件模式.
但根本不存在这种需求, 需要刷固件按 reset 不就行了.
复写这个函数, 需要在 bheznz.c, 也就是键盘的自定义代码中覆盖这个函数
title: keyboard/bheznz/bheznz.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void bootloader_jump(void) {
//写入 bootloader 标记, 下次重启自动进入bootloader
BKP->DR10 = RTC_BOOTLOADER_FLAG;
NVIC_SystemReset();
}
void mcu_reset(void) {
BKP->DR10 = RTC_BOOTLOADER_FLAG;
NVIC_SystemReset();
}
void boardInit(void) {
//JTAG-DP Disabled and SW-DP Enabled 这句话我还不知道干嘛的, 继续保留
AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE;
}
`bootloader_jump()` 和 `mcu_reset()` 分别是按下 QK_BOOT 和 开发板reset 按钮执行的操作. 现在改成写入 bootloader 标记位, 重置或者重新上电, bootloader 会识别并进入 DFU 模式.
boardInit 这里就没必要动寄存器了, 正常引导就行了.
> 原始代码见 `platforms/chibios/bootloaders/stm32duino.c`
### 配置 usb disc 引脚
上文说到 野火的usb 有个开关引脚 PD6, 这个引脚如果处于高电平状态, 会导致usb无法工作.
chibios 已经考虑到 usb 初始化的定制需求. 预留了 usb 的扩展宏.
这里需要修改 `board.h` 中, 以下两个宏命令. 往其中添加 PD6 引脚的状态修改代码.
1
2
tmk 的源码中可以找到调用的位置
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
//lib/chibios/os/hal/include/hal_usb.h:400
//tmk_core/protocol/chibios/usb_main.c:363:
void init_usb_driver(USBDriver *usbp) {
for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
usb_endpoint_in_init(&usb_endpoints_in[i]);
usb_endpoint_in_start(&usb_endpoints_in[i]);
}
for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
usb_endpoint_out_init(&usb_endpoints_out[i]);
usb_endpoint_out_start(&usb_endpoints_out[i]);
}
/*
* Activates the USB driver and then the USB bus pull-up on D+.
* Note, a delay is inserted in order to not have to disconnect the cable
* after a reset.
*/
usbDisconnectBus(usbp);
usbStop(usbp);
wait_ms(50);
usbStart(usbp, &usbcfg);
usbConnectBus(usbp);
}
从源码中可知, tmk 在初始化usb 驱动时, 会先调用 disc 函数关闭 usb , 完成状态重置, 然后重新连接 usb bus.
> `platforms/chibios/boards/STM32_F103_STM32DUINO/board/board.h` 这里也有部分代码考虑到 USB DISC pin 但是都处于注释状态, 并没有提供选项. 我打算提个 PR 优化一下, 就当做公益了.
## 编译和刷写固件
接下来就是运行编译命令, 碰到什么问题一个一个解决了.
1
qmk compile -kb bheznz -km default
### 通过 qmk flash 刷写固件
qmk cli 中内置了 stm32 dfu 更新的命令
按下 reset 让开发板进入 dfu 模式, 然后运行
1
qmk flash -kb bheznz -km default
`qmk` 会通过 `dfu-tool` 发现处于 `dfu` 状态的设备, 然后把编译好的固件写入到键盘中.
这部分没啥好介绍的, 参考前面的文章, 把 `stmd32duino-bootloader` 刷好.
开机按住 k1 或者按一下 reset 都能看到 bootloader 指示灯闪烁, 就是成功进入 dfu 模式
## 结尾
到这里, 指南者的 qmk 固件就迁移完毕了.
刷完固件, 重置开发板, bootloader 如果没有进入 DFU 模式, 接下来就会加载用户代码部分, 也就是 qmk 键盘固件.
正常启动的话, 就可以在电脑中看到键盘了.
![连接效果](https://img.bhe.ink/IMG_2091.jpeg)
> 基础的键盘部分只需要配置对 PIN 和 bootloader, 保证 USB 功能正常启动, 测试一下键位对不对就行了.
后面再继续写复杂一点的, 支持 RGB, EEPROM 和OELD等部分.