2017년 8월 6일 일요일

Working on Canbus to ELM327 dongle over bluetooth to Android


Hey guys, hello from stinking hot Thailand!  Wish you were here!

I've been working on my Android app that's like an enhanced EVIC display.  It uses Torque's theme system for the gauges so it looks pretty great.  It also incorporates GPS and Google Maps to track your trip and you can configure GEVCU settings via a tab talking Wifi.  It will be free and open source once stable.  I've got everything working except actually reading the GEVCU's available data through PIDs with an ELM327 chip inside an OBD-2 Bluetooth dongle, which I have wired into my GEVCU test bench.

I have put a lot of time into this so far, setting up a combination of Eclipse and Arduino IDE, analyzing the existing code, reading the ELM327 chip manual, contacting ELM327 tech support, OBD-2 manuals, Canbus documentation, searching for sample code, forum discussions, etc. so I'm not coming to you lightly, don't want to waste your time!

Of course, I crossed my fingers but nothing worked, so I started going backwards through the data path.  Using Android-based Bluetooth command line apps, I can send AT commands and get a response from the ELM327 chip but when I send PIDs I get no response.  ELM327 tech support says the ECU (GEVCU) must respond to the Mode 01 PID 00 command before it will open up a comms path so that's the first PID I need to support.

Now I've worked backward to the point where I can't get the most basic function working - send a frame on the bus and get it back in my device code all within the firmware.  I've fiddled for hours, no joy.

I'm using Jack's current evtv.me source code which includes the new EVIC handler.

I've made a new device similar to ELM327_Emu.cpp called ELM327_Bluetooth.cpp and registered it as device id 0x651.  Because the ELM327 chip handles all AT commands, I never see them and will handle all PIDs inside my code, not using OBD2Handler.cpp.  I've largely followed what I see other devices do in the code already.

Here are I think the relevant pieces of code:

1) Set up Canbus mask and filter in GEVCU.ino:

// eight = 5; //how many RX mailboxes
eight = 7; // Using two more mailboxes for ELM Bluetooth to Android because ELM327Emu also uses two
sysPrefs->write(EESYS_CAN_RX_COUNT, eight);
        ...
thirtytwo = 0x7f0; //standard frame, ignore bottom 4 bits
sysPrefs->write(EESYS_CAN_MASK5, thirtytwo);
sysPrefs->write(EESYS_CAN_MASK6, thirtytwo);
        ...
thirtytwo = 0x651;
sysPrefs->write(EESYS_CAN_FILTER5, thirtytwo);  // ELM327Bluetooth device 0x651
sysPrefs->write(EESYS_CAN_FILTER6, thirtytwo);

2) Create my device object during init in GEVCU.ino:

ELM327Bluetooth *elmBT = new ELM327Bluetooth();

3) Attach to both CANbuses inside my ELM327_Bluetooth.cpp

CanHandler::getInstanceCar()->attach(this, 0x651, 0x7f0, false);    // Callback for my device(?) and standard mask
CanHandler::getInstanceEV()->attach(this, 0x651, 0x7f0, false);

4) Send a test frame to both Canbuses in a modified SerialConsole.cpp

CAN_FRAME output;
output.length = 1;
output.id = 0x651; // the ELM327Bluetooth device handler?
output.extended = 0;
output.rtr = 0;
output.fid = 0x0;
output.priority = 0;
output.data.bytes[0] = newValue;
                ...

CanHandler::getInstanceEV()->sendFrame(output);
CanHandler::getInstanceCar()->sendFrame(output);


5) Handle matching frames inside my ELM327_Bluetooth.cpp

void ELM327Bluetooth::handleCanFrame(CAN_FRAME *frame)
{
...
}

6) I see the frames sent down into the Canbus library, but nothing ever comes back to my device's callback method.


Now, I'm sure this is due to my incomplete understanding of Canbus protocols and the existing code.  Some points of confusion:

- I see "id" in the Canbus frame object.  Is this id the device id (and therefore priority?) of the target device on the bus or is it for PIDs because in some places in the code the id is clearly a PID?  I figured that the higher-level OBD2 protocol data like mode and PID would be encoded inside the Canbus frame's generic data payload.  Is Mode 1 always assumed?  I see nothing in the code where I can set the mode.  

- Am I using the filter and mask facility correctly?  Do I put my device id in the filter?

- Am I attaching to the bus correctly?

- Am I sending a frame correctly?

Thanks for any insight!

Not my circus, not my monkeys.

--
Collin:

Didn't you have an ELM327 object started?  You were going to do OBDII pids but I never tested it with anything...

Mike - thanks for all the work.  This is certainly something I'd like to support.  I've just been Tesla-ing pretty hard.  

But a quick view of the GEVCU code looks pretty good to me.  All the relevant objects look pretty good to me.

CANPIDLISTENER.  This appears to be an object that looks for 7EO or 7DF frames.  That is messages of ID 7EO or 7DF.  These appear to be 
the CAN message IDs used by the PID protocol.  This object receives the PID request and tries to formulate a reply from available GEVCU data.  I think it gets this data from OBDIIHandler.

ELM327EMU and ELM327PROCESSOR appear to deal with AT commands over the ELM interface.  This is a unique serial port over which we will receive PID requests and send responses.

So the basic structure is all there and registered.  The problem is actually testing it, getting it to work, and then of course expanding it to handle all possible PID communication that we have something close to data for.

All I see on the device list is:

PIDLISTENER = 0x6000,
ELM327EMU = 0x650,

So I'm pretty sure you have to ENABLE those from the serial console to get it working at all.

--
Yes, I did write a couple modules to support OBDII communication. You
do indeed need to enable those devices to make any of it work.  I
think that was all written about two years ago. The problem is, this
is one of those "he's got more ideas than time" sort of things. I'm
pretty sure that two years ago I tested it to work. There are indeed
two options. There is a module that allows the GEVCU wifi or bluetooth
module to pretend to be an ELM327 device. I think at the time I wrote
that we only had wifi but I was actually able to make it all work that
way - torque supports wifi connected devices. Now it should be easier
as more recent GEVCU boards have the capability to use bluetooth. The
other approach is to use canpidlistener which directly listens for
OBDII PID traffic and responds right over CAN. This would allow one to
use an ELM327 based bluetooth dongle. I think this had some limited
testing. But, in the meantime I believe there have been some changes
to the codebase and maybe it broke.

Now I'll answer the original questions with in-line quoting: 


>> Here are I think the relevant pieces of code:
>>
>> 1) Set up Canbus mask and filter in GEVCU.ino:
>>
>> // eight = 5; //how many RX mailboxes
>> eight = 7; // Using two more mailboxes for ELM Bluetooth to Android
>> because ELM327Emu also uses two
>> sysPrefs->write(EESYS_CAN_RX_COUNT, eight);
>>         ...
>> thirtytwo = 0x7f0; //standard frame, ignore bottom 4 bits
>> sysPrefs->write(EESYS_CAN_MASK5, thirtytwo);
>> sysPrefs->write(EESYS_CAN_MASK6, thirtytwo);
>>         ...
>> thirtytwo = 0x651;
>> sysPrefs->write(EESYS_CAN_FILTER5, thirtytwo);  // ELM327Bluetooth device
>> 0x651
>> sysPrefs->write(EESYS_CAN_FILTER6, thirtytwo); 
Why use an ID of 0x651? You should be accepting frames in the 0x7E0
range. That's the ID that is likely to be used by ELM327 to ask you
(asking as the ECU) for a PID reply. You then reply with an address 8
higher. For instance, if ELM327 sends on 0x7E0 you reply on 0x7E8.
But, you've got the mask correct. 


>>
>> 2) Create my device object during init in GEVCU.ino:
>>
>> ELM327Bluetooth *elmBT = new ELM327Bluetooth();
>>
>> 3) Attach to both CANbuses inside my ELM327_Bluetooth.cpp
>>
>> CanHandler::getInstanceCar()->attach(this, 0x651, 0x7f0, false);    //
>> Callback for my device(?) and standard mask
>> CanHandler::getInstanceEV()->attach(this, 0x651, 0x7f0, false);
>> 
Once again, use 0x7E0 not 0x651. This ID is not your device ID but
rather the CAN frame ID. 


>> 4) Send a test frame to both Canbuses in a modified SerialConsole.cpp
>>
>> CAN_FRAME output;
>> output.length = 1;
>> output.id = 0x651; // the ELM327Bluetooth device handler?
>> output.extended = 0;
>> output.rtr = 0;
>> output.fid = 0x0;
>> output.priority = 0;
>> output.data.bytes[0] = newValue;
>>                 ...
>>
>> CanHandler::getInstanceEV()->sendFrame(output);
>> CanHandler::getInstanceCar()->sendFrame(output);
>> 
Same basic advice. But, yes, you seem to be calling sendFrame properly. 

>> Now, I'm sure this is due to my incomplete understanding of Canbus
>> protocols and the existing code.  Some points of confusion:
>>
>> - I see "id" in the Canbus frame object.  Is this id the device id (and
>> therefore priority?) of the target device on the bus or is it for PIDs
>> because in some places in the code the id is clearly a PID?  I figured that
>> the higher-level OBD2 protocol data like mode and PID would be encoded
>> inside the Canbus frame's generic data payload.  Is Mode 1 always assumed?
>> I see nothing in the code where I can set the mode.
>>
>> - Am I using the filter and mask facility correctly?  Do I put my device
>> id in the filter?
>>
>> - Am I attaching to the bus correctly?
>>
>> - Am I sending a frame correctly? 
I think you're attaching to the bus more or less properly but your use
of IDs is wrong. You are sending properly. You are using masks
properly and I think you have the basic idea for the filter ID. Most
all of your troubles just come down to the frame ID. OBDII uses the
frame ID as a key for who is sending and what they want. ECUs listen
on 0x7E0 through 0x7E7. There is a broadcast address 0x7DF which
should make every ECU respond. Torque might use that ID so plan
accordingly. All ECUs reply 8 higher than their listening ID (even if
they are replying to 0x7DF). So, if torque sending 7DF and the ECU
would normally be 0x7E2 it replies on 0x7EA.

Mode 1 is where most all the magic happens. There are other modes but
mostly they're not relevant. The code in GEVCU can reply to mode 1, 3,
4, and 9. Mode 3 gives out fault codes. Mode 4 clears them. Mode 9 has
some more vehicle data like the VIN number of the car.

Wikipedia actually has a good article about all of this stuff:
https://en.wikipedia.org/wiki/OBD-II_PIDs 

--
I looked at the source code in CanPIDListener and I don't see any
reason it wouldn't work as-is for the intended use. Just hook up an
ELM327 to the can bus wires and run the CanPIDListener (enable its ID
with enable=0x6000) The existing code should pick up messages from the
ELM327 device and reply back with the proper ID. The code is hardwired
to use CAN0 so your ELM327 device would have to be on that bus.

Hopefully that gives a good headstart. 


--
It took a while until I understood the masking and ID stuff properly myself. I finally got it by translating the numbers to binary:

I want to listen to 4 different ID’s : 0x258, 0x259, 0x25a, 0x268 (convert from hex to binary)

CAN_ID_STATUS           0x258 -> 01001011000
CAN_ID_ACTUAL_VALUES    0x259 -> 01001011001
CAN_ID_ERRORS           0x25a -> 01001011010
CAN_ID_TORQUE_LIMIT     0x268 -> 01001101000


the next step is to find out the bit positions where all IDs are the same (orange marked). Set all these bit positions to 1 then you’ll get the mask (convert from binary to hex):

CAN_MASK                0x7cc <- 11111001100

then you check every bit position where you have a 1 in the mask, if the bit is set in the IDs. If so, set it also in your masked ID (convert from binary to hex):

CAN_MASKED_ID           0x248 <- 01001001000
And voila, you’ll get messages for all 4 IDs. But also more, as IDs like 0x25b, etc. also match the scheme. In certain cases, it might make sense, to use a separate filter for 0x268 to be able to use a mask of 11111111100 instead.

Hope this helps with masks and IDs

Regarding the data: Another way might be to obtain the actual values through WebSockets. If you connect via TCP to port 2000 of the ichip and open a WebSocket communication, you’ll get all dynamic parameters streamed as JSON objects every 100ms. (refer to my fork until Collin merges the changes). This might be easier than emulating ELM327.

--
Thanks for all of the suggestions guys.  I've been beating on this off and on, then with an injection of excitement from Jack's last video about the new GEVCU board and smartphone integration.

Bottom line, still no joy.  I can't get what I believe to be the simplest thing to work: register on the second Canbus, send a packet and get the packet back.  I've performed a lot of debug-by-print-statement down into the can_due library and the packet just vanishes, ever to be seen again.

There is some interrupt flag magic happening but the packet also makes it into this section of code in CANRaw::mailbox_int_handler.  I always hit the else{} statement, no frame in the queue and interrupts get disabled.  Note that tx_buffer_head and tx_buffer_tail are never initialized but I suspect the runtime sets them to zero, which means they will never be different values, therefore this code will never execute.  Is this the right code path or just some residual stuff that doesn't matter and didn't get cleaned up?  I also looked at Collin's latest git source, slightly changed for timestamping but essentially the same.

Other than that I'm beginning to wonder if my GEVCU 4.2 board has bad Canbus hardware and the packet is never getting onto the wire or read back again.  Do the code and libraries as they stand send the packet down into silicon or is there a shortcut way of catching it in the software stack when sender and receiver are both in the GEVCU?

Thanks for any additional help!

case 3: //transmit
SerialUSB.println("CANRaw::mailbox_int_handler: transmitting "); // ANDROID

if (tx_buffer_head != tx_buffer_tail) 
{ //if there is a frame in the queue to send
SerialUSB.println("CANRaw::mailbox_int_handler: there is a frame in the queue "); // ANDROID

mailbox_set_id(mb, tx_frame_buff[tx_buffer_head].id, tx_frame_buff[tx_buffer_head].extended);
mailbox_set_datalen(mb, tx_frame_buff[tx_buffer_head].length);
mailbox_set_priority(mb, tx_frame_buff[tx_buffer_head].priority);
for (uint8_t cnt = 0; cnt < 8; cnt++)
mailbox_set_databyte(mb, cnt, tx_frame_buff[tx_buffer_head].data.bytes[cnt]);
global_send_transfer_cmd((0x1u << mb));
tx_buffer_head = (tx_buffer_head + 1) % SIZE_TX_BUFFER;
}
else {
SerialUSB.println("CANRaw::mailbox_int_handler: no frame in the queue "); // ANDROID

disable_interrupt(0x01 << mb);
}
break;


Not my circus, not my monkeys.

--

댓글 없음:

댓글 쓰기