همون طور که قبل تر گفته بودم به عنوان اولین پروژه یک چشمک زن هشتایی می سازیم.

کد برنامه

/*
 * 8-LED-flasher.c
 *
 * Created: 08/11/1399 10:24:35 ب.ظ
 * Author : bsimjoo
 */
#define F_CPU 1000000

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRD=0xff;
    PORTD=0xff;
    while (1)
    {
        for(uint8_t i=1;1;i*=2){
            _delay_ms(300);
            PORTD=i;
            if(i>=128) break;
        }
    }
}

تفسیر کد...

در خط اول با نوشتن #define F_CPU 1000000 مقدار فرکانس کاری CPU را مشخص می کنیم. این تکه کد برای کار با _delay_ms ضروری است. دستور #define توسط کامپایلر اجرا می شود و هرجا از برنامه که کلمه F_CPU نوشته شده باشد مقدار گفته شده را جایگزین می کند. در واقع در کد کامپایل شده هیچ اثری از F_CPU نیست. فرکانس کاری CPU توسط یک کلاک مشخص می شود، درواقع اکثر مدار های منطقی از یک کلاک برای هماهنگی تمام اعضا استفاده می کنند. میکرو ها هم از این فاعده خارج نیستند. در میکرو ATMega8 کلاک می تواند با 4 بسامد 1MHz, 2MHz, 4MHz و 8MHz نوسان کند. به ازای هر 1MHz فرکانس کلاک یک ملیون دستور در ثانیه توسط CPU اجرا می شود. البته کلاک داخلی میکرو ها زیاد دقیق نیستند و در دمای 25 درجه و با ولتاژ 5V در حدود %3± خطا دارند که یعنی برای فرکانس 1MHz در بازه ی 0.97MHz تا 1.03MHz است که این بازه ی زیادی هست. البته این خطا برای پروژه فعلی زیاد مهم نیست و در آینده در مورد دستیابی به فرکانس های دقیق تر توضیح خواهم داد. همان طور که از اسم کلاک پیداست، CPU برای دستیابی به زمان از آن استفاده می کند.

در میکرو ATmega8 کلاک به طور پیشفرض با فرکانس 1MHz ±3% نوسان می کند. برای ایجاد وقفه کتابخانه delay.h را با دستور #include <util/delay.h> وارد و با دستور _delay_ms(300); وقفه ای در حدود 300ms ایجاد کردیم. شیوه کار ایجاد وقفه به این صورت است که ابتدا محاسبه می شود انجام یک دستور توسط CPU چقدر زمان می گیرد، سپس به تعداد لازم دستور در یک حلقه اجرا می شود تا وقفه مورد نظر حاصل شود، اما همانطور که میدانید این زمان دقیق نخواهد بود.

در تابع main همانطور که قبلا اشاره ای داشتم ابتدا کار رجیستر را انجام می دهیم. میکروی ATmega8 دارای سه پروت با نام های B, C, D است. (شاید مثل من برای شما هم سوال باشد که چرا از A شروع نشد. جواب را من هم نمی دانم!). دستور DDRD=0xff; پورت D را به صورت خروجی تنظیم می کند. این کار با گذاشتن بیت های یک در رجیستری DDRX پورت انجام می شود. از آنجا که هر PORT دارای 8 PIN است پس مقدار DDRD می تواند در بازه 0 تا 255 یا از 0b0000000 تا 0b11111111 یا از 0x00 تا 0xff باشد.(درواقع مقادیر گفته شده با هم برابرند و در مبنا های مختلف 10، 2 و 8 گفته شده اند. اگر با مبانی عددی آشنا نیستید در آینده به آن خواهم پرداخت.) پس رجیستری DDRX جهت انتقال داده را برای یک پورت مشخص می کند. بیت یک پین مربوطه را خروجی و بیت صفر پین مربوطه را ورودی می کند. اما برای تعیین وضعیت پورت به یک رجیستری دیگر نیز نیاز است. رجیستری PORTX وضعیت روشن یا خاموش بودن پین های پورت را مشخص می کند. در واقع اگر هر پین از پورت X خروجی باشد زمانی که بیت مربوطه در رجیستری PORTX یک باشد آن پین روشن (مثلا 5V+) و اگر صفر باشد خاموش (0v) می شود. اگر هم پین ورودی باشد بیت صفر پتانسیل پین را 0V می کند و اگر بیت یک باشد پتانسیل پین را 5V+ کرده و مقاومت PULL-UP داخلی را فعال می کند. (pull-up ساز و کاری است که برای تشخیص وصل شدن پین به GND یا همان 0V یا به زبان ساده سر منفی باطری است) پس با توجه به توضیحات اگر 8 تا LED را به پورت D میکرو وصل کنیم دستور PORTD=0xff; تمام آن ها را روشن می کند.

سپس یک حلقه نسبتا عبدی داریم(چون تا زمانی که میکرو به برق وصل است یا RESET نشده این حلقه در جریان است.) که درون حلقه باید برنامه چشمک زن نوشته شود. برای حلقه for یک متقیر i از نوع uint8_1 تعریف می کنیم که در واقع از نوع unsigned char است. انتخاب این نوع به اینخاطر است که ما به مقادیر 8 بیتی نیازمندیم و نیازی هم به علامت مثبت و منفی نداریم (اگر متغیر علامت دار باشد 7 بیت برای مقدار و یک بیت برای علامت داریم ولی ما هر 8 بیت را برای مقدار می خواهیم پس متغیر را از نوع بی علامت انتخاب می کنید. این مسائل مربوط به بحث مبانی عددی هستند). مقدار اولیه i را هم یک انتخاب می کنید که برابر می شود با 0b00000001 و هر بار i را دو برابر می کنیم تا توان ها دو را در i داشته باشیم؛ پس خواهیم داشت 0b00000100, 0b00000010, 0b00000001 ,... . پس ما حداکثر تا مقدار i=128 نیاز داریم. اما اگر این را مستقیما در شرط حلقه ی for بنویسیم overflow رخ می دهد چون حداکثر مقدار uint8 عدد 255 است پس اگر دستور 258=2*128 اجرا شود overflow اتفاق می افتد و از آنجا که در حلقه های for ابتدا عملگر (در اینجا i*=2) اجرا می شود و سپس شرط حلقه بررسی می شود این اتفاق رخ خواهد داد؛ و اگر هم که شرط را i<128 بگذاریم آنگاه i به 128 نمی رسد و LED آخری روشن نمیشود. پس شرط حلقه را 1 (مطلق) می گذاریم و درون و در انتهای حلقه مقدار i را چک می کنیم و هر بار مقدار پورت D را برابر با i می کنیم به این ترتیب LED ها به ترتیب یکی پس از دیگری و با وقفه حدودا 300ms روشن خواهند شد.

مدار

شماتیک مدار به صورت زیر است:

البته گذاشتن مقاومت در برنامه proteus اختیاری است ولی در مدار واقعی با توجه به منبع 5V یا 6V گذاشتن یک مقاومت 390 اهمی الزامی است. چرا که LED از قانون اهم تبعیت نمی کند و با روشن شدنش مقاومتش صفر میشه و به میکرو و خود LED آسیب می زنه. برای انتخاب مقاومت LED می توانید به سادگی از نرم افزار ElectroDroid استفاده کنید. شماره پین میکرو بر پایه های در نقشه شماتیک بالا نوشته شده ولی می توانید از دیتاشیت این میکرو هم استفاده کنید (حتما دانلود کنید در آینده خیلی به درد می خوره!)