Страницы

понедельник, 29 февраля 2016 г.

QWAKE - быстрый вариант протокола WAKE.

Потребовалось мне в одном проекте передавать несколько потоков данных с приличной скоростью по одному каналу. Нужен был подходящий способ упаковки и распаковки данных. А для большой скорости лучше бы использовать не байты, а как минимум, слова данных. Для такой цели точкой старта подошёл протокол WAKE (ссылка на источник). Я немного доработал этот протокол, перевёл его на 16-бит и написал функции на Си для любого процессора ARM ( в данном случае был STM32). В результате получился Qwick WAKE протокол:



QWAKE Serial Protocol
specification

Протокол QWAKE является логическим уровнем интерфейса управления оборудованием с помощью асинхронного последовательного канала. Физический уровень интерфейса протоколом не определяется, может использоваться, например, RS-232 или RS-485. В данном примере протокол QWAKE является надстройкой UDP протокола передачи данных по WIFI. Протокол позволяет производить обмен пакетами данных (data frames) длиной до 32767 байт с адресуемыми устройствами, которых может быть до 127.
Последовательный канал SPI должен быть сконфигурирован следующим образом:
• число бит в посылке – 16
• скорость обмена – 300…2000000 бод
Протокол QWAKE разработан для передачи по одной асинхронной линии нескольких независимых потоков данных: управления, MIDI, PCM audio.
Протокол является расширением протокола WAKE. Основой протокола WAKE является протокол SLIP (UNIX™ Serial Link Interface Protocol). Передача данных осуществляется в двоичном виде, т.е. используются все возможные значения слова (0000h…FFFFh). Для передачи служебной информации зарезервированы два кода: FEND = E3E3h (Frame End) и FESC = D6D2h (Frame Escape). Управляющий код FEND служит для обозначения начала посылки, а код FESC служит для передачи ESC-последовательностей. Если в потоке данных встречаются байты, значения которых совпадают с управляющими кодами, производится подмена этих байт ESC последовательностями. Такой механизм называют стаффингом (stuffing). Код FEND заменяется последовательностью <FESC>, <TFEND>, а код FESC – последовательностью <FESC>, <TFESC>, где TFEND = D6D3h (Transposed FEND), TFESC = D6D4h (Transposed FESC). Коды TFEND и TFESC являются управляющими только в ESC последовательностях, поэтому при передаче данных они в подмене не нуждаются.

Таблица 1. Управляющие коды протокола WAKE
Обозначение
Пояснение
HEX-значение
FEND
Frame End
E3E3h
FESC
Frame Escape
D6D2h
TFEND
Transposed Frame End
D6D3h
TFESC
Transposed Frame Escape
D6D4h

Таблица 2. Подмена слов данных ESC-последовательностями
Слово данных
Передаваемая последовательность
E3E3h
D6D2h, D6D3h
D6D2h
D6D2h, D6D4h

Структура пакета QWAKE следующая: пакет всегда начинается управляющим кодом FEND (E3E3h). Затем следует слово адреса. За ним следует слово количества данных и собственно слова данных. Завершает пакет необязательное слово контрольной суммы CRC-16 (в данном случае не используется).

Таблица 3. Структура пакета QWAKE
FEND
ADDR
N
Data1
DataN
CRC

FEND: Управляющий код FEND (E3E3h) является признаком начала пакета. Благодаря стаффингу, этот код больше нигде в потоке данных не встречается, что позволяет в любой ситуации однозначно определять начало пакета.
ADDR: Слово адреса используется для адресации отдельных устройств. Слово адреса разделено на два байта. Первый байт определяет адрес устройства. Для адресации используется 7 бит, а старший бит, передаваемый вместе с адресом, должен всегда быть сброшен:

D7
D6
D5
D4
D3
D2
D1
D0
ADDR =
0
A6
A5
A4
A3
A2
A1
A0
Адрес 0 – широкополосный.
Иногда возникает необходимость передать какую-то команду или данные сразу всем устройствам. Для этого предусмотрен коллективный вызов (broadcast), который осуществляется путем передачи нулевого адреса. Учитывая разрядность адреса и один зарезервированный адрес для коллективного вызова, максимальное количество адресуемых устройств составляет 127.

CH: Второй байт адреса определяет номер потока данных в устройстве (канал). В протоколе WAKE этот байт отвечал за команду. Для того, чтобы передавать несколько потоков данных в устройстве, применяется нумерация каналов. Неявно нумерация каналов определяет команду. Всего каналов 256.

D7
D6
D5
D4
D3
D2
D1
D0
CH =
C7
C6
C5
C4
C3
C2
C1
C0

Каналы выбираются произвольно в зависимости от нужд приложения. Рекомендуется использовать несколько стандартных кодов каналов:
Таблица 4. Стандартные каналы протокола QWAKE
Код
Название
Описание канала

00h
С_Nop
Нет операции
01h
C_Err
Передача кода ошибки
02h
C_Echo
Запрос возврата переданного пакета
03h
C_Info
Запрос информации об устройстве

Коды остальных каналов выбираются в зависимости от нужд приложения. Каналы обычно имеют несколько параметров, которые передаются далее в виде пакета данных.
N: Слово количества данных имеет значение, равное количеству передаваемых байт данных:

D15
D14
D13
D12
D11
D10
D9
D8
D7
D6
D5
D4
D3
D2
D1
D0
N =
0
N14
N13
N12
N11
N10
N9
N8
N7
N6
N5
N4
N3
N2
N1
N0
Таким образом, код количества данных занимает 16 бит, старший бит всегда равен 0. В результате один пакет может содержать до 32767 байт данных. Значение N не учитывает служебные байты пакета FEND, ADDR, CH, N и CRC. В результате стаффинга фактическая длина пакета может возрасти. Значение N не учитывает этот факт и отражает количество полезных байт данных (т.е. значение N всегда таково, как будто стаффинг не осуществляется). Если передаваемая команда не имеет параметров, то передается N = 00h и байты данных опускаются.
Благодаря резервированию старшего бита во время передачи адреса и размера данных стаффинга не происходит, и данные передаются в прямом виде.

Data1…DataN: Слова данных, количество байт в которых определяется значением N. При N = 00h байты данных отсутствуют. При нечётном N последнее слово передаётся с нулями в младшем байте. Слова данных могут иметь любое значение, кроме FEND (E3E3h) и FESC (D6D2h). Если возникает необходимость передать одно из этих значений, то производится стаффинг, т.е. передача ESC-последовательности (см. таблицу 2), состоящей из управляющего кода FESC и кода TFEND (TFESC).
СRC: Слово контрольной суммы CRC-16. Может отсутствовать в некоторых реализациях протокола. Контрольная сумма CRC-16 рассчитывается перед операцией стаффинга для всего пакета, начиная с слова FEND и заканчивая последним байтом данных. Для расчета контрольной суммы используется полином CRC = X16 + X7 + X4 + 1. Значение CRC перед вычислением инициализируется числом F3DEh. При передаче значения слова контрольной суммы E3E3h и D6D2h заменяются ESC-последовательностями (см. таблицу 2).

 Программы.

 Заголовочный файл qwake.h:

/*------------------------------------------------------------------------------
* файл qwake.h
* определения функций протокола QWAKE
*-----------------------------------------------------------------------------*/
#ifndef    _QWAKE_H_
#define    _QWAKE_H_

typedef void (*q_callback) (void);

typedef union _adch_t
{
    uint16_t    adch;   
    struct
    {
        uint8_t        addr;    // little-endian: по старшему адресу
        uint8_t        ch;        // little-endian: по младшему адресу
    };
} adch_t;

typedef struct _qwake_transmit_parameters
{
    uint16_t    size_out;       
    uint16_t    *output;
} qwake_transmit_parameters;

void        qwake_transmit        (uint16_t *in, uint16_t n_in,  uint16_t adch);
void        qwake_receive        (uint16_t *input, uint16_t size_in); 
uint16_t    **qwake_receive_set    (uint16_t *buf, q_callback fn, uint16_t adch);

extern qwake_transmit_parameters    qt;

#endif    // _QWAKE_H_


Файл qwake.c:

/*------------------------------------------------------------------------------
* файл qwake.c
* работа с протоколом QWAKE
*-----------------------------------------------------------------------------*/
#include    "stm32f4xx.h"
#include    "stm32f446_it.h"
#include    "global.h"
#include    "qwake.h"

#define    FEND    (uint16_t)0xE3E3    // Frame End
#define    FESC    (uint16_t)0xD6D2    // Frame Escape
#define    TFEND    (uint16_t)0xD6D3    // Transposed Frame End
#define    TFESC    (uint16_t)0xD6D4    // Transposed Frame Escape

// структура с данными принимаемых потоков
typedef struct _qwake_receive_struct
{
    uint8_t        num_channels;            // число запрограммированных каналов в приёмнике
    uint16_t    n_out;                    // размер выходного буфера
    adch_t        adch[MAX_QCHANNEL];        // адреса и каналы в приёмнике
    uint16_t    *output[MAX_QCHANNEL];    // адреса выходного буфера для каждого канала
    q_callback    q_func[MAX_QCHANNEL];    // функции по заполнению выходных каналов
} qwake_receive_struct;

qwake_transmit_parameters        qt = {0};
static qwake_receive_struct        qr = {0};    // по умолчанию потоки не запрограммированы

/*******************************************************************************
* Заполнение внутренней структуры адресов и каналов
* Вход: адрес, номер канала, адрес функции возврата
* Выход: заполняется структура при наличии места
* при успешном заполнении возвращает адрес указателя на выходной буфер канала, иначе 0
*******************************************************************************/
uint16_t **qwake_receive_set (uint16_t *buf, q_callback fn, uint16_t adch)
{
    uint16_t **temp = ((uint16_t **)0);    // если нет места   
    uint8_t    num = qr.num_channels; 
    if (num < MAX_QCHANNEL)
    {
        qr.adch[num].adch = adch;
        qr.output[num] = buf;
        qr.q_func[num] = fn;
        qr.num_channels++;
        temp = &qr.output[num];
    }
    return temp;
}

/*******************************************************************************
* формирование выходного потока по протоколу QWAKE
* Вход: адрес буфера in с данными, размер буфера, адрес и канал передачи
* Выход: заполняется буфер qt.output
* Буфер всегда пополняется, обнулять его будет драйвер передатчика.
*******************************************************************************/
void qwake_transmit (uint16_t *in, uint16_t n_in,  uint16_t adch)
{       
    uint16_t    n_out        = qt.size_out + 6;    // минимальный размер пакета
    uint16_t    *out        = qt.output;
   
    *out++ = FEND;    // заголовок
    *out++ = adch;    // адрес-канал
    *out++ = n_in;    // размер данных
   
    while (n_in)
    {
        if (n_in == 1)        // передача последнего байта
        {
            *(uint8_t *)out = *(uint8_t *)in;
            out++;
            n_out += 2;   
            n_in = 0;
        }
        else
        {
            switch (*in)
            {
            case FEND:    // байт-стаффинг FEND
                *out++ = FESC;
                *out++ = TFEND;
                in++;
                n_out += 4;
                n_in -= 2;
                break;
               
            case FESC:    // байт-стаффинг FESC
                *out++ = FESC;
                *out++ = TFESC;
                in++;
                n_out += 4;   
                n_in -= 2;
                break;
               
            default:    // обычные данные
                *out++ = *in++;
                n_out += 2;
                n_in -= 2;
                break;
            }       
        }
    }
    qt.size_out = n_out;    // сохранение размера и указателя
    qt.output = out;
}

/*******************************************************************************
* разборка входного потока по протоколу QWAKE
* Вход: адрес буфера input с данными, размер буфера
* в структуре qr хранятся каналы приёмника и их параметры
* Выход: заполняется буфер qr.output[nch] соответствующего канала приёмника
* по приёму пакета выполняется функция qr.q_func[nch] ()
*******************************************************************************/
void qwake_receive (uint16_t *input, uint16_t size_in)
{
    typedef enum _q_in_states
    {
        Q_IN_WAIT = 0,
        Q_IN_ADCH,
        Q_IN_NUM,
        Q_IN_DATA,
    } q_in_states;

    typedef struct _q_in_parameters
    {
        q_in_states    q_in_state;    // состояние парсера
        uint16_t    *out;        // указатель на выходной буфер
        uint16_t    n_out;        // размер выходного буфера
        uint8_t        channel;    // номер принимаемого канала       
    } q_in_parameters;
   
    static q_in_parameters    qin =
    {
        Q_IN_WAIT,
        0,
        0,
        0
    };
   

    uint16_t        adch;    // замена статических переменных регистровыми
    q_in_states        qstate        = qin.q_in_state;
    uint16_t        *nout         = qin.out;
    uint16_t        nn_out         = qin.n_out;
    uint8_t            nch         = qin.channel;
   
    while (size_in)    // разбирать до конца входных данных
    {
        switch (qstate)
        {
        // ожидание начала пакета
        case Q_IN_WAIT:
            if (*input == FEND)    // ожидаем стартовой посылки
            {
                qstate = Q_IN_ADCH;
            }
            input++;            // продвигаемся по входной цепочке
            size_in -= 2;
            break;
        // сравнение с нашим адресом и номером канала               
        case Q_IN_ADCH:
            adch = *input++;            // прочитать адрес пакета
            size_in -= 2;
            // определение канала приёма данных
            nch = 0;
            while ((adch != qr.adch[nch].adch)                 // наш канал
                && (adch != (qr.adch[nch].adch & 0xFF00))    // широкополосный канал (little-endian)
                && (nch < qr.num_channels))                    // всего наших каналов
            {
                nch++;
            }
            if (nch < qr.num_channels)    // если нашли совпадение
            {
                nout = qr.output[nch];    // записываем адрес буфера канала приёмника
                qstate = Q_IN_NUM;           
            }
            else                        // не наш пакет - ждём следующего
            {
                qstate = Q_IN_WAIT;                                       
            }
            break;
        // получение числа байт на входе   
        case Q_IN_NUM:
            qr.n_out = nn_out = *input++;// записать количество параметров
            size_in -= 2;
            if (nn_out)    // есть данные - разбираем
            {
                qstate = Q_IN_DATA;
            }
            else        // нет данных - просто команда без параметров
            {
                qr.q_func[nch] ();        // отдать буфер
                qstate = Q_IN_WAIT;        // разбирать входной пакет дальше
            }
            break;
        // приём данных   
        case Q_IN_DATA:
            if (*input != FESC)   
            {
                if (nn_out > 1)    // передача слов данных
                {
                    *nout++ = *input++;
                    nn_out -= 2;   
                }
                else            // передача последнего байта
                {
                    *(uint8_t *)nout = *(uint8_t *)input;
                    input++;
                    nn_out = 0;                   
                }
                size_in -= 2;   
            }
            else            // стаффинг
            {
                input++;
                switch (*input)
                {
                case TFEND:    // подмена символов при стаффинге
                    *nout++ = FEND;
                    input++;
                    size_in -= 2;
                    nn_out -= 2;                           
                    break;
                   
                case TFESC:
                    *nout++ = FESC;
                    input++;
                    size_in -= 2;
                    nn_out -= 2;
                    break;
                   
                default:    // в случае ошибки стаффинга - сброс
                    qstate = Q_IN_WAIT;                           
                    break;
                }
            }
            if (!nn_out)    // по окончании посылки
            {
                qr.q_func[nch] ();    // отдать буфер
                qstate = Q_IN_WAIT;    // разбирать входной пакет дальше
            }
            break;
           
        default:
            qstate = Q_IN_WAIT;
            break;
        }
    }
    // сохранение регистровых переменных в статических
    qin.q_in_state = qstate;
    qin.out = nout;
    qin.n_out = nn_out;
    qin.channel = nch;
}


Как использовать.

Тестирование я проводил в такой программе:
/*------------------------------------------------------------------------------
* файл test.c
* проверка работы системы
*-----------------------------------------------------------------------------*/

#include    <string.h>
#include    "stm32f4xx.h"
#include    "stm32f446_it.h"
#include    "test.h"
#include    "global.h"
#include    "qwake.h"

static uint16_t    **adr_inbuf_ch6;
static uint16_t    **adr_inbuf_ch3;
static uint8_t     ch6_inbuf1[128];
static uint8_t     ch6_inbuf2[128];
static uint8_t     ch3_inbuf1[128];
static uint8_t     ch3_inbuf2[128];
static uint8_t     qbuffer1[128];
static uint8_t     qbuffer2[128];

void        test_qwake                (void);
void        q_ch6                    (void);    // ф-я по приёму сообщения
void        q_ch3                    (void);    // ф-я по приёму сообщения

/*******************************************************************************
* Проверка работы QWAKE упаковщика и распаковщика
*
*
*******************************************************************************/
void test_qwake (void)
{
    static const uint8_t test_string1[]    =
    {0xE3, 0xE3, 0xD2, 0xD6, 0x10, 0x11, 0x12, 0x13, 0x14};
    static const uint8_t test_string2[]    = "Это вторая тестовая строка для упаковки. Test2";
    adch_t    adr_ch;
       
    // упаковка и распаковка по адресу
    qt.output = (uint16_t *)qbuffer1;    // обнуление адресов и размеров будет в драйвере передачи
    qt.size_out = 0;                    // а пока на стороне отправителя
    adr_ch.addr = 10;
    adr_ch.ch = 6;
    // подготовить распаковщик с каналом 6
    adr_inbuf_ch6 = qwake_receive_set ((uint16_t *)ch6_inbuf1, &q_ch6, adr_ch.adch);
    // передать сообщение в канал 6
    qwake_transmit ((uint16_t *)test_string1, sizeof (test_string1), adr_ch.adch);

    adr_ch.addr = 10;
    adr_ch.ch = 3;
    // подготовить распаковщик с каналом 3
    adr_inbuf_ch3 = qwake_receive_set ((uint16_t *)ch3_inbuf1, &q_ch3, adr_ch.adch);
    // передать сообщение в канал 3
    qwake_transmit ((uint16_t *)test_string2, sizeof (test_string2), adr_ch.adch);
   
    // проверить выходной буфер
    qwake_receive ((uint16_t *)qbuffer1, qt.size_out);
   
    // сформировать широкополосный пакет
    qt.output = (uint16_t *)qbuffer2;
    qt.size_out = 0;
    adr_ch.addr = 0;
    adr_ch.ch = 6;
   
    qwake_transmit ((uint16_t *)test_string2, sizeof (test_string2), adr_ch.adch);
   
    // принять широкополосный пакет
    qwake_receive ((uint16_t *)qbuffer2, qt.size_out);
   
}

// коллбэк по приёму сообщения в потоке 6
void q_ch6 (void)
{
    if (*adr_inbuf_ch6 == (uint16_t *)ch6_inbuf1)
    {
        *adr_inbuf_ch6 = (uint16_t *)ch6_inbuf2;
    }
    else
    {
        *adr_inbuf_ch6 = (uint16_t *)ch6_inbuf1;
    }
}

// коллбэк по приёму сообщения в потоке 3
void q_ch3 (void)
{
    if (*adr_inbuf_ch3 == (uint16_t *)ch3_inbuf1)
    {
        *adr_inbuf_ch3 = (uint16_t *)ch3_inbuf2;
    }
    else
    {
        *adr_inbuf_ch3 = (uint16_t *)ch3_inbuf1;
    }
}

Комментариев нет:

Отправить комментарий