> 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/circuitpython/circuitpython-for-pico-rp2040.md).

# CircuitPython for Pico RP2040

## บอร์ด Raspberry Pi Pico

บอร์ด **Raspberry Pi Pico** ใช้ตัวประมวลผลแบบ 32 บิต ภายในมีซีพียู **Arm Cortex-M0+ Dual-Core** และทีมพัฒนาได้มีการเปิดตัวบอร์ดดังกล่าวในเดือนมกราคม พ.ศ. 2564 ผู้ใช้สามารถเขียนโปรแกรมในเบื้องต้น โดยใช้ภาษา **C** และ **MicroPython**&#x20;

บริษัท **Adafruit** ได้พัฒนาบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป **RP2040 SoC** เช่นเดียวกัน และได้พัฒนา **CircuirPython v6.x** ให้รองรับการใช้งานตัวประมวลผลดังกล่าวด้วย และผู้ที่สนใจสามารถดาวน์โหลดไฟล์ **.UF2** จากเว็บไซต์ต่อไปนี้เพื่อนำมาติดตั้ง **CircuitPython** สำหรับ **Raspberry Pi Pico**

ตัวอย่างไฟล์ที่ได้นำมาทดลองใช้งานจากเว็บ [**https://circuitpython.org/board/raspberry\_pi\_pico/**](https://circuitpython.org/board/raspberry_pi_pico/)&#x20;

&#x20;`adafruit-circuitpython-raspberry_pi_pico-en_US-6.2.0-rc.0.uf2`

<br>

![รูป: เว็บไซต์สำหรับดาวน์โหลดไฟล์ CircuitPython Firmware (.UF2)](/files/-MXXRAfRfD2Ym8GCjrkh)

การติดตั้งก็ทำได้ง่าย เริ่มต้นโดยการกดปุ่ม **BOOTSEL** บนบอร์ดค้างไว้ แล้วเสียบสาย **USB** เชื่อมต่อกับคอมพิวเตอร์ จากนั้นปล่อยปุ่มดังกล่าว เพื่อให้อุปกรณ์เริ่มทำงานและเข้าสู่โหมด **USB UF2 Bootloader** จากนั้นจะมองเห็นไดรฟ์ใหม่ปรากฏขึ้น (**RPI-RP2**) จากนั้นให้คลิกเลือกและลากไฟล์ **.UF2**  ไปยังไดรฟ์ดังกล่าว

เมื่อได้ติดตั้งไฟล์เฟิร์มแวร์สำหรับ **CircuitPython** ได้สำเร็จแล้ว บอร์ด **Pico** จะเริ่มต้นทำงานใหม่อีกครั้งและปรากฏไดรฟ์ใหม่ชื่อว่า **CIRCUITPY** และพร้อมใช้งาน ผู้ใช้สามารถเปิดใช้งานโปรแกรมอย่างเช่น **Thonny IDE** เชื่อมต่อกับ **CircuitPython** ผ่านทาง **Serial**

![รูป: เลือก CircuitPython (generic) สำหรับ Interpreter ใน Thonny IDE ](/files/-MXX_DdcnLYrAXoCVM_k)

![รูป: การเชื่อมต่อกับบอร์ดไมโครคอนโทรลเลอร์และเปิดไฟล์ code.py ](/files/-MXX_9-VfuOngpwgeVde)

เมื่อเริ่มต้นทำงาน **CircuitPython** จะมองเห็นไฟล์ `code.py` ใน **Flash File Storage** ของบอร์ด **Pico** หรือไม่ ถ้ามีไฟล์ดังกล่าว **CircuitPython** ก็จะทำคำสั่งของโค้ดไพธอนภายในไฟล์ดังกล่าว

ในส่วนของ **Shell** จะมีข้อความเริ่มต้นซึ่งแสดงข้อมูลเกี่ยวกับเวอร์ชันของ **CircuitPython** ดังนี้

`Adafruit CircuitPython 6.2.0-rc.0 on 2021-04-01; Raspberry Pi Pico with rp2040`&#x20;

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

ลองแก้ไขโค้ดในไฟล์ `code.py` ตามตัวอย่างต่อไปนี้

```python
import board
import digitalio
import time

# use the onboard LED pin as output
LED_PIN = board.GP25 # the same as board.LED
led = digitalio.DigitalInOut(LED_PIN)
led.direction = digitalio.Direction.OUTPUT
 
while True:
    led.value = not led.value # toggle the LED output
    time.sleep(0.5) # sleep for 0.5 seconds

led.deinit() # turn off and release the LED pin
```

การทำงานของโค้ดนี้สาธิตการทำให้ **LED** ที่ขา **GP25** กระพริบได้ โดยการเปิดใช้งานขา **GPIO** ดังกล่าวในทิศทางเอาต์พุต และใช้คำสั่งจากคลาส `DigitalInOut` จากโมดูล [`digitalio`](https://circuitpython.readthedocs.io/en/latest/shared-bindings/digitalio/index.html) ของ **CircuitPython**

ให้บันทึกการแก้ไขโค้ดลงไฟล์ดังกล่าวโดยกดปุ่ม **Ctrl+S** จากนั้นกดปุ่ม **Ctrl+D** เพื่อให้โค้ดดังกล่าวเริ่มทำงานใหม่ ซึ่งจะทำให้ **LED (ขา GP25)** บนบอร์ดไมโครคอนโทรลเลอร์กระพริบต่อเนื่องไป  ถ้าต้องการหยุดการทำงานของโค้ดดังกล่าว ให้กดปุ่ม **Ctrl+C** (สองครั้ง) ในหน้าต่าง **Shell**&#x20;

## โค้ดตัวอย่างที่ 2: Push Button&#x20;

ถ้ดไปเป็นการลองอ่านสถานะอินพุตจากขา **GP15** ที่มีการต่อวงจรปุ่มกดภายนอกแบบ **Active-Low** และมีการทำคำสั่งซ้ำ เพื่อตรวจสอบสถานะของอินพุตจากปุ่มกด ถ้ามีการกดปุ่ม จะได้สถานะเป็น **Low** หรือ **0** และทำให้ **LED** หยุดกระพริบ และจบการทำงานของโค้ด&#x20;

```python
import board
import digitalio
import time

led = digitalio.DigitalInOut(board.GP25)
led.direction = digitalio.Direction.OUTPUT

button = digitalio.DigitalInOut(board.GP15)
button.switch_to_input(pull=digitalio.Pull.UP)
 
while True:
    if button.value == 0:
        print('Button pressed...')
        break
    led.value = not led.value
    time.sleep(0.2)

led.deinit()
button.deinit()
```

## โค้ดตัวอย่างที่ 3: PWM-based LED Dimming&#x20;

ลองเปลี่ยนมาสร้างสัญญาณแบบ **PWM (Pulse Width Modulation)** โดยใช้คลาส `PWMOut` ของโมดูล `pwmio` ตามตัวอย่างต่อไปนี้

```python
import board
import digitalio
import pwmio
import time
import math

pwm_led = pwmio.PWMOut(board.GP25, frequency=500,
             duty_cycle=0, variable_frequency=False)
 
button = digitalio.DigitalInOut(board.GP15)
button.switch_to_input(pull=digitalio.Pull.UP)

index = 0
N = 32
k = (2*math.pi/N)
while True:
    if button.value == 0:
        print('Button pressed...')
        break
    value = (1 + math.sin(k*index))/2
    pwm_led.duty_cycle = (int)((2**16-1)*value)
    index = (index+1) % N
    time.sleep(0.05)

pwm_led.deinit()
button.deinit()
```

เมื่อโค้ดทำงาน สามารถสังเกตเห็นได้ว่า **LED** จะค่อย ๆ สว่างขึ้นแล้วดับลงอย่างช้า ๆ (**LED Dimming**) แล้วเกิดซ้ำไปเรื่อย ๆ&#x20;

ความสว่าง (**LED Brightness**) เปลี่ยนแปลงได้ (ตามฟังก์ชันรูปไซน์) และเกิดจากการปรับค่าความกว้างหรือ **Duty Cycle** ของสัญญาณพัลส์แบบ **PWM** ที่มีค่าอยู่ในช่วง **0..65535** หรือ 16 บิต และตั้งค่าความถี่ไว้ที่ **500Hz**

## โค้ดตัวอย่างที่ **4**: Rotary Encoder Inputs&#x20;

ตัวอย่างถัดไปเป็นการใช้งานโมดูล **Rotary Encoder** ที่ให้สัญญาณดิจิทัลแบบพัลส์จำนวน 2 ช่อง (**A** และ **B**) เมื่อมีการหมุนเกิดขึ้น จะทำให้เกิดสัญญาณพัลส์ทั้งสองช่อง และสามารถตรวจสอบทิศทางการหมุนและการเปลี่ยนแปลงตำแหน่งตามจำนวนสเต็ป (**Steps**) ที่เกิดขึ้นได้

ผู้พัฒนา **CircuitPython** ได้สร้างคลาส `IncrementalEncoder` ของโมดูล `rotaryio` ไว้สำหรับการใช้งานในลักษณะนี้แล้ว มาดูตัวอย่างการเขียนโค้ดดังนี้

```python
import time
import board
import rotaryio

print('Rotary Encoder demo...')
rotary_pins = (board.GP16, board.GP17)
encoder = rotaryio.IncrementalEncoder( *rotary_pins )
last_position = None
try:
    while True:
        if encoder.position < 0:
            encoder.position = 0
        elif encoder.position > 100:
            encoder.position = 100
        position = encoder.position
        if last_position is None or position != last_position:
            print(position)
        last_position = position
        time.sleep(0.1)
except KeyboardInterrupt:
    print('Terminated...')
encoder.deinit()
del encoder
```

ในโค้ดตัวอย่างนี้ มีการเลือกใช้ขา **GP16** และ **GP17** สำหรับสัญญาณอินพุตของ **Incremental Rotary Encoder** ซึ่งใช้แรงดันไฟเลี้ยง **3.3V**&#x20;

และได้มีการสร้างอ็อปเจกต์จากคลาส `rotaryio.IncrementalEncoder` และใช้ตัวแปร `encoder` ในการอ้างอิง ถ้าต้องการอ่านค่าของตัวนับที่ระบุตำแหน่งในขณะนั้น ก็ให้อ่านค่าจาก `encoder.position` หรือจะเขียนค่าลงในตัวแปรดังกล่าวก็ได้ ในตัวอย่างนี้มีการตรวจสอบค่าโดยการอ่านค่าซ้ำไปเรื่อย ๆ เว้นระยะเวลาประมาณ 0.1 วินาที และมีการจำกัดค่าให้อยู่ในช่วง **0..100**&#x20;

## โค้ดตัวอย่างที่ 5: NeoPixel RGB LED - Adjust Brightness&#x20;

ตัวอย่างถัดไปเป็นการนำโมดูล **NeoPixel / WS2812B RGB LEDs** (มีจำนวน 8 ตำแหน่ง หรือ จำนวนพิกเซล `NUM_PIXELS` เท่ากับ 8) มาต่อเพิ่ม ที่ขา **GP18** ของบอร์ด **Pico** โดยใช้โมดูล **Rotary Encoder** เป็นอุปกรณ์อินพุตเพื่อใช้ในการปรับระดับความสว่างของ **LEDs** (เลือกแสงสีแดง)

```python
import time
import board
import rotaryio
import neopixel # requires the CircuitPython Neopixel library

print('Rotary Encoder + NeoPixel demo...')
pixel_pin   = board.GP18
rotary_pins = (board.GP16, board.GP17)
NUM_PIXELS  = 8
color = (255,0,0) # use red color

pixels = neopixel.NeoPixel(pixel_pin,
    NUM_PIXELS, brightness=0.0, auto_write=False)

encoder = rotaryio.IncrementalEncoder( *rotary_pins )
last_position = None
try:
    pixels.fill( color ) # use red color
    pixels.show()
    while True:
        if encoder.position < 0:
            encoder.position = 0
        elif encoder.position > 100:
            encoder.position = 100
        position = encoder.position
        if last_position is None or position != last_position:
            pixels.brightness = position/100.0
            pixels.show()
            print(position)
        last_position = position
        time.sleep(0.1)
except KeyboardInterrupt:
    print('Terminated...')
pixels.deinit()
encoder.deinit()
del encoder, pixels
```

ค่าของตัวนับที่อ่านได้อยู่ในช่วง **0..100** จะถูกนำมาหารด้วย **100.0** เพื่อให้ได้ตัวเลขในช่วง **0.0** ถึง **1.0** และใช้กำหนดระดับความสว่างของโมดูล **NeoPixel**&#x20;

สำหรับการใช้งานโมดูล **NeoPixel** จะต้องติดตั้งไลบรารีเพิ่ม โดยใช้ไฟล์ที่มีชื่อว่า `neopixel.mpy` ซึ่งสามารถดาวน์โหลดได้จาก <https://circuitpython.org/libraries> (อยู่ในไฟล์ `.zip` รวมกับไฟล์ต่าง ๆ ที่เป็นไลบรารีของ **CircuitPython**) แล้วนำไปใส่ลงในไดเรกทอรี `lib` ภายในไดรฟ์ **CIRCUITPY** (**Flash Storage**) ของบอร์ด **Pico**&#x20;

![](/files/-MXZuTQ74HkRHP7mrrxV)

![](/files/-MX_ght0AB0lXVbaivR7)

**ข้อสังเกต**: โมดูล **NeoPixel / WS2812B** โดยทั่วไปแล้ว จะใช้แรงดันไฟเลี้ยง **+5V** แต่ก็สามารถใช้ **+3.3V** ได้เช่นกัน&#x20;

## โค้ดตัวอย่างที่ 6:  NeoPixel RGB LEDs - Turn On/Off Pixels

ตัวอย่างถัดไปสาธิตการใช้งานโมดูล **Rotary Encoder** กับโมดูล **NeoPixel** ในลักษณะที่แตกต่างจากตัวอย่างที่แล้ว โดยใช้ตำแหน่งที่ได้จาก **Rotary Encoder**  มากำหนดจำนวนของ **RGB LED** ของโมดูล **NeoPixel** ที่ให้แสงสีแดง ซึ่งจะอยู่ระหว่าง 0 ถึง 8 (**RGB LED** ในทุกตำแหน่ง ให้แสงสีแดง)

```python
import time
import board
import rotaryio
import neopixel # requires the CircuitPython Neopixel library

print('Rotary Encoder + NeoPixel demo...')
pixel_pin   = board.GP18
rotary_pins = (board.GP16, board.GP17)
NUM_PIXELS  = 8
color = (255,0,0) # use red color

pixels = neopixel.NeoPixel(pixel_pin,
    NUM_PIXELS, brightness=1.0, auto_write=False)

encoder = rotaryio.IncrementalEncoder( *rotary_pins )
last_position = None

def set_color(level):
    global pixels
    pixels[0:NUM_PIXELS] = level*[color] + (NUM_PIXELS-level)*[(0,0,0)] 
    pixels.show()

try:
    set_color(0)
    while True:
        if encoder.position < 0:
            encoder.position = 0
        elif encoder.position > NUM_PIXELS:
            encoder.position = NUM_PIXELS
        position = encoder.position
        if last_position is None or position != last_position:
            set_color(position)
            print(position)
        last_position = position
        time.sleep(0.1)
except KeyboardInterrupt:
    print('Terminated...')
pixels.deinit()
encoder.deinit()
del encoder, pixels
```

## โค้ดตัวอย่างที่ 7: SH1106 I2C OLED Display&#x20;

ตัวอย่างถัดไปเป็นการสาธิตการใช้งานโมดูล **SH1106 I2C OLED** ขนาด **128 x 64** พิกเซล โดยเชื่อมต่อผ่านบัส **I2C** ความเร็ว **400kHz** และเลือกใช้ขา **GP18** และ **GP19** ของบอร์ด Pico สำหรับขาสัญญาณ **SDA** และ **SCL** ของบัส **I2C**&#x20;

```python
import time
import board
import busio
from sh1106 import SH1106_I2C

i2c_pins = (board.GP19,board.GP18)
i2c = busio.I2C( scl=i2c_pins[0], sda=i2c_pins[1], frequency=400000 )
while not i2c.try_lock():
    pass
# scan I2C devices
print( [hex(x) for x in i2c.scan()] )

WIDTH  = 128
HEIGHT = 64
disp = SH1106_I2C( WIDTH, HEIGHT, i2c, 0x3c )
disp.poweron()
disp.framebuf.rect(0, 0, WIDTH, HEIGHT//2, 1) # draw a frame
m = 4
disp.framebuf.fill_rect(m, m, WIDTH-2*m, HEIGHT//2-2*m, 1) # fill area
disp.framebuf.text("CircuitPython", 24, 12, 0) # show text 
disp.framebuf.text(" Pico RP2040 ", 24, 42, 1) # show text 
disp.framebuf.text("SH1106 I2C LCD", 20, 54, 1) # show text 
disp.show()
invert = 0
for i in range(5):
    disp.invert( invert ) # invert display
    invert = not invert
    time.sleep(1.0)
disp.poweroff()
i2c.unlock()
i2c.deinit()
print('done')
```

นอกจากนั้นได้สร้างไฟล์ `sh1106.py` เพื่อใช้งานเป็นไลบรารีสำหรับการใช้งานโมดูล **SH1106** ในเบื้องต้น การทำงานของคลาส `SH1106` ต้องใช้งานร่วมกับ `FrameBuffer` จากโมดูล `adafruit_framebuf` ดังนั้นจึงต้องติดตั้งไฟล์ `adafruit_framebuf.mpy` ลงใน`/lib` และไฟล์ `font5x8.bin` ด้วย&#x20;

ไฟล์ `sh1106.py`

```python
# This is a modified version of the SH1106 MicroPython library
# https://github.com/robert-hh/SH1106/blob/master/sh1106.py

from micropython import const
import adafruit_framebuf as framebuf

# dependencies:
# https://github.com/adafruit/Adafruit_CircuitPython_framebuf
# https://github.com/adafruit/Adafruit_CircuitPython_framebuf/blob/master/examples/font5x8.bin

_SET_CONTRAST        = const(0x81)
_SET_NORM_INV        = const(0xa6)
_SET_DISP            = const(0xae)
_SET_SCAN_DIR        = const(0xc0)
_SET_SEG_REMAP       = const(0xa0)
_LOW_COLUMN_ADDRESS  = const(0x00)
_HIGH_COLUMN_ADDRESS = const(0x10)
_SET_PAGE_ADDRESS    = const(0xB0)

class SH1106_I2C:
    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
        self.width  = width
        self.height = height
        self.i2c    = i2c
        self.addr   = addr
        self.external_vcc = external_vcc
        self.pages  = self.height//8
        self.buffer = bytearray(self.pages * self.width)
        fb = framebuf.FrameBuffer(self.buffer, self.width, self.height,
                                  framebuf.MVLSB)
        self.framebuf = fb     
        self.framebuf.fill(0) # fill with black color
        self.show()

    def write_cmd(self, cmd):
        buf = bytearray(2)
        buf[0] = 0x80  # Co=1, D/C#=0
        buf[1] = cmd
        self.i2c.writeto(self.addr, buf)

    def write_data(self,buf):
        self.i2c.writeto(self.addr, b'\x40'+buf)

    def contrast(self, contrast):
        self.write_cmd(_SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(_SET_NORM_INV | (invert & 1))

    def rotate(self, flag, update=True):
        if flag:
            self.write_cmd(_SET_SEG_REMAP | 0x01)  # mirror display vertically
            self.write_cmd(_SET_SCAN_DIR  | 0x08)  # mirror display horizontally
        else:
            self.write_cmd(_SET_SEG_REMAP | 0x00)
            self.write_cmd(_SET_SCAN_DIR  | 0x00)
        if update:
            self.show()

    def poweroff(self):
        self.write_cmd(_SET_DISP | 0x00)

    def poweron(self):
        self.write_cmd(_SET_DISP | 0x01)

    def show(self):
        for page in range(self.pages):
            self.write_cmd(_SET_PAGE_ADDRESS | page)
            self.write_cmd(_LOW_COLUMN_ADDRESS  | 2)
            self.write_cmd(_HIGH_COLUMN_ADDRESS | 0)
            self.write_data(
                self.buffer[ self.width*page : self.width*(page+1)] )

######################################################################
```

![](/files/-MX_guUPm8WlinU_WTDg)

## โค้ดตัวอย่างที่ 8: DHT22 Temperature & Humidity Sensor

ถัดไปเป็นตัวอย่างการอ่านค่าอุณหภูมิและความชื้นสัมพัทธ์จากโมดูล **DHT22** และนำมาแสดงเป็นข้อความบนโมดูล **SH1106 I2C OLED**&#x20;

โมดูล **DHT22** ใช้แรงดันไฟเลี้ยง **+3.3V** และในการต่อวงจร ขา **DATA** ของโมดูลนี้ ต่อเข้ากับขา **GP16** ของบอร์ด **Pico**&#x20;

การเขียนโค้ด **CircuitPython** เพื่อใช้งานโมดูล **DHT22** ก็ทำได้ไม่ยาก เนื่องจากมีไลบรารีไว้ให้ใช้งาน แต่จะต้องเพิ่มไฟล์ `adafruit_dht.mpy` (หาได้จาก [**CircuitPython Library Bundle**](https://circuitpython.org/libraries))&#x20;

```python
import time
import board
import busio
from sh1106 import SH1106_I2C
from adafruit_dht import DHT22

dht = DHT22(board.GP16)

i2c_pins = (board.GP19,board.GP18)
i2c = busio.I2C( scl=i2c_pins[0], sda=i2c_pins[1], frequency=400000 )
while not i2c.try_lock():
    pass
# scan I2C devices
print( [hex(x) for x in i2c.scan()] )

WIDTH  = 128
HEIGHT = 64
disp = SH1106_I2C( WIDTH, HEIGHT, i2c, 0x3c )
disp.poweron()
disp.framebuf.rect(0, 0, WIDTH, HEIGHT, 1) # draw a frame

while True:
    try:
        dht.measure()
        disp.framebuf.fill_rect(1, 1, WIDTH-2, HEIGHT-2, 0)
        text = " Temperature: {:.1f}".format(dht.temperature)
        print(text)
        disp.framebuf.text(text, 4, 20, 1) # show text
        text = " Rel.Humdity: {:.1f}".format(dht.humidity)
        print(text)
        disp.framebuf.text(text, 4, 40, 1) # show text
        disp.show()
        time.sleep(2.0)
    except RuntimeError as ex:
        pass # DHT checksum error
    except KeyboardInterrupt:
        break

dht.exit()
i2c.unlock()
i2c.deinit()
print('done')
```

![](/files/-MXaE_lpz1anLeqr1rv4)

## กล่าวสรุป

เนื้อหาในส่วนนี้แนะนำการใช้งาน **CircuitPython** สำหรับบอร์ด **Pico RP2040** ในเบื้องต้น พร้อมโค้ดตัวอย่างเพื่อทดสอบการทำงานร่วมกับโมดูลหรือวงจรอื่น&#x20;

{% hint style="info" %}
**เผยแพร่ภายใต้ลิขสิทธิ์**\
**Attribution-ShareAlike 4.0 International (**[**CC BY-SA 4.0**](https://creativecommons.org/licenses/by-sa/4.0/)**)**
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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/circuitpython/circuitpython-for-pico-rp2040.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.
