Print this page

Программный UART для любого микроконтроллера. Модификация кода для AVR. Ч2

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

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

   Первое, что я сделал - это создал заголовочный файл softUart.h. В него я прописал включение заголовочного файла compilers_3.h, обеспечиващего совместимость с тремя AVR компиляторами (IAR, CodeVision, GNU GCC), макроопределения и прототипы пользовательских функций. Прототипы, следуя своему стилю, обозвал с использованием приставки. В данном случае - “SUART_”. Это позволяет легко определять, к каким файлам относятся те или иные функции. 

#ifndef SOFT_UART_H
#define SOFT_UART_H

#include "compilers_3.h"

/*___________________________Настройки_____________________________________*/

/*тактовая частота мк*/
#ifndef F_CPU
#define F_CPU 8000000L
#endif

/*прескалер таймера 1L, 8L, 64L, 256L, 1024L*/
#define SUART_PRESCALER 1L

/*скорость обмена*/
#define SUART_BAUD_RATE 19200L

/*вместимость приемного буфера*/
#define SUART_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

/*функция или макрос, выполняемый во время
ожидания поступления данных в приемный буфер.
она запускается в функции SUART_GetChar()*/
#define SUART_Idle()

/*________________________Пользовательские функции___________________________*/

void SUART_Init(void);
char SUART_GetChar(void);
void SUART_PutChar(char ch);
void SUART_PutStr(char *str);
void SUART_PutStrFl(char __flash *str);
void SUART_FlushInBuf(void);
char SUART_Kbhit(void);
void SUART_TurnRxOn(void);
void SUART_TurnRxOff(void);

/*___________________________________________________________________________*/

#endif //SOFT_UART_H
 

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

Программный 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)
 

А также макросы для расчета и проверки таймерной константы. Эти макросы позволяют быстро подобрать оптимальное значение частоты микроконтроллера, предделителя таймера и скорости передачи данных. 
 

#define TIME_VALUE (F_CPU/(SUART_BAUD_RATE*NUM_OF_CYCLES*SUART_PRESCALER))
#define TIMER_TCNT_VALUE (0xff - (TIME_VALUE - 1))
#define ERROR_VALUE ((F_CPU*100)/(TIME_VALUE*SUART_BAUD_RATE*NUM_OF_CYCLES*SUART_PRESCALER))

#if ((TIME_VALUE > 256)||(TIME_VALUE == 0))
#error "TIMER_TCNT_VALUE is not correct. Change PRESCALER, BAUD_RATE or F_CPU"
#endif

#if ((ERROR_VALUE > 100)||(ERROR_VALUE < 100))
#warning "Baud rate error > 1%"
#endif

#if SUART_PRESCALER==1L
   #define CSXX (0<<CS02)|(0<<CS01)|(1<<CS00)
#elif SUART_PRESCALER==8L
   #define CSXX (0<<CS02)|(1<<CS01)|(0<<CS00)
#elif SUART_PRESCALER==64L
   #define CSXX (0<<CS02)|(1<<CS01)|(1<<CS00)
#elif SUART_PRESCALER==256L
   #define CSXX (1<<CS02)|(0<<CS01)|(0<<CS00)
#elif SUART_PRESCALER==1024L
   #define CSXX (1<<CS02)|(1<<CS01)|(1<<CS00)
#else
   #error "prescaller not correct"
#endif
 
   Функции void timer_set( int BAUD_RATE ) и void set_timer_interrupt( timer_isr ) удалил, а функцию инициализации программного UART`a, из которой они вызывались, подправил следующим образом. 
 
 

void SUART_Init(void)
{
   /*инициализация флагов*/
   flag_tx_busy = FALSE;
   flag_rx_ready = FALSE;
   flag_rx_waiting_for_stop_bit = FALSE;
   flag_rx_off = FALSE;

   /*настройка выводв*/
   RX_DDRX &= ~(1<<RX_PIN);
   RX_PORTX |= (1<<RX_PIN);
   TX_DDRX |= (1<<TX_PIN);
   TX_PORTX |= (1<<TX_PIN);

   /*настройка таймера Т0*/
   TCCR0B = CSXX;
   TCNT0 = TIMER_TCNT_VALUE;
   TIMSK |= (1<<TOIE0);
}
 
   Как видите, я задействовал 8-ми разрядный таймер Т0 в режиме NORMAL с прерыванием по событию «переполнение». Предделитель таймера (CSXX) определяется константой SUART_PRESCALER, а таймерная константа TIMER_TCNT_VALUE  вычисляется с помощью макроса. 
 
   Основной код программного UART`a поместил в обработчик прерывания таймера Т0, добавив в его начало код перезаписи значения счетного регистра TCNT0.  Лучшим вариантом было бы использование режима СТС, но этот режим у 8-ми разрдядных таймеров иногда отсутствует.
 
ISR(TIM0_OVF_VECTOR)
{
   uint8_t start_bit, flag_in;
   uint8_t rx_data;
   static uint16_t internal_rx_buffer;

TCNT0 += TIMER_TCNT_VALUE;
    …..
    //основной код
    ….
}

   Не удивляйтесь, что обработчик пррывания выглядит не так как принято в IAR`е. Просто объявление прерывания "запаковано" в макросе ISR. Это было сделано с целью совместимости кода с несколькими компиляторами. (Написание нескольких проектов отнимает много времени, не говоря уже об их дальнейшем исправлении. )

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

static volatile uint8_t inbuf[SUART_BUF_SIZE];
static volatile uint8_t qin = 0;
static volatile uint8_t qout = 0;

static volatile uint8_t flag_rx_ready;
static uint8_t flag_rx_waiting_for_stop_bit;
static uint8_t flag_rx_off;
static uint8_t rx_mask;
static uint8_t timer_rx_ctr;
static uint8_t bits_left_in_rx;

static volatile uint8_t flag_tx_busy;
static volatile uint8_t timer_tx_ctr;
static volatile uint8_t bits_left_in_tx;
static volatile uint16_t internal_tx_buffer;
 
  И в заключении, я добавил функции вывода строк из ОЗУ и из flash памяти. 

void SUART_PutStr(char *str)
{
   char ch;
   while (*str){
      ch = *str++;
      SUART_PutChar(ch);
   }
}


void SUART_PutStrFl(char __flash *str)
{
   char ch;
   ch = read_byte_flash(*str);
   while (ch) {
      SUART_PutChar(ch);
      str++;
      ch = read_byte_flash(*str);
   }
}


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

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

Файлы

SUART-Tiny45-IAR.rar
SUART-Tiny45-WINAVR.rar
SUART-Tiny45-CV.rar
SUART-Tiny45-Proteus.rar
SUART-Tiny45-AS6.rar 

SUART-Mega16-IAR.rar
SUART- Mega16-WINAVR.rar
SUART- Mega16-CV.rar
SUART- Mega16-Proteus.rar
SUART-Mega16-AS6.rar

Related items