Control 7-segment display with Flipper Zero
This commit is contained in:
parent
668fd49fe3
commit
53e18600aa
80
gpio/gpio_7segment/README.md
Normal file
80
gpio/gpio_7segment/README.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# GPIO 7-Segment Output
|
||||||
|
## Introduction
|
||||||
|
This is a GPIO push-pull output demo application for driving a 7-segment display on the Flipper Zero. The goal of this project is to show application developers how GPIO works for push-pull output. This project was derived from the [\plugins\basic](..\..\plugins\basic\README.md) tutorial.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation Directions
|
||||||
|
This project is intended to be overlayed on top of an existing firmware repo.
|
||||||
|
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
|
||||||
|
- Copy the "gpio_7segment" [folder](..) to the \applications\plugins\gpio_7segment folder in your firmware.
|
||||||
|
- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
|
||||||
|
- NOTE: You can also extract the gpio_output_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
|
||||||
|
|
||||||
|
|
||||||
|
## Running the updated firmware
|
||||||
|
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
|
||||||
|
|
||||||
|
- Press the OK button on the flipper to pull up the main menu.
|
||||||
|
- Choose "Applications" from the menu.
|
||||||
|
- Choose "Gpio" from the sub-menu.
|
||||||
|
- Choose "GPIO 7-Segment Output"
|
||||||
|
|
||||||
|
- The flipper should say "GPIO 7-Segment".
|
||||||
|
- Click the "OK" button to display a random number from 1-6.
|
||||||
|
|
||||||
|
- Press the BACK button to exit.
|
||||||
|
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
- application.fam
|
||||||
|
- specifies the settings for our application.
|
||||||
|
- adds "gpio" to requires.
|
||||||
|
|
||||||
|
- gpio_7segment.png
|
||||||
|
- The icon for our application that shows up in the "Misc" folder.
|
||||||
|
|
||||||
|
- gpio_7segment_app.c
|
||||||
|
- Similar to the plugin\basic tutorial.
|
||||||
|
- bool digits[70] is defined to store the segments to glow for different digits.
|
||||||
|
- each group of 7 booleans represents a number.
|
||||||
|
- note: in production code you could replace with a single byte [8-bits] to represent the segments to turn on/off. Like bit 0 is middle LED, bit 1 is bottom right, etc.
|
||||||
|
- gpio_7segment_render_callback(...) callback paints the canvas.
|
||||||
|
- Title of application
|
||||||
|
- two boxes (looks like a 7-segment display)
|
||||||
|
- index is set to the first index with our LED information
|
||||||
|
- for example to draw a 2 we index at 7*2 = 14 (which is the 15th bool, since it starts at a count of 0.)
|
||||||
|
- digits[index++] ? "A7" : "a7"
|
||||||
|
- digits[index] is the bool (true/false) from our digits table.
|
||||||
|
- if it is true we will use the text "A7" (CAPS to show on)
|
||||||
|
- if it is false we will use the text "a7" (lowercase)
|
||||||
|
- index++ will increment index to the next number.
|
||||||
|
- data->invert ? "GND" : "3.3v"
|
||||||
|
- if invert is set to true we show the text "GND" (pins will GND to glow.)
|
||||||
|
- if invert is set to false we show the text "3.3v" (pins will 3.3 to glow.)
|
||||||
|
- We show the number we are displaying on the 7-segment display.
|
||||||
|
- gpio_7segment_show(...) displays a number.
|
||||||
|
- index is set to the first index with our LED information
|
||||||
|
- for example to draw a 2 we index at 7*2 = 14 (which is the 15th bool, since it starts at a count of 0.)
|
||||||
|
- furi_hal_gpio_write(&gpio_ext_pa7, digits[index++] ^ invert);
|
||||||
|
- THIS IS THE CODE THAT CHANGES THE STATE OF THE PIN!
|
||||||
|
- gpio_ext_pa7 is the pin (a7). You can see all pin names at \firmware\targets\f7\furi_hal\furi_hal_resources.c
|
||||||
|
- digits[index++] ^ invert
|
||||||
|
- digits[index] is the bool (true/false) from our digits table.
|
||||||
|
- if invert is false, (digits[index++] ^ false) == digits[index++] (so same as table.)
|
||||||
|
- if invert it true, (digits[index++] ^ true) == !digits[index++] (so inverted from table value. This is needed for common anode 7-segment LEDs.)
|
||||||
|
- gpio_7segment_disconnect_pin(...) disconnects a pin.
|
||||||
|
- GpioModeOutputOpenDrain means that when the value is false the pin will be GND, but when the value is true the pin will be disconnected.
|
||||||
|
- GpioPullNo means there is no pull-up resistor between 3.3v and pin & there is no pull-down resistor between GND and pin.
|
||||||
|
- we write a true, so pin is disconnected.
|
||||||
|
- Some people use Input with Push/Pull-No to disconnect pins while others use OutputOpenDrain. I'm not sure if the pins are really disconnected just because you aren't reading, so I think I would prefer to use OutputOpenDrain.
|
||||||
|
- gpio_7segment_app(...) is our entry point
|
||||||
|
- furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
|
||||||
|
- this sets the A7 pin to be OutputPushPull.
|
||||||
|
- PushPull means pin will either be GND or 3.3volts.
|
||||||
|
- (As opposed to OutputOpenDrain when pin is GND or disconnected.)
|
||||||
|
- We use "init_simple" instead of "init", because other params aren't needed.
|
||||||
|
- context->data->digit = (furi_hal_random_get() % 6) + 1;
|
||||||
|
- when OK button is pressed we use furi_hal_random_get() to get a random number (I think from the secure random number generator).
|
||||||
|
- (x % 6) will return the remainder from dividing x by 6. Which should be a number between 0 and 5. We then add 1 to the value so we get a value between 1 and 6.
|
||||||
|
- gpio_7segment_disconnect_pin(&gpio_ext_pa7);
|
||||||
|
- when our app exits, we disconnect the pins. If we didn't the number would still show.
|
10
gpio/gpio_7segment/application.fam
Normal file
10
gpio/gpio_7segment/application.fam
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
App(
|
||||||
|
appid="GPIO_7Segment_Output",
|
||||||
|
name="GPIO 7-Segment Output",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="gpio_7segment_app",
|
||||||
|
requires=["gui", "gpio"],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
fap_icon="gpio_7segment.png",
|
||||||
|
fap_category="Gpio",
|
||||||
|
)
|
BIN
gpio/gpio_7segment/gpio_7segment.png
Normal file
BIN
gpio/gpio_7segment/gpio_7segment.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
277
gpio/gpio_7segment/gpio_7segment_app.c
Normal file
277
gpio/gpio_7segment/gpio_7segment_app.c
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
@CodeAllNight
|
||||||
|
https://github.com/jamisonderek/flipper-zero-tutorials
|
||||||
|
|
||||||
|
This app controlls a 7-segment display connected to the Flipper Zero GPIO pins.
|
||||||
|
|
||||||
|
WARNING: You should connect 220 ohm resistors to each of your output pins! The
|
||||||
|
Flipper Zero only has a 51ohm internal resistor and has a MAX rating of 20mA per
|
||||||
|
pin. Without a resistor you could potentially be at 65mA per pin (even with a
|
||||||
|
1.5v drop across the LED you would still be at 35mA!)
|
||||||
|
|
||||||
|
When you run the app, it will display which pins should control which segments.
|
||||||
|
|
||||||
|
Be sure to connect your common cathode pin to GND (or common anode pin to 3.3volts.)
|
||||||
|
WARNING: You should connect 220 ohm resistors to each of your output pins.
|
||||||
|
|
||||||
|
All pins will initially have 3.3volts (so you can test that all segments are
|
||||||
|
connected properly). Pressing the UP arrow inverts the output (so instead of
|
||||||
|
3.3volts for making LED glow, it will send GND signal) this is needed if you are
|
||||||
|
using common anode instead of common cathode 7-segment LEDs.
|
||||||
|
|
||||||
|
Press the OK button to have the Flipper randomly guess a number between 1-6
|
||||||
|
(I used this app to replace a 6-sided die.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <furi_hal_gpio.h>
|
||||||
|
#include <furi_hal_resources.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <locale/locale.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Our 7-segment display is connected in the following order...
|
||||||
|
segment 0 = PA7, segment 1 = PA6, segment 2 = PA4, segment 3 = PB3
|
||||||
|
segment 4 = PB2, segment 5 = PC3, segment 6 = PC1
|
||||||
|
|
||||||
|
Common cathode connection goes to GND pin.
|
||||||
|
We output a 3.3V to turn LED on and a GND to turn LED off.
|
||||||
|
|
||||||
|
seg:
|
||||||
|
55555
|
||||||
|
4 6
|
||||||
|
4 6
|
||||||
|
00000
|
||||||
|
3 1
|
||||||
|
3 1
|
||||||
|
22222
|
||||||
|
|
||||||
|
//seg:0 , 1 , 2 , 3 , 4 , 5 , 6
|
||||||
|
0 = false, true, true, true, true, true, true
|
||||||
|
1 = false, true, false, false, false, false, true
|
||||||
|
2 = true, false, true, true, false, true, true
|
||||||
|
3 = true, true, true, false, false, true, true
|
||||||
|
4 = true, true, false, false, true, false, true
|
||||||
|
5 = true, true, true, false, true, true, false
|
||||||
|
6 = true, true, true, true, true, true, false
|
||||||
|
7 = false, true, false, false, false, true, true
|
||||||
|
8 = true, true, true, true, true, true, true
|
||||||
|
9 = true, true, true, false, true, true, true
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TAG "gpio_7segment_app"
|
||||||
|
|
||||||
|
bool digits[70] = {
|
||||||
|
/* 0 */ false, true, true, true, true, true, true,
|
||||||
|
/* 1 */ false, true, false, false, false, false, true,
|
||||||
|
/* 2 */ true, false, true, true, false, true, true,
|
||||||
|
/* 3 */ true, true, true, false, false, true, true,
|
||||||
|
/* 4 */ true, true, false, false, true, false, true,
|
||||||
|
/* 5 */ true, true, true, false, true, true, false,
|
||||||
|
/* 6 */ true, true, true, true, true, true, false,
|
||||||
|
/* 7 */ false, true, false, false, false, true, true,
|
||||||
|
/* 8 */ true, true, true, true, true, true, true,
|
||||||
|
/* 9 */ true, true, true, false, true, true, true,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GpioEventTypeKey,
|
||||||
|
} GpioEventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GpioEventType type; // The reason for this event.
|
||||||
|
InputEvent input; // This data is specific to keypress data.
|
||||||
|
} GpioEvent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriString* buffer;
|
||||||
|
int digit;
|
||||||
|
bool invert;
|
||||||
|
} GpioData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriMessageQueue* queue; // Message queue (GpioEvent items to process).
|
||||||
|
FuriMutex* mutex; // Used to provide thread safe access to data.
|
||||||
|
GpioData* data; // Data accessed by multiple threads (acquire the mutex before accessing!)
|
||||||
|
} GpioContext;
|
||||||
|
|
||||||
|
// Invoked when input (button press) is detected. Queues message and returns to caller.
|
||||||
|
// @param input_event the input event that caused the callback to be invoked.
|
||||||
|
// @param queue the message queue for queueing key event.
|
||||||
|
static void gpio_7segment_input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
|
||||||
|
furi_assert(queue);
|
||||||
|
GpioEvent event = {.type = GpioEventTypeKey, .input = *input_event};
|
||||||
|
furi_message_queue_put(queue, &event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked by the draw callback to render the screen.
|
||||||
|
// @param canvas surface to draw on.
|
||||||
|
// @param ctx a pointer to the application GpioContext.
|
||||||
|
static void gpio_7segment_render_callback(Canvas* canvas, void* ctx) {
|
||||||
|
// Attempt to aquire context, so we can read the data.
|
||||||
|
GpioContext* context = ctx;
|
||||||
|
if(furi_mutex_acquire(context->mutex, 200) != FuriStatusOk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioData* data = context->data;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str_aligned(canvas, 5, 0, AlignLeft, AlignTop, "GPIO 7-Segment LED");
|
||||||
|
|
||||||
|
// Draw two boxes (7-segment LEDs)
|
||||||
|
canvas_draw_frame(canvas, 45, 22, 20, 16);
|
||||||
|
canvas_draw_frame(canvas, 45, 37, 20, 16);
|
||||||
|
|
||||||
|
// Draw the labels (use CAPS if LED should be glowing.)
|
||||||
|
int index = 7 * data->digit;
|
||||||
|
canvas_draw_str_aligned(canvas, 50, 39, AlignLeft, AlignTop, digits[index++] ? "A7" : "a7");
|
||||||
|
canvas_draw_str_aligned(canvas, 66, 39, AlignLeft, AlignTop, digits[index++] ? "A6" : "a6");
|
||||||
|
canvas_draw_str_aligned(canvas, 50, 54, AlignLeft, AlignTop, digits[index++] ? "A4" : "a4");
|
||||||
|
canvas_draw_str_aligned(canvas, 32, 39, AlignLeft, AlignTop, digits[index++] ? "B3" : "b3");
|
||||||
|
canvas_draw_str_aligned(canvas, 32, 26, AlignLeft, AlignTop, digits[index++] ? "B2" : "b2");
|
||||||
|
canvas_draw_str_aligned(canvas, 50, 13, AlignLeft, AlignTop, digits[index++] ? "C3" : "c3");
|
||||||
|
canvas_draw_str_aligned(canvas, 66, 26, AlignLeft, AlignTop, digits[index++] ? "C1" : "c1");
|
||||||
|
|
||||||
|
// Tell user if GPIO pins are GND or 3.3v to glow.
|
||||||
|
canvas_draw_str_aligned(canvas, 90, 40, AlignLeft, AlignTop, data->invert?"GND":"3.3v");
|
||||||
|
|
||||||
|
// Display the current number.
|
||||||
|
furi_string_printf(data->buffer, "Digit: %d", data->digit);
|
||||||
|
canvas_draw_str_aligned(canvas, 90, 50, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
|
||||||
|
|
||||||
|
// Release the context, so other threads can update the data.
|
||||||
|
furi_mutex_release(context->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the GPIO pin output to display a number.
|
||||||
|
// @param digit a value between 0-9 to display.
|
||||||
|
// @param invert use true if your 7-segment LED display is common anode
|
||||||
|
// (and all the output pins go to cathode side of LEDs). use false if
|
||||||
|
// your display is common cathode.
|
||||||
|
void gpio_7segment_show(int digit, bool invert) {
|
||||||
|
// There are 7 segments per digit.
|
||||||
|
int index = 7 * digit;
|
||||||
|
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pa7, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pa6, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pa4, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pb3, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pb2, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pc3, digits[index++] ^ invert);
|
||||||
|
furi_hal_gpio_write(&gpio_ext_pc1, digits[index++] ^ invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnects a GpioPin via OutputOpenDrive, PushPullNo, output true.
|
||||||
|
// @pin pointer to GpioPin to disconnect.
|
||||||
|
void gpio_7segment_disconnect_pin(const GpioPin* pin) {
|
||||||
|
furi_hal_gpio_init(pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
|
||||||
|
furi_hal_gpio_write(pin, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the entry point to our application.
|
||||||
|
int32_t gpio_7segment_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
// Configure our initial data.
|
||||||
|
GpioContext* context = malloc(sizeof(GpioContext));
|
||||||
|
context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
|
context->data = malloc(sizeof(GpioData));
|
||||||
|
context->data->buffer = furi_string_alloc();
|
||||||
|
context->data->digit = 8;
|
||||||
|
context->data->invert = false;
|
||||||
|
|
||||||
|
// Queue for events (input)
|
||||||
|
context->queue = furi_message_queue_alloc(8, sizeof(GpioEvent));
|
||||||
|
|
||||||
|
// Set ViewPort callbacks
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, gpio_7segment_render_callback, context);
|
||||||
|
view_port_input_callback_set(view_port, gpio_7segment_input_callback, context->queue);
|
||||||
|
|
||||||
|
// Set our 7 GPIO external pins to output (push-pull).
|
||||||
|
// NOTE: For common anode LED, we *could* use GpioModeOutputOpenDrain.
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pa6, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pb3, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pb2, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
|
||||||
|
|
||||||
|
// Display the number (this should light all 7 of the LED segments)
|
||||||
|
gpio_7segment_show(context->data->digit, context->data->invert);
|
||||||
|
|
||||||
|
// Open GUI and register view_port
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
GpioEvent event;
|
||||||
|
bool processing = true;
|
||||||
|
do {
|
||||||
|
if (furi_message_queue_get(context->queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||||
|
FURI_LOG_T(TAG, "Got event type: %d", event.type);
|
||||||
|
switch (event.type) {
|
||||||
|
case GpioEventTypeKey:
|
||||||
|
// Short press of back button exits the program.
|
||||||
|
if (event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
|
||||||
|
FURI_LOG_I(TAG, "Short-Back pressed. Exiting program.");
|
||||||
|
processing = false;
|
||||||
|
} else if (event.input.type == InputTypeShort && event.input.key == InputKeyOk) {
|
||||||
|
FURI_LOG_I(TAG, "OK pressed.");
|
||||||
|
if (furi_mutex_acquire(context->mutex, FuriWaitForever) == FuriStatusOk) {
|
||||||
|
// Pick a random number between 1 and 6...
|
||||||
|
context->data->digit = (furi_hal_random_get() % 6) + 1;
|
||||||
|
|
||||||
|
gpio_7segment_show(context->data->digit, context->data->invert);
|
||||||
|
furi_mutex_release(context->mutex);
|
||||||
|
}
|
||||||
|
} else if (event.input.type == InputTypeShort && event.input.key == InputKeyUp) {
|
||||||
|
FURI_LOG_I(TAG, "UP pressed.");
|
||||||
|
if (furi_mutex_acquire(context->mutex, FuriWaitForever) == FuriStatusOk) {
|
||||||
|
// Invert our output (switch between common anode/cathode 7-segment LEDs)
|
||||||
|
context->data->invert = !context->data->invert;
|
||||||
|
gpio_7segment_show(context->data->digit, context->data->invert);
|
||||||
|
furi_mutex_release(context->mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send signal to update the screen (callback will get invoked at some point later.)
|
||||||
|
view_port_update(view_port);
|
||||||
|
} else {
|
||||||
|
// We had an issue getting message from the queue, so exit application.
|
||||||
|
processing = false;
|
||||||
|
}
|
||||||
|
} while (processing);
|
||||||
|
|
||||||
|
// Disconnect the GPIO pins.
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pa7);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pa6);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pa4);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pb3);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pb2);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pc3);
|
||||||
|
gpio_7segment_disconnect_pin(&gpio_ext_pc1);
|
||||||
|
|
||||||
|
// Free resources
|
||||||
|
view_port_enabled_set(view_port, false);
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
furi_message_queue_free(context->queue);
|
||||||
|
furi_mutex_free(context->mutex);
|
||||||
|
furi_string_free(context->data->buffer);
|
||||||
|
free(context->data);
|
||||||
|
free(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user