Electronic – STM32F3 CAN Hardware not Entering Init Mode

armccanmicrocontrollerstm32

I'm trying to write a bare metal driver for the STM32F302 on a Nucleo-64 board. I'm having trouble getting past the very first steps and getting the hardware to set the Initialization Acknowledgment. This driver is being written to run side by side with the mbed OS framework if that detail is important, I had issues whereby the firmware would go to an error state if I called the CAN constructor they provide (admittedly the board isn't technically supported under mbed OS 5, but I've been told by an engineer with ST that this is due to the amount of static RAM).

Here's the snippets of code that are relevant:

void can_initialize(unsigned int frequency)
{
    // Setup the CAN registers
    RCC->APB1ENR &= ~RCC_APB1ENR_USBEN;
    RCC->APB1ENR |= RCC_APB1ENR_CANEN;

    MY_CAN_PORT->MCR |= CAN_MCR_RESET;

    // Set to init mode
    can_setMode(CAN_MODE::INIT);

    // Enable transmit and receive
    MY_CAN_PORT->MSR |= (CAN_MSR_RXM | CAN_MSR_TXM);

    // Set the frequency
    can_setFrequency(frequency);
}

void can_setMode(CAN_MODE new_mode)
{
    switch (new_mode)
    {
        case CAN_MODE::SLEEP:
            MY_CAN_PORT->MCR |= CAN_MCR_SLEEP;
            // Wait for acknowledgement from hardware
            // SLAK = 1, INAK = 0
            while (~(MY_CAN_PORT->MSR & CAN_MSR_SLAK) 
                   || (MY_CAN_PORT->MSR & CAN_MSR_INAK));
            break;
        case CAN_MODE::INIT:
            MY_CAN_PORT->MCR |= CAN_MCR_INRQ;
            if (my_mode == CAN_MODE::SLEEP)
            {
                MY_CAN_PORT->MCR &= ~CAN_MCR_SLEEP;
            }
            // Wait for the acknowledgement from hardware
            // INAK = 1 indicates initialization mode
            while (~(MY_CAN_PORT->MSR & CAN_MSR_INAK));
            break;
        case CAN_MODE::NORMAL:
            // first make sure we aren't in loopback mode
            if (MY_CAN_PORT->BTR & CAN_BTR_LBKM)
            {
                // go to init so we can modify loopback mode
                can_setMode(CAN_MODE::INIT);
                // clear loopback mode
                MY_CAN_PORT->BTR &= ~CAN_BTR_LBKM;
                can_setMode(CAN_MODE::NORMAL);
            }
            MY_CAN_PORT->MCR &= ~(CAN_MCR_INRQ);
            // Wait for acknowledgement from hardware
            // SLAK = 0, INAK = 0
            while ((MY_CAN_PORT->MSR & CAN_MSR_SLAK) 
                    || (MY_CAN_PORT->MSR & CAN_MSR_INAK));
            break;
        case CAN_MODE::LOOPBACK:
            // go to init so we can modify loopback mode
            can_setMode(CAN_MODE::INIT);
            // set loopback mode
            MY_CAN_PORT->BTR |= CAN_BTR_LBKM;
            can_setMode(CAN_MODE::NORMAL);
            break;
    }

    my_mode = new_mode;
}

I'm planning to use the device in Loopback mode for testing so I currently have nothing externally connected. Do I need a two node CAN bus just to put the peripheral in Init mode?

I noted that there's an extremely similar post and I've tried some of the proposed solutions here: CAN Initialization Timeout Error in STM32F4

Things I've Tried in HW:

  • I've attempted to connect TX and RX directly, though since it isn't sending anything (I think) that shouldn't be required.
  • I've also attempted to tie CANRX to 3V3 in order to emulate the bus being in a recessive state for the 11 bits, but this didn't solve the issue either.
  • Tried on a second identical board.

Short of connecting up a second node I'm not sure what to do from here. If there's any tricks to these boards I'm not thinking of or if there are more details required to diagnose the problem let me know!

Best Answer

This code is wrong:

while(~(MY_CAN_PORT->MSR & CAN_MSR_INAK));

~ is not the logical NOT operator, but the bitwise complement operator!

So this does not mean "while init ack is not set", but it means "while 1". When INAK (binary mask 0x00000001) is set you get 0xFFFFFFFE and when it is not set, you get 0xFFFFFFFF. Both evaluate to true/1.

Surely your debugger should hang up here?

Fix it by using logical comparisons instead:

while((MY_CAN_PORT->MSR & CAN_MSR_INAK)==0)
  ;

Note that the placement of the semi colon on a line of its own is good practice, so that the reader can tell that this is intentionally an empty loop and you didn't just dump the semicolon at the end of the line by habit/accident.