Q: 循迹模块的 DO 是什么?
# e1 P6 O; |7 i- @) U2 }' x3 ^! R) M
. f. U: u' h6 z) \8 YA: DO 是模块比较器根据反射光强输出的数字电平,只表示当前是否超过设定阈值。
2 I, T! d7 z' A. o+ w w `/ C. p' L* O$ G8 {
Q: 为什么先用 DO 做循迹?
* u6 f; h: M/ W3 ^% `
) H; K. m4 v8 S7 W& W. S! rA: 它把复杂的光学变化压成容易读取的状态,能先验证传感器排布、方向判断和基本差速动作。
- [! g: b3 @4 U2 Y' j9 V! T. @- Z- ?( _. D9 X6 t/ q; l! i" ]
Q: DO 循迹常见使用场景?8 _' k- |3 e8 r' D# J$ y/ D
~3 _- {' p2 U3 R0 r) m/ w+ b% _# ZA: 黑线跟随、边缘检测、全黑停车、全白丢线保护和低速路径验证。% ^2 X$ }& ~6 a
/ n I# o& G0 D9 y6 w( w
把八路模块放到白底上,8 个 DO 各自保持稳定电平;黑线从探头下方经过,某一路的指示灯亮起,DO 也随之翻转。STM32 接收到的不是连续的灰度,而是模块上的比较器根据电位器阈值作出的二选一结果:黑,或者白;1,或者 0。原始反射光强的细节被留在了比较器之前,但这个简化后的结果已经足够让小车沿着线跑起来。
# ?$ }. \! P/ i) q
# ]' {; v% P& `接线时把车头朝前,从左到右将探头编号为 S0 到 S7。代码约定黑线为 1、白底为 0;全黑停车,整排白底视为丢线,保持上一次转向方向低速找线。至于黑线到底对应高电平还是低电平,以实际读到的状态为准,统一在配置项中处理。
* ~: C, j( N, k* g- }
: a1 _0 j9 J, f1 TDO 不是“数字版 ADC”; \0 r! L8 M% C) x; j: D; N2 y
0 X8 n6 d; V6 A! N' K
很多常见 MH-Sensor-Series、TCRT5000 类模块都有 VCC、GND、DO 和 AO。其中 AO 是传感头此刻反射光强对应的模拟电压;DO 则经过板上的比较器。你拧动电位器,其实是在移动比较器的分界线:高过线的一边输出一种电平,低过线的另一边输出另一种电平。- m3 t) Q4 u8 L+ u% V* ]) S' r6 Z
0 u# G8 Z, E6 t, {) e; F/ c2 ~+ P0 \红外发射 + 接收 -> 反射强弱 -> 模拟电压 AO -> 比较器 + 电位器 -> DO: 0 或 1
" X& X* r& b# \: j3 b
9 L2 R0 `/ [" R% k/ `: v/ X8 f4 k所以 DO 的优势和代价都很直接:4 k. k3 P' g. h5 M, P' y7 }; U! i
# n$ \: r3 Z$ B& S
它能让 STM32 只读 GPIO,不需要先写 ADC、平均、阈值。; l1 f/ h9 c; m! x h( l3 _
6 D7 ?7 _$ U1 c* v) E
模块上已经先定好了“黑”和“白”的边界;环境一变,电位器就得重新调。9 c/ F8 `& M5 E F& ~7 I [2 S& o0 W: D
# k- n$ B! I# t' ~当它误判时,STM32 只看见一个 0 或 1,很难知道原始反射值离边界到底有多远。
/ Z \- D% |- W, D
, H+ p& A4 E* p. q; |刚开始做完整低速循迹时,我会先用 DO,因为黑线位置、转向和差速之间的关系能马上落到轮子上;需要更细的边缘位置、调试环境光或建立更稳定的判断时,再回头用 AO。7 e B* |1 S. ~! H# t, T- Y% E
6 a. b' v+ H0 w% H3 s4 h
八个模块怎么接,不靠一张万能线序图; T' |: I, a( R/ O
& L- s* u) h( `, i( C
8 路模块都需要 VCC 和 GND,每个 DO 各接一根 STM32 输入线。模块常见支持 3.3V 到 5V 供电,但仍然应当以买到的模块资料为准。我使用 5V 供电,DO 输出通常可以直接给 3.3V MCU 读取。
" a" |# Z6 J! c3 q8 `) r5 r! q4 J: I. V# f" y* `
我暂定的 GPIO 是:
& W8 G! l: H- I) B) H1 {* l3 e
1 h, I4 E: ~5 Z( d+ Y ?PA15 需要关闭 JTAG 后才能当普通 GPIO 使用,工程会在初始化时处理这一点。你的循迹板如果排针顺序不同,只改引脚表,传感器从左到右的权重顺序不要跟着物理线束乱掉。
+ K4 |: t# ]- [( M, s2 m0 h7 Q: W' p, |
车头朝前看时,S0 是最左侧,S7 是最右侧。八个模块必须排在同一条横线上,离地高度尽量一致。一个模块抬得更高,另一个贴得更近,代码再整齐,读到的也不是同一条黑线。, m6 @5 R) R# g1 G
! D) [) g8 N3 N& m4 W. P5 U8 H1 D9 T! K
先把电位器调到黑白交界,而不是随便拧
" f0 }7 P: u# G
5 W8 [. O0 {6 g$ V( Y先不上车跑。给模块供电,把其中一个探头依次放到白底和黑线下方,观察模块板上的比较器指示灯或者 OLED 显示。慢慢调电位器,找到一个位置:白底稳定输出 0,黑线稳定输出 1。
; m# {4 g% p( u A6 p# ?8 T; e& B# L4 c6 T* X# D/ c G
不是所有模块都按这套电平方向工作。工程把这个约定写成一个宏:
9 T) S6 M% K" p" ]" R$ |/ u! S* w. V1 O
#define LINE_SENSOR_BLACK_IS_HIGH 1
$ ]0 S# W# e; Q* `7 B
9 m) D9 S6 G' _# H6 O8 W若你实际测到黑线是 0、白底是 1,不要急着把八根 DO 线交换,也不需要把所有判断条件里的 if 逐行取反,只要改为:9 p6 [4 L$ y4 Q+ B
5 \: Y' ^6 e7 v; J7 v" H
#define LINE_SENSOR_BLACK_IS_HIGH 0
& B9 L. l! E2 o6 Q3 |& ]5 x
' _8 L; r' m) c9 b模块指示灯亮不亮也不必和“黑线为 1”强行绑定。灯接在比较器的哪一侧、板子是否低电平点亮,都取决于模块电路。相信你在 OLED 里实测到的逻辑值。
9 H1 O) L% {1 ]$ b" p; {8 ^. a
5 p# z/ S7 H& u3 S2 T( U八个 0/1 先装进一个字节2 V5 h% S% ], v- s
% m# B( a0 d0 k; k: I/ ^- W
定时器每 10ms 读取一次传感器。bit0 放 S0,一直到 bit7 放 S7,这样 8 路状态只占一个 uint8_t:
0 j1 u: y5 H; O! p' S# g/ L |
- N9 ?, ]' s% {S7 S6 S5 S4 S3 S2 S1 S0
" G1 V9 O; D# z) y4 A' W# T! ]! ^0 K5 c0 E t
0 0 0 1 1 0 0 0 二进制:00011000% [8 Y7 y, s% L& {! L0 F+ h
1 b/ O( _& H7 L1 Y/ s1 O% ~( g3 Z
工程在 LineSensor_ReadMask() 里把它们收起来:
6 n" O% O7 g6 u! Z8 p. G1 q$ [5 n$ R2 r1 r5 `) F$ M" T
if ((GPIO_ReadInputDataBit(port, pin) != Bit_RESET) == LINE_SENSOR_BLACK_IS_HIGH)+ x; {. \& r# L, J4 G
6 k1 B- J ~3 M% m- `& s
mask |= (uint8_t)(1u = 10)( u# R/ G" _6 J% |7 Y
4 f% I* O+ A. \ K+ l" ~control_divider = 0;
5 ?5 W5 {4 V9 ^5 @: p1 x! _! F# L
1 U4 ~6 v; M3 f8 o' ?LineSensor_Update();
( Y* E- o4 {" O! n
# }! |2 q# @: q+ I) k( E% sMotor_Update();: e9 E! R2 t( L
7 u8 z3 Q) B& X: G- @. R, BLineSensor_Update() 只是读取 8 路 GPIO、算一个很小的固定差速;Motor_Update() 只是写方向脚和 TIM2 的比较值。这些动作短、时间稳定,放进 10ms 中断能保证控制周期不被主循环里多一条调试代码打乱。3 V* F$ ^# g0 h5 J- h+ S
4 E' Q( d! K; h& t% g+ D
Motor_Update() 写的是 Motor_SetTargetPercent() 存下来的两个目标值。这个接口就是前面 PWM 那篇里单轮版本的两轮版:' l# P3 G. x. ?) R
+ B2 D: y( h* C" K5 d9 Cvoid Motor_SetTargetPercent(int16_t left, int16_t right)3 q, g! J4 z* H4 h- z2 q0 I/ N
' D; H# x1 C( D3 A% H0 r5 c& D2 cleft_target = Motor_LimitPercent(left);
# S" ]+ J0 ^8 \& b/ ~0 b! I( I9 L& V! d$ f6 }
right_target = Motor_LimitPercent(right);
8 b F! X0 [$ {6 C
0 r: n+ H9 U5 t% O: O% PMotor_LimitPercent() 做的是同一件事:把越界的数夹回 -100 到 100。单轮调用现在只是一个薄壳:
, G; y8 J# b$ g# h6 M j3 a! A) A0 q/ c5 s& _+ {7 X* @
void Motor_SetPercent(int16_t percent)
) a- ?6 s9 g N7 H0 i8 p
" {- J: @+ n' J# VMotor_SetTargetPercent(percent, percent);0 ~, l1 V& Q- M& f4 o2 J+ v: y
k) t% m* K0 z' ?# Z- }OLED 刷屏仍然放在 main(),每 100ms 才做一次:* i% n) |+ z2 C. U
2 s6 q/ k1 y4 X Q' W& b
if (Timer_Expired(&report_time, 100))
8 n! n6 E7 M5 ^& W4 H I
/ n* P9 b* {9 S# L0 sOLED_ShowNum(0, 0, LineSensor_GetMask(), 3, OLED_6X8);
5 S6 }$ ~) i+ @" O/ g$ x8 P9 v, z# V# s/ I& k# D4 j: _4 z
把一整屏 OLED 数据塞进 10ms 中断,刚刚获得的稳定节拍又会被自己砸掉。控制任务和调试任务都需要时间,但不该抢同一段时间。
. Q! P& S8 ^/ C
, Z, k6 {+ W6 c7 L* ~" Q从“哪边压线”到固定差速
' t, l; {1 U( i' b
5 ~! G- R" k5 J8 V八路传感器不是八句独立的 if。代码给每个位置一份权重:
) T) |% v7 m2 C9 o; C! L- E* u& V: }- B5 M W7 l q, I) a6 F* Q
static const int8_t weights[8] = {-7, -5, -3, -1, 1, 3, 5, 7};1 z) a" g) k( I; D& |
$ [7 M8 F5 d ]黑线被 S0、S1 看到,平均位置偏左;被 S6、S7 看到,平均位置偏右;中间的 S3/S4 命中,位置接近 0。这里算出来的 position 只是“线偏在哪一边”的离散描述,不是任何神秘控制量。 @) Z* }# t) Y2 @4 ]
4 ^) j% \/ h( O! y$ G+ g
left = 30, right = 300 D O3 p( E4 T A* v" z; a2 |
- G! }3 s% W" l5 X$ D) Y7 p
线偏左,左轮慢、右轮快,小车就往左收回来:# [, E- V0 |1 ~8 L. x$ O1 A0 R( b8 Q1 r
$ S$ G. [8 k$ B. M: t轻微左偏:left = 24, right = 36
9 ^+ x) B: @' M0 X' }6 r3 q
; y0 r$ v7 z2 V7 |( p" V& s' I明显左偏:left = 18, right = 42
9 n' P! {3 J( L- T V6 h7 D8 [
$ k8 ~% P1 _( \, l严重左偏:left = 10, right = 50
! t3 ]9 T0 u, D& I* e* ~: Z2 c, h Z5 v2 A
线偏右时反过来。工程把这些差速写成 6、12、20 三档,不会假装它们对每个底盘都正确:
4 I7 I6 f! V! v2 x6 d' X3 B9 H6 \, Q% G. @; Z2 i
if (position == 0)% N. C6 u" \8 q4 F2 W
' c/ O8 ^. |+ M' r+ o4 ?7 Zcorrection = 0;
' j2 _7 Z4 b& \7 D- h0 K
- B+ T' Y, D9 ^4 B: \else if ((position = 5))0 O4 G2 b% t# }# r, |
/ p( L* y1 S& m% tcorrection = 20;
4 C2 ?2 b5 [& T3 |' ^! Y# T/ n# U6 ?" y2 j% S. L( r
else if ((position = 2))& q4 J1 d y$ E- S
4 b5 a. t8 ~6 \& Q- J* V, T
correction = 12;7 L; l/ `5 j9 @/ o7 K0 x! S
% t/ r5 N# ~& J0 u, H7 L% Gelse if (position != 0)& f/ c8 T# B2 y/ h5 J5 p& y
. p/ Q* h- ]. v" ~+ H# Wcorrection = 6;
3 K8 K* ?' j2 P3 M7 i0 v/ y& K# Y! ?- v1 C" _) E
LINE_BASE_SPEED 默认是 30。某些电机带着车身低于 50% 就压不过静摩擦,30/10/50 在它们身上不会换来漂亮的渐变转向,只会换来停、抖、突然窜出去。那不是 8 路循迹逻辑错了,是电机、减速箱、供电、地面和占空比门槛没有一起考虑。先架空观察方向,再从实际能稳定起转的最低速度往上调。
/ ^$ T. g4 e D* A
! l! E2 n3 x3 E两个特殊画面,必须单独写
$ d# O" S3 _' O7 W* R( |
1 b/ d$ q$ W& C八个模块同时看到黑线,工程默认停车:' Z9 v( ` |- w/ Y0 }2 V
2 Z( Q% N4 j8 b+ ~" J, E- iif (sensor_mask == 0xFF)
6 ^5 S; O* H7 N; q# _6 s5 Q6 ]3 A2 y+ u) N$ ]
Motor_SetTargetPercent(0, 0);# Z+ {& Q; y2 @/ F* M& G
5 ?* G' m9 l. o3 R) p
它可能意味着终点横线、加宽的标记、传感器贴得太低,也可能只是一张整块黑纸。默认将它作为最保守的安全行为;赛道把全黑定义为“掉头”或“停止线”时,再将这个状态映射到相应动作。5 |. ?) B1 i9 i" D% }
6 j' v6 y: O' a# t八个模块都没看到黑线时,不让车直接停住。它保留最后一次偏转方向,低速往那边找:
) Y. f( i% Z' S* g+ Z0 Z7 {4 c) X( V* v1 d" R6 a4 f
if (sensor_mask == 0x00)8 S( ~# Z4 U! J0 _: w( H x
# V* v; u- A! m! [if (last_turn < 0)- O0 z4 A0 O5 X8 g
4 O9 y- D1 k/ v% d# X; y9 XMotor_SetTargetPercent(10, 35);
' x# N* t9 ^( d; h5 v1 v) `- b% Q1 ?1 n' U' m9 `
Motor_SetTargetPercent(35, 10);/ m: s6 ?- ]% [" @
& }& U' W7 U R- D8 I& x1 O6 G
最后一次线偏左,就继续往左寻找;最后一次偏右,就继续往右寻找。这个策略不保证每一种赛道都能找回线,它只是比“丢线以后随机直走”更有依据。若你只用 2 路传感器,很容易在一个急弯处直接把线丢掉;8 路排在车头前方,能给这段寻找动作多留一些位置线索。6 |7 T8 \# M! B, H
' k$ U R; U1 ?! Z
先在 OLED 里确认,再放到地上
+ B5 |5 p( {! v: w3 R; `: r6 @/ S9 ~( ]
主循环每 100ms 更新调试信息。OLED 三行依次显示:
, G9 H) N6 M" w5 H0 E4 d" b; g* S" j
第一行:8 路掩码的十进制值& K3 H9 p) L* ?9 m j
" S( B! C$ H: V. ~
第二行:left 目标速度# c9 ?, ]3 j! p6 `* R
. O% u/ H7 K( T" h
第三行:right 目标速度7 E& y. ^% c3 X
9 A0 ^1 a+ g6 ?
先把一小段黑线从 S0 缓慢扫到 S7,看掩码和左右目标速度怎样变化。再把整排放到白底,确认它进入上一次转向的找线逻辑;最后用整块黑卡纸盖住,确认两轮目标都变成 0。
/ S7 a6 Q* f n7 ]# m3 D0 S- R6 R5 Q: X* p3 |
确认这些之后才接电机。先架空,让左/右轮执行的方向与画面中“线偏左就向左修正”一致;若实际转向反了,通常是电机安装方向、驱动板方向脚或左右电机定义和代码约定相反。先统一这些基础方向,不要把 8 路权重全部颠倒来掩盖一根电机线接反的问题。
6 u# l- V5 k2 f6 ^) N
0 P" v4 y6 B: S; T2 W7 M/ c! ]放到地面后,环境光一变、黑线褪色、白底反光、模块高度不齐、两个同型号模块电位器位置不一样,都可能让 DO 在边缘附近来回跳。重新逐个调电位器,把黑白切换点留在稳定范围中间;真需要知道每个模块离阈值还有多远,就进入 AO 模拟输出篇。! E \& A. }# \8 u6 Z: h4 X/ C
7 m( m# O8 @3 @5 [( ^/ ^! B现在的转向是固定三档差速:看到线在左边就给左慢右快,看到线在右边就反过来。急弯、不同速度、不同电机启动门槛和不同赛道,都会让同一张表出现过转或转不过去。* s- c& h, _/ i$ C1 O
p, |, q2 P( @$ C) g; g. O- `9 \但它有一个好处:每一个动作都能从 mask、position、左右目标速度直接追出来。你把黑线挪到 S0,OLED 显示更大的左偏转向;把黑线挪回中间,两个目标又回到同一速度。传感器、差速和轮子之间的因果链没有被参数遮住。- D: Y3 z/ E+ E' E0 _# }2 d8 Z
3 l$ i; J7 s: S: m2 m' yDO 模式给小车的是一排已经越过阈值的决定。把这些决定接到可靠的固定节拍和低速差速上,小车就能沿线走;需要观察比较器作出决定前的电压时,再使用 AO。; Q$ l# ]6 K5 Y5 q# f
3 W9 W' Y" x/ Y8 b4 P' r8 ]/ }
——7 Z* w4 T8 l/ |) f' `9 |2 L
本文转载自 ★博客园 首页,版权归原作者所有;如涉侵权请回帖告知,我们会立即删除。 |