本文章說明Raspberry Pi Pico 分別使用Multicore與PIO(Programmable I/O)功能建立一個Rotary Encoder的驅動程式,除了測試KY-040 knob外,另外使用IR LED(發射與接收)建立一組IR LED Rotary Encoder用來偵測物體移動的方向。
展示項目:
KY-040 knob
- 旋鈕順時針轉動時,每轉一格WS2812燈條LED亮的數目增加一顆。
- 旋鈕逆時針轉動時,每轉一格WS2812燈條LED亮的數目減少一顆。
- 按下按鈕時,LED燈條以現有亮的數目作流動顯示。
IR LED Rotary Encoder:
- 偵測移動的方向,WS2812燈條依移動方向,以流動方式顯示。
- 五秒鐘沒有偵測到物體移動過,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; }
沒有留言:
張貼留言