روش راه اندازی پروتکل SPI در میکروکنترلر AVR با نمونه کد Slave

توسط | 3 دی, 1398 | AVR, میکروکنترلر, وبلاگ | 2 دیدگاه ها

راه-اندازی-spi-avr-slave

آموزش AVR یوبرد

شاهکار 8بیتی اتمل

آموزش های رایگان AVR

فیلم های آموزش AVR

آموزش خصوصی AVR

اخبار جدید یوبرد در اینستاگرام

SPI چیست؟ راه اندازی SPI در AVR چگونه است؟ در SPI چگونه دادۀ دریافتی را از دست ندهیم؟ برنامه نویسی SPI چگونه است؟ بافر نرم افزاری در SPI به چه شکل است؟ چگونه از وقفۀ SPI استفاده کنیم؟ چرا از وقفۀ SPI استفاده می کنیم؟ پاسخ این سوالات را در ادامه می بینیم.

در برخی پروژه ها لازم است چند میکروکنترلر در کنار هم قرار گیرند. در این پروژه ها گاهی نیاز است بین میکروکنترلرهای استفاده شده ارتباط برقرار شود. یکی از پروتکل های ارتباطی مناسب برای المان هایی که روی یک بورد قرار گرفته اند پروتکل ارتباطی SPI است. با توجه به ویژگی هایی که SPI دارد می توان تعدادی میکروکنترلر را به هم متصل کرد و بین آنها ارتباط برقرار نمود. SPI در ارتباط بین میکروکنترلر و وسایل جانبی دیگر (که از SPI پشتیبانی می کنند) مانند حافظه های SD (SD Card)، سنسورها، ADC های خارجی، نمایشگرها، ماژول های فرستنده و گیرندۀ رادیویی و … ، در فواصل نزدیک کاربرد دارد. در پروتکل ارتباطی SPI به راحتی می توان چند Slave را به یک یا چند Master متصل کرد و بین آنها ارتباط برقرار کرد. با توجه به دلایل ذکر شده، استفاده از SPI در پروژه ها می تواند مسیر رسیدن به اهداف پروژه را هموارتر کند.

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

فیلم آموزش میکروکنترلرهای AVR مقدماتی

فیلم آموزش ARM STM32 مقدماتی

فیلم آموزش آردوینو مقدماتی

راه اندازی SPI در AVR

در این نمونه کد SPI، یک میکروکنترلر Master اعداد صفر تا 9 را برای میکروکنترلر Slave می فرستد و همزمان اعداد را روی LCD که به آن متصل است نمایش می دهد. در طرف دیگر میکروکنترلر Slave و LCD کاراکتری و Seven segment قرار دارد. این میکروکنترلر می تواند داده های دریافتی توسط SPI را روی LCD و یا Seven segment نمایش دهد. در این پروژه میکروکنترلر Master یک ATmega32 و میکروکنترلر Slave یک ATmega64 است. در این نوشته نمونه کد SPI برای Slave بررسی می شود. دریافت داده از رابط SPI در AVR در اینجا بدون استفاده از توابع SPI و کتابخانۀ SPI در کدویژن انجام می شود. در اینجا با استفاده از وقفۀ SPI این کار انجام می شود.

کدهای نوشته شده برای SPI در فایل main.c

در ابتدا چند کتابخانه را فراخوانی کرده ایم. سپس defineهایی انجام شده است. این defineها به صورت زیر هستند:

#include <mega64A.h>
#include <delay.h>
#include <stdio.h>
#include "\lib\7segment.h"
#include <alcd.h>
#include <spi.h>

#define SS_PORT    PORTB.0
#define CK_PORT    PORTB.1
#define SI_PORT    PORTB.2
#define SO_PORT    PORTB.3

#define SS_DDR    DDRB.0
#define CK_DDR    DDRB.1
#define SI_DDR    DDRB.2
#define SO_DDR    DDRB.3

//Uncomment the bellow comment for using the LCD
#define _USE_CHARACTER_LCD_     2
#define _USE_7SEGMENT_          1
#define _Display_     _USE_CHARACTER_LCD_


#define  receive_data_buffer_size   16

در اینجا تعیین کرده ایم که از LCD کاراکتری استفاده می شود. برنامه با دستورهای پیش پردازنده ای که در جاهای مختلف نوشته ایم تنها کدهای مربوط به LCD کاراکتری را کامپایل می کند. و کدهای Seven segment را کامپایل نمی کند.

در این کد قرار است یک بافر نرم افزاری بسازیم. بنابراین اندازۀ این بافر را define کرده ایم. برای آشنایی بیشتر با بافر می توانید به نوشتۀ «انواع بافر در USART میکروکنترلر AVR» مراجعه کنید.

در  ادامه دو متغیر عمومی تعریف کرده ایم:

char receive_data_buffer[receive_data_buffer_size] , trash=0;

یک رشته به عنوان بافر نرم افزاری با اندازۀ receive_data_buffer_size که آن را بالاتر define کرده ایم. و یک متغیر به نام trash برای داده هایی که دریافت می کنیم و نیازی به آنها نداریم.

دو تابع هم به صورت زیر نوشتیم که آنها را شرح خواهیم داد:

void show_begin(void);
void show_data(void);

در تابع main پس از تعیین ورودی و خروجی بودن پایه های ارتباطی SPI، رجیسترهای SPI مقداردهی شده اند:

void main(void)
{

	SS_DDR =0;
	SI_DDR =0;
	SO_DDR =1;
	CK_DDR =0;

	// SPI initialization
	// SPI Type: Slave
	// SPI Clock Rate: 2000.000 kHz
	// SPI Clock Phase: Cycle Start
	// SPI Clock Polarity: Low
	// SPI Data Order: MSB First
	SPCR=(1<<SPIE) | (1<<SPE) | (0<<DORD) | (0<<MSTR) | (0<<CPOL) | (0<<CPHA);
	SPSR=(0<<SPI2X); . . . . . .}

وقتی میکروکنترلر slave است تنها پایه ای که نیاز است خروجی باشد پایۀ MISO است. همچنین بیت MSTR در رجیستر SPCR باید صفر باشد.

سپس با دستورهای اسمبلی پرچم وقفۀ SPI غیر فعال شده است:

// Clear the SPI interrupt flag
#asm
	in   r30,spsr
	in   r30,spdr
	#endasm

می توانستیم با نوشتن 1 در پرچم وقفۀ SPI (بیت SPIF در رجیستر SPSR) آن را صفر کنیم. در اینجا خود کدویژن با دستورهای اسمبلی این کار را کرده است. هنگام استفاده از کدویزارد این کد اسمبلی تولید می شود.

بعد از آن پرچم کلی وقفه ها فعال شده است. قبل از حلقۀ while تابع show_begin اجرا شده است. در حلقۀ while نیز تابع show_data همواره در حال اجراست:

#asm("sei");

show_begin();

while (1)
{
  show_data();
	}

در ادامه روتین وقفۀ SPI را داریم:

// SPI interrupt service routine
interrupt [SPI_STC] void spi_isr(void)
{
	unsigned char data;
	static unsigned char counter =0;
	data=SPDR;

	if (counter < receive_data_buffer_size)
	{
		receive_data_buffer[counter++] = data +48;
	}
	else
	{
		receive_data_buffer[0] = data +48;
		counter =1;
	}
}

سینتکس روتین وقفه در کدویژن به این صورت است:

interrupt [SPI_STC] void spi_isr(void)

که ابتدا کلمۀ interrupt، سپس داخل کروشه نام بردار وقفۀ مورد نظر قرار می گیرد. بعد از آن هم عبارت void spi_isr(void) قرار می گیرد که بسته به این که چه وقفه ای باشد، نام روتین وقفه متفاوت است.

یک متغیر data و یک متغیر static به نام counter تعریف کرده ایم. هر وقت یک بایت دریافت می شود، دادۀ موجود در این 8 بیت، اعداد 0 تا 9 هستند. این داده در رجیستر SPDR قرار دارد و ما این داده را در متغیر data می ریزیم. این داده ها باید در بافری که ساختیم قرار بگیرند. این بافر (receive_data_buffer) یک متغیر آرایه ای است. ما می خواهیم هر کدام از اعدادی که دریافت می شود در یکی از جایگاه های این آرایه جایگذاری شود. بنابراین متغیر counter را تعریف کردیم و در هر بار اتفاق افتادن وقفۀ SPI که به معنی دریافت 8 بیت داده است، یک واحد به آن اضافه می کنیم تا داده ای که می آید در جایگاه بعدی این آرایه قرار بگیرد. برای این که مقدار این متغیر پس از خروج از روتین وقفه برای دفعات بعد تغییر نکند آن را از نوع static تعریف کردیم.

در ابتدا مقدار counter با اندازۀ بافر تعریف شده مقایسه می شود و اگر مقدار counter کوچکتر از اندازۀ بافر (receive_data_buffer_size) باشد، دادۀ دریافتی که اکنون در data قرار دارد، در جایگاهی با اندیس counter قرار می گیرد. اگر از SPI داده ای دریافت شود و این وقفه اتفاق بیفتد و مقدار متغیر counter بزرگتر و یا مساوی اندازۀ بافر باشد، در این صورت شرط دوم صحیح است و متغیر بعدی در  جایگاه صفرم قرار می گیرد. پس از آن مقدار counter برابر 1 می شود تا دادۀ بعدی در جایگاه یکم قرار بگیرد.

در اینجا دادۀ دریافتی عددی بین صفر تا 9 است. اگر این عدد را بخواهیم در LCD نمایش دهیم باید آن را به کاراکتر ASCII تبدیل کنیم. برای این کار کافی است 48 واحد به مقدار data اضافه کنیم. راه دیگر استفاده از تابع sprint است. که ما از آن استفاده نکردیم. با این وصف اگر اعداد 48 تا 57 را در تابع lcd_puts قرار دهیم، روی LCD اعداد صفر تا 9 نمایش داده می شود. اگر دادۀ دریافتی را به کاراکتر اسکی تبدیل نکنیم، وقتی عدد صفر را دریافت می کنیم، این عدد در جدول کدهای اسکی بیانگر کاراکتر Null است. اگر این کاراکتر را نمایش دهیم هیچ چیزی نمایش داده نمی شود.

تصویر 1 – جدول کدهای ASCII

تابع بعدی show_begin است:

void show_begin(void)
{
	#if ( _Display_  == _USE_CHARACTER_LCD_ )
	lcd_init(16);
	lcd_clear();
	lcd_putsf("SPI Transmit");
	lcd_gotoxy(0,1);
	lcd_putsf("ATmega64_Slave");
	delay_ms(700);lcd_clear();
	lcd_putsf("WWW.UBOARD.IR");
	delay_ms(700);lcd_clear();
	#endif

	#if ( _Display_  == _USE_7SEGMENT_ )
	seven_segment_init();
	PORTA = 0XF8; delay_ms(500);PORTA = 0XF7; delay_ms(200); PORTA = 0XF6; delay_ms(200);
	PORTA = 0XF5; delay_ms(200);PORTA = 0XF4; delay_ms(200);PORTA = 0XF3; delay_ms(200);
	PORTA = 0XF2; delay_ms(200);PORTA = 0XF1; delay_ms(200);PORTA = 0XF0; delay_ms(200);
	delay_ms(500);segment_puts (0x00);
	#endif
}

در تابع show_begin ابتدا بررسی می شود که کدام نمایشگر فعال است. اگر نمایشگر LCD باشد، ابتدا پیکربندی می شود. سپس یک بار clear می شود. بعد از آن عبارت “SPI Transmit” روی LCD نوشته می شود. در سطر بعد نیز عبارت “ATmega64_Slave” نمایش داده می شود. سپس clear می شود. عبارت “WWW.UBOARD.IR” روی آن نمایش داده می شود و بعد از 700 میلی ثانیه پاک می شود. این تابع چون قبل از حلقۀ while بی نهایت قرار دارد تنها یک بار اجرا می شود.

اگر نمایشگر مورد استفاده seven segment باشد، با توجه به کدهای نوشته شده در این قسمت، ابتدا seven segment پیکربندی می شود. با این کار PORTA که seven segment به آن متصل است خروجی می شود. در سطرهای به PORTA مقادیری داده شده است. با توجه به این مقادیر اعداد 8 تا صفر روی هر چهار رقم seven segment با تأخیرهای نوشته شده نمایش داده می شوند. اگر PORTA برابر 0x00 شود، همۀ LEDهای seven segment خاموش می شوند.

تابع show_data نیز به صورت زیر تعریف شده است:

void show_data (void)
{
	#if ( _Display_  == _USE_CHARACTER_LCD_ )
	lcd_gotoxy(0,0);
	lcd_putsf("recive data = ");
	lcd_gotoxy(0,1);
	//receive_data_buffer[0] = SPDR+48;
	lcd_puts(receive_data_buffer);
	delay_ms(100);
	#endif

	#if ( _Display_  == _USE_7SEGMENT_ )
	segment_puts (SPDR);
	#endif
}

در این تابع نیز ابتدا بررسی می شود که کدام نمایشگر انتخاب شده است. هر یک از نمایشگرها که انتخاب شده باشند، تنهای کدهای مربوط به آن نمایشگر به کد HEX تبدیل می شود. اگر LCD انتخاب شده باشد، در سطر اول عبارت “receive data = ” نمایش داده می شود. در سطر دوم نیز دادۀ دریافتی به نمایش در می آید. دادۀ دریافتی که در اینجا نمایش داده می شود، کل محتویات  receive_data_buffer است که به صورت یک رشته نمایش داده می شود. بنابراین داده های قبلب نیز نمایش داده می شوند.

وقتی seven segment انتخاب شده باشد، دادۀ دریافتی که اعداد 0 تا 9 هستند نمایش داده می شوند.

فایل 7segment.h

در این کتابخانه ابتدا چند دستور پیش پردازنده آمده است. بعد از آن defineهایی نیز انجام شده است.

#ifndef _SEVEN_SEGMENT_INCLUDED_

#define _SEVEN_SEGMENT_INCLUDED_

#ifndef _DELAY_INCLUDED_
#include <delay.h>
#endif

#define first_digit     PORTA.7
#define second_digit    PORTA.6
#define third_digit     PORTA.5

اگر کتابخانۀ delay.h در مراحل قبل فراخوانی نشده باشد، در اینجا فراخوانی می شود.

چهار متغیر به صورت زیر تعریف شده اند:

flash unsigned char display_codes1[]={0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89};
flash unsigned char display_codes2[]={0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49};
flash unsigned char display_codes3[]={0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29};

volatile unsigned char yekan=0,dahgan=0,sadgan=0,temp=0;

سه متغیر اول آرایه هایی هستند که عناصر آنها به PORTA داده می شوند. اگر عددی را در اندیس این آرایه ها قرار دهیم، مقداری که در جایگاه با آن اندیس قرار دارد روی PORTA قرار می گیرد. این مقادیر طوری تعیین شده اند که seven segment عدد قرار گرفته شده در اندیس را نمایش می دهد. آرایۀ اول برای نمایش رقم یکان، آرایۀ دوم برای نمایش رقم دهگان و آرایۀ سوم برای نمایش رقم صدگان است.

در ادامه تابع پیکربندی seven segment امده است:

seven_segment_init()
{
	DDRA=0xff;
}

این تابع تنها PORTA را خروجی می کند.

تابع segment_puts نیز در پایان این کتابخانه نوشته شده است:

void segment_puts(unsigned char input)
{
	if ( input < 10)
	{
		yekan = input;
		dahgan = 0;
		sadgan =0;
	}
	else if (  input > 99  )
	{

		yekan   = input % 10 ;
		sadgan = input / 100 ;
		temp    = input / 10 ;
		dahgan = temp % 10 ;
	}
	else if ( input > 9 )
	{
		yekan   = input % 10 ;
		dahgan = input / 10 ;
		sadgan=0;
	}

	PORTA=display_codes1[yekan];
	delay_ms(5);
	PORTA=display_codes2[dahgan];
	delay_ms(5);
	PORTA=display_codes3[sadgan];
	delay_ms(5);
}

عددی که در ورودی این تابع قرار می گیرد در مراحل بعدی مورد بررسی قرار می گیرد. اگر یک رقمی باشد (از 10 کوچکتر باشد)، در متغیر yekan ریخته می شود. اگر دو رقمی باشد، رقم یکان آن در متغیر yekan و رقم دوم در متغیر dahgan ریخته می شود. اگر سه رقمی باشد نیز به همین صورت رقم یکان در متغیر yekan، رقم دوم در متغیر dahgan و رقم سوم در متغیر sadgan ریخته می شود. در نهایت نیز مقدار PORTA برابر می شود با عناصری از آرایه های مذکور. به طوری که متغیرهای yekan، dahgan و sadgan تعیین می کندد که کدام عنصر از این ارایه ها در PORTA قرار بگیرد. مقدار تأخیر نیز 5 ms در نظر گرفته شده که این مقادیر به سرعت روی seven segment نمایش داده شوند و عدد دو یا سه رقمی را بتوانیم بدون چشمک زدن seven segment ببینیم.

از راه اندازی SPI در AVR نتایج زیر حاصل می شوند:

  1. SPI یکی از پروتکل های ارتباطی مناسب برای المان های روی یک بورد است.
  2. SPI در ارتباط بین میکروکنترلر با میکروکنترلر و میکروکنترلر با وسایل جانبی (که ارتباط SPI را پشتیبانی می کنند) می تواند استفاده شود.
  3. برای این که میکروکنترلر علاوه بر ارتباط SPI بتواند پردازش های دیگر نیز انجام دهد، باید برای SPI به طور اصولی برنامه نوشت و همچنین از وقفۀ SPI استفاده کرد.
  4. یکی از روش های دریافت درست داده ها و از دست ندادن آنها استفاده از بافر نرم افزاری است.
  5. برای تبدیل اعداد به کد اسکی حتماً لازم نیست از توابعی مثل sprint استفاده کنیم. کافی است عدد را با 48 جمع کنیم.
  6. برای نمایش در سون سگمنت بهتر است تابع نوشته شود.


 

رضا اسدی

رضا اسدی

مدیر یوبرد، خالق و توسعه دهندۀ پلتفرم یوبرد، مجری پروژه های الکترونیکی، فعال در صنعت آسانسور، سابقه فعالیت در صنعت خودرو و همکاری در صنعت پزشکی و صنایع دیگر، آموزگار آموزش های یوبرد

سفارش پروژه میکروکنترلر یوبرد

زیرساخت مطمئن صنعت

جدیدترین تاپیک های AVR

برنامه-نویسی-میکروکنترلر-ویژوال-استودیو

برنامه نویسی میکروکنترلرها با ویژوال استودیو

نصب VisualGDB روی ویژوال استودیو، ساخت پروژه برای برنامه نویسی میکروکنترلرها با ویژوال استودیو، ساخت پروژه برای STM32 در ویژوال استودیو، کدنویسی STM32 در ویژوال استودیو، ساخت پروژه برای AVR در ویژوال استودیو، ساخت پروژه برای LPC در ویژوال استودیو، ساخت پروژۀ آردوینو در ویژوال استودیو

رله-relay

رله، سوییچ تحریک پذیر

تعریف رله، تاریخچه، طرز کار رلۀ الکترومکانیکی، اصطلاحات، انواع الکترومکانیکی، رلۀ حالت جامد، رلۀ هیبریدی، رلۀ حرارتی و انواع تحریک شونده با عوامل غیر الکتریکی، انواع موجود در صنعت برق و برق صنعتی، رلۀ ایمنی و برخی انواع حفاظتی، رلۀ دیجیتال، لزوم استفاده و راه اندازی رله، مشخصات الکتریکی

پروتکل-i2c-protocol

پروتکل I2C، ادغامی از USART و SPI توسط Philips

پروتکل I2C، تاریخچه I2C، ویژگی ها و کاربردهای I2C، عبارات و اصطلاحات I2C، باس، اتصالات و گسترش شبکه I2C، سیگنال های I2C، قالب داده و آدرس در I2C، انتقال داده در I2C، آدرس دهی 10 بیتی، قابلیت Multi-master، حکمیت، مشخصات الکتریکی و زمانی، مقدار مقاومت های پول آپ، Clock Stretching

سون-سگمنت-seven-segment

سون سگمنت، نمایشگر هفت قسمتی

سون سگمنت چیست؟ انواع 7-segment، تاریخچه 7-segment، ساختار 7-segment و نمایش در آن، تعداد ارقام و 7-segmentهای مالتی پلکس، کاربردها، انواع اندازه ها و رنگ ها و مدار راه اندازی 7-segment، بایاس 7-segment، مقدار مقاومت در راه اندازی 7-segment، درایور سون سگمنت، پایه های 7-segment

2 دیدگاه ها

  1. سلام و درود بر شما برادر عزیز و بزرگوارم،خوشبختانه در ده سال اخیر مطالب آموزشی در مورد میکروکنتر،چه avr ،چه arm ، … و در نهایت بوردهای آردوینو بسیار زیاد و متنوع شده ولی متاسفانه اغلب تکراری و یا با تغییرات جزئی در انجام همان پروژه های عمومی و تکراری صورت پذیرفته و متاسفانه هنوز به پروژه های متوسط به بالا و بدون رفرنس مناسب چه از حیث کتب و سایتهای اینترنتی ازجمله طراحی و ساخت اینورتر سینوسی،اسپید کنترلر موتورهای براشلس بدون سنسور و…و…و‌.. هنوز پرداخته نشده واین یک علامت سوال بزرگ است.

    پاسخ
    • سلام وقت بخیر. بله درسته. در یوبرد مدت زیادیست درحال بستر سازی برای این کار هستیم. انشالا این موارد بزودی آغاز می شوند.

      پاسخ

یک دیدگاه بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دیگر آموزش های یوبرد

آموزش زبان C و MISRA-C یوبرد

گام نخست دنیای میکروکنترلر

آموزش طراحی PCB و نویز یوبرد

تجسم دنیای الکترونیک

آموزش لحیم کاری و IPC-A-610 یوبرد

ساخت دنیای الکترونیک

آموزش میکروکنترلرهای ARM STM32 یوبرد

شروع بازی ST

آموزش آردوینو یوبرد

جادۀ آسفالت میکروکنترلر

آموزش FreeRTOS یوبرد

زمان واقعی در میکروکنترلر و پردازنده های کوچک با FreeRTOS

آموزش ماژول های SIM800 یوبرد

تلفن همراه صنعت

آموزش زبان ++C و ++MISRA-C یوبرد

لمس شی گرایی در میکروکنترلرها

آموزش میکروکنترلرهای LPC یوبرد

یادگار فیلیپس

آموزش های شاخص

دانلود

لطفا برای دریافت لینک دانلود اطلاعات خواسته شده را وارد نمایید
ضبط پیام صوتی

زمان هر پیام صوتی 4 دقیقه است