本帖最后由 sonicfirr 于 2022-2-3 14:02 编辑
在下的GD32L233C-START专题测评:
一、开箱评测 https://bbs.eeworld.com.cn/thread-1192788-1-1.html
二、GD32L233C-START环境搭建https://bbs.eeworld.com.cn/thread-1193053-1-1.html
三、点灯案例与扩展https://bbs.eeworld.com.cn/thread-1193183-1-1.html
本篇测评,本人对官方的Demo工程进行分析,研究其代码结构
1、源文件目录
前篇帖子已经说过,官方Demo要将官方固件库目录“GD32L23x_Firmware_Library”和Demo案例目录“GD32L233C_START_Demo_Suites”,放在同一目录下。下面先来看看固件库的文件目录结构:
2、源码分析
以Keil工程为例,案例需要的启动文件有:“system_gd32l23x.c”和“startup_gd32l23x.s”。其中:
1)startup_gd32l23x.s是汇编启动文件,路径是“..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Source\ARM”,启动文件中定义了stack和heap都是1KB——也彰显了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是系统启动C文件,肯定是时钟的初始化,位于“..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Source”,其中定义了初始化的三个函数。另有一个更新系统时钟的函数“void SystemCoreClockUpdate(void)”,不过通过代码查找,发现案例并没有做调用。
函数签名 |
功能 |
void SystemInit(void) |
系统初始化,RCU初始化,并调用时钟初始化和向量表偏移设置函数。 |
static void system_clock_config(void) |
时钟初始化,根据宏定义调用对应的初始化函数,案例默认外部高速晶振64MHz。 |
static void system_clock_64m_hxtal(void) |
以外部高速晶振64MHz配置系统时钟。 |
文件中定义了全局变量存储系统时钟数,也有表示时钟的宏定义:
#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)再看CMSIS目录中的头文件“gd32l23x.h”和“system_gd32l23x.h”,位于“..\GD32L23x_Firmware_Library\CMSIS\GD\GD32L23x\Include”,system_gd32l23x.h主要是对应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 */
gd32l23x.h代码量比较多(就不粘贴),主要是一些系统宏定义(表示系统版本、芯片型号、时钟类型等),一个封装异常和中断向量的枚举,各个外围的基地址宏定义。与STM32的库不同,外围接口的寄存器和掩码宏定义分布在对应的驱动文件中了。需要关注一点,代码的最后一行连接了头文件——#include "gd32l23x_libopt.h",而这个文件则连接了其它的外围驱动头文件。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 */
另有几个宏定义和类型重定义,实现了诸如布尔型、位操作等功能——前几天一直在研究CM23核的位带寻址,始终没有找到文档资料。不过看这些宏定义实现位操作还是很容易的,尤其GPIO接口中还有很丰富的位操作寄存器:
/* 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)再以点灯案例为主,分析应用源码,在main.c中首先调用了“systick_config()”以进行SysTick的初始化,该函数定义在systick.c(位于“..\GD32L233C_START_Demo_Suites\Projects\01_GPIO_Running_LED”),定义了SysTick的周期性中断(周期1ms),并实现了一个毫秒级延时:
#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)main()函数中,接着是GPIO的时钟初始化,使用了函数“void rcu_periph_clock_enable(rcu_periph_enum periph)”(RCU是复位和设置单元模块,)。
//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));
}
从DataSheet中可以看到GPIO是挂载在AHB2总线上的,而上面函数传入的参数是枚举类型,定义了RCU中各个寄存器的偏移和设置位等信息。先看传参类型的枚举定义(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:省略后续部分
}
再来看手册中对于寄存器RCU_AHBEN的说明,其在RCU模块的偏移是0x14,而GPIOA的设置位正是第17bit,所以不难猜到参数宏RCU_REGIDX_BIT的两个参数正是“寄存器和位偏移”。
//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)再来看GPIO的功能初始化,与ST库不同,GD的库没有使用初始化结构体,而是直接传参,本人猜测这里保证传参数不大于4(以实现寄存器传参),所以GPIO的初始化分成两个函数实现“gpio_mode_set()模式初始化”和“gpio_output_options_set()输出选项设置”。
//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)最后看看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。
|