通过ESP8266通过WIFI与ARDUINO进行编程和数据交换

像许多其他自制产品一样,我经常将AVR微控制器用于各种业余工艺品。 而且由于有了Arduino概念,这些手工艺品现在也呈现出优雅的外观。 确实,对于一块大约300-400卢布的东西,我们得到了一块微型的多层板,带面罩,丝网印刷以及微控制器的外围设备完全与之分离(此外,在SMD版本中!)。 我不是在谈论同一“ Arduino”系列的所有插件:传感器,控制器,显示器和整套设备,我们急需的其他外围设备。 同样,所有东西也不便宜,而且性能出色。 几乎不再需要滋生某些东西并焊接在“膝盖”上。


但是所有这些各种各样的业余手工艺品自然都需要进行初步编程。 后来,随着各种改进,我经常不得不重新制作这些工艺品。 很显然,与不断地将它们拖到常规程序员那里相比,远程执行此操作更为方便。 总的来说,由于使用了相同的Arduino平台,所以这里有很多选项:蓝牙,ZigBee,带有您的个人协议的无线电频道,IR甚至Wi-Fi。 所有这些都允许您与微控制器建立无线联系。 但是我们将停止最后的选择。 主要有四个原因:

1:现代,物联网!

2:每个公寓中都有一个无线路由器,请在家庭网络中注册设备,瞧!

3:您的手工艺品在其发展方面实现了革命性的飞跃; 它们不仅可以在远处进行编程,现在还可以与周围的世界进行通信:电子时钟独立于NTP服务器时钟获取确切的时间,行政设备由城市或国家/地区的另一端控制,注册设备将累积的数据存储在云等 等

4:有一系列很棒的ESP8266芯片,要在其上实现这一切并不容易。

此外,在本文中,以伺服器上的机械臂为例,将对基于AVR微控制器的设备与PC(或其他任何设备)的远程编程和数据交换进行分解和演示。 我想立即指出,以下列出的所有程序都是纯示例性的,没有商业价值。 因此,不能接受诸如程序员为何cast割,功能差或为什么没有其他服务随处可见的主张。 由于代码是开放的,因此任何人都可以自行决定完成代码,但是我仍然有足够的工作要做。

假定读者已经熟悉Arduino模块(屏蔽)以及ESP8266的连接和固件。 实际上,已经在Web上发布了大量资料,解释了使用这些设备的基本原理,在此我不再赘述。 对于初学者,本文结尾处提供了有关这些问题的有用链接的列表,您可以在其中找到很多信息,以及为什么这些信息对您不起作用 。 根据我以前的电子工程师的经验,我可以负责任地声明99%的问题如下:

1.接触不良。 由于“ Arduino”屏蔽层意味着通过“父母”类型的导线而不是通过焊接相互切换,因此某些地方的某些地方经常会消失。 看看吧。 正如他们所说,确实,电子学是接触科学。

2.电源问题。 在需要3.3的情况下,请勿提供5伏电源。 有时烟雾会从ESP8266发出。 虽然,另一方面,它可以毫无问题地从五伏设备中提取逻辑信号。

3.功率不足的问题。 ESP8266具有邪恶的性质,有时会消耗将近300毫安,尽管在此之前它可以满足30毫安。 因此,脆弱的稳定器3.3伏板“ Arduino”,您可以将其加起来等于零,将其连接起来,立即沉入微观值。 而且您不明白为什么它起作用了,然后就不起作用了。

4.对结论的困惑。 始终检查哪些信号到达何处。 RXD接收器必须连接到TXD发送器,TXD必须连接到RXD,但是MOSI必须连接到MOSI,MISO必须连接到MISO,依此类推。

5.不要依赖ESP8266中的在线上拉电阻,请始终通过5-10千欧姆的外部电阻(而不只是跳线)将引线拉至零或电源。 否则,您充其量只能获得前所未有的电流消耗,然后闻到燃烧的塑料令人不愉快的气味。

6.软件浅滩。 由于针对个人用户的软件是由相同的爱好者编写的,因此在更新相同固件的版本时,固件本身的故障和错误会定期出现。 通过在相关论坛中进行爬网(有时甚至是英文)来对待它。 一些同志甚至声称,ESP芯片本身就像圣彼得堡的天气一样潮湿,但另一方面,也有人认为自2014年(首次发布之年)以来,这种情况已经大大改善了(与天气不同)。

7.神秘故障。 这是一种罕见的但令人费解的现象。 例如,我没有远程“ Arduino”设备。 相反,它发生了,但是有错误。 但是,如果连接了编程器的电缆(但没有编程器本身),则不会出错。 我对自己说:“ AHA”,并在数据传输引脚和同步引脚之间焊接了一个15 pF的电容器。 一切正常。 但是那天被杀了。

因此,让我们从最简单的开始。 我们有一个在中国制造的机械臂MechArm(但不是Howard Volovits组装的)和一台带有Windows的个人计算机。 任务是远程刷新程序并从计算机进行管理。

对于控制控制器,我们用一条可爱的微型ATmega328P石头制成的Arduino Nano围巾。 将该板完全推入机械臂。

现在我们决定如何编程。 最适合远程固件的三种主要方法是:通过SPI接口,通过内置的引导程序,通过JTAG端口。

最简单的选项当然是内置的引导加载程序(bootloader)。 这是在FLASH中预注册的存储器,该程序会根据某种协议(例如,使用最简单的UART)接收代码,并使用特殊命令将其写入已加载程序的位置。 例如,这适用于引导加载程序ARDUINO IDE。 复位或启动后,引导加载程序会等待一段时间以接收数据,如果不等待,它将从零地址开始执行程序。 如果数据到达,他将其写入程序部分。 下次重置后,下载的程序开始运行。 详细地说,也许我的描述不准确,但本质就是这样。 结果,我们只需要三个输出即可进行编程:RTD接收器,RESET复位和GND地。 通常,TRD发射机也用于验证录制的节目,但是对于简单的演示应用程序(不适用于核电站),可以省略验证。

加载程序本身是用汇编语言编写的,在AVR的数据表中有一些简单加载程序的示例。 如果现有的引导加载程序位于公共领域,则可以对其进行挖掘;如果已知其可用的协议,则可以以现成的形式使用它。 唯一需要注意的是,您需要通过闪烁特殊的保险丝位来将AVR配置为特殊模式,这是由普通程序员完成的,然后您还可以将引导加载程序缝入微控制器的存储器中(也就是说,一次没有程序员就无法做)。

第二种选择是通过串行SPI接口进行编程。 这里没有内部引导程序,但是我们通过发送特殊命令,然后通过上述接口发送数据来进行编程。 这里我们有一个外部引导程序,但是您仍然需要编写它。 除了RESET和GND外,还有四个MOSI输出用于传输,MISO-数据,SLK同步,CS-芯片选择。 但通常,您也可以删除MISO和CS。 数据将仅被接受(然后将不对程序进行验证),而我们只有一个晶体。

每种方法都有其优缺点(而且由于人类寿命短,我根本没有考虑过JTAG)。 但是最后,我倾向于SPI,因为我懒得用汇编语言编写,但是我没有找到开放的现成的引导加载程序(我看起来不太好)。

如前所述,为了建立无线通道,我选择了目前广为人知的ESP8266芯片-一个微控制器,或者说是中国制造商乐鑫(Espressif)具有Wi-Fi接口的整个SoC(片上系统)。 除了Wi-Fi,它还具有从外部闪存执行程序的能力。 专门针对我的项目,我选择了ESP8266-07,它带有512 KB的内存。


通常,任何ESP8266都适合用于实现SPI的额外支脚。 因此,最简单的ESP8266-01不适合我们,因为它的输入/输出端口支脚很少。 但是,另一方面,价格差不到一百卢布,而且价格平等。 嗯,带有ESP的大型调试板(为了方便起见,其中有一堆外围设备)也不适合我们,因为它们无法进入我们想要将其推入机械臂的位置。

这个想法的全球本质如下。 加载到微控制器中的程序主体通过WI-FI(在家庭网络内)从计算机无线传输到ESP。 并且已经使用SPI接口通过有线方式将ESP将此程序直接写入微控制器的FLASH存储器中。 然后自然重置它并允许加载的程序执行。 此外,ESP必须具有一个独立的单元,该单元还可以管理与微控制器的数据交换,因为我们不仅要编程,还要与之交换数据。 特别是,对于带有MechArm的项目,在记录程序后,我们仍会发送伺服控制信号以使此手运动。 因此,在ESP本身上,建议我们提高用于程序传输的TCP服务器和用于控制MechArm的UDP服务器。 因此,这些服务器会加入家庭网络,并仔细听是否有人要上载新代码到MechaArm或向他人挥手。

因此,我在网上发现该固件已允许您通过空中编程AVR,但主要问题是该固件无法用于其他用途。 编程后,我们想与AVR进行远程通信。

我们将使用什么软件:

对于PC,我用IntelliJ IDEA JAVA编写了所有内容。 但基本上,您可以做任何事情,对我们而言,最主要的是编写一个客户端,该客户端将发送用于将AVR刷新到ESP8266的程序。

我自己用C语言编写ATMEL STUDIO的 AVR程序,很少用汇编器编写。 我原则上不使用Arduino草图,几乎所有必要的库都是在一两个小时内编写的,并且对它的工作有充分的了解。 我尝试过草图,但是只要您在AVR上没有操作系统,草图就会从朋友手中夺走外围设备,并经常失败。 是的,与ATMEL STUDIO相比,Arduino IDE本身当然是非常原始的东西。 但是这里的问题当然是有争议的,对于人文学科和学童来说,使用草图可能会更有趣,更轻松。

为了编程ESP8266,我使用了NodeMCU固件,并用Lua编写了程序。 不,我想用Java和C编写,但是ESP中没有。 语作为应用到我们的任务中并不难,要掌握几个琐碎的小知识。 实际上,为了下载程序并在ESP上进行调试,我选择了IDE ESPlorer 。 免费的国内产品(但是您可以将其捐赠给作者),当然不能与上述媒体进行比较,但是就像礼物马所说的那样。但是要使用ESPlorer并在LUA上进行写入,我们首先需要将ESP8266芯片中的基本固件(由制造商提供)更改为新的。 NODE MCU PyFlasher程序将帮助我们开展这项事业。 我的意思是,它将有助于重新刷新它。 我们将自己创建固件并将其交给创建者网站NodeMCU ,您可以在此处了解有关此过程的更多信息

一切都非常容易理解。 我们将SPI支持和位操作添加到基本库中(在LUA中,在我们的情况下,位操作被重载并且对它们无用)。 您不应该在固件中增加很多库,因为ESP8266上存在各种软件,因此只剩下很少的内存,有些可悲的20 kB。

当然,您可以只购买成品固件,其中许多已经在Internet上闲逛了,但我不建议这样做。 至少因为其中一些不支持位操作(我们需要它们),并且没有通过SPI调节数据传输速率。
因此,默认情况下,它们以40 MHz的速度除以某个小系数进行传输,因此AVR没有时间消化它们。

谁懒得创建固件,可以从云中下载我的固件。

现在我们有了固件,需要将其加载到ESP8266中,而不是基本版本中。 为此,我们需要一个简单的USB适配器-UART。

我们将TXD引脚连接到RXD,将RXD连接到TXD,我们建立了公共接地,但是看起来好像不使用适配器上方便的3.3 V电源输出。 在大多数情况下,ESP8266会完全耗尽它。 因此,我们将其单独喂入。 然后,我们将ESP置于编程模式(如果有人忘记的话,将GP0置于地面)并运行NODE MCU PyFlasher



重要的是,不要忘记擦除闪存(是的,擦除所有数据),否则,根据固件版本,编程后,内存中可能会残留不必要的垃圾,这又将在进一步工作时将垃圾倒入控制台。 在此之前,我使用的软件无法事先擦除内存,但由于无济于事,我感到非常痛苦。 棺材刚刚打开,只有NODE MCU创作者的英语论坛中的真相。

获得必要的固件后,我们现在可以使用来自NODE MCU的非常方便的API编写和调试LUA程序(也有MicroPython,但我没有使用它)。 我们启动了已经提到的ESPlorer。



我们还将其配置为与ESP8266一起使用,并设置串行连接的参数。 一切都很简单,并且在Internet上反复声明。

现在我们在LUA上编写程序,然后将其上传到ESP8266:

Lua引导加载程序用于AVR写入ESP8266
<b>function InstrProgrammingEnable () -- instruction for MC "enable programming"</b> p=0 while p<31 do p=p+1 pin=8 gpio.write(pin, gpio.LOW) spi.send(1, 0xAC,0x53) read = spi.recv( 1, 8) spi.send(1,0,0) gpio.write(pin, gpio.HIGH) if (string.byte(read)== 83) then print("connection established") p=33 if(p==31) then print("no connection") end end end end <b>function ProgrammingDisable ()</b> pin=2--END OF ESET FOR MK gpio.mode(pin, gpio.INPUT) pin=8 gpio.mode(pin, gpio.INPUT) pin=5--CLK MASTER for SPI gpio.mode(pin, gpio.INPUT) pin=6--MISO MASTER for SPI gpio.mode(pin, gpio.INPUT) pin=7--MOSI MASTER for SPI gpio.mode(pin, gpio.INPUT) end <b>function ProgrammingEnable ()</b> pin=2-- RESET FOR MK gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.LOW) pin=2--POZITIV FOR 4MSEC RESET FOR MK gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.HIGH) tmr.delay(4) gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.LOW) tmr.delay(25000) end <b>function InstrFlashErase() </b> pin=8 gpio.write(pin, gpio.LOW) spi.send(1,0xAC,0x80,0,0) gpio.write(pin, gpio.HIGH) tmr.delay(15000) pin=2--RESET FOR MK gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.HIGH) tmr.delay(20000) gpio.write(pin, gpio.LOW) print( "FLASH is erased") InstrProgrammingEnable () end <b>function InstrStorePAGE(H, address, data)</b> pin=8 gpio.write(pin, gpio.LOW) spi.send(1,H,0,address,data) gpio.write(pin, gpio.HIGH) tmr.delay(500) end <b>function InstrWriteFLASH(page_address_low,page_address_high)</b> pin=8 gpio.write(pin, gpio.LOW) spi.send(1,0x4C,page_address_high,page_address_low,0) gpio.write(pin, gpio.HIGH) tmr.delay(5000)--        end <b>function Programming (payload)</b> pin=8--CS MASTER for SPI gpio.mode(pin, gpio.OUTPUT, gpio.PULLUP) pin=4--LED LIGHTS ON LOW gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.LOW) print(string.len(payload)) page_count = 7 --  1  for k =0 ,page_count ,1 do--quantity of pages for i=0 , 127, 2 do-- -1 address = i/2 data=payload:byte(i+1+128*k) if data == nil then data = 0xff end InstrStorePAGE(0x40,address,data) -- tmr.delay(100)-- otherwise not in time write data =payload:byte(i+1+1+128*k) if data == nil then data = 0xff end InstrStorePAGE(0x48,address,data) -- tmr.delay(100) end page_address_low=bit.band(k ,3)*64 -- 3   11 page_address_high=k/4+frame1024*2 tmr.delay(1000) InstrWriteFLASH(page_address_low,page_address_high) tmr.wdclr() end pin=4--LED gpio.mode(pin, gpio.OUTPUT) gpio.write(pin, gpio.HIGH) end <b>--MAIN BLOCK</b> wifi.setmode(wifi.STATION) --wifi.sta.config(" ","") -- set SSID and password of your access point station_cfg={} tmr.delay(30000) station_cfg.ssid=" " tmr.delay(30000) station_cfg.pwd="" tmr.delay(30000) wifi.sta.config(station_cfg) tmr.delay(30000) wifi.sta.connect() tmr.delay(1000000) print(wifi.sta.status()) print(wifi.sta.getip()) while ( wifi.sta.status()~=1 ) do if( wifi.sta.status()==5) then break end end sv=net.createServer(net.TCP,30) tmr.delay(100) print("SERVER READY") sv:listen(4000,function(c) c:on("receive", function(c, payload) print(payload) if (payload =="program\r\n") then c:send("ready\r\n") print("ready for program\r\n") spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, spi.DATABITS_8,320,spi.FULLDUPLEX) ProgrammingEnable () tmr.delay(100) InstrProgrammingEnable () tmr.delay(100) InstrFlashErase() tmr.delay(100) frame1024=0--    st=net.createServer(net.TCP,30) st:listen(4001,function(c) c:on("receive", function(c, payload) tmr.wdclr() Programming (payload) frame1024=frame1024+1 end) end) end if (payload =="data\r\n") then c:send("ready\r\n") print("ready for data\r\n") srv=net.createServer(net.UDP) tmr.delay(1000) pin=10 gpio.write(pin, gpio.HIGH) uart.setup(0,9600,8,0,1,0) srv:listen(5000) srv:on("receive", function(srv, pl) pl=pl*1 --print(pl) uart.write(0,pl) tmr.wdclr() end) end if (payload =="stop\r\n") then if(st~=nil) then st:close() frame1024=0 ProgrammingDisable () print("stop program") end if(srv~=nil) then srv:close() print("stop data") end end end) end) end) 



相关功能执行以下操作的地方:

函数InstrProgrammingEnable() -通过SPI发送的特殊命令将微控制器置于编程模式。

函数ProgrammingEnable() -在开始编程之前仅将AVR复位25 ms

函数ProgrammingDisable() -编程结束后,我们将ESP8266中的SPI输出转换为无效状态,以使它们在微控制器上执行代码时不会干扰我们(突然在此处使用)

函数InstrFlashErase() -我们在编程之前先擦除微控制器上的闪存。 为何不需要对此进行解释。

函数InstrStorePAGE(H,地址,数据) -此命令将程序的字节写入微控制器的内部缓冲区。 但这不是闪存记录本身,因为闪存是在此处逐页写入128字节。

函数InstrWriteFLASH(page_address_low,page_address_high) -但这是闪存记录,需要时间,请注意5,000μs的时间延迟。

函数编程(有效负载) -使用上述函数的最大,最重要的函数。 它以1024字节的块的形式接收所传输的程序,将其分成字节并形成它们的地址,然后将其发送到内部缓冲区中的微控制器,并每128字节初始化一次闪存记录。 然后,他获取下一个千字节的代码并重复执行该操作,自然会在地址中加上偏移量,以便进一步写入而不覆盖已记录的代码。 起初,我尝试转发整个程序,但是当我在ESP8266中超过6 KB时,可用内存刚刚结束,它崩溃了。 一千字节被认为是最方便的单位,因为它被整齐地划分为多个部分,并且可以通过TCP方便地传输(我们仍然需要从计算机上获取它)。 您也知道,也不需要更大的大小,在当前版本中,TCP将传输的数据包限制为1500或字节(但出于某种原因,1440已传输给我)。

似乎没有什么复杂的,但必须克服一些陷阱。

接下来是主块。 在其中:

我们已在无线网络中注册。

首先,我们创建一个侦听三个命令的TCP服务器:

1.“编程”(我们将编程)

2.“数据”(我们将更改数据),

3.“停止”(我们停止一切)。

如果进行编程,我们首先初始化SPI,然后创建另一个TCP服务器,该服务器捕获每千字节的数据(固件代码),并为其调用微控制器编程功能。 我知道创建第二台服务器看起来很愚蠢,但这是必须的,因为本地API仅支持创建一个套接字,并且我们需要将“ program”和“ data”命令与传输的数据分开,因为从外观上看它们没有区别,有字节和这是字节。

如果我们不想编程,而是要交换数据,在我们的情况下将它们发送到微控制器,那么我们首先要通过TCP发送字符串“ data”。 响应于此,将已经创建一个UDP服务器(我提醒您,我们无需用机械手进行动态管理,并且不需要延迟TCP数据包的形成,实际上可以发送一个字节作为整个TCP帧泛音)。 UDP数据报将很小并且会很快形成。

UART初始化后,已经通过TXD导线将无线接收的每个字节发送到微控制器,如果在那里更新了相应的程序,则微控制器必须接受它。 另一个方向的数据交换也不难组织,但我尚未实现它。

好了,通过“停止”命令,上述服务器(除了第一个服务器)关闭了连接,主服务器再次进入了“程序”和“数据”命令的等待状态。

由于SPI接口是在ESP8266中以编程方式进行仿真的,因此信号CS,CLK,MISO,MOSI,RESET(用于AVR)的I / O端口(可用于AVR)可以使用,但不能使用我的引导加载程序中指示的那些接口。 另外,事实证明,在这种情况下,CS和MISO原则上也可以中断,没有它们也可以工作。 好了,ESP8266板内置的LED上使用了一个引脚,该引脚有时会闪烁并表明程序仍在运行。

未完成记录错误检查(除了对AVR的第一个请求,但此信息仅显示在控制台上),未编程EEPROM,未缝制32 KB以上的内容-简而言之,仍有工作要做。 SPI的交换速度约为115 Kbit,几秒钟后所有内容都会闪烁,就像使用常规串行编程器(如ISP500)一样。

获取代码,输入您的网络和密码,在ESplorer上进行编译,将其称为“ init”(以便在重新启动时启动)并将其发送给ESP8266。 它应该工作。 至少在作为无线程序员的意义上。

现在,我们将与管理方-个人计算机打交道。

实际上,我们需要将在ATMEL STUDIO环境中编写的程序转换成的HEX文件,然后通过WI-FI将其发送到我们已知的套接字端口(在本例中为4000)。 不足之处在于,我们需要一个二进制BIN文件进行传输,而ATMEL STUDIO只用十六进制来取悦我们。 有两种出路; 或使用特殊的程序转换器(例如WinHex)将其转换为BIN格式,或在程序中自行执行。 我还没有做,但这似乎并不困难,您需要切断标题并做其他事情。

结果,我用JAVA编写了bootloader程序(主要是因为我不知道如何做其他事情),它在简单漂亮且免费的IntelliJ IDEA环境中工作。 它创建一个TCP客户端,以搜索ESP8266上运行的服务器。 如果找到,它将与他联系并向他发送位于该地址的文件。 代码如下。

基于PC的JAVA文件下载器
 import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.List; public class Net { <b> public static void main(String args[]) { new Http_client(4000); }</b> } class Http_client extends Thread { int port; String s; String Greetings_from_S; Http_client(int port){ this.port = port; start(); } public void run() { //192.168.1.113 -  ESP8266   .  ,      //    ,    try (Socket socket = new Socket("192.168.1.113", port)) { PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true); pw.println("program");// Greetings with SERVER System.out.println("program"); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); Greetings_from_S = br.readLine(); System.out.println(Greetings_from_S); if(Greetings_from_S.equals("ready")) { try { File file = new File("d:BlinkOUT.bin");//    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); byte [] data = new byte[bis.available()]; bis.read(data); byte [] data_buffer = new byte[1024]; int frames = data.length/1024; System.out.println(frames); int residy = data.length%1024; for (int i = 0; i < frames;i++) { for (int k = 0; k< (1024); k++) { data_buffer[k] = data[k+1024*(i)]; } sendingChunk(data_buffer); } byte [] data_buffer2= new byte[residy]; for (int i = 0; i < residy;i++) { data_buffer2[i] = data[i+1024*(frames)]; } sendingChunk(data_buffer2); pw.println("stop");// System.out.println("stop program"); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } } public void sendingChunk (byte [] data_buffer){ try (Socket socket = new Socket("192.168.1.113", 4001)){ BufferedOutputStream bos = new BufferedOutputStream((socket.getOutputStream())); bos.write(data_buffer); bos.flush(); System.out.println(data_buffer.length); } catch (Exception e) { System.out.println(e); } } } 



当然,在这里,太多的伤口缠起来了,原则上不需要各种准备。 如果建立了TCP连接,则建立连接。 唯一的问题是,尽管我明确指出了大小,但我并不需要以我真正需要的任何方式以1024字节的片段发送文件。 显然,JAVA无法访问某种最终缓冲区,它发送他想要的大小的数据包,这对于接收方是完全不可接受的。 起初,我尝试延迟,以使缓冲区不厌倦等待下一个片段并按原样发送它。 但是,延迟在到达10秒时开始起作用,对于传输1 KB来说,这似乎有点太大了。

但是后来我注意到,由于某种原因,第一件作品总是很顺利,有人订购了它,而从第二件作品开始就已经出现了不可预测的酒渣。 因此,我让客户端打开了连接,以1024字节发送了一部分代码,然后关闭了连接。 依此类推,直到发送完整个文件。 一切正常。

唯一要开始的就是在计算机上安装JAVA运行时。 但是我通常从IntelliJ IDEA开始,因为您始终可以看到控制台中正在发生的事情(但是这里您需要一个JAVA环境)。 虽然,当然,您需要精巧地制作GUI。 也就是说,文件路径所在的窗口,更改窗口中端口号的能力以及其他必要的东西。 并将所有这些收集为可执行文件。

正如Koroviev曾经说过的,Tapericha让我们回到了公民身上,实际上是回到了刚开始提到的机械臂MechArm。 现在,我们有机会对其进行远程编程,然后再对其进行管理。 让我们继续进行微控制器方面的控制程序。

在这种情况下,我们需要控制四个伺服器。 这些是。

此类驱动器通过占空比为2-4%的20毫秒(50 Hz)周期的矩形脉冲进行控制。 也就是说,2%是一个方向的完整转弯,而另一个方向是4%。 该任务仅适用于AVR中的集成PWM。



一个伺服驱动器用于左右移动。 第二个人-来自自己; 第三上下 第四个是爪子本身,必须压缩和扩展。 一切都用C编写,并编译为ATMEL STUDIO中的HEX文件。 有点奇怪的程序是由于这样的事实:最初,手是由绑在与微控制器的电线绑在一起的键盘上控制的。 但是昨天的电报,我们必须进一步发展。

您当然可以使用“ ARDUINO”中的伺服器草图,但我不喜欢它们。 写自己更有趣。 此外,当PWM依次切换到每个伺服器时,所有四个伺服器应同时工作,而不应处于多路复用模式。 因为没有人取消重力,并且如果控制脉冲不再到达相应的伺服驱动器,抬起的肢体将立即下降。 我不确定“ ARDUINO”草图是否可以同时运行四个伺服系统。 但是我们自己可以很好地编写满足必要要求的程序。 通常,在没有将羊羔与山羊分开的操作系统的情况下,使用草图竞争微控制器的外围设备(而且我们甚至不知道提前选择哪些草图)实在太麻烦了。

这是我们使用ESP8266-07写到Arduino Nano的代码本身。

用于控制微控制器AVRmega328P的MechArm的程序
 #define F_CPU 16000000 #include <avr/io.h> #include <stdint.h>//    #include <avr/interrupt.h> #include <math.h> //  #include <stdio.h> // - #include <avr/eeprom.h> #include <setjmp.h> #include <stdlib.h> //  #define UART_BAUD_RATE 115200 //  1    20 #define COUNTER1_OFF TCCR1B=0b00000000 // CS02 CS01 CS00 - 000 - ; 001  ; 010 c  8; 011 -64; 100 -256; 101 -1024 #define COUNTER1_ON TCCR1B=0b00000011 //  0       0  1 #define COUNTER0_OFF TCCR0B=0b00000000 // CS02 CS01 CS00 - 000 - ; 001  ; 010 c  8; 011 -64; 100 -256; 101 -1024 #define COUNTER0_ON TCCR0B=0b00000100 //  2       B2(PD6)  3(PD7) #define COUNTER2_OFF TCCR2B=0b00000000 // CS02 CS01 CS00 - 000 - ; 001  ; 010 c  8; 011 -64; 100 -256; 101 -1024 #define COUNTER2_ON TCCR2B=0b00000110 volatile uint16_t period_20ms; volatile uint8_t State_of_keyboard; volatile uint8_t start_position [6]; volatile int8_t number_servo; ISR(USART_RX_vect)//   UART { State_of_keyboard=UDR0; return; } ISR(TIMER0_COMPA_vect)//  0    { PORTB &=~(1<<0); TIMSK0&=~(1<<OCIE0A); TIFR0 |=(1<<OCF0A); return; } ISR(TIMER0_COMPB_vect) //  1    { PORTB &=~(1<<1); TIFR0 |=(1<<OCF0B); TIMSK0 &=~(1<<OCIE0B); return; } ISR(TIMER2_COMPA_vect)//  2(PD6)    { PORTD &=~(1<<6); TIFR2 |=(1<<OCF2A); TIMSK2 &=~(1<<OCIE2A); return; } ISR(TIMER2_COMPB_vect)//  3(PD7)    { PORTD &=~(1<<7); TIFR2 |=(1<<OCF2B); TIMSK2 &=~(1<<OCIE2B); return; } ISR(TIMER1_OVF_vect){//   20      COUNTER1_OFF; COUNTER0_OFF; COUNTER2_OFF; TIFR0 |=(1<<OCF0A); TIFR0 |=(1<<OCF0B); TIFR2 |=(1<<OCF2A); TIFR2 |=(1<<OCF2B); TIFR1 |=(1<<TOV1); PORTB |=(1<<0)|(1<<1); PORTD |=(1<<6)|(1<<7); TCNT1 = period_20ms; //  20  TCNT0 = 0; TCNT2 = 0; TIMSK0|=(1<<OCIE0A)|(1<<OCIE0B); TIMSK2|=(1<<OCIE2A)|(1<<OCIE2B); OCR0A=start_position[1];//  0  0 OCR0B=start_position[2];//  0  1 OCR2A=start_position[3];//  0  2 OCR2B=start_position[4];//  0  3 COUNTER1_ON; COUNTER2_ON; COUNTER0_ON; return; } void time_delay(long i) { cli();sei(); long k; i*=2000; for(k=0;k<i;k++){;;}; } void timer_counter0_1_2_INIT()//   0,1,2 { // 1 TCCR1A &=~(1<<COM1A0)|~(1<<COM1A1)|~(1<<COM1B0)|~(1<<COM1B1);//   TCCR1A &=~(1<<WGM10)|~(1<<WGM11); TCCR1B &=~(1<<WGM12)|~(1<<WGM13);//    period_20ms=60575; TCNT1 = period_20ms; TIMSK1|=(1<<TOIE1);//|    //TIFR0   TOV0 // 0 TCCR0A &=~(1<<COM0A0)|~(1<<COM0A1)|~(1<<COM0B0)|~(1<<COM0B1);//   TCCR0A &=~(1<<WGM00)|~(1<<WGM01); TCCR0B &=~(1<<WGM02);//    // 2 TCCR2A &=~(1<<COM2A0)|~(1<<COM2A1)|~(1<<COM2B0)|~(1<<COM2B1);//   TCCR2A &=~(1<<WGM20)|~(1<<WGM21); TCCR2B &=~(1<<WGM22);//    COUNTER1_ON; } void servo_reset() { start_position[1]=97;//  0  0 start_position[2]=70;//  0  1 start_position[3]=92;//  0  2 start_position[4]=124; // 0  3 COUNTER1_ON; time_delay(100); } void servo_go( int8_t moven, uint8_t servo_position_max, uint8_t servo_position_min)// { switch (moven){ case 1: start_position[number_servo]++; if(start_position[number_servo]==servo_position_max){start_position[number_servo]--;};//  +90  break; case 2: start_position[number_servo]--; if(start_position[number_servo]==servo_position_min){start_position[number_servo]++;};//6  -90  break; }; time_delay(20); return; } //PORTB-0,1, PORTD - 6,7 -  , 8-  COUNTER 0 int main(void) { uint8_t servo_positionmin=0, servo_positionmax=0; int8_t const servo_position1max = 122, servo_position1min=58; //  int8_t const servo_position2max = 120, servo_position2min=36;// int8_t const servo_position3max = 125, servo_position3min=68;// int8_t const servo_position4max = 129, servo_position4min=108;// 128 108 sei(); DDRD = 0B11000010; //   D2-D5  , D0  RX, D1  TX, D6 D7   3  4 PORTD = 0B00111110; //     DDRB |=(1<<0)|(1<<1);//         PORTB &=(~1<<0)|(~1<<1); UCSR0A=0;// UART UCSR0B=0b10010000; UCSR0C=0b00000110; UBRR0L=103;//  115200 UBRR0H=0; timer_counter0_1_2_INIT(); servo_reset(); PORTB |=(1<<5); while (1) { switch (State_of_keyboard) { case 1://   1 PD0(PB0) number_servo=1; servo_positionmin=servo_position1min; servo_positionmax=servo_position1max; break; case 2: //   1 PD0(PB0) number_servo=1; servo_positionmin=servo_position1min; servo_positionmax=servo_position1max; break; case 5: number_servo=2; //   2 PD1(PB1) servo_positionmin=servo_position2min; servo_positionmax=servo_position2max; break; case 6: number_servo=2; //   2 PD1(PB1) servo_positionmin=servo_position2min; servo_positionmax=servo_position2max; break; case 7: number_servo=3;//   3 PD6 servo_positionmin=servo_position3min; servo_positionmax=servo_position3max; break; case 8: number_servo=3;//   3 PD6 servo_positionmin=servo_position3min; servo_positionmax=servo_position3max; break; case 3: number_servo=4; //   4 PD7 servo_positionmin=servo_position4min; servo_positionmax=servo_position4max; break;//  case 4: number_servo=4; //   4 PD7 servo_positionmin=servo_position4min; servo_positionmax=servo_position4max; break;//  // c    - ,       4-  //        ,         } if(State_of_keyboard==1||State_of_keyboard==3||State_of_keyboard==5||State_of_keyboard==7) { servo_go(1,servo_positionmax,servo_positionmin);// } if(State_of_keyboard==2||State_of_keyboard==4||State_of_keyboard==6||State_of_keyboard==8) //     { servo_go(2,servo_positionmax,servo_positionmin);// } time_delay(20); } } 



从文本和注释中可以清楚地看出该程序的本质。 我们将T1计数器用于20 ms的示例周期,并使用T0,T2计数器向I / O端口的四行发送PWM信号,因为这两个计数器中的每一个都可以在两个设备上工作。
该程序通过加载计数寄存器OCR0A,OCR0B,OCR2A,OCR2B来设置伺服的初始位置。 由于我们并不总是需要180度跨度,因此还引入了约束常数。 而且,由于来自UART的中断,程序会捕获ESP8266发送的数字(从1到8),并将其转换为相应伺服的命令。 有四个驱动器,每个驱动器在两个方向上工作,因此从1到8的整数就足够了。 一旦选择了编号,上述计数器寄存器的内容将递增或递减,分别改变控制脉冲的占空比和所选伺服驱动器的旋转角度。 我们未选择的那些驱动器将保留旧的旋转角度值(因为相应寄存器的内容虽然已更新,但未更改),并继续将机械臂保持在同一位置。

现在,我们只需要编写一个控制程序(对不起,就很抱歉),可以通过WI-FI直接从计算机控制机械手。
该代码也是用JAVA编写的,但是有点高贵。 出现了GUI,并具有编辑ESP8266的端口号和网络地址的功能。



从窗口可以清楚地看到发生了什么。 由于以下原因,我没有此处提供该程序文本(可在Github上找到 ):约有95%的体积是窗口创建和来自键盘的信号处理。 但是本质与以前的JAVA程序相同。 创建了一个客户端,只有UDP,它根据所按下的键在指定端口上的指定地址上发送一个1到8的数字。
或者,您可以立即从此处获取可执行文件。 对于带有Windows的64位计算机。 甚至不需要安装的JAVA环境。 一切都已被压缩到178 MB。

因此,对机械笔进行了组装,调试,并赠送给他的弟弟以纪念他的周年纪念日。 可以从另一个城市的Skype上用伏特加酒捡起塑料桩。 尽管对于“大爆炸理论”系列中的霍华德·沃洛维察的机械臂,她仍然遥不可及。

但是,在接下来的文章中(如果有兴趣的话),我们将能够通过手机进行管理,使用四轮机器人手推车进行同样的操作,并通过Internet上的手表服务器更新电子表中的时间。 然后,我们将旧的智能手机放在购物车上,并通过模式识别将其从视频驱动到神经网络,然后将控制信号发送给电机, 哦,已经有东西带给我了...

漂亮的ESP8266以及所有这些。
如果有人觉得这篇文章有趣,我会很高兴。

[1] ESP8266的管脚和规格
[2] 连接ESP8266。 快速入门。
[3] 通过云更新NodeMCU固件
[4] NODE MCU PyFlasher
[5] ESPlorer-ESP8266的IDE
[6] AVR的C编程
[7] 文章复习-“用C语言编程微控制器”
[8] NodeMCU API的描述
[9] Lua参考
[10] Lua脚本和模块
[11] IntelliJ IDEA
[12] 立即将Java下载到台式计算机!
[13] Atmel Studio

Source: https://habr.com/ru/post/zh-CN406559/


All Articles