Trong phần trước mình đã demo giới thiệu của phương pháp PWM mềm, thuật toán ở bài trước đó khá ổn nếu điều khiển PWM ra 1 số lượng GPIO hữu hạn ! Mình sẽ thử lấy 1 ví dụ với PWM ra LED 256 mức sáng bằng vi điều khiển ATmega8, chương trình tạo PWM thông thường sẽ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <mega8.h> #include <delay.h> unsigned char dem; unsigned char sang=200; interrupt [TIM1_OVF] void ngattimer1_1ms(void) { if(sang>=dem)PORTC.0=1; else PORTC.0=0; dem++; TCNT1H=0xFE; // Khoi tao lai gia tri cho TCNT1 TCNT1L=0x0B; } void main(void) { PORTC=0xff; DDRC=0xff; // KHOI TAO TIMER1 TCCR1A=0x00; TCCR1B=0x01; // Chia tan so cho 1 TCNT1H=0xFE; // Khoi tao lai gia tri cho TCNT1 TCNT1L=0x0B; TIMSK=0x04; // Kich hoat ngat tran TIMER1 #asm("sei") // Kich hoat ngat toan cuc while (1) { if(sang<255)sang++; else sang=0; delay_ms(50); } } |
Phương pháp PWM mềm thông thường này sẽ rất hao tổn tài nguyên của MCU nếu bạn điều khiển quá nhiều GPIO và số lượng bit PWM lớn do mỗi mức PWM là 1 lần ngắt, nói cách khác, muốn tạo ra 256 mức phải ngắt 256 lần. Có 1 thủ thuật khác hay hơn để tạo ra 256 mức PWM (8bit PWM) chỉ với 8 lần ngắt nhờ lợi dụng đặc điểm của số nhị phân.
Ví dụ, mình cần tạo ra 1 PWM có max 256 mức, độ rộng xung ở mức cao là 167, cách tạo ra PWM bằng phương pháp B.A.M sẽ như sau:
167(thập phân)=10100111(nhị phân)
Trọng số của các bit nhị phân được đánh từ trái sang phải bắt đầu từ 0
Lần ngắt thứ 1: Bit trọng số 0 có giá trị là 1 => đưa chân GPIO lên logic 1 > delay 1 tick
Lần ngắt thứ 2: Bit trọng số 1 có giá trị là 1 => đưa chân GPIO lên logic 1 > delay 2 tick
Lần ngắt thứ 3: Bit trọng số 2 có giá trị là 1 => đưa chân GPIO lên logic 1 > delay 4 tick
Lần ngắt thứ 4: Bit trọng số 3 có giá trị là 0 => đưa chân GPIO lên logic 0 > delay 8 tick
Lần ngắt thứ 5: Bit trọng số 4 có giá trị là 0 => đưa chân GPIO lên logic 0 > delay 16 tick
Lần ngắt thứ 6: Bit trọng số 5 có giá trị là 1 => đưa chân GPIO lên logic 1 > delay 32 tick
Lần ngắt thứ 7: Bit trọng số 6 có giá trị là 0 => đưa chân GPIO lên logic 0 > delay 64 tick
Lần ngắt thứ 8: Bit trọng số 7 có giá trị là 1 => đưa chân GPIO lên logic 1 > delay 128 tick
Như vậy là kết thúc 1 chu kì PWM chỉ với 8 lần ngắt. Mình sẽ demo chỉnh sửa code trên sang thuật toán B.A.M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <mega8.h> #include <delay.h> unsigned char vitri_bit=0; unsigned char sang=167; unsigned int vl_bit_timer[8]={0xFFB1,0xFF63,0xFEC7,0xFD8E,0xFB1D,0xF63B,0xEC77,0xD8EF}; //unsigned int vl_bit_timer[8]={0xFEC7,0xFD8F,0xFB1F,0xF63F,0xEC77,0xD8FF,0xB1DF,0x64BF}; //8 lan interrupt [TIM1_OVF] void ngattimer1_1ms(void) { if((sang & (1<<vitri_bit)))PORTC.0=1; else PORTC.0=0; TCNT1H=vl_bit_timer[vitri_bit]>>8; // Khoi tao lai gia tri cho TCNT1 ( bit 0); TCNT1L=vl_bit_timer[vitri_bit]; vitri_bit++;if(vitri_bit==7)vitri_bit=0; //tang bit len 1 } void main(void) { PORTC=0xff; DDRC=0xff; // KHOI TAO TIMER1 TCCR1A=0x00; TCCR1B=0x01; // Chia tan so cho 1 TIMSK=0x04; // Kich hoat ngat tran TIMER1 #asm("sei") // Kich hoat ngat toan cuc while (1) { } } |
Ở chương trình trên, 1 biến vitri_bit được tạo để giám sát xem chúng ta đang cần làm việc với bit nào, mỗi lần ngắt mình sẽ nạp lại giá trị cho timer để thay đổi thời gian nhảy vào lần ngắt tiếp theo (tạo delay)
Dạng sóng của phương pháp B.A.M sẽ như sau:
Tổng thời gian ở mức cao sẽ tương đường với phương pháp PWM thông thường
Dowload source code demo trên atmega8 (code vision AVR, proteus 8.4) tại đây