В этот раз рассмотрим вопросы реализации задержек и использования базовых таймеров. Также слегка зацепим систему тактирования STM32F4.

В первом уроке мы мигали светодиодом, используя некую программную задержку, суть которой сводилась к тому чтобы гонять пустой цикл for (; nCount!=0; nCount—). Как ни странно, но перевести «мартышек в попугаи», т.е. циклы в миллисекунды или микросекунды удалось далеко не сразу. На форумах звучала цифра «около 10 тактов» за один проход цикла, т.е. при тактовой частоте 16МГц это будет (1/16 000 000)*10=0,625 мкс. Практические замеры подтвердили правильность результатов.

В итоге, в одной из библиотек, удалось откопать функцию, позволяющую использовать задержки в миллисекундах:

void delay_ms(uint32_t ms)
{
volatile uint32_t nCount; //переменная для счета
RCC_ClocksTypeDef RCC_Clocks; //переменная для считывания текущей частоты
RCC_GetClocksFreq (&RCC_Clocks); //считываем текущую тактовую частоту
 
nCount=(RCC_Clocks.HCLK_Frequency/10000)*ms; //пересчитываем мс в циклы
for (; nCount!=0; nCount--); //гоняем пустые циклы
}

Для проверки работы функции используем код ногодрыга, постоянно инвертируем ножку с задержкой в 10мс, т.е. осциллограф должен показать меандр с частотой 1000/10*2=50Гц

#include <stm32f4xx.h>
#include <stm32f4xx_gpio.h>
#include <stm32f4xx_rcc.h>
 
void delay_ms(uint32_t ms)
{
volatile uint32_t nCount; //переменная для счета
RCC_ClocksTypeDef RCC_Clocks; //переменная для считывания текущей частоты
RCC_GetClocksFreq (&RCC_Clocks); //считываем текущую тактовую частоту
 
nCount=(RCC_Clocks.HCLK_Frequency/10000)*ms; //пересчитываем мс в циклы
for (; nCount!=0; nCount--); //гоняем пустые циклы
}
 
int main(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; //настройки порта
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Вкл тактирование D
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //Выбираем нужные выводы
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода
	GPIO_Init(GPIOD, &GPIO_InitStructure); //вызов функции инициализации
 
    while(1)
    {
        GPIO_ToggleBits(GPIOD, GPIO_Pin_12);
        delay_ms(10);
    }
}

Результат, при работе от внутреннего генератора на 16МГц,
stm32_delay_RC

Как видно задержка 10.1мс вместо 10, поэтому пришлось откопать кусок кода, который позволяет перевести работу камня от внешнего кварца. Тут нужно быть внимательным, ибо тактовая частота в кокосе настраивается в файле stm32f4xx.h о чем будет сказано чуть ниже.

RCC->CR|=((uint32_t)RCC_CR_HSEON);
while (!(RCC->CR & RCC_CR_HSERDY)) {}; //ожидание готовности
RCC->CFGR &=~RCC_CFGR_SW; //сброс битов
RCC->CFGR |= RCC_CFGR_SW_HSE; // Выбрать HSE для тактирования SW0=1.

При работе, от внешнего генератора результат порадовал:
stm32_delay_XT

Можно сделать вывод, что функция работает, задержку выдает исправно, нужны точные задержки — юзай кварц, если точность не нужна то достаточно внутреннего RC генератора.

Кстати, iEugene0x7CA провел подобный эксперимент, демонстрирующий работу AVR от внутреннего RC генератора и от внешнего кварца и любезно поделился результатами наблюдений. В качестве опыта использовался код ногодрыга на 50Гц:

PORTB.0=1;
delay_ms(10);
PORTB.0=0;
delay_ms(10);

Осциллограмма работы подобного кода, при работе от внутреннего RC генератора и температуре +20С:
atmega20C

Тоже самое, только после охлаждении микроконтроллера сжиженным газом 🙂
atmega-35C

Как видим частота «уплыла» почти на 1Гц. Ну и на закуску работа от кварца:
atmega20Xtal
Поэтому пользуйтесь delay с пониманием происходящего.

Перейдем к использованию таймеров, задача прежняя — подрыгать ножкой с определенной частотой. В STM32F4 имеется несколько типов таймеров, пока рассмотрим так называемые Basic. В документации мы видим, что к ним относятся TIM 6 и 7.
basic_timers_toc

Это самые простые таймеры, они умеют тикать и вызывать прерывание по совпадению. Так как они 16 битные, то умеют тикать от 0 до 2^16 = 65 536. Идея прерывания по совпадению аналогична AVR, т.е. когда таймер досчитает до определенного значения, которое мы определяем сами, происходит прерывание, в котором можно определять необходимые дальнейшие действия.

Также, как и в ситуации с ножками, прежде чем использовать таймер нужно включить тактирование шины, на которой он сидит, а сидит он на APB1. Включить тактирование можно, с помощью регистра RCC APB1 peripheral clock enable register (RCC_APB1ENR). Включить тактирование TIM6 можно так:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);

Но самый прикол начинается в тот момент, когда нужно определить на какой частоте работает APB1, тут можно запросто отхватить опухоль мозга. Далее я приведу небольшие наброски того, что удалось понять на текущий момент, естественно система тактирования достойна отдельного рассмотрения, вероятно в ближайшем будущем это будет. Но совсем пропустить этот момент сейчас нельзя, ибо тут слишком все взаимосвязано, поэтому считаем это предварительным ознакомлением.

Поехали разбираться. Даташит на этот счет выдает такую вот общую схемку:
basic_timers_clock

После нескольких дней курения форумов и даташитов, имеется следующая картина: в качестве основного источника может выступать HSE (e-external) внешний генератор (кварц 4-26МГц) и HSI(I-internal) — внутренний (RC на 16МГц). Похоже на AVR. Но есть особенность, называемая PLLCLK, не влезая в подробности эта система позволяет умножить базовую частоту, в качестве которой берется HSE или HSI, таким образом получаются заветные 168МГц.

Разобраться с системой тактирования таймеров не так просто. Как видно, сигнал от основного источника подается на шину AHB, где ее поджидает местный prescaler(делитель) 1,2.. 512. Далее используется делитель APB, причем если делитель не равен 1, то частота умножается на 2. Этот момент нужно учитывать, ибо это местная фича.

Окончательно выносят мозг пояснения к этой таблице:
Когда TIMPRE бит в регистре RCC_DCKCFGR сброшен и APBx предделитель 1, тогда TIMxCLK = PCLKx, в противном случае TIMxCLK = 2x PCLKx.
Когда TIMPRE бит регистра RCC_DCKCFGR установлен и APBx предделитель 1,2 или 4, тогда TIMxCLK = HCLK, в противном случае TIMxCLK = 4x PCLKx.

В итоге главное понять, что максимальная частота AHB 180МГц, APB2 90МГц, а APB1 45МГц. Хоть как то прошарить эту тему поможет фирменная софтина от STM, которая поставляется в виде экселевского файла. Она, в том числе, поможет сгенерить файл system_stm32f4xx.c в котором выставляются все настройки частоты и прескалеров. Настоятельно рекомендую скачать ее и ознакомиться, дабы просто составить общее впечатление того, как работает механизм тактирования.

stm_tools

Заканчивая разговор о тактовой частоте нужно запомнить, что камень всегда стартует на внутреннем генераторе, даже если используется внешний кварц. Переключиться на внешний кварц можно тем куском кода, что был приведен выше. Для простоты работы прескалеры не трогаем, тактовую частоту выставляем в файле stm32f4xx.h в строках HSI_VALUE и HSE_VALUE. При таких настройках таймер будет тикать на основной частоте, т.е. либо 16МГц внутренних, либо 8МГц внешних.

Попробуем подрыгать ногой PD12, при помощи таймера с частотой 50Гц.

#include <stm32f4xx.h>
#include <stm32f4xx_gpio.h>
#include <stm32f4xx_rcc.h>
 
int main(void)
{
        //Работаем от внешнего кварца 8МГц
	RCC->CR|=((uint32_t)RCC_CR_HSEON);
	while (!(RCC->CR & RCC_CR_HSERDY)) {};
	RCC->CFGR &=~RCC_CFGR_SW; // Очистить биты SW0, SW1.
	RCC->CFGR |= RCC_CFGR_SW_HSE; // Выбрать HSE для тактирования SW0=1.
 
	//Настройка таймера 6
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); //включить тактирование таймера 6
	TIM6->PSC = 8000 - 1; //Настройка делителя на 1000 "тиков" в секунду, ибо основная частота 8МГц
	TIM6->ARR = 10-1; //Регистр сравнения, досчитали до 10 - вызвали прерывание, т.д. каждые 10мс
	TIM6->DIER |= TIM_DIER_UIE; //Разрешаем прерывания от таймера
	TIM6->CR1 |= TIM_CR1_CEN; //Включить отсчет таймера
	NVIC_EnableIRQ(TIM6_DAC_IRQn); //Разрешение прерывания TIM6_DAC_IRQn 
 
	//настройка ножки
	GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включаем тактирование порта D
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //Выбираем нужные выводы
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода
	GPIO_Init(GPIOD, &GPIO_InitStructure); //вызов функции инициализации
 
    while(1) {}
}
 
void TIM6_DAC_IRQHandler(void)
{
	TIM6->SR &= ~TIM_SR_UIF; //Сбрасываем флаг прерывания
	GPIO_ToggleBits(GPIOD, GPIO_Pin_12); //инвертируем состояние ножки
}

Может показаться, что исходник большой и не понятный, но если внимательно разобраться то ничего сложного нет. Настраивается предделитель таймера, чтобы таймер тикал нужное количество раз за секунду, в регистр сравнения пихаем нужное количество отсчетов, разрешаем обработку прерывания от таймера, запускаем таймер. Все это было в AVR ничего нового. Внутри прерывания инвертируем ножку.

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

При работе от кварца
stm32_tim6_xt

Статья получилась довольно сумбурной, кардинально переписывал ее 7 раз, в итоге все равно как капля в море, ибо в отличии от AVR, работа с STM32 без даташита не мыслима. Если расписывать все тонкости настройки регистров, то тут никаких статей не хватит. Поэтому будем учиться постепенно, пока это только первые шаги. К таймерам мы еще вернемся.

Проект кокоса

9 комментариев: Базовые таймеры. Реализация задержек.

  • Привет!
    TCCR1B |= (1 << WGM12)|(1 << CS12); // 8MHz/256/(1+31249)
    OCR1A = 3810;
    Помогите определить значение OCR1A для микроконтроллера AVR Mega8L. На ней добавлен кварц KW12000.
    Хочу настроить таймер. Вручную установил 3810 но он каждый 5 часов ошибается на 2 минута.
    Как будет правильное значение OCR1A чтобы работал как часы?

  • если кварц на 12мгц, то 8L не рассчитана на эту частоту, тем более если вы отталкиваетесь от 8мгц в рассчетах

  • Здравствуйте!
    Использую stm32f407vg, плата discovery. Пробовал запускать, работает хорошо, но после отключения питания, видимо, сбрасываются какие-то настройки, выдаёт на ноге какую-то ерунду. И даже стирание программы из памяти не помогает, только если загрузить другой проект. Частотные настройки изменял, но не помогает. Подскажите, пожалуйста, — что происходит?

  • Да, такой косяк встречается, причем с разными прошивками, точно работающими. Лечится как вы правильно сказали другой прошивкой. Возможно, это происходит тогда, когда подключаешь что либо при работающем микроконтроллере, т.е. все подключения делать только на отключенном мк. Но это только версия, реального объяснения этому у меня нет.

  • Решил проблему, работая не напрямую с регистрами, а используя функции для работы с таймером. Настройки не менял. Пример:
    TIM_TimeBaseStructure.TIM_Prescaler = 8000 — 1;
    TIM_TimeBaseStructure.TIM_Period = 10 — 1;

  • Порой читаю и думаю — это каким двинутым нужно быть, чтобы это создать и каким более двинутым быть, чтобы в этом всём разобраться! Да ещё и «… опухоль мозга отхватить».

  • Попробуйте взглянуть на эту проблему с другой стороны, как можно было бы решить ее иначе? Тогда придет понимание происходящего

  • Привет ! подскажите откуда взялась эта функция void TIM6_DAC_IRQHandler(void) и что в ней значит DAC (ну то что это цап понятно но причем он здесь) ? пожалуй начнем разбираться с этого 🙂 а вообще ну и дичь же эти прерывания! ну и соответственно встает вопрос не проще ли было пользоваться библиотекой с таймерами, вместо CMSIS?

  • вопрос скорее к разработчикам из ст, зачем они один обработчик используют и для таймера6 и для цапа, видимо так было удобнее. можно и пользоваться и библиотекой с таймерами, разницы нет. Upd: ковырялся в даташите, интересная фраза по поводу tim6,7 попалась These timers are mainly used for DAC trigger generation.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Свежие записи
Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика