单片机学习小组
直播中

h1654155275.5753

7年用户 1100经验值
私信 关注

如何利用STM32F103寄存器方式点亮LED流水灯?

如何利用STM32F103寄存器方式点亮LED流水灯?

回帖(1)

李远

2022-2-8 14:40:39
  前言:
  以本文章主要讲解以正点原子STM32mini板(STM32F103RC)+面板板+3只红绿蓝LED 搭建威廉希尔官方网站 ,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁。与STM32F103系列芯片的地址映射和寄存器映射原理等
  注:
         本篇博客是基于正点原子STM32mini开发板(STM32F103RC)所作,不同的32板子可能会有所不同,注意区分。


一、stm32芯片寄存器与GPIO端口简介

   寄存器是 CPU 内部的构造,它主要用于信息的存储。
1.地址映射和寄存器映射原理

      存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射。
寄存器映射:
      每个寄存器都是32bit,占用4个Byte即4个存储单元。可以把寄存器看作一个特殊的单元,一个这样的单元占32bit,只要找到这个单元的起始地址就可以对其进行操作。
      其映射地址 = 外设总基地址(块基地址)+ 总线相对于外设总基地址的偏移 + 具体外设基地址相对于总线基地址的偏移 + 寄存器相对于具体外设基地址的偏移。
2.GPIO简介与工作模式

GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚。
GPIO的工作模式主要有八种:4种输入方式,4种输出方式。
分别为输入浮空,输入上拉,输入下拉,模拟输入;
输出方式为开漏输出,开漏复用输出,推挽输出,推挽复用输出。
(1)GPIO_Mode_AIN 模拟输入 (应用ADC模拟输入,或者低功耗下省电)
(2)GPIO_Mode_IN_FLOATING 浮空输入 (浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
(3)GPIO_Mode_IPD 下拉输入 (IO内部下拉电阻输入)
(4)GPIO_Mode_IPU 上拉输入 (IO内部上拉电阻输入)
(5)GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
(6)GPIO_Mode_Out_PP 推挽输出 (推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
(7)GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
(8)GPIO_Mode_AF_PP 复用推挽输出 (片内外设功能(TX1,MOSI,MISO.SCK.SS))
3.输入输出简介

      
  输入: 进行数据的采集,外部威廉希尔官方网站 通过IO口输入模拟量,然后通过“TTL肖特基触发器”(肖特基触发器是将相对缓慢变化的模拟信号变成矩形信号,便于后面读取),进入输入数据寄存器,最后就能给CPU读取数据。
  输出: GPIO的输出与51的 IO口是差不多的概念,都是输出高、低电平来控制外部威廉希尔官方网站 :
  处理过程:CPU下达输出高或低电平指令,指令配置“位设置/清除寄存器(GPIOx_BSRR)”(设置就是“1”高电平,清除就是“0”低电平),再由位寄存器配置输出数据寄存器(GPIOx_ODR),经过一个选择器(选择是一般输出还是复用功能输出),然后进行输出控制,控制是什么模式:推挽、开漏或者关闭,然后输出高或低电平到IO口。
  下面是正点原子手册上的一些介绍:    

    STM32  的每个  IO  端口都有  7  个寄存器来控制。他们分别是:配置模式的  2  个  32  位的端口
  配置寄存器  CRL  和  CRH ; 2  个  32  位的数据寄存器  IDR  和  ODR ; 1  个  32  位的置位 / 复位寄存器
  BSRR ;一个  16  位的复位寄存器  BRR ; 1  个  32  位的锁存寄存器  LCKR ;这里我们仅介绍常用 的
  几个寄存器,我们常用的  IO  端口寄存器只有  4  个: CRL 、 CRH 、 IDR 、 ODR 。
        CRL  和  CRH  控制着每个  IO  口的模式及输出速率。
  STM32 的 IO 口位配置表如表
接下来我们看看端口低配置寄存器 CRL 的描述
该寄存器的复位值为0X4444 4444,从上图可以看到,复位值其实就是配置端口为浮空输入模式。从上图还可以得出:STM32 的CRL控制着每组IO端口(A~G)的低8位的模式。 每个IO端口的位占用CRL的 4 个位,高两位为CNF,低两位为MODE。这里我们可以记住几个常用的配置,比如0X0表示模拟输入模式(ADC 用)、0X3表示推挽输出模式(做输出口用,50M速率)、0X8表示上/下拉输入模式(做输入口用)、0XB表示复用输出(使用IO口的第二功能,50M速率)。
   CRH的作用和CRL完全一样,只是CRL控制的是低8位输出口,而CRH控制的是高8位输出口。这里我们对CRH就不做详细介绍了。
   给个实例,比如我们要设置PORTA的8位为上拉输入,13位为复用输出。代码如下:
GPIOC->CRH&=0XFFF00FFF;//清掉这 2 个位原来的设置,同时也不影响其他位的设置GPIOC->CRH|=0X00038000; //PC11 输入,PC12 输出GPIOC->ODR=1<<11; //PC11 上拉 通过这3句话的配置,我们就设置了PA8为上拉输入,PA13复用输出。
   IDR是一个端口输入数据寄存器,只用了低16位。该寄存器为只读寄存器,并且只能以16位的形式读出。该寄存器各位的描述如图所示:
要想知道某个IO口的状态,你只要读这个寄存器,再看某个位的状态就可以了。使用起来是比较简单的。
   ODR是一个端口输出数据寄存器,也只用了低16位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前IO口的输出状态。而向该寄存器写数据,则可以控制某个IO口的输出电平。该寄存器的各位描述如图所示:
具体更多可以去翻阅正点原子stm32库函数不完全手册。
4.GPIO初始化步骤

第一步:使能GPIOx口的时钟
第二步:指明GPIOx口的哪一位,这一位的速度大小以及模式
第三步:调用GPIOx初始化函数进行初始化
第四步:调用GPIO-SetBits函数,进行相应位的置位

5.实例


1.对于单个GPIO口的初始化如下
GPIO_InitTypeDef GPIO_InitStructure;


第一步:使能GPIOA的时钟:


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
第二步:设置GPIOA参数:输出OR输入,工作模式,端口翻转速率


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_6| GPIO_Pin_7| GPIO_Pin_8; //设定要操作的管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
第三步:调用GPIOA口初始化函数,进行初始化。


GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA
第四步:调用GPIO-SetBits函数,进行相应为的置位。


GPIO_SetBits(GPIOA,GPIO_Pin_0); //输出高
2.对于多个GPIO口的初始化如下
GPIO_InitTypeDef GPIO_InitStructure;
第一步:使能GPIOA,GPIOE的时钟:


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
第二步:设置GPIOA,GPIOE参数:输出OR输入,工作模式,端口翻转速率
第三步:调用GPIOA口初始化函数,进行初始化。
第四步:调用GPIO-SetBits函数,进行相应为的置位。


▶把第二、三、四步合并分别设置GPIOA和GPIOE
先设置GPIOA


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 第四个口,PA4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOA,&GPIO-InitST); //根据设定参数初始化GPIOA
GPIO_SetBits(GPIOA,GPIO_Pin_4); //输出高
再设置GPIOE


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 第三个口,PE3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOE,&GPIO-InitST); //根据设定参数初始化GPIOE
GPIO_SetBits(GPIOE,GPIO_Pin_3); //输出高
二、软件设计
1.实验原理
51单片机的点灯是,通过控制寄存器将片外引脚(我们称之为IO口)拉低拉高,输出高低电平,以控制LED亮灭。
其过程:单片机给指令->控制寄存器->给IO口电平->控制LED亮灭


stm32的点灯则是,通过使能外设GPIO时钟,发出指令给外设GPIO,外设GPIO收到指令后,着手配置自己的寄存器,然后给IO口模式,让其实现各种功能。
其过程:CPU给指令->GPIO收到指令->配置内部寄存器->配置IO口模式(注意是模式)->控制LED亮灭


2.代码编写
1.配置寄存器
led.h



#ifndef __LED_H
#define __LED_H         
#include "sys.h"
   
//LED端口定义
#define LED0 BIT_ADDR(GPIOB_ODR_Addr,6)   // PB6输出
#define LED1 BIT_ADDR(GPIOC_ODR_Addr,6)          // PC6输出
#define LED2 BIT_ADDR(GPIOD_ODR_Addr,2)          // PD2输出

void LED_Init(void);        //初始化                                                     
#endif
led.c



#include "sys.h"   
#include "led.h"

//初始化PB6、PC6和PD2为输出口,并使能这3个口的时钟
//LEDIO初始化
void LED_Init(void)
{
        RCC->APB2ENR|=1<<3;    //使能PORTB时钟
        RCC->APB2ENR|=1<<4;    //使能PORTC时钟
        RCC->APB2ENR|=1<<5;    //使能PORTD时钟
       
        GPIOB->CRL&=0XF0FFFFFF; //PB6清零
        GPIOB->CRL|=0X03000000; //PB6推挽输出           
    GPIOB->ODR|=1<<6;       //PB6输出高
       
        GPIOC->CRL&=0XF0FFFFFF; //PC6清零
        GPIOC->CRL|=0X03000000; //PC6推挽输出
        GPIOC->ODR|=1<<6;      //PC6输出高
       
        GPIOD->CRL&=0XFFFFF0FF; //PD2清零
        GPIOD->CRL|=0X00000300;//PD2推挽输出         
    GPIOD->ODR|=1<<2;      //PD2输出高
                                                                                          
}
2.主函数编写
对于主函数的编写,我们首先需要编写一个简单的延时函数delay,控制LED轮流电亮。在主函数中,我们用一个while死循环保证三个LED灯可以一直轮流交替亮。对于如何控制LED灯的亮灭,我们用到的是BIT_ADDR(GPIOX_ODR_Addr,n)函数来控制输出口的电平,从而达到控制LED的亮灭的功能。


test.c


#include "sys.h"               
#include "led.h"

void delay(unsigned int i)   //简单延时函数
{
        unsigned char j;               
        unsigned char k;
        for(;i>0;i--)
                for(j=500; j>0; j--)
               for(k =200; k>0; k--);
}

int main(void)
{                 

        LED_Init();                                   //初始化与LED连接的硬件接口   
        while(1)
        {
               
                LED0=0;  //灯亮
                LED1=1;  //灯灭
                LED2=1;
                delay(20);  //延时
                LED0=1;
                LED1=0;
                LED2=1;
                delay(20);
            LED0=1;
                LED1=1;
                LED2=0;
                delay(20);
               
        }         
}


3.程序的烧录
用对应的线接stm32板子的u***232接口使之与pc连接。


这里我用的是FlyMcu。点击搜索串口,我的串口是COM5  串口波特率则可以通过bps那里设置


对于STM32F103,可以设置为最高:460800,而如果是 F4,则建议最高设置为:76800 即可。


DTR的低电平复位,RTS高电平进BootLoader


效验与编译后执行勾选,点击开始编程输出正常时烧录完成



三、硬件部分

1.材料与GPIO口的选择

材料:正点原子stm32mini开发板+杜邦线+红、黄、绿led灯+面包板


GPIO口选择GPIOB、GPIOC、GPIOD这3个端口控制LED灯具体为PB6、PC6、PD2(可以自行选择,只需要稍微修改代码即可)

2.连线

按着stm32手册连线即可
LED灯的短脚连接IO口,长脚连接3.3V(注:尽量不要连接5V的端口,5V的端口没有电阻保护,容易烧坏LED灯)


3.结果展示




四、汇编语言实现流水灯


四、汇编语言实现流水灯
1.代码部分

RCC_APB2ENR EQU 0x40021018

GPIOA_CRH EQU   0x40010804
GPIOA_ODR EQU   0x4001080C
                                    
GPIOB_CRL EQU   0x40010C00    ;寄存器映射
GPIOB_ODR EQU   0x40010C0C       
       
GPIOC_CRH EQU   0x40011004
GPIOC_ODR EQU   0x4001100C       
       
       
Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
                                        ;NOINIT: = NO Init,不初始化。READWRITE : 可读,可写。ALIGN =3 : 2^3 对齐,即8字节对齐。
Stack_Mem       SPACE   Stack_Size
__initial_sp

                AREA    RESET, DATA, READONLY

__Vectors       DCD     __initial_sp               
                DCD     Reset_Handler              
                    
                    
                AREA    |.text|, CODE, READONLY
                    
                THUMB
                REQUIRE8
                PRESERVE8
                    
                ENTRY
Reset_Handler
               
               
MainLoop                BL LED2_Init
                BL LED2_ON
                BL Delay             ;LED2灯闪烁
                BL LED2_OFF
                BL Delay
                               
                                BL LED1_Init                               
                                BL LED1_ON
                BL Delay             ;LED1灯闪烁
                BL LED1_OFF
                BL Delay
                               
                BL LED3_Init                               
                                BL LED3_ON
                BL Delay            ;LED3灯闪烁
                BL LED3_OFF
                BL Delay
                               
                B MainLoop
                               
            
LED1_Init
                PUSH {R0,R1, LR}        ;R0,R1,LR中的值放入堆栈
               
                LDR R0,=RCC_APB2ENR      ;LDR是把地址装载到寄存器中(比如R0)。
                ORR R0,R0,#0x08         ;开启端口GPIOB的时钟,ORR 按位或操作,01000将R0的第二位置1,其他位不变               
                LDR R1,=RCC_APB2ENR
                STR R0,[R1]             ;STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
                                ;上面一部分汇编代码是控制时钟的
               
               
                LDR R0,=GPIOB_CRL
                ORR R0,R0,#0X00000020   ;GPIOB_Pin_1配置为通用推挽输出;开启的是pb1,所以是2,为0010,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOB_CRL
                STR R0,[R1]
               
                LDR R0,=GPIOB_ODR   
                BIC R0,R0,#0X00000002      ;BIC 先把立即数取反,再按位与
                LDR R1,=GPIOB_ODR          ;GPIOB_Pin_1输出为0;由r1控制ord寄存器
                STR R0,[R1]                 ;将ord寄存器的值变为r0的值
            
                POP {R0,R1,PC}             ;将栈中之前存的R0,R1,LR的值返还给R0,R1,PC


            
LED1_OFF
                PUSH {R0,R1, LR}   
               
                LDR R0,=GPIOB_ODR
                BIC R0,R0,#0X00000002    ;因为是PB1所以对应二进制0010;GPIOB_Pin_1输出为0,LED1熄灭
                            LDR R1,=GPIOB_ODR
                STR R0,[R1]
            
                POP {R0,R1,PC}
            
LED1_ON
                PUSH {R0,R1, LR}   
               
                 LDR R0,=GPIOB_ODR
                ORR R0,R0,#0X00000002    ;GPIOB_Pin_1输出为1,LED1亮
                 LDR R1,=GPIOB_ODR
                STR R0,[R1]
                POP {R0,R1,PC}           


                               

LED2_Init
                PUSH {R0,R1, LR};R0,R1,LR中的值放入堆栈
               
                LDR R0,=RCC_APB2ENR
                ORR R0,R0,#0x04                   ;打开GPIOA的时钟
                LDR R1,=RCC_APB2ENR
                STR R0,[R1]               
               
                LDR R0,=GPIOA_CRH
                ORR R0,R0,#0X00020000   ;GPIOA_Pin_12配置为通用推挽输出
                LDR R1,=GPIOA_CRH
                STR R0,[R1]
               
                LDR R0,=GPIOA_ODR
                BIC R0,R0,#0X00001000   
                LDR R1,=GPIOA_ODR            ;GPIOA_Pin_12输出为0
                STR R0,[R1]
            
                POP {R0,R1,PC}
                               
LED2_OFF
                PUSH {R0,R1, LR}   
               
               LDR R0,=GPIOA_ODR
               BIC R0,R0,#0X00001000        ;GPIOA_Pin_12输出为0,LED2熄灭
                            LDR R1,=GPIOA_ODR
                STR R0,[R1]
            
                POP {R0,R1,PC}
            
LED2_ON
                PUSH {R0,R1, LR}   
               
                LDR R0,=GPIOA_ODR
                ORR R0,R0,#0X00001000     ;GPIOA_Pin_12输出为1,LED2亮
                                 LDR R1,=GPIOA_ODR
                STR R0,[R1]
                               
                                 POP {R0,R1,PC}
                                 

LED3_Init
                PUSH {R0,R1, LR}
               
                LDR R0,=RCC_APB2ENR
                ORR R0,R0,#0x10                    ;打开GPIOC的时钟
                LDR R1,=RCC_APB2ENR
                STR R0,[R1]               
               
                LDR R0,=GPIOC_CRH
                ORR R0,R0,#0X02000000   ;GPIOC_Pin_14配置为通用推挽输出
                LDR R1,=GPIOC_CRH
                STR R0,[R1]
               
                LDR R0,=GPIOC_ODR
                BIC R0,R0,#0X00004000   ;GPIOC_Pin_14输出为0
                LDR R1,=GPIOC_ODR
                STR R0,[R1]
            
                POP {R0,R1,PC}
            
LED3_OFF
                PUSH {R0,R1, LR}   
               
                LDR R0,=GPIOC_ODR
                BIC R0,R0,#0X00004000  ;GPIOC_Pin_14输出为0,LED3熄灭
                            LDR R1,=GPIOC_ODR
                STR R0,[R1]
            
                POP {R0,R1,PC}
            
LED3_ON
                PUSH {R0,R1, LR}   
               
                 LDR R0,=GPIOC_ODR
                ORR R0,R0,#0X00004000   ;GPIOC_Pin_14输出为1,LED3亮
                 LDR R1,=GPIOC_ODR
                STR R0,[R1]
                               
                POP {R0,R1,PC}        
               
Delay
                PUSH {R0,R1, LR}
               
                MOVS R0,#0
                MOVS R1,#0
                MOVS R2,#0
               
DelayLoop0        
                ADDS R0,R0,#1

                CMP R0,#300
                BCC DelayLoop0
               
                MOVS R0,#0
                ADDS R1,R1,#1
                CMP R1,#300
                BCC DelayLoop0

                MOVS R0,#0
                MOVS R1,#0
                ADDS R2,R2,#1
                CMP R2,#15
                BCC DelayLoop0
               
                POP {R0,R1,PC}   

                END



2.实验结果


ps建议:
正点原子和野火的板子和操作手册都很不错的,值得学习,本次代码就是在正点原子所给的资料里面的流水灯代码所改,是新手起步的好教程。面包版和杜邦线也是非常好用的硬件工具,不需要复杂的焊接。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分