联想拯救者 R720 键盘矩阵逆向

联想拯救者R720 键盘矩阵逆向

朋友托我帮他的R720笔记本改装成KVM.于是便有了此文.

键盘介绍

主流键盘基本上都是矩阵键盘.这种方式比较节省IO.这块R720也不例外.
R720的键盘一共有96个键,用矩阵键盘的方式,理论上需要(6+16=22)个IO.(其实还是挺多的)
为了避免鬼键,实际的接线比理想情况下复杂.
而且相较于很多DIY机械键盘的直来直去线路:
常见
R720这块键盘的内部Wiring就显得很乱了:
R720
刚测出结果的时候,总感觉自己一定是弄错了什么地方.直到后来搜到了一款名叫Framework Laptop的笔记本,有着类似的KeyMap才放下心来.

KeyMap测绘

搜到一篇教人把笔记本改装为KVM的文章,文章里面提到了矩阵键盘驱动控制模块Monkey.
我一看这不和ATMEGA32U4差不多吗,应该是同一个系列的产品,淘宝上这东西卖50.这不是要了我的命吗.
自从去年单片机涨价后,Atmel的产品到现在也没恢复过去的价格,觉得自己现在要是买了就是纯纯大冤种.
况且完全可以自己做一个类似的东西,当时手上有这么多IO的板子就一个Raspberry Pi Pico,而QMK也支持了Pico,于是先借助Pico对KeyMap进行测绘.
得到如下结果

Excel

你问我怎么知道Row Pin和Col Pin的,这不是巧了吗,Google一下找到了同模具笔记本的原理图(网站).
顺便还知道了触摸板引脚定义,后面直接开启QMK的PS/2鼠标支持顺便把触摸板一起驱动了.
32P-0.8mm-FPC

Schematic

得到Key Map后,就可以开始画板子的工作了.
迫于当时无法使用Pico编译出支持PS/2 Mouse的固件,所以把主控换成了常见的ATMEGA32U4.
换主控后,IO开始紧缺了,不过没关系.可以使用拓展IO的芯片来应付这种情况.
看了一眼QMK仓库里面的方案,大部分都是使用的输出IO拓展来解决问题.
非常不巧的是,这块键盘的扫描方向和常规键盘相反,所以不出意外的话我就要出意外了.
于是购入74HC595对输出IO进行拓展并完美的踩了坑.重新检查才意识到我得使用74HC165拓展输入IO才行.

拖拉好久并且又踩到了PS/2 Mouse必须使用指定的IO驱动等好几个坑,最终绘制出下面使用74HC165原理图的方案(沉痛浪费很多次JLC免费打板机会.)

原理图

QMK Pin Map

参考测绘出的Key Map,再借助网页工具得到最终QMK固件所需的KeyMap
为了让多媒体键工作正常,于是设置了两层Key Map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
LAYOUT_pa(
KC_PMNS, KC_PSLS, KC_P0, KC_LALT, KC_SPC, KC_Z, KC_B, KC_N, KC_DOWN, KC_LSFT, KC_PAST, KC_LCTL, KC_UP, KC_ENT, KC_F8,
KC_C, KC_Q, KC_RALT, KC_X, KC_V, KC_M, KC_DOT, KC_RSFT, KC_COMM, KC_RCTL, KC_SLSH, KC_QUOT,
KC_PPLS, KC_P9, MO(1), KC_E, KC_F2, KC_G, KC_H, KC_BSLS, KC_F7, KC_P8, KC_MINS, KC_RGHT,
KC_P2, KC_LGUI, KC_TAB, KC_F4, KC_F1, KC_T, KC_Y, KC_O, KC_F6, KC_F9, KC_F12, KC_PSLS,
KC_P3, KC_P7, KC_GRV, KC_CAPS, KC_S, KC_5, KC_6, KC_F10, KC_F5, KC_0, KC_EQL, KC_DEL,
KC_PDOT, KC_1, KC_3, KC_2, KC_4, KC_7, KC_9, KC_8, KC_P, KC_BSPC, KC_P4,
KC_P1, KC_F3, KC_W, KC_R, KC_U, KC_F11, KC_I, KC_LEFT, KC_LBRC, KC_RBRC, KC_P5,
KC_NUM, KC_A, KC_ESC, KC_F, KC_J, KC_L, KC_K, KC_APP, KC_SCLN, KC_D, KC_P6),

LAYOUT_pa(
KC_PAUS, KC_MNXT, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PGDN, KC_TRNS, KC_TRNS, KC_TRNS, KC_PGUP, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_MPRV, KC_TRNS, KC_TRNS, KC_VOLD, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_MPLY, KC_TRNS, KC_END,
KC_PSCR, KC_TRNS, KC_TRNS, KC_TRNS, KC_MUTE, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_DEL, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_INS, KC_VOLU, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_HOME, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS),
};

实物

实物

遗憾

背光的电压是5V,但是我没有适配.我已经过了那个喜欢背光的年纪了.

More

笔记本主板的那个叫啥BoardView也下载到了,再对着这玩意人肉Convert(测量,然后手动绘制)一下就得到了DWG图.
知道了精确的开孔,再画板子就方便固定了

Draw Outline

参考资料

老旧笔记本改造成便携KVM
Framework Laptop Key Matrix
QMK Firmware
Laptop Bios & Schematics

对ESP32 SPIFFS分区进行OTA

前言

为SPIFFS分区OTA适用于只想更新资源部分,而不更新运行代码的场景,比如主题里面的图片资源,字体库之类的东西.

分区表

1
2
3
4
5
# Name,   Type, SubType, Offset,  Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x300000,
storage, data, spiffs, 0x310000, 0xC000,

这里为了加快OTA速度,我将SPIFFS分区设置的非常小,只有49152字节(48KB)
同时app分区只有app0分区.

OTA涉及到的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取当前的SPIFFS分区
const esp_partition_t *spiffs_patition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
// ESP_PARTITION_TYPE_DATA 和分区表内的data对应
// ESP_PARTITION_SUBTYPE_DATA_SPIFFS 对应SPIFFS类型
// 最后一个参数label传递NULL,表示是个SPIFFS分区就行,叫啥都没关系.

// 擦除找到的SPIFFS分区内容.从头开始擦除offset=0, 擦除全部内容size=spiffs_patition->size
esp_partition_erase_range(spiffs_patition, 0, spiffs_patition->size);

// spiffs2_start spiffs2_end是我用到的新的spiffs文件内容.使用embed_file方式嵌入在程序中.
// 写入新的spiffs2的内容到刚才找到的SPIFFS分区,由于我这时候新的spiffs已经是完整的了,所以offset=0,
// 写入尺寸就是此时具有的新spiffs内容的大小size=spiffs2_end - spiffs2_start - 1
esp_partition_write(spiffs_patition, 0, spiffs2_start,
spiffs2_end - spiffs2_start - 1);

// 重启后,SPIFFS内容已经变成刚才新写入的了.
esp_restart();

Repository

模拟OTA操作.
Esp_SPIFFS_OTA_Demo

ESP32在休眠模式下保持PWM输出

前言

IDF手册上说如果PWM的clk_cfg配置为RTC8M_CLK支持Light-Sleep模式,但是官方的Repo里面也没有如何使用这个特性的例子.

实操

经过一圈摸索.需要配置menuconfig才行

1
idf.py menuconfig

找到Component config->Hardware Settings -> Sleep Config, 关闭light sleep GPIO reset workaround

在esp-idf内置的LEDC (LED Controller) basic example的基础上修改,得到以下代码

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

#include <stdio.h>

#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "rtc.h"

#define PIN_LED GPIO_NUM_5

static void example_ledc_init(void) {
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 1000,
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.clk_cfg = LEDC_USE_RTC8M_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {.channel = LEDC_CHANNEL_0,
.duty = 128,
.gpio_num = PIN_LED,
.speed_mode = LEDC_LOW_SPEED_MODE,
.hpoint = 0,
.timer_sel = LEDC_TIMER_0};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
void app_main(void) {
// Set the LEDC peripheral configuration
example_ledc_init();
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC8M, ESP_PD_OPTION_ON);
while (1) {
for (int i = 1; i < (1 << 13); i += 128) {
ESP_LOGE("Duty", "Current Duty is %d", i);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, i));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
esp_sleep_enable_timer_wakeup(1000 * 1000 * 1);
esp_light_sleep_start();
}
}
}
© 2025 Do U Find IT? All Rights Reserved.
Theme by hiero