Перейдем к модификации описанного программного UART`a под конкретный камушек. Поскольку пишу я в основном про 8-ми разрядные AVR, то эти контроллеры и будем рассматривать. 
   Во всех микроконтроллерах семейства mega есть аппаратный модуль UART, а вот в микроконтроллерах семейства tiny нет. По идее программный UART может потребоваться именно для младшего семейства, но не исключаю возможность использования этого кода и в mega`ах. Пути эмбеддера неисповедимы.       
   Проекты написаны в двух вариантах – для ATTiny45 и для ATmega16. Впрочем, отличия там очень незначительные и касаются только инициализации и прерывания задействованного таймера. 

Программный UART для Tiny45. Заголовочный файл

   Первое, что я сделал - это создал заголовочный файл softUart.h. В него я прописал включение заголовочного файла ioavr.h, макроопределения и прототипы пользовательских функций. Прототипы, следуя своему стилю, обозвал с использованием приставки. В данном случае - “SUART_”. Это позволяет легко определять, к каким файлам относятся те или иные функции. 

#ifndef SOFT_UART_H
#define SOFT_UART_H
#include <ioavr.h>

//___________________________Настройки_______________________
#ifndef F_CPU
   #define F_CPU          8000000L    //тактовая частота мк
#endif
#define PRESCALER      64L            //предделитель таймера 1L,8L,64L,256L,1024L
#define BAUD_RATE    4800L         //скорость обмена 

#define IN_BUF_SIZE    32             //вместимость приемного буфера

//пин приемника
#define RX_PINX      PINB
#define RX_PORTX   PORTB
#define RX_DDRX     DDRB
#define RX_PIN        1  

//пин передатчика
#define TX_PORTX   PORTB
#define TX_DDRX     DDRB
#define TX_PIN         0

//_________________Пользовательские функции____________________
void SUART_Init(void);
void SUART_PutChar(char ch);
char SUART_GetChar(void);
void SUART_FlushInBuf(void);
char SUART_Kbhit(void);
void SUART_TurnRxOn(void);
void SUART_TurnRxOff(void);
//____________________________________________________________
#endif //SOFT_UART_H

   Макроопределения F_CPU, PRESCALER и BAUD_RATE используются для расчета таймерной константы, обеспечивающей заданную скорость обмена по UART`у. Константа PRESCALER может принимать только значения, записанные в комментарии - 1L,8L,64L,256L,1024L. При задании других значений, компилятор будет выдавать ошибку. 

  Также в заголовочный файл я добавил определения, задающие приемный и передающий вывод микроконтроллера. 

Программный UART для Tiny45. Сишный файл

   В сишный файл, как и требовалось, я добавил недостающие кусочки кода. 
Макросы, используемые для обращения к приемному и передающему выводу. 

#define get_rx_pin_status()    RX_PINX & (1<<RX_PIN)
#define set_tx_pin_high()      TX_PORTX |= (1<<TX_PIN)
#define set_tx_pin_low()       TX_PORTX &= ~(1<<TX_PIN)

Функцию idle(), которую сделал пустой, поскольку она мне не требовалась. 

void idle()
{
}

   Функции void timer_set( int BAUD_RATE ) и void set_timer_interrupt( timer_isr ) удалил, а функцию инициализации программного UART`a, из которой они вызывались, подправил следующим образом. 

//формула для расчета таймерной константы
#define TIMER_CONST (0xff - (F_CPU/(BAUD_RATE*3*PRESCALER))) 

#if PRESCALER==1L 
  #define CSXX (0<<CS02)|(0<<CS01)|(1<<CS00)
#elif PRESCALER==8L 
  #define CSXX (0<<CS02)|(1<<CS01)|(0<<CS00)
#elif PRESCALER==64L 
  #define CSXX (0<<CS02)|(1<<CS01)|(1<<CS00)
#elif PRESCALER==256L 
  #define CSXX (1<<CS02)|(0<<CS01)|(0<<CS00)
#elif PRESCALER==1024L 
  #define CSXX (1<<CS02)|(1<<CS01)|(1<<CS00)
#else 
  #error "prescaller not correct"
#endif

void SUART_Init(void)
{
        //инициализация флагов UART`a
flag_tx_ready = FALSE;
flag_rx_ready = FALSE;
flag_rx_waiting_for_stop_bit = FALSE;
flag_rx_off = FALSE;
rx_num_of_bits = 10;
tx_num_of_bits = 10;

        //инициализация выводов мк
        RX_DDRX &= ~(1<<RX_PIN);
        RX_PORTX |= (1<<RX_PIN);
        TX_DDRX |= (1<<TX_PIN);
        TX_PORTX |= (1<<TX_PIN);  
         
        //инициализация таймера 
        TCCR0B = CSXX;  
        TCNT0 = TIMER_CONST;
        TIMSK |= (1<<TOIE0);
}

   Как видите, я задействовал 8-ми разрядный таймер Т0 в режиме NORMAL с прерыванием по событию «переполнение». Предделитель таймера (CSXX) определяется константой PRESCALER, а таймерная константа (TIMER_CONST)  вычисляется с помощью макроса. 

   Основной код программного UART`a поместил в обработчик прерывания таймера Т0, добавив в его начало код перезаписи значения счетного регистра TCNT0. 

#pragma vector = TIM0_OVF_vect
__interrupt void TimerOvf(void)
{
    …..
   TCNT0 = TIMER_CONST;
    ….
    //основной код
    ….
}

   И последняя моя доработка кода программного UART`a – добавление квалификатора volatile к  переменным, который используются и в прерывании и в обычных функциях. 

static volatile unsigned char inbuf[IN_BUF_SIZE];
static volatile unsigned char qin = 0;
static volatile unsigned char qout = 0;

static char             flag_rx_waiting_for_stop_bit;
static char             flag_rx_off;
static char             rx_mask;
static volatile char        flag_rx_ready;
static volatile char        flag_tx_ready;
static char            timer_rx_ctr;
static volatile char        timer_tx_ctr;
static char            bits_left_in_rx;
static volatile char        bits_left_in_tx;
static char            rx_num_of_bits;
static char            tx_num_of_bits;
static char            internal_rx_buffer;
static volatile char        internal_tx_buffer;
static char            user_tx_buffer;

   Далее я сделал проект в IAR`е, подключил к нему полученную «либу» программного UART`а и написал пару строк кода для проверки результатов. 

   Думаю, теперь вы без проблем модифицируете этот софтовый UART под любой другой камень.

Файлы