Control 7-segment display with Flipper Zero

This commit is contained in:
Derek Jamison 2023-02-21 11:45:07 -05:00
parent 668fd49fe3
commit 53e18600aa
4 changed files with 367 additions and 0 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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