prettyprint

2023年2月20日 星期一

[Raspberry Pi Pico (c-sdk)] Rotary Encoder: Multicore vs. PIO

 本文章說明Raspberry Pi Pico 分別使用Multicore與PIO(Programmable I/O)功能建立一個Rotary Encoder的驅動程式,除了測試KY-040 knob外,另外使用IR LED(發射與接收)建立一組IR LED Rotary Encoder用來偵測物體移動的方向。

展示項目:

KY-040 knob

  1. 旋鈕順時針轉動時,每轉一格WS2812燈條LED亮的數目增加一顆。
  2. 旋鈕逆時針轉動時,每轉一格WS2812燈條LED亮的數目減少一顆。
  3. 按下按鈕時,LED燈條以現有亮的數目作流動顯示。
IR LED Rotary Encoder:
  1. 偵測移動的方向,WS2812燈條依移動方向,以流動方式顯示。
  2. 五秒鐘沒有偵測到物體移動過,LED燈條熄滅。

一、Rotary Encoder簡述:

KY-040共有五的pins:

CLK(or A), DT (or B), SW(switch), +(VCC) and GND。


  • 當旋轉時CLK與DT輸出變化如下圖所示。


  • SW按鈕:
按鈕按下時連接,SW Pin連接GND,所以Raspberry Pi Pico連接SW Pin的腳位須先PULL-UP。

二、自製IR LED Rotary Encoder:

線路圖如下:




 三、程式說明:

KY-040 knob停止時的狀態(CLK, DT)分別為(0,0) or (1,1),自製IR LED Rotary Encoder停止狀態為(1,1)。旋鈕旋轉一格為從(0,0)->(1,1) or (1,1)->(0,0)。完全移動經過IR LED Rotary Encoder,輸出為(1,1)->(0,1)->(0,0)->(1,0)->(1,1), or (1,1)->(1,0)->(0,0)->(0,1)->(1,1)四個狀態全經過。

分別製作Pi Pico 的驅動程式在core1與PIO上執行。當偵測到狀態變化時,主動呼叫Callback function,主程式中斷處理callback function。
程式動態說明請參閱成果影片,詳細程式碼附於文末。

四、成果影片:




五、程式碼。

  • pio_rotary_encoder.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"

#include "hardware/clocks.h"
#include "pico/util/queue.h"

#include "pio_rotary_encoder.h"
#include "rotary_encoder.pio.h"



queue_t pio_rotary_encoder_queue;

void (*pio_callback)(queue_t *q);

void rotary_encoder_irq_handler() {
    PIO_RE_QUEUE_T q;
    q.action=PIO_RE_UNKNOW;
    if (pio_interrupt_get(pio0, 0)) { // 1: clockwise, 2: countclockwise
        pio_interrupt_clear(pio0, 0); 
        uint32_t act = pio_sm_get_blocking(pio0, 0);
        if (act == 1) {
            q.action = PIO_RE_CLOCKWISE;
            queue_try_add(&pio_rotary_encoder_queue, &q);
            pio_callback(&pio_rotary_encoder_queue);
        }
        if (act == 2) {
            q.action = PIO_RE_COUNTERCLOCKWISE;
            queue_try_add(&pio_rotary_encoder_queue, &q);
            pio_callback(&pio_rotary_encoder_queue);
        }
         
    }
    if (pio_interrupt_get(pio0, 1)) {
        pio_interrupt_clear(pio0, 1);
        q.action = PIO_RE_SWPRESS;
        queue_try_add(&pio_rotary_encoder_queue, &q);
        pio_callback(&pio_rotary_encoder_queue);
    }
    
    
}

void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq) {
    gpio_pull_up(in_base);  // software pull-up

    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_switch_program);

    c = rotary_switch_program_get_default_config(offset);
    
    pio_gpio_init(pio, in_base);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  1,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 1);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq) {
    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_encoder_program);

    c = rotary_encoder_program_get_default_config(offset);
    
    for (int i=0; i < 2; i++) pio_gpio_init(pio, in_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  2,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 2);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
  
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void pio_rotary_encoder_queue_init() {
    queue_init(&pio_rotary_encoder_queue, sizeof(PIO_RE_QUEUE_T), 5);
}

void pio_rotary_encoder_default_init() {
    pio_rotary_encoder_queue_init();
    pio_rotary_switch_init(RE_PIO, RE_SM1, RE_SW_PIN, 2000);
    pio_rotary_encoder_init(RE_PIO, RE_SM0, RE_CLK_DT_PIN, RE_SM_FREQ);
}
void pio_rotary_encoder_cb(void (*cb)(queue_t *q)) {
    pio_callback=cb;
}

  • pio_rotary_encoder.h
 #ifndef _PIO_ROTARY_ENCODER_H_
#define _PIO_ROTARY_ENCODER_H_

#include "pico/util/queue.h"

#define RE_CLK_DT_PIN  16
#define RE_SW_PIN      18
#define RE_PIO      pio0
#define RE_SM0      (0)
#define RE_SM1      (1)
#define RE_SM_FREQ  100000
enum PIO_RE_ACTION{
    PIO_RE_CLOCKWISE=1,
    PIO_RE_COUNTERCLOCKWISE=2,
    PIO_RE_SWPRESS=3,
    PIO_RE_UNKNOW=4
};

typedef struct _PIO_RE_QUEUE_T {
    enum PIO_RE_ACTION action;
}PIO_RE_QUEUE_T;


void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_default_init();
void pio_rotary_encoder_cb(void (*cb)(queue_t *q));


#endif 
  • rotary_encoder.pio
 .program rotary_encoder
start:
in pins, 2 [31]
mov x, isr          ; CLK, DT present state
mov y, osr          ; CLK, DT previous state
jmp x!=y, rotary    ; not eaqual, rotary occure
jmp saveoldvalue    ; clear ISR
rotary:
set y, 0b01         ;
jmp x!=y, pos10
jmp check_dir
pos10:
set y, 0b10
jmp x!=y, saveoldvalue
jmp check_dir
saveoldvalue:
mov osr, isr 
in NULL, 32  ;; imporant to clear ISR
jmp start 

check_dir:
out y,1     ; previous state DT bit
mov osr,x   ; save oldvalue
in NULL, 32 ; clear ISR
in x, 1     ; shift in present state DT bit 
mov x, isr  ; present state DT bit
jmp x!=y counterclockwise
jmp clockwise

clockwise:
set y,1         ; clockwise: value = 1
jmp output
counterclockwise:
set y,2         ; counterclockwise: value = 2
output:
mov isr, y
push
irq wait 0
jmp start  

.program rotary_switch
wait 0 pin 0 [31]       ; Switch PIN is initially pulled up 
irq wait 1              ; [31] wait for button bounce
wait 1 pin 0 [31]

  • core1_rotary_encoder.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "core1_rotary_encoder.h"


queue_t core1_rotary_encoder_queue;

void (*core1_rotary_encoder_cb_t)(queue_t *q);
void core1_rotary_encoder_queue_init() {
    queue_init(&core1_rotary_encoder_queue, sizeof(CORE1_RE_QUEUE_T), 5);
}

void core1_rotary_encoder() {
    core1_rotary_encoder_queue_init();
    gpio_init_mask(0b111<<(BASE_PIN-1));
    gpio_set_dir_in_masked(0b111<<(BASE_PIN-1));
 
    gpio_pull_up(SW_PIN);
    bool clk = gpio_get(CLK_PIN);
    bool oldclk=clk;
    bool dt = gpio_get(DT_PIN);
    bool olddt = dt;
    CORE1_RE_QUEUE_T q;
    while(1) {
        clk=gpio_get(CLK_PIN);
        dt=gpio_get(DT_PIN);
        
        if (clk != dt) {
       
            if(clk != oldclk) { 
                q.action = CORE1_RE_CLOCKWISE;
                queue_add_blocking(&core1_rotary_encoder_queue, &q); 
                core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            }
            if (dt != olddt) {
                q.action = CORE1_RE_COUNTERCLOCKWISE;
                queue_add_blocking(&core1_rotary_encoder_queue, &q);
                core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            }
   
      
        }
        oldclk=clk;olddt=dt;
        
        if (!gpio_get(SW_PIN)) {
            q.action=CORE1_RE_SWPRESS;
            queue_add_blocking(&core1_rotary_encoder_queue, &q);
            core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            while(!gpio_get(SW_PIN)){
                tight_loop_contents();
            }
        }
               
        sleep_ms(1);

    }
}



void core1_rotary_encoder_cb(void (*rotary_encoder_cb_t)(queue_t *q)) {
    core1_rotary_encoder_cb_t=rotary_encoder_cb_t;
}

  • core1_rotary_encoder.h
 #ifndef _CORE1_ROTARY_ENCODER_H_
#define _CORE1_ROTARY_ENCODER_H_

#define CLK_PIN 17
#define DT_PIN  16
#define SW_PIN  18
#define BASE_PIN 16

enum CORE1_RE_ACTION{
    CORE1_RE_CLOCKWISE=1,
    CORE1_RE_COUNTERCLOCKWISE=2,
    CORE1_RE_SWPRESS=3,
    CORE1_RE_UNKNOW=4
};

typedef struct _CORE1_RE_QUEUE_T {
    enum CORE1_RE_ACTION action;
}CORE1_RE_QUEUE_T;



void core1_rotary_encoder_cb(void (*rotary_encoder_cb_t)(queue_t *q));
void core1_rotary_encoder();

#endif 

  • rotary_encoder.c
 #include <stdio.h>
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "pio_rotary_encoder.h"
#include "core1_rotary_encoder.h"
#include "ws2812.h"

#include "pio_rotary_encoder.h"
#include "core1_rotary_encoder.h"

#define PIO_ROTARY_ENCODER  1
#define IR_DETECT           0

#define TOTAL_PIXEL 10
#define LED 25
int total_pixel=0;
bool playing=false;
uint8_t current_dir=0;
bool new_dir=false;

void rotary_encoder_cb(queue_t* q ) {
    #if PIO_ROTARY_ENCODER
    PIO_RE_QUEUE_T data;
    #else
    CORE1_RE_QUEUE_T data;
    #endif 
    if (!queue_is_empty(q)) { 
        queue_remove_blocking(q, &data);
        printf("action:%d\n",data.action);
        #if PIO_ROTARY_ENCODER
        printf("PIO Rotary Encoder:%d\n", data.action);
        #else
        printf("CORE1 Rotary Encoder:%d\n", data.action);
        #endif
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_CLOCKWISE) 
        #else
        if (data.action == CORE1_RE_CLOCKWISE) 
        #endif
        {
            current_dir = data.action;
            new_dir=true;
            total_pixel++;
            if (total_pixel > 10) total_pixel = 10;
        }
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_COUNTERCLOCKWISE) 
        #else
        if (data.action == CORE1_RE_COUNTERCLOCKWISE) 
        #endif
        {
            current_dir = data.action;
            new_dir=true;
            total_pixel--;
            if (total_pixel < 0) total_pixel = 0;
        }
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_SWPRESS) 
        #else
        if (data.action == CORE1_RE_SWPRESS) 
        #endif
        {
              playing = !playing;
        }
    }
}
uint32_t pixels[TOTAL_PIXEL];

void ir_detect_led_dispaly() {

    absolute_time_t timeout;
    
    while(1) {
        #if PIO_ROTARY_ENCODER
        if (current_dir == PIO_RE_CLOCKWISE)
        #else 
        if (current_dir == CORE1_RE_CLOCKWISE)
        #endif
        {
            if (new_dir) {
                timeout = make_timeout_time_ms(5000);
                new_dir=false;;
            }
           
            for (int i = 0 ; i < TOTAL_PIXEL; i++) {
                for (int j=0; j <= i; j++) {
                    ws2812_put_pixel(0x00ff00);
                }
                for (int k=i+1; k < TOTAL_PIXEL; k++) {
                    ws2812_put_pixel(0x000000);
                }
                sleep_ms(50);
            }

        }
        #if PIO_ROTARY_ENCODER
        if (current_dir == PIO_RE_COUNTERCLOCKWISE)
        #else 
        if (current_dir == CORE1_RE_COUNTERCLOCKWISE)
        #endif
        {
            if (new_dir) {
                timeout = make_timeout_time_ms(5000);
                new_dir=false;
            }
         
            for (int i = 0 ; i < TOTAL_PIXEL; i++) {
                for (int k=0; k < TOTAL_PIXEL-i-1; k++) {
                    ws2812_put_pixel(0x000000);
                }
                for (int j=TOTAL_PIXEL-i-1; j < TOTAL_PIXEL; j++) {
                    ws2812_put_pixel(0x0000ff);
                }
                
                sleep_ms(50);
            }
        }
        
        if (!new_dir & current_dir > 0) {
            if (absolute_time_diff_us(get_absolute_time(), timeout) < 0) {
                current_dir = 0;
                for (int j=0; j < TOTAL_PIXEL; j++) {
                    ws2812_put_pixel(0x000000);
                }
                sleep_ms(50);
            }
        }

    }
}

void Rotary_led_display() {
    int i, j, k;
    while(1) {
            if (playing) {
                
                for (i=0; i < total_pixel; i++) {
                    for (k = 0; k <= i; k++)
                        //ws2812_put_pixel(pixels[k]);
                        ws2812_put_pixel(rand());
                    for (j=i+1; j < TOTAL_PIXEL; j++) 
                        ws2812_put_pixel(0x000000);
                        
                    sleep_ms(50);
                 }
                 
                
                
            } else { // setting
                for (int i=0; i < TOTAL_PIXEL; i++) {
                ws2812_put_pixel(0x000000);
                }
                sleep_ms(1);
                for (int i=0; i < total_pixel; i++) {
                    ws2812_put_pixel(0x5f5f5f);
                } 
                sleep_ms(10);
            }
        
            

    }
}

int main()
{
    stdio_init_all();
    
    ws2812_pio_init(WS2812_PIO, WS2812_SM, WS2812_PIN, WS2812_PIO_FREQ);
    gpio_init(LED);
    gpio_set_dir(LED,true);
    #if PIO_ROTARY_ENCODER
    //pio_rotary_switch_init(RE_PIO, RE_SM1, RE_SW_PIN, RE_SM_FREQ);
    //pio_rotary_encoder_init(RE_PIO, RE_SM0, RE_CLK_DT_PIN, RE_SM_FREQ);   
    pio_rotary_encoder_default_init();
    pio_rotary_encoder_cb(rotary_encoder_cb);
    gpio_put(LED,true);
    #else 
    multicore_launch_core1(core1_rotary_encoder);
    core1_rotary_encoder_cb(rotary_encoder_cb);
    gpio_put(LED,false);
    #endif
    uint32_t color;
    for (int i=0; i < TOTAL_PIXEL; i++) {
        color = (0xff0000)/(TOTAL_PIXEL-1)*i+(0x0000ff)/(TOTAL_PIXEL-1)*(TOTAL_PIXEL-1-i);
        color = (color&0xff0000)>>8 | (color&0x00ff00)<<8 | (color&0x0000ff);
        pixels[i] = color;
    }
    
    #if  IR_DETECT
    ir_detect_led_dispaly();
    #else
    Rotary_led_display();
    #endif


    puts("Hello, world!");

    return 0;
}



沒有留言:

張貼留言