【第六课】 《IO输入输出》
结合上课讲的知识和查询手册发散的知识点,进行梳理如下:
一、IO口的硬件部分
1. 电气特性方面:
- IO口的VDD最大支持电压+5.5V,普通IO对地最大电压为VDD+0.3V。当工作在5V条件下时,IO最大电压5.3V;工作在3.3V条件下时,IO最大电压3.6V。

- 3.3V工作条件下,双向模式和推挽模式的高电平电流如下,这个差距表明在比如spi有些条件写IO口要设置为推挽模式。同时,3.3V的IO口速度比5V工作条件下的低,比STM32的IO速度要低。另外,输出低电平的灌电流能力是20mA,注意电路设计时不要超电流了。



2. IO口的四种模式

-
芯片上电进入工作状态时默认所有IO进入高阻输入状态,这就是为什么初始化里面要有下面的代码了,也就是说所有IO口在执行下面代码后、进入工作状态前全部初始化为准双向口。IO口模式配置可以在ISP里面可视化配置。
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
-
由于P3.0和P3.1为烧录用的通讯接口,所以这两个IO口的模式略有不同,手册里都写的很清楚。
-
上面提到,推挽模式下高电平可输出达20mA,而准双向口仅200uA左右。在使用PWM、ADC等功能时要开启推挽模式。(修订:ADC需要高阻输入模式)
二、软件部分
按键检测电平代码很简单,即 if(!PXX)
就可以。下面讲一些关于按键的驱动代码特征:
1. 关于按键消抖
按键电路不可避免的会在几个ms的时间内存在抖动,在这期间电平的波动变化的,因此会导致按键误触发的问题。这是所有按键都会存在的问题。

按键抖动有前言抖动和后沿抖动两种情况,而消抖分为软件消抖和硬件消抖两种方式。硬件层面可以通过在按键电路中加入100nF电容以达到去耦的目的,如下图

而软件消抖的逻辑则是判断按键按下的时刻后,通过延时避开抖动周期后再次检测按键逻辑,以规避掉前沿抖动。代码如下:
if(Board_BTN_L == 0)
{
delay_ms(50);
if (Board_BTN_L == 0) BoardLED_ON();
}
这段代码的延时时间可以在几十ms到几百ms不等,根据个人按键习惯调整。
2. 关于延时函数
在使用ardiuno或者stm32环境进行单片机开发时很容易通过调用库函数实现 delay();
或者在FreeRTOS中的 Vtaskdelay();
或者 pdMS_TO_TICKS(500)
等等。但是在刚使用AI8051时就有点摸不着头脑:延时函数呢?
其实AI8051的延时函数回归到单片机发生延时的最本质特性,即tick数(心跳)。每个单片机在进行计时时都是根据频率特定计算下的TICK数来实现计时的。打个比方,当MCU频率在10MHz时,一个TICK就是0.05us,要实现延时100ms,就需要100ms/0.05us=2000000个tick。因此在你研究AI8051U的计时函数时会发现它首先与系统频率有关:
void Delay100ms(void) //@20.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 499998UL;
while (i) i--;
}
那么这个i为什么在20MHz下取499998呢?这里的 499998UL
很可能是经过事先估算或者调试得出的一个比较接近实现 100 毫秒延时效果的值(具体我也不太了解,有大佬解释一下吗)。
当然官方也给出了一个自适应系统频率的延时函数如下:
//========================================================================
// 函数: void delay_ms(unsigned char ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数, 这里只支持1~255ms. 自动适应主时钟.
// 返回: none.
// 版本: VER1.0
// 日期: 2013-4-1
// 备注:
//========================================================================
void delay_ms(u8 ms)
{
u16 i;
do{
i = MAIN_Fosc / 6000;
while(--i);
}while(--ms);
}
3. 关于按键的短按、长按检测
在视频的按键实验环节会出现一个现象,也就是当持续按下按键时系统会不断循环执行判断和开关灯的代码,从而导致长按时达不到预期效果。解决方法很简单,就在执行开关灯后面加一个 while()
判断就行了。
我这里再扩展一下,那么要实现长按操作呢?这里涉及到一个长按判断和连续长按判断的逻辑,也就是当按键按下时比如持续1秒后判断为长按,然后每3秒判断长按未松开时长按继续生效。感兴趣的同学可以写一下相关代码,这里应该要用到下节课的计时器。