Мы закончили с инициализацией 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.
Спасибо!
Получится, отпишите.
У меня всё нормально срабатывает.