1 新建工程模板——寄存器版
1.1 头文件中<>与""的区别
#include <ste3210x.h> // 头文件不在当前目录下,在软件的安装(根)目录下
#include "stm32f10x.h" // 头文件在当前目录下,如果找不到再去软件的安装(根)目录下找
1.2 .axf文件与.hex文件
- .axf文件是可以通过编译器DownLoad(F8)下载的可执行文件。
- .hex文件是可以通过串口下载的可执行文件。
1.3 新建工程
-
一.新建工程:打开KEIL5,点击New μVision Project -> 创建一个.uvprojx文件(名称根据自己喜好命名)。
-
二.选择CPU型号:在Select Device for Target ‘Target 1’…中,点击STM32F1 Series,再点击STM32F103VE。
如果这里没有出现你想要的 CPU 型号,或者一个型号都没有,说明你的KEIL5没有添加device库,KEIL5 不像 KEIL4 那样自带了很多 MCU 的型号, KEIL5 需要自己添加。
-
三.添加文件:添加 startup_stm32f10x_hd.s文件(启动文件),创建 main.c文件(定义一个SystemInit函数),创建 stm32f10x.h文件,并导入 main.c 中。
-
四.新建文件:手动创建stm32f10x.h文件,用于存放寄存器映射的代码。(注:这个stm32f10x.h文件并非官方固件库中的文件)
-
五.配置魔术棒选项卡:
-
5.1 Target 中选中微库“Use MicroLib”,为的是在日后编写串口驱动的时候可以使用 printf 函数。
-
5.2 Target 中ARM Complier 选择 Use of complier version 5。
-
5.3 Output 选项卡中把输出文件夹定位到我们工程目录下的 output 文件夹,如果想在编译的过程中生成 hex 文件,那么那 Create HEX File 选项勾上。
-
5.4 在 Listing 选项卡中把输出文件夹定位到我们工程目录下的 Listing 文件夹。
-
-
六.下载器配置:在仿真器连接好电脑和开发板且开发板供电正常的情况下,打开编译软件 KEIL,在魔术棒选项卡里面选择仿真器的型号。
-
6.1 Debug 选项配置:
-
6.2 Utilities 选项配置:
-
6.3 Utilities 选项配置:选择目标板,具体选择多大的 FLASH 要根据板子上的芯片型号决定。 (F103-“指南者”选 512K。)
如果SWJ勾选不了,点击 Flash Download 旁边的Pack,将勾选上的Enable去掉。
这里面有个小技巧就是把 Reset and Run 也勾选上,这样程序下载完之后就会自动运行,否则需要手动复位。擦除的 FLASH 大小选择 Sectors 即可,不要选择 Full Chip,不然下载会比较慢。
-
-
七.下载程序:如果前面步骤都成功了,接下来就可以把编译好的程序下载到开发板上运行。下载程序不需要其他额外的软件,直接点击 KEIL 中的 LOAD 按钮即可。
2 使用寄存器点亮LED灯
在写这一节程序的时候,只需要一份资料:《STM32F10X-中文参考手册》。
2.1 如何点亮STM32的LED灯
对于51而言,想要点亮一盏灯:
// 假设LED链接到芯片的P0.0口,MCU给低电平点亮
#include <reg51.h>
sbit LED = P0^0;
void main(void)
{
LED = 0; //位操作
P0 = 0XFE; // 总线操作
}
对于STM32,使用类似于51的写法:
- 打开时钟:为了降低功耗,时钟全部复位为0(即关闭)。
- 配置IO口的输出模式:STM32的IO口需要配置输入/输出模式。
- 配置ODR寄存器:对某一位进行操作(打开或关闭)。
#include "stm32f10x.h"
int main(void)
{
// 配置时钟
* (unsigned int*) 0X40021018 |= (1<<3);
// 配置IO口输出模式
* (unsigned int*) 0X40010C00 |= (1<<4);
// 配置GPIOB ODR
* (unsigned int*) 0X40010C0C &= ~(1<<1);
}
void SystemInit(void)
{
// 函数体为空,目的是为了骗过编译器不报错
}
-
配置ODR寄存器:《2.3存储器映像》找到GPIOB的基地址 -> 《8.2.4端口输出数据寄存器》找到GPIO_ODR的地址偏移,相加得到GPIO_ODR寄存器地址。
-
配置IO口输出模式:
- 低寄存器控制低8位IO口的输入/输出模式;CNF0[1:0]与MODE0[1:0]共同控制PB0的模式、CNF1[1:0]与MODE1[1:0]共同控制PB1的模式…
- 2.3存储器映像》找到GPIOB的基地址 -> 《8.2.1配置低寄存器》找到GPIO_CRL的地址偏移,相加得到GPIO_CRL寄存器地址。
- 打开时钟:
- 在《2存储器和总线构架》中由图1 系统结构可知:复位和时钟控制(RCC)挂在AHB系统总线上,GPIOB在APB2总线上。
- 《2.3存储器映像》找到RCC的基地址 -> 《6.3.7APB2外设时钟使能寄存器》找到RCC_APB2ENR的地址偏移,相加得到RCC_APB2ENR寄存器地址。
2.2 练习
1.将三个LED灯一起点亮(呈白灯)。
#include "stm32f10x.h"
int main(void)
{
// 配置时钟
* (unsigned int*) 0X40021018 |= (1<<3);
// 配置IO口输出模式
* (unsigned int*) 0X40010C00 |= (1<<4); // PB1
* (unsigned int*) 0X40010C00 &= ~(1<<7);
* (unsigned int*) 0X40010C00 |= (1<<0); // PB0
* (unsigned int*) 0X40010C00 &= ~(1<<2);
* (unsigned int*) 0X40010C00 |= (1<<20); // PB5
* (unsigned int*) 0X40010C00 &= ~(1<<22);
// 配置GPIOB ODR
* (unsigned int*) 0X40010C0C &= ~(1<<1); // PB1 = 0
* (unsigned int*) 0X40010C0C &= ~(1<<0); // PB0 = 0
* (unsigned int*) 0X40010C0C &= ~(1<<5); // PB5 = 0
}
void SystemInit(void)
{
// 函数体为空,目的是为了骗过编译器不报错
}
- 对GPIO_CRL的第2位、第7位、第22位置0的原因:复位后这三位都是1。在置0后输出模式由通用开漏输出模式转换为通用推挽输出模式。
2.写一个简单的延迟函数,让LED灯闪烁
#include "stm32f10x.h"
void delay(void)
{
int x, y;
for(x = 1100; x>0; x--)
{
for(y = 1000; y>0; y--);
}
}
int main(void)
{
// 配置时钟
* (unsigned int*) 0X40021018 |= (1<<3);
// 配置IO口输出模式
* (unsigned int*) 0X40010C00 |= (1<<4); // PB1 Blue
* (unsigned int*) 0X40010C00 &= ~(1<<7);
* (unsigned int*) 0X40010C00 |= (1<<0); // PB0 Green
* (unsigned int*) 0X40010C00 &= ~(1<<2);
* (unsigned int*) 0X40010C00 |= (1<<20); // PB5 Red
* (unsigned int*) 0X40010C00 &= ~(1<<22);
// 配置GPIOB ODR
while(1)
{
* (unsigned int*) 0X40010C0C &= ~(1<<1); // PB1 = 0
delay();
* (unsigned int*) 0X40010C0C |= (1<<1);
delay();
}
}
void SystemInit(void)
{
// 函数体为空,目的是为了骗过编译器不报错
}
- 上面的这段代码呈现的效果为:白灯与黄灯(红与绿混合为黄色)交替闪烁。
- 原因:GPIOB_ODR复位值为0x0000 0000 ,而STM32上的LED灯为低电平触发,恰好使得红灯与绿灯常亮。
- 要想让STM32上的LED灯呈现出蓝、绿、红三色交替闪烁的效果,需要在while循环前将GPIO_ODR相应的位置1。
#include "stm32f10x.h"
void delay(void)
{
int x, y;
for(x = 1100; x>0; x--)
{
for(y = 1000; y>0; y--);
}
}
int main(void)
{
// 配置时钟
* (unsigned int*) 0X40021018 |= (1<<3);
// 配置IO口输出模式
* (unsigned int*) 0X40010C00 |= (1<<4); // PB1 Blue
* (unsigned int*) 0X40010C00 &= ~(1<<7);
* (unsigned int*) 0X40010C00 |= (1<<0); // PB0 Green
* (unsigned int*) 0X40010C00 &= ~(1<<2);
* (unsigned int*) 0X40010C00 |= (1<<20); // PB5 Red
* (unsigned int*) 0X40010C00 &= ~(1<<22);
* (unsigned int*) 0X40010C0C |= (1<<1);
* (unsigned int*) 0X40010C0C |= (1<<5);
* (unsigned int*) 0X40010C0C |= (1<<0);
// 配置GPIOB ODR
while(1)
{
* (unsigned int*) 0X40010C0C &= ~(1<<1); // PB1 = 0
delay();
* (unsigned int*) 0X40010C0C |= (1<<1);
delay();
* (unsigned int*) 0X40010C0C &= ~(1<<0); // PB0 = 0
delay();
* (unsigned int*) 0X40010C0C |= (1<<0);
delay();
* (unsigned int*) 0X40010C0C &= ~(1<<5); // PB5 = 0
delay();
* (unsigned int*) 0X40010C0C |= (1<<5);
delay();
}
}
void SystemInit(void)
{
// 函数体为空,目的是为了骗过编译器不报错
}