/* * Author: Jon Trulson * Copyright (c) 2016 Intel Corporation. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include "mcp2515.h" #include #include // in ms #define MCP2515_MAX_TIMEOUT (5000) // static array of cnf1-3 value for setting speed. These values were // taken from the Seeed code at: // https://github.com/Seeed-Studio/CAN_BUS_Shield/ struct _mcp2515_config { uint8_t cnf1; uint8_t cnf2; uint8_t cnf3; }; // NOTE: These indexes must match the enum MCP2515_SPEED_T values in // mcp2515_regs.h! Add any new values to the end, with a corresponding // enum value in MCP2515_SPEED_T. static struct _mcp2515_config mcp2515_configs[] = { { 0x3f, 0xff, 0x87 }, // 0 - 5kbps { 0x1f, 0xff, 0x87 }, // 1 - 10kbps { 0x0f, 0xff, 0x87 }, // 2 - 20kbps { 0x0f, 0xba, 0x07 }, // 3 - 25kbps { 0x0f, 0xf1, 0x85 }, // 4 - 31k25kbps { 0x09, 0xbe, 0x07 }, // 5 - 33kbps { 0x07, 0xff, 0x87 }, // 6 - 40kbps { 0x07, 0xfa, 0x87 }, // 7 - 50kbps { 0x03, 0xff, 0x87 }, // 8 - 80kbps { 0x03, 0xbe, 0x07 }, // 9 - 83kbps { 0x03, 0xad, 0x07 }, // 10 - 95kbps { 0x03, 0xfa, 0x87 }, // 11 - 100kbps { 0x03, 0xf0, 0x86 }, // 12 - 125kbps { 0x01, 0xfa, 0x87 }, // 13 - 200kbps { 0x41, 0xf1, 0x85 }, // 14 - 250kbps { 0x00, 0xf0, 0x86 }, // 15 - 500kbps { 0x00, 0xa0, 0x04 }, // 16 - 666kbps { 0x00, 0xd0, 0x82 }, // 17 - 1000kbps }; // For SPI, these are our CS on/off functions, if needed static void mcp2515_cs_on(const mcp2515_context dev) { assert(dev != NULL); if (dev->gpio) mraa_gpio_write(dev->gpio, 0); } static void mcp2515_cs_off(const mcp2515_context dev) { assert(dev != NULL); if (dev->gpio) mraa_gpio_write(dev->gpio, 1); } // convert an integer id into the 4-byte breakout format used by the // device. static void mcp2515_int_to_id(const mcp2515_context dev, int id, bool ext, bool is_filter, MCP2515_ID_T *did) { assert(dev != NULL); assert(did != NULL); // mask off all but the lower 29 bits id &= 0x1fffffff; did->SIDH = (id >> 3) & 0xff; did->SIDL = ((id & 0x07) << 5); did->EID8 = 0; did->EID0 = 0; // This is a sort of special case (ext vs. filter). When using // this function to create a filter, but the msg type is standard, // the filtering/masking engine can use the first 16 bits of // extended filter data (EID8, EID0) to compare against the first // 16 bits of the data payload (D0 and D1). For a standard id, // the extended bits 16 and 17 are ignored, so we only include // these (along with the extended bit) on true extended ids // regardless of filtering. if (ext || is_filter) { if (ext) { // add ext bits 17 and 16 did->SIDL |= ((id & 0x18000000) >> 27) & 0x03; // set the extended id bit did->SIDL |= MCP2515_SIDL_EXIDE; } // now fill in the extended bits did->EID8 = (id >> 19) & 0xff; did->EID0 = (id >> 11) & 0xff; } } // convert the 4-byte breakout format used by the device into an integer id static int mcp2515_id_to_int(const mcp2515_context dev, bool *ext, MCP2515_ID_T *did) { assert(dev != NULL); assert(did != NULL); int id = 0; *ext = false; id |= (did->SIDH << 3); id |= (did->SIDL >> 5) & 0x07; // is it an extended id? if (did->SIDL & MCP2515_SIDL_EXIDE) { *ext = true; id |= ((did->SIDL & 0x03) << 27); id |= (did->EID8 << 19); id |= (did->EID0 << 11); } return id; } // init... mcp2515_context mcp2515_init(int bus, int cs_pin) { mcp2515_context dev = (mcp2515_context)malloc(sizeof(struct _mcp2515_context)); if (!dev) return NULL; // zero out context memset((void *)dev, 0, sizeof(struct _mcp2515_context)); // make sure MRAA is initialized int mraa_rv; if ((mraa_rv = mraa_init()) != MRAA_SUCCESS) { printf("%s: mraa_init() failed (%d).\n", __FUNCTION__, mraa_rv); mcp2515_close(dev); return NULL; } if (!(dev->spi = mraa_spi_init(bus))) { printf("%s: mraa_spi_init() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // Only create cs context if we are actually using a valid pin. // A hardware controlled pin should specify cs as -1. if (cs_pin >= 0) { if (!(dev->gpio = mraa_gpio_init(cs_pin))) { printf("%s: mraa_gpio_init() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } mraa_gpio_dir(dev->gpio, MRAA_GPIO_OUT); mcp2515_cs_off(dev); } if (mraa_spi_mode(dev->spi, MRAA_SPI_MODE0)) { printf("%s: mraa_spi_mode() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } if (mraa_spi_frequency(dev->spi, 10000000)) { printf("%s: mraa_spi_frequency() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // reset if (mcp2515_reset(dev)) { printf("%s: mcp2515_reset() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // make sure the mode is config (should be after a reset), set a // default speed, and then set normal mode. if (mcp2515_set_opmode(dev, MCP2515_OPMODE_CONFIG)) { printf("%s: mcp2515_set_opmode(config) failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } if (mcp2515_set_speed(dev, MCP2515_SPEED_50KBPS)) { printf("%s: mcp2515_set_speed() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // clear all filters and masks if (mcp2515_set_filter(dev, MCP2515_RX_FILTER0, false, 0) || mcp2515_set_filter(dev, MCP2515_RX_FILTER1, false, 0) || mcp2515_set_filter(dev, MCP2515_RX_FILTER2, false, 0) || mcp2515_set_filter(dev, MCP2515_RX_FILTER3, false, 0) || mcp2515_set_filter(dev, MCP2515_RX_FILTER4, false, 0) || mcp2515_set_filter(dev, MCP2515_RX_FILTER5, false, 0)) { printf("%s: mcp2515_set_filter() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } if (mcp2515_set_mask(dev, MCP2515_RX_MASK0, false, 0) || mcp2515_set_mask(dev, MCP2515_RX_MASK1, false, 0)) { printf("%s: mcp2515_set_mask() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // set the mode for any filter. With the above settings, all // packets will be received since all of the mode bits are 0, so // the filter bits are ignored. if (mcp2515_set_rx_buffer_mode(dev, MCP2515_RX_BUFFER0, MCP2515_RXMODE_ANY_FILTER) || mcp2515_set_rx_buffer_mode(dev, MCP2515_RX_BUFFER1, MCP2515_RXMODE_ANY_FILTER)) { printf("%s: mcp2515_set_rx_buffer_mode() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // turn off all interrupt enables if (mcp2515_set_intr_enables(dev, 0)) { printf("%s: mcp2515_set_intr_enables() failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } // switch to normal operating mode if (mcp2515_set_opmode(dev, MCP2515_OPMODE_NORMAL)) { printf("%s: mcp2515_set_opmode(normal) failed.\n", __FUNCTION__); mcp2515_close(dev); return NULL; } return dev; } void mcp2515_close(mcp2515_context dev) { assert(dev != NULL); mcp2515_uninstall_isr(dev); if (dev->spi) mraa_spi_stop(dev->spi); if (dev->gpio) mraa_gpio_close(dev->gpio); free(dev); } // bus read and write functions upm_result_t mcp2515_bus_read(const mcp2515_context dev, uint8_t cmd, uint8_t *args, unsigned int arglen, uint8_t *data, uint8_t len) { int buflen = len + 1 + arglen; uint8_t sbuf[buflen]; memset((void *)sbuf, 0, buflen); int index = 0; sbuf[index++] = cmd; if (args && arglen) { for (int i=0; ispi, sbuf, sbuf, buflen)) { mcp2515_cs_off(dev); printf("%s: mraa_spi_transfer_buf() failed.\n", __FUNCTION__); return UPM_ERROR_OPERATION_FAILED; } mcp2515_cs_off(dev); // now copy it into user buffer for (int i=0; ispi, sbuf, sbuf, len + 1)) { mcp2515_cs_off(dev); printf("%s: mraa_spi_transfer_buf() failed.\n", __FUNCTION__); return UPM_ERROR_OPERATION_FAILED; } mcp2515_cs_off(dev); return UPM_SUCCESS; } upm_result_t mcp2515_write_reg(const mcp2515_context dev, uint8_t reg, uint8_t value) { assert(dev != NULL); uint8_t arg[2]; // reg addr, value arg[0] = reg; arg[1] = value; return mcp2515_bus_write(dev, MCP2515_CMD_WRITE, arg, 2); } upm_result_t mcp2515_write_regs(const mcp2515_context dev, uint8_t reg, uint8_t *buffer, int len) { assert(dev != NULL); assert(buffer != NULL); int writeLen = len + 1; uint8_t arg[1+len]; // reg addr, data... arg[0] = reg; for (int i=0; i MCP2515_MAX_PAYLOAD_DATA) len = MCP2515_MAX_PAYLOAD_DATA; if (len < 0) len = 0; packet.DLC = (len & _MCP2515_TXBDLC_MASK) << _MCP2515_TXBDLC_SHIFT; if (rtr) packet.DLC |= MCP2515_TXBDLC_RTR; // now the payload for (int i=0; i> _MCP2515_RXSTATUS_RXMSG_SHIFT; return buffers; } MCP2515_MSGTYPE_T mcp2515_rx_status_msg_type(const mcp2515_context dev) { assert(dev != NULL); uint8_t rx_status_byte; if (mcp2515_bus_read(dev, MCP2515_CMD_RX_STATUS, NULL, 0, &rx_status_byte, 1)) return MCP2515_MSGTYPE_STD; // not really, but.... MCP2515_MSGTYPE_T msgtype = (rx_status_byte & (_MCP2515_RXSTATUS_MSGTYPE_MASK << _MCP2515_RXSTATUS_MSGTYPE_SHIFT)) >> _MCP2515_RXSTATUS_MSGTYPE_SHIFT; return msgtype; } MCP2515_FILTERMATCH_T mcp2515_rx_status_filtermatch(const mcp2515_context dev) { assert(dev != NULL); uint8_t rx_status_byte; if (mcp2515_bus_read(dev, MCP2515_CMD_RX_STATUS, NULL, 0, &rx_status_byte, 1)) return MCP2515_RX_FILTER0; // not really, but.... MCP2515_FILTERMATCH_T fm = (rx_status_byte & (_MCP2515_RXSTATUS_FILTERMATCH_MASK << _MCP2515_RXSTATUS_FILTERMATCH_SHIFT)) >> _MCP2515_RXSTATUS_FILTERMATCH_SHIFT; return fm; } upm_result_t mcp2515_get_rx_msg(const mcp2515_context dev, MCP2515_RX_BUFFER_T bufnum, MCP2515_MSG_T *msg) { assert(dev != NULL); assert(msg != NULL); uint8_t cmd = 0; uint8_t rxbctrl_reg = 0; switch (bufnum) { case MCP2515_RX_BUFFER0: cmd = MCP2515_CMD_READ_RXBUF_RXB0SIDH; rxbctrl_reg = MCP2515_REG_RXB0CTRL; break; case MCP2515_RX_BUFFER1: cmd = MCP2515_CMD_READ_RXBUF_RXB1SIDH; rxbctrl_reg = MCP2515_REG_RXB1CTRL; break; default: return UPM_ERROR_INVALID_PARAMETER; } // first see if we have a message waiting MCP2515_RXMSG_T rxmsgs = mcp2515_rx_status_msgs(dev); bool msgavail = false; if (rxmsgs == MCP2515_RXMSG_BOTH) msgavail = true; else if (rxmsgs == MCP2515_RXMSG_RXB0 && bufnum == MCP2515_RX_BUFFER0) msgavail = true; else if (rxmsgs == MCP2515_RXMSG_RXB1 && bufnum == MCP2515_RX_BUFFER1) msgavail = true; // nope if (!msgavail) return UPM_ERROR_NO_RESOURCES; // get the control reg upm_result_t rv; uint8_t rxbctrl; if ((rv = mcp2515_read_reg(dev, rxbctrl_reg, &rxbctrl))) return rv; // go ahead and grab the message and fill in the details memset(msg, 0, sizeof(MCP2515_MSG_T)); if ((rv = mcp2515_bus_read(dev, cmd, NULL, 0, msg->pkt.data, MCP2515_MAX_PKT_DATA))) { printf("mcp2515_bus_read failed\n"); return UPM_ERROR_OPERATION_FAILED; } // if we are here, we got the packet, so decode and determine some // things. // id & ext MCP2515_ID_T did; did.SIDH = msg->pkt.SIDH; did.SIDL = msg->pkt.SIDL; did.EID8 = msg->pkt.EID8; did.EID0 = msg->pkt.EID0; msg->id = mcp2515_id_to_int(dev, &(msg->ext), &did); // rtr if (!msg->ext) { // The RTR flag bit is the same for both the RXB0CTRL and // RXB1CTRL registers, so it's safe to just compare against // the first regardless of the buffer we are interested in. if (rxbctrl & MCP2515_RXB0CTRL_RXRTR) msg->rtr = true; } else { // ext message stores rtr separately if (msg->pkt.DLC & MCP2515_RXBDLC_RTR) msg->rtr = true; } // filter num if (bufnum == MCP2515_RX_BUFFER0) { if (rxbctrl & MCP2515_RXB0CTRL_FILHIT) msg->filter_num = 1; else msg->filter_num = 0; } else { msg->filter_num = (int)((rxbctrl & (_MCP2515_RXB1CTRL_FILHIT_MASK << _MCP2515_RXB1CTRL_FILHIT_SHIFT)) >> _MCP2515_RXB1CTRL_FILHIT_SHIFT); } // now the payload length msg->len = ((msg->pkt.DLC & (_MCP2515_RXBDLC_MASK << _MCP2515_RXBDLC_SHIFT)) >> _MCP2515_RXBDLC_SHIFT); return UPM_SUCCESS; } void mcp2515_print_msg(const mcp2515_context dev, MCP2515_MSG_T *msg) { assert(dev != NULL); assert(msg != NULL); // print it out printf("id %08x ext %d rtr %d filt %d len %d\n", msg->id, msg->ext, msg->rtr, msg->filter_num, msg->len); printf("\tpayload: "); for (int i=0; ilen; i++) printf("0x%02x ", msg->pkt.data[MCP2515_PKT_D0 + i]); printf("\n"); } upm_result_t mcp2515_abort_tx(const mcp2515_context dev, MCP2515_TX_BUFFER_T bufnum) { assert(dev != NULL); uint8_t reg; switch (bufnum) { case MCP2515_TX_BUFFER0: reg = MCP2515_REG_TXB0CTRL; break; case MCP2515_TX_BUFFER1: reg = MCP2515_REG_TXB1CTRL; break; case MCP2515_TX_BUFFER2: reg = MCP2515_REG_TXB2CTRL; break; default: return UPM_ERROR_INVALID_PARAMETER; } // Clear the TXREQ bit. return mcp2515_bit_modify(dev, reg, MCP2515_TXBCTRL_TXREQ, 0); } upm_result_t mcp2515_install_isr(const mcp2515_context dev, int pin, void (*isr)(void *), void *arg) { assert(dev != NULL); mcp2515_uninstall_isr(dev); if ( !(dev->intr = mraa_gpio_init(pin)) ) { printf("%s: mraa_gpio_init failed.\n", __FUNCTION__); return UPM_ERROR_OPERATION_FAILED; } mraa_gpio_dir(dev->intr, MRAA_GPIO_IN); // install our interrupt handler mraa_gpio_isr(dev->intr, MRAA_GPIO_EDGE_FALLING, isr, arg); return UPM_SUCCESS; } void mcp2515_uninstall_isr(const mcp2515_context dev) { assert(dev != NULL); if (!dev->intr) return; mraa_gpio_isr_exit(dev->intr); mraa_gpio_close(dev->intr); dev->intr = NULL; } upm_result_t mcp2515_set_intr_enables(const mcp2515_context dev, uint8_t enables) { assert(dev != NULL); return mcp2515_write_reg(dev, MCP2515_REG_CANINTE, enables); } upm_result_t mcp2515_get_intr_flags(const mcp2515_context dev, uint8_t *flags) { assert(dev != NULL); return mcp2515_read_reg(dev, MCP2515_REG_CANINTF, flags); } upm_result_t mcp2515_set_intr_flags(const mcp2515_context dev, uint8_t flags) { assert(dev != NULL); return mcp2515_bit_modify(dev, MCP2515_REG_CANINTF, flags, 0xff); } upm_result_t mcp2515_clear_intr_flags(const mcp2515_context dev, uint8_t flags) { assert(dev != NULL); return mcp2515_bit_modify(dev, MCP2515_REG_CANINTF, flags, 0); } upm_result_t mcp2515_get_error_flags(const mcp2515_context dev, uint8_t *flags) { assert(dev != NULL); return mcp2515_read_reg(dev, MCP2515_REG_EFLG, flags); } upm_result_t mcp2515_clear_error_flags(const mcp2515_context dev, uint8_t flags) { assert(dev != NULL); return mcp2515_bit_modify(dev, MCP2515_REG_EFLG, flags, 0); }