本文章說明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;
}



沒有留言:
張貼留言