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

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

Видимо я что-то не так с ДМА делаю, в rx_buff заполняется первый ответный пакет и после этого данные в нём не меняются, хотя ответы разные на шине присутствуют

нужно ли перед повторным включением DMA на прием сбрасывать какой-нибудь счетчик?

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

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


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

Зачем такая дикая свистопляска с таймерами ради модбаса, когда есть регистр RTO? Получили прерывание по таймауту - выставили флаг - поменяли приемный буфер, а в суперлупе, как увидели флаг, обработали принятые данные!..

В случае с DMA нужно лишь, чтобы буферы были достаточно большими. В обработчике прерывания по таймауту отключаем DMA, меняем буферы, ставим флаг - вуаля!

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

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


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

Пофиксил. Не хватало сброса флага окончания передачи из уарта в память на приеме

void USART1_IRQHandler(void) 
{
	const uint32_t sr = USART1->SR, cr = USART1->CR1;
	if((sr & USART_SR_TC) && (cr & USART_CR1_TCIE)) 
    {	// Окончили отправку
		USART1->SR = ~(USART_SR_TC);
        DMA2_Stream5->NDTR = 255;
        DMA2->HIFCR = DMA_HIFCR_CTCIF5;         
		DMA2_Stream5_En();
        GPIOA->BSRR = GPIO_BSRR_BR11;   // Прием
        USART_status = USART_READ;
        // Настройка таймера на Timeout
        TIM7->CR1 = 0;
        TIM7->CNT = 0;
        TIM7->ARR = 100000-1;      
		TIM7_En();  
	}
    else if((sr & USART_SR_IDLE) && (cr & USART_CR1_IDLEIE))
	{	// Окончили прием ответа
        (void)USART1->DR;
		USART1->SR = ~(USART_SR_IDLE);
		DMA2_Stream5->CR = 0;
        GPIOA->BSRR = GPIO_BSRR_BS11; // Прием окончен
        USART_status = USART_WAIT;
        TIM7->CR1 = 0;
        TIM7->CNT = 0;
        TIM7->ARR = 500-1; // Задержка отправки второго пакета после приема       
		TIM7_En();  // Включаем счетчик для задержки отправки второго пакета
        recive_fn(rx_buf);
	}
}

 

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


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

К чему эти игры с TIM7? На практике толку от них никакого не будет кроме напрасной траты таймера.

 

Я не заметил паузу после перевода трансмиттера на передачу перед началом отправки. Без неё ждут ошибки передачи, особенно на высоких скоростях.

 

P. S. Посмотри на стиль своей программы и подумай, как у тебя будет выглядеть программа, если вдруг тебе понадобится сделать, например, 4 мастера и 2 слэйва?

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


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

Задачи multimaster не стоит а с несколькими slave не вижу проблемы.

использую дальше вот так


enum ModBUS_packet_status{
	IDLE,
	SEND,
	SEND_ERROR,
	WAIT_ANSWER,
	ANSWER_OK,
	ANSWER_ERROR,
    DONE
};

enum ModBUS_Error{
    NONE,
    TIMEOUT,
    SLAVE_ID_ERROR,
    CMD_ERROR,
    FUNCTION_CODE_ERROR,
    ACCESS_ADDRESS_ERROR,
    DATA_ERROR,
    CRC_ERROR
};

enum ModBUS_cmd{
	//Read_Coil_Status = 0x01,
	//Read_Input_Status = 0x02,
	Read_Holding_Registers = 0x03,
	//Read_Input_Registers = 0x04,
	//Force_Single_Coil = 0x05,
	Preset_Single_Register = 0x06,
	//Force_Multiple_Coils = 0x0F,
	//Preset_Multiple_Registers = 0x10
};
#define MODBUS_QUEUE_SIZE	2

volatile struct _ModBUS_packet_queue
{
	uint8_t SlaveID;
    enum ModBUS_cmd cmd;
	uint16_t address;
	uint16_t request_data;
	uint32_t answer_data;
    enum ModBUS_packet_status status;
    enum ModBUS_Error error;
    
}ModBUS_packet_queue[MODBUS_QUEUE_SIZE]=
{{1, 0x03, 0x602C, 0x0002, 0, 0, 0},
 {2, 0x03, 0x602C, 0x0002, 0, 0, 0}};/*
 {2, 0x03, 0x0B1C, 0x0002, 0, 0, 0},
 {2, 0x03, 0x0B1D, 0x0002, 0, 0, 0}
};*/

volatile uint8_t read_queue_USART_pointer;

uint8_t Check_Error(uint8_t *Response)
{
	if(Response[1]&0x80)
		return Response[2];
	return 0;
	// Error code
	// 0x01 - Function code error
	// 0x02 - Access address error
	// 0x03 - Data error, such as write data exceeding the limit
	// 0x08 - CRC check error
}

void Recive_ModBUS(uint8_t *Response)
{
    if(USART_Get_Error()== USART_NO_ERROR)
    {
        if(Response[0] != ModBUS_packet_queue[read_queue_USART_pointer].SlaveID)
        {
            ModBUS_packet_queue[read_queue_USART_pointer].answer_data = 0;
            
            ModBUS_packet_queue[read_queue_USART_pointer].error = SLAVE_ID_ERROR;
        } else if(Check_Error(Response))
        {
            ModBUS_packet_queue[read_queue_USART_pointer].answer_data = 0;
            
            if(Check_Error(Response) & 0x01)
                ModBUS_packet_queue[read_queue_USART_pointer].error = FUNCTION_CODE_ERROR;
            else if(Check_Error(Response) & 0x02)
                ModBUS_packet_queue[read_queue_USART_pointer].error = ACCESS_ADDRESS_ERROR;
            else if(Check_Error(Response) & 0x03)
                ModBUS_packet_queue[read_queue_USART_pointer].error = DATA_ERROR;
            else if(Check_Error(Response) & 0x04)
                ModBUS_packet_queue[read_queue_USART_pointer].error = CRC_ERROR;
        } else if(Response[1] != Read_Holding_Registers)
        {
            ModBUS_packet_queue[read_queue_USART_pointer].answer_data = 0;
            
            ModBUS_packet_queue[read_queue_USART_pointer].error = CMD_ERROR;
        } else
        {    
            ModBUS_packet_queue[read_queue_USART_pointer].answer_data = (Response[4]<<24) | (Response[5]<<16) | (Response[6]<<8) | Response[7];
        }
        
        if(ModBUS_packet_queue[read_queue_USART_pointer].error)
            ModBUS_packet_queue[read_queue_USART_pointer].status = ANSWER_ERROR;
        else
            ModBUS_packet_queue[read_queue_USART_pointer].status = DONE; 
    }
    else
    {
        ModBUS_packet_queue[read_queue_USART_pointer].status = SEND_ERROR;
        ModBUS_packet_queue[read_queue_USART_pointer].error = TIMEOUT;
    }
    if(++read_queue_USART_pointer == MODBUS_QUEUE_SIZE) read_queue_USART_pointer = 0;    
}
void Send_ModBUS()
{
    if(ModBUS_packet_queue[read_queue_USART_pointer].status != SEND)
    {
        char msg[8];
        uint16_t reg = ModBUS_packet_queue[read_queue_USART_pointer].address;
        uint16_t data = ModBUS_packet_queue[read_queue_USART_pointer].request_data;
        msg[0] = ModBUS_packet_queue[read_queue_USART_pointer].SlaveID;
        msg[1] = ModBUS_packet_queue[read_queue_USART_pointer].cmd;
        msg[2] = (uint8_t)(reg>>8);
        msg[3] = (uint8_t)(reg&0xFF);
        msg[4] = (uint8_t)(data>>8);
        msg[5] = (uint8_t)(data&0xFF);
        
        uint16_t crc = ModBUS_CRC16((uint8_t*)msg,6);
        msg[6]=(uint8_t)(crc&0xFF);
        msg[7]=(uint8_t)(crc>>8)&0xFF;
        
        if(USART1_put(8, msg, Recive_ModBUS) == USART_SEND)
            ModBUS_packet_queue[read_queue_USART_pointer].status = SEND;
    }
}

void ModBUS_Exec()
{
    Send_ModBUS();
}

 

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


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

1 hour ago, Reystlin said:

Задачи multimaster не стоит а с несколькими slave не вижу проблемы.

Причём тут мультимастер? На четырёх портах сидят четыре мастера. Ещё на дух портах сидят два слэйва. Итого задействовано 6 штук UART. Таймеров-то хватит? :)

Я в ПЛК ещё сделал статистику запросов. На практике полезно при поиске несправностей в сети. Для примера фрагмент карты памяти простого ПЛК с двумя UART, каждый может быть сконфигурирован как мастер, или как слэйв.

image.thumb.png.4190283923921804a067032e61a4a86d.png

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


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

у меня задача попроще. есть 10 серводвигателей и нужно постоянно циклически их положение получать.

Без таймера нормально не работает - нужна задержка между отправкой текущего пакета и следующего.

так-же таймаут нужно как-то определять

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

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


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

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

у меня задача попроще. есть 10 серводвигателей и нужно постоянно циклически их положение получать.

Без таймера нормально не работает - нужна задержка между отправкой текущего пакета и следующего.

Это всё элементарно делается на одном таймере.

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

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

Я про неё ещё несколько десятков постов назад писал. Странно что не заметили.....

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


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

Неужели у уартов STM32 нет флага, который выдерживал бы заданную паузу после передачи последнего символа из посылки DMA? Ну, даже если так, можно кривой модбас хоть на шести уартах одновременно реализовать при помощи всего одного систика, просто счетчики отдельные завести на каждый уарт.

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


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

39 minutes ago, jcxz said:

Это всё элементарно делается на одном таймере.

Так у меня и так на одном таймере, на TIM7 оба события....

в зависимости от состояния перестраиваю время срабатывания прерывания.

40 minutes ago, jcxz said:

Я про неё ещё несколько десятков постов назад писал. Странно что не заметили.....

Видимо проглядел... спасибо

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


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

9 hours ago, Reystlin said:

Без таймера нормально не работает - нужна задержка между отправкой текущего пакета и следующего.

Если МК мастер, то тебе достаточно перед отправкой сделать паузу >1.75мс (заметь, "больше", а не строго "равно" ), что при работе под FreeRTOS не вызывает проблем. Пишешь код под ОС РВ, а софтовые таймеры ОС упорно игнорируешь, хотя тут они очень удобны.

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

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


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

Лучше кривой модбас реализовать в виде конечного автомата: ты ему в кольцевой буфер отправляешь структуры данных, которые нужно будет переслать, а он уже самостоятельно по прерываниям контролирует паузы и т.д., и т.п. В суперлупе каждый проход вызывать этот modbus_process().

Вот с приемом намного проще, двух буферов-структур хватит: заполняешь себе посредством DMA один буфер, как только получил прерывание idle, выставляешь флаг готовности буфера и начинаешь заполнять второй. А в суперлупе какой-нибудь modbus_incoming() будет обрабатывать данные из этого буфера.

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

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


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

12 часов назад, Reystlin сказал:

Так у меня и так на одном таймере, на TIM7 оба события....

Я говорил про случай работы с несколькими каналами связи параллельно (несколькими UART). О такой работе тут выше говорили другие люди. Ваш код имеет жёсткую связь UART-таймер, а значит потребует отдельного таймера на каждый новый UART. Что излишне расточительно.

 

PS: Также нет никакой обработки ошибок. А если например IDLE появился не из-за того, что входящий пакет закончился, а из-за обрыва на линии?

Также нет никакой обработки коллизий событий от таймера и UART. Например запустился отсчёт времени приёма; RX-пакет приходит в самом конце таймаута; получаете прерывание IDLE от UART чуть раньше, а запрос на прерывание от таймера приходит когда находитесь в ISR UART. Что будет? каков приоритет у ISR UART и ISR таймера? Предположим у таймера приоритет <= приоритету UART. Тогда сразу после выхода из ISR UART получите прерывание от таймера, которое переведёт вашу переменную состояния в IDLE (хотя в это время вроде должна идти выдержка на передачу). Если приоритет ISR таймера выше чем у UART - тоже ничего хорошего - опять катавасия. Так как нету корректного взаимодействия между обработчиками событий UART и таймера. И нету чёткой отработки машины состояний.

И вообще всё очень сыро и плохо продумано....

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


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

1 hour ago, jcxz said:

И вообще всё очень сыро и плохо продумано....

То есть не мне одному видится, что ТС не продумал как строить Модбас-мастер. Он посложнее слэйва будет.

1 hour ago, jcxz said:

Также нет никакой обработки ошибок

Она тут особо не нужна. В Модбас-фрейме есть длина блока данных и контрольная сумма. Их ведь всё равно проверять перед обработкой запроса.

 

ИМХО, ТС упорно игнорирует возможности FreeRTOS, поэтому у него получается громоздкий и негибкий код в части отработки таймаутов.

3 hours ago, Eddy_Em said:

модбас реализовать в виде конечного автомата

Вот это хороший способ. У меня так и сделано. Работает в отдельной задаче. Всё получается очень просто: быстренько прошёлся по состояниям и через yield() вернул время ОС.

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


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

13 hours ago, tonyk_av said:

Если МК мастер, то тебе достаточно перед отправкой сделать паузу >1.75мс (заметь, "больше", а не строго "равно" ), что при работе под FreeRTOS не вызывает проблем. Пишешь код под ОС РВ, а софтовые таймеры ОС упорно игнорируешь, хотя тут они очень удобны.

 

у меня нет ОС.... не добавлять же её только из-за работы с ModBUS

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


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

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

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

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

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

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

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

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

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

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