Контроллеры 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, если требуется изменить сразу кучу битов - классические "чтение-модификация-запись".
При этом стартует контроллер всегда от внутреннего генератора на 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, если требуется изменить сразу кучу битов - классические "чтение-модификация-запись".
Привет тезка !
ОтветитьУдалитьтоже пишете на ассемблере под stm32 ? :-)