2714|1

155

帖子

1

TA的资源

一粒金砂(高级)

楼主
 

四、Demo工程的分析 [复制链接]

本帖最后由 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。

 

此帖出自GD32 MCU论坛

最新回复

CM23核的位带寻址在stm32总曾经用过,不是很常用资料确实不多 位带寻址相比直接操作寄存器代码更简洁,运行效率更高。避免在多任务,或中断时出现紊乱等。   详情 回复 发表于 2022-2-7 07:23
点赞 关注
 

回复
举报

6802

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

CM23核的位带寻址在stm32总曾经用过,不是很常用资料确实不多

位带寻址相比直接操作寄存器代码更简洁,运行效率更高。避免在多任务,或中断时出现紊乱等。

此帖出自GD32 MCU论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表