Перейдем к модификации описанного
программного 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 под любой другой камень.
Файлы