前言


历届题目

历届题目百度网盘

省赛程序题

4届-双路输出控制器

题目要求

通过题目知道需要用到的外设 对应管脚
LCD /
LED PC8~PC15PD2
USART1(题目要求USART2) PA9PA10
按键 PB0~PB2PA0
EEPROM PB6PB7
PWM PA1PA2

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "i2c_hal.h"
#include "lcd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdbool.h"
#include "rtc.h"

#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define ADDR 0x00	//存储地址

#define KEY_TIME 10

#define C1_STATE 0
#define C2_STATE 2
#define C3_STATE 4
#define C4_STATE 6
#define C5_STATE 8
#define C6_STATE 9

typedef struct
{
    uint32_t TASK_KEY;
} TASK_TIMESTypeDef;

typedef struct
{
    bool KEY1_DOWN_FLAG;	//按键1
    bool KEY2_DOWN_FLAG;	//按键2
    bool KEY3_DOWN_FLAG;	//按键3
    bool KEY4_DOWN_FLAG;	//按键4
    bool DATA_OVER_FLAG;	//接收串口数据处理完成标志位
    bool Alarm_OVER_FLAG;	//闹钟到时标志位
    bool RUN_START_FLAG;	//脉冲开始输出计数标志位
    bool RUN_START_OVER_FLAG;	//脉冲输出计数完成标志位
} DATA_TypeDef;

typedef struct
{
    uint16_t PA1_duty;
    uint16_t PA2_duty;
    uint8_t RX_hour;	//时
    uint8_t RX_min;	//分
    uint8_t RX_sec;	//秒
    uint8_t CHxNumber;	//哪个管脚
    uint8_t RunTime_Count;	//运行时间
} PARAMETER_TypeDef;

void TASK_init(void);
void LCD_Dis1(void);
void Add_Space(uint8_t date[][20], uint8_t State);
void LCD_function(void);
void LED_Dis(uint8_t num, uint8_t swch);
void TASK_TIME_init(void);
uint32_t get_task_time(uint32_t time);
uint32_t get_time(void);
void RtcGetTime_function(void);
void FlagRun_function(void);
void KEY_function(void);
uint8_t KEY_Proc(void);
void PWM_Out(uint16_t Pin);
void GPIO_Out(uint16_t Pin, GPIO_PinState PinState);
void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len);
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len);
void Parameter_Read(void);
void Parameter_Write(void);
void USART1_function(void);
int8_t Hour_DealWith(uint8_t *date);
int8_t Min_DealWith(uint8_t *date);
int8_t Sec_DealWith(uint8_t *date);
void Alarm_SetTime(uint16_t hour, uint16_t min, uint16_t sec);

#endif
my.c
(初始化部分)
void TASK_init(void)
{
    LCD_Init();
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LED_Dis(0xFF, RESET);
    I2CInit();
    HAL_TIM_Base_Start_IT(&htim6);
    HAL_RTC_GetTime(&hrtc, &my_sTime, RTC_FORMAT_BIN);	//获取时间
    HAL_RTC_GetDate(&hrtc, &my_sDate, RTC_FORMAT_BIN);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
#if 0	//默认占空比是10 10(需要重置EEPROM里的数据时打开)
    Parameter.PA1_duty = 10;
    Parameter.PA2_duty = 10;
    Parameter_Write();
#endif
    Parameter_Read();
    LCD_Dis1();
}
(时间片部分)
uint32_t get_time(void)
{
    return sys_time;
}

uint32_t get_task_time(uint32_t time)
{
    if(time > get_time())
    {
        time--;
    }
    else
    {
        time = 0;
    }
    return time;
}

void TASK_TIME_init(void)
{
    Task_time.TASK_KEY = get_time() + KEY_TIME;
}
(LED,其他部分)
void LED_Dis(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

//配置低电平
void GPIO_Out(uint16_t Pin, GPIO_PinState PinState)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = 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);

    HAL_GPIO_WritePin(GPIOA, Pin, PinState);	//拉高/拉低
}

//配置PWM
void PWM_Out(uint16_t Pin)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
(LCD部分)
void LCD_function(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        if(LCD_State & (1 << i))
        {
            LCD_State &= (~(0x01 << i));
            LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
        }
    }

    if(Data.DATA_OVER_FLAG)
    {
        Data.DATA_OVER_FLAG = 0;
        memset(LCD_Line_BUFF[C6_STATE], 0, sizeof(LCD_Line_BUFF[C6_STATE]));
        sprintf((char *)LCD_Line_BUFF[C6_STATE], "  %02d:%02d:%02d-PA%d-%dS", Parameter.RX_hour, Parameter.RX_min, Parameter.RX_sec, Parameter.CHxNumber, Parameter.RunTime_Count);
        Add_Space(LCD_Line_BUFF, C6_STATE);
        LCD_DisplayStringLine(C6_STATE * 24, LCD_Line_BUFF[C6_STATE]);
        Alarm_SetTime(Parameter.RX_hour, Parameter.RX_min, Parameter.RX_sec);	//设置闹钟
    }

    //以下是刷新输出通道那行
    memset(LCD_Line_BUFF[C4_STATE], 0, sizeof(LCD_Line_BUFF[C4_STATE]));	//清空数组
    if(KEY1_Index && (!KEY3_Index))	//PA1输出PA2没输出
    {
        sprintf((char *)LCD_Line_BUFF[C4_STATE], "  Channel: PA1");
        Add_Space(LCD_Line_BUFF, C4_STATE);
        LCD_DisplayStringLine(C4_STATE * 24, LCD_Line_BUFF[C4_STATE]);
    }
    else if(KEY3_Index && (!KEY1_Index))	//PA2输出PA1没输出
    {
        sprintf((char *)LCD_Line_BUFF[C4_STATE], "  Channel: PA2");
        Add_Space(LCD_Line_BUFF, C4_STATE);
        LCD_DisplayStringLine(C4_STATE * 24, LCD_Line_BUFF[C4_STATE]);
    }
    else if(KEY1_Index && KEY3_Index)	//两个同时输出
    {
        sprintf((char *)LCD_Line_BUFF[C4_STATE], "  Channel: PA1,PA2");
        Add_Space(LCD_Line_BUFF, C4_STATE);
        LCD_DisplayStringLine(C4_STATE * 24, LCD_Line_BUFF[C4_STATE]);
    }
    else	//都没输出
    {
        sprintf((char *)LCD_Line_BUFF[C4_STATE], "  Channel: None");
        Add_Space(LCD_Line_BUFF, C4_STATE);
        LCD_DisplayStringLine(C4_STATE * 24, LCD_Line_BUFF[C4_STATE]);
    }
}

void LCD_Dis1(void)
{
    sprintf((char *)LCD_Line_BUFF[C1_STATE], "  PWM_PA1:%d%%", Parameter.PA1_duty);
    Add_Space(LCD_Line_BUFF, C1_STATE);
    sprintf((char *)LCD_Line_BUFF[C2_STATE], "  PWM_PA2:%d%%", Parameter.PA2_duty);
    Add_Space(LCD_Line_BUFF, C3_STATE);
    sprintf((char *)LCD_Line_BUFF[C3_STATE], "  Time:%02d:%02d:%02d", my_sTime.Hours, my_sTime.Minutes, my_sTime.Seconds);
    Add_Space(LCD_Line_BUFF, C3_STATE);
    sprintf((char *)LCD_Line_BUFF[C4_STATE], "  Channel: None");
    Add_Space(LCD_Line_BUFF, C4_STATE);
    sprintf((char *)LCD_Line_BUFF[C5_STATE], "  Command:");
    Add_Space(LCD_Line_BUFF, C5_STATE);
    sprintf((char *)LCD_Line_BUFF[C6_STATE], "       None");
    Add_Space(LCD_Line_BUFF, C6_STATE);

    LCD_State |= (0x01 << C1_STATE);
    LCD_State |= (0x01 << C2_STATE);
    LCD_State |= (0x01 << C3_STATE);
    LCD_State |= (0x01 << C4_STATE);
    LCD_State |= (0x01 << C5_STATE);
    LCD_State |= (0x01 << C6_STATE);
}

void Add_Space(uint8_t date[][20], uint8_t State)
{
    for(uint8_t i = 0; i < 20; i++)
    {
        if(date[State][i] == '\0')
        {
            date[State][i] = ' ';
        }
    }
}
(RTC+闹钟部分)
//闹钟设置时间
void Alarm_SetTime(uint16_t hour, uint16_t min, uint16_t sec)
{
    RTC_AlarmTypeDef sAlarm = {0};

    sAlarm.AlarmTime.Hours = hour;
    sAlarm.AlarmTime.Minutes = min;
    sAlarm.AlarmTime.Seconds = sec;
    sAlarm.AlarmTime.SubSeconds = 0x0;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 0x1;
    sAlarm.Alarm = RTC_ALARM_A;
    if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
    {
        Error_Handler();
    }
}

//回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    if(Parameter.RunTime_Count != 0)
    {
        Data.Alarm_OVER_FLAG = 1;
    }
}


void RtcGetTime_function(void)
{
    static int8_t Old_Time = -1;

    HAL_RTC_GetTime(&hrtc, &my_sTime, RTC_FORMAT_BIN);	//获取时间
    HAL_RTC_GetDate(&hrtc, &my_sDate, RTC_FORMAT_BIN);

    if(Old_Time != my_sTime.Seconds)
    {
        sprintf((char *)LCD_Line_BUFF[C3_STATE], "  Time:%02d:%02d:%02d", my_sTime.Hours, my_sTime.Minutes, my_sTime.Seconds);
        Add_Space(LCD_Line_BUFF, C3_STATE);
        LCD_State |= (0x01 << C3_STATE);
    }
    Old_Time = my_sTime.Seconds;
}
(按键部分)
uint8_t KEY_Proc(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }
    return 0;
}



void KEY_function(void)
{
    static uint8_t Old_Value;

    if(0 == get_task_time(Task_time.TASK_KEY))
    {
        Task_time.TASK_KEY = get_time() + KEY_TIME;
        KEY_VALUE = KEY_Proc();
        KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ Old_Value);
        KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ Old_Value);
        Old_Value = KEY_VALUE;

        switch(KEY_UP)
        {
        case 1:
        {
            Data.KEY1_DOWN_FLAG = 1;
            break;
        }
        case 2:
        {
            Data.KEY2_DOWN_FLAG = 1;
            break;
        }
        case 3:
        {
            Data.KEY3_DOWN_FLAG = 1;
            break;
        }
        case 4:
        {
            Data.KEY4_DOWN_FLAG = 1;
            break;
        }
        default:
            break;
        }
    }
}
(标志位执行功能部分)
void FlagRun_function(void)
{

    if(Data.KEY1_DOWN_FLAG)
    {
        Data.KEY1_DOWN_FLAG = 0;
        KEY1_Index = !KEY1_Index;	//取反
        if(KEY1_Index)	//PA1输出脉冲
        {
            PWM_Out(GPIO_PIN_1);
            TIM2->CCR2 = Parameter.PA1_duty * 10;
            LED_Dis(0x01, SET);	//LED1亮
        }
        else	//PA1输出低电平
        {
            GPIO_Out(GPIO_PIN_1, GPIO_PIN_RESET);
            LED_Dis(0x01, RESET);	//LED1灭
        }
    }
    if(Data.KEY2_DOWN_FLAG)	//调整占空比并且保存到EEPROM
    {
        Data.KEY2_DOWN_FLAG = 0;
        Parameter.PA1_duty += 10;
        if(Parameter.PA1_duty > 90)
        {
            Parameter.PA1_duty = 10;
        }
        if(KEY1_Index)	//在PA1输出脉冲时才写入寄存器否则不写入
        {
            TIM2->CCR2 = Parameter.PA1_duty * 10;
        }
        sprintf((char *)LCD_Line_BUFF[C1_STATE], "  PWM_PA1:%d%%", Parameter.PA1_duty);
        Add_Space(LCD_Line_BUFF, C1_STATE);
        LCD_State |= (0x01 << C1_STATE);
        Parameter_Write();
    }
    if(Data.KEY3_DOWN_FLAG)
    {
        Data.KEY3_DOWN_FLAG = 0;
        KEY3_Index = !KEY3_Index;	//取反
        if(KEY3_Index)	//PA2输出脉冲
        {
            PWM_Out(GPIO_PIN_2);
            TIM2->CCR3 = Parameter.PA2_duty * 10;
            LED_Dis(0x02, SET);	//LED2亮
        }
        else	//PA2输出低电平
        {
            GPIO_Out(GPIO_PIN_2, GPIO_PIN_RESET);
            LED_Dis(0x02, RESET);	//LED2灭
        }
    }
    if(Data.KEY4_DOWN_FLAG)
    {
        Data.KEY4_DOWN_FLAG = 0;
        Parameter.PA2_duty += 10;
        if(Parameter.PA2_duty > 90)
        {
            Parameter.PA2_duty = 10;
        }
        if(KEY3_Index)	//在PA2输出脉冲时才写入寄存器否则不写入
        {
            TIM2->CCR3 = Parameter.PA2_duty * 10;
        }
        sprintf((char *)LCD_Line_BUFF[C2_STATE], "  PWM_PA2:%d%%", Parameter.PA2_duty);
        Add_Space(LCD_Line_BUFF, C2_STATE);
        LCD_State |= (0x01 << C2_STATE);
        Parameter_Write();
    }
    if(Data.Alarm_OVER_FLAG)
    {
        Data.Alarm_OVER_FLAG = 0;
        if(1 == Parameter.CHxNumber)
        {
            PWM_Out(GPIO_PIN_1);
            TIM2->CCR2 = Parameter.PA1_duty * 10;	//需要写入不然不会触发
            LED_Dis(0x01, SET);	//LED1亮
        }
        else if(2 == Parameter.CHxNumber)
        {
            PWM_Out(GPIO_PIN_2);
            TIM2->CCR3 = Parameter.PA2_duty * 10;
            LED_Dis(0x02, SET);	//LED2亮
        }
        Data.RUN_START_FLAG = 1;
    }
    if(Data.RUN_START_OVER_FLAG)	//脉冲输出完成
    {
        Data.RUN_START_OVER_FLAG = 0;
        if(1 == Parameter.CHxNumber)
        {
            GPIO_Out(GPIO_PIN_1, GPIO_PIN_RESET);	//输出低电平
            LED_Dis(0x01, RESET);	//LED1灭
        }
        else if(2 == Parameter.CHxNumber)
        {
            GPIO_Out(GPIO_PIN_2, GPIO_PIN_RESET);	//输出低电平
            LED_Dis(0x02, RESET);	//LED2灭
        }
    }
}
(USAER数据处理+时间处理部分)
//接收的数据格式:hh:mm:ss-PAx-yS(0<y<10)
//不考虑换行
void USART1_function(void)
{
    int8_t hh, mm, ss, x, y;

    if(RX_Over_Flag)
    {
        RX_Over_Flag = 0;
        if(15 == RX_LEN)
        {
            //先判断固定的那些字符是否相等
            if(':' == RX_BUFF[2] && ':' == RX_BUFF[5] && '-' == RX_BUFF[8] && 'P' == RX_BUFF[9] && 'A' == RX_BUFF[10] && '-' == RX_BUFF[12] && 'S' == RX_BUFF[14])
            {
                hh = Hour_DealWith(RX_BUFF);	//小时处理
                if(hh != -1)
                {
                    mm = Min_DealWith(RX_BUFF);	//分钟处理
                    if(mm != -1)
                    {
                        ss = Sec_DealWith(RX_BUFF);	//秒处理
                        if(ss != -1)
                        {
                            if(('1' == RX_BUFF[11]) || ('2' == RX_BUFF[11]))
                            {
                                if(RX_BUFF[13] > '0' && RX_BUFF[13] <= '9')
                                {
                                    Data.DATA_OVER_FLAG = 1;
                                    Parameter.RX_hour = hh;
                                    Parameter.RX_min = mm;
                                    Parameter.RX_sec = ss;
                                    Parameter.CHxNumber = RX_BUFF[11] - '0';
                                    Parameter.RunTime_Count = RX_BUFF[13] - '0';
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    HAL_UART_Receive_DMA(&huart1, RX_BUFF, RX_MAX_LEN);
}

//小时处理
int8_t Hour_DealWith(uint8_t *date)
{
    uint8_t a, b, c;
    a = date[0] - '0';
    b = date[1] - '0';
    c = a * 10 + b;

    if(a >= 0 && a <= 2)
    {
        if(b >= 0 && b <= 9)
        {
            if(c >= 0 && c <= 23)
            {
                return c;
            }
        }
    }
    return -1;
}
//分钟处理
int8_t Min_DealWith(uint8_t *date)
{
    uint8_t a, b, c;
    a = date[3] - '0';
    b = date[4] - '0';
    c = a * 10 + b;

    if(a >= 0 && a <= 5)
    {
        if(b >= 0 && b <= 9)
        {
            if(c >= 0 && c <= 59)
            {
                return c;
            }
        }
    }
    return -1;
}
//秒处理
int8_t Sec_DealWith(uint8_t *date)
{
    uint8_t a, b, c;
    a = date[6] - '0';
    b = date[7] - '0';
    c = a * 10 + b;

    if(a >= 0 && a <= 5)
    {
        if(b >= 0 && b <= 9)
        {
            if(c >= 0 && c <= 59)
            {
                return c;
            }
        }
    }
    return -1;
}
(EEPROM部分)
//写数据
void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    while(len--)
    {
        I2CSendByte(*date++);
        I2CWaitAck();
        addr++;
        if((addr & 0x07) == 0)
        {
            break;
        }
    }
    I2CStop();
    HAL_Delay(5);
}
//读数据
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    I2CStart();
    I2CSendByte(0xA1);
    I2CWaitAck();
    while(len--)
    {
        *date++ = I2CReceiveByte();
        if(len)
        {
            I2CSendAck();
        }
        else
        {
            I2CSendNotAck();
        }
    }
    I2CStop();
}

//写入参数EEPROM
void Parameter_Write(void)
{
    uint8_t temp[2];
    temp[0] = Parameter.PA1_duty;
    temp[1] = Parameter.PA2_duty;

    EEPROM_Write(ADDR, temp, 2);
}

//读取参数EEPROM
void Parameter_Read(void)
{
    uint8_t temp[2];
    EEPROM_Read(ADDR, temp, 2);
    Parameter.PA1_duty = temp[0];
    Parameter.PA2_duty = temp[1];
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t timer_x;

    if(htim == &htim6)
    {
        sys_time++;

        if(Data.RUN_START_FLAG)
        {
            timer_x++;
            if((Parameter.RunTime_Count * 1000) == timer_x)	//运行到规定秒数
            {
                timer_x = 0;
                Data.RUN_START_FLAG = 0;
                Data.RUN_START_OVER_FLAG = 1;
            }
        }
    }
}
main.c
#include "my.h"


int main(void)
{
    TASK_init();

    while(1)
    {
        KEY_function();
        LCD_function();
        RtcGetTime_function();
        FlagRun_function();
        USART1_function();
    }
}
usart.h(系统自带)
#include "main.h"

#define RX_MAX_LEN	50

extern uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
extern uint8_t RX_LEN;	//接收长度
extern uint8_t RX_Over_Flag;	//接收完成标志位
extern DMA_HandleTypeDef hdma_usart1_rx;
usart.c(系统自带)
uint8_t RX_BUFF[RX_MAX_LEN];
uint8_t RX_LEN = 0;
uint8_t RX_Over_Flag = 0;

void MX_USART1_UART_Init(void)
{
    ...
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);	//接收中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);	//DMA接收
}

void USART1_IRQHandler(void)
{
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);	//清除标志位
        HAL_UART_DMAStop(&huart1);
        RX_LEN = RX_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);	//获取长度
        RX_Over_Flag = 1;
    }
    HAL_UART_IRQHandler(&huart1);
}

实验现象

5届-双通道方波频率检测与倍频输出

题目要求

通过题目知道需要用到的外设 对应管脚
LCD /
LED PC8~PC15PD2
USART1 PA9PA10
按键 PB0~PB2PA0
EEPROM PB6PB7
通道输入(输入捕获) PA1PA2
倍频输出(输出比较) PA6PA7

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "lcd.h"
#include "i2c_hal.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "stdbool.h"


#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define ADDR 0x00

#define C1_STATE 2
#define C2_STATE 4
#define C3_STATE 6
#define C4_STATE 8
#define C5_STATE 9

#define KEY_TIME 10	//按键扫描时间
#define PWMIC_TIME 100	//PWM捕获时间

typedef struct
{
    uint32_t CH1_pluse_val;	//通道1总频率
    uint32_t CH1_duty;	//通道1有效电平值
    uint32_t CH2_pluse_val;	//通道2总频率
    uint32_t CH2_duty;	//通道2有效电平值
} PWM_OCTypeDef;

typedef struct
{
    bool KEY1_DOWN_Flag;	//按键1
    bool KEY2_DOWN_Flag;	//按键2
    bool KEY3_DOWN_Flag;	//按键3
    bool KEY4_DOWN_Flag;	//按键4
    bool UARTorLOCAL_Flag;	//串口还是本地切换标志位
    bool CH_SWITCH_Flag;	//通道切换
} DATA_TypeDef;

typedef struct
{
    uint32_t CH2_IC_freq;	//输入捕获通道2的频率
    uint32_t CH3_IC_freq;	//输入捕获通道3的频率
    uint8_t Mult_CH2;	//通道2倍频数(默认2)
    uint8_t Mult_CH3;	//通道3倍频数
} PWM_ICTypeDef;

typedef struct
{
    uint32_t CH_ONE;	//记录4次的值
    uint32_t CH_TWO;
    uint32_t CH_THREE;
    uint32_t CH_FOUR;
    uint8_t RUN_FLAG;	//运行标志位
} PWM_PARAMETER_TypeDef;

typedef struct
{
    uint32_t TASK_KEY;
    uint32_t TASK_TIMIC;
} TASK_TIMESTypeDef;

void TASK_init(void);
void LCD_Dis(void);
void LCD_function(void);
void LED_Write_all(uint8_t num);
uint32_t get_time_task(uint32_t time);
uint32_t get_time(void);
void TASK_TIME_init(void);
void KEY_function(void);
uint8_t KEY_Proc(void);
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len);
void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len);
void PWM_Read(void);
void PWM_Wirte(void);
void USART1_function(void);
void FlagRun_function(void);
void LED_Dis(uint8_t num, uint8_t swch);
void TIM_IN_function(void);
void Add_Space(uint8_t date[][20], uint8_t index);
void PWM_SET_PA7(uint32_t freq, uint16_t duty);
void PWM_SET_PA6(uint32_t freq, uint16_t duty);
void GPIO_OUT(uint16_t pin,GPIO_PinState PinState);
void PWM_OC(uint16_t pin);

#endif
my.c
(初始化部分)
PWM_PARAMETER_TypeDef TIM2_CH2 = {0};	//捕获通道2
PWM_PARAMETER_TypeDef TIM2_CH3 = {0};	//捕获通道3
PWM_OCTypeDef PWM_oc = {0};
DATA_TypeDef Data = {0};
PWM_ICTypeDef PWM_ic = {0};
TASK_TIMESTypeDef TASK_TIME;
uint32_t sys_time = 0;
uint8_t LCD_Line_BUFF[10][20] = {""};
uint16_t LCD_State = 0xFFFF;
uint8_t CAPTURE_MODE = 1;
uint8_t KEY_UP, KEY_DOWN, KEY_VALUE;

void TASK_init(void)
{
    LCD_Init();
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LED_Dis(0xFF, RESET);
    I2CInit();
    HAL_TIM_Base_Start_IT(&htim6);
    TASK_TIME_init();
    PWM_Read();
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);	//开启捕获
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_3);	//开启捕获
    HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);
    HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);
    LCD_Dis();
}
(时间片部分)
uint32_t get_time_task(uint32_t time)
{
    if(time > get_time())
    {
        time--;
    }
    else
    {
        time = 0;
    }
    return time;
}

uint32_t get_time(void)
{
    return sys_time;
}

void TASK_TIME_init(void)
{
    TASK_TIME.TASK_KEY = get_time() + KEY_TIME;
    TASK_TIME.TASK_TIMIC = get_time() + PWMIC_TIME;
}
(PWM输入捕获部分)
void TIM_IN_function(void)
{
    if(0 == get_time_task(TASK_TIME.TASK_TIMIC))
    {

        TASK_TIME.TASK_TIMIC = get_time() + PWMIC_TIME;

        if(5 == TIM2_CH2.RUN_FLAG)
        {
            PWM_ic.CH2_IC_freq = 1000000 / (TIM2_CH2.CH_FOUR - TIM2_CH2.CH_TWO);
            if((PWM_ic.CH2_IC_freq > 5000) || (PWM_ic.CH2_IC_freq <50))	//大于50KHz小于50Hz
            {
                //PWM_SET_PA6(1000 - 1, 0);	//输出低电平
				GPIO_OUT(GPIO_PIN_6,GPIO_PIN_RESET);	//输出低电平
            }
            else
            {
				PWM_OC(GPIO_PIN_6);	//输出PWM
                HAL_TIM_OC_Stop_IT(&htim3, TIM_CHANNEL_1);
                PWM_SET_PA6(PWM_ic.CH2_IC_freq * PWM_ic.Mult_CH2, 50);
                HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);
            }
            sprintf((char *)LCD_Line_BUFF[C1_STATE], " Channel(1):%dHz", PWM_ic.CH2_IC_freq);	//PA1捕获
            Add_Space(LCD_Line_BUFF, C1_STATE);
            LCD_State |= (0x01 << C1_STATE);
            TIM2_CH2.RUN_FLAG = 0;
            HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);	//重新开启
        }
        if(5 == TIM2_CH3.RUN_FLAG)
        {
            if((PWM_ic.CH3_IC_freq > 50000) || (PWM_ic.CH3_IC_freq <50))	//大于50KHz小于50Hz
            {
                PWM_SET_PA7(1000 - 1, 0);	//输出低电平
            }
            else
            {
                HAL_TIM_OC_Stop_IT(&htim3, TIM_CHANNEL_2);
                PWM_SET_PA7(PWM_ic.CH3_IC_freq * PWM_ic.Mult_CH3, 50);
                HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);
            }
            PWM_ic.CH3_IC_freq = 1000000 / (TIM2_CH3.CH_FOUR - TIM2_CH3.CH_TWO);
            sprintf((char *)LCD_Line_BUFF[C3_STATE], " Channel(2):%dHz", PWM_ic.CH3_IC_freq);	//PA1捕获
            Add_Space(LCD_Line_BUFF, C3_STATE);
            LCD_State |= (0x01 << C3_STATE);
            TIM2_CH3.RUN_FLAG = 0;
            HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_3);	//重新开启
        }
    }
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim == &htim2)
    {
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            if(CAPTURE_MODE)
            {
                switch(TIM2_CH2.RUN_FLAG)
                {
                case 0:	//记录第一次的上升沿值 设置下一次为下降沿
                {
                    TIM2_CH2.CH_ONE = TIM2->CCR2;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING);	//下降沿
                    TIM2_CH2.RUN_FLAG = 1;
                    break;
                }
                case 1:	//记录第一次的下降沿值 设置下一次为上升沿
                {
                    TIM2_CH2.CH_TWO = TIM2->CCR2;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING);	//上升沿
                    TIM2_CH2.RUN_FLAG = 2;
                    break;
                }
                case 2:	//记录第二次的上升沿值 设置下一次为下降沿
                {
                    TIM2_CH2.CH_THREE = TIM2->CCR2;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING);	//下降沿
                    TIM2_CH2.RUN_FLAG = 3;
                    break;
                }
                case 3:	//记录第二次的下降沿值 停止捕获 清0
                {
                    TIM2_CH2.CH_FOUR = TIM2->CCR2;
                    HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2);
                    __HAL_TIM_SetCounter(&htim2, 0);
                    TIM2_CH2.RUN_FLAG = 5;
                    break;
                }
                default:
                    break;
                }
            }
        }
        else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
        {
            if(!CAPTURE_MODE)
            {
                switch(TIM2_CH3.RUN_FLAG)
                {
                case 0:	//记录第一次的上升沿值 设置下一次为下降沿
                {
                    TIM2_CH3.CH_ONE = TIM2->CCR3;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_3, TIM_INPUTCHANNELPOLARITY_FALLING);	//下降沿
                    TIM2_CH3.RUN_FLAG = 1;
                    break;
                }
                case 1:	//记录第一次的下降沿值 设置下一次为上升沿
                {
                    TIM2_CH3.CH_TWO = TIM2->CCR3;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_3, TIM_INPUTCHANNELPOLARITY_RISING);	//上升沿
                    TIM2_CH3.RUN_FLAG = 2;
                    break;
                }
                case 2:	//记录第二次的上升沿值 设置下一次为下降沿
                {
                    TIM2_CH3.CH_THREE = TIM2->CCR3;
                    __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_3, TIM_INPUTCHANNELPOLARITY_FALLING);	//下降沿
                    TIM2_CH3.RUN_FLAG = 3;
                    break;
                }
                case 3:	//记录第二次的下降沿值 停止捕获 清0
                {
                    TIM2_CH3.CH_FOUR = TIM2->CCR3;
                    HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_3);
                    __HAL_TIM_SetCounter(&htim2, 0);
                    TIM2_CH3.RUN_FLAG = 5;
                    break;
                }
                default:
                    break;
                }
            }
        }

    }
}
(PWM输出比较部分)
//输出比较
void PWM_SET_PA6(uint32_t freq, uint16_t duty)
{
    PWM_oc.CH1_pluse_val = 1000000 / freq;	//总频率
    PWM_oc.CH1_duty = PWM_oc.CH1_pluse_val * duty / 100;	//有效电平的值
    TIM3->CCR1 = PWM_oc.CH1_pluse_val;
}
void PWM_SET_PA7(uint32_t freq, uint16_t duty)
{
    PWM_oc.CH2_pluse_val = 1000000 / freq;	//总频率
    PWM_oc.CH2_duty = PWM_oc.CH2_pluse_val * duty / 100;	//有效电平的值
    TIM3->CCR2 = PWM_oc.CH2_pluse_val;
}


void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t pwm1 = 1, pwm2 = 1;

    if(htim == &htim3)
    {
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            if(pwm1)
            {
                TIM3->CCR1 += PWM_oc.CH1_duty;	//高电平有效时间
            }
            else
            {
                TIM3->CCR1 += (PWM_oc.CH1_pluse_val - PWM_oc.CH1_duty);
            }
            pwm1 = !pwm1;
        }
        else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            if(pwm2)
            {
                TIM3->CCR2 += PWM_oc.CH2_duty;	//高电平有效时间
            }
            else
            {
                TIM3->CCR2 += (PWM_oc.CH2_pluse_val - PWM_oc.CH2_duty);
            }
            pwm2 = !pwm2;
        }
    }
}
(标志位执行功能部分)
void FlagRun_function(void)
{
    if(Data.KEY1_DOWN_Flag)	//按键1
    {
        Data.KEY1_DOWN_Flag = 0;
        Data.UARTorLOCAL_Flag = !Data.UARTorLOCAL_Flag;
        if(Data.UARTorLOCAL_Flag)
        {
            LED_Dis(0x01, SET);
        }
        else
        {
            LED_Dis(0x01, RESET);
        }
    }
    if(Data.KEY2_DOWN_Flag)
    {
        Data.KEY2_DOWN_Flag = 0;
        Data.CH_SWITCH_Flag = !Data.CH_SWITCH_Flag;
        if(Data.CH_SWITCH_Flag)
        {
            sprintf((char *)LCD_Line_BUFF[C5_STATE], "                  2");	//当前通道号
        }
        else
        {
            sprintf((char *)LCD_Line_BUFF[C5_STATE], "                  1");	//当前通道号
        }
        LCD_State |= (0x01 << C5_STATE);
    }
    if(Data.KEY3_DOWN_Flag)
    {
        Data.KEY3_DOWN_Flag = 0;
        if(!Data.CH_SWITCH_Flag)	//通道1
        {
            PWM_ic.Mult_CH2--;
            if(PWM_ic.Mult_CH2 <= 1)
            {
                PWM_ic.Mult_CH2 = 1;
            }
            sprintf((char *)LCD_Line_BUFF[C2_STATE], " N(1):%d           ", PWM_ic.Mult_CH2);	//PA6倍频
            LCD_State |= (0x01 << C2_STATE);
        }
        else	//通道2
        {
            PWM_ic.Mult_CH3--;
            if(PWM_ic.Mult_CH3 <= 1)
            {
                PWM_ic.Mult_CH3 = 1;
            }
            sprintf((char *)LCD_Line_BUFF[C4_STATE], " N(2):%d           ", PWM_ic.Mult_CH3);	//PA6倍频
            LCD_State |= (0x01 << C4_STATE);
        }
        PWM_Wirte();	//存储
    }
    if(Data.KEY4_DOWN_Flag)
    {
        Data.KEY4_DOWN_Flag = 0;
        if(!Data.CH_SWITCH_Flag)	//通道1
        {
            PWM_ic.Mult_CH2++;
            if(PWM_ic.Mult_CH2 >= 10)
            {
                PWM_ic.Mult_CH2 = 10;
            }
            sprintf((char *)LCD_Line_BUFF[C2_STATE], " N(1):%d           ", PWM_ic.Mult_CH2);	//PA6倍频
            LCD_State |= (0x01 << C2_STATE);
        }
        else	//通道2
        {
            PWM_ic.Mult_CH3++;
            if(PWM_ic.Mult_CH3 >= 10)
            {
                PWM_ic.Mult_CH3 = 10;
            }
            sprintf((char *)LCD_Line_BUFF[C4_STATE], " N(2):%d          ", PWM_ic.Mult_CH3);	//PA6倍频
            LCD_State |= (0x01 << C4_STATE);
        }
        PWM_Wirte();	//存储
    }
}
(USART处理数据部分)
void USART1_function(void)
{
    uint8_t c1, c2, c3, c4, c5;
    int d1, d2;
    if(RX_Over_Flag)
    {
        RX_Over_Flag = 0;
        if(Data.UARTorLOCAL_Flag)
        {
            if(RX_LEN)
            {
                sscanf((char *)RX_BUFF, "%c%c%c%c%d%c%d", &c1, &c2, &c3, &c4, &d1, &c5, &d2);	//提取
                if(('S' == c1) && ('E' == c2) && ('T' == c3) && (':' == c4) && (':' == c5))
                {
                    if((1 == d1) || (2 == d1))
                    {
                        if(d2 >= 2 && d2 <= 10)
                        {
                            if(1 == d1)
                            {
                                PWM_ic.Mult_CH2 = d2;
                                sprintf((char *)LCD_Line_BUFF[C2_STATE], " N(1):%d           ", PWM_ic.Mult_CH2);	//PA6倍频
                                LCD_State |= (0x01 << C2_STATE);	//刷新
                            }
                            else
                            {
                                PWM_ic.Mult_CH3 = d2;
                                sprintf((char *)LCD_Line_BUFF[C4_STATE], " N(2):%d           ", PWM_ic.Mult_CH3);	//PA6倍频
                                LCD_State |= (0x01 << C4_STATE);	//刷新
                            }
                            PWM_Wirte();	//保存到EEPROM
                        }
                    }
                }
            }
        }
    }
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);
}
(EEPROM部分)
void PWM_Wirte(void)
{
    uint8_t temp[2] = {0};
    temp[0] = PWM_ic.Mult_CH2;
    temp[1] = PWM_ic.Mult_CH3;
    EEPROM_Write(ADDR, temp, 2);
    HAL_Delay(5);
}

void PWM_Read(void)
{
    uint8_t temp[2] = {0};
    EEPROM_Read(ADDR, temp, 2);
    PWM_ic.Mult_CH2 = temp[0];
    PWM_ic.Mult_CH3 = temp[1];
    HAL_Delay(5);
}

void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();

    while(len--)
    {
        I2CSendByte(*date++);
        I2CWaitAck();
    }
    I2CStop();
}

void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    I2CStart();
    I2CSendByte(0xA1);
    I2CWaitAck();
    while(len--)
    {
        *date++ = I2CReceiveByte();
        if(len)
        {
            I2CSendAck();
        }
        else
        {
            I2CSendNotAck();
        }
    }
    I2CStop();
}
(按键部分)
uint8_t KEY_Proc(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }
    return 0;
}



void KEY_function(void)
{
    static uint8_t Old_VALUE;
    if(0 == get_time_task(TASK_TIME.TASK_KEY))
    {
        KEY_VALUE = KEY_Proc();

        KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ Old_VALUE);
        KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ Old_VALUE);
        Old_VALUE = KEY_VALUE;

        switch(KEY_UP)
        {
        case 1:
        {
            Data.KEY1_DOWN_Flag = 1;
            break;
        }
        case 2:
        {
            if(Data.UARTorLOCAL_Flag)	//本地才有效
            {
                break;
            }
            Data.KEY2_DOWN_Flag = 1;
            break;
        }
        case 3:
        {
            if(Data.UARTorLOCAL_Flag)	//本地才有效
            {
                break;
            }
            Data.KEY3_DOWN_Flag = 1;
            break;
        }
        case 4:
        {
            if(Data.UARTorLOCAL_Flag)	//本地才有效
            {
                break;
            }
            Data.KEY4_DOWN_Flag = 1;
            break;
        }
        default:
            break;
        }
        TASK_TIME.TASK_KEY = get_time() + KEY_TIME;
    }
}
(LED部分)
void LED_Dis(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

void LED_Write_all(uint8_t num)
{
    uint16_t a;
    a = GPIOC->ODR;
    GPIOC->ODR = (uint16_t)num << 8;
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    GPIOC->ODR = a;
}
(LCD部分)
void LCD_function(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        if(LCD_State & (1 << i))
        {
            LCD_State &= (~(0x01 << i));
            LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
        }
    }
}

void Add_Space(uint8_t date[][20], uint8_t index)
{
    for(uint8_t i = 0; i < 20; i++)
    {
        if(date[index][i] == '\0')
        {
            date[index][i] = ' ';
        }
    }
}

void LCD_Dis(void)
{
    sprintf((char *)LCD_Line_BUFF[C1_STATE], " Channel(1):%dHz", PWM_ic.CH2_IC_freq);	//PA1捕获
    sprintf((char *)LCD_Line_BUFF[C2_STATE], " N(1):%d           ", PWM_ic.Mult_CH2);	//PA6倍频
    sprintf((char *)LCD_Line_BUFF[C3_STATE], " Channel(2):%dHz", PWM_ic.CH3_IC_freq);	//PA1捕获
    sprintf((char *)LCD_Line_BUFF[C4_STATE], " N(2):%d           ", PWM_ic.Mult_CH3);	//PA6倍频
    sprintf((char *)LCD_Line_BUFF[C5_STATE], "                  1");	//当前通道号默认1

    Add_Space(LCD_Line_BUFF, C1_STATE);
    Add_Space(LCD_Line_BUFF, C3_STATE);
    LCD_State |= (0x01 << C1_STATE);
    LCD_State |= (0x01 << C2_STATE);
    LCD_State |= (0x01 << C3_STATE);
    LCD_State |= (0x01 << C4_STATE);
    LCD_State |= (0x01 << C5_STATE);

}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t timer_200ms;
    if(htim == &htim6)
    {
        timer_200ms++;
        sys_time++;
        if(200 == timer_200ms)	//切换输入捕获的通道
        {
            timer_200ms = 0;
            CAPTURE_MODE ^= 1;
        }
    }
}
(其他部分)
//引脚输出低电平或者高电平
void GPIO_OUT(uint16_t pin,GPIO_PinState PinState)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	GPIO_InitStruct.Pin = 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);
	HAL_GPIO_WritePin(GPIOA,pin,PinState);	//高、低
}
//引脚输出PWM
void PWM_OC(uint16_t pin)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);	
}
main.c
#include "my.h"

int main(void)
{
    TASK_init();

    while(1)
    {
        KEY_function();
        LCD_function();
        USART1_function();
        FlagRun_function();
        TIM_IN_function();
    }
}
usart.h(系统自带)
#include "main.h"

#define RX_MAX_LEN	50

extern uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
extern uint8_t RX_LEN;	//接收长度
extern uint8_t RX_Over_Flag;	//接收完成标志位
extern DMA_HandleTypeDef hdma_usart1_rx;
usart.c(系统自带)
uint8_t RX_BUFF[RX_MAX_LEN];
uint8_t RX_LEN = 0;
uint8_t RX_Over_Flag = 0;

void MX_USART1_UART_Init(void)
{
    ...
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);	//接收中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);	//DMA接收
}

void USART1_IRQHandler(void)
{
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);	//清除标志位
        HAL_UART_DMAStop(&huart1);
        RX_LEN = RX_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);	//获取长度
        RX_Over_Flag = 1;
    }
    HAL_UART_IRQHandler(&huart1);
}

实验现象

6届-电压测量监控设备

题目要求

通过题目知道需要用到的外设 对应管脚
LCD /
LED PC8~PC15PD2
USART1 PA9PA10
按键 PB0~PB2PA0
ADC PB15
RTC /
EEPROM PB6PB7

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdbool.h"
#include "i2c_hal.h"
#include "lcd.h"


#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define ADDR 0x00	//存储地址

#define C1_STATE 2	//页面2
#define C2_STATE 3
#define C3_STATE 4	//页面2
#define C4_STATE 5

#define KEY_TIME 10
#define ADC_TIME 100
#define LED_TIME 200

typedef struct
{
    uint32_t TASK_KEY;
    uint32_t TASK_ADC;
    uint32_t TASK_LED;
} TASK_TINESTypeDef;



typedef struct
{
    bool KEY1_DOWN_Flag;	//1
    bool KEY2_DOWN_Flag;	//2
    bool KEY3_DOWN_Flag;	//3
    bool KEY4_DOWN_Flag;	//4
    bool ADC_Over_Flag;	//ADC采集完成标志位
    bool MENU_Flag;	//页面标志位
    bool LED_RUN_Flag;	//报警LED开关
    uint8_t TIMER_CHOOSE_Flag;	//时间选择 (1 2 3)
    bool ALARM_Over_Flag;	//闹钟标志位
    bool Storage_Success_Flag;	//存储标志位
} DATA_TypeDef;

typedef struct
{
    float My_ADC;
    float K;	//默认0.1保存到EEPROM
    uint8_t LED_STATE;	//LED状态默认打开ON
    uint8_t temp_hour;	//定时时间缓存
    uint8_t temp_min;
    uint8_t temp_sec;
} PARAMETER_TypeDef;

void LED_function(void);
void LED_Write_all(uint8_t data);
void TASK_TIMES_init(void);
uint32_t get_time_task(uint32_t time);
uint32_t get_time(void);
uint8_t KEY_Proc(void);
void KEY_function(void);
void LCD_function(void);
void LCD_Dis1(void);
void LCD_Dis2(void);
void LED_DIS(uint8_t num, uint8_t swch);
void TASK_Init(void);
void ADC_function(void);
void GetTime_function(void);
void Flag_Run_function(void);
void Alarm_SetTime(uint8_t Hours, uint8_t Minutes, uint8_t Seconds);
void USART1_function(void);
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len);
void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len);
void EEPROM_SET_Read(void);
void EEPROM_SET_Write(void);
void HightLight(uint8_t Index,PARAMETER_TypeDef *Sparameter);
#endif
my.c
(初始化部分)
#include "my.h"
uint8_t LED_state = 0XFF;	//LED状态
uint8_t KEY_UP, KEY_DOWN, KEY_VALUE;
uint16_t KEY_COUNT;
DATA_TypeDef Data = {0};
PARAMETER_TypeDef parameter = {0.0, 0.1, 0, 0, 0, 0};
uint16_t LCD_State = 0xFFFF;
uint8_t LCD_Line_BUFF[10][20] = {""};
uint32_t ADC_BUFF[100];
RTC_TimeTypeDef my_time = {0};
RTC_DateTypeDef my_date = {0};
TASK_TINESTypeDef TASK_TIMES;
uint32_t sys_time = 0;

void TASK_Init(void)
{
    LCD_Init();
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LED_Write_all(0xFF);
    I2CInit();
    HAL_TIM_Base_Start_IT(&htim6);
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);	//校准
    HAL_ADC_Start_DMA(&hadc2, (uint32_t *)&ADC_BUFF, 100);
    HAL_RTC_GetTime(&hrtc, &my_time, RTC_FORMAT_BIN);	//获取时间
    HAL_RTC_GetDate(&hrtc, &my_date, RTC_FORMAT_BIN);
    TASK_TIMES_init();	//时间片初始化
    EEPROM_SET_Read();	//读取k
    LCD_Dis1();
}
(时间片轮询部分)
//初始化时间片
void TASK_TIMES_init(void)
{
    TASK_TIMES.TASK_KEY = get_time() + KEY_TIME;
    TASK_TIMES.TASK_ADC = get_time() + ADC_TIME;
    TASK_TIMES.TASK_LED = get_time() + LED_TIME;
}

//获取系统时间
uint32_t get_time(void)
{
    return sys_time;
}

//比较时间
uint32_t get_time_task(uint32_t time)
{
    if(time > sys_time)
    {
        time -= sys_time;
    }
    else
    {
        time = 0;
    }
    return time;
}
(LED部分)
void LED_Write_all(uint8_t data)
{
    uint16_t a;
    a = GPIOC->ODR;
    GPIOC->ODR = (uint16_t)data << 8;
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    GPIOC->ODR = a;
}

//闪烁
void LED_function(void)
{
    if(0 == get_time_task(TASK_TIMES.TASK_LED))	//200ms
    {
        TASK_TIMES.TASK_LED = get_time() + LED_TIME;
        if(Data.LED_RUN_Flag)
        {
            LED_state ^= (1 << 0);	//第一个LED
        }
        else
        {
            LED_state = 0xFF;
        }
        LED_Write_all(LED_state);
    }
}
(ADC部分)
void ADC_function(void)
{
    uint32_t adc = 0, temp;
    uint8_t count;

    if(0 == get_time_task(TASK_TIMES.TASK_ADC))
    {
        TASK_TIMES.TASK_ADC = get_time() + ADC_TIME;	//重新赋值
        HAL_ADC_Stop_DMA(&hadc2);	//停止ADC
        for(uint8_t i = 0; i < 100 - 1; i++)
        {
            count = 0;
            for(uint8_t j = 0; j < 100 - 1 - i; j++)
            {
                if(ADC_BUFF[j] > ADC_BUFF[j + 1])
                {
                    temp = ADC_BUFF[j];
                    ADC_BUFF[j] = ADC_BUFF[j + 1];
                    ADC_BUFF[j + 1] = temp;
                    count++;
                }
            }
            if(0 == count)
            {
                break;
            }
        }

        for(uint8_t k = 1; k < 100 - 1; k++)
        {
            adc += ADC_BUFF[k];
        }
        HAL_ADC_Start_DMA(&hadc2, (uint32_t *)&ADC_BUFF, 100);
        parameter.My_ADC = (adc / 98) * 3.3f / 4096;
        Data.ADC_Over_Flag = 1;
        if((parameter.My_ADC > (3.3 * parameter.K)) && (0 == parameter.LED_STATE))	//报警
        {
            Data.LED_RUN_Flag = 1;
        }
        else
        {
            Data.LED_RUN_Flag = 0;
        }
    }
}
(按键部分)
uint8_t KEY_Proc(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;

    }
    return 0;
}


void KEY_function(void)
{
    static uint8_t Old_VALUE;


    if(0 == get_time_task(TASK_TIMES.TASK_KEY))
    {
        TASK_TIMES.TASK_KEY = get_time() + KEY_TIME;
        KEY_VALUE = KEY_Proc();
        KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ Old_VALUE);
        KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ Old_VALUE);
        Old_VALUE = KEY_VALUE;

        if(KEY_DOWN)
        {
            KEY_COUNT = 0;
        }
        if(KEY_COUNT < 10)
        {
            switch(KEY_UP)
            {
            case 1:
            {
                Data.KEY1_DOWN_Flag = 1;
                break;
            }
            case 2:
            {
                Data.KEY2_DOWN_Flag = 1;
                break;
            }
            case 3:
            {
                if(!Data.MENU_Flag)	//主页面失效
                {
                    break;
                }
                Data.KEY3_DOWN_Flag = 1;
                break;
            }
            case 4:
            {
                if(!Data.MENU_Flag)	//主页面失效
                {
                    break;
                }
                Data.KEY4_DOWN_Flag = 1;
                break;
            }
            default:
                break;
            }
        }
    }
}
(标志位实现功能部分)
void Flag_Run_function(void)
{
    static uint8_t KEY2_Index = 0;
    static uint8_t HMS_Index = 0;	//时分秒选择
    uint8_t Report_Arr[30] = "";

    if(Data.KEY1_DOWN_Flag)	//按1
    {
        Data.KEY1_DOWN_Flag = 0;
        parameter.LED_STATE = !parameter.LED_STATE;	//报警LED功能开关
    }
    if(Data.KEY2_DOWN_Flag)	//按2
    {
        Data.KEY2_DOWN_Flag = 0;
        switch(KEY2_Index)
        {
        case 0:	//第一次按下切换页面
        {
            Data.MENU_Flag = 1;
            LCD_Dis2();
            KEY2_Index = 1;
            break;
        }
        case 1:	//第二次按下更新定时时间且回到主页面
        {

            Alarm_SetTime(parameter.temp_hour, parameter.temp_min, parameter.temp_sec);	//设置闹钟
            Data.MENU_Flag = 0;
            LCD_Dis1();
            KEY2_Index = 0;	//按键索引清0
            HMS_Index = 0;	//时分秒选择清0
            Data.TIMER_CHOOSE_Flag = 0;	//无选择状态,按键4无效
            break;
        }
        default:
            break;
        }
    }
    if(Data.KEY3_DOWN_Flag)	//按3
    {
        Data.KEY3_DOWN_Flag = 0;
        if(1 == KEY2_Index)	//在设置页面才有效
        {
            switch(HMS_Index)
            {
            case 0:	//选中时
            {
                Data.TIMER_CHOOSE_Flag = 1;
                HMS_Index = 1;
                break;
            }
            case 1:	//选中分
            {
                Data.TIMER_CHOOSE_Flag = 2;
                HMS_Index = 2;
                break;
            }
            case 2:	//选中秒
            {
                Data.TIMER_CHOOSE_Flag = 3;
                HMS_Index = 0;
                break;
            }
            default:
                break;
            }
			HightLight(Data.TIMER_CHOOSE_Flag,&parameter);
        }
    }
    if(Data.KEY4_DOWN_Flag)	//按4
    {
        Data.KEY4_DOWN_Flag = 0;
        if(Data.TIMER_CHOOSE_Flag)	//默认是0 不为0表示已经选中任意一个
        {
            switch(Data.TIMER_CHOOSE_Flag)
            {
            case 1:	//小时
            {
                parameter.temp_hour++;
                if(parameter.temp_hour > 23)
                {
                    parameter.temp_hour = 0;
                }
                break;
            }
            case 2:
            {
                parameter.temp_min++;
                if(parameter.temp_min > 59)
                {
                    parameter.temp_min = 0;
                }
                break;
            }
            case 3:
            {
                parameter.temp_sec++;
                if(parameter.temp_sec > 59)
                {
                    parameter.temp_sec = 0;
                }
				
                break;
            }
            default:
                break;
            }
			HightLight(Data.TIMER_CHOOSE_Flag,&parameter);	//高亮
        }
    }
    if(Data.ALARM_Over_Flag)	//触发闹钟 上报电压
    {
        Data.ALARM_Over_Flag = 0;
        sprintf((char *)Report_Arr, "%.2f+%.1f+%02d%02d%02d\n", parameter.My_ADC, parameter.K, my_time.Hours, my_time.Minutes, my_time.Seconds);
        HAL_UART_Transmit(&huart1, (uint8_t *)Report_Arr, sizeof(Report_Arr), 300);
    }
    if(Data.Storage_Success_Flag)
    {
        Data.Storage_Success_Flag = 0;
        EEPROM_SET_Write();
    }
}
(LCD部分)
//高亮时分秒函数
//参数1:行
//参数2:列(0~19)
//参数3:存储时/分/秒的变量
void HightLight(uint8_t Index,PARAMETER_TypeDef *Sparameter)
{
	LCD_SetBackColor(White);	//高亮前颜色
	LCD_SetTextColor(Black);
	switch(Index)
	{
		case 1:
		{
			LCD_DisplayChar(C3_STATE*24,319-(6*16),Sparameter->temp_hour/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(7*16),Sparameter->temp_hour%10+'0');
			break;
		}
		case 2:
		{
			LCD_DisplayChar(C3_STATE*24,319-(9*16),Sparameter->temp_min/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(10*16),Sparameter->temp_min%10+'0');			
			break;
		}
		case 3:
		{
			LCD_DisplayChar(C3_STATE*24,319-(12*16),Sparameter->temp_sec/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(13*16),Sparameter->temp_sec%10+'0');			
			break;
		}
		default:break;		
	}
	LCD_SetBackColor(Black);	//还原颜色
	LCD_SetTextColor(White);
	switch(Index)
	{
		case 1:
		{
			LCD_DisplayChar(C3_STATE*24,319-(12*16),Sparameter->temp_sec/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(13*16),Sparameter->temp_sec%10+'0');			

			break;
		}
		case 2:
		{
			LCD_DisplayChar(C3_STATE*24,319-(6*16),Sparameter->temp_hour/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(7*16),Sparameter->temp_hour%10+'0');						
			break;
		}
		case 3:
		{
			LCD_DisplayChar(C3_STATE*24,319-(9*16),Sparameter->temp_min/10+'0');
			LCD_DisplayChar(C3_STATE*24,319-(10*16),Sparameter->temp_min%10+'0');			
			break;
		}
		default:break;		
	}	
}

void LCD_function(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        if(LCD_State & (1 << i))
        {
            LCD_State &= (~(0x01 << i));
            LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
        }
    }
    if((!Data.MENU_Flag) && Data.ADC_Over_Flag)	//主页面并且采集完才刷新
    {
        Data.ADC_Over_Flag = 0;
        sprintf((char *)LCD_Line_BUFF[C1_STATE], "    V1:%.2fV        ", parameter.My_ADC);
        LCD_State |= (0x01 << C1_STATE);
    }
    if(!Data.MENU_Flag)	//主页面才刷新
    {
        if(parameter.LED_STATE)
        {
            sprintf((char *)LCD_Line_BUFF[C3_STATE], "    LED:OFF          ");
        }
        else
        {
            sprintf((char *)LCD_Line_BUFF[C3_STATE], "    LED:ON          ");
        }
        LCD_State |= (0x01 << C3_STATE);	//刷新
    }
}


void LCD_Dis1(void)
{
    sprintf((char *)LCD_Line_BUFF[C1_STATE], "    V1:%.2fV        ", parameter.My_ADC);
    sprintf((char *)LCD_Line_BUFF[C2_STATE], "    K:%.1f", parameter.K);
    if(parameter.LED_STATE)
    {
        sprintf((char *)LCD_Line_BUFF[C3_STATE], "    LED:OFF          ");
    }
    else
    {
        sprintf((char *)LCD_Line_BUFF[C3_STATE], "    LED:ON          ");
    }
    sprintf((char *)LCD_Line_BUFF[C4_STATE], "    T:%02d-%02d-%02d", my_time.Hours, my_time.Minutes, my_time.Seconds);

    LCD_State |= (0x01 << C1_STATE);
    LCD_State |= (0x01 << C2_STATE);
    LCD_State |= (0x01 << C3_STATE);
    LCD_State |= (0x01 << C4_STATE);
}

void LCD_Dis2(void)
{
    sprintf((char *)LCD_Line_BUFF[C1_STATE], "      setting       ");
    sprintf((char *)LCD_Line_BUFF[C3_STATE], "      %02d-%02d-%02d      ", parameter.temp_hour, parameter.temp_min, parameter.temp_sec);
    LCD_ClearLine(C2_STATE * 24);
    LCD_ClearLine(C4_STATE * 24);
    LCD_State |= (0x01 << C1_STATE);
    LCD_State |= (0x01 << C3_STATE);

}
(USART处理数据部分)
void USART1_function(void)
{
    uint8_t c1, c2, c3, arr[20];
    float k;
    if(RX_Over_Flag)
    {
        RX_Over_Flag = 0;
        if(6 == RX_LEN)
        {
            sscanf((char *)RX_BUFF, "%c%f%c%c", &c1, &k, &c2, &c3);	//提取
            if(('k' == c1) && ('\\' == c2) && ('n' == c3))	//判断格式
            {
                if(k >= 0.1 && k <= 0.9)	//判断范围
                {
                    parameter.K = k;
                    Data.Storage_Success_Flag = 1;
                    sprintf((char *)LCD_Line_BUFF[C2_STATE], "    K:%.1f", parameter.K);
                    LCD_State |= (0x01 << C2_STATE);
                    HAL_UART_Transmit(&huart1, (uint8_t *)"ok\n", sizeof("ok\n"), 300);
                }
            }
        }

    }
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);
}
(EEPROM部分)
//写入数据
void EEPROM_SET_Write(void)
{
    uint8_t num[1] = {0};
    num[0] = parameter.K * 10;	//扩大10倍当整型存储
    EEPROM_Write(0x00, num, 1);
}

//读取数据
void EEPROM_SET_Read(void)
{
    uint8_t num[1] = {0};
    EEPROM_Read(0x00, num, 1);
    parameter.K = (float)num[0] / 10;	//还原小数【一定要强制类型转换否则结果是0】
}

void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    while(len--)
    {
        I2CSendByte(*date++);
        I2CWaitAck();
    }
    I2CStop();
    HAL_Delay(5);
}
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();

    I2CStart();
    I2CSendByte(0xA1);
    I2CWaitAck();

    while(len--)
    {
        *date++ = I2CReceiveByte();

        if(len)
        {
            I2CSendAck();
        }
        else
        {
            I2CSendNotAck();
        }
    }
    I2CStop();
    HAL_Delay(5);
}
(RTC+闹钟部分)
void GetTime_function(void)
{
    static uint8_t old_time;
    old_time = my_time.Seconds;
    HAL_RTC_GetTime(&hrtc, &my_time, RTC_FORMAT_BIN);	//获取时间
    HAL_RTC_GetDate(&hrtc, &my_date, RTC_FORMAT_BIN);
    if((old_time != my_time.Seconds) && (!Data.MENU_Flag))
    {
        sprintf((char *)LCD_Line_BUFF[C4_STATE], "    T:%02d-%02d-%02d", my_time.Hours, my_time.Minutes, my_time.Seconds);
        LCD_State |= (0x01 << C4_STATE);
    }
}

//闹钟设置
void Alarm_SetTime(uint8_t Hours, uint8_t Minutes, uint8_t Seconds)
{
    RTC_AlarmTypeDef sAlarm = {0};

    sAlarm.AlarmTime.Hours = Hours;
    sAlarm.AlarmTime.Minutes = Minutes;
    sAlarm.AlarmTime.Seconds = Seconds;
    sAlarm.AlarmTime.SubSeconds = 0x0;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 0x1;
    sAlarm.Alarm = RTC_ALARM_A;
    if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
    {
        Error_Handler();
    }
}

//闹钟中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    Data.ALARM_Over_Flag = 1;
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t timer_100ms;
    if(htim == &htim6)
    {
        sys_time++;	//系统时间
        timer_100ms++;

        if(100 == timer_100ms)	//按键长短计数
        {
            timer_100ms = 0;
            KEY_COUNT++;
        }
    }
}
main.c
#include "my.h"

int main(void)
{
    TASK_Init();
    while(1)
    {
        KEY_function();
        ADC_function();
        LCD_function();
        LED_function();
        GetTime_function();
        Flag_Run_function();
        USART1_function();
    }
}
usart.h(系统自带)
#include "main.h"

#define RX_MAX_LEN	50

extern uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
extern uint8_t RX_LEN;	//接收长度
extern uint8_t RX_Over_Flag;	//接收完成标志位
extern DMA_HandleTypeDef hdma_usart1_rx;
usart.c(系统自带)
uint8_t RX_BUFF[RX_MAX_LEN];
uint8_t RX_LEN = 0;
uint8_t RX_Over_Flag = 0;

void MX_USART1_UART_Init(void)
{
    ...
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);	//接收中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);	//DMA接收
}

void USART1_IRQHandler(void)
{
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);	//清除标志位
        HAL_UART_DMAStop(&huart1);
        RX_LEN = RX_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);	//获取长度
        RX_Over_Flag = 1;
    }
    HAL_UART_IRQHandler(&huart1);
}

实验现象

7届-模拟液位检测告警系统

题目要求

通过题目知道需要用到的外设 对应管脚
LCD /
LED PC8~PC15PD2
USART1(G4板没有串口2) PA9PA10
按键 PB0~PB2PA0
ADC PB15

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "lcd.h"
#include "i2c_hal.h"
#include "stdio.h"
#include "stdlib.h"
#include "stdbool.h"
#include "string.h"


#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define BT_STATE 1
#define C1_STATE 3
#define C2_STATE 5
#define C3_STATE 7
#define D1_STATE 4
#define D2_STATE 6
#define D3_STATE 8

#define ADDR 0x00


typedef struct
{
    bool KEY1_DOWN_Flag;	//1
    bool KEY2_DOWN_Flag;	//2
    bool KEY3_DOWN_Flag;	//3
    bool KEY4_DOWN_Flag;	//4
    bool MENU_Flag;	//页面标志位
    bool ADC_Over_Flag;	//ADC采集完成标志位
    uint8_t HightLight_State;	//高亮索引
    bool Grade_Check_Flag;	//液位等级发生改变标志位
    bool Inquire_Flag;	//串口查询标志位
} DATA_TypeDef;

typedef struct
{
    uint8_t Height;	//液位高度
    float ADC;	//adc值
    uint8_t Level;	//等级
    uint8_t Threshold_1;	//阈值1
    uint8_t Threshold_2;	//阈值2
    uint8_t Threshold_3;	//阈值3
} RARAMETER_TypeDef;

void KEY_function(void);
uint8_t KEY_Proc(void);
void LCD_function(void);
void LCD_Dis1(void);
void LCD_Dis2(void);
void LED_DIS(uint8_t num, uint8_t swch);
void TASK_Init(void);
void ADC_Filtering(void);
void Flag_Run_function(void);
void EEPROM_Read(uint8_t addr, uint8_t *date, uint8_t len);
void EEPROM_Write(uint8_t addr, uint8_t *date, uint8_t len);
void Parameter_Read(void);
void Parameter_Write(void);
void Judge_Grade(void);
void USART1_function(void);
void LED_Togg(uint8_t num);
#endif
my.c
(初始化部分)
#include "my.h"

uint8_t Old_Garde;	//上一次等级
DATA_TypeDef Data = {0};
RARAMETER_TypeDef parameter = {0,0.0,0,0,0,0};
uint16_t LCD_State = 0xFFFF;
uint8_t KEY_UP,KEY_DOWN,KEY_VALUE;
uint8_t KEY_COUNT;
uint32_t ADC_BUFF[10];
uint8_t LCD_Line_BUFF[10][20]  ={""};

uint8_t KEY_Proc(void)
{
	if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
	{
		if(!KEY1)
			return 1;
		if(!KEY2)
			return 2;
		if(!KEY3)
			return 3;
		if(!KEY4)
			return 4;		
	}
	return 0;
}
(按键部分)
uint8_t KEY_Proc(void)
{
	if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
	{
		if(!KEY1)
			return 1;
		if(!KEY2)
			return 2;
		if(!KEY3)
			return 3;
		if(!KEY4)
			return 4;		
	}
	return 0;
}

void KEY_function(void)
{
	static uint8_t Old_Value;
	
	KEY_VALUE = KEY_Proc();
	KEY_UP = ~KEY_VALUE & (KEY_VALUE^Old_Value);
	KEY_DOWN = KEY_VALUE & (KEY_VALUE^Old_Value);
	Old_Value = KEY_VALUE;
	
	if(KEY_DOWN)
	{
		KEY_COUNT = 0;
	}
	if(KEY_COUNT < 10)
	{
		switch(KEY_UP)
		{
			case 1:
			{
				Data.KEY1_DOWN_Flag = 1;
				break;
			}
			case 2:
			{
				if(!Data.MENU_Flag)	//主页面失效
				{
					break;
				}
				Data.KEY2_DOWN_Flag = 1;
				break;
			}
			case 3:
			{
				if(!Data.MENU_Flag)	//主页面失效
				{
					break;
				}				
				Data.KEY3_DOWN_Flag = 1;
				break;
			}
			case 4:
			{
				if(!Data.MENU_Flag)	//主页面失效
				{
					break;
				}				
				Data.KEY4_DOWN_Flag = 1;
				break;
			}
			default:break;			
		}
	}
}
(标志位实现功能部分)
void Flag_Run_function(void)
{
	if(Data.KEY1_DOWN_Flag)
	{
		Data.KEY1_DOWN_Flag = 0;
		Data.MENU_Flag = !Data.MENU_Flag;
		Data.HightLight_State = 0;	//清除高亮否则错乱
		if(Data.MENU_Flag)
		{
			LCD_Dis2();
		}
		else
		{
			//保存到EEPROM并且回到页面1
			Parameter_Write();
			LCD_Dis1();
		}
	}
	if(Data.KEY2_DOWN_Flag)
	{
		Data.KEY2_DOWN_Flag = 0;
		switch(Data.HightLight_State)
		{
			case 0:
			{
				Data.HightLight_State = 1;	//高亮1
				LCD_State |= (0x01<<D1_STATE); 
				break;
			}
			case 1:
			{
				Data.HightLight_State = 2;	//高亮2
				LCD_State |= (0x01<<D2_STATE); 
				break;
			}
			case 2:
			{			
				Data.HightLight_State = 3;	//高亮3
				LCD_State |= (0x01<<D3_STATE);					
				break;
			}
			case 3:
			{
				Data.HightLight_State = 1;	//高亮3
				LCD_State |= (0x01<<D1_STATE);
				break;
			}			
			default:break;
		}
			
	}
	if(Data.KEY3_DOWN_Flag)
	{
		Data.KEY3_DOWN_Flag = 0;
		switch(Data.HightLight_State)	//通过高亮索引来判断增加哪个值
		{
			case 1:
			{
				parameter.Threshold_1 += 5;
				if(parameter.Threshold_1 >= 95)
				{
					parameter.Threshold_1 = 95;
				}
				sprintf((char*)LCD_Line_BUFF[D1_STATE],"  Threshold1:%02dcm   ",parameter.Threshold_1);	
				LCD_State |= (0x01<<D1_STATE);				
				break;
			}
			case 2:
			{
				parameter.Threshold_2 += 5;
				if(parameter.Threshold_2 >= 95)
				{
					parameter.Threshold_2 = 95;
				}
				sprintf((char*)LCD_Line_BUFF[D2_STATE],"  Threshold2:%02dcm   ",parameter.Threshold_2);
				LCD_State |= (0x01<<D2_STATE);				
				break;
			}
			case 3:
			{
				parameter.Threshold_3 += 5;
				if(parameter.Threshold_3 >= 95)
				{
					parameter.Threshold_3 = 95;
				}
				sprintf((char*)LCD_Line_BUFF[D3_STATE],"  Threshold3:%02dcm   ",parameter.Threshold_3);
				LCD_State |= (0x01<<D3_STATE);				
				break;
			}
			default:break;			
		}
	}
	if(Data.KEY4_DOWN_Flag)
	{
		Data.KEY4_DOWN_Flag = 0;
		switch(Data.HightLight_State)	//通过高亮索引来判断增加哪个值
		{
			case 1:
			{
				parameter.Threshold_1 -= 5;
				if(parameter.Threshold_1 <= 5)
				{
					parameter.Threshold_1 = 5;
				}
				sprintf((char*)LCD_Line_BUFF[D1_STATE],"  Threshold1:%02dcm   ",parameter.Threshold_1);	
				LCD_State |= (0x01<<D1_STATE);				
				break;
			}
			case 2:
			{
				parameter.Threshold_2 -= 5;
				if(parameter.Threshold_2 <= 5)
				{
					parameter.Threshold_2 = 5;
				}
				sprintf((char*)LCD_Line_BUFF[D2_STATE],"  Threshold2:%02dcm   ",parameter.Threshold_2);
				LCD_State |= (0x01<<D2_STATE);				
				break;
			}
			case 3:
			{
				parameter.Threshold_3 -= 5;
				if(parameter.Threshold_3 <= 5)
				{
					parameter.Threshold_3 = 5;
				}
				sprintf((char*)LCD_Line_BUFF[D3_STATE],"  Threshold3:%02dcm   ",parameter.Threshold_3);
				LCD_State |= (0x01<<D3_STATE);				
				break;
			}
			default:break;			
		}
	}	
}
(USART处理数据部分)
void USART1_function(void)
{
	if(RX_Over_Flag)
	{
		RX_Over_Flag = 0;
		if(RX_LEN)
		{
			if(1 == RX_LEN)
			{
				if('C' == RX_BUFF[0])	//发送的是C
				{
					uint8_t temp[30] = "\0";
					
					Data.Inquire_Flag = 1;	//LED闪烁
					//返回当前液位高度和液位等级(需要换行)
					sprintf((char*)temp,"C:H%d+L%d\r\n",parameter.Height,parameter.Level);
					HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),300);
				}
				else if('S' == RX_BUFF[0])	//发送的是S
				{
					uint8_t temp[30] = "\0";
					
					Data.Inquire_Flag = 1;	//LED闪烁
					//返回当前液位高度和液位等级(需要换行)
					sprintf((char*)temp,"S:TL%d+TM%d+TH%d\r\n",parameter.Threshold_1,parameter.Threshold_2,parameter.Threshold_3);
					HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),300);
				}
				else	//无效命令
				{
					HAL_UART_Transmit(&huart1,(uint8_t*)"Invalid command!\r\n",sizeof("Invalid command!\r\n"),300);
				}					
			}	
			else
			{
				HAL_UART_Transmit(&huart1,(uint8_t*)"Invalid command!\r\n",sizeof("Invalid command!\r\n"),300);
			}
		}
		HAL_UART_Receive_DMA(&huart1,(uint8_t*)RX_BUFF,RX_MAX_LEN);
	}
}
(LCD部分)
void LCD_function(void)
{
	for(uint8_t i = 0;i<10;i++)
	{
		if(LCD_State & (0x01<<i))
		{
			LCD_State &= (~(1<<i));
			if(Data.HightLight_State)
			{
				switch(Data.HightLight_State)
				{
					case 1:
					{
						LCD_SetBackColor(White);
						LCD_SetTextColor(Black);
						LCD_DisplayStringLine(D1_STATE*24,LCD_Line_BUFF[D1_STATE]);
						
						LCD_SetBackColor(Black);
						LCD_SetTextColor(White);
						LCD_DisplayStringLine(D2_STATE*24,LCD_Line_BUFF[D2_STATE]);
						LCD_DisplayStringLine(D3_STATE*24,LCD_Line_BUFF[D3_STATE]);
						break;
					}
					case 2:
					{
						LCD_SetBackColor(White);
						LCD_SetTextColor(Black);
						LCD_DisplayStringLine(D2_STATE*24,LCD_Line_BUFF[D2_STATE]);
						
						LCD_SetBackColor(Black);
						LCD_SetTextColor(White);
						LCD_DisplayStringLine(D1_STATE*24,LCD_Line_BUFF[D1_STATE]);
						LCD_DisplayStringLine(D3_STATE*24,LCD_Line_BUFF[D3_STATE]);
						break;
					}
					case 3:
					{
						LCD_SetBackColor(White);
						LCD_SetTextColor(Black);
						LCD_DisplayStringLine(D3_STATE*24,LCD_Line_BUFF[D3_STATE]);
						
						LCD_SetBackColor(Black);
						LCD_SetTextColor(White);
						LCD_DisplayStringLine(D2_STATE*24,LCD_Line_BUFF[D2_STATE]);
						LCD_DisplayStringLine(D1_STATE*24,LCD_Line_BUFF[D1_STATE]);
						break;
					}					
				}
			}
			else
			{
						LCD_SetBackColor(Black);
						LCD_SetTextColor(White);	
						LCD_DisplayStringLine(i*24,LCD_Line_BUFF[i]);
			}
			
		}
	}
	if((!Data.MENU_Flag) && Data.ADC_Over_Flag)
	{
		Data.ADC_Over_Flag = 0;
		parameter.Height = parameter.ADC*30.3f;	//K约等于30.30
		Judge_Grade();
		sprintf((char*)LCD_Line_BUFF[C1_STATE],"    Height:%dcm     ",parameter.Height);
		sprintf((char*)LCD_Line_BUFF[C2_STATE],"    ADC:%.2fV       ",parameter.ADC);
		sprintf((char*)LCD_Line_BUFF[C3_STATE],"    Level:%d         ",parameter.Level);
		LCD_State |= (0x01<<C1_STATE);
		LCD_State |= (0x01<<C2_STATE);
		LCD_State |= (0x01<<C3_STATE);	
	}	
}

void LCD_Dis1(void)
{
	sprintf((char*)LCD_Line_BUFF[BT_STATE],"     Liquid Level   ");	//标题
	sprintf((char*)LCD_Line_BUFF[C1_STATE],"    Height:%dcm    ",parameter.Height);	
	sprintf((char*)LCD_Line_BUFF[C2_STATE],"    ADC:%.2fV       ",parameter.ADC);	
	sprintf((char*)LCD_Line_BUFF[C3_STATE],"    Level:%d         ",parameter.Level);
	
	LCD_ClearLine(D1_STATE*24);
	LCD_ClearLine(D2_STATE*24);
	LCD_ClearLine(D3_STATE*24);	
	
	LCD_State |= (0x01<<BT_STATE);
	LCD_State |= (0x01<<C1_STATE);
	LCD_State |= (0x01<<C2_STATE);
	LCD_State |= (0x01<<C3_STATE);			
}

void LCD_Dis2(void)
{
	sprintf((char*)LCD_Line_BUFF[BT_STATE],"     Parameter Setup");	//标题
	sprintf((char*)LCD_Line_BUFF[D1_STATE],"  Threshold1:%02dcm   ",parameter.Threshold_1);	
	sprintf((char*)LCD_Line_BUFF[D2_STATE],"  Threshold2:%02dcm   ",parameter.Threshold_2);	
	sprintf((char*)LCD_Line_BUFF[D3_STATE],"  Threshold3:%02dcm   ",parameter.Threshold_3);	
	
	LCD_ClearLine(C1_STATE*24);
	LCD_ClearLine(C2_STATE*24);
	LCD_ClearLine(C3_STATE*24);
	LCD_State |= (0x01<<BT_STATE);
	LCD_State |= (0x01<<D1_STATE);
	LCD_State |= (0x01<<D2_STATE);
	LCD_State |= (0x01<<D3_STATE);
}
(LED,ADC滤波部分)
void LED_DIS(uint8_t num,uint8_t swch)
{
	if(SET == swch)
	{
		HAL_GPIO_WritePin(GPIOC,(uint16_t)num<<8,GPIO_PIN_RESET);
	}
	else
	{
		HAL_GPIO_WritePin(GPIOC,(uint16_t)num<<8,GPIO_PIN_SET);
	}
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

//LED翻转
void LED_Togg(uint8_t num)
{
	HAL_GPIO_TogglePin(GPIOC,(uint16_t)num<<8);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

void ADC_Filtering(void)
{
	uint8_t count;
	uint32_t temp;
	uint32_t adc_value;
	
	for(uint8_t i = 0;i<10;i++)
	{
		count = 0;
		for(uint8_t j = 0;j<10-i;j++)
		{
			if(ADC_BUFF[j] > ADC_BUFF[j+1])
			{
				temp = ADC_BUFF[j];
				ADC_BUFF[j] = ADC_BUFF[j+1];
				ADC_BUFF[j+1] = temp;
				count++;
			}
		}
		if(0 == count)
		{
			break;
		}
	}
	for(uint8_t k = 1; k<10-1;k++)
	{
		adc_value+= ADC_BUFF[k];
	}
	parameter.ADC = (adc_value/8)*3.3f/4096;
	Data.ADC_Over_Flag = 1;
}
(EEPROM部分)
//页写
void EEPROM_Write(uint8_t addr,uint8_t *date,uint8_t len)
{
	I2CStart();
	I2CSendByte(0xA0);	//写操作
	I2CWaitAck();
	
	I2CSendByte(addr);
	I2CWaitAck();
	
	while(len--)
	{
		I2CSendByte(*date++);
		I2CWaitAck();
		addr++;
		if((addr & 0x07) == 0)
		{
			break;
		}
	}
	I2CStop();
	HAL_Delay(10);
}

//页读
void EEPROM_Read(uint8_t addr,uint8_t *date,uint8_t len)
{
	I2CStart();
	I2CSendByte(0xA0);	//写操作
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	I2CStart();
	I2CSendByte(0xA1);
	I2CWaitAck();
	while(len--)
	{
		*date++ = I2CReceiveByte();
		if(len)
			I2CSendAck();
		else
			I2CSendNotAck();
	}
	I2CStop();
	HAL_Delay(10);
}



//写入数据
void Parameter_Write(void)
{
	uint8_t temp[3] = {0};
	temp[0] = parameter.Threshold_1;
	temp[1] = parameter.Threshold_2;
	temp[2] = parameter.Threshold_3;
	
	EEPROM_Write(ADDR,temp,3);
}

//读数据
void Parameter_Read(void)
{
	uint8_t temp[3] = {0};
	
	EEPROM_Read(ADDR,temp,3);
	
	parameter.Threshold_1 = temp[0];
    parameter.Threshold_2 = temp[1];
	parameter.Threshold_3 = temp[2];
}
(判断液位等级部分)
//判断液位等级
void Judge_Grade(void)			
{
	static uint8_t count;//避免上电触发标志位
	count++;
	if(count>1)
	{
		count = 2;
	}
	if(parameter.Height <= parameter.Threshold_1)	//液位高度<=阈值1
	{
		parameter.Level = 0;	//等级:0
	}
	else if(parameter.Height > parameter.Threshold_1 && parameter.Height <= parameter.Threshold_2)	//液位高度大于阈值1且小于等于阈值2
	{
		parameter.Level = 1;	//等级:1
	}
	else if(parameter.Height > parameter.Threshold_2 && parameter.Height <= parameter.Threshold_3)	//液位高度大于阈值2且小于等于阈值3
	{
		parameter.Level = 2;	//等级:2
	}
	else if(parameter.Height > parameter.Threshold_3)	//液位高度大于阈值3
	{
		parameter.Level = 3;	//等级:3	
	}
	
	if((Old_Garde != parameter.Level) && (count >1))	//液位发送改变LED闪烁且上报串口助手
	{
		int8_t x;
		uint8_t temp[30] = "\0";
		x = parameter.Level - Old_Garde;
		if(x > 0)	//表示上升
		{
			sprintf((char*)temp,"A:H%d+L%d+U\r\n",parameter.Height,parameter.Level);
			HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),300);
		}
		else	//表示下降
		{
			sprintf((char*)temp,"A:H%d+L%d+D\r\n",parameter.Height,parameter.Level);
			HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),300);
		}
		Data.Grade_Check_Flag = 1;
	}
	Old_Garde = parameter.Level;
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	static uint8_t timer_20ms;
	static uint8_t timer_100ms;
	static uint16_t timer_200ms;
	static uint16_t timer_1000ms;
	static uint16_t timer_1000ms_2;
	if(htim == &htim6)
	{
		timer_20ms++;
		timer_100ms++;
		timer_1000ms++;
		
		if(10 == timer_20ms)
		{
			timer_20ms = 0;
			KEY_function();
		}
		if(100 == timer_100ms)
		{
			timer_100ms = 0;
			KEY_COUNT++;
		}
		if(1000 == timer_1000ms)
		{
			timer_1000ms = 0;
			LED_Togg(0x01);	//LED1s闪烁表示运行中
			ADC_Filtering();
		}
		if(Data.Grade_Check_Flag)	//液位等级发生改变
		{
			timer_200ms++;
			if(0 == (timer_200ms % 200))
			{
				LED_Togg(0x02);
			}
			if(5000 == timer_200ms)	//5s
			{
				timer_200ms = 0;
				Data.Grade_Check_Flag = 0;
				LED_DIS(0x02,RESET);	//熄灭LED2
			}
		}
		if(Data.Inquire_Flag)	//接收到串口查询命令
		{
			timer_1000ms_2++;
			if(0 == (timer_1000ms_2 % 200))
			{
				LED_Togg(0x04);
			}
			if(5000 == timer_1000ms_2)
			{
				timer_1000ms_2 = 0;
				Data.Inquire_Flag = 0;
				LED_DIS(0x04,RESET);	//熄灭LED3
			}
		}
	}
}
main.c
int main(void)
{
    TASK_Init();

    while(1)
    {
        LCD_function();
        Flag_Run_function();
        USART1_function();
    }
}
usart.h(系统生成)
#include "main.h"

#define RX_MAX_LEN	50

extern uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
extern uint8_t RX_LEN;	//接收长度
extern uint8_t RX_Over_Flag;	//接收完成标志位
extern DMA_HandleTypeDef hdma_usart1_rx;
usart.c(系统生成)
uint8_t RX_BUFF[RX_MAX_LEN];
uint8_t RX_LEN = 0;
uint8_t RX_Over_Flag = 0;

void MX_USART1_UART_Init(void)
{
    ...
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);	//接收中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);	//DMA接收
}

void USART1_IRQHandler(void)
{
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);	//清除标志位
        HAL_UART_DMAStop(&huart1);
        RX_LEN = RX_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);	//获取长度
        RX_Over_Flag = 1;
    }
    HAL_UART_IRQHandler(&huart1);
}

实验现象

8届-模拟升降控制器

题目要求

通过题目知道需要用到的外设 对应管脚
LCD /
LED PC8~PC15PD2
RTC /
按键 PB0~PB2PA0
PWM PA6PA7

具体表格化

要求
按键按下 1s 以后,电机开始运行
关门: PA5 低电平 ,PA7输出频率 2KHz50% 占空比,关门时间 4s
开门: PA5 高电平 ,PA7输出频率 2KHz60% 占空比,开门时间 4s
上升1层:PA4 高电平 ,PA6输出频率 1KHZ80% 占空比,上升时间 6s
下降1层:PA4 低电平 ,PA6输出频率 1KHZ60% 占空比,下降时间 6s
到达一个目标平台后,如果还有另外目标平台,则开门停留 2s
到达目标平台后,平台数字在 1s 内闪烁 2次
LED1~LED4 表示 1~4平台 LED5~LED6 表示 电机上下时的流水方向
按下当前平台对应按键无效,电机运转过程中按键无效
升降机的顺序与按键顺序无关,优先按低到高,再高到低,上下平台都有按键则先上再下

CubeMX配置

注意问题

  • 需要把提供的 lcd.c 里面3个 Write_Rxx函数添加(防止LCD和LED混乱):
函数名
{
    unsigned short PCOUT = GPIOC->ODR;
    ....
    GPIOC->ODR = PCOUT
}
  • PWM需要注意不要频繁去 Stop 不然输出的PWM频率占空比会不准确最好是不用时输出设置占空比为0

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "gpio.h"
#include "rtc.h"
#include "tim.h"
#include "my.h"
#include "lcd.h"

#include "string.h"
#include "stdlib.h"
#include "stdio.h"
#include "stdbool.h"
/***************************宏定义*****************************/

//LCD Display
#define LCD_SPACE0	0
#define LCD_SPACE1	1
#define LCD_TEST	2
#define LCD_SPACE3	3
#define LCD_SPACE4	4
#define LCD_NUMBER	5
#define LCD_SPACE6	6
#define LCD_SPACE7	7
#define LCD_RTC	8
#define LCD_SPACE9	9

//时间片
#define KEY_TIMEST	10	//10ms扫描一次
#define RTC_TIMEST  200	//200ms扫描一次

//按键读取电平状态
#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)	//PB0
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)	//PB1
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)	//PB2
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)	//PA0

//有误差故加多点
#define KEY_OVER_TIME	1000	//按键按完需要等待的时间
#define SJJ_OC_OVER_TIME	4000	//升降机开关门需要的时间
#define SJJ_UD_OVER_TIME	6000	//升降机上下需要的时间
#define SJJ_WAIT_OVER_TIME	2000	//升降机停留需要的时间



/***************************变量*****************************/

//时间片结构体
typedef struct
{
    uint8_t KEY_time;
    uint8_t RTC_TIME;
} TASK_TIMESTypeDef;

//系统数据
typedef struct
{
    uint8_t Platform;	//当前平台(1~4)
    bool LED1_State;	//LED1状态(代表4个平台)
    bool LED2_State;	//LED2状态
    bool LED3_State;	//LED3状态
    bool LED4_State;	//LED4状态

    bool KEY_Timer_Start;	//按键按下 开始计时标志位
    uint16_t KEY_Timer_Count;	//按键按下后计时时间
    bool KEY_Timer_Over;	//按键按下计时 完成标志位
    bool KEY_InFore_Flag;	//按键是否有效(电机运行期间无效 1:有效 0:无效)

    bool SJJ_Close_Start;	//升降机开始关门 标志位
    bool SJJ_Close_Over;	//升降机关门结束 标志位
    bool SJJ_Open_Start;	//升降机开始开门 标志位
    bool SJJ_Open_Over;	//升降机开门结束 标志位
    uint16_t SJJ_OpenClose_Timer_Count;	//升降机开门/关门计时时间

    uint16_t SJJ_Wait_Timer_Count;	//升降机停留计时时间
    bool SJJ_Wait_Start;	//开始停留
    bool SJJ_Wait_Over;	//停留结束

    bool SJJ_UP_Start;	//升降机开始上升 标志位
    bool SJJ_UP_Over;	//升降机上升完成 标志位
    bool SJJ_DOWN_Start;	//升降机开始下降 标志位
    bool SJJ_DOWN_Over;	//升降机下降完成 标志位
    uint16_t SJJ_UpDown_Timer_Count;	//升降机上下计时时间

    bool Platform_Over;	//到达目标平台标志位
    bool Platform_NumberBlink_Flag;	//数字闪烁
    bool Platform_UPorDOWN;	//选择 1:上-->下/0:下-->上扫描 标志位
    uint8_t PWM_Out_UpDown_Flag;	//上下电机pwm输出切换标志位
    uint8_t PWM_Out_OpenClose_Flag;	//开关电机pwm输出切换标志位
} DATA_TypeDef;


extern DATA_TypeDef Data;

extern TASK_TIMESTypeDef TASK_TIMES;

extern uint8_t LCD_Line_BUFF[10][20];	//LCD显示数组



/***************************函数*****************************/

void LCD_function(void);
void LCD_DrawMonoPict1(uint16_t Xpos, uint16_t Ypos, uint32_t BackColor, uint32_t TextColor, uint8_t *Pict);
void TASK_Init(void);
void RTC_function(void);
void PWM_SET_PA6(uint32_t freq, uint16_t duty);
void PWM_SET_PA7(uint32_t freq, uint16_t duty);
void KEY_SCAN(void);
uint8_t KEY_pro(void);
void LED_DIS(uint8_t num, uint8_t swch);
void Flag_Run(void);
void SJJ_UP(void);
uint8_t Check(void);
void SJJ_DOWN(void);
uint8_t Check_DOWN(void);
uint8_t Check_UP(void);
#endif
my.c
(初始化部分)
//初始化
void TASK_Init(void)
{
    LCD_Init();	//LCD初始化
    LCD_SetBackColor(Black);	//背景
    LCD_SetTextColor(White);	//字体颜色
	LED_DIS(0xFF,RESET);	//LED默认灭
    HAL_TIM_Base_Start_IT(&htim6);	//定时器6开启中断
    HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);	//获取时间
    HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BIN);	//获取日期
    Data.Platform = 1;	//默认平台为1
    Data.SJJ_Open_Over = 1;	//默认升降机开门结束状态
    Data.Platform_UPorDOWN = 1;	//默认上
    Data.KEY_InFore_Flag = 1;	//按键默认有效
    LED_DIS(0xF0, SET);	//流水灯默认全部亮表示可按
	
	LCD_DisplayStringLine(Line2,(uint8_t*)"                    ");	//先空格覆盖再显示
	LCD_DrawMonoPict1(Line2,4,Black,White,Dang);	//"当"
	LCD_DrawMonoPict1(Line2,5,Black,White,Qian);	//"前"
	LCD_DrawMonoPict1(Line2,6,Black,White,Ping);	//"平"
	LCD_DrawMonoPict1(Line2,7,Black,White,Tai);		//"台"
}
(LCD部分)
uint8_t Dang[] = {0x00,0x00,0x00,0x00,0x10,0x60,0xC0,0xC0,0x80,
				  0x00,0xF8,0x00,0x00,0x00,0x00,0xF0,0x00,0x00,
				  0x00,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,0x08,
				  0x18,0x18,0x18,0x18,0x18,0x98,0x58,0xFF,0x00,
	              0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0xFF,
				  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,
	              0x03,0x01,0x00,0x00,0x3F,0x18,0x18,0x18,0x18,
	              0x1F,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x00};/*"当",0*/

uint8_t Qian[] = {0x00,0x80,0x00,0x00,0x00,0xFE,0x00,0x00,0xF0,
				  0x10,0x10,0x10,0xF0,0x10,0x10,0x10,0xF0,0x10,
				  0x10,0x10,0x10,0x90,0x10,0x00,0x00,0x00,0x81,
				  0x83,0x42,0xFF,0x00,0x00,0x47,0xC4,0x44,0x44,
	              0x47,0x44,0x44,0x44,0x47,0x44,0xC4,0x04,0x84,
				  0x07,0x02,0x00,0x00,0x00,0x01,0x00,0x10,0x3F,
	              0x00,0x04,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
	              0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x07,0x02,0x00};/*"前",0*/			  
		
uint8_t Ping[] = {0x00,0x00,0x00,0xF8,0x00,0x00,0x20,0xC0,0x80,
				  0x80,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
				  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
				  0xFF,0x18,0x18,0x18,0x18,0x18,0x98,0x58,0xFF,
				  0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
				  0x18,0x08,0x00,0x00,0x00,0x08,0x1F,0x00,0x04,
				  0x06,0x02,0x01,0x00,0x10,0x3F,0x00,0x00,0x00,
				  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/*"平",0*/

uint8_t Tai[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x20,
				 0x18,0xF8,0x10,0x00,0xC0,0x40,0x40,0x40,0x40,
				 0x40,0x40,0xC0,0x40,0x00,0x00,0x00,0x08,0x1C,
				 0x06,0x02,0x01,0x00,0x00,0x00,0xF0,0x0F,0x00,
				 0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
				 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
				 0x01,0x06,0x0C,0x1F,0x18,0x10,0x00,0x07,0x02,
				 0x02,0x02,0x02,0x02,0x02,0x03,0x02,0x02,0x00};/*"台",0*/
uint16_t LCD_state = 0xFFFF;	//行变化的标记
uint8_t LCD_Line_BUFF[10][20] =
{
    "                    ",	//0
    "                    ",	//1
    "        NOW.        ",	//2
    "                    ",	//3
    "                    ",	//4
    "         1          ",	//5
    "                    ",	//6
    "                    ",	//7
    "      00:00:00      ",	//8
    "                    "	//9
};

//LCD检测
void LCD_function(void)
{
    uint8_t i;

    for(i = 0; i < 10; i++)
    {
		if(i != 2)	//防止操作到中文显示那行
		{
			if(LCD_state & (0x01 << i))	//如果该行发生变化(上电默认全部都发生变化)
			{
				LCD_state &= (~(1 << i));	//清除状态
				LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);	//重新显示该行
			}			
		}
    }
}

//汉字显示函数
/*
参数1:x坐标(0~240)
参数2:y坐标(319~0)
参数3:背景颜色
参数4:字体颜色
参数5:需要显示的字模数组
*/
void LCD_DrawMonoPict1(uint16_t Xpos, uint16_t Ypos, uint32_t BackColor, uint32_t TextColor, uint8_t *Pict)
{
    uint8_t index = 0, i = 0, Xpos_Temp;
    Xpos_Temp = Xpos;	//保存初始坐标
    Ypos = 319 - (24 * Ypos);	//右移多少个汉字再显示24*24一行最多显示13个
    LCD_SetCursor(Xpos, Ypos); 	//写入坐标
    LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */

    for(index = 0; index < 72; index++)
    {
        //需要改变Y两次(换行),0-24  24-48  48-72
        if(24 == index || 48 == index)
        {
            Xpos = Xpos_Temp;	//x坐标恢复初始
            Ypos = Ypos - 8;	//Y右移8个像素
        }
        for(i = 0; i < 8; i++)
        {
            if((Pict[index] & (1 << i)) == 0x00)	//如果对应位置数据是0x00则显示背景颜色
            {
                LCD_WriteRAM(BackColor);
            }
            else	//不为0显示字体
            {
                LCD_WriteRAM(TextColor);
            }
        }
        LCD_SetCursor(Xpos++, Ypos); 	//写入坐标
        LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
    }
}
(RTC,LED部分)
RTC_TimeTypeDef rtc_time;	//时间结构体
RTC_DateTypeDef rtc_date;	//日期结构体


//RTC显示(默认MX设置时间:12:50:55)
void RTC_function(void)
{
    static uint8_t Seconds_last;
    if(!TASK_TIMES.RTC_TIME)	//200ms读取一次RTC时间
    {
        TASK_TIMES.RTC_TIME = RTC_TIMEST;
        HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);	//获取时间
        HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BIN);	//获取日期

        if(Seconds_last != rtc_time.Seconds)
        {
            Seconds_last = rtc_time.Seconds;
            sprintf((char *)LCD_Line_BUFF[LCD_RTC], "      %02d:%02d:%02d      ", rtc_time.Hours, rtc_time.Minutes, rtc_time.Seconds);
            LCD_state |= (0x01 << LCD_RTC);
        }
    }
}

//LED 点亮/熄灭任意
void LED_DIS(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);	//点亮
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);	//灭
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);	//拉高
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);	//拉低
}
(按键部分)
//按键
uint8_t KEY_pro(void)
{
    uint8_t value;
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            value = 1;
        if(!KEY2)
            value = 2;
        if(!KEY3)
            value = 3;
        if(!KEY4)
            value = 4;

        return value;
    }
    return 0;
}


uint8_t KEY_UP;	//按下松开瞬间有效
uint8_t KEY_DOWN;	//按下瞬间有效
uint8_t KEY_VALUE;	//按键键值
uint16_t KEY_COUNT = 0;	//短按计时


//执行对应按键标志位状态
void KEY_SCAN(void)
{
    static uint8_t KEY_Old;

    if(!TASK_TIMES.KEY_time)
    {
        TASK_TIMES.KEY_time = KEY_TIMEST;
        KEY_VALUE = KEY_pro();
        KEY_UP = ~(KEY_VALUE) & (KEY_Old ^ KEY_VALUE);	//抬起瞬间有有效
        KEY_DOWN = (KEY_VALUE) & (KEY_Old ^ KEY_VALUE);	//按下瞬间有效
        KEY_Old = KEY_VALUE;
        if(KEY_DOWN)
        {
            KEY_COUNT = 0;
        }

        if(KEY_COUNT < 10)	//小于1000ms短按且是等待启动
        {
            if(0 == Data.KEY_InFore_Flag)
            {
                return;	//退出
            }
            switch(KEY_UP)
            {
            case 1:
            {
                if(1 == Data.Platform)	//当前平台按下按键没效果
                {
                    break;
                }
                Data.LED1_State = 1;	//去平台1
                Data.KEY_Timer_Start = 1;
                Data.KEY_Timer_Count = 0;	//清0等待时间
                break;
            }

            case 2:
            {
                if(2 == Data.Platform)	//当前平台按下按键没效果
                {
                    break;
                }
                Data.LED2_State = 1;	//去平台2
                Data.KEY_Timer_Start = 1;
                Data.KEY_Timer_Count = 0;	//清0等待时间
                break;
            }

            case 3:
            {
                if(3 == Data.Platform)	//当前平台按下按键没效果
                {
                    break;
                }
                Data.LED3_State = 1;	//去平台3
                Data.KEY_Timer_Start = 1;
                Data.KEY_Timer_Count = 0;	//清0等待时间
                break;
            }

            case 4:
            {
                if(4 == Data.Platform)	//当前平台按下按键没效果
                {
                    break;
                }
                Data.LED4_State = 1;	//去平台4
                Data.KEY_Timer_Start = 1;
                Data.KEY_Timer_Count = 0;	//清0等待时间
                break;
            }

            default:
                break;
            }
        }
    }
}
(判断标志位完成功能部分)
DATA_TypeDef Data;	//数据结构体

//判断标志位完成功能
void Flag_Run(void)
{

    if(Data.LED1_State)
    {
        LED_DIS(0x01, SET);	//点亮LED1
    }
    if(Data.LED2_State)
    {
        LED_DIS(0x02, SET);	//点亮LED1
    }
    if(Data.LED3_State)
    {
        LED_DIS(0x04, SET);	//点亮LED1
    }
    if(Data.LED4_State)
    {
        LED_DIS(0x08, SET);	//点亮LED1
    }
    if(Data.KEY_Timer_Start)	//按下等待开始计时
    {
        if(KEY_OVER_TIME <= Data.KEY_Timer_Count)	//1s
        {
            Data.KEY_Timer_Start = 0;	//清除标志位
            Data.KEY_Timer_Over = 1;
        }
    }
    if(Data.KEY_Timer_Over)	//按下等待完成
    {
        Data.KEY_Timer_Over = 0;
        Data.SJJ_Close_Start = 1;
        LED_DIS(0xF0, RESET);	//灯全灭表示运行
        if(0 == Check_UP())
            Data.Platform_UPorDOWN = 0;
        else
            Data.Platform_UPorDOWN = 1;
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);	//PA5低电平
        Data.PWM_Out_OpenClose_Flag = 1;	//关门pwm
        Data.KEY_InFore_Flag = 0;	//按键无效
        Data.SJJ_OpenClose_Timer_Count = 0;	//清0
    }
    if(Data.SJJ_Close_Start)	//开始升降机关门
    {
        if(SJJ_OC_OVER_TIME <= Data.SJJ_OpenClose_Timer_Count)	//4s
        {
            Data.SJJ_Close_Start = 0;	//清除标志位
            Data.PWM_Out_OpenClose_Flag = 3;	//开门电机pwm输出无效
            Data.SJJ_Close_Over = 1;
        }

    }

    if(Data.SJJ_Wait_Start)	//开始停留计时
    {
        Data.KEY_InFore_Flag = 1;	//按键有效
        if(SJJ_WAIT_OVER_TIME <= Data.SJJ_Wait_Timer_Count)	//2s
        {
            Data.SJJ_Wait_Start = 0;
            Data.SJJ_Wait_Over = 1;
        }
    }
    if(Data.SJJ_Wait_Over)	//停留完成
    {
        Data.SJJ_Wait_Over = 0;
        Data.SJJ_Close_Over = 1;
        Data.KEY_InFore_Flag = 0;	//按键无效
    }
    if(Data.Platform_NumberBlink_Flag)	//闪烁字完成
    {
        Data.Platform_NumberBlink_Flag = 0;
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);	//PA5高电平
        Data.PWM_Out_UpDown_Flag = 3;	//上下电机输出无效
        Data.SJJ_OpenClose_Timer_Count = 0;	//清0
        Data.PWM_Out_OpenClose_Flag = 2;	//开门电机pwm输出
        Data.SJJ_Open_Start = 1;	//开始开门
    }
    if(Data.SJJ_Open_Start)	//开始升降机开门
    {
        if(SJJ_OC_OVER_TIME <= Data.SJJ_OpenClose_Timer_Count)	//4s
        {
            Data.SJJ_Open_Start = 0;	//清除标志位
            Data.PWM_Out_OpenClose_Flag = 3;	//开门电机pwm输出无效
            Data.SJJ_Open_Over = 1;
        }
    }
    if(Data.Platform_UPorDOWN)	//上
    {
        SJJ_UP();
    }
    else if(0 == Data.Platform_UPorDOWN)	//下
    {
        SJJ_DOWN();
    }
}
(下降到目标平台函数部分)
//升降机下降到目标
void SJJ_DOWN(void)
{
    if(Data.SJJ_Close_Over)	//升降机关门完成
    {
        Data.SJJ_Close_Over = 0;

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);	//PA4低电平下降
        Data.PWM_Out_UpDown_Flag = 2;
        Data.SJJ_UpDown_Timer_Count = 0;	//清0
        Data.SJJ_DOWN_Start = 1;	//开始下降标志位
    }
    if(Data.SJJ_DOWN_Start)	//开始下降
    {
        if(SJJ_UD_OVER_TIME <= Data.SJJ_UpDown_Timer_Count)	//6s
        {
            Data.SJJ_DOWN_Start = 0;
            LED_DIS(0xF0, RESET);
            Data.SJJ_DOWN_Over = 1;
        }
    }
    if(Data.SJJ_DOWN_Over)	//下降完成
    {
        Data.Platform--;	//平台--
        if(Check() == 0 )	//未到达
        {
            Data.SJJ_UpDown_Timer_Count = 0;	//清0
            Data.SJJ_DOWN_Start = 1;	//继续下降
        }
        Data.SJJ_DOWN_Over = 0;
    }
    if(Data.SJJ_Open_Over)	//开门完成判断当前平台上面有没有目标平台再判断当前下面(因为题目要求先上后下)
    {

        if(Check_UP())	//有就停留2s再上
        {
            Data.SJJ_Wait_Timer_Count = 0;
            Data.SJJ_Wait_Start = 1;
            Data.Platform_UPorDOWN = 1;	//往上
        }
        else	//没有就判断下面有没有
        {

            if(Check_DOWN())	//如果往下有目标则开始下降
            {
                Data.SJJ_Wait_Timer_Count = 0;
                Data.SJJ_Wait_Start = 1;
                Data.Platform_UPorDOWN = 0;	//往下
            }
            else	//没有
            {

                LED_DIS(0xF0, SET);
                Data.KEY_InFore_Flag = 1;	//按键有效
            }
        }

        Data.SJJ_Open_Over = 0;
    }
}
(上升到目标平台函数部分)
//升降机上升到目标
void SJJ_UP(void)
{

    if(Data.SJJ_Close_Over)	//升降机关门完成
    {
        Data.SJJ_Close_Over = 0;

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);	//PA4高电平上升
        Data.PWM_Out_UpDown_Flag = 1;
        Data.SJJ_UpDown_Timer_Count = 0;	//清0
        Data.SJJ_UP_Start = 1;
    }
    if(Data.SJJ_UP_Start)	//开始上升
    {
        if(SJJ_UD_OVER_TIME <= Data.SJJ_UpDown_Timer_Count)	//6s
        {
            Data.SJJ_UP_Start = 0;
            LED_DIS(0xF0, RESET);
            Data.SJJ_UP_Over = 1;
        }
    }
    if(Data.SJJ_UP_Over)	//上升完成
    {
        Data.Platform++;	//平台++
        if(Check() == 0 )	//未到达
        {
            Data.SJJ_UpDown_Timer_Count = 0;	//清0
            Data.SJJ_UP_Start = 1;	//继续上升
        }
        Data.SJJ_UP_Over = 0;
    }
    if(Data.SJJ_Open_Over)	//开门完成判断当前平台上面还有没有,有则继续上没就判断下面
    {

        if(Check_UP())	//有就停留2s再上
        {
            Data.SJJ_Wait_Timer_Count = 0;
            Data.SJJ_Wait_Start = 1;
        }
        else	//没有就判断下面有没有
        {

            if(Check_DOWN())	//如果往下有目标则开始下降
            {
                Data.SJJ_Wait_Timer_Count = 0;
                Data.SJJ_Wait_Start = 1;
                Data.Platform_UPorDOWN = 0;	//往下
            }
            else	//没有则恢复可按状态
            {

                LED_DIS(0xF0, SET);
                Data.KEY_InFore_Flag = 1;	//按键有效
            }
        }

        Data.SJJ_Open_Over = 0;
    }
}
(检测目标平台函数部分)
//检测当前平台上面有没有目标平台
uint8_t Check_UP(void)
{
    uint8_t temp = 0;
    for(uint8_t i = Data.Platform + 1; i <= 4; i++)
    {
        if(2 == i)
        {
            if(Data.LED2_State)
            {
                temp++;
            }
        }
        if(3 == i)
        {
            if(Data.LED3_State)
            {
                temp++;
            }
        }
        if(4 == i)
        {
            if(Data.LED4_State)
            {
                temp++;
            }
        }
    }

    return temp;
}

//检测当前平台下面有没有目标平台
uint8_t Check_DOWN(void)
{
    uint8_t temp;
    for(uint8_t j = Data.Platform - 1; j > 0; j--)
    {
        if(1 == j)
        {
            if(Data.LED1_State)
            {
                temp++;
            }
        }
        if(2 == j)
        {
            if(Data.LED2_State)
            {
                temp++;
            }
        }
        if(3 == j)
        {
            if(Data.LED3_State)
            {
                temp++;
            }
        }
        if(4 == j)
        {
            if(Data.LED4_State)
            {
                temp++;
            }
        }

    }

    return temp;
}

//检测是否到达目标平台  返回0表示未到达 >0表示到达
uint8_t Check(void)
{
    uint8_t i = 0;
    switch(Data.Platform)
    {
    case 1:
    {
        if(Data.LED1_State)
        {
            Data.LED1_State = 0;
            LED_DIS(0x01, RESET);	//熄灭LED
            Data.Platform_Over = 1;	//到达标志位置1
            Data.SJJ_DOWN_Start = 0;
            Data.SJJ_UP_Start = 0;
            i++;
        }
        break;
    }
    case 2:
    {
        if(Data.LED2_State)
        {
            Data.LED2_State = 0;
            LED_DIS(0x02, RESET);	//熄灭LED
            Data.Platform_Over = 1;	//到达标志位置1
            Data.SJJ_DOWN_Start = 0;
            Data.SJJ_UP_Start = 0;
            i++;
        }
        break;
    }
    case 3:
    {
        if(Data.LED3_State)
        {
            Data.LED3_State = 0;
            LED_DIS(0x04, RESET);	//熄灭LED
            Data.Platform_Over = 1;
            Data.SJJ_DOWN_Start = 0;
            Data.SJJ_UP_Start = 0;
            i++;
        }
        break;
    }
    case 4:
    {
        if(Data.LED4_State)
        {
            Data.LED4_State = 0;
            LED_DIS(0x08, RESET);	//熄灭LED
            Data.Platform_Over = 1;
            Data.SJJ_DOWN_Start = 0;
            Data.SJJ_UP_Start = 0;
            i++;
        }
        break;
    }
    default:
        break;
    }
    return i;
}
(PWM输出比较部分)
typedef struct
{
    uint32_t pulse_val;	//频率所需值
    uint32_t duty_val;	//有效电平所需值
} TIM_OCTypeDef;	//输出比较

TIM_OCTypeDef TIM3_CH1;	//通道1(PA6)
TIM_OCTypeDef TIM3_CH2;	//通道2(PA7)

//设置PA6频率占空比
void PWM_SET_PA6(uint32_t freq, uint16_t duty)
{
    TIM3_CH1.pulse_val = 1000000 / freq;	//计算频率所需值
    TIM3_CH1.duty_val = TIM3_CH1.pulse_val * duty / 100;	//计算有效电平所需值
    TIM3->CCR1 = TIM3_CH1.pulse_val;
}

//设置PA7频率占空比
void PWM_SET_PA7(uint32_t freq, uint16_t duty)
{
    TIM3_CH2.pulse_val = 1000000 / freq;	//计算频率所需值
    TIM3_CH2.duty_val = TIM3_CH2.pulse_val * duty / 100;	//计算有效电平所需值
    TIM3->CCR2 = TIM3_CH2.pulse_val;
}


//输出比较回调函数
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t PWM_CH1_Flag = 1;
    static uint8_t PWM_CH2_Flag = 1;

    if(htim == &htim3)
    {
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)	//通道1
        {
            if(PWM_CH1_Flag)
            {
                TIM3->CCR1 += TIM3_CH1.duty_val;	//高电平持续时间
            }
            else
            {
                TIM3->CCR1 += TIM3_CH1.pulse_val - TIM3_CH1.duty_val;	//低电平持续时间
            }
            PWM_CH1_Flag = !PWM_CH1_Flag;	//取反
        }

        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)	//通道2
        {
            if(PWM_CH2_Flag)
            {
                TIM3->CCR2 += TIM3_CH2.duty_val;	//高电平持续时间
            }
            else
            {
                TIM3->CCR2 += TIM3_CH2.pulse_val - TIM3_CH2.duty_val;	//低电平持续时间
            }
            PWM_CH2_Flag = !PWM_CH2_Flag;	//取反
        }
    }

}
(定时器部分)
TASK_TIMESTypeDef TASK_TIMES;
uint32_t sys_time = 0;	//系统时间

//定时器6回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t count_1s = 0;
    static uint16_t count_200ms = 0;
    static uint8_t LED_Index = 1;

    if(htim == &htim6)
    {
        sys_time++;
        Data.KEY_Timer_Count++;	//按键按下等待时间
        Data.SJJ_OpenClose_Timer_Count++;	//开关门时间
        Data.SJJ_UpDown_Timer_Count++;	//上下时间
        Data.SJJ_Wait_Timer_Count++;	//停留时间

        if(TASK_TIMES.KEY_time)
        {
            TASK_TIMES.KEY_time--;
        }
        if(TASK_TIMES.RTC_TIME)
        {
            TASK_TIMES.RTC_TIME--;
        }
        if(0 == (sys_time % 100))	//相当于100ms++一次
        {
            KEY_COUNT++;	//按键短按计数
        }
        if(Data.Platform_Over)	//平台数字闪烁
        {
            count_1s++;
            if(count_1s == 10)
            {
                sprintf((char *)LCD_Line_BUFF[LCD_NUMBER], "                    ");
                LCD_state |= (0x01 << LCD_NUMBER);
            }
            if(count_1s == 400)
            {
                sprintf((char *)LCD_Line_BUFF[LCD_NUMBER], "         %d          ", Data.Platform);
                LCD_state |= (0x01 << LCD_NUMBER);
            }
            if(count_1s == 600)
            {
                sprintf((char *)LCD_Line_BUFF[LCD_NUMBER], "                     ");
                LCD_state |= (0x01 << LCD_NUMBER);
            }
            if(count_1s == 1000)
            {
                count_1s = 0;
                Data.Platform_Over = 0;
                Data.Platform_NumberBlink_Flag = 1;
                sprintf((char *)LCD_Line_BUFF[LCD_NUMBER], "         %d          ", Data.Platform);
                LCD_state |= (0x01 << LCD_NUMBER);
            }

        }
		//上下
		if(1 == Data.PWM_Out_UpDown_Flag)	
		{
			Data.PWM_Out_UpDown_Flag = 0;
			PWM_SET_PA6(1000, 80);	//PA6上升频率1000占空比80   
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);	//通道1			
		}
		else if(2 == Data.PWM_Out_UpDown_Flag)
		{
			Data.PWM_Out_UpDown_Flag = 0;
			PWM_SET_PA6(1000, 60);	//PA6下降频率1000占空比60   
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);	//通道1			
		}
		else if(3 == Data.PWM_Out_UpDown_Flag)
		{
			Data.PWM_Out_UpDown_Flag = 0;
			PWM_SET_PA6(1000, 0);	//PA6频率1000占空比0 无效状态   
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);	//通道1				
		}
		else
		{
			;
		}
		
		//开关
	    if(1 == Data.PWM_Out_OpenClose_Flag)	
		{
			Data.PWM_Out_OpenClose_Flag = 0;
			PWM_SET_PA7(2000, 50);	//PA7关门频率 2000占空比50
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);	//通道1			
		}
		else if(2 == Data.PWM_Out_OpenClose_Flag)
		{
			Data.PWM_Out_OpenClose_Flag = 0;
			PWM_SET_PA7(2000, 60);	//PA7开门频率2000占空比60   
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);	//通道1			
		}
		else if(3 == Data.PWM_Out_OpenClose_Flag)
		{
			Data.PWM_Out_OpenClose_Flag = 0;
			PWM_SET_PA7(2000, 0);	//PA7频率2000占空比0 无效状态   
			HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);	//通道1				
		}
		else{
			;
		}
		

		if(Data.SJJ_UP_Start)	//上升时的流水方向
        {
            count_200ms++;
            if(200 == count_200ms)
            {
                count_200ms = 0;
                switch(LED_Index)
                {
                case 1:
                    LED_DIS(0x10, SET);LED_Index = 2;break;
                case 2:
                    LED_DIS(0x20, SET);LED_Index = 3;break;
                case 3:
                    LED_DIS(0x40, SET);LED_Index = 4;break;
                case 4:
                    LED_DIS(0x80, SET);LED_Index = 5;break;
                case 5:
                    LED_DIS(0x10, RESET);LED_Index = 6;break;
                case 6:
                    LED_DIS(0x20, RESET);LED_Index = 7;break;
                case 7:
                    LED_DIS(0x40, RESET);LED_Index = 8;break;
                case 8:
                    LED_DIS(0x80, RESET);LED_Index = 1;break;
                }
            }
        }
        else if(Data.SJJ_DOWN_Start)	//下降时的流水方向
        {
            count_200ms++;
            if(200 <= count_200ms)
            {
                count_200ms = 0;
                switch(LED_Index)
                {
                case 1:
                    LED_DIS(0x80, SET);LED_Index = 2;break;
                case 2:
                    LED_DIS(0x40, SET);LED_Index = 3;break;
                case 3:
                    LED_DIS(0x20, SET);LED_Index = 4;break;
                case 4:
                    LED_DIS(0x10, SET);LED_Index = 5;break;
                case 5:
                    LED_DIS(0x80, RESET);LED_Index = 6;break;
                case 6:
                    LED_DIS(0x40, RESET);LED_Index = 7;break;
                case 7:
                    LED_DIS(0x20, RESET);LED_Index = 8;break;
                case 8:
                    LED_DIS(0x10, RESET);LED_Index = 1;break;
                }
            }
        }
        else	//索引恢复
        {
            LED_Index = 1;
        }		
    }
}
main.c
int main()
{
    TASK_Init();

    while(1)
    {
        LCD_function();
        RTC_function();
        KEY_SCAN();
        Flag_Run();
    }
}

实验现象

9届-电子定时器

题目要求

通过题目知道需要用到的外设 对应引脚
LCD /
LED PC8~PC15PD2
按键 PB0~PB2PA0
PWM PA6
EEPROM PB6PB7

具体表格化

要求
LCD显示存储位置(1~5),定时时间,当前状态(Standby,Setting,Running,Pause)
按键1(B1):存储位置切换(1~5循环),并且显示对应存储的时间
按键2(B2):进入设置状态,短按为时,分,秒循环切换并且高亮,长按则存储到当前位置,并且退出设置状态
按键3(B3):时,分,秒数字递增,短按增加一次,长按快速增加直到松开,超出范围则从头循环
按键4(B4):定时器启动,短按启动运行,运行期间短按则暂停,以此循环;长按则取消定时器运行,回到Standby状态
定时器运行时PA6输出1KHz80%占空比信号,LED1以 0.5s 频率闪烁;定时器停止或者暂停时停止PWM输出LED1灭
掉电重启后显示存储位置1的定时时间
按住按键 0.8s 以上为长按

其他问题

  • 在题目的基础上增加了:当处于运行或者暂停状态下按键4才有效,其他也考虑了很多,功能算全部完成了
  • 注意时间秒分判断那是判断是否到了 255,如果是判断0则时间到了不会00:00:00,判断这个是 255:59:59

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "tim.h"
#include "gpio.h"
#include "lcd.h"
#include "string.h"
#include "stdio.h"
#include "stdbool.h"
#include "i2c_hal.h"
#include "stdlib.h"

#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

//每页的首地址 = (页数*每页多少字节) --->结果是十进制需要转换为十六进制
#define WZ1_ADDR	0x00	//0*8 = 0
#define WZ2_ADDR	0x08	//1*8 = 8
#define WZ3_ADDR	0x10	//2*8 = 16
#define WZ4_ADDR	0x18	//3*8 = 24
#define WZ5_ADDR	0x20	//4*8 = 32

//LCD Dis
#define WZ_NUM	2	//位置
#define TIME_NUM	5	//时间
#define ZT_NUM	8	//状态

//State
#define STANDBY 1	//停止
#define SETTING 2	//设置
#define RUNNING 3	//运行
#define PAUSE 4	//暂停
#define NULLER 0	//无

//时 分 秒 无 索引
#define H_INDEX	1
#define M_INDEX	2
#define S_INDEX	3
#define NULL_INDEX	0

//时间
typedef struct
{
    signed char Hour;
    signed char Min;
    signed char Sec;
} TIMER_TypeDef;

typedef struct
{
    uint8_t WZ_State;	//当前位置(1~5)
    uint8_t One_Read_State;	//上电读取标志位
    bool KEY1_Short_Flag;	//按键1短按标志位
    bool KEY2_Short_Flag;	//按键2短按标志位
    bool KEY2_Long_Flag;	//按键2长按标志位
    bool KEY3_Short_Flag;	//按键3短按标志位
    bool KEY3_Long_Flag;	//按键3长按标志位
    bool KEY4_Short_Flag;	//按键4短按标志位
    bool KEY4_Long_Flag;	//按键4长按标志位
    uint8_t Now_State;	//当前状态(STANDBY  SETTING  RUNNING  PAUSE  NULLER)
    uint8_t LCD_HIGH_State;	//高亮
    uint16_t TIMERT_1S_Count;	//秒计数
} DATA_TypeDef;

extern DATA_TypeDef Data;




void Flag_function(void);
void LCD_function(void);
void TASK_Init(void);
void LED_DIS(uint8_t num, uint8_t swch);
void EEPROM_Read(uint8_t addr, uint8_t *data, uint8_t len);
void EEPROM_Write(uint8_t addr, uint8_t *Data, uint8_t len);
void Read_Time(TIMER_TypeDef *tim, uint8_t addr);
void Write_Time(TIMER_TypeDef *tim, uint8_t addr);
uint8_t KEY_pro(void);
void KEY_function(void);
void KEY1_Short_function(void);
void KEY2_Short_function(void);
void KEY3_Short_function(void);
void Timer_CountDown(TIMER_TypeDef *timx);
void KEY4_Long_function(TIMER_TypeDef *timx);
#endif
my.c
(初始化部分)
/*****************测试数据****************/
#define WR	0

//TIM1默认时间:12:59:50
//TIM2默认时间:07:30:28
//TIM3默认时间:13:22:30
//TIM4默认时间:01:04:20
//TIM5默认时间:02:10:59

/*********************************/

DATA_TypeDef Data;
TIMER_TypeDef TIM_1 = {0};	//位置1定时器
TIMER_TypeDef TIM_2 = {0};	//位置2定时器
TIMER_TypeDef TIM_3 = {0};	//位置3定时器
TIMER_TypeDef TIM_4 = {0};	//位置4定时器
TIMER_TypeDef TIM_5 = {0};	//位置5定时器
TIMER_TypeDef TIM_TEMP = {0};	//缓存时间

static uint8_t HMS_Index = H_INDEX;	//当前时分秒的位置(默认--时)
uint8_t vkey4_count = 0;	//记录定时器初始时间,等定时完成恢复

uint32_t LCD_state = 0xFFFF;
uint8_t LCD_LINE_BuFF[10][20] =
{

    "                    ",
    "                    ",
    "    No 1            ",	//存储位置【3】
    "                    ",
    "                    ",
    "                    ",	//定时时间【6】
    "                    ",
    "                    ",
    "                    ",	//状态【9】
    "                    ",
};

//按键需要
uint8_t KEY_UP, KEY_DOWN, KEY_COUNT_TIME = 0, KEY_VALUE;
uint32_t sys_time = 0;	//系统时间


void TASK_Init(void)
{
    LCD_Init();
    I2CInit();
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    HAL_GPIO_WritePin(GPIOC, 0xFF00, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    HAL_TIM_Base_Start_IT(&htim6);
    Data.WZ_State = 1;	//默认上电存储位置是1
    Data.One_Read_State = 1;	//读取
#if WR	//写入时间
    TIM_1.Hour = 12;
    TIM_1.Min = 59;
    TIM_1.Sec = 50;
    Write_Time(&TIM_1, WZ1_ADDR);
    TIM_2.Hour = 7;
    TIM_2.Min = 30;
    TIM_2.Sec = 28;
    Write_Time(&TIM_2, WZ2_ADDR);
    TIM_3.Hour = 13;
    TIM_3.Min = 22;
    TIM_3.Sec = 30;
    Write_Time(&TIM_3, WZ3_ADDR);
    TIM_4.Hour = 1;
    TIM_4.Min = 4;
    TIM_4.Sec = 20;
    Write_Time(&TIM_4, WZ4_ADDR);
    TIM_5.Hour = 2;
    TIM_5.Min = 10;
    TIM_5.Sec = 59;
    Write_Time(&TIM_5, WZ5_ADDR);
#endif
}
(LCD部分)
//LCD检测
void LCD_function(void)
{
    uint8_t i;


    for(i = 0; i < 10; i++)
    {
        if(LCD_state & (0x01 << i))
        {
            LCD_state &= (~(1 << i));	//清除状态
            LCD_DisplayStringLine(i * 24, (uint8_t *)LCD_LINE_BuFF[i]);	//先刷新

            if(Data.LCD_HIGH_State)	//再高亮对应位置
            {
                switch(Data.LCD_HIGH_State)
                {
                case H_INDEX:	//高亮--时
                {
                    LCD_SetBackColor(Red);
                    LCD_DisplayChar(Line5, 319 - (16 * 6), LCD_LINE_BuFF[5][6]);
                    LCD_DisplayChar(Line5, 319 - (16 * 7), LCD_LINE_BuFF[5][7]);
                    LCD_SetBackColor(Black);
                    break;
                }
                case M_INDEX:	//高亮--分
                {
                    LCD_SetBackColor(Red);
                    LCD_DisplayChar(Line5, 319 - (16 * 9), LCD_LINE_BuFF[5][9]);
                    LCD_DisplayChar(Line5, 319 - (16 * 10), LCD_LINE_BuFF[5][10]);
                    LCD_SetBackColor(Black);
                    break;
                }
                case S_INDEX:	//高亮--秒
                {
                    LCD_SetBackColor(Red);
                    LCD_DisplayChar(Line5, 319 - (16 * 12), LCD_LINE_BuFF[5][12]);
                    LCD_DisplayChar(Line5, 319 - (16 * 13), LCD_LINE_BuFF[5][13]);
                    LCD_SetBackColor(Black);
                    break;
                }
                default:
                {
                    break;
                }
                }
            }

        }
    }
    if(Data.One_Read_State)	//只在上电执行一次
    {
        Data.One_Read_State = 0;
        Read_Time(&TIM_1, WZ1_ADDR);
    }
}
(按键部分)
uint8_t KEY_pro(void)
{

    if((KEY1 == RESET) || (KEY2 == RESET) || (KEY3 == RESET) || (KEY4 == RESET))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }

    return 0;
}

void KEY_function(void)
{
    static uint8_t KEY_Old_value;

    KEY_VALUE = KEY_pro();
    KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ KEY_Old_value);
    KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ KEY_Old_value);
    KEY_Old_value = KEY_VALUE;

    if(KEY_DOWN)
        KEY_COUNT_TIME = 0;

    if(KEY_COUNT_TIME < 10)	//短按
    {
        switch(KEY_UP)
        {
        case 1:	//切换存储位置
        {
            if((RUNNING == Data.Now_State) || (PAUSE == Data.Now_State))	//运行或者暂停状态下按键无效
            {
                break;
            }
            Data.KEY1_Short_Flag = 1;
            break;
        }
        case 2:	//切换时分秒并高亮
        {
            if((RUNNING == Data.Now_State) || (PAUSE == Data.Now_State))	//运行或者暂停状态下按键无效
            {
                break;
            }
            Data.KEY2_Short_Flag = 1;
            Data.Now_State = SETTING;	//设置状态
            break;
        }
        case 3:
        {
            if(SETTING == Data.Now_State)	//如果是在设置状态下才有效
            {
                Data.KEY3_Short_Flag = 1;
            }
            break;
        }
        case 4:
        {
            Data.KEY4_Short_Flag = 1;
            break;
        }
        default:
            break;
        }
    }
    else
    {
        switch(KEY_VALUE)
        {
        case 1:
        {
            break;
        }
        case 2:
        {
            if((RUNNING == Data.Now_State) || (PAUSE == Data.Now_State))	//运行或者暂停状态下按键无效
            {
                break;
            }
            Data.KEY2_Long_Flag = 1;
            Data.Now_State = NULLER;	//设置状态
            break;
        }
        case 3:
        {
            if(SETTING == Data.Now_State)	//如果是在设置状态下才有效
            {
                Data.KEY3_Long_Flag = 1;
            }
            break;
        }
        case 4:
        {
            if((RUNNING == Data.Now_State) || (PAUSE == Data.Now_State))
            {
                Data.KEY4_Long_Flag = 1;
            }
            break;
        }
        default:
            break;
        }
    }
}
(标志位功能实现部分)
//标志位功能实现
void Flag_function(void)
{
    static uint8_t Num = 0;

    if(Data.KEY1_Short_Flag)	//按键1短按
    {
        Data.KEY1_Short_Flag = 0;
        KEY1_Short_function();
    }
    if(Data.KEY2_Short_Flag)	//按键2短按
    {
        Data.KEY2_Short_Flag = 0;
        KEY2_Short_function();
    }
    if(Data.KEY2_Long_Flag)	//按键2长按
    {
        Data.KEY2_Long_Flag = 0;
        Data.LCD_HIGH_State = 0;	//清除高亮
        HMS_Index = 1;	//时分秒控制恢复初始
        LCD_state |= (0x01 << TIME_NUM);
        sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "                    ");
        LCD_state |= (0x01 << ZT_NUM);
        switch(Data.WZ_State)	//判断位置
        {
        case 1:
        {
            Write_Time(&TIM_1, WZ1_ADDR);
            break;
        }
        case 2:
        {
            Write_Time(&TIM_2, WZ2_ADDR);
            break;
        }
        case 3:
        {
            Write_Time(&TIM_3, WZ3_ADDR);
            break;
        }
        case 4:
        {
            Write_Time(&TIM_4, WZ4_ADDR);
            break;
        }
        case 5:
        {
            Write_Time(&TIM_5, WZ5_ADDR);
            break;
        }

        }

    }
    if(Data.KEY3_Short_Flag)	//按键3短按
    {
        Data.KEY3_Short_Flag = 0;
        KEY3_Short_function();
    }
    if(Data.KEY3_Long_Flag)	//按键3长按
    {
        Data.KEY3_Long_Flag = 0;
        KEY3_Short_function();
        HAL_Delay(60);	//触发长按的间隔60ms(这样数字递增的速度会慢点)
    }
    if(Data.KEY4_Short_Flag)	//按键4短按
    {
        Data.KEY4_Short_Flag = 0;
        Data.LCD_HIGH_State = 0;	//清除高亮
        HMS_Index = 1;	//时分秒控制恢复初始
        LCD_state |= (0x01 << TIME_NUM);

        switch(Num)
        {
        case 0:
        {
            Data.Now_State = RUNNING;	//设置状态
            sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "       Running      ");
            LCD_state |= (0x01 << ZT_NUM);
            Data.TIMERT_1S_Count = 0;	//清0
            HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);	//开启PWM模式
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 800);	//输出PWM 1KHz 80%占空比
            Num = 1;
            break;
        }
        case 1:
        {
            Data.Now_State = PAUSE;	//设置状态
            sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "       Pause        ");
            LCD_state |= (0x01 << ZT_NUM);
            Data.TIMERT_1S_Count = 0;	//清0
            HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);	//停止PWM模式
            LED_DIS(0x01, RESET);
            Num = 0;
            break;
        }
        }
    }
    if(Data.KEY4_Long_Flag)	//按键4长按
    {
        Data.KEY4_Long_Flag = 0;
        switch(Data.WZ_State)
        {
        case 1:
        {
            KEY4_Long_function(&TIM_1);
            break;
        }
        case 2:
        {
            KEY4_Long_function(&TIM_2);
            break;
        }
        case 3:
        {
            KEY4_Long_function(&TIM_3);
            break;
        }
        case 4:
        {
            KEY4_Long_function(&TIM_4);
            break;
        }
        case 5:
        {
            KEY4_Long_function(&TIM_5);
            break;
        }
        default:
            break;
        }

    }
    if(STANDBY == Data.Now_State)
    {
        HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);	//停止PWM模式
        LED_DIS(0x01, RESET);
        Num = 0;	//默认是暂停状态才能保证在standby状态下短按B4就开始运行
    }
}
(按键4长按功能实现部分)
//按键4长按功能实现
void KEY4_Long_function(TIMER_TypeDef *timx)
{
    timx->Hour = TIM_TEMP.Hour;
    timx->Min = TIM_TEMP.Min;
    timx->Sec = TIM_TEMP.Sec;
    vkey4_count = 0;
    Data.Now_State = STANDBY;		//状态
    sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "      Standby       ");
    LCD_state |= (0x01 << ZT_NUM);
    sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", timx->Hour, timx->Min, timx->Sec);	//刷新时间
    LCD_state |= (0x01 << TIME_NUM);
}
(按键3短按功能实现部分)
//按键3短按功能实现
void KEY3_Short_function(void)
{
    //先判断当前位置是哪个定时器
    switch(Data.WZ_State)
    {
    case 1:
    {
        switch(Data.LCD_HIGH_State)	//根据高亮的地方判断
        {
        case H_INDEX:	//(0~23)
        {
            TIM_1.Hour += 1;
            if(TIM_1.Hour > 23)
                TIM_1.Hour = 0;
            break;
        }
        case M_INDEX:	//(0~59)
        {
            TIM_1.Min += 1;
            if(TIM_1.Min > 59)
                TIM_1.Min = 0;
            break;
        }
        case S_INDEX:	//(0~59)
        {
            TIM_1.Sec += 1;
            if(TIM_1.Sec > 59)
                TIM_1.Sec = 0;
            break;
        }
        default:
            break;
        }
        sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", (&TIM_1)->Hour, (&TIM_1)->Min, (&TIM_1)->Sec);	//更新时间
        break;
    }
    case 2:
    {
        switch(Data.LCD_HIGH_State)	//根据高亮的地方判断
        {
        case H_INDEX:	//(0~23)
        {
            TIM_2.Hour += 1;
            if(TIM_2.Hour > 23)
                TIM_2.Hour = 0;
            break;
        }
        case M_INDEX:	//(0~59)
        {
            TIM_2.Min += 1;
            if(TIM_2.Min > 59)
                TIM_2.Min = 0;
            break;
        }
        case S_INDEX:	//(0~59)
        {
            TIM_2.Sec += 1;
            if(TIM_2.Sec > 59)
                TIM_2.Sec = 0;
            break;
        }
        default:
            break;
        }
        sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", (&TIM_2)->Hour, (&TIM_2)->Min, (&TIM_2)->Sec);
        break;
    }
    case 3:
    {
        switch(Data.LCD_HIGH_State)	//根据高亮的地方判断
        {
        case H_INDEX:	//(0~23)
        {
            TIM_3.Hour += 1;
            if(TIM_3.Hour > 23)
                TIM_3.Hour = 0;
            break;
        }
        case M_INDEX:	//(0~59)
        {
            TIM_3.Min += 1;
            if(TIM_3.Min > 59)
                TIM_3.Min = 0;
            break;
        }
        case S_INDEX:	//(0~59)
        {
            TIM_3.Sec += 1;
            if(TIM_3.Sec > 59)
                TIM_3.Sec = 0;
            break;
        }
        default:
            break;
        }
        sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", (&TIM_3)->Hour, (&TIM_3)->Min, (&TIM_3)->Sec);
        break;
    }
    case 4:
    {
        switch(Data.LCD_HIGH_State)	//根据高亮的地方判断
        {
        case H_INDEX:	//(0~23)
        {
            TIM_4.Hour += 1;
            if(TIM_4.Hour > 23)
                TIM_4.Hour = 0;
            break;
        }
        case M_INDEX:	//(0~59)
        {
            TIM_4.Min += 1;
            if(TIM_4.Min > 59)
                TIM_4.Min = 0;
            break;
        }
        case S_INDEX:	//(0~59)
        {
            TIM_4.Sec += 1;
            if(TIM_4.Sec > 59)
                TIM_4.Sec = 0;
            break;
        }
        default:
            break;
        }
        sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", (&TIM_4)->Hour, (&TIM_4)->Min, (&TIM_4)->Sec);
        break;
    }
    case 5:
    {
        switch(Data.LCD_HIGH_State)	//根据高亮的地方判断
        {
        case H_INDEX:	//(0~23)
        {
            TIM_5.Hour += 1;
            if(TIM_5.Hour > 23)
                TIM_5.Hour = 0;
            break;
        }
        case M_INDEX:	//(0~59)
        {
            TIM_5.Min += 1;
            if(TIM_5.Min > 59)
                TIM_5.Min = 0;
            break;
        }
        case S_INDEX:	//(0~59)
        {
            TIM_5.Sec += 1;
            if(TIM_5.Sec > 59)
                TIM_5.Sec = 0;
            break;
        }
        default:
            break;
        }
        sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", (&TIM_5)->Hour, (&TIM_5)->Min, (&TIM_5)->Sec);
        break;
    }
    default:
        break;
    }

    LCD_state |= (0x01 << TIME_NUM);	//刷新显示
}
(按键2功能实现部分)
//按键2功能实现
void KEY2_Short_function(void)
{
    sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "       Setting      ");
    LCD_state |= (0x01 << ZT_NUM);
    switch(HMS_Index)	//控制时分秒
    {
    case H_INDEX:	//时
    {
        Data.LCD_HIGH_State = H_INDEX;
        HMS_Index = M_INDEX;
        break;
    }
    case M_INDEX:	//分
    {
        Data.LCD_HIGH_State = M_INDEX;
        HMS_Index = S_INDEX;
        break;
    }
    case S_INDEX:	//秒
    {
        Data.LCD_HIGH_State = S_INDEX;
        HMS_Index = H_INDEX;
        break;
    }
    default:
        break;
    }
    LCD_state |= (0x01 << TIME_NUM);

}
(按键1功能实现部分)
//按键1功能实现
void KEY1_Short_function(void)
{
    switch(Data.WZ_State)
    {
    case 1:	//读取位置2并且显示
    {

        Read_Time(&TIM_2, WZ2_ADDR);
        sprintf((char *)LCD_LINE_BuFF[WZ_NUM], "    No 2            ");
        Data.WZ_State = 2;
        break;
    }
    case 2:	//读取位置3并且显示
    {
        Read_Time(&TIM_3, WZ3_ADDR);
        sprintf((char *)LCD_LINE_BuFF[WZ_NUM], "    No 3            ");
        Data.WZ_State = 3;
        break;
    }
    case 3:	//读取位置4并且显示
    {
        Read_Time(&TIM_4, WZ4_ADDR);
        sprintf((char *)LCD_LINE_BuFF[WZ_NUM], "    No 4            ");
        Data.WZ_State = 4;
        break;
    }
    case 4:	//读取位置5并且显示
    {
        Read_Time(&TIM_5, WZ5_ADDR);
        sprintf((char *)LCD_LINE_BuFF[WZ_NUM], "    No 5            ");
        Data.WZ_State = 5;
        break;
    }
    case 5:	//读取位置1并且显示
    {
        Read_Time(&TIM_1, WZ1_ADDR);
        sprintf((char *)LCD_LINE_BuFF[WZ_NUM], "    No 1            ");
        Data.WZ_State = 1;
        break;
    }
    default:
        break;
    }

    LCD_state |= (0x01 << WZ_NUM);	//刷新

}
(掉电保护部分)
//页写
void EEPROM_Write(uint8_t addr, uint8_t *Data, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);	//写操作
    I2CSendAck();

    I2CSendByte(addr);
    I2CSendAck();

    while(len--)
    {
        I2CSendByte(*Data++);
        I2CSendAck();
        addr++;
        if((addr & 0x07) == 0)
            break;
    }
    I2CStop();
}

//页读
void EEPROM_Read(uint8_t addr, uint8_t *data, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);	//写操作
    I2CSendAck();

    I2CSendByte(addr);	//需要读的开始地址
    I2CSendAck();
    I2CStart();
    I2CSendByte(0xA1);
    I2CSendAck();

    while(len--)
    {
        *data++ = I2CReceiveByte();

        if(len)
            I2CSendAck();
        else
            I2CSendNotAck();
    }
    I2CStop();
}

//从EEPROM读取时间并且显示
//参数1:读取的时间需要保存在哪个定时器 参数2:定时器地址
void Read_Time(TIMER_TypeDef *tim, uint8_t addr)
{
    uint8_t Read_Temp[3] = {0};
    EEPROM_Read(addr, (uint8_t *)Read_Temp, 3);	//读取
    HAL_Delay(10);
    tim->Hour = Read_Temp[0];
    tim->Min = Read_Temp[1];
    tim->Sec = Read_Temp[2];
    sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", tim->Hour, tim->Min, tim->Sec);	//刷新时间
    LCD_state |= (0x01 << TIME_NUM);
}

//往EEPROM写入时间
//参数1:需要写入的定时器 参数2:定时器地址
void Write_Time(TIMER_TypeDef *tim, uint8_t addr)
{
    uint8_t Write_Temp[3] = {0};
    Write_Temp[0] = tim->Hour;
    Write_Temp[1] = tim->Min;
    Write_Temp[2] = tim->Sec;
    EEPROM_Write(addr, (uint8_t *)Write_Temp, 3);
    HAL_Delay(10);
}
(LED,倒计时部分)
void LED_DIS(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);	//点亮
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);	//熄灭
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

//倒计时
void Timer_CountDown(TIMER_TypeDef *timx)
{
    timx->Sec--;
    if(timx->Sec == 255)	//注意溢出,无符号0之后变255
    {
        timx->Min--;
        timx->Sec = 59;
    }
    if(timx->Min == 255)
    {
        timx->Hour--;
        timx->Min = 59;
    }


    if((timx->Hour == 255) && (timx->Min == 59) && (timx->Sec == 59))	//停止 0:0:0 再减1 就是 255:59:59
    {
        //恢复初值
        timx->Hour = TIM_TEMP.Hour;
        timx->Min = TIM_TEMP.Min;
        timx->Sec = TIM_TEMP.Sec;
        vkey4_count = 0;
        Data.Now_State = STANDBY;		//状态
        sprintf((char *)LCD_LINE_BuFF[ZT_NUM], "      Standby       ");
        LCD_state |= (0x01 << ZT_NUM);
    }
    sprintf((char *)LCD_LINE_BuFF[TIME_NUM], "      %02d:%02d:%02d      ", timx->Hour, timx->Min, timx->Sec);	//刷新时间
    LCD_state |= (0x01 << TIME_NUM);
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t count = 0;
    static uint16_t key_count = 0;
    static uint16_t LED_500ms_count = 0;

    if(htim == &htim6)
    {
        sys_time++;
        count++;
        key_count++;
        Data.TIMERT_1S_Count++;

        if(10 == count)	//按键扫描
        {
            count = 0;
            KEY_function();
        }
        if(80 == key_count)	//长按计数
        {
            key_count = 0;
            KEY_COUNT_TIME++;
        }
        if(RUNNING == Data.Now_State)
        {
            LED_500ms_count++;
            if(500 == LED_500ms_count)
            {
                LED_500ms_count = 0;
                HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);	//翻转电平
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
            }

            if(1000 == Data.TIMERT_1S_Count)
            {
                Data.TIMERT_1S_Count = 0;
                switch(Data.WZ_State)
                {
                case 1:
                {
                    if(0 == vkey4_count)
                    {
                        TIM_TEMP.Hour = TIM_1.Hour;
                        TIM_TEMP.Min = TIM_1.Min;
                        TIM_TEMP.Sec = TIM_1.Sec;
                        vkey4_count++;
                    }
                    Timer_CountDown(&TIM_1);
                    break;
                }
                case 2:
                {
                    if(0 == vkey4_count)
                    {
                        TIM_TEMP.Hour = TIM_2.Hour;
                        TIM_TEMP.Min = TIM_2.Min;
                        TIM_TEMP.Sec = TIM_2.Sec;
                        vkey4_count++;
                    }
                    Timer_CountDown(&TIM_2);
                    break;
                }
                case 3:
                {
                    if(0 == vkey4_count)
                    {
                        TIM_TEMP.Hour = TIM_3.Hour;
                        TIM_TEMP.Min = TIM_3.Min;
                        TIM_TEMP.Sec = TIM_3.Sec;
                        vkey4_count++;
                    }
                    Timer_CountDown(&TIM_3);
                    break;
                }
                case 4:
                {
                    if(0 == vkey4_count)
                    {
                        TIM_TEMP.Hour = TIM_4.Hour;
                        TIM_TEMP.Min = TIM_4.Min;
                        TIM_TEMP.Sec = TIM_4.Sec;
                        vkey4_count++;
                    }
                    Timer_CountDown(&TIM_4);
                    break;
                }
                case 5:
                {
                    if(0 == vkey4_count)
                    {
                        TIM_TEMP.Hour = TIM_5.Hour;
                        TIM_TEMP.Min = TIM_5.Min;
                        TIM_TEMP.Sec = TIM_5.Sec;
                        vkey4_count++;
                    }
                    Timer_CountDown(&TIM_5);
                    break;
                }
                default:
                    break;
                }
            }

        }
    }
}
main.c
#include "lcd.h"
#include "my.h"

int main()
{
    TASK_Init();

    while(1)
    {
        LCD_function();
        Flag_function();
    }
}

实验现象

10届-电压检测仪

题目要求

通过题目知道需要用到的外设 对应引脚
LCD /
LED PC8~PC15PD2
按键 PB0~PB2PA0
ADC PB15
EEPROM PB6PB7

具体表格化

要求
上电:读取EEPROM里的参数
默认页面:显示读取R37的ADC值(0~3.3V),还有显示状态(Upper,Normal,Lower)
按键1:在默认页面和参数设置页面之间切换,当在参数设置页面切换到默认页面时把参数保存到EEPROM里
按键2:高亮参数页面的4个选项(上限电压,下限电压,上限指示灯,下限指示灯),循环高亮
按键3:增加高亮处的值,如果是电压则 +0.3V,如果是指示灯则LED+1
按键4:减小高亮处的值,如果是电压则 -0.3V,如果是指示灯则LED-1
按键2,3,4在参数页面才有效
电压参数设置范围 0~3.3V 指示灯参数设置范围 1~8(LED1~LED8)
上下限指示灯不能是同一个!上限电压设定不能小于下限电压,下限电压设定不能大于上限电压
R37输出电压 > 上限电压 则上限指示灯0.2s闪烁 下限指示灯熄灭\text{R37输出电压 > 上限电压 则上限指示灯0.2s闪烁 下限指示灯熄灭}
R37输出电压 < 下限电压 则下限指示灯0.2s闪烁 上限指示灯熄灭\text{R37输出电压 < 下限电压 则下限指示灯0.2s闪烁 上限指示灯熄灭}
R37输出电压在上限和下限之间(包含上下限)时,上下限指示灯熄灭\text{R37输出电压在上限和下限之间(包含上下限)时,上下限指示灯熄灭}

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H

#include "main.h"
#include "adc.h"
#include "tim.h"
#include "gpio.h"
#include "i2c_hal.h"
#include "lcd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdbool.h"

//对应行数

#define BT_State 2
#define C1_State 3
#define C2_State 4
#define C3_State 5
#define C4_State 6

//按键电平读取
#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define DATA_ADDR 0x08	//第8页开始存储参数

#define KEY_TIME 20	//20ms扫描一次
#define ADC_TIME 100	//100ms读取一次

//参数结构体
typedef struct
{
    uint8_t UP_LED;	//超出上限指示灯(1~8)
    uint8_t DOWN_LED;	//超出下限指示灯(1~8)
    float V_UP;	//电压上限(保留一位小数)
    float V_DOWN;	//电压下限(保留一位小数)
    float Now_V;	//当前电压
} PARAMETER_TypeDef;

//时间扫描结构体
typedef struct
{
    uint16_t KEY_TIME_COUNT;	//按键扫描
    uint16_t ADC_TIME_COUNT;	//ADC扫描
} TASK_TypeDef;

//数据结构体
typedef struct
{
    bool KEY1_DOWN_Flag;	//按键1按下标志位
    bool KEY2_DOWN_Flag;	//按键2按下标志位
    bool KEY3_DOWN_Flag;	//按键3按下标志位
    bool KEY4_DOWN_Flag;	//按键4按下标志位
    bool MENU_State;	//页面1,2切换标志位
    bool One_Flag;	//是否第一次上电标志位
    uint8_t High_Line_Flag;	//高亮选择
    bool UP_LED_Flag;	//上限指示灯标志位
    bool DOWN_LED_Flag;	//下限指示灯标志位
    bool LED_SCAN_Flag;	//LED闪烁标志位
} DATA_TypeDef;



void LED_DIS(uint8_t num, uint8_t swch);	//LED显示
void TASK_Init(void);	//初始化
void LCD_function(void);	//LCD检测刷新
void KEY_function(void);	//按键检测
uint8_t KEY_Pro(void);	//按键读取
void EEPROM_Page_Write(uint8_t addr, uint8_t *date, uint8_t len);	//EEPROM页写
void EEPROM_Page_Read(uint8_t addr, uint8_t *date, uint8_t len);	//EEPROM页读
void Parameter_Read(PARAMETER_TypeDef *para);	//读取EEPROM参数
void Parameter_Write(PARAMETER_TypeDef *para);	//写入EEPROM参数
void LCD_Dis1(void);	//主页面
void LCD_Dis2(void);	//参数页面
void Run_function(void);	//标志位执行功能函数
void GetAdc(void);	//获取ADC
void Add_V(PARAMETER_TypeDef *para, uint8_t dir);	//增加电压
void Add_LED(PARAMETER_TypeDef *para, uint8_t dir);	//增加LED
void Arr_KG(uint8_t arr[][20], uint8_t NUM);	//添加空格
void Reduce_LED(PARAMETER_TypeDef *para, uint8_t dir);	//减小电压
void Reduce_V(PARAMETER_TypeDef *para, uint8_t dir);	//减小LED
void LED_Togg(uint8_t num);	//LED翻转
void Judge_function(void);	//判断是否超出限制函数
void LED_Flicker(void);	//LED闪烁执行

#endif
my.c
(初始化部分)
uint32_t LCD_State = 0xFFFF;
uint8_t LCD_Line_BUFF[10][20] =
{
    "                    ",
    "                    ",
    "                    ",	//2		页面1,2标题
    "                    ",	//3		页面2 参数1
    "                    ",	//4		页面2 参数2
    "                    ",	//5  	页面1当前电压 页面2 参数3
    "                    ",	//6  	页面1当前状态 页面2 参数4
    "                    ",
    "                    ",
    "                    ",
};

static uint8_t choose_Index = 0;	//按键2的索引,决定第一次按下时的位置,默认0
PARAMETER_TypeDef parameter_Data = {0};
TASK_TypeDef task_timer = {0};
DATA_TypeDef Data = {0};
uint8_t KEY_UP, KEY_DOWN, KEY_VALUE, KEY_TIMER_COUNT;

//初始化
void TASK_Init(void)
{
    LCD_Init();
    I2CInit();
    LED_DIS(0xFF, RESET);
    HAL_TIM_Base_Start_IT(&htim6);
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);	//ADC校验
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    Data.MENU_State = 0;	//默认是0
    Parameter_Read(&parameter_Data);	//上电读取参数
# if 0
    parameter_Data.Now_V = 2.04;
    parameter_Data.V_UP  = 2.1;
    parameter_Data.V_DOWN = 0.4;
    parameter_Data.UP_LED = 1;
    parameter_Data.DOWN_LED = 3;
    Parameter_Write(&parameter_Data);
#endif
}
(EEPROM读写部分)
//页写
void EEPROM_Page_Write(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);	//写操作
    I2CWaitAck();

    I2CSendByte(addr);	//写入地址
    I2CWaitAck();

    while(len--)
    {
        I2CSendByte(*date++);
        I2CWaitAck();

        addr++;
        if((addr & 0x07) == 0)
        {
            break;
        }
    }
    I2CStop();
    HAL_Delay(10);
}



//页读
void EEPROM_Page_Read(uint8_t addr, uint8_t *date, uint8_t len)
{
    I2CStart();
    I2CSendByte(0xA0);	//写入
    I2CWaitAck();

    I2CSendByte(addr);
    I2CWaitAck();
    I2CStart();
    I2CSendByte(0xA1);
    I2CWaitAck();
    while(len--)
    {
        *date++ = I2CReceiveByte();
        if(len)
            I2CSendAck();
        else
            I2CSendNotAck();
    }
    I2CStop();
}

//写入参数
void Parameter_Write(PARAMETER_TypeDef *para)
{
    //存储时乘以10以整数形式保存
    uint8_t Temp[4] = {0};
    Temp[0] = para->UP_LED;
    Temp[1] = para->DOWN_LED;
    Temp[2] = para->V_UP * 10;
    Temp[3] = para->V_DOWN * 10;

    EEPROM_Page_Write(DATA_ADDR, (uint8_t *)Temp, 4);
}

//读取参数
void Parameter_Read(PARAMETER_TypeDef *para)
{
    //取出时除以10恢复小数
    uint8_t Temp[4] = {0};

    EEPROM_Page_Read(DATA_ADDR, (uint8_t *)Temp, 4);
    para->UP_LED = Temp[0];
    para->DOWN_LED = Temp[1];
    para->V_UP = Temp[2] / (float)10;
    para->V_DOWN = Temp[3] / (float)10;
}
(按键部分)
//按键检测
uint8_t KEY_UP, KEY_DOWN, KEY_VALUE, KEY_TIMER_COUNT;

uint8_t KEY_Pro(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }

    return 0;
}


void KEY_function(void)
{
    static uint8_t key_oldValue;
    if(!task_timer.KEY_TIME_COUNT)
    {
        task_timer.KEY_TIME_COUNT = KEY_TIME;
        KEY_VALUE = KEY_Pro();
        KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ key_oldValue);
        KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ key_oldValue);
        key_oldValue = KEY_VALUE;

        if(KEY_DOWN)
        {
            KEY_TIMER_COUNT = 0;
        }

        if(KEY_TIMER_COUNT < 10)
        {
            switch(KEY_UP)
            {

            case 1:
            {
                Data.KEY1_DOWN_Flag = 1;
                break;
            }
            case 2:
            {
                if(0 == Data.MENU_State)	//2,3,4按键在主页面失效
                {
                    break;
                }
                Data.KEY2_DOWN_Flag = 1;
                break;
            }
            case 3:
            {
                if((0 == Data.MENU_State) || (0 == Data.High_Line_Flag))	//2,3,4按键在主页面失效且需要按键2选中参数项才有效
                {
                    break;
                }
                Data.KEY3_DOWN_Flag = 1;
                break;
            }
            case 4:
            {
                if((0 == Data.MENU_State) || (0 == Data.High_Line_Flag))	//2,3,4按键在主页面失效且需要按键2选中参数项才有效
                {
                    break;
                }
                Data.KEY4_DOWN_Flag = 1;
                break;
            }
            default:
                break;
            }
        }
    }
}
(标志位功能实现部分)
//标志位功能实现
void Run_function(void)
{
    if(Data.KEY1_DOWN_Flag)
    {
        Data.KEY1_DOWN_Flag = 0;
        choose_Index = 0;	//按键2的索引恢复初值
        Data.MENU_State = !Data.MENU_State;	//取反
        if(Data.MENU_State)	//切换到参数设置页面
        {
            LCD_Dis2();
        }
        else	//切换到主页面并且保存参数到EEEPROM
        {
            Parameter_Write(&parameter_Data);	//写入
            LCD_Dis1();
        }
    }
    if(Data.KEY2_DOWN_Flag)
    {
        Data.KEY2_DOWN_Flag = 0;
        switch(choose_Index)
        {
        case 0:	//选择上限电压项
        {
            Data.High_Line_Flag = 1;	//高亮第一个选项
            LCD_State |= (0x01 << C1_State);
            choose_Index = 1;
            break;
        }
        case 1:	//选择下限电压项
        {
            Data.High_Line_Flag = 2;	//高亮第二个选项
            LCD_State |= (0x01 << C2_State);
            choose_Index = 2;
            break;
        }
        case 2:	//选择上限LED项
        {
            Data.High_Line_Flag = 3;	//高亮第三个选项
            LCD_State |= (0x01 << C3_State);
            choose_Index = 3;
            break;
        }
        case 3:	//选择下限LED项
        {
            Data.High_Line_Flag = 4;	//高亮第四个选项
            LCD_State |= (0x01 << C4_State);
            choose_Index = 0;
            break;
        }
        default:
            break;
        }

    }
    if(Data.KEY3_DOWN_Flag)
    {
        Data.KEY3_DOWN_Flag = 0;
        switch(Data.High_Line_Flag)
        {
        case 1:	//上限电压改变
        {
            Add_V(&parameter_Data, SET);
            break;
        }
        case 2:	//下限电压改变
        {
            Add_V(&parameter_Data, RESET);
            break;
        }
        case 3:	//上限LED改变(需要关闭所有LED再选否则之前的灯还在亮)
        {
            LED_DIS(0xFF, RESET);
            Add_LED(&parameter_Data, SET);
            break;
        }
        case 4:	//上限LED改变
        {
            LED_DIS(0xFF, RESET);
            Add_LED(&parameter_Data, RESET);
            break;
        }
        default:
            break;
        }
    }
    if(Data.KEY4_DOWN_Flag)
    {
        Data.KEY4_DOWN_Flag = 0;
        switch(Data.High_Line_Flag)
        {
        case 1:	//上限电压改变
        {
            Reduce_V(&parameter_Data, SET);
            break;
        }
        case 2:	//下限电压改变
        {
            Reduce_V(&parameter_Data, RESET);
            break;
        }
        case 3:	//上限LED改变
        {
            LED_DIS(0xFF, RESET);
            Reduce_LED(&parameter_Data, SET);
            break;
        }
        case 4:	//上限LED改变
        {
            LED_DIS(0xFF, RESET);
            Reduce_LED(&parameter_Data, RESET);
            break;
        }
        default:
            break;
        }
    }
}
(判断是否超出电压部分)
//判断是否超出限制函数
void Judge_function(void)
{
	uint16_t x = 0,y = 0,z = 0;
	x = parameter_Data.Now_V*100;	//扩大100倍再比较,因为电压值是小数当上下限相等时几率很小的所以扩大后就精确
	y = parameter_Data.V_UP*100;
	z = parameter_Data.V_DOWN*100;
    if(x > y)	//当前电压>上限
    {
        Data.UP_LED_Flag = 1;	//上限LED亮
        Data.DOWN_LED_Flag = 0;	//下限LED熄灭

        if(!Data.MENU_State)	//主页面才显示状态 参数页面不显示
        {
            sprintf((char *)LCD_Line_BUFF[C4_State], "    Status:%s", "Upper");	//状态
            LCD_State |= (0x01 << C4_State);
        }
    }
    if(x < z)	//当前电压<下限
    {
        Data.UP_LED_Flag = 0;	//上限LED熄灭
        Data.DOWN_LED_Flag = 1;	//下限LED亮
	
        if(!Data.MENU_State)	//主页面才显示状态 参数页面不显示
        {
            sprintf((char *)LCD_Line_BUFF[C4_State], "    Status:%s", "Lower");	//状态
            LCD_State |= (0x01 << C4_State);
        }
    }
	
    if((x <= y) && (x >= z))	//下限<= 当前电压 <=上限
    {
        Data.UP_LED_Flag = 0;	//上限LED熄灭
        Data.DOWN_LED_Flag = 0;	//下限LED熄灭
        LED_DIS(0xFF, RESET);
        if(!Data.MENU_State)	//主页面才显示状态 参数页面不显示
        {
            sprintf((char *)LCD_Line_BUFF[C4_State], "    Status:%s", "Normal");	//状态
            LCD_State |= (0x01 << C4_State);
        }
    }
}
(电压,指示灯的设定部分)
//增加电压函数
//参数1:电压数据存储结构体
//参数2:SET--上限 RESET--下限
void Add_V(PARAMETER_TypeDef *para, uint8_t dir)
{
    if(SET == dir)
    {
        para->V_UP += 0.3f;
        if(para->V_UP >= 3.3f)
        {
            para->V_UP = 3.3;
        }
        sprintf((char *)LCD_Line_BUFF[C1_State], "Max Volt:%.1fV", parameter_Data.V_UP);	//上限电压
        LCD_State |= (0x01 << C1_State);	//刷新

    }
    else
    {
        para->V_DOWN += 0.3f;
        if(para->V_DOWN >= 3.3f)
        {
            para->V_DOWN = 3.3;
        }
		if((para->V_DOWN) >= (para->V_UP))	//如果下限大于等于上限则下限最大是:等于的这个值
		{
			para->V_DOWN = para->V_UP;
		}		
        sprintf((char *)LCD_Line_BUFF[C2_State], "Min Volt:%.1fV", parameter_Data.V_DOWN);	//下限电压
        LCD_State |= (0x01 << C2_State);	//刷新
    }
}

//增加LED函数
//参数1:LED数据存储结构体
//参数2:SET--上限LED RESET--下限LED
void Add_LED(PARAMETER_TypeDef *para, uint8_t dir)
{
    if(SET == dir)
    {
        para->UP_LED += 1;
        if(para->UP_LED >= 8)
        {
            para->UP_LED = 8;
        }
        if((para->UP_LED) == (para->DOWN_LED))	//如果相等就跳过这个LED
        {
            if(para->UP_LED >= 8)
            {
                para->UP_LED = 1;
            }
            else
            {
                para->UP_LED += 1;
            }
        }
        sprintf((char *)LCD_Line_BUFF[C3_State], "Upper:LD%d", parameter_Data.UP_LED);	//上限电压
        LCD_State |= (0x01 << C3_State);	//刷新

    }
    else
    {
        para->DOWN_LED += 1;
        if(para->DOWN_LED >= 8)
        {
            para->DOWN_LED = 8;
        }
        if((para->DOWN_LED) == (para->UP_LED))	//如果相等就跳过这个LED
        {
            if(para->DOWN_LED >= 8)
            {
                para->DOWN_LED = 1;
            }
            else
            {
                para->DOWN_LED += 1;
            }
        }
        sprintf((char *)LCD_Line_BUFF[C4_State], "Lower:LD%d", parameter_Data.DOWN_LED);	//下限电压
        LCD_State |= (0x01 << C4_State);	//刷新
    }
}

//减少电压函数
//参数1:电压数据存储结构体
//参数2:SET--上限 RESET--下限
void Reduce_V(PARAMETER_TypeDef *para, uint8_t dir)
{
    if(SET == dir)
    {
        para->V_UP -= 0.3f;
        if(para->V_UP <= 0)
        {
            para->V_UP = 0;
        }
		if((para->V_UP) <= (para->V_DOWN))	//如果上限小于等于下限则上限最大是:等于的这个值(因为不可能上限小于下限的)
		{
			para->V_UP = para->V_DOWN;
		}		
        sprintf((char *)LCD_Line_BUFF[C1_State], "Max Volt:%.1fV", parameter_Data.V_UP);	//上限电压
        LCD_State |= (0x01 << C1_State);	//刷新

    }
    else
    {
        para->V_DOWN -= 0.3f;
        if(para->V_DOWN <= 0)
        {
            para->V_DOWN = 0;
        }
        sprintf((char *)LCD_Line_BUFF[C2_State], "Min Volt:%.1fV", parameter_Data.V_DOWN);	//下限电压
        LCD_State |= (0x01 << C2_State);	//刷新
    }
}

//减少LED函数
//参数1:LED数据存储结构体
//参数2:SET--上限LED RESET--下限LED
void Reduce_LED(PARAMETER_TypeDef *para, uint8_t dir)
{
    if(SET == dir)
    {
        para->UP_LED -= 1;
        if(para->UP_LED <= 1)
        {
            para->UP_LED = 1;
        }
        if((para->UP_LED) == (para->DOWN_LED))	//如果相等就跳过这个LED
        {
            if(para->UP_LED <= 1)
            {
                para->UP_LED = 8;
            }
            else
            {
                para->UP_LED -= 1;
            }
        }
        sprintf((char *)LCD_Line_BUFF[C3_State], "Upper:LD%d", parameter_Data.UP_LED);	//上限电压
        LCD_State |= (0x01 << C3_State);	//刷新

    }
    else
    {
        para->DOWN_LED -= 1;
        if(para->DOWN_LED <= 1)
        {
            para->DOWN_LED = 1;
        }
        if((para->DOWN_LED) == (para->UP_LED))	//如果相等就跳过这个LED
        {
            if(para->DOWN_LED <= 1)
            {
                para->DOWN_LED = 8;
            }
            else
            {
                para->DOWN_LED -= 1;
            }
        }
        sprintf((char *)LCD_Line_BUFF[C4_State], "Lower:LD%d", parameter_Data.DOWN_LED);	//下限电压
        LCD_State |= (0x01 << C4_State);	//刷新
    }
}
(ADC部分)
//R37电压读取
void GetAdc(void)
{
    uint32_t adc;
    if(!task_timer.ADC_TIME_COUNT)
    {
        task_timer.ADC_TIME_COUNT = ADC_TIME;
        HAL_ADC_Start(&hadc2);
        HAL_ADC_PollForConversion(&hadc2, 300);	//300ms超时
        if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))	//读取完成
        {
            adc = HAL_ADC_GetValue(&hadc2);
            parameter_Data.Now_V = adc * 3.3 / 4096;
            if(parameter_Data.Now_V > (double)3.29)
                parameter_Data.Now_V = 3.3;
        }
    }
}
(LED部分)
//LED闪烁
void LED_Flicker(void)
{
    if(Data.LED_SCAN_Flag)
    {
        Data.LED_SCAN_Flag = 0;
        if(Data.UP_LED_Flag)
        {
            switch(parameter_Data.UP_LED)
            {
            case 1:
                LED_Togg(0x01);
                break;
            case 2:
                LED_Togg(0x02);
                break;
            case 3:
                LED_Togg(0x04);
                break;
            case 4:
                LED_Togg(0x08);
                break;
            case 5:
                LED_Togg(0x10);
                break;
            case 6:
                LED_Togg(0x20);
                break;
            case 7:
                LED_Togg(0x40);
                break;
            case 8:
                LED_Togg(0x80);
                break;
            default:
                break;
            }
        }
        if(!Data.UP_LED_Flag)	//顺便判断标志位等于0时直接熄灭因为有时候翻转完刚刚好是低电平而又改变了指示灯就会导致常亮
        {
            switch(parameter_Data.UP_LED)
            {
            case 1:
                LED_DIS(0x01,RESET);
                break;
            case 2:
                LED_DIS(0x02,RESET);
                break;
            case 3:
                LED_DIS(0x04,RESET);
                break;
            case 4:
                LED_DIS(0x08,RESET);
                break;
            case 5:
                LED_DIS(0x10,RESET);
                break;
            case 6:
                LED_DIS(0x20,RESET);
                break;
            case 7:
                LED_DIS(0x40,RESET);
                break;
            case 8:
                LED_DIS(0x80,RESET);
                break;
            default:
                break;
            }
        }		
        if(Data.DOWN_LED_Flag)
        {
            switch(parameter_Data.DOWN_LED)
            {
            case 1:
                LED_Togg(0x01);
                break;
            case 2:
                LED_Togg(0x02);
                break;
            case 3:
                LED_Togg(0x04);
                break;
            case 4:
                LED_Togg(0x08);
                break;
            case 5:
                LED_Togg(0x10);
                break;
            case 6:
                LED_Togg(0x20);
                break;
            case 7:
                LED_Togg(0x40);
                break;
            case 8:
                LED_Togg(0x80);
                break;
            default:
                break;
            }
        }
        if(!Data.DOWN_LED_Flag)
        {
            switch(parameter_Data.DOWN_LED)
            {
            case 1:
                LED_DIS(0x01,RESET);
                break;
            case 2:
                LED_DIS(0x02,RESET);
                break;
            case 3:
                LED_DIS(0x04,RESET);
                break;
            case 4:
                LED_DIS(0x08,RESET);
                break;
            case 5:
                LED_DIS(0x10,RESET);
                break;
            case 6:
                LED_DIS(0x20,RESET);
                break;
            case 7:
                LED_DIS(0x40,RESET);
                break;
            case 8:
                LED_DIS(0x80,RESET);
                break;
            default:
                break;
            }
        }			
    }
}

//LED
void LED_DIS(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);	//点亮
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);	//熄灭
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

//LED翻转
void LED_Togg(uint8_t num)
{
    HAL_GPIO_TogglePin(GPIOC, (uint16_t)num << 8);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
(LCD显示,补充空格部分)
//页面1显示
void LCD_Dis1(void)
{
    Data.High_Line_Flag = 0;
    sprintf((char *)LCD_Line_BUFF[BT_State], "        Main");	//标题
    sprintf((char *)LCD_Line_BUFF[C1_State], "                    ");
    sprintf((char *)LCD_Line_BUFF[C2_State], "                    ");
    sprintf((char *)LCD_Line_BUFF[C3_State], "    Volt:%.2fV", parameter_Data.Now_V);	//电压


    LCD_State |= 0xFF;
}

//页面2显示
void LCD_Dis2(void)
{
    sprintf((char *)LCD_Line_BUFF[BT_State], "      Setting");	//标题
    sprintf((char *)LCD_Line_BUFF[C1_State], "Max Volt:%.1fV", parameter_Data.V_UP);	//上限电压
    sprintf((char *)LCD_Line_BUFF[C2_State], "Min Volt:%.1fV", parameter_Data.V_DOWN);	//下限电压
    sprintf((char *)LCD_Line_BUFF[C3_State], "Upper:LD%d           ", parameter_Data.UP_LED);	//上限灯
    sprintf((char *)LCD_Line_BUFF[C4_State], "Lower:LD%d           ", parameter_Data.DOWN_LED);	//下限灯

    LCD_State |= 0xFF;
}


//LCD 显示
void LCD_function(void)
{
    uint8_t i;

    for(i = 0; i < 10; i++)
    {
        if(LCD_State & (0x01 << i))
        {
            Arr_KG(LCD_Line_BUFF, i);	//补齐空格
            LCD_State &= (~(1 << i));
            switch(Data.High_Line_Flag)
            {
            case 1:
            {
                LCD_SetBackColor(Blue);
                LCD_DisplayStringLine(Line3, LCD_Line_BUFF[3]);
                LCD_SetBackColor(Black);
                LCD_DisplayStringLine(Line4, LCD_Line_BUFF[4]);
                LCD_DisplayStringLine(Line5, LCD_Line_BUFF[5]);
                LCD_DisplayStringLine(Line6, LCD_Line_BUFF[6]);
                break;
            }
            case 2:
            {
                LCD_SetBackColor(Blue);
                LCD_DisplayStringLine(Line4, LCD_Line_BUFF[4]);
                LCD_SetBackColor(Black);
                LCD_DisplayStringLine(Line3, LCD_Line_BUFF[3]);
                LCD_DisplayStringLine(Line5, LCD_Line_BUFF[5]);
                LCD_DisplayStringLine(Line6, LCD_Line_BUFF[6]);
                break;
            }
            case 3:
            {
                LCD_SetBackColor(Blue);
                LCD_DisplayStringLine(Line5, LCD_Line_BUFF[5]);
                LCD_SetBackColor(Black);
                LCD_DisplayStringLine(Line4, LCD_Line_BUFF[4]);
                LCD_DisplayStringLine(Line3, LCD_Line_BUFF[3]);
                LCD_DisplayStringLine(Line6, LCD_Line_BUFF[6]);
                break;
            }
            case 4:
            {
                LCD_SetBackColor(Blue);
                LCD_DisplayStringLine(Line6, LCD_Line_BUFF[6]);
                LCD_SetBackColor(Black);
                LCD_DisplayStringLine(Line5, LCD_Line_BUFF[5]);
                LCD_DisplayStringLine(Line4, LCD_Line_BUFF[4]);
                LCD_DisplayStringLine(Line3, LCD_Line_BUFF[3]);
                break;
            }
            default:
            {
                LCD_SetBackColor(Black);
                LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
                break;
            }
            }
        }
    }
    if(!Data.One_Flag)	//首次上电刷新一下
    {
        Data.One_Flag = 1;
        LCD_Dis1();	//显示页面1
    }
    if(!Data.MENU_State)	//主页面才显示电压和状态 参数页面不显示
    {
        sprintf((char *)LCD_Line_BUFF[C3_State], "    Volt:%.2fV", parameter_Data.Now_V);	//电压
        sprintf((char *)LCD_Line_BUFF[C4_State], "    Status:");	//状态
        LCD_State |= (0x01 << C3_State);
        LCD_State |= (0x01 << C4_State);
    }
}

//补充空格
void Arr_KG(uint8_t arr[][20], uint8_t NUM)
{
    for(uint8_t j = 0; j < 20; j++)
    {
        if(arr[NUM][j] == '\0')
            arr[NUM][j] = ' ';
    }
}
(定时器部分)
//定时器回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t Key_timer_count;
    static uint16_t LED_count;
    if(htim == &htim6)
    {
        Key_timer_count++;
        if(task_timer.KEY_TIME_COUNT)
            task_timer.KEY_TIME_COUNT--;
        if(task_timer.ADC_TIME_COUNT)
            task_timer.ADC_TIME_COUNT--;

        if(100 == Key_timer_count)
        {
            Key_timer_count = 0;
            KEY_TIMER_COUNT++;
        }
        if(Data.UP_LED_Flag || Data.DOWN_LED_Flag)
        {
            LED_count++;
            if(200 == LED_count)	//200ms
            {
                LED_count = 0;
                Data.LED_SCAN_Flag = 1;
            }
        }
    }
}
main.c
#include "my.h"

int main()
{
    TASK_Init();

    while(1)
    {
        LCD_function();
        Run_function();
        KEY_function();
        GetAdc();
        Judge_function();
        LED_Flicker();
    }
}

实验现象

11届- 可调占空比PWM器

题目要求

通过题目知道需要用到的外设 对应引脚
LCD /
LED PC8~PC15PD2
按键 PB0~PB2PA0
ADC PB15
PWM PA6PA7(TIM16,TIM17)

具体表格化

要求
上电默认自动模式,处于数据页面,PA6默认占空比10%,频率100Hz,PA7默认占空比10%,频率200Hz
按键1(B1):切换页面,数据页面/参数设置页面
按键2(B2):每按一次PA6手动模式下占空比增加10%,加到90%再按一次回到10%
按键3(B3):每按一次PA7手动模式下占空比增加10%,加到90%再按一次回到10%
按键4(B4):手动模式/自动模式切换
按键2,3仅在参数设置页面有效,按键3在数据页面下则是交换PA6和PA7之间的频率
处于数据页面时LED2亮,处于参数设置页面则熄灭;处于自动模式时LED1亮,处于手动模式则熄灭
手动模式下输出占空比只受参数设置影响,跟R37电压无关
自动模式下输出占空比跟R37有关,关系是:Duty = V~R37~ / 3.3V * ARR

其他问题

这一届挺简单,但是我一开始在PWM那卡很久,我一开始是使用PWM输出比较的方式输出PWM,使用示波器查看发现频繁去改变频率会导致频率误差太大,也试了延时1s再改变也是这样,先关再开也不行,然后我就换了普通PWM输出模式,就是使用TIM16和TIM17,测试结果挺稳定的,所以题目如果没说需要可调频率的话能用普通PWM尽量用普通PWM

CubeMX配置

程序编写

main.h
#ifndef __MY_H
#define __MY_H
#include "main.h"
#include "gpio.h"
#include "i2c_hal.h"
#include "adc.h"
#include "tim.h"
#include "lcd.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "stdbool.h"

#define KEY_TIME 20
#define ADC_TIME 50


#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define BT_State 0
#define C1_State 2
#define C2_State 4


typedef struct
{
    uint16_t KEY_TIMER_COUNT;
    uint16_t ADC_TIMER_COUNT;
} TASK_TIMERTypeDef;

typedef struct
{
    bool KEY1_DOWN_Flag;	//按键1
    bool KEY2_DOWN_Flag;	//按键2
    bool KEY3_DOWN_Flag;	//按键3
    bool KEY4_DOWN_Flag;	//按键4
    bool MODE_Flag;	//模式 0:自动 1:手动
    uint8_t MENU_Flag;	//页面1:0 页面2:1
    bool SWITCH_Flag;	//切换频率标志位
} DATA_TypeDef;

typedef struct
{
    uint16_t PA6_duty;	//手动模式下PA6占空比
    uint16_t PA7_duty;	//手动模式下PA7占空比
    uint16_t auto_duty1;	//自动模式下的占空比1
    uint16_t auto_duty2;	//自动模式下的占空比2
    float ADC_value;	//ADC最终值
} PWM_DUTY_TypeDef;


void PWM_OUT(void);
void Flag_Run_fuction(void);
void LCD_Dis1(void);
void LCD_Dis2(void);
void LCD_function(void);
void Get_ADC(void);
uint8_t KEY_Pro(void);
void KEY_function(void);
void TASK_Init(void);
void LED_DIS(uint8_t num, uint8_t swch);

#endif
my.c
(初始化部分)
TASK_TIMERTypeDef task_timer = {0};
PWM_DUTY_TypeDef pwm_duty = {1000, 500, 0, 0};	//初始化为PA6PA7是10占空比
DATA_TypeDef Data = {0};
uint32_t sys_time = 0;

uint8_t KEY_UP, KEY_DOWN, KEY_VALUE;
uint16_t KEY_COUNT;

uint32_t ADC_BUFF[10];	//采集ADC10次结果为一次


uint16_t LCD_State = 0xFFFF;

uint8_t LCD_Line_BUFF[10][20] =
{
    "                   1",	//标题 0
    "                    ",
    "                    ",	//参数 2
    "                    ",
    "                    ",	//参数 4
    "                    ",
    "                    ",
    "                    ",
    "                    ",
    "                    "
};


//初始化
void TASK_Init(void)
{
    LCD_Init();
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    LED_DIS(0xFF, RESET);
    HAL_TIM_Base_Start_IT(&htim6);
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);	//ADC校准
    HAL_ADC_Start_DMA(&hadc2, (uint32_t *)ADC_BUFF, 10);	//开始转换
    HAL_TIM_PWM_Start(&htim16, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim17, TIM_CHANNEL_1);
    LED_DIS(0x03, SET);	//默认是自动模式LED1点亮
    LCD_Dis1();
}
(LCD,ADC滤波部分)
//LCD显示
void LCD_function(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        if(LCD_State & (1 << i))
        {
            LCD_State &= (~(0x01 << i));
            LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
        }
    }
}

void LCD_Dis1(void)
{
    sprintf((char *)LCD_Line_BUFF[BT_State], "       Data         ");
    sprintf((char *)LCD_Line_BUFF[C1_State], "     V:%.2fv", pwm_duty.ADC_value);
    if(Data.MODE_Flag)	//手动
    {
        sprintf((char *)LCD_Line_BUFF[C2_State], "     Mode:MAMU      ");
    }
    else	//自动
    {
        sprintf((char *)LCD_Line_BUFF[C2_State], "     Mode:AUTO      ");
    }
    LCD_State |= 0xFF;
}

void LCD_Dis2(void)
{
    sprintf((char *)LCD_Line_BUFF[BT_State], "       Para         ");
    sprintf((char *)LCD_Line_BUFF[C1_State], "     PA6:%d%%        ", pwm_duty.PA6_duty / 100);
    sprintf((char *)LCD_Line_BUFF[C2_State], "     PA7:%d%%        ", pwm_duty.PA7_duty / 50);
    LCD_State |= 0xFF;
}

//ADC获取+滤波
void Get_ADC(void)
{
    uint32_t temp;
    uint8_t count = 0;
    uint32_t adc;
    if(!task_timer.ADC_TIMER_COUNT)
    {
        for(uint8_t i = 0; i < 10 - 1; i++)
        {
            count = 0;
            for(uint8_t j = 0; j < 10 - 1 - i; j++)
            {
                if(ADC_BUFF[j] > ADC_BUFF[j + 1])	//升序
                {
                    temp = ADC_BUFF[j];
                    ADC_BUFF[j] = ADC_BUFF[j + 1];
                    ADC_BUFF[j + 1] = temp;
                    count = 1;
                }
                if(0 == count)
                    break;
            }
        }
        for(uint16_t k = 1; k < 10 - 1; k++)
        {
            adc += ADC_BUFF[k];
        }
        pwm_duty.ADC_value = (float)(adc / 8) * 3.3f / 4096;
        pwm_duty.auto_duty1 =  pwm_duty.ADC_value / 3.3f * 10000;	//自动模式下占空比TIM16 【因为ARR是10000】
        pwm_duty.auto_duty2 = pwm_duty.ADC_value / 3.3f * 5000;	//自动模式下占空比TIM17【因为ARR是5000】
    }
    if(!Data.MENU_Flag)
    {
        LCD_Dis1();
    }
}
(按键部分)
uint8_t KEY_Pro(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }

    return 0;
}

void KEY_function(void)
{
    static uint8_t KEY_OldVALUE;
    if(!task_timer.KEY_TIMER_COUNT)
    {
        task_timer.KEY_TIMER_COUNT = KEY_TIME;
        KEY_VALUE = KEY_Pro();

        KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ KEY_OldVALUE);
        KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ KEY_OldVALUE);
        KEY_OldVALUE = KEY_VALUE;

        if(KEY_DOWN)
            KEY_COUNT = 0;

        if(KEY_COUNT < 10)
        {
            switch(KEY_UP)
            {
            case 1:
            {
                Data.KEY1_DOWN_Flag = 1;
                break;
            }
            case 2:
            {
                if(!Data.MENU_Flag)	//数据页面失效
                {
                    break;
                }
                Data.KEY2_DOWN_Flag = 1;
                break;
            }
            case 3:
            {

                if(!Data.MENU_Flag)	//数据页面互换频率
                {
                    uint32_t temp;
                    temp = TIM16->ARR;
                    TIM16->ARR = TIM17->ARR;
                    TIM17->ARR = temp;
                    Data.SWITCH_Flag = 1;
                    break;
                }
                else	//参数页面
                {
                    Data.KEY3_DOWN_Flag = 1;
                    break;
                }
            }
            case 4:
            {
                Data.KEY4_DOWN_Flag = 1;
                break;
            }
            default:
                break;
            }
        }
    }
}
(标志位执行部分)
//标志位执行
void Flag_Run_fuction(void)
{
    if(Data.KEY1_DOWN_Flag)	//按键1切换页面
    {
        Data.KEY1_DOWN_Flag = 0;
        switch(Data.MENU_Flag)
        {
        case 0:
        {
            Data.MENU_Flag = 1;
            LED_DIS(0x02, RESET);
            LCD_Dis2();
            break;
        }
        case 1:
        {
            Data.MENU_Flag = 0;
            LED_DIS(0x02, SET);
            LCD_Dis1();
            break;
        }
        }
    }
    if(Data.KEY2_DOWN_Flag)	//按键2
    {
        Data.KEY2_DOWN_Flag = 0;
        pwm_duty.PA6_duty += 1000;	//占空比加10%
        if(pwm_duty.PA6_duty > 9000)
        {
            pwm_duty.PA6_duty = 1000;
        }
        TIM16->CCR1 = pwm_duty.PA6_duty;
        sprintf((char *)LCD_Line_BUFF[C1_State], "     PA6:%d%%        ", pwm_duty.PA6_duty / 100);
        LCD_State |= (0x01 << C1_State);
    }
    if(Data.KEY3_DOWN_Flag)	//按键3
    {
        Data.KEY3_DOWN_Flag = 0;
        pwm_duty.PA7_duty += 500;	//占空比加10%
        if(pwm_duty.PA7_duty >= 5000)
        {
            pwm_duty.PA7_duty = 500;
        }
        TIM17->CCR1 = pwm_duty.PA7_duty;
        sprintf((char *)LCD_Line_BUFF[C2_State], "     PA7:%d%%        ", pwm_duty.PA7_duty / 50);
        LCD_State |= (0x01 << C2_State);
    }
    if(Data.KEY4_DOWN_Flag)	//按键4切换手动自动
    {
        Data.KEY4_DOWN_Flag = 0;
        Data.MODE_Flag = !Data.MODE_Flag;	//取反【自动--点亮 手动--熄灭】
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    }
}
(PWM改变部分)
void PWM_OUT(void)
{
    if(1 == Data.MODE_Flag)	//手动
    {
        if(Data.SWITCH_Flag)	//交换频率
        {
            TIM16->CCR1 = pwm_duty.PA7_duty;
            TIM17->CCR1 = pwm_duty.PA6_duty;
        }
        else
        {
            TIM16->CCR1 = pwm_duty.PA6_duty;
            TIM17->CCR1 = pwm_duty.PA7_duty;
        }


    }
    if(0 == Data.MODE_Flag)	//自动	占空比 = ADC电压值*3.3V
    {
        if(Data.SWITCH_Flag)	//如果切换了
        {
            if((pwm_duty.ADC_value > 0) && (pwm_duty.ADC_value < 3.3f))
            {
                TIM16->CCR1 = pwm_duty.auto_duty2;
                TIM17->CCR1 = pwm_duty.auto_duty1;
            }
            else if(pwm_duty.ADC_value >= 3.3f)
            {
                TIM16->CCR1 = 5000;	//输出高电平
                TIM17->CCR1 = 10000;	//输出高电平
            }
            else if(pwm_duty.ADC_value <= 0)
            {
                TIM16->CCR1 = 0;	//输出低电平
                TIM17->CCR1 = 0;	//输出低电平
            }
        }
        else
        {
            if((pwm_duty.ADC_value > 0) && (pwm_duty.ADC_value < 3.3f))
            {
                TIM16->CCR1 = pwm_duty.auto_duty1;
                TIM17->CCR1 = pwm_duty.auto_duty2;
            }
            else if(pwm_duty.ADC_value >= 3.3f)
            {
                TIM16->CCR1 = 10000;	//输出高电平
                TIM17->CCR1 = 5000;	//输出高电平
            }
            else if(pwm_duty.ADC_value <= 0)
            {
                TIM16->CCR1 = 0;	//输出低电平
                TIM17->CCR1 = 0;	//输出低电平
            }
        }


    }
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t time_100ms;
    static uint16_t time_150ms;
    if(htim == &htim6)
    {
        sys_time++;
        time_100ms++;
        time_150ms++;
        if(task_timer.KEY_TIMER_COUNT)
            task_timer.KEY_TIMER_COUNT--;
        if(task_timer.ADC_TIMER_COUNT)
            task_timer.ADC_TIMER_COUNT--;
        if(100 == time_100ms)
        {
            KEY_COUNT++;
        }
        if(150 == time_150ms)	//150ms扫描一次
        {
            time_150ms = 0;
            PWM_OUT();
        }
    }

}
(LED部分)
void LED_DIS(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
main.c
int main()
{
    TASK_Init();

    while(1)
    {
        LCD_function();
        Get_ADC();
        KEY_function();
        Flag_Run_fuction();
    }
}

实验现象

12届-停车计费系统

题目要求

通过题目知道需要用到的外设 对应引脚
LCD /
LED PC8~PC15PD2
按键 PB0~PB2PA0
USART1 PA9PA10
PWM PA7(TIM3)

备注:这个后面费率计算不会,故这届不搞了

13届-密码锁

题目要求

通过题目知道需要用到的外设 对应引脚
LCD /
LED PC8~PC15PD2
按键 PB0~PB2PA0
USART1 PA9PA10
PWM PA1

CubeMX配置

程序编写

my.h
#ifndef __MY_H
#define __MY_H

#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "lcd.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "stdbool.h"


#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

#define BT_STATE 1
#define C1_STATE 3
#define C2_STATE 4
#define C3_STATE 5



typedef struct
{
    bool KEY1_DOWN_Flag;	//1
    bool KEY2_DOWN_Flag;	//2
    bool KEY3_DOWN_Flag;	//3
    bool KEY4_DOWN_Flag;	//4
    uint8_t mm_1;	//密码第一位
    uint8_t mm_2;	//密码第一位
    uint8_t mm_3;	//密码第一位
    uint16_t freq;	//频率
    float duty;	//占空比
    bool MENU_Flag;	//默认0是密码页面 1是输出页面
    bool Run_Count_5s;	//5秒开始计数标志位
    uint16_t Run_Timer_Count;	//计数值
    bool LED2_Count_5s;	//LED2开始计数标志位
    uint16_t LED2_Timer_Count;	//计数值


} DATA_TypeDef;

void LCD_Dis2(void);
void LCD_Dis1(void);
void TASK_Init(void);
void LED_DIS(uint8_t num, uint8_t swch);
void KEY_function(void);
uint8_t KEY_Proc(void);
void LCD_function(void);
void Flag_Run_function(void);
void USART1_function(void);
#endif
my.c
(初始化部分)
#include "my.h"

uint8_t Password[3] = {1, 2, 3};	//默认密码
uint8_t Password_Error_Count = 0;	//密码错误计数
uint8_t num1 = 0, num2 = 0, num3 = 0;	//按键按下默认数字
uint8_t One_Open_Flag = 0;	//第一次上电
DATA_TypeDef Data = {0};
uint16_t LCD_State = 0xFFFF;
uint8_t LCD_Line_BUFF[10][20] =
{
    "                   ",
    "                   ",	//页面1 psc 【1】
    "                   ",
    "                   ",	//页面1 C1【3】
    "                   ",	//页面1 C2 页面2【4】
    "                   ",	//页面1 C3 页面2【5】
    "                   ",
    "                   ",
    "                   ",
    "                   "
};
uint8_t KEY_UP, KEY_DOWN, KEY_VALUE;
uint16_t KEY_COUNT;

void TASK_Init(void)
{
	LCD_Init();
	LCD_Clear(Black);
	LCD_SetBackColor(Black);
	LCD_SetTextColor(White);
	LED_DIS(0xFF,RESET);
	HAL_TIM_Base_Start_IT(&htim6);
	TIM2->CCR2 = 500;
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);	//默认1000Hz
	LCD_Dis1();
}
(LCD部分)
void LCD_function(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        if(LCD_State & (1 << i))
        {
            LCD_State &= (~(0x01 << i));
            LCD_DisplayStringLine(i * 24, LCD_Line_BUFF[i]);
        }
    }
}

void LCD_Dis1(void)
{
    Data.mm_1 = '@';
    Data.mm_2 = '@';
    Data.mm_3 = '@';
    num1 = 0;	//清0
    num2 = 0;
    num3 = 0;
    sprintf((char *)LCD_Line_BUFF[BT_STATE], "       PSD          ");
    sprintf((char *)LCD_Line_BUFF[C1_STATE], "    B1:%c            ", Data.mm_1);
    sprintf((char *)LCD_Line_BUFF[C2_STATE], "    B2:%c            ", Data.mm_2);
    sprintf((char *)LCD_Line_BUFF[C3_STATE], "    B3:%c            ", Data.mm_3);

    LCD_State |= 0xFF;
}

void LCD_Dis2(void)
{
    sprintf((char *)LCD_Line_BUFF[BT_STATE], "       STA          ");
    sprintf((char *)LCD_Line_BUFF[C1_STATE], "    F:%dHz        ", Data.freq);
    sprintf((char *)LCD_Line_BUFF[C2_STATE], "    D:%.0f%%           ", Data.duty);
    sprintf((char *)LCD_Line_BUFF[C3_STATE], "                    ");

    LCD_State |= 0xFF;
}
(按键部分)
uint8_t KEY_Proc(void)
{
    if((!KEY1) || (!KEY2) || (!KEY3) || (!KEY4))
    {
        if(!KEY1)
            return 1;
        if(!KEY2)
            return 2;
        if(!KEY3)
            return 3;
        if(!KEY4)
            return 4;
    }
    return 0;
}



void KEY_function(void)
{
    static uint8_t Old_Value;
    KEY_VALUE = KEY_Proc();
    KEY_UP = ~KEY_VALUE & (KEY_VALUE ^ Old_Value);
    KEY_DOWN = KEY_VALUE & (KEY_VALUE ^ Old_Value);
    Old_Value = KEY_VALUE;

    if(KEY_DOWN)
    {
        KEY_COUNT = 0;
    }
    if(KEY_COUNT < 10)
    {
        switch(KEY_UP)
        {
        case 1:
        {
            if(Data.MENU_Flag || Data.LED2_Count_5s)	//输出页面失效
            {
                break;
            }
            Data.KEY1_DOWN_Flag = 1;
            break;
        }
        case 2:
        {
            if(Data.MENU_Flag || Data.LED2_Count_5s)	//输出页面失效
            {
                break;
            }
            Data.KEY2_DOWN_Flag = 1;
            break;
        }
        case 3:
        {
            if(Data.MENU_Flag || Data.LED2_Count_5s)	//输出页面失效
            {
                break;
            }
            Data.KEY3_DOWN_Flag = 1;
            break;
        }
        case 4:
        {
            if(Data.MENU_Flag || Data.LED2_Count_5s)	//输出页面失效
            {
                break;
            }
            Data.KEY4_DOWN_Flag = 1;
            break;
        }
        default:
            break;
        }
    }
}
(标志位实现功能部分)
void Flag_Run_function(void)
{
    if(Data.KEY1_DOWN_Flag)
    {
        Data.KEY1_DOWN_Flag = 0;
        Data.mm_1 = num1;
        sprintf((char *)LCD_Line_BUFF[C1_STATE], "    B1:%c            ", Data.mm_1 + '0');	//数字转字符
        LCD_State |= (0x01 << C1_STATE);
        num1++;
        if(num1 > 9)
        {
            num1 = 0;
        }
    }
    if(Data.KEY2_DOWN_Flag)
    {
        Data.KEY2_DOWN_Flag = 0;
        Data.mm_2 = num2;
        sprintf((char *)LCD_Line_BUFF[C2_STATE], "    B2:%c            ", Data.mm_2 + '0');	//数字转字符
        LCD_State |= (0x01 << C2_STATE);
        num2++;
        if(num2 > 9)
        {
            num2 = 0;
        }
    }
    if(Data.KEY3_DOWN_Flag)
    {
        Data.KEY3_DOWN_Flag = 0;
        Data.mm_3 = num3;
        sprintf((char *)LCD_Line_BUFF[C3_STATE], "    B3:%c            ", Data.mm_3 + '0');	//数字转字符
        LCD_State |= (0x01 << C3_STATE);
        num3++;
        if(num3 > 9)
        {
            num3 = 0;
        }
    }
    if(Data.KEY4_DOWN_Flag)
    {
        Data.KEY4_DOWN_Flag = 0;
        if(!Data.MENU_Flag)	//在密码页面
        {
            if((Data.mm_1 == Password[0]) && (Data.mm_2 == Password[1]) && (Data.mm_3 == Password[2]))	//密码正确
            {
                //输出2KHz方波10%占空比跳转输出状态页面
                TIM2->ARR = 499;
                TIM2->CCR2 = 50;
                Data.freq = 1000000 / (TIM2->ARR+1);
                Data.duty = (float)TIM2->CCR2 / (TIM2->ARR+1) * 100;
                Data.MENU_Flag = 1;
                Data.Run_Count_5s = 1;
                LED_DIS(0x01, SET);	//点亮
                LCD_Dis2();
            }
            else
            {
                //密码错误重置为@ 计数+1
                Password_Error_Count++;
                LCD_Dis1();
            }
        }
    }
    if(Password_Error_Count >= 3)	//密码错误3次及以上
    {
        Password_Error_Count = 0;	//清0
        Data.LED2_Count_5s = 1;
    }
}
(定时器部分)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t timer_100ms;
    static uint8_t timer_20ms;
    static uint16_t timer_5s;
    static uint16_t timer_LED_5s;

    if(htim == &htim6)
    {
        timer_20ms++;
        timer_100ms++;

        if(20 == timer_20ms)
        {
            timer_20ms = 0;
            KEY_function();
        }

        if(100 == timer_100ms)
        {
            timer_100ms = 0;
            KEY_COUNT++;
        }
        if(Data.Run_Count_5s)
        {
            timer_5s++;
            if(5000 == timer_5s)
            {
                timer_5s = 0;
                Data.Run_Count_5s = 0;
                LED_DIS(0x01, RESET);	//熄灭
                TIM2->ARR = 999;	//恢复1KHz 50%占空比
                TIM2->CCR2 = 500;
                Data.MENU_Flag = 0;
                LCD_Dis1();	//回到密码页面
            }
        }
        if(Data.LED2_Count_5s)
        {
            timer_LED_5s++;

            if(0 == (timer_LED_5s % 100))	//间隔100ms闪烁
            {
                HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9);
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
            }
            if(5000 == timer_LED_5s)
            {
                timer_LED_5s = 0;
                Data.LED2_Count_5s = 0;
                LED_DIS(0x02, RESET);	//熄灭
            }
        }

    }
}
(USART处理数据部分)
void USART1_function(void)
{

    if(RX_Over_Flag)
    {
        RX_Over_Flag = 0;
        One_Open_Flag++;
        if(One_Open_Flag >= 200)
        {
            One_Open_Flag = 2;
        }
        if(One_Open_Flag > 1)
        {
            if(7 == RX_LEN)	//不包含换行
            {
                //因为接收数组是字符需要转整型
                //发送格式:xxx-yyy
                if((Password[0] == (RX_BUFF[0] - '0')) &&
                        (Password[1] == (RX_BUFF[1] - '0')) &&
                        (Password[2] == (RX_BUFF[2] - '0')) && ('-' == RX_BUFF[3]))
                {
                    //判断是否在0~9之间
                    if((RX_BUFF[4] >= '0' && RX_BUFF[4] <= '9') &&
                            (RX_BUFF[5] >= '0' && RX_BUFF[5] <= '9') &&
                            (RX_BUFF[6] >= '0' && RX_BUFF[6] <= '9'))
                    {
                        Password[0] = RX_BUFF[4] - '0';
                        Password[1] = RX_BUFF[5] - '0';
                        Password[2] = RX_BUFF[6] - '0';
                        HAL_UART_Transmit(&huart1, (uint8_t *)"修改成功!\r\n", sizeof("修改成功\r\n"), 300);
                    }
                    else
                    {
                        HAL_UART_Transmit(&huart1, (uint8_t *)"新密码含有非法字符,修改失败!\r\n", sizeof("新密码含有非法字符,修改失败!\r\n"), 300);
                    }
                }
                else
                {
                    HAL_UART_Transmit(&huart1, (uint8_t *)"密码错误,修改失败!\r\n", sizeof("密码错误,修改失败!\r\n"), 300);
                }
            }
            else
            {
                HAL_UART_Transmit(&huart1, (uint8_t *)"长度过长,修改失败!\r\n", sizeof("长度过长,修改失败!\r\n"), 300);
            }
        }
        memset((uint8_t *)RX_BUFF, 0, sizeof(RX_BUFF));
        HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);	//重新打开接收
    }
}
(LED部分)
void LED_DIS(uint8_t num, uint8_t swch)
{
    if(SET == swch)
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_RESET);
    }
    else
    {
        HAL_GPIO_WritePin(GPIOC, (uint16_t)num << 8, GPIO_PIN_SET);
    }
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
main.c
int main(void)
{
    TASK_Init();

    while(1)
    {
        Flag_Run_function();
        LCD_function();
        USART1_function();
    }
}
usart.h(系统生成)
#include "my.h"

#define RX_MAX_LEN 50

extern uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
extern uint8_t RX_LEN;	//接收长度
extern uint8_t RX_Over_Flag;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
usart.c(系统生成)
uint8_t RX_BUFF[RX_MAX_LEN];	//接收数组
uint8_t RX_LEN;	//接收长度
uint8_t RX_Over_Flag = 0;

void MX_USART1_UART_Init(void)
{
    ....
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);	//串口中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//空闲中断
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RX_BUFF, RX_MAX_LEN);
}

//从 stm32g4xx_it.c剪切过来
void USART1_IRQHandler(void)
{
    if(SET == __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);
        HAL_UART_DMAStop(&huart1);
        RX_LEN = RX_MAX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);	//接收长度
        RX_Over_Flag = 1;
    }

    HAL_UART_IRQHandler(&huart1);
}

实验现象

客观题

在Github工程文件

决赛程序题