在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。(已完成)
===可以直接申请个强大的 【免费+包邮送】的 【AI8051U 实验箱】 来做实验了,
当然也可以 申请 STCAI-万能实验板-V2.3
凭我这段认可,就可以去申请了
【免费 + 包邮 送】及 如何【扫码 小量购买】 单片机/烧录器/开发工具 - 其他技术交流 国芯人工智能技术交流网站 - AI32位8051交流社区
用【STCAI-万能实验板】做实验,DIY 拿奖励,500元/人,前20名 ! - 做实验拿奖励@STCAI万能板,500元 国芯人工智能技术交流网站 - AI32位8051交流社区
STCAI-万能实验板-V2.3,支持 封装形式/接口:
LQFP48, LQFP32,DFN8;
TSSOP28/24/20/16/14;
SOP28/24/20;
WSOP16/8;
SOP16/8;
SOT23-6/5/4/3;
DIP40/28/20/16/8;
贴片 电阻 / 电容 也可直接焊在插件的2个焊盘之间;
TF卡 插座,FPC接口;
FPC焊接/插座支持间距:
0.5mm、0.62mm、0.65mm、0.7mm、0.8mm
<h1>Jellyfish从89C51到AI8051学习贴第十天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第九集 数码管</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li>通过74HC595芯片实现少端口控制多端口。</li>
<li>数码管显示数字。</li>
<li>高位溢出会存储到CY寄存器</li>
<li>AiCube-ISP-v6.96P虚拟仿真显示</li>
</ol>
<h2>0x02 学习心得</h2>
<ol>
<li>
<p>74HC595使用</p>
<blockquote>
<ul>
<li><strong>DS (SER)</strong>: 串行数据输入。像排队一样,数据一位一位进场。</li>
<li><strong>SHCP (SRCLK)</strong>: 移位时钟。上升沿时,DS 上的数据进入移位寄存器,原有的数据向后移一位。</li>
<li><strong>STCP (RCLK)</strong>: 锁存时钟(关键)。上升沿时,将移位寄存器里的 8 位数据<strong>一次性</strong>搬运到外部引脚输出。</li>
<li><strong>Q7' (Q7S)</strong>: 级联输出。用于将数据传给下一个 595 的 DS 引脚。</li>
</ul>
</blockquote>
<pre><code class="language-c">/**
* @brief 向 595 系统发送一个字节
* 左移操作会触发 RLC 指令,最高位溢出到 PSW 寄存器的 CY 位。
*/
void HC595_SendByte(unsigned char dat) {
unsigned char i;
for (i = 0; i < 8; i++) {
HC595_DS = dat >> 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
dat <<= 1; // 左移,CY 标志位在底层被使用
HC595_SHCP = 0; // 产生移位脉冲
HC595_SHCP = 1;
}
}
/**
* @brief 级联更新函数(控制 16 个端口)
* @param data1 第一个 595 的输出 (Q0-Q7)
* @param data2 第二个 595 的输出 (Q8-Q15)
*/
void HC595_Update(unsigned char data1, unsigned char data2) {
HC595_STCP = 0;
HC595_SendByte(data2); // 先发送远端(第二片)数据
HC595_SendByte(data1); // 后发送近端(第一片)数据
HC595_STCP = 1; // 锁存脉冲,16 位数据同时更新输出
}
</code></pre>
</li>
<li>
<p>数码管8位bit与数字对应关系(通过<strong>AiCube-ISP-v6.96P</strong>字库生成工具一键生成)</p>
<pre><code class="language-c">0x3F, /*'0', 0*/
0x06, /*'1', 1*/
0x5B, /*'2', 2*/
0x4F, /*'3', 3*/
0x66, /*'4', 4*/
0x6D, /*'5', 5*/
0x7D, /*'6', 6*/
0x07, /*'7', 7*/
0x7F, /*'8', 8*/
0x6F, /*'9', 9*/
</code></pre>
</li>
<li>
<p>高位溢出会存储到CY寄存器</p>
<blockquote>
<p>在 8051/STC 架构的指令集中,左移操作通常是通过 <strong>补零移位</strong> 或 <strong>带进位循环移位</strong> 指令实现的。</p>
<p><strong>最高位(MSB)溢出</strong>:当 8 位变量的最左边一位(第 7 位)被移出时,它并没有消失,而是被推入了 CPU 的 <strong>程序状态字寄存器 (PSW)</strong> 中的 <strong>CY (Carry) 位</strong> 。</p>
</blockquote>
</li>
</ol>
<h2>0x03 待学习重点</h2>
<ol>
<li>
<p>89C51的经验有多少可以迁移到STC32G上(已完成)</p>
<blockquote>
<p>目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。</p>
</blockquote>
</li>
<li>
<p>如何将传统51单片机项目迁移到STC32G平台上(已完成)</p>
<blockquote>
<p>通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!</p>
</blockquote>
</li>
<li>
<p>硬件USB控制器可以玩出什么花样?(持续学习 持续探索)</p>
<ul>
<li>USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上<strong>MAX232</strong>打上RJ45就可以用来配置交换机了。</li>
</ul>
</li>
<li>
<p>在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直<strong>USB不停电下载</strong>。(已完成)</p>
</li>
<li>
<p>定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)</p>
<blockquote>
<p>初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。</p>
<p>答案:想的差不多,方法如下</p>
<ul>
<li>通过数组(<strong>包括结构体数组</strong>),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。</li>
</ul>
</blockquote>
</li>
<li>
<p>如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?</p>
<blockquote>
<p>会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。</p>
</blockquote>
</li>
<li>
<p>74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?</p>
</li>
</ol>
<h1>Jellyfish从89C51到AI8051学习贴第十天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第九集 数码管</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li><strong>通过74HC595芯片实现少端口控制多端口。</strong></li>
<li><strong>数码管显示数字。</strong></li>
<li><strong>高位溢出会存储到CY寄存器</strong></li>
<li><strong>AiCube-ISP-v6.96P虚拟仿真显示</strong></li>
</ol>
<h2>0x02 学习心得</h2>
<ol>
<li>
<p><strong>74HC595使用</strong></p>
<blockquote>
<ul>
<li><strong>DS (SER)</strong>: 串行数据输入。像排队一样,数据一位一位进场。</li>
<li><strong>SHCP (SRCLK)</strong>: 移位时钟。上升沿时,DS 上的数据进入移位寄存器,原有的数据向后移一位。</li>
<li><strong>STCP (RCLK)</strong>: 锁存时钟(关键)。上升沿时,将移位寄存器里的 8 位数据<strong>一次性</strong>搬运到外部引脚输出。</li>
<li><strong>Q7' (Q7S)</strong>: 级联输出。用于将数据传给下一个 595 的 DS 引脚。</li>
</ul>
</blockquote>
<pre><code class="language-c">/**
* @brief 向 595 系统发送一个字节
* 左移操作会触发 RLC 指令,最高位溢出到 PSW 寄存器的 CY 位。
*/
void HC595_SendByte(unsigned char dat) {
unsigned char i;
for (i = 0; i < 8; i++) {
HC595_DS = dat >> 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
dat <<= 1; // 左移,CY 标志位在底层被使用
HC595_SHCP = 0; // 产生移位脉冲
HC595_SHCP = 1;
}
}
/**
* @brief 级联更新函数(控制 16 个端口)
* @param data1 第一个 595 的输出 (Q0-Q7)
* @param data2 第二个 595 的输出 (Q8-Q15)
*/
void HC595_Update(unsigned char data1, unsigned char data2) {
HC595_STCP = 0;
HC595_SendByte(data2); // 先发送远端(第二片)数据
HC595_SendByte(data1); // 后发送近端(第一片)数据
HC595_STCP = 1; // 锁存脉冲,16 位数据同时更新输出
}
</code></pre>
</li>
<li>
<p><strong>数码管8位bit与数字对应关系(通过****AiCube-ISP-v6.96P</strong>字库生成工具一键生成)</p>
<pre><code> 0x3F, /*'0', 0*/
0x06, /*'1', 1*/
0x5B, /*'2', 2*/
0x4F, /*'3', 3*/
0x66, /*'4', 4*/
0x6D, /*'5', 5*/
0x7D, /*'6', 6*/
0x07, /*'7', 7*/
0x7F, /*'8', 8*/
0x6F, /*'9', 9*/
</code></pre>
</li>
<li>
<p><strong>高位溢出会存储到CY寄存器</strong></p>
<blockquote>
<p>在 8051/STC 架构的指令集中,左移操作通常是通过 <strong>补零移位</strong> 或 <strong>带进位循环移位</strong> 指令实现的。</p>
<p><strong>最高位(MSB)溢出</strong>:当 8 位变量的最左边一位(第 7 位)被移出时,它并没有消失,而是被推入了 CPU 的 <strong>程序状态字寄存器 (PSW)</strong> 中的 <strong>CY (Carry) 位</strong> 。</p>
</blockquote>
</li>
</ol>
<h2>0x03 待学习重点</h2>
<ol>
<li>
<p><strong>89C51的经验有多少可以迁移到STC32G上(已完成)</strong></p>
<blockquote>
<p><strong>目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。</strong></p>
</blockquote>
</li>
<li>
<p><strong>如何将传统51单片机项目迁移到STC32G平台上(已完成)</strong></p>
<blockquote>
<p><strong>通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!</strong></p>
</blockquote>
</li>
<li>
<p><strong>硬件USB控制器可以玩出什么花样?(持续学习 持续探索)</strong></p>
<ul>
<li><strong>USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。</strong></li>
</ul>
</li>
<li>
<p><strong>在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载</strong>。(已完成)</p>
</li>
<li>
<p><strong>定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)</strong></p>
<blockquote>
<p><strong>初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。</strong></p>
<p><strong>答案:想的差不多,方法如下</strong></p>
<p>通过数组(<strong>包括结构体数组</strong>),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。</p>
</blockquote>
</li>
<li>
<p><strong>如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?</strong></p>
<blockquote>
<p><strong>会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。</strong></p>
</blockquote>
</li>
<li>
<p><strong>74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?</strong></p>
</li>
</ol>
<h1>Jellyfish从89C51到AI8051学习贴第十一天!实验箱已到货!!!</h1>
<blockquote>
<p>今天交作业:第九集的数码管课后小练习。</p>
</blockquote>
<p><img src="data/attachment/forum/202601/31/020114galbllycftllct5r.png" alt="image.png" title="image.png" /></p>
<h2>实验箱效果展示</h2>
<p><img src="data/attachment/forum/202601/31/020708ucz58tff37y1f3sr.gif" alt="VID20260131015909.gif" title="VID20260131015909.gif" /></p>
<p><img src="data/attachment/forum/202601/31/020518ws9nmtung0msn335.jpg" alt="IMG20260130222317.jpg" title="IMG20260130222317.jpg" /></p>
<p><img src="data/attachment/forum/202601/31/020526dzpjg3jt7w3s1w31.jpg" alt="IMG20260130222347.jpg" title="IMG20260130222347.jpg" /></p>
<h2>0x01 学习重点</h2>
<ol>
<li>定时器中断中不可以放printf,会导致定时器失效。</li>
<li>使用定时器实现多个任务并行。</li>
<li>多任务并行调度,通过按键更改调度情况。</li>
<li>通过HC595实现数码管控制。</li>
</ol>
<h2>0x02 学习心得</h2>
<ol>
<li>
<p>使用定时器实现多任务并行。</p>
<pre><code class="language-c">TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 300, 300, LED0_Blink}, /* task 1 Period: 300ms */
// {0, 600, 600, LED1_Blink}, /* task 1 Period: 600ms */
// {0, 900, 900, LED2_Blink}, /* task 1 Period: 600ms */
{0, 10, 10, KEY_Task}, /* task 1 Period: 600ms */
{0, 1, 1, Seg_Task}, /* task 1 Period: 1000ms */
{0, 0, 0, Time_Task},
};
u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps);
//========================================================================
// 函数: Task_Handler_Callback
// 描述: 任务标记回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
//========================================================================
//在中断函数中执行这个函数代码,实现数量累加
void Task_Marks_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.TIMCount) /* If the time is not 0 */
{
Task_Comps.TIMCount--; /* Time counter decrement */
if(Task_Comps.TIMCount == 0) /* If time arrives */
{
/*Resume the timer value and try again */
Task_Comps.TIMCount = Task_Comps.TRITime;
Task_Comps.Run = 1; /* The task can be run */
}
}
}
}
//========================================================================
// 函数: Task_Pro_Handler_Callback
// 描述: 任务处理回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
//========================================================================
//这个函数在main函数里面持续执行,当判断发现有任务时间到了就开始执行
void Task_Pro_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.Run) /* If task can be run */
{
Task_Comps.Run = 0; /* Flag clear 0 */
Task_Comps.TaskHook(); /* Run task */
}
}
}
</code></pre>
</li>
<li>
<p>实现595芯片控制数码管</p>
<pre><code class="language-c">void Init_595(void)
{
HC595_SER =0;
HC595_SCK =0;
HC595_RCK =0;
}
void Send_595(u8 dat){
u8 i;
for(i=0; i<8; i++){
dat <<= 1;
HC595_SER = CY;
HC595_SCK = 1;
HC595_SCK = 0;
}
}
void Display_Seg(u8 HC595_1,u8 HC595_2){
Send_595(HC595_1); //数码管段码
Send_595(HC595_2); //数码管位码
HC595_RCK = 1;
HC595_RCK = 0;
}
void Seg_Task(void){
if (Seg_no==0){
Display_Seg(SEG_NUM,~T_NUM);
}else if (Seg_no==1){
Display_Seg(SEG_NUM|SEG_NUM,~T_NUM);
}else if (Seg_no==2){
Display_Seg(SEG_NUM,~T_NUM);
}else if (Seg_no==3){
Display_Seg(SEG_NUM,~T_NUM);
}else if(Seg_no==4){
Display_Seg(SEG_NUM[(int)((haomiao/10000)%10)],~T_NUM);
}else if (Seg_no==5){
Display_Seg(SEG_NUM[(int)((haomiao/1000)%10)]|SEG_NUM,~T_NUM);
}else if (Seg_no==6){
Display_Seg(SEG_NUM[(int)((haomiao/100)%10)],~T_NUM);
}else if (Seg_no==7){
Display_Seg(SEG_NUM[(int)((haomiao/10)%10)],~T_NUM);
}
Seg_no++;
if (Seg_no>=8) Seg_no=0;
}
</code></pre>
<ol start="3">
<li>
<p>实现计时功能<br />
<strong>重要!这里变量类型如果是 unsigned int会很快溢出,根本跑不上秒位,或者不用毫秒做单位。</strong></p>
<pre><code class="language-c">unsigned long haomiao =0;
void Time_Task(){
haomiao+=10;
}
</code></pre>
</li>
<li>
<p>实现按键开始\停止计时<br />
<strong>定义结构体为extend变量,可以在io.c中修改!</strong></p>
<pre><code class="language-c">void KEY_Task(void)
{
if( P33 == 0 )
{
Key_Vol++;
if( Key_Vol==5 )
{
//按键按下的任务
if(time_state==1){
Task_Comps.TRITime=0;
Task_Comps.TIMCount=0;
time_state=0;
}else{
Task_Comps.TRITime=10;
Task_Comps.TIMCount=10;
time_state=1;
}
//printf( "按键单击\r\n" );
}
}
else
{
Key_Vol = 0;
}
}
</code></pre>
</li>
</ol>
<h2>0x03 待学习重点</h2>
</li>
<li>
<p>89C51的经验有多少可以迁移到STC32G上(已完成)</p>
<blockquote>
<p>目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。</p>
</blockquote>
</li>
<li>
<p>如何将传统51单片机项目迁移到STC32G平台上(已完成)</p>
<blockquote>
<p>通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!</p>
</blockquote>
</li>
<li>
<p>硬件USB控制器可以玩出什么花样?(持续学习 持续探索)</p>
<ul>
<li>USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上<strong>MAX232</strong>打上RJ45就可以用来配置交换机了。</li>
</ul>
</li>
<li>
<p>在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直<strong>USB不停电下载</strong>。(已完成)</p>
</li>
<li>
<p>定时器一次只能定时一个此,如果有很多个任务怎么办?(已完成)</p>
<blockquote>
<p>初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。</p>
<p>答案:想的差不多,方法如下</p>
<ul>
<li>通过数组(<strong>包括结构体数组</strong>),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。</li>
</ul>
</blockquote>
</li>
<li>
<p>如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?(已完成)</p>
<blockquote>
<p>会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。</p>
</blockquote>
</li>
<li>
<p>74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?(已完成,实测实验箱<strong>44.2368MHz</strong>以上数码管显示会出现问题。)</p>
</li>
</ol>
<h1>Jellyfish从89C51到AI8051学习贴第十二天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十集 虚拟键盘LED和数码管</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li><strong>使用AiCube-ISP-v6.96P进行 键盘 LED 数码管仿真</strong></li>
</ol>
<h2>0x02 学习心得</h2>
<ol>
<li><strong>密码锁课后小练习</strong></li>
</ol>
<p><img src="data/attachment/forum/202601/31/182217ydagcf68psf80pfa.png" alt="image-20260131180036898.png" title="image-20260131180036898.png" /></p>
<p><img src="data/attachment/forum/202601/31/182235nsmjj4jjpp99ppcp.gif" alt="VID20260131181153.gif" title="VID20260131181153.gif" /></p>
<pre><code class="language-c//while函数里面执行这个"> if (bUsbOutReady){
u8 i;
for(i=0;i<8;i++){
if(DisplayStr=='-'){
if(DisplayStr == 'n'){
strcpy(DisplayStr, "--------");
}
DisplayStr=UsbOutBuffer;
if(i==7){
// 3. 使用 strcmp 比较字符串内容
if(strcmp(DisplayStr, "12345678") == 0) {
strcpy(DisplayStr, "----open");
}else{
strcpy(DisplayStr, "--------");
}
}
break;
}
}
usb_OUT_done(); //查询方式处理USB接收的数据
}
//定时器定时调度这个
//SEG7_ShowString函数不能处理‘open’字符串,所以单独使用段码展示。
u8 open[]={
0x40, /*'-', 36*/
0x40, /*'-', 36*/
0x40, /*'-', 36*/
0x40, /*'-', 36*/
0x5C, /*'O', 24*/
0x73, /*'P', 25*/
0x79, /*'E', 14*/
0x54, /*'N', 23*/
};
char DisplayStr[] = "--------";
void Display_Task(){
if(strcmp(DisplayStr, "----open") == 0) {
SEG7_ShowCode(open);
}else{
SEG7_ShowString(DisplayStr);
}
}
</code></pre>
<h2>0x03 待学习重点</h2>
<ol>
<li>
<p>89C51的经验有多少可以迁移到STC32G上**(已完成)**</p>
<blockquote>
<p>目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。</p>
</blockquote>
</li>
<li>
<p>如何将传统51单片机项目迁移到STC32G平台上**(已完成)**</p>
<blockquote>
<p>通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!</p>
</blockquote>
</li>
<li>
<p>硬件USB控制器可以玩出什么花样?<strong>(持续学习 持续探索)</strong></p>
<ul>
<li>USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。</li>
</ul>
</li>
<li>
<p>在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。<strong>(已完成)</strong></p>
</li>
<li>
<p>定时器一次只能定时一个此,如果有很多个任务怎么办?<strong>(已完成)</strong></p>
<blockquote>
<p>初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。</p>
<p>答案:想的差不多,方法如下:</p>
<p>通过数组(<strong>包括结构体数组</strong>),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。</p>
</blockquote>
</li>
<li>
<p>如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?<strong>(已完成)</strong></p>
<blockquote>
<p>会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。</p>
</blockquote>
</li>
<li>
<p><strong>74HC595会不会跟不上单片机的速度而导致了数据的丢失?<strong>如果会,那么速度多快就会丢失数据?</strong>(已完成)</strong></p>
<blockquote>
<p>实测实验箱<strong>44.2368MHz</strong>以上数码管显示会出现问题。)</p>
</blockquote>
</li>
</ol>
<h1>Jellyfish从89C51到AI8051学习贴第十三天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十一集 矩阵键盘、十二集 复位系统</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li>通过x+y个引脚实现x*y个引脚的实时按下状态检测。</li>
<li>了解4种复位模式(上电复位、低压复位、复位脚、看门狗复位),以及触发方式。</li>
</ol>
<h2>0x02 学习心得</h2>
<ul>
<li>
<p><strong>扫描矩阵键盘思路</strong></p>
<blockquote>
<p><strong>逐行扫描法(最常用)</strong></p>
<ol>
<li><strong>初始化</strong>:将所有列线(Col)设为输入并上拉(高电平),行线(Row)设为输出。</li>
<li><strong>扫描</strong>:依次将某一行拉低(其余行为高)。</li>
<li><strong>判断</strong>:读取列线状态。如果有列线变低,说明该行与该列的交点按键被按下。</li>
<li><strong>计算</strong>:<code>键值 = 行号 × 总列数 + 列号</code>。</li>
</ol>
<p><strong>行列反转法(速度快)</strong></p>
<ol>
<li><strong>第一步</strong>:行输出全 0,列输入,读取列线值(记录哪一列为低)。</li>
<li><strong>第二步</strong>:列输出刚才读取到的值,行输入,读取行线值(记录哪一行为低)。</li>
<li><strong>合并</strong>:将两次读取的电平状态组合,即可精确定位坐标。</li>
</ol>
</blockquote>
</li>
<li>
<p><strong>4种复位模式</strong></p>
<blockquote>
<ol>
<li>上电复位 (POR - Power-On Reset)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:当 VCC 电源从 0V 攀升至系统工作电压时,内部硬件检测到电压跨越阈值,自动触发。</li>
<li><strong>特点</strong>:
<ul>
<li><strong>冷启动</strong>:这是最彻底的复位,会重置所有寄存器、I/O 状态及内部存储空间。</li>
<li><strong>标志位</strong>:通常在寄存器中表现为 <code>POF</code> (Power-On Flag)。</li>
</ul>
</li>
<li><strong>典型场景</strong>:设备第一次插入电池或接通电源。</li>
</ul>
<ol start="2">
<li>低压复位 (LVR - Low Voltage Reset / Brown-out)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:系统运行过程中,电源电压跌落至设定的阈值(如 2.4V 或 3.3V)以下时触发。</li>
<li><strong>特点</strong>:
<ul>
<li><strong>保护机制</strong>:防止电压过低导致 CPU 逻辑混乱、写 Flash 错误或程序跑飞。</li>
<li><strong>可调性</strong>:现代单片机通常允许在 ISP 下载软件 或软件寄存器中设置不同的掉电检测电压(LVD)。</li>
</ul>
</li>
<li><strong>典型场景</strong>:电池电量耗尽瞬间、大电流负载(如电机启动)导致电压掉闸。</li>
</ul>
<ol start="3">
<li>复位脚复位 (NRST - External Reset Pin)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:外部复位引脚(RST/NRST)被施加了特定的电平信号(通常是<strong>持续一定时间的低电平</strong>,STC 某些型号为高电平)。</li>
<li><strong>特点</strong>:
<ul>
<li><strong>硬件干预</strong>:由外部电路(如复位按键、外部监控芯片)物理控制。</li>
<li><strong>电路设计</strong>:通常需要外接 <strong>RC 复位电路</strong>(10k电阻 + 0.1μF电容)来过滤干扰波形。</li>
</ul>
</li>
<li><strong>典型场景</strong>:用户按下设备上的物理“Reset”小孔按键。</li>
</ul>
<ol start="4">
<li>看门狗复位 (WDT - Watchdog Timer Reset)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:看门狗计数器溢出。程序必须在规定时间内“喂狗”(重置计数值),如果程序死锁或跑飞未能及时喂狗,计数器归零即触发复位。</li>
<li><strong>特点</strong>:
<ul>
<li><strong>自愈功能</strong>:是嵌入式系统摆脱“死机”状态的最后防线。</li>
<li><strong>软硬结合</strong>:需要在软件中初始化看门狗频率,并在主循环(<code>while(1)</code>)中妥善放置喂狗代码。</li>
</ul>
</li>
<li><strong>典型场景</strong>:程序因强电磁干扰进入死循环,或逻辑陷入阻塞无法退出。</li>
</ul>
</blockquote>
</li>
</ul>
<h2>0x03 待学习重点</h2>
<ol>
<li>
<p>89C51的经验有多少可以迁移到STC32G上**(已完成)**</p>
<blockquote>
<p>目前看感觉在前学过的那些东西已经跟不上时代了,现在重新系统性的学习AI8051。</p>
</blockquote>
</li>
<li>
<p>如何将传统51单片机项目迁移到STC32G平台上**(已完成)**</p>
<blockquote>
<p>通过AiCube项目创建助手功能选择IO口、中断等功能类型,一键生成初始keil工程,然后把核心逻辑代码复制过来就能搞定!</p>
</blockquote>
</li>
<li>
<p>硬件USB控制器可以玩出什么花样?<strong>(持续学习 持续探索)</strong></p>
<ul>
<li>USB转双串口(其实是不是通过软件模拟可以实现USB转N串口?加上蓝牙模块可以实现无线串口调试了,再加上MAX232打上RJ45就可以用来配置交换机了。</li>
</ul>
</li>
<li>
<p>在用户代码中嵌入 USB-CDC 代码,实现一直修改代码一直USB不停电下载。<strong>(已完成)</strong></p>
</li>
<li>
<p>定时器一次只能定时一个此,如果有很多个任务怎么办?<strong>(已完成)</strong></p>
<blockquote>
<p>初步猜测可能性:1、在定时器函数内部增加if/else判断某个全局变量的值确定要执行哪一段语句。2、使用多个定时器。</p>
<p>答案:想的差不多,方法如下:</p>
<p>通过数组(<strong>包括结构体数组</strong>),定时器中断函数对数组的多个成员累加,在主函数判断实现多任务序列调度。</p>
</blockquote>
</li>
<li>
<p>如果我的函数执行的很慢,定时器等待的时间很短,会不会出现问题?<strong>(已完成)</strong></p>
<blockquote>
<p>会!如果太慢跟不上定时器的调用就会跳拍。但是可以通过累计需要调用函数的次数,每次执行完减一,达到不会漏执行的效果。</p>
</blockquote>
</li>
<li>
<p>74HC595会不会跟不上单片机的速度而导致了数据的丢失?如果会,那么速度多快就会丢失数据?<strong>(已完成)</strong></p>
<blockquote>
<p>实测实验箱<strong>44.2368MHz</strong>以上数码管显示会出现问题。)</p>
</blockquote>
</li>
</ol>
<p><img src="data/attachment/forum/202602/13/180754mtprln00lfzbnhf2.png" alt="image.png" title="image.png" /></p>
<h1>Jellyfish从89C51到AI8051学习贴第十四天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十一集 矩阵键盘的课后作业</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ul>
<li><strong>练习1:简易洗衣机面板</strong></li>
</ul>
<h2>0x02 学习心得</h2>
<ul>
<li>
<p><strong>扫描矩阵键盘思路</strong></p>
<blockquote>
<p><strong>逐行扫描法(最常用)</strong></p>
<ol>
<li><strong>初始化</strong>:将所有列线(Col)设为输入并上拉(高电平),行线(Row)设为输出。</li>
<li><strong>扫描</strong>:依次将某一行拉低(其余行为高)。</li>
<li><strong>判断</strong>:读取列线状态。如果有列线变低,说明该行与该列的交点按键被按下。</li>
<li><strong>计算</strong>:<code>键值 = 行号 × 总列数 + 列号</code>。</li>
</ol>
<p><strong>行列反转法(速度快)</strong></p>
<ol>
<li><strong>第一步</strong>:行输出全 0,列输入,读取列线值(记录哪一列为低)。</li>
<li><strong>第二步</strong>:列输出刚才读取到的值,行输入,读取行线值(记录哪一行为低)。</li>
<li><strong>合并</strong>:将两次读取的电平状态组合,即可精确定位坐标。</li>
</ol>
</blockquote>
<pre><code class="language-c">//先完成端口定义
#define ROW1 P06 //端口定义
#define ROW2 P07
#define COL1 P00
#define COL2 P01
#define COL3 P02
#define COL4 P03
//初始化的时候得使用准双向口
P0M0 = 0x00; P0M1 = 0x00;
//定时器数组如下
TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 1, 1, Seg_Task}, /* 显示函数: 1ms */
{0, 1000,1000, Time_Task}, /* 倒数函数: 1s */
{0, 10, 10, Key_Task}, /* 矩阵键盘按键函数: 10ms */
{0, 10, 10, Key32_Task}, /* P3.2开始按键检测函数: 10ms */
};
//关键函数
bit time_state=0;
u8 key_num = 0x01;
u8 last_key = 0xff; // 记录上次状态
u32 total_sec=0;
u8 Disp_Buf; // 定义一个全局或静态数组,存放 4 位数码管要显示的数字
// --- 数码管显示 ---
void Seg_Task(void){
if (Seg_no==0){
Display_Seg(SEG_NUM,~T_NUM);
}else if (Seg_no==1){
Display_Seg(SEG_NUM,~T_NUM);
}else if (Seg_no==2){
Display_Seg(SEG_NUM,~T_NUM);
}else if (Seg_no==3){
Display_Seg(SEG_NUM,~T_NUM);
}else if(Seg_no==4){
Display_Seg(SEG_NUM],~T_NUM);
}else if (Seg_no==5){
Display_Seg(SEG_NUM]|SEG_NUM,~T_NUM);
}else if (Seg_no==6){
Display_Seg(SEG_NUM],~T_NUM);
}else if (Seg_no==7){
Display_Seg(SEG_NUM],~T_NUM);
}
Seg_no++;
if (Seg_no>=8) Seg_no=0;
}
// --- 倒计时 ---
void Time_Task(){
u8 Second, Min;
if (total_sec!=0) {
// Hour = total_sec / 3600; // 算出小时
Min= (total_sec % 3600) / 60;// 算出分钟
Second = (total_sec % 60);
// Disp_Buf = Hour / 10; // 小时十位
// Disp_Buf = Hour % 10; // 小时个位
Disp_Buf = Min / 10; // 分钟十位
Disp_Buf = Min % 10; // 分钟个位
Disp_Buf = Second / 10; // 分钟十位
Disp_Buf = Second % 10; // 分钟个位
total_sec-=1;
};
}
// --- 矩阵键盘 ---
void Key_Task(void)
{
static u8 last_raw_key = 0xff; // 记录上一次扫描到的原始物理键值
u8 current_raw_key = 0xff; // 本次扫描到的原始物理键值
u8 r_temp = 0, c_temp = 0;
// --- 步骤一:扫描行 ---
COL1=0; COL2=0; COL3=0; COL4=0;
ROW1=1; ROW2=1;
//_nop_();//给电平建立时间
if(ROW1 == 0) r_temp = 0; // 第一行按下
else if(ROW2 == 0) r_temp = 4; // 第二行按下
else { goto scan_process; } // 没按键,直接跳到末尾清零
// --- 步骤二:扫描列 ---
COL1=1; COL2=1; COL3=1; COL4=1;
ROW1=0; ROW2=0;
//_nop_();// 给电平建立时间
if (total_sec!=0) return;//当前没有洗衣任务才允许重置倒数时间,放这里刚好代替延时
if(COL1 == 0) c_temp = 0;
else if(COL2 == 0) c_temp = 1;
else if(COL3 == 0) c_temp = 2;
else if(COL4 == 0) c_temp = 3;
else { goto scan_process; }
current_raw_key = r_temp + c_temp; // 计算出当前物理键值
scan_process:
// --- 步骤三:双稳态消抖 + 松手检测 ---
if(current_raw_key == last_raw_key) // 连续两次(20ms)读到一样的值
{
if(current_raw_key != 0xff && current_raw_key != key_num)// 只有当检测到确实有按键(不等于0xff),且跟上次存的值不一样时,才更新
{
key_num = current_raw_key;// 更新最终键值
}
}
last_raw_key = current_raw_key; // 备份本次结果给下一次比对
// 恢复扫描初态,准备下一次 10ms
COL1=0; COL2=0; COL3=0; COL4=0; ROW1=1; ROW2=1;
}
// --- 按键检测 ---
void Key32_Task(void)
{
if( P33 == 0 )
{
Key_Vol++;
if( Key_Vol==5 && total_sec==0 ) //当前没有洗衣任务才允许重置倒数时间
{
//按键按下的任务,模式1需要5分钟,模式2需要10分钟以此类推
total_sec=key_num*300;
}
}
else
{
Key_Vol = 0;
}
}
</code></pre>
</li>
</ul>
<h2>0x03 效果展示</h2>
<p><img src="data/attachment/forum/202602/13/213007cfo4lbb4e4zobbbt.gif" alt="VID202602132122541.gif" title="VID20260213212254 (1).gif" /></p>
<p><img src="data/attachment/forum/202602/15/010632oyvxvuiefvkkizrx.png" alt="image-20260213180916449.png" title="image-20260213180916449.png" /></p>
<h1>Jellyfish从89C51到AI8051学习贴第十五天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十二集 密码锁</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ul>
<li><strong>练习1:密码锁</strong></li>
</ul>
<h2>0x02 学习心得</h2>
<ul>
<li>
<p><strong>4种复位模式</strong></p>
<blockquote>
<ol>
<li>上电复位 (POR - Power-On Reset)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:当 VCC 电源从 0V 攀升至系统工作电压时,内部硬件检测到电压跨越阈值,自动触发。</li>
<li>特点:
<ul>
<li><strong>冷启动</strong>:这是最彻底的复位,会重置所有寄存器、I/O 状态及内部存储空间。</li>
<li><strong>标志位</strong>:通常在寄存器中表现为 <code>POF</code> (Power-On Flag)。</li>
</ul>
</li>
<li><strong>典型场景</strong>:设备第一次插入电池或接通电源。</li>
</ul>
<ol>
<li>低压复位 (LVR - Low Voltage Reset / Brown-out)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:系统运行过程中,电源电压跌落至设定的阈值(如 2.4V 或 3.3V)以下时触发。</li>
<li>特点:
<ul>
<li><strong>保护机制</strong>:防止电压过低导致 CPU 逻辑混乱、写 Flash 错误或程序跑飞。</li>
<li><strong>可调性</strong>:现代单片机通常允许在 ISP 下载软件 或软件寄存器中设置不同的掉电检测电压(LVD)。</li>
</ul>
</li>
<li><strong>典型场景</strong>:电池电量耗尽瞬间、大电流负载(如电机启动)导致电压掉闸。</li>
</ul>
<ol>
<li>复位脚复位 (NRST - External Reset Pin)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:外部复位引脚(RST/NRST)被施加了特定的电平信号(通常是<strong>持续一定时间的低电平</strong>,STC 某些型号为高电平)。</li>
<li>特点:
<ul>
<li><strong>硬件干预</strong>:由外部电路(如复位按键、外部监控芯片)物理控制。</li>
<li><strong>电路设计</strong>:通常需要外接 <strong>RC 复位电路</strong>(10k电阻 + 0.1μF电容)来过滤干扰波形。</li>
</ul>
</li>
<li><strong>典型场景</strong>:用户按下设备上的物理“Reset”小孔按键。</li>
</ul>
<ol>
<li>看门狗复位 (WDT - Watchdog Timer Reset)</li>
</ol>
<ul>
<li><strong>触发方式</strong>:看门狗计数器溢出。程序必须在规定时间内“喂狗”(重置计数值),如果程序死锁或跑飞未能及时喂狗,计数器归零即触发复位。</li>
<li>特点:
<ul>
<li><strong>自愈功能</strong>:是嵌入式系统摆脱“死机”状态的最后防线。</li>
<li><strong>软硬结合</strong>:需要在软件中初始化看门狗频率,并在主循环(<code>while(1)</code>)中妥善放置喂狗代码。</li>
</ul>
</li>
<li><strong>典型场景</strong>:程序因强电磁干扰进入死循环,或逻辑陷入阻塞无法退出。</li>
</ul>
</blockquote>
</li>
</ul>
<h3>计算看门狗超时时间(24Mhz下)</h3>
<div class="language-math">\frac{12\times 32768\times2^{6}}{24\times10^{6} }=1.048576秒</div>
<pre><code class="language-c">//初始化的时候要运行这个函数断开重置usb
void USB_Reset_U(void)
{
P3M0 = 0x00;
P3M1 = 0x00;
P3M0 &= ~0x03;
P3M1 |= 0x03;
USBCON = 0X00;
USBCLK = 0X00;
IRC48MCR = 0X00;
Delay10ms();
}
//配置看门狗寄存器
EN_WDT=1; //开启看门狗
IDL_WDT=1;//空闲模式继续累计看门狗
WDT_CONTR|=0x05; //配置喂狗超时时间,101 分频64,12x32768x2^6/(24*10^6)=1.048576
EA=1; //开启总中断
//定时器调度情况如下:
TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 1, 1, Seg_Task}, /* task 1 Period: 1ms */
{0, 10, 10, Key_Task},
{0, 1000,1000, Dog},
{0, 10, 10, Key33_Task},
};
//关键定义
u8 SEG_NUM[] = {
0x3F, /*'0', 0*/
0x06, /*'1', 1*/
0x5B, /*'2', 2*/
0x4F, /*'3', 3*/
0x66, /*'4', 4*/
0x6D, /*'5', 5*/
0x7D, /*'6', 6*/
0x07, /*'7', 7*/
0x7F, /*'8', 8*/
0x6F, /*'9', 9*/
0x77, /*'A', 10*/
0x7C, /*'B', 11*/
0x39, /*'C', 12*/
0x5E, /*'D', 13*/
0x79, /*'E', 14*/
0x71, /*'F', 15*/
0x40, /*'-', 16*/
0x00, /*' ', 17*/
0x80, /*'.', 18*/
0x5C, /*'O', 19*/
0x73, /*'P', 20*/
0x79, /*'E', 21*/
0x54, /*'N', 22*/
0x3E, /*'U', 23*/
};
u8 T_NUM[] =
{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
u8 Key_Vol = 0;
u8 Key33_Vol = 0;
u8 Seg_no = 0;
u8 Disp_Buf = {16, 16, 16, 16, 16, 16, 16, 16}; // 定义一个全局或静态数组,存放 8 位数码管要显示的数字
u8 open = {16, 16, 16, 16, 19, 20, 21, 22}; // 定义密码
u8 passwd = {1, 2, 3, 4, 5, 6, 7, 8}; // 定义密码
u8 version = {23, 1, 0}; // 定义密码
u8 dis_ver = 3;
//关键函数
// --- 数码管显示 1ms 调度任务逻辑 ---
//正常显示Disp_Buf中的内容,当dis_ver大于0时,后三位显示版本号,dis_ver会每秒减一。
void Seg_Task(void)
{
if (Seg_no == 0)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else if (Seg_no == 1)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else if (Seg_no == 2)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else if (Seg_no == 3)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else if (Seg_no == 4)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else if (Seg_no == 5)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else
{
Display_Seg(SEG_NUM], ~T_NUM);
}
}
else if (Seg_no == 6)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else
{
Display_Seg(SEG_NUM] | SEG_NUM, ~T_NUM);
}
}
else if (Seg_no == 7)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM], ~T_NUM);
}
else
{
Display_Seg(SEG_NUM], ~T_NUM);
}
}
Seg_no++;
if (Seg_no >= 8)
Seg_no = 0;
}
// --- 矩阵键盘 10ms 调度任务逻辑 ---
void Key_Task(void)
{
u8 i, j;
u8 current_raw_key = 0xff; // 本次扫描到的原始物理键值
u8 r_temp = 0, c_temp = 0;
// --- 步骤一:扫描行 ---
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
//_nop_();// 【核心修复】给电平建立时间
if (ROW1 == 0)
r_temp = 0; // 第一行按下
else if (ROW2 == 0)
r_temp = 4; // 第二行按下
else
{
goto scan_process;
} // 没按键,直接跳到末尾清零
// --- 步骤二:扫描列 ---
COL1 = 1;
COL2 = 1;
COL3 = 1;
COL4 = 1;
ROW1 = 0;
ROW2 = 0;
_nop_();// 给电平建立时间
if (COL1 == 0)
c_temp = 0;
else if (COL2 == 0)
c_temp = 1;
else if (COL3 == 0)
c_temp = 2;
else if (COL4 == 0)
c_temp = 3;
else
{
goto scan_process;
}
current_raw_key = r_temp + c_temp; // 计算出当前物理键值
scan_process:
// --- 步骤三:双稳态消抖 + 松手检测 ---
if (current_raw_key == 0xff)
{
Key_Vol = 0;
}
Key_Vol++;
if (current_raw_key != 0xff && Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),才更新
{
for (i = 0; i < 8; i++)
{
if (Disp_Buf == 16)
{
if (Disp_Buf == 22)
{
for (j = 0; j < 8; j++)
{
Disp_Buf = 16;
}
}
Disp_Buf = current_raw_key + 1;
CLR_WDT = 1; // 按键按下的时候喂狗
break;
}
}
// 这里可以触发一个“按键按下”的标志位,供外部使用
}
// 恢复扫描初态,准备下一次 10ms
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
}
// --- 软件复位按键判断 10ms 调度任务逻辑 ---
void Key33_Task(void)
{
if (P33 == 0)
{
Key33_Vol++;
if (Key33_Vol == 5)
{
IAP_CONTR = 0x20; //软件复位
}
}
else
{
Key33_Vol = 0;
}
}
// --- 看门狗及密码检测 1000ms 调度任务逻辑 ---
void Dog()
{
bit Is_Equal = 1; // 假设相等
u8 i;
if (Disp_Buf == 16)
{
CLR_WDT = 1; //当第一位是“-”时,喂狗。
}
if (dis_ver != 0)
{
dis_ver--; //显示版本号的倒计时。
}
if (Disp_Buf != 16 && Disp_Buf != open)
{
// 3. 比较内容
for (i = 0; i < 8; i++)
{
if (Disp_Buf != passwd)
{
Is_Equal = 0;
break;
}
}
if (Is_Equal)
{
for (i = 0; i < 8; i++)
{
Disp_Buf = open;
}
}
else
{
for (i = 0; i < 8; i++)
{
Disp_Buf = 16;
}
}
}
}
</code></pre>
<ul>
<li>
<p><strong>用户系统区</strong></p>
<p>新发现一个叫做用户系统区的东西,可以用来实现OTG升级等功能。</p>
<blockquote>
<p>新设计<strong>Ai8051U</strong>-34K64-LQFP48/LQFP44/PDIP40,可以指定用户区的64K的最后部分做用户自己完全独立的User_ISP_BootLoader区,上电后,从完全独立于用户完整的64K程序区的,独立的系统ISP_BootLoader区运行,判断要不要下载用户程序后,会复位到User_ISP_BootLoader区,复位到User_ISP_BootLoader区后,User_ISP_BootLoader区再判断是否要更新用户自己的最后要运行的用户程序。</p>
</blockquote>
</li>
<li>
<p><strong>必须IDL_WDT=1;//空闲模式继续累计看门狗,不然看门狗溢出时间老是算不对</strong></p>
</li>
</ul>
<img src="data/attachment/forum/202602/15/015037dr4ddq0jydyk5k0z.gif" width="100%" />
<p><img src="data/attachment/forum/202602/15/165230gs6oww9xotz2ws4y.png" alt="image.png" title="image.png" /></p>
<h1>Jellyfish从89C51到AI8051学习贴第十六天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十三集 外部中断的课后作业</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li>练习1:雕刻机防护系统</li>
</ol>
<h2>0x02 学习心得</h2>
<ul>
<li>外部中断分<strong>上升下降沿</strong>(IT0=0时)和<strong>仅下降沿</strong>(IT0=1时),通过**IT**进行配置。</li>
</ul>
<p><img src="data/attachment/forum/202602/15/171755g5jyjz1azhsp3psw.png" alt="image.png" title="image.png" /></p>
<ul>
<li>
<p>需要使用LED0代表激光工作状态,矩阵键盘1代表开启激光,但是他们的P00端口重复了,屏蔽掉了矩阵键盘的0和4按键。</p>
<pre><code class="language-c">//初始化寄存器,使能中断
IT0=1; //配置p3.2的init0中断
EX0=1; //开启init0中断
EA=1; //开启全局中断
//定时器调度结构体
TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 10, 10, Key_Task},
{0, 1000, 1000, led_work},
};
// --- 控制激光点亮函数 1000ms 调度任务逻辑 ---
void led_work(){
if (led && P32!=0) P00=0; //当全局控制变量为真,且p32不为0才能点亮激光
else P00=1;
}
// --- 矩阵键盘 10ms 调度任务逻辑 ---
void Key_Task(void)
{
u8 i, j;
u8 current_raw_key = 0xff; // 本次扫描到的原始物理键值
u8 r_temp = 0, c_temp = 0;
// --- 步骤一:扫描行 ---
// COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
//_nop_();// 【核心修复】给电平建立时间
if (ROW1 == 0)
r_temp = 0; // 第一行按下
else if (ROW2 == 0)
r_temp = 4; // 第二行按下
else
{
goto scan_process;
} // 没按键,直接跳到末尾清零
// --- 步骤二:扫描列 ---
// COL1 = 1;
COL2 = 1;
COL3 = 1;
COL4 = 1;
ROW1 = 0;
ROW2 = 0;
_nop_();// 给电平建立时间
if (COL1 == 0)
c_temp = 0;
else if (COL2 == 0)
c_temp = 1;
else if (COL3 == 0)
c_temp = 2;
else if (COL4 == 0)
c_temp = 3;
else
{
goto scan_process;
}
current_raw_key = r_temp + c_temp; // 计算出当前物理键值
scan_process:
// --- 步骤三:双稳态消抖 + 松手检测 ---
if (current_raw_key == 0xff)
{
Key_Vol = 0;
}
Key_Vol++;
if (current_raw_key != 0xff && Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),且跟上次存的值不一样时,才更新
{
if (current_raw_key==1 && P32!=0) led = 1;
}
// COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
}
//中断函数
void INT0_Isr() interrupt 0
{
led=0; //关闭激光全局控制变量
P00=1; //马上关闭激光
}
</code></pre>
</li>
</ul>
<img src="data/attachment/forum/202602/15/172712lkbs3r5bxs6kxswb.gif" width="100%">
<h1>Jellyfish从89C51到AI8051学习贴第十六天!</h1>
<blockquote>
<p>今天继续学习《8051U深度入门到32位51大型实战教学视频》<strong>第十四集 IO中断</strong>。</p>
</blockquote>
<h2>0x01 学习重点</h2>
<ol>
<li>练习1:多路抢答器</li>
</ol>
<p><img src="data/attachment/forum/202602/15/192843c3kqy8gig7og57g7.png" alt="image-20260215173837352.png" title="image-20260215173837352.png" /></p>
<h2>0x02 学习心得</h2>
<ul>
<li><strong>IO口的准双向口的高电平会带弱上拉,此时外部悬空则无法触发到上升沿。</strong></li>
<li><strong>IO口的准双向口的配置成底电平后就无法再次检测到上升沿了。</strong></li>
<li>同一个IO口中断<strong>触发过程中</strong>,<strong>再次触发</strong>会先<strong>等待上一次中断完毕</strong>后再响应新得中断。此外等待过程中<strong>不响应同样的第三个中断</strong>。</li>
<li>通过复用矩阵键盘的P00-P04(P00作为抢答复位按键)实现多路抢答器。</li>
</ul>
<pre><code class="language-c">//复用矩阵键盘的P00-P04(P00作为抢答复位按键)
//直接给595发第一个数码管为0
Init_595();
Display_Seg(0x3F, ~0x01);
//矩阵键盘配置P06为低电平,这样可以给P00-P04提供下降沿
P06=0;
//配置为下降沿触发模式
P0IM0&=0xF0;
P0IM1&=0xF0;
P0INTE|=0x0F;
P0WKUE|=0x0f; //设置P0口中断唤醒省电模式
EA=1;
//中断函数
void P0_Isr() interrupt 37
{
//如果有人胜利了就不要再响应
static win=0;
unsigned char intf;
intf = P0INTF;
//判断是谁触发了中断
if (intf)
{
P0INTF = 0x00;
if (intf & 0x01)
{
//P00作为恢复抢答按键,顺便把数码管显示0
win=0;
Init_595();
Display_Seg(0x3F, ~0x01);
}else
if (intf & 0x02 && win == 0)
{
//P01中断,把胜利标志位置1,再把数码管显示1
win=1;
Init_595();
Display_Seg(0x06, ~0x01);
}else
if (intf & 0x04 && win == 0)
{
win=1;
Init_595();
Display_Seg(0x5B, ~0x01);
}else
if (intf & 0x08 && win == 0)
{
win=1;
Init_595();
Display_Seg(0x4F, ~0x01);
}else
{
return;
}
}
}
</code></pre>
<img src="data/attachment/forum/202602/15/192903mtoz67t6vaaxpt9z.gif" width="100%">
<video src="https://www.stcaimcu.com/forum.php?mod=attachment&aid=MTMyMDcwfGYyY2FlMWMwfDE3NzExNTQ4MzF8MjEzMDV8MA">