Compare commits
No commits in common. "main" and "add-subghz-rx-tx-worker" have entirely different histories.
main
...
add-subghz
2
.gitignore
vendored
@ -50,5 +50,3 @@ modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
.vscode/
|
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "engine"]
|
||||
path = vgm/apps/air_labyrinth/engine
|
||||
url = https://github.com/flipperdevices/flipperzero-game-engine
|
63
README.md
@ -1,30 +1,7 @@
|
||||
# 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 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.
|
||||
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.
|
||||
|
||||
|
||||
## Firmware
|
||||
@ -33,52 +10,16 @@ NOTE: This game uses the game `engine`, so you must recursively clone the `flipp
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### 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
|
||||
### 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.
|
||||
|
@ -1,33 +0,0 @@
|
||||
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
|
@ -1,10 +0,0 @@
|
||||
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
|
@ -1,46 +0,0 @@
|
||||
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
|
@ -1,14 +0,0 @@
|
||||
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
|
@ -1,16 +0,0 @@
|
||||
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
|
@ -1,45 +0,0 @@
|
||||
# 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
|
@ -1,80 +0,0 @@
|
||||
# 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.
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,277 +0,0 @@
|
||||
/*
|
||||
@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;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
#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.
|
Before Width: | Height: | Size: 204 KiB |
Before Width: | Height: | Size: 1.7 KiB |
@ -1,20 +0,0 @@
|
||||
#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.
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
@ -1,206 +0,0 @@
|
||||
#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;
|
||||
}
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,57 +0,0 @@
|
||||
# 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".
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
@ -1,47 +0,0 @@
|
||||
#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;
|
||||
}
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,31 +0,0 @@
|
||||
# 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);
|
||||
```
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
@ -1,43 +0,0 @@
|
||||
#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;
|
||||
}
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,70 +0,0 @@
|
||||
# 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 & 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.
|
||||
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,260 +0,0 @@
|
||||
/*
|
||||
@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;
|
||||
}
|
@ -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",
|
||||
|
@ -1,26 +0,0 @@
|
||||
# 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.
|
@ -1,14 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 141 B |
@ -1,280 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
# 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 & 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.
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,329 +0,0 @@
|
||||
/*
|
||||
@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;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
# 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
|
@ -1,54 +0,0 @@
|
||||
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 */
|
@ -1,147 +0,0 @@
|
||||
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 */
|
@ -1,86 +0,0 @@
|
||||
# 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 & 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.
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,226 +0,0 @@
|
||||
/*
|
||||
@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;
|
||||
}
|
Before Width: | Height: | Size: 343 KiB |
@ -1,10 +0,0 @@
|
||||
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)
|
@ -1,11 +0,0 @@
|
||||
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)
|
@ -1,10 +0,0 @@
|
||||
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)
|
@ -1,12 +0,0 @@
|
||||
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)
|
@ -1,13 +0,0 @@
|
||||
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)
|
@ -1,10 +0,0 @@
|
||||
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)
|
@ -1,8 +0,0 @@
|
||||
This is just a general purpose GPIO pin.
|
||||
|
||||
Generic GPIO uses PC3.
|
||||
|
||||
CHECK: "LPTIM1.ETR LPTIM2.ETR/ADC1.4"
|
||||
|
||||
Devices:
|
||||
- None
|
@ -1,4 +0,0 @@
|
||||
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
|
||||
|
||||
Devices:
|
||||
- All
|
@ -1,19 +0,0 @@
|
||||
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)
|
||||
|
@ -1,6 +0,0 @@
|
||||
This pin is used for general IO, timer & SWD only.
|
||||
|
||||
CHECK: "LPTIM1.OUT/SWCLK"
|
||||
|
||||
Devices:
|
||||
- None
|
@ -1,4 +0,0 @@
|
||||
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
|
||||
|
||||
Devices:
|
||||
- All
|
@ -1,6 +0,0 @@
|
||||
This pin is used for back key (PA13) and SWD only.
|
||||
|
||||
CHECK: "PA13/SWIO"
|
||||
|
||||
Devices:
|
||||
- None
|
@ -1,11 +0,0 @@
|
||||
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)
|
@ -1,9 +0,0 @@
|
||||
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)
|
@ -1,11 +0,0 @@
|
||||
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)
|
@ -1,13 +0,0 @@
|
||||
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)
|
@ -1,9 +0,0 @@
|
||||
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)
|
@ -1,27 +0,0 @@
|
||||
# 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.)
|
@ -1,4 +0,0 @@
|
||||
This is one of three ground pins on the Flipper Zero. You can use pins 8 (L8), 11 (R3), 18 (R10) interchangeably.
|
||||
|
||||
Devices:
|
||||
- All
|
@ -1,13 +0,0 @@
|
||||
# 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)
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,47 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
# 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)
|
@ -1,12 +0,0 @@
|
||||
#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;
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB |
@ -1,14 +0,0 @@
|
||||
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.",
|
||||
)
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
# 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);
|
||||
```
|
||||
|
@ -1,10 +0,0 @@
|
||||
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",
|
||||
)
|
@ -1,166 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* 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);
|
@ -1,142 +0,0 @@
|
||||
#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;
|
||||
}
|
Before Width: | Height: | Size: 7.1 KiB |
@ -1,272 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/**
|
||||
* 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);
|
@ -1,123 +0,0 @@
|
||||
# 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 _<your working directory>_ 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 _<your working directory>_ 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 _<your working directory>_ 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
|
||||
```
|
@ -1,11 +0,0 @@
|
||||
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",
|
||||
)
|
@ -1,5 +0,0 @@
|
||||
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
|
@ -1,347 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#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);
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
#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);
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
#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);
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
#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);
|
||||
}
|