บอร์ด Raspberry Pi Pico
บอร์ด Raspberry Pi Pico ใช้ตัวประมวลผลแบบ 32 บิต ภายในมีซีพียู Arm Cortex-M0+ Dual-Core และทีมพัฒนาได้มีการเปิดตัวบอร์ดดังกล่าวในเดือนมกราคม พ.ศ. 2564 ผู้ใช้สามารถเขียนโปรแกรมในเบื้องต้น โดยใช้ภาษา C และ MicroPython
บริษัท Adafruit ได้พัฒนาบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป RP2040 SoC เช่นเดียวกัน และได้พัฒนา CircuirPython v6.x ให้รองรับการใช้งานตัวประมวลผลดังกล่าวด้วย และผู้ที่สนใจสามารถดาวน์โหลดไฟล์ .UF2 จากเว็บไซต์ต่อไปนี้เพื่อนำมาติดตั้ง CircuitPython สำหรับ Raspberry Pi Pico
ตัวอย่างไฟล์ที่ได้นำมาทดลองใช้งานจากเว็บ https://circuitpython.org/board/raspberry_pi_pico/
adafruit-circuitpython-raspberry_pi_pico-en_US-6.2.0-rc.0.uf2
การติดตั้งก็ทำได้ง่าย เริ่มต้นโดยการกดปุ่ม BOOTSEL บนบอร์ดค้างไว้ แล้วเสียบสาย USB เชื่อมต่อกับคอมพิวเตอร์ จากนั้นปล่อยปุ่มดังกล่าว เพื่อให้อุปกรณ์เริ่มทำงานและเข้าสู่โหมด USB UF2 Bootloader จากนั้นจะมองเห็นไดรฟ์ใหม่ปรากฏขึ้น (RPI-RP2 ) จากนั้นให้คลิกเลือกและลากไฟล์ .UF2 ไปยังไดรฟ์ดังกล่าว
เมื่อได้ติดตั้งไฟล์เฟิร์มแวร์สำหรับ CircuitPython ได้สำเร็จแล้ว บอร์ด Pico จะเริ่มต้นทำงานใหม่อีกครั้งและปรากฏไดรฟ์ใหม่ชื่อว่า CIRCUITPY และพร้อมใช้งาน ผู้ใช้สามารถเปิดใช้งานโปรแกรมอย่างเช่น Thonny IDE เชื่อมต่อกับ CircuitPython ผ่านทาง Serial
เมื่อเริ่มต้นทำงาน 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
โค้ดตัวอย่างที่ 1: Onboard LED Blink
ลองแก้ไขโค้ดในไฟล์ code.py
ตามตัวอย่างต่อไปนี้
Copy 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
ของ CircuitPython
ให้บันทึกการแก้ไขโค้ดลงไฟล์ดังกล่าวโดยกดปุ่ม Ctrl+S จากนั้นกดปุ่ม Ctrl+D เพื่อให้โค้ดดังกล่าวเริ่มทำงานใหม่ ซึ่งจะทำให้ LED (ขา GP25) บนบอร์ดไมโครคอนโทรลเลอร์กระพริบต่อเนื่องไป ถ้าต้องการหยุดการทำงานของโค้ดดังกล่าว ให้กดปุ่ม Ctrl+C (สองครั้ง) ในหน้าต่าง Shell
โค้ดตัวอย่างที่ 2: Push Button
ถ้ดไปเป็นการลองอ่านสถานะอินพุตจากขา GP15 ที่มีการต่อวงจรปุ่มกดภายนอกแบบ Active-Low และมีการทำคำสั่งซ้ำ เพื่อตรวจสอบสถานะของอินพุตจากปุ่มกด ถ้ามีการกดปุ่ม จะได้สถานะเป็น Low หรือ 0 และทำให้ LED หยุดกระพริบ และจบการทำงานของโค้ด
Copy 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
ลองเปลี่ยนมาสร้างสัญญาณแบบ PWM (Pulse Width Modulation) โดยใช้คลาส PWMOut
ของโมดูล pwmio
ตามตัวอย่างต่อไปนี้
Copy 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 ) แล้วเกิดซ้ำไปเรื่อย ๆ
ความสว่าง (LED Brightness ) เปลี่ยนแปลงได้ (ตามฟังก์ชันรูปไซน์) และเกิดจากการปรับค่าความกว้างหรือ Duty Cycle ของสัญญาณพัลส์แบบ PWM ที่มีค่าอยู่ในช่วง 0..65535 หรือ 16 บิต และตั้งค่าความถี่ไว้ที่ 500Hz
โค้ดตัวอย่างที่ 4 : Rotary Encoder Inputs
ตัวอย่างถัดไปเป็นการใช้งานโมดูล Rotary Encoder ที่ให้สัญญาณดิจิทัลแบบพัลส์จำนวน 2 ช่อง (A และ B ) เมื่อมีการหมุนเกิดขึ้น จะทำให้เกิดสัญญาณพัลส์ทั้งสองช่อง และสามารถตรวจสอบทิศทางการหมุนและการเปลี่ยนแปลงตำแหน่งตามจำนวนสเต็ป (Steps ) ที่เกิดขึ้นได้
ผู้พัฒนา CircuitPython ได้สร้างคลาส IncrementalEncoder
ของโมดูล rotaryio
ไว้สำหรับการใช้งานในลักษณะนี้แล้ว มาดูตัวอย่างการเขียนโค้ดดังนี้
Copy 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
และได้มีการสร้างอ็อปเจกต์จากคลาส rotaryio.IncrementalEncoder
และใช้ตัวแปร encoder
ในการอ้างอิง ถ้าต้องการอ่านค่าของตัวนับที่ระบุตำแหน่งในขณะนั้น ก็ให้อ่านค่าจาก encoder.position
หรือจะเขียนค่าลงในตัวแปรดังกล่าวก็ได้ ในตัวอย่างนี้มีการตรวจสอบค่าโดยการอ่านค่าซ้ำไปเรื่อย ๆ เว้นระยะเวลาประมาณ 0.1 วินาที และมีการจำกัดค่าให้อยู่ในช่วง 0..100
โค้ดตัวอย่างที่ 5: NeoPixel RGB LED - Adjust Brightness
ตัวอย่างถัดไปเป็นการนำโมดูล NeoPixel / WS2812B RGB LEDs (มีจำนวน 8 ตำแหน่ง หรือ จำนวนพิกเซล NUM_PIXELS
เท่ากับ 8) มาต่อเพิ่ม ที่ขา GP18 ของบอร์ด Pico โดยใช้โมดูล Rotary Encoder เป็นอุปกรณ์อินพุตเพื่อใช้ในการปรับระดับความสว่างของ LEDs (เลือกแสงสีแดง)
Copy 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
สำหรับการใช้งานโมดูล NeoPixel จะต้องติดตั้งไลบรารีเพิ่ม โดยใช้ไฟล์ที่มีชื่อว่า neopixel.mpy
ซึ่งสามารถดาวน์โหลดได้จาก https://circuitpython.org/libraries (อยู่ในไฟล์ .zip
รวมกับไฟล์ต่าง ๆ ที่เป็นไลบรารีของ CircuitPython ) แล้วนำไปใส่ลงในไดเรกทอรี lib
ภายในไดรฟ์ CIRCUITPY (Flash Storage ) ของบอร์ด Pico
ข้อสังเกต : โมดูล NeoPixel / WS2812B โดยทั่วไปแล้ว จะใช้แรงดันไฟเลี้ยง +5V แต่ก็สามารถใช้ +3.3V ได้เช่นกัน
โค้ดตัวอย่างที่ 6: NeoPixel RGB LEDs - Turn On/Off Pixels
ตัวอย่างถัดไปสาธิตการใช้งานโมดูล Rotary Encoder กับโมดูล NeoPixel ในลักษณะที่แตกต่างจากตัวอย่างที่แล้ว โดยใช้ตำแหน่งที่ได้จาก Rotary Encoder มากำหนดจำนวนของ RGB LED ของโมดูล NeoPixel ที่ให้แสงสีแดง ซึ่งจะอยู่ระหว่าง 0 ถึง 8 (RGB LED ในทุกตำแหน่ง ให้แสงสีแดง)
Copy 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
ตัวอย่างถัดไปเป็นการสาธิตการใช้งานโมดูล SH1106 I2C OLED ขนาด 128 x 64 พิกเซล โดยเชื่อมต่อผ่านบัส I2C ความเร็ว 400kHz และเลือกใช้ขา GP18 และ GP19 ของบอร์ด Pico สำหรับขาสัญญาณ SDA และ SCL ของบัส I2C
Copy 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
ด้วย
ไฟล์ sh1106.py
Copy # 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)] )
######################################################################
โค้ดตัวอย่างที่ 8: DHT22 Temperature & Humidity Sensor
ถัดไปเป็นตัวอย่างการอ่านค่าอุณหภูมิและความชื้นสัมพัทธ์จากโมดูล DHT22 และนำมาแสดงเป็นข้อความบนโมดูล SH1106 I2C OLED
โมดูล DHT22 ใช้แรงดันไฟเลี้ยง +3.3V และในการต่อวงจร ขา DATA ของโมดูลนี้ ต่อเข้ากับขา GP16 ของบอร์ด Pico
การเขียนโค้ด CircuitPython เพื่อใช้งานโมดูล DHT22 ก็ทำได้ไม่ยาก เนื่องจากมีไลบรารีไว้ให้ใช้งาน แต่จะต้องเพิ่มไฟล์ adafruit_dht.mpy
(หาได้จาก CircuitPython Library Bundle )
Copy 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')
กล่าวสรุป
เนื้อหาในส่วนนี้แนะนำการใช้งาน CircuitPython สำหรับบอร์ด Pico RP2040 ในเบื้องต้น พร้อมโค้ดตัวอย่างเพื่อทดสอบการทำงานร่วมกับโมดูลหรือวงจรอื่น
เผยแพร่ภายใต้ลิขสิทธิ์
Attribution-ShareAlike 4.0 International ( CC BY-SA 4.0 )