SPI bus is a favorite amongst most developers inside the embedded world. One master, many slaves and high speed opens a wide variety of possible peripherals. Displays, sensors, and flash memories are some of the most common ones. In this case the peripheral will be MCP25625, Microchips' controller and integrated transceiver for the CAN bus.
The introduction to the CAN bus explained how it works. Like an extension to that part this is a tutorial which will introduce you to creating your complete solution for the CAN.
MCP25625 Click Board
MCP25625 represents the standalone, cost-effective CAN solution that can be easy controlled by MCU through the SPI bus. This device implements CAN2.0B protocol and allows up to 1 Mb/s transfer rate. On the MCU side it supports SPI 0 and SPI 3 modes at clock speeds up to 10 MHz. Typical standby current is about 10 microamps.
The device has 3 transmit, 2 receive buffers and 6 configurable filters. In addition to that there are 2 masks for each receive buffer, which used alongside with filters opens a wide spread of options for configuring the receiver. Every buffer, no matter receive or transmit, has its' own GPIO pin which can be configured as RTS ( request to send ), RXIE ( receive interrupt ), or even as a simple GPIO pin. Filtering the receive messages and usage of buffer pins highly reduces usage of MCU resources.
MCP25625 automatically disconnects CAN bus pins when the device is unpowered so a Brown-Out Event will not load the CAN bus. There is also protection against short-circuit conditions and high-voltage transients in automotive environments.
MCP25625 click does not strictly follow the mikroBUS standard. The board is adapted to cover all functionalities that MCP25625 can provide. There is a choice between using RS-232 connector or you can solder the pins and connect to CAN bus with a simple 2-wire cable.
MCP25625 Library
Same as click board our library covers all functionalities of the MCP25625. The library also can be found on our Libstock page.
The library uses a lot of structs inside hardware layer. Almost all registers are described using structs. Benefits brought by this, are very low RAM space usage and fast and clean implementation of higher layers which uses just a few functions from hardware layer. Disadvantage might be that the user has to create at least one instance of every structure in higher layer but most of the structs taking just a few bytes of RAM space. Some structs, like mcp25625_transfer can be used for transmission and reception, also the same instance of mcp25625_id can be used for masks and filters configuration.
One of the functionalities that MCP25625 has is LOOPBACK work mode. In this mode the device does not use CAN bus. Messages are copied directly from TX to RX buffers so this feature allows developer to focus on implementation.
Hardware Layer
Our journey through the hardware layer might start with explaining structs used to describe the registers. Almost all of them are defined as bit fields because one register is usually used for multiple settings and each field represents a setting. Generally all MCP25625 registers can be divided in three groups :
- Control registers
- Status registers
- Data registers
Control group of registers as its' own name says, are used for controlling the device behavior. This group allows writing, reading and a special kind of writing named masking. Structs that are used to describe this type of register can be easily recognized by "_ctl" suffix in the name of the struct. All registers that belong to this group contain 2 common members. One of them is named reg and the other one is named mask . Reg member is used for storing the address of the register and mask is used for storing the value when we have to edit only a part of the whole register. This is going to be explained in more detailed later.
Status group of registers only allows reading. There are only three registers that belong to this type : TEC, REC and CANSTAT. TEC and REC registers are error counters for transmission and reception. They are not organized as bit fields because the whole register is used for storing the 8-bit value which represents the number of errors detected during the particular process. Because of this the only struct that belongs to this group is mcp25625_can_stat .
Data group of registers is used for storing of different types of data. Some of them for example, are used for RX EID masks, while other ones are used for storing the filter SID and so on. This group allows reading and writing as well.
Each group of registers ( structs ) have functions used for transferring the data. Functions mcp25625_hw_ctl_set , mcp25625_hw_ctl_get and mcp25625_hw_ctl_update are used for control group of registers. Because status group of registers only allows reading, they only use the mcp25625_hw_ctl_get function. Data group of registers uses mcp25625_hw_data_set , mcp25625_hw_data_get , mcp25625_hw_filter_set and mcp25625_hw_mask_set .
Note that functions used by control group accepts void pointers to achieve ability to accept different types of structs so casting is needed in case of usage of these functions. From someones' point of view, this solution to describe MCP25625 might look too complex but as said earlier it brings a very small amount of functions which means less flash space used on the MCU. Everything will looks much simpler after the explanation of how to implement the higher layer.
Implementation
The library provided already implemented functions for some of the most important operations like sending or receiving data. Your implementation might be different than this one because MCP25625 allows a few possibilities for the same operation. For example, request to transfer the message can be achieved by pulling the particular RTS pin to active state or by accessing and editing the TXBnCTRL register. Situation is the same for checking the RX buffers and many other operations. Also in the case of this implementation provided, all structs needed for operations on registers are declared as static members of this module.
//--------------------------------------- // CONTROL TYPES //--------------------------------------- static mcp25625_can_ctl can_ctl; static mcp25625_rts_ctl rts_pins; static mcp25625_rxp_ctl rx_pins; static mcp25625_int_ctl ie_ctl; static mcp25625_txb_ctl tx_ctl; static mcp25625_rxb0_ctl rx0_ctl; static mcp25625_rxb1_ctl rx1_ctl; static mcp25625_cnf1_ctl cnf_1; static mcp25625_cnf2_ctl cnf_2; static mcp25625_cnf3_ctl cnf_3; static can_opmode_t default_mode;
This also can be done by declaring these structs as private inside the function when you need some of them. Lets take bit timing configuration function for example.
int mcp25625_btl_config ( uint8_t BRP, uint8_t SJW, uint8_t PRSEG, uint8_t PHSEG1, uint8_t PHSEG2, bool SAM, bool BTLMODE, bool WAKFIL, bool SOFR ) { mcp25625_cnf1_ctl cnf_1; mcp25625_cnf2_ctl cnf_2; mcp25625_cnf3_ctl cnf_3; }
The choice is yours, I prefer to have all static members in this case, because all structs take just a few bytes of RAM space.
Initialization
Init function is something that is a must do in the case of non-object oriented programming languages, and actually it replaces the constructor. Inside our init function we will call the execution of the HW layer init function which initializes type of data used as translator and executes hardware resets of the device. One important thing can be also placed here. Remember the part where we said that control structs have a reg member which represents the address of the register. Now is the time to assign the proper values to them.
int mcp25625_init ( void ) { can_ctl.reg = CTL_CAN; rts_pins.reg = CTL_RTS; rx_pins.reg = CTL_RXP; tx_ctl.reg = CTL_TXB; rx0_ctl.reg = CTL_RXB0; rx1_ctl.reg = CTL_RXB1; cnf_1.reg = CTL_CNF1; cnf_2.reg = CTL_CNF2; cnf_3.reg = CTL_CNF3; if( mcp25625_hw_init() ) return MCP25625_HW_ERR; }
Switch operation mode
Probably the easiest example can be implementation of the function that switches device working mode. The datasheet says that success of this kind of operation should be checked by reading CANSTAT register and reading the part of the register which represents current working mode. So implementation might looks like this.
int mcp25625_mode ( can_opmode_t mode ) { can_ctl.reqop = mode; can_ctl.mask = CAN_REQOP; mcp25625_hw_ctl_update( ( void* )&can_ctl ); if( mcp25625_hw_ctl_get( ( void* )&can_stat ) ) if( can_stat.opmod != mode ) return MCP25625_CTL_ERR; return MCP25625_OK; }
First of all we are assigning the requested operation mode to the reqop member of the can_ctl variable which actually represents the CANCTRL register. We don't want to overwrite other settings inside this register and because of that we assign the mask member value which will be used as mask. Then by calling mcp25625_hw_ctl_update , we are actually updating only bits responsible for this setting through the SPI bus. After that we are populating the can_stat variable with the value from CANSTAT register and comparing the value provided as parameter and value gotten from CANSTAT to confirm the success of the operation.
Already implemented higher level functions will give you an idea how to do something more complex. The datasheet must be your best friend during the implementation of this functions because some more complex functions require exact order of operations to achieve success. Place it near yourself and enjoy the transfer of ones and zeroes through the SPI and CAN bus.
Summary
This was introduction how to use our library for MCP25625. The most important functions are already implemented so if you have no time to go deeper to exploring this excellent CAN controller you can follow examples and and adapt them the way you want. The desired CAN application is not far from you. Car diagnostic? Why not, at least a simple one.