简介:面向STM32F103C8T6智能小车爱好者的完整实验源码,整合红外遥控、红外避障、红外跟随、红外循迹与电机调速五大功能,可直接在KEIL4中打开工程并烧录验证。程序基于L293D驱动芯片与TT直流减速电机,配套红外避障/跟随模块、循迹模块及遥控接收管,演示了多种红外传感器的组合控制方式,适合正在做课程设计或入门嵌入式小车控制的读者参考学习。压缩包共41个文件,其中.c与.h为源码文件,.hex为可直接烧写的固件,.uvproj为工程配置,.map与.axf供链接调试分析,.o/.crf等为编译中间产物;目录按SYSTEM、USER等模块组织,另有电机驱动电路原理图与必看程序说明,方便二次开发与调试。压缩包整体体积仅365KB,下载和学习成本都很低;作者在自己的自研小车上亲测可用,工程可靠性有保障。已有1639人学习下载,可作为红外多模式小车项目的快速起步模板。
1. 一块最小系统板顶五台车的功能:这个红外小车项目到底在做什么
拿到stm32f103c8t6智能小车红外遥控+红外避障+红外跟随+红外循迹+调速实验程序源代码.rar这个包名,懂的同行应该立刻能想到:这是把大学电子设计竞赛和课程设计里最常见的五合一红外小车方案塞进了一个工程。蓝板(STM32F103C8T6最小系统板)加三路或五路红外传感器,再利用一对红外发射接收管做遥控解码,就能让一辆小车分别跑出遥控、避障、跟随、循迹四种行为,最后再用定时器输出PWM调速。对于正在做课设、准备电赛或者刚把C8T6调通串口想搞点大作业的人来说,这份代码的价值不在某一行神操作,而在于它把“传感器输入 -> 逻辑判断 -> 电机输出”这条完整链路全部打通了,而且每种红外功能共用同一套底层驱动。
五年前我做类似项目时踩过最大的坑,不是代码写不出来,而是四个红外功能各写一套逻辑,最后main函数里挤了三百行if-else,换个传感器方向就找不到北。这份代码真正值得拆解的,是它如何用有限几个GPIO和定时器资源做状态切换。下面我按自己的工程习惯,把这个包背后的硬件选型、代码结构和参数调法完整走一遍。
2. 底盘硬件与红外传感器选型:C8T6引脚资源怎么分配才够用
2.1 从最小系统板到电机驱动:C8T6的引脚功能摸底
STM32F103C8T6是LQFP48封装,满打满算37个GPIO,但做小车实际能动用的没那么多。PA9/PA10是串口1的TX/RX,如果要烧录调试最好留着;PA13/PA14是SWD下载口,绝对别碰。剩下的引脚里,我需要至少4个输出接电机驱动板的IN1~IN4(或者用PB0/PB1接PWM调速),再加上5路红外循迹、1路红外避障、1路红外跟随和1个红外遥控接收头,总IO数在11到13个之间,C8T6完全扛得住。
常见引脚分配做法是:PA0~PA3接电机驱动板(比如L298N或TB6612FNG)的方向控制,PB0/PB1接PWM调速;PA4~PA8接五路循迹传感器;PA11接避障传感器,PA12接跟随传感器,PB5接红外遥控接收头。这样分配的好处是PA口和PB口分别承担数字输入和PWM输出,避免定时器通道互相抢占。
提示:L298N的ENA/ENB如果直接接3.3V,电机全速转,根本没法调速。必须把ENA/ENB接到支持PWM的定时器通道上,C8T6的TIM2_CH1(PA0)、TIM2_CH2(PA1)、TIM3_CH3(PB0)都是常用选择。
2.2 LM393比较器输出与传感器检测距离调节
市面上卖的循迹、避障、跟随模块,核心几乎都是LM393电压比较器加一对红外发射接收管。发射管持续发出38kHz或直流红外光,接收管根据反射强度改变电压,LM393把这个电压与可调电位器设定的阈值比较,输出TTL电平。检测到白色地面(反射强)时输出低电平,检测到黑线(反射弱)时输出高电平,这个逻辑决定了代码里的判断极性。
距离调节要特别留意:红外避障模块的检测距离一般在2~30厘米可调,出厂默认可能只有5厘米,太近了转弯都来不及。我一般把避障阈值电位器调到10厘米左右,跟随模块调到15到20厘米,循迹模块则压低灵敏度,让传感器距离地面1.5到2.5厘米时刚好能识别黑线。实际调法很简单:把小车放在白纸上,拧电位器到指示灯刚好亮起,再放回黑线上确认灯灭,反复两次就准了。
2.2.1 传感器供电与逻辑电平匹配
C8T6的GPIO容忍5V输入,但传感器模块如果直接供5V,输出高电平也是5V,虽然STM32引脚标称容忍5V,长期跑还是建议统一3.3V供电。循迹模块的VCC接3.3V完全没问题,红外功率低,检测距离略短但不影响课设演示。若现场环境光干扰大,再用5V供电并串1kΩ电阻分压进GPIO。
2.3 电机测速与编码器:没有测速的调速都是盲调
标题里的“调速实验”如果只是固定占空比,那就没意思了。带编码器的直流减速电机(常见的是霍尔编码器,AB相输出)可以接在PB6/PB7、PB8/PB9上,用定时器编码器模式读转速。C8T6的TIM4_CH1/CH2恰好是PB6/PB7,TIM3_CH1/CH2是PA6/PA7,都能做编码器输入。不过注意,如果循迹用了PA6/PA7,编码器只能挤到TIM4上,两个电机各占一组通道,刚好用完。
没有编码器的版本也能做开环调速,占空比按电池电压查表补偿。但作为实验程序,我建议至少保留编码器接口的代码框架,后续升级PID调速时不用改引脚。
3. 工程代码结构与底层驱动:main.c怎么把红外数据和电机控制串起来
3.1 代码工程里应该有哪些文件
拿到压缩包源码后,先别急着编译。先看目录结构,一个清晰的工程至少包含这些文件:main.c(主逻辑)、motor.c/h(电机驱动)、infrared.c/h(红外传感器读取)、remote.c/h(红外遥控解码)、timer.c/h(PWM与定时器初始化)、pid.c/h(调速控制)。如果哪个版本把这些全塞进main.c还跑通了,说明作者赶时间但基本功扎实,参考价值要打折扣。
我自己的工程习惯是:infrared.c只做一件事——把各传感器GPIO电平读进来,打包成一个8位无符号整数返回。remote.c用外部中断加定时器捕获做NEC协议解码。motor.c把PWM占空比和方向控制封装成Motor_SetSpeed(int left, int right)。这样main.c里就能写成非常干净的状态机。
3.1.1 初始化代码示例:GPIO与定时器的最小配置
以标准外设库写法为例(很多实验源码还是老版固件库,HAL用户自行平移),C8T6的RCC时钟、GPIO和TIM初始化如下:
void GPIO_InitConfig(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOA、GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // 循迹传感器输入 PA4~PA8,上拉输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8; GPIO_Init(GPIOA, &GPIO_InitStructure); // 电机方向控制 PA0~PA3,推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_Init(GPIOA, &GPIO_InitStructure); }这段代码的逻辑是:输入引脚设成上拉,这样传感器悬空时读到的电平是确定的1,不会误触发。输出引脚速度设50MHz其实对电机驱动这种低频场景没必要,但养成习惯没坏处。方向控制引脚不需要PWM,所以不用映射到复用功能。
3.2 红外遥控解码:NEC协议的坑与C8T6的外部中断
红外遥控常见协议是NEC,载波38kHz,数据格式为引导码(9ms高电平+4.5ms低电平)+ 8位地址码 + 8位地址反码 + 8位数据码 + 8位数据反码。C8T6跑72MHz主频,用外部中断检测下降沿,再用定时器读两次沿之间的时间间隔,就能区分数据位0(560us高+560us低)和数据位1(560us高+1.68ms低)。
注意:网上很多源码用延时函数轮询接收头,小车一动电机产生干扰就会解码失败。正确做法是把接收头接到STM32的EXTI线(比如PB5),在中断服务函数里记录定时器计数,主循环只消费解码结果。
// 红外解码中断服务函数(PB5外部中断) void EXTI9_5_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line5) != RESET) { static uint32_t last_time = 0; uint32_t now_time = TIM_GetCounter(TIM2); uint32_t duration = now_time - last_time; last_time = now_time; if(duration > 7000) // 引导码,约9ms bit_count = 0; else if(duration > 1000) // 数据位1,约1.7ms remote_code |= (1 << bit_count++); else // 数据位0,约560us bit_count++; if(bit_count >= 32) { decoded_flag = 1; bit_count = 0; } EXTI_ClearITPendingBit(EXTI_Line5); } }这个做的逻辑是:TIM2的计数器工作在自由运行模式,不分频到1MHz,即每微秒计数一次。两次中断间的计数值就是高低电平的总时长。判断阈值取1000到7000之间,能稳定区分三种信号。注意remote_code要在解码完成后才赋值,不然中断里频繁写32位变量,主循环读半截会出问题。
3.3 电机PWM调速:TIM3产生可调占空比的两种方式
调速不外乎两种实现:直接改CCR寄存器,或者用定时器更新中断里逐渐逼近目标占空比。前者响应快但有电流冲击,后者是软件上的软启动。实验代码里最常用的是直接改CCR,因为简单直观:
void Motor_SetSpeed(int left_speed, int right_speed) { // left_speed范围 -100 ~ +100,负值代表反转 if(left_speed >= 0) { GPIO_SetBits(GPIOA, GPIO_Pin_0); // IN1 = 1 GPIO_ResetBits(GPIOA, GPIO_Pin_1); // IN2 = 0 TIM_SetCompare1(TIM3, left_speed * 100); // CCR1 = 占空比 } else { GPIO_ResetBits(GPIOA, GPIO_Pin_0); GPIO_SetBits(GPIOA, GPIO_Pin_1); TIM_SetCompare1(TIM3, (-left_speed) * 100); } // 右电机同理,用TIM3_CH2 }这里有个参数陷阱:如果TIM3的ARR是9999,那CCR的值范围是0~9999,占空比就是CCR/ARR。left_speed * 100相当于把-100~100映射到0~10000,但这依赖ARR正好是10000。更稳的写法是把ARR固定成9999,然后TIM_SetCompare1(TIM3, left_speed * 9999 / 100)。我看过太多源码在PWM频率和占空比范围上对不上,导致小车一快一慢。
4. 红外避障、跟随、循迹与遥控四种模式:一个状态机切换的完整实现
4.1 避障模式:单传感器前方探测与转弯方向决策
避障逻辑最简单:一个红外传感器装在小车正前方,检测到障碍物输出高电平(或低电平,取决于模块),小车就后退并转弯。真正的坑在转弯方向——固定右转的话,如果右边也有墙,小车会卡死。好一点的实验程序会加一个随机数或记忆上一次转向。
void Mode_Avoid(void) { if(IR_Avoid_Read() == OBSTACLE_DETECTED) // 有障碍 { Motor_SetSpeed(-40, -40); // 后退 Delay_ms(200); Motor_SetSpeed(-30, 30); // 原地右转(左轮正转,右轮反转) Delay_ms(350); } else { Motor_SetSpeed(50, 50); // 直行 } }这个代码的关键参数是后退时间和转弯时间。200ms后退在3.3V供电下大约移动2厘米,350ms原地转弯大约是45度。如果电机电压高或者轮径大,时间要缩。改成左转只需把Motor_SetSpeed(-30, 30)换成Motor_SetSpeed(30, -30)。
4.2 跟随模式:双传感器差动判断目标位置
跟随比避障多一个维度:需要判断目标在左边还是右边。常见做法是把一个传感器模块朝向斜前方(或者用两个模块分开左右),目标是检测到反射时认为“前面有人”。我做的跟随方案是左右各一个红外传感器,夹角约30度:
| 左传感器 | 右传感器 | 动作 |
|---|---|---|
| 0 | 0 | 目标丢失,原地旋转搜索 |
| 1 | 0 | 目标偏左,左转 |
| 0 | 1 | 目标偏右,右转 |
| 1 | 1 | 目标居中,直行 |
void Mode_Follow(void) { uint8_t left = IR_Follow_Left_Read(); uint8_t right = IR_Follow_Right_Read(); if(left && right) Motor_SetSpeed(45, 45); // 居中直行 else if(left && !right) Motor_SetSpeed(25, 45); // 左轮慢,右轮快,左转 else if(!left && right) Motor_SetSpeed(45, 25); // 右转 else Motor_SetSpeed(-20, 20); // 丢目标,原地搜索 }这里的转速差25和45是个保守值,实测效果是转弯半径大约30厘米。如果想跟得更紧,可以加大差值到20和60,但要注意电机启动瞬间电流变大,电池电压跌落可能导致C8T6复位。我在做毕设时遇到过转速一加大,红外接收头就误触发,后来在电机供电和逻辑供电之间加了电容隔离才解决。
4.3 循迹模式:五路传感器状态查表控制
五路循迹是这份代码的技术含量最高点。五个传感器排成一排,中间的是主探测位,两边的是纠偏位。理想状态是中间两个传感器压着黑线,此时直行。小车偏左时,左侧传感器离开黑线,右侧传感器还压线,就需要右转。
const uint8_t TRACK_TURN_TABLE[32] = { // 依次对应 PB4~PA8 五路传感器(左到右) // bit4=最左, bit0=最右, 1表示检测到黑线 [0b00100] = ACTION_FORWARD, // 居中 [0b00110] = ACTION_TURN_RIGHT_SLOW, // 偏右 [0b01100] = ACTION_TURN_LEFT_SLOW, [0b00010] = ACTION_TURN_RIGHT_FAST, [0b01000] = ACTION_TURN_LEFT_FAST, [0b00000] = ACTION_STOP, // 全丢线 [0b11111] = ACTION_STOP, // 全压线(十字路口) };查表法比if-else链的好处是:传感器状态和动作一一对应,加新路况只需往表里加一行。每个传感器的电平组合成一个5位整数作为下标,TRACK_TURN_TABLE[state]直接得到动作码。有些源码用switch-case,效果一样,但扩展性差一些。
void Mode_Tracking(void) { uint8_t state = 0; state |= IR_Track_Left2_Read() << 4; state |= IR_Track_Left1_Read() << 3; state |= IR_Track_Mid_Read() << 2; state |= IR_Track_Right1_Read()<< 1; state |= IR_Track_Right2_Read()<< 0; switch(TRACK_TURN_TABLE[state]) { case ACTION_FORWARD: Motor_SetSpeed(55, 55); break; case ACTION_TURN_RIGHT_SLOW: Motor_SetSpeed(40, 55); break; case ACTION_TURN_LEFT_SLOW: Motor_SetSpeed(55, 40); break; // ... 略 } }循迹中最容易忽略的是传感器安装高度和间距。五个传感器排成一排,间距要小于黑线宽度(常见黑线胶带宽度1.8cm),这样至少有一个传感器能覆盖黑线。高度控制在1.5cm左右,太高则环境光干扰变大,太低则地面不平会刮擦。调间距比调代码重要十倍,源码里的查表参数只有在传感器间距正确时才有意义。
4.4 遥控模式:键值映射与调速档位切换
遥控模式的实现分两步:第一步是NEC解码拿到原始键值,第二步是把键值映射成小车动作。很多源码在第二步偷懒,直接拿遥控器厂商定义的键码,换一个遥控器就全部失灵。正确做法是在初始化时让小车进入“学习模式”,按一下遥控器某个键,代码把解码值存到EEPROM(C8T6无内部EEPROM,可用Flash模拟或用外部AT24C02),之后按键映射就固定了。
void Mode_Remote(void) { if(decoded_flag) { decoded_flag = 0; switch(remote_code) { case KEY_FORWARD: // 0x45,实际以学习值为准 Motor_SetSpeed(60, 60); break; case KEY_BACKWARD: // 0x46 Motor_SetSpeed(-60, -60); break; case KEY_LEFT: // 0x44 Motor_SetSpeed(-30, 60); break; case KEY_RIGHT: // 0x43 Motor_SetSpeed(60, -30); break; case KEY_STOP: // 0x40 Motor_SetSpeed(0, 0); break; case KEY_PLUS: // 0x48 加速 speed_limit = (speed_limit < 100) ? speed_limit + 10 : 100; break; case KEY_MINUS: // 0x49 减速 speed_limit = (speed_limit > 20) ? speed_limit - 10 : 20; break; } } }遥控模式下有个容易踩的坑:红外遥控接收头和不带编码器的直流电机共用电源时,电机启动瞬间的电压跌落会让接收头误码。我在代码里加了个软件滤波——连续两次解码值相同才执行动作,这比加硬件滤波简单得多,效果也够用。
4.5 模式切换机制:按键、串口还是拨码开关
四种模式共用一个main循环,通常用KEY1/KEY2两个按键切换模式。按下一次,当前模式编号加1,取模后进入下一个模式。也可以用串口发指令切换,方便调试。
int main(void) { uint8_t mode = 0; // 0遥控 1避障 2跟随 3循迹 GPIO_Config(); Timer_PWM_Config(); EXTI_Remote_Config(); USART1_Config(); while(1) { if(KEY_Scan() == KEY1_PRESS) mode = (mode + 1) % 4; switch(mode) { case 0: Mode_Remote(); break; case 1: Mode_Avoid(); break; case 2: Mode_Follow(); break; case 3: Mode_Tracking(); break; } Delay_ms(20); // 主循环节拍20ms,电机控制周期50Hz } }20ms的延时让主循环稳定在50Hz,这个频率对电机控制来说足够平滑。如果延时太短,比如2ms,传感器读取和模式切换都会正常,但电机驱动信号更新太快,PWM输出与定时器产生竞争,可能出现电机抖动。
5. 调速与PID:从固定占空比到编码器闭环的参数整定方法
5.1 开环调速的局限:占空比与转速的非线性关系
标题里的“调速实验”如果只是写TIM_SetCompare(x)然后观察转速变化,那调完就完了。直流电机在低占空比下的死区效应很明显:占空比低于15%时电机根本不转,从20%到30%转速变化又很大,从70%到100%变化就不明显了。这意味着开环调速时,低速档位和高速档位的分辨率不一致,小车走直线需要不断微调左右轮的偏置。
5.2 编码器测速与增量式PID核心代码
带霍尔编码器的电机,C8T6读AB相可以用定时器编码器模式,完全由硬件计数,不占CPU。初始化TIM4为编码器模式后,直接TIM_GetCounter(TIM4)就能拿到脉冲数,配合定时器中断计算速度:
// 每10ms读一次编码器计数,计算实际转速 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { static int16_t last_count_left = 0; int16_t cur_count_left = TIM_GetCounter(TIM4); int16_t delta = cur_count_left - last_count_left; last_count_left = cur_count_left; pid_left.actual_speed = (int16_t)((int32_t)delta * 1000 / 10 / ENCODER_LINES); // ENCODER_LINES 是电机编码器线数(如13线霍尔,经减速后一圈脉冲 = 13 * 减速比) TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }增量式PID计算公式是:output += Kp * (e[k] - e[k-1]) + Ki * e[k] + Kd * (e[k] - 2*e[k-1] + e[k-2])。在STM32上实现时要注意整型溢出问题:转速误差在几十到几百之间,把Kp调成小数在定点运算里需要缩放。常见做法是把Kp放大100倍,输出再除以100:
int32_t PID_Calc(PID_TypeDef *pid, int16_t target_speed) { int16_t error = target_speed - pid->actual_speed; int32_t delta_out; pid->error[2] = pid->error[1]; pid->error[1] = pid->error[0]; pid->error[0] = error; // 增量式PID,系数已放大100倍 delta_out = (int32_t)pid->Kp * (pid->error[0] - pid->error[1]) + (int32_t)pid->Ki * pid->error[0] + (int32_t)pid->Kd * (pid->error[0] - 2*pid->error[1] + pid->error[2]); delta_out /= 100; pid->output += delta_out; // 输出限幅,防止积分饱和 if(pid->output > pid->max_output) pid->output = pid->max_output; if(pid->output < pid->min_output) pid->output = pid->min_output; return pid->output; }这套代码最容易被忽视的是pid->output的初值设置。如果初始为0,PID启动时会先猛蹿一下再收敛,因为误差大导致积分项快速累积。我一般把output预设在目标占空比附近,比如目标转速对应的开环占空比,PID只在那个基础上做微调。
5.3 参数整定口诀与实测方法
调PID没有捷径,但有个稳定的顺序:先令Ki=Kd=0,只调Kp,从小到大加到系统出现等幅震荡,记下此时的Kp(记为Ku)和震荡周期Tu。然后用Ziegler-Nichols参数表换算:Kp=0.6Ku,Ki=1.2Ku/Tu,Kd=3Ku*Tu/40。
| 参数 | 过小表现 | 过大表现 |
|---|---|---|
| Kp | 响应慢,爬不上目标速度 | 电机啸叫,速度波动大 |
| Ki | 稳态误差消除不了,跑不快时左右轮转速不一致 | 过冲明显,起步回声重 |
| Kd | 抗干扰差,颠簸路面转速突变 | 高频抖动,PWM输出毛刺多 |
我在C8T6上做两轮差速小车,常用参数范围是Kp=30~80、Ki=5~20、Kd=0~5。注意这是放大100倍后的值,直接用原始小数会超出int32的预算范围。实际调试时,先把编码器测速值打印到串口,观察空载和负载两种情况下实际转速曲线再定参数。
6. 四合一模式整合与现场调试:用逻辑分析仪和串口打印快速定位问题
6.1 模式切换时的电机防抖与传感器互扰
四合一模式最怕切换瞬间电机乱转。原因是模式切换函数里直接改了PWM占空比,而传感器状态还没稳定。我加了一个50ms的“模式切换过渡期”:切换模式后先把电机停住,延时50ms,再开始执行新模式逻辑。这个时间足够让所有传感器输出稳定。
另一个问题是红外遥控接收头与循迹传感器的载波频率冲突。循迹模块的红外发射管是常亮的,周围环境如果有同频红外光,会干扰接收头解码。解决方法是把遥控接收头贴在高于底盘3cm的支架上,或者给接收头套一个黑色热缩管只留正前方开口,物理屏蔽侧方干扰。
6.2 串口输出每个传感器的原始电平,比猜代码快十倍
调试四方模式时,我最常用的是串口打印传感器状态:
printf("Mode:%d Track:%d%d%d%d%d Avoid:%d Follow:%d%d\r\n", mode, IR_Track_Left2_Read(), IR_Track_Left1_Read(), IR_Track_Mid_Read(), IR_Track_Right1_Read(), IR_Track_Right2_Read(), IR_Avoid_Read(), IR_Follow_Left_Read(), IR_Follow_Right_Read());把这段加在main循环里,用USB转TTL接PA9/PA10,波特率115200,电脑端OpenCRT或Python串口工具看。逻辑很简单:小车不动时用手在传感器前晃,看串口数据是否跟着变。如果某一个传感器的电平永远不变,先用万用表量模块输出引脚电压,排除接线问题,再看代码读的GPIO端口有没有配错。
6.3 循迹地图设计与速度适配的最终验证
循迹模式跑不跑得稳,很大程度卡在场地和速度上。用黑色电工胶带在浅色地板上贴一个S弯,转弯半径不小于30厘米,直线段不少于50厘米。循迹速度先给到30%占空比跑一圈,确认每个弯都能过,再逐步加到50%、70%。速度一高,小车在急弯处会甩尾,传感器脱离黑线。这时需要调高转向灵敏度,也就是把查表里的慢转动作改成快转,或者适当降低直行速度保持一致性。
如果赛道是十字路口,五路全为1表示压在线交叉点上,这时候的决策是:直行优先还是转弯优先取决于比赛规则。源码里的ACTION_STOP是个安全选择,但在实际比赛中,我会把十字路口的处理改成“上一次转弯方向延续”,比如小车进十字前正在左转,就继续左转,这样省一个判断逻辑。
6.4 一个调车技巧:把循迹传感器读数映射成LED显示
不用串口的时候,可以借用C8T6板载LED(PC13)做简易指示:让LED闪烁快慢代表传感器状态变化频率。但更实用的做法是把五个循迹传感器的值映射到C8T6上五个空闲GPIO外接的LED,小车过线时哪颗灯亮一目了然。我看过有同学用PA0~PA3的电机方向控制引脚顺带点灯,结果一开电机灯就乱跳,最后发现是方向控制引脚下拉电阻不够。有条件还是尽量用串口,数据直观且不用分心看灯。
本文还有配套的精品资源,点击获取