SOFT_UART_X3 с уверенным приемом и полным дуплексом, для любого микроконтроллера, используя один таймер

Небольшая предыстория.
Захотелось мне сделать uart на тех пинах, на которых его отродясь небыло. Хотел найти сразу готовое решение чтобы не изобретать сами знаете что, однако не все так просто. Порывшись, сразу нашел Программный UART для любого микроконтроллера, вроде реализация всем хороша, на передачу работает отлично. А вот на прием, где-то один байт из сорока приходит с ошибкой, по понятной причине,- просто опрашивая RX пин по таймеру, уверенного приема не добиться, рано или поздно попадем на границу фронта. Есть конечно способы сделать прием уверенным, но для этого обычно приходится задействовать много аппаратных ресурсов, иногда два таймера плюс прерывание с ножки. А удобно бы использовать один только таймер.


Немного покумекав, придумал увеличить частоту опроса ножки RX. Опрашивая чаще, можно избегать попадания на границу фронта. Вопрос только в том, на сколько нужно увеличить частоту опроса ножки, чтобы всегда попадать в середину битового интервала. Для этого посмотрим на картинку:

На ней изображены самые худшие условия для приема. Предположим это стартовый бит, на нем и попробую объяснить как работает алгоритм.
Х1. Видно, что когда частота опроса ножки RX совпадает со скоростью передачи (множитель х1) можно легко получить неопределенное состояние пина RX — точки опроса 1 и 2.
Х2. Умножая частоту опроса на 2 уже больше шансов получить верное значение. Предоложим мы считали в точке 1 низкое состояние, прибавляем пол битового интервала и оказываемся в точке 2 — в середине битового интервала, дальше можно читать следующий бит в точке 4 следующий в точке 6 и т.д. Но предположим что в точке 1 мы считали высокое состояние, тогда отсчет начнется с точки 2 и прибавив к ней пол битового интерала мы окажемся в точке 3 с неопределенным знчением и дальше прием будет не верен.
Х3. Рассмотрим, подходит ли нам множитель х3. Предположим в точке 1 мы считали низкое значение, сдвигаемся на шаг в точку 2 и далее начинаем обычный прием точки 5, 8 и т.д. Если же в точке 1 мы схватили высокое знчение, в точке 2 мы получим уже верное, сдвенувшись на один шаг в точку 3 начинаем обычный прием точки 6,9 и т.д. В любом случае мы оказываемся примерно в середине битового интервала, что гарантирует верное значение бита.

А теперь сам код:
//Full duplex SOFT UART
//11 bit protocol: 1-start bit 8-bit data 1-parity bit(even) 1-stop bit
//bps = timer_irq/3
//
//Transmit bytes
//1- copy bytes to tx_buf[]
//2- run tx_uart(number of bytes in tx_buf);
//
//Recive bytes
//1- waiting rx_byte_no > 0
//2- read rx_buf[]
//3- rx_byte_no = 0

#include "soft_uart.h"


void timer_uart(void)
{
    unsigned char tx_mask, rx_mask;

    if(flag_tx_rdy == TRUE)  //TX section
    {
        tx_counter++;
        if(tx_counter == 3)
        {
            tx_counter = 0;
        
            switch (state_tx)
            {
                case 1:  //start bit
                {
                    set_tx_pin_low();
                    state_tx = 2;
                
                    break; 
                }
            
                case 2: // data bits
                {
                    tx_mask = tx_byte & 1;
                    tx_byte >>= 1;
                    if(tx_mask == 0) 
                    {
                        set_tx_pin_low();
                    }
                    else
                    {
                        set_tx_pin_high();
                    
                        parity_bit_tx = ~parity_bit_tx;
                    }
            
                    bit_no_tx++;
                    if(bit_no_tx > BITS)
                    {
                        state_tx = 3;
                    }

                    break; 
                }
            
                case 3: //parity bit
                {
                    if(parity_bit_tx == 0xFF)
                    {
                        set_tx_pin_high();
                    }
                    else
                    {
                        set_tx_pin_low();
                    }
                    state_tx = 4;
                
                    break;   
                }
            
                case 4:  //stop bit
                {
                    set_tx_pin_high();
                
                    flag_tx_rdy = FALSE;
                
                    break;   
                }
            }
        }
    }
    
    if(flag_rx_rdy == TRUE) //RX section
    {
        rx_counter++;
        
        if(rx_counter == rx_counter_equ)
        {
            rx_counter = 0;
            
            switch (state_rx)
            {
                case 1:  //start bit
                {
                    rx_mask = get_rx_pin_status();
                    if(rx_mask == 0) 
                    {
                        state_rx = 2;
                        rx_counter_equ = 4;
                    }
                
                    break; 
                }
            
                case 2:  //data bits
                {
                    rx_counter_equ = 3;
                    
                    rx_mask = get_rx_pin_status();
                
                    if(rx_mask == 1)
                    {
                        parity_bit_rx = ~parity_bit_rx;
                    }
                
                    rx_byte >>= 1;
                    rx_mask <<= (BITS - 1);
                    rx_byte |= rx_mask;

                
                    bit_no_rx++;
                    if(bit_no_rx > BITS)
                    {
                        state_rx = 3;
                    }
                
                    break;  
                }
            
                case 3:  //parity bit
                {
                    rx_mask = get_rx_pin_status();
                    parity_bit_rx &= 0x01;
                
                    if(parity_bit_rx == rx_mask)
                    {
                        state_rx = 4;
                    }
                    else
                    {
                        rx_byte = 0;
                        bit_no_rx = 1;
                        parity_bit_rx = 0x00;
                        state_rx = 1;
                    }
                
                    break;
                }
            
                case 4:  //stop bit
                {
                    rx_mask = get_rx_pin_status();
                
                    if(rx_mask == 1)
                    {
                        rx_buf[rx_byte_no] = rx_byte;
                    
                        rx_byte_no++;
                        if(rx_byte_no == RX_BUF_SIZE - 1)
                        {
                            rx_byte_no = 0;
                        }

                    }

                    rx_byte = 0;
                    bit_no_rx = 1;
                    parity_bit_rx = 0x00;
                    state_rx = 1;
                    rx_counter_equ = 1;
                
                    break;  
                }
            }
        }
    }
}

void tx_uart( unsigned int bytes)
{
    unsigned int i;
    
    led_on();
    
    for(i = 0; i < bytes; i++)
    {
        while(flag_tx_rdy == TRUE){}

        tx_byte = tx_buf[i];
        bit_no_tx = 1;
        parity_bit_tx = 0x00;
        state_tx = 1;
        
        flag_tx_rdy = TRUE;
    }
    
    led_off();
}

void rx_uart(void)
{
    rx_byte = 0;
    bit_no_rx = 1;
    parity_bit_rx = 0x00;
    state_rx = 1;
    rx_counter_equ = 1;    

    flag_rx_rdy = TRUE;
}

void init_uart(void)
{
    flag_tx_rdy = FALSE;
    flag_rx_rdy = FALSE;
    
    bit_no_rx = 1;
    
    tx_byte_no = 0;
    rx_byte_no = 0;
    
    set_tx_pin_high();
}


Код не оптимизирован и оставлен как есть для удобочитаемости. Позволяет сделать RX или TX на любых пинах контроллера и в любых колличествах, на сколько хватит ножек и производительности. Конечно, не алгоритм не код ничего принципиально нового из себя не представляют и наверняка где-то используются, однако готовой реализации я не нашел, если видели что-то подобное подскажите.
Из преимуществ: нужен только один таймер на любое количество uart-ов, полный дуплекс, легко прикручивается.
Из недостатков: отъедает больше ресурсов.

В следующем посте опишу его применение на практике. Если есть что сказать по существу, буду только рад, ваши вопросы, предложения…

UPD
Велосипед я таки изобрел, код из статьи которая упоминается выше тоже х3 и соответственно рабочий. Видимо когда скачал начал что-то мудрить, доведя до х1, потом снова до х3)) Ничего, полезно)
Так что смотрите на статью как объяснение как все это работает + мой вариант реализации без оптимизации и битом четности.
  • +4
  • 24 июня 2012, 19:09
  • batson
  • 1
Файлы в топике: soft_uart_x3.zip

Комментарии (33)

RSS свернуть / развернуть
На любом микроконтроллере это работать не будет. Да и по мимо прерываний таймера есть еще другие прерывания. Также код довольно тяжеловесный. Весь этот код нужно засунуть в прерывание?
0
  • avatar
  • a9d
  • 24 июня 2012, 19:40
На совсем слабых да, может не хватить или на самой маленькой скорости, скажем 600bps, будет работать. Весь код в прерывние, на вид код большой, но если присмотреться то в каждом прирывании выполняется лишь небольшой его кусок.
0
Это от архитектуры зависит. Компилятор этот код превратит в кашу и определить частоту опроса на арме будет уже практически не возможно. Да и прерывание таймера может быть вытеснено.
0
Да я не спорю, софтовая реализация все-таки, моя скорее дополняет совсем не рабочий пример из статьи в самом начале. Хотя практика показывает что х3 работает очень стабильно, но об этом в следующей статье расскажу.
0
не будет он работать стабильно. Добавь парочку прерываний. Увеличь размер проекта и все перестанет работать и будет безбожно глючить. Добавь ОС и весь юарт точняк слетит.
0
Опять же не спорю, если какой-то жесткач, то любой универсальный способ вряд ли подойдет. В моем случае оно работает не в самых загруженных условиях.
0
переходники i2c<->UART, spi<->uart работают везде и на любом микроконтроллере. Также никто не отменял синхронный юарт, он тоже работает на всех микроконтроллерах.
-1
синхронный юарт
нонсенс :)
0
??
0
да дятел просто. букву S в USART проморгал. ну о большем даже говорить нечего.
0
аааа. Граммар Наци негодует.
0
это предложение уже вешать на себя ярлык?
(или я просто туплю? ;) )
0
В условиях реального времени использовать надо хардварю, и взять для этого более мощный проц (имеющий требуемое количество хардвари) или преобразователь интерфейса — совершенно не проблема.
Софтовая реализация вполне пойдет в легковесных проекта.
Чего вы тут распитушились — совершенно не понятно.
0
Делать нужно все как попало. Некоторые протоколы обречены быть хардваными.
0
Дык это, насколько я помню даташит — то у Атмег 16х используется, для получения профита… Может все таки его на Асме переписать, облегчит?
0
Асм уже не актуально, уже давно от него отказался, переносимость кода мне намного важнее.
+2
Чтобы уменьшить нагрузку на проц. можно отлавливать только 1й бит на 3х-кратной скорости, а потом занижать частоту опроса и уверенно принимать всю посылку. Наглядней можно посмотреть у Белова А.В. «Самоучитель по микропроцессорной технике» (см. рис.49 на стр.178)
0
Я думал об этом, выигрыш в нагрузке безусловно будет, только для уверенного приема важен именно момент, когда считывание идет на 3х-кратной скорости. Значит качество приема это не повысит и на идее забил, решил не заморачиваться.
0
Если передача бит идет строго с определенным и известным периодом и скважностью (а в USART это именно так и происходит), то для уверенного приема байта достаточно определить середину (плюс-минус) первого, стартового бита, а после приема последнего, стопового — опять ждать прихода стартового с утроенной частотой опроса линии приема. Уж не думаю, что при кварцовой точности периодов передатчика и приемника, да на скоростях UART-а, за какие-то 8-10 бит может образоваться сдвиг фаз больше чем половина длительности одного бита, что привело бы к ошибке в чтении
0
Вы не учли, что передача тут осуществляется тем же таймером. соответственно если дергать скорость таймера — надо дорабатывать алгоритм передачи в условиях «непостоянной частоты задающего генератора». Сделать можно, но разве оно того стоит?
if(rx_counter == rx_counter_equ)
Вполне себе достаточно.
0
Сдвиг по фазе здесь возможен из-за какого-нибудь «долгого» прерывания, по этому критический момент определения первого бита и середины последующих остается. С другой стороны, замедление частоты потом все же повышает стабильность обнаружения следующих бит, за счет отсутствия лишних прераваний. Так что замедление частоты не лишено смысла, все в ваших руках, можете реализовать, может я и зря не стал этого делать. На счет передачи — angel5a правильно говорит, тоже добавить можно, тут разве будет небольшая заморочка с джиттером, решаемо.
0
С другой стороны, замедление частоты потом все же повышает стабильность обнаружения следующих бит, за счет отсутствия лишних прераваний
А вы лукавый, однако. Двумя постами выше вы говорили совершенно противоположное. К чему я это? Да к вашим же словам
все в ваших руках, можете реализовать, может я и зря не стал этого делать
Мне это не нужно и вряд ли когда понадобится, не имею потребностей в извращениях. Это я для вас подкинул мыслю, чтобы не успокаивались на достигнутом. Если мне понадобится UART, я выберу железный вариант, и камень соответствующий, и не стану тратить время и нервы над софтовыми свистоплясками… Зачем?
+1
Эт я высказался категорично, имел ввиду существенно не повысит, не стоит придираться к словам. Таки в любом случае ваш коммент был по существу и полезен.
Насчет «в ваших руках» — не имею ввиду именно вас, скорее сообщество в целом, никого насильно не принуждаю) может кто-нибудь, кто считает это дело нужным, добавит в это дело свои пять копеек, я буду только рад.
0
тут смотрели?
0
Тык оттуда и брал) Только сейчас посмотрел что он тоже х3) Видимо я начал его ковырять раньше чем понял это))
Ну да ладно, я же говорю что алгоритм не нов и должен был где-то быть. А статья тогда пускай просто будет объяснением как все это дело работает + мой вариант реализации, лишним не будет.
0
Просто Огромное Спасибо за проделанные труды.
Хотел плюсануть статью, а рейтинга и силе недостаточно ((
+1
Позанудствую, tx_uart:
while(flag_tx_rdy == TRUE){}
Это, простите, что за хрень? Для передачи ждем пока передатчик в готовом состояние, а как стал не готов, передаем?!
Кому удосужится сопровождать ваш код, не раз вспомнит вас матерным словом.
+1
Это не занудство, мне тоже этот прием не очень по душе, но варианта лучше я не нашел.
0
flag_tx_busy, flag_tx_nrdy или flag_tx_empty
Имя переменной должно отвечать задаче, которую она выполняет. Иначе обзывайте переменные просто как i, ii, iii, iiii (и такое именование бывает встречается в коде для строк), чем давать им противоречащее имя. Понятно что вариант отстойный, но он хоть не вводит в заблуждение.
rdy — ready — готов
busy — занят
nrdy — not ready — не готов
empty — пустой (буффер/регистр)
+1
Тут небольшая тонкость, — флаги flag_tx_rdy и flag_rx_rdy как бы включают (или отключают если это понадобится) передатчик и приемник соответственно, отсюда и такое название — готов передавать, готов принимать. Задумывались они для этого, это уже потом они используются произвольным образом.
0
Велосипед :)
См. теорему Котельникова (Найквиста — Шеннона).
0
// SOFT UART ONLY RX — STM32F103C8T6 — (1.05.2015, zz555)
// 10 bit protocol: 1-start bit, 8-bit data, 1-stop bit
// bps = timer_irq/3
//
// Recive bytes
// 1 — waiting flag_rx_rdy = 1
// 2 — read rx_buf[]
// 3 — flag_rx_rdy = 0, rx_byte_no = 0
//
// PA1 (IN) RX
// PC13 (OUT) LED

#include «stm32f10x.h»

#define PC13_1 GPIOC->BSRR |= GPIO_ODR_ODR13 // PC13 OUT ON/OFF
#define PC13_0 GPIOC->BRR |= GPIO_ODR_ODR13
#define PC13_N GPIOC->ODR^=GPIO_ODR_ODR13 // PC13 OUT Инвертируем
#define RX_BUF_SIZE 256

uint8_t rx_buf[RX_BUF_SIZE];
uint8_t rx_byte;
uint8_t rx_byte_no;
uint8_t state_rx;
uint8_t rx_counter;
uint8_t rx_counter_equ;
uint8_t rx_mask;
uint8_t bit_no_rx;
uint8_t flag_rx_rdy;
uint8_t err;
uint8_t i;
uint8_t k;
uint32_t TimingDelay;

void TIM2_IRQHandler(void) {
// RX
if (rx_byte_no>0) {
k++;
if (k>30) {k=0; flag_rx_rdy=1;}
}
if (flag_rx_rdy==0) {
if (state_rx>1) {k=0; rx_counter++;} else rx_counter=1;
if (rx_counter==rx_counter_equ) {
rx_counter=0;
switch (state_rx) {
case 1: // start bit
{
if ((GPIOA->IDR & GPIO_IDR_IDR1)==GPIO_IDR_IDR1) {} else { // PA1 — RX
rx_byte=0; bit_no_rx=1; state_rx=2; rx_counter_equ=4;
}
break;
}

case 2: // data bits
{
if ((GPIOA->IDR & GPIO_IDR_IDR1)==GPIO_IDR_IDR1) rx_mask=1; else rx_mask=0; // PA1 — RX
rx_byte >>= 1;
rx_mask <<= 7;
rx_byte |= rx_mask;
bit_no_rx++;
if (bit_no_rx==9) state_rx=3;
rx_counter_equ=3;
break;
}

case 3: // stop bit
{
if ((GPIOA->IDR & GPIO_IDR_IDR1)==GPIO_IDR_IDR1) { // PA1 — RX
rx_buf[rx_byte_no]=rx_byte;
rx_byte_no++;
if (rx_byte_no==RX_BUF_SIZE-1) rx_byte_no=0;
} else err=1;
state_rx=1;
rx_counter_equ=1;
break;
}
}
}
}
TIM2->SR &= ~TIM_SR_UIF;
}

// Функция временной задержки в милисекундах
void Delay_ms(uint32_t nTime) {
TimingDelay = nTime*2400;
while (TimingDelay != 0) TimingDelay--;
}

void flash (void) {
err=0;
for(i=0; i<10; i++) {
PC13_0;
Delay_ms(10);
PC13_1;
Delay_ms(10);
}
}

//=======================================================================================================================
void Init_STM() {
RCC->APB2ENR |= (RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN);
GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0 | GPIO_CRL_MODE1 | GPIO_CRL_CNF1 | GPIO_CRL_MODE2 | GPIO_CRL_CNF2);
GPIOA->CRL |= (GPIO_CRL_MODE0_0 | GPIO_CRL_CNF1_1 | GPIO_CRL_MODE2_0); // PA0 — TX — OUT, PA1 — RX — IN, PA2 — SISCLK — OUT
GPIOA->BSRR = GPIO_BSRR_BS1;
GPIOC->CRH |= GPIO_CRH_MODE13; // PC13 OUT

// инициализации таймера TIM2
RCC->CFGR |= RCC_CFGR_HPRE_DIV2; // sys_clk/2 (36/2=18 MHz)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // разрешаем тактирование таймера 2
TIM2->DIER |= TIM_DIER_UIE; // Update interrupt enable
TIM2->PSC = 625-1; // Предделитель частоты перед таймером (при 0 максимальная частота) (18000/625=28.8 KHz)
TIM2->ARR = 1; // До скольки считает таймер (28.8/3=9600 Hz)
TIM2->CR1 |= (TIM_CR1_CEN | TIM_CR1_ARPE); // Запускаем таймер
NVIC_EnableIRQ (TIM2_IRQn); // Разрешаем прерывания TIM2
}

//=======================================================================================================================
int main (void) {
Init_STM();

PC13_0;
Delay_ms(10); // Пауза 0,01 с
PC13_1;

// Init_UART
state_rx=1;
rx_counter_equ=1;
rx_byte_no=0;
flag_rx_rdy=0;

while (1) {
if (flag_rx_rdy==1) {
if (err==1) flash();
for(i=0; i<rx_byte_no; i++) {
if (rx_buf[i]==65) PC13_0;
}
Delay_ms(10);
PC13_1;
rx_byte_no=0;
flag_rx_rdy=0;
}
}
}
0
Тест «BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAB» — LED ON
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.