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
)
 



沒有留言:

張貼留言