Мы закончили с инициализацией RCC, но у нас ничего не работает. Оставил я так специально. Что бы ввести функции millis()
, micros()
, delay()
и delayMicrosecods()
. Окончание полной инициализации зависит, кроме всего прочего, от таймера SysTick. И ещё одного файла, который нам подарила CMSIS.
Если обратится к первой части цикла, мы в каталоге Core/Src оставили файл system_stm32f4xx.c:
Этот файл сгенерирован CMSIS и содержит в себе некоторые функции для работы с МК. Так как нам они тоже нужны, мы его перенесём в каталог STM32Lib/Device/Stm32F4xx, потому что он нужен для всех микроконтроллеров STM32F4xx. Но в том виде как он есть, он нам не очень подходит. И вот почему:
Обратите внимание на определение HSE_VALUE
. Значение строго фиксировано и равно максимальной частоте кварца, которая стоит в CubeMX. А мы хотим использовать разные частоты. Каждый раз редактировать — не наш метод, мы введём переменную uint32_t HSE_Value
:
В RCC_HSE_Init.h объявляем её как extern uint32_t HSE_Value
. Теперь везде, где в файле system_stm32f4xx.c есть упоминание HSE_VALUE
, заменяем её на переменную HSE_Value
:
SystemCoreClock = HSE_Value; // Правлено мной // SystemCoreClock = HSE_VALUE; /* HSE used as PLL clock source */ /* * Подставляется действительное значение кварца */ pllvco = (HSE_Value / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); // Правлено !!! // pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
Показывать, где конкретно находятся строки, не буду. Простой поиск их вам покажет. Что это нам даёт? Теперь в файле RCC_HSE_Init.cpp мы можем выставить частоту нужного нам кварца и делителя для него. Вот часть кода:
switch (Quartz) { case Quartz_4: Div_PLLM = 2; HSE_Value = 4000000; break; case Quartz_6: Div_PLLM = 3; HSE_Value = 6000000; break; case Quartz_8: Div_PLLM = 4; HSE_Value = 8000000; break; case Quartz_10: Div_PLLM = 5; HSE_Value = 10000000; break; }
На этом инициализация RCC закончена, но нам нужно кое-что ещё.
Нужно настроить таймер SysTick и добавить функции. В конце файла RCC_HSE_Init.cpp есть обращение к функции SysTickConfig()
. Она находится в файлах Timers.h и Timers.cpp. Здесь у нас определено несколько функций:
//****************************************************************************** // Секция прототипов глобальных функций //****************************************************************************** void SysTickConfig(void); // Инициализация таймера SysTick и необходимых переменных uint32_t millis(void); // Возращает миллисекунды uint32_t micros(void); // Возвращает микросекунды void delay(uint16_t MS); // Задержка в миллисекундах void delayMicroseconds(uint16_t US); // Задержка в микросекундах
Инициализация SysTick довольно проста, и информации об этом очень много. Этот таймер настраивается на прерывание каждую миллисекунду и инкрементирование переменной, считающей количество миллисекунд:
// Инициализируем SysTick void SysTickConfig(void) { SystemCoreClockUpdate(); SysTick_Config(SystemCoreClock/1000); NVIC_EnableIRQ(SysTick_IRQn); _CyclePerMicroseconds = SystemCoreClock/1000000; usCalibr(); } // Обработка прерывания от SysTick extern "C" void SysTick_Handler(void) { SysTick->CTRL; // Сбрасываем флаг прерывания от SysTick _SysTickCounter++; // Инкрементируем переменную-счётчик миллисекунд }
Интересны здесь две функции... Во-первых, SystemCoreClockUpdate()
.
Функция находится в файле system_stm32f4xx.c. Она проверяет наши настройки RCC и обновляет глобальную переменную SystemCoreClock
, которая будет содержать тактовую частоту ядра МК в Герцах. Здесь на сайте видел вопрос, как под HAL узнать тактовую частоту ядра и тактовую частоту шин. В CMSIS частота ядра определяется просто считыванием этой переменной, а частота шин делением этой частоты на делитель, находящийся в регистрах соответствующей шины. И, как всегда, есть некоторая хитрость. В делителе шины коэффициент деления может задаваться таким образом (зависит от RCC соответствующего МК):
- 0в0000 — деления нет,
- 0в0100 — деление на 2,
- 0в0101 — деление на 4 и так далее.
Как видим, значение не соответствуют коэффициенту деления, и, поделив 168МГц на число, ранее считанное из регистра, мы получаем полный обман зрения. 168 / 4 = 42, а должны были получить 84, так как из регистра, содержащего коэффициент деления, мы считали число 4 вместо 2.
Чтобы такой путаницы не было, в файле system_stm32f4xx.c определены два массива:
const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4};
Они нужны для того, чтобы, считав коэффициент деления, делить не на него, а на значение, соответствующее позиции в соответствующем массиве. Если будет интересно как, то это будет описано в материале про I2C, так как там довольно интересные вычисления тактирования.
Ну и вторая функция usCalibr()
, которая занимается тем, что вычисляет поправку для счётчика микросекунд, так как часть времени уходит на сами команды и некоторые другие процессы, тоже занимающие время. Описывать досконально не вижу смысла. Там всё просто.
Остаётся в файл STM32.h внести нашу новую библиотеку и откомпилировать. Должно всё заработать, и теперь нам нужно проверить наш код. Пишем небольшую программку:
int main(void) { uint16_t Temp1, Temp2, Temp3, Temp4; SystemClock_Config(Quartz_8); Temp1 = millis(); delay(10); Temp2 = millis(); Temp3 = micros(); delayMicroseconds(10); Temp4 = micros(); while (1) { } }
Объявляем 4 переменные TempX
. В первой запоминаем текущие миллисекунды, делаем задержку 10 миллисекунд и во вторую переменную запоминаем новое значение миллисекунд. То же самое делаем и с микросекундами. Делаем точку останова напротив while(1)
и врубаем отладчик. После загрузки нажимаем кнопку F8, у нас программа начинает выполняться и на строке while(1)
остановится. В закладке Variables отладчика мы увидим наши переменные:
Как можно видеть, разница Temp2 - Temp1 = 10 - 0 = 10 миллисекунд. Разница Temp4 - Temp3 = 10011 - 10001 = 10 микросекунд. Как видим, всё в порядке.
На этом пока всё и, как обычно, данный проект, названный F407VExx_micros, на Яндекс Диске и в виде архива.
Тема на форуме - перейти.
Привет, можете пожалуйста объяснить, как именно APBPrescTable[] используется. Спасибо.
Объявляем переменную
Далее
В переменной частота шины APB.
Спасибо!
Получится, отпишите.
У меня всё нормально срабатывает.