So there I was, reviewing a piece of code I wrote a while ago, when I noticed that there was a bug in it. "Wait a minute", says I, "how is this code working? It shouldn't be!".

The code in question, which is for an ATmega32M1:
typedef enum {
CAN_BUS_SPEED_100,
CAN_BUS_SPEED_125,
CAN_BUS_SPEED_200,
CAN_BUS_SPEED_250,
CAN_BUS_SPEED_500
} can_bus_speed_enum_t;
typedef struct {
can_bus_speed_enum_t bus_speed;
struct {
uint8_t canbt1;
uint8_t canbt2;
uint8_t canbt3;
} reg_vals;
} can_bt_reg_t;
typedef struct {
struct {
can_bus_speed_enum_t bus_speed;
} can;
/* other members omitted for brevity */
} config_t;
config_t config = {
{
CAN_BUS_SPEED_500
}
};
static const can_bt_reg_t can_bus_speed_bt_conf[] PROGMEM = {
{ CAN_BUS_SPEED_100, { 0x08, 0x0C, 0x37 } },
{ CAN_BUS_SPEED_125, { 0x06, 0x0C, 0x37 } },
{ CAN_BUS_SPEED_200, { 0x02, 0x0E, 0x4B } },
{ CAN_BUS_SPEED_250, { 0x02, 0x0C, 0x37 } },
{ CAN_BUS_SPEED_500, { 0x00, 0x0C, 0x36 } }
};
void init_can() {
for(size_t i = 0; i < (sizeof(can_bus_speed_bt_conf) / sizeof(can_bus_speed_bt_conf[0])); i++) {
if(config.can.bus_speed == can_bus_speed_bt_conf[i].bus_speed) {
CANBT1 = pgm_read_byte(&can_bus_speed_bt_conf[i].reg_vals.canbt1);
CANBT2 = pgm_read_byte(&can_bus_speed_bt_conf[i].reg_vals.canbt2);
CANBT3 = pgm_read_byte(&can_bus_speed_bt_conf[i].reg_vals.canbt3);
break;
}
}
if(CANBT1 == 0 && CANBT2 == 0 && CANBT3 == 0) return;
/* rest of CAN peripheral initialisation... */
}
int main(void) {
init_can();
/* etc... */
}
Spotted the bug? It's in this line within
init_can():
if(config.can.bus_speed == can_bus_speed_bt_conf[i].bus_speed) {
Yes, I forgot to surround the access of
can_bus_speed_bt_conf[i].bus_speed with a
pgm_read_byte() call, necessitated due to that variable being declared as PROGMEM. So, it shouldn't work properly, because without that, the comparison will not be against the correct value. What I think should happen is that some random value - whatever resides at the same address in RAM as where the variable actually lives in flash - will be used instead, and the comparison will fail at every loop iteration; this will leave the CANBTn registers uninitialised, and so the later test for that (registers having the default value of zero) will be true, and the function will exit prematurely, leaving the CAN peripheral unconfigured and still in the standby state.
However, the bizarre thing is,
this code works! In my testing so far, the code appears to successfully set up the correct register values (or, at least,
some kind of workable values) for 500kbps operation, configures the rest of the CAN peripheral, and I am receiving stuff from the bus perfectly fine. I just don't understand how...

The only reason I can think of for this to work is by accident or happenstance: that when it reads
can_bus_speed_bt_conf[i].bus_speed it's looking at a RAM area with a value of 0x04, which matches the
CAN_BUS_SPEED_500 enum member's value. If the value in memory was something else that happened to match any other enum value, yes it would initialise the registers, but for the wrong CAN bus speed, and I wouldn't have a working system - which isn't the case.
So, why does this code work? How can a PROGMEM variable accessed without using a
pgm_read_* function yield the correct value still?