В уроке научимся отрабатывать точные временные задержки малой длительности. Будем для этого использовать разные методы.
Предыдущий урок Список уроков Следующий урок
В уроках Ардино и в этом курсе, посвященном программированию STM32, я не устаю повторять, что программа должна выполняться параллельными процессами, операции производиться в фоновом режиме и т.д. Недопустимо, чтобы программа ”висела” в основном цикле, ожидая какого-либо события.
Но в некоторых ситуациях без коротких задержек не обойтись. Главное, чтобы они не блокировали программу на значительное время. Речь идет не об отладочных программах, а о реальных рабочих проектах. Примеров можно привести множество.
Один из самых типичных следующий. Часто возникает необходимость формировать задержку между сигналами при обращении к аппаратному устройству, модулю, микросхеме, подключенной к микроконтроллеру. Например, LCD-дисплей на базе контроллера HD4478 требует временные задержки между сигналами не менее 450, 800, 1000 нс и другие. В системах с Ардуино на ATmega328/168 изменение состояния цифрового вывода занимает не менее 4 мкс. О подобных задержках можно и не думать, а просто последовательно формировать сигналы.
STM32 более быстродействующий микроконтроллер. Если не принять специальных мер он переключит состояние выводов быстрее, чем допустимо и, например, дисплей работать не будет. Требуется искусственно формировать задержки. Бывает необходимо формировать не минимально допустимую задержку, а паузу с точно выдержанной длительностью.
Попробую формализовать задачу. Урок посвящен способам формирования коротких точных временных задержек. Программа может при этом “зависать”. Если требуется формировать задержку не минимально-допустимую, а строго определенную, то могут быть даже запрещены прерывания.
Еще добавлю, что создавать задержки длительностью несколько мкс и короче не так просто. Я попытался облегчить эту задачу. В конце урока представлю мою библиотеку формирования коротких задержек.
HAL-функция задержки.
Упоминаю о ней формально. Функция простая. Все умеют ей пользоваться. Мы применяли ее в предыдущих уроках.
HAL_Delay
void HAL_Delay (uint32_t Delay)
Функция отрабатывает задержку, заданную в миллисекундах. Паузу менее 1 мс с помощью нее сформировать нельзя. Это главный недостаток функции. Миллисекундные импульсы не всегда можно считать короткими.
Для отсчета времени функция использует системные тики с периодом 1 мс, которые формирует системный таймер.
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
Использование прерывания системного таймера.
Если функция HAL_Delay использует прерывание системного таймера, то почему бы нам не пойти тем же путем.
Создадим программный таймер в main.c
/* USER CODE BEGIN PV */
volatile uint32_t timer1=0; // программный таймер 1 мс
/* USER CODE END PV */
и в файле stm32f1xx_it.c
/* USER CODE BEGIN EV */
extern volatile uint32_t timer1; // программный таймер 1 мс
/* USER CODE END EV */
В обработчике прерывания системного таймера будем считать тики.
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
timer1++;
Теперь в главном цикле мы можем считать значение нашего таймера, сбросить его.
/* USER CODE BEGIN WHILE */
while (1) {
timer1=0; while(timer1 < 250) {} // пауза 250 мс
HAL_GPIO_TogglePin(Led13_GPIO_Port,Led13_Pin); // инверсия светодиода
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Светодиод мигает 2 раза в секунду.
Использование аппаратных таймеров.
Принцип такой же, как и в предыдущем способе. Но аппаратный таймер может считать намного быстрее, а значит, получится отрабатывать более короткие отрезки времени.
Я создал проект Lesson24_1. Настроил систему тактирования на 72 мГц, вывод светодиода PC13 “развернул” на выход.
Настроил таймер 2 на режим простого счетчика со значением предделителя 71, регистра перезагрузки 65535.
Таймер 2 тактируется от синхросигнала шины APB1. У нас его частота 72 мГц. С учетом предделителя частота тактирования таймера будет 1 мГц или период 1 мкс. Запускаем таймер.
HAL_TIM_Base_Start(&htim2); // запуск таймера 2
Теперь он постоянно считает импульсы и его можно использовать для формирования коротких задержек.
TIM2->CNT= 0; while(TIM2->CNT < 5) {} // пауза 5 мкс
Следующая программа формирует импульс низкого уровня длительностью 5 мкс и периодом 20 мкс.
while (1) {
Led13_GPIO_Port -> BSRR = Led13_Pin << 16; // сброс бита
TIM2->CNT= 0; while(TIM2->CNT < 5) {} // пауза 5 мкс
Led13_GPIO_Port -> BSRR = Led13_Pin; // установка бита
TIM2->CNT= 0; while(TIM2->CNT < 15) {} // пауза 15 мкс
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Если длительность импульса должна отрабатываться с высокой точностью, необходимо на время его формирования запрещать прерывания.
__disable_irq (); // запретить прерывания
Led13_GPIO_Port -> BSRR = Led13_Pin << 16; // сброс бита
TIM2->CNT= 0; while(TIM2->CNT < 5) {} // пауза 5 мкс
Led13_GPIO_Port -> BSRR = Led13_Pin; // установка бита
__enable_irq (); // разрешить прерывания
Время задержки отсчитывается от оператора
TIM2->CNT= 0;
Поэтому, в течение отсчета времени можно выполнять какие-то действия, при условии, что их длительность меньше времени задержки.
TIM2->CNT= 0; // начало отсчета задержки
// выполнение программы
while(TIM2->CNT < 15) {} // ожидание окончания задержки
Загрузить проект с примерами урока можно по ссылке Lesson24_1.
Использование DWT-счетчика.
У микроконтроллера STM32 есть еще один скрытый таймер. Это элемент DWT-модуля (Data Watchpoint and Trace Unit), узла предназначенного для отладки системы.
Если забыть об остальных узлах модуля, то для нас DWT-таймер это 32х разрядный счетчик, значение которого увеличивается на единицу с каждым тактовым импульсом микроконтроллера. Для платы STM32F103C8T6 максимальная частота составляет 72 мГц, период 13,88 нс.
У DWT-счетчика есть адрес в пространстве памяти STM32 (0xE0001004). Значение счетчика можно считать и изменить. Перед использованием DWT-таймера необходимо разрешить его работу.
#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004
#define DWT_CONTROL *(volatile uint32_t *)0xE0001000
#define SCB_DEMCR *(volatile uint32_t *)0xE000EDFC
SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // разрешение счётчика
DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; // запуск счётчика
Теперь можно формировать задержку по принципу из предыдущих примеров урока.
DWT->CYCCNT = 0; // сброс счётчика
while(DWT->CYCCNT < 360) {} // задержка 5 мкс (5 * 72 )
Следующая программа формирует импульс низкого уровня длительностью 5 мкс и периодом 20 мкс.
SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // разрешение счётчика
DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; // запуск счётчика
while (1) {
Led13_GPIO_Port -> BSRR = Led13_Pin << 16; // сброс бита
DWT->CYCCNT = 0; while(DWT->CYCCNT < 360) {} // задержка 5 мкс (5 * 72 )
Led13_GPIO_Port -> BSRR = Led13_Pin; // установка бита
DWT->CYCCNT = 0; while(DWT->CYCCNT < 1080) {} // задержка 15 мкс (15 * 72 )
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Проект с примером Lesson24_2
К недостаткам способа следует отнести необходимость выполнения следующих действий:
- выполнить в программе определения и установки регистров DWT;
- вычислять константу для каждого времени задержки с учетом частоты тактирования микроконтроллера.
Библиотека DelayDWT.
Я разработал библиотеку, которая позволяет формировать задержки, используя DWT-таймер. Время задержки задается непосредственно в микросекундах или наносекундах. Учет частоты тактирования микроконтроллера происходит автоматически.
void initDelayDwt(void)
Функция инициализации DWT счетчика. Разрешает работу счетчика, устанавливает по умолчанию время задержки 1 мкс. Должна быть выполнена до использования других функций библиотеки.
initDelayDwt(); //инициализация DWT-счетчика
void delayNs(uint32_t ns)
Формирование задержки, заданной в наносекундах.
Конечно, функция не обеспечивает разрешающую способность 1 нс. Но, даже для платы STM32F103C8T6, у которой командный цикл равен 13,88 нс, функция способна отрабатывать десятые доли микросекунды. Для более быстрых плат STM32 разрешающая способность будет еще меньше.
delayNs(5000); // задержка 5000 нс
inline void delaySetNs(void)
Формирование задержки, заданной в наносекундах, с заранее рассчитанным значением тиков.
В предыдущей функции значение задержки пересчитывается в число тиков DWT-счетчика. Используются 2 операции деления и одна умножения. Это занимает достаточно много времени и искажает конечную длительность задержки. Функция delaySetNs использует заранее вычисленное с помощью функции setDelay число тиков.
Также значительное время тратится на вызов самой функции. Поэтому она объявлена inline (встроенной) функцией. В результате время задержки отрабатывается значительно точнее.
setDelay(5000); // предварительный расчет задержки 5000 нс
delaySetNs(); // задержка 5000 нс
void setDelay(uint32_t ns)
Предварительный расчет числа тиков из значения задержки.
void delayMks(uint32_t mks)
Формирование задержки, заданной в микросекундах.
delayMks(5); // задержка 5 мкс
Для использования библиотеки DelayDWT необходимо произвести обычные действия, описанные в уроке 14.
- Создаем каталог Libraries в корневой папке проекта и копируем туда папку с библиотекой DelayDWT.
- В Atollic TrueStudio:
- Подключаем библиотеку.
/* USER CODE BEGIN Includes */
#include "DelayDWT.h"
- Правой кнопкой мыши нажимаем на папку DelayDWT в проекте, Add/Remove Include Path -> OK.
- Правой кнопкой по Libraries, Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder -> Apply.
- Надо не забыть инициализировать DWT-счетчик функцией initDelayDwt().
Для сравнения я реализовал цикл из 2х задержек с использованием разных функций.
initDelayDwt(); //инициализация DWT-счетчика
__disable_irq (); // запретить прерывания
setDelay(5000); // предварительный расчет задержки 5000 нс
while (1) {
Led13_GPIO_Port -> BSRR = Led13_Pin << 16; // сброс бита
//delayMks(5);
//delayNs(5000);
delaySetNs();
Led13_GPIO_Port -> BSRR = Led13_Pin; // установка бита
//delayMks(5);
//delayNs(5000);
delaySetNs();
}
В идеальном случае частота сигнала на выводе PC13 должна быть 100 кГц, период 10 мкс. У меня получилось.
Функция | Частота | Период |
delayNs(5000) | 84,81 кГц | 11,79 мкс |
delaySetNs() | 96,78 кГц | 10,33 мкс |
Функция delaySetNs отрабатывает время точнее. С учетом того, что в цикле по 2 вызова функций задержки, а также требуется время для перехода на начало цикла и переключение вывода, длительность импульса выдерживается достаточно точно.
Загрузить библиотеку DelayDWT можно по этой ссылке.
Пример работы с библиотекой Lesson24_3.
Вроде тема незначительная, но как мне кажется, получился очень полезный практичный урок.
В следующем уроке я собираюсь рассказать о подключении к STM микроконтроллерам знакосинтезирующих LCD индикаторов. Представлю свою библиотеку.
Спасибо!Надеюсь видеть больше уроков
Стараюсь по мере сил.
DWT->CYCCNT или DWT_CYCCNT ?
Или оба правильно?
Здравствуйте!
В файле core_cm3.h определена структура регистров DWT.
typedef struct
{
__IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) Control Register */ __IOM uint32_t CYCCNT; /*!< Offset: . . . . . . . . . . . . . } DWT_Type; DWT->CYCCNT это обращение к элементу структуры.
DWT_CYCCNT=0; это обращение по конкретному адресу.
#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004
Спасибо, разобрался.
1-Создаем каталог Libraries в корневой папке проекта и копируем туда папку с библиотекой DelayDWT.
2-перетаскиваю в Libraries folder DelayDWT
3-/* USER CODE BEGIN Includes */
#include «DelayDWT.h»
4-Правой кнопкой по Libraries, Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder -> Apply. для папки DelayDWT
После компиляции — ../Core/Src/main.c:31:10: fatal error: DelayDWT.h: No such file or directory
#include «DelayDWT.h»
добился чистой компиляции проекта с библиотекой!!
но без :
initDelayDwt();
в main.c
если пишу эту функцию в мейн.с (перед вайл(1)), то получаю еррор:
/Users/sergei/Documents/STM32/Project/Start100rb_4/Debug/../Core/Src/main.c:126: undefined reference to `initDelayDwt’
collect2: error: ld returned 1 exit status
Core/Src/main.o: In function `main’:
/Users/sergei/Documents/STM32/Project/Start100rb_4/Debug/../Core/Src/main.c:127: undefined reference to `initDelayDwt’
/Users/sergei/Documents/STM32/Project/Start100rb_4/Debug/../Core/Src/main.c:129: undefined reference to `setDelay’
если добавлю :
initDelayDwt(); //инициализация DWT-счетчика
__disable_irq (); // запретить прерывания
setDelay(5000); // предварительный расчет задержки 5000 нс
(Для более быстрых плат STM32 разрешающая способность будет еще меньше.)
Да нет, разрешающая способность будет выше, а интервал будет меньше. Вроде так.
Спасибо за познавательный материал.
Эдуард, скачал Lesson24_2, проект в кубеМХ есть, но не нашёл сишного кода, где с помощью таймера2 вы делаете микросекундную задержку. Очень хотелось бы увидеть его…
Здравствуйте!
В уроке есть такой пример.
TIM2->CNT= 0; while(TIM2->CNT < 5) {} // пауза 5 мкс
а готового кода в архиве, который можно загрузить в плату и посмотреть нет?
В уроке все есть.
Устанавливаете режим таймера, запускаете его.
Теперь в любом месте программы вставляете строчку
TIM2->CNT= 0; while(TIM2->CNT < 5) {} // пауза 5 мкс
А для наносекунд другой таймер использовать?
Здравствуйте!
Единицы наносекунд нельзя отсчитать на этих таймерах.
И чем отличается задержка таймера базового от задержки циклической Системного таймера.Я хочу использовать базовый таймер в качестве задержки для инициализации lcd,.Но проблема что там наносек.Микросекунды и милисекунды
И ещё а как использовать задержки на базовом таймере в прерывании?.И не будет задержка та которая прописана у вас тормозить программу в основном цикле.Если там есть ещё другие строки и они отвечают за другие процессы?
Да. Это блокирующий режим. Он останавливает программу. В реальных программах можно использовать на короткие задержки.
значит это мне не подходит.Остаётся таймер в прерывании. А ваше какое мнение?
Все зависит от конкретной задачи.
if((GPIOB->IDR&GPIO_IDR_IDR_8)==0)
{
__disable_irq ();
TIM2->CNT= 0;
// pauza1=0;
if (d==0)
{
d=4095;
}
d—;
while(TIM2->CNT < 15) {}
}
__enable_irq ()
я правильно написал?
Задача такова что бы не было сбоев работы процессора в основном цикле.DS18B20
как эту библиотеку портировать для камня f401?
// ����� RS
i=0;
d = _GPIO_Pin_RS;
while(i > 1;
if(d == 0) break;
i++;
}
if(i CRL |= 0b0011 <CRL &= ~ (0b1100 <CRH |= 0b0011 <CRH &= ~ (0b1100 <ODR &= ~ (1 << i);
на чипе F401ccu выдает ошибку
'GPIO_TypeDef' {aka 'struct ‘} has no member named ‘CRH’
‘GPIO_TypeDef’ {aka ‘struct ‘} has no member named ‘CRL’
на все строчки с участием этих самых CRL и CRН. Вопрос такой: зачем здесь весь оператор ИФ? я понял смысл цикла ВАЙЛ, но вот с ИФом возникли проблемы.
Здравствуйте!
CRL и CRH это регистры управления портами ввода-вывода. Про них написано в уроке 7.
А если просто использовать вставки __nop в цикле? Не так уж и точно получится, но если калибровать под каждый проект, то вроде можно.