文章目录
一、使用寄存器方式点亮流水灯
单片机: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 复制相关库文件
-
获取库函数文件
官网获取地址:https://www.st.com/zh/embedded-software/stsw-stm32054.html -
在库函数中拷贝相关文件到对应文件夹下
库文件夹 | 拷贝的文件 | 粘贴位置 |
---|---|---|
CoreSupport | core_cm3.c core_cm3.h | core |
arm | 启动文件(根据单片机Flash大小选择,具体内容可自行进行查找) | core |
inc | inc文件夹 | lib |
src | src文件夹 | lib |
STM32F10x | stm32f10x.h system_stm32f10x.c system_stm32f10x.h | user |
STM32F10x_StdPeriph_Template | stm32f10x_conf.h stm32f10x_it.c stm32f10x_it.h main.c | user |
- 可以将原来生成工程自动创建的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闪烁周期)实际为多少。
- 首先设置options for target:
将Xtal改为8MHZ。因为STM32F103C8的外部晶振为8MHZ。
- 设置Debug
- 设置检测引脚
- 最后波形图
可以看出循环一周下来时间大约是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