并通过ESP8266传递确切时间的神圣信号。
第四部分
所以这一切都是巧合。 首先,我在Gytayms上看到了一篇有关步进电机控制的窗帘的文章。 我记得第二年,我有同样的发动机空转。 然后我的目光落在唱歌
碗上,唱歌
碗已经在架子上收集灰尘五年了。 然后各种聪明的想法开始浮现在我的脑海...
不,当然,有时根据我的心情,我拿着杯子拿了一段时间,并从中提取了各种迷人的声音,但这并不是我想要的。 我想并行执行一些操作,然后让杯子本身发出声音。 很明显,在一千年前,这将需要一个单独的
奴隶 ,三百年前,这将是一个巧妙的发条机构,现在……好吧,现在我们有了步进电机和Arduino ProMini板以及其他
非精密电子设备。 剩下的只是对
牛编码。 同时要确保这把西藏凿子同时击中准确的时间-是徒劳的,或者是产生了那么多精确的时间服务器的东西。 她知道如何让ESP8266与他们通信。
所以...
有一个带有拍板的唱歌碗。

必须使槌敲打在碗的边缘。 自动地。 还可以进行远程控制(和重新编程!)。 只是像旧表一样打磨时间,却具有现代精确性。
展望未来,我将展示最终发生的情况。 声音效果更好。
但是,让我们从顺序开始。 首先,我必须了解机械师的外观和工作方式。 对于电子产品和软件,我很平静-
撰写了三篇有关如何从远处处理arduinki的
文章 。
主要的运动元件是一个简单的28YBJ-48步进电机,我需要了解他是否可以操纵槌。

dvigun与arduino的联系并不困难,幸运的是,它与现成的ULN2003驱动程序一起出售。 仅需要提供5伏的独立电源和200-300 mA的备用电源,因为您肯定在arduino本身上没有足够的转换器。 然后,在任意四个数字端口(我取了PB1,PB2,PB3,PB4)上,我们以八张的数量传输以下位笔记本。
PORTB=0b00000010;
为了沿相反方向旋转,我们将转移相同的笔记本,但顺序相反。
PORTB=0b00010010; PORTB=0b00010000; PORTB=0b00011000; PORTB=0b00001000; PORTB=0b00001100; PORTB=0b00000100; PORTB=0b00000110; PORTB=0b00000010;
唯一出现的是数据传输的速度。 显然,电动机轴旋转得越频繁,但达到什么极限? 描述中有一个神秘的100 Hz频率,但这到底是什么意思-一个完整周期的周期或每个半字节分别?
在实验过程中,事实证明,显然是指精确四边形的变化频率。 在最大程度上,我设法将该频率加速到147 Hz,在此频率下,发动机轴旋转了一秒钟或两秒钟。 我没有精确测量它,但是您可以自己判断带有此齿轮箱的该型号在特殊敏捷性方面没有差异。 但是对于我的槌来说,原则上似乎是合适的。
但是,毕竟,不仅速度对我们很重要(或者甚至不很重要),而且发动机可以作用于工作流体的力也很大。 在专门针对该引擎的帖子中,有人争辩说他们不能用手停下来。 事实证明,轴本身是的,您不会停下来,但是实际上已经有一个10厘米长的小杠杆(我决定使用杠杆系统),即使受到很小的局部冲击,它也很容易停转。
因此,最初的最简单的选择是不通过螺栓连接到轴上的杠杆将打浆器推到悬架上,从而使碗状物受到冲击。 声音太弱了。 因此,我决定呼吁重力帮助(用谢尔顿·库珀的话说,这是非常“无情的bit子”)。 在该实施例中,杠杆将拍板相对于朝向地球中心的方向拉动直至大约30度的角度,然后从其上脱离并沿途到达碗中。 我真的很喜欢这种声音,无论是从下方还是邻居。 释放机构在安装在杠杆末端的磁铁上制成。 当他们上升时,重力击败了磁力门,并释放了锁。 然后,我做了一个有帮助的机械限位器-一根横杆,槌在接近上升点时与之相遇。 发动机继续旋转,拉杆被拉开并强行脱离了电磁锁。 在这里,发动机是靠重力来帮助的,因此,很少需要进行分离。
设计本身是根据埃菲尔铁塔儿童设计师的细节组装而成的。 我购买了很长时间,并定期将其零件用于我的手工艺品。 当然,这座塔并没有成为埃菲尔铁塔,但在我看来,这绝不是更糟的:)
一切工作正常,但是一分钱都没有-声音总是一样的力量。 对于时间跳动这是正常的,但是在自由模式下,我不仅希望听到不同的时间暂停,还希望听到不同强度的声音。 因此,有必要使用电磁体,这也是非常有用的。 常规的磁铁也很有用-一排五个小磁铁,我用它们作为阻尼器来抑制打碗后打浆机的振动。

最初,我将其安装在杠杆的末端,但设计笨拙且不可靠。 因此,电磁体移到了槌上。 他消耗了大约300 mA的电流,当然,不可能从arduino港口对其进行控制。 我不得不在一个小面包板上放置一个简单的晶体管键。

R1-560欧姆,VD1-1N4007,VT1-BD139
我将主要的电子零件组装在“ Arduino ProMini”和ESP8266-07模块上,根据我的
旧文章 ,我逐步完成了其固件。 结果,我像往常一样,有机会对arduino进行无线编程,并与它进行远程通信,交换数据,最终我成功地使用了它。 该图出于历史原因显示了Arduino Nano,但其连接没有什么不同。

因此,我想要什么,然后体现在程序代码中。
- 当您打开系统时,必须独立进入监视模式。
- 计算机(智能手机)上应该有一个用于更改操作模式和传输必要数据的应用程序。
- 模式应该简单-时钟,随机咕m和手动控制。
看起来,我是从最简单的事情开始的-几个小时。 的确,任何初学者的业余无线电都首先收集探针,然后收集电子钟。 然后,他想知道为什么这款手表每小时落后一分钟-理论上他似乎计算正确。
我已经有组装好的电子钟了。

现在,它们对我有用的主要功能是能够使用相同的ESP8266微电路从NTP服务器中拖出准确的时间,这是第一个也是最简单的化身。
几年前,我什至想发表一篇关于该主题的文章,但是在看到已经完成了多少次之后,我改变了主意。 他们终究会笑。 但是在这篇文章的背景下,对他们的工作进行分析是非常适当的。 正如我之前在文章中提到的,我使用LUA语言为ESP8266编写程序。 就是这样
因此,加载到该ESP模块中的代码就是这样。 uart.setup(0,9600,8,0,1,0) timezone = 3
底线很简单。 一次(或不一次),将调用设置UDP客户端的函数,该函数将调用确切的时间服务器并相应地询问确切的时间。 作为响应,服务器转储了32个字节,必须从中提取所需的4个字节的数据。 不幸的是,这不是分钟和小时,而是自1900年1月1日至今已经过的秒数。 因此,您将不得不使用各种复杂的操作从这些秒的四个字节中计算当前时间。
此外,一切都更加简单。 启动UART发送器,并将计算出的时间放入三个字节(小时,分钟和秒)中。
然后,我再次将这段代码插入到我的LUA引导程序(链接)中,就在已与WI-FI网络建立连接的位置,但是尚未开始进一步的工作。
在全视图下,它看起来像这样。 function InstrProgrammingEnable ()
当然,这与我的概念背道而驰,ESP8266是一个干净的无线网桥,而ATMEL微控制器完成了其余工作,但正如他们所说:“一次,不是……”。
因此,我们获得了最初的准确时间(直接从NTP服务器或通过计算机上的应用程序间接获得,这都没有关系),然后我们要自己考虑时间。 首先,没有什么可加载网络的;其次,从理论上讲,ATMEL允许您精确地计算秒数。 从理论上讲,是的。 但实际上,存在陷阱。
关于AVR上实时时钟的一小部分。从理论上讲,在AVR微控制器上构建手表并不复杂。 最狂热的设计师甚至为此将32768 Hz的时钟石英推入电路。 但是实际上,这不是必需的。 实际上,时钟石英对于形成秒级的中断并唤醒
休眠的 (注意)微控制器是必需的。 如果您的设备一直在工作,并且时钟通常在工作,那么将多余的石英放到现有的石英上,并在其下方放两个输入输出支脚是鲁re的。 可以使用已经存在的石英谐振器,那里的谐振频率为八或十六兆赫。 它的量化精度足以满足您的眼睛,并且将一秒计为计时器也很容易。
实际上,AVR微控制器已经具备了所有功能。 如您所知,输入时钟信号(例如8 MHz)到达所谓的预分频器的芯片内部(例如,arduino最常见的AVRmega328P),可以根据程序员的需求对其进行进一步分频(通常为8、64、256、1024)。 然后他到达某种计时器(例如T1),该计时器立即开始递增。
因此,我们取8 MHz除以256。我们分别得到计数器31250 Hz的时钟频率。 因此,由于T1计数器为16位数字,因此最多可以计数到65535,因此它将有时间在一秒钟内计数到31250。 我们需要什么。 另外,我们的定时器还有另一个非常有用的比较寄存器。 如果我们在此处写下数字31250,则在某些情况下,它将不断与T1计数器的内容进行比较,最后,当它等于T1时,计数器将生成一个中断信号,例如,保持秒数。
结果很方便,但不幸的是,并不完全准确。 对于我们的计数器而言,其量化误差为256 / 8,000,000,这在计算多达32微秒的一秒时会产生相当大的误差。 这导致每天2.8秒(0.000032 * 3600 * 24)的错误。
但是,如果我们将原始的8 MHz除以较小的数量(例如,除以64),则量化精度将提高4倍,达到8μs,并将产生的误差减少到每天0.33秒。 但是,不幸的是,在这种情况下,计数器将需要计数至125,000,并且十六位寄存器中的该数字将不会输入。 我们必须在比较寄存器中写入一个较小的数字(仍然可以容纳62500)),并在程序本身中添加一个循环,其中一秒的计数不是一个中断,而是两个中断。
但是我们采用了理想的情况,真正的石英谐振器(特别是安装在“中国制造”板上)会带给您很多惊喜。 不,总的来说,如果您看一下
数据表中的标准石英,那么从理论上讲并不是一切都那么糟糕。
如我们所见,中档石英的表现非常好。 他的调谐不稳定,为25 ppm(或25 ppm),也就是说,他将以不为8 MHz的频率谐振,但是会以例如0002 MHz的频率谐振,这将给我们带来多达2.1秒的误差每天。 但这是一个持续的错误,可以考虑。 这样的石英也可以在每度5-10 ppm的温度下漂浮,但是在设备的室内操作条件下,误差也很小。 仍然存在诸如老化的因素,但是它很少,并且将石英的特性改变为至少有些可见的状态,大概五年。 还是十。
在这里,我们很高兴地拿到一些arduino的中国克隆,例如ARDUINO UNO。

我们在其上运行一个用于计数时间的测试程序并开始使用它。 每小时积压一分钟? 容易! 第二个Arduino UNO板? 没有任何更好的。
以Arduino ProMini为例。

而且这里更好,是的。 错误减少到每小时二十秒。 好了,已经可以与杜鹃机械表媲美了。
我手头的最后一块板是Arduino Nano。

她是唯一一位表现出或多或少理智结果的人。
但是,即使有了这样的董事会,仅使用理论构造,您自己就会明白,您不会做得很精确。 需要对电路板进行配置,我叹了一口气,爬到示波器后面。
事实证明,Arduino开发板具有令人不快的功能-石英谐振器连接到的输出没有输出到针梳,尽管它对应于端口PB7。 就像,由于端口被石英占据,所以您不必执着。 而且对于表面安装和端子之间的间距为0.5 mm的情况,仅举起微控制器的脚就很难拿起示波器探头。 但是,即使加入右腿也没有给我任何好处。 要么是因为我戳错了,要么是在错误的地方戳了戳,这是因为石英谐振器的输出,也许不是时钟发生器的输出,通常是在微控制器内部。 因此,我不得不解决-将预分频器置于最小除法系数上-将其置为1,在比较寄存器中写入零,以使该中断立即产生抖动,并以特殊模式进入微控制器,在该特殊模式下,端口支路PB1随每个此类中断更改其逻辑状态。
逻辑上,当您打开Arduino Nano 16 MHz电路板时,此端口的输出端应出现8 MHz弯曲。
发生了什么事。 示波器显示的频率为8。002 31 MHz。 而且,最后一次放电有自己的寿命,我仍然不明白示波器的精度是否不足,或者晶体振荡器的频率是否像这样浮动。 更像是一秒钟。
那里的热稳定性也很好,没有气味。 如果您在板上呼吸(顺便说一句,容器是否仍然受潮?)或带起烙铁(距离较远),则石英可能会立即移动五十赫兹。 由于初始频率为16 MHz,因此这些测量值仍大致翻了一番。
因此,在arduino电路板(至少是中国血统的电路板)中,不可能在16 MHz的时钟频率下获得超过200 Hz的精度。 这使我们每天在这些板上组装的手表的终极精度不超过一秒钟。 那很好。
因为我已经在前面提到过,所以有中文版的Arduino UNO,总的来说,一切都不好。 而且它们很常见,因为它们既便宜又方便。
因此,它们的频率可能与声明的频率相差一百多赫兹! 即使是最差的中国石英,这甚至都不具有特色。
谜语始于在石英本身上写入12 MHz的事实! 在卖家的描述中也是如此。

但是没有可以确定的是12 MHz。 如果在板上启用了UART串行端口,您将看到自己。 由于UART调谐到该频率,您将无法工作。 并调谐到16 MHz的频率-将。 此外,我亲自观察了两个Arduino Uno板上的波形。 第一块板的发电机频率为15.8784 MHz,第二块板的频率为15.8661 MHz。
但是随后突然发现12 MHz石英与AVR微控制器没有直接关系,而是设计用来通过USB通过计算机操作串行端口(以下载草图)。 因此,关于内部没有石英,但RC链调谐不良的假设没有实现。 我们所需的石英尺寸要小得多,并且位于微控制器芯片旁边。 但是它很小,上面没有铭文。
结果,我仍然不知道如何以及在哪里找到如此糟糕质量的石英谐振器。但显然,在中国,一切皆有可能。而且我以某种方式想到了使用arduinki进行严肃交易的胆小鬼。好了,软件可以并且应该自己编写,但是如何处理模块本身的质量?显然,从电子元器件上看,中国人正在将所有最便宜,最不合格的元器件推入其中。AVR的唱歌碗计划。最后,在克服了精确计时的所有困难之后,我为Arduino ProMini编写了以下代码AVRmega328P微控制器的C程序 #define F_CPU 8000000 #include <avr/io.h> #include <avr/interrupt.h> #include <stdint.h>// #include <math.h> // #include <stdio.h> // - #include <avr/eeprom.h> #include <stdbool.h> #include <setjmp.h> #include <stdlib.h> volatile bool change_mode = false; volatile bool boom =false; volatile bool go_ahead=true; volatile bool go_back=false; volatile bool gerkon=false; volatile uint8_t latency=2;// latency = 1 volatile uint8_t hour=12; volatile uint8_t hour24=12;// 12 volatile uint8_t minute=0; volatile uint8_t secund=0; volatile uint8_t power=0; volatile uint8_t pause_between_boom=0; volatile uint8_t first_byte=0; volatile uint8_t second_byte=0; volatile uint8_t third_byte=0; volatile uint8_t firth_byte=0; volatile uint8_t fifth_byte=0; volatile uint8_t cSREG; ISR(USART_RX_vect) { // , // – , . if (first_byte==0) { first_byte=UDR0; change_mode=true; goto ret; } if (second_byte==0) { second_byte=UDR0; goto ret; } if (third_byte==0) { third_byte=UDR0; goto ret; } if (firth_byte==0) { firth_byte=UDR0; goto ret; } if (fifth_byte==0) { fifth_byte=UDR0; goto ret; } cSREG=UDR0; ret: return; } ISR(PCINT1_vect )//PC2 int 10 // { if (go_ahead) { UDR0=44; // 44 } if (go_back) { gerkon=true; } } ISR(TIMER1_COMPA_vect) { // secund++; if (secund ==60) { secund=0; minute++; if(minute==60) { minute=0; hour++; if(hour==12) { hour=1;// 12 } hour24++; if(hour24==24) { hour24=1; } boom=true; } } } void time_delay(long dell)// { long i; dell=dell*796;// 8 for(i=0;i<dell;i++){;;}; sei();// , - .WTF ?????????????????????? } void turn_onkward()// { uint8_t legnth=170;// ( 0 170) for(uint16_t i =0;i<=legnth;i++) { go_ahead=true; PORTB=0b00000010;// time_delay(latency); PORTB=0b00000110; time_delay(latency); PORTB=0b00000100; time_delay(latency); PORTB=0b00001100; time_delay(latency); PORTB=0b00001000; time_delay(latency); PORTB=0b00011000; time_delay(latency); PORTB=0b00010000; time_delay(latency); PORTB=0b00010010; time_delay(latency); if (i>140) { PORTD |=(1<<PORTD2);// , 1 - } } time_delay(100); go_ahead=false; } void turn_backward(uint8_t pause, uint8_t force_of_sound)// // // { uint8_t legnth=170;// ( 0 170) for(uint16_t i =0;i<=legnth;i++) { go_back=true; PORTB=0b00010010; time_delay(latency); PORTB=0b00010000; time_delay(latency); PORTB=0b00011000; time_delay(latency); PORTB=0b00001000; time_delay(latency); PORTB=0b00001100; time_delay(latency); PORTB=0b00000100; time_delay(latency); PORTB=0b00000110; time_delay(latency); PORTB=0b00000010;//16 ms , latency = 2 time_delay(latency); if (i==force_of_sound*17) { PORTD &=~(1<<PORTD2);// , 0 - } if (gerkon) { gerkon=false; break; } } time_delay(50); time_delay(pause*1000);// go_back=false; } void sound(uint8_t force,uint8_t pause) // 1 10 { turn_onkward(); turn_backward(pause,force); } int main(void) { sei(); // UART 9600 8 time_delay(2000);// , esp - UCSR0A=0; UCSR0B=0b10011000;// a UART UCSR0C=0b00000110; UBRR0L=51;// 8 9600 UART UBRR0H=0; // INT0 2 10 // PCICR|=(1<<PCIE1);// 14-8 PCMSK1|=(1<<PCINT10);// INT10 DDRC&=~(1<<PORTC2); DDRB=0b00111110;//PB1-PB4 , PB5 DDRD=0b00000100; // PD2 //SET INTERRUPT FROM TIMER1 AND SET TIMER1 GTCCR=0;//RESET PRESCALER TCCR1A=0;//I/O NORMAL WORK TCCR1C=0; TCCR1B=0B00001100;//1/256 PRESCALING AND CTC MODE TCNT1H=0;//RESET TIMER1 TCNT1L=0; TIMSK1=0B00000010;//SET COMPARE A INTERRUPT ENABLED OCR1AH=0x79;//SET TIME CONSTANT IN COMPARE REGISTER OCR1AL=0xa7;// 31143 7 972 608 TCCR0B=0b00000010;// 8 0 255 while (1) { begining: time_delay(1000); if (first_byte!=0) { UDR0=first_byte;// . (100,101,102) } if (first_byte==100)// ( NTP { hour=second_byte;// if (hour>12)// 12 (24 ) { hour=hour-12; } if (hour==0) { hour=12; } minute=third_byte;// secund=firth_byte;// power=fifth_byte;// first_byte=0;// second_byte=0; third_byte=0; firth_byte=0; fifth_byte=0; change_mode=false; goto clock_mode; } if (first_byte==101)// { power=second_byte; pause_between_boom=third_byte; first_byte=0; second_byte=0; third_byte=0; firth_byte=0; fifth_byte=0; change_mode=false; goto random_mode; } if (first_byte==102)// { power=second_byte; first_byte=0; second_byte=0; third_byte=0; firth_byte=0; fifth_byte=0; change_mode=false; goto hand_mode; } // , first_byte=0; second_byte=0; third_byte=0; firth_byte=0; fifth_byte=0; goto begining; clock_mode: while(change_mode==false) { if (boom)// { for(uint8_t i =0;i<hour;i++) { if ((hour24>21)|(hour24<10))// { sound(3,0);// 10 (), 0 boom=false; } else { sound(power,0);// 10 (), 0 boom=false; } } } } goto begining; random_mode: while(change_mode==false) { uint8_t random_power = TCNT0;// 1 uint8_t random_pause = TCNT1L;// 1 random_pause=TCNT0;// 1 random_power=random_power/25; if (random_power<5) { random_power=random_power+2;// } random_pause=(random_pause/25)+pause_between_boom; UDR0=random_pause; time_delay(100); sound(random_power,random_pause); } goto begining; hand_mode: sound(power,0); goto begining; } }
一切都很简单。初始化外设后,微控制器进入无限循环,等待UART命令。命令代码如下:100时钟模式101随机模式102手动模式。由于AVR与命令的来源无关,因此会触发ESP8266发出的第一个命令。如前所述,ESP模块紧贴网络,将确切的时间从NTP服务器拖动到微控制器。因此,首先,arduinka进入时钟节拍模式。通过中断T1计时器,可以对秒,分钟和小时进行计数,并在必要时调用函数来回设置步进电机,以节省时间。如果随着时间的推移,拉动阀瓣的杠杆开始相对于电机轴运动,则舌簧开关的中断将设置为相同的零点。计算机的应用程序。它完全基于相同的旧程序,而此处的外观仅变化。
一样,与AVR的通信通道通过HTTP和UDP连接建立。然后,如有必要,以UDP数据包的形式发送必要的控制命令和相关数据。当然,将控制和数据分离在不同的通道上会更正确,但是,首先,您需要在引导加载程序中编辑LUA代码,其次,这毫无意义,因为微控制器以及命令和数据是一个接一个地接收的,并且相同的UART。但是,是的,有时(很少)AVR使他们感到困惑。但这并不令人恐惧,因为如果微控制器无法识别该命令,则它将不会执行该命令,并且还会在计算机应用程序上对此进行一些修饰,从而提示您重复输入。该代码可在Github上获得。聚苯乙烯一般而言,藏族僧侣不仅在唱歌碗中用拍手敲打。如果您沿着碗的边缘小心地敲击槌,则不会产生任何敲击声,就会产生美妙的声音,在其下具有共鸣的神圣本质。但这对于Arduino来说确实是一个严峻的挑战。