找回密码
 立即注册
搜索
热搜: 活动 交友 discuz

八路循迹模块的 DO:先让小车沿着线跑起来 - Zw-awa

wwhefen |8 查看 |0 回复 [复制链接]
发表于 2026-10-8 23:41:06 | 显示全部楼层 |阅读模式
Q: 循迹模块的 DO 是什么?
, ?) Y, ]5 @* ]" O6 Q& f* p' F3 M5 O9 _, z+ E
A: DO 是模块比较器根据反射光强输出的数字电平,只表示当前是否超过设定阈值。+ i" ^; j. c& V5 _2 k, w; f! l
4 W4 S+ G, }: J' |) s
Q: 为什么先用 DO 做循迹?
  I# e/ y. f% q! i: |3 E) J& ^% l# M8 n8 ?
A: 它把复杂的光学变化压成容易读取的状态,能先验证传感器排布、方向判断和基本差速动作。8 G# N6 V4 b$ \5 K; e$ p
' p6 D; u6 j" z. M4 _7 o7 o
Q: DO 循迹常见使用场景?( r- M7 @' |: X: w' Z3 r" U
" ?# f0 U( S  f" ^# s6 O9 B
A: 黑线跟随、边缘检测、全黑停车、全白丢线保护和低速路径验证。! m3 h$ b' F0 }  F, X

4 a; G- x. t6 A8 @3 X% M' y把八路模块放到白底上,8 个 DO 各自保持稳定电平;黑线从探头下方经过,某一路的指示灯亮起,DO 也随之翻转。STM32 接收到的不是连续的灰度,而是模块上的比较器根据电位器阈值作出的二选一结果:黑,或者白;1,或者 0。原始反射光强的细节被留在了比较器之前,但这个简化后的结果已经足够让小车沿着线跑起来。9 C. S: N# [( d2 E
9 d; J% `. A7 l5 E& ]5 x
接线时把车头朝前,从左到右将探头编号为 S0 到 S7。代码约定黑线为 1、白底为 0;全黑停车,整排白底视为丢线,保持上一次转向方向低速找线。至于黑线到底对应高电平还是低电平,以实际读到的状态为准,统一在配置项中处理。
, ?- X3 \2 ^( i( X0 X* W/ y# U
3 O7 K* F0 d; H& X  P, D2 tDO 不是“数字版 ADC”2 [( D) N0 ]9 m* z! K- K
. [, H) v! q  [$ \  O+ a( h3 E
很多常见 MH-Sensor-Series、TCRT5000 类模块都有 VCC、GND、DO 和 AO。其中 AO 是传感头此刻反射光强对应的模拟电压;DO 则经过板上的比较器。你拧动电位器,其实是在移动比较器的分界线:高过线的一边输出一种电平,低过线的另一边输出另一种电平。* m9 E9 t" R4 L7 n
' s/ O9 ^! E' N# Y9 q
红外发射 + 接收 -> 反射强弱 -> 模拟电压 AO -> 比较器 + 电位器 -> DO: 0 或 1# [5 Z3 d: S8 c7 Y( `( N, u, p

5 F# K7 a, V+ h( e所以 DO 的优势和代价都很直接:
$ ?4 K( l7 Z. j7 K5 |$ o) o; S6 @# m* e) V$ b
它能让 STM32 只读 GPIO,不需要先写 ADC、平均、阈值。
. Q. j4 x" w5 W) m6 D2 {) z( @2 r4 x" E
模块上已经先定好了“黑”和“白”的边界;环境一变,电位器就得重新调。
  m0 I8 Z  W" r( O2 W  {! A; ^' h, i. v
当它误判时,STM32 只看见一个 0 或 1,很难知道原始反射值离边界到底有多远。# H% j7 [$ T3 s) A3 X8 c4 k
4 c+ k6 n4 p/ L7 E+ P% ~
刚开始做完整低速循迹时,我会先用 DO,因为黑线位置、转向和差速之间的关系能马上落到轮子上;需要更细的边缘位置、调试环境光或建立更稳定的判断时,再回头用 AO。7 Y4 V9 N' R! y5 I$ Z3 B# e3 W
- r( Q' F5 O2 [* B* @/ d" v
八个模块怎么接,不靠一张万能线序图
) o& k/ K0 j2 I9 V/ B, X/ N/ K
2 G  m5 b6 s9 J" V. c- ?8 路模块都需要 VCC 和 GND,每个 DO 各接一根 STM32 输入线。模块常见支持 3.3V 到 5V 供电,但仍然应当以买到的模块资料为准。我使用 5V 供电,DO 输出通常可以直接给 3.3V MCU 读取。
" s( s/ c; d, P& u  |. j) ^1 g! d2 z! _
我暂定的 GPIO 是:) l9 k; @8 f; f" t& H$ t  X
; u) a- K. m* K  H, ~% z% \, H
PA15 需要关闭 JTAG 后才能当普通 GPIO 使用,工程会在初始化时处理这一点。你的循迹板如果排针顺序不同,只改引脚表,传感器从左到右的权重顺序不要跟着物理线束乱掉。" ]5 y7 C& k( e0 d" s
# Q! L0 j2 I8 m
车头朝前看时,S0 是最左侧,S7 是最右侧。八个模块必须排在同一条横线上,离地高度尽量一致。一个模块抬得更高,另一个贴得更近,代码再整齐,读到的也不是同一条黑线。/ T4 t8 }4 ^3 @0 ]9 g

+ c7 K) j/ F- B) H* i先把电位器调到黑白交界,而不是随便拧% q) o9 x2 Z- H# o3 s

4 T# S* z) w3 C- l先不上车跑。给模块供电,把其中一个探头依次放到白底和黑线下方,观察模块板上的比较器指示灯或者 OLED 显示。慢慢调电位器,找到一个位置:白底稳定输出 0,黑线稳定输出 1。+ j7 I5 K4 t( P/ w/ K

# W1 P) b, k* l不是所有模块都按这套电平方向工作。工程把这个约定写成一个宏:
+ L+ V1 I8 [0 J5 J, Q6 l0 s
% c2 n) \& ^' Y7 f- n8 q. p#define LINE_SENSOR_BLACK_IS_HIGH 1/ h* O! w* e  T& l) {& |4 I8 g
* {4 z* K- [+ k, ^* p* m
若你实际测到黑线是 0、白底是 1,不要急着把八根 DO 线交换,也不需要把所有判断条件里的 if 逐行取反,只要改为:! q  A9 w0 w* W2 L5 Y

# s7 k/ z. {( _' g4 z6 ^#define LINE_SENSOR_BLACK_IS_HIGH 0
% G* p$ I/ x3 p. P0 _/ c3 [7 [! A0 v9 _# H; m' M
模块指示灯亮不亮也不必和“黑线为 1”强行绑定。灯接在比较器的哪一侧、板子是否低电平点亮,都取决于模块电路。相信你在 OLED 里实测到的逻辑值。6 I6 B; b" k6 H4 y0 L* v- S' L2 ^

9 w4 O7 @5 n( r8 ?& C# B八个 0/1 先装进一个字节
( B' O0 Z0 b) A! n
. u( d$ d4 Y. l  ~( z定时器每 10ms 读取一次传感器。bit0 放 S0,一直到 bit7 放 S7,这样 8 路状态只占一个 uint8_t:
$ S% E; P3 L3 K+ Q" i% e% A9 ]4 r0 M9 [) `" K: E
S7 S6 S5 S4 S3 S2 S1 S0+ p0 \: f7 z9 E: e& d
& w* s5 C" h# G2 ~4 B. _
0 0 0 1 1 0 0 0 二进制:00011000$ `0 h) t  Y8 l" U7 v* S! S- k

* K' g# g: E9 h6 Q7 @, R工程在 LineSensor_ReadMask() 里把它们收起来:! g: g: X/ R$ C
' X2 @0 H& ]# D3 C% s
if ((GPIO_ReadInputDataBit(port, pin) != Bit_RESET) == LINE_SENSOR_BLACK_IS_HIGH)
5 K) `9 S# D5 S1 E: v# E4 b  I
* B+ ?9 N9 p1 f% ?/ k  p, imask |= (uint8_t)(1u = 10)
6 Z8 j! B. ]% t& T; X. T; [1 F8 A& O2 X( `# [( ?
control_divider = 0;
% Y" Y! k( \. d& y, R" l  b3 V# r0 ?
$ e+ m: j! M; oLineSensor_Update();
, `0 W1 g, |/ I6 ?7 m  m5 B
. {. d4 j. h# xMotor_Update();+ x' L  W9 ]" ?
6 D6 \) _% K4 z; c; H% Z2 W2 s
LineSensor_Update() 只是读取 8 路 GPIO、算一个很小的固定差速;Motor_Update() 只是写方向脚和 TIM2 的比较值。这些动作短、时间稳定,放进 10ms 中断能保证控制周期不被主循环里多一条调试代码打乱。: z3 s7 z9 a; L" d

, U8 V; t% J8 F$ i. D/ P: KMotor_Update() 写的是 Motor_SetTargetPercent() 存下来的两个目标值。这个接口就是前面 PWM 那篇里单轮版本的两轮版:
: p& y/ V7 P' \  \+ l
3 u$ M# e4 X1 Z' x$ Lvoid Motor_SetTargetPercent(int16_t left, int16_t right)
! l* p+ c! [  ]
4 H$ e  i/ }; i7 d6 s; ~left_target = Motor_LimitPercent(left);: y, `2 h. j- N0 j. P
, J7 h! ]1 a9 V. M0 m* m
right_target = Motor_LimitPercent(right);" y5 @3 r5 M3 j7 M
; w- |& Y' J; k- E' r0 S0 s
Motor_LimitPercent() 做的是同一件事:把越界的数夹回 -100 到 100。单轮调用现在只是一个薄壳:
, d$ _/ l7 _8 f# S2 i
  k7 Q1 V& \  |! t& Z! }. r- {void Motor_SetPercent(int16_t percent)
- a, y. v5 p3 b( k4 _9 I, k' J
+ v' v1 p  K4 ~6 Y. l# C9 Z  l) BMotor_SetTargetPercent(percent, percent);
: v8 G% T% f9 e4 L0 u4 P. a3 O2 h) `) F" s9 Q2 g* k
OLED 刷屏仍然放在 main(),每 100ms 才做一次:! W4 R; _0 g7 W1 `, B. \# C' v* Y

, x- t" x! ^9 }- b0 I( ^( ~( Q) qif (Timer_Expired(&report_time, 100))1 x. j$ t' U& F  W
: [" T; N( c& P5 t
OLED_ShowNum(0, 0, LineSensor_GetMask(), 3, OLED_6X8);1 B7 s5 G8 O$ M0 Z- {4 x

# ~" R# B5 X, S8 R9 T) H3 \0 G' z把一整屏 OLED 数据塞进 10ms 中断,刚刚获得的稳定节拍又会被自己砸掉。控制任务和调试任务都需要时间,但不该抢同一段时间。
5 i8 `. m8 q. D. s8 O, h/ ?4 R; F; O( O; a9 Q3 X3 ^
从“哪边压线”到固定差速, o' Z  _1 ~$ Y, g7 S7 [

# z' e( f7 q3 S9 ]八路传感器不是八句独立的 if。代码给每个位置一份权重:
+ r( N* j! M5 f9 _( k- G2 o0 s% l6 z& E4 ?& [
static const int8_t weights[8] = {-7, -5, -3, -1, 1, 3, 5, 7};7 e( l% z9 J8 b( ?

9 ], S1 }& b4 R. C  U黑线被 S0、S1 看到,平均位置偏左;被 S6、S7 看到,平均位置偏右;中间的 S3/S4 命中,位置接近 0。这里算出来的 position 只是“线偏在哪一边”的离散描述,不是任何神秘控制量。
6 U' m3 v) W: @
/ {7 U; P) k0 P+ T5 X6 }left = 30, right = 304 V7 Y* _' n9 }0 m" D3 I. w
9 H/ Q- |, n) j7 I/ O+ w
线偏左,左轮慢、右轮快,小车就往左收回来:+ V, e4 m+ d. D" C$ }
0 N3 O' q' l2 L7 e7 }
轻微左偏:left = 24, right = 36
/ T+ a0 P- m( j
" D/ \4 `1 p* C. F! l: s明显左偏:left = 18, right = 426 X5 `; W3 P, ~: K& L8 M
4 {8 m& U0 b+ }. y5 o/ o
严重左偏:left = 10, right = 50- K7 e  [! Q' X; M) y1 @% l- e
% |" l( _7 Q/ E7 K
线偏右时反过来。工程把这些差速写成 6、12、20 三档,不会假装它们对每个底盘都正确:% j- L: v' m# w
6 H2 ], T- O, G  `: `) N8 k  C4 m
if (position == 0)( |1 ]. ~3 m: h: V2 ~  K
% e* o& M9 _3 K
correction = 0;
1 l) ~. j( A. }; E' S
5 ~9 o6 u5 x4 U! ^- e3 celse if ((position = 5)). t4 a2 L5 S4 m+ t

+ G9 h& S  [/ r% z, ?8 V& wcorrection = 20;8 q' d: O9 V9 F8 p# X( i
& \( C" t- S. N" n9 w* _  q7 l9 U
else if ((position = 2))
2 F: A! Z, z- a! l  V) p6 N2 q+ }1 k9 w1 q, r& N2 J. _+ n
correction = 12;$ z, z, g5 I/ _2 }+ h3 x! i

6 G  J" s8 o5 _else if (position != 0)3 c( X" O. [  E) u2 E
4 |+ p  v: L6 ^) ~. F4 H( s
correction = 6;$ Y9 K9 y5 l6 z( n! u3 z# e, {

+ Z) P0 n8 `. x- X9 z3 P' jLINE_BASE_SPEED 默认是 30。某些电机带着车身低于 50% 就压不过静摩擦,30/10/50 在它们身上不会换来漂亮的渐变转向,只会换来停、抖、突然窜出去。那不是 8 路循迹逻辑错了,是电机、减速箱、供电、地面和占空比门槛没有一起考虑。先架空观察方向,再从实际能稳定起转的最低速度往上调。
- D6 E8 w: z+ O1 c' F4 ?% R  q) a5 T6 ~. N# i: c4 g( W1 J
两个特殊画面,必须单独写
( @9 x3 X4 ~/ w6 E# ~
6 ~3 e0 b  K1 q- p. Z8 r八个模块同时看到黑线,工程默认停车:
& j5 E0 f" k9 ?3 Y; x) y5 g- o% i7 d6 a6 ?+ q& M
if (sensor_mask == 0xFF)
9 B: N5 z" s, T3 d) P% N6 p' A- Y# h. b9 i' m
Motor_SetTargetPercent(0, 0);
2 I% i/ b6 T" z. J+ _
, c, L, k% s( s- d' s5 K! X1 @7 T它可能意味着终点横线、加宽的标记、传感器贴得太低,也可能只是一张整块黑纸。默认将它作为最保守的安全行为;赛道把全黑定义为“掉头”或“停止线”时,再将这个状态映射到相应动作。
+ p/ i( {# y+ `$ i( s8 A
8 i4 [' C% E$ d6 D# {0 e, Q八个模块都没看到黑线时,不让车直接停住。它保留最后一次偏转方向,低速往那边找:5 q3 p; o8 h$ d

9 i) ^8 A4 z5 Y( s  p; `if (sensor_mask == 0x00)( Q$ V/ g: n2 C9 b% I. x2 i
, X0 `' l& A2 x* [5 _) t6 j
if (last_turn < 0)
# C8 v& `% s/ h7 S6 y  I, p. ?( l# \: e  c2 R3 s
Motor_SetTargetPercent(10, 35);
" X& Q* C+ d5 Z" ?( o' o
# z1 a. u# k$ t5 c' w7 K9 wMotor_SetTargetPercent(35, 10);
( `: L/ D! P7 S7 `, i2 Q* [: r  Y; k
最后一次线偏左,就继续往左寻找;最后一次偏右,就继续往右寻找。这个策略不保证每一种赛道都能找回线,它只是比“丢线以后随机直走”更有依据。若你只用 2 路传感器,很容易在一个急弯处直接把线丢掉;8 路排在车头前方,能给这段寻找动作多留一些位置线索。+ t; ]0 {* w  ?! w6 O4 M+ y& k
0 S' e$ }' {- c) `0 Q; t
先在 OLED 里确认,再放到地上
! g7 I: f0 m4 T* n$ _
6 i+ M& n8 j; Y; W( }2 q( L0 p主循环每 100ms 更新调试信息。OLED 三行依次显示:
8 i2 B$ B0 P3 \8 U$ U2 @6 \0 Q3 ^/ F. Y5 l
第一行:8 路掩码的十进制值, |- N9 j9 I* A- C  |! n
1 Z9 n! F4 Q( E* X
第二行:left 目标速度
# v4 _, R9 w- H8 m/ N' m, ?; D
2 m9 ?: x" ]) S9 C: q5 e( j  N$ s第三行:right 目标速度8 x: e5 N6 n  [  g: ]0 p: b( D
( C2 i) O$ w4 i
先把一小段黑线从 S0 缓慢扫到 S7,看掩码和左右目标速度怎样变化。再把整排放到白底,确认它进入上一次转向的找线逻辑;最后用整块黑卡纸盖住,确认两轮目标都变成 0。
+ k' b3 |: B2 Q- B
: C2 _# `. D& [% g! X4 _确认这些之后才接电机。先架空,让左/右轮执行的方向与画面中“线偏左就向左修正”一致;若实际转向反了,通常是电机安装方向、驱动板方向脚或左右电机定义和代码约定相反。先统一这些基础方向,不要把 8 路权重全部颠倒来掩盖一根电机线接反的问题。  w. G+ Q# _  s7 e. y6 [: d

$ D$ q. F0 H7 x, o; `5 m2 L放到地面后,环境光一变、黑线褪色、白底反光、模块高度不齐、两个同型号模块电位器位置不一样,都可能让 DO 在边缘附近来回跳。重新逐个调电位器,把黑白切换点留在稳定范围中间;真需要知道每个模块离阈值还有多远,就进入 AO 模拟输出篇。
8 F* |- G0 L! o' e! c- }; o8 I  S2 G6 W" I
现在的转向是固定三档差速:看到线在左边就给左慢右快,看到线在右边就反过来。急弯、不同速度、不同电机启动门槛和不同赛道,都会让同一张表出现过转或转不过去。
' J, T* B' ]3 C0 R# c
  {+ ~1 R- G3 G( c) P但它有一个好处:每一个动作都能从 mask、position、左右目标速度直接追出来。你把黑线挪到 S0,OLED 显示更大的左偏转向;把黑线挪回中间,两个目标又回到同一速度。传感器、差速和轮子之间的因果链没有被参数遮住。
5 ?3 x3 t% T5 C0 V2 c" y" Q; C+ W2 d0 ]; h3 C6 t
DO 模式给小车的是一排已经越过阈值的决定。把这些决定接到可靠的固定节拍和低速差速上,小车就能沿线走;需要观察比较器作出决定前的电压时,再使用 AO。
% `6 j& I0 `" |' i/ I5 u/ O, p6 W' c& R
——/ S+ ~1 s% C, v
本文转载自 ★博客园 首页,版权归原作者所有;如涉侵权请回帖告知,我们会立即删除。
wwhefen

27

主题

0

回帖

75

积分

管理员

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

本版积分规则 上传到网盘

Archiver|手机版|小黑屋|缘诚社区 ( 苏ICP备2026070684 )

GMT+8, 2026-11-13 00:20 , Processed in 0.073593 second(s), 24 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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