Zapic's Blog
DIY一个给电脑显示器用的重力感应屏幕旋转装置
无标签

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