This post was last edited by sonicfirr on 2022-2-3 14:02
The following GD32L233C-START special review:
1. Unboxing review https://bbs.eeworld.com.cn/thread-1192788-1-1.html
2. GD32L233C-START environment construction https://bbs.eeworld.com.cn/thread-1193053-1-1.html
3. Lighting case and extension https://bbs.eeworld.com.cn/thread-1193183-1-1.html
In this review, I analyzed the official Demo project and studied its code structure.
1. Source file directory
As mentioned in the previous post, the official demo should put the official firmware library directory "GD32L23x_Firmware_Library" and the demo case directory "GD32L233C_START_Demo_Suites" in the same directory. Let's take a look at the file directory structure of the firmware library:
2. Source code analysis
Taking the Keil project as an example, the startup files required by the case are: "system_gd32l23x.c" and "startup_gd32l23x.s". Among them:
1) startup_gd32l23x.s is the assembly startup file, the path is "..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Source\ARM", and the startup file defines that both stack and heap are 1KB, which also highlights the storage capacity of L233C.
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000400
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
2) system_gd32l23x.c is the system startup C file, which is definitely the clock initialization file. It is located in "..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Source", which defines three initialization functions. There is also a function "void SystemCoreClockUpdate(void)" to update the system clock, but through code search, it is found that the case does not call it.
Function signature |
Function |
void SystemInit(void) |
The system is initialized, the RCU is initialized, and the clock initialization and vector table offset setting functions are called. |
static void system_clock_config(void) |
Clock initialization, call the corresponding initialization function according to the macro definition, the case default external high-speed crystal oscillator 64MHz. |
static void system_clock_64m_hxtal(void) |
Configure the system clock with an external high-speed crystal oscillator at 64MHz. |
The file defines global variables to store the system clock number, and also has macro definitions to represent the clock:
#include "gd32l23x.h" //by author:这个头文件是系统头文件,下一小节将分析。
/* system frequency define */
#define __IRC16M (IRC16M_VALUE) /* internal 16 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK (__IRC16M) /* main oscillator frequency */
#define VECT_TAB_OFFSET (uint32_t)0x00000000U /* vector table base offset */
/* select a system clock by uncommenting the following line */
//#define __SYSTEM_CLOCK_8M_HXTAL (__HXTAL)
//#define __SYSTEM_CLOCK_16M_IRC16M (__IRC16M)
#define __SYSTEM_CLOCK_64M_PLL_HXTAL (uint32_t)(64000000)
//#define __SYSTEM_CLOCK_64M_PLL_IRC16M (uint32_t)(64000000)
#define SEL_IRC16M 0x00
#define SEL_HXTAL 0x01
#define SEL_PLL 0x02
/* set the system clock frequency and declare the system clock configuration function */
#ifdef __SYSTEM_CLOCK_8M_HXTAL
uint32_t SystemCoreClock = __SYSTEM_CLOCK_8M_HXTAL;
static void system_clock_8m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_64M_PLL_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_64M_PLL_HXTAL;
static void system_clock_64m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_64M_PLL_IRC16M)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_64M_PLL_IRC16M;
static void system_clock_64m_irc16m(void);
#else
uint32_t SystemCoreClock = __SYSTEM_CLOCK_16M_IRC16M;
static void system_clock_16m_irc16m(void);
#endif /* __SYSTEM_CLOCK_16M_HXTAL */
/* configure the system clock */
static void system_clock_config(void);
3) Look at the header files "gd32l23x.h" and "system_gd32l23x.h" in the CMSIS directory, which are located in "..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Include". System_gd32l23x.h mainly corresponds to the declaration of functions and global variables in system_gd32l23x.c:
#ifndef SYSTEM_GD32L23X_H
#define SYSTEM_GD32L23X_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* system clock frequency (core clock) */
extern uint32_t SystemCoreClock;
/* function declarations */
/* initialize the system and update the SystemCoreClock variable */
extern void SystemInit(void);
/* update the SystemCoreClock with current core clock retrieved from cpu registers */
extern void SystemCoreClockUpdate(void);
#ifdef __cplusplus
}
#endif
#endif /* SYSTEM_GD32L23X_H */
The gd32l23x.h code is quite large (I will not paste it here), mainly some system macro definitions (indicating system version, chip model, clock type, etc.), an enumeration of package exceptions and interrupt vectors, and base address macro definitions of various peripherals. Unlike the STM32 library, the register and mask macro definitions of the peripheral interface are distributed in the corresponding driver files. One thing to note is that the last line of the code connects to the header file - #include "gd32l23x_libopt.h", and this file connects to other peripheral driver header files. Code for gd32l23x_libopt.h:
#ifndef GD32L23X_LIBOPT_H
#define GD32L23X_LIBOPT_H
#include "gd32l23x_adc.h"
#include "gd32l23x_crc.h"
#include "gd32l23x_cau.h"
#include "gd32l23x_dac.h"
#include "gd32l23x_dbg.h"
#include "gd32l23x_dma.h"
#include "gd32l23x_exti.h"
#include "gd32l23x_fmc.h"
#include "gd32l23x_gpio.h"
#include "gd32l23x_syscfg.h"
#include "gd32l23x_i2c.h"
#include "gd32l23x_fwdgt.h"
#include "gd32l23x_pmu.h"
#include "gd32l23x_rcu.h"
#include "gd32l23x_ctc.h"
#include "gd32l23x_rtc.h"
#include "gd32l23x_spi.h"
#include "gd32l23x_timer.h"
#include "gd32l23x_usart.h"
#include "gd32l23x_lpuart.h"
#include "gd32l23x_wwdgt.h"
#include "gd32l23x_misc.h"
#include "gd32l23x_cmp.h"
#include "gd32l23x_trng.h"
#include "gd32l23x_slcd.h"
#include "gd32l23x_lptimer.h"
#include "gd32l23x_vref.h"
#endif /* GD32L23X_LIBOPT_H */
There are also several macro definitions and type redefinitions to implement functions such as Boolean and bit operations. I have been studying the bit-band addressing of the CM23 core for a few days, but I have never found any documentation. However, it is still easy to implement bit operations by looking at these macro definitions, especially since there are a lot of bit operation registers in the GPIO interface:
/* enum definitions */
typedef enum {DISABLE = 0, ENABLE = !DISABLE} EventStatus, ControlStatus;
typedef enum {RESET = 0, SET = !RESET} FlagStatus;
typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrStatus;
/* bit operations */
#define REG64(addr) (*(volatile uint64_t *)(uint32_t)(addr))
#define REG32(addr) (*(volatile uint32_t *)(uint32_t)(addr))
#define REG16(addr) (*(volatile uint16_t *)(uint32_t)(addr))
#define REG8(addr) (*(volatile uint8_t *)(uint32_t)(addr))
#define BIT(x) ((uint32_t)((uint32_t)0x01U<<(x)))
#define BITS(start, end) ((0xFFFFFFFFUL << (start)) & (0xFFFFFFFFUL >> (31U - (uint32_t)(end))))
#define GET_BITS(regval, start, end) (((regval) & BITS((start),(end))) >> (start))
4) Taking the lighting case as the main example, the application source code is analyzed. In main.c, "systick_config()" is first called to initialize SysTick. This function is defined in systick.c (located in "..\GD32L233C_START_Demo_Suites\Projects\01_GPIO_Running_LED"), which defines the periodic interrupt of SysTick (period 1ms) and implements a millisecond delay:
#include "gd32l23x.h"
#include "systick.h"
volatile static uint32_t delay;
/*!
\brief configure systick
\param[in] none
\param[out] none
\retval none
*/
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
//by author:SystemCoreClock全局量存储系统时钟,除以1000也就是1ms的时钟数
if(SysTick_Config(SystemCoreClock / 1000U)) {
/* capture error */
while(1) {
}
}
/* configure the systick handler priority */
//by author:开启SysTick异常,ISR位于gd32l23x_it.c
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
/*!
\brief delay a time in milliseconds
\param[in] count: count in milliseconds
\param[out] none
\retval none
*/
//by author:设置延时计数值delay,并等待SysTick中断回调将其减为0
void delay_1ms(uint32_t count)
{
delay = count;
while(0U != delay) {
}
}
/*!
\brief delay decrement
\param[in] none
\param[out] none
\retval none
*/
//by author:这是SysTick中断回调,对delay进行减一操作,也就是做延时计数
void delay_decrement(void)
{
if(0U != delay) {
delay--;
}
}
/*!
\brief this function handles SysTick exception
\param[in] none
\param[out] none
\retval none
*/
void SysTick_Handler(void)
{
delay_decrement(); //by author:SysTick ISR,调用了delay计数函数。
}
5) In the main() function, the GPIO clock is initialized next, using the function "void rcu_periph_clock_enable(rcu_periph_enum periph)" (RCU is the reset and setup unit module).
//by author:main()函数中的GPIO时钟初始化
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOB);
//by author:位于gd32l23x_rcu.c中,设置RCU中对应GPIO时钟设置寄存器的对应bit
void rcu_periph_clock_enable(rcu_periph_enum periph)
{
RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));
}
From the DataSheet, we can see that the GPIO is mounted on the AHB2 bus, and the parameters passed in by the above function are enumeration types, which define the offset and setting bits of each register in the RCU. First look at the enumeration definition of the parameter type (gd32l23x_rcu.h):
/* peripheral clock enable */
typedef enum {
/* AHB peripherals */
RCU_DMA = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 0U), /*!< DMA clock */
RCU_CAU = RCU_REGIDX_BIT(AHB2_REG_OFFSET, 1U), /*!< CAU clock */
RCU_TRNG = RCU_REGIDX_BIT(AHB2_REG_OFFSET, 3U), /*!< TRNG clock */
RCU_CRC = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 6U), /*!< CRC clock */
RCU_GPIOA = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 17U), /*!< GPIOA clock */
RCU_GPIOB = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 18U), /*!< GPIOB clock */
RCU_GPIOC = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 19U), /*!< GPIOC clock */
RCU_GPIOD = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 20U), /*!< GPIOD clock */
RCU_GPIOF = RCU_REGIDX_BIT(AHBEN_REG_OFFSET, 22U), /*!< GPIOF clock */
//by author:省略后续部分
}
Let's look at the description of register RCU_AHBEN in the manual. Its offset in the RCU module is 0x14, and the setting bit of GPIOA is exactly bit 17, so it is not difficult to guess that the two parameters of the parameter macro RCU_REGIDX_BIT are "register and bit offset".
//by author:第四个宏定义并不相邻,这里写在一起,方便读者查看。
#define RCU_REGIDX_BIT(regidx, bitpos) (((uint32_t)(regidx)<<6) | (uint32_t)(bitpos))
#define RCU_REG_VAL(periph) (REG32(RCU + ((uint32_t)(periph)>>6)))
#define RCU_BIT_POS(val) ((uint32_t)(val) & 0x1FU)
#define AHBEN_REG_OFFSET 0x14U /*!< AHB enable register offset */
6) Let's look at the function initialization of GPIO. Unlike the ST library, the GD library does not use the initialization structure, but directly passes parameters. I guess that the parameter passed here is guaranteed to be no more than 4 (to realize register parameter passing), so the initialization of GPIO is divided into two functions to implement "gpio_mode_set() mode initialization" and "gpio_output_options_set() output option setting".
//by author:设置GPIOA.7,8的输出功能
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7 | GPIO_PIN_8);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7 | GPIO_PIN_8);
/*!
\brief set GPIO mode
\param[in] gpio_periph: GPIOx(x = A,B,C,D,F)
only one parameter can be selected which is shown as below:
\arg GPIOx(x = A,B,C,D,F)
\param[in] mode: gpio pin mode
only one parameter can be selected which is shown as below:
\arg GPIO_MODE_INPUT: input mode
\arg GPIO_MODE_OUTPUT: output mode
\arg GPIO_MODE_AF: alternate function mode
\arg GPIO_MODE_ANALOG: analog mode
\param[in] pull_up_down: gpio pin with pull-up or pull-down resistor
only one parameter can be selected which is shown as below:
\arg GPIO_PUPD_NONE: floating mode, no pull-up and pull-down resistors
\arg GPIO_PUPD_PULLUP: with pull-up resistor
\arg GPIO_PUPD_PULLDOWN:with pull-down resistor
\param[in] pin: GPIO pin
one or more parameters can be selected which are shown as below:
\arg GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
\param[out] none
\retval none
*/
void gpio_mode_set(uint32_t gpio_periph, uint32_t mode, uint32_t pull_up_down, uint32_t pin)
{
uint16_t i;
uint32_t ctl, pupd;
ctl = GPIO_CTL(gpio_periph);
pupd = GPIO_PUD(gpio_periph);
for(i = 0U; i < 16U; i++) {
if((1U << i) & pin) {
/* clear the specified pin mode bits */
ctl &= ~GPIO_MODE_MASK(i);
/* set the specified pin mode bits */
ctl |= GPIO_MODE_SET(i, mode);
/* clear the specified pin pupd bits */
pupd &= ~GPIO_PUPD_MASK(i);
/* set the specified pin pupd bits */
pupd |= GPIO_PUPD_SET(i, pull_up_down);
}
}
GPIO_CTL(gpio_periph) = ctl;
GPIO_PUD(gpio_periph) = pupd;
}
//by author:GPIO模式设置还是配置GPIO_CTL和GPIO_PUD的过程,也可以看到相较STM32F1系列,GD32L23x增加了上下拉寄存器。
/*!
\brief set GPIO output type and speed
\param[in] gpio_periph: GPIOx(x = A,B,C,D,F)
only one parameter can be selected which is shown as below:
\arg GPIOx(x = A,B,C,D,F)
\param[in] otype: gpio pin output mode
only one parameter can be selected which is shown as below:
\arg GPIO_OTYPE_PP: push pull mode
\arg GPIO_OTYPE_OD: open drain mode
\param[in] speed: gpio pin output max speed
only one parameter can be selected which is shown as below:
\arg GPIO_OSPEED_2MHZ: output max speed 2MHz
\arg GPIO_OSPEED_10MHZ: output max speed 10MHz
\arg GPIO_OSPEED_50MHZ: output max speed 50MHz
\param[in] pin: GPIO pin
one or more parameters can be selected which are shown as below:
\arg GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
\param[out] none
\retval none
*/
void gpio_output_options_set(uint32_t gpio_periph, uint8_t otype, uint32_t speed, uint32_t pin)
{
uint16_t i;
uint32_t ospeed;
if(GPIO_OTYPE_OD == otype) {
GPIO_OMODE(gpio_periph) |= (uint32_t)pin;
} else {
GPIO_OMODE(gpio_periph) &= (uint32_t)(~pin);
}
/* get the specified pin output speed bits value */
ospeed = GPIO_OSPD(gpio_periph);
for(i = 0U; i < 16U; i++) {
if((1U << i) & pin) {
/* clear the specified pin output speed bits */
ospeed &= ~GPIO_OSPEED_MASK(i);
/* set the specified pin output speed bits */
ospeed |= GPIO_OSPEED_SET(i, speed);
}
}
GPIO_OSPD(gpio_periph) = ospeed;
}
//by author:输出模式配置寄存器GPIO_OMODE(配置开漏或推挽)和GPIO_OSPD(配置IO翻转速度)
7) Finally, let’s look at the reset and set functions of GPIO.
/*!
\brief set GPIO pin bit
\param[in] gpio_periph: GPIOx(x = A,B,C,D,F)
only one parameter can be selected which is shown as below:
\arg GPIOx(x = A,B,C,D,F)
\param[in] pin: GPIO pin
one or more parameters can be selected which are shown as below:
\arg GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
\param[out] none
\retval none
*/
void gpio_bit_set(uint32_t gpio_periph, uint32_t pin)
{
GPIO_BOP(gpio_periph) = (uint32_t)pin;
}
//by author:置位函数使用了BOP寄存器,GPIO的位操作寄存器。
/*!
\brief reset GPIO pin bit
\param[in] gpio_periph: GPIOx(x = A,B,C,D,F)
only one parameter can be selected which is shown as below:
\arg GPIOx(x = A,B,C,D,F)
\param[in] pin: GPIO pin
one or more parameters can be selected which are shown as below:
\arg GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
\param[out] none
\retval none
*/
void gpio_bit_reset(uint32_t gpio_periph, uint32_t pin)
{
GPIO_BC(gpio_periph) = (uint32_t)pin;
}
//by author:复位函数使用了GPIO位清除寄存器BC。
|