Compare commits

...

307 Commits

Author SHA1 Message Date
Derek Jamison
c07fbbe530 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2024-07-13 09:46:33 -06:00
Derek Jamison
b714ac51f4 JavaScript BigBanging SPI MAX7219 2024-07-13 09:46:17 -06:00
Derek Jamison
942748870b ChatGPT training for JavaScript 2024-06-26 11:32:13 -05:00
Derek Jamison
c9c8ecdb95 JavaScript ADC demo. 2024-06-14 20:57:47 -05:00
Derek Jamison
e3053c4d0d Update with protocol format 2024-05-17 22:56:03 -05:00
Derek Jamison
307ac450ed LFRFID FSK GEO DEMO 2024-05-17 22:49:40 -05:00
Derek Jamison
e6acfb405f InstaFob 2024-05-10 19:45:00 -05:00
Derek Jamison
fcf242ac47 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2024-04-27 11:54:38 -05:00
Derek Jamison
bbd40cb103 Initial check-in for T5577 2024-04-27 11:54:32 -05:00
Derek Jamison
0420e698ee
Update Light_on.sub for Matthew's house 2024-04-26 17:44:42 -05:00
Derek Jamison
546f5f048b Add more pre-build FAL files 2024-04-20 19:06:08 -05:00
Derek Jamison
3c5019697d Add note about creating .sub files 2024-04-20 09:45:56 -05:00
Derek Jamison
3f74c682b1 JS: Flipboard 2024-04-19 18:00:58 -05:00
Derek Jamison
6b9195fb42 Prebuilt files for momentum and official. 2024-04-13 12:33:58 -05:00
Derek Jamison
c70c4fb65e JS: vgm_sensor tutorial 2024-04-12 15:00:38 -05:00
Derek Jamison
6b1cc1013e
Update README.md 2024-04-07 10:02:28 -05:00
Derek Jamison
fe2480db16 Updated comments 2024-04-06 08:17:05 -05:00
Derek Jamison
63154ff02d JS: BadUSB Readme 2024-04-05 23:35:14 -05:00
Derek Jamison
df144e1753 Delete MP3 file from repo. 2024-04-05 15:31:36 -05:00
Derek Jamison
1a7616538f Automated JavaScript BadUSB demo 2024-04-05 15:30:24 -05:00
Derek Jamison
9006626231 Update cookie clicker with updated Bluetooth API. 2024-04-01 12:44:44 -05:00
Derek Jamison
9c944be2e6 Speaker ffi example 2024-03-31 15:49:09 -05:00
Derek Jamison
de3e8f323d Intro JS 2024-03-30 20:45:39 -05:00
Derek Jamison
ca9efc0342 API change requires DateTime. 2024-03-24 19:58:33 -05:00
Derek Jamison
89f34981b4 Update readme with Fine Tuning directions. 2024-03-24 14:28:10 -05:00
Derek Jamison
00e2574c5a Fix crash when no VGM. Use void* context (support more input scenarios). 2024-03-24 14:15:04 -05:00
Derek Jamison
05e629786a IMU Controller 2024-03-21 19:29:36 -05:00
Derek Jamison
6f17e6eb48 Low frequency RFID spreadsheet 2024-03-19 16:30:44 -05:00
Derek Jamison
db6b5e4f1a
Merge pull request #31 from Willy-JL/air-labyrinth-fixes
Air Labyrinth Fixes
2024-03-14 13:58:25 +07:00
WillyJL
4c228ffddf
Air Labyrinth: Use PYTHON3 var 2024-03-06 19:35:01 +00:00
Willy-JL
b17c15ac07 AirLabyrinth: Fix sprites on windows (still precompiled tho) 2024-03-04 22:48:01 +00:00
Willy-JL
d27edb92fe AirLabyrinth: Fix for gcc12 toolchain 2024-03-04 22:46:30 +00:00
Derek Jamison
d5c329739a
Merge pull request #28 from kitsunehunter/auto_reader_power
Automatically turn on connected HID reader when starting read functions
2024-03-04 02:28:30 -08:00
Derek Jamison
a54146a4af Update readme 2024-02-28 17:44:53 -08:00
Derek Jamison
9e839e1a79 Update readme 2024-02-28 17:42:24 -08:00
Derek Jamison
4e33c80125 Add the fxbm asset 2024-02-28 17:38:17 -08:00
Derek Jamison
e4ac198717 Make assets pre-built 2024-02-28 17:34:34 -08:00
Derek Jamison
119f4f244e Update readme with v0.1 game image. 2024-02-23 14:57:24 -06:00
Derek Jamison
f6d509a409 Updated readme 2024-02-23 14:46:17 -06:00
Derek Jamison
28eb2041c2 Air Labyrinth v0.1 2024-02-23 14:37:06 -06:00
Derek Jamison
53eed82f55 capturing my notes on security1.0/2.0 2024-02-23 13:52:02 -06:00
Derek Jamison
fdb828016a Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2024-02-17 23:10:20 -06:00
Derek Jamison
31ef491bd9 Latest OFW needs this file instead (thanks Prophet) 2024-02-04 19:54:24 -06:00
Derek Jamison
0662157ca6 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials into main 2024-02-04 13:14:03 -06:00
Derek Jamison
b995a55ef3 Genie Recorder v3 2024-02-04 13:13:58 -06:00
Derek Jamison
eb39e8bc4a Xtreme/RM now contain the auto-detect patch! 2024-01-30 22:48:57 -06:00
Derek Jamison
2184839b43 Key 0001 and 0002 using "Protocol: Genie" 2024-01-28 12:02:51 -06:00
Derek Jamison
95d939cd2a badusb example commands 2024-01-28 11:48:33 -06:00
Derek Jamison
8792cccfcb delete unused file 2024-01-28 11:44:56 -06:00
Derek Jamison
809ff21416 A couple of badusb test scripts 2024-01-28 11:32:42 -06:00
Derek Jamison
393b4febc1 bug fix: free resource 2024-01-28 11:28:06 -06:00
Derek Jamison
dca8b03b8a clarify this is viewport 2024-01-28 11:27:45 -06:00
Derek Jamison
97a19a32f6 Updated readme 2024-01-28 11:24:31 -06:00
Derek Jamison
d617a8503a Update readme 2024-01-28 11:15:00 -06:00
Derek Jamison
2b940d644f
Link to YouTube video - Update README.md 2024-01-27 10:53:26 -06:00
Derek Jamison
50141c9e74 Update directions for CFW 2024-01-23 12:27:11 -06:00
Xavier
84e7a70362 automatically supply GPIO power when using read functions to turn on reader 2024-01-22 20:25:44 -05:00
Derek Jamison
6d812948f3 fix issue #27 - PushPull if connected to GND (reported by llgarrido) 2024-01-22 10:26:19 -06:00
Derek Jamison
7e4d4a06cb Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2024-01-20 13:58:34 -06:00
Derek Jamison
8241069354 IR Blaster auto-detect patch for Offical Firmware 2024-01-20 13:56:03 -06:00
Derek Jamison
a012ec78e0 Version 1.9 - fix first LED "laggy" issue (reported by Z3BRO). 2024-01-17 10:26:35 -06:00
Derek Jamison
fe3bb3b354 Use logic LOW when not sending data. 2024-01-17 10:24:28 -06:00
Derek Jamison
9ec25ed0b4 Reduce to 512 LEDS (out of mem) on some FW. 2023-12-16 08:47:55 -08:00
Derek Jamison
cafdbcc001 Improvements for ws2812b_tester app 2023-12-06 18:30:15 -06:00
Derek Jamison
216c15dd77 Fix version on application.fam. Refactor code for COUNT_OF. 2023-12-06 14:22:33 -06:00
Derek Jamison
774b228856 v1.7: blanking signal. Reset remaining leds. 2023-12-06 13:57:06 -06:00
Derek Jamison
6b817ad23e v1.6: Add support for 1000 LEDS, Add "dirty" support to remove update flicker. 2023-12-06 12:40:19 -06:00
Derek Jamison
606abe7887 Ver1.3: (w35, w36, PACS_ fields, filename) 2023-12-03 14:02:46 -06:00
Derek Jamison
46a0d76f52 Update readme 2023-12-03 13:40:34 -06:00
Derek Jamison
49204805cf
Merge pull request #18 from kitsunehunter/main
Add wiegand formats, decoding, enhance log file
2023-12-03 13:30:15 -06:00
kitsunehunter
2808278087
Merge pull request #1 from kitsunehunter/Add_Wiegand_Formats_and_Decoding
Add wiegand formats, decoding, enhance log file
2023-11-28 12:04:44 -05:00
Xavier
6e1ced7e8b verbose output off to get to decoded data faster 2023-11-28 11:56:02 -05:00
Derek Jamison
812c50b41f Add version in About screen. 2023-11-26 13:12:20 -06:00
Derek Jamison
4abe084717 Reverse direction so single LED goes in RGBW order. 2023-11-26 13:02:00 -06:00
Derek Jamison
e517031c7c Add LED Speed option 2023-11-26 11:09:43 -06:00
Derek Jamison
b875cb8103 iOS17. :) 2023-11-26 10:27:53 -06:00
Derek Jamison
145067632f Update iOS18 instructions + stop timer on BACK button. 2023-11-26 10:23:45 -06:00
Derek Jamison
2739089114 Update WS2812B with .flipcorg data 2023-11-25 18:30:52 -06:00
Derek Jamison
04480cbbe3 WS2812B Tester 2023-11-25 15:05:52 -06:00
Xavier
447e7ec040 documentation 2023-11-23 00:16:41 -05:00
Xavier
4db81a96bd edit H10301 decoder to display FC and CN first for consistancy 2023-11-22 21:20:42 -05:00
Xavier
5004306d9d edit file naming struct for readibility 2023-11-22 01:15:31 -05:00
Xavier
d9f9ccae08 edit log file encoding for wiegand_load compatibility 2023-11-21 17:50:33 -05:00
Xavier
d43bc66305 rename RAW_Data to PACS_Binary 2023-11-21 16:16:22 -05:00
Xavier
742bfc1559 add pm3 command for single step encoding 2023-11-21 16:15:22 -05:00
Xavier
54785fe8d9 modify log file to provide PACS binary string only for downgrades 2023-11-21 16:13:09 -05:00
Xavier
07c286642b add decoding formats C10005 and C1k35s 2023-11-21 16:08:54 -05:00
Derek Jamison
ddb07eaeee Fix bug found by Slackware! 2023-11-15 17:10:57 -06:00
Derek Jamison
6934ea564d SPI demo w/W25Q32 chip 2023-11-15 15:43:03 -06:00
Derek Jamison
3d0aed8283 Updated skeleton app 2023-10-26 16:38:18 -05:00
Derek Jamison
27a50ac914 Update readme 2023-10-20 17:21:30 -05:00
Derek Jamison
32b40d268a Fix off-by-one bug. Stop clicking when all found. 2023-10-20 16:43:38 -05:00
Derek Jamison
87df63a3e9 YIKES! BUG COULD HURT YOUR FLIPPER? 2023-10-20 10:21:32 -05:00
Derek Jamison
5d92a3c39b Add NULL check, just to be safe. 2023-10-19 09:53:15 -05:00
Derek Jamison
1d10fa2019 Script to make keys.txt into fix.GNE file 2023-10-18 22:12:08 -05:00
Derek Jamison
88992639c6 Genie v2 - supports custom protocol handler 2023-10-18 19:39:57 -05:00
Derek Jamison
0f7ab59050 Update readme with warning about fast-forwarding. 2023-10-13 23:26:23 -05:00
Derek Jamison
45d8c3d723 Add a few genie sub files (no python required). 2023-10-13 23:20:45 -05:00
Derek Jamison
0c8570244c bug #14: Update x10 remote with latest 2023-10-12 12:12:21 -05:00
Derek Jamison
7376e5a6e7 Flipper Zero modulation samples 2023-10-11 16:39:12 -05:00
Derek Jamison
367a8ff09e Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2023-10-11 16:30:05 -05:00
Derek Jamison
4202990995 genie_recorder 1.3 (no private API) 2023-10-11 16:27:41 -05:00
Derek Jamison
09b0eed345
Update README.md 2023-10-11 12:26:55 -05:00
Derek Jamison
12560931d5
Update README.md
Remove ufbt directions.  Clarify install directions.
2023-10-11 12:17:41 -05:00
Derek Jamison
4eeafe3357 Thread demo (PWM example) 2023-10-06 13:46:11 -07:00
Derek Jamison
7595c42fdc
Update README.md 2023-10-04 15:09:13 -05:00
Derek Jamison
1cc4506a6b Add version info. 2023-10-03 16:56:07 -05:00
Derek Jamison
1569680800 Updated readme and about text. 2023-10-03 16:54:55 -05:00
Derek Jamison
b6482bfdfd Issue reported by RogueMaster (api_symbols.csv) 2023-10-03 12:29:28 -05:00
Derek Jamison
90644e11b2 Edit script to allow for multiple numbers. 2023-10-03 12:27:46 -05:00
Derek Jamison
c0159c6920 Created genie.py for generating a RAW .SUB file 2023-10-02 18:50:38 -05:00
Derek Jamison
3e463b17b6 Remove crashing bug. 2023-10-02 16:29:58 -05:00
Derek Jamison
1044c0bf8c Fix crashing bug -- can't append 1000s of times? 2023-10-02 11:46:57 -05:00
Derek Jamison
54c1f4fd13 Genie GIRUD-1T 2023-10-01 19:08:10 -05:00
Derek Jamison
fec280d3a5 Genie recorder 2023-09-28 14:56:39 -05:00
Derek Jamison
88db25b8de remove random_name API call 2023-09-13 22:03:29 -05:00
Derek Jamison
7e8bdc0559 v1.1 - Add scan code (needs refactor) 2023-09-13 21:56:30 -05:00
Derek Jamison
384d618485 Fix typo. :) 2023-09-08 10:02:33 -05:00
Derek Jamison
1b454277c8 v1.5 (L/R count, OK=flush+CLOSED, signal=vibrate) 2023-09-06 22:30:54 -05:00
Derek Jamison
46f987a00e Update readme 2023-09-06 12:10:22 -05:00
Derek Jamison
356f49365b Add support for W48 (H2004064) 2023-09-06 12:02:26 -05:00
Derek Jamison
7d18e75287 Also increment fap_version 2023-09-02 22:25:24 -05:00
Derek Jamison
31fbce8209 bug fixes (see changelog) 2023-09-02 22:08:39 -05:00
Derek Jamison
99e76d6214 update comment. 2023-09-02 19:45:28 -05:00
Derek Jamison
1b5dfecf20 update .fam file. 2023-09-02 19:43:45 -05:00
Derek Jamison
d99a059a01 Fix huge delay on Transmit Signal. 2023-09-02 19:42:39 -05:00
Derek Jamison
be6e4ba9ed Add ver info to readme. Add note about Xtreme DEV. 2023-09-02 16:38:31 -05:00
Derek Jamison
6ac133d820 Add version to FAP DESC as well. 2023-09-02 16:34:56 -05:00
Derek Jamison
936221cded Added versioning 2023-09-02 16:32:50 -05:00
Derek Jamison
61e8fb3d70 Add .flipcorg meta data. 2023-09-02 16:24:29 -05:00
Derek Jamison
109cbd7b4e Add description and YouTube link 2023-09-02 16:12:37 -05:00
Derek Jamison
6b0fe23993 Latest updated based on video 2023-09-02 15:58:20 -05:00
Derek Jamison
c28bf80713 Add Keeloq diagram 2023-09-01 19:05:59 -05:00
Derek Jamison
aa89a121c4 Added "Test attack" diagram 2023-09-01 17:53:27 -05:00
Derek Jamison
b44ff0786c Update contact info 2023-09-01 17:30:53 -05:00
Derek Jamison
c0241ed223 Rolling-Flaws (KeeLoq rolling code teacher) 2023-09-01 17:24:39 -05:00
Derek Jamison
26104939ab Signal send demo - Aug 22, 2023 session 2023-08-23 01:26:23 -05:00
Derek Jamison
568f74348f Add skeleton_app for jump starting FZ apps! 2023-08-22 11:40:59 -05:00
Derek Jamison
68714ad1c5 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2023-08-19 16:44:05 -05:00
Derek Jamison
a13cfb712b Update subghz_demo - latest firmware changes. 2023-08-19 16:44:00 -05:00
Derek Jamison
6921a26905
Update README.md 2023-08-15 17:41:19 -05:00
Derek Jamison
4d1d89a791 Add Discord info 2023-08-15 17:31:14 -05:00
Derek Jamison
f370cff1a7 Add "binary counter" effect 2023-08-10 17:30:19 -07:00
Derek Jamison
e5df7c5e5b Update screenshot 2023-08-10 17:10:02 -07:00
Derek Jamison
961294756e Version 1.1 (more effects) 2023-08-10 17:08:58 -07:00
Derek Jamison
593b7dd948 update banner image 2023-08-10 12:59:02 -07:00
Derek Jamison
b5238d6a6f Add .flipc pictures 2023-08-10 12:46:13 -07:00
Derek Jamison
6e06cd4550 GPIO Badge v1.0 2023-08-10 12:34:49 -07:00
Derek Jamison
8dfd5a598a Send princeton signals 2023-08-07 10:22:27 -05:00
Derek Jamison
37252b745b
Merge pull request #13 from LionZXY/catalog/full_description
Add full description for Rock Paper app
2023-07-22 10:40:30 -05:00
Nikita Kulikov
06a5c43621 Add full description 2023-07-19 17:29:38 +04:00
Derek Jamison
af269d5c3f Remove doc images 2023-07-15 22:05:25 -05:00
Derek Jamison
e9022c7eb0 Example of basic_scenes split across files. 2023-07-15 22:04:30 -05:00
Derek Jamison
4beca0ce95 Add screenshots 2023-07-12 11:17:06 -05:00
Derek Jamison
00480d1fc8 v1.3 - Echo signals a few times 2023-07-11 18:18:08 -05:00
Derek Jamison
d931817ae5 Updated README 2023-07-11 17:52:49 -05:00
Derek Jamison
0112d1cbfe Add license to RPS game 2023-07-11 17:41:58 -05:00
Derek Jamison
8c3b4e66b2 version 1.2 of RPS 2023-07-11 17:38:16 -05:00
Derek Jamison
4bf3a47358 Build with: ufbt update --channel=release 2023-07-11 17:11:55 -05:00
Derek Jamison
b84221eb3c BACK at "play again" should broadcast quit. 2023-07-11 16:52:35 -05:00
Derek Jamison
a9915dc092 Update RPS for latest API changes 2023-07-11 09:45:07 -05:00
Derek Jamison
8a782817c7 r15ch13 contribution 2023-07-02 10:19:56 -05:00
Derek Jamison
3f9554fb07 Add comment about GpioModeAnalog 2023-07-01 09:42:13 -05:00
Derek Jamison
5c5333e55e Add blink PWM example 2023-07-01 09:40:35 -05:00
Derek Jamison
ecbb5c037b kilohm https://physics.nist.gov/cuu/pdf/sp811.pdf 2023-07-01 08:46:17 -05:00
Derek Jamison
d536cf1101 Update per bunni feedback 2023-07-01 08:45:06 -05:00
Derek Jamison
a0eff12f17 UART demo, making UART read easier. 2023-06-29 14:26:28 -05:00
Derek Jamison
4c304ac075 Add Blink LED example 2023-06-11 20:37:51 -05:00
Derek Jamison
7a0540971e SHTC3 app to print id. 2023-06-11 17:15:32 -05:00
Derek Jamison
1d8134f778 appid must be lowercase in latest firmware 2023-06-11 17:03:26 -05:00
Derek Jamison
435aad3ecf #3 - update signal for serialize/deserialize 2023-06-11 16:52:50 -05:00
Derek Jamison
885c465b44 #8 - Add decimal values to id & facility. 2023-06-11 15:50:30 -05:00
Derek Jamison
bd1b14bb01 Add x,y,m values to instructions. 2023-06-11 15:39:12 -05:00
Derek Jamison
052f4f8679 fix initialization. add speed. 2023-06-11 15:16:07 -05:00
Derek Jamison
6fad46cfb2 dev 30.1 RM no longer has DEED macro 2023-06-11 13:18:24 -05:00
Derek Jamison
d1816692ce 30.x requires [a-z0-9_] for app id 2023-06-11 12:44:28 -05:00
Derek Jamison
5741fcc57c Added config screen 2023-06-11 12:21:13 -05:00
Derek Jamison
d922892bea #11 wrong spot - Make click location configurable. 2023-06-11 12:11:42 -05:00
Derek Jamison
554e36a4f2
Delete plugins/basic_scenes/docs directory 2023-06-04 16:49:28 -05:00
Derek Jamison
bee1c4c67c
Delete basic_scenes.png 2023-06-04 16:49:17 -05:00
Derek Jamison
d02135a37a
Delete basic_scenes.c 2023-06-04 16:49:05 -05:00
Derek Jamison
2e6174b0d6
Delete application.fam 2023-06-04 16:48:52 -05:00
Derek Jamison
c7d1b4177d
Update README.md
Add moved notice.
2023-06-04 16:48:31 -05:00
Derek Jamison
2fb10c8b1f Move Basic_scenes under UI folder 2023-06-04 16:44:27 -05:00
Derek Jamison
0672ab0b77 ViewDispatcher example 2023-06-04 16:25:43 -05:00
Derek Jamison
808e0772c2 Simple ViewPort demo - Hello World 2023-06-01 17:16:55 -05:00
Derek Jamison
d0733985b0
Update README.md 2023-05-25 11:00:58 -05:00
Derek Jamison
833b1d2a04 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials 2023-05-02 13:02:13 -04:00
Derek Jamison
f1c4a190e6 Add MOSFET support to Wiegand app. 2023-05-02 13:02:09 -04:00
Derek Jamison
bf381dd765
Update README.md 2023-04-30 10:48:01 -04:00
Derek Jamison
89b590090e Added link to Visual Guide to FZ GUI Components. 2023-04-26 09:36:07 -04:00
Derek Jamison
2e17693d87 typo 2023-04-22 11:26:24 -04:00
Derek Jamison
d5e63fda45 Marauder readme 2023-04-21 19:51:43 -04:00
Derek Jamison
a7c155e88d Add security topic to readme. 2023-04-20 12:29:37 -04:00
Derek Jamison
0466503c64 Remove debug spew 2023-04-20 11:35:21 -04:00
Derek Jamison
8b86b58e88 Move into secret_ssid + README file. 2023-04-20 10:45:49 -04:00
Derek Jamison
0230baa7e8 Allow acting as ATT wifi. 2023-04-19 23:24:30 -04:00
Derek Jamison
a829cb7620 Improve comments 2023-04-19 23:23:51 -04:00
Derek Jamison
25500ab2db Improved SSID encode/scan. 2023-04-19 23:17:45 -04:00
Derek Jamison
d2740d37e8 Improve comments. 2023-04-19 16:12:47 -04:00
Derek Jamison
80e445b094 Fix bug + add commented test code. 2023-04-19 15:31:49 -04:00
Derek Jamison
6ad7d9e2b0 Add scan.ps1 script 2023-04-19 12:40:33 -04:00
Derek Jamison
f7f6e2a17e fix issue #7 - invoke view_dispatcher_remove_view 2023-04-18 22:06:36 -04:00
Derek Jamison
bfd76eac24
Merge pull request #6 from Brodan/main
fix basic_scenes tutorial cleanup function and update readme accordingly.
2023-04-18 17:57:18 -04:00
brodan
f500d4b577 fix basic_scenes tutorial cleanup function and update readme accordingly 2023-04-18 17:54:13 -04:00
Derek Jamison
d71c9ee7cd Update README -- deprecated tutorial. 2023-04-17 17:33:33 -04:00
Derek Jamison
195a8a9265 issues #4 - Wrong pins for I2C 2023-04-17 17:21:45 -04:00
Derek Jamison
505926825e Add UNUSED to functions 2023-04-15 15:47:19 -04:00
Derek Jamison
30c9d6d186 Minor updates 2023-04-15 15:31:06 -04:00
Derek Jamison
257f048fde Update readme to fix typos 2023-04-15 14:56:55 -04:00
Derek Jamison
3c53683796 Change category to Bluetooth to align with Roguemaster FW naming 2023-04-15 14:48:52 -04:00
Derek Jamison
e6d61aa22c Initial check-in for hid_cookie 2023-04-15 14:34:24 -04:00
Derek Jamison
3a1a0c19ab Updated flipc.org files 2023-04-13 19:13:12 -04:00
Derek Jamison
e8339970c1 add fap_icon_assets_symbol to FAM file 2023-04-13 19:07:02 -04:00
Derek Jamison
bc1e3e541a Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials into main 2023-04-13 18:45:36 -04:00
Derek Jamison
f4703885d4 Fix paths for non-windows systems. 2023-04-13 18:45:31 -04:00
Derek Jamison
54122eacc3 Updated readme 2023-04-09 17:29:38 -04:00
Derek Jamison
a49aeb3536 Add prebuilt for Release 0.80.1 2023-04-09 11:42:52 -04:00
Derek Jamison
ffdd292550 RPS play again signal from remote user. 2023-04-09 09:24:27 -04:00
Derek Jamison
a5ca0c1976 Viewport code example. 2023-04-09 09:22:17 -04:00
Derek Jamison
ff73bde3f5 Updated .gitignore 2023-04-09 09:19:36 -04:00
Derek Jamison
3a312faca8 Add reader and clips to electronics. 2023-04-08 09:06:40 -04:00
Derek Jamison
74322c1400 Add wiegand to the tutorials readme. 2023-04-08 09:01:05 -04:00
Derek Jamison
0860e09785 Add install directions for Wiegand. 2023-04-08 08:57:13 -04:00
Derek Jamison
25caf3752c Initial Wiegand application 2023-04-06 23:46:18 -04:00
Derek Jamison
e9b37d5b50 Cookie Clicker over bluetooth! 2023-03-30 16:03:11 -04:00
Derek Jamison
10fee7301b Add link to YouTube series tutorial. 2023-03-28 21:38:29 -04:00
Derek Jamison
78dfd1bf2e Integrate Karol's feedback on README.md file. 2023-03-28 21:30:07 -04:00
Derek Jamison
9dd831b58d Renamed image. 2023-03-28 18:50:50 -04:00
Derek Jamison
84aeb858b7 Improvements to basic scenes tutorial. 2023-03-28 15:43:42 -04:00
Derek Jamison
fe0490a5a3 Initial draft of Basic Scenes tutorial 2023-03-28 15:11:37 -04:00
Derek Jamison
2b41dcff2f Update with archived releases 2023-03-25 00:06:02 -04:00
Derek Jamison
96db4ad485 Update links to direct raw files 2023-03-24 23:39:17 -04:00
Derek Jamison
029804873a update readme 2023-03-24 23:23:31 -04:00
Derek Jamison
df12e82738 v1.0 RPS for RC-0.80.0 official fw 2023-03-24 22:44:53 -04:00
Derek Jamison
d93e358bb6 v1.0 RPS for V0.79.1 official fw 2023-03-24 22:34:02 -04:00
Derek Jamison
13736cdccd Relaxed timing. Update readme. 2023-03-24 09:12:37 -04:00
Derek Jamison
c6a176d94a fix freq not available bug w/join w/o games. 2023-03-23 17:15:20 -04:00
Derek Jamison
2fa4067702 Do not require sync clicking. 2023-03-23 17:10:59 -04:00
Derek Jamison
1540853283 Play again prompt at end of game. 2023-03-23 16:50:56 -04:00
Derek Jamison
b96047cd7b Update social info for existing player. 2023-03-23 15:03:24 -04:00
Derek Jamison
3d1fff533f Support subghz chat messages. 2023-03-23 14:54:57 -04:00
Derek Jamison
dfe9b6371e Breaking change - Update protocol. 2023-03-23 14:24:24 -04:00
Derek Jamison
a97e6df1d9 Host exit removes game from list. 2023-03-23 12:41:51 -04:00
Derek Jamison
a5cc35db5b fix some remote games not added to list. 2023-03-23 11:22:47 -04:00
Derek Jamison
e7b49ae7b2 Use constants for enter/backspace. 2023-03-23 10:28:32 -04:00
Derek Jamison
6f43c5bc7b Move social type to separate line. 2023-03-23 10:14:50 -04:00
Derek Jamison
228f46fea9 Make "edit contact info" first choice. 2023-03-23 10:09:33 -04:00
Derek Jamison
bf0b8063a7 save/load social preferences. 2023-03-23 09:48:30 -04:00
Derek Jamison
2ca12eb770 Code cleanup 2023-03-23 08:33:00 -04:00
Derek Jamison
7d87a22b10 RPS - Allow configuring social info. 2023-03-22 14:10:57 -04:00
Derek Jamison
c88d5dbf3a Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials into main 2023-03-21 19:14:58 -04:00
Derek Jamison
efe95a10de Added sample messages to readme 2023-03-21 19:14:46 -04:00
Derek Jamison
0fb6fb32d1 Initial code for i2c_demo 2023-03-21 10:08:36 -04:00
Derek Jamison
418c419171 Initial check-in for scenes demo. 2023-03-08 08:57:38 -05:00
Derek Jamison
f11f6a86b1 initial checkin for knob_component 2023-03-08 08:45:17 -05:00
Derek Jamison
97b7900a99 Remove game when another Flipper does a join. 2023-03-06 21:41:32 -05:00
Derek Jamison
fda2fbcdbb Show past games. 2023-03-06 20:51:41 -05:00
Derek Jamison
615e307bf5 Save game results w/contact info to SD card. 2023-03-06 14:48:32 -05:00
Derek Jamison
3e5acab130 Free sender name. 2023-03-06 12:43:22 -05:00
Derek Jamison
c683a15a5f Host ACKs the join request. 2023-03-06 12:40:58 -05:00
Derek Jamison
74f931e1f6 Added main menu + join feature. 2023-03-05 23:18:32 -05:00
Derek Jamison
272a7bc594 Links tool - help me build tutorials w/various fw 2023-03-04 16:24:33 -05:00
Derek Jamison
72b1d89078 Add vibro on move. Song on game end. 2023-03-04 16:17:45 -05:00
Derek Jamison
b8db448de7 furi_hal_subghz_is_tx_allowed not in all fw. 2023-03-04 12:55:46 -05:00
Derek Jamison
8f0c94c1a5 furi_hal_subghz_is_tx_allowed(..) not in all fw. 2023-03-04 11:29:55 -05:00
Derek Jamison
69f7ee770d Reformat document with VSCode. 2023-03-04 10:12:36 -05:00
Derek Jamison
9d123a5963 issue #2 - unused var gives errors in latest SDK 2023-03-04 10:06:26 -05:00
Derek Jamison
b6fef49575 Mesmic Mx2125 Dual-Axis Accelerometer 2023-03-03 15:26:06 -05:00
Derek Jamison
91da549b6a Demo of GPIO interrupt 2023-03-02 15:20:43 -05:00
Derek Jamison
4c450f8f9f Error-prone script: convert SUB RAW into CSV! 2023-03-02 12:16:22 -05:00
Derek Jamison
dd93de7dbf Test to see if BACKSPACE and DELETE keys work. 2023-03-02 12:14:50 -05:00
Derek Jamison
ba5a8c6749 The usb id triggers rekordbox fw update. 2023-03-02 12:14:11 -05:00
Derek Jamison
5c613a19db Youtube: GPIO pull-up/down resistors 2023-02-28 14:09:13 -05:00
Derek Jamison
f376b55dec Update youtube entries 2023-02-27 19:25:24 -05:00
Derek Jamison
043013c8c1 Fix my second NRF24L01 to be named correctly. 2023-02-27 19:23:40 -05:00
Derek Jamison
4475b64c5e Update descriptions 2023-02-27 17:56:25 -05:00
Derek Jamison
541cf81a61 Update youtube index 2023-02-27 16:32:17 -05:00
Derek Jamison
5cb69ccedd Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials into main 2023-02-27 16:27:01 -05:00
Derek Jamison
ee8e6c9d32 Added first draft of "pins" project 2023-02-27 16:26:51 -05:00
Derek Jamison
d53407d42b Add electronic tools - iron and logic analyzer 2023-02-26 20:13:51 -05:00
Derek Jamison
31c9be7ef7 Improved accuracy for hc_sr04 ultrasonic device! 2023-02-23 13:02:23 -05:00
Derek Jamison
2d35562eeb Copy of original hc_sr04 code 2023-02-23 12:26:41 -05:00
Derek Jamison
53e18600aa Control 7-segment display with Flipper Zero 2023-02-21 11:45:07 -05:00
Derek Jamison
668fd49fe3 Merge branch 'main' of https://github.com/jamisonderek/flipper-zero-tutorials into main 2023-02-21 11:44:39 -05:00
Derek Jamison
681c379419 Basic application with custom canvas draw. 2023-02-21 11:44:35 -05:00
Derek Jamison
ad34f24762 7-segment link 2023-02-20 23:37:53 -05:00
Derek Jamison
e565593360 Add link to resistors. 2023-02-20 23:31:55 -05:00
Derek Jamison
093511c7e7 Add section for wires. 2023-02-20 23:07:42 -05:00
Derek Jamison
08e9dd6b40 7-segment youtube link 2023-02-20 15:29:52 -05:00
Derek Jamison
2df8a0766d Add links to electronics videos 2023-02-19 13:02:15 -05:00
Derek Jamison
1a941f5749 Add links for electronic parts 2023-02-19 10:53:00 -05:00
Derek Jamison
1d364099a6 Update readme with new youtube video link 2023-02-15 19:52:32 -05:00
Derek Jamison
d0df56c65d Update README for subghz samples 2023-02-15 16:30:38 -05:00
Derek Jamison
9783c9fb53 Update readme. 2023-02-15 16:15:43 -05:00
Derek Jamison
1a7a71e7f1 Add subghz readme files 2023-02-15 16:11:15 -05:00
Derek Jamison
f23c54ac4f Page with links to YouTube videos 2023-02-15 12:10:44 -05:00
Derek Jamison
4a881a0f6f Updated application.fam file 2023-02-15 11:28:47 -05:00
Derek Jamison
7c09c11f9b update readme 2023-02-15 11:26:53 -05:00
Derek Jamison
dd608f6d8e Add images to rock paper scissors game. 2023-02-14 15:49:09 -05:00
Derek Jamison
58ccc2ef0b More game states + helper methods 2023-02-12 13:05:32 -05:00
Derek Jamison
ca5515c8b9 Initial version of rock paper scissors. 2023-02-11 16:18:31 -05:00
Derek Jamison
4a63e040e8 Add X10 remote samples 2023-02-09 20:30:02 -05:00
Derek Jamison
43fade6f5f Update readme to have correct png filename 2023-02-08 17:10:30 -05:00
Derek Jamison
ceaac67386 Updated quantum-fire - filenames&protocol capture. 2023-02-03 14:57:52 -05:00
Derek Jamison
084bd051e9 Chevy HHR 2006 lock/unlock 2023-02-02 15:40:50 -05:00
Derek Jamison
8928073d1a
Merge pull request #1 from jamisonderek/add-subghz-rx-tx-worker
Add subghz rx tx worker bug fixes
2023-01-31 00:11:55 -05:00
1100 changed files with 105431 additions and 190 deletions

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
.vscode/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "engine"]
path = vgm/apps/air_labyrinth/engine
url = https://github.com/flipperdevices/flipperzero-game-engine

View File

@ -1,7 +1,30 @@
# flipper-zero-tutorials
[Discord invite: NsjCvqwPAd](https://discord.com/invite/NsjCvqwPAd)
[YouTube: @MrDerekJamison](https://YouTube.com/@MrDerekJamison)
I will use this repository for my Flipper Zero projects. The various README.md files should describe how to use the files on your Flipper Zero.
Feel free to reach out to me at CodeAllNight@outlook.com with any questions or leave them in the [issues section](https://github.com/jamisonderek/flipper-zero-tutorials/issues) for this project.
Feel free to reach out to me at Discord with any questions or leave them in the [issues section](https://github.com/jamisonderek/flipper-zero-tutorials/issues) for this project.
## Video Game Module
### air_labyrinth
[game](./vgm/apps/air_labyrinth/README.md) - This is a game where you control a ball through a maze. The game uses the accelerometer in the Video Game Module to control the ball. For best experience, it is recommended to connect the VGM to the HDMI input on a TV. The game is also displayed on the Flipper Zero's screen.
NOTE: This game uses the game `engine`, so you must recursively clone the `flipper-zero-tutorials` repository to get the `engine` submodule, or download the [https://github.com/flipperdevices/flipperzero-game-engine](https://github.com/flipperdevices/flipperzero-game-engine) into a folder called `./vmg/apps/air_labyrinth/engine`.
## RFID
### [spreadsheet](./rfid/README.md) - This is a spreadsheet that can be used to convert RFID data from one format to another, and to create new credentials with a shared facility code. It can also be used to create RFID data for the T5577 chip.
## YouTube
### videos
[video index](./youtube/README.md) - This is a list of my various YouTube video tutorials.
## Electronics
### electronics projects
[parts list](./electronics/README.md) - This is a list of electronics parts that I will be using in my videos or tutorials.
## Firmware
@ -10,16 +33,52 @@ Feel free to reach out to me at CodeAllNight@outlook.com with any questions or l
## GPIO
### wiegand
[project](./gpio/wiegand/README.md) - This is a tool for reading and writing Wiegand data. Wiegand is typically used by NFC, RFID and keypads. This tool can be used to read a Wiegand signal, save and display the data on the Flipper Zero's screen. It can also play the signal back.
### gpio-pins
[tool](./gpio/pins/README.md) - This is a set of files you can copy to your Flipper Zero so that you can easily lookup the meaning of a pin.
### hc_sr04
[project](./gpio/hc_sr04/README.md) - This is an improvement over the original HC_SR04 library so that you can get mm accuracy in measurements. It displays ultrasonic distance in both inches/cm. The readme explains how the code gets more accurate timings.
### gpio-gpio-7segment
[project](./gpio/gpio_7segment/README.md) - This application can control a common-anode or common-cathode 7-segment display. When you click the OK button the display shows a random dice roll (1-6); but you can modify code to display any number from 0-9.
### gpio-gpio-polling-demo
[tutorial](./gpio/gpio_polling_demo/README.md) - This is a "hello world" demonstration of reading a GPIO pin using polling.
### gpio-gpio-interrupt-demo
[tutorial](./gpio/gpio_interrupt_demo/README.md) - This is a "hello world" demonstration of triggering a callback when a GPIO pin transitions from VCC to GND.
## Subghz
### gpio-memsic_2125
[tutorial](./gpio/memsic_2125/README.md) - This is a demostration of using GPIO interrupts to interpret data from the Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
## Subghz [folder](./subghz/README.md)
### subghz-plugins-subghz_demo
[tutorial](./subghz/plugins/subghz_demo/README.md) - This is a demonstration of sending & receiving radio signals using the subghz_tx_rx worker library.
### subghz-plugins-rock_paper_scissors
[game](./subghz/plugins/rock_paper_scissors/README.md) -
This is a two player game that uses the subghz_tx_rx worker library for communication. Two Flipper Zeros running this game can play Rock, Paper, Scissors against each other!
### subghz-protocol-x10-decoder
[project](./subghz/protocols/x10/README.md) - This is a protocol decoder for the Flipper Zero to decode the x10 series of devices when doing a read from the Sub-GHz menu.
### subghz-samples-chevy-hhr-2006
[data](./subghz/samples/chevy-hhr-2006/README.md) - These Flipper Zero subghz captures are from the remote control for the Chevy HHR 2006 keyfob.
### subghz-samples-quantum-fire
[data](./subghz/samples/quantum-fire/README.md) - These Flipper Zero subghz captures are from the remote control for the Quantum Fire (QF-6LR).
### subghz-samples-x10
[data](./subghz/samples/x10/README.md) - These Flipper Zero subghz captures are from the remote control for the X10.
## Marauder
[tutorial](./marauder/README.md) - This is a tutorial on how to use the Marauder tool to attack 2.4GHz devices.
[tool](./marauder/secret_ssid/README.md) - This is a tool that can be used to send a secret message over WiFi. The message is encoded into a SSID, which is then sent out over WiFi. The message can be decoded on a PC.
## Support
[Buy me a coffee](https://ko-fi.com/codeallnight) - Thanks for supporting my work.

33
badusb/basic-commands.txt Normal file
View File

@ -0,0 +1,33 @@
REM This is a comment (REMark) in your code.
GUI r
DELAY 1000
STRING notepad
ENTER
STRINGLN Hello
ALTSTRING World
ENTER
ALTCHAR 176
ENTER
STRING_DELAY 800
STRINGLN TYPE THIS SLOW...
STRINGLN AND THIS IS FAST AGAIN!!!
DEFAULT_DELAY 1000
STRINGLN EACH LINE
STRINGLN HAS A ONE
STRINGLN SECOND DELAY!
DEFAULT_DELAY 0
ENTER
REPEAT 9
STRINGLN WE PRESSED ENTER 10 TIMES.
SHIFT UP
LEFT
SHIFT UP
REPEAT 9
DELETE
DOWN
HOLD A
WAIT_FOR_BUTTON_PRESS
RELEASE A
GUI-SHIFT S
WAIT_FOR_BUTTON_PRESS
ESCAPE

10
badusb/ddj-400.txt Normal file
View File

@ -0,0 +1,10 @@
ID 0x2b73:0x0026 ddj-400:ddj-400
REM #
REM # Triggers DDJ-400 Rekordbox firmware update application on Windows.
REM # (because of USB ID and NAME of device.)
REM #
GUI r
DELAY 1000
ESCAPE
DELAY 60000
DELAY 60000

View File

@ -0,0 +1,46 @@
REM
REM ## ON WINDOWS, "DELETE" ACTS AS A BACKSPACE KEY.
REM ## ON WINDOWS, "DELETE" should use HID_KEYBOARD_DELETE_FORWARD instead.
REM
REM ## ON WINDOWS, "BACKSPACE" DOESN'T WORK.
REM ## ON WINDOWS, "BACKSPACE" should use HID_KEYBOARD_DELETE.
REM
REM ## THIS SCRIPT TESTS TO SEE IF YOU HAVE THE BUGFIX INSTALLED.
REM ##
REM ## WHEN DONE, YOUR COMPUTER SHOULD BE AT CTRL-ALT-DEL SCREEN.
REM ##
REM
REM Open windows notepad
DELAY 1000
GUI r
DELAY 500
STRING notepad
DELAY 500
ENTER
DELAY 750
REM
REM ##
DELAY 300
DEFAULT_DELAY 300
STRING HELLO 1 2 3
ENTER
STRING WORLD 4 5 6
ENTER
STRING YESNO 7 8 9
ENTER
UP
DELAY 1000
CTRL-SHIFT RIGHT
DELAY 1000
RIGHT
DELAY 1000
LEFT
DELAY 1000
LEFT
DELAY 1000
BACKSPACE
DELAY 1000
DELETE
DELAY 5000
CTRL-ALT DELETE
DELAY 1000

1007
badusb/sub-raw-to-csv.txt Normal file

File diff suppressed because it is too large Load Diff

14
badusb/sysrq.txt Normal file
View File

@ -0,0 +1,14 @@
REM BE CAREFUL RUNNING THIS SCRIPT!
REM
REM TESTING VARIOUS SYSRQ COMMANDS
REM
WAIT_FOR_BUTTON_PRESS
SYSRQ b
WAIT_FOR_BUTTON_PRESS
SYSRQ c
WAIT_FOR_BUTTON_PRESS
SYSRQ i
WAIT_FOR_BUTTON_PRESS
SYSRQ o
WAIT_FOR_BUTTON_PRESS
GUI

View File

@ -0,0 +1,16 @@
REM HELLO WORLD
DELAY 1000
WINDOWS r
DELAY 1000
STRINGLN notepad
DELAY 1000
STRING HELLO WORLD!
ENTER
ALTSTRING ABC
ENTER
ENTER
STRING WORLD
ENTER
HOLD A
WAIT_FOR_BUTTON_PRESS
RELEASE A

45
electronics/README.md Normal file
View File

@ -0,0 +1,45 @@
# Electronics
## Overview
As I am creating tutorials, I will try to include links to either
the product that I bought previously, or a similar product if the
original product is no longer for sale (some of the parts I have are many years old).
If you are just starting out, I'd recommend ordering this starter kit...
- [Amazon link](https://amzn.to/3IyKacg) Great starter kit : Boardboard + jumper wires + some starter components (LEDs, resistors, pots, transistors, capacitors, switches, 74959 IC, CdS sensor, buzzers, etc.)
**NOTE:** I own many other kits with various sensors that we will also be utilizing in future videos. I'll add links to those kits as I use them in my YouTube videos.
## Parts list
### Tools
- [Amazon link](https://amzn.to/3kyRqM3) Inexpensive logic analyzer that runs on Windows and Linux machines. I use PulseView for viewing and analyzing signals using this probe. You do NEED to solder the probes.
- [Amazon link](https://amzn.to/4267wwm) This is the soldering station I use for my projects. It's fairly expensive, so if you are on a budget you can get an iron for a lot cheaper -- something like [link](https://amzn.to/3IVu9x3) should work okay (but I don't personally own it).
### Breadboards
- [Amazon link](https://amzn.to/3IyKacg) Great starter kit : Boardboard + jumper wires + some starter components (LEDs, resistors, pots, transistors, capacitors, switches, 74959 IC, CdS sensor, buzzers, etc.)
- [Amazon link](https://amzn.to/3lRGRUM) Large breadboard, very similar to the one in my videos. Great for prototyping LARGE circuits.
- [Amazon link](https://amzn.to/3IAEUoi) Variety of breadboards (2 with rails, 6 without) + jumper wires; very similar to other boards I own.
### Wires
- [Amazon link](https://amzn.to/3lTbfOd) These are the jumper wires (Dupont Wire) I ordered. They have male/male, male/female, female/female. You can never have too many of these cables -- I also bought [link](https://amzn.to/3YXlob8) and [link](https://amzn.to/41cpJZL); all of which are very similar.
- [Amazon link](https://amzn.to/3xBuUVw) I also ordered these jumper wires, which came with some rotary encoders. (In a future video, I'll use the encoders.)
- [Amazon link](https://amzn.to/3KjRF8c) These are the aligator clips I bought to clip on to various connections.
- [Amazon link](https://amzn.to/3zEQmu2) Quick splice wires are very useful for making connections without cutting the wires. I used these in my Wiegand video.
### Resistors
- [Amazon link](https://amzn.to/3Igs0uB) A good assortment of resistors that I bought in the past. They are 1% tolerance (which is really good).
- NOTE: If you never owned resistors before, you may **also** want to purchase something like [link](https://amzn.to/3xEphpS) because they have three color bands + tolerance band; which is fairly common in older electronics {so it's good to get familiar with the color codes} -- I own something similar, back from my RadioShack days, but it is not this exact same kit.
- [Amazon link](https://amzn.to/3Kk6nvJ) I love how easy it is to read the values on this decade box (and I own two of these) but they are expensive.
- [Amazon link](https://amzn.to/3IeSHzw) This circuit board has variable resistors, moving the jumpers around is a little bit of a pain, but it's nice to be able to get a precise value vs. using a trimmer or potentiometer.
### LEDs
- [Amazon link](https://amzn.to/3XNYLER) This is one of my main assortment of LEDs.
- [Amazon search result](https://amzn.to/3Ij0rkg) 7-Segment displays are available with "common cathode" and "common anode". I bought mine from Vetco Electronics in Bellevue, WA, USA -- so I don't have an Amazon link to recommend. For "common cathode" one pin goes to GND and the Flipper Zero GPIO output pins need to drive output to 3.3volts. For "common anode" one pin goes to 3.3volts or 5.0volts and the Flipper Zero GPIO output pins need to drive output to GND. Both configurations are possible; so either will work fine. Most people like "common cathode" because then you write a GPIO 'true' to turn the LED on (instead of 'true' to turn it off.)
### Misc
-[Amazon link](https://amzn.to/3ZMzzQk) This is a 125KHz RFID reader with keypad. The output is 4-bit Wiegand for the keypad and 26/34-bit Wiegand for the RFID reader. I bought this to use in my Wiegand video.
## Not-Disclaimer
Amazon has currently suspended by affilate commisions, so when you purchase through links on this page I will not earn an affiliate commission. #nocommissionsearned

View File

@ -0,0 +1,80 @@
# GPIO 7-Segment Output
## Introduction
This is a GPIO push-pull output demo application for driving a 7-segment display on the Flipper Zero. The goal of this project is to show application developers how GPIO works for push-pull output. This project was derived from the [\plugins\basic](..\..\plugins\basic\README.md) tutorial.
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "gpio_7segment" [folder](..) to the \applications\plugins\gpio_7segment folder in your firmware.
- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the gpio_output_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Applications" from the menu.
- Choose "Gpio" from the sub-menu.
- Choose "GPIO 7-Segment Output"
- The flipper should say "GPIO 7-Segment".
- Click the "OK" button to display a random number from 1-6.
- Press the BACK button to exit.
## How it works
- application.fam
- specifies the settings for our application.
- adds "gpio" to requires.
- gpio_7segment.png
- The icon for our application that shows up in the "Misc" folder.
- gpio_7segment_app.c
- Similar to the plugin\basic tutorial.
- bool digits[70] is defined to store the segments to glow for different digits.
- each group of 7 booleans represents a number.
- note: in production code you could replace with a single byte [8-bits] to represent the segments to turn on/off. Like bit 0 is middle LED, bit 1 is bottom right, etc.
- gpio_7segment_render_callback(...) callback paints the canvas.
- Title of application
- two boxes (looks like a 7-segment display)
- index is set to the first index with our LED information
- for example to draw a 2 we index at 7*2 = 14 (which is the 15th bool, since it starts at a count of 0.)
- digits[index++] ? "A7" : "a7"
- digits[index] is the bool (true/false) from our digits table.
- if it is true we will use the text "A7" (CAPS to show on)
- if it is false we will use the text "a7" (lowercase)
- index++ will increment index to the next number.
- data->invert ? "GND" : "3.3v"
- if invert is set to true we show the text "GND" (pins will GND to glow.)
- if invert is set to false we show the text "3.3v" (pins will 3.3 to glow.)
- We show the number we are displaying on the 7-segment display.
- gpio_7segment_show(...) displays a number.
- index is set to the first index with our LED information
- for example to draw a 2 we index at 7*2 = 14 (which is the 15th bool, since it starts at a count of 0.)
- furi_hal_gpio_write(&gpio_ext_pa7, digits[index++] ^ invert);
- THIS IS THE CODE THAT CHANGES THE STATE OF THE PIN!
- gpio_ext_pa7 is the pin (a7). You can see all pin names at \firmware\targets\f7\furi_hal\furi_hal_resources.c
- digits[index++] ^ invert
- digits[index] is the bool (true/false) from our digits table.
- if invert is false, (digits[index++] ^ false) == digits[index++] (so same as table.)
- if invert it true, (digits[index++] ^ true) == !digits[index++] (so inverted from table value. This is needed for common anode 7-segment LEDs.)
- gpio_7segment_disconnect_pin(...) disconnects a pin.
- GpioModeOutputOpenDrain means that when the value is false the pin will be GND, but when the value is true the pin will be disconnected.
- GpioPullNo means there is no pull-up resistor between 3.3v and pin & there is no pull-down resistor between GND and pin.
- we write a true, so pin is disconnected.
- Some people use Input with Push/Pull-No to disconnect pins while others use OutputOpenDrain. I'm not sure if the pins are really disconnected just because you aren't reading, so I think I would prefer to use OutputOpenDrain.
- gpio_7segment_app(...) is our entry point
- furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
- this sets the A7 pin to be OutputPushPull.
- PushPull means pin will either be GND or 3.3volts.
- (As opposed to OutputOpenDrain when pin is GND or disconnected.)
- We use "init_simple" instead of "init", because other params aren't needed.
- context->data->digit = (furi_hal_random_get() % 6) + 1;
- when OK button is pressed we use furi_hal_random_get() to get a random number (I think from the secure random number generator).
- (x % 6) will return the remainder from dividing x by 6. Which should be a number between 0 and 5. We then add 1 to the value so we get a value between 1 and 6.
- gpio_7segment_disconnect_pin(&gpio_ext_pa7);
- when our app exits, we disconnect the pins. If we didn't the number would still show.

View File

@ -0,0 +1,10 @@
App(
appid="gpio_7segment_output",
name="GPIO 7-Segment Output",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_7segment_app",
requires=["gui", "gpio"],
stack_size=2 * 1024,
fap_icon="gpio_7segment.png",
fap_category="Gpio",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,277 @@
/*
@CodeAllNight
https://github.com/jamisonderek/flipper-zero-tutorials
This app controlls a 7-segment display connected to the Flipper Zero GPIO pins.
WARNING: You should connect 220 ohm resistors to each of your output pins! The
Flipper Zero only has a 51ohm internal resistor and has a MAX rating of 20mA per
pin. Without a resistor you could potentially be at 65mA per pin (even with a
1.5v drop across the LED you would still be at 35mA!)
When you run the app, it will display which pins should control which segments.
Be sure to connect your common cathode pin to GND (or common anode pin to 3.3volts.)
WARNING: You should connect 220 ohm resistors to each of your output pins.
All pins will initially have 3.3volts (so you can test that all segments are
connected properly). Pressing the UP arrow inverts the output (so instead of
3.3volts for making LED glow, it will send GND signal) this is needed if you are
using common anode instead of common cathode 7-segment LEDs.
Press the OK button to have the Flipper randomly guess a number between 1-6
(I used this app to replace a 6-sided die.)
*/
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <locale/locale.h>
/*
Our 7-segment display is connected in the following order...
segment 0 = PA7, segment 1 = PA6, segment 2 = PA4, segment 3 = PB3
segment 4 = PB2, segment 5 = PC3, segment 6 = PC1
Common cathode connection goes to GND pin.
We output a 3.3V to turn LED on and a GND to turn LED off.
seg:
55555
4 6
4 6
00000
3 1
3 1
22222
//seg:0 , 1 , 2 , 3 , 4 , 5 , 6
0 = false, true, true, true, true, true, true
1 = false, true, false, false, false, false, true
2 = true, false, true, true, false, true, true
3 = true, true, true, false, false, true, true
4 = true, true, false, false, true, false, true
5 = true, true, true, false, true, true, false
6 = true, true, true, true, true, true, false
7 = false, true, false, false, false, true, true
8 = true, true, true, true, true, true, true
9 = true, true, true, false, true, true, true
*/
#define TAG "gpio_7segment_app"
bool digits[70] = {
/* 0 */ false, true, true, true, true, true, true,
/* 1 */ false, true, false, false, false, false, true,
/* 2 */ true, false, true, true, false, true, true,
/* 3 */ true, true, true, false, false, true, true,
/* 4 */ true, true, false, false, true, false, true,
/* 5 */ true, true, true, false, true, true, false,
/* 6 */ true, true, true, true, true, true, false,
/* 7 */ false, true, false, false, false, true, true,
/* 8 */ true, true, true, true, true, true, true,
/* 9 */ true, true, true, false, true, true, true,
};
typedef enum {
GpioEventTypeKey,
} GpioEventType;
typedef struct {
GpioEventType type; // The reason for this event.
InputEvent input; // This data is specific to keypress data.
} GpioEvent;
typedef struct {
FuriString* buffer;
int digit;
bool invert;
} GpioData;
typedef struct {
FuriMessageQueue* queue; // Message queue (GpioEvent items to process).
FuriMutex* mutex; // Used to provide thread safe access to data.
GpioData* data; // Data accessed by multiple threads (acquire the mutex before accessing!)
} GpioContext;
// Invoked when input (button press) is detected. Queues message and returns to caller.
// @param input_event the input event that caused the callback to be invoked.
// @param queue the message queue for queueing key event.
static void gpio_7segment_input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
GpioEvent event = {.type = GpioEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
// Invoked by the draw callback to render the screen.
// @param canvas surface to draw on.
// @param ctx a pointer to the application GpioContext.
static void gpio_7segment_render_callback(Canvas* canvas, void* ctx) {
// Attempt to aquire context, so we can read the data.
GpioContext* context = ctx;
if(furi_mutex_acquire(context->mutex, 200) != FuriStatusOk) {
return;
}
GpioData* data = context->data;
// Title
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 5, 0, AlignLeft, AlignTop, "GPIO 7-Segment LED");
// Draw two boxes (7-segment LEDs)
canvas_draw_frame(canvas, 45, 22, 20, 16);
canvas_draw_frame(canvas, 45, 37, 20, 16);
// Draw the labels (use CAPS if LED should be glowing.)
int index = 7 * data->digit;
canvas_draw_str_aligned(canvas, 50, 39, AlignLeft, AlignTop, digits[index++] ? "A7" : "a7");
canvas_draw_str_aligned(canvas, 66, 39, AlignLeft, AlignTop, digits[index++] ? "A6" : "a6");
canvas_draw_str_aligned(canvas, 50, 54, AlignLeft, AlignTop, digits[index++] ? "A4" : "a4");
canvas_draw_str_aligned(canvas, 32, 39, AlignLeft, AlignTop, digits[index++] ? "B3" : "b3");
canvas_draw_str_aligned(canvas, 32, 26, AlignLeft, AlignTop, digits[index++] ? "B2" : "b2");
canvas_draw_str_aligned(canvas, 50, 13, AlignLeft, AlignTop, digits[index++] ? "C3" : "c3");
canvas_draw_str_aligned(canvas, 66, 26, AlignLeft, AlignTop, digits[index++] ? "C1" : "c1");
// Tell user if GPIO pins are GND or 3.3v to glow.
canvas_draw_str_aligned(canvas, 90, 40, AlignLeft, AlignTop, data->invert?"GND":"3.3v");
// Display the current number.
furi_string_printf(data->buffer, "Digit: %d", data->digit);
canvas_draw_str_aligned(canvas, 90, 50, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
// Release the context, so other threads can update the data.
furi_mutex_release(context->mutex);
}
// Sets the GPIO pin output to display a number.
// @param digit a value between 0-9 to display.
// @param invert use true if your 7-segment LED display is common anode
// (and all the output pins go to cathode side of LEDs). use false if
// your display is common cathode.
void gpio_7segment_show(int digit, bool invert) {
// There are 7 segments per digit.
int index = 7 * digit;
furi_hal_gpio_write(&gpio_ext_pa7, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pa6, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pa4, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pb3, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pb2, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pc3, digits[index++] ^ invert);
furi_hal_gpio_write(&gpio_ext_pc1, digits[index++] ^ invert);
}
// Disconnects a GpioPin via OutputOpenDrive, PushPullNo, output true.
// @pin pointer to GpioPin to disconnect.
void gpio_7segment_disconnect_pin(const GpioPin* pin) {
furi_hal_gpio_init(pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(pin, true);
}
// This is the entry point to our application.
int32_t gpio_7segment_app(void* p) {
UNUSED(p);
// Configure our initial data.
GpioContext* context = malloc(sizeof(GpioContext));
context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
context->data = malloc(sizeof(GpioData));
context->data->buffer = furi_string_alloc();
context->data->digit = 8;
context->data->invert = false;
// Queue for events (input)
context->queue = furi_message_queue_alloc(8, sizeof(GpioEvent));
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, gpio_7segment_render_callback, context);
view_port_input_callback_set(view_port, gpio_7segment_input_callback, context->queue);
// Set our 7 GPIO external pins to output (push-pull).
// NOTE: For common anode LED, we *could* use GpioModeOutputOpenDrain.
furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pa6, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pb3, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pb2, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
// Display the number (this should light all 7 of the LED segments)
gpio_7segment_show(context->data->digit, context->data->invert);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Main loop
GpioEvent event;
bool processing = true;
do {
if (furi_message_queue_get(context->queue, &event, FuriWaitForever) == FuriStatusOk) {
FURI_LOG_T(TAG, "Got event type: %d", event.type);
switch (event.type) {
case GpioEventTypeKey:
// Short press of back button exits the program.
if (event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
FURI_LOG_I(TAG, "Short-Back pressed. Exiting program.");
processing = false;
} else if (event.input.type == InputTypeShort && event.input.key == InputKeyOk) {
FURI_LOG_I(TAG, "OK pressed.");
if (furi_mutex_acquire(context->mutex, FuriWaitForever) == FuriStatusOk) {
// Pick a random number between 1 and 6...
context->data->digit = (furi_hal_random_get() % 6) + 1;
gpio_7segment_show(context->data->digit, context->data->invert);
furi_mutex_release(context->mutex);
}
} else if (event.input.type == InputTypeShort && event.input.key == InputKeyUp) {
FURI_LOG_I(TAG, "UP pressed.");
if (furi_mutex_acquire(context->mutex, FuriWaitForever) == FuriStatusOk) {
// Invert our output (switch between common anode/cathode 7-segment LEDs)
context->data->invert = !context->data->invert;
gpio_7segment_show(context->data->digit, context->data->invert);
furi_mutex_release(context->mutex);
}
}
break;
default:
break;
}
// Send signal to update the screen (callback will get invoked at some point later.)
view_port_update(view_port);
} else {
// We had an issue getting message from the queue, so exit application.
processing = false;
}
} while (processing);
// Disconnect the GPIO pins.
gpio_7segment_disconnect_pin(&gpio_ext_pa7);
gpio_7segment_disconnect_pin(&gpio_ext_pa6);
gpio_7segment_disconnect_pin(&gpio_ext_pa4);
gpio_7segment_disconnect_pin(&gpio_ext_pb3);
gpio_7segment_disconnect_pin(&gpio_ext_pb2);
gpio_7segment_disconnect_pin(&gpio_ext_pc3);
gpio_7segment_disconnect_pin(&gpio_ext_pc1);
// Free resources
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(context->queue);
furi_mutex_free(context->mutex);
furi_string_free(context->data->buffer);
free(context->data);
free(context);
return 0;
}

View File

@ -0,0 +1,9 @@
#gpio_badge
This is an app for the [GPIO Diagnostics Card](https://tindie.com/stores/MakeItHackin) by MakeItHackin.
## Instructions
- Left/Right buttons to change speed. Up/Down buttons to change effect.
- When effect is Left/Right, you can use OK button to stop/start (try to get it to start near the middle RED led!)
- Back button to exit.

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

20
gpio/gpio_badge/README.md Normal file
View File

@ -0,0 +1,20 @@
#gpio_badge
This is an app for the [GPIO DIAGNOSTICS BOARD FOR FLIPPER ZERO](https://github.com/MakeItHackin/FlipperDiagnosticsBoard) by [MakeItHackin](https://github.com/MakeItHackin).
## Instructions
- Left/Right buttons to change speed of the effect.
- Up/Down buttons to change effect.
- When effect is Left to right lights, you can use OK button to stop/start (try to get it to stop near the middle RED led!)
- Back button to exit.
## Installing
The easiest way to install is [flipc.org](https://flipc.org/jamisonderek/flipper-zero-tutorials?branch=main&root=gpio%2Fgpio_badge), choose your firmware, then click Install. The "GPIO Badge" is currently in the Misc category.
## Feature requests
Got ideas that you think would be cool to add? Drop your ideas in Discord [Discord invite: NsjCvqwPAd](https://discord.com/invite/NsjCvqwPAd).
Looking for videos on how to write your own C code for the Flipper Zero? Check out this [playlist](https://www.youtube.com/playlist?list=PLM1cyTMe-PYJCYA-yQ8OcC9jILsS004Tm) on GPIO.

View File

@ -0,0 +1,10 @@
App(
appid="gpio_badge",
name="GPIO Badge",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_diag_lights_app",
requires=["gui", "subghz"],
stack_size=2 * 1024,
fap_icon="gpio_badge.png",
fap_category="Misc",
)

View File

@ -0,0 +1,206 @@
#include <furi.h>
#include <gui/gui.h>
const GpioPin* const pin_leds[] = {
&gpio_ext_pa7,
&gpio_ext_pa6,
&gpio_ext_pa4,
&gpio_ext_pb3,
&gpio_ext_pb2,
&gpio_ext_pc3,
&gpio_swclk,
&gpio_swdio,
&gpio_usart_tx,
&gpio_usart_rx,
&gpio_ext_pc1,
&gpio_ext_pc0,
&gpio_ibutton,
};
uint32_t speed = 50;
typedef enum {
EffectIdLRL,
EffectIdBothLRL,
EffectIdLR,
EffectIdCounter,
EffectIdCount // Make sure this is last
} EffectId;
EffectId effect = EffectIdLR;
bool animating = true;
int lri = 0;
#define MAX_EFFECT 1
const GpioPin* const pin_back = &gpio_button_back;
static void my_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 8, "GPIO BADGE");
canvas_draw_str(canvas, 5, 28, "Left/Right : Speed");
canvas_draw_str(canvas, 5, 38, "Up/Down : Effect");
}
static void my_input_callback(InputEvent* event, void* context) {
UNUSED(context);
if(event->type == InputTypePress) {
uint32_t last_speed = speed;
if(event->key == InputKeyLeft) {
speed *= 0.9;
if(speed < 1) {
speed = 1;
}
} else if(event->key == InputKeyRight) {
speed *= 1.1;
if(speed < last_speed + 5) {
speed += 5;
}
} else if(event->key == InputKeyUp) {
if(effect-- == 0) {
animating = true;
effect = EffectIdCount - 1;
}
} else if(event->key == InputKeyDown) {
animating = true;
if(++effect == EffectIdCount) {
effect = 0;
}
} else if(event->key == InputKeyOk) {
animating = !animating;
}
}
}
void do_effect_lrl() {
int i = 0;
int d = 1;
for(size_t c = 0; c < COUNT_OF(pin_leds) * 2; c++) {
furi_hal_gpio_write(pin_leds[i], true);
furi_delay_ms(speed);
furi_hal_gpio_write(pin_leds[i], false);
i += d;
if(i == COUNT_OF(pin_leds) || i < 0) {
if(i < 1)
i++;
else
i--;
d = d * -1;
}
if(furi_hal_gpio_read(pin_back) == false || effect != EffectIdLRL) {
break;
}
}
}
void do_effect_both_lrl() {
int i = 0;
int d = 1;
for(size_t c = 0; c < COUNT_OF(pin_leds) * 2; c++) {
furi_hal_gpio_write(pin_leds[i], true);
furi_hal_gpio_write(pin_leds[COUNT_OF(pin_leds) - (i + 1)], true);
furi_delay_ms(speed);
furi_hal_gpio_write(pin_leds[i], false);
furi_hal_gpio_write(pin_leds[COUNT_OF(pin_leds) - (i + 1)], false);
i += d;
if(i == COUNT_OF(pin_leds) || i < 0) {
if(i < 1)
i++;
else
i--;
d = d * -1;
}
if(furi_hal_gpio_read(pin_back) == false || effect != EffectIdBothLRL) {
break;
}
}
}
void do_effect_lr() {
for(size_t c = 0; c < COUNT_OF(pin_leds) * 2; c++) {
furi_hal_gpio_write(pin_leds[lri], true);
furi_delay_ms(speed);
furi_hal_gpio_write(pin_leds[lri], false);
if(animating) {
lri++;
}
if(lri == COUNT_OF(pin_leds)) {
lri = 0;
}
if(furi_hal_gpio_read(pin_back) == false || effect != EffectIdLR) {
break;
}
}
}
void do_effect_counter() {
uint32_t c = 0;
while(true) {
for(size_t i = 0; i < COUNT_OF(pin_leds); i++) {
if((c & (1 << i)) != 0) {
furi_hal_gpio_write(pin_leds[i], true);
}
}
furi_delay_ms(speed);
for(size_t i = 0; i < COUNT_OF(pin_leds); i++) {
furi_hal_gpio_write(pin_leds[i], false);
}
c++;
if(furi_hal_gpio_read(pin_back) == false || effect != EffectIdCounter) {
break;
}
}
}
void do_effects() {
while(furi_hal_gpio_read(pin_back)) {
switch(effect) {
case EffectIdLRL:
do_effect_lrl();
break;
case EffectIdBothLRL:
do_effect_both_lrl();
break;
case EffectIdCounter:
do_effect_counter();
break;
default:
do_effect_lr();
break;
}
}
}
int gpio_diag_lights_app(void* p) {
UNUSED(p);
Gui* gui = furi_record_open(RECORD_GUI);
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, my_draw_callback, NULL);
view_port_input_callback_set(view_port, my_input_callback, NULL);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
for(size_t i = 0; i < COUNT_OF(pin_leds); i++) {
furi_hal_gpio_init_simple(pin_leds[i], GpioModeOutputPushPull);
}
do_effects();
// Typically when a pin is no longer in use, it is set to analog mode.
// but we need to use open drain for the 1Wire pin.
for(size_t i = 0; i < COUNT_OF(pin_leds); i++) {
furi_hal_gpio_init_simple(pin_leds[i], GpioModeAnalog);
}
furi_hal_gpio_init_simple(&gpio_ibutton, GpioModeOutputOpenDrain);
// Remove the directions from the screen.
gui_remove_view_port(gui, view_port);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

57
gpio/gpio_blink/README.md Normal file
View File

@ -0,0 +1,57 @@
# GPIO_BLINK
This is the classic "Hello World" of GPIO, where we blink an LED. You will need an LED (any color is fine) and a resistor (220 ohms - 1 kilohm should be fine). Connect the long side of the LED into pin A7. Connect the short size of the LED to one side of the resistor (doesn't matter which side). Connect the other side of the resistor to any of the pin GND.
Run the application and the LED should start blinking. Hold the back button to exit.
YouTube video that demonstrates blinking the LED:
[![YouTube video that demonstrates blinking the LED](https://img.youtube.com/vi/27RtJAxY2RY/0.jpg)](https://youtu.be/27RtJAxY2RY)
- [https://youtu.be/27RtJAxY2RY](https://youtu.be/27RtJAxY2RY)
## How it works
- For list of pins see [/firmware/targets/f7/furi_hal/furi_hal_resources.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/firmware/targets/f7/furi_hal/furi_hal_resources.c) in your firmware repo.
- We define a variable for our LED...
```c
const GpioPin* const pin_led = &gpio_ext_pa7;
```
- We initialize the pin for output.
- ``GpioModeOutputPushPull`` means true = 3.3 volts, false = 0 volts.
- ``GpioModeOutputOpenDrain`` means true = floating, false = 0 volts.
```c
furi_hal_gpio_init_simple(pin_led, GpioModeOutputPushPull);
```
- We turn the LED on (3.3 volts on the pin).
```c
furi_hal_gpio_write(pin_led, true);
```
- We wait 500 ms.
```c
furi_delay_ms(500);
```
- We turn the LED off (0 volts on the pin).
```c
furi_hal_gpio_write(pin_led, false);
```
- We wait 500 ms.
```c
furi_delay_ms(500);
```
- We loop if the back button is not currently pressed.
```c
while(furi_hal_gpio_read(pin_back))
```
- Typically when a pin is no longer in use, it is set to analog mode. This causes the pin to be a floating input.
```c
furi_hal_gpio_init_simple(pin_led, GpioModeAnalog);
```
## Other ideas
- We could connect the long side of LED (anode) to +3.3volts and then the resistor to pin A7. We could then use `GpioModeOutputOpenDrain` and then when the A7 pin is pulled to GND the LED would turn on; this is called "active low".

View File

@ -0,0 +1,10 @@
App(
appid="gpio_blink",
name="GPIO Blink",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_blink_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="blink.png",
fap_category="GPIO",
)

47
gpio/gpio_blink/blink.c Normal file
View File

@ -0,0 +1,47 @@
#include <furi.h>
#include <gui/gui.h>
// For list of pins see https://github.com/flipperdevices/flipperzero-firmware/blob/dev/firmware/targets/f7/furi_hal/furi_hal_resources.c
const GpioPin* const pin_led = &gpio_ext_pa7;
const GpioPin* const pin_back = &gpio_button_back;
static void my_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 8, "GPIO - BLINK LED DEMO");
canvas_draw_str(canvas, 5, 22, "Connect long LED to pin");
canvas_draw_str(canvas, 5, 32, "A7. Connect short LED");
canvas_draw_str(canvas, 5, 42, "to 220 ohm resistor.");
canvas_draw_str(canvas, 5, 52, "Connect other end of");
canvas_draw_str(canvas, 5, 62, "resistor to pin GND.");
}
int gpio_blink_app(void* p) {
UNUSED(p);
// Show directions to user.
Gui* gui = furi_record_open(RECORD_GUI);
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, my_draw_callback, NULL);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Initialize the LED pin as output.
// GpioModeOutputPushPull means true = 3.3 volts, false = 0 volts.
// GpioModeOutputOpenDrain means true = floating, false = 0 volts.
furi_hal_gpio_init_simple(pin_led, GpioModeOutputPushPull);
do {
furi_hal_gpio_write(pin_led, true);
furi_delay_ms(500);
furi_hal_gpio_write(pin_led, false);
furi_delay_ms(500);
// Hold the back button to exit (since we only scan it when restarting loop).
} while(furi_hal_gpio_read(pin_back));
// Typically when a pin is no longer in use, it is set to analog mode.
furi_hal_gpio_init_simple(pin_led, GpioModeAnalog);
// Remove the directions from the screen.
gui_remove_view_port(gui, view_port);
return 0;
}

BIN
gpio/gpio_blink/blink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,31 @@
# GPIO_BLINK_PWM
This is the classic "Hello World" of GPIO, where we blink an LED using PWM (Pulse Width Modulation). You will need an LED (any color is fine) and a resistor (220 ohms - 1 kilohm should be fine). Connect the long side of the LED into pin A7. Connect the short size of the LED to one side of the resistor (doesn't matter which side). Connect the other side of the resistor to any of the pin GND.
Run the application and the LED should start blinking. Hold the back button to exit.
## How it works
- Pulse width modulation is supported on pin A7 using FuriHalPwmOutputIdTim1PA7. You can also do pulse width modulation on pin A4 using FuriHalPwmOutputIdLptim2PA4. Using PWM has the advantage that the voltage on the pins transition automatically at some fixed frequency and duty cycle, without your code having to keep changing the state of the pins. The PWM library only supports an integer for the frequency, so it will not work for if you need to blink slower than 1 Hz (1 time per second). The duty cycle is the percentage of time the light should be on (so it should typically be a number between 1 and 99).
- We define a variable for our LED...
```c
const FuriHalPwmOutputId channel_led = FuriHalPwmOutputIdTim1PA7;
```
- We start the PWM timer, blinking LED at 1 Hz, 20% duty cycle (the pin will be at 3.3 volts 20% of the time, then 0 volts the remainder).
```c
furi_hal_pwm_start(channel_led, 1, 20);
```
- In a loop we sleep if the back button is not currently pressed.
```c
while(furi_hal_gpio_read(pin_back)) {
furi_delay_ms(100);
}
```
- We stop the PWM timer. This stops the timer and sets the pin to analog (floating input).
```c
furi_hal_pwm_stop(channel_led);
```

View File

@ -0,0 +1,10 @@
App(
appid="gpio_blink_pwm",
name="GPIO Blink PWM",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_blink_pwm_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="blink_pwm.png",
fap_category="GPIO",
)

View File

@ -0,0 +1,43 @@
#include <furi_hal.h>
#include <furi_hal_pwm.h>
#include <gui/gui.h>
// Pin A7 is FuriHalPwmOutputIdTim1PA7, pin A4 is FuriHalPwmOutputIdLptim2PA4.
const FuriHalPwmOutputId channel_led = FuriHalPwmOutputIdTim1PA7;
const GpioPin* const pin_back = &gpio_button_back;
static void my_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 8, "GPIO - BLINK PWM LED");
canvas_draw_str(canvas, 5, 22, "Connect long LED to pin");
canvas_draw_str(canvas, 5, 32, "A7. Connect short LED");
canvas_draw_str(canvas, 5, 42, "to 220 ohm resistor.");
canvas_draw_str(canvas, 5, 52, "Connect other end of");
canvas_draw_str(canvas, 5, 62, "resistor to pin GND.");
}
int gpio_blink_pwm_app(void* p) {
UNUSED(p);
// Show directions to user.
Gui* gui = furi_record_open(RECORD_GUI);
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, my_draw_callback, NULL);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Start blinking LED at 1 Hz, 20% duty cycle
furi_hal_pwm_start(channel_led, 1, 20);
// Hold the back button to exit (since we only scan it when restarting loop).
while(furi_hal_gpio_read(pin_back)) {
furi_delay_ms(100);
}
// Stop blinking LED (floating input)
furi_hal_pwm_stop(channel_led);
// Remove the directions from the screen.
gui_remove_view_port(gui, view_port);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,70 @@
# BASIC DEMO
## Introduction
This is a basic demo of using GPIO interrupts.
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "gpio_interrupt_demo" [folder](..) to the \applications\plugins\gpio_interrupt_demo folder in your firmware.
- Build &amp; deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the gpio_interrupt_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
## Connecting the hardware
GPIO pin C3 is our interrupt pin (with internal pull-up resistor). I connect pin
to a 220 ohm resistor and then the other side of the resistor to one side of a
momentary switch. Other side of the switch connects to our GND pin.
GPIO A7, A6 and A4 connect to 220ohm resistors then the + or - pins of LED...
LED1: +A7 -A6
LED2: +A6 -A7
LED3: +A7 -A4
LED4: +A4 -A7
LED5: +A6 -A4
LED6: +A4 -A6
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Applications" from the menu.
- Choose "GPIO" from the sub-menu.
- Choose "GPIO Interrupt Demo"
- Message "GND pin C3 to stop." should display
- LEDs should blink in order.
- press switch (or connect wire/resistor between C3 and GND)
- LEDs should stop and display should say "Stopped on LED 4." (or whatever LED was lit.)
- Press DOWN button on Flipper Zero to start over.
- Press the BACK button to exit.
## How it works
- application.fam
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo.png
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo_app.c
- GPIO specific code:
- furi_hal_gpio_init(&gpio_ext_pc3, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh);
- gpio_ext_pc3 is the GPIO pin we are using for interrupt input.
- GpioModeInterruptFall - means trigger callback on falling edge (VCC->GND)
- GpioModeInterruptRise - means trigger callback on rising edge (GND->VCC)
- GpioModeInterruptRiseFall - means trigger callback on either edge
- GpioPullUp - Means enable internal pull-up resistor between VCC and pin.
- GpioPullDown - Means enable internal pull-down resistor between GND and pin.
- GpioPullNo - Means do not enable internal pull-up/pull-down resistor.
- GpioSpeedVeryHigh - priority of input?
- furi_hal_gpio_add_int_callback(&gpio_ext_pc3, demo_gpio_fall_callback, NULL);
- gpio_ext_pc3 is the GPIO pin we are using for interrupt input.
- second parameter is callback with signature: "void fnName(void* ctx)"
- third parameter is object to pass into callback for context (or NULL if no object).
- once furi_hal_gpio_add_int_callback is invoked, the callback is enabled.
- demo_gpio_fall_callback(void* ctx)
- this method gets invoked by our callback (because of previous add_int_callback) wheneven pin C3 gets pulled to GND.
- interrupt callbacks should try to do mininal amount of processing.
- furi_hal_gpio_remove_int_callback(&gpio_ext_pc3);
- removes the callback, so method is no longer invoked on pin transistion.

View File

@ -0,0 +1,10 @@
App(
appid="gpio_interrupt_demo",
name="GPIO Interrupt Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_interrupt_demo_app",
requires=["gui", "gpio"],
stack_size=2 * 1024,
fap_icon="gpio_interrupt_demo.png",
fap_category="Gpio",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,260 @@
/*
@CodeAllNight
https://github.com/jamisonderek/flipper-zero-tutorials
This is a GPIO interrupt demo for the Flipper Zero. The goal of this project is
to show how interrupt GPIO input can be used in your own application.
GPIO pin C3 is our interrupt pin (with internal pull-up resistor). I connect pin
to a 220 ohm resistor and then the other side of the resistor to one side of a
momentary switch. Other side of the switch connects to our GND pin.
GPIO A7, A6 and A4 connect to 220ohm resistors then the + or - pins of LED...
LED1: +A7 -A6
LED2: +A6 -A7
LED3: +A7 -A4
LED4: +A4 -A7
LED5: +A6 -A4
LED6: +A4 -A6
LEDs blink in order. When switch is pressed, pin C3 transitions from VCC to GND and the
interrupt callback method gets invoked. This copies the current LED number into the
selected LED (which stops LEDs from blinking). Pressing down button clears selected LED,
so LEDs start again.
*/
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <locale/locale.h>
#define TAG "gpio_interrupt_demo_app"
typedef enum {
DemoEventTypeKey,
// You can add additional events here.
DemoEventTypeTick,
} DemoEventType;
typedef struct {
DemoEventType type; // The reason for this event.
InputEvent input; // This data is specific to keypress data.
// You can add additional data that is helpful for your events.
} DemoEvent;
typedef struct {
FuriString* buffer;
// You can add additional state here.
} DemoData;
typedef struct {
FuriMessageQueue* queue; // Message queue (DemoEvent items to process).
FuriMutex* mutex; // Used to provide thread safe access to data.
DemoData* data; // Data accessed by multiple threads (acquire the mutex before accessing!)
} DemoContext;
static uint8_t currentLed; // The current LED to light (1-6)
static uint8_t selectedLed; // The LED at the time of GPIO interrupt (or 0 if none).
// Invoked when input (button press) is detected.
// We queue a message and then return to the caller.
// @input_event the button that triggered the callback.
// @queue our message queue.
static void demo_input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
// Invoked from our timer. We queue a message and then return to the caller.
// @ctx our message queue.
static void demo_tick(void* ctx) {
furi_assert(ctx);
FuriMessageQueue* queue = ctx;
DemoEvent event = {.type = DemoEventTypeTick};
furi_message_queue_put(queue, &event, 0);
}
// Invoked when our GPIO pin transitions from VCC to GND.
// @ctx unused for this method.
static void demo_gpio_fall_callback(void* ctx) {
UNUSED(ctx);
selectedLed = currentLed;
}
// Invoked by the draw callback to render the screen.
// We render our UI on the callback thread.
// @canvas the surface to render our UI
// @ctx a pointer to a DemoContext object.
static void demo_render_callback(Canvas* canvas, void* ctx) {
// Attempt to aquire context, so we can read the data.
DemoContext* demo_context = ctx;
if(furi_mutex_acquire(demo_context->mutex, 200) != FuriStatusOk) {
return;
}
DemoData* data = demo_context->data;
if(selectedLed) {
furi_string_printf(data->buffer, "Stopped on LED %d.", selectedLed);
} else {
furi_string_printf(data->buffer, "GND pin C3 to stop.");
}
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas, 10, 20, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
// Release the context, so other threads can update the data.
furi_mutex_release(demo_context->mutex);
}
// Turns on one of the LEDs.
// @pin the LED to turn on (value of 1-6)
void update_led(uint8_t led) {
if(led == 1 || led == 3) {
// Pin A7 is 3.3 volts for LED 1 & 3.
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_ext_pa7, true);
} else {
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Pin A7 is GND (false) for LED 2 & 4.
furi_hal_gpio_write(&gpio_ext_pa7, !(led == 2 || led == 4));
}
if(led == 2 || led == 5) {
// Pin A6 is 3.3 volts for LED 2 & 5.
furi_hal_gpio_init(&gpio_ext_pa6, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_ext_pa6, true);
} else {
furi_hal_gpio_init(&gpio_ext_pa6, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Pin A6 is GND (false) for LED 1 & 6.
furi_hal_gpio_write(&gpio_ext_pa6, !(led == 1 || led == 6));
}
if(led == 4 || led == 6) {
// Pin A4 is 3.3 volts for LED 4 & 6.
furi_hal_gpio_init(&gpio_ext_pa4, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_ext_pa4, true);
} else {
furi_hal_gpio_init(&gpio_ext_pa4, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Pin A4 is GND (false) for LED 3 & 5.
furi_hal_gpio_write(&gpio_ext_pa4, !(led == 3 || led == 5));
}
}
// Disconnects a GpioPin via OutputOpenDrive, PushPullNo, output true.
// @pin pointer to GpioPin to disconnect.
void disconnect_pin(const GpioPin* pin) {
furi_hal_gpio_init(pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(pin, true);
}
// Program entry point
int32_t gpio_interrupt_demo_app(void* p) {
UNUSED(p);
// Configure our initial data.
DemoContext* demo_context = malloc(sizeof(DemoContext));
demo_context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
demo_context->data = malloc(sizeof(DemoData));
demo_context->data->buffer = furi_string_alloc();
// Queue for events (tick or input)
demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent));
// LEDs connected on GPIO output pins (A7, A6, A4) via 220 ohm resistors.
currentLed = 1;
selectedLed = 0;
update_led(currentLed);
// GPIO pin (C3) is pull-up to VCC. Add switch to ground for change in value.
// I use 220ohm resistor from switch to C3 (but optional if you are SURE
// the pin is in input/interrupt mode).
//
// GpioModeInterruptRiseFall means callback invoked when going from VCC (from our
// pull-up resistor) to GND.
//
// NOTE: You can use GpioModeInterruptRise for invoking on a GND->VCC and
// GpioModeInterruptRiseFall for invoking on both transitions.
//
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh);
// NOTE: "add_int_callback" does "enable_int_callback" automatically.
// For the 3rd parameter, you can pass any object that you want to be passed
// to your callback method.
furi_hal_gpio_add_int_callback(&gpio_ext_pc3, demo_gpio_fall_callback, NULL);
// Timer triggers every 70ms (incrementing our current LED).
FuriTimer* timer = furi_timer_alloc(demo_tick, FuriTimerTypePeriodic, demo_context->queue);
furi_timer_start(timer, 70);
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, demo_render_callback, demo_context);
view_port_input_callback_set(view_port, demo_input_callback, demo_context->queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Main loop
DemoEvent event;
bool processing = true;
do {
if(furi_message_queue_get(demo_context->queue, &event, 1000) == FuriStatusOk) {
FURI_LOG_T(TAG, "Got event type: %d", event.type);
switch(event.type) {
case DemoEventTypeKey:
// Short press of back button exits the program.
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
FURI_LOG_I(TAG, "Short-Back pressed. Exiting program.");
processing = false;
} else if(event.input.type == InputTypeShort && event.input.key == InputKeyDown) {
// Clear our LED stop value.
selectedLed = 0;
}
break;
case DemoEventTypeTick:
if(selectedLed == 0) {
if(++currentLed > 6) {
currentLed = 1;
}
update_led(currentLed);
}
break;
default:
break;
}
// Send signal to update the screen (callback will get invoked at some point later.)
view_port_update(view_port);
}
} while(processing);
// Free resources
furi_hal_gpio_remove_int_callback(&gpio_ext_pc3);
furi_timer_free(timer);
// Pull all pins open.
disconnect_pin(&gpio_ext_pc3);
disconnect_pin(&gpio_ext_pa7);
disconnect_pin(&gpio_ext_pa6);
disconnect_pin(&gpio_ext_pa4);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(demo_context->queue);
furi_mutex_free(demo_context->mutex);
furi_string_free(demo_context->data->buffer);
free(demo_context->data);
free(demo_context);
return 0;
}

View File

@ -1,5 +1,5 @@
App(
appid="Gpio_Polling_Demo",
appid="gpio_polling_demo",
name="Gpio Polling Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="gpio_polling_demo_app",

26
gpio/hc_sr04/README.md Normal file
View File

@ -0,0 +1,26 @@
# HC_SR04
## Description
The original version is from [SquachWare hc_sr04](https://github.com/skizzophrenic/SquachWare-CFW/tree/dev/applications/plugins/hc_sr04).
This version modifies the code to use furi_hal_cortex_timer_get(0) to
obtain a high precision counter. We also tune the calculations based on measured observations. The display shows 2 digits after the decimal point, displaying in both cm and inches.
## Pins
- (Flipper -> HC_SR04 device)
- (5V -> VCC)
- (GND -> GND)
- (13|TX -> Trig)
- (14|RX -> Echo)
## Code changes
- #include <furi_hal_cortex.h>
- This has the definitions for FuriHalCortexTimer and furi_hal_cortex_timer_get(...)
- FuriHalCortexTimer beginTimer = furi_hal_cortex_timer_get(0);
- This gets the current time in beginTimer.start.
- The offset is 0 (we could use offset ).
- furi_string_printf(str_buf, "Distance: %0.2f cm", (double)plugin_state->distance);
- This rounds the output to the nearest hundredths place.
- hc_sr04_duration_to_cm(...)
- I have no idea why total_dist needs to be multiplied by 1.588, but doing so seems to make all of the measurements correct.
- The code overhead, delays in IN/OUT, etc. seem to take the same time as sound tranvelling for 0.497 cm there and back; so we add this fixed constant. If you Flipper was faster/slower you would need to adjust this constant.

View File

@ -0,0 +1,14 @@
App(
appid="hc_sr04_dist_sensor",
name="[HC-SR] Dist. Sensor",
apptype=FlipperAppType.EXTERNAL,
entry_point="hc_sr04_app",
cdefines=["APP_HC_SR04"],
requires=[
"gui",
],
stack_size=2 * 1024,
order=20,
fap_icon="dist_sensor10px.png",
fap_category="GPIO",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

280
gpio/hc_sr04/hc_sr04.c Normal file
View File

@ -0,0 +1,280 @@
// insired by
// https://github.com/esphome/esphome/blob/ac0d921413c3884752193fe568fa82853f0f99e9/esphome/components/ultrasonic/ultrasonic_sensor.cpp
// Ported and modified by @xMasterX
#include <furi.h>
#include <furi_hal_power.h>
#include <furi_hal_console.h>
#include <furi_hal_cortex.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <gui/elements.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
NotificationApp* notification;
bool have_5v;
bool measurement_made;
uint32_t echo; // us
float distance; // cm
} PluginState;
const NotificationSequence sequence_done = {
&message_display_backlight_on,
&message_green_255,
&message_note_c5,
&message_delay_50,
&message_sound_off,
NULL,
};
static void render_callback(Canvas* const canvas, void* ctx) {
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
// border around the edge of the screen
// canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(
canvas, 64, 2, AlignCenter, AlignTop, "HC-SR04 Ultrasonic\nDistance Sensor");
canvas_set_font(canvas, FontSecondary);
if(!plugin_state->have_5v) {
elements_multiline_text_aligned(
canvas,
4,
28,
AlignLeft,
AlignTop,
"5V on GPIO must be\nenabled, or USB must\nbe connected.");
} else {
if(!plugin_state->measurement_made) {
elements_multiline_text_aligned(
canvas, 64, 28, AlignCenter, AlignTop, "Press OK button to measure");
elements_multiline_text_aligned(
canvas, 64, 40, AlignCenter, AlignTop, "13/TX -> Trig\n14/RX -> Echo");
} else {
FuriString* str_buf;
str_buf = furi_string_alloc();
furi_string_printf(str_buf, "Echo: %0.2f ms", (double)(plugin_state->echo/1000.0f));
canvas_draw_str_aligned(
canvas, 8, 28, AlignLeft, AlignTop, furi_string_get_cstr(str_buf));
furi_string_printf(str_buf, "Distance: %0.2f cm", (double)plugin_state->distance);
canvas_draw_str_aligned(
canvas, 8, 38, AlignLeft, AlignTop, furi_string_get_cstr(str_buf));
furi_string_printf(str_buf, "Distance: %0.2f in", (double)(plugin_state->distance/2.54f));
canvas_draw_str_aligned(
canvas, 8, 48, AlignLeft, AlignTop, furi_string_get_cstr(str_buf));
furi_string_free(str_buf);
}
}
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void hc_sr04_state_init(PluginState* const plugin_state) {
plugin_state->echo = -1;
plugin_state->distance = -1;
plugin_state->measurement_made = false;
furi_hal_power_suppress_charge_enter();
plugin_state->have_5v = false;
if(furi_hal_power_is_otg_enabled() || furi_hal_power_is_charging()) {
plugin_state->have_5v = true;
} else {
furi_hal_power_enable_otg();
plugin_state->have_5v = true;
}
}
uint32_t hc_sr04_duration_to_usActual(uint32_t duration) {
return duration * 1.5888f / 1e2f;
}
float hc_sr04_duration_to_cm(uint32_t duration) {
const float speed_sound_m_per_s = 343.0f;
duration *= 1.588f; // calculated by measurement.
const float time_one_way_sec = duration / 2e6f;
float total_dist = time_one_way_sec * speed_sound_m_per_s;
total_dist += 0.497f; // offset for fixed delays.
return total_dist;
}
static void hc_sr04_measure(PluginState* const plugin_state) {
//plugin_state->echo = 1;
//return;
if(!plugin_state->have_5v) {
if(furi_hal_power_is_otg_enabled() || furi_hal_power_is_charging()) {
plugin_state->have_5v = true;
} else {
return;
}
}
//furi_hal_light_set(LightRed, 0xFF);
notification_message(plugin_state->notification, &sequence_blink_start_yellow);
const uint32_t timeout_ms = 2000;
// Pin 13 / TX -> Trig
furi_hal_gpio_write(&gpio_usart_tx, false);
furi_hal_gpio_init(&gpio_usart_tx, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
// Pin 14 / RX -> Echo
furi_hal_gpio_write(&gpio_usart_rx, false);
furi_hal_gpio_init(&gpio_usart_rx, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
//FURI_CRITICAL_ENTER();
// 10 ms pulse on TX
furi_hal_gpio_write(&gpio_usart_tx, true);
furi_delay_ms(10);
furi_hal_gpio_write(&gpio_usart_tx, false);
const uint32_t start = furi_get_tick();
while(furi_get_tick() - start < timeout_ms && furi_hal_gpio_read(&gpio_usart_rx))
;
while(furi_get_tick() - start < timeout_ms && !furi_hal_gpio_read(&gpio_usart_rx))
;
FuriHalCortexTimer beginTimer = furi_hal_cortex_timer_get(0);
while(furi_get_tick() - start < timeout_ms && furi_hal_gpio_read(&gpio_usart_rx))
;
FuriHalCortexTimer endTimer = furi_hal_cortex_timer_get(0);
//FURI_CRITICAL_EXIT();
uint32_t duration = endTimer.start - beginTimer.start;
plugin_state->echo = hc_sr04_duration_to_usActual(duration);
plugin_state->distance = hc_sr04_duration_to_cm(duration);
plugin_state->measurement_made = true;
//furi_hal_light_set(LightRed, 0x00);
notification_message(plugin_state->notification, &sequence_blink_stop);
notification_message(plugin_state->notification, &sequence_done);
}
int32_t hc_sr04_app() {
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
hc_sr04_state_init(plugin_state);
furi_hal_console_disable();
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E("hc_sr04", "cannot create mutex\r\n");
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_disable_otg();
}
furi_hal_console_enable();
furi_hal_power_suppress_charge_exit();
furi_message_queue_free(event_queue);
free(plugin_state);
return 255;
}
plugin_state->notification = furi_record_open(RECORD_NOTIFICATION);
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
case InputKeyDown:
case InputKeyRight:
case InputKeyLeft:
break;
case InputKeyOk:
hc_sr04_measure(plugin_state);
break;
case InputKeyBack:
processing = false;
break;
default:
break;
}
}
}
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_disable_otg();
}
furi_hal_power_suppress_charge_exit();
// Return TX / RX back to usart mode
furi_hal_gpio_init_ex(
&gpio_usart_tx,
GpioModeAltFunctionPushPull,
GpioPullUp,
GpioSpeedVeryHigh,
GpioAltFn7USART1);
furi_hal_gpio_init_ex(
&gpio_usart_rx,
GpioModeAltFunctionPushPull,
GpioPullUp,
GpioSpeedVeryHigh,
GpioAltFn7USART1);
furi_hal_console_enable();
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
return 0;
}

51
gpio/i2c_demo/README.md Normal file
View File

@ -0,0 +1,51 @@
# I2C DEMO
## Introduction
This is a basic demonstration of reading/writing I2C protocol.
For this demo, we connect a I2C device to pins:
- 3V3 (3V3, pin 9) = VCC
- GND (GND, pin 18) = GND
- SCL (C0, pin 16) = SCL
- SDA (C1, pin 15) = SDA
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "i2c_demo" [folder](..) to the \applications\plugins\i2c_demo folder in your firmware.
- Build &amp; deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the i2c_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Gpio folder.
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Applications" from the menu.
- Choose "GPIO" from the sub-menu.
- Choose "I2C Demo"
- The flipper should say "I2C NOT FOUND" if no I2C devices are connected.
- NOTE: If your I2C device already has pull-up resistors, then you do not need to add them to your SCL and SDA lines.
- Connect an I2C device (like a BH1750) to pins 9 (3V3), 16 (SCL), 15 (SDA), 18 (GND).
- The message should change to "FOUND I2C DEVICE"
- The next line should have the address of the I2C device.
- If the device is a BH1750 then you should also see "WRITE/READ SUCCESS" and a value that changes with the brightness on the sensor.
- Press the BACK button to exit.
## How it works
- application.fam
- specifies the name of our application.
- specifies the entry point for our application.
- specifies we use the GUI.
- specifies our icon is the i2c_demo.png file.
- specifies our application can be found in the "GPIO" category.
- i2c_demo.png
- The icon for our application that shows up in the "GPIO" folder.
- i2c_demo_app.c
- This is the demo application that uses I2C.

View File

@ -0,0 +1,10 @@
App(
appid="i2c_demo",
name="I2C Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="i2c_demo_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="i2c_demo.png",
fap_category="GPIO",
)

BIN
gpio/i2c_demo/i2c_demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,329 @@
/*
@CodeAllNight
https://github.com/jamisonderek/flipper-zero-tutorials
This is a basic demonstration of reading/writing I2C protocol.
For this demo, we connect a I2C device to pins:
- 3V3 (3V3, pin 9) = VCC
- GND (GND, pin 18) = GND
- SCL (C0, pin 16) = SCL
- SDA (C1, pin 15) = SDA
*/
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <locale/locale.h>
typedef enum {
I2cDemoStateNotFound,
I2cDemoStateFound,
I2cDemoStateWriteSuccess,
I2cDemoStateReadSuccess,
I2cDemoStateWriteReadSuccess,
} I2cDemoState;
typedef enum {
DemoEventTypeTick,
DemoEventTypeKey,
// You can add additional events here.
} DemoEventType;
typedef struct {
DemoEventType type; // The reason for this event.
InputEvent input; // This data is specific to keypress data.
// You can add additional data that is helpful for your events.
} DemoEvent;
typedef struct {
FuriString* buffer;
// You can add additional state here.
int address;
I2cDemoState state;
uint16_t value;
} DemoData;
typedef struct {
FuriMessageQueue* queue; // Message queue (DemoEvent items to process).
FuriMutex* mutex; // Used to provide thread safe access to data.
DemoData* data; // Data accessed by multiple threads (acquire the mutex before accessing!)
} DemoContext;
// Invoked when input (button press) is detected. We queue a message and then return to the caller.
static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
// Invoked by the timer on every tick. We queue a message and then return to the caller.
static void tick_callback(void* ctx) {
furi_assert(ctx);
FuriMessageQueue* queue = ctx;
DemoEvent event = {.type = DemoEventTypeTick};
// It's OK to loose this event if system overloaded (so we don't pass a wait value for 3rd parameter.)
furi_message_queue_put(queue, &event, 0);
}
// Invoked by the draw callback to render the screen. We render our UI on the callback thread.
static void render_callback(Canvas* canvas, void* ctx) {
// Attempt to aquire context, so we can read the data.
DemoContext* demo_context = ctx;
if(furi_mutex_acquire(demo_context->mutex, 200) != FuriStatusOk) {
return;
}
DemoData* data = demo_context->data;
canvas_set_font(canvas, FontPrimary);
if(data->address) {
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "FOUND I2C DEVICE");
furi_string_printf(data->buffer, "Address 0x%02x", (data->address));
canvas_draw_str_aligned(
canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer));
if(data->state == I2cDemoStateWriteSuccess) {
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "WRITE SUCCESS");
} else if(data->state == I2cDemoStateReadSuccess) {
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "READ SUCCESS");
} else if(data->state == I2cDemoStateFound) {
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "FOUND DEVICE");
} else if(data->state == I2cDemoStateWriteReadSuccess) {
canvas_draw_str_aligned(
canvas, 64, 40, AlignCenter, AlignCenter, "WRITE/READ SUCCESS");
}
furi_string_printf(data->buffer, "value %d", (data->value));
canvas_draw_str_aligned(
canvas, 64, 50, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer));
} else {
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "I2C NOT FOUND");
canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, "pin15=SDA. pin16=SCL");
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "pin9=VCC. pin18=GND");
}
// Release the context, so other threads can update the data.
furi_mutex_release(demo_context->mutex);
}
void demo_i2c_call() {
uint8_t addr = 0x46;
uint8_t reg = 0x20;
uint8_t value8 = 0;
uint16_t value16 = 0;
uint8_t buffer[3] = {0x20, 0, 0};
uint32_t timeout = 100;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
// Typically you use one of the following methods...
furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, addr, timeout);
furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, timeout);
furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, buffer, 1, timeout);
furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, buffer, 1, buffer, 2, timeout);
// or one of these helper methods...
furi_hal_i2c_write_reg_8(&furi_hal_i2c_handle_external, addr, reg, value8, timeout);
furi_hal_i2c_write_reg_16(&furi_hal_i2c_handle_external, addr, reg, value16, timeout);
furi_hal_i2c_read_reg_8(&furi_hal_i2c_handle_external, addr, reg, &value8, timeout);
furi_hal_i2c_read_reg_16(&furi_hal_i2c_handle_external, addr, reg, &value16, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
}
uint8_t demo_i2c_find_device() {
uint8_t addr = 0;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
for(uint8_t try_addr = 0; try_addr != 0xff; try_addr++) {
if(furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, try_addr, 5)) {
addr = try_addr;
break;
}
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return addr;
}
bool demo_i2c_init_bh1750(uint8_t addr) {
bool result = false;
uint8_t buffer[1];
buffer[0] = 0x1; // write a 0x1 to init a BH1750 device.
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, 100);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return result;
}
bool demo_i2c_write_one_time_h_res_mode_bh1750(uint8_t addr) {
bool result = false;
uint8_t buffer[1] = {
0x20}; // write a 0x20 for "One Time H-Resolution Mode" from BH1750 device.
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
if(furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, 100)) {
result = true;
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return result;
}
bool demo_i2c_read_one_time_h_res_mode_bh1750(uint8_t addr, uint16_t* value) {
bool result = false;
uint8_t buffer[2];
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
// Read 2 bytes from BH1750 device.
if(furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, buffer, 2, 100)) {
*value = (buffer[0] << 8) | buffer[1];
result = true;
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return result;
}
bool demo_i2c_write_read_bh1750(uint8_t addr, uint16_t* value) {
bool result = false;
uint8_t buffer[2] = {
0x20, 0}; // write a 0x20 for "One Time H-Resolution Mode" from BH1750 device.
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
if(furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, buffer, 1, buffer, 2, 100)) {
*value = (buffer[0] << 8) | buffer[1];
result = true;
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return result;
}
bool demo_i2c_read_reg_bh1750(uint8_t addr, uint16_t* value) {
bool result = false;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
if(furi_hal_i2c_read_reg_16(&furi_hal_i2c_handle_external, addr, 0x20, value, 100)) {
result = true;
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return result;
}
// Our main loop invokes this method after acquiring the mutex, so we can safely access the protected data.
static void update_i2c_status(void* ctx) {
DemoContext* demo_context = ctx;
DemoData* data = demo_context->data;
uint8_t addr = 0;
addr = demo_i2c_find_device();
if(addr) {
data->state = I2cDemoStateFound;
if(demo_i2c_init_bh1750(addr)) {
data->state = I2cDemoStateWriteSuccess;
if(demo_i2c_write_one_time_h_res_mode_bh1750(addr)) {
data->state = I2cDemoStateWriteSuccess;
if(demo_i2c_read_one_time_h_res_mode_bh1750(addr, &data->value)) {
data->state = I2cDemoStateReadSuccess;
if(demo_i2c_write_read_bh1750(addr, &data->value)) {
data->state = I2cDemoStateWriteReadSuccess;
}
}
}
}
}
data->address = addr;
}
int32_t i2c_demo_app(void* p) {
UNUSED(p);
// Configure our initial data.
DemoContext* demo_context = malloc(sizeof(DemoContext));
demo_context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
demo_context->data = malloc(sizeof(DemoData));
demo_context->data->buffer = furi_string_alloc();
demo_context->data->address = 0;
demo_context->data->state = I2cDemoStateNotFound;
demo_context->data->value = 0;
// Queue for events (tick or input)
demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent));
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, demo_context);
view_port_input_callback_set(view_port, input_callback, demo_context->queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Update the screen fairly frequently (every 1000 milliseconds = 1 second.)
FuriTimer* timer = furi_timer_alloc(tick_callback, FuriTimerTypePeriodic, demo_context->queue);
furi_timer_start(timer, 1000);
demo_i2c_call();
// Main loop
DemoEvent event;
bool processing = true;
do {
if(furi_message_queue_get(demo_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
switch(event.type) {
case DemoEventTypeKey:
// Short press of back button exits the program.
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
processing = false;
}
break;
case DemoEventTypeTick:
// Every timer tick we update the i2c status.
furi_mutex_acquire(demo_context->mutex, FuriWaitForever);
update_i2c_status(demo_context);
furi_mutex_release(demo_context->mutex);
break;
default:
break;
}
// Send signal to update the screen (callback will get invoked at some point later.)
view_port_update(view_port);
} else {
// We had an issue getting message from the queue, so exit application.
processing = false;
}
} while(processing);
// Free resources
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(demo_context->queue);
furi_mutex_free(demo_context->mutex);
furi_string_free(demo_context->data->buffer);
free(demo_context->data);
free(demo_context);
return 0;
}

43
gpio/ir_blaster/README.md Normal file
View File

@ -0,0 +1,43 @@
# IR-Blaster
## Overview
Good news -- The latest Xtreme and RogueMaster firmware now contain the patch! Additional settings and modifications were made by Sil333033. Thanks to RogueMaster for crediting me with the original code.
I created a YouTube video to explain how to modify official firmware to use an external IR Blaster. Here is the [original video](https://www.youtube.com/watch?v=o_Tz68ju4Dg). In the comments **Jeff-ss6qt** posted a question about if we can detect the IR hardware and switch automatically. It turns out that we can! This tutorial will show you how! Here is the [updated video](https://youtu.be/gRizmfNbIsM) that discusses the patch below, which does auto-detection.
[![Flipper Zero: Auto-detect IR Blaster](https://img.youtube.com/vi/gRizmfNbIsM/0.jpg)](https://youtu.be/gRizmfNbIsM)
## How to use this patch
You can apply this patch to your official firmware...
1. Recursively clone the repo like in the video.
2. Instead of manually making the edits, 4:40-7:00.
- 2a. Copy this [ir-blaster.patch](./ir-blaster.patch) file into the root folder of your firmware (same folder as where the fbt file is).
- 2b. In VS Code, right click on `ir-blaster.patch` and choose `Open in Integrated Terminal`
- 2c. In the terminal window type: `git apply --verbose --whitespace=fix --ignore-space-change ir-blaster.patch`
- NOTE: For **Unleashed** or **Xtreme** firmware, you need to apply the `cfw-ir-blaster.patch` instead (since they already have variables defined for external infrared).
- In step 2b, copy the `cfw-ir-blaster.patch` file instead of the `ir-blaster.patch` file.
- In step 2c, type: `git apply --verbose --whitespace=fix --ignore-space-change cfw-ir-blaster.patch`
- NOTE: For **RogueMaster**, you need to apply the `cfw-ir-blaster.patch` instead with a custom directory parameter (since the target files are in a `firmware` subdirectory).
- In step 2b, copy the `cfw-ir-blaster.patch` file instead of the `ir-blaster.patch` file.
- In step 2c, type: `git apply --verbose --whitespace=fix --ignore-space-change --directory=firmware cfw-ir-blaster.patch`
3. 7:00-7:50 - In the source control pane, you should see a bunch of edits to `furi_hal_infrared.c`. You will not have any edits to `infrared_app.c`.
4. 7:50 - Ctrl+Shift+B then [Release] Flash (USB, with resources)
Your Flipper Zero will now automatically detect if you have an IR Blaster connected and use it instead of the internal IR LED. If you do not have an IR Blaster connected, it will use the internal IR LED!
## How does it work?
When the IR Blaster is attached and use that instead of the built-in IR. When you remove the module, it reverts back to the built-in IR -- no setting changes required!
It automatically provides the +5 volts during the transmission, so no need to go into GPIO settings and change with that either (unless you want the status LED always on).
The detection is just based on "something" being present on pin A7, so other modules may also cause the Flipper Zero to no longer use the built-in IR port but typically you should be using your IR Blaster. We use an internal pull-up resistor on pin A7 and then read the value. If it is `high` we assume something nothing is connected. If it is `low` we assume the IR Blaster is connected (although all we really know is some module is pulling the pin toward ground.) The external CC1101 module, WiFi Dev Board and FlipBoard all return `high` when in the internal pull-up configuration; so there is a good chance other 3rd party modules are not detected as an IR Blaster.
This is really just for those times when you are needing to adjust your TV and don't have the accessory nearby -- simply unplug whatever is connected to your Flipper and do a quick IR transmit using the built-in LED. In general, I would always recommend using the IR Blaster module.
You can find more information about the IR Blaster module I used in the description of the YouTube video.
## More information
If you are interested in learning more about the Flipper Zero, check out my YouTube channel: https://youtube.com/@MrDerekJamison
I also have a Discord server where you can ask questions and share your projects: https://discord.com/invite/NsjCvqwPAd

View File

@ -0,0 +1,54 @@
diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c
index 9c0d84c55..b2592ee6d 100644
--- a/targets/f7/furi_hal/furi_hal_infrared.c
+++ b/targets/f7/furi_hal/furi_hal_infrared.c
@@ -2,6 +2,7 @@
#include <furi_hal_interrupt.h>
#include <furi_hal_resources.h>
#include <furi_hal_bus.h>
+#include <furi_hal_power.h>
#include <stm32wbxx_ll_tim.h>
#include <stm32wbxx_ll_dma.h>
@@ -79,6 +80,7 @@ static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle;
static InfraredTimTx infrared_tim_tx;
static InfraredTimRx infrared_tim_rx;
static bool infrared_external_output;
+static bool was_otg_requested = false;
static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift);
static void furi_hal_infrared_async_tx_free_resources(void);
@@ -585,6 +587,9 @@ static void furi_hal_infrared_async_tx_free_resources(void) {
} else {
furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
}
+ if(was_otg_requested) {
+ furi_hal_power_disable_otg();
+ }
furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL);
furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL);
@@ -645,6 +650,23 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) {
furi_delay_us(5);
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */
furi_delay_us(5);
+
+ // Detect if a module (like IR Blaster) is connected to PA7.
+ furi_hal_gpio_init(&gpio_ext_pa7, GpioModeInput, GpioPullUp, GpioSpeedHigh);
+ furi_delay_ms(1);
+ infrared_external_output = !furi_hal_gpio_read(&gpio_ext_pa7);
+ furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+
+ if(infrared_external_output) {
+ was_otg_requested = !furi_hal_power_is_otg_enabled();
+ if(was_otg_requested) {
+ furi_hal_power_enable_otg();
+ furi_delay_ms(100);
+ }
+ } else {
+ was_otg_requested = false;
+ }
+
if(infrared_external_output) {
LL_GPIO_ResetOutputPin(
gpio_ext_pa7.port, gpio_ext_pa7.pin); /* when disable it prevents false pulse */

View File

@ -0,0 +1,147 @@
diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c
index 3b20b6bc3..723ce0bd6 100644
--- a/targets/f7/furi_hal/furi_hal_infrared.c
+++ b/targets/f7/furi_hal/furi_hal_infrared.c
@@ -2,6 +2,7 @@
#include <furi_hal_interrupt.h>
#include <furi_hal_resources.h>
#include <furi_hal_bus.h>
+#include <furi_hal_power.h>
#include <stm32wbxx_ll_tim.h>
#include <stm32wbxx_ll_dma.h>
@@ -9,12 +10,6 @@
#include <furi.h>
#include <math.h>
-// #define INFRARED_TX_DEBUG
-
-#if defined INFRARED_TX_DEBUG
-#define gpio_infrared_tx gpio_ext_pa7
-#endif
-
#define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200
#define INFRARED_POLARITY_SHIFT 1
@@ -84,6 +79,8 @@ typedef enum {
static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle;
static InfraredTimTx infrared_tim_tx;
static InfraredTimRx infrared_tim_rx;
+static bool is_infrared_external = false;
+static bool was_otg_requested = false;
static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift);
static void furi_hal_infrared_async_tx_free_resources(void);
@@ -348,27 +345,29 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc
LL_TIM_SetAutoReload(
INFRARED_DMA_TIMER,
__LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(INFRARED_DMA_TIMER), freq));
-#if defined INFRARED_TX_DEBUG
- LL_TIM_OC_SetCompareCH1(
- INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
- LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
- /* LL_TIM_OCMODE_PWM2 set by DMA */
- LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE);
- LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH);
- LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
- LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N);
- LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER);
-#else
- LL_TIM_OC_SetCompareCH3(
- INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
- LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
- /* LL_TIM_OCMODE_PWM2 set by DMA */
- LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE);
- LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH);
- LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
- LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N);
- LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER);
-#endif
+ if(is_infrared_external) {
+ LL_TIM_OC_SetCompareCH1(
+ INFRARED_DMA_TIMER,
+ ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
+ LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
+ /* LL_TIM_OCMODE_PWM2 set by DMA */
+ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE);
+ LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH);
+ LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
+ LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N);
+ LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER);
+ } else {
+ LL_TIM_OC_SetCompareCH3(
+ INFRARED_DMA_TIMER,
+ ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
+ LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
+ /* LL_TIM_OCMODE_PWM2 set by DMA */
+ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE);
+ LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH);
+ LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
+ LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N);
+ LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER);
+ }
LL_TIM_DisableMasterSlaveMode(INFRARED_DMA_TIMER);
LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER);
LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER);
@@ -377,11 +376,11 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc
static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) {
LL_DMA_InitTypeDef dma_config = {0};
-#if defined INFRARED_TX_DEBUG
- dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1);
-#else
- dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2);
-#endif
+ if(is_infrared_external) {
+ dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1);
+ } else {
+ dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2);
+ }
dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL;
dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
dma_config.Mode = LL_DMA_MODE_NORMAL;
@@ -575,7 +574,11 @@ static void furi_hal_infrared_async_tx_free_resources(void) {
(furi_hal_infrared_state == InfraredStateIdle) ||
(furi_hal_infrared_state == InfraredStateAsyncTxStopped));
- furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+ if(was_otg_requested) {
+ furi_hal_power_disable_otg();
+ }
+ const GpioPin* gpio_pin = (is_infrared_external) ? &gpio_ext_pa7 : &gpio_infrared_tx;
+ furi_hal_gpio_init(gpio_pin, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL);
furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL);
@@ -636,10 +639,29 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) {
furi_delay_us(5);
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */
furi_delay_us(5);
+
+ // Detect if a module (like IR Blaster) is connected to PA7.
+ furi_hal_gpio_init(&gpio_ext_pa7, GpioModeInput, GpioPullUp, GpioSpeedHigh);
+ furi_delay_ms(1);
+ is_infrared_external = !furi_hal_gpio_read(&gpio_ext_pa7);
+ furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+
+ if(is_infrared_external) {
+ was_otg_requested = !furi_hal_power_is_otg_enabled();
+ if(was_otg_requested) {
+ furi_hal_power_enable_otg();
+ furi_delay_ms(100);
+ }
+ } else {
+ was_otg_requested = false;
+ }
+
+ const GpioPin* gpio_pin = (is_infrared_external) ? &gpio_ext_pa7 : &gpio_infrared_tx;
+
LL_GPIO_ResetOutputPin(
- gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */
+ gpio_pin->port, gpio_pin->pin); /* when disable it prevents false pulse */
furi_hal_gpio_init_ex(
- &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1);
+ gpio_pin, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1);
FURI_CRITICAL_ENTER();
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */

View File

@ -0,0 +1,86 @@
# MEMSIC 2125
## Introduction
This is a demostration of using GPIO interrupts to interpret data from the Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "memsic_2125" [folder](..) to the \applications\plugins\memsic_2125 folder in your firmware.
- Build &amp; deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the memsic_2125.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
## Connecting the hardware
The Memsic Mx2125 is a 6-pin IC. From the top view the pins look like the following:
<br/>
<img alt="Mx2125 chip with pin 1 on top-left" src="./memsic_2125_chip.jpg" height=150/>
<br/>
The pins should be connected to the Flipper Zero as follows:
| Memsic | Name | Purpose | Flipper |
| ------ | ---- | ------------------------------------------ | ------------- |
| Pin 1 | Tout | Temperature Out | not connected |
| Pin 2 | Yout | Y-axis PWM Out (100Hz, duty cycle = value) | C0 |
| Pin 3 | GND | Ground | GND |
| Pin 4 | GND | Ground | GND |
| Pin 5 | Xout | X-axis PWM Out (100Hz, duty cycle = value) | C1 |
| Pin 6 | Vdd | Drain voltage (3.3V to 5V DC) | 3v3 |
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Applications" from the menu.
- Choose "Misc" from the sub-menu.
- Choose "Memsic 2125"
- The flipper should say "Memsic 2125 C1:X C0:Y". This is a reminder that the X-Axis (pin 5) connects to pin C1 and Y-Axis (pin 2) connects to pin C0.
- A circle should show in the middle of the screen when the Mx2125 is on a level surface.
- Tilting along the X and Y axis should move the circle.
- The values at the bottom of the screen show the X and Y duty cycle amounts.
- NOTE: A value of 50% means that the device is level.
- Press the BACK button to exit.
## How it works
- application.fam
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo.png
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo_app.c
- See [basic demo](../../plugins/basic/README.md) for explanation.
- GPIO specific:
- AxisData is a structure to hold information about an axis.
- pin is the pin we are monitoring, so we can read the state later.
- high is the timestamp when the pin last transitioned to high.
- low is the timestamp when the pin last transitioned to low.
- value is the duty cycle (which represents the amount of tilt.)
- reading is set to true when the UI is reading this structure. If this value is true, then in our callback we will not modify value. This is used to ensure access to value is thread safe.
- DemoData has two AxisData references (for x-axis data and y-axis data.)
- Our entry point initializes xData with the c1 pin & yData with the c0 pin.
- furi_hal_gpio_init(xData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh) initializes an interrupt to happen on the Rise or Fall of the c1 pin. We don't need a pull-up since the pin will be set to GND or 3.3V by the Mx2125.
- furi_hal_gpio_add_int_callback(xData->pin, pulse_callback, xData) sets the callback routine to pulse_callback and will pass our xData whenever the pin changes state.
- We do the same process as the xData for the yData using the c0 pin.
- pulse_callback(void \*) is our GPIO interrupt callback.
- The input parameter is actually a pointer to AxisData.
- furi_hal_cortex_timer_get(0) gets the current timestamp.
- furi_hal_gpio_read(d->pin) gets the state of the pin (true=HIGH/false=LOW).
- if we transitioned from high to low to high & our render_callback is not currently reading the value, then we update the value with the new duty cycle number.
- When exiting we remove the callback routine using furi_hal_gpio_remove_int_callback.
- In our render_callback(Canvas*, void*) we render the UI
- We set xData->reading to true before accessing xData->value and then set it to false once we are done accessing the data.
- We calcuate the X and Y coordinate to draw a circle based on xData->value and yData->value.

View File

@ -0,0 +1,10 @@
App(
appid="memsic_2125",
name="[Mx2125] Accelerometer",
apptype=FlipperAppType.EXTERNAL,
entry_point="memsic_2125_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="memsic_2125.png",
fap_category="Gpio",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,226 @@
/*
@CodeAllNight
https://github.com/jamisonderek/flipper-zero-tutorials
This is a demostration of using GPIO interrupts to interpret data from the
Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
Memsic - Name - Purpose - Flipper
====== ==== ========================================== =============
Pin 1 - Tout - Temperature Out - not connected
Pin 2 - Yout - Y-axis PWM Out (100Hz, duty cycle = value) - C0
Pin 3 - GND - Ground - GND
Pin 4 - GND - Ground - GND
Pin 5 - Xout - X-axis PWM Out (100Hz, duty cycle = value) - C1
Pin 6 - Vdd - Drain voltage (3.3V to 5V DC) - 3v3
*/
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <locale/locale.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define TAG "memsic_2125_app"
typedef enum {
DemoEventTypeKey,
} DemoEventType;
typedef struct {
DemoEventType type; // The reason for this event.
InputEvent input; // This data is specific to keypress data.
} DemoEvent;
typedef struct {
const GpioPin* pin; // pin being monitored.
uint32_t high; // timestamp of when pin went high.
uint32_t low; // timestamp of when pin when low.
float value; // duty cycle (0.0 to 100.0)
bool reading; // when true, then value will not be updated.
} AxisData;
typedef struct {
FuriString* buffer;
AxisData* xData;
AxisData* yData;
} DemoData;
typedef struct {
FuriMessageQueue* queue; // Message queue
DemoData* data;
} DemoContext;
// Invoked when input (button press) is detected.
// We queue a message and then return to the caller.
// @input_event the button that triggered the callback.
// @queue our message queue.
static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
// Invoked whenever the monitored pin changes state.
// @data pointer to an AxisData.
void pulse_callback(void* data) {
// Get the current time from high-resolution timer.
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(0);
uint32_t now = timer.start;
// Our parameter is a pointer to the axis data.
furi_assert(data);
AxisData* d = (AxisData*)data;
// Get the current state of the pin (true = 3.3volts, false = GND)
bool state = furi_hal_gpio_read(d->pin);
if(state) {
// state=true, so we are in GND->3v3 transition.
// See if we have timings for both the high & low transitions.
if((d->high != 0) && (d->low != 0) && (d->high < d->low) && !d->reading) {
uint32_t durationCycle = now - d->high;
uint32_t durationLow = d->low - d->high;
// Update the value of the axis to reflect the duty cycle.
d->value = (100.0f * durationLow) / durationCycle;
}
// Store the current time that the rise transition happened.
d->high = timer.start;
} else {
// Store the current time that the fall transition happened.
d->low = timer.start;
}
}
// Invoked by the draw callback to render the screen.
// We render our UI on the callback thread.
// @canvas the surface to render our UI
// @ctx a pointer to a DemoContext object.
static void render_callback(Canvas* canvas, void* ctx) {
// Attempt to aquire context, so we can read the data.
DemoContext* demo_context = ctx;
DemoData* data = demo_context->data;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 1, 1, AlignLeft, AlignTop, "Memsic 2125 C1:X C0:Y");
data->xData->reading = true;
data->yData->reading = true;
furi_string_printf(
data->buffer, "%0.3f %0.3f", (double)data->xData->value, (double)data->yData->value);
data->xData->reading = false;
data->yData->reading = false;
canvas_draw_str_aligned(
canvas, 30, 55, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
// Draw a circle based on the xData & yData information.
data->xData->reading = true;
uint8_t x = (0.5f + ((50.0f - data->xData->value) / 12.0f)) * 128;
data->xData->reading = false;
x = MAX(0, MIN(x, 127));
data->yData->reading = true;
uint8_t y = (0.5f + (((float)data->yData->value - 50.0f) / 12.0f)) * 64;
data->yData->reading = false;
y = MAX(0, MIN(y, 63));
canvas_draw_circle(canvas, x, y, 2);
}
// Program entry point
int32_t memsic_2125_app(void* p) {
UNUSED(p);
// Configure our initial data.
DemoContext* demo_context = malloc(sizeof(DemoContext));
demo_context->data = malloc(sizeof(DemoData));
demo_context->data->buffer = furi_string_alloc();
AxisData* xData = malloc(sizeof(AxisData));
xData->pin = &gpio_ext_pc1;
xData->high = 0;
xData->low = 0;
xData->reading = false;
xData->value = 0;
demo_context->data->xData = xData;
AxisData* yData = malloc(sizeof(AxisData));
yData->pin = &gpio_ext_pc0;
yData->high = 0;
yData->low = 0;
yData->reading = false;
yData->value = 0;
demo_context->data->yData = yData;
// Queue for events (tick or input)
demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent));
// x-axis is a 100Hz pulse, with variable duty-cycle (which we need to measure).
// Invoke the pulse_callback method passing the xData structure whenever the pin
// transitions (Rise GND->3v3 or Fall 3v3->GND).
furi_hal_gpio_init(xData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(xData->pin, pulse_callback, xData);
// y-axis is a 100Hz pulse, with variable duty-cycle (which we need to measure).
// Invoke the pulse_callback method passing the xData structure whenever the pin
// transitions (Rise GND->3v3 or Fall 3v3->GND).
furi_hal_gpio_init(yData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(yData->pin, pulse_callback, yData);
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, demo_context);
view_port_input_callback_set(view_port, input_callback, demo_context->queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Main loop
DemoEvent event;
bool processing = true;
do {
if(furi_message_queue_get(demo_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
FURI_LOG_T(TAG, "Got event type: %d", event.type);
switch(event.type) {
case DemoEventTypeKey:
// Short press of back button exits the program.
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
FURI_LOG_I(TAG, "Short-Back pressed. Exiting program.");
processing = false;
}
break;
default:
break;
}
// Send signal to update the screen (callback will get invoked at some point later.)
view_port_update(view_port);
} else {
// We had an issue getting message from the queue, so exit application.
processing = false;
}
} while(processing);
// Free resources
furi_hal_gpio_remove_int_callback(yData->pin);
furi_hal_gpio_remove_int_callback(xData->pin);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(demo_context->queue);
furi_string_free(demo_context->data->buffer);
free(demo_context->data);
free(demo_context);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

View File

@ -0,0 +1,10 @@
Connected via USB cable it will have +5 volts OTHERWISE you must enable via GPIO/"5V on GPIO" and click "ON".
Max of 1 watt (1-Amp).
Look at device datasheet. If 5 volts, connect to "VIN", "VCC" or "+5V" (if NO VIN) on the device.
If datasheet says 3.3 volts, do NOT use this pin!
Devices:
- HC-SR04
- Joystick (via a 10K resistor)
- Mx2125 (recommend 3v3 pin)

View File

@ -0,0 +1,11 @@
For SPI, the naming convetion is changing. You may see "MOSI", "MO", "SI", "COPI", "CO", "PI". This pin is Flipper data out "SDO" (which should connect to the device data in "SDI") pin. "I" refers to "Input" and "O" refers to "Output". "M/C" refers to "Main", "Master" (not used anymore), "Controller". "S/P" refers to "Subnode", "Slave" (not used anymore), "Perpherial". "SD" refers to "Serial Data".
Generic GPIO uses PA7.
Devices:
- DHT11[GPIO] (Data)
- BME280[SPI] (SDI)
- BMP280[SPI] (SDI)
- NRF24L01[SPI] (MO)
- NRF24L01[SPI] (MOSI)
- CC1101[SPI] (SI)

View File

@ -0,0 +1,10 @@
For SPI, the naming convetion is changing. You may see "MISO", "MI", "SO", "CIPO", "CI", "PO". This pin is Flipper data out "SDI" (which should connect to the device data in "SDO") pin. "I" refers to "Input" and "O" refers to "Output". "M/C" refers to "Main", "Master" (not used anymore), "Controller". "S/P" refers to "Subnode", "Slave" (not used anymore), "Perpherial". "SD" refers to "Serial Data".
Generic GPIO uses PA6.
Devices:
- BME280[SPI] (SDO)
- BMP280[SPI] (SDO)
- NRF24L01[SPI] (MI)
- NRF24L01[SPI] (MISO)
- CC1101[SPI] (SO)

View File

@ -0,0 +1,12 @@
For SPI, this is the Chip Select (which enables the data lines.) Typically LOW to enable -- "CSN" is "Chip Select Not".
Generic GPIO uses PA4.
CHECK: "LPTIM2.OUT/ADC1.9/COMP1 IN- COMP2 IN-"
Devices:
- BME280[SPI] (CS)
- BMP280[SPI] (CS)
- NRF24L01[SPI] (CSN)
- NRF24L01[SPI] (CSN)
- CC1101[SPI] (CSN)

View File

@ -0,0 +1,13 @@
For SPI, this is the Serial Clock (which says when to read/write data.) Typically Mode 0 (Idle LOW, Sample Rising Edge, Shift Falling Edge.)
Generic GPIO uses PB3.
CHECK: "ADC1.9/COMP1 IN- COMP2 IN-/SWO"
Devices:
- BME280[SPI] (SCK)
- BMP280[SPI] (SCK)
- NRF24L01[SPI] (SCK)
- NRF24L01[SPI] (SCK)
- CC1101[SPI] (SCLK)

View File

@ -0,0 +1,10 @@
For SPI, libraries do not treat as CS (although it is shown in some diagrams.) This is just a GPIO pin, but it typically get connected to "CE" (for Chip Enable -- e.g. take chip out of standby.) It is typically logic LOW. On the CC1101 it is used for GDO0 (general purpose IRQ).
Generic GPIO uses PB2.
CHECK: "LPTIM1.OUT/COMP1 IN+"
Devices:
- NRF24L01[SPI] (CE)
- NRF24L01[SPI] (CE)
- CC1101[SPI] (GDO0)

View File

@ -0,0 +1,8 @@
This is just a general purpose GPIO pin.
Generic GPIO uses PC3.
CHECK: "LPTIM1.ETR LPTIM2.ETR/ADC1.4"
Devices:
- None

View File

@ -0,0 +1,4 @@
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
Devices:
- All

View File

@ -0,0 +1,19 @@
DANGER: Connect external modules with large capacitive load only when Flipper Zero is powered off. Otherwise, data on the microSD card can be corrupted.
This pin is 3.3 Volts output. NOTE: Some people recommend using the 5V pin (L1) and a 3.3 volt regulator with output having a parallel 10uF and 100nF capacitor; so that voltage is more stable.
Max of 1200 mA (3.96 watt).
Look at device datasheet. If 3.3 volts, connect to "VIN", "VCC", "3.3V/3V3" (if NO VIN) on the device.
Devices:
- NMEA GPS[USART] (VIN)
- DHT11[GPIO] (VCC)
- BME280[I²C] (VCC)
- BME280[SPI-MODE] (VCC)
- BMP280[SPI] (VIN)
- BH1750[I²C] (VCC)
- NRF24L01[SPI] (VCC)
- NRF24L01[SPI] (VCC)
- CC1101[SPI] (VCC)

View File

@ -0,0 +1,6 @@
This pin is used for general IO, timer & SWD only.
CHECK: "LPTIM1.OUT/SWCLK"
Devices:
- None

4
gpio/pins/R3(11)_GND.txt Normal file
View File

@ -0,0 +1,4 @@
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
Devices:
- All

View File

@ -0,0 +1,6 @@
This pin is used for back key (PA13) and SWD only.
CHECK: "PA13/SWIO"
Devices:
- None

View File

@ -0,0 +1,11 @@
This pin is Flipper TX on USART (so connect to RX on device).
The Signal Generator/Clock Generator app uses LPTIM1.EXT (pin 13) for the clock signal.
Generic GPIO uses PB6.
CHECK: "COMP2 IN+/MCO"
Devices:
- NMEA GPS[USART] (RX)
- HC-SR04[GPIO] (Trig)

View File

@ -0,0 +1,9 @@
This pin is Flipper RX on USART (so connect to TX on device).
Generic GPIO uses PB7.
CHECK: "LPTIM1.IN2/COMP2 IN-"
Devices:
- NMEA GPS[USART] (TX)
- HC-SR04[GPIO] (Echo)

View File

@ -0,0 +1,11 @@
This pin is Flipper TX on LPUART [Low Power UART] (so connect to RX on device).
For I2C devices this is SDA (Serial Data) pin.
Generic GPIO uses PC1.
CHECK: "LPTIM1.OUT/ADC1.2"
Devices:
- BME280[I2C] (SDA)
- BH1750[I2C] (SDA)

View File

@ -0,0 +1,13 @@
This pin is Flipper RX on LPUART [Low Power UART] (so connect to TX on device).
For I2C devices this is SCL (Serial Clock) pin.
For ADC devices this is ADC1.1 pin (flipperscope application uses LL library to show values.)
Generic GPIO uses PC0.
CHECK: "LPTIM1.IN LPTIM2.IN"
Devices:
- BME280[I2C] (SCL)
- BH1750[I2C] (SCL)

View File

@ -0,0 +1,9 @@
For 1-Wire (iButton) devices this is the wire. It has built-in 1K pull-up resistor to 5 volts.
Generic GPIO uses PC0.
CHECK: "PB14/TIM1.2N/I2C3.SDA-Pullup"
Devices:
- iButton[1Wire] (center of button)
- DS18B20[1Wire] (yellow wire)

27
gpio/pins/README.md Normal file
View File

@ -0,0 +1,27 @@
# Pins
## Description
To help making things easier, I have been trying to use specific colored wires for connecting to various devices. I also made a breakout board that connects to the top of the Flipper Zero and exposes the same colored wires on a breadboard for quick prototyping. The breadboard also exposes a copy of each general output with a 220 ohm series resistor (which is helpful for driving LEDs).
The pins have different names depending on their meaning, so this
helps me connect to the proper pins.
I use the following colors (for some usages like LPUART v. I2C it isn't a match; but I think it is more common for the pins to be I2C):
- White = +5V
- Red = +3.3V
- Black = GND
- Orange = OUT [from Flipper perspective]
- Indigo = IN [from Flipper perspective]
- Yellow = IN/OUT
- Green = SELECT [TYPICALLY LOW]
- Brown = CLOCK
- Blue = GENERAL/IRQ
- Grey = TYPICALLY UNUSED
NOTE: For General Purpose Input/Output (like controlling a 7-segment LED display) instead of a specific protocol, the majority of the colors have no meanings.
## Installing
I copy these files into a folder on my Flipper Zero (like "pins") so that I can easily browse all of the pins. If you click on a file and chose "Info" so that you see the full name without scrolling. If you click on a file and choose "Show" then you will see details about that pin.
## Future work
Many devices have printing on the underside, so you have to be careful when connecting to those (or connect wires from breadboard to device - instead of plugging in directly to device.) Some devices have two sets of adjecent pins, so they don't work in breadboard (I'm working on making an adapter soon) so they can go into my breadboard (but you can also connect wires from breadboard to device.)

View File

@ -0,0 +1,4 @@
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
Devices:
- All

13
gpio/shtc3/README.md Normal file
View File

@ -0,0 +1,13 @@
# SHTC3
This is just a simple console application that uses i2c to communicate with an SHTC3 temperature and humidity device. It is assumed that you have debug mode turned on (Settings, System, Debug ON). To see the logs, either connect to serial port or use https://lab.flipper.net/cli and type ``log`` then enter. Once you are viewing the logs, run the application.
The code does the following:
- The address of the device is hard-coded to 0x70.
- Wake-up (0x3517) is sent to wake up the device.
- Read id (0xefc8) is sent to read the device ID.
- The output is expected to match "(xxxx1xxxxx000111)".
Resources:
- [I2CDevices.org info](https://i2cdevices.org/devices/shtc3)
- [SHTC3 Datasheet](https://www.waveshare.com/w/upload/3/33/SHTC3_Datasheet.pdf)

View File

@ -0,0 +1,10 @@
App(
appid="i2c_read_shtc3",
name="I2C SHTC3 Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="i2c_read_shtc3_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="i2c_read_shtc3.png",
fap_category="GPIO",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,47 @@
#include <furi.h>
#include <furi_hal.h>
uint8_t address = 0x70 << 1;
uint32_t timeout = 1000;
FuriHalI2cBusHandle* i2c = &furi_hal_i2c_handle_external;
void wake_up() {
uint8_t data_wake_up[2] = {0x35, 0x17};
if(furi_hal_i2c_tx(i2c, address, data_wake_up, 2, timeout)) {
FURI_LOG_I("I2C", "Wake up success");
} else {
FURI_LOG_I("I2C", "Wake up failed");
}
}
void read_id() {
uint8_t data_read_id[2] = {0xef, 0xc8};
uint8_t data_response[3] = {0};
if(furi_hal_i2c_trx(i2c, address, data_read_id, 2, data_response, 3, timeout)) {
uint16_t value = (data_response[0] << 8) | data_response[1];
// put binary value into buffer and null terminate
char buffer[16];
for(uint8_t i = 0; i < 16; i++) {
buffer[15 - i] = (value & (1 << i)) ? '1' : '0';
}
buffer[16] = 0;
FURI_LOG_I("I2C", "Read ID: (%s) %04x", buffer, value);
FURI_LOG_I("I2C", "Expected ID: (xxxx1xxxxx000111)");
FURI_LOG_I("I2C", "CRC 8: %02x", data_response[2]);
} else {
FURI_LOG_I("I2C", "Read ID failed");
}
}
int32_t i2c_read_shtc3_app(void* p) {
UNUSED(p);
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
wake_up();
read_id();
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return 0;
}

Binary file not shown.

20
gpio/spi_demo/README.md Normal file
View File

@ -0,0 +1,20 @@
# SPI_DEMO
This is Not intended to be a full demo, but a simple example of how to use the SPI interface on the Flipper Zero.
Please see [https://youtu.be/W6bGYZ5PgIc](https://youtu.be/W6bGYZ5PgIc) for a video demo.
My video this Saturday, Nov 18th 2023, will cover how to wire the device between the Flipper Zero and the chip that supports SPI, such as the W25Q32.
The connections are:
- Flipper Pin 2 - MOSI (D0 on the W25Q32)
- Flipper Pin 3 - MISO (D1 on the W25Q32)
- Flipper Pin 4 - CS (CS on the W25Q32)
- Flipper Pin 5 - SCK (SLK on the W25Q32)
- Flipper Pin 8 - GND (GND on the W25Q32)
- Flipper Pin 9 - 3V3 (VCC on the W25Q32)
The `Hellow-World.sal` file can be viewed using [https://www.saleae.com/downloads/](https://www.saleae.com/downloads/) software. This is a capture of my W25Q32 chip being read by the Flipper Zero. The commands were sent using the `SPI Mem Manager` application.
You can find the `SPI Mem Manager` application at [https://github.com/flipperdevices/flipperzero-good-faps/tree/dev/spi_mem_manager](https://github.com/flipperdevices/flipperzero-good-faps)

12
gpio/spi_demo/app.c Normal file
View File

@ -0,0 +1,12 @@
#include <furi.h>
#include <furi_hal.h>
void spi_demo();
void spi_demo_v2();
int32_t main_learn_spi(void* _p) {
UNUSED(_p);
spi_demo();
spi_demo_v2();
return 0;
}

BIN
gpio/spi_demo/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,14 @@
App(
appid="learn_spi_app",
name="[WIP] Learn SPI",
apptype=FlipperAppType.EXTERNAL,
entry_point="main_learn_spi",
stack_size=4 * 1024,
requires=[
"gui",
],
order=10,
fap_icon="app.png",
fap_category="GPIO",
fap_description="Demo of communicating with W25Q32 IC.",
)

39
gpio/spi_demo/spi.c Normal file
View File

@ -0,0 +1,39 @@
/*
Demo reading from a w25q32 chip using SPI.
*/
#include <furi.h>
#include <furi_hal.h>
#define TAG "LearnSPI-SPI"
static uint32_t timeout = 1000;
static FuriHalSpiBusHandle* spi = &furi_hal_spi_bus_handle_external;
void read_w25q32_spi() {
uint8_t command_read_memory[10] = {0x03, 0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
uint8_t data_response[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Extra space for NULL termination
furi_hal_spi_acquire(spi);
if(furi_hal_spi_bus_trx(spi, command_read_memory, data_response, 9, timeout)) {
char* data_response_str = (char*)data_response + 4;
FURI_LOG_E(
TAG,
"MESSAGE STORED IN CHIP AT ADDRESS 0x%02X%02X%02X IS: %s",
command_read_memory[1],
command_read_memory[2],
command_read_memory[3],
data_response_str);
} else {
FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_trx failed.");
}
furi_hal_spi_release(spi);
}
void spi_demo() {
furi_hal_spi_bus_handle_init(spi);
read_w25q32_spi();
furi_hal_spi_bus_handle_deinit(spi);
}

42
gpio/spi_demo/spi_v2.c Normal file
View File

@ -0,0 +1,42 @@
/*
Improved demo reading from a w25q32 chip using SPI.
*/
#include <furi.h>
#include <furi_hal.h>
#define TAG "LearnSPI-SPI-V2"
static uint32_t timeout = 1000;
static FuriHalSpiBusHandle* spi = &furi_hal_spi_bus_handle_external;
void read_w25q32_spi_v2() {
uint8_t command_read_memory[1] = {0x03}; // Read command
uint8_t read_memory_address[3] = {0x00, 0x01, 0x00}; // Memory address is 0x000100
uint8_t data_response[6] = {0, 0, 0, 0, 0, 0}; // Read 5 bytes + NULL terminator.
furi_hal_spi_acquire(spi);
if(furi_hal_spi_bus_tx(spi, command_read_memory, 1, timeout) &&
furi_hal_spi_bus_tx(spi, read_memory_address, 3, timeout) &&
(furi_hal_spi_bus_rx(spi, data_response, 5, timeout))) {
char* data_response_str = (char*)data_response;
FURI_LOG_E(
TAG,
"MESSAGE STORED IN CHIP AT ADDRESS 0x%02X%02X%02X IS: %s",
read_memory_address[0],
read_memory_address[1],
read_memory_address[2],
data_response_str);
} else {
FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed.");
}
furi_hal_spi_release(spi);
}
void spi_demo_v2() {
furi_hal_spi_bus_handle_init(spi);
read_w25q32_spi_v2();
furi_hal_spi_bus_handle_deinit(spi);
}

59
gpio/uart_demo/README.md Normal file
View File

@ -0,0 +1,59 @@
# UART_DEMO
This is a demonstration of using the UART to read (RX) and transmit (TX) data. You can connect
pins RX and TX together with a wire or resistor to create a loopback. If you have a second
Flipper Zero then you can connect TX of the first Flipper Zero to RX of the second and the RX of
the first to TX of the second. You also should connect one of the ground pins from the first
Flipper Zero to the second Flipper Zero.
When you run the demo application, you will see three options.
- The first option "Clear" will clear the menu.
- The second option "Seng Msg 1" will send "Hello World!" follow by new-line delimiter.
- The third option "Seng Msg 2" will send "Testing 123." follow by new-line delimiter.
Whenever data is received and the delimiter is detected, the new text will be added as an additional
entry in the menu.
# How it works
uart_demo.c is the main class that you should look at. The goal was to simplify all
the complexity of worker threads and buffers into other files that you should not have
to look at.
We include uart_helper.h...
```c
#include "uart_helper.h"
```
We create a callback to get invoked whenever a new line (text+delimiter) is detected...
```c
// This callback will be invoked whenever a new line is read on the UART!
static void uart_demo_process_line(FuriString* line, void* context) {
// For the demo, we have code to add the line to the submenu.
}
```
We configure the UartHelper as follows. We specify the baud rate, the delimiter of our
message, if we want the delimiter included in our callback data, and which callback to
invoke. When data is received by the UART it will be added to a ring buffer. Once the
delimiter is detected in the data, our callback will be invoked with the received data.
The UartHelper uses worker threads and ring buffers to do the processing, but all we need
to do is call ``uart_helper_alloc`` to start receiving data.
```c
#define DEVICE_BAUDRATE 115200
#define LINE_DELIMITER '\n'
#define INCLUDE_LINE_DELIMITER false
app->uart_helper = uart_helper_alloc();
uart_helper_set_baud_rate(app->uart_helper, DEVICE_BAUDRATE);
uart_helper_set_delimiter(app->uart_helper, LINE_DELIMITER, INCLUDE_LINE_DELIMITER);
uart_helper_set_callback(app->uart_helper, uart_demo_process_line, app);
```
We can also send a string using two helper methods...
```c
// Send a c-style string
uart_helper_send(app->uart_helper, "Hello World!\n", 13);
// Send a FuriString
uart_helper_send_string(app->uart_helper, my_furi_string);
```

View File

@ -0,0 +1,10 @@
App(
appid="uart_demo",
name="UART Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="uart_demo_main",
requires=["gui"],
stack_size=1 * 1024,
fap_icon="uart_demo_10px.png",
fap_category="GPIO",
)

View File

@ -0,0 +1,166 @@
/**
* A ring buffer. This is a circular buffer that can be used to store data until a
* delimiter is found, at which point the line can be extracted. The ring buffer is a
* fixed size and will overwrite old data if the buffer is full.
*
* @author CodeAllNight
*/
#include <furi.h>
/**
* The size of the ring buffer. This is the maximum number of bytes that can be stored in the
* buffer. If the buffer is full, the oldest data will be overwritten.
*/
static const uint32_t ring_buffer_size = 4096;
/**
* A ring buffer. This is used to store data received from the UART. The data is stored in a
* ring buffer so that it can be processed in the background while the main loop is doing other
* things.
*/
typedef struct {
// The delimiter character
char delimiter;
// If true, the delimiter will be included in the extracted line
bool include_delimiter;
// The ring buffer. This is a circular buffer that can be used to store data until a
// delimiter is found, at which point the line can be extracted. The ring buffer is a
// fixed size and will overwrite old data if the buffer is full.
uint8_t* ring_buffer;
// The next index to read from the ring buffer. An empty buffer will have
// ring_buffer_read == ring_buffer_write
size_t ring_buffer_read;
// The next index to write to the ring buffer
size_t ring_buffer_write;
} RingBuffer;
RingBuffer* ring_buffer_alloc() {
RingBuffer* buffer = malloc(sizeof(RingBuffer));
buffer->ring_buffer = malloc(ring_buffer_size);
buffer->ring_buffer_read = 0;
buffer->ring_buffer_write = 0;
buffer->delimiter = '\n';
buffer->include_delimiter = false;
return buffer;
}
void ring_buffer_free(RingBuffer* buffer) {
free(buffer->ring_buffer);
free(buffer);
}
void ring_buffer_set_delimiter(RingBuffer* rb, char delimiter, bool include_delimiter) {
rb->delimiter = delimiter;
rb->include_delimiter = include_delimiter;
}
size_t ring_buffer_available(RingBuffer* rb) {
size_t available;
if(rb->ring_buffer_write == rb->ring_buffer_read) {
// Empty buffer has size - 1 available bytes
available = ring_buffer_size - 1;
} else if(rb->ring_buffer_read > rb->ring_buffer_write) {
// Write can go up to read - 1
available = rb->ring_buffer_read - rb->ring_buffer_write;
} else {
// Write can go up to end of buffer, then from start to read - 1
available = (ring_buffer_size - rb->ring_buffer_write) + rb->ring_buffer_read;
}
return available;
}
bool ring_buffer_add(RingBuffer* rb, uint8_t* data, size_t length) {
bool hasDelim = false;
for(size_t i = 0; i < length; i++) {
// Copy the data into the ring buffer
rb->ring_buffer[rb->ring_buffer_write] = data[i];
// Check if the data is the delimiter
if(data[i] == (uint8_t)rb->delimiter) {
hasDelim = true;
}
// Update the write pointer, wrapping if necessary
if(++rb->ring_buffer_write >= ring_buffer_size) {
rb->ring_buffer_write = 0;
}
// Check if the buffer is full
if(rb->ring_buffer_write == rb->ring_buffer_read) {
// ERROR: buffer is full, discard oldest byte (read index)
if(++rb->ring_buffer_read >= ring_buffer_size) {
rb->ring_buffer_read = 0;
}
}
}
return hasDelim;
}
size_t ring_buffer_find_delim(RingBuffer* rb) {
size_t index = FURI_STRING_FAILURE;
// Search for the delimiter, starting at the read index
size_t i = rb->ring_buffer_read;
// While the buffer is not empty and the delimiter has not been found
while(i != rb->ring_buffer_write) {
// Check if the current byte is the delimiter
if(rb->ring_buffer[i] == (uint8_t)rb->delimiter) {
// Found the delimiter
index = i;
break;
}
// Update the index, wrapping if necessary
if(++i >= ring_buffer_size) {
i = 0;
}
}
return index;
}
void ring_buffer_extract_line(RingBuffer* rb, size_t delim_index, FuriString* line) {
if(delim_index > rb->ring_buffer_read) {
// line is in one chunk
furi_string_set_strn(
line,
(char*)&rb->ring_buffer[rb->ring_buffer_read],
delim_index - rb->ring_buffer_read + (rb->include_delimiter ? 1 : 0));
} else {
// line is split across the buffer wrap, so we need to copy it in two chunks
// first chunk is from read index to end of buffer
furi_string_set_strn(
line,
(char*)&rb->ring_buffer[rb->ring_buffer_read],
ring_buffer_size - rb->ring_buffer_read);
// second chunk is from start of buffer to delimiter
for(size_t i = 0; i < delim_index; i++) {
furi_string_push_back(line, (char)rb->ring_buffer[i]);
}
// add the delimiter if required
if(rb->include_delimiter) {
furi_string_push_back(line, (char)rb->ring_buffer[delim_index]);
}
}
// update the buffer read pointer, wrapping if necessary
rb->ring_buffer_read = delim_index + 1;
if(rb->ring_buffer_read >= ring_buffer_size) {
rb->ring_buffer_read = 0;
}
}
void ring_buffer_clear(RingBuffer* rb) {
rb->ring_buffer_read = 0;
rb->ring_buffer_write = 0;
}

View File

@ -0,0 +1,88 @@
/**
* A ring buffer. This is a circular buffer that can be used to store data until a
* delimiter is found, at which point the line can be extracted. The ring buffer is a
* fixed size and will overwrite old data if the buffer is full.
*
* @author CodeAllNight
*/
#include <furi.h>
/**
* Ring buffer structure
*/
typedef struct RingBuffer RingBuffer;
/**
* Allocates a new ring buffer. This is a circular buffer that can be used to store data
* until a delimiter is found, at which point the line can be extracted. The ring buffer
* is a fixed size and will overwrite old data if the buffer is full.
*
* @return A new ring buffer
*/
RingBuffer* ring_buffer_alloc();
/**
* Frees a ring buffer.
*
* @param rb The ring buffer to free
*/
void ring_buffer_free(RingBuffer* rb);
/**
* Sets the delimiter for the ring buffer. The delimiter is the character that will be
* searched for when extracting a line from the buffer.
*
* @param rb The ring buffer
* @param delimiter The delimiter character
* @param include_delimiter If true, the delimiter will be included in the extracted line
*/
void ring_buffer_set_delimiter(RingBuffer* rb, char delimiter, bool include_delimiter);
/**
* Returns the number of bytes available in the ring buffer. This is the number of bytes
* that can be added to the buffer before it is full.
*
* @param rb The ring buffer
*
* @return The number of bytes available in the ring buffer
*/
size_t ring_buffer_available(RingBuffer* rb);
/**
* Adds data to the ring buffer. If the buffer is full, the oldest data will be overwritten.
*
* @param rb The ring buffer
* @param data The data to add
* @param length The length of the data to add
*
* @return True if the data was added successfully, false if the buffer is full
*/
bool ring_buffer_add(RingBuffer* rb, uint8_t* data, size_t length);
/**
* Searches the ring buffer for the delimiter. If the delimiter is found, the index of the
* delimiter is returned. If the delimiter is not found, the function returns FURI_STRING_FAILURE
* (-1).
*
* @param rb The ring buffer
*
* @return The index of the delimiter, or FURI_STRING_FAILURE if the delimiter is not found
*/
size_t ring_buffer_find_delim(RingBuffer* rb);
/**
* Extracts a line from the ring buffer. The line is stored in the provided FuriString.
*
* @param rb The ring buffer
* @param delim_index The index of the delimiter
* @param line The FuriString to store the line in
*/
void ring_buffer_extract_line(RingBuffer* rb, size_t delim_index, FuriString* line);
/**
* Clears the ring buffer. This will remove all data from the buffer.
*
* @param rb The ring buffer
*/
void ring_buffer_clear(RingBuffer* rb);

142
gpio/uart_demo/uart_demo.c Normal file
View File

@ -0,0 +1,142 @@
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/modules/submenu.h>
#include <gui/view_dispatcher.h>
#include <gui/canvas.h>
#include <input/input.h>
#include "uart_helper.h"
#define DEVICE_BAUDRATE 115200
#define LINE_DELIMITER '\n'
#define INCLUDE_LINE_DELIMITER false
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
uint32_t index;
UartHelper* uart_helper;
FuriString* message2;
} UartDemoApp;
typedef enum {
UartDemoSubMenuViewId = 1,
} UartDemoViewIds;
/**
* This callback function is called when a submenu item is clicked.
*
* @param context The context passed to the submenu.
* @param index The index of the submenu item that was clicked.
*/
static void uart_demo_submenu_item_callback(void* context, uint32_t index);
/**
* Adds the default submenu entries.
*
* @param submenu The submenu.
* @param context The context to pass to the submenu item callback function.
*/
static void uart_demo_submenu_add_default_entries(Submenu* submenu, void* context) {
UartDemoApp* app = context;
submenu_reset(submenu);
submenu_add_item(submenu, "Clear", 0, uart_demo_submenu_item_callback, context);
submenu_add_item(submenu, "Send Msg 1", 1, uart_demo_submenu_item_callback, context);
submenu_add_item(submenu, "Send Msg 2", 2, uart_demo_submenu_item_callback, context);
app->index = 3;
}
static void uart_demo_submenu_item_callback(void* context, uint32_t index) {
UartDemoApp* app = context;
if(index == 0) {
// Clear the submenu and add the default entries.
uart_demo_submenu_add_default_entries(app->submenu, app);
} else if(index == 1) {
// Send a "Hello World!" message over the UART.
uart_helper_send(app->uart_helper, "Hello World!\n", 13);
} else if(index == 2) {
furi_string_printf(app->message2, "Index is %ld.\n", app->index);
uart_helper_send_string(app->uart_helper, app->message2);
} else {
// The item was received data.
}
}
static void uart_demo_process_line(FuriString* line, void* context) {
UartDemoApp* app = context;
submenu_add_item(
app->submenu,
furi_string_get_cstr(line),
app->index++,
uart_demo_submenu_item_callback,
app);
}
static bool uart_demo_navigation_callback(void* context) {
UNUSED(context);
// We don't want to handle any navigation events, the back button should exit the app.
return true;
}
static uint32_t uart_demo_exit(void* context) {
UNUSED(context);
// Exit the app.
return VIEW_NONE;
}
static UartDemoApp* uart_demo_app_alloc() {
UartDemoApp* app = malloc(sizeof(UartDemoApp));
// Initialize the GUI. Create a view dispatcher and attach it to the GUI.
// Create a submenu, add default entries and add the submenu to the view
// dispatcher. Set the submenu as the current view.
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
app->submenu = submenu_alloc();
uart_demo_submenu_add_default_entries(app->submenu, app);
view_dispatcher_add_view(
app->view_dispatcher, UartDemoSubMenuViewId, submenu_get_view(app->submenu));
view_set_previous_callback(submenu_get_view(app->submenu), uart_demo_exit);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, uart_demo_navigation_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, UartDemoSubMenuViewId);
// Allocate a string to store the second message.
app->message2 = furi_string_alloc();
// Initialize the UART helper.
app->uart_helper = uart_helper_alloc();
uart_helper_set_baud_rate(app->uart_helper, DEVICE_BAUDRATE);
uart_helper_set_delimiter(app->uart_helper, LINE_DELIMITER, INCLUDE_LINE_DELIMITER);
uart_helper_set_callback(app->uart_helper, uart_demo_process_line, app);
return app;
}
static void uart_demo_app_free(UartDemoApp* app) {
uart_helper_free(app->uart_helper);
furi_string_free(app->message2);
view_dispatcher_remove_view(app->view_dispatcher, UartDemoSubMenuViewId);
view_dispatcher_free(app->view_dispatcher);
submenu_free(app->submenu);
furi_record_close(RECORD_GUI);
free(app);
}
int32_t uart_demo_main(void* p) {
UNUSED(p);
UartDemoApp* app = uart_demo_app_alloc();
view_dispatcher_run(app->view_dispatcher);
uart_demo_app_free(app);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -0,0 +1,272 @@
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
* It uses a stream buffer to receive data from the UART ISR, and a worker thread
* to dequeue data from the stream buffer and process it. The worker thread uses
* a ring buffer to hold data until a delimiter is found, at which point the line
* is extracted and the process_line callback is invoked.
*
* @author CodeAllNight
*/
#include <furi_hal.h>
#include "ring_buffer.h"
/**
* Callback invoked when a line is read from the UART.
*/
typedef void (*ProcessLine)(FuriString* line, void* context);
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
*/
typedef struct {
// UART bus & channel to use
FuriHalBus uart_bus;
FuriHalSerialHandle* serial_handle;
bool uart_init_by_app;
// Stream buffer to hold incoming data (worker will dequeue and process)
FuriStreamBuffer* rx_stream;
// Worker thread that dequeues data from the stream buffer and processes it
FuriThread* worker_thread;
// Buffer to hold data until a delimiter is found
RingBuffer* ring_buffer;
// Callback to invoke when a line is read
ProcessLine process_line;
void* context;
} UartHelper;
/**
* WorkerEventFlags are used to signal the worker thread to exit or to process data.
* Each flag is a bit in a 32-bit integer, so we can use the FuriThreadFlags API to
* wait for either flag to be set.
*/
typedef enum {
WorkerEventDataWaiting = 1 << 0, // bit flag 0 - data is waiting to be processed
WorkerEventExiting = 1 << 1, // bit flag 1 - worker thread is exiting
} WorkerEventFlags;
/**
* Invoked when a byte of data is received on the UART bus. This function
* adds the byte to the stream buffer and sets the WorkerEventDataWaiting flag.
*
* @param handle Serial handle
* @param event FuriHalSerialRxEvent
* @param context UartHelper instance
*/
static void uart_helper_received_byte_callback(
FuriHalSerialHandle* handle,
FuriHalSerialRxEvent event,
void* context) {
UartHelper* helper = context;
if(event == FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(helper->rx_stream, (void*)&data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventDataWaiting);
}
}
/**
* Worker thread that dequeues data from the stream buffer and processes it. When
* a delimiter is found in the data, the line is extracted and the process_line callback
* is invoked. This thread will exit when the WorkerEventExiting flag is set.
*
* @param context UartHelper instance
* @return 0
*/
static int32_t uart_helper_worker(void* context) {
UartHelper* helper = context;
FuriString* line = furi_string_alloc();
uint32_t events;
do {
events = furi_thread_flags_wait(
WorkerEventDataWaiting | WorkerEventExiting, FuriFlagWaitAny, FuriWaitForever);
if(events & WorkerEventDataWaiting) {
size_t length_read = 0;
do {
// We will read out of the stream into this temporary read_buffer in chunks
// of up to 32 bytes. A tricker implementation would be to read directly
// into our ring_buffer, avoiding the extra copy.
uint8_t read_buffer[32];
size_t available = ring_buffer_available(helper->ring_buffer);
if(available == 0) {
// In our case, the buffer is large enough to hold the entire response,
// so we should never hit this case & data loss is fine in the
// exceptional case.
// If loosing data is unacceptable, you could transfer the buffer into
// a string and prepend that to the response, or you could increase the
// size of the buffer.
// Buffer is full, so data loss will happen at beginning of string!
// We read one byte (hopefully the delimiter).
available = 1;
}
// We will read up to 32 bytes, but no more than the available space in the
// ring buffer.
size_t request_bytes = available < COUNT_OF(read_buffer) ? available :
COUNT_OF(read_buffer);
// hasDelim will be true if at least one delimiter was found in the data.
bool hasDelim = false;
// Read up to 32 bytes from the stream buffer into our temporary read_buffer.
length_read =
furi_stream_buffer_receive(helper->rx_stream, read_buffer, request_bytes, 0);
// Add the data to the ring buffer, check for delimiters, and process lines.
if(length_read > 0) {
// Add the data to the ring buffer. If at least one delimiter is found,
// hasDelim will be true.
hasDelim = ring_buffer_add(helper->ring_buffer, read_buffer, length_read);
if(hasDelim) {
size_t index;
do {
// Find the next delimiter in the ring buffer.
index = ring_buffer_find_delim(helper->ring_buffer);
// If a delimiter was found, extract the line and process it.
if(index != FURI_STRING_FAILURE) {
// Extract the line from the ring buffer, advancing the read
// pointer to the next byte after the delimiter.
ring_buffer_extract_line(helper->ring_buffer, index, line);
// Invoke the callback to process the line.
if(helper->process_line) {
helper->process_line(line, helper->context);
}
}
} while(index != FURI_STRING_FAILURE);
}
}
// Keep reading until we have read all of the data from the stream buffer.
} while(length_read > 0);
}
// Keep processing data until the WorkerEventExiting flag is set.
} while((events & WorkerEventExiting) != WorkerEventExiting);
furi_string_free(line);
return 0;
}
UartHelper* uart_helper_alloc() {
// rx_buffer_size should be large enough to hold the entire response from the device.
const size_t rx_buffer_size = 2048;
// worker_stack_size should be large enough stack for the worker thread (including functions it calls).
const size_t worker_stack_size = 1024;
// uart_baud is the default baud rate for the UART.
const uint32_t uart_baud = 115200;
// The uart_helper uses USART1.
UartHelper* helper = malloc(sizeof(UartHelper));
helper->uart_bus = FuriHalBusUSART1;
helper->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
// Typically the UART is already enabled and will assert if you try to enable again.
helper->uart_init_by_app = !furi_hal_bus_is_enabled(helper->uart_bus);
if(helper->uart_init_by_app) {
furi_hal_serial_init(helper->serial_handle, uart_baud);
} else {
// If UART is already initialized, disable the console so it doesn't write to the UART.
// furi_hal_console_disable();
}
// process_line callback gets invoked when a line is read. By default the callback is not set.
helper->process_line = NULL;
// Set the baud rate for the UART
furi_hal_serial_set_br(helper->serial_handle, uart_baud);
// When a byte of data is received, it will be appended to the rx_stream. A worker thread
// will dequeue the data from the rx_stream.
helper->rx_stream = furi_stream_buffer_alloc(rx_buffer_size, 1);
// The worker thread will remove the data from the rx_stream and copy it into the ring_buffer.
// The worker thread will then process the data in the ring_buffer, invoking the process_line
// callback whenever a delimiter is found in the data.
helper->ring_buffer = ring_buffer_alloc();
// worker_thread is the routine that will process data from the rx_stream.
helper->worker_thread =
furi_thread_alloc_ex("UartHelperWorker", worker_stack_size, uart_helper_worker, helper);
furi_thread_start(helper->worker_thread);
// Set the callback to invoke when data is received.
furi_hal_serial_async_rx_start(
helper->serial_handle, uart_helper_received_byte_callback, helper, false);
return helper;
}
void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter) {
// Update the delimiter character and flag to determine if delimiter should be part
// of the response to the process_line callback.
ring_buffer_set_delimiter(helper->ring_buffer, delimiter, include_delimiter);
}
void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context) {
// Set the process_line callback and context.
helper->process_line = process_line;
helper->context = context;
}
void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate) {
// Update the baud rate for the UART.
furi_hal_serial_set_br(helper->serial_handle, baud_rate);
ring_buffer_clear(helper->ring_buffer);
}
void uart_helper_send(UartHelper* helper, const char* data, size_t length) {
// Transmit data via UART TX.
furi_hal_serial_tx(helper->serial_handle, (uint8_t*)data, length);
}
void uart_helper_send_string(UartHelper* helper, FuriString* string) {
const char* str = furi_string_get_cstr(string);
// UTF-8 strings can have character counts different then lengths!
// Count the bytes until a null is encountered.
size_t length = 0;
while(str[length] != 0) {
length++;
}
// Transmit data
uart_helper_send(helper, str, length);
}
void uart_helper_free(UartHelper* helper) {
// Signal that we want the worker to exit. It may be doing other work.
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventExiting);
// Wait for the worker_thread to complete it's work and release its resources.
furi_thread_join(helper->worker_thread);
// Free the worker_thread.
furi_thread_free(helper->worker_thread);
furi_hal_serial_control_release(helper->serial_handle);
// If the UART was initialized by the application, deinitialize it, otherwise re-enable the console.
if(helper->uart_init_by_app) {
furi_hal_serial_deinit(helper->serial_handle);
} else {
// furi_hal_console_enable();
}
// Free the rx_stream and ring_buffer.
furi_stream_buffer_free(helper->rx_stream);
ring_buffer_free(helper->ring_buffer);
free(helper);
}

View File

@ -0,0 +1,266 @@
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
* It uses a stream buffer to receive data from the UART ISR, and a worker thread
* to dequeue data from the stream buffer and process it. The worker thread uses
* a ring buffer to hold data until a delimiter is found, at which point the line
* is extracted and the process_line callback is invoked.
*
* @author CodeAllNight
*/
#include <furi_hal.h>
#include "ring_buffer.h"
/**
* Callback invoked when a line is read from the UART.
*/
typedef void (*ProcessLine)(FuriString* line, void* context);
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
*/
typedef struct {
// UART bus & channel to use
FuriHalBus uart_bus;
FuriHalUartId uart_channel;
bool uart_init_by_app;
// Stream buffer to hold incoming data (worker will dequeue and process)
FuriStreamBuffer* rx_stream;
// Worker thread that dequeues data from the stream buffer and processes it
FuriThread* worker_thread;
// Buffer to hold data until a delimiter is found
RingBuffer* ring_buffer;
// Callback to invoke when a line is read
ProcessLine process_line;
void* context;
} UartHelper;
/**
* WorkerEventFlags are used to signal the worker thread to exit or to process data.
* Each flag is a bit in a 32-bit integer, so we can use the FuriThreadFlags API to
* wait for either flag to be set.
*/
typedef enum {
WorkerEventDataWaiting = 1 << 0, // bit flag 0 - data is waiting to be processed
WorkerEventExiting = 1 << 1, // bit flag 1 - worker thread is exiting
} WorkerEventFlags;
/**
* Invoked by ISR when a byte of data is received on the UART bus. This function
* adds the byte to the stream buffer and sets the WorkerEventDataWaiting flag.
*
* @param ev Event type
* @param data Byte of data received
* @param context UartHelper instance
*/
static void uart_helper_received_byte_callback(UartIrqEvent ev, uint8_t data, void* context) {
UartHelper* helper = context;
if(ev == UartIrqEventRXNE) {
furi_stream_buffer_send(helper->rx_stream, &data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventDataWaiting);
}
}
/**
* Worker thread that dequeues data from the stream buffer and processes it. When
* a delimiter is found in the data, the line is extracted and the process_line callback
* is invoked. This thread will exit when the WorkerEventExiting flag is set.
*
* @param context UartHelper instance
* @return 0
*/
static int32_t uart_helper_worker(void* context) {
UartHelper* helper = context;
FuriString* line = furi_string_alloc();
uint32_t events;
do {
events = furi_thread_flags_wait(
WorkerEventDataWaiting | WorkerEventExiting, FuriFlagWaitAny, FuriWaitForever);
if(events & WorkerEventDataWaiting) {
size_t length_read = 0;
do {
// We will read out of the stream into this temporary read_buffer in chunks
// of up to 32 bytes. A tricker implementation would be to read directly
// into our ring_buffer, avoiding the extra copy.
uint8_t read_buffer[32];
size_t available = ring_buffer_available(helper->ring_buffer);
if(available == 0) {
// In our case, the buffer is large enough to hold the entire response,
// so we should never hit this case & data loss is fine in the
// exceptional case.
// If loosing data is unacceptable, you could transfer the buffer into
// a string and prepend that to the response, or you could increase the
// size of the buffer.
// Buffer is full, so data loss will happen at beginning of string!
// We read one byte (hopefully the delimiter).
available = 1;
}
// We will read up to 32 bytes, but no more than the available space in the
// ring buffer.
size_t request_bytes = available < COUNT_OF(read_buffer) ? available :
COUNT_OF(read_buffer);
// hasDelim will be true if at least one delimiter was found in the data.
bool hasDelim = false;
// Read up to 32 bytes from the stream buffer into our temporary read_buffer.
length_read =
furi_stream_buffer_receive(helper->rx_stream, read_buffer, request_bytes, 0);
// Add the data to the ring buffer, check for delimiters, and process lines.
if(length_read > 0) {
// Add the data to the ring buffer. If at least one delimiter is found,
// hasDelim will be true.
hasDelim = ring_buffer_add(helper->ring_buffer, read_buffer, length_read);
if(hasDelim) {
size_t index;
do {
// Find the next delimiter in the ring buffer.
index = ring_buffer_find_delim(helper->ring_buffer);
// If a delimiter was found, extract the line and process it.
if(index != FURI_STRING_FAILURE) {
// Extract the line from the ring buffer, advancing the read
// pointer to the next byte after the delimiter.
ring_buffer_extract_line(helper->ring_buffer, index, line);
// Invoke the callback to process the line.
if(helper->process_line) {
helper->process_line(line, helper->context);
}
}
} while(index != FURI_STRING_FAILURE);
}
}
// Keep reading until we have read all of the data from the stream buffer.
} while(length_read > 0);
}
// Keep processing data until the WorkerEventExiting flag is set.
} while((events & WorkerEventExiting) != WorkerEventExiting);
furi_string_free(line);
return 0;
}
UartHelper* uart_helper_alloc() {
// rx_buffer_size should be large enough to hold the entire response from the device.
const size_t rx_buffer_size = 2048;
// worker_stack_size should be large enough stack for the worker thread (including functions it calls).
const size_t worker_stack_size = 1024;
// uart_baud is the default baud rate for the UART.
const uint32_t uart_baud = 115200;
// The uart_helper uses USART1.
UartHelper* helper = malloc(sizeof(UartHelper));
helper->uart_bus = FuriHalBusUSART1;
helper->uart_channel = FuriHalUartIdUSART1;
// Typically the UART is already enabled and will assert if you try to enable again.
helper->uart_init_by_app = !furi_hal_bus_is_enabled(helper->uart_bus);
if(helper->uart_init_by_app) {
furi_hal_uart_init(helper->uart_channel, uart_baud);
} else {
// If UART is already initialized, disable the console so it doesn't write to the UART.
furi_hal_console_disable();
}
// process_line callback gets invoked when a line is read. By default the callback is not set.
helper->process_line = NULL;
// Set the baud rate for the UART
furi_hal_uart_set_br(helper->uart_channel, uart_baud);
// When a byte of data is received, it will be appended to the rx_stream. A worker thread
// will dequeue the data from the rx_stream.
helper->rx_stream = furi_stream_buffer_alloc(rx_buffer_size, 1);
// The worker thread will remove the data from the rx_stream and copy it into the ring_buffer.
// The worker thread will then process the data in the ring_buffer, invoking the process_line
// callback whenever a delimiter is found in the data.
helper->ring_buffer = ring_buffer_alloc();
// worker_thread is the routine that will process data from the rx_stream.
helper->worker_thread =
furi_thread_alloc_ex("UartHelperWorker", worker_stack_size, uart_helper_worker, helper);
furi_thread_start(helper->worker_thread);
// Set the callback to invoke when data is received.
furi_hal_uart_set_irq_cb(helper->uart_channel, uart_helper_received_byte_callback, helper);
return helper;
}
void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter) {
// Update the delimiter character and flag to determine if delimiter should be part
// of the response to the process_line callback.
ring_buffer_set_delimiter(helper->ring_buffer, delimiter, include_delimiter);
}
void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context) {
// Set the process_line callback and context.
helper->process_line = process_line;
helper->context = context;
}
void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate) {
// Update the baud rate for the UART.
furi_hal_uart_set_br(helper->uart_channel, baud_rate);
ring_buffer_clear(helper->ring_buffer);
}
void uart_helper_send(UartHelper* helper, const char* data, size_t length) {
// Transmit data via UART TX.
furi_hal_uart_tx(helper->uart_channel, (uint8_t*)data, length);
}
void uart_helper_send_string(UartHelper* helper, FuriString* string) {
const char* str = furi_string_get_cstr(string);
// UTF-8 strings can have character counts different then lengths!
// Count the bytes until a null is encountered.
size_t length = 0;
while(str[length] != 0) {
length++;
}
// Transmit data
uart_helper_send(helper, str, length);
}
void uart_helper_free(UartHelper* helper) {
// Signal that we want the worker to exit. It may be doing other work.
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventExiting);
// Wait for the worker_thread to complete it's work and release its resources.
furi_thread_join(helper->worker_thread);
// Free the worker_thread.
furi_thread_free(helper->worker_thread);
// If the UART was initialized by the application, deinitialize it, otherwise re-enable the console.
if(helper->uart_init_by_app) {
furi_hal_uart_deinit(helper->uart_channel);
} else {
furi_hal_console_enable();
}
// Free the rx_stream and ring_buffer.
furi_stream_buffer_free(helper->rx_stream);
ring_buffer_free(helper->ring_buffer);
free(helper);
}

View File

@ -0,0 +1,75 @@
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
* It uses a stream buffer to receive data from the UART ISR, and a worker thread
* to dequeue data from the stream buffer and process it. The worker thread uses
* a ring buffer to hold data until a delimiter is found, at which point the line
* is extracted and the process_line callback is invoked.
*
* @author CodeAllNight
*/
#include <furi.h>
/**
* A helper class for parsing data received over UART.
*/
typedef struct UartHelper UartHelper;
/**
* Callback function for processing a line of data.
*
* @param line The line of data to process.
*/
typedef void (*ProcessLine)(FuriString* line, void* context);
/**
* Allocates a new UartHelper. The UartHelper will be initialized with a baud rate of 115200.
* Log messages will be disabled since they also use the UART.
*
* IMPORTANT -- FURI_LOG_x calls will not work!
*
* @return A new UartHelper.
*/
UartHelper* uart_helper_alloc();
/**
* Sets the delimiter to use when parsing data. The default delimeter is '\n' and
* the default value for include_delimiter is false.
*
* @param helper The UartHelper.
* @param delimiter The delimiter to use.
* @param include_delimiter If true, the delimiter will be included in the line of data passed to the callback function.
*/
void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter);
/**
* Sets the callback function to be called when a line of data is received.
*
* @param helper The UartHelper.
* @param process_line The callback function.
* @param context The context to pass to the callback function.
*/
void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context);
/**
* Sets the baud rate for the UART. The default is 115200.
*
* @param helper The UartHelper.
* @param baud_rate The baud rate.
*/
void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate);
/**
* Sends data over the UART TX pin.
*/
void uart_helper_send(UartHelper* helper, const char* data, size_t length);
/**
* Sends a string over the UART TX pin.
*/
void uart_helper_send_string(UartHelper* helper, FuriString* string);
/**
* Frees the UartHelper & enables log messages.
*/
void uart_helper_free(UartHelper* helper);

123
gpio/wiegand/README.md Normal file
View File

@ -0,0 +1,123 @@
# Wiegand
This application supports W4, W8, W24, W26, W32, W34, W37, W40 and W48 formats.
This application can be used to test Wiegand readers and keypads. It can save the data to a file, and can load and replay the data. Timings are measured and displayed; which can be used to help debug Wiegand readers.
## Wiring
The D0 and D1 wires of the Wiegand must be about 1K or higher for the Flipper to be able to effectively pull them down to ground. If the line has a lower resistance, like 100 ohms, then you need to add MOSFETs to help pull the lines low. The following [YouTube video](https://youtu.be/OVyd3ffnZ0M) is a demonstration of how to correctly wire the device.
In all configurations:
- Pin A7 goes to your Wiegand D1 (white) wire.
- Pin A4 goes to your Wiegand D0 (green) wire.
- Pin GND goes to your Wiegand GND (black) wire.
This is sufficient for reading signals with both this application and the Debug Accessor application.
If the pull-up resistors on your card reader are less than 1K, you also need the following:
- Two additional MOSFETs are required. I used IRF540 (but perhaps IRL540 would be better?)
- Two additional Pull-down resitors are required. I used 5K, but 4.7K would be fine as well.
- I'm still learning, but perhaps a MOSFET driver / optocoupler would provide even more protection to the Flipper GPIO pins? Use this circuit at your own risk. :)
Here is how you wire up the MOSFET and the pull-down resistors:
- The source pin of each MOSFET connects to ground.
- The gate pin connects to one side of a resistor. The other side of the resistor goes to ground.
- For the MOSFET used for D1:
- The gate pin of the MOSFET for D1 goes to pin A6 on the Flipper. (It should also already be connected to the pull-down resistor)
- The drain pin of the MOSFET for D1 goes to pin A7 on the Flipper.
- For the MOSFET used for D0:
- The gate pin of the MOSFET for D0 goes to pin B3 on the Flipper. (It should also already be connected to the pull-down resistor)
- The drain pin of the MOSFET for D0 goes to pin A4 on the Flipper.
## W4: 4-bit Wiegand
This format is used by some keypads. Digits 0-9 are sent as 0-9. ESC is sent as 10 and ENTER as 11. There is no parity bit. The application will display
the button pressed (including ESC and ENTER).
## W8: 8-bit Wiegand
This format is used by some keypads. The last 4 bits are the actual data.Digits 0-9 are sent as 0-9. ESC is sent as 10 and ENTER as 11. The first 4 bits are the inverse of the last 4 bits. The application will display
the button pressed (including ESC and ENTER). If there are any bit errors, the application will show the incorrect bits.
## W26: 26-bit Wiegand
This is a 26-bit format used by many readers. The first bit is an even parity bit. The next 8 bits are the facility code. The next 16 bits are the card number. The last bit is an odd parity bit. The application will display the facility code and card number. If there are any bit errors, the application will show the incorrect bits (the even partity is the first 13 bits, odd parity is the last 13 bits).
## W24: 24-bit Wiegand
This is similar to W26, but without the leading and trailing parity bits. The first 8 bits are the facility code. The next 16 bits are the card number. The application will display the facility code and card number.
## W35: 35-bit Wiegand
This is HID 35 bit Corporate 1000 - C1k35s format. The first bit is odd parity 2 (based on bits 2-35). The next bit is even parity (based on 4-5,7-8,10-11,...,33-34). Then 12 bit company code. Then 20 bit card id. Then odd parity 1.
## W36: 36-bit Wiegand
This is decode HID 36 bit Keyscan - C15001 format. The first bit is an even parity bit. The next 10 bits are the OEM number. The next 8 bits are the facility code. The next 16 bits are the card number. The last bit is an odd parity bit.
Other 36 bit credentials may be decoded incorrectly.
## W48: 48-bit Wiegand
This is HID 48 bit Corporate 1000 - C1k48s format. The first bit is odd parity 2 (based on bits 2-48). The next bit is even parity (based on 4-5,7-8,10-11,...,46-47). Then 22 bit company code. Then 23 bit card id. Then odd parity 1 (based on 3-4,6-7,9-10,...,45-46).
## W32/W34/W37/W40: 32/34/37/40-bit Wiegand
These formats are not very standardized, so the application will not try to interpret the data. You can modify the wiegand_data.c file to add your own interpretation.
## Installation
### Step 1. Install Git and VS Code.
If you have not already installed Git and VS Code, you will need to do so. The following links will take you to the download pages for Git and VS Code.
- [Git](https://git-scm.com/downloads)
- [VS Code](https://code.visualstudio.com/download)
### Step 2. Clone the Flipper Zero firmware.
Clone the Flipper Zero firmware from GitHub. The following command will clone the firmware into a folder named official-firmware. (The below commands may wrap on your screen. You can copy and paste the entire command; there should only be two lines.)
```console
cd <your working directory>
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git official-firmware
```
Replace _&lt;your working directory&gt;_ with the directory where you want to clone the firmware.
### Step 3. Run FBT to build the firmware and configure VS Code.
Run the following commands from the root of the firmware folder to build the firmware and configure VS Code. Replace _&lt;your working directory&gt;_ with the directory where you cloned the firmware. In some environments you do not need the "./" at the beginning of the command.
```console
cd <your working directory>
cd official-firmware
./fbt vscode_dist
./fbt updater_package
```
\*\*\* **Please follow the steps at [FBT VSCode integration](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md#vscode-integration) before proceeding.** \*\*\*
### Step 4. Open the applications_user folder in VS Code.
After following the previous step, navigate to the **applications_user** folder in VS Code.
### Step 5. Crete a new folder for the application.
Create a new folder for the application. The name of the folder will be the name of the application. For this example, we will use the name **wiegand**.
### Step 6. Copy the application files.
Copy the files from this project to the **wiegand** folder. Be sure to copy the scenes directory into a scenes directory in the **wiegand** folder.
### Step 7. Build the application.
Build the application by running the following command from the root of the firmware folder. Replace _&lt;your working directory&gt;_ with the directory where you cloned the firmware. In some environments you do not need the "./" at the beginning of the command.
```console
cd <your working directory>
cd official-firmware
./fbt launch_app APPSRC=applications_users/wiegand
```

View File

@ -0,0 +1,11 @@
App(
appid="wiegand_reader",
name="Wiegand Reader",
apptype=FlipperAppType.EXTERNAL,
entry_point="wiegand_app",
requires=["gui"],
stack_size=2 * 1024,
fap_version=(1, 3),
fap_icon="wiegand.png",
fap_category="GPIO",
)

View File

@ -0,0 +1,5 @@
Filetype: Flipper Wiegand Key File
Version: 1
Protocol: RAW
Bits: 48
RAW_Data: D0 0 2243 D0 124823 127090 D0 249670 251937 D0 374517 376784 D0 499364 501631 D0 627194 629461 D0 752041 754307 D0 876888 879155 D0 1001753 1004020 D0 1126600 1128867 D0 1251447 1253714 D1 1376330 1378618 D0 1501154 1503415 D0 1626022 1628264 D0 1750845 1753111 D0 1875691 1877958 D0 2000557 2002824 D1 2125440 2127739 D1 2253284 2255577 D0 2378103 2380366 D1 2502981 2505267 D0 2627806 2630070 D0 2752650 2754917 D0 2877497 2879764 D0 3002363 3004629 D0 3127210 3129477 D0 3252057 3254324 D0 3376904 3379171 D0 3501751 3504017 D0 3626598 3628865 D1 3751479 3753765 D1 3879324 3881604 D1 4004200 4006479 D0 4129018 4131282 D1 4253897 4256182 D0 4378722 4380985 D1 4503600 4505886 D1 4628463 4630742 D1 4753319 4755599 D0 4878178 4880402 D1 5003035 5005321 D0 5127860 5130124 D0 5252704 5254971 D0 5380534 5382800 D1 5505415 5507694 D1 5630267 5632550 D0 5755117 5757360 D1 5879976 5882257

View File

@ -0,0 +1,347 @@
#include "../wiegand.h"
void wiegand_add_info_4bit_8bit(FuriString *buffer)
{
if (bit_count == 8)
{
for (int i = 0; i < 4; i++)
{
furi_string_cat_printf(
buffer, "\nbit %d: %d %d (bit %d)", i, data[i], data[i + 4], i + 4);
if (data[i] == data[i + 4])
{
furi_string_cat_printf(buffer, " - ERROR");
}
else
{
furi_string_cat_printf(buffer, " - OK");
}
}
}
if (bit_count == 4 || bit_count == 8)
{
int code = 0;
int offset = bit_count == 4 ? 0 : 4;
for (int i = 0; i < 4; i++)
{
code = code << 1;
code += data[i + offset] ? 1 : 0;
}
if (code <= 9)
{
furi_string_cat_printf(buffer, "\nButton: %d", code);
}
else if (code == 10)
{
furi_string_cat_printf(buffer, "\nButton: Escape");
}
else if (code == 11)
{
furi_string_cat_printf(buffer, "\nButton: Enter");
}
}
}
void wiegand_add_info_26bit(FuriString *buffer)
{
// 26 bit wiegand, the first bit is the even parity bit, which is
// based on the next 12 bits. The number of bits that are a 1 should
// be even.
// After the parity bit, the next 8 bits are the facility code.
// Then the next 16 bits are the card id .
furi_string_cat_printf(buffer, "\nFacility: ");
int code = 0;
int count = 0;
uint32_t dec = 0;
for (int i = 1; i < 25; i++)
{
code = code << 1;
dec = dec << 1;
code |= data[i] ? 1 : 0;
dec |= data[i] ? 1 : 0;
if (++count % 4 == 0)
{
furi_string_cat_printf(buffer, "%X", code);
code = 0;
}
if (i == 8)
{
furi_string_cat_printf(buffer, " (%ld)", dec);
dec = 0;
}
// Parity, then 8 bit facility code, then id.
if (i == 9)
{
furi_string_cat_printf(buffer, "\nCard: ");
}
}
furi_string_cat_printf(buffer, " (%ld)", dec);
int parity = 0;
if (data[0])
{
parity = 1;
}
for (int i = 1; i < 13; i++)
{
if (data[i])
{
parity++;
}
}
if (parity % 2 == 0)
{
furi_string_cat_printf(buffer, "\nEven Parity (%d): OK", parity);
}
else
{
furi_string_cat_printf(buffer, "\nEven Parity (%d): ERROR", parity);
}
if (data[13])
{
parity = 1;
}
else
{
parity = 0;
}
for (int i = 14; i < 26; i++)
{
if (data[i])
{
parity++;
}
}
if (parity % 2 == 0)
{
furi_string_cat_printf(buffer, "\nOdd Parity (%d): ERROR", parity);
}
else
{
furi_string_cat_printf(buffer, "\nOdd Parity (%d): OK", parity);
}
}
void wiegand_add_info_24bit(FuriString *buffer)
{
// 24 bit wiegand (no parity info).
// The First 8 bits are the facility code.
// Then the next 16 bits are the card id.
furi_string_cat_printf(buffer, "\nFacility: 0x");
int code = 0;
int count = 0;
uint32_t dec = 0;
for (int i = 0; i < 24; i++)
{
code = code << 1;
dec = dec << 1;
code |= data[i] ? 1 : 0;
dec |= data[i] ? 1 : 0;
if (++count % 4 == 0)
{
furi_string_cat_printf(buffer, "%X", code);
code = 0;
}
// The first 8 bits are facility code, then comes id.
if (i == 8)
{
furi_string_cat_printf(buffer, " (%ld)", dec);
dec = 0;
furi_string_cat_printf(buffer, "\nCard: 0x");
}
}
furi_string_cat_printf(buffer, " (%ld)", dec);
}
void wiegand_add_info_48bit(FuriString *buffer)
{
// We assume this is HID 48 bit Corporate 1000 - H2004064 format.
// The first bit is odd parity 2 (based on bits 2-48).
// The next bit is even parity (based on 4-5,7-8,10-11,...,46-47).
// Then 22 bit company code.
// Then 23 bit card id.
/// Then odd parity 1 (based on 3-4,6-7,9-10,...,45-46).
// 22 bits company code (bits 3-24; data[2..23])
uint32_t code = 0;
for (int i = 2; i <= 23; i++)
{
code = code << 1;
code |= data[i] ? 1 : 0;
}
furi_string_cat_printf(buffer, "\nFacility: %lX (%ld)", code, code);
// 23 bit card id (bits 25-47; data[24..46]).
code = 0;
for (int i = 24; i <= 46; i++)
{
code = code << 1;
code |= data[i] ? 1 : 0;
}
furi_string_cat_printf(buffer, "\nCard: %lX (%ld)", code, code);
// TODO: Add the 3 parity checks.
}
void wiegand_add_info_35bit(FuriString *buffer)
{
// We assume this is HID 35 bit Corporate 1000 - C1k35s format.
// 12 bits company code
uint32_t code = 0;
for (int i = 2; i <= 13; i++)
{
code = code << 1;
code |= data[i] ? 1 : 0;
}
furi_string_cat_printf(buffer, "\nFacility: %lX (%ld)", code, code);
// 20 bit card id
code = 0;
for (int i = 14; i <= 33; i++)
{
code = code << 1;
code |= data[i] ? 1 : 0;
}
furi_string_cat_printf(buffer, "\nCard: %lX (%ld)", code, code);
}
void wiegand_add_info_36bit(FuriString *buffer)
{
// We assume this is HID 36 bit Keyscan - C15001 format.
// 10 bits OEM
uint32_t oem = 0;
for (int i = 1; i <= 10; i++)
{
oem = (oem << 1) | (data[i] ? 1 : 0);
}
furi_string_cat_printf(buffer, "\nOEM: %lX (%ld)", oem, oem);
// 8 bits facility code
uint32_t facilityCode = 0;
for (int i = 11; i <= 18; i++)
{
facilityCode = (facilityCode << 1) | (data[i] ? 1 : 0);
}
furi_string_cat_printf(buffer, "\nFacility: %lX (%ld)", facilityCode, facilityCode);
// 16 bits card ID
uint32_t cardID = 0;
for (int i = 19; i <= 34; i++)
{
cardID = (cardID << 1) | (data[i] ? 1 : 0);
}
furi_string_cat_printf(buffer, "\nCard: %lX (%ld)", cardID, cardID);
}
void wiegand_add_info(FuriString *buffer)
{
furi_string_push_back(buffer, '\n');
if (bit_count == 4 || bit_count == 8)
{
wiegand_add_info_4bit_8bit(buffer);
}
else if (bit_count == 26)
{
wiegand_add_info_26bit(buffer);
}
else if (bit_count == 24)
{
wiegand_add_info_24bit(buffer);
}
else if (bit_count == 35)
{
wiegand_add_info_35bit(buffer);
}
else if (bit_count == 36)
{
wiegand_add_info_36bit(buffer);
}
else if (bit_count == 48)
{
wiegand_add_info_48bit(buffer);
}
furi_string_push_back(buffer, '\n');
}
void wiegand_button_callback(GuiButtonType result, InputType type, void *context)
{
App *app = context;
if (type == InputTypeShort && result == GuiButtonTypeLeft)
{
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataSceneSaveButtonEvent);
}
else if (type == InputTypeShort && result == GuiButtonTypeCenter)
{
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataScenePlayButtonEvent);
}
}
void wiegand_data_scene_on_enter(void *context)
{
App *app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Wiegand Data");
FuriString *buffer = furi_string_alloc(1024);
furi_string_printf(buffer, "Bits: %d\n", bit_count);
for (int i = 0; i < bit_count; i++)
{
furi_string_push_back(buffer, data[i] ? '1' : '0');
if ((bit_count - i - 1) % 22 == 21)
{
furi_string_push_back(buffer, '\n');
}
}
// furi_string_cat_printf(buffer, "\nPulse: %ld us", (data_rise[0] - data_fall[0]) / 64);
// furi_string_cat_printf(buffer, "\nPeriod: %ld us", (data_fall[1] - data_fall[0]) / 64);
wiegand_add_info(buffer);
for (int i = 0; i < bit_count;)
{
uint32_t pulse = (data_rise[i] - data_fall[i]) / 64;
i++;
uint32_t period = (i < bit_count) ? (data_fall[i] - data_fall[i - 1]) / 64 : 0;
furi_string_cat_printf(
buffer, "\n%c : %ld us, %ld us", data[i] ? '1' : '0', pulse, period);
}
widget_add_text_scroll_element(app->widget, 0, 12, 128, 34, furi_string_get_cstr(buffer));
if (!data_saved)
{
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Save", wiegand_button_callback, app);
}
widget_add_button_element(
app->widget, GuiButtonTypeCenter, "Play", wiegand_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}
bool wiegand_data_scene_on_event(void *context, SceneManagerEvent event)
{
App *app = context;
bool consumed = false;
switch (event.type)
{
case SceneManagerEventTypeCustom:
switch (event.event)
{
case WiegandDataScenePlayButtonEvent:
wiegand_play();
consumed = true;
break;
case WiegandDataSceneSaveButtonEvent:
scene_manager_next_scene(app->scene_manager, WiegandSaveScene);
consumed = true;
break;
default:
consumed = false;
break;
}
break;
default:
break;
}
return consumed;
}

View File

@ -0,0 +1,43 @@
#include "../wiegand.h"
void wiegand_instructions_scene_on_enter(void* context) {
App* app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Instructions");
widget_add_text_scroll_element(
app->widget,
0,
15,
128,
40,
"Version 1.3\n"
"Only use on devices you own!\n"
"Connect D0 (Green) to pin A4\n"
"Connect D1 (White) to pin A7\n"
"Connect GND (Black) to GND\n"
"\nOption 1 (no mosfet):\n"
"Add a 10K inline resistor on D0\n"
"between keypad and door.\n"
"Connect Flipper on door\n"
"side. Add 10K inline resistor\n"
"on D1 between keypad and\n"
"door. Connect Flipper on\n"
"door side. Do not inturrupt\n"
"the D0/D1 connections while\n"
"adding the resistors."
"\n\nOption 2 (mosfet):\n"
"Connect pin A6 to gate of\n"
"mosfet for D1. Connect 5K\n"
"resistor from gate to GND.\n"
"Connect pin A7 to drain of\n"
"mosfet for D1. Connect source\n"
"of mosfet for D1 to GND.\n"
"Connect pin B3 to gate of\n"
"mosfet for D0. Connect 5K\n"
"resistor from gate to GND.\n"
"Connect pin A4 to drain of\n"
"mosfet for D0. Connect\n"
"source of mosfet for D0 to\n"
"GND.\n");
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}

View File

@ -0,0 +1,75 @@
#include "../wiegand.h"
void wiegand_load_scene_on_enter(void* context) {
App* app = context;
bit_count = 0;
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, WIEGAND_SAVE_EXTENSION, NULL);
browser_options.base_path = WIEGAND_SAVE_FOLDER;
furi_string_set(app->file_path, browser_options.base_path);
FuriString* buffer = furi_string_alloc(1024);
if(dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* data_file = storage_file_alloc(storage);
if(storage_file_open(
data_file, furi_string_get_cstr(app->file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
while(!storage_file_eof(data_file)) {
char ch;
furi_string_reset(buffer);
while(storage_file_read(data_file, &ch, 1) && !storage_file_eof(data_file)) {
furi_string_push_back(buffer, ch);
if(ch == '\n') {
break;
}
}
if(furi_string_start_with(buffer, "RAW_Data: ")) {
bit_count = 0;
int length = furi_string_size(buffer);
uint32_t temp;
char ch;
for(int i = 8; i < length - 1; i++) {
if(furi_string_get_char(buffer, i) == 'D') {
i++;
data[bit_count] = furi_string_get_char(buffer, i) == '1';
i += 2; // Skip space
temp = 0;
while(i < length && (ch = furi_string_get_char(buffer, i)) != ' ') {
temp = temp * 10 + (ch - '0');
i++;
}
data_fall[bit_count] = temp;
i++; // Skip space
temp = 0;
while(i < length && (ch = furi_string_get_char(buffer, i)) != ' ' &&
ch != '\n') {
temp = temp * 10 + (ch - '0');
i++;
}
data_rise[bit_count] = temp;
bit_count++;
}
}
break;
}
}
storage_file_close(data_file);
}
storage_file_free(data_file);
furi_record_close(RECORD_STORAGE);
}
if(bit_count == 0) {
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, WiegandMainMenuScene);
} else {
data_saved = true;
scene_manager_next_scene(app->scene_manager, WiegandDataScene);
}
}

View File

@ -0,0 +1,75 @@
#include "../wiegand.h"
/*
Triggers a custom event that is handled in the main menu on_scene handler.
@param context Pointer to the application context.
@param index Index of the selected menu item to map to custom event.
*/
void wiegand_menu_callback(void* context, uint32_t index) {
App* app = context;
WiegandMainMenuEvent event = WiegandMainMenuUnknownEvent;
switch(index) {
case WiegandMainMenuInstructions:
event = WiegandMainMenuInstructionsEvent;
break;
case WiegandMainMenuRead:
event = WiegandMainMenuReadEvent;
break;
case WiegandMainMenuScan:
event = WiegandMainMenuScanEvent;
break;
case WiegandMainMenuLoad:
event = WiegandMainMenuLoadEvent;
break;
}
if(event != WiegandMainMenuUnknownEvent) {
scene_manager_handle_custom_event(app->scene_manager, event);
}
}
/*
Displays the main menu.
@param context Pointer to the application context.
*/
void wiegand_main_menu_scene_on_enter(void* context) {
App* app = context;
submenu_reset(app->submenu);
submenu_set_header(app->submenu, "Wiegand");
submenu_add_item(
app->submenu, "Instructions", WiegandMainMenuInstructions, wiegand_menu_callback, app);
submenu_add_item(app->submenu, "Read", WiegandMainMenuRead, wiegand_menu_callback, app);
submenu_add_item(
app->submenu, "Scan w/AutoSave", WiegandMainMenuScan, wiegand_menu_callback, app);
submenu_add_item(app->submenu, "Load", WiegandMainMenuLoad, wiegand_menu_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandSubmenuView);
}
bool wiegand_main_menu_scene_on_event(void* context, SceneManagerEvent event) {
App* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case WiegandMainMenuInstructionsEvent:
scene_manager_next_scene(app->scene_manager, WiegandInstructionsScene);
consumed = true;
break;
case WiegandMainMenuReadEvent:
scene_manager_next_scene(app->scene_manager, WiegandReadScene);
consumed = true;
break;
case WiegandMainMenuScanEvent:
scene_manager_next_scene(app->scene_manager, WiegandScanScene);
consumed = true;
break;
case WiegandMainMenuLoadEvent:
scene_manager_next_scene(app->scene_manager, WiegandLoadScene);
consumed = true;
break;
default:
consumed = false;
break;
}
}
return consumed;
}

View File

@ -0,0 +1,60 @@
#include "../wiegand.h"
void single_vibro() {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro);
furi_record_close(RECORD_NOTIFICATION);
}
void wiegand_play() {
uint32_t* delays = malloc(sizeof(uint32_t) * bit_count * 2);
for(int i = 0; i < bit_count - 1; i++) {
delays[i * 2] = (data_rise[i] - data_fall[i]) / 64;
delays[i * 2 + 1] = (data_fall[i + 1] - data_rise[i]) / 64;
}
delays[(bit_count - 1) * 2] = (data_rise[bit_count - 1] - data_fall[bit_count - 1]) / 64;
delays[(bit_count - 1) * 2 + 1] = 1;
for(int i = 0; i < bit_count; i++) {
// Delays are always at least 1 tick.
if(delays[i * 2] == 0) delays[i * 2] = 1;
if(delays[i * 2 + 1] == 0) delays[i * 2 + 1] = 1;
if(delays[i * 2 + 1] > 5) delays[i * 2 + 1] -= 1;
}
furi_hal_gpio_write(pinD0, true);
furi_hal_gpio_init(pinD0, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pinD0mosfet, false);
furi_hal_gpio_init(pinD0mosfet, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pinD1, true);
furi_hal_gpio_init(pinD1, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pinD1mosfet, false);
furi_hal_gpio_init(pinD1mosfet, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
single_vibro();
furi_delay_ms(500);
int j = 0;
for(int i = 0; i < bit_count; i++) {
if(data[i]) {
furi_hal_gpio_write(pinD1mosfet, true); // Activate the mosfet to ground wire
furi_hal_gpio_write(pinD1, false); // Ground the open-drain wire
furi_delay_us(delays[j++]);
furi_hal_gpio_write(pinD1, true); // Float the wire
furi_hal_gpio_write(pinD1mosfet, false); // Deactivate the mosfet
furi_delay_us(delays[j++]);
} else {
furi_hal_gpio_write(pinD0mosfet, true); // Activate the mosfet to ground wire
furi_hal_gpio_write(pinD0, false); // Ground the open-drain wire
furi_delay_us(delays[j++]);
furi_hal_gpio_write(pinD0, true); // Float the wire
furi_hal_gpio_write(pinD0mosfet, false); // Deactivate the mosfet
furi_delay_us(delays[j++]);
}
}
furi_hal_gpio_init_simple(pinD0, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD0mosfet, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1mosfet, GpioModeAnalog);
}

View File

@ -0,0 +1,121 @@
#include "../wiegand.h"
void wiegand_isr_d0(void *context)
{
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD0);
data[bit_count] = 0;
if (rise)
{
data_rise[bit_count] = time;
if (bit_count < MAX_BITS)
{
bit_count++;
}
}
else
{
data_fall[bit_count] = time;
}
}
void wiegand_isr_d1(void *context)
{
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD1);
data[bit_count] = 1;
if (rise)
{
data_rise[bit_count] = time;
if (bit_count < MAX_BITS)
{
bit_count++;
}
}
else
{
data_fall[bit_count] = time;
}
}
void wiegand_start_read(void *context)
{
App *app = context;
data_saved = false;
bit_count = 0;
furi_hal_power_enable_otg();
furi_hal_gpio_init_simple(pinD0, GpioModeInterruptRiseFall);
furi_hal_gpio_init_simple(pinD1, GpioModeInterruptRiseFall);
furi_hal_gpio_add_int_callback(pinD0, wiegand_isr_d0, NULL);
furi_hal_gpio_add_int_callback(pinD1, wiegand_isr_d1, NULL);
furi_timer_start(app->timer, 100);
}
void wiegand_stop_read(void *context)
{
App *app = context;
furi_hal_gpio_remove_int_callback(pinD0);
furi_hal_gpio_remove_int_callback(pinD1);
furi_hal_gpio_init_simple(pinD0, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1, GpioModeAnalog);
furi_timer_stop(app->timer);
}
void wiegand_timer_callback(void *context)
{
App *app = context;
uint32_t duration = DWT->CYCCNT;
const uint32_t one_millisecond = 64000;
if (bit_count == 0)
{
return;
}
duration -= data_fall[bit_count - 1];
FURI_CRITICAL_ENTER();
if (duration > 25 * one_millisecond)
{
if (bit_count == 4 || bit_count == 8 || bit_count == 24 || bit_count == 26 ||
bit_count == 32 || bit_count == 34 || bit_count == 35 || bit_count == 36 ||
bit_count == 37 || bit_count == 40 || bit_count == 48)
{
wiegand_stop_read(app);
scene_manager_next_scene(app->scene_manager, WiegandDataScene);
}
else
{
// No data, clear
bit_count = 0;
}
}
FURI_CRITICAL_EXIT();
}
void wiegand_read_scene_on_enter(void *context)
{
App *app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Read Wiegand");
widget_add_string_element(
app->widget, 0, 25, AlignLeft, AlignTop, FontSecondary, "Waiting for signal...");
widget_add_string_element(
app->widget, 0, 45, AlignLeft, AlignTop, FontSecondary, "D0/Green/A4");
widget_add_string_element(
app->widget, 0, 53, AlignLeft, AlignTop, FontSecondary, "D1/White/A7");
wiegand_start_read(app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}
void wiegand_read_scene_on_exit(void *context)
{
App *app = context;
wiegand_stop_read(app);
}

View File

@ -0,0 +1,153 @@
#include "../wiegand.h"
void wiegand_data_scene_save_name_text_input_callback(void *context)
{
App *app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataSceneSaveFileEvent);
}
void ensure_dir_exists(Storage *storage)
{
// If apps_data directory doesn't exist, create it.
if (!storage_dir_exists(storage, WIEGAND_APPS_DATA_FOLDER))
{
FURI_LOG_I(TAG, "Creating directory: %s", WIEGAND_APPS_DATA_FOLDER);
storage_simply_mkdir(storage, WIEGAND_APPS_DATA_FOLDER);
}
else
{
FURI_LOG_I(TAG, "Directory exists: %s", WIEGAND_APPS_DATA_FOLDER);
}
// If wiegand directory doesn't exist, create it.
if (!storage_dir_exists(storage, WIEGAND_SAVE_FOLDER))
{
FURI_LOG_I(TAG, "Creating directory: %s", WIEGAND_SAVE_FOLDER);
storage_simply_mkdir(storage, WIEGAND_SAVE_FOLDER);
}
else
{
FURI_LOG_I(TAG, "Directory exists: %s", WIEGAND_SAVE_FOLDER);
}
}
void wiegand_save(void *context)
{
App *app = context;
FuriString *buffer = furi_string_alloc(1024);
FuriString *file_path = furi_string_alloc();
furi_string_printf(
file_path, "%s/%s%s", WIEGAND_SAVE_FOLDER, app->file_name, WIEGAND_SAVE_EXTENSION);
Storage *storage = furi_record_open(RECORD_STORAGE);
ensure_dir_exists(storage);
File *data_file = storage_file_alloc(storage);
if (storage_file_open(
data_file, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_OPEN_ALWAYS))
{
furi_string_printf(buffer, "Filetype: Flipper Wiegand Key File\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Version: 1\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Protocol: RAW\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Bits: %d\n", bit_count);
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "RAW_Data: ");
for (int i = 0; i < bit_count; i++)
{
furi_string_cat_printf(
buffer,
"D%d %ld %ld ",
data[i] ? 1 : 0,
data_fall[i] - data_fall[0],
data_rise[i] - data_fall[0]);
}
furi_string_push_back(buffer, '\n');
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "PACS_Binary: ");
for (int i = 0; i < bit_count; i++)
{
furi_string_cat_printf(buffer, "%d", data[i] ? 1 : 0);
}
furi_string_push_back(buffer, '\n');
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "PM3_Command: hf ic encode --bin ");
for (int i = 0; i < bit_count; i++)
{
furi_string_cat_printf(buffer, "%d", data[i] ? 1 : 0);
}
furi_string_cat_printf(buffer, " --ki 0\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
storage_file_close(data_file);
}
storage_file_free(data_file);
furi_record_close(RECORD_STORAGE);
furi_string_free(file_path);
furi_string_free(buffer);
}
void wiegand_save_scene_on_enter(void *context)
{
App *app = context;
text_input_reset(app->text_input);
FuriHalRtcDateTime datetime;
furi_hal_rtc_get_datetime(&datetime);
snprintf(
app->file_name,
50,
"%02d_%02d_%02d_%02d_%02d_%02d",
datetime.year,
datetime.month,
datetime.day,
datetime.hour,
datetime.minute,
datetime.second);
// set_random_name(app->file_name, WIEGAND_KEY_NAME_SIZE);
text_input_set_header_text(app->text_input, "Name the key");
text_input_set_result_callback(
app->text_input,
wiegand_data_scene_save_name_text_input_callback,
app,
app->file_name,
WIEGAND_KEY_NAME_SIZE,
true);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandTextInputView);
}
bool wiegand_save_scene_on_event(void *context, SceneManagerEvent event)
{
App *app = context;
bool consumed = false;
switch (event.type)
{
case SceneManagerEventTypeCustom:
switch (event.event)
{
case WiegandDataSceneSaveFileEvent:
wiegand_save(app);
data_saved = true;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, WiegandDataScene);
consumed = true;
break;
default:
consumed = false;
break;
}
break;
default:
break;
}
return consumed;
}

View File

@ -0,0 +1,161 @@
#include "../wiegand.h"
FuriTimer *timer = NULL;
static void wiegand_scan_isr_d0(void *context)
{
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD0);
data[bit_count] = 0;
if (rise)
{
data_rise[bit_count] = time;
if (bit_count < MAX_BITS)
{
bit_count++;
}
}
else
{
data_fall[bit_count] = time;
}
}
static void wiegand_scan_isr_d1(void *context)
{
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD1);
data[bit_count] = 1;
if (rise)
{
data_rise[bit_count] = time;
if (bit_count < MAX_BITS)
{
bit_count++;
}
}
else
{
data_fall[bit_count] = time;
}
}
static void wiegand_start_scan(void *context)
{
UNUSED(context);
data_saved = false;
bit_count = 0;
furi_hal_power_enable_otg();
furi_hal_gpio_init_simple(pinD0, GpioModeInterruptRiseFall);
furi_hal_gpio_init_simple(pinD1, GpioModeInterruptRiseFall);
furi_hal_gpio_add_int_callback(pinD0, wiegand_scan_isr_d0, NULL);
furi_hal_gpio_add_int_callback(pinD1, wiegand_scan_isr_d1, NULL);
furi_timer_start(timer, 100);
}
static void wiegand_stop_scan(void *context)
{
UNUSED(context);
furi_hal_gpio_remove_int_callback(pinD0);
furi_hal_gpio_remove_int_callback(pinD1);
furi_hal_gpio_init_simple(pinD0, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1, GpioModeAnalog);
furi_timer_stop(timer);
}
static void wiegand_scan_found(void *context)
{
App *app = context;
FuriHalRtcDateTime datetime;
furi_hal_rtc_get_datetime(&datetime);
snprintf(
app->file_name,
50,
"%02d_%02d_%02d_%02d_%02d_%02d",
datetime.year,
datetime.month,
datetime.day,
datetime.hour,
datetime.minute,
datetime.second);
furi_hal_vibro_on(true);
furi_delay_ms(50);
furi_hal_vibro_on(false);
furi_delay_ms(50);
wiegand_save(app);
furi_hal_vibro_on(true);
furi_delay_ms(50);
furi_hal_vibro_on(false);
furi_delay_ms(1000);
wiegand_start_scan(app);
}
static void wiegand_scan_timer_callback(void *context)
{
App *app = context;
uint32_t duration = DWT->CYCCNT;
const uint32_t one_millisecond = 64000;
if (bit_count == 0)
{
return;
}
duration -= data_fall[bit_count - 1];
bool found = false;
FURI_CRITICAL_ENTER();
if (duration > 25 * one_millisecond)
{
if (bit_count == 4 || bit_count == 8 || bit_count == 24 || bit_count == 26 ||
bit_count == 32 || bit_count == 34 || bit_count == 35 || bit_count == 36 ||
bit_count == 37 || bit_count == 40 || bit_count == 48)
{
wiegand_stop_scan(app);
found = true;
}
else
{
// No data, clear
bit_count = 0;
}
}
FURI_CRITICAL_EXIT();
if (found)
{
wiegand_scan_found(app);
}
}
void wiegand_scan_scene_on_enter(void *context)
{
App *app = context;
timer = furi_timer_alloc(wiegand_scan_timer_callback, FuriTimerTypePeriodic, app);
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Scan Wiegand");
widget_add_string_element(
app->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, "Files saved automatically.");
widget_add_string_element(
app->widget, 0, 30, AlignLeft, AlignTop, FontSecondary, "Vibrates on detection.");
widget_add_string_element(
app->widget, 0, 45, AlignLeft, AlignTop, FontSecondary, "D0/Green/A4");
widget_add_string_element(
app->widget, 0, 53, AlignLeft, AlignTop, FontSecondary, "D1/White/A7");
wiegand_start_scan(app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}
void wiegand_scan_scene_on_exit(void *context)
{
App *app = context;
wiegand_stop_scan(app);
furi_timer_free(timer);
}

Some files were not shown because too many files have changed in this diff Show More