Skip to content

Commit

Permalink
Merge pull request #14 from tinymovr/set_mcp_freq
Browse files Browse the repository at this point in the history
Restore Arduino Uno functionality
  • Loading branch information
yconst committed Apr 23, 2024
2 parents 99153c0 + 9f6aa1a commit 7d59d19
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 7 deletions.
206 changes: 206 additions & 0 deletions examples/arduino_uno_mcp2515/arduino_uno_mcp2515.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
* This is a minimal example of the Tinymovr-Arduino library
* functionality, using the Arduino Uno with MCP2515 to connect
* to CAN bus. The example allows sending a few basic commands,
* and supports reading back information.
*
* For CAN endpoint reference check out:
* https://tinymovr.readthedocs.io/en/latest/api/guide.html#api-reference
*/


#include "Arduino.h"
#include <CAN.h> // "CAN Adafruit Fork" library
#include <tinymovr.hpp>

// ---------------------------------------------------------------
// REQUIRED CAN HARDWARE INTERFACE CODE
// ADAPT BELOW TO YOUR CAN ADAPTER HARDWARE

// The send_cb and recv_cb functions need to be implemented
// according to the CAN bus hardware you use. Below an example
// usable with MCP2515 type breakouts

/*
* Function: send_cb
* --------------------
* Is called to send a CAN frame
*
* arbitration_id: the frame arbitration id
* data: pointer to the data array to be transmitted
* data_size: the size of transmitted data
* rtr: if the ftame is of request transmit type (RTR)
*/
void send_cb(uint32_t arbitration_id, uint8_t *data, uint8_t data_size, bool rtr)
{
CAN.beginExtendedPacket(arbitration_id, data_size, rtr);
for (int i=0; i<data_size; i++)
{
CAN.write(data[i]);
}
CAN.endPacket();
}

/*
* Function: recv_cb
* --------------------
* Is called to receive a CAN frame
*
* arbitration_id: the frame arbitration id
* data: pointer to the data array to be received
* data_size: pointer to the variable that will hold the size of received data
*/
bool recv_cb(uint32_t *arbitration_id, uint8_t *data, uint8_t *data_size)
{
int packetSize = CAN.parsePacket();
if (packetSize > 0) {
*data_size = packetSize;
for (int i = 0; i < packetSize; i++) {
int r = CAN.read();
if (r == -1) return false;
data[i] = (uint8_t)r;
}
*arbitration_id = CAN.packetId();
return true;
}
return false;
}

/*
* Function: delay_us_cb
* --------------------
* Is called to perform a delay
*
* us: the microseconds to wait for
*/
void delay_us_cb(uint32_t us)
{
delayMicroseconds(us);
}
// ---------------------------------------------------------------

// ---------------------------------------------------------------
// EXAMPLE CODE
// ADAPT BELOW TO YOUR PROGRAM LOGIC

// The Tinymovr object
Tinymovr tinymovr(1, &send_cb, &recv_cb, &delay_us_cb, 100);

/*
* Function: setup
* --------------------
* Perform hardware initialization
*/
void setup()
{
Serial.begin(115200);
while (!Serial);

// Most MCP2515 breakouts have a 8MHz crystal, this needs
// to be specified here
CAN.setClockFrequency(8e6);

// start the CAN bus at 1Mbps
if (!CAN.begin(1000E3)) {
Serial.println("Starting CAN failed!");
while (1);
}

// NOTE: You NEED to enable filtering using this pattern,
// otherwise the library will not function correctly,
// especially with a lot of Tinymovr units on the bus
if (!CAN.filterExtended(0x0, 0x700))
{
Serial.println("Setting CAN filters failed!");
while (1);
}

// As a final step check that the hash returned by the node
// is the same as the hash stored by the Tinymovr library.
// This is crucial to prevent potential mismatches in commands.
uint32_t got_hash = tinymovr.get_protocol_hash();
if (got_hash != avlos_proto_hash)
{
Serial.println("Wrong device spec!");
Serial.print("Got: ");
Serial.println(got_hash);
Serial.print("Need: ");
Serial.println(avlos_proto_hash);
while(1);
}
}

/*
* Function: loop
* --------------------
* Program loop.
* Listen for commands coming from serial and
* transmit to Tinymovr.
*/
void loop()
{
if (Serial.available() > 0) {
uint8_t receivedChar = Serial.read();
if (receivedChar == 'Q')
{
Serial.println("Received Calibration command");
tinymovr.controller.set_state(1);
}
else if (receivedChar == 'A')
{
Serial.println("Received Closed Loop command");
tinymovr.controller.set_state(2);
tinymovr.controller.set_mode(2);
}
else if (receivedChar == 'Z')
{
Serial.println("Received Idle command");
tinymovr.controller.set_state(0);
}
else if (receivedChar == 'R')
{
Serial.println("Received reset command");
tinymovr.reset();
}
else if (receivedChar == '<')
{
Serial.println("Received L turn command");
float pos_estimate = tinymovr.sensors.user_frame.get_position_estimate();
Serial.println(pos_estimate);
tinymovr.controller.position.set_setpoint(pos_estimate - 8192.0f);
}
else if (receivedChar == '>')
{
Serial.println("Received R turn command");
float pos_estimate = tinymovr.sensors.user_frame.get_position_estimate();
Serial.println(pos_estimate);
tinymovr.controller.position.set_setpoint(pos_estimate + 8192.0f);
}
else if (receivedChar == 'I')
{
// Print board information
Serial.print("Device ID: ");
Serial.print(tinymovr.comms.can.get_id());
Serial.print(", Temp:");
Serial.print(tinymovr.get_temp());
Serial.print(", State:");
Serial.print(tinymovr.controller.get_state());
Serial.print(", Mode:");
Serial.print(tinymovr.controller.get_mode());
Serial.print("\n");
Serial.print("Position estimate: ");
Serial.print(tinymovr.sensors.user_frame.get_position_estimate());
Serial.print(", Velocity estimate: ");
Serial.print(tinymovr.sensors.user_frame.get_velocity_estimate());
Serial.print("\n");
Serial.print("Iq estimate: ");
Serial.print(tinymovr.controller.current.get_Iq_estimate());
Serial.print(", Iq setpoint: ");
Serial.print(tinymovr.controller.current.get_Iq_setpoint());
Serial.print("\n");
Serial.println("---");
}
}
delay(50);
}
// ---------------------------------------------------------------
8 changes: 5 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ Functions are called with their name and required parameters.

Even though the library itself is hardware-agnostic (by means of dependency injection), the examples we provide depend on the [CAN Adafruit Fork](https://github.com/adafruit/arduino-CAN) library for low-level hardware access and configuration. Why this library? Because it is the only one offering robust extended frame hardware filtering configuration. There are a ton of CAN libraries for Arduino and embedded in general, but most have only the basic functionality working and fully tested, not the advanced features that Tinymovr requires.

However, not all hardware is compatible with the CAN Adafruit Fork library. Namely, the MCP2515 breakout boards (the blue ones) with 8MHz crystal, of which there are plenty to be found from Chinese sources, are incompatible. The CAN Adafruit Fork library has the crystal frequency harcoded to 16MHz, which breaks the CAN timing configuration. Unfortunately, Adafruit has archived the library repository so it is impossible to raise an issue on this matter.
~~However, not all hardware is compatible with the CAN Adafruit Fork library. Namely, the MCP2515 breakout boards (the blue ones) with 8MHz crystal, of which there are plenty to be found from Chinese sources, are incompatible. The CAN Adafruit Fork library has the crystal frequency harcoded to 16MHz, which breaks the CAN timing configuration. Unfortunately, Adafruit has archived the library repository so it is impossible to raise an issue on this matter.

As we cannot afford to maintain another low-level CAN library fork, this situation will most probably remain as is for the foreseeable future. If you use this MCP2515 breakout our advice would be to switch to something like a CAN BUS Shield with a 16 MHz crystal. Alternatively, you may try to fork the CAN Adafruit Fork library, or implement your own `send_cb`, `recv_cb` and `delay_us_cb` routines. In both cases, you can ask for community support in our [Discord server](https://discord.gg/vNvmpfthug), but these options are not officially supported.
As we cannot afford to maintain another low-level CAN library fork, this situation will most probably remain as is for the foreseeable future. If you use this MCP2515 breakout our advice would be to switch to something like a CAN BUS Shield with a 16 MHz crystal. Alternatively, you may try to fork the CAN Adafruit Fork library, or implement your own `send_cb`, `recv_cb` and `delay_us_cb` routines. In both cases, you can ask for community support in our [Discord server](https://discord.gg/vNvmpfthug), but these options are not officially supported.~~

We've tested this library with an Arduino MKR CAN Bus Shield. Other boards/shields should be compatible.
There was an undocumented function that allows setting the MCP2515 crystal frequency; thus the MCP2515 breakout boards (the blue ones) with 8MHz crystal are compatible after all. Hooray!!

We've tested this library with an Arduino Uno with an MCP2515 breakout, and an Arduino MKR WiFi 1010 with a CAN Bus Shield. Other boards/shields should be compatible.

## License

Expand Down
8 changes: 4 additions & 4 deletions src/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
#endif

#define CAN_EP_SIZE (12)
#define CAN_EP_MASK ((1 << CAN_EP_SIZE) - 1)
#define CAN_EP_MASK ((1UL << CAN_EP_SIZE) - 1)
#define CAN_SEQ_SIZE (9)
#define CAN_SEQ_MASK (((1 << CAN_SEQ_SIZE) - 1) << CAN_EP_SIZE)
#define CAN_SEQ_MASK (((1UL << CAN_SEQ_SIZE) - 1) << CAN_EP_SIZE)
#define CAN_DEV_SIZE (8)
#define CAN_DEV_MASK (((1 << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_SEQ_SIZE))
#define CAN_DEV_MASK (((1UL << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_SEQ_SIZE))

typedef void (*send_callback)(uint32_t arbitration_id, uint8_t *data, uint8_t dlc, bool rtr);
typedef bool (*recv_callback)(uint32_t *arbitration_id, uint8_t *data, uint8_t *dlc);
Expand All @@ -48,7 +48,7 @@ class Node {
uint8_t _dlc;
uint32_t get_arbitration_id(uint32_t cmd_id)
{
return ((this->can_node_id << (CAN_EP_SIZE + CAN_SEQ_SIZE)) & CAN_DEV_MASK) | (cmd_id & CAN_EP_MASK);
return ((((uint32_t)this->can_node_id) << (CAN_EP_SIZE + CAN_SEQ_SIZE)) & CAN_DEV_MASK) | (cmd_id & CAN_EP_MASK);
}
void send(uint32_t cmd_id, uint8_t *data, uint8_t data_size, bool rtr)
{
Expand Down

0 comments on commit 7d59d19

Please sign in to comment.