镜子里的屏幕之三:软件设计
在上个帖子里,已经使用STM32CubeMX软件搭建了软件的框架,现在就来编写代码实现我们需要的功能。
镜子里的屏幕这个项目使用到的硬件设备有显示、RTC、MPU6050、按键等。下面分别进行软件的编写。
首先是显示部分,我使用的是7段LED显示器,扫描显示,只要把显示数据送到控制端口,再分别选通位选线就可以了。具体的端口分配见 镜子里的屏幕之一:电路设计,这里不再赘述。具体代码如下:
一、显示部分:
宏定义
/* 关闭LED显示,清除段选择 */ #define NUM_X do{ \ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin|SEG_7_G_Pin|SEG_7_dp_Pin, GPIO_PIN_RESET); \ HAL_GPIO_WritePin(GPIOC, Hour_Shiwei_Pin|Hour_Gewei_Pin|Min_Shiwei_Pin|Min_Gewei_Pin|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET); \ } while(0)
/* 显示 0..9 */ #define NUM_0 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin, GPIO_PIN_SET); } while(0) #define NUM_1 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_B_Pin|SEG_7_C_Pin, GPIO_PIN_SET); } while(0) #define NUM_2 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_3 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_4 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_5 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_6 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin|SEG_7_G_Pin|SEG_7_dp_Pin, GPIO_PIN_SET); } while(0) #define NUM_7 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin, GPIO_PIN_SET); } while(0) #define NUM_8 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_9 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) /* 镜像显示 0..9 */ #define NUM_F0 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin, GPIO_PIN_SET); } while(0) #define NUM_F1 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_B_Pin|SEG_7_C_Pin, GPIO_PIN_SET); } while(0) #define NUM_F2 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_F3 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_F4 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_E_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_F5 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_F6 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin|SEG_7_G_Pin|SEG_7_dp_Pin, GPIO_PIN_SET); } while(0) #define NUM_F7 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin, GPIO_PIN_SET); } while(0) #define NUM_F8 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_F_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) #define NUM_F9 do{ HAL_GPIO_WritePin(GPIOB, SEG_7_A_Pin|SEG_7_B_Pin|SEG_7_C_Pin|SEG_7_D_Pin|SEG_7_E_Pin|SEG_7_G_Pin, GPIO_PIN_SET); } while(0) /*------------------------------------------------------------------------------------------------------------------------------*/ /* 小时十位 */ #define HOUR_SHIWEI do{ HAL_GPIO_WritePin(GPIOC, Hour_Shiwei_Pin, GPIO_PIN_SET); } while(0) /* 小时个位 */ #define HOUR_GEWEI do{ HAL_GPIO_WritePin(GPIOC, Hour_Gewei_Pin, GPIO_PIN_SET); } while(0) /* 分钟十位 */ #define MIN_SHIWEI do{ HAL_GPIO_WritePin(GPIOC, Min_Shiwei_Pin, GPIO_PIN_SET); } while(0) /* 分钟个位 */ #define MIN_GEWEI do{ HAL_GPIO_WritePin(GPIOC, Min_Gewei_Pin, GPIO_PIN_SET); } while(0) /* 冒号 */ #define COLON_ON do{ HAL_GPIO_WritePin(GPIOC, _colon__Pin, GPIO_PIN_SET); } while(0) #define COLON_OFF do{ HAL_GPIO_WritePin(GPIOC, _colon__Pin, GPIO_PIN_RESET); } while(0) /*--------------------------------------------------------------------------------------------------------------------------------*/ /* 小时十位 */ #define H_SHIWEI 3 /* 小时个位 */ #define H_GEWEI 2 /* 分钟十位 */ #define M_SHIWEI 1 /* 分钟个位 */ #define M_GEWEI 0 #define LIGHTMAX 20000 //最大显示时间
显示函数:
亮度控制
void DelayLight(uint32_t sTime) { uint32_t tmp; //亮灯时间 tmp = LIGHTMAX; while (tmp > sTime) { tmp--; } NUM_X; //灭灯时间 while (tmp > 0) { tmp--; } }
正常显示
void LED_ShowNum(uint8_t Position, uint8_t _BCD) { uint32_t LightDelay = 0; uint32_t tmp; tmp = LIGHTMAX / 7; /* 关闭显示 */ // NUM_X; if (_BCD > 9 | Position > 3) return; //超出显示范围,退出 switch (_BCD) { case 0: NUM_0; LightDelay = LIGHTMAX - tmp * 6; //6 segment break; case 1: NUM_1; LightDelay = LIGHTMAX - tmp * 2; //2 segment break; case 2: NUM_2; LightDelay = LIGHTMAX - tmp * 5; //5 segment break; case 3: NUM_3; LightDelay = LIGHTMAX - tmp * 5; //5 segment break; case 4: NUM_4; LightDelay = LIGHTMAX - tmp * 4; //4 segment break; case 5: NUM_5; LightDelay = LIGHTMAX - tmp * 5; //5 segment break; case 6: NUM_6; LightDelay = LIGHTMAX - tmp * 6; //6 segment break; case 7: NUM_7; LightDelay = LIGHTMAX - tmp * 3; //3 segment break; case 8: NUM_8; LightDelay = LIGHTMAX - tmp * 7; //7 segment break; case 9: NUM_9; LightDelay = LIGHTMAX - tmp * 6; //6 segment break; // default: // NUM_X; } switch (Position) { case 0: MIN_GEWEI; break; case 1: MIN_SHIWEI; break; case 2: HOUR_GEWEI; break; case 3: HOUR_SHIWEI; break; // default: // NUM_X; } DelayLight(LightDelay); }
镜像显示
void LED_ShowNumMirror(uint8_t Position, uint8_t _BCD) { uint16_t LightDelay = 0; uint32_t tmp; tmp = LIGHTMAX / 7; /* 关闭显示 */ // NUM_X; if (_BCD > 9 | Position > 3) return; //超出显示范围,退出 switch (_BCD) { case 0: NUM_F0; LightDelay = LIGHTMAX - tmp * 6; break; case 1: NUM_F1; LightDelay = LIGHTMAX - tmp * 2; break; case 2: NUM_F2; LightDelay = LIGHTMAX - tmp * 5; break; case 3: NUM_F3; LightDelay = LIGHTMAX - tmp * 5; break; case 4: NUM_F4; LightDelay = LIGHTMAX - tmp * 4; break; case 5: NUM_F5; LightDelay = LIGHTMAX - tmp * 5; break; case 6: NUM_F6; LightDelay = LIGHTMAX - tmp * 6; break; case 7: NUM_F7; LightDelay = LIGHTMAX - tmp * 3; break; case 8: NUM_F8; LightDelay = LIGHTMAX - tmp * 7; break; case 9: NUM_F9; LightDelay = LIGHTMAX - tmp * 6; break; // default: // NUM_X; } switch (Position) { case 0: MIN_GEWEI; break; case 1: MIN_SHIWEI; break; case 2: HOUR_GEWEI; break; case 3: HOUR_SHIWEI; break; // default: // NUM_X; } DelayLight(LightDelay); } /* 日期 时间 镜像 显示日期 */ void ShowTime(RTC_DateTypeDef* sDate, RTC_TimeTypeDef* sTime, uint8_t ScreenMirror, uint16_t ShowDate) { void (* LED_Show)( uint8_t Position, uint8_t _BCD ); LED_Show = &LED_ShowNum; if (ScreenMirror == 1) LED_Show = &LED_ShowNumMirror; if (ShowDate == 0) {//显示时间 //Hour if ((HoursTwinkle && LightON) || !HoursTwinkle){ LED_Show(H_SHIWEI, (sTime->Hours & 0xF0) >> 4); LED_Show(H_GEWEI, sTime->Hours & 0x0F); } //Minutes if ((MinutesTwinkle && LightON) || !MinutesTwinkle){ LED_Show(M_SHIWEI, (sTime->Minutes & 0xF0) >> 4); LED_Show(M_GEWEI, sTime->Minutes & 0x0F); } } else {//显示日期 //Month if ((MonthTwinkle && LightON) || !MonthTwinkle){ LED_Show(H_SHIWEI, (sDate->Month & 0xF0) >> 4); LED_Show(H_GEWEI, sDate->Month & 0x0F); } //Date if ((DateTwinkle && LightON) || !DateTwinkle){ LED_Show(M_SHIWEI, (sDate->Date & 0xF0) >> 4); LED_Show(M_GEWEI, sDate->Date & 0x0F); } } }
二、RTC初始化
static void MX_RTC_Init(void) { /* USER CODE BEGIN RTC_Init 0 */ /* USER CODE END RTC_Init 0 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; /* USER CODE BEGIN RTC_Init 1 */ /* USER CODE END RTC_Init 1 */ /**Initialize RTC Only */ hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; hrtc.Init.SynchPrediv = 255; hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN Check_RTC_BKUP */ // if ((hrtc.Instance->CR & RTC_CR_BKP) == (uint32_t)RESET) if ((hrtc.Instance->ISR & RTC_ISR_INITS) == (uint32_t)RESET) { /* USER CODE END Check_RTC_BKUP */ /**Initialize RTC and set the Time and Date */ sTime.Hours = 0x12; sTime.Minutes = 0x0; sTime.Seconds = 0x0; sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; sTime.StoreOperation = RTC_STOREOPERATION_RESET; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK) { Error_Handler(); } sDate.WeekDay = RTC_WEEKDAY_MONDAY; sDate.Month = RTC_MONTH_NOVEMBER; sDate.Date = 0x12; sDate.Year = 0x18; if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK) { Error_Handler(); } /**Enable Calibration */ if (HAL_RTCEx_SetCalibrationOutPut(&hrtc, RTC_CALIBOUTPUT_1HZ) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN RTC_Init 2 */ // /* Disable the write protection for RTC registers */ // __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); // hrtc.Instance->CR |= (uint32_t)RTC_CR_BKP;//设置RTC初始化标志 // /* Enable the write protection for RTC registers */ // __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); } /* End if (hrtc.Instance->ISR & RTC_ISR_INITS) */ /* USER CODE END RTC_Init 2 */ }
这里添加了检测RTC初始化的代码,数据手册说可以通过ISR寄存器的INITS位来检测,经过实际测试,当年份是xx00年的时候,不管有没有初始化,INITS都是为零。不过1-99年也足够用了。也可以采用CR寄存器的BKP位做初始化标识,见注释掉的代码。
三、MPU6050的初始化与数据读取
//初始化MPU6050 void InitMPU6050(void) { HAL_Delay(100); Single_WriteI2C(PWR_MGMT_1, 0x00); //解除休眠状态 Single_WriteI2C(SMPLRT_DIV, 0x07); Single_WriteI2C(CONFIG, 0x06); Single_WriteI2C(GYRO_CONFIG, 0x18); Single_WriteI2C(ACCEL_CONFIG, 0x00); } //合成数据 uint16_t GetData(uint16_t REG_Address) { uint8_t H,L; H=Single_ReadI2C(REG_Address); L=Single_ReadI2C(REG_Address+1); return (H<<8)+L; } //读取数据 void MPU6050Read(short *mData, uint8_t reg_add) { mData[0] = (short) GetData(reg_add); mData[1] = (short) GetData(reg_add); mData[2] = (short) GetData(reg_add); }
四、I2C读写
//************************************** //向I2C设备写入一个字节数据 //************************************** void Single_WriteI2C(uint16_t REG_Address,uint8_t REG_data) { HAL_StatusTypeDef hal_status; hal_status = HAL_I2C_Mem_Write(&hi2c2, SlaveAddress, REG_Address, I2C_MEMADD_SIZE_8BIT, ®_data, sizeof(uint8_t), I2C_TIMEOUT); if (hal_status != HAL_OK) { Error_Handler(); } } //************************************** //从I2C设备读取一个字节数据 //************************************** uint8_t Single_ReadI2C(uint16_t REG_Address) { uint8_t REG_data; HAL_StatusTypeDef hal_status; hal_status = HAL_I2C_Mem_Read(&hi2c2, SlaveAddress, REG_Address, I2C_MEMADD_SIZE_8BIT, ®_data, sizeof(uint8_t), I2C_TIMEOUT); if (hal_status != HAL_OK) { Error_Handler(); } return REG_data; }
五、按键检测
宏定义及函数声明
#define KEYDOWNLTIME 3000 #define TREMBLINGTIME 10 #define DOWN 0x80 #define LONGDOWN 0x40 #define RELEASE 0x20 #define TREMBLING 0x10 #define VALID 0x01 #define NOKEYDOWN 0x00 uint8_t GetKeyState(void); void UserKeyTest(void); void ClearKeyState(void);
按键代码:
/************************************** * 按键状态 * bits_7=1,键按下 * bits_6=1,长按 * bits_5=1,键释放 * bits_4=1,键消抖 =0,没有消抖 * bits_3=1, * bits_2=1, * bits_1=1, * bits_0=1,按键有效 **************************************/ __IO uint8_t KeyState = 0; uint32_t KeyDownTimes = 0; uint32_t TremblingTimes = 0; void UserKeyTest(void) { static GPIO_PinState SetKey = GPIO_PIN_SET; GPIO_PinState SetKeytmp; if ((KeyState & VALID) != 0) return;//按键没有处理等待处理 if ((KeyState & TREMBLING) == 0) {//消抖 TremblingTimes++; if (TremblingTimes > TREMBLINGTIME) KeyState |= TREMBLING;//完成消抖动 return; } TremblingTimes = 0; SetKeytmp = HAL_GPIO_ReadPin(SetKey_GPIO_Port, SetKey_Pin); if (SetKey != SetKeytmp) {//开始消除按键抖动 KeyState &= ~TREMBLING; SetKey = SetKeytmp; return; } /* SetKey = 1,按键没有按下或已释放,SetKey = 0,按键按下 */ SetKey = SetKeytmp; if ((KeyState & LONGDOWN) != 0) {//如果是长按状态,则等待键释放 if (SetKey == RESET) { return; } else { KeyDownTimes = 0; KeyState = NOKEYDOWN; } } if (SetKey == RESET) {//有键按下 if ((KeyState & DOWN) != 0) { KeyDownTimes++; if (KeyDownTimes > KEYDOWNLTIME) { KeyState |= LONGDOWN;//长按 KeyState |= VALID;//长按 } } else { KeyState |= DOWN; } } else {//无键按下 KeyDownTimes = 0; if ((KeyState & DOWN) != 0) { KeyState |= RELEASE; KeyState |= VALID; } }/* END if (SetKey == RESET) */ } /* 返回按键状态 */ uint8_t GetKeyState(void) { return KeyState; } /* 解除按键状态 */ void ClearKeyState(void) { KeyDownTimes = 0; KeyState = KeyState & LONGDOWN; } 按键利用系统定时器1ms中断函数void SysTick_Handler(void)来进行检测 六、冒号闪烁: 利用RTC产生的秒中断实现,在外部中断程序里添加如下代码: void EXTI0_1_IRQHandler(void) { /* USER CODE BEGIN EXTI0_1_IRQn 0 */ //冒号闪烁 if (HAL_GPIO_ReadPin(LD2_GPIO_Port, LD2_Pin) != GPIO_PIN_RESET) { HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET); COLON_OFF; LightON = 0; } else { HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET); if (SecON) COLON_ON; LightON = 1; } /* USER CODE END EXTI0_1_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); /* USER CODE BEGIN EXTI0_1_IRQn 1 */ /* USER CODE END EXTI0_1_IRQn 1 */ }
七、主程序及变量定义
__IO uint8_t SecON = 1; //秒显示开关,默认显示 __IO uint8_t LightON = 0; //闪烁时灯的开关 __IO uint8_t YearTwinkle = 0; //年闪烁开关 __IO uint8_t MonthTwinkle = 0; //月闪烁开关 __IO uint8_t DateTwinkle = 0; //日闪烁开关 __IO uint8_t HoursTwinkle = 0; //时闪烁开关 __IO uint8_t MinutesTwinkle = 0; //分闪烁开关 __IO uint8_t ScreenMirror = 0; int main(void) { /* USER CODE BEGIN 1 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; uint8_t KeyState = 0; uint8_t screenmirror = 0; uint16_t showdatetime = 0; short acc_x[3], acc_y[3], acc_z[3], gy_x[3], gy_y[3], gy_z[3], temp; char tmp[50]; float temptmp; /* 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_I2C2_Init(); MX_ADC_Init(); /* Initialize interrupts */ MX_NVIC_Init(); /* USER CODE BEGIN 2 */ InitMPU6050(); ClearKeyState(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* 读取日期和时间 */ HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD); HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD); /* 读取MPU6050 */ MPU6050Read(acc_x, ACCEL_XOUT_H); MPU6050Read(acc_y, ACCEL_YOUT_H); MPU6050Read(acc_z, ACCEL_ZOUT_H); MPU6050Read(gy_x, GYRO_XOUT_H); MPU6050Read(gy_y, GYRO_YOUT_H); MPU6050Read(gy_z, GYRO_ZOUT_H); temp = (short) GetData(TEMP_OUT_H); // sprintf(tmp, "Acc: %8d, %8d, %8d \r\n", acc_x[0], acc_x[1], acc_x[2]); // temptmp = ((double) temp / 340.0) + 36.53; if (acc_x[2] < 0) screenmirror = 0; else screenmirror = 1; // sprintf(tmp, "TEMP: %8.2f \r\n", temptmp); /* 按键处理 */ KeyState = GetKeyState(); if (((KeyState & VALID) != 0) && ((KeyState & DOWN) != 0)) {//短按一下显示月、日 showdatetime = 500; /* 按键处理完毕解除按键状态 */ ClearKeyState(); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ ShowTime(&sDate, &sTime, screenmirror, showdatetime); //显示 if (showdatetime != 0) showdatetime--; } /* USER CODE END 3 */ }
这里只利用了MPU6050的X轴的数据,没有进行具体的分析计算。说实话吧,我对MPU6050不太熟,呵呵。
GPIO、I2C、NVIC等的初始化基本是CubeMX自动生成的,默认就行
代码里大部分都有注释。
最后上两张照片
正常显示
镜像显示
至此这个项目就算完成了,后续需要什么功能就添加,在此也不多说了。
感谢你看完这个,错误遗漏之处在所难免,多包涵。谢谢!