prettyprint

2025年3月19日 星期三

[Raspberry Pi Pico] PIO programming: 7-segment display and 4x4 keypad as examples

  本文章介紹使用Raspberry Pi Pico的PIO功能製作一個8位數的七段式顯示器的驅動程式,另外結合以前使用PIO製作的4x4 KEYPAD作為輸入,建構一個簡單的計算器。

8位數七段顯示除使用8個共陰極的顯示器外,另外使用2個74HC595 shift register與8個NPN電晶體,第一個shift register控制七段顯示器A~G與dp的顯示,第二個shift register Q0~Q7連接個別7段顯示器的pin 3/8控制顯示器顯示(low)或關閉(high)。

線路圖下所示:


顯示方式以快速一次顯示一個7段顯示器,因此每顯示一個位數須輸出2 bytes,第一個byte為7段顯示器內容,第二個byte為第幾個顯示器開啟(其他關閉)。

PIO程式部份使用side-set 控制shift register 的SH_CP與ST_CP clock,程式碼如文末所示。

4x4 keypad詳細內容請參閱:

Raspberry Pi Pico PIO(Programmable IO) Episode 1: 4x4 matrix keypad


成果影片



程式碼:

CMakeLists.txt(main)
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.0)
set(toolchainVersion 13_3_Rel1)
set(picotoolVersion 2.1.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(pico_pio_7seg C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(pico_pio_7seg pico_pio_7seg.c)
pico_set_program_name(pico_pio_7seg "pico_pio_7seg")
pico_set_program_version(pico_pio_7seg "0.1")
target_compile_definitions(pico_pio_7seg PRIVATE
WIFI_SSID="$ENV{WIFI_SSID}"
)
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(pico_pio_7seg 0)
pico_enable_stdio_usb(pico_pio_7seg 1)
# Add the standard library to the build
target_link_libraries(pico_pio_7seg
pico_stdlib
)
# Add the standard include files to the build
target_include_directories(pico_pio_7seg PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Add any user requested libraries
target_link_libraries(pico_pio_7seg
hardware_pio
)
add_subdirectory(disp_7segment)
add_subdirectory(pico_keypad)
target_link_libraries(pico_pio_7seg
disp_7segment
pico_keypad)
pico_add_extra_outputs(pico_pio_7seg)
pico_pio_7seg.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pico/multicore.h"
#include "disp_7segment.pio.h"
#include "disp_7segment.h"
#include "keypad.h"
#include "stdlib.h"
#include "string.h"
#define DS_PIN 16
#define SH_ST_CLK_PIN 17
uint8_t* disp_expression_value(double value) {
static uint8_t value_str[20];
sprintf(value_str, "%f", value);
if (strchr(value_str, '.')) {
for (int i = strlen(value_str)-1; i>=0; i--) {
if (value_str[i] == '0' ) {
value_str[i]=0;
} else {
if (value_str[i] == '.') value_str[i]=0;
break;
}
}
}
if (value_str[0]==0) {
value_str[0]='0';
}
disp_7segment_write_str_digits(value_str);
return value_str;
}
void demo();
int main()
{
stdio_init_all();
disp_7segment_init(pio0, 1, DS_PIN, SH_ST_CLK_PIN, 8, 10000000);
keypad_default_init();
//demo();
uint8_t exp[256]={0};
uint8_t input[20]={0};
int pos_index=0;
uint8_t key;
uint8_t ks[2]={0,0};
double pre_result=0;
uint8_t pre_op=0;
disp_7segment_write_str_digits("0");
while (true) {
key = get_new_keypad_value();
switch (key) {
case '0': case '1': case '2' :case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.':
if (pos_index > 8) continue;
input[pos_index++] = key;
disp_7segment_write_str_digits(input);
break;
case 'b':
--pos_index;
if (pos_index < 0) { memset(input, 0, sizeof(input));pos_index = 0; }
input[pos_index] = 0;
disp_7segment_write_str_digits(input);
break;
case '+': case '-': case '*':
// update expression
ks[0] = key;
strcat(exp, input);
strcat(exp, ks);
// calculate previous sub-expression
if (pre_op) {
if (pre_op == '+') {
pre_result += atof(input);
}
if (pre_op == '-') {
pre_result -= atof(input);
}
if (pre_op == '*') {
pre_result *= atof(input);
}
disp_expression_value(pre_result);
}
else
pre_result=atof(input);
pre_op = key;
//clean input buffer
memset(input, 0, 20);
pos_index = 0;
break;
case '=': //calculate total expression
strcat(exp, input);
if (pre_op) {
if (pre_op == '+') {
pre_result += atof(input);
}
if (pre_op == '-') {
pre_result -= atof(input);
}
if (pre_op == '*') {
pre_result *= atof(input);
}
}
printf("%s=%f\n", exp, pre_result);
disp_expression_value(pre_result);
memset(input, 0, 20);
pos_index = 0;
pre_op = 0;
pre_result = 0;
memset(exp, 0, sizeof(exp));
break;
}
//disp_7segment_write_str_digits(input);
tight_loop_contents();
}
}
void demo() {
//sleep_ms(5000);
uint8_t buf[20];
for (int i =0; i < 16;i++) {
disp_7segment_write_digits(i);
sleep_ms(500);
}
sleep_ms(500);
disp_7segment_write_str_digits("a");
sleep_ms(500);
disp_7segment_write_str_digits("ab");
sleep_ms(500);
disp_7segment_write_str_digits("abc");
sleep_ms(500);
disp_7segment_write_str_digits("abcd");
sleep_ms(500);
disp_7segment_write_str_digits("abced");
sleep_ms(500);
disp_7segment_write_str_digits("abcdef");
sleep_ms(1000);
for (int i =-1; i >= -15;i--) {
disp_7segment_write_digits(i);
sleep_ms(500);
}
sleep_ms(500);
disp_7segment_write_str_digits("1");
sleep_ms(500);
disp_7segment_write_str_digits("12");
sleep_ms(500);
disp_7segment_write_str_digits("123");
sleep_ms(500);
disp_7segment_write_str_digits("1234");
sleep_ms(500);
disp_7segment_write_str_digits("12345");
sleep_ms(500);
disp_7segment_write_str_digits("123456");
sleep_ms(500);
disp_7segment_write_str_digits("1234567");
sleep_ms(500);
disp_7segment_write_str_digits("12345678");
sleep_ms(1000);
for (double i=0.67; i < 100; i+=5.71) {
sprintf(buf, "%f", i);
disp_expression_value(i);
sleep_ms(500);
}
sleep_ms(2000);
}

disp_7segment library:
  • disp_7segment.c
#include "pico/stdlib.h"
#include "stdio.h"
#include "stdlib.h"
#include "disp_7segment.h"
#include "disp_7segment.pio.h"
#include "string.h"
#include "pico/multicore.h"
uint8_t led_digit[] = {
0b11111100, //0
0b01100000, //1
0b11011010, //2
0b11110010, //3
0b01100110, //4
0b10110110, //5
0b10111110, //6
0b11100000, //7
0b11111110, //8
0b11100110, //9
0b11101110, //A
0b00111110, //b
0b10011100, //C
0b01111010, //d
0b10011110, //E
0b10001110, //F
0b00000010, //-
0b00000000, // default no display
};
static uint8_t total_digits;
static PIO disp_7segment_pio;
static uint disp_7segment_sm;
static void display_digits(uint8_t *value_str, uint8_t value_len);
void core1_display_digits(void) {
uint32_t popup_data;
uint8_t display_data_len=0;
uint8_t display_data_str[20]={0};
while(1) {
if (multicore_fifo_pop_timeout_us(100,&popup_data)) {
strcpy(display_data_str, (uint8_t*)popup_data);
display_data_len = strlen(display_data_str);
}
display_digits(display_data_str, display_data_len);
}
}
void disp_7segment_init(PIO pio, uint sm, uint ds, uint sh_st_clk, uint8_t digits, uint freq) {
total_digits = digits;
uint offset = pio_add_program(pio, &disp_7segment_program);
pio_sm_config c = disp_7segment_program_get_default_config(offset);
pio_gpio_init(pio, ds);
for (int i = 0; i < 2; i++) pio_gpio_init(pio, sh_st_clk+i);
disp_7segment_pio = pio;
disp_7segment_sm = sm;
pio_sm_set_consecutive_pindirs(pio, sm, ds, 1, true);
pio_sm_set_consecutive_pindirs(pio, sm, sh_st_clk, 2, true);
sm_config_set_sideset_pin_base(&c, sh_st_clk);
sm_config_set_out_pins(&c, ds,1);
sm_config_set_out_shift(&c, true, true, 8);
if (freq > 100000) freq = 100000;
sm_config_set_clkdiv(&c, SYS_CLK_HZ/(freq*2));
//sm_config_set_clkdiv(&c, 62.5);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, pio_encode_set(pio_y, digits%8 ? digits/8+1 : digits/8));
pio_sm_set_enabled(pio, sm, true);
// launch core1 to continuely display ditits
multicore_launch_core1(core1_display_digits);
}
static uint8_t char_to_index(uint8_t c) {
//if (c >='0' && c <='9') return c-'0';
switch (c) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
return c-'0';
break;
case 'A': case 'a':
return 10;
break;
case 'B': case 'b':
return 11;
break;
case 'C': case 'c':
return 12;
break;
case 'D': case 'd':
return 13;
break;
case 'E': case 'e':
return 14;
break;
case 'F': case 'f':
return 15;
break;
case '-':
return 16;
break;
default:
return 17;
break;
}
}
static void display_digits(uint8_t *value_str, uint8_t value_len) {
uint32_t digit_pos=0;
uint8_t temp_total_digitals = (total_digits %8) ? (total_digits/8+1)*8 : total_digits;
bool dp = false;
uint8_t real_digits=total_digits;
for (int i=0, pos=0; i < real_digits; i++) {
if (i > value_len-1) pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, 0);
else {
if (value_str[value_len-i-1] == '.')
{
dp=true;
real_digits++;
continue;
}
if (dp) {
dp = false;
pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, led_digit[char_to_index(value_str[value_len-i-1])]+1);
} else {
pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, led_digit[char_to_index(value_str[value_len-i-1])]);
}
}
digit_pos=0;
digit_pos = 1 << (temp_total_digitals-pos-1);
pio_sm_put_blocking(disp_7segment_pio, disp_7segment_sm, digit_pos);
pos++;
//sleep_us(100);
}
}
void disp_7segment_write_digits(int32_t value) {
static uint8_t value_str[20];
sprintf(value_str, "%ld", value);
multicore_fifo_push_blocking((uint32_t)value_str);
}
void disp_7segment_write_str_digits(uint8_t* value) {
static uint8_t value_str[20];
if (value[0] == 0)
strcpy(value_str,"0");
else
strcpy(value_str, value);
multicore_fifo_push_blocking((uint32_t)value_str);
}
  • disp_7segment.h
#ifndef __DISP_7_SEGMENT__
#define __DISP_7_SEGMENT__
#include "hardware/pio.h"
extern uint8_t led_digit[];
void disp_7segment_init(PIO pio, uint sm, uint ds, uint sh_st_clk, uint8_t digits, uint freq);
void disp_7segment_write_digits(int32_t value);
void disp_7segment_write_str_digits(uint8_t* value);
#endif
  • disp_7segment.pio
.program disp_7segment
.side_set 2 ;SH_CP(bit0), ST_CP(bit1)
mov isr, y side 0b00 ;total digits preload to y
;isr used to store y temporarily
.wrap_target
digits_loop:
set x,7 side 0b00 ;a~g and dp
seg_loop:
out pins, 1 side 0b00
jmp x--, seg_loop side 0b01 ;shift out one shift register data
jmp y--, digits_loop side 0b00 ;
mov y, isr side 0b10 ;restore y
.wrap

  • CMakeLists.txt
add_library(disp_7segment INTERFACE)
pico_generate_pio_header(disp_7segment ${CMAKE_CURRENT_LIST_DIR}/disp_7segment.pio)
target_sources(disp_7segment INTERFACE
${CMAKE_CURRENT_LIST_DIR}/disp_7segment.c
)
target_include_directories(disp_7segment INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(disp_7segment INTERFACE
hardware_pio
pico_multicore
)

pico_keypad libarary:
  • keypad.c
#include "keypad.pio.h"
#include "keypad.h"
#include "hardware/clocks.h"
#include "stdio.h"
#include "pico/stdlib.h"
static uint8_t key_value=0;
static PIO keypad_pio=pio1;
static uint keypad_sm=0;
static const uint8_t keys[4][4]={ // define user key
{'1','2','3','+'},
{'4','5','6','-'},
{'7','8','9','*'},
{'b','0','.','='}
};
static void keypad_handle() {
if (pio_interrupt_get(keypad_pio, KEYPAD_PIO_INT_NUM)) {
key_value=0;
pio_interrupt_clear(keypad_pio, KEYPAD_PIO_INT_NUM);
uint32_t x, y;
y=pio_sm_get_blocking(keypad_pio, keypad_sm);
x=pio_sm_get_blocking(keypad_pio, keypad_sm);
for(uint8_t i = 0 ; i < 4; i++){
if ((x >> i)==1) {x=i;break;}
}
for(uint8_t j = 0 ; j < 4; j++){
if ((y >> j)==1) {y=j;break;}
}
key_value = keys[x][y];
}
}
void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq) {
uint offset=0;
pio_sm_config c;
offset = pio_add_program(pio, &keypad_program);
c = keypad_program_get_default_config(offset);
for (int i=0; i < 4; i++) pio_gpio_init(pio, in_base+i);
for (int i=0; i < 4; i++) pio_gpio_init(pio, set_base+i);
pio_sm_set_consecutive_pindirs(pio, sm, in_base, 4, false);
pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);
sm_config_set_in_pins(&c, in_base);
sm_config_set_set_pins(&c, set_base, 4);
sm_config_set_in_shift(&c, false, false, 32);
float div = clock_get_hz(clk_sys)/freq;
sm_config_set_clkdiv(&c, div);
uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
pio_interrupt_source_t pis_int_num;
switch (KEYPAD_PIO_INT_NUM) {
case 0: pis_int_num = pis_interrupt0; break;
case 1: pis_int_num = pis_interrupt1; break;
case 2: pis_int_num = pis_interrupt2; break;
case 3: pis_int_num = pis_interrupt3; break;
}
pio_set_irq0_source_enabled(pio, pis_int_num, true);
irq_add_shared_handler(pio_irq, keypad_handle, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(pio_irq, true);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
uint8_t get_new_keypad_value() {
uint8_t ret_vale = key_value;
key_value=0;
return ret_vale;
}
/* default value:
pio: pio1
sm: 0
set pins:gpio 8-11
in pins: 12-15
*/
void keypad_default_init() {
keypad_pio_init(keypad_pio, keypad_sm, 8, 12, 100000);
}
  • keypad.h
#ifndef __KEYPAD_H
#define __KEYPAD_H
void keypad_default_init();
uint8_t get_new_keypad_value();
void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq);
#endif
  • keypad.pio
.define PUBLIC KEYPAD_PIO_INT_NUM 0 // only 0-3, PIO version 0
.program keypad
.wrap_target
set_row_1:
set pins, 0b0001 // pull up line 1(pin 1)
set x,0b0001 // store value in register X
in pins,4 // get input value of line 5~8 (pin 5~8)
mov y, isr // and store in Y
jmp !y set_row_2 // If no button is pressed (0b0000), continue checking line 2
jmp rx_fifo [10] // if any button is pressed, jump to push x, y
set_row_2:
set pins, 0b0010
set x,0b0010
in pins,4
mov y, isr
jmp !y set_row_3
jmp rx_fifo [10] // waiting for button bounce
set_row_3:
set pins, 0b0100
set x,0b0100
in pins,4
mov y, isr
jmp !y set_row_4
jmp rx_fifo [10]
set_row_4:
set pins, 0b1000
set x,0b1000
in pins,4
mov y, isr
jmp !y set_row_1
nop [10]
rx_fifo:
push // push y col value
in x,4
push // and then x row value
irq KEYPAD_PIO_INT_NUM // raising interrupt
wait 0 pin 0 // check whether key is released
wait 0 pin 1
wait 0 pin 2
wait 0 pin 3
.wrap
  • CMakeLists.txt
add_library(pico_keypad INTERFACE)
pico_generate_pio_header(pico_keypad ${CMAKE_CURRENT_LIST_DIR}/keypad.pio)
target_sources(pico_keypad INTERFACE
${CMAKE_CURRENT_LIST_DIR}/keypad.c
)
target_include_directories(pico_keypad INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(pico_keypad INTERFACE
hardware_pio
)



沒有留言:

張貼留言