野火指南者F103开发板之三: 自制QMK键盘固件的 EEPROM 和 RGB 矩阵

前言

在文章 [1]和 文章 [2] 中, 分别介绍了 bootloader 和 qmk 核心功能的移植.

这篇文章会稍微深入一些, 介绍自己一步一步了解和熟悉 stm32f103 的过程, 摸索之后配置好了键盘更多功能.

这篇文章中, 将通过阅读相关 datasheet, 了解 stm32 的基础知识, 给固件加上更多功能:

  • 外置持久化存储 i2c eeprom
  • 基于 dma 和 pwm 的 RGB Matrix.

成功点亮RGB

源码仓库 https://github.com/hitsmaxft/qmk-kb-bheznz

给键盘加上持久记忆特性

qmk固件里需要存储当前的配置状态, 比如 rgb 当前使用的主题等, 为了在拔掉电源的情况下, 也能持久化记住配置.
开发板的持久化记录数据方式, 除了像固件这样刷写进去, 还有动态修改的方式.
在这里我们需要开启 qmk 中的 eeprom 特性.

基于 i2c 的外置 eeprom

野火指南者开发板上自带了一块 eeprom, 大小为 2Kb(2048bit), 也就是 256B(字节) 但是官方给的资料太少了.
这里还需要找到具体的datasheet把需要的参数挖掘出来.

根据以下参考资料

从上面的数据, 挖掘到以下信息

  • Internally Organized 256 x 8 (2K)
  • 8-byte Page (1K, 2K) Write Modes

MCU 和 EEPROM 的连线上, 从野火开发板的wiki 上明确提到了, 用到 I2C1 接口, 引脚信息如下

  • SCL PB6
  • SDA PB7

这个两个引脚就不要接其他功能了, 专门用来往 eeprom 芯片里写入数据.
除了留出引脚, i2c 的写入还依赖时钟信号.

F10x 中 TIM3 TIM4 似乎会同时开关, 这个我还没完全确认

关于 eeprom 的处理代码, 涉及代码文件如下

  • platforms/chibios/drivers/i2c_master.c i2s 驱动代码, 比如引脚初始化
  • drivers/eeprom/eeprom_i2c.c eeprom 读写 i2c 接口的代码
  • drivers/eeprom/eeprom_i2c.h eeprom 所有可用的配置选项, 需要在 config.h 或者 halconf 中配置

关于 eeprom 的地址大小, eeprom._i2c.h 中就提到, 256B 及以下, 应该选 1. 因此, 需要仔细阅读数据手册和头文件里的注释.

The address size in bytes of the EEPROM. For EEPROMs with <=256 bytes, this
will likely be 1. For EEPROMs >256 and <=65536, this will be 2. For EEPROMs

65536, this will likely need to be 2 with the modified variant of
EXTERNAL_EEPROM_I2C_ADDRESS above.

高级特性默认都是关闭状态, 在 halconf 中需要一个一个开启, 包括后面的 PWM 和 DMA

halconf.h
1
2
3
4
5
#pragma once

#define HAL_USE_I2C
#include_next <halconf.h>

接下来在 mcu 中声明 stm32 相关的宏变量.

mcuconf.h
1
2
3
4
5
6
7
#pragma once

#include_next <mcuconf.h>

#undef STM32_I2C_USE_I2C2
#define STM32_I2C_USE_I2C1 TRUE//f103 所使用的特性都是 v1, 也包括 dmav1, gpiov1

halconf 优先级比较高, 可以提前定义模版变量.
mcuconf 先导入上一级的, 这里就得释放变量再重新定义

最后 在 confg.h 配置 eeprom 的核心参数:

title:config.
1
2
3
4
5
6
#    define STM32_ONBOARD_EEPROM_SIZE  256 //Byte
# define EXTERNAL_EEPROM_I2C_BASE_ADDRESS 0b10100000 //default value
# define EXTERNAL_EEPROM_BYTE_COUNT 256 //words
# define EXTERNAL_EEPROM_PAGE_SIZE 8 //Byte
# define EXTERNAL_EEPROM_ADDRESS_SIZE 1 //1 for 1k/2k, 2 for 4k
# define EXTERNAL_EEPROM_WRITE_TIME 5 //ms

我就简单加一行测试代码来输出 eeprom 是否启用成功来输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "eeconfig.h"

#include "debug.h"
#include "print.h"

void keyboard_post_init_user(void) {
//开启 debug, 否则 dprint 是不会输出内容了
debug_enable=true;
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case KC_A:
if (record->event.pressed) {
//通过这个命令, 检测 a 键按下事件, 把当前 eeconfig 的状态输出到控制台日志
dprintf("eeprom enabled: ", eeconfig_is_enabled());
}
return true;
break;
default:
return true; // Process all other keycodes normally
}
return true;
}

另外, 还需要在 keyboard.json 中, 把 console 特性开启了, 这样子qmk会通过 usb HID 接口输出日志信息.

1
2
3
4
5
"features": {
"command": true,
"console": true,
},

更新完固件之后, 运行 qmk console , 通过按下 A 键就可以看看 eeprom 的初始化是否正常了.

基于 flash 模拟的软 eeprom

除了使用专门的 eeprom 硬件, 更常用的是用 mcu 自己的flash 来存储配置. 虽然速度慢一点, 同时有寿命问题, 但是实现简单.

rules.mk 中加入

1
EEPROM_DRIVER = vendor

或者

1
2
3
4
5
{
"eeprom": {
"driver": "vender"
}
}

qmk 已经内置了 stm32f1 和 stm32f4 的 eeprom 模拟实现. 一个配置就搞定了. 而且支持 wearleveling 均衡磨损算法, 可以有效保护 flash寿命.

Wear-Leveling,中文译名为均衡磨损,是一种算法,用于保护固态硬盘(SSD)中的存储单元,防止其因过度使用而损坏。SSD的存储单元,特别是NAND Flash,有固定的擦写次数限制。如果对同一个存储单元反复进行擦写操作,它将更快地达到其寿命极限。
Wear-Leveling算法的工作原理是将擦除和写入操作平均地分配到SSD的所有存储单元中,而不是集中在少数几个单元上。这样,所有存储单元的磨损程度就更加均衡,从而延长了SSD的整体寿命。
这种算法通过管理空闲块(未使用的存储单元)和数据块(已使用的存储单元)之间的转换来实现。当某个数据块被频繁擦写时,Wear-Leveling算法会将该块标记为空闲块,并在其他空闲块中选择一个块来存储数据。这样,原本频繁擦写的数据块就能得到休息,而其他空闲块则承担了更多的擦写操作。

光污染 RGB 键盘矩阵

最简单的 bitbang 驱动方式

在不少硬件项目中,特别在那些没有专用硬件序列化接口的情况下,bitbang 是一种可行的方案来驱动 RGB LED。

Bit-banging 是一种软件模拟的串行通信技术,可以在没有专用序列化硬件支持的情况下实现。这通常意味着通过快速切换 GPIO 的高低状态(电位),来模拟需要的时序脉冲。这种手动翻转通信协议的一个典型例子是 WS2812B LED 的数据线控制。由于 WS2812B 要求非常精确的时序来区分逻辑“0”和逻辑“1”,对代码的执行效率有较高的要求。

在 qmk 中启用 bitbang 驱动是非常简单的一件事情.

这里只简单开启两种动画作为测试.

titile:keyboard.json
1
2
3
4
5
6
7
8
9
10
11
12
"rgb_matrix": {
"driver": "ws2812"
,"animations":{
"typing_heatmap": true,
"multisplash" : true
}
}
, "w2812": {
"pin": "B0",
"driver": "bitbang"
}

你也可以在 rules.mk 中开启 ws2812 的驱动方式为 bitbang

title:rules.mk
1
WS2812_DRIVER = bitbang

以及在 config.h 中配置需要 pin

1
#define WS2812_DI_PIN B0

尽管 bitbang 是一种通用方法,但高速 GPIO 切换会占用 MCU 大量资源,并且在多任务环境下可能导致时序问题。为了优化性能,可以考虑将数据发送和其他程序任务在时间上错开,减少资源争用的机会。此外,减小要控制的 LED 数量或降低更新频率也能降低对 MCU 资源的需求。

bitbang 这里作为一种简单验证的方法进行介绍, 就不过多展开了, 首先确保 LED 连线和工作正常, 接下来会介绍更高效的方式驱动 RGB 矩阵.

这里再放一张最后成果图, 换上了 4x4 行列扫描的薄膜键盘,, 一个小键盘大工告成.

图片

放个视频 https://pbs.twimg.com/ext_tw_video_thumb/1788391340189061120/pu/img/vMVQsPZF3kNjueFM?format=jpg&name=large

使用 DMA 和 PWM 实现 rgb 会更节省资源, 但篇幅比较大, 待续