১৪. 8051 এ টাইমার পেরিফেরালের প্র্যাক্টিকাল ব্যবহার




আমরা আগেই জেনেছি যে টাইমার হল প্রত্যেকটি মাইক্রোকন্ট্রোলারের জন্যে খুবই গুরুত্বপূর্ণ একটি পেরিফেরাল্‌। যেকোন টাইম রিলেটেড অপারেশন করার জন্যে টাইমার পেরিফেরালটি খুবই গুরুত্বপূর্ণ ভুমিকা পালন করে থাকে। এর আগের পোস্টে আমরা Hello world প্রোগ্রাম লিখার সময় একটি Delay() ফাংশন ব্যবহার করেছিলাম। কিন্তু সেই ফাংশনে আমরা জাস্ট লুপ ঘুরিয়ে কিছু নম্বর কাউন্ট করিয়েছিলাম সিপিউ কে দিয়ে। এতে করে সিপিউ কিছুক্ষনের জন্যে ব্যস্ত ছিল এবং আমরা মোটামোটি ভাবে কিছুক্ষনের একটি ডিলে পেয়েছিলাম। কিন্তু ঠিক কতটা সময়ের ডিলে পেয়েছিলাম তা আমরা কেউই জানিনা। এভাবে Delay() ফাংশন তৈরি করলে আসলে পারফেক্ট হয়না। তবে একটি বুদ্ধি আছে যেটা দিয়ে আমরা এইভাবেও ঠিক মাপের Delay() ফাংশন তৈরি করতে পারবো। সেটা হল কোন একটি পিনকে আউটপুট হিসেবে ডিক্লেয়ার করে সেই পিনের সিগন্যাল অসসিলোস্কোপ দিয়ে পরিমাপ করা। অসসিলোস্কোপের স্ক্রিনে দেখে আমরা লুপ বা কত পর্যন্ত কাউন্ট করলে উক্ত ডিলে পাওয়া যাবে তা নির্ধারণ করতে পারি। ট্রায়াল এন্ড এরর মেথড আরকি। কিন্তু এভাবে ডিলের একটি সমস্যা থেকেই গেলো। সেটি হল এই সময়ের জন্যে সিপিউ কিন্তু সংখ্যা গণনা করতেই ব্যস্ত আছে। এই সময়ের মাঝে যদি আমাদের আর কোন কাজ করতে হয় সেটা কিন্তু আমরা করতে পারছি না। দেখা গেলো এই সময়ের মাঝে আমাদের কোন একটি পিনে ইনপুট সিগন্যাল আসছে এবং ঐ সিগন্যাল রিলেটেড কোন একটি অপারেশন আমাদের করতে হবে। কিন্তু সিপিউ যেহেতু সংখ্যা গণনায় ব্যস্ত তাই হয়ত সে কয়েক মিলিসেকেন্ড পর সেই কাজটি করছে। কয়েক মিলিসেকেন্ড আমাদের কাছে অনেক কম সময় হলেও কিন্তু মাইক্রোকন্ট্রোলারের কাছে অনেক সময়। আর এরকম হলে তো আমাদের সিস্টেম মোটও রিয়েল টাইম সিস্টেম হল না। সুতরাং আমরা এটি বুঝতে পারলাম যে সিপিউকে দিয়ে সংখ্যা গণনা করিয়ে প্রাপ্ত ডিলে মোটেও সুবিধার নয়।




এর জন্যে আমাদের সবচেয়ে বেস্ট অপশন হল টাইমার পেরিফেরাল্‌। এবার আমরা টাইমার সম্পর্কে এমন একটি তথ্য জানবো যা আগে জানিনি। সেটি হল টাইমার পেরিফেরাল্‌ দুটি কিন্তু সিপিউ এর উপর নির্ভরশীল নয়। এরা সিপিউ এর প্যারালালে কাজ করতে পারে। সিপিউ কখন ব্যস্ত আছে আর কখন নেই, এসবের দিকে টাইমারকে নির্ভর করতে হয়না। টাইমার কিন্তু নিজের মত সময় গণনা করতে পারে এবং নিজের টার্গেটে পৌছালে সে ইন্টারাপ্ট জেনারেট করে সিপিউ কে দিয়ে উক্ত কাজ করিয়ে নেয়। এতে করে সিস্টেম যথেষ্ট রোবাস্ট হয়। তাহলে আমাদের যদি প্রোগ্রামে কিছুক্ষনের জন্যে দেরি করতে হয় তাহলে আমরা টাইমারকে দিয়ে সেই সময়টুকু কাউন্ট করিয়ে নিতে পারি এবং সেই সময় কাউন্ট করা হয়ে গেলে টাইমারই সিপিউ কে জানিয়ে দিবে তাকে কি করতে হবে।

আমরা এর আগের টাইমার পেরিফেরালের পোস্টে পড়েছি যে আমাদের 8051 মাইক্রোকন্ট্রোলারের দুটি টাইমার রয়েছে এবং তাদের অপারেশন অনুযায়ী মোটামোটি ভাবে তাদেরকে নিচের ছবির মত করে দেখানো যায়।




টাইমার-০ দিয়ে মোটামোটি ভাবে ডিলে টাইপের কাজ করানো হয় এবং টাইমার-১ দিয়ে বডরেট জেনারেট করানো হয়। আর কথা না বাড়িয়ে তাহলে এবার একটি উদাহরনের মাধ্যমে বিষয়টি দেখে নেয়া যাক। উদাহরন হিসেবে আমরা টাইমার-০ নিয়ে কাজ করব এবং প্রথমে MODE-1 অর্থাৎ ১৬-বিটের টাইমার মোডে কাজ করব।

ধরা যাক আমরা একটি লেড বাল্ব কে ৫০ মিলিসেকেন্ড পর পর জ্বালাতে এবং নিভাতে চাই। এই ৫০ মিলিসেকেন্ড সময়টি হয়ত আমাদের চোখে ধরা পড়ার মত নয়, কিন্তু মাইক্রোকন্ট্রোলারের কাছে এটা অনেক বড় একটি সময় যেহেতু সে একটি কাজ করে ১ মাইক্রোসেকেন্ডে।

এখন আমাদের কাজ হল টাইমার-০ কে মোড-১ এর জন্যে কনফিগার করা এবং ৫০ মিলিসেকেন্ড সময় গণনা করতে রেজিস্টারের ভ্যালু কত হবে তা হিসেব করে বের করা এবং সবশেষে সেই অনুযায়ী প্রোগ্রাম করা। আমরা তাহলে টাইমার রিলেটেড রেজিস্টারগুলোর ছবি একবার দেখে নিই। কোন রেজিস্টারের কোন বিটের কি কাজ তা জানার জন্যে আমরা এই পোস্টটি পড়ে নিব।






টাইমার-০ এর জন্যে TMOD রেজিস্টারের লেস সিগনিফিক্যান্ট চারটি বিট - GATE0, C/T, M1, M0.
মোড-০ এর জন্যে M0 এবং  M1 এর কম্বিনেশন হয় M1 = 0 এবং M0 = 1
আমরা টাইমার হিসেবে ব্যবহার করতে চাই, কাউন্টার হিসেবে নয়, তাই বিট C/T = 0
ফ্রি রানিং মোডে টাইমার ব্যবহার করতে চাই, ইন্টারাপ্ট দিয়ে নয়, এজন্যে বিট GATE0 = 0
টাইমার-১ রিলেটেড বিটগুলো 0 করে রাখতে হবে ব্যবহার না করলে।

এই অনুযায়ী প্রত্যেক বিটে মান বসালে TMOD রেজিস্টারের ভ্যালু হবে বাইনারি আকারে এরকম আকারের - 
TMOD  = 0  0  0  0  0  0  0  1 (binary)
             = 0x01 (hex)

TCON রেজিস্টারে আমরা পরে হাত দিব। এর আগে আমাদেরকে TH0 এবং TL0 এর মান হিসাব করতে হবে যাতে করে টাইমার-০ তার টার্গেট বুঝতে পারে। TH0 এবং TL0 এর মান হিসাব করার জন্যে সুত্র নিচে দেয়া হল।

XTAL = 12 MHz
Timer 0 frequency = 12 /12 = 1 MHz
Timer0 Tick = 1 / 1MHz = 1 uS.

এর মানে হল ১ মাইক্রোসেকেন্ডে টাইমার-০ এর রেজিস্টারের মান এক ধাপ করে বৃদ্ধি পায়।

Delay = tick * 1uS.

যেমন ১০০০ টিক মানে হল = (১০০০ * ১) মাইক্রোসেকেন্ড = ১০০০ মাইক্রোসেকেন্ড = ১ মিলিসেকেন্ড

Count = Delay / Tick

Register_Value =  Timer Max - Count
                        =  Timer Max - (Delay / Tick)
তাহলে উপরের সূত্রানুযায়ী এখন আমরা ৫০ মিলিসেকেন্ডের জন্যে TH0 এবং TL0 রেজিস্টারের মান বের করতে পারব।

Register Value = 65,536 - (50,000 / 1)                  50 milliseconds = 50,000 microseconds
                      = 15,536

এই 15,536 কে হেক্সাডেসিমেলে কনভার্ট করলে দাড়ায় 3CB0. এই ভ্যালু থেকে ৮ বিট করে ভাগ করে দিলে,       
TH0 = 3C এবং TL = B0

এই ১৫,৫৩৫ ভ্যালু যে আমরা TH0 এবং TL0 রেজিস্টারে সেট করে দিলাম এটার দ্বারা কি করে টাইমার বুঝবে যে তাকে ৫০ মিলিসেকেন্ড সময় গণনা করতে হবে। বিষয়টা বেশ ইন্টারেস্টিং। ১২ মেগাহার্টজ ক্রিস্টাল অসসিলেটরের জন্যে  টাইমার তো ১ মাইক্রোসেকেন্ড পর পর ১ ধাপ করে তার মান বৃদ্ধি করে। আমরা যে মান তার রেজিস্টারে সেট করে দিয়েছি সেটা থেকেই সে গণনা শুরু করবে। অর্থাৎ টাইমার পেরিফেরাল্‌সে ১টি ক্লক পালস্‌ ঢুকলে  ১৫,৫৩৬ থেকে ১৫,৫৩৭ হবে, আরেকটি ক্লক পালসে্‌ ১৫,৫৩৮ হবে। এভাবে টাইমার রেজিস্টার ৩৫,৫৩৫ পর্যন্ত পৌছাবে। 

তাহলে টোটাল স্টেপ হচ্ছে = ৩৫,৫৩৫ - ১৫,৫৩৬ = ৪৯,.৯৯৯। যেহেতু ১৫,৫৩৬ থেকেই শুরু হয় তাই ৫০,০০০ ধাপ।
১ ধাপ করে মান বৃদ্ধি পেতে সময় লাগে ১ মাইক্রোসেকেন্ড, 
সুতরাং ৫০,০০০ ধাপ বৃদ্ধি পেতে সময় লাগে ৫০,০০০ * ১ = ৫০,০০০ মাইক্রোসেকেন্ড বা ৫০ মিলিসেকেন্ড।
খুবই সিম্পল হিসাব-নিকাশ। এখন ৩৫,৫৩৫ এ যেয়ে টাইমার-০ TCON রেজিস্টারের TF0 বিট হাই করে জানান দিয়ে দিবে যে সে ওভার-ফ্লো হয়েছে। এই ফ্ল্যাগ-বিট কে দেখে সিপিউ তখন ইন্টারাপ্ট-সার্ভিস-রুটিনে যাবে, যদি টাইমার-০ ইন্টারাপ্ট ফিচারটি কাজে লাগানো হয়। এবং এই ইন্টারাপ্ট-সারভিস-রুটিন এক্সিকিউট হয়ে গেলে TF0 বিটটি অটোমেটিক ক্লিয়ার হয়ে যাবে। আর ইন্টারাপ্ট ব্যবহার না করলে প্রোগ্রামার কে ক্লিয়ার করে দিতে হবে প্রোগ্রামের মাধ্যমে। এই হল টাইমারের কাজ করার মূল নীতি। মোড-০ এর ক্ষেত্রেও ঠিক এমনটাই হয় কিন্তু সেক্ষেত্রে ৮১৯১ গণনার পড়ে টাইমার ওভার-ফ্লো হয়ে যায়। আর তাছাড়াও মোড-০ কোন সিগন্যালের pulse width পরিমাপ করতে ব্যবহৃত হয়।

টাইমারকে ডিলে ফাংশন হিসেবে ব্যবহার করার অ্যালগড়িদম -
  • টাইমারের মোড সিলেক্ট করে দিতে হবে TMOD রেজিস্টারের ভ্যালু সেট করার মাধ্যমে।
  • TH0 এবং TL0 রেজিস্টারে সূত্রানুযায়ী মান লোড করে দিতে হবে।
  • TMOD রেজিস্টারের TR0 বিট সেট করে দিয়ে টাইমার অন করতে হবে।
  • টাইমার ওভার-ফ্লো অর্থাৎ TF0 = 1 না হওয়া পর্যন্ত অপেক্ষা করতে হবে।
  • TF0 বিট ক্লিয়ার করে দিতে হবে।
  • TR0 বিট ক্লিয়ার করে টাইমার স্টপ করতে হবে।

এবারে তাহলে ৫০মিলিসেকেন্ড পর পর লেড লাইট অন অফ করার কোড দেখা যাক। প্রথমে ইন্টারাপ্ট ছাড়া নরমাল ফাংশন আকারে দেখা যাক।

 #include<reg52.h>                 //include header file for at89s52 mcu of 8051 family 
 sbit led = P2^0;                  //declare P2.0 pin as output pin  
 void delay(void);                 //delay() function prototype  
 void main()  
 {  
      TMOD = 0x01;                 //set timer0 in mode1  
      P2 = 0xFE;                   //declare P2.0 bit as ouput b11111110  
      while(1)                     //infinite loop to run the program continuous  
      {  
           led = 0;                //make P2.0 pin low  
           delay();                //dealy 50 milisecond   
           led = 1;                //make P2.0 pin high  
           delay();                //dealy 50 milisecond  
      }  
 }  
 /*  
      FCPU = 12 MHz  
      Timer0 Frequency = 12 / 12 = 1 MHz  
      1 tick = 1 / Timer0 Frequency  
             = 1 / 1 MHz  
             = 1 uS  
      delay = tick * 1 uS  
      count = delay / tick  
      Register Value = Timer Max - count  
                     = Timer Max - (delay / tick)  
      for 50 ms delay   
      ---------------  
      Register Value = Timer Max - (delay / tick)  
                     = 65536 - (50*1000/1)  
                     = 65536 - 50000  
                     = 15536 in hex => 0x3C B0  
                                         -- --  
                                        TH0 TL0  
 */  
 void delay()       
 {  
      TH0 = 0x3C;               //load 0x3C in TH0 and  
      TL0 = 0xB0;               //load 0xB0 in TL0 to get 50ms  
      TR0 = 1;                  //run timer0  
      while(TF0 == 0);          //wait until timer0 overflow has been occured  
      TF0 = 0;                  //clear the overflow flag  
      TR0 = 0;                  //stop timer0  
 }  


delay() ফাংশনের ভিতরে অ্যালগরিদম অনুযায়ীই জাস্ট ইন্সট্রাকশন দেয়া হয়েছে। আর মেইন ফাংশনের ভিতর এই delay() ফাংশনটি কল করা হয়েছে লেড অন করার পর একবার এবং অফ করার পর একবার। খুবই সিম্পল একটি কোড।

এবারে তাহলে ইন্টারাপ্টের মাধ্যমে একই কাজ করা যাক। এক্ষেত্রে ইন্টার্নাল ইন্টারাপ্ট সম্পর্কেও আমাদের ধারনা ক্লিয়ার হয়ে যাবে।

 #include<reg52.h>  
 sbit led = P2^0;                 //declare P2.0 pin as output pin  
 void init_timer(void);           //delay() function prototype  
 void main()  
 {  
      P2 = 0xFE;                  //declare P2.0 bit as ouput b11111110  
      init_timer();               //just initialize the timer0 at the very beginning   
      while(1)                    //infinite loop to run the program continuous  
      {  
           //do other work here. because interrupt takes care of toggling led  
      }  
 }  
 /*  
      FCPU = 12 MHz  
      Timer0 Frequency = 12 / 12 = 1 MHz  
      1 tick = 1 / Timer0 Frequency  
             = 1 / 1 MHz  
             = 1 uS  
      delay = tick * 1 uS  
      count = delay / tick  
      Register Value = Timer Max - count  
                     = Timer Max - (delay / tick)  
      for 50 ms delay   
      ---------------  
      Register Value = Timer Max - (delay / tick)  
                     = 65536 - (50*1000/1)  
                     = 65536 - 50000  
                     = 15536 in hex => 0x3C B0  
                                         -- --  
                                        TH0 TL0  
 */  
 void init_timer(void)       
 {  
      TMOD = 0x01;           //set timer0 in mode1  
      TH0 = 0x3C;            //load 0x3C in TH0 and  
      TL0 = 0xB0;            //load 0xB0 in TL0 to get 50ms  
      ET0 = 1;               //enbale Timer0 interrupt in IE register  
      EA = 1;                //enable global interrupt in IE register  
      TR0 = 1;               //run timer0  
 }  
 void timer_0() interrupt 1  
 {  
      led = ~led;           //just toggle the led status.  
 }  

উপরের কোডটি ঠিক আগের মতই। কিন্তু এক্ষেত্রে কিন্তু মেইন ফাংশনের while(1) লুপের ভিতরে কোন ফাংশন কল করা হয়নি। বরং আমরা ৫০ মিলিসেকেন্ড পর পর যা করতে চেয়েছি সেটাকে ইন্টারাপ্ট-সার্ভিস-রুটিনের মধ্যে করা হয়েছে। আর এইবারে মেইন ফাংশনের শুরুতেই init_timer() ফাংশন দিয়ে টাইমার-০ এবং ইন্টারাপ্ট কে কনফিগার করে দেয়া হয়েছে। ইন্টারাপ্ট এবং টাইমার রিলেটেড রেজিস্টার এবং বিট গুলো ভালোভাবে পড়লেই কোডটি বোঝা যাবে বলে আশা করি। সার্কিটটি হবে নিচের মত-



এক্ষেত্রে পার্থক্য হল ইন্টারাপ্ট মোডে আমাদের TF0 বিটকে নিয়ে চিন্তা করতে হয়না। কেননা ইন্টারাপ্ট-সার্ভিস-রুটিন এক্সিকিউট হলেই তা অটোমেটিক ক্লিয়ার হয়ে যায়। কিন্তু নরমাল মোডে আমাদের TF0 এবং TR0 বিট নিয়ে চিন্তা করতে হয়। টাইমার-১ এর ক্ষেত্রেও ঠিক একই ভাবে অপারেশনগুলো হয়ে থাকে। শুধু  বিটগুলো তখন TF1, TR, ET1 এবং রেজিস্টার TH1 এবং TL1 হয়ে যায়।

আচ্ছা এখন বিষয় হল আমরা টাইমার-০ বা টাইমার-১ ব্যবহার করে যে পদ্ধতিতে Delay() ফাংশন তৈরি করা শিখলাম,  সেটায় কিন্তু  ৬৫.৫৩৫ মিলিসেকেন্ডের বেশি সময় কাউন্ট করা যাবেনা। কেন যাবেনা তা হয়ত এতক্ষনে সবাই বুঝতে পেরেছে। আমাদের যদি ১ সেকেন্ড বা ২ সেকেন্ড বা ৬৫ মিলিসেকেন্ডের বেশি ডিলেয়র প্রয়োজন হয় তাহলে?? তাহলে আমাদের অবশ্যই ইন্টারাপ্টের সাহায্য নিতে হবে এবং আরেকটু সুক্ষ্ণ হিসাব করতে হবে। এটা আমরা পরের কোন পোস্টে শিখব।  

মোড-২ বা অটো-রিলোড মোড সম্পর্কে আমরা UART কমিউনিকেশনের প্র্যাক্টিকাল ইমপ্লিমেন্টের পোস্টে শিখতে পারবো। কেননা এটি বডরেটের জন্যে ব্যবহৃত হয়।

মন্তব্যসমূহ

এই ব্লগটি থেকে জনপ্রিয় পোস্টগুলি