Using Registers on the STM8S
Saturday, June 23rd, 2012A few weeks ago I had a rant about the STM8S Standard Peripheral library after it cost me a fair amount of time tracking down what appeared to be a bug in the library. As a result of this I have moved over to accessing the registers on the chip directly to control the operation of the chip. A recent question ion one of the forums I haunt has prompted this post. Here I am going to present a few different ways of achieving the same task, one using the STD Peripheral Library and two examples using the registers directly but in different ways.
The task we are going to be looking at is one I perform as part of my initialisation of the STM8S, namely I set the clock up to a known state. In this case we will be setting the system to use the HSI clock running at 16 MHz with no dividers.
It is important to note that you will need to have a copy of the reference manual for the chip available when using direct register access to control the microcontroller. In this case you should be looking for document RM0016 on ST’s web site.
Using the Standard Peripheral Library
Using the STD Peripheral Library makes this a relatively simple task. We only need to call four methods:
CLK_DeInit();
CLK_SYSCLKConfig(CLK_PRESCALAR_CPUDIV1); // CPU Prescalar = 1.
CLK_SYSCLKConfig(CLK_PRESCALAR_HSIDIV1); // Prescalar = 1, 16 MHz.
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, // Automatically switch
CLK_SOURCE_HSI, // Switch to internal timer.
DISABLE, // Disable the clock switch interrupt.
CLK_CURRENTCLOCKSTATE_DISABLE); // Disable the previous clock.
The headers for the above methods can be found in the file stm8s_clk.h and the source code for the methods can be found in the file stm8s_clk.c. These source files can be found in the source code folder of the STD Peripheral Library.
Direct Register Access – Method 1
This first method of accessing the registers continues to use the STD Peripheral Library files but we do not make any calls into the library. Instead we use the definitions for the registers to access the chip directly.
So let’s start by breaking the above four methods down into their direct register equivalents.
CLK_DeInit
The first thing we need to do is to reset all of the registers to the default values:
CLK->ICKR = CLK_ICKR_RESET_VALUE;
CLK->ECKR = CLK_ECKR_RESET_VALUE;
CLK->SWR = CLK_SWR_RESET_VALUE;
CLK->SWCR = CLK_SWCR_RESET_VALUE;
CLK->CKDIVR = CLK_CKDIVR_RESET_VALUE;
CLK->PCKENR1 = CLK_PCKENR1_RESET_VALUE;
CLK->PCKENR2 = CLK_PCKENR2_RESET_VALUE;
CLK->CSSR = CLK_CSSR_RESET_VALUE;
//
// The following set has to be performed twice.
//
CLK->CCOR = CLK_CCOR_RESET_VALUE;
while ((CLK->CCOR & CLK_CCOR_CCOEN) != 0);
CLK->CCOR = CLK_CCOR_RESET_VALUE;
CLK->HSITRIMR = CLK_HSITRIMR_RESET_VALUE;
CLK->SWIMCCR = CLK_SWIMCCR_RESET_VALUE;
As you can see, the library certainly hides a large amount of work from you. Most of the above code is simply a case of setting up the registers to default values.
To find out how this all works you need to start looking in the stm8s.h file. A quick search for CLK soon leads you to the following type declaration:
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;
If you have a look at the technical reference sheet for the chip, you will find section which shows the memory layout for the registers. These are at fixed locations in memory and should map to the above layout. The __IO is defined as volatile and will prevent the compiler from optimising out any references to the variables.
The next thing to note is that we still do not have a definition for CLK. A little more searching in the same file will lead you to the following statement:
#define CLK ((CLK_TypeDef *) CLK_BaseAddress)
So this defines CLK for us as a pointer to a location in memory. Some more searching (again in the same file) leads us to the following line of code:
#define CLK_BaseAddress 0x50C0
So the code CLK->ICKR = 0; will set the register at location 0x50C0 to zero.
One point to note is the statement while ((CLK->CCOR & CLK_CCOR_CCOEN) != 0);. This illustrates the use of another type of declaration you will find in stm8s.h, namely, CLK_CCOR_CCOEN. This declaration allows you to mask off certain bits within a register and use this to set or check values in a register. The name is made up of three parts:
Name | Description |
CLK | Clock registers are being access. |
CCOR | This relates to the CCOR register. |
CCOEN | Mask the CCOEN bits in the register. |
CLK_SYSCLKConfig
The next task is to set the prescalar for the system clock. This is being set to 1 to ensure the system runs at 16 MHz.
CLK->CKDIVR &= (uint8_t) (~CLK_CKDIVR_HSIDIV);
CLK->CKDIVR |= (uint8_t) ((uint8_t) CLK_PRESCALAR_HSIDIV1 & (uint8_t) CLK_CKDIVR_HSIDIV);
The first line resets the prescalar to a known value whilst the second selects the divider which will be used.
CLK_ClockSwitchConfig
The final operation is to switch the system clock to the HSI and this is achieved with the following code:
CLK->SWCR |= CLK_SWCR_SWEN;
CLK->SWCR &= (uint8_t) (~CLK_SWCR_SWIEN);
CLK->SWR = (uint8_t) CLK_SOURCE_HSI;
uint16_t downCounter = CLK_TIMEOUT;
while ((((CLK->SWCR & CLK_SWCR_SWBSY) != 0 ) && (downCounter != 0)))
{
downCounter--;
}
Direct Register Access – Method 2
This method uses the register declarations found in the header files provided by IAR. So for the STM8S103F3 we will be looking in the file
CLK_DeInit
As before, the first thing we will do is to reset the registers to a known set of values:
CLK_ICKR = 0;
CLK_ECKR = 0;
CLK_SWR = 0xe1;
CLK_SWCR = 0;
CLK_CKDIVR = 0x10;
CLK_PCKENR1 = CLK_PCKENR1_SPI | CLK_PCKENR1_TIM2; // Enable the peripheral clocks we need.
CLK_PCKENR2 = 0;
CLK_CSSR = 0;
CLK_CCOR = 0;
while (CLK_CCOR_CCOEN != 0);
CLK_CCOR = 0;
CLK_HSITRIMR = 0;
CLK_SWIMCCR = 0;
The first thing you will notice is that by using this method we are not using the pointer dereferencing operator. Instead the application is accessing the registers directly. So let’s have a look at the header file and dissect the reset of the ICKR register. Searching for CLK_ICKR leads us to the following code:
typedef struct
{
unsigned char HSIEN : 1;
unsigned char HSIRDY : 1;
unsigned char FHW : 1;
unsigned char LSIEN : 1;
unsigned char LSIRDY : 1;
unsigned char REGAH : 1;
} __BITS_CLK_ICKR;
__IO_REG8_BIT(CLK_ICKR, 0x50C0, __READ_WRITE, __BITS_CLK_ICKR);
The first things we see is the definition of the structure which maps on to the format of the ICKR register. Each bit field is broken out and maps on to the sections of the register as defined in the data sheet.
The final line of code in the above snippet uses the __IO_REG8_BIT macro to map the data structure onto the address 0x50C0 and create a new name with bit level access.
The next thing to note is the while loop which checks the CCOOEN bit in the CCOR register – while (CLK_CCOR_CCOEN != 0);. As above, this uses a three part notation to form a reference, this time it is to a particular bit in a register. This is not a mask as in the previous example. This is broken down as follows:
Name | Description |
CLK | Clock registers are being access. |
CCOR | This relates to the CCOR register. |
CCOEN | CCOEN bits in the CCOR register. |
Some more digging in the file
#define CLK_CCOR_CCOEN CLK_CCOR_bit.CCOEN
The CLK_CCOR_bit declaration was created by the __IO_REG8_BIT macro. This is the name which has been given to the location in memory of the ICKR register.
CLK_SYSCLKConfig
The next task is to set the prescalar for the system clock. This is being set to 1 to ensure the system runs at 16 MHz. Note that a prescalar of 1 maps to the prescalar bits in the register being set to zero.
CLK_CKDIVR = 0;
CLK_ClockSwitchConfig
The final operation is to switch the system clock to the HSI and this is achieved with the following code:
CLK_SWCR_SWEN = 1;
CLK_SWR = 0xe1; // Use HSI as the clock source.
while (CLK_SWCR_SWBSY != 0); // Pause while the clock switch is busy.
Conclusion
So there you have it, three different ways of performing the same task. The method used will be down to individual preference. Happy experimenting.