UART ISR Tx Rx Архитектура

Я усложняю вещи?

Я разрабатываю свой код для связи микроконтроллера 8051 с периферийным устройством через UART. Периферийное устройство отвечает на команды хоста и может отвечать только на одну команду за раз. Это простой протокол отправки и получения. (tx1, rx1, tx2, rx2, tx3, rx3) Каждое сообщение TX завершается CR, каждый ответ завершается знаком >. Я не могу отправить новое сообщение, пока не получу ответ на последнее. Ответы также могут эхо-печатать исходное сообщение TX в начале, если я включу эту опцию (но это вызывает больше трафика)

Пример сообщения:

  • ТХ: привет
  • RX: Мир!>

Или с опцией эха...

  • ТХ: привет
  • RX: Привет\rМир!>

Вариант A Функция, такая как getHello, будет состоять как из отправки, так и из получения. Параллельная процедура ISR будет собирать входящие байты и выбрасывать флаг при получении символа '>'.

char* getHello(char * buf){
    sendMsg("Hello\r");
    delay(10ms); //wait a little bit

    //wait for receive to come in or timeout to occur
    while(!receiveFlag || !timeoutFlag);  //thrown by ISR
    receiveMsg(buf);
    //parse the message and do some other stuff
    return buf;
}

Плюсы:

  • Все содержится в одной функции.
  • Легче отлаживать

Минусы:

  • Эта функция блокируется и может зависнуть, если периферийное устройство не отвечает, поэтому необходимо реализовать тайм-аут.
  • Сообщения не могут быть получены не по порядку, они должны быть последовательными (например, tx1, rx1, tx2, rx2, tx3, rx3)

Вариант Б Используется параллельный подход. Будут созданы две отдельные функции. один для отправки сообщения, а другой для вершины при получении ответа от ISR.

void sendHello(){
    sendMsg("Hello\r");
    //do some other stuff if needed
}

char* receiveMsg(char * buf){
    //figure out from echo print what the tx message was
    //use a switch statement to decide which response parser to call
    switch(txMessage){ //pseudo code
    case "Hello":
        receiveMsg(buf);
        //parse the message and do some other stuff
        break;
    }
    return buf;
}

Плюсы:

  • Может обрабатывать параллельные сообщения, возвращающиеся не по порядку, потому что он полагается на эхо-печать сообщения tx, чтобы выяснить, как его анализировать. (т.е. tx1, tx2, tx3, rx1, rx2, rx3)

Минусы:

  • довольно сложно отлаживать
  • порождает несколько потоков
  • много дополнительного кода
  • не стоит, так как сообщения обязательно вернутся по порядку

Прямо сейчас я использую вариант Б, но по мере того, как я продолжаю работу над проектом, я начинаю чувствовать, что он становится слишком сложным. Мне интересно, что вы, ребята, думаете.

Спасибо!


person Jonathan    schedule 22.11.2012    source источник
comment
case "Hello": вы сравниваете указатели с этим, а не строки.   -  person ouah    schedule 23.11.2012
comment
Вообще говоря, я бы: Очистил буфер приема и подождал определенное время, чтобы убедиться, что ничего не приходит. Затем отправил приглашение и запустил таймер. Затем при каждом прерывании байта, поступающего в буфер, буферизируйте его до тех пор, пока не получите конечный символ › или не истечет время ожидания. Затем прерывание может активировать флаг, указывающий обратно на основной, что тайм-аут или допустимый пакет был получен.   -  person Myforwik    schedule 23.11.2012
comment
Ууу, да, я знаю, я поленился со своим псевдокодом.   -  person Jonathan    schedule 23.11.2012
comment
Myforwik - похоже, ваше предложение очень похоже на вариант б. есть ли отличия?   -  person Jonathan    schedule 23.11.2012


Ответы (2)


Я склонен делать такие вещи, однако у Id, как правило, есть отдельный «класс» последовательного порта ( struct + functions ) и класс протокола, который живет поверх последовательного порта. Я использовал их все время в своих встроенных системах. Это дает вам лучшее из обоих миров: блокирующий синхронный вызов и асинхронный вызов, чтобы вы могли выполнять псевдомногозадачность.

typedef struct serial_port_s serial_port;
typedef void (*serial_on_recived_proc)(serial_port* p);
typedef struct serial_port_s{
    bool timeoutFlag;
    bool receiveFlag;
    void* context;
    serial_on_recived_proc response_handler;
};

void send_serial(serial_port* p, char* message)
{
    //SendMsg?
}
void receive_serial(serial_port* p, char* response)
{
    //receiveMsg?
}

bool has_data(serial_port* p)
{
    return p->receiveFlag;
}

bool has_timed_out(serial_port* p)
{
    return p->timeoutFlag;
}
bool is_serial_finished(serial_port* p)
{
    return has_data(p) || has_timed_out(p); 
}

bool serial_check(serial_port* p)
{
    if(is_serial_finished(p) && p->response_handler != NULL)
    {
       p->response_handler(p)
       p-> response_handler = NULL;
       return true;
    }
    return false;
}

void send(serial_port* p, char* message, char* response)
{
    p->response_handler=NULL;
    send_serial(p, message);
    while(!is_serial_finished(p));
    receive_serial(p, response);
}

void sendAsync(serial_port* p, char* message, serial_on_recived_proc handler, void* context)
{
    p->response_handler = handler;
    p->context = context;
    send_serial(p, message);
}

void pow_response(serial_port* p)
{
    // could pass a pointer to a struct, or anything depending on what you want to do
    char* r = (char*)p->context;  
    receive_serial(p, r);
    // do stuff with the pow response
}

typedef struct
{
   char text[100];       
   int x;
   bool has_result;
} bang_t;

void bang_parse(bang_t* bang)
{
   bang->x = atoi(bang->text);
}

void bang_response(serial_port* p)
{
    bang_t* bang = (bang_t*)p->context;  
    receive_serial(p, bang->text);
    bang_parse(bang);
    bang->has_result=true;
}

void myFunc();
{
    char response[100];
    char pow[100];
    bang_t bang1;
    bang_t bang2;
    serial_port p; //
    int state = 1;
    // whatever you need to do to set the serial port

    // sends and blocks till a response/timeout
    send(&p, "Hello", response);
    // do what you like with the response

    // alternately, lets do an async send...
    sendAsync(&p, "Pow", pow_response, pow);       

    while(true)
    {
        // non block check, will process the response when it arrives               
        if(serial_check(p))
            {
              // it has responded to something, we can send something else...

              // using a very simple state machine, work out what to send next.
              // in practice I'd use enum for states, and functions for managing state
              // transitions, but for this example I'm just using an int which
              // I just increment to move to the next state
              switch(state)
              {
              case 1: 
                 // bang1 is the context, and will receive the data
                 sendAsync(&p, "Bang1", bang_response, &bang1);
                 state++; 
                 break;
              case 2:
                 // now bang2 is the context and will get the data...
                 sendAsync(&p, "Bang2", bang_response, &bang2);
                 state++; 
                 break;
              default:
                 //nothing more to send....
                 break;
              }
            }
        // do other stuff you want to do in parallel
    }
};
person Keith Nicholas    schedule 23.11.2012
comment
Кейт, интересный ответ. Я собираюсь реализовать что-то подобное сейчас и посмотреть, как это работает. я очень ценю ваше время - person Jonathan; 23.11.2012
comment
нет проблем, я только что обновил его, чтобы он очищал обработчик после обработки ответа, иначе он будет продолжать срабатывать. На практике, вероятно, есть и другие небольшие проблемы. Я также стремлюсь сделать аппаратно-зависимый бит очень-очень маленьким, поэтому я могу протестировать большую часть кода на ПК с помощью заглушек (таким образом я могу использовать последовательный порт ПК и отсортировать всю логику, прежде чем пытаться встроить ее, ее также проще провести модульное тестирование) - person Keith Nicholas; 23.11.2012
comment
Можете ли вы порекомендовать конкретную среду разработки для тестирования кода на ПК? Прямо сейчас я разрабатываю в рабочей среде IAR и строю непосредственно во встроенную систему. - person Jonathan; 23.11.2012
comment
Я использую Visual Studio, но если вы работаете в Linux, вы можете использовать gcc и ваш любимый инструмент. Хитрость заключается в том, чтобы держать все специфичные для оборудования вещи как можно более изолированными, а затем отключать все эти функции. Не так сложно, как кажется на первый взгляд, и как только вы это сделаете, разработка на ПК пойдет намного быстрее (я также написал среду модульного тестирования C под названием SeaTest, которую я также использую code.google.com/p/seatest для модульного тестирования). Моделирование вашей системы и модульное тестирование значительно упрощает ее установку на устройство. Обычная проблема с аппаратным обеспечением - это особенности аппаратного обеспечения и проблемы с синхронизацией. - person Keith Nicholas; 23.11.2012
comment
Привет, Кит, мне очень нравится идея указателя на функцию с асинхронным вызовом. Можете ли вы объяснить, что делает контекст? - person Jonathan; 27.11.2012
comment
контекст является очень распространенным шаблоном, который вы увидите в коде C при работе с обратными вызовами с использованием указателей на функции. Это сделано для того, чтобы вы могли получить доступ к данным. в моем примере я просто передаю char* в качестве своего контекста, поэтому, когда срабатывает обратный вызов, он может поместить результат в эту строку, вернув контекст обратно в char*. Чаще всего вы создаете структуры и передаете их в качестве контекста. Я обновлю свой ответ другим примером.... - person Keith Nicholas; 27.11.2012

Возьмите вещи просто. Процедура ISR должна быть очень быстрой, поэтому для меня лучший подход — иметь такой глобальный RXBuffer:

#include <cstdint>
#include <deque>
#include <algorithm>

class RXBuffer {
public:
    friend class ISR;

    typedef std::deque<uint8_t>::const_iterator const_iterator;

    RXBuffer();
    size_t size() const { m_buffer.size(); }

    // read from the buffer in a container in the range [first, last)
    template <typename Iterator>
    void read(Iterator first, Iterator last, Iterator to)
    {
        // how many bytes do you want to read?
        size_t bytes_to_read = std::distance(first, last);

        if (bytes_to_read >= size())
        {
            // read the whole buffer
            std::copy(begin(), end(), first);

            // empty the buffer
            m_buffer.clear();

            return size();
        }
        else
        {
            // copy the data
            copy(begin(), begin() + bytes_to_read, firt);

            // now enque the element
            m_buffer.erase(begin(), begon() + bytes_to_read);

            return bytes_to_read;
        }
    }

private:
    void put(uint8_t data)
    {
        // check buffer overflow
        m_buffer.push_back(data);
    }

    const_iterator begin() const { return m_buffer.begin(); }
    const_iterator end() const { return m_buffer.end(); }

private:
    std::deque<uint8_t> m_buffer;           // buffer where store data
    size_t m_size;                          // effective size of the container
};

class ISR {
public:
    ISR(RXBuffer& buffer) : m_buffer(buffer) {}

    // ISR Routine
    void operator () (uint8_t data)
    {
        m_buffer.put(data);
    } 

private:
    RXBuffer& m_buffer;
};
RXBuffer g_uart1_rx_buffer;

Теперь у вас есть ISR и RXBuffer, где хранятся данные поиска, так что вам нужно что-то, чтобы обернуть функции UART. Вы можете реализовать следующим образом:

class UART {
public:
    UART(unsigned int uart_device, RXBuffer& rx_buffer) :
        m_uart(uart_device), m_buffer(rx_buffer)
    {
    }

    unsigned int uart_device() const { return m_uart; }

    // set the timeout during a read operation
    void timeout(unsigned ms) { m_timer.countdown(ms); }

    template <typename InputIterator>
    void read(InputIterator first, InputIterator last)
    {
        // start the timer
        m_timer.start();

        size_t size = std::distance(first, last);
        size_t read_bytes = 0;

        while (read_bytes != size && !m_timer.is_expired())
        {
            read_bytes += m_buffer.read(first + read_bytes, last);
        }

        if (read_bytes != size) throw std::exception("timeout");
    }

    template <typename OutputIterator>
    void send(OutputIterator first, OutputIterator last)
    {
        size_t size = std::distance(first, last);
        uart_send(m_uart, &(*first), size);
    }

private:
    unsigned int m_uart;
    RXBuffer& m_buffer;
    timer m_timer;
};
person Elvis Dukaj    schedule 23.11.2012