在示波器上画任何东西
组件和用品
| × | 50 | ||||
| × | 1 |
应用和在线服务
|
关于这个项目
R-2R DAC
数模转换器的最简单实现之一可以通过设计 R-2R 梯形 DAC 来实现。这种类型的 DAC 只需要电阻即可工作,使其成为非常适合初学者的 DAC。
这种类型的 DAC 的基本原理是您只需要两个电阻值。 R 和 2R。然后您可以如下图所示进行设置。
R 可以是任何值,只要原理图中每次出现的 R 都是相同的值即可。
例如,如果我们选择 R =1k,那么 2R 将是数量的两倍。在这种情况下,2R =2k。所以你可以只使用 1k 和 2k 电阻。
如果您使用 R=3.3K,则 2R =6.6k,依此类推。如果您只想对所有电阻器使用相同值的电阻器,那么要获得 2R,您只需将两个电阻器串联,这会增加您的元件数量。
位数取决于您伸出的 2R 分支的数量。在这个项目中,我们将为 Arduino Uno 或 Nano 使用一个 8 位 R-2R 和一个 6 位 R-2R。如果您正在使用另一个您知道支持至少 8 位全端口操作的微控制器,例如 STM32“蓝色药丸”和“黑色药丸”,那么您可以只使用两个 8 位 DAC。
关于 DAC IC 的旁注:
该项目专注于使用 R-2R DAC,但您可以随意使用 DAC IC 来实现类似的功能。如果您打算使用 DAC IC,我建议使用支持快速通信协议(例如 SPI)的 IC,因为 I2C 太慢而无法绘制详细的图像。我不会在这篇文章中介绍如何使用 DAC IC,但可能会在以后的文章中介绍。 R-2R 仍然是我的首选方法,因为它可以输出更详细的图像。
端口操作
我们将使用微控制器的并行端口操作。这样做的原因是因为它允许更快的输出速度并保持代码简单。您可以在此链接中阅读有关 Arduino 端口寄存器的更多详细信息。
Arduino Uno 和 Nano 具有 ATmega328p 微控制器。该微控制器与 Atmega168 共享相同的引脚映射:
端口引脚和位由它们的标签 PXN 表示,其中 X 是端口字母,N 是位数。
示例:PB1 用于 PORT B 位 1。同理 PC5 用于 PORT C 位 5。
如果我们检查引脚,我们会看到微控制器具有端口 B、C 和 D。通过返回 Arduino 参考,我们可以看到哪些端口可以写入或读取,或两者兼而有之。在我们的应用中,我们只关心写。
文档说明 PORTD 是读/写的,它被映射到数字引脚 0 到 7。这为我们提供了完整的 8 位端口来写入。
接下来,我们将 PORTB 如上所述映射到数字引脚 8-13。附加声明表示无法访问将完成完整 8 位的 6&7。此端口只能用于写入 6 位。
PORTC 也是如此。它被映射到模拟引脚 0-5,但也保留了两个引脚,让我们只能写入 6 位。
由于这些限制,我们必须将 8 位 DAC 用于 PORTD,将 6 位 DAC 用于 PORTC 或 PORTB。
我们将使用 PORTD 和 PORTB,现在我们可以开始将 DAC 连接到引脚。
如果您需要有关如何组装的视觉帮助,这就是我在面包板上组装 R-2R 的方式。请注意,我使用了两个 R-2R,因为我是在我的 STM32“黑色药丸”上测试它的。我把两根跳线伸出来,这样我就可以把我的示波器探头和侧面的地线连接起来。我使用了所有 10k 电阻器,因此对于我的 2R,我串联了两个 10k 电阻器。
这是使用 Tinkercad 的更清洁的面包板设置。我用 X 和 Y 标记了输出,因为它们将是我们的示波器探头所连接的。如果您需要有关设置的更多指导,我将提供示意图,显示 R-2R 梯形图由所有电阻值相同的梯形图和另一个电阻值相同的梯形图组成。
一定要检查你的理由!每次我组装这个设置时,我往往会忘记。还要仔细检查你没有留下任何没有连接的间隙。
ArduinoCode
现在是时候绘制 Arduino 草图了。
首先,我们需要将端口设置为输出。来自关于端口操作的 Arduino 参考。我们可以看到如何设置端口:
基本上,该位的值为 1 会将其设置为输出,值为 0 会将其设置为输入。对于 PORTD,我们可以直接写入,因为所有位都可以访问。对于 PORTB,请记住位 6 和 7 不可用,因为它们映射到晶振。最好使用值为 0 的按位 OR 以避免修改不会设置的位。
在我们的设置循环中,这将是代码:
附上代码供下载,不用担心手动输入。
配置下方的 for 循环只是重新映射 6 位端口的值。这些值将来自具有 8 位范围值的列表。要重新映射到 6 位,我们的最大值将是 2^6-1,即 63。我们从 0 到 2^8-1 的范围进行映射,即 255。我们在 Arduino 代码中重新映射的原因是因为我们的手绘图仪工具将以 8 位输出 x 和 y 点。我们可以在任一代码中更改它,但最好在使用的微控制器上更改。
基本上我们需要一些延迟,具体取决于示波器。我发现我的 1 很好。其实完全省略延迟也是可以的。对于点,它将由我们的手动绘图仪工具自动生成,因此您只需复制和粘贴数字即可,但基本上这是要绘制的点数,以便迭代变量可以通过它们。无论如何,这是完整的代码:
x_points 和 y_points 数组在您每次绘制时都会有所不同,并且会通过我们的工具自动生成。
关于快速 PWM 的旁注:
如果您是微控制器的高级用户,您可能会意识到如果关心速度,那么 也许可以通过快速 PWM 和修改时钟寄存器来实现。然后将 PWM 输出馈送到低通滤波器以获得模拟电压,从而减少引脚数。我已经尝试了这两种方法,这仍然可以更好地支持具有更多细节的图像。至少在 Arduino Uno 中
绘图工具
我使用 tkinter 在 Python 3 中制作了这个工具。如果你没有安装 tkinter,请用 pip 安装。
pip install tk
代码将被附加,但基本上它会记录您单击光标的窗口上的坐标。不要尝试调整窗口大小,因为它会改变分辨率。如果您想更精确,请使用 Windows 中的放大镜工具。该窗口基于 255x255,因为这些将是我们位范围的最大值。当然,如果您使用的是 Arduino UNO,其中一个维度将映射到 6 位。
要调用该工具,只需使用此格式
python drawlog.py> arduino_list.txt
这将创建一个名为 arduino_list.txt 的 .txt 文件,其中包含为您生成的 x_points、y_points 和 NUM_POINTS 的 Arduino 列表,准备复制并粘贴到您的代码中。
例如,这是打开 .txt 文件时的样子。你只需要替换代码中的那些行。
这是打开后的样子。从那里你可以画任何东西。
我画了下图来测试一下。代码在窗口关闭时生成。
将代码上传到 Arduino 后,这就是 X-Y 模式下的样子。
重要提示:
如果您遇到有关内存的编译错误,这是由于 Arduino UNO 中可用的内存不足。这意味着您应该绘制一个不太详细的图像。我认为最高点数约为 400,但可能更多。如果您使用 STM32“蓝色药丸”或“黑色药丸”,则此数字超过 4, 000。
示波器 X-Y 模式
示波器必须设置为 x-y 模式。 8 位 R-2R DAC 输出 (PORTD) 将连接到通道 1,6 位 R-2R DAC (PORTB) 将连接到通道 2。从那里您可以使用旋钮进行调整,直到看到图像。
而已!如果您有任何问题随时问。此外,如果您想在蓝色药丸或黑色药丸上试用,请访问我的 GitHub 页面链接,其中包含一些示例代码
现在这是我绘制的一些图像。
图> 图> 图>
随意分享你的!!!
编辑:
内存管理的重要细节
正如蒂姆在评论中提到的
- 在您的 Arduino 代码中,点 t 上的迭代器是“字节”类型,因此最多仅支持 255 个点。为什么不把它变成一个“int”?
- 您的绘图仪代码为 x_points 和 y_points 输出“const unsigned long”——这在您稍后修改 y 点时不起作用。另外,为什么不直接使用“byte”来节省内存?
请注意您使用的硬件限制。对于 Arduino Uno,最好使用字节,但是如果您使用的是 STM32 甚至 ESP32,您将有更多的点可以绘制。感谢大家的反馈。
Timster :
我找到了一种方法来大量增加 UNO 支持的点数 - 将它们移动到程序存储空间中。您可以使用 PROGMEM
修饰符。这样你可以存储大约 15,000 个积分!
所以像这样声明数组:
const int NUM_POINTS =...
const byte x_points[NUM_POINTS] PROGMEM ={...
const byte y_points[NUM_POINTS] PROGMEM ={...
不要修改 setup 函数中的 y_points(即删除 for
在那里循环)。而是在循环函数中进行位移。
您还需要一个特殊的命令来从程序存储器中读取数据(pgm_read_byte_near)。所以void loop()
中的for循环 看起来像:
for (int t=0; t {
PORTD =pgm_read_byte_near(x_points + t);
PORTB =pgm_read_byte_near( y_points + t)>> 2;
delayMicroseconds(FIGURE_DELAY);
}
然后可以存储一张高分辨率图像或多个较小的图像并在它们之间循环😃
代码
- Arduino 代码
- 绘图工具
Arduino 代码Arduino
在 Arduino Uno 或 Nano 板中使用的 Arduino 草图const byte Figure_DELAY =1; // 在美国跟踪延迟。调整如果需要const int NUM_POINTS =87;//图中XY点的数量// x和y坐标绘制字节x_points[NUM_POINTS] ={106,106,105,104,103,102,101,100,99,98,97,96,935,924,91 ,90,90,89,88,87,87,87,86,86,86,86,87,89,90,91,93,95,97,99,101,102,102,104,104,105,105,106,106,106,106,106,106,108,109,110,112,113,115,117,119,121,122,123,123,124,124,124,124,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,110,109,109,109,108,107,107};字节y_points [NUM_POINTS] ={78,80 ,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,98,99,100,101,103,105,106,108,110,112,113,113,114,115,115,115,115,115,115,114,112,112,110,109,107,106,106,108,110,112,114,114,115,116,116,117,117,117,117,117,116,115,113,112,110,108,106,104,103,102,100,99,98,97,96,95,94,93 ,92,91,90,89,87,86,84,82,81,80,78};void setup(){ // 初始化端口 D 和 B 用于写入 DDRD =B11111111; DDRB =B00111111;字节 t; for (t=0; t绘图工具Python
将点导出到绘图的绘图工具将 tkinter 导入为 tkX =[]Y =[]lastx, lasty =0, 0# xy 和 addLine 仅用于图形目的# on_move_press 是将一个记录到列表中的工具,并且修复 Ydef xy(event):# 鼠标点击时记录坐标 global lastx, lasty lastx, lasty =event.x, event.ydef addLine(event):# 从旧点到新点画线 global lastx, lasty canvas.create_line ((lastx, lasty, event.x, event.y)) # 这使得绘图的新起点 lastx, lasty =event.x, event.y# 在 listdef on_move_press( event) 上记录点击坐标: curX, curY =(event.x, event.y) curY=255-curY # 因为 tkinter 使用不同的坐标 X.append(str(curX)) Y.append(str(curY))# windowroot 所需的设置 =tk.Tk( )root.geometry("255x255")root.columnconfigure(0, weight=1)root.rowconfigure(0, weight=1)canvas =tk.Canvas(root)canvas.grid(column=0, row=0,sticky =(tk.N, tk.W, tk.E, tk.S))# 绑定左键并拖动到函数并启动 loopcanvas.bind("", xy)canvas.bind(" ", addLine)root.bind(" ",on_move_press)root.mainloop()# 删除每第二个条目以减少点数和在范围内增加跟踪刷新 i (1,int(len(X)/2)):X.pop(i) Y.pop(i)print("const int NUM_POINTS =%s;" % str(len(X) ))print("const unsigned long x_points[NUM_POINTS] ={%s};" % ','.join(X))print("const unsigned long y_points[NUM_POINTS] ={%s};" %',' .join(Y))#call python drawlog.py> arduino_list.txt
示意图
制造工艺