大家学习嵌入式的时候,多多学习用KEIL写代码,虽然作为编译器,大家常用vscode等常用工具关联编码,但目前keil仍然是主流工具之一,学习掌握十分必要。
1.再次创建项目
1.1编译器自动生成文件
1.2初始文件
这样下次创建新项目时,只需复制上一个项目,删除自动生成的文件,后根据自我需求进行修改。
重新打开工程后,需要重新配置,重新配置后才会生成文件。
2.再做项目
2.1需求
在第一个项目中,我们点亮了黄灯。接下来点亮蓝灯和绿灯。
2.2准备工作
2.2.1原理图查接线
由原理图可知,LED负极端给一个低电平就可以点亮。得出有效条件:低电平有效
2.2.2有效信息写代码
我们知道,所有外设都基本挂在系统总线上,APB1 APB2.从总线上找GPIO
他们都属于GPIOA同一组时钟。
配置PA1为输出,看寄存器CRL.PA0是后面的四位,那PA1就是前面的四位。
通用推挽输出的配置通常需要将 CNF 设置为 00(推挽输出),而 MODE 根据所需的速度选择,比如 2MHz、10MHz 或 50MHz。例如,对于 50MHz 的速度,MODE 位应设置为 11。因此,对应的 CRL 位需要设置为 0011,即十进制的 3,也就是0x 30
最后几位应该是1101,对应16进制D。所以0xfffd。
蓝灯就会亮起。黄灯之所以不亮是因为如果没有延迟,是观察不到的,它的程序反应速度很快。
绿灯对应引脚PA8。因为8-15的引脚不在低寄存器里,需要看高寄存器。也就是CRH的最低四位
基地址+偏移地址。*(uint32 *)(0x4001 0800+0x04)=0x03
输出低电平,偏移量0x0c.给ODR8一个0.也就是0xfeff.
这样绿灯就会亮起。
看到这里,也就明白了,让三灯同时亮起。
也就是ODR端口数据寄存器配置为0xfefc。
端口配置寄存器CRL,就让低8位为00110011,其他全是0,也就是0x33.
这就是电灯案例的标准库寄存器写法,但是不是有些难看?
寄存器写法的步骤也无非就是开启GPIO时钟,配置端口寄存器输入输出,配置输入输出数据。
先找到引脚的起始地址,再找寄存器对应偏移量,加一起就可以找到寄存器操作地址,还需要强转成32位指针。可不可以不需要这些地址?
我们可以选择优化!
2.3代码优化
不知道大家发现没,我们并没有用到Start里面的.h文件。并没有引入各种额外文件。
如果简化的话,就需要引入额外文件。将一些地址加以宏定义,就会好看许多。
2.3.1 改进1 使用宏定义改写寄存器地址
引入文件
时钟配置也可以
RCC->APB2ENR=4;
注意stm32f10x.h文件里面集成了很多寄存器和引脚的宏定义。只有引用他才能宏替换地址。
优点:这样比地址好看很多,只要记住模块名和寄存器名称,就能配置。
但是目前缺陷也很明显。例如RCC->APB2ENR=4,不仅打开了GPIOA时钟,也将其他的模块关闭了。我们应该在不影响其他模块的前提下,打开GPIOA时钟。
C语言里面讲过位运算,只改想要的位,不改其他位。
2.3.2 使用位运算对某一位进行操作
跟0作位于,一定为0;跟1作位或,一定为1;
置0,与0位于,置一,与1位或.
2.3.3 改进2 使用位运算实现只改变某一位的值。
RCC->APB2ENR=4 4即0x0100,只要让第三位为1,其他保持不变。那就只要和一个第三位为1,其他为0的数作位或即可。也就是与(1<<2)做位或。
RCC->APB2ENR|=(1<<2)即可。
配置PA0为输出,GPIOA->CRL=0x03; 0011,cnF0=00,mode=11,前两位为0,后两位是1,其他位不受影响。这样才能保证PA0为输出。
GPIOA->CRL|=(1<<0);
GPIOA->CRL|=(1<<1);
GPIOA->CRL&=~(1<<3);
GPIOA->CRL&=~(1<<4);
PA0输出低电平,ODR最后一位为0,其他位不改变。
GPIOA->ODR&=~(1<<0);
led1 黄灯就会亮。
这种写代码方法只需要记住模块和寄存器,后续还要记住第几位,需要查手册。
2.3.4 改进3 使用宏定义改变每一位的表示
时钟开启可以改进:RCC->APB2ENR|=(1<<2)
改进后:RCC->APB2ENR|=RCC_APB2ENR_IOPAENR
大家要熟悉这种写法,以后寄存器写法的代码都是这种风格。完全靠宏定义取代了地址的写法,这个主推!!!
3.GPIO整体概述
GPIO引脚就是通用输入输出引脚。存在意义便是用程序控制他们的输入或输出。
3.1 与GPIO相关寄存器
3.2 8种工作模式
3.3推挽输出总结
3.4 开漏输出
3.5 推挽输出和开漏输出的选择
使用推挽:
1.驱动能力需求较高的场合
2.高速信号传输
3.无需共用信号线的场合
使用开漏:
1.多个设备共用信号线
2.不同电压系统之间的接口
3.需要外部上拉电阻来确定逻辑高电平的场合
3.6复用输出模式
复用输出信号来自片上外设(芯片中各种外设模块)
通用开漏,连ODR。
复用开漏,那条线打到片上外设,选哪个模块,配置哪个
复用功能(AF),端口必须配置成复用输出。
看下图,输入模式下,输出完全不导通。
浮空输入,上拉下拉都断开。还想上下拉的话,外部接上拉电阻或下拉电阻。
总结:
模拟输入耗电极小,肖特基触发器关闭。
输入模式就不用分通用还是复用了,都是外部输入来的。
4.GPIO寄存器
每组GPIO端口,都有7个相关寄存器。
配置寄存器和数据寄存器几乎是配置GPIO必用的,必须背会。
上下拉输入都是10 00,那么就用ODR这一位来区分。下拉ODR配0,上拉ODR配1.
0-7引脚用端口配置低寄存器来配,8-15引脚用端口高寄存器来配。
复位值16进制,每一位都是4,0100,对应浮空输入,断掉上下拉电阻的开关,最省电。
输入数据寄存器IDR,16位,一个引脚对应1位。
BS0为1,间接ODR0为1;BR0为1,间接导致OER0为0.如果同时启动BSy和BRy,只有BS起作用。
LCK0直接对ODR0进行锁定。锁定之后在下次端口位复位前不改配置。
5.Keil+VSCode优化开发体验
为了让页面更好看,联想能力更强。就用keil负责与32的联接烧录,VSCode复制写代码。
VSCode我之前在C语言的教程中,安装过,这里不再阐述,关于汉化和C/C++的扩展插件也下载好。
关键插件:Keil Assistant
5.1 下载安装VScode
6.GPIO应用案例:流水灯
需求:在三个LED灯上实现流水灯效果。
注意点:加入延时效果(定时器)。
6.1软件设计
之前我们有过点灯的案例,启动文件和用户文件已经配好,那么可以直接复制使用。将文件的名字和工程名字改过来就可以了。删掉无关文件和目录(编译器自动生成)。
点开工程,完成一系列必要的配置。连上硬件和S-TINK.
之后确定即可。
在VScode里面打开.
导入文件,后写代码
配置GPIOA时钟,再把三个GPIO引脚配成通用推挽输出。记住位或置一,位于置零。
然后我们想想,初始状态得让他们全关灯,然后一个一个亮,前一个亮,后一个就得灭。
我们需要定义延时函数。
我们这个芯片可以用系统滴答定时器。每滴答一次-1.那么多长时间滴答一次?
CPU主频72MHZ,所以一秒钟72M次滴答,1/72M 秒滴答一次。
这样就可以实现流水灯了。
#include "stm32f10x.h"
//定义延时函数
void delay_ms(u16 ms);
void delay_us(u16 us);
void delay_s(u16 s);int main(void)
{//1.时钟配置,开启GPIOA时钟RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;//2.工作模式配置,PA0 PA1 PA8通用推挽输出 CNF=00,MODE=11GPIOA->CRL&=~GPIO_CRL_CNF0;GPIOA->CRL|=GPIO_CRL_MODE0;GPIOA->CRL&=~GPIO_CRL_CNF1;GPIOA->CRL|=GPIO_CRL_MODE1;GPIOA->CRH&=~GPIO_CRH_CNF8;GPIOA->CRH|=GPIO_CRH_MODE8;//3.初始全高电平,都置1GPIOA->ODR|=GPIO_ODR_ODR0;GPIOA->ODR|=GPIO_ODR_ODR1;GPIOA->ODR|=GPIO_ODR_ODR8;//4.在循环中依次点亮,延迟一段时间关闭while(1){//点亮LED1GPIOA->ODR&=~GPIO_ODR_ODR0;//延时半秒delay_ms(500);//关闭LED1GPIOA->ODR|=GPIO_ODR_ODR0;//点亮LED2GPIOA->ODR&=~GPIO_ODR_ODR1;//延时半秒delay_ms(500);//关闭LED2GPIOA->ODR|=GPIO_ODR_ODR1;//点亮LED3GPIOA->ODR&=~GPIO_ODR_ODR8;//延时半秒delay_ms(500);//关闭LED3GPIOA->ODR|=GPIO_ODR_ODR8;}}void delay_us(u16 us)
{//设置系统定时器的初始计数值SysTick->LOAD=72 * us;//配置系统定时器SysTick->CTRL=0x05;//轮询等待计数值变为0,countflag-1while(!(SysTick->CTRL&SysTick_CTRL_COUNTFLAG)){}
//关闭定时器
SysTick->CTRL&=~SysTick_CTRL_ENABLE;
}void delay_s(u16 s)
{while (s--){delay_ms(1000);}
}void delay_ms(u16 ms)
{while (ms--){delay_us(1000);}}