diff --git a/README.md b/README.md index f43a7339a..06793ea0a 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,7 @@ App|Description [pio_pwm](pio/pwm) | Pulse width modulation on PIO. Use it to gradually fade the brightness of an LED. [pio_quadrature_encoder](pio/quadrature_encoder) | A quadrature encoder using PIO to maintain counts independent of the CPU. [pio_quadrature_encoder_substep](pio/quadrature_encoder_substep) | High resolution speed measurement using a standard quadrature encoder. +[pio_seven_segment](pio/seven_segment) | Drive a multiplexed four digit 7-segment LED display using the PIO. [pio_spi_flash](pio/spi) | Use PIO to erase, program and read an external SPI flash chip. [pio_spi_loopback](pio/spi) | Use PIO to run a loopback test with all four CPHA/CPOL combinations. [pio_squarewave](pio/squarewave) | Drive a fast square wave onto a GPIO. This example accesses low-level PIO registers directly, instead of using the SDK functions. diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 023bd4ea8..e2ad2d922 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -14,6 +14,7 @@ if (TARGET hardware_pio) add_subdirectory_exclude_platforms(pwm) add_subdirectory_exclude_platforms(quadrature_encoder) add_subdirectory_exclude_platforms(quadrature_encoder_substep) + add_subdirectory_exclude_platforms(seven_segment) add_subdirectory_exclude_platforms(spi) add_subdirectory_exclude_platforms(squarewave) add_subdirectory_exclude_platforms(st7789_lcd) diff --git a/pio/seven_segment/7_segment.c b/pio/seven_segment/7_segment.c new file mode 100644 index 000000000..c842c8b6f --- /dev/null +++ b/pio/seven_segment/7_segment.c @@ -0,0 +1,49 @@ +#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 + +// By convention the segments are labelled as follows: +// +// AAAA +// F B +// F B +// GGGG +// E C +// E C +// DDDD . + +// You can define a custom bit pattern like this: +// the order here is EDBGACF. but should match the way you wire up the GPIOs +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/seven_segment/7_segment_wiring.fzz b/pio/seven_segment/7_segment_wiring.fzz new file mode 100644 index 000000000..33927f4cd Binary files /dev/null and b/pio/seven_segment/7_segment_wiring.fzz differ diff --git a/pio/seven_segment/7_segment_wiring_diagram.png b/pio/seven_segment/7_segment_wiring_diagram.png new file mode 100644 index 000000000..38e2c17e3 Binary files /dev/null and b/pio/seven_segment/7_segment_wiring_diagram.png differ diff --git a/pio/seven_segment/7seg 07.35.32.svg b/pio/seven_segment/7seg 07.35.32.svg new file mode 100644 index 000000000..36ff7d31e --- /dev/null +++ b/pio/seven_segment/7seg 07.35.32.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pio/seven_segment/CMakeLists.txt b/pio/seven_segment/CMakeLists.txt new file mode 100644 index 000000000..f2215f1d0 --- /dev/null +++ b/pio/seven_segment/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(pio_seven_segment) + +target_sources(pio_seven_segment PRIVATE 7_segment.c) + +add_subdirectory_exclude_platforms(pio_7_segment_library) + +target_link_libraries(pio_seven_segment PRIVATE + pico_stdlib + hardware_pio + pio_7_segment_library + ) + +pico_add_extra_outputs(pio_seven_segment) + +# add url via pico_set_program_url +example_auto_set_url(pio_seven_segment) \ No newline at end of file diff --git a/pio/seven_segment/README.adoc b/pio/seven_segment/README.adoc new file mode 100644 index 000000000..521d242c7 --- /dev/null +++ b/pio/seven_segment/README.adoc @@ -0,0 +1,22 @@ += Driving a 7 segment LED display +This example demonstrates how a PIO state machine can be used to control a four digit multiplexed 7-segment display with the Raspberry Pi Pico (RP2040). + +Multiplexed displays work by rapidly displaying each digit in turn and the PIO provides an excellent way to relieve the CPU of this time-consuming task. + +The total current required is typically more than the GPIO pins can supply so you're likely to need an external drive circuit like the one below. + +The PIO code uses four `side-set` pins to control the digit multiplex lines and displays the segment patterns received on the FIFO. It uses a non-blocking PULL to keep showing the same segments until the CPU sends new data. + +The provided example spells out the word **Pico** and then counts from 0 to 9999. + +== Wiring information +Connect the display to your board using a circuit like the one below, making any changes for your display and transistors. + +Connect the circuit to an external 5V supply or power it via USB (in which case reconnect the _+5V_ rail to _VBUS_ instead of _VSYS_). + +TIP: this circuit is for a _common anode_ display that takes about 15mA per segment. + +[[pio_7_segment_wiring-diagram]] +[pdfwidth=75%] +.Wiring diagram +image::7_segment_wiring_diagram.png[] \ No newline at end of file diff --git a/pio/seven_segment/pio_7_segment_library/7_segment_lib.c b/pio/seven_segment/pio_7_segment_library/7_segment_lib.c new file mode 100644 index 000000000..c971d344d --- /dev/null +++ b/pio/seven_segment/pio_7_segment_library/7_segment_lib.c @@ -0,0 +1,105 @@ +#include +#include "hardware/gpio.h" +#include "7_segment_lib.h" + +// segment patterns for digits 0-9 +// +// By convention the segments are labelled as follows: +// +// AAAA +// F B +// F B +// GGGG +// E C +// E C +// DDDD . +// +// the bit ordering should match the way you connect up the GPIOs. Here it is +// EDBGACF. +static const uint8_t segments[] = { + 0b11101110, // 0 + 0b00100100, // 1 + 0b11111000, // 2 + 0b01111100, // 3 + 0b00110110, // 4 + 0b01011110, // 5 + 0b11011110, // 6 + 0b00101100, // 7 + 0b11111110, // 8 + 0b01111110 // 9 +}; + + +// Simple example of how to convert an integer between -999 and 9999 +// into a 32-bit word representing up to four 7-segment digits. +// Credit to @lurch for the code to handle negative numbers. +// +uint32_t int_to_seven_segment (int num) { + uint32_t word = 0; + if (num < -999 || num > 9999) { + // number out of range, display 'E' symbol + // EDBGACF. + word = 0b11011010; + } else { + if (num == 0) { + word = segments[0]; + } else { + bool negative = num < 0; + if (negative) { + num *= -1; + } + int bitshift; + for (bitshift = 0; bitshift < 32 && num > 0; bitshift += 8) { + word |= segments[num % 10] << bitshift; + num /= 10; + } + if (negative) { + // display '-' symbol + // EDBGACF. + word |= 0b00010000 << bitshift; + } + } + } + return word; +} + + +// configures and initialises 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/seven_segment/pio_7_segment_library/7_segment_lib.h b/pio/seven_segment/pio_7_segment_library/7_segment_lib.h new file mode 100644 index 000000000..754638656 --- /dev/null +++ b/pio/seven_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/seven_segment/pio_7_segment_library/7_segment_lib.pio b/pio/seven_segment/pio_7_segment_library/7_segment_lib.pio new file mode 100644 index 000000000..cc87826bb --- /dev/null +++ b/pio/seven_segment/pio_7_segment_library/7_segment_lib.pio @@ -0,0 +1,49 @@ +.program seven_segment + +;; SIDE pins: digit enable lines 4,3,2,1 +;; 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/seven_segment/pio_7_segment_library/CMakeLists.txt b/pio/seven_segment/pio_7_segment_library/CMakeLists.txt new file mode 100644 index 000000000..9359b7930 --- /dev/null +++ b/pio/seven_segment/pio_7_segment_library/CMakeLists.txt @@ -0,0 +1,18 @@ +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