AI8051U学习手记 | 再次感谢STC免费提供的AI8051U最小系统板和冲哥的优质教程
<h2>一、认识AI8051</h2><blockquote>
<p>最近上STC论坛查资料,突然发现主页有学习AI8051送最小核心板的活动,申请了一张学习精进一下自己。</p>
</blockquote>
<h3>1.AI8051特点</h3>
<p>阅读官方文档可以总结如下特点:</p>
<ol>
<li><strong>双核兼容架构</strong>:支持 <strong>8 位与 32 位双指令集</strong>。</li>
<li><strong>硬件加速能力</strong>:<strong>内置 TFPU</strong>(三角函数浮点运算器),可快速处理 FFT、语音识别等复杂算法,无需额外 DSP。</li>
<li><strong>丰富存储与外设</strong>:集成 34KB SRAM、64KB Flash,支持 ISP/IAP;外设含 12 位 ADC、16 位 PWM、USB 2.0、CAN/LIN 总线等,可<strong>直接驱动显示屏、音频设备</strong>。</li>
<li><strong>开发便捷性</strong>:<strong>兼容 Keil C51/C251</strong> ,内置 USB 接口,<strong>自身就是仿真器</strong>。</li>
<li><strong>可靠性</strong>:低功耗,抗干扰能力强,性价比突出,仅1.9元一片,最小系统板8元包邮。</li>
</ol>
<h3>2.AI8051擎天柱最小系统板</h3>
<p>收到的开发板有两处未焊接元器件,虽然测试后发现并没有什么影响,但是本着打破砂锅问到底的精神去看了原理图发现如下:</p>
<p><img src="data/attachment/forum/202511/06/120849k6afce1eyssa6og1.png" alt="a15ad856-af48-4e90-8cbe-aa6267055805.png" title="a15ad856-af48-4e90-8cbe-aa6267055805.png" /></p>
<p><img src="data/attachment/forum/202511/06/120849naww3acmc8tw3rc2.png" alt="Pastedimage20251106115830.png" title="Pasted image 20251106115830.png" />补焊后效果如下:<br />
<img src="data/attachment/forum/202511/06/120859ynwwcxfxtnoxanow.jpg" alt="2b5fafb7e824b267416d8fdad94569ea1.jpg" title="2b5fafb7e824b267416d8fdad94569ea 1.jpg" /></p>
<blockquote>
<p>参考资料:</p>
<p><a href="https://www.stcaimcu.com/data/download/Datasheet/AI8051U.pdf">AI8051U 《单片机原理及应用》 《实验指导书》</a></p>
<p><a href="https://www.stcaimcu.com/thread-11902-1-1.html">《8051U深度入门到32位51大型实战视频》</a></p>
</blockquote>
实验箱已安排 | AI8051U学习手记 | 感谢宏晶和冲哥提供的宝贵学习机会
<h2>二、环境配置</h2><blockquote>
<p>AI8051的Keil环境配置十分简单,确保Keil MDK,C51,C251正确安装,通过AICude-ISP一键添加型号、头文件、仿真器驱动到Keil即可。</p>
</blockquote>
<h3>1.下载Keil</h3>
<p>点击Keil Product Downloads进入keil官网下载界面后,逐个点击下载按照指引安装即可。<br />
<img src="https://www.stcaimcu.com/data/attachment/forum/202511/06/122411hmxf5juxr4ggk1rx.png" alt="0f6e3433-3541-483a-b97e-4c9cab6334cc.png" title="0f6e3433-3541-483a-b97e-4c9cab6334cc.png" /></p>
<h3>2.下载AICube-ISP</h3>
<p>点击<a href="https://www.stcai.com/gjrj">深圳国芯人工智能有限公司-工具软件</a>进入STC官网下载界面后,点击下载按照指引安装即可。</p>
<p><img src="data/attachment/forum/202511/06/122433lwm49hvc9vea4a48.png" alt="6ec24872-acac-4355-ab5f-78158b93f6ed.png" title="6ec24872-acac-4355-ab5f-78158b93f6ed.png" /></p>
<h3>3.一键添加型号、头文件、仿真器驱动到Keil</h3>
<p>确保Keil安装正确后,确保Keil关闭后,打开AICube-ISP,进入Keil仿真设置一键导入。</p>
<p><img src="data/attachment/forum/202511/06/122442gz8hg58ud6wdg8gd.png" alt="128f9f00-f301-40ea-84a5-6ca106e519fc.png" title="128f9f00-f301-40ea-84a5-6ca106e519fc.png" /><br />
引用官方提示:</p>
<blockquote>
<p>1、添加前请先<strong>关闭Keil</strong>软件,请务必先关闭Keil软件,否则会导致添加失败<br />
2、添加MCU选型数据库文件到 Keil安装路径下的<strong>UV2(或UV3\或UV4\取决于Keil的版本)目录中</strong><br />
3、安装仿真器的驱动程序到<strong>Keil安装路径</strong>下的C51\目录中<br />
4、复制8051头文件到<strong>Keil安装路径</strong>下的C51\INC\STC\目录中<br />
5、复制80251头文件到<strong>Keil安装路径</strong>下的C251\INC\STC\目录中<br />
6、请确认Keil的安装目录下<strong>有C51目录和C251目录</strong>。请确认Keil的安装目录下<strong>有且仅有</strong>UV2、UV3、UV4目录中的一个存在</p>
</blockquote>
要 做到 USB不停电下载;
要 尝试 图形化配置外设;
推荐优先看的 printf_usb("Hello World !\r\n")及usb不停电下载, 演示视频链接
从 www.STCAI.com
下载 最新的 AiCube-ISP-V6.96E 或以上版本软件 !
下载 最新的 USB库函数,永远用最新的 USB库函数 !
下载 最新的 用户手册 !
下载 最新的 上机实践指导书 !
https://v.stcai.com/sv/1c5eec2-197fcd9b766/1c5eec2-197fcd9b766.mp4
上面是 小李 演示:Ai8051U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成
https://v.stcai.com/sv/1fce8086-197cf2b9dd4/1fce8086-197cf2b9dd4.mp4
上面是 小赵 演示:Ai8051U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成
<h3>三、LED跑马灯</h3>
<p><em>首先感谢管理员大大提供的USB不停电下载与图形化配置外设教程,学习内容将在后续的打卡中体现~</em></p>
<blockquote>
<p>LED跑马灯、闪光灯是许多开发板的第一个例程,既方便学习者学习搭建程序模板,也便于检测开发板好坏。</p>
</blockquote>
<h4>1.下载例程</h4>
<p>官方的AICube-IDF提供了便捷的资料下载接口,点击跳转浏览器下载即可。</p>
<p><img src="data/attachment/forum/202511/06/161244lmehubzsuhjnvu5m.png" alt="3.1.1AICube-ISP下载例程.png" title="3.1.1 AICube-ISP下载例程.png" /></p>
<h4>2.打开例程</h4>
<p>解压后会获得如下几个文件夹,为充分发挥AI8051的强大性能,直接选择32位例程即可。<br />
![]<br />
双击打开\AI8051U-DEMO-CODE-V1.2\Ai8051U-32Bit\01-用P0口做跑马灯\C语言\sample.uvproj即可通过Keil查看、修改、编译工程。</p>
<p><img src="data/attachment/forum/202511/06/161252esi3icoixiexczsn.png" alt="3.2.2跑马灯例程Keil界面.png" title="3.2.2 跑马灯例程Keil界面.png" /></p>
<h3>3.修改并编译下载例程</h3>
<p>例程中最上部的说明往往格外重要。本例程我们主要可以获取两个信息,例程使用 <strong>P0 口</strong>驱动LED灯,时钟 <strong>24 MHZ</strong>。</p>
<p><img src="data/attachment/forum/202511/06/161307kx4xmmz244boo0ma.png" alt="3.3.1例程说明.png" title="3.3.1 例程说明.png" /><br />
通过观察开发板或原理图可以发现,原厂的LED是焊接在 <strong>P20 - P27</strong> 上的,这意味着我们实际需要使用的是 <strong>P2 口</strong>。<br />
<img src="data/attachment/forum/202511/06/161338vxh5za1whhxw1ffi.png" alt="3.3.2开发板的LED.png" title="3.3.2 开发板的LED.png" /><br />
因此,我们需要修改程序,当然在 P00-P07 补焊一排LED灯理论上也是可以的。<br />
主要修改的地方有这两处。</p>
<h5>1.修改P2M0为0xff推挽输出,并将P0M0改为0X00准双向口。</h5>
<p><img src="data/attachment/forum/202511/06/161349pwv5lncczceucuc5.png" alt="3.3.3引脚配置.png" title="3.3.3 引脚配置.png" /></p>
<h5>2.将驱动LED的端口从 P00-P07 改为 P20-P27</h5>
<p><img src="data/attachment/forum/202511/06/161356qou6vfuroy2ro4c5.png" alt="3.3.4程序修改.png" title="3.3.4 程序修改.png" /></p>
<h5>3.确保Keil输出hex文件打开后,点击Rebuild编译。</h5>
<p><img src="data/attachment/forum/202511/06/161403jna6vlzgzvnnna0e.png" alt="3.3.5配置HEX输出并编译.png" title="3.3.5 配置HEX输出并编译.png" /></p>
<h5>4.使用AICube-ISP烧录程序</h5>
<p>打开编译好的HEX文件</p>
<p><img src="data/attachment/forum/202511/06/161449w4v4clel4v466cc7.png" alt="3.3.6导入编译好的文件.png" title="3.3.6 导入编译好的文件.png" /><br />
配置时钟为24Mhz</p>
<p><img src="data/attachment/forum/202511/06/161456v8fmvjtbebfaesuj.png" alt="3.3.7配置时钟.png" title="3.3.7 配置时钟.png" /><br />
开发板用USB连接电脑后,按住开发板的P32 INTO按键,点击一下POWER按键,串口显示如图即可点击下载烧录。</p>
<p><img src="data/attachment/forum/202511/06/161511rxav4m2h45z25pez.png" alt="3.3.8开发板烧录按键.png" title="3.3.8 开发板烧录按键.png" /></p>
<p><img src="data/attachment/forum/202511/06/161525e326b6be3eetck2e.png" alt="3.3.9连接串口并烧录.png" title="3.3.9 连接串口并烧录.png" /><br />
点击后,等待出现操作成功字样,开发板LED会自动开启跑马灯效果。</p>
<p><img src="data/attachment/forum/202511/06/161841qpy119hpnbkvobyo.gif" alt="11月6日.gif" title="11月6日.gif" /></p>
<p>也可以试试流水灯效果</p>
<pre><code>while(1)
{
led_mask = 0x00;
for(i = 0; i < 8; i++)
{
led_mask |= (1 << i);
P2 = ~led_mask;
delay_ms(250);
}
delay_ms(500);
P2 = 0xFF;
delay_ms(500);
}
</code></pre>
深圳国芯人工智能有限公司-工具软件
<h2>四、USB串口输出</h2>
<h3>1. 通过AiCube-ISP进入AICube</h3>
<p><img src="data/attachment/forum/202511/07/133401l9y5tfqjef29929n.png" alt="4.1.1打开AICube.png" title="4.1.1 打开AICube.png" /></p>
<h4>2. 创建AICube项目</h4>
<p><img src="data/attachment/forum/202511/07/133525t3j7yytg9j37kbvh.png" alt="4.2.1创建AICube项目.png" title="4.2.1 创建AICube项目.png" /></p>
<h4>3.配置USB通信串行总线并生成Keil项目</h4>
<p>勾选USB,通信串行总线,确保设置如图后,点击绿色按钮或按下<strong>F5快捷键</strong>,将自动创建并打开Keil项目。</p>
<p><img src="data/attachment/forum/202511/07/133536gvcacfga9wf9lvnn.png" alt="4.3.1设置通信串行总线并生成Keil项目.png" title="4.3.1 设置通信串行总线并生成Keil项目.png" /><br />
生成项目打开主函数后如图所示</p>
<p><img src="data/attachment/forum/202511/07/133548ltwx8xntrwum3llk.png" alt="4.3.2生成的Keil项目.png" title="4.3.2 生成的Keil项目.png" /><br />
直接编译确保模板被正确创建</p>
<p><img src="data/attachment/forum/202511/07/133559jl4glg4q4ykk2mg0.png" alt="4.3.3编译模板.png" title="4.3.3 编译模板.png" /></p>
<h3>4.添加代码到模板并编译</h3>
<p>通过注释关闭 <code>USBLIB_OUT_Done()</code>,并在相应位置中添加 <code>printf_usb("Hello_World\r\n");</code>,编译完成后使用AICube-ISP下载即可(建议将IRC频率设置为40Mhz),此处不再赘述。</p>
<p><img src="data/attachment/forum/202511/07/133613ako279dhd2g2igg0.png" alt="4.4.1添加代码到模板并编译.png" title="4.4.1 添加代码到模板并编译.png" /></p>
<h4>5.打开串口助手监听串口</h4>
<p>设置合适的COM口,波特率使用默认的115200即可,打开串口后显示如下内容。</p>
<p><img src="data/attachment/forum/202511/07/133620se1l116kt1j3tckq.png" alt="4.5.1串口输出效果.png" title="4.5.1 串口输出效果.png" /></p>
<h2>五、USB免下电下载</h2>
<p>很方便的功能</p>
<h3>1.创建工程模板</h3>
<p>参照<em>USB串口输出</em>部分使用AICube一键生成工程模板。</p>
<h3>2.在生成的工程main.c中添加如下内容</h3>
<p>添加如下内容并编译</p>
<p><img src="data/attachment/forum/202511/08/205926l6qj8e7enb1q18d1.png" alt="5.2.1添加代码.png" title="5.2.1 添加代码.png" /></p>
<h3>3.烧录程序</h3>
<p>此时打开AICube-ISP仍是如下状态,按照之前的方法进行烧录即可</p>
<p><img src="data/attachment/forum/202511/08/205932zt3r0fx511px2t40.png" alt="5.3.1烧录程序.png" title="5.3.1 烧录程序.png" /><br />
烧录成功后串口信息如下,此时即可不断电直接烧录。</p>
<p><img src="data/attachment/forum/202511/08/205941ujcuau1y1ps2czo1.png" alt="5.3.2成功效果.png" title="5.3.2 成功效果.png" /></p>
<h3>4.运行程序</h3>
<p>此时通过串口助手即可与单片机通讯。<br />
根据代码易得,发送6,7会反馈不同的信息。</p>
<p><img src="data/attachment/forum/202511/08/205948aiz3iiclii39siyc.png" alt="5.4.1运行程序.png" title="5.4.1 运行程序.png" /></p>
USB通信 都搞定了,建议直接申请 AI8051U 实验箱,
尝试 用 AiCube 图形化实现 各外设的 功能实现
<h2>六、嵌入式C语言基础</h2>
<h2>1、USB-CDC串口printf函数实现</h2>
<h3>1.1 功能概述</h3>
<p>printf函数是开发调试的核心工具,可通过USB-CDC串口快速打印变量值和调试信息,实现"免冷启动下载"调试。</p>
<h3>1.2 启用步骤</h3>
<ol>
<li>打开USB库配置文件</li>
<li>找到printf宏定义位置(通常在第6行附近)</li>
<li><strong>移除行首的反斜杠 <code>\</code></strong> 以启用功能</li>
<li>重新编译工程</li>
</ol>
<blockquote>
<p><strong>常见问题排查</strong>:</p>
<ul>
<li>配置文件选项错误</li>
<li>liba库文件未正确添加</li>
<li>误删关键系统文件</li>
</ul>
</blockquote>
<h3>1.3 printf重定向原理</h3>
<p>工程中printf被重定向为 <code>define</code>宏替换:</p>
<pre><code class="language-c">// 示例:printf被重定向到USB输出函数
#define printfUSB_Printf_HID
</code></pre>
<p><code>define</code>的作用:在预处理阶段,将代码中所有 <code>printf</code>替换为 <code>USB_Printf_HID</code>。</p>
<h3>1.4 格式化字符串语法</h3>
<h4>1.4.1 基本结构</h4>
<pre><code class="language-c">printf("格式控制字符串", 参数列表);
</code></pre>
<h4>1.4.2 字符类型</h4>
<ol>
<li>
<p><strong>普通字符</strong>:原样输出</p>
<pre><code class="language-c">printf("Hello World\n");// 输出:Hello World(并换行)
</code></pre>
</li>
<li>
<p><strong>转换说明</strong>:以 <code>%</code>开头,用于插入变量值</p>
<ul>
<li><code>%s</code>:输出字符串</li>
<li><code>%d</code>:以十进制整数形式输出</li>
<li><code>%u</code>:无符号十进制整数</li>
</ul>
</li>
<li>
<p><strong>转义字符</strong>:</p>
<ul>
<li><code>\n</code>:换行符(Newline)</li>
<li><code>\t</code>:水平制表符(Tab,通常=4个空格)</li>
<li><code>\\</code>:输出反斜杠本身</li>
<li><code>%%</code>:输出百分号本身(转义)</li>
</ul>
</li>
</ol>
<h4>1.4.3 参数数量规则</h4>
<p><strong>N个转换说明符 = N+1个参数</strong>(格式字符串本身算第一个参数)</p>
<pre><code class="language-c">// 正确示例:3个%对应3个额外参数,共4个参数
printf("今天是%d年%d月%d日", 2025, 11, 9);// 输出:今天是2025年11月9日
</code></pre>
<h4>1.4.4 实战示例</h4>
<pre><code class="language-c">// 接收到数据后原样返回并打印自定义信息
if (接收标志) {
USB_SendData(接收缓冲区);// 原路返回数据
printf("STC YYDS\n"); // 打印自定义字符串
清空接收区();
}
</code></pre>
<h3>1.5 串口调试技巧</h3>
<h4>1.5.1 文本模式 vs 十六进制模式</h4>
<ul>
<li><strong>文本模式</strong>:直接显示ASCII字符</li>
<li><strong>十六进制模式</strong>:将每个字符转换为HEX值显示</li>
</ul>
<p>示例:</p>
<pre><code>文本模式显示:STC YYDS
十六进制模式:53 54 43 20 59 59 44 53
(53='S', 54='T', 43='C', 20='空格', 59='Y', 44='D')
</code></pre>
<p><strong>应用场景</strong>:调试数值变量时,十六进制模式更直观。</p>
<h4>1.5.2 免冷启动下载配置</h4>
<ol>
<li>下载软件中勾选三个关键选项(自动下载相关)</li>
<li>选择生成的 <code>.obj</code>文件</li>
<li>选择24MHz主频</li>
<li>识别USB-CDC虚拟串口号(如COM3)</li>
<li><strong>波特率任意设置</strong>(USB-CDC不受波特率限制)</li>
</ol>
<blockquote>
<p><strong>警告</strong>:printf不宜放在无延时的循环外部,可能导致系统崩溃。</p>
</blockquote>
<h2>2、数的进制系统</h2>
<h3>2.1 进制转换方法</h3>
<h4>通用公式</h4>
<p>任意N进制数 → 十进制:</p>
<pre><code>数值 = 每位数字 × N^位权 的总和
</code></pre>
<h4>2.1.1 二进制 → 十进制</h4>
<p>示例:<code>1011b</code></p>
<pre><code>1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8 + 0 + 2 + 1 = 11
</code></pre>
<h4>2.1.2 十六进制 → 十进制</h4>
<p>示例:<code>0x3A4</code></p>
<pre><code>3×16² + 10×16¹ + 4×16⁰ = 768 + 160 + 4 = 932
</code></pre>
<h3>2.2 快捷转换工具</h3>
<p><strong>推荐使用程序员计算器</strong>:</p>
<ul>
<li>十进制83 → 十六进制0x53</li>
<li>十六进制0x53 → 十进制83</li>
</ul>
<h3>2.3 十六进制对照表(必须熟记)</h3>
<table>
<thead>
<tr>
<th>十进制</th>
<th>十六进制</th>
<th>十进制</th>
<th>十六进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0x0</td>
<td>8</td>
<td>0x8</td>
</tr>
<tr>
<td>1</td>
<td>0x1</td>
<td>9</td>
<td>0x9</td>
</tr>
<tr>
<td>2</td>
<td>0x2</td>
<td>10</td>
<td>0xA</td>
</tr>
<tr>
<td>3</td>
<td>0x3</td>
<td>11</td>
<td>0xB</td>
</tr>
<tr>
<td>4</td>
<td>0x4</td>
<td>12</td>
<td>0xC</td>
</tr>
<tr>
<td>5</td>
<td>0x5</td>
<td>13</td>
<td>0xD</td>
</tr>
<tr>
<td>6</td>
<td>0x6</td>
<td>14</td>
<td>0xE</td>
</tr>
<tr>
<td>7</td>
<td>0x7</td>
<td>15</td>
<td>0xF</td>
</tr>
</tbody>
</table>
<h2>3、C语言基本数据类型</h2>
<h3>3.1 类型长度与范围(32位编译器)</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>位数</th>
<th>有符号范围</th>
<th>无符号范围</th>
<th>常用宏定义</th>
</tr>
</thead>
<tbody>
<tr>
<td>char</td>
<td>8</td>
<td>-128 ~ 127</td>
<td>0 ~ 255</td>
<td><code>int8_t</code> / <code>U8</code></td>
</tr>
<tr>
<td>short</td>
<td>16</td>
<td>-32768 ~ 32767</td>
<td>0 ~ 65535</td>
<td><code>int16_t</code> / <code>U16</code></td>
</tr>
<tr>
<td>int</td>
<td>16/32</td>
<td>依赖编译器</td>
<td>0 ~ 65535/2³²-1</td>
<td><code>int</code> / <code>U32</code></td>
</tr>
<tr>
<td>long</td>
<td>32</td>
<td>-2³¹ ~ 2³¹-1</td>
<td>0 ~ 2³²-1</td>
<td><code>int32_t</code></td>
</tr>
<tr>
<td>float</td>
<td>32</td>
<td>IEEE 754浮点</td>
<td>-</td>
<td><code>float</code></td>
</tr>
<tr>
<td>double</td>
<td>64</td>
<td>IEEE 754浮点</td>
<td>-</td>
<td><code>double</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>关键提示</strong>:32位编译器默认不支持64位变量,使用 <code>double</code>需在文件头添加特殊编译指令(如 <code>#pragma pack(8)</code>)。</p>
</blockquote>
<h3>3.2 变量定义语法</h3>
<pre><code class="language-c">数据类型 变量名;
</code></pre>
<p>示例:</p>
<pre><code class="language-c">unsigned char x; // 定义无符号字符变量x(0-255)
int y; // 定义整型变量y
</code></pre>
<h3>3.3 类型简化技巧(宏定义)</h3>
<p>使用 <code>define</code>简化复杂类型声明:</p>
<pre><code class="language-c">#define U8unsigned char // 定义U8代替unsigned char
#define U16 unsigned int // 定义U16代替unsigned int
// 后续可简洁定义变量
U8 x = 20; // 等效于 unsigned char x = 20;
U16 y = 1000; // 等效于 unsigned int y = 1000;
</code></pre>
<h3>3.4 变量作用域规则</h3>
<pre><code class="language-c">// 全局变量:定义在最外层大括号,整个文件可见
U8 global_var = 100;
void function() {
// 局部变量:仅在本大括号内有效
U8 local_var = 50;
if (1) {
U8 inner_var = 10;// 仅在if块内有效
}
// inner_var在此已失效
}
</code></pre>
<p><strong>核心原则</strong>:大括号 <code>{}</code>界定变量生命周期。</p>
<h2>4、C语言常用运算符</h2>
<h3>4.1 算术运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>说明</th>
<th>示例(X=20, Y=10)</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>+</code></td>
<td>加法</td>
<td><code>X + Y</code></td>
<td>30</td>
</tr>
<tr>
<td><code>-</code></td>
<td>减法</td>
<td><code>X - Y</code></td>
<td>10</td>
</tr>
<tr>
<td><code>*</code></td>
<td>乘法</td>
<td><code>X * Y</code></td>
<td>200</td>
</tr>
<tr>
<td><code>/</code></td>
<td>整数除法</td>
<td><code>X / Y</code></td>
<td>2</td>
</tr>
<tr>
<td><code>%</code></td>
<td>取模(余数)</td>
<td><code>X % Y</code></td>
<td>0</td>
</tr>
<tr>
<td><code>++</code></td>
<td>自增</td>
<td><code>X++</code> → X=21</td>
<td>-</td>
</tr>
<tr>
<td><code>--</code></td>
<td>自减</td>
<td><code>X--</code> → X=19</td>
<td>-</td>
</tr>
</tbody>
</table>
<p><strong>关键特性</strong>:</p>
<ul>
<li>除法 <code>/</code>结果只保留整数部分</li>
<li>取模 <code>%</code>获取除法余数</li>
<li><strong>避免溢出</strong>:8位×8位可能溢出,建议强制转换为16位</li>
</ul>
<pre><code class="language-c">U8 x = 200, y = 10;
U16 result = (U16)x * y;// 强制转换防止溢出
// 错误示例:U8 result = x * y; // 结果会错误(2000>255)
</code></pre>
<h3>4.2 关系运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>说明</th>
<th>示例(X=20, Y=10)</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>==</code></td>
<td>等于</td>
<td><code>X == Y</code></td>
<td>假(0)</td>
</tr>
<tr>
<td><code>!=</code></td>
<td>不等于</td>
<td><code>X != Y</code></td>
<td>真(1)</td>
</tr>
<tr>
<td><code>></code></td>
<td>大于</td>
<td><code>X > Y</code></td>
<td>真(1)</td>
</tr>
<tr>
<td><code><</code></td>
<td>小于</td>
<td><code>X < Y</code></td>
<td>假(0)</td>
</tr>
<tr>
<td><code>>=</code></td>
<td>大于等于</td>
<td><code>X >= Y</code></td>
<td>真(1)</td>
</tr>
<tr>
<td><code><=</code></td>
<td>小于等于</td>
<td><code>X <= Y</code></td>
<td>假(0)</td>
</tr>
</tbody>
</table>
<p><strong>核心原则</strong>:结果为<strong>真(1)<strong>或</strong>假(0)</strong>,0为假,非0为真。</p>
<h3>4.3 逻辑运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>说明</th>
<th>示例(A=5, B=10)</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&&</code></td>
<td>逻辑与</td>
<td><code>(A>0) && (B>0)</code></td>
<td>真(1)</td>
</tr>
<tr>
<td>`</td>
<td></td>
<td>`</td>
<td>逻辑或</td>
</tr>
<tr>
<td><code>!</code></td>
<td>逻辑非</td>
<td><code>!A</code></td>
<td>假(0)</td>
</tr>
</tbody>
</table>
<p><strong>运算规则</strong>:</p>
<ul>
<li><code>&&</code>:两边都为真才是真</li>
<li><code>||</code>:一边为真就是真</li>
<li><code>!</code>:真变假,假变真</li>
</ul>
<h3>4.4 位运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>说明</th>
<th>示例(按位操作)</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&</code></td>
<td>按位与</td>
<td><code>0101 & 1100 = 0100</code></td>
</tr>
<tr>
<td>`</td>
<td>`</td>
<td>按位或</td>
</tr>
<tr>
<td><code>^</code></td>
<td>按位异或</td>
<td><code>0101 ^ 1100 = 1001</code></td>
</tr>
<tr>
<td><code>~</code></td>
<td>按位取反</td>
<td><code>~0101 = 1010</code></td>
</tr>
<tr>
<td><code><<</code></td>
<td>左移</td>
<td><code>0101 << 1 = 1010</code>(低位补0)</td>
</tr>
<tr>
<td><code>>></code></td>
<td>右移</td>
<td><code>0101 >> 1 = 0010</code>(高位补0)</td>
</tr>
</tbody>
</table>
<p><strong>记忆口诀</strong>:</p>
<ul>
<li>按位与 <code>&</code>:<strong>全1为1,有0为0</strong></li>
<li>按位或 <code>|</code>:<strong>有1为1,全0为0</strong></li>
<li>按位异或 <code>^</code>:<strong>相同为0,相异为1</strong></li>
</ul>
<h3>4.5 赋值运算符</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>等价形式</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>=</code></td>
<td>直接赋值</td>
<td><code>C = A + B</code></td>
</tr>
<tr>
<td><code>+=</code></td>
<td><code>C = C + A</code></td>
<td><code>C += A</code></td>
</tr>
<tr>
<td><code>-=</code></td>
<td><code>C = C - A</code></td>
<td><code>C -= A</code></td>
</tr>
<tr>
<td><code>*=</code></td>
<td><code>C = C * A</code></td>
<td><code>C *= A</code></td>
</tr>
<tr>
<td><code>/=</code></td>
<td><code>C = C / A</code></td>
<td><code>C /= A</code></td>
</tr>
<tr>
<td><code>%=</code></td>
<td><code>C = C % A</code></td>
<td><code>C %= A</code></td>
</tr>
</tbody>
</table>
<h3>4.6 条件运算符</h3>
<pre><code class="language-c">条件 ? 表达式1 : 表达式2
</code></pre>
<p><strong>逻辑</strong>:条件为真执行表达式1,否则执行表达式2。</p>
<h3>4.7 if-else控制结构</h3>
<pre><code class="language-c">if (条件表达式) {
// 条件为真(非0)时执行
printf("条件为真\n");
} else {
// 条件为假(0)时执行
printf("条件为假\n");
}
</code></pre>
<p><strong>关键要点</strong>:</p>
<ul>
<li>条件可以是任意表达式(算术、关系、逻辑运算)</li>
<li><strong>else必须与if配对使用</strong>,不能单独存在</li>
<li>代码块需用英文大括号 <code>{}</code>包围</li>
<li>遵循<strong>缩进原则</strong>提高可读性</li>
</ul>
<h2>5、注意事项</h2>
<h3>5.1 数据溢出陷阱</h3>
<pre><code class="language-c">// 错误示例:8位变量溢出
U8 x = 200; // 最大值255
U8 y = 10;
U8 result = x * y;// 200*10=2000 > 255,结果错误为208
// 正确做法:强制类型转换
U16 result = (U16)x * y;// 使用16位变量存储
</code></pre>
<p><strong>溢出特征</strong>:当计算结果超过变量类型最大值时,高位数据丢失,无法恢复。</p>
<h3>5.2 调试技巧总结</h3>
<ol>
<li><strong>printf定位</strong>:在关键位置打印变量值</li>
<li><strong>十六进制模式</strong>:调试数值变量更直观</li>
<li><strong>格式符组合</strong>:<code>%d</code>整数、<code>%u</code>无符号、<code>%s</code>字符串必须掌握</li>
<li><strong>转义字符</strong>:<code>\n</code>换行、<code>\t</code>制表符规范输出格式</li>
</ol>
<table>
<thead>
<tr>
<th>知识点</th>
<th>核心内容</th>
<th>常见错误</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>printf</strong></td>
<td>重定向到USB-CDC,格式符%对应参数</td>
<td>中文引号、参数数量不匹配</td>
</tr>
<tr>
<td><strong>进制转换</strong></td>
<td>2^位权求和,熟记0-F对照</td>
<td>忘记16的幂次计算</td>
</tr>
<tr>
<td><strong>数据类型</strong></td>
<td>U8=8位, U16=16位, 注意范围</td>
<td>溢出导致数据错误</td>
</tr>
<tr>
<td><strong>运算符</strong></td>
<td>/取整, %取余, &&||逻辑, &|位运算</td>
<td>混淆逻辑与位运算</td>
</tr>
<tr>
<td><strong>if语句</strong></td>
<td>0为假, 非0为真, else配if</td>
<td>使用中文符号, 作用域混乱</td>
</tr>
</tbody>
</table>
<h1>七、GPIO与按键</h1>
<h3>1、认识GPIO:从点灯开始</h3>
<p>GPIO是通用输入输出端口的缩写,说白了就是能控制高低电平的引脚。</p>
<h3>1.1 高低电平不是想多高就多高</h3>
<p><strong>电压极限参数(划重点!)</strong></p>
<ul>
<li>VCC对地电压最大 <strong>5.5V</strong>(实测我的实验箱是3.3V供电)</li>
<li>IO口电压不能超过 <strong>VCC + 0.3V</strong>
<ul>
<li>3.3V供电时,IO口别超过3.6V</li>
<li>5V供电时,IO口别超过5.3V</li>
</ul>
</li>
</ul>
<h3>1.2 GPIO的四种工作模式</h3>
<table>
<thead>
<tr>
<th>模式</th>
<th>我的理解</th>
<th>应用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>准双向口</strong></td>
<td>能进能出,但输出电流小(拉电流只有μA级)</td>
<td><strong>最常用</strong>,默认就是这个</td>
</tr>
<tr>
<td><strong>推挽输出</strong></td>
<td>输出能力贼强,灌电流拉电流都能到20mA</td>
<td>驱动LED、蜂鸣器</td>
</tr>
<tr>
<td><strong>高阻输入</strong></td>
<td>只读模式,相当于悬空</td>
<td>读模拟信号</td>
</tr>
<tr>
<td><strong>开漏输出</strong></td>
<td>只能输出低电平,高电平靠外部上拉</td>
<td>I2C通信</td>
</tr>
</tbody>
</table>
<p>这里有个坑:准双向口的拉电流很小,想直接驱动LED亮度不够,得用推挽输出或者像实验箱那样用灌电流方式(LED负极接IO口)。</p>
<h3>1.3 施密特触发器:电平判定的"缓冲带"</h3>
<p><strong>输入电平判定标准(3.3V供电)</strong></p>
<ul>
<li><strong>低电平</strong>:必须 <strong>< 0.99V</strong> (开了施密特触发器的情况下)</li>
<li><strong>高电平</strong>:必须 <strong>> 1.09V</strong></li>
</ul>
<p>这个"滞后效应"就是施密特触发器的作用,防止电压在阈值附近抖动造成误判。AI8051U上电默认是开启施密特触发器的,所以直接按手册的0.99V/1.09V判断就行。</p>
<hr />
<h2>2、按键检测:从理论到代码</h2>
<h3>2.1 按键电路其实很简单</h3>
<p>实验箱上的按键电路长这样:</p>
<pre><code>VCC ────┬── P32
│
[按键]
│
GND
</code></pre>
<p>没按下时,P32被上拉电阻拉到高电平(3.3V);按下后,直接接地变成低电平(0V)。所以检测按键就是判断IO口是不是等于0。</p>
<h3>2.2 第一个任务:按下亮,松开灭</h3>
<p>照着视频敲的第一版代码:</p>
<pre><code class="language-c">void main() {
P40 = 0;// 打开LED总开关(这个坑后面讲)
while(1) {
if (P32 == 0) { // 按键按下
P00 = 0; // LED亮
} else {
P00 = 1; // LED灭
}
}
}
</code></pre>
<h3>2.3 第二个任务:按下灭,松开亮</h3>
<p>这个简单,把 <code>if-else</code>里的内容互换就行。</p>
<pre><code class="language-c">// 方式一:直接取反
P00 = !P00;// 原来是0变1,是1变0
// 方式二:条件判断
if (P32 == 0) {
P00 = 1; // 按下灭
} else {
P00 = 0; // 松开亮
}
</code></pre>
<p>两种方式效果一样,但方式一更简洁。</p>
<hr />
<h2>4、按键消抖:</h2>
<h3>4.1 现象:按一下灯闪个不停</h3>
<p>写到第三个任务"按一下切换状态"时,问题出现了。我的代码:</p>
<pre><code class="language-c">U8 state = 0;
void main() {
while(1) {
if (P32 == 0) {
state = !state;// 状态取反
P00 = state;
}
}
}
</code></pre>
<p>结果按一下按钮,灯疯狂闪烁,用串口打印发现 <code>state</code>在0和1之间乱跳。这就是<strong>按键抖动</strong>!</p>
<h3>4.2 消抖方案:延时+二次确认</h3>
<p>正确的消抖流程应该是:</p>
<ol>
<li>检测到按键按下(低电平)</li>
<li><strong>延时20ms</strong>(跳过抖动期)</li>
<li>再次检测,如果还是按下,才认为是有效按键</li>
<li>执行操作后,<strong>等待按键松开</strong></li>
</ol>
<pre><code class="language-c">void main() {
// 初始化代码...
while(1) {
if (P32 == 0) { // 第一次检测
delay_ms(20); // 消抖延时
if (P32 == 0) { // 第二次确认
state = !state;
P00 = state;
while(P32 == 0); // 等待松开(这个最关键!)
}
}
}
}
</code></pre>
<h3>4.3 延时函数怎么来?</h3>
<p><strong>步骤</strong>:</p>
<ol>
<li>打开AICube-ISP → 工具 → <strong>软件延时计算器</strong></li>
<li>系统主频:<strong>24MHz</strong>(必须和工程一致)</li>
<li>延时时间:<strong>20毫秒</strong></li>
<li>指令集:<strong>AR32</strong>(32位选这个,8位选AR8)</li>
<li>点击<strong>生成C代码</strong></li>
</ol>
<p>生成的函数长这样:</p>
<pre><code class="language-c">void delay_ms(unsigned int ms) {
// 一堆汇编指令,反正很准
}
</code></pre>
<p><strong>重要提醒</strong>:使用前必须在 <code>main()</code>开头加三行配置:</p>
<pre><code class="language-c">WTST = 0; // 设置指令延时参数最小
EAXFR = 1; // 扩展XFR访问使能
CKCON = 0; // 提高XRAM访问速度
</code></pre>
<p>这三行在官方例程里都有,直接复制就行。</p>
<hr />
<h2>4、三个任务完整代码</h2>
<h3>任务1:按下亮,松开灭</h3>
<pre><code class="language-c">#include "AI8051U.H"
void main() {
// 初始化三件套
WTST = 0;
EAXFR = 1;
CKCON = 0;
P40 = 0;// 打开LED开关
while(1) {
if (P32 == 0) { // 按下
P00 = 0; // 亮
} else { // 松开
P00 = 1; // 灭
}
}
}
</code></pre>
<h3>任务2:按下灭,松开亮</h3>
<pre><code class="language-c">// 只改if-else内部
if (P32 == 0) {
P00 = 1;// 按下灭
} else {
P00 = 0;// 松开亮
}
</code></pre>
<h3>任务3:按一下切换(带消抖)</h3>
<pre><code class="language-c">#include "AI8051U.H"
#include "intrins.h"// 包含nop
#define U8 unsigned char
U8 state = 0;
// 延时函数声明
void delay_ms(unsigned int ms);
void main() {
// 初始化三件套
WTST = 0;
EAXFR = 1;
CKCON = 0;
P40 = 0;// 打开LED开关
while(1) {
if (P32 == 0) { // 检测按下
delay_ms(20); // 消抖
if (P32 == 0) { // 确认按下
state = !state;// 状态翻转
P00 = state; // 更新LED
// 等待松开(防止连击)
while(P32 == 0);
}
}
}
}
// 生成的延时函数
void delay_ms(unsigned int ms) {
// 自动生成的代码,别手打
}
</code></pre>
页:
[1]