pokk 0 April 14, 2021 Posted April 14, 2021 (edited) · Report post Для передачи данных между процессорами использовал протокол с фиксированной длиной запросного пакета и ответного пакета. Для абстрагирования и переносимости протокола решил написать драйвер 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 Edited April 14, 2021 by pokk Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 55 April 14, 2021 Posted April 14, 2021 · Report post Вообще протокол с фиксированной длиной пакета - штука достаточно простая, но есть нюансы. Нужно четко определяться, что делать в случаях, если данных пришло меньше, чем ждали. Или больше. Окучивать таймауты, отбрасывать мусор, склеивать данные, которые успели прилететь от другого пакета и т.д. Протоколов, лишенных таких недостатков, много. Некоторые опираются на железные возможности интерфейса. Например, 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, хоть без него. У меня работа по прерываниям. В прерываниях, кстати, реализована вся логика отсеивания мусора, синхронизации кадров и т.д., в том числе и логика самого фреймера. Думаю, принцип ясен, в общем. Quote Share this post Link to post Share on other sites More sharing options...