项目目标
不知何故,我盖了房子,一个骨架。 在我的奢华庄园里,没有天然气,并且在不久的将来也不会出现,这就是为什么我选择了骨架-对于我来说,其他所有东西,用电加热都非常昂贵。 好吧,还因为它是最便宜的技术之一。
好吧,我在屋子里扔了水管,挂了电池,一个锅炉,似乎很热,但是出了点问题。
听了自己的话,我意识到这是我不喜欢的蟾蜍,因为当我不在家(每天12-16小时)时,暖气有效。 而且它可能不起作用,只能在到达之前打开它,因为骨架略有惯性并且可以让您快速升高温度。 相同的情况下,很长一段时间要离开家时。 好吧,总的来说,在街上随温度变化运行,转动锅炉的手柄总有点不合逻辑。
很明显,没有自动化,锅炉是最简单的锅炉,但是它具有用于连接外部控制继电器的触点。 当然,您可以立即购买具有所有必要功能的锅炉,但是对我来说,这样的锅炉有些不人道。 另外,我想蹲下来,为灵魂撒尿,学一点C,尽管是arduino版本。
实际上关于要求:
- 设定温度控制
- 根据室外温度或手动控制冷却液温度
- 具有不同设置的时区,白天更冷,晚上更热
- 自动模式,具有自动昼夜转换
- 手动模式,周末无自动转换
- 非自动模式,您可以在其中手动设置任何冷却液温度并打开/关闭锅炉
- 通过按钮和屏幕以及通过网站/移动应用程序在本地进行加热控制
那是一开始,然后我受苦并补充说:
- 路灯控制(LED射灯)
- 基于运动传感器,警笛和路灯的警报系统
- 计量锅炉每天/每月/每年+一年中每个月所消耗的能源
- 仅通过缓慢闪烁指示灯来发出警报模式
- 指示灯快速闪烁并发出警报声的蜂鸣声
- 指示灯快速闪烁并警笛不断鸣叫以发出信号
本文的目的是分享经验,用俄语描述一些我在互联网上找不到的内容。 我认为本文对已经有点编程的初学者Arduino入门者很有用,因为 我没有描述的绝对基本的东西。 我试图编写尽可能清晰的代码,希望我能成功。
一开始是什么
最初,该项目是在大量的Arduino Nano + ESP8266上实现的,但是ESP并不是作为屏蔽,而是作为单独的设备。 为什么这样 是的,因为我已经拥有了所有这些东西,但是这个词根本没有钱,所以我原则上不想买新的铁。 ESP为什么不像盾牌? 现在我什至不记得了。
Arduino操纵了所有过程,因为它具有所需的GPIO数量,而ESP将所有数据发送到Blynk服务器,因为它知道互联网并且没有足够的GPIO。 他们通过UART连接起来,并互相发送JSON和数据。 该计划是不寻常的,但它运作了一年几乎没有任何投诉。 任何感兴趣的人都可以看到编解码器 。
我马上要预约,当时我还不是很熟练(甚至现在我想做得更好),因此,孕妇和孩子最好不要看。 此外,所有内容都是在Arduino IDE中编写的,到了晚上,它就不会被记住,这在重构方面受到很大限制,那里的一切都非常原始。
铁
因此,一年过去了,资金允许购买具有足够GPIO的ESP32 devkit v1,它可以访问Internet并通常是超级控制器。 除了笑话,我在工作结束时非常喜欢她。
铁表:
- ESP32 devkit v1 noname中国
- 3个温度传感器DS18B20,室内温度,室外温度和管道中的冷却液温度
- 4个继电器块
- 红外传感器HC-SR501
我不会制定一个方案,我认为所有带有引脚名称的宏都将清晰可见。
为什么选择FreeRTOS和Arduino Core
一堆库是在Arduino上编写的,尤其是同一个Blynk,因此您将不会离开Arduino Core。
FreeRTOS,因为它使您可以组织一小块铁片的工作,类似于成熟的工业控制器的工作。 每个任务都可以移动到自己的任务中,可以在需要时停止,启动,创建,删除-与编写一堆冗长的Arduino代码相比,这一切都更加灵活,最终所有事情都在循环函数中完成。
当使用FreeRTOS时,如果仅处理器功率足够,每个任务将在严格指定的时间执行。 相反,在Arduino中,所有代码都在一个函数中执行,并且在一个线程中执行,如果速度变慢,则其余任务将被延迟。 在管理快速流程时,这一点尤其明显。在此项目中,手电筒的闪烁和警报器蜂鸣声将在下面讨论。
关于逻辑
关于FreeRTOS任务
→链接到项目的整个编解码器
因此,在使用FreeRTOS时,设置功能扮演主要功能的角色,进入应用程序的入口,在其中创建FreeRTOS任务(以下称任务),根本无法使用循环功能。
考虑一个用于计算冷却液温度的小任务:
void calculate_water_temp(void *pvParameters) { while (true) { if (heating_mode == 3) {} else { if (temp_outside > -20) max_water_temp = 60; if (temp_outside <= -20 && temp_outside > -25) max_water_temp = 65; if (temp_outside <= -25 && temp_outside > -30) max_water_temp = 70; if (temp_outside <= -30) max_water_temp = 85; } vTaskDelay(1000 / portTICK_RATE_MS); } }
它被声明为必须带有_void pvParameters
的函数,该函数内部组织了一个无限循环,我while (true)
使用了该循环。
vTaskDelay(1000 / portTICK_RATE_MS)
简单的温度计算(如果操作模式允许),然后通过vTaskDelay(1000 / portTICK_RATE_MS)
对任务执行安乐死1秒钟。 在这种模式下,它不消耗CPU时间,任务使用的变量(即上下文)存储在堆栈中,以便在时间到时将其移出堆栈。
必须在安装程序中创建下一个任务。 这是通过调用xTaskCreate
方法完成的:
xTaskCreate(calculate_water_temp, "calculate_water_temp", 2048, NULL, 1, NULL);
有很多参数,但是对我们来说, calculate_water_temp很重要-包含任务代码的函数的名称,而2048是堆栈的大小(以字节为单位)。
堆栈的大小最初将每个人的大小设置为1024字节,然后我通过键入来计算所需的方法,如果控制器开始因堆栈溢出而崩溃(从uart的输出中可以看出),我只是将堆栈的大小增加了2倍,如果没有帮助,则增加了2倍,依此类推直到它起作用。 当然,这并不会节省太多内存,但是ESP32具有足够的内存,在我的情况下,您不会对此感到烦恼。
您也可以指定任务的句柄-例如,创建后可以用来控制任务的句柄-删除。 这是示例中的最后一个NULL。 这样创建一个句柄:
TaskHandle_t slow_blink_handle;
接下来,在创建任务时,将指向该xTaskCreate
的指针传递给xTaskCreate参数:
xTaskCreate(outside_lamp_blinks, "outside_lamp_blynk", 10000, (void *)1000, 1, &slow_blink_handle);
如果要删除任务,请执行以下操作:
vTaskDelete(slow_blink_handle);
可以在panic_control panic_control
代码中看到如何使用它。
FreeRTOS Mutex专业人士
Mutex用于消除访问uart,wifi等资源时任务之间的冲突。 就我而言,我需要互斥体来实现wifi和访问闪存。
创建一个指向互斥锁的链接:
SemaphoreHandle_t wifi_mutex;
在setup
创建一个互斥锁:
wifi_mutex = xSemaphoreCreateMutex();
此外,当我们需要访问任务资源时,它会使用互斥对象,从而让其余任务知道该资源正在忙碌,而无需尝试使用它:
xSemaphoreTake(wifi_mutex, portMAX_DELAY);
portMAX_DELAY
无限期地等待其他任务释放资源和互斥锁,所有时间该任务将进入睡眠状态。
处理完资源后,我们提供互斥锁,以便其他人可以使用它:
xSemaphoreGive(wifi_mutex);
您可以在send_data_to_blynk send_data_to_blynk
中更send_data_to_blynk
查看代码。
实际上,在控制器的操作过程中不使用互斥锁并不明显,但是在JTAG调试过程中,错误不断消失,使用互斥锁后消失。
tasok的简短说明
get_temps
每30秒从传感器接收一次温度,通常不需要这样做。
get_time_task
从NTP服务器获取时间。 以前,时间来自RTC模块DS3231,但是经过一年的工作,它开始出现故障,因此我决定完全摆脱它。 我认为这对我没有任何特殊影响,主要是时间会影响加热时区的切换(白天或晚上)。 如果在控制器运行时Internet消失,则时间只会冻结,时区将保持不变。 如果控制器关闭并且打开后没有互联网,则时间将始终为0:00:00-夜间供暖模式。
calculate_water_temp
上面已考虑。
detect_pir_move
从HC-SR501传感器接收运动信号。 当检测到运动时,传感器形成一个逻辑单元+ 3.3V,通过digitalRead
进行检测,顺便说一句,该传感器的检测引脚应上拉至GND- pinMode(pir_pin, INPUT_PULLDOWN);
heating_control
切换加热模式。
out_lamp_control
路灯的控制。
panic_control
检测到运动时控制警笛和聚光灯。 为了产生警报器和闪烁的灯光效果,使用了单独的任务outside_lamp_blinks
和siren_beeps
。 当使用FreeRTOS时,闪烁和哔哔声完全按设定的间隔完美地工作,其他任务不会影响其工作,因为 他们生活在不同的溪流中。 FreeRTOS保证任务中的代码将在指定的时间执行。 在loop
实现这些功能时loop
所有工作都不那么顺利,因为 受其他代码执行的影响。
guard_control
保护模式的控制。
send_data_to_blynk
将数据发送到Blynk应用程序。
run_blynk
根据使用Blynk的手册的要求启动Blynk.run()
任务。 据我了解,这是从应用程序到控制器获取数据所必需的。 通常, Blynk.run()
应该处于loop
,但是我基本上不想在此放置任何内容并将其作为单独的任务。
write_setting_to_pref
记录设置和操作模式,以便在重新启动后捕获它们。 关于首选项将在下面描述。
count_heated_hours
计算锅炉的运行时间。 我做的很简单,如果在任务启动时(每30秒一次)打开锅炉,则闪存中所需键的值将增加1。
send_heated_hours_to_app
在此任务中,将值提取并乘以0.00833(1/120小时),将锅炉的接收运行小时数发送到Blynk应用程序。
feed_watchdog
提要看门狗。 我不得不写看门狗,因为 每隔几天控制器可能会冻结。 它所连接的对象尚不清楚,可能会对电源产生某种干扰,但是使用看门狗可以解决此问题。 看门狗定时器10秒,如果控制器在10秒钟内不可用就可以。
heart_beat
带脉冲的任务。 当我通过控制器时,我想知道它工作正常。 因为 在我的板上没有内置LED,我不得不使用UART LED-安装Serial.begin(9600);
并在UART中写入一个长字符串。 效果很好。
ESP32 NVS磨损均衡
下面的描述相当粗糙,字面意义上只是为了传达问题的实质。 更多细节
Arduino使用EEPROM存储器将数据存储在非易失性存储器中。 这是一种小型存储器,其中每个字节可以分别写入和擦除,而闪存仅按扇区擦除。
ESP32中没有EEPROM,但通常有4 Mb的闪存,您可以在其中创建用于控制器固件或用于存储用户数据的分区。 用户数据的节有几种类型-NVS,FATFS和SPIFFS。 应该根据要记录的数据类型进行选择。
因为 该项目中写入的所有数据均为Int类型,因此我选择了NVS-Non-Volitile Storage。 这种类型的分区非常适合存储较小的,经常是可重写的数据。 要了解原因,您应该更深入地了解NVS的组织。
与EEPROM和FLASH一样,对数据进行覆盖也有限制,EEPROM中的字节可以被覆盖100,000到1,000,000次,并且FLASH扇区是相同的。 如果我们每秒写一次数据,那么我们得到60秒x 60分钟x 24小时= 86400次/天。 也就是说,在此模式下,字节将持续11天,这有点。 此后,该字节将不可用于写入和读取。
为了解决这个问题,Arduino EEPROM库的update()
put()
函数仅在数据更改时才写入数据。 也就是说,您可以每秒写入一些很少更改的设置和模式代码。
NVS使用另一种控制磨损平衡的方法。 如上所述,闪存扇区中的数据可以部分写入,但只能擦除整个扇区。 因此,NVS中的数据记录是以一种日志记录的方式进行的,该日志记录分为几页,这些页放在闪存的一个扇区中。 数据成对地写入key:value。 实际上,它比使用EEPROM更容易,因为 使用有意义的名称比使用内存中的地址更容易。 Upd:密钥长度不超过15个字符!
如果首先将值1
写入键somekey
,然后将值2
写入相同的键,则第一个值将不会被删除,只会被标记为已删除(已擦除),并且新条目将添加到日志中:

如果尝试通过somekey
读取数据, somekey
返回此键somekey
最后一个值。 因为 由于日志是通用的,因此不同键的值在写入时会紧挨着存储。
该页面的状态为:空-空,没有条目,活动-当前正在向其写入数据,已满-已充满,您无法对其进行写入。 当页面空间不足时,她来自
活动进入完全,然后下一个空白页变为活动。

据我从Espressif网站和各种论坛上的文档中了解到,当免费页面结束时,页面清理就开始了。 更准确地说,据此,仅剩1个可用页面时将进行擦除。
如果需要清除页面,则当前记录(非擦除)将移至另一页,并且该页面将被覆盖。
因此,每个特定页面的写-擦除操作非常少见,页面越多-越少。 基于此,我将NVS分区的大小增加到1 MB,以我的记录速率,这足以满足170年的需求,这通常就足够了。 接下来是关于调整NVS部分的大小。
为了方便使用NVS,用于Arduino Core的ESP32编写了一个便捷的自选设置库, 此处介绍了如何使用它。
关于VisualGDB的一些知识
当我开始使用Arduino IDE时,与Visual Studio相比,该功能的惨痛让我立即感到惊讶。 他们说,尽管VS也很适合我,但它也不是一个源泉,但是在Arduino IDE中写超过50行的内容却很痛苦而且很长。 因此,出现了选择IDE进行开发的问题。 因为 我对VS很熟悉,因此选择了VisualGDB 。
在Arduino IDE之后,ESP32的开发简直就是天堂。 什么是到定义的转换,在项目中搜索调用以及重命名变量的能力。
使用VisualGDB更改ESP32分区表
如上所述,可以使用ESP32分区更改表;我们将考虑如何实现。
该表被编辑为csv文件,默认情况下,VisualGDB写入下表:
Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x140000, app1, app, ota_1, 0x150000,0x140000, spiffs, data, spiffs, 0x290000,0x170000,
在这里,我们看到NVS下的一个部分,两个应用程序部分以及其他几个部分。 在这些细微差别中,可以注意到,应始终从零地址开始将app0(您的应用程序)写入偏移量0x10000,否则引导加载程序将不会检测到它。 另外,应选择偏移量,以使各部分不会彼此“重叠”。 分区表本身写入偏移量0x8000。 如您所见,在这种情况下,NVS的大小为0x5000-20KB,这不是很大。
我修改了分区表,如下所示:
Name, Type, SubType, Offset, Size, Flags app0, app, ota_0, 0x10000, 0x140000, nvs, data, nvs, , 1M, otadata, data, ota, , 0x2000, spiffs, data, spiffs, , 0x170000,
不要忘记在“名称”之前添加网格,如果使用此表,则需要将此行视为注释。
如您所见,NVS的大小已增加到1 MB。 如果未指定偏移量,则该部分将在上一个偏移量之后立即开始,因此仅指示app0的偏移量就足够了。 可以在记事本中将CSV文件编辑为txt,然后将已保存文件的CSV权限更改为csv。
接下来,分区表必须转换为二进制,因为 它以这种形式进入控制器。 为此,运行转换器:
c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\tools\gen_esp32part.exe part_table_name.csv part_table_name.bin
。 第一个参数是CSV,第二个参数是输出二进制文件。
生成的二进制文件应放置在c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\tools\partitions\part_table_name.csv
,然后必须指定构建解决方案的负责人,以及没有默认分区表。 您可以通过在文件c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\boards.txt
写入表的名称来做到这一点c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\boards.txt
。 就我而言,这是esp32doit-devkit-v1.build.partitions=part_table_name
经过这些操作后,VisualGDB在构建应用程序时将完全采用您的分区表并将其放入
~project_folder_path\Output\board_name\Debug\project_name.ino.partitions.bin
,已经从那里将其倒入板中。
JTAG调试器CJMC-FT232H
据我所知,这是可以与ESP32一起使用的最便宜的调试器,它花了我大约600卢布,在Aliexpress上有很多。

连接调试器时,Windows在其上安装了不适合的驱动程序,需要使用Zadig程序进行更改,那里的所有操作都很简单,我将不再对其进行描述。
它以以下方式连接到ESP32 devkit-v1:
FT232H-ESP32
AD0-GPIO13
AD1-GPIO12
AD2-GPIO15
AD3-GPIO14
然后,在Project -> VisualGDB Project Properties
需要进行以下设置:

然后单击测试。 有时会发生第一次未建立连接,过程似乎冻结,然后您需要中断并重复测试。 如果一切正常,则测试连接过程大约需要5秒钟。
我通常会组装项目,然后通过USB将其上传到ESP32本身(而不是通过调试器),然后使用Debug- Debug -> Attach to Running Embedded Firmware
开始调试。 在代码中,您可以设置断点,查看故障时的变量值,在“ Debug -> Windows -> Threads
窗口中,您可以看到代码在哪个FreeRTOS任务中停止了,这在调试过程中发生错误时非常有用。 调试器的这些功能足以让我舒适地工作。
当我开始使用NVS时,调试总是会由于晦涩的错误而中断。 据我了解,这是因为调试器需要在默认的NVS部分中创建类似转储的内容,但是此时控制器已经使用了NVS。 当然,可以通过创建2个NVS分区来避免这种情况,其中一个具有用于调试的默认名称,另一个具有其自身的需要。 但是那里没有什么复杂的,在添加的代码中,它第一次起作用,所以我没有检查它。
毛刺ESP32
像任何带有Aliexpress的设备一样,我的ESP32开发板也有自己的,没有描述过的故障。 当她到达时,我从开发板上给一些工作在I2C上的外围设备供电,但过了一段时间,如果在+ 5V引脚上连接了任何消耗性设备甚至电容器,板上开始重启。 为什么这样完全是不可理解的。
现在,我使用0.7A的中文电荷为电路板供电,使用3.3V电路板脚的ds18b20传感器为电路板供电,并使用2A的另一电荷为继电器和运动传感器供电。 电路板的GND脚当然连接到其余熨斗的GND引脚。 便宜而开朗是我们的选择。
关于项目成果
我有机会灵活地控制房屋的供暖,节省了金钱和汗水。 目前,如果加热在室外-5--7处全天保持23度,则锅炉运行大约11个小时。 如果白天将温度保持在20度并仅在晚上加热到23度,那么锅炉已经运行了9个小时。 锅炉容量为6千瓦,当前千瓦价格为2.2卢布,每天约26.4卢布。 我们地区供暖季节的持续时间为200天,供暖季节的平均温度约为-5度。 因此,我们在供暖季节节省了约5000r。
设备的成本不超过2000r,也就是说,成本将在几个月后被退回,更不用说现成的这种自动化系统至少要花费20,000r的事实。 另一件事是,我花了大约一周的纯工作时间来编写固件和调试程序,但是例如在工作过程中,我终于意识到C ++中的指针是什么,并且获得了很多其他经验(例如,许多小时的调试工作,无法理解的故障)。 如您所知,经验很难被高估。
Blynk移动应用程序的屏幕截图:



当然,项目中的代码不是杰作,但是我在缺乏时间的情况下编写了代码,并且主要着眼于可读性。 根本没有时间重构。 总的来说,我有很多借口来解释为什么我的代码如此恐怖,但这是我的最爱,因此,我将对其进行详细介绍,我将不再进一步讨论该主题。
如果我的涂鸦可以帮助某人,我将感到非常高兴。 我将很高兴收到任何评论和建议。