├── Firmware ├── .gitignore ├── u8g2_rs │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── font │ │ ├── font.h │ │ └── u8g2_font_fusion_pixel_16_mn.c │ ├── src │ │ └── lib.rs │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ └── Cargo.lock ├── .vscode │ └── settings.json ├── src │ ├── lib.rs │ ├── config.rs │ ├── gpio.rs │ ├── regs.rs │ ├── main.rs │ ├── power.rs │ ├── softwire.rs │ ├── rtc.rs │ ├── bluetooth.rs │ ├── display.rs │ └── assets.rs ├── .cargo │ └── config.toml ├── Cargo.toml └── Cargo.lock ├── Image ├── icon.png ├── PCBATop.png ├── backview.png ├── frontview.jpg ├── geber_top.png ├── PCBABottom.png ├── bluetoothMode.png ├── geber_bottom.png ├── BackViewInFusion.png ├── PreviewInFusion.png └── mini_app_qr_code.jpg ├── .gitmodules ├── README.md └── LICENSE /Firmware/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Firmware/u8g2_rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /Image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/icon.png -------------------------------------------------------------------------------- /Image/PCBATop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/PCBATop.png -------------------------------------------------------------------------------- /Image/backview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/backview.png -------------------------------------------------------------------------------- /Image/frontview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/frontview.jpg -------------------------------------------------------------------------------- /Image/geber_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/geber_top.png -------------------------------------------------------------------------------- /Image/PCBABottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/PCBABottom.png -------------------------------------------------------------------------------- /Image/bluetoothMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/bluetoothMode.png -------------------------------------------------------------------------------- /Image/geber_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/geber_bottom.png -------------------------------------------------------------------------------- /Image/BackViewInFusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/BackViewInFusion.png -------------------------------------------------------------------------------- /Image/PreviewInFusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/PreviewInFusion.png -------------------------------------------------------------------------------- /Image/mini_app_qr_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosgoo/friday_ink/HEAD/Image/mini_app_qr_code.jpg -------------------------------------------------------------------------------- /Firmware/u8g2_rs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "u8g2.h": "c" 4 | } 5 | } -------------------------------------------------------------------------------- /Firmware/u8g2_rs/font/font.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_H_ 2 | #define FONT_H_ 3 | #include 4 | extern const uint8_t u8g2_font_fusion_pixel_16_mn[] U8G2_FONT_SECTION("u8g2_font_fusion_pixel_16_mn"); 5 | #endif -------------------------------------------------------------------------------- /Firmware/u8g2_rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![no_std] 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | 7 | -------------------------------------------------------------------------------- /Firmware/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "riscv32imac-unknown-none-elf", 3 | "rust-analyzer.check.allTargets": false, 4 | "rust-analyzer.showUnlinkedFileNotification": false, 5 | } 6 | -------------------------------------------------------------------------------- /Firmware/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | pub mod assets; 3 | pub mod bluetooth; 4 | pub mod config; 5 | pub mod display; 6 | pub mod gpio; 7 | pub mod power; 8 | pub mod regs; 9 | pub mod rtc; 10 | pub mod softwire; 11 | -------------------------------------------------------------------------------- /Firmware/src/config.rs: -------------------------------------------------------------------------------- 1 | // 按钮消抖样式, 单位ms 2 | pub const DEBOUNCE_TIME: u64 = 50; 3 | // 配对模式超时重启时间, 单位ms 4 | pub const PAIR_MODE_TIME_OUT: u8 = 20; 5 | 6 | // 闹钟, 用于每天定时刷新 7 | pub const ALARM_HOUR: u8 = 0; 8 | pub const ALARM_MINUTE: u8 = 0; 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Firmware/ch58x-hal"] 2 | path = Firmware/ch58x-hal 3 | url = git@github.com:vjspdhpp/ch58x-hal.git 4 | [submodule "Firmware/u8g2_rs/u8g2"] 5 | path = Firmware/u8g2_rs/u8g2 6 | url = git@github.com:vjspdhpp/u8g2.git 7 | -------------------------------------------------------------------------------- /Firmware/u8g2_rs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv32imac-unknown-none-elf" 3 | 4 | [alias] 5 | run-demo = "run --release --example" 6 | 7 | [target.riscv32imac-unknown-none-elf] 8 | runner = "wchisp flash" 9 | # runner = "wlink -v flash --dry-run" 10 | # runner = "wlink -v flash" 11 | # runner = "wlink -v flash --watch-serial" 12 | 13 | # runner = "probe-rs run --chip CH582 --chip-description-path definition.yaml" 14 | 15 | rustflags = [ 16 | # "-C", "link-arg=-Tmemory.x", 17 | "-C", 18 | "link-arg=-Tlink.x", 19 | # "--emit", "obj", 20 | # "--emit", "asm", 21 | ] 22 | -------------------------------------------------------------------------------- /Firmware/u8g2_rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "u8g2_rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | build = "build.rs" 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | 9 | [dependencies] 10 | libc = "0.2" 11 | 12 | [build-dependencies] 13 | cc = { version = "1.0", features = ["parallel"] } 14 | bindgen = "0.65" 15 | 16 | # [env] 17 | # CC = { value = "C:\MounRiver\MRS_Community\toolchain\RISC-VEmbeddedGCC12\bin\riscv-none-embed-gcc.exe", force = true } 18 | # AR = { value = "C:/MounRiver/MRS_Community/toolchain/RISC-VEmbeddedGCC12/bin/riscv-none-elf-ar.exe", force = true } 19 | -------------------------------------------------------------------------------- /Firmware/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv32imac-unknown-none-elf" 3 | 4 | [alias] 5 | run-demo = "run --release --example" 6 | build-hex = "objcopy --release -p friday_rs -- -O ihex friday_rs.hex" 7 | 8 | [target.riscv32imac-unknown-none-elf] 9 | runner = "wchisp flash" 10 | # runner = "wlink -v flash --dry-run" 11 | # runner = "wlink -v flash" 12 | # runner = "wlink -v flash --watch-serial" 13 | 14 | # runner = "probe-rs run --chip CH582 --chip-description-path definition.yaml" 15 | 16 | rustflags = [ 17 | # "-C", "link-arg=-Tmemory.x", 18 | "-C", 19 | "link-arg=-Tlink.x", 20 | # "--emit", "obj", 21 | # "--emit", "asm", 22 | ] 23 | -------------------------------------------------------------------------------- /Firmware/src/gpio.rs: -------------------------------------------------------------------------------- 1 | pub const GPIO_PIN_0: u32 = 0x00000001; 2 | pub const GPIO_PIN_1: u32 = 0x00000002; 3 | pub const GPIO_PIN_2: u32 = 0x00000004; 4 | pub const GPIO_PIN_3: u32 = 0x00000008; 5 | pub const GPIO_PIN_4: u32 = 0x00000010; 6 | pub const GPIO_PIN_5: u32 = 0x00000020; 7 | pub const GPIO_PIN_6: u32 = 0x00000040; 8 | pub const GPIO_PIN_7: u32 = 0x00000080; 9 | pub const GPIO_PIN_8: u32 = 0x00000100; 10 | pub const GPIO_PIN_9: u32 = 0x00000200; 11 | pub const GPIO_PIN_10: u32 = 0x00000400; 12 | pub const GPIO_PIN_11: u32 = 0x00000800; 13 | pub const GPIO_PIN_12: u32 = 0x00001000; 14 | pub const GPIO_PIN_13: u32 = 0x00002000; 15 | pub const GPIO_PIN_14: u32 = 0x00004000; 16 | pub const GPIO_PIN_15: u32 = 0x00008000; 17 | pub const GPIO_PIN_16: u32 = 0x00010000; 18 | pub const GPIO_PIN_17: u32 = 0x00020000; 19 | pub const GPIO_PIN_18: u32 = 0x00040000; 20 | pub const GPIO_PIN_19: u32 = 0x00080000; 21 | pub const GPIO_PIN_20: u32 = 0x00100000; 22 | pub const GPIO_PIN_21: u32 = 0x00200000; 23 | pub const GPIO_PIN_22: u32 = 0x00400000; 24 | pub const GPIO_PIN_23: u32 = 0x00800000; 25 | pub const GPIO_PIN_ALL: u32 = 0xFFFFFFFF; -------------------------------------------------------------------------------- /Firmware/src/regs.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | pub const SAFE_ACCESS_SIG1: u8 = 0x57; // WO: safe accessing sign value step 1 3 | pub const SAFE_ACCESS_SIG2: u8 = 0xA8; // WO: safe accessing sign value step 2 4 | pub const RB_CLK_XT32K_PON: u8 = 0x01; // RWA, external 32KHz oscillator power on 5 | pub const RB_CLK_INT32K_PON: u8 = 0x02; // RWA, internal 32KHz oscillator power on 6 | pub const RB_CLK_OSC32K_XT: u8 = 0x04; // RWA, 32KHz oscillator source selection: 0=RC, 1=XT 7 | pub const RB_CLK_OSC32K_FILT: u8 = 0x08; // RWA, internal 32KHz oscillator low noise mode disable: 0=enable, 1=disable 8 | pub const RB_32K_CLK_PIN: u8 = 0x80; // RO, 32KHz oscillator clock pin status 9 | pub const RB_CLK_XT32M_PON: u8 = 0x04; // RWA, external 32MHz oscillator power control: 0=power down, 1-power on 10 | pub const RB_CLK_SYS_MOD: u8 = 0xC0; // RWA, system clock source mode: 00=divided from 32MHz, 01=divided from PLL-480MHz, 10=directly from 32MHz, 11=directly from 32KHz 11 | pub const RB_PWR_DCDC_EN: u16 = 0x0200u16; // RWA, DC/DC converter enable: 0=DC/DC disable and bypass, 1=DC/DC enable 12 | pub const RB_PWR_DCDC_PRE: u16 = 0x0400u16; // RWA, DC/DC converter pre-enable 13 | pub const RB_PWR_PLAN_EN: u16 = 0x8000u16; // RWA/WZ, power plan enable, auto clear after sleep executed 14 | pub const RB_PWR_MUST_0010: u16 = 0x1000u16; // RWA, must write 0010 15 | pub const RB_SLP_GPIO_WAKE: u8 = 0x10; // RWA, enable GPIO waking 16 | pub const RB_WAKE_EV_MODE: u8 = 0x40; // RWA, event wakeup mode: 0=event keep valid for long time, 1=short pulse event 17 | 18 | -------------------------------------------------------------------------------- /Firmware/Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["u8g2_rs"] } 2 | [package] 3 | name = "friday_rs" 4 | version = "2.0.1" 5 | edition = "2021" 6 | authors = ["Chaosgoo", "Chaosgoo "] 7 | license = "GPL-3.0 license" 8 | keywords = ["calendar", "epaper", "embedded"] 9 | # build = "build.rs" 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [build-dependencies] 13 | bindgen = "0.65" 14 | cc = "1.0" 15 | 16 | [dependencies] 17 | ch58x = { version = "0.3.0", features = ["ch58x", "rt"] } 18 | ch58x-hal = { path = "./ch58x-hal" } 19 | u8g2_rs = { path = "./u8g2_rs" } 20 | 21 | 22 | fugit = "0.3.7" 23 | nb = "1.1.0" 24 | embedded-hal-02 = { package = "embedded-hal", version = "0.2.7", features = [ 25 | "unproven", 26 | ] } 27 | embedded-hal-1 = { version = "1.0.0-rc.2", package = "embedded-hal" } 28 | embedded-hal-nb = "1.0.0" 29 | embedded-hal-async = "1.0.0" 30 | 31 | 32 | embassy-sync = { version = "0.5.0", optional = true } 33 | embassy-time = { version = "0.3.0" } 34 | embassy-futures = "0.1.1" 35 | embassy-executor = { version = "0.5.0", features = [ 36 | # "nightly", 37 | "integrated-timers", 38 | "arch-riscv32", 39 | "executor-thread", 40 | ] } 41 | 42 | qingke = { version = "0.1.7", features = ["critical-section-impl"] } 43 | qingke-rt = { version = "0.1.7", features = ["highcode"] } 44 | panic-halt = "0.2.0" 45 | chrono = { version = "0.4.31", default-features = false } 46 | 47 | [features] 48 | default = ["ble", "embassy"] 49 | embassy = ["dep:embassy-sync"] 50 | ble = [] 51 | power_measure = [] 52 | 53 | [dev-dependencies] 54 | 55 | [profile.release] 56 | # panic = "unwind" 57 | panic = "abort" 58 | opt-level = "z" 59 | lto = true 60 | codegen-units = 1 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 今天是周五吗 2 | 3 | [bilibili视频介绍](https://www.bilibili.com/video/BV1gf8TerEiX) 4 | [MakerWorld地址](https://makerworld.com.cn/zh/models/375640) 5 | 6 | Top | Bottom 7 | -|- 8 | | 9 | 10 | 这是一个使用CH582F作为主控, 电子墨水屏为显示载体, 由一颗CR2032纽扣电池驱动的装置. 11 | 12 | 其本质上是一个日历, 但是加入了周五的判断, 使其成为一个周五检测器. 13 | 14 | `受即刻App的iOS小部件启发而来` 15 | 16 | 17 | ## PCB 预览 18 | Top | Bottom 19 | -|- 20 | | 21 | 22 | 23 | ## 基础参数 24 | 25 | ### 硬件参数 26 | * 主控 CH582F 32KB Ram + 448KB Flash 27 | * 时钟芯片 PCF8563T 28 | * 屏幕 1.54英寸墨水屏 SSD1607 29 | * DCDC芯片 SGM6603-3.3YN6G 30 | * 电源 CR2032电池 31 | 32 | #### 注: 33 | * 当使用屏幕驱动IC为SSD1607时候, **需要焊接SGM6603**, 因为需要借助SGM6603来彻底断开墨水屏供电 34 | * 这块屏幕是我从闲鱼上捡来的, 买回来发现和合宙9.9块钱的墨水屏丝印一样, 驱动IC也一样. 35 | * 在实际使用时候,休眠状态下功耗始终降低不下来. 于是我一怒之下怒一下了, 加了个DCDC来管理墨水屏的供电. 有效的将他的休眠电流从70μA降低到了3μA 36 | * 当使用屏幕驱动IC为SSD1681时候, **不需要焊接SGM6603**, 此时使用0欧电阻短接SGM6603的Pin5和Pin6, 并且无需焊接SGM6603下方4.7μH的电感 37 | * 这是目前中景园在售的黑白双色电子墨水屏, 显示效果比我在咸鱼上买的效果好很多. 虽然分辨率同为200x200,但就是效果清晰, 对比度也好, 缺点就是比我咸鱼上5块钱买的贵. 38 | * 由于能够正常休眠,所以不需要DCDC了, 功耗表现稍微比SSD1607版本好一丢丢 39 | 40 | ### 尺寸 41 | * 34mm×39mm×8mm 42 | 43 | ### 耗电信息 44 | * 休眠状态下≈3μA 45 | * 刷新 10~20mA, 刷新完毕后会立刻进入休眠 46 | 47 | 48 | ## 时间校准说明 49 | 开机时候长按按钮, 进入时间同步模式, 并在看见如下图后松开按钮 50 | 51 | 52 | 53 | 此时装置将会搜索周围的蓝牙广播.当搜索到符合约定格式的时间广播适合会自动重启. 54 | 55 | 时间广播格式为`'F' + 时间戳的16进制字符串 + 'R'`, 可以使用名为`Friday Ink时间同步`的小程序进行时间同步. 56 | 57 | 58 | 59 | 当进入时间同步模式后20s内无法搜索到符合要求的时间广播,会自动退出同步. 60 | 61 | 62 | ## 编译及烧录 63 | 推荐在Linux环境下进行编译, 这里我使用的是WSL2内的ubuntu子系统. 64 | 1. clone 本项目, cd进入后执行`git submodule update --init --recursive` 65 | 2. 安装Rust 66 | 3. 跟着[riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain)仓库的Release界面下载riscv32-elf-ubuntu-22.04-gcc-nightly,配置好环境变量 67 | 4. 根据你的MRS_Community配置u8g2_rs内的build.rs中头文件目录 68 | 5. 根据屏幕类型,修改main.rs中创建屏幕的参数 69 | 6. 执行`cargo build-hex`获得编译好的hex文件 70 | 7. 使用WCHISPStudio工具串口模式下载得到的hex文件 71 | 72 | 73 | ## 设定集 74 | 75 | 76 | 77 | 78 | 79 | ## Q&A 80 | 81 | ### Q: 时间校准时候卡在校准页怎么办 82 | ### A: 推荐提前发送时间广播, 让手机和装置足够接近. 然后装置进入校准, 并在发现20S后未能自动退出校准或按钮无法强制退出校准时, 取下电池, 重新安装电池. 83 | 84 | ## 已知问题 85 | * 时间校准有一定概率卡死在校准页 86 | * 考虑引入看门狗, 看门狗超时复位 87 | * FPC座HC-FPC-05-09-24RLTAG和我自己在淘宝买的不一样, 但是接触更加牢靠.代价是墨水屏排线超出PCB范围了 88 | 89 | ## 参考资料 90 | 91 | * [ch58x-hal](https://github.com/ch32-rs/ch58x-hal) 92 | * CH582F原理图参考azunya的[纽扣电池蓝牙温湿度计CR1220+CH592F+SHT40](https://oshwhub.com/azunya/ch592f-cr1220) 93 | * SSD1681的驱动来自pikot的[U8g2_Arduino](https://github.com/pikot/U8g2_Arduino.git) -------------------------------------------------------------------------------- /Firmware/u8g2_rs/font/u8g2_font_fusion_pixel_16_mn.c: -------------------------------------------------------------------------------- 1 | /* 2 | Fontname: -FreeType-Fusion Pixel 8px Monospaced zh_hans-Medium-R-Normal--16-160-72-72-P-139-ISO10646-1 3 | Copyright: Copyright (c) 2022, TakWolf (https://takwolf.com), with Reserved Font Name 'Fusion Pixel'. 4 | Glyphs: 105/20601 5 | BBX Build Mode: 0 6 | */ 7 | #include 8 | const uint8_t u8g2_font_fusion_pixel_16_mn[1436] U8G2_FONT_SECTION("u8g2_font_fusion_pixel_16_mn") = 9 | "i\0\3\3\4\4\3\5\6\16\16\0\376\14\376\14\376\1\177\2\360\4O \5\0\214(!\10\302\206" 10 | "(\36\204\4\42\10F\304(D\234\4#\17\306\204(D\244\303A\304\351p\20\221\4$\21\346t\250" 11 | "\204\242\303AD$\61\35BD%\0%\16\246\204(D$\241\222P$T\42\11&\17\306\204\250\204" 12 | "\42\21IQ$b\24\223\10'\7B\306(\16\1(\13\344v\250D$\375\243H\0)\14\344t(" 13 | "DB\221\376D\22\1*\15\206\224\250\204\242CI(\22\221\4+\13f\224\250\204\242CI(\2," 14 | "\11Dt\250D$\21\0-\7&\244(\16\5.\6\42\206(\10/\14\346t(u\22\352I\250\21" 15 | "\0\60\13\306\204\250H&\376d\42\1\61\13\306\204\250\204\42F}:\24\62\16\306\204(Hd\241&" 16 | "\241H(<\24\63\17\306\204(Hd\241H(\26*\232H\0\64\14\306\204(\225H&N\207\243\2" 17 | "\65\15\306\204(\36\205$\262P\321D\2\66\15\306\204\250HF\341\341\304\311D\2\67\13\306\204(\16" 18 | "E\235\204z\2\70\16\306\204\250H&\222\242H\304\311D\2\71\15\306\204\250H&N\207\243\320D\2" 19 | ":\10\202\206(\350\20\2;\13\244t\250\324\21E$\21\0<\15\246\204(\225\204\42\241X(\26\12" 20 | "=\12f\224(\16u\350\241\0>\17\246\204(\204b\241X(\22\212\204B\0\77\20\306\204\250\204\42" 21 | "\21I\250$\224#\11E\0@\15\306\204\250\204\42\21\351\360QL\42A\16\306\204\250\204\42\21\247\303" 22 | "A\304I\0B\17\306\204(H$\21\311D\22q\62\221\0C\12\306\204\250HF\375L\42D\14\306" 23 | "\204(H$\21\177\62\221\0E\14\306\204(\36\205\207\243\306C\1F\13\306\204(\36\205\207\243\36\1" 24 | "G\13\306\204\250HF\235\70\211\30H\14\306\204(D\234\16\7\21\237\4I\13\306\204(\16%\241~" 25 | ":\24J\13\306\204(\365#\211\244(\2K\14\306\204(D\234L$\21\237\4L\12\306\204(\204\372" 26 | "\307C\1M\13\306\204(D\244\303'>\11N\13\306\204(H$\21\377I\0O\14\306\204\250\204\42" 27 | "\21\177R\24\1P\16\306\204(H$\21'\23I\250\21\0Q\15\346t\250\204\42\21\177R\24\13\5" 28 | "R\16\306\204(H$\21'\23I\304I\0S\16\306\204\250HF\261P,T\64\221\0T\13\306\204" 29 | "(\16%\241\376\11\0U\12\306\204(D\374\247\303\1V\14\306\204(D\374\311D\22\12\1W\13\306" 30 | "\204(D|:|\42\11X\14\306\204(D\234\24\65\211\70\11Y\13\306\204(D|R\324\23\0Z" 31 | "\15\306\204(\16E%\241&\241\360P[\13\344v(\16#\375\323!\0\134\13\346t(\204\232\205z" 32 | "\26j]\13\344t(\16!\375\323a\0^\12F\304\250\204\42\21I\0_\7&t(\16\5`\11" 33 | "D\306(DB\221\0a\12\206\204\250H&N\42\6b\16\306\204(\204\32I$\21'\23\11\0c" 34 | "\12\206\204\250HF\315$\2d\13\306\204(u\42\231\70\211\30e\13\206\204\250H&\222\211H\42f" 35 | "\15\306\204(\225\204\242CI\250'\0g\15\246t\250H&\222\210Qh\42\1h\14\306\204(\204\32" 36 | "I$\21\237\4i\15\306\204\250\204r\70\211\250\323\241\0j\15\346t(\325\241\207\242\36M$\0k" 37 | "\15\306\204(\204:\221L$\21'\1l\12\306\204(HD\375\221Dm\13\206\204(H\244\303C\211" 38 | "$n\12\206\204(H$\21\237\4o\14\206\204\250\204\42\21'E\21\0p\15\246t(H$\21'" 39 | "\23I(\4q\13\246t\250H&N\42F\5r\14\206\204(D$\23I\250\21\0s\15\206\204\250" 40 | "H\207\20\221t\10\221\0t\14\246\204\250\204\242CI\250Y(u\11\206\204(D|\22\61v\14\206" 41 | "\204(D\234L$\241\20\0w\12\206\204(D\234\16\17\5x\14\206\204(D$E\221\210\223\0y" 42 | "\15\246t(D\234D\214B\23\11\0z\14\206\204(\16%\241H(<\24{\16\346t(\225\204\232" 43 | "\204b\241f\241\0|\6\342v(~}\17\346t(\204b\241f\241H\250I(\4~\11F\244(" 44 | "HD\22\1\0\0\0\4\377\377N\15\36\356t\60~\207\310\241r\230\34*\207\221\304$\21Ih\22" 45 | "\212\345P\71T\16\25\3N\224\36\356t\260\16\303\303\34\42\207\312\241r\250\34r\30\36\306B\261P" 46 | ",\24\13E\207\17N\312\35\356t\60\355\20\263\242X$\24\22\325!r\250\370\60<\314\241r\250\330" 47 | "\16\61\2T\27 \356t\260\355\20\223Qd\24\211HJ$\245C\351P\22\223\304&\222\211$\207\311" 48 | "\241\42\0Th!\356t\260\16\245CIQ\244(:\204D\207\220F\221\242\350P:\224HJ$\21" 49 | "\311D\62\11Y) \356t\260\16\303\303\34\42\207\212\17\237\345P\71L$\207\210\344\20\241X(\242" 50 | "C\354\20\1^t\36\356t\260\344P\71\364P:\34\305B\71\344\60<\14Er\210H|\370,\207" 51 | "\212\1e\345\31\354v\60>\324!t\10\35B\207\34\276C\350\20:\204\16\71<\24f/\37\356t" 52 | "\260\16\303\303P,\24\213\16\237\345P\71Dd\24\31Er\210H,:\224\16\3g\10 \356t\60" 53 | "\17\303\303P,\24\13\17\303\303P,\24\13\17\303\303H\16\21\311!t\210\35B\0"; 54 | -------------------------------------------------------------------------------- /Firmware/u8g2_rs/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | fn main() { 4 | let src = [ 5 | // "u8g2/csrcmui_u8g2.c", 6 | // "u8g2/csrcmui.c", 7 | "u8g2/csrc/u8g2_arc.c", 8 | "u8g2/csrc/u8g2_bitmap.c", 9 | "u8g2/csrc/u8g2_box.c", 10 | "u8g2/csrc/u8g2_buffer.c", 11 | "u8g2/csrc/u8g2_button.c", 12 | "u8g2/csrc/u8g2_circle.c", 13 | "u8g2/csrc/u8g2_cleardisplay.c", 14 | "u8g2/csrc/u8g2_d_memory.c", 15 | "u8g2/csrc/u8g2_d_setup.c", 16 | "u8g2/csrc/u8g2_font.c", 17 | "u8g2/csrc/u8g2_fonts.c", 18 | "u8g2/csrc/u8g2_hvline.c", 19 | "u8g2/csrc/u8g2_input_value.c", 20 | "u8g2/csrc/u8g2_intersection.c", 21 | "u8g2/csrc/u8g2_kerning.c", 22 | "u8g2/csrc/u8g2_line.c", 23 | "u8g2/csrc/u8g2_ll_hvline.c", 24 | "u8g2/csrc/u8g2_message.c", 25 | "u8g2/csrc/u8g2_polygon.c", 26 | "u8g2/csrc/u8g2_selection_list.c", 27 | "u8g2/csrc/u8g2_setup.c", 28 | "u8g2/csrc/u8g2.h", 29 | "u8g2/csrc/u8log_u8g2.c", 30 | "u8g2/csrc/u8log_u8x8.c", 31 | "u8g2/csrc/u8log.c", 32 | "u8g2/csrc/u8x8_8x8.c", 33 | "u8g2/csrc/u8x8_byte.c", 34 | "u8g2/csrc/u8x8_cad.c", 35 | "u8g2/csrc/u8x8_capture.c", 36 | "u8g2/csrc/u8x8_d_ssd1607_200x200.c", 37 | "u8g2/csrc/u8x8_d_ssd1681_200x200.c", 38 | "u8g2/csrc/u8x8_debounce.c", 39 | "u8g2/csrc/u8x8_display.c", 40 | "u8g2/csrc/u8x8_fonts.c", 41 | "u8g2/csrc/u8x8_gpio.c", 42 | "u8g2/csrc/u8x8_input_value.c", 43 | "u8g2/csrc/u8x8_message.c", 44 | "u8g2/csrc/u8x8_selection_list.c", 45 | "u8g2/csrc/u8x8_setup.c", 46 | "u8g2/csrc/u8x8_string.c", 47 | "u8g2/csrc/u8x8_u8toa.c", 48 | "u8g2/csrc/u8x8_u16toa.c", 49 | "font/u8g2_font_fusion_pixel_16_mn.c", 50 | ]; 51 | let stdint_path = "/mnt/c/MounRiver/MRS_Community/toolchain/toolchain/riscv-none-embed/include"; // 根据你的输出调整此路径 52 | let stdarg_path = 53 | "/mnt/c/MounRiver/MRS_Community/toolchain/toolchain/lib/gcc/riscv-none-embed/8.2.0/include"; // 根据你的输出调整此路径 54 | let limits_path = "/mnt/c/MounRiver/MRS_Community/toolchain/toolchain/lib/gcc/riscv-none-embed/8.2.0/include-fixed"; // 根据你的输出调整此路径 55 | // C:\MounRiver\MRS_Community\toolchain\toolchain\lib\gcc\riscv-none-embed\8.2.0\include 56 | let mut builder = cc::Build::new(); 57 | let build = builder 58 | .include("u8g2/csrc") 59 | .include("font") 60 | .files(src.iter()) 61 | .static_flag(true); 62 | 63 | build.compile("u8g2_rs"); 64 | let bindings = bindgen::Builder::default() 65 | .clang_arg(format!("-I{}", stdint_path)) 66 | .clang_arg(format!("-I{}", stdarg_path)) 67 | .clang_arg(format!("-I{}", limits_path)) 68 | .clang_arg("--target=riscv32") 69 | .clang_arg("-march=rv32imac") 70 | .clang_arg("-mabi=ilp32") 71 | .clang_arg("-mcmodel=medany") 72 | .header("u8g2/csrc/u8g2.h") 73 | .header("font/font.h") 74 | .clang_arg("-I./u8g2/csrc") 75 | .clang_arg("-I./font") 76 | .use_core() 77 | .generate() 78 | .expect("Unable to generate bindings"); 79 | 80 | // 3. 将绑定文件写入到指定位置 81 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 82 | bindings 83 | .write_to_file(out_path.join("bindings.rs")) 84 | .expect("Couldn't write bindings!"); 85 | } 86 | 87 | // riscv-none-embed-gcc -march=rv32imac -mabi=ilp32 -mcmodel=medany -msmall-data-limit=8 -Os -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -g -DDEBUG=1 -I".StdPeriphDriver/inc" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB\Port" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\HAL\include" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB\u8g2\csrc" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB\u8g2" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB\softwire" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB\easylogger\inc" -I"E:\GitHub\WeActStudio.WCH-BLE-Core\Examples\CH582\FridayInk\LIB" -I".RVMSIS" -std=gnu99 -MMD -MP -MF"APP/FridayInk.d" -MT"APP/FridayInk.o" -c -o"APP/FridayInk.o"".APP/FridayInk.c" 88 | -------------------------------------------------------------------------------- /Firmware/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(stmt_expr_attributes)] 4 | 5 | extern crate u8g2_rs; 6 | 7 | use ch58x_hal::ble::ffi::TMOS_SystemProcess; 8 | use ch58x_hal::gpio::{Input, Level, Output, OutputDrive, Pin, Pull}; 9 | use ch58x_hal::peripherals; 10 | use ch58x_hal::{println, uart::UartTx}; 11 | use embassy_executor::Spawner; 12 | use embassy_time::{Duration, Timer}; 13 | use friday_rs::bluetooth::{observer_task, observer_task_init, observer_timeout_task}; 14 | use friday_rs::display::{u8x8_byte_ch582f_hw_spi, u8x8_gpio_and_delay_ch582f, Display, DriverIC}; 15 | use friday_rs::rtc::set_default_rtc; 16 | use friday_rs::softwire::SoftwareI2C; 17 | use friday_rs::{config, power, rtc}; 18 | 19 | #[panic_handler] 20 | fn panic(info: &core::panic::PanicInfo) -> ! { 21 | use core::fmt::Write; 22 | 23 | let pa9 = unsafe { peripherals::PA9::steal() }; 24 | let uart1 = unsafe { peripherals::UART1::steal() }; 25 | let mut serial = UartTx::new(uart1, pa9, Default::default()).unwrap(); 26 | 27 | let _ = writeln!(&mut serial, "panic\n\n\n{}", info); 28 | 29 | loop {} 30 | } 31 | 32 | enum FridayMode { 33 | Normal, 34 | TimePair, 35 | } 36 | 37 | fn print_embassy_logo() { 38 | println!("\n\nHello World from ch58x-hal!"); 39 | println!( 40 | r#" 41 | ______ __ 42 | / ____/___ ___ / /_ ____ _____________ __ 43 | / __/ / __ `__ \/ __ \/ __ `/ ___/ ___/ / / / 44 | / /___/ / / / / / /_/ / /_/ (__ |__ ) /_/ / 45 | /_____/_/ /_/ /_/_.___/\__,_/____/____/\__, / 46 | /____/ on CH582F"# 47 | ); 48 | println!("System Clocks: {}", ch58x_hal::sysctl::clocks().hclk); 49 | println!("ChipID: 0x{:02x}", ch58x_hal::signature::get_chip_id()); 50 | } 51 | 52 | #[embassy_executor::main(entry = "qingke_rt::entry")] 53 | async fn main(spawner: Spawner) -> ! { 54 | let mut config = ch58x_hal::Config::default(); 55 | config.clock.use_pll_48mhz().enable_lse(); 56 | let p = ch58x_hal::init(config); 57 | ch58x_hal::embassy::init(); 58 | let uart = UartTx::new(p.UART1, p.PA9, Default::default()).unwrap(); 59 | unsafe { 60 | ch58x_hal::set_default_serial(uart); 61 | } 62 | print_embassy_logo(); 63 | 64 | let wake_up_btn = Input::new(unsafe { peripherals::PB4::steal().degrade() }, Pull::Up); 65 | let scl: u8 = p.PA13.pin() + p.PA13.port() * 32; 66 | let sda_pin: u8 = p.PA12.pin() + p.PA13.port() * 32; 67 | 68 | static mut SOFTI2C: Option = None; 69 | static mut RTC_INSTANCE: Option = None; 70 | 71 | unsafe { 72 | SOFTI2C = Some(SoftwareI2C::new(sda_pin, scl)); 73 | if let Some(ref mut i2c) = SOFTI2C { 74 | RTC_INSTANCE = Some(rtc::PCF8563::new(i2c)); 75 | } 76 | set_default_rtc(RTC_INSTANCE.as_mut().unwrap()); 77 | } 78 | 79 | // 按需选择屏幕驱动 80 | let mut display = Display::new( 81 | DriverIC::SSD1607, 82 | Some(u8x8_byte_ch582f_hw_spi), 83 | Some(u8x8_gpio_and_delay_ch582f), 84 | ); 85 | 86 | display.init(); 87 | display.set_power_save(false); 88 | let rtc = rtc::take(); 89 | if cfg!(feature = "power_measure") { 90 | let mut now = rtc.now().unwrap(); 91 | if now.minute == 59 { 92 | rtc::take().set_alarm(0, 0, now.hour + 1, 0).unwrap(); 93 | } else { 94 | rtc::take() 95 | .set_alarm(0, 0, now.hour, now.minute + 1) 96 | .unwrap(); 97 | } 98 | } else { 99 | rtc.set_alarm(0, 0, config::ALARM_HOUR, config::ALARM_MINUTE) 100 | .unwrap(); 101 | } 102 | let alarm = rtc.check_alarm(); 103 | if let Ok(alarming) = alarm { 104 | if alarming { 105 | rtc.clear_alarm().unwrap(); 106 | println!("wake up by ALARM!"); 107 | } else { 108 | println!("wake up by PIN!"); 109 | } 110 | } 111 | let mut boot_mode: FridayMode = FridayMode::Normal; 112 | if wake_up_btn.is_low() { 113 | Timer::after(Duration::from_millis(config::DEBOUNCE_TIME)).await; 114 | boot_mode = if wake_up_btn.is_low() { 115 | FridayMode::TimePair 116 | } else { 117 | FridayMode::Normal 118 | } 119 | } 120 | 121 | let now = rtc.now().unwrap(); 122 | 123 | match boot_mode { 124 | FridayMode::TimePair => { 125 | println!("FridayMode::TimePair @ {:?}", now); 126 | display.embassy_logo(); 127 | } 128 | FridayMode::Normal => { 129 | println!("FridayMode::Normal @ {:?}", now); 130 | display.is_friday(now); 131 | } 132 | } 133 | // waiting for epd draw done. 134 | // ch58x_hal::delay_ms(5000u16); 135 | display.set_power_save(true); 136 | // ch58x_hal::delay_ms(10u16); 137 | match boot_mode { 138 | FridayMode::Normal => { 139 | println!("Enter SLEEP"); 140 | power::wake_up_cfg(); 141 | power::low_power_shutdown(0); 142 | } 143 | _ => { 144 | observer_task_init(); 145 | let _ = spawner.spawn(observer_timeout_task()); 146 | let _ = spawner.spawn(observer_task()); 147 | } 148 | } 149 | 150 | loop { 151 | Timer::after(Duration::from_micros(300)).await; 152 | unsafe { 153 | TMOS_SystemProcess(); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Firmware/src/power.rs: -------------------------------------------------------------------------------- 1 | use ch58x::ch58x; 2 | use ch58x_hal::{isp::flash_rom_reset, with_safe_access}; 3 | use qingke::riscv; 4 | use qingke_rt::highcode; 5 | 6 | use crate::{gpio::{GPIO_PIN_22, GPIO_PIN_23, GPIO_PIN_4}, regs}; 7 | #[derive(Clone, Copy)] 8 | pub enum SysClk { 9 | ClkSourceLsi = 0x00, 10 | ClkSourceLse = 0x01, 11 | ClkSourceHse16mhz = 0x22, 12 | ClkSourceHse8mhz = 0x24, 13 | ClkSourceHse6_4mhz = 0x25, 14 | ClkSourceHse4mhz = 0x28, 15 | ClkSourceHse2mhz = (0x20 | 16), 16 | ClkSourceHse1mhz = (0x20 | 0), 17 | ClkSourcePll80mhz = 0x46, 18 | ClkSourcePll60mhz = 0x48, 19 | ClkSourcePll48mhz = (0x40 | 10), 20 | ClkSourcePll40mhz = (0x40 | 12), 21 | ClkSourcePll36_9mhz = (0x40 | 13), 22 | ClkSourcePll32mhz = (0x40 | 15), 23 | ClkSourcePll30mhz = (0x40 | 16), 24 | ClkSourcePll24mhz = (0x40 | 20), 25 | ClkSourcePll20mhz = (0x40 | 24), 26 | ClkSourcePll15mhz = (0x40 | 0), 27 | } 28 | 29 | fn set_sys_clock(sysclk: SysClk) { 30 | /* 31 | R8_SAFE_ACCESS_SIG = SAFE_ACCESS_SIG1; 32 | R8_SAFE_ACCESS_SIG = SAFE_ACCESS_SIG2; 33 | SAFEOPERATE; 34 | R8_PLL_CONFIG &= ~(1 << 5); // 35 | R8_SAFE_ACCESS_SIG = 0; 36 | */ 37 | with_safe_access(|| unsafe { 38 | let sys = ch58x::SYS::steal(); 39 | sys.pll_config().modify(|r, w| w.bits(r.bits() & !(1 << 5))); 40 | }); 41 | 42 | if (sysclk as u8 & 0x20) != 0 { 43 | let sys = unsafe { ch58x::SYS::steal() }; 44 | if sys.hfck_pwr_ctrl().read().clk_xt32m_pon().bit_is_clear() { 45 | with_safe_access(|| unsafe { 46 | sys.hfck_pwr_ctrl() 47 | .modify(|_r, w| w.clk_xt32m_pon().set_bit()); 48 | for _ in 0..=1200 { 49 | riscv::asm::nop(); 50 | riscv::asm::nop(); 51 | } 52 | }) 53 | } 54 | with_safe_access(|| unsafe { 55 | let sys = ch58x::SYS::steal(); 56 | sys.clk_sys_cfg() 57 | .modify(|_r, w| w.bits((0 << 6) | sysclk as u16 & 0x1f)); 58 | riscv::asm::nop(); 59 | riscv::asm::nop(); 60 | riscv::asm::nop(); 61 | riscv::asm::nop(); 62 | }); 63 | with_safe_access(|| unsafe { 64 | let sys = ch58x::SYS::steal(); 65 | sys.flash_cfg().write(|w| w.bits(0x51)) 66 | }); 67 | } else if (sysclk as u8 & 0x40) != 0 { 68 | let sys = unsafe { ch58x::SYS::steal() }; 69 | if sys.hfck_pwr_ctrl().read().clk_pll_pon().bit_is_clear() { 70 | with_safe_access(|| unsafe { 71 | sys.hfck_pwr_ctrl() 72 | .modify(|_r, w| w.clk_pll_pon().set_bit()); 73 | for _ in 0..=2000 { 74 | riscv::asm::nop(); 75 | riscv::asm::nop(); 76 | } 77 | }) 78 | } 79 | with_safe_access(|| unsafe { 80 | sys.clk_sys_cfg() 81 | .modify(|_r, w| w.bits((1 << 6) | sysclk as u16 & 0x1f)); 82 | riscv::asm::nop(); 83 | riscv::asm::nop(); 84 | riscv::asm::nop(); 85 | riscv::asm::nop(); 86 | }); 87 | with_safe_access(|| unsafe { 88 | match sysclk { 89 | SysClk::ClkSourcePll80mhz => sys.flash_cfg().write(|w| w.bits(0x02)), 90 | _ => sys.flash_cfg().write(|w| w.bits(0x52)), 91 | } 92 | }); 93 | } else { 94 | let sys = unsafe { ch58x::SYS::steal() }; 95 | with_safe_access(|| unsafe { 96 | sys.clk_sys_cfg() 97 | .modify(|r, w| w.bits(r.bits() | regs::RB_CLK_SYS_MOD as u16)); 98 | }) 99 | } 100 | with_safe_access(|| unsafe { 101 | let sys = ch58x::SYS::steal(); 102 | sys.pll_config().modify(|r, w| w.bits(r.bits() | 1 << 7)); 103 | }) 104 | } 105 | 106 | #[highcode] 107 | pub fn low_power_shutdown(rm: u8) { 108 | let sys = unsafe { ch58x::SYS::steal() }; 109 | let pfic = unsafe { ch58x::PFIC::steal() }; 110 | 111 | flash_rom_reset(); 112 | 113 | with_safe_access(|| unsafe { 114 | // 关闭电压监控 115 | sys.bat_det_ctrl().write(|w| w.bits(0)); 116 | if sys.rtc_cnt_32k().read().bits() > 0x3fff { 117 | // 超过500ms 118 | // x32Kpw = (x32Kpw & 0xfc) | 0x01; // LSE驱动电流降低到额定电流 119 | sys.xt32k_tune() 120 | .modify(|r, w| w.bits((r.bits() & 0xfc) | 0x01)); 121 | } 122 | sys.xt32m_tune() 123 | .modify(|r, w| w.bits((r.bits() & 0xfc) | 0x03)); 124 | // x32Mpw = (x32Mpw & 0xfc) | 0x03; // 150%额定电流 125 | }); 126 | 127 | set_sys_clock(SysClk::ClkSourceHse6_4mhz); 128 | 129 | // deep sleep 130 | pfic.sctlr() 131 | .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 2)) }); 132 | with_safe_access(|| { 133 | sys.slp_power_ctrl() 134 | .modify(|_r, w| w.ram_ret_lv().set_bit()); 135 | sys.power_plan().modify(|_r, w| unsafe { 136 | w.bits(regs::RB_PWR_PLAN_EN as u16 | regs::RB_PWR_MUST_0010 as u16 | rm as u16) 137 | }); 138 | }); 139 | with_safe_access(|| { 140 | sys.ck32k_config() 141 | .modify(|_r, w| w.clk_int32k_pon().set_bit()) 142 | }); 143 | unsafe { 144 | pfic.sctlr().modify(|r, w| w.bits(r.bits() & !(1 << 3))); 145 | riscv::asm::wfi(); 146 | riscv::asm::nop(); 147 | riscv::asm::nop(); 148 | } 149 | flash_rom_reset(); 150 | with_safe_access(|| { 151 | sys.rst_wdog_ctrl() 152 | .modify(|_r, w| w.software_reset().set_bit()) 153 | }); 154 | } 155 | 156 | pub fn wake_up_cfg() { 157 | let sys = unsafe { ch58x::SYS::steal() }; 158 | let pfic = unsafe { ch58x::PFIC::steal() }; 159 | // 0x00000010 160 | let pin: u16 = (GPIO_PIN_4 | ((GPIO_PIN_4 & (GPIO_PIN_22 | GPIO_PIN_23)) >> 14)) as u16; 161 | // GPIOB_ITModeCfg(GPIO_Pin_1, GPIO_ITMode_FallEdge); /* 下降沿唤醒 */ 162 | sys.pb_int_mode() 163 | .modify(|r, w| unsafe { w.bits(r.bits() | pin) }); 164 | sys.pb_clr() 165 | .modify(|r, w| unsafe { w.bits(r.bits() | pin as u32) }); 166 | sys.pb_int_if().modify(|r, w| unsafe { w.bits(pin) }); 167 | sys.pb_int_en().modify(|r, w| unsafe { w.bits(pin) }); 168 | pfic.ienr2() 169 | .write(|w| unsafe { w.bits(1 << ((18u32) & 0x1F)) }); 170 | with_safe_access(|| unsafe { 171 | let m = 0x00; 172 | riscv::asm::nop(); 173 | sys.slp_wake_ctrl() 174 | .modify(|r, w| w.bits(r.bits() | regs::RB_WAKE_EV_MODE | regs::RB_SLP_GPIO_WAKE)); 175 | sys.slp_power_ctrl().modify(|r, w| w.bits(r.bits() | m)); 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /Firmware/src/softwire.rs: -------------------------------------------------------------------------------- 1 | use ch58x_hal::gpio::{AnyPin, Input, Level, Output, OutputDrive}; 2 | use ch58x_hal::{i2c, println}; 3 | use core::cell::RefCell; 4 | use embedded_hal_02::blocking::i2c::{Read, Write, WriteRead}; 5 | use embedded_hal_1::i2c::{AddressMode, SevenBitAddress}; 6 | 7 | enum SDA<'a> { 8 | OutputMode(Output<'a, AnyPin>), 9 | InputMode(Input<'a, AnyPin>), 10 | } 11 | 12 | pub struct SoftwareI2C { 13 | sda: RefCell>, 14 | sda_pin: u8, 15 | scl: RefCell>, 16 | } 17 | 18 | impl SoftwareI2C { 19 | pub fn new(sda: u8, scl: u8) -> Self { 20 | println!("SoftwareI2C @ scl:{}, sda:{}", scl, sda); 21 | unsafe { 22 | let _sda: SDA = SDA::OutputMode(Output::new( 23 | AnyPin::steal(sda), 24 | Level::Low, 25 | OutputDrive::_5mA, 26 | )); 27 | let scl = Output::new(AnyPin::steal(scl), Level::Low, OutputDrive::_5mA); 28 | 29 | SoftwareI2C { 30 | sda: RefCell::new(_sda), 31 | sda_pin: sda, 32 | scl: RefCell::new(scl), 33 | } 34 | } 35 | } 36 | 37 | fn set_scl_high(&self) { 38 | self.scl.borrow_mut().set_high() 39 | } 40 | 41 | fn set_scl_low(&self) { 42 | self.scl.borrow_mut().set_low() 43 | } 44 | 45 | fn set_sda_high(&self) { 46 | let mut sda = self.sda.borrow_mut(); 47 | match &mut *sda { 48 | SDA::OutputMode(output) => { 49 | output.set_high(); 50 | } 51 | SDA::InputMode(_) => unsafe { 52 | let mut output = 53 | Output::new(AnyPin::steal(self.sda_pin), Level::High, OutputDrive::_5mA); 54 | output.set_high(); 55 | *sda = SDA::OutputMode(output); 56 | }, 57 | }; 58 | } 59 | 60 | fn set_sda_low(&self) { 61 | let mut sda = self.sda.borrow_mut(); 62 | match &mut *sda { 63 | SDA::OutputMode(output) => { 64 | output.set_low(); 65 | } 66 | SDA::InputMode(_) => unsafe { 67 | let output = 68 | Output::new(AnyPin::steal(self.sda_pin), Level::Low, OutputDrive::_5mA); 69 | *sda = SDA::OutputMode(output); 70 | }, 71 | }; 72 | } 73 | 74 | fn read_sda(&self) -> bool { 75 | let mut sda = self.sda.borrow_mut(); 76 | match &mut *sda { 77 | SDA::OutputMode(output) => { 78 | output.set_low(); 79 | let to_input = 80 | unsafe { Input::new(AnyPin::steal(self.sda_pin), ch58x_hal::gpio::Pull::Up) }; 81 | let res = to_input.is_high(); 82 | *sda = SDA::InputMode(to_input); 83 | res 84 | } 85 | SDA::InputMode(input) => input.is_high(), 86 | } 87 | } 88 | 89 | fn delay() { 90 | ch58x_hal::delay_us(20); 91 | } 92 | 93 | fn start_condition(&mut self) { 94 | self.set_scl_high(); 95 | self.set_sda_high(); 96 | Self::delay(); 97 | self.set_sda_low(); 98 | Self::delay(); 99 | self.set_scl_low(); 100 | Self::delay(); 101 | } 102 | 103 | fn stop_condition(&mut self) { 104 | self.set_sda_low(); 105 | self.set_scl_high(); 106 | Self::delay(); 107 | self.set_sda_high(); 108 | Self::delay(); 109 | } 110 | 111 | fn write_bit(&self, bit: bool) { 112 | if bit { 113 | self.set_sda_high(); 114 | } else { 115 | self.set_sda_low(); 116 | } 117 | Self::delay(); 118 | self.set_scl_high(); 119 | Self::delay(); 120 | self.set_scl_low(); 121 | Self::delay(); 122 | } 123 | 124 | fn read_bit(&self) -> bool { 125 | // self.set_sda_high(); 126 | self.read_sda(); 127 | self.set_scl_low(); 128 | Self::delay(); 129 | self.set_scl_high(); 130 | Self::delay(); 131 | let bit = self.read_sda(); 132 | Self::delay(); 133 | self.set_scl_low(); 134 | Self::delay(); 135 | bit 136 | } 137 | 138 | fn write_byte(&self, byte: u8) -> bool { 139 | for i in 0..8 { 140 | self.write_bit((byte & (1 << (7 - i))) != 0); 141 | } 142 | // Read ACK/NACK 143 | !self.read_bit() 144 | } 145 | 146 | fn read_byte(&self, ack: bool) -> u8 { 147 | let mut byte = 0; 148 | for i in 0..8 { 149 | if self.read_bit() { 150 | byte |= 1 << (7 - i); 151 | } 152 | } 153 | self.write_bit(!ack); 154 | byte 155 | } 156 | } 157 | 158 | impl Write for SoftwareI2C { 159 | type Error = i2c::Error; 160 | /// 写入数据 161 | /// - addr 设备地址 162 | /// - bytes 写入数据 = \[reg, data\] 163 | fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { 164 | self.start_condition(); 165 | if !self.write_byte(addr) { 166 | self.stop_condition(); 167 | return Err(i2c::Error::Nack); 168 | } 169 | for &byte in bytes { 170 | if !self.write_byte(byte) { 171 | self.stop_condition(); 172 | return Err(i2c::Error::Nack); 173 | } 174 | } 175 | self.stop_condition(); 176 | Ok(()) 177 | } 178 | } 179 | 180 | impl WriteReg for SoftwareI2C { 181 | type Error = i2c::Error; 182 | fn write_reg(&mut self, addr: u8, reg: u8, bytes: &[u8]) -> Result<(), Self::Error> { 183 | self.start_condition(); 184 | if !self.write_byte(addr) { 185 | self.stop_condition(); 186 | return Err(i2c::Error::Nack); 187 | } 188 | if !self.write_byte(reg) { 189 | self.stop_condition(); 190 | return Err(i2c::Error::Nack); 191 | } 192 | for &byte in bytes { 193 | if !self.write_byte(byte) { 194 | self.stop_condition(); 195 | return Err(i2c::Error::Nack); 196 | } 197 | } 198 | self.stop_condition(); 199 | Ok(()) 200 | } 201 | } 202 | 203 | impl Read for SoftwareI2C { 204 | type Error = i2c::Error; 205 | /// 读取 206 | /// - addr 设备地址 207 | /// - buffer 读取到的内容 208 | fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 209 | self.start_condition(); 210 | if !self.write_byte(addr) { 211 | self.stop_condition(); 212 | return Err(i2c::Error::Nack); 213 | } 214 | let mut i = 0; 215 | let len = buffer.len(); 216 | for byte in buffer.iter_mut() { 217 | *byte = self.read_byte(i != len - 1); 218 | i += 1; 219 | } 220 | self.stop_condition(); 221 | Ok(()) 222 | } 223 | } 224 | 225 | impl WriteRead for SoftwareI2C { 226 | type Error = i2c::Error; 227 | /// 写入然后读取 228 | /// - addr 设备地址 229 | /// - bytes 数据内容 = \[reg, data\] 230 | /// - buffer 读取到的内容 231 | fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { 232 | self.write(addr, bytes)?; 233 | self.read(addr | 1, buffer) 234 | } 235 | } 236 | 237 | pub trait WriteReg { 238 | /// Error type 239 | type Error; 240 | /// 写入数据 241 | /// - addr 设备地址 242 | /// - reg 寄存器地址 243 | /// - bytes 数据内容 244 | fn write_reg(&mut self, addr: A, reg: u8, bytes: &[u8]) -> Result<(), Self::Error>; 245 | } 246 | -------------------------------------------------------------------------------- /Firmware/src/rtc.rs: -------------------------------------------------------------------------------- 1 | use core::cell::RefCell; 2 | 3 | use crate::softwire::SoftwareI2C; 4 | use crate::softwire::WriteReg; 5 | use ch58x_hal::{ 6 | i2c, 7 | prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead}, 8 | println, 9 | }; 10 | 11 | const PCF8563_ADDR: u8 = 0xA2; 12 | const PCF8563_IS_RUNNING_FLAG: u8 = 0b00100000; 13 | pub struct PCF8563 { 14 | i2c: RefCell<&'static mut SoftwareI2C>, 15 | addr: u8, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct Time { 20 | pub year: u8, 21 | pub month: u8, 22 | pub day: u8, 23 | pub hour: u8, 24 | pub minute: u8, 25 | pub second: u8, 26 | pub week: u8, 27 | } 28 | 29 | #[allow(unused)] 30 | mod regs { 31 | pub const PCF8563_CLKOUTCONTROL: u8 = 0x0d; // Bit 7 PWR_MODE, bits 6:1 XG_OFFS_TC, bit 0 OTP_BNK_VLD 32 | pub const PCF8563_CONTROL_1: u8 = 0x0; //< Control and status register 1 33 | pub const PCF8563_CONTROL_2: u8 = 0x1; //< Control and status register 2 34 | pub const PCF8563_VL_SECONDS: u8 = 0x02; //< register address for VL_SECONDS 35 | pub const PCF8563_CLKOUT_MASK: u8 = 0x83; //< bitmask for SqwPinMode on CLKOUT pin 36 | pub const PCF8563_HOUR_ALARM: u8 = 0x0a; // 闹钟小时寄存器地址, 值取低6位, 取值范围 0~24 37 | pub const PCF8563_MINUTE_ALARM: u8 = 0x09; // 闹钟分钟寄存器地址, 值取低5位, 取值范围0~59 38 | pub const PCF8563_DAY_ALARM: u8 = 0x0b; // 闹钟天寄存器地址, 值取低5位, 取值范围0~59 39 | pub const PCF8563_WEEKDAY_ALARM: u8 = 0x0c; // 闹钟星期寄存器地址, 值取低5位, 取值范围0~59 40 | } 41 | 42 | impl PCF8563 { 43 | pub fn new(i2c: &'static mut SoftwareI2C) -> Self { 44 | Self { 45 | i2c: RefCell::new(i2c), 46 | addr: PCF8563_ADDR, 47 | } 48 | } 49 | 50 | pub fn start(&mut self) -> Result<(), i2c::Error> { 51 | println!("PCF8563 start"); 52 | let mut buffer = [0u8; 1]; 53 | self.read_byte(regs::PCF8563_CONTROL_1, &mut buffer)?; 54 | if (buffer[0] & PCF8563_IS_RUNNING_FLAG) == 0 { 55 | return Err(i2c::Error::Bus); 56 | } 57 | if buffer[0] & (1 << 5) != 0 { 58 | self.write_byte(regs::PCF8563_CONTROL_1, buffer[0] & !(1 << 5)) 59 | } else { 60 | Ok(()) 61 | } 62 | } 63 | 64 | pub fn set_time(&mut self, time: Time) -> Result<(), i2c::Error> { 65 | let mut buffer = [0u8; 7]; 66 | buffer[0] = Self::bin_to_bcd(time.second); 67 | buffer[1] = Self::bin_to_bcd(time.minute); 68 | buffer[2] = Self::bin_to_bcd(time.hour); 69 | buffer[3] = Self::bin_to_bcd(time.day); 70 | buffer[4] = Self::bin_to_bcd(time.week); 71 | buffer[5] = Self::bin_to_bcd(time.month); 72 | buffer[6] = Self::bin_to_bcd(time.year); 73 | self.write_bytes(regs::PCF8563_VL_SECONDS, &mut buffer) 74 | } 75 | 76 | pub fn now(&self) -> Result { 77 | println!("PCF8563 now()"); 78 | let mut buffer = [0u8; 7]; 79 | self.read_byte(regs::PCF8563_VL_SECONDS, &mut buffer)?; 80 | let second = Self::bcd_to_bin(buffer[0] & 0x7F); // 忽略最高位VL位 81 | let minute = Self::bcd_to_bin(buffer[1] & 0x7F); 82 | let hour = Self::bcd_to_bin(buffer[2] & 0x3F); // 忽略最高两位 83 | let day = Self::bcd_to_bin(buffer[3] & 0x3F); // 忽略最高位 84 | let week = Self::bcd_to_bin(buffer[4] & 0x07); // 只取最低3位 85 | let month = Self::bcd_to_bin(buffer[5] & 0x1F); // 忽略最高位 86 | let year = Self::bcd_to_bin(buffer[6]); // 读取年份并加上2000 87 | 88 | Ok(Time { 89 | year, 90 | month, 91 | day, 92 | hour, 93 | minute, 94 | second, 95 | week, 96 | }) 97 | } 98 | 99 | pub fn check_alarm(&mut self) -> Result { 100 | let mut alarm: u8; 101 | let mut buffer = [0u8; 1]; 102 | self.read_byte(regs::PCF8563_CONTROL_2, &mut buffer)?; 103 | alarm = buffer[0]; 104 | alarm = alarm & 8; // isolate the alarm flag bit 105 | return Ok(alarm == 8); 106 | } 107 | 108 | pub fn set_alarm(&mut self, week: u8, day: u8, hour: u8, minute: u8) -> Result<(), i2c::Error> { 109 | println!("set alarm"); 110 | let mut buffer = [0u8; 4]; 111 | buffer[0] = Self::bin_to_bcd(minute); 112 | buffer[1] = Self::bin_to_bcd(hour); 113 | buffer[2] = Self::bin_to_bcd(day) | 0x80; 114 | buffer[3] = Self::bin_to_bcd(week) | 0x80; 115 | self.write_bytes(regs::PCF8563_MINUTE_ALARM, &mut buffer)?; 116 | let mut ctlreg = [0u8]; 117 | // self.read_byte(regs::PCF8563_CONTROL_2, &mut ctlreg)?; 118 | ctlreg[0] = 0x02 | 0x04; 119 | self.write_bytes(regs::PCF8563_CONTROL_2, &mut ctlreg) 120 | } 121 | 122 | pub fn clear_alarm(&mut self) -> Result<(), i2c::Error> { 123 | let mut buffer = [0u8; 1]; 124 | self.read_byte(regs::PCF8563_CONTROL_2, &mut buffer)?; 125 | buffer[0] = buffer[0] - 0b00001000; 126 | self.write_bytes(regs::PCF8563_CONTROL_2, &mut buffer) 127 | } 128 | 129 | pub fn is_running(&mut self) -> Result { 130 | let mut buf = [0u8; 1]; 131 | self.read_byte(regs::PCF8563_CONTROL_1, &mut buf)?; 132 | return Ok(((buf[0] >> 5) & 1) != 0); 133 | } 134 | 135 | pub fn lost_power(&mut self) -> Result { 136 | let mut buf = [0u8; 1]; 137 | self.read_byte(regs::PCF8563_VL_SECONDS, &mut buf)?; 138 | return Ok((buf[0] >> 7) != 0); 139 | } 140 | 141 | pub fn stop(&mut self) -> Result<(), i2c::Error> { 142 | let mut buf = [0u8; 1]; 143 | self.read_byte(regs::PCF8563_CONTROL_1, &mut buf)?; 144 | if (buf[0] & PCF8563_IS_RUNNING_FLAG) == 0 { 145 | return Err(i2c::Error::Bus); 146 | } 147 | if buf[0] & (1 << 5) != 0 { 148 | self.write_byte(regs::PCF8563_CONTROL_1, buf[0] | (1 << 5)) 149 | } else { 150 | Ok(()) 151 | } 152 | } 153 | 154 | fn bcd_to_bin(value: u8) -> u8 { 155 | ((value / 16) * 10) + (value % 16) 156 | } 157 | fn bin_to_bcd(value: u8) -> u8 { 158 | value + 6 * (value / 10) 159 | } 160 | 161 | fn dec_to_bcd(value: u8) -> u8 { 162 | value / 10 * 16 + value % 10 163 | } 164 | 165 | fn read_byte(&self, reg_addr: u8, buf: &mut [u8]) -> Result<(), i2c::Error> { 166 | println!("PCF8563 read_byte {:x?} from {:x}", buf, reg_addr); 167 | let mut i2c = self.i2c.borrow_mut(); 168 | i2c.write_read(self.addr, &[reg_addr], buf) 169 | } 170 | 171 | fn write_bytes(&mut self, reg_addr: u8, bytes: &mut [u8]) -> Result<(), i2c::Error> { 172 | println!("PCF8563 write {:x?} into reg_addr {:x}", bytes, reg_addr); 173 | let mut i2c = self.i2c.borrow_mut(); 174 | i2c.write_reg(self.addr, reg_addr, &bytes) 175 | } 176 | 177 | fn write_byte(&mut self, reg_addr: u8, byte: u8) -> Result<(), i2c::Error> { 178 | println!("PCF8563 write {:x} into reg_addr {:x}", byte, reg_addr); 179 | let mut i2c = self.i2c.borrow_mut(); 180 | i2c.write(reg_addr, &[byte]) 181 | } 182 | } 183 | 184 | use core::cell::UnsafeCell; 185 | 186 | struct Singleton { 187 | inner: UnsafeCell>, 188 | } 189 | 190 | unsafe impl Sync for Singleton {} 191 | 192 | static PERIPHERALS: Singleton = Singleton { 193 | inner: UnsafeCell::new(None), 194 | }; 195 | 196 | pub fn set_default_rtc(rtc: &'static mut PCF8563) { 197 | let peripherals = unsafe { &mut *PERIPHERALS.inner.get() }; 198 | if peripherals.is_none() { 199 | *peripherals = Some(rtc); 200 | } else { 201 | panic!("RTC has already been set"); 202 | } 203 | } 204 | 205 | pub fn take() -> &'static mut PCF8563 { 206 | let peripherals = unsafe { &mut *PERIPHERALS.inner.get() }; 207 | peripherals.as_mut().expect("RTC is not set") 208 | } 209 | -------------------------------------------------------------------------------- /Firmware/src/bluetooth.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::regs; 3 | use crate::rtc; 4 | use crate::rtc::Time; 5 | use ch58x::ch58x; 6 | use ch58x_hal::ble::ffi::*; 7 | use ch58x_hal::ble::gap::*; 8 | use ch58x_hal::ble::MacAddress; 9 | use ch58x_hal::gpio::Input; 10 | use ch58x_hal::gpio::Pin; 11 | use ch58x_hal::gpio::Pull; 12 | use ch58x_hal::with_safe_access; 13 | use ch58x_hal::{ble, peripherals, println}; 14 | use chrono::prelude::*; 15 | use embassy_sync::channel::Channel; 16 | use embassy_time::{Duration, Timer}; 17 | use qingke::riscv; 18 | use qingke_rt::highcode; 19 | 20 | type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; 21 | 22 | static EVENTS: Channel = Channel::new(); 23 | 24 | const DEFAULT_DISCOVERY_MODE: u8 = DEVDISC_MODE_ALL; 25 | const DEFAULT_DISCOVERY_ACTIVE_SCAN: u8 = 1; // false 26 | const DEFAULT_DISCOVERY_WHITE_LIST: u8 = 0; 27 | 28 | static mut SCAN_RES_SIZE: u8 = 0; 29 | 30 | #[repr(C)] 31 | #[derive(Debug)] 32 | pub struct AdStructure<'d> { 33 | ad_len: u8, 34 | ad_type: u8, 35 | ad_data: &'d [u8], 36 | } 37 | 38 | impl<'d> AdStructure<'d> { 39 | pub fn new(data: &'d [u8]) -> Option { 40 | if data.len() < 3 { 41 | // 数据长度不足,无法创建 AdStructure 42 | return None; 43 | } 44 | 45 | let ad_len = data[0] as usize; 46 | let ad_type = data[1]; 47 | if data.len() < 1 + ad_len { 48 | // 数据长度不足以包含完整的 AD 数据 49 | return None; 50 | } 51 | 52 | let ad_data = &data[2..1 + ad_len]; 53 | let ad_structure = AdStructure { 54 | ad_len: ad_len as u8, 55 | ad_type, 56 | ad_data, 57 | }; 58 | Some(ad_structure) 59 | } 60 | 61 | pub fn parse2time(&self) -> Option