Программный UART для любого микроконтроллера. Модификация кода для AVR. Ч2
09/02/2012 - 20:04
Pavel Bobkov
Перейдем к модификации описанного программного 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
#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;
{
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;
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);
}
}
{
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 под любой другой камень.
Файлы
Tagged under
Comments
- исправить регистр TIMSK на TIMSK0 в функции инициализации
- правильно установить fuse бит CLKDIV8, чтобы тактовая частота контроллера не делилась на 8.
- уменьшить размер буфера IN_BUF_SIZE и размеры стеков CSTACK, RSTACK, потому что ОЗУ у tiny13 меньше, чем у tiny45
ну и вроде все...
Архив проекта выложил на форуме.
На 16мгц проблем нет, на скорости 9600 все работает.
мне нужно связать два Attiny85 по SoftwareUART, и поподключать каждый к компу через usb. Но как я понял, реализация vusb для attiny85 возможна лишь на скорости 16,5MHz
internal_tx_buffer = (user_tx_buffer<<1) | 0x200;
. К чему тут 0х200? Или вот в GetChar: Code:ch = inbuf[qout] & 0xFF;
?Code:
internal_tx_buffer = (user_tx_buffer<<1) | 0x200;
Байт данных сдвигается на один разряд влево и в 10-ом разряде устанавливается единица (0x200 - это 1000000000 в двоичном коде). Это единица и есть стоповый бит.
Смысл второй строчки объяснить не могу. Понятно, что это маска, но зачем она здесь не знаю.
void PutStr(char* s)
{
SUART_TurnRxOff();
for (is=0; is
void PutStr(char* s)
{
SUART_TurnRxOff();
for (is=0; is
void PutStr(char* s)
Проверка готовности выполняется внутри функций библиотеки, погуглив для себя сделал вывод, если товарищ Pashgan исправит упомянутые выше ошибки данный драйвер считаю наиболее удачным.{
SUART_TurnRxOff();
for (is=0; is<=(strlen(s)-1); is++) SUART_PutChar(s[is]);
SUART_TurnRxOn();
}
F_CPU 8000000L
SUART_PRESCALER 8L
SUART_BAUD_RATE 9600L
для мега16
//
// Target(s)...: любой AVR контроллер
//
// Compiler....: IAR 5.11A
//
// Description.: Драйвер программного UART`a на микроконтроллер ах AVR.
// Код написан с использованием appnote фирмы IAR.
//
// Data........: 05.03.13
Да проект , под CV , но содержимое IAR
обработчик прерывания в СV
interrupt [TIM0_OVF] void tim0_ovf(void)
обработчик прерывания в твоем архиве
ISR(TIM0_OVF_VECTOR)
CV, даже не пытается компилировать проект, many error.
Ну самое главное не это, может ли софтовый UART принимать строку а не побайтно? Просто если это не возможно нет смысла делать дальше , итак целый день убил на проект, все заработало , а с приемом строки вышел облом
Может, если добавить соответствующую функцию.
1Quote: 2Quote: 3Quote:
Мне кажет пакетная передача и прием данных это должны были быть основные функции в реализации софтового UART . Да жаль что зря потратил время, нужно было сразу МК с двумя железными UART закладывать в проект. Не доглядел я что тут проблемы с пакетным приемом, сам виноват.
Например такая может быть функция
Code:
void SUART_GetStr(char *buf, uint8_t size)
{
uint8_t i;
/*копируем size - 1 символов*/
for(i = 0; i < size - 1; i++){
buf[ i ] = SUART_GetChar();
}
/*добавляем конец строки*/
buf[ i ] = 0;
}
Я только что проверил код в железяке. У меня ловит строки на скорости 38400.
Quoting Pashgan:
А где это строка находится? В хидере? короче у меня в старых исходниках её вроде нигде нет и этот дефайн нигде не используется.
rx_data = (uint8_t)intern al_rx_buffer;
if ( rx_data != 0xC2 ){
inbuf[qin] = rx_data;
if ( ++qin >=SUART_BUF_SIZ E ){
qin = 0;
}
}
В функции приема байтов стоит условие на проверку пришедшего байта и если он 0xC2, то он просто пропускается, поэтому у меня и приходили битые пакеты , потому что в них проскакивал байт с таким значением 0xC2, я теперь не пойму а как другие пользовались этими исходниками? Ведь это явный баг влияющий на работу софтового UART. Убрал это строку, тестирую целый день , полет нормальный, софтовый UART работает отлично параллельно с железным . Жаль потраченных двух дней, из за такой ерунды.
Quote: Нормальная ситуация. Сам много раз так попадал и вывел из этого свое правило: "если долго не можешь отловить баг, значит он в какой-то мелочи". Такие штуки, конечно, неприятны, но они повышают инженерное чутье.
Или может надо их как-то по-другому изменять. Помоги, пожалуйста, сижу ломаю, ломаю код, ничего не выходит.
Аналогичная проблема, отправка работает отлично, а вот строки принимает неверно, начиная со второго символа начинают приниматься крякозябры. Если отправлять по байту раз в секунду, то все принимается без ошибок. Помогите плиз доделать эту прошивку, чтобы можно было примать строки длинной до 32 байт. Спасибо!
На tiny45 cvavr не работал прием, зависала.
поменял размер приемного буффера на 8 ,
вроде отпустило
RX_NUM_OF_BITS поменял на 8
но прием все равно не работает
Может что посоветуете?
NUM_OF_CYCLES ?
Для STM32F030F4P6 SOFT UART незаменимая штука! Там всего 1 UART.
Тест «BBBBBBBBBBBBBB BBBBBBBBBBBBBBB BBBBBBBBBBBBBBB BBBBBBBBBBBBBBB BBBBBBBBAB» — LED ON на букву "А" в посылке.
http://electronix.ru/forum/index.php?showtopic=127730
/* Receiver Section */
if (flag_rx_off == FALSE){
if (flag_rx_waiting_for_stop_bit){
if ( --timer_rx_ctr == 0 ){
flag_rx_waiting_for_stop_bit = FALSE;
flag_rx_ready = FALSE;
rx_data = (uint8_t)internal_rx_buffer;
if ( rx_data != 0xC2 ){
inbuf[qin] = rx_data;
if ( ++qin >= SUART_BUF_SIZE ){
qin = 0;
}
}
}
}
else{ // rx_test_busy
не понимаю назначение условия if ( rx_data != 0xC2 ){
если в битовом буфере не 0xC2 то добавляем байт в буфер приема
но при этом при посылке байта 0xC2 он тоже не принимается
как это можно обойти?
Вот это баг в исходниках софтового UART!!! Я два дня просидел над своим кодом не понимая почему иногда приходит битый пакет данных, уже перелопатил весь свой код , поставил кучу контрольных условий, изменил топологию платы, и когда у меня уже придраться было не к чему, решил глянуть исходники софтового UART , а именно: код приема данных, и тут я был в шоке , а что это за хрень?
rx_data = (uint8_t)intern al_rx_buffer;
if ( rx_data != 0xC2 ){
inbuf[qin] = rx_data;
if ( ++qin >=SUART_BUF_SIZ E ){
qin = 0;
}
}
В функции приема байтов стоит условие на проверку пришедшего байта и если он 0xC2, то он просто пропускается, поэтому у меня и приходили битые пакеты , потому что в них проскакивал байт с таким значением 0xC2, я теперь не пойму а как другие пользовались этими исходниками? Ведь это явный баг влияющий на работу софтового UART. Убрал это строку, тестирую целый день , полет нормальный, софтовый UART работает отлично параллельно с железным . Жаль потраченных двух дней, из за такой ерунды.
vadim
на какой скрости используешь, проц, тактовая? у меня на меге8515, 7.3728МГц быстрее 14400кб/с начинает глючить не стал разбираться оставил как есть - просто надо было завести старый проект.
uint8_t TX_PIN;
А сделать так же с TX_PORTX, TX_DDRX не получается...
Помогите, как правильно это реализовать
https://cloud.mail.ru/public/ND4D/s1S89iXp1
RSS feed for comments to this post