基于STM32的简单电子表的实现

本文记录了一次基于STM32F103C8Tx单片机实现电子表的课程设计过程,主要涉及STM32的原理图设计、电源处理、数码管及按键电路、定时器中断和代码编写。通过Proteus仿真进行验证,虽有仿真闪烁问题,但实际应用中人眼无法察觉。项目简单但富有学习价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32的简易电子表

前言:最近在做嵌入式选修课设,基于STM32F103C8Tx实现电子表的简单模拟。由于没有芯片,老师只是要求我们用Proteus进行仿真(Proteus仿真有点不准确),课程结束以后特地写一篇博客来记录自己的想法

  1. 首先我们需要设计原理图,原理图共有两部分,一部分是电源的设计,另一部分是数码管以及按键的电路设计

对于电源部分我们需要对VCC进行降压处理(STM32芯片正常工作电压为3.3V),这里用到了BUCK电路,有需要了解的可以点击了解BUCK电路
stm32F103c8
在这里插入图片描述
下面是数码管以及按键的原理图

在这里插入图片描述
在这里插入图片描述
板子实例(布线不是很好)
在这里插入图片描述

2. 接下来我们可以利用Cube生成代码,时间的更新我们采用定时器中断即可

1.打开STM32CubeMX,新建项目选择STM32F103C8Tx
2.设置RCC时钟源
在这里插入图片描述
3.设置GPIO引脚
在这里插入图片描述在这里插入图片描述
4.配置定时器,这里选择的是TIM3(有关定时器的知识读者可自行了解)
在这里插入图片描述
5.配置时钟树
在这里插入图片描述
6.生成代码并用keil打开

3. 编写代码

1.void SystemClock_Config(void)

void SystemClock_Config(void)
{
    HAL_StatusTypeDef ret = HAL_OK;
    RCC_OscInitTypeDef RCC_OscInitStructure; 
    RCC_ClkInitTypeDef RCC_ClkInitStructure;
    
    RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE;    	//时钟源为HSE
	RCC_OscInitStructure.HSEState=RCC_HSE_ON;                      	//打开HSE
	RCC_OscInitStructure.HSEPredivValue=RCC_HSE_PREDIV_DIV1;		    //HSE预分频
	RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;										//打开PLL
    RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;						//PLL时钟源选择HSE
    RCC_OscInitStructure.PLL.PLLMUL=RCC_PLL_MUL9; 									//主PLL倍频因子
    ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);										//初始化
	
    if(ret!=HAL_OK) while(1);
    
    //选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2
    RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;			//设置系统时钟时钟源为PLL
    RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1;							//AHB分频系数为1
    RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV2; 							//APB1分频系数为2
    RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV1; 							//APB2分频系数为1
    ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_2);	//同时设置FLASH延时周期
		
    if(ret!=HAL_OK) while(1);
		
}

2.void GPIO_INIT(void)

void void GPIO_INIT(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOA_CLK_ENABLE();           									//开启GPIOA时钟
	__HAL_RCC_GPIOB_CLK_ENABLE();           									//开启GPIOB时钟
	
    GPIO_Initure.Pin=K4_Pin|K2_Pin;														//K4_Pin|K2_Pin
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  									//推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          									//上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;    	 									//高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
	
	GPIO_Initure.Pin=K1_Pin;																	//K3_Pin|K1_Pin
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  									//推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          									//上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;    	 									//高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
		
	GPIO_Initure.Pin=K3_Pin;																	//K3_Pin|K1_Pin
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  									//推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          									//上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;    	 									//高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
		
	GPIO_Initure.Pin=LIGHTS_Pin|G_Pin|B_Pin|C_Pin |E_Pin|D_Pin; 	//LIGHTS_Pin|G_Pin|B_Pin|C_Pin |E_Pin|D_Pin
	GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  											//推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          											//上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;    	 											//高速
	HAL_GPIO_Init(GPIOA,&GPIO_Initure);
		
	GPIO_Initure.Pin=F_Pin|A_Pin|P_Pin;										//F_Pin|A_Pin|P_Pin;
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  							//推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          							//上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;    	 							//高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
		
	GPIO_Initure.Pin=KEY1_Pin|KEY0_Pin;										//KEY1_Pin|KEY0_Pin;
    GPIO_Initure.Mode=GPIO_MODE_INPUT;  									//input
    GPIO_Initure.Speed=GPIO_SPEED_LOW;    	 							//低速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
		
    HAL_GPIO_WritePin(GPIOA, K3_Pin|K4_Pin|K2_Pin|LIGHTS_Pin|G_Pin|B_Pin|C_Pin |E_Pin|D_Pin, GPIO_PIN_SET);	//置1
    HAL_GPIO_WritePin(GPIOB, K1_Pin|F_Pin|A_Pin|P_Pin, GPIO_PIN_SET);													//置1
}

3.void TIM3_Init(void)

void TIM3_Init(void)
{  
    TIM3_Handler.Instance=TIM3;                          		 //TIMER3
    TIM3_Handler.Init.Prescaler=7200-1;                  		 //分频系数  500ms  500ms中断一次
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    		 //向上计数器
    TIM3_Handler.Init.Period=5000-1;                     	   //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;	 //时钟分频因子
    HAL_TIM_Base_Init(&TIM3_Handler);
    
    HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE   
}

4.void HAL_TIM_Base_MspInit(TIM_HandleTypeDef htim)

//设置中断优先级
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
	{
		__HAL_RCC_TIM1_CLK_ENABLE();            //使能TIM1时钟
		__HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
		HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    //设置中断优先级
		HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断   
	}
}

5.void TIM3_IRQHandler(void)

//TIMER3中断服务函数
void TIM3_IRQHandler(void)
{
	
		//HAL_GPIO_TogglePin(A_GPIO_Port, A_Pin);        
		//HAL_GPIO_TogglePin(B_GPIO_Port, B_Pin);       
		//HAL_GPIO_TogglePin(C_GPIO_Port, C_Pin);        
		//HAL_GPIO_TogglePin(D_GPIO_Port, D_Pin);        
		//HAL_GPIO_TogglePin(E_GPIO_Port, E_Pin);       
		//HAL_GPIO_TogglePin(F_GPIO_Port, F_Pin);
		//HAL_GPIO_TogglePin(G_GPIO_Port, G_Pin);
		//HAL_GPIO_TogglePin(P_GPIO_Port, P_Pin);
		//HAL_GPIO_TogglePin(LIGHTS_GPIO_Port, LIGHTS_Pin);
		//Timer500ms();
		TimerOneSecond();
		//HAL_GPIO_TogglePin(LIGHTS_GPIO_Port, LIGHTS_Pin);
		//delay_ms(10);
		HAL_TIM_IRQHandler(&TIM3_Handler);
	
}

*6.void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef htim)

//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
        //HAL_GPIO_TogglePin(A_GPIO_Port, A_Pin);        
		//HAL_GPIO_TogglePin(B_GPIO_Port, B_Pin);
    }
}

PS:这些都是基本的初始化函数也就是我们在Cube中配置的,我们需要的是设置定时器向上溢出的时间,并在定时器中断时对时间进行更新

下面我们编写显示函数
依次显示数码管的每个数字,利用视觉欺骗效果,人眼是观察不出来的,弊端就是仿真时会因电脑性能的原因而发生闪烁,实际上人眼是识别不出来的

uint8_t SEG[10]={0xfc, 0x60, 0xda, 0xf2, 0x66, 0xb6, 0xbe, 0xe4, 0xfe, 0xf6};

int	DELAY_TIME[4]={10, 10, 10, 10};

//选择点亮的数码管并将对应GPIO管脚置1
static void setSegOn(int index)
{
	
		//GPIO PORT RESET
		HAL_GPIO_WritePin(K1_GPIO_Port, K1_Pin, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(K2_GPIO_Port, K2_Pin, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(K3_GPIO_Port, K3_Pin, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(K4_GPIO_Port, K4_Pin, GPIO_PIN_RESET);
		
		//GPIO PORT SET
		switch(index)
		{
			case 0:
				HAL_GPIO_WritePin(K1_GPIO_Port, K1_Pin, GPIO_PIN_SET);
				break;
			case 1:
				HAL_GPIO_WritePin(K2_GPIO_Port, K2_Pin, GPIO_PIN_SET);
				break;
			case 2:
				HAL_GPIO_WritePin(K3_GPIO_Port, K3_Pin, GPIO_PIN_SET);
				break;
			case 3:
				HAL_GPIO_WritePin(K4_GPIO_Port, K4_Pin, GPIO_PIN_SET);
				break;
			default:
				Error_Handler();
				break;
		}
}


//通过对不同管脚的置位来显示对应的数字
static void DisplaySeg(uint8_t SegCode)
{
		uint8_t value;
		//P
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(P_GPIO_Port, P_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(P_GPIO_Port, P_Pin, GPIO_PIN_SET);
		}
		
		//G
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(G_GPIO_Port, G_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(G_GPIO_Port, G_Pin, GPIO_PIN_SET);
		}
		
		//F
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(F_GPIO_Port, F_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(F_GPIO_Port, F_Pin, GPIO_PIN_SET);
		}
		
		//E
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(E_GPIO_Port, E_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(E_GPIO_Port, E_Pin, GPIO_PIN_SET);
		}
		
		//D
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(D_GPIO_Port, D_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(D_GPIO_Port, D_Pin, GPIO_PIN_SET);
		}
		
		//C
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(C_GPIO_Port, C_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(C_GPIO_Port, C_Pin, GPIO_PIN_SET);
		}
		
		//B
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(B_GPIO_Port, B_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(B_GPIO_Port, B_Pin, GPIO_PIN_SET);
		}
		
		//A
		SegCode = SegCode>>1;
		value = SegCode&0x01;
		if(value)
		{
			HAL_GPIO_WritePin(A_GPIO_Port, A_Pin, GPIO_PIN_RESET);
		}
		else
		{
			HAL_GPIO_WritePin(A_GPIO_Port, A_Pin, GPIO_PIN_SET);
		}
}


static void DisplayValue(uint8_t value)
{
	
		DisplaySeg(SEG[value]);

}

//显示时间
void Display(void)
{
		//第一个数字HOUR的十位
		uint8_t Temp;
		Temp = Time_Buf.Hour / 10;
		setSegOn(0);
		DisplayValue(Temp);
		delay_ms(1400);
	
		//第二个数字HOUR的个位
		Temp = Time_Buf.Hour % 10;
		setSegOn(1);
		DisplayValue(Temp);
		delay_ms(1400);
	
		//第三个数字MINUTE的十位
		Temp = Time_Buf.Min / 10;
		setSegOn(2);
		DisplayValue(Temp);
		delay_ms(1400);
		
		//第四个数字MINUTE的个位
		Temp = Time_Buf.Min % 10;
		setSegOn(3);
		DisplayValue(Temp);
		delay_ms(1400);
		
		//点亮秒灯
		if(Time_Buf.Ms)
		{
			HAL_GPIO_WritePin(LIGHTS_GPIO_Port, LIGHTS_Pin, GPIO_PIN_RESET);
			//delay_ms(10);
			//HAL_GPIO_WritePin(LIGHTS_GPIO_Port, LIGHTS_Pin, GPIO_PIN_SET);
		}
		else
		{
			HAL_GPIO_WritePin(LIGHTS_GPIO_Port, LIGHTS_Pin, GPIO_PIN_SET);
		}
}

//初始化TIME
void Init(void)
{
		Time_Buf.Hour = 0;
		Time_Buf.Min = 0;
		Time_Buf.Sec = 0;
		Time_Buf.Ms = 0;
}

针对按键事件,只需要扫描有无按键按下而后执行响应的操作

int GLIMMER_TIME = 500;
int COUNT = -1;
int END = 1;
int temp;

//判断按键是否被按下
void IsEdit(void)
{
		
		if(!HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin))
		{
				//检查按钮是否未松开
				while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)){
						delay_ms(10);
				}
				COUNT = (COUNT + 1) % 4;
				END = 0;
		}
}


//按键被按下执行相应的改变
void Edit(void)
{
		//	check whether edit button is push
		if(!HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) && END == 0)
		{
				//检查按钮是否未松开
				while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)){
						delay_ms(10);
				}
				//change the corresponding time
				if(COUNT == 0)
				{
						Time_Buf.Hour =  ( (((Time_Buf.Hour / 10) + 1) % 3) * 10 +  Time_Buf.Hour % 10) % 24;
					
				}
				else if(COUNT == 1)
				{
						Time_Buf.Hour = ((Time_Buf.Hour / 10) * 10 + ((Time_Buf.Hour % 10) + 1) % 10) % 24;
				}
				else if(COUNT == 2)
				{
						Time_Buf.Min = (((Time_Buf.Min / 10 + 1) % 6) * 10 + Time_Buf.Min % 10) % 60;
				}
				else
				{
						Time_Buf.Min = (((Time_Buf.Min % 10 + 1) % 10) + Time_Buf.Min / 10 * 10 ) % 60;
				}
				
		}
	
}

定时器中断时的时间更新

//每500ms在定时器中断里执行一次时间的改变
void Timer500ms(void)
{
		Time_Buf.Ms = (Time_Buf.Ms + 1) % 2;
		Time_Buf.Sec += Time_Buf.Ms;
		if(Time_Buf.Sec >= 60)
		{
			Time_Buf.Sec = 0;
			Time_Buf.Min += 1;
			if(Time_Buf.Min >= 60)
			{
					Time_Buf.Min = 0;
					Time_Buf.Hour = (Time_Buf.Hour + 1) % 24;
			}
		}
}

Main()

Time_Def Time_Buf;

int main(void)
{
	HAL_Init();
  	Init();

  	HAL_Init();                    	 	//初始化HAL库    
    Stm32_Clock_Init();   				//设置时钟,72M
	LED_Init();							//初始化LED	
    TIM3_Init();     					//定时器3初始化
  	while (1)
  	{

		IsEdit();
		Edit();
		Display();		

 	 }

}

4.下面使用Proteus进行仿真

需要下载至少8.6版本的Proteus,低版本不能对STM32芯片进行仿真
在这里插入图片描述
在这里插入图片描述
至此整个作业就全部完成啦!!!!!
嵌入式软硬结合,还是很有意思的,虽然项目很简单但是我还是从中收获了很多。
革命尚未成功,同志仍需努力!
感谢薛老师的辛勤付出!

### 设计方案 STM32Cube生态系统提供了完整的工具链来支持STM32微控制器的开发工作[^1]。对于设计一款基于STM32的电子手表而言,可以利用STM32CubeMX配置初始设置并生成初始化代码框架,之后通过STM32CubeIDE完成具体功能模块编程。 #### 主要硬件需求 - STM32系列MCU(推荐低功耗型号) - LCD显示屏用于时间显示 - 实时时钟RTC芯片确保精准的时间记录 - 按键输入控制界面切换等操作 - 可充电锂电池供电系统保障续航能力 #### 软件架构概述 项目初期借助STM32CubeMX图形化界面快速搭建外设连接关系图,并导出适合后续编译器使用的工程文件结构;接着,在此基础之上编写核心算法逻辑处理部分,比如获取当前时刻、调整日期格式等功能实现;最后经过调试优化达到预期效果即可烧录至目标板卡运行测试验证性能指标是否满足要求。 ```c // 初始化实时时钟 (RTC) void RTC_Init(void){ /* 配置RTC时基 */ } // 更新LCD屏幕上的时间信息 void UpdateTimeOnScreen(uint8_t hour,uint8_t minute,uint8_t second){ /* 显示小时分钟秒数 */ } ``` 上述函数展示了如何启动内部定时装置以及刷新视觉呈现层面上的内容更新机制[^2]。 ### 教程指南 为了帮助开发者更高效地掌握整个流程,下面给出了一份简化版的学习路径: 1. 学习理解STM32基本原理及其周边电路构成; 2. 掌握使用STM32CubeMX创建新工程项目的方法; 3. 编写简单的LED闪烁实验熟悉语法特性; 4. 尝试加入更多复杂度较高的外围设备接口通信协议学习过程; 5. 结合实际应用场景深入研究特定领域知识如本案例中的日历管理服务调用方式等。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值