Дуплексный UART на таймере Timer_A

Привет всем поклонникам MSP430!

Заметил в этом блоге статью по реализации UART'а на таймере Timer_A и решил поделиться своим опытом. Я работаю с MSP430 уже довольно давно и знаю «из первых рук», что отсутствие аппаратного UART'а в серии Value Line этого семейства значительно усложняет работу с этим чипом, особенно для новичков. Так что тема действительно актуальная, даже для более продвинутых чипов семейства MSP430, поскольку лишнего UART'a, как мы знаем, не бывает, а Timer_A есть всегда.

Использование таймера для эмуляции UART'а — идея не новая. Вопрос в том как использовать возможности конкретного таймера наиболее эффективно. Предлагаемое решение позволяет реализовать дуплексный UART со скоростью приёма-передачи 9600 bps даже при тактовой частоте микроконтроллера 1 MHz, используя встроенный калиброваный генератор тактовой частоты (DCO), т.е. без использования 32 kHz кварца или кристалла.

Модуль таймера Timer_A присутсвует во всех версиях MSP430, но отличается количеством регистров захвата/сравнения (CCR — Capture/Compare Register). В простейших чипах типа G2231 их два, в более продвинутых — три. Для реализации дуплексного UART'а нам необходимо два регистра — один для приёмника и один для передатчика. Таким образом, даже простейший MSP430 можно снабдить дуплексным UART'ом.

Для начала — небольшой экскурс в теорию. При асинхронной последавательной передаче данных приёмник и передатчик тактируются своими собственными, независимыми генераторами тактовой частоты. Для их всаимной синхронизации используется страртовый бит, с которого начинается передача каждого символа. При обнаружении стартового бита, приёмник запусткает свой тактовый генератор и начинает сэмплирование сигнала данных RXD. Сэмплирование должно осуществляться как можное ближе к середине битового интервала (BITTIME). То есть первое сэмплирование осуществлятся через полтора битовых интервала после фронта стартового бита, а каждое последующее — через один интервал. В этом случае безошибочный приём данных будет обеспечен даже при взаимном расхождении тактовых частот приёмника и передатчика до 5%, так как суммарная погрешность при семплировании 8-битного сивола не превысит половины битового интервала.

В простейших реализациях программого UART'а для тактирования приёмника и передатчика используется прерывание по таймеру. Обработчик прерывания приёмнка затем считывает бит из порта данных RXD и добавляет его в сдвиговый регистр приёмника, а обработчик прерывания передатчика записывает бит из сдвигового регистра передатчика в порт данныз TXD. Такая схема однако не учитывает задержек, возникающих при обработке прерывания. Например, если в момент возникновения прерывани таймера процессор уже находился в режиме обработки прерывания от другого источника, то обработка прерывания таймера будет отложена до завершения текущего прерывания. В общем случае эти задержки имеют случайных характер и проявляются в нестабильности частоты сэмплированя UART'а. В результате такой постейший UART работает достаточно хорошо только при низких скоростях обмена данными и низкой загрузке процессора.

Значительно лучших результатов можно добиться используя аппаратные возможности таймера Timer_A для сэмплирования сигнала данных приёмника RXD и формирования сигнала данных передатчика TXD.

Реализация приёмника UART
Для реализации приёмника используется один из регистров захвата/сравнения, например CCR1. В режиме ожидания стартового бита (Idle mode) таймер находится в режиме «захвата». В этом режиме текущее состояние счётчика таймера (регистр TAR) копируется в регистр захвата (CCR1) по фронту сигнала на входе CCI1A. Этот вход и является входом данных нашего UART'a (RXD). Поскольку запоминание значения счётчика происходит на аппаратном уровне, оно не подвержено влиянию задержек обработки прерываний и служит начальной фазой нашего генератора тактовой частоты приёмника.

В процессе обработки первого прерывания (стартовый бит) таймер переводится из режима захвата в режим сравнения. В этом режиме происходит обратная процедура: когда текущий счётчик достигает значения установленного в регистре CCR1, происходит сэмплирование сигнала на входе CCI1A и его значение запоминается в бите SCCI регистра CCTL1. Поскольку сэмплирование производится аппаратно, оно опять-таки не подвержено влиянию программных задержек.

Таким образом, мы посностью устраняем вслияние программных задержек и добиваемся максимальной стабильности тактового генератора приёмника. Исходный текст обработчика прерываний приёмника представлен ниже.


#pragma vector = TIMERA1_VECTOR
__interrupt void timerA1_isr(void)
{
    static unsigned char rx_bitc = 8;   // счётсчик битов приёмника
    static unsigned char rx_data;       // регистр данных приёмника

    // первым делом готовим CCR к приёму следующего бита
    TACCR1 += UART_BITTIME;

    // это стартовый бит?
    if (TACCTL1 & CAP) {
        // да, переводим таймер в режим сравнения
        BIT_CLR(TACCTL1, CAP);
        // полтора битовых интервала до первого бита данных
        TACCR1 += UART_HALF_BT;
    } else {
        // ... нет, обрабатываем очередной бит данных:
        // сдвигаем слово данных вправо (ноль в старший бит)
        rx_data >>= 1;
        // если была принята "1", то корректируем старший бит
        if (TACCTL1 & SCCI) {
            rx_data |= 0x80;
        }

        // все биты получены?
        if (--rx_bitc == 0) {
            // да, переключаем таймер в режим захвата
            BIT_SET(TACCTL1, CAP);
            // сохраняем принятый символ в буфере приёмника
            uart_rx = rx_data;
            // сбрасываем счётчик битов
            rx_bitc = 8;
            // пробуждаем процессор
            __bic_SR_register_on_exit(LPM0_bits);
        }
    }
}


Реализация передатчика UART
Для реализации передатчика используется второй регистр захвата/сравнения, в данном случае CCR0. Для устранения влияния программных задердек на формируемый сигнал используются возможности выходного модуля (Output Unit) таймера Timer_A. Этот модуль позвляет переключать выходной сигнал OUTx в момент когда значение текущего счётчика достигает значения установленного в регистре CCR0. Исходный текст обработчика прерываний передатчика представлен ниже, а его подробный анализ оставляется в качестве упражнения для заинтересованного читателя.


#pragma vector=TIMERA0_VECTOR
__interrupt void timerA0_isr(void)
{
    static unsigned char tx_bitc = 10;  // счёчик битов передатчика

    // передача символа завершена?
    if (tx_bitc--) {
        // нет, подготовить CCR0 к передаче следующего бита
        TACCR0 += UART_BITTIME;

        if (tx_data & 0x01) {
            // бит=1, используем режим 1 (Set) выходного модуля
            BIT_CLR(TACCTL0, OUTMOD_6);
        } else {
            // бит=0, используем режим 5 (Reset) выходного модуля
            BIT_SET(TACCTL0, OUTMOD_5);
        }

        // сдвигаем регистр данных вправо
        tx_data >>= 1;
    } else {
        // ... да, запрещаем прерывание и устанавливаем OUT0 в "1"
        TACCTL0 = OUT;

        // сбрасываем счётчик битов
        tx_bitc = 10;
    }
}


Полный текст демонстрационной программы для LaunchPad представлен в приложении. Для компиляции программы используется система разработки IAR Kicksart.
  • +6
  • 29 января 2012, 23:49
  • geko
  • 1
Файлы в топике: uartdemo.zip

Комментарии (17)

RSS свернуть / развернуть
Извините — не совсем понял как фиксируете условие старт для приема данных на RX?
0
  • avatar
  • Zov
  • 30 января 2012, 11:00
по фронту в режиме capture.
0
А как это по фронту?
Ведь в состоянии ПАУЗА на линии RX линия удерживается в «1»?
Тоесть — фиксировать старт необходимо по спаду
______
пауза |_________|
0
  • avatar
  • Zov
  • 30 января 2012, 11:52
спад — это и есть «задний» или «падающий» фронт сигнала.
0
Просто всегда оперировал понятиями по «фронту» — нарастающий, и по «спаду».
-1
  • avatar
  • Zov
  • 30 января 2012, 13:33
Гут. Подробное объяснение принципа работы — это как раз то, чего не хватало моим статьям. А так идея у меня та же. Только у меня полудуплексный, чтобы один обработчик таймера оставить позьзователю — полнодуплексный отбирает таймер целиком. :)
0
  • avatar
  • _YS_
  • 30 января 2012, 13:57
Спасибо за детальное описание.
При тестировании программы возникла проблема — по одному байту контроллер принимает данные без проблем, но когда отправляю строку получается черти что. Можешь подсказать в чем проблема.
0
В примере программы отсутствует буферизация входных данных на уровне прерывания. Возможно причина в этом.
0
Хорошая статья но возник один нубский вопрос.
А возможно ли после доработки кода реализовать данный проект на экспериментальной плате MSP430F5438 Experimenter Board используя имеющийся там разъем USB для связи с ПК?
И если не возможно, то идею какого другого проект с использованием этой платы вы могли бы предложить новичку?

З.Ы. К сожалению, сейчас лежу с гипсом и кроме этой экспериментальной платки ничего не имею, а время хотелось бы с толком провести)
0
а зачем? на F5438 есть аппартный UART
плата мощная, есть чем поиграться, например FFT звука в реальном времени
0
Вы правы, почитав даташит тоже разобрался.
FTT по примерам от TI, тоже худо бедно освоил.
Решил теперь парсер написать, чтобы можно было подключать GPS модуль и выводить измеренные данные на встроенный экранчик на плате.
0
А проводилась ли проверка, что дуплекс действительно работает? Проверить это просто, например, не нажимая клавишу одну за одной в терминале, а сделав в него copy&paste — чтобы символы шли не по одному, а пакетом. MSP430 их будет принимать и сразу слать эхо, вот и получится реальный дуплекс. Так вот, у меня не получается. На 9600 даже два символа вставить нельзя — идет месиво. На 4800 получше, но при вставке 7-8 символов (и авто-повторе) начинают теряться отдельные символы:

>Error: bffer oveflow!
You typed: Error: bffer oveflow!
>Error: bufer overlow!
You typed: Error: bufer overlow!

И даже на 2400 при вставке добиться потери символов не так уж сложно.
0
Этот вопрос уже возникал. Ещё раз поясню: драйвер поддерживает дуплексный режим, т.е. приём по линии RxD и передача по TxD происходят одновременно и независимо друг от друга (в отличие от полу-дуплексного драйвера, который находится либо в режиме приёма, либо в режиме передачи). Максимальная скорость зависит от тактовой частоты процессора. Это связано с тем, что драйвер — программно/аппаратный. В часности, прерывание как приёмника, так и передатчика происходит на каждый бит. Если длительность обработки прерывания превышает допустимый предел (грубо говоря, около 1/2 бита), то вероятность потери бита либо при приёме, либо при передаче резко возрастает. Это реальный предел возможностей реализации прогаммного UART'а. При тактоворй частоте процессора 16 MHz драйвер позволяет принимать/передавать данные в дуплексном режиме до 19200 bps, а при частоте 1 MHz — 2400 bps. Моё утверждение, что драйвер поддерживает 9600 bps при частоте 1 MHz относится к режиму терминала, который как Вы правильно заметили, близок к полу-дуплексному.
0
Спасибо за быстрый ответ.

> Этот вопрос уже возникал. Ещё раз поясню:

Возникал где? Если можете, дайте пожалуйста ссылку, потому что все реализации «дуплексного» режима, что я просмотрел, основываются на референсном коде от TI, все имеют тут же проблему и нигде на нее не жалуются (из чего я делаю вывод, что никто серьезно не тестировал).

Те соотношения тактовый частот и бод — на чем они основаны? Мои текущие замеры interrupt latency для приема (замеряется как значение регистра TAR на входе в обработчик минус TACCR1) показывают, что в момент overrun'а задержка составляет чуть больше значения тактов/бит — например, 109 против 104. Т.е., буквально еще чуть-чуть — и получится. Можно переписать на асме например.

Но прежде чем это делать — неплохо бы понять, а что вообще может задерживать обработчик на более 100 тактов. У меня (использую mspgcc) длина обработчика — 136 байт, или 68 слов. Если предположить что по-RISC'овому каждое командное слово выполняется за такт, то никаких проблем быть не должно (обработчик передачи еще короче).

Но вот дорвался наконец-то до таблиц растактовки — вижу, что 2-х словная команда может и 5 тактов выполняться. Наверное, можно таки объяснить потерю 100+ тактов, особенно учитывая неидеальное распределение TXD/RXD в LaunchPad с учетом приоритета прерываний — надо было их наоборот сделать, чтоб таймер RXD имел более высокий приоритет. А так, если обработчик приема уже поздновато вызвался для текущего бита, потом еще передача обогнала, то на следующий бит приема точно не успеет.
0
> (грубо говоря, около 1/2 бита)

Есть обоснование этой цифры? Мы нигде ничего не ждем на промежуток в 1/2 бита, только на 1 и 1.5 бита. Так что по логике суммарная длительность обработчиков приема и передачи (которые вызываются поочередно) дожна быть меньше периода оного бита.

Добавим немного математики: 1) в обработчике мы не сразу берем значение принятого бита, теоретически прерывание может быть вызвано для старого бита, а к тому времени как мы прочтем, это уже будет новый; 2) частоты передатчика и приемника синхронизированы не идеально (например, принимаем на 9615 бод). Возьмем с запасом и скажем, что суммарная длительность обработчиков должна быть не более 80% длительности бита. Итого, 83 такта.

Я переписал обработчик приема на ассемблере — 44 такта при приеме «среднего» бита. С постоянными overrrun'ами существенно помогло — теперь бОльшая часть передается в дуплексе без них. Однако они все равно регулярно случаются, в процентах десяти.

Посчитал сколько тактов gcc нагенерил для обработчика передачи — 60. Ну что ж, сейчас суммарная длительность как раз равна длительности бита (+ до 5 тактов задержки на завершение выполнения текущей инструкции, при которой происходит прерывание).

Надо переписать и передачу на асме. Хочется попринимать ставки — поможет или нет ;-D. Может ли в 10% случаев 2+2 быть 5? Может ли в 10% случаев сила тяжести действовать не вниз, а в сторону?
0
Вы правы. Даже не учитывая расхождения тактовых частот приёмника и передатчика (т.е. в идеальном случае), для обеспечения дуплексной работы UART'а необходимо выполнение следующего условия:

суммарная длительность ВСЕХ обработчиков прерываний не должна превышать длительности одного бита

Отсюда и «грубая» оценка, что длительность обработки прерывания как приёмника, так и передатчика не должна превышать 1/2 бита (преполагая, что в системе только два источника прерываний и длительность их обработки примерно одинакова).

При скорости 9600 bps (битовый интервал ~104 us) и тактовой частоте 1 MHz (тактовый интервал 1 us) в вашем распоряжении имеется 104 такта на обработку ВСЕХ прерываний.

Предположим, что оба прерывания произошли одновременно. Прерывание передатчика, имея более высокий приоритет, начнёт обрабатываться первым. Допустим обработка заняла 60 тактов. Таким образом, для обработки прерывания приёмника осталось 44 такта. Если Вы не успели записать новое значение в регистр TACCR, потеря бита неизбежна.

В своих вычислениях учтите также 6 тактов interrupt latency (см. MSP430x2xx Family User's Guide, раздел 2.2.3.1 Interrupt Acceptance).
0
Отлично! Это то, что нужно!
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.