STM32 новичку в ARM что к чему

Кто любит RISC в жизни, заходим, не стесняемся.
Ответить
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

[uquote="Lum1noFor",url="/forum/viewtopic.php?p=4236080#p4236080"]Судя по моим мучениям с этой простейшей штукой...[/uquote]
Сделай на IDLE и не мучайся. После этого и DMA прикручивается просто и логично. И таймер сэкономишь. Механизм IDLE как и RTO измеряет паузу в битах передаваемых данных, потому не зависит от скорости обмена в отличие от метода с таймером TIM.
Реклама
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

Появилось понимание, откуда растут ноги: проблема всегда была перед глазами:

Код: Выделить всё

void USART1_IRQHandler(void) {

	if (SymbolCount == 0) {
				  TIM4->CNT = 0;
				  TIM4->CR1 |= TIM_CR1_CEN;
	}

	ReceivedText[SymbolCount++] = USART1->DR;
}


void TIM4_IRQHandler(void) {

TIM4->SR &=~ TIM_SR_UIF;

TIM4->CR1 &=~ TIM_CR1_CEN;

USART_SendStr("Time out!"); // ПРОБЛЕМА ЗДЕСЬ!

SymbolCount = 0;

}
Если убрать строчку с передачей текста ошибки через USART1, все работает нормально. Все прерывания, кроме RXNE выключены. Код инициализации прерывания прикрепляю ниже.

Код: Выделить всё

#define USART_TX_ON (GPIOB->ODR |= GPIO_ODR_ODR5)
#define USART_TX_OFF (GPIOB->ODR &=~ GPIO_ODR_ODR5)

const Baudrate = 9600;

void USART1_Init(void) {
	
	GPIOA->CRH		&=~ GPIO_CRH_CNF9_0								; // PA9 as alternate funtion Push-Pull (TX). (CNF = "0b10").
	GPIOA->CRH 		|= 	GPIO_CRH_CNF9_1								;
GPIOA->CRH 		|=  (GPIO_CRH_MODE9 | GPIO_CRH_MODE9)			; // PA9 is OUTPUT, 50MHz max. (MODE = "0b11").

        GPIOA->CRH		&=~ GPIO_CRH_CNF10_0							; // PA10 as input with Pull-Up (RX) (CNF = "0b10").
	GPIOA->CRH 		|= 	GPIO_CRH_CNF10_1							;
	GPIOA->CRH 		&=~  (GPIO_CRH_MODE10 | GPIO_CRH_MODE10)		; // PA10 as Input.
	
	
	GPIOB->CRL 		&=~ (GPIO_CRL_CNF5_0 | GPIO_CRL_CNF5_1)			; // PB5 as output with Push-Pull (RX/TX). (CNF = "0b00").
	GPIOB->CRL 		|=  (GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1)		; // PA5 is OUTPUT, 50MHz max. (MODE = "0b11").
	
	
	GPIOB->ODR		&=~	GPIO_ODR_ODR5								; // TX = 0;
	
	
	USART1->BRR     = SystemCoreClock/Baudrate;
	
	USART1->CR1 	= 0;
	USART1->CR2 	= 0;
	USART1->CR3 	= 0;
	
	USART1->CR1		|=  USART_CR1_RXNEIE							; // Enable RX interrupt.
	USART1->CR1		&=~ USART_CR1_TXEIE								; // Disable TX interrupt.
	USART1->CR1		&=~ USART_CR1_IDLEIE							; // Disable Idle interrupt.
	USART1->CR1		&=~ USART_CR1_TCIE								; // Disable transmit complete interrupt.
	USART1->CR1		&=~ USART_CR1_PEIE								; // Disable PEIE interrupt.
	USART1->CR3		&=~ USART_CR3_CTSIE								; // Disable CTSIE interrupt.
	
	
	NVIC_SetPriority(USART1_IRQn, 5)								; // Set USART1 IRQ Priority.
	NVIC_EnableIRQ (USART1_IRQn)									;
	
	USART1->CR1		|= 	USART_CR1_UE								; // Enable USART1.
	USART1->CR1		|= 	USART_CR1_RE								; // Enable USART1 Reciever.
	USART1->CR1		|= 	USART_CR1_TE								; // Enable USART1 Transmitter.
	
}

Код: Выделить всё

void USART_SendSymbol(uint8_t usymb) {

USART_TX_ON;

while (((USART1->SR & USART_SR_TXE) >> USART_SR_TXE_Pos) == 0); // Wait for TXE flag.

USART1->DR = usymb;             // Trancieve data.

while (((USART1->SR & USART_SR_TXE) >> USART_SR_TXE_Pos) == 0); // Wait for TXE flag.

Delay_us(1000);

USART_TX_OFF;

}



void USART_SendStr(char *ustr) {
	  char *q = ustr;
	  while (*q)
	  {
		  USART_SendSymbol(*q++);
	  }
}

В чем может быть причина?
Реклама
Друг Кота
Аватара пользователя
Сообщения: 4906
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Миллисекундная задержка на каждый символ - итого, USART_SendStr() висит несколько миллисекунд в прерывании таймера. - как-то некрасиво
Контактная информация:
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

WiseLord писал(а):Миллисекундная задержка на каждый символ - итого, USART_SendStr() висит несколько миллисекунд в прерывании таймера. - как-то некрасиво
Согласен, но без нее у меня не получилось. У меня стоит конвертер USART -> RS485, и для его работы мне нужно переключать линию Read/Write. Соответственно, при передаче я подаю на линию Read/Write единицу, а в конце передачи я должен перевести ее обратно в 0. И если после окончания передачи я переведу линию Read/Write раньше, чем через 1 мс, передача идет криво. Переписал код вот так (теперь задержка всего одна):

Код: Выделить всё

void USART_SendSymbol(uint8_t usymb) {

while (((USART1->SR & USART_SR_TXE) >> USART_SR_TXE_Pos) == 0); // Wait for TXE flag.

USART1->DR = usymb;             // Trancieve data.

}



void USART_SendStr(char *ustr) {
	  char *q = ustr;
	  
	  USART_TX_ON;
	  
	  while (*q)
	  {
		  USART_SendSymbol(*q++);
	  }
	  
	  Delay_us(1000);
	  
	  USART_TX_OFF;
}
Реклама
Эиком - электронные компоненты и радиодетали
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

Lum1noFor писал(а):И если после окончания передачи я переведу линию Read/Write раньше, чем через 1 мс, передача идет криво
Открою тебе страшную тайну: у USART есть ещё флаги, кроме USART_SR_TXE, показывающие состояние передатчика. А ещё у USART есть ДВА регистра: один для загрузки передаваемого байта, а второй сдвиговый, содержимое которого выдаётся на ножку TX. Флаг USART_SR_TXE показывает тебе, что регистр для получения от тебя байта свободен, а вот окончание выдачи битиков в линию из второго регистра показывает флаг USART_SR_TC. Вот по прерыванию от него-то тебе и нужно переключать линию.

Это всё нарисовано в мурзилке у СТМ.

А ещё одна страшная тайна- это MAX13487...
Последний раз редактировалось tonyk Ср май 25, 2022 14:04:33, всего редактировалось 1 раз.
Реклама
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

[uquote="tonyk",url="/forum/viewtopic.php?p=4236343#p4236343"]
Lum1noFor писал(а):И если после окончания передачи я переведу линию Read/Write раньше, чем через 1 мс, передача идет криво
Открою тебе страшную тайну: у USART есть ещё флаги, кроме USART_SR_TXE, показывающие состояние передатчика. А ещё у USART есть ДВА регистра: один для загрузки передаваемого байта, а второй сдвиговый, содержимое которого выдаётся на ножку TX. Флаг USART_SR_TXE показывает тебе, что регистр для получения от тебя байта свободен, а вот окончание выдачи битиков в линию из второго регистра показывает флаг USART_SR_TC. Вот по прерыванию от него-то тебе и нужно переключать линию.

Это всё нарисовано в мурзилке у СТМ.[/uquote]


Исправил, спасибо за замечание. Но проблему с перекрывающимися прерываниями это не решает.

UPD:

Мое заключение о том, что после возврата из одного прерывания в другое по стеку, выполнение первого прерывания начинается сначала НЕВЕРНО - проверил. Все выполняется откуда надо. Чтобы не вводить людей в заблуждение.
Последний раз редактировалось Lum1noFor Ср май 25, 2022 14:17:59, всего редактировалось 1 раз.
Реклама
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

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

А ещё одна страшная тайна- это MAX13487...

Добавлено after 1 minute 40 seconds:
Не вижу твоих обработчиков прерываний. По-идее, в каждом из них должно запрещаться прерывание от другого.
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

[uquote="tonyk",url="/forum/viewtopic.php?p=4236346#p4236346"]Я ж тебе с самого начала предлагал делать на IDLE, а ты сам себе геморрой устроил на ровном месте.

А ещё одна страшная тайна- это MAX13487...

Добавлено after 1 minute 40 seconds:
Не вижу твоих обработчиков прерываний. По-идее, в каждом из них должно запрещаться прерывание от другого.[/uquote]

Если я в прерывании USART1 запрещу выполнение прерывания таймера, то он не будет запускаться вообще.

Вот мои обработчики прерываний (USART1 - приоритет 5, TIM4 - приоритет 4):

Код: Выделить всё

void USART1_IRQHandler(void) {

	if (SymbolCount == 0) {
				  TIM4->CNT = 0;
				  TIM4->CR1 |= TIM_CR1_CEN;
	}

	ReceivedText[SymbolCount++] = USART1->DR;

}


void TIM4_IRQHandler(void) {

TIM4->SR &=~ TIM_SR_UIF;

TIM4->CR1 &=~ TIM_CR1_CEN;

USART_SendStr("Time out!");

SymbolCount = 0;

}
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

у тебя не правильный алгоритм.
Ты должен запускать таймер в режиме OPM сразу после загрузки UART_DR, а внутри обработчиков запрещать оба прерывания и сбрасывать, например, в обработчике от таймера запрос от UART и наоборот. Тогда будет работать. Только, по-сути, так ты просто повторяешь алгоритм формирования IDLE в UART.
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

Весь день проковырялся с этой штукой, выяснил кое-что странное. Оказывается, таймер вообще тут не при чем - зря его обвиняли. Вешаю светодиод на любую ногу МК и беру простейшее прерывание USART1 (для исключения влияния чего-либо даже задержку делаю на цикле):

Код: Выделить всё

void USART1_IRQHandler(void) {

	uint32_t i = 0;

	USART1->SR	&=~  USART_SR_RXNE; // Clear USART1 RXNE Interrupt flag


		GPIOC->ODR |= GPIO_ODR_ODR12; // Enable LED

		for (i = 0; i < 65535; i++); // Delay

		GPIOC->ODR &=~ GPIO_ODR_ODR12;  // Disable LED

		for (i = 0; i < 65535; i++);  // Delay

}
Шлю один любой символ через терминал - светодиод моргает, как положено, 1 раз. Шлю его несколько раз вручную - все работает отлично. Но стоит только отправить через терминал не один символ (например, "Q"), а два или более подряд ("QE"), то светодиод начинает моргать бесконечно. То есть МК не может выйти из прерывания вообще, и гоняет его в цикле. Я так понимаю это из-за того, что следующий символ приходит раньше, чем закончилось предыдущее прерывание. Из-за чего такое может происходить?
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

Уберите задержки из прерывания!
Перед сбросом флага проверяйте из-за него вы туда попали или нет.
Проверяйте как настроены прерывания от UART - по каким причинам они могут возникнуть. Могут ли, например, из-за ошибок.
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

У тебя не правильный алгоритм.
Ессно, у тебя так будет. Тебе надо разрешать прерывание после загрузки DR, а в обработчике, после окончания передачи, его запрещать.
Друг Кота
Аватара пользователя
Сообщения: 7385
Зарегистрирован: Пт авг 28, 2009 21:34:30
Откуда: 845-й км.

Сообщение uldemir »

Есть вопрос. У меня возникает глюк и я не могу выцепить в чем причина. Поэтому пытаюсь всё проверять. Сейчас на подозрении - стек. Глюк появлялся у меня спустя около 20 секунд после начала хода. Я увеличил размер стека с 4096 до 8192 байт и один проход выполнился нормально. Второй тоже нормально, но на третьем проходе - опять появился глюк. На подозрении прерывания. Их у меня толпы.

И вот вопрос про прерывание PendSV - оно у меня довольно длинное. Но имеет самый низкий приоритет. Может ли быть так, что оно вызовется снова не завершив текущее. А как он знает, что текущее завершилось? Собственно, я иногда и в самом этом обработчике взвожу запрос на PendSV, чтобы обработчик перезапустился (там у меня машина состояний).

Код: Выделить всё

void PendSV_Handler(void) {
  profiler();
}

void profiler(void) {
...
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  // перезапуск, чтобы начать двигаться сразу
...
}
Приоритеты распределены так:

Код: Выделить всё

// IRQ priorities:
#define SysTick_IRQ_Priority  2
#define I2C_IRQ_PRI           2
#define TIMER0_IRQ_PRI        3
#define TIMER1_IRQ_PRI        3
#define USART0_IRQ_PRI        4
#define USART3_TX_IRQ_PRI     4
#define LDMA_IRQ_PRI          5
#define ADC0_IRQ_Priority     5
#define LETIMER0_IRQ_Priority 5
#define PendSV_IRQ_priority   7
Память распределена следующим образом:

Код: Выделить всё

section              size        addr
.text               72968           0
.ARM.exidx              8       72968
.stack               8192   536870912
.data                1856   536879104
.bss               173420   536880960
.heap               13136   537054384
А можно как-нибудь узнать, что стек вылез за рамки?
Потрогал лапой паяльник
Аватара пользователя
Сообщения: 362
Зарегистрирован: Вс апр 03, 2022 07:01:29

Сообщение НовыйДень »

Я увеличил размер стека с 4096 до 8192 байт
Это влияет только на предупреждение при компиляции. Физически во время работы МК размер стека не контролируется. Можно посмотреть только в аналитике (Static Stack Analyzer) величину максимального потребления стека функциями. В ОЗУ вот так всё это укладывается:
Изображение
Каждый вызов вложенной функции или прерывания увеличивает потребление стека. При возврате из вложенных функций и прерываний потребление стека уменьшается. А если стек увеличивается слишком сильно, он наползет на область хранения статических переменных и повредит их содержимое. В этом и будет проявляться косяк - в повреждении значений статических переменных.
Для контроля положения вершины стека есть регистр ядра SP (Stack Pointer). Регистр не имеет адреса и доступен либо через ассемблер, либо через сишную обертку __get_MSP(), возвращается текущий адрес вершины стека.
Поскольку вы используете динамическое выделение памяти, под него будет выделяться место, начиная от конца хранения статических переменных (по рисунку выше - в белой части). И вот тут возможно взаимное столкновение динамического выделения памяти (которое идет вверх по адресам) и стека (который идет вниз по адресам).
В чем именно проявляется глюк? В каком поведении? Проанализируйте, что конкретно портится.
Может ли быть так, что оно вызовется снова не завершив текущее. А как он знает, что текущее завершилось?
Нет, то же самое прерывание имеет тот же самый приоритет и вытеснить само себя не может. Поэтому, флаг запроса будет выставлен, но как только произойдет выход из текущего прерывания, МК снова войдет в это прерывание с его начала. Более подробно о работе прерываний прочтите в Programming Manual.
Друг Кота
Аватара пользователя
Сообщения: 7385
Зарегистрирован: Пт авг 28, 2009 21:34:30
Откуда: 845-й км.

Сообщение uldemir »

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

Расположение стека я показал. Причем в отличии от того, что делал Keil, у силабов стек расположен внизу памяти и данные запортить не может. Он может только наехать на область где мог бы быть флеш. Но тогда, если бы адрес возврата был бы записан в пустоту, я бы наверняка получил бы HardFault.

Ок, проведу тесты и посмотрю, может зря я на стек бочку качу.
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

[uquote="COKPOWEHEU",url="/forum/viewtopic.php?p=4236415#p4236415"]Уберите задержки из прерывания!
Перед сбросом флага проверяйте из-за него вы туда попали или нет.
Проверяйте как настроены прерывания от UART - по каким причинам они могут возникнуть. Могут ли, например, из-за ошибок.[/uquote]


Специально проверил - все прерывания кроме RX отключены. Даже пошел в лоб вот так:

Код: Выделить всё

void USART1_IRQHandler(void) {

uint32_t i = 0;

NVIC->ICER[1] |= 0x20			; // Disable USART1 interrupt.

USART1->SR = 0					;


GPIOC->ODR |= GPIO_ODR_ODR12	; // Enable LED.

for (i = 0; i < 65535; i++)		; // Delay

GPIOC->ODR &=~ GPIO_ODR_ODR12	; // Disable LED.

for (i = 0; i < 65535; i++)		; // Delay

NVIC->ISER[1] |= 0x20			; // Enable USART1 interrupt.

}
Симптомы те же: если к моменту прихода следующей посылки предыдущее прерывание USART1 еще не закончило свое выполнение (например, внутри прерывания оказалась большая задержка, как у меня), тогда это самое прерывание начнет бесконечно крутиться в цикле, хоть флаги обобнуляйся. Я понимаю, что это не правильно пихать в прерывание задержки, но я хочу понять, из-за чего это, и как с этим бороться.

UPD: разобрался.

Чтобы отработать такую ситуацию, есть флаг ORE - "Overrun error". Прерывание по этому флагу то же самое, что и для RX - RXNEIE. Поэтому оно и вылезает у меня всегда. Проблема была в том, что этот флаг невозможно сбросить простым обнулением, нужно выполнить сначала чтение из USART1->DR, а затем чтение из USART1->SR. Только тогда он сбрасывается.

В итоге, эта проблема решилась вот так:

Код: Выделить всё

void USART1_IRQHandler(void) {

if ((((USART1->SR & USART_SR_RXNE) >> USART_SR_RXNE_Pos) == 1) & (((USART1->SR & USART_SR_ORE) >> USART_SR_ORE_Pos) != 1)) {

ReceivedText[SymbolCount++] = USART1->DR;

} else {

(void)USART1->DR;
(void)USART1->SR;

}
Здесь проверяется флаг RXNE (хотя, в любом случае мы попали в это прерывание именно через него, но если в друг включены другие прерывания, то проверять его нужно), и флаг ORE не должен быть равен единице. Если все нормально, читаем данные. Если Overrun, то просто сбрасываем все флаги и ничего не делаем. Ну, либо, выдаем ошибку.
Это не хвост, это антенна
Сообщения: 1379
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

Мне страшно представить, когда ты начнёшь осваивать UART+DMA... А ведь там без IDLE или RTO никак...
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18795
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

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

Мой уютный бложик... заходите!
Контактная информация:
Открыл глаза
Сообщения: 65
Зарегистрирован: Ср сен 24, 2014 12:30:09

Сообщение Lum1noFor »

Проблема была в том, что на одном бите разрешения прерывания RXNEIE висело два флага от разных событий - RXNE и ORE. И чтобы сбросить один из этих флагов, ORE, нужно не просто считать регистр, а выполнить определенную последовательность действий. В итоге, я получаю 10 символов по USART1 вот таким кодом:

Код: Выделить всё

void USART1_IRQHandler(void) {

uint8_t i = 0;

if ((((USART1->SR & USART_SR_RXNE) >> USART_SR_RXNE_Pos) == 1) & (((USART1->SR & USART_SR_ORE) >> USART_SR_ORE_Pos) != 1) & (((USART1->SR & USART_SR_IDLE) >> USART_SR_IDLE_Pos) != 1)) {

	ReceivedText[SymbolCount++] = USART1->DR;
	(void)USART1->SR;

} else {

	if (((USART1->SR & USART_SR_ORE) >> USART_SR_ORE_Pos) == 1) {

		USART_SendStr("Overrun!");

		(void)USART1->DR;
		(void)USART1->SR;

		memset(ReceivedText, 0, sizeof ReceivedText);

		SymbolCount = 0;

	}

	if (((USART1->SR & USART_SR_IDLE) >> USART_SR_IDLE_Pos) == 1) {
		if (SymbolCount != 10) {

			USART_SendStr("Time out!");
			memset(ReceivedText, 0, sizeof ReceivedText);
			SymbolCount = 0;

			(void)USART1->DR;
			(void)USART1->SR;

		} else {

			SymbolCount = 0;

				for (i = 0; i < 11; i++) {
					ReceivedCommand[i] = ReceivedText[i];
				}

			ReceivedCommand[11] = 0;

			(void)USART1->DR;
			(void)USART1->SR;

		}
	}
}

}


Вроде все работает как надо, но после приема 10 символов почему-то еще один лишний раз срабатывает прерывание и выдается одна ошибка Time out. Как будто принимается один лишний символ. В счетчике SymbolCount на тот момент оказывается число 1, а должно быть 0.
Первый раз сказал Мяу!
Сообщения: 26
Зарегистрирован: Вт май 17, 2022 09:49:48

Сообщение Viktorkin »

[uquote="НовыйДень",url="/forum/viewtopic.php?p=4236747#p4236747"]В ОЗУ вот так всё это укладывается:
Изображение
l.[/uquote]
Ооо, спасибо, наглядно изображено.
Ответить

Вернуться в «ARM»