文章目录
用Proteus 设计一个STM32最小系统板+LED流水灯实验原理图,仿真运行。以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只_(或更多)红绿蓝LED 搭建电路,使用GPIOA、GPIOB、GPIOC这3个端口控制LED灯,轮流闪烁,间隔时长1秒。
1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数;
2)用C语言寄存器方式编程实现,代码须有详细注解。
3)STM32最小系统核心板子出厂时已经焊接好了1个led灯(标注了PC13处),一般可通过此灯的点亮让编程者验证自己烧录的代码是否正常运行了。请查阅最小版电路原理图和相关资料,将这个灯也用在流水灯中,重编新程序。
一、stm32C8T6介绍
STM32F103C8T6是一款由意法半导体公司(ST)推出的基于Cortex-M3内核的32位微控制器,硬件采用LQFP48封装,属于ST公司微控制器中的STM32系列。除了被我们熟知的STM32,ST公司还有SPC5X系列、STM8系列等,具体参数如下:
二、GPIOx端口的各寄存器地址和详细参数
1.地址查找
·第一步,找到GPIOB的基地址
也就是找到GPIOB的小区。结论是,所有GPIOB相关的寄存器,都住在0x4001 0C00到0x4001 0FFF范围内。
·第二步,找到端口输入寄存器的地址偏移
找到存储数据的那个屋子,结论是0x4001 0C00+8 = 0x4001 0C08
·第三步,找到知道数据的那个人
PB3的数据位于从右往左数第4个。
而这个寄存器的位数是32位(虽然高16位没有用到),这就是32位的单片机的意思。每个寄存器都占据4字节,32位。
PB3的输入数据位于0x4001 0C08这个地址上,这个地址上存放数据的右起第4个位就是PB3引脚对应的高低电平。
直接访问这个地址:
unsigned int *pGPIOB_IDR = (unsigned int *)0x40010C08;
unsigned char PB3 = *pGPIOB_IDR & 0x8;//取出从右往左数的第4位
2.直接操作寄存器来点亮LED
使用LED在PB8。
·配置时钟使能。
参考手册,搜索"时钟",在表1里可以看到。
时钟控制名字叫做RCC,属于AHB总线。GPIOB属于APB2。
下图系统结构可以看到时钟的从属关系,可以看出AHB总线包含RCC时钟控制,GPIO是属于APB2的。
GPIO端口B的地址从0x4001 0C00开始。接下来只寻找时钟使能寄存器的地址:
复位和时钟控制RCC的地址从0x4002 1000开始;
可以在6.3.7小节找到APB2外设时钟使能寄存器(RCC_APB2ENR),偏移地址是0x18,所以APB2的地址就是0x4002 1018。
看手册RCC_APB2ENR,位3是IOPBEN,名字是IO端口B时钟使能,就是我们想要的。把RCC_APB2ENR的位3赋值为1,就是开启GPIOB时钟。
·配置为通用输出
既然叫做IO,那么肯定就是可以输入,可以输出。
控制LED需要输出高电平或是低电平,所以需要配置为输出。
由于STM32的每个IO都需要4个位来配置,所以一个32位的寄存器最大只能配置8个IO(32位的单片机的寄存器就是32位的)。STM32中,用端口配置低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。
配置引脚PB8,使用的寄存器是GPIOB_CRH。下面我们来寻找这个寄存器的地址。
关于此寄存器的说明位于8.2.2小节。先看标题GPIOx,表示不管是PA,PB还是PE,都能用。
偏移地址是0x04,意思是在基地址的基础上再加0x04,所以,对于GPIOB来说就是0x4001 0c04。如果配置PB0-PB7,那么需要的寄存器是低位的寄存器GPIOB_CRL,它的地址是0x4001 0c00。我们需要配置的寄存器是GPIOB_CRH。
找到需要操作的寄存器后,把它配置为通用输出。
复位值是0x4444 4444,并不是0x0000 0000。所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。
我们需要的是输出高低电平,所以要设置为输出。输出模式有:
推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。
开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。
开漏是需要外接上拉电阻才可以输出高电平的,这里并不适合。所以需要设置为推挽输出。
所以配置为输出模式,通用推挽输出。所以设置GPIOB_CRH的MODE8与CNF8为0b0011,即0x3。此寄存器中其它的位暂时不做修改,使用默认值,也就是GPIOB_CRH设置为:0x4444 4443。
点亮LED需要输出低电平,我们需要输出0,得知地址的偏移是0x0C,所以这个数据寄存器的地址就是0x4001 0C0C,把第8位写为0。
·使用直接赋值的方式写寄存器的地址
int main(void)
{
unsigned int *pRCC_APB2ENR = (unsigned int *)0x40021018;
unsigned int *pGPIOB_CRH = (unsigned int *)0x40010c04;
unsigned int *pGPIOB_ODR = (unsigned int *)0x40010c0c;
*pRCC_APB2ENR = 0x00000008;
*pGPIOB_CRH = 0x44444443;
*pGPIOB_ODR = 0x00000000;
return 0;
}
三、设计思路
需要在 STM32F103C8T6 上面通过 初始化GPIO 来实现点亮 LED 灯。
外设实现的功能可能是完全不同的,但是,多数情况下,我们在设计程序的时候不需要考虑外设具体如何怎样实现功能,只需要给外设接在哪里、高电平有效还是低电平有效。因此,完成题目要求的时候,只需要找到 GPIOA-5、GPIOB-9、GPIOC-14 的地址,然后通过 GPIO的初始化,控制寄存器将片外引脚(我们称之为 IO口)拉低拉高, 输出高低电平,以控制LED亮灭。
点灯是所有学单片机的人都应该学会的一项技能。C51单片机和 stm32 点灯类似。
51单片机的点灯是,通过控制寄存器将片外引脚(我们称之为IO口)拉低拉高,输出高低电平,以控制LED亮灭。
其过程:单片机给指令->控制寄存器->给IO口电平->控制LED亮灭
stm32的点灯则是,通过使能外设GPIO时钟,发出指令给外设GPIO,外设GPIO收到指令后,着手配置自己的寄存器,然后给IO口模式,让其实现各种功能。其过程:CPU给指令->GPIO收到指令->配置内部寄存器->配置IO口模式(注意是模式)->控制LED亮灭
STM32开发板中包含较多寄存器,实现流水灯操作,需要对相应的引脚进行操作,对相应的引脚进行时钟配置、输入输出模式设置、最大速率设置。
于是利用 STM32F103C8T6 实现流水灯,要经过以下步骤:
·时钟配置
·输入输出模式设置
·最大速率设置
·烧录程序
·运行
四、实现过程
1、时钟配置
找到时钟使能寄存器映射基地址
找到端口偏移地址以及对应端口所在位置
·外设时钟使能寄存器,偏移量为0x18,起始地址0x4002 1000,该寄存器地址为0x4002 1018
·使能对应端口时钟
查询数据手册可发现,外设时钟使能寄存器,设偏移量为0x18,起始地址0x4002 1000,该寄存器地址为0x4002 1018
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018) #时钟使能寄存器
手册RCC_APB2ENR,位3是IOPBEN,名字是IO端口B时钟使能,就是我们想要的。把RCC_APB2ENR的位3赋值为1,就是开启GPIOB时钟
RCC->APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC->APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC->APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<4;
2、输入输出模式设置、最大速率设置
本次实验采用通用推挽输出模式,最高输出时钟频率2Mhz。分别用到GPIOA-5、GPIOB-9、GPIOC-14 这3个引脚上三个引脚。其中A4属于端口配置低寄存器偏移地址为0x00,B9、C14属于端口配置高寄存器偏移地址为0x04。
·找到GPIOx端口基地址
配置对应引脚寄存器,基地址+偏移量
故而配置如下:
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
设置输出模式为推挽输出,输出速度为2Mhz
例:将GPIOB-9配置成推挽输出模式,且最大速度为2MHz
首先,其为GPIOB9端口,其属于端口配置高寄存器模块,则由上图可知,CNF9和MODE9位为0,其余位为F,即:GPIOB_CRH&=0xFFFFFF0F;又因其为推挽输出模式,且最大速度为2MHz,所以4位寄存器的配置就是CNF9【00】MODE9【10】,0010换成十进制数就是2,即:GPIOB_CRH|=0x00000020
GPIOA_CRL&=0xFFF0FFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOA->ODR|=1<<5; //设置PA5初始灯为灭
GPIOB_CRH&=0xFF0FFFFF; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOB->ODR|=0x1<<9; //设置初始灯为灭
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOC->ODR|=0x1<<14; //设置初始灯为灭
3、代码实现
1.创建新工程
·新建 test2 文件夹 —> 点击 Project 下的 New uVision Project —> 输入文件名 test2
·选择 STM32F103C8
·创建项目出现弹窗,勾选 CORE 项,点击 OK 完成创建
2.添加启动文件
点击 Target1→Source Group1→双击→设置打开文件类型为 Asm Source file→选择 startup_stm32f10x_md.s→点击 Add,如下图所示:
3.代码编写并编译
(1)加上PC13口前代码
//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
//函数声明
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
//时延函数
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<10000;i++);
}
//控制灯亮灭
void A_LED_LIGHT()
{
GPIOA_ODR=0x0<<5; //PA5低电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x1<<14; //PC14高电平
}
void B_LED_LIGHT()
{
GPIOA_ODR=0x1<<5; //PA5高电平
GPIOB_ODR=0x0<<9; //PB9低电平
GPIOC_ODR=0x1<<14; //PC14高电平
}
void C_LED_LIGHT()
{
GPIOA_ODR=0x1<<5; //PA5高电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x0<<14; //PC14低电平
}
//------------------------主函数--------------------------
int main(){
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
GPIOA_CRL&=0xFF0FFFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOB_CRH&=0xFFFFFF0F; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOC_CRH&=0xF0FFFFFF; //设置位清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOA_ODR |= (1<<5);
GPIOB_ODR |= (1<<9); //设置灯的初始状态为灭
GPIOC_ODR |= (1<<14);
while(1)
{
A_LED_LIGHT();
Delay_ms(60);
B_LED_LIGHT();
Delay_ms(60);
C_LED_LIGHT();
Delay_ms(60);
}
}
(2)加上PC13口后代码
·由于GPIOC已配置寄存器,只需要加上PC13推挽输出,再循环时增添一个PC13灯亮循环即可,由于PC14和PC13共用一个GPIOC,所以需要增加相应与活=或运算。
//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
//函数声明
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void C3_LED_LIGHT(void);
//时延函数
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<10000;i++);
}
//控制灯亮灭
void A_LED_LIGHT()
{
GPIOA_ODR &= (0<<5); //PA5低电平,红灯亮
GPIOB_ODR |= (1<<9); //PB8高电平,黄灯灭
GPIOC_ODR |= (1<<14); //PC14高电平,绿灯灭
GPIOC_ODR |= (1<<13); //PC13,PC13口灯不亮
}
void B_LED_LIGHT()
{
GPIOA_ODR |= (1<<5); //高电平
GPIOB_ODR &= (0<<9); //低电平
GPIOC_ODR |= (1<<14); //高电平
GPIOC_ODR |= (1<<13);//高电平
}
void C_LED_LIGHT()
{
GPIOA_ODR |= (1<<5); //高
GPIOB_ODR |= (1<<9); //高
GPIOC_ODR &= (0<<14); //低
GPIOC_ODR |= (1<<13); //高
}
void C3_LED_LIGHT()
{
GPIOA_ODR |= (1<<5); //高
GPIOB_ODR |= (1<<9); //高
GPIOC_ODR = (0<<13);//高
GPIOC_ODR |= (1<<14); //低
}
//------------------------主函数--------------------------
int main(){
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
GPIOA_CRL&=0xFF0FFFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOB_CRH&=0xFFFFFF0F; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOC_CRH&=0xF0FFFFFF; //设置位清零
GPIOC_CRH|=0x02000000; //PC14推挽输出,把第27、26、25、24变为0010
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x00200000; //PC13推挽输出,把第23、22、21、20变为0010
GPIOA_ODR |= (1<<5);
GPIOB_ODR |= (1<<9); //设置灯的初始状态为灭
GPIOC_ODR |= (1<<14);
GPIOC_ODR |= (1<<13);
while(1)
{
A_LED_LIGHT();//红灯亮
Delay_ms(60);
B_LED_LIGHT();//黄灯亮
Delay_ms(60);
C_LED_LIGHT();//绿灯亮
Delay_ms(60);
C3_LED_LIGHT();//PC13口亮
Delay_ms(60);
}
}
编译,生成HEX文件
4、仿真图设计
5、硬件连接
用杜邦线分别将A5,B9,C14和LED负极连接起来,再将LED正极接+,板子3.3接+
五、结果实现
1、仿真
2、硬件
总结
这次算是对stm32使用的入门,是第一次通过编写程序实现stm32硬件的应用。这次实验通过简单的寄存器操作,我深刻理解了stm32的工作原理;了解了寄存器的相关介绍;学习了地址查找,直接操作寄存器;FlyMu的使用及ST-link的配置、硬件的连接,收获很大,受益匪浅。
参考:
1.STM32启动文件:startup_stm32f10x_hd.s等启动文件的简单描述
2.STM32F103C8T6寄存器方式借助面包板点亮LED流水灯详解
3.STM32寄存器的简介、地址查找,与直接操作寄存器