找回密码
 立即注册
楼主: LilMonsterOvO

【STC单片机学习全记录】从点亮LED到玩转ADC,我的嵌入式进阶之路

[复制链接]
  • 打卡等级:以坛为家III
  • 打卡总天数:623
  • 最近打卡:2026-02-12 10:50:13

33

主题

2870

回帖

6425

积分

论坛元老

积分
6425
发表于 2026-1-5 16:28:27 | 显示全部楼层
LilMons*** 发表于 2026-1-5 16:13
我就是为了用最新的STC32G144K246,先用A8051U来熟悉一下STC的开发流程

参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-6 01:07:01 | 显示全部楼层
【STC单片机入门实战】Day 2:解放双手!实现USB不停电下载(第4集学习笔记)
记录从STM32转战STC单片机第2天,解决最烦人的手动下载问题
📅 整体学习路线回顾根据课程目录,我的完整学习路径如下:
第一阶段:基础入门(第2-5集)
  • ✅ Day 1:硬件介绍 + 点亮LED(第2-3集)
  • 🔄 Day 2:USB不停电下载(第4集)​ ← 今天学习
  • Day 3:C语言基础(第5集)

第二阶段:外设入门(第6-9集)
  • Day 4:I/O输入输出(第6集)
  • Day 5:定时器中断(第7集)
  • Day 6:定时器调度任务(第8集)
  • Day 7:数码管显示(第9集)

第三阶段:进阶应用(第10-15集)
  • 虚拟键盘、矩阵按键、复位系统、外部中断、IO中断、定时器计数器等

第四阶段:高级功能(第16-23集)
  • DS18B20、串口通信、ADC、Flash、比较器、PCA等

今日学习重点:USB不停电下载昨天点亮LED虽然成功了,但每次下载程序都要手动按P3.2按钮的操作,引起了我的注意。这确实是个效率瓶颈!今天学习的第4集内容就是解决这个问题——实现USB不停电下载,彻底解放双手。
STM32STC的下载体验对比思考:在STM32开发中,我习惯了ST-Link的一键下载调试。STC这种需要手动断电的下载方式,在初期调试频繁时会严重影响效率。这也是我从STM32转来时最需要适应的点之一。
对比维度
STM32
STC (传统方式)
STC (USB不停电)

下载方式
SWD/JTAG调试器
串口+手动断电按钮
直接USB连接

硬件需求
ST-Link/V2等调试器
USB转串口工具
仅USB线

操作步骤
编译→一键下载
编译→手动断电→按按钮→下载
编译→自动下载

开发效率
⭐⭐⭐⭐⭐
⭐⭐
⭐⭐⭐⭐

学习成本
中等
低,但操作繁琐
中等


核心原理解析传统下载的问题所在STC单片机默认是通过系统ISP监控程序实现下载的,这个监控程序在芯片上电时会先运行,检测是否有下载命令。但用户程序运行后,监控程序就被“覆盖”了,要重新进入下载模式就必须:
  • 断电复位
  • 按住P3.2强制进入ISP模式
  • 重新上电

USB不停电下载的聪明解法STC的方案是在用户程序中嵌入一个“后门”
  • USB虚拟串口(CDC):程序运行后,USB接口模拟成一个串口设备
  • 特殊握手协议:当STC-ISP软件发送特定命令字符串@STCISP#时
  • 软件复位:程序识别到命令后,自动软复位并跳转到ISP监控程序
  • 无缝切换:实现从用户程序到下载模式的平滑过渡


📋 实现步骤详解第一步:获取必要的库文件从STC官网(深圳国芯人工智能有限公司-库函数)下载USB库文件,关键文件包括:
  • stc_usb_cdc_32.LIB→ USB通信库(二进制,保护源码)
  • stc32_stc8_usb.h→ 头文件(接口声明)

注意:STC提供8位和32位两种库,AI8051U是32位内核,要选择对应的32位库文件。
第二步:工程配置与移植1. 库文件添加
  1. // 工程结构变化:
  2. Demo.Uvproj
  3. ├── main.c          // 用户主程序
  4. ├── AI8051U.H       // 芯片头文件
  5. └── stc_usb_cdc_32.LIB  // ← 新增的USB库
复制代码

2. 关键代码分析
  1. /*------------------------------------------------------------------
  2. * 文件名称:STC32G_USB_CDC_Demo.c
  3. * 功能描述:STC32G系列单片机USB CDC虚拟串口通信示例程序
  4. *           演示如何通过USB接口实现单片机与电脑间的数据通信
  5. * 硬件平台:STC系列开发板(屠龙刀、擎天柱等)
  6. * 开发环境:Keil C51
  7. * 作者:根据STC32G视频教程整理
  8. * 日期:2026年1月6日
  9. *------------------------------------------------------------------*/
  10. /*--- 头文件包含区域 ---*/
  11. #include "stc32g.h"           // STC32G系列单片机专用头文件,包含所有寄存器定义
  12. #include "stc32_stc8_usb.h"   // STC官方USB库头文件,提供CDC/HID功能支持
  13. #include "math.h"             // 数学函数库,本例中虽未直接使用,但为后续功能扩展预留
  14. #include "stdio.h"            // 标准输入输出库,支持printf_usb格式化输出功能
  15. /*--- 主函数:程序入口点 ---*/
  16. void main()
  17. {
  18.     /*--- 系统初始化部分 ---*/
  19.    
  20.     /* 关键步骤1:使能扩展寄存器访问权限
  21.      * P_SW2是特殊功能寄存器,其最高位(bit7)控制XFR扩展寄存器的访问
  22.      * 0x80 = 1000 0000二进制,通过"或等于"操作只设置bit7,不影响其他位
  23.      * 这是访问USB相关特殊寄存器的必要步骤[3](@ref)
  24.      */
  25.     P_SW2 |= 0x80;
  26.    
  27.     /*--- GPIO端口模式配置 ---*/
  28.     /* 将所有IO口(P0-P7)设置为准双向口模式
  29.      * 准双向口是传统51单片机标准模式,兼具输入输出能力
  30.      * 每个端口由两个寄存器控制:PxM1和PxM0
  31.      * 配置为00:准双向口;01:推挽输出;10:高阻输入;11:开漏输出
  32.      * 这里统一配置为准双向口,确保USB通信时端口状态稳定[3,6](@ref)
  33.      */
  34.     P0M1 = 0x00;   P0M0 = 0x00;  // 配置P0口
  35.     P1M1 = 0x00;   P1M0 = 0x00;  // 配置P1口  
  36.     P2M1 = 0x00;   P2M0 = 0x00;  // 配置P2口
  37.     P3M1 = 0x00;   P3M0 = 0x00;  // 配置P3口(特别注意:P3.0/P3.1与USB D-/D+共用)
  38.     P4M1 = 0x00;   P4M0 = 0x00;  // 配置P4口
  39.     P5M1 = 0x00;   P5M0 = 0x00;  // 配置P5口
  40.     P6M1 = 0x00;   P6M0 = 0x00;  // 配置P6口
  41.     P7M1 = 0x00;   P7M0 = 0x00;  // 配置P7口
  42.    
  43.     /*--- USB模块初始化 ---*/
  44.     /* usb_init()函数是STC官方USB库的核心初始化函数
  45.      * 该函数会自动配置以下内容:
  46.      * 1. 初始化USB时钟源(内部48MHz IRC)
  47.      * 2. 配置USB控制寄存器(USBCON, USBCLK等)
  48.      * 3. 设置USB端点缓冲区和描述符表
  49.      * 4. 将P3.0/P3.1设置为高阻输入模式,避免影响USB D-/D+信号质量
  50.      * 此函数调用后,USB硬件模块开始工作,等待电脑枚举识别[6](@ref)
  51.      */
  52.     usb_init();
  53.    
  54.     /*--- 中断系统使能 ---*/
  55.     /* EA = 1:开启51单片机全局中断开关
  56.      * 类似于STM32中的__enable_irq()功能
  57.      * 这是USB中断正常工作的重要前提条件
  58.      * 注意:USB中断在stc32_stc8_usb.h中已有默认的中断服务函数
  59.      */
  60.     EA = 1;
  61.     /*--- 主循环:程序核心逻辑 ---*/
  62.     while (1)
  63.     {
  64.         /* bUsbOutReady是USB库定义的标志变量
  65.          * 当电脑通过USB虚拟串口发送数据到单片机时,该标志会自动置1
  66.          * 这种查询方式比中断方式更简单可靠,适合初学者使用
  67.          */
  68.         if (bUsbOutReady)
  69.         {
  70.             /* 示例代码:通过printf_usb向电脑发送调试信息
  71.              * printf_usb是USB库提供的格式化输出函数,用法与标准printf相同
  72.              * OutNumber变量包含本次接收到的数据字节数
  73.              * 实际应用中,可以根据接收到的数据内容进行相应处理
  74.              */
  75.             
  76.             // 发送接收到的数据字节数(演示用,实际应用可修改)
  77. //            USB_SendData(UsbOutBuffer, OutNumber);   // 发送接收数据原样返回(测试用)
  78.             
  79.             /* 使用printf_usb输出格式化调试信息
  80.              * 注意:每个printf_usb调用都会作为一个完整的USB数据包发送
  81.              * "\n"是换行符,使输出在串口助手中显示更整齐
  82.              */
  83.             printf_usb("1. Read Num:%d\n", OutNumber);  // 第一次输出
  84.             printf_usb("2. Read Num:%d\n", OutNumber);  // 第二次输出  
  85.             printf_usb("3. Read Num:%d\n", OutNumber);  // 第三次输出
  86.             printf_usb("4. Read Num:%d\n", OutNumber);  // 第四次输出
  87.             
  88.             /* 重要:标记当前USB数据包处理完成
  89.              * 该函数会清除bUsbOutReady标志,使能接收下一个数据包
  90.              * 如果没有调用此函数,USB将无法继续接收新数据
  91.              * 这是STC USB库的重要工作机制[6](@ref)
  92.              */
  93.             usb_OUT_done();
  94.         }
  95.         
  96.         /* 此处可以添加其他应用代码
  97.          * 由于USB通信采用查询方式,需要确保主循环执行时间不会太长
  98.          * 如果处理任务较重,建议使用定时器中断或状态机架构
  99.          */
  100.     }
  101. }
  102. /*=== 程序使用说明和开发建议 ===*/
  103. /*
  104. * 1. 硬件连接注意事项:
  105. *    - 确保USB数据线质量良好,MicroUSB接口连接可靠
  106. *    - P3.0/P3.1专用于USB通信,不要作为普通IO使用
  107. *    - 电脑端需要安装STC USB驱动(STC-ISP软件自带)
  108. *
  109. * 2. 编译环境配置:
  110. *    - 在Keil项目中需要添加STC官方提供的USB库文件(.LIB)
  111. *    - 设置正确的头文件包含路径
  112. *    - 根据具体芯片型号选择正确的内存模式
复制代码

第三步:STC-ISP软件配置

STC-ISP软件配置

STC-ISP软件配置
必须勾选的三个选项:
  • 使用默认内部自定义命令​ → 对应@STCISP#
  • 下次使用ID接口进行ISP下载​ → 实现后续下载
  • 每次下载前都先发送自定义命令​ → 自动化发送密钥

工作流程:Keil编译生成HEX → STC-ISP检测到文件变化 → 自动发送@STCISP#命令 → 单片机收到后软复位进入下载模式 → 自动开始编程

有趣的知识点笔记1. 查询模式 vs 中断模式教程推荐使用查询模式,原因很实际:
  • 查询模式:在主循环中定期检查USB状态
  • 中断模式:USB事件触发中断立即响应
  • 选择原因:查询模式代码更简单,不易因中断嵌套产生问题,适合初学者

2. 或等于(|=)操作的重要性
  1. P_SW2 |= 0x80;  // 正确:只改第7位,不影响其他位
  2. P_SW2 = 0x80;   // 危险:清零了所有其他位!
复制代码
重要:寄存器操作中,|=是“置位”操作,=是“赋值”操作。STC很多寄存器有保留位或默认配置,错误使用=可能导致系统异常。

总结与期待今天虽然只是理论学习,但USB不停电下载这个功能让我对STC单片机的设计理念有了新认识:
  • 用户思维:从实际开发痛点出发设计功能
  • 渐进式:先解决“有没有”,再考虑“好不好”
  • 生态思维:软硬件工具链的协同设计

最期待的时刻:收到“擎天柱最小系统板”后,我要验证的第一件事就是这个USB不停电下载。从手动到自动的体验提升,是开发效率的关键一步!
作为有STM32背景的学习者,我越来越理解:不同的芯片有不同的哲学。STM32追求强大和通用,STC追求实用和经济。在资源受限的环境中寻找优雅的解决方案,这是一种不同的技术乐趣。
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-7 01:30:21 | 显示全部楼层
【STC单片机入门实战】Day 3:夯实C语言基础,为32位8051编程铺路(第5集学习笔记)一、核心知识点回顾1. USB-CDC串口printf函数实现
  • 功能:将printf函数重定向到USB-CDC串口,用于调试输出
  • 实现方式:通过宏定义将printf重定向到gap_printf_HID函数
  • 使用方法:在USB库中打开printf的HID宏定义(去掉反斜杠注释)

2. 数的进制转换
  • 二进制:由0和1组成,计算机内部存储格式
  • 十进制:日常使用的计数方式
  • 十六进制:编程中常用,以0x开头表示
  • 转换方法:使用程序员计算器或按位权展开计算

3. 变量的基本类型
  • 8位无符号整数:unsigned char,范围0-255
  • 8位有符号整数:signed char,范围-128~127
  • 16位无符号整数:unsigned int,范围0-65535
  • 16位有符号整数:signed int,范围-32768~32767

4. C语言常用运算符
  • 算术运算符:+、-、*、/、%(取余)、++、--
  • 关系运算符:>、<、>=、<=、==、!=
  • 逻辑运算符:&&(与)、||(或)、!(非)
  • 位运算符:&(与)、|(或)、^(异或)、~(取反)、<<(左移)、>>(右移)
  • 赋值运算符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=

二、工程代码分析1. 工程文件结构05.C语言基础/
├── ai8051u.h           // 8051U芯片头文件
├── demo.uvproj         // Keil工程文件
├── main.c              // 主程序文件
├── stc32_stc8_usb.h    // STC USB库头文件
├── stc_usb_cdc_32.LIB  // USB CDC库文件
├── sha256.h            // SHA-256头文件
└── sha256.c            // SHA-256实现文件
2. 主程序代码分析2.1 宏定义部分#define u8  unsigned char    // 8位无符号整数
#define u16 unsigned int     // 16位无符号整数
  • 作用:简化变量定义,提高代码可读性
  • 使用:后续定义变量时可直接使用u8代替unsigned char

2.2 全局变量定义u8 X = 200;
u8 Y = 10;
  • 类型:u8(8位无符号整数)
  • 范围:0-255,X=200和Y=10均在此范围内

2.3 主函数结构void main(void)
{
    // 初始化代码
    // USB初始化
    // 主循环
}
2.4 USB初始化与配置usb_init();                                     // USB CDC 接口初始化
IE2 |= 0x80;                                    // 使能USB中断
EA = 1;                                         // 全局中断使能
while (DeviceState != DEVSTATE_CONFIGURED);     // 等待USB配置完成
  • usb_init():初始化USB CDC功能
  • IE2 |= 0x80:启用USB中断
  • EA = 1:打开全局中断开关
  • 等待USB配置完成:确保USB设备正常工作后再进入主循环

2.5 主循环与USB数据处理while(1)
{
    if (bUsbOutReady)                           // 检测到接收到数据
    {
        if( X && Y )    // 如果X和Y都为真
        {
            printf("条件为真\r\n");
        }
        
        usb_OUT_done();                         // 清除接收标志
    }
}
  • bUsbOutReady:USB接收就绪标志
  • printf():重定向到USB-CDC的打印函数
  • usb_OUT_done():通知USB模块数据已处理完成

三、关键函数解析1. printf函数
  • 原型:#define printf gap_printf_HID
  • 功能:将格式化字符串输出到USB-CDC串口
  • 使用示例
    printf("X / Y = %u \r\n",(u16)(X/Y));
    printf("X %% Y = %u \r\n",(u16)(X%Y));
  • 格式化说明符
    • %d:十进制有符号整数
    • %u:十进制无符号整数
    • %s:字符串
    • %%:输出百分号


2. usb_init()
  • 功能:初始化USB CDC功能
  • 调用时机:主函数开始,系统初始化阶段
  • 返回值:无

3. usb_OUT_done()
  • 功能:通知USB模块数据已处理完成
  • 调用时机:处理完USB接收数据后
  • 返回值:无

四、编程技巧总结1. 宏定义简化类型声明
  • 技巧:使用#define为常用数据类型创建别名
  • 好处:提高代码可读性,方便统一修改类型
  • 示例
    #define u8  unsigned char
    #define u16 unsigned int
2. 变量类型选择
  • 原则:根据变量实际取值范围选择合适类型
  • 注意事项:避免溢出,例如8位变量最大只能存储255
  • 示例
    // 错误示例:200*10=2000,超过u8范围
    u8 X=200,Y=10,Z;
    Z=X*Y; // 结果错误,溢出

    // 正确示例:使用u16避免溢出
    u16 Z;
    Z=(u16)X*Y; // 结果正确
3. printf函数使用
  • 换行符:使用\r\n确保跨平台兼容性
  • 百分号输出:使用%%输出单个百分号
  • 类型转换:输出前进行类型转换,避免格式不匹配

4. 条件判断
  • 逻辑:0为假,非0为真
  • 示例
    if( X && Y ) // X和Y都非0时为真
    {
        // 执行代码
    }
5. 运算符优先级
  • 算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
  • 注意事项:不确定优先级时使用括号

五、学习心得1. C语言基础的重要性
  • 本节课内容是STC 32位8051编程的基础
  • 掌握C语言核心概念是后续学习单片机外设、通信协议等内容的前提
  • 良好的C语言基础能提高代码质量和调试效率

2. 实践出真知
  • 通过USB-CDC的printf功能,可以方便地进行代码调试
  • 实际编写代码并观察运行结果,有助于加深对知识点的理解
  • 遇到问题时,结合理论知识和实际现象分析,提高解决问题的能力

3. 编程规范的养成
  • 宏定义、变量命名、代码缩进等良好习惯,有助于提高代码可读性和可维护性
  • 注释的合理使用,便于后续代码修改和他人阅读

4. 重点关注内容
  • 变量类型与溢出问题:在单片机编程中尤为重要
  • printf函数的灵活运用:调试的重要工具
  • 运算符的正确使用:特别是位运算和逻辑运算

六、课后练习
  • 尝试修改main.c中的变量X和Y的值,观察printf输出结果
  • 实现不同进制数的转换与输出
  • 练习使用不同的运算符,观察运算结果
  • 编写代码实现简单的数学运算,如加减乘除
  • 尝试使用位运算实现某些功能,如位清零、位置1等

七、SHA256算法实现7.1 为什么要学习SHA256?
之前的课程中,我们学习了C语言的基础知识,包括变量类型、运算符、函数等。现在,我将这些知识应用到实际的算法实现中——SHA256哈希算法。
哈希函数在单片机领域有很多应用:
  • 数据完整性验证:确保数据在传输或存储过程中没有被篡改
  • 密码存储:安全地存储用户密码(虽然实际应用中还需要加盐)
  • 数字签名:验证数据的来源和完整性
  • 唯一标识符:为数据生成唯一的标识符

学习SHA256算法,不仅可以巩固之前的C语言基础,还能深入理解密码学的核心概念。
7.2 SHA256算法原理
SHA-256(Secure Hash Algorithm 256-bit)是美国国家安全局(NSA)设计的一种密码散列函数,属于SHA-2家族,符合FIPS 180-4标准。它将任意长度的输入数据转换为256位(32字节)的固定长度输出,称为消息摘要
核心步骤(结合之前的C语言知识):
  • 数据填充
    • 首先添加一个1位(对应二进制的10000000,即十六进制0x80)
    • 然后添加若干个0位,使数据长度模512等于448位
    • 最后添加64位的原始数据长度(使用大端格式,之前学过的字节序概念)

  • 初始化哈希值
    • 使用8个32位的初始哈希值(H0-H7)
    • 这些值来自于前8个素数(2, 3, 5, 7, 11, 13, 17, 19)的平方根的小数部分的前32位
    • 对应C语言中的uint32_t state[8]数组

  • 处理512位数据块
    • 将每个512位数据块转换为16个32位字(W[0]-W[15])—— 使用位运算和类型转换
    • 通过扩展函数生成额外的48个32位字(W[16]-W[63])—— 使用循环和位运算
    • 执行64轮哈希计算,每轮使用不同的常量K和消息字W—— 大量使用位运算和循环
    • 更新哈希值状态—— 数组操作

  • 生成最终哈希值
    • 将8个32位哈希值连接起来,形成256位的最终哈希值
    • 转换为大端格式,方便存储和传输


7.3 与之前课程知识点的联系
之前课程知识点
在SHA256中的应用

变量类型使用uint8_t、uint32_t、uint64_t等固定宽度类型,确保跨平台兼容性
位运算大量使用循环右移、异或、与、或等位运算,是SHA256的核心
函数设计将复杂算法拆分为init、update、final等简单函数,符合模块化设计原则
数组操作使用数组存储哈希状态、消息调度数组等
循环结构使用for循环处理数据块、执行64轮哈希计算
指针操作使用指针处理输入数据,提高效率
条件判断处理缓冲区状态、填充规则等
十六进制转换将二进制哈希值转换为可读的十六进制字符串
8.4 实现思路8.4.1 结构体设计typedef struct {
    uint32_t state[8];          // 当前哈希值状态 (H0-H7)
    uint64_t bit_count;         // 输入数据的总位数 (模 2^64)
    uint8_t buffer[64];         // 数据缓冲区,用于存储不足512位的数据块
} sha256_context;
  • state:存储当前的8个哈希值
  • bit_count:记录输入数据的总位数,确保正确处理大文件
  • buffer:临时存储不足512位的数据块,当积累到512位时进行处理

7.4.2 核心函数设计(我的实现思路)
  • sha256_init():初始化SHA-256上下文,设置初始哈希值
    • 学习笔记:使用宏定义的初始哈希值,确保每次初始化都一样

  • sha256_update():处理输入数据,将数据填充到缓冲区
    • 学习笔记:这个函数可以多次调用,适合处理大文件
    • 之前学过缓冲区的概念,这里就是缓冲区的实际应用

  • sha256_compress():核心压缩函数,处理512位数据块
    • 学习笔记:这是SHA-256最复杂的部分,包含64轮计算
    • 大量使用之前学过的位运算

  • sha256_final():完成哈希计算,生成最终哈希值
    • 学习笔记:处理剩余数据,添加填充位和长度信息

  • sha256():一次性计算哈希值的便捷函数
    • 学习笔记:内部调用init、update、final,简化使用

  • sha256_to_hex():将哈希值转换为十六进制字符串
    • 学习笔记:使用之前学过的进制转换知识


7.5 关键代码解析(我的学习心得)7.5.1 初始哈希值#define SHA256_INITIAL_STATE {
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
}
  • 学习笔记:这些值是SHA-256标准规定的,来自于前8个素数的平方根
  • 之前学过宏定义,这里用宏来定义初始哈希值,方便使用

7.5.2 K常量static const uint32_t K[64] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
    // ... 共64个常量
};
  • 学习笔记:这些常量来自于前64个素数的立方根
  • 使用static const修饰,确保它们在编译时就被确定,提高运行效率

7.5.3 压缩函数核心for (i = 0; i < 64; i++) {
    uint32_t S1 = right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25);
    uint32_t ch = (e & f) ^ ((~e) & g);
    uint32_t temp1 = h + S1 + ch + K + W;
    uint32_t S0 = right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22);
    uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
    uint32_t temp2 = S0 + maj;

    /* 更新工作变量 */
    h = g;
    g = f;
    f = e;
    e = d + temp1;
    d = c;
    c = b;
    b = a;
    a = temp1 + temp2;
}
  • 学习笔记:这是SHA-256最核心的部分,每轮计算都使用位运算
  • 之前学过的位运算(右移、异或、与、或)在这里得到了充分应用
  • 每轮更新8个工作变量,形成一个迭代过程

7.6 遇到的问题及解决方案(我的调试经历)
  • 问题:在sha256_update()函数中,变量j未声明解决方案:在使用前添加size_t j;声明学习心得:编译时要仔细检查警告信息,变量一定要先声明再使用
  • 问题:缓冲区处理逻辑复杂,容易出错解决方案:分步骤处理:
    • 首先检查缓冲区是否有足够空间
    • 然后填充缓冲区并处理完整块
    • 最后处理剩余的完整块学习心得:复杂逻辑要拆分成简单步骤,提高代码可读性

  • 问题:字节序处理(大端/小端)解决方案:明确使用大端格式处理多字节数据学习心得:跨平台编程时要注意字节序问题,明确使用哪种格式

7.7 测试用例设计(算法验证)
使用FIPS 180-4标准中定义的测试向量验证实现的正确性:
  • 测试向量1:空字符串
    • 输入:""
    • 预期输出:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

  • 测试向量2:"abc"
    • 输入:"abc"
    • 预期输出:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

  • 测试向量3:"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
    • 输入:"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
    • 预期输出:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1


7.8 代码集成与使用(如何在项目中使用)7.8.1 头文件包含#include "sha256.h"7.8.2 使用示例uint8_t hash[32];
char hex_hash[65];

// 计算SHA-256哈希值
sha256((uint8_t*)"abc", 3, hash);

// 转换为十六进制字符串
sha256_to_hex(hash, hex_hash);

// 输出结果
printf("SHA-256: %s\r\n", hex_hash);

main.c

2.62 KB, 下载次数: 0

sha256.c

11.4 KB, 下载次数: 0

sha256.h

3.16 KB, 下载次数: 0

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-8 00:55:55 | 显示全部楼层
开箱点亮Ai8051U擎天柱核心板USB下载程序全记录大家好!最近我收到了期待已久的Ai8051U擎天柱核心板,心情超级激动!这款板子基于STC的32位8051单片机,功能强大,特别适合学习和项目开发。开箱后,我迫不及待地插上USB线,想试试板子自带的流水灯程序。但没想到,第一步就遇到了小挫折:插上USB后,STC-ISP软件居然没有检测到串口!板子上的LED灯倒是亮着,说明程序在跑,但怎么才能下载自己的程序呢?于是,我静下心来,翻看了随板附带的《Ai8051U系列技术手册》,重点学习了第2.2章节“安装AiCube-ISP下载/编程/烧录工具,含强大的辅助开发工具”。今天,我就把这次学习经历整理成博客,分享给大家,希望能帮到同样刚入手的朋友们
一、开箱初体验:默认流水灯程序与问题发现收到Ai8051U擎天柱核心板时,包装简洁,板子设计精致,正面印有“擎天柱”标识。我按照说明,直接用USB-TypeC线连接电脑和板子。
截图202601080022527037.jpg

板子通电后,一组LED闪烁,这就是默认的流水灯程序,效果很酷!但当我打开STC-ISP软件(版本v6.96N),准备下载自己的程序时,问题来了:软件界面上的“扫描串口”列表空空如也,没有显示任何COM口或USB设备。板子明明通电了,为什么软件检测不到呢?



我一开始以为是驱动问题,但手册提醒我:Ai8051U支持硬件USB下载,不需要安装额外驱动,只要USB连接的鼠标能工作,USB-HID驱动就是好的。那问题出在哪儿?继续读手册,我发现了关键点:USB下载需要正确的上电顺序硬件操作,不能简单插拔USB代替电源开关。
二、手册解读:学习USB下载的正确姿势手册第2.2章节详细介绍了AiCube-ISP软件的安装和USB下载流程。我总结了几点核心内容:
  • AiCube-ISP软件:这是STC官方提供的下载/编程工具,集成了各种辅助开发功能,如串口助手、延时计算器等。软件是绿色版,解压就能用,超级方便。
  • 上电工作过程:单片机复位时,默认从系统程序区启动,判断是否要下载用户程序。如果USB的D+和D-信号接触不良,单片机可能直接跳转到用户程序区运行流水灯,导致软件无法检测到下载模式。
  • USB下载流程图:手册强调,下载时必须用电源开关控制上电,而不是依赖USB插拔,以确保GND、D+、D-、VCC的接触顺序正确。


截图202601080049449506.jpg
截图202601080051153430.jpg

三、实战操作:一步步解决无串口检测问题按照手册,我重新操作了一遍,终于成功!下面是详细步骤,我在关键环节留了图片位置,方便大家对照。
步骤1:安装AiCube-ISP软件
我先从STC官网(https://www.stcai.com/gjrj)下载了最新版AiCube-ISP压缩包,解压到D盘,创建了桌面快捷方式。软件界面简洁,功能一目了然。



步骤2:硬件连接与上电顺序
手册指出,USB下载有三种方法,我用了最可靠的方法一:P3.2按键结合停电上电
  • 用USB-TypeA线连接电脑和板子(板子是TypeC口,我用转接头连接)。
  • 按住板子上的P3.2按键(即P3.2接地)。
  • 按下电源按钮(Power_SW)停电,再松开按钮上电——这就是“冷启动”。
  • 这时,电脑端的AiCube-ISP软件自动识别出了“(HID1) USB-Writer”,表示可以下载了!


截图202601080041208977.jpg

这次正确操作后,软件立马检测到了设备。
步骤3:下载默认流水灯程序验证
为了测试,我打开了03.点亮第一个LED程序的HEX文件,点击“下载/编程”,几秒后提示成功。板子上的P20和P21引脚的LED重新点亮,证明下载功能正常!



四、总结与心得这次经历让我深刻体会到:硬件操作的小细节决定成败。Ai8051U的USB下载功能很强大,但必须严格按手册步骤来。总结几个要点:
  • 软件安装简单,AiCube-ISP工具包很实用。
  • 下载时一定要用电源开关控制上电,避免USB插拔顺序问题。
  • 如果软件不识别,先检查P3.2按键是否按下,再按下松开POWER按键冷启动。

现在,我可以愉快地折腾我的Ai8051U了!下一步我准备尝试用USB-CDC虚拟串口通信,相信有手册指导,会顺利很多。如果你也遇到了类似问题,希望这篇博客能帮到你。欢迎在评论区交流心得!

好了,这就是我的学习笔记。如果你有Ai8051U板子,不妨跟着试试,祝你玩得开心!

AI8051U.pdf

66.66 MB, 下载次数: 0

芯片手册

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-8 00:57:37 | 显示全部楼层
LilMons*** 发表于 2026-1-7 01:30
【STC单片机入门实战】Day 3:夯实C语言基础,为32位8051编程铺路(第5集学习笔记)一、核心知识点回顾1. U ...

这个程序有问题,过些天更新SHA256的程序
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-13 02:23:23 | 显示全部楼层
【STC单片机入门实战】I/O输入输出学习笔记(第6集学习笔记)一、GPIO基本概念1. GPIO定义
  • GPIO(General Purpose Input Output):通用输入输出端口
  • 通俗理解:单片机的引脚,可以输入或输出高低电平

2. 高低电平定义
  • 高电平:接近电源正极电压(VCC),逻辑1
  • 低电平:接近电源负极电压(GND),逻辑0
  • 注意事项:IO口电压有极限范围,3.3V供电时,IO口电压应在0-3.6V之间

3. IO口四种模式
  • 准双向口:既能输入也能输出,灌电流可达20mA,拉电流仅为微安级
  • 推挽输出:输出电流大,可达20mA
  • 高阻输入:用于高阻抗输入场合
  • 开漏模式:需要外部上拉电阻

4. 拉电流与灌电流
  • 拉电流:电流从IO口流出,准双向口模式下仅几百微安
  • 灌电流:电流流入IO口,准双向口模式下可达20mA

5. 输入电平阈值(3.3V供电)
  • 打开施密特触发器时:
    • 低电平最大值:0.99V
    • 高电平最小值:1.09V

  • 施密特触发器默认使能(上电复位后)

二、按键输入检测1. 按键原理
  • 机械按键:按下时导通,松开时断开
  • 实验箱按键连接:P32引脚通过电阻上拉,按下后接地
  • 未按下:引脚为高电平(1)
  • 按下:引脚为低电平(0)

2. 按键抖动问题
  • 现象:机械按键按下和松开时,电平会出现抖动
  • 抖动时间:一般在20ms以内
  • 影响:导致程序误判按键状态
  • 解决方案:延时消抖(本次课程)、定时器消抖(后续课程)

三、代码实现1. 工程结构
  • 主要文件:main.c、ai8051u.h
  • 开发环境:基于8051U/AI8051U单片机

2. 核心代码分析2.1 系统初始化WTST = 0;   // 将程序指令执行速度设置为最快
EAXFR = 1;  // 允许访问扩展寄存器(XFR)
CKCON = 0;  // 提升XRAM访问速度

// 设置所有IO口为推挽输出模式
P0M1 = 0x00; P0M0 = 0x00;
P1M1 = 0x00; P1M0 = 0x00;
P2M1 = 0x00; P2M0 = 0x00;
P3M1 = 0x00; P3M0 = 0x00;
P4M1 = 0x00; P4M0 = 0x00;
P5M1 = 0x00; P5M0 = 0x00;
P6M1 = 0x00; P6M0 = 0x00;
P7M1 = 0x00; P7M0 = 0x00;2.2 延时消抖函数void Delay20ms(void)  // @24.000MHz
{
    unsigned long edata i;
    _nop_();
    _nop_();
    i = 119998UL;
    while (i) i--;
}2.3 任务1:按下P32灯亮,松开灯灭if( P32 == 0 )  // 判断P32按键是否按下
{
    P20 = 0;    // 灯亮
}
else
{
    P20 = 1;    // 灯灭
}2.4 任务2:按下P32灯灭,松开灯亮if( P32 == 1 )  // 判断P32按键是否松开
{
    P20 = 0;    // 灯亮
}
else
{
    P20 = 1;    // 灯灭
}2.5 任务3:按一下灯亮,再按一下灯灭(带消抖)if( P32 == 0 )  // 检测到按键按下
{
    Delay20ms();  // 延时20ms消抖
    if( P32 == 0 )  // 再次确认按键按下
    {
        state = !state;  // 状态取反
        P20 = state;     // 输出状态到LED
        printf("state:%d\r\n",(int)state);  // 串口打印状态
        while( P32 == 0 );  // 等待按键松开
    }
}四、关键知识点总结1. IO口配置
  • 通过PnM1和PnM0寄存器配置IO口模式
  • 00:准双向口模式
  • 01:推挽输出模式
  • 10:高阻输入模式
  • 11:开漏模式

2. 按键检测流程
  • 读取IO口电平
  • 延时消抖(20ms)
  • 再次读取确认
  • 执行相应操作
  • 等待按键松开

3. 消抖方法
  • 硬件消抖:RC滤波电路
  • 软件消抖:延时消抖、定时器消抖
  • 本次课程使用:20ms延时消抖

4. while循环的使用
  • while(1):无限循环,用于主程序
  • while(条件):等待条件满足,如等待按键松开

五、课后练习题
  • 按一下P32按钮灯亮,再按一下P33按钮灯灭
  • 按一下P32,左边四个灯亮,右边四个灯灭;再按一下,左边四个灭,右边四个亮
  • 按一下亮一颗灯,再按一下亮两颗灯,直到全亮

六、代码优化方向
  • 使用定时器消抖:替代延时函数,提高CPU利用率
  • 动态延时:根据需要调整延时时间
  • 按键扫描:实现多按键检测
  • 中断方式:使用外部中断处理按键,减少CPU占用

七、学习心得
  • GPIO是单片机与外部世界交互的重要接口
  • 按键抖动是机械按键的固有特性,必须进行消抖处理
  • 准双向口是最常用的IO口模式,适合大多数应用场景
  • 软件消抖简单易实现,但会占用CPU资源
  • 学习单片机编程需要结合硬件原理和软件实现

通过本次课程的学习,我掌握了GPIO的基本概念、按键输入检测的原理和实现方法,以及如何处理按键抖动问题。这些知识是单片机编程的基础,将在后续的学习中得到广泛应用。
八、学习练习:流水灯实现8.1 练习目的
  • 巩固GPIO输入输出的基本概念
  • 掌握按键检测和消抖技术
  • 实现复杂的LED控制逻辑
  • 培养模块化编程思维

8.2 硬件需求
  • AI8051U单片机开发板
  • P32按键一个
  • P2口8个LED灯

8.3 功能描述
  • 按键控制:按下P32按键,流水灯开始流动;松开P32按键,流水灯停止流动并保持当前状态
  • 流水模式:从P20到P27依次点亮LED,到达P27后反向流动,从P27到P20依次点亮,循环往复
  • 消抖处理:添加20ms延时消抖,确保按键检测准确
  • 状态保持:松开按键后,LED保持当前点亮状态

8.4 实现思路
  • 按键检测:使用P32按键作为输入,检测按键状态
  • 消抖处理:添加20ms延时,避免按键抖动影响
  • 状态控制:使用全局变量控制流水灯的开始和停止
  • 流水逻辑:使用位运算控制LED点亮,通过变量记录当前位置和流动方向
  • 模块化设计:将流水灯逻辑封装为独立函数,提高代码可读性和可维护性

8.5 核心代码实现8.5.1 全局变量定义u8 led_flow_flag = 0;    // 流水灯控制标志:0-停止,1-流动
u8 led_current = 0;      // 当前点亮的LED位置(0-7对应P20-P27)
u8 flow_direction = 0;   // 流水方向:0-正向,1-反向8.5.2 延时函数void Delay100ms(void)    // 控制流水灯速度
{
    unsigned long edata i;
    _nop_();
    _nop_();
    i = 599998UL;
    while (i) i--;
}8.5.3 流水灯流动函数void led_flow(void)
{
    // 根据当前位置点亮对应的LED
    P2 = ~(1 << led_current);
   
    // 控制流水方向
    if (flow_direction == 0)  // 正向流动:P20 -> P27
    {
        led_current++;
        if (led_current >= 8)  // 到达最右端,改变方向
        {
            led_current = 6;
            flow_direction = 1;
        }
    }
    else  // 反向流动:P27 -> P20
    {
        led_current--;
        if (led_current >= 8)  // 到达最左端,改变方向(利用无符号数溢出)
        {
            led_current = 1;
            flow_direction = 0;
        }
    }
   
    Delay100ms();  // 控制流水速度
}8.5.4 主控制逻辑while(1){    // 按键检测:P32按下开始流水,松开停止流水    if( P32 == 0 )    {        Delay20ms();  // 延时20ms消抖        if( P32 == 0 )        {            led_flow_flag = 1;  // 设置流水灯开始标志        }    }    else    {        led_flow_flag = 0;  // 按键松开,停止流水灯    }        // 流水灯控制逻辑    if (led_flow_flag)    {        led_flow();  // 调用流水灯流动函数    }    // 否则保持当前状态}8.6 视频展示

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-14 00:44:20 | 显示全部楼层
【STC单片机入门实战】I/O输入输出学习笔记(第6集学习笔记)一、GPIO基本概念1. GPIO定义
  • GPIO(General Purpose Input Output):通用输入输出端口
  • 通俗理解:单片机的引脚,可以输入或输出高低电平

2. 高低电平定义
  • 高电平:接近电源正极电压(VCC),逻辑1
  • 低电平:接近电源负极电压(GND),逻辑0
  • 注意事项:IO口电压有极限范围,3.3V供电时,IO口电压应在0-3.6V之间

3. IO口四种模式
  • 准双向口:既能输入也能输出,灌电流可达20mA,拉电流仅为微安级
  • 推挽输出:输出电流大,可达20mA
  • 高阻输入:用于高阻抗输入场合
  • 开漏模式:需要外部上拉电阻

4. 拉电流与灌电流
  • 拉电流:电流从IO口流出,准双向口模式下仅几百微安
  • 灌电流:电流流入IO口,准双向口模式下可达20mA

5. 输入电平阈值(3.3V供电)
  • 打开施密特触发器时:
    • 低电平最大值:0.99V
    • 高电平最小值:1.09V

  • 施密特触发器默认使能(上电复位后)

二、按键输入检测1. 按键原理
  • 机械按键:按下时导通,松开时断开
  • 实验箱按键连接:P32引脚通过电阻上拉,按下后接地
  • 未按下:引脚为高电平(1)
  • 按下:引脚为低电平(0)

2. 按键抖动问题
  • 现象:机械按键按下和松开时,电平会出现抖动
  • 抖动时间:一般在20ms以内
  • 影响:导致程序误判按键状态
  • 解决方案:延时消抖(本次课程)、定时器消抖(后续课程)

三、代码实现1. 工程结构
  • 主要文件:main.c、ai8051u.h
  • 开发环境:基于8051U/AI8051U单片机

2. 核心代码分析2.1 系统初始化WTST = 0;   // 将程序指令执行速度设置为最快
EAXFR = 1;  // 允许访问扩展寄存器(XFR)
CKCON = 0;  // 提升XRAM访问速度

// 设置所有IO口为推挽输出模式
P0M1 = 0x00; P0M0 = 0x00;
P1M1 = 0x00; P1M0 = 0x00;
P2M1 = 0x00; P2M0 = 0x00;
P3M1 = 0x00; P3M0 = 0x00;
P4M1 = 0x00; P4M0 = 0x00;
P5M1 = 0x00; P5M0 = 0x00;
P6M1 = 0x00; P6M0 = 0x00;
P7M1 = 0x00; P7M0 = 0x00;2.2 延时消抖函数void Delay20ms(void)  // @24.000MHz
{
   unsigned long edata i;
   _nop_();
   _nop_();
   i = 119998UL;
   while (i) i--;
}2.3 任务1:按下P32灯亮,松开灯灭if( P32 == 0 )  // 判断P32按键是否按下
{
   P20 = 0;    // 灯亮
}
else
{
   P20 = 1;    // 灯灭
}2.4 任务2:按下P32灯灭,松开灯亮if( P32 == 1 )  // 判断P32按键是否松开
{
   P20 = 0;    // 灯亮
}
else
{
   P20 = 1;    // 灯灭
}2.5 任务3:按一下灯亮,再按一下灯灭(带消抖)if( P32 == 0 )  // 检测到按键按下
{
   Delay20ms();  // 延时20ms消抖
   if( P32 == 0 )  // 再次确认按键按下
  {
       state = !state;  // 状态取反
       P20 = state;     // 输出状态到LED
       printf("state:%d\r\n",(int)state);  // 串口打印状态
       while( P32 == 0 );  // 等待按键松开
  }
}四、关键知识点总结1. IO口配置
  • 通过PnM1和PnM0寄存器配置IO口模式
  • 00:准双向口模式
  • 01:推挽输出模式
  • 10:高阻输入模式
  • 11:开漏模式

2. 按键检测流程
  • 读取IO口电平
  • 延时消抖(20ms)
  • 再次读取确认
  • 执行相应操作
  • 等待按键松开

3. 消抖方法
  • 硬件消抖:RC滤波电路
  • 软件消抖:延时消抖、定时器消抖
  • 本次课程使用:20ms延时消抖

4. while循环的使用
  • while(1):无限循环,用于主程序
  • while(条件):等待条件满足,如等待按键松开

五、课后练习题
  • 按一下P32按钮灯亮,再按一下P33按钮灯灭
  • 按一下P32,左边四个灯亮,右边四个灯灭;再按一下,左边四个灭,右边四个亮
  • 按一下亮一颗灯,再按一下亮两颗灯,直到全亮

六、代码优化方向
  • 使用定时器消抖:替代延时函数,提高CPU利用率
  • 动态延时:根据需要调整延时时间
  • 按键扫描:实现多按键检测
  • 中断方式:使用外部中断处理按键,减少CPU占用

七、学习心得
  • GPIO是单片机与外部世界交互的重要接口
  • 按键抖动是机械按键的固有特性,必须进行消抖处理
  • 准双向口是最常用的IO口模式,适合大多数应用场景
  • 软件消抖简单易实现,但会占用CPU资源
  • 学习单片机编程需要结合硬件原理和软件实现

通过本次课程的学习,我掌握了GPIO的基本概念、按键输入检测的原理和实现方法,以及如何处理按键抖动问题。这些知识是单片机编程的基础,将在后续的学习中得到广泛应用。
八、学习练习:流水灯实现8.1 练习目的
  • 巩固GPIO输入输出的基本概念
  • 掌握按键检测和消抖技术
  • 实现复杂的LED控制逻辑
  • 培养模块化编程思维

8.2 硬件需求
  • AI8051U单片机开发板
  • P32按键一个
  • P2口8个LED灯

8.3 功能描述
  • 按键控制:按下P32按键,流水灯开始流动;松开P32按键,流水灯停止流动并保持当前状态
  • 流水模式:从P20到P27依次点亮LED,到达P27后反向流动,从P27到P20依次点亮,循环往复
  • 消抖处理:添加20ms延时消抖,确保按键检测准确
  • 状态保持:松开按键后,LED保持当前点亮状态

8.4 实现思路
  • 按键检测:使用P32按键作为输入,检测按键状态
  • 消抖处理:添加20ms延时,避免按键抖动影响
  • 状态控制:使用全局变量控制流水灯的开始和停止
  • 流水逻辑:使用位运算控制LED点亮,通过变量记录当前位置和流动方向
  • 模块化设计:将流水灯逻辑封装为独立函数,提高代码可读性和可维护性

8.5 核心代码实现8.5.1 全局变量定义u8 led_flow_flag = 0;    // 流水灯控制标志:0-停止,1-流动
u8 led_current = 0;      // 当前点亮的LED位置(0-7对应P20-P27)
u8 flow_direction = 0;   // 流水方向:0-正向,1-反向8.5.2 延时函数void Delay100ms(void)    // 控制流水灯速度
{
   unsigned long edata i;
   _nop_();
   _nop_();
   i = 599998UL;
   while (i) i--;
}8.5.3 流水灯流动函数void led_flow(void)
{
   // 根据当前位置点亮对应的LED
   P2 = ~(1 << led_current);
   
   // 控制流水方向
   if (flow_direction == 0)  // 正向流动:P20 -> P27
  {
       led_current++;
       if (led_current >= 8)  // 到达最右端,改变方向
      {
           led_current = 6;
           flow_direction = 1;
      }
  }
   else  // 反向流动:P27 -> P20
  {
       led_current--;
       if (led_current >= 8)  // 到达最左端,改变方向(利用无符号数溢出)
      {
           led_current = 1;
           flow_direction = 0;
      }
  }
   
   Delay100ms();  // 控制流水速度
}8.5.4 主控制逻辑while(1){    // 按键检测:P32按下开始流水,松开停止流水    if( P32 == 0 )    {        Delay20ms();  // 延时20ms消抖        if( P32 == 0 )        {            led_flow_flag = 1;  // 设置流水灯开始标志        }    }    else    {        led_flow_flag = 0;  // 按键松开,停止流水灯    }        // 流水灯控制逻辑    if (led_flow_flag)    {        led_flow();  // 调用流水灯流动函数    }    // 否则保持当前状态}8.6 按键控制流水灯视频




main.c

3.76 KB, 下载次数: 0

按键控制流水灯程序

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看II
  • 打卡总天数:22
  • 最近打卡:2026-01-15 23:35:10
已绑定手机

0

主题

18

回帖

80

积分

注册会员

积分
80
发表于 2026-1-14 01:46:30 | 显示全部楼层
STC单片机入门实战】定时器学习笔记(第7集学习笔记)一、课程概述
本次课程主要讲解了8051U单片机中定时器的使用,包括定时器的基本原理、配置方法以及实际应用案例。通过三个任务示例,展示了定时器中断在解决CPU占用问题中的重要作用。
二、定时器的作用与原理2.1 为什么需要定时器?
传统的延时函数(如Delay20ms)会阻塞CPU执行,导致在延时期间无法响应其他任务(如按键检测)。定时器中断可以解决这个问题,它允许CPU在执行主任务的同时,通过中断机制处理定时任务。
2.2 定时器工作原理
  • 定时器本质:定时器是一个计数器,从设定值开始计数,当计数到溢出值(65536)时产生中断。
  • 时钟源:定时器使用系统时钟(本工程为24MHz),可以通过分频器调整计数速度。
  • 自动重载:16位自动重载模式下,定时器溢出后会自动重新加载初始值,实现周期性中断。
  • 中断机制:当定时器溢出时,会触发中断,CPU暂停当前任务,执行中断服务程序,执行完毕后返回继续执行原任务。

2.3 定时器配置参数
参数
作用
示例值

TM0PS定时器预分频系数0x5B(91分频)
AUXR选择12T/1T模式0x7F(12T模式)
TMOD定时器模式0x00(16位自动重载)
TH0/TL0定时器初始值0x013F(3秒定时)
TR0定时器运行控制1(启动)/0(停止)
ET0定时器中断使能1(使能)/0(禁止)2.4 定时时间计算公式定时时间 = (65536 - 初始值) * 12 * (TM0PS + 1) / 系统时钟频率
例如,3秒定时计算:
  • 系统时钟:24MHz
  • 初始值:0x013F = 319
  • TM0PS:0x5B = 91
  • 定时时间 = (65536 - 319) * 12 * (91 + 1) / 24000000 ≈ 3秒

三、代码实现分析3.1 工程文件结构c:\Software\STC\测试工程\07.定时器\
├── ai8051u.h          // 芯片头文件
├── stc32_stc8_usb.h   // USB头文件
├── stc_usb_cdc_32.LIB // USB库文件
├── main.c             // 主程序文件3.2 核心代码分析3.2.1 定时器初始化函数void Timer0_Init(void)    // 500毫秒@24.000MHz
{
    TM0PS = 0x0F;         // 设置定时器时钟预分频
    AUXR &= 0x7F;         // 定时器时钟12T模式
    TMOD &= 0xF0;         // 设置定时器模式
    TL0 = 0xDC;           // 设置定时器初始值低8位
    TH0 = 0x0B;           // 设置定时器初始值高8位
    TF0 = 0;              // 清除TF0标志
    TR0 = 1;              // 定时器0开始计时
    ET0 = 1;              // 使能定时器0中断
}3.2.2 定时器中断服务程序void Timer0_Isr(void) interrupt 1  // 每500毫秒执行一次
{
    state = !state;        // 状态取反
    P20 = state;           // 控制LED1
    P21 = !state;          // 控制LED2(与LED1相反)
}3.2.3 主函数结构void main(void)
{
    // 系统初始化
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;
   
    // IO口模式设置
    P0M1 = 0x00;   P0M0 = 0x00;
    // ... 其他IO口设置
   
    // USB初始化
    usb_init();
    IE2 |= 0x80;   // 使能USB中断
   
    EA = 1;        // 开启总中断
   
    while(DeviceState != DEVSTATE_CONFIGURED);  // 等待USB配置完成
   
    while(1)       // 主循环
    {
        // USB数据处理
        if (bUsbOutReady)
        {
            usb_OUT_done();
        }
        
        // 按键检测与任务处理
        // ...
    }
}四、任务示例任务1:LED3秒闪烁,按键计数
  • 功能:LED每3秒闪烁一次,按下按键时串口打印按键次数
  • 实现要点
    • 3秒定时器中断控制LED状态
    • 主循环中检测按键,使用Delay20ms消抖
    • 串口打印时注意特殊字符处理(添加\xfd解决乱码)


任务2:按键点亮LED,3秒后自动熄灭
  • 功能:按下按键点亮LED,3秒后自动熄灭
  • 实现要点
    • 按键按下时点亮LED并启动3秒定时器
    • 定时器中断中熄灭LED并关闭定时器


任务3:救护车灯控制
  • 功能:按下按键红蓝灯交替闪烁,再次按下停止闪烁
  • 实现要点
    • 使用Run_State变量控制运行状态
    • 500毫秒定时器中断控制红蓝灯交替
    • 停止时关闭定时器并熄灭所有灯


五、函数定义、声明和调用5.1 函数定义
函数定义包含返回值类型、函数名、参数列表和函数体:
void Delay20ms(void)    // @24.000MHz
{
    unsigned long edata i;
    _nop_();
    _nop_();
    i = 119998UL;
    while (i) i--;
}5.2 函数声明
函数声明告诉编译器函数的存在,位于调用之前:
void Timer0_Init(void);    // 定时器初始化函数声明5.3 函数调用
函数调用时需要提供参数(如果有的话):
Timer0_Init();    // 调用定时器初始化函数
Delay20ms();      // 调用延时函数5.4 函数位置注意事项
  • 如果函数定义在调用之后,必须先声明
  • 如果函数定义在调用之前,可以省略声明

六、注意事项
  • 特殊字符处理:在串口打印中,某些汉字(如"次"、"过"等)需要在后面添加\xfd,否则会显示乱码。
  • 中断优先级:定时器中断优先级高于主循环,确保定时任务及时执行。
  • IO口模式:使用定时器控制LED时,需要正确设置IO口为推挽输出模式。
  • 按键消抖:机械按键需要添加消抖处理(如Delay20ms),避免误触发。

七、课后练习实现7.1 电子功德箱实现
功能需求
  • 按下按钮P32,切换功德模式(单倍/双倍)
  • 按下按钮P33,根据当前模式增加功德值(单倍+1,双倍+2)
  • 通过串口打印功德增加信息与当前功德值
  • LED指示灯显示当前模式(P20亮表示单倍,P21亮表示双倍)

实现代码
// 全局变量定义u8 state = 0;                 // 初始状态u8 Run_State = 0;             // 运行状态(用于模式切换)u16 merit = 0;                // 功德值void main(void){    // 系统初始化    WTST = 0;       EAXFR = 1;      CKCON = 0;          // IO口模式设置    P0M1 = 0x00;   P0M0 = 0x00;    P1M1 = 0x00;   P1M0 = 0x00;    P2M1 = 0x00;   P2M0 = 0x00;  // LED端口设置为推挽输出    P3M1 = 0x00;   P3M0 = 0x00;  // 按键端口设置为推挽输出    // ... 其他IO口设置        // USB初始化    usb_init();    IE2 |= 0x80;                // 使能USB中断    EA = 1;                     // 开启总中断        // 初始状态为单倍功德模式    Run_State = 0;    P20 = 0;                    // P20表示单倍模式    P21 = 1;                    // P21熄灭    printf("Current mode: Single merit\r\n");        while (DeviceState != DEVSTATE_CONFIGURED);  // 等待USB配置完成        while(1)    {        // USB数据处理        if (bUsbOutReady)        {            usb_OUT_done();        }                // 任务1:P32按键切换模式 单倍/双倍        if( P32 == 0 )                              // 判断P32按键是否按下        {            Delay20ms();                            // 延时20ms消抖            if( P32 == 0 )            {                Run_State = !Run_State;             // 模式取反                if(Run_State == 1)                {                    printf("Current mode: Double merit\r\n");                    P21 = 0;                        // P21表示双倍模式                    P20 = 1;                        // P20熄灭                }                else                {                    printf("Current mode: Single merit\r\n");                    P20 = 0;                        // P20表示单倍模式                    P21 = 1;                        // P21熄灭                }                while( P32 == 0 );                  // 等待P32释放            }        }                // 任务2:P33按键增加功德值        if( P33 == 0 )                              // 判断P33按键是否按下        {            Delay20ms();                            // 延时20ms消抖            if( P33 == 0 )            {                if(P20 == 0)                        // P20表示单倍模式                {                    merit += 1;                    printf("Merit+1\r\n");                }                else if(P21 == 0)                   // P21表示双倍模式                {                    merit += 2;                    printf("Merit+2\r\n");                }                printf("Current merit\xfd:%d\r\n", (int)merit);                while( P33 == 0 );                  // 等待P33释放            }        }    }}
实现要点
  • 模式切换:使用Run_State变量控制当前模式,通过P32按键切换
  • LED指示:P20亮表示单倍模式,P21亮表示双倍模式
  • 按键消抖:使用Delay20ms()函数消除按键抖动
  • 功德计算:根据当前模式计算功德值增加量
  • 串口通信:使用printf函数输出功德信息

串口输出结果
  

电子功德箱串口输出

电子功德箱串口输出

遇到的问题及解决方案
  • 问题:串口输出中文显示乱码原因:8051U单片机编译器对某些中文字符支持不好,会出现乱码解决方案:将所有中文字符串替换为英文,确保串口输出正常显示
    // 中文(乱码)// printf("当前为单倍功德模式\r\n");// 英文(正常显示)printf("Current mode: Single merit\r\n");
  • 问题:按键检测不准确原因:机械按键存在抖动现象解决方案:添加Delay20ms()函数进行消抖处理
  • 问题:LED指示灯不亮原因:IO口模式设置不正确解决方案:将LED端口设置为推挽输出模式(P2M1 = 0x00; P2M0 = 0x00;)

通过这个项目,我们学习了如何综合运用单片机的GPIO、串口通信、按键检测等功能,实现一个完整的。同时,我们也学会了如何解决实际开发中遇到的问题,如串口乱码、按键抖动等。

main.c

4.42 KB, 下载次数: 0

电子功德箱主程序

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-2-12 18:04 , Processed in 0.665272 second(s), 81 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表