STM32F103c8t6系统最小版实现LED流水灯(附工程文件下载)

本次实验主要有以下两个任务,所以本博客也会按照此顺序进行讲解

  1. 学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤(时钟配置、输入输出模式设置、最大速率设置)
  2. 以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝LED 搭建电路,使用GPIO端口控制LED灯,轮流闪烁,间隔时长1秒。

一、实验介绍

1. 试验器材介绍

  1. stm32f103c8t6系统最小版
  2. stlink-v2(根据自己需求也可以采用串口烧入方法)
  3. 面包板
  4. 杜邦线(含公对公、母对母)
  5. 红、绿、黄 三个发光二极管
  6. keil5软件

2. 实验理论介绍

芯片的地址映射和寄存器映射原理

存储器映射

存储器在产家制作完成后是一片没有任何信息的物理存储器,而CPU要进行访存就涉及到内存地址的概念,因此存储器映射就是为物理内存按一定编码规则分配地址的行为。值得注意,存储器映射一般是由产家规定,用户不能随意更改。
在这里插入图片描述

寄存器映射

寄存器映射是在存储器映射的基础上进行的。

以STM32为例,操作硬件本质上就是操作寄存器。在存储器片上外设区域,四字节为一个单元,每个单元对应不同的功能。当我们控制这些单元时就可以驱动外设工作,我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元。但若每次都是通过这种方式访问地址,不好记忆且易出错。这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名实质上就是寄存器名字。给已分配好地址(通过存储器映射实现)的有特定功能的内存单元取别名的过程就叫寄存器映射。

如何找到芯片中寄存器的地址

如果想要找到芯片中某个寄存器的地址的话,就需要查看数据手册,并通过一定的计算,就可以找到寄存器的地址啦。
寄存器的地址分为基地址偏移地址
详细计算方法可以参考博客:STM32寄存器的简介、地址查找,与直接操作寄存器

在这里插入图片描述

GPIO口初始化设置
GPIO口介绍

每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),
两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器
根据数据手册中列出的每个I/O端口的特定硬件特征,GPIO端口的每个位可以由软件分别配置成多种模式。

  • 输入浮空
  • 输入上拉
  • 输入下拉
  • 模拟输入
  • 开漏输出
  • 推挽式输出
  • 推挽式复用功能
  • 开漏复用功能
GPIO口在哪里

参考芯片手册,在stm32f103c8t6中有A B C D E F G七个GPIO口,由上文提到的地址计算方法,可计算出寄存器地址,在通过储存器原理图可找到接口。
在这里插入图片描述
关于GPIO的更多地址资料可参考芯片的数据手册
(下载资料都以网盘的形式放在了文末)
在这里插入图片描述

初始化步骤

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

这篇博客中有详尽的关于GPIO初始化的实例与代码,供大家参考
STM32入门-GPIO初始化步骤

二、实验操作

实验操作分三部分

  • 软件代码编写
  • 硬件接线
  • 烧入调试

其实网上的流水灯代码资料是比较多的,所以我可能对于代码的讲解不会很详尽,对于硬件接线一些上手操作可能会细点。

1. C语言库函数版

环境配置(库函数之类的东西)

这个里面涉及到了一个keil模块化编程的事,说着比较麻烦,但是学会了又很简单,建议百度找找其他博客学一下。

C语言源码

此程序是将A5 A6 A7作为输出口的

#include "stm32f10x.h"  

void Delay(u32 count)  	 //延迟函数,参数为设置的延迟时间(不准确,具体时间可由keil的示波器观察)
{   
	u32 i=0;   
	for(;i<count;i++);  
} 

int main(void)  
{    
	GPIO_InitTypeDef  GPIO_InitStructure;   		//设置PA5、PA6、PA7为输出口
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);     
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7; 	//GPIO口的初始化操作
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_Init(GPIOA, &GPIO_InitStructure); 
	
	GPIO_SetBits(GPIOA,GPIO_Pin_5);   				//初始化GPIO为高电平,LED低电平亮,高电平灭
	GPIO_SetBits(GPIOA,GPIO_Pin_6); 
	GPIO_SetBits(GPIOA,GPIO_Pin_7); 
	
	while(1) 										//永真循环,进行不断的流水灯循环闪烁
	{
		GPIO_ResetBits(GPIOA,GPIO_Pin_5);			
		GPIO_SetBits(GPIOA,GPIO_Pin_6);  
		GPIO_SetBits(GPIOA,GPIO_Pin_7);  		
		Delay(4600000);Delay(4600000);Delay(4600000);	//流水灯中,由延时函数控制时间间隔
		GPIO_SetBits(GPIOA,GPIO_Pin_5);
		GPIO_ResetBits(GPIOA,GPIO_Pin_6);  
		GPIO_SetBits(GPIOA,GPIO_Pin_7);
		Delay(4600000);Delay(4600000);Delay(4600000);
		GPIO_SetBits(GPIOA,GPIO_Pin_5);
		GPIO_SetBits(GPIOA,GPIO_Pin_6);  
		GPIO_ResetBits(GPIOA,GPIO_Pin_7);
		Delay(4600000);Delay(4600000);Delay(4600000);
	}  
}

接线

  1. 连接st-link与stm32芯片:按照下图对应连接
    在这里插入图片描述

  2. 将芯片插在面包插在面包板上
    在这里插入图片描述

  3. 插入led二极管:二极管长脚为正级、短脚为负级,所以长脚应该接入电源,短脚与stm32芯片相接
    在这里插入图片描述

  4. 利用stm32为面包板供电(也可用面包板的电源模块进行供电,我嫌麻烦,所以就直接用芯片供电了)
    在这里插入图片描述

程序烧入芯片

  1. 编译程序
    在这里插入图片描述

  2. 配置st-link:在魔法棒中配置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  3. 烧入程序:点击LOAD图标烧入
    在这里插入图片描述

结果观察

可以观察到三个led灯依次闪烁,时间间隔约为一秒
在这里插入图片描述

2. 汇编语言版

汇编语言我不会,学着太麻烦了,所以参考了另外一位博主的代码,尝试理解了一下,并把它的代码烧入了试试,也是可行的,相当于是验证性实验了,代码写得很详细。
注意:此代码是将PA7,PB9,PC15作为输出的,与上个C语言代码不同
具体工程配置、具体讲解可参考他的博客: STM32F103C8T6实现流水灯(c语言和汇编两个版本)

RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,时钟,0x40021018为时钟地址
GPIOC_CRH EQU 0x40011004;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOC_ORD EQU 0x4001100c;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOA_CRL EQU 0x40010800;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOA_ORD EQU 0x4001080C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOB_CRH EQU 0x40010C04;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04 
GPIOB_ORD EQU 0x40010C0C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
Stack_Size EQU  0x00000400;栈的大小
;分配一个stack段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
                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               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                    
                    
                AREA    |.text|, CODE, READONLY
                    
                THUMB
                REQUIRE8
                PRESERVE8
                    
                ENTRY
Reset_Handler 
				bl LED_Init;bl:带链接的跳转指令。当使用该指令跳转时,当前地址(PC)会自动送入LR寄存器
MainLoop        BL LED_ON_C
                BL Delay
                BL LED_OFF_C
                BL Delay
				BL LED_ON_A
                BL Delay
                BL LED_OFF_A
                BL Delay
				BL LED_ON_B
                BL Delay
                BL LED_OFF_B
                BL Delay
                
                B MainLoop;B:无条件跳转。
LED_Init;LED初始化
                PUSH {R0,R1, LR};R0,R1,LR中的值放入堆栈
                
                LDR R0,=RCC_APB2ENR;LDR是把地址装载到寄存器中(比如R0)。
                ORR R0,R0,#0x1c;ORR 按位或操作,11100将R0的第二位置1,其他位不变
                LDR R1,=RCC_APB2ENR
                STR R0,[R1];STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
				;上面一部分汇编代码是控制时钟的
				
				
                ;初始化GPIOA部分
                LDR R0,=GPIOA_CRL
                BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与
                LDR R1,=GPIOA_CRL
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOA_CRL
                ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOA_CRL
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x80; 二进制为0b1000 0000 ,第7位就是a7引脚的输出电压
                LDR R1,=GPIOA_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
				
				 ;初始化GPIOB部分
                LDR R0,=GPIOB_CRH
                BIC R0,R0,#0xffffff0f;BIC 先把立即数取反,再按位与,用的是b9,所以把第二位置零
                LDR R1,=GPIOB_CRH
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOB_CRH
                ORR R0,#0x00000020;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOB_CRH
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x200; 二进制为0b10 0000 0000,第16位就是b9引脚的输出电压
                LDR R1,=GPIOB_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
				
				 ;初始化GPIOC部分
                LDR R0,=GPIOC_CRH
                BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与,就是将三十二位全部置零
                LDR R1,=GPIOC_CRH
                STR R0,[R1]
                ;上面的代码是初始化CPIOC_CRH
                LDR R0,=GPIOC_CRH
                ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
                LDR R1,=GPIOC_CRH
                STR R0,[R1]
				;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
                ;将PC15置1
                MOV R0,#0x8000; 二进制为0b1000 0000 0000 0000,第16位就是c15引脚的输出电压
                LDR R1,=GPIOC_ORD ;由r1控制ord寄存器
                STR R0,[R1] ;将ord寄存器的值变为r0的值
             
                POP {R0,R1,PC};将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED_ON_A;亮灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x00 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {R0,R1,PC}
             
LED_OFF_A;熄灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x80 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                POP {R0,R1,PC}  
LED_ON_B;亮灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {R0,R1,PC}
             
LED_OFF_B;熄灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x200 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                POP {R0,R1,PC}  
LED_ON_C;亮灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x0000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
                LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1];将r0的值赋予在GPIOC_ORD中
             
                POP {R0,R1,PC}
             
LED_OFF_C;熄灯
                PUSH {R0,R1, LR}    
                
                MOV R0,#0x8000 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
                LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
                STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
             
                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,#330
                BCC DelayLoop0
                
                MOVS R0,#0
                ADDS R1,R1,#1
                CMP R1,#330
                BCC DelayLoop0 ;无进位

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

结果观察

在这里插入图片描述

三、总结

本次实验用C语言和汇编语言的方式实现了,主要是练习了如何翻看芯片手册来获取想要的信息,也从一定程度上掌握了程序的烧入、调试方法,这也是自己的第一次硬件尝试,终于是迈入了第一只脚进去,以前一直对这个东西有种恐惧,做完了之后也发现没有那么难,也产生了点兴趣。
虽然确实学的过程有点煎熬,以后慢慢来吧。

资料下载连接(含汇编语言、C语言流水灯工程,芯片资料手册、数据手册):

链接:https://pan.baidu.com/s/1IWem14AVSB9_i4AyY7VNdQ
提取码:z6kw

四、参考文献

【嵌入式系统】存储器 映射与寄存器映射原理
STM32寄存器的简介、地址查找,与直接操作寄存器
STM32入门-GPIO初始化步骤
STM32F103C8T6实现流水灯(c语言和汇编两个版本)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值