|
STM32 UART<>DMA, шаг за шагом |
|
|
|
May 2 2012, 19:17
|
Участник

Группа: Участник
Сообщений: 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 опубликовав полученный рабочий код с каментами
|
|
|
|
|
 |
Ответов
|
May 3 2012, 03:13
|

Частый гость
 
Группа: Свой
Сообщений: 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
--------------------
Руслан
|
|
|
|
|
May 4 2012, 08:02
|
Участник

Группа: Участник
Сообщений: 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 ?
|
|
|
|
|
May 4 2012, 08:53
|

Частый гость
 
Группа: Свой
Сообщений: 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 в передачу. Надеюсь понятно описал.
--------------------
Руслан
|
|
|
|
|
May 5 2012, 06:12
|
Участник

Группа: Участник
Сообщений: 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" = НУЛЮ ) за то всё последующие посылки появляются в буфере. Есть предположения почему так происходит? хотелось бы разобраться
|
|
|
|
|
May 5 2012, 06:48
|

неотягощённый злом
     
Группа: Свой
Сообщений: 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); }
--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
|
|
|
|
|
May 5 2012, 07:13
|

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

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