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