【STM32】复用脚资源最大化:多功能引脚复用方案
在实际产品开发中,为了在资源受限的环境中实现更多功能,很多时候就会考虑端口复用,采用分时复用技术是优化I/O口资源利用的核心策略。单个I/O口可在不同时间段内交替执行输入与输出功能,显著减少物理接口数量。例如,在LED显示阵列中,一个I/O口通过快速切换时序,依次控制多路LED的亮灭状态,避免了传统方案中每个LED独立连接的冗余设计。

系统介绍
(1)蜂鸣器驱动与温度检测的复用设计。
(2)多个按键检测设计。
(3)多个LED灯控制设计。
(4)常用的LED灯与按键复用设计。
(5)进阶的LED灯与按键复用设计。
(1)主控芯片:本项目采用STM32F103RCT6为主控芯片,只要掌握基本原理,可快速适配任何内核芯片,大幅降低了硬件平台迁移和代码重构的成本。
(2)LED灯:普通的白发红LED灯,直径为4mm。
(3)按键:普通的轻触开关,尺寸为5*5*5mm。
(4)蜂鸣器:无源压电式1407型蜂鸣器。
(5)测温传感器:3950-100K热敏电阻,一种负温度系数(NTC)热敏电阻。
使用1个ADC口实现蜂鸣器驱动与温度检测的双重功能。蜂鸣器工作电流小,串联一个限流电阻,直接由I/O口驱动;温度检测电路设计使用电阻分压电路,并添加滤波电容提升抗干扰能力。

采用端口功能动态切换机制,在空闲状态下将端口配置为模拟输入模式以启用ADC检测功能;当需要驱动蜂鸣器时,临时关闭ADC检测并将端口切换至推挽输出模式以提供足够的驱动电流。蜂鸣器驱动完成后,为避免干扰和确保系统稳定,需延时至少300ms后,再将端口恢复为模拟输入模式并重新启用ADC检测,从而实现端口的复用和系统的稳定运行。
(1)初始化:系统初始化期间,将复用端口按ADC初始化步骤进行配置。使能端口和ADC时钟,设置相关的参数,并开启ADC校准。需要注意的是当ADC时钟被关闭时,ADC配置会被重置。校准只需在初始化执行一次,但在后续程序运行中,当重新启动ADC时无需再次执行校准操作(除非系统复位)。
// * @name : void Adc_Init(void)// * @function: 初始化ADC ADBUZZ:PB1(ADC12_IN9)voidAdc_Init(void){ADC_InitTypeDef ADC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_ADC1, ENABLE ); //使能ADC1通道时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14MGPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚GPIO_Init(GPIOB, &GPIO_InitStructure);ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1ADC_ResetCalibration(ADC1); //使能复位校准while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束ADC_StartCalibration(ADC1); //开启AD校准while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束}
(2)端口切换输入输出模式:在进行ADC检测或蜂鸣器驱动功能切换时,需根据当前任务需求配置端口模式:执行ADC检测前将端口设置为模拟输入模式,而在启动蜂鸣器驱动前则将端口切换至推挽输出模式,以确保各功能模块能够正常工作。
// * @name :void Set_ADBUZZ_GPIO_Mode(void)// * @function :测温蜂鸣器引脚输入输出设置// * @parameters :mode(0为输出 1为输入)void Set_ADBUZZ_GPIO_Mode(u8 mode){GPIO_InitTypeDef GPIO_InitStructure;if(mode == 1) //输入{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_ADC1, ENABLE );//使能ADC1通道时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚GPIO_Init(GPIOB, &GPIO_InitStructure);}else//输出{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_ResetBits(GPIOB,GPIO_Pin_1); //拉低}}
(3)ADC检测:在非蜂鸣器驱动状态下,可正常采集ADC值并进行数据处理,通过ADC采样值计算出对应的温度数值,实现温度监测功能。获取ADC值,使用的是单次转换模式,通过多次采集转换数据并计算均值,实现滤波处理,确保输出的稳定性。
// * @name :u16 Get_Adc1(u8 ch)// * @function :获得ADC值// * @parameters :ch:通道值// * @retvalue :返回ADC值u16 Get_Adc(u8 ch){ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,一个序列,采样时间为239.5周期ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); //等待转换结束return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果}// * @name :void adc_prg(void)// * @function :ADC处理(这里使用9通道)u8 ad_cnt;u32 AD_data;u16 AD_data_now=3723; //滤波后的ADC值(初始预设值为25摄氏度对应阻值的ADC值)u8 AD_temp_now; //转换后的温度值voidAdc_Prg(void){u16 temp_ad;Set_ADBUZZ_GPIO_Mode(1); //设置为输入脚temp_ad=Get_Adc(ADC_Channel_9);AD_data=AD_data+temp_ad;ad_cnt++;if(ad_cnt>=64) //滤波处理{ad_cnt=0;AD_data_now= AD_data/64;AD_data=0;AD_temp_now = Scan_Temp(AD_data_now); //ADC值转换温度值}}
结合3950-100K热敏电阻的阻温-温度对应表和电阻分压电路特性,生成温度值-AD值映射表,再采用查表方式实现AD值到温度值的转换。
// * @name :u8 Scan_temp(u16 temp)// * @function :将AD转成温度值// * @parameters :temp: ADC值// * @retvalue :返回温度值u8 Scan_Temp(u16 temp){u8 i=0;if(temp==0) i=200; //超过200度,都统一取值为200度else{for(i=0;i<200;i++){if(R_T_TABLE[i]<=temp) //NTC阻值与温度对应关系表break;}}return i;}
(4)蜂鸣器驱动:蜂鸣器驱动采用软件PWM方案,通过通用定时器TIM3配置输出频率为4KHz、占空比为50%的方波信号。定时器计数值设定为125us触发定时中断,在中断服务程序中对蜂鸣器驱动端口进行电平取反操作,从而生成稳定的方波信号驱动蜂鸣器工作。
// * @name :void TIM3_IRQHandler(void)// * @function :定时器3中断(125us)voidTIM3_IRQHandler(void){if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//检查指定的TIM中断发生与否{TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位time_125us++;if(buzzer_on_flag==1) //蜂鸣器驱动标志位{BUZZER=!BUZZER; //125us*2->4KHz}}}
当蜂鸣器完成所需时长的鸣叫后,立即进入300ms的延时等待状态,以确保蜂鸣器完全停止发声并消除可能的干扰。延时结束后,系统重新使能ADC检测功能,恢复正常的温度数据采集和处理流程。
// * @name :void SET_Buzzer(u8 x)// * @function :蜂鸣器函数// * @parameters :x(鸣叫时长,10ms为基础单位)void SET_Buzzer(u8 x){Set_ADBUZZ_GPIO_Mode(0); //设置输出ntc_start_flag=0; //检测ADC标准位buzzer_time=x;buzzer_on_flag=1;}// * @name :void Time_Prg(void)// * @function :时间处理函数(10ms执行一次)void Time_Prg(void){if(buzzer_time==0){buzzer_on_flag=0;BUZZER=0; //将端口输出为0}else{buzzer_time--;if(buzzer_time==0)ntc_start_flag=0;}if(ntc_start_flag==0) //延时300ms,再检测ADC{ntc_start_cnt++;if(ntc_start_cnt>=30){ntc_start_cnt=0;ntc_start_flag=1;}}}
(1)独立按键检测:每个按键都使用独立的I/O口进行检测,确保各按键工作状态相互独立,互不影响。这种设计编程逻辑简单直观,便于实现和调试,同时硬件连接也相对简单可靠。该方法的主要缺点是需要较多的MCU管脚资源,当按键数量较多时会浪费大量的I/O口,导致管脚利用率较低。因此,独立按键检测适用于按键数量较少或MCU管脚资源充足的系统应用场景。
(2)一个I/O口检测两个按键:通过将一个按键接地,另一个按键串联电阻接电源的方式,利用切换I/O口内部上下拉状态并掌握好时序判断来实现检测功能。该方法具有电路简单、无需复杂外围电路的优点,但存在无法区分两个按键同时按下情况的缺点。

(3)矩阵键盘检测:矩阵键盘检测通过行列交叉结构来检测按键状态,虽然在软件实现上增加了一定的复杂性,但能够用较少的I/O口检测多个按键,有效节省了MCU管脚资源。这种设计特别适用于需要较多按键但I/O口有限的应用场景,通过行列扫描的方式实现按键识别,既减少了硬件连接复杂度,又提高了管脚利用率。
(4)电阻分压电路检测:使用单个ADC口实现多按键状态检测,利用不同阻值电阻构成的分压电路,通过采集电压值来识别具体按键。该方法能够有效节省I/O口资源并便于扩展多个按键,但存在组合按键易冲突、对电阻精度要求高以及抗干扰能力较弱等不足。在实际应用中,这种检测方式广泛用于按键数量较多但MCU管脚资源紧缺的系统中,如遥控器、键盘控制器、调速档位等设备的按键检测电路设计。
本项目采用电阻分压电路检测按键方案,通过1个ADC口实现4个按键的检测功能。当电源电压为3.3V时,各按键对应的电压值分别为0V、0.825V、1.65V、2.475V,其中无按键按下时电压值为3.3V。该设计有效节省了I/O口资源,便于实现多按键检测功能。

当需要扩展至10个按键时,每个按键按下对应的电压值会发生相应变化,这要求电阻阻值也要随之调整,通过电阻分压电路的计算公式可以得出各个按键串联电阻的理论阻值。此外,随着按键数量增加,相邻按键间的电压差会变得更小,这对ADC的分辨率和系统的抗干扰能力提出了更高要求。

按键按下后,检测电压会随着参与分压的电阻变化而变化。影响按键检测准确性的因素包括ADC精度、电阻阻值误差以及温度漂移等,这些都会造成按键检测误差。为了提高按键检测的可靠性,需要适当放宽每个按键按下后的电压判定范围。通过电源电压值和按键总数,可计算出每个按键的理论电压均值。确定上拉电阻后,各按键串联电阻的阻值可通过分压电路公式计算得出,并根据市场上的标准阻值进行选取。在实际应用中,可通过实测各按键按下时的电压值与理论值的偏差,来校准判定基准,即在理论电压基础上加入合理的容差范围,从而使按键判断更为稳定可靠。
(1)初始化:初始化配置与上述蜂鸣器驱动及温度检测的端口复用方案保持一致,具体实现可参考上述代码示例。
(2)ADC检测:与上述蜂鸣器驱动及温度检测的端口复用方案保持一致,具体实现可参考相关代码。该部分重点讲解如何通过ADC采样值来判断按键状态:首先根据电阻分压电路计算公式得出各按键按下后的理论电压值: ,然后在实际测量中考虑电压偏差因素,通过比较实际采样值与理论值的差异来准确识别按键编号。
u8 ad_cnt;u32 AD_data;u16 AD_data_now=4096;float voltage_value=0; //按键电压值#define VOL_DEVIATION 0.2 //电压偏差值#define VOL_KEY1 0 //按键1按下 理论电压值#define VOL_KEY2 0.825 //按键2按下 理论电压值#define VOL_KEY3 1.65 //按键3按下 理论电压值#define VOL_KEY4 2.475 //按键4按下 理论电压值#define VOL_NOKEY 3.3 //无按键按下 理论电压值// * @name :void Adc_Prg(void)// * @function :ADC处理(这里使用0通道)voidAdc_Prg(void){u16 temp_ad;temp_ad=Get_Adc(ADC_Channel_0);AD_data=AD_data+temp_ad;ad_cnt++;if(ad_cnt>=10) //滤波次数影响检测速度{ad_cnt=0;AD_data_now= AD_data1/10;AD_data=0;}voltage_value=AD_data_now*3.3/4096; //计算当前按键按下电压值//判断按键键值if(voltage_value<(VOL_KEY1+VOL_DEVIATION))key_value=1;else if((voltage_value>=(VOL_KEY2-VOL_DEVIATION))&&(voltage_value<(VOL_KEY2+VOL_DEVIATION)))key_value=2;else if((voltage_value>=(VOL_KEY3-VOL_DEVIATION))&&(voltage_value<(VOL_KEY3+VOL_DEVIATION)))key_value=3;else if((voltage_value>=(VOL_KEY4-VOL_DEVIATION))&&(voltage_value<(VOL_KEY4+VOL_DEVIATION)))key_value=4;else if(voltage_value>=(VOL_NOKEY-VOL_DEVIATION))key_value=0;}
项目只使用了单击功能,可以根据需要增加双击、长按或组合按键功能。需要注意的是组合按键需要规避其他功能按键的电压值,以免产生误判。
使用3个通用I/O口控制6个LED灯,每个I/O口可串联限流电阻实现亮度调节,也可通过软件时序控制实现调光功能。该方法需参考单片机引脚驱动电流,当LED发生开路或短路故障时,会导致电流路径异常,进而影响LED驱动逻辑,严重时可能损坏单片机。

若扩展至4个通用I/O口则可控制12个LED灯。

与共阴阳数码管原理相同,可以参考文章《【STM32】4位阴阳脚位数码管的控制方式》。这里涉及I/O口的三种输出状态:高电平、低电平和高阻态。高阻态表示I/O口输出状态既不是高电平也不是低电平,呈现极高的阻抗,对下级电路没有任何影响。而部分单片机不支持高阻态输出时,可将相应端口设置为浮空输入模式实现该效果。
使用n个通用口可以控制(n-1)*n个LED灯。若要单点亮某个LED灯,则只需同一时间对n个I/O设置为对应输出状态即可。若要同时显示多个LED灯,则需要采用分时复用显示方式,每个I/O口作为公共端口,通过分不同时序进行显示。
(1)初始化:系统初始化阶段,将LED灯相关引脚设置成推挽输出模式,并对所有引脚进行统一操作,将其置1或清零。此设计确保系统上电过程中数码管维持稳定状态,避免因初始化不当导致的乱码显示问题。
(2)分时复用显示:当 LED 灯闪烁频率超过 100Hz时,人眼便难以察觉闪烁效果。不过,频率并非越高越好,因为频率升高会导致流经 LED 灯的电流增大,进而缩短其使用寿命,因此需合理旋转闪烁频率。在此设定中,定时器 3 每 1ms 触发一次中断,利用这 1ms 的间隔,对每位(com)所对应的 2段(seg) LED 灯进行点亮刷新操作。
(3)显示函数:执行逻辑分为两个步骤:首先清空显示缓冲区中的现有数据,随后将当前需要点亮的LED段码写入对应的显示位置。
#define BIT0 0x0001#define BIT1 0x0002#define BIT2 0x0004#define BIT3 0x0008#define BIT4 0x0010#define BIT5 0x0020#define BIT6 0x0040#define BIT7 0x0080#define bittst(_sfr,_bit) ((_sfr)&(_bit))u8 led_dsp_ram[3];// * @name :void LED_Prg(void)// * @function :LED显示函数。//led1(com0):led3(seg1:2) led5(seg2:4)//led2(com1):led1(seg1:0) led4(seg2:3)//led3(com2):led2(seg1:1) led6(seg2:5)voidclr_all_dsp(void){u8 i;for(i=0;i<3;i++){led_dsp_ram[i]=0x00;}}voidLED_Prg(void){clr_all_dsp();if(0!=bittst(led_count_cnt,BIT0))led_dsp_ram[1] |= 0x01;if(0!=bittst(led_count_cnt,BIT1))led_dsp_ram[2] |= 0x01;if(0!=bittst(led_count_cnt,BIT2))led_dsp_ram[0] |= 0x01;if(0!=bittst(led_count_cnt,BIT3))led_dsp_ram[1] |= 0x02;if(0!=bittst(led_count_cnt,BIT4))led_dsp_ram[0] |= 0x02;if(0!=bittst(led_count_cnt,BIT5))led_dsp_ram[2] |= 0x02;}
常用的LED灯与按键矩阵复用电路,通过4个通用I/O口实现对4个LED灯的控制以及2个按键状态的检测。其中,LEDC1和LEDC2作为LED灯的公共控制端,LEDS1和LEDS2则作为LED灯段段端与按键输入复用接口,在不冲突的情况下可同时完成LED显示与按键识别功能。

可扩展至多个I/O口复用扫描,如3*2、2*3等矩阵复用电路。


核心原理是分时复用技术,通过I/O脚在输出模式与输入模式间的动态切换,在不同时间段内分别执行LED驱动控制和按键状态检测任务。当I/O脚用于输出控制LED显示时,需暂时禁用按键检测功能;当I/O脚切换至输入模式检测按键时,则需关闭显示功能,以避免两者相互干扰影响系统稳定性。
n*m矩阵复用电路,使用n+m个通用I/O口(其中n为公共端com口,m为段选端seg口)控制n*m个LED灯并检测m个按键状态。
(1)初始化:系统初始化阶段,将相关引脚设置成推挽输出模式,并对所有引脚进行统一操作,将其置1或清零。此设计确保系统上电过程中数码管维持稳定状态,避免因初始化不当导致的乱码现象。
(2)LED显示:该过程在定时器3中断执行,驱动频率设定为100Hz。首先将所有公共端口(com)输出为1,所有段选端口(seg)输出为0,目的是将所有LED处于熄灭状态;再根据显示需求判断段选端口(seg)是否需要点亮,若需点亮则将对应段选端口(seg)输出为1;最后将需要点亮的公共端口(com)输出为0,且为保证显示效果,每1ms仅点亮一个公共端口(com)。
// * @name :void TIM3_IRQHandler(void)// * @function :定时器3中断:1msu8 time_led_1ms=0;u8 led_on_cnt[2]; //用于存储LED点亮数组:0为COM1端,1为COM2端void set_all_led_off(void){LEDC1=1;LEDC2=1;LEDS1=0;LEDS2=0;}void out_seg(u8 dsp){if(dsp==0x01)LEDS1=1;if(dsp==0x02)LEDS2=1;}void TIM3_IRQHandler(void) //TIM3中断{if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){TIM_ClearITPendingBit(TIM3, TIM_IT_Update);time_led_1ms++;if(time_led_1ms>2) //可以利用刷新频率调节LED亮度time_led_1ms=0;if(stop_dsp_flag==0) //待处理按键结束后{switch (time_led_1ms){case 1:set_all_led_off();out_seg(led_on_cnt[0]); //更改数值即可任意点亮所需LEDLEDC1=0;break;case 2:set_all_led_off();out_seg(led_on_cnt[1]); //更改数值即可任意点亮所需LEDLEDC2=0;break;default:set_all_led_off(); //其他时段关闭显示break;}}}}
(3)按键检测:首先将所有LED熄灭以避免按键按下时显示混乱;再将复用引脚配置为浮空输入模式,检测引脚电平状态(可进行延时消抖处理,但时间需控制合理避免阻塞);若检测到低电平则判定为按键按下,处理完成后将引脚恢复为推挽输出模式,依次完成所有按键的扫描检测。
按键消抖采用计数累加算法而非简单延时方式,避免了延时期间单片机无法执行其他任务的问题。本项目按键检测每10ms扫描一次,通过比较新旧按键状态值,当连续多次检测到相同按键状态时进行计数累加,达到预设阈值后确认为有效按键按下。
// * @name :void KEY_Prg((void))// * @function :按键检测程序#define LEDC1_PIN 13#define LEDC2_PIN 9#define LEDS1_PIN 14#define LEDS2_PIN 0#define LED_IN 1#define LED_OUT 0#define LEDC1 PBout(13)#define LEDC2 PBout(9)#define LEDS1 PBout(14)#define LEDS2 PCout(0)#define LEDS1_K1 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14) //读取电平#define LEDS2_K2 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_0) //读取电平u8 key_delay=0;u8 key_down_flag=0; //按键按下标志u8 key_value=0;u8 key_old=0;u8 stop_dsp_flag=0; //禁止显示标志voidKEY_Prg(void){stop_dsp_flag=1;LEDC1=1;LEDC2=1;LEDS1=0;LEDS2=0;key_value=0;//KEY1Set_GPIO_Mode(LED_IN,LEDS1_PIN); //浮空输入模式if(LEDS1_K1==0)key_value=1;Set_GPIO_Mode(LED_OUT,LEDS1_PIN); //推挽输出模式//KEY2Set_GPIO_Mode(LED_IN,LEDS2_PIN);if(LEDS2_K2==0)key_value=2;Set_GPIO_Mode(LED_OUT,LEDS2_PIN);if(key_value==key_old){if(key_delay>2) //更改阈值改变判断速度{if(key_value>0){if(!key_down_flag){key_down_flag=1;switch(key_value){case1:break;case2:break;default:break;}}}else{key_delay=0;key_down_flag=0;}}else{key_delay++;}}else{key_delay=0;key_old=key_value;}stop_dsp_flag=0;}
进阶版的LED灯与按键矩阵复用电路,通过4个通用I/O口实现对4个LED灯的控制以及4个按键状态的检测。其中,LEDC1和LEDC2作为LED灯的公共控制端与按键输入复用接口,LEDS1和LEDS2则作为LED灯段段端与按键输入复用接口,在不冲突的情况下可同时完成LED显示与按键识别功能。

可扩展至多个I/O口复用扫描,如3*2、2*3等矩阵复用电路。


核心原理采用分时复用技术,与普通矩阵复用电路原理一致。其唯一区别在于按键检测时需进行双向扫描:即检测任一按键时,需同时从按键两端接口进行判断,只有当两端接口均检测到按键按下信号时,才判定该按键有效按下。该设计通过双端检测机制有效提升了按键识别的可靠性,避免了单端检测可能产生的误判问题。
n*m矩阵复用电路,使用n+m个通用I/O口(其中n为公共端com口,m为段选端seg口)控制n*m个LED灯并检测n*m个按键状态。
(1)初始化、LED显示:与普通矩阵复用相同。
(2)按键检测:首先将所有LED熄灭以避免按键按下时显示混乱;再将段选端复用引脚配置为浮空输入模式,检测引脚电平状态;若检测到高电平则判定为有按键按下,此时将该复用脚配置为推挽输出,但由于段选端接口上有多个按键,需进一步处理;将公共端复用引脚配置为浮空输入模式,检测其电平状态;若为低电平则判定该按键有效;完成检测后将该引脚恢复为推挽输出模式,依次完成剩下按键的扫描检测。
// * @name :void KEY_Prg((void))// * @function :按键检测程序#define LEDC1_PIN 11#define LEDC2_PIN 12#define LEDS1_PIN 10#define LEDS2_PIN 15#define LED_IN 1#define LED_OUT 0#define LEDC1 PBout(11)#define LEDC2 PBout(12)#define LEDS1 PBout(10)#define LEDS2 PBout(15)#define LEDC1_K1_K2 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) //读取电平#define LEDC2_K3_K4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12) //读取电平#define LEDS1_K1_K3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) //读取电平#define LEDS2_K2_K4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15) //读取电平u8 key_delay=0;u8 key_down_flag=0; //按键按下标志u8 key_value=0;u8 key_old=0;u8 stop_dsp_flag=0; //禁止显示标志voidKEY_Prg(void){stop_dsp_flag=1;LEDC1=1;LEDC2=1;LEDS1=0;LEDS2=0;key_value=0;//KEY1 & KEY3Set_GPIO_Mode(LED1_IN,LEDS1_PIN); //设置seg口输入if(LEDS1_K1_K3==1) //判断seg口是否为1,1为有按键按下{Set_GPIO_Mode(LED1_OUT,LEDS1_PIN); //设置seg口输出LEDS1=0;//seg口输出0(恢复预设)Set_GPIO_Mode(LED1_IN,LEDC1_PIN); //设置com口输入//delay_us(150); //可适当添加延时if(LEDC1_K1_K2==0) //判断com口是否为0,0为有按键按下key_value=1;Set_GPIO_Mode(LED1_OUT,LEDC1_PIN); //设置com口输出Set_GPIO_Mode(LED1_IN,LEDC2_PIN); //设置com口输入//delay_us(150); //可适当添加延时if(LEDC2_K3_K4==0) //判断com口是否为0,0为有按键按下key_value=3;Set_GPIO_Mode(LED1_OUT,LEDC2_PIN); //设置com口输出}Set_GPIO_Mode(LED1_OUT,LEDS1_PIN);LEDS1=0;//seg口输出0(恢复预设)//KEY2 & KEY4Set_GPIO_Mode(LED1_IN,LEDS2_PIN); //设置seg口输入if(LEDS2_K2_K4==1) //判断seg口是否为1,1为有按键按下{Set_GPIO_Mode(LED1_OUT,LEDS2_PIN); //设置seg口输出LEDS2=0; //seg口输出0(恢复预设)Set_GPIO_Mode(LED1_IN,LEDC1_PIN); //设置com口输入//delay_us(150); //可适当添加延时if(LEDC1_K1_K2==0) //判断com口是否为0,0为有按键按下key_value=2;Set_GPIO_Mode(LED1_OUT,LEDC1_PIN); //设置com口输出Set_GPIO_Mode(LED1_IN,LEDC2_PIN); //设置com口输入//delay_us(150); //可适当添加延时if(LEDC2_K3_K4==0) //判断com口是否为0,0为有按键按下key_value=4;Set_GPIO_Mode(LED1_OUT,LEDC2_PIN); //设置com口输出}Set_GPIO_Mode(LED1_OUT,LEDS2_PIN);LEDS2=0; //seg口输出0(恢复预设)if(key_value==key_old){······}else{······}stop_dsp_flag=0;}
项目最终测试结果符合设计要求,下面是演示视频。
夜雨聆风