# 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**


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://think-embedded.gitbook.io/micropython/micropython-for-rp2040-pico/pio-signaling-and-measurement.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
