> For the complete documentation index, see [llms.txt](https://think-embedded.gitbook.io/micropython/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://think-embedded.gitbook.io/micropython/micropython-for-rp2040-pico/pio-signaling-and-measurement.md).

# PIO Signaling and Measurement

## โค้ดตัวอย่างที่ 1

มาเริ่มต้นด้วยการอ่านค่าความถี่ของ **RP2040** ที่ใช้สำหรับไมโครไพธอน ดังนี้

```python
import machine
freq = machine.freq() # get the CPU frequency in Hz
print( 'CPU frequency: {} MHz'.format(int(freq/1e6)) )
```

และจะได้ข้อความเอาต์พุตดังนี้ (ซึ่งจะได้ความถี่เท่ากับ **125 MHz)**

`CPU frequency: 125 MHz`                                       &#x20;

ถ้าลองเขียนโค้ดไมโครไพธอน เพื่อเปิดใช้งานขา **GPIO** เช่น ขา  **GP14** ให้เป็นเอาต์พุต และใช้คำสั่งเพื่อเปลี่ยนสถานะลอจิก **High** และ **Low** ตามลำดับ และทำซ้ำไปเรื่อย ๆ แบบ `while` จากนั้นลองวัดสัญญาณเอาต์พุตที่ได้

```python
import machine

pin = machine.Pin(14, machine.Pin.OUT)
try:
    while True:
        pin.low()
        pin.high()
except KeyboardInterrupt:
    pass
print('Done')
```

จากรูปคลื่นสัญญาณที่ได้จะเห็นว่า สัญญาณมีการเปลี่ยนแปลงสถานะลอจิก แต่มีคาบเวลาที่ไม่คงที่ ค่อนข้างเลื่อนไปมา หรือมี **Jitter** เกิดขึ้นอย่างเห็นได้ชัด

![รูป: คลื่นสัญญาณเอาต์พุตที่วัดได้](/files/-MT_8VXdKnW9kHkZInus)

## โค้ดตัวอย่างที่ 2

ตัวอย่างนี้ลองสร้างสัญญาณเอาต์พุตที่ขา **GP14** เป็นเอาต์พุต และเขียนโปรแกรมสำหรับ **PIO** โดยเลือกใช้ **StateMachine** หมายเลข **0** (**SM0**) และใช้คำสั่ง `set()` เพื่อทำให้ขา **GP14** เปลี่ยนหรือสลับสถานะลอจิก **1** กับ **0** ตามลำดับ ให้ทำซ้ำไปเรื่อย ๆ  เริ่มต้นมีสถานะลอจิกเป็น **Low**  และในการทำงานของ **SM0** ได้เลือกใช้ความถี่ เช่น **2 MHz**

การทำคำสั่ง `set()` จะใช้เพียงหนึ่งไซเคิลเท่านั้น ดังนั้นถ้าทำสองคำสั่งที่อยู่ระหว่าง `wrap_target()` กับ `wrap()` ก็จะใช้ 2 ไซเคิล  ความถี่เอาต์พุตที่ควรจะได้คือ **2 MHz /2 = 1 MHz**

ข้อสังเกต: การใช้พารามิเตอร์ `set_base` เป็นการกำหนดหมายเลขของขาเริ่มต้น (ใช้ได้สูงสุดถึง 5 ขา ที่เรียงติดกัน) และจะนำไปใช้กับ **SM** ในตัวอย่างนี้ได้ระบุอาร์กิวเนนต์เป็น `Pin(14)` มีเพียงขาเดียวเท่านั้น และในส่วนของ **Python decorator** `@asm_pio(`) มีการใช้พารามิเตอร์ `set_init` เพื่อกำหนดทิศทางเป็นเอาต์พุตและกำหนดค่าเริ่มต้น โดยระบุอาร์กิวเมนต์เป็น `(PIO.OUT_LOW)`

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=PIO.OUT_LOW )
def pio_test():
    wrap_target() 
    set(pins,1) # 1-cycle: drive output pin to 1
    set(pins,0) # 1-cycle: drive output pin to 0
    wrap()      # 0-cycle: unconditional jump to wrap target

# create an instance of StateMachine 0 (SM0)
sm = StateMachine(0, pio_test, freq=2_000_000, set_base=Pin(14) )
sm.active(1) # run the SM0

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0) # stop SM0
```

หรือจะเปลี่ยนไปใช้วิธี **Side Setting** แทนก็ได้

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( sideset_init=PIO.OUT_LOW )
def test():
    wrap_target()
    nop() .side(1) # drive side pin high
    nop() .side(0) # drive side pin low
    wrap()
sm = StateMachine(0, test, freq=2_000_000, sideset_base=Pin(14) )
sm.active(1) # run the SM0

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0) # stop SM0
```

![รูป: คลื่นสัญญาณเอาต์พุต ความถี่ 1 MHz](/files/-MT_DYXsrR7eQo9SSkub)

ถ้าเปลี่ยนความถี่ในการทำงานของ **SM0** จาก **2 MHz** ให้เป็น **50 MHz** ความถี่ของสัญญาณเอาต์พุตที่ได้ควรจะเป็น **50 MHz /2 =** **25 MHz** ตามรูปคลื่นสัญญาณที่วัดได้จะเห็นว่า มีลักษณะไม่เป็นคลื่นสี่เหลี่ยม แต่มีอัตราการเปลี่ยนระดับแรงดันไฟฟ้าสองระดับ เท่ากับ **25 MHz**

![รูป: คลื่นสัญญาณเอาต์พุต ความถี่ 25 MHz](/files/-MT_DhUg0IMy9fHXNfO6)

## โค้ดตัวอย่างที่ 3

ตัวอย่างนี้คล้ายกับตัวอย่างที่แล้ว แต่มีการเปลี่ยนจากการใช้คำสั่ง `wrap_target()` และ `wrap()` เพื่อกำหนดขอบเขตการทำลำดับของคำสั่งแบบวนซ้ำโดยอัตโนมัติ มาเป็นการใช้คำสั่ง `label()` และ `jmp()` แทน&#x20;

คำสั่ง `set()` มีการใช้งานอยู่ 2 คำสั่ง ตามลำดับ ใช้สำหรับกำหนดสถานะลอจิกของขาเอาต์พุต (ใช้ขา `GP14` เพียงขาเดียวในตัวอย่างนี้) ให้เป็น **High** และ **Low** สลับกันไป (เริ่มต้นมีสถานะลอจิกเป็น **Low**)

เมื่อทำคำสั่ง `set()` ทั้งสองคำสั่งแล้ว จึงทำคำสั่ง `jmp()` เพื่อให้ย้อนกลับไปเริ่มทำคำสั่งที่อยู่ถัดไปจากบรรทัดที่มีการประกาศ `label()` โดยรวมทั้งหมด มี 3 คำสั่ง แต่ละคำสั่งใช้เวลาหนึ่งไซเคิล ดังนั้นจึงใช้ 3 ไซเคิล ต่อหนึ่งรอบ

ในตัวอย่างนี้ได้เลือกใช้ความถี่สำหรับ **SM0** ของ **PIO** ให้เท่ากับ **5 MHz** ดังนั้น จะได้ความถี่ของสัญญาณเอาต์พุตเท่ากับ **5 MHz /3 = 1.667 MHz** มีช่วงเวลาที่เป็น **High : Low** เท่ากับ **1 : 2** หรือมีค่า **Duty Cycle = 33.33%**

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio_test():
    label('loop')
    set(pins,1) # 1-cycle: drive output pin to 1
    set(pins,0) # 1-cycle: drive output pin to 0
    jmp('loop') # 1-cycle: unconditional jump 

# 5MHz/3 = 1.667 MHz
sm = StateMachine(0, pio_test, freq=5_000_000, set_base=Pin(14) )
sm.active(1)

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0)
```

![รูป: คลื่นสัญญาณเอาต์พุต ความถี่ 1.667 MHz (Duty Cycle: 33.33%)](/files/-MT_FbNoWn6k3FnEzxsK)

## โค้ดตัวอย่างที่ 4

จากโค้ดตัวอย่างที่ 3 ลองมาเพิ่มการหน่วงเวลา (**delay**) หลังจากทำคำสั่ง `set()` แต่ละคำสั่งดูบ้าง โดยเขียนจำนวนไซเคิล (เป็นค่าคงที่เลขจำนวนเต็มบวกขนาดไม่เกิน 5 บิต หรือ 1..31) ในวงเล็บสี่เหลี่ยมต่อท้ายคำสั่ง เช่น `[1]` หมายถึง หน่วงเวลาไว้หนึ่งไซเคิลหลังจากทำคำสั่งนั้นเสร็จแล้ว

จากนั้นมาลองวัดสัญญาณเอาต์พุตดูว่า มีการเปลี่ยนแปลงไปอย่างไร

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio_test():
    label('loop')
    set(pins,1) [1] # 2 cycles: drive output pin to 1
    set(pins,0) [1] # 2 cycles: drive output pin to 0
    jmp('loop')     # 1 cycle: unconditional jump 

# output frequency: 5MHz/5 = 1MHz
sm = StateMachine(0, pio_test, freq=5_000_000, set_base=Pin(14) )
sm.active(1)

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0)
```

จากรูปสัญญาณเอาต์พุตที่ได้ จะเห็นว่า มีความถี่ **1 MHz** และความกว้างช่วงที่เป็น **High** และ **Low** มีอัตราส่วน **2:3** หรือ **Duty Cycle = 40%**

![รูป: คลื่นสัญญาณเอาต์พุต ความถี่ 1 MHz (Duty Cycle: 40%)](/files/-MT_IJfemLOupHZyJlPv)

## โค้ดตัวอย่างที่ 5

ตัวอย่างนี้สาธิตการใช้งานขา **GPIO** จำนวน 2 ขา คือ **GP14** และ **GP15** โดยใช้เป็นเอาต์พุตเหมือนกันและจะถูกกำหนดสถานะลอจิกโดย **StateMachine (SM)** ของ **PIO** แต่ใช้งานต่างรูปแบบกัน เช่น ได้เลือกใช้ **GP14** ใช้กับคำสั่ง `set()` แต่ **GP15** ใช้สำหรับ `.side()` ในรูปแบบที่เรียกว่า **Side Setting** เกิดขึ้นพร้อมกับการคำสั่งในบรรทัดเดียวกัน

ความถี่ในการทำงานของ **SM** ในตัวอย่างนี้คือ **2 MHz** ดังนั้น ความถี่ของสัญญาณเอาต์พุตที่ได้จากขา **GP14** และ **GP15** คือ **2 MHz /2 = 1 MHz** แต่สถานะลอจิกของขาทั้งสองจะต่างกัน

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=(PIO.OUT_LOW), sideset_init=(PIO.OUT_LOW) )
def pio_test():
    wrap_target()
    set(pins,1)  .side(0) # GP14=1, GP15=0
    set(pins,0)  .side(1) # GP14=0, GP15=1
    wrap()

sm = StateMachine(0, pio_test, freq=2_000_000,
                  set_base=Pin(14), sideset_base=Pin(15) )
sm.active(1)

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0)
```

![รูป: คลื่นสัญญาณเอาต์พุต ความถี่ 1 MHz (Duty Cycle: 50%) 2 ช่อง สถานะลอจิกตรงข้ามกัน](/files/-MTcptsPL-CNDW6YS2rS)

## โค้ดตัวอย่างที่ 6

ตัวอย่างนี้สาธิตการใช้ **StateMachine** พร้อมกัน 4 ชุด (เลือกใช้ **SM 0,1,2,3** ของ **PIO 0**) ให้สร้างสัญญาณเอาต์พุตที่ขา **GP13**, **GP12,** **GP11**, **GP10** ตามลำดับ และให้รอสัญญาณอินพุตที่ขา **GP14** เปลี่ยนจาก **High** เป็น **Low** ก่อน ซึ่งได้จากการต่อวงจรปุ่มกดจากภายนอก จากนั้นจึงเริ่มทำงาน

วงจร **SM0 - SM3** จะเริ่มต้นสร้างสัญญาณพัลส์ไม่พร้อมกัน เนื่องจากมีการใช้คำสั่ง `nop() [...]` และหน่วงเวลาไว้แตกต่างกัน ก่อนเริ่มทำลำดับคำสั่งที่วนซ้ำระหว่าง `wrap_target()` และ `wrap()`

การทำงานของ **SM0 - SM3** ใช้ความถี่ **2 MHz** และในการทำงานแต่ละรอบของกลำดับคำสั่งจะใช้เวลาเท่ากับ 4 ไซเคิล ดังนั้นจะได้ความถี่ **2 MHz /4 = 500 kHz**&#x20;

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio0():
    wait(0, pin, 0) # wait for input button goes low
    nop() [0]
    wrap_target() 
    set(pins,1)
    set(pins,0) [2]
    wrap() 

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio1():
    wait(0, pin, 0) # wait for input button goes low
    nop() [1]
    wrap_target() 
    set(pins,1)
    set(pins,0) [2]
    wrap() 

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio2():
    wait(0, pin, 0) # wait for input button goes low
    nop() [2]
    wrap_target() 
    set(pins,1)
    set(pins,0) [2]
    wrap() 

@asm_pio( set_init=(PIO.OUT_LOW) )
def pio3():
    wait(0, pin, 0) # wait for input button goes low
    nop() [3]
    wrap_target() 
    set(pins,1)
    set(pins,0) [2]
    wrap() 

button = Pin(14, Pin.IN, Pin.PULL_UP)
pins = [Pin(13),Pin(12),Pin(11),Pin(10)]
pio_funcs = [pio0, pio1, pio2, pio3]
sm_list = []
for i in range(4):
    sm_list.append( StateMachine(i, 
            pio_funcs[i], freq=2_000_000,
            in_base=button, set_base=pins[i] ) )
for sm in sm_list:
    sm.active(1)

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    for sm in sm_list:
        sm.active(0)
```

![รูป: คลื่นสัญญาณเอาต์พุตทั้ง 4 ช่อง จากขา GP13-GP10 ตามลำดับ](/files/-MTdBGYDH2y7nrbU8kof)

## โค้ดตัวอย่างที่ 7

ตัวอย่างนี้สาธิตการเขียนโค้ดเพื่อให้ **PIO** คอยตรวจสอบอินพุตแล้วให้เอาต์พุตเปลี่ยนสถานะของเอาต์พุตตามสถานะของอินพุตที่อ่านเข้ามา และใช้วงจรปุ่มกดสร้างสัญญาณอินพุต ไม่กดได้ลอจิก **High** และแต่ถ้ากดปุ่มค้างไว้จะได้ลอจิก **Low**

ขา **GP14** ถูกเลือกใช้เป็นขาอินพุต และขา **GP15** ถูกเลือกใช้เป็นขาเอาต์พุต และกำหนดความถี่ในการทำงานเท่ากับ **5 MHz (1 Cycle = 200 ns)**

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( set_init=(PIO.OUT_HIGH) )
def pio_test(): 
    wrap_target()
    label('start')
    jmp(pin,'drive_high') # check jump pin for branch
    set(pins,0) # drive output low
    jmp('start')
    label('drive_high')
    set(pins,1) # drive output pin
    wrap()

in_pin  = Pin(14, Pin.IN, Pin.PULL_UP )
out_pin = Pin(15, Pin.OUT )
sm = StateMachine(0)
sm.init( pio_test, freq=5_000_000,
         set_base=out_pin, jmp_pin=in_pin )
sm.active(1) # start the SM

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0)
```

![รูป: คลื่นสัญญาณอินพุต CH1 และเอาต์พุต CH2 เมื่อมีการกดปุ่มแล้วปล่อย](/files/-MTdZbMEo7i8L2nhvv0D)

![รูป: ขอบขาลงของสัญญาณอินพุตจากนั้นจึงเป็นขอบขาลงของสัญญาณเอาต์พุต](/files/-MTdZeDaLW6qvO7RzhsD)

## โค้ดตัวอย่างที่ 8

ตัวอย่างนี้คล้าย**ตัวอย่างที่ 7** แต่ใช้วิธีตรวจสอบสถานะของอินพุตที่ขา **GP14** โดยใช้การเลื่อนบิตเข้ามาหนึ่งตำแหน่ง ซึ่งจะเก็บไว้ใน **ISR (Input Shift Register)** จากนั้นก็สำเนาค่าจาก **ISR** ไปยัง **OSR (Output Shift Register)** แล้วเลื่อนบิตออกไปหนึ่งตำแหน่งไปยังขาเอาต์พุตที่ขา **GP15**

```python
import utime as time
from rp2 import PIO, asm_pio, StateMachine
from machine import Pin

@asm_pio( out_shiftdir=PIO.SHIFT_RIGHT,
          in_shiftdir=PIO.SHIFT_LEFT,
          out_init=PIO.OUT_LOW )
def pio_test():
    wrap_target()
    in_(pins,1)  # shift input pin into ISR
    mov(osr,isr) # copy from ISR to OSR
    out(pins,1)  # shift data from OSR to output pin
    wrap()       # jump to wrap target

# create an instance of StateMachine 0 (SM0)
sm = StateMachine(0, pio_test, freq=15_000_000,
                  out_base=Pin(15), in_base=Pin(14) )
sm.active(1) # run the SM0

try:
    while True:
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    sm.active(0) # stop SM0
```

![รูป: คลื่นสัญญาณอินพุต CH1 และเอาต์พุต CH2 เมื่อมีการกดปุ่มแล้วปล่อย](/files/-MTdviqK5uFgsSC_FeLD)

![รูป: ขอบขาลงของสัญญาณอินพุตจากนั้นจึงเป็นขอบขาลงของสัญญาณเอาต์พุต](/files/-MTdvnWoH1pplgEeuRIF)

## กล่าวสรุป

เนื้อหาในส่วนนี้ได้นำเสนอตัวอย่างโค้ดไมโครไพธอน เพื่อนำไปทดลองใช้งานกับบอร์ด **Raspberry Pi Pico (RP2040)** สาธิตการทำงานของวงจร **Programmable I/O (PIO)** ที่อยู่ภายใน และใช้เครื่องมือวัดออสซิลโลสโคปในการวัดสัญญาณ **I/O** เพื่อให้เห็นพฤติกรรมการทำงานของ **PIO** เช่น การเปลี่ยนแปลงของสัญญาณและการตอบสนองในเชิงเวลาที่เกิดจากการทำงานคำสั่งพื้นฐานของ **PIO**
