一、基于寄存器操作的LED流水灯
1、寄存器
(1)寄存器的定义
寄存器是存储代码的硬件,由多个具有存储器组合而成。在冯诺依曼结构中的计算机中,具有重要的作用,用于存储数据。
(2)STM32中的GPIO口
GPIO(general porpose intput output):通用输入输出端口的简称。可以通过软件控制其输出和输入。stm32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通信,控制以及数据采集的功能。
Cortex‐M3 支持4GB 存储空间。整块4G存储器开始地址标为0x0000_0000,结束地址为0xFFFF_FFFF,地址的位数是32位,那么2^32=4,294,967,296。
由于一个基本的存储单元是8bits即1Byte(每个地址对应一个存储单元,这样如果只是访问某一bit就要使用位操作,或者使用位带操作),因此4,294,967,296/1024=4,194,304KB,4,194,304/1024=4096MB,4094/1024=4GB。
这4GB的存储空间被划分成8个块,每一块用来与特定功能完成映射。映射关系如图所示。
每个寄存器都是32bit,占用4个Byte即4个存储单元。可以把寄存器看作一个特殊的单元,一个这样的单元占32bit,只要找到这个单元的起始地址就可以对其进行操作。
其映射地址 = 外设总基地址(块基地址)+ 总线相对于外设总基地址的偏移 + 具体外设基地址相对于总线基地址的偏移 + 寄存器相对于具体外设基地址的偏移。
2、LED流水灯
(1)配置操作
1.配置时钟使能。
因为流水灯要操作的引脚都是在GPIO端口的,所以根据系统结构图,属于AHB总线,所以所要用的端口的复位和时间控制都受RCC控制。
再看寄存器组起始地址表,可以看到RCC的地址范围,且可以看到要控制的寄存器都是在APB2总。
从上面发现复位和时钟控制的起始地址为0x4002 1000,
再翻到这里发下偏移量为0x18,所以该寄存器的地址为0x4002 1018
上图表格表示的寄存器里各位的含义,比如第三位也就是2那个位置为1时,就是GPIOA的时钟开启了。这时我们就可以用代码表达出来了。
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018) #时钟使能寄存器
RCC_AP2ENR|=1<<2; //开启APB2-GPIOA外设时钟使能
2.配置端口配置寄存器
可以发现上面的时钟使能寄存器开启时钟是针对一个区域的,并不能确定引脚,而这个寄存器就是确定引脚的,端口配置寄存器有两个,分别为端口配置低寄存器(CRL)和端口配置高寄存器(CRH)。
每四位配置一个端口,如11 01,11就是选择开启功能,01就是选择模式和确定最大速度,但有一点不一样,低寄存器的偏移地址为0x00,高寄存器的偏移地址为0x04。
以PA7为示例,相应端口配置器GPIOA_CRL地址为GPIOA的基址+上偏移量
在开头第二张图的那个位置可以找到GPIOA的地址为0x40010800, 低寄存器偏移如上图所示为0
以PA7口为输出口为例,就是要改变的是红色方框里的内容,
根据它下方表格里的内容得知要将他设置为推挽输出,输出模式最大速度为2MHz就是将它赋为0010,转换进制就是2,所以即将地址0x40010800的最前头一位赋为2,代码如下
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
GPIOA_CRL=0x20000000; //PA7推挽输出,2Mhz
3.配置端口输出寄存器(ORD)
可以看到偏移量为0xc,所以该寄存器的地址等于端口的基址加上偏移量,在相应的位赋值可以控制输出电压,0为低电压,1为高电压,以pa7引脚为例子,想要输出高电压,就需要在第八位赋1。
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
GPIOA_ORD|=1<<7; //设置初始灯为亮
(2)代码
1.使用A7、B9、C15端口
//--------------APB2使能时钟寄存器------------------------
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
//----------------GPIOA输出寄存器 ------------------------
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
//----------------GPIOB输出寄存器 ------------------------
#define GPIOB_ORD *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
//----------------GPIOC输出寄存器 ------------------------
#define GPIOC_ORD *((unsigned volatile int*)0x4001100C)
//-------------------延时函数-----------------------
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
//A7高电平,B9、C15低电平
void A_LED_LIGHT(){
GPIOA_ORD=0x0<<7;
GPIOB_ORD=0x1<<9;
GPIOC_ORD=0x1<<15;
}
//B9高电平,A7、C15低电平
void B_LED_LIGHT(){
GPIOA_ORD=0x1<<7;
GPIOB_ORD=0x0<<9;
GPIOC_ORD=0x1<<15;
}
//C15高电平,A7、B9低电平
void C_LED_LIGHT(){
GPIOA_ORD=0x1<<7;
GPIOB_ORD=0x1<<9;
GPIOC_ORD=0x0<<15;
}
int main()
{
int j=100;
//使能GPIOA、GPIOB、GPIOC端口时钟
RCC_AP2ENR|=1<<2;
RCC_AP2ENR|=1<<3;
RCC_AP2ENR|=1<<4;
GPIOA_CRL&=0x0FFFFFFF; //清除该位原来的设置
GPIOA_CRL|=0x20000000; //A7推挽输出,2Mhz
GPIOA_ORD|=0x1<<7; //设置初始灯为灭
GPIOB_CRH&=0xFFFFFF0F; //清除该位原来的设置
GPIOB_CRH|=0x00000020; //B9推挽输出,2Mhz
GPIOB_ORD|=0x1<<9; //设置初始灯为灭
GPIOC_CRH&=0x0FFFFFFF; //清除该位原来的设置
GPIOC_CRH|=0x20000000; //C15推挽输出,50Mhz
GPIOC_ORD|=0x1<<15; //设置初始灯为灭
while(j)
{
A_LED_LIGHT();
Delay_ms(10000);
B_LED_LIGHT();
Delay_ms(10000);
C_LED_LIGHT();
Delay_ms(10000);
}
}
寄存器LED
2.使用A7、B9、C13(stm32自带LED灯)
#include "stm32f10x.h"
//--------------APB2???????------------------------
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA????? ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
//----------------GPIOB????? ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ORD *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC????? ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ORD *((unsigned volatile int*)0x4001100C)
//-------------------???????-----------------------
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ORD=0x0<<7;
GPIOB_ORD=0x1<<9;
//GPIOC_ORD=0x1<<15;
GPIOC->ODR = 0x00002000;
}
void B_LED_LIGHT(){
GPIOA_ORD=0x1<<7;
GPIOB_ORD=0x0<<9;
//GPIOC_ORD=0x1<<15;
GPIOC->ODR = 0x00002000;
}
void C_LED_LIGHT(){
GPIOA_ORD=0x1<<7;
GPIOB_ORD=0x1<<9;
//GPIOC_ORD=0x0<<15;
GPIOC->ODR = 0x00002000;
}
void D_LED_LIGHT(){
GPIOA_ORD=0x1<<7;
GPIOB_ORD=0x1<<9;
//GPIOC_ORD=0x1<<15;
GPIOC->ODR = 0x00000000;
}
int main()
{
int j=100;
RCC_AP2ENR|=1<<2;
RCC_AP2ENR|=1<<3;
RCC_AP2ENR|=1<<4;
//????????? RCC_APB2ENR|=1<<3|1<<4;
GPIOA_CRL&=0x0FFFFFFF;
GPIOA_CRL|=0x20000000;
GPIOA_ORD|=0x1<<7;
GPIOB_CRH&=0xFFFFFF0F;
GPIOB_CRH|=0x00000020;
GPIOB_ORD|=0x1<<9;
/*
GPIOC_CRH&=0x0FFFFFFF;
GPIOC_CRH|=0x20000000;
GPIOC_ORD|=0x1<<15;
*/
GPIOC->CRH = 0x00300000;
GPIOC->ODR = 0x00002000;
while(j)
{
A_LED_LIGHT();
Delay_ms(10000000);
B_LED_LIGHT();
Delay_ms(10000000);
C_LED_LIGHT();
Delay_ms(10000000);
D_LED_LIGHT();
Delay_ms(10000000);
}
}
寄存器LED2
二、基于STM32固件库的流水灯
1、固件库
(1)新建文件
(2)引入固件库
将固件库复制到library文件夹中
后再在Keil中将添加的文件加入进Library中。
后将这三个文件添加到User文件中
然后将USE——STDPERIPH——DRIVER添加到魔术棒的C/C++的Define栏目中。
具体步骤可见 江协科技的STM32入门教程中新建工程视频1。
2、 LED流水灯
(1)代码
通过查看各个固件文件,查找函数完成对各个接口的操作,完成一下代码。
//Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
//Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
//main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
//设置时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
//定义结构体
GPIO_InitTypeDef GPIO_InitStructure;
//将输出模式设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
//输出点为a0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
//或使用GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 |GPIO_Pin_7;
//输出速度为50HZ
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//以上完成结构体GPIO_InitStructure的参数配置
//用结构体完成对GPIO的配置
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_Init(GPIOC,&GPIO_InitStructure);
while(1)
{
//GPIO_WritBit,对单个端口进行写入、消除控制,
//使用GPIOx,GPIO_Pin_0-16分别表示x0至x16
//Bit_RESET为亮灯,Bit_SET为熄灭
GPIO_WriteBit(GPIOA,GPIO_Pin_7,Bit_RESET);
GPIO_WriteBit(GPIOB,GPIO_Pin_9,Bit_SET);
GPIO_WriteBit(GPIOC,GPIO_Pin_15,Bit_SET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_7,Bit_SET);
GPIO_WriteBit(GPIOB,GPIO_Pin_9,Bit_RESET);
GPIO_WriteBit(GPIOC,GPIO_Pin_15,Bit_SET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_7,Bit_SET);
GPIO_WriteBit(GPIOB,GPIO_Pin_9,Bit_SET);
GPIO_WriteBit(GPIOC,GPIO_Pin_15,Bit_RESET);
Delay_ms(500);
}
}
3、使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形
使用keil的软件仿真功能中的逻辑分析仪查看波形,从而更快地定位到问题所在,进而解决问题。
1、首先,设置options for target
Debug页的设置:
2、点击Debug,进入调试界面
3、选择逻辑分析仪
4、选择要观察的引脚
①点击Setup Logic Analyzer
②添加要观察的引脚
在新建的时候直接输入 PORTA.8 代表PA8口,输入完之后按回车键,软件会自动变成位定义。
5、运行程序
6、观察波形,把光标移动到逻辑分析仪显示波形的区域,上下滚动滑轮,就可以放大和缩小波形:
由此波形图可知,周期差不多为2s
三、参考:
【LinZJ0423】STM32F103寄存器方式点亮LED流水灯
【39度C】STM32 GPIO的配置寄存器(CRL、CRH)快速学习
【普通网友】利用KEIL的软件仿真的逻辑分析仪观察GPIO的波形和串口输出的波形