STM32实现LED流水灯

一、前期主备

(一)STM32F103C8T6基础版介绍

随着嵌入式的开发与不断钻研,开发板种类也在不断变多,今天我们在这里使用的是最基础的STM32F103C8T6基础板,虽然是基础板,但是它的功能还是挺齐全的很适合新手使用。

下图是STM32F103C8T6的引脚分布图介绍:

edf839a4e1944e49968956f92db8cfa3.png

引脚配置说明如下:

912b77ae41314fccae5e64a251fca1d9.png


(二)GPIO介绍

熟悉完开发板后,我们现在来学习GPIO端口i的定义。

——GPIO(General Purpose Input Output)通用输入输出口
——可配置为8种输入输出模式
——引脚电平:OV-3.3V.部分引脚可容忍5V 
——输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂呜器、 
       模拟通信协议输出时序等
——输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

具体的可以参考江协科技的视频。

260c7075ea9d46e9a380ae42bbdf9820.png

这里我截取了江协老师的PPT内容,让大家更好理解。

GPIO外设配置

d0146d4e554c4a92b163c5d771cdebe6.png

端口配置低寄存器

e77abcc67f5c4904a0a2eaa26fa94957.png

端口配置高寄存器

d878cbc18c1e40518cd966357368a442.png

具体的GPIO端口配置可以参考STM32中文参考手册

(三)LED灯

——LED:发光二极管,正向通电点亮。

83dd95b4e2464eaca14cc61eaca179d1.png

                                          a9bb67b18ecf486c9aadb59187485866.png

二、工程代码

(一)标准库实现

这里我参考的是江协科技的教学。具体步骤如下:

1、新建工程

f9cb6ec66ee945ed8d62eb7d49d27452.png

点击peoject,再点击new project。

29617c2118ba4af79f5a9aefffa290b1.png

在文件夹里创建文件

11f20926c1ee47fb9ca9fa933020017f.png

再在刚刚创建的文件夹里新建三个文件,分别叫Start,Library,User。

18ac144c217b43d7abdbf96fb66f12fa.png

加入启动文件。(这里的启动文件我找的是江协科技的)

d9514c3989c041c9afe656c4ab72f3c2.png

添加三个工程项目。

2、编写时延函数

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);	//开启GPIOA的时钟
															//使用各个外设前必须开启时钟,否则对外设的操作无效
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;				//GPIO引脚,赋值为所有引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
															//函数内部会自动根据结构体的参数配置相应寄存器
															//实现GPIOA的初始化
	
	/*主循环,循环体内的代码会一直循环执行*/
	while (1)
	{
		/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
		GPIO_Write(GPIOA, ~0x0001);	//0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0002);	//0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0004);	//0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0008);	//0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0010);	//0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0020);	//0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0040);	//0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
		GPIO_Write(GPIOA, ~0x0080);	//0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
		Delay_ms(100);				//延时100ms
	}
}

3、程序的烧录与实现

在编写完程序后,就开始进行程序的烧录,我这里采用的是Stlink烧录,方便快捷。

38178f0c58c24c9c91aa8580fb2596a8.png

用杜邦线将Stlink烧录器与stm32开发板接连。然后依次进行编译和烧录。

ceb4a726ef224aa796b7035f11862c6d.png

4、LED流水灯接线图

我采用的是面包板与杜邦线连接,平面接线图如下图所示:

0596aa5f407b42ffa8030e4d6d8eb91a.png

(二)寄存器实现

设计寄存器步骤:

以GPIOB和0号引脚(B0)为例,将其设置为低电平:

#define GPIOA_ODR (*(unsigned int *)0x4001080C)
GPIOB_ODR &= ~(1<<0);  // 最后一位变0

将相应的bit位设置为高电位则灯不亮,GPIO的A0、B0、C15代码如下:

//给GPIOA、GPIOB、GPIOC配置输入寄存器
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
// 3个LED初始化为不亮(即高点位)
GPIOB_ODR |= (1<<0); // 最后一位设置为1
GPIOC_ODR |= (1<<15); // 倒数第15位设置为1
GPIOA_ODR |= (1<<0); // 最后一位设置为1

main.c:

 
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
 
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
 
#define GPIOB_CRH (*(unsigned int *)0x40010C04)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
 
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
	
 
 
void SystemInit(void);
void Delay_ms(volatile  unsigned  int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void Delay_ms( volatile  unsigned  int  t)
{
     unsigned  int  i;
     while(t--)
         for (i=0;i<800;i++);
}
 
void A_LED_LIGHT(){
	GPIOA_ODR=0x0<<4;		//PA4低电平
	GPIOB_ODR=0x1<<9;		//PB9高电平
	GPIOC_ODR=0x1<<15;		//PC15高电平
}
void B_LED_LIGHT(){
	GPIOA_ODR=0x1<<4;		//PA4高电平
	GPIOB_ODR=0x0<<9;		//PB9低电平
	GPIOC_ODR=0x1<<15;		//PC15高电平
}
void C_LED_LIGHT(){
	GPIOA_ODR=0x1<<4;		//PA4高电平
	GPIOB_ODR=0x1<<9;		//PB9高电平
	GPIOC_ODR=0x0<<15;		//PC15低电平	
}
 
int main(){
	int j=100;
	// 开启时钟
	RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
	RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
	RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
	
	
	// 设置 GPIO 为推挽输出
	GPIOB_CRH&= 0xffffff0f;	//设置位 清零		
	GPIOB_CRH|=0x00000020;  //PB9推挽输出
 
	GPIOC_CRH &= 0x0fffffff; //设置位 清零		
	GPIOC_CRH|=0x30000000;  //PC15推挽输出
 
 
	GPIOA_CRL &= 0xfff0ffff; //设置位 清零		
	GPIOA_CRL|=0x00010000; //PA4推挽输出
 
	// 3个LED初始化为不亮(即高点位)
	GPIOB_ODR |= (1<<9); 
	GPIOC_ODR |= (1<<15); 
	GPIOA_ODR |= (1<<4);  
	
	while(j){
 
		A_LED_LIGHT();
		Delay_ms(1000000);
		
		B_LED_LIGHT();
		Delay_ms(1000000);
 
		C_LED_LIGHT();
		Delay_ms(1000000);
 
 
	}
	
}
 
 
void SystemInit(){
	
}

三、成果展示:

VID_20241113_201050

四、心得体会与总结

1. Keil工程创建:

- 首先要新建工程,选择对应的STM32芯片型号,这确保了后续编译等操作能基于正确的芯片资源进行。

- 添加必要的启动文件以及分组来管理代码,比如创建源文件和头文件分组等,方便代码的组织与维护。

2. GPIO输入输出:

- 明确要使用的GPIO引脚,通过配置相应的寄存器来设置其为输入或输出模式。例如,要实现流水灯,一般将引脚设置为推挽输出模式。

- 对于输出,可通过向对应的输出数据寄存器写入高低电平来控制引脚的输出状态,从而实现LED的亮灭控制。

3. 寄存器知识:

- STM32的寄存器是对芯片内部各种硬件资源进行控制和配置的关键。每个寄存器都有其特定的地址和功能。

- 比如GPIO的端口配置寄存器(CRL、CRH等)用于设置引脚的模式(输入、输出、复用等),输出数据寄存器(ODR)用于控制引脚的输出电平。直接操作寄存器能更深入地理解芯片的工作原理,但也需要对寄存器的每一位含义有清晰的了解,编写代码时要注意按位操作的准确性。

4. 标准固件库知识:

- 标准固件库提供了一系列的函数来方便对STM32芯片进行开发,它对底层寄存器操作进行了封装。

- 例如,对于GPIO的配置,有诸如GPIO_Init()等函数,通过设置结构体成员的值来完成对GPIO引脚模式、速度等的配置,相较于直接操作寄存器,使用固件库函数可以提高开发效率,代码也更具可读性和可维护性,但在一些对资源占用和执行速度有严格要求的场景下,可能需要结合寄存器操作来优化。

在整个流水灯实验过程中,通过以上各方面知识的综合运用,成功实现了LED按照一定顺序依次点亮熄灭,呈现出流水灯的效果。

五、心得体会

通过完成用STM32实现流水灯实验,收获颇丰。

在工程创建方面,体会到了合理组织工程结构的重要性,清晰的分组和文件管理能让后续代码的查找、修改和扩展都更加便捷,避免了代码混乱带来的困扰。

对于GPIO输入输出的配置,深入理解了如何通过软件来控制硬件引脚的状态,这是实现与外部设备交互的基础,也感受到了硬件和软件紧密结合的魅力。

在学习寄存器知识时,虽然一开始觉得寄存器的位操作比较复杂且容易出错,但当真正掌握后,能深切感受到直接操作寄存器带来的对芯片底层控制的精准性,也更能明白芯片内部资源是如何被一步步调配使用的。

而标准固件库则大大提高了开发效率,让我可以更快地实现功能,不用过多纠结于底层寄存器的细节。但同时也明白在一些特殊场景下,还是需要深入到寄存器层面去优化代码。

总的来说,这个实验是一个很好的入门实践,让我对STM32的开发流程、硬件控制以及相关知识有了较为全面的认识,为后续更复杂的项目开发打下了坚实的基础。

 

### 使用STM32C8T6生成PWM波 为了利用STM32C8T6微控制器生成PWM波形,需配置定时器及其通道来实现这一功能。具体而言,STM32C8T6支持多个定时器用于PWM输出,其中包括TIM1, TIM2, TIM3 和 TIM4等[^2]。 #### 配置步骤概述 对于生成PWM波的操作主要涉及以下几个方面: - **初始化GPIO端口**:设置相应的引脚模式为复用推挽输出。 - **配置定时器参数**:设定预分频系数、自动重装载寄存器值以及周期数。 - **启动PWM输出**:使能指定的定时器通道并开启中断(如果需要的话)。 下面给出一段基于标准外设库的标准C语言程序实例,该例子展示了如何使用TIM2定时器在PA0上产生PWM信号。 ```c #include "stm32f1xx_hal.h" // 定义全局变量 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(); // GPIO初始化 MX_TIM2_Init(); // TIM2初始化 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); // 启动PWM输出 (PA0) while(1){ } } /** * @brief 系统时钟配置函数. */ void SystemClock_Config(void){ RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /* Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; if(HAL_RCC_OscConfig(&RCC_OscInitStruct)!=HAL_OK){ Error_Handler(); } /* Initialize 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_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider=RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider=RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider=RCC_HCLK_DIV1; if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct,HSE_STARTUP_TIMEOUT)!=HAL_OK){ Error_Handler(); } } /** * @brief GPIO初始化函数. */ static void MX_GPIO_Init(void){ __HAL_RCC_GPIOD_CLK_ENABLE(); // 这里省略了其他无关代码... } /** * @brief TIM2初始化函数. */ static void MX_TIM2_Init(void){ TIM_OC_InitTypeDef sConfigOC={0}; htim2.Instance=TIM2; htim2.Init.Prescaler=79; // 设置预分频值 htim2.Init.CounterMode=TIM_COUNTERMODE_UP; htim2.Init.Period=999; // 自动重载值 htim2.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; if(HAL_TIM_PWM_Init(&htim2)!=HAL_OK){ Error_Handler(); } sConfigOC.OCMode=TIM_OCMODE_PWM1; sConfigOC.Pulse=500; // 占空比初始值 sConfigOC.OCPolarity=TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode=TIM_OCFAST_DISABLE; if(HAL_TIM_PWM_ConfigChannel(&htim2,&sConfigOC,TIM_CHANNEL_1)!=HAL_OK){ Error_Handler(); } } ``` 上述代码片段实现了基本的功能需求,在实际项目开发过程中可能还需要进一步调整参数以满足特定应用场景的要求。此外,也可以考虑采用CubeMX工具来自动生成部分底层驱动代码,从而简化编程工作量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值