用于ATtiny13的全双工软件UART

所有Geektimes用户您好!一次,由于我的服务,我需要在流行的ATtiny13微控制器上实现软件UART。谷歌搜索,我发现了很多与此主题相关的文章,其中许多都发布在这里:


还有其他资源:


通常,最新的实现可以满足我的需求(全双工)。但是,首先,该代码是用CodeVision AVR编写的,出于纯粹的宗教原因,我不使用它;其次,对汇编插入的评论不佳,这更加阻碍了理解代码的愿望。我设定了使用纯C语言编写用户友好的全双工UART库的目标。同时写一篇有关此的文章,因为由于内存和控制器资源(只有一个8位定时器)的数量非常有限,所以任务非常有趣。对于编程微控制器的初学者来说,这将是很好的培训材料,因为 我本人在编写库的过程中,从头开始掌握AVR的体系结构。

谁在乎-欢迎来到Kat,它将有很多文本,很多源代码以及代码中的注释。

所以,让我们开始吧。我将简单地将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个数据位和1个停止,总共10个(0x0A),然后将txbyte数据与起始位一起写入进行传输。之后,它们将立即开始传输。函数void 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输入的逻辑状态,如果为1,则以接收变量rxbyte的高位写入1,然后将接收到的位的计数器减1,如果变为零,则结束接收。否则,我们将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初始化功能。没有参数,没有返回值。初始化全局变量和微控制器寄存器。根据代码中的注释,所有内容都应该清楚。

因此,是时候使用我们的库编写一些简单的main()了,看看在代码量方面发生了什么并检查性能。

main.c
#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中编译的:GitHub上的源代码

Source: https://habr.com/ru/post/zh-CN386485/


All Articles