Compare commits

..
258 Commits
Author SHA1 Message Date
Armel FAUVEAU fe9c4e9432 Merge pull request #484 from armel/feature_update_v5
Feature update v5
2026-08-04 17:45:07 +02:00
Armel FAUVEAU 1e4698409b Prepare new version 5.8.0 2026-08-04 17:15:56 +02:00
Armel FAUVEAU 39f46a8f93 Update donors 2026-08-04 14:41:27 +02:00
Armel FAUVEAU 3518ab8294 Fix restart command during Beacon TX 2026-08-04 04:09:02 +02:00
Armel FAUVEAU c45b7d2deb Update README 2026-08-04 03:33:12 +02:00
Armel FAUVEAU f904b87172 Update README 2026-08-04 03:28:16 +02:00
Armel FAUVEAU 926787ddf3 Trim Fox Hunt buffers and drop hot-path divisions (-20 B flash) 2026-08-04 02:22:58 +02:00
Armel FAUVEAU fb9491caa5 Factor duplicated Fox Hunt helpers (-32 B flash) 2026-08-04 01:44:35 +02:00
Armel FAUVEAU 04e8d47130 Add F + number reverse-cycle and Beacon TX bitmap 2026-08-04 01:16:06 +02:00
Armel FAUVEAU 3bdddce67b Rework Fox Hunt/Beacon for ARDF: fox IDs, TX window, unified keys 2026-08-03 23:35:02 +02:00
Armel FAUVEAU d5cde97e5b Update rolling Fusion development firmware 2026-08-03 01:18:55 +02:00
Armel FAUVEAU 66ed14dead Show progressive CW beacon text 2026-08-03 01:18:25 +02:00
Armel FAUVEAU 602dfad6f3 Update rolling Fusion development firmware 2026-08-02 17:35:46 +02:00
Armel FAUVEAU 0203586cdc Fix RX mode restore after scan transitions (Issue #480) 2026-08-02 17:35:09 +02:00
Armel FAUVEAU 3e5795255d Update rolling Fusion development firmware 2026-08-02 05:51:35 +02:00
Armel FAUVEAU a8912d3208 Fix: handle restart in modal apps 2026-08-02 05:50:40 +02:00
Armel FAUVEAU fe602b075d Update rolling Fusion development firmware 2026-08-02 05:23:43 +02:00
Armel FAUVEAU e445cfca85 Fox Hunt: factor shared beacon draw code, -156 B flash 2026-08-02 05:22:37 +02:00
Armel FAUVEAU 18e9de38e0 Update rolling Fusion development firmware 2026-08-02 05:03:14 +02:00
Armel FAUVEAU f371795ef8 Update next version 2026-08-02 05:02:48 +02:00
Armel FAUVEAU e9181c2287 Add Fox Hunt signal-history gauge and persistent settings 2026-08-02 05:01:20 +02:00
Armel FAUVEAU 5437bf2d8e Update rolling Fusion development firmware 2026-08-01 23:41:34 +02:00
Armel FAUVEAU b972e026ca Enforce TX gates in Beacon mode: F LOCK, TX LOCK, battery, AM (Issue #478) 2026-08-01 23:40:20 +02:00
Armel FAUVEAU 0cad22f369 Update donors 2026-08-01 18:33:57 +02:00
Armel FAUVEAU 2a13c1819d Update rolling Fusion development firmware 2026-08-01 16:14:56 +02:00
Armel FAUVEAU 4799eb46ac Fix RX mode restore after Close Call (Issue #478) 2026-08-01 16:14:08 +02:00
Armel FAUVEAU 2569e4649d Update rolling Fusion development firmware 2026-08-01 15:36:21 +02:00
Armel FAUVEAU a59f3edcff Add Beacon mode 2026-08-01 15:35:46 +02:00
Armel FAUVEAU 027240de54 Update rolling Fusion development firmware 2026-08-01 03:38:43 +02:00
Armel FAUVEAU 73f44911ed Add Fox Hunt mode 2026-08-01 03:38:03 +02:00
Armel FAUVEAU c0e3909853 Update rolling Fusion development firmware 2026-07-31 02:37:14 +02:00
Armel FAUVEAU 31ff3b4df0 Remove dead empty block 2026-07-31 02:36:23 +02:00
Armel FAUVEAU bbee375623 Update rolling Fusion development firmware 2026-07-30 19:13:04 +02:00
Armel FAUVEAU 12dfa8a87f Optimize RAM usage by making lookup tables const 2026-07-30 19:05:16 +02:00
Armel FAUVEAU c9da65117f Avoid unnecessary flash sector erases 2026-07-30 02:56:06 +02:00
Armel FAUVEAU 222448e1ad Avoid full-buffer channel attribute writes 2026-07-30 02:32:13 +02:00
Armel FAUVEAU e1610bcc6f Avoid fast scan RSSI and chevron overlap 2026-07-29 23:23:30 +02:00
Armel FAUVEAU 7f906300e3 Add opt-in Fusion development firmware build 2026-07-28 18:33:19 +02:00
Armel FAUVEAU bf7eda8a48 Fix false Remove Offset indicator after disabling reverse (issue #469) 2026-07-27 17:09:39 +02:00
Armel FAUVEAU 2ee1d96e5c Smooth power-on backlight fade with smoothstep easing (~470 ms) 2026-07-26 16:53:15 +02:00
Armel FAUVEAU 65ec12bb0d Show active scan list name when it changes during scan (discussion #462) 2026-07-22 04:05:07 +02:00
Armel FAUVEAU dd3fc786b5 Gate RF logging on key assignment 2026-07-20 22:29:15 +02:00
Armel FAUVEAU 10868858c5 Update README 2026-07-19 15:50:34 +02:00
Armel FAUVEAU 3bd3ebba2c Merge pull request #457 from armel/feature_update_v5
Feature update v5
2026-07-19 01:41:58 +02:00
Armel FAUVEAU 39cbbd7e90 Prepare new version v5.7.0 2026-07-19 01:40:08 +02:00
Armel FAUVEAU 0567f01786 Keep RF log indexes traffic-only 2026-07-15 04:55:06 +02:00
Armel FAUVEAU fc12a35418 Update .gitignore 2026-07-15 04:55:06 +02:00
Armel FAUVEAU 2580acfb07 Give session markers a trafficSeq and stream them to K5Viewer 2026-07-13 04:27:33 +02:00
Armel FAUVEAU 56a6083689 Stream paginated RF log history to K5Viewer 2026-07-12 05:15:57 +02:00
Armel FAUVEAU 29725ed704 Allow unlock from RF LOG display 2026-07-11 03:26:47 +02:00
Armel FAUVEAU b44df132d0 Add ACTIONS lock scope to SetLck 2026-07-11 03:07:08 +02:00
Armel FAUVEAU 676f26be34 Remove legacy Python k5viewer tool 2026-07-09 05:25:42 +02:00
Armel FAUVEAU 46034ec39f Rename screenshot module to k5viewer 2026-07-09 05:22:39 +02:00
Armel FAUVEAU e2d21c61c2 Add optional RX/TX log streaming to K5Viewer 2026-07-09 05:12:57 +02:00
Armel FAUVEAU f8e1b7a436 Increase the scanrange skip list from 32 to 64 entries 2026-07-08 03:41:27 +02:00
Armel FAUVEAU 9447eaada9 Add secure clear log confirmation flow 2026-07-07 14:15:02 +02:00
Armel FAUVEAU 43533c838b Reorder RX/TX log flash layout to shrink the view cache 2026-07-07 02:56:22 +02:00
Armel FAUVEAU 7306db5f8f Show TX power level for TX rows in RX/TX log S-meter view 2026-07-07 02:36:16 +02:00
Armel FAUVEAU fbd7b37c21 Log minimum battery voltage per session in RX/TX log 2026-07-07 02:30:15 +02:00
Armel FAUVEAU 8d3754b42f Center S-meter text in the RX/TX log detail badge 2026-07-07 02:18:58 +02:00
Armel FAUVEAU 6a556e0601 Add S-meter to RX/TX log 2026-07-07 00:13:50 +02:00
Armel FAUVEAU 9c1b1cb063 Remove unused REGA emergency alarm feature (ENABLE_REGA) 2026-07-06 15:25:29 +02:00
Armel FAUVEAU ae7bf65383 Ignore squelch-only incoming state in RX/TX log 2026-07-06 12:22:50 +02:00
Armel FAUVEAU fb1360cce2 Replace unreachable RX/TX log EEPROM mapping with documentation comment 2026-07-06 03:52:57 +02:00
Armel FAUVEAU 374e52c4d1 Resolve RX/TX log channel names at display time instead of storing them 2026-07-06 03:35:54 +02:00
Armel FAUVEAU 42aeac3469 Move left by 1 px 2026-07-06 03:18:22 +02:00
Armel FAUVEAU 98ac57b032 Keep status bar layout stable when RX/TX log filter label is shown 2026-07-06 00:11:49 +02:00
Armel FAUVEAU 3e6e7c87b5 Add F + UP/DOWN shortcuts to jump to first/last RX/TX log entry 2026-07-05 23:28:31 +02:00
Armel FAUVEAU e908eda1da Make RX/TX log wrap-around scrolling optional via ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP 2026-07-05 23:19:00 +02:00
Armel FAUVEAU 4fccdb78b9 Dedupe RX/TX log entry copies by aligning RAM and flash struct layouts 2026-07-05 23:01:27 +02:00
Armel FAUVEAU a14127867e Remove redundant name sanitization already done by SETTINGS_FetchChannelName 2026-07-05 22:48:40 +02:00
Armel FAUVEAU c02aac265a Remove unused KEY_STAR 2026-07-05 22:38:36 +02:00
Armel FAUVEAU c24592cbe8 Factor RX/TX log empty screen rendering 2026-07-05 22:33:33 +02:00
Armel FAUVEAU ff096e12e5 Simplify RX/TX log badge numbering and dedupe repeated reset/stop code 2026-07-05 21:01:31 +02:00
Armel FAUVEAU 5fcc1598dd Fix RX/TX log wrap upward before total is known 2026-07-05 20:02:35 +02:00
Armel FAUVEAU 9b8493b450 Fix RX/TX log end-of-list scrolling 2026-07-05 19:16:35 +02:00
Armel FAUVEAU f1df96f1f6 Keep RX/TX log numbering continuous across sessions 2026-07-05 18:44:32 +02:00
Armel FAUVEAU f36c524fa7 Expose 512 entries in RX/TX log view instead of 250 2026-07-05 18:39:40 +02:00
Armel FAUVEAU 9ab5542326 Fix duplicate session marker on repeated reboots without traffic 2026-07-05 18:16:49 +02:00
Armel FAUVEAU 0187c05e65 Add RX/TX history log stored in external flash 2026-07-05 17:59:58 +02:00
Armel FAUVEAU 914c354da0 Update donors 2026-07-02 00:57:02 +02:00
Armel FAUVEAU a771387edd Merge pull request #437 from armel/feature_update_v5
Feature update v5
2026-06-30 16:06:49 +02:00
Armel FAUVEAU 09bc14e64b Add NOTICE 2026-06-30 16:05:51 +02:00
Armel FAUVEAU 58c0170fb1 Fix auto key lock timer on startup (issue #431) 2026-06-29 15:09:55 +02:00
Armel FAUVEAU 5b4b3e2a71 Merge pull request #423 from armel/feature_update_v5
Feature update v5
2026-06-21 19:02:08 +02:00
Armel FAUVEAU 65a5c9f4ef Prepare new version v5.6.1 2026-06-21 19:01:14 +02:00
Armel FAUVEAU a2936402c2 Fix screenshot state updates without display redraw 2026-06-21 18:07:10 +02:00
Armel FAUVEAU f069ed935f Fix screensaver state during deep sleep 2026-06-21 04:28:58 +02:00
Armel FAUVEAU dcbbd744ac Add screenshot state flags 2026-06-21 03:21:16 +02:00
Armel FAUVEAU bca06e3382 Fix NAR+ FM demod boost 2026-06-21 00:17:50 +02:00
Armel FAUVEAU 8723863fb0 Fix F+4 verify VHF harmonic before accepting frequency 2026-06-20 13:41:14 +02:00
Armel FAUVEAU c775fa8b2f Update donors 2026-06-18 02:29:34 +02:00
Armel FAUVEAU 80e7d4b3d1 Fix F+4 frequency scan sensitivity 2026-06-17 03:25:24 +02:00
Armel FAUVEAU e6a727eeda Fix this fucking and stupid roger beep (Issue #416) 2026-06-17 02:32:23 +02:00
Armel FAUVEAU 887e18efe6 Fix reset defaults (Issue #413) 2026-06-16 16:00:56 +02:00
Armel FAUVEAU 20b87feb1d Merge pull request #407 from mrkusypl/feature_update_v5_2
Code refactoring (4 B)
2026-06-15 22:20:52 +02:00
mrkusypl e2e9c9ba60 Code refactoring (4 B) 2026-06-15 20:40:56 +02:00
Armel FAUVEAU 337eb0cb12 Update donors 2026-06-15 16:10:31 +02:00
Armel FAUVEAU 504a2b0fb0 Update .gitignore 2026-06-15 03:15:45 +02:00
Armel FAUVEAU 16b07c6374 Merge pull request #405 from armel/feature_update_v5
Feature update v5
2026-06-15 03:10:46 +02:00
Armel FAUVEAU f5b20d4f80 Prepare v5.6.0 2026-06-15 03:09:32 +02:00
Armel FAUVEAU ae14bf5df3 Hollow out backlight bulb icon when manual light is off 2026-06-13 14:33:42 +02:00
Armel FAUVEAU 9dc85852ba Widen screenshot chunk fingerprint to 16 bits to fix stale chunks 2026-06-13 13:20:10 +02:00
Armel FAUVEAU f7d29f6a14 Save 128 bytes of SRAM in backlight PWM while preserving distinct brightness steps 2026-06-13 03:58:43 +02:00
Armel FAUVEAU 1184618731 Reduce screenshot peak stack usage with on-demand chunk computation 2026-06-13 03:30:22 +02:00
Armel FAUVEAU 3d15d59ce0 Remove dead screenshot bit packing flush 2026-06-12 18:06:44 +02:00
Armel FAUVEAU 15fccc31d6 Fix AM to FM dual-watch RX reconfiguration (issue #389) 2026-06-11 16:49:06 +02:00
Armel FAUVEAU 48f753caaf Reduce screenshot RAM usage with per-chunk frame hashing 2026-06-10 13:46:18 +02:00
Armel FAUVEAU edc4673983 Add subaudible detection for scan ranges 2026-06-09 04:57:15 +02:00
Armel FAUVEAU 9e03f02296 Refine VFO lock icon placement during scan 2026-06-08 04:11:36 +02:00
Armel FAUVEAU 25ea1f827b Add scan RSSI sparkline indicator 2026-06-08 01:50:28 +02:00
Armel FAUVEAU 8f11a32c9d Merge pull request #392 from mrkusypl/feature_update_v5_2
Code refactoring (32 B)
2026-06-06 18:23:54 +02:00
mrkusypl 576b8c552f Code refactoring (32 B) 2026-06-06 17:56:33 +02:00
Armel FAUVEAU 85db5a2394 Update .gitignore 2026-06-03 04:06:57 +02:00
Armel FAUVEAU 98989d4f0a Fix bandscope 8.33 kHz frequency rounding (issue #380) 2026-05-28 00:26:46 +02:00
Armel FAUVEAU 8c976cfffd Update donors 2026-05-25 12:24:23 +02:00
Armel FAUVEAU 0ce5422db3 Fix ENABLE_FEAT_F4HWN_RX_TX_TIMER guard with ENABLE_FEAT_F4HWN_DEBUG 2026-05-25 01:41:27 +02:00
Armel FAUVEAU d939009949 Merge pull request #370 from mrkusypl/feature_update_v5_2
Code refactoring (104 B)
2026-05-25 01:35:25 +02:00
mrkusypl 50efade5c4 Code refactoring (104 B) 2026-05-25 00:46:34 +02:00
Armel FAUVEAU 682a59fc0e Merge pull request #365 from mrkusypl/feature_update_v5_2
Prevent screensaver during M-SCAN; Code refactoring (20 B)
2026-05-23 14:40:01 +02:00
mrkusypl 3d3f370b86 Minor correction for checking gFM_ChannelPosition 2026-05-23 10:48:59 +02:00
mrkusypl eebeba09db Prevent screensaver during M-SCAN; Code refactoring (20 B) 2026-05-23 02:21:11 +02:00
Armel FAUVEAU 6cceb26814 Merge pull request #357 from mrkusypl/feature_update_v5_2
Code refactoring (54 B)
2026-05-17 17:12:44 +02:00
mrkusypl a77b132fa5 Code refactoring (54 B) 2026-05-17 16:48:43 +02:00
Armel FAUVEAU 86aa05f1ec Sync screensaver frames to k5viewer 2026-05-17 12:33:16 +02:00
Armel FAUVEAU 953f579adb Prevent screensaver during FM scan 2026-05-17 02:40:13 +02:00
Armel FAUVEAU d429b3afe8 Prevent screensaver during Beam 2026-05-17 02:34:06 +02:00
Armel FAUVEAU a65a4f601a Improve F+4 frequency copy scan UI 2026-05-17 02:15:22 +02:00
Armel FAUVEAU 546cd35681 Add LOGO+ scrolling SetSav mode 2026-05-13 03:21:43 +02:00
Armel FAUVEAU 8ecf976067 Exit SetSav when waking from SetTmr sleep 2026-05-13 02:49:21 +02:00
Armel FAUVEAU 3e54dc1c00 Merge pull request #350 from mrkusypl/feature_update_v5_2
Code refactoring (16 B)
2026-05-13 01:24:17 +02:00
Armel FAUVEAU 9eb9a9da5c Keep RX blink while reducing LCD redraws 2026-05-13 01:16:57 +02:00
mrkusypl a74529b9ec Code refactoring (16 B) 2026-05-13 01:10:47 +02:00
Armel FAUVEAU 7ccb53572a Fix SetNav is BLTime is OFF 2026-05-13 00:44:17 +02:00
Armel FAUVEAU c515188a06 Improve SetSav from @mrkusypl feedback 2026-05-12 23:19:23 +02:00
Armel FAUVEAU cdcb950ccd Block SetSav during RX 2026-05-12 19:39:27 +02:00
Armel FAUVEAU 13ca3b8a39 Skip SetSav during TX 2026-05-12 18:38:40 +02:00
Armel FAUVEAU 34acff0fbf Throttle SetTmr status refresh 2026-05-12 18:34:10 +02:00
Armel FAUVEAU fa75ab4243 Add SetSav BLMin screensaver 2026-05-12 18:01:46 +02:00
Armel FAUVEAU 1589d8bdd7 Merge pull request #345 from mrkusypl/feature_update_v5_2
Code refactoring (288 B)
2026-05-11 22:59:28 +02:00
mrkusypl a5597a1ab9 Minor corrections to refactoring 2026-05-11 21:43:05 +02:00
mrkusypl 955b245e66 Code refactoring (328 B) 2026-05-11 15:21:48 +02:00
Armel FAUVEAU 2c4bca4202 Archive LOGO 2026-05-10 15:24:01 +02:00
Armel FAUVEAU 6c4c240e9a Enable boot beep with POnMsg LOGO (Issue #340) 2026-05-08 16:57:09 +02:00
Armel FAUVEAU e21d472609 Merge pull request #338 from armel/feature_update_v5
Feature update v5
2026-05-08 03:18:49 +02:00
Armel FAUVEAU 8f89312e99 Add binaries 2026-05-08 03:17:05 +02:00
Armel FAUVEAU ec9fdd17bf Prepare new version v5.5.0 2026-05-07 19:24:59 +02:00
Armel FAUVEAU 1ed550fb92 Add LOGO option to POnMsg menu 2026-05-07 15:32:07 +02:00
Armel FAUVEAU 7d69b74b08 Fix(aircopy): include VFO area in Settings map so scan-range source frequencies are replicated 2026-05-06 01:47:37 +02:00
Armel FAUVEAU f50a29ae08 Merge pull request #334 from mrkusypl/feature_update_v5_2
Code refactoring (224 B)
2026-05-06 00:52:10 +02:00
mrkusypl 7a4226f483 Code refactoring (224 B) 2026-05-06 00:03:26 +02:00
Armel FAUVEAU 4523108293 Skip spectrum curve smoothing in manual mode (issue #333) 2026-05-05 22:00:10 +02:00
Armel FAUVEAU c92e63da0d Restore spectrum predictable 1 dB step on manual squelch line (issue #333) 2026-05-05 21:51:21 +02:00
Armel FAUVEAU 33d949a4be Move BEAM Action 2026-05-05 16:51:53 +02:00
Armel FAUVEAU a9ff58e4a7 Beam: full Apache 2.0 header in beam.c 2026-05-05 04:39:49 +02:00
Armel FAUVEAU b8936ace0e Add beam feature behind ENABLE_FEAT_F4HWN_BEAM flag 2026-05-05 04:35:03 +02:00
Armel FAUVEAU 59ed9cd827 Fix display bug (overlapping) 2026-05-04 22:03:53 +02:00
Armel FAUVEAU a0e4d2708b Merge pull request #331 from mrkusypl/feature_update_v5_2
Displaying VFO_Lock on the second channel while receiving on the first; Code refactoring (42 B)
2026-05-04 19:28:13 +02:00
mrkusypl c0c1d9881c Displaying VFO_Lock on the second channel while receiving on the first; Code refactoring (42 B) 2026-05-04 12:38:43 +02:00
Armel FAUVEAU c1d5b7bd55 Add scan mode menu 2026-05-04 04:56:15 +02:00
Armel FAUVEAU 932d6e954a Add volatile scanrange exclusions 2026-05-04 03:36:40 +02:00
Armel FAUVEAU 3e6b169b7b Improve fast scan detection and display metadata 2026-05-03 22:12:54 +02:00
Armel FAUVEAU ec4ea28325 Reset fast scan RSSI floor on AM/FM changes 2026-05-03 16:49:47 +02:00
Armel FAUVEAU 5157dc573b Fix scan resume, AM/FM precheck and list refresh 2026-05-03 14:39:13 +02:00
Armel FAUVEAU a0aefae01b Fix manual scan resume from active RX 2026-05-03 02:07:32 +02:00
Armel FAUVEAU 46cf7d7d57 Fix Breakout beep audio setup order 2026-05-02 00:43:59 +02:00
Armel FAUVEAU 9a7b199bb9 Merge pull request #325 from mrkusypl/feature_update_v5_2
Refactoring of beep handling (94 B)
2026-05-02 00:41:31 +02:00
mrkusypl 1e03dc4af0 Minor changes to the refactoring of beep handling 2026-05-02 00:35:43 +02:00
mrkusypl 673d1b57ab Refactoring of beep handling (94 B) 2026-05-01 19:39:59 +02:00
Armel FAUVEAU 6ae360d029 Add faster scanning (range & memory) behind ENABLE_FEAT_F4HWN_SCAN_FASTER flag 2026-05-01 18:35:14 +02:00
Armel FAUVEAU d5a29aa241 Show scan range bandwidth instead of unused frequencyChangeStep in scan-range mode 2026-05-01 00:26:08 +02:00
Armel FAUVEAU e0154de186 Fix stuck squelch on quick PTT by seeding listenPrevRssi with peak.rssi at listen entry 2026-05-01 00:04:24 +02:00
Armel FAUVEAU 915083522d Save additional spectrum analyzer parameters 2026-04-30 23:12:52 +02:00
Armel FAUVEAU c6e7ac33e2 Show homologated DCS index on 2 digits (saves 8B) 2026-04-30 00:48:27 +02:00
Armel FAUVEAU bc494a4526 Merge pull request #322 from mrkusypl/feature_update_v5_2
Show DCS codes from 39 to 121; Code refactoring (16 B)
2026-04-30 00:42:05 +02:00
mrkusypl 1ee871cc79 Revert "Show DCS codes from 39 to 121" 2026-04-30 00:01:50 +02:00
mrkusypl b83e4c2491 Show DCS codes from 39 to 121; Code refactoring (16 B) 2026-04-29 20:54:00 +02:00
Armel FAUVEAU c1f8e945c9 Show homologated DCS index in Rx/Tx DCS menus 2026-04-29 19:27:41 +02:00
Armel FAUVEAU 9013f83734 Merge pull request #320 from mrkusypl/feature_update_v5_2
Major code refactoring (412 B)
2026-04-29 18:43:21 +02:00
mrkusypl a46604d8f3 Major code refactoring (412 B) 2026-04-29 17:06:18 +02:00
Armel FAUVEAU 037de071eb Clamp displayed RSSI at S9+40 (-53 dBm) ceiling 2026-04-29 00:41:17 +02:00
Armel FAUVEAU 2c116f9503 Fix S-meter display during scan reception 2026-04-29 00:35:14 +02:00
Armel FAUVEAU 50681ef90b Merge pull request #318 from mrkusypl/feature_update_v5_2
Add long-press functionality to ChName, minor refactoring
2026-04-28 22:46:13 +02:00
mrkusypl 0501422c64 Add long-press functionality to ChName, minor refactoring 2026-04-28 17:45:34 +02:00
Armel FAUVEAU 786318453f Merge pull request #314 from armel/feature_update_v5
Feature update v5
2026-04-28 03:51:29 +02:00
Armel FAUVEAU 61662f3965 Prepare new version v5.4.0 2026-04-28 03:48:04 +02:00
Armel FAUVEAU b3cfda76e5 Merge pull request #313 from mrkusypl/feature_update_v5_2
Code refactoring (68 B)
2026-04-27 22:13:05 +02:00
mrkusypl a20f1ce28e Code refactoring (68 B) 2026-04-27 16:26:49 +02:00
Armel FAUVEAU 8a292755b0 Prepare new version 2026-04-27 15:15:10 +02:00
Armel FAUVEAU 915fc6aa90 SysInf tweak 2026-04-27 15:12:48 +02:00
Armel FAUVEAU cac63f978e Revert "Add UI_PrintStringYOffset to shift big-font"
This reverts commit d5edba1fc2.
2026-04-27 15:03:44 +02:00
Armel FAUVEAU d5edba1fc2 Add UI_PrintStringYOffset to shift big-font 2026-04-27 06:10:44 +02:00
Armel FAUVEAU 2f0852cd03 Add SysInf build details page 2026-04-27 04:12:51 +02:00
Armel FAUVEAU a65f84305e Show firmware version in big font 2026-04-27 04:00:08 +02:00
Armel FAUVEAU 1c0d5ea628 Move SysInf battery data to dedicated page (and patch m lower...) 2026-04-27 03:54:13 +02:00
Armel FAUVEAU a0a5b93408 Merge pull request #312 from mrkusypl/feature_update_v5_2
Code refactoring (12 B)
2026-04-27 02:46:17 +02:00
mrkusypl e8cb32a4ca Code refactoring (12 B) 2026-04-27 02:34:49 +02:00
Armel FAUVEAU 706dea756c Refactor: QR codes only in SysInf menu, drop welcome splash 2026-04-26 23:20:44 +02:00
Armel FAUVEAU 1ea8aba88e Add QR code welcome screen with repo and wiki links 2026-04-26 15:12:24 +02:00
Armel FAUVEAU c2d6a4b751 Fix excluded scan gauge rendering for large channel lists 2026-04-26 04:39:14 +02:00
Armel FAUVEAU db61bce014 Merge pull request #310 from mrkusypl/feature_update_v5_2
Entering text into ChName in a multi-tap system
2026-04-26 03:09:28 +02:00
mrkusypl a3c03f18f0 Fix for string terminator overwriting 2026-04-26 02:24:27 +02:00
mrkusypl 8977a0a112 Entering text into ChName in a multi-tap system 2026-04-26 01:09:57 +02:00
Armel FAUVEAU 09a5e3c1e7 Fix limit bug with scan gauge 2026-04-25 05:09:32 +02:00
Armel FAUVEAU 4fa821e15f Oops ! Preserve scan gauge width calculation, with or without priority labels 2026-04-25 04:50:50 +02:00
Armel FAUVEAU c9a383919a Improve scan progress gauge when priority channels are in use 2026-04-25 04:39:18 +02:00
Armel FAUVEAU e8ba069000 Merge pull request #308 from mrkusypl/feature_update_v5_2
Code refactoring (200 B)
2026-04-25 02:21:18 +02:00
mrkusypl acd2c340ea Code refactoring (200 B) 2026-04-25 02:10:52 +02:00
Armel FAUVEAU 24fd6fd9b1 Improve spectrum trigger line rendering 2026-04-25 00:17:50 +02:00
Armel FAUVEAU 86bb710ca4 Merge pull request #307 from mrkusypl/feature_update_v5_2
Reversing the up/down button functions on the Spectrum
2026-04-24 20:04:32 +02:00
mrkusypl 7148c15f98 Reversing the up/down button functions on the Spectrum 2026-04-24 20:02:05 +02:00
Armel FAUVEAU 2d7093651d Show sweep direction arrow next to A:NORM/WEAK/STRG label 2026-04-24 19:45:20 +02:00
Armel FAUVEAU 874e5aac4b Extract SETTINGS_LoadEepromDtmf to file scope to drop the GCC nested-function extension 2026-04-24 19:35:48 +02:00
Armel FAUVEAU 8051605c16 Guard interlaced sweep code behind SPECTRUM_INTERLACE_LARGE_SWEEPS to silence unused warnings 2026-04-24 19:30:14 +02:00
Armel FAUVEAU 134624825f Merge pull request #306 from mrkusypl/feature_update_v5_2
Code refactoring (232 B)
2026-04-24 18:04:04 +02:00
mrkusypl 7d0fcc8415 Code refactoring (232 B) 2026-04-24 17:51:48 +02:00
Armel FAUVEAU c27b72d6bc Merge pull request #305 from mrkusypl/feature_update_v5_2
Code refactoring (180 B)
2026-04-24 04:49:11 +02:00
Armel FAUVEAU 066c0d0e14 Spectrum experimentation 2026-04-24 04:48:18 +02:00
mrkusypl e77b87f1f2 Another code refactoring (40 B) 2026-04-24 00:09:38 +02:00
mrkusypl 2dd5e1a509 Code refactoring (140 B) 2026-04-23 18:32:39 +02:00
Armel FAUVEAU ab9e97805e Merge pull request #300 from mrkusypl/feature_update_v5_2
Refactoring ScanProgress_DrawGaugeLine
2026-04-23 02:28:38 +02:00
Armel FAUVEAU 65c584ff7d Spectrum/BK4829: interlace large sweeps and cut SPI overhead 2026-04-23 02:27:52 +02:00
mrkusypl 4c76ca1fb4 Refactoring ScanProgress_DrawGaugeLine 2026-04-22 22:33:30 +02:00
Armel FAUVEAU 094896b5f1 UI format progress index with dynamic zero padding based on total digits 2026-04-22 04:00:29 +02:00
Armel FAUVEAU 669dad5b2e Merge pull request #298 from mrkusypl/feature_update_v5_2
Changing the length of the scan progress gauge
2026-04-22 03:31:48 +02:00
Armel FAUVEAU b9902e0c49 UI unify and center top-right menu info badges 2026-04-22 03:30:52 +02:00
mrkusypl 0a955c1f15 Changing the length of the scan progress gauge 2026-04-22 01:54:49 +02:00
Armel FAUVEAU 9f649b5f5b Merge pull request #297 from mrkusypl/feature_update_v5_2
Code refactoring (44 B)
2026-04-21 20:35:33 +02:00
Armel FAUVEAU bb992ca85d Scan progress gauge behind ENABLE_FEAT_F4HWN_SCAN_PROGRESS 2026-04-21 20:34:20 +02:00
mrkusypl 679cf4ea1f Code refactoring (44 B) 2026-04-21 17:26:18 +02:00
Armel FAUVEAU 0004074ffe Fix large-range spectrum stability and manual dB floor behavior 2026-04-21 05:25:28 +02:00
Armel FAUVEAU 35e5d8df90 Fix spectrum manual trigger persistence across scan relaunches 2026-04-21 03:07:23 +02:00
Armel FAUVEAU fa0de5f576 Spectrum: fix and polish manual mode (manualSetFlag) from PR #296 2026-04-21 02:36:32 +02:00
Armel FAUVEAU 4162618473 Merge pull request #296 from mrkusypl/feature_update_v5_2
Code refactoring, improvements in Spectrum
2026-04-21 01:58:39 +02:00
mrkusypl 284af7ca1d Code refactoring, improvements in Spectrum 2026-04-20 23:05:05 +02:00
Armel FAUVEAU c6f1f7b483 Spectrum: align channel name and frequency at x=43 to avoid dBm overlap 2026-04-19 03:21:43 +02:00
Armel FAUVEAU 6cea90d507 Merge pull request #292 from mrkusypl/feature_update_v5_2
Code optimization (80 B), behavior of the EXIT button when editing ChName
2026-04-19 00:29:34 +02:00
Armel FAUVEAU 46608af767 Spectrum refactoring 2026-04-18 18:36:39 +02:00
mrkusypl 24167a7902 Code optimization (80 B), behavior of the EXIT button when editing ChName 2026-04-18 18:30:22 +02:00
Armel FAUVEAU e300c4e7e3 Improve ChName editing with long-press EXIT backspace (issue #281) 2026-04-18 01:09:40 +02:00
Armel FAUVEAU 1a815b315f Update donors 2026-04-18 00:42:49 +02:00
Armel FAUVEAU a7fed2c2d8 Spectrum refactoring 2026-04-18 00:41:24 +02:00
Armel FAUVEAU 9e92037674 Fix build without ENABLE_FMRADIO in APP_TimeSlice500ms 2026-04-15 22:17:05 +02:00
Armel FAUVEAU e536725731 Show homologated CTCSS index in Rx/Tx CTCSS menus 2026-04-15 00:20:13 +02:00
Armel FAUVEAU f59370cee6 Merge pull request #268 from mrkusypl/feature_update_v5_2
Improvements to FM Radio and the UI
2026-04-14 23:11:03 +02:00
mrkusypl bdaf320c08 Fix the channel number display 2026-04-09 10:17:07 +02:00
Armel FAUVEAU fb60d6f7fd Add WIDE/NARROW filter selection for AM RX 2026-04-09 02:44:20 +02:00
Armel FAUVEAU 22295eedcf Update donors 2026-04-08 18:08:50 +02:00
mrkusypl 21c215e9fd Improvements to FM Radio and the UI 2026-04-05 21:01:13 +02:00
Armel FAUVEAU 0061613883 Merge pull request #265 from armel/feature_update_v5
Feature update v5
2026-04-05 06:29:27 +02:00
Armel FAUVEAU aecbcc6409 Update README 2026-04-05 06:27:34 +02:00
Armel FAUVEAU 642dcc9a43 Add support for decoding packed Quansheng stock firmware images 2026-04-03 05:00:38 +02:00
Armel FAUVEAU 2b05990a2c Refactor hidden menu entry to start from first hidden item 2026-04-03 04:28:39 +02:00
Armel FAUVEAU 975084baca Merge pull request #252 from mrkusypl/feature_update_v5_2
Code refactoring (108 B)
2026-04-02 00:06:08 +02:00
mrkusypl 775cd7751a Undo refactoring of AUDIO_Apply(FM/AM)Profile 2026-04-01 13:26:29 +02:00
mrkusypl c5da503cb3 Code refactoring (136 B) 2026-04-01 00:37:44 +02:00
Armel FAUVEAU 5f8a492f4f Merge pull request #251 from armel/feature_update_v5
Update donors
2026-03-31 19:01:33 +02:00
Armel FAUVEAU 8dd5da195e Update donors 2026-03-31 19:00:32 +02:00
110 changed files with 10059 additions and 2898 deletions

No files matched your search

+4 -2
View File
@@ -8,10 +8,12 @@ firmware
compile_commands.json
.vscode
.claude
.agents
/docs
k5_eeprom.raw
/build
/serialtool/__pycache__
__pycache__/
.DS_Store
.DS_Store
AGENTS.md
+35 -7
View File
@@ -70,6 +70,7 @@ if(ENABLE_FEAT_F4HWN)
"AUTHOR_STRING_2=\"${AUTHOR_STRING_2}\""
"VERSION_STRING_1=\"${VERSION_STRING_1}\""
"VERSION_STRING_2=\"${VERSION_STRING_2}\""
"DISPLAY_VERSION_STRING_2=\"${DISPLAY_VERSION_STRING_2}\""
"EDITION_STRING=\"${EDITION_STRING}\""
ALERT_TOT=10
)
@@ -78,6 +79,7 @@ endif()
target_compile_definitions(App INTERFACE
"AUTHOR_STRING=\"${AUTHOR_STRING}\""
"VERSION_STRING=\"${VERSION_STRING}\""
"BUILD_COMMIT=\"${BUILD_COMMIT}\""
)
if(NOT SQL_TONE)
@@ -122,7 +124,7 @@ enable_feature(ENABLE_FMRADIO
ui/fmradio.c
)
enable_feature(ENABLE_AIRCOPY
app/aircopy.c
app/aircopy.c
ui/aircopy.c
)
enable_feature(ENABLE_NOAA)
@@ -170,10 +172,6 @@ enable_feature(ENABLE_SCAN_RANGES)
# ---- CONTRIB MODS ----
# Thank you @markusb
enable_feature(ENABLE_REGA
app/rega.c
)
# Thank you @reppad
enable_feature(ENABLE_EXTRA_UART_CMD)
@@ -183,8 +181,8 @@ enable_feature(ENABLE_FEAT_F4HWN)
enable_feature(ENABLE_FEAT_F4HWN_GAME
app/breakout.c
)
enable_feature(ENABLE_FEAT_F4HWN_SCREENSHOT
screenshot.c
enable_feature(ENABLE_FEAT_F4HWN_K5VIEWER
k5viewer.c
)
enable_feature(ENABLE_FEAT_F4HWN_SPECTRUM)
enable_feature(ENABLE_FEAT_F4HWN_RX_TX_TIMER)
@@ -194,6 +192,10 @@ enable_feature(ENABLE_FEAT_F4HWN_RESUME_STATE)
enable_feature(ENABLE_FEAT_F4HWN_NARROWER)
enable_feature(ENABLE_FEAT_F4HWN_INV)
enable_feature(ENABLE_FEAT_F4HWN_CTR)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_PROGRESS)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_FASTER)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_RSSI)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE)
enable_feature(ENABLE_FEAT_F4HWN_RESCUE_OPS)
enable_feature(ENABLE_FEAT_F4HWN_VOL)
enable_feature(ENABLE_FEAT_F4HWN_AUDIO)
@@ -203,6 +205,32 @@ enable_feature(ENABLE_FEAT_F4HWN_GMRS_FRS_MURS)
enable_feature(ENABLE_FEAT_F4HWN_CA)
enable_feature(ENABLE_FEAT_F4HWN_DEBUG)
enable_feature(ENABLE_FEAT_F4HWN_MEM)
enable_feature(ENABLE_FEAT_F4HWN_BEAM
app/beam.c
)
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG
app/rxtx_log.c
)
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT
app/foxhunt.c
)
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP)
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER)
if(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER AND NOT (ENABLE_FEAT_F4HWN_RXTX_LOG AND ENABLE_FEAT_F4HWN_K5VIEWER))
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER requires ENABLE_FEAT_F4HWN_RXTX_LOG (log data) and ENABLE_FEAT_F4HWN_K5VIEWER (serial transport).")
endif()
if(ENABLE_FEAT_F4HWN_BEAM AND NOT ENABLE_AIRCOPY)
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_BEAM requires ENABLE_AIRCOPY (it reuses g_FSK_Buffer, AIRCOPY_Obfuscate and the FSK packet plumbing).")
endif()
enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
enable_feature(ENABLE_FEAT_F4HWN_LOGO)
enable_feature(ENABLE_FEAT_F4HWN_LOGO_SAV)
if(ENABLE_FEAT_F4HWN_LOGO_SAV AND NOT ENABLE_FEAT_F4HWN_LOGO)
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_LOGO_SAV requires ENABLE_FEAT_F4HWN_LOGO.")
endif()
# ---- DEBUGGING ----
+47 -33
View File
@@ -40,9 +40,16 @@
#include "misc.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/main.h"
#include "ui/ui.h"
#ifdef ENABLE_REGA
#include "app/rega.h"
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include "app/beam.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#include "app/foxhunt.h"
#endif
#if defined(ENABLE_FMRADIO)
@@ -57,7 +64,7 @@ inline static void ACTION_1750() { ACTION_AlarmOr1750(true); };
inline static void ACTION_ScanRestart() { ACTION_Scan(true); };
void (*action_opt_table[])(void) = {
void (*const action_opt_table[])(void) = {
[ACTION_OPT_NONE] = &FUNCTION_NOP,
[ACTION_OPT_POWER] = &ACTION_Power,
[ACTION_OPT_MONITOR] = &ACTION_Monitor,
@@ -127,9 +134,14 @@ void (*action_opt_table[])(void) = {
#else
[ACTION_OPT_RXMODE] = &FUNCTION_NOP,
#endif
#ifdef ENABLE_REGA
[ACTION_OPT_REGA_ALARM] = &ACTION_RegaAlarm,
[ACTION_OPT_REGA_TEST] = &ACTION_RegaTest,
#ifdef ENABLE_FEAT_F4HWN_BEAM
[ACTION_OPT_BEAM] = &ACTION_Beam,
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
[ACTION_OPT_RXTX_LOG] = &ACTION_RxTxLog,
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
[ACTION_OPT_FOXHUNT] = &ACTION_FoxHunt,
#endif
};
@@ -212,7 +224,7 @@ void ACTION_Scan(bool bRestart)
DTMF_clear_RX();
#endif
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
RADIO_SelectVfos();
@@ -237,15 +249,14 @@ void ACTION_Scan(bool bRestart)
// channel mode. Keep scanning but toggle between scan lists
RADIO_NextValidList(1);
UI_MAIN_NotifyScanListChanged();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
#endif
// jump to the next channel
CHFRSCANNER_Start(false, gScanStateDir);
gScanPauseDelayIn_10ms = 1;
gScheduleScanListen = false;
CHFRSCANNER_ManualResume(gScanStateDir);
} else {
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
if(gScanRangeStart == 0) // No ScanRange
@@ -343,22 +354,14 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
break;
}
if (!bKeyHeld && bKeyPressed) // button pushed
{
if (bKeyHeld != bKeyPressed) { // button pushed or released after hold
// (!bKeyHeld && bKeyPressed) or (bKeyHeld && !bKeyPressed)
return;
}
// held or released beyond this point
// held or released after short press
if(!(bKeyHeld && !bKeyPressed)) // don't beep on released after hold
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (bKeyHeld && !bKeyPressed) // button released after hold
{
return;
}
// held or released after short press beyond this point
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
#ifdef ENABLE_FMRADIO
if (gFmRadioMode) { // do not run these actions in FM radio mode
@@ -382,6 +385,12 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
case ACTION_OPT_POWER_HIGH:
case ACTION_OPT_REMOVE_OFFSET:
#endif
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
case ACTION_OPT_BEAM:
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
case ACTION_OPT_FOXHUNT:
#endif
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
@@ -416,6 +425,12 @@ void ACTION_FM(void)
gMonitor = false;
if (gScanStateDir != SCAN_OFF) {
// Stop the channel/frequency scan before switching to the FM radio.
gScanKeepResult = false;
CHFRSCANNER_Stop();
}
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
@@ -472,7 +487,7 @@ static void ACTION_Scan_FM(bool bRestart)
static void ACTION_AlarmOr1750(const bool b1750)
{
if(gEeprom.KEY_LOCK && gEeprom.KEY_LOCK_PTT)
if(gEeprom.KEY_LOCK && (gSetting_set_lck & SET_LCK_PTT))
return;
#if defined(ENABLE_ALARM)
@@ -597,7 +612,7 @@ void ACTION_Wn(void)
if (pVfo->Modulation == MODULATION_AM)
{
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(pVfo), true);
return;
}
@@ -660,26 +675,25 @@ void ACTION_Mute(void)
BK1080_WriteRegister(BK1080_REG_05_SYSTEM_CONFIGURATION2, gMute ? 0x0A10 : 0x0A1F);
#endif
gEeprom.VOLUME_GAIN = gMute ? 0 : gEeprom.VOLUME_GAIN_BACKUP;
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
(0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
gUpdateStatus = true;
}
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
void ACTION_ToggleVfoSetting(bool *setting) {
*setting = !(*setting);
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
}
void ACTION_Power_High(void)
{
gPowerHigh = !gPowerHigh;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
ACTION_ToggleVfoSetting(&gPowerHigh);
}
void ACTION_Remove_Offset(void)
{
gRemoveOffset = !gRemoveOffset;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
ACTION_ToggleVfoSetting(&gRemoveOffset);
}
#endif
#endif
+3
View File
@@ -50,6 +50,9 @@ void ACTION_SwitchDemodul(void);
void ACTION_Power_High(void);
void ACTION_Remove_Offset(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
void ACTION_RxTxLog(void);
#endif
#endif
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
+27 -43
View File
@@ -30,8 +30,8 @@
#include "settings.h"
#include <stddef.h>
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#include "screenshot.h"
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "k5viewer.h"
#endif
static const uint16_t Obfuscation[8] = { 0x6C16, 0xE614, 0x912E, 0x400D, 0x3521, 0x40D5, 0x0313, 0x80E9 };
@@ -90,12 +90,15 @@ DECLARE_AIRCOPY_BANK(1)
static const AIRCOPY_Segment_t AIRCOPY_Segments_Settings[] = {
{ 0xA000, 0xA170, AIRCOPY_WRITE_BYTES },
{ 0x880E, 0x886E, AIRCOPY_WRITE_BYTES },
{ 0x9000, 0x90E5, AIRCOPY_WRITE_BYTES }, // VFO area (full 14 VFOs 0x9000..0x90E0) +
// Fox Hunt settings tail 0x90E0..0x90E5
};
// total_blocks = ceil(0x170/64) + ceil(0x60/64) + ceil(0xE5/64) = 6 + 2 + 4 = 12
static const AIRCOPY_TransferMap_t AIRCOPY_Map_Settings = {
.segments = AIRCOPY_Segments_Settings,
.num_segments = 2,
.total_blocks = 8
.num_segments = 3,
.total_blocks = 12
};
// Finally
@@ -138,8 +141,8 @@ static void AIRCOPY_clear()
{
crc[i] = 0;
}
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
SCREENSHOT_Update(true);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(true);
#endif
}
@@ -158,23 +161,25 @@ static inline const AIRCOPY_Segment_t *AIRCOPY_FindSegmentForOffset(uint16_t off
return NULL;
}
static inline void AIRCOPY_CheckComplete(void)
static inline void AIRCOPY_CheckComplete(uint16_t *num)
{
*num = *num + 1;
const AIRCOPY_TransferMap_t *map = AIRCOPY_GetCurrentMap();
uint16_t done = gAirCopyBlockNumber + gErrorsDuringAirCopy;
if (done >= map->total_blocks)
{
gAircopyState = AIRCOPY_COMPLETE;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
SCREENSHOT_Update(false);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(false);
#endif
}
}
static inline void AIRCOPY_Obfuscation(void)
void AIRCOPY_Obfuscate(unsigned int count)
{
for (unsigned int i = 0; i < 34; i++) {
for (unsigned int i = 0; i < count; i++) {
g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8];
}
}
@@ -218,8 +223,8 @@ bool AIRCOPY_SendMessage(void)
// Check if transfer is complete
if (CurrentSegmentIndex >= map->num_segments) {
gAircopyState = AIRCOPY_COMPLETE;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
SCREENSHOT_Update(false);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(false);
#endif
return 0;
}
@@ -230,7 +235,7 @@ bool AIRCOPY_SendMessage(void)
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
AIRCOPY_Obfuscation();
AIRCOPY_Obfuscate(34);
RADIO_SetTxParameters();
@@ -257,21 +262,18 @@ void AIRCOPY_StorePacket(void)
BK4819_PrepareFSKReceive();
if ((Status & 0x0010U) != 0 || g_FSK_Buffer[0] != 0xABCD || g_FSK_Buffer[35] != 0xDCBA) {
gErrorsDuringAirCopy++;
BK4819_ResetFSK(); // <- important
BK4819_PrepareFSKReceive(); // <- re-arm proprement
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
AIRCOPY_Obfuscation();
AIRCOPY_Obfuscate(34);
uint16_t Crc = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
if (g_FSK_Buffer[34] != Crc) {
gErrorsDuringAirCopy++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
@@ -280,36 +282,18 @@ void AIRCOPY_StorePacket(void)
const AIRCOPY_Segment_t *seg = AIRCOPY_FindSegmentForOffset(Offset);
if (seg == NULL) {
gErrorsDuringAirCopy++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
if (seg->write_mode == AIRCOPY_WRITE_BYTES)
{
/* Raw bytes stream, written in 8-byte EEPROM chunks */
const uint8_t *p8 = (const uint8_t *)&g_FSK_Buffer[2];
const uint8_t *pData = (const uint8_t *)&g_FSK_Buffer[2];
for (unsigned int i = 0; i < 8; i++)
{
EEPROM_WriteBuffer(Offset + (i * 8), p8 + (i * 8));
}
}
else
for (unsigned int i = 0; i < 8; i++)
{
/* Structured data path (kept as-is) */
const uint16_t *pData = &g_FSK_Buffer[2];
for (unsigned int i = 0; i < 8; i++)
{
EEPROM_WriteBuffer(Offset, pData);
pData += 4;
Offset += 8;
}
EEPROM_WriteBuffer(Offset + (i * 8), pData + (i * 8));
}
gAirCopyBlockNumber++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gAirCopyBlockNumber);
}
static void AIRCOPY_InitTransfer(bool isSendMode)
+4
View File
@@ -106,5 +106,9 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
const AIRCOPY_TransferMap_t* AIRCOPY_GetCurrentMap(void);
// XOR-obfuscate `count` words of g_FSK_Buffer starting at index 1.
// Self-inverse: applying twice restores the original buffer.
void AIRCOPY_Obfuscate(unsigned int count);
#endif // ENABLE_AIRCOPY
#endif // APP_AIRCOPY_H
+435 -59
View File
@@ -24,6 +24,9 @@
#ifdef ENABLE_AIRCOPY
#include "app/aircopy.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include "app/beam.h"
#endif
#include "app/app.h"
#include "app/chFrScanner.h"
#include "app/dtmf.h"
@@ -36,6 +39,9 @@
#include "app/generic.h"
#include "app/main.h"
#include "app/menu.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#include "app/scanner.h"
#if defined(ENABLE_UART) || defined(ENABLE_USB)
#include "app/uart.h"
@@ -69,24 +75,39 @@
#include "sram-overlay.h"
#endif
#include "ui/battery.h"
#include "ui/helper.h"
#include "ui/inputbox.h"
#include "ui/main.h"
#include "ui/menu.h"
#include "ui/status.h"
#include "ui/ui.h"
#include "ui/welcome.h"
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#include "screenshot.h"
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "k5viewer.h"
#endif
static bool flagSaveVfo;
static bool flagSaveSettings;
static bool flagSaveChannel;
#ifdef ENABLE_FEAT_F4HWN_SLEEP
static KEY_Code_t gSleepWakeKey = KEY_INVALID;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
static bool gScreenSaverDisplayed;
static uint8_t gScreenSaverTick;
static uint16_t gMatrixRandom = 0xACE1;
static uint8_t gMatrixHeads[32];
static uint8_t gMatrixSpeeds[32];
static KEY_Code_t gScreenSaverWakeKey = KEY_INVALID;
#endif
static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void (*ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) = {
void (*const ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) = {
[DISPLAY_MAIN] = &MAIN_ProcessKeys,
[DISPLAY_MENU] = &MENU_ProcessKeys,
[DISPLAY_SCANNER] = &SCANNER_ProcessKeys,
@@ -98,20 +119,203 @@ void (*ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
#ifdef ENABLE_AIRCOPY
[DISPLAY_AIRCOPY] = &AIRCOPY_ProcessKeys,
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
[DISPLAY_RXTX_LOG] = &RXTX_LOG_ProcessKeys,
#endif
};
#ifdef ENABLE_REGA
// This is a hack for REGA as I need a special display element only for it with no key
static_assert(ARRAY_SIZE(ProcessKeysFunctions) == DISPLAY_N_ELEM-1);
#else
static_assert(ARRAY_SIZE(ProcessKeysFunctions) == DISPLAY_N_ELEM);
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
static uint8_t ScreenSaverRandom(void)
{
gMatrixRandom = (uint16_t)(gMatrixRandom * 2053u + 13849u);
return (uint8_t)(gMatrixRandom >> 8);
}
static void ScreenSaverSetPixel(uint8_t x, uint8_t y, bool fill)
{
const uint8_t pattern = 1u << (y & 7u);
uint8_t *target = (y < 8) ? &gStatusLine[x] : &gFrameBuffer[(y >> 3) - 1][x];
if (fill)
*target |= pattern;
else
*target &= (uint8_t)~pattern;
}
static void ScreenSaverDrawMatrixGlyph(uint8_t x, int16_t y, uint8_t glyph)
{
static const uint8_t glyphs[][3] = {
{0x1F, 0x11, 0x1F},
{0x15, 0x1F, 0x15},
{0x1D, 0x15, 0x17},
{0x17, 0x15, 0x1D},
{0x1F, 0x04, 0x1F},
{0x1B, 0x15, 0x1B},
{0x0E, 0x11, 0x0E},
{0x11, 0x0A, 0x04}
};
for (uint8_t dx = 0; dx < 3; dx++) {
uint8_t pixels = glyphs[glyph & 7u][dx];
for (uint8_t dy = 0; dy < 5; dy++) {
const int16_t py = y + dy;
if ((pixels & (1u << dy)) && py >= 0 && py < LCD_HEIGHT)
ScreenSaverSetPixel(x + dx, (uint8_t)py, true);
}
}
}
static void ScreenSaverRenderMatrix(bool reset)
{
if (reset) {
for (uint8_t i = 0; i < ARRAY_SIZE(gMatrixHeads); i++) {
gMatrixHeads[i] = ScreenSaverRandom() % 112u;
gMatrixSpeeds[i] = 1u + (ScreenSaverRandom() % 3u);
}
} else {
for (uint8_t i = 0; i < ARRAY_SIZE(gMatrixHeads); i++) {
gMatrixHeads[i] += gMatrixSpeeds[i];
if (gMatrixHeads[i] > 112u)
gMatrixHeads[i] = ScreenSaverRandom() & 0x0Fu;
}
}
UI_StatusClear();
UI_DisplayClear();
for (uint8_t col = 0; col < ARRAY_SIZE(gMatrixHeads); col++) {
const uint8_t x = col * 4u;
for (uint8_t trail = 0; trail < 8; trail++) {
const int16_t y = (int16_t)gMatrixHeads[col] - ((int16_t)trail * 7);
const uint8_t glyph = (uint8_t)(col + trail + gMatrixHeads[col]);
if (trail == 0) {
ScreenSaverDrawMatrixGlyph(x, y, glyph);
} else if ((trail < 4) || ((glyph & 1u) != 0)) {
ScreenSaverDrawMatrixGlyph(x, y, glyph);
}
}
}
ST7565_BlitStatusLine();
ST7565_BlitFullScreen();
}
static void ScreenSaverScrollLogoLine(uint8_t *line)
{
const uint8_t first = line[0];
memmove(line, line + 1, LCD_WIDTH - 1);
line[LCD_WIDTH - 1] = first;
}
static void ScreenSaverRenderLogoPlus(bool reset)
{
if (reset) {
UI_DisplayLogo();
return;
}
ScreenSaverScrollLogoLine(gStatusLine);
for (uint8_t line = 0; line < FRAME_LINES; line++)
ScreenSaverScrollLogoLine(gFrameBuffer[line]);
ST7565_BlitStatusLine();
ST7565_BlitFullScreen();
}
static void ScreenSaverUpdateViewer(void)
{
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(false);
#endif
}
static bool ScreenSaverCanDisplay(void)
{
if (gSetting_set_sav == SET_SAV_OFF ||
gEeprom.BACKLIGHT_TIME == 0 ||
gEeprom.BACKLIGHT_TIME >= 61 ||
gScreenSaverDisplayed ||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
gWakeUp ||
#endif
gCurrentFunction == FUNCTION_TRANSMIT ||
FUNCTION_IsRx() ||
gPttIsPressed
#ifdef ENABLE_FMRADIO
|| (gFM_ScanState != FM_SCAN_OFF && !gFM_FoundFrequency)
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
|| gBeamActive
#endif
)
{
return false;
}
if (gScreenToDisplay == DISPLAY_MAIN)
return true;
#ifdef ENABLE_FMRADIO
if (gScreenToDisplay == DISPLAY_FM)
return true;
#endif
return false;
}
static void ScreenSaverTryDisplay(void)
{
if (!ScreenSaverCanDisplay())
return;
if (gSetting_set_sav == SET_SAV_LOGO)
UI_DisplayLogo();
else if (gSetting_set_sav == SET_SAV_LOGO_PLUS)
ScreenSaverRenderLogoPlus(true);
else if (gSetting_set_sav == SET_SAV_MATRIX)
ScreenSaverRenderMatrix(true);
gScreenSaverDisplayed = true;
gScreenSaverTick = 0;
gUpdateDisplay = false;
gUpdateStatus = false;
ScreenSaverUpdateViewer();
}
static void ScreenSaverExit(void)
{
if (gScreenSaverDisplayed) {
gScreenSaverDisplayed = false;
gUpdateDisplay = true;
gUpdateStatus = true;
}
}
#endif
bool APP_IsScreenSaverDisplayed(void)
{
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
return gScreenSaverDisplayed;
#else
return false;
#endif
}
static void CheckForIncoming(void)
{
if (!g_SquelchLost)
return; // squelch is closed
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverExit();
#endif
// squelch is open
if (gScanStateDir == SCAN_OFF)
@@ -492,6 +696,10 @@ void APP_StartListening(FUNCTION_Type_t function)
{
const unsigned int vfo = gEeprom.RX_VFO;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverExit();
#endif
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
gRxTimerCountdown_500ms = 7200;
#endif
@@ -557,11 +765,7 @@ void APP_StartListening(FUNCTION_Type_t function)
gUpdateStatus = true;
}
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 to 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
#ifdef ENABLE_VOICE
if (gVoiceWriteIndex == 0) // AM/FM RX mode will be set when the voice has finished
@@ -570,6 +774,10 @@ void APP_StartListening(FUNCTION_Type_t function)
FUNCTION_Select(function);
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
RXTX_LOG_BeginRx(gRxVfo, function);
#endif
#ifdef ENABLE_FMRADIO
if (function == FUNCTION_MONITOR || gFmRadioMode)
#else
@@ -800,6 +1008,25 @@ static void CheckRadioInterrupts(void)
AIRCOPY_StorePacket();
}
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
if ((interrupts.fskFifoAlmostFull || interrupts.fskRxFinied) &&
gBeamActive &&
gBeamMode == BEAM_MODE_RX &&
(gBeamStatus == BEAM_STATUS_RX_WAIT || gBeamStatus == BEAM_STATUS_ERROR))
{
const unsigned int wordsToRead = interrupts.fskRxFinied ? (36 - gFSKWriteIndex) : 4;
for (unsigned int i = 0; i < wordsToRead; i++) {
const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F);
if (gFSKWriteIndex < 36)
g_FSK_Buffer[gFSKWriteIndex++] = word;
}
gBeamRxWordCount = gFSKWriteIndex;
gUpdateDisplay = true;
BEAM_StorePacket();
}
#endif
}
}
@@ -816,6 +1043,22 @@ void APP_EndTransmission(void)
}
}
void APP_HandleEndTransmission(void) {
if (gFlagEndTransmission) {
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
FUNCTION_Select(FUNCTION_FOREGROUND);
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
gFlagEndTransmission = false;
}
#ifdef ENABLE_VOX
static void HandleVox(void)
{
@@ -851,24 +1094,10 @@ static void HandleVox(void)
gVOX_NoiseDetected = false;
if (gCurrentFunction == FUNCTION_TRANSMIT && !gPttIsPressed && !gVOX_NoiseDetected) {
if (gFlagEndTransmission) {
//if (gCurrentFunction != FUNCTION_FOREGROUND)
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0) {
//if (gCurrentFunction != FUNCTION_FOREGROUND)
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
APP_HandleEndTransmission();
gUpdateStatus = true;
gUpdateDisplay = true;
gFlagEndTransmission = false;
}
return;
}
@@ -892,10 +1121,10 @@ static void HandleVox(void)
void APP_Update(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Parse incoming packets on every tick so serial keys are never missed,
// regardless of whether the screen needs redrawing.
SCREENSHOT_ParseInput();
K5VIEWER_ParseInput();
#endif
#ifdef ENABLE_VOICE
@@ -996,7 +1225,7 @@ void APP_Update(void)
APP_EndTransmission();
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
RADIO_SetVfoState(VFO_STATE_TIMEOUT);
@@ -1046,6 +1275,9 @@ void APP_Update(void)
&& gScanStateDir == SCAN_OFF
&& !gPttIsPressed
&& gCurrentFunction != FUNCTION_POWER_SAVE
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
#ifdef ENABLE_VOICE
&& gVoiceWriteIndex == 0
#endif
@@ -1124,7 +1356,11 @@ void APP_Update(void)
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF &&
gScanStateDir == SCAN_OFF &&
!gCssBackgroundScan)
!gCssBackgroundScan
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
)
{ // dual watch mode, toggle between the two VFO's
DualwatchAlternate();
goToSleep = false;
@@ -1135,7 +1371,11 @@ void APP_Update(void)
gPowerSave_10ms = power_save1_10ms; // come back here in a bit
gRxIdleMode = false; // RX is awake
}
else if (gEeprom.DUAL_WATCH == DUAL_WATCH_OFF || gScanStateDir != SCAN_OFF || gCssBackgroundScan || goToSleep)
else if (
#ifdef ENABLE_FEAT_F4HWN_BEAM
!gBeamActive &&
#endif
(gEeprom.DUAL_WATCH == DUAL_WATCH_OFF || gScanStateDir != SCAN_OFF || gCssBackgroundScan || goToSleep))
{ // dual watch mode off or scanning or rssi update request
// go back to sleep
@@ -1154,7 +1394,11 @@ void APP_Update(void)
// Authentic device checked removed
}
else {
else
#ifdef ENABLE_FEAT_F4HWN_BEAM
if (!gBeamActive)
#endif
{
// toggle between the two VFO's
DualwatchAlternate();
gPowerSave_10ms = power_save1_10ms;
@@ -1338,6 +1582,10 @@ void APP_TimeSlice10ms(void)
SETTINGS_SaveVfoIndicesFlush();
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
RXTX_LOG_Task10ms();
#endif
BACKLIGHT_Update();
gFlashLightBlinkCounter++;
@@ -1381,6 +1629,36 @@ void APP_TimeSlice10ms(void)
if (gUpdateDisplayCurrent) {
gUpdateDisplay = false;
}
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
bool screenSaverRendered = false;
if (gScreenSaverDisplayed) {
if (gUpdateDisplayCurrent) {
gUpdateDisplayCurrent = false;
} else if (gUpdateStatusCurrent) {
gUpdateStatusCurrent = false;
gUpdateStatus = false;
}
if (gSetting_set_sav == SET_SAV_MATRIX) {
if (++gScreenSaverTick >= 8u) {
gScreenSaverTick = 0;
ScreenSaverRenderMatrix(false);
screenSaverRendered = true;
}
} else if (gSetting_set_sav == SET_SAV_LOGO_PLUS) {
if (++gScreenSaverTick >= 16u) {
gScreenSaverTick = 0;
ScreenSaverRenderLogoPlus(false);
screenSaverRendered = true;
}
}
}
#endif
if (gUpdateDisplayCurrent) {
GUI_DisplayScreen();
}
@@ -1388,9 +1666,15 @@ void APP_TimeSlice10ms(void)
UI_DisplayStatus();
}
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
if (gUpdateDisplayCurrent || gUpdateStatusCurrent) {
SCREENSHOT_Update(false);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
if (gUpdateDisplayCurrent || gUpdateStatusCurrent
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|| screenSaverRendered
#endif
) {
K5VIEWER_Update(false);
} else if (K5VIEWER_HasPendingStateChange()) {
K5VIEWER_Update(false);
}
#endif
@@ -1437,8 +1721,7 @@ void APP_TimeSlice10ms(void)
if (gAlarmState == ALARM_STATE_TXALARM) {
gAlarmState = ALARM_STATE_SITE_ALARM;
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
BK4819_Enable_AfDac_DiscMode_TxDsp();
@@ -1487,6 +1770,10 @@ void APP_TimeSlice10ms(void)
SCANNER_TimeSlice10ms();
#if defined(ENABLE_SCAN_RANGES) && defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
CHFRSCANNER_UpdateCssDetection();
#endif
#ifdef ENABLE_AIRCOPY
if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState == AIRCOPY_TRANSFER && gAirCopyIsSendMode == 1) {
if (!AIRCOPY_SendMessage()) {
@@ -1529,12 +1816,31 @@ void APP_TimeSlice500ms(void)
if (--gKeypadLocked == 0)
gUpdateDisplay = true;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
RXTX_LOG_Tick500ms();
#endif
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
if (gSetting_set_tmr && (gCurrentFunction == FUNCTION_TRANSMIT || FUNCTION_IsRx())) {
const uint16_t timerCountdown = (gCurrentFunction == FUNCTION_TRANSMIT)
? gTxTimerCountdown_500ms
: gRxTimerCountdown_500ms;
if ((timerCountdown & 1u) != 0u)
gUpdateStatus = true;
}
#endif
if (gKeyInputCountdown > 0)
{
if (--gKeyInputCountdown == 0)
{
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex > 0 && gInputBoxIndex < 4) && (!gFmRadioMode))
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex > 0 && gInputBoxIndex < 4)
#ifdef ENABLE_FMRADIO
&& (!gFmRadioMode)
#endif
)
{
channelMoveSwitch();
@@ -1559,7 +1865,7 @@ void APP_TimeSlice500ms(void)
{
if (gDTMF_RX_live[0] != 0)
{
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
gUpdateDisplay = true;
}
}
@@ -1596,27 +1902,41 @@ void APP_TimeSlice500ms(void)
&& gEeprom.BACKLIGHT_TIME < 61
) {
BACKLIGHT_TurnOff();
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverTryDisplay();
#endif
}
#ifdef ENABLE_FEAT_F4HWN_SLEEP
if (gSleepModeCountdown_500ms == gSetting_set_off * 120 && gWakeUp) {
//ST7565_Init();
ST7565_FixInterfGlitch();
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverExit();
#endif
BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false);
gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10;
gWakeUp = false;
gUpdateDisplay = true;
gUpdateStatus = true;
}
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx()
#ifdef ENABLE_AIRCOPY
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx() && gScreenToDisplay != DISPLAY_AIRCOPY)
#else
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx())
&& gScreenToDisplay != DISPLAY_AIRCOPY
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
)
{
if (gSleepModeCountdown_500ms > 0 && --gSleepModeCountdown_500ms == 0) {
gBacklightCountdown_500ms = 0;
gPowerSave_10ms = 1;
gWakeUp = true;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverExit();
#endif
// TODO:
// PWM_PLUS0_CH0_COMP = 0;
BACKLIGHT_SetBrightness(0);
@@ -1697,6 +2017,9 @@ void APP_TimeSlice500ms(void)
#endif
#ifdef ENABLE_AIRCOPY
&& gScreenToDisplay != DISPLAY_AIRCOPY
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
) {
if (gEeprom.AUTO_KEYPAD_LOCK && gKeyLockCountdown > 0 && !gDTMF_InputMode
@@ -1771,6 +2094,17 @@ void APP_TimeSlice500ms(void)
#endif
}
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
if (gBacklightCountdown_500ms == 0 &&
gEeprom.BACKLIGHT_TIME > 0 &&
gEeprom.BACKLIGHT_TIME < 61 &&
!gAskToSave &&
!gCssBackgroundScan)
{
ScreenSaverTryDisplay();
}
#endif
BATTERY_TimeSlice500ms();
SCANNER_TimeSlice500ms();
UI_MAIN_TimeSlice500ms();
@@ -1832,24 +2166,53 @@ static void ALARM_Off(void)
static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
#ifdef ENABLE_FEAT_F4HWN_SLEEP
#ifdef ENABLE_FEAT_F4HWN_SLEEP
if (gSleepWakeKey != KEY_INVALID) {
if (Key == gSleepWakeKey && !bKeyPressed)
gSleepWakeKey = KEY_INVALID;
return;
}
if(gWakeUp)
{
if(!bKeyPressed || Key == KEY_PTT)
if(bKeyPressed || Key == KEY_PTT)
{
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverExit();
#endif
BACKLIGHT_TurnOn();
if(Key != KEY_PTT)
{
Key = KEY_INVALID;
gSleepWakeKey = Key;
gBeepToPlay = BEEP_NONE;
return;
}
}
else
else if(Key != KEY_PTT)
{
Key = KEY_INVALID;
}
}
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
if (gScreenSaverWakeKey != KEY_INVALID) {
if (Key == gScreenSaverWakeKey && !bKeyPressed)
gScreenSaverWakeKey = KEY_INVALID;
return;
}
if (bKeyPressed && gScreenSaverDisplayed) {
ScreenSaverExit();
BACKLIGHT_TurnOn();
if (Key != KEY_PTT) {
gScreenSaverWakeKey = Key;
gBeepToPlay = BEEP_NONE;
return;
}
}
#endif
#endif
if (Key == KEY_EXIT && !BACKLIGHT_IsOn() && gEeprom.BACKLIGHT_TIME > 0)
{ // just turn the light on for now so the user can see what's what
@@ -1907,7 +2270,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
if (Key == KEY_EXIT && bKeyHeld) { // exit key held pressed
// clear the live DTMF decoder
if (gDTMF_RX_live[0] != 0) {
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
gDTMF_RX_live_timeout = 0;
gUpdateDisplay = true;
}
@@ -1943,12 +2306,10 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
bool lowBatPopup = gLowBattery && !gLowBatteryConfirmed && gScreenToDisplay == DISPLAY_MAIN;
#ifdef ENABLE_FEAT_F4HWN // Disable PTT if KEY_LOCK
bool lck_condition = false;
bool lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
if(gSetting_set_lck)
lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
else
lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT && Key != KEY_PTT;
if((gSetting_set_lck & SET_LCK_PTT) == 0)
lck_condition = lck_condition && Key != KEY_PTT;
if (lck_condition)
#else
@@ -1964,6 +2325,12 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
return;
}
// The ACTIONS restriction only applies when the keypad is actually
// locked: this block is also entered for the low battery popup, where
// action keys must keep working as before
const bool passActionKey = ((gSetting_set_lck & SET_LCK_ACTIONS) == 0 || !gEeprom.KEY_LOCK) &&
(Key == KEY_SIDE1 || Key == KEY_SIDE2 || (Key == KEY_MENU && bKeyHeld));
if (Key == KEY_F) { // function/key-lock key
if (!bKeyPressed)
return;
@@ -1978,8 +2345,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
// KEY_MENU has a special treatment here, because we want to pass hold event to ACTION_Handle
// but we don't want it to complain when initial press happens
// we want to react on realese instead
else if (Key != KEY_SIDE1 && Key != KEY_SIDE2 && // pass side buttons
!(Key == KEY_MENU && bKeyHeld)) // pass KEY_MENU held
else if (!passActionKey)
{
if ((!bKeyPressed || bKeyHeld || (Key == KEY_MENU && bKeyPressed)) && // prevent released or held, prevent KEY_MENU pressed
!(Key == KEY_MENU && !bKeyPressed)) // pass KEY_MENU released
@@ -2089,7 +2455,8 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
}
}
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
else if ((!bKeyHeld && bKeyPressed) || (gAlarmState == ALARM_STATE_TX1750 && bKeyHeld && !bKeyPressed)) {
// else if ((!bKeyHeld && bKeyPressed) || (gAlarmState == ALARM_STATE_TX1750 && bKeyHeld && !bKeyPressed)) {
else if ((bKeyHeld != bKeyPressed) && (gAlarmState == ALARM_STATE_TX1750 || bKeyPressed)) {
ALARM_Off();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
@@ -2104,12 +2471,21 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
}
#endif
}
else if (gScreenToDisplay != DISPLAY_INVALID && (
else if (
#ifdef ENABLE_FEAT_F4HWN_BEAM
gBeamActive ||
#endif
(gScreenToDisplay != DISPLAY_INVALID && (
(Key != KEY_SIDE1 && Key != KEY_SIDE2)
#ifdef ENABLE_FEAT_F4HWN // For F + SIDE1 or F + SIDE2
|| (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2))
#endif
)) {
))) {
#ifdef ENABLE_FEAT_F4HWN_BEAM
if (gBeamActive)
BEAM_ProcessKeys(Key, bKeyPressed, bKeyHeld);
else
#endif
ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld);
}
else if (!SCANNER_IsScanning()
+2 -1
View File
@@ -24,12 +24,13 @@
#include "radio.h"
void APP_EndTransmission(void);
void APP_HandleEndTransmission(void);
void APP_StartListening(FUNCTION_Type_t function);
uint32_t APP_SetFreqByStepAndLimits(VFO_Info_t *pInfo, int8_t direction, uint32_t lower, uint32_t upper);
uint32_t APP_SetFrequencyByStep(VFO_Info_t *pInfo, int8_t direction);
void APP_Update(void);
void APP_TimeSlice10ms(void);
void APP_TimeSlice500ms(void);
bool APP_IsScreenSaverDisplayed(void);
#endif
+325
View File
@@ -0,0 +1,325 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include <assert.h>
#include <string.h>
#include "app/aircopy.h"
#include "app/beam.h"
#include "audio.h"
#include "driver/bk4819.h"
#include "driver/crc.h"
#include "driver/st7565.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/main.h"
#include "ui/ui.h"
#define BEAM_PACKET_MAGIC 0xBEA5u
#define BEAM_PACKET_VERSION 2u
// One byte per field instead of bit-packing — payload still fits in 64 bytes,
// and avoids costly shift/mask/store sequences on Cortex-M.
typedef struct {
uint16_t magic;
uint8_t version;
uint8_t _pad;
uint32_t rx_frequency;
uint32_t tx_offset_frequency;
uint8_t rx_code;
uint8_t tx_code;
uint8_t rx_codetype;
uint8_t tx_codetype;
uint8_t modulation;
uint8_t tx_offset_direction;
uint8_t tx_lock;
uint8_t busy_channel_lock;
uint8_t output_power;
uint8_t channel_bandwidth;
uint8_t frequency_reverse;
uint8_t dtmf_ptt_id_mode;
uint8_t dtmf_decoding_enable;
uint8_t step_setting;
uint8_t scrambling_type;
uint8_t band;
uint8_t scanlist;
uint8_t compander;
char name[16];
} BEAM_Payload_t;
static_assert(sizeof(BEAM_Payload_t) <= 64);
BEAM_Mode_t gBeamMode = BEAM_MODE_TX;
BEAM_Status_t gBeamStatus = BEAM_STATUS_READY;
uint16_t gBeamCopiedChannel = 0xFFFFu;
uint8_t gBeamRxWordCount;
bool gBeamActive;
static VFO_Info_t gBeamRadioVfo;
static void BEAM_SetRadioToBeamFrequency(void)
{
const uint16_t channel = FREQ_CHANNEL_FIRST + BAND6_400MHz;
RADIO_InitInfo(&gBeamRadioVfo, channel, DEFAULT_FREQ);
gBeamRadioVfo.CHANNEL_BANDWIDTH = BANDWIDTH_NARROW;
gBeamRadioVfo.OUTPUT_POWER = OUTPUT_POWER_LOW1;
RADIO_ConfigureSquelchAndOutputPower(&gBeamRadioVfo);
gRxVfo = &gBeamRadioVfo;
gTxVfo = &gBeamRadioVfo;
gCurrentVfo = gRxVfo;
RADIO_SetupRegisters(true);
BK4819_SetupAircopy();
BK4819_ResetFSK();
}
static void BEAM_SendPacket(void)
{
memset(g_FSK_Buffer, 0, sizeof(g_FSK_Buffer));
g_FSK_Buffer[0] = 0xABCDu;
BEAM_Payload_t * const payload = (BEAM_Payload_t *)&g_FSK_Buffer[2];
const VFO_Info_t *vfo = &gEeprom.VfoInfo[gEeprom.TX_VFO];
payload->magic = BEAM_PACKET_MAGIC;
payload->version = BEAM_PACKET_VERSION;
payload->rx_frequency = vfo->freq_config_RX.Frequency;
payload->tx_offset_frequency = vfo->TX_OFFSET_FREQUENCY;
payload->rx_code = vfo->freq_config_RX.Code;
payload->tx_code = vfo->freq_config_TX.Code;
payload->rx_codetype = vfo->freq_config_RX.CodeType;
payload->tx_codetype = vfo->freq_config_TX.CodeType;
payload->modulation = vfo->Modulation;
payload->tx_offset_direction = vfo->TX_OFFSET_FREQUENCY_DIRECTION;
payload->tx_lock = vfo->TX_LOCK;
payload->busy_channel_lock = vfo->BUSY_CHANNEL_LOCK;
payload->output_power = vfo->OUTPUT_POWER;
payload->channel_bandwidth = vfo->CHANNEL_BANDWIDTH;
payload->frequency_reverse = vfo->FrequencyReverse;
payload->dtmf_ptt_id_mode = vfo->DTMF_PTT_ID_TX_MODE;
#ifdef ENABLE_DTMF_CALLING
payload->dtmf_decoding_enable = vfo->DTMF_DECODING_ENABLE;
#endif
payload->step_setting = vfo->STEP_SETTING;
payload->scrambling_type = vfo->SCRAMBLING_TYPE;
payload->band = vfo->Band;
payload->scanlist = vfo->SCANLIST_PARTICIPATION;
payload->compander = vfo->Compander;
if (IS_MR_CHANNEL(vfo->CHANNEL_SAVE)) {
SETTINGS_FetchChannelName(payload->name, vfo->CHANNEL_SAVE);
} else {
memcpy(payload->name, vfo->Name, sizeof(vfo->Name));
}
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
g_FSK_Buffer[35] = 0xDCBAu;
AIRCOPY_Obfuscate(32);
// Show "SENDING" before the blocking FSK transmission so the user gets
// immediate visual feedback instead of a frozen "BEAM TX" screen.
gBeamStatus = BEAM_STATUS_TX_WAIT;
UI_DisplayMain();
ST7565_BlitFullScreen();
RADIO_SetTxParameters();
BK4819_SendFSKData(g_FSK_Buffer);
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
gBeamStatus = BEAM_STATUS_TX_DONE;
gBeepToPlay = BEEP_880HZ_60MS_TRIPLE_BEEP;
gUpdateDisplay = true;
}
static void BEAM_SavePayloadToFirstFreeChannel(const BEAM_Payload_t *payload)
{
uint16_t channel = 0xFFFFu;
for (uint16_t c = MR_CHANNEL_FIRST; IS_MR_CHANNEL(c); c++) {
if (!RADIO_CheckValidChannel(c, false, 0)) {
channel = c;
break;
}
}
if (channel == 0xFFFFu) {
gBeamStatus = BEAM_STATUS_RX_FULL;
gUpdateDisplay = true;
return;
}
VFO_Info_t vfo;
RADIO_InitInfo(&vfo, channel, payload->rx_frequency);
// CRC + magic + version already validate the payload — no need to clamp fields.
vfo.TX_OFFSET_FREQUENCY = payload->tx_offset_frequency;
vfo.freq_config_RX.Code = payload->rx_code;
vfo.freq_config_TX.Code = payload->tx_code;
vfo.freq_config_RX.CodeType = payload->rx_codetype;
vfo.freq_config_TX.CodeType = payload->tx_codetype;
vfo.TX_OFFSET_FREQUENCY_DIRECTION = payload->tx_offset_direction;
vfo.Modulation = payload->modulation;
vfo.TX_LOCK = payload->tx_lock;
vfo.BUSY_CHANNEL_LOCK = payload->busy_channel_lock;
vfo.OUTPUT_POWER = payload->output_power;
vfo.CHANNEL_BANDWIDTH = payload->channel_bandwidth;
vfo.FrequencyReverse = payload->frequency_reverse;
vfo.DTMF_PTT_ID_TX_MODE = payload->dtmf_ptt_id_mode;
#ifdef ENABLE_DTMF_CALLING
vfo.DTMF_DECODING_ENABLE = payload->dtmf_decoding_enable;
#endif
vfo.STEP_SETTING = payload->step_setting;
vfo.StepFrequency = gStepFrequencyTable[vfo.STEP_SETTING];
vfo.SCRAMBLING_TYPE = payload->scrambling_type;
vfo.Band = payload->band;
vfo.SCANLIST_PARTICIPATION = payload->scanlist;
vfo.Compander = payload->compander;
memcpy(vfo.Name, payload->name, sizeof(vfo.Name));
vfo.Name[sizeof(vfo.Name) - 1] = '\0';
RADIO_ApplyOffset(&vfo);
RADIO_ConfigureSquelchAndOutputPower(&vfo);
SETTINGS_SaveChannel(channel, gEeprom.TX_VFO, &vfo, 3);
SETTINGS_SaveChannelName(channel, vfo.Name);
gBeamCopiedChannel = channel;
gBeamStatus = BEAM_STATUS_RX_SAVED;
gUpdateDisplay = true;
BACKLIGHT_TurnOn();
return;
}
static void BEAM_KeyMenu(void)
{
BEAM_SetRadioToBeamFrequency();
if (gBeamMode == BEAM_MODE_TX) {
BEAM_SendPacket();
} else {
gBeamStatus = BEAM_STATUS_RX_WAIT;
gBeamCopiedChannel = 0xFFFFu;
gBeamRxWordCount = 0;
gFSKWriteIndex = 0;
BK4819_PrepareFSKReceive();
}
}
static void BEAM_KeyExit(void)
{
BK4819_ResetFSK();
if (gBeamMode == BEAM_MODE_RX && gBeamCopiedChannel != 0xFFFFu) {
gEeprom.MrChannel[gEeprom.TX_VFO] = gBeamCopiedChannel;
gEeprom.ScreenChannel[gEeprom.TX_VFO] = gBeamCopiedChannel;
RADIO_ConfigureChannel(gEeprom.TX_VFO, VFO_CONFIGURE_RELOAD);
}
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
GUI_SelectNextDisplay(DISPLAY_MAIN);
gBeamActive = false;
}
void ACTION_Beam(void)
{
gBeamMode = BEAM_MODE_TX;
gBeamStatus = BEAM_STATUS_READY;
gBeamCopiedChannel = 0xFFFFu;
gBeamRxWordCount = 0;
gBeamActive = true;
GUI_SelectNextDisplay(DISPLAY_MAIN);
}
void BEAM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
return;
if (Key != KEY_PTT)
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
switch (Key) {
case KEY_UP:
case KEY_DOWN:
gBeamMode ^= 1; // (gBeamMode == BEAM_MODE_TX) ? BEAM_MODE_RX : BEAM_MODE_TX
gBeamStatus = BEAM_STATUS_READY;
gBeamRxWordCount = 0;
break;
case KEY_MENU:
BEAM_KeyMenu();
break;
case KEY_EXIT:
BEAM_KeyExit();
return;
case KEY_PTT:
break;
default:
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
gUpdateDisplay = true;
}
void BEAM_StorePacket(void)
{
if (gFSKWriteIndex < 36)
return;
gBeamRxWordCount = gFSKWriteIndex;
gFSKWriteIndex = 0;
const uint16_t Status = BK4819_ReadRegister(BK4819_REG_0B);
BK4819_PrepareFSKReceive();
if ((Status & 0x0010U) != 0 || g_FSK_Buffer[0] != 0xABCDu || g_FSK_Buffer[35] != 0xDCBAu)
goto error;
AIRCOPY_Obfuscate(32);
if (g_FSK_Buffer[34] != CRC_Calculate(&g_FSK_Buffer[1], 2 + 64))
goto error;
BEAM_Payload_t * const payload = (BEAM_Payload_t *)&g_FSK_Buffer[2];
if (payload->magic != BEAM_PACKET_MAGIC || payload->version != BEAM_PACKET_VERSION)
goto error;
BEAM_SavePayloadToFirstFreeChannel(payload);
BK4819_ResetFSK();
return;
error:
gBeamStatus = BEAM_STATUS_ERROR;
gBeamRxWordCount = 0;
gUpdateDisplay = true;
BACKLIGHT_TurnOn();
}
#endif // ENABLE_FEAT_F4HWN_BEAM
+53
View File
@@ -0,0 +1,53 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_BEAM_H
#define APP_BEAM_H
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include <stdbool.h>
#include <stdint.h>
#include "driver/keyboard.h"
typedef enum {
BEAM_MODE_TX = 0,
BEAM_MODE_RX
} BEAM_Mode_t;
typedef enum {
BEAM_STATUS_READY = 0,
BEAM_STATUS_TX_DONE,
BEAM_STATUS_RX_WAIT,
BEAM_STATUS_RX_SAVED,
BEAM_STATUS_RX_FULL,
BEAM_STATUS_ERROR,
BEAM_STATUS_TX_WAIT
} BEAM_Status_t;
extern BEAM_Mode_t gBeamMode;
extern BEAM_Status_t gBeamStatus;
extern uint16_t gBeamCopiedChannel;
extern uint8_t gBeamRxWordCount;
extern bool gBeamActive;
void ACTION_Beam(void);
void BEAM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void BEAM_StorePacket(void);
#endif // ENABLE_FEAT_F4HWN_BEAM
#endif // APP_BEAM_H
+115 -212
View File
@@ -16,8 +16,12 @@
#include "app/breakout.h"
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#include "screenshot.h"
#if defined(ENABLE_UART) || defined(ENABLE_USB)
#include "app/uart.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "k5viewer.h"
#endif
static uint32_t randSeed = 1;
@@ -25,9 +29,8 @@ static uint8_t blockAnim = 0;
static bool isInitialized = false;
bool isPaused = false;
bool isBeep = false;
uint8_t levelCountBreackout = 1;
uint8_t levelCountBreakout = 1;
uint16_t tone = 0;
uint16_t score = 0;
@@ -42,115 +45,7 @@ Brick brick[BRICK_NUMBER];
Racket racket;
Ball ball;
const uint8_t BITMAP_block[4][15] =
{
{
0b00011110,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00011110,
},
{
0b00011110,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00011110,
},
{
0b00011110,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00011110,
},
{
0b00011110,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00011110,
}
};
const uint8_t BITMAP_blockOn[15] =
{
0b00011110,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00011110,
};
const uint8_t BITMAP_blockEmpty[15] =
{
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
};
static const uint8_t BRICK_ANIM_PATTERNS[4] = {0b00110001, 0b00101001, 0b00100101, 0b00100011};
// Initialise seed
void srand_custom(uint32_t seed) {
@@ -172,18 +67,16 @@ int randInt(int min, int max) {
void reset(void)
{
ballCount = BALL_NUMBER;
levelCountBreackout = 1;
levelCountBreakout = 1;
score = 0;
}
// PlayBeep
void playBeep(uint16_t tone)
{
BK4819_PlayTone(tone, true); // 500 Hz ON
BK4819_PrepareToPlayTone(true);
AUDIO_AudioPathOn();
BK4819_ExitTxMute();
SYSTEM_DelayMs(100);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone, 100);
AUDIO_AudioPathOff();
}
@@ -191,10 +84,10 @@ void playBeep(uint16_t tone)
void drawScore()
{
// Clean status line
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
// Level
sprintf(str, "Level %02u", levelCountBreackout);
sprintf(str, "Level %02u", levelCountBreakout);
GUI_DisplaySmallest(str, 0, 1, true, true);
// Ball
@@ -206,6 +99,12 @@ void drawScore()
GUI_DisplaySmallest(str, 88, 1, true, true);
}
// Render the ball
void renderBall(bool state) {
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w - 1, ball.y + ball.h - 1, state);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, state);
}
// Init ball
void initBall() {
ball.x = 62;
@@ -215,21 +114,25 @@ void initBall() {
ball.dx = 0;
ball.dy = 1;
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h - 1, true);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, true);
renderBall(true);
}
// Calculate the direction of the bounced ball
void directionBall(int16_t x, uint8_t w, int8_t num) {
ball.dx = map(x + w - ball.x, 0, w, num, -num);
ball.dy *= -1;
}
// Draw ball
void drawBall() {
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h -1, false);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, false);
renderBall(false);
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.y <= 0) // Up
{
ball.dx = map(randInt(0, 7), 0, 7, 3, -3);
ball.dx = randInt(-3, 3);
ball.dy = 1;
}
else if (ball.x <= 2) // Left
@@ -243,9 +146,7 @@ void drawBall() {
// And now Down...
if (ball.y == 47) {
if (ball.x + 1 >= racket.x && ball.x - 1 <= racket.x + racket.w) {
ball.dx = map(racket.x + racket.w - ball.x, 0, racket.w, 3, -3);
ball.dy *= -1;
isBeep = true;
directionBall(racket.x, racket.w, 3);
tone = 400;
}
}
@@ -253,7 +154,6 @@ void drawBall() {
ballCount--;
UI_DisplayClear();
drawScore();
isBeep = true;
tone = 800;
if (ballCount < 0) {
reset();
@@ -261,15 +161,13 @@ void drawBall() {
drawWall();
isPaused = true;
UI_PrintStringSmallBold("GAME", 32, 0, 4);
UI_PrintStringSmallBold("OVER", 66, 0, 4);
UI_PrintStringSmallBold("GAME OVER", 0, LCD_WIDTH - 1, 4);
}
initRacket();
initBall();
}
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h - 1, true);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, true);
renderBall(true);
}
// Init wall
@@ -289,7 +187,6 @@ void initWall() {
brick[i].y = -8 + 8 * k;
brick[i].w = 14;
brick[i].h = 5;
brick[i].s = randInt(0, 3);
brick[i].destroy = false;
j++;
@@ -302,6 +199,11 @@ void drawWall() {
for (i = 0; i < BRICK_NUMBER; i++) {
if (brick[i].destroy == false) {
uint8_t *fb_ptr = gFrameBuffer[brick[i].y / 8] + brick[i].x;
fb_ptr[0] = 0b00011110;
fb_ptr[14] = 0b00011110;
if ((ball.x + 1 >= brick[i].x &&
ball.x - 1 <= brick[i].x + brick[i].w) &&
((ball.y + 1 >= brick[i].y &&
@@ -309,32 +211,37 @@ void drawWall() {
brick[i].destroy = true;
score++;
ball.dx = map(brick[i].x + brick[i].w - ball.x, 0, brick[i].w, 2, -2);
ball.dy *= -1;
directionBall(brick[i].x, brick[i].w, 2);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_blockOn, sizeof(BITMAP_blockOn));
memset(fb_ptr + 1, 0b00111111, 13);
ST7565_BlitLine(brick[i].y / 8);
playBeep(600);
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_blockEmpty, sizeof(BITMAP_blockEmpty));
memset(fb_ptr + 0, 0b00000000, 15);
ST7565_BlitLine(brick[i].y / 8);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
if (score % BRICK_NUMBER == 0) {
levelCountBreackout++;
levelCountBreakout++;
ballCount++;
initWall();
return;
}
}
if (brick[i].destroy == false) {
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_block[(brick[i].s + blockAnim) % 4], sizeof(BITMAP_block[(brick[i].s + blockAnim) % 4]));
} else {
for(uint8_t k = 0; k < 13; k++) {
fb_ptr[k + 1] = BRICK_ANIM_PATTERNS[(blockAnim + k) % 4];
}
}
}
}
}
// Render the racket shape
void renderRacket(int x, bool state) {
UI_DrawRectangleBuffer(gFrameBuffer, x + 1, racket.y, x + racket.w - 2, racket.y + racket.h, state);
UI_DrawLineBuffer(gFrameBuffer, x, racket.y + 1, x + racket.w - 1, racket.y + 1, state);
}
// Init racket
void initRacket() {
racket.w = 24;
@@ -343,18 +250,15 @@ void initRacket() {
racket.h = 2;
racket.p = racket.x;
UI_DrawRectangleBuffer(gFrameBuffer, racket.x + 1, racket.y, racket.x + racket.w - 2, racket.y + racket.h, true);
UI_DrawLineBuffer(gFrameBuffer, racket.x, racket.y + 1, racket.x + racket.w - 1, racket.y + 1, true);
renderRacket(racket.x, true);
}
// Draw racket
void drawRacket() {
if (racket.p != racket.x) {
UI_DrawRectangleBuffer(gFrameBuffer, racket.p + 1, racket.y, racket.p + racket.w - 2, racket.y + racket.h, false);
UI_DrawLineBuffer(gFrameBuffer, racket.p, racket.y + 1, racket.p + racket.w - 1, racket.y + 1, false);
renderRacket(racket.p, false);
racket.p = racket.x;
UI_DrawRectangleBuffer(gFrameBuffer, racket.x + 1, racket.y, racket.x + racket.w - 2, racket.y + racket.h, true);
UI_DrawLineBuffer(gFrameBuffer, racket.x, racket.y + 1, racket.x + racket.w - 1, racket.y + 1, true);
renderRacket(racket.x, true);
}
}
@@ -382,7 +286,7 @@ static void OnKeyDown(uint8_t key)
kbd.counter = 0;
if(isPaused)
{
UI_PrintStringSmallBold("PAUSE", 0, 128, 4);
UI_PrintStringSmallBold("PAUSE", 0, LCD_WIDTH - 1, 4);
}
break;
case KEY_EXIT:
@@ -404,10 +308,10 @@ static void OnKeyDown(uint8_t key)
// HandleUserInput
static bool HandleUserInput()
{
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Parse incoming packets on every tick so serial keys are never missed,
// regardless of whether the screen needs redrawing.
SCREENSHOT_ParseInput();
K5VIEWER_ParseInput();
#endif
kbd.prev = kbd.current;
@@ -416,17 +320,13 @@ static bool HandleUserInput()
if (kbd.current == KEY_INVALID)
return true;
// Movement keys: dispatch on every tick while held
if (kbd.current == KEY_UP || kbd.current == KEY_DOWN ||
kbd.current == KEY_4 || kbd.current == KEY_0)
{
OnKeyDown(kbd.current);
return true;
}
// Action keys (MENU, EXIT): dispatch only on rising edge
if (kbd.current != kbd.prev)
{
if (
// Movement keys: dispatch on every tick while held
(kbd.current == KEY_UP || kbd.current == KEY_DOWN ||
kbd.current == KEY_4 || kbd.current == KEY_0) ||
// Action keys (MENU, EXIT): dispatch only on rising edge
(kbd.current != kbd.prev)
){
OnKeyDown(kbd.current);
}
@@ -441,62 +341,65 @@ static void Tick()
// APP_RunBreakout
void APP_RunBreakout(void) {
static uint8_t swap = 0;
static uint8_t swap = 0;
// Init seed
srand_custom(BK4819_ReadRegister(BK4819_REG_67) & 0x01FF * gBatteryVoltageAverage * gEeprom.VfoInfo[0].pRX->Frequency);
// Init seed
srand_custom(BK4819_ReadRegister(BK4819_REG_67) & 0x01FF * gBatteryVoltageAverage * gEeprom.VfoInfo[0].pRX->Frequency);
// Init led
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Init led
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Finish the brightness fade if it is in progress
BACKLIGHT_UpdateTickless();
// Finish the brightness fade if it is in progress
BACKLIGHT_UpdateTickless();
// Init game
UI_DisplayClear();
reset();
initWall();
initRacket();
initBall();
memset(gStatusLine, 0, sizeof(gStatusLine));
isInitialized = true;
// Init game
UI_DisplayClear();
reset();
initWall();
initRacket();
initBall();
UI_StatusClear();
isInitialized = true;
while(isInitialized)
while(isInitialized)
{
#if defined(ENABLE_UART) || defined(ENABLE_USB)
UART_ServiceCommands();
#endif
Tick();
if(!isPaused)
{
Tick();
if(!isPaused)
if(swap == 0)
{
if(swap == 0)
{
blockAnim = (blockAnim + 1) % 4;
}
swap = (swap + 1) % 4;
drawScore();
drawWall();
drawRacket();
drawBall();
if(isBeep)
{
playBeep(tone);
isBeep = false;
}
else
{
SYSTEM_DelayMs(40 - MIN(levelCountBreackout - 1, 20)); // Add more fun...
}
blockAnim = (blockAnim + 1) % 4;
}
swap = (swap + 1) % 4;
ST7565_BlitStatusLine(); // Blank status line
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
if(isPaused || swap == 0)
{
SCREENSHOT_Update(false);
}
#endif
drawScore();
drawWall();
drawRacket();
drawBall();
if(tone != 0)
{
playBeep(tone);
tone = 0;
}
else
{
SYSTEM_DelayMs(40 - MIN(levelCountBreakout - 1, 20)); // Add more fun...
}
}
}
ST7565_BlitStatusLine(); // Blank status line
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
if(isPaused || swap == 0)
{
K5VIEWER_Update(false);
}
#endif
}
}
-1
View File
@@ -49,7 +49,6 @@ typedef struct {
uint8_t y; // y
uint8_t w; // width
uint8_t h; // height
uint8_t s; // style
bool destroy; // active, if true, check this button, else bypass
} Brick;
+830 -7
View File
@@ -1,9 +1,17 @@
#include <stddef.h>
#include <string.h>
#include "app/app.h"
#include "app/chFrScanner.h"
#include "audio.h"
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
#include "driver/systick.h"
#endif
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "ui/main.h"
//#include "debugging.h"
int8_t gScanStateDir;
@@ -13,6 +21,29 @@ bool gScanPauseMode;
#ifdef ENABLE_SCAN_RANGES
uint32_t gScanRangeStart;
uint32_t gScanRangeStop;
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
DCS_CodeType_t gScanRangeCssType = CODE_TYPE_OFF;
uint8_t gScanRangeCssCode = 0xFF;
static uint8_t scanRangeCssCandidate = 0xFF;
static uint8_t scanRangeCssHitCount = 0;
#endif
#define SCAN_RANGE_SKIP_MAX 64
#if (SCAN_RANGE_SKIP_MAX & (SCAN_RANGE_SKIP_MAX - 1)) != 0
#error SCAN_RANGE_SKIP_MAX must be a power of two
#endif
typedef struct {
uint16_t sample[SCAN_RANGE_SKIP_MAX];
uint32_t start;
uint32_t stop;
uint16_t step;
uint8_t count;
uint8_t next;
} ScanRangeSkipList_t;
static ScanRangeSkipList_t scanRangeSkip;
#endif
typedef enum {
@@ -36,13 +67,685 @@ uint8_t initialCROSS_BAND_RX_TX;
static void NextFreqChannel(void);
static void NextMemChannel(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static void ScanFastResetState(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static bool ScanFastEnabled(void)
{
return gSetting_set_scn;
}
#endif
#ifdef ENABLE_SCAN_RANGES
static void ScanRangeSkipClear(void)
{
scanRangeSkip.count = 0;
scanRangeSkip.next = 0;
}
static void ScanRangeSkipSync(void)
{
const uint16_t step = gRxVfo->StepFrequency;
if (scanRangeSkip.start == gScanRangeStart &&
scanRangeSkip.stop == gScanRangeStop &&
scanRangeSkip.step == step)
{
return;
}
ScanRangeSkipClear();
scanRangeSkip.start = gScanRangeStart;
scanRangeSkip.stop = gScanRangeStop;
scanRangeSkip.step = step;
}
static uint16_t ScanRangeSkipSampleForFrequency(uint32_t frequency)
{
const uint16_t step = gRxVfo->StepFrequency;
if (!gScanRangeStart || step == 0 || frequency < gScanRangeStart || frequency > gScanRangeStop)
return 0;
const uint32_t sample = (frequency - gScanRangeStart) / step;
if (sample >= 0xFFFFu)
return 0;
return (uint16_t)(sample + 1);
}
static bool ScanRangeSkipContainsFrequency(uint32_t frequency)
{
const uint16_t sample = ScanRangeSkipSampleForFrequency(frequency);
if (sample == 0)
return false;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i)
if (scanRangeSkip.sample[i] == sample)
return true;
return false;
}
static uint32_t ScanRangeNextFrequency(void)
{
uint32_t frequency;
uint8_t guard = SCAN_RANGE_SKIP_MAX + 1;
ScanRangeSkipSync();
do
{
frequency = APP_SetFreqByStepAndLimits(gRxVfo, gScanStateDir, gScanRangeStart, gScanRangeStop);
gRxVfo->freq_config_RX.Frequency = frequency;
} while (--guard && ScanRangeSkipContainsFrequency(frequency));
return frequency;
}
bool CHFRSCANNER_ExcludeCurrentScanRange(void)
{
ScanRangeSkipSync();
uint16_t sample = ScanRangeSkipSampleForFrequency(lastFoundFrqOrChan);
if (sample == 0)
sample = ScanRangeSkipSampleForFrequency(gRxVfo->freq_config_RX.Frequency);
if (sample == 0)
return false;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i)
if (scanRangeSkip.sample[i] == sample)
return true;
scanRangeSkip.sample[scanRangeSkip.next] = sample;
scanRangeSkip.next = (scanRangeSkip.next + 1) & (SCAN_RANGE_SKIP_MAX - 1);
if (scanRangeSkip.count < SCAN_RANGE_SKIP_MAX)
scanRangeSkip.count++;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
ScanFastResetState();
#endif
return true;
}
bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal)
{
if (first_ordinal == 0)
first_ordinal = 1;
if (last_ordinal < first_ordinal)
return false;
if (first_ordinal > 0xFFFFu)
return false;
if (last_ordinal > 0xFFFFu)
last_ordinal = 0xFFFFu;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i) {
const uint16_t sample = scanRangeSkip.sample[i];
if (sample >= first_ordinal && sample <= last_ordinal)
return true;
}
return false;
}
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
void CHFRSCANNER_UpdateCssDetection(void)
{
if (!gScanRangeStart || !FUNCTION_IsRx())
{
gScanRangeCssType = CODE_TYPE_OFF;
gScanRangeCssCode = 0xFF;
scanRangeCssCandidate = 0xFF;
scanRangeCssHitCount = 0;
return;
}
uint32_t cdcssFreq;
uint16_t ctcssFreq;
const BK4819_CssScanResult_t result = BK4819_GetCxCSSScanResult(&cdcssFreq, &ctcssFreq);
if (result == BK4819_CSS_RESULT_CDCSS)
{
const uint8_t Code = DCS_GetCdcssCode(cdcssFreq);
if (Code != 0xFF && Code != gScanRangeCssCode)
{
gScanRangeCssType = CODE_TYPE_DIGITAL;
gScanRangeCssCode = Code;
scanRangeCssCandidate = 0xFF;
scanRangeCssHitCount = 0;
gUpdateDisplay = true;
}
}
else if (result == BK4819_CSS_RESULT_CTCSS)
{
const uint8_t Code = DCS_GetCtcssCode(ctcssFreq);
if (Code != 0xFF)
{
if (Code == scanRangeCssCandidate)
{
if (++scanRangeCssHitCount >= 2 && Code != gScanRangeCssCode)
{
gScanRangeCssType = CODE_TYPE_CONTINUOUS_TONE;
gScanRangeCssCode = Code;
gUpdateDisplay = true;
}
}
else
{
scanRangeCssCandidate = Code;
scanRangeCssHitCount = 1;
}
}
}
}
#endif
#endif
static void CHFRSCANNER_AbortActiveReception(void)
{
if (!FUNCTION_IsRx())
return;
AUDIO_AudioPathOff();
gEnableSpeaker = false;
gMonitor = false;
gRxReceptionMode = RX_MODE_NONE;
gScanPauseMode = false;
FUNCTION_Init();
FUNCTION_Select(FUNCTION_FOREGROUND);
}
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
#define SCAN_FAST_PRECHECK_STEPS 6
#define SCAN_FAST_RSSI_MARGIN 16
#define SCAN_FAST_SQUELCH_MARGIN 8
#define SCAN_FAST_WEAK_MARGIN 8
#define SCAN_FAST_RECHECK_DELAY_US 350
#define SCAN_FAST_FINE_STEP_LIMIT 250
#define SCAN_FAST_FINE_REFINE_SPAN 1000
#define SCAN_FAST_FINE_REFINE_MAX 80
#define SCAN_FAST_FINE_RSSI_DROP 8
#define SCAN_FAST_RSSI_MAX 65535u
// Settle loop guard: at most this many 1us waits while the BK4819 glitch
// indicator stays above SCAN_FAST_GLITCH_THRESHOLD. Caps the worst-case
// settling time per step at ~50us before we read the RSSI anyway.
#define SCAN_FAST_GLITCH_GUARD_MAX 50
#define SCAN_FAST_GLITCH_THRESHOLD 200
// HF/VHF boundary in Hz: BK4819_PickRXFilterPathBasedOnFrequency() switches
// the front-end filter path here, so we only re-run that (relatively
// expensive) call when we actually cross the boundary.
#define SCAN_FAST_HF_VHF_BOUNDARY_HZ 28000000u
typedef enum {
SCAN_FAST_DISABLED,
SCAN_FAST_QUIET_BATCH,
SCAN_FAST_CANDIDATE
} scan_fast_result_t;
static uint16_t scanFastReg30;
static uint16_t scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
static uint32_t scanFastPrevFrequency;
static bool scanFastLastFullTuneCandidate;
static VFO_Info_t scanFastDisplayVfo;
static bool scanFastDisplayVfoValid;
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
static uint16_t scanRssiSparkline[CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
static uint8_t scanRssiSparklineWrite;
static uint8_t scanRssiSparklineCount;
static void ScanRssiSparklineReset(void)
{
memset(scanRssiSparkline, 0, sizeof(scanRssiSparkline));
scanRssiSparklineWrite = 0;
scanRssiSparklineCount = 0;
}
static void ScanRssiSparklinePush(uint16_t rssi)
{
scanRssiSparkline[scanRssiSparklineWrite] = rssi;
scanRssiSparklineWrite++;
if (scanRssiSparklineWrite >= CHFRSCANNER_RSSI_SPARKLINE_WIDTH)
scanRssiSparklineWrite = 0;
if (scanRssiSparklineCount < CHFRSCANNER_RSSI_SPARKLINE_WIDTH)
scanRssiSparklineCount++;
}
bool CHFRSCANNER_HasScanRssiSparkline(void)
{
return gScanStateDir != SCAN_OFF && scanRssiSparklineCount > 1;
}
uint8_t CHFRSCANNER_GetScanRssiSparklineLevel(uint8_t index)
{
uint16_t minRssi = SCAN_FAST_RSSI_MAX;
uint16_t rssi = 0;
uint8_t oldest;
if (index >= CHFRSCANNER_RSSI_SPARKLINE_WIDTH || scanRssiSparklineCount == 0)
return 0;
if (index < CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount)
return 0;
oldest = (uint8_t)((scanRssiSparklineWrite + CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount)
% CHFRSCANNER_RSSI_SPARKLINE_WIDTH);
index -= (uint8_t)(CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount);
for (uint8_t i = 0; i < scanRssiSparklineCount; i++)
{
const uint16_t sample = scanRssiSparkline[(oldest + i) % CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
if (sample != 0 && sample < minRssi)
minRssi = sample;
}
rssi = scanRssiSparkline[(oldest + index) % CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
if (rssi == 0 || minRssi == SCAN_FAST_RSSI_MAX || rssi <= minRssi + 2)
return 0;
// 48 RSSI units ~= 24 dB. This keeps quiet jitter low while strong hits pop.
return (uint8_t)MIN(((uint32_t)(rssi - minRssi) * 5u + 24u) / 48u, 5u);
}
#endif
static void ScanFastResetState(void)
{
// Called on every scan (re)start, after a reception, and on each
// wraparound to the start of the channel list / range. The noise
// floor is re-warmed up from current conditions instead of carrying
// stale calibration into a later sweep.
scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
scanFastPrevFrequency = 0;
scanFastLastFullTuneCandidate = false;
scanFastDisplayVfoValid = false;
}
static void ScanFastResetNoiseFloor(void)
{
scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
}
static uint16_t ScanFastReadRssi(void)
{
uint8_t guard = SCAN_FAST_GLITCH_GUARD_MAX;
while (guard-- && BK4819_GetGlitchIndicator() >= SCAN_FAST_GLITCH_THRESHOLD)
{
SYSTICK_DelayUs(1);
}
// Discard first read: after fast tuning the RSSI/AGC value may still be stale.
BK4819_GetRSSI();
return BK4819_GetRSSI();
}
static void ScanFastTune(uint32_t frequency)
{
if (scanFastPrevFrequency == 0 ||
((frequency < SCAN_FAST_HF_VHF_BOUNDARY_HZ) !=
(scanFastPrevFrequency < SCAN_FAST_HF_VHF_BOUNDARY_HZ)))
{
BK4819_PickRXFilterPathBasedOnFrequency(frequency);
}
scanFastPrevFrequency = frequency;
BK4819_SetFrequency(frequency);
BK4819_WriteRegister(BK4819_REG_30, 0);
BK4819_WriteRegister(BK4819_REG_30, scanFastReg30);
}
const VFO_Info_t *CHFRSCANNER_GetScanDisplayVfo(void)
{
if (!ScanFastEnabled() || !scanFastDisplayVfoValid || gScanStateDir == SCAN_OFF || FUNCTION_IsRx())
return NULL;
return &scanFastDisplayVfo;
}
static bool ScanFastUpdateDisplayVfo(uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation)
{
ChannelScanDisplayInfo_t info;
if (!SETTINGS_FetchChannelScanDisplayInfo(channel, &info))
{
scanFastDisplayVfoValid = false;
return false;
}
scanFastDisplayVfo = gEeprom.VfoInfo[gEeprom.RX_VFO];
scanFastDisplayVfo.CHANNEL_SAVE = channel;
scanFastDisplayVfo.freq_config_RX = info.rx;
scanFastDisplayVfo.freq_config_TX = info.tx;
scanFastDisplayVfo.TX_OFFSET_FREQUENCY = info.offset;
scanFastDisplayVfo.StepFrequency = info.stepFrequency;
scanFastDisplayVfo.STEP_SETTING = info.stepSetting;
scanFastDisplayVfo.Modulation = info.modulation;
scanFastDisplayVfo.TX_OFFSET_FREQUENCY_DIRECTION = info.txOffsetFrequencyDirection;
scanFastDisplayVfo.OUTPUT_POWER = info.outputPower;
scanFastDisplayVfo.FrequencyReverse = info.frequencyReverse;
scanFastDisplayVfo.CHANNEL_BANDWIDTH = info.channelBandwidth;
scanFastDisplayVfo.BUSY_CHANNEL_LOCK = info.busyChannelLock;
scanFastDisplayVfo.TX_LOCK = info.txLock;
#ifdef ENABLE_DTMF_CALLING
scanFastDisplayVfo.DTMF_DECODING_ENABLE = info.dtmfDecodingEnable;
#endif
scanFastDisplayVfo.DTMF_PTT_ID_TX_MODE = info.dtmfPttIdTxMode;
if (!scanFastDisplayVfo.FrequencyReverse)
{
scanFastDisplayVfo.pRX = &scanFastDisplayVfo.freq_config_RX;
scanFastDisplayVfo.pTX = &scanFastDisplayVfo.freq_config_TX;
}
else
{
scanFastDisplayVfo.pRX = &scanFastDisplayVfo.freq_config_TX;
scanFastDisplayVfo.pTX = &scanFastDisplayVfo.freq_config_RX;
}
scanFastDisplayVfoValid = true;
if (frequency)
*frequency = info.rx.Frequency;
if (modulation)
*modulation = info.modulation;
return true;
}
static uint16_t ScanFastSaturatingAdd(uint16_t value, uint16_t add)
{
return (value > SCAN_FAST_RSSI_MAX - add) ? SCAN_FAST_RSSI_MAX : (uint16_t)(value + add);
}
static uint16_t ScanFastSaturatingSub(uint16_t value, uint16_t sub)
{
return (value > sub) ? (uint16_t)(value - sub) : 0;
}
static uint16_t ScanFastGetNoiseTrigger(void)
{
return ScanFastSaturatingAdd(scanFastNoiseFloor, SCAN_FAST_RSSI_MARGIN);
}
static uint16_t ScanFastGetSquelchTrigger(void)
{
return ScanFastSaturatingSub(gRxVfo->SquelchOpenRSSIThresh, SCAN_FAST_SQUELCH_MARGIN);
}
static bool ScanFastIsNearCandidate(uint16_t rssi)
{
const uint16_t rssiWithMargin = ScanFastSaturatingAdd(rssi, SCAN_FAST_WEAK_MARGIN);
const uint16_t squelchTrigger = ScanFastGetSquelchTrigger();
if (scanFastNoiseFloor == SCAN_FAST_RSSI_MAX)
return gRxVfo->SquelchOpenRSSIThresh > 0 &&
rssiWithMargin >= gRxVfo->SquelchOpenRSSIThresh;
return rssiWithMargin >= ScanFastGetNoiseTrigger() &&
rssiWithMargin >= squelchTrigger;
}
static uint16_t ScanFastReadCandidateRssi(void)
{
uint16_t rssi = ScanFastReadRssi();
if (ScanFastIsNearCandidate(rssi))
{
SYSTICK_DelayUs(SCAN_FAST_RECHECK_DELAY_US);
const uint16_t retryRssi = ScanFastReadRssi();
if (retryRssi > rssi)
rssi = retryRssi;
}
return rssi;
}
static bool ScanFastIsCandidate(uint16_t rssi)
{
const uint16_t squelchTrigger = ScanFastGetSquelchTrigger();
if (scanFastNoiseFloor == SCAN_FAST_RSSI_MAX)
{
scanFastNoiseFloor = rssi;
return gRxVfo->SquelchOpenRSSIThresh > 0 &&
rssi >= gRxVfo->SquelchOpenRSSIThresh;
}
const uint16_t noiseTrigger = ScanFastGetNoiseTrigger();
const uint16_t rssiWithMargin = ScanFastSaturatingAdd(rssi, SCAN_FAST_WEAK_MARGIN);
if ((rssi >= noiseTrigger && rssi >= squelchTrigger) ||
(rssiWithMargin >= noiseTrigger && rssiWithMargin >= squelchTrigger))
{
return true;
}
if (rssi < scanFastNoiseFloor)
scanFastNoiseFloor = rssi;
else
scanFastNoiseFloor = (uint16_t)((7u * scanFastNoiseFloor + rssi + 4u) >> 3);
return false;
}
static void ScanFastApplyChannelShape(ModulationMode_t modulation)
{
const bool modulationChanged = gRxVfo->Modulation != modulation;
const bool bandwidthChanged = gRxVfo->CHANNEL_BANDWIDTH != BANDWIDTH_WIDE;
if (!modulationChanged && !bandwidthChanged)
return;
gRxVfo->Modulation = modulation;
gRxVfo->CHANNEL_BANDWIDTH = BANDWIDTH_WIDE;
if (modulationChanged)
RADIO_SetModulation(modulation);
if (modulation == MODULATION_AM)
{
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
}
else
{
#ifdef ENABLE_AM_FIX
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_WIDE, true);
#else
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_WIDE, false);
#endif
}
if (modulationChanged)
{
// AM and FM use different demod/AGC profiles, so their RSSI
// baselines are not directly comparable. Relearn the floor after
// crossing that boundary instead of treating the next FM block as
// a wall of candidates.
ScanFastResetNoiseFloor();
}
}
#endif
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_SCAN_RANGES)
static void ScanRangeFastRefineCandidate(uint16_t firstRssi)
{
const uint16_t step = gRxVfo->StepFrequency;
if (step == 0 || step >= SCAN_FAST_FINE_STEP_LIMIT)
return;
uint16_t maxSteps = SCAN_FAST_FINE_REFINE_SPAN / step;
if (maxSteps == 0)
maxSteps = 1;
if (maxSteps > SCAN_FAST_FINE_REFINE_MAX)
maxSteps = SCAN_FAST_FINE_REFINE_MAX;
uint16_t bestRssi = firstRssi;
uint32_t bestFrequency = gRxVfo->freq_config_RX.Frequency;
uint8_t fallingSteps = 0;
for (uint16_t i = 0; i < maxSteps; ++i)
{
const uint32_t prevRxFrequency = gRxVfo->pRX->Frequency;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
RADIO_ApplyOffset(gRxVfo);
const uint32_t freq = gRxVfo->pRX->Frequency;
if ((gScanStateDir > 0 && freq < prevRxFrequency) ||
(gScanStateDir < 0 && freq > prevRxFrequency))
{
break;
}
ScanFastTune(freq);
const uint16_t rssi = ScanFastReadRssi();
if (rssi > bestRssi)
{
bestRssi = rssi;
bestFrequency = gRxVfo->freq_config_RX.Frequency;
fallingSteps = 0;
}
else if (bestRssi > rssi && bestRssi - rssi >= SCAN_FAST_FINE_RSSI_DROP)
{
if (++fallingSteps >= 3)
break;
}
}
gRxVfo->freq_config_RX.Frequency = bestFrequency;
RADIO_ApplyOffset(gRxVfo);
ScanFastTune(gRxVfo->pRX->Frequency);
}
static scan_fast_result_t ScanRangeFastPrecheck(void)
{
if (!gScanRangeStart)
return SCAN_FAST_DISABLED;
if (gRxVfo->SquelchOpenRSSIThresh == 0)
return SCAN_FAST_DISABLED;
// Mute AF DAC during the precheck sweep: avoids audio glitches and
// saves a few uA on each silent step. The bit is restored on the real
// tune by RADIO_SetupRegisters() in NextFreqChannel().
scanFastReg30 = BK4819_ReadRegister(BK4819_REG_30) & ~BK4819_REG_30_MASK_ENABLE_AF_DAC;
for (uint8_t i = 0; i < SCAN_FAST_PRECHECK_STEPS; ++i)
{
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
RADIO_ApplyOffset(gRxVfo);
const uint32_t freq = gRxVfo->pRX->Frequency;
// Detect wraparound: scanning forward but the new freq is lower
// than the previous one (or scanning backward but it's higher)
// means the range has wrapped from stop back to start. Reset the
// noise floor so the new pass re-warms up from current conditions.
if (scanFastPrevFrequency != 0 &&
((gScanStateDir > 0 && freq < scanFastPrevFrequency) ||
(gScanStateDir < 0 && freq > scanFastPrevFrequency)))
{
ScanFastResetState();
}
ScanFastTune(freq);
const uint16_t rssi = ScanFastReadCandidateRssi();
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
ScanRssiSparklinePush(rssi);
#endif
if (ScanFastIsCandidate(rssi))
{
ScanRangeFastRefineCandidate(rssi);
return SCAN_FAST_CANDIDATE;
}
}
return SCAN_FAST_QUIET_BATCH;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static bool MemChannelFastPrecheck(uint16_t channel)
{
uint32_t frequency;
ModulationMode_t modulation;
if (gRxVfo->SquelchOpenRSSIThresh == 0)
{
scanFastLastFullTuneCandidate = false;
return true;
}
if (!ScanFastUpdateDisplayVfo(channel, &frequency, &modulation))
{
scanFastLastFullTuneCandidate = false;
return true;
}
ScanFastApplyChannelShape(modulation);
// Mute AF DAC for the same reason as in ScanRangeFastPrecheck().
scanFastReg30 = BK4819_ReadRegister(BK4819_REG_30) & ~BK4819_REG_30_MASK_ENABLE_AF_DAC;
ScanFastTune(frequency);
const uint16_t rssi = ScanFastReadCandidateRssi();
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
ScanRssiSparklinePush(rssi);
#endif
if (ScanFastIsCandidate(rssi))
{
scanFastLastFullTuneCandidate = true;
return true; // signal detected: let the full tune path follow
}
// No signal here: still mirror the probed frequency in the VFO so the
// status line (channel name + frequency) keeps in sync as we skip.
// RADIO_ConfigureChannel() will overwrite these values cleanly when a
// candidate is eventually retained.
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = frequency;
return false;
}
static void AdvanceMemScanList(const bool enabled)
{
if (enabled)
if (++currentScanList >= SCAN_NEXT_NUM)
currentScanList = SCAN_NEXT_CHAN_SCANLIST1;
}
static void SetMemScanProgressChannel(uint16_t channel)
{
gEeprom.MrChannel[ gEeprom.RX_VFO] = channel;
gEeprom.ScreenChannel[gEeprom.RX_VFO] = channel;
gRxVfo->CHANNEL_SAVE = channel;
}
#endif
#if defined(ENABLE_FEAT_F4HWN_RESUME_STATE) || defined(ENABLE_SCAN_RANGES)
void CHFRSCANNER_ScanRange(void) {
gScanRangeStart = gScanRangeStart ? 0 : gTxVfo->pRX->Frequency;
if (gScanRangeStart) {
gScanRangeStart = 0;
return;
}
gScanRangeStart = gTxVfo->pRX->Frequency;
gScanRangeStop = gEeprom.VfoInfo[!gEeprom.TX_VFO].freq_config_RX.Frequency;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
ScanFastResetState();
#endif
if(gScanRangeStart > gScanRangeStop)
SWAP(gScanRangeStart, gScanRangeStop);
ScanRangeSkipSync();
}
#endif
@@ -55,18 +758,30 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi
}
RADIO_SelectVfos();
CHFRSCANNER_AbortActiveReception();
gNextMrChannel = gRxVfo->CHANNEL_SAVE;
currentScanList = SCAN_NEXT_CHAN_SCANLIST1;
gScanStateDir = scan_direction;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
ScanRssiSparklineReset();
#endif
ScanFastResetState();
#endif
if (IS_MR_CHANNEL(gNextMrChannel))
{
{
bool scanListChanged = false;
if(!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) {
RADIO_NextValidList(1);
scanListChanged = true;
}
if (storeBackupSettings || scanListChanged)
UI_MAIN_NotifyScanListChanged();
// channel mode
if (storeBackupSettings) {
initialFrqOrChan = gRxVfo->CHANNEL_SAVE;
@@ -93,6 +808,16 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi
gScanPauseMode = false;
}
void CHFRSCANNER_ManualResume(const int8_t scan_direction)
{
CHFRSCANNER_Start(false, scan_direction);
gScanPauseDelayIn_10ms = (gRxVfo->SquelchOpenRSSIThresh == 0)
? scan_pause_delay_in_3_10ms
: 1;
gScheduleScanListen = false;
}
/*
void CHFRSCANNER_ContinueScanning(void)
{
@@ -119,6 +844,18 @@ void CHFRSCANNER_ContinueScanning(void)
void CHFRSCANNER_ContinueScanning(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (scanFastLastFullTuneCandidate &&
gCurrentFunction != FUNCTION_INCOMING &&
!g_SquelchLost)
{
// A rejected full-tune candidate is just a false RSSI hit. Keep the
// learned floor; resetting here can make the next channel blind when
// it is the real signal, especially while scanning down.
scanFastLastFullTuneCandidate = false;
}
#endif
if (gCurrentFunction == FUNCTION_INCOMING &&
(IS_FREQ_CHANNEL(gNextMrChannel) || gCurrentCodeType == CODE_TYPE_OFF))
{
@@ -136,6 +873,15 @@ void CHFRSCANNER_ContinueScanning(void)
void CHFRSCANNER_Found(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// After a real reception the BK4819 AGC has shifted, biasing the next
// few RSSI readings high. Reset the precheck state so it warms up from
// the current noise floor instead of carrying stale calibration into
// the post-reception scan, which otherwise turns nearly every channel
// into a CANDIDATE and erases the speed gain.
ScanFastResetState();
#endif
if (gEeprom.SCAN_RESUME_MODE > 80) {
if (!gScanPauseMode) {
gScanPauseDelayIn_10ms = scan_pause_delay_in_5_10ms * (gEeprom.SCAN_RESUME_MODE - 80) * 5;
@@ -208,6 +954,9 @@ void CHFRSCANNER_Stop(void)
}
gScanStateDir = SCAN_OFF;
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_FEAT_F4HWN_SCAN_RSSI)
ScanRssiSparklineReset();
#endif
const uint32_t chFr = gScanKeepResult ? lastFoundFrqOrChan : initialFrqOrChan;
const bool channelChanged = chFr != initialFrqOrChan;
@@ -243,11 +992,46 @@ static void NextFreqChannel(void)
{
#ifdef ENABLE_SCAN_RANGES
if(gScanRangeStart) {
gRxVfo->freq_config_RX.Frequency = APP_SetFreqByStepAndLimits(gRxVfo, gScanStateDir, gScanRangeStart, gScanRangeStop);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (ScanFastEnabled())
{
const scan_fast_result_t fastResult = ScanRangeFastPrecheck();
if (fastResult == SCAN_FAST_QUIET_BATCH)
{
scanFastLastFullTuneCandidate = false;
gScanPauseDelayIn_10ms = 1;
gUpdateDisplay = true;
return;
}
if (fastResult == SCAN_FAST_DISABLED)
{
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
}
else
{
scanFastLastFullTuneCandidate = true;
}
}
else
{
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
}
#else
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
#endif
}
else
#endif
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
scanFastLastFullTuneCandidate = false;
#endif
gRxVfo->freq_config_RX.Frequency = APP_SetFrequencyByStep(gRxVfo, gScanStateDir);
}
RADIO_ApplyOffset(gRxVfo);
RADIO_ConfigureSquelchAndOutputPower(gRxVfo);
@@ -348,23 +1132,58 @@ static void NextMemChannel(void)
}
if (!enabled || chan == 0xFFFF)
{
{
const uint16_t searchStart = gNextMrChannel;
chan = RADIO_FindNextChannel(gNextMrChannel + gScanStateDir, gScanStateDir, true, gEeprom.SCAN_LIST_DEFAULT);
if (chan == 0xFFFF)
{ // no valid channel found
{ // no valid channel found -> wrapping back to the first channel
chan = MR_CHANNEL_FIRST;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// Wraparound: re-warm the precheck noise floor on the new pass
// so it tracks current RF conditions instead of an EMA that
// accumulated drift over the previous full sweep.
ScanFastResetState();
#endif
}
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
else if ((gScanStateDir > 0 && chan < searchStart) ||
(gScanStateDir < 0 && chan > searchStart))
{
// RADIO_FindNextChannel() wraps internally, so 0xFFFF is not
// returned on a normal full-sweep wrap. Detect that transition
// here and restart the RSSI floor learning for the new pass.
ScanFastResetState();
}
#endif
gNextMrChannel = chan;
//sprintf(str, "----> Chan %d\n", chan + 1);
//LogUart(str);
}
if (gNextMrChannel != prev_chan)
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
SetMemScanProgressChannel(gNextMrChannel);
if (ScanFastEnabled() && !MemChannelFastPrecheck(gNextMrChannel))
{
gScanPauseDelayIn_10ms = 1;
gUpdateDisplay = true;
AdvanceMemScanList(enabled);
return;
}
#endif
if (gNextMrChannel != prev_chan
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|| scanFastLastFullTuneCandidate
#endif
)
{
#ifndef ENABLE_FEAT_F4HWN_SCAN_FASTER
gEeprom.MrChannel[ gEeprom.RX_VFO] = gNextMrChannel;
gEeprom.ScreenChannel[gEeprom.RX_VFO] = gNextMrChannel;
#endif
RADIO_ConfigureChannel(gEeprom.RX_VFO, VFO_CONFIGURE_RELOAD);
RADIO_SetupRegisters(true);
@@ -378,7 +1197,11 @@ static void NextMemChannel(void)
gScanPauseDelayIn_10ms = scan_pause_delay_in_3_10ms;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
AdvanceMemScanList(enabled);
#else
if (enabled)
if (++currentScanList >= SCAN_NEXT_NUM)
currentScanList = SCAN_NEXT_CHAN_SCANLIST1; // back round we go
#endif
}
+22 -1
View File
@@ -4,6 +4,8 @@
#include <stdbool.h>
#include <stdint.h>
#include "radio.h"
// scan direction, if not equal SCAN_OFF indicates
// that we are in a process of scanning channels/frequencies
extern int8_t gScanStateDir;
@@ -13,12 +15,31 @@ extern bool gScanPauseMode;
#ifdef ENABLE_SCAN_RANGES
extern uint32_t gScanRangeStart;
extern uint32_t gScanRangeStop;
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
extern DCS_CodeType_t gScanRangeCssType;
extern uint8_t gScanRangeCssCode;
#endif
bool CHFRSCANNER_ExcludeCurrentScanRange(void);
bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal);
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
void CHFRSCANNER_UpdateCssDetection(void);
#endif
#endif
void CHFRSCANNER_Found(void);
void CHFRSCANNER_Stop(void);
void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_direction);
void CHFRSCANNER_ManualResume(const int8_t scan_direction);
void CHFRSCANNER_ContinueScanning(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
const VFO_Info_t *CHFRSCANNER_GetScanDisplayVfo(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
bool CHFRSCANNER_HasScanRssiSparkline(void);
uint8_t CHFRSCANNER_GetScanRssiSparklineLevel(uint8_t index);
#define CHFRSCANNER_RSSI_SPARKLINE_WIDTH 24u
#endif
#endif
#if defined(ENABLE_FEAT_F4HWN_RESUME_STATE) || defined(ENABLE_SCAN_RANGES)
void CHFRSCANNER_ScanRange(void);
@@ -29,4 +50,4 @@ void CHFRSCANNER_ContinueScanning(void);
extern uint32_t lastFoundFrqOrChanOld;
#endif
#endif
#endif
+5 -1
View File
@@ -212,6 +212,10 @@ DTMF_CallMode_t DTMF_CheckGroupCall(const char *pMsg, const unsigned int size)
}
#endif
void DTMF_clear_input_box_memory() {
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
}
void DTMF_clear_input_box(void)
{
memset(gDTMF_InputBox, 0, sizeof(gDTMF_InputBox));
@@ -470,7 +474,7 @@ void DTMF_Reply(void)
if (pString == NULL)
return;
Delay = (gEeprom.DTMF_PRELOAD_TIME < 200) ? 200 : gEeprom.DTMF_PRELOAD_TIME;
Delay = MAX(gEeprom.DTMF_PRELOAD_TIME, 200);
if (gEeprom.DTMF_SIDE_TONE)
{ // the user will also hear the transmitted tones
+1
View File
@@ -82,6 +82,7 @@ extern DTMF_ReplyState_t gDTMF_ReplyState;
bool DTMF_ValidateCodes(char *pCode, const unsigned int size);
char DTMF_GetCharacter(const unsigned int code);
void DTMF_clear_input_box_memory(void);
void DTMF_clear_input_box(void);
void DTMF_Append(const char code);
void DTMF_Reply(void);
+35 -22
View File
@@ -40,7 +40,6 @@ volatile int8_t gFM_ScanState;
bool gFM_AutoScan;
uint8_t gFM_ChannelPosition;
bool gFM_FoundFrequency;
bool gFM_AutoScan;
uint16_t gFM_RestoreCountdown_10ms;
@@ -95,6 +94,11 @@ int FM_ConfigureChannelState(void)
return 0;
}
void FM_SetFrequency(void)
{
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
}
void FM_TurnOff(void)
{
gFmRadioMode = false;
@@ -164,7 +168,12 @@ void FM_Tune(uint16_t Frequency, int8_t Step, bool bFlag)
gFM_ScanState = Step;
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
}
void FM_AudioPathOn(void) {
AUDIO_AudioPathOn();
gEnableSpeaker = true;
}
void FM_PlayAndUpdate(void)
@@ -177,16 +186,15 @@ void FM_PlayAndUpdate(void)
}
FM_ConfigureChannelState();
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
SETTINGS_SaveFM();
gFmPlayCountdown_10ms = 0;
gScheduleFM = false;
gAskToSave = false;
AUDIO_AudioPathOn();
gEnableSpeaker = true;
BACKLIGHT_TurnOn();
FM_AudioPathOn();
}
int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit)
@@ -288,7 +296,7 @@ static void Key_DIGITS(KEY_Code_t Key, uint8_t state)
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gEeprom.FM_FrequencyPlaying = gEeprom.FM_SelectedFrequency;
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
gRequestSaveFM = true;
return;
}
@@ -308,7 +316,7 @@ static void Key_DIGITS(KEY_Code_t Key, uint8_t state)
#endif
gEeprom.FM_SelectedChannel = Channel;
gEeprom.FM_FrequencyPlaying = gFM_Channels[Channel];
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
gRequestSaveFM = true;
return;
}
@@ -363,7 +371,7 @@ static void Key_FUNC(KEY_Code_t Key, uint8_t state)
gEeprom.FM_IsMrMode = !gEeprom.FM_IsMrMode;
if (!FM_ConfigureChannelState()) {
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
gRequestSaveFM = true;
}
else
@@ -391,8 +399,14 @@ static void Key_FUNC(KEY_Code_t Key, uint8_t state)
static void Key_EXIT(uint8_t state)
{
if (state != BUTTON_EVENT_SHORT)
return;
if (gInputBoxIndex) {
if (state != BUTTON_EVENT_SHORT)
return;
}
else {
if (state != BUTTON_EVENT_PRESSED)
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
@@ -471,7 +485,7 @@ static void Key_MENU(uint8_t state)
gFM_Channels[gEeprom.FM_SelectedChannel] = 0xFFFF;
FM_ConfigureChannelState();
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
gRequestSaveFM = true;
}
@@ -549,7 +563,7 @@ static void Key_UP_DOWN(uint8_t state, int8_t Step)
gRequestSaveFM = true;
Bail:
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
FM_SetFrequency();
gRequestDisplayScreen = DISPLAY_FM;
}
@@ -603,20 +617,19 @@ void FM_Play(void)
if (!gEeprom.FM_IsMrMode)
gEeprom.FM_SelectedFrequency = gEeprom.FM_FrequencyPlaying;
AUDIO_AudioPathOn();
gEnableSpeaker = true;
BACKLIGHT_TurnOn();
FM_AudioPathOn();
GUI_SelectNextDisplay(DISPLAY_FM);
return;
goto Display;
}
if (gFM_ChannelPosition < FM_CHANNELS_MAX)
gFM_Channels[gFM_ChannelPosition++] = gEeprom.FM_FrequencyPlaying;
if (gFM_ChannelPosition >= FM_CHANNELS_MAX) {
FM_PlayAndUpdate();
GUI_SelectNextDisplay(DISPLAY_FM);
return;
goto Display;
}
}
@@ -625,6 +638,7 @@ void FM_Play(void)
else
FM_Tune(gEeprom.FM_FrequencyPlaying, gFM_ScanState, false);
Display:
GUI_SelectNextDisplay(DISPLAY_FM);
}
@@ -639,9 +653,8 @@ void FM_Start(void)
// Disable UHF LNA, enable VHF LNA
BK4819_PickRXFilterPathBasedOnFrequency(10320000); // 103.2 MHz < 280 MHz
AUDIO_AudioPathOn();
FM_AudioPathOn();
gEnableSpeaker = true;
gUpdateStatus = true;
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
+1361
View File
File diff suppressed because it is too large. Load diff
+57
View File
@@ -0,0 +1,57 @@
/* Copyright 2026 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_FOXHUNT_H
#define APP_FOXHUNT_H
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#include "keyboard_state.h"
#include "../bitmaps.h"
#include "../board.h"
#include "py32f0xx.h"
#include "../driver/backlight.h"
#include "../driver/bk4819-regs.h"
#include "../driver/bk4819.h"
#include "../driver/gpio.h"
#include "../driver/keyboard.h"
#include "../driver/py25q16.h"
#include "../driver/st7565.h"
#include "../driver/system.h"
#include "../driver/systick.h"
#include "../external/printf/printf.h"
#include "../font.h"
#include "../helper/battery.h"
#include "../misc.h"
#include "../radio.h"
#include "../settings.h"
#include "../ui/battery.h"
#include "../ui/helper.h"
#include "../ui/ui.h"
#include "../audio.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
// Entry point wired as an assignable side-key action (see action.c).
void ACTION_FoxHunt(void);
// Self-contained modal loop that owns the screen and keypad until EXIT.
void APP_RunFoxHunt(void);
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
#endif // APP_FOXHUNT_H
+2 -14
View File
@@ -91,7 +91,7 @@ void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld)
return;
}
#endif
gBeepToPlay = BEEP_440HZ_500MS;
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
gPttWasReleased = true;
}
}
@@ -103,20 +103,8 @@ void GENERIC_Key_PTT(bool bKeyPressed)
if (!bKeyPressed || SerialConfigInProgress())
{ // PTT released
if (gCurrentFunction == FUNCTION_TRANSMIT) {
// we are transmitting .. stop
if (gFlagEndTransmission) {
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
APP_HandleEndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
FUNCTION_Select(FUNCTION_FOREGROUND);
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
gFlagEndTransmission = false;
#ifdef ENABLE_VOX
gVOX_NoiseDetected = false;
#endif
+79 -60
View File
@@ -44,6 +44,7 @@
#include "radio.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/main.h"
#include "ui/ui.h"
#include <stdlib.h>
@@ -104,7 +105,7 @@ static void toggle_chan_scanlist(void)
gTxVfo->SCANLIST_PARTICIPATION = scanlist;
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, true, true);
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
}
gVfoConfigureMode = VFO_CONFIGURE;
@@ -224,6 +225,12 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
case KEY_4:
HideFKeyIcon();
if (gScanStateDir != SCAN_OFF) {
// Stop the channel/frequency scan before saving the RX mode for Close Call.
gScanKeepResult = false;
CHFRSCANNER_Stop();
}
gBackup_CROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
@@ -261,34 +268,51 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
break;
case KEY_7:
#ifdef ENABLE_VOX
ACTION_Vox();
//#else
// toggle_chan_scanlist();
#ifdef ENABLE_FEAT_F4HWN_GAME
if (!beep) {
APP_RunBreakout();
} else {
#endif
#ifdef ENABLE_VOX
ACTION_Vox();
//#else
// toggle_chan_scanlist();
#endif
#ifdef ENABLE_FEAT_F4HWN_GAME
}
#endif
break;
case KEY_8:
gTxVfo->FrequencyReverse = gTxVfo->FrequencyReverse == false;
gRequestSaveChannel = 1;
if (!beep) {
ACTION_BackLightOnDemand();
}
else {
gTxVfo->FrequencyReverse = gTxVfo->FrequencyReverse == false;
gRequestSaveChannel = 1;
}
break;
case KEY_9:
if (RADIO_CheckValidChannel(gEeprom.CHAN_1_CALL, false, 0)) {
gEeprom.MrChannel[Vfo] = gEeprom.CHAN_1_CALL;
gEeprom.ScreenChannel[Vfo] = gEeprom.CHAN_1_CALL;
if (!beep) {
ACTION_BackLight();
}
else {
if (RADIO_CheckValidChannel(gEeprom.CHAN_1_CALL, false, 0)) {
gEeprom.MrChannel[Vfo] = gEeprom.CHAN_1_CALL;
gEeprom.ScreenChannel[Vfo] = gEeprom.CHAN_1_CALL;
#ifdef ENABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, gEeprom.CHAN_1_CALL + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, gEeprom.CHAN_1_CALL + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
}
}
if (beep)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
#ifdef ENABLE_FEAT_F4HWN // Set Squelch F + UP or Down and Step F + SIDE1 or F + SIDE2
@@ -299,15 +323,16 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
if (gScanStateDir != SCAN_OFF) {
RADIO_NextValidList(isKeyUp ? 1 : -1);
UI_MAIN_NotifyScanListChanged();
} else {
// Adjust squelch: UP increments, DOWN decrements
if (gSquelchLevelOriginal == 10)
gSquelchLevelOriginal = gEeprom.SQUELCH_LEVEL;
if (isKeyUp) {
gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL < 9) ? gEeprom.SQUELCH_LEVEL + 1 : 9;
if (gEeprom.SQUELCH_LEVEL < 9) gEeprom.SQUELCH_LEVEL++;
} else {
gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL > 0) ? gEeprom.SQUELCH_LEVEL - 1 : 0;
if (gEeprom.SQUELCH_LEVEL > 0) gEeprom.SQUELCH_LEVEL--;
}
gVfoConfigureMode = VFO_CONFIGURE;
}
@@ -481,6 +506,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
if (value == 0)
{
gEeprom.SCAN_LIST_DEFAULT = MR_CHANNELS_LIST + 1;
UI_MAIN_NotifyScanListChanged();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
#endif
@@ -499,6 +525,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
RADIO_NextValidList(1);
}
UI_MAIN_NotifyScanListChanged();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
@@ -555,10 +582,10 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
return;
}
gKeyInputCountdown = (gInputBoxIndex >= totalDigits) ? (key_input_timeout_500ms / 16) : (key_input_timeout_500ms / 3);
gKeyInputCountdown = key_input_timeout_500ms / (gInputBoxIndex >= totalDigits ? 16 : 3);
if (gInputBoxIndex > totalDigits) {
gInputBoxIndex = totalDigits;
gInputBoxIndex = totalDigits;
return;
}
@@ -569,11 +596,8 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
// convert to int
uint32_t inputFreq = StrToUL(inputStr);
// how many zero to add
uint8_t zerosToAdd = totalDigits - inputLength;
// add missing zero
for (uint8_t i = 0; i < zerosToAdd; i++) {
for (uint8_t i = 0; i < totalDigits - inputLength; i++) {
inputFreq *= 10;
}
@@ -654,30 +678,6 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
HideFKeyIcon();
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(gEeprom.MENU_LOCK == true && Key != 2) {
return;
}
#endif
if(Key == 8)
{
ACTION_BackLightOnDemand();
return;
}
else if(Key == 9)
{
ACTION_BackLight();
return;
}
#ifdef ENABLE_FEAT_F4HWN_GAME
else if(Key == 7)
{
APP_RunBreakout();
return;
}
#endif
processFKeyFunction(Key, false);
}
@@ -758,15 +758,30 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
if (bKeyPressed) { // long press MENU key
#ifdef ENABLE_FEAT_F4HWN
// Exclude channel
// Exclude current scan entry
if(gScanStateDir != SCAN_OFF)
{
if(FUNCTION_IsRx() || gScanPauseDelayIn_10ms > 9)
{
#ifdef ENABLE_SCAN_RANGES
if(gScanRangeStart && !IS_MR_CHANNEL(gNextMrChannel))
{
if(CHFRSCANNER_ExcludeCurrentScanRange())
{
UI_MAIN_NotifyScanProgressDataChanged();
lastFoundFrqOrChan = lastFoundFrqOrChanOld;
CHFRSCANNER_ContinueScanning();
}
return;
}
#endif
ChannelAttributes_t *att = MR_GetChannelAttributes(lastFoundFrqOrChan);
att->exclude = true;
MR_SaveChannelAttributesToFlash(lastFoundFrqOrChan, att);
UI_MAIN_NotifyScanProgressDataChanged();
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
@@ -909,6 +924,12 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld)
return;
}
#endif
if (gScanStateDir != SCAN_OFF) {
// Stop the channel/frequency scan before saving the RX mode for the CTCSS/DCS scan.
gScanKeepResult = false;
CHFRSCANNER_Stop();
}
// scan the CTCSS/DCS code
gBackup_CROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
@@ -941,12 +962,12 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
uint16_t Channel = gEeprom.ScreenChannel[gEeprom.TX_VFO];
if (bKeyHeld || !bKeyPressed) { // key held or released
if (gInputBoxIndex > 0) {
gInputBoxIndex = 0;
gHasVfoBackup = false;
}
if (gInputBoxIndex > 0) {
gInputBoxIndex = 0;
gHasVfoBackup = false;
}
if (bKeyHeld || !bKeyPressed) { // key held or released
if (!bKeyPressed) {
if (!bKeyHeld || IS_FREQ_CHANNEL(Channel))
return;
@@ -1011,9 +1032,7 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
}
// jump to the next channel
CHFRSCANNER_Start(false, Direction);
gScanPauseDelayIn_10ms = 1;
gScheduleScanListen = false;
CHFRSCANNER_ManualResume(Direction);
gPttWasReleased = true;
}
@@ -1079,4 +1098,4 @@ void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
}
}
+229 -78
View File
@@ -222,6 +222,13 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
*pMax = ARRAY_SIZE(gSubMenu_RX_TX) - 1;
break;
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
case MENU_SET_SAV:
//*pMin = 0;
*pMax = SET_SAV_LEN - 1;
break;
#endif
#ifndef ENABLE_FEAT_F4HWN
#ifdef ENABLE_AM_FIX
case MENU_AM_FIX:
@@ -419,13 +426,19 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
#endif
case MENU_SET_LCK:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_LCK) - 1;
*pMax = SET_LCK_LEN - 1;
break;
case MENU_SET_MET:
case MENU_SET_GUI:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_MET) - 1;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_SCN) - 1;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
//*pMin = 0;
@@ -457,6 +470,28 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
#endif
#endif
case MENU_VOL: {
// SysInf paginates:
// page 0 = identity,
// page 1 = build date/time,
// page 2 = battery,
// +1 if F4HWN_MEM (Flash/SRAM),
// +2 if F4HWN_QRCODE (Code QR + Wiki QR).
int32_t vol_max = 0;
#ifdef ENABLE_FEAT_F4HWN
vol_max += 2;
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
vol_max += 1;
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
vol_max += 2;
#endif
if (vol_max == 0) return -1;
*pMax = vol_max;
break;
}
default:
return -1;
}
@@ -594,7 +629,7 @@ void MENU_AcceptSetting(void)
case MENU_MEM_NAME:
for (int i = 9; i >= 0; i--) {
if (edit[i] != ' ' && edit[i] != '_' && edit[i] != 0x00 && edit[i] != 0xff)
if (edit[i] != ' ' && edit[i] != 0x00 && edit[i] != 0xff)
break;
edit[i] = ' ';
}
@@ -690,7 +725,7 @@ void MENU_AcceptSetting(void)
case MENU_LIST_CH:
gTxVfo->SCANLIST_PARTICIPATION = gSubMenuSelection;
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, false, true);
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
return;
@@ -717,7 +752,7 @@ void MENU_AcceptSetting(void)
case MENU_COMPAND:
gTxVfo->Compander = gSubMenuSelection;
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, false, true);
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
// gRequestSaveChannel = 1;
@@ -778,7 +813,7 @@ void MENU_AcceptSetting(void)
case MENU_D_LIVE_DEC:
gSetting_live_DTMF_decoder = gSubMenuSelection;
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
if (!gSetting_live_DTMF_decoder)
BK4819_DisableDTMF();
gFlagReconfigureVfos = true;
@@ -829,7 +864,7 @@ void MENU_AcceptSetting(void)
#endif
case MENU_DEL_CH:
SETTINGS_UpdateChannel(gSubMenuSelection, NULL, false, false, true);
SETTINGS_UpdateChannel(gSubMenuSelection, NULL, false);
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
return;
@@ -967,6 +1002,11 @@ void MENU_AcceptSetting(void)
case MENU_SET_GUI:
gSetting_set_gui = gSubMenuSelection;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
gSetting_set_scn = gSubMenuSelection;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
@@ -997,6 +1037,11 @@ void MENU_AcceptSetting(void)
case MENU_SET_TMR:
gSetting_set_tmr = gSubMenuSelection;
break;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
case MENU_SET_SAV:
gSetting_set_sav = gSubMenuSelection;
break;
#endif
case MENU_TX_LOCK:
gTxVfo->TX_LOCK = gSubMenuSelection;
gRequestSaveChannel = 1;
@@ -1030,6 +1075,11 @@ void MENU_ShowCurrentSetting(void)
gSubMenuSelection = gEeprom.SQUELCH_LEVEL;
break;
case MENU_VOL:
// SysInf is paginated; always start on page 0 (identity).
gSubMenuSelection = 0;
break;
case MENU_STEP:
gSubMenuSelection = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING);
break;
@@ -1426,6 +1476,11 @@ void MENU_ShowCurrentSetting(void)
case MENU_SET_GUI:
gSubMenuSelection = gSetting_set_gui;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
gSubMenuSelection = gSetting_set_scn;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
@@ -1454,6 +1509,11 @@ void MENU_ShowCurrentSetting(void)
case MENU_SET_TMR:
gSubMenuSelection = gSetting_set_tmr;
break;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
case MENU_SET_SAV:
gSubMenuSelection = gSetting_set_sav;
break;
#endif
case MENU_TX_LOCK:
gSubMenuSelection = gTxVfo->TX_LOCK;
break;
@@ -1464,6 +1524,29 @@ void MENU_ShowCurrentSetting(void)
}
}
static KEY_Code_t edit_last_key = 255;
static uint8_t edit_char_index = 0;
static const char* const char_map[10] = {
" 0", // KEY_0
".,-()@/\\+=*#<>[]~1", // KEY_1
"abc2", // KEY_2
"def3", // KEY_3
"ghi4", // KEY_4
"jkl5", // KEY_5
"mno6", // KEY_6
"pqrs7", // KEY_7
"tuv8", // KEY_8
"wxyz9" // KEY_9
};
static bool MENU_IsEditingName() {
return !gCssBackgroundScan
&& UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME
&& gIsInSubMenu
&& edit_index >= 0;
}
static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Offset;
@@ -1471,33 +1554,55 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
int32_t Max;
uint16_t Value = 0;
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (edit_index >= 10)
return;
if (edit_index < 10)
uint8_t key_idx = Key - KEY_0;
if (bKeyHeld)
{
if (Key <= KEY_9)
edit[edit_index] = '0' + key_idx;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (Key != edit_last_key)
{
edit_last_key = Key;
edit_char_index = 0;
}
else
{
edit_char_index++;
if (char_map[key_idx][edit_char_index] == '\0')
{
edit[edit_index] = '0' + Key - KEY_0;
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
gRequestDisplayScreen = DISPLAY_MENU;
edit_char_index = 0;
}
}
char c = char_map[key_idx][edit_char_index];
if (edit_is_uppercase && c >= 'a' && c <= 'z')
{
c -= 32;
}
edit[edit_index] = c;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld)
return;
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_MENU;
@@ -1586,7 +1691,7 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
gInputBoxIndex = 0;
//Value = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
Value = ((gInputBox[0] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1;
Value = (((gInputBox[0] * 10 + gInputBox[1]) * 10 + gInputBox[2]) * 10 + gInputBox[3]) - 1;
if (IS_MR_CHANNEL(Value))
{
@@ -1643,6 +1748,53 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (MENU_IsEditingName())
{
if (!bKeyPressed)
{
if (bKeyHeld)
return; // release after a long press, keep editing
if (edit_index == 0)
goto Skip;
if (edit_index > 0)
{ // step back one character while editing the channel name
edit_index--;
edit_last_key = 255;
gAskForConfirmation = 0;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (!bKeyHeld)
{ // wait to see if the user wants a short exit or a long backspace
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
Skip:
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are editing from one of them. */
BACKLIGHT_TurnOn();
gAskForConfirmation = 0;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld || !bKeyPressed)
return;
@@ -1650,29 +1802,22 @@ static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
if (!gCssBackgroundScan)
{
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are exiting from one of them. */
BACKLIGHT_TurnOn();
if (gIsInSubMenu)
{
if (gInputBoxIndex == 0 || UI_MENU_GetCurrentMenuId() != MENU_OFFSET)
{
gAskForConfirmation = 0;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
goto Skip;
}
else
{
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are exiting from one of them. */
BACKLIGHT_TurnOn();
gInputBox[--gInputBoxIndex] = 10;
gRequestDisplayScreen = DISPLAY_MENU;
}
// ***********************
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
@@ -1703,9 +1848,9 @@ static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed || (bKeyHeld && (!MENU_IsEditingName() || gAskForConfirmation)))
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_MENU;
@@ -1751,12 +1896,18 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
SETTINGS_FetchChannelName(edit, gSubMenuSelection);
// pad the channel name out with '_'
edit_index = strlen(edit);
while (edit_index < 10)
edit[edit_index++] = '_';
edit[edit_index] = 0;
// pad the channel name out with ' '
size_t len = strlen(edit);
if (len < 10)
{
memset(edit + len, ' ', 10 - len);
edit[10] = '\0';
}
edit_index = 0; // 'edit_index' is going to be used as the cursor position
edit_last_key = 255;
edit_char_index = 0;
edit_is_uppercase = false;
// make a copy so we can test for change when exiting the menu item
memcpy(edit_original, edit, sizeof(edit_original));
@@ -1766,9 +1917,14 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
else
if (edit_index >= 0 && edit_index < 10)
{ // editing the channel name characters
edit_last_key = 255;
if (++edit_index < 10)
return; // next char
if (bKeyHeld) {
edit_index = 10;
}
else if (++edit_index < 10) {
return;
}
// exit
gFlagAcceptSetting = false;
@@ -1844,7 +2000,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
@@ -1854,13 +2010,8 @@ static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
if (edit_index < 10)
{
edit[edit_index] = '-';
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
edit[edit_index] = !bKeyHeld ? '-' : '*';
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
@@ -1868,6 +2019,9 @@ static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
return;
}
if (bKeyHeld)
return;
RADIO_SelectVfos();
#ifdef ENABLE_NOAA
@@ -1898,15 +2052,23 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
uint16_t Channel;
bool bCheckScanList;
if (!bKeyPressed)
return;
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
if (!gEeprom.SET_NAV && gIsInSubMenu) {
Direction = -Direction;
}
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0)
{ // change the character
if (bKeyPressed && edit_index < 10 && Direction != 0)
if (edit_index < 10 && Direction != 0)
{
const char unwanted[] = "$%&!\"':;?^`|{}";
const char unwanted[] = "$%&!\"':;?^`|{}_";
char c = edit[edit_index] + Direction;
unsigned int i = 0;
while (i < sizeof(unwanted) && c >= 32 && c <= 126)
@@ -1918,25 +2080,13 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
}
}
edit[edit_index] = (c < 32) ? 126 : (c > 126) ? 32 : c;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (!bKeyHeld)
{
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gInputBoxIndex = 0;
}
else
if (!bKeyPressed)
return;
if (SCANNER_IsScanning()) {
return;
}
@@ -2067,19 +2217,20 @@ void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
case KEY_F:
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (!bKeyHeld && bKeyPressed)
if (!bKeyPressed)
break;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (edit_index < 10)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (edit_index < 10)
{
edit[edit_index] = ' ';
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
gRequestDisplayScreen = DISPLAY_MENU;
}
if (bKeyHeld)
edit[edit_index] = '#';
edit_is_uppercase = !edit_is_uppercase;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
break;
}
-187
View File
@@ -1,187 +0,0 @@
/*
* REGA Alam and Test
* ==================
*
* This bit of code is to implement the REGA test and alarm functions.
* This function sends out a fixed ZVEI tone sequence for testing or alarm purposes.
* Further information: https://www.rega.ch/en/our-missions/sites-and-infrastructure/emergency-radio
*
* There are two REGA ACTIONS: Test and Alarm.
* The Test action sends out a fixed ZVEI tone sequence for testing purposes.
* The Alarm action sends out a fixed ZVEI tone sequence for alarm purposes.
*
* These actions can be assigned to a key in the settings menu.
*
* The Test/Alarm function will perform the following actions:
* - Set the radio to transmit mode
* - Wait 100ms to allow the radio to switch to transmit mode and stabilize the tramsmitter
* - Send out the ZVEI tone sequence
* - Wait 100ms to allow the radio to finish transmitting
* - Set the radio back to receive mode
*
* The ZVEI tone squence for alarm is: 21414
* The ZVEI tone squence for test is: 21301
*
* To save space the two tone sequences are stored as precalculated register values.
* This avoids the need to calculate the register values at runtime.
* They are hardcoded as array of 32bit integers in the rega.h file.
*
* Copyright 2025 Markus Bärtschi
* https://github.com/markusb
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "rega.h"
#include "radio.h"
#include "action.h"
#include "misc.h"
#include "settings.h"
#include "functions.h"
#include "driver/bk4819.h"
#include "driver/st7565.h"
#include "driver/system.h"
#include "driver/gpio.h"
#include "bsp/dp32g030/gpio.h"
#include "ui/helper.h"
#include "ui/ui.h"
const uint16_t rega_alarm_tones[5] = {
1160, // 2 1160Hz
1060, // 1 1060Hz
1400, // 4 1400Hz
1060, // 1 1060Hz
1400, // 4 1400Hz
};
const uint16_t rega_test_tones[5] = {
1160, // 2 1160Hz
1060, // 1 1060Hz
1260, // 3 1260Hz
2400, // 0 2400Hz
1060, // 1 1060Hz
};
// Global variable to pass the message to the display
char rega_message[16];
// Transmit the ZVEI tone sequence for alarm
void ACTION_RegaAlarm()
{
const char message[16] = "REGA Alarm";
REGA_TransmitZvei(rega_alarm_tones,message);
}
// Tranmit the ZVEI tone sequence for test
void ACTION_RegaTest()
{
const char message[16] = "REGA Test";
REGA_TransmitZvei(rega_test_tones,message);
}
// Display the REGA message on the screen
void UI_DisplayREGA()
{
UI_DisplayClear();
UI_DisplayPopup(rega_message);
ST7565_BlitFullScreen();
}
// Transmit a ZVEI tone sequence on the REGA frequency
// Configures the radio on VFO A with the required parameters
// tones: array of 5 ZVEI tones
// message: message to display on the screen
void REGA_TransmitZvei(const uint16_t tones[],const char message[])
{
// Copy the message text
strncpy(rega_message,message,16);
// Trigger the display
gScreenToDisplay = DISPLAY_REGA;
// Flash the green LED twice
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
SYSTEM_DelayMs(70);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
SYSTEM_DelayMs(50);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
SYSTEM_DelayMs(70);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Select VFO A
gEeprom.TX_VFO = 0;
gEeprom.ScreenChannel[gEeprom.TX_VFO] = gEeprom.FreqChannel[gEeprom.TX_VFO];
gRxVfo = gTxVfo;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
// Set the REGA frequency, no offset
gTxVfo->freq_config_RX.Frequency = REGA_FREQUENCY;
gTxVfo->freq_config_TX = gTxVfo->freq_config_RX;
gTxVfo->TX_OFFSET_FREQUENCY = 0;
// Set the modulation to FM narrow
gTxVfo->Modulation = MODULATION_FM;
gTxVfo->CHANNEL_BANDWIDTH = BK4819_FILTER_BW_NARROW;
// Set Tx Squelch Tone
gTxVfo->freq_config_TX.CodeType = CODE_TYPE_CONTINUOUS_TONE;
gTxVfo->freq_config_TX.Code = REGA_CTCSS_FREQ_INDEX;
BK4819_SetCTCSSFrequency(CTCSS_Options[gTxVfo->freq_config_TX.Code]);
// Turn Rx squelch tone off
gTxVfo->freq_config_RX.CodeType = CODE_TYPE_OFF;
// Set the transmit power to high
gTxVfo->OUTPUT_POWER = OUTPUT_POWER_HIGH;
// Lock the keyboard
gEeprom.KEY_LOCK = true;
gRequestSaveChannel = 1;
gRequestSaveSettings = true;
// Configure the receiver
BK4819_RX_TurnOn();
// Set the radio to transmit mode
RADIO_PrepareCssTX();
FUNCTION_Select(FUNCTION_TRANSMIT);
// Wait to allow the radio to switch to transmit mode and stabilize the transmitter
SYSTEM_DelayMs(ZVEI_PRE_LENGTH_MS);
// Send out the ZVEI2 tone sequence
for (int i = 0; i < ZVEI_NUM_TONES; i++)
{
BK4819_PlaySingleTone(tones[i], ZVEI_TONE_LENGTH_MS, 100, true);
SYSTEM_DelayMs(ZVEI_PAUSE_LENGTH_MS);
}
// Wait to allow the radio to finish transmitting
SYSTEM_DelayMs(ZVEI_POST_LENGTH_MS);
// Flash the green LED twice
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
SYSTEM_DelayMs(70);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
SYSTEM_DelayMs(70);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
SYSTEM_DelayMs(70);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Display the normal screen
gRequestDisplayScreen = DISPLAY_MAIN;
}
-65
View File
@@ -1,65 +0,0 @@
/*
* REGA Alam and Test
* ==================
*
* This function sends out a fixed ZVEI tone sequence for testing or alarm purposes for the Alpine REGA alarm channel.
* Further information: https://www.rega.ch/en/our-missions/sites-and-infrastructure/emergency-radio
*
* There are two REGA ACTIONS: Test and Alarm.
* The Test action sends out a fixed ZVEI tone sequence for testing purposes.
* The Alarm action sends out a fixed ZVEI tone sequence for alarm purposes.
*
* These actions can be assigned to a key in the settings menu.
*
* The Test/Alarm function will perform the following actions:
* - Set the radio configuration to the REGA frequency, CTCSS, FM, full power etc.
* - Start transmitting
* - Wait 100ms to allow the radio to switch to transmit mode and stabilize the transmitter
* - Send out the ZVEI tone sequence
* - Wait 100ms to allow the radio to finish transmitting
* - Set the radio back to receive mode
*
* The REGA frequency is 161.300 Mhz
*
* The ZVEI tone squence for alarm is: 21414
* The ZVEI tone squence for test is: 21301
*
* The two tone sequences are hardcoded in two arrays
*
* Copyright 2025 Markus Bärtschi
*
* https://github.com/markusb
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef REGA_H
#define REGA_H
#include <stdint.h>
#define ZVEI_NUM_TONES 5
#define ZVEI_TONE_LENGTH_MS 70 // 70
#define ZVEI_PAUSE_LENGTH_MS 10 // 10
#define ZVEI_PRE_LENGTH_MS 300
#define ZVEI_POST_LENGTH_MS 100
#define REGA_CTCSS_FREQ_INDEX 18 // dcs.c: CTCSS_Options[18] = 1230
#define REGA_FREQUENCY 16130000 // 43370000 16130000
void ACTION_RegaAlarm(void);
void ACTION_RegaTest(void);
void UI_DisplayREGA(void);
void REGA_TransmitZvei(const uint16_t[], const char[]);
#endif
+1376
View File
File diff suppressed because it is too large. Load diff
+89
View File
@@ -0,0 +1,89 @@
/* Copyright 2026
*
* Licensed under the Apache License, Version 2.0.
*/
#ifndef APP_RXTX_LOG_H
#define APP_RXTX_LOG_H
#include <stdbool.h>
#include <stdint.h>
#include "driver/keyboard.h"
#include "functions.h"
#include "radio.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#define RXTX_LOG_VISIBLE_COUNT 512
// Field order mirrors the leading fields of RXTX_LogFlashEntry_t
// (rxtx_log.c) so both layouts match byte-for-byte up to and including
// battVolt, copied in one pass. Scan-only fields (sequence) sit past the
// copied prefix in the flash layout and are not cached in RAM.
// The channel name is not stored in flash: it is resolved from `channel` when
// exporting the K5Viewer packet.
typedef struct {
uint32_t frequency;
uint32_t trafficSeq;
uint16_t durationSeconds;
uint16_t channel;
uint8_t flags;
uint8_t sMeter;
uint8_t battVolt;
} RXTX_LogEntry_t;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
#define RXTX_LOG_K5VIEWER_VERSION 2u
#define RXTX_LOG_K5VIEWER_ROW_COUNT 64u
#define RXTX_LOG_K5VIEWER_HISTORY_ROW_COUNT 64u
#define RXTX_LOG_K5VIEWER_HISTORY_START 0xFFFFFFFFu
#define RXTX_LOG_K5VIEWER_NAME_LENGTH 10u
#define RXTX_LOG_K5VIEWER_STATUS_ACTIVE (1u << 0)
#define RXTX_LOG_K5VIEWER_STATUS_HAS_TRAFFIC (1u << 1)
#define RXTX_LOG_K5VIEWER_STATUS_CLEARING (1u << 2)
#define RXTX_LOG_K5VIEWER_STATUS_DISABLED (1u << 3)
#define RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE 4u
typedef struct __attribute__((packed)) {
uint32_t frequency;
uint32_t trafficSeq;
uint16_t durationSeconds;
uint16_t channel;
uint8_t flags;
uint8_t meter;
uint8_t battVolt;
char channelName[RXTX_LOG_K5VIEWER_NAME_LENGTH];
} RXTX_LogK5ViewerRow_t;
// On-wire packet layout: a disabled log sends only the 4-byte status header.
// An enabled log continues with one live row and
// RXTX_LOG_K5VIEWER_ROW_COUNT row slots, zero-padded past the last valid row.
// The packet is streamed row by row and never built whole in RAM.
#define RXTX_LOG_K5VIEWER_PACKET_SIZE (RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE + ((RXTX_LOG_K5VIEWER_ROW_COUNT + 1u) * sizeof(RXTX_LogK5ViewerRow_t)))
#define RXTX_LOG_K5VIEWER_HISTORY_PACKET_SIZE (RXTX_LOG_K5VIEWER_HISTORY_ROW_COUNT * sizeof(RXTX_LogK5ViewerRow_t))
uint32_t RXTX_LOG_K5ViewerSignature(void);
void RXTX_LOG_SendK5ViewerPacket(void (*send)(const uint8_t *data, uint16_t size));
// Sends the page of history rows below `beforeSeq` (HISTORY_START begins
// a dump below the live packet's coverage) and returns the bound for the
// next page, 0 once the visible history is exhausted.
uint32_t RXTX_LOG_SendK5ViewerHistoryPage(uint32_t beforeSeq, void (*send)(const uint8_t *data, uint16_t size));
#endif
void RXTX_LOG_Init(void);
bool RXTX_LOG_IsEnabled(void);
void RXTX_LOG_BeginRx(const VFO_Info_t *vfo, FUNCTION_Type_t function);
void RXTX_LOG_BeginTx(const VFO_Info_t *vfo);
void RXTX_LOG_EndActive(void);
void RXTX_LOG_Task10ms(void);
void RXTX_LOG_Tick500ms(void);
const char *RXTX_LOG_GetFilterName(void);
void ACTION_RxTxLog(void);
void RXTX_LOG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void UI_DisplayRxTxLog(void);
#endif
#endif
+122 -29
View File
@@ -41,6 +41,101 @@ bool gScanUseCssResult;
STEP_Setting_t stepSetting;
uint8_t scanHitCount;
typedef enum
{
SCAN_FREQ_VERIFY_OFF,
SCAN_FREQ_VERIFY_FUNDAMENTAL,
SCAN_FREQ_VERIFY_HARMONIC
} SCAN_FrequencyVerifyState_t;
static SCAN_FrequencyVerifyState_t scanFreqVerifyState;
static uint32_t scanVerifyFundamental;
static uint32_t scanVerifyHarmonic;
static uint16_t scanVerifyFundamentalRssi;
static bool SCANNER_ShouldVerifyVhfSecondHarmonic(const uint32_t frequency)
{
const uint32_t fundamental = frequency / 2;
return frequency >= (frequencyBandTable[BAND3_137MHz].lower * 2) &&
frequency < (frequencyBandTable[BAND4_174MHz].lower * 2) &&
fundamental >= frequencyBandTable[BAND3_137MHz].lower &&
fundamental < frequencyBandTable[BAND4_174MHz].lower;
}
static void SCANNER_StartCssScanAtFrequency(const uint32_t frequency)
{
gScanFrequency = frequency;
gScanCssResultCode = 0xFF;
gScanCssResultType = 0xFF;
scanHitCount = 0;
gScanUseCssResult = false;
gScanProgressIndicator = 0;
gScanCssState = SCAN_CSS_STATE_SCANNING;
gScanDelay_10ms = scan_delay_10ms;
BK4819_SetScanFrequency(gScanFrequency);
if (!gCssBackgroundScan)
GUI_SelectNextDisplay(DISPLAY_SCANNER);
gUpdateStatus = true;
}
static void SCANNER_TuneForFrequencyVerification(const uint32_t frequency)
{
BK4819_SetFrequency(frequency);
BK4819_PickRXFilterPathBasedOnFrequency(frequency);
BK4819_RX_TurnOn();
}
static uint16_t SCANNER_ReadVerificationRssi(void)
{
(void)BK4819_GetRSSI();
return BK4819_GetRSSI();
}
static void SCANNER_StartFrequencyVerification(const uint32_t harmonic)
{
scanVerifyFundamental = harmonic / 2;
scanVerifyHarmonic = harmonic;
scanFreqVerifyState = SCAN_FREQ_VERIFY_FUNDAMENTAL;
SCANNER_TuneForFrequencyVerification(scanVerifyFundamental);
gScanDelay_10ms = 2;
}
static bool SCANNER_HandleFrequencyVerification(void)
{
switch (scanFreqVerifyState) {
case SCAN_FREQ_VERIFY_FUNDAMENTAL:
scanVerifyFundamentalRssi = SCANNER_ReadVerificationRssi();
scanFreqVerifyState = SCAN_FREQ_VERIFY_HARMONIC;
SCANNER_TuneForFrequencyVerification(scanVerifyHarmonic);
gScanDelay_10ms = 2;
return true;
case SCAN_FREQ_VERIFY_HARMONIC: {
const uint16_t harmonicRssi = SCANNER_ReadVerificationRssi();
const uint16_t margin = 4;
uint32_t verifiedFrequency = scanVerifyHarmonic;
if (scanVerifyFundamentalRssi > harmonicRssi &&
scanVerifyFundamentalRssi - harmonicRssi >= margin)
{
verifiedFrequency = scanVerifyFundamental;
}
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
SCANNER_StartCssScanAtFrequency(verifiedFrequency);
return true;
}
default:
return false;
}
}
static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
@@ -62,7 +157,7 @@ static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
gInputBoxIndex = 0;
// uint16_t chan = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
uint16_t chan = ((gInputBox[0] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1;
uint16_t chan = (((gInputBox[0] * 10 + gInputBox[1]) * 10 + gInputBox[2]) * 10 + gInputBox[3]) - 1;
if (IS_MR_CHANNEL(chan)) {
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
@@ -243,16 +338,13 @@ static void SCANNER_Key_STAR(bool bKeyPressed, bool bKeyHeld)
return;
}
static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Direction)
static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
{
if (pKeyHeld) {
if (!bKeyPressed)
return;
if (!bKeyPressed) {
return;
}
else {
if (!bKeyPressed)
return;
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
@@ -266,7 +358,7 @@ static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Directio
gShowChPrefix = RADIO_CheckValidChannel(gScanChannel, false, 0);
gRequestDisplayScreen = DISPLAY_SCANNER;
}
else
else if (!bKeyHeld)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
@@ -344,8 +436,9 @@ void SCANNER_Start(bool singleFreq)
gScanCssState = SCAN_CSS_STATE_OFF;
gScanFrequency = 0xFFFFFFFF;
BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
BK4819_EnableFrequencyScan();
// Keep the RX input path selected by RADIO_SetupRegisters().
// 0xFFFFFFFF means "unknown frequency" here and would turn both LNAs off.
BK4819_SetFrequencyScan(true);
gUpdateStatus = true;
}
@@ -369,6 +462,7 @@ void SCANNER_Start(bool singleFreq)
g_SquelchLost = false;
gScannerSaveState = SCAN_SAVE_NO_PROMPT;
gScanProgressIndicator = 0;
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
}
void SCANNER_Stop(void)
@@ -380,6 +474,7 @@ void SCANNER_Stop(void)
gUpdateStatus = true;
gCssBackgroundScan = false;
gScanUseCssResult = false;
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
@@ -401,6 +496,10 @@ void SCANNER_TimeSlice10ms(void)
return;
}
if (SCANNER_HandleFrequencyVerification()) {
return;
}
switch (gScanCssState) {
case SCAN_CSS_STATE_OFF: {
// must be RF frequency scanning if we're here ?
@@ -418,27 +517,21 @@ void SCANNER_TimeSlice10ms(void)
else
scanHitCount = 0;
BK4819_DisableFrequencyScan();
BK4819_SetFrequencyScan(false);
if (scanHitCount < 3) {
BK4819_EnableFrequencyScan();
BK4819_SetFrequencyScan(true);
}
else if (SCANNER_ShouldVerifyVhfSecondHarmonic(gScanFrequency)) {
SCANNER_StartFrequencyVerification(gScanFrequency);
}
else {
BK4819_SetScanFrequency(gScanFrequency);
gScanCssResultCode = 0xFF;
gScanCssResultType = 0xFF;
scanHitCount = 0;
gScanUseCssResult = false;
gScanProgressIndicator = 0;
gScanCssState = SCAN_CSS_STATE_SCANNING;
if(!gCssBackgroundScan)
GUI_SelectNextDisplay(DISPLAY_SCANNER);
gUpdateStatus = true;
SCANNER_StartCssScanAtFrequency(gScanFrequency);
}
gScanDelay_10ms = scan_delay_10ms;
if (scanFreqVerifyState == SCAN_FREQ_VERIFY_OFF) {
gScanDelay_10ms = scan_delay_10ms;
}
//gScanDelay_10ms = 1; // 10ms
break;
}
@@ -509,8 +602,8 @@ void SCANNER_TimeSlice500ms(void)
if (SCANNER_IsScanning() && gScannerSaveState == SCAN_SAVE_NO_PROMPT && gScanCssState < SCAN_CSS_STATE_FOUND) {
gScanProgressIndicator++;
#ifndef ENABLE_NO_CODE_SCAN_TIMEOUT
if (gScanProgressIndicator > 32) {
if (gScanCssState == SCAN_CSS_STATE_SCANNING && !gScanSingleFrequency)
if (gScanCssState == SCAN_CSS_STATE_SCANNING && gScanProgressIndicator > 32) {
if (!gScanSingleFrequency)
gScanCssState = SCAN_CSS_STATE_FOUND;
else
gScanCssState = SCAN_CSS_STATE_FAILED;
@@ -528,4 +621,4 @@ void SCANNER_TimeSlice500ms(void)
bool SCANNER_IsScanning(void)
{
return gCssBackgroundScan || (gScreenToDisplay == DISPLAY_SCANNER);
}
}
+1127 -315
View File
File diff suppressed because it is too large. Load diff
+20 -13
View File
@@ -42,7 +42,8 @@
#include <stdint.h>
#include <string.h>
static const uint8_t DrawingEndY = 40;
static const uint8_t DrawingEndY = 40;
static const uint8_t DrawingTopY = 8; // Reserve top 8px for frequency display (gFrameBuffer[0])
static const uint8_t U8RssiMap[] = {
121,
@@ -78,29 +79,35 @@ static const uint16_t scanStepValues[] = {
static const uint16_t scanStepBWRegValues[] = {
// RX RXw TX BW
// 0b0 000 000 001 01 1000
// 1
// 1 (S_STEP_0_01kHz, index 0)
0b0000000001011000, // 6.25
// 10
// 10 (S_STEP_0_1kHz, index 1)
0b0000000001011000, // 6.25
// 50
// 50 (S_STEP_0_5kHz, index 2)
0b0000000001011000, // 6.25
// 100
// 100 (S_STEP_1_0kHz, index 3)
0b0000000001011000, // 6.25
// 250
// 250 (S_STEP_2_5kHz, index 4)
0b0000000001011000, // 6.25
// 500
// 500 (S_STEP_5_0kHz, index 5)
0b0010010001011000, // 6.25
// 625
// 625 (S_STEP_6_25kHz, index 6)
0b0100100001011000, // 6.25
// 833
// 833 (S_STEP_8_33kHz, index 7)
0b0110110001001000, // 6.25
// 1000
// 1000 (S_STEP_10_0kHz, index 8)
0b0110110001001000, // 6.25
// 1250
// 1250 (S_STEP_12_5kHz, index 9)
0b0111111100001000, // 6.25
// 2500
// 1500 (S_STEP_15_0kHz, index 10)
0b0011011000101000, // 25
// 10000
// 2000 (S_STEP_20_0kHz, index 11)
0b0011011000101000, // 25
// 2500 (S_STEP_25_0kHz, index 12)
0b0011011000101000, // 25
// 5000 (S_STEP_50_0kHz, index 13)
0b0011011000101000, // 25
// 10000 (S_STEP_100_0kHz, index 14)
0b0011011000101000, // 25
};
+21 -8
View File
@@ -347,9 +347,9 @@ static void CMD_0514(uint32_t Port, const uint8_t *pBuffer)
#endif
gSerialConfigCountDown_500ms = 12; // 6 sec
// turn the LCD backlight off
BACKLIGHT_TurnOff();
if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on
BACKLIGHT_TurnOff(); // turn the LCD backlight off
SendVersion(Port);
}
@@ -593,8 +593,8 @@ static void CMD_052F(uint32_t Port, const uint8_t *pBuffer)
}
#endif
// turn the LCD backlight off
BACKLIGHT_TurnOff();
if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on
BACKLIGHT_TurnOff(); // turn the LCD backlight off
SendVersion(Port);
}
@@ -863,7 +863,20 @@ void UART_HandleCommand(uint32_t Port)
#endif
} // switch
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
gUART_LockScreenshot = 20; // lock screenshot
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
gUART_LockK5Viewer = 20; // lock the K5Viewer stream
#endif
}
}
void UART_ServiceCommands(void)
{
#ifdef ENABLE_USB
if (UART_IsCommandAvailable(UART_PORT_VCP))
UART_HandleCommand(UART_PORT_VCP);
#endif
#ifdef ENABLE_UART
if (UART_IsCommandAvailable(UART_PORT_UART))
UART_HandleCommand(UART_PORT_UART);
#endif
}
+1 -1
View File
@@ -32,6 +32,6 @@ enum
bool UART_IsCommandAvailable(uint32_t Port);
void UART_HandleCommand(uint32_t Port);
void UART_ServiceCommands(void);
#endif
+29 -97
View File
@@ -33,32 +33,32 @@
#include "settings.h"
#include "ui/ui.h"
static const uint16_t BEEP_Classic_array[][3] = { /* Tone Duration Repeats */
[BEEP_NONE] = {0, 0, 0 },
[BEEP_1KHZ_60MS_OPTIONAL] = {1000, 60, 1 },
[BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL] = {500, 60, 2 },
#ifdef ENABLE_DTMF_CALLING
[BEEP_880HZ_200MS] = {880, 200, 1 },
[BEEP_880HZ_500MS] = {880, 500, 1 },
#endif
#ifdef ENABLE_FEAT_F4HWN
[BEEP_400HZ_30MS] = {400, 30, 1 },
[BEEP_500HZ_30MS] = {500, 30, 1 },
[BEEP_600HZ_30MS] = {600, 30, 1 },
#endif
[BEEP_880HZ_60MS_TRIPLE_BEEP] = {880, 60, 3 }
};
BEEP_Type_t gBeepToPlay = BEEP_NONE;
static void AUDIO_PlayBeepPulse(void) {
BK4819_ExitTxMute();
SYSTEM_DelayMs(60);
BK4819_EnterTxMute();
SYSTEM_DelayMs(20);
}
void AUDIO_PlayBeep(BEEP_Type_t Beep)
{
if (Beep == BEEP_NONE)
return;
if (Beep != BEEP_880HZ_60MS_DOUBLE_BEEP &&
Beep != BEEP_500HZ_60MS_DOUBLE_BEEP &&
Beep != BEEP_440HZ_500MS &&
#ifdef ENABLE_DTMF_CALLING
Beep != BEEP_880HZ_200MS &&
Beep != BEEP_880HZ_500MS &&
#endif
#ifdef ENABLE_FEAT_F4HWN
Beep != BEEP_400HZ_30MS &&
Beep != BEEP_500HZ_30MS &&
Beep != BEEP_600HZ_30MS &&
#endif
!gEeprom.BEEP_CONTROL)
if ((Beep == BEEP_1KHZ_60MS_OPTIONAL ||
Beep == BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL) &&
!gEeprom.BEEP_CONTROL)
return;
if (gCurrentFunction == FUNCTION_RECEIVE)
@@ -67,6 +67,9 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
if (gCurrentFunction == FUNCTION_MONITOR)
return;
if (Beep >= ARRAY_SIZE(BEEP_Classic_array))
return;
#ifdef ENABLE_FMRADIO
if (gFmRadioMode)
BK1080_Mute(true);
@@ -81,49 +84,14 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
uint16_t ToneConfig = BK4819_ReadRegister(BK4819_REG_71);
uint16_t ToneFrequency;
switch (Beep)
{
default:
case BEEP_NONE:
ToneFrequency = 220;
break;
case BEEP_1KHZ_60MS_OPTIONAL:
ToneFrequency = 1000;
break;
case BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL:
case BEEP_500HZ_60MS_DOUBLE_BEEP:
ToneFrequency = 500;
break;
case BEEP_440HZ_500MS:
ToneFrequency = 440;
break;
case BEEP_880HZ_60MS_DOUBLE_BEEP:
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
case BEEP_880HZ_500MS:
#endif
ToneFrequency = 880;
break;
#ifdef ENABLE_FEAT_F4HWN
case BEEP_400HZ_30MS:
ToneFrequency = 400;
break;
case BEEP_500HZ_30MS:
ToneFrequency = 500;
break;
case BEEP_600HZ_30MS:
ToneFrequency = 600;
break;
#endif
}
if(Beep == BEEP_400HZ_30MS || Beep == BEEP_500HZ_30MS || Beep == BEEP_600HZ_30MS)
{
BK4819_WriteRegister(BK4819_REG_70, BK4819_REG_70_ENABLE_TONE1 | ((1 & 0x7f) << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
}
#endif
BK4819_PlayTone(ToneFrequency, true);
BK4819_PrepareToPlayTone(true);
SYSTEM_DelayMs(2);
@@ -131,46 +99,12 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
SYSTEM_DelayMs(60);
uint16_t Duration;
switch (Beep)
{
case BEEP_880HZ_60MS_DOUBLE_BEEP:
AUDIO_PlayBeepPulse();
[[fallthrough]];
case BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL:
case BEEP_500HZ_60MS_DOUBLE_BEEP:
AUDIO_PlayBeepPulse();
[[fallthrough]];
case BEEP_1KHZ_60MS_OPTIONAL:
BK4819_ExitTxMute();
Duration = 60;
break;
#ifdef ENABLE_FEAT_F4HWN
case BEEP_400HZ_30MS:
case BEEP_500HZ_30MS:
case BEEP_600HZ_30MS:
BK4819_ExitTxMute();
Duration = 30;
break;
#endif
case BEEP_440HZ_500MS:
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
BK4819_ExitTxMute();
Duration = 200;
break;
case BEEP_880HZ_500MS:
#endif
default:
BK4819_ExitTxMute();
Duration = 500;
break;
for (uint8_t i = 0; i < BEEP_Classic_array[Beep][BEEP_REPEATS]; i++) {
BK4819_PlayToneRaw( BEEP_Classic_array[Beep][BEEP_TONE],
BEEP_Classic_array[Beep][BEEP_DURATION]);
SYSTEM_DelayMs(20);
}
SYSTEM_DelayMs(Duration);
BK4819_EnterTxMute();
SYSTEM_DelayMs(20);
AUDIO_AudioPathOff();
SYSTEM_DelayMs(5);
@@ -185,7 +119,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
SYSTEM_DelayMs(10);
#endif
if (gEnableSpeaker)
AUDIO_AudioPathOn();
@@ -200,7 +133,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
#ifdef ENABLE_VOX
gVoxResumeCountdown = 80;
#endif
}
#ifdef ENABLE_VOICE
+8 -3
View File
@@ -22,23 +22,28 @@
#include "driver/gpio.h"
enum {
BEEP_TONE = 0,
BEEP_DURATION,
BEEP_REPEATS
};
enum BEEP_Type_t
{
BEEP_NONE = 0,
BEEP_1KHZ_60MS_OPTIONAL,
BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL,
BEEP_440HZ_500MS,
#ifdef ENABLE_DTMF_CALLING
BEEP_880HZ_200MS,
BEEP_880HZ_500MS,
#endif
BEEP_500HZ_60MS_DOUBLE_BEEP,
#ifdef ENABLE_FEAT_F4HWN
BEEP_400HZ_30MS,
BEEP_500HZ_30MS,
BEEP_600HZ_30MS,
#endif
BEEP_880HZ_60MS_DOUBLE_BEEP
BEEP_880HZ_60MS_TRIPLE_BEEP
};
typedef enum BEEP_Type_t BEEP_Type_t;
+144
View File
@@ -57,6 +57,21 @@ const uint8_t gFontLight[9] =
0b00001100,
};
// Same bulb outline as gFontLight with the inner filament removed,
// used when the manual backlight is currently off
const uint8_t gFontLightOff[9] =
{
0b00001100,
0b00010010,
0b00100001,
0b01100001,
0b01100001,
0b01100001,
0b00100001,
0b00010010,
0b00001100,
};
const uint8_t gFontMute[12] =
{
0b00011100,
@@ -286,6 +301,135 @@ const uint8_t BITMAP_NOAA[12] =
};
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
const uint8_t BITMAP_FoxHuntSignal[10] =
{ // point source + the speaker bitmap's two sound waves, oriented horizontally
0b00001000,
0b00011100,
0b00011100,
0b00001000,
0b00000000,
0b00100010,
0b00011100,
0b01000001,
0b00100010,
0b00011100
};
const uint8_t BITMAP_FoxHuntSpeaker[10] =
{ // speaker cone + two sound waves (bit0 = top row)
0b00011100,
0b00011100,
0b00111110,
0b01111111,
0b00000000,
0b00100010,
0b00011100,
0b01000001,
0b00100010,
0b00011100
};
const uint8_t BITMAP_FoxHuntUp[11] =
{ // filled up triangle (getting nearer)
0b00100000,
0b00110000,
0b00111000,
0b00111100,
0b00111110,
0b00111111,
0b00111110,
0b00111100,
0b00111000,
0b00110000,
0b00100000
};
const uint8_t BITMAP_FoxHuntDown[11] =
{ // filled down triangle (getting farther)
0b00000001,
0b00000011,
0b00000111,
0b00001111,
0b00011111,
0b00111111,
0b00011111,
0b00001111,
0b00000111,
0b00000011,
0b00000001
};
const uint8_t BITMAP_FoxHuntFlat[11] =
{ // equals sign (stable)
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010,
0b00010010
};
const uint8_t BITMAP_FoxHuntBars[11] =
{ // ascending bars = S-meter (staircase) gauge mode (bit0 = top row)
0b01000000,
0b01000000,
0b00000000,
0b01110000,
0b01110000,
0b00000000,
0b01111100,
0b01111100,
0b00000000,
0b01111111,
0b01111111
};
const uint8_t BITMAP_FoxHuntGraph[15] =
{ // sine wave = signal-history gauge mode (bit0 = top row)
0b00001000,
0b00000100,
0b00000010,
0b00000100,
0b00001000,
0b00010000,
0b00100000,
0b00010000,
0b00001000,
0b00000100,
0b00000010,
0b00000100,
0b00001000,
0b00010000,
0b00100000
};
const uint8_t BITMAP_FoxHuntTx[16] =
{
0b00011100,
0b00100010,
0b01000001,
0b00011100,
0b00100010,
0b00000000,
0b00001000,
0b00011100,
0b00011100,
0b00001000,
0b00000000,
0b00100010,
0b00011100,
0b01000001,
0b00100010,
0b00011100
};
#endif
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
const uint8_t BITMAP_CurrentIndicator[8] = {
0xFF,
+12
View File
@@ -12,6 +12,7 @@ extern const uint8_t gFontS[6];
extern const uint8_t gFontKeyLock[9];
extern const uint8_t gFontLight[9];
extern const uint8_t gFontLightOff[9];
extern const uint8_t gFontMute[12];
extern const uint8_t gFontXB[2][6];
@@ -44,6 +45,17 @@ extern const uint8_t BITMAP_compand[6];
extern const uint8_t BITMAP_NOAA[12];
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
extern const uint8_t BITMAP_FoxHuntSignal[10];
extern const uint8_t BITMAP_FoxHuntSpeaker[10];
extern const uint8_t BITMAP_FoxHuntUp[11];
extern const uint8_t BITMAP_FoxHuntDown[11];
extern const uint8_t BITMAP_FoxHuntFlat[11];
extern const uint8_t BITMAP_FoxHuntBars[11];
extern const uint8_t BITMAP_FoxHuntGraph[15];
extern const uint8_t BITMAP_FoxHuntTx[16];
#endif
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
extern const uint8_t BITMAP_CurrentIndicator[8];
#endif
+51
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <stddef.h>
#include "dcs.h"
#include "misc.h"
@@ -26,6 +27,12 @@ const uint16_t CTCSS_Options[50] = {
2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503, 2541
};
// Indices in CTCSS_Options for the 12 non-homologated tones.
static const uint8_t CTCSS_ExtraIdx[12] = {
1, 26, 28, 30, 32, 34, 36, 38, 39, 41,
45, 49
};
const uint16_t DCS_Options[104] = {
0x0013, 0x0015, 0x0016, 0x0019, 0x001A, 0x001E, 0x0023, 0x0027,
0x0029, 0x002B, 0x002C, 0x0035, 0x0039, 0x003A, 0x003B, 0x003C,
@@ -42,6 +49,13 @@ const uint16_t DCS_Options[104] = {
0x01C3, 0x01CA, 0x01D3, 0x01D9, 0x01DA, 0x01DC, 0x01E3, 0x01EC,
};
// Indices in DCS_Options for the 21 non-PMR446 DCS codes.
static const uint8_t DCS_ExtraIdx[21] = {
5, 9, 19, 25, 34, 36, 41, 43, 44, 48,
50, 54, 56, 60, 71, 72, 73, 74, 75, 82,
83
};
static uint32_t DCS_CalculateGolay(uint32_t CodeWord)
{
unsigned int i;
@@ -108,3 +122,40 @@ uint8_t DCS_GetCtcssCode(int Code)
return Result;
}
static uint8_t DCS_GetApprovedIndex(uint8_t Option, size_t options_size, size_t extraidx_size, const uint8_t *extraidx)
{
unsigned int i;
unsigned int extra_pos = 0;
uint8_t approved_index = 0;
if (Option >= options_size)
return 0xFF;
for (i = 0; i < options_size; i++) {
const bool is_extra = extra_pos < extraidx_size &&
i == extraidx[extra_pos];
if (is_extra) {
extra_pos++;
continue;
}
if (i == Option)
return approved_index;
approved_index++;
}
return 0xFF;
}
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option)
{
return DCS_GetApprovedIndex(Option, ARRAY_SIZE(CTCSS_Options), ARRAY_SIZE(CTCSS_ExtraIdx), CTCSS_ExtraIdx);
}
uint8_t DCS_GetDcsApprovedIndex(uint8_t Option)
{
return DCS_GetApprovedIndex(Option, ARRAY_SIZE(DCS_Options), ARRAY_SIZE(DCS_ExtraIdx), DCS_ExtraIdx);
}
+2 -1
View File
@@ -40,6 +40,7 @@ extern const uint16_t DCS_Options[104];
uint32_t DCS_GetGolayCodeWord(DCS_CodeType_t CodeType, uint8_t Option);
uint8_t DCS_GetCdcssCode(uint32_t Code);
uint8_t DCS_GetCtcssCode(int Code);
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option);
uint8_t DCS_GetDcsApprovedIndex(uint8_t Option);
#endif
+57 -7
View File
@@ -32,7 +32,9 @@
#endif
#define PWM_FREQ 4000
#define DUTY_CYCLE_LEVELS 64
// 32 levels keep every step of the value[] table distinct after
// quantization while halving the DMA duty cycle buffer (128 bytes saved)
#define DUTY_CYCLE_LEVELS 32
#define DUTY_CYCLE_ON_VALUE GPIO_PIN_MASK(GPIO_PIN_BACKLIGHT)
#define DUTY_CYCLE_OFF_VALUE (DUTY_CYCLE_ON_VALUE << 16)
@@ -52,12 +54,23 @@ static int16_t currentBrightness = 0;
static int16_t targetBrightness = 0;
static int16_t fadeStep = 0;
static void BACKLIGHT_SetHardwareBrightness(uint8_t brightness);
#ifdef ENABLE_FEAT_F4HWN
// Power-on fade-in tuning.
// STEPS = number of intermediate brightness stops (smoothness).
// STEP_MS = pacing between stops.
// STEPS * STEP_MS ~= total fade duration in ms.
#define BL_STARTUP_FADE_STEPS 40
#define BL_STARTUP_FADE_STEP_MS 12
#endif
#ifdef ENABLE_FEAT_F4HWN
const uint8_t value[] = {
0, // 0 off
8, // 1 visible in the dark
14, // 2
22, // 3
16, // 2
24, // 3
32, // 4
48, // 5
72, // 6
@@ -107,10 +120,15 @@ void BACKLIGHT_InitHardware()
static void BACKLIGHT_Sound(void)
{
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_ALL)
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND ||
gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_ALL
#ifdef ENABLE_FEAT_F4HWN_LOGO
|| gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_LOGO
#endif
)
{
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
}
gK5startup = false;
@@ -123,6 +141,37 @@ void BACKLIGHT_UpdateTickless(void) {
}
}
#ifdef ENABLE_FEAT_F4HWN
// Soft, progressive power-on fade-in.
// Ramps from the current brightness (0 at power-on) to targetBrightness using
// a smoothstep (3x^2 - 2x^3) easing curve. Easing gently at both ends reads as
// a smooth, progressive fade instead of the abrupt linear ramp, and stepping
// through many fine stops keeps it fluid even with the 32-level PWM.
static void BACKLIGHT_FadeInStartup(void)
{
const int16_t from = currentBrightness; // 0 at power-on
const int16_t span = targetBrightness - from; // ramp amplitude
if (span <= 0) {
gUpdateBacklight = false;
return;
}
for (uint16_t s = 1; s < BL_STARTUP_FADE_STEPS; s++) {
// x in Q8 (0..256), e = smoothstep(x) in Q16 (0..65536)
uint32_t x = (uint32_t)s * 256 / BL_STARTUP_FADE_STEPS;
uint32_t e = (x * x * (768 - 2 * x)) >> 8;
currentBrightness = from + (int16_t)(((uint32_t)span * e) >> 16);
BACKLIGHT_SetHardwareBrightness((uint8_t)currentBrightness);
SYSTEM_DelayMs(BL_STARTUP_FADE_STEP_MS);
}
currentBrightness = targetBrightness;
BACKLIGHT_SetHardwareBrightness((uint8_t)currentBrightness);
gUpdateBacklight = false;
}
#endif
void BACKLIGHT_TurnOn(void)
{
#ifdef ENABLE_FEAT_F4HWN_SLEEP
@@ -156,10 +205,11 @@ void BACKLIGHT_TurnOn(void)
if(startup)
#endif
{
BACKLIGHT_UpdateTickless();
#ifdef ENABLE_FEAT_F4HWN
BACKLIGHT_FadeInStartup();
BACKLIGHT_Sound();
#else
BACKLIGHT_UpdateTickless();
startup = false;
#endif
}
+21 -5
View File
@@ -411,6 +411,11 @@ void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet)
BK4819_WriteRegister(BK4819_REG_33, gBK4819_GpioOutState);
}
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin)
{
return (gBK4819_GpioOutState & (0x40u >> Pin)) != 0;
}
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord)
{
// REG_51
@@ -1719,7 +1724,7 @@ void BK4819_PrepareFSKReceive(void)
BK4819_WriteRegister(BK4819_REG_59, 0x3068);
}
static void BK4819_PlayRogerNormal(void)
static void BK4819_PlayRogerNormal(BK4819_FilterBandwidth_t Bandwidth)
{
#if 0
const uint32_t tone1_Hz = 500;
@@ -1734,12 +1739,19 @@ static void BK4819_PlayRogerNormal(void)
BK4819_EnterTxMute();
BK4819_SetAF(BK4819_AF_MUTE);
BK4819_WriteRegister(BK4819_REG_70, BK4819_REG_70_ENABLE_TONE1 | (66u << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
const uint8_t rogerToneGain = (Bandwidth == BK4819_FILTER_BW_WIDE) ? 32u : 66u;
if (Bandwidth == BK4819_FILTER_BW_WIDE)
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_NARROW, true);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
BK4819_EnableTXLink();
SYSTEM_DelayMs(50);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
BK4819_WriteRegister(BK4819_REG_70,
BK4819_REG_70_ENABLE_TONE1 |
(rogerToneGain << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
BK4819_ExitTxMute();
SYSTEM_DelayMs(80);
@@ -1752,6 +1764,10 @@ static void BK4819_PlayRogerNormal(void)
BK4819_EnterTxMute();
BK4819_WriteRegister(BK4819_REG_70, 0x0000);
if (Bandwidth == BK4819_FILTER_BW_WIDE)
BK4819_SetFilterBandwidth(Bandwidth, true);
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE); // 1 1 0000 0 1 1111 1 1 1 0
}
@@ -1804,10 +1820,10 @@ void BK4819_PlayRogerMDC(void)
BK4819_WriteRegister(BK4819_REG_58, 0x0000);
}
void BK4819_PlayRoger(void)
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth)
{
if (gEeprom.ROGER == ROGER_MODE_ROGER) {
BK4819_PlayRogerNormal();
BK4819_PlayRogerNormal(Bandwidth);
} else if (gEeprom.ROGER == ROGER_MODE_MDC) {
BK4819_PlayRogerMDC();
}
+6 -3
View File
@@ -77,6 +77,7 @@ void BK4819_SetAGC(bool enable);
void BK4819_InitAGC(bool amModulation);
void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet);
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin);
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord);
void BK4819_SetCTCSSFrequency(uint32_t BaudRate);
@@ -105,7 +106,9 @@ void BK4819_SetCompander(const unsigned int mode);
void BK4819_DisableVox(void);
void BK4819_DisableDTMF(void);
void BK4819_EnableDTMF(void);
void BK4819_PrepareToPlayTone(bool bTuningGainSwitch);
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch);
void BK4819_PlayToneRaw(const unsigned int tone_Hz, const unsigned int delay);
void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay, const unsigned int level, const bool play_speaker);
void BK4819_EnterTxMute(void);
void BK4819_ExitTxMute(void);
@@ -147,11 +150,11 @@ uint8_t BK4819_GetGlitchIndicator(void);
uint8_t BK4819_GetExNoiceIndicator(void);
uint16_t BK4819_GetVoiceAmplitudeOut(void);
uint8_t BK4819_GetAfTxRx(void);
void BK4819_SetRxAudioGain(void);
bool BK4819_GetFrequencyScanResult(uint32_t *pFrequency);
BK4819_CssScanResult_t BK4819_GetCxCSSScanResult(uint32_t *pCdcssFreq, uint16_t *pCtcssFreq);
void BK4819_DisableFrequencyScan(void);
void BK4819_EnableFrequencyScan(void);
void BK4819_SetFrequencyScan(bool enable);
void BK4819_SetScanFrequency(uint32_t Frequency);
void BK4819_Disable(void);
@@ -167,7 +170,7 @@ uint8_t BK4819_GetCTCType(void);
void BK4819_SendFSKData(uint16_t *pData);
void BK4819_PrepareFSKReceive(void);
void BK4819_PlayRoger(void);
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth);
void BK4819_Enable_AfDac_DiscMode_TxDsp(void);
+108 -87
View File
@@ -39,6 +39,16 @@ static const uint16_t FSK_RogerTable[7] = {0xF1A2, 0x7446, 0x61A4, 0x6544, 0x4E8
static uint16_t gBK4819_GpioOutState;
#define SHORT_DELAY() \
__asm volatile("nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n")
bool gRxIdleMode;
static inline void CS_Assert()
@@ -197,63 +207,78 @@ static uint16_t BK4819_ReadU16(void)
uint16_t Value;
SDA_SetDir(false);
SYSTICK_DelayUs(1);
SHORT_DELAY();
Value = 0;
for (i = 0; i < 16; i++)
{
Value <<= 1;
Value |= SDA_ReadInput();
SCL_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
SDA_SetDir(true);
return Value;
}
static uint16_t reg_30_cache = 0xFFFF;
static uint16_t reg_47_cache = 0xFFFF;
uint16_t BK4819_ReadRegister(BK4819_REGISTER_t Register)
{
uint16_t Value;
CS_Release();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Assert();
BK4819_WriteU8(Register | 0x80);
Value = BK4819_ReadU16();
CS_Release();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SDA_Set();
if (Register == BK4819_REG_30)
reg_30_cache = Value;
else if (Register == BK4819_REG_47)
reg_47_cache = Value;
return Value;
}
void BK4819_WriteRegister(BK4819_REGISTER_t Register, uint16_t Data)
{
if (Register == BK4819_REG_30)
{
if (Data == reg_30_cache)
return;
reg_30_cache = Data;
}
else if (Register == BK4819_REG_47)
{
if (Data == reg_47_cache)
return;
reg_47_cache = Data;
}
CS_Release();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Assert();
BK4819_WriteU8(Register);
SYSTICK_DelayUs(1);
SHORT_DELAY();
BK4819_WriteU16(Data);
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Release();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SDA_Set();
@@ -271,14 +296,14 @@ void BK4819_WriteU8(uint8_t Data)
else
SDA_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
Data <<= 1;
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
}
@@ -294,14 +319,14 @@ void BK4819_WriteU16(uint16_t Data)
else
SDA_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
Data <<= 1;
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
}
@@ -416,6 +441,11 @@ void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet)
BK4819_WriteRegister(BK4819_REG_33, gBK4819_GpioOutState);
}
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin)
{
return (gBK4819_GpioOutState & (0x40u >> Pin)) != 0;
}
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord)
{
// REG_51
@@ -671,11 +701,11 @@ void BK4819_SetFilterBandwidth(const BK4819_FilterBandwidth_t Bandwidth, const b
break;
case BK4819_FILTER_BW_NARROWER: // 6.25kHz
// 0x205C = (0b010 << 12) | (0b000 << 9) | (0b001 << 6) |
// (0b01 << 4) | (1 << 3) | (1 << 2)
// 0x2058 = (0b010 << 12) | (0b000 << 9) | (0b001 << 6) |
// (0b01 << 4) | (1 << 3)
// = RF 3.0 kHz, weak-RF 2.0 kHz, AF Tx LPF2 2.5 kHz,
// 6.25k mode, FM gain +6 dB.
val = 0x205C;
// 6.25k mode, FM gain 0 dB.
val = 0x2058;
break;
case BK4819_FILTER_BW_AM: // Stock AM preset
@@ -1006,7 +1036,7 @@ void BK4819_EnableDTMF(void)
(15u << BK4819_REG_24_SHIFT_MAX_SYMBOLS)); // 0 ~ 15
}
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
void BK4819_PrepareToPlayTone(bool bTuningGainSwitch)
{
uint16_t ToneConfig = BK4819_REG_70_ENABLE_TONE1;
@@ -1021,10 +1051,23 @@ void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
BK4819_WriteRegister(BK4819_REG_30, 0);
BK4819_WriteRegister(BK4819_REG_30, BK4819_REG_30_ENABLE_AF_DAC | BK4819_REG_30_ENABLE_DISC_MODE | BK4819_REG_30_ENABLE_TX_DSP);
}
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
{
BK4819_PrepareToPlayTone(bTuningGainSwitch);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(Frequency));
}
void BK4819_PlayToneRaw(const unsigned int tone_Hz, const unsigned int delay) {
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(delay);
BK4819_EnterTxMute();
}
// level 0 ~ 127
void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay, const unsigned int level, const bool play_speaker)
{
@@ -1044,11 +1087,7 @@ void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay,
BK4819_EnableTXLink();
SYSTEM_DelayMs(50);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(delay);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone_Hz, delay);
if (play_speaker)
{
@@ -1432,24 +1471,15 @@ void BK4819_GenTail(uint8_t Tail)
// freq(Hz) * 20.64888 for XTAL 13M/26M or
// freq(Hz)*20.97152 for XTAL 12.8M/19.2M/25.6M/38.4M
switch (Tail)
{
case 0: // 134.4Hz CTCSS Tail
BK4819_WriteRegister(BK4819_REG_52, 0x828F); // 1 00 0 001010 001111
break;
case 1: // 120° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xA28F); // 1 01 0 001010 001111
break;
case 2: // 180° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xC28F); // 1 10 0 001010 001111
break;
case 3: // 240° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xE28F); // 1 11 0 001010 001111
break;
case 4: // 55Hz tone freq
BK4819_WriteRegister(BK4819_REG_07, 0x046f); // 0 00 0 010001 101111
break;
}
if (Tail <= 3)
// 0: 134.4Hz CTCSS Tail
// 1: 120° phase shift
// 2: 180° phase shift
// 3: 240° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0x028F | (Tail << 13));
else if (Tail == 4)
// 4: 55Hz tone freq
BK4819_WriteRegister(BK4819_REG_07, 0x046F);
}
void BK4819_PlayCDCSSTail(void)
@@ -1531,6 +1561,14 @@ uint8_t BK4819_GetAfTxRx(void)
return BK4819_ReadRegister(BK4819_REG_6F) & 0x003F;
}
void BK4819_SetRxAudioGain(void) {
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
}
bool BK4819_GetFrequencyScanResult(uint32_t *pFrequency)
{
const uint16_t High = BK4819_ReadRegister(BK4819_REG_0D);
@@ -1566,7 +1604,7 @@ BK4819_CssScanResult_t BK4819_GetCxCSSScanResult(uint32_t *pCdcssFreq, uint16_t
return BK4819_CSS_RESULT_NOT_FOUND;
}
void BK4819_DisableFrequencyScan(void)
void BK4819_SetFrequencyScan(bool enable)
{
// REG_32
//
@@ -1582,32 +1620,10 @@ void BK4819_DisableFrequencyScan(void)
// 1 = enable
// 0 = disable
//
BK4819_WriteRegister(BK4819_REG_32, // 0x0244); // 00 0000100100010 0
BK4819_WriteRegister(BK4819_REG_32,
( 0u << 14) | // 0 frequency scan Time
(290u << 1) | // ???
( 0u << 0)); // 0 frequency scan enable
}
void BK4819_EnableFrequencyScan(void)
{
// REG_32
//
// <15:14> 0 frequency scan time
// 0 = 0.2 sec
// 1 = 0.4 sec
// 2 = 0.8 sec
// 3 = 1.6 sec
//
// <13:1> ???
//
// <0> 0 frequency scan enable
// 1 = enable
// 0 = disable
//
BK4819_WriteRegister(BK4819_REG_32, // 0x0245); // 00 0000100100010 1
( 0u << 14) | // 0 frequency scan time
(290u << 1) | // ???
( 1u << 0)); // 1 frequency scan enable
(enable ? 1u : 0u)); // frequency scan (1: enable | 0: disable)
}
void BK4819_SetScanFrequency(uint32_t Frequency)
@@ -1671,7 +1687,7 @@ void BK4819_Disable(void)
void BK4819_StopScan(void)
{
BK4819_DisableFrequencyScan();
BK4819_SetFrequencyScan(false);
BK4819_Disable();
}
@@ -1743,7 +1759,7 @@ void BK4819_PrepareFSKReceive(void)
BK4819_WriteRegister(BK4819_REG_59, 0x3068);
}
static void BK4819_PlayRogerNormal(void)
static void BK4819_PlayRogerNormal(BK4819_FilterBandwidth_t Bandwidth)
{
#if 0
const uint32_t tone1_Hz = 500;
@@ -1758,26 +1774,31 @@ static void BK4819_PlayRogerNormal(void)
BK4819_EnterTxMute();
BK4819_SetAF(BK4819_AF_MUTE);
BK4819_WriteRegister(BK4819_REG_70, // BK4819_REG_70_ENABLE_TONE1 | (66u << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
0xC300
);
const uint8_t rogerToneGain = (Bandwidth == BK4819_FILTER_BW_WIDE) ? 32u : 67u;
if (Bandwidth == BK4819_FILTER_BW_WIDE)
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_NARROW, true);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
BK4819_EnableTXLink();
SYSTEM_DelayMs(50);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
BK4819_WriteRegister(BK4819_REG_70,
BK4819_REG_70_ENABLE_TONE1 |
(rogerToneGain << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
BK4819_ExitTxMute();
SYSTEM_DelayMs(80);
BK4819_EnterTxMute();
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone2_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(80);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone2_Hz, 80);
BK4819_WriteRegister(BK4819_REG_70, 0x0000);
if (Bandwidth == BK4819_FILTER_BW_WIDE)
BK4819_SetFilterBandwidth(Bandwidth, true);
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE); // 1 1 0000 0 1 1111 1 1 1 0
}
@@ -1830,10 +1851,10 @@ void BK4819_PlayRogerMDC(void)
BK4819_WriteRegister(BK4819_REG_58, 0x0000);
}
void BK4819_PlayRoger(void)
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth)
{
if (gEeprom.ROGER == ROGER_MODE_ROGER) {
BK4819_PlayRogerNormal();
BK4819_PlayRogerNormal(Bandwidth);
} else if (gEeprom.ROGER == ROGER_MODE_MDC) {
BK4819_PlayRogerMDC();
}
+22 -3
View File
@@ -51,7 +51,13 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
_MK_MAPPING(0x008000, 0x008000, 0x00886E), // 1024 MR + 7 VFO Attributes * 2 Bytes (ex 0x002000) 0x008000 -> 0x00880E
// List name * 4 Bytes 0x00880E -> 0x00886E
_MK_MAPPING(0x009000, 0x009000, 0x0090D6), // 14 VFO * 16 Bytes (ex 0x001000)
_MK_MAPPING(0x009000, 0x009000, 0x0090E7), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old
// 0x90D6 bound was 10 B short: it truncated the
// 470 MHz VFO1 record at 0x90D0 -> 0x90DF)
// Fox Hunt settings * 7 Bytes 0x90E0 -> 0x90E7
// (ENABLE_FEAT_F4HWN_FOXHUNT, written directly by
// app/foxhunt.c; concatenated here so aircopy clones
// them together with the VFOs)
_MK_MAPPING(0x00A000, 0x00A000, 0x00A170), // Settings * 16 Bytes (ex 0x004000) 0x00A000 -> 0x00A010
// Settings * 16 Bytes (ex 0x005000) 0x00A010 -> 0x00A020
@@ -59,13 +65,26 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
// MR FM * 128 Bytes (0x003000) 0x00A028 -> 0x00A0A8
// Settings * 80 Bytes (0x007000) 0x00A0A8 -> 0x00A0F8
// Settings * 56 Bytes (0x008000) 0x00A0F8 -> 0x00A130
// Settings Scanlist * 8 Bytes (0x009000) 0x00A130 -> 0x00A140
// Settings AES * 16 Bytes (0x00A000) 0x00A140 -> 0x00A150
// Settings Scanlist * 8 Bytes (0x009000) 0x00A130 -> 0x00A138
// Settings AES * 16 Bytes (0x00A000) 0x00A138 -> 0x00A148
// Settings Spectrum * 8 Bytes 0x00A148 -> 0x00A150
// Settings * 8 Bytes (0x00B000) 0x00A150 -> 0x00A158
// Settings F4HWN * 8 Bytes (0x00C000) 0x00A158 -> 0x00A160
// Settings Version * 16 Bytes 0x00A160 -> 0x00A170
_MK_MAPPING(0x010000, 0x00B000, 0x00B200), // Calibration 512 Bytes!!!
_MK_MAPPING(0x011000, 0x00C000, 0x00D000), // Boot Logo sector (4 KB):
// [0x00..0x07] 8-byte header (reserved)
// [0x08..0x407] 128x64 monochrome bitmap, 1024 Bytes
// ST7565-native: 8 pages * 128 columns, column-major LSB-top
// Not mapped, for documentation only (the EEPROM API uses 16-bit
// addresses and could not reach a 32 KB window anyway):
//
// 0x1E0000 -> 0x1E8000: RX/TX append-only log * 32 KB / 8 sectors
// (ENABLE_FEAT_F4HWN_RXTX_LOG, accessed directly
// by app/rxtx_log.c, not through this layer)
};
static void AddrTranslate(uint16_t EEPROM_Addr, uint16_t Size, uint32_t *PY25Q16_Addr_out, uint16_t *Size_out, bool *End_out);
+33 -5
View File
@@ -27,7 +27,7 @@ KEY_Code_t gKeyReading1 = KEY_INVALID;
uint16_t gDebounceCounter = 0;
bool gWasFKeyPressed = false;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Short press: hold key for SERIAL_KEY_SHORT_POLLS calls.
// Must exceed key_debounce_10ms (2) to trigger ProcessKey(key, true, false).
#define SERIAL_KEY_SHORT_POLLS 5
@@ -39,8 +39,14 @@ bool gWasFKeyPressed = false;
// Packet types for serial key injection (K5Viewer → radio)
#define SERIAL_KEY_TYPE 0x03
#define SERIAL_KEY_TYPE_LONG 0x04
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
#define SERIAL_FEATURE_TYPE 0x05
#endif
volatile KEY_Code_t gKeyFromSerial = KEY_INVALID;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
volatile uint8_t gSerialViewerFeatures = 0;
#endif
static uint8_t gSerialKeyHoldCount = 0;
static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press
@@ -71,13 +77,35 @@ bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b)
break;
case STATE_KA_2:
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
if (b == 0x00)
*state = STATE_KA_3;
else if (b == SERIAL_FEATURE_TYPE)
*state = STATE_KA_FEATURE;
else
*state = STATE_IDLE;
#else
*state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE;
#endif
break;
case STATE_KA_3:
if (b == 0x00) connected = true;
if (b == 0x00) {
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
gSerialViewerFeatures = 0;
#endif
connected = true;
}
*state = STATE_IDLE;
break;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
case STATE_KA_FEATURE:
gSerialViewerFeatures = b;
connected = true;
*state = STATE_IDLE;
break;
#endif
case STATE_KEY_1:
*state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE;
@@ -156,7 +184,7 @@ static const KEY_Code_t keyboard[5][4] = {
KEY_Code_t KEYBOARD_Poll(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Serial-injected key: hold it for SHORT or LONG polls depending on press type,
// so the debounce counter in app.c reaches the right threshold:
// - Short: key_debounce_10ms (2) → ProcessKey(key, true, false)
@@ -265,4 +293,4 @@ KEY_Code_t KEYBOARD_GetKey(void)
void HideFKeyIcon(void) {
gWasFKeyPressed = false;
gUpdateStatus = true;
}
}
+14 -2
View File
@@ -50,6 +50,9 @@ typedef enum {
STATE_KA_1,
STATE_KA_2,
STATE_KA_3,
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
STATE_KA_FEATURE,
#endif
STATE_KEY_1,
STATE_KEY_2,
STATE_KEY_3,
@@ -61,10 +64,19 @@ extern KEY_Code_t gKeyReading1;
extern uint16_t gDebounceCounter;
extern bool gWasFKeyPressed;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll).
extern volatile KEY_Code_t gKeyFromSerial;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
#define SERIAL_VIEWER_FEATURE_RF_LOG 0x01u
#define SERIAL_VIEWER_FEATURE_RF_LOG_HISTORY 0x02u
#define SERIAL_VIEWER_FEATURE_RF_LOG_RESTART 0x80u
// Extension flags announced by the viewer's feature keepalive.
extern volatile uint8_t gSerialViewerFeatures;
#endif
bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b);
#endif
@@ -73,4 +85,4 @@ KEY_Code_t KEYBOARD_GetKey(void);
void HideFKeyIcon(void);
#endif
#endif
+6 -2
View File
@@ -284,9 +284,13 @@ void PY25Q16_WriteBuffer(uint32_t Address, const void *pBuffer, uint32_t Size, b
if (0 != memcmp(pBuffer, (char *)SectorCache + SecOffset, SecSize))
{
bool Erase = false;
const uint8_t *oldData = SectorCache + SecOffset;
const uint8_t *newData = (const uint8_t *)pBuffer;
for (uint32_t i = 0; i < SecSize; i++)
{
if (0xff != SectorCache[SecOffset + i])
// NOR flash programming can only change bits from 1 to 0.
if ((oldData[i] & newData[i]) != newData[i])
{
Erase = true;
break;
@@ -491,4 +495,4 @@ void DMA1_Channel4_5_6_7_IRQHandler()
TC_Flag = true;
}
}
}
+4 -4
View File
@@ -24,7 +24,7 @@
#include "driver/st7565.h"
#include "driver/system.h"
#include "misc.h"
#include "screenshot.h"
#include "k5viewer.h"
#define SPIx SPI1
@@ -164,8 +164,8 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u
void ST7565_BlitLine(unsigned line)
{
ST7565_BlitScreen(line + 1);
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
SCREENSHOT_Update(true); // Force immediate capture
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(true); // Force immediate capture
#endif
}
@@ -260,7 +260,7 @@ void ST7565_FillScreen(uint8_t value)
// D=0, display OFF
#define ST7565_CMD_DISPLAY_ON_OFF 0xAE
uint8_t cmds[] = {
const uint8_t cmds[] = {
ST7565_CMD_BIAS_SELECT | 0, // Select bias setting: 1/9
ST7565_CMD_COM_DIRECTION | (0 << 3), // Set output direction of COM: normal
ST7565_CMD_SEG_DIRECTION | 1, // Set scan direction of SEG: reverse
+2 -2
View File
@@ -23,7 +23,7 @@
#include "py32f071_ll_gpio.h"
#include "py32f071_ll_usart.h"
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "driver/keyboard.h"
#endif
@@ -150,7 +150,7 @@ void UART_LogSend(const void *pBuffer, uint32_t Size)
}
}
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
bool UART_IsCableConnected(void) {
static uint8_t read_ptr = 0;
static ParseState_t state = STATE_IDLE;
+1 -1
View File
@@ -27,7 +27,7 @@ void UART_Init(void);
void UART_Send(const void *pBuffer, uint32_t Size);
void UART_LogSend(const void *pBuffer, uint32_t Size);
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
bool UART_IsCableConnected(void);
#endif
+15 -9
View File
@@ -18,7 +18,7 @@
#include "usb_config.h"
#include "py32f071_ll_bus.h"
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "driver/keyboard.h"
#endif
@@ -42,19 +42,25 @@ void VCP_Init()
NVIC_EnableIRQ(USBD_IRQn);
}
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
bool VCP_ScreenshotPing(void)
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
bool VCP_K5ViewerPing(void)
{
// State machine for parsing incoming packets:
// Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive
// Short key press: 0xAA 0x55 0x03 <key> → inject short press
// Long key press: 0xAA 0x55 0x04 <key> → inject long press
// Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive, no extensions
// Feature keepalive (ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER builds only):
// 0x55 0xAA 0x05 <flags> → viewer alive, extensions enabled
// flags bit 0 = RF log stream
// flags bit 1 = paged RF log history
// flags bit 7 = restart RF log synchronization
// Short key press: 0xAA 0x55 0x03 <key> → inject short press
// Long key press: 0xAA 0x55 0x04 <key> → inject long press
//
// State transitions:
// IDLE → 0x55 → KA_1
// KA_1 → 0xAA → KA_2 (else IDLE)
// KA_2 → 0x00 → KA_3 (else IDLE)
// KA_3 → 0x00 → keepalive OK, IDLE
// KA_2 → 0x00 → KA_3 (else 0x05 → KA_FEATURE, else IDLE)
// KA_3 → 0x00 → keepalive OK, clear extensions, IDLE
// KA_FEATURE → <flags> → keepalive OK, set extensions, IDLE
//
// IDLE → 0xAA → KEY_1
// KEY_1 → 0x55 → KEY_2 (else IDLE)
@@ -89,4 +95,4 @@ bool VCP_ScreenshotPing(void)
return connected;
}
#endif // ENABLE_FEAT_F4HWN_SCREENSHOT
#endif // ENABLE_FEAT_F4HWN_K5VIEWER
+2 -2
View File
@@ -29,8 +29,8 @@ extern volatile uint32_t VCP_RxBufPointer;
void VCP_Init();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
bool VCP_ScreenshotPing(void);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
bool VCP_K5ViewerPing(void);
#endif
static inline void VCP_Send(const uint8_t *Buf, uint32_t Size)
+1 -1
View File
@@ -362,7 +362,7 @@ const uint8_t gFontSmall[95-1][6] =
{0x20, 0x40, 0x40, 0x3D, 0x00, 0x00}, // 'j'
{0x00, 0x7F, 0x10, 0x28, 0x44, 0x00}, // 'k'
{0x00, 0x00, 0x3F, 0x40, 0x00, 0x00}, // 'l'
{0x7C, 0x08, 0x10, 0x10, 0x08, 0x7C}, // 'm'
{0x7C, 0x04, 0x7C, 0x04, 0x78, 0x00}, // 'm'
{0x7C, 0x04, 0x04, 0x04, 0x78, 0x00}, // 'n'
{0x38, 0x44, 0x44, 0x44, 0x38, 0x00}, // 'o'
{0x7C, 0x14, 0x14, 0x14, 0x08, 0x00}, // 'p'
+27 -1
View File
@@ -33,6 +33,9 @@
#include "frequencies.h"
#include "functions.h"
#include "helper/battery.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#include "misc.h"
#include "radio.h"
#include "settings.h"
@@ -151,7 +154,7 @@ void FUNCTION_Transmit()
// clear the DTMF RX live decoder buffer
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
#if defined(ENABLE_FMRADIO)
if (gFmRadioMode)
@@ -239,8 +242,31 @@ void FUNCTION_Select(FUNCTION_Type_t Function)
const FUNCTION_Type_t PreviousFunction = gCurrentFunction;
const bool bWasPowerSave = PreviousFunction == FUNCTION_POWER_SAVE;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
const bool previousWasActive =
PreviousFunction == FUNCTION_TRANSMIT ||
PreviousFunction == FUNCTION_MONITOR ||
PreviousFunction == FUNCTION_RECEIVE;
const bool previousWasTx = PreviousFunction == FUNCTION_TRANSMIT;
const bool nextIsActive =
Function == FUNCTION_TRANSMIT ||
Function == FUNCTION_MONITOR ||
Function == FUNCTION_RECEIVE;
const bool nextIsTx = Function == FUNCTION_TRANSMIT;
if (previousWasActive &&
(!nextIsActive ||
(previousWasTx != nextIsTx)))
RXTX_LOG_EndActive();
#endif
gCurrentFunction = Function;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
if (nextIsActive && !nextIsTx && (!previousWasActive || previousWasTx))
RXTX_LOG_BeginRx(gRxVfo, Function);
#endif
if (bWasPowerSave && Function != FUNCTION_POWER_SAVE) {
BK4819_Conditional_RX_TurnOn_and_GPIO6_Enable();
gRxIdleMode = false;
+1
View File
@@ -17,6 +17,7 @@
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include <stdbool.h>
#include <stdint.h>
enum FUNCTION_Type_t
+2 -2
View File
@@ -228,7 +228,7 @@ void BATTERY_TimeSlice500ms(void)
if (lowBatteryCountdown < lowBatteryPeriod) {
if (lowBatteryCountdown == lowBatteryPeriod-1 && !gChargingWithTypeC && !gLowBatteryConfirmed) {
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
}
return;
}
@@ -241,7 +241,7 @@ void BATTERY_TimeSlice500ms(void)
// not on charge
if (!gLowBatteryConfirmed) {
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
#ifdef ENABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_LOW_VOLTAGE);
#endif
+6 -6
View File
@@ -73,12 +73,14 @@ BOOT_Mode_t BOOT_GetMode(void)
void BOOT_ProcessMode(BOOT_Mode_t Mode)
{
GUI_DisplayType_t display = DISPLAY_MAIN;
if (Mode == BOOT_MODE_F_LOCK)
{
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
gEeprom.CURRENT_STATE = 0; // Don't resume is active...
#endif
GUI_SelectNextDisplay(DISPLAY_MENU);
display = DISPLAY_MENU;
}
#ifdef ENABLE_AIRCOPY
else
@@ -119,11 +121,9 @@ void BOOT_ProcessMode(BOOT_Mode_t Mode)
gEeprom.CURRENT_STATE = 0; // Don't resume is active...
#endif
GUI_SelectNextDisplay(DISPLAY_AIRCOPY);
display = DISPLAY_AIRCOPY;
}
#endif
else
{
GUI_SelectNextDisplay(DISPLAY_MAIN);
}
GUI_SelectNextDisplay(display);
}
+338
View File
@@ -0,0 +1,338 @@
/* Copyright 2024 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "debugging.h"
#include "driver/st7565.h"
#include "k5viewer.h"
#include "misc.h"
#include "driver/vcp.h"
#include "driver/keyboard.h"
#include "driver/bk4819.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
#include "app/rxtx_log.h"
#endif
// SRAM optimization: minimize static allocations
// - previousHash: one fingerprint per 8-byte chunk instead of a full
// 1024-byte copy of the previous frame (chunks are only compared,
// never retransmitted from history). A 16-bit fingerprint keeps the
// collision odds at ~1/65536 per chunk, so a stale chunk slipping
// through is rare; the forcedBlock rotation repairs that residual
// within at most 128 frames.
// Stack optimization: chunks are computed on demand straight from
// gStatusLine/gFrameBuffer instead of building the whole 1024-byte
// frame on the stack. Changed chunks are tracked in a 16-byte bitmap.
// Peak stack drops from ~1.3 KB to ~40 bytes, at the cost of
// transforming each transmitted chunk twice (negligible next to the
// blocking UART transfer).
static uint16_t previousHash[128];
static uint8_t previousStateFlags = 0xFF;
static uint8_t forcedBlock = 0;
static uint8_t keepAlive = 3;
static bool hasConnectionPing = false;
static bool wasConnected = false;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
static uint32_t previousRfLogSignature = 0;
static bool rfLogSent = false;
// Pagination bound of the history dump: rows with trafficSeq below this
// value remain to be sent; HISTORY_START arms a new dump, 0 means done.
static uint32_t rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
#endif
// FNV-1a over one 8-byte chunk, folded to 16 bits
static uint16_t K5VIEWER_Hash(const uint8_t *data)
{
uint32_t h = 2166136261u;
for (uint8_t i = 0; i < 8; i++) {
h = (h ^ data[i]) * 16777619u;
}
return (uint16_t)(h ^ (h >> 16));
}
void K5VIEWER_ParseInput(void)
{
if (K5VIEWER_IsLocked())
return;
if (UART_IsCableConnected()) {
keepAlive = 15;
hasConnectionPing = true;
gUSB_K5ViewerEnabled = false;
}
else if (VCP_K5ViewerPing()) {
keepAlive = 15;
hasConnectionPing = true;
gUSB_K5ViewerEnabled = true;
}
}
static void K5VIEWER_Send(const uint8_t *buf, uint16_t len)
{
if (gUSB_K5ViewerEnabled) {
cdc_acm_data_send_with_dtr(buf, len);
} else {
UART_Send(buf, len);
}
}
enum {
K5VIEWER_CHUNK_SIZE = 8,
K5VIEWER_CHUNKS_PER_LINE = 16,
K5VIEWER_HALF_LINE_COLUMNS = LCD_WIDTH / 2,
K5VIEWER_MARKER_BASE = 0xF0,
K5VIEWER_TYPE_DIFF = 0x02,
K5VIEWER_TYPE_RXTX_LOG = 0x05,
K5VIEWER_TYPE_RXTX_LOG_HISTORY = 0x06,
K5VIEWER_FLAG_DEEP_SLEEP = 1 << 0,
K5VIEWER_FLAG_LED_RED = 1 << 1,
K5VIEWER_FLAG_LED_GREEN = 1 << 2,
};
static uint8_t K5VIEWER_StateFlags(void)
{
uint8_t flags = 0;
#ifdef ENABLE_FEAT_F4HWN_SLEEP
if (gWakeUp)
flags |= K5VIEWER_FLAG_DEEP_SLEEP;
#endif
if (BK4819_IsGpioOutSet(BK4819_GPIO5_PIN1_RED))
flags |= K5VIEWER_FLAG_LED_RED;
if (BK4819_IsGpioOutSet(BK4819_GPIO6_PIN2_GREEN))
flags |= K5VIEWER_FLAG_LED_GREEN;
return flags;
}
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
static bool K5VIEWER_HasPendingRfLogUpdate(void)
{
if (!RXTX_LOG_IsEnabled())
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
if ((gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG_RESTART) != 0) {
gSerialViewerFeatures &= ~SERIAL_VIEWER_FEATURE_RF_LOG_RESTART;
rfLogSent = false;
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
}
if ((gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG) == 0)
return false;
return !rfLogSent || RXTX_LOG_K5ViewerSignature() != previousRfLogSignature;
}
static void K5VIEWER_SendRfLogFrameHeader(uint8_t type, uint16_t len)
{
uint8_t header[5] = {0xAA, 0x55, type, (uint8_t)(len >> 8), (uint8_t)len};
K5VIEWER_Send(header, sizeof(header));
}
static void K5VIEWER_SendRfLogFrameEnd(void)
{
const uint8_t end = 0x0A;
K5VIEWER_Send(&end, 1);
}
static void K5VIEWER_SendRfLog(void)
{
// Capture the signature before streaming: a state change landing
// during the blocking send still differs afterwards and triggers a
// resend on the next cycle.
previousRfLogSignature = RXTX_LOG_K5ViewerSignature();
rfLogSent = true;
const uint16_t size = RXTX_LOG_IsEnabled()
? RXTX_LOG_K5VIEWER_PACKET_SIZE
: RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE;
K5VIEWER_SendRfLogFrameHeader(K5VIEWER_TYPE_RXTX_LOG, size);
RXTX_LOG_SendK5ViewerPacket(K5VIEWER_Send);
K5VIEWER_SendRfLogFrameEnd();
}
static bool K5VIEWER_HasPendingRfLogHistory(void)
{
return RXTX_LOG_IsEnabled() &&
(gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG_HISTORY) != 0 &&
rfLogHistoryBefore != 0;
}
static void K5VIEWER_SendRfLogHistory(void)
{
K5VIEWER_SendRfLogFrameHeader(K5VIEWER_TYPE_RXTX_LOG_HISTORY,
RXTX_LOG_K5VIEWER_HISTORY_PACKET_SIZE);
rfLogHistoryBefore = RXTX_LOG_SendK5ViewerHistoryPage(rfLogHistoryBefore, K5VIEWER_Send);
K5VIEWER_SendRfLogFrameEnd();
}
#endif
bool K5VIEWER_HasPendingStateChange(void)
{
if (gUART_LockK5Viewer > 0 || keepAlive == 0 || !hasConnectionPing)
return false;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
if (K5VIEWER_HasPendingRfLogUpdate())
return true;
if (K5VIEWER_HasPendingRfLogHistory())
return true;
#endif
return !wasConnected || K5VIEWER_StateFlags() != previousStateFlags;
}
// Compute one 8-byte output chunk directly from the display buffers.
// Frame layout: per line (status + 7 frame lines), 8 bit layers of
// 16 bytes each. Each bit layer is split into two 64-column chunks.
static void K5VIEWER_Chunk(uint8_t chunkIdx, uint8_t *dest)
{
const uint8_t chunkInLine = chunkIdx % K5VIEWER_CHUNKS_PER_LINE;
const uint8_t line = chunkIdx / K5VIEWER_CHUNKS_PER_LINE;
const uint8_t bit = chunkInLine / 2;
const uint8_t columnBase = (chunkInLine % 2) * K5VIEWER_HALF_LINE_COLUMNS;
const uint8_t *src = (line == 0 ? gStatusLine : gFrameBuffer[line - 1])
+ columnBase;
for (uint8_t j = 0; j < K5VIEWER_CHUNK_SIZE; j++) {
uint8_t acc = 0;
for (uint8_t k = 0; k < K5VIEWER_CHUNK_SIZE; k++) {
if (src[j * K5VIEWER_CHUNK_SIZE + k] & (1 << bit)) acc |= (1 << k);
}
dest[j] = gSetting_set_inv ? ~acc : acc;
}
}
void K5VIEWER_Update(bool force)
{
if (K5VIEWER_IsLocked())
return;
if (keepAlive > 0) {
if (--keepAlive == 0) {
// Connection just lost → reset state for next reconnection
wasConnected = false;
hasConnectionPing = false;
previousStateFlags = 0xFF;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
gSerialViewerFeatures = 0;
rfLogSent = false;
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
#endif
return;
}
} else {
return;
}
// Connection is alive — detect reconnection and force full frame
if (!wasConnected) {
force = true;
}
const uint8_t stateFlags = K5VIEWER_StateFlags();
const bool stateChanged = (stateFlags != previousStateFlags);
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
const bool rfLogPending = K5VIEWER_HasPendingRfLogUpdate();
const bool rfLogHistoryPending = K5VIEWER_HasPendingRfLogHistory();
#else
const bool rfLogPending = false;
const bool rfLogHistoryPending = false;
#endif
// ==== FIRST PASS: Count changed chunks ====
uint16_t deltaLen = 0;
uint8_t changedBitmap[16] = {0}; // 1 bit per chunk
uint8_t chunk[9]; // [0] = index, [1..8] = payload
for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) {
K5VIEWER_Chunk(chunkIdx, &chunk[1]);
bool changed = K5VIEWER_Hash(&chunk[1]) != previousHash[chunkIdx];
bool isForced = (chunkIdx == forcedBlock);
if (changed || isForced || force) {
changedBitmap[chunkIdx >> 3] |= 1 << (chunkIdx & 7);
deltaLen += 9;
}
}
forcedBlock = (forcedBlock + 1) % 128;
if (deltaLen == 0 && !stateChanged && !rfLogPending && !rfLogHistoryPending)
return;
// Skip transmission if a key is currently pressed
// UART_Send is blocking - would freeze the main loop and lose keypresses
if (gKeyReading0 != KEY_INVALID)
return;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
// A pending RF log packet may be the only thing to send: skip the
// frame section when it carries nothing. Without the RF log stream
// the early return above already guarantees this condition.
if (deltaLen != 0 || stateChanged)
#endif
{
// ==== Send version marker and state flags ====
// 0xF0 keeps a resync-safe marker before the standard AA 55 header.
uint8_t versionMarker = K5VIEWER_MARKER_BASE | stateFlags;
K5VIEWER_Send(&versionMarker, 1);
// ==== Send header ====
uint8_t header[5] = {
0xAA, 0x55, K5VIEWER_TYPE_DIFF,
(uint8_t)(deltaLen >> 8),
(uint8_t)(deltaLen & 0xFF)
};
K5VIEWER_Send(header, 5);
// ==== SECOND PASS: Send only changed chunks ====
for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) {
if (!(changedBitmap[chunkIdx >> 3] & (1 << (chunkIdx & 7))))
continue;
chunk[0] = chunkIdx;
K5VIEWER_Chunk(chunkIdx, &chunk[1]);
K5VIEWER_Send(chunk, 9);
// Update the fingerprint only once the chunk is actually sent,
// so chunks skipped by an early return stay marked as changed.
// Hashing the recomputed payload also keeps the fingerprint in
// sync if the display buffer changed between the two passes.
previousHash[chunkIdx] = K5VIEWER_Hash(&chunk[1]);
}
uint8_t end = 0x0A;
K5VIEWER_Send(&end, 1);
}
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
if (rfLogPending)
K5VIEWER_SendRfLog();
if (rfLogHistoryPending)
K5VIEWER_SendRfLogHistory();
#endif
previousStateFlags = stateFlags;
wasConnected = true;
}
+6 -5
View File
@@ -14,10 +14,11 @@
* limitations under the License.
*/
#ifndef SCREENSHOT_H
#define SCREENSHOT_H
#ifndef K5VIEWER_H
#define K5VIEWER_H
void SCREENSHOT_Update(bool force);
void SCREENSHOT_ParseInput(void);
void K5VIEWER_Update(bool force);
void K5VIEWER_ParseInput(void);
bool K5VIEWER_HasPendingStateChange(void);
#endif
#endif
+11 -5
View File
@@ -24,6 +24,9 @@
#include "audio.h"
#include "board.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#include "misc.h"
#include "radio.h"
#include "settings.h"
@@ -99,6 +102,10 @@ void Main(void)
SETTINGS_InitEEPROM();
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
RXTX_LOG_Init();
#endif
#ifdef ENABLE_FEAT_F4HWN
gDW = gEeprom.DUAL_WATCH;
gCB = gEeprom.CROSS_BAND_RX_TX;
@@ -157,11 +164,7 @@ void Main(void)
#ifdef ENABLE_FEAT_F4HWN
gEeprom.KEY_LOCK = 0;
SETTINGS_SaveSettings();
gMenuCursor = MENU_ITEMS;
#ifdef ENABLE_NOAA
gMenuCursor += 1; // move to hidden section, fix me if change... !!!
#endif
gMenuCursor = UI_MENU_GetMenuIdx(FIRST_HIDDEN_MENU_ITEM);
gSubMenuSelection = gSetting_F_LOCK;
#endif
}
@@ -249,6 +252,9 @@ void Main(void)
BOOT_ProcessMode(BootMode);
if (gEeprom.AUTO_KEYPAD_LOCK && !gEeprom.KEY_LOCK)
gKeyLockCountdown = gEeprom.AUTO_KEYPAD_LOCK * 30; // 15 seconds step
// GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0);
gUpdateStatus = true;
+16 -9
View File
@@ -116,6 +116,10 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
bool gWakeUp = false;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
bool gSetting_set_scn = 1;
#endif
#ifdef ENABLE_FEAT_F4HWN
uint8_t gSetting_set_pwr = 1;
bool gSetting_set_ptt = 0;
@@ -123,7 +127,7 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
uint8_t gSetting_set_ctr = 10;
bool gSetting_set_inv = false;
uint8_t gSetting_set_eot = 0;
bool gSetting_set_lck = false;
uint8_t gSetting_set_lck = SET_LCK_KEYS;
bool gSetting_set_met = 0;
bool gSetting_set_gui = 0;
#ifdef ENABLE_FEAT_F4HWN_AUDIO
@@ -133,6 +137,9 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
bool gSetting_set_nfm = 0;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
uint8_t gSetting_set_sav = SET_SAV_OFF;
#endif
bool gSetting_set_tmr = 0;
bool gSetting_set_ptt_session;
#ifdef ENABLE_FEAT_F4HWN_DEBUG
@@ -203,9 +210,9 @@ volatile bool gTxTimeoutReached;
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
volatile uint16_t gRxTimerCountdown_500ms;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
volatile uint8_t gUART_LockScreenshot = 0; // lock screenshot if Chirp is used
bool gUSB_ScreenshotEnabled = false;
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
volatile uint8_t gUART_LockK5Viewer = 0; // lock the K5Viewer stream if Chirp is used
bool gUSB_K5ViewerEnabled = false;
#endif
#endif
@@ -639,14 +646,14 @@ void MR_PrintCacheStats(void)
#endif
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
bool SCREENSHOT_IsLocked(void)
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
bool K5VIEWER_IsLocked(void)
{
if (gUART_LockScreenshot > 0) {
gUART_LockScreenshot--;
if (gUART_LockK5Viewer > 0) {
gUART_LockK5Viewer--;
return true;
}
return false;
}
#endif
#endif
+36 -8
View File
@@ -39,8 +39,6 @@
#define FM_CHANNELS_MAX 48
#define MR_CHANNELS_MAX 1024
#define MR_CHANNELS_LIST 24
#define MENU_ITEMS 69
// CACHE-BASED OPTIMIZATION: Only keep active channels in RAM
// Full array stays in EEPROM, cache holds ~10 most-used channels
#define MR_CHANNELS_CACHE_SIZE 10
@@ -178,14 +176,41 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
extern bool gWakeUp;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
extern bool gSetting_set_scn;
#endif
#ifdef ENABLE_FEAT_F4HWN
// Keypad lock scope. Values are BOTH a bitmask (ACTIONS/PTT bits tested
// individually) AND a contiguous 0..3 menu index: the order must stay
// aligned with gSubMenu_SET_LCK[] and any new entry must keep the range
// contiguous so that SET_LCK_LEN remains valid as menu bound and EEPROM
// range check.
enum SET_LCK_t {
SET_LCK_KEYS = 0u,
SET_LCK_ACTIONS = 1u,
SET_LCK_PTT = 2u,
SET_LCK_ACTIONS_PTT = SET_LCK_ACTIONS | SET_LCK_PTT,
SET_LCK_LEN
};
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
enum SET_SAV_t {
SET_SAV_OFF,
SET_SAV_LOGO,
SET_SAV_LOGO_PLUS,
SET_SAV_MATRIX,
SET_SAV_LEN
};
#endif
extern uint8_t gSetting_set_pwr;
extern bool gSetting_set_ptt;
extern uint8_t gSetting_set_tot;
extern uint8_t gSetting_set_ctr;
extern bool gSetting_set_inv;
extern uint8_t gSetting_set_eot;
extern bool gSetting_set_lck;
extern uint8_t gSetting_set_lck;
extern bool gSetting_set_met;
extern bool gSetting_set_gui;
#ifdef ENABLE_FEAT_F4HWN_AUDIO
@@ -195,6 +220,9 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
extern bool gSetting_set_nfm;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
extern uint8_t gSetting_set_sav;
#endif
extern bool gSetting_set_tmr;
extern bool gSetting_set_ptt_session;
#ifdef ENABLE_FEAT_F4HWN_DEBUG
@@ -321,11 +349,11 @@ extern volatile bool gTxTimeoutReached;
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
extern volatile uint16_t gRxTimerCountdown_500ms;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
extern volatile uint8_t gUART_LockScreenshot; // lock screenshot if Chirp is used
extern bool gUSB_ScreenshotEnabled;
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
extern volatile uint8_t gUART_LockK5Viewer; // lock the K5Viewer stream if Chirp is used
extern bool gUSB_K5ViewerEnabled;
bool SCREENSHOT_IsLocked(void);
bool K5VIEWER_IsLocked(void);
#endif
#endif
@@ -472,4 +500,4 @@ void FUNCTION_NOP();
static inline bool SerialConfigInProgress() { return gSerialConfigCountDown_500ms != 0; }
#endif
#endif
+162 -232
View File
@@ -19,6 +19,9 @@
#include "am_fix.h"
#include "app/dtmf.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#ifdef ENABLE_FMRADIO
#include "app/fm.h"
#endif
@@ -70,64 +73,45 @@ const char gModulationStr[MODULATION_UKNOWN][4] = {
// Audio impression: fuller bass, more direct sound, while still keeping the 3 kHz top-end limit.
static void AUDIO_ApplyFMProfile(uint8_t profile)
{
switch (profile)
{
default:
case 0: // FLAT
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x3200);
break;
{ // | 0x54 || 0x55 |
static const uint16_t fm_profiles[][2] = {
{0x9009, 0x3200}, // 0: FLAT
{0x9009, 0x33A9}, // 1: CLEAN
{0x9009, 0x3600}, // 2: MID
{0x8546, 0x3AF0}, // 3: BOOST
{0x8566, 0x3D00} // 4: MAX
};
case 1: // CLEAN
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x33A9);
break;
if (profile >= ARRAY_SIZE(fm_profiles))
profile = 0;
case 2: // MID
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x3600);
break;
case 3: // BOOST
BK4819_WriteRegister(0x54, 0x8546);
BK4819_WriteRegister(0x55, 0x3AF0);
break;
case 4: // MAX
BK4819_WriteRegister(0x54, 0x8566);
BK4819_WriteRegister(0x55, 0x3D00);
break;
}
BK4819_WriteRegister(0x54, fm_profiles[profile][0]);
BK4819_WriteRegister(0x55, fm_profiles[profile][1]);
}
static void AUDIO_ApplyAMProfile(uint8_t profile)
{
switch (profile)
{
default:
case 0: // SHARP (ALPHA test profile) - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), low IF gain (REG55 bits[11:8]=1, ref=169)
// Selective and crisp, best adjacent channel rejection, may sound harsh on strong signals
BK4819_WriteRegister(0x2b, 0x0300);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31A9);
break;
case 1: // STOCK - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), moderate IF gain (REG55 bits[11:8]=4, ref=180)
// Selective filter with balanced gain, punchy and detailed, good compromise between rejection and sensitivity
BK4819_WriteRegister(0x2b, 0x0500);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31A9);
break;
case 2: // OPEN (BRAVO test profile) - Medium-wide IF filter (REG54 bits[14:8]=8, bits[7:0]=70), high IF gain (REG55 bits[11:8]=8, ref=192)
// Wide and pleasant, better sensitivity on weak signals, may struggle with adjacent channel interference
BK4819_WriteRegister(0x2b, 0x0300);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x8846);
BK4819_WriteRegister(0x55, 0x38C0);
break;
}
{ // | 0x2b || 0x2f || 0x54 || 0x55 |
static const uint16_t am_profiles[][4] = {
// SHARP (ALPHA test profile) - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), low IF gain (REG55 bits[11:8]=1, ref=169)
// Selective and crisp, best adjacent channel rejection, may sound harsh on strong signals
{0x0300, 0x9990, 0x9009, 0x31A9},
// STOCK - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), moderate IF gain (REG55 bits[11:8]=4, ref=180)
// Selective filter with balanced gain, punchy and detailed, good compromise between rejection and sensitivity
{0x0500, 0x9990, 0x9009, 0x31A9},
// OPEN (BRAVO test profile) - Medium-wide IF filter (REG54 bits[14:8]=8, bits[7:0]=70), high IF gain (REG55 bits[11:8]=8, ref=192)
// Wide and pleasant, better sensitivity on weak signals, may struggle with adjacent channel interference
{0x0300, 0x9990, 0x8846, 0x38C0}
};
if (profile >= ARRAY_SIZE(am_profiles))
profile = 0;
BK4819_WriteRegister(0x2b, am_profiles[profile][0]);
BK4819_WriteRegister(0x2f, am_profiles[profile][1]);
BK4819_WriteRegister(0x54, am_profiles[profile][2]);
BK4819_WriteRegister(0x55, am_profiles[profile][3]);
}
static void AUDIO_ApplyUSBProfile(void)
@@ -252,6 +236,24 @@ void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_
RADIO_ConfigureSquelchAndOutputPower(pInfo);
}
void RADIO_ValidateAndSetCode(FREQ_Config_t *pFreq_Config, uint8_t tmp) {
switch (pFreq_Config->CodeType) {
default:
case CODE_TYPE_OFF:
pFreq_Config->CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > ((pFreq_Config->CodeType == CODE_TYPE_CONTINUOUS_TONE ? ARRAY_SIZE(CTCSS_Options) : ARRAY_SIZE(DCS_Options)) - 1))
tmp = 0;
break;
}
pFreq_Config->Code = tmp;
}
void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure)
{
VFO_Info_t *pVfo = &gEeprom.VfoInfo[VFO];
@@ -325,7 +327,7 @@ void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure
}
pVfo->Band = band;
pVfo->SCANLIST_PARTICIPATION = bParticipation;
pVfo->SCANLIST_PARTICIPATION = bParticipation;
pVfo->CHANNEL_SAVE = channel;
uint32_t base;
@@ -370,49 +372,8 @@ void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure
pVfo->freq_config_RX.CodeType = (data[2] >> 0) & 0x0F;
pVfo->freq_config_TX.CodeType = (data[2] >> 4) & 0x0F;
tmp = data[0];
switch (pVfo->freq_config_RX.CodeType)
{
default:
case CODE_TYPE_OFF:
pVfo->freq_config_RX.CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
tmp = 0;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
tmp = 0;
break;
}
pVfo->freq_config_RX.Code = tmp;
tmp = data[1];
switch (pVfo->freq_config_TX.CodeType)
{
default:
case CODE_TYPE_OFF:
pVfo->freq_config_TX.CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
tmp = 0;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
tmp = 0;
break;
}
pVfo->freq_config_TX.Code = tmp;
RADIO_ValidateAndSetCode(&pVfo->freq_config_RX, data[0]);
RADIO_ValidateAndSetCode(&pVfo->freq_config_TX, data[1]);
if (data[4] == 0xFF)
{
@@ -552,17 +513,20 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
}
else
{ // squelch >= 1
Base += gEeprom.SQUELCH_LEVEL; // my eeprom squelch-1
// VHF UHF
PY25Q16_ReadBuffer(Base + 0x00, &pInfo->SquelchOpenRSSIThresh, 1); // 50 10
PY25Q16_ReadBuffer(Base + 0x10, &pInfo->SquelchCloseRSSIThresh, 1); // 40 5
PY25Q16_ReadBuffer(Base + 0x20, &pInfo->SquelchOpenNoiseThresh, 1); // 65 90
PY25Q16_ReadBuffer(Base + 0x30, &pInfo->SquelchCloseNoiseThresh, 1); // 70 100
PY25Q16_ReadBuffer(Base + 0x40, &pInfo->SquelchCloseGlitchThresh, 1); // 90 90
PY25Q16_ReadBuffer(Base + 0x50, &pInfo->SquelchOpenGlitchThresh, 1); // 100 100
Base += gEeprom.SQUELCH_LEVEL; // my eeprom squelch-1
// VHF UHF
uint8_t *sq_ptrs[6] = {
&pInfo->SquelchOpenRSSIThresh, // 50 10
&pInfo->SquelchCloseRSSIThresh, // 40 5
&pInfo->SquelchOpenNoiseThresh, // 65 90
&pInfo->SquelchCloseNoiseThresh, // 70 100
&pInfo->SquelchCloseGlitchThresh, // 90 90
&pInfo->SquelchOpenGlitchThresh // 100 100
};
for(uint8_t i = 0; i < 6; i++) {
PY25Q16_ReadBuffer(Base + (i * 0x10), sq_ptrs[i], 1);
}
uint16_t noise_open = pInfo->SquelchOpenNoiseThresh;
uint16_t noise_close = pInfo->SquelchCloseNoiseThresh;
@@ -586,14 +550,14 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
if (glitch_close == glitch_open && glitch_close <= 253)
glitch_close += 2;
pInfo->SquelchOpenRSSIThresh = (rssi_open > 255) ? 255 : rssi_open;
pInfo->SquelchCloseRSSIThresh = (rssi_close > 255) ? 255 : rssi_close;
pInfo->SquelchOpenGlitchThresh = (glitch_open > 255) ? 255 : glitch_open;
pInfo->SquelchCloseGlitchThresh = (glitch_close > 255) ? 255 : glitch_close;
pInfo->SquelchOpenRSSIThresh = MIN(rssi_open, 255);
pInfo->SquelchCloseRSSIThresh = MIN(rssi_close, 255);
pInfo->SquelchOpenGlitchThresh = MIN(glitch_open, 255);
pInfo->SquelchCloseGlitchThresh = MIN(glitch_close, 255);
#endif
pInfo->SquelchOpenNoiseThresh = (noise_open > 127) ? 127 : noise_open;
pInfo->SquelchCloseNoiseThresh = (noise_close > 127) ? 127 : noise_close;
pInfo->SquelchOpenNoiseThresh = MIN(noise_open, 127);
pInfo->SquelchCloseNoiseThresh = MIN(noise_close, 127);
}
// *******************************
@@ -624,30 +588,15 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
uint8_t Op = 0; // Low eeprom calibration data
uint8_t currentPower = pInfo->OUTPUT_POWER;
if(currentPower == OUTPUT_POWER_USER)
{
if(gSetting_set_pwr == 5)
{
Op = 1; // Mid eeprom calibration data
}
else if(gSetting_set_pwr == 6)
{
Op = 2; // High eeprom calibration data
}
if (currentPower == OUTPUT_POWER_USER)
currentPower = gSetting_set_pwr;
}
else
{
if (currentPower == OUTPUT_POWER_MID)
{
Op = 1; // Mid eeprom calibration data
}
else if(currentPower == OUTPUT_POWER_HIGH)
{
Op = 2; // High eeprom calibration data
}
currentPower--;
}
if (currentPower == 5)
Op = 1; // Mid eeprom calibration data
else if (currentPower == 6)
Op = 2; // High eeprom calibration data
PY25Q16_ReadBuffer(0x100D0 + (Band * 16) + (Op * 3), Txp, 3);
@@ -755,18 +704,11 @@ void RADIO_ApplyOffset(VFO_Info_t *pInfo)
{
uint32_t Frequency = pInfo->freq_config_RX.Frequency;
switch (pInfo->TX_OFFSET_FREQUENCY_DIRECTION)
{
case TX_OFFSET_FREQUENCY_DIRECTION_OFF:
break;
case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
Frequency += pInfo->TX_OFFSET_FREQUENCY;
break;
case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
Frequency -= pInfo->TX_OFFSET_FREQUENCY;
break;
}
if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_ADD)
Frequency += pInfo->TX_OFFSET_FREQUENCY;
else if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_SUB)
Frequency -= pInfo->TX_OFFSET_FREQUENCY;
pInfo->freq_config_TX.Frequency = Frequency;
}
@@ -790,6 +732,12 @@ void RADIO_SelectVfos(void)
RADIO_SelectCurrentVfo();
}
BK4819_FilterBandwidth_t RADIO_GetAMFilterBandwidth(const VFO_Info_t *pVfo)
{
// On BK4829, AM "wide" intentionally reuses the wider RF filter preset.
return (pVfo->CHANNEL_BANDWIDTH == BANDWIDTH_WIDE) ? BK4819_FILTER_BW_WIDE : BK4819_FILTER_BW_AM;
}
void RADIO_SetupRegisters(bool switchToForeground)
{
BK4819_FilterBandwidth_t Bandwidth = gRxVfo->CHANNEL_BANDWIDTH;
@@ -808,7 +756,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
if (gRxVfo->Modulation == MODULATION_AM)
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
else
{
switch (Bandwidth)
@@ -860,6 +808,9 @@ void RADIO_SetupRegisters(bool switchToForeground)
#endif
BK4819_SetFrequency(Frequency);
// Keep the demodulator in sync when retuning without entering RX audio.
RADIO_SetModulation(gRxVfo->Modulation);
BK4819_SetupSquelch(
gRxVfo->SquelchOpenRSSIThresh, gRxVfo->SquelchCloseRSSIThresh,
gRxVfo->SquelchOpenNoiseThresh, gRxVfo->SquelchCloseNoiseThresh,
@@ -872,12 +823,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
// AF RX Gain and DAC
//BK4819_WriteRegister(BK4819_REG_48, 0xB3A8); // 1011 00 111010 1000
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
uint16_t InterruptMask = BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
@@ -897,7 +843,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
BK4819_SetCTCSSFrequency(SQL_TONE);
BK4819_SetTailDetection(SQL_TONE); // Default 550 = QS's 55Hz tone method
InterruptMask = BK4819_REG_3F_CxCSS_TAIL | BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL;
break;
case CODE_TYPE_CONTINUOUS_TONE:
@@ -909,24 +855,17 @@ void RADIO_SetupRegisters(bool switchToForeground)
// BK4819_SetTailDetection(CTCSS_Options[Code]);
//#endif
InterruptMask = 0
| BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST
| BK4819_REG_3F_SQUELCH_FOUND
| BK4819_REG_3F_SQUELCH_LOST;
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(CodeType, Code));
InterruptMask = 0
| BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CDCSS_FOUND
| BK4819_REG_3F_CDCSS_LOST
| BK4819_REG_3F_SQUELCH_FOUND
| BK4819_REG_3F_SQUELCH_LOST;
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CDCSS_FOUND
| BK4819_REG_3F_CDCSS_LOST;
break;
}
@@ -944,11 +883,8 @@ void RADIO_SetupRegisters(bool switchToForeground)
else
{
BK4819_SetCTCSSFrequency(2625);
InterruptMask = 0
| BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST
| BK4819_REG_3F_SQUELCH_FOUND
| BK4819_REG_3F_SQUELCH_LOST;
InterruptMask |= BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST;
}
#endif
@@ -1107,26 +1043,25 @@ void RADIO_SetTxParameters(void)
void RADIO_SetModulation(ModulationMode_t modulation)
{
#ifdef ENABLE_BYP_RAW_DEMODULATORS
// BYP on BK4829 uses full audio bypass profile.
if (modulation == MODULATION_BYP) {
BK4819_EnterBypass();
if (modulation == MODULATION_BYP || modulation == MODULATION_RAW) {
uint16_t reg_3d_val = 0x0000;
if (modulation == MODULATION_BYP) {
// BYP on BK4829 uses full audio bypass profile.
BK4819_EnterBypass();
reg_3d_val = 0x2AAB;
} else {
// RAW on BK4829 uses RX-only filter bypass profile.
BK4819_EnterRaw();
// reg_3d_val = 0x0000;
}
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, 0x2AAB);
BK4819_WriteRegister(BK4819_REG_3D, reg_3d_val);
RADIO_SetupAGC(false, false);
return;
}
// RAW on BK4829 uses RX-only filter bypass profile.
if (modulation == MODULATION_RAW) {
BK4819_EnterRaw();
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, 0x0000);
RADIO_SetupAGC(false, false);
return;
}
#endif
#ifdef ENABLE_BYP_RAW_DEMODULATORS
// Ensure we always leave bypass / raw mode before applying normal modulation settings.
BK4819_ExitBypass();
#endif
@@ -1156,11 +1091,12 @@ void RADIO_SetModulation(ModulationMode_t modulation)
// So for now we just keep it as is to maintain compatibility.
//
uint16_t uVar1 = BK4819_ReadRegister(0x31);
switch (modulation)
{
case MODULATION_AM:
{
uint16_t uVar1 = BK4819_ReadRegister(0x31);
BK4819_WriteRegister(0x31, uVar1 | 1); // AM Demodulation Enable
BK4819_WriteRegister(0x42, 0x6f5c);
BK4819_WriteRegister(0x2a, 0x7434);
@@ -1172,32 +1108,14 @@ void RADIO_SetModulation(ModulationMode_t modulation)
BK4819_WriteRegister(0x55, 0x31a9);
#endif
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
break;
}
case MODULATION_USB:
{
uint16_t uVar1 = BK4819_ReadRegister(0x31);
BK4819_WriteRegister(0x31, uVar1 & 0xfffe); // AM Demodulation Disable
BK4819_WriteRegister(0x42, 0x6b5a);
BK4819_WriteRegister(0x2a, 0x7400);
BK4819_WriteRegister(0x2b, 0x0000);
BK4819_WriteRegister(0x2f, 0x9890);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
AUDIO_ApplyUSBProfile();
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
#endif
break;
}
case MODULATION_FM:
default:
{
uint16_t uVar1 = BK4819_ReadRegister(0x31);
BK4819_WriteRegister(0x31, uVar1 & 0xfffe); // AM Demodulation Disable
BK4819_WriteRegister(0x42, 0x6b5a);
BK4819_WriteRegister(0x2a, 0x7400);
@@ -1205,7 +1123,10 @@ void RADIO_SetModulation(ModulationMode_t modulation)
BK4819_WriteRegister(0x2f, 0x9890);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
AUDIO_ApplyFMProfile(gSetting_set_audio_fm);
if (modulation == MODULATION_USB)
AUDIO_ApplyUSBProfile();
else
AUDIO_ApplyFMProfile(gSetting_set_audio_fm);
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
@@ -1284,16 +1205,12 @@ void RADIO_PrepareTX(void)
RADIO_SelectCurrentVfo();
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
#ifdef ENABLE_FEAT_F4HWN
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0 && gCurrentVfo->TX_LOCK == true
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
&& gAlarmState != ALARM_STATE_SITE_ALARM
#endif
#else
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
&& gAlarmState != ALARM_STATE_SITE_ALARM
#endif
&& gCurrentVfo->TX_LOCK == true
#endif
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
&& gAlarmState != ALARM_STATE_SITE_ALARM
#endif
){
// TX frequency not allowed
@@ -1358,6 +1275,10 @@ void RADIO_PrepareTX(void)
FUNCTION_Select(FUNCTION_TRANSMIT);
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
RXTX_LOG_BeginTx(gCurrentVfo);
#endif
gTxTimerCountdown_500ms = 0; // no timeout
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
@@ -1397,27 +1318,37 @@ void RADIO_PrepareTX(void)
void RADIO_SendCssTail(void)
{
switch (gCurrentVfo->pTX->CodeType) {
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_PlayCDCSSTail();
break;
default:
BK4819_PlayCTCSSTail();
break;
}
if (gEeprom.TAIL_TONE_ELIMINATION) {
switch (gCurrentVfo->pTX->CodeType) {
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_PlayCDCSSTail();
break;
default:
BK4819_PlayCTCSSTail();
break;
}
SYSTEM_DelayMs(200);
SYSTEM_DelayMs(200);
}
}
void RADIO_SendEndOfTransmission(void)
{
BK4819_PlayRoger();
BK4819_FilterBandwidth_t Bandwidth = gCurrentVfo->CHANNEL_BANDWIDTH;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
{
Bandwidth = BK4819_FILTER_BW_NARROWER;
}
#endif
BK4819_PlayRoger(Bandwidth);
DTMF_SendEndOfTransmission();
// send the CTCSS/DCS tail tone - allows the receivers to mute the usual FM squelch tail/crash
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
RADIO_SetupRegisters(false);
}
@@ -1427,7 +1358,6 @@ void RADIO_PrepareCssTX(void)
SYSTEM_DelayMs(200);
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
RADIO_SetupRegisters(true);
}
+6
View File
@@ -21,6 +21,7 @@
#include <stdint.h>
#include "dcs.h"
#include "driver/bk4819.h"
#include "frequencies.h"
enum {
@@ -148,6 +149,9 @@ extern DCS_CodeType_t gCurrentCodeType;
extern VfoState_t VfoState[2];
// Human-readable label per VfoState_t (defined in ui/main.c), e.g. "TX DISABLE".
extern const char *const VfoStateStr[];
bool RADIO_CheckValidList(uint8_t scanList);
void RADIO_NextValidList(int8_t direction);
bool RADIO_CheckValidChannel(uint16_t channel, bool checkScanList, uint8_t scanList);
@@ -155,12 +159,14 @@ uint16_t RADIO_FindNextChannel(uint16_t ChNum, int8_t Direction, bool bCheckScan
void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_t Frequency);
void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure);
void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo);
void RADIO_ValidateAndSetCode(FREQ_Config_t *pFreq_Config, uint8_t tmp);
void RADIO_ApplyOffset(VFO_Info_t *pInfo);
void RADIO_SelectVfos(void);
void RADIO_SetupRegisters(bool switchToForeground);
#ifdef ENABLE_NOAA
void RADIO_ConfigureNOAA(void);
#endif
BK4819_FilterBandwidth_t RADIO_GetAMFilterBandwidth(const VFO_Info_t *pVfo);
void RADIO_SetTxParameters(void);
void RADIO_SetupAGC(bool listeningAM, bool disable);
void RADIO_SetModulation(ModulationMode_t modulation);
+32
View File
@@ -98,6 +98,38 @@ void SysTick_Handler(void)
if (gCurrentFunction != FUNCTION_MONITOR && gCurrentFunction != FUNCTION_TRANSMIT)
DECREMENT_AND_TRIGGER(gScanPauseDelayIn_10ms, gScheduleScanListen);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// Scan stall watchdog: if the scan-resume countdown has expired
// (gScanPauseDelayIn_10ms == 0) but no resume was actually scheduled
// (gScheduleScanListen == false) and no real reception is happening
// (squelch closed -> green LED off, not in RX/TX/MONITOR), the scan
// loop is stuck. Force a resume after this many 10ms ticks. The
// gScanPauseDelayIn_10ms == 0 guard ensures we never override the
// user-configured ScnRev (SCAN_RESUME_MODE) pause: while that pause
// is decrementing, the watchdog stays asleep.
#define SCAN_FAST_STALL_WATCHDOG_10ms 25 // 250 ms
static uint16_t scanFastStallCounter;
if (gSetting_set_scn
&& gScanStateDir != SCAN_OFF
&& gScanPauseDelayIn_10ms == 0
&& !gScheduleScanListen
&& !g_SquelchLost
&& gCurrentFunction != FUNCTION_RECEIVE
&& gCurrentFunction != FUNCTION_TRANSMIT
&& gCurrentFunction != FUNCTION_MONITOR)
{
if (++scanFastStallCounter >= SCAN_FAST_STALL_WATCHDOG_10ms) {
scanFastStallCounter = 0;
gScheduleScanListen = true;
}
}
else
{
scanFastStallCounter = 0;
}
#endif
DECREMENT_AND_TRIGGER(gTailNoteEliminationCountdown_10ms, gFlagTailNoteEliminationComplete);
#ifdef ENABLE_VOICE
-174
View File
@@ -1,174 +0,0 @@
/* Copyright 2024 Armel F4HWN
* https://github.com/armel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "debugging.h"
#include "driver/st7565.h"
#include "screenshot.h"
#include "misc.h"
#include "driver/vcp.h"
#include "driver/keyboard.h"
// SRAM optimization: minimize static allocations
// - previousFrame: 1024 bytes (REQUIRED - need to compare for delta)
// - No currentFrame or deltaFrame static buffers
static uint8_t previousFrame[1024] = {0};
static uint8_t forcedBlock = 0;
static uint8_t keepAlive = 3;
void SCREENSHOT_ParseInput(void)
{
if (SCREENSHOT_IsLocked())
return;
if (UART_IsCableConnected()) {
keepAlive = 15;
gUSB_ScreenshotEnabled = false;
}
else if (VCP_ScreenshotPing()) {
keepAlive = 15;
gUSB_ScreenshotEnabled = true;
}
}
static void SCREENSHOT_Send(const uint8_t *buf, uint16_t len)
{
if (gUSB_ScreenshotEnabled) {
cdc_acm_data_send_with_dtr(buf, len);
} else {
UART_Send(buf, len);
}
}
void SCREENSHOT_Line(uint8_t *src, uint8_t *dest, uint16_t *idx) {
for (uint8_t b = 0; b < 8; b++) {
for (uint8_t i = 0; i < 128; i += 8) {
uint8_t acc = 0;
for (uint8_t k = 0; k < 8; k++) {
if (src[i + k] & (1 << b)) acc |= (1 << k);
}
dest[(*idx)++] = gSetting_set_inv ? ~acc : acc;
}
}
}
void SCREENSHOT_Update(bool force)
{
// Build frame in a temporary stack buffer
// This is 1024 bytes but it's temporary and gets freed after the function
uint8_t frameBuffer[1024];
uint16_t index = 0;
uint8_t acc = 0;
uint8_t bitCount = 0;
static bool wasConnected = false;
if (SCREENSHOT_IsLocked())
return;
if (keepAlive > 0) {
if (--keepAlive == 0) {
// Connection just lost → reset state for next reconnection
wasConnected = false;
return;
}
} else {
return;
}
// Connection is alive — detect reconnection and force full frame
if (!wasConnected) {
force = true;
wasConnected = true;
}
// ==== BUILD FRAME ONCE ====
// Status line: 8 bit layers × 128 columns
SCREENSHOT_Line(gStatusLine, frameBuffer, &index);
// Frame buffer: 7 lines × 8 bit layers × 128 columns
for (uint8_t l = 0; l < 7; l++) {
SCREENSHOT_Line(gFrameBuffer[l], frameBuffer, &index);
}
if (bitCount > 0)
frameBuffer[index++] = acc;
if (index != 1024)
return;
// ==== FIRST PASS: Count changed chunks ====
uint16_t deltaLen = 0;
uint8_t changedChunks[128]; // List of changed chunk indices
uint8_t changedCount = 0;
for (uint8_t chunk = 0; chunk < 128; chunk++) {
uint8_t *cur = &frameBuffer[chunk * 8];
uint8_t *prev = &previousFrame[chunk * 8];
bool changed = memcmp(cur, prev, 8) != 0;
bool isForced = (chunk == forcedBlock);
bool fullUpdate = force;
if (changed || isForced || fullUpdate) {
changedChunks[changedCount++] = chunk;
deltaLen += 9;
}
}
forcedBlock = (forcedBlock + 1) % 128;
if (deltaLen == 0)
return;
// Skip transmission if a key is currently pressed
// UART_Send is blocking - would freeze the main loop and lose keypresses
if (gKeyReading0 != KEY_INVALID)
return;
// ==== Send version marker (for backward compatibility detection) ====
// New format: sends 0xFF before header
// Old format: doesn't exist, so viewers can differentiate
uint8_t versionMarker = 0xFF;
SCREENSHOT_Send(&versionMarker, 1);
// ==== Send header ====
uint8_t header[5] = {
0xAA, 0x55, 0x02,
(uint8_t)(deltaLen >> 8),
(uint8_t)(deltaLen & 0xFF)
};
SCREENSHOT_Send(header, 5);
// ==== SECOND PASS: Send only changed chunks ====
uint8_t chunk[9];
for (uint8_t i = 0; i < changedCount; i++) {
uint8_t chunkIdx = changedChunks[i];
uint8_t *cur = &frameBuffer[chunkIdx * 8];
uint8_t *prev = &previousFrame[chunkIdx * 8];
chunk[0] = chunkIdx;
memcpy(&chunk[1], cur, 8);
SCREENSHOT_Send(chunk, 9);
// Update previousFrame for next comparison
memcpy(prev, cur, 8);
}
uint8_t end = 0x0A;
SCREENSHOT_Send(&end, 1);
}
+192 -94
View File
@@ -30,6 +30,23 @@
EEPROM_Config_t gEeprom = { 0 };
// Load a DTMF code from EEPROM, falling back to default_val if invalid.
static void SETTINGS_LoadEepromDtmf(uint32_t addr, char *dest, size_t size, const char *default_val)
{
uint8_t buf[16];
if (size > sizeof(buf))
size = sizeof(buf);
PY25Q16_ReadBuffer(addr, buf, size);
if (DTMF_ValidateCodes((char *)buf, size)) {
memcpy(dest, buf, size);
} else {
strcpy(dest, default_val);
}
}
void SETTINGS_InitEEPROM(void)
{
uint8_t Data[16] = {0};
@@ -218,9 +235,12 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
gEeprom.FM_Band = fmCfg.band;
//gEeprom.FM_Space = fmCfg.space;
uint16_t freqLoLimit = BK1080_GetFreqLoLimit(gEeprom.FM_Band);
gEeprom.FM_SelectedFrequency =
(fmCfg.selFreq >= BK1080_GetFreqLoLimit(gEeprom.FM_Band) && fmCfg.selFreq <= BK1080_GetFreqHiLimit(gEeprom.FM_Band)) ?
fmCfg.selFreq : BK1080_GetFreqLoLimit(gEeprom.FM_Band);
(fmCfg.selFreq >= freqLoLimit && fmCfg.selFreq <= BK1080_GetFreqHiLimit(gEeprom.FM_Band)) ?
fmCfg.selFreq : freqLoLimit;
gEeprom.FM_SelectedChannel = fmCfg.selChn;
gEeprom.FM_IsMrMode = fmCfg.isMrMode;
@@ -294,51 +314,25 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
PY25Q16_ReadBuffer(0x00A0A8 + 0x48, Data, 8);
gEeprom.DTMF_CODE_PERSIST_TIME = (Data[0] < 101) ? Data[0] * 10 : 100;
gEeprom.DTMF_CODE_INTERVAL_TIME = (Data[1] < 101) ? Data[1] * 10 : 100;
#ifdef ENABLE_DTMF_CALLING
gEeprom.PERMIT_REMOTE_KILL = (Data[2] < 2) ? Data[2] : true;
// 0EE0..0EE7
PY25Q16_ReadBuffer(0x00A0F8, Data, sizeof(gEeprom.ANI_DTMF_ID));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.ANI_DTMF_ID))) {
memcpy(gEeprom.ANI_DTMF_ID, Data, sizeof(gEeprom.ANI_DTMF_ID));
} else {
strcpy(gEeprom.ANI_DTMF_ID, "123");
}
SETTINGS_LoadEepromDtmf(0x00A0F8, gEeprom.ANI_DTMF_ID, sizeof(gEeprom.ANI_DTMF_ID), "123");
// 0EE8..0EEF
PY25Q16_ReadBuffer(0x00A0F8 + 0x8, Data, sizeof(gEeprom.KILL_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.KILL_CODE))) {
memcpy(gEeprom.KILL_CODE, Data, sizeof(gEeprom.KILL_CODE));
} else {
strcpy(gEeprom.KILL_CODE, "ABCD9");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x08, gEeprom.KILL_CODE, sizeof(gEeprom.KILL_CODE), "ABCD9");
// 0EF0..0EF7
PY25Q16_ReadBuffer(0x00A0F8 + 0x10, Data, sizeof(gEeprom.REVIVE_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.REVIVE_CODE))) {
memcpy(gEeprom.REVIVE_CODE, Data, sizeof(gEeprom.REVIVE_CODE));
} else {
strcpy(gEeprom.REVIVE_CODE, "9DCBA");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x10, gEeprom.REVIVE_CODE, sizeof(gEeprom.REVIVE_CODE), "9DCBA");
#endif
// 0EF8..0F07
PY25Q16_ReadBuffer(0x00A0F8 + 0x18, Data, sizeof(gEeprom.DTMF_UP_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.DTMF_UP_CODE))) {
memcpy(gEeprom.DTMF_UP_CODE, Data, sizeof(gEeprom.DTMF_UP_CODE));
} else {
strcpy(gEeprom.DTMF_UP_CODE, "12345");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x18, gEeprom.DTMF_UP_CODE, sizeof(gEeprom.DTMF_UP_CODE), "12345");
// 0F08..0F17
PY25Q16_ReadBuffer(0x00A0F8 + 0x28, Data, sizeof(gEeprom.DTMF_DOWN_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.DTMF_DOWN_CODE))) {
memcpy(gEeprom.DTMF_DOWN_CODE, Data, sizeof(gEeprom.DTMF_DOWN_CODE));
} else {
strcpy(gEeprom.DTMF_DOWN_CODE, "54321");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x28, gEeprom.DTMF_DOWN_CODE, sizeof(gEeprom.DTMF_DOWN_CODE), "54321");
// 0F18..0F1F
PY25Q16_ReadBuffer(0x00A130, Data, 8);
@@ -460,8 +454,21 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
// 1FF0..0x1FF7
// TODO: address TBD
PY25Q16_ReadBuffer(0x00A158, Data, 8);
const uint8_t set_ptt_scn_sav = Data[7] & 0x0F;
const bool set_ptt_scn_sav_erased = Data[7] == 0xFF;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < (SET_SAV_LEN << 2);
#else
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < 4;
#endif
gSetting_set_pwr = (((Data[7] & 0xF0) >> 4) < 7) ? ((Data[7] & 0xF0) >> 4) : 0;
gSetting_set_ptt = (((Data[7] & 0x0F)) < 2) ? ((Data[7] & 0x0F)) : 0;
gSetting_set_ptt = set_ptt_scn_sav_valid ? (set_ptt_scn_sav & 0x01) : 0;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
gSetting_set_scn = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav & 0x02) == 0) : 0;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
gSetting_set_sav = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav >> 2) & 0x03) : SET_SAV_OFF;
#endif
gSetting_set_tot = (((Data[6] & 0xF0) >> 4) < 4) ? ((Data[6] & 0xF0) >> 4) : 0;
gSetting_set_eot = (((Data[6] & 0x0F)) < 4) ? ((Data[6] & 0x0F)) : 0;
@@ -470,7 +477,6 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
int tmp = ((Data[5] & 0xF0) >> 4);
gSetting_set_inv = (((tmp >> 0) & 0x01) < 2) ? ((tmp >> 0) & 0x01): 0;
gSetting_set_lck = (((tmp >> 1) & 0x01) < 2) ? ((tmp >> 1) & 0x01): 0;
gSetting_set_met = (((tmp >> 2) & 0x01) < 2) ? ((tmp >> 2) & 0x01): 0;
gSetting_set_gui = (((tmp >> 3) & 0x01) < 2) ? ((tmp >> 3) & 0x01): 0;
gSetting_set_ctr = (((Data[5] & 0x0F)) > 00 && ((Data[5] & 0x0F)) < 16) ? ((Data[5] & 0x0F)) : 10;
@@ -485,7 +491,7 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
#else
gSetting_set_inv = 0;
#endif
gSetting_set_lck = (tmp >> 1) & 0x01;
gSetting_set_lck = (Data[2] < SET_LCK_LEN) ? Data[2] : SET_LCK_KEYS;
gSetting_set_met = (tmp >> 2) & 0x01;
gSetting_set_gui = (tmp >> 3) & 0x01;
@@ -501,13 +507,8 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
gSetting_set_off = (Data[4] >> 1) > 120 ? 60 : (Data[4] >> 1);
#endif
// Warning
// Be aware, Data[3] is use by Spectrum
// Warning
// And set special session settings for actions
gSetting_set_ptt_session = gSetting_set_ptt;
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
#endif
}
@@ -589,6 +590,124 @@ uint32_t SETTINGS_FetchChannelFrequency(const uint16_t channel)
return info.frequency;
}
bool SETTINGS_FetchChannelScanInfo(const uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation)
{
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t settings[4];
} __attribute__((packed)) info;
PY25Q16_ReadBuffer(channel * 16, &info, sizeof(info));
if (frequency)
*frequency = info.frequency;
if (modulation)
{
uint8_t mode = info.settings[3] >> 4;
if (mode >= MODULATION_UKNOWN)
mode = MODULATION_FM;
*modulation = (ModulationMode_t)mode;
}
return info.frequency != 0 && info.frequency != 0xFFFFFFFF;
}
bool SETTINGS_FetchChannelScanDisplayInfo(const uint16_t channel, ChannelScanDisplayInfo_t *info)
{
if (info == NULL)
return false;
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t data[8];
} __attribute__((packed)) raw;
PY25Q16_ReadBuffer(channel * 16, &raw, sizeof(raw));
if (raw.frequency == 0 || raw.frequency == 0xFFFFFFFF)
return false;
memset(info, 0, sizeof(*info));
info->rx.Frequency = raw.frequency;
info->tx.Frequency = raw.frequency;
info->offset = (raw.offset >= _1GHz_in_KHz) ? (_1GHz_in_KHz / 100) : raw.offset;
info->rx.CodeType = (raw.data[2] >> 0) & 0x0F;
info->tx.CodeType = (raw.data[2] >> 4) & 0x0F;
RADIO_ValidateAndSetCode(&info->rx, raw.data[0]);
RADIO_ValidateAndSetCode(&info->tx, raw.data[1]);
uint8_t tmp = raw.data[3] & 0x0F;
if (tmp > TX_OFFSET_FREQUENCY_DIRECTION_SUB)
tmp = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
info->txOffsetFrequencyDirection = tmp;
tmp = raw.data[3] >> 4;
if (tmp >= MODULATION_UKNOWN)
tmp = MODULATION_FM;
info->modulation = (ModulationMode_t)tmp;
tmp = raw.data[6];
if (tmp >= STEP_N_ELEM)
tmp = STEP_12_5kHz;
info->stepSetting = (STEP_Setting_t)tmp;
info->stepFrequency = gStepFrequencyTable[tmp];
if (raw.data[4] == 0xFF)
{
info->frequencyReverse = false;
info->channelBandwidth = BANDWIDTH_WIDE;
info->outputPower = OUTPUT_POWER_LOW1;
info->busyChannelLock = false;
info->txLock = true;
}
else
{
const uint8_t d4 = raw.data[4];
info->frequencyReverse = !!((d4 >> 0) & 1u);
info->channelBandwidth = !!((d4 >> 1) & 1u);
info->outputPower = ((d4 >> 2) & 7u);
info->busyChannelLock = !!((d4 >> 5) & 1u);
info->txLock = !!((d4 >> 6) & 1u);
}
switch (info->txOffsetFrequencyDirection)
{
case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
info->tx.Frequency = raw.frequency + info->offset;
break;
case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
info->tx.Frequency = raw.frequency - info->offset;
break;
default:
break;
}
if (raw.data[5] == 0xFF)
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = false;
#endif
info->dtmfPttIdTxMode = PTT_ID_OFF;
}
else
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = (raw.data[5] >> 0) & 1u;
#endif
const uint8_t pttId = (raw.data[5] >> 1) & 7u;
info->dtmfPttIdTxMode = pttId < ARRAY_SIZE(gSubMenu_PTT_ID) ? pttId : PTT_ID_OFF;
}
return true;
}
void SETTINGS_FetchChannelName(char *s, const uint16_t channel)
{
if (s == NULL)
@@ -998,8 +1117,6 @@ void SETTINGS_SaveSettings(void)
if(gSetting_set_inv == 1)
tmp = tmp | (1 << 0);
if (gSetting_set_lck == 1)
tmp = tmp | (1 << 1);
if (gSetting_set_met == 1)
tmp = tmp | (1 << 2);
if (gSetting_set_gui == 1)
@@ -1013,15 +1130,22 @@ void SETTINGS_SaveSettings(void)
#endif
tmp = (gSetting_set_inv << 0) |
(gSetting_set_lck << 1) |
(gSetting_set_met << 2) |
(gSetting_set_gui << 3);
State[2] = gSetting_set_lck;
State[5] = ((tmp << 4) | (gSetting_set_ctr & 0x0F));
State[6] = ((gSetting_set_tot << 4) | (gSetting_set_eot & 0x0F));
State[7] = ((gSetting_set_pwr << 4) | (gSetting_set_ptt & 0x0F));
uint8_t set_ptt_scn_sav = gSetting_set_ptt & 0x01;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (!gSetting_set_scn)
set_ptt_scn_sav |= 0x02;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
set_ptt_scn_sav |= (gSetting_set_sav & 0x03) << 2;
#endif
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
State[7] = ((gSetting_set_pwr << 4) | set_ptt_scn_sav);
PY25Q16_WriteBuffer(0x00A158, SecBuf, 8, false);
#endif
@@ -1086,7 +1210,7 @@ void SETTINGS_SaveChannel(uint16_t Channel, uint8_t VFO, const VFO_Info_t *pVFO,
PY25Q16_WriteBuffer(OffsetVFO, Buf, 0x10, false);
SETTINGS_UpdateChannel(Channel, pVFO, true, true, true);
SETTINGS_UpdateChannel(Channel, pVFO, true);
if (IS_MR_CHANNEL(Channel)) {
#ifndef ENABLE_KEEP_MEM_NAME
@@ -1117,58 +1241,32 @@ void SETTINGS_SaveChannelName(uint16_t channel, const char * name)
PY25Q16_WriteBuffer(0x004000 + offset, buf, 0x10, false);
}
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep, bool check, bool save)
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep)
{
#ifdef ENABLE_NOAA
if (!IS_NOAA_CHANNEL(channel))
if (IS_NOAA_CHANNEL(channel))
return;
#endif
{
ChannelAttributes_t state;
ChannelAttributes_t att = {
.band = 0x7,
.compander = 0,
.unused_1 = 0,
.unused_2 = 0,
.exclude = 0,
.scanlist = 0,
}; // default attributes
// 0x0D60
PY25Q16_ReadBuffer(0x008000 + (channel * 2), &state, 2);
ChannelAttributes_t att = {
.band = 0x7,
.compander = 0,
.unused_1 = 0,
.unused_2 = 0,
.exclude = 0,
.scanlist = 0,
};
if (keep) {
att.band = pVFO->Band;
att.compander = pVFO->Compander;
att.unused_1 = 0;
att.unused_2 = 0;
att.exclude = 0;
att.scanlist = pVFO->SCANLIST_PARTICIPATION;
if (check && state.__val == att.__val)
return; // no change in the attributes
}
state.__val = att.__val;
#ifndef ENABLE_FEAT_F4HWN
save = true;
#endif
if(save)
{
uint16_t buf[MR_CHANNELS_MAX + 24];
PY25Q16_ReadBuffer(0x008000, buf, sizeof(buf));
buf[channel] = state.__val;
PY25Q16_WriteBuffer(0x008000, buf, sizeof(buf), false);
}
MR_SetChannelAttributes(channel, &att);
if (IS_MR_CHANNEL(channel)) { // it's a memory channel
if (!keep) {
// clear/reset the channel name
SETTINGS_SaveChannelName(channel, "");
}
}
if (keep) {
att.band = pVFO->Band;
att.compander = pVFO->Compander;
att.scanlist = pVFO->SCANLIST_PARTICIPATION;
}
MR_SetChannelAttributes(channel, &att);
if (IS_MR_CHANNEL(channel) && !keep)
SETTINGS_SaveChannelName(channel, "");
}
void SETTINGS_WriteBuildOptions(void)
+34 -6
View File
@@ -34,7 +34,10 @@ enum POWER_OnDisplayMode_t {
#endif
POWER_ON_DISPLAY_MODE_MESSAGE,
POWER_ON_DISPLAY_MODE_VOLTAGE,
POWER_ON_DISPLAY_MODE_NONE
#ifdef ENABLE_FEAT_F4HWN_LOGO
POWER_ON_DISPLAY_MODE_LOGO,
#endif
POWER_ON_DISPLAY_MODE_NONE,
};
typedef enum POWER_OnDisplayMode_t POWER_OnDisplayMode_t;
@@ -124,9 +127,14 @@ enum ACTION_OPT_t {
ACTION_OPT_REMOVE_OFFSET,
#endif
#endif
#ifdef ENABLE_REGA
ACTION_OPT_REGA_ALARM,
ACTION_OPT_REGA_TEST,
#ifdef ENABLE_FEAT_F4HWN_BEAM
ACTION_OPT_BEAM,
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
ACTION_OPT_RXTX_LOG,
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
ACTION_OPT_FOXHUNT,
#endif
ACTION_OPT_LEN
};
@@ -194,7 +202,6 @@ typedef struct {
uint8_t TX_TIMEOUT_TIMER;
bool KEY_LOCK;
#ifdef ENABLE_FEAT_F4HWN
bool KEY_LOCK_PTT;
bool SET_NAV;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
@@ -307,9 +314,30 @@ typedef struct {
extern EEPROM_Config_t gEeprom;
typedef struct {
FREQ_Config_t rx;
FREQ_Config_t tx;
uint32_t offset;
uint16_t stepFrequency;
STEP_Setting_t stepSetting;
ModulationMode_t modulation;
uint8_t txOffsetFrequencyDirection;
uint8_t outputPower;
bool frequencyReverse;
uint8_t channelBandwidth;
uint8_t busyChannelLock;
uint8_t txLock;
#ifdef ENABLE_DTMF_CALLING
uint8_t dtmfDecodingEnable;
#endif
PTT_ID_t dtmfPttIdTxMode;
} ChannelScanDisplayInfo_t;
void SETTINGS_InitEEPROM(void);
void SETTINGS_LoadCalibration(void);
uint32_t SETTINGS_FetchChannelFrequency(const uint16_t channel);
bool SETTINGS_FetchChannelScanInfo(const uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation);
bool SETTINGS_FetchChannelScanDisplayInfo(const uint16_t channel, ChannelScanDisplayInfo_t *info);
void SETTINGS_FetchChannelName(char *s, const uint16_t channel);
void SETTINGS_FactoryReset(bool bIsAll);
#ifdef ENABLE_FMRADIO
@@ -321,7 +349,7 @@ void SETTINGS_SaveSettings(void);
void SETTINGS_SaveChannelName(uint16_t channel, const char * name);
void SETTINGS_SaveChannel(uint16_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, uint8_t Mode);
void SETTINGS_SaveBatteryCalibration(const uint16_t * batteryCalibration);
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep, bool check, bool save);
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep);
void SETTINGS_WriteBuildOptions(void);
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
void SETTINGS_WriteCurrentState(void);
+45 -52
View File
@@ -37,9 +37,8 @@ static int get_bit(uint8_t* array, int bit_index) {
void UI_DisplayAircopy(void)
{
char String[16] = { 0 };
char *pPrintStr = { 0 };
uint16_t percent;
char String[16];
char *pPrintStr;
UI_DisplayClear();
@@ -60,15 +59,13 @@ void UI_DisplayAircopy(void)
// show the remaining 2 small frequency digits
UI_PrintStringSmallNormal(String + 7, 97, 0, 3);
String[7] = 0;
// show the main large frequency digits
UI_DisplayFrequency(String, 16, 2, false);
} else {
const char *ascii = INPUTBOX_GetAscii();
sprintf(String, "%.3s.%.3s", ascii, ascii + 3);
UI_DisplayFrequency(String, 16, 2, false);
}
memset(String, 0, sizeof(String));
// show the main large frequency digits
UI_DisplayFrequency(String, 16, 2, false);
// Get the current map and calculate percentage based on its total blocks
const AIRCOPY_TransferMap_t *currentMap = AIRCOPY_GetCurrentMap();
@@ -78,47 +75,44 @@ void UI_DisplayAircopy(void)
if (doneBlocks > currentMap->total_blocks)
doneBlocks = currentMap->total_blocks;
percent = (doneBlocks * 10000) / currentMap->total_blocks;
if (gAirCopyIsSendMode == 0) {
sprintf(String, "RCV:%02u.%02u%% E:%d", percent / 100, percent % 100, gErrorsDuringAirCopy);
} else if (gAirCopyIsSendMode == 1) {
sprintf(String, "SND:%02u.%02u%%", percent / 100, percent % 100);
}
// Draw gauge
if(gAircopyStep != 0)
{
UI_PrintString(String, 2, 127, 5, 8);
gFrameBuffer[4][1] = 0x3c;
gFrameBuffer[4][2] = 0x42;
for(uint8_t i = 1; i <= AIRCOPY_BAR_WIDTH + 2; i++)
{
gFrameBuffer[4][2 + i] = 0x81;
}
gFrameBuffer[4][125] = 0x42;
gFrameBuffer[4][126] = 0x3c;
}
// Draw memory selection
if(gAircopyState == AIRCOPY_READY)
if (gAircopyState == AIRCOPY_READY)
{
doneBlocks = 0;
memset(gFrameBuffer[5], 0, 128);
memset(gFrameBuffer[6], 0, 128);
if(gAircopyCurrentMapIndex < AIRCOPY_NUM_BANKS) {
sprintf(String, "MEM %03u - %03u%", (gAircopyCurrentMapIndex * 128) + 1, (gAircopyCurrentMapIndex + 1) * 128);
}
else
{
sprintf(String, "Settings");
sprintf(String, "MEM %03u - %03u", (gAircopyCurrentMapIndex * 128) + 1, (gAircopyCurrentMapIndex + 1) * 128);
} else {
strcpy(String, "Settings");
}
UI_PrintString(String, 2, 127, 5, 8);
}
else
{
uint16_t percent = (doneBlocks * 10000) / currentMap->total_blocks;
if (gAirCopyIsSendMode == 0) {
sprintf(String, "RCV:%02u.%02u%% E:%d", percent / 100, percent % 100, gErrorsDuringAirCopy);
} else {
sprintf(String, "SND:%02u.%02u%%", percent / 100, percent % 100);
}
// Draw gauge
if(gAircopyStep != 0)
{
UI_PrintString(String, 2, 127, 5, 8);
gFrameBuffer[4][1] = 0x3c;
gFrameBuffer[4][2] = 0x42;
for(uint8_t i = 1; i <= AIRCOPY_BAR_WIDTH + 2; i++)
{
gFrameBuffer[4][2 + i] = 0x81;
}
gFrameBuffer[4][125] = 0x42;
gFrameBuffer[4][126] = 0x3c;
}
}
if (doneBlocks > 0)
@@ -131,19 +125,12 @@ void UI_DisplayAircopy(void)
lErrorsDuringAirCopy = gErrorsDuringAirCopy;
}
const AIRCOPY_TransferMap_t *currentMap = AIRCOPY_GetCurrentMap();
uint16_t total = currentMap->total_blocks;
uint16_t done = gAirCopyBlockNumber + gErrorsDuringAirCopy;
if (done > total) done = total;
uint16_t b = 0;
uint16_t fraction_accumulator = 0;
for (uint8_t col = 0; col < AIRCOPY_BAR_WIDTH; col++)
{
/* Map column [0..BAR_WIDTH-1] to block [0..total-1] */
uint16_t b = (uint16_t)((col * (uint32_t)total) / AIRCOPY_BAR_WIDTH);
bool processed = (b < done);
bool processed = (b < doneBlocks);
bool error = processed && get_bit(crc, b);
if (!processed)
@@ -152,8 +139,14 @@ void UI_DisplayAircopy(void)
gFrameBuffer[4][col + 4] = 0x81; // error gap (intentional hole)
else
gFrameBuffer[4][col + 4] = 0xBD; // ok filled
}
// DDA/Bresenham algorythm
fraction_accumulator += currentMap->total_blocks;
while (fraction_accumulator >= AIRCOPY_BAR_WIDTH) {
fraction_accumulator -= AIRCOPY_BAR_WIDTH;
b++;
}
}
}
ST7565_BlitFullScreen();
+1 -2
View File
@@ -31,7 +31,7 @@
void UI_DisplayFM(void)
{
char String[16] = {0};
char String[16];
char *pPrintStr = String;
UI_DisplayClear();
@@ -80,7 +80,6 @@ void UI_DisplayFM(void)
UI_PrintString(pPrintStr, 0, 127, 3, 10); // memory, vfo, scan
memset(String, 0, sizeof(String));
if (gAskToSave || (gEeprom.FM_IsMrMode && gInputBoxIndex > 0)) {
UI_GenerateChannelString(String, gFM_ChannelPosition);
} else if (gAskToDelete) {
+24
View File
@@ -40,6 +40,8 @@ void UI_GenerateChannelString(char *pString, const uint16_t Channel)
pString[2] = '-';
for (i = 0; i < 2; i++)
pString[i + 3] = (gInputBox[i] == 10) ? '-' : gInputBox[i] + '0';
pString[5] = 0;
}
void UI_GenerateChannelStringEx(char *pString, const bool bShowPrefix, const uint16_t ChannelNumber)
@@ -323,6 +325,23 @@ static void sort(int16_t *a, int16_t *b)
}
}
void GUI_DisplaySmallestInverse(const char *pString, uint8_t x, uint8_t Line,
bool statusbar, bool fill, uint8_t end)
{
// First draw the string normally
GUI_DisplaySmallest(pString, x, (Line * 8) + 1, statusbar, fill);
// Now invert the framebuffer/statusline bits for the rendered area
uint8_t start = (x - 2);
uint8_t *buffer = statusbar ? gStatusLine : gFrameBuffer[Line];
buffer[start] ^= 0x3E;
for (uint8_t i = start + 1; i < end; i++) {
buffer[i] ^= 0x7F;
}
buffer[end] ^= 0x3E;
}
void UI_DisplayUnlockKeyboard(uint8_t shift) {
if (gEeprom.KEY_LOCK && gKeypadLocked > 0)
{ // tell user how to unlock the keyboard
@@ -406,3 +425,8 @@ void UI_DisplayClear()
{
memset(gFrameBuffer, 0, sizeof(gFrameBuffer));
}
void UI_StatusClear()
{
memset(gStatusLine, 0, sizeof(gStatusLine));
}
+2
View File
@@ -38,6 +38,7 @@ void UI_DrawPixelBuffer(uint8_t (*buffer)[128], uint8_t x, uint8_t y, bool black
void PutPixel(uint8_t x, uint8_t y, bool fill);
void PutPixelStatus(uint8_t x, uint8_t y, bool fill);
void GUI_DisplaySmallest(const char *pString, uint8_t x, uint8_t y, bool statusbar, bool fill);
void GUI_DisplaySmallestInverse(const char *pString, uint8_t x, uint8_t Line, bool statusbar, bool fill, uint8_t endX);
void UI_DisplayUnlockKeyboard(uint8_t shift);
bool IsEmptyName(const char *name, uint8_t len);
#endif
@@ -45,5 +46,6 @@ void UI_DrawLineBuffer(uint8_t (*buffer)[128], int16_t x1, int16_t y1, int16_t x
void UI_DrawRectangleBuffer(uint8_t (*buffer)[128], int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool black);
void UI_DisplayClear();
void UI_StatusClear();
#endif
+15
View File
@@ -41,4 +41,19 @@ const char* INPUTBOX_GetAscii()
inputBoxAscii[i] = (c==10)? '-' : '0' + c;
}
return inputBoxAscii;
}
const char* INPUTBOX_GetAsciiAlignRight() {
int targetPos = 7;
for (int i = 7; i >= 0; i--) {
if (gInputBox[i] != 10) {
inputBoxAscii[targetPos--] = '0' + gInputBox[i];
}
}
while(targetPos >= 0)
inputBoxAscii[targetPos--] = '-';
return inputBoxAscii;
}
+2
View File
@@ -25,7 +25,9 @@ extern char gInputBox[8];
extern uint8_t gInputBoxIndex;
void INPUTBOX_Append(const KEY_Code_t Digit);
const char* INPUTBOX_GetAscii();
const char* INPUTBOX_GetAsciiAlignRight();
#endif
+2 -11
View File
@@ -34,7 +34,7 @@ static void Render(void)
unsigned int i;
char String[7];
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
UI_DisplayClear();
UI_PrintString("PASSWORD", 0, 127, 1, 10);
@@ -80,16 +80,7 @@ void UI_DisplayLock(void)
switch (Key)
{
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
case KEY_0...KEY_9:
INPUTBOX_Append(Key - KEY_0);
if (gInputBoxIndex < 6) // 6 frequency digits
+749 -65
View File
File diff suppressed because it is too large. Load diff
+13 -1
View File
@@ -22,12 +22,16 @@
enum center_line_t {
CENTER_LINE_NONE = 0,
CENTER_LINE_IN_USE,
CENTER_LINE_SCAN_PROGRESS,
CENTER_LINE_AUDIO_BAR,
CENTER_LINE_AUDIO_SCOPE,
CENTER_LINE_RSSI,
CENTER_LINE_AM_FIX_DATA,
CENTER_LINE_DTMF_DEC,
CENTER_LINE_CHARGE_DATA
CENTER_LINE_CHARGE_DATA,
#ifdef ENABLE_FEAT_F4HWN_BEAM
CENTER_LINE_BEAM
#endif
};
enum Vfo_txtr_mode{
@@ -49,6 +53,14 @@ void UI_DisplayAudioScope(void);
void UI_MAIN_TimeSlice500ms(void);
void UI_DisplayMain(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
void UI_MAIN_NotifyScanProgressDataChanged(void);
void UI_MAIN_NotifyScanListChanged(void);
#else
static inline void UI_MAIN_NotifyScanProgressDataChanged(void) {}
static inline void UI_MAIN_NotifyScanListChanged(void) {}
#endif
#ifdef ENABLE_AGC_SHOW_DATA
void UI_MAIN_PrintAGC(bool force);
#endif
+344 -134
View File
@@ -27,6 +27,7 @@
#include "../driver/eeprom.h"
#include "../driver/st7565.h"
#include "../external/printf/printf.h"
#include "../font.h"
#include "../frequencies.h"
#include "../helper/battery.h"
#include "../misc.h"
@@ -40,6 +41,7 @@
#include "inputbox.h"
#include "menu.h"
#include "ui.h"
#include "welcome.h"
const t_menu_item MenuList[] =
@@ -166,6 +168,12 @@ const t_menu_item MenuList[] =
#ifdef ENABLE_NOAA
{"SetNWR", MENU_NOAA_S },
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
{"SetScn", MENU_SET_SCN },
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
{"SetSav", MENU_SET_SAV },
#endif
#endif
// hidden menu items from here on
// enabled if pressing both the PTT and upper side button at power-on
@@ -192,7 +200,7 @@ const t_menu_item MenuList[] =
const uint8_t FIRST_HIDDEN_MENU_ITEM = MENU_F_LOCK;
const char gSubMenu_TXP[][6] =
const char* const gSubMenu_TXP[] =
{
"USER",
"LOW 1",
@@ -204,29 +212,26 @@ const char gSubMenu_TXP[][6] =
"HIGH"
};
const char gSubMenu_SFT_D[][4] =
const char* const gSubMenu_SFT_D[] =
{
"OFF",
"+",
"-"
};
const char gSubMenu_W_N[][7] =
const char* const gSubMenu_W_N[] =
{
"WIDE",
"NARROW"
};
const char gSubMenu_OFF_ON[][4] =
const char* const gSubMenu_OFF_ON[] =
{
"OFF",
"ON"
};
const char gSubMenu_NA[4] =
{
"N/A"
};
const char* gSubMenu_NA = "N/A";
const char* const gSubMenu_RXMode[] =
{
@@ -237,7 +242,7 @@ const char* const gSubMenu_RXMode[] =
};
#ifdef ENABLE_VOICE
const char gSubMenu_VOICE[][4] =
const char* const gSubMenu_VOICE[] =
{
"OFF",
"CHI",
@@ -254,7 +259,7 @@ const char* const gSubMenu_MDF[] =
};
#ifdef ENABLE_ALARM
const char gSubMenu_AL_MOD[][5] =
const char* const gSubMenu_AL_MOD[] =
{
"SITE",
"TONE"
@@ -262,7 +267,7 @@ const char* const gSubMenu_MDF[] =
#endif
#ifdef ENABLE_DTMF_CALLING
const char gSubMenu_D_RSP[][11] =
const char* const gSubMenu_D_RSP[] =
{
"DO\nNOTHING",
"RING",
@@ -280,7 +285,7 @@ const char* const gSubMenu_PTT_ID[] =
"APOLLO\nQUINDAR"
};
const char gSubMenu_PONMSG[][8] =
const char* const gSubMenu_PONMSG[] =
{
#ifdef ENABLE_FEAT_F4HWN
"ALL",
@@ -290,23 +295,36 @@ const char gSubMenu_PONMSG[][8] =
#endif
"MESSAGE",
"VOLTAGE",
#ifdef ENABLE_FEAT_F4HWN_LOGO
"LOGO",
#endif
"NONE"
};
const char gSubMenu_ROGER[][6] =
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
const char* const gSubMenu_SET_SAV[] =
{
"OFF",
"LOGO",
"LOGO+",
"MATRIX"
};
#endif
const char* const gSubMenu_ROGER[] =
{
"OFF",
"ROGER",
"MDC"
};
const char gSubMenu_RESET[][4] =
const char* const gSubMenu_RESET[] =
{
"VFO",
"ALL"
};
const char * const gSubMenu_F_LOCK[] =
const char* const gSubMenu_F_LOCK[] =
{
"DEFAULT+\n137-174\n400-470",
"FCC HAM\n144-148\n420-450",
@@ -327,7 +345,7 @@ const char * const gSubMenu_F_LOCK[] =
"UNLOCK\nALL",
};
const char gSubMenu_RX_TX[][6] =
const char* const gSubMenu_RX_TX[] =
{
"OFF",
"TX",
@@ -335,14 +353,14 @@ const char gSubMenu_RX_TX[][6] =
"TX/RX"
};
const char gSubMenu_BAT_TXT[][8] =
const char* const gSubMenu_BAT_TXT[] =
{
"NONE",
"VOLTAGE",
"PERCENT"
};
const char gSubMenu_BATTYP[][12] =
const char* const gSubMenu_BATTYP[] =
{
"1600mAh K5",
"2200mAh K5",
@@ -351,14 +369,14 @@ const char gSubMenu_BATTYP[][12] =
"2500mAh K1"
};
const char gSubMenu_SET_NAV[][17] =
const char* const gSubMenu_SET_NAV[] =
{
"LEFT\nRIGHT\nUV-K1",
"UP\nDOWN\nUV-K5(8)",
};
#ifndef ENABLE_FEAT_F4HWN
const char gSubMenu_SCRAMBLER[][7] =
const char* const gSubMenu_SCRAMBLER[] =
{
"OFF",
"2600Hz",
@@ -375,7 +393,7 @@ const char gSubMenu_SCRAMBLER[][7] =
#endif
#ifdef ENABLE_FEAT_F4HWN
const char gSubMenu_SET_PWR[][6] =
const char* const gSubMenu_SET_PWR[] =
{
"< 20m",
"125m",
@@ -386,13 +404,13 @@ const char gSubMenu_SCRAMBLER[][7] =
"5"
};
const char gSubMenu_SET_PTT[][8] =
const char* const gSubMenu_SET_PTT[] =
{
"CLASSIC",
"ONEPUSH"
};
const char gSubMenu_SET_TOT[][7] = // Use by SET_EOT too
const char* const gSubMenu_SET_TOT[] =
{
"OFF",
"SOUND",
@@ -400,20 +418,30 @@ const char gSubMenu_SCRAMBLER[][7] =
"ALL"
};
const char gSubMenu_SET_LCK[][9] =
const char* const gSubMenu_SET_LCK[] =
{
"KEYS",
"KEYS+PTT"
"KEYS\nACTIONS",
"KEYS\nPTT",
"KEYS\nACTIONS\nPTT"
};
const char gSubMenu_SET_MET[][8] =
const char* const gSubMenu_SET_MET[] =
{
"TINY",
"CLASSIC"
};
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
const char* const gSubMenu_SET_SCN[] =
{
"NORMAL",
"FAST"
};
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
const char gSubMenu_SET_AUD_FM[][6] =
const char* const gSubMenu_SET_AUD_FM[] =
{
"FLAT",
"CLEAN",
@@ -422,7 +450,7 @@ const char gSubMenu_SCRAMBLER[][7] =
"MAX"
};
const char gSubMenu_SET_AUD_AM[][6] =
const char* const gSubMenu_SET_AUD_AM[] =
{
"SHARP",
"STOCK",
@@ -431,7 +459,7 @@ const char gSubMenu_SCRAMBLER[][7] =
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
const char gSubMenu_SET_NFM[][9] =
const char* const gSubMenu_SET_NFM[] =
{
"NARROW",
"NARROWER"
@@ -439,7 +467,7 @@ const char gSubMenu_SCRAMBLER[][7] =
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
const char gSubMenu_SET_KEY[][9] =
const char* const gSubMenu_SET_KEY[] =
{
"KEY_MENU",
"KEY_UP",
@@ -470,17 +498,13 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
#endif
#ifdef ENABLE_TX1750
{"1750Hz", ACTION_OPT_1750},
#endif
#ifdef ENABLE_REGA
{"REGA\nALARM", ACTION_OPT_REGA_ALARM},
{"REGA\nTEST", ACTION_OPT_REGA_TEST},
#endif
{"LOCK\nKEYPAD", ACTION_OPT_KEYLOCK},
{"VFO A\nVFO B", ACTION_OPT_A_B},
{"VFO\nMEM", ACTION_OPT_VFO_MR},
{"MODE", ACTION_OPT_SWITCH_DEMODUL},
#ifdef ENABLE_BLMIN_TMP_OFF
{"BLMIN\nTMP OFF", ACTION_OPT_BLMIN_TMP_OFF}, //BackLight Minimum Temporay OFF
{"BLMIN\nTMP OFF", ACTION_OPT_BLMIN_TMP_OFF}, //BackLight Minimum Temporary OFF
#endif
#ifdef ENABLE_FEAT_F4HWN
{"RX MODE", ACTION_OPT_RXMODE},
@@ -495,6 +519,15 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
{"POWER\nHIGH", ACTION_OPT_POWER_HIGH},
{"REMOVE\nOFFSET", ACTION_OPT_REMOVE_OFFSET},
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
{"BEAM", ACTION_OPT_BEAM},
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
{"RF LOG", ACTION_OPT_RXTX_LOG},
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
{"FOX HUNT\nBEACON", ACTION_OPT_FOXHUNT},
#endif
#endif
};
@@ -523,6 +556,64 @@ int32_t gSubMenuSelection;
char edit_original[17]; // a copy of the text before editing so that we can easily test for changes/difference
char edit[17];
int edit_index;
bool edit_is_uppercase = false;
static void UI_MENU_DrawTopRightRoundedBadge(const char *text, const uint8_t line, const bool center_in_area, const uint8_t area_x1, const uint8_t area_x2)
{
const size_t length = strlen(text);
const size_t char_pitch = ARRAY_SIZE(gFontSmall[0]) + 1u;
const size_t text_width = length * char_pitch;
const size_t capsule_span = text_width + 1u; // matches UI_PrintStringSmallNormalInverse x_end computation
uint8_t text_x;
if (length == 0 || line == 0 || line >= FRAME_LINES) {
return;
}
if (center_in_area && area_x2 > area_x1 + 2u) {
const uint8_t min_x = area_x1 + 1u;
uint8_t max_x;
const uint8_t area_width = area_x2 - area_x1 + 1u;
if (capsule_span >= area_width) {
text_x = min_x;
} else {
text_x = (uint8_t)(area_x1 + ((area_width - capsule_span) / 2u));
}
if (area_x2 > capsule_span) {
max_x = (uint8_t)(area_x2 - capsule_span);
} else {
max_x = min_x;
}
if (max_x < min_x) {
max_x = min_x;
}
if (text_x < min_x) {
text_x = min_x;
} else if (text_x > max_x) {
text_x = max_x;
}
} else {
if (capsule_span >= (LCD_WIDTH - 3u)) {
text_x = 1u;
} else {
const uint8_t global_shift_right = 1u;
const uint8_t base_text_x = (uint8_t)(LCD_WIDTH - capsule_span - 3u);
const uint8_t max_text_x = (uint8_t)(LCD_WIDTH - capsule_span - 1u);
const uint16_t shifted_x = (uint16_t)base_text_x + global_shift_right;
if (shifted_x > max_text_x) {
text_x = max_text_x;
} else {
text_x = (uint8_t)shifted_x;
}
}
}
UI_PrintStringSmallNormalInverse(text, text_x, 0, line);
}
void UI_DisplayMenu(void)
{
@@ -531,6 +622,9 @@ void UI_DisplayMenu(void)
const unsigned int menu_item_x2 = LCD_WIDTH - 1;
unsigned int i;
char String[64]; // bigger cuz we can now do multi-line in one string (use '\n' char)
char top_right_badge[16];
const int m = UI_MENU_GetCurrentMenuId();
#ifdef ENABLE_DTMF_CALLING
char Contact[16];
@@ -578,57 +672,56 @@ void UI_DisplayMenu(void)
#else
{ // new menu layout .. experimental & unfinished
const int menu_index = gMenuCursor; // current selected menu item
i = 1;
const int menu_count = (int)gMenuListCount;
if (!gIsInSubMenu) {
while (i < 2)
{ // leading menu items - small text
const int k = menu_index + i - 2;
if (k < 0)
UI_PrintStringSmallNormal(MenuList[gMenuListCount + k].name, 0, 0, i); // wrap-a-round
else if (k >= 0 && k < (int)gMenuListCount)
UI_PrintStringSmallNormal(MenuList[k].name, 0, 0, i);
i++;
}
if (menu_index >= 0 && menu_index < menu_count)
{
if (!gIsInSubMenu)
{
// leading menu items - small text
int prev_index = menu_index - 1;
if (prev_index < 0) {
prev_index = menu_count - 1;
}
UI_PrintStringSmallNormal(MenuList[prev_index].name, 0, 0, 1);
// current menu item - keep big n fat
if (menu_index >= 0 && menu_index < (int)gMenuListCount)
// current menu item - keep big n fat
UI_PrintString(MenuList[menu_index].name, 0, 0, 2, 8);
i++;
while (i < 4)
{ // trailing menu item - small text
const int k = menu_index + i - 2;
if (k >= 0 && k < (int)gMenuListCount)
UI_PrintStringSmallNormal(MenuList[k].name, 0, 0, 1 + i);
else if (k >= (int)gMenuListCount)
UI_PrintStringSmallNormal(MenuList[gMenuListCount - k].name, 0, 0, 1 + i); // wrap-a-round
i++;
// trailing menu item - small text
int next_index = menu_index + 1;
if (next_index >= menu_count) {
next_index = 0;
}
UI_PrintStringSmallNormal(MenuList[next_index].name, 0, 0, 4);
// draw the menu index number/count
#ifndef ENABLE_FEAT_F4HWN
sprintf(String, "%2u.%u", 1 + menu_index, menu_count);
UI_PrintStringSmallNormal(String, 2, 0, 6);
#endif
}
else
{
// current menu item
// strcat(String, ":");
UI_PrintString(MenuList[menu_index].name, 0, 0, 0, 8);
// UI_PrintStringSmallNormal(String, 0, 0, 0);
}
// draw the menu index number/count
#ifndef ENABLE_FEAT_F4HWN
sprintf(String, "%2u.%u", 1 + gMenuCursor, gMenuListCount);
UI_PrintStringSmallNormal(String, 2, 0, 6);
#endif
#ifdef ENABLE_FEAT_F4HWN
sprintf(String, "%02u/%u", 1 + menu_index, menu_count);
UI_PrintStringSmallNormal(String, 6, 0, 6);
#endif
}
else if (menu_index >= 0 && menu_index < (int)gMenuListCount)
{ // current menu item
// strcat(String, ":");
UI_PrintString(MenuList[menu_index].name, 0, 0, 0, 8);
// UI_PrintStringSmallNormal(String, 0, 0, 0);
}
#ifdef ENABLE_FEAT_F4HWN
sprintf(String, "%02u/%u", 1 + gMenuCursor, gMenuListCount);
UI_PrintStringSmallNormal(String, 6, 0, 6);
#endif
}
#endif
// **************
memset(String, 0, sizeof(String));
String[0] = '\0';
top_right_badge[0] = '\0';
bool already_printed = false;
@@ -644,7 +737,7 @@ void UI_DisplayMenu(void)
uint8_t gaugeMax = 0;
//#endif
switch (UI_MENU_GetCurrentMenuId())
switch (m)
{
case MENU_SQL:
sprintf(String, "%d", gSubMenuSelection);
@@ -842,6 +935,12 @@ void UI_DisplayMenu(void)
strcpy(String, gSubMenu_OFF_ON[gSubMenuSelection]);
break;
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
case MENU_SET_SAV:
strcpy(String, gSubMenu_SET_SAV[gSubMenuSelection]);
break;
#endif
case MENU_MEM_CH:
case MENU_1_CALL:
case MENU_DEL_CH:
@@ -865,7 +964,7 @@ void UI_DisplayMenu(void)
{ // show the frequency so that the user knows the channels frequency
const uint32_t frequency = SETTINGS_FetchChannelFrequency(gSubMenuSelection);
sprintf(String, "%u.%05u", frequency / 100000, frequency % 100000);
UI_PrintString(String, menu_item_x1, menu_item_x2, 4, 8);
UI_PrintString(String, menu_item_x1, menu_item_x2, 5, 8);
}
SETTINGS_FetchChannelName(String, gSubMenuSelection);
@@ -899,15 +998,34 @@ void UI_DisplayMenu(void)
{ // show the channel name being edited
//UI_PrintString(edit, menu_item_x1, 0, 2, 8);
UI_PrintString(edit, menu_item_x1, menu_item_x2, 2, 8);
if (edit_index < 10)
//UI_PrintString("^", menu_item_x1 + (8 * edit_index), 0, 4, 8); // show the cursor
UI_PrintString("^", menu_item_x1 - 1 + (8 * edit_index),0, 4, 8); // show the cursor
if (edit_index < 10) {
// UI_PrintString("^", menu_item_x1 - 1 + (8 * edit_index),0, 4, 8); // show the cursor
uint8_t x = menu_item_x1 - 1;
for (uint8_t i = 0; i < 10; i++)
{
if (i != edit_index)
{
if (edit[i] != 'g' && edit[i] != 'j')
{
UI_DrawLineBuffer(gFrameBuffer, x, 29, x + 6, 29, 1);
}
}
else
{
UI_DrawLineBuffer(gFrameBuffer, x + 2, 30, x + 4, 30, 1);
UI_DrawPixelBuffer(gFrameBuffer, x + 3, 29, 1);
}
x += 8;
}
UI_PrintStringSmallNormal(edit_is_uppercase ? "ABC" : "abc", 77, 0, 4);
}
}
if (!gAskForConfirmation)
{ // show the frequency so that the user knows the channels frequency
sprintf(String, "%u.%05u", frequency / 100000, frequency % 100000);
UI_PrintString(String, menu_item_x1, menu_item_x2, 4 + (gIsInSubMenu && edit_index >= 0), 8);
UI_PrintString(String, menu_item_x1, menu_item_x2, 5, 8);
}
}
@@ -978,7 +1096,7 @@ void UI_DisplayMenu(void)
case MENU_S_LIST:
if (gSubMenuSelection == MR_CHANNELS_LIST + 1)
strcpy(String, "ALL");
else if (gSubMenuSelection == 0 && UI_MENU_GetCurrentMenuId() == MENU_LIST_CH)
else if (gSubMenuSelection == 0 && m == MENU_LIST_CH)
strcpy(String, "OFF");
else {
const char *name = gListName[gSubMenuSelection - 1];
@@ -1003,11 +1121,19 @@ void UI_DisplayMenu(void)
break;
#endif
case MENU_UPCODE:
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_UP_CODE, gEeprom.DTMF_UP_CODE + 8);
if (gEeprom.DTMF_UP_CODE[8] != '\0' && gEeprom.DTMF_UP_CODE[8] != 0xFF) {
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_UP_CODE, gEeprom.DTMF_UP_CODE + 8);
} else {
sprintf(String, "%.8s", gEeprom.DTMF_UP_CODE);
}
break;
case MENU_DWCODE:
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_DOWN_CODE, gEeprom.DTMF_DOWN_CODE + 8);
if (gEeprom.DTMF_DOWN_CODE[8] != '\0' && gEeprom.DTMF_DOWN_CODE[8] != 0xFF) {
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_DOWN_CODE, gEeprom.DTMF_DOWN_CODE + 8);
} else {
sprintf(String, "%.8s", gEeprom.DTMF_DOWN_CODE);
}
break;
#ifdef ENABLE_DTMF_CALLING
@@ -1049,18 +1175,98 @@ void UI_DisplayMenu(void)
strcpy(String, gSubMenu_ROGER[gSubMenuSelection]);
break;
case MENU_VOL:
case MENU_VOL: {
// SysInf is paginated. Pages appear in this order, only when their
// feature flag is enabled:
// 0 -> identity
// next -> Build date/time (ENABLE_FEAT_F4HWN)
// next -> Battery (ENABLE_FEAT_F4HWN)
// next -> Flash / SRAM usage (ENABLE_FEAT_F4HWN_MEM)
// next, +1 -> CODE / WIKI QR codes (ENABLE_FEAT_F4HWN_QRCODE)
// In non-F4HWN builds, page 0 keeps the old battery-voltage display.
const uint8_t page = (uint8_t)gSubMenuSelection;
uint8_t p = 0;
if (page == p++) {
// Page 0: firmware identity.
#ifdef ENABLE_FEAT_F4HWN
sprintf(String, "%s\n%s",
AUTHOR_STRING_2,
VERSION_STRING_2
);
sprintf(String, "%s\n%s", AUTHOR_STRING_2, DISPLAY_VERSION_STRING_2);
UI_PrintStringSmallNormal(Edition, menu_item_x1 - 1, menu_item_x2, 6);
#else
sprintf(String, "%u.%02uV\n%u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
sprintf(String, "%u.%02uV\n%u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
#endif
break;
}
#ifdef ENABLE_FEAT_F4HWN
if (page == p++) {
strcpy(top_right_badge, "BUILD");
UI_PrintStringSmallNormal(BuildDate, menu_item_x1 - 1, menu_item_x2, 3);
UI_PrintStringSmallNormal(BuildTime, menu_item_x1 - 1, menu_item_x2, 4);
UI_PrintStringSmallNormal(BuildCommit, menu_item_x1 - 1, menu_item_x2, 6);
already_printed = true;
break;
}
if (page == p++) {
char val[16];
strcpy(top_right_badge, "BATTERY");
sprintf(val, "%u.%02uV %u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 3);
UI_PrintStringSmallNormal(gSubMenu_BATTYP[gEeprom.BATTERY_TYPE], menu_item_x1 - 1, menu_item_x2, 5);
already_printed = true;
break;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
if (page == p++) {
uint16_t flash_pct = 0;
uint16_t ram_pct = 0;
UI_GetMemPercents(&flash_pct, &ram_pct);
char val[16];
// MEMORY title capsule centered in right zone, fb line 1.
strcpy(top_right_badge, "MEMORY");
// Flash + SRAM values stacked below, normal small font, with a fb-line of breathing space.
sprintf(val, "FLASH %u.%u%%",
(unsigned)(flash_pct / 100), (unsigned)((flash_pct / 10) % 10));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 3);
sprintf(val, "SRAM %u.%u%%",
(unsigned)(ram_pct / 100), (unsigned)((ram_pct / 10) % 10));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 5);
already_printed = true;
break;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Right zone: x=49..127 (79 px). QR centered at x=72..104.
// Capsule label above QR (small-font Inverse style at fb line 1).
if (page == p || page == p + 1) {
const bool is_wiki = (page == (p + 1));
strcpy(top_right_badge, is_wiki ? "WIKI" : "CODE");
UI_DrawQRCode(is_wiki, 72, 28);
already_printed = true;
break;
}
p += 2;
#endif
break;
}
case MENU_RESET:
strcpy(String, gSubMenu_RESET[gSubMenuSelection]);
@@ -1187,19 +1393,25 @@ void UI_DisplayMenu(void)
strcpy(String, gSubMenu_SET_MET[gSubMenuSelection]); // Same as SET_MET
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
strcpy(String, gSubMenu_SET_SCN[gSubMenuSelection]);
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM) {
strcpy(String, gSubMenu_SET_AUD_AM[gSubMenuSelection]);
UI_PrintStringSmallNormal("AM", 114, 0, 0);
strcpy(top_right_badge, "AM");
}
else if (gTxVfo->Modulation == MODULATION_USB) {
strcpy(String, "USB");
UI_PrintStringSmallNormal("USB", 108, 0, 0);
strcpy(top_right_badge, "USB");
}
else {
strcpy(String, gSubMenu_SET_AUD_FM[gSubMenuSelection]);
UI_PrintStringSmallNormal("FM", 114, 0, 0);
strcpy(top_right_badge, "FM");
}
break;
#endif
@@ -1227,11 +1439,7 @@ void UI_DisplayMenu(void)
//#endif
}
// gEeprom.VOLUME_GAIN = gSubMenuSelection;
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
break;
#endif
@@ -1259,7 +1467,7 @@ void UI_DisplayMenu(void)
unsigned int len = strlen(String);
bool small = false;
if (len > 0)
if (String[0] != '\0')
{
// count number of lines
for (i = 0; i < len; i++)
@@ -1288,23 +1496,6 @@ void UI_DisplayMenu(void)
y = (small ? 3 : 2) - (lines / 2);
// only for SysInf
if(UI_MENU_GetCurrentMenuId() == MENU_VOL)
{
sprintf(edit, "%u.%02uV %u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage)
);
UI_PrintStringSmallNormal(edit, 54, 127, 1);
#ifdef ENABLE_FEAT_F4HWN
UI_PrintStringSmallNormal(Edition, 54, 127, 6);
#endif
y = 2;
}
// draw the text lines
for (i = 0; i < len && lines > 0; lines--)
{
@@ -1326,16 +1517,16 @@ void UI_DisplayMenu(void)
}
}
if (UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_1 || UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_2)
if (m == MENU_S_PRI_CH_1 || m == MENU_S_PRI_CH_2)
{
}
if ((UI_MENU_GetCurrentMenuId() == MENU_R_CTCS || UI_MENU_GetCurrentMenuId() == MENU_R_DCS) && gCssBackgroundScan)
if ((m == MENU_R_CTCS || m == MENU_R_DCS) && gCssBackgroundScan)
UI_PrintString("SCAN", menu_item_x1, menu_item_x2, 4, 8);
#ifdef ENABLE_DTMF_CALLING
if (UI_MENU_GetCurrentMenuId() == MENU_D_LIST && gIsDtmfContactValid) {
if (m == MENU_D_LIST && gIsDtmfContactValid) {
Contact[11] = 0;
memcpy(&gDTMF_ID, Contact + 8, 4);
sprintf(String, "ID:%4s", gDTMF_ID);
@@ -1343,22 +1534,41 @@ void UI_DisplayMenu(void)
}
#endif
if (UI_MENU_GetCurrentMenuId() == MENU_R_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_R_DCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_DCS
#ifdef ENABLE_DTMF_CALLING
|| UI_MENU_GetCurrentMenuId() == MENU_D_LIST
#endif
) {
sprintf(String, "%03d", gSubMenuSelection);
UI_PrintStringSmallNormal(String, 107, 0, 0);
const bool is_ctcs = (m == MENU_R_CTCS || m == MENU_T_CTCS);
const bool is_dcs = (m == MENU_R_DCS || m == MENU_T_DCS);
if (is_ctcs || is_dcs) {
if (gSubMenuSelection == 0) {
strcpy(top_right_badge, is_ctcs ? "00/00" : "000/00");
} else {
const uint8_t approved_index = is_ctcs ?
DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) :
DCS_GetDcsApprovedIndex(gSubMenuSelection - 1);
const uint8_t width = is_ctcs ? 2 : 3;
if (approved_index != 0xFF) {
sprintf(top_right_badge, "%0*u/%02u", width, (unsigned)gSubMenuSelection, (unsigned)approved_index + 1);
} else {
sprintf(top_right_badge, "%0*u/--", width, (unsigned)gSubMenuSelection);
}
}
}
if ((UI_MENU_GetCurrentMenuId() == MENU_RESET ||
UI_MENU_GetCurrentMenuId() == MENU_MEM_CH ||
UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME ||
UI_MENU_GetCurrentMenuId() == MENU_DEL_CH) && gAskForConfirmation)
#ifdef ENABLE_DTMF_CALLING
if (m == MENU_D_LIST) {
sprintf(top_right_badge, "%03d", gSubMenuSelection);
}
#endif
if (top_right_badge[0] != '\0') {
UI_MENU_DrawTopRightRoundedBadge(top_right_badge, 1, true, menu_item_x1, menu_item_x2);
}
if ((m == MENU_RESET ||
m == MENU_MEM_CH ||
m == MENU_MEM_NAME ||
m == MENU_DEL_CH) && gAskForConfirmation)
{ // display confirmation
char *pPrintStr = (gAskForConfirmation == 1) ? "SURE?" : "WAIT!";
UI_PrintString(pPrintStr, menu_item_x1, menu_item_x2, 5, 8);
+56 -33
View File
@@ -23,7 +23,7 @@
#include "audio.h" // VOICE_ID_t
#include "settings.h"
typedef struct {
typedef struct __attribute__((packed)) {
const char name[7]; // menu display area only has room for 6 characters
uint8_t menu_id;
} t_menu_item;
@@ -137,6 +137,9 @@ enum
MENU_SET_MET,
MENU_SET_GUI,
MENU_SET_TMR,
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
MENU_SET_SCN,
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
MENU_SET_NFM,
#endif
@@ -160,69 +163,88 @@ enum
MENU_F2SHRT,
MENU_F2LONG,
MENU_MLONG,
MENU_BATTYP
MENU_BATTYP,
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
MENU_SET_SAV
#endif
};
extern const uint8_t FIRST_HIDDEN_MENU_ITEM;
extern const t_menu_item MenuList[];
extern const char gSubMenu_TXP[8][6];
extern const char gSubMenu_SFT_D[3][4];
extern const char gSubMenu_W_N[2][7];
extern const char gSubMenu_OFF_ON[2][4];
extern const char gSubMenu_NA[4];
extern const char gSubMenu_TOT[11][7];
extern const char* const gSubMenu_RXMode[4];
extern const char* const gSubMenu_TXP[8];
extern const char* const gSubMenu_SFT_D[3];
extern const char* const gSubMenu_W_N[2];
extern const char* const gSubMenu_OFF_ON[2];
extern const char* gSubMenu_NA;
extern const char* const gSubMenu_TOT[11];
extern const char* const gSubMenu_RXMode[4];
#ifdef ENABLE_VOICE
extern const char gSubMenu_VOICE[3][4];
extern const char* const gSubMenu_VOICE[3];
#endif
extern const char* const gSubMenu_MDF[4];
extern const char* const gSubMenu_MDF[4];
#ifdef ENABLE_ALARM
extern const char gSubMenu_AL_MOD[2][5];
extern const char* const gSubMenu_AL_MOD[2];
#endif
#ifdef ENABLE_DTMF_CALLING
extern const char gSubMenu_D_RSP[4][11];
extern const char* const gSubMenu_D_RSP[4];
#endif
#ifdef ENABLE_FEAT_F4HWN
extern const char gSubMenu_SET_PWR[7][6];
extern const char gSubMenu_SET_PTT[2][8];
extern const char gSubMenu_SET_TOT[4][7];
extern const char gSubMenu_SET_LCK[2][9];
extern const char gSubMenu_SET_MET[2][8];
extern const char* const gSubMenu_SET_PWR[7];
extern const char* const gSubMenu_SET_PTT[2];
extern const char* const gSubMenu_SET_TOT[4];
extern const char* const gSubMenu_SET_LCK[];
extern const char* const gSubMenu_SET_MET[2];
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
extern const char* const gSubMenu_SET_SCN[2];
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
extern const char gSubMenu_SET_NFM[2][9];
extern const char* const gSubMenu_SET_NFM[2];
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
extern const char gSubMenu_SET_KEY[][9];
extern const char* const gSubMenu_SET_KEY[5];
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
extern const char gSubMenu_SET_AUD_FM[5][6];
extern const char gSubMenu_SET_AUD_AM[3][6];
extern const char* const gSubMenu_SET_AUD_FM[5];
extern const char* const gSubMenu_SET_AUD_AM[3];
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
extern const char* const gSubMenu_SET_SAV[];
#endif
#endif
extern const char* const gSubMenu_PTT_ID[5];
#ifdef ENABLE_FEAT_F4HWN
extern const char gSubMenu_PONMSG[5][8];
#ifdef ENABLE_FEAT_F4HWN_LOGO
extern const char* const gSubMenu_PONMSG[6];
#else
extern const char* const gSubMenu_PONMSG[5];
#endif
#else
extern const char gSubMenu_PONMSG[4][8];
extern const char* const gSubMenu_PONMSG[4];
#endif
extern const char gSubMenu_ROGER[3][6];
extern const char gSubMenu_RESET[2][4];
extern const char* const gSubMenu_F_LOCK[F_LOCK_LEN];
extern const char gSubMenu_RX_TX[4][6];
extern const char gSubMenu_BAT_TXT[3][8];
extern const char gSubMenu_BATTYP[5][12];
extern const char* const gSubMenu_ROGER[3];
extern const char* const gSubMenu_RESET[2];
extern const char* const gSubMenu_F_LOCK[F_LOCK_LEN];
extern const char* const gSubMenu_RX_TX[4];
extern const char* const gSubMenu_BAT_TXT[3];
extern const char* const gSubMenu_BATTYP[5];
extern const char* const gSubMenu_SET_NAV[2];
#ifndef ENABLE_FEAT_F4HWN
extern const char gSubMenu_SCRAMBLER[11][7];
extern const char* const gSubMenu_SCRAMBLER[11];
#endif
typedef struct {char* name; uint8_t id;} t_sidefunction;
typedef struct /* __attribute__((packed)) */ {
const char* name;
uint8_t id;
} t_sidefunction;
extern const uint8_t gSubMenu_SIDEFUNCTIONS_size;
extern const t_sidefunction gSubMenu_SIDEFUNCTIONS[];
extern const t_sidefunction gSubMenu_SIDEFUNCTIONS[];
extern bool gIsInSubMenu;
@@ -233,6 +255,7 @@ extern int32_t gSubMenuSelection;
extern char edit_original[17];
extern char edit[17];
extern int edit_index;
extern bool edit_is_uppercase;
void UI_DisplayMenu(void);
int UI_MENU_GetCurrentMenuId();
+34 -36
View File
@@ -26,58 +26,56 @@
void UI_DisplayScanner(void)
{
char String[16] = {0};
char String[16];
char *pPrintStr = String;
bool bCentered;
uint8_t Start;
UI_DisplayClear();
if (gScanSingleFrequency || (gScanCssState != SCAN_CSS_STATE_OFF && gScanCssState != SCAN_CSS_STATE_FAILED)) {
sprintf(String, "FREQ:%u.%05u", gScanFrequency / 100000, gScanFrequency % 100000);
// 1st line
if (gScannerSaveState == SCAN_SAVE_CHANNEL) {
pPrintStr = "Save?";
} else if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
strcpy(String, "Save:");
UI_GenerateChannelStringEx(String + 5, gShowChPrefix, gScanChannel);
pPrintStr = String;
} else if ((gScanCssState < SCAN_CSS_STATE_FOUND) && ((gScanProgressIndicator & 1u) != 0)) {
pPrintStr = "";
} else if (gScanCssState == SCAN_CSS_STATE_OFF) {
pPrintStr = "Search Freq";
} else if (gScanCssState == SCAN_CSS_STATE_SCANNING) {
pPrintStr = "Search Tone";
} else if (gScanCssState == SCAN_CSS_STATE_FOUND) {
pPrintStr = "Scan Complete";
} else {
pPrintStr = "FREQ:**.*****";
pPrintStr = "Scan Failed";
}
UI_PrintString(pPrintStr, 2, 0, 1, 8);
if (gScanCssState < SCAN_CSS_STATE_FOUND || !gScanUseCssResult) {
pPrintStr = "CTC:******";
// 2nd line
if (gScanSingleFrequency || (gScanCssState != SCAN_CSS_STATE_OFF && gScanCssState != SCAN_CSS_STATE_FAILED)) {
sprintf(String, "Freq:%u.%05u", gScanFrequency / 100000, gScanFrequency % 100000);
pPrintStr = String;
} else {
pPrintStr = "Freq:---.-----";
}
UI_PrintString(pPrintStr, 2, 0, 3, 8);
// 3rd line
if (gScanCssState < SCAN_CSS_STATE_FOUND) {
pPrintStr = "Tone:---";
} else if (!gScanUseCssResult) {
pPrintStr = "Tone:None";
} else if (gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
sprintf(String, "CTC:%u.%uHz", CTCSS_Options[gScanCssResultCode] / 10, CTCSS_Options[gScanCssResultCode] % 10);
sprintf(String, "CTCSS:%u.%uHz", CTCSS_Options[gScanCssResultCode] / 10, CTCSS_Options[gScanCssResultCode] % 10);
pPrintStr = String;
} else {
sprintf(String, "DCS:D%03oN", DCS_Options[gScanCssResultCode]);
pPrintStr = String;
}
UI_PrintString(pPrintStr, 2, 0, 3, 8);
memset(String, 0, sizeof(String));
if (gScannerSaveState == SCAN_SAVE_CHANNEL) {
pPrintStr = "SAVE?";
Start = 0;
bCentered = 1;
} else {
Start = 2;
bCentered = 0;
if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
strcpy(String, "SAVE:");
UI_GenerateChannelStringEx(String + 5, gShowChPrefix, gScanChannel);
pPrintStr = String;
} else if (gScanCssState < SCAN_CSS_STATE_FOUND) {
strcpy(String, "SCAN");
memset(String + 4, '.', (gScanProgressIndicator & 7) + 1);
pPrintStr = String;
} else if (gScanCssState == SCAN_CSS_STATE_FOUND) {
pPrintStr = "SCAN CMP.";
} else {
pPrintStr = "SCAN FAIL.";
}
}
UI_PrintString(pPrintStr, Start, bCentered ? 127 : 0, 5, 8);
UI_PrintString(pPrintStr, 2, 0, 5, 8);
ST7565_BlitFullScreen();
}
+147 -94
View File
@@ -16,7 +16,11 @@
#include <string.h>
#include "app/app.h"
#include "app/chFrScanner.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#ifdef ENABLE_FMRADIO
#include "app/fm.h"
#endif
@@ -43,46 +47,76 @@ static void convertTime(uint8_t *line, uint8_t type)
uint8_t m = t / 60;
uint8_t s = t - (m * 60); // Replace modulo with subtraction for efficiency
gStatusLine[0] = gStatusLine[7] = gStatusLine[14] = 0x00; // Quick fix on display (on scanning I, II, etc.)
char str[6];
sprintf(str, "%02u:%02u", m, s);
UI_PrintStringSmallBufferNormal(str, line);
gUpdateStatus = true;
}
#endif
#endif
void UI_DisplayStatus()
{
char str[8] = "";
gUpdateStatus = false;
memset(gStatusLine, 0, sizeof(gStatusLine));
if (APP_IsScreenSaverDisplayed())
return;
UI_StatusClear();
uint8_t *line = gStatusLine;
unsigned int x = 0;
unsigned int x1 = 0;
#ifdef ENABLE_NOAA
// NOAA indicator
if (!(gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) && gIsNoaaMode) { // NOASS SCAN indicator
memcpy(line + x, BITMAP_NOAA, sizeof(BITMAP_NOAA));
char str[8] = "";
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
// The filter label reuses the leftmost slot (pixels 2..16) normally
// reserved by the power-save and scan indicators; those two are skipped
// on the log screen so the rest of the bar keeps its usual layout.
const bool isRxTxLogScreen = gScreenToDisplay == DISPLAY_RXTX_LOG;
if (isRxTxLogScreen) {
const char *filter = RXTX_LOG_GetFilterName();
const uint8_t end = (filter[2] == 0) ? 10 : 14;
GUI_DisplaySmallestInverse(filter, 2, 0, true, true, end);
}
// Power Save indicator
else if (gCurrentFunction == FUNCTION_POWER_SAVE) {
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
}
x += 8;
#else
// Power Save indicator
if (gCurrentFunction == FUNCTION_POWER_SAVE) {
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
}
x += 8;
#endif
unsigned int x1 = x;
#if defined(ENABLE_FEAT_F4HWN_RX_TX_TIMER) && !defined(ENABLE_FEAT_F4HWN_DEBUG)
bool isTransmit = gCurrentFunction == FUNCTION_TRANSMIT;
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
if (!isRxTxLogScreen && gSetting_set_tmr && (isTransmit || FUNCTION_IsRx())) {
#else
if (gSetting_set_tmr && (isTransmit || FUNCTION_IsRx())) {
#endif
convertTime(line, !isTransmit);
x += 39;
} else {
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
if (!isRxTxLogScreen)
#endif
{
#ifdef ENABLE_NOAA
// NOAA indicator
if (!(gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) && gIsNoaaMode) { // NOASS SCAN indicator
memcpy(line + x, BITMAP_NOAA, sizeof(BITMAP_NOAA));
}
// Power Save indicator
else if (gCurrentFunction == FUNCTION_POWER_SAVE) {
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
}
#else
// Power Save indicator
if (gCurrentFunction == FUNCTION_POWER_SAVE) {
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
}
#endif
}
x += 8;
x1 = x;
#ifdef ENABLE_DTMF_CALLING
if (gSetting_KILLED) {
@@ -92,15 +126,18 @@ void UI_DisplayStatus()
else
#endif
{ // SCAN indicator
if (gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) {
if ((gScanStateDir != SCAN_OFF || SCANNER_IsScanning())
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
&& !isRxTxLogScreen
#endif
) {
if (IS_MR_CHANNEL(gNextMrChannel) && !SCANNER_IsScanning()) { // channel mode
uint8_t end = 0;
if(gEeprom.SCAN_LIST_DEFAULT == MR_CHANNELS_LIST + 1)
{
sprintf(str, gEeprom.SCAN_LIST_ENABLED ? "%s+" : "%s", "ALL");
end = gEeprom.SCAN_LIST_ENABLED ? 18 : 14;
strcpy(str, "ALL");
end = 14;
}
else
{
@@ -108,15 +145,22 @@ void UI_DisplayStatus()
// Check if name is valid
if (!IsEmptyName(name, sizeof(gListName[0]))) {
sprintf(str, "%.3s%s", name, gEeprom.SCAN_LIST_ENABLED ? "+" : "");
end = gEeprom.SCAN_LIST_ENABLED ? 18 : 14;
}
else {
sprintf(str, "%02d%s", gEeprom.SCAN_LIST_DEFAULT, gEeprom.SCAN_LIST_ENABLED ? "+" : "");
end = gEeprom.SCAN_LIST_ENABLED ? 14 : 10;
sprintf(str, "%.3s", name);
end = 14;
} else {
sprintf(str, "%02d", gEeprom.SCAN_LIST_DEFAULT);
end = 10;
}
}
if (gEeprom.SCAN_LIST_ENABLED) {
strcat(str, "+");
end += 4;
}
#ifdef ENABLE_FEAT_F4HWN
GUI_DisplaySmallestInverse(str, 2, 0, true, true, end);
#else
GUI_DisplaySmallest(str, 2, 1, true, true);
gStatusLine[0] ^= 0x3E;
@@ -125,6 +169,7 @@ void UI_DisplayStatus()
gStatusLine[x] ^= 0x7F;
}
gStatusLine[end] ^= 0x3E;
#endif
}
else { // frequency mode
memcpy(line + x + 1, gFontS, sizeof(gFontS));
@@ -154,49 +199,49 @@ void UI_DisplayStatus()
#endif
if(!SCANNER_IsScanning()) {
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
if(gCurrentFunction == FUNCTION_TRANSMIT && gSetting_set_tmr == true)
{
convertTime(line, 0);
}
else if(FUNCTION_IsRx() && gSetting_set_tmr == true)
{
convertTime(line, 1);
}
else
#endif
{
if(!gAirCopyBootMode) {
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(gEeprom.MENU_LOCK == true) {
memcpy(line + x + 2, gFontRO, sizeof(gFontRO));
if(!gAirCopyBootMode) {
const void *src = NULL; // Pointer to the font/bitmap to copy
size_t sSize = 0; // Size of the font/bitmap
uint8_t sOff = 2; // Offset relative to the reference position
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if (gEeprom.MENU_LOCK) {
src = gFontRO;
sSize = sizeof(gFontRO);
} else
#endif
{
uint8_t xb = (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF);
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) {
if (gDualWatchActive) { // DWR - dual watch + respond
src = gFontDWR;
sOff = xb ? 2 : 0;
sSize = sizeof(gFontDWR) - (xb ? 5 : 0);
} else {
src = gFontHold;
sOff = 3;
sSize = sizeof(gFontHold);
}
} else {
src = xb ? gFontXB : gFontMO; // XB - crossband
sSize = xb ? sizeof(gFontXB) : sizeof(gFontMO); // MO - main only
}
else
{
#endif
uint8_t dw = (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) + (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF) * 2;
if(dw == 1 || dw == 3) { // DWR - dual watch + respond
if(gDualWatchActive)
memcpy(line + x + (dw==1?0:2), gFontDWR, sizeof(gFontDWR) - (dw==1?0:5));
else
memcpy(line + x + 3, gFontHold, sizeof(gFontHold));
}
else if(dw == 2) { // XB - crossband
memcpy(line + x + 2, gFontXB, sizeof(gFontXB));
}
else
{
memcpy(line + x + 2, gFontMO, sizeof(gFontMO));
}
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
}
#endif
}
// Perform the memcpy if a source was selected
if (src) {
memcpy(line + x + sOff, src, sSize);
}
}
}
x += sizeof(gFontDWR) + 3;
#endif
#if defined(ENABLE_FEAT_F4HWN_RX_TX_TIMER) && !defined(ENABLE_FEAT_F4HWN_DEBUG)
}
#endif
#ifdef ENABLE_VOX
// VOX indicator
if (gEeprom.VOX_SWITCH) {
@@ -243,8 +288,19 @@ void UI_DisplayStatus()
}
#endif
else if (gBackLight) {
src = gFontLight;
size = sizeof(gFontLight);
// Distinguish the manual backlight sub-state: BACKLIGHT_TIME == 0
// means the light is currently off (hollow bulb), otherwise it is
// forced on (bulb with filament).
if (gEeprom.BACKLIGHT_TIME == 0) {
src = gFontLightOff;
size = sizeof(gFontLightOff);
}
else
{
src = gFontLight;
size = sizeof(gFontLight);
}
}
#ifdef ENABLE_FEAT_F4HWN_CHARGING_C
else if (gChargingWithTypeC) {
@@ -263,38 +319,35 @@ void UI_DisplayStatus()
UI_DrawBattery(line + x2, gBatteryDisplayLevel, gLowBatteryBlink);
bool BatTxt = true;
switch (gSetting_battery_text) {
default:
case 0:
BatTxt = false;
break;
case 1: // voltage
const uint16_t voltage = (gBatteryVoltageAverage <= 999) ? gBatteryVoltageAverage : 999; // limit to 9.99V
sprintf(str, "%u.%02u", voltage / 100, voltage % 100);
break;
case 1: // voltage
case 2: // percentage
//gBatteryVoltageAverage = 999;
sprintf(str, "%02u%%", BATTERY_VoltsToPercent(gBatteryVoltageAverage));
if (gSetting_battery_text == 1) {
const uint16_t voltage = MIN(gBatteryVoltageAverage, 999); // limit to 9.99V
sprintf(str, "%u.%02u", voltage / 100, voltage % 100);
} else {
//gBatteryVoltageAverage = 999;
sprintf(str, "%02u%%", BATTERY_VoltsToPercent(gBatteryVoltageAverage));
}
x2 -= (7 * strlen(str));
UI_PrintStringSmallBufferNormal(str, line + x2);
/*
uint8_t shift = (strlen(str) < 5) ? 92 : 88;
GUI_DisplaySmallest(str, shift, 1, true, true);
for (uint8_t i = shift - 2; i < 110; i++) {
gStatusLine[i] ^= 0x7F; // invert
}
*/
break;
}
if (BatTxt) {
x2 -= (7 * strlen(str));
UI_PrintStringSmallBufferNormal(str, line + x2);
/*
uint8_t shift = (strlen(str) < 5) ? 92 : 88;
GUI_DisplaySmallest(str, shift, 1, true, true);
for (uint8_t i = shift - 2; i < 110; i++) {
gStatusLine[i] ^= 0x7F; // invert
}
*/
}
// **************
ST7565_BlitStatusLine();
+5 -5
View File
@@ -30,8 +30,8 @@
#ifdef ENABLE_FMRADIO
#include "ui/fmradio.h"
#endif
#ifdef ENABLE_REGA
#include "app/rega.h"
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#include "ui/inputbox.h"
#include "ui/main.h"
@@ -48,7 +48,7 @@ bool gAskToSave;
bool gAskToDelete;
void (*UI_DisplayFunctions[])(void) = {
void (*const UI_DisplayFunctions[])(void) = {
[DISPLAY_MAIN] = &UI_DisplayMain,
[DISPLAY_MENU] = &UI_DisplayMenu,
[DISPLAY_SCANNER] = &UI_DisplayScanner,
@@ -61,8 +61,8 @@ void (*UI_DisplayFunctions[])(void) = {
[DISPLAY_AIRCOPY] = &UI_DisplayAircopy,
#endif
#ifdef ENABLE_REGA
[DISPLAY_REGA] = &UI_DisplayREGA,
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
[DISPLAY_RXTX_LOG] = &UI_DisplayRxTxLog,
#endif
};
+2 -2
View File
@@ -34,8 +34,8 @@ enum GUI_DisplayType_t
DISPLAY_AIRCOPY,
#endif
#ifdef ENABLE_REGA
DISPLAY_REGA,
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
DISPLAY_RXTX_LOG,
#endif
DISPLAY_N_ELEM,
+177 -27
View File
@@ -20,6 +20,7 @@
#include "driver/py25q16.h"
#include "driver/st7565.h"
#include "external/printf/printf.h"
#include "font.h"
#include "helper/battery.h"
#include "settings.h"
#include "misc.h"
@@ -29,8 +30,82 @@
#include "version.h"
#include "bitmaps.h"
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
#include "screenshot.h"
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
#include "k5viewer.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO
// Boot logo storage in PY25Q16 external flash, aligned on a 4 KB sector,
// placed past the calibration zone (0x010000-0x010200).
//
// Layout inside the sector starting at LOGO_FLASH_ADDR:
// [0x00..0x07] : 8-byte header (reserved for future magic/version/flags)
// [0x08..0x407]: 128x64 monochrome bitmap (1024 B)
// ST7565-native: 8 pages * 128 columns, column-major LSB-top
#define LOGO_FLASH_ADDR 0x011000
#define LOGO_HEADER_SIZE 8
#define LOGO_BITMAP_ADDR (LOGO_FLASH_ADDR + LOGO_HEADER_SIZE)
static void UI_LoadLogo(void)
{
// Skip 8-byte header, then read 128x64 bitmap (1024 B):
// page 0 -> gStatusLine, pages 1..7 -> gFrameBuffer.
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR, gStatusLine, sizeof(gStatusLine));
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR + sizeof(gStatusLine), gFrameBuffer, sizeof(gFrameBuffer));
}
void UI_DisplayLogo(void)
{
UI_LoadLogo();
ST7565_BlitStatusLine();
ST7565_BlitFullScreen();
}
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// QR code (version 4, 33x33 modules, EC level L) encoding:
// https://github.com/armel/uv-k1-k5v3-firmware-custom
// Stored in framebuffer column-major format: 5 fb-lines x 33 columns.
// Each byte packs 8 vertical pixels (bit 0 = top). Last fb-line uses
// only bit 0 (row 32); bits 1..7 are always 0.
//
// static const uint8_t BITMAP_QR_GitHub[5][33] = {
// { 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0xB8, 0xCB, 0xA0, 0x6D, 0x07, 0xCB, 0x1F, 0xD2, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F },
// { 0x87, 0xA3, 0x69, 0xC3, 0x19, 0x0E, 0x55, 0x1F, 0x43, 0x11, 0x16, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22 },
// { 0xE0, 0x0B, 0x1D, 0x28, 0xF5, 0x87, 0x55, 0xEB, 0xA8, 0x11, 0xA3, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22 },
// { 0xFD, 0x04, 0x75, 0x75, 0x75, 0x04, 0xFD, 0x01, 0xF7, 0xE0, 0xD6, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x2C, 0x2B },
// { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00 }
// };
static const uint8_t BITMAP_QR_GitHub_Compressed[137] = {
0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0xB8, 0xCB, 0xA0, 0x6D, 0x07, 0xCB, 0x1F, 0xD2, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F,
0x87, 0xA3, 0x69, 0xC3, 0x19, 0x0E, 0x55, 0x1F, 0x43, 0x11, 0x16, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22,
0xE0, 0x0B, 0x1D, 0x28, 0xF5, 0x87, 0x55, 0xEB, 0xA8, 0x11, 0xA3, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22,
0xFD, 0x04, 0x75, 0x75, 0x75, 0x04, 0xFD, 0x01, 0xF7, 0xE0, 0xD6, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x2C, 0x2B,
0x7F, 0x09, 0x8C, 0xA3, 0x00
};
// QR code (version 4, 33x33 modules, EC level L) encoding:
// https://github.com/armel/uv-k1-k5v3-firmware-custom/wiki
// Stored in framebuffer column-major format: 5 fb-lines x 33 columns.
// Last fb-line uses only bit 0 (row 32); bits 1..7 are always 0.
//
// static const uint8_t BITMAP_QR_GitHub_Wiki[5][33] = {
// { 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0x0F, 0x74, 0x0E, 0xD2, 0xB0, 0x74, 0xB1, 0x6D, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F },
// { 0xCD, 0x5D, 0x83, 0x65, 0xE7, 0xC6, 0x55, 0xBD, 0x6B, 0x3F, 0xA9, 0x1C, 0xA5, 0xE0, 0x69, 0xE3, 0x4A, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22 },
// { 0xFF, 0x25, 0xAE, 0xB6, 0x30, 0xF8, 0x55, 0xDD, 0x07, 0xB6, 0xC2, 0x1C, 0xA5, 0xE0, 0x69, 0xC4, 0x66, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22 },
// { 0xFD, 0x04, 0x74, 0x74, 0x74, 0x05, 0xFD, 0x01, 0xF7, 0xAE, 0x81, 0x1C, 0xA5, 0xE0, 0x69, 0x54, 0xFE, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x24, 0x33 },
// { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00 }
// };
static const uint8_t BITMAP_QR_GitHub_Wiki_Compressed[137] = {
0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0x0F, 0x74, 0x0E, 0xD2, 0xB0, 0x74, 0xB1, 0x6D, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F,
0xCD, 0x5D, 0x83, 0x65, 0xE7, 0xC6, 0x55, 0xBD, 0x6B, 0x3F, 0xA9, 0x1C, 0xA5, 0xE0, 0x69, 0xE3, 0x4A, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22,
0xFF, 0x25, 0xAE, 0xB6, 0x30, 0xF8, 0x55, 0xDD, 0x07, 0xB6, 0xC2, 0x1C, 0xA5, 0xE0, 0x69, 0xC4, 0x66, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22,
0xFD, 0x04, 0x74, 0x74, 0x74, 0x05, 0xFD, 0x01, 0xF7, 0xAE, 0x81, 0x1C, 0xA5, 0xE0, 0x69, 0x54, 0xFE, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x24, 0x33,
0x7F, 0x53, 0x8E, 0xA3, 0x00
};
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
@@ -88,11 +163,62 @@ static inline uint16_t pct_x100(uint32_t used, uint32_t total)
{
return (uint16_t)((used * 10000u) / total); // 7559 => 75.59%
}
void UI_GetMemPercents(uint16_t *flash_pct_x100, uint16_t *ram_pct_x100)
{
uint32_t ram_used = 0;
uint32_t flash_used = 0;
build_usage(&ram_used, &flash_used);
if (flash_pct_x100) *flash_pct_x100 = pct_x100(flash_used, FLASH_SIZE_BYTES);
if (ram_pct_x100) *ram_pct_x100 = pct_x100(ram_used, RAM_SIZE_BYTES);
}
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Set a single pixel at LCD-physical (x, y). y=0..7 maps to gStatusLine,
// y=8..63 maps to gFrameBuffer (line = (y-8)/8, bit = (y-8)%8).
static void QR_SetPixel(uint8_t x, uint8_t y)
{
if (x >= 128 || y >= 64) return;
if (y < 8) {
gStatusLine[x] |= (uint8_t)(1u << y);
} else {
const uint8_t fb_y = (uint8_t)(y - 8u);
gFrameBuffer[fb_y >> 3][x] |= (uint8_t)(1u << (fb_y & 7u));
}
}
// Render a square QR bitmap stored in framebuffer column-major format
// (size cols × ceil(size/8) fb-lines, row-major in memory).
static void QR_Draw(const uint8_t *bitmap, uint8_t size, uint8_t origin_x, uint8_t origin_y)
{
for (uint8_t qy = 0; qy < size; qy++) {
for (uint8_t qx = 0; qx < size; qx++) {
// const uint16_t idx = (uint16_t)(qy >> 3) * (uint16_t)size + (uint16_t)qx;
// if ((bitmap[idx] >> (qy & 7u)) & 1u) {
if (qy < 32 ?
((bitmap[(uint16_t)(qy >> 3) * (uint16_t)size + (uint16_t)qx] >> (qy & 7u)) & 1u) :
((bitmap[132 + (qx >> 3)] >> (qx & 7u)) & 1u)) {
QR_SetPixel((uint8_t)(origin_x + qx),
(uint8_t)(origin_y + qy));
}
}
}
}
void UI_DrawQRCode(bool wiki, uint8_t origin_x, uint8_t origin_y)
{
// QR_Draw(wiki ? (const uint8_t *)BITMAP_QR_GitHub_Wiki
// : (const uint8_t *)BITMAP_QR_GitHub,
QR_Draw(wiki ? (const uint8_t *)BITMAP_QR_GitHub_Wiki_Compressed
: (const uint8_t *)BITMAP_QR_GitHub_Compressed,
33, origin_x, origin_y);
}
#endif
void UI_DisplayReleaseKeys(void)
{
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
#if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV)
ST7565_ContrastAndInv();
#endif
@@ -107,12 +233,7 @@ void UI_DisplayReleaseKeys(void)
void UI_DisplayWelcome(void)
{
char WelcomeString0[16];
char WelcomeString1[16];
char WelcomeString2[16];
char WelcomeString3[32];
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
#if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV)
ST7565_ContrastAndInv();
@@ -122,16 +243,27 @@ void UI_DisplayWelcome(void)
#ifdef ENABLE_FEAT_F4HWN
ST7565_BlitStatusLine();
ST7565_BlitFullScreen();
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_NONE || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND) {
ST7565_FillScreen(0x00);
return;
}
#else
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_NONE || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_FULL_SCREEN) {
ST7565_FillScreen(0xFF);
return;
}
#endif
} else {
memset(WelcomeString0, 0, sizeof(WelcomeString0));
memset(WelcomeString1, 0, sizeof(WelcomeString1));
#ifdef ENABLE_FEAT_F4HWN_LOGO
else if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_LOGO) {
UI_LoadLogo();
}
#endif
else {
char WelcomeString0[16];
char WelcomeString1[16];
char WelcomeString2[16];
char WelcomeString3[32];
// 0x0EB0
PY25Q16_ReadBuffer(0x00A0C8, WelcomeString0, 16);
@@ -181,18 +313,35 @@ void UI_DisplayWelcome(void)
UI_PrintString(WelcomeString1, 0, 127, 2, 10);
#ifdef ENABLE_FEAT_F4HWN
UI_PrintStringSmallNormal(Version, 0, 128, 4);
const size_t version_width = strlen(DisplayVersion) * (ARRAY_SIZE(gFontSmall[0]) + 1u);
const uint8_t version_x = version_width < LCD_WIDTH
? (uint8_t)((LCD_WIDTH - version_width + 1u) / 2u)
: 0u;
const uint8_t capsule_left = version_x > 2u ? (uint8_t)(version_x - 3u) : 0u;
const size_t capsule_right_candidate = version_x + version_width + 2u;
const uint8_t capsule_right = capsule_right_candidate < LCD_WIDTH
? (uint8_t)capsule_right_candidate
: (LCD_WIDTH - 1u);
UI_DrawLineBuffer(gFrameBuffer, 0, 35, 18, 35, 1);
gFrameBuffer[4][19] ^= 0x7F;
for (uint8_t x = 20; x < 108; x++)
UI_PrintStringSmallNormal(DisplayVersion, version_x, 0, 4);
if (capsule_left > 0u)
{
UI_DrawLineBuffer(gFrameBuffer, 0, 35, capsule_left - 1u, 35, 1);
}
gFrameBuffer[4][capsule_left] ^= 0x7F;
for (uint8_t x = capsule_left + 1u; x < capsule_right; x++)
{
gFrameBuffer[4][x] ^= 0xFF;
gFrameBuffer[3][x] ^= 0x80;
}
gFrameBuffer[4][108] ^= 0x7F;
UI_DrawLineBuffer(gFrameBuffer, 109, 35, 127, 35, 1);
gFrameBuffer[4][capsule_right] ^= 0x7F;
if (capsule_right < LCD_WIDTH - 1u)
{
UI_DrawLineBuffer(gFrameBuffer, capsule_right + 1u, 35, LCD_WIDTH - 1u, 35, 1);
}
/*
#ifdef ENABLE_FEAT_F4HWN_MEM
uint32_t ram_used = 0;
uint32_t flash_used = 0;
@@ -210,6 +359,7 @@ void UI_DisplayWelcome(void)
GUI_DisplaySmallest(WelcomeString3, 5, 1, true, true);
ST7565_BlitStatusLine();
#endif
*/
sprintf(WelcomeString3, "%s Edition", Edition);
UI_PrintStringSmallNormal(WelcomeString3, 0, 127, 6);
@@ -217,12 +367,12 @@ void UI_DisplayWelcome(void)
#else
UI_PrintStringSmallNormal(Version, 0, 127, 6);
#endif
//ST7565_BlitStatusLine(); // blank status line : I think it's useless
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
SCREENSHOT_Update(true);
#endif
}
}
ST7565_BlitStatusLine();
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
K5VIEWER_Update(true);
#endif
}
+17
View File
@@ -17,8 +17,25 @@
#ifndef UI_WELCOME_H
#define UI_WELCOME_H
#include <stdbool.h>
#include <stdint.h>
void UI_DisplayReleaseKeys(void);
void UI_DisplayWelcome(void);
#ifdef ENABLE_FEAT_F4HWN_LOGO
void UI_DisplayLogo(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Draw the embedded GitHub QR code (V4, 33x33) at (origin_x, origin_y).
// `wiki` selects the wiki URL bitmap, otherwise the repo URL bitmap.
void UI_DrawQRCode(bool wiki, uint8_t origin_x, uint8_t origin_y);
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
// Compute current FLASH and SRAM usage as percentages × 100 (e.g. 7559 = 75.59%).
void UI_GetMemPercents(uint16_t *flash_pct_x100, uint16_t *ram_pct_x100);
#endif
#endif
+6 -2
View File
@@ -6,8 +6,12 @@
#endif
#ifdef ENABLE_FEAT_F4HWN
const char Version[] = AUTHOR_STRING_2 " " VERSION_STRING_2;
const char Edition[] = EDITION_STRING;
const char Version[] = AUTHOR_STRING_2 " " VERSION_STRING_2;
const char DisplayVersion[] = AUTHOR_STRING_2 " " DISPLAY_VERSION_STRING_2;
const char Edition[] = EDITION_STRING;
const char BuildDate[] = __DATE__;
const char BuildTime[] = __TIME__;
const char BuildCommit[] = BUILD_COMMIT;
#else
const char Version[] = AUTHOR_STRING VER;
#endif
+4
View File
@@ -23,5 +23,9 @@ extern const char UART_Version[];
#endif
#ifdef ENABLE_FEAT_F4HWN
extern const char DisplayVersion[];
extern const char Edition[];
extern const char BuildDate[];
extern const char BuildTime[];
extern const char BuildCommit[];
#endif
+38
View File
@@ -19,6 +19,23 @@ message("Build type: " ${CMAKE_BUILD_TYPE})
enable_language(C ASM)
option(DEV "Prepare the rolling Fusion development firmware" OFF)
find_package(Git QUIET)
set(BUILD_COMMIT "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE BUILD_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT BUILD_COMMIT)
set(BUILD_COMMIT "unknown")
endif()
endif()
if(ENABLE_FEAT_F4HWN)
if(NOT AUTHOR_STRING_1)
set(AUTHOR_STRING_1 "EGZUMER")
@@ -56,7 +73,17 @@ else()
set(EXE_NAME "firmware")
endif()
if(DEV)
if(NOT "${TARGET}" STREQUAL "f4hwn.fusion")
message(FATAL_ERROR "DEV is only supported for the Fusion target")
endif()
set(DISPLAY_VERSION_STRING_2 "${VERSION_STRING_2} Dev")
else()
set(DISPLAY_VERSION_STRING_2 "${VERSION_STRING_2}")
endif()
message("EXE_NAME = " ${EXE_NAME})
message("Development build = " ${DEV})
add_executable(${EXE_NAME})
@@ -95,6 +122,17 @@ add_custom_command(
COMMENT "Generating .bin file"
)
if(DEV)
add_custom_command(
TARGET ${EXE_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/${EXE_NAME}.bin"
"${CMAKE_SOURCE_DIR}/archive/f4hwn.fusion.development.bin"
COMMENT "Preparing archive/f4hwn.fusion.development.bin"
)
endif()
# Generate .hex file
add_custom_command(
TARGET ${EXE_NAME}
+36 -11
View File
@@ -9,6 +9,7 @@
"toolchainFile": "${sourceDir}/cmake/gcc-arm-none-eabi.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"DEV": false,
"ENABLE_LTO": false,
"ENABLE_FMRADIO": false,
"ENABLE_UART": true,
@@ -34,7 +35,7 @@
"ENABLE_BOOT_BEEPS": false,
"ENABLE_SHOW_CHARGE_LEVEL": false,
"ENABLE_REVERSE_BAT_SYMBOL": false,
"ENABLE_NO_CODE_SCAN_TIMEOUT": true,
"ENABLE_NO_CODE_SCAN_TIMEOUT": false,
"ENABLE_SQUELCH_MORE_SENSITIVE": true,
"ENABLE_FASTER_CHANNEL_SCAN": true,
"ENABLE_RSSI_BAR": true,
@@ -44,11 +45,10 @@
"ENABLE_BYP_RAW_DEMODULATORS": false,
"ENABLE_BLMIN_TMP_OFF": false,
"ENABLE_SCAN_RANGES": true,
"ENABLE_REGA": false,
"ENABLE_EXTRA_UART_CMD": false,
"ENABLE_FEAT_F4HWN": true,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_SCREENSHOT": false,
"ENABLE_FEAT_F4HWN_K5VIEWER": false,
"ENABLE_FEAT_F4HWN_SPECTRUM": true,
"ENABLE_FEAT_F4HWN_RX_TX_TIMER": true,
"ENABLE_FEAT_F4HWN_CHARGING_C": false,
@@ -66,12 +66,24 @@
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": false,
"ENABLE_FEAT_F4HWN_CA": true,
"ENABLE_FEAT_F4HWN_DEBUG": false,
"ENABLE_FEAT_F4HWN_QRCODE": false,
"ENABLE_FEAT_F4HWN_MEM": false,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
"ENABLE_FEAT_F4HWN_SCAN_FASTER": false,
"ENABLE_FEAT_F4HWN_SCAN_RSSI": false,
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": false,
"ENABLE_FEAT_F4HWN_BEAM": false,
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": false,
"ENABLE_FEAT_F4HWN_LOGO": false,
"ENABLE_FEAT_F4HWN_LOGO_SAV": false,
"ENABLE_AGC_SHOW_DATA": false,
"ENABLE_UART_RW_BK_REGS": false,
"ENABLE_SWD": false,
"VERSION_STRING_1": "v0.22",
"VERSION_STRING_2": "v5.3.1"
"VERSION_STRING_2": "v5.8.0"
}
},
{
@@ -90,7 +102,7 @@
"ENABLE_FMRADIO": false,
"ENABLE_VOX": false,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_PMR": true,
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
@@ -107,7 +119,7 @@
"ENABLE_FMRADIO": true,
"ENABLE_VOX": true,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_PMR": true,
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
@@ -124,7 +136,7 @@
"ENABLE_FMRADIO": true,
"ENABLE_VOX": false,
"ENABLE_AIRCOPY": false,
"ENABLE_FEAT_F4HWN_SCREENSHOT": false,
"ENABLE_FEAT_F4HWN_K5VIEWER": false,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_SPECTRUM": false,
"ENABLE_FEAT_F4HWN_PMR": true,
@@ -148,7 +160,7 @@
"ENABLE_FMRADIO": false,
"ENABLE_VOX": true,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_PMR": true,
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
@@ -166,7 +178,7 @@
"ENABLE_FMRADIO": true,
"ENABLE_VOX": false,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_GAME": true,
"ENABLE_FEAT_F4HWN_PMR": true,
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
@@ -183,7 +195,7 @@
"ENABLE_FMRADIO": true,
"ENABLE_VOX": true,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_GAME": true,
"ENABLE_FEAT_F4HWN_PMR": true,
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
@@ -191,6 +203,19 @@
"ENABLE_FEAT_F4HWN_VOL": true,
"ENABLE_FEAT_F4HWN_AUDIO": true,
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
"ENABLE_FEAT_F4HWN_SCAN_RSSI": true,
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
"ENABLE_FEAT_F4HWN_BEAM": true,
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
"ENABLE_FEAT_F4HWN_QRCODE": true,
"ENABLE_FEAT_F4HWN_MEM": true,
"ENABLE_FEAT_F4HWN_LOGO": true,
"ENABLE_FEAT_F4HWN_LOGO_SAV": true,
"ENABLE_SWD": true,
"EDITION_STRING": "Fusion",
"TARGET": "f4hwn.fusion"
@@ -227,4 +252,4 @@
"configurePreset": "Fusion"
}
]
}
}
+77
View File
@@ -0,0 +1,77 @@
F4HWN Edition Fusion firmware for Quansheng UV-K1 and UV-K5 V3
================================================================
This product includes software originally developed by Dual Tachyon
for the Quansheng UV-K5 open-source firmware project:
https://github.com/DualTachyon/uv-k5-firmware
This product includes work derived from Egzumer's UV-K5 custom
firmware:
https://github.com/egzumer/uv-k5-firmware-custom
This product includes work developed by Armel / F4HWN and contributors
for the F4HWN firmware projects, including the F4HWN Edition Fusion
port for UV-K1 and UV-K5 V3 radios:
https://github.com/armel/uv-k5-firmware-custom
https://github.com/armel/uv-k1-k5v3-firmware-custom
The UV-K1 and UV-K5 V3 port is the result of joint work by muzkr and
Armel / F4HWN.
Additional attribution notices
------------------------------
This product includes contributions and/or derived work from, among
others:
Dual Tachyon
OneOfEleven
fagci
Egzumer
muzkr
Armel / F4HWN
Project-specific derived components
-----------------------------------
The spectrum analyzer implementation includes work originally developed
by fagci:
https://github.com/fagci
Third-party components
----------------------
This product includes CherryUSB by sakumisu, licensed under the
Apache License, Version 2.0. Its license is included at:
Middlewares/CherryUSB/LICENSE
This product includes CMSIS Version 5 by Arm, licensed under the
Apache License, Version 2.0. Its license is included at:
App/external/CMSIS_5/LICENSE.txt
This product includes the embedded printf implementation by Marco
Paland, licensed under the MIT License. Its license is included at:
App/external/printf/LICENSE
License and endorsement notice
------------------------------
This NOTICE file is provided for attribution purposes only and does not
modify the terms of any applicable license.
The main firmware is licensed under the Apache License, Version 2.0.
Redistributions of this product or derivative works, in source or object
form, must comply with the Apache License, Version 2.0, including its
conditions on license copies, modified-file notices, retained copyright
and attribution notices, and NOTICE files.
Attribution in this file does not imply endorsement by Dual Tachyon,
OneOfEleven, fagci, Egzumer, muzkr, Armel / F4HWN or any other
contributor.
+259 -164
View File
@@ -31,144 +31,243 @@ Anyway, have fun.
> _FR - CE FIRMWARE N'A PAS DE VÉRITABLE CERVEAU. VEUILLEZ UTILISER LE VÔTRE. Utilisez ce firmware à vos risques et périls. Il n'y a absolument aucune garantie qu'il fonctionnera d'une manière ou d'une autre sur votre (vos) radio(s), il peut même bousiller votre (vos) radio(s), dans ce cas, vous devrez acheter une autre radio. Quoi qu'il en soit, amusez-vous bien._
> [!NOTE]
> EN - About Chirp, as many others firmwares, you need to use a dedicated driver available on [this repository](https://github.com/armel/uv-k5-chirp-driver).
> EN - About CHIRP, as with many other firmwares, you need to use a dedicated driver. The matching CHIRP driver is now bundled with each release of this repository, so you can download the firmware and its driver together from the [Releases page](https://github.com/armel/uv-k1-k5v3-firmware-custom/releases).
>
> _FR - A propos de Chirp, comme beaucoup d'autres firmwares, vous devez utiliser un pilote dédié disponible sur [ce dépôt](https://github.com/armel/uv-k5-chirp-driver)._
> _FR - A propos de CHIRP, comme pour beaucoup d'autres firmwares, vous devez utiliser un pilote dédié. Le driver CHIRP correspondant est désormais fourni avec chaque release de ce dépôt, ce qui permet de récupérer ensemble le firmware et son pilote depuis la page des [Releases](https://github.com/armel/uv-k1-k5v3-firmware-custom/releases)._
> [!CAUTION]
> EN - I recommend to backup your calibration data with [uvtools2](https://armel.github.io/uvtools2/) just after flashing this firmware. It's a good reflex to have.
> EN - I recommend backing up your calibration data with [UV Studio](https://armel.github.io/uvstudio/#dump-calib) immediately after flashing this firmware. It is a good habit to adopt.
>
> _FR - Je recommande de sauvegarder vos données de calibration avec [uvtools2](https://armel.github.io/uvtools2/) juste après avoir flashé ce firmware. C'est un bon réflexe à avoir._
> _FR - Je recommande de sauvegarder vos données de calibration avec [UV Studio](https://armel.github.io/uvstudio/#dump-calib) juste après avoir flashé ce firmware. C'est un bon réflexe à adopter._
# Donations
Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR, Julia DF7JA, Ken 2E0UMK, Victor TI2SYS, Tobi DG9LAY, Deaglan K4DFQ, Catherine PALMER and Brian WA6JFK for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻
Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C / F4WFS (3 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR, Julia DF7JA, Ken 2E0UMK, Victor TI2SYS, Tobi DG9LAY, Deaglan K4DFQ, Catherine PALMER, Brian WA6JFK, Stéphane Hintzy, Roger F1HCN, Marcin Kusaj, Flavio Cottarelli, Bob N1MLZ, Carlos EA1IJ, Brian M7YLF, Giuseppe IT9LLH and 邓 月 for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻
## Table of Contents
* [My Features](#main-features)
* [Main features and improvements from F4HWN](#main-features-and-improvements-from-f4hwn)
* [Main Features from Egzumer](#main-features-from-egzumer)
* [Manual](#manual)
* [Compiling and Building from Docker](#compiling-and-Building-from-docker)
* [Flashing the Firmware with UVTools2](#flashing-the-firmware-with-uvtools2)
* [Flashing the Firmware with UV Studio](#flashing-the-firmware-with-uv-studio)
* [Credits](#credits)
* [Other sources of information](#other-sources-of-information)
* [License](#license)
## Main features and improvements from F4HWN:
## Main features and improvements from F4HWN
* several firmware versions:
* Bandscope (with spectrum analyzer made by Fagci),
* Broadcast (with commercial FM radio support),
* Basic (with spectrum analyzer and commercial FM radios support, but without certain functions such as Vox, Aircopy, etc.),
* RescueOps (specifically designed for first responders: firefighters, sea rescue, mountain rescue),
* Game (with a small breakout game),
* improve default power settings level:
* Low1 to Low5 (<~20mW, ~125mW, ~250mW, ~500mW, ~1W),
* Mid ~2W,
* High ~5W,
* User (see SetPwr),
* improve S-Meter (IARU Region 1 Technical Recommendation R.1 for VHF/UHF - [read more](https://hamwaves.com/decibel/en/)),
* S-Meter (S0/S9) Level EEPROM settings that were introduced in the Egzumer firmware are now ignored and replaced by hardcoded values to comply with the IARU Recommendation.
* improve bandscope (Spectrum Analyser):
* add channel name,
* add save of some spectrum parameters,
* improve UI:
* menu index is always visible, even if a menu is selected,
* s-meter new design (Classic or Tiny),
* MAIN ONLY screen mode,
* DUAL and CROSS screen mode,
* RX blink on VFO RX,
* RX LED blink,
* Squelch level and Monitor,
* Step value,
* CTCSS or DCS value,
* KeyLock message,
* last RX,
* move BatTxt menu from 34/63 to 30/63 (just after BatSave menu 29/63),
* rename BackLt to BLTime,
* rename BltTRX to BLTxRx,
* improve memory channel input,
* improve keyboard frequency input,
* add percent and gauge to Air Copy,
* improve audio bar,
* and more...
* new menu entries and changes:
* add SetPwr menu to set User power (<20mW, 125mW, 250mW, 500mW, 1W, 2W or 5W),
* add SetPTT menu to set PTT mode (Classic or OnePush),
* add SetTOT menu to set TOT alert (Off, Sound, Visual, All),
* add SetCtr menu to set contrast (0 to 15),
* add SetInv menu to set screen in invert mode (Off or On),
* add SetEOT menu to set EOT (End Of Transmission) alert (Off, Sound, Visual, All),
* add SetMet menu to set s-meter style (Classic or Tiny),
* add SetLck menu to set what is locked (Keys or Keys + PTT),
* add SetGUI menu to set font size on the VFO baseline (Classic or Tiny),
* add TXLock menu to open TX on channel,
* add SetTmr menu to set RX and TX timers (Off or On),
* add SetOff menu to set the delay before the transceiver goes into deep sleep (Off or 1 minute to 2 hours),
* add SetNFM menu to set Narrow width (12.5kHz or 6.25kHz),
* rename BatVol menu (52/63) to SysInf, which displays the firmware version in addition to the battery status,
* improve PonMsg menu,
* improve BackLt menu,
* improve TxTOut menu,
* improve ScnRev menu (CARRIER from 250ms to 20s, STOP, TIMEOUT from 5s to 2m)
* improve KeyLck menu (OFF, delay from 15s to 10m)
* add HAM CA F Lock band (for Canadian zone),
* add PMR 446 F Lock band,
* add FRS/GMRS/MURS F Lock band,
* remove blink and SOS functionality,
* remove AM Fix menu (AM Fix is ENABLED by default),
* add support of 3500mAh battery,
* improve status bar:
* add SetPtt mode in status bar,
* change font and bitmaps,
* move USB icon to left of battery information,
* add RX and TX timers,
* improve lists and scan lists options:
* add new list 3,
* add new list 0 (channel without list...),
* add new scan lists options,
* scan list 0 (all channels without list),
* scan list 1,
* scan list 2,
* scan list 3,
* scan lists [1, 2, 3],
* scan all (all channels with or without list),
* add scan list shortcuts,
* add resume mode on startup (scan, bandscope and broadcast FM),
* new actions:
* RX MODE,
* MAIN ONLY,
* PTT,
* WIDE NARROW,
* 1750Hz,
* MUTE,
* POWER HIGH (RescueOps),
* REMOVE OFFSET (RescueOps),
* new key combinations:
* add the F + UP or F + DOWN key combination to dynamically change the Squelch level,
* add the F + F1 or F + F2 key combination to dynamically change the Step,
* add F + 8 to quickly switch backlight between BLMin and BLMax on demand (this bypass BackLt strategy),
* add F + 9 to return to BackLt strategy,
* add long press on MENU, in * SCAN mode, to temporarily exclude a memory channel,
* add short press on [0, 1, 2, 3, 4 or 5], in * SCAN mode, to dynamically change scan list.
* many fix:
* squelch,
* s-meter,
* DTMF overlaying,
* scan list 2 ignored,
* scan range limit,
* clean display on startup,
* no more PWM noise,
* and more...
* enabled AIR COPY
* disabled ENABLE_DTMF_CALLING,
* disabled SCRAMBLER,
* remove 200Tx, 350Tx and 500Tx,
* unlock TX on all bands needs only to be repeat 3 times,
* code refactoring and many memory optimization,
* displays the live screen of the Quansheng K5 on your computer via a USB-to-Serial cable,
* and more...
### Fusion edition
Fusion is the reference edition of the project. It provides an all-in-one firmware for the UV-K1 and UV-K5 V3, including:
- Fagci's spectrum analyzer,
- broadcast FM radio,
- VOX and AirCopy,
- BEAM wireless channel transfer,
- [UV Studio](https://armel.github.io/uvstudio/) with integrated K5Viewer screen mirroring, screenshots and remote keyboard control,
- advanced RX audio profiles and Audio Scope,
- first-responder-oriented controls,
- the Breakout game,
- automatic RX/TX activity logging with RF Log,
- full Fox Hunt and Morse Beacon support.
### Radio and signal handling
- Reworked output-power levels:
- `Low 1`: below approximately 20 mW,
- `Low 2`: approximately 125 mW,
- `Low 3`: approximately 250 mW,
- `Low 4`: approximately 500 mW,
- `Low 5`: approximately 1 W,
- `Mid`: approximately 2 W,
- `High`: approximately 5 W,
- `User`: configurable through `SetPwr`.
- S-meter calibrated according to the [IARU Region 1 recommendation for VHF/UHF](https://hamwaves.com/decibel/en/):
- fixed S0 to S9+ values replace the former EEPROM S-meter thresholds,
- Classic and Tiny display styles are available.
- Configurable 12.5 kHz or 6.25 kHz narrow-FM bandwidth.
- Per-channel TX lock.
- Adjustable RX audio volume.
- Advanced RX audio profiles for FM and AM reception.
- Regional frequency-lock profiles for amateur bands, PMR446, FRS, GMRS and MURS.
- Support for 1600, 2200 and 3500 mAh battery profiles.
### Spectrum analyzer
- Channel names displayed in the spectrum view.
- Persistent spectrum settings.
- Faster and smoother spectrum rendering.
- Improved behavior when freezing the spectrum or disconnecting USB-C.
- Spectrum analyzer state can be restored automatically at startup.
### Scanning
- Support for up to 24 named scan lists.
- Each memory channel can be assigned to:
- `OFF`,
- one scan list from `01` to `24`,
- `ALL`.
- The `ALL` list scans every channel except those assigned to `OFF`.
- Automatic selection of the next valid list when the requested list is empty.
- Direct scan-list selection while scanning:
- `00` selects `ALL`,
- `01` to `24` select the corresponding list.
- Long press on `MENU` while scanning to exclude the current memory channel.
- Up to 64 frequency exclusions.
- Very fast scanning mode, reaching approximately 150 frequencies per second.
- Scan progress, RSSI and detected CTCSS/DCS information.
- Configurable scan resume behavior.
- Scan state can be restored automatically at startup.
### User interface
- Improved VFO screen with:
- Classic and Tiny S-meter styles,
- Classic and Tiny frequency-information layouts,
- `MAIN ONLY`, `DUAL` and `CROSS` display modes,
- RX activity indication on the active VFO,
- optional RX LED blinking,
- squelch, monitor, step and CTCSS/DCS information,
- last-RX indication,
- RX and TX timers.
- Improved status bar with updated fonts and icons.
- Menu index remains visible while editing an entry.
- Improved frequency and memory-channel input.
- Improved audio-level display.
- AirCopy progress percentage and gauge.
- Smooth backlight fading.
- Manual backlight controls for quickly switching between minimum and maximum brightness.
- Configurable contrast and inverted-display mode.
- Configurable navigation layout for the different radio models.
- Improved power-on message and optional startup logo.
- System-information pages for:
- firmware version and build information,
- battery information,
- Flash and SRAM usage,
- project and documentation QR codes.
### Audio and transmission controls
- Classic and OnePush PTT modes.
- Configurable timeout-timer alerts:
- disabled,
- sound,
- visual,
- sound and visual.
- Configurable end-of-transmission alerts using the same modes.
- Audio Scope during RX and TX.
- Improved Audio Scope behavior with OnePush PTT, DTMF and the 1750 Hz tone.
- Configurable automatic deep-sleep timeout.
- Quick actions for:
- RX mode,
- main-VFO-only display,
- PTT,
- wide/narrow bandwidth,
- 1750 Hz tone,
- mute,
- RX audio profile,
- maximum power,
- offset removal.
### Fox Hunt and Beacon
- Dedicated Fox Hunt receiver with:
- calibrated S-meter display,
- scrolling signal-history graph,
- peak, minimum and trend indicators,
- selectable RF attenuation,
- silent, Geiger-style and received-audio modes.
- Integrated Morse Beacon transmitter with:
- `MOE`, `MOI`, `MOS`, `MOH`, `MO5` and `MO` identifiers,
- optional callsign identification,
- configurable TX and idle periods,
- live TX and idle countdowns,
- persisted Beacon settings,
- interactive control during transmission,
- TX-lock, modulation and battery-safety checks.
- Fox Hunt and Beacon screens are mirrored to UV Studio's integrated K5Viewer.
### RF Log
- Automatic logging of RX and TX activity when RF Log is enabled.
- Logs stored in the radio's external Flash memory.
- Recorded information includes:
- RX or TX direction,
- frequency and channel,
- channel name,
- activity duration,
- S-meter level,
- battery voltage.
- On-radio history with RX/TX filtering and detailed views.
- Live RF Log dashboard and history access through UV Studio.
### Connectivity and data transfer
- Live screen streaming to [UV Studio](https://armel.github.io/uvstudio/) over a USB serial connection.
- Screenshot capture and download.
- Remote keyboard control through the integrated K5Viewer.
- Automatic reconnection after a USB disconnect.
- RF Log monitoring, analytics and CSV export.
- Firmware flashing, calibration backup and restore, and boot-logo management from the same interface.
- BEAM transfer of complete channel settings between compatible radios.
- Improved AirCopy interface and progress reporting.
### Settings and controls
- New or extended menu entries:
- `SetPwr`: configurable User output power,
- `SetPTT`: Classic or OnePush PTT,
- `SetTOT`: timeout-timer alert,
- `SetEOT`: end-of-transmission alert,
- `SetCtr`: display contrast,
- `SetInv`: inverted display,
- `SetLck`: keypad or keypad-and-PTT lock,
- `SetMet`: S-meter style,
- `SetGUI`: VFO information style,
- `SetRxA`: RX audio profile,
- `SetTmr`: RX and TX timers,
- `SetOff`: automatic deep-sleep delay,
- `SetNFM`: narrow-FM bandwidth,
- `SetVol`: RX audio volume,
- `SetScn`: scan mode,
- `SetNav`: radio-specific navigation layout.
- Improved `PonMsg`, `BackLt`, `TxTOut`, `ScnRev` and `KeyLck` menus.
- Full VFO state restoration with a long press on `EXIT`.
- Squelch changes made with `F + UP` or `F + DOWN` are persisted.
### Keyboard shortcuts and assignable actions
- `F + UP` or `F + DOWN`: adjust the squelch level.
- `F + F1` or `F + F2`: adjust the frequency step.
- `F + 8`: temporarily switch the backlight between minimum and maximum brightness.
- `F + 9`: return to the configured backlight strategy.
- Configurable short- and long-press actions include:
- RX mode,
- main-VFO-only display,
- virtual PTT,
- wide/narrow bandwidth,
- 1750 Hz tone,
- mute,
- RX audio profile,
- maximum power,
- offset removal,
- BEAM,
- RF Log,
- Fox Hunt / Beacon.
### Reliability and optimization
- Improved squelch and S-meter behavior.
- Fixed DTMF overlay issues.
- Fixed scan-range limits.
- Cleaner startup display.
- Removed PWM-related audio noise.
- Improved serial and K5Viewer key handling.
- Improved VFO persistence and restoration.
- Extensive code refactoring and memory optimization.
- DTMF calling and the scrambler remain disabled in Fusion.
- Legacy AM Fix code is not used by this firmware.
## Main features from Egzumer:
* many of OneOfEleven mods:
@@ -203,7 +302,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
## Manual
Up to date manual is available in the [Wiki section](https://github.com/armel/uv-k5-firmware-custom/wiki)
Up to date manual is available in the [Wiki section](https://github.com/armel/uv-k1-k5v3-firmware-custom/wiki)
## Radio performance
@@ -211,21 +310,21 @@ Please note that the Quansheng UV-Kx radios are not professional quality transce
performance is strictly limited. The RX front end has no track-tuned band pass filtering
at all, and so are wide band/wide open to any and all signals over a large frequency range.
Using the radio in high intensity RF environments will most likely make reception anything but
easy (AM mode will suffer far more than FM ever will), the receiver simply doesn't have a
great dynamic range, which results in distorted AM audio with stronger RX'ed signals.
There is nothing more anyone can do in firmware/software to improve that, once the RX gain
adjustment I do (AM fix) reaches the hardwares limit, your AM RX audio will be all but
non-existent (just like Quansheng's firmware).
On the other hand, FM RX audio will/should be fine.
Using the radio in high intensity RF environments will most likely make reception difficult,
especially in AM mode. The receiver simply does not have a great dynamic range, so stronger
signals can easily cause distortion, desensitization and poor AM audio.
This is fundamentally a hardware limitation: firmware can improve behavior at the margins, but
it cannot overcome the front-end design of the radio.
In practice, AM reception will degrade first and most severely, while FM reception is generally
more tolerant and should remain more usable.
But, they are nice toys for the price, fun to play with.
## Compiling and Building from Docker
This project provides a Docker-based build system to compile all firmware editions for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script.
This project provides a Docker-based build system to compile the Fusion firmware for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script.
All build outputs are generated inside the `build/<Preset>` directory, according to the CMake presets defined in `CMakePresets.json`.
The documented build output is generated inside `build/Fusion`, using the CMake presets defined in `CMakePresets.json`.
### Prerequisites
@@ -238,40 +337,25 @@ The script `compile-with-docker.sh` performs the following actions:
1. Builds the Docker image (`uvk1-uvk5v3`) if it does not already exist.
2. Removes any previous `build` directory to ensure a clean configuration.
3. Runs CMake using the selected preset inside the Docker container.
4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files for the chosen edition.
3. Runs CMake using the `Fusion` preset inside the Docker container.
4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files.
### Usage
```bash
./compile-with-docker.sh <Preset> [extra CMake options]
./compile-with-docker.sh Fusion [extra CMake options]
```
### Available Presets
### Documented Preset
- **Custom**
- **Bandscope**
- **Broadcast**
- **Basic**
- **RescueOps**
- **Game**
- **Fusion**
- **All** (builds all editions sequentially)
### Examples
Build a single edition:
Build Fusion:
```bash
./compile-with-docker.sh Fusion
./compile-with-docker.sh Bandscope
./compile-with-docker.sh Broadcast
```
Build everything:
```bash
./compile-with-docker.sh All
```
### Passing Additional CMake Options
@@ -282,41 +366,51 @@ These are forwarded directly to `cmake --preset` inside the container.
Examples:
```bash
./compile-with-docker.sh Bandscope -DENABLE_SPECTRUM=ON
./compile-with-docker.sh Broadcast -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON
./compile-with-docker.sh Bandscope -DSQL_TONE=600
./compile-with-docker.sh Fusion -DENABLE_SPECTRUM=ON
./compile-with-docker.sh Fusion -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON
./compile-with-docker.sh Fusion -DSQL_TONE=600
```
To prepare the rolling development firmware:
```bash
./compile-with-docker.sh Fusion -DDEV=ON
```
This keeps the regular build output in `build/Fusion` and also updates
`archive/f4hwn.fusion.development.bin`. The development build is identified as
`DEV` in the firmware information screen. Publishing the updated archive file
remains an explicit Git operation.
### Notes
- The first run may take a few minutes while Docker builds the base image.
- Running with `All` will build every firmware variant in sequence.
- Each build runs inside Docker, so your host environment remains clean.
## Flashing the Firmware with UVTools2
## Flashing the Firmware with UV Studio
You can flash the UV-K5 V3 and UV-K1 directly from your web browser using the cross-platform WebSerial-based [UVTools2](https://armel.github.io/uvtools2/).
You can flash the UV-K5 V3 and UV-K1 directly from your web browser using the Web Serial-based [UV Studio](https://armel.github.io/uvstudio/).
It works on Chrome, Chromium and Edge (desktop versions), and does not require installing any driver or software on your computer.
UV Studio combines firmware flashing, calibration maintenance, boot-logo management, K5Viewer and RF Log in a single interface. It requires no application installation, server or account. Use a desktop browser with Web Serial support, such as Chrome, Brave, Edge, Opera or Firefox 151+.
## Steps to flash the firmware
- Open UVTools2 in [flash](https://armel.github.io/uvtools2/?mode=flash) mode (or click the Flash Firmware tab).
- Open the [Flash Firmware](https://armel.github.io/uvstudio/#flash) view in UV Studio.
- Connect your radio to your computer using a compatible USB programming cable (USB-C or Baofeng/Kenwood like double jack USB cable).
- Make sure your radio is in **DFU mode (flash mode)**.
- Select the firmware .bin file on your computer.
- Select an official F4HWN Fusion release from the catalog or load a local `.bin` firmware file.
- Click on `Flash Firmware`, then select the serial port associated with your radio.
- The progress bar will guide you through the flashing steps.
Once finished, your radio restart with the new firmware.
Once finished, your radio restarts with the new firmware.
## Steps to dump or restore calibration data
[UVTools2](https://armel.github.io/uvtools2/) can also dump and restore calibration data, which is highly recommended. It’s best to create a dump right after installing F4HWN firmware, and to restore it before installing another firmware (or when returning to the stock firmware, for example).
[UV Studio](https://armel.github.io/uvstudio/) can also dump and restore calibration data, which is highly recommended. It is best to create a dump immediately after installing the F4HWN firmware, and to restore it before installing another firmware or returning to the stock firmware.
### Dump
- Open UVTools2 in [dump](https://armel.github.io/uvtools2/?mode=dump) mode (or click the Dump Calib tab).
- Open the [Dump Calibration](https://armel.github.io/uvstudio/#dump-calib) view in UV Studio.
- Power on your radio in **normal mode**.
- Click `Dump Calibration Data`.
@@ -327,9 +421,9 @@ When the process is complete, click `Download calibration.dat` to save the file
### Restore
- Open UVTools2 in [restore](https://armel.github.io/uvtools2/?mode=restore) mode (or click the Restore Calib tab).
- Open the [Restore Calibration](https://armel.github.io/uvstudio/#restore-calib) view in UV Studio.
- Power on your radio in **normal mode**.
- Select your calibration.dat file on your computer.
- Select your `calibration.dat` file on your computer.
Click `Restore Calibration Data` and wait until the process fully completes.
@@ -342,6 +436,7 @@ Click `Restore Calibration Data` and wait until the process fully completes.
Many thanks to various people:
* [Muzkr](https://github.com/muzkr)
* [Mrkusypl](https://github.com/mrkusypl)
* [Andrej](https://github.com/Tunas1337)
* [Egzumer](https://github.com/egzumer)
* [OneOfEleven](https://github.com/OneOfEleven)
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more