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

Передача через USART с DMA

Quote

но кажется, что это костыль, возможно есть более элегантное решение

Для F4 это правильное решение.

Красивое решение называется MAX13487.

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


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

2 часа назад, Reystlin сказал:

Таким образом?

Настоятельно рекомендую GPIO-хи настраивать после настройки UART-модуля, а DMA-поток - перед настройкой UART.

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


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

Добавил блокировку возможности отправки сообщения во время получения ответа

первый пакет отправляю и получаю нормально

после второй отправки на короткое время проваливает PA11 и снова запускается отправка, не дожидаясь приема данных не могу понять в чем косяк

почему-то после повторной отправки сразу же срабатывает USART_SR_IDLE не дожидаясь приема...

void Init_USART1()
{
		// GPIO
		RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
		GPIO_InitTypeDef GPIO_InitStruct_ModBUS = {0};
    GPIO_InitStruct_ModBUS.Speed = GPIO_SPEED_FAST;
    GPIO_InitStruct_ModBUS.Alternate = GPIO_AF7_USART1;
    // PA9 - DI - TX
    GPIO_InitStruct_ModBUS.Pin = GPIO_PIN_9;
    GPIO_InitStruct_ModBUS.Mode = GPIO_MODE_AF_PP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct_ModBUS);
    // PA10 - RO - RX
    GPIO_InitStruct_ModBUS.Pin = GPIO_PIN_10;
    GPIO_InitStruct_ModBUS.Mode = GPIO_MODE_AF_OD;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct_ModBUS);
		// PA11 - DE/RE - CTS
    GPIO_InitStruct_ModBUS.Pin = GPIO_PIN_11;
    GPIO_InitStruct_ModBUS.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct_ModBUS);
		
		GPIOA->ODR&=~(1<<11); // Приём
		
		// USART1
		RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
		// APB2 = 84MHz
    uint32_t baud_rate = 38400;
    uint16_t uartdiv = 168000000 / baud_rate;
    USART1->BRR = (( ( ( uartdiv / 16 ) << USART_BRR_DIV_Mantissa_Pos ) |
    				( ( uartdiv % 16 ) << USART_BRR_DIV_Fraction_Pos ) ))-2;
		
		USART1->CR1 = 0
		|	USART_CR1_OVER8			// Размер кадра 8 бит
		| USART_CR1_UE				// Включаем USART1
		//| USART_CR1_M				// Формат кадра Start bit, 8 bits, n Stop bits
		//| USART_CR1_PCE			// Parity mode control disable
		//| USART_CR1_PS			// Parity mode Even
		//| USART_CR1_PEIE		// Разрешение прерывания PEIE ошибка четности
		//| USART_CR1_TXEIE		// Разрешение прерывания TXEIE опустошение регистра передатчика
		//| USART_CR1_TCIE		// Разрешение прерывания TCIE завершение передачи
		//| USART_CR1_RXNEIE	// Разрешение прерывания RXNEIE завершение приема
		//| USART_CR1_IDLEIE	// Разрешение прерывания IDLEIE обнаружение сигнала IDLE на линии
		| USART_CR1_TE				// Разрешение передачи
		| USART_CR1_RE				// Разрешение приема
		//| USART_CR1_RWU				// Reciver wakeup
		//| USART_CR1_SBK				// Send break
		;
		
		USART1->CR2 = 0
		//	USART_CR2_LINEN		// Включение режима LIN
		|	USART_CR2_STOP_1	// 2 Стоп бита
		//| USART_CR2_STOP_0
		//| USART_CR2_CLKEN		// Включить вывод CK
		//| USART_CR2_CPOL		// Определяет уровень сигнала на линии CK в отсутствии симпульсов
		//| USART_CR2_CPHA		// Определяет по которому фронту импульсов на линии CK происходи захват
		//| USART_CR2_LBCL		// Нужно ли тактировать через линию CK последний передаваемый бит
		//| USART_CR2_LBDIE			// Разрешение прерывания по обнаружению сигнала break
		//| USART_CR2_LBDL			// Длительность ожидаемого сигнала break
		;
		
		USART1->CR3 = 0
		//	USART_CR3_CTSIE			// Включение прерывания по линии CTS
		//USART_CR3_CTSE			// Включение контроля линии CTS(Данные начнут передаваться когда CTS = 0)
		//|	USART_CR3_RTSE			// Включение контроля линии RTS(Dscnfdkztncz КЕЫ = 0 когда приемник пуст и готов)
		|	USART_CR3_DMAT		// Включить режим DMA для передатчика
		| USART_CR3_DMAR		// Включить режим DMA для приемника
		//| USART_CR3_SCEN			// Включить режим Smartcard
		//| USART_CR3_NACK			// Поылать ли NACK в режиме Smartcard
		//| USART_CR3_HDSEL			// Однопроводный полудуплексный режим
		//| USART_CR3_IRLP			// Включение режима low-power для IrDA
		//| USART_CR3_IREN			// Включение режиме IrDA
		//| USART_CR3_EIE				// Включение прерывания по ошибкам передачи
		;
		
		// DMA2 - Stream7 - Transmit
		RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
		
		DMA2_Stream7->PAR  = (uint32_t)&(USART1->DR);		// Адрес назначения
		DMA2_Stream7->M0AR = (uint32_t)(tx_buf); 				// Адрес источника
		DMA2_Stream7->FCR = 0;													// Настройка FIFO буфера
		DMA2_Stream7->CR = DMA_SxCR_CHSEL_2;		// Выбираем 4 канал
		DMA2_Stream7->CR |= DMA_SxCR_MINC;			// Инкремент адреса памяти
		DMA2_Stream7->CR |= DMA_SxCR_DIR_0;			// Направление - из памяти в перефереию
		DMA2_Stream7->CR |= DMA_SxCR_TCIE;			// Прерывание по окончанию пересылки пакета
		
		NVIC_EnableIRQ(USART1_IRQn);
		NVIC_SetPriority(USART1_IRQn, 0x04);
		NVIC_SetPriority(DMA2_Stream7_IRQn, 0x04);
		NVIC_EnableIRQ(DMA2_Stream7_IRQn);
		
		// DMA2 - Stream 5 - Recive
		DMA2_Stream5->PAR  = (uint32_t)&(USART1->DR);		// Адрес источника 
		DMA2_Stream5->M0AR = (uint32_t)(rx_buf); 				// Адрес назначения
		DMA2_Stream5->FCR = 0;													// Настройка FIFO буфера
		DMA2_Stream5->NDTR = 255;
		DMA2_Stream5->CR = DMA_SxCR_CHSEL_2;		// Выбираем 4 канал
		DMA2_Stream5->CR |= DMA_SxCR_MINC;			// Инкремент адреса памяти
		DMA2_Stream5->CR |= DMA_SxCR_PFCTRL;		// DMA - Ведомый
}
enum USART_RS485_Status{
	USART_IDLE = 0x00,
	USART_SEND = 0x01,
	USART_READ = 0x02
};
void (*recive_fn)(uint8_t*);
enum USART_RS485_Status status = USART_IDLE;
// Принимает строку.
enum USART_RS485_Status USART1_put(uint8_t size, char *text, void (*recive)(uint8_t*))
{
	if(status!=USART_IDLE)
		return status;
	recive_fn = recive;
	for(uint8_t i =0;i<size;i++)
		tx_buf[i]=text[i];
	GPIOA->ODR|=(1<<11); // Передача
  DMA2_Stream7->CR &= ~DMA_SxCR_EN;   // Отключаем поток DMA
  DMA2_Stream7->NDTR = size;
  DMA2_Stream7->CR |= DMA_SxCR_EN;
	status = USART_SEND;
	return 0;
};

void DMA2_Stream7_IRQHandler(void) {
   if (DMA2->HISR & DMA_HISR_TCIF7)
	 {
		DMA2->HIFCR = DMA_HIFCR_CTCIF7;	// сброс флага события TCIF
		USART1->SR = ~(USART_SR_TC);
    USART1->CR1 |= USART_CR1_TCIE;		// Разрешение прерывания TCIE завершение передачи
   }
};

void USART1_IRQHandler(void) 
{
	const uint32_t sr = USART1->SR, cr = USART1->CR1;
	if((sr & USART_SR_TC) && (cr & USART_CR1_TCIE)) 
		{	// Окончили отправку пакета
		USART1->CR1 &= ~USART_CR1_TCIE;	
		USART1->SR &= ~(USART_SR_TC);
		USART1->SR &= ~(USART_SR_IDLE);
		USART1->CR1 |= USART_CR1_IDLEIE;	
		DMA2_Stream5->CR |= DMA_SxCR_EN;
		GPIOA->ODR&=~(1<<11); 					// Приём
			status = USART_READ;
	} else if((sr & USART_SR_IDLE) && (cr & USART_CR1_IDLEIE))
	{	// Окончили прием ответа от МК
		USART1->SR &= ~(USART_SR_IDLE);
		USART1->CR1 &= ~(USART_CR1_IDLEIE);	
		DMA2_Stream5->CR &= ~(DMA_SxCR_EN);
			GPIOA->ODR|=(1<<11); // Прием окончен
			recive_fn(rx_buf);
		status = USART_IDLE;
	}
}

 

2022-07-23 18-39-25(2).jpg

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

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


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

Quote

Добавил блокировку возможности отправки сообщения во время получения ответа

Зачем? Разреши приём при передаче. Сработал IDLE, значит, передача закончилась и можно переключаться на приём. Попутно проверишь, что принятое совпало с отправленным.

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


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

работаю над реализацией ModBUS поверх RS485, полудуплексная передача по одному каналу.

Задумка вот в чем:

есть очередь сообщений, кладем в неё сообщения в любых местах программы, основном цикле смотри если есть сообщение в очереди то пытаемся отправить, если передатчик не занят то отправляем(status==USART_IDLE) если занят то продолжаем основной цикл.

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


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

On 7/23/2022 at 9:54 PM, Reystlin said:

Задумка вот в чем:

есть очередь сообщений, кладем в неё сообщения в любых местах программы

Очень плохая задумка отправлять Модбас-сообщения через очередь. Размер RTU 256 байт, TCP 260. Даже на "жирных" в части ОЗУ МК это очень расточительно.

Делай как в ПЛК. Есть регистры, есть объекты синхронизации на запись-чтение регистров, есть задача, обслуживающая Модбас. У задачи есть пул _запросов_, который она заполняет, читая очередь команд. Команды содержат код запроса, его параметры, состояние и уникальный идентификатор запроса. Тогда любая задача может просто добавить запрос в пул запросов, получить его идентификатор, а дальше попросить задачу-обработчик Модбаса непрерывно исполнять запрос, исполнить один, и так далее. Обрати внимание, что доступ к пространству регистров только через объекты синхронизации, поэтому, возможно, для получения минимальных задержек понадобится сделать двойную буферизацию доступа. Ещё не забывай, что каждый запрос на работу с очередью сообщений это вызов ОС, что долго/дорого, особенно с учётом копирований в/из очереди.

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


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

On 7/23/2022 at 8:30 PM, tonyk_av said:

Очень плохая задумка отправлять Модбас-сообщения через очередь. Размер RTU 256 байт, TCP 260. Даже на "жирных" в части ОЗУ МК это очень расточительно.

Делай как в ПЛК. Есть регистры, есть объекты синхронизации на запись-чтение регистров, есть задача, обслуживающая Модбас. У задачи есть пул _запросов_, который она заполняет, читая очередь команд. Команды содержат код запроса, его параметры, состояние и уникальный идентификатор запроса. Тогда любая задача может просто добавить запрос в пул запросов, получить его идентификатор, а дальше попросить задачу-обработчик Модбаса непрерывно исполнять запрос, исполнить один, и так далее. Обрати внимание, что доступ к пространству регистров только через объекты синхронизации, поэтому, возможно, для получения минимальных задержек понадобится сделать двойную буферизацию доступа. Ещё не забывай, что каждый запрос на работу с очередью сообщений это вызов ОС, что долго/дорого, особенно с учётом копирований в/из очереди.

Если я правильно понял, это примерно похоже на то что я и собирался реализовать

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

void USART1_IRQHandler(void) 
{
	const uint32_t sr = USART1->SR, cr = USART1->CR1;
	if((sr & USART_SR_TC) && (cr & USART_CR1_TCIE)) 
		{	// Окончили отправку пакета
		USART1->CR1 &= ~USART_CR1_TCIE;	
		USART1->SR &= ~(USART_SR_TC);
		USART1->SR &= ~(USART_SR_IDLE);
		USART1->CR1 |= USART_CR1_IDLEIE;	
		DMA2_Stream5->CR |= DMA_SxCR_EN;
		GPIOA->ODR&=~(1<<11); 					// Приём
			status = USART_READ;
	} else if((sr & USART_SR_IDLE) && (cr & USART_CR1_IDLEIE))
	{	// Окончили прием ответа от МК
		USART1->SR &= ~(USART_SR_IDLE);
		USART1->CR1 &= ~(USART_CR1_IDLEIE);
		uint8_t dr = USART1->DR;
		DMA2_Stream5->CR &= ~(DMA_SxCR_EN);
			GPIOA->ODR|=(1<<11); // Прием окончен
			recive_fn(rx_buf);
		status = USART_IDLE;
	}
}

 

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


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

9 минут назад, Reystlin сказал:

...согласен, лишнее...

От '&' здесь эффект куда негативнее, чем от лишней строчки, на которую Вы указали.

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


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

Там все '|=' и '&=' надо убирать. Во всём коде.

Откуда все они это берут??? Ведь все как под копирку лепят эти '|=' и '&=' ??? И никто даже не заглядывает в реф.мануалы. И даже на мгновение не включают голову...  :fool:

В 23.07.2022 в 20:57, Reystlin сказал:
const uint32_t sr = USART1->SR, cr = USART1->CR1;

А const тут - на кой?  :wacko2:

И почему:

В 23.07.2022 в 18:41, Reystlin сказал:
enum USART_RS485_Status status = USART_IDLE;

без volatile?

И почему:

В 23.07.2022 в 18:41, Reystlin сказал:
DMA2_Stream7->CR |= DMA_SxCR_EN;
	status = USART_SEND;

установка значения status идёт после старта работы UART, а не до?

Также почему recive_fn - тоже без volatile?

 

В 23.07.2022 в 18:41, Reystlin сказал:
for(uint8_t i =0;i<size;i++)
		tx_buf[i]=text[i];

Есть такая функция полезная как memcpy(). Попробуйте её - понравится.

В 23.07.2022 в 18:41, Reystlin сказал:

		DMA2_Stream5->CR = DMA_SxCR_CHSEL_2;		// Выбираем 4 канал
		DMA2_Stream5->CR |= DMA_SxCR_MINC;			// Инкремент адреса памяти
		DMA2_Stream5->CR |= DMA_SxCR_PFCTRL;		// DMA - Ведомый

Это делается одной операцией, а не 5-ю чтениями/записями. Если конечно не стоит задача сляпать как можно более тормозной и громоздкий код.

 

Также вангую, что не отрабатываются необходимые задержки между предыдущим приёмом и передачей (хотя из этого "кода" этого и не видно).

И не понятен смысл манипуляций с ОК-режимом PA.11.  :umnik2:

И не понятно - зачем читается USART.DR после завершения приёма?

 

PS: Всё не смотрел - только мельком, по диагонали. А то после таких "кодов" изжога начинается...  :bad:

Вобщем - всё переделывать. К нормальному виду. Открыв мануал и учебник по си. В таком виде оно неработопособно. Иначе потом будет ещё одна тема "Почему перестаёт работать при включении оптимизации?" как у другого "деятеля" тут.

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


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

17 минут назад, jcxz сказал:

А const тут - на кой?  :wacko2:

Чем он не угодил? Человеку подрезает возможность что-то изменить, машине дает волю оптимизировать.

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


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

В 24.07.2022 в 12:10, Arlleex сказал:

Чем он не угодил? Человеку подрезает возможность что-то изменить, машине дает волю оптимизировать.

Оптимизатору оно до лампочки: он и так прекрасно видит, что нигде после они не используются на запись.

А если уже говорить о человеческой оптимизации, то в первую очередь выкинуть cr из условий в ISR. Ибо не нужен там.

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


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

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

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

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

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

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

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

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

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