برنامج UART مزدوج كامل لـ ATtiny13

مرحبا لجميع مستخدمي Geektimes! ذات مرة كنتيجة لخدمتي ، كنت بحاجة إلى تنفيذ برنامج UART على متحكم ATtiny13 الشهير. Googling ، لقد وجدت عددًا كبيرًا من المقالات حول هذا الموضوع ، يتم نشر العديد منها هنا:


وهناك موارد أخرى:


أحدث تنفيذ ، بشكل عام ، يلبي احتياجاتي (ازدواج كامل). ولكن ، أولاً ، الرمز مكتوب في CodeVision AVR ، والذي لا أستخدمه لأسباب دينية بحتة ، وثانيًا ، تم التعليق بشكل سيئ على إدخالات المجمع مما يزيد من تثبيط الرغبة في فهم الشفرة. لقد حددت هدفًا لكتابة مكتبة UART مزدوجة الاتجاه سهلة الاستخدام في لغة C خالصة. وفي الوقت نفسه ، اكتب مقالًا حول هذا الموضوع ، لأن المهمة مثيرة للاهتمام للغاية نظرًا لمقدار محدود للغاية من موارد الذاكرة ووحدة التحكم (مؤقت واحد 8 بت). بالنسبة للمبتدئين في برمجة الميكروكونترولر ، ستكون هذه مادة تدريبية جيدة ، مثل أنا نفسي في عملية كتابة المكتبة ، من الصفر تقريبًا أتقنت هندسة AVR.

من يهتم - مرحبًا بكم في القات ، سيكون هناك الكثير من النصوص ، الكثير من أكواد المصدر مع التعليقات في الكود.

لذا ، لنبدأ. سأقوم ببساطة بنشر ملف رأس uart13.h بالشكل الذي به مع التعليقات في الكود ، كل شيء بسيط هناك.

ملف رأس Uart13.h
/*    UART   ATtiny */

#ifndef _UART13_H_
#define _UART13_H_ 1

#include <avr/io.h>
#include <avr/interrupt.h>

/*
*	        
*	    UART.
*/

#define TXPORT PORTB		//    
#define RXPORT PINB		//    
#define TXDDR DDRB		//     
#define RXDDR DDRB		//     
#define TXD 0			//       
#define RXD 1			//       

/*
*	  ,     ()
*	 BAUD_DIV   :
*	BAUD_DIV = (CPU_CLOCK / DIV) / BAUD_RATE
*	 CPU_CLOCK -   , BAUD_RATE -   UART,
*	 DIV -    ,   TCCR0B.
*	,   9.6 ,   8,   9600 :
*	BAUD_DIV = (9 600 000 / 8) / 9600 = 125 (0x7D).
*/

//#define T_DIV		0x01	// DIV = 1
#define T_DIV		0x02	// DIV = 8
//#define T_DIV		0x03	// DIV = 64
#define BAUD_DIV	0x7D	//  = 9600 

/*
*	         UART
*/

volatile uint16_t txbyte;
volatile uint8_t rxbyte;
volatile uint8_t txbitcount;
volatile uint8_t rxbitcount;

void uart_init();
void uart_send(uint8_t tb);
int16_t uart_recieve(uint8_t* rb);

#endif /* _UART13_H_ */



لكني سأقسم وصف رمز تنفيذ المكتبة إلى أجزاء حتى لا أحول المقالة إلى جناح كبير مع كود.

مقاطعة TIM0_COMPA
ISR(TIM0_COMPA_vect)
{
	TXPORT = (TXPORT & ~(1 << TXD)) | ((txbyte & 0x01) << TXD); //    TXD   txbyte
	txbyte = (txbyte >> 0x01) + 0x8000;	//  txbyte   1   1    (0x8000)
	if(txbitcount > 0)			//    (   ),
	{
		txbitcount--;			//     .
	}
}


هنا نرى رمز معالج مقاطعة الموقت مقارنة بسجل OCR0A. يعمل باستمرار ويحدث في كل مرة يصل فيها المؤقت إلى القيمة المسجلة في سجل OCR0A. عند حدوث ذلك ، تتم إعادة تعيين قيمة المؤقت في سجل TCNT0 تلقائيًا (وضع مؤقت CTC ، المعين في سجل TCCR0A). يتم استخدام هذه المقاطعة لإرسال البيانات عبر UART. يتم استخدام متغير txbyte كسجل إزاحة: في كل مرة يحدث فيها مقاطعة ، يتم تعيين البت الأقل أهمية من متغير txbyte على إخراج TXD للدائرة المصغرة ، وبعد ذلك يتم تحويل محتويات المتغير إلى اليمين ، ويتم كتابة واحد إلى البت العالي المحرر.

طالما لا توجد بيانات لنقلها ، يتم تخزين الرقم 0xFFFF في المتغير ، وبالتالي ، يتم الحفاظ على مستوى منطق مرتفع باستمرار عند إخراج TXD. عندما نريد نقل البيانات ، يجب أن نكتب في عداد البت عدد البتات للإرسال: 1 بداية ، 8 بت بيانات ووقف واحد ، لإجمالي 10 (0x0A) ، وكتابة بيانات txbyte لإرسالها مع بت البداية. بعد ذلك ، سيبدأ نقلها على الفور. تعمل وظيفة الفراغ uart_send (uint8_t tb) في تكوين الحزمة.

مقاطعة TIM0_COMPB
ISR(TIM0_COMPB_vect)
{
	if(RXPORT & (1 << RXD))			//      RXD
		rxbyte |= 0x80;			//   1,   1    rxbyte
	
	if(--rxbitcount == 0)			//   1         
	{
		TIMSK0 &= ~(1 << OCIE0B);	//  ,   TIM0_COMPB
		TIFR0 |= (1 << OCF0B);		//    TIM0_COMPB
		GIFR |= (1 << INTF0);		//     INT0
		GIMSK |= (1 << INT0);		//   INT0
	}
	else
	{
		rxbyte >>= 0x01;		//   rxbyte   1
	}
}


نرى هنا معالج مقاطعة المؤقت مقارنةً بسجل OCR0B. يعمل بشكل مشابه للمقاطعة TIM0_COMPA ، ولكن ، على عكس ذلك ، عند تنفيذ هذا المقاطعة ، لا تتم إعادة ضبط مؤقت TCNT0. هذا الانقطاع مسموح به فقط عندما نتلقى البيانات ، وبقية الوقت محظور. عندما يحدث ذلك ، نتحقق من الحالة المنطقية لمدخل RXD ، وإذا كان واحدًا ، فإننا نكتب واحدًا إلى الترتيب العالي لمتغير الاستقبال rxbyte ، ثم ننقص بمقدار عداد البتات المستلمة بمقدار واحد ، وإذا أصبح صفرًا ، فإننا ننهي الاستقبال. خلاف ذلك ، نقوم بتحويل متغير rxbyte إلى اليمين لإعداده لاستقبال البت التالي.

مقاطعة INT0
ISR(INT0_vect)
{
	rxbitcount = 0x09;			// 8    1  
	rxbyte = 0x00;				//   rxbyte
	if(TCNT0 < (BAUD_DIV / 2))		//        
	{
		OCR0B = TCNT0 + (BAUD_DIV / 2);	//         
	}
	else
	{
		OCR0B = TCNT0 - (BAUD_DIV / 2);	//        
	}
	GIMSK &= ~(1 << INT0);			//    INT0
	TIFR0 |= (1 << OCF0A) | (1 << OCF0B);	//    TIM0_COMPA (B)
	TIMSK0 |= (1 << OCIE0B);		//    OCR0B
}


مقاطعة INT0. يعمل على الحافة الزائدة للنبض عند مدخل INT0 ، ويستخدم لتتبع بداية استقبال المعلومات. يضبط عداد البت إلى 9 ، ويعيد ضبط محتويات متغير rxbyte. لتعيين قيمة التسجيل OCR0B ، الذي يحدد تردد المقاطعة TIM0_COMPB ، يجب أن يكون في الوقت المناسب في منتصف البت المستلم. بعد ذلك ، يتم تمكين المقاطعة TIM0_COMPB ، ويتم تعطيل المقاطعة INT0.

فيما يلي وظائف المستخدم للعمل مع UART.

دالة Uart_send
void uart_send(uint8_t tb)
{
	while(txbitcount);		//      
	txbyte = (tb + 0xFF00) << 0x01; //     txbyte        1
	txbitcount = 0x0A;		//     10
}


وظيفة نقل UART بايت. يستغرق بايت لنقل كوسيطة ؛ لا توجد قيمة إرجاع. إذا تم نقل بايت في وقت استدعاء الوظيفة ، فإنه ينتظر حتى يكتمل النقل ، وبعد ذلك يكتب بايت txbyte إلى 8 بتات أقل من المتغير للإرسال ، وتبقى 8 بتات أعلى 0xFF ، ثم تحول المتغير إلى اليسار ، وبالتالي إنشاء بت البداية في ترتيب منخفض. يضبط عداد البت على 10.

دالة Uart_recieve
int16_t uart_recieve(uint8_t* rb)
{
	if(rxbitcount < 0x09)		//       9
	{
		while(rxbitcount);	//     
		*rb = rxbyte;		//      
		rxbitcount = 0x09;	//    
		return (*rb);		// 
	}
	else
	{
		return (-1);		//   -1 ( )
	}
}


وظيفة استقبال UART بايت. يأخذ وسيطة مؤشر إلى متغير 8 بت حيث سيتم احتواء البايت المستلم. إرجاع البايت المستلم ، إذا تم تلقي البايت ، وإذا لم يكن هناك شيء لاستلامه ، يتم إرجاع (-1). إذا تم استدعاء الوظيفة في الوقت الحالي ، فإن الوظيفة تنتظر اكتمالها. إذا تم استدعاء الوظيفة مرتين ، في المرة الأولى التي ستقوم بإرجاع البايت المتلقاة ، والمرة الثانية (-1).

دالة Uart_init
void uart_init()
{
	txbyte = 0xFFFF;		//     -  
	rxbyte = 0x00;			//     -  
	txbitcount = 0x00;		//     -  (   )
	rxbitcount = 0x09;		//      - 9 (  )
	
	TXDDR |= (1 << TXD);		//       
	RXDDR &= ~(1 << RXD);		//       
	TXPORT |= (1 << TXD);		//     TXD
	RXPORT |= (1 << RXD);		//     RXD
	OCR0A = BAUD_DIV;		//    OCR0A    
	TIMSK0 |= (1 << OCIE0A);	//   TIM0_COMPA
	TCCR0A |= (1 << WGM01);		//   CTC ( TCNT0   OCR0A)
	TCCR0B |= T_DIV;		//        
	MCUCR |= (1 << ISC01);		//   INT0    
	GIMSK |= (1 << INT0);		//   INT0
	sei();				//   
}


وظيفة التهيئة UART. لا جدال ، لا قيمة إرجاع. تهيئة المتغيرات العالمية وسجلات وحدة التحكم الدقيقة. من التعليقات في الكود يجب أن يكون كل شيء واضحًا.

لذا ، حان الوقت لكتابة بعض الكلمات الرئيسية البسيطة () باستخدام مكتبتنا ومعرفة ما حدث من حيث كمية التعليمات البرمجية والتحقق من الأداء.

ج الرئيسية
#include "uart13.h"

int main(void)
{
	uint8_t b = 0;
	uart_init();
	while (1) 
	{
		if(uart_recieve(&b) >= 0)	//    ,    
			uart_send(b);		//   ,  
	}
	return (0);
}


نقوم بتجميع:
استخدام ذاكرة البرنامج: 482 بايت 47.1٪
استخدام كامل لذاكرة البيانات: 5 بايت 7.8٪ ممتلئة

ليس سيئًا ، لدينا أكثر من نصف ذاكرة وحدة التحكم الدقيقة في المخزون!
التحقق في Proteus:

المحاكاة على ما يرام!


خلاصة القول: اتضح أن التنفيذ قابل للاستخدام تمامًا ، ويتم إرسال البيانات واستلامها بشكل مستقل ، ولا يتم استخدام مكتبة delay.h على الإطلاق ، ويتم ترك أكثر من نصف ذاكرة وحدة التحكم الدقيقة في الاحتياطي.

أرفق شفرة المصدر للمكتبة ، يتم تجميعها في avr-gcc: Sources on GitHub

Source: https://habr.com/ru/post/ar386485/


All Articles