该项目不包含Arduino这个项目最初看起来应该与众不同-一个巨大的结构,包括一个带有罐和泵的基座,一个安装在其上的水族馆和一个在其上方的番茄绿洲。计划在番茄绿洲的天堂中建造一个瀑布,并在水族馆中养鱼,其主要要求是能够吃掉计划外的水族馆居民并保持玻璃清洁。主要候选人是somiki和gourami。您可能已经猜到了,我的座右铭是“懒惰是进步的动力”(以及您该怎么做,以免清洁水族馆和不给番茄浇水)。如果这个座右铭在与他的妻子协调大纲草图的阶段还没有倒塌的话,可能会竖立起来。她没有想到将这个bandura用作客厅的主要装饰的想法,甚至瀑布也没有使她相信这一点。但是一个自治系统的想法是生物学和电子学的共生,它不想飞出我的脑海,这个项目的范围缩小到一个花盆的大小-共生共生变成了水培,挽救了鱼类的生命。水培法的主要思想是使用营养液代替土壤。这允许一个数量级加速植物生长。但是,不能将根部降低到水中-它们需要氧气,否则氧气将开始死亡。在这方面,有多种选择-像在水族馆中那样,用压缩机不断地吹水,或者定期用营养液淹没根部,并在一段时间后将其排干。第一种选择有一个缺点-压缩机持续嗡嗡作响。第二种选择有一个优势-大多数根都在空气中,可以主动呼吸,并且加速生长的效果应该更大。此外,将它们浸入保留水分的特殊多孔颗粒基质中。选择是显而易见的,我以第二种选择为基础。以鱼为例,该系统可能几乎是完全封闭的-鱼的分泌物是由生物滤池中的特殊细菌处理的,加工后的产品被喂入植物中,一层沙层过滤水,净水返回到水族箱中。在理想情况下,偶尔将饲料撒到自动进料器中,西红柿会从灌木丛中收集。但是它并没有一起生长,也许是为了更好-他们知道邮寄必要细菌菌株的订单将如何结束。结果,番茄植株的设备呈现出轮廓。两艘船-下部的船用水,上部的船用水和植物。对于洪水,我们将使用带有直流电动机的小型中国水泵,对于排水,我们将使用自动虹吸管。视频中虹吸管的工作原理:具有类似虹吸管的水培法:该设备的大脑是ATMEGA328P微控制器(仅是因为有放置器在手)。他的任务包括按照时间表管理洪水和排放,监视水箱中的水位并发出缺水信号,控制植物的照明(我们希望有一定的日光最小长度;当自然光结束时,逐渐打开人工照明),一个用于查看的用户界面整个经济的状态,管理和配置。显然,这需要对水位传感器,光传感器,实时时钟和某种用户终端的解决方案。在描述细节之前,先列出项目资源:在这里您可以看到结果和制造过程的照片。短片:该项目位于GitHub上。在该版本中,在那里布置了一个文件,该文件包含KiCAD中的电子零件项目以及SolidWorks中的设计铃音项目(附加了用于打印的STL文件)。固件组合件的功能—
« ». , , , USB AVR (, , , , ), . - , , 'ADK_ROOT' , 'scons'.
电子部分的方案:
更多详细信息,陷阱说明和一些代码。软件问题的说明在最后。也许有人会感兴趣的是使用I2C,Valcoder,RTC模块和图形显示器的新示例。项目中的所有代码都是从头开始编写的,而没有使用第三方解决方案(因为我可以)。水位传感器
最敏感的问题首先确定。当然,会有某种类型的浮标的变体,例如,它将使应用格雷码的导轨移动,并读取光学传感器。但这看起来确实不可靠。在eBay上的搜索没有得到结果-要么有浮子开关(是否达到了所需的水平),要么是浸入的电极和基于介质电导率的读数,但这立即被注意到,因为水的成分会随着添加的肥料和溶解的电导率而不断变化。来自基材的杂质。结果,出现了使用超声波测距仪的想法,这是一种通常放置在不同机器人上的测距仪之一。按照计划,将传感器放置在水箱盖中,信号直接从水面反射。购买了HC-SR04(最小工作距离的最小值的选择-他有2cm),然后用一桶水检查了这个概念。事实证明,它是对自己有用的(有人担心水表面不会有法线反射,或者光束方向性会不足,而水箱壁也会有多余的反射)。顺便说一句,测距仪也是一个备用选择,但红外线。应该在水面上用反射器扔一个浮子。唯一的问题是它们的最小工作距离(我发现的最小距离)为10厘米,对于给定的尺寸来说已经足够了。应该在水面上用反射器扔一个浮子。唯一的问题是它们的最小工作距离(我发现的最小距离)为10厘米,对于给定的尺寸来说已经足够了。应该在水面上用反射器扔一个浮子。唯一的问题是它们的最小工作距离(我发现的最小距离)为10厘米,对于给定的尺寸来说已经足够了。
根据项目的结果,这种方法是有效的,并且可以在实践中使用,没有发现任何问题。值得采取措施将电路板与湿气隔离(在密封情况下)。只是传感器本身保持打开状态,也许它仍然存在。传感器的接口很简单-脉冲发送到触发输入,触发触发回波信号。在回波输出端会产生一个脉冲,其长度等于从辐射开始到反射回波信号被接收的时间。通过测量脉冲长度,了解声速以及信号到达物体并返回的事实,您可以计算距离。在项目中,这是在LevelGauge类中实现的。为了测量脉冲长度,使用了MK AVR“输入捕获”的硬件功能。在这种情况下,硬件定时器在脉冲的上升沿复位,并在定时器的下降值复位,硬件存储在寄存器ICR1中,并产生中断。因此,可以以足够的精度和最小的处理器时间消耗来测量脉冲持续时间。即使使用这种型号的传感器,也会发现毛刺-接通电源后,回声线会一直保持活动状态。他通过向触发器施加脉冲并一直等到第一个回声定位周期过去而绕过了。背光灯
背光由三个LED组成。我从铝型材上弯曲了三角形框架,并用环氧树脂将LED粘在上面。我订购了一个电流稳定器,电流为700mA。每个二极管上的压降约为3伏,稳定器要求输入和输出电压之间的差至少为2伏,我打算用12伏电源为整个晶片组装机供电。从这里可以很容易地计算出为什么只有三个LED。二极管为暖白色。在我看来,太阳光谱和所有这些自然。但是正如我后来发现的那样,在我订购它们之后,植物通常会使用红色和蓝色的组合。据我了解,整个问题仅在于效率。如果您的农场有全天候照明,那么您对所有花费的能源都花光了就很感兴趣。在白色照明下,绿叶将反射绿色成分,浪费在照明上的大部分能量将被浪费。

稳定器的一个重要功能是存在用于PWM调节的输入,我用它来调节亮度。这是另一个中国人的耙子。首先,事实证明这只是当前的开/关功能。也就是说,我希望输出电流不会被调制,其值将取决于PWM信号的占空比,但是电流只是重复了控制输入上的脉冲。但这还不算太糟,另一个伏击是调节器对相当高的频率的PWM反应不足。我不得不将其降低到300Hz,或多或少地正常工作。 PWM信号由微控制器使用定时器之一在硬件中生成。
背光组件的另一个重要部分是光传感器。选择光电晶体管作为该角色。是的,其中有两个-一个在LED上方用于测量自然光,另一个在LED下方以提供反馈。没错,自动夏时制扩展功能尚未实现,就像夏天一样,而且没有必要(动机很重要)。假定一旦第一传感器检测到照明水平降低(并且分配给白天的时间还没有到期),就调节光,使得第二传感器产生对应于期望照明的水平。为此,您需要在代码中实现一个简单的PID控制器。但是,在界面中时,您只能看到当前传感器的读数,并手动缠绕所需的背光亮度。注意传感器的连接。它们每个都有两个固定范围,可通过将相应的电阻连接到零来进行选择。此时,连接到第二电阻器的微控制器的脚将转移到高电阻状态。您可以同时打开两个电阻,然后会有三个固定的测量范围。来自发射器电阻器的信号通过RC电路以过滤调制脉冲-来自LED的光与电流调节器上的PWM信号一起脉动。水泵
最便宜的中文齿轮,带直流电机。当然可以伏击。尽管它表示为12V,但在此电压下不能长时间工作。在组装结构之前烧毁了一个。该方案为其提供了PWM,在接口中配置了最大功率,实际上它的设置没有超过70%。在这个水平上,他在工作中疯狂呼啸,但大多数时候他以更低的功率工作-大约30%,并且安静地低声嘶哑。关于其运行模式的以下内容,在泛洪逻辑的描述中。必须将较大的电容器(图中的C8)放置在更靠近泵浦电源电路的位置,否则会对整个电路产生很大的干扰(实际上,事实证明LED的电流调节器对它们最敏感,会发出轻音乐)。实时时钟
为了这些目的使用微控制器的资源是一个疯狂的想法。石英时钟发生器具有非常好的精度,在另一个项目中,这种方法效果很好。但麻烦的是,绝对所有硬件计时器都已用于其他用途。别无选择,只能找到外部RTC模块。赞美中国人,他们在那里而且很便宜。
基于DS3231的模块具有I2C接口,其自身的冗余电源-停电不会使时间出错。在几个固定频率上有曲折输出-1 kHz,4 kHz和8 kHz。这对于音频信号非常有用-再次,您不需要加载MCU,也没有空闲的计时器。 32Kbit EEPROM是一个奖励,但是在本项目中未使用。令人惊讶的是,它非常准确-几个月后,它失去了几秒钟的力量。他说,他考虑了温度对发电机频率的影响,显然这是可行的。但是,如果时间消失了,则有可能进行软件频率校正。温度传感器读数可用,并且在该项目中显示在界面中。Rtc类负责在代码中使用此模块。显示
我一直想用图形显示做些什么。使用I2C接口搜索最便宜的设备就可以使用此选项。
基于颇受欢迎的控制器SSD1306的单色OLED显示128x64像素。选择时,您需要仔细查看描述-同一芯片支持I2C以外的其他接口,并且有些选项没有它。或者他们说它是通用的,它也支持I2C,但实际上有必要通过将空值重新排列到其他站点来稍微修改电路板。因此,如果您打算使用I2C,最好选择仅在板上显示I2C的板,这样对于几乎没有任何文档(仅针对芯片的文档)的板就不会有太多麻烦了。该版本的工作电压为5V,该板具有控制器所需的3.3V稳压器。我遇到过评论,在某些版本中可能不是。显示通常令人满意。我注意到只有一个令人不快的功能-像素行的亮度取决于其中点亮的像素数。亮度越高,亮度越低。如果在屏幕上交替显示一些狭窄元素的完全填充区域,则线条之间的对比度可能会很明显。但是实际上,这在我的图片中是不可见的,也不引人注目。控制器可以被配置为以在像素矩阵上显示屏幕存储器的内容的各种模式进行操作。当我将每个字节映射到一个高8像素的垂直列上,并且这些列从左向右水平移动时,用八像素高的行填充屏幕对我来说更方便。在这种模式下,绘制文本更加方便。通常,通常采用一种方法,其中在RAM MCU中复制显示存储器-首先,在RAM中执行所有与图像有关的操作,然后将所有更改的像素复制到显示存储器中。在此项目中,不使用这种方法来节省资源。所有更改的位置都会立即在显示存储器中重新绘制。如评论中所建议,OLED显示器会随着时间而褪色。我也怀疑这一点(记住什么是屏幕保护程序),并提供了在控件上一次活动后几分钟后关闭显示器的功能。当转动或按下编码器时,它会亮起。在代码中,在Display类中实现了使用显示器的工作。瓦尔科德:
在我看来,对于至少具有某些用户界面的设备,valcoder是最佳控制选项。它紧凑且非常舒适。他们可以方便地浏览和选择菜单项,更改任何参数的值,切换模式等。要进行连接,需要微控制器的三个输入脚。一个用于按钮(可以按下手柄),两个用于valcoder本身。编码器发出格雷码信号。在每个转弯步骤中,一位在两条线上改变。该顺序确定旋转方向。一切似乎都很简单,但是,显然,开发人员并不总是能够为这种设备提供高质量的支持。例如,在我的3D打印机上,有一个RAMPS板和一个带显示器的板,并且连接了完全相同的编码器。 Marlin固件可以使用它,但是使用它的经验非常糟糕-没有可靠性的感觉-当您在旋转旋钮时单击旋钮时,界面通常会停留在预期的错误菜单项或参数值上。快速旋转时,感觉就像是在跳过点击。在某些时候,切换并不是在点击过程中开始的,但是介于两者之间的某个位置非常令人不快。是的,Marlin是什么,有时候我对车上的内置多媒体系统有相同的感觉。在这方面,有一些技巧(当然,请参见RotEnc类附近的代码)。首先,对于将任何按钮连接到微控制器的任何人来说,这都是显而易见的一点-您需要应对跳动。实际上,该机械编码器及其信号线是相同的按钮,并且它们还具有颤振。首先,我们过滤颤振,然后处理信号线的状态序列。可能有带有光学传感器的valcoder,它已经取决于它们的信号处理方案。如果将光电晶体管的支脚直接引出,则可能会缓慢旋转而发出嘎嘎声,但是如果有任何引入滞后的处理方案,则不需要软件抑制。但是这种设备比较昂贵,很少在业余设备中使用,最常见的是机械设备,几美元一捆。其次,一点不太明显,可能是马林被烧伤的地方之一-手柄在旋转过程中具有稳定的位置-咔嗒声。该模型为每次点击包含四个步骤的代码序列。因此,您需要响应单击,而不是响应序列步骤。最重要的是要与稳定的位置同步。许多人只需输入常量STEPS_PER_CLICK,然后例如对每四个步骤进行响应。但是问题是信号不完美,序列可能不完全正确。使用某种拼写,代码可能会“误入歧途”,结果,将在单击中间的某个位置获得每四个步骤,这会使用户感到不舒服。同时,特定型号手柄的固定位置对应于固定代码值,它必须附加到它。第三,同样,对于或多或少有经验的微控制器系统开发人员来说,很明显的一点是-使用硬件中断来更改输入线的状态。至少,“丢失”序列步骤的风险较小。但总的来说,正如您所知,打扰是我们的一切。MCU应该在可能的情况下进入睡眠状态,仅在中断时才唤醒(从外围设备或定时器执行延迟任务)。这些是良好的系统架构设计的原则。整体设计
它由简易材料制成,并在ABS的3D打印机上打印了各种零件。虹吸管的工作原理如上图所示。对我来说,它是外部PVC管,内部是带有漏斗的内部管。对于经典的虹吸管,还需要另一个膝盖,但是对于我来说,建设性地制造它已经很困难了。当发现排水管有问题时,在下部水箱的壁上贴了一个小水槽,内管的末端浸入其中,对排水管产生阻力,虹吸管可以正常工作。事实证明,ABS是一种非常疏水的材料。水实际上不会从中溢出;我什至不得不重做虹吸漏斗。值得考虑的特性是,不可能创建一些小型液压系统(例如,我想在虹吸漏斗上制作导向表面,以使水扭曲,以改善虹吸响应。但是对于这样的ABS尺寸和疏水性,这是没有意义的) 。我还首先尝试用热胶枪将所有胶粘在一起。它不起作用-最初似乎一切都变紧了,但是几天后它就掉了。最好的选择是中亚。即使在水下,细节也要紧紧抓住。设计中最大的错误估计是透明容器。我完全忘记了水在阳光下绽放的事实。我不得不用不透明的材料包裹它。好了,您可以定期添加高锰酸钾进行消毒,这似乎并没有损害植物。溢流算法如下:首先以低功率打开泵,然后安静地注满整个上部水箱。该过程由液位传感器监控。当水开始通过虹吸漏斗溢出时,下部水箱中的水位下降停止,这被传感器检测到。在低功率下产生的小流量不足以触发虹吸。泵停止运转,记入泵入上部水箱的体积被记住。将根部保留在溶液中几分钟,然后再次打开泵。首先,在低功率状态下,直到水再次到达漏斗为止(在停机期间,由于虹吸管的作用其水位降低),并且当达到漏斗的液位时,泵会开启以增加功率,从而提供足以触发虹吸管的流量。确保通过虹吸管的流量高于泵流量,结果,下部水箱中的水位开始上升,这被传感器检测到并且泵停止。从黎明到黄昏,洪水周期以固定的,可配置的时间间隔定期开始。根据计划,应该用光传感器来固定黎明,如果需要,可以将日光的长度延长到设定值,但是直到那时才有人动手。黎明时间只需在设置中进行设置。C ++ 11在哪里?
也许有人会怀疑C ++ 11在微控制器的编程中是否有用(在那些通常知道微控制器可以用C ++进行编程的人中)。我将尝试举例说明C ++ 11在此领域的优势(除了constexpr,override,default等显而易见的小事情)。字符串资源的放置
许多人知道微控制器中的RAM是非常有限的资源。例如,如果您的应用程序具有用户界面,并且程序使用了大量的行,则可能会出现问题。如果在代码中写类似PromptUser("Are you sure you want to format SD-card?");
那么参数中传递的行将被放置在初始化的数据部分(此后为AVR平台的GCC编译器的行为)-即在RAM区域中,该区域在启动时(在调用主函数之前)从程序闪存进行初始化。函数PromptUser()将传递一个指向RAM中所需位置的指针。如果在整个程序中使用类似的方法,则RAM将很快结束(在该项目中使用的ATMEGA328P中,它只有2 KB,并且也用于BSS,堆和堆栈)。为了解决此限制,PromptUser()之类的函数学会了不使用指向RAM的指针,而是指向程序闪存中某个区域的指针。您只能在特殊说明的帮助下从那里阅读,例如,avr-libc中的特殊说明包装在eeprom_read_系列的功能中[byte | word | dword | ...]。在这种情况下,必须首先将字符串放在配备有PROGMEM属性的变量中,该变量告诉编译器应将其放在程序存储器中。char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
PromptUser(prompt);
如果要集中声明所有行,这将很不方便。然后,您必须首先在头文件中声明它们的声明:extern char prompt[] PROGMEM;
在一个单独的.cpp文件中,定义:char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
代码重复是不好的,并且当有很多这样的行时非常不方便。是的,可以通过创建一个棘手的宏来避免此问题,并将头文件包含在单独的.cpp文件中,该宏将在其中扩展为定义,而在其他情况下,它将扩展为声明。但是对于C ++ 11,如果在声明时使用类成员的初始化,则有一个更简洁的选项。在头文件中,使用以下行声明类:#define DEF_STR(__name, __text) \
const char __name[sizeof(__text)] = __text;
class Strings {
public:
DEF_STR(Prompt, "Are you sure you want to format SD-card?")
DEF_STR(OtherString, "...")
…
} __attribute__((packed));
extern const Strings strings PROGMEM;
在.cpp文件中:const Strings strings PROGMEM;
现在所有的行都在一个地方声明,放在程序存储器中,您可以像这样访问它们:PromptUser(strings.prompt);
在该项目中,基于相同原理的方法用于确定位图-图形显示器上显示的各种图片。
struct Bitmap {
const u8 *data;
u8 numPages,
numColumns;
} __PACKED;
template<u8... data>
constexpr static u8
Bitmap_NumDataBytes()
{
return sizeof...(data);
}
#define DEF_BITMAP(__name, __numPages, ...) \
const u8 __CONCAT(__name, __data__) \
[Bitmap_NumDataBytes<__VA_ARGS__>()] = { __VA_ARGS__ }; \
const Bitmap __name { \
reinterpret_cast<const u8 *>(OFFSETOF(Bitmaps, __CONCAT(__name, __data__))), \
__numPages, \
sizeof(__CONCAT(__name, __data__)) / __numPages};
class Bitmaps {
public:
DEF_BITMAP(Thermometer, 1,
0b01101010,
0b10011110,
0b10000001,
0b10011110,
0b01101010
)
DEF_BITMAP(Sun, 1,
0b00100100,
0b00011000,
0b10100101,
0b01000010,
0b01000010,
0b10100101,
0b00011000,
0b00100100
)
...
};
extern const Bitmaps bitmaps PROGMEM;
区别在于,除了图像数据本身之外,还必须放置属性(图像大小)。每个字节定义一列八个像素。列可以填充一个或多个行;其编号由名称后的第二个参数指示。事实证明,对于任意宽度,位图的高度应为8的倍数,这对于该项目是完全可以接受的。二进制文字
您可能已经注意到,先前示例中的位图使用二进制文字来确定。这确实非常方便-您可以直接在代码中编辑简单的位图,尤其是在编辑器允许突出显示位图的情况下。例如,font.h文件中的字体字符定义:
可变参数模板
那没有他们的话。好吧,例如,用于显示控制器的命令的长度可以为一到几个字节。发送以下代码:SendCommand(Command::DISPLAY_ON);
SendCommand(Command::SET_COM_PINS, COM_PINS | COM_PINS_ALTERNATIVE);
SendCommand(Command::SET_COLUMN_ADDRESS, curVp.minCol, curVp.maxCol);
方便,不是吗?
template <typename... TByte>
void
SendCommand(TByte... bytes)
{
cmdSize = sizeof...(bytes);
controlSent = false;
cmdInProgress = true;
SetCmdByte(sizeof...(bytes) - 1, bytes...);
i2cBus.RequestTransfer(DISPLAY_ADDRESS, true,
CommandTransferHandler);
}
template <typename... TByte>
inline void
SetCmdByte(int idx, u8 byte, TByte... bytes)
{
cmdBuf[idx] = byte;
SetCmdByte(idx - 1, bytes...);
}
inline void
SetCmdByte(int, u8 byte)
{
cmdBuf[0] = byte;
}
variant.h文件描述了一个类,该类使用可变参数模板类似于boost :: variant。它用于组织用户界面页面。重点再次在于节省内存-动态内存管理是一种无法接受的奢侈,您必须躲避(尽管2K仍然很大,您无法躲避,但是在同一ATMEGA行中,其大小达到512字节,每个字节每帐户)。在我的界面中,随时可以在屏幕上显示一页。因此,对于所有页面,您可以使用同一块内存,在C中称为联合。对于C ++中的类,通常将其称为“变体”。与union不同,我们需要记住在调用新内容的构造函数之前先调用先前内容的析构函数。 Variant<MainPage,
Menu,
LinearValueSelector,
TimeSelector> curPage;
...
template <class TPage>
static constexpr u8
GetPageTypeCode()
{
return decltype(curPage)::GetTypeCode<TPage>();
}
...
curPage.Engage(nextPageTypeCode, page);
为了进行编译,将GCC和GNU binutils用于AVR平台(在Ubuntu中有一个现成的软件包gcc-avr)。组装过程的细节已在上面给出。编译器的参数看起来像这样(省略了特定于项目的退款和包含项):
链接:
将代码段转换为十六进制格式:
创建EEPROM映像:
用于微控制器的固件:
PS第一个西红柿已经成熟,而且味道不佳。显然,他们不喜欢节食。可能必须改变文化。avr-g++ -o build/native-debug/src/firmware/cpu/lighting.cpp.o -c -fno-exceptions -fno-rtti -std=c++1y -Wall -Werror -Wextra -ggdb3 -Os -mcall-prologues -mmcu=atmega328p -fshort-wchar -fshort-enums src/firmware/cpu/lighting.cpp
avr-g++ -o build/native-debug/src/firmware/cpu/cpu -mmcu=atmega328p build/native-debug/src/firmware/cpu/adc.cpp.o build/native-debug/src/firmware/cpu/application.cpp.o …
avr-objcopy -j .text -j .data -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_rom.hex
avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_eeprom.hex
avrdude -p atmega328p -c avrisp2 -P /dev/avrisp -U flash:w:build/native-debug/src/firmware/cpu/cpu_rom.hex:i