Подключение трех устройств UART к микроконтроллеру без потери данных

У меня есть микроконтроллер TM4C123GXL Launchpad с 8 последовательными портами UART. Я использую Keil uVision5 с Tiva Ware для программирования.

Я хотел соединить 3 устройства UART с микроконтроллером без потери байтов. Скорость передачи установлена ​​на 115200 бит/с без контроля четности, как показано на рисунке ниже. Все устройства периодически отправляют фрейм данных каждые 5 мс.

Система связи

Время TX и RX рассчитывается с использованием No_0f_bytes*10/BaudRate.

Я успешно соединил устройства с UART µC. Проблема в связи в том, что я теряю байты. Если я устанавливаю связь с одним устройством (Устройство 2), я все равно теряю байты всего кадра (20 байт).

Это связано с 16-байтовым ограничением FIFO Tm4c123 или чем-то еще?

Я также реализовал функцию µDMA TM4C123. Но все равно байты теряются. Что я могу сделать, чтобы улучшить систему для передачи и приема кадров данных без потерь?

Редактировать:

Вот архитектура программного обеспечения:

Я использую периодическое прерывание таймера 5 мс для приема и передачи кадра. Весь кадр имеет первые 2 байта заголовка и байт счетчика в качестве последнего байта.

void Timer1IntHandler(void) //  Periodic Service Routine every 5ms
{

DisableIntrupts();

bool Correct=ReadJoystick(); //10 bytes  Device 1

if(Correct)
{
GenerateServoCardsFrame();

SendServo1Frame();   //20 bytes  Device 2
SendServo2Frame(); //17 bytes  Device 3
ReadServo1Frame();  //15 bytes Device 2
ReadServo2Frame();  //20 bytes Device 3

GenerateJoystickFrame();

SendJoystickFrame(); //10 bytes   Device 1

EnableIntrupts();
}

}

main()
{
SetupClock()  ;   //Setup 16 MHz Clock
SetupJoystick();  //Initalize uart1 port for Device1
SetupServoCard1(); //Initalize uart2 port for Device2
SetupServoCard2(); //Initalize uart3 port for Device3

InitalizePeriodicTimerHandler(5);   //Periodic Service Routine every 5ms  (Timer1IntHandler)

while(1)
{
}

}


bool ReadJoystick(void)
{
    int BytePos=0;
    int CountInvalid=0;
    int LoopoutTime=0;

    while(1)
    {
    if (ROM_UARTCharsAvail(UART1))                              
        { 
            ByteRX = ROM_UARTCharGetNonBlocking(UART1);            
            if (BytePos==0)
            {
                if (ByteRX== 0xA1)      //Header1 found                              
                {
                    KArray[0] = Bytebuf ;
                    BytePos ++;
                }
                else
                {
                    CountInvalid++;
                    if (CountInvalid>5) 
                        return 0;
                }           
            }            
            else if (BytePos ==1) 
            {           
                if (ByteRX == 0x66)      //Header2 found                                   
                {   
                    KArray[1] = ByteRX;
                    BytePos ++;
                }
                else                                                   
                    BytePos=0;                                                        
            }            
            else
            {
                KArray[BytePos++] = ByteRX;                
                if (BytePos==10)                                                      
                    return 1;      //Frame Recived                                                   
            }
        }
        else                                                          
        {
            SysCtlDelay(0.25*SysCtlClockGet()/3 / 1000);        //   0.25ms delay
            LoopoutTime++;            
            if (LoopoutTime > 10)                                      
                return 0;            
        }    
    }       

}

Согласно моим расчетам, 1 байт требует 10/115200 = 0,08680 мс, а для чтения FIFO из 16 байт требуется 1,38 мс. В таблице на рисунке показано общее время передачи 4,08 мс и время приема 3,91 мс, что в сумме составляет 8 мс. Это больше, чем моя программа обслуживания периодических прерываний.

Нужно ли увеличивать время периодического прерывания?

вы пробовали это в более низкой скорости передачи данных? Более высокие скорости передачи данных означают большую подверженность ошибкам.
Устройства не могут быть настроены на более низкую скорость передачи данных. Совместима только скорость передачи 115200 бит/сек.
что это за устройство?
Один из них — джойстик, а два других — карта сервоконтроллера. Их прошивка ставится кем-то другим.
Да, 16-байтовый FIFO мал. Это было бы причиной.
Байты, входящие через UART, теряются, когда программное обеспечение драйвера UART не обслуживает UART достаточно быстро. Вы не предоставили достаточно сведений о своем программном обеспечении, чтобы кто-либо мог дать подробные предложения. Как правило, я бы посоветовал вам иметь более отзывчивый дизайн программного обеспечения или более быструю тактовую частоту процессора.
Вам пришлось бы читать каждый байт так быстро (примерно каждые 64 микросекунды для скорости 115200 бод)
@MasoodSalik Я давно написал драйверы UART для чипа 16C554. Они имеют 16-байтовые FIFO и являются счетверенными устройствами, поэтому имеется четыре порта. Я использовал скорость передачи данных точно так же, как вы упомянули. И все это хорошо работало с очень старым и медленным микропроцессором (4,77 МГц 8088). Никогда не терялось ни одного байта... никогда. Все просто работает. Если у вас есть проблема, то она либо скрыта в каком-то коде, который вы не писали (библиотечная процедура, о которой вы делаете неверные предположения), либо ошибка в том, что вы пишете. Если ваши внешние устройства работают, нет другого способа увидеть это.
@MasoodSalik ЕДИНСТВЕННЫЙ способ, которым 20-байтовый кадр вызывает проблемы, - это если вы ОЧЕНЬ ЛЕНЬ в своем кодировании и заставляете свое программное обеспечение сидеть и ничего не делать, пока происходит общение. Скорее, ваше программное обеспечение должно быть активным в удалении байтов из FIFO (и есть несколько способов приблизиться к этому: начать очистку при «полузаполненном» — это только один пример) во время обмена данными. Я ничего не знаю о вашем TM4C123GXL, но порты с поддержкой FIFO почти всегда имеют разные «уровни срабатывания», которые вы можете выбрать в зависимости от ситуации (первый вошел, наполовину заполненный или полный, чтобы назвать три). Используйте один.
@MasoodSalik См. раздел «14.3.8 Операция FIFO» в техническом описании TM4C123GXL . Примечание: (1) « После сброса оба FIFO отключены » и (2) « FIFO могут быть индивидуально настроены для запуска прерываний на разных уровнях. Доступные конфигурации включают ⅛, ¼, ½, ¾ и ⅞ ». Для приема , я бы, наверное, выбрал ½ или ¾, в зависимости от. Вам действительно нужно прочитать техническое описание и получить контроль над кодировкой вашего приемника. Ваши внешние устройства и микроконтроллер TI в порядке. Вы используете этот сайт, чтобы не читать и не думать самостоятельно?
Я обновил вопрос с архитектурой программного обеспечения. Служба периодических прерываний считывает и передает кадр. @jonk Я просмотрел техническое описание TM4C123GXL. Если мы используем Interrupt для получения байтов, мы можем определить уровень Interrupt Fifo. Я также настроил uC с помощью прерывания, но проблема в том, что вызывалась подпрограмма непрерывного прерывания, хотя на последовательной шине не было данных.
@MasoodSalik Это должно работать, если у вас правильно написан код. У меня нет вопросов по этому поводу. Я делал подобные вещи так много раз в прошлом. Я уверен, что устройство ASIC (MCU) спроектировано правильно, чтобы программисты могли делать это должным образом. Это не та ошибка, которую совершают разработчики ИС. Вы, вероятно, думаете, что прерывание, которое вы получаете, связано, когда оно поступает по другой причине, с которой вы не справляетесь должным образом. Платы стоят всего 13 долларов США, и я заказал одну вчера. Это будет легко проверить. Но придется подождать несколько недель.
Я был на нем в течение 4 месяцев. Пробовали разные варианты, но не получилось. В даташите указано, что UART похож на 16C550. На странице en.wikipedia.org/wiki/16550_UART говорится: «В исходном 16550 была ошибка, которая препятствовала использованию этого FIFO. Позднее NS выпустила 16550A, в котором эта проблема была исправлена. Однако не все производители приняли эту номенклатуру, продолжая ссылаться на фиксированный чип как 16550. Согласно другому источнику, проблема с FIFO была исправлена ​​только в модели 16550AF, а модель A все еще содержала ошибки (согласно этому источнику, модели C и CF тоже в порядке).
@MasoodSalik Конечно, в прошлом мне приходилось сталкиваться с множеством кремниевых ошибок, в том числе с некоторыми из 16550-х. Но ничего из этого здесь не применимо. Это современное устройство, основанное на многолетнем опыте. Я почти уверен, что это работает так, как задокументировано. Но я узнаю достаточно скоро. Вы также можете найти опечатки TI на устройстве. Дважды проверьте, что (поскольку мне скоро придется это сделать). TI обычно НЕ исправляет ошибки кремния, а вместо этого документирует их. (Микрочип на самом деле часто исправляет их.)
@MasoodSalik Начните здесь: Микроконтроллеры Tiva C Series TM4C123x, Silicon Revisions 6 и 7, Silicon Errata . Найдите «Uart» и посмотрите, что вы там найдете. Также не помешает просмотреть ошибки ARM: ARM Cortex-M4F Errata (v3) . Но вряд ли это сильно поможет, поскольку речь идет о процессоре.
@MasoodSalik Если это еще не ясно, вы должны провести исследование на любом устройстве. В данном случае это означает изучение ошибок MCU (от ARM), ошибок ASIC (от TI) и ошибок LaunchPad (также от TI, но находится отдельно в документации на плате). Вы также должны внимательно изучить все подробности, касающиеся настройки порта, таймеров и почти всего, что можно найти в почти 1500-страничном описании. Ты говоришь, что провел с этим 4 месяца. Я бы потратил первую неделю (или две) перед тем, как написать ОДНУ СТРОКУ кода, тщательно изучая эти документы. Ты сделал это?
Однако в этом случае может показаться, что виновато плохое программирование. Это не то, как вы пишете ISR - слишком много кода, задержка прерывания будет ужасной. А затем очевидные ошибки, которые можно найти за 5 секунд, такие как постоянное отключение прерывания. Я также могу в значительной степени гарантировать, что у вас есть недостающие volatileошибки повсюду. Форма mainused относится к эпохе динозавров, что говорит о том, что используется плохой компилятор. И так далее.
@jonk Я просмотрел документы, которые вы перечислили. Есть кремниевая ошибка, которая не связана со мной. Я не просматривал таблицы данных, так как использую периферийную библиотеку драйверов Tiva Ware, которая автоматически обрабатывает программирование на уровне регистров. А я работаю раз в неделю. Лундин: Понял, что нельзя писать длинную подпрограмму в ISR.
@MasoodSalik Рад услышать обновление. Спасибо. Я уверен, что использую микроконтроллеры задолго до вашего рождения (уже около 40 лет). В то время я часто писал код для обработки нескольких 16-байтовых каналов FIFO — внешних и внутренних по отношению к микроконтроллерам. . Иногда к ним относятся кремниевые жучки. В каждом отдельном случае мне удавалось заставить их работать безупречно. Но я написал свой собственный код, провел собственное исследование и работу, и, используя эту информацию, разработал соответствующий код. Если вы используете библиотеку, то вы зависите от работы других, и если есть проблема, вы должны связаться с ними, я полагаю.
Да. Конечно. TivaWare™ от Texas Instruments разработан для упрощения и ускорения процесса разработки, поэтому я использую его. В моей программной архитектуре были ошибки. Теперь я установил успешную связь между двумя устройствами. Скоро будут эксперименты с несколькими устройствами.
@MasoodSalik Рад слышать. Программное обеспечение, предназначенное для того, чтобы помочь людям «быстро приступить к работе», часто не является лучшим программным обеспечением, а просто чем-то, созданным для того, чтобы помочь их представителям на местах, работающим с клиентами, быстро показать клиенту, что проблема заключается в программном обеспечении клиента, а не в оборудовании производителя. Часто это не решает ничего, кроме самых тривиальных потребностей конечного использования. Я никогда не использую такое программное обеспечение, кроме как для перекрестной проверки, зная, что реальная ценность от него будет очень ограниченной. И да, обычно дело в ошибках в том, что вы пишете. Это был мой опыт.
@MasoodSalik Обычно я разбиваю код драйвера на четыре части с двумя буферами в качестве посредника. Есть драйвер приемника, состоящий из высокоуровневого интерфейса для остального кода и низкоуровневого интерфейса для оборудования с буфером между ними. И та же концепция, повторенная, для передающей стороны. Буферы отделяют использование более высокого уровня от аппаратной поддержки более низкого уровня. Вы можете добавить управление пулом буферов, если хотите посложнее. Либо поставлять статически выделенные буферы на постоянной основе.

Ответы (1)

Дизайн вашего программного обеспечения не очень хорош и, вероятно, является причиной потери входящих байтов.

Во-первых, я не уверен, ошибка это или опечатка. Вы отключаете прерывания в начале Timer1IntHandler(), но затем вы снова разрешаете прерывания только в том случае, если Correctэто правда. Разве вы не хотите повторно разрешать прерывания перед возвратом независимо от условного? Кажется странным, что прерывания можно оставить отключенными, когда функция возвращается.

Похоже, ваш код читает символы из UART1 только внутри ReadJoystick()функции. И я подозреваю, что UART1 не читается, пока вызываются все эти функции от GenerateServoCardsFrame()до . SendJoystickFrame()Сколько времени требуется для запуска этих функций? Могут ли эти функции занять достаточно много времени, чтобы FIFO UART1 заполнился и переполнился? Это может быть, когда входящие байты отбрасываются.

Если бы я разрабатывал это программное обеспечение, я бы реализовал его совершенно иначе, чем то, что сделали вы. Я бы включил запрос прерывания UART и создал бы быструю процедуру обработки прерывания UART. Единственное, что будет делать UART ISR, это копировать байты в/из регистров UART TX/RX. Я бы создал два круговых (кольцевых) буфера для хранения байтов. UART ISR скопирует полученный байт из регистра RX UART в циклический буфер RX. И UART ISR скопирует байт для передачи из циклического буфера TX в регистр TX UART. UART ISR не будет пытаться интерпретировать значение любого из байтов. Все, что он делает, это перемещает байты между буферами RAM и периферийным устройством UART. Это делает ISR UART коротким, что позволяет программе в целом лучше реагировать на другие прерывания.

Затем я бы создал main()функцию с бесконечным циклом, и внутри бесконечного цикла я бы вызвал функцию, вызываемую SerialReceive()для чтения сообщений из буфера RX. SerialReceive()будет реализован как конечный автомат. Если какие-либо байты доступны в буфере RX, то он будет обрабатывать конечное число байтов через конечный автомат. Конечный автомат будет иметь состояния для заголовка кадра, тела и трейлера, подобные тому, что вы сделали. SerialReceive()немедленно возвращается, когда либо сообщение завершено, либо больше нет доступных байтов. Когда сообщение является неполным из-за того, что больше нет доступных байтов, SerialReceive()оно не будет ждать их, вместо этого оно запомнит текущее состояние и сообщение, чтобы продолжить с тем же сообщением при повторном вызове из main().

Если вам нужно что-то делать периодически, настройте таймер, как вы сделали, но вместо того, чтобы выполнять всю работу в ISR таймера, просто вместо этого установите флаг. Основной бесконечный цикл должен неоднократно проверять флаг и делать все необходимое, когда флаг установлен ISR таймера. Выполнение работы из контекста означает main(), что система может реагировать на другие прерывания во время выполнения работы.

Короткие ISR позволяют системе в целом лучше реагировать на другие запросы прерывания. Если вы проводите слишком много времени в ISR, как я полагаю, вы делаете в своем ISR с таймером, тогда система не будет реагировать.

Обновление: в своем комментарии вы говорите, что эти функции зацикливаются до завершения передачи, и они занимают более 7 миллисекунд. Этого времени достаточно, чтобы 80 байт поступили на UART, и ваш код не считывает эти байты в течение этого времени, поэтому, конечно, байты будут потеряны.

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

Буфер RX и TX должен быть больше любого сообщения. Обычно размер буферов равен степени двойки, потому что это позволяет легко обвести указатели буферов назад к началу. Так что сделайте их 256 байт (или 128 или 64, но почему бы не больше?).

У вас должен быть независимый набор буферов RX/TX для каждого UART.

Изменение периода вашего периодического таймера ISR не повлияет на проблему с вашим исходным кодом. В вашем периодическом ISR ваш код тратит 7 миллисекунд, НЕ читая UART. Ваш код потеряет байты независимо от периода таймера.

Конечная скобка была опечаткой при написании псевдокода. Мои функции передачи UART сделаны таким образом, что пока все байты не будут отправлены, он будет оставаться в цикле. Таким образом, для отправки 20 байт это стоит 1,74 мс. Все функции в цикле «если» занимают 7,11 мс. Должен ли я увеличить периодичность ISR? Я успешно внедрил циклический буфер для хранения полученных байтов UART с помощью прерывания. И проверка флага в цикле while в main(). Он отлично работает с одним устройством. Скоро буду экспериментировать с 2-мя устройствами. Какой размер кольцевого буфера я должен установить? 2x байта данных?
Должен ли размер кругового буфера быть вдвое больше фактического кадра?
@MasoodSalik, я обновил свой ответ, чтобы учесть ваши комментарии.
Существует ли какая-либо концепция, согласно которой, как только я вызываю функцию, функция вызывает себя через некоторое время (вычисленное), пока не будет достигнуто какое-то условие; без остановки системы. Я хотел реализовать его для функции передачи. Как только я заполняю Tx Fifo, функция снова вызывает себя через некоторое время и пополняет FIFO до тех пор, пока не будет отправлен весь кадр.
@MasoodSalik, ты думаешь об этом неправильно. Вместо того, чтобы вычислять, когда вы можете пополнить запасы, пусть UART сообщит вам, когда вы можете пополнить запасы. В частности, включите соответствующий IRQ UART, чтобы ISR UART вызывался, когда байты могут быть добавлены в FIFO. Честно говоря, я не думаю, что вам следует пытаться использовать UART FIFO, пока вы не заработаете без FIFO. И я настоятельно рекомендую вам посмотреть пример кода в Интернете. Мой ответ представляет собой обзор и не включает важные детали, которые могут сбить вас с толку.