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

# STM32 Code Examples

เนื้อหาในส่วนนี้มีตัวอย่างโค้ดในภาษาไมโครไพธอนเพื่อนำไปทดลองใช้กับบอร์ด **STM32F411CE** ได้นำเสนอไว้เพื่อเป็นแนวทางการเรียนรู้ และสาธิตให้เห็นว่า ภาษาไมโครไพธอน ซึ่งแม้ว่าเป็นภาษาคอมพิวเตอร์ระดับสูงกว่า **C/C++** ก็สามารถนำมาใช้ในการเรียนรู้หลักการทำงานของไมโครคอนโทรลเลอร์ได้เช่นกัน&#x20;

{% hint style="info" %}
ผู้อ่านควรมีความรู้พื้นฐานเกี่ยวกับการเขียนโค้ดภาษาไพธอนมาบ้างแล้ว และการทำความเข้าใจโค้ดตัวอย่างสำหรับ **STM32** เกี่ยวข้องกับวงจรภายในไมโครคอนโทรลเลอร์ตระกูล **STM32F4** และการทำงานของฮาร์ดแวร์ประเภทต่าง ๆ ที่นำมาต่อเพิ่ม&#x20;
{% endhint %}

## **การใช้งาน Thonny IDE สำหรับไมโครไพธอน**

ซอฟต์แวร์ประเภท **Python IDE** ที่นำมาใช้ในการเขียนโค้ดสำหรับไมโครไพธอน มีอยู่หลายตัวเลือก เอกสารนี้แนะนำการใช้งาน [**Thonny IDE (Python IDE for beginners**](https://thonny.org/)) ในเบื้องต้น ซึ่งเป็นซอฟต์แวร์ประเภท **Open Source** และนำไปติดตั้งใช้งานได้หลายระบบปฏิบัติการ (**Windows**, **Mac OS** และ **Linux**)

ถ้าใช้ **Linux** เช่น **Ubuntu** หรือ **Raspbian OS** (สำหรับบอร์ด **Raspberry Pi**) ในโหมด **GUI Desktop** ก็ให้ทำคำสั่งต่อไปปนี้เพื่อติตตั้งใช้งาน (ใช้ **Python 3** และคำสั่ง **pip3**)&#x20;

`$ sudo apt-get install python3-tk`

`$ pip3 install thonny`

และเรียกใช้ โดยทำคำสั่ง&#x20;

`$ thonny &`

เริ่มต้นการใช้งานโดยเปิดโปรแกรม **Thonny IDE** แล้ว ให้ทำเมนูคำสั่ง **Run > Select Interpreter** เลือก **MicroPython (generic)** และเลือกพอร์ต (**Port**) ที่กำลังเชื่อมต่อกับบอร์ด **STM32** ในขณะใช้งาน

![รูปภาพ: ตัวอย่างการตั้งค่าในหน้า Tab "Interpreter" (Windows)](/files/-MIhRIaQfY1mDsvRoSct)

![รูปภาพ: ตัวอย่างการตั้งค่าในหน้า Tab "Interpreter" (Linux/Raspbian OS)](/files/-MJ1lu-yUQNAVZdWn_bv)

![รูปภาพ: หน้าต่างหลักของ Thonny IDE และ REPL Shell ที่พร้อมใช้งาน](/files/-MIhRauZNH2THmBJgIJu)

เมื่อเชื่อมต่อได้แล้ว จะมองเห็นข้อความในบริเวณ **Shell (REPL)** ในรูปภาพตัวอย่างแสดงให้เห็นว่า สามารถเชื่อมต่อกับไมโครไพธอน (เช่น เวอร์ชัน **v1.12**) สำหรับบอร์ด **STM32F411CE** ได้แล้ว ผู้ใช้สามารถลองพิมพ์และรันคำสั่งของไมโครไพธอนผ่าน **REPL Shell** (>>>)

![รูปภาพ: ตัวอย่างการทำคำสั่งใน REPL Shell](/files/-MIhmn1o__cmSp6_yXxR)

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

ถ้าจะลองรันโค้ดที่เปิดอยู่ในบริเวณ **Code Editor** ก็ให้กดปุ่ม **Run** หรือถ้าจะหยุดการทำงานของโค้ดที่กำลังรันอยู่ ก็ให้ปุ่ม **Ctrl+C** หรือถ้าจะรีเซตบอร์ด (**Soft Reboot**) ก็ให้กดปุ่ม **Ctrl+D**&#x20;

![รูปภาพ: ไดแกรมแสดงตำแหน่งขา PinOut ของบอร์ด STM32F411CEU6 BlackPill](/files/-MIhq-IbS7DBcNZznt2R)

ข้อมูลต่าง ๆ เกี่ยวกับบอร์ด [**WeAct Studio Mini-F4** **(STM32F4x1**)](https://github.com/WeActTC/MiniF4-STM32F4x1/) สามารถศึกษาได้จาก **URL** ต่อไปนี้

{% embed url="<https://github.com/WeActTC/MiniF4-STM32F4x1/>" %}

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

ตัวอย่างโค้ดแรกนี้ สาธิตการทำงาน **LED** ที่ต่อกับขา **PC13** ของ **STM32F411CE** กระพริบได้ โดยเว้นระยะเวลา **100** มิลลิวินาที **(msec)**&#x20;

ถ้าต้องการหยุดการทำงานของโค้ด ให้กดปุ่ม **Ctrl+C** เมื่อใช้ **Thonny IDE** ในการเขียนและรันโค้ดตัวอย่าง

ภายใต้ **/flash** ซึ่งเป็น **Flash Filesystem** ของไมโครไพธอน เราจะเห็นไฟล์ชื่อ **boot.py** และ **main.py** เมื่อบอร์ดถูกรีเซตและเริ่มทำงาน จะมีการทำคำสั่งต่าง ๆ ในไฟล์ **boot.py**&#x20;

ถ้าในไฟล์ **boot.py** มีคำสั่ง **`pyb.main('main.py')`** จึงจะรันโค้ดในไฟล์ **main.py** โดยอัตโนมัติ

```python
import machine
import time

LED_OFF = 1
# use the onboard Blue LED
led = machine.Pin('PC13', machine.Pin.OUT)
try:
    while True:
        led.value( not led.value() )
        time.sleep_ms( 100 )
except KeyboardInterrupt:
    pass
finally:
    led.value( LED_OFF )
    print('Done')   
```

![รูปภาพ: ตัวอย่างการเขียนโค้ดและบันทึกลงไฟล์ main.py](/files/-MIhnHYCFrNBP110g4X3)

## โค้ดตัวอย่างที่ 2: LED Blink (I/O Polling)

โค้ดตัวอย่างนี้ สาธิตการใช้คำสั่งจากคลาส [**`pyb.Switch`**](https://docs.micropython.org/en/latest/library/pyb.Switch.html) และ [**`pyb.LED`**](http://docs.micropython.org/en/latest/library/pyb.LED.html) สำหรับปุ่มกด (**Push Button** หรือ **KEY Switch**) และ **LED** (**Blue**) ที่มีอยู่บนบอร์ด

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

คำสั่ง **`time.ticks_ms()`** ใช้อ่านค่าเวลาของระบบ เป็นเลขจำนวนเต็มบวก และมีหน่วยเป็นมิลลิวินาที&#x20;

คำสั่ง **`time.ticks_diff()`** ใช้ในการคำนวณผลต่างหรือช่วงเวลา (**Time Interval**) ระหว่างการบันทึกเวลา 2 ครั้ง (เวลาล่าสุดกับเวลาก่อนหน้านั้น) และมีหน่วยเป็นมิลลิวินาที

```python
import utime as time
import pyb
sw  = pyb.Switch() # user push button
led = pyb.LED(1)   # on-board LED (blue), PC13 pin
    
try:
    last_time = time.ticks_ms() # save timestamp
    while not sw.value():       # is button pressed ?
        now = time.ticks_ms()   # read current timestamp
        time_diff = time.ticks_diff( now, last_time )
        if time_diff >= 500:
            led.toggle()        # toggle LED
            last_time = now     # update timestamp
except KeyboardInterrupt:
    pass
finally:
    led.off() # turn off LED
    print('Done')
```

## โค้ดตัวอย่างที่ 3: LED Toggle (Event-Triggered)

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

เมื่อมีเหตุการณ์ที่เกิดขึ้น (**Event-triggered**) โดยการกดปุ่มแล้วปล่อยในแต่ละครั้ง ในกรณีนี้คือ จะส่งผลทำให้ **LED** สลับสถานะ แต่ถ้ากดปุ่มค้างไว้สัก 2–3 วินาที จะทำให้จบการทำงานของโปรแกรม หรือถ้ารันโค้ดผ่าน **REPL** และกดปุ่ม **Ctrl+C** จะทำให้จบการทำงานของโปรแกรมเช่นกัน

```python
import pyb
import utime as time

sw  = pyb.Switch() # user push button
led = pyb.LED(1)   # on-board LED (blue), PC13 pin

# set callback function for the push button.
# toggle the LED if the button switch is pressed.
sw.callback( lambda: led.toggle() )

try:
    last_time = time.ticks_ms() # save timestamp
    while True: # main loop
        now = time.ticks_ms() # get current time (msec)
        if sw.value(): # button hold pressed
            if time.ticks_diff( now, last_time ) >= 2000:
                print( 'button long pressed' )
                break # exit the while loop
        else:
            last_time = now # update timestamp
except KeyboardInterrupt: # interrupted by Ctrl+C
    pass
finally:
    sw.callback(None) # disable callback for button
    led.off()         # turn off LED
    print('Done')
```

## โค้ดตัวอย่างที่ 4: LED Blink using Hardware Timer&#x20;

ตัวอย่างถัดไปสาธิตการใช้งาน **Hardware Timer** ของ **STM32F4** จากคลาส [**`pyb.Timer`**](http://docs.micropython.org/en/latest/library/pyb.Timer.html) โดยสามารถเลือกใช้ **Timer** จากหมายเลข **TIM1**..**TIM11** ที่มีขนาด 16 บิต (ยกเว้น **TIM2** และ **TIM5** ที่มีขนาด 32 บิต) และมีตัวหารความถี่ (**Frequency Divider**) ขนาด 16 บิต

ในตัวอย่างนี้ ความถี่ของการนับ (เลือกโหมดการนับขึ้น **`pyb.Timer.UP`**) ได้ถูกตั้งค่าให้เท่ากับ **10 Hz** และมีการกำหนดฟังก์ชันให้ทำงานในรูปแบบที่เรียกว่า **Callback Function**&#x20;

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

```python
import utime as time
from machine import Pin
import pyb

led = pyb.LED(1) # on-board LED (blue)
# create Timer (select from TIM1..TIM11),
# set timer frequency = 10 Hz (for fast LED blink)
tim = pyb.Timer( 2, mode=pyb.Timer.UP, freq=10 )
tim.callback( lambda t: led.toggle() )
try:
    while True: # main loop
        pass # do nothing in main loop
except KeyboardInterrupt:
    pass
finally:
    tim.callback(None) # disable timer callback
    tim.deinit() # turn off the timer
    led.off()    # turn off the LED
    print('Done')
```

## โค้ดตัวอย่างที่ 5: LED Blink using Software Timer in Periodic Mode

ตัวอย่างถัดไปสาธิตการใช้งาน **Software Timer** (หรือเรียกว่า **Virtual Timer** สำหรับไมโครไพธอน) จากคลาส **`machine.Timer`** โดยใช้ **FreeRTOS** เป็นตัวจัดการเชิงเวลา และจะต้องกำหนดหมายเลข **Timer ID** ให้เท่ากับ **-1**

ในตัวอย่างนี้ได้ตั้งค่าให้มีคาบการนับ เท่ากับ 500 มิลลิวินาที เมื่อตัวนับเริ่มนับขึ้นจาก 0 จนครบเวลาหนึ่งคาบของการนับ (**Count Overflow Event**) จะมีการเรียกฟังก์ชันสำหรับ **Callback** และจะเกิดซ้ำไปเรื่อย ๆ (**Periodic Mode**)

```python
import pyb
from machine import Timer
import utime as time

sw  = pyb.Switch()  # user push button
led = pyb.LED(1)    # on-board LED (blue)
# create a software timer in periodic mode 
tim = Timer(-1)
tim.init( mode=Timer.PERIODIC,
          period=500, # period in msec
          callback=lambda t: led.toggle() )
try:
    while True:
        if sw.value(): # check the button's state
            break
except KeyboardInterrupt:
    pass
finally:
    sw.callback(None)
    led.off()
    tim.deinit()
    print('Done')
```

## โค้ดตัวอย่างที่ 6: LED Pulsing using Software Timer in One-Shot Mode

ตัวอย่างถัดไปเป็นการใช้งาน **Software Timer** (ไทม์เมอร์แบบซอฟต์แวร์) จากคลาส **`machine.Timer`** แต่เลือกใช้โหมด **One Shot** แทน **Periodic** ซึ่งหมายความว่า ถ้าครบคาบเวลา จะมีการเรียกฟังก์ชันสำหรับ **Callback** หนึ่งครั้ง และทำเพียงครั้งเดียว (ไม่ทำซ้ำ)

แต่ให้สังเกตว่า การเปิดใช้งาน **Software Timer** ในกรณีนี้ จะเกิดขึ้นเมื่อมีการกดปุ่มบนบอร์ดหนึ่งครั้ง เมื่อปุ่มถูกกดจะทำให้ฟังก์ชัน **`start_timer()`** ทำงาน แล้วเปิดใช้งานไทม์เมอร์ในโหมด **One Shot** และจะต้องรอให้ผ่านไปครบหนึ่งคาบก่อน (ซึ่งในกรณีคือ 1000 มิลลิวินาที) จึงจะมีการเรียกฟังก์ชัน **`led_pulses()`** ให้ทำงานในลำดับถัดไป เมื่อทำให้ **LED** กระพริบไปจนครบ 10 ครั้ง แล้วจึงจบการทำงานของโปรแกรม

```python
import pyb
from machine import Timer
import utime as time

done = False # global variable

def start_timer(): # callback for button
    sw.callback( None ) # disable callback for button
    tim.init( mode=Timer.ONE_SHOT,
          period=1000, # in msec
          callback=led_pulses )
          
def led_pulses(t): # callback for timer
    global done
    led = pyb.LED(1) # on-board LED (blue)
    # blink the LED for 10 times 
    for i in range(20):
        led.toggle()
        time.sleep_ms(100)
    t.deinit() # disable timer 
    done = True
    
tim = Timer(-1)    # create a virtual timer 
sw  = pyb.Switch() # use onboard button
sw.callback( start_timer ) # set callback for button
try:
    while not done:
        pass # do nothing in the main loop
except KeyboardInterrupt:
    pass
finally:
    print('Done')
```

## โค้ดตัวอย่างที่ 7: LED Blink using Hardware Timer in PWM Mode

ตัวอย่างถัดไปสาธิตการเปิดใช้งาน **Hardware Timer** ของ **STM32F4** จากคลาส **`pyb.Timer`** เพื่อสร้างสัญญาณ **PWM** (**Pulse Width Modulation**) ที่จะต้องกำหนดความถี่ (**Frequency**) ในหน่วยเป็น **Hz** และความกว้างของพัลส์ (**Pulse Width**) ในหน่วยเป็นไมโครวินาที (**usec**) สำหรับช่วงที่ลอจิกของสัญญาณเป็น **High** (ซึ่งหมายถึง การกำหนดค่า **Duty Cycle** ของสัญญาณ **PWM**)

การใช้งาน **Hardware Timer** เพื่อสร้างสัญญาณ **PWM** เป็นเอาต์พุตได้ จะต้องเลือกจาก **TIM1..TIM11** และเลือกช่องสัญญาณ (**Channel**) ให้ถูกต้องได้ ในตัวอย่างนี้ เราได้เลือกใช้ **TIM4** (ขนาด 16 บิต) ที่มีช่อง **CH1 .. CH4** ให้เลือกใช้ได้ ถ้าเลือก **TIM4\_CH3** จะตรงกับขา **PB8** แต่ถ้าเลือก **TIM4\_CH4** จะตรงกับขา **PB9** เป็นต้น (ดูแผนผัง **PinOut** ของบอร์ด)

ในตัวอย่างนี้ ได้เลือกใช้ **TIM4\_CH3** สำหรับขา **PB8** ซึ่งจะต้องนำไปต่อกับวงจร **LED** ภายนอก มีการตั้งความถี่ให้เท่ากับ **5 Hz** (มีคาบเท่ากับ 200 มิลลิวินาที) และค่า **Pulse Width** ให้เท่ากับครึ่งหนึ่งของคาบเวลา หรือจะได้ค่า **Duty Cycle** เท่ากับ **50%**

คำสั่งที่เกี่ยวข้องกับ **`pyb.Timer`** เช่น คำสั่ง **`freq()`** จะให้ค่าความถี่ที่ได้ตั้งค่าไว้ใช้ (มีหน่วยเป็น **Hz**) คำสั่ง **`prescaler()`** จะให้ตัวเลขสำหรับตัวหารความถี่ (**Prescaler**) และคำสั่ง **`period()`** จะให้ตัวเลขเป็นคาบเวลาของตัวนับ

```python
import utime as time
from machine import Pin
import pyb

# print system frequencies
freq = pyb.freq() 
print( 'CPU  freq. [Hz]:', freq[0] )  # 96 MHz
print( 'AHB  freq. [Hz]:', freq[1] )  # 96 MHz
print( 'APB1 freq. [Hz]:', freq[2] )  # 24 MHz
print( 'APB2 freq. [Hz]:', freq[3] )  # 48 MHz

# create Timer (use TIM4)
tim = pyb.Timer( 4, freq=5 ) # 5 Hz (for LED blink)
# Choose PB8 pin for TIM4_CH3 or PB9 pin for TIM4_CH4
pwm = tim.channel( 3, pyb.Timer.PWM,
         pin=pyb.Pin.board.PB8, pulse_width=0 )
pwm.pulse_width( tim.period()//2 ) # 50% duty cycle
print( 'prescaler   : {:>8}'.format( tim.prescaler()) )
print( 'frequency   : {:>8} [Hz]'.format( tim.freq()) )
print( 'source freq.: {:>8} [Hz]'.format( tim.source_freq()) )
print( 'period      : {:>8} [us]'.format( tim.period()) )
print( 'pulse width : {:>8} [us]'.format( pwm.pulse_width()) )

try:
    while True:
        pass # do nothing in the main loop
except KeyboardInterrupt:
    pass
finally:
    tim.deinit()
    print('Done')
```

ถ้าลองรันโค้ดตัวอย่างนี้ เราจะได้ข้อความเอาต์พุตและตัวเลขดังนี้

`CPU freq. [Hz]: 96000000` \
`AHB freq. [Hz]: 96000000` \
`APB1 freq. [Hz]: 24000000` \
`APB2 freq. [Hz]: 48000000` \
`prescaler : 624` \
`frequency : 5 [Hz]` \
`source freq.: 48000000 [Hz]` \
`period : 15359 [us]` \
`pulse width : 7679 [us]`

จากข้อความเอาต์พุต มีความหมายดังนี้

* ความถี่ของซีพียู (**CPU**) หรือ **SysClk** เท่ากับ **96 MHz**
* ความถี่ของการอินเทอร์เฟสด้วยบัส **AHB** เท่ากับ **96 MHz = SysClk/1**
* ความถี่ของการอินเทอร์เฟสด้วยบัส **APB1** จะได้ **24 MHz = SysClk/4**
* ความถี่ของการอินเทอร์เฟสด้วยบัส **APB2** จะได้ **48 MHz = SysClk/2**
* ความถี่ของ **Timer** (**TIM4**) ได้ตั้งค่าให้นับด้วยความถี่เท่ากับ **5 Hz**
* ตัวหารความถี่ (**Prescaler**) เท่ากับ **624** และคาบ (**Period**) เท่ากับ **15359**

จากตัวเลขเหล่านี้ เราสามารถระบุความสัมพันธ์ได้ดังนี้

$$
PWM, frequency = \frac{ Timer, frequency}{(prescaler+1) \times (period+1)}
$$

หรือ คำนวณเป็นตัวเลขได้ดังนี้

$$
PWM, frequency = \frac{48, MHz}{(624+1)\times(15359+1)} = 5,Hz
$$

**ข้อสังเกต:** วงจรไทม์เมอร์ **TIM4** ภายใน **STM32F411CE** เชื่อมต่อโดยใช้บัส **APB1** ที่มีความถี่ **24 MHz (= 96MHz /4)** แต่เนื่องจากว่า **APB1 Prescaler=4** ซึ่งมากกว่า **1** จึงมีการเพิ่มความถี่เป็น 2 เท่า สำหรับใช้เป็นความถี่ของตัวนับ (**APB Clock Timers**) และได้ความถี่เท่ากับ **48 MHz (**&#xE23;ายละเอียดศึกษาได้จาก **Clock Tree** ในเอกสาร [**Reference Manual**](https://www.st.com/resource/en/reference_manual/dm00119316-stm32f411xc-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)**)** และไฟล์ [**`timer.c`**](https://github.com/micropython/micropython/blob/master/ports/stm32/timer.c) ของ **Micropython** สำหรับ **STM32 port)**

ถ้าลองเปลี่ยนจาก **TIM4** เป็น **TIM1** (และใช้ช่อง **CH3** ซึ่งตรงกับขา **PA10**) ก็สามารถทำงานได้เช่นกัน แต่มีความแตกต่างคือ **TIM1** เชื่อมต่อกับบัส **APB2** ที่ใช้ความถี่ **48MHz** ความถี่ของตัวนับ (เป็น 2 เท่า) จะเท่ากับ **96 MHz** และถ้ากำหนดความถี่ให้ได้ **5 Hz** เหมือนเดิม จะได้ค่าสำหรับ **Period** ในกรณีนี้เท่ากับ **30719**

## โค้ดตัวอย่างที่ &#x38;**:** Dual-LED Blink using Hardware Timer in Output-Compare (OC) mode

ตัวอย่างนี้ สาธิตการทำให้ **LED** จำนวน 2 ดวง (ที่นำมาต่อวงจรเพิ่มบนเบรดบอร์ด) กระพริบได้ด้วยอัตราคงที่ โดยใช้ **Hardware Timer** ที่ทำงานในโหมด **Output Compare** (**OC**) และกำหนดให้ขา **I/O** สำหรับ **OC Output** สลับสถานะได้โดยอัตโนมัติ เมื่อตัวนับมีค่าเท่าค่าเปรียบเทียบที่ได้กำหนดไว้ (**Compare Value**)

ในตัวอย่างนี้ เราได้เลือกใช้ **Timer 3** (**TIM3**) และช่องสัญญาณ 1 และ 2 (**T3\_CH1** และ **T3\_CH2**) ซึ่งตรงกับขา **PB4 (LED1)** และ **PB5** (**LED2**) ตามลำดับ ความถี่ของ **TIM3** เท่ากับ **2 Hz** และจะทำให้ **LED** ทั้งสองดวง สลับสถานะทุก ๆ 500 มิลลิวินาที แต่ช่วงเวลาที่เกิดการสลับสถานะจะไม่พร้อมกัน

```python
import pyb

# T3_CH1 -> PB4 pin, T3_CH2 -> PB5 pin
timer = pyb.Timer(3, freq=2) # use TIM3, freq. 2 Hz
half_period = (timer.period()+1)//2
ch1 = timer.channel(1, mode=pyb.Timer.OC_TOGGLE,
            pin=pyb.Pin.board.PB4, compare=0)
ch2 = timer.channel(2, mode=pyb.Timer.OC_TOGGLE,
            pin=pyb.Pin.board.PB5, compare=half_period)
try:
    while True: 
        pass # do nothing in main loop
except KeyboardInterrupt:
    pass
finally:
    timer.deinit() # turn off timer
    print('Done')
```

## โค้ดตัวอย่างที่ &#x39;**:** LED Fading using Hardware Timer in PWM Mode

ตัวอย่างถัดไป สาธิตการใช้ **Hardware Timer** จากคลาส [**`pyb.Timer`**](http://docs.micropython.org/en/latest/library/pyb.Timer.html) ในโหมด **PWM (**&#xE40;ลือกใช้ **TIM4** ช่องสัญญาณ **CH3)** ตั้งค่าความถี่ให้เท่ากับ **1000 Hz** และปรับค่า **Duty Cycle** ให้เปลี่ยนแปลงได้โดยใช้ค่าตัวเลขที่คำนวณเก็บไว้ในอาร์เรย์ เพื่อใช้กำหนดความกว้างของพัลส์

การตั้งค่าความถี่ในตัวอย่างนี้ เราไม่ได้กำหนดค่าโดยตรง แต่ใช้อีกวิธีหนึ่งคือ กำหนดค่า **Prescaler** ให้เท่ากับ **47** และค่า **Period** ให้เท่ากับ **999**

$$
PWM,frequency = \frac{Timer,frequency}{(prescaler+1)\times(period+1)}
$$

หรือ คำนวณเป็นตัวเลขได้ดังนี้

$$
PWM,frequency = \frac{48,MHz}{(47+1)\times(999+1)} = 1000,Hz
$$

สัญญาณ **PWM** ที่ได้ (ขา **PB9**) จะถูกนำไปใช้ขับวงจร **LED** และจะเห็นได้ว่า ความสว่างของ **LED** เปลี่ยนแปลงตามค่า **Duty Cycle** ของสัญญาณ

```python
import utime as time
from machine import Pin
import pyb
import math

# create a hardware Timer (use TIM4)
tim = pyb.Timer( 4, prescaler=47, period=999 ) # TIM4
# Freq.(Hz) = APB2 freq. (Hz)/(prescaler+1)/(period+1)
#           = 48 MHz /48 /1000 = 1 kHz or 1000 Hz
# choose PB8 pin for TIM4_CH3, or PB9 pin for TIM4_CH4
pwm = tim.channel(4, pyb.Timer.PWM,
         pin=pyb.Pin.board.PB9, pulse_width=0)
print( 'PWM period   :', tim.period() )
print( 'PWM frequency:', tim.freq()   )
try:
    P = tim.period() # get PWM period
    N = 16
    steps = [int(P*math.sin(math.pi*i/N)) for i in range(N)]
    while True:
        for pw in steps:
           pwm.pulse_width( pw ) # change pulse width
           time.sleep_ms( 100 )
except KeyboardInterrupt:
    pass
finally:
    tim.deinit() # disable timer
    print('Done')
```

## โค้ดตัวอย่างที่ 1&#x30;**:** Multi-LED Blink using Software Timers

ตัวอย่างนี้สาธิตการทำงาน **LED** จำนวน 3 ดวง กระพริบได้ด้วยอัตราที่ไม่เท่ากัน (เช่น **1 Hz**, **2 Hz** และ **4 Hz** เป็นต้น) โดยใช้ **Software Timer** เป็นตัวช่วยดำเนินการ

วงจร **LED** ที่นำมาต่อเพิ่มบนเบรดบอร์ด จำนวน 3 ชุด (ต่อที่ขา **PB7**, **PB8** และ **PB9** ตามลำดับ) ทำงานแบบ **Active-Low** ซึ่งหมายความว่า ถ้าให้เอาต์พุตเป็น 0 จะทำให้ **LED** อยู่ในสถานะ **ON** แต่ถ้าเป็น 1 จะได้สถานะเป็น **OFF**

```python
import utime as time
from machine import Pin, Timer
from micropython import const
import pyb

LED_ON  = const(0)
LED_OFF = const(1)
pin_names = ['PB7', 'PB8', 'PB9'] # output pins
leds   = []
timers = []

def timer_cb(t): # timer callback function
    for i in range(len(leds)):
        if t is timers[i]:
             # toggle: read-modify-write
             x = leds[i].value()
             leds[i].value( not x )
             break
             
for pin in pin_names:      # create Pin objects
    leds.append( Pin(pin,mode=Pin.OUT_PP,value=LED_OFF) )
for i in range(len(leds)): # create Timer objects
    timers.append( Timer(-1, freq=(1<<i), callback=timer_cb) )

try:
    while True:
        pass # do nothing in the main loop
except KeyboardInterrupt:
    pass
finally:
    for led in leds:   # turn off all LEDs
        led.value(LED_OFF)
    for tim in timers: # turn off all timers
        tim.deinit() 
    print('Done')
```

![รูปภาพ: ตัวอย่างการต่อวงจรโมดูล RGB LED บนเบรดบอร์ด](/files/-MIkyF95mHQ5RAqPqgsP)

## โค้ดตัวอย่างที่ 1&#x31;**:** Button Click Counter using External Interrupt

ตัวอย่างนี้สาธิตการเขียนโค้ดเพื่อตรวจสอบการกดปุ่มภายนอก โดยใช้หลักการทำงานของไมโครคอนโทรลเลอร์ที่เรียกว่า อินเทอร์รัพท์สำหรับขา **GPIO** (หรือเรียกที่ว่า **External Interrupt**) และใช้คลาส [**`pyb.ExtInt`**](http://docs.micropython.org/en/latest/library/pyb.ExtInt.html) มีการจำแนกเหตุการณ์ได้เป็น 3 กรณีคือ ขอบขาขึ้น (**Rising Edge**) ขอบขาลง (**Falling Edge**) และเป็นได้ทั้งขอบขาขึ้นและขาลง

ในตัวอย่างนี้ เราเลือกใช้เฉพาะขอบขาลง และใช้ขา **PA0** ที่ต่อกับวงจรปุ่มกดภายนอก (ทำงานแบบ **Active-Low**) เป็นอินพุต ทุกครั้งที่เกิดเหตุการณ์ขอบขาลงที่สัญญาณอินพุต จะมีการเรียกฟังก์ชันสำหรับ **Callback** ซึ่งจะทำให้ตัวแปร **`clicked`** มีค่าเป็น **`True`** และปิดการทำงานของอินเทอร์รัพท์ดังกล่าวชั่วคราว

ค่าของตัวแปร **`clicked`** จะถูกตรวจสอบในเงื่อนไขสำหรับการทำซ้ำ ถ้ามีค่าเป็นจริง ก็ให้เพิ่มค่าของตัวนับและแสดงข้อความเอาต์พุต จากนั้นกำหนดให้ค่าตัวแปร **`clicked`** เป็น **`False`** และเปิดการทำงานของอินเทอร์รัพท์อีกครั้ง

ข้อสังเกต: การต่อวงจรปุ่มกด เมื่อกดปุ่มแล้วปล่อย อาจเกิดการกระเด้งของปุ่ม (**Bouncing**) ทำให้เกิดขอบขาขึ้นหรือขาลงที่สัญญาณอินพุตมากกว่าหนึ่งครั้งได้ วิธีแก้ไขปัญหานี้อย่างง่ายในเบื้องต้นคือ เราสามารถเลือกใช้ตัวเก็บประจุ เช่น **0.1uF** มาต่อคร่อมที่ขาสัญญาณกับ **GND**

```python
import utime as time
from machine import Pin, Timer
import pyb

clicked = False # global variable

def ext_int_cb(irq_line):
    global ext_irq, clicked
    ext_irq.disable() # disable interrupt temporarily
    clicked = True    # set flag

btn_pin = Pin('PA0', mode=Pin.IN)

ext_irq = pyb.ExtInt( btn_pin,
            pyb.ExtInt.IRQ_FALLING,
            pyb.Pin.PULL_UP, ext_int_cb )
            
try:
    cnt = 0  # initialize click counter
    while True: # main loop
        if clicked: # the button was clicked
            cnt += 1  # increment click counter
            print('Button clicked', cnt)
            clicked = False  # clear flag
            ext_irq.enable() # re-enable interrupt
        time.sleep_ms(200)
        
except KeyboardInterrupt:
    pass
finally:
    ext_irq.disable()
    print('Done')

```

![รูปภาพ: ตัวอย่างการต่อวงจรปุ่มภายนอกบนเบรดบอร์ด](/files/-MIhbC2N0axLmFcjq6Zr)

## โค้ดตัวอย่างที่ 1&#x32;**:** Rotary Encoder Reading using External Interrupt

ตัวอย่างถัดไป สาธิตการประยุกต์ใช้งานอินเทอร์รัพท์ภายนอกที่ขาอินพุต 2 ขา คือ การนำไปต่อกับโมดูลที่เรียกว่า **Incremental Rotary Encoder** โดยใช้สัญญาณ 2 เส้นคือ **A**, **B** (หรือบางที ก็ตั้งชื่อว่า **CLK, DATA**) ตามลำดับ

เมื่อมีการเปลี่ยนตำแหน่งเชิงมุมของแกนหมุนที่ตัวโมดูล จะทำให้เกิดสัญญาณพัลส์ที่ขา **A**, **B** โดยมีเฟสต่างกัน 90 องศา (**Phase Shift**) ความกว้างของพัลส์ขึ้นอยู่กับอัตราความเร็วในการหมุน และมีทิศทางการหมุนได้สองทิศทาง (หมุนทวนหรือหมุนตามเข็มนาฬิกา)

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

ในการตรวจสอบการเปลี่ยนแปลงที่สัญญาณอินพุตทั้งสอง เราสามารถเปิดใช้งานอินเทอร์รัพท์ภายนอกได้ โดยเลือกชนิดของขอบเหตุการณ์เป็นทั้งแบบ **Rising** และ **Falling Edge**

ในตัวอย่างนี้ ได้เลือกใช้ขา **PB4** และ **PB5** ที่นำไปต่อกับโมดูล **Rotary Encoder** เมื่อมีการเปลี่ยนขอบสัญญาณใด ๆ ที่ขาทั้งสอง จะมีการเรียกฟังก์ชันสำหรับ **Callback** ที่เกี่ยวข้องกับแต่ละขา และจะตรวจสอบว่า จะต้องเพิ่มหรือลดค่าของตัวนับ (ใช้ตัวแปรชื่อ **`cnt`**)

การหมุนเชิงมุมไปหนึ่งตำแหน่ง จะทำให้ตัวนับ เพิ่มขึ้นหรือลดลง ครั้งละ 4 ดังนั้นค่าของตัวนับจะถูกหารด้วย 4 เพื่อใช้เป็นค่าของตำแหน่ง (**`pos`**) นอกจากนั้นยังมีการกำหนดค่าต่ำสุดและสูงสุดไว้สำหรับค่าของตัวนับ

```python
import utime as time
from machine import Pin
import pyb
from micropython import const
# constants
POS_MAX = const(100)
POS_MIN = const(0)
# global variables 
cnt = 0
pos = 0

def ext_a_cb(irq_line): # callback for pin A
    global cnt, pos
    a, b = a_pin.value(), b_pin.value()
    step = 1 if a^b else -1
    new_cnt = cnt+step
    new_cnt = max(4*POS_MIN,new_cnt)
    new_cnt = min(4*POS_MAX,new_cnt)
    cnt = new_cnt
    pos = cnt//4

def ext_b_cb(irq_line): # callback for pin B
    global cnt, pos
    a, b = a_pin.value(), b_pin.value()
    step = -1 if a^b else +1
    new_cnt = cnt+step
    new_cnt = max(4*POS_MIN,new_cnt)
    new_cnt = min(4*POS_MAX,new_cnt)
    cnt = new_cnt
    pos = cnt//4

a_pin = Pin('PB4', mode=Pin.IN) # pin A
b_pin = Pin('PB5', mode=Pin.IN) # pin B

# enable external interrupt for pin A
ext_a = pyb.ExtInt( a_pin,
            pyb.ExtInt.IRQ_RISING_FALLING,
            pyb.Pin.PULL_UP, ext_a_cb )

# enable external interrupt for pin B
ext_b = pyb.ExtInt( b_pin,
            pyb.ExtInt.IRQ_RISING_FALLING,
            pyb.Pin.PULL_UP, ext_b_cb )

try:
    last_pos = pos
    while True: # main loop
        if last_pos != pos: # position changed ?
            print('Position: {}'.format(pos))
            last_pos = pos
        time.sleep_ms(20)
except KeyboardInterrupt:
    pass
finally:
    # disable external interrupts 
    pyb.ExtInt( a_pin,
            pyb.ExtInt.IRQ_RISING_FALLING,
            pull=pyb.Pin.PULL_NONE, callback=None)
    pyb.ExtInt( b_pin,
            pyb.ExtInt.IRQ_RISING_FALLING,
            pull=pyb.Pin.PULL_NONE, callback=None)
    print('Done')
```

![รูปภาพ: การต่อวงจรร่วมกับโมดูล Rotary Encoder](/files/-MIhe-Cw7nUC2yv4QnZl)

## โค้ดตัวอย่างที่ 1&#x33;**:** Rotary Encoder Reading using Timer in Quadrature-Encoder Mode

ตัวอย่างถัดไปสาธิตการใช้ **Hardware Timer** ในโหมดการนับ โดยใช้สัญญาณอินพุตแบบ **Quadrature Encoder** เหมือนในกรณีของโมดูล **Incremental Rotary Encoder** ซึ่งมี 2 ช่องสัญญาณ

ทุกครั้งมีการเปลี่ยนแปลงที่ขาอินพุต จะมีการเพิ่มหรือลดค่าของตัวนับโดยอัตโนมัติ ซึ่งขึ้นอยู่กับทิศทางการหมุน ตัวนับภายในจะมีค่าเพิ่มขึ้นหรือลดลงครั้งละ 4 เมื่อหมุนไปหนึ่งตำแหน่ง ดังนั้นเราจึงหารด้วย 4 แล้วนำผลลัพธ์ที่ได้มาใช้ระบุตำแหน่ง (**Position**)

นอกจากนั้นยังมีการกำหนดช่วงของตำแหน่งในตัวอย่างนี้ ให้อยู่ในช่วง **0..99** และถ้านับเกิน จะเกิด **Rollover** โดยอัตโนมัติ เช่น ถ้านับไปถึง 0 ถัดไปจะเป็น 99 หรือในทางตรงข้าม ถ้านับถึง 99 แล้ว ถัดไปคือ 0

เนื่องจากเราได้เลือกใช้ขา **PB4** และ **PB5** ถ้าจะใช้งานร่วมกับ **Timer** ก็จะตรงกับ **TIM3** และช่อง **1** และ **2** (**TIM3\_CH1** และ **TIM3\_CH2**)

```python
import utime as time
from machine import Pin
import pyb
from micropython import const

args = {'pull': pyb.Pin.PULL_NONE, 'af': pyb.Pin.AF2_TIM3}
a_pin = Pin('PB4', mode=pyb.Pin.AF_PP, **args)
b_pin = Pin('PB5', mode=pyb.Pin.AF_PP, **args)

NUM_STEPS = const(100)
timer = pyb.Timer(3, prescaler=1, period=(4*NUM_STEPS-1))
channel = timer.channel(1, pyb.Timer.ENC_AB)
timer.counter(0) # reset counter

try:
    saved_cnt = timer.counter()//4
    while True:
        cnt = timer.counter()//4
        if saved_cnt != cnt:
            saved_cnt = cnt
            print( 'Position: {}'.format(cnt) )
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
finally:
    timer.deinit() # disable timer
    print('Done')
```

## โค้ดตัวอย่างที่ 1&#x34;**: Frequency Measurement using Timer in Input Capture (IC) Mode**

วงจร **Hardware Timer** ของ **STM32** สามารถทำงานในโหมดที่เรียกว่า **Input Capture (IC)** ตัวนับจะทำงานด้วยความถี่คงที่ เมื่อมีเหตุการณ์ เช่น ขอบขาขึ้นหรือขาลง ตามที่กำหนดไว้ จะมีการอ่านค่าของตัวนับในขณะนั้น และนำไปเก็บใส่ลงในรีจิสเตอร์ที่เกี่ยวข้อง (**Input Capture Register**) ด้วยหลักการทำงานในลักษณะ เราสามารถนำมาใช้วัดความกว้างของสัญญาณพัลส์ วัดคาบของสัญญาณแบบมีคาบ เป็นต้น

ในตัวอย่างนี้ เราจะสร้างสัญญาณ **PWM** สำหรับ **R/C Servo** ที่ขา **PB4** โดยใช้ **Timer 3** ช่องหมายเลข **1 (TIM3\_CH1)** ให้มีความถี่ **50 Hz** และมีความกว้างของพัลส์อยู่ในช่วง 1000 ถึง 2000 ไมโครวินาที

ในการทดสอบการทำงานของโค้ด ขา **PB4** จะถูกเชื่อมต่อทางไฟฟ้าด้วยสายไฟภายนอก (**Jumper Wire**) กับขา **PB3** เป็นอินพุตสำหรับ **Timer 2** (**TIM2** มีขนาด 32 บิต) ช่องหมายเลข **2** (**TIM2\_CH2**) ที่ทำงานในโหมด **Input Capture** ตัวนับ **TIM2** จะทำงานด้วยความถี่ **1 MHz** (นับขึ้นทุก ๆ 1 ไมโครวินาที)

```python
import pyb
import utime as time

# Use TIM3_CH1 / PB4 to create PWM output
# Frequency = 50 Hz, period=20000 usec, pulse width 1500 usec
servo_pin = pyb.Pin.board.PB4 # PWM output pin

timer3 = pyb.Timer(3, mode=pyb.Timer.UP, prescaler=83, period=19999)

servo  = timer3.channel(1, mode=pyb.Timer.PWM, pin=servo_pin)
servo.pulse_width(0)

saved_capture = 0
t_pulse = 0

def ic_cb(timer):
    global saved_capture, t_pulse
    if ic_pin.value(): # rising edge
        saved_capture = ic.capture()
    else: # falling edge
        t_pulse = (ic.capture() - saved_capture)
        t_pulse &= 0x0fffffff

# Use TIM2_CH2/ PB3 for input capture
# Frequency = 1MHz (1 usec resolution)
ic_pin = pyb.Pin.board.PB3 # Input capture pin
timer2 = pyb.Timer(2, prescaler=83, period=0x0fffffff)
print( hex(timer2.period()), timer2.prescaler() )

ic = timer2.channel(2, mode=pyb.Timer.IC,
    pin=ic_pin, polarity=pyb.Timer.BOTH, callback=ic_cb)

try:
    values = [1000, 1250, 1500, 1750, 2000]
    while True:
        for pw in values:
            servo.pulse_width( pw )
            time.sleep_ms(100)
            print( 'Pulse width {} usec'.format(t_pulse) )
            t_pulse = 0 # clear measurement value
        time.sleep_ms(500)
except KeyboardInterrupt:
    pass
finally:
    # turn off timers
    timer2.deinit() 
    timer3.deinit() 
    print('Done')
```

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

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

โมดูล [**DHT22**](https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf) (**AM2302**) ที่ผลิตโดยบริษัท **Aosong Electronics (China)** เป็นอุปกรณ์ประเภทเซ็นเซอร์สำหรับวัดค่าอุณหภูมิ (**Temperature**) และความชื้นสัมพัทธ์ (**Relative Humidity**) ในอากาศ และมีราคาไม่แพง จึงเหมาะสำหรับนำมาฝึกเขียนโปรแกรมไมโครคอนโทรลเลอร์เพื่อการเชื่อมต่อและอ่านข้อมูล อุปกรณ์นี้ใช้เพียงสัญญาณข้อมูลเพียง 1 เส้น แบบสองทิศทาง **(Bidirectional Digital I/O**)

ไมโครไพธอนสำหรับ **STM32** ได้ร่วมไลบรารี [**`dht`**](https://github.com/micropython/micropython/blob/master/drivers/dht/dht.py) มาให้แล้ว สามารถใช้คำสั่งที่เกี่ยวข้องเพื่ออ่านค่าจากโมดูล **DHT22** ได้สะดวก

โค้ดตัวอย่างนี้เลือกใช้ขา **B5** สำหรับเชื่อมต่อกับขา **DATA** ของโมดูล **DHT22** (ใช้แรงดันไฟเลี้ยง **3.3V**) และที่ขา **DATA** จะต้องมีตัวต้านทานแบบ **Pull up** เช่น 4.7k โอห์ม ต่อไปยัง **VCC**

```python
from machine import Pin
import time
import dht

sensor = dht.DHT22( Pin('B5') )

def read_dht22():
    try:
        # start measurement
        sensor.measure()
        # read measurement results
        t,h = sensor.temperature(), sensor.humidity()
        return (t,h)
    except OSError:
        # checksum eror
        return None

try:
    t_last = time.ticks_ms()
    while True:
        t_now = time.ticks_ms()
        if time.ticks_diff( t_now, t_last ) >= 2000:
            t_last = t_now
            results = read_dht22()
            if results:
                t,h = results
                print( 'T={:.1f} deg.C, H={:.1f} %'.format(t,h))
            else:
                print( 'DHT22 reading error...')
except KeyboardInterrupt:
    pass
finally:
    print('Done')
```

![รูปภาพ: ตัวอย่างข้อความเอาต์พุตเมื่อการค่าจากโมดูล DHT22](/files/-MIljjrJ2wFxN6SCvmg2)

![รูปภาพ: ตัวอย่างการต่อวงจรเพื่ออ่านค่าจากโมดูล DHT22 ](/files/-MIlo5SMPKtDs77UmqUK)

## โค้ดตัวอย่างที่ 1&#x36;**: DS18B20 Temperature Sensor (OneWire)**

ตัวอย่างอย่างถัดไปสาธิตการอ่านค่าจากโมดูลเซ็นเซอร์สำหรับวัดอุณหภูมิ โดยใช้ไอซี **DS18B20 (**[**Datasheet**](https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf)**)** ของบริษัท **MAXIM Integrated** และใช้รูปแบบการเชื่อมต่อและสื่อสารข้อมูลที่เรียกว่า **OneWire**

การต่อใช้งานไอซีหรือโมดูล **DS18B20** ให้ใช้แรงดันไฟเลี้ยง **+3.3V** และที่ขา **DATA (DQ)** ของโมดูลนี้ ให้ต่อตัวต้านทาน **4.7k** หรือ **10k** แบบ **Pull-up** ไปยังขา **VCC (+3.3V)**

สำหรับการเขียนโค้ด ผู้พัฒนาไมโครไพธอนได้จัดทำไลบรารีสำหรับ **DS18B20** ไว้ให้ลองใช้งานแล้ว มีอยู่ 2 ไฟล์ที่จะต้องใช้ร่วมกัน คือ [**onewire.py**](https://github.com/micropython/micropython/blob/master/drivers/onewire/onewire.py) และ [**ds18x20.py**](https://github.com/micropython/micropython/blob/master/drivers/onewire/ds18x20.py) ดังนั้นให้ดาวน์โหลดไฟล์ทั้งสองและนำไปใส่ลงใน **MicroPython Flash Drive** ของบอร์ด **STM32** จึงจะสามารถทดลองรันโค้ดตัวอย่างต่อไปนี้ได้

ขั้นตอนการทำงานของโค้ดตัวอย่างเริ่มต้นด้วยการเปิดใช้งานขา **B5** เป็นขา **DATA** สำหรับ **OneWire** และใช้กับโมดูลเซ็นเซอร์ **DS18B20** (ใช้คลาสชื่อ **`OneWire`** และ **`DS18X20`** ตามลำดับ) จากนั้นมีการสแกนหรือตรวจดูว่า พบอุปกรณ์ **DS18B20** หรือไม่ **(**&#xE2D;ุปกรณ์แต่ละตัวจะมีหมายเลขที่ไม่ซ้ำกัน เรียกว่า **ROM ID** หรือ **Serial Code** มีขนาด 64 บิ&#xE15;**)** ถ้าพบว่า มีอย่างน้อย 1 ตัว ก็จะทำการอ่านค่าอุณหภูมิ แลัวจะได้ค่าตัวเลขที่มีหน่วยเป็นเซลเซียส (**Celsius**) มีความละเอียด 0.5°C แสดงผลเป็นข้อความเอาต์พุต

```python
import machine
import time
from onewire import OneWire
from ds18x20 import DS18X20

data_pin = machine.Pin('B5')
ow = OneWire( data_pin )
ow.reset()
ds = DS18X20( ow )
ds18b20_list = ds.scan() 

if len(ds18b20_list) == 0:
    raise RuntimeError('No DS18B20 found!!!')

try:
    while True:
        for rom_addr in ds18b20_list:
            ds.convert_temp()
            time.sleep_ms(750)
            temp = ds.read_temp( rom_addr )
            print('Temperature: {:.1f} deg.C'.format(temp) )
except KeyboardInterrupt:
    pass
finally:
    print('Done')
```

![รูปภาพ: ตัวอย่างการต่อวงจรเพื่อทดลองอ่านค่าจาก DS18B20](/files/-MImVJfVRejkxLhF9ho4)

## โค้ดตัวอย่างที่ 1&#x37;**: BH1750 Ambient Light Sensor (I2C)**

โค้ดตัวอย่างนี้สาธิตการอ่านค่าจากโมดูลเซ็นเซอร์แสงแบบดิจิทัล (**Digital Ambient Light Sensor**) โดยให้ค่าความสว่าง (ความเข้มแสง) เป็นข้อมูลตัวเลขจำนวนเต็มขนาด 16 บิต และมีหน่วยเป็น "ลักซ์" (**Lux**)

ไมโครคอนโทรลเลอร์ **STM32** จะทำหน้าที่เป็นอุปกรณ์ **I2C Master** เพื่อสื่อสารกับโมดูล **BH1750** ที่เป็นอุปกรณ์ **I2C Slave** ผ่านทางบัส **I2C**&#x20;

ในการสื่อสารข้อมูลด้วบัส **I2C** จะต้องระบุแอดเดรส **(I2C address)** ในการสื่อสารข้อมูลกัน โมดูล **BH1750** มีแอดเดรสที่เลือกใช้ได้ 2 ค่าคือ **0x23** และ **0x5C**

การเขียนโค้ดไมโครไพธอนเพื่อใช้งานบัส **I2C** นั้น ก็ใช้คำสั่งในกลุ่ม [**`machine.I2C`**](https://docs.micropython.org/en/latest/library/machine.I2C.html) เช่น คำสั่ง **`writeto()`** และ **`readfrom()`** เพื่อเขียนหรืออ่านข้อมูลจากอุปกรณ์ **I2C Slave** เป็นต้น&#x20;

การทำงานของโค้ดตัวอย่าง เริ่มต้นด้วยการเปิดใช้งาน **I2C1** ซึ่งเป็น **I2C Bus** หมายเลข 1 ของ **STM32F411CE** (หรือจะลองเปลี่ยนไปใช้ **I2C2** แทนก็ได้)

* **I2C1: SCL = PB6 pin, SDA = PB7 pin**
* **I2C2: SCL = PB10 pin, SDA = PB3 pin**

จากนั้นจึงเริ่มสแกนหาอุปกรณ์ที่เชื่อมต่อกับบัส **I2C** และดูว่า มีแอดเดรสอยู่ในรายการของโมดูล **BH1750** หรือไม่ ถ้าใช่ ก็ให้เขียนข้อมูลไปยังอุปกรณ์ดังกล่าวเพื่อกำหนดค่าเริ่มต้นในการใช้งาน และหลังจากนั้น จึงเป็นการอ่านค่าจากโมดูล **BH1750** แล้วแสดงค่าที่ได้เป็นข้อความโดยใช้คำสั่ง **`print()`** &#x20;

```python
import machine
import time 

BH1750_ADDR_LIST = [0x23, 0x5C]

# use I2C1 (SCL1=PB6 and SDA1=PB7 pins on STM32F411CEU6)
i2c = machine.I2C( 1, freq=100000 ) # use I2C1 pins
# OR use explicit pins
# i2c = machine.I2C( scl=machine.Pin("PB6"), sda=machine.Pin("PB7") )
# i2c = machine.I2C( scl=machine.Pin("PB10"), sda=machine.Pin("PB3") )

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

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

bh1750_devices = []

# scan I2C slave devices 
found_devices = i2c.scan()
if len(found_devices) == 0:
    print('No I2C devices found..')
else:
    for addr in found_devices:
        print( 'Found device 0x{:02X}h.'.format(addr) )
        if addr in BH1750_ADDR_LIST:
            # initialize BH1750 devices
            bh1750_init( addr )
            bh1750_devices.append( addr )

try:
    while True:
        results = []
        for addr in bh1750_devices:
            # read sensor value
            value = int( bh1750_read(addr) )
            if value:
                s = '0x{:02X}: [{:5d}]'.format(addr,value)
                results.append( s )
        print( ', '.join(results) )
        time.sleep_ms(1000)
except KeyboardInterrupt:
    pass
finally:
    print('Done')
```

![รูปภาพ: ตัวอย่างข้อความเอาต์พุตเมื่ออ่านค่าจากโมดูล BH1750](/files/-MIlWBdxORYdtFgrB_DR)

![รูปภาพ: ตัวอย่างการต่อวงจรเพื่อใช้งานโมดูล BH1750 (2 ชุด พร้อมกัน)](/files/-MIloHMUxeLu4JT_cau0)

## โค้ดตัวอย่างที่ 1&#x38;**: SSD1306 OLED Display (I2C)**&#x20;

ตัวอย่างถัดไปสาธิตการเขียนโค้ดเพื่อแสดงข้อความบนโมดูลแสดงผลแบบ **OLED** ที่ใช้ชิป **SSD1306 Driver** และเชื่อมต่อผ่านทางบัส **I2C**

โมดูล **SSD1306 OLED** สามารถนำมาใช้สำหรับแสดงผลแบบกราฟิก **Monochrome** มีขนาดเล็ก เช่น  **128x64, 128x32, 96x32** หรือ **64x48** พิกเซล เป็นต้น และเชื่อมต่อกับไมโครคอนโทรลเลอร์ได้ในสองรูปแบบคือ ใช้บัส **I2C** หรือ **SPI** ถ้าใช้ในโหมด **I2C** อุปกรณ์นี้มีแอดเดรสเท่ากับ **`0x3C`**

ในการเขียนโค้ดไมโครไพธอน ก็ได้มีการพัฒนาไลบรารีสำหรับโมดูลนี้ไว้แล้ว สามารถดาวน์โหลดไฟล์ [**ssd1306.py**](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py) จาก **Github** ของ **MicroPython** ได้ แล้วนำไปใส่ลงใน **MicroPython Flash Drive** ซึ่งก็ทำได้ง่าย โดยสำเนำไฟล์ดังกล่าวไปใส่ลงในไดร์ฟชื่อ **STM32F411CE**&#x20;

ในโค้ดตัวอย่าง จะเห็นได้ว่า ได้เลือกใช้บัส **I2C1** ของ **STM32F411CE** เพื่อใช้งานร่วมกับโมดูล **OLED SSD1306** ที่มีขนาด **128x32** พิกเซล มีการแสดงข้อความ และเลื่อนข้อความไปทางซ้าย

```python
import machine
import time
from ssd1306 import *

I2C_ADDR = 0x3C
W, H = 128,32
BLACK, WHITE = 0,1

i2c = machine.I2C( scl=machine.Pin("PB6"), sda=machine.Pin("PB7") )
# scan for I2C devices
if 0x3C not in i2c.scan():
    raise RuntimeError('OLED 1306 not found!!!')

display = SSD1306_I2C(W, H, i2c, addr=I2C_ADDR)

# fill the entire display
display.fill( BLACK )
display.rect(0, 0, W, H, WHITE ) 
# write some text lines (using the default font)
xpos,ypos = 0, 2
text_lines = ["Hi!", "MicroPython", "STM32F411CE"]
for line in text_lines:
    display.text( '{:^16s}'.format(line), xpos, ypos )
    ypos += 10
display.show() # update the display 
time.sleep_ms(1000)
display.rect(0, 0, W, H, BLACK )

STEPS = 2
for i in range(W//STEPS):
    # scroll display to the left
    display.scroll(-STEPS, 0)
    display.show() # update the display 
    time.sleep_ms(100)

display.poweroff()
print('Done')
```

![รูปภาพ: การต่อวงจรเพื่อทดลองใช้งานโมดูล 0.91" OLED SSD1306 (White Color)](/files/-MImC3IIUDD2Zpgb0UvQ)

โค้ดตัวอย่างต่อไปนี้ ใช้ทดสอบการทำงานสำหรับโมดูล **0.66" OLED shield for Wemos D1 Mini** ที่มีขนาด **64x48** พิกเซล และใช้แอดเดรส **0x3C**&#x20;

โมดูลนี้ใช้แรงดันไฟเลี้ยง **3.3V** และขา **D1** และ **D2** ของโมดูลดังกล่าว ตรงกับขา **SCL** และ **SDA** ตามลำดับ

```python
import machine
import time
from ssd1306 import *

W, H = 64,48
BLACK, WHITE = 0,1

# 0.66" 64x48 OLED shield for Wemos D1 Mini
i2c = machine.I2C( scl=machine.Pin("PB6"),
                   sda=machine.Pin("PB7"),
                   freq=400000 )
# scan for I2C devices
i2c_devices = i2c.scan()
print( [hex(addr) for addr in i2c_devices])

if 0x3C not in i2c_devices:
    raise RuntimeError('OLED 1306 not found!!!')

display = SSD1306_I2C( W, H, i2c, addr=0x3C )
display.fill( BLACK )
display.text( '{:^9s}'.format('0LED'),  0, 6 )
display.text( '{:^9s}'.format('0.66"'), 0, 22 )
display.text( '{:^9s}'.format('64x48'), 0, 38 )
display.show() # update the display 
```

![รูปภาพ: การต่อวงจรใช้งานโมดูล OLED ขนาด 64x48 พิกเซล](/files/-MJB27zOkc3leJS_04aW)

## โค้ดตัวอย่างที่ 1&#x39;**: SHT31 Sensor + SSD1306 OLED Display (I2C)**

โค้ดในตัวอย่างนี้สาธิตการอ่านค่าจากโมดูลเซ็นเซอร์ [**SHT31-DIS**](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) สำหรับวัดอุณหภูมิและความชื้นสัมพัทธ์ (ใช้ไอซีที่ผลิตโดยบริษัท [**SENSIRION**](https://www.sensirion.com/)) และแสดงค่าที่อ่านได้เป็นข้อความโดยใช้โมดูลแสดงผลแบบกราฟิก **I2C OLED SSD1306** ทั้งสองอุปกรณ์นี้เชื่อมต่อแบบบัส **I2C** ( โมดูล **SHT31-DIS**  มีแอดเดรสเท่ากับ **`0x44`** ในขณะที่โมดูล **I2C OLED SSD1306** มีแอดเดรสตรงกับ **`0x3C`** )

```python
import machine
import time
from ssd1306 import *
from sht3x import *

W, H = 128,32
BLACK, WHITE = 0,1

i2c = machine.I2C( scl=machine.Pin("PB6"),
                   sda=machine.Pin("PB7"),
                   freq=400000 )

# scan for I2C devices
i2c_devices = i2c.scan()
print( [hex(addr) for addr in i2c_devices])

if 0x3C not in i2c_devices:
    raise RuntimeError('OLED 1306 not found!!!')
if 0x44 not in i2c_devices:
    raise RuntimeError('SHT31-DIS not found!!!')

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

display = SSD1306_I2C(W, H, i2c, addr=0x3C)
# fill the entire display
display.fill( BLACK )

display.text( '{:^16s}'.format('SHT31-DIS'), 0, H//2 )
display.show() # update the display 
time.sleep_ms(1000)
display.fill( BLACK )

try:
    state = 0
    t_last = time.ticks_ms()
    while True:
        t_now = time.ticks_ms()
        if time.ticks_diff( t_now, t_last ) >= 500:
            t_last = t_now
            if state == 0: # start measurement
                sht3x.measure()
            else: # read the measurement result
                t,h = sht3x.read()
                text = "T: {:.1f} deg.C".format(t)
                display.fill( BLACK )
                display.text( '{:^16s}'.format(text), 0, 8 )
                text = "H: {:.1f} % RH ".format(h)
                display.text( '{:^16s}'.format(text), 0, 24 )
                display.show() # update the display
            state = (state+1) % 2
except KeyboardInterrupt:
    pass
except Exception as ex:
    print('Error', ex)
finally:
    print('Done')
    
```

โค้ดไมโครไพธอนที่จะต้องนำไปบันทึกลงในไฟล์ **sht3x.py** สำหรับ **SHT31-DIS** เพื่อใช้กับโค้ดตัวอย่างข้างบน

```python
# File: sht3x.py
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.writeto( 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.writeto( self.addr, bytearray([0x2c,0x06]) )
        except OSError:
            raise RuntimeError('SHT3x: I2C write failed!')
 
    def read(self):
        try:
            raw = self.i2c.readfrom(self.addr, 6)
        except OSError:
            raise RuntimeError('SHT3x: I2C write failed!')
        
        # is 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)

```

![รูปภาพ: การต่อวงจรทดลองโดยใช้โมดูล SHT31 และโมดูล I2C OLED SSD1306](/files/-MIo6yA4pWKYoR-ZCkM0)

## โค้ดตัวอย่างที่ 2&#x30;**: WS2812B RGB LEDs**

อุปกรณ์ **RGB LED** ที่ใช้ไอซี **WS2812B** หรือบางทีก็เรียกว่า **NeoPixel** ใช้สัญญาณอินพุตรับข้อมูลเพียง 1 เส้น สำหรับการเลื่อนบิตข้อมูลเข้าไปเพื่อใช้ในการกำหนดค่าสี **RGB (Red, Green, Blue)** อย่างละ 8 บิต หรือ 24 บิต ต่อ **RGB** หนึ่งตำแหน่ง (พิกเซล)  ถ้ามี **RGB LED** หลายตำแหน่ง เช่น มีลักษณะเป็นแถบเรียงต่อกันแบบอาร์เรย์ (**REB LED Strip**) ก็จะส่งข้อมูลต่อกันไป

บริษัท **WorldSemi** ผู้ออกแบบและพัฒนาไอซี **WS2812B** ได้กำหนดวิธีการส่งข้อมูลหรือโพรโทคอล (**Protocol**) เอาไว้แล้ว ซึ่งจะต้องเป็นไปตามนั้น มิเช่นนั้นการรับข้อมูลอาจไม่ถูกต้อง หรือไม่ได้รับข้อมูล

อ้างอิงตามเอกสาร [**Datasheet**](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) การจำแนกข้อมูลบิตแต่ละบิต จะใช้ความกว้างของสัญญาณพัลส์ (**Pulse**) เป็นตัวกำหนด (หน่วยเป็นไมโครวินาที) ดังนี้

![รูปภาพ: การกำหนดความกว้างของสัญญาณบิต (0, 1, RET) สำหรับ WS2812B](/files/-MIoLSwE0HWb2owaTXex)

![](/files/-MIoLX8McQ_grUU3dbIO)

บิตที่มีค่าเป็น **1** (ช่วง **High** กว้างกว่าช่วง **Low**)

* **T1H**: เริ่มต้นด้วยช่วง **High** กว้างประมาณ **0.8us** และ
* **T1L**: ตามด้วยช่วง **Low** กว้างประมาณ **0.45us**

บิตที่มีค่าเป็น **0** (ช่วง **Low** กว้างกว่าช่วง **High**)

* **T0H**: เริ่มต้นด้วยช่วงช่วง **High** กว้างประมาณ **0.4 usec** และ
* **T0L**: ตามด้วยช่วง **Low** กว้างประมาณ **0.85 usec**

และมีความคลาดเคลื่อนได้ไม่เกิน **+/- 150 usec**

ลำดับของบิตในแต่ละไบต์ จะเป็นแบบ **MSB First** และลำดับข้อมูลไบต์ที่จะถูกส่งออกไป เป็นแบบ **GRB Format** คือ ไบต์แรกสำหรับสีเขียว (**G**) ไบต์ที่สองสำหรับสีแดง (**R**) และไบต์ที่สามสำหรับสีเขียว (**B**) เพื่อใช้กับ **RGB LED** ในตำแหน่งแรก

ในการส่งข้อมูลไบต์เหล่านี้ เราจะใช้วงจร **SPI** ที่มีให้เลือกใช้มากกว่าหนึ่งชุด **(**&#xE40;ช่น เลือกใช้ **SPI Bus 1** หรือ **SPI Bus 2)** ของ **STM32F411CE** เป็นตัวดำเนินการ และข้อมูลจะถูกส่งออกไปผ่านขา **SPI MOSI**

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

เราจะใช้ข้อมูล 4 บิต สำหรับเป็นตัวกำหนดค่าบิต 0 หรือ 1 ของค่าสีแต่ละบิต ดังนี้

* “**1110**” (ช่วง **High** กว้างกว่าช่วง **Low**) หมายถึง บิต 1
* “**1000**” (ช่วง **Low** กว้างกว่าช่วง **High**) หมายถึง บิต 0

ถ้าเรามีค่าสี เช่น 3 ไบต์ หรือ 24 บิต (เรียกว่า **Color Bits**) ต่อหนึ่งตำแหน่ง เราจะต้องสร้างสัญญาณบิตสำหรับ **SPI** ที่ขา **MOSI** ทั้งหมด 24\*4 = 96 บิต (เรียกว่า **SPI Data Bits**) หรือ 12 ไบต์

คำถามคือ แต่ละบิตที่ถูกส่งออกไปทาง **SPI** จะต้องมีความกว้างเท่าไหร่ ? ถ้าเราลองเลือกความถี่ **SCK** ให้เท่ากับ **3.2 MHz** จะได้ความกว้างของบิตเท่ากับ **1/3.2MHz = 0.3125 usec** (ไมโครวินาที) และจะได้ความกว้างดังนี้ (หน่วยเป็น **usec**)

* “**1110**” (บิต 1): **T1H**=3\*0.3125=0.9375 , **T1L**=0.3125&#x20;
* “**1000**” (บิต 0): **T0H**=0.3125, **T0L**=3\*0.3125 = 0.9375

ซึ่งยังถือว่า ค่าเหล่านี้ยังอยู่ในช่วงที่รับได้ (เมื่อพิจารณา **+/-150 usec** แล้ว)

ลองมาดูโค้ดสาธิตการทำงาน โดยเราจะเลือกใช้บัส **SPI** หมายเลข 1 และจะต้องใช้ขา **A7** ของ **STM32F411CE** เป็นขาเอาต์พุต นำไปต่อกับขา **DIN** ของโมดูล **WS2812B** เลือกใช้แบบ **RGB LED Bar** มีทั้งหมด 8 ดวง (8 ตำแหน่ง)

```python
import pyb
import utime as time
from ws2812b import *

# set the number of RGB LEDs 
NUM_LEDS = 8

# use SPI bus 1, use "A7" pin as SPI1 MOSI pin
# use SCK freq. = 3.2MHz
spi_bus = 1 
spi = pyb.SPI(spi_bus, pyb.SPI.MASTER,
              baudrate=3200000,
              polarity=0, phase=1)

neopixel = WS2812B( spi, NUM_LEDS )

data = NUM_LEDS * [(0,0,0)]
data[0:5] = [
    (255,0,0),(150,0,0),(20,0,0),
    (10,0,0),(5,0,0) ]
# write data to WS2812B and update colors
neopixel.write( data )
neopixel.show()

try:
    while True:
        # rotate the elements in the list
        data.append( data.pop(0) )
        # write data to WS2812B and update colors
        neopixel.write( data )
        neopixel.show()
        time.sleep_ms(250)
        
except KeyboardInterrupt:
    pass
finally:
    # turn all the RGB LED off
    neopixel.write( NUM_LEDS*[(0,0,0)] )
    neopixel.show()
    print('Done')

```

โค้ดไมโครไพธอนที่จะต้องนำไปบันทึกลงไฟล์ **ws2812b.py** เพื่อใช้ร่วมกับโค้ดตัวอย่างข้างบน

```python
# lookup table for bit pattern conversion
BITS_CONV = (0x88, 0x8e, 0xe8, 0xee)

class WS2812B():

    def __init__(self, spi, num_leds):
        self.spi = spi
        self.num_leds = num_leds
        # SPI data buffer
        buf_size = self.num_leds * 3 * len(BITS_CONV)
        self.buf = bytearray( buf_size )
        
    def write(self, colors, start=0 ):
        i = start
        for r,g,b in colors:
            for c in (g,r,b): # for GRB format
                # convert 2 color bits into 8 SPI data bits
                self.buf[i+0] = BITS_CONV[c >> 6 & 0b11]
                self.buf[i+1] = BITS_CONV[c >> 4 & 0b11]
                self.buf[i+2] = BITS_CONV[c >> 2 & 0b11]
                self.buf[i+3] = BITS_CONV[c & 0b11]
                i += 4
            
    def show(self):
        self.spi.send(self.buf) # send data buffer to the SPI bus
```

![รูปภาพ: การต่อวงจรทดลองโดยใช้โมดูล WS2812B RGB LED Bar](/files/-MIoaO8oINHzHEKSsKHg)

## โค้ดตัวอย่างที่ 21: SPI SSD1306 OLED&#x20;

โค้ตัวอย่างนี้สาธิตการใช้งานโมดูล **SSD1306 OLED** ขนาด **128x64** พิกเซล แต่ใช้วิธีการเชื่อมต่อแบบ **SPI**  และเชื่อมต่อขากับขา **I/O** ของ **STM32** ดังนี้

`SPI OLED           STM32F411CEU6`\
`GND                GND` \
`VCC                3.3V` \
`D0 (SCLK)          SCK1 / PA5` \
`D1 (MOSI)          MOSI1 / PA7` \
`RST (/RESET)       PB6` \
`DC (DATA/CMD)      PB7` \
`CS (/Chip Select)  PA4`

หลักการทำงานของโค้ดมีดังนี้ เริ่มต้นด้วยการเลือกใช้ **SPI Bus 1 (SPI1)** ซึ่งมีขาที่เกี่ยวข้องได้แก่ **SCK1, MOSI1, MISO1** มีการตั้งความถี่ของ **SPI Bus Frequency** ให้เท่ากับ **8 MHz** และทำงานโหมด **(0,0)**&#x20;

ถัดไปเป็นการใช้งานโมดูล **`SSD1306_SPI`** จากไฟล์ **ssd1306.py** และให้ใช้ขาของ **SPI1** (ใช้แค่ขา **SCK1** และ **MOSI1**) และ **I/O** อีก 3 ขา สำหรับ **RST**, **DC**, **CS**

ข้อมูลที่จะถูกนำมาแสดงผลบนโมดูลกราฟิก **OLED** นั้น ได้จากการสุ่มตัวเลขในช่วง 0 ถึง 100 เก็บไว้ในอาร์เรย์ แล้วนำมาแสดงค่าในรูปของกราฟแท่ง&#x20;

ในการทำงานแต่ละรอบของ **while loop** จะมีการลบข้อมูลออกหนึ่งค่าที่ตำแหน่งสุดท้ายของอาร์เรย์ สุ่มตัวเลขใหม่และใส่ลงในอาร์เรย์ในตำแหน่งแรก&#x20;

```python
from machine import Pin, SPI
from ssd1306 import SSD1306_SPI as ssd
import time
import random

spi = SPI(1, baudrate=8000000, polarity=0, phase=0)
rst = Pin('B6', mode=Pin.OUT) 
dc  = Pin('B7', mode=Pin.OUT) 
cs  = Pin('A4', mode=Pin.OUT)
W, H = 128, 64
display = ssd( W, H, spi, dc, rst, cs )
display.poweron()
display.fill(0)

rand_next = lambda: random.randint(1,100)

N = 32
try:
    data = [rand_next() for i in range(N)]
    while True:
        x = 1
        y = H-2
        display.fill(0)
        for value in data:
            h = int((H-4)*value/100)
            display.fill_rect(x,y-h,3,h,1)
            x = x + 4            
        display.show()
        time.sleep(1.0)
        data.pop(-1)
        data.insert(0, random.randint(1,100)) 
except KeyboardInterrupt:
    print('Done')
finally:
    spi.deinit()
```

![รูปภาพ: การต่อวงจรใช้งานโมดูล SPI OLED 128x64 พิกเซล](/files/-MJBsBvhVoqS1VQEvReD)

## โค้ดตัวอย่างที่ 22: Analog Light Sensor + SPI SSD1306 OLED&#x20;

โค้ดตัวอย่างนี้สาธิตการอ่านค่าจากโมดูลเซ็นเซอร์แสงแบบแอนะล็อก (**CJMCU** [**TEMT6000**](https://www.vishay.com/docs/81579/temt6000.pdf) **Ambient Light Sensor Module**) ที่มี 3 ขา คือ **V** (**VCC)**,  **G** (**GND)**, **S (SIGNAL)**

ขา **S** เป็นขาสัญญาณเอาต์พุต-แอนะล็อกจากโมดูลดังกล่าว เชื่อมต่อกับขา **PA1** ของ **STM32** และมีการเปิดใช้งานวงจร **ADC** (**Analog-to-Digtal Converter**) ภายใน เพื่อแปลงค่าสัญญาณอินพุต (ระดับแรงดันไฟฟ้าในช่วง 0V ถึง 3.3V) ให้เป็นค่าเลขจำนวนเต็ม&#x20;

ไมโครไพธอนมีคำสั่งที่เกี่ยวข้องกับ **ADC** ไว้ให้ใช้งานคือ [**machine.ADC**](https://docs.micropython.org/en/latest/library/machine.ADC.html) โดยจะต้องสร้างออบเจกต์จากคลาสดังกล่าว และระบุชื่อขาที่จะใช้งาน (ในกรณีนี้คือ **`"A1"`**)  จากนั้นจึงใช้คำสั่ง [**`read_u16()`**](https://docs.micropython.org/en/latest/library/machine.ADC.html#machine.ADC.read_u16) เพื่ออ่านค่าอินพุตที่ขาดังกล่าว และจะได้เป็นเลขจำนวนเต็มในช่วง 0 ถึง 65535 ค่าที่อ่านได้จะถูกนำมาแสดงผลแบบกราฟแท่งบนจอ **OLED** และอัปเดตทุก ๆ 0.1 วินาที

```python
from machine import Pin, SPI, ADC
from ssd1306 import SSD1306_SPI as ssd
import time

ain = Pin('A1') # use PA1 pin as analog input pin
adc = ADC(ain)

spi = SPI(1, baudrate=8000000, polarity=0, phase=0)
rst = Pin('B6', mode=Pin.OUT) 
dc  = Pin('B7', mode=Pin.OUT) 
cs  = Pin('A4', mode=Pin.OUT)
W, H = 128, 64
display = ssd( W, H, spi, dc, rst, cs )
display.poweron()
display.fill(0)

N = 32
try:
    data = []
    while True:
        if len(data) > N:
            data.pop(-1)
        value = adc.read_u16()//256           
        data.insert(0, value) 
        x = 1
        y = H-2
        display.fill(0)
        for value in data:
            h = int((H-4)*value/256)
            display.fill_rect(x,y-h,3,h,1)
            x = x + 4            
        display.show()
        time.sleep(0.1)
except KeyboardInterrupt:
    print('Done')
finally:
    spi.deinit()
```

![รูปภาพ: การต่อวงจรใช้งานโมดูล SPI OLED และ TEMT6000 Light Sensor](/files/-MJC4ScuaBq2B5ANTz_2)

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