流水灯进阶——寄存器&HAL库实现周期流水灯

一、寄存器位移点灯

  根据之前博客中介绍的寄存器/地址映射,在这里采用寄存器位移方法点灯就比较简单了。我们采用的工具仍然是Keil,STM32最小核心板,面包板,若干杜邦线以及LED

【问题描述】

  通过配置STM32的PA5、PB9、PC14端口,使其周期性地输出高低电平,采用通用推挽输出模式,最高输出时钟频率为2Mhz。进而驱动LED灯的亮灭,实现流水灯的功能。

【问题分析】

  1. 首先,要使对应IO口的时钟使能;

  2. 其次,要操作寄存器,一定要知道寄存器是谁,在哪里。因此第二步骤,配置寄存器地址;

  3. 之后,配置端口的输入输出模式;

  4.如果要实现周期性的亮灭,需要加入延迟函数delay();

  5. 最后,编译成功后,将程序烧录到单片机中。

【问题解答】

1.使对应IO口的时钟使能

  经过查阅STM32F103x的芯片手册的发现:复位和时钟控制的起始地址为0x4002 1000,控制GPIO等外设的寄存器为APB2外设时钟,因此加上偏移量0x18,得到使能时钟地址为0x4002 1018

  查询APB外设时钟位定义,发现位2、位3、位4分别使能GPIOA、GPIOB、GPIOC。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  -------------------------------代码如下------------------------------

 //----------------APB2使能时钟寄存器 ---------------------
#define CLKENR  *((volatile unsigned int *)0x4002 1018)

CLKENR|=1<<2|1<<3|1<<4;	  //APB2-GPIOA、GPIOB、GPIOC外设时钟使能

【语句解释】

关键词:volatile

  当地址是IO端口的时候,读写这个地址是不能对它进行缓存的,这是相对于某些嵌入式中有cache才有这个。比如写这个IO端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候再写到io端口,这样就不能使数据及时的写到IO端口,有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到IO端口,从而避免了读写IO端口的延时。

  简单来说:让对volatile 变量的存取不能缓存到寄存器,每次使用时需要重新存取。

2.寄存器地址配置

  查询数据手册,得到GPIOA、GPIOB、GPIOC的起始地址为0x40010800、0x40010C00、0x40011000
在这里插入图片描述
  -------------------------------代码如下------------------------------

//----------------GPIOA寄存器地址 -----------------------
#define GPIOA		*((unsigned volatile int*)0x40010800)
//----------------GPIOB寄存器地址 -----------------------
#define GPIOB    	*((unsigned volatile int*)0x40010C00)
//----------------GPIOC寄存器地址 -----------------------
#define GPIOC		*((unsigned volatile int*)0x40011004)

3.配置输入输出模式以及最高输出速率

  STM32中,用低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。因此在本次实验中用到的三个引脚A5、B9、C14。端口A5需要用低寄存器(GPIOx_CRL)来配置,B9、C14需要高寄存器(GPIOx_CRH)来配置。

  根据数据手册,可得低寄存器(GPIOx_CRL)基于寄存器地址偏移量为0x00,高寄存器(GPIOx_CRH)在寄存器地址偏移量为0x04;输出寄存器基于GPIO寄存器地址偏移量为0x0C

  ---------------------配置寄存器地址代码如下--------------------

//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
#define	GPIOC_ODR		*((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL		*((unsigned volatile int*)0x40010C00)
#define	GPIOC_ODR		*((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)
#define	GPIOC_ODR		*((unsigned volatile int*)0x4001100C)

  根据下面的规则进行输入输出模式配置。
在这里插入图片描述

--------------------配置输入输出模式代码如下------------------

 
	GPIOA_CRL&=0xFF0FFFFF;		//设置位 清零	
	GPIOA_CRL|=0x00200000;		//PA5推挽输出,把第23、22、21、20变为0010
	GPIOA_ODR&=(0<<4);			//设置初始灯为灭

	GPIOB_CRH&=0xFFFFFF0F;		//设置位 清零	
	GPIOB_CRH|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010
 	GPIOB_ODR&=(0<<5);			//设置初始灯为灭
 
	GPIOC_CRH&=0xF0FFFFFF;		//设置位 清零	
	GPIOC_CRH|=0x02000000;		//PC14推挽输出,把第27、26、25、24变为0010
    GPIOC_ODR&=(0<<14);			//设置初始灯为灭

4.编写Delay()函数

  编写Delay()函数如下:

 void Delay()
 {
   u32 i=0;
   for(;i<10000;i++);
 }

5.创建keil工程,并编译

---------------------------完整代码如下---------------------------

#include "stm32f10x.h""

#define CLKENR  *((volatile unsigned int *)0x40021018)

//----------------GPIOA寄存器地址 -----------------------
#define GPIO_A		*((unsigned volatile int*)0x40010800)
//----------------GPIOB寄存器地址 -----------------------
#define GPIO_B    	*((unsigned volatile int*)0x40010C00)
//----------------GPIOC寄存器地址 -----------------------
#define GPIO_C		*((unsigned volatile int*)0x40011004)

//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
#define	LED1		*((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRH		*((unsigned volatile int*)0x40010C04)
#define	LED2		*((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)
#define	LED3		*((unsigned volatile int*)0x4001100C)

 void Delay()
 {
   u32 i=0;
   for(;i<10000;i++);
 }
 int main()
 {
		CLKENR|=1<<2|1<<3|1<<4;	  //APB2-GPIOA、GPIOB、GPIOC外设时钟使能
	 
		GPIOA_CRL&=0xFF0FFFFF;		//设置位 清零	
		GPIOA_CRL|=0x00200000;		//PA5推挽输出,把第23、22、21、20变为0010
		LED1&=(0<<4);			//设置初始灯为灭

	 
		GPIOB_CRH&=0xFFFFFF0F;		//设置位 清零	
		GPIOB_CRH|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010
		LED2&=(0<<5);			//设置初始灯为灭
	 
	 
		GPIOC_CRH&=0xF0FFFFFF;		//设置位 清零	
		GPIOC_CRH|=0x02000000;		//PC14推挽输出,把第27、26、25、24变为0010
		LED3&=(0<<14);			//设置初始灯为灭
	 
    while(1)
    {
    	//LED1
			LED1|=1<<5;		//PB5高电平
			Delay();
			LED1&=(0<<5);		//PB5低电平,因为是置0,所以用按位与
			
			//LED2
			LED2|=1<<9;		//PB9高电平
			Delay();
			LED2&=(0<<9);		//PB5低电平,因为是置0,所以用按位与
			
			//LED3
			LED3|=1<<14;		//PB14高电平
			Delay();
			LED3&=(0<<14);		//PB5低电平,因为是置0,所以用按位与
				
    }
    
 }
 

  进行编译!

在这里插入图片描述

6.软件仿真

  关于软件仿真的设置可以去看博主之前写的文章
  观看GPIOA_5波形:
在这里插入图片描述
在这里插入图片描述

  观看GPIOB_9波形:
在这里插入图片描述
在这里插入图片描述
  观看GPIOC_14波形:
在这里插入图片描述
在这里插入图片描述
  每个灯亮的间隔为1.6ms,如果想要闪烁更为明显可以调整delay()函数中的数值。

7.硬件实现

  接线方法原理可以在之前的博客中查看,演示视频如下:
在这里插入图片描述
会发现灯亮灭周期太短,经过修改延迟函数,变为如下:
在这里插入图片描述

二、基于HAL库点灯

  STM32cubeMX的下载与安装过程,可以参考之前的博客

1.新建工程

  (1)“File”—“New Project”,完成工程创建。
在这里插入图片描述

  (2)找到自己需要的芯片型号,“Start Project ”
在这里插入图片描述

2.环境配置

(1)选择System Core下的SYS,将Debug设置为Serial Wire:

  这个根据自己的情况进行选择 :JTAG/SWD接口这是ALIENTEK精英STM32F103板载的20针标准JTAG调试口(JTAG),该JTAG口直接可以和ULINK、JLINK或者STLINK等调试器(仿真器)连接。

  SW,Serial Wire 是串行调试接口,在本次实验中用这个即可。
在这里插入图片描述

(2)时钟配置

  在Cubemx晶振选择有3种:

  Disable 这个是不用外部晶振;
  Crystal/Ceramic Resontor 这个是用无源外部晶振;
  BYPASS Clock Source 这个是有源外部晶振;

  选择BYPASS Clock Source:系统时钟由外部提供
选择Crystal/Ceramic Resontor:系统时钟由外部晶振和芯片内部电路协作提供。

  选择“RCC”-“HSE”-“Crystal/Ceramic Resontor
在这里插入图片描述

  配置完成后,芯片上的脚出现高亮。
在这里插入图片描述
(3)继续时钟配置,限定最大时钟频率。

  在“时钟树”下进行配置,将HCLK数值改为“2”,这样就设定了最大2MHz的HCKL的数值。

在这里插入图片描述

(4)GPIO配置

  首先,找到PA5,PB9,PC14,右键,选择“GPIO_Output
在这里插入图片描述

  之后,点击右侧菜单栏的“GPIO”,将三个引脚的输出默认低电平,输出模式默认“no pull up and no pull down”:如果引脚配置为output,一般选择no pull,这样,引脚才能根据你的output数据,进行正确输出。

(5)导出源代码

  首先,在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件:
在这里插入图片描述

  其次,取消勾选Use lastest available version,选择其他版本:
在这里插入图片描述

  之后,在Code Generate中选择第一个,然后Generate Code,即生成代码:
在这里插入图片描述

  最后,点击“Create Code”.

在这里插入图片描述

3.编写流水灯代码

(1)打开由CubeMx软件生成的代码。

  找到之前存放文件的文件路径,打开找到“MDK-ARM”,打开带有MDK标识的工程
在这里插入图片描述

(2)编写main()函数代码

  ①找到main.c函数,在main函数体中的while()循环中写入使LED亮的代码;
在这里插入图片描述

  ②在while()循环中写入如下代码:

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);  // 灯1灭
		HAL_Delay(1000); // 延时1s
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);  // 灯1亮
		
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);  // 灯2灭
		HAL_Delay(1000); // 延时1s
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);  // 灯2亮
			
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET);  // 灯3灭
		HAL_Delay(1000); // 延时1s
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET);  // 灯3亮

  ③编译通过
在这里插入图片描述

4.软件仿真

  和上述寄存器实现流水灯方法一致,观看GPIOA_5波形:
在这里插入图片描述
  GIOPB_9波形:
在这里插入图片描述

  GIOPC_12波形:
在这里插入图片描述
在这里插入图片描述
  通过软件仿真,发现周期并不是1s.而是0.7s左右。

5.硬件实现。

  所有电路连接和上述一样,只需将对应程序的hex文件烧录到单片机中即可,实物演示如图所示:

在这里插入图片描述

三、总结

  对寄存器的操作,实质上时对其地址进行操作,要注意位运算,在过程中由于没有取“|”运算,导致时钟配置出现问题,后续检查过程中发现如果不进行位或运算,该时钟使能就会被其他GPIO时钟覆盖。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是基于STM32CubeIDE和HAL库的定时器中断实现LED流水灯的代码: ```c #include "main.h" #include "stm32f4xx_hal.h" /* 定义GPIO引脚 */ #define LED1_Pin GPIO_PIN_0 #define LED1_GPIO_Port GPIOA #define LED2_Pin GPIO_PIN_1 #define LED2_GPIO_Port GPIOA #define LED3_Pin GPIO_PIN_2 #define LED3_GPIO_Port GPIOA #define LED4_Pin GPIO_PIN_3 #define LED4_GPIO_Port GPIOA /* 定义全局变量和函数 */ TIM_HandleTypeDef htim2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); HAL_TIM_Base_Start_IT(&htim2); /* 启动定时器并开启中断 */ while (1) { /* 主函数不做任何事情 */ } } /* 定时器中断处理函数 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t led_status = 0x01; /* 初始状态下第一个LED亮 */ /* 根据led_status位控制LED的亮灭 */ if (led_status & 0x01) HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); if (led_status & 0x02) HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); if (led_status & 0x04) HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); if (led_status & 0x08) HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET); else HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_RESET); /* 更新led_status的值 */ if (led_status == 0x08) led_status = 0x01; else led_status <<= 1; } /* System Clock Configuration */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /* GPIO初始化 */ void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 使能GPIOA时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 配置GPIO引脚为输出模式 */ GPIO_InitStruct.Pin = LED1_Pin | LED2_Pin | LED3_Pin | LED4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } /* 定时器初始化 */ void MX_TIM2_Init(void) { /* 使能TIM2时钟 */ __HAL_RCC_TIM2_CLK_ENABLE(); /* 初始化htim2的各项参数 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 8399; /* 预分频值 */ htim2.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */ htim2.Init.Period = 999; /* 自动重装值 */ htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频 */ if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } } ``` 注释已经非常详细了,简单来说就是通过定时器中断不断地改变LED的状态,从而实现LED流水灯的效果。每当定时器计数器达到自动重装值时,就会产生一次定时器中断,然后在中断处理函数中改变LED的状态。 需要注意的是,这里使用的定时器是TIM2,预分频值为8399,自动重装值为999,因此定时器的计数频率为84MHz / (8399 + 1) = 10kHz,即每隔100ms产生一次定时器中断。如果需要改变LED流水灯的速度,可以调整预分频值和自动重装值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值