Систему тактирования я уже бегло рассматривал в случае использования SPL. Стандартная периферийная библиотека, на мой взгляд, предлагается фирмой STM в качестве инструмента для быстрого освоения семейства микроконтроллеров STM8, программистами ранее не работавшими с ними. Сейчас я считаю, что она совершенно лишняя. Хотя, в примерах я использую именованные константы взятые из заголовочных файлов SPL.
Документация которая понадобится для прочтения статьи: Reference Manual STM8S - RM0016, глава 9. В качестве целевого микроконтроллера я буду использовать 20-пиновый STM8S103F3P6.
Скачать полные исходники со сборочными файлам и скомпилированными прошивками, можно по ссылке в конце статьи.
Система тактирования микроконтроллеров STM8S представлена на следующей картинке:
Регистры отвечающие из систему тактирования и их значения по умолчанию представлены на следующей таблице:
Дефолтные настройки системы тактирования можно изменять через Options Bytes. Они разные для различных серий STM8S. В случае STM8S103 они выглядят так:
Начнем с простого. В качестве шаблонного проекта возьмем Blink на таймере TIM4 из предыдущей статьи.
В главной функции main() заменим строки:
// Set fCPU = 16MHz CLK_CKDIVR=0;
на вызов функции следующего содержания:
static inline void clk_setup() { // Set fCPU = 16MHz CLK_CKDIVR=0; }
С этой функцией и будем в дальнейшем работать.
За подключение периферии к шине тактирования отвечают два регистра: CLK_PCKENR1 и CLK_PCKENR2
Т.к. в программе из периферии используется лишь таймер TIM4, в функцию clk_setup() следует добавить строки:
CLK_PCKENR1=CLK_PCKENR1_TIM4;
CLK_PCKENR2=0;
Внутренними генераторам HSI и LSI управляет регистр CLK_ICKR:
В установках по умолчанию для этого регистра, низкочастотный LSI - генератор выключен, а высокочастотный HSI генератор включен. Это установки полностью соответствуют нашим запросам и регистр CLK_ICKR пока можно не трогать.
Чип STM8S после включения питания стартует с внутренним 16 МГц генератором тактового сигнала HSI. Выход частоты с него можно менять установкой предделителя fHSI равному 1, 2, 4, или 8. По умолчанию применяется предделитель равный 8.
Частотой процессора можно управлять с помощью предделителя fCPU. По умолчанию он равен 1.
Предделители fHSI и fCPU устанавливаются через регистр CLK_CKDIVR:
Если записать в регистр значение CLK_PRESCALER_HSIDIV1, т.е. ноль, т.о. микроконтроллер будет работать на частоте 16 МГц. Светодиод при этом будет мигать с полупериодом в 1 секунду. Однако если понизить частоту процессора до 1 МГц командой:
CLK_CKDIVR=CLK_PRESCALER_HSIDIV1|CLK_PRESCALER_CPUDIV16;
то светодиод все-равно будет мигать с полупериодом в 1 секунду. Почему? Т.к. задержка формируется через таймер, и если посмотреть на схему тактирования:
то видно, что предделитель fCPU никак на периферию не влияет. Он влияет только на частоту процессора. Если вместо задержки на таймере и использовать задержку на счетчике, например из примера предыдущей статьи: Передача параметров из Си в ассемблерную функцию, то она уже будет работать должным образом.
При подключении внешнего кварца, генератору HSE требуется некоторое время для стабилизации частоты. По умолчанию, это время равно 2048 тактам. Это значение можно менять через параметр Option Bytes - HSECNT.
В связи с этим, существует два варианта переключения на HSE: автоматический и ручной.
Автоматический вариант предполагает минимум действий со стороны программиста. Требуется всего пара команд, чтобы указать новый источник тактового сигнала и дать команду коммутатору на переключение на него. Далее микроконтроллер все сделает сам. Он включит генератор HSE, дождется его стабилизации, и после автоматически переключится на него. Пользовательская программа при этом не ждет переключения, она занимается своими делами.
Блок-схема алгоритма представлена на картинке ниже:
Пример программы с реализацией такого алгоритма у меня получился таким:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_hse_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_128; // =7, prescaler =128 // TIM4_ARR = 124; // freq Timer IRQ =1kHz TIM4_ARR = 16; // freq Timer IRQ =1kHz/2MHz HSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main(void) { // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. enableInterrupts(); // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2 MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_hse_auto(); } static void switch_to_hse_auto() { // Enables Clock switch CLK_SWCR |= CLK_SWCR_SWEN; // Enable HSE CLK_SWR = CLK_SOURCE_HSE; }
Здесь таймер настроен на мигание светодиода с полупериодом в 1 секунду, при частоте генератора 2 МГц. Если поставить кварц с другой частотой, то частота мигания светодиода тоже измениться. Ну и конечно же, при "горячем" вынимание кварца, скажем, из беспаячной макетки мигание светодиода остановится.
Для переключения генератора тактового сигнала используется управляющий регистр коммутатора:
Для указания целевого генератора используется регистр CLK_SWR:
После переключения генератора, содержимое регистра CLK_SWR копируется в CLK_CMSR:
Программу можно модифицировать с использованием прерывания для отключения внутреннего генератора HSI:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 void IRQ_Handler_CLK(void) __interrupt(2) { // turn off HSI CLK_ICKR &= (uint8_t)(~CLK_ICKR_HSIEN); // Clear Pending Flag CLK_SWCR &= (uint8_t)~(CLK_SWCR_SWIF); } extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_hse_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_128; // =7, prescaler =128 // TIM4_ARR = 124; // freq Timer IRQ =1kHz TIM4_ARR = 16; // freq Timer IRQ =1kHz/2MHz HSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main( void ) { // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. enableInterrupts(); // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_hse_auto(); } static void switch_to_hse_auto() { // Enables Clock switch & Switch interrupt CLK_SWCR |= (CLK_SWCR_SWEN | CLK_SWCR_SWIEN); // Enable HSE CLK_SWR = CLK_SOURCE_HSE; }
Если когда-либо потребуется отключить HSE, например после обратного переключения на HSI, тогда следует воспользоваться регистром CLK_ECKR:
Алгоритм для ручного переключения генератора представлен на блок-схеме ниже:
Реализация этого алгоритма у меня получилась такой:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 void IRQ_Handler_CLK(void) __interrupt(2) { CLK_SWCR &= (uint8_t)~(CLK_SWCR_SWIF); // Enables Clock switch CLK_SWCR |= (CLK_SWCR_SWEN); } extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static uint8_t switch_to_hse_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_128; // =7, prescaler =128 // TIM4_ARR = 124; // freq Timer IRQ =1kHz TIM4_ARR = 16; // freq Timer IRQ =1kHz/2MHz HSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main( void ) { enableInterrupts(); // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE if (switch_to_hse_auto()) CLK_ICKR &= (uint8_t)(~CLK_ICKR_HSIEN);// turn off HSI } static uint8_t switch_to_hse_auto() { // Enables Switch interrupt CLK_SWCR |= (CLK_SWCR_SWIEN); // Enable HSE CLK_SWR = CLK_SOURCE_HSE; wfi(); while((CLK_SWCR & CLK_SWCR_SWBSY) != 0 ); return (CLK_CMSR == CLK_SOURCE_HSE) ? 1 : 0; }
Модуль безопасности системы тактирования CSS позволяет отслеживать работу генератора HSE и при неполадках с его стороны, автоматически переключать такирование на внутренний генератор HSI. При этом для HSI устанавливается предделитель равный 8, генератор HSE отключается, выставляются служебные флаги.
CSS конфигурируется через регистр CLK_CSSR:
Простейший случай использования CSS добавляет всего две строчки к предыдущему примеру:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_hse_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_128; // =7, prescaler =128 // TIM4_ARR = 124; // freq Timer IRQ =1kHz TIM4_ARR = 16; // freq Timer IRQ =1kHz/2MHz HSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main(void) { // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. enableInterrupts(); // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_hse_auto(); } static void switch_to_hse_auto() { // Enables Clock switch CLK_SWCR |= CLK_SWCR_SWEN; // Enable HSE CLK_SWR = CLK_SOURCE_HSE; // waiting for switch to HSE while((CLK_SWCR & CLK_SWCR_SWBSY) != 0 ); // Enable CSS CLK_CSSR |= CLK_CSSR_CSSEN; }
Обращаю внимание, что перед включением CSS нужно дождаться переключения на HSE.
Можно использовать прерывание чтобы установить свой предделитель HSI при срабатывании CSS. Т.к. прерывание у системы тактирования одно на всех, нужно либо конфигурировать его на какое-то одно событие, либо парсить флаги при входе в обработчик прерывания.
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 void IRQ_Handler_CLK(void) __interrupt(2) { // Clear Pending Flag & Disable Interrupt CLK_CSSR &= ~(CLK_CSSR_CSSD|CLK_CSSR_CSSDIE); // Set fCPU = 16MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV1; } extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_hse_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_128; // =7, prescaler =128 // TIM4_ARR = 124; // freq Timer IRQ =1kHz TIM4_ARR = 16; // freq Timer IRQ =1kHz/2MHz HSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main(void) { enableInterrupts(); // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); э // let's go.. // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_hse_auto(); } static void switch_to_hse_auto() { // Enables Clock switch CLK_SWCR |= CLK_SWCR_SWEN; // Enable HSE CLK_SWR = CLK_SOURCE_HSE; while((CLK_SWCR & CLK_SWCR_SWBSY) != 0 ); // Set CSSEN bit CLK_CSSR |= (CLK_CSSR_CSSEN|CLK_CSSR_CSSDIE); }
Еще одним интересным вариантом является возможность тактировать микроконтроллер от 128 кГц низкочастотного внутреннего генератора LSI. Генератор имеет погрешность ±12%, так что использование протоколов с четкими временным границами может быть затруднено. С помощью предделителя CPUDIV возможно снижать частоту CPU вплоть до 1 кГц.
Для того что бы задействовать LSI в качестве главного генератора тактовой частоты, необходимо будет выставить в Option Bytes флаг LSI_EN:
Пример Blink'a с полупериодом 1 сек. на генераторе LSI:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_lsi_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_64; // =6, prescaler =64 TIM4_ARR = 1; // freq Timer IRQ =1kHz/ 128 kHz LSI TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main(void) { // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. enableInterrupts(); // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2 MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_lsi_auto(); } static void switch_to_lsi_auto() { // Enables Clock switch CLK_SWCR |= CLK_SWCR_SWEN; // Enable LSI CLK_SWR = CLK_SOURCE_LSI; while((CLK_SWCR & CLK_SWCR_SWBSY) != 0 ); }
Кроме внешнего кварца, номинал которого не может быть меньше 1МГц, микроконтроллер STM8S можно тактировать от внешнего генератора тактовой частоты, который лишен этого ограничения. Для подключения используется пин OSCIN, при этом пин OSCOUT остается свободным как GPIO, и может использоваться для других нужд.
Что бы включить поддержку внешнего генератора, в Options Bytes нужно будет выставить флаг EXTCLK:
Генератора у меня под рукой не было, зато был DS3231, у которого имеется пин с меандром на выходе и частотой 32 кГц. Причем это высокоточный тактовый сигнал, в отличии от LSI.
Ну и опять снова Blink уже с тактированием от DS3231:
#include <stdint.h> #include "stm8s103f.h" #include "stm8s_tim4.h" #include "stm8s_clk.h" #define CFG_GCR_AL ((uint8_t)0x02) /*!< Activation Level bit mask */ #define LED 5 extern void delay(void) __interrupt(23); volatile uint16_t count; static inline void clk_setup(); static void switch_to_lsi_auto(); void delay_ms(uint16_t ms) { count = ms; CFG_GCR = CFG_GCR_AL; // =2, set AL bit TIM4_SR = 0x0; // Clear Pending Bit TIM4_PSCR = TIM4_PRESCALER_16; // prescaler =16 TIM4_ARR = 1; // freq Timer IRQ =1kHz/ 32 kHz ext.HSE TIM4_IER = (uint8_t)TIM4_IT_UPDATE; // =1, enable interrupt TIM4_CR1 = TIM4_CR1_CEN; // =1, enable counter wfi(); // goto sleep TIM4_CR1 = 0x0; // disable counter } int main(void) { // Setup Clock System clk_setup(); // Setup GPIO PB_DDR=(1<<LED); PB_CR1=(1<<LED); // let's go.. enableInterrupts(); // main loop for(;;) { PB_ODR ^= (1<<LED); delay_ms(1000); } } static inline void clk_setup() { // Set fCPU = 2 MHz CLK_CKDIVR=CLK_PRESCALER_HSIDIV8; // enable TIM4 and turn off other peripherals CLK_PCKENR1=CLK_PCKENR1_TIM4; CLK_PCKENR2=0; // enable HSE switch_to_lsi_auto(); } static void switch_to_lsi_auto() { // Enables Clock switch CLK_SWCR |= CLK_SWCR_SWEN; // Enable LSI CLK_SWR = CLK_SOURCE_HSE; while((CLK_SWCR & CLK_SWCR_SWBSY) != 0 ); }
Заключение. Остался не рассмотренным выход тактовой частоты CCO, я не смог придумать примера его использования кроме синхронизации нескольких микроконтроллеров. Еще я не затронул регулятор напряжения MVR, отключение которого позволяет повысить энергосбережение. Но S-серию я не рассматриваю как энергосберегающую. Все остальное вроде рассмотрел.
Скачать полные исходники со сборочными файлам и скомпилированными прошивками, можно по ссылке .