一、准备所需元件

1.STM32F103C8T6主控

2.OLED屏幕

3.按键*3

4.MPU6050模块

5.LED灯*3

6.电位器*1

7.面包板*1

8.公对公杜邦线诺干

9.Stlink下载器

二、引脚配置

引脚配置我的代码和火禾的不同,有需要可以按着下面的表格来配置

三、代码文件

除了界面的代码是按照火禾实验室写的,其他模块的代码都是我自己按照他功能写的,如有不足或者有我没发现的BUG,请在评论区指教,谢谢。

主函数

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "i2c.h"
#include "rtc.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "OLED.h"
#include "key.h"
#include "interface.h"
#include "mpu6050.h"
#include "DMP_test.h"
#include "mpu6050.h"
#include "DMP_test.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void OLED_Proc(void);
void ADC_Proc(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//RTC
RTC_DateTypeDef rtc_date;
RTC_TimeTypeDef rtc_time;

//FLAG
uint8_t oled_shift = 1;

//TIME Start
extern uint8_t hou,min,sec; 
extern uint8_t flag_startwatch;

//ADC
uint16_t adc_val;
double V;

//Game val
extern int secore;
extern uint16_t ground_pos; 
extern uint8_t barrier_pos;
extern uint8_t cloud_pos;
extern uint8_t flag_dino_jump;

uint16_t cnt;
uint16_t cnt_game;
uint16_t cnt_ground;
uint16_t cnt_cloud;
uint16_t cnt_jump;
uint8_t cnt_key;

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_TIM4_Init();
  MX_I2C1_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
	OLED_Init();
	HAL_TIM_Base_Start_IT(&htim4);
	HAL_ADCEx_Calibration_Start(&hadc1);
	int jjj = mpu_dmp_init();
	while(jjj)
	{
		HAL_Delay(1000);
		jjj = mpu_dmp_init();
 	}		
	MPU6050_calibrate();
	//时间初始化
	rtc_date.Year = 25;
	rtc_date.Month = 9;
	rtc_date.Date = 4;
	HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);
	rtc_time.Hours = 20;
	rtc_time.Minutes = 23;
	rtc_time.Seconds = 6;
	HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		OLED_Proc();
		ADC_Proc();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  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_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, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_ADC;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void OLED_Proc(void)
{
	HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);	
	HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);
	
	switch(oled_shift)
	{
		case 1:
			OLED_MAIN();
			break;
		case 2:
			OLED_SET();
			break;
		case 3:
			OLED_SET_DATA();
			break;
		case 4:
			OLED_SET_TIME();
			break;
		case 5:
			OLED_Menu();
			break;
		case 6:
			OLED_StopWatch();
			break;
		case 7:
			OLED_showLED();
			break;
		case 8:
			OLED_MPU6050();
			break;
		case 9:
			OLED_Game_one();
			break;
		case 10:
			OLED_Game_tow();
			break;
		case 11:
			OLED_Emoji();
			break;
		case 12:
			OLED_Gradienter();
			break;
	}
}
void ADC_Proc(void)
{
	int sum = 0;
	if(oled_shift == 1)
	{
		for(int i=0; i<3000; i++)
		{
			HAL_ADC_Start(&hadc1);
			HAL_ADC_PollForConversion(&hadc1,10);
			adc_val = HAL_ADC_GetValue(&hadc1);
			sum += adc_val;
		}
		adc_val = sum / 3000;
		V = adc_val/4096.0*3.3;
	}
}

//1ms
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim -> Instance ==TIM4)
	{
		//60ms按键扫描
		cnt_key++;
		if(cnt_key >= 60){cnt_key = 0;KEY();}
		
		//秒表
		if(flag_startwatch == 1)
		{
			cnt++;
			if(cnt >= 1000) {cnt = 0;sec++;}
			if(sec >= 60){sec = 0; min++;}
			if(min >= 60){min = 0; hou++;}
			if(hou >= 24){hou = 0;}
		}
		//游戏
		if(oled_shift == 10)
		{
			cnt_game++;
			cnt_ground++;
			cnt_cloud++;
			
			if(cnt_game >= 100){cnt_game = 0; secore++;}
			
			if(cnt_ground >= 20)
			{
				cnt_ground = 0;
				ground_pos++;
				barrier_pos++;
				if(ground_pos >= 256) ground_pos = 0;
				if(barrier_pos >= 144) barrier_pos = 0;
			}
			if(cnt_cloud >= 50)
			{
				cnt_cloud = 0;
				cloud_pos++;
				if(cloud_pos >= 200) cloud_pos = 0;
			}
			if(flag_dino_jump == 1)
			{
				cnt_jump++;
				if(cnt_jump >= 1000) {cnt_jump = 0; flag_dino_jump = 0;}
			}
		}
	}
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

按键代码

#include "key.h"
#include "stdint.h"
#include "stm32f1xx.h"
#include "main.h"
#include "OLED.h"
#include "rtc.h"
#include "math.h"
#include "interface.h"
#include "Delay.h"

//按键
uint8_t key_val,key_down,key_up,key_old;
//主界面
uint8_t option_flag_main = 1; 	//主界面当前选择的选项标志位
//设置界面
uint8_t option_flag_set = 1;	//设置界面当前选择的选项标志位
//修改界面
uint8_t option_flag_datatime = 1; //修改界面当前选择的选项标志位
uint8_t flag_set_datatime;	//选择当前修改时间(年月日时分秒)标志位

//菜单界面
uint8_t option_flag_menu = 1; //菜单界面当前选择选项标志位
uint8_t flag_menu_next = 0; //菜单字界面
extern int8_t target_selection; //滑动界面标志位
//秒表界面
uint8_t option_flag_stopwatch = 1;
uint8_t confirm_flag_stopwatch = 0;
//手电筒界面
uint8_t option_flag_led = 1;
uint8_t confirm_flag_led = 0;
//MPU6050界面
uint8_t option_flag_mpu6050 = 1;
//游戏界面
uint8_t option_flag_game = 1;
extern uint8_t flag_dino_jump;

extern uint8_t oled_shift; //当前界面位数

//RTC枚举体
extern RTC_DateTypeDef rtc_date;
extern RTC_TimeTypeDef rtc_time;

uint8_t flag_ruturn_set=0;			//防止确认按键重复触发(设置界面返回)
uint8_t flag_ruturn_datatime=0; //防止确认按键重复触发(修改时间界面返回)
uint8_t flag_ruturn_menu = 0; 	//防止确认按键重复触发(菜单界面返回)
uint8_t flag_ruturn_stopwatch = 0;	//防止确认按键重复触发(秒表界面返回)
uint8_t flag_ruturn_led = 0;		//防止确认按键重复触发(LED界面返回)
uint8_t flag_ruturn_mpu6050 = 0;//防止确认按键重复触发(MPU6050界面返回)
uint8_t flag_ruturn_game = 0; //防止确认按键重复触发(游戏界面返回)
uint8_t flag_ruturn_emoji = 0;//防止确认按键重复触发(表情界面返回)
uint8_t flag_ruturn_gradienter = 0;//防止确认按键重复触发(水平仪界面返回)

uint8_t flag_off;


void KEY(void)
{
	key_val = Scanf_KEY();
	key_down	 = key_val & (key_val ^ key_old);
	key_up = ~key_val & (key_val ^ key_old);
	key_old = key_val;
	
	//主界面
	if(oled_shift == 1)
	{
		//上一项
		if(key_down == 1)
		{
			if(--option_flag_main <= 0) option_flag_main = 2;
		}
		//下一项
		else if(key_down == 2)
		{
			if(++option_flag_main >= 3) option_flag_main = 1;
		}
		//确认
		else if(key_down == 3)
		{
			//进入菜单界面
			if(option_flag_main == 1) {oled_shift = 5;option_flag_main = 1;}
			//选择设置模式
			if(option_flag_main == 2) {oled_shift = 2;option_flag_main = 1;}
			OLED_Clear();
			OLED_Update();
		}
	}
	//设置界面
	if(oled_shift == 2)
	{
		//上一项
		if(key_down == 1)
		{
			if(--option_flag_set <= 0) option_flag_set = 3;
		}
		//下一项
		else if(key_down == 2)
		{
			if(++option_flag_set >= 4) option_flag_set = 1;
		}
		//确认
		else if(key_down == 3)
		{
			if(key_down == 3 && option_flag_set == 1) flag_ruturn_set = ! flag_ruturn_set;
			//返回主界面
			if(option_flag_set == 1 && flag_ruturn_set == 0){oled_shift = 1;}
			//进入设置时间界面 并且复位标志位
			if(option_flag_set == 2){oled_shift = 3;option_flag_set = 1;flag_ruturn_set=1;}
			//关机
			if(option_flag_set == 3)
			{
				flag_off = 1;
				option_flag_set = 1;
				flag_ruturn_set=1;
				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET);
			}
			OLED_Clear();
			OLED_Update();
		}
	}
	//修改时间界面
	if(oled_shift == 3 || oled_shift == 4)
	{
		if(key_down == 1)
		{
			//当没有确认修改时间时才可以进行选择
			if(flag_set_datatime == 0) option_flag_datatime -= 1;
			if(option_flag_datatime <= 0) option_flag_datatime = 7;
			if(option_flag_datatime >= 5) {oled_shift = 4;OLED_Clear();}
			else {oled_shift = 3;OLED_Clear();}
			//修改时间 加
			if(flag_set_datatime == 1) // 年
			{
				rtc_date.Year = (rtc_date.Year + 1);
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 2) //月
			{
				rtc_date.Month = (rtc_date.Month + 1);
				if(rtc_date.Month > 12) rtc_date.Month = 1;
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 3) //日
			{
				rtc_date.Date = (rtc_date.Date + 1);
				if(rtc_date.Date > 31) rtc_date.Date = 1;
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 4) //时
			{
				rtc_time.Hours = (rtc_time.Hours + 1);
				if(rtc_time.Hours > 23) rtc_time.Hours = 0;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 5) //分
			{
				rtc_time.Minutes = (rtc_time.Minutes + 1);
				if(rtc_time.Minutes > 59) rtc_time.Minutes = 0;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 6) //秒
			{
				rtc_time.Seconds = (rtc_time.Seconds + 1);
				if(rtc_time.Seconds > 59) rtc_time.Seconds = 0;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
		}
		else if(key_down == 2)
		{
			if(flag_set_datatime == 0) option_flag_datatime += 1;
			if(option_flag_datatime >= 8) option_flag_datatime = 1;
			if(option_flag_datatime >= 5) {oled_shift = 4;OLED_Clear();}
			else {oled_shift = 3;OLED_Clear();}
			//修改时间 减
			if(flag_set_datatime == 1) // 年
			{
				rtc_date.Year = (rtc_date.Year - 1);
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 2) //月
			{
				rtc_date.Month = (rtc_date.Month - 1);
				if(rtc_date.Month <= 0) rtc_date.Month = 12;
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 3) //日
			{
				rtc_date.Date = (rtc_date.Date - 1);
				if(rtc_date.Date <= 0) rtc_date.Date = 31;
				if(HAL_RTC_SetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 4) //时
			{
				if(rtc_time.Hours == 0) rtc_time.Hours = 24;
				rtc_time.Hours --;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 5) //分
			{
				if(rtc_time.Minutes == 0) rtc_time.Minutes = 60;
				rtc_time.Minutes --;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
			else if(flag_set_datatime == 6) //秒
			{
				if(rtc_time.Seconds == 0) rtc_time.Seconds = 60;
				rtc_time.Seconds --;
				if(HAL_RTC_SetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN) == HAL_OK){}
			}
		}
		else if(key_down == 3)
		{
			flag_ruturn_datatime = ! flag_ruturn_datatime;
			//返回设置界面
			if(option_flag_datatime == 1 && flag_ruturn_datatime == 0){oled_shift = 2;option_flag_datatime=1; OLED_Clear();OLED_Update();}
			//选择修改的时间(年月日时分秒)
			if(option_flag_datatime == 2 && flag_set_datatime == 0) flag_set_datatime = 1;else if(option_flag_datatime == 2 && flag_set_datatime == 1){flag_set_datatime = 0;}
			if(option_flag_datatime == 3 && flag_set_datatime == 0) flag_set_datatime = 2;else if(option_flag_datatime == 3 && flag_set_datatime == 2){flag_set_datatime = 0;}
			if(option_flag_datatime == 4 && flag_set_datatime == 0) flag_set_datatime = 3;else if(option_flag_datatime == 4 && flag_set_datatime == 3){flag_set_datatime = 0;}
			if(option_flag_datatime == 5 && flag_set_datatime == 0) flag_set_datatime = 4;else if(option_flag_datatime == 5 && flag_set_datatime == 4){flag_set_datatime = 0;}
			if(option_flag_datatime == 6 && flag_set_datatime == 0) flag_set_datatime = 5;else if(option_flag_datatime == 6 && flag_set_datatime == 5){flag_set_datatime = 0;}
			if(option_flag_datatime == 7 && flag_set_datatime == 0) flag_set_datatime = 6;else if(option_flag_datatime == 7 && flag_set_datatime == 6){flag_set_datatime = 0;}
		}
	}
	//菜单界面
	if(oled_shift == 5)
	{
		//上一项
		if(key_down == 1)
		{
			target_selection--;
			if(target_selection < 0)  target_selection = 6;
		}
		//下一项
		else if(key_down == 2)
		{
			target_selection++;
			if(target_selection > 6)  target_selection = 0;
		}
		//确认键
		else if(key_down == 3)
		{
			flag_ruturn_menu = !flag_ruturn_menu;
			//选中退出图标按下确认
			if(target_selection == 0 && flag_ruturn_menu == 0){oled_shift = 1; target_selection=0;}
			//选择秒表界面
			if(target_selection == 1){MenuToFunction(); oled_shift = 6;}
			//选择手电筒界面
			if(target_selection == 2){MenuToFunction();oled_shift = 7;}
			//选择MPU6050界面
			if(target_selection == 3){MenuToFunction();oled_shift = 8;}
			//选择游戏界面
			if(target_selection == 4){ MenuToFunction();oled_shift = 9;}
			//选择动态表情界面
			if(target_selection == 5){MenuToFunction();oled_shift = 11;}
			//选择水平仪界面
			if(target_selection == 6){MenuToFunction();oled_shift = 12;}
			OLED_Clear();
			Delay_ms(1);
			OLED_Update();
		}
	}
	//子菜单秒表界面
	if(oled_shift == 6)
	{
		//上一项
		if(key_down == 1)
		{
			option_flag_stopwatch--;
			if(option_flag_stopwatch <= 0) option_flag_stopwatch = 4;
		}
		//下一项
		else if(key_down == 2)
		{
			option_flag_stopwatch++;
			if(option_flag_stopwatch > 4) option_flag_stopwatch = 1;
		}
		//确认键
		else if(key_down == 3)
		{
			//只有在退回键
			if(option_flag_stopwatch == 1) flag_ruturn_stopwatch = !flag_ruturn_stopwatch;
			if(option_flag_stopwatch == 1 && flag_ruturn_stopwatch == 0){oled_shift = 5;option_flag_stopwatch = 1;confirm_flag_stopwatch = 0;flag_ruturn_menu=1;}
		}
	}
	//子菜单手电筒界面
	if(oled_shift == 7)
	{
		if(key_down == 1)
		{
			option_flag_led -= 1;
			if(option_flag_led <= 0) option_flag_led = 3;
		}
		else if(key_down == 2)
		{
			option_flag_led += 1;
			if(option_flag_led >= 4) option_flag_led = 1;
		}
		else if(key_down == 3)
		{
			if(option_flag_led == 1) flag_ruturn_led = !flag_ruturn_led;
			if(option_flag_led == 1 && flag_ruturn_led == 0){oled_shift = 5; option_flag_led = 1; confirm_flag_led = 0;flag_ruturn_menu=1;}
		}
	}
	//子菜单MPU6050
	if(oled_shift == 8)
	{
		if(key_down == 1)
		{
			
		}
		else if(key_down == 2)
		{
			
		}
		else if(key_down == 3)
		{
			flag_ruturn_mpu6050 = !flag_ruturn_mpu6050;
			if(option_flag_mpu6050 == 1 && flag_ruturn_mpu6050 == 0){oled_shift = 5;option_flag_mpu6050 = 1;flag_ruturn_menu = 1;}
		}
	}
	//小恐龙游戏主界面
	if(oled_shift == 9)
	{
		if(key_down == 1)
		{
			option_flag_game--;
			if(option_flag_game <= 0) option_flag_game = 2;
		}
		else if(key_down == 2)
		{
			option_flag_game++;
			if(option_flag_game >= 3) option_flag_game = 1;
		}
		else if(key_down == 3)
		{
			if(option_flag_game == 1) flag_ruturn_game = !flag_ruturn_game;
			if(option_flag_game == 1 && flag_ruturn_game == 0){oled_shift =5; flag_ruturn_game = 0;option_flag_game = 1;flag_ruturn_menu = 1;}
			if(option_flag_game == 2){oled_shift = 10;OLED_Clear();}
		}
	}
	if(oled_shift == 10)
	{
		if(key_down == 1)
		{
			flag_dino_jump = 1;
		}
	}
	//动态表情包
	if(oled_shift == 11)
	{
		if(key_down == 3)
		{
			flag_ruturn_emoji = !flag_ruturn_emoji;
			if(flag_ruturn_emoji == 0) {oled_shift = 5;flag_ruturn_menu = 1;}
		}
	}
	//水平仪
	if(oled_shift == 12)
	{
		if(key_down == 3)
		{
			flag_ruturn_gradienter = !flag_ruturn_gradienter;
			if(flag_ruturn_gradienter == 0){oled_shift = 5;flag_ruturn_menu = 1;}
		}
	}
	//开机
	if(oled_shift != 2)
	{
		if(key_down == 3 && flag_off == 1)
		{
			flag_off = 0;
			oled_shift = 1;
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET);
		}
	}
}
//扫描按键按下
uint8_t Scanf_KEY(void)
{
	uint8_t key_val;
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET)
	{
		key_val = 1;
	}
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2) == RESET)
	{
		key_val = 2;
	}
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) == RESET)
	{
		key_val = 3;
	}
	return key_val;
}


界面代码

#include "interface.h"
#include "main.h"
#include "OLED.h"
#include "mpu6050.h"
#include "DMP_test.h"
#include "stdlib.h"
#include "math.h"

//主界面
extern uint8_t option_flag_main;
//设置界面
extern uint8_t option_flag_set;
//修改界面
extern uint8_t option_flag_datatime;
//选中修改的时间变量 年月日时分秒
extern uint8_t flag_set_datatime;

//秒表界面
extern uint8_t option_flag_stopwatch;
extern uint8_t confirm_flag_stopwatch;
uint8_t hou,min,sec;
uint8_t flag_startwatch = 0;

//手电筒界面
extern uint8_t option_flag_led;
extern uint8_t confirm_flag_led;

//MPU6050界面
extern uint8_t option_flag_mpu6050;
extern uint8_t confirm_flag_mpu6050;

//游戏界面
extern uint8_t option_flag_game;
uint16_t ground_pos; 
uint8_t flag_barrier;
uint8_t barrier_pos;
uint8_t cloud_pos;
uint8_t flag_dino_jump=0;//0:奔跑 1:跳跃
uint8_t jump_pos;
double pi=3.1415927;
extern uint16_t cnt_jump;
extern uint8_t flag_ruturn_game;


struct Object_Position{
	uint8_t minX,minY,maxX,maxY;
};

struct Object_Position dino;
struct Object_Position barrier;

//adc
extern uint16_t adc_val;
extern double V;

//RTC结构体
extern RTC_DateTypeDef rtc_date;
extern RTC_TimeTypeDef rtc_time;

extern uint8_t oled_shift; //当前界面位数


/*----------------------------------主界面-------------------------------------*/
void OLED_MAIN(void)
{
	OLED_Battery();
	OLED_Printf(0,0,OLED_6X8,"20%d-%02d-%02d",rtc_date.Year,rtc_date.Month,rtc_date.Date);
	OLED_Printf(16,16,OLED_12X24,"%02d:%02d:%02d",rtc_time.Hours,rtc_time.Minutes,rtc_time.Seconds);
	OLED_ShowChinese(0, 48, "菜单");
	OLED_ShowChinese(96, 48, "设置");
	switch(option_flag_main)
	{
		case 1:
			OLED_ReverseArea(0,48,32,16);
			OLED_Update();
			break;
		case 2:
			OLED_ReverseArea(96,48,32,16);
			OLED_Update();
			break;
	}
}
/*----------------------------------设置界面-------------------------------------*/
void OLED_SET(void)
{
	OLED_ShowImage(0,0,16,16,Return);
	OLED_ShowChinese(0, 16, "日期时间设置");
	OLED_ShowChinese(0, 32, "关机");
	switch(option_flag_set)
	{
		case 1:
			OLED_ReverseArea(0,0,16,16);
			OLED_Update();
			break;
		case 2:
			OLED_ReverseArea(0,16,96,16);
			OLED_Update();
			break;
		case 3:
			OLED_ReverseArea(0,32,32,16);
			OLED_Update();
			break;
	}
}
/*----------------------------------修改年月日界面-------------------------------------*/
void OLED_SET_DATA(void)
{
	OLED_ShowImage(0,0,16,16,Return);
	OLED_Printf(0,16,OLED_8X16,"  :20%2d",rtc_date.Year,flag_set_datatime);
	OLED_Printf(0,32,OLED_8X16,"  :%02d",rtc_date.Month);
	OLED_Printf(0,48,OLED_8X16,"  :%02d",rtc_date.Date);
	OLED_ShowChinese(0, 16, "年");
	OLED_ShowChinese(0, 32, "月");
	OLED_ShowChinese(0, 48, "日");
	
	if(flag_set_datatime == 0)
	{
		switch(option_flag_datatime)
		{
			case 1:
				OLED_ReverseArea(0,0,16,16);
				OLED_Update();
				break;
			case 2:
				OLED_ReverseArea(0,16,16,16);
				OLED_Update();
				break;
			case 3:
				OLED_ReverseArea(0,32,16,16);
				OLED_Update();
				break;
			case 4:
				OLED_ReverseArea(0,48,16,16);
				OLED_Update();
				break;
		}
	}
	else{
		switch(flag_set_datatime)
		{
			case 1:
				OLED_ReverseArea(24,16,32,16);
				OLED_Update();
				break;
			case 2:
				OLED_ReverseArea(24,32,16,16);
				OLED_Update();
				break;
			case 3:
				OLED_ReverseArea(24,48,16,16);
				OLED_Update();
				break;
		}
	}
}

/*----------------------------------修改时分秒界面-------------------------------------*/
void OLED_SET_TIME(void)
{
	OLED_Printf(0,0,OLED_8X16,"  :%02d ",rtc_time.Hours);
	OLED_Printf(0,16,OLED_8X16,"  :%02d",rtc_time.Minutes);
	OLED_Printf(0,32,OLED_8X16,"  :%02d",rtc_time.Seconds);
	OLED_ShowChinese(0, 0, "时");
	OLED_ShowChinese(0, 16, "分");
	OLED_ShowChinese(0, 32, "秒");
	
	if(flag_set_datatime <= 3)
	{
		switch(option_flag_datatime)
		{
			case 5:
				OLED_ReverseArea(0,0,16,16);
				OLED_Update();
				break;
			case 6:
				OLED_ReverseArea(0,16,16,16);
				OLED_Update();
				break;
			case 7:
				OLED_ReverseArea(0,32,16,16);
				OLED_Update();
				break;
		}
	}
	else
	{
		switch(flag_set_datatime)
		{
			case 4:
				OLED_ReverseArea(24,0,16,16);
				OLED_Update();
				break;
			case 5:
				OLED_ReverseArea(24,16,16,16);
				OLED_Update();
				break;
			case 6:
				OLED_ReverseArea(24,32,16,16);
				OLED_Update();
				break;
		}
	}
		
}

/*----------------------------------滑动菜单界面-------------------------------------*/

uint8_t pre_selection;//上次选择的选项(图标的索引值 0开始)
int8_t target_selection;//目标选项		(图标的索引值 0开始)
uint8_t x_pre=48;//上次选项的x坐标
uint8_t Speed=8;//图标移动的速度

void OLED_Menu(void)
{
	//初始清屏一下
	OLED_Clear();
	//显示一下选中框
	OLED_Printf(0,0,OLED_6X8,"T:%d ",target_selection);
	OLED_ShowImage(42,10,44,44,Frame);
	//当上一次选中的选项小于目标选项(图标向左移动)
	if(pre_selection < target_selection)
	{
		//上次选中的x坐标开始向左移动
		x_pre-=Speed;
		//移动到0时
		if(x_pre == 0)
		{
			//上次选择的选项加1
			pre_selection++;
			//上次选项的x坐标复位
			x_pre = 48;
		}
	}
	//当上一次选中的选项大于目标选项 (图标向右移动)
	if(pre_selection > target_selection)
	{
		//上次选中的x坐标开始向右移动
		x_pre+=Speed;
		//移动到96时
		if(x_pre == 96)
		{
			//上次选择的选项减1
			pre_selection--;
			//上次选项的x坐标复位
			x_pre = 48;
		}
	}
	//上次选择的选项大于等于1时才显示左边的图标
	if(pre_selection >= 1)
	{
		OLED_ShowImage(x_pre-48,16,32,32,Menu_Graph[pre_selection - 1]);
	}
	//上次选择的选项大于等于2时才显示左右两边的图标
	if(pre_selection >= 2)
	{
		OLED_ShowImage(x_pre-96,16,32,32,Menu_Graph[pre_selection - 2]);
	}
	OLED_ShowImage(x_pre,16,32,32,Menu_Graph[pre_selection]);
	OLED_ShowImage(x_pre+48,16,32,32,Menu_Graph[pre_selection+1]);
	OLED_ShowImage(x_pre+96,16,32,32,Menu_Graph[pre_selection+2]);
	OLED_Update();
}
/*----------------------------------子菜单秒表界面-------------------------------------*/
void OLED_StopWatch(void)
{
	OLED_ShowImage(0,0,16,16,Return);
	OLED_Printf(32,20,OLED_8X16,"%02d:%02d:%02d",hou,min,sec);
	OLED_ShowChinese(8, 44, "开始");
	OLED_ShowChinese(48, 44, "停止");
	OLED_ShowChinese(88, 44, "清除");
	
	switch(option_flag_stopwatch)
	{
		case 1:
			OLED_ReverseArea(0,0,16,16);
			OLED_Update();
			flag_startwatch = 0;
			break;
		case 2:
			OLED_ReverseArea(8,44,32,16);
			OLED_Update();
			flag_startwatch = 1;
			break;
		case 3:
			OLED_ReverseArea(48,44,32,16);
			OLED_Update();
			flag_startwatch = 0;
			break;
		case 4:
			OLED_ReverseArea(88,44,32,16);
			OLED_Update();
			flag_startwatch = 0;
			hou = min = sec = 0;
			break;
	}
}
//跳动界面
void MenuToFunction(void)
{
	uint8_t temp = 0;
	for(uint8_t i=0;i<=16;i++)
	{
		OLED_Clear();
		if(pre_selection>=1 && temp == 0)
		{
			OLED_ShowImage(x_pre-48,16+8*i,32,32,Menu_Graph[pre_selection-1]);
			temp = 1;
		}
		OLED_ShowImage(x_pre,16+8*i,32,32,Menu_Graph[pre_selection]);
		OLED_ShowImage(x_pre+48,16+8*i,32,32,Menu_Graph[pre_selection+1]);
		OLED_Update();
	}
}

/*----------------------------------子菜单手电筒界面-------------------------------------*/
void OLED_showLED(void)
{
	OLED_ShowImage(0,0,16,16,Return);
	OLED_Printf(20,20,OLED_12X24,"OFF");
	OLED_Printf(72,20,OLED_12X24,"ON");
	
	
	switch(option_flag_led)
	{
		case 1:
			OLED_ReverseArea(0,0,16,16);
			OLED_Update();
			break;
		case 2:
			OLED_ReverseArea(20,20,36,24);
			OLED_Update();
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
			break;
		case 3:
			OLED_ReverseArea(72,20,24,24);
			OLED_Update();
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);
			break;
	}
}
/*----------------------------------子菜单MPU6050界面-------------------------------------*/
void OLED_MPU6050(void)
{
	mpu_dmp_get_data(&pitch,&roll,&yaw);
	OLED_ShowImage(0,0,16,16,Return);
	OLED_Printf(0,16,OLED_8X16,"Pitch:%.2f ",pitch);
	OLED_Printf(0,32,OLED_8X16,"Roll :%.2f ",roll);
	OLED_Printf(0,48,OLED_8X16,"Yaw  :%.2f ",yaw);
	
	switch(option_flag_mpu6050)
	{
		case 1:
			OLED_ReverseArea(0,0,16,16);
			OLED_Update();
			break;
	}
}
/*----------------------------------子菜单游戏界面-------------------------------------*/
int secore;//游戏分数

void Game_Init(void)
{
	secore=ground_pos=barrier_pos=cloud_pos=jump_pos=0;
}

//游戏选择主界面
void OLED_Game_one(void)
{
	OLED_ShowImage(0,0,16,16,Return);
	OLED_ShowChinese(0,16, "谷歌小恐龙");
	
	switch(option_flag_game)
	{
		case 1:
			OLED_ReverseArea(0,0,16,16);
			OLED_Update();
			break;
		case 2:
			OLED_ReverseArea(0,16,80,16);
			Game_Init();
			OLED_Update();
	}
}
//检测是否遇到障碍物函数
void isColliding(struct Object_Position *a,struct Object_Position *b)
{
	if((a->maxX>b->minX)&&(a->minX<b->maxX)&&(a->maxY>b->minY)&&(a->minY<b->maxY))
	{
		OLED_Clear();
		OLED_ShowString(28,24,"Game Over",OLED_8X16);
		OLED_Update();
		Delay_s(1);
		OLED_Clear();
		OLED_Update();
		oled_shift = 9;
		flag_ruturn_game = 1;
	}
}

//显示游戏整体界面
void OLED_Game_tow(void)
{
	OLED_Clear();
	OLED_Printf(98,0,OLED_6X8,"%05d ",secore);
	OLED_Game_Ground();
	OLED_Game_Barrier();
	OLED_Game_Cloud();
	OLED_Game_Dino();
	OLED_Update();
	isColliding(&dino,&barrier);
}
//游戏显示地面函数
void OLED_Game_Ground(void)
{
	if(ground_pos < 128)
	{
		for(int i=0; i<128; i++)
		{
			OLED_DisplayBuf[7][i] = Ground[i+ground_pos];
		}
	}
	else{
		for(int i=0; i<255-ground_pos; i++)
		{
			OLED_DisplayBuf[7][i] = Ground[i+ground_pos];
		}
		for(int i=255-ground_pos; i<128; i++)
		{
			OLED_DisplayBuf[7][i] = Ground[i-(255-ground_pos)];
		}
	}

}
//游戏显示障碍物
void OLED_Game_Barrier(void)
{
	if(barrier_pos >= 143)
	{
		flag_barrier = rand()%3;
	}
	barrier.minX=127-barrier_pos;
	barrier.maxX=143-barrier_pos;
	barrier.minY=44;
	barrier.maxY=62;
	OLED_ShowImage(127-barrier_pos,44,16,18,Barrier[flag_barrier]);
}	

void OLED_Game_Cloud(void)
{
	OLED_ShowImage(127-cloud_pos,9,16,8,Cloud);
}

void OLED_Game_Dino(void)
{
	jump_pos = 38*sin((float)(pi*cnt_jump/1000));
	if(flag_dino_jump == 0)
	{
		if(cloud_pos % 2 == 0)OLED_ShowImage(0,44,16,18,Dino[0]);
		else OLED_ShowImage(0,44,16,18,Dino[1]);
	}
	else{
		OLED_ShowImage(0,44-jump_pos,16,18,Dino[2]);
	}
	dino.minX=0;
	dino.maxX=16;
	dino.minY=44-jump_pos;
	dino.maxY=62-jump_pos;
}
/*----------------------------------子菜单表情包界面-------------------------------------*/

void OLED_Emoji(void)
{
	//闭眼
	for(int i=0; i<6; i++)
	{
		OLED_Clear();
		OLED_ShowImage(30,10+i,16,16,Eyebrow[0]);
		OLED_ShowImage(82,10+i,16,16,Eyebrow[1]);
		OLED_DrawEllipse(40,32,6,6-i,1);
		OLED_DrawEllipse(88,32,6,6-i,1);
		OLED_ShowImage(54,40,20,20,Mouth);
		OLED_Update();
		Delay_ms(100);
	}
	//睁眼
	for(int i=0; i<3; i++)
	{
		OLED_Clear();
		OLED_ShowImage(30,12-i,16,16,Eyebrow[0]);
		OLED_ShowImage(82,12-i,16,16,Eyebrow[1]);
		OLED_DrawEllipse(40,32,6,4+i,1);
		OLED_DrawEllipse(88,32,6,4+i,1);
		OLED_ShowImage(54,40,20,20,Mouth);
		OLED_Update();
		Delay_ms(100);
	}
	Delay_ms(100);

}

/*----------------------------------子菜单水平仪界面-------------------------------------*/
void OLED_Gradienter(void)
{
	mpu_dmp_get_data(&pitch,&roll,&yaw);
	OLED_Clear();
	OLED_DrawCircle(64,32,30,0);
	OLED_DrawCircle(64-roll,32+pitch,4,1);
	OLED_Update();
}
int Battery_Capacity;

void OLED_Battery(void)
{
	Battery_Capacity = (adc_val -3276) * 100 / 813;
	if(Battery_Capacity < 0) Battery_Capacity = 0;

	
	if(Battery_Capacity >= 100){OLED_Printf(83,4,OLED_6X8,"%d%%",Battery_Capacity);OLED_ShowImage(110,0,16,16,Battery);} 
	else if(Battery_Capacity>=10&&Battery_Capacity<=99)
	{
		OLED_Printf(83,4,OLED_6X8," %d%% ",Battery_Capacity);
		OLED_ShowImage(110,0,16,16,Battery);
		OLED_ClearArea((112+Battery_Capacity/10),5,(10-Battery_Capacity/10),6);
	}
	else
	{
		OLED_ShowImage(110,0,16,16,Battery);
		OLED_ClearArea(112,5,10,6);
		OLED_Printf(83,4,OLED_6X8,"  %d%%",Battery_Capacity);
		OLED_ClearArea(83,4,2,8);
	}
}
extern uint8_t key_val,key_down,key_up,key_old;

void Test_Oled(void)
{
	OLED_Printf(0,0,OLED_8X16,"VAL: %d ",key_val);
	OLED_Printf(0,16,OLED_8X16,"down: %d ",key_down);
	OLED_Printf(0,32,OLED_8X16,"up: %d ",key_up);
	OLED_Printf(0,48,OLED_8X16,"old: %d ",key_old);
	OLED_Update();
}



OLED的驱动是江协的,MPU6050的驱动可以去复制火禾的,这两个代码我就不放上来了。

四、视频演示

火禾实验室多功能可编程手表复刻-HAL版

想要画板子和把板子打出来的同志如果用我的代码记得把引脚改一下。复刻的成本打板加元器件差不多一百左右。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐