© Warren Gay 2018

Warren Gay, Beginning STM32, https://doi.org/10.1007/978-1-4842-3624-6_19

19. CAN Bus Software

Warren Gay

(1)St. Catharines, Ontario, Canada

The CAN bus demonstration in the previous chapter illustrated three STM32 MCUs sharing messages on a common bus. None were masters and none were slaves. All of this was orchestrated with the help of the STM32 CAN bus peripheral and the libopencm3 device driver.

This chapter will discuss the use of the libopencm3 driver API so that you can build CAN bus applications of your own. When combined with the use of FreeRTOS, you will have a convenient environment from which to program more-complex creations.

Initialization

The project source modules are located in the following directory:

$ cd ~/stm32f103c8t6/rtos/can

The most demanding part of setting up the CAN bus peripheral is the configuration and initialization of it. Listing 19-1 illustrates the initialize_can() function that was provided in source module canmsgs.c.

Listing 19-1 The CAN Initialization Code
0090: void
0091: initialize_can(bool nart,bool locked,bool altcfg) {
0092:
0093:   rcc_periph_clock_enable(RCC_AFIO);
0094:   rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_CAN1EN);
0095:
0096:   /******************************************************
0097:    * When:
0098:    *    altcfg     CAN_RX=PB8,  CAN_TX=PB9
0099:    *    !altcfg    CAN_RX=PA11, CAN_TX=PA12
0100:    *****************************************************/
0101:   if ( altcfg ) {
0102:       rcc_periph_clock_enable(RCC_GPIOB);
0103:       gpio_set_mode(GPIOB,GPIO_MODE_OUTPUT_50_MHZ,
                GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN,
                GPIO_CAN_PB_TX);
0104:       gpio_set_mode(GPIOB,GPIO_MODE_INPUT,GPIO_CNF_INPUT_FLOAT,
                GPIO_CAN_PB_RX);
0105:
0106:       gpio_primary_remap(   // Map CAN1 to use PB8/PB9
0107:               AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_OFF, // Optional
0108:               AFIO_MAPR_CAN1_REMAP_PORTB);
0109:   } else  {
0110:       rcc_periph_clock_enable(RCC_GPIOA);
0111:       gpio_set_mode(GPIOA,GPIO_MODE_OUTPUT_50_MHZ,
                GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN,GPIO_CAN_TX);
0112:       gpio_set_mode(GPIOA,GPIO_MODE_INPUT,
                GPIO_CNF_INPUT_FLOAT,GPIO_CAN_RX);
0113:
0114:       gpio_primary_remap( // Map CAN1 to use PA11/PA12
0115:           AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_OFF, // Optional
0116:       AFIO_MAPR_CAN1_REMAP_PORTA);
0117:   }
0118:
0119:   can_reset(CAN1);
0120:   can_init(
0121:       CAN1,
0122:       false,    // ttcm=off
0123:       false,    // auto bus off management
0124:       true,     // Automatic wakeup mode.
0125:       nart,     // No automatic retransmission.
0126:       locked,   // Receive FIFO locked mode
0127:       false,    // Transmit FIFO priority (msg id)
0128:       PARM_SJW, // Resynch time quanta jump width (0..3)
0129:       PARM_TS1, // segment 1 time quanta width
0130:       PARM_TS2, // Time segment 2 time quanta width
0131:       PARM_BRP, // Baud rate prescaler for 33.333 kbs
0132:       false,    // Loopback
0133:       false);   // Silent
0134:
0135:   can_filter_id_mask_16bit_init(
0137:       0,                         // Filter bank 0
0138:       0x000 << 5, 0x001 << 5,    // LSB == 0
0139:       0x000 << 5, 0x001 << 5,    // Not used
0140:       0,                         // FIFO 0
0141:       true);
0142:
0143:   can_filter_id_mask_16bit_init(
0145:       1,                         // Filter bank 1
0146:       0x010 << 5, 0x001 << 5,    // LSB == 1 (no match)
0147:       0x001 << 5, 0x001 << 5,    // Match when odd
0148:       1,                         // FIFO 1
0149:       true);
0150:
0151:   canrxq = xQueueCreate(33,sizeof(struct s_canmsg));
0152:
0153:   nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
0154:   nvic_enable_irq(NVIC_CAN_RX1_IRQ);
0155:   can_enable_irq(CAN1,CAN_IER_FMPIE0|CAN_IER_FMPIE1);
0156:
0157:   xTaskCreate(can_rx_task,"canrx",400,NULL,
            configMAX_PRIORITIES-1,NULL);
0158: }

This function provides initialization in the following basic steps:

  1. The AFIO subsystem’s clock is enabled (line 93). This is necessary so that we can chose which GPIOs are used for the CAN bus ports.

  2. The CAN bus peripheral’s clock is enabled (line 94). This is required for the peripheral to function.

  3. The appropriate GPIO has its clock enabled (line 102 or 110, depending upon the configuration chosen by Boolean argument altcfg).

  4. The GPIO output mode is chosen for CAN_TX (line 103 or 111).

  5. The GPIO input mode is chosen for CAN_RX (line 104 or 112).

  6. The AFIO mapping is chosen for the CAN_TX and CAN_RX lines (lines 106 to 108, or lines 114 to 116).

  7. The libopencm3 routine can_reset() is called to initialize and configure the CAN bus peripheral (lines 119 to 133).

  8. CAN filter bank 0 is configured in lines 135 to 141 to determine where certain messages should go.

  9. CAN filter bank 1 is configured in lines 143 to 149 to determine where other messages should go.

  10. A FreeRTOS receive queue named canrxq is created in line 151.

  11. The STM32 NVIC has two interrupt channels enabled in lines 153 and 154.

  12. The CAN bus peripheral has the FIFO message pending interrupts enabled for FIFO 0 and 1 (line 155).

  13. Finally, a receiving task is created in line 157.

There is obviously quite a bit of detail in this procedure. Let’s break down some of the steps.

can_init()

The can_init() function is provided by libopencm3 and requires several arguments to configure the device. Let’s examine the calling arguments in more detail. The arguments provided are as follows:

  1. Argument CAN1 indicates which peripheral to use. There is only one available for the STM32F103C8T6.

  2. This argument is supplied with false to indicate that we are not using time-triggered communication mode.

  3. This argument is supplied with false to indicate that we are not using automatic bus-off mode (if too many errors occur, the bus can be auto-disabled).

  4. This argument is supplied with true to indicate that we want automatic wakeup mode, should the MCU be put to sleep.

  5. This argument is supplied with our called argument nart. When true, this indicates that we do not want the CAN peripheral auto-retransmit when an error is detected.

  6. The argument is supplied with our called argument locked. When true, it means that when a receive FIFO becomes full, no new message will displace an existing message (the FIFO is locked). When false, new messages can displace existing messages when the FIFO is full.

  7. This argument is supplied as false to have outgoing messages be given priority according to their message ID. Otherwise, messages are transmitted in chronological order.

  8. PARM_SJW

  9. PARM_TS1

  10. PARM_TS2 defines CAN synchronization parameters.

  11. PARM_ BRP is declared as 78 and canmsgs.h so that the effective baud rate is 33.333 kbs.

  12. The second-to-last argument is supplied with false to disable the loopback capability of the peripheral.

  13. The last argument is supplied with false so that it operates in “normal mode.” When in silent mode, the peripheral can receive remote data but cannot initiate messages (it is silent).

CAN Receive Filters

The CAN bus peripheral has the ability to filter messages of interest. If you imagine a large set of bus-connected communicators, it becomes evident that a lot of message traffic will be received. Normally, not every node is interested in all messages. Processing every message would eat away at the available CPU budget.

The CAN peripheral supports two FIFO queues for receiving messages. With the help of filtering, this demonstration arranges for even-numbered message IDs to land in FIFO 0 and odd-numbered messages in FIFO 1. This is arranged by the configuration of filter banks 0 and 1.

The call to can_filter_id_mask16bit_init() in lines 135 to 141 arranges a set of messages to land in FIFO 0 (line 140). Argument two in this example is declaring the configuration of filter bank 0 (line 137). The last argument (true) simply enables the filter.

Arguments three (line 138) and four (line 139) define the actual filter ID value and bit mask to use. These are 16-bit filters, but the filter is 32 bits wide. For this reason, two identical filters are used:

  • 0x000 is the resulting ID to match against after applying the mask.

  • 0x001 is the bit mask to be applied to the ID before comparing.

Both of these arguments must be shifted up five bits to the left in order to left justify the 11-bit identifiers in the 16-bit field.

In the second configured filter (lines 143 to 149) we have the same mask value (0x001) but compare two different ID values:

  • 0x010 is a “no match” ID.

  • 0x001 is the odd value after masking.

If the mask 0x001 is applied to an ID, it matches 0x001 when the ID is odd. However, no matter what ID is supplied after being masked with 0x001, it will never match the value 0x010 given. This is simply another way of disabling the second unused filter.

As configured, a message will always be odd or even and will wind up in one of the FIFO receive queues (CAN peripheral FIFO).

There are several other possibilities for specifying filters, including using 32-bit values so that extended ID values can be compared. The reader is encouraged to review the libopencm3 API documentation and the STMicroelectronics RM0008 reference manual, section 24.7.4, for more information.

CAN Receive Interrupts

Each CAN FIFO (first in first out queue) has its own ISR. This permits the designer to allocate different interrupt priorities to each FIFO queue. In this demo, we treat both identically, so Listing 19-2 illustrates the interlude routines used to redirect the code to one common function named can_rx_isr().

Listing 19-2 The CAN Receive Interlude ISRs
0058: void
0059: usb_lp_can_rx0_isr(void) {
0060:     can_rx_isr(0,CAN_RF0R(CAN1)&3);
0061: }


0067: void
0068: can_rx1_isr(void) {
0069:     can_rx_isr(1,CAN_RF1R(CAN1)&3);
0070: }

The macros CAN_RF0R() and CAN_RF1R() allow the code to determine the length of the FIFO queues. The common code for the CAN receive ISR is shown in Listing 19-3.

Listing 19-3 The Common CAN Receive ISR
0029: static void
0030: can_rx_isr(uint8_t fifo,unsigned msgcount) {
0031:     struct s_canmsg cmsg;
0032:     bool xmsgidf, rtrf;
0033:
0034:     while ( msgcount-- > 0 ) {
0035:         can_receive(
0036:             CAN1,
0037:             fifo,                   // FIFO # 1
0038:             true,                   // Release      
0039:             &cmsg.msgid,
0040:             &xmsgidf,               // true if msgid is extended
0041:             &rtrf,                  // true if requested transmission
0042:             (uint8_t *)&cmsg.fmi,   // Matched filter index
0043:             &cmsg.length,           // Returned length
0044:             cmsg.data,
0045:             NULL);                  // Unused timestamp
0046:         cmsg.xmsgidf = xmsgidf;
0047:         cmsg.rtrf = rtrf;
0048:         cmsg.fifo = fifo;
0049:         // If the queue is full, the message is lost
0050:         xQueueSendToBackFromISR(canrxq,&cmsg,NULL);
0051:     }
0052: }

The general flow of the code is as follows:

  1. Receive the message (lines 35 to 45).

  2. Queue the message to FreeRTOS queue canrxq (line 50).

  3. Repeat until there are no more messages (line 34).

To understand the other details, we need to know about the structures involved. These are illustrated in Listing 19-4.

Listing 19-4 Message Structures Found in canmsgs.h
0020: struct s_canmsg {
0021:   uint32_t    msgid;        // Message ID
0022:   uint32_t    fmi;          // Filter index
0023:   uint8_t     length;       // Data length
0024:   uint8_t     data[8];      // Received data
0025:   uint8_t     xmsgidf : 1;  // Extended message flag
0026:   uint8_t     rtrf : 1;     // RTR flag
0027:   uint8_t     fifo : 1;     // RX Fifo 0 or 1
0028: };
0029:
0030: enum MsgID {
0031:   ID_LeftEn = 100,         // Left signals on/off (s_lamp_en)
0032:   ID_RightEn,              // Right signals on/off (s_lamp_en)
0033:   ID_ParkEn,               // Parking lights on/off (s_lamp_en)
0034:   ID_BrakeEn,              // Brake lights on/off (s_lamp_en)
0035:   ID_Flash,                // Inverts signal bulb flash
0036:   ID_Temp,                 // Temperature
0037:   ID_HeartBeat = 200,      // Heartbeat signal (s_lamp_status)
0038:   ID_HeartBeat2            // Rear unit heartbeat
0039: };
0040:
0041: struct s_lamp_en {
0042:   uint8_t     enable : 1;  // 1==on, 0==off
0043:   uint8_t     reserved : 1;
0044: };
0045:
0046: struct s_temp100 {
0047:   int         celciusx100;  // Degrees Celcius x 100
0048: };
0049:
0050: struct s_lamp_status {
0051:   uint8_t     left : 1;     // Left signal on
0052:   uint8_t     right : 1;    // Right signal on
0053:   uint8_t     park : 1;     // Parking lights on
0054:   uint8_t     brake : 1;    // Brake lines on
0055:   uint8_t     flash : 1;    // True for signal flash
0056:   uint8_t     reserved : 4;
0057: };

Essentially, the message is received into the struct s_canmsg. See lines 35 to 45 of Listing 19-3. Some parts have to be loaded and then copied to the structure. For example, the structure member xmsgidf is a 1-bit-sized member, so it is received in a local variable named xmsgidf (line 40) and then copied to cmsg.xmsgidf in line 46. Other members are copied into the structure in lines 47 and 48. By the time execution continues at line 50 the structure is fully populated and then copied to the queue. Notice that the FreeRTOS routine called is xQueueSendToBackFromISR(); i.e., ending in “FromISR().” This is necessary since special arrangements often need to be made in an ISR due to their asynchronous nature.

The main payload is carried in the data[8] array, and its active length is given by member length in this program. Our application uses truly small messages.

The message is indicated by the message ID. This is documented in the following:

0030: enum MsgID {
0031:   ID_LeftEn = 100,    // Left signals on/off (s_lamp_en)
0032:   ID_RightEn,         // Right signals on/off (s_lamp_en)
0033:   ID_ParkEn,          // Parking lights on/off (s_lamp_en)
0034:   ID_BrakeEn,         // Brake lights on/off (s_lamp_en)
0035:   ID_Flash,           // Inverts signal bulb flash
0036:   ID_Temp,            // Temperature
0037:   ID_HeartBeat = 200, // Heartbeat signal (s_lamp_status)
0038:   ID_HeartBeat2       // Rear unit heartbeat
0039: };

Pop quiz: Which is the highest-priority message in this set?

Answer: ID_LeftEn (with value 100).

Why? Because this is the lowest (defined) message ID in the set. Recall that with the nature of CAN dominant bits the lowest message ID will always win an arbitration contest.

These are message types used by our demo program. Message ID value ID_HeartBeat is an “I’m alive” message from the front controller, while ID_HeartBeat2 is a similar message from the rear controller. Our demo doesn’t do anything with these messages when received, but with more code the main controller could warn if the front or rear controller wasn’t sending a message at regular intervals.

Message ID values ID_LeftEn, ID_RightEn, ID_ParkEn, and ID_BrakeEn indicate a lamp-control message. The struct s_lamp_en carries the intended action. Its member enable indicates whether the message is to turn on or off a lamp. This data is carried in the data[] array member of s_canmsg:

0041: struct s_lamp_en {
0042:   uint8_t     enable : 1;    // 1==on, 0==off
0043:   uint8_t     reserved : 1;
0044: };

The message ID_Temp is used both to request a temperature and to receive one. The main control unit will request a temperature reading by sending ID_Temp, with the member rtrf set to true. When the rear control unit receives this message, it will take a reading and reply with the rtrf flag set to false. The temperature returned is carried in the data[] member as a struct s_temp100:

0046: struct s_temp100 {
0047:   int        celciusx100;    // Degrees Celcius x 100
0048: };

Application Receiving

Once the ISR queues the data message s_canmsg, something must pull messages out of the queue. In module canmsgs.c there is a task defined for this purpose:

0076: static void
0077: can_rx_task(void *arg __attribute((unused))) {
0078:     struct s_canmsg cmsg;
0079:
0080:     for (;;) {
0081:         if ( xQueueReceive(canrxq,&cmsg,portMAX_DELAY) == pdPASS )
0082:             can_recv(&cmsg);
0083:     }
0084: }

This small task simply pulls messages from canrxq that were queued by the ISR. If there are no messages in the queue, the task will block forever due to the timeout argument portMAX_DELAY in line 81. If a message is successfully pulled from the queue, the application function can_recv() is called with it (not to be confused with the libopencm3 routine named can_receive()).

Processing the Message

The application is made aware of incoming CAN messages when the can_recv() function is called by the module canmsgs.c. This is performed outside of an ISR call, so most programming functions should be safe to use. Listing 19-5 illustrates the function declared in rear.c.

Listing 19-5 Processing a Received CAN Message in the Application (from rear.c)
0119: void
0120: can_recv(struct s_canmsg *msg) {
0121:     union u_msg {
0122:         struct s_lamp_en    lamp;
0123:     } *msgp = (union u_msg *)msg->data;
0124:     struct s_temp100 temp_msg;
0125:
0126:     gpio_toggle(GPIO_PORT_LED,GPIO_LED);
0127:
0128:     if ( !msg->rtrf ) {
0129:         // Received commands:
0130:         switch ( msg->msgid ) {
0131:         case ID_LeftEn:
0132:         case ID_RightEn:
0133:         case ID_ParkEn:
0134:         case ID_BrakeEn:
0135:         case ID_Flash:
0136:             lamp_enable((enum MsgID)msg->msgid,msgp->lamp.enable);
0137:             break;
0138:         default:
0139:             break;
0140:         }
0141:     } else {
0142:         // Requests:
0143:         switch ( msg->msgid ) {
0144:         case ID_Temp:
0145:             temp_msg.celciusx100 = degrees_C100();
0146:             can_xmit(ID_Temp,false,false,sizeof temp_msg,&temp_msg);
0147:             break;
0148:         default:
0149:             break;
0150:         }
0151:     }
0152: }

The message is passed into can_recv() with a pointer to the s_canmsg structure, which is populated with the received data. Because the data member msg->data is interpreted based upon its message ID, the union u_msg is declared in lines 121 to 123. This permits the programmer to access the msg->data array as a struct s_lamp_en when it is needed.

To show sign of life, line 126 toggles the onboard LED (PC13). Normal messages will not have the msg->rtrf flag set (line 128). In this case, we expect the usual lamp commands (lines 130 to 137).

Otherwise, when msg->rtrf is true, this represents a request for the rear module to read the temperature and respond with it (lines 143 to 150). The function degrees_C100() returns the temperature in degrees Celsius times one hundred. This is simply transmitted by line 146. Note that the third argument of the call is the RTR flag, which is sent as false (this is the response). The function can_xmit() is declared in canmsgs.c, not to be confused with the libopencm3 routine can_transmit().

Be mindful that can_recv() is called as part of another task. This requires safe inter-task communication.

Sending CAN Messages

Sending messages is easy, since we simply need to call the libopencm3 routine can_transmit():

0018: void
0019: can_xmit(uint32_t id,bool ext,bool rtr,uint8_t length,void *data) {
0020:
0021:   while ( can_transmit(CAN1,id,ext,rtr,length,(uint8_t*)data) == -1 )
0022:       taskYIELD();
0023: }

In the Blue Pill hardware there is only one CAN bus controller, so CAN1 is hardcoded as argument one here. The message ID is passed through id, the extended address flag is passed through ext, and the request flag rtr is passed as argument four. Lastly, the length of the data and the pointer to the data are supplied. If the call fails by returning -1, taskYIELD() is called to share the CPU time. The call will fail if the sending CAN bus peripheral queue is full.

This brings up an important point to keep in mind. Upon successful return from can_transmit(), the caller cannot assume that the message has been sent yet. Recall that in our configuration we declared whether messages are sent in priority sequence or in chronological sequence. But the bus can also be busy, delaying the actual sending of our messages. Further, if our message(s) are lower priority (higher message ID values) than others on the bus, our CAN peripheral must wait until it can win bus arbitration.

Summary

The remainder of the demo is routine C code. The reader is encouraged to review it. By packaging the CAN bus application API in modules canmsgs.c and canmsgs.h, the task of writing the application becomes easier. It also saves time by using common tested code.

This demo has only scratched the surface of what can be done on the CAN bus. Some folks may want to listen in on their vehicles, but a word of caution is warranted. Some CAN bus designs, like GMLAN (General Motors Local Area Network), can include 12-volt signal spikes for use as a wakeup signal. There are likely a number of other variations of that theme.

The CAN bus has been applied to a number of other applications, such as factory and elevator controls. After working with this demo project, you can entertain new design ideas.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.70.255