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

STM32 UART<>DMA

Здравствуйте!

IAR ARM 6.30.7 STM32F100

Делаю слейв Modbus RTU c RS485 интерфейсом. Всё было бы "ОК", но по 2-м UARTAM одновременно нужно работать, а при этом моя

 

реализация на прерываниях, начинает то на один то другой UART данные не до давать при интенсивном обмене с мастерами (ещё все АЦП задействованы на макс. скорости с прерываниями DMA по заверш. передачи оцифровки, но их отключение не помогает).

Решил сделать на связке UART-DMA.

Просмотрел на эту тему все топики здесь, погуглил, мануалы почитал... сделал - но очень зыбко работает моя реализация протокола, какой-то мелочи я не просекаю.

 

Для начала я хочу реализовать приём по-байтный, а отправку через DMA.

Начинается всё с приёма запроса от мастера, uart и драйвер настроен на приём:

1) принял байтик -> положил в буфер -> перезагрузил таймер (отсчёт frame end длительность 1,5-3 байт завершения пакета) и так каждый байтик, до срабатывания таймера (срабатывает когда пауза в 1,5-3 байта (как настрою))

2) таймер отсчитал framе end, сработало его прерывание -> останавливаю таймер-> блокирую приёмник UARTа (чтобы не реагировал на входящие)

3) разбор входящего сообщения (можно вынести из прерывания во внешний цикл и сигнализировать от пришедшем пакете через какие-нибудь битики) включает в себя проверку адреса слейва и контрольной суммы (потом делается более детальный разбор, но для начала самое-то)

4) реакция на входящее сообщение:

4.1) чужое или "по-дороге битое" -> перевод uart в режим ожидания входящих байт (исходное состояние)

4.2) достойное ответа -> формирование ответа в буфере

5) когда буфер заполнен ответом, запускаю таймер на отсчёт времени переключения драйвера из режима "приём" в режим "передача" (например для ADM2587E 2,5мкСек)

6) сработало прерывание таймера (драйвер переключился в "передача")->останавливаю таймер->настраиваю DMA на передачу подготовленного буфера и запускаю передачу DMA1_Channel7->CCR |= DMA_CCR7_EN; (всё, данные должны автоматом вылетать с ножки TX, до опустошения буфера)

7) по опустошению буфера срабатывает прерывание DMA_ISR_TCIFx (если разрешено)(или таймера USART_CR1_TCIE .. в этом есть вопрос ??) данные (как-бы) переданы и можно переключаться на приём

8) запускаю таймер на отсчёт времени переключения драйвера из режима "передача" в режим "приём"

9) сработало прерывание таймера (драйвер переключился в "приём")-> перевод uart в режим ожидания входящих байт (исходное состояние)

 

Трабл:

Сейчас получается, до п.7 всё норм.,запускаю DMA... бывает что весь буфер приходит (длина ответа в среднем 64 байта), а зачастую 1 байт (левый!!!) и срабатывает DMA или UART "Transmission Complete" (я с ними экспериментировал)

 

Ещё из наблюдений:

1) Если я разрешаю DMA_ISR_TCIFx (но при этом USART_CR1_TCIE запрещён) и запускаю DMA, то мне приходится к DMA1_Channelх->CNDTR дополнительно "+2" делать, но имею при этом лишний байт "0" он либо затирает последний значащий байт пакета данных (если CNDTR = кол-ву передаваемых байт), либо просто лишний (если CNDTR = кол-ву передаваемых байт + 2)

 

 

2) Если я разрешаю USART_CR1_TCIE (но DMA_ISR_TCIFx при этом запрещён или разрешён но просто чистит флаги) и запускаю DMA, то с DMA1_Channelх->CNDTR всё "ок" равно кол-ву передаваемых байт.

 

В общем надеюсь с вашей помощь найти решение своего трабла и увековечить тему "STM32 DMA UART" в FAQ опубликовав полученный рабочий код с каментами

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


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

В общем надеюсь с вашей помощь найти решение своего трабла и увековечить тему "STM32 DMA UART" в FAQ опубликовав полученный рабочий код с каментами

 

По поводу глюка с последними байтами при передаче через DMA, есть ньюанс. У DMA флаг TCIF выставляется когда последний байт буфера записан в регистр DR усарта. При этом выдача этого байта самим усартом ещё не закончена. Поэтому переводить 485й в приём рано. Для переключения 485 в приём используйте флаг USART_CR1_TCIE, он выставится когда последний байт полностью передан.

 

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

 

В остальном логика работы верная. Единственное, я никогда не заморачивался с временем переключения драйвера 485, всегда работал с ним сразу после переключения. Ну и по поводу глюков при работе по прерываниям с 2 портами - всё же где то ваш косяк, не знаю при приёме или при передаче, но вариант с работой по прерываниям должен прекрасно работать.

 

Загляните в мою недавнюю тему про USART+DMA, там и приём и передача сделаны с DMA (правда для FreeRTOS, но смысл понять можно).

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

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


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

можно подробнее про флаг IDLE усарта (сейчас приёмом ч/з DMA занимаюсь)

Теоретически:

1) настраиваю прерывание от UART по IDLE

2) настраиваю DMA на приём от RX (и соотв. сообщаю UART что приём идет через DMA). В настройках указываю буфер куда валить данные и его размер (можно зациклить DMA_Mode_Circular чтобы не переполнялся)

3) начали приходить данные (например 4 байта я отправил с ПК на контроллер)

4) когда все 4 байта придут, срабатывает прерывание IDLE (действительно срабатывает) и в буфере указанном DMA будут лежать эти 4 байта

так?

а кол-во принятых байт лежит в DMAx_Channely->CNDTR ?

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


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

можно подробнее про флаг IDLE усарта (сейчас приёмом ч/з DMA занимаюсь)

Теоретически:

1) настраиваю прерывание от UART по IDLE

2) настраиваю DMA на приём от RX (и соотв. сообщаю UART что приём идет через DMA). В настройках указываю буфер куда валить данные и его размер (можно зациклить DMA_Mode_Circular чтобы не переполнялся)

3) начали приходить данные (например 4 байта я отправил с ПК на контроллер)

4) когда все 4 байта придут, срабатывает прерывание IDLE (действительно срабатывает) и в буфере указанном DMA будут лежать эти 4 байта

так?

а кол-во принятых байт лежит в DMAx_Channely->CNDTR ?

Ну почти так.

 

1) Настраиваете UART (тактование, ноги), включаете прерывание от UART по флагу IDLE (для определения конца входящего фрейма) и по флагу TCIE (для перевода драйвера 485 в приём после окончания передачи). Включаете в UART работу с DMA на приём и передачу.

2) Сразу инициируете приём с DMA на всю величину входного буфера и спокойненько занимаетесь другими задачами, пока не сработает прерывание от UART. Прерывания от DMA в принципе не нужны.

 

Переполнения не будет, т.к. заполнив буфер, DMA закончит свою работу по приёму.

 

3) В обработчике прерывания UART (не важно по какому флагу) начинаете новый приём, предварительно запомнив значение DMAx_Channely->CNDTR.

3.1) Если прерывание было по флагу IDLE - считаете исходя из CNDTR размер принятого пакета и делаете мероприятия, необходимые при приёме пакета.

3.2) Если прерывание по TCIE - ничего делать не надо, только снять флаг TCIE.

 

В процедуре, инициирующей приём, переводите свой драйвер 485 в приём.

В процедуре, инициирующей передачу, переводите свой драйвер 485 в передачу.

 

Надеюсь понятно описал.

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


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

Здравствуйте!

С приёмом получился вот такой код (считаем, что ножки и NVIC уже настроены):

#define SetDIR2ToRX    GPIO_WriteBit(GPIOA, GPIO_Pin_1,  (BitAction)(0));
#define SetDIR2ToTX    GPIO_WriteBit(GPIOA, GPIO_Pin_1,  (BitAction)(1));

#define U2RXBUFFSIZE  256 //размер буфера приёмника

void RxDMA1Ch6 (void) {//настройка DMA на чтение данных из UART
  DMA_InitTypeDef DMA_InitStructure;
  
  DMA_Cmd(DMA1_Channel6, DISABLE);//отключаю DMA для получения доступа к регистрам
  DMA1->IFCR |= DMA_IFCR_CTCIF6 | DMA_IFCR_CGIF6 | DMA_IFCR_CHTIF6 | DMA_IFCR_CTEIF6;//очищу все флаги прерываний 
  DMA_DeInit(DMA1_Channel6);//на всякимй случай
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART2->DR);//источник - регистр данных UART
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &uart2data.Buffer[0];//приёмник - мой буфер (размер 256 байт)
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//направление из переферии в память (буфер)
  DMA_InitStructure.DMA_BufferSize = U2RXBUFFSIZE;//256 байт размер принимающего буфера
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//адрес переферии не инкрементируется
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//адрес (ссылка на буфер) инкрементируется
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//размер данных переферии БАЙТ
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//размер данных буфера БАЙТ
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//по заполнению буфера DMA останавливается
  DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel6, &DMA_InitStructure); 
  DMA_Cmd(DMA1_Channel6, ENABLE);//включаю DMA... и он начинает складывать поступающие данные в заданный буфер
}

void uart2rs485_init (void){//настройка UART
  SetDIR2ToRX;//драйвер RS485 на приём
  
  USART_InitTypeDef USART_InitStructure;
  USART_InitStructure.USART_BaudRate = 115200;//
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART_Init(USART2, &USART_InitStructure);
  
  USART2->CR3 |=  USART_CR3_DMAR;//USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
  
  USART2->CR1 |=  USART_CR1_RE;//разрешить приёмник
  USART2->CR1 |=  USART_CR1_IDLEIE;//разр. прерывания по приёму "паузе" между посылаемыми байтами
  USART2->BRR = 104;// 12.0000.000МГц / 115200bps = 104h скорость связи
  USART2->CR1 |=  USART_CR1_UE;//разрешить UART2
  RxDMA1Ch6();//настройка DMA на чтение данных из UART
}

void USART2_IRQHandler(void)
{
  volatile u32 IIR = USART2->SR;
  RAM_DATA.Iload++;//RAM_DATA.Iload счётчик кол-ва вхождений в IRQ (для отладки)

    if (IIR & USART_SR_IDLE)            // Между байтами при приёме обнаружена пауза в 1 IDLE байт
      {
        USART2->DR; // Снимаем флаг прерывания по IDLE (иначе будет входить в эту ветку бесконечно)
        RAM_DATA.Iz++;//RAM_DATA.Iz счётчик кол-ва вхождений в ветку IDLE(для отладки)
        DMA_Cmd(DMA1_Channel6, DISABLE);//выкл. DMA для получения доступа к его регистрам
        RAM_DATA.Uload = U2RXBUFFSIZE - DMA1_Channel6->CNDTR;//кол-во принятых байт в RAM_DATA.Uload
        RxDMA1Ch6();//снова настройка DMA на чтение данных из UART
      }
}

Действительно приятно отвлекаться только на одно прерывание когда всё уже сложено в буфер, когда с приёмом /передачей по DMA закончу, обязательно попробую "поговорить" с контроллером на скорости 2МБит/сек, похоже он не сильно напряжётся.

 

Есть один не существенный косяк, после ресета, когда я отправляю контроллеру от ПК первую дозу данных, буфер не заполняется (по счётчикам я вижу что есть вхождение в прерывание по IDLE, но в буфере переданные данные не появляются и кол-во принятых данных в "U2RXBUFFSIZE - DMA1_Channel6->CNDTR" = НУЛЮ ) за то всё последующие посылки появляются в буфере.

Есть предположения почему так происходит? хотелось бы разобраться

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


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

У меня другой подход. Я не использую вообще никаких прерываний ни от ДМА ни от Уарта на приём данных.

ОДИН раз настраиваю ДМА и Уарт а далее в фоне просто читаю из кольцевого буфера ДМА принятые данные.

С чтением можно не торопиться размер буфера достаточен...

//=============================================================================
int uart_getc(uart_t* const uart)
{
    dma_t* const dma = &uart->dma.rx;

    if (dma->size - dma->sfr->CNDTR != dma->idx)
    {
        int x = dma->buf[dma->idx];

        if (++dma->idx >= dma->size)
        {
            dma->idx = 0;
        }

        return (x);
    }

    return (-1);
}

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


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

У меня другой подход. Я не использую вообще никаких прерывания ни от ДМА ни от Уарта на приём данных.

ОДИН раз настраиваю ДМА и Уарт а далее в фоне просто читаю из кольцевого буфера ДМА принятые данные.

С чтением можно не торопиться размер буфера достаточен...

Почему бы и нет, если протокол позволяет

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


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

Это да. Для modbus-rtu этот метод напрямую не совсем применим.

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


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

Использую DMA для чтения данных из регистра юарта DR и записи в буфер в RAM. Проблема в том, что, судя по всему, DMA-контроллер сразу после включения соответствующего канала начинает считывать уже хранящийся байт в приёмном регистре юарта. А этот байт относится к другой, предыдущей, независимой от этой операции.

Мне нужно, чтобы передача начиналась только тогда, когда придёт новый байт данных - как сделать?

Пока что считываю на один байт больше, чем должно прийти данных, а первый ненужный байт просто отбрасываю. Но мне не особо нравится это решение.

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


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

DMA-контроллер сразу после включения соответствующего канала начинает считывать уже хранящийся байт в приёмном регистре юарта. А этот байт относится к другой, предыдущей, независимой от этой операции.

Мне нужно, чтобы передача начиналась только тогда, когда придёт новый байт данных - как сделать?

ни у кого нет идей?

Можно очистить приёмный буфер UART-а: например, прочитав байт из приёмного буфера перед стартом ДМА. Или сбросом флага приёма (что хуже). Или может быть запретом приёмника (правда, не знаю сделает ли UART STM32 сброс буфера при запрете приёмника).

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


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

Первые два варианта точно пробовал, третий, если не изменяет память, тоже - ничего не помогло.

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


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

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

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

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

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

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

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

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

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

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