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

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



##### **2. IO口的四种模式**
!(data/attachment/forum/202412/04/205542qcnvvbntp636bp4h.png "image.png")
1. 芯片上电进入工作状态时默认所有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;
```
2. 由于P3.0和P3.1为烧录用的通讯接口,所以这两个IO口的模式略有不同,手册里都写的很清楚。
3. 上面提到,推挽模式下高电平可输出达20mA,而准双向口仅200uA左右。在使用PWM、~~**ADC**~~等功能时要开启推挽模式。**(修订:ADC需要高阻输入模式)**
---
#### **二、软件部分**
按键检测电平代码很简单,即 `if(!PXX)`就可以。下面讲一些关于按键的驱动代码特征:
##### 1. 关于按键消抖
按键电路不可避免的会在几个ms的时间内存在抖动,在这期间电平的波动变化的,因此会导致按键误触发的问题。这是所有按键都会存在的问题。
!(data/attachment/forum/202412/04/210829mw0e84gjffzeckcy.png "image.png")
按键抖动有前言抖动和后沿抖动两种情况,而消抖分为软件消抖和硬件消抖两种方式。硬件层面可以通过在按键电路中加入100nF电容以达到去耦的目的,如下图
!(data/attachment/forum/202412/04/211052g4c17qczrekr6b8q.png "image.png")
而软件消抖的逻辑则是判断按键按下的时刻后,通过延时避开抖动周期后再次检测按键逻辑,以规避掉前沿抖动。代码如下:
```
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 毫秒延时效果的值(具体我也不太了解,有大佬解释一下吗)。
当然官方也给出了一个自适应系统频率的延时函数如下:
```c
//========================================================================
// 函数: voiddelay_ms(unsigned char ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数, 这里只支持1~255ms. 自动适应主时钟.
// 返回: none.
// 版本: VER1.0
// 日期: 2013-4-1
// 备注:
//========================================================================
voiddelay_ms(u8 ms)
{
u16 i;
do{
i = MAIN_Fosc / 6000;
while(--i);
}while(--ms);
}
```
##### 3. 关于按键的短按、长按检测
在视频的按键实验环节会出现一个现象,也就是当持续按下按键时系统会不断循环执行判断和开关灯的代码,从而导致长按时达不到预期效果。解决方法很简单,就在执行开关灯后面加一个 `while()`判断就行了。
我这里再扩展一下,那么要实现长按操作呢?这里涉及到一个长按判断和连续长按判断的逻辑,也就是当按键按下时比如持续1秒后判断为长按,然后每3秒判断长按未松开时长按继续生效。感兴趣的同学可以写一下相关代码,这里应该要用到下节课的计时器。
272761180
发表于 2024-12-4 21:35:31
本节课课后练习1代码及演示视频。
[!(/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg) 附件:02_AI8051U_ButtonLED.zip](forum.php?mod=attachment&aid=68587 "attachment")
```c
/*实验现象:使用按键控制LED开启和关闭
// 开发板左侧按键开启板载LED,右侧按键关闭板载LED;
// 拓展版上侧按键仅开启红色LED,下侧按键仅关闭LED;
备注:24MHz频率
*/
```
<video controls="controls" src="forum.php?mod=attachment&aid=68586"></video>
VCC
发表于 2024-12-4 21:45:04
272761180 发表于 2024-11-27 22:38
【第五课】《C语言基础》
比如要给P0M0赋值为 1100 0000,就可以 P0M0 |=(0X80 | 0X40);或者P0M0 |=(0X80 + 0X40);
|= 这里是笔误?
VCC
发表于 2024-12-4 21:56:13
272761180 发表于 2024-12-4 21:31
### **【第六课】《IO输入输出》**
结合上课讲的知识和查询手册发散的知识点,进行梳理如下:
上面提到,推挽模式下高电平可输出达20mA,而准双向口仅200uA左右。在使用PWM、ADC等功能时要开启推挽模式
ADC口是模拟输入口,此时应该避免数字输出的驱动器对外部进来的待测模拟信号产生影响
所以使用ADC时应该用高阻输入模式吧
272761180
发表于 2024-12-4 22:10:02
本节课课后练习2代码及演示视频。
```
/*
实验现象:使用按键控制LED开启和关闭
// 拓展版上侧按键LED依次增加点亮个数,下侧按键关闭所有灯;
备注:24MHz频率
*/
void BUTTON_LEDCOUNT(void)
{
if(BTN1 == 0) //下键
{
delay_ms(50);
if (BTN1 == 0)
{
P4 = 0xff;
while(!BTN1);
}
}
if(BTN2 == 0)//上键
{
delay_ms(50);
if (BTN2 == 0)
{
P4 = ~((~P4<<1)+1);
while(!BTN2);
}
}
}
```
<video controls="controls" src="forum.php?mod=attachment&aid=68593"></video>
[!(/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg) 附件:06_02_ButtonLED+.zip](forum.php?mod=attachment&aid=68594 "attachment")
272761180
发表于 2024-12-4 22:13:19
VCC 发表于 2024-12-4 21:45
|= 这里是笔误?
额这里意思是不改变其他IO口的模式,仅改变最高两位的意思,大佬看得真细
272761180
发表于 2024-12-4 22:21:10
VCC 发表于 2024-12-4 21:56
ADC口是模拟输入口,此时应该避免数字输出的驱动器对外部进来的待测模拟信号产生影响
所以使用ADC时应 ...
感谢指正
272761180
发表于 2024-12-4 22:29:44
VCC 发表于 2024-11-27 03:06
你提到:
大佬好不容易逮到你,容我再请教一个问题:我在第三课的笔记关于IRC频段里面画了一个EXCEL图,按照4个频段调节范围发现在12.71MHz-19.7MHz时理应无法使用内部高速IRC的。然而在ISP里面有一个18.432MHz,这是指其他型号MCU支持的频段而AI8051U不可选吗?还是说配置外部IRC可以使用?这个18.432MHz一般用在什么时候呢?
VCC
发表于 2024-12-4 22:38:24
272761180 发表于 2024-12-4 22:29
大佬好不容易逮到你,容我再请教一个问题:我在第三课的笔记关于IRC频段里面画了一个EXCEL图,按照4个频 ...
被叫大佬可真开心啊哈哈哈
你说得好有道理,18.432MHz真的位于你的图表范围之外。你看得太细了!!
把我问住了
外部振荡器就不叫IRC了,IRC就是internal RC的意思
你问18.432MHz它的用途?它是另一个常见的晶振11.0592MHz的1.667倍
但凡是这些倍数的频率,都是为了和9600 115200这些标准串口波特率数字相匹配的。
但这些都是古代的事情了。
而现在的芯片早就不挑晶振了,再加上数字电路中的小数分频技术,一切都是可以设置的。也不用去选购专门规格的晶振
VCC
发表于 2024-12-5 02:13:56
VCC 发表于 2024-11-28 01:27
哈哈哈,其实我也没找清楚。
文档百密一疏是难免的,只关注了常用的、复杂的内容。反而忽视了这种经典而 ...
如果要使用64位变量需要在程序文件申明 #program float64,主要是双精度数据。
其实他的PPT里面是正确的
应为
#pragma float64
但是他读错了。怎么你也跟着写错了?莫非你是照着AI转文本的?{:4_165:}
另外注意,只有C251模式允许用这个
C51不支持这个。
主要是双精度数据。
你这个理解也不对,使用 #pragma float64 后,唯一的改变就是,C251把double型变量当做双精度浮点数处理了,否则默认是单精度浮点数
但是,仅限对double型变量有影响
不论是C51还是C251,都不支持64位整数变量unsigned long long int