STM32寄存器和标准库点流水灯

一、使用寄存器方式点亮流水灯

单片机:stm32f103C8T6
TTL转接口
三个红黄绿LED发光二极管
若干杜邦线
KeilMDK软件
单片机下载软件mcuisp
使用引脚:A1,B1,B10

1.1 程序设计思路

要点亮流水灯,就需要让灯依次亮灭,故分三个状态

  • A1亮,B1,B10灭
  • B1亮,A1,B10灭
  • B10亮,A1,B1灭

所以,在while循环中通过给寄存器进行位操作来给对应引脚置高低电平。

1.2 KeilMDK工程创建

可以浅浅参考https://blog.csdn.net/wakeup_high/article/details/132947427?spm=1001.2014.3001.5502

1.3 c程序文件

//APB2外设时钟使能寄存器
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//选择CPIOA1,GPIOB1,GPIOB10作为点灯输出引脚
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)	//GPIOA配置寄存器
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00) //GPIOB配置低寄存器
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04) //GPIOB配置高寄存器
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C) //GPIOA输出数据寄存器
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C) //GPIOB输出数据寄存器


//延时函数
void Delay_ms( volatile unsigned int t){
	unsigned int i,j;
	for(j = 0; j < t; j++){
		for(i = 0; i < 1000 ; i++);
	}
}

int main() {
	//使能a,b时钟
	RCC_APB2ENR|=1<<2;
	RCC_APB2ENR|=1<<3;
	//GPIOA1设置为推挽输出
	GPIOA_CRL&=0xFFFFFF0F; //设置位 清零
	GPIOA_CRL|=0x00000030; //PA1推挽最快速输出
	//GPIOB1设置为推挽输出
	GPIOB_CRL&=0xFFFFFF0F;
	GPIOB_CRL|=0x00000030;
	//GPIOB10设置为推挽输出
	GPIOB_CRH&=0xFFFFF0FF;
	GPIOB_CRH|=0x00000300;
	while(1){
		//位操作置B1,B10低电平,A1高电平
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<10);
		GPIOA_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B10低电平,B1高电平
		GPIOA_ODR=~(1<<1);
		GPIOB_ODR=~(1<<10);
		GPIOB_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B1低电平,B10高电平
		GPIOA_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR|=1<<10;
		Delay_ms(10000);
	}
}

1.4 运行效果

在这里插入图片描述

1.5 加入PC13管脚

不看电路,按照最开始的想法,置1为高电平,置0为低电平,这时会发生什么呢?
先附上程序

//APB2外设时钟使能寄存器
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//选择CPIOA1,GPIOB1,GPIOB10作为点灯输出引脚
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)	//GPIOA配置寄存器
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00) //GPIOB配置低寄存器
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04) //GPIOB配置高寄存器
#define GPIOC_CRH *((unsigned volatile int*)0x40011004) //GPIOC配置高寄存器
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C) //GPIOA输出数据寄存器
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C) //GPIOB输出数据寄存器
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C) //GPIOC输出数据寄存器

//延时函数
void Delay_ms( volatile unsigned int t){
	unsigned int i,j;
	for(j = 0; j < t; j++){
		for(i = 0; i < 1000 ; i++);
	}
}

int main() {
	//使能a,b时钟
	RCC_APB2ENR|=1<<2;
	RCC_APB2ENR|=1<<3;
	RCC_APB2ENR|=1<<4;
	//GPIOA1设置为推挽输出
	GPIOA_CRL&=0xFFFFFF0F; //设置位 清零
	GPIOA_CRL|=0x00000030; //PA1推挽最快速输出
	//GPIOB1设置为推挽输出
	GPIOB_CRL&=0xFFFFFF0F;
	GPIOB_CRL|=0x00000030;
	//GPIOB10设置为推挽输出
	GPIOB_CRH&=0xFFFFF0FF;
	GPIOB_CRH|=0x00000300;
	//GPIOC13设置为推挽输出
	GPIOC_CRH&=0xFF0FFFFF;
	GPIOC_CRH|=0x00300000;
	while(1){
		//位操作置B1,B10, C13低电平,A1高电平
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<10);
		GPIOC_ODR&=~(1<<13);
		GPIOA_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B10, C13低电平,B1高电平
		GPIOA_ODR=~(1<<1);
		GPIOB_ODR=~(1<<10);
		GPIOC_ODR&=~(1<<13);
		GPIOB_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B1, C13低电平,B10高电平
		GPIOA_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<1);
		GPIOC_ODR&=~(1<<13);
		GPIOB_ODR|=1<<10;
		Delay_ms(10000);
		//位操作置A1,B1, B10低电平,C13高电平
		GPIOA_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<10);
		GPIOC_ODR|=1<<13;
		//GPIOC_ODR&=~(1<<13);
		Delay_ms(10000);
	}
}

现象:
在这里插入图片描述
此时出现了一个很奇怪的现象,就是PC13管脚和内置LED(PC13)的状态是相反的。那么要调整过来,就尝试将PC13的置位状态相反(以前是1为高电平,现在0为高电平),代码如下:

//APB2外设时钟使能寄存器
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//选择CPIOA1,GPIOB1,GPIOB10作为点灯输出引脚
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)	//GPIOA配置寄存器
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00) //GPIOB配置低寄存器
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04) //GPIOB配置高寄存器
#define GPIOC_CRH *((unsigned volatile int*)0x40011004) //GPIOC配置高寄存器
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C) //GPIOA输出数据寄存器
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C) //GPIOB输出数据寄存器
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C) //GPIOC输出数据寄存器

//延时函数
void Delay_ms( volatile unsigned int t){
	unsigned int i,j;
	for(j = 0; j < t; j++){
		for(i = 0; i < 1000 ; i++);
	}
}

int main() {
	//使能a,b时钟
	RCC_APB2ENR|=1<<2;
	RCC_APB2ENR|=1<<3;
	RCC_APB2ENR|=1<<4;
	//GPIOA1设置为推挽输出
	GPIOA_CRL&=0xFFFFFF0F; //设置位 清零
	GPIOA_CRL|=0x00000030; //PA1推挽最快速输出
	//GPIOB1设置为推挽输出
	GPIOB_CRL&=0xFFFFFF0F;
	GPIOB_CRL|=0x00000030;
	//GPIOB10设置为推挽输出
	GPIOB_CRH&=0xFFFFF0FF;
	GPIOB_CRH|=0x00000300;
	//GPIOC13设置为推挽输出
	GPIOC_CRH&=0xFF0FFFFF;
	GPIOC_CRH|=0x00300000;
	while(1){
		//位操作置B1,B10, C13低电平,A1高电平
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<10);
		GPIOC_ODR|=1<<13;
		GPIOA_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B10, C13低电平,B1高电平
		GPIOA_ODR=~(1<<1);
		GPIOB_ODR=~(1<<10);
		GPIOC_ODR|=1<<13;
		GPIOB_ODR|=1<<1;
		Delay_ms(10000);
		//位操作置A1,B1, C13低电平,B10高电平
		GPIOA_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<1);
		GPIOC_ODR|=1<<13;
		GPIOB_ODR|=1<<10;
		Delay_ms(10000);
		//位操作置A1,B1, B10低电平,C13高电平
		GPIOA_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<1);
		GPIOB_ODR&=~(1<<10);
		//GPIOC_ODR|=1<<13;
		GPIOC_ODR&=~(1<<13);
		Delay_ms(10000);
	}
}

此时流水灯就变成了这样:
在这里插入图片描述
可见,成功将内置LED加入了流水灯行列。那么为啥和前面的高低电平不同了呢?查看stm32f103c8t6的原理图:
在这里插入图片描述
可以看到内置LED电路是一个发光二极管和电阻串联,那么只看这一小部分就是这样:
在这里插入图片描述
所以它俩的状态才会相反。

二、使用标准库点亮流水灯

2.1 创建工程

在这里插入图片描述
命名(我这里新建了一个Stm32LibLed文件夹,然后在这个文件夹里创建工程文件LED)
在这里插入图片描述
选择芯片
在这里插入图片描述
点击Ok后,下图页面不用选择,直接关掉
在这里插入图片描述
文件管理器打开刚才新建的Stm32LibLed文件夹,在其中添加如下文件夹:

  • user(主要用来存放用户自己编写的c)
  • obj(用来存放输出文件:.hex,.o等中间文件)
  • lib(用来存放库函数源码)
  • core(存放核心文件、启动文件)

2.2 复制相关库文件

库文件夹拷贝的文件粘贴位置
CoreSupportcore_cm3.c core_cm3.hcore
arm启动文件(根据单片机Flash大小选择,具体内容可自行进行查找)core
incinc文件夹lib
srcsrc文件夹lib
STM32F10xstm32f10x.h system_stm32f10x.c system_stm32f10x.huser
STM32F10x_StdPeriph_Templatestm32f10x_conf.h stm32f10x_it.c stm32f10x_it.h main.cuser
  • 可以将原来生成工程自动创建的Listings和Objects文件删掉(这俩其实也是存放中间文件的,所以等同于现在的obj文件夹,但是DebugConfig不能删,这个文件和调试有关,删掉会报错)
    在这里插入图片描述
    …等图片

2.3 添加文件到工程

在这里插入图片描述
Add Files
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击Add即可添加。其它文件夹相同,除了core文件夹中必须把启动文件(.s)加入,其它加入都是.c文件,所有添加完的应该是这样:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后目录应该是这样:
在这里插入图片描述
如果此时点main.c会报错,因为这些.c文件中的头文件还不含有,所以接下来配置

2.4 配置工程

  • 设置OutPut路径,选择自己建的obj文件夹。勾选生成hex文件
    在这里插入图片描述
  • 配置全局宏定义变量
    STM32F10X_MD,USE_STDPERIPH_DRIVER赋值到对应位置
    在这里插入图片描述
    最后,需要将main.c中的代码都删掉,写你自己的代码。

2.5 编写标准库函数流水灯代码

#include "stm32f10x.h"

void delay_ms(uint16_t time)
{
	uint16_t i = 0;
	while(time--)
	{
		i = 10000;
		while(i--);
	}
}

int main(void)
{
	//时钟使能A,B
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	//配置
	GPIO_InitTypeDef led_initA1;
	led_initA1.GPIO_Pin = GPIO_Pin_1;
	led_initA1.GPIO_Mode = GPIO_Mode_Out_PP;
	led_initA1.GPIO_Speed = GPIO_Speed_10MHz;
	
	GPIO_InitTypeDef led_initB1;
	led_initB1.GPIO_Pin = GPIO_Pin_1;
	led_initB1.GPIO_Mode = GPIO_Mode_Out_PP;
	led_initB1.GPIO_Speed = GPIO_Speed_10MHz;
	
	GPIO_InitTypeDef led_initB10;
	led_initB10.GPIO_Pin = GPIO_Pin_10;
	led_initB10.GPIO_Mode = GPIO_Mode_Out_PP;
	led_initB10.GPIO_Speed = GPIO_Speed_10MHz;
	
	//初始化
	GPIO_Init(GPIOA, &led_initA1);
	GPIO_Init(GPIOB, &led_initB1);
	GPIO_Init(GPIOB, &led_initB10);
	while(1)
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_1);
		GPIO_ResetBits(GPIOB, GPIO_Pin_10);
		GPIO_SetBits(GPIOA, GPIO_Pin_1);
		delay_ms(1000);
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
		GPIO_ResetBits(GPIOB, GPIO_Pin_10);
		GPIO_SetBits(GPIOB, GPIO_Pin_1);
		delay_ms(1000);
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
		GPIO_ResetBits(GPIOB, GPIO_Pin_1);
		GPIO_SetBits(GPIOB, GPIO_Pin_10);
		delay_ms(1000);
	}
}

2.6 效果

在这里插入图片描述

2.7 亮灭准确周期

LED灯的亮/灭周期是通过软件循环延时完成的,其准确周期大致是多少呢?
在没有示波器条件下,可以使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形,更方便动态跟踪调试和定位代码故障点。 请用此功能观察GPIO端口的输出波形,并分析时序状态正确与否、高低电平转换周期(LED闪烁周期)实际为多少。

  1. 首先设置options for target:
    将Xtal改为8MHZ。因为STM32F103C8的外部晶振为8MHZ。
    在这里插入图片描述
  2. 设置Debug
    在这里插入图片描述
  3. 设置检测引脚
    在这里插入图片描述
    在这里插入图片描述
  4. 最后波形图
    在这里插入图片描述
    在这里插入图片描述
    可以看出循环一周下来时间大约是2.5s。
    在这里插入图片描述
    而延迟函数调整的实际时间是0.84s左右。

参考
https://blog.csdn.net/qq_46467126/article/details/120847240?spm=1001.2014.3001.5502
https://blog.csdn.net/xing_2020/article/details/128709187

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值