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