V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
Kawa
V2EX  ›  分享创造

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

  •  1
     
  •   Kawa · 2022-04-29 00:19:48 +08:00 · 2581 次点击
    这是一个创建于 727 天前的主题,其中的信息可能已经有所发展或是发生改变。

    买了个能转的显示器当副屏, 然后因为买的太便宜, 没有带旋转感应的(不知道别的有没有).
    然后觉得好像不大方便, 就想着写一个脚本, 能够一键切换屏幕旋转状态.
    最后配合 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 的内存占用, 基本不吃米.

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

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

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

    或者. 把物理按钮进一步替换为接近开关. 连按一下都省了.
    dongpengfei1
        8
    dongpengfei1  
       2022-05-03 13:53:15 +08:00
    改用霍尔检测,被动接收,这个比重力感应稳定多了
    Kawa
        9
    Kawa  
    OP
       2022-05-04 01:15:24 +08:00 via Android
    @dongpengfei1 霍尔只能检测两个方向吧, 利用重力传感器能检查四向, 不过我这个需求里只需要两个方向, 用霍尔也不是不行.
    用霍尔的话, 对于塑料件是不是需要装配额外的装置去触发传感器呢?
    经过我连续四天的使用, 我没有感觉到重力传感器有什么不精确的地方, 用起来还是很舒服的.
    在成本相近的情况下, 我觉得符合直觉 安装方便的重力传感器确实更好.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3160 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:46 · PVG 22:46 · LAX 07:46 · JFK 10:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.