“模拟调试”这个标题似乎有点神秘。嵌入式固件开发人员读完后可能会产生认知失调;但相信我,稍后它就会有意义。标题暗示的是处理
微控制器中处理的信号的任务。许多涉及小型 MCU 的任务都与处理来自麦克风、水听器和压力
传感器等传感器的原始信号有关。其中一些信号需要清理然后进行处理。
该处理可以使用多种数字信号处理(DSP)固件技术,例如FIR和IIR
滤波器、混频器和FFT。随着信号流经微处理器,我们希望通过调试验证的数据可能会很广泛。例如,信号通过滤波器后是什么样子,或者当信号通过相关器时,相关器的输出是什么。这就是模拟调试的用武之地。它允许您实时观察信号。
较小的微控制器可能缺乏较大处理器所拥有的一些强大的调试工具,例如 BDM、J-Tag 和 SWD。较小的 MCU 也可以作为基础金属运行,而不使用操作系统,这意味着操作系统中可用的任何调试工具都会丢失。工具的缺乏和实时信号处理的复杂性可能会导致代码调试出现问题。然而,调试需要深入了解微处理器内部数据发生的情况,并且在处理流模拟信号时,您可能希望查看这些信号在模拟域中的实际情况。
通常,在调试固件时,工程师会使用微控制器上的串行端口(如果存在)打印出正在执行的代码的变量值或指示符。这里有很多问题。首先,在小型 MCU 中,由于内存不足,可能没有足够的空间用于打印例程。其次,速度可能是一个问题。在 DSP 类型的处理中,我们通常会一个接一个样本地对传入信号进行实时处理,并且我们无法停下来处理相当长的打印调用。第三,打印例程通常会使用中断,这可能会导致实时系统出现问题。,将数据转储到串行端口不会为您提供正在处理的数据的直接模拟视图。
例如,假设您使用模数转换器 (ADC) 从传感器接收信号。您可以将示波器挂在传感器的输出端,并以模拟视图查看信号和噪声。但是,如果您通过串行端口查看相同的信号,则在 MCU 读取 ADC 并将其发送到该串行端口后,您会看到一堆数字。现在,您可以将这些数字放入
电子表格中并绘制图表,或者设置另一台带有数字和模拟转换器以及显示器的设备以再次查看数据。但它似乎有点缓慢和乏味,而且肯定不是实时的。
现在,如果串行端口不可用或不适合调试,工程师可以使用连接到 MCU 的 LED,该 LED 可以根据正在调试的程序中的各种条件打开或关闭。示波器可以连接到 LED 或可用的 I/O 线,以通过切换固件中的 LED 或 I/O 线来查看状态或测量状态更改之间的时序。它工作得很好,但不符合获得信号模拟视图的想法,因为信号正在由滤波器、相关器、切片器和混频器的各个阶段进行处理。
使用 DAC 进行模拟调试
有一个连接示波器探头的地方,我们可以在其中快速将处理过的样本转储到固件中。那么,我们可以用什么呢?个想法是将数模转换器 (DAC) 连接到 MCU,或者更好的是使用可用作 MCU 上的外设的转换器。
为了尝试这项技术,我将 Analog Devices 的AD7801(一个 8 位 DAC)连接到我正在开发的Arduino Nano设计中。Nano 的是 Microchip ATmega328,它没有板载 DAC。AD7801 使用 8 条数据线的并行输入,这些数据线由另一条线计时,写入速度非常快。值得注意的是,我们可以使用此设置查看 8 位数据,但 10 位、12 位或其他大小可以与其他 DAC 一起使用,或者可以缩放以适合 8 位 DAC。我将 8 条数据线连接到 Arduino 上的 DAC 端口,并将 WR 线连接到 Arduino 的 D13,如图1所示。
裸机系统上模拟调试的关键选项
图 1 DAC 通过 8 条数据线连接到 Arduino。
现在,要将数据发送到 DAC,只需要 3 行 Arduino IDE C 代码:
PORTD=数据;// 将数据字节放在 D0 到 D7 上
PORTB = PORTB & B11011111; //将D13拉低以将数据锁存到AD7801中
端口 B = 端口 B | B00100000;// 将D13拉高
在 16 MHz Arduino 上,此代码需要大约 5 个周期或大约 312 ns,DAC 的稳定时间为 1.2 us。因此,您可以看到这种数据显示方法可以相对快速地完成,无需中断,也无需太多代码。可以将该代码插入固件的适当位置以查看感兴趣的数据。将这 3 行代码放入宏或函数中可能会更简洁。如果您为此创建一个函数,则应该使用“always_inline”编译指令来编译它,以确保它运行速度快。
现在连接了 DAC,让我们看一些调试示例。请看图 2。
裸机系统上模拟调试的关键选项
图 2示波器快照显示了在启用 DAC 的设置中模拟调试的工作原理。
这是传入传感器信号的示波器快照 - 为了清晰起见,删除了刻度线。底部迹线(粉色/紫色)是进入 ATmega328 上 ADC 引脚时的原始信号。您可以在这条线上看到明显的噪音。上面的迹线(黄色)是在 MCU 固件中进行一些滤波和其他处理后的相同信号。
此流程中插入了 DAC 写入调试代码,因此 DAC 中的采样时序与 ADC 相同。如果需要,您还可以对 MCU 中的信号进行抽取。现在忽略信号中的“尖峰”,我们看到处理已经消除了大部分噪声。我们现在有了可以评估的清晰信号。应该注意的是,DAC 输出是连续的信号流,而不仅仅是一些短内存缓冲捕获。
但“尖峰”是什么?这些是我特意放入代码中的一些调试功能,以查看处理的进行情况。您看到的信号实际上是被信号介质破坏的专有数字信号。该代码的任务是通过以下方式读取数字数据包:
发现前导码“数据包开始”符号序列
跟踪样本时间,以便我们可以在适当的时间切片样本
继续收集样本直到数据包结.
这是已添加注释的已处理信号的视图。我在代码中所做的是将信号从值 50 缩放到值 200。这允许在 256 个可用值中留出一些空间,以便在信号上方和下方添加“尖峰”。我们首先看到的是标记为“检测到前导码”的“尖峰”。它是在代码验证已找到前导码 (B00000011) 时创建的,并且可以使用以下 Arduino IDE 代码轻松生成:
端口 = 255; // 将 255 放在 D0 到 D7 上
PORTB = PORTB & B11011111; //将D13拉低以将数据锁存到AD7801中
端口 B = 端口 B | B00100000;// 将D13拉高
它在示波器迹线上创建一个 312 ns 宽的标记,其幅度等于 DAC 的电压。
信号迹线内向上和向下的“尖峰”是指示代码确定符号边界的位置的标记。这对于在正确的时间对符号进行切片非常重要,并且当出现长时间的 0 或 1 时,这一点变得至关重要。这是因为没有发现从 0 到 1 或 1 到 0 的转换。在示波器上查看这些“尖峰”非常有用,因为它使我们能够验证实际时间并验证没有丢失。这些符号边界“尖峰”是通过使用以下 Arduino IDE 代码向 DAC 发送 127 来创建的,该代码被插入到符号计时代码中的适当位置:
端口 = 127; // 将 127 放在 D0 到 D7 上
PORTB = PORTB & B11011111; //将D13拉低以将数据锁存到AD7801中
端口 B = 端口 B | B00100000;// 将D13拉高
通过使用以下代码向 DAC 发送 0,符号转换被标记为“尖峰”,该代码被插入到监视从 0 到 1 或 1 到 0 的符号转换的代码中:
端口=0;// 将 0 放在 D0 到 D7 上
PORTB = PORTB & B11011111; //将D13拉低以将数据锁存到AD7801中
端口 B = 端口 B | B00100000;// 将D13拉高
您可以看到,使用 DAC 查看覆盖在实际处理跟踪上的调试信息可以极大地帮助调试代码的各个部分。它比使用 LED、I/O 线和示波器强大许多倍。它也比串行端口发送数据作为定时信息更有用。
眼尖的人可能已经注意到,在图 3 的右边缘,探头衰减不是 x1 或 x10,而是 x53.5。这是一个可以在许多较新的示波器上完成的技巧,有时称为自定义衰减设置。它设置为 53.5 的原因是它允许使用示波器的光标直接读取 DAC 的 8 位输入值。也就是说,如果我将光标向上滑动到前导码检测“尖峰”的顶部,则示波器光标读数为 255,如果我将光标移动到符号边界“尖峰”的末尾,则示波器光标读数为 127。
使用 8 位 DAC 时,此设置的公式为 255/MaxVolts,即输入二进制输入(本例中为 255)时 DAC 的输出电压。因此,对于 5V 电源轨,自定义设置为 51.0——我的电源轨只有 4.77V,所以我的数字是 53.5。使用 10:1 探头时,在将其输入示波器时,您可能需要将该数字乘以 10。
这非常方便,因为您可以直接读取 DAC 设置的数字;换句话说,内部变量在调用 DAC 时使用的值。这点考虑一下吧。从本质上讲,您可以通过这种方式“实时”读取变量,几乎与 print 语句一样好,但速度更快且非侵入性。请注意,示波器垂直刻度的噪声和分辨率会降低精度,因此您可能只能得到实际值的 ±1 或 2 个计数,但仍然相当不错。
除了传输信号之外,使用这种技术,8 位 DAC 还可以同时表示 8 个二进制标志的状态,或程序中 8 位变量的当前值。换句话说,使用 8 位 DAC 为我们提供的信息是监控单个 I/O 线所提供的信息的 8 倍。
使用 PWM 进行模拟调试
现在,如果您没有 DAC 可以使用怎么办?您可以使用微控制器上的脉宽调制器 (PWM) 外设来执行类似的操作。许多小型 MCU 都有 PWM,即使有,通常也有多个 — 通常是 6 个。 PWM 和 DAC 之间的区别之一是 PWM 输出需要使用低通滤波器进行滤波以转换输出到一个电压水平。因此,当您将信号样本发送到 PWM 时,电压电平会重新创建可以在示波器上显示的信号,就像使用 DAC 所做的那样。可以使用简单的 RC 滤波器来执行滤波。
但这里有一些注意事项;低通滤波器意味着只能显示低频内容的信号,响应速度较慢。因此,您应该将 PWM 的频率初始化为可用的频率。在 16 MHz ATmega328 上,PWM 的频率可以设置为 31 kHz 左右,因此低通信号可以设计为大约 3-4 kHz 的频率内容。
初始化后,使用 PWM 的 Arduino IDE 代码甚至比 DAC 代码更简单。将 8 位值写入 PWM 的代码非常简单:
AnalogWrite(PinNumber, 数据)
这里“data”是8位采样值,“PinNumber”是PWM输出的引脚号。
尽管 PWM 可能不那么准确或无法显示更高频率的信号,但它具有一个有趣的功能。一些 MCU 具有多达 6 个 PWM,这意味着多达 6 个输出可以传送实时数据。您可以使用 4 迹线示波器同时显示 4 个变量,留下 2 个备用 PWM 输出。此外,通过 2 个输出(PWM 或 DAC),您可以提供 DSP 信号处理中常用的 I & Q 数据,从而允许您探索负频率。需要注意的是,就像 DAC 代码一样,PWM 代码不需要中断。
其他调试工具
可用于处理 DAC 或 PWM 传递的信号的另一个强大工具是频谱。图 4中的范围屏幕截图显示了一个示例。上面的迹线显示了微控制器中生成的波形。该信号实际上是两个频率(f1 = 165 Hz 和 f2 = 135 Hz)逐个样本地混合或相乘,然后在生成时发送到 DAC。在频率混合中,结果是频率之和与频率之差的频率。原始生成频率被混合操作抑制,如示波器迹线下半部分的 FFT 所示。大多数示波器,甚至是业余爱好者级别的示波器,都提供 FFT 作为数学运算之一。
如果您的系统没有 DAC 或 PWM,您仍然可以使用一些东西来获取有关正在运行的固件中的信号的一些信息。例如,您可以编写代码来对 PWM 信号进行位处理。尽管它对于低频信号或缓慢变化的变量很可能有用。
希望模拟调试的想法现在更清晰了。从固件流式传输数据并将其显示在示波器上的主要概念可以是一个强大的工具,可以加快信号处理固件调试的速度。如果可行,选择带有 DAC 外设的 MCU 或在您的个原型 PCB 中集成 DAC 可能会很有用。它始终可以在以后删除或在物料清单 (BOM) 中设为 NO-POP。