Программный контроллер интерфейса на STM32

    Проблема обратной совместимости, вероятнее всего, будет всегда.

    В области разработки электроники порой приходится поддерживать устройства 30-летней давности (а иногда и старше).

    В таких аппаратах иногда всё собрано на логике, без каких-либо программируемых элементов.
    Кроме того, в старой технике существуют доморощенные интерфейсы, которые не реализуются какими-либо серийно выпускаемыми контроллерами.

    В таких случаях совместимые контроллеры приходится реализовывать на CPLD\FPGA\ASIC.

    Я подумал, что можно обойтись без указанных выше средств, а именно реализовать контроллер интерфейса программно на микроконтроллере серии STM32F4.

    Основная идея заключается в использовании связки TIM+DMA+GPIO:

    • Таймер настраивается на требуемую частоту и генерирует запросы для DMA
    • DMA по запросам таймера перекладывает данные из памяти в регистры GPIO
    • В результате на линиях GPIO с нужной частотой выставляются нужные значения

    Ограничения STM32:

    • К регистрам GPIO имеет доступ только DMA2, а запросы к DMA2 умеют генерировать только TIM1 и TIM8.
    • Транзакция DMA из памяти в регистры периферии или обратно занимает около 10-12 тактов шины (зависит от кучи условий, описанных в Application note AN4031).

    Таким образом, максимум для данного решения — 16 линий с частотой порядка 12-14 МГц.

    Для проверки жизнеспособности идеи был выбран интерфейс MIL-STD 1573 (известный у нас как МКО).

    Интерфейс представляет собой дифференциальную пару с кодом Манчестер-2 — на каждый бит (занимающий 1 мкс) приходится переход сигнала из 0 в 1 либо обратно, то есть 2 уровня (значение бита определяется не уровнем сигнала, а направлением его перепада).

    Данные передаются 16-битными словами + 1 бит чётности + 3 бита синхросигнала (1,5 + 1,5 бита на разных уровнях), итого 20 мкс.

    Тактовая частота — 2 МГц, теоретическая полезная пропускная способность — чуть менее 1 Мбит/с (около 0,8).



    Идеологически это шина Master-Slave, инициатором обмена всегда является Master, требования ко времени реакции устройств — порядка единиц микросекунд.

    Обмен всегда подразумевает «Запрос — Ответ»

    Ниже для наглядности показана осциллограмма обмена между устройством, имеющим отечественный МКО-контроллер с двухполярным питанием ±15 вольт, и получившимся в результате программным контроллером.

    Это запрос Master'ом пакета и ответ Slave'а на данный запрос (видно только первое слово пакета и синхросигнал второго слова, за которым ещё 30 слов).



    Как видно, уровни напряжения отличаются почти в 2 раза (оба укладываются в ГОСТ), но временные характеристики сигналов одинаковые. Устройства успешно «понимают» друг друга.

    Поскольку мы реализуем физический интерфейс, имеются весьма жёсткие требования к реалтайму.

    Потребуется делать много вещей внутри прерываний, причём очень быстро (не более единиц микросекунд, то есть до 1000 тактов).

    Кроме того, прерывания, относящиеся к физическому интерфейсу, должны иметь наивысший приоритет и вытеснять всё остальное.

    Необходимо реализовать механизмы синхронизации и калибровки (на начальном этапе).

    Мной была выбрана следующая конфигурация:

    Передача: TIM1(2 МГц) + DMA2_Stream5_DMA_CHANNEL_6 + GPIOD->ODR (PD0 — прямой сигнал дифференциальной пары; PD1 — инверсный (можно было бы обойтись одним выходом+инвертор)).
    Массив в памяти заполняется согласно протоколу МКО, затем запускается таймер+DMA, которые выпуливают массив из памяти (по одному байту) на ноги GPIO. В конце дополнительно выпуливается 0, чтобы задавить линию.

    Приём: TIM8(1 Мгц) + DMA2_Stream1_DMA_CHANNEL_7 + GPIOB->IDR (PB6 — прямой сигнал дифференциальной пары; инверсный вход не используется совсем).

    На ноге GPIO настраивается прерывание по изменению входного уровня (это значит, что в канале кто-то что-то начал передавать, пора начинать слушать).

    По срабатыванию прерывания запускается таймер+DMA (на максимально возможную длину пакета в МКО), которые собирают с ноги GPIO уровни (по одному байту) в массив в памяти. Массив позднее анализируется.

    Для приёма также введён вспомогательный таймер TIM2 (1/20 МГц — соответствует длительности одного слова в МКО).

    Он используется для обработки первого принятого из канала слова (по которому принимается решение, прекращать ли приём и выходить в передачу, или же принимать пакет дальше).

    Если после анализа первого слова приём продолжился, этот же таймер останавливает таймер+DMA после приёма количества слов, указанного в первом принятом слове.

    Также, после срабатывания этого таймера пуляется ответное слово (или целый ответный пакет)

    После приёма данных и остановки таймера+DMA выставляется packet_received_length, который означает, что есть принятые данные и их надо распарсить и отправить наверх.

    На время передачи приём отключается (прерывание на ноге-уловителе отключено)

    Примерно прикинув архитектуру, я взялся за реализацию.

    Поскольку я буду работать с регистрами GPIO по одному байту, а полезных там 1 или 2 бита, мне нужно уметь преобразовывать полезные данные в то, что будет передано с помощью DMA в GPIO, и обратно.

    Сначала мне потребовалось немножко макросов для удобной работы с форматом слов в МКО:

    #define MKO_RX_GPIO_OFFSET  6 //PB6
    #define MKO_RX_1  (long long)(1 << MKO_RX_GPIO_OFFSET)
    #define MKO_RX_BYTE_MASK  ((MKO_RX_1 << 56) + (MKO_RX_1 << 48) + (MKO_RX_1 << 40) + (MKO_RX_1 << 32) + (MKO_RX_1 << 24) + (MKO_RX_1 << 16) + (MKO_RX_1 << 8) + MKO_RX_1)
    
    #define MKO_LOW   (long long)(2)
    #define MKO_HIGH  (long long)(1)
    
    #define MKO_0 ((MKO_HIGH << 8) + MKO_LOW)
    #define MKO_1 ((MKO_LOW << 8) + MKO_HIGH)
    
    #define MKO_0x0 ((MKO_0 << 48) + (MKO_0 << 32) + (MKO_0 << 16) + MKO_0)
    #define MKO_0x1 ((MKO_1 << 48) + (MKO_0 << 32) + (MKO_0 << 16) + MKO_0)
    .
    .
    #define MKO_0xF ((MKO_1 << 48) + (MKO_1 << 32) + (MKO_1 << 16) + MKO_1)
    
    #define CMD_ACK_WORD  ((MKO_LOW << 56) + (MKO_LOW << 48) + (MKO_LOW << 40) + (MKO_HIGH << 32) + (MKO_HIGH << 24) + (MKO_HIGH << 16))
    #define DATA_WORD     ((MKO_HIGH << 56) + (MKO_HIGH << 48) + (MKO_HIGH << 40) + (MKO_LOW << 32) + (MKO_LOW << 24) + (MKO_LOW << 16))
    
    const unsigned long long mko_tetrades[16] = {MKO_0x0, MKO_0x1, MKO_0x2, MKO_0x3, MKO_0x4, MKO_0x5, MKO_0x6, MKO_0x7, MKO_0x8, MKO_0x9, MKO_0xA, MKO_0xB, MKO_0xC, MKO_0xD, MKO_0xE, MKO_0xF};
    

    Также мне нужны были функции упаковки\распаковки данных:

    void short_to_mko(unsigned char* data, unsigned int start_pos, unsigned int command_word, unsigned int input_data)
    {
      if (command_word)
        *(long long*)&data[start_pos] |= CMD_ACK_WORD;
      else
        *(long long*)&data[start_pos] |= DATA_WORD;
    
      *(long long *)&data[start_pos +  8] = mko_tetrades[(input_data >> 12) & 0xf];
      *(long long *)&data[start_pos + 16] = mko_tetrades[(input_data >> 8) & 0xf];
      *(long long *)&data[start_pos + 24] = mko_tetrades[(input_data >> 4) & 0xf];
      *(long long *)&data[start_pos + 32] = mko_tetrades[(input_data >> 0) & 0xf];
    
      input_data -= (input_data >> 1) & 0x5555;
      input_data = ((input_data >> 2) & 0x3333) + (input_data & 0x3333);
      input_data = ((input_data >> 4) + input_data) & 0x0f0f;
      input_data = ((input_data >> 8) + input_data) & 0x00ff;
        
      if (input_data & 1)
        *(unsigned int*)&data[start_pos + 40] = MKO_0;
      else
        *(unsigned int*)&data[start_pos + 40] = MKO_1;
    }
    
    unsigned int mko_to_short(unsigned char* data, unsigned int start_pos)
    {
      unsigned int output_data;
      long long byte;
      unsigned int crc;
      
      byte = (*(long long *)&data[start_pos]) & MKO_RX_BYTE_MASK;
      output_data = MKO_RX_BYTE_PACK(byte);
      output_data <<= 8;
      byte = (*(long long *)&data[start_pos + 8]) & MKO_RX_BYTE_MASK;
      output_data |= MKO_RX_BYTE_PACK(byte) & 0xff;
      crc = output_data & 0xffff;
      
      crc -= (crc >> 1) & 0x5555;
      crc = ((crc >> 2) & 0x3333) + (crc & 0x3333);
      crc = ((crc >> 4) + crc) & 0x0f0f;
      crc = ((crc >> 8) + crc) & 0x00ff;
      
      if ((crc & 1) == (data[start_pos + 16] >> MKO_RX_GPIO_OFFSET))
        packet_error = 1;
      
      return output_data & 0xffff;
    }

    Теперь можно браться непосредственно за контроллерную часть.

    Уловитель фронта принимаемого сигнала, зарядка приёмного DMA и таймера TIM2 (вспомогательного, для определения количества принятого)

    void mko_start_receive()
    {
      unsigned int i;
      
      (EXTI->IMR) &= (~RX_START_INT);//выключаем прерывание (не ловим, если уже поймали (включим после окончания приёма)
          
      //заряжаем DMA на приём максимально возможной последовательности (если надо - потом на лету остановим в прерывании таймера TIM2)
      htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->NDTR = 660;
      htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->CR |= DMA_IT_TC;
      htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->CR |= DMA_SxCR_EN;
    
      i = 50;
      while(i--);//сдвигаем запуск DMA более чем на 1 период (около 1.5 мкс)
    
      TIM8->CNT = 80;//калибровка (внутри одного периода) приёмного DMA (куда попадают отсчёты - сейчас на 250 нс (середина отсчёта))
    
      __HAL_TIM_ENABLE_DMA(&htim8, TIM_DMA_UPDATE);
      __HAL_TIM_ENABLE(&htim8);
       
      TIM2->SR = 0;//дабы не генерировалось прерывание сразу после запуска таймера
      TIM2->ARR = 1400 - 1;//заряжаем таймер на 20 мкс (1 слово) (чтобы проанализировать первое приянтое и решить, что делать далее)
      TIM2->CNT = 0;
      HAL_TIM_Base_Start_IT(&htim2);//заряжаем на 20 мкс (1 слово)
      
    }
    
    void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
    {  
      /* EXTI line interrupt detected */
      if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
      {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
        
        if ((GPIO_Pin == GPIO_PIN_6) && ((GPIOB->IDR) & GPIO_PIN_6))
          mko_start_receive();
    
      }
    }

    Вторая проверка состояния пина добавлена в качестве антидребезга.

    Волшебные константы — результаты калибровки приёмного таймера+DMA (об этом ниже).

    Далее функция запуска и настройки приёмного и передающего DMA, в том числе задание конечного адреса GPIO (сейчас GPIOB и GPIOD)

    HAL_StatusTypeDef HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length)
    {
      
      if((htim->State == HAL_TIM_STATE_BUSY))
         return HAL_BUSY;
      else if((htim->State == HAL_TIM_STATE_READY))
      {
        if((pData == 0U) && (Length > 0U)) 
          return HAL_ERROR;                                    
        else
          htim->State = HAL_TIM_STATE_BUSY;
      }  
    
      htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = TIM_DMAPeriodElapsedCplt;
      htim->hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback = TIM_DMAError ;
    
      
      if (htim->Instance == TIM1)
        HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)pData, (uint32_t)&(GPIOD->ODR), Length);
      
      if (htim->Instance == TIM8)
      {
    #ifdef RX_CALIB
        HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)pData, (uint32_t)&(GPIOB->ODR), Length);
    #else
        HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)&(GPIOB->IDR), (uint32_t)pData, Length);
    #endif
      }
        
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_UPDATE);
      __HAL_TIM_ENABLE(htim);  
      return HAL_OK;
    }

    Макрос RX_CALIB я завёл для того, чтобы откалибровать приёмный таймер+DMA, а именно для того, чтобы видеть куда приходятся выборки на входном сигнале (в идеале они должны попадать на середину бита).

    Теперь основная логика МКО: анализ первого принятого слова, затем ответ либо зарядка на приём остального; ответ после приёма остального и выставление packet_received_length

    void mko_slave_receive(void)
    {
      register unsigned int data;
      register unsigned int i;
      static unsigned int first = 1;
      static unsigned int mko_data_length;
      
      data = mko_to_short(&in_arr[0] ,0);
        
      if (first)//если это первое срабатывание таймера - анализируем первое принятое слово и решаем, что делать далее
      {
        if (!packet_error)
        {
          if (data & MASTER_DATA_REQUEST)//значит надо останавливать приём отвечать
          {
            first = 1;
            HAL_TIM_Base_Stop_IT(&htim2);
            HAL_TIM_Base_Stop_DMA(&htim8);
            htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->CR &= ~DMA_SxCR_EN;      
                
            mko_send((unsigned char*)&out_arr[i][0], out_array_data_length[i]);//после того, как отработает передающий DMA, прерывание на уловителе будет включено заново
            
            memcpy(in_arr_copy, in_arr, IN_ARRAY_LENGTH);
    #ifndef RX_CALIB //дабы не затереть in_arr, где при калибровке лежит выходной меандр
            memset(in_arr, 0, IN_ARRAY_LENGTH);//это занимает около 4 мкс, гипотетически можно убрать
    #endif
            
            packet_received_length = 1;
            
          }
          else//надо продолжать приём, зарядив таймер на нужное количество слов
          {
            first = 0;
            data = data & 0x1F;
            if (!data)
              data = 32;
            TIM2->ARR = (data * 1680) - 1;
            mko_data_length = data;
          }
        }
        else
        {
          packet_received_length = 1;//считаем, что приняли одно слово и оно с ошибкой
          HAL_TIM_Base_Stop_IT(&htim2);
          HAL_TIM_Base_Stop_DMA(&htim8);
          htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->CR &= ~DMA_SxCR_EN;
          EXTI->IMR |= RX_START_INT;//enable interrupt - ловим следующий
        }
      }
      else//если второе срабатывание - значит приняли весь пакет, надо пулять ответное слово и анализировать данные (ставим флаг packet_received_length)
      {
        first = 1;
        HAL_TIM_Base_Stop_IT(&htim2);
        HAL_TIM_Base_Stop_DMA(&htim8);
        htim8.hdma[TIM_DMA_ID_UPDATE]->Instance->CR &= ~DMA_SxCR_EN;   
                
        mko_send((unsigned char*)&out_arr[i][0], out_array_data_length[i]);//после того, как отработает передающий DMA, прерывание на уловителе будет включено заново
        
        memcpy(in_arr_copy, in_arr, IN_ARRAY_LENGTH);
    #ifndef RX_CALIB //дабы не затереть in_arr, где при калибровке лежит выходной меандр
        memset(in_arr, 0, IN_ARRAY_LENGTH);//это занимает около 4 мкс, гипотетически можно убрать
    #endif
          
        packet_received_length = mko_data_length;
        
      }
    }
    
    
    
    void TIM2_IRQHandler(void)
    {
      if (interface == INTERFACE_MKO_SLAVE)//в МКО ОУ если сработал этот таймер - принято слово (или целый пакет)
        mko_slave_receive();
      else if (interface == INTERFACE_MKO_MASTER)//в МКО КШ если сработал этот таймер - принято слово (или целый пакет)
        mko_master_receive();
      
      TIM2->SR = ~(TIM_IT_UPDATE);
      HAL_NVIC_ClearPendingIRQ(TIM2_IRQn);
    }

    Код приведён в сжатом виде, полный проект представляет собой преобразователь MKO-Ethernet с кучей дополнительного функционала.

    Однако приведённого описания и кода достаточно для понимания сути идеи.

    Да, я реализовал минимальную логику, в ГОСТе на МКО описано гораздо больше.

    Однако для обратной совместимости с конкретным устройством этого оказалось достаточно.

    По факту проект оказался вполне успешным, контроллер полностью справляется с возложенными функциями как в режиме Master, так и Slave.

    Итого, если требуется обеспечить совместимость с чем-то древним/нестандартным, необязательно привлекать плисовода. В значительном количестве случаев можно обойтись и программной реализацией контроллера.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 16

      0
      В выводе данных в GPIO через DMA есть одна проблема. DMA не умеет в «чтение-модификация-запись» и это значит, что теряется целый порт. Ну, если только не заполнять буфер не только состояниями ног интерфейса, но и состоянием всех ног этого порта, настроенных на выход. Но мне кажется, что вывод можно организовать чисто на таймере, настроенном в режим PWM и DMA. Если нужно вывод в две ноги (а-ля дифференциал), то можно задействовать два канала таймера, а заполнять регистры сравнения с помощью DMA можно через регистр TIMx->DMAR и соответствующие настройки таймера. Таймер умеет генерировать пачку запросов DMA, в соответствии с заданным количеством обновляемых регистров, за одно событие (например, переполнение до значения в регистре TIMx->ARR)
      Если хорошо подумать, то наверняка можно организовать и приём данных, используя таймер в режиме захвата.
      У STM32 очень мощные и крутые таймеры. Разобравшись в их режимах работы, можно очень много чего реализовать.
        +2
        DMA не умеет в «чтение-модификация-запись» и это значит, что теряется целый порт.

        Используйте GPIOx_BSR, и порт теряться не будет.

          0
          В разных линейках STM32 эти регистры отличаются по своим битовым картам.
          Да и к тому же, один черт нужно тратить таймер что на реализацию через GPIO, что на реализацию на чистом таймере. Мне даже думается, что принимаемые данные таймером в режиме захвата можно писать в буфер как временнЫе диаграммы и потом уже можно подвергать их программному анализу. И тут уже можно и данные вытащить и диагностику с адаптацией сделать.
          0
          Работа большая проделана, но по сути это всего лишь реализация приёма-передачи кодом Манчестера. Это не соответствует ни ГОСТу, ни MIL-STD. На уровне сигналов у вас нет никакого обработчика. Надо же анализировать фронты. И в целом контролировать возможные ошибки и сбои. STM32 с этим не справится — медленные прерывания.
          Реализацию кода Манчестера можно найти у SiLabs. В их контроллерах есть CLU (configurable logic unit) и PRS (peripheral reflex system). И вроде у Cypress есть что-такое, была тут серия статей. Но для вашей задачи лучше взять Миландр с контроллером МКИО, и Ethernet там есть. Но в любом случае для соответствия стандартам надо по каждому пункту стандарта писать обработчик.
            +2
            Конкретно для этого интерфейса существуют серийные контроллеры канала. И если делать именно такой и на кристалле — то всё нужно делать по-серьёзному, а не подобным образом.

            Однако если Вы имеете ввиду, что для передачи 1 мегабита на 10 метров по дифпаре нужно было по-честному заводить линии на АЦП и делать честный ЦОС — то я считаю, что это избыточно. Это не радиотракт. Здесь нет созвездий, нет фаз, нет уровней амплитуд, нет многолучёвости, АЧХ канала и прочего.
            Это просто низкоскоростной поток битов с довольно-таки лютой амплитудой, и перепутать 0 и 1 здесь ох как непросто. И за время пакета максимальной длительности синхронизация здесь никуда не уедет.
            Абсолютное большинство интерфейсов вроде SPI, UART и прочих (в том числе параллельных) низкоскоростных (до 10 мбит/с) вполне реализуемы программно и будут работать с достаточным качеством.
            Конкретно это устройство было достаточно сурово протестировано, в том числе на диапазонах температур -40/+65 и рядом с шумящими в мегагерцовом диапазоне устройствами (в том числе силовыми).

            P.S. Это устройство создавалось, чтобы отказаться от отечественного контроллера производства, если не ошибаюсь, НПП «Модуль».
              0
              И за время пакета максимальной длительности синхронизация здесь никуда не уедет.
              В манчестере она в принципе никуда уехать не может на любой длительности пакета, так как он является самосинхронизирующимся кодом.
                0
                В моей реализации синхронизация происходит однократно — в начале пакета (по первому фронту), а не в каждом слове, и уж тем более бите.
                Максимальная длина пакета — (32 слова данных + 1 командное) * 20 бит = 660 бит (то есть 660 микросекунд).
                Это нужно было учитывать, однако реальное «уплывание» составило порядка единиц наносекунд на самых длинных пакетах. А допустимое «уплывание» — не более 250 наносекунд. Так что запас вполне себе приличный.
                  +2
                  Такой подход, хоть и работоспособен, но сводит на нет одно из основных преимуществ манчестерского кодирования.
            0
            при использовании таймера для захвата (в режиме захвата) вполне можно сделать внутрибитовую автоподстройку.
              0
              Я думал на этот счёт. А именно — завести два пина, которые бы реагировали один на фронт, другой на спад. И скорее всего, для манчестера это вполне прокатило бы.
              Однако позже было добавлено требование помимо манчестера реализовать как раз-таки доморощенный интерфейс в той же железяке (для обратной совместимости с особенно древним железом). И вот там уже никакой внутрибитовой синхронизации нет, только стартовый синхроимпульс и линия данных.
              Поэтому было решено делать всё по одной схеме с таймером+DMA.
                +1
                Не надо два пина, можно два канала таймера настроить на срабатывание по разным фронтам одного сигнала:
                  // ...
                
                  LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI1FP1);
                  LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING);
                  LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING);
                
                  // ...
                

                Этот же таймер по переполнению может ловить таймауты внутри пакета (когда не пришел очередной фронт на заданном интервале). Правда я не пробовал для этого задействовать DMA — скорости у меня были на три порядка меньше Ваших. Поэтому работал по прерываниям от таймера, ловя флаги от каналов и флаг переполнения для отработки таймаута. Можно ли это разогнать до Ваших скоростей — сказать не берусь, надо пробовать, но код, вызываемый по прерыванию достаточно короткий, да и процессор у Вас помощней моего, может и прокатит.
              0

              Только 1 вопрос: почему не взять 1582ВЖ3Г-0291 (контроллер протокола МКИО с выходом на шину SPI), пристыковать его к вашей системе и забыть про работу с МКИО, а заниматься только обработкой/транслированием данных? Там ведь предусмотрена вся работа в соответствии с ГОСТ.
              Хотя этот вопрос, наверное, к вашему руководителю.

                0
                Как я уже писал в комментарии чуть выше, реализовывать пришлось не только МКО, но и старинный «велосипедный» интерфейс.
                Кроме того, смысл статьи не в том, как реализовать именно МКО на костылях, а как сделать практически любой программный контроллер (приблизительно до 10 МГц). В том числе и такой, который не выпускают серийно.
                На мой взгляд, именно связка TIM+DMA+GPIO является самой функциональной. Об этом и статья.
                  0

                  Понятно, интересно в целом!

                0
                Задача слишком простая, чтобы решать её столь сложным способом. Для преобразования манчестера в spi — достаточно PIC10F204T в корпусе sot23-6 за 26 рублей.
                  0
                  А Вы обратили внимание, что автор обрабатывает поток 1 МБит/с?

                Only users with full accounts can post comments. Log in, please.