Setup calibration mode#
When dealing with analog values, there is often a calibration of the hardware required to achieve the best accuracy. This example situation is a good reason for implementing a manufacturer-specific factory area.
Example Goal#
The example focus is the user object for CAN triggered write access to a manufacturer-specific area.
Object Type Idea#
The main idea is a collection of object entries, which are writable after a simple identification mechanism.
For a tiny use-case we assume to achieve a simple calibration mechanism. The application uses the calibration values to transform input values with a formula:
adcValue = (adcRaw * calFactor) / calDivisor + calOffset;
Object Entry Definitions#
We define some manufacturer specific entries in the object dictionary:
Index | Subindex | Type | Access | Value | Description |
---|---|---|---|---|---|
2F00h | 0 | UNSIGNED32 | Const | 4 | Max. Subindex |
2F00h | 1 | UNSIGNED32 | Write Only | 0 | Calibration Key |
2F00h | 2 | SIGNED32 | Read Write | 1 | Cal. Factor |
2F09h | 3 | SIGNED32 | Read Write | 1 | Cal. Divisor |
2F00h | 4 | SIGNED32 | Read Write | 0 | Cal. Offset |
To achieve the permanent calibration after setting the values, the subindex 2 to 4 must be storable in NVM.
The Number of Entries is a constant, which is read-only for the CAN network and conforms to the standard way of defining subindex 0 of an array.
The key to get the wanted functionality is the entry at subindex 1. This object entry accepts write access of a calibration key value. With the correct key, this operation enables the write access to the other calibration object entries from subindex 2 to 4. A wrong key disables the write access.
Implement Object Type#
Lets implement the calibration user type as shown in the CANopen Usage: User Object:
uint8_t calWriteAllowed = 0u;
const CO_OBJ_TYPE COTCal = { 0, 0, 0, CalWrite };
#define CO_TCAL ((CO_OBJ_TYPE*)&COTCal)
The write function is called when the CAN network writes to the related object entry via SDO request. We typically send an error, because the calibration data is read-only per default:
int16_t CalWrite(CO_OBJ *obj, struct CO_NODE_T *node, void *buf, uint32_t size)
{
/* indicate an write error */
return CO_ERR_TYPE_WR;
}
Now we want to change this function to enable the write access for the calibration values after a secret key-value is written to the object entry at subindex 1.
#define CAL_KEY 0x1f3c7a3b /* a random value for enabling write access */
int16_t CalWrite(CO_OBJ *obj, struct CO_NODE_T *node, void *buf, uint32_t size)
{
uint32_t value = *((uint32_t *)buf);
CO_ERR result = CO_ERR_TYPE_WR;
uint8_t subidx = CO_GET_SUB(obj->Key);
if (subidx == 1u) {
if (value == CAL_KEY) {
calWriteAllowed = 1u;
} else {
calWriteAllowed = 0u;
}
result = 0u;
} else {
if ((subidx != 0u) &&
(calWriteAllowed == 1u)) {
*(int32_t *)obj->Data = value;
result = 0u;
}
}
return result;
}
Implement Object Entries#
Parameter Handling#
First, we need to get a memory area for our calibration data. We get this area by creating a structure with the calibration data values:
struct CAL_MEM_T {
int32_t Factor;
int32_t Divisor;
int32_t Offset;
};
struct CAL_MEM_T CalDefaults = {
(int32_t)1u, /* factor */
(int32_t)1u, /* divisor */
(int32_t)0u /* offset */
};
struct CAL_MEM_T CalValue;
For store and restore operations, the parameter group control structure specifies the memory area and the reset type of these parameters:
const CO_PARA CalParaObj = {
0L, /* placement in non-volatile memory */
sizeof(struct CAL_MEM_T), /* size of parameter memory */
(uint8_t*)&CalValue, /* start address of parameter mem */
(uint8_t*)&CalDefaults, /* start address of default para. */
CO_RESET_NODE, /* reset type for reload parameter */
(void*)"Calibration", /* user parameter identification */
CO_PARA___E /* enable parameter storage on cmd */
};
Finally, in the standard parameter store/restore entries, we use a separate subindex for the calibration values (for example: subindex #2):
const CO_OBJ ExampleObjDir[] = {
:
{ CO_KEY(0x1010, 2, CO_UNSIGNED32|CO_OBJ____RW), CO_TPARA, (CO_DATA)&CalParaObj },
:
};
Calibration Object Entry#
We use our user type to define the calibration object entry:
const CO_OBJ ExampleObjDir[] = {
:
{ CO_KEY(0x2F00, 0, CO_UNSIGNED8 |CO_OBJ_D__RW), 0u, 4u },
{ CO_KEY(0x2F00, 1, CO_UNSIGNED32|CO_OBJ_D___W), CO_TCAL, 0u },
{ CO_KEY(0x2F00, 2, CO_UNSIGNED32|CO_OBJ____RW), CO_TCAL, (CO_DATA)&CalValue.Factor },
{ CO_KEY(0x2F00, 3, CO_UNSIGNED32|CO_OBJ____RW), CO_TCAL, (CO_DATA)&CalValue.Divisor },
{ CO_KEY(0x2F00, 4, CO_SIGNED32 |CO_OBJ____RW), CO_TCAL, (CO_DATA)&CalValue.Offset },
:
};
Well, that's it. Now we have an (unsecured) protected calibration area for manufacturer-specific data. This concept is a starting point for more secure solutions with advanced algorithms.