Arduino 家用蒸馏设备





5.00/5 (13投票s)
使用 Arduino 蒸馏洗手液和其他社交润滑剂
- 下载 Still_-_Mega_-_Platformio_20211220_1022.zip - 2 MB
- 下载 Still_Mega_20211218_0840.zip - 16.1 KB
- 下载 Scale_Calibration_20211006_0906.zip - 2.1 KB
- 观看此项目的视频
更新:2021/12/18 - 加热控制改进了加热控制功能
引言
早在 3000 多年前,古美索不达米亚人就知道蒸馏的过程,至今它仍然是我们时代的重要组成部分。蒸馏器发酵液体,然后通过将产品的温度升高到高于酒精沸点但低于水沸点来将酒精含量与水分离,收集酒精蒸汽并将其冷凝回液体。工业界有巨大的发酵谷物与糖和酵母混合的罐子。他们精心控制温度,并收集他们装瓶和销售的产品,使用相同的程序。这就是为什么你的姑婆在新年夜仍然跳舞,以及,如果你像我一样,为什么你在多年前的办公室圣诞派对上向你老板漂亮的女儿求婚。有很多网站和 YouTube 视频可以帮助你开始建造自己的蒸馏器,所以我不会过多赘述,而是告诉你我构建的 Arduino 项目,以帮助我运行我的蒸馏器。
如何建造蒸馏器
拿一个煮锅、一个电炉、一桶冷水和一些铜管。将铜管插入锅盖的一个孔中,然后将其放入水桶中,然后收集从底部流出的液体。
蒸馏
根据维基百科上关于私酿酒的文章,“通过发酵谷物淀粉中的糖不会产生有毒剂量的甲醇”,只有当不道德的蒸馏器用甲醇掺假产品以“改善”其效力时才会出现问题。你在网上找到的大部分关于家庭蒸馏的信息会告诉你,因为发酵产生的少量毒素的沸点低于酒精,这些毒素会首先被蒸发掉,因此在运行开始时被丢弃。然而,科学研究表明,甲醇、丙酮和醛(坏东西)在家用蒸馏或商业蒸馏过程中都会以微量出现,并且无论如何都会存在。上面链接的私酿酒视频中那位老田纳西州山民已经在饮用他的蒸馏器产品 40 多年里丢弃“头颈部和前馏分”,而且他甚至不戴眼镜。以这个例子为例,我觉得可以说,私酿酒可以清洁你的汽车发动机缸体并让你喝醉,但不会让你失明,除非你用你妻子的指甲油去除剂来掺假它……你probably 不应该这样做。
Arduino
对于这个项目,我使用了 Arduino Mega 2560 微控制器。它有可能在较小的板上运行,但我不想冒险出现内存问题或控制引脚不足的风险。
这是所需组件的列表
蒸馏器
16 加仑不锈钢锅(非铝制) | $30 |
电炉 | $25 |
高/窄桶 | $8 |
10 英尺 3/8 英寸外径铜管及配件 | $30 |
总计 | $100.00 |
Arduino
Arduino 2560 微控制器 | $15 |
YZC-133 3Kg 电子秤称重传感器 | $5 |
1 TM7711 电子称重传感器 | $10 |
LCD1604 | $10 |
2 个 DS18B20 温度传感器 | $7 |
4x4 键盘 | $3 |
5KΩ 电位器 | $3 |
蜂鸣器 | $2 |
SG90 9G 伺服电机 | $3 |
5V 1 路继电器板模块 | $3 |
74HC595 移位寄存器 | $2 |
LED、各种电阻、电线、焊锡和烙铁 | $20 |
总计 | $90.00 |
建造这个蒸馏器最困难、最耗时的一部分是在不锈钢锅盖上钻两个孔。我不得不将钻头的按钮按住才能启动,然后我用手拿着钻头,每个孔总共花了 5 多个小时!(在此过程中弄坏了半打钻头)。我用锤子把盖子砸平,以便能够固定钻头,然后坐在我的书桌前看《邪恶力量》第 12 季 DVD,直到我终于穿过最小的孔,然后用更大的钻头将其扩大。完成此操作后,冷凝器(冷水桶)必须用坚固的电线加固,以确保铜管(虫形管)始终向下流动(否则,当铜管在两次蒸馏之间未正确干燥时,会出现称为“蓝虫”的腐蚀)。我说过我不会过多描述实际蒸馏器的建造部分……所以,我们将继续讨论 Arduino 部分。
菜单
这个蒸馏器项目的材料界面主要由一个 LCD1604 和一个 4x4 按钮矩阵(一些 LED 和一个蜂鸣器)组成,并由一组菜单运行。我之前在一个名为Arduino - 带有模拟电压分压器按钮控制器的锻炼次数计数器的项目中讨论过这些类型的菜单。我在这里总结一下,让您有个大概了解,但如果您对菜单系统感兴趣,应该阅读那篇文章。由于 Arduino 微控制器内存容量有限,创建过多字符串变量可能会用其自身的变量(字符串)组件覆盖您项目的软件部分,最好完全避免使用字符串,并 resort 到字符数组。或者,您可以使用 F() 宏,它“告诉编译器参数的内容应存储在闪存中(只读内存或PROGMEM),而不是 SRAM,从而为您节省宝贵的资源以供更好地使用”(techexploration.com)。但是,通过这些菜单,我更进一步,强制微控制器要求文本以显示在 LCD1604 屏幕上。这些信息(每个菜单的标题,需要时)在使用 F() 宏绘制 LCD 时发送到调用方法,LCD 被告知要在屏幕上显示什么,然后字符串被销毁并仅驻留在 LCD 屏幕的内存中,而微控制器本身将其从自己的内存中清除(除了我保留的 16x4 字符数组,以防万一我决定将反馈闪烁到 LCD 上并希望在短暂的闪烁反馈灵感之后将屏幕重置回其原来的辉煌,但我从未抽出时间来做,所以……它就那么没用)。
为了构建这个菜单,我首先在记事本文本文件中将其“绘制”出来,并生成了下面显示的文本,您可以在其中看到每个菜单项之间的关系,通过它们各自的缩进。
菜单项本身是
class classMenuItem
{
public:
classMenuItem *cParent = NULL;
classMenuItem *cFirstChild = NULL;
classMenuItem *cPrevSibling = NULL;
classMenuItem *cNextSibling = NULL;
enuMenuAction eMenuAction = mnuAction_NoAction;
byte Index;
};
然后通过setup()
调用中创建的“树状”组件中的每个节点相互连接
void Menus_init()
{
// menus
cMenus[0].cFirstChild = &cMenus[1];
cMenus[0].cNextSibling = NULL;
cMenus[0].eMenuAction = mnuAction_NoAction;
// case 1 : "Modes"
cMenus[1].cFirstChild = &cMenus[2];
cMenus[1].cNextSibling = &cMenus[30];
cMenus[1].eMenuAction = mnuAction_NoAction;
// case 2: return "warm up";
cMenus[2].cFirstChild = &cMenus[3];
cMenus[2].cNextSibling = &cMenus[5];
cMenus[2].eMenuAction = mnuAction_NoAction;
// case 3: "Warm-up select"
cMenus[3].cFirstChild = NULL;
cMenus[3].cNextSibling = &cMenus[4];
cMenus[3].eMenuAction = mnuAction_Select_WarmUp;
// case 4: "Warm-up - MaxTemp"
cMenus[4].cFirstChild = NULL;
cMenus[4].cNextSibling = NULL;
cMenus[4].eMenuAction = mnuAction_WarmUp_MaxTemp_Set;
// case 5: "head"
cMenus[5].cFirstChild = &cMenus[6];
cMenus[5].cNextSibling = &cMenus[10];
cMenus[5].eMenuAction = mnuAction_NoAction;
// case 6: "head - select"
cMenus[6].cFirstChild = NULL;
cMenus[6].cNextSibling = &cMenus[7];
cMenus[6].eMenuAction = mnuAction_Select_Head;
// case 7: "head - auto-hearts"
cMenus[7].cFirstChild = &cMenus[8];
cMenus[7].cNextSibling = NULL;
cMenus[7].eMenuAction = mnuAction_NoAction;
// case 8: "head - auto-hearts - Toggle"
cMenus[8].cFirstChild = NULL;
cMenus[8].cNextSibling = &cMenus[9];
cMenus[8].eMenuAction = mnuAction_Head_AutoHearts_Toggle;
// case 9: "head - auto-hearts - Max Volume"
cMenus[9].cFirstChild = NULL;
cMenus[9].cNextSibling = NULL;
cMenus[9].eMenuAction = mnuAction_Head_AutoHearts_MaxVolume;
// case 10: "hearts"
cMenus[10].cFirstChild = &cMenus[11];
cMenus[10].cNextSibling = &cMenus[24];
cMenus[10].eMenuAction = mnuAction_NoAction;
// case 11: "hearts - select"
cMenus[11].cFirstChild = NULL;
cMenus[11].cNextSibling = &cMenus[12];
cMenus[11].eMenuAction = mnuAction_Select_Hearts;
/////////////////// content excised for brevity //////////////////////////
// case 89 : Light Toggle
cMenus[89].cFirstChild = NULL;
cMenus[89].cNextSibling = &cMenus[90];
cMenus[89].eMenuAction = mnuAction_Light_Toggle;
// case 90 : Shine Density Set
cMenus[90].cFirstChild = NULL;
cMenus[90].cNextSibling = &cMenus[91];
cMenus[90].eMenuAction = mnuAction_ShineDensity_Set;
// case 91 : "ExitMenu"
cMenus[91].cFirstChild = NULL;
cMenus[91].cNextSibling = NULL;
cMenus[91].eMenuAction = mnuAction_ExitMenu;
// set cParent & cPrevSibling
for (int intMnuCounter = 0; intMnuCounter < bytMenus_Num; intMnuCounter ++)
{
cMenus[intMnuCounter].Index = (byte)intMnuCounter;
classMenuItem *cMnu = &cMenus[intMnuCounter];
if (cMnu->cFirstChild != NULL)
{
classMenuItem *cChild = cMnu->cFirstChild;
do
{
cChild->cParent = cMnu;
cChild = cChild->cNextSibling;
} while (cChild != NULL);
}
if (cMnu->cNextSibling != NULL)
cMnu->cNextSibling->cPrevSibling = cMnu;
}
/*
Serial.println(F("Menus_init() debug for-loop start"));
for (int intCounter = 0; intCounter < bytMenus_Num; intCounter ++)
debug_Print_Menu(intCounter);
Serial.println(F("Menus_init() debug for-loop end"));
//*/
}
一旦它们组合在一起(在上面显示的代码末尾),就使用for-next
和do-while
循环来创建指向父节点的反向链接(而不是冒着程序员错误地重复输入相同数据两次的风险)。使用指针来跟踪用户在菜单中的位置,以便 LCD1604 显示可以反映用户在菜单指针上向上/向下滚动或向前/向后滚动时期望看到的内容。
这不是最简单的菜单组装方法,但构建了几个这样的菜单后,您会变得更加舒适,并且可以轻松添加/更改项目。不过,我确实计划编写一个 C# 应用程序,该应用程序将更好地与 Arduino 开发人员进行交互,以图形化方式构建 Arduino 菜单,然后生成实现设计所需的 Arduino 代码……当我抽出时间来做的时候。
当用户做出“最终选择”时,以下方法使用switch-case
将程序流重定向到适当的方法调用。
void mnuSelect()
{
switch (cMnu_Selection->eMenuAction)
{
case mnuAction_WarmUp_MaxTemp_Set:
{
WarmUp_MaxTemp_Set();
}
break;
case mnuAction_Head_AutoHearts_Toggle:
{
bolHead_AutoHearts = !bolHead_AutoHearts;
EEPROM_Head_AutoHearts_Toggle_Write();
mnuDraw();
}
break;
case mnuAction_Head_AutoHearts_MaxVolume:
{
Head_AutoHearts_MaxVolume_Set();
}
break;
case mnuAction_Hearts_HeatControls_Toggle:
{
bolHearts_HeatControl_Toggle = !bolHearts_HeatControl_Toggle ;
EEPROM_Hearts_HeatControl_Toggle_Write();
mnuDraw();
}
break;
// excised for brevity
case mnuAction_NoAction:
{
cMnu_Current = cMnu_Selection;
cMnu_Selection = cMnu_Current->cFirstChild;
mnuDraw();
}
break;
}
}
所有菜单操作都列在一个枚举列表中。
4x4 按钮矩阵
按钮控制器是 4x4 按钮矩阵。传统上,在古代,人们使用 8 个令人恼火的引脚将他们的 4x4 按钮控制器连接到微控制器。他们通过将一半引脚设置为高电平,另一半设置为低电平,然后反过来进行操作来轮询。这种令人憎恶的做法是公共卫生的禁忌(在昏厥县的疑病症城市有一项地方法规禁止这种可怕的做法),必须加以阻止,正如我在我的文章微控制器矩阵电压分压器按钮计算器中所解释的那样。
视频中的那个人说“一个引脚统治它们!”
按钮控制器仅使用一个 Arduino 引脚的工作方式是,将合适的电阻焊接到 8 个按钮矩阵引脚上,并向其中一半提供 VCC,单个模拟引脚可用于检测输出端的电压水平。当配置正确时,每个按钮通过不同的电阻组合重定向电流,形成一个唯一的电压分压器,从而创建一个微控制器可以检测到的唯一电压信号。由于每个按钮都会产生独特的电压,因此微控制器可以识别哪个按钮被按下。无需连接 8 个引脚,只需连接 1 个。无需将某些引脚设置为输出,而其他引脚设置为输入以进行测试并反向重复过程。无需使用keypad.h库(它会花费很长时间来为您执行相同的操作)。您只需要查询您那一个单独的模拟引脚,然后使用if-else
语句来控制按钮矩阵。
如果您打算使用 4x4 按钮矩阵,我强烈建议您看看我的文章,因为旧的方法太老套了。
温度计
此项目中有两个温度计连接到 Arduino。蒸馏器只需要两个:一个用于测量发酵液(煮锅)中的温度,另一个用于监测冷凝器(冷水桶)中的温度。我使用的 DS18B20 温度传感器每个都有其唯一的工厂“烧录”索引,因此它们都可以连接到微控制器的单个引脚,您可以通过其数字顺序引用任何一个(或任意数量)中的任意一个。我的意思是,连接到您微控制器的具有最小“工厂烧录索引”的那个被称为第 0 个温度计,下一个按顺序是第 1 个,依此类推。因此,由于您不知道它们实际的“工厂烧录索引”号,一旦它们连接到您的微控制器,您就必须自己弄清楚哪个是哪个。在此项目中,这很容易,因为冷水桶的温度将始终比煮锅中发酵液的温度低。这些“参考”值(我在此处避免使用“索引”一词,但实际上我们讨论的是索引参考值)如果您添加或替换您的温度计之一,它们将发生变化,这意味着您可能需要在更改、移除或添加温度计时修改代码(如果这样做的话)。
这是我在该项目中用于在loop()
方法中引用这些温度计的代码行。
Thermometers.requestTemperatures();
fltTemperature_Wash = Thermometers.getTempCByIndex(0);
fltTemperature_Cond = Thermometers.getTempCByIndex(1);
返回的值是摄氏温度,一旦您将它们整理好,这一切就相当基础了。
秤
YZC-133 称重传感器很难组装。我同时在网上订购了该项目的所有东西,很高兴发现温度计很容易添加到项目中,然后我期望秤也能轻松即插即用,但直到为时已晚才发现事实并非如此。这方面的研究本可以帮助我提前做好准备。这些秤是机械的“类似电位器”的铝块,当它们受到外部力的扭曲时,其电阻值会发生变化。由于 Arduino(以及我所知道的所有其他微控制器)不测量电阻变化,而只测量通过其模拟引脚的电压变化,因此您需要通过这些秤运行电流以产生输出电压,该电压将随秤电阻的变化而变化。我读过的一些文章建议构建一个惠斯通电桥,然后可以用来创建 Arduino 可以测量的电压差,但在这些秤的情况下,产生的电压差约为纳伏,远低于 Arduino 的 5mV 电压分辨率。因此,最终,我不得不购买 TM7711 电子称重传感器,它们本质上是专门为这些秤设计的“电压信号放大器”。
这并非唯一的惊喜。有一个实际的“构建”需要您自己动手组装,才能“扭曲”这些铝块并将它们变成可用的称重秤。Instructables 上有一篇很好的文章,为我解释了一切。这是他们发布的一张图片,展示了我上段提到的“构建”。
当您购买这些秤时,您只得到中间带有孔的块和从中伸出的电线。所以我不得不去一家专营海事紧固件的商店购买将它们组装在一起所需的螺丝,因为我在当地家得宝找到的壁挂式演示螺丝尺寸(我在那里买了我的桶和铜管)无法轻松地将测试螺丝拧入铝块的楔形部分。当我终于拿到螺丝时,我尝试使用我找到的一些旧的、腐烂的栅栏木材,因为它看起来尺寸合适,但它散架了。然后我尝试在钻孔并拧紧之前用胶带将其固定在一起,但塑料电工胶带在木材之间产生了缓冲,导致秤不稳定,除了无法产生准确结果之外。需要新的、薄的、硬木。我没有买桃花心木或柚木之类的,只是一些不错的、不显眼的杂牌木材。这是必不可少的。
当我最终正确组装好它时,它仍然需要配置。
配置并不算太糟糕。我从某个地方(不再确定是哪里)下载了一个示例配置草图,它有点笨拙,所以我添加了更好的控件来调整值,同时逐渐接近每个秤的适当校准因子。
float fltscale_3kg_CalibrationFactor = 301925;
您可以在下载 Scale_Calibration.zip中查看该草图。
秤本身以英制单位(磅)报告其结果,因此校准过程只因我开始时使用的“已知质量”是公制单位且未能正确校准而变得复杂。一旦我弄清楚“已知质量”必须以磅为单位,过程就大大简化了。
秤的结果以磅为单位报告。然后通过以下方法将其转换为私酿酒的升
float Convert_Lbs_to_Liters(float fltPounds, bool bolReport) { // my moonshine weights 865g/L // there are 2.20462lbs/1000g // converts lbs to LMS(litres of moonshine) // // there are 2.20462 pounds in a kilo float fltGrams = fltPounds / 2.20462 * 1000; // 1L of shine weights 865g (you should weigh your own product) float fltLiters = fltGrams / (float).865; if (bolReport) { Serial.print(F("Convert:")); Serial.print(String(fltPounds)); Serial.print(F("lbs = ")); Serial.print(String(fltGrams)); Serial.print(F("grams = ")); Serial.print(String(fltLiters)); Serial.println(F("Liters of shine")); } return fltLiters; }
它将测得的重量除以 2.20462(每公斤磅数)以将磅转换为公斤。我用厨房秤称了我的产品一升,它告诉我每升重 865 克。所以上面显示的方法然后将秤上称出的质量除以 0.865,并将消毒剂的称量质量转换为其计算体积。您可以重置秤,以便无论哪个收集容器放在哪个称重秤上,您都能获得相当准确的液体(当然是消毒剂)体积读数。
我最初买了 2 个 3 公斤的秤加上一个 1 公斤的秤。但是当我将第一个 3 公斤的秤安装到蒸馏箱中时,我不够小心(更像是“测试产品”),并且在注意到秤(铝块)未远离箱体后架边缘之前就将螺丝拧紧了。将螺丝拧紧产生的力远大于秤额定的 3 公斤,并在此过程中将其损坏。所以现在,我的项目只有两个秤,而不是我原本打算使用的三个。
LED 和 74HC595
在冷凝器的两侧各有一组三个 LED。一组反映煮锅(发酵液)中液体的温度,另一组反映冷凝器中液体的温度。所有这六个 LED 都连接到一个 74HC595 移位寄存器。如果您从未用过移位寄存器,您会惊讶地发现,您仅用微控制器的 3 个数字引脚就可以控制多达 8 个数字外围设备。其工作原理是通过微控制器向移位寄存器串行发送信息(使用 3 个引脚:一个时钟、一个锁存和一条数据线),然后移位寄存器在接收到单数据线上的信息后,根据从微控制器接收到的位序列,将其 8 个输出引脚的值设置为高或低。您可以将这些移位寄存器串联起来,并用它们控制任意数量的数字输出线。
可以将通道继电器(一种电子控制开关,仅使用 Arduino 的 5V 即可切换 120V 电源的电流开关)添加到移位寄存器,但我选择为了简单起见,将六个 LED 与单个通道继电器分开,因为我用来构建此项目的 Arduino Mega 2560 微控制器有足够的引脚,而且它们不足从来不是真正的威胁。
在焊接阶段,我制作了两个独立的灯板,并且没有过多担心哪个灯连接到 74HC595 移位寄存器的哪个输出引脚,指望着我可以在它们全部连接在一起后在 IDE 草图中更改它们的地址值。每个 LED 都需要自己的电阻,它们都接地,所以除了确保它们的阳极/阴极腿方向正确之外,连接它们 all 到一起并没有什么真正的问题。
这是草图中引脚“地址”声明
const byte LED_Condenser_Blue = B00100000;
const byte LED_Condenser_Green = B00010000;
const byte LED_Condenser_Red = B00001000;
const byte LED_Wash_Red = B00000100;
const byte LED_Wash_Green = B00000010;
const byte LED_Wash_Orange = B00000001;
我将它们放在此处,但尽管设法将冷凝器灯与发酵液灯分开,它们的寻址顺序并不与其项目最终结果的物理左右外观相匹配,这无关紧要,因为变量名在引用它们时已使用。
由于 LED 旨在反映发酵液和冷凝器的温度,因此我创建了一个方法,该方法接受所有相关参数,然后相应地为指定的 LED 设置每个位值。
构建
这个项目比我预期的要困难得多。东西坏了,没有按计划进行,或者需要很多调整。运行蒸馏器本身占用了我很多时间,我不得不每隔几个小时睡一觉然后起床,更换冷凝器中的水。我计划让冷水通过饮水机(我在 eBay 上花了 120.00 美元买了一个),但后来在获取足够强大的水泵以将水从饮水机中抽出来并推到冷凝器底部时遇到了问题。当我得到了一个合适的水泵时,它太强大了(每小时 400 升),以至于饮水机(每小时冷却 1 升)可以忽略不计,而且水泵在水变冷之前就从饮水机顶部抽水。这个想法是在冷凝器底部注入冷水,然后让冷凝器在顶部(铜管加热的地方)溢出到饮水机中,但用于将溢出物(从冷凝器到饮水机)引导的管子直径为 1 1/2 英寸!事实证明,这不足以跟上水泵的速度。所以水泵尽可能地吸水,直到它在溢出水涓涓流入时吸入空气。饮水机本身的设计是,“冷却室”的进水口(通常放在上面的瓶子里的水)和出水口(饮水龙头)都在顶部,所以注入其中的温水是首先被抽出的水。整个计划都必须被取消、废弃和停止。不好。至少现在,我可以起床吃燕麦片而不用烧水了,除了这个……有点浪费。
我确实将水泵改造成了一种“水虹吸”(某种东西),我用它来转移我通过发酵果汁瓶酿造的便宜葡萄酒,而不会吞噬掉底部所有浪费的酵母。效果很好。但是,当我运行蒸馏器时,我仍然每隔几个小时虹吸一次冷凝器里的水。总的来说……一个有趣的项目。
蒸汽压力和您的冷凝器(非 Arduino 相关的蒸馏问题)
如果您确实建造了自己的蒸馏器,您可能会发现您的煮锅漏蒸汽(酒精),并且您的产品(消毒剂)产量不如预期。如果是这种情况,请查看下面的图表,该图表考虑了您的冷凝器相对于您的发酵液煮锅的位置。
将冷凝器置于煮锅上方,锅中升起的蒸汽将足以将蒸汽推入冷凝器并降低煮锅中的压力。
历史
- 2021 年 4 月 19 日:初始帖子
- 2021 年 8 月 1 日:喷嘴调整后返回模式位置
- 2021 年 10 月 6 日:修复了秤功能,以更准确地测量液体含量
- 2021 年 12 月 18 日:改进了加热控制功能