[Thảo luận]PWM với GPIO

[Thảo luận]PWM với GPIO - Updated

Chả là cái này mình nghe được từ anh Hải hồi training RYA vòng 1, dạo gần đây thấy nhiều anh em G-Force hỏi quá nên lên đây lập cái thread mời các bô lão vào thảo luận.:))

Về định nghĩa PWM là gì, cũng như cách tạo PWM bằng chân PWM của PIC(hoặc các dòng microcontroller khác) xin không nhắc lại.

500đ hình cho nó bớt chán:


1) Mục đích: cách code cho các chân GPIO để tạo xung PWM, giúp có thể tận dụng nhiều chân vi điều khiển nói chung trong việc làm hiệu ứng đẹp cho led....
Dùng khi cần nhiều chân GPIO, nếu chỉ 1 2 chân thì ráng làm chân PWM đi.

2) Cách thực hiện: nói ví dụ cho dễ hiểu, không chơi lý thuyết nữa =))
a/Cách 1:
_cho 1 timer A vào ngắt sau 1 khoảng thời gian nhỏ (lấy 0,1ms đi)
_chọn 1 khoảng cycle =timer A x độ phân giải, cycle phải đủ nhỏ để tạo thời gian lưu ảnh trong mắt (ví dụ pwm 32 bit thì lấy cycle là 3,2ms nhé.)
_cần xuất ra 2 port 1.0 giá trị 12/32, 1.1 giá trị 25/32 trong 10s.
_bắt đầu: khi vào ngắt timer, ta thực hiện tuần tự như sau
__dem++; //tăng dem lên 1
__if (dem==32) dem = 0; //khi hết 3,2ms thì reset.
__if (dem<12) P1.0=1, else P1.0=0;
__if (dem<25) P1.1=1, else P1.1=0;
__thoát ngắt.
_nên dùng 1 timer B để sau 10s dừng timer A lại và tắt cả 2 port đi, không nên dùng chỉ 1 timer(sẽ giải thích sau).

Nhận xét:
_không phức tạp
_tạo được xung PWM y hết như chân PWM tạo ra(giống cái hình 500đ ở trên kia)
_tuy vậy,độ phân giải càng cao thì phải vào ngắt càng nhiều, mỗi lần vào ngắt phải check tất cả các chân -> càng nhiều chân càng nhiều lệnh if, càng tăng số chu kỳ máy tiêu tốn cho 1 lần vào ngắt -> thời gian thực tế vào ngắt 1 lần > thời gian mong muốn.
_code như thế này người ta copy của mình dễ ợt:D

Thế nên phương pháp anh Hải hướng dẫn sẽ giải quyết được những vấn đề ở trên theo 1 cách rất pro:

b/Cách 2: cho là đề bài cũng như trên, 2 bước đầu thực hiện hoàn toàn tương tự
_Thực hiện trong 1 ngắt của timer khác việc huyển 2 giá trị pwm 12 và 25 (lưu trong 2 biến pwm1,pwm2) về nhị phân bằng cách của anh Hoàng
Ví dụ khi i=2^(4) thì em sẽ kiểm tra bit thứ 4 của biến dutycycle bằng lệnh and với 1 mặt nạ (1<<4) xem kết quả là 0 hay 1 -> bit đó là 0 hay 1.
có điều ở đây nếu chúng ta:
__lưu giá trị nhị phân vào mảng -> chỉ cần xuất giá trị của 1 phần tử trong mảng khi vào ngắt cần kiểm tra -> ít tốn chu kỳ máy.
__thực hiện and với mask rồi xuất luôn -> tốn chu kỳ máy hơn do phải thực hiện lệnh and và sau đó mới xuất.
mà chu kỳ máy rất quan trọng do timer A có giá trị nhỏ -> có câu trả lời cho anh Hoàng rồi nhé. kết quả là 12-> 01100; 25-> 11001,lưu vào mảng pwm1[0:4] và pwm2[0:4].

_bắt đầu tạo xung PWM: khi vào ngắt ta thực hiện tuần tự
__dem++; //tăng dem lên 1
__xài switch case biến dem tại các mốc có giá trị là 2^n(với n là số bit của xung PWM)
xét ví dụ PWM có giá trị max là 32-> 5bit:
___case 1: xuất bit pwm1[0] ra P1.0, pwm2[0] ra P1.1 -> P1.0 tắt, P1.1 sáng
___case 2: xuất bit pwm1[1] ra P1.0, pwm2[1] ra P1.1 -> P1.0 tắt, P1.1 tắt
___case 4: xuất bit pwm1[2] ra P1.0, pwm2[2] ra P1.1 -> P1.0 sáng, P1.1 tắt
___case 8: xuất bit pwm1[3] ra P1.0, pwm2[3] ra P1.1 -> P1.0 sáng, P1.1 sáng
___case 16:xuất bit pwm1[4] ra P1.0, pwm2[4] ra P1.1 -> P1.0 tắt, P1.1 sáng
___case 31:reset dem về 0.(vì còn 1 khoảng 0.1ms khi đếm từ 0->1 nên ta chọn giá trị 31)
__thoát ngắt
_nên dùng timer B để định sau 10s tắt các chân P1.0 ,P1.1 và dừng timer A vì: vào ngắt phải chạy 1 số lệnh tại các mốc như trên, đặc biệt càng nhiều port thì càng sai lệch về thời gian -> nếu dùng để định 1 khoảng thời gian bằng vài chục ~ vài trăm ngàn lần timer A thì sai số sẽ rất đáng kể.

Nhận xét:
_Số lần kiểm tra, xuất giá trị, vào ngắt,...nói chung làm những việc vô công rỗi nghề ít -> ít sai số về thời gian
_điều khiển được nhiều chân hơn giải thuật kia
_code nhìn vào là ngại đọc ngay'+_+

3)Kết: nhìn chung các giải thuật trên đầu bị hạn chế về độ phân giải, đòi hỏi VXL phải mạnh để hiển thị được độ phân giải cao, sự chính xác trong phát xung PWM không thể nào so sánh được với chân PWM gốc của vi điều khiển :D nên mình khuyến khích sử dụng các IC PWM driver trong các ứng dụng cần sự chính xác.

Mời các bô lão vào ném gạch :) Còn thằng G-Force nào nữa mà hỏi mình không giải thích nữa đâu :))
 
Mình thì suy nghĩ như sau:
+ Chỉ cần dùng ngắt của 1 timer thôi, trong ngắt này ta cũng sẽ kiểm tra số lần vào ngắt (giá trị i như bạn nói trên) và để dễ lập trình thì ta sẽ kiểm tra khi giá trị i bằng 2^(j) với j sẽ tăng lên 1 khi i=2^(j), điều kiện để reset biến i là khi i=2^(jmax) với jmax là số bit pwm.
+ Không cần dùng mảng, khi điều kiện i=2^(j) xảy ra thì ta sẽ xuất bit thứ j của biến duty cycle ra chân gpio mà ta mong muốn tạo pwm.
 
Em hiểu ý anh Hoàng rồi, có điều dùng 2 timer sẽ tốt hơn, em sẽ giải thích ở phần trên.
Còn về phần xuất mảng, ý em là biến duty cycle được cho ở dạng decimal(ví dụ là int pwm= 10). nếu ta ghi i[0] thì liệu nó có hiểu là bit 0 của biến pwm đó ko, nếu có thì đơn giản lệnh hơn nhiều. Nếu không thì phải dùng 1 chương trình con đổi từ thập phân -> nhị phân, lưu giá trị vào mảng x nào đó.
 
Cái chỗ dùng 2 hay 1 timer thì sao cũng được, a chưa làm thử nên cũng không biết cái mức chia nhỏ nhất là bao nhiêu để màu sắc nhìn thấy là ok. Còn cái kiểm tra bit thì đơn giản thôi mà em. Ví dụ khi i=2^(4) thì em sẽ kiểm tra bit thứ 4 của biến dutycycle bằng lệnh and với 1 mặt nạ (1<<4) xem kết quả là 0 hay 1 -> bit đó là 0 hay 1.
 

Manhdd

Cố Vấn CLB
Staff member
Uhm. Trước mình thi RYA với LPC1114-12MHz cũng sử dụng giải thuật quét kiểu này:
Dùng 1 timer (TIMER32_0) quét 16 LED RGB (48 kênh PWM :d). Khi đó, với tần số quét là 50Hz (chu kỳ PWM 20ms), và độ phân giải là 32 (32 nấc chọn duty cycle) thì chất lượng ánh sáng khá tốt, điều khiển ok.

Lúc đó, ARM còn có 1 timer quét liên tục Led ma trận nữa :) . Nên có lẽ, với VĐK chạy ở tần số thấp hơn, giải thuật này vẫn có thể áp dụng khá ổn ^^
 

Manhdd

Cố Vấn CLB
Staff member
Mới cụ thể hóa vào PIC, điều khiển LED RGB trên RB0 - RB1 - RB2 qua biến trở. Chỉnh độ phân giải là 4 mới không thấy nháy :ar! . Giải thuật viết đơn giản, giành cho đa phần các bạn đang học PIC :p

Code:
/****************************************************************
 *
 * www.payitforward.edu.vn
 * 
 ****************************************************************/
/****************************************************************
 *
 * PIC Training Course
 * 
 ****************************************************************/

/****************************************************************
 *
 *	Module		: ADC_tuning_RGB.c
 *	Description	: Reading POT to tune RGB LED with rainbow colors
 *	Notes		: Output PWM on non-PWM pins RB0-RB1-RB2
 *             Tool		: HI-TECH PIC
 *	Chip		: 16F887
 * 	History		: 07/03/2012
 *				
 *	Author		: Nguyen Tien Manh, CLB NCKH DDT 
 *				
 *
 ****************************************************************/
 

 /****************************************************************
 * IMPORT
 ****************************************************************/
#include <htc.h>
__CONFIG(XT & WDTDIS & PWRTEN & MCLREN & UNPROTECT & SWBOREN & 
		IESODIS & FCMDIS & LVPDIS & DEBUGDIS); //1st config. Word

__CONFIG(BORV21); //2st config. Word

#define _XTAL_FREQ 	4000000

#define FPWM 150 // PWM frequency (> 50 Hz)
#define RESPWM 4 // PWM resolution (up to 256)
#define PWMC 1000000/FPWM // PWM period (in us)
#define PWMUT PWMC/RESPWM // PWM Update Time (in us)

/*****************************************************************
GLOBAL VARIABLE
******************************************************************/

unsigned char RGBLED [3] = {0,0,0}; // PWM duty on each Led; 0:R - 1:G - 2:B
					
int PWMTICK = 0; //PWM Ticker, count each PWMUT

/*****************************************************************
* ROUTINES
******************************************************************/
void port_init()
{
	TRISA0 = 1; //AN0 (ADC chanel 0) is input
	TRISB0 = 0; // Red led
	TRISB1 = 0; // Green led
	TRISB2 = 0; // Blue led
}
void ADC_init()
{
	//AN0 is analog
	ANSEL=1;		
	//ADC conversion clock source = Fosc/8
	ADCS0=1;		
	ADCS1=0;
	// Vref = VCC = 5V
	VCFG0=0;		
	VCFG1=0;
	// Select channel 0
	CHS0=0;			
	CHS1=0;
	CHS2=0;
	CHS3=0;
	//Result formatting: Left justified
	ADFM=0;			
	// Turn on ADC module
	ADON=1;			
	__delay_ms(1); // delay for initiation of module ADC 
}
void timer1_init()
{
	// ** Timer1 Setting
	TMR1CS = 0; // Timer1 Clock Source '0/1 internal/external'
	T1OSCEN = 0; // Timer1 Oscillator disable

	// Prescale Rate Select
	T1CKPS0 = 0;
	T1CKPS0 = 0; 	// 1 : 1 //	
	// T1 Module Register (initial value)
	TMR1H = (-(PWMUT - 1))>> 8; // High byte
	TMR1L = (-(PWMUT - 1))& 0xFF; // Low byte
	// Disable gate control
	TMR1GE = 0;

	// Config interrupt for Timer1
	GIE = 1; // Gobal Interrupt Enable
	PEIE = 1; // Peripherals Interrupt Enable
	TMR1IE = 1; //Timer1 Interrupt Enable
	TMR1IF = 0; // Timer1 Interrupt Flag 'On/off 1/0'

	// Enable timer 1
	TMR1ON = 1;
	__delay_ms (1); // delay for initiation of timer 1
}
void ADC_2_rainbow ()
{
	//Convert 8-bits ADRESH to rainbow RGB code, store in RGBLED[]
	
    if ((ADRESH>=0) && (ADRESH <= 256/4))
    {
        RGBLED[0] = 255; // Red led
        RGBLED[1] = 255*ADRESH/64; // Green led
        RGBLED[2] = 0; // Blue led
    }
    else if ((ADRESH>256/4) && (ADRESH <= 256/2))
    {
        RGBLED[0] = 255*(128-ADRESH)/64;
        RGBLED[1] = 255;
        RGBLED[2] = 0;
    }
    else if ((ADRESH>256/2) && (ADRESH <= 256*3/4))
    {
        RGBLED[0] = 0;
        RGBLED[1] = 255*(192-ADRESH)/64;
        RGBLED[2] = 255*(ADRESH-128)/64;
    }
    else
    {
        RGBLED[0] = 255*(ADRESH-192)/64;
		RGBLED[1] = 0;
        RGBLED[2] = 255;
    }
	// Scale RGB code to fit PWM resolution
	RGBLED[0] = RGBLED[0]*RESPWM/256;
	RGBLED[1] = RGBLED[1]*RESPWM/256;
	RGBLED[2] = RGBLED[2]*RESPWM/256;
}
void out_leds ()
{
	// Compare PWMTICK with RGBLED[] and export result to LED RGB (RB0-RB1-RB2)
		// PWMTICK is period increased  by timer. So that, PWM is created on 3 pins
	PORTB = ( (PWMTICK >= RGBLED[2])<<2 
				| (PWMTICK >= RGBLED[1])<<1 
				| (PWMTICK >= RGBLED[0]) );
}

//Interrupt service rountine
void interrupt isr()
{
	if (TMR1IF && TMR1IE) //Determine timer interrupt
	{
		TMR1ON = 0;  // Stop timer1
        if (PWMTICK == RESPWM)
            PWMTICK = 0; // Reset PWM period
		// Convert ADC result to ADC rainbow
		ADC_2_rainbow (); 
		// Output as PWM
		out_leds ();
		// Continue ticker
		PWMTICK++;		

		TMR1IF = 0; // pull down interrupt flag
		// Reset T1
		TMR1H = (-(PWMUT - 1))>> 8; // High byte
		TMR1L = (-(PWMUT - 1))& 0xFF; // Low byte
		TMR1ON = 1; // Allow timer1 1o count
	}
	return;
}

 /****************************************************************
 * MAIN
 ****************************************************************/	
 
void main()
{
	port_init();
	ADC_init();
	timer1_init();
	while (1)
	{
		__delay_ms(30); // pause ADC reading and delay for PWM output (in interrupt)
		GODONE = 1;		// start converting
		while (GODONE); // wait for conversion
	}
}

/****************************************************************
 * END OF ADC_tuning_RGB.c
 ****************************************************************/
 

phungquan2512

Trứng gà
Mình vừa đọc sơ qua datasheet con PIC, có phần PWM, capture, compare. PWM thì mọi người đã nói rõ ở trên, không hiểu capture và compare có chức năng như thế nào nhỉ??? nhờ mọi người giải đáp giùm.
 
nói nôm na là thế này:

_Capture:
+đo khoảng thời gian chưa biết từ lúc bắt đầu tới khi có 1 tín hiệu cần đo, thường là tín hiệu tác động từ chân của MCU hoặc các tín hiệu trong MCU. timer chạy lên hoài luôn
+nếu bạn đặt timer có interval 1ms chẳng hạn, rồi mỗi lần vào ngắt mới kiểm tra tín hiệu tác động đó, thì sẽ dư thừa xung clock của hệ thống:D (tính từ lúc xảy ra ngắt đến hết interval)
+dùng để đo tần số, đo ngưỡng tín hiệu (Vcc, ADC) xem có tới thredshold chưa.
_Compare:
+là bạn đặt sẵn cho nó 1 khoảng thời gian đã biết ,sau khoảng thời gian đó thì sẽ có 1 tín hiệu như bạn mong muốn, rồi từ tín hiệu đó xử lý ra sao thì tùy.
+dùng tạo xung PWM, truyền UART, RTC.
Cái này bạn nên đặt câu hỏi trong phần timer để rõ thêm nhé:D còn nếu hỏi cho con MSP430 thì còn nhiều cái để nói nữa (continuous, up, down & up/down):D
 

huyphuc92

Trứng gà
Đào mộ cái coi nào.
Các bạn có ý tưởng cho việc xuất 1 chuổi xung PWM A và B sao cho A lệch pha B 90 độ không (mục đích để lái servo)? Mình dùng TM4C129, xuất một chuỗi xung có số lượng và tần số xung xác định, duty thì duy trì 50%, nhưng làm cho 2 xung A và B lệch nhau 90 độ thì chưa có ý tưởng gì. :)
 

mafiaWolf

Chủ tịch Hội phụ nữ PIF
Đào mộ cái coi nào.
Các bạn có ý tưởng cho việc xuất 1 chuổi xung PWM A và B sao cho A lệch pha B 90 độ không (mục đích để lái servo)? Mình dùng TM4C129, xuất một chuỗi xung có số lượng và tần số xung xác định, duty thì duy trì 50%, nhưng làm cho 2 xung A và B lệch nhau 90 độ thì chưa có ý tưởng gì. :)
Servo nào cần 2 xung nhỉ? Theo mình thì tạo biến đếm hay thêm cái timer nữa :v
 

huyphuc92

Trứng gà
Servo nào cần 2 xung nhỉ? Theo mình thì tạo biến đếm hay thêm cái timer nữa :v

Servo MRJ-2S của mitsubishi đó bạn. Bạn có thể tham khảo manual của nó, phần parameter 21, có 2 xung A và B với các chế độ xung khác nhau. Thật ra mình có thể xuất 1 xung PWM thôi, chân còn lại chọn chiều cũng được (parameter 21 = 0011 hoặc 0001), cái này mình làm rồi.

Mình muốn làm chế độ xung đôi lệch pha 90 độ, mình nghĩ nó có thể giảm nhiễu khi kết hợp thêm một đường truyền vi sai thay vì open collector như mình đang dùng.

Ý bạn là dung biến đếm hay timer??? Mình không hiểu. Modul PWM có chế độ deah bands, nhưng hình như nó chỉ dùng trong lái cầu H phải không?
 
Top