GD32F350固件库解析(一)
前言
从事嵌入式的学习和工作有3年的时间了,中间一直是用到什么学什么,慢慢探索,最近由于感到进步很慢,而且学的很不系统,很多东西都是一知半解,移植成了,能用就不管了。于是我决定认真的对库函数进行一次解析,一可以充实自己的生活,二能巩固自己的专业技能,不白白浪费时光。
为什么不是stm32
按照道理来讲,我应该写stm32相关的解析才对,但是stm32的库函数解析的文章太多了,我想来研究一次其他芯片,正好公司用gd32用的比较多。
GPIO
首先,做这一行的对这个东西都比较熟悉了,在认真看库函数之前,我对gpio的理解有一下几点:
- 工作模式
模式 | 备注 |
---|---|
推挽输出 | 51单片机、LED很多都是这个模式,一般单个引脚能推出20mA电流 |
开漏输出 | 单片机本身没有推挽模式下的电流输出能力,其类似于MOS的控制端,控制外部电路的电平高低,需要外接上拉电阻。 |
浮空输入 | 按键、外部中断、外部电平状态要求的模式 |
模拟输入 | ADC要求的模式 |
基本输入输出模式就上边4种,但是有的单片机可以设置内部的弱上拉和弱下拉电阻。具体应用环境还不太清楚。
模式 | 备注 |
---|---|
复用推挽 | 串口USART等外设需要用到的引脚模式,即其控制权在外设模块手中。 |
复用开漏 | II2等接口要求的输出模式,需外接上拉电阻,控制权也在相应的外设模块手中。 |
- 速率问题
不同芯片gpio速率不同,GD32F350有50MHz、10MHz、2MHz(复位值)三种模式。
相应的,速率越快,功耗越大,在低功耗应用的需要配置为低速率。 - GD32有端口锁定功能,能够锁定端口的配置,防止意外修改寄存器。
- GD32F350有输出翻转寄存器,这个寄存器功能很好用啊,不知道stm32后来的有没有这个寄存器,以前写程序都是:
// 这是以前用的做法
gpio_state = gpio_bit_read(GPIOA,GPIO_PIN_1);
if(gpio_state){
gpio_bit_reset(GPIOA,GPIO_PIN_1);
}else{
gpio_bit_set(GPIOA,GPIO_PIN_1);
}
// 这是以前用的做法
gpio_state = gpio_bit_read(GPIOA,GPIO_PIN_1);
if(gpio_state){
GPIOA->BSRR = 1<<32;
}else{
GPIOA->BSRR = 1<<0;
}
或者上边的做法太笨了,读一次,判断一次,执行一次,而且语句多,还带有分支,更好的做法还可以这么做:
// 这是更好的做法
GPIOA->ODR ^= GPIO_PIN_1;
上边用一条语句就操作了,在GD32中还可以这么做:
这样做,没有异或操作,会快一些1?
// 这是操作翻转寄存器的做法
//GPIOA->GPIO_TG = GPIO_PIN_15;
GPIO_TG(GPIOA) = GPIO_PIN_15; //GD32F350库改变了写法
GD32 GPIO例程
可以看到gd32的库中,只有这两个例程:
一个是键盘的轮询。
int main(void)
{
systick_config();
gd_eval_led_init(LED1);
gd_eval_key_init(KEY_TAMPER,KEY_MODE_GPIO);
while(1){
/* check whether the button is pressed */
if(RESET == gd_eval_key_state_get(KEY_TAMPER)){
delay_1ms(100);
/* check whether the button is pressed */
if(RESET == gd_eval_key_state_get(KEY_TAMPER)){
gd_eval_led_toggle(LED1);
}
}
}
}
一个是走马灯。
int main(void)
{
systick_config();
gd_eval_led_init(LED1);
gd_eval_led_init(LED2);
gd_eval_led_init(LED3);
gd_eval_led_init(LED4);
while(1){
/* turn on led1, turn off led4*/
gd_eval_led_on(LED1);
gd_eval_led_off(LED4);
delay_1ms(1000);
/* turn on led2, turn off led1*/
gd_eval_led_on(LED2);
gd_eval_led_off(LED1);
delay_1ms(1000);
/* turn on led3, turn off led2*/
gd_eval_led_on(LED3);
gd_eval_led_off(LED2);
delay_1ms(1000);
/* turn on led4, turn off led3*/
gd_eval_led_on(LED4);
gd_eval_led_off(LED3);
delay_1ms(1000);
}
}
可以看到,例程中操作灯和读端口状态的时候用的都是BSP中封装好的函数,再以后的开发过程中,我们也要注意避免在mian中直接调用库函数,破坏移植性。
内行人看这个例程是肯定有大问题的,阻塞,长按检测等,但是如果要实现那些功能,就需要timer等外设的使用,所以这里就不纠结啦。
EXTI
GPIO是最最基础的。下面说和GPIO直接关联的外部中断。这个常常被用作外部按键的检测。关于按键长按双击检测的文章,推荐一篇文章,写的很不错,值得借鉴。
配置外部中断主要分为以下几个步骤:
- 开时钟
- 配置端口为输入模式
- 配置nvic相应中断使能
- 配置exti线路映射
- exti初始化
- 清除中断标志位
在外部中断中不适合做纯延时去抖,会造成很大的阻塞
void gd_eval_key_init(key_typedef_enum keynum, keymode_typedef_enum keymode)
{
/* enable the key clock */
rcu_periph_clock_enable(KEY_CLK[keynum]);
rcu_periph_clock_enable(RCU_CFGCMP);
/* configure button pin as input */
gpio_mode_set(KEY_PORT[keynum], GPIO_MODE_INPUT, GPIO_PUPD_NONE, KEY_PIN[keynum]);
if (keymode == KEY_MODE_EXTI) {
/* enable and set key EXTI interrupt to the lowest priority */
nvic_irq_enable(KEY_IRQn[keynum], 2U, 0U);
/* connect key EXTI line to key GPIO pin */
syscfg_exti_line_config(KEY_PORT_SOURCE[keynum], KEY_PIN_SOURCE[keynum]);
/* configure key EXTI line */
exti_init(KEY_EXTI_LINE[keynum], EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(KEY_EXTI_LINE[keynum]);
}
}
这个以后查一下资料再来修正。GD32手册上说是单周期翻转,以前是几个周期? ↩︎