寄存器方式点亮LED
前言
这个实验使用的处理器是STM32F103C8T6,听说初学单片机的时候建学寄存器,于是我就用了这个核心板进行学习,之前有接触过STC51单片机,新建寄存器的工程比较简单,像51工程那样几个文件就好了。点亮LED主要是学习GPIO作为输出功能使用、寄存器及单片机的时钟系统。
1、新建MDK工程
1.1 点击Project -> New uVision Project…
1.2 在电脑合适的位置新建一个存放工程的文件夹,并对该文件夹进行命名,然后把工程文件都放到这个文件夹里面再对工程命名。
1.3 建好后会提示要添加对应芯片型号,这里选STM32F103C8T6,也可以在Search输入框搜索芯片,这里如果没有得选,则说明安装pack芯片支持包(Keil.STM32F1xx_DFP.1.0.5.pack)不成功导致的。
有的时候会提示下面这个对话框,如果已经安装了PACK包,可以直接叉掉即可。
1.4 新建一个空白文本文档,保存为main.c文件。
1.5 鼠标右键Source Group1点击Add Existing Files to Group…,添加刚才新建的main.c文件到工程分组里。
1.6 新建完的工程如下图:
1.7 写完代码编译之前勾选输出HEX文件,如果不勾选则无法输出下载文件。
1.8 最终写完代码编译发现最终会有一个报错提示如下:
原因就是没有添加对应的.s启动文件,去网上找了一个启动文件添加到工程就行。
启动文件下载路径:http://www.openedv.com/posts/list/313.htm
鼠标右键Source Group1点击Add Existing Files to Group…,添加.s文件到工程分组里,如下图:
查看芯片数据手册可以知道C8T6的flash是64k的,所以要用中容量的启动文件。
查阅中文参考手册得知下面的数据:
小容量: FLASH ≤ 32K
中容量: 64K ≤ FLASH ≤ 128K
大容量: 256K ≤ FLASH
ld:低密度产品,FLASH小于64K是小容量
md:中等密度产品,FLASH=64 or 128是中容量
hd:高密度产品,FLASH大于128 是大容量
综上描述,这里选择的是 startup_stm32f10x_md.s 作为启动文件。
1.9 发现了添加了还是会报错,于是就去看了下正点原子的开发指南新建工程章节,发现这个103的寄存器是要适当修改这个启动文件内容的。具体是 Reset_Handler 函数,该函数修改后代码如下:
要把132到134行代码屏蔽或者删掉才可以,因为没有用到 SystemInit 函数,所以注释掉
如果是库函数版本的工程,可以忽略这个步骤。
2、硬件连接
LED连接到芯片的PC13引脚上,一端接到了VCC,所以需要低电平才能点亮LED,高电平熄灭。
板子正面图:
3、查看手册&编写程序
3.1 查看手册配置
参考手册:STM32F1中文参考手册.pdf、STM32F103C8T6芯片数据手册.pdf
3.1.1 查看总线架构,得知LED(GPIOC)挂载在总线在APB2上。
3.1.2 查看获取挂载在APB2的GPIOC端口及RCC的基地址。
RCC:0x40021000
GPIOC:0x40011000
3.1.3 端口配置寄存器,由于LED所在的端口是PC13,所以用的寄存器是GPIOx_CRH(5处有说明)
采用推挽输出模式,速度50M,那么bit23-20对应的值就要设置为0011即对应十进制为3。
3.1.4 获取APB2总线的偏移地址为:0x18,时钟需要使能第四个位。
3.1.5 配置端口输出数据寄存器GPIOx_ODR,操作bit13,偏移地址:0x0C
3.2 程序编写
3.2.1 点亮LED
/* 使能端口时钟,设置第4位为1为使能 */
从前面可以获取到RCC的基地址和APB2的偏移地址,两者加起来就是这个地址了
(*(volatile int *)0x40021018) |= 1<<4;
/* 配置输出模式 */
首先把整个端口清零,以免有误操作,之后再设置这个端口对应的IO口的值为3(不采用直接给端口赋值的方式,采样位移方法直观高效)
(*(volatile int *)0x40011004) &= ~(0x0F<<20);
(*(volatile int *)0x40011004) |= 3<<20;
/* 配置端口输出功能,输出低电平就可以点亮LED了(采取清零操作) */
(*(volatile int *)0x4001100C) &= ~(1<<13);
这里为什么要用volatile 这个关键字修饰就不详细描述了,可以网上看看教程。
整理代码如下:
int main(void)
{
/* 使能端口时钟 */
(*(volatile int *)0x40021018) |= 1<<4;
/* 配置输出模式 */
(*(volatile int *)0x40011004) &= ~(0x0F<<20);//清0
(*(volatile int *)0x40011004) |= 3<<20; //置1
/* 配置端口输出功能,输出低电平 */
(*(volatile int *)0x4001100C) &= ~(1<<13);
while(1);
}
为了方便直观看代码,可以采取宏定义的方法来修改,更加直观可见
整理代码如下:
#define RCC_APB2ENR (*(volatile int *)0x40021018)
#define GPIOC_CRH (*(volatile int *)0x40011004)
#define GPIOC_ODR (*(volatile int *)0x4001100C)
int main(void)
{
/* 使能端口时钟 */
RCC_APB2ENR |= 1<<4;
/* 配置输出模式 */
GPIOC_CRH &= ~(0x0F<<20);
GPIOC_CRH |= 3<<20;
/* 配置端口输出功能,输出低电平 */
GPIOC_ODR &= ~(1<<13);
while(1);
}
上面几行代码就可以简单点亮一个LED了。
3.2.2 LED闪烁
想要LED实现闪烁效果,可以在点亮和熄灭之间添加延时间隔就可以清晰看到闪烁效果了。
简单写个for循环就可以达到延时功能。
整理代码如下:
#define RCC_APB2ENR (*(volatile int *)0x40021018)
#define GPIOC_CRH (*(volatile int *)0x40011004)
#define GPIOC_ODR (*(volatile int *)0x4001100C)
void Delay_ms(volatile int t);
int main(void)
{
/* 使能端口时钟 */
RCC_APB2ENR |= 1<<4;
/* 配置输出模式 */
GPIOC_CRH &= ~(0x0F<<20);
GPIOC_CRH |= 3<<20;
/* 配置端口输出功能,输出低电平 */
//GPIOC_ODR &= ~(1<<13);
//GPIOC_ODR |= (1<<13);
while(1)
{
GPIOC_ODR &= ~(1<<13);//输出低电平
Delay_ms(1000000);
GPIOC_ODR |= (1<<13);//输出高电平
Delay_ms(1000000);
}
}
void Delay_ms(volatile int t)
{
int i;
while(t--)
for(i=0;i<800;i++);
}
4、总结
通过这个寄存器的学习,使我学会了查阅芯片的使用手册及如何操作寄存器从而达到操作GPIO的效果,另外也加固了对C语言的学习,比如位操作、volatile关键字在这里的用法作用等。
实验效果