STM32学习-0点亮一个LED

0点亮一个LED

程序树
在这里插入图片描述

  • 组 FWLib 下面存放的是 ST 官方提供的固件库函数,里面的函数我们可以根据需要添加和删除,但是一定要注意在头文件 stm32f10x_conf.h 文件中注释掉删除的源文件对应的头文件,这里面的文件内容用户不需要修改。
  • 组 CORE 下面存放的是固件库必须的核心文件和启动文件。这里面的文件用户不需要修改。
  • 组SYSTEM是ALIENTEK提供的共用代码,这些代码的作用和讲解在第五章都有讲解,大家可以翻过去看下。
  • 组 HARDWARE 下面存放的是每个实验的外设驱动代码,他的实现是通过调用 FWLib下面的固件库文件实现的,比如 led.c 里面调用 stm32f10x_gpio.c 里面的函数对 led 进行初始化,这里面的函数是讲解的重点。后面的实验中可以看到会引入多个源文件。
  • 组 USER 下面存放的主要是用户代码。但是 system_stm32f10x.c 文件用户不需要修改,同时 stm32f10x_it.c 里面存放的是中断服务函数,这两个文件的作用在 3.1 节有讲解,大家可以翻过去看看。Main.c 函数主要存放的是主函数了,这个大家应该很清楚。

在这里插入图片描述

主程序代码

#include "led.h"
#include "delay.h"
#include "sys.h"
int main(void)
{
    delay_init(); //延时函数初始化
    LED_Init(); //初始化与 LED 连接的硬件接口
    while(1)
    { 
        LED0=~LED0;
        delay_ms(1000); //延时 300ms
    }
}

延时函数初始化

delay_init(); //延时函数初始化

具体的实现方式为

#include "delay.h"
#include "sys.h"
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_UCOS
#include "includes.h"					//ucos 使用	  
#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/2
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!

//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.

//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
// 	 
static u8  fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数
#ifdef OS_CRITICAL_METHOD 	//如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{				   
	OSIntEnter();		//进入中断
    OSTimeTick();       //调用ucos的时钟服务程序               
    OSIntExit();        //触发任务切换软中断
}
#endif

//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()	 
{

#ifdef OS_CRITICAL_METHOD 	//如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
	u32 reload;
#endif
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;	//为系统时钟的1/8  
	 
#ifdef OS_CRITICAL_METHOD 	//如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.
	reload=SystemCoreClock/8000000;		//每秒钟的计数次数 单位为K	   
	reload*=1000000/OS_TICKS_PER_SEC;//根据OS_TICKS_PER_SEC设定溢出时间
							//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
	fac_ms=1000/OS_TICKS_PER_SEC;//代表ucos可以延时的最少单位	   
	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
	SysTick->LOAD=reload; 	//每1/OS_TICKS_PER_SEC秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    
#else
	fac_ms=(u16)fac_us*1000;//非ucos下,代表每个ms需要的systick时钟数   
#endif
}								    

#ifdef OS_CRITICAL_METHOD	//使用了ucos
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;	//LOAD的值	    	 
	ticks=nus*fac_us; 			//需要的节拍数	  		 
	tcnt=0;
	told=SysTick->VAL;        	//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;//时间超过/等于要延迟的时间,则退出.
		}  
	}; 									    
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{	
	if(OSRunning==TRUE)//如果os已经在跑了	    
	{		  
		if(nms>=fac_ms)//延时的时间大于ucos的最少时间周期 
		{
   			OSTimeDly(nms/fac_ms);//ucos延时
		}
		nms%=fac_ms;				//ucos已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));	//普通方式延时,此时ucos无法启动调度.
}
#else//不用ucos时
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; //时间加载	  		 
	SysTick->VAL=0x00;        //清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数	 
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 
void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;           //清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	  	    
} 
#endif

主要是利用系统时钟SysTick实现的。该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外部时钟,如果使用了 ucos,那么还会根据 OS_TICKS_PER_SEC 的配置情况,来配置SysTick 的中断时间,并开启 SysTick 中断。

关于delay_us()与delay_ms()函数

利用SysTick时钟实现的。
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);这一句把SysTick的时钟选择外部时钟,这里需要注意的是:SysTick 的时钟源自 HCLK 的 8 分频,假设我们外部晶振为 8M,然后倍频到 72M,那么 SysTick 的时钟即为 9Mhz,也就是 SysTick 的计数器VAL 每减 1,就代表时间过了 1/9us。所以 fac_us=SystemCoreClock/8000000;这句话就是计算在 SystemCoreClock 时钟频率下延时 1us 需要多少个 SysTick 时钟周期。同理,fac_ms=(u16)fac_us*1000;就是计算延时 1ms 需要多少个 SysTick 时钟周期,它自然是 1us的 1000 倍。初始化将计算出 fac_us 和 fac_ms 的值。在不使用 ucos 的时候:fac_us,为 us 延时的基数,也就是延时 1us,SysTick->LOAD所应设置的值。fac_ms 为 ms 延时的基数,也就是延时 1ms,SysTick->LOAD 所应设置的值。fac_us 为 8 位整形数据,fac_ms 为 16 位整形数据。正因为如此,系统时钟如果不是 8的倍数,则会导致延时函数不准确,这也是我们推荐外部时钟选择 8M 的原因。这点大家要特别留意。
当使用 ucos 的时候,fac_us,还是 us 延时的基数,不过这个值不会被写到SysTick->LOAD 寄存器来实现延时,而是通过时钟摘取的办法实现的(后面会介绍)。而fac_ms 则 代 表 ucos 自 带 的 延 时 函 数 所 能 实 现 的 最 小 延 时 时 间 ( 如OS_TICKS_PER_SEC=200,那么 fac_ms 就是 5ms)。

初始化与 LED 连接的硬件接口

LED_Init(); //初始化与 LED 连接的硬件接口

实现如下

#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED 端口定义
#define LED0 PAout(8) // PA8
#define LED1 PDout(2) // PD2
void LED_Init(void);//初始化
#endif

#include "led.h"
//LED IO 初始化
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE); //使能 PA,PD 端口时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化 GPIOA.8
    GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 输出高
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽输出
    GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽输出 ,IO 口速度为 50MHz
    GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 输出高
}

GPIO口的参数配置

STM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口
可以由软件配置成如下 8 种模式:
1、输入浮空
2、输入上拉
3、输入下拉
4、模拟输入
5、开漏输出
6、推挽输出
7、推挽式复用功能
8、开漏复用功能
每个 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。STM32 的很多 IO 口都是 5V 兼容的,这些 IO 口在与 5V 电平的外设连接的时候很有优势,具体哪些 IO 口是 5V 兼容的,可以从该芯片的数据手册管脚描述章节查到(I/O Level 标 FT 的就是 5V 电平兼容的)。

由于STM32有很多寄存器,为了提高效率,需要用不同的时钟来配置IO口。
在使用一个IO口之前,需要对IO口的引脚、GPIO口的速度、GPIO口的模式进行配置。
为了方便起见,我们使用结构体进行对参数的赋值,使用指针进行对参数传递。

/* 
  ******************************************************************************
  * @file    stm32f10x_gpio.h
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    11-March-2011
  * @brief   This file contains all the functions prototypes for the GPIO 
  *          firmware library.
  * 
  * 
*/
typedef struct
{
	uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
	                                  This parameter can be any value of @ref GPIO_pins_define */
	
	GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
	                                  This parameter can be a value of @ref GPIOSpeed_TypeDef */
	
	GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
	                                  This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;

能够看出,我们是使用这个函数来进行对GPIO口的参数配置

GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化 GPIOA.8

函数原型

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

用枚举类型enum说明参数

//GPIO的模式选择
typedef enum
{ 
	GPIO_Mode_AIN = 0x0, //模拟输入
	GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
	GPIO_Mode_IPD = 0x28, //下拉输入
	GPIO_Mode_IPU = 0x48, //上拉输入
	GPIO_Mode_Out_OD = 0x14, //开漏输出
	GPIO_Mode_Out_PP = 0x10, //通用推挽输出
	GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
	GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;
//GPIO的传输速度选择
typedef enum
{
	GPIO_Speed_10MHz = 1,
	GPIO_Speed_2MHz,
	GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

枚举类型enum参考

GPIO口的操作

  • 读写GPIO口电平的状态

IDR 是一个端口输入数据寄存器,只用了低 16 位。该寄存器为只读寄存器,并且只能以16 位的形式读出。
要想知道某个 IO 口的电平状态,你只要读这个寄存器,再看某个位的状态就可以了。使用起来是比较简单的。

ODR 是一个端口输出数据寄存器,也只用了低 16 位。该寄存器为可读写,从该寄存器读
出来的数据可以用于判断当前 IO 口的输出状态

BSRR 寄存器是端口位设置/清除寄存器。该寄存器和 ODR 寄存器具有类似的作用,都可
以用来设置 GPIO 端口的输出位是 1 还是 0。下面我们看看该寄存器的描述如下图:
需要注意的是:这个寄存器的高16位是用来给端口置0的,低16位是用来给端口置1的。
在这里插入图片描述
在这里插入图片描述

操作GPIO口的输入\输出电平状态

函数说明返回值使用的寄存器
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)读取输入电平的状态1(Bit_SET)或者 0(Bit_RESET);IDR
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);设置高低电平ODR
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);设置为高电平BSRR 和 BRR
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)设置为低电平BSRR 和 BRR

flag = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取PA5的输入电平状态
GPIO_SetBits(GPIOB, GPIO_Pin_5);   			    //设置为PB5为输出高电平
GPIO_ResetBits (GPIOB, GPIO_Pin_5);             //设置PB5输出低电平

这里我们还是做个概括性的总结,操作步骤为:
1) 使能 IO 口时钟。调用函数为 RCC_APB2PeriphClockCmd()。
2) 初始化 IO 参数。调用函数 GPIO_Init();
3) 操作 IO。操作 IO 的方法就是上面我们讲解的方法。
上面我们讲解了 STM32 IO 口的基本知识以及固件库操作 GPIO 的一些函数方法。

示例-流水灯的实现

参考我的这一篇文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值