Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pio example for 7_segment display #375

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ App|Description
App|Description
---|---
[hello_pio](pio/hello_pio) | Absolutely minimal example showing how to control an LED by pushing values into a PIO FIFO.
[7_segment](pio/7_segment/) | Use PIO to control a four digit multiplexed 7-segment LED display.
[apa102](pio/apa102) | Rainbow pattern on on a string of APA102 addressable RGB LEDs.
[clocked_input](pio/clocked_input) | Shift in serial data, sampling with an external clock.
[differential_manchester](pio/differential_manchester) | Send and receive differential Manchester-encoded serial (BMC).
Expand Down
44 changes: 44 additions & 0 deletions pio/7_segment/7_segment.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2023 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
**/

#include <stdio.h>
#include "pico/stdlib.h"
#include "7_segment_lib.h"


const PIO pio = pio0;
const uint first_segment_pin = 8; // gpio 15-8 = segments E,D,B,G,A,C,F,dp
const uint first_digit_pin = 16; // gpio 19-16 = common anodes 4,3,2,1

// EDBGACF. bit ordering depends on your display and wiring
const uint32_t Pico = 0b10111010 << 24 | // 'P'
0b10000000 << 16 | // 'i'
0b11010000 << 8 | // 'c'
0b11010100; // 'o'

int main() {
uint sm;
stdio_init_all();

if (seven_segment_init (pio, &sm, first_segment_pin, first_digit_pin)) {
puts ("running");

// display scrolling 'Pico'
for (int shift = 24; shift >= 0; shift -= 8) {
pio_sm_put (pio, sm, Pico >> shift);
sleep_ms (1000);
}

// count to 9999
for (int i = 0; i < 10000; i += 1) {
sleep_ms (100);
pio_sm_put (pio, sm, int_to_seven_segment (i));
}
}

puts ("done");
while (true);
}
Binary file added pio/7_segment/7_segment_schematic.fzz
Binary file not shown.
Binary file added pio/7_segment/7_segment_schematic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added pio/7_segment/7_segment_wiring.fzz
Binary file not shown.
Binary file added pio/7_segment/7_segment_wiring.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions pio/7_segment/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
add_executable(pio_7_segment)

target_sources(pio_7_segment PRIVATE 7_segment.c)

add_subdirectory(pio_7_segment_library)

target_link_libraries(pio_7_segment PRIVATE
pico_stdlib
hardware_pio
pio_7_segment_library)

pico_add_extra_outputs(pio_7_segment)

example_auto_set_url(pio_7_segment)
66 changes: 66 additions & 0 deletions pio/7_segment/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
= Driving a 7 segment LED display with the PIO

This example demonstrates how the RP2040 PIO can be used to control a four digit multiplexed 7-segment display.

Multiplexed displays work by rapidly displaying each digit in turn. The PIO is a good way to relieve the CPU of this repetitive task.

The code uses four `side-set` pins to control the digit multiplex lines and displays the segment patterns received on the FIFO. A non-blocking PULL is used to keep showing the same segments until the CPU sends new data.

The provided example spells out the word **Pico** then counts from 0 to 9999.

== Wiring information

Miniature _('bubble')_ displays can sometimes be driven directly from the GPIO pins but full sized ones usually need an external drive circuit like the example shown.


Connect the circuit to an external 5V supply or power it via USB (in which case you may prefer to take the _+5V_ rail from _VBUS_).

TIP: make any changes required for your display and transistors: this is for a _common anode_ display that takes about 15mA per segment.

[[pio_7_segment_wiring]]
[pdfwidth=75%]
.Wiring diagram
image::7_segment_wiring.png[]

[[pio_7_segment_schematic]]
[pdfwidth=50%]
.Schematic (common anode driver)
image::7_segment_schematic.png[]


[[pio_7_segment_connections-table]]
.Connections table
[options="header,footer"]
|==================================================
|RP2040 |Pico pin |Display driver
|GP8 |11 |segment dp
|GP9 |12 |segment F
|GP10 |14 |segment C
|GP11 |15 |segment A
|GP12 |16 |segment G
|GP13 |17 |segment B
|GP14 |19 |segment D
|GP15 |20 |segment E
|GP16 |21 |digit 1 (thousands)
|GP17 |22 |digit 2 (hundreds)
|GP18 |24 |digit 3 (tens)
|GP19 |25 |digit 4 (units)
|==================================================

== Bill of materials

.A list of materials for the example circuit
[[pio_onewire_bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | *Details*
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| 4 digit 7 segment display (common anode) | 1 | e.g. https://shop.pimoroni.com/products/4-digit-7-segment-display
| NPN bipolar transistor | 12 | e.g. https://thepihut.com/products/npn-bipolar-transistors-pn2222-10-pack (10-pack)
| PNP bipolar transistor | 4 | e.g. https://thepihut.com/products/bipolar-transistor-kit-5-x-pn2222-npn-and-5-x-pn2907-pnp (5-PNP + 5-NPN)
| 180 ohm resistor | 8 | generic part
| 220 ohm resistor | 4 | generic part
| M/M jumper wire | 22 | generic part
| M/F jumper wire | 12 | generic part
|===
84 changes: 84 additions & 0 deletions pio/7_segment/pio_7_segment_library/7_segment_lib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2023 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
**/

#include <stdio.h>
#include "hardware/gpio.h"

#include "7_segment_lib.h"


// define which segments to illuminate for the digits 0-9
//
static const uint8_t segments[] = {
// EDBGACF. the bit ordering depends on the display used
0b11101110, // 0
0b00100100, // 1
0b11111000, // 2
0b01111100, // 3
0b00110110, // 4
0b01011110, // 5
0b11011110, // 6
0b00101100, // 7
0b11111110, // 8
0b01111110 // 9
};


// convert an integer into a 32-bit word representing up to four 7-segment digits
//
uint32_t int_to_seven_segment (int num) {
uint32_t word = 0;
if (num == 0) {
word = segments[0];
} else {
for (int bitshift = 0; bitshift < 32 && num > 0; bitshift += 8) {
word |= segments[num % 10] << bitshift;
num /= 10;
}
}
return word;
}


// configure and initialise a PIO state machine
//
bool seven_segment_init (PIO pio, uint *p_sm, uint segment_pinbase, uint digit_pinbase) {
// add the program to the PIO shared instruction memory
if (pio_can_add_program (pio, &seven_segment_program) == false) {
puts ("could not add the pio program");
return false;
}
uint offset = pio_add_program (pio, &seven_segment_program);

// claim a free state machine
int sm = pio_claim_unused_sm (pio, false);
if (sm == -1) {
puts ("could not claim a state machine");
return false;
} else {
*p_sm = (uint)sm;
}

// set segment pins to PIO output
for (int pin = 0; pin < 8; pin += 1) {
pio_gpio_init (pio, segment_pinbase + pin);
}
pio_sm_set_consecutive_pindirs (pio, *p_sm, segment_pinbase, 8, true);

// set digit mux pins to PIO output
for (int pin = 0; pin < 4; pin += 1) {
pio_gpio_init (pio, digit_pinbase + pin);
}
pio_sm_set_consecutive_pindirs (pio, *p_sm, digit_pinbase, 4, true);

// initialise X register to zero
pio_sm_exec_wait_blocking (pio, *p_sm, pio_encode_mov (pio_x, pio_null));

// configure and enable the state machine
seven_segment_sm_init (pio, *p_sm, offset, segment_pinbase, digit_pinbase);

return true;
}
6 changes: 6 additions & 0 deletions pio/7_segment/pio_7_segment_library/7_segment_lib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "hardware/pio.h"
#include "hardware/clocks.h" // for clock_get_hz() in generated header
#include "7_segment_lib.pio.h" // generated by pioasm

uint32_t int_to_seven_segment (int num);
bool seven_segment_init (PIO pio, uint *p_sm, uint segment_pinbase, uint digit_pinbase);
50 changes: 50 additions & 0 deletions pio/7_segment/pio_7_segment_library/7_segment_lib.pio
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
;;
;; Copyright (c) 2023 mjcross
;;
;; SPDX-License-Identifier: BSD-3-Clause
;;

.program seven_segment

;; SIDE pins: digit enable lines
;; OUT pins: segments (multiplexed)

.side_set 4 ; leaves one `delay` bit

; note: for the digits to look the same brightness it's
; important to show them for the same amount of time
.wrap_target
pull noblock side 0b0001 ; TX FIFO (or X) -> OSR keep showing 1000's for 1 tick
; if the FIFO is empty then the OSR is loaded from X
public entry_point:
mov x, osr side 0b0000 ; save the segments in X and show no digits for 1 tick

out pins, 8 side 0b1000 [1] ; OSR[07..00] -> segments and show 1's digit for 2 ticks
out pins, 8 side 0b0100 [1] ; OSR[15..08] -> segments and show 10's digit for 2 ticks
out pins, 8 side 0b0010 [1] ; OSR[23..16] -> segments and show 100's digit for 2 ticks
out pins, 8 side 0b0001 ; OSR[31..24] -> segments and show 1000's digit for 1 tick
.wrap
;; 6 instructions


% c-sdk {
static inline void seven_segment_sm_init (PIO pio, uint sm, uint offset,
uint segment_pinbase, uint digit_pinbase) {
// create new sm config
pio_sm_config config = seven_segment_program_get_default_config (offset);

// configure the common cathodes and segment pin groups
sm_config_set_out_pins (&config, segment_pinbase, 8);
sm_config_set_sideset_pins (&config, digit_pinbase);

// configure the clock divider
float div = clock_get_hz (clk_sys) * 10e-3;
mjcross marked this conversation as resolved.
Show resolved Hide resolved
sm_config_set_clkdiv (&config, div);

// apply the configuration to the state machine initialise the program counter
pio_sm_init (pio, sm, offset + seven_segment_offset_entry_point, &config);

// enable the state machine
pio_sm_set_enabled (pio, sm, true);
}
%}
19 changes: 19 additions & 0 deletions pio/7_segment/pio_7_segment_library/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
add_library(pio_7_segment_library INTERFACE)

target_sources(pio_7_segment_library INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/7_segment_lib.c)

# invoke pio_asm to assemble the state machine programs
#
pico_generate_pio_header(pio_7_segment_library ${CMAKE_CURRENT_LIST_DIR}/7_segment_lib.pio)

target_link_libraries(pio_7_segment_library INTERFACE
pico_stdlib
hardware_pio
)

# add the `binary` directory so that the generated headers are included in the project
#
target_include_directories(pio_7_segment_library INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
1 change: 1 addition & 0 deletions pio/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
if (NOT PICO_NO_HARDWARE)
add_subdirectory(7_segment)
add_subdirectory(addition)
add_subdirectory(apa102)
add_subdirectory(clocked_input)
Expand Down