STM8+SPL+CLK: Тактирование и подключение внешнего кварца

разделы: STM8 , дата: 12 июля 2016г.

В STM8 система тактирования не такая сложная как в MSP430, но гораздо сложнее чем в AVR. Здесь две основные тактовые шины. Одна идет на ЦПУ, другая на периферию. Генераторы могут быть внутренними(HSI/LSI) или внешними(HSE/LSE). В S-серии делитель может ставится и на генератор и на главную шину. В L-серии - только не шину. Зато в L-серии имеется низкочастотный RTC, со всеми вытекающими хлопотами. Чипы STM8 тактируются максимально на 16 MHz, за исключением чипов STM8S 207/208/007, на которые можно ставить внешний кварц до 24 MHz.

Мне удалось практически без изменений портировать модуль тактирования CLK, стандартной периферийной библиотеки(далее SPL), под компилятор SDCC.

Регистры тактирования S-серии, в SPL описываются структурой:

 
typedef struct CLK_struct
{
  __IO uint8_t ICKR;     // Internal Clocks Control Register
  __IO uint8_t ECKR;     // External Clocks Control Register
  uint8_t RESERVED;      // Reserved byte
  __IO uint8_t CMSR;     // Clock Master Status Register
  __IO uint8_t SWR;      // Clock Master Switch Register
  __IO uint8_t SWCR;     // Switch Control Register
  __IO uint8_t CKDIVR;   // Clock Divider Register
  __IO uint8_t PCKENR1;  // Peripheral Clock Gating Register 1
  __IO uint8_t CSSR;     // Clock Security System Register
  __IO uint8_t CCOR;     // Configurable Clock Output Register
  __IO uint8_t PCKENR2;  // Peripheral Clock Gating Register 2
  uint8_t RESERVED1;     // Reserved byte
  __IO uint8_t HSITRIMR; // HSI Calibration Trimmer Register
  __IO uint8_t SWIMCCR;  // SWIM clock control register
}
CLK_TypeDef;

Описание этих регистров для S-серии подробно на русском языке рассмотрено здесь: Микроконтроллеры STM8. Система тактирования.

Описание регистров для L-серии рассмотрено здесь: 8L-Курс, Часть 4 - Тактирование

При инициализации CLK модуля, в эти регистры записываются следующие значения:

#define CLK_ICKR_RESET_VALUE     ((uint8_t)0x01)
#define CLK_ECKR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_CMSR_RESET_VALUE     ((uint8_t)0xE1)
#define CLK_SWR_RESET_VALUE      ((uint8_t)0xE1)
#define CLK_SWCR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_CKDIVR_RESET_VALUE   ((uint8_t)0x18)
#define CLK_PCKENR1_RESET_VALUE  ((uint8_t)0xFF)
#define CLK_PCKENR2_RESET_VALUE  ((uint8_t)0xFF)
#define CLK_CSSR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_CCOR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_HSITRIMR_RESET_VALUE ((uint8_t)0x00)
#define CLK_SWIMCCR_RESET_VALUE  ((uint8_t)0x00)

Для S-серии, функционал модуля составляют следующие функции:

void CLK_DeInit(void);

void CLK_HSECmd(FunctionalState NewState);
void CLK_HSICmd(FunctionalState NewState);
void CLK_LSICmd(FunctionalState NewState);
void CLK_CCOCmd(FunctionalState NewState);
void CLK_ClockSwitchCmd(FunctionalState NewState);
void CLK_FastHaltWakeUpCmd(FunctionalState NewState);
void CLK_SlowActiveHaltWakeUpCmd(FunctionalState NewState);
void CLK_PeripheralClockConfig(CLK_Peripheral_TypeDef CLK_Peripheral, FunctionalState NewState);
ErrorStatus CLK_ClockSwitchConfig(CLK_SwitchMode_TypeDef CLK_SwitchMode, CLK_Source_TypeDef CLK_NewClock, FunctionalState ITState, CLK_CurrentClockState_TypeDef CLK_CurrentClockState);
void CLK_HSIPrescalerConfig(CLK_Prescaler_TypeDef HSIPrescaler);
void CLK_CCOConfig(CLK_Output_TypeDef CLK_CCO);
void CLK_ITConfig(CLK_IT_TypeDef CLK_IT, FunctionalState NewState);
void CLK_SYSCLKConfig(CLK_Prescaler_TypeDef CLK_Prescaler);
void CLK_SWIMConfig(CLK_SWIMDivider_TypeDef CLK_SWIMDivider);
void CLK_ClockSecuritySystemEnable(void);
void CLK_SYSCLKEmergencyClear(void);
void CLK_AdjustHSICalibrationValue(CLK_HSITrimValue_TypeDef CLK_HSICalibrationValue);
uint32_t CLK_GetClockFreq(void);
CLK_Source_TypeDef CLK_GetSYSCLKSource(void);
FlagStatus CLK_GetFlagStatus(CLK_Flag_TypeDef CLK_FLAG);
ITStatus CLK_GetITStatus(CLK_IT_TypeDef CLK_IT);
void CLK_ClearITPendingBit(CLK_IT_TypeDef CLK_IT);

Для L-серии он следующий:

void CLK_DeInit(void);

/* Internal/external clocks, CSS and CCO configuration functions **************/
void CLK_HSICmd(FunctionalState NewState);
void CLK_AdjustHSICalibrationValue(uint8_t CLK_HSICalibrationValue);
void CLK_LSICmd(FunctionalState NewState);
void CLK_HSEConfig(CLK_HSE_TypeDef CLK_HSE);
void CLK_LSEConfig(CLK_LSE_TypeDef CLK_LSE);
void CLK_ClockSecuritySystemEnable(void);
void CLK_ClockSecuritySytemDeglitchCmd(FunctionalState NewState);
void CLK_CCOConfig(CLK_CCOSource_TypeDef CLK_CCOSource, CLK_CCODiv_TypeDef CLK_CCODiv);

/* System clocks configuration functions ******************/
void CLK_SYSCLKSourceConfig(CLK_SYSCLKSource_TypeDef CLK_SYSCLKSource);
CLK_SYSCLKSource_TypeDef CLK_GetSYSCLKSource(void);
uint32_t CLK_GetClockFreq(void);
void CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_TypeDef CLK_SYSCLKDiv);
void CLK_SYSCLKSourceSwitchCmd(FunctionalState NewState);

/* Peripheral clocks configuration functions **********************************/
void CLK_RTCClockConfig(CLK_RTCCLKSource_TypeDef CLK_RTCCLKSource, CLK_RTCCLKDiv_TypeDef CLK_RTCCLKDiv);
void CLK_BEEPClockConfig(CLK_BEEPCLKSource_TypeDef CLK_BEEPCLKSource);
void CLK_PeripheralClockConfig(CLK_Peripheral_TypeDef CLK_Peripheral, FunctionalState NewState);

/* CSS on LSE configuration functions *****************************************/
void CLK_LSEClockSecuritySystemEnable(void);
void CLK_RTCCLKSwitchOnLSEFailureEnable(void);

/* Low power clock configuration functions ************************************/
void CLK_HaltConfig(CLK_Halt_TypeDef CLK_Halt, FunctionalState NewState);
void CLK_MainRegulatorCmd(FunctionalState NewState);

/* Interrupts and flags management functions **********************************/
void CLK_ITConfig(CLK_IT_TypeDef CLK_IT, FunctionalState NewState);
FlagStatus CLK_GetFlagStatus(CLK_FLAG_TypeDef CLK_FLAG);
void CLK_ClearFlag(void);
ITStatus CLK_GetITStatus(CLK_IT_TypeDef CLK_IT);
void CLK_ClearITPendingBit(CLK_IT_TypeDef CLK_IT);

Структура описывающая набор регистров для L-серии:

typedef struct CLK_struct
{
  __IO uint8_t CKDIVR;      /*!< Clock Master Divider Register */
  __IO uint8_t CRTCR;  /*!< RTC Clock selection Register */
  __IO uint8_t ICKCR;     /*!< Internal Clocks Control Register */
  __IO uint8_t PCKENR1;  /*!< Peripheral Clock Gating Register 1 */
  __IO uint8_t PCKENR2;  /*!< Peripheral Clock Gating Register 2 */
  __IO uint8_t CCOR;       /*!< Configurable Clock Output Register */
  __IO uint8_t ECKCR;     /*!< External Clocks Control Register */
  __IO uint8_t SCSR;     /*!< System clock status Register */
  __IO uint8_t SWR;      /*!< System clock Switch Register */
  __IO uint8_t SWCR;     /*!< Switch Control Register */
  __IO uint8_t CSSR;     /*!< Clock Security Sytem Register */
  __IO uint8_t CBEEPR;     /*!< Clock BEEP Register */
  __IO uint8_t HSICALR;     /*!< HSI Calibration Register */
  __IO uint8_t HSITRIMR; /*!< HSI clock Calibration Trimmer Register */
  __IO uint8_t HSIUNLCKR; /*!< HSI Unlock  Register */
  __IO uint8_t REGCSR;  /*!< Main regulator control status register */
  __IO uint8_t PCKENR3;  /*!< Peripheral Clock Gating Register 3 */
}
CLK_TypeDef;

Предварительно загружаемые в них значения:

#define CLK_CKDIVR_RESET_VALUE    ((uint8_t)0x03)
#define CLK_CRTCR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_ICKCR_RESET_VALUE     ((uint8_t)0x11)
#define CLK_PCKENR1_RESET_VALUE   ((uint8_t)0x00)
#define CLK_PCKENR2_RESET_VALUE   ((uint8_t)0x80)
#define CLK_PCKENR3_RESET_VALUE   ((uint8_t)0x00)
#define CLK_CCOR_RESET_VALUE      ((uint8_t)0x00)
#define CLK_ECKCR_RESET_VALUE     ((uint8_t)0x00)
#define CLK_SCSR_RESET_VALUE      ((uint8_t)0x01)
#define CLK_SWR_RESET_VALUE       ((uint8_t)0x01)
#define CLK_SWCR_RESET_VALUE      ((uint8_t)0x00)
#define CLK_CSSR_RESET_VALUE      ((uint8_t)0x00)
#define CLK_CBEEPR_RESET_VALUE    ((uint8_t)0x00)
#define CLK_HSICALR_RESET_VALUE   ((uint8_t)0x00)
#define CLK_HSITRIMR_RESET_VALUE  ((uint8_t)0x00)
#define CLK_HSIUNLCKR_RESET_VALUE ((uint8_t)0x00)
#define CLK_REGCSR_RESET_VALUE    ((uint8_t)0xB9)

В SPL examples есть несколько примеров по использованию библиотеки. Взяв за основу с одной стороны их, а с другой стороны пример из предыдущего поста, для опроса состоянии кнопки, я составил программу которая по нажатию кнопки переключается на внешний кварц. На плату с stm8s103f3p6 кварц подключается на пины A1:A2. Конденсаторы для проверки не обязательны, они нужны для стабильной работы кварца.

Текст программы:

#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_clk.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define BTN_LED_PORT GPIOC
#define BTN_LED  GPIO_PIN_4

#define BTN_PORT GPIOD
#define BTN GPIO_PIN_2

static void delay(uint32_t t)
{
    while(t--);
}

uint8_t i, press;

ErrorStatus status = FALSE;

int main( void )
{

    // ----------- GPIO CONFIG -------------------
    GPIO_DeInit(LED_PORT);
    GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_LED_PORT);
    GPIO_Init(BTN_LED_PORT, BTN_LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_PORT);
    GPIO_Init(BTN_PORT, BTN, GPIO_MODE_IN_FL_NO_IT); // Input floating, no external interrupt

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // set 16 MHz for CPU


    i=0; press=0;
    for(;;)
    {
        if(GPIO_ReadInputPin(BTN_PORT, BTN))
        {
            GPIO_WriteHigh(BTN_LED_PORT, BTN_LED);
            if (press == 0)
            {
                status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE,
                        CLK_CURRENTCLOCKSTATE_DISABLE);
                press=1;
            }
        } else
            GPIO_WriteLow(BTN_LED_PORT, BTN_LED);

            delay(1000); i++;

        if (i > 50)
        {
            GPIO_WriteReverse(LED_PORT, LED);
            i=0;
        }
    }
}

О смене генератора с HSI на внешний кварц(HSE), можно будет судить по мигающему зеленому светодиоду на плате. Правда если ваш кварц будет на 16 MHz то на первый взгляд ничего не изменится, т.к. частота внутреннего генератора так же равна 16 MHz. Разница будет если при работающем от кварца микроконтроллере, вытащить его из макетки. Тогда светодиод остановится. Я ставил кварцы на 8 и 25 MHz, и с ними переход на внешний резонатор было видно по изменившейся частоте мигания светодиода.

Теперь немного про вынимание кварца из работающего микроконтроллера. В STM8 есть фича "Clock security system", которая позволяет обнаруживать сбой в работе кварца, автоматически переключаться на внутренний генератор, а также генерировать прерывание при возникновении такого события. Включать эту фичу следует уже после того, как подключили HSE.

Вариант предыдущей программы с использованием Clock security system:

#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_clk.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define BTN_LED_PORT GPIOC
#define BTN_LED  GPIO_PIN_4

#define BTN_PORT GPIOD
#define BTN GPIO_PIN_2


INTERRUPT_HANDLER(IRQ_Handler_CLK, 2)
{
    CLK_ClearITPendingBit(CLK_IT_CSSD);
    /* Disable CSS interrupt */
    CLK_ITConfig(CLK_IT_CSSD, DISABLE);
}

static void delay(uint32_t t)
{
    while(t--);
}

uint8_t i, press;

ErrorStatus status = FALSE;

int main( void )
{

    // ----------- GPIO CONFIG -------------------
    GPIO_DeInit(LED_PORT);
    GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_LED_PORT);
    GPIO_Init(BTN_LED_PORT, BTN_LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_PORT);
    GPIO_Init(BTN_PORT, BTN, GPIO_MODE_IN_FL_NO_IT); // Input floating, no external interrupt


    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // set 16 MHz for CPU



    i=0; press=0;
    for(;;)
    {
        if(GPIO_ReadInputPin(BTN_PORT, BTN))
        {
            GPIO_WriteHigh(BTN_LED_PORT, BTN_LED);
            if (press == 0)
            {
                status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE,
                                CLK_CURRENTCLOCKSTATE_DISABLE);
                if (status)
                {
                    CLK_ITConfig(CLK_IT_CSSD, ENABLE); // ebanle CLK inteerupt
                    CLK_ClockSecuritySystemEnable();

                    enableInterrupts();
                }

                press=1;
            }
        } else
            GPIO_WriteLow(BTN_LED_PORT, BTN_LED);

            delay(1000); i++;

        if (i > 50)
        {
            GPIO_WriteReverse(LED_PORT, LED);
            i=0;
        }
    }
}

Теперь при вынимании кварца во время работы, зеленый светодиод не остановится, он продолжает мигать, но очень медленно на частоте 2 MHz. Т.е. Clock security system сбросит все настройки CLK до дефолтовых.

Здесь прерывание по второму вектору оформлено в силе SPL. Чтобы это было возможно, в stm8s.h я вставил следующий макрос:

#define INTERRUPT_HANDLER( a, b )         void a(void) __interrupt(b)
#define ISR( a, b )                       void a(void) __interrupt(b)

Вторая строка не соответствует SPL, но она делает определение прерывания короче.

Для L-серии синтаксис немного другой. Пример последней програмы для L-серии будет таким:

#include "stm8l.h"
#include "stm8l_gpio.h"
#include "stm8l_clk.h"

#define PORT GPIOB

#define LED   GPIO_Pin_1
#define BTN_LED GPIO_Pin_2

#define BTN   GPIO_Pin_3

static void delay(uint32_t t)
{
    while(t--);
}

uint8_t i, press;

INTERRUPT_HANDLER(IRQ_Handler_CLK, 17)
{
        CLK_ClearITPendingBit(CLK_IT_CSSD);
        /* Disable CSS interrupt */
        CLK_ITConfig(CLK_IT_CSSD, DISABLE);
}

int main( void )
{
    // ---------   GPIO Config ---------------------------

    GPIO_DeInit(PORT);

    GPIO_Init(PORT, LED|BTN_LED, GPIO_Mode_Out_PP_Low_Fast);

    GPIO_Init(PORT, BTN, GPIO_Mode_In_FL_No_IT); // Input floating, no external interrupt

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

    CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);

    i=0; press=0;
    for(;;)
    {
        if(GPIO_ReadInputDataBit(PORT, BTN))
        {
            GPIO_SetBits(PORT, BTN_LED);
            if (press == 0)
            {
                // Select HSE as system clock source
                CLK_SYSCLKSourceSwitchCmd(ENABLE);
                CLK_SYSCLKSourceConfig(CLK_SYSCLKSource_HSE);
                
                while (CLK_GetSYSCLKSource() != CLK_SYSCLKSource_HSE){};

                CLK_ITConfig(CLK_IT_CSSD, ENABLE); // ebanle CLK inteerupt
                CLK_ClockSecuritySystemEnable();

                enableInterrupts();
                press=1;
            }
        } else
            GPIO_ResetBits(PORT, BTN_LED);

            delay(1000); i++;

        if (i > 50)
        {
            GPIO_ToggleBits(PORT, LED);
            i=0;
        }
    }
}

Программа проверялась на 051-ом чипе, и в этом случае при вынимании кварца происходил возврат к пользовательским настройкам CLK, а не дефолтовым. В данном случае, это 16 MHz.

поделиться: