carysun 发表于 2024-12-9 11:13:39

AI8051U学习打卡 | 建议立即赠送 强大的AI8051U实验箱

<h1>AI8051U学习打卡</h1>
<h2>视频教程第一集-序言配套程序</h2>
<h3>一、Ai8051U强在哪?(例程演示)</h3>
<ol>
<li>
<p><strong>屏幕显示和视频播放(flash编程器)。</strong></p>
<ul>
<li>8080的8位并口,TFT屏刷屏演示;</li>
<li>烧录hex(<code>~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\1.试验箱代码AI8051U-DEMO-CODE-V1.2-20240930\37.2-2.4寸ILI9341驱动TFT显示屏实验程序-带触摸功能,硬件I8080接口程序\obj</code>),频率40MHz,屏幕校准,手写。</li>
<li>flash演示 <code>~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\1.试验箱代码AI8051U-DEMO-CODE-V1.2-20240930\72.4-QSPI-TFT_DMA_P2P外设到外设_显示视频级动画效果程序-ILI9341</code>
<ul>
<li>ISP - 工具 - 串行Flash编辑器</li>
<li>导入图片,上电运行。</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>IIS录放音。</strong></p>
<ul>
<li>录放音演示,频率要改为36.864MHz</li>
<li><code>~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\1.试验箱代码AI8051U-DEMO-CODE-V1.2-20240930\82-I2S接口-数字录放音-存储在FLASH中-TLV320AIC23B\obj</code></li>
</ul>
</li>
<li>
<p><strong>PWM_DMA</strong></p>
<ul>
<li>主频40MHz</li>
<li><code>~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\AI8051U-PWMA-DMA-直接驱动WS2812彩灯\AI8051U-PWMA-DMA-直接驱动WS2812彩灯\obj</code></li>
<li>需要WS2812点阵屏,8×32LED、涂色亚克力面板、硫酸纸或散光膜</li>
<li>STC32G的话可以用SPI+DMA实现。Ai8051U用PWM_DMA可实现1000多颗WS2812灯珠显示,且抗干扰。</li>
</ul>
</li>
</ol>
<hr />
<ol start="4">
<li>
<p><strong>频谱分析仪(上位机)</strong></p>
<ul>
<li>演示程序:`~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\1.试验箱代码AI8051U-DEMO-CODE-V1.2-20240930\73-频谱分析256点FFT-USB-CDC送电脑ISP软件调试接口FFT绘图显示</li>
<li>ISP - 调试仿真接口 - FFT绘图</li>
<li>使用IIC录音</li>
</ul>
</li>
<li>
<p><strong>手写计算器</strong></p>
<ul>
<li><code>~\Ai8051U视频教程第一集-序言配套程序\01.序\演示代码和视频\手写输入计算器-Ai8051U-20241117.mp4</code></li>
</ul>
</li>
<li>
<p><strong>QSPI,PWM移相,硬件乘除,单精度浮点</strong></p>
</li>
</ol>
<h3>二、关于Ai8051U</h3>
<p>Ai8051U,USB 型 1T 8051,支持32位和8位指令集, RMB2.3</p>
<p>管脚兼容天王级别的: 89C52RC, 12C5A60S2</p>
<p>要兼容 8位8051指令集, 可以用 Keil C51/IAR/SDCC 编译器</p>
<p>===就相当于更强大的 8H8K64U</p>
<p>要兼容 32位8051指令集,可以用 Keil C251 编译器,双核兼容设计</p>
<p>===就相当于更强大的 32G12K128, 32G8K64</p>
<p>34K SRAM(2K edata, 32K xdata), 64K Flash</p>
<p>TFPU@120MHz, 硬件浮点/硬件三角函数 运算器</p>
<p>DMA支持PWM, DMA支持外设直接到外设, P2P</p>
<p>120MHz-PWM支持硬件移相,16位PWM; 真12位ADC</p>
<p>USB, 4组串口,12位ADC, 轨到轨比较器</p>
<p>QSPI, SPI, I2S, I2C, TFT-i8080/M6800 接口</p>
<p>PDIP40,LQFP44,LQFP48</p>

carysun 发表于 2024-12-9 11:56:16

<h2>第二集-硬件及工具介绍配套程序</h2>
<h3>一、试验箱介绍</h3>
<p>**Ai8051U 实验箱V1.2**</p>
<p><strong>包括:</strong></p>
<ul>
<li><strong>透明盒子×1个</strong></li>
<li><strong>开发板×1块</strong></li>
<li><strong>跳线帽(短路帽)×1组</strong></li>
<li><strong>USB-TypeA数据线×1条,用于烧录程序</strong></li>
</ul>
<p><strong>可以从官网下载使用说明书。官网地址:</strong><a href="https://stcai.com/syx">深圳国芯人工智能有限公司-实验箱</a></p>
<blockquote>
<p><strong>PCB</strong></p>
<p><strong>PCBA:‌*</strong>*PCBA(Printed Circuit Board Assembly)指的是印刷电路板组件**‌。</p>
</blockquote>
<h3>二、关于开发板</h3>
<p><strong>官网下载Ai8051U芯片手册。</strong></p>
<ul>
<li><strong>USB Link1D接口;官方发行的下载烧录调试工具;见说明书:五</strong></li>
<li><strong>USBTypeA、TypeC烧录接口</strong></li>
<li><strong>USB转双串口,也是TypeC的;STC自己的芯片(AI8H2K12U)波特率最高10Mb/S,在背面,可以替换CH340。主流、主推。</strong>
<blockquote>
<p><strong>89C52RC的UART案例选用的波特率是9600bits/8=1200Bytes/S</strong></p>
<p><strong>现在的PC基本都不再提供串口,因此需要使用一个USB转串口的芯片(CH340N、CH340K)来实现PC与单片机的通讯</strong></p>
</blockquote>
</li>
<li><strong>TF卡插座,可以用于跑文件系统。</strong></li>
<li><strong>示波器BNC输入;需要表笔连接;右边红色电容用于调节波形。</strong></li>
<li><strong>立体声线路输出(上,接音响)、立体声耳机输出(下)</strong></li>
<li><strong>话筒录音</strong></li>
<li><strong>OLED屏接口</strong></li>
<li><strong>8路流水灯</strong></li>
<li><strong>8位数码管</strong></li>
<li><strong>TFT彩屏</strong></li>
<li><strong>掉电检测电压调节,用于在掉电之前保存用户数据</strong></li>
<li><strong>红外接收</strong></li>
<li><strong>矩阵键盘,横向用4个引脚、纵向用2个引脚控制8个按键。</strong>
<blockquote>
<p><strong>板子上的按键都是轻触开关</strong></p>
</blockquote>
</li>
<li><strong>ADC键盘,1个引脚控制16个按键。</strong></li>
<li><strong>T0、T1按键</strong></li>
<li><strong>INT0、INT1按键</strong></li>
<li><strong>电源按键、复位按键,ISP下载,参见说明书【电源按键】</strong></li>
<li><strong>QSPI / SPI Flash,猫和老鼠的照片存在这上面,MCU读取然后显示到TFT上。</strong></li>
<li>**LCD插口和对比度调节电位器(<strong><strong>可调电阻器</strong>)</strong> **<img src="file:///D:/%E5%B5%8C%E5%85%A5%E5%BC%8F/AI8051U/assets/image-20241208161117634.png?lastModify=1733716395" alt="image-20241208161117634" /></li>
<li><strong>RTC电池,TypeA掉电时为MCU RTC(Real-time Clock)供电</strong></li>
</ul>
<h4>背面</h4>
<ul>
<li><strong>32.768KHz无源晶振</strong>
<blockquote>
<p><strong>2^15=32768</strong></p>
<p><strong>对比STC89C52RC用的是</strong></p>
<ol>
<li>**11.0592MHz **无源晶振,11.0592MHz能够整除UART的多种波特率所需的分母。如:11_<strong>059</strong>_<strong>200/9600=1152</strong></li>
<li><strong>或12MHz。</strong></li>
</ol>
<p><strong>对于定时器,分频可选两种,分别是12分频和6分频,默认是12分频。系统的时钟频率为11.0592MHz时,按默认分频,计数脉冲的频率是11059200/12 Hz,因此一个计数脉冲的时间是12/11059200 s,大约是1.08us。</strong></p>
<p><strong>如果采用12MHz的晶振,一个计数脉冲的时间是12/12MHz=1us,(定时1ms,计数器加1000次,每次耗时1us)由此可见,对于没有UART等通信波特率需求时,使用12MHz的晶振对于计时更加精确。</strong></p>
</blockquote>
</li>
<li><strong>24C02 EEPROM</strong>
<blockquote>
<p><strong>可以用于防止MCU烧掉时,数据被毁。</strong></p>
<p><strong>EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦写可编程只读存储器)是一种非易失性存储器(断电后仍能保留数据),可以多次写入和擦除数据。EEPROM广泛应用于需要永久存储数据的电子设备中, 常用于存储设备工作模式、用户偏好设置、关键参数等信息。</strong></p>
<p>**STC89C52RC 时用的即是 **<strong>AT24C02CN</strong>,存储容量为2Kb = 256Bytes(2048位,256字节),采用I2C协议进行读写。</p>
</blockquote>
</li>
<li><strong>DS18B20 温度传感器</strong>
<blockquote>
<p><strong>STC89C52用的是同款。</strong></p>
<p><strong>低成本、高精度。</strong></p>
</blockquote>
</li>
<li><strong>无源蜂鸣器</strong></li>
<li><strong>SP3485,485通信</strong></li>
<li><strong>AI8H2K12U,USB转双串口。板子上用的像是第三款16脚的这种。立创商城¥2.3。</strong></li>
</ul>
<h3>三、软件环境</h3>
<h4>1 安装keil</h4>
<ul>
<li>
<p><strong>Keil官网下载C251;</strong></p>
</li>
<li>
<p><strong>或安装</strong> <code>~\Ai8051U视频教程第二集-硬件及工具介绍配套程序\02.硬件及工具介绍\网友推荐的Keil-C51C251环境搭建</code>,其中C251和官网最新版是一样的;</p>
</li>
<li>
<p><strong>有版权问题,需要破解。</strong></p>
</li>
<li>
<p><strong>已安装过keil C51、MDK,观察Keil版本为5.38.0.0。使用最新版的C251安装后,版本居然降低为5.25.3.0。</strong></p>
<ul>
<li><strong>安装并激活后,打开Keil报错:</strong><code>*** Warning: Registered ARM Compiler ignored, Version needs to be 5 or higher. Path:'ARMCLANG' !</code></li>
<li><strong>最终下载最新版的MDK,并覆盖安装解决问题。问题处理后版本5.41.0.0。</strong></li>
</ul>
</li>
</ul>
<h5>安装后比较ARM、C51、C251文件夹的大小</h5>
<ul>
<li><strong>ARM:2.95 GB;另</strong> <code>C:\Users\xxx\AppData\Local\Arm\Packs</code> &gt; 1GB</li>
<li><strong>C51:202 MB</strong></li>
<li><strong>C251:48.6MB</strong></li>
</ul>
<h4>2 ISP</h4>
<p><strong>官网(</strong><a href="https://stcai.com/">https://stcai.com/</a>)下载。ISP始终使用最新版即可!</p>
<p><strong>下载最新版****AIapp-ISP-V6.95A 版</strong></p>
<blockquote>
<p><strong>我需要替换原 :stcai-isp-v6.94H.exe 文件修改时间是2024/7/9</strong></p>
<p><strong>老师视频中课件的版本是:6.94X,视频制作时的版本是6.94Y,bilibili视频更新时间2024-11-21 00:26:11,当前学习时间是2024/12/08 17:18</strong></p>
<p><strong>版本从旧到新依次是:94H、94X、94Y、95A</strong></p>
</blockquote>
<h4>3 添加头文件</h4>
<p><img src="file:///D:/%E5%B5%8C%E5%85%A5%E5%BC%8F/AI8051U/assets/image-20241208215137451.png?lastModify=1733716395" alt="image-20241208215137451" /></p>
<h4>4 代码包</h4>
<p><strong>官网(</strong><a href="https://stcai.com/">https://stcai.com/</a>)下载 <strong>试验箱代码包</strong>包含:</p>
<ul>
<li><strong>手册</strong></li>
<li><strong>原理图</strong></li>
<li><strong>Keil 中断拓展插件,文件</strong> <code>拓展Keil的C代码中断号.zip</code>,解压,exe需要安装。<br />
<strong>keil C51/C251 编译器只支持31以内中断号,超过31编译报错。热心网友提供的简单拓展工具,可将中断号拓展到254</strong></li>
</ul>
<h3>四、ISP USB方式烧录程序</h3>
<p><strong>1、 使用 USB 线将实验箱与电脑进行连接</strong></p>
<p><strong>2、 打开 AIapp-ISP-v6.94R 下载软件</strong></p>
<p><strong>3、 选择单片机型号为“AI8051U-34K64”,打开需要下载的用户程序</strong></p>
<p><strong>4、 实验箱使用硬件 USB 接口下载。进入 USB 下载模式需要</strong></p>
<ul>
<li><strong>先</strong>==按住==实验箱上的 <code>P3.2/INT0</code> 按键 / 接地</li>
<li><strong>然后按一下 ON/OFF 电源按键 / 断电,接着松开 ON/OFF 电源按键/上电,</strong></li>
<li>**最后可松开 **<code>P3.2/ INT0</code> 按键。</li>
</ul>
<p><strong>** 正常情况下就能识别出“STC USB Writer (HID1)”设备**</strong></p>
<p><strong>5、 【打开程序文件】</strong></p>
<p><strong>6、选择的芯片型号、串口、CPU指令模式(32-Bit)、IRC频率,点击 STC-ISP 下载软件中的【下载 / 编程】按钮。</strong></p>
<p><strong>选24MHz原因</strong></p>

carysun 发表于 2024-12-10 10:51:33

<h1>第三讲 点亮第一颗LED</h1>
<h2>一、Keil创建工程</h2>
<h3>1. 创建工程(参考芯片手册6.5章节)</h3>
<ul>
<li>
<p><strong>手工创建Project目录</strong></p>
</li>
<li>
<p><strong>选择芯片Device:</strong><code>STC MCU Database</code> - <code>AI8051U-32Bit Series</code></p>
</li>
<li>
<p><strong>CPU Mode: 魔法棒图标,打开</strong> <code>Options for Target</code>窗口,<code>CPU Mode</code>改为 <code>Source (251 native)</code></p>
</li>
<li>
<p>**勾选 **<code>Byte Interrupt Frame Size</code></p>
<blockquote>
<p><strong>80251 的指令模式有“Binary”和“Source”两种模式,Ai8051U系列目前只支持“Source”模式</strong></p>
<p><strong>由于 Ai8051U 系列单片机在中断中的压栈和出栈都是4字节模式,建议“4 Byte Interrupt Frame Size”选项也打上钩</strong></p>
</blockquote>
</li>
<li>
<p><code>Memory Model</code> 选择 <code>XSmall: near vars, far const, ptr-4</code>模式。</p>
<blockquote>
<p><strong>80251 的存储器模式,在 Keil 环境下有如下图所示的5种模式。选择理由见芯片手册6.5.5。</strong></p>
</blockquote>
</li>
<li>
<p><code>Code Rom Size</code> 选择 <code>Large: 64K program</code> 或者 <code>Huge: 64 functions, 16M progr.</code></p>
<ul>
<li><strong>代码超64K设置,参考6.5.7(暂时无需修改)</strong></li>
</ul>
</li>
<li>
<p><code>Output</code> Tab页,勾选 <code>Create HEX File</code>;</p>
</li>
<li>
<p><strong>程序空间超过 64K,则</strong> <code>HEX format</code>必须选择“HEX-386”模式,只有程序空间在 64K 以内,“ HEX format”才可选择“HEX-80”模式</p>
</li>
</ul>
<h3>2. 添加头文件(利用ISP、结合芯片手册6.4章节)</h3>
<ul>
<li>
<p><strong>冲哥的方式是使用ISP【头文件】Tab页 功能直接把系统头文件保存到项目里,这样就不会依赖环境,在拷给别人的时候,不会提示找不到头文件。</strong></p>
</li>
<li>
<p><strong>模块化开发的方法</strong></p>
<ul>
<li><strong>使用</strong> <code>Manage Project Items</code>调整 <code>Source Group</code>为模块文件夹,并添加模块代码文件(.c、.h等)。</li>
<li><code>Options for Target</code>的 <code>C251</code>页签中添加 <code>Include Paths</code>以便在程序中引入头文件。</li>
</ul>
</li>
</ul>
<h3>3. 代码编辑,编译</h3>
<ol>
<li><strong>编辑 格式设置,扳手图标 -&gt; 打开</strong> <code>Configuration</code>窗口,<code>Editor</code>选项卡,下面【Tab size】:4,这样Tab键就等于4个空格。</li>
<li><code>Encoding</code>选择 <code>GB2312</code>中文就不会乱码了。</li>
<li><strong>技巧:右键头文件名,</strong><code>Open document &lt;AI8051U.H&gt;</code>、<code>Insert '#include &lt;AI8051U.H&gt;'</code></li>
<li>==下载程序时,看一眼hex的时间,防止弄错。==这里没有用到时钟,所以频率随便选一个(或默认)即可。</li>
<li>**Translate、Build、Rebuild...**<del>编译单个文件,全部编译</del><br />
==问题:为什么编译后会自动下载到MCU里呢?视频里这个效果是什么前提下出现的呢?==</li>
</ol>
<h2>二、LED0点亮原理</h2>
<h3>原理图分析</h3>
<pre><code> P40=0;
 P00=0;
</code></pre>
<h4>AI8051U-34K64-QFP48 (AI8051U-LQFP48)管脚</h4>
<blockquote>
<p><strong>方法一:查看芯片手册;</strong></p>
<p><strong>方法二:查看ISP封装脚位</strong></p>
</blockquote>
<ul>
<li><strong>P00-07,8 pins</strong></li>
<li><strong>P0X-P5X,8*6-2pins,其中没有P54、P55</strong></li>
<li><strong>GND、VCC</strong></li>
<li>==最多IO口数量45,而不是46,我猜少UCAP(P44)。==</li>
</ul>
<h4>SFR</h4>
<ul>
<li>
<p><strong>P0M1:P0口模式配置寄存器1;93H(地址)</strong></p>
<ul>
<li><strong>应该不可位寻址。虽然手册里有 如P02M1的写法,但应该仅是指示作用,程序里使用并不成功,且头文件</strong> <code>AI8051U.H</code>里也找不到。</li>
</ul>
</li>
<li>
<p><strong>P0M0:P0口模式配置寄存器0;94H(地址,低位地址更大)</strong></p>
</li>
<li>
<p><strong>P0、P1...:端口数据寄存器,可位寻址P00、P01</strong></p>
</li>
</ul>
<h4>端口模式</h4>
<table>
<thead>
<tr>
<th><strong>PnM1</strong></th>
<th><strong>PnM0</strong></th>
<th><strong>Pn(I/O)口工作模式</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>0</strong></td>
<td><strong>0</strong></td>
<td><strong>准双向口(弱上拉)(传统8051端口模式,弱上拉)</strong> <strong>灌电流可达20mA,拉电流为270~150uA(存在制造误差)</strong></td>
</tr>
<tr>
<td><strong>0</strong></td>
<td><strong>1</strong></td>
<td><strong>推挽输出(强上拉输出,可达20mA,要加限流电阻)</strong></td>
</tr>
<tr>
<td><strong>1</strong></td>
<td><strong>0</strong></td>
<td><strong>高阻输入(</strong>==电流既不能流入也不能流出==) **默认值(复位值)新款芯片刚上电都是这个模式。**</td>
</tr>
<tr>
<td><strong>1</strong></td>
<td><strong>1</strong></td>
<td><strong>开漏模式(Open-Drain),内部上拉电阻断开开漏模式既可读外部状态也可对外输出(高电平或低电平)。如要正确读外部状态或需要对外输出高电平,需外加上拉电阻,否则读不到外部状态,也对外输不出高电平。</strong> <strong>【开漏工作模式】,对外设置输出为1,等同于【高阻输入】</strong> <strong>【开漏工作模式】,【打开内部上拉电阻或外部加上拉电阻】,简单等同于【准双向口】</strong></td>
</tr>
</tbody>
</table>
<pre><code> P00M1=0;
 P00M0=1;
 ​
 // 冲哥:
 P00M1=0;
 P00M0=0;
</code></pre>
<h3>问题:为什么用==准双向口==,而不用 推挽输出呢???</h3>
<h2>三、ISP - I/O口配置工具</h2>
<p><strong>这种格式居然来自这里。还是习惯把高位写在前面。</strong></p>
<pre><code>     P0M0 = 0x00; P0M1 = 0x00;
     P1M0 = 0x00; P1M1 = 0x00;
     P2M0 = 0x00; P2M1 = 0x00;
     P3M0 = 0x00; P3M1 = 0x00;
     P4M0 = 0x00; P4M1 = 0x00;
     P5M0 = 0x00; P5M1 = 0x00;
     P6M0 = 0x00; P6M1 = 0x00;
     P7M0 = 0x00; P7M1 = 0x00;
</code></pre>

carysun 发表于 2024-12-10 11:15:39

<h1>第四讲 USB不停电下载</h1>
<p><strong>书接上回,这节课解释冲哥是如何做到无需按键自动下载</strong> <code>hex</code>的。</p>
<h2>1.实验对比演示</h2>
<p><strong>ISP程序下载界面打开,冲哥演示不用按键下载程序的方式:</strong></p>
<p><strong>勾选三个选项,波特率选择9600。使用按键断电方式下载一次,之后就直接点击【下载/编程】按钮即可,无需再按键了。</strong></p>
<h2>2.下载所需文件</h2>
<ul>
<li><strong>STC官网-软件工具-库函数-USB库文件。</strong></li>
<li><strong>带USB的单片机(STC32G、STC8H)都可以使用。</strong></li>
<li><strong>关于CDC模式和HID模式,冲哥建议使用CDC。</strong></li>
</ul>
<blockquote>
<p><strong>‌****CDC模式和HID模式的区别主要体现在以下方面</strong>‌:</p>
<ul>
<li><strong>‌****定义与用途</strong>‌:
<ul>
<li>**‌****CDC(Communication Device Class)**‌:通信设备类,用于模拟提供虚拟COM端口UART接口的串行端口,常用于与串口相关的通信应用,如USB转RS232、USB转Ethernet等‌12。</li>
<li>**‌****HID(Human Interface Device)**‌:人机接口设备,通常用于连接键盘、鼠标、游戏控制器等外设,以默认的数据接口形式进行连接和传输数据‌12。</li>
</ul>
</li>
<li><strong>‌****驱动需求</strong>‌:
<ul>
<li><strong>‌****CDC</strong>‌:在Windows 10下通常无需驱动,但在Windows 7及以下系统可能需要驱动‌4。</li>
<li><strong>‌****HID</strong>‌:大多数Windows操作系统下都无需安装驱动,即插即用‌5。</li>
</ul>
</li>
<li><strong>‌****传输速度与效率</strong>‌:
<ul>
<li><strong>‌****CDC</strong>‌:据说从PC传输1字节需要10毫秒,相对较慢‌6。</li>
<li><strong>‌****HID</strong>‌:传输速度相对较快,更适合需要高效数据传输的场合‌6。</li>
</ul>
</li>
<li><strong>‌****兼容性</strong>‌:
<ul>
<li><strong>‌****CDC</strong>‌:通过提供虚拟COM端口,可以兼容旧的应用程序,只需很少的硬件和软件修改‌2。</li>
<li><strong>‌****HID</strong>‌:由于与键盘、鼠标等外设兼容,因此在新设备或新应用中可能更容易被接受和使用。</li>
</ul>
</li>
</ul>
<p><strong>这些区别使得CDC模式和HID模式在各自的应用场景中发挥着不同的作用。</strong></p>
</blockquote>
<h2>3.移植关键部分到工程:</h2>
<h3>3.1 添加头文件</h3>
<ul>
<li>
<p><strong>Interrupt、Query区别</strong></p>
<ul>
<li><code>STC_CDC_INTERRUPT_LIBRARY</code>无论程序执行到哪里都会使用中断马上响应。</li>
<li><code>STC_CDC_QUERY_LIBRARY</code>执行到循环中相应的调用语句时,才响应,冲哥建议这种方式。</li>
</ul>
</li>
<li>
<p><strong>复制</strong> <code>stc_usb_cdc_32.LIB</code>(选择查询模式、cdc模式、32位的lib)、<code>stc32_stc8_usb.h</code>到Project目录中。</p>
<ul>
<li><strong>注意:使用库文件夹里的版本更新(相比DEMO文件夹里的版本)</strong></li>
</ul>
</li>
</ul>
<h4>关于lib文件</h4>
<p><strong>n个</strong> <code>.h</code>+n个(这里是5、6个).<code>c</code>=1个 <code>.lib</code>,==别人打不开,看不到源码,防止修改,提高移植的便捷性。==</p>
<blockquote>
<p><strong>如果你将</strong> <code>.c</code>文件编译成一个静态库(<code>.lib</code>),并且只分发这个库文件和对应的 <code>.h</code>文件,那么其他人确实无法直接查看 <code>.c</code>文件中的源码。他们只能看到 <code>.h</code>文件中声明的接口,而无法看到实现细节。这是保护源码的一种方式。</p>
<p><strong>然而,有几点需要注意:</strong></p>
<ol>
<li><strong>‌****安全性</strong>‌:虽然编译成库文件可以隐藏源码,但它并不能完全防止逆向工程。有足够技术和工具的人仍然可能尝试反编译或分析库文件以获取一些信息。因此,如果你需要更强的源码保护,可能需要考虑其他的安全措施,如代码混淆或加密。</li>
<li><strong>‌****可维护性</strong>‌:将源码编译成库文件后,如果你需要对其进行修改或调试,就必须重新编译。这可能会增加开发和维护的复杂性,特别是当库文件很大或依赖关系复杂时。</li>
<li><strong>‌****可移植性</strong>‌:不同的编译器和平台可能会生成不同的库文件格式,因此你需要确保你的库文件与目标平台兼容。此外,如果其他人想要在不同的平台上使用你的库,他们可能需要获取针对该平台的编译版本。</li>
<li><strong>‌****开源与闭源</strong>‌:分发库文件而不是源码是闭源软件的一种常见做法。然而,开源软件则通常会将源码公开,以便其他人可以查看、修改和分发。选择开源还是闭源取决于你的项目需求、商业模型以及你对源码保护的态度。</li>
</ol>
<p><strong>总之,将</strong> <code>.c</code>文件编译成 <code>.lib</code>文件是一种隐藏源码的有效方法,但它并不能提供绝对的安全保护。在决定是否采用这种方法时,你需要权衡其优缺点并考虑你的具体需求。</p>
</blockquote>
<h4>Keil使用技巧:全局搜索</h4>
<p><strong>双击要搜索的内容,</strong><code>Ctrl+F</code> 或 点击文本+望远镜(以前的版本放大镜)icon。</p>
<h3>3.2 USB初始化函数(lib+.h库实现)</h3>
<h3>3.3 命令参数</h3>
<p><strong>例程代码中的</strong> <code>&quot;@STCISP#&quot;</code>是程序给ISP的一个命令(与ISP设置要匹配),ISP接受到这个字符串,就会自动切换COM口进行下载。</p>
<h3>3.4 打开P_SW2寄存器和IE2寄存器(只打开一位!)</h3>
<pre><code> P_SW2 |= 0x80;  // EAXFR: Enable all XFR
</code></pre>
<h4>相关寄存器说明</h4>
<p><strong>USB的寄存器属于XFR(扩展RAM区特殊功能寄存器)</strong></p>
<p><strong>EAXFR:EA XFR(Enable All XFR)</strong></p>
<pre><code> IE2 |= 0x80;    // EUSB, Enable USB Interruption. 新下载的库文件例程里居然没有这一句,还是冲哥之前的版本(STCAI官网库、例程分开下载那个时候的版本)里有这句,这句没有应该是不行。
 EA = 1;         // Enable All Interruption
</code></pre>
<h3>3.5. L57编译警告处理</h3>
<pre><code> WARNING L57: UNCALLED FUNCTION, IGNORED FOR OVERLAY PROCESS
 NAME :    OLED12864 Displaycontent/uti1
</code></pre>
<p><strong>定义了函数但是没有使用,其实可以忽略。处理方法:</strong></p>
<p><code>Options for Target</code> - <code>L251 Misc</code> - <code>disable Warning Numbers</code>: 57</p>
<h2>4. 不停电下载测试过程</h2>
<ul>
<li>
<p><strong>ISP-“硬件选项”Tab页-【选择CPU指令模式】:32-Bit。</strong></p>
</li>
<li>
<p><strong>勾选左下角【当目标文件变化时自动装载并发送下载命令】</strong></p>
</li>
<li>
<p><strong>“收到用户命令后复位到ISP监控程序区” Tab页,波特率:9600,勾选</strong></p>
<ul>
<li><strong>使用默认的内部自定义命令&quot;@STCISP#“</strong></li>
<li><strong>下次使用HID接口进行ISP下载</strong></li>
<li><strong>每次下载前都先发送自定义命令</strong></li>
</ul>
</li>
<li>
<p><strong>按键方式下载一次。</strong></p>
</li>
<li>
<p><strong>之后修改代码,编译完成自动下载到MCU。</strong></p>
</li>
</ul>
<p><strong>问题:原理还是不清楚,冲哥这节课也就是讲怎么用的。</strong></p>

carysun 发表于 2024-12-11 09:39:00

<h1>第五讲 C语言基础</h1>
<h2>1.C语言 USB-CDC串口之printf函数的实现</h2>
<p><strong>USB-CDC串口之printf函数可以帮助我们打印出变量的类型等,快速开发。</strong></p>
<h3>1.1 打开USB库中的 <code>PRINTF_HID</code>宏定义(去掉 <code>//</code>)</h3>
<p><strong>该步骤可选</strong></p>
<p><strong>我的:</strong></p>
<pre><code> #define PRINTF_USB              //printf输出直接重定向到USB口
</code></pre>
<p><strong>打开此”标志宏“,用于该头文件的92行的如下代码段:</strong></p>
<pre><code> int printf_usb (const char *fmt, ...);
 
 #if defined PRINTF_SEGLED
 #define printfSEG7_ShowString
 #elif defined PRINTF_USB
 #define printfprintf_usb
 #endif
</code></pre>
<p><strong>即打开如上注释后可以使用</strong> <code>printf</code>来替换 <code>printf_usb</code>,调用函数 <code>int printf_usb (const char *fmt, ...);</code>。</p>
<p><strong>其实不做这个步骤,程序中直接调用</strong> <code>printf_usb</code>也是一样的。</p>
<h4>问题:==新USB库文件与此不同==</h4>
<p><strong>第5讲发现我的注释语句与冲哥的不同,我的</strong> <code>stc32_stc8_usb.h</code>里相同位置是:</p>
<pre><code> #define PRINTF_USB //printf输出直接重定向到USB口
</code></pre>
<p><strong>我的USB库文件是从官网下的,感觉应该是比冲哥的新。</strong></p>
<p><strong>另一点变化是现在库文件和例程也不是分开下载了,而是一个下载链接,库文件zip里就有例程。</strong></p>
<p>**上节发现新例程里 与 冲哥视频里的 **<code>main.c</code>对比,还少了一句代码:</p>
<pre><code> IE2 |= 0x80;
</code></pre>
<p>==请冲哥或是哪位大侠给解读下,是否现在打开 <code>PRINTF_USB</code>的宏定义也是一样的呢?==</p>
<p>==还有例程里 <code>IE2 |= 0x80;</code>这句是不能少的吧?是新例程的错误吗?==</p>
<p><strong>感谢论坛大佬答复:</strong></p>
<blockquote>
<p><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=11902&amp;extra=page%3D1&amp;page=21">【新提醒】《8051U深度入门到32位51大型实战视频》,【免费 + 包邮 送】实验箱@Ai8051U,100万套 - 第21页 - TFT彩屏,触摸屏,DMA-i8080/M6800并口自动刷屏,DMA-SPI刷屏,外设直接到外设 国芯技术交流网站 - AI32位8051交流社区</a></p>
<p><strong>来自:乘风***</strong></p>
</blockquote>
<p><strong>官网的USB库文件经过更新,</strong> <code>printf</code>输出直接重定向到USB口的定义名称改成:<code>PRINTF_USB</code> <code>USB_HID</code> 与 <code>USB_CDC</code>都可以通过这个定义重定向到USB口输出 printf 数据。</p>
<p>==<code>IE2 |= 0x80;</code>是使能USB中断,<code>usb_init();</code>函数里面有设置,所以例子没有重新配置。==</p>
<p>==32系列 <code>IE2</code>相关的中断位操作使能后,需要重新设置 <code>EUSB</code>,安全起见可以在初始化完成,启动总中断前设置一次 <code>IE2 |= 0x80;</code>==</p>
<h4>“标志宏”或“空宏”</h4>
<p><strong>在C语言中,</strong><code>#define</code>指令是预处理指令的一种,用于定义宏。当你看到 <code>#define</code>后面只有一个参数,比如 <code>#define PRINTF_USB</code>,这意味着定义了一个名为 <code>PRINTF_USB</code>的宏,但它没有替换文本或值。这样的宏通常被称为“标志宏”或“空宏”。</p>
<blockquote>
<p><strong>定义这样的宏有几个可能的用途:</strong></p>
<ol>
<li>
<p><strong>‌****条件编译</strong>‌:** **标志宏经常用于条件编译。通过定义或不定义这些宏,你可以控制代码的哪些部分会被编译器编译,哪些部分会被忽略。例如:</p>
<pre><code> #ifdef PRINTF_USB
 // 只有当PRINTF_USB被定义时,这部分代码才会被编译
 void print_to_usb(...) {
     // 实现向USB打印的功能
 }
 #endif
</code></pre>
<p><strong>在这个例子中,如果</strong> <code>PRINTF_USB</code>被定义了(即使它没有值),那么 <code>print_to_usb</code>函数就会被编译。如果没有定义 <code>PRINTF_USB</code>,那么这部分代码就会被编译器忽略。</p>
</li>
<li>
<p><strong>‌****编译时检查</strong>‌:** **有些开发者使用标志宏作为编译时检查的一种方式。他们可以在代码中检查某个宏是否被定义,以此来确定是否包含了某些特定的功能或代码块。</p>
</li>
<li>
<p><strong>‌****文档或注释</strong>‌:** **虽然这不是其主要用途,但有时标志宏也被用作一种文档或注释的形式,来指示代码的某个特定部分或功能是可选的或依赖于某些编译时条件的。</p>
</li>
<li>
<p><strong>‌****占位符</strong>‌:** **在某些情况下,开发者可能会先定义一个空宏作为占位符,然后在未来的某个时间点为其添加具体的替换文本或值。</p>
</li>
</ol>
<p><strong>总的来说,</strong><code>#define PRINTF_USB</code>这样的定义本身并不直接产生任何代码或值,但它可以在编译过程中被用来控制代码的编译行为或作为其他预处理指令的一部分。</p>
</blockquote>
<h3>1.2 理解PRINTF的函数原型的定义</h3>
<pre><code> #define printf printf_hid
     
 # 语法
 #define NEW OLD
</code></pre>
<p><code>int printf_hid (const char *fmt, ...);</code></p>
<p><strong>参数fmt -- 是格式控制字符串,包含了两种类型的对象:****普通字符</strong>和<strong>转换说明</strong> 。</p>
<ul>
<li>
<p><strong>普通字符:在输出时,普通字符将原样不动地复制到标准输出。</strong></p>
<pre><code> printf(&quot;8051U深度入门到32位51大型实战视频\r\n&quot;);
</code></pre>
</li>
<li>
<p><strong>占位符(转换说明):不直接输出,用于控制 printf 中参数的转换和打印。每个转换说明都由一个百分号字符(%)开始,以转换说明符结束,如</strong> <code>%s</code>,从而说明输出数据的类型、宽度、精度等。</p>
<pre><code> printf(&quot;8051U深度入门到32位51大型实战视频,%s\r\n&quot;,&quot;加油&quot;);
</code></pre>
<p><strong>转换说明简介:</strong></p>
<ol>
<li><strong>类型:根据不同的 fmt 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 fmt 参数中指定的每个 % 标签。关于附加参数,既可以是变量,也可以是常量。</strong></li>
<li><strong>位置:</strong><code>printf()</code>函数的普通字符和转换说明放在&quot; &quot;双引号内,附加参数放在双引号外,每个附加参数之间用逗号隔开。</li>
<li>**数量:printf() 的附加参数与转换说明符是⼀⼀对应关系,如果有 n 个转换说明符, **<code>printf()</code> 的参数就应该有 <code>n + 1</code> 个。如果参数个数少于对应的转换说明符,<code>printf()</code> 可能会输出内存中的任意值。</li>
</ol>
</li>
</ul>
<h4>printf_usb的fmt格式</h4>
<ul>
<li><strong>类似printf,但似乎不完全等价。注意:</strong><code>\t</code>: 8个空格</li>
<li><strong>冲哥PPT里的</strong> <code>n.m</code>:n整数占几行,m表示小数占几行,应该是个错误。
<blockquote>
<p>**printf fmt **<code>n.m</code>与限定宽度占位符、限定小数位数占位符</p>
<pre><code> printf(&quot;%6.2f\n&quot;, 0.8); // 输出为 &quot;0.80&quot;
</code></pre>
<p><strong>说明:%6.2f 表示输出字符串最小宽度为6,小数位数为2。整体长度不足 6 位时,右对齐显示。</strong></p>
<p><strong>关于</strong> <code>printf</code>参考:https://blog.csdn.net/weixin_46672094/article/details/121618950</p>
</blockquote>
</li>
</ul>
<h4>接收缓冲区 【HEX模式】展示</h4>
<p><strong>参考ASCII码表。</strong></p>
<h4>可变数量的参数</h4>
<pre><code> int printf_usb (const char *fmt, ...);
</code></pre>
<p><strong>关于上句中三个点怎么理解?</strong></p>
<p><strong>在C语言中,函数声明中的三个点(</strong><code>...</code>)表示该函数接受可变数量的参数,也被称为**“可变参数列表”或“变长参数表”**。这种语法允许函数在调用时传入比定义时更多的参数,而这些额外的参数在函数内部可以通过特定的宏和类型转换来访问。</p>
<p><strong>对于你给出的函数声明:</strong></p>
<pre><code> int printf_hid(const char *fmt, ...);
</code></pre>
<p><strong>这里的</strong> <code>...</code>意味着 <code>printf_hid</code>函数除了接受一个 <code>const char *</code>类型的 <code>fmt</code>参数(通常是一个格式字符串)之外,<strong>还可以接受任意数量和类型的额外参数。这些额外参数的数量和类型在编译时是不确定的,而是在函数调用时由调用者决定。</strong></p>
<p>**在C语言中,**<strong>处理可变参数列表通常需要使用 <code>&lt;stdarg.h&gt;</code>头文件中的宏</strong>。以下是一个简化的例子,展示了如何在函数内部访问这些可变参数:</p>
<pre><code> #include &lt;stdio.h&gt;
 #include &lt;stdarg.h&gt;
 
 int printf_hid(const char *fmt, ...) {
     va_list args;
     int result;
 
     // 初始化可变参数列表
     va_start(args, fmt);
 
     // 假设这里我们简单地调用vprintf来打印格式化的字符串
     // vprintf是标准库函数,它接受一个va_list类型的参数来处理可变参数
     result = vprintf(fmt, args);
 
     // 清理可变参数列表
     va_end(args);
 
     return result;
 }
 
 int main() {
     printf_hid(&quot;Hello, %s! You are %d years old.\n&quot;, &quot;Alice&quot;, 30);
     return 0;
 }
</code></pre>
<p><strong>在这个例子中,</strong><code>printf_hid</code>函数内部使用 <code>va_list</code>类型的变量 <code>args</code>来存储可变参数列表。<code>va_start</code>宏用于初始化 <code>args</code>,使其指向第一个可变参数。然后,我们可以使用 <code>vprintf</code>函数(它是 <code>printf</code>的变体,接受 <code>va_list</code>作为参数)来打印格式化的字符串和可变参数。最后,<code>va_end</code>宏用于清理 <code>args</code>。</p>
<p><strong>需要注意的是,使用可变参数列表时要特别小心,因为编译器无法进行类型检查,这可能导致运行时错误。因此,在使用可变参数函数时,务必确保传入的参数与格式字符串中的格式说明符匹配。</strong></p>
<h3>1.3 测试</h3>
<ul>
<li><strong>打开ISP - 【打开程序文件】,选择hex,查看时间确认一下。</strong></li>
<li><strong>【输入用户程序运行时的IRC频率】选择24MHz,</strong></li>
<li><strong>与上节一样:ISP-“硬件选项”Tab页-【选择CPU指令模式】:32-Bit。</strong></li>
<li><strong>与上节一样:勾选左下角【当目标文件变化时自动装载并发送下载命令】</strong></li>
<li><strong>与上节一样:“收到用户命令后复位到ISP监控程序区” Tab页,波特率:9600,勾选</strong>
<ul>
<li><strong>使用默认的内部自定义命令&quot;@STCISP#“</strong></li>
<li><strong>下次使用HID接口进行ISP下载</strong></li>
<li><strong>每次下载前都先发送自定义命令</strong></li>
</ul>
</li>
<li><strong>收到用户命令后</strong></li>
<li><strong>【下载/编程】</strong></li>
</ul>
<blockquote>
<p><strong>‌****IRC频率是单片机的内部时钟频率</strong>‌。</p>
<p><strong>‌****详细解释</strong>‌:</p>
<ul>
<li><strong>IRC(Inner RC Oscillator 内部RC振荡器)是单片机内部的一种时钟源。</strong></li>
<li><strong>IRC频率决定了单片机的运行速度以及单片机可以单位时间执行的指令数量,是单片机实现功能的关键因素之一‌。</strong></li>
<li><strong>在“Inner RC Oscillator”中,“RC”是“Resistor-Capacitor”的缩写,即“电阻-电容”。这种振荡器是由内部的电阻(R)和电容(C)元件组成的电路,用于产生振荡信号。RC振荡器是一种简单的振荡器类型,其振荡频率由电阻和电容的值决定,通常用于提供时钟信号给单片机或其他集成电路。不过,需要注意的是,RC振荡器的频率稳定性和精度可能相对较低,因此在某些高精度应用中可能需要使用其他类型的振荡器,如晶体振荡器(Crystal Oscillator)或陶瓷振荡器(Ceramic Oscillator)。</strong></li>
</ul>
<p><strong>开发板的外部晶振频率是32.768MHz</strong></p>
</blockquote>
<ul>
<li>
<p><strong>下载完成后,多了一个单片机程序映射的COM口</strong> <code>(COM?)CDC1-UART1.2CDC+HID</code>,选择它。</p>
</li>
<li>
<p><strong>ISP-</strong><code>CDC/HID-串口助手</code> Tab页</p>
<ul>
<li><strong>USB-CDC串口和普通串口区别:任意波特率都可以工作,因为本质是USB在通讯。</strong></li>
</ul>
</li>
<li>
<p><strong>发送缓冲区录入</strong> <code>1</code>,【打开串口】,【发送数据】,接收缓冲区红色的字是MCU接收到的上位机发送数据,蓝色字是MCU程序 <code>printf_usb</code>的数据。</p>
<ul>
<li><strong>上位机发送数据不想显示在ISP的接收缓冲区,【更多设置】取消勾选【显示发送的数据】</strong></li>
</ul>
</li>
</ul>
<h2>2.数的进制:2进制、10进制、16进制</h2>
<h2>3.数据的基本类型</h2>
<ul>
<li><strong>想要使用64位变量,需要在程序文件里面添加申明:</strong><code>#pragma float64</code>,即需要使用 <code>double</code>类型时,需要添加这句。</li>
<li><strong>short int和int都是2Bytes,在C251里是一样的。</strong></li>
<li><code>#define u8 unsigned char</code></li>
</ul>
<h2>4.C语言常用运算符</h2>
<h2>遗留问题</h2>
<ul>
<li><strong>Keil中‌C251是否支持C99/C11语言标准?搜遍芯片手册,没提及,可能是不支持,只支持C80/C90。</strong></li>
<li><code>&quot;\n&quot;</code>待测试:
<pre><code> // printf(&quot;STC YYDS. %s\n&quot;, buf);        // 有必要\r\n吗?需要测试
 
 // 个人觉得,不使用宏替换,直接使用原函数名printf_usb更好,使用printf反而让人误解。
 printf_usb(&quot;STC YYDS. %s\n&quot;, buf);    
</code></pre>
</li>
</ul>

carysun 发表于 2024-12-12 01:33:31

<h1>第6讲 IO输入输出</h1>
<h2>1.什么是GPIO</h2>
<ul>
<li>
<p><strong>GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平。</strong></p>
</li>
<li>
<p><strong>芯片手册 附录M 电气特性绝对最大额定值</strong></p>
<ul>
<li><strong>关注:VDD对地电压、I/O口对地电压</strong></li>
<li><strong>VDD是5.5V,I/O最大5.8V,;VDD3.3V,I/O最大3.6V</strong></li>
</ul>
</li>
</ul>
<h3>问题:“推挽/强上拉口,用拉电流驱动发光二极管”,不是推电流,而是拉电流吗?</h3>
<h3>关于LED限流电阻的阻值</h3>
<p><strong>弱上拉/准双向口,用灌电流驱动发光二极管,限流电阻尽量大于1KΩ,不要小于470Ω,试验箱用得是3.3KΩ,原因:</strong></p>
<p><strong>AI8051U芯片灌电流可达20mA,让其某个GPIO引脚外接串联发光二极管、电阻、Vcc,为什么电阻要尽量大于1KΩ,不要小于470Ω,其计算过程是?</strong></p>
<blockquote>
<p><strong>当AI8051U芯片的某个GPIO引脚外接串联发光二极管(LED)、电阻和Vcc时,电阻的选择至关重要,</strong></p>
<ul>
<li><strong>以确保LED在安全的电流范围内工作,</strong></li>
<li><strong>同时保护GPIO引脚不受过流损害。</strong></li>
</ul>
<p><strong>以下是对电阻值选择的计算过程及原因的解释:</strong></p>
<h3>计算过程</h3>
<ol>
<li><strong>‌****确定LED的工作电流</strong>‌:
<ul>
<li><strong>发光二极管(LED)通常有一个最大工作电流,超过这个电流可能会损坏LED或缩短其寿命。假设我们选择的LED的最大工作电流为LED_max = 20mA(与AI8051U的灌电流能力相匹配)。</strong></li>
</ul>
</li>
<li><strong>‌****确定电源电压和LED的正向电压</strong>‌:
<ul>
<li><strong>电源电压(Vcc)是已知的,假设为5V。</strong></li>
<li><strong>LED的正向电压 V_f 是LED在正常工作时的电压降,这个值通常可以在LED的数据手册中找到。假设 V_f = 2V。</strong></li>
</ul>
</li>
<li><strong>‌****计算电阻值</strong>‌:
<ul>
<li><strong>根据欧姆定律,电阻(R)可以通过以下公式计算:</strong> <code>R = (Vcc - V_f) / LED_max</code></li>
<li><strong>将已知值代入公式:</strong> <strong>R = (5V-2V) / 0.02A = 150Ω</strong></li>
<li><strong>但是,这个计算结果是理论上的最小值,实际上我们需要选择一个比这个大得多的电阻来确保安全。</strong></li>
</ul>
</li>
<li><strong>‌****考虑安全裕量和实际电阻值</strong>‌:
<ul>
<li><strong>为了保护LED和GPIO引脚,我们需要考虑一定的****安全裕量</strong>。因此,实际选择的电阻值应该大于计算出的理论值。</li>
<li><strong>在实践中,对于标准的指示LED,电阻值通常选择在</strong>==几百欧姆到几千欧姆之间==。对于AI8051U的情况,推荐电阻值尽量大于1kΩ,且不要小于470Ω,这是为了提供更安全的电流限制,并考虑到GPIO引脚的最大输出电流能力。</li>
</ul>
</li>
</ol>
<h3>原因解释</h3>
<ul>
<li><strong>‌****保护LED</strong>‌:选择较大的电阻值可以确保LED不会因电流过大而损坏或过热。</li>
<li><strong>‌****保护GPIO引脚</strong>‌:AI8051U的GPIO引脚虽然可以承受一定的电流,但长时间工作在接近最大电流值下可能会缩短引脚的使用寿命或导致其他不可预测的问题。</li>
<li><strong>‌****提高电路的稳定性</strong>‌:较大的电阻值可以减小电路中的电流变化对LED亮度的影响,提高电路的稳定性和可靠性。</li>
</ul>
<p><strong>综上所述,选择电阻值时要综合考虑LED的工作电流、电源电压、LED的正向电压以及安全裕量等因素,以确保电路的正常工作和元件的安全。在实际应用中,通常会选择比理论计算值更大的电阻来提供额外的保护。</strong></p>
<p><strong>在电子工程和电路设计领域,术语</strong>**“安全裕量”(Safety Margin)<strong>和</strong>“安全余量”(Safety Allowance)**基本上可以互换使用,它们都指的是在设计电路或系统时为了应对不确定性、变化或异常条件而预留的额外能力或容量。这种预留是为了确保即使在不利条件下,电路或系统也能安全、可靠地工作。</p>
</blockquote>
<ul>
<li>
<p><strong>芯片手册 附录M 直流特性 VDD3.3V(还有VDD=5.0V的不做讨论)关注:</strong></p>
<ul>
<li><strong>V</strong><del>ILI</del> 输入低电平 打开施密特触发器 最大值0.99V</li>
<li><strong>V</strong><del>IHI</del> 输入高电平(普通I/O) 打开施密特触发器 最小值1.18V</li>
</ul>
</li>
<li>
<p><strong>端口施密特触发控制寄存(PxNCS)器上电复位默认为0使能(即打开状态)。</strong></p>
</li>
</ul>
<h2>2.按键输入检测</h2>
<ul>
<li>
<p><strong>试验箱手册:按键SW2 SW3 分别连接 P32(INT0) P33(INT1)。</strong></p>
</li>
<li>
<p><strong>芯片手册:13.1 I/O口相关寄存器</strong></p>
<ul>
<li><strong>可见GPIO(P0-P7)复位值都是0xFF。</strong></li>
</ul>
</li>
<li>
<p><strong>市面上的按键</strong></p>
<ul>
<li><strong>轻触开关</strong></li>
</ul>
</li>
<li>
<p><strong>注意:按位取反(<code>~</code>)和非(<code>!</code>)运算是不同的。</strong></p>
<ul>
<li><strong>变量</strong> <code>u8 state = 0;</code> 取反 1,再取反 0</li>
</ul>
</li>
<li>
<p><strong>通过</strong> <code>printf_usb</code>观察按键抖动。ISP中操作</p>
<ul>
<li><strong>串口选择(COM8) CDC-1UART1.2CDC+HID</strong></li>
<li><strong>串口选COM8(冲哥是8,和前面保持一致)</strong></li>
<li><strong>【打开串口】</strong></li>
<li><strong>勾选【编程完自动打开】,可选。</strong></li>
</ul>
</li>
<li>
<p><strong>按键消抖,冲哥用的是前沿抖动延时是20ms,51时用的是10ms。</strong></p>
</li>
</ul>
<h3>延时函数 <code>Delay1ms</code></h3>
<ul>
<li>
<p><strong>没有延时的话(不跳过前沿抖动的话)会发现,灯是亮的,按下按键,闪一下,灯还是亮的。</strong></p>
</li>
<li>
<p>**下图中 **==注意:系统初始化时务必将WTST初始化为00H==<br />
**官方实验包里的每个程序都有:**</p>
<pre><code>     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
     EAXFR = 1; //扩展寄存器(XFR)访问使能
     CKCON = 0; //提高访问XRAM速度
</code></pre>
</li>
<li>
<p>**软件延时计算器 - **<strong>系统频率</strong> 要和 硬件选项 - <strong>输入用户程序运行时的IRC频率</strong> 保持一致。</p>
</li>
<li>
<p><code>_nop_()</code>要求 <code>#include &lt;INTRINS.H&gt;</code></p>
<blockquote>
<p><strong>Intrinsic 英 /ɪnˈtrɪnzɪk/ adj. 内在的,固有的</strong></p>
</blockquote>
</li>
</ul>
<h4>定时器 相较于 延时函数 的优势</h4>
<p><strong>在51单片机(或任何类似的微控制器)开发中,使用定时器相较于使用延时函数通常具有多个优势,这些优势主要体现在以下几个方面:</strong></p>
<ol>
<li><strong>‌****精确性</strong>‌:
<ul>
<li><strong>定时器能够提供非常精确的延时,因为它们是基于微控制器的内部时钟运行的。这意味着你可以准确地设置延时的时间,而不用担心由于处理器速度变化或代码执行时间的不确定性导致的延时误差。</strong></li>
<li><strong>延时函数(如循环延时)通常依赖于处理器的速度和当前代码的执行时间,因此它们可能不够精确,特别是在处理器负载变化或代码优化时。</strong></li>
</ul>
</li>
<li><strong>‌****效率</strong>‌:
<ul>
<li><strong>使用定时器可以在不占用大量处理器时间的情况下实现延时。一旦定时器被配置并启动,它就在后台运行,不需要处理器持续执行指令来保持延时。</strong></li>
<li><strong>相比之下,延时函数通常通过执行一系列空指令或循环来实现延时,这会占用处理器的时间,降低系统的整体效率。</strong></li>
</ul>
</li>
<li><strong>‌****多功能性</strong>‌:
<ul>
<li><strong>定时器不仅可以用于延时,还可以用于生成定时信号、测量时间间隔、产生PWM(脉宽调制)信号等多种功能。这使得定时器成为微控制器中非常强大的工具。</strong></li>
<li><strong>延时函数的功能相对单一,只能用于实现简单的延时。</strong></li>
</ul>
</li>
<li><strong>‌****可预测性</strong>‌:
<ul>
<li><strong>定时器的行为是可预测的,因为它们是基于硬件时钟运行的。这意味着你可以准确地知道定时器何时会触发或完成延时。</strong></li>
<li><strong>延时函数的行为可能受到多种因素的影响,如处理器速度的变化、中断的干扰等,因此它们的延时可能不如定时器那么可预测。</strong></li>
</ul>
</li>
<li><strong>‌****节能</strong>‌:
<ul>
<li><strong>在某些情况下,使用定时器可以比使用延时函数更加节能。例如,在低功耗应用中,定时器可以在不需要处理器持续运行时仍然保持精确的时间跟踪。</strong></li>
<li><strong>延时函数可能需要处理器持续运行以维持延时,这会消耗更多的电能。</strong></li>
</ul>
</li>
</ol>
<p><strong>综上所述,使用定时器在51单片机开发中通常比使用延时函数更具优势。定时器提供了更高的精确性、效率、多功能性、可预测性和节能性,使得它们成为实现延时和其他时间相关功能的首选工具。</strong></p>
<h3>不允许使用sbit作为参数</h3>
<p><strong>在C251(Keil C51编译器)中,</strong><code>sbit</code> 关键字用于在函数外部声明对特定位的引用,但它不能作为函数参数的类型。当你尝试将 <code>sbit</code> 用作函数参数时,编译器会报错,因为这不是一个有效的用法。</p>
<p>**错误消息 **<code>error C25: syntax error near 'sbit'</code> 指出编译器在解析函数原型时遇到了不期望的 <code>sbit</code> 关键字。</p>
<p>**如果你想要编写一个函数来操作特定的LED(通过特定位),你应该在函数内部使用 **<code>sbit</code> 来定义该位,并将函数的参数设置为更通用的类型,如 <code>unsigned char</code> 或 <code>int</code>,然后在函数内部根据这个参数来决定操作哪个 <code>sbit</code>。但是,通常更好的做法是直接操作 <code>sbit</code> 定义的变量,而不是尝试将它作为参数传递。</p>
<p>**下面是一个示例,展示了如何正确使用 **<code>sbit</code> 来控制LED,并且不使用 <code>sbit</code> 作为函数参数:</p>
<pre><code> // 在全局范围或头文件中定义sbit
 sbit LED_XX = P10; // 假设LED连接到P1口的第0位
 ​
 // 函数原型,不使用sbit作为参数
 void Int_LED_TurnOnLED(void);
 ​
 // 函数实现
 void Int_LED_TurnOnLED(void) {
     LED_XX = 1; // 打开LED
 }
 ​
 // 在main函数中调用
 void main(void) {
     Int_LED_TurnOnLED(); // 调用函数来打开LED
     // ... 其他代码
 }
</code></pre>
<p><strong>在这个例子中,</strong><code>LED_XX</code> 被定义为连接到P1口的第0位的 <code>sbit</code>。然后,我们编写了一个函数 <code>Int_LED_TurnOnLED</code>,它不接受任何参数,直接操作 <code>LED_XX</code> 来打开LED。</p>
<p>**如果你确实需要在函数中操作多个LED,并且想要通过参数来选择哪一个LED被打开,你可以考虑使用指针或者数组来间接地引用这些 **<code>sbit</code> 定义的变量。但是,请注意,<code>sbit</code> 本身不能直接用作数组元素或指针的目标。相反,你可以使用 <code>unsigned char</code> 类型的数组来存储每个LED的状态,并在函数中使用这些状态来更新实际的 <code>sbit</code> 变量。</p>
<h3>文件结构调整问题</h3>
<p>==头文件(<code>.h</code>)似乎不需要添加到项目也一样可以编译成功,但这样一来左边Project结构树里就看不见头文件了,如何编辑呢?==</p>
<p><strong>注意:如下界面不能使用</strong> <code>All Files(*.*)</code> 一次Add多个文件,虽然系统支持,但添加后Files里面易出现带路径的文件,确实让编译报错了。</p>
<blockquote>
<p><strong>error - cannot create command input file '.\Objects</strong>.<strong>.\workspace\C251_Ai8051U\06_light_led_with_key\Src\Com\Com_Util.__i'</strong></p>
</blockquote>
<h3>编码测试</h3>
<p><strong>输入用户程序运行时的IRC频率:24MHz</strong></p>
<blockquote>
<p><strong>IRC(Inner RC Oscillator 内部RC振荡器),“RC”是“Resistor-Capacitor”的缩写,即“电阻-电容”。</strong></p>
</blockquote>
<h2>3.课后小练</h2>
<h2>4. 问题</h2>
<h3>4.1 程序编译报错</h3>
<pre><code> Rebuild started: Project: light_led_with_key
 Rebuild target 'Src'
 compiling main.c...
 compiling Int_Key.c...
 compiling Int_LED.c...
 compiling Com_Util.c...
 linking...
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  usb_OUT_done
     MODULE:.\Objects\main.obj (main)
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  bUsbOutReady
     MODULE:.\Objects\main.obj (main)
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  printf_usb
     MODULE:.\Objects\main.obj (main)
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  ?printf_usb?BYTE
     MODULE:.\Objects\main.obj (main)
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  DeviceState
     MODULE:.\Objects\main.obj (main)
 *** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
     SYMBOL:  usb_init
     MODULE:.\Objects\main.obj (main)
 *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
     SYMBOL:  usb_init
     MODULE:.\Objects\main.obj (main)
     ADDRESS: FF000AH
 *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
     SYMBOL:  DeviceState
     MODULE:.\Objects\main.obj (main)
     ADDRESS: FF0010H
 *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
     SYMBOL:  printf_usb
     MODULE:.\Objects\main.obj (main)
     ADDRESS: FF001EH
 *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
     SYMBOL:  bUsbOutReady
     MODULE:.\Objects\main.obj (main)
     ADDRESS: FF004CH
 *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
     SYMBOL:  usb_OUT_done
     MODULE:.\Objects\main.obj (main)
     ADDRESS: FF004FH
 Program Size: data=8.0 edata+hdata=269 xdata=0 const=72 code=531
 Target not created.
 Build Time Elapsed:  00:00:03
</code></pre>
<p><strong>问题原因终于找到了。</strong></p>
<p><strong>右键 stc_usb_cdc_32.LIB,打开Options for File 'stc_usb_cdc_32.LIB',发现File Type居然是Image file。改成Library file,问题解决。</strong></p>
<p><strong>不知道是按了什么组合键还是什么原因,文件类型怎么会是Image file呢?匪夷所思。</strong></p>

carysun 发表于 2024-12-13 15:20:40

<h1>学习打卡!!!</h1>
<h1>第7讲 定时器</h1>
<h2>复制模板项目过程</h2>
<ul>
<li>
<p><strong>复制上一讲的Project文件夹</strong></p>
</li>
<li>
<p><strong>文件夹改名,如:07_led_toggle_with_timer</strong></p>
</li>
<li>
<p><code>~.uvproj</code>改名,如:led_toggle_with_timer.uvproj</p>
</li>
<li>
<p><strong>删除,除了Src、led_toggle_with_timer.uvproj以外的文件或文件夹</strong></p>
<blockquote>
<p><strong>但似乎删除后,系统马上会创建两个空文件夹:Listings、Objects</strong></p>
</blockquote>
</li>
<li>
<p><strong>打开uvproj文件,在keil中检查Options for Target窗口</strong></p>
<blockquote>
<p><strong>Device页签 芯片没变。</strong></p>
<p><strong>Target页签 CPU Mode, Memory Model, Code Rom Size, 4 Byte Interrupt Frame Size都没变</strong></p>
</blockquote>
<p><strong>Output页签Name of Executable: 改为led_toggle_with_timer</strong></p>
<blockquote>
<p><strong>Output页签 Create HEX File, HEX Format都没变</strong></p>
<p><strong>C251页签 Include Paths没变</strong></p>
<p><strong>L251 Misc disable Warning Numbers没变</strong></p>
<p><strong>Debug页签 ...</strong></p>
<p><strong>Utilities...</strong></p>
</blockquote>
</li>
</ul>
<h2>任务</h2>
<p><strong>任务1:LED灯三秒取反一次,这期间任意时刻按下按钮,串口打印按键次数。</strong></p>
<p><strong>使用延时函数在执行延时代码期间,程序始终就在Delay内部的循环里。这个时候按键不会有反应,因为程序根本就没有执行到后面的按键响应处理语句。</strong></p>
<p><strong>任务2:灯按一下点亮三秒后熄灭。</strong></p>
<p><strong>任务3:救护车灯控制器,按下报警按钮,红蓝交替闪烁(LED1和LED2    表示红和蓝灯),再按一下报警按钮,红蓝灯停止。</strong></p>
<h2>关于定时器</h2>
<ul>
<li><strong>相比STC89C52只有3个定时器,AI8051U有6个定时器。</strong></li>
<li><strong>定时器/计数器的核心部件是一个加法计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同:</strong>
<ul>
<li><strong>如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或者每1个时钟得到一个计数脉冲,计数值加1。</strong></li>
<li><strong>如果计数脉冲来自单片机外部引脚,则为计数方式,每来一个脉冲加1。</strong></li>
</ul>
</li>
<li><strong>当 定时器/计数器 工作在计数模式时,对外部脉冲计数不分频。只有 定时器/计数器 工作在定时模式时,才通过TXx12(如T0x12)控制是否分频。是系统时钟/12 还是 系统时钟/1(不分频)后让 TX(如T0)进行计数。</strong></li>
<li><code>FOSC</code> ‌<strong>Frequency of Oscillator Clock/Frequency of System Clock</strong>‌,即‌**晶振频率(时钟频率)**‌。它指的是外部晶振的频率。</li>
</ul>
<p>** 思考:定时器一次只能定时一次,如果我有很多个定时任务怎么办?**</p>
<p><strong>设置多个定时器。</strong></p>
<h3>定时器作用</h3>
<p>**(1) 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作**</p>
<p>**(2) 替代长时间的Delay,提高程序的运行效率和处理速度(可以打断主循环)**</p>
<h3>ISP - 定时器计算器</h3>
<ul>
<li><strong>【时钟频率】 如24MHz需要与 【输入用户程序运行时的IRC频率】 保持一致。</strong></li>
<li><strong>【定时长度】为3秒时,选择【定时器模式】为 16位自动重载,报:“当前的设定无法产生指定的时间 !”,所以只能选择 24位自动重载</strong></li>
<li><strong>勾选【使能定时器中断】,添加中断函数。</strong>
<pre><code> void Timer0_Init(void)      //3秒@24.000MHz
 {
 TM0PS = 0x5B;         //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
 AUXR &amp;= 0x7F;         //定时器时钟12T模式
 TMOD &amp;= 0xF0;         //设置定时器模式
 TL0 = 0x3F;             //设置定时初始值
 TH0 = 0x01;             //设置定时初始值 0x013F
 TF0 = 0;                //清除TF0标志
 TR0 = 1;                //定时器0开始计时
 ET0 = 1;                //使能定时器0中断
 }
</code></pre>
</li>
</ul>
<h3>周期计算</h3>
<ul>
<li>
<p><strong>12T = FOSC/12,24MHz/12,0.5us一次计数</strong></p>
</li>
<li>
<p>**SYSclk=24MHz;FOSC=24MHz**</p>
</li>
<li>
<p><strong>代入公式:(65536-0x013F)×12×(0x5B+1)/(24×10</strong>^6^<strong>) = 65217×12×92×10</strong>^-6^ /24 = 65217×46×10^-6^ S= 2999982us 约等于3S</p>
</li>
<li>
<p><strong>定时器0的时钟 = 系统时钟SYSclk ÷ (TMOPS+1)</strong></p>
<ul>
<li><strong>TM0PS:定时器时钟预分频,目的是让时钟频率更低,时钟更慢,这样就可以定更长的时间。</strong></li>
<li>**假如 TM0PS=0,那么 24MHz/(0+1)=24MHz;假如 TM0PS=1,那么 24MHz/(1+1)=12MHz;**</li>
</ul>
</li>
<li>
<p>**FOSC 实际是 **<code>定时器0的时钟 = 系统时钟SYSclk ÷ (TMOPS+1)</code>是在此基础上 除12。</p>
</li>
<li>
<p>==频率公式 与 上面的周期公式成倒数关系。前提是没有 <code>/2</code>的话,这个 <code>/2</code>是什么意思??==</p>
</li>
<li>
<p><strong>16位自动重载定时器的本质就是从设定值数到65536(溢出)之后置位一次标志位,如果使能ET0就可以进入中断!</strong></p>
</li>
</ul>
<h3>代码</h3>
<ul>
<li>
<pre><code> void Timer0_Isr(void) interrupt 1
 {
 }
</code></pre>
<ul>
<li><code>Isr</code> 是 ‌<strong>Interrupt Service Routine</strong>‌ 的缩写。中断服务例程(ISR)是一种特殊的函数,当微控制器或处理器接收到中断信号时,它会暂停当前正在执行的程序,并跳转到这个ISR来执行特定的任务。</li>
</ul>
</li>
</ul>
<h2>代码测试过程</h2>
<ul>
<li><strong>打开ISP - 【打开程序文件】,选择hex,查看时间确认一下。</strong></li>
<li><strong>【输入用户程序运行时的IRC频率】选择24MHz,</strong></li>
<li><strong>与上节一样:ISP-“硬件选项”Tab页-【选择CPU指令模式】:32-Bit。</strong></li>
<li><strong>与上节一样:勾选左下角【当目标文件变化时自动装载并发送下载命令】</strong></li>
<li><strong>与上节一样:“收到用户命令后复位到ISP监控程序区” Tab页,波特率:9600,勾选</strong>
<ul>
<li><strong>使用默认的内部自定义命令&quot;@STCISP#“</strong></li>
<li><strong>下次使用HID接口进行ISP下载</strong></li>
<li><strong>每次下载前都先发送自定义命令</strong></li>
</ul>
</li>
<li><strong>下载完成串口自动打开。勾选【编程完自动打开】</strong></li>
<li><strong>【下载/编程】</strong></li>
<li><strong>下载完成后,多了一个单片机程序映射的COM口</strong> <code>(COM?)CDC1-UART1.2CDC+HID</code>,选择它。
<ul>
<li><strong>串口选择(COM8) CDC-1UART1.2CDC+HID</strong></li>
</ul>
</li>
<li><strong>CDC/HID-串口助手 Tab页</strong>
<ul>
<li><strong>【串口】选COM8(冲哥是8,和前面保持一致)</strong></li>
<li><strong>【打开串口】</strong></li>
<li><strong>勾选【编程完自动打开】,</strong>==下载完程序后,ISP自动跳转到 CDC/HID-串口助手 Tab页==</li>
<li><strong>上位机发送数据不想显示在ISP的接收缓冲区,【更多设置】取消勾选【显示发送的数据】</strong></li>
</ul>
</li>
</ul>
<h2>CDC/HID-串口助手 Tab页 接收缓冲区 打印 <code>?</code>的乱码问题</h2>
<p><strong>打印</strong> <code>按键按下次?22 次</code></p>
<p><strong>应为</strong> <code>按键按下次数:22 次</code></p>
<pre><code> // printf(&quot;%d 按键按下次数:%u 次\n&quot;, (int)res, count++);
 printf(&quot;%d 按键按下次数\xfd:%u 次\n&quot;, (int)res, count++);
</code></pre>
<p><strong>芯片手册 附录J 关于Keil软件中0xFD 问题的说明</strong></p>
<h3>关于Keil软件中0xFD 问题,可否使用utf-8来解决这个问题?</h3>
<p><strong>c51 c251编码时用utf8 还是GB2312 有什么讲究吗?</strong></p>
<p><strong>使用UTF8会产生其他已知的问题吗?比如串口通讯交互的话,有可能会涉及到系统里是GBK,为了打印出来一致,需要使用GBK编码。</strong></p>
<h2>课后练习</h2>
<p><strong>电子功德箱:</strong></p>
<p>**   1.按下按钮1,串口显示“双倍功德时间”,再次按下显示“单倍功德时间”; **</p>
<p>**   2.按下按键2,双倍功德时间下串口显示“功德+2 当前功德:xxx”;    **</p>
<p>**   3.按下按键2,单倍功德时间下串口显示“功德+1 当前功德:xxx”;**</p>
<p>** 4.功德+1时,LED点亮1秒后熄灭表示功德成功点亮;**</p>
<p>** 5.功德+2时,LED点亮2秒后熄灭表示功德成功点亮。**</p>
<h3>本讲提到的Keil软件中的0xFD问题,请问keil中Encoding改为UTF-8,可以避免这个问题吗?</h3>
<h3>定时器0输出时钟频率公式中 <code>/2</code>的意思是?</h3>

carysun 发表于 2024-12-15 14:10:19

<pre><code># AI_USB.H 实测
1. 现在的AIapp-ISP | keil仿真设置 | 添加型号和头文件到Keil中,ISP会添加 `AI_USB.H`、`STC32_STC8_USB.H`
与 `AI8051U.H`一并添加到如 D:\Keil_v5\C251\INC\STC 的目录下
2. `AI_USB.H`、`STC32_STC8_USB.H` 内容完全一样。
3. `#include &lt;AI_USB.H&gt;` 或者 `#include &lt;STC32_STC8_USB.H&gt;`都可以,仍需引入lib,否则编译无法通过。
</code></pre>

carysun 发表于 2024-12-15 14:11:22

<h1>AI_USB.H 实测</h1>
<ol>
<li>现在的AIapp-ISP | keil仿真设置 | 添加型号和头文件到Keil中,ISP会添加 <code>AI_USB.H</code>、<code>STC32_STC8_USB.H</code><br />
与 <code>AI8051U.H</code>一并添加到如 D:\Keil_v5\C251\INC\STC 的目录下</li>
<li><code>AI_USB.H</code>、<code>STC32_STC8_USB.H</code> 内容完全一样。</li>
<li><code>#include &lt;AI_USB.H&gt;</code> 或者 <code>#include &lt;STC32_STC8_USB.H&gt;</code>都可以,仍需引入lib,否则编译无法通过。</li>
</ol>
页: [1]
查看完整版本: AI8051U学习打卡 | 建议立即赠送 强大的AI8051U实验箱