به نام خدا

سلام!

واقعا راست میگن که نابرده رنج گنج میسر نمیشود مزد آن گرفت جان برادر که کار کرد!

چند وقتی بود خیلی نارحت بودم که نمیتونم برای خودم برنامه بریزم که چه کار کنم! مثلا صبح از خواب بیدار میشدم میگفتم امروز فلان ماژول رو وصل میکردم! تا ظهر پا کامپیوتر میشستم موقع نماز میدیدم چیزی گیرم نیومد! (یعنی هیچ کاری نکردم) بعد از ظهر هم پا کامپیوتر میشستم تا شب دوباره وقت نماز شب میدیدم هیچ کاری نکردم! خیلی اعصابم خورد میشد! (دلیلش این بود که هر روز میخواستم یه کاری کنم ولی نمیشد!) امروز گفتم میرم سراغ PWM تا ظهر نشستم دوباره هیچ کاری نکردم! بعد از ظهر اومدم چند تا سرچ زدم تا به این فیلم در یوتیوب رسیدم! بعد نشستم هرچی نوشت رو منم نوشتم! نمیدونم چرا سورس رو نذاشته بود مثل آدم دانلود کنیم! 

بالاخره خدا رو شکر درست شد!

حالا میخوام از روی کد و مطالعه یوزرمانول که میتونید از اینجا دانلود کنید PWM رو توضیح بدم! (البته خودم بلد نیستم و قطعا نمیتونم به شما یاد بدم فقط چیزایی که تو دیتاشیت نوشته رو میارم اینجا تا کارتون راحت تر بشه)!

خوب بریم سراغ اصل مطلب:

قبل از هر چیز توصیه میکنم این مطلب رو از سایت الکترولت بخونید!

کلمات کلیدی مربوطه:

– Update: Counter overflow/underflow, counter initialization (by software or internal/external trigger)

– Trigger event (counter start, stop, initialization or count by internal/external trigger)

– Input capture

– Output compare

– Break input

کانال تایمر چیست؟!

اولین سوال که برام پیش اومد که اگه شما هم مبتدی باشید براتون پیش اومده اینه که کانال تایمر چیه؟! مثلا زمان رو نشون میده یا میگه چند تا سیکل و پالس گذسته یا در هر پالس اونم 0 یا 1 میشه؟! خیر هیچکدام از اینها نیست!

جوابی که من از طریق کد و دیتاشیت بدست آوردم اینه که طبق یه چیزی که به میکرو اعلام میکنیم، هر وقت تایمر به مقداری (که اونم ما تعیین میکنیم) رسید پایه مربوط به کانال مربوطه (مثلا کانال 1) در حالت 5 ولت یا 0 ولت قرار میگیره!

یعنی مثلا فرض کنید ما تایمر 8 بیتی داریم و یک کانال (در این میکرو تایمر 4 یک تایمر 8 بیتی است اما کانال ندارد، ولی ما فرض میکنیم) خوب نهایت یه تایمر 8 بیتی 255 هست ما میایم رجیستر CCR رو روی 100 تنظیم میکنیم! حالا تایمر از 0 تا 100 که میشمره کانال مقدار 0 ولت داره و وقتی از 100 تا 255 میشمره کانال مقدار 5 ولت داره!  

خوب امیدوارم فهمیده باشید!


حالت PWM

TIM2_CCMR2_OC2M = 0X07; //PWM Mode

توی دیتاشیت (بالا معرفی کردم) داریم: صفحه 236


Bits 6:4 OC1M[2:0]: Output compare 1 mode

These bits defines the behavior of the output reference signal OC1REF from which OC1 is derived.

OC1REF is active high whereas OC1 active level depends on the CC1P bit.

000: Frozen - The comparison between the output compare register TIMx_CCR1 and the counter

TIMx_CNT has no effect on the outputs

001: Set channel 1 to active level on match. OC1REF signal is forced high when the counter

TIMx_CNT matches the capture/compare register 1 (TIMx_CCR1).

010: Set channel 1 to inactive level on match. OC1REF signal is forced low when the counter

TIMx_CNT matches the capture/compare register 1 (TIMx_CCR1).

011: Toggle - OC1REF toggles when TIMx_CNT=TIMx_CCR1

100: Force inactive level - OC1REF is forced low

101: Force active level - OC1REF is forced high

110: PWM mode 1 - In up-counting, channel 1 is active as long as TIMx_CNT< TIMx_CCR1.

Otherwise, channel 1 is inactive. In down-counting, channel 1 is inactive (OC1REF = 0) as long as

TIMx_CNT> TIMx_CCR1. Otherwise, channel 1 is active (OC1REF = 1).

111: PWM mode 2 - In up-counting, channel 1 is inactive as long as TIMx_CNT< TIMx_CCR1 Otherwise, channel 1 is active.

(نکته: چند خط انگلیسی بالا در مورد TIM1_CCMR1 هست که در واقع کانال 1 از تایمر 1 هست. در صورتی که ما با کانال 2 از تایمر 2 کار میکنیم! دلیل اینکه این متن رو آوردم این بود که چون همه ی رجیستر های مربوط با کانال ها در همه تایمر ها یکسان هست! خود User Manual فقط از این تایمر رو توضیح داده و برای بقیه توضیحی ننوشته و گفته برای توضیحات به تایمر 1 برید)

الان ما اون مدی که پر رنگ کردم رو انتخاب کردیم! نوشته مادامی که TIMx_CCR1 بزگتر از TIMx_CNT باشه! کانال ما inactive هست! حالا چرا گفته inactive خوب میگفت 0 هست! خیر. دلیل این که گفته inactive اینه که ما در رجیستر TIM2_CCER1_CC2P میتونیم تعیین کنیم که active Low باشه یا active High یعنی در این مثال که کدش رو در زیر قرار میدم، ما گفتیم که active Low باشه! یعنی در حالت active یا فعال مقدار 0 ولت داشته باشه و در حالت inactive یا غیر فعال مقدار 5 ولت (یا 1 منطقی داشته باشه) داشته باشه (یعنی برعکس اونچیزی که رایج هست)

 خوب حالا در اینجا طبق توضیحات بالا هر وقت که TIMx_CCR1 مقدار 0 داشته باشه! شرط غلط میشه و کانال active میشه و طبق چیزایی که گفتیم active برای ما همون 0 هست! پس اگه TIMx_CCR1 صفر باشه خروجی هم صفر هست! و برعکس اگه 0xFFFF باشه خروجی هم 1 هست!

امیدوارم فهمیده باشید! اگر هم نفهمیدید باید حداقل دو بار مطلب رو بخونید تا متوجه بشید! چون شاید بعضی از این چیزایی که در بالا گفتم رو در ادامه تکمیل کنم....!


انتخاب کانال به عنوان خروجی!

  TIM2_CCMR2_CC2S = 00;   // CC2 Chanels as output

طبق دیتاشیت : (صفحه : 239)

CC2S[1:0]: Capture/compare 2 selection

This bitfield defines the direction of the channel (input/output) as well as the used input.

00: CC2 channel is configured as output

01: CC2 channel is configured as input, IC2 is mapped on TI2FP2

10: CC2 channel is configured as input, IC2 is mapped on TI1FP2

11:CC2 channel is configured as input, IC2 is mapped on TRC. This mode works only if an internal trigger input is selected through the TS bit (TIM5_SMCR register).

که ما حالت اول یعنی خروجی رو انتخاب کردیم!


active Low یا active High مسئله این است؟

خوب این موضوع رو کمی در قسمت حالت PWM توضیح دادم. 

  TIM2_CCER1_CC2P =  1 ;   // OC2 Active Low

در دیتاشیت صفحه 242

CC1P: Capture/compare 1 output polarity

  CC1 channel configured as output:

        0: OC1 active high

        1: OC1 active low

  CC1 channel configured as input for capture function (see Figure 64):

         0: Capture is done on a rising edge of TI1F or TI2F

         1: Capture is done on a falling edge of TI1F or TI2F

خوب دوباره من از قسمت CC1P کپی کردم در حالی که ما از CC2P استفاده میکنیم! چون همش مثل هم هست توی دیتا شیت یه بار توضیح داده! دیگه از CC2P رو توضیح نداده!


فعال کردن کانال

خوب داریم:

  TIM2_CCER1_CC2E =  1 ;   // Timer OutPut to Pin PD.3

ببینید خوب اگه ما اینکار رو نکنیم پایه PD.3 به عنوان یک GPIO شناخته میشه و کاربرد کانال تایمر 2 رو نداره! با اینکار میگیم این پین رو به تایمر وصل کنه!


فعال کردن تایمر 2 و وقفه مربوطه!

خوب داریم :

  TIM2_IER_UIE = 1;     // ENABLE UPDATE INTERRUPT
  TIM2_CR1_CEN = 1; // ENABLE Timer 2
  asm ("rim");// ENABLE iNTERRUPT

فقط اینو بگم که اونجور که من فهمیدم UPDATE INTERRUPT وقتی رخ میده که تایمر به آخرش برسه! یعنی در تایمر 8 بیتی وقتی تایمر به 255 برسه روال وقفه یا همون IRQHandler اجرا میشه! (شایدم اشتباه گفته باشم! مطمئن نیستم)


رجیستر TIM2_CCR2

خوب این چیزی که همون اول درباره اش صحبت کردیم! تایمر تا مقداری که در این رجیستر قرار داره میشمره و بعد از اینکه از این مقدار رد شد پایه (کانال) مربوطه رو تغییر میده! مثلا اگه رجیستر TIM2_CCR2 رو روی 90 تنظیم کنیم تا وقتی که کوچکتراز 90 هست (مثلا) پایه مربوطه 0 هست وقتی از 90 رد میشه پایه مربوطه 1 میشه! این رجیستر 16 بیتی چون تایمر 2 شونزده بیتی هست!

بنابراین به صورت TIM2_CCR2H و TIM2_CCR2L باید بهش دسترسی داشته باشید!


کد کامل:

خوب بعد از این همه حرف رسیدیم به آخر خط، اینم از کد زیر (اینم بگم که این کد رو من از روی فیلمی که توی یوتیوب بود نوشتم)

البته یه چیزایی هم داره که من توضیح ندادم! چیز سختی نبود دیگه ربطی به تایمر نداره. بخونید میفهمید!

#include "iostm8s003f3.h"
#include "stdint.h"
#include "stdbool.h"
volatile int16_t pwmval =0;
volatile bool increment = true;

uint16_t values[] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                      2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,
                      5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11,
                      12, 12, 13, 13, 14, 15, 15, 16, 17, 17, 18, 19, 20, 21, 22,
                      23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
                      43, 45, 47, 49, 52, 54, 56, 59, 61, 64, 67, 70, 73, 76, 79,
                      83, 87, 91, 95, 99, 103, 108, 112, 117, 123, 128, 134, 140,
                      146, 152, 159, 166, 173, 181, 189, 197, 206, 215, 225, 235,
                      245, 256, 267, 279, 292, 304, 318, 332, 347, 362, 378, 395,
                      412, 431, 450, 470, 490, 512, 535, 558, 583, 609, 636, 664,
                      693, 724, 756, 790, 825, 861, 899, 939, 981, 1024, 1069,
                      1117, 1166, 1218, 1272, 1328, 1387, 1448, 1512, 1579, 
                      1649, 1722, 1798, 1878, 1961, 2048, 2139, 2233, 2332,
                      2435, 2543, 2656, 2773, 2896, 3025, 3158, 3298, 3444, 
                      3597, 3756, 3922, 4096, 4277, 4467, 4664, 4871, 5087, 
                      5312, 5547, 5793, 6049, 6317, 6596, 6889, 7194, 7512, 
                      7845, 8192, 8555, 8933, 9329, 9742, 10173, 10624, 11094, 
                      11585, 12098, 12634, 13193, 13777, 14387, 15024, 15689, 
                      16384, 17109, 17867, 18658, 19484, 20346, 21247, 22188,
                      23170 , 24196, 25267, 26386, 27554, 28774, 30048, 31378,32768,
                      34218, 35733, 37315, 38967, 40693, 42494, 44376, 46340, 
                      48392, 50534, 52772, 55108, 57548, 60096, 62757, 65535
};

#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_OVR_IRQHandler (void){
    uint16_t currentVal = values [pwmval];
    if (pwmval == 0)
      increment = true;
    else if (pwmval >= 254)
      increment = false;
  
    if (increment)
      pwmval += 3;
    else
      pwmval -= 3;
    
    TIM2_CCR2H = (currentVal >> 8);
    TIM2_CCR2L = (currentVal & 0XFF);
    
    TIM2_SR1 &= ~ (1<<0);       // Clear Interrupt Flag
    
    
}
void intiTimer (void)
{
  CLK_PCKENR1 = 0xFF; // Enable Clock for Timer
  TIM2_PSCR_PSC = 00;   
  TIM2_CCMR2_OC2M = 0X07; //PWM Mode
  TIM2_CCMR2_CC2S = 00;   // CC2 Chanels as output PWM
  TIM2_CCER1_CC2P =  1 ;   // OC2 Active Low
  TIM2_CCER1_CC2E =  1 ;   // Timer OutPut to Pin PD.3
  TIM2_IER_UIE = 1;     // ENABLE UPDATE INTERRUPT
  TIM2_CR1_CEN = 1; // ENABLE Timer 2
  asm ("rim");// ENABLE iNTERRUPT
  
}
                
int main( void )
{
  CLK_CKDIVR = 0X0F;    // Div 128 to 125KHz
  intiTimer();
  while (1);
  return 0;
}

البته در کد بالا فرکانس کاری میکرو رو روی 125 کیلوهرتز گذاشته که ما میتونیم اونو بزاریم روی 16 و کلاک تایمر رو بزاریم روی 125 کیلوهرتز چون اینجوری اگه کد دیگه ای هم بخوایم اجرا کنیم! سرعت بسیار پایین میاد!

خوب امیدوارم مفید بوده باشه!

نزدیک 2 ساعت زمان برد!

ببینید چقدر دوستتون دارم که نشستم براتون این مطلب رو نوشتم هرچند خیلی نقص داره (به بزرگی خودتون ببخشید) قصد داشتم یه پستی باشه که حداقل یه چیزی هرچند کوچک یاد داده باشم!

موفق باشید!

یا علی مدد....!



قسمت جدید اضافه شده:
خوب یه نکته دیگه هم گفتم بگم:
رفتم تست کردم برنامه رو دیدم اگه کلاک رو هم روی 16 مگاهرتز بزاریم اتفاقی نمیفته و فقط تایع وقفه تایمر سریعتر اجرا میشه و نور LED زودتر خاموش و روشن میشه! و اگه TIM2_PSCR_PSC رو روی 1 بزاریم تقریبا همون حالت قبل اتفاق میفته!
سریع چند تا چیزی که فهمیدم رو میگم و میرم...
اول اینکه کد جدید به شکل زیر میشه:
#include "iostm8s003f3.h"
#include "stdint.h"
#include "stdbool.h"
volatile int16_t pwmval =0;
volatile bool increment = true;

uint16_t values[] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                      2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,
                      5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11,
                      12, 12, 13, 13, 14, 15, 15, 16, 17, 17, 18, 19, 20, 21, 22,
                      23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
                      43, 45, 47, 49, 52, 54, 56, 59, 61, 64, 67, 70, 73, 76, 79,
                      83, 87, 91, 95, 99, 103, 108, 112, 117, 123, 128, 134, 140,
                      146, 152, 159, 166, 173, 181, 189, 197, 206, 215, 225, 235,
                      245, 256, 267, 279, 292, 304, 318, 332, 347, 362, 378, 395,
                      412, 431, 450, 470, 490, 512, 535, 558, 583, 609, 636, 664,
                      693, 724, 756, 790, 825, 861, 899, 939, 981, 1024, 1069,
                      1117, 1166, 1218, 1272, 1328, 1387, 1448, 1512, 1579, 
                      1649, 1722, 1798, 1878, 1961, 2048, 2139, 2233, 2332,
                      2435, 2543, 2656, 2773, 2896, 3025, 3158, 3298, 3444, 
                      3597, 3756, 3922, 4096, 4277, 4467, 4664, 4871, 5087, 
                      5312, 5547, 5793, 6049, 6317, 6596, 6889, 7194, 7512, 
                      7845, 8192, 8555, 8933, 9329, 9742, 10173, 10624, 11094, 
                      11585, 12098, 12634, 13193, 13777, 14387, 15024, 15689, 
                      16384, 17109, 17867, 18658, 19484, 20346, 21247, 22188,
                      23170 , 24196, 25267, 26386, 27554, 28774, 30048, 31378,32768,
                      34218, 35733, 37315, 38967, 40693, 42494, 44376, 46340, 
                      48392, 50534, 52772, 55108, 57548, 60096, 62757, 65535
};
#define LED_BLICK() PA_ODR_ODR3=!PA_ODR_ODR3
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_OVR_IRQHandler (void){
    uint16_t currentVal = values [pwmval];
    if (pwmval == 0)
      increment = true;
    else if (pwmval >= 254)
      increment = false;
  
    if (increment)
      pwmval += 3;
    else
      pwmval -= 3;
    
    TIM2_CCR2H = (currentVal >> 8);
    TIM2_CCR2L = (currentVal & 0XFF);
    
    LED_BLICK ();
    
    TIM2_SR1 &= ~ (1<<0);       // Clear Interrupt Flag
    
    
}
void intiTimer (void)
{
  CLK_PCKENR1 = 0xFF; // Enable Clock for Timer
  TIM2_PSCR_PSC = 0X01; // fCK_CNT = fCK_PSC/2^(PSCR[3:0])   -- note : fCK_PSC = fmaster
  TIM2_CCMR2_OC2M = 0X07; //PWM Mode
  TIM2_CCMR2_CC2S = 00;   // CC2 Chanels as output PWM
  TIM2_CCER1_CC2P =  1 ;   // OC2 Active Low
  TIM2_CCER1_CC2E =  1 ;   // Timer OutPut to Pin PD.3
  TIM2_IER_UIE = 1;     // ENABLE UPDATE INTERRUPT
  TIM2_CR1_CEN = 1; // ENABLE Timer 2
  asm ("rim");// ENABLE iNTERRUPT
  
}
                
int main( void )
{
  PA_DDR_DDR3  =1;
  PA_ODR_ODR3 =0;
  PA_CR1_C13 = 1;
  PA_CR2_C23 = 1;
  
  CLK_CKDIVR = 0X00;    
  intiTimer();
  while (1);
  return 0;
}
البته در کد بالا من یه LED به پایه A.3 وصل کردم که هر بار تابع وقفه اجرا میشه حالتش عوضش میشه! یعنی اگه خاموش بوده روشن میشه و اگه روشن بوده خاموش میشه! این رو برای تست سرعت اجرای تابع وقفه گذاشتم تا ببینم در ثانیه چقدر اجرا میشه! (الان سرعتش بالاست و اگه به LED نگاه کنید همیشه روشنه چون سریع روشن و خاموش میشه)

میخوام TIM2_PSCR_PSC توضیح بدم
طبق فرمولی که توی دیتاشیت گفته (صفحه 225) به این صورت استفاده میشه!
fCK_CNT = fCK_PSC/2^(PSCR[3:0])
همچنین طبق دیتاشیت مقدار fCK_PSC برابر با فرکانس اصلی هست! (همون 16 مگاهرتز برای ما) یعنی مقداری که توی رجیستر TIM2_PSCR_PSC  قرار میگیره یک توانی هست برای 2. خوب حالا میخوایم ببینیم مفهوم کلی اش چی میشه؟!
ببینید ما این مقدار رو روی 8 قرار میدیم! که 2 به توان 8 میشه 256 حالا 16 ملیون تفسیم بر 256 چقدر میشه؟ میشه 62500 یعنی کلاک تایمر ما 62500 هست یا به عبارت دیگه تایمر ما در هر ثانیه 62500 سیکل طی میکنه و یا بازم به عبارت دیگه هر سیکل 1/62400 ثانیه طول میکشه که میشه 0.000016 ثانیه! پس تایمر ما هر وقت که یک واحد بهش اضافه میشه 0.000016 ثانیه طول میکشه! مثلا فرض کنید مقدار تایمر ما الان 29 هست 0.000016 ثانیه بعد میشه 30. حالا ما میخوایم ببینیم که چقدر طول میکشه که مقدار کل 16 بیت تایمر ما طی بشه (تایمر ما 16 بیتی هست پس تا 65535 میشمره و هر شمردنی هم 0.000016 ثانیه طول میکشه) پس از 0 تا 65535 در 65536*0.000016 ثانیه طول خواهید کشید که این مقدار برابر است با 1.048576 ثانیه! یک مقداری نزدیک 1 ثانیه هست! پس در کد بالا که ما یه LED گذاشته بودیم توی تابع وقفه باید LED ما هر یک ثانیه خاموش و در ثانیه بعد روشن بشه!
البته نکته اینجاست که PWM همون مقدار رو داره. یعنی اگه PWM ما روی 50 درصد دیوتی باشه همون 50 درصده فقط توی کد ما که دیوتی سایکل تغییر میکنه LED ما خیلی دیر واکنش نشون میده و دیر تغییر حالت میده!
به نظرم تست کنید تا خودتون ببینید!
فقط تنها چیزی که باید در کد بالا تغییر بدید اینه که به جای این خط:
TIM2_PSCR_PSC = 0X01; // fCK_CNT = fCK_PSC/2^(PSCR[3:0])   -- note : fCK_PSC = fmaster
اینو بزارید:
TIM2_PSCR_PSC = 0X08; // fCK_CNT = fCK_PSC/2^(PSCR[3:0])   -- note : fCK_PSC = fmaster
و بعد یه LED هم به پایه A.3 وصل کنید!
امیدوارم فهمیده باشید که چی گفتم!
موفق باشید
یا علی مدد...!

جدید:
کد زیر رو هم نوشتم که با درصد کار میکنه! یعنی درصد دیوتی سایکل رو میدی و PWM تنظیم میشه!
دیگه وقفه نمیخواست! پاکش کردم:
#include "iostm8s003f3.h"
#include "stdint.h"
#include "stdbool.h"
void intiTimer (void)
{
  CLK_PCKENR1 = 0xFF; // Enable Clock for Timer
  TIM2_PSCR_PSC = 0X01; // fCK_CNT = fCK_PSC/2^(PSCR[3:0])   -- note : fCK_PSC = fmaster
  TIM2_CCMR2_OC2M = 0X07; //PWM Mode
  TIM2_CCMR2_CC2S = 00;   // CC2 Chanels as output PWM
  TIM2_CCER1_CC2P =  1 ;   // OC2 Active Low
  TIM2_CCER1_CC2E =  1 ;   // Timer OutPut to Pin PD.3
//  TIM2_IER_UIE = 1;     // ENABLE UPDATE INTERRUPT
  TIM2_CR1_CEN = 1; // ENABLE Timer 2
//  asm ("rim");// ENABLE iNTERRUPT
  
}
void SetDutyPWM (int persent)
{
    int val = 65535 * persent/100;
    TIM2_CCR2H = (val >> 8);
    TIM2_CCR2L = (val & 0XFF);  
}
        
void delay (void)
{
  long int i = 10000;
  while (i--);
}
int main( void )
{
  int Counter = 0;
  CLK_CKDIVR = 0X00;    
  intiTimer();
 
  while (1)
  {
    for (Counter = 0; Counter <=100 ; Counter += 1){
          SetDutyPWM (Counter);
          delay ();
    }
  }
  return 0;
}

موفق باشید!
فعلا
یا علی مدد...!