Всем доброго времени суток, сегодня мы продолжим исследовать возможности STM32CubeMx и рассмотрим таймеры. Вот предыдущие статьи цикла:
Без лишних слов, перейдем сразу к делу. В предыдущей статье мы разобрались как настраивать и использовать порты ввода-вывода, а сегодня рассмотрим конфигурацию таймера и прерываний. Пусть будет такая задача - задействуем таймер таким образом, чтобы он генерировал прерывания, допустим, каждые 500 мс, и в прерывании помигаем диодом для наглядной демонстрации работы. В принципе, ничего сложного, классический тестовый пример, так что приступаем.
Выполняем привычную последовательность действий - создаем и настраиваем новый проект в STM32CubeMx. И начнем с уже знакомого - активируем один из выводов для работы в качестве выхода, чтобы управлять светодиодом. Пусть будет PD12, не буду даже скриншот приводить, все это мы уже осуществляли в предыдущей статье курса 👍
Вторым этапом в данном случае будет активация и настройка непосредственно таймера, возьму TIM3 для этого примера, существенной разницы, какой именно модуль задействовать, нет:
Выбрав TIM3 на левой панели осуществляем последовательно следующее:
- собственно, активируем его, задав Clock Source
- задаем предделитель частоты таймера через поле Prescaler
- и, наконец, задаем период в поле Counter Period.
Сегодня у нас базовый пример, поэтому остальные настройки не трогаем, оставим пока на будущее. Итак, разбираемся с частотами и временными интервалами.
Таймер сидит на шине APB1, частота которой составляет 16 МГц (это настройка по умолчанию для данного контроллера и текущей версии CubeMx - как изменить или узнать тактовые частоты я расскажу в следующей статье). Выбираем делитель частоты равным 16000 (необходимо установить значение, меньшее на 1). В итоге получаем (16 МГц / 16000) = 1000 Гц. То есть один тик таймера будет соответствовать 1 мс. Установив период 500, получим прерывания по переполнению таймера каждые 500 мс - то, что нам и надо. Здесь же, во вкладке "NVIC Settings", нужно включить прерывание, и на этом процесс настройки завершен:
Генерируем код и получаем готовый проект для выбранной IDE.
Теперь осталось совсем немного - запустить наш таймер (STM32CubeMx занимается только инициализацией, все активные действия мы должны совершать сами). Кроме того, в прерывании по переполнению таймера мы будем менять состояние светодиода. Cам обработчик прерывания CubeMx создал за нас, и найти его совсем несложно - для всех прерываний генерируется отдельный файл stm32f4xx_it.c (либо для другого контроллера аналогичный, например, stm32f1xx_it.c). В принципе, мы можем поместить наш код непосредственно в обработчик. Но HAL предлагает альтернативный способ с использованием callback-функций. Пара слов об этой концепции...
В библиотеке реализованы различные функции, объявленные с атрибутом weak
, которые может использовать пользователь для добавления своего кода. Атрибут weak
используется для функций-заглушек, которые могут быть переопределены. Таким образом, при наступлении определенного события в библиотеке происходит вызов такой callback-функции, тело которой является по умолчанию пустым. Но если программист переопределил эту функцию, то будет вызван этот новый вариант.
Для наглядности и понимания процессов рассмотрим на нашем конкретном примере. Механизм выглядит следующим образом:
- Для таймера (как и для любой другой периферии) есть ряд callback-функций. Их список можно найти в описании HAL, нас же сейчас интересует вполне конкретная, а именно
HAL_TIM_PeriodElapsedCallback()
. Именно она соответствует событию переполнения таймера. - Эту функцию мы определяем, поместив в нее необходимый функционал.
- И по итогу она будет вызвана автоматически из библиотеки при наступлении соответствующего события.
Давайте все это поэтапно осуществим. Для начала идем в main()
и запускаем таймер:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim3); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Поскольку мы будем использовать прерывания, то нам нужна функция HAL_TIM_Base_Start_IT()
. А теперь в этом же файле main.c добавим callback-функцию. При этом обязательно размещаем внутри секций вида:
/* USER CODE BEGIN 4 */ /* USER CODE END 4 */
Это нужно для того, чтобы после того как файлы будут перегенерированы (при изменении проекта в CubeMx), данный код был также автоматически перенесен в новые файлы:
/* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); } /* USER CODE END 4 */
Здесь при помощи HAL_GPIO_TogglePin()
мы меняем состояние светодиода.
В итоге функция будет вызываться каждый раз при переполнении таймера (аналогично соответствующему прерыванию). И, поскольку тут в качестве аргумента наличествует структура TIM_HandleTypeDef
, которая является базовой при работе с таймерами, то мы можем ее использовать, чтобы убедиться, что прерывание вызвано именно TIM3
, а не другим модулем:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); } }
Теперь все готово, собираем проект и прошиваем контроллер. Сразу же видим мигающий диод, причем мигает он каждые 500 мс, а следовательно задача успешно реализована 👍
Как видите, STM32CubeMx оказывается довольно-таки удобным инструментом для решения своих задач, и это заметно даже в небольших проектах, поэтому в следующих статьях мы продолжим его изучение, оставайтесь на связи!
Спасибо за материал! Ответьте, пожалуйста, на вопрос! Можно ли при работе с АЦП устанавливать частоту дискретизации, т.е. количество выборок в секунду? Или нужно использовать прерывание по таймеру и делать однократное преобразование?
Ну АЦП у тебя будет обрабатывать сигнал с высокой частотой (1 мвыб/ с, например), а уж сколько раз считывать значение - твое дело. Можно спокойно включить АЦП в непрерывном режиме и считывать значение по таймеру, допустим, 5 раз в секунду.
Спасибо за ответ! А в каком регистре задается значение, указанное вами 1 мвыборка/с? Наверное, слишком бегло я просмотрел даташит и не увидел. Могли вы привести просто пару строчек кода с примером! Спасибо!
Там можно менять время выборки (sampling time). Если я правильно помню, регистр ADC->SMPR.
STM32Cube вещь интересная.
Но! Сделав простой пример, поразился размером прошивки даже с оптимизацией. На SPL в 3 раза меньше получается, уж не говоря про проект на чисто регистрах.
И мало хелпа, я так и не смог запустить ADC.
С размером кода - это, конечно, минус HAL_Driver, но в то же время это вполне ожидаемый минус, то есть, в принципе, ничего неожиданного в этом нет =)
В Версии иара 7.3 строка HAL_TIM_Base_Start_IT(&htim3); работает в таком виде только: HAL_TIM_Base_Start_IT(htim3); , на описанный вид ругается. И еще, чтобы сделать, например 10 мс период, необходимо например при 1 кГц предделителя ставить 9 (а не 10) в число периода.
а также строка HAL_TIM_IRQHandler(&htim3); меняется на HAL_TIM_IRQHandler(&htim3);
*извиняюсь: В Версии иара 7.3 строка HAL_TIM_Base_Start_IT(&htim3); работает в таком виде только: HAL_TIM_Base_Start_IT(&htim3);
также извиняюсь HAL_TIM_IRQHandler(&htim3); на HAL_TIM_IRQHandler(&htim3);
хмхм чето не ладное с ответами((( HAL_TIM_Base_Start_IT(&htim3) строка
пишется без &
Беда с этими amp просто какая то) Но главное, что суть ясна, спасибо за дополнения =)
И насчет точности времени: приходит на APB1 Timer Clocks 2 МГц, выставляю такие параметры (используя таймер 12)
htim12.Instance = TIM12;
htim12.Init.Prescaler = 2000;
htim12.Init.CounterMode = TIM_COUNTERMODE_UP;
htim12.Init.Period = 3;
htim12.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim12);
получаю на осцилле период в 8 мс (частотой 125 Гц), то есть таймер меняется через каждые 4 мс. То есть получается, что для получения нужного периода необходимо убавлять единицу, возможно это происходит, что счет начинается с 0, и по факту при занесении константы 3, у нас будет период 4 мс., верно ли я полагаю???
Да, период по всей видимости надо ставить на единицу меньше...
И еще, прескаллер 16000-1 для повышения точности.
Подскажите, break interrupt - это прерывания чем вызываются.
Здравствуйте. Подскажите пожалуйста. Вот строка HAL_TIM_Base_Start_IT(&htim3); запускает таймер? То есть таймер начинает считать только после этой строки. Правильно ли я понял?
И в связи с этим вопрос: а при настройке таймера в режим аппаратного ШИМ эта строка так же необходима?
Да, иначе таймер просто выключен будет
Друзья, недавно в stm. Сразу начал осваивать hal. Требуется помощь.
Подскажите, пожалуйста, как правильно обрабатывать прерывания DMA? Я настроил прерывания от DMA. Настроил таймеры. Обработчик находится в _it.c. я так понимаю.
У меня алгоритм следующий. 1) Таймер по захвату передает данные посредством DMA. DMA по окончанию приема должно выработать прерывание. Но так как перывания от DMA объединены, нам надо убедиться, что это "прерывание по завершению" прочитав соответствующий флаг.
Верная ли последовательность?
Как это сделать (прочитать флаг)?
Нужно ли потом очищать флаги?
2) Нужно определить по спаду или нарастанию на таймере-счетчике произошел запрос от DMA.
Как прочитать регистр флагов прерывания?
3) подскажите как пользоваться библиотекой, иногда получается (с выводами, с USART, а иногда нет).
Вот например очистка флагов DMA. В dma.h есть такой define:
#define __HAL_DMA_CLEAR_FLAG(__HANDLE__, __FLAG__) \
(((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HIFCR = (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LIFCR = (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HIFCR = (__FLAG__)) : (DMA1->LIFCR = (__FLAG__)))
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)\
(((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HISR & (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LISR & (__FLAG__)) :\
((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HISR & (__FLAG__)) : (DMA1->LISR & (__FLAG__)))
Как им воспользоваться, как с помощью него считывать?
И еще один вопрос. После создания проекта в Cube в keil у меня на некоторых строках в файлах .h следующая надпись unknow type name uint32_t , в частности напротив этой строки #include "stm32f4xx_hal_def.h" . Это ненормально же? Как это исправить можно?
Если Кейл четвертый, скинь мне проект на почту, я посмотрю, в чем проблема.
Aveal.MicroTechnics@gmail.com
Keil5. Сразу после генерации проекта в Cube, еще ничего не пишу, просто проект собираю и в .h файлах такая штука появляется
TIM1 расширенный режим. Генерация комплементарных сигналов PWM с deadtime.
Запуск HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
Вопрос: сигнал генерится только на одном выходе TIM1_CH1N (PA7), на другом выходе TIM1_CH1 (PA8) фиксированный уровень. В чем может быть проблема?
Если ВЫ уж используете HAL, то советую не в прерывании писать а использовать Callback, например HAL_TIM_PeriodElapsedCallback(прерывание по периоду).
Здравствуйте.
Подскажите пожалуйста как пользоваться Callback-ми для обработки данных? Вот допустим получил данные по SPI, мне необходимо обработать их. Если не писать обработку в обработчике прерываний, то как это сделать с помощью callback?
Просто пиши свой код в callback-функции вместо прерывания
Здравствуйте.
Столкнулся с проблемой разного времени выхода на вектор прерывания. В cube установил высокий приоритет HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); у всех остальных HAL_NVIC_SetPriority(x, 0, 1);
Вот код void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
counter_time ++;
switch (counter_time)
{
case 1:
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
break;
case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); flag_1 = 1; break;
case 3: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); break;
case 4: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); counter_time =0;break;
}
}
Смотрел осциллографом pin stm32f429. Все болтается такое ощущение что что-то выполняет какие то операции и не дает сразу перейти на вектор но что не понятно. Подскажите что делать
Здравствуйте!
Я начинающий, поэтому не судите строго, но данное занятие я проделать не смог.
Не объясняется что , как и почему, почему именно эти значения в инфекциях и откуда взяли. Также куда вставили последний кусок
В общем приходится додумывать самому
в общем разобрался откуда ноги растут
https://www.youtube.com/watch?v=GrF0Kto5c48
вот не плохое видео. которое хорошо дополняет этот урок
сам все делаю в воркбенхе код после куба автоматом разбит на файлы
править нужно майн и stm32f1xx_it.c
единственное пока не понял у меня 103 контроллер от кварца на 72 мегагерца. таймер 2 на 1APB частота 36М делил на 36к и потом умножал на 499 получалось 2 секунды. пока с определением времени вопрос открытый
Добрый день!
На самом деле вот в этом абзаце полностью объясняется откуда берутся два числа, которые используются в примере:
"Выбираем делитель частоты равным 16000. В итоге получаем (16 МГц / 16000) = 1000 Гц. То есть один тик таймера будет соответствовать 1 мс. Установив период 499 (это значение в Cube надо ставить на единицу меньше, чем целевое значение, аналогично и значение делителя) получим прерывания по переполнению таймера каждые 500 мс — то, что нам и надо."
Ну а последний кусок - это же прерывание, из названия функции это видно...
По поводу частоты - видимо тактирование неверно настроено
Добрый день Aveal. Спасибо за такие классные материалы по HAL, всё очень просто и понятно.
Есть у меня небольшой вопрос. Подразумевает ли Cube возможность изменение периода таймера во время работы контроллера? Я хочу изменять период таймера на работающем контроллере, без перепрошивки.
Я предполагаю нужно в этой строке "htim3.Init.Period = 499" вместо числового значения поставить переменную "htim3.Init.Period = a", а потом перезапускать таймер после изменения значения этой переменной. Но боюсь Cube сотрёт эту переменную, т.к. она будет находится вне блока User Code.
Может есть ещё способы?
А может ткнёте носом в мануал\ссылку, где это всё расписано 🙂
Можно просто в регистр таймера напрямую писать значения - регистр TIMx->ARR, насколько я помню.
Нет, при такой схеме мы просто альтернативно инициализируем таймер, в зависимости от некоего условия на входе.
Могу кусок простого кода добавить для ясности.
Проблема в том, что пишу с Мака, а реальный код - на работе.
Но суть-то в другом: в Кубе мы МОЖЕМ указывать некие ПЕРЕМЕННЫЕ, а вот где и в каком блоке кода их можно использовать - я про это говорил.
На вкус и цвет.
В самом Cube надо в настройках писать не числа, а имена переменных или даже формулы с переменными (надо только там галку тыкнуть, чтоб не проверял на корректность) - тогда в коде, который он генерит, будет ровно то, что вы напишете в строке параметра.
Поясню на примере. Допустим, мы пишем код для приемника и передатчика и хотим, чтобы это была одна и та же программа, которая, в зависимости от 0 или 1 на некоем пине работала бы либо как приемник, либо как передатчик, но при этом, моргала бы разной частотой светодиода - штоб отличать одну от другой.
Пусть за отличие приемника от передатчика морганием светодиода будет отвечать шестой таймер, как самый простой.
Пусть у нас на ARB частота равна 36МГц.
Пусть мы хотим на приемнике мигать лениво, а на передатчике - в 2 раза чаще.
1. До функции main() в пользовательском блоке объявляем свою переменную - MyTIM6_Cnt (название - произвольное).
2. В CubeMX указываем в прескалере "36000 - 1"
3. В CubeMX указываем значение счетчика "MyTIM6_Cnt -1" (справа указывая, что данное поле проверять не надо, а надо вставлять в код так, как написано).
4. В main(), после инициализаций, добавляем в юзерской части проверку порта, который по-умолчанию Pull-Up.
(А если хотим сделать девайс обратным - просто замыкаем вывод на землю)
5. А дальше - просто: после всех инициализаций в юзерском блоке проверяем значение порта, присваиваем MyTIM6_Cnt нужное значение и еще раз переинициализируем и запускаем таймер:
MX_TIM6_Init();
HAL_Base_Start_IT(&htim6);
Вот и всё - получаем кубом код, который не зависит от того, что написано там в полях настройки не будет испорчен при изменении настроек куба.
Одна особенность.
По умолчанию, все инициализации всего в Keil делаются в файле main.c
Когда девайсов становится много - удобнее тыкнуть галочку в настройках проекта, штоб инициализации для каждого устройства и их ".h"-файлы были различными (чтоб не мешали в main.c)
Так вот - тогда у вас эта ваша переменная типа MyTIIM6_Cnt - не опознается в отдельных файлах инициализаций.
Просто добавьте там строчку, типа
extern MyTIM6_Cnt.
Но не
но не оставляйте так - указывайте между словом extern и именем переменной - тот самый тип, который нужен.
если у вас было в main.c указано
unsigned short MyTIM6_Cnt
то и в файле инициализации нужно будет сказать полно:
extern unsigned short MyTIM6_Cnt;
Впрочем, это уже знания Си, а не МК.
Только при такой схеме при изменении периода каждый раз будет происходить полная переинициализация, остановка, запуск таймера - ничего хорошего в этом нет.
Нет, при такой схеме мы просто альтернативно инициализируем таймер, в зависимости от некоего условия на входе.
Могу кусок простого кода добавить для ясности.
Проблема в том, что пишу с Мака, а реальный код — на работе.
Но суть-то в другом: в Кубе мы МОЖЕМ указывать некие ПЕРЕМЕННЫЕ, а вот где и в каком блоке кода их можно использовать — я про это говорил.
На вкус и цвет.
P.S. У вас не туда и не так привязываются ответы.
Что-то с базой не так.
А, понял.
Нет, после повторной инициализации таймера и его запуска (а это все происходит по коду после системных инициализаций) - получаем результат.
Тут ведь как - изначальная настройка таймера - была в системном блоке.
А вот в юзерском - опрашиваем пин и еще раз таймер перенастраиваем на запуск.
Ну, короче, если вы поймете, как в настройках CubeMX указывать не числа, а переменные (и где и как в коде эти переменные располагать и где их использовать) - вы малаццы.
Мне, так-то, добавить уже нечего.
Давайте я завтра просто пример кода сюда скопирую?
Вот кусок кода из main.c
(на ibyt ARB1 для таймера задана частота 36МГц)
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
unsigned short MyTIM6_Cnt=0; /* Здесь будет храниться значение счетчика таймера */
#define MODE_RECEIVER 0
#define MODE_TRANSMITTER 1
/* Т.к. пин режима "подтянут" к единице, то по умолчанию - мы передатчик */
unsigned short RxTxMode = MODE_TRANSMITTER;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_NVIC_Init(void);
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
/* TEST HAL_GPIO_WritePin(Light_1_GPIO_Port, Light_1_Pin, GPIO_PIN_SET);*/
/* Опрашиваем пин режима и принимаем решение кто мы сейчас */
RxTxMode = (HAL_GPIO_ReadPin(RxTxMode_GPIO_Port, RxTxMode_Pin) == GPIO_PIN_RESET)
? MODE_RECEIVER
: MODE_TRANSMITTER;
/* Пусть приемник будет мигать раз в секунду, передатчик - 2 раза в секунду */
MyTIM6_Cnt = (RxTxMode == MODE_RECEIVER) ? (500-1) : (250-1);
/* А теперь еще раз инициализируем таймер и запускаем его */
MX_TIM6_Init();
HAL_TIM_Base_Start_IT(&htim6);
if (RxTxMode == MODE_RECEIVER)
{
while (1)
{
/* Здесь код приемника */
}
}
else /* MODE_TRANSMITTER */
{
while (1)
{
/* Здесь код передатчика */
}
}
/* USER CODE END 2 */
А вот то, что генерит Куб в инициализации таймера:
/* TIM6 init function */
void MX_TIM6_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
htim6.Instance = TIM6;
htim6.Init.Prescaler = 36000 - 1;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = MyTIM6_Cnt;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
В строчках
htim6.Init.Prescaler = 36000 - 1;
htim6.Init.Period = MyTIM6_Cnt;
Куб подставил именно то, что было указано в настройках Куба.
Итак, это пример того, что вовсе необязательно в настройках Куба указывать абсолютные числа - можно (и нужно иногда) указывать свои переменные.
завтра ответить попробую, не сегодня.
а завтра - предъявлю владение CubeMX в тех вопросах, где малость разобрался.
Оки?
Ув. Aveal насчет частот все понятно.
вы не указали что надо поставить галочку NVIC Setting ну отсюда и естественно куда вкладывать код моргания диода (без галочки он не генерируется), так же нет объяснения откуда взялся параметр (&htim3) - по логике вещей понятно, но на видео по ссылке, что вкладывал автор все показывает и становится понятно, т.к. хотелось, что бы инструкция была относительно универсальна. (т.е. к разным МК и таймерам)
для своего Мк выставил все верно - таймер2 частота 36МГЦ (по кубу и это максимальная по даташиту) при делении на 36к и умножении на 499 почему то получилось 2сек, в итоге сделал умножение на 249 и вроде стала 1сек., но почему не понял
(c)"вы не указали что надо поставить галочку NVIC Setting"
А я два часа голову себе ломал - что я не так делаю. Досадная неточность автора. Спасибо Ефим.
Не совсем понял...В тексте же написано - "В этом же окне во вкладке NVIC Settings нужно, собственно, включить нужное нам прерывание, и на этом настройки заканчиваются."
Ну и, в общем-то, сказано, куда поместить код моргания диодом - "Кстати, сам обработчик прерывания Cube создал за нас и найти его совсем несложно — для всех прерываний генерируется отдельный файл stm32f4xx_it.c."
Здравствуйте!
Настраиваю в Кубе таймер на энкодер. Куб позволяет подтягивать входы вниз, вверх или оставить болтаться. Однако в созданном коде (IAR EWARM) нигде не нашел, чтобы эти входы инициализировались. Между тем, таймер и не считает данные с энкодера. Может, надо было вручную?
Добрый день!
А можете выслать проект на почту, я посмотрю?
Вообще Cube должен сам все полностью проинициализировать.
Спасибо. Разобрался, в общем. Есть вопрос - как посчитать количество импульсов за единицу времени. Как я понял - нужно считать "тики" между спадом импульса и фронтом следующего. Нет ли у Вас подобного примера? Может, будет желание статью написать?
Наверно удобнее всего будет внешние прерывания использовать - https://microtechnics.ru/stm32f3-ispolzovanie-vneshnix-preryvanij/ . Тоже через Cube можно легко настроить. Тогда при приходе переднего (к примеру) фронта будет срабатывать прерывание и в нем просто можно инкрементировать счетчик.
Метод надежен, но есть слабое место. При низкой частоте импульсов - очень большая погрешность. Я собираюсь считывать сигнал с импульсного водомера. Частота (средняя), пусть, 10 имп./сек. При опросе счетчика, по прерыванию таймера, например, каждую секунду - может быть полная лажа. 8-11...а это более 10%
Я думал, что ловить нужно спад импульса, начинать считать "тики" и по фронту следующего - считывать счетчик. Частота "тиков" известна, все остальное - уже детали. Но вот именно этот кусок - мне непонятно, как реализовать. Спад - подсчет - остановка по фронту... Если подскажете примером - буду очень признателен 🙂
Ну я и имел ввиду не прерывание таймера, а прерывания внешней линии EXTI - ловим в прерывании передний или задний фронт импульса (или и то, и другое). Параллельно работает таймер (можно без прерывания). И попав в прерывание EXTI считываем значение счетчика таймера. Затем для другого импульса - и рассчитываем период.
Другой вариант - без таймера отдельного - например, на SysTick (он по умолчанию включен) отсчитывать секунду. А через EXTI считать число импульсов за секунду.
Спасибо. Метод с обработкой значения счетчика в прерывании по входу - похоже, то, что надо. Но там, видимо, есть нюанс - с учетом переполнения таймера, оно может попасть в период между импульсами на входе. Возможно, таймер нужно сбрасывать в 0, в обработчике прерывания?
Нет ли у Вас интереса на эту тему сделать урок? Например, урок в DISCOVERY - как считать количество нажатий на кнопку за 1...10 сек?
Ну да, таймер можем сбросить в прерывании.
Вообще хорошая тема для статьи, но до Нового Года со временем тяжело, если только после получится =)
Здравствуйте!
Вот, как раз ищу, как сбросить таймер, когда он еще не "досчитал" до сброса?
Спасибо
Ну и соответственно количество за единицу времени если знаем, то рассчитываем период. Можно и не считать количество, а настроить внешнее прерывание на передний и задний фронт и по значению таймера рассчитать период.
А вообще у STM32 в таймерах огромнейшее количество разных режимов - вполне возможно, что какое-то аппаратное решение и для такой задачи найдется, надо даташит поковырять.
при вхождении в процедуру обработки прерывания надо сбрасывать флаг прерывания или он автоматически сбрасывается?
Нужно сбрасывать. В HAL это происходит внутри функции HAL_TIM_IRQHandler(&htim3);
Здравствуйте! Есть необходимость менять htim3.Init.Period прямо в прерывании. Я пытаюсь сделать так:
В прерывании
htim3.Init.Period = 10000; // или др. значение
ошибку не выдает но и период прерываний не меняется... подскажите пожалуйста как это исправить
Лучше всего менять так:
TIM3->ARR = новое_значение.
Здравствуйте!
Я так и делаю: TIM4->ARR = "расчетное значение" - для генерации импульсов управления частотником - от 20 до 5000 Гц. Почему-то при росте частоты генерация срывается и возобновляется через (примерно) 4 секунды, а при уменьшении все работает непрерывно. Если бы контроллер сбивали импульсы от энкодера или собственного выхода, это было бы и при падении частоты (кажется). Если бы МК зависал от внешних помех, то перестал бы работать весь, а не только таймер TIM4. Если бы не хватало быстродействия (чтобы переключать выход с частотой в сотню - другую герц?), его не хватало бы и после паузы в несколько секунд... В общем, не знаю, что и предполагать. Ваши опыт и знания что-нибудь подсказывают?
Спасибо
Добрый вечер!
А можете код выслать посмотреть, если есть такая возможность, конечно.
Править комментарий здесь, похоже, нельзя, но Вы можете - если слишком длинно - сократить его до разумных пределов, или удалить совсем
Добрый день!
Если что можно мне на почту отсылать архивом проекты - Aveal.MicroTechnics@gmail.com.
Здесь проблема в том, что период меняется в цикле while(1). Нужно менять в прерывании по переполнению TIM4. Получается, что в момент изменения периода счетчик таймера равен неизвестному значению - и это значение может оказаться больше, чем новый период. Поэтому и получается, что сбои случаются при уменьшении периода.
Спасибо, я об этом не подумал. Завтра проверю и отпишусь
Здравствуйте! Сегодня попробовал записывать в прерывании в регистр ARR значение, вычисленное в цикле. Но переменная в обработчике получает нулевое значение независимо от результатов расчета. Может, что то опять не так делаю? Сообщение с проектом на Ваш адрес почему-то не ушло, сейчас попробую сообщение без вложения. Спасибо
Письмо без вложения ушло. Значит, дело было не в адресе, а во вложении (проект IAR, завернутый в RAR) - в списке запрещенных такого файла нет, не знаю, почему