给电脑显示器用的重力感应屏幕旋转器

2022-04-29 00:19:48 +08:00
 Kawa

买了个能转的显示器当副屏, 然后因为买的太便宜, 没有带旋转感应的(不知道别的有没有).
然后觉得好像不大方便, 就想着写一个脚本, 能够一键切换屏幕旋转状态.
最后配合 MultiMonitorTool 的加载配置文件功能实现了一键切换布局.
我找到了内心的平静.

直到我发现, 这实在是太蠢了.
我每次都要找到脚本在哪, 再双击他切换布局.

于是我找了一个右键菜单编辑器, 把这个脚本放进了桌面的右键菜单里. 然后在桌面右键点击两次就能切换旋转状态.
我找到了内心的平静.

直到我发现, 这实在是太蠢了.
为什么不能直接根据实际状态旋转呢?

(x

DIY 这样一个东西, 大约需要 30 来块钱.
我买了一块已经焊好 USB 的 CH341A 和一块 MMA8452Q 模块, 某宝大约二十拿下.
然后再买了一些很长的杜邦线(因为要把 CH341 藏到桌子下面), 这样一共就花了三十多.

把 CH341 调成 3.3v 输出和 I2C 模式, 接上 MMA8452 的 4 根 I2C 针脚.
然后用装上 CH341 的 I2C 模式驱动:驱动
从里面找到CH341DLL.DLL, 之后要用.
使用测试工具验证连接是否正确, 驱动是否安装成功: CH341 测试工具
找到压缩包内的CH341PAR\VC\CH341PAR.EXE, 然后再把之前从驱动压缩包里翻出来的CH341DLL.DLL放在一起.
运行CH341PAR.EXE, 切换到 EEPROM 配置.
按照文档说明, 只接四根线的话, MMA8452 的设备地址应该是0x1c: MMA8452 文档
然后在0x0d上能够读取的 MMA8452 的 WHO_AM_I 信息: 0x2a
到这里硬件部分算是连接成功了.

然后编写代码轮询传感器, 获取屏幕旋转状态并通过 MultiMonitorTool 自动切换布局. MultiMonitorTool
从刚刚的 CH341 测试工具包里可以找到CH341DLL.hCH341DLL.lib
代码仅供参考, 我一个写 JS 的, 写出来的玩意能跑就行.

#include <windows.h>
#include <stdio.h>
#include <signal.h>

#include "CH341DLL.H"

#define DEVICE_ADDR 0x1c // MMA8452 设备地址
#define DEVICE_ID 0 // CH341 设备编号

#define getbit(x, y)   ((x) >> (y)&1)

// 检查设备状态并自动初始化重连
int CheckDevice(int DeviceId) {
    UCHAR read = 0;
    // 尝试读取 MMA8452 的 WHO_AM_I 信息
    if (!CH341ReadI2C(DeviceId, DEVICE_ADDR, 0x0d, &read)) {
        // 如果读不到, 尝试重新打开 CH341, 并再次尝试读取 WHO_AM_I 信息
        CH341CloseDevice(DeviceId);
        if (CH341OpenDevice(DeviceId) == INVALID_HANDLE_VALUE || !CH341ReadI2C(DeviceId, DEVICE_ADDR, 0x0d, &read)) {
            printf("Failed to open device #%d.\n", DeviceId);
            return 0;
        } else {
            printf("Opened device #%d.\n", DeviceId);
        }
    }
    // 根据读到的 WHO_AM_I 信息检查模块是否为 MMA8452, 如果没连接模块应该读到 0xFF
    if (read != 0x2a) {
        puts("Invalid device WHO_AM_I response.");
        return 0;
    }
    // 读取模块状态
    if (!CH341ReadI2C(DeviceId, DEVICE_ADDR, 0x2a, &read)) {
        printf("Device #%d closed unexpected.\n", DeviceId);
        return 0;
    }
    // 如果模块没有被启用
    if (read != 0x01) {
        if(
            !CH341WriteI2C(DeviceId, DEVICE_ADDR, 0x2a, 0b00000000) // 禁用模块(必须禁用掉模块才能调整模块设置)
            || !CH341WriteI2C(DeviceId, DEVICE_ADDR, 0x11, 0b11000000) // 启用旋转方向检测
            || !CH341WriteI2C(DeviceId, DEVICE_ADDR, 0x2a, 0b00000001) // 启用模块
            || (!CH341ReadI2C(DeviceId, DEVICE_ADDR, 0x2a, &read) || read != 0x01) // 检查模块是否被正确启用
        ) {
            printf("Failed to initialize device #%d.\n", DeviceId);
            return 0;
        } else {
            printf("Device #%d initialized successfully.\n", DeviceId);
        }
    }
    return 1;
}

int main() {
    UCHAR mBuffer = 0;
    UCHAR Last = 255;
    UCHAR Curr = 255;
    if (LoadLibrary("CH341DLL.DLL") == NULL) {
        puts("Cannot load required Dynamic Library CH341DLL.DLL.");
        return -1;
    }
    while (1) {
        Sleep(1000);
        if (!CheckDevice(DEVICE_ID)) {
            continue;
        }
        // 读取模块旋转状态
        if (!CH341ReadI2C(0, DEVICE_ADDR, 0x10, &mBuffer) || mBuffer == 0xff) {
            puts("Device disconnected unexpected.");
            continue;
        }
        // 取出数据
        // 我的显示器只能旋转 90 度, 于是偷懒只取出了表示横屏还是竖屏的数据位
        // 如果有需要, 可以根据文档取得更多数据
        // 此数据第 2 位表示横屏竖屏
        // 第 1 位表示旋转方向
        // 这两位结合可以判断屏幕的四向旋转
        // 具体请参考 MMA8452 文档对于 0x10 数据的解释
        Curr = getbit(mBuffer, 2);
        if (Curr != Last) {
            if (Curr == 0) {
                system("MultiMonitorTool.exe /LoadConfig landscape.cfg"); // 用 MMT 加载预先写好的显示器布局配置文件
            } else {
                system("MultiMonitorTool.exe /LoadConfig portrait.cfg");
            }
        }
        Last = Curr;
    }
}

代码必须使用 VC 去编译, 因为 CH341DLL.dll 就是用的 VC, mingw 弄半天发现跑不起来就是这个操蛋原因.
把编译出来的东西跟 MultiMonitorTool 还有 CH341DLL.dll 放在一起, 就能使用了.
编译的成品有需要可以分享出来, 但是写了这么久还是有点懒了, 晚点再传吧.

把模块粘在显示器背后, 再把这个轮询程序放到后台, 非常完美. 纯 C 编写的工具占用也非常小, 挂后台 0.3M 的内存占用, 基本不吃米.

2724 次点击
所在节点    分享创造
9 条回复
Kawa
2022-04-29 00:30:36 +08:00
这玩意属于是玩具的性质, 集成度不高, 不是很耐操, 裸板裸线 USB 接口也没固定, 但是可以用.
插到机箱后面之类的地方八百年碰不着, 损坏是不大可能, 但是得考虑粘在显示器背后的模块不掉下来.

关于轮询的问题, 我在群里聊这个的时候, 群友说轮询属实不大优雅, MMA8452 提供了两个可编程中断啥的...
没怎么听懂也不大理解, 感觉像是事件监听之类的吧, 但是毕竟 1s 一次的轮询也吃不了多少米, 我做的这个玩意对功耗要求不是很严格, 轮询问题也不大.png
hs0000t
2022-04-29 00:52:38 +08:00
真不错,之前也想过要不要做个类似的工作,后来把转屏打包成了个快捷键就结束了,没有到定制硬件的程度
顺便建议不要学某 up 主的风格说话,容易招来不必要的麻烦
snoopyhai
2022-04-29 08:58:28 +08:00
非技术党, 纯幻想.

显示器从横向转到纵向是不是需要人手去掰?
那把你外置的感应器换成一个按钮.
每次掰完显示器顺手按一下, 是不是可以解决轮询问题?
Cheons
2022-04-29 10:13:43 +08:00
伪需求,屏幕懒得动。直接上两个屏幕
Ourobotos
2022-04-29 11:06:46 +08:00
@snoopyhai 新需求又来了,可旋转显示器支架+微型慢速电机+可编程开关,实现屏幕物理自动旋转
Kawa
2022-04-29 12:04:06 +08:00
@snoopyhai
轮询其实不算问题, 因为开销真的很小, 属于是洁癖吧.
物理按钮的话, 我觉得屋里按钮跟键盘快捷键和鼠标右键菜单比起来, 好像真的没什么优势.
snoopyhai
2022-04-29 12:26:33 +08:00
@Kawa 物理按钮只替换传感器. 快捷键和鼠标右键不影响.
物理按钮可以理解为按钮 /快捷键的实体按键.

或者. 把物理按钮进一步替换为接近开关. 连按一下都省了.
dongpengfei1
2022-05-03 13:53:15 +08:00
改用霍尔检测,被动接收,这个比重力感应稳定多了
Kawa
2022-05-04 01:15:24 +08:00
@dongpengfei1 霍尔只能检测两个方向吧, 利用重力传感器能检查四向, 不过我这个需求里只需要两个方向, 用霍尔也不是不行.
用霍尔的话, 对于塑料件是不是需要装配额外的装置去触发传感器呢?
经过我连续四天的使用, 我没有感觉到重力传感器有什么不精确的地方, 用起来还是很舒服的.
在成本相近的情况下, 我觉得符合直觉 安装方便的重力传感器确实更好.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/849924

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX