这个专栏主要是用来记录关于立创梁山派开发版的学习笔记的,之所以选择这时候记录这个板子,是因为立创那边最近新开了一个训练营,要用到这个板;而且正好可以对上一个专栏的STM32F4开发做个巩固,立创的这个板子用的是兆易创新的F470ZGT6,也是M4的内核,开发起来与STM32的M4系列其实差不了太多,就是写法上有些改变,这个在记录过程中会进行描述的。
立创的这个梁山派,可以说是散心病狂,同价位很难找到对手了,毕竟梁山派的理念就是“不靠卖板挣钱,以培养优秀工程师为己任”,PCB是四层板还给了沉金,只能说自家有板厂就是豪横,除了开发板本体以外还送支持SW下载和串口的DAP调试器。硬件外设及接口,由于是和核心板的定位,所以相对较少,不过好在IO口都用排针引出来了,可以自己整模块搭建外围电路,而且还给了一块SDRAM,对于想做GUI、小型离线游戏以及超大工程的开发者来说也是可以的。
具体的硬件参数大家自己去他们的主页介绍查看——立创开发板,学生身份貌似还可以免费申请。作为一款学习板来说是很强的。而且,GD32嘛,大家懂得都懂,基本上可以PIN对PIN替换STM32的芯片,这个板上的GD32F470和STM32F429基本上没啥区别,对比一下二者的引脚图,可以说就是一模一样的。
除此之外二者的寄存器的地址什么的都大差不差,也就是说,STM32F429的代码直接下载到GD32F470基本上是可以直接用的,当然,必须是外部的晶振一样的情况下。
除了命名方式上有些许修改,然后就是GD的主频稍高,不过这个不同之处是可以通过修改时钟来解决的,这个在我的嵌入式学习笔记——STM32的时钟树部分有过介绍,当时是将STM32F407给超频到200MHZ了。
有关GD的产品线,大家可以去它的官网进行查询。兆易创新。
拿到一个开发板,要做的第一件事就是找到对应芯片的资料,最准确,最保险的第一手资料就是直接在其官网,输入对应的型号就可以得到了,首先打开兆易创新的官网,然后在产品中心找到MCU,
这里有M3、M4、M23、M33这些选项,GD32F470是M4系列的芯片,如果想要了解ARM Cortex_M的相关内容的话,可以去笔者这篇文章中查看:嵌入式学习笔记——基于Cortex-M的单片机介绍
然后在搜索栏输入GD32F470ZGT6,就会弹出对应器件的简介,可以看出,它最高支持240MHZ, 有1M的Flash,512K的SRAM,这里的Flash就类电脑手机的硬盘;SRAM就类似电脑的内存条,手机的运存。
然后点击进去,就可以看到官方的介绍:GD32F4产品系列紧贴市场高端需求,以高性能、强实时、大容量特性,强化更为广泛的市场领先优势。
GD32F4系列MCU采用Arm® Cortex®-M4内核,处理器主频高达240MHz,可支持算法复杂度更高的嵌入式应用,具备更快速的实时处理能力,并拥有业界领先的大容量存储优势。
GD32F4系列具有丰富的外设资源特性,可提供多达4个USART和4个UART,3个I2C,6个SPI,2个I2S,2个CAN2.0B、1个SDIO接口、1个10/100M以太网控制器,并支持USB2.0 FS和HS通信。还配备了3个采样率高达2.6M SPS的12位高速ADC和2个12位DAC。
关于这些知识,可以大致看一眼,暂时不是很重要,最重要的是拿到这两个东西,一个是数据手册,一个是用户指南,拿到这两个东西,关于GD的硬件设计注意事项,引脚功能,控制器的配置方式,功能都有了,这两个东西对于软件开发者来说就是字典的作用,用到对应的东西了就到这里面去查询即可。
首先还是来看数据手册,前面的简介部分不看了,大家有兴趣的可以自己去看一下,这里我们直接看第2章的第2节Block diagram(框图部分)。
如下图所示,通过这个框图可以快速找到整个芯片的外设、内核、内存、时钟总线之间的关系。首先位于左上角的是ARM的M4内核,其通过AHB总线与其他所有的外设联系起来,其中AHB总线又被分成了几个部分,每个部分都挂接着不同的外设资源,这个图在后面的编程过程中是需要经常使用的。在做初始化的时候,第一步就是打开对应的时钟,此时就需要查询外设对应的时钟线,通过这个图就可以快速获取。
处理系统框图,对于开发者来说第二个常用的就是2.6节的引脚功能介绍,在整个2.6节,可以查询到所有IO的功能,下图的的是引脚复用功能。假设我们要用USART0,此时在这个表中就可以找到,对应的复用是AF7,引脚是PA9和PA10。
关于这个部分,这里给大家截个图,如果看不清的话,大家可以移步去笔者的嵌入式学习笔记——基于Cortex-M的单片机介绍查看。
数据手册里面的其他东西暂时不做赘述,后面遇见一个说一个,其实在编程开发过程中,更多用的是上面的两个,剩下的内容大部分是硬件参数,对应我们绘制原理图和PCB的时候有指导作用,一些硬件的重要参数就需要根据手册的要求进行设置,这个部分留到原理图和PCB篇进行介绍。
除了芯片手册,用的更多的就是这个用户手册了,这个里面详细介绍了芯片的所有外设资源的框图,寄存器,寄存器每一位的详细功能,结构框图,在实际使用过程中,需要开发者根据这个手册对相应的寄存器进行配置,以用来实现对应的功能,绝大多数的配置都是看这个手册就搞定了,除了系统滴答和中断的那个部分是需要查询内核手册外,其他的东西,看这个手册就够了。
除了上面的两个手册,我们还需要拿到两个东西,一个就是官方的固件库,另一个就是开发环境需要的芯片包,下载网址在这:https://www.gd32mcu.com/cn/download下拉选中F4,然后找到应用软件,将GD32F4xx_Firmware_Library_V3.0.4以及线面的GD32F4xx_Add都下载了。
好了,到此,开发所需的MCU的官方资料就都找齐了,接下来就是搭建开发环境(上面这些东西立创官方都是有提供的,这里笔者只是演示一下怎么寻找)
开发环境笔者还是使用的KEIL的MDK,当然这个开发板是支持IAR和VScode的,大家可以根据自己的喜好来,开发环境的搭建就就不赘述了,大家跟着立创的教程就可以搞定,使用的社区版,也没有什么要注意的事项。官方的教学链接:
开发环境
器件包安装
除了上面的这些东西,还需要有有原理图才能进行开发,这时就需要去立创下载了,链接如下:
梁山Pi开发板
百度网盘资料:https://pan.baidu.com/s/1O_2eZLAs25Kt0vKwWS6D8w?pwd=h2jd 提取码:h2jd
这个资源包的01,就是上面介绍的那些文档的来源。这里面的资料是很全的,懒得翻的同学可以直接下载这个。
在硬件资料里面就可以看见原理图了。
至此,资料、开发环境就都齐活了,可以正式开始开发了,关于STM32、GD32这一类的单片机,一般都是有对应的固件库进行编程的,是很方便,但是会让大家接触底层逻辑的变少,有时候会不明所以,所以笔者的建议是,第一遍,如果可以的话,可以尽力尝试自己看手册用寄存器配置,使用完寄存器后再去用库函数、甚至是CubeMX、HAL库都会更清晰。
1.在拿到官方的固件库后,首先先新建一个自己的工程文件夹,尽量不要用中文,然后在工程文件夹内再新建四个文件夹,命名参考如下:
2.打开官方的固件库,在里面找到工程所需的文件,
(1)CMSIS的系统配置文件,system_gd32f4xx.c
路径:GD32F4xx_Firmware_Library_V3.0.4\Firmware\CMSIS\GD\GD32F4xx\Source
(2)CMSIS的启动文件,startup_gd32f450_470.s,这要选择对应的型号
路径:GD32F4xx_Firmware_Library_V3.0.4\Firmware\CMSIS\GD\GD32F4xx\Source\ARM
(3).除了上面这两个源文件,还需要包含对应的头文件,
CMSIS里面的头文件 include文件夹
路径:GD32F4xx_Firmware_Library_V3.0.4\Firmware\CMSIS\GD\GD32F4xx
(4)除了这个头文件的文件夹,还需要额外包含一下下面四个头文件,直接将这四个头文件放到上面的include文件夹即可
路径:GD32F4xx_Firmware_Library_V3.0.4\Firmware\CMSIS
至此CMSIS的文件所需的就搞定了。
(5)然后就需要将外设固件库整体(红框内的src和include文件夹整体)copy到我们自己工程下的Firmware下,这个里面全部是外设的配置相关的内容。
自己的工程目录
(6)然后就是将模板Template文件夹下的中断相关函数和头文件,系统滴答的函数和头文件copy到我们自己工程目录的USER文件夹下。
(7)打开MDK ,新建工程,选中存放工程的文件夹,然后给工程取个名字,注意不能使用中文。
选择芯片型号,直接在索引栏输入GD32F470ZG即可,回车,在下方会有对应的型号出现,如果没有出现下图的现象,就是芯片支持包(PACK包)没有安装好,需要再安装一下。
这是系统帮我们生成工程的这里我们自己手动添加,不需要系统帮忙直接点击OK
最后添加完毕后,编译出现0warings,0errors就可以了。如果有工程搭建异常的,可以直接拿官方的工程哈,详细步骤立创也有文档。新建工程模板
想要我这个工程的也可以私信获取哈。
点亮LED就需要操作GPIO口,GPIO是单片机连接外界电路与内部的桥梁,对GPIO不太了解的同学可以去笔者的嵌入式学习笔记——认识STM32的 GPIO口进行查看,与GD32是一样的。
在了解了GPIO是个啥,有啥功能以后,我们来看一下这款单片机的GPIO口,涉及到芯片的参数,前面提到过,需要查询芯片的数据手册,通过数据手册我们可以查询到,这个单片机有114个GPIO
按照命名规则,对应端口为GPIOA(0-15)、GPIOB(0-15)、GPIOC(0-15)、GPIOD(0-15)、GPIOE(0-15)、GPIOF(0-15)、GPIOG(0-15);加上GPIOH(0-1),齐总GPIOH0和H1是外部高速时钟的输入源
PC14和PC15是外部低速时钟的输入
除此之外,其他的都是可以实现GPIO的各项功能的。
在对GPIO的数量,命名,功能有了大致的认识后,接下来就可以借助GPIO的功能来实现一些功能了,首当其冲的自然是点灯了。为了能正常点灯,第一步就是要在原理图上找到对应的GPIO口
通过观察原理图,我们不仅要知道GPIO口时那个,还需要知道驱动方式,很明显,4个LED都是高电平有效,GPIO输出高电平LED亮,输出低电平LED灭。
找到对应GPIO口管脚后到底要这么配置呢,这个时候就需要用到前面提到过的GD32用户参考手册了。使用到了GPIO就去对应的GPIO章节查看,这里是在第七章,首先是对GPIO的特性、功能做了一个简介。
这些都在前面做过介绍了,重点是接下来的第三节关于GPIO的功能描述:
1.每个通用 I/O 端口都可以通过 32 位控制寄存器(GPIOx_CTL):配置为:
1: GPIO 输入;2.GPIO 输出;3.AF 功能;4.模拟模式。也就是说GPIO一共四种大的模式,这里的模式是需要我们配置对应的地方来控制的,就拿这里的灯来说,就是需要配置为输出模式。
2.当选择 AF 功能时,引脚 AF 输入/输出是通过 AF 功能输出使能 来选择;这里明显是针对AF模式下说的,我们现在只需点亮一个LED不用管这个,后面遇到AF功能的时候有个印象即可。
3.当端口配置为 输出(GPIO 输出或 AFIO 输出)时,可以通过 GPIO 输出模式寄存器(GPIOx_OMODE)配置为推挽或开漏模式。这个点我们需要注意了,这里提到了输出的另外两种子模式,一种是推挽,一种是开漏,关于这两个模式的具体区别大家去上面的GPIO介绍那篇中查看。由于我们的LED既需要控制开,有需要控制关,而且外部电路是没有上拉电阻的,所以我们需要选用既能输出高又能输出低的推挽模式。
4.输出端口的最大速度可以通过 GPIO输出速度寄存器(GPIOx_OSPD) 配置,这里的也是输出有关的,所以也需要进行配置。
5.每个端口可以通过 GPIO 上/下拉寄存器(GPIOx_PUD)配置为浮空(无上拉或下拉),上拉或下拉功能。都说了是每一个端口,自然再配置输出模式的时候也是要配置的,只不过在输出模式下,不需要这个若上下拉的加入,所以直接配置为浮空。
根据上面的描述,已经大致知道了配置流程,接下来就要解决怎么配的问题,根据上面的描述,明显是有一个个的寄存器供我们来写入数据进而控制对应功能的。那么具体怎么控制呢,接着往下看用户手册。
在手册第7.4节,专门对各个寄存器进行了介绍,一眼看去是不是上面提到的寄存器在这里都有,接下来我们一一查看就可以了。需要注意一下,这里有个GPIOx的地址,这个地址就是寄存器在单片机内存的位置了,寄存器编程操作的也就是这个地址上的东西,只不过在固件库中已经帮封装好了,就不太需要我们去折腾基地址,偏移地址这些了,当然有想法的同学也可以折腾一下,也很好理解的。
第一个寄存器就是端口控制寄存器,前面的功能描述中提到过,它是用来控制GPIO的具体模式的,一共有输入,输出,AF、模拟四种大功能;我们都知道二进制只有0和1两种状态,这里有四种状态,猜一下,一个GPIO管脚需要用到几位寄存器来进行控制。
答案很明显,四种模式嘛,那肯定是2位二进制啊,一共有“00 01 10 11”四种模式,话说到这,前面提到过一组GPIO端口有0-15共16个管脚,那么这一组是不是刚刚好就是16*2=32位二进制控制呢,而我们的GPIOx_CTL刚好就是32位。
上面只是我们的推测,接下来了来看看手册怎么描述的,如下图,每个GPIO管脚都对应两位,一共四种模式,其中CTLx的x与GPIOA x对应。
这是手册中给我们的信息,那么怎么跟编程关联起来呢,我们打开工程,找到Firmware,然后找到里面的gd32f4xx_gpio.c,双击打开,然后右键跳转,打开头文件。
下拉,就可以看见一些熟悉的东西,GPIO_CTL(gpiox) ,这玩意是不是和上面的寄存器名称大差不差的,可以发现,这就是一个宏定义,就拿GPIOA来举例吧,GPIOA的基地址是:0x4002 0000 GPIOA_CTL的偏移地址是0x00,也就是说,GPIOA_CTL的地址是0X4002 0000;
注意宏定义中的意义,GPIO_CTL后面是需要传参的,而这个参数也就是上方的GPIOA,而GPIOA又是一个宏定义,有一个GPIO_BASE+0x00000000U;
那我们继续跳转,来到了gd32f4xx.h,可以看见GPIO_BASE还是一个宏定义,又指向了AHB1_BUS_BASE,到此终于知道了值是0x4002 0000U,然后一路反向加回来,0x4002 0000U+0x0000 000U+0x0000 0000U+0x00U,最终等于0X4002 0000也就是GPIOA_CTL的地址。
好了,绕了一大圈,终于知道了GPIO_CTL在编程时的写法。来实战一下,我们需要初始化PE3、PD7、PG3、PE5为输出模式,具体的写法应该是:
需要提醒一下,在同时操作多个二进制位的时候,我们最好是先清零,然后再赋值,这样才能避免寄存器的原有值造成影响。
补充一点关于位操作的知识,由于操作二进制位的手要注意避免影响到其他的位所以:
位操作过程中,写0的语法是 &=~(1<<对应的位)
写1的语法是|=(1<<对应的位)
举个栗子,
PE3 ,一次要写两位,最好是先清零,清零方式:GPIOE3的控制位是6-7位,于是将0x03<<6为后得到:1100 0000,取反0011 1111然后位于与操作,0-5位与1相与保持原来的值不变,而6-7与0相与直接被置零。
GPIO_CTL(GPIOE) &= ~(0x03<<6);
清零后还需要写入对应模式的值,输出模式,对应的值应该是01,也就是说对第六位写入1就可以了,于是按照如下操作即可。
GPIO_CTL(GPIOE) |= (0x01<<6);
依次类推,可以将其他四个GPIO也初始化为输出模式,代码如下:
//选择对应管脚是输入模式还是输出模式,2位控制一个管脚;输出模式
//PE3
GPIO_CTL(GPIOE) &= ~(0x03<<6);
GPIO_CTL(GPIOE) |= (0x01<<6);
//PD7
GPIO_CTL(GPIOD) &= ~(0x03<<14);
GPIO_CTL(GPIOD) |= (0x01<<14);
//PG3
GPIO_CTL(GPIOG) &= ~(0x03<<6);
GPIO_CTL(GPIOG) |= (0x01<<6);
//PA5
GPIO_CTL(GPIOA) &= ~(0x03<<10);
GPIO_CTL(GPIOA) |= (0x01<<10);
然后是端口输出模式寄存器,前面也提到过,这个寄存器主要是用来选择推挽输出还是开漏输出的,这里只有推挽和开漏两个模式,对应一个二进制位的0和1是不是刚刚合适。
果然,手册中也是一位二进制位控制一个GPIO的管脚,那么在代码中如何控制呢,根据刚刚的经验,不难找到其具体的写法。
然后就是具体的配置了,由于这里只是用到了一位二进制位,所以可以不清零,直接写入对应的值即可。
配置代码:
//设置输出模式为推挽输出 1位控制一个管脚;
GPIO_OMODE(GPIOE) &=~(0x01<<3);//设置PE3为推挽输出
GPIO_OMODE(GPIOD) &=~(0x01<<7);//设置PD7为推挽输出
GPIO_OMODE(GPIOG) &=~(0x01<<3);//设置PG3为推挽输出
GPIO_OMODE(GPIOA) &=~(0x01<<5);//设置PA5为推挽输出
剩下的就都可以类推了,接下来就是输出速度控制,这个输出输出速度实际上是和功耗挂钩的,输出速度越快,功耗越大。这里我们就点个灯,所以可以直接配置一个最慢的速度,四度一共有四个模式,所以需要两位二进制位来控制,因此操作时也需要先清零后赋值。
也就是说这里写入00就可以了。具体的代码:
//设置输出速度,2位控制一个管脚;
GPIO_OSPD(GPIOE) &= ~(0x03<<6);//PE3速度等级为0,点个灯而已
GPIO_OSPD(GPIOD) &= ~(0x03<<14);//PD7速度等级为0
GPIO_OSPD(GPIOG) &= ~(0x03<<6);//PG3速度等级为0
GPIO_OSPD(GPIOA) &= ~(0x03<<10);//PA5速度等级为0
再然后就是端口上拉下拉寄存器了,在输出模式下,不需要这个弱上下拉,所以配置为浮空模式,也就是写入00;如下图所示,可以发现,这个寄存器和前面的端口控制寄存器一样,也是每两位控制一个GPIO管脚的。
在代码中的写法如下:
GPIO_PUD(gpiox)
具体的配置代码如下:
//设置上下拉,2位控制一个管脚;输出,不需要上下拉(无上下拉模式,浮空)
GPIO_PUD(GPIOE) &= ~(0x03<<6);//PE3为浮空模式00
GPIO_PUD(GPIOD) &= ~(0x03<<14);//PD7为浮空模式00
GPIO_PUD(GPIOG) &= ~(0x03<<6);//PG3为浮空模式00
GPIO_PUD(GPIOA) &= ~(0x03<<10);//PA5为浮空模式00
再往下就是使用端口输出控制器寄存器来实现具体的电平输出了,写1输出高,写0输出低,至于其他的那些寄存器,暂时用不上,后面用到了再一一介绍。
输出控制寄存器的具体写法如下:
GPIO_OCTL(gpiox)
根据原理图可以知道,LED灯需要输出高电平才可以点亮。于是配置代码如下:
//设置初始状态,输出控制寄存器,移位控制一个管脚
GPIO_OCTL(GPIOE) |=(0x01<<3);//设置PE3为高,开启LED1
GPIO_OCTL(GPIOD) |=(0x01<<7);//设置PD7为高,开启LED2
GPIO_OCTL(GPIOG) |=(0x01<<3);//设置PG3为高,开启LED3
GPIO_OCTL(GPIOA) |=(0x01<<5);//设置PA5为高,开启LED4
到这里,关于GPIO口的配置就结束了,那么是不是把这些代码丢到程序中就可以点亮小灯了呢,答案是否,为什么呢;原因就是我们上面的所有步骤只是相当于打开了分开关,总闸还没开呢,那么总闸怎么开呢?
上面已经将GPIO的各个配置都做好了,但是缺少了一步,开启对应GPIO的时钟,而且这个时钟的开启必须是在GPIO的功能配置之前。必须先打开总开关,然后再开启分开关。关于这个总开关,其实在上面的介绍中提到过,就是框图的那儿,当时是说,所有的外设都是通过时钟总线与ARM内核通信和控制的,所以我们需要在下图中找到GPIO的位置,然后再去用户手册开启对应的时钟。下图中红框的位置就是GPIO,它挂接在AHB1上。
找到了对应的时钟总线后,就需要编程开启了,那么怎么开启呢,和上面配置GPIO一样,面对配置的问题,直接找用户编程手册,在编程手册第4章,专门介绍了时钟单元,这里可以直奔主题,在4.3节RCU寄存器部分直接下拉,找到**4.3.10. AHB1 使能寄存器(RCU_AHB1EN)**注意是AHB1使能寄存器。可以看到该寄存器的后面8位每一位对应着一个GPIO的端口,写入1表示使能,也就是说我们要对GPIOA、D、E、G对应的控制位写入1。
纳闷这个寄存器的具体写法是什么样呢,思路与找GPIO的寄存器写法一致,我们找到对应的头文件,gd32f4xx_RCU.h。进入后下来在57行就有着这个寄存器在代码中的写法。
RCU_AHB1EN |=(1<<0);//开启A端口的时钟
RCU_AHB1EN |=(1<<3);//开启D端口的时钟
RCU_AHB1EN |=(1<<4);//开启E端口的时钟
RCU_AHB1EN |=(1<<6);//开启G端口的时钟
至此,整个点灯所需要的寄存器就配置完毕了,接下来就是编写自己所需的功能函数以及头文件了。
#include "led.h"
/************************************************
函数名称 : Led_Init
功 能 : led灯gpio引脚配置
参 数 : 无
返 回 值 : 无
作 者 : LC
备 注 :
LED1------PE3-------推挽输出------高电平有效
LED2------PD7-------推挽输出------高电平有效
LED3------PG3-------推挽输出------高电平有效
LED4------PA5-------推挽输出------高电平有效
*************************************************/
void Led_Init(void)
{
//开启端口A,D,E,G的时钟 1位控制一个端口;
RCU_AHB1EN |=(1<<0);//开启A端口的时钟
// RCU_AHB1EN |=(1<<3);//开启D端口的时钟
// RCU_AHB1EN |=(1<<4);//开启E端口的时钟
RCU_AHB1EN |=(0X3<<3);//开启DE端口的时钟
RCU_AHB1EN |=(1<<6);//开启G端口的时钟
//选择对应管脚是输入模式还是输出模式,2位控制一个管脚;输出模式
//PE3
GPIO_CTL(GPIOE) &= ~(0x03<<6);
GPIO_CTL(GPIOE) |= (0x01<<6);
//PD7
GPIO_CTL(GPIOD) &= ~(0x03<<14);
GPIO_CTL(GPIOD) |= (0x01<<14);
//PG3
GPIO_CTL(GPIOG) &= ~(0x03<<6);
GPIO_CTL(GPIOG) |= (0x01<<6);
//PA5
GPIO_CTL(GPIOA) &= ~(0x03<<10);
GPIO_CTL(GPIOA) |= (0x01<<10);
//设置输出模式为推挽输出 1位控制一个管脚;
GPIO_OMODE(GPIOE) &=~(0x01<<3);//设置PE3为推挽输出
GPIO_OMODE(GPIOD) &=~(0x01<<7);//设置PD7为推挽输出
GPIO_OMODE(GPIOG) &=~(0x01<<3);//设置PG3为推挽输出
GPIO_OMODE(GPIOA) &=~(0x01<<5);//设置PA5为推挽输出
//设置输出速度,2位控制一个管脚;
GPIO_OSPD(GPIOE) &= ~(0x03<<6);//PE3速度等级为0,点个灯而已
GPIO_OSPD(GPIOD) &= ~(0x03<<14);//PD7速度等级为0
GPIO_OSPD(GPIOG) &= ~(0x03<<6);//PG3速度等级为0
GPIO_OSPD(GPIOA) &= ~(0x03<<10);//PA5速度等级为0
//设置上下拉,2位控制一个管脚;输出,不需要上下拉(无上下拉模式,浮空)
GPIO_PUD(GPIOE) &= ~(0x03<<6);//PE3为浮空模式00
GPIO_PUD(GPIOD) &= ~(0x03<<14);//PD7为浮空模式00
GPIO_PUD(GPIOG) &= ~(0x03<<6);//PG3为浮空模式00
GPIO_PUD(GPIOA) &= ~(0x03<<10);//PA5为浮空模式00
//设置初始状态,输出控制寄存器,移位控制一个管脚
GPIO_OCTL(GPIOE) &=~(0x01<<3);//设置PE3为低,关闭LED1
GPIO_OCTL(GPIOD) &=~(0x01<<7);//设置PD7为低,关闭LED2
GPIO_OCTL(GPIOG) &=~(0x01<<3);//设置PG3为低,关闭LED3
GPIO_OCTL(GPIOA) &=~(0x01<<5);//设置PA5为低,关闭LED4
}
/***********************************************
*函数名 :Led_Flow
*函数功能 :实现一个简单的流水灯(非阻塞)
*函数参数 :u8 dir方向 ,u8 speed(流水速度)
*函数返回值:void
*函数描述 :
dir 0: LED1-LED2-LED3-LED4
1: LED4-LED3-LED2-LED1
speed: 1-9 数字越大,流水越慢
*********************************************/
void Led_Flow(uint8_t dir,uint8_t speed)
{
static uint8_t n=1;
static uint32_t cnt=0;
switch(n)
{
case 1: LED_1_ON;break;
case 2: LED_2_ON;break;
case 3: LED_3_ON;break;
case 4: LED_4_ON;break;
default: break;
}
cnt++;
//延时切换灯
if(cnt> 500000 * speed) //不精准延时
{
switch(n)
{
case 1: LED_1_OFF;break;
case 2: LED_2_OFF;break;
case 3: LED_3_OFF;break;
case 4: LED_4_OFF;break;
default: break;
}
if(dir==0)
{
n++; //往左切灯
if(n>4)
{
n=1;
}
}
else if(dir==1)
{
n--;
if(n==0)
{
n=4;
}
}
cnt=0;
}
}
//以下为头文件
#ifndef _LED_H
#define _LED_H
#include "gd32f4xx.h"
#include "systick.h"
#define LED_1_ON GPIO_OCTL(GPIOE) |=(0x01<<3)//置1拉高对应管脚,LED1灯亮 PE3
#define LED_1_OFF GPIO_OCTL(GPIOE) &=~(0x01<<3)//置0拉低对应管脚,LED1灯灭
#define LED_1_TURN GPIO_OCTL(GPIOE) ^= (0x01<<3)//异或操作实现翻转
#define LED_2_ON GPIO_OCTL(GPIOD) |=(0x01<<7)//置1拉高对应管脚,LED2灯亮 PD7
#define LED_2_OFF GPIO_OCTL(GPIOD) &=~(0x01<<7)//置0拉低对应管脚,LED2灯灭
#define LED_2_TURN GPIO_OCTL(GPIOD) ^= (0x01<<7)//异或操作实现翻转
#define LED_3_ON GPIO_OCTL(GPIOG) |=(0x01<<3)//置1拉高对应管脚,LED3灯亮 PG3
#define LED_3_OFF GPIO_OCTL(GPIOG) &=~(0x01<<3)//置0拉低对应管脚,LED3灯灭
#define LED_3_TURN GPIO_OCTL(GPIOG) ^= (0x01<<3)//异或操作实现翻转
#define LED_4_ON GPIO_OCTL(GPIOA) |=(0x01<<5)//置1拉高对应管脚,LED4灯亮 PA5
#define LED_4_OFF GPIO_OCTL(GPIOA) &=~(0x01<<5)//置0拉低对应管脚,LED4灯灭
#define LED_4_TURN GPIO_OCTL(GPIOA) ^= (0x01<<5)//异或操作实现翻转
void Led_Init(void);
void Led_Flow(uint8_t dir,uint8_t speed);
#endif /* BSP_LED_H */
然后在主函数中初始化、调用宏定义或者流水灯功能函数就可以实现功能了。