编辑
我已成功完成上述任务.然而,现在唯一的问题是Arduino代码我已准确,正确地读取电压,但没有其他寄存器.我也可以写油门了.如果我调用不同数量的Serial.println()语句,其他寄存器上的读数会发生变化,在某些情况下,电压寄存器也会停止工作.这在我的代码中可以找到
Serial.print("Voltage: );
如果我打印出所有这些寄存器,答案就会改变.我无法弄清楚为什么会这样.
/* DEFINITIONS */ #include <math.h> /* FLOATS */ uint8_t command[5]; uint8_t response[3]; /* INTEGERS */ byte deviceId = 0x17; double throttleOut = 0; double voltage = 0; double rippleVoltage = 0; double current = 0; double power = 0; double throttle = 0; double pwm = 0; double rpm = 0; double temp = 0; double becVoltage = 0; double safeState = 0; double linkLiveEnabled = 0; double eStopStatus = 0; double rawNTC = 0; /* SETUP */ void setup() { Serial1.begin(115200); Serial.begin(115200); } void loop() { flushPort(); ReadWriteRegister(128,1000,true);//_throttleOut is 0[0%] to 65535[100%] voltage = ReadWriteRegister(0,false) / 2042.0 / 0.05; rippleVoltage = ReadWriteRegister(1,false) / 2042 / 0.25; current = ReadWriteRegister(2,false) / 204200 * 50; power = voltage * current; throttle = (ReadWriteRegister(3,false) / 2042.0 / 1.0); pwm = ReadWriteRegister(4,false) / 2042.0 / 3.996735; rpm = ReadWriteRegister(5,false) / 2042.0 / 4.89796E-5; int poleCount = 20;//Motor pole count rpm = rpm / (poleCount / 2); temp = ReadWriteRegister(6,false) / 2042.0 * 30.0; becVoltage = ReadWriteRegister(7,false) / 2042 / 0.25; safeState = ReadWriteRegister(26,false); linkLiveEnabled = ReadWriteRegister(25,false); eStopStatus = ReadWriteRegister(27,false) == 0 ? false : true; rawNTC = ReadWriteRegister(9,false) / 2042.0 / 0.01567091; rawNTC = 1.0 / (log(rawNTC * 10200.0 / (255.0 - rawNTC) / 10000.0 ) / 3455.0 + 1.0 / 298.0) - 273.0; Serial.print("Voltage: "); Serial.println(voltage); Serial.print("Current: "); Serial.println(current); } void flushPort() { command[0] = command[1] = command[2] = command[3] = command[4] = 0; Serial1.write(command,5); while (Serial1.available() > 0) { Serial1.read(); } } double ReadWriteRegister(int reg,int value,bool writeMode) { // Send read command command[0] = (byte)(0x80 | deviceId); command[1] = (byte)reg; command[2] = (byte)((value >> 8) & 0xFF); command[3] = (byte)(value & 0xFF); command[4] = (byte)(0 - command[0] - command[1] - command[2] - command[3]); Serial1.write(command,5); // Read response if(Serial1.available() == 3) { response[0] = (byte)Serial1.read(); response[1] = (byte)Serial1.read(); response[2] = (byte)Serial1.read(); } if ((byte)(response[0] + response[1] + response[2]) == 0) { return (double)((response[0] << 8) + (response[1])); } else { Serial.println("Error communicating with device!"); } }
编辑2
一些usb逻辑分析仪拍摄的照片.
[]
[]
[]
[]
[]
[]
[]
并且所有数据包都在这一个:
[]
也许这会有助于超时等.这就是我所拥有的所有信息:.
解决方法
看看这个片段:
Serial1.write(command,5); // Read response if(Serial1.available() == 3) {
write函数仅将命令放入输出缓冲区并开始发送第一个字符.它在所有字符传输之前返回.这需要500us!
然后,您将查看是否收到了3个字符的响应.但是命令还没有完成传输,你当然没有等待258us(3次86us).如果设备需要时间来执行命令,甚至可能需要更长的时间.
您必须做两件事:等待命令发送,并等待接收响应.试试这个:
Serial1.write(command,5); Serial1.flush(); // wait for command to go out // Wait for response to come back while (Serial1.available() < 3) ; // waitin'.... // Read response response[0] = (byte)Serial1.read(); response[1] = (byte)Serial1.read(); response[2] = (byte)Serial1.read();
这称为“阻塞”,因为Arduino在您等待响应时不会执行任何其他操作.
但是,如果遗漏了一个角色,你的程序可能会“挂起”,如果没有正确发送/接收第二个角色,则等待第四个角色(它会发生).所以你应该在while循环中放置500us超时:
// Wait for response uint32_t startTime = micros(); while ((Serial1.available() < 3) && (micros() - startTime < 500UL)) ; // waitin'...
…或更长时间,如果您知道设备响应的速度有多快.然后你可以确定你是否真的得到了回复:
完整程序更新(v2):
/* DEFINITIONS */ #include <math.h> /* INTEGERS */ byte deviceId = 0x17; uint8_t command[5]; uint8_t response[3]; /* FLOATS */ double throttleOut = 0.0; double voltage = 0.0; double rippleVoltage = 0.0; double current = 0.0; double power = 0.0; double throttle = 0.0; double pwm = 0.0; double rpm = 0.0; double temp = 0.0; double becVoltage = 0.0; uint8_t safeState = 0; uint8_t linkLiveEnabled = 0; bool eStopStatus = 0; double rawNTC = 0.0; /* SETUP */ void setup() { Serial1.begin(115200); Serial.begin(115200); Serial.println( F("---------------------------") ); // According to the spec,you can synchronize with the device by writing // five zeroes. Although I suspect this is mostly for the SPI and I2c // interfaces (not TTL-level RS-232),it won't hurt to do it here. Serial1.write( command,5 ); delay( 250 ); // ms while (Serial1.available()) Serial1.read(); // throw away // Set the throttle just once ReadWriteRegister(128,1000);//_throttleOut is 0[0%] to 65535[100%] } // For 12-bit A/D conversions,the range is 0..4096. Values at // the top and bottom are usually useless,so the value is limited // to 6..4090 and then shifted down to 0..4084. The middle of this // range will be the "1.0" value: 2042. Depending on what is being // measured,you still need to scale the result. const double ADC_FACTOR = 2042.0; void loop() { uint32_t scanTime = millis(); // mark time now so we can delay later voltage = ReadWriteRegister( 0,0 ) / ADC_FACTOR * 20.0; rippleVoltage = ReadWriteRegister( 1,0 ) / ADC_FACTOR * 4.0; current = ReadWriteRegister( 2,0 ) / ADC_FACTOR * 50.0; power = voltage * current; throttle = ReadWriteRegister( 3,0 ) / ADC_FACTOR * 1.0; pwm = ReadWriteRegister( 4,0 ) / ADC_FACTOR * 0.2502; rpm = ReadWriteRegister( 5,0 ) / ADC_FACTOR * 20416.66; const int poleCount = 20;//Motor pole count rpm = rpm / (poleCount / 2); temp = ReadWriteRegister( 6,0 ) / ADC_FACTOR * 30.0; becVoltage = ReadWriteRegister( 7,0 ) / ADC_FACTOR * 4.0; safeState = ReadWriteRegister( 26,0 ); linkLiveEnabled = ReadWriteRegister( 25,0 ); eStopStatus = ReadWriteRegister( 27,0 ); rawNTC = ReadWriteRegister( 9,0 ) / ADC_FACTOR * 63.1825; const double R0 = 1000.0; const double R2 = 10200.0; const double B = 3455.0; rawNTC = 1.0 / (log(rawNTC * R2 / (255.0 - rawNTC) / R0 ) / B + 1.0 / 298.0) - 273.0; Serial.print( F("Voltage: ") ); Serial.println(voltage); Serial.print( F("Current: ") ); Serial.println(current); Serial.print( F("Throttle: ") ); Serial.println(throttle); Serial.print( F("RPM: ") ); Serial.println(rpm); // These prints do not actually send the characters,they only queue // them up to be sent gradually,at 115200. The characters will be // pulled from the output queue by a TX interrupt,and given to the // UART one at a time. // // To prevent these interrupts from possibly interfering with any other // timing,and to pace your program,we will wait *now* for all the // characters to be sent to the Serial Monitor. Serial.flush(); // Let's pace things a little bit more for testing: delay here until // it's time to scan again. const uint32_t SCAN_INTERVAL = 1000UL; // ms while (millis() - scanTime < SCAN_INTERVAL) ; // waitin' } int16_t ReadWriteRegister(int reg,int value) { // Flush input,as suggested by Gee Bee while (Serial1.available() > 0) Serial1.read(); // Send command (register number determines whether it is read or write) command[0] = (byte)(0x80 | deviceId); command[1] = (byte)reg; command[2] = (byte)((value >> 8) & 0xFF); command[3] = (byte)(value & 0xFF); command[4] = (byte)(0 - command[0] - command[1] - command[2] - command[3]); Serial1.write(command,5); // The command bytes are only queued for transmission,they have not // actually gone out. You can either wait for command to go out // with a `Serial1.flush()` *OR* add the transmission time to the // timeout value below. However,if anything else has queued bytes // to be sent and didn't wait for them to go out,the calculated // timeout would be wrong. It is safer to flush now and guarantee // that *all* bytes have been sent: anything sent earlier (I don't // see anything else,but you may change that later) *plus* // these 5 command bytes. Serial1.flush(); // Now wait for response to come back,for a certain number of us // The TIMEOUT could be as short as 3 character times @ the Serial1 // baudrate: 3 * (10 bits/char) / 115200bps = 261us. This is if // the device responds immediately. Gee Bee says 20ms,which would // be 20000UL. There's nothing in the spec,but 1ms seems generous // for reading the raw NTC value,which may require an ADC conversion. // Even the Arduino can do that in 100us. Try longer if you get // timeout warnings. const uint32_t TIMEOUT = 2000UL; uint32_t startTime = micros(); while ((Serial1.available() < 3) && (micros() - startTime < TIMEOUT)) ; // waitin'... int16_t result; if (Serial1.available() >= 3) { response[0] = (byte)Serial1.read(); response[1] = (byte)Serial1.read(); response[2] = (byte)Serial1.read(); // Verify the checksum if (response[0] + response[1] + response[2] != 0) { Serial.print( reg ); Serial.println( F(" Checksum error!") ); Serial.flush(); // optional,use it for now to stay synchronous } // Cast to 16 bits *first*,then shift and add result = (((int16_t) response[0]) << 8) + (int16_t) response[1]; } else { // Must have timed out,because there aren't enough characters Serial.print( reg ); Serial.println( F(" Timed out!") ); Serial.flush(); // optional,use it for now to stay synchronous result = 0; } return result; // You must always return something }
评论:
>您的结果计算中出现错误(可能)已在上面的答案中修复.我认为,在加法之外加注使你失去前八位.如上计算应给出正确的答案.
>经过一番谷歌搜索,我看到这是一个Castle Serial Link controller.这将是有用的知道.它描述了我在上面的ReadWriteRegister函数中使用的校验和.该函数可以告诉您它是否超时或校验和是否错误.这也意味着可能需要更长的超时.目前尚不清楚您的设备是否等待最多480毫秒来获取最新值,或者是否持续缓存它们并立即响应从ESC接收的最后一个值.但是,由于ESC接收命令然后发送新值所花费的时间,写入将不会反映在最长480ms的读取值中.见ESC Castle Link protocol.
> ReadWriteRegister函数返回一个16位整数,效率更高.比较浮点数永远不会好. BTW,double只是8位Arduinos上的单浮点数.
> ReadWriteRegister函数不需要writemode参数,因为寄存器编号决定了您是在写还是正在读取设备.
>仅在设置中执行写入油门值.
更新2
您的逻辑分析仪镜头显示为ESC显示“扫描”.它正在尝试每个设备ID,其中一些回复非零电压.此外,它似乎运行在9600,而不是115200.这是来自不同的设置?
无论如何,它确认了控制器规范所说的内容:写入5个字节,读取3.校验和值如预期.但是,它的运行速度比程序慢10倍,因此它不会提供有关超时的新信息.这可能意味着在设备响应之前有一个小的延迟,可能是~1位时间,或大约100us.
你读过控制器规格吗?您应该将程序与规范进行比较,以确保您了解控制器的工作原理.
我已将上面的程序修改为:
>在设置中与控制器同步(写入5个零字节并等待250ms),
>使用规范中的缩放数字(而不是它们的倒数?),
>使用有意义的常数而不是“魔法”数字(例如2042),
>对几个寄存器使用整数或布尔类型而不是double(请参阅safeState,linkLiveStatus和eStopStatus),
>将超时时间增加到2ms(如果继续频繁超时,则继续增加),以及
>发生错误时输出注册号.
如果您想在这方面取得成功,您必须学会阅读规范并将其要求转换为符合要求的代码.你开始的程序在最坏的情况下是非保形的,或者说最好是误导.对于说“INTEGERS”和“FLOATS”的评论我特别感到好笑,但这些部分却反其道而行之.
也许这是修复别人代码的一课?它确实会遇到许多问题.如果我每次都说镍,我说:
>“这个号码是什么?”
>“那个评论错了!”
>“规范说你应该……”
>“为什么这么难读?我只是添加一些间距.”
……我会成为一个非常富有的人.