diff --git a/README.md b/README.md index ccab5663b..d23174ef5 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/pio/7_segment/7_segment.c b/pio/7_segment/7_segment.c new file mode 100644 index 000000000..8cef6561e --- /dev/null +++ b/pio/7_segment/7_segment.c @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2023 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#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); +} \ No newline at end of file diff --git a/pio/7_segment/7_segment_schematic.fzz b/pio/7_segment/7_segment_schematic.fzz new file mode 100644 index 000000000..02d0bcb76 Binary files /dev/null and b/pio/7_segment/7_segment_schematic.fzz differ diff --git a/pio/7_segment/7_segment_schematic.png b/pio/7_segment/7_segment_schematic.png new file mode 100644 index 000000000..77b0f1639 Binary files /dev/null and b/pio/7_segment/7_segment_schematic.png differ diff --git a/pio/7_segment/7_segment_wiring.fzz b/pio/7_segment/7_segment_wiring.fzz new file mode 100644 index 000000000..33927f4cd Binary files /dev/null and b/pio/7_segment/7_segment_wiring.fzz differ diff --git a/pio/7_segment/7_segment_wiring.png b/pio/7_segment/7_segment_wiring.png new file mode 100644 index 000000000..38e2c17e3 Binary files /dev/null and b/pio/7_segment/7_segment_wiring.png differ diff --git a/pio/7_segment/CMakeLists.txt b/pio/7_segment/CMakeLists.txt new file mode 100644 index 000000000..d7d878fe8 --- /dev/null +++ b/pio/7_segment/CMakeLists.txt @@ -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) diff --git a/pio/7_segment/README.adoc b/pio/7_segment/README.adoc new file mode 100644 index 000000000..5b2fd0a2c --- /dev/null +++ b/pio/7_segment/README.adoc @@ -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 +|=== \ No newline at end of file diff --git a/pio/7_segment/pio_7_segment_library/7_segment_lib.c b/pio/7_segment/pio_7_segment_library/7_segment_lib.c new file mode 100644 index 000000000..8996bb9e5 --- /dev/null +++ b/pio/7_segment/pio_7_segment_library/7_segment_lib.c @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2023 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#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; +} \ No newline at end of file diff --git a/pio/7_segment/pio_7_segment_library/7_segment_lib.h b/pio/7_segment/pio_7_segment_library/7_segment_lib.h new file mode 100644 index 000000000..754638656 --- /dev/null +++ b/pio/7_segment/pio_7_segment_library/7_segment_lib.h @@ -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); diff --git a/pio/7_segment/pio_7_segment_library/7_segment_lib.pio b/pio/7_segment/pio_7_segment_library/7_segment_lib.pio new file mode 100644 index 000000000..72e940cb8 --- /dev/null +++ b/pio/7_segment/pio_7_segment_library/7_segment_lib.pio @@ -0,0 +1,55 @@ +;; +;; 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 to refresh the display at 2kHz - about + // as slow as we can expect with a 16bit divider + float div = clock_get_hz (clk_sys) * 5e-4; + // limit to 16 integer bits of SM clock divisor + if (div > 65535.0) { + div = 65535.0; + } + 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); +} +%} diff --git a/pio/7_segment/pio_7_segment_library/CMakeLists.txt b/pio/7_segment/pio_7_segment_library/CMakeLists.txt new file mode 100644 index 000000000..e687ab2e6 --- /dev/null +++ b/pio/7_segment/pio_7_segment_library/CMakeLists.txt @@ -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} + ) \ No newline at end of file diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 0b9d0b3ac..100210264 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -1,4 +1,5 @@ if (NOT PICO_NO_HARDWARE) + add_subdirectory(7_segment) add_subdirectory(addition) add_subdirectory(apa102) add_subdirectory(clocked_input)