реклама на сайте
подробности

 
 
> STM32 UART<>DMA, шаг за шагом
Tanker
сообщение May 2 2012, 19:17
Сообщение #1


Участник
*

Группа: Участник
Сообщений: 34
Регистрация: 30-06-07
Пользователь №: 28 806



Здравствуйте!
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 опубликовав полученный рабочий код с каментами
Go to the top of the page
 
+Quote Post
 
Start new topic
Ответов
athlon64
сообщение May 3 2012, 03:13
Сообщение #2


Частый гость
**

Группа: Свой
Сообщений: 156
Регистрация: 10-03-10
Из: Уфа
Пользователь №: 55 882



Цитата(Tanker @ May 3 2012, 01:17) *
В общем надеюсь с вашей помощь найти решение своего трабла и увековечить тему "STM32 DMA UART" в FAQ опубликовав полученный рабочий код с каментами


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

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

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

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

Сообщение отредактировал athlon64 - May 3 2012, 03:15


--------------------
Руслан
Go to the top of the page
 
+Quote Post
Tanker
сообщение May 4 2012, 08:02
Сообщение #3


Участник
*

Группа: Участник
Сообщений: 34
Регистрация: 30-06-07
Пользователь №: 28 806



можно подробнее про флаг IDLE усарта (сейчас приёмом ч/з DMA занимаюсь)
Теоретически:
1) настраиваю прерывание от UART по IDLE
2) настраиваю DMA на приём от RX (и соотв. сообщаю UART что приём идет через DMA). В настройках указываю буфер куда валить данные и его размер (можно зациклить DMA_Mode_Circular чтобы не переполнялся)
3) начали приходить данные (например 4 байта я отправил с ПК на контроллер)
4) когда все 4 байта придут, срабатывает прерывание IDLE (действительно срабатывает) и в буфере указанном DMA будут лежать эти 4 байта
так?
а кол-во принятых байт лежит в DMAx_Channely->CNDTR ?
Go to the top of the page
 
+Quote Post
athlon64
сообщение May 4 2012, 08:53
Сообщение #4


Частый гость
**

Группа: Свой
Сообщений: 156
Регистрация: 10-03-10
Из: Уфа
Пользователь №: 55 882



Цитата(Tanker @ May 4 2012, 14:02) *
можно подробнее про флаг 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 в передачу.

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


--------------------
Руслан
Go to the top of the page
 
+Quote Post
Tanker
сообщение May 5 2012, 06:12
Сообщение #5


Участник
*

Группа: Участник
Сообщений: 34
Регистрация: 30-06-07
Пользователь №: 28 806



Здравствуйте!
С приёмом получился вот такой код (считаем, что ножки и 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" = НУЛЮ ) за то всё последующие посылки появляются в буфере.
Есть предположения почему так происходит? хотелось бы разобраться
Go to the top of the page
 
+Quote Post
demiurg_spb
сообщение May 5 2012, 06:48
Сообщение #6


неотягощённый злом
******

Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643



У меня другой подход. Я не использую вообще никаких прерываний ни от ДМА ни от Уарта на приём данных.
ОДИН раз настраиваю ДМА и Уарт а далее в фоне просто читаю из кольцевого буфера ДМА принятые данные.
С чтением можно не торопиться размер буфера достаточен...
Код
//=============================================================================
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);
}


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post
athlon64
сообщение May 5 2012, 07:13
Сообщение #7


Частый гость
**

Группа: Свой
Сообщений: 156
Регистрация: 10-03-10
Из: Уфа
Пользователь №: 55 882



Цитата(demiurg_spb @ May 5 2012, 12:48) *
У меня другой подход. Я не использую вообще никаких прерывания ни от ДМА ни от Уарта на приём данных.
ОДИН раз настраиваю ДМА и Уарт а далее в фоне просто читаю из кольцевого буфера ДМА принятые данные.
С чтением можно не торопиться размер буфера достаточен...

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


--------------------
Руслан
Go to the top of the page
 
+Quote Post



Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 19th August 2025 - 17:55
Рейтинг@Mail.ru


Страница сгенерированна за 0.01434 секунд с 7
ELECTRONIX ©2004-2016