Перейти к содержанию
    

Драйвер USART на прием переменной длины пакета

Для передачи данных между процессорами использовал протокол с фиксированной длиной запросного пакета и ответного пакета. Для абстрагирования и переносимости протокола решил написать драйвер USART что бы прием передача происходила только через эти функции.
Алгоритм драйвера закладывал такой прием / передача данных происходит через кэш буфер.
Для передачи, данных в линию USART ложем данные в кэш (функцией DriverWriteUsart) и запускаем выдачу.  
Для приема данных в прерывании складываем их в кэш а в протоколе считываем данные из кэша функцией GetDataFromUsartCache(DRIVER_USART_ENCODER)
 
В протоколе это выглядит  так SendRequest формирует данные в пакет и ложит в кэш USART после чего данные от туда выдаются в линию с помощью DMA  
После отправки запроса устанавливаем драйвер на прием ответного пакета функцией DriverReadUsart() с указанием сколько байт будет занимать ответный пакет (с ожиданием приема данных через DMA) а дальше считываем GetDataFromUsartCache все что приняли с проверкой CRC.
Теперь решил переделать протокол и ответный пакет расширился (ID,CMD,TYPE,LEN,<DATA>,CRC) где LEN длина данных и теперь я не могу использовать DriverReadUsart с ожиданием конкретного количества байт, так как количество байт неизвестно.

Spoiler

void vTaskConnection (void *pvParameters)
{
    //--------------------------------------------------------------------------------------------
    Init_CRC();
    //--------------------------------------------------------------------------------------------
    InitDriverUsart(DRIVER_USART_XXX,USART_XXX,19200,50);  // 50ms timeout
    //--------------------------------------------------------------------------------------------
    //--------------------------------------------------------------------------------------------
    if(HandlerInitPayload!=0){
        HandlerInitPayload(0);
    }
    //--------------------------------------------------------------------------------------------    
    while(1){
    	vTaskDelay( 200 / portTICK_RATE_MS ); 
        //-------------------------------------------------------------------------------------
        SendRequest((uint8_t*)&RequestPack,sizeof(RequestPack_t));
        DriverReadUsart(DRIVER_USART_XXX,17+20+8+1+12);    //Устанавливаем чтение N байт ответного пакета
        //-------------------------------------------------------------------------------------
        //------------------------Событие приема пакета----------------------------------------
        //-------------------------------------------------------------------------------------
        if(OnReceiveCompleated(DRIVER_USART_XXX)){
            ReadAllPack(RxBuffer); 
            if(IsValidPack(RxBuffer)==1){
                if(HandlerRxPayload!=0){
                    HandlerRxPayload(&RxBuffer[2]);
                    //----------------------------------------------
                    DebugConnect.CntPackReceive++;
                    if(DebugConnect.CntPackReceive>=(0xFFFF-1)){DebugConnect.CntPackReceive=(0xFFFF-1);}                    
                }
            }else{
              DebugConnect.CntNotValidPack++;
              if(DebugConnect.CntNotValidPack>=(0xFF-1)){DebugConnect.CntNotValidPack=(0xFF-1);}
            }
        }
    }
}
//==================================================================================================
/*
    * @Описание:    Описание функции.
    * @Параметр:    
    * @Возврат:     Нету
*/
void SendRequest(uint8_t*data,uint8_t lenght){
uint32_t CRC32=0; 
uint8_t temp;
    ResetCrc();
    //-----------------------------------
    DriverWriteUsart(DRIVER_USART_XXX,0x11);  //START byte
    Tread_CRC(0x11);
    //-----------------------------------
    DriverWriteUsart(DRIVER_USART_XXX,lenght);  //START byte
    Tread_CRC(lenght);    
    //-----------------------------------
    for(uint8_t i = 0; i < lenght; i++) {
        temp=data[i];
        DriverWriteUsart(DRIVER_USART_XXX,temp);
        Tread_CRC(temp);
    }
    CRC32=GetCRC();
    //-----------------------------------
    DriverWriteUsart(DRIVER_USART_XXX,CRC32>>24);
    DriverWriteUsart(DRIVER_USART_XXX,CRC32>>16);
    DriverWriteUsart(DRIVER_USART_XXX,CRC32>>8);
    DriverWriteUsart(DRIVER_USART_XXX,CRC32);
    //-----------------------------------
    SendUsart(DRIVER_USART_XXX);    //Команда на отправку данных из внутренего буфера USART в линию
}

//==================================================================================================
/*
    * @Описание:   Считываем весь пакет из DriverUsart (приемного буффера) 
    * @Параметр:    
    * @Возврат:     
*/
static void ReadAllPack(uint8_t *data){
    uint8_t len=GetlenghtRx(DRIVER_USART_ENCODER);
    for(uint8_t i=0;i<len;i++){
        data[i]=GetDataFromUsartCache(DRIVER_USART_ENCODER);
    }
}
//==================================================================================================
/*
	* @Описание:	Описание функции.
	* @Параметр:	
	* @Возврат:		Нету
*/
void DriverReadUsart(uint8_t NumDriver,uint8_t lenght){
uint32_t Status;
	IndexReadCahe=(~IndexReadCahe)&0x01;
	DriverUsart[NumDriver].lenghtRx=0;
	DriverUsart[NumDriver].OnReceiveCompleated=0;
    DriverUsart[NumDriver].OffsetCache=0;
	DriverUsart[NumDriver].OnReceiveTimeOut=0;    		
    if(DriverUsart[NumDriver].UseDma==1){
        Status=DmaUsartTransactions(DriverUsart[NumDriver].USARTx,RECEIVE,CaheRx[NumDriver][IndexReadCahe],lenght,DriverUsart[NumDriver].RxTimeOuut);
    }
    if(Status==1){
		DriverUsart[NumDriver].lenghtRx=lenght;
		DriverUsart[NumDriver].OnReceiveCompleated=1;    	
    }
    if(Status==0){
    	DriverUsart[NumDriver].OnReceiveTimeOut=1;    		
    }
}

 

Пока думаю в направление сделать выбор в драйвере USART  использовать на прием DMA или прерывание, и в прерывание ложить данные в кэш. Но сейчас обработчик прерывание USART находиться в само нижнем уровне (протокол -> драйвер USART -> аппаратная настройка USART) и там обработчик нечего не знает об кэше USART

 

 

Изменено пользователем pokk

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вообще протокол с фиксированной длиной пакета - штука достаточно простая, но есть нюансы. Нужно четко определяться, что делать в случаях, если данных пришло меньше, чем ждали. Или больше. Окучивать таймауты, отбрасывать мусор, склеивать данные, которые успели прилететь от другого пакета и т.д. Протоколов, лишенных таких недостатков, много. Некоторые опираются на железные возможности интерфейса. Например, Ethernet, CAN, USB - пакетные интерфейсы: в их протоколе уже заложена возможность передачи данных неделимыми порциями - пакетами. Аппаратура предоставляет буферам периферии эти самые пакеты (кадры). Ну а USART - это байтовый интерфейс, и вот чтобы получить возможность логически оперировать кадрами данных, нужно изобретать кадр-ориентированный протокол поверх байтового. Делать это можно разными способами, но для USART прекрасно подходят, например, SLIP или COBS (это алгоритмы превращения потока байт в кадры и обратно, называются они фреймерами). Выбор конкретного зависит от множества факторов: есть ли у Вас место под буфер данных или его нет, нужна ли прогнозируемая оценка загрузки канала связи и т.д. В большинстве случаев у меня, например, используется SLIP, он же - байт-стаффинг. Принцип работы, думаю, нагуглите. По сути, на вход фреймера подаются данные для передачи, он их упаковывает в соответствии с его алгоритмом, и этот уже упакованный поток байт отправляется в линию. При приеме же наоборот, поток байт подается на вход фреймера, он распаковывает их и на выходе получаем кадр. Удобно еще то, что отпадает необходимость передавать в протоколе длину сообщения. Она выявляется самим фреймером. Естественно, на этом работа фреймера заканчивается. Что делать с кадрами дальше - вопрос другой. Я, например, буферизую как исходящие, так и входящие данные. Поток приема и обработки входящих данных выглядит так

static void ExchTask(void)
{
  exch_Init();
  
  while(1)
  {
    if(exch_WaitMsg(10))
      exch_ParsMsg();
  }
}


В exch_Init() подготавливаются кольцевые буферы для входящих и исходящих сообщений

void exch_Init(void)
{
  rb_Init(&ExchRxQ);
  rb_Init(&ExchTxQ);
  hw_EnUARTRxIrq();
}


Функция exch_WaitMsg() смотрит, есть ли во входном почтовом ящике хотя бы одно необработанное сообщение, если нету - таймаут

u32 exch_WaitMsg(u32 tim)
{
  u32 len = 0;
  if(rb_GetBusy(&ExchRxQ) > sizeof(len))
    rb_Peek(&ExchRxQ, (RBTYPE *)&len, sizeof(len));
  if(len == 0) eds_Timeout(tim);
  return len;
}


Очереди у меня - обычные байтовые. Фреймер всегда кладет в них сообщение в формате

u32 len;
...    ; // len байт сообщения


Парсинг сообщения происходит в exch_ParsMsg()

void exch_ParsMsg(void)
{
  while(1)
  {
    u32 len = 0;
    if(rb_GetBusy(&ExchRxQ) > sizeof(len))
      rb_Peek(&ExchRxQ, (RBTYPE *)&len, sizeof(len));
    if(len > 0)
    {
      if(len <= EXCH_MSG_MAXSIZE &&
         len <= sizeof(sExchRxMsg))
      {
        __ALIGNED(4) sExchRxMsg msg;
        rb_FreeR(&ExchRxQ, sizeof(len));
        rb_Read(&ExchRxQ, (RBTYPE *)&msg, len);
        if(hw_CalcCRC32((u32 *)&msg, len / 4) == 0)
          ProcMsg(&msg);
      }
      else rb_FreeR(&ExchRxQ, sizeof(len) + len);
    }
    else break;
  }
}

Здесь, по сути, циклично вычитываются все входящие сообщения, проверяется их корректность.

Ну а если входящее сообщение корректно (я пользуюсь обычным CRC-32), отдаю его функции ProcMsg()

static void ProcMsg(sExchRxMsg *rmsg)
{
  switch(rmsg->cmd)
  {
    case EXCH_CMD_RSTMCU:
      ...
    case EXCH_CMD_GETSYS:
      ...
    case EXCH_CMD_PROGFW:
      ...
  }
}


Для обработки сообщений разной длины в ProcMsg() можно передавать len из exch_ParsMsg(), а rmsg объявить буфером на u8.

Для передачи пользуюсь функцией exch_SendMsg()

u32 exch_SendMsg(u8 msg[], u32 len)
{
  u32 reterr = 0;
  if(len > 0 && rb_GetFree(&ExchTxQ) >= sizeof(len) + len)
    rb_Write(&ExchTxQ, (RBTYPE *)&len, sizeof(len)),
    rb_Write(&ExchTxQ, msg, len), hw_EnUARTTxIrq();
  else reterr = 1;
  return reterr;
}


Прикладной код же сообщения отправляет уже в нужном структурированном формате, естественно

void exch_SendAckROMCmd(u32 ack)
{
  __ALIGNED(4) sExchTxMsg msg;
  const u32 len = sizeof(msg);
  msg.cmd    = EXCH_CMD_PROGFW;
  msg.bl.ack = ack;
  msg.crc    = hw_CalcCRC32((u32 *)&msg,
                            len / 4 - 1);
  exch_SendMsg((u8 *)&msg, len);
}


Операционка у меня кооперативная, поэтому нет необходимости в средствах обеспечения потокобезопасности. Как попадают данные в кольцевой буфер - вопрос вторичный. Хоть с DMA, хоть без него. У меня работа по прерываниям. В прерываниях, кстати, реализована вся логика отсеивания мусора, синхронизации кадров и т.д., в том числе и логика самого фреймера. Думаю, принцип ясен, в общем.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...