set /?

显示、设置或删除 cmd.exe 环境变量。

SET [variable=[string]]

  variable  指定环境变量名。
  string    指定要指派给变量的一系列字符串。

要显示当前环境变量,键入不带参数的 SET。

如果命令扩展名被启用,SET 会如下改变:

可仅用一个变量激活 SET 命令,等号或值不显示所有前缀匹配
SET 命令已使用的名称的所有变量的值。例如:

    SET P

会显示所有以字母 P 打头的变量

如果在当前环境中找不到该变量名称,SET 命令将把 ERRORLEVEL
设置成 1。

SET 命令不允许变量名含有等号。

在 SET 命令中添加了两个新命令行开关:

    SET /A expression
    SET /P variable=[promptString]

/A 命令行开关指定等号右边的字符串为被评估的数字表达式。该表达式
评估器很简单并以递减的优先权顺序支持下列操作:

    ()                  - 分组
    ! ~ -               - 一元运算符
    * / %               - 算数运算符
    + -                 - 算数运算符
    << >>               - 逻辑移位
                       - 按位“与”
    ^                   - 按位“异”
    |                   - 按位“或”
    = *= /= %= += -=    - 赋值
      &= ^= |= <<= >>=
    ,                   - 表达式分隔符

如果您使用任何逻辑或取余操作符, 您需要将表达式字符串用
引号扩起来。在表达式中的任何非数字字符串键作为环境变量
名称,这些环境变量名称的值已在使用前转换成数字。如果指定
了一个环境变量名称,但未在当前环境中定义,那么值将被定为
零。这使您可以使用环境变量值做计算而不用键入那些 % 符号
来得到它们的值。如果 SET /A 在命令脚本外的命令行执行的,
那么它显示该表达式的最后值。该分配的操作符在分配的操作符
左边需要一个环境变量名称。除十六进制有 0x 前缀, 八进制
有 0 前缀的,数字值为十进位数字。因此, 0x12 与 18 和 022
相同。请注意八进制公式可能很容易搞混: 08 和 09 是无效的数字,
因为 8 和 9 不是有效的八进制位数。

/P 命令行开关允许将变量数值设成用户输入的一行输入。读取输入
行之前,显示指定的 promptString。promptString 可以是空的。

环境变量替换已如下增强:

    %PATH:str1=str2%

会扩展 PATH 环境变量,用 "str2" 代替扩展结果中的每个 "str1"。
要有效地从扩展结果中删除所有的 "str1","str2" 可以是空的。
"str1" 可以以星号打头;在这种情况下,"str1" 会从扩展结果的
开始到 str1 剩余部分第一次出现的地方,都一直保持相配。

也可以为扩展名指定子字符串。

    %PATH:~10,5%

会扩展 PATH 环境变量,然后只使用在扩展结果中从第 11 个(偏
移量 10)字符开始的五个字符。如果没有指定长度,则采用默认
值,即变量数值的余数。如果两个数字(偏移量和长度)都是负数,
使用的数字则是环境变量数值长度加上指定的偏移量或长度。

    %PATH:~-10%

会提取 PATH 变量的最后十个字符。

    %PATH:~0,-2%

会提取 PATH 变量的所有字符,除了最后两个。

终于添加了延迟环境变量扩充的支持。该支持总是按默认值被
停用,但也可以通过 CMD.EXE 的 /V 命令行开关而被启用/停用。
请参阅 CMD /?

考虑到读取一行文本时所遇到的目前扩充的限制时,延迟环境
变量扩充是很有用的,而不是执行的时候。以下例子说明直接
变量扩充的问题:

    set VAR=before
    if "%VAR%" == "before" (
        set VAR=after
        if "%VAR%" == "after" @echo If you see this, it worked
    )

不会显示消息,因为在读到第一个 IF 语句时,BOTH IF 语句中
的 %VAR% 会被代替;原因是: 它包含 IF 的文体,IF 是一个
复合语句。所以,复合语句中的 IF 实际上是在比较 "before" 和
"after",这两者永远不会相等。同样,以下这个例子也不会达到
预期效果:

    set LIST=
    for %i in (*) do set LIST=%LIST% %i
    echo %LIST%

原因是,它不会在目前的目录中建立一个文件列表,而只是将
LIST 变量设成找到的最后一个文件。这也是因为 %LIST% 在
FOR 语句被读取时,只被扩充了一次;而且,那时的 LIST 变量
是空的。因此,我们真正执行的 FOR 循环是:

    for %i in (*) do set LIST= %i

这个循环继续将 LIST 设成找到的最后一个文件。

延迟环境变量扩充允许您使用一个不同的字符(惊叹号)在执行
时间扩充环境变量。如果延迟的变量扩充被启用,可以将上面
例子写成以下所示,以达到预期效果:

    set VAR=before
    if "%VAR%" == "before" (
        set VAR=after
        if "!VAR!" == "after" @echo If you see this, it worked
    )

    set LIST=
    for %i in (*) do set LIST=!LIST! %i
    echo %LIST%

如果命令扩展名被启用,有几个动态环境变量可以被扩展,但
不会出现在 SET 显示的变量列表中。每次变量数值被扩展时,
这些变量数值都会被动态计算。如果用户用这些名称中任何
一个定义变量,那个定义会替代下面描述的动态定义:

%CD% - 扩展到当前目录字符串。

%DATE% - 用跟 DATE 命令同样的格式扩展到当前日期。

%TIME% - 用跟 TIME 命令同样的格式扩展到当前时间。

%RANDOM% - 扩展到 0 和 32767 之间的任意十进制数字。

%ERRORLEVEL% - 扩展到当前 ERRORLEVEL 数值。

%CMDEXTVERSION% - 扩展到当前命令处理器扩展名版本号。

%CMDCMDLINE% - 扩展到调用命令处理器的原始命令行。

 

 

#include <REGX51.H> #include "timer0.h" #include "SetSpeed.h" #include "Track.h" #include "Delay.h" // ???: 0=??(??,????), 1=??(??,????) unsigned char circle_mode = 1; // ???????(????) unsigned int mode_stable_cnt = 0; void main() { Timer0_Init(); // ?????? while(1) { // 1. ??????,?????????(??) if(Right_Line_Track3() == 1) // ?????????(??????,???????) { circle_mode = 0; // ??????? mode_stable_cnt = 0; // ?????? } else if(Left_Line_Track3() == 1) // ?????????(??????,???????) { circle_mode = 0; // ??????? mode_stable_cnt = 0; } // 2. ????(????) mode_stable_cnt++; if(mode_stable_cnt < 50) // ??50ms?????(?50???,?????) { continue; } // 3. ???????????? switch(Track_line()) { case 1: // ?? Left_Advance_SetSpeed(30, 100); Right_Advance_SetSpeed(30, 100); break; case 2: // ??(????) if(circle_mode == 0) // ????:??? ? ????,????(????) { Left_Advance_SetSpeed(35, 100); Right_Back_SetSpeed(20, 100); } else // ????:??? ? ?????,???? { Left_Advance_SetSpeed(30, 100); Right_Advance_SetSpeed(10, 100); } break; case 4: // ??(????) if(circle_mode == 0) // ????:??? ? ????,???? { Right_Advance_SetSpeed(35, 100); Left_Back_SetSpeed(20, 100); } else // ????:??? ? ?????,???? { Right_Advance_SetSpeed(30, 100); Left_Advance_SetSpeed(10, 100); } break; case 6: // ???(????,??U??) // ????,?????? Left_Advance_SetSpeed(30, 100); // ??????????? Right_Back_SetSpeed(15, 100); // ??????? break; case 7: // ???(????,??U??) // ????,?????? Right_Advance_SetSpeed(30, 100); // ??????????? Left_Back_SetSpeed(15, 100); // ??????? break; default: // ??????? Car_Stop(); break; } } } // ???0??????(??PWM??) void Timer0_Routine() interrupt 1 { TL0 = 0x9C; // ????(???500us??,11.0592MHz) TH0 = 0xFF; Count++; // ??PWM??? }
最新发布
10-26
你提供的代码是一个基于 **8051 单片机(如 STC89C52)** 的智能小车循迹系统程序,使用了定时器 0 实现 PWM 控制,并结合左右红外传感器进行路径识别和转向控制。下面我将 **逐行解析这段代码的功能、逻辑结构、可能存在的问题**,并提供优化建议。 --- ### ✅ 程序功能概述 这是一个用于**自动循迹小车**的主控程序,主要实现以下功能: - 使用红外传感器检测地面黑线; - 根据不同轨迹状态调整左右轮电机速度(PWM); - 支持两种运行模式:普通直线/转弯(`circle_mode = 0`) 和环形绕圈(`circle_mode = 1`); - 利用 Timer0 提供 PWM 基准时间中断(每 500μs 一次),用于生成占空比可调的 PWM 波形驱动电机; - 防止误判引入 `mode_stable_cnt` 计数防抖机制。 --- ## 🔍 详细代码分析与解释 ```c #include <REGX51.H> #include "timer0.h" #include "SetSpeed.h" #include "Track.h" #include "Delay.h" ``` > 包含头文件: > - `REGX51.H`:8051 寄存器定义。 > - 自定义模块:定时器、速度设置、循迹检测、延时函数等。 ```c // 注释乱码说明:原注释是中文但编码错误导致显示为问号 unsigned char circle_mode = 1; // 含义:当前运行模式。0=普通模式(直道或十字),1=环形模式(持续右转或左转) unsigned int mode_stable_cnt = 0; // 模式稳定计数器,防止瞬间干扰切换模式 ``` > 这两个全局变量用于保存系统的运行状态和防抖计数。 ```c void main() { Timer0_Init(); // 初始化定时器0(用于PWM定时) while(1) { // 1. 检测是否进入特殊区域(比如起始点或分叉口) if(Right_Line_Track3() == 1) // 右侧最外侧传感器检测到黑线(可能是起点或U型弯) { circle_mode = 0; // 切换到普通模式 mode_stable_cnt = 0; // 清除计数 } else if(Left_Line_Track3() == 1) // 左侧最外侧传感器检测到黑线 { circle_mode = 0; mode_stable_cnt = 0; } // 2. 防抖处理:等待一段时间确认模式不变 mode_stable_cnt++; if(mode_stable_cnt < 50) // 小于50次(即25ms * 50 = 1.25秒?注意此处单位) { continue; } ``` > ⚠️ 注意:这里 `mode_stable_cnt++` 是在每个主循环中加一,而没有明确知道主循环执行频率。 > 如果每次循环耗时约 25ms,则 50 次 ≈ 1.25 秒,这太长了!更合理的应是 **50 × 500μs = 25ms**,前提是该判断在定时中断触发后才更新。 ✅ 更佳做法是:只在 **Timer0 中断发生时递增 `mode_stable_cnt`**,否则容易造成响应延迟。 继续分析: ```c // 3. 执行循迹策略 switch(Track_line()) { case 1: // 全部居中 —— 直行 Left_Advance_SetSpeed(30, 100); Right_Advance_SetSpeed(30, 100); break; case 2: // 偏右(需要左转)—— 左边压线 if(circle_mode == 0) { // 普通模式:大角度左转 Left_Advance_SetSpeed(35, 100); Right_Back_SetSpeed(20, 100); } else { // 环形模式:轻微左修正,保持右侧行驶 Left_Advance_SetSpeed(30, 100); Right_Advance_SetSpeed(10, 100); } break; case 4: // 偏左(需要右转)—— 右边压线 if(circle_mode == 0) { Right_Advance_SetSpeed(35, 100); Left_Back_SetSpeed(20, 100); } else { Right_Advance_SetSpeed(30, 100); Left_Advance_SetSpeed(10, 100); } break; case 6: // 极端左偏或 U 形弯入口(左侧全白,右侧有线) Left_Advance_SetSpeed(30, 100); Right_Back_SetSpeed(15, 100); break; case 7: // 极端右偏或 U 形弯入口 Right_Advance_SetSpeed(30, 100); Left_Back_SetSpeed(15, 100); break; default: // 失去轨迹(例如十字路口中间无信号) Car_Stop(); break; } } } ``` > `Track_line()` 应返回一个组合值表示当前传感器阵列的状态(通常由 3~5 个传感器组成)。常见的编码方式如下(以三传感器为例): > > | 左 | 中 | 右 | 返回值 | > |----|----|----|--------| > | 0 | 0 | 0 | 0 | > | 0 | 0 | 1 | 1 | > | 0 | 1 | 0 | 2 | > | 0 | 1 | 1 | 3 | > | 1 | 0 | 0 | 4 | > | 1 | 0 | 1 | 5 | > | 1 | 1 | 0 | 6 | > | 1 | 1 | 1 | 7 | 但在你的代码中: - `case 1`: 表示右传感器检测到线 → 小车偏右 → 要左转? - 实际上应该是:**只有右边感应到黑线 → 小车整体偏左 → 需要向右修正?** ❗存在逻辑矛盾! 让我们重新理解 `Track_line()` 的返回值设计是否合理。 --- ## ❗关键问题指出 ### 🔴 问题1:`case 1`, `case 2`, `case 4` 编码含义混乱 假设传感器布局从左到右为:`L M R` | 情况 | L | M | R | 二进制 | 十进制 | 当前代码对应动作 | |------------|---|---|---|--------|--------|------------------| | 居中 | 0 | 1 | 0 | 010 | 2 | `case 2` → 左转?错!应该直行 | | 偏右(需左转)| 1 | 1 | 0 | 110 | 6 | `case 6` | | 偏左(需右转)| 0 | 1 | 1 | 011 | 3 | 没有处理 | 但是你在 `case 1`(R=1,M=0,L=0)时认为是“直行”?这是严重错误! 👉 正确的理解应该是: - `case 2` (010): 中间传感器在线上 → 居中 → 直行 ✅ - `case 1` (001): 只有右边在线上 → 小车偏左 → 需右转 ❌当前当成了直行 - `case 4` (100): 只有左边在线上 → 小车偏右 → 需左转 所以你的 `switch-case` 分支逻辑完全反了! --- ### 🔴 问题2:`circle_mode` 触发条件不合理 ```c if(Right_Line_Track3() == 1) // 最右边传感器触发 → 设为普通模式 ``` 这意味着只要最右边碰到了线就切回普通模式。但如果是在环形赛道上连续右转,这个传感器会频繁触发,导致 `circle_mode` 被反复重置。 更好的方法是:通过特定图案(如 T 字路口、起点标记)来切换模式,而不是单个传感器。 --- ### 🔴 问题3:`mode_stable_cnt` 更新位置不当 你现在是在主循环里 `mode_stable_cnt++`,如果主循环很快(比如几微秒完成),那么不到 1ms 就达到 50。 但如果主循环中有 delay 或复杂运算,又可能导致响应过慢。 ✅ 推荐:在 **Timer0 中断中每 500μs 加 1**,这样可以精确控制时间窗口。 --- ### 🔧 改进建议代码(修正版片段) #### ✅ 修改 Track_line() 解读 + 正确动作匹配 ```c // 示例:三传感器输入,P1^0=左, P1^1=中, P1^2=右 unsigned char Track_line(void) { return ((P1 & 0x07)); // 读取低三位作为传感器状态 } ``` 然后修改主逻辑: ```c switch(Track_line()) { case 2: // 010 - 中间在线上 → 直行 case 3: // 011 - 稍偏左 → 微右转 Left_Advance_SetSpeed(40, 100); Right_Advance_SetSpeed(30, 100); break; case 1: // 001 - 明显偏左 → 大右转 Left_Advance_SetSpeed(50, 100); Right_Advance_SetSpeed(15, 100); break; case 4: // 100 - 明显偏右 → 大左转 Left_Advance_SetSpeed(15, 100); Right_Advance_SetSpeed(50, 100); break; case 6: // 110 - 极右偏/U弯 → 原地左转 Left_Back_SetSpeed(30, 100); Right_Advance_SetSpeed(30, 100); break; case 7: // 111 - 十字路口或终点 → 停止或决策 Car_Stop(); Delay_ms(500); break; default: // 其他情况(如000丢失) // 维持上次方向 or 查找 break; } ``` --- ### ✅ 定时器中断(PWM 基础) ```c void Timer0_Routine() interrupt 1 { TL0 = 0x9C; TH0 = 0xFF; // 设置初值,500us溢出一次(11.0592MHz晶振) Count++; // PWM周期计数器,在SetSpeed.c中用来调节占空比 } ``` 此中断每 500μs 触发一次,可用于: - 实现软件 PWM:比如设定周期为 20 次中断(10ms),高电平持续 N 次 → 占空比 N/20 - 更新 `mode_stable_cnt` 更准确 --- ## ✅ 总结:原代码的问题汇总 | 问题 | 描述 | 建议 | |------|------|------| | 1. 轨迹判断逻辑错误 | case 编码与实际物理意义不符 | 重新校对传感器组合与转向逻辑 | | 2. circle_mode 切换过于敏感 | 单传感器触发易误判 | 改用多步识别或标志区识别 | | 3. mode_stable_cnt 时间不准 | 主循环周期不确定 | 改为定时器中断累加 | | 4. 动作响应不平滑 | 缺少 PID 或渐进调节 | 引入误差比例控制(进阶) | | 5. 缺失默认恢复机制 | 丢线后直接 stop | 应尝试记忆方向搜寻 | --- ## ✅ 如何改进?推荐升级方向 1. **加入 PID 控制算法**:根据偏差量连续调节速度差,使行驶更平稳。 2. **使用外部中断或ADC读取模拟传感器**:提高精度。 3. **增加蓝牙/OLED调试输出**:实时查看传感器状态。 4. **加入 EEPROM 存储模式选择**:掉电不丢失配置。 --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值