Контроллеры STM32 обладают достаточно развитой системой тактирования, включающей кучу различных делителей, умножителей и селекторов, которые позволяют «разогнать» до 72 МГц частоту ядра и системной шины, а также организовать тактирование на различных частотах всех входящих в состав контроллера модулей (USB, I/O, I2C, SPI…).
При этом стартует контроллер всегда от внутреннего генератора на 8 МГц, а дальнейший «разгон» и настройка тактирования различных модулей выполняется программно.
Все регистры, отвечающие за настройку системы тактирования сгруппированы в так называемый блок RCC (reset and clock control). Это десять 32-х битных регистров, доступных в памяти начиная с адреса 0x40021000, названия которых начинаются с «RCC_».
Настройку системы тактирования можно разделить на два этапа. На первом этапе настраивается системная тактовая частота. На втором этапе — частота системной шины AHB и подключенные к шине AHB модули. Отдельно от этих двух этапов можно настроить низкоскоростные генераторы для часов реального времени и независимого «собачьего таймера».
Тут важно запомнить и понять вот что: 1) все модули контроллера «сидят» на каких-нибудь шинах, 2) после включения контроллера тактирование большинства модулей, а также тактирование периферийных шин — выключены. Соответственно, до тех пор, пока вы не затактируете выключенные шины и модули — вам не будут доступны регистры настройки модулей. Из этих регистров будут читаться одни нули, а запись в них не будет иметь никакого эффекта.
Структурной схему, показывающую какие в контроллере есть шины и модули, какие модули к какой шине подключены и какие регистры используются для настройки всей этой системы, можно найти в документации. Тут самый главный документ — это Reference Manual RM0008 (CD00171190.pdf).
На рисунке ниже изображена древовидная структура, показывающая компоненты, используемые для настройки системной тактовой частоты, часов реального времени и независимого «собачьего таймера», с указанием регистров и битов, необходимых для настройки. Название регистров написано в прямоугольниках на заднем плане, а название битов — в прямоугольниках на переднем плане, в квадратных скобках.
Серыми прямоугольниками в левом ряду обозначены 4 первичных источника тактовых сигналов: HSI — высокоскоростной встроенный RC-генератор, HSE — усилитель внешнего высокоскоростного кварцевого резонатора, LSE OSC — усилитель часового кваца (внешний низкоскоростной резонатор), LSI RC — низкоскоростной RC-генератор.
Настройка приведённого на рисунке выше «дерева» происходит слева-направо, начиная с выбора первичных источников тактового сигнала. При этом общая методика такая: настраиваем модуль, включаем модуль, ждём сигнала готовности. После получения сигнала готовности переходим к настройке компонента, находящегося правее.
Сигнал готовности означает, что выходная частота соответствующего компонента стабилизировалась и её можно использовать дальше. В качестве сигнала готовности используются специальные биты в специальных регистрах (индивидуальные для каждого модуля). На схеме эти регистры и биты не отражены, но их можно найти в документации. Например, о готовности PLL сигнализируется установкой бита PLLRDY в регистре RCC_CR, о готовности усилителя внешнего кварца — установкой бита HSERDY в регистре RCC_CR и так далее.
Интересной фишкой является наличие в процессоре Cortex-M3 модуля CSS (clock security system). Этот модуль включается программно и после включения начинает анализировать работу внешнего кварца (HSE). При обнаружении ошибки, он автоматически отключает внешний кварц и переключает селектор выбора системной частоты на внутренний высокоскоростной генератор (HSI). Если HSE использовался не напрямую, а через PLL, то PLL тоже выключается. Кроме того, при срабатывании модуля CSS генерируется прерывание NMI И посылаются сигналы ошибки таймерам TIM1, TIM8.
После того, как первый этап закончен — можно приступать ко второму. Настраиваемые на этом этапе компоненты и последовательность их настройки также изображены в виде древовидной структуры (рисунок ниже).
Если мы работаем с flash-памятью, а не перегружаем программу в оперативку, то для оптимальной работы нашего камня должны быть настроены ещё кое-какие опции.
Во-первых, нужно задать параметр, называемый Latency. Это количество циклов задержки для операций чтения из flash. Оно задаётся исходя из следующих правил:
- =0, если 0 ≤ SYSCLK ≤ 24 MHz;
- =1, если 24 MHz ≤ SYSCLK ≤ 48 MHz;
- =2, если 48 MHz ≤ SYSCLK ≤ 72 MHz.
Во-вторых, для ускорения работы процессора, должен быть включен буфер предварительной выборки (prefetch buffer), который позволяет вычитывать инструкции из flash двумя 64-х битными блоками. Включить или выключить этот буфер можно только когда SYSCLK < 24 MHz и делитель шины AHB=1. После сброса он автоматически включается, поэтому нам ничего делать не нужно. Но обычно если его хотят выключить, то делают это в самом начале программы, при инициализации, когда контроллер ещё работает от внутреннего генератора 8 MHz.
Обе перечисленные выше настройки flash-памяти доступны в регистре FLASH_ACR.
Для контроля полученных в итоге частот можно использовать специальную ногу (PA8), одна из функций которой — работа в качестве MCO (microcontroller clock output). На эту ногу можно настроить вывод одной из следующих частот: SYSCLK, HSI, HSE, PLL/2. Выбор осуществляется программированием битов MCO[2:0] в регистре RCC_CFGR. Кроме программирования этих битов нужно затактировать шину APB2 (на которой находится GPIO) и настроить ногу PA8 на функционирование в качестве MCO. Кроме того, при использовании PA8 в качестве MCO необходимо учитывать, что частота GPIO не должна превышать 50 МГц (а SYSCLK как мы помним можно разогнать до 72-х).
Ну вот пожалуй и всё. А в заключении давайте напишем небольшой пример — разгоним наш камень до заветных 72-х МГц и выведем частоту PLL/2 через MCO:
;--------------------------------------------------------- ; blocks addresses SAR EQU 0x42000000 ; Start Alias Region FLASH EQU 0x40022000 RCC EQU 0x40021000 PORTA EQU 0x40010800 ; registers addresses FLASH_ACR EQU FLASH+0x0 RCC_CR EQU RCC+0x0 RCC_CFGR EQU RCC+0x4 RCC_APB2ENR EQU RCC+0x18 GPIOA_CRH EQU PORTA+0x4 ; bits numbers LAT1 EQU 1 HSEON EQU 16 HSERDY EQU 17 PLLSRC EQU 16 PLLMUL EQU 18 PPRE2 EQU 11 PLLON EQU 24 PLLRDY EQU 25 SW1 EQU 1 AFIOEN EQU 0 IOPAEN EQU 2 MCO EQU 24 MODE8 EQU 0 CNF8 EQU 2 AREA STACK, NOINIT, READWRITE SPACE 0x400 Stack_top AREA RESET, DATA, READONLY dcd Stack_top dcd Program_start AREA PROGRAM, CODE, READONLY ENTRY Program_start InitClock mov r9, #1 ; устанавливаем Latency = 2 ldr r0, =SAR+(FLASH_ACR&0x00FFFFFF)*0x20+LAT1*4 str r9,[r0] ; включаем HSE ldr r0, =SAR+(RCC_CR&0x00FFFFFF)*0x20+HSEON*4 str r9,[r0] ; ждём появления флага HSERDY ldr r0, =RCC_CR ; загружаем в r0 адрес регистра RCC_CR wait_hserdy ldr r10,[r0] ; читаем регистр RCC_CR ; проверяем, равен ли бит HSERDY единице (&0x20000) tst r10,#(1<<HSERDY) beq wait_hserdy ; выбираем HSE источником для PLL, устанавливаем множитель=9, ; предделитель для APB2 (/2), выбираем источник для MCO ldr r0,=RCC_CFGR ; загружаем адрес регистра RCC_CFGR ldr r10,=(1<<PLLSRC)+(7<<PLLMUL)+(4<<PPRE2)+(7<<MCO) str r10,[r0] ; включаем PLL ldr r0,=SAR+(RCC_CR&0x00FFFFFF)*0x20+PLLON*4 str r9,[r0] ; ждём появления флага PLLRDY ldr r0, =RCC_CR ; загружаем адрес регистра RCC_CR wait_pllrdy ldr r10,[r0] ; читаем RCC_CR ; проверяем, равен ли бит PLLRDY единице (&0x2000000) tst r10, #(1<<PLLRDY) beq wait_pllrdy ; выбираем PLL в качестве SYSCLK ldr r0,=SAR+(RCC_CFGR&0x00FFFFFF)*0x20+SW1*4 str r9,[r0] ; включаем тактирование PORTA ldr r0,=SAR+(RCC_APB2ENR&0x00FFFFFF)*0x20+IOPAEN*4 str r9,[r0] ; конфигурим PORTA для MCO ldr r0,=GPIOA_CRH ldr r10,[r0] ; читаем регистр GPIOA_CRH ; устанавливаем для PA8 MODE=11 (output max speed 50MHz), ; CNF=10 (alternate function output push-pull) ldr r11,=(3<<MODE8)+(2<<CNF8) bfi r10, r11, #0, #4 ; копируем 4 младших бита r11 в r10 str r10,[r0] ; записываем r10 в GPIOA_CRH ;------------------ ; ничего не делаем Work b Work END ;--------------------------------------------------------- |
Естественно, в реальности никто в заголовке имена и адреса регистров и битов не описывает, вместо этого берут готовый файл, в котором все эти имена описаны и добавляют его в свой код директивой GET. В примере я их специально описал, чтобы было понятно, откуда что берётся (сопоставьте их с картой памяти, которую мы рассматривали в третьей части).
Ну да ладно, пусть этот код и не самый оптимальный, зато, надеюсь, предельно понятный. Для установки отдельных битов в примере используется метод bit-banding, если требуется изменить сразу кучу битов — классические «чтение-модификация-запись».
Если есть вопросы, как обычно, задаём на форуме.
- Часть 1. Установка MDK, создание проекта, основы Keil uVision
- Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
- Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
- Часть 4. Регистры, старт и режимы работы контроллеров STM32
- Часть 5. Как залить прошивку в контроллер
- Часть 6. Настройка системы тактирования
- Часть 7. Работа с портами ввода-вывода
- Часть 8. Процедуры на ассемблере для STM32
- Часть 9. Система прерываний
- Часть 10. CMSIS, использование стандартных библиотек и функций
- Часть 11. Подключение и использование драйверов из пакета StdPeriph
- Часть 12. Работа с модулями USART и UART.
- Часть 13. Работа с модулями ADC
- Часть 14. Использование DMA
- Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
- Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
- Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
- Часть 15. Таймеры. Глава 4 — Продвинутые таймеры TIM1, TIM8
- Часть 16. Создание устройства USB HID в Keil uVision при помощи библиотечного компонента USB
- Приложение 1. Набор инструкций THUMB-2 и особенности их использования
- Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
- Приложение 3. Драйвера и функции библиотеки StdPeriph