I am running into an Hard Fault Exception while copying some data on a micro-controller from one struct to another. I tried different implementations which should do all the same. See my code lines:
memcpy(&msg.data, data, 8);
memcpy(&msg.data, data, sizeof(*data));
memcpy(&msg.data, data, sizeof(msg.data));
msg.data = *data; // Hard Fault
The first three lines are working pretty fine. The last one ends with an Hard Fault Exception. The assembly for the lines with memcpy
is the same. The assembly for the direct assignment differs:
memcpy(&msg.data, data, sizeof(msg.data));
800c480: f107 030c add.w r3, r7, #12
800c484: 330b adds r3, #11
800c486: 2208 movs r2, #8
800c488: 6879 ldr r1, [r7, #4]
800c48a: 4618 mov r0, r3
800c48c: f7f4 f82e bl 80004ec <memcpy>
msg.data = *data; // Hard Fault
800c490: 687b ldr r3, [r7, #4]
800c492: f107 0217 add.w r2, r7, #23
800c496: cb03 ldmia r3!, {r0, r1}
800c498: 6010 str r0, [r2, #0]
800c49a: 6051 str r1, [r2, #4]
I am using the GNU Arm Embedded Toolchain 5.4.1 20160919.
Here is a minimal code example which (hopefully) shows the the problem. The data structure msg_t
must use the packed
attribute to match some hardware registers. On the micro-controller this codes ends in a Hard Fault at the line with msg.data = *data;
#include <stdint.h>
#include <string.h>
#include <stdio.h>
typedef struct canData_s {
uint8_t d1;
uint8_t d2;
uint8_t d3;
uint8_t d4;
uint8_t d5;
uint8_t d6;
uint8_t d7;
uint8_t d8;
} canData_t;
#pragma pack(push, 1)
typedef struct msg_s {
uint32_t stdId;
uint32_t extId;
uint8_t ide;
uint8_t rtr;
uint8_t dlc;
canData_t data; // 8 Bytes
uint8_t navail; // not available
uint32_t timestamp;
} msg_t;
#pragma pack(pop)
void setData(canData_t *data) {
msg_t msg;
msg.data = *data;
// Do something more ...
printf("D1:%d", msg.data.d1);
// ...
}
int main() {
canData_t data;
memset(&data, 0, 8);
setData(&data);
}
Why does copying the structure by a direct assignment fail?
If the structures are of compatible types, yes, you can, with something like: memcpy (dest_struct, source_struct, sizeof (*dest_struct)); The only thing you need to be aware of is that this is a shallow copy.
In C/C++, we can assign a struct (or class in C++ only) variable to another variable of same type. When we assign a struct variable to another, all members of the variable are copied to the other struct variable.
When you use non-standard #pragma pack
you force the compiler to store the struct without any padding. The struct members before data
are in groups of 4+4+3, then data
at byte 11, which is misaligned.
So you force data
to always be allocated misaligned, which can cause hardware exceptions on some CPUs if this is accessed as a word (32 bits). The code msg.data = *data;
generated by the compiler might assume that when you copy two structs, they are always properly aligned, as that's normally the case. And the most efficient implementation of the copy would work with 32 bit chunks of data, so that's what it will use.
The question here is why this struct is packed to begin with, since it can neither be a hardware register mapping nor a data protocol mapping. Things like CAN-bus IDE and RTR are just single bits; I very much doubt any CAN controller reserves a whole 8 bit register for that. For example ST's "bxCAN" controller place these as individual bits in the CAN_TIxR register (CAN TX mailbox identifier register). Every other CAN controller on the market will behave similarly.
As for the CAN frame itself, you cannot directly memory-map that. The CAN controller will grab the raw CAN frame and place it in its own memory-mapped registers.
Either re-make this struct without padding or use the actual CAN controller registers as provided by your hardware.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With