蓝桥杯嵌入式CT117E硬件开发平台经验分享11 | 第九届蓝桥杯国赛题

基于 基于 CT117E 嵌入式竞赛板 嵌入式竞赛板 的 “电子秤”程序设计与调试

赛题硬件框图

蓝桥杯嵌入式CT117E硬件开发平台经验分享11 | 第九届蓝桥杯国赛题_第1张图片

由于赛题细节多,赛题PDF要求书放入了附件,链接后续加入。本文主要讲述本届题目的困难点:扩展板的AD按键,串口输出。

1. 由于本届赛题用到了扩展版的AD按键,只要读取AD的值,并根据不同按键AD值就可以判断出是哪个按键按下,但本届的难点在于既是AD按键,又要求按键长短按有不同的功能,所以相对来说就需要设计按键的思路了。本文用到的方法其实就是在原中断按键扫描的基础上进行替换设计的。无非是把按键扫描函数换成了AD值的判断。实现方法如下:

#ifndef __ADC_KEY_H
#define __ADC_KEY_H

#include "stm32f10x.h"
#include "bsp_adc.h"             //需要依赖adc配置
#include "bsp_GeneralTim.h"     //需要依赖时钟提供时许
#include "Def_config.h"        // 需要依赖宏定义

typedef struct{		
	u8 KEY_VALUE;   //使用数据后及时清除到0xff !!!
	JUDGE_ENUM IS_Key_ShortPress;  //使用数据后及时清除到FAUSE状态!!!
	JUDGE_ENUM IS_Key_LongPress;  //判断长按否? 不需要认为清除
	JUDGE_ENUM IS_Key_LongFresh;  //长按刷新,长按多久按键计算快速累加或累减一次使用数据后及时清除到FAUSE状态!!!!
	JUDGE_ENUM IS_Key_Touch;   //按键是否按下
}KEYSCANF_TypeDef;
extern KEYSCANF_TypeDef KEYSCANF_Structure;

/********************当前设置的按键扫描中断为 5MS************/
#define KEY_LONGSTATUS_Time  800000/GENERAL_TIM_SetUs  // 多久算长按
#define KEY_EliShaking_Time  10000/GENERAL_TIM_SetUs    // 按键消抖值
#define Key_LongTimeSet 100000/GENERAL_TIM_SetUs  //按键长按后多久更新一次 比如长按后加一,本设置为多久加一


extern u8 Key_Scan(void);
extern void Key_Interrup(void);

#endif 



#include "Adc_Key.h"
/*******************************************************************************  
* 文件名称:扩展版AD按键函数  PA5  - ADC1  CH5
* 作者:    PANDAS
* 单位:    广西师范大学电子工程学院电子与通信工程研究生
* 联系方式:[email protected]
* 
* 程序说明:    按键松开 -- 3.3V   |4095
*   S1     S2     S3     S4
*   0V    0.43V  0.93V  1.42V      |  0     540     1157     1764
*   S5     S6     S7     S8        
*  1.92V  2.33V  2.84V  3.22V      | 2380   2890    3518     3998   
* 注意:#include "bsp_adc.h" //需要依赖adc配置
* 日期版本:2020-10-17  V1.0
*******************************************************************************/
extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL]; 
u8 Key_Scan(void)
{
	if(ADC_ConvertedValue[1] < 100)  
	{
		return 1;
	}			
	else if(ADC_ConvertedValue[1] < 800)
	{
		return 2;
	}
	else if(ADC_ConvertedValue[1] < 1500)
	{
		return 3;
	}
	else if(ADC_ConvertedValue[1] < 2000)
	{
		return 4;
	}
	else if(ADC_ConvertedValue[1] < 2500)
	{
		return 5;
	}
	else if(ADC_ConvertedValue[1] < 3200)
	{
		return 6;
	}
	else if(ADC_ConvertedValue[1] < 3700)
	{
		return 7;
	}
	else if(ADC_ConvertedValue[1] < 4020)
	{
		return 8;
	}
	else{
		return 0xff;
	}
}
/**
  * @说明     按键状态机函数
  * @参数     none
  * @返回值   None
  * @注意   1.按键状态机只做出 状态, 但不消除状态
            2.KEYSCANF_Structure.KEY_VALUE ,KEYSCANF_Structure.IS_Key_ShortPress 被使
			用后需要人为复位0xff,FALSE,这样才能保证每一次的状态都能被处理
            3.Key_Interrup()函数需要放入定时器中断,且中断时间小于10MS,最好为10的公约数:如1,2,5MS
例程:
@1: 按键初始化放入main.c
@2: 中断放入函数
	extern void Key_Interrup(void);
	void TIM4_IRQHandler(void)
	{
		Key_Interrup();
	}
@3:按键处理函数
	void KEY_Proc(void)
	{
		static u8 i = 0;
		if(KEYSCANF_Structure.KEY_VALUE != 0xff)  // There are remaining parking spaces
		{				
			switch(KEYSCANF_Structure.KEY_VALUE)
			{
				case 1:
					if(KEYSCANF_Structure.IS_Key_ShortPress == TRUE)
					{
						KEYSCANF_Structure.IS_Key_ShortPress = FALSE;   //!!!手动清除短按标志位
						短按处理自制函数();
					}
					break;
				case 2:	
					if(KEYSCANF_Structure.IS_Key_LongPress == TRUE)
					{
						长按处理自制函数();
					}
					break;
				case 3:	
					if(KEYSCANF_Structure.IS_Key_LongPress == TRUE)
					{
						if(KEYSCANF_Structure.IS_Key_LongFresh == TRUE)
						{
							KEYSCANF_Structure.IS_Key_LongFresh = FALSE;   //手动清除快速按键刷新标志位
							长按处理快速累加/累减自制函数();
						}
					}
					break;
			}
			KEYSCANF_Structure.KEY_VALUE = 0xff;  //在此清除的目的在于 不至于在状态机中有按键值,而在按键处理函数检测时已经被状态机刷新销毁,上面switch判断后才能清除!
		}
	}

  * @包含出处  #include "bsp_key_statemachine.h"
  */
u8 keyCheck = 0;
uint8_t   keyState = 0;
uint16_t  keyPrev = 0xff;       //上一次的键值
u32 keyLongCheck = 0;
u16 Key_LongTime = 0;
u8 keyCountTime = 0;
KEYSCANF_TypeDef KEYSCANF_Structure = {0xff,FALSE,FALSE,FALSE,FALSE};   
void Key_Interrup(void)      
{
	uint8_t keyPress = 0xff;
	keyCountTime ++;
	if(keyCountTime >= KEY_EliShaking_Time) //消抖完成
	{
		keyCountTime = 0;
		keyCheck = 1;
	}
	if(keyCheck == 1)
	{
		keyCheck = 0;
		keyPress = Key_Scan();
		switch(keyState)
		{
			case 0://按键未按下态
				if(keyPress != 0xff)//表示有按键按下
				{
					keyPrev = keyPress; //记录当前按键状态
					keyState = 1;
				}
				else
				{
					keyState = 0;
				}
			break;
			case 1://表示有按键按下,判断当前值和上一次的值是否一样,若不一样则为抖动!
				if(keyPress == keyPrev)//不是抖动
				{
					keyState = 2;
				}else{
					keyState = 0; //是抖动!返回上一层
					keyPrev = 0xff;
				}	 
				break;
			case 2:
				if(keyPress != keyPrev)//表示按键已松开,只有长按状态被清除,长按计时结束,短按状态不清除,等待用户使用并清除
				{		
					if(keyLongCheck < KEY_LONGSTATUS_Time)   //短按
					{
						KEYSCANF_Structure.IS_Key_ShortPress = TRUE;						
						KEYSCANF_Structure.KEY_VALUE = keyPrev;
					}
					KEYSCANF_Structure.IS_Key_LongPress = FALSE;
					keyPrev = 0xff;
					keyState = 0;  // 按键结束返回初始状态机
					keyLongCheck = 0; //长按计时清0
					Key_LongTime = 0;
				}else{					
					keyLongCheck++;
					if(keyLongCheck >= KEY_LONGSTATUS_Time)//按下时间超过 长按时间  ,可以直接累加,因为超过长按时间之后每隔扫描时间(10MS)就可以返回一次按键值,而且按键值用过后会清除
					{
						keyLongCheck = KEY_LONGSTATUS_Time + 1;
	
						KEYSCANF_Structure.KEY_VALUE = keyPrev;       //返回值标记哪个按键  ,在调用后清除,所以可以直接做到累加功能
						KEYSCANF_Structure.IS_Key_ShortPress = FALSE;
						KEYSCANF_Structure.IS_Key_LongPress = TRUE;						
					}
				}
				break;
			default : break;
		}
	}
	if(KEYSCANF_Structure.IS_Key_LongPress == TRUE)   //按键长按 刷新,进行长按快速累减或累加使用,人为设定多久累加一次
	{
		Key_LongTime ++;
		if(Key_LongTime >= Key_LongTimeSet)
		{
			Key_LongTime = 0;
			KEYSCANF_Structure.IS_Key_LongFresh = TRUE;
		}
	}
}


2. 按键是AD采集,用到了ADC的DMA读取。

#ifndef __ADC_H
#define	__ADC_H


#include "stm32f10x.h"
#include "Def_config.h"

// ADC 编号选择
// 可以是 ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
#define    ADCx                          ADC1
#define    ADC_CLK                       RCC_APB2Periph_ADC1

// ADC GPIO宏定义
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
#define    ADC_GPIO_CLK                  RCC_APB2Periph_GPIOA  
#define    ADC_PORT                      GPIOA
#define    ADC_PIN                       (GPIO_Pin_4 | GPIO_Pin_5)

// ADC 中断相关宏定义
#define    ADC_IRQ                       ADC1_2_IRQn
#define    ADC_IRQHandler                ADC1_2_IRQHandler

#define ADC1_DR_Address    ((uint32_t)0x4001244C)

// 转换通道个数
#define    NOFCHANEL	 2
// ADC1转换的电压值通过MDA方式传到SRAM
extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL]; 

extern void ADCx_Init(void);
extern void ADC_Read(u8 Channel_x);

#endif /* __ADC_H */


#include "bsp_adc.h"
/*******************************************************************************  
* 文件名称:ADC采集驱动
* 作者:    PANDAS
* 单位:    广西师范大学电子工程学院电子与通信工程研究生
* 联系方式:[email protected]
* 实验目的:1.
*           2.
* 程序说明:1.开发板R37电位器: PB0-ADC1 channel 8   ADC1 12位分辨率 0xfff:2^12  = 4096
*           2.开发板RP5电位器: PA4-ADC1 channel 4 
			3.开发板RP6电位器: PA5-ADC1 channel 5 
* 日期版本:2020-10-17  V1.0
*******************************************************************************/
__IO uint16_t ADC_ConvertedValue[NOFCHANEL]={0,0};  
/**
  * @brief  ADC GPIO 初始化
  * @param  无
  * @retval 无
  */
static void ADCx_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 打开 ADC IO端口时钟
	RCC_APB2PeriphClockCmd( ADC_GPIO_CLK, ENABLE );
	
	// 配置 ADC IO 引脚模式
	// 必须为模拟输入
	GPIO_InitStructure.GPIO_Pin = ADC_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	
	// 初始化 ADC IO
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);				
}
static void ADCx_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	// 打开DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	DMA_DeInit(DMA1_Channel1);					// 复位DMA控制器  ADC1 对应 DMA1通道1,ADC3对应DMA2通道5,ADC2没有DMA功能
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;	// 复位DMA控制器
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;    // 存储器地址 
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   // 数据源来自外设
	DMA_InitStructure.DMA_BufferSize = NOFCHANEL;   // 缓冲区大小,应该等于数据目的地的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // 外设寄存器只有一个,地址不用递增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 存储器地址递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小为半字,即两个字节
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;  // 内存数据大小也为半字,跟外设数据大小相同
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  // 循环传输模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;  // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   // 禁止存储器到存储器模式,因为是从外设到存储器
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);   // 初始化DMA
	/* Enable DMA1 channel1 */
	DMA_Cmd(DMA1_Channel1, ENABLE);		// 使能 DMA 通道
}
/**
  * @brief  配置ADC工作模式
  * @param  无
  * @retval 无
  */
static void ADCx_Mode_Config(void)
{
	ADC_InitTypeDef ADC_InitStructure;	

	// 打开ADC时钟
	RCC_APB2PeriphClockCmd( ADC_CLK, ENABLE );
	
	// ADC 模式配置
	// 只使用一个ADC,属于独立模式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	
	// 禁止扫描模式,多通道才要,单通道不需要
	ADC_InitStructure.ADC_ScanConvMode = ENABLE ; 

	// 连续转换模式
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 

	// 不用外部触发转换,软件开启即可
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;

	// 转换结果右对齐
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	
	// 转换通道x个
	ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;	
		
	// 初始化ADC
	ADC_Init(ADCx, &ADC_InitStructure);
	
	// 配置ADC时钟为PCLK2的8分频,72/8 即9MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
	
	// 配置 ADC 通道转换顺序和采样时间
	ADC_RegularChannelConfig(ADCx, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADCx, ADC_Channel_5, 2, ADC_SampleTime_55Cycles5);
//	ADC_AutoInjectedConvCmd(ADCx,ENABLE);
	
	// 使能ADC DMA 请求
	ADC_DMACmd(ADCx, ENABLE);
	// 开启ADC ,并开始转换
	ADC_Cmd(ADCx, ENABLE);
	
	// 初始化ADC 校准寄存器  
	ADC_ResetCalibration(ADCx);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADCx));
	
	// ADC开始校准
	ADC_StartCalibration(ADCx);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADCx));
	
	// 由于没有采用外部触发,所以使用软件触发ADC转换 
	ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
/**
  * @brief  ADC初始化
  * @param  无
  * @retval 无
  * @attention   别忘了main.c调用初始化
  */
void ADCx_Init(void)
{
	ADCx_GPIO_Config();
	ADCx_DMA_Config();
	ADCx_Mode_Config();
}
/*********************************************END OF FILE**********************/

3. 串口调用,其实就是接收数据、判断、发送的过程。

#ifndef __USART2_H
#define __USART2_H

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "Def_config.h"

#define USART2_RecTab_Max 50
typedef struct{		
	char USART2_RecTab[USART2_RecTab_Max];
	u8 USART2_RecNum ;
	JUDGE_ENUM IS_USART2_ReceiveStart;
	JUDGE_ENUM IS_USART2_RecSucess;
}USART2REC_TypeDef;
extern USART2REC_TypeDef USART2REC_Structure; //USART2 接收定义。在别的函数调用需要extern 

extern void USART2_Config(u16 USATR2_Boads);
extern void USART2_SendString(char *str);

#endif


#include "usart2.h"
#include <stdio.h>   //需要用此头文件,若Main函数调用printf,则也需要添加
#include <string.h>
/**
  * @说明     STATE_ENUM类型需要包含#include "Def_config.h"
***/
#include "Def_config.h"
/*******************************************************************************  
* 文件名称:串口二驱动函数
* 作者:    PANDAS
* 单位:    广西师范大学电子工程学院电子与通信工程研究生
* 联系方式:[email protected]
* 实验目的:1.
*           2.
* 程序说明:1.蓝桥杯嵌入式开发板调试接口
*           2.
* 日期版本:2020-10-17  V1.0
*******************************************************************************/
USART2REC_TypeDef USART2REC_Structure; //USART2 接收定义。在别的函数调用需要extern 
/**
  * @说明     配置中断向量控制器
  * @参数     None
  * @返回值   None
  * @注意     NVIC_Priority_Structure.USART2_Priority;需要提前配置
  */
void NVIC_USART2Configuration(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	/* Enable the RTC Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_Priority_Structure.USART2_Priority;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}
/**
  * @说明     USART2 相关GPIO和工作模式配置
  * @参数     None 
  * @返回值   None
  * @GPIO     PA2:TX2   PA3:RX2
  */
void USART2_Config(u16 USATR2_Boads)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);  //在使用串口复用功能的时候,要开启相应的功能时钟USART
	
	NVIC_USART2Configuration();
    //配置USART2 TX引脚工作模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //配置USART2 RX引脚工作模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //串口2工作模式配置
    USART_InitStructure.USART_BaudRate = USATR2_Boads;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No ;  //奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不采用硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   //串口模式:双线全双工
    USART_Init(USART2, &USART_InitStructure);
    
	USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);    //使能USART2接收中断!!!
    USART_Cmd(USART2, ENABLE);   //使能USART外设!!!!!,勿忘	
}
/**
  * @说明     USART2字符串发送函数
  * @参数     str: 指向字符串的指针
  * @返回值   None
  */
void USART2_SendString(char *str)
{
    uint8_t index = 0;  //注意,在此的字符串数组长度,超过255则,需要修改index类型。
    
    do
    {
        USART_SendData(USART2,str[index]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TXE) == RESET);
        index++;        
    }
    while(str[index] != 0);  //检查字符串结束标志    
}
/**
  * @说明     串口中断接收处理
  * @参数     none
  * @返回值   None
  * @包含出处  #include "usart2.h"
  * @注意     需要将此放入对应定时器的中断函数中
  *          如:在stm32f10x_it.c中:
  *			extern void USART2_Receive(void);
  *			void USART2_IRQHandler(void)
  *			{
  *				USART2_Receive();
  *			}
  */
void USART2_Receive(void)
{
	char ucTemp=0;
	
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)   //使用 if 语句来判断是否是真的产生 USART 数据接收这个中断事件
	{
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);
		ucTemp = USART_ReceiveData(USART2);
		if(ucTemp == 'C' || ucTemp == 'S')
		{	
			USART2REC_Structure.USART2_RecNum = 0;
			USART2REC_Structure.IS_USART2_ReceiveStart = TRUE;
			memset(USART2REC_Structure.USART2_RecTab,0x00,USART2_RecTab_Max);
		}		
		if(USART2REC_Structure.IS_USART2_ReceiveStart == TRUE)
		{
			USART2REC_Structure.USART2_RecTab[USART2REC_Structure.USART2_RecNum++] = USART_ReceiveData(USART2);
		}
		if(USART2REC_Structure.USART2_RecNum > USART2_RecTab_Max - 1)
		{
			USART2REC_Structure.USART2_RecNum = 0;
		}
		if(ucTemp == '\n' || USART2REC_Structure.USART2_RecNum > 0)
		{
			USART2REC_Structure.IS_USART2_RecSucess = TRUE;
			USART2REC_Structure.IS_USART2_ReceiveStart = FALSE; // 跟USART2REC_Structure.USART2_RecNum>0联用,获取有效字节个数
		}
	}
}
/**
  * @说明     重定向C库函数printf 到USART2
  * @参数     None
  * @返回值   None
  * @ 由printf函数调用
  * @  需要stdio.h头文件支持,  编译器选项中的target->Use MicroLIB需要勾选!!!!!!!!!!!!!
  */
int fputc(int ch, FILE *f)
{
    USART_SendData(USART2,(unsigned char) ch);
    while(USART_GetFlagStatus(USART2,USART_FLAG_TC) != SET);  //检查串口发送是否完成标志位
    return (ch);
}
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
	/* 等待串口输入数据 */
	while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);

	return (int)USART_ReceiveData(USART2);
}

你可能感兴趣的:(单片机嵌入式,嵌入式,stm32,单片机)