การใช้งาน Arduino TimerOne Library#
บทความนี้นำเสนอการใช้งาน Arduino TimerOne Library ในเบื้องต้นสำหรับบอร์ดไมโครคอนโทรลเลอร์ Arduino Uno / Nano ที่ใช้ชิป ATmega328P เช่น การเรียกใช้ฟังก์ชันที่มีคาบเวลาการทำงานคงที่ และการสร้างสัญญาณ PWM
Keywords: Arduino Nano / Uno, TimeOne Library, ATmega328P, PWM, Timer/Counter
▷ Arduino TimerOne Library#
TimerOne เป็น Arduino Library ที่สำหรับการเขียนโปรแกรมบอร์ดไมโครคอนโทรลเลอร์ เช่น บอร์ด Arduino Uno / Nano ที่ใช้ชิป ATmega328P และภายในชิปมีวงจรตัวนับที่มีชื่อว่า Timer/Counter (TC) มี 3 วงจร ได้แก่ TC0, TC1 และ TC2 รีจิสเตอร์สำหรับการเก็บค่าตัวนับมีขนาด 8 บิต ยกเว้น TC1 ที่มีขนาด 16 บิต
การใช้งาน TimerOne เพื่อทำคำสั่งในรูปแบบของการเรียกใช้ฟังก์ชันซ้ำด้วยอัตราคงที่ หรือ เพื่อสร้างสัญญาณ PWM (Pulse Width Modulation) จำนวน 2 ช่อง ได้พร้อมกัน มีความถี่เท่ากัน แต่ปรับค่า Duty Cycle ได้ต่างกัน
โดยปรกติแล้วคำสั่ง analogWrite(...)
ของ Arduino API จะเกี่ยวข้องกับการสร้างสัญญาณ PWM
เป็นเอาต์พุต และจะต้องใช้วงจร Timer/Counter ด้วย ถ้าเลือกใช้ขา D9 หรือ D10
ของบอร์ด Arduino เป็นเอาต์พุต การสร้างสัญญาณ PWM จะต้องใช้วงจร TC1
และสัญญาณที่ได้มีความถี่ 490Hz โดยประมาณ (เป็น Default Frequency
ผู้ใช้ไม่สามารถเลือกความถี่ได้โดยตรง)
TimerOne
เป็นชื่อของ C++ Class ของไลบรารีดังกล่าว ซึ่งมีไฟล์ Source Code
สองไฟล์ที่เกี่ยวข้องคือ TimeOne.h
(Header File) และ
TimerOne.cpp
(C++ Class Implementation File)
ถ้าลองค้นหา Repository ใน GitHub จะพบว่ามีไลบรารีที่เกี่ยวข้องกับ TimerOne ดังนี้
github:PaulStoffregen/TimerOne
- ได้รับการพัฒนามาตั้งแต่ปีค.ศ. 2008 โดย Paul Stoffregen และนักพัฒนารายอื่นอีก
- รองรับการใช้งานชิป ATmega168/328, ATmega1280/2560 และ ATmega32U4
รูป: ซอร์สโค้ดและ GitHub Repository ของ TimerOne
ฟังก์ชันการทำงานของ TimerOne เกี่ยวข้องกับวงจร TC1
และในการเขียนโปรแกรมโดยใช้ TimerOne จะต้องมีการติดตั้งไลบรารีใน Arduino IDE ก่อน
โดยไปที่ Library Manager และค้นหาด้วยคำว่า TimerOne
เมื่อพบแล้วให้คลิกเลือก และกดปุ่ม
Install และเวอร์ชันล่าสุดที่ได้ทดลองใช้คือ TimerOne v1.1.1 (Release Date: 2023-04-14)
รูป: การใช้ Library Manager เพื่อค้นหาและติดตั้งไลบรารี TimerOne
ในโค้ด Arduino Sketch จะต้องมีการเขียนประโยคคำสั่ง
เพื่อนำไลบรารีดังกล่าวมาใช้ ซึ่งก็คือคำสั่ง #include "TimerOne.h"
หรือ #include <TimerOne.h>
▷ โค้ดตัวอย่างที่ 1 การใช้ TimerOne เพื่อเรียกฟังก์ชันและทำให้ LED กระพริบด้วยอัตราคงที่#
Timer1
เป็นชื่อของตัวแปรที่อ้างอิงอ็อบเจกต์ (Object Reference)
และได้มีการสร้างเอาไว้แล้วในโค้ดของไลบรารี จากคลาส TimeOne
ดังนั้นคำสั่งแรกของการเริ่มต้นใช้งานสำหรับวงจรตัวนับ TC1 ก็คือ
Timer1.initialize()
ถัดไปคือคำสั่ง Timer1.attachInterrupt(...)
ซึ่งเป็นการเปิดใช้งานอินเทอร์รัพท์ของวงจรตัวนับ TC1
โดยจะต้องระบุว่า จะให้เรียกใช้ฟังก์ชันใด (เป็นฟังก์ชันสำหรับ Callback)
และมีการเว้นช่วงเวลาในการเกิดอินเทอร์รัพท์ หรือ มีคาบเวลา (Period)
การทำงานเท่าไหร่ ในหน่วยเป็นไมโครวินาที (Microseconds)
การเขียนโค้ดเพื่อเริ่มใช้งาน Timer1
และเรียกใช้ฟังก์ชันด้วยเว้นระยะเวลาเป็นคาบเวลา ทำได้สองแนวทางคือ
Timer1.initialize();
Timer1.attachInterrupt( callback_function, period_in_usec );
และ
Timer1.initialize( period_in_usec );
Timer1.attachInterrupt( callback_function );
#include "TimerOne.h"
// see: https://github.com/PaulStoffregen/TimerOne/
#define PERIOD_USEC (100000) // 100 msec
void setup() {
// Configure the onboard LED pin as an output pin.
pinMode( LED_BUILTIN, OUTPUT );
// Option 1
// 1) Intialize the TimerOne without specifying the period.
// 2) Enable the TimerOne interrupt for every 100 msec.
Timer1.initialize();
Timer1.attachInterrupt( timerOneCallback, PERIOD_USEC );
// Option 2
// 1) Initialize the TimerOne and specify the period (100 msec).
// 2) Enable the TimerOne interrupt.
//Timer1.initialize( PERIOD_USEC );
//Timer1.attachInterrupt( timerOneCallback );
}
void loop() { // no action
}
// Global variable
int led_state = 0; // used to keep the LED state.
void timerOneCallback( ) { // Callback function for Timer1
led_state = !led_state; // Toggle the LED state
digitalWrite( LED_BUILTIN, led_state ); // Write output pin
}
ในโค้ดตัวอย่างที่ 1 ฟังก์ชัน timerOneCallback()
จะทำงานทุก ๆ 100 msec
และเมื่อทำงานในแต่ละครั้ง จะทำให้เกิดการสลับสถานะลอจิกที่ขา D13 ซึ่งต่อกับวงจร LED
บนบอร์ด ดังนั้น LED จะติดหรือดับ สลับกันไปทุกๆ 100 มิลลิวินาที
▷ โค้ดตัวอย่างที่ 2 การปรับคาบเวลาในการทำงานของ Timer1#
โค้ดตัวอย่างที่ 2 เป็นการทดลองใช้ค่าตัวเลขสำหรับการกำหนดคาบเวลาในการทำงานของ
Timer1 โดยแบ่งเป็นสองกรณี ค่าตัวเลขที่มากที่สุด (64*65536UL - 1)
และค่าตัวเลขที่น้อยที่สุด (1)
#include "TimerOne.h"
// 16MHz/1024/65536 = 0.238418Hz or 4.1943 seconds
#define MAX_PERIOD_USEC (64*65536UL - 1)
#define MIN_PERIOD_USEC (1)
#define PERIOD_USEC (MAX_PERIOD_USEC)
void setup() {
// Configure the onboard LED pin as an output pin.
pinMode( LED_BUILTIN, OUTPUT );
Timer1.initialize();
Timer1.attachInterrupt( timerOneCallback, PERIOD_USEC );
}
void loop() { // no action
}
// Global variable
int led_state = 0; // used to keep the LED state.
void timerOneCallback( ) { // Callback function for Timer1
led_state = !led_state; // Toggle the LED state
digitalWrite( LED_BUILTIN, led_state ); // Write output pin
}
ถ้าทดลองค่าสูงสุด (64*65536UL - 1)
แล้ววัดสัญญาณเอาต์พุตด้วยออสซิลโลสโคป
จะได้ระยะเวลาในการเปลี่ยนหรือสลับสถานะลอจิกที่ขาเอาต์พุตดังนี้
รูป: ตัวอย่างการตั้งคาบเวลาไว้สูงสุด (วัดคาบได้ค่าประมาณ 4.2 วินาที)
แต่ถ้าลองตั้งค่าให้น้อยที่สุดคือ 1 usec เมื่อวัดสัญญาณจริง พบว่า
จะได้คาบเวลาประมาณ 9 usec ทั้งนี้ก็เป็นเพราะว่า การตอบสนองต่ออินเทอร์รัพท์ และการคำสั่งของฟังก์ชัน
timerOneCallback()
ต้องใช้เวลาในการทำงานมากกว่า 1 usec
ดังนั้นในทางปฏิบัติก็ควรตั้งค่าคาบเวลา ไม่น้อยกว่า 10 usec
(ทั้งนี้ก็ขึ้นอยู่กับระยะเวลามากที่สุดในการทำงานของฟังก์ชัน Callback ด้วย)
รูป: ตัวอย่างการตั้งคาบเวลาไว้ 1usec แต่เมื่อวัดสัญญาณจริงได้ประมาณ 9 usec
รูป: ตัวอย่างการตั้งคาบเวลาไว้ 15usec
▷ โค้ดตัวอย่างที่ 3 การปรับคาบเวลาโดยรับค่าจากสัญญาณอินพุต-แอนะล็อก#
โค้ดตัวอย่างที่ 3 สาธิตการอ่านค่าจากขาแอนะล็อกอินพุต เพื่อใช้ปรับคาบเวลาของสัญญาณเอาต์พุต ถ้าต้องการให้สัญญาณเอาต์พุตปรับเปลี่ยนคาบเวลาได้ ก็สามารถใช้วิธีการอ่านแรงดันอินพุตจากสัญญาณ แอนะล็อกในช่วง 0V แต่ไม่เกิน 5V โดยใช้วงจรแบ่งแรงดันไฟฟ้าที่สร้างได้จากตัวต้านทานปรับค่าได้ นำมาต่อเข้าที่ขา A0 เพื่อใช้เป็นสัญญาณอินพุต
ค่าที่อ่านได้ด้วยคำสั่ง analogRead(...)
จะอยู่ในช่วง 0 .. 1023
แล้วนำไปสเกลค่าและแปลงให้เป็นค่าตัวเลขสำหรับคาบเวลา โดยใช้คำสั่ง
map(...)
เช่น ในช่วง 100 .. 1000 usec
เพื่อใช้กับคำสั่ง Timer1.setPeriod(...)
#include "TimerOne.h"
#define MIN_PERIOD_USEC (100) // 100 usec
#define MAX_PERIOD_USEC (1000) // 1000 usec
void setup() {
// Configure the onboard LED pin as an output pin.
pinMode( LED_BUILTIN, OUTPUT );
Timer1.initialize( MIN_PERIOD_USEC );
Timer1.attachInterrupt( timerOneCallback );
}
void loop() {
// Read analog value (0..1023) at A0 pin.
int value = analogRead( A0 );
int period_usec = map( value, 0, 1023,
MIN_PERIOD_USEC, MAX_PERIOD_USEC );
Timer1.setPeriod( period_usec );
delay(100);
}
// Global variable
int led_state = 0; // used to keep the LED state.
void timerOneCallback( ) { // Callback function for Timer1
led_state = !led_state; // Toggle the LED state
digitalWrite( LED_BUILTIN, led_state ); // Write output pin
}
ตัวอย่างการวัดสัญญาณโดยหมุนปรับตัวต้านทานเพื่อให้ระดับแรงดันไฟฟ้าของสัญญาณอินพุต-แอนะล็อกมีการเปลี่ยนแปลง
รูป: หมุนปรับค่าให้ได้คาบเวลาประมาณ 1000 usec
รูป: หมุนปรับค่าให้ได้คาบเวลาประมาณ 100 usec
▷ โค้ดตัวอย่างที่ 4 การสร้างสัญญาณ PWM จำนวน 2 ช่องสัญญาณ#
โค้ดตัวอย่างที่ 4 สาธิตการสร้างสัญญาณ PWM จากวงจร TC1 ซึ่งสามารถสร้างสัญญาณ PWM ได้สองช่องสัญญาณพร้อมกัน แต่ต้องมีความถี่เท่ากัน ปรับค่า Duty Cycle ให้แตกต่างได้ และจะต้องใช้ขา I/O Pin สำหรับ TC1 เป็นเอาต์พุต คือ OC1A / PB1 และ OC1B / PB2 ซึ่งตรงกับขา D9 และ D10 ของบอร์ด Arduino Uno / Nano ตามลำดับ
#include "TimerOne.h"
#define DUTY_CYCLE(x) ((x)*(1024L)/100)
#define PERIOD_US (1000) // 1000 usec (1 msec)
void setup() {
Timer1.initialize();
Timer1.setPeriod( PERIOD_US );
// Set the initial duty cycle: 0..100%
int dc = 50;
// Convert the duty cycle to a 10-bit integer value
// and update the Timer1's PWM outputs.
Timer1.pwm( 9, DUTY_CYCLE(dc) ); // D9 / PB1 pin
Timer1.pwm( 10, DUTY_CYCLE(100-dc) ); // D10 / PB2 pin
}
void loop() { // no action
}
รูป: การวัดสัญญาณเอาต์พุตทั้งสองช่อง (PWM Frequency 1kHz, Duty Cycle 50%)
▷ โค้ดตัวอย่างที่ 5 การสร้างสัญญาณ PWM และปรับค่า Duty Cycle ได้#
โค้ดตัวอย่างที่ 5 สาธิตการอ่านค่าจากขาสัญญาณแอนะล็อก-อินพุต A0 มาปรับค่า Duty Cycle ของสัญญาณ PWM ทั้งสองสัญญาณ ถ้าสัญญาณช่องหนึ่งมีค่า Duty Cycle สัญญาณอีกช่องหนึ่งจะต้องมีค่าลดลง
#include "TimerOne.h"
#define DUTY_CYCLE(x) ((x)*(1024L)/100)
#define PERIOD_US (1000)
void setup() {
Serial.begin( 115200 );
Timer1.initialize();
Timer1.setPeriod( PERIOD_US );
// Set the initial duty cycle: 0..100%
int dc = 0;
// Convert the PWM duty cycle to a 10-bit integer value and
// update the Timer1's PWM outputs.
Timer1.pwm( 9, DUTY_CYCLE(dc) ); // D9 / PB1 pin
Timer1.pwm( 10, DUTY_CYCLE(100-dc) ); // D10 / PB2 pin
}
void loop() {
// Read analog value at A0 pin.
int value = analogRead(A0);
// Use the analog value to set the PWM duty cycle.
int dc = map(value,0,1024,0,100);
Timer1.pwm( 9, DUTY_CYCLE(dc) ); // D9 / PB1 pin
Timer1.pwm( 10, DUTY_CYCLE(100-dc) ); // D10 / PB2 pin
Serial.println( String("Duty Cycle: ") + dc );
delay(100);
}
รูป: การต่อวงจรแบ่งแรงดันไฟฟ้าด้วยตัวต้านทานปรับค่าได้ร่วมกับบอร์ด Arduino Nano
ตัวอย่างการปรับค่า Duty Cycle และการวัดสัญญาณ PWM ด้วยออสซิลโลสโคป มีดังนี้
รูป: สัญญาณ PWM ที่ขา D9 และ D10 ที่ได้มีการปรับค่า Duty Cycle ให้เพิ่มขึ้นหรือลดลง
รูป: สัญญาณ PWM ที่ขา D9 และ D10 เมื่อค่า Duty Cycle เป็น 0% และ 100% ตามลำดับ
▷ โค้ดตัวอย่างที่ 6 การสร้างสัญญาณ PWM และการเรียกใช้ฟังก์ชันตามคาบเวลา#
ในตัวอย่างที่ 6 ได้มีการแก้ไขเพิ่มเติมจากโค้ดตัวอย่างที่ 5 โดยมีการเปิดใช้อินเทอร์รัพท์
ให้มีการเรียกใช้ฟังก์ชัน timerOneCallback()
เพื่อทำให้ LED กระพริบ
ทุกครั้งที่เกิดอินเทอร์รัพท์จาก TC1 ตามคาบเวลาที่กำหนดไว้ คือ 1000 ไมโครวินาที
จะมีการเพิ่มค่าตัวนับ ticks
ครั้งละหนึ่ง ซึ่งจะนับค่าอยู่ในช่วง 0 .. 9 แล้วเริ่มต้นนับใหม่
เมื่อค่าของตัวแปร ticks
มีค่าเท่ากับ 0 ในแต่ละรอบของการนับ
จะทำให้ LED สลับสถานะการติดหรือดับหนึ่งครั้ง
#include "TimerOne.h"
#define DUTY_CYCLE(x) ((x)*(1024L)/100)
#define PERIOD_US (1000)
void setup() {
//Serial.begin(115200);
pinMode( LED_BUILTIN, OUTPUT );
Timer1.initialize();
Timer1.setPeriod( PERIOD_US );
// Set the initial duty cycle: 0..100%
int dc = 50;
// Convert the PWM duty cycle to a 10-bit integer value and
// update the Timer1's PWM outputs.
Timer1.pwm( 9, DUTY_CYCLE(dc) ); // D9 / PB1 pin
Timer1.pwm( 10, DUTY_CYCLE(100-dc) ); // D10 / PB2 pin
// Activate the Timer1 alarm.
Timer1.attachInterrupt( timerOneCallback );
}
void loop() {
int value = analogRead(A0); // Read analog value at A0 pin.
// Use the analog value to set the PWM duty cycle.
int dc = map(value,0,1024,0,100);
Timer1.pwm( 9, DUTY_CYCLE(dc) ); // D9 / PB1 pin
Timer1.pwm( 10, DUTY_CYCLE(100-dc) ); // D10 / PB2 pin
// Serial.println( String("Duty Cycle: ") + dc );
delay(100);
}
// Global variable
int led_state = 0; // used to keep the LED state.
int ticks = 0; // used to count ticks.
void timerOneCallback( ) { // Callback function for Timer1
if (ticks == 0) {
led_state = !led_state; // Toggle the LED state
digitalWrite( LED_BUILTIN, led_state ); // Write output pin
}
ticks = (ticks+1) % 10;
}
รูป: การวัดสัญญาณ PWM ที่ขา D9 และสัญญาณที่ขา D13
▷ กล่าวสรุป#
บทความนี้ได้นำเสนอประโยชน์ของการใช้งานไลบรารี TimerOne สำหรับการเขียนโปรแกรม Arduino Sketch โดยใช้บอร์ดไมโครคอนโทรลเลอร์ Uno/Nano และมีตัวอย่างโค้ดให้นำไปทดลองใช้งานได้จริง
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Created: 2024-01-17 | Last Updated: 2024-01-17