# 废话不多说,先总结流程
1. 先下载官方的库文件,这一步只是为了拿到官方写好的启动文件,不然手写启动文件太麻烦了
2. 有了启动文件就可以直接写main函数了,所以我们第二部创建Keil的工程
3. 然后就是查手册并写代码,烧入程序运行查看结果
## 下载官方库文件
在ST官网的软件菜单中找到STM32标准外设软件库:
这部分不懂可以看:
STM32标准外设库(标准库)官网下载方法,附带2021最新标准固件库下载链接_stm32标准库官网-CSDN博客
## 在Keil中新建工程
这部分也就要注意芯片型号选对,没有芯片信号就是没装pack包,可以看这个:
新建完成工程之后,应该是啥文件都没有的,这个时候需要搞两个文件:
1. 启动文件:这个来自于刚才下载的压缩包里,要注意的是,型号要选对,都是STM32F103,也有大中小容量的分别,导致启动文件不一样,这个不懂可以看:
STM32的启动文件的选择(由容量大小决定)_keil 不同容量单片机怎么选启动文件-CSDN博客
STM32不同型号的芯片对应的启动文件如何选择_startup_stm32f10x_cl-CSDN博客
2. 自己新建一个main.c文件,用来待会儿写代码
补充:启动文件是单片机上电后第一个运行的文件,并且是用汇编写的,里面主要是对堆栈、中断向量表之类的很底层的东西进行初始化,最后进入到C语言的环境,也就是启动文件运行完成了才能跑C语言代码。初学阶段不太需要了解,但是工程大了变量多了,堆栈不够用了是需要修改启动文件的,所以以后还是要学的(启动文件代码也不多),有关于启动文件的分析,可以看下这个:
15. 启动文件详解 — [野火]STM32库开发实战指南——基于野火MINI开发板 文档
我使用的C8T6是64kb的中容量版本,所以启动文件选取hd.s
然后写下main函数并编译:(有报错)
这个是由于汇编文件中调用了SystemInit函数,但是没有定义:
解决办法:写个空函数,让他能够调用不报错就行(严谨的讲,一些操作并没有完成)
到这里反正编译过了,就不用管了。
## 最后编写代码
### 知识储备
核心:(这个是最重点的内容,虽然我说的不准确,但是也请你记下来)
1. 对单片机编程,就是在操作寄存器,无非就是对寄存器进行 读取 和 写入 。(告诉你你要干什么)
2. 寄存器不只是存储器,是连接了硬件的,对寄存器的 读取 和 写入 就会间接影响硬件的工作状态。(解释为什么要这么干)
3. 寄存器在冯诺依曼的架构中,不过就是内存中间的某一部分。所以想要访问寄存器,就理解为访问内存就好了,所以在C语言里面,就是用指针访问。(告诉你该怎么干)
而对于本次的点灯操作,就是要控制一个硬件(GPIO的引脚,就是芯片引脚),改变他的工作状态(修改输出模式并修改成输出低电平)。
但是这里需要注意的是,在STM32的结构里,GPIO是一个挂载在APB2总线上面的外设,所以我们要对GPIO进行操作,先要把APB2总线打开,才能通过 CPU --》 APB2总线 --》 GPIO 这条通路进行操作。所以我们可以得到以下的操作步骤:
1. 打开APB2上面的GPIO通路的时钟(让通路的中间部分打通)
2. 配置GPIO的模式,初始化
3. 修改GPIO的引脚输出电压
这里再补充一点我的心得:
对于任何的外设,不论是哪一个,你只需要学习两个或者三个操作即可:
1. 对这个外设初始化
2. 对这个外设进行 读取 或者 写入 或者 读取和写入 这些操作就够了
比如,你想学UART,不就是学会了初始化、发送数据、接收数据三个操作就够了吗?(这里只是说驱动层面,通讯协议之类的是上层了,那些就不是单片机自身的知识点了)
再比如,你想学ADC不就是,初始化 + 读取ADC的测量值 两个操作就够了吗?
最后看这个总结,接下来就可以写代码了:
寄存器修改的代码怎么写:
*(unsigned int * )( 基础地址 + 偏移地址 ) = 你想修改的值;
稍微解释一下:
首先,STM32把内存像是切蛋糕一样先切出了几个大块,每一块都是一个的外设的所有寄存器。这就是为什么有 基础地址 ,就好像先告诉你你要找的人在哪条街道。
其次,在一块里面,也有好多寄存器,对吧,然后你要找到某一寄存器,就要通过 偏移地址 。就好像是,找到街道之后告诉你,你要找到人在这条街道的第几个房子里。
最后,地址是找到了,我们要对地址下的值进行修改所以,我们会写出 ( * 地址 = 值 ) 这样子的代码,但是这个样子会报错。因为我们这里的 地址 在编译器看来,是一个常数,你怎么可以对常数进行解引用呢?所以我们还用一个强制类型转换( unsigned int * ),这里unsigned是必须有的,因为寄存器地址就是0开始的,没有负数。int是指明这个地址是32位的,因为STM32就是32位的。
好的,有了以上的这些知识储备,就可以知道该怎么写代码了。
(二次修改添加的内容)
如何读写单个寄存器的值:
(假设对一个8位寄存器进行操作,每次都是操作第5位)
情况1:不修改其他寄存器的值,把某一位置1:
用按位或来置1:
* (unsigned char *)(地址) |= (1 << 5) ;
情况2:某一位置清0
用按位与来清0:
* (unsigned char *)(地址) &= ~(1 << 5) ; // 注意要取反
情况3:读取某一位置的值
用按位与配合if语句:
if ( * (unsigned char *)(地址) & (1 << 5) ) {
// 运行到这里说明这个位是1
} else {
// 运行到这里说明这个位是0
}
### 实操
0. 首先声明,我的开发板的LED是接到GPIO_C 13端口的,并且是低电平驱动。
然后手册是:STM32F103的中文手册,寄存器手册。
1. 先开启APB2的GPIOC的时钟:
1.1 查询基地址(这个要在手册的寄存器映射部分找!)
1.2 然后找偏移地址:
1.3 分析一下要写什么数据进去:
(一般EN就是使能,就是写1打开,写0关闭;这个是我的经验,所以就不用看下面的手册对于值的解释了,下面的模式选择再带你们看)
因为手册上写的是第4位是GPIO-C的时钟,所以给1的值是1 0000b = 0x10 (二进制转16进制)
1.4 代码:(数字内的空格记得删掉,我这么些是为了易读)
*(unsigned int *) (0x4002 1000 + 0x18) |= 0x10;
2. 然后如法炮制,写GPIO C的模式选择。
2.1 GPIO C的基础地址:(在寄存器映射部分找)
2.2 模式寄存器的偏移地址:(这个在GPIO外设部分找)
2.3 最后是看下这些寄存器要给什么值:
(这张图片和上一张图片在手册里其实是连着的,所有的寄存器手册都是这个结构)
查手册可以知道我们要把GPIO设置成输出模式,且最好是推挽输出模式:则寄存器的值设置为:第23位是0,第22位是0,第21位是1,第20位是0,则对应的16进制代码是:0x0020 0000
2.4 最后写代码:(数字内的空格记得删掉,我这么些是为了易读)
*(unsigned int *)(0x4001 1000 + 0x04) = 0x0020 0000;
3. 最后是让端口输出低电平
3.1 因为我们还是操作GPIO—C端口,所以基地址肯定没变,不用查了
3.2 然后查询偏移地址:
3.3 最后查要写入的值,可以看到上图里面已经有了,就是写1是高电平,写0是低电平。
然后这里补充一点点,仔细看手册,开头是GPIOx_ORD这里x是小写的!意味着x是A或者是B或者是C...那么我们怎么区分呢?或者说,为啥我这里操作就是在改GPIO_C而不是在改GPIO_A呢?因为基地址我们写的就是GPIO_C了,加上偏移地址,肯定是GPIO_C的输出寄存器。如果我们基地址选择的是GPIO_D,然后加上这里的偏移地址,那不就是操作GPIO_D吗。所以由此我们还可以看出,GPIO_A和GPIO_B和...的内部寄存器结构是一样的,就好像是两排完全一样的学生宿舍的结构。
第二个要补充的是,请注意偏移地址之下,还有一个默认值!这个东西也挺重要的,学习过STM32时钟树相关知识的同学肯定知道时钟对于芯片的重要性,肯定会有人问,我芯片时钟都没配置芯片怎么能跑的起来的,就是因为默认值给我们的芯片接上了内部的时钟,所以虽然我们没有配置时钟,但是芯片的默认值已经帮我们配置了一个时钟。(但是速率不怎么样,实际使用时我们还是要手动配置到外部晶振的)而在这里,由于默认值就是0,也就是输出低电平,所以其实这一行代码我们不写,灯也还是能被点亮的。
3.4 所以代码是:
*(unsigned int *)(0x4001 1000 + 0x0C) = 0x0000;
4 代码写完了,编译并且烧录,看结果
4.1 完整代码预览:
void SystemInit(void); // 汇编文件里调用的函数的声明
void SystemInit(void){} // 函数定义
#define RCC_BASE 0x40021000 // 手册查到的基础地址
#define GPIOC_BASE 0x40011000 // 手册查到的基础地质
int main()
{
// APB2外设时钟寄存器配置,开启GPIO-C的时钟
*(unsigned int *)(0x40021000 + 0x18) |= 0x10;
// 配置引脚模式为输出模式
*(unsigned int *)(0x40011000 + 0x04) |= 0x00200000;
// GPIO-C 13引脚的输出电平置低
*(unsigned int *)(0x40011000 + 0x0C) = 0x0;
while(1); // 防止程序结束
}
// by likeszm
4.2 编译和下载程序:
你要先配置好ST-Link,这个你可以看:
STM32学习笔记3:KEIL5中使用ST-Link烧录调试STM32芯片的步骤_keil5使用stlink-CSDN博客
下载完成之后应该会在输出窗口显示类似这些东西:
4.3 现象:程序下载完成之后也不一定会立马有现象,可以按一下重启键,让芯片从头开始跑代码
最后提醒一句,本文所有的重点思想都是块引用内的文字,希望各位细细体会。
就是这种文本框内的文字啦。
by 李科笙不写作业
完结撒花