Micro:bit Code Examples

ตัวอย่างโค้ดไมโครไพธอนสำหรับบอร์ดไมโครบิต

การเรียนรู้จากตัวอย่างโค้ด ก็เป็นวิธีหนึ่งที่ช่วยทำให้รู้จักคำสั่งต่าง ๆ ของไมโครไพธอนสำหรับไมโครบิตได้ง่ายขึ้น ลองมาดูตัวอย่างโค้ดที่สามารถนำไปทดลองกับบอร์ดไมโครบิตได้

ตัวอย่างโค้ดไมโครไพธอนสำหรับไมโครบิตในเอกสารนี้ สามารถนำไปใช้ได้กับ MicroPython Online Editor แต่ในบางกรณีแนะนำให้ลองใช้ซอฟต์แวร์ที่เป็น Offline Editor เช่น Mu Editor หรือ Thonny IDE เป็นต้น

โค้ดตัวอย่างที่ 1: การแสดงข้อความและสัญลักษณ์บน 5x5 LED Display

การทำงานของโค้ดตัวอย่างนี้ เริ่มต้นด้วยการแสดงข้อความเป็นภาษาอังกฤษ "Hello, World!" ด้วยคำสั่ง display.scroll() (ทำเพียงครั้งเดียว) เนื่องจากมีหลายตัวอักษร จึงใช้วิธีแสดงทีละตัว แล้วเลื่อนจากขวาไปซ้าย จนครบตัวอักษรสุดท้ายของข้อความ

ในการแสดงข้อความแบบเลื่อนไป โดยใช้คำสั่ง display.scroll() เราสามารถกำหนดช่วงเวลาในการอัปเดตสกรีน 5x5 LED Display ได้ด้วย เช่น 100 (หน่วยเป็นมิลลิวินาที) ถ้าต้องการให้เกิดการเปลี่ยนแปลงช้าลง ก็ให้เพิ่มค่าตัวเลข

from microbit import *
# display a text message
display.scroll('Hello, World!', 100 )
while True:
    # display the Heart icon (5x5 pixels)
    display.show(Image.HEART)
    # wait for 1 second before proceeding
    sleep(1000)
    # clear the display
    display.clear()
    sleep(1000)

ถัดไปมีประโยคคำสั่ง while เพื่อทำขั้นตอนซ้ำ ได้แก่ การแสดงรูปสัญลักษณ์ 'หัวใจ' หรือImage.HEART โดยใช้คำสั่ง display.show() และให้เว้นช่วงเวลาประมาณ 1000 มิลลิวินาที โดยใช้คำสั่ง sleep() ตามด้วยคำสั่ง display.clear() เพื่อเคลียร์การแสดงผล

ข้อสังเกต: คำสั่งต่าง ๆ ที่เกี่ยวข้องกับไมโครไพธอนสำหรับไมโครบิต จะอยู่ภายใต้ชื่อของแพคเกจหรือโมดูลชื่อ microbit ดังนั้นในบรรทัดแรกของโค้ด จึงมีคำสั่ง from microbit import *

ถ้าต้องการลองใช้รูปไอคอนของไมโครไพธอนที่ได้มีการกำหนดไว้แล้ว (Built-in Images) ก็สามารถดูได้จากเอกสารในหัวข้อ Images ตัวอย่างเช่น รูปกราฟิกแสดงอารมณ์ความรู้สึก

  • Image.HAPPY (รู้สึกมีความสุข)

  • Image.SMILE (รูปหน้ายิ้ม)

  • Image.SAD (รู้สึกเศร้า)

  • Image.CONFUSED (รู้สึกสับสน)

  • Image.ANGRY (รู้สึกโกรธ)

  • Image.ASLEEP (รู้สึกง่วงนอน)

  • Image.SURPRISED (รู้สึกประหลาดใจ)

โค้ดต่อไปนี้ สาธิตการแสดงรูปสัญลักษณ์ โดยระบุไว้ในอาร์เรย์ (Array) หรือ List ในภาษาไพธอน และจะเลือกมาแสดงผลทีละรูปตามลำดับ แล้ววนซ้ำไปเรื่อย ๆ

from microbit import *

images_list = [ Image.HAPPY, Image.SMILE,
    Image.SAD, Image.CONFUSED, Image.ANGRY,
    Image.ASLEEP, Image.SURPRISED ]

while True:
    for image in images_list:
        display.show( image )
        sleep(1000)
        display.clear()
        sleep(1000)

เราสามารถกำหนดรูปกราฟิกขนาด 5x5 พิกเซลได้เอง (User-defined Images) สำหรับนำไปแสดงผลบน LED Display โดยใช้คำสั่ง Image() และ display.show()

การใช้คำสั่ง Image() เป็นการกำหนดค่าพิกเซลขนาด 5x5 (หนึ่งพิกเซลสำหรับ LED หนึ่งดวง) มีค่าความสว่างได้ 0..9 (0=LED OFF และ 9=สว่างมากที่สุด) ในแต่ละแถว เรียงจากซ้ายไปขวา และเรียงจากแถวบนลงล่าง

ยกตัวอย่าง เช่น ถ้าต้องการสร้างรูปกากบาท (Cross Mark) เราก็กำหนดรูปกราฟิกดังนี้

from microbit import *

# define a cross-mark image 
image_x = Image("90009:09090:00900:09090:90009")

# display the image 
display.show( image_x )

โค้ดตัวอย่างที่ 2: การสุ่มเลข 1-6 เมื่อกดปุ่มหรือเขย่าบอร์ด

ถ้าต้องการทำให้บอร์ดไมโครบิต มีพฤติกรรมการทำงานเหมือน (Electronic Dice) เช่น เมื่อกดปุ่ม A หรือเขย่าบอร์ด (Shaking) จะทำให้เกิดการสุ่มเลข เหมือนการทอยลูกเต๋า จะเขียนโค้ดอย่างไร ?

โค้ดตัวอย่างนี้ เมื่อมีการกดปุ่ม A ในแต่ละครั้ง จะมีการสุ่มเลขที่เป็นจำนวนเต็ม (Integer) ที่จะได้ค่าอยู่ระหว่าง 1 ถึง 6 โดยใช้คำสั่ง random.randint() เก็บไว้ในตัวแปรชื่อ number แล้วจึงนำค่านี้ไปแสดงผล โดยเรียกใช้ฟังก์ชัน showRandomNumber() ที่ได้สร้างขึ้นเอง แต่ถ้ามีการกดปุ่ม B ก็จะเป็นการเคลียร์การแสดงผลบน LED Display ด้วยคำสั่ง display.clear()

ข้อสังเกต: การใช้คำสั่งของ random เพื่อสุ่มเลขนั้น ไม่ใช่การสุ่มเลขที่แท้จริง (True Random Number Generation) แต่ใช้อัลกอริทึมทางคณิตศาสตร์ในการกำหนดตัวเลขในลำดับถัดไป (จึงเป็นแบบ Pseudo-random Number Generation)

from microbit import *
import random
import time 

def showRandomNumber():
    # create a pseudo-random number between 1..6
    number = random.randint(1, 6)
    # display the random number
    display.show( number )

# read analog value from pin A0
seed = pin0.read_analog()
# set the random seed using P0 analog input value
random.seed( seed ) 

while True:
    if button_a.was_pressed():
        showRandomNumber()
    elif button_b.was_pressed():
        display.clear()

เมื่อโปรแกรมเริ่มทำงานในแต่ละครั้ง เราสามารถกำหนดค่าเริ่มต้น (หรือ Seed Value) โดยใช้คำสั่ง random.seed() ก่อนเริ่มใช้คำสั่งของ random สำหรับการสุ่มตัวเลข ในตัวอย่างนี้ ได้ใช้ค่าเลขจำนวนเต็มที่อ่านได้จากขา P0 แบบแอนะล็อกของบอร์ดไมโครบิต (แต่ที่ขา P0 ไม่ได้ต่อใช้งาน) มาใช้เป็นค่าเริ่มต้น

การตรวจสอบเงื่อนไขไปตามลำดับนั้น เราได้ใช้ประโยคคำสั่งแบบ if-elif โดยมีเงื่อนไขในการตรวจสอบ

  • ดูว่า มีการกดปุ่ม A หรือไม่ ซึ่งเราจะใช้คำสั่ง button_a.was_pressed()

  • ถัดไปดูว่า มีการกดปุ่ม B หรือไม่ โดยใช้คำสั่ง button_b.was_pressed()

คำถามสำหรับการทดลองโดยใช้ฮาร์ดแวร์:

ถ้าไม่ได้ต่อขา P0 กับวงจรใด ๆ (ไม่มีการใช้งานกับวงจรภายนอก) และอ่านอินพุตแบบแอนะล็อกจากขา P0 ของบอร์ดไมโครบิต จะได้ค่าที่แตกต่างกันไปหรือไม่ ? ลองมาดูโค้ดตัวอย่าง เมื่อกดปุ่ม A แต่ละครั้ง จะอ่านค่าจากอินพุต P0 เช่น ทั้งหมด 10 ครั้ง

from microbit import *

N = 10
while True:
    if button_a.was_pressed():
        value_strings = []
        for i in range(N):
            # read value from A0 pin 
            value = pin0.read_analog()
            # append the value as a string to the list
            value_strings.append( str( value ) ) 
        # show all values as a string and send to serial
        print( '>', ','.join( value_strings ), '\n' ) 
    sleep(100)

เมื่ออัปโหลดโค้ดไปยังบอร์ด ให้กดปุ่ม Open Serial เพื่อรับค่าจากบอร์ด ซึ่งจะเป็นข้อความที่ถูกส่งกลับมายังคอมพิวเตอร์ โดยใช้คำสั่ง print() แต่ถ้าต้องการปิดการรับข้อมูลจาก Serial ให้กดปุ่ม Close Serial

ลองกดปุ่ม A หลายครั้ง โดยไม่ใช้นิ้วสัมผัสที่ขา P0 ที่บริเวณ Edge Connector และอยู่ใกล้ปุ่ม A) และเปรียบเทียบกับการใช้นิ้วสัมผัส

โค้ดตัวอย่างถัดไป ได้เปลี่ยนจากการแสดงตัวเลข ให้เป็นการแสดงรูปกราฟิกแทน (ใช้จำนวนจุด หรือ Dots ที่มีจำนวนตามตัวเลขที่สุ่มได้) ในตัวอย่างนี้ เราสุ่มเลขที่มีค่าระหว่าง 1 ถึง 6 ไว้ในตัวแปร number แล้วนำค่าที่ได้นี้ (ลบออก 1) ไปใช้อ้างอิง Image จากตัวแปรแบบอาร์เรย์ image_list ซึ่งเริ่มต้นนับที่ index เท่ากับ 0

ในการตรวจสอบเหตุการณ์หรือเงื่อนไข เพื่อดูว่า มีการเขย่าบอร์ดเกิดขึ้นหรือไม่ เราจะใช้คำสั่ง accelerometer.is_gesture() ที่ระบุว่าเป็น "shake" ถ้ามีการเขย่าบอร์ด ให้ตรวจสอบดูเงื่อนไขถัดไปในการอัปเดตค่าสุ่มตัวเลขถัดไปคือ จะต้องเกิดขึ้นหลังจากครั้งที่แล้วอย่างน้อย 1000 มิลลิวินาที (msec)

โค้ดตัวอย่างมีการใช้คำสั่ง time.ticks_ms() เพื่ออ่านค่าเวลาของระบบ (หน่วยเป็น msec) และใช้คำสั่ง time.ticks_diff() เพื่อหาผลต่างระหว่างช่วงเวลา (Time Difference) ผลต่างของค่าตัวเลขเวลาในปัจจุบันกับค่าตัวเวลาในอดีตที่บันทึกเก็บไว้ (ผลต่างระหว่าง now_shake กับ last_shake) จะต้องมีค่ามากกว่า 1000 มิลลิวินาที

from microbit import *
import random, time 

images_list = [
  Image("00000:00000:00900:00000:00000"), # Number=1
  Image("00000:00090:00000:09000:00000"), # Number=2
  Image("00009:00000:00900:00000:90000"), # Number=3
  Image("00000:09090:00000:09090:00000"), # Number=4
  Image("90009:00000:00900:00000:90009"), # Number=5
  Image("09090:00000:09090:00000:09090")] # Number=6

def showRandomNumber():
    # create a pseudo-random number between 1..6
    number = random.randint(1, 6)
    # select the image from list (indexing 0..5)
    image = images_list[ number-1 ]
    # display the selected image (random number)
    display.show( image )
    
last_shake = time.ticks_ms()
while True:
    if accelerometer.is_gesture("shake"):
        now_shake = time.ticks_ms()
        # check the difference timestamp >= 1000 msec
        if time.ticks_diff(now_shake,last_shake) >= 1000:
            last_shake = now_shake
            showRandomNumber()

โค้ดตัวอย่างที่ 3: สุ่มตำแหน่งเพื่อทำให้ LED สว่าง 5 จุด

ตัวอย่างถัดไปสาธิตการสุ่มหาตำแหน่ง (x,y) บน LED Matrix Display ซึ่งจะมีทั้งหมด 5 ตำแหน่งที่ทำให้ LED สว่าง (ON) โดยมีเงื่อนไขว่า ในแต่ละแถวแนวนอนหรือแนวตั้ง จะต้องไม่ซ้ำกัน

ในการกำหนดสถานะ ON/OFF หรือกำหนดค่าความสว่าง (0..9) ให้ LED ในตำแหน่งหรือพิกัดที่ต้องการ เราจะใช้คำสั่ง display.set_pixel() การกดปุ่ม B จะเปลี่ยนโหมดการสุ่มตำแหน่งและอัปเดตไปเรื่อย ๆ (Auto mode) หรือ โหมดที่ต้องกดปุ่ม A เพื่อสุ่มตำแหน่งใหม่ในแต่ละครั้ง (Manual Mode) ในโค้ดตัวอย่างนี้ จะเห็นได้ว่า มีการสาธิตการใช้โครงสร้างข้อมูลในภาษาไพธอน อย่างเช่น เซต (Set) และอาร์เรย์สองมิติ (Two-dimensional Array)

from microbit import *
import random

# create an n x n matrix with random elements from {0,1}
def create_matrix( n ):
    # create a matrix filled with zeros
    m = [n*[0] for i in range(n)] 
    # use a set to keep the row numbers already selected
    selected = set()
    
    # for each column of the matrix
    for col in range(n): 
        while True:
            # randomize a new row number
            row = random.randint(0,n-1)
            if row not in selected: 
               # if the row number is not in the set,
               # add the row number to the set
               selected.add( row )  
               # set the element at (row,col) to 1
               m[row][col] = 1 
               # stop the while loop
               break
    return m

# read analog value from pin A0
seed = pin0.read_analog()
# set the random seed using P0 analog input value
random.seed( seed )

N = 5
autorunning = True
while True:
    if button_b.was_pressed():
        autorunning = not autorunning
    if button_a.is_pressed() or autorunning:
        display.clear()
        pixels = create_matrix(N)
        for r in range(N): # for each row
          for c in range(N): # for each column
            display.set_pixel( c, r, 9*(pixels[r][c]) )
        sleep( 500 )
    else:
        sleep( 100 )

โค้ดตัวอย่างที่ 4: การเอียงบอร์ดเพื่อทำให้ LED สว่างทุกดวง

ตัวอย่างถัดไปสาธิตการทำให้ LED สว่าง ทีละดวง เริ่มต้นจากดวงแรกที่พิกัด (2,2) ซึ่งอยู่ตรงกลางบอร์ด และมีการกระพริบที่ LED เพื่อระบุตำแหน่งพิกัดขณะนั้น

ถ้ามีการเอียงบอร์ดไปในทิศทางใด จะทำให้ตำแหน่งของ LED ที่กระพริบเปลี่ยนไป และ LED ในตำแหน่งก่อนหน้านั้น จะเปลี่ยนจาก OFF (0) เป็น ON (1) และจะเห็นว่า มีจำนวน LED ที่อยู่ในสถานะ ON เพิ่มมากขึ้น

เมื่อทำให้ LED ทุกดวงสว่างครบแล้ว (5*5 = 25 ดวง) โดยการเอียงบอร์ดตามแกนอ้างอิง x หรือ y จะเห็นได้ว่า หลังจากนั้น LED ทุกดวงกระพริบ ถ้าต้องการจะเริ่มต้นใหม่ ให้กดปุ่ม A

การตรวจสอบดูว่า มีการเอียงบอร์ดไปทางใด เราจะใช้คำสั่งของ accelerometer เช่น get_x() และ get_y() อ่านค่าสำหรับแกน x และ y ถ้าวางบอร์ดในแนวราบ ค่าที่อ่านได้สำหรับแกน x และ y จะเข้าใกล้ 0 แต่

ถ้ามีการเอียงบอร์ดไปทางด้านหนึ่ง ค่าจะเพิ่มเป็นบวกมากขึ้น แต่ถ้าเอียงไปอีกด้านหนึ่งค่าจะลดลง ได้ค่าตัวเลขเป็นลบ (น้อยกว่า 0)

from microbit import *

LEVEL = 250
N = 5

def initialize():
    global leds, x, y, last_pos, count_ones 
    # create an array with N*N elements, filled with 0's
    leds = [ 0 for i in range(N*N) ]
    x,y = (N//2,N//2) # center position (x,y)
    last_pos = (x,y)
    leds[N*y+x] = 1   # the center LED is ON
    count_ones  = 1
    display.clear()   # clear display
    display.on()      # turn on display
    display.set_pixel(x,y,9) # the center LED is ON

sleep(1000)
initialize()

while True:
    if button_a.was_pressed():
        initialize()
        sleep(1000)

    a_x = accelerometer.get_x() # read x-value from accelerator
    a_y = accelerometer.get_y() # read y-value from accelerator
    
    if a_x > LEVEL:
        x = min( x + 1, N-1 )
    elif a_x < -LEVEL:
        x = max( x - 1, 0 )
        
    if a_y > LEVEL:
        y = min( y + 1, N-1 )
    elif a_y < -LEVEL:
        y = max( y - 1, 0 )

    if leds[N*y + x] == 0:
        leds[N*y + x] = 1
        count_ones = count_ones+1
 
    if last_pos != (x,y):
        last_x, last_y = last_pos
        if leds[N*last_y + last_x] == 1:
            display.set_pixel(last_x,last_y,9)
        last_pos = (x,y)
    else:
        if count_ones == N*N:
            if display.is_on():
                display.off()
            else:
                display.on()
        else: 
            display.set_pixel(x,y,9-display.get_pixel(x,y))
    sleep(150)

โค้ดตัวอย่างที่ 5: การกำหนดสีให้โมดูล RGB LED (WS2812 NeoPixel)

โมดูล RGB LED ที่ใช้ชิป WS2812B (SMD 5050) สามารถโปรแกรมค่าสีแบบ RGB (Red, Green, Blue) ได้ โดยกำหนดค่าในช่วง 0..255 สำหรับแต่ละสี

เราสามารถใช้คำสั่งจากไลบรารี neopixel สำหรับไมโครบิต โดยสามารถระบุได้ว่า จะใช้แถบ RGB LED จำนวนกี่ดวง หรือระบุตำแหน่งทั้งหมด ในตัวอย่างนี้ จะใช้เพียงหนึ่งตำแหน่ง (ดวงเดียว) โดยทำคำสั่ง NeoPixel(pin1, 1) และเลือกใช้ขา P1 ต่อไปยังขาสัญญาณอินพุตควบคุมของโมดูล NeoPixel ที่มีเพียงหนึ่งพิกเซล RGB (หนึ่งตำแหน่ง)

from microbit import *
from neopixel import *
import random 

np = NeoPixel( pin1, 1 )
values = [
    (0,0,0),(127,0,0),(0,127,0),(0,0,127),
    (127,127,0),(127,0,127),(0,127,127),(255,255,255) ]
while True:
    rgb = random.choice( values )
    np[0] = rgb  # set the RGB value (first position)
    np.show()    # update the RGB LED 
    sleep(1000)

การกำหนดค่าให้แต่ละตำแหน่งจะใช้ Tuple ที่มีข้อมูลตัวเลข 3 ตัว สำหรับ (R,B,G) ตามลำดับ เลือกสุ่มมาจากอาร์เรย์ values โดยใช้คำสั่ง random.choice()

จากรูปภาพจะเห็นได้ว่า มีการใช้โมดูลของ Keyestudio นำมาต่อขาสำหรับ Edge Connector และมีขาแบบ Male Pin Headers การเชื่อมต่อทางไฟฟ้า ก็ใช้วิธีเสียบสายไฟ 3 เส้น เชื่อมต่อไปยังโมดูล NeoPixel (ควรตรวจสอบการต่อวงจรให้ถูกต้องก่อนเสียบสาย USB เพื่อจ่ายไฟให้บอร์ดไมโครบิต)

ข้อควรระวัง: ในการต่อวงจรเพื่อใช้งาน NeoPixel ร่วมกับบอร์ดไมโครบิต สามารถใช้แรงดันไฟเลี้ยง 3.3V จากบอร์ดไมโครบิตได้ แต่ถ้าต้องการจะใช้โมดูล NeoPixel แบบหลายตำแหน่ง (มี RGB LED หลายดวง) จะใช้ปริมาณกระแสมาก แนะนำให้ใช้แหล่งจ่ายไฟ DC 3.3V จากภายนอก และต่อ GND ของระบบร่วมกัน

โค้ดตัวอย่างที่ 6: การสร้างสัญญาณ PWM จำนวน 3 ช่องเอาต์พุต

ถัดไปเป็นการสาธิตการเขียนโค้ด เพื่อสร้างสัญญาณแบบ PWM (Pulse Width Modulation) ซึ่งเป็นสัญญาณรูปคลื่นสี่เหลี่ยมแบบมีคาบ (Periodic Rectangular Waveform) แต่สามารถปรับค่าความกว้างช่วงที่เป็น High Pulse (หรือเรียกว่า Duty Cycle) ได้ 0% ถึง 100%

บอร์ดไมโครบิต สามารถสร้างสัญญาณ PWM ที่กำหนดค่า Duty Cycle ได้ มีขนาดความละเอียด 10 บิต ซึ่งเป็นค่าในช่วง 0..1023 (Duty Cycle 0% .. 100%) และสามารถกำหนดคาบสัญญาณได้เช่นกัน (ต่ำสุดคือ 256 ไมโครวินาที) ถ้าเพิ่มคาบสัญญาณให้กว้างขึ้น ก็จะได้สัญญาณ PWM ที่มีความถี่ต่ำลง

ในตัวอย่างนี้ เราจะใช้คำสั่ง write_analog() สำหรับ Microbit Pin สร้างสัญญาณ PWM และกำหนดให้มีความถี่ 500 Hz (มีคาบ 2 msec หรือ 2000 usec) จำนวน 3 ช่องสัญญาณ (ใช้ขา P0, P1, P2 เป็นเอาต์พุต) เพื่อนำไปขับวงจรหรือโมดูล RGB LED ทำให้เปลี่ยนสีได้

from microbit import *
import math

pins = [pin0, pin1, pin2] # pin list for output 

N = 64 # N+1 = the number of values in the table
# create a table of values using the cos() function
sin_table = []
for i in range(0,N+1):
    value = 1+math.cos(2*math.pi*i/N)
    value = math.floor( value*512 )
    sin_table.append( min(1023,value) )

# set the period of the PWM signal (2msec)
for pin in pins:
  pin.set_analog_period_microseconds(2000)
    
while True:
  for pin in pins:
    for value in sin_table:
      # set PWM duty cycle 0..1023 (10-bit)
      pin.write_analog( value )
      sleep(50)
    sleep(200)
    

สัญญาณ PWM จำนวน 3 ช่อง จะถูกใช้ในการกำหนดค่าสีของโมดูล RGB LED นั้น ในโค้ดตัวอย่างได้กำหนดให้ค่า Duty Cycle ของแต่ละช่องสัญญาณเพิ่มขึ้นหรือลดลงตามรูปแบบของฟังก์ชัน cosine แบ่งเป็นทั้งหมด N=64 ระดับ และสเกลค่าให้อยู่ในช่วง 0..1023

ข้อสังเกต: โมดูล RGB LED ที่ได้เลือกนำมาใช้งานนั้น มีขา Pin Headers ได้แก่ V (VCC), R (Red), B (Blue), G (Green) ตามลำดับ และทำงานแบบ Active-Low

โค้ดตัวอย่างที่ 7: การอ่านค่าจากขาแอนะล็อกอินพุต

ถัดไปเป็นตัวอย่างการอ่านค่าจากขา P0 ที่สามารถใช้เป็นขาอินพุตแอนะล็อกได้ โดยนำมาใช้อ่านค่าจากโมดูล Key-Switch แบบแอนะล็อก (หรือ ADKeypad) หลักการทำงานของโมดูลประเภทนี้คือ เมื่อป้อนแรงดันไฟเลี้ยง VCC กับ GND แล้ว จะได้แรงดันไฟฟ้าที่ขาสัญญาณเอาต์พุตที่ขึ้นอยู่กับสถานะการกดปุ่ม (โดยทั่วไป ก็จะกดเพียงปุ่มเดียวในแต่ละช่วงเวลา)

from microbit import *
import time 

analog_pin = pin0
N = 7
TP_MSEC = 50
ts_prev = running_time()
last_btn = 0

while True:
  ts_now = running_time()
  if time.ticks_diff(ts_now, ts_prev) >= TP_MSEC:
    ts_prev = ts_now
    values = []
    for i in range(N): # repeat N times
       # read the value from the analog pin P0 
       value = analog_pin.read_analog()
       values.append(value)
    # apply the median filter
    value = sorted(values)[N//2]
    if value > 600: # no button press
      last_btn = 0
      continue
    btn = 0
    if value < 25:
      btn = 1
    elif value < 50:
      btn = 2
    elif value < 100:
      btn = 3
    elif value < 150:
      btn = 4
    elif value > 250 and value < 300:
      btn = 5
    if last_btn != btn and btn > 0:
      print( 'SW{} {}'.format(btn, value) )
      display.show( btn, 10 )
    last_btn = btn

โมดูลที่ได้เลือกมาใช้งาน มีทั้งหมด 5 ปุ่ม มีข้อความเขียนกำกับเอาไว้คือ SW1, SW2, ..., SW5 ถ้าอ่านค่าจากขา P0 แบบแอนะล็อก โดยใช้คำสั่ง read_analog() จะได้ค่าในช่วง 0..1023 ซึ่งก็ขึ้นอยู่กับสถานะการกดปุ่ม และนำมาใช้ในการจำแนกหรือตรวจสอบดูว่า ปุ่มใดกำลังถูกกดอยู่ในขณะนั้น

ให้ทดลองอ่านค่าแล้วส่งออกมาทาง Serial ด้วยคำสั่ง print() และทำซ้ำไปเรื่อย ๆ แล้วกดไปทีละปุ่ม เราจะเห็นค่าอินพุตที่อ่านได้สำหรับแต่ละกรณี ยกตัวอย่างเช่น ถ้ายังไม่กดปุ่มใด ๆ เลย ค่าที่อ่านได้จะอยู่ในช่วงประมาณ 800 ถึง 850 เป็นต้น

โค้ดตัวอย่างที่ 8: การตรวจสอบการใช้นิ้วสัมผัสที่ Touch Pad

ตัวอย่างนี้สาธิตการตรวจสอบดูว่า มีการใช้นิ้วสัมผัสที่บริเวณ Touch Pad 0 ของบอร์ดไมโครบิตหรือไม่ (และให้ใช้อีกนิ้วหนึ่ง สัมผัสที่ Pad GND ของบอร์ด) โดยใช้คำสั่ง pin0.is_touched() ซึ่งจะได้ค่าแบบ boolean

ถ้ามีการสัมผัสด้วยนิ้วในแต่ละครั้งที่ขา P0 จะทำให้สถานะของ LED Display เปลี่ยนไป ซึ่งมีอยู่ 4 รูปแบบ หรือ ระดับ ดังนี้

  • ระดับที่ 1 เริ่มต้น ไม่มี LED อยู่ในสถานะ ON

  • ระดับที่ 2 มี LED เพียง 1ดวง (ตรงกลาง) ในสถานะ ON

  • ระดับที่ 3 มี LED จำนวน 3x3 ดวงในสถานะ ON

  • ระดับที่ 4 มี LED จำนวน 5x5 ดวง (ทุกดวง) ในสถานะ ON

from microbit import *

LED_PATTERNS = [
    Image("00000:00000:00900:00000:00000"),
    Image("00000:09990:09990:09990:00000"),
    Image("99999:99999:99999:99999:99999") ]

display.on()
display.clear()
level = 0

print('Please touch Pad 0 with one finger')
print('and use another finger to touch pad GND.')

while True:
    if pin0.is_touched():
        while pin0.is_touched():
            sleep(10)
        if level == 0:
            display.off()
        else:
            display.on()
            display.show( LED_PATTERNS[level-1] )       
            print('Display on')
        level = (level+1) % 4      

โค้ดตัวอย่างที่ 9: การตรวจสอบการเชื่อมต่ออุปกรณ์ I2C Slave

บอร์ดไมโครบิต สามารถเชื่อมต่อกับอุปกรณ์อื่นในระบบบัส I2C ที่ใช้สายสัญาณเพียง 2 เส้นคือ SDA (Serial Data Line) และ SCL (Serial Clock Line) ไมโครบิตจะทำหน้าที่เป็นอุปกรณ์ I2C Master ซึ่งเป็นฝ่ายเริ่มต้น หรือควบคุมการทำงานของอุปกรณ์อีกฝ่ายหนึ่งคือ I2C Slave เช่น ต้องการจะเขียน (Write Operation) หรืออ่านข้อมูล (Read Operation) เป็นต้น นอกจากนั้นจะต้องมีการระบุแอดเดรสขนาด 7 บิต (7-bit Slave Address) ของอุปกรณ์แต่ละตัวในระบบบัสเดียวกัน

การเขียนโค้ดเพื่อใช้งานบัส I2C ก็มีคำสั่งในกลุ่ม i2c ไว้ให้ใช้งาน โดยทั่วไป เราก็ใช้ความเร็ว 100kHz หรือ 400kHz ในการสื่อสารสำหรับบัส I2C

บอร์ดไมโครบิตมีวงจรหรือไอซีที่เชื่อมต่อด้วยบัส I2C อยู่แล้ว ได้แก่ ไอซีวัดความเร่ง (Accelerometer IC) และไอซีเข็มทิศดิจิทัล (Digital Compass IC) เป็นต้น แต่ถ้าเราจะนำอุปกรณ์ I2C Slave ตัวอื่น มาต่อเพิ่ม ก็ให้เลือกใช้ขา Pin 20 และ Pin 19 ของบอร์ดไมโครบิต เป็นขาสัญญาณ SDA และ SCL ตามลำดับ และอุปกรณ์เหล่านั้นจะต้องทำงานที่แรงดันไฟฟ้า +3.3V เช่นกัน

ลองมาดูตัวอย่างการเขียนโค้ด เพื่อตรวจสอบหรือสแกนอุปกรณ์ (Device Scan) และแสดงหมายเลขแอดเดรส (ฐานสิบหก) ของอุปกรณ์ Slave Device ในระบบบัส I2C ของบอร์ดไมโครบิต ส่งเป็นข้อความออกทาง Serial โดยแบ่งเป็นสองวิธี

  1. สร้างฟังก์ชัน scan_i2c() ที่เราสร้างขึ้นเองแล้วเรียกใช้ หรือ

  2. เรียกใช้ฟังก์ชัน i2c.scan() ซึ่งมีไว้ให้แล้ว

from microbit import *

def scan_i2c():
    dev_list = []
    for addr in range(0x01, 0x7f):
        try:
           # try to read one byte
           i2c.read(addr, 1)
        except OSError:
            pass
        else:
            dev_list.append( addr )
    return dev_list

i2c.init( freq=100000, sda=pin20, scl=pin19)

while True:
    print("Scanning I2C bus... Method 1")
    # Method 1
    dev_list = scan_i2c()
    for dev in dev_list:
        print("> Found device at 0x{0:02x}".format(dev) )
    print( 40*'-' )
    # Method 2
    print("Scanning I2C bus... Method 2")
    dev_list = i2c.scan()
    for dev in dev_list:
        print("> Found device at 0x{0:02x}".format(dev) )
    print( 40*'-' )
    sleep(5000)
       

ถ้าทำงานได้ถูกต้อง จะต้องพบอุปกรณ์หมายเลข 0x0e (Compass) และ 0x1d (Accelerometer) สำหรับบอร์ดไมโครบิตเวอร์ชัน 1.3B

โค้ดตัวอย่างที่ 10: การอ่านค่าจากโมดูลเซ็นเซอร์แสง BH1750

ตัวย่างถัดไปเป็นการอ่านข้อมูลจากโมดูลเซ็นเซอร์วัดแสง BH1750 (GY-302) ซึ่งจะให้ค่าที่มีความละเอียด 16 บิต (0..65535) หน่วยเป็นลักซ์ (Lux) เมื่อเริ่มต้นก่อนใช้งาน จะต้องส่งคำสั่งไปกำหนดโหมดการทำงานของ BH1750

ในตัวอย่างนี้ ได้ใช้โหมด Continuous Measurement ความละเอียด 1.0 Lux ต่อหนึ่งบิต (รายละเอียดเชิงเทคนิคเกี่ยวกับ BH1750 แนะนำให้ศึกษาจากเอกสาร Datasheet ของผู้ผลิต)

โมดูลที่นำมาใช้งานนั้น เชื่อมต่อแบบบัส I2C ใช้แรงดันไฟเลี้ยง 3.3V และมีหมายเลขแอดเดรสของอุปกรณ์คือ 0x23 (= 35 decimal) ซึ่งเป็น Default Address

โมดูลนี้มีขา ADDR ถ้าต่อขาดังกล่าวด้วยสายไฟไปยังขา VCC (3.3V) จะได้แอดเดรสเป็น 0x5C (92 dec) แต่ถ้าต่อไปยังขา GND หรือปล่อยไว้ไม่ต้องต่อขา (Not Connected) จะได้แอดเดรสเป็น 0x23 (25 dec)

ตัวอย่างโค้ดนี้ จะอ่านข้อมูลจากโมดูลเซ็นเซอร์ทั้งหมด 10 ครั้ง ในแต่ละครั้งจะส่งข้อความแสดงค่าที่อ่านได้ออกทาง Serial โดยใช้คำสั่ง print()

from microbit import *
import time 

BH1750_ADDR = 0x23 # or 35 (dec) 

i2c.init( freq=100000, sda=pin20, scl=pin19)
print( 'Scan I2C devices..')
print( 'List of found devices:', i2c.scan() )

def bh1750_init(addr):
    try:
        # power on the BH1750
        i2c.write(addr, bytearray([0x01]) )
        # reset the BH1750
        i2c.write(addr, bytearray([0x07]) )
        time.sleep_ms(200)
        # set mode to 1.0x high-resolution,
        # continuous measurement
        i2c.write(addr, bytearray([0x10]) )
        time.sleep_ms(150)
        return True
    except Exception:
        return False

def bh1750_read(addr):
    try:
        data  = i2c.read(addr, 2) # read two bytes
        value = (data[0]<<8 | data[1])/(1.2)
        return value
    except Exception:
        print('BH1750 reading error')
        return None

NUM_READINGS = 10
if bh1750_init(BH1750_ADDR):
    for i in range(NUM_READINGS): # repeat 10 times
        value = bh1750_read(BH1750_ADDR)
        print( 'Light level: [{0:>7.1f}] lx'.format(value) )
        time.sleep(1)
    print('Done....\n\n')
else:
    print('BH1750 not found...\n\n')

โค้ดตัวอย่างที่ 11: การอ่านค่าและแสดงสถานะการทำงานของ Wii Nunchuk

โมดูล Wii Nunchuk เป็นอุปกรณ์เสริมที่ใช้ร่วมกับ Wii Remote ของ Nintendo แต่ก็สามารถนำมาใช้งานเพื่อต่อเข้ากับบอร์ดไมโครคอนโทลเลอร์ได้ โดยใช้วิธีสื่อสารแบบบัส I2C แต่จะต้องมี PCB Adapter ใช้ในการแปลงคอนเนกเตอร์ให้เป็นแบบ Pin Headers (หรือจะดัดแปลงสายไฟและเปลี่ยนชนิดของคอนเนกเตอร์ใหม่ก็ได้)

อุปกรณ์ใช้หมายเลขแอดเดรสเท่ากับ 0x52 และรูปแบบข้อมูลที่ได้จากอุปกรณ์นี้ (อ่านคราวละ 6 ไบต์) ได้ถูกกำหนดไว้โดยผู้ผลิต แนะนำให้ลองสืบค้นดูในอินเทอร์เน็ตและศึกษาจากแหล่งข้อมูลอื่นที่ได้อธิบายหรือระบุความหมายข้อมูลไบต์แต่ละตำแหน่ง

โดยสรุป ข้อมูลไบต์ที่อ่านได้ มีดังนี้

  • ไบต์แรกและไบต์ที่สอง เป็นค่าของคันโยกควบคุม (Joystick) ในแกน x และแกน y ตามลำดับ ถ้าอยู่ในตำแหน่งตรงกลาง จะได้ค่าใกล้เคียง 127

  • สามไบต์ถัดไปคือ ค่าที่ได้จากตัววัดความเร่ง 3 แกน คือ แกน x, y และ z ตามลำดับ อย่างละหนึ่งไบต์

  • ไบต์ที่หกเป็นตัวระบุสถานะของปุ่มกด C (บิตที่ 1) และ Z (บิตที่ 0) ถ้าบิตมีค่าเป็น 0 หมายความว่า ปุ่มที่เกี่ยวข้องกับบิตดังกล่าว กำลังถูกกดอยู่ในขณะนั้น

โค้ดตัวอย่างนี้ สาธิตการอ่านค่าจาก Wii Nunchuk มีดังนี้

from microbit import *

# 7-bit address of the Wii Nunchuck 
addr = 0x52  

def nunchuk_init():
    try:
        # send command to initialize the device
        i2c.write( addr, bytearray([0x40,0x00]) )
        # send command to disable data encryption
        i2c.write( addr, bytearray([0xF0,0x55]) )
        i2c.write( addr, bytearray([0xFB,0x00]) )
    except Exception:
        return False
    return True

def nunchuk_read():
    rawdata = None
    try:
        # read data
        i2c.write( addr, bytearray([0x00]) )
        sleep(50)
        # read raw data (expect 6 bytes)
        rawdata = i2c.read( addr, 6 )
    except Exception:
        return None
    if rawdata:
        data = rawdata
        # (x,y) position
        x,y = data[0],data[1]
        # accelerometer value for the x-,y- and z-axis
        ax,ay,az = data[2],data[3],data[4]
        btn_z = ((data[5] & 0x01) == 0)
        btn_c = ((data[5] & 0x02) == 0)
        return (x,y,ax,ay,az,btn_z,btn_c)

print( 'Micro:Bit Wii Nunchuck Interfacing (I2C)' )
print( 'Press the button A to start!' )
display.show( Image.ARROW_W )

while True:
    if button_a.was_pressed():
        display.show( ' ' )
        break
    
if nunchuk_init() == False:
    print('Nunchuk initialization failed..')
    while True:
        sleep(100)

while True:
    sleep(100)
    data = nunchuk_read()
    if data is None:
        continue
    print( 'x=%d, y=%d' % (data[0],data[1]) )
    print( 'ax=%d, ay=%d, az=%d' % (data[2],data[3],data[4]) )
    print( 'button Z pressed=%s' % (str(data[5])) )
    print( 'button C pressed=%s' % (str(data[6])) )
    print( 40*'-' )

จากโค้ดตัวอย่างนี้ เราสามารถนำไปประยุกต์ใช้ในการเล่นหรือควบคุมเกมคอมพิวเตอร์ เช่น ใช้คันโยกแกน X และ Y รวมถึงปุ่มกด C และ Z เป็นต้น

โค้ดตัวอย่างที่ 12: การอ่านค่าจากโมดูลเซ็นเซอร์ SHT31

โค้ดในตัวอย่างนี้ สาธิตการอ่านค่าจากโมดูลเซ็นเซอร์ SHT31-DIS สำหรับวัดอุณหภูมิและความชื้นสัมพัทธ์ (ใช้ไอซีที่ผลิตโดยบริษัท SENSIRION) และแสดงค่าที่อ่านได้เป็นข้อความเอาต์พุตทาง Serial โดยใช้คำสั่ง print()

# File: main.py
from microbit import *
from sht3x import *

i2c.init( freq=100000, sda=pin20, scl=pin19 )
print( 'Scan I2C devices..')
print( 'List of found devices:', i2c.scan() )

sht3x = SHT3x( i2c, 0x44 )
try:
    sht3x.reset()
except Exception as ex:
    print('Error', ex)

for i in range(10):
    sht3x.measure()
    temp, humid = sht3x.read()
    text = "T: {:.1f} deg.C".format(temp)
    text = text + ', ' + "H: {:.1f} % RH ".format(humid)
    print( text )
    sleep( 1000 )

ในโค้ดตัวอย่างนี้ มีการสร้างออบเจ็กต์จากคลาส SHT3x ที่ได้มีการสร้างไว้ในไฟล์ sht3x.py และสามารถเรียกใช้คำสั่ง measure() เพื่อสั่งตัวเซ็นเซอร์ให้อัปเดตค่าครั้งถัดไป และใช้คำสั่ง read() เพื่ออ่านค่าตัวเลขสำหรับอุณหภูมิและความชื้นสัมพัทธ์

โค้ดอีกหนึ่งไฟล์ (sht3x.py) ที่จะต้องนำไปบันทึกลงใน Flash Storage ของไมโครบิต เพื่อใช้ร่วมกับโค้ดตัวอย่างข้างบน มีดังนี้

# File: sht3x.py (MicroPython for BBC Micro:bit)
from micropython import const
import time

class SHT3x:
    def __init__(self, i2c, addr):
        self.i2c  = i2c
        self.addr = addr
    
    def reset(self):
        try: 
            self.i2c.write( self.addr, bytearray([0x30,0xa2]) )
        except OSError:
            raise RuntimeError('SHT3x: I2C write failed!')

    def measure(self):
        try: 
            # send command: measurement, high repeatability, with clock stretching 
            self.i2c.write( self.addr, bytearray([0x2c,0x06]) )
        except OSError:
            raise RuntimeError('SHT3x: I2C write failed!')
 
    def read(self):
        try:
            raw = self.i2c.read(self.addr, 6)
        except OSError:
            raise RuntimeError('SHT3x: I2C write failed!')
        
        # check CRC8 mismatch ?
        if (self.crc8(raw[0:2]) != raw[2]) or (self.crc8(raw[3:5]) != raw[5]):
            raise RuntimeError('SHT3x: CRC mismatch!')
        
        temp = -45 + (175 * ((raw[0] << 8) + raw[1]) / 65535.0)
        humid = 100 * ((raw[3] << 8) + raw[4]) / 65535.0
        return (temp,humid)

    def crc8(self,buf):
        """ Polynomial 0x31 (x8 + x5 +x4 +1) """
        polynom = const(0x31)
        crc = 0xff;
        for i in range(0, len(buf)):
            crc ^= buf[i]
            for j in range(8):
                if crc & 0x80:
                    crc = (crc << 1) ^ polynom
                else:
                    crc = (crc << 1)
        return (crc & 0xff)
        

สำหรับการทดลองต่อวงจร โมดูลนี้ใช้แรงดันไฟเลี้ยงที่ 3.3V จากบอร์ดไมโครบิตได้ การเชื่อมต่อกับโมดูลเซ็นเซอร์ SHT31-DIS ใช้วิธี I2C Bus ที่มีสายสัญญาณ 2 เส้น SCL และ SDA (ขา SCL และ SDA นำไปต่อกับขา P19 และ P20 ของบอร์ดไมโครบิตตามลำดับ) และมีการกำหนดแอดเดรสไว้เท่ากับ 0x44

แนะนำให้ใช้โปรแกรม Mu Editor ในการเขียนโค้ด แล้วให้กดปุ่มไอคอน Files จะมีการแสดงรายการแบ่งเป็น 2 กลุ่ม (แบ่งเป็นด้านซ้ายกับขวามือ) เราสามารถเลือกไฟล์จากกลุ่มหนึ่ง ลากไปวาง (Drag & Drop) ใส่อีกกลุ่มหนึ่ง ซึ่งเป็นการสำเนาไฟล์ที่เลือก ระหว่างคอมพิวเตอร์กับอุปกรณ์ไมโครบิต

ลำดับขั้นตอนการทดสอบโค้ดกับบอร์ดไมโครบิตโดยใช้ Mu Editor

  • สร้างไฟล์ใหม่ โดยกดปุ่ม New แล้วเขียนโค้ดตามตัวอย่างและบันทึกลงในไฟล์ชื่อ sht3x.py ในคอมพิวเตอร์ของผู้ใช้

  • กดปุ่ม Files แล้วเลือกไฟล์ sht3x.py จากรายการไฟล์ในคอมพิวเตอร์ของผู้ใช้ แล้วลากไปยังบอร์ดไมโครบิต (Drag & Drop เพื่อสำเนาไฟล์)

  • เขียนโค้ดตัวอย่าง main.py แล้วอัปโหลดไปยังบอร์ดไมโครบิต โดยกดปุ่ม Flash แล้วจึงกดปุ่ม REPL

  • ในบริเวณช่องรับคำสั่งของ REPL ให้กดปุ่มบนแป้นพิมพ์ Ctrl+D เพื่อเริ่มต้นการทำงานของไมโครไพธอนใหม่อีกครั้ง และรันโค้ด main.py โดยอัตโนมัติ

  • สังเกตข้อความเอาต์พุตที่ปรากฏในบริเวณ REPL

ข้อสังเกต: ในโปรแกรม Mu Editor (v1.1.0alpha2) การเลือกทำคำสั่งจากปุ่มไอคอน Flash, Files และ REPL จะทำพร้อมกันไม่ได้ จะต้องเลือกอย่างใดอย่างหนึ่งในแต่ละช่วงเวลา เช่น ถ้าเปิดใช้ REPL อยู่ในขณะนั้น จะไม่สามารถทำขั้นตอน Flash หรือ Files ได้

โค้ดตัวอย่างที่ 13: SHT31 + I2C 16x2 LCD

โค้ดตัวอย่างนี้สาธิตการใช้งานโมดูล 16x2 LCD (แสดงผลข้อความแบบ Alphanumeric มี 2 แถว ๆ ละ 16 ตัวอักษร) แบบ I2C ที่ใช้ไอซี PCF8574 เป็นตัวควบคุมการทำงาน และอ่านค่าจากโมดูลเซ็นเซอร์ SHT31 ผ่านทาง I2C เช่นเดียวกันแล้วนำค่าที่ได้มาแสดงเป็นข้อความบนจอโมดูล LCD

โมดูล SHT31 มีการกำหนดแอดเดรสไว้เท่ากับ 0x44 ในขณะที่โมดูล 16x2 LCD-PCF8574 มีแอดเดรสเท่ากับ 0x3F

from microbit import *
from sht3x import *
from lcd_pcf8574 import LCD
import gc 

i2c.init( freq=400000, sda=pin20, scl=pin19 )
print( 'Scan I2C devices..')
print( 'List of found devices:', 
    [hex(d) for d in i2c.scan()] )

sht3x = SHT3x( i2c, 0x44 )
lcd   = LCD(i2c, 0x3f)

try:
    sht3x.reset()
    lcd.reset() # reset the LCD module first
    lcd.clear() # clear LCD 
    lcd.goto_line( 0 ) # goto the first line
    lcd.print('MicroPython...')
    lcd.goto_line( 1 ) # goto the second line
    lcd.print('Micro:bit')
except Exception as ex:
    print('Error', ex)

t_last = time.ticks_ms()
state = 0
try:
    while True:
        t_now = time.ticks_ms()
        if time.ticks_diff( t_now, t_last ) >= 500:
            t_last = t_now 
            if state == 0:
                sht3x.measure()
                temp, humid = sht3x.read()
            elif state == 1:
                lcd.goto_line( 0 ) # goto the first line
                text = ' T: {:2.1f} deg.C'.format(temp)
                text = text + max(16-len(text),0)*' '
                lcd.print( text )
            else:
                lcd.goto_line( 1 ) # goto the second line
                text = ' H: {:2.1f} %RH'.format(humid)
                text = text + max(16-len(text),0)*' '
                lcd.print( text )
            state = (state+1) % 3
            gc.collect()
            
except KeyboardInterrupt:
    pass
finally:
    print('Done..')

โค้ดไมโครไพธอนสำหรับไฟล์ lcd_pcf8574.py มีดังนี้

# file: lcd_pcf8574.py
from micropython import const
import utime as time

_RS = const(0x01) # PCF8574 Pin 0 (RS)
_RW = const(0x02) # PCF8574 Pin 1 (RW)
_CS = const(0x04) # PCF8574 Pin 2 (CS or EN)
_BL = const(0x08) # PCF8574 Pin 3
_CURSOR_BLINK         = const(0x01)
_CURSOR_ON            = const(0x02)
_DISP_ON              = const(0x04)
CMD_CLEAR_DISP        = const(0x01)
CMD_RETURN_HOME       = const(0x02)
CMD_CSTRY_MODE_SET    = const(0x04)
CMD_DISP_CTRL         = const(0x08)
CMD_CURSOR_DISP_SHIFT = const(0x10)
CMD_FUNC_SET          = const(0x20)
CMD_SET_CGRAM_ADDR    = const(0x40)
CMD_SET_DDRAM_ADDR    = const(0x80)

class LCD():
    def __init__(self, i2c, addr):
        self._i2c  = i2c
        self._addr = addr
        self._disp_mode = 0
        
    def _pcf8574_write( self, data ):
        self._i2c.write( self._addr, bytearray(data) )

    def _write4bits( self, data ):
        data = data | _BL
        self._pcf8574_write( [data | _CS] )
        time.sleep_us(300)
        self._pcf8574_write( [data] )
        time.sleep_us(300)
        
    def write( self, data, cmd=True ):
        _h = data & 0xf0        # high nibble
        _l = (data << 4) & 0xf0 # low nibble
        _mode = 0 if cmd else _RS
        self._write4bits( _h | _mode )
        self._write4bits( _l | _mode )
        
    def reset( self ):
        self._write4bits( 0x03 << 4 )
        time.sleep_ms(5)
        self._write4bits( 0x03 << 4)
        time.sleep_us(150)
        self._write4bits( 0x03 << 4)
        self._write4bits( 0x02 << 4)
        # function set: 4-bit data lines, 2 text lines, 5x8 dots
        self.write( CMD_FUNC_SET | 0x08 )
        # display ctrl: display on, cursor off
        self._disp_mode = _DISP_ON 
        self.write( CMD_DISP_CTRL | self._disp_mode )
        # go home position (move cursor to the first line)
        self.write( CMD_RETURN_HOME )
        
    def clear(self):
        self.write( CMD_CLEAR_DISP )
        
    def return_home( self ):
        self.write( CMD_RETURN_HOME )
        
    def goto_line( self, line ):
        addr = (0 if line==0 else 0x40) 
        self.write( CMD_SET_DDRAM_ADDR | addr )
        
    def blink_cursor( self, blink=True ):
        if blink:
            self._disp_mode |= _CURSOR_BLINK
        else:
            self._disp_mode &= ~_CURSOR_BLINK
        self.write( CMD_DISP_CTRL | self._disp_mode )
        
    def show_cursor( self, show=True ):
        if show:
            self._disp_mode |= _CURSOR_ON
        else:
            self._disp_mode &= ~_CURSOR_ON
        self.write( CMD_DISP_CTRL | self._disp_mode )
        
    def print( self, text ):
        for ch in text:
            self.write( ord(ch), False )
            

โค้ดตัวอย่างที่ 14: การสร้างรูปกราฟิกบน LED Matrix

โค้ดตัวอย่างต่อไปนี้ สาธิตการสร้างรูปกราฟิกตามรูปแบบที่กำหนดโดยเงื่อนไข โดยใช้ค่าของพิกัด (x,y) บนแผง 5x5 LED Matrix (N=5)

ฟังก์ชัน create_pattern(..) ใช้สำหรับสร้างข้อมูลอาร์เรย์ data ที่มีข้อมูลสมาชิกเป็น 0 หรือ 1 และมีจำนวนเท่ากับ 5x5 (25) ค่าของอาร์กิวเมนต์ i สำหรับฟังก์ชันนี้ เป็นเลขจำนวนเต็ม i=0,1,...,6 จะเป็นตัวเลือกว่า ต้องการสร้างข้อมูลในอาร์เรย์เป็นแบบใด

ฟังก์ชัน conditions(..) ทำหน้าที่ระบุสถานะที่พิกัด (x,y) ของ LED Matrix ตามเงื่อนไขที่กำหนดไว้ และ i เป็นอาร์กิวเมนต์ เพื่อกำหนดรูปแบบ หรือ กรณีในการสร้างรูปกราฟิก ฟังก์ชันนี้จะถูกเรียกใช้ในฟังก์ชัน create_pattern(..)

ฟังก์ชัน show_pattern(..) ใช้สำหรับนำค่าที่ได้จากอาร์เรย์ data ไปใช้เพื่อแสดงสถานะของ LED Matrix บนบอร์ดไมโครบิต ถ้ามีสถานะเป็น 0 หมายถึง OFF แต่ถ้ามีค่าเท่ากับ 1 หมายถึง ON (มีค่าของความสว่าง 9)

เมื่อทดสอบการทำงานของโค้ด ให้กดปุ่ม A เพื่อเปลี่ยนรูปแบบกราฟิกในลำดับถัดไป

from microbit import * 
from micropython import const
import time, random

N  = const(5) # number of rows and columns (NxN)
BL = const(9) # max. brightness level 

def conditions( i, x, y ):
    if i==0:
        return int(x <= y)
    elif i==1:
        return int(not(x < y))
    elif i==2:
        return int(x >= N-1-y)
    elif i==3:
        return int(x < N-y)
    elif i==4:
        dx = abs(x-N//2)
        return int((y-1) > dx or (N-y-2) > dx) 
    elif i==5:
        dy = abs(y-N//2)
        return int((x-1) > dy or (N-x-2) > dy)  
    elif i==6: # random 
        return int(random.randint(0,BL) > BL//2)
    return 0

def create_pattern( i ):
    data = []
    for y in range(N): # for each row
        for x in range(N): # for each column
            data.append( conditions(i,x,y) )
    return data

def show_pattern( data ):
    for y in range(N): # for each row
        for x in range(N): # for each column
            # set the brightness at (x,y)
            display.set_pixel(x, y, BL*data[x+y*N] )

display.show( Image.ARROW_W )
while True:
    for i in range(7):
        while not button_a.was_pressed():
            pass
        data = create_pattern(i)
        show_pattern( data )

โค้ดตัวอย่างที่ 15: Conway's Game of Life

โค้ดตัวอย่างถัดไป สาธิตการจำลองสถานการณ์ที่เรียกว่า Conway's Game of Life ซึ่งกล่าวถึง ระบบที่ประกอบด้วยเซลล์ (Cells) ที่ถูกจัดเรียงแบบเมตริกซ์ (อาร์เรย์สองมิติ) แต่ละเซลล์ที่พิกัด (x,y) มีสถานะเป็น 0 หรือ 1 ซึ่ง 0 หมายถึง ไม่มีชีวิต (dead) และ 1 หมายถึง เซลล์กำลังมีชีวิตอยู่ (alive)

สถานะเริ่มต้นของเซลล์ อาจได้จากการสุ่ม (Randomization of Cell States) และสถานะของเซลล์ที่เปลี่ยนแปลงไปตามเวลา (เวลาเป็นแบบ discrete-time) ขึ้นอยู่กับสถานะของเซลล์โดยรอบ (Neighbouring Cells) โดยทั่วไปแต่ละเซลล์ในอาร์เรย์สองมิติ จะมีเซลล์ที่อยู่รอบ ๆ ติดกัน ไม่เกิน 8 เซลล์ (มีอยู่รอบทิศ)

การกำหนดสถานะของเซลล์ในลำดับเวลาถัดไป เป็นไปตามกฎ (Rules) ได้ดังนี้

  1. ถ้าเซลล์นั้นมีชีวิต และมีจำนวนเซลล์รอบ ๆ ที่มีชีวิตอยู่ เท่ากับ 2 หรือ 3 ให้เซลล์นั้นยังคงมีชีวิตต่อไป (Healthy Population)

  2. ถ้าเซลล์นั้นมีชีวิต แต่มีจำนวนเซลล์รอบ ๆ ที่มีชีวิตอยู่ น้อยกว่า 2 หรือ มากกว่า 3 ให้เซลล์นั้นตายไป (กรณีนี้เรียกว่า Underpopulation และ Overpopulation ตามลำดับ)

  3. ถ้าเซลล์นั้นไม่มีชีวิตหรือตายไปแล้ว แต่มีจำนวนเซลล์รอบ ๆ ที่มีชีวิตอยู่ เท่ากับ 3 ให้เซลล์นั้นเริ่มต้นหรือกลับมามีชีวิตใหม่อีกครั้ง (กรณีนี้เรียกว่า Reproduction)

ในโค้ดตัวอย่างนี้ ขนาดของเมตริกซ์เท่ากับ 5x5 (N=5) แต่การเก็บสถานะของเซลล์ จะใช้อาร์เรย์มิติเดียว (One-dimensional Array หรือ List) โดยใช้ชื่อตัวแปรว่า cells ดังนั้นสถานะของเซลล์ที่พิกัด (x,y) ในอาร์เรย์ดังกล่าวคือ cells[x + N*y]

ตัวอย่างการเขียนโค้ดเพื่อสาธิต Conway's Game of Life ด้วย MakeCode Static TypeScript สำหรับบอร์ดไมโครบิต สามารถศึกษาได้จาก https://makecode.microbit.org/examples/gameofLife

# Conway's Game of Life
# based on https://www.hackster.io/ivo-ruaro/conway-s-game-of-life-e383e3

from microbit import * 
from micropython import const
import random
from random import randint
import time 
import gc

N  = const(5) # number of rows and colums (NxN)
BL = const(9) # brightness level 
cells = []

def show():
    global cells
    for y in range(N):
        for x in range(N):
            display.set_pixel(x, y, BL*cells[x + N*y])

def count_neighbours(x, y):
    global cells
    n = 0
    # count the number of live neighbouring cells
    for dy in [-1, 0, 1]:
        for dx in [-1, 0, 1]:
            _x = (x + dx) % N
            _y = (y + dy) % N
            n += cells[_x + N*_y]
    n -= cells[x + N*y] # excludes the cell itself
    return n

def update():
    global cells
    new_cells = []
    for y in range(N):
        for x in range(N):
            num_neighbours = count_neighbours(x, y)
            is_alive = (cells[x + N*y] == 1)
            if is_alive:
                if num_neighbours < 2: # underpopulation
                    new_cells.append(0)
                elif num_neighbours == 2 or num_neighbours == 3:
                    new_cells.append(1) 
                elif num_neighbours > 3: # overpopulation
                    new_cells.append(0)
            else:
                if num_neighbours == 3: # reproduction
                    new_cells.append(1)
                else:
                    new_cells.append(0) # dies
    return new_cells

def reset():
    global cells
    random.seed( time.ticks_ms() )
    cells = [int(randint(0,BL) > BL//2) for i in range(N*N)]

# press button A to start
display.show( Image.ARROW_W )
while True:
    if button_a.was_pressed():
        break

reset()
show()
time.sleep(1.0)

while True:
    if button_a.was_pressed():
        reset()
    else:
        cells = update()
    show()
    gc.collect()
    time.sleep(0.5)

โค้ดตัวอย่างที่ 16: การวัดความกว้างของสัญญาณพัลส์

โค้ดตัวอย่างนี้ สาธิตการวัดความกว้างของสัญญาณบแบบพัลส์ (Pulse) เช่น สัญญาณประเภท PWM (Pulse Width Modulation) เช่น ถ้าเราต้องการทราบความกว้างของพัลส์ช่วงที่เป็นลอจิก 1 (High) เราจะสามารถใช้บอร์ดไมโครบิตวัดค่าได้หรือไม่

ตัวอย่างนี้สร้างสัญญาณ PWM ที่ความถี่ 50Hz หรือ มีความกว้าง 20 msec เป็นสัญญาณแบบ PWM โดยเลือกใช้ขา pin8 และใช้คำสั่ง write_analog() ซึ่งจะต้องระบุค่าตัวเลข (ขนาด 10 บิต) ในช่วง 0..1023 (หรือเท่ากับ 0% .. 100% สำหรับค่า Duty Cycle ของสัญญาณดังกล่าว)

ถ้ากำหนดให้ Duty Cycle = 25% 50% และ 75% โดยทางทฤษฏี จะได้ความกว้างเท่ากับ 5000, 10000 และ 15000 ไมโครวินาที ตามลำดับ

สัญญาณเอาต์พุตที่ขา pin8 จะถูกป้อนกลับเข้าที่ขา pin11 ซึ่งถูกใช้เป็นขาอินพุต-ดิจิทัล (ในการทดลอง สามารถใช้ลวดสายไฟ Jumper Wire เชื่อมต่อระหว่างขาทั้งสอง)

การวัดความกว้างของพัลส์ช่วงที่เป็น 1 สามารถทำได้ง่ายโดยใช้คำสั่ง time_pulse_us() ของกลุ่มคำสั่ง machine

การเรียกใช้คำสั่งนี้ จะต้องระบุขา (Pin) ที่ต้องการใช้ เลือกประเภทของพัลส์เป็น High (1) หรือ Low (0) และกำหนดค่าตัวเลขสำหรับ Timeout (หน่วยเป็นไมโครวินาที)

from microbit import * 
import machine 
import time 

period_ms = 20
display.off() # disable LED matrix display 

pin8.set_analog_period( period_ms ) # set period = 20msec
pin8.write_analog(0) # set duty cycle to 0

values = [25, 50, 75] # PWM duty cycles in percents

while True:
    print( 'Measured : Expected' )
    for v in values:
        pin8.write_analog( 1023*v//100 )
        t = machine.time_pulse_us( pin11, 1, 100000 )
        pin8.write_analog( 0 )
        args = (t, v*(period_ms*1000)//100 )
        print( '   {:5d} : {:5d} us'.format( *args ) )
        
    print( 40*'-' )
    time.sleep(5.0)

โค้ดตัวอย่างที่ 17: การวัดระยะห่างจากสิ่งกีดขวางโดยใช้โมดูล Ultrasonic Sensor HC-SR04P

HC-SR04P เป็นโมดูลเซ็นเซอร์ที่ใช้สัญญาณคลื่นเสียงอัลตราโซนิค (ความถี่สูง ประมาณ 40kHz) ในการตรวจสอบและวัดระยะห่างจากวัดถุกีดขวาง

หลักการทำงานของเซ็นเซอร์ประเภทนี้คือ การใช้ตัวส่ง (Transmitter) สร้างสัญญาณที่เป็นคลื่นเสียงออกไป เมื่อไปกระทบวัตถุ จะเกิดคลื่นสะท้อนกลับมายังตัวรับ (Receiver) เมื่อจับเวลาการเดินทางของคลื่นเสียงในทิศทางไปและกลับ และกำหนดอัตราเร็วของคลื่นเสียงในอากาศ (เช่น 340 เมตร/วินาที โดยประมาณ) ก็จะสามารถคำนวณระยะห่างจากวัตถุได้

โมดูล HC-SR04P สามารถวัดระยะห่างจากวัตถุได้สูงสุด ประมาณ 4 เมตร และใช้แรงดันไฟเลี้ยง 3.3V หรือ 5V ได้ โมดูลนี้มีขาอินพุต TRIG (Trigger) และขาเอาต์พุต ECHO เมื่อได้รับสัญญาณพัลส์ (ความกว้างอย่างน้อย 10 ไมโครวินาที) ที่ขา TRIG จะมีการสร้างสัญญาณคลื่นเสียงออกไป หลังจากนั้นจะเกิดสัญญาณพัลส์ที่ขา ECHO ความกว้างของสัญญาณพัลส์ที่ขา ECHO จะเป็นตัวระบุระยะเวลาการเดินทางของคลื่นเสียงและสะท้อนกลับมาถึงตัวรับ

from microbit import *
from machine import *
from micropython import const
import time

TRIG_PIN, ECHO_PIN = pin13, pin12
TIMEOUT_US  = const(20000)
SOUND_SPEED = const(340*100) # cm/sec

def measure(trig_pin, echo_pin, timeout=TIMEOUT_US):
    trig_pin.write_digital(1)
    time.sleep_us(20)
    trig_pin.write_digital(0)
    tp = time_pulse_us( echo_pin, 1, timeout )
    return tp

display.off() # disable LED matrix display
TRIG_PIN.write_digital(0)

while not button_a.was_pressed():
    dt = measure( TRIG_PIN, ECHO_PIN )
    if dt != -1:
        # convert time duration to distance
        distance = (SOUND_SPEED*dt)//(2*100000)
        print( 'Distance: {} cm'.format(distance/10.0) )
    time.sleep_ms(200)
    
print('Done')

การวัดความกว้างของพัลส์ ก็ทำได้ง่าย โดยใช้คำสั่ง time_pulse_us() ในชุดคำสั่ง machine จากนั้นจะต้องแปลงค่าที่ได้วัดได้สำหรับระยะเวลา (มีหน่วยเป็นไมโครวินาที) ให้เป็นระยะทาง (มีหน่วยเป็นเซนติเมตร)

โค้ดตัวอย่างนี้เลือกใช้ขา pin12 สำหรับสัญญาณ ECHO และ pin13 สำหรับ TRIG ตามลำดับ โดยจะทำการวัดค่าที่เป็นระยะห่างจากวัตถุซ้ำไปเรื่อย ๆ จนกว่าจะหยุดเมื่อมีการกดปุ่ม Button A

โค้ดตัวอย่างที่ 18: การใช้งานไอซี MCP4921 SPI DAC

MCP4921 ของบริษัท Microchip เป็นไอซีประเภท DAC (Digital-to-Analog Converter) มีเพียงเอาต์พุต 1 ช่องสัญญาณ (แต่ถ้าเป็น MCP4922 จะมี 2 ช่อง) มีความละเอียดของข้อมูล เท่ากับ 12 บิต (4096 ระดับ) และรับข้อมูลโดยใช้บัส SPI

ตัวอย่างนี้ สาธิตการสร้างสัญญาณรูปไซน์ (Sinusoidal Waveform) หนึ่งคาบ โดยใช้ไอซี MCP4921 DAC สร้างสัญญาณเอาต์พุตแบบแอนะล็อก ใช้แรงดันไฟเลี้ยงและแรงดันอ้างอิง (Reference Voltage) เท่ากับ +3.3V

ถ้าจะลองต่อวงจรบนเบรดบอร์ด ก็ให้ใช้ตัวถังของไอซีแบบ PDIP-8 มี 8 ขา ดังนี้

  • VDD เป็นขาแรงดันไฟเลี้ยง

  • /CS เป็นขาสัญญาณอินพุต Chip-Select ทำงานแบบ Active-Low

  • SCK เป็นขาสัญญาณอินพุต Serial Clock Input (ความถี่สูงสุดที่ใช้ได้คือ 20MHz)

  • SDI เป็นขาอินพุตสำหรับข้อมูลที่เลื่อนเข้าทีละบิต Serial Data Input

  • /LDAC เป็นขาอินพุตสำหรับสัญญาณควบคุม DAC Ouput Latch ทำงานแบบ Active-Low โดยทั่วไป ให้ต่อกับ GND ของวงจร เพื่อให้เอาต์พุตที่การเปลี่ยนแปลงโดยอัตโนมัติหลังจากเขียนข้อมูล 16 บิต (เมื่อ /CS เปลี่ยนจาก LOW เป็น HIGH)

  • VREFA เป็นขาแรงดันไฟฟ้าอ้างอิงสำหรับการสร้างสัญญาณแบบแอนะล็อก

  • AVSS เป็นขา Ground (GND)

  • VOUTA เป็นขาสัญญาณเอาต์พุตแบบแอนะล็อก (DAC Output)

การเขียนข้อมูลไปยัง MCP4921 มีครั้งละ 2 ไบต์ สำหรับข้อมูล 16 บิต (ส่งข้อมูลบิตที่ 15..8 และบิตที่ 7..0 ตามลำดับ) ส่งผ่านบัส SPI ไมโครบิตทำหน้าที่เป็น SPI Master และไอซี MCP4921 เป็น SPI Slave เลือกใช้โหมดการทำงานของ SPI เป็น (CPOL=1, CPHA=1) หรือโหมด 3 และลองใช้ความถี่ เช่น 1MHz

ข้อมูล 16 บิต มีการแบ่งออกเป็น 2 ส่วน คือ ส่วนแรกมี 4 บิต เรียกว่า Config Bits และส่วนที่สองมี 12 บิต เรียกว่า Data Bits ซึ่งเป็นตัวกำหนดระดับแรงดันเอาต์พุตที่ขา VOUTA

กลุ่มคำสั่งที่เกี่ยวข้องกับการใช้งานบัส SPI คือ machine.spi และเริ่มต้นโดยการใช้คำสั่ง spi.init() ซึ่งมีอาร์กิวเมนต์ดังนี้

  • ความเร็วในการส่งข้อมูล (baudrate)

  • ขนาดข้อมูลหนึ่งเฟรม (bits = จำนวนบิตในการเลื่อนข้อมูล) เช่น 8 บิต

  • โหมดการทำงานของ SPI ที่ต้องการเลือกใช้งาน เลือกโหมด (0,0) หรือ (1,1)

  • ขา GPIO ที่ใช้งานสำหรับสัญญาณ SCK (Serial Clock), MOSI (Master-Out / Slave-In) และ MISO (Master-In / Slave-Out) ของบัส SPI

ถ้าต้องการส่งข้อมูลออกไป ก็ใช้คำสั่ง spi.write() ซึ่งจะต้องระบุอาร์กิวเมนต์ที่เป็นบัฟเฟอร์ข้อมูล เช่น มีชนิดข้อมูลเป็น bytearray และมีขนาดหรือความยาวตามจำนวนไบต์ที่ต้องการส่ง

from microbit import *
from micropython import const
import time
import math

SCK_PIN  = pin13 # -> MCP4921 SCK pin
MOSI_PIN = pin15 # -> MCP4921 SDI pin
MISO_PIN = pin14 # not used
CS_PIN   = pin16 # -> MCP4921 /CS pin

CS_PIN.write_digital(1)
spi.init( baudrate=1000000, bits=8, mode=3,
         sclk=SCK_PIN,
         mosi=MOSI_PIN,
         miso=MISO_PIN )

spi_buf = bytearray(2)

def write_output( value ):
    global spi_buf
    spi_buf[0] = 0x70 | ((value >> 8) & 0x0f)
    spi_buf[1] = value
    CS_PIN.write_digital(0) # /CS low
    spi.write( spi_buf )    # SPI data transfer 
    CS_PIN.write_digital(1) # /CS high

N_BITS = const(12)
MAX_VALUE = const(2**N_BITS - 1)
N_STEPS = const(128)

for i in range(N_STEPS):
    v = math.sin( 2*math.pi*i/N_STEPS )
    write_output( int((1+v)*MAX_VALUE/2) )
    time.sleep_us( 500 )

write_output( MAX_VALUE//2 )

อ้างอิงจากเอกสาร Datasheet ของ MCP4921 ขาเอาต์พุต VOUTA สามารถจ่ายหรือรับกระแสได้ไม่เกิน 25mA ดังนั้น ถ้าต่อวงจร LED อนุกรมกับตัวต้านทานจำกัดกระแส เช่น 330 โอห์ม ที่ขาเอาต์พุตดังกล่าว ก็สามารถสังเกตการเปลี่ยนแปลงของระดับแรงดันไฟฟ้าได้ (ซึ่งจะส่งผลต่อความสว่างของ LED) ในกรณีที่ไม่มีเครื่องวัดคลื่นสัญญาณ เช่น ออสซิลโลสโคป

โค้ดตัวอย่างที่ 19: การใช้ไอซี PCF8574 แสดงสถานะลอจิกของโมดูล LED ที่มี 8 ดวง

ตัวอย่างนี้ เป็นการแสดงสถานะติดหรือดับ (ON หรือ OFF) สำหรับโมดูล LED Bar ที่มี 8 ดวง โดยใช้ไอซี PCF8574 (8-bit I/O Expander) เป็นตัวกำหนดสถานะเอาต์พุต 8 บิต (ในตัวอย่างนี้ PCF8574 ทำงานในโหมดเอาต์พุตเท่านั้น)

โมดูลหรือไอซี PCF8574 ใช้วิธีสื่อสารข้อมูลกับไมโครบิตได้ด้วยบัส I2C และในตัวอย่างนี้ได้กำหนดให้โมดูลนี้ มีแอดเดรสเท่ากับ 0x20 และในการเชื่อมต่อสายกับไมโครบิต ก็ได้ใช้ขา pin19 และ pin20 สำหรับสัญญาณ SCL และ SDA ของบัส I2C ตามลำดับ

ไมโครบิตทำหน้าที่เป็น I2C Master และโมดูล PCF8574 ทำหน้าที่เป็น I2C Slave คอยรับข้อมูลหนึ่งไบต์ เพื่อนำมากำหนดสถาะลอจิกสำหรับเอาต์พุต 8 บิต ที่นำไปต่อกับโมดูล 8-Bit LED Bar

from microbit import *
from micropython import const 
import time

PCF8574_ADDR = const(0x20) 
i2c.init( freq=100000, scl=pin19, sda=pin20)

display.clear() # clear display

if PCF8574_ADDR not in i2c.scan():
    print('Device not found!')
    display.show( Image.NO )

data = 0x01 # set the initial value 
buf  = bytearray(1) # single-byte buffer array

while True:
    buf[0] = ~data # invert logic 
    try:
        i2c.write( PCF8574_ADDR, bytes(buf) )
    except Exception:
        break 
    # rotate shift-left
    data = (data << 1) | (data >> 6)
    time.sleep_ms(100)

ข้อสังเกต: โมดูล LED Bar ที่ได้เลือกมาใช้งานนั้น มีขาอินพุต-ดิจิทัล 8 ขา LED0 .. LED7 ใช้กำหนดสถานะของ LED แต่ละดวง ทำงานแบบ Active-Low และมีขา VCC สำหรับป้อนแรงดันไฟเลี้ยง (ใช้ 3.3V)

เมื่อทดสอบการทำงานของโค้ดกับอุปกรณ์จริง จะเห็นได้ว่า จะมีเพียง LED หนึ่งดวงในแต่ละช่วงเวลา ที่อยู่ในสถานะ ON (สว่าง) และตำแหน่งของ LED ที่สว่าง จะถูกเลื่อนไปเรื่อย ๆ แล้ววนซ้ำใหม่

ข้อสังเกต: โมดูล PCF8574(A) ที่ได้เลือกมาใช้งานนั้น มีขา A2..A0 สำหรับเอาไว้กำหนดค่าบิตของแอดเดรส ดังนั้นแต่ละอุปกรณ์จึงมีแอดเดรสแตกต่างกันได้ในบัสเดียวกัน ในรูปตัวอย่าง มีการใช้ Jumper (สีเหลือง 3 อัน) กำหนดค่าบิตเป็น 0 หรือ 1 ให้กับขา A2..A0

โค้ดตัวอย่างที่ 20: การกำหนดสถานะลอจิกของโมดูล LED ที่มี 8 ดวง โดยใช้ค่าอินพุตแอนะล็อก

ตัวอย่างนี้สาธิตการอ่านค่าจากโมดูลที่มีตัวต้านทานปรับค่าและเลื่อนตำแหน่งได้แบบเชิงเส้น (Linear Potentiometer) โดยนำมาใช้เป็นวงจรแบ่งแรงดัน (Voltage Divider) ใช้แรงดันไฟเลี้ยง 3.3V และเลือกใช้ขา pin0 สำหรับอ่านค่าแรงดันอินพุต (อยู่ในช่วง 0V ถึง 3.3V) ที่ได้จากวงจรแบ่งแรงดันดังกล่าว

ค่าที่อ่านได้จากขาอินพุต pin0 จะอยู่ในช่วง 0..1023 (เนื่องจาก ADC มีความละเอียด 10 บิต) แต่จะถูกนำมาแปลงให้เป็นเลขจำนวนเต็ม ให้มีค่าอยู่ในช่วง 0..9 และเก็บไว้ในตัวแปร value จากนั้นจะนำไปใช้กำหนดสถานะการทำงานของโมดูล LED Bar ที่มีจำนวน 8 ดวง

ค่าของตัวแปร value จะถูกใช้ในการกำหนดจำนวนหรือระดับของ LED ที่อยู่ในสถานะ ON เช่น ถ้า value มีค่าเท่ากับ 0 ซึ่งเป็นค่าต่ำสุด ก็จะไม่มีดวงใด LED สว่าง หรือถ้าใช้ค่าสูงสุดคือ 9 จะทำให้ LED ทุกดวงสว่าง เป็นต้น

from microbit import *
from micropython import const 
import time

ANALOG_PIN = pin0 
PCF8574_ADDR = const(0x20)

i2c.init( freq=100000, scl=pin19, sda=pin20)

buf = bytearray(1)
last_value = 0

def update_leds(value):
    data = 0
    for i in range(8):
        data = (data << 1) | int(i < value)
    buf[0] = ~data
    try:
        i2c.write( PCF8574_ADDR, bytes(buf) )
    except Exception:
        return False # error
    return True # ok
    
while True:
    # read analog value and map it to a range 0..9
    value = ANALOG_PIN.read_analog()//120 
    if last_value != value:
        print( value )
        if not update_leds(value):
            break
        last_value = value
    time.sleep_ms(10)

โค้ดตัวอย่างที่ 21: การใช้โมดูล Rotary Encoder เป็นอุปกรณ์อินพุต

โดยทั่วไปแล้ว โมดูล Rotary Encoder มีขาสัญญาณเอาต์พุต A และ B (หรือเรียกแบบอื่น เช่น CLK กับ DT ตามลำดับ) และมีหลักการทำงานเหมือนสวิตช์ปุ่มกดคือ สถานะปรกติเป็น High (H) แต่เป็น Low (L) ถ้ามีการกดสวิตช์ในช่วงเวลาหนึ่ง

โดยปรกติขาสัญญาณ A และ B ของโมดูล Rotary Encoder มีสถานะเป็นลอจิก High (H) แต่เมื่อมีการหมุนในทิศทางตามหรือทวนเข็มนาฬิกา จะทำให้เกิดสัญญาณแบบ Pulse ที่ขา A และ B กล่าวคือ มีการเปลี่ยนจาก H->L (ขอบขาลง) หรือ L->H (ขอบขาขึ้น) ที่เกิดขึ้นตามสเต็ปการหมุน แต่ทั้งสองสัญญาณ A และ B จะมีเฟสต่างกัน (ขอบขาขึ้นหรือขาลง เกิดขึ้นไม่พร้อมกันทั้งสองสัญญาณ)

โค้ดตัวอย่างนี้ สาธิตการตรวจสอบดูว่ามีการหมุนเกิดขึ้นหรือไม่ โดยดูจากสัญญาณพัลส์ที่ขา A เกิดพัลส์แบบ High และมีความกว้าง (Pulse Width) ตามเงื่อนไขที่กำหนดไว้หรือไม่ จำนวนพัลส์ที่เกิดขึ้นขึ้นอยู่กับจำนวนสเต็ปที่หมุนไป

เมื่อเกิดพัลส์ที่สัญญาณ A ก็ตรวจสอบดูด้วยว่า สถานะลอจิกของสัญญาณ B เป็นอย่างไร เพื่อจำแนกว่า เป็นการหมุนในทิศทางใด เช่น ถ้าให้ทิศทางการหมุนเป็นตัวกำหนดว่า จะเพิ่มหรือลดค่าของตัวแปร level ที่เป็นเลขจำนวนเต็ม และให้ตัวแปร level ถูกจำกัดให้มีค่าอยู่ในช่วง 0 ถึง 9 เท่านั้น

ในตัวอย่างนี้ ได้ใช้โมดูล WS2812 Neopixel เพื่อใช้ RGB LED จำนวน 8 ดวง แสดงค่าของตัวแปร level ในขณะนั้น ค่าของตัวแปร level เป็นตัวกำหนดว่า จะมี LED ทั้งหมดกี่ดวงที่อยู่ในสถานะ ON (สว่าง)

ขา pin16 ของไมโครบิต ได้ถูกเลือกใช้เป็นขาเอาต์พุตสำหรับขา DIN ของโมดูล Neopixel และใช้แรงดันไฟเลี้ยง +3.3V จากโมดูลเสริมที่นำมาต่อกับ Edge Connector ของบอร์ดไมโครบิต

from microbit import *
from machine import *
from neopixel import NeoPixel
from micropython import const
import time

GREEN = (0,255,0)    # default color
NUM_LEDS = const(8)  # number of WS2812 LEDs
T_MIN = const(10000) # in microseconds
T_MAX = const(30000) # in microseconds

display.off() # turn off display

WS2812_PIN = pin16
PIN_A = pin13
PIN_B = pin14 

PIN_A.set_pull( PIN_A.NO_PULL )
PIN_B.set_pull( PIN_B.NO_PULL )

np = NeoPixel( WS2812_PIN, NUM_LEDS )
for i in range(NUM_LEDS):
    np[i] = GREEN
    np.show()
    time.sleep_ms(200)
    
np.clear() # turn off all LEDs
np.show() 

level = last_level = 0

while True:
    t = time_pulse_us( PIN_A, 1, T_MAX )
    if t >= T_MIN:
        if PIN_B.read_digital():
            level = level-1 # decrement level
        else:
            level = level+1 # increment level
        level = max(0, min(level, NUM_LEDS))
    if last_level != level:
        # if the level was changed, 
        # then update the colors of the LEDs.
        last_level = level
        for i in range(NUM_LEDS):
            if i < level:
                np[i] = GREEN
            else:
                np[i] = (0,0,0)
            np.show()
        print (level, t)

โค้ดตัวอย่างที่ 22: การรับส่งข้อมูลแบบ UART Loopback

บอร์ดไมโครบิตสามารถสื่อสารแบบ Serial กับอุปกรณ์อื่นได้ เนื่องจากมีวงจร UART อยู่ภายใน nRF51822 โดยจะต้องเลือกใช้ขา Pin จำนวน 2 ขา สำหรับใข้งานเป็นขา TXD (ส่งข้อมูลออก) และ RXD (รับข้อมูลเข้ามา)

โค้ดตัวอย่างนี้ สาธิตการใช้อุปกรณ์หรือโมดูล USB-to-Serial (ทำงานที่ระดับ +3.3V และใช้ไฟเลี้ยงจากพอร์ต USB) นำมาต่อเข้ากับขา pin0 และ pin1 เพื่อใช้เป็นขา TXD และ RXD ตามลำดับ และคอยรับข้อความจากคอมพิวเตอร์ของผู้ใช้ จากนั้นเมื่อได้รับแล้วก็ส่งข้อความนั้นกลับไป ดังนั้นจึงเป็นการทดสอบการใช้งานในรูปแบบที่เรียกว่า UART Loopback แต่ถ้าได้รับข้อความว่า 'exit' หรือกดปุ่ม Button A ของบอร์ดไมโครบิต จะจบการทำงานของโปรแกรม

Microbit Pin

USB-to-Serial Pin

pin0 (TXD)

RX

pin1 (RXD)

TX

GND

GND

from microbit import *

uart.init(baudrate=9600, tx=pin0, rx=pin1)
uart.write( b'UART loopback test...\r\n' )

while True:
    if button_a.was_pressed():
        break
    data = uart.read() # read next line as bytes
    if data:
        # strip and convert received data to lowercase
        data = data.strip().lower()
        if data.find(b'exit') == 0:
            uart.write( b'Switching back to REPL...\r\n' )
            break
        else:
            uart.write( data )
            uart.write( b'\r\n' )

# switch back to REPL console
sleep(1.0)
uart.init(baudrate=115200)
print('Done...')

โปรแกรมสำหรับคอมพิวเตอร์ของผู้ใช้ที่ได้นำมาใช้ตัวอย่างนี้คือ Arduino IDE - Serial Monitor เช่น ส่งข้อความและรับข้อความตอบกลับจากไมโครบิตได้ (ตั้งค่า Baudrate ให้ตรงกับ 9600 สำหรับตัวอย่างนี้)

ข้อสังเกต: โดยปรกติ วงจร UART จะถูกใช้ในการสื่อสารกับ REPL ของไมโครไพธอน แต่หลังจากได้ใช้คำสั่ง uart.init() ที่มีการระบุขาสำหรับ TXD และ RXD และกำหนดค่า Baudrate หรือความเร็วในการรับส่งข้อมูลแล้ว จะไม่สามารถสื่อสารผ่านทาง REPL Console ได้ต่อไป แต่ถ้าจะกลับไปสื่อสารกับ REPL Console ได้อีกครั้ง ก็ให้ทำคำสั่งดังนี้

uart.init(baudrate=115200)

โค้ดตัวอย่างที่ 23: การอ่านค่าจาก Analog Input แล้วส่งออกทาง UART

โค้ดตัวอย่างถัดไป สาธิตการอ่านค่าจากขาอินพุตแบบแอนะล็อก (Analog Input) ของบอร์ดไมโครบิต โดยเลือกใช้ขา pin2 และได้รับสัญญาณอินพุตจากโมดูลเซ็นเซอร์วัดความชื้นในดินแบบคาปาซิทีฟ (Capacitive Soil Moisture Sensor) แล้วนำค่าที่อ่านได้ (อยู่ในช่วง 0 ถึง1023) ส่งออกเป็นข้อความผ่านทาง UART โดยเลือกใช้ขาภายนอก pin0 และ pin1 สำหรับ TXD และ RXD ตามลำดับ

from microbit import *
import time

analog_pin = pin2

print( 'Analog read with UART output...' )
print( 'Press button A to stop...' )

# use UART with external pins (pin0 and pin1)
uart.init(baudrate=9600, tx=pin0, rx=pin1)

last_time = time.ticks_ms()
while True:
    if button_a.was_pressed():
        break
    now = time.ticks_ms()
    if time.ticks_diff(now, last_time) >= 500:
        last_time = now
        values = []
        for i in range(9):
            values.append( analog_pin.read_analog() )
        value = sorted(values)[4]
        uart.write( str(value) + '\r\n' )

# switch back to REPL serial-console
sleep(1.0)
uart.init(baudrate=115200)
print('Done...')

โมดูลเซ็นเซอร์วัดความชื้นในดินที่ได้เลือกมาลองใช้งาน จะตอบสนองต่อระดับความชื้นในดินดังนี้ ถ้ามีความชื้นมาก หรือมีปริมาณน้ำมาก หรือนำไปจุ่มน้ำในระดับหนึ่ง จะได้แรงดันไฟฟ้าที่ลดลง หรืออ่านค่าตัวเลขได้น้อยลง

ข้อความที่ถูกส่งออกไปนั้น จะมีหนึ่งค่าตัวเลขต่อหนึ่งบรรทัด และถ้าใช้โปรแกร Arduino IDE - Serial Plotter ก็จะเห็นกราฟเชิงเส้นจากข้อมูลที่ได้รับมาตามลำดับ

จากรูปกราฟตัวอย่างที่ได้จากการทดลอง สังเกตได้ว่า มีช่วงเวลาหนึ่งที่ค่าตัวเลขลดต่ำลงอย่างรวดเร็วแล้วเพิ่มขึ้น ซึ่งเกิดขึ้นเนื่องจากได้มีการเทน้ำในช่วงเวลาสั้น ๆ ลงในกระถางบริเวณที่มีแท่งวัดของโมดูลเซ็นเซอร์เสียบอยู่ในดิน

คำเตือน: การนำอุปกรณ์มาทดลองใช้งานนั้น เป็นไปเพื่อการสาธิตการทำงานของโค้ดตัวอย่างเท่านั้น ไม่เหมาะกับการนำไปใช้งานจริง ในสภาพแวดล้อมทั่วไป

โค้ดตัวอย่างที่ 24: การตรวจสอบปริมาณการใช้หน่วยความจำแบบ Heap

เมื่อมีการทำคำสั่งต่าง ๆ สร้างตัวแปรและอ็อปเจกต์สำหรับไมโครไพธอน จะมีการใช้หน่วยความจำ SRAM ของระบบที่ถูกจัดสรรไว้และเรียกว่า Heap ('ฮีป') ถ้าอ็อปเจกต์ใด ไม่ถูกอ้างอิงโดยตัวแปรหรือใช้งานอีกต่อไป เช่น โดยการใช้คำสั่ง del จะเป็นหน้าที่ของส่วนที่เรียกว่า Garbage Collector เพื่อจัดการและคืนหน่วยความจำ

โค้ดตัวอย่างนี้ สาธิตการใช้คำสั่งเพื่อตรวจสอบสถานะการใช้หน่วยความจำสำหรับ Heap และเรียกใช้ Garbage Collector (gc) ของไมโครไพธอน

from microbit import *
import gc # use the garbage collector

def check_mem():
    # heap memory allocted and free memory for heap
    return gc.mem_alloc(), gc.mem_free()

mem_info_text = 'Used: {:4d}, Free: {:4d} bytes'
print( mem_info_text.format( *check_mem() ) )

gc.collect() # force the garbage collector to run
print( mem_info_text.format( *check_mem() ) )

มาดูผลการรันโค้ดและความแตกต่างระหว่างการใช้โปรแกรม Mu Editor และ Thonny IDE ตามลำดับ

เฟิร์มแวร์สำหรับไมโครไพธอนที่ใช้คือ

MicroPython v1.9.2-34-gd64154c73 on 2017-09-01; micro:bit v1.0.1 with nRF51822

ถ้าใช้ Mu-Editor v1.0.2 จะได้ข้อความเอาต์พุตดังนี้

Used: 1104, Free: 8944 bytes
Used: 1056, Free: 8992 bytes

แต่ถ้าใช้ Thonny IDE v3.2.6 จะเป็นดังนี้

Used: 6192, Free: 3856 bytes
Used: 1424, Free: 8624 bytes

เราจะสังเกตเห็นความแตกต่างของปริมาณหน่วยความจำที่ใช้

โค้ดตัวอย่างที่ 25: การอ่านและแสดงค่าจากโมดูล SDS011 Dust Sensor

SDS011 เป็นโมดูลเซ็นเซอร์ราคาถูก ประเภท Air Quality Sensor / Laser Dust Sensor ที่ได้มีการพัฒนาโดยบริษัท Nova Fitness Co.Ltd. ในประเทศจีน สามารถตรวจจับและวัดความเข้มข้นของฝุ่นละอองขนาดเล็ก PM2.5 และ PM10 ได้ มีหน่วยวัดเป็นไมโครกรัมต่อลูกบาศก์เมตร (µg/m^3) มีความละเอียดในการวัดค่า 0.3 μg/m^3

โมดูล SDS011 ใช้แรงดันไฟเลี้ยง +5Vdc (4.7~5.3V) และรับส่งข้อมูลผ่าน Serial โดยใช้ขา TXD และ RXD (วงจรลอจิกทำงานที่ระดับแรงดันไฟฟ้า 3.3V) และตั้งค่า Baudrate ไว้เท่ากับ 9600 8N1

ผู้อ่านควรศึกษาคู่มือหรือเอกสาร Datasheet ของโมดูล SDS011 ก่อนนำอุปกรณ์ไปต่อทดลองใช้งาน เพื่อให้ใช้งานได้อย่างถูกต้องและไม่เกิดความเสียหาย

โดยปรกติ โมดูลเซ็นเซอร์จะส่งข้อมูลออกมา 10 ไบต์ ทุก ๆ 1 วินาที โดยอัตโนมัติ มีลำดับข้อมูลไบต์ดังนี้

  • ไบต์ที่ 0: 0xaa

  • ไบต์ที่ 1: 0xc0

  • ไบต์ที่ 2 และ 3: ค่า PM2.5 (low byte and high byte) นำไปหารด้วย 10

  • ไบต์ที่ 4 และ 5: ค่า PM10 (low byte and high byte) นำไปหารด้วย 10

  • ไบต์ที่ 6 และ 7: Reserved

  • ไบต์ที่ 7: Checksum

  • ไบต์ที่ 8: 0xab

สูตรการคำนวณค่า PM2.5 หรือ PM10 จากข้อมูล 2 ไบต์ในแต่ละกรณี

การเชื่อมต่อกับโมดูล SDS011 มีขาของคอนเนกเตอร์ดังนี้

Pin Name

Description

NC

Not Connected

1μm

PM2.5: 0-999μg/m3, PWM Output

5V

5V DC Input

2.5μm

PM10: 0-999 μg/m3, PWM Output

GND

Ground

RXD

RXD (3.3V logic level)

TXD

TXD (3.3V logic level)

ให้ใช้แรงดันไฟเลี้ยง 5V USB สำหรับโมดูล SDS011 และเชื่อมต่อกับบอร์ดไมโครบิตดังนี้

SDS011 Pin

Microbit Pin

GND

GND

RXD

Pin0 (used as Tx pin)

TXD

Pin1 (used as Rx pin)

โค้ดตัวอย่างต่อไปนี้สาธิตการอ่านค่าจากโมดูล SDS011 แล้วนำมาแสดงผลบนจอ LCD 16x2 I2C (PCF8574)

from microbit import *
from lcd_pcf8574 import LCD
import time
import gc 

# initialize the I2C
i2c.init( freq=400000, sda=pin20, scl=pin19 )
lcd = LCD( i2c, 0x3f ) # set the address of PCF8574
lcd.reset() # reset LCD 
lcd.clear() # clear LCD

# use the UART with external pins (pin0 & pin1)
uart.init(baudrate=9600, tx=pin0, rx=pin1)

def get_data():
    # switch to UART with external pins
    uart.init(baudrate=9600, tx=pin0, rx=pin1)
    uart.read(64)    # flush serial input
    data = bytes([]) # clear data buffer
    display.show(Image.HEART_SMALL)
    while True:
        if button_a.is_pressed():
            break
        b = uart.read()
        if b:
            data += b
            if len(data) >= 10:
                break
    if data and len(data)==10:
        data = data[2:] # remove the first two bytes
        pm25 = ((data[1] << 8) | data[0])/10
        pm10 = ((data[3] << 8) | data[2])/10
        # calculate the checksum byte
        checksum = sum(b for b in data[0:6]) % 256
        if checksum == data[6] and data[7] == 0xab:
            return [pm25,pm10]
        else:
            return ['error']
    return None

data = None
while True:
    if button_a.is_pressed():
        break
    pm = get_data() # read sensor values
    if pm:
        uart.init(baudrate=115200) # switch to serial-REPL
        display.show(Image.HEART)
        text = []
        if len(pm)==2:
           text.append( 'pm2.5 {} ug/m3'.format(pm[0]) )
           text.append( ' pm10 {} ug/m3\n'.format(pm[1]) )
        elif len(pm)==1 and pm[0]=='error':
           text.append( 'checksum error' )
           text.append( 16*' ' )
        # show text lines on LCD
        for i in range(len(text)): 
            lcd.goto_line( i ) 
            lcd.print( text[i] )
            print( text[i] )
        time.sleep_ms(500)
        data = None

    time.sleep_ms(500)
    display.clear()

uart.init(baudrate=115200) # switch to serial-REPL
time.sleep_ms(500)
print('Done...')

การทำงานของโค้ดตัวอย่างนี้ จะต้องใช้ UART โดยเลือกใช้ขา pin0 และ pin1 ที่เชื่อมต่อและรับข้อมูลจากโมดูล SDS011 จากนั้นเมื่อรับข้อมูลไบต์ได้ครบ 10 ไบต์แล้ว และตรวจสอบข้อมูลที่ได้รับว่าถูกต้อง จึงแปลงข้อมูลไบต์ให้เป็นค่าตัวเลขสำหรับ PM2.5 และ PM10 แล้วนำไปแสดงบนบนหน้าจอ LCD 16x2 แบบ I2C (มีแอดเดรสของอุปกรณ์ตรงกับ 0x3f) ถัดไปจึงมีการเปลี่ยนไปใช้ UART ที่เชื่อมต่อกับพอร์ต USB คอมพิวเตอร์ของผู้ใช้ เพื่อส่งข้อความไปยังหน้าต่าง REPL Terminal ของโปรแกรม Mu Editor ด้วยคำสั่ง print()

ถ้าต้องการจบการทำงานของโปรแกรม ให้กดปุ่ม Button A ค้างไว้

โค้ดตัวอย่างที่ 26: การอ่านค่าจากเซ็นเซอร์วัดอุณหภูมิแบบอินฟราเรด MLX90614

MLX90614 ของบริษัท Melexis เป็นเซ็นเซอร์วัดอุณหภูมิแบบไม่ต้องสัมผัส โดยใช้การตรวจจับรังสีอินฟราเรดจากวัตถุ (Non-Contact Infrared Temperature Sensor) แล้วนำมาคำนวณเพื่อให้ได้ค่าตัวเลขสำหรับอุณหภูมิ สื่อสารข้อมูลด้วยบัส I2C (ความเร็ว 100kHz และมีแอดเดรสตรงกับ 0x5a)

อุปกรณ์ที่ได้เลือกมาทดลองใช้งานคือ รุ่น MLX90614ESF-BAA TO-39 Package (โมดูล GY-906 / HW-691) สามารถวัดอุณหภูมิอากาศแวดล้อม (Ambient Temperature) ในช่วง -40 °C…+85 ˚C และอุณหภูมิของวัตถุ (Object Temperature) ในช่วง -70 °C…+380 ˚C มีความแม่นยำในการวัด +/- 0.5 ˚C และความละเอียด 0.02 °C (อ้างอิงจากเอกสาร Datasheet)

การอ่านข้อมูลสำหรับอุณหภูมิ จะต้องระบุแอดเดรสของรีจิสเตอร์ที่เก็บข้อมูลแต่ละตัวขนาด 16 บิตภายใน RAM ขนาด 32x16 เช่น

  • 0x06 = TA (Ambient Temperature)

  • 0x07 = TOBJ1 (Object Temperature, Zone 1)

ข้อมูลที่อ่านได้ขนาด 2 ไบต์ จะต้องนำมาแปลงให้เป็น 16-bit signed แล้วคูณด้วย 0.02 และแปลงจากหน่วย Kelvin ให้เป็น Celsius ตามลำดับ

from microbit import *
import time
from mlx90614 import *

i2c.init(freq=100000, sda=pin20, scl=pin19)
# print( [hex(a) for a in i2c.scan()] )

MLX_ADDR = 0x5a
mlx = MLX90614( MLX_ADDR )

while not button_a.is_pressed():
    ta, tobj = mlx.read_temp()
    if ta != None and tobj != None:
        print( '({:.2f},{:.2f})'.format(ta,tobj) )
    time.sleep_ms(1000)

โค้ดสำหรับไฟล์ mlx90614.py ที่จำเป็นต้องใช้ร่วมกับโค้ดสาธิตการทำงาน มีดังนี้

from micropython import const
from microbit import i2c
import ustruct

class MLX90614:
    def __init__(self, addr=0x5a):
        self.addr = addr
        
    def _read_temp(self,reg):
        try:
            i2c.write(self.addr, bytes([reg]), repeat=True)
            data = i2c.read(self.addr, 3) # read 3 bytes
        except OSError as e:
            return None
        value  = ustruct.unpack('<H', data[0:2])[0]
        pec    = data[2]
        addr_w = (self.addr << 1)
        addr_r = (self.addr << 1) | 1
        byte_seq  =  [addr_w, reg, addr_r, data[0],  data[1]]
        crc8   = MLX90614._crc8( byte_seq, len(byte_seq) )
        if crc8 == pec:
            # note: 0.02 degrees per LSB
            return ((value * 0.02) - 273.15) # in Celsius
        else: # CRC8 failed
            return None
            
    def read_temp(self): 
        # read ambient and object temperature values
        return [self._read_temp(0x06), self._read_temp(0x07)]
        
    @staticmethod
    def _crc8(data, n):
        POLYNOMIAL = 0x07 # P(x)=x^8+x^2+x^1+1 = 100000111
        crc = 0x00 # init-value
        for i in range(n):
            crc ^= data[i]
            for j in range(8):
                if crc & 0x80:
                    crc = (crc << 1) ^ POLYNOMIAL
                else:
                    crc = (crc << 1)
        return crc & 0xff

จากการทำงานของโค้ดตัวอย่าง ข้อมูลที่ถูกส่งออกมาเป็นข้อความเอาต์พุต แสดงค่าตัวเลขสำหรับอุณหภูมิอากาศแวดล้อม และอุณหภูมิของวัตถุ เช่น สำหรับการทดลอง ได้นำแก้วที่ใส่กาแฟร้อน มาวางอยู่ห่างจากโมดูลเซ็นเซอร์ประมาณ 1 cm.

เราสามารถใช้โปรแกรม Mu Editor รับข้อความและแสดงรูปกราฟได้ง่าย เนื่อจากข้อความแต่ละบรรทัดที่ถูกส่งออกมา มีตัวเลข 2 ค่า ได้แก่ อุณหภูมิอากาศแวดล้อม และอุณหภูมิของวัตถุ ตามลำดับ เราจะมองเห็นกราฟ 2 เส้น ในหน้าต่าง Plotter ของ Mu Editor

เราพอจะมองเห็นการนำอุปกรณ์นี้ไปประยุกต์ใช้งาน เช่น การตรวจจับการเปลี่ยนแปลงอุณหภูมิของวัตถุ เปรียบเทียบกับอุณหภูมิอากาศแวดล้อม เป็นต้น

ข้อสังเกต: ปัจจัยอย่างเช่น ระยะห่างของวัตถุจากเซ็นเซอร์ สัมประสิทธิ์การแผ่รังสีความร้อน (Emissivity) ของวัตถุ มีผลต่อค่าของอุณหภูมิที่วัดได้

กล่าวสรุป

บอร์ดไมโครบิตสามารถนำมาใช้ เพื่อฝึกเขียนโค้ดด้วยภาษาไมโครไพธอนได้ และใช้ร่วมกับโมดูลหรือวงจรอิเล็กทรอนิกส์รูปแบบต่าง ๆ ได้ เหมาะสำหรับผู้เริ่มต้นเรียนรู้ภาษาไพธอนและใช้งานไมโครคอนโทรลเลอร์ในเบื้องต้น หรือมีบอร์ดไมโครบิตอยู่แล้ว

อย่างไรก็ตาม เนื่องด้วยข้อจำกัดของตัวประมวลผลหลัก (nRF51822) ของบอร์ดไมโครบิต (บอร์ดเวอร์ชันแรก v1.3 หรือ v1.5) อย่างเช่น ความเร็วในการประมวลผล (16MHz) ความจุของหน่วยความจำ Flash และ SRAM ที่ค่อนข้างน้อย อีกทั้งเฟิร์มแวร์ของไมโครไพธอนที่ใช้งานได้กับบอร์ดไมโครบิต (เป็นเวอร์ชัน MicroPython v1.9.2 Build 2017-09-01 / microbit v1.0.1) ดังนั้นการนำไปใช้งานที่มีความซับซ้อน อาจจะไม่เหมาะสม เมื่อเปรียบเทียบกับบอร์ดไมโครคอนโทรลเลอร์ที่เป็นตัวเลือกอื่น เช่น STM32 หรือ ESP32 เป็นต้น

เผยแพร่ภายใต้ลิขสิทธิ์ Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)

Last updated