Top.Mail.Ru

Часть 4. STM32 и С++. Инициализация тактового генератора. Продолжение.

Мы закончили с инициализацией 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, на Яндекс Диске и в виде архива.

Подписаться
Уведомить о
guest

4 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Igor
7 месяцев назад

Привет, можете пожалуйста объяснить, как именно APBPrescTable[] используется. Спасибо.

Igor
Ответ на комментарий  Эдуард
7 месяцев назад

Спасибо!

4
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x