神农鼎 发表于 2026-1-27 08:21:15

在用户代码中嵌入 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

jellyfish 发表于 2026-1-29 02:16:40

<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 &lt; 8; i++) {
      HC595_DS = dat &gt;&gt; 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
      dat &lt;&lt;= 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>

jellyfish 发表于 2026-1-29 22:24:45

<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 &lt; 8; i++) {
      HC595_DS = dat &gt;&gt; 7; // 取最高位送到 DS或者直接移位后使用CY寄存器
      dat &lt;&lt;= 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>

jellyfish 发表于 2026-1-31 02:25:27

<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&lt;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&lt;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&lt;8; i++){
      dat &lt;&lt;= 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&gt;=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( &quot;按键单击\r\n&quot; );
                }
        }
        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>

jellyfish 发表于 2026-1-31 18:22:52

<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&lt;8;i++){
                if(DisplayStr=='-'){
                  if(DisplayStr == 'n'){
                        strcpy(DisplayStr, &quot;--------&quot;);
                  }
                  DisplayStr=UsbOutBuffer;
                  if(i==7){
                        // 3. 使用 strcmp 比较字符串内容
                        if(strcmp(DisplayStr, &quot;12345678&quot;) == 0) {
                            strcpy(DisplayStr, &quot;----open&quot;);
                        }else{
                            strcpy(DisplayStr, &quot;--------&quot;);
                        }
                  }
                  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[] = &quot;--------&quot;;
void Display_Task(){
    if(strcmp(DisplayStr, &quot;----open&quot;) == 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>

jellyfish 发表于 2026-2-3 01:43:52

<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>

jellyfish 发表于 2026-2-13 21:30:57

<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&gt;=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 &amp;&amp; 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 &amp;&amp; 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>

jellyfish 发表于 2026-2-15 01:50:54

<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 &amp;= ~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 &gt;= 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 &amp;&amp; Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),才更新
    {

      for (i = 0; i &lt; 8; i++)
      {
            if (Disp_Buf == 16)
            {
                if (Disp_Buf == 22)
                {
                  for (j = 0; j &lt; 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 &amp;&amp; Disp_Buf != open)
    {
      // 3. 比较内容
      for (i = 0; i &lt; 8; i++)
      {
            if (Disp_Buf != passwd)
            {
                Is_Equal = 0;
                break;
            }
      }
      if (Is_Equal)
      {
            for (i = 0; i &lt; 8; i++)
            {
                Disp_Buf = open;
            }
      }
      else
      {
            for (i = 0; i &lt; 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%" />

jellyfish 发表于 2026-2-15 17:28:35

<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 &amp;&amp; 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 &amp;&amp; Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),且跟上次存的值不一样时,才更新
    {
      if (current_raw_key==1 &amp;&amp;   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%">

jellyfish 发表于 2026-2-15 19:31:49

<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&amp;=0xF0;
P0IM1&amp;=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 &amp; 0x01)
      {
            //P00作为恢复抢答按键,顺便把数码管显示0
            win=0;
            Init_595();
            Display_Seg(0x3F, ~0x01);
      }else
      if (intf &amp; 0x02 &amp;&amp; win == 0)
      {
            //P01中断,把胜利标志位置1,再把数码管显示1
            win=1;
            Init_595();
            Display_Seg(0x06, ~0x01);
            
      }else
      if (intf &amp; 0x04 &amp;&amp; win == 0)
      {
            win=1;
            Init_595();
            Display_Seg(0x5B, ~0x01);
      }else
      if (intf &amp; 0x08 &amp;&amp; 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&amp;aid=MTMyMDcwfGYyY2FlMWMwfDE3NzExNTQ4MzF8MjEzMDV8MA">
页: 1 [2] 3
查看完整版本: Jellyfish从89C51到AI8051学习贴