SLLA652 April 2025 TCAN2410-Q1 , TCAN2411-Q1 , TCAN2450-Q1 , TCAN2451-Q1 , TCAN2845-Q1 , TCAN2847-Q1 , TCAN2855-Q1 , TCAN2857-Q1
With an overview of how to program the device – this is time to step outside of the part and look into how to simplify the configuration of the device from a firmware perspective. In this section a general overview of what type of firmware is needed to adequately work with this device, suggested type definitions to simplify the overall programming, as well as some general advice for features that can require a bit more complexity than simple getter and setter functions.
The vast majority of configuration functions on this device are a simple act of reading and/or writing registers with some exceptions w.r.t. selective wake and the watchdog timer. That means the bulk of the firmware is largely just going to be getter and setter functions and in the simplest of implementations have a generic type definition for register structure as well as one function for reading and one function for writing (which is going to be built upon any additional SPI driver code needed for the controller). In short there needs to be at least 4 different groups of files when working with this device for best results: a register definition map (.h file only) that defines base register addresses as a constant, a data structure definition file (.h file only) which defines the data types and structures that are used by the firmware, a SPI driver file (.h and .c) which can vary based on controller but is used to interact with the SPI bus of the SBC, and finally a main library file (.h and .c) that contain all getter and setter functions + any special use functions that go beyond reading and writing functions. This library can be further integrated into the final firmware for the system.
From the four suggested file groups listed previously, there are suggestions on how each one can be structured. In the first suggested file – the register map – needs to essentially follow the following guidelines.
/*header comment*/
#ifndef TCAN28_REGS_CONSTS
#define FIRST_REG_NAME baseAddress
...
#define LAST_REG_NAME baseAddress
/*any other constants that can be useful to define in application such as
*specific configuration bit patterns used in application*/
/*the following Bit definitions are optional but can be used to generate bit patterns without havingto go through the bitwise operators*/
#define BIT0 0x01
#define BIT1 0x02
#define BIT2 0x04
#define BIT3 0x08
#define BIT4 0x10
#define BIT5 0x20
#define BIT6 0x40
#define BIT7 0x80
#endif
The next file is going to be a little more involved and that is the data structure definition file. At the simplest form only 1 register data structure needs to be defined – and that is a generic register.
/*Data Structure header Comment - simple Data Structure*/
typedef struct
{
union
{
uint8_t word;
struct
{
uint8_t Bit0: 1;
uint8_t Bit1: 1;
...
uint8_t Bit6: 1;
uint8_t Bit7: 1;
};
};
}GenReg;
To cover what this code snippet does a look line by line can be taken. The first line states typedef struct – which what that is saying is that a custom struct is being defined – this can be used by creating a variable of “GenReg” type (as listed in last line of example). Taking a look at the second portion of the structure definition comprises of a union of an unsigned 8-bit integer and a structure of more unsigned 8-bit integers; the structure contains data members that resemble specific register fields – in the generic example each of the 8 bits in the byte are represented, but is not directly required. The union has the structure’s members and the uint8_t byte to reside in the same memory location to allow for fine control of specific register fields or control over the entire byte. The data members are arranged by LSB – in the example that means the LSB of the byte is represented by the first data member, if the length was greater than 1, call this N, then the first data member represents the least significant N bits of the byte. This is important that within the union the main byte and the data members within the structure have the same number of bits, in this case 8, to avoid any unforeseen behavior.
Using these types of data structures is also straightforward when passed by reference – the following example gives a basic use case of the previously defined data type.
/**Example Usage of Generic Register Data Structure*
*Assume that previously defined typedef structure is defined elsewhere in file*
*For example, both read/write are in one function – these can be split into a read and write function*/
int genRegInteract(GenReg* gr, uint8_t bitField, uint8_t bitData, uint8_t readWriteBit)
{
int result;
if(readWriteBit == 0) //read mode
{
switch(bitField)
{
case 0:
result = gr->Bit0;
case 1:
result = gr->Bit1;
...
case 6:
result = gr->Bit6;
default:
result = gr->Bit7;
}
}
else // write mode
{
switch(bitField)
{
case 0:
gr->Bit0 = bitData;
result = gr->Bit0;
case 1:
gr->Bit1 = bitData;
result = gr->Bit1;
...
case 6:
gr->Bit6 = bitData;
result = gr->Bit6;
default:
gr->Bit7 = bitData;
result = gr->Bit7;
}
}
return result;
}
int main()
{
//create instance of typedef struct
GenReg regGen;
//set all 8 bits to 0b1 using the main uint8_t variable
//Optional but gives known default value
regGen.word = 0xFF;
//read back byte
uint8_t genRegByte = regGen.word //Byte in genReg = 0xFF;
//perform read action on bit in position 4 (4th Most Significant Bit)
int readOneResult = genRegInteract(®Gen, 4, 0, 0);
//perform write action on bit in position 7 (Most Significant Bit)
int writeEchoOne = genRegInteract(®Gen,7,0,1);
//access main byte
uint8_t genRegByte = regGen.word// Byte is set to 0x7F now
//return finish code
return 0;
}
This data structure definition allows quick construction and use of register writes and register reads that can be specified down to a specific bit field. The previous example kept every bit individual – but register specific type definitions can be made from this template with slight modifications and not only can code readability be increased but this can add an extra layer of protection from sending data to the incorrect location – however if this method is pursued there can be a lot of data-structures defined in firmware – so for memory limited applications a single register definition can be the best idea.
The next major file group is the SPI driver code to actually interact with the SBC – this code is extremely dependent on the MCU used in the system as there can be slightly different for every application. There are a few commonalities that can be shared across the SPI driver code – namely they need to be able to perform a SPI read and a SPI write – so at the most simple there can only need to be 2 functions – read and write. Most MCUs have documentation on how to access the 4-Wire SPI modules and that needs to be core of any SPI driver for these SBCs – all the other functionality needed can be handled by the other file groups.
Finally – the core backbone of any firmware stack for the SBC – the main library file. This is going to be comprised of the function definitions and linking to other needed file groups (registers, data structures, and SPI driver) within the .h file. The .C file can contain all the implementation details needed – this is the core file that needs to be used in the system firmware stack to integrate communication and control of the SBC. The good news is that the vast majority of the main library file is comprised of simple getter and setter functions that can read and write to the abstracted register data types and depending on specific use case this can be all the main library functions need to do – however there are a few caveats where simple getters/setters can not be sufficient – this is with respect to partial networking/selective wake and watchdog timer control.
For partial networking the actual programming flow is still essentially just getters/setters, but there are much more than 1 selective wake register so even though each data-structure representation of a register can be used to create multiple data types to be used during partial networking configuration this is usually designed for to create a separate struct just for partial networking configuration if used. The example starter code that is available for both the TCAN28xx and TCAN24xx families uses the following partial network struct for selective wake configuration.
//States TCAN28xx, but applicable to TCAN24xx as well
typedef struct TCAN28xx_PN_Config
{
//ID: the ID to look for selective wake.
//Right justified whether 11 or 29 bits.
//If Using Extended ID, make sure to set IDE to 1
uint32_t ID: 29;
//IDE: Extended ID used. Set to 1 if using 29 bit IDs
//Valid values are 0 or 1.
uint8_t IDE: 1;
//IDMask: The Value to be used for masking the ID.
//All 0's means all bits must match exactly.
//A 1 means that bit is ignored in the ID field for ID Verification
uint32_t IDMask: 29;
//DLC: A DLC field value to match
// Valid Values are 0,1
uint8_t DLC: 4;
//Data Mask Enable. Set to 1 if using a data field mask. Turning this
//on requires the DLC to match, and the data fields are masks.
//A 1 means match this bit in the data fields. A single match across
//all the data fields will be a valid WUF
//Valid values are 0, 1
uint8_t DataMaskEN: 1;
//CAN data to match
uint8_t DATA[8];
//Selective Wake FD passive. Set to 1 to ignore frames with
// FD enabled. A 0 (default) results in error counter increment
//Valid values are 0, 1
uint8_t SWFDPassive: 1;
//CAN Data Rate
uint8_t CAN_DR: 3;
//CAN FD Data Rate vs. The selected CAN Data Rate
uint8_t FD_DR: 2;
//Frame counter threshold. Sets at what value the error counter must
//reach to set a frame overflow flag (+1). Default is 31
}TCAN28xx_PN_Config;
The main difference between the previous example compared to the generic register setup is that instead of focusing on the register structure of all the partial networking registers – this focuses purely on the configurable parameters and the data contained within. This sets up the following configuration parameters:
This structure can be used to read and write selective wake configurations on the TCAN28xx and TCAN24xx – but in general can apply to many similar selective wake structures as this partial networking is highly standardized.
For reading the following example is what TI generally gives:
/*Reads Partial Networking Configuration*/
/*TCAN_READ and TCAN_WRITE functions are calls to the SPI driver for */
/*SPI reads and writes*/
TCAN28xx_STATUS_ENUM TCAN28xx_PN_Config_Read(TCAN28xx_PN_Config *pnConfig)
{
uint8_t data[17] = {0}; //Data stream of the registers
uint8_t sw_config[2] = {0}; //Registers for SW configuration
uint8_t index = 0;
uint32_t id = 0;
uint8_t address = REG_TCAN28xx_SW_ID1;
for(index = 0; index < 17; index++)
{
TCAN_READ(address, &data[index]);
address++; //Increment address we are writing to.
}
//Read the sw_config registers
TCAN_READ(REG_TCAN28xx_SW_ID1,&sw_config[0]);
TCAN_READ(REG_TCAN28xx_SW_ID3, &sw_config[1]);
//If IDE is enabled
if(data[2] & REG_BITS_TCAN28xx_SW_ID3_IDE)
{
pnConfig->IDE=1;
//SW_ID1[7:0] which is ID[17:10]
id = (uint32_t)data[0]<<10;
//SW_ID2[7:0] which is ID[9:2]
id |= (uint32_t)data[1]<<2;
//SW_ID[4:0] which is ID[28:24]
id |= (uint32_t)(data[2]& 0x1F)<<24;
//SW_ID3[7:6] which is ID[1:0]
id |= (uint32_t)(data[2] & 0xC0) >> 6;
//SW_ID4[7:2] which is ID[23:18]
id |= ((unit32_t)(data[3] & 0xFC) >> 2) << 18;
pnConfig->ID = id;
//SW_ID_MASK1[1:0] which is IDM[17:16]
id = (uint32_t)(data[4] & 0x03) << 16;
//SW_ID_MASK2[7:0] which is IDM[15:8]
id |= (uint32_t)data[5] << 8;
//SW_ID_MASK3[7:0] which is IDM[7:0]
id |= (uint32_t)data[6];
//SW_ID_MASK4[7:0] which is IDM[28:21]
id |= (uint32_t)data[7]<<21;
//SW_ID_MASK_DLC[7:5] which is IDM[20:18]
id |= ((uint32_t)(data[8] & 0xE0)>>5)<<18;
pnConfig->IDMask = id;
pnConfig->DLC = ((data[8] >>1) & 0x0F);
} else {
//Else we just build normal ID
pnConfig->IDE = 0;
//SW_ID3[5:0] which is ID[10:6]
id = (uint32_t)(data[2] & 0x3F) << 6;
//SW_ID4[7:2] which is ID[5:0]
id |= (uint32_t)(data[3] & 0xFC) >> 2;
pnConfig->ID = id;
//SW_ID_MASK4[7:0] which is IDM[10:3]
id = (uint32_t)data[7]<<3;
//SW_ID_MASK_DLC[7:5] which is IDM[2:0]
id |= (unit32_t)(data[8] & 0xE0)>>5;
pnConfig->IDMask = id;
pnConfig->DLC = ((data[8] >> 1) & 0x0F);
}
//Check if data mask is enabled
if(data[7] & REG_BITS_TCAN28xx_SW_ID_MASK_DLC_DATA_MASK_EN)
{
pnConfig->DataMaskEN = 1;
}
//Move the data fields over to the struct
for(index = 0; index < 8; index++)
{
pnConfig->DATA[index] = data[index + 9];
}
//Now the selective wake configuration registers
//If SWFD passive bit is set
if(sw_config[0] & 0x80)
{
pnConfig->SWFDPassive = 1;
} else {
pnConfig->SWFDPassive = 0;
}
pnConfig->CAN_DR = ((sw_config[0]>>4) & 0x07);
pnConfig->FD_DR = ((sw_config[0]>>2) & 0x03);
pnConfig->FrameErrorThreshold = sw_config[1];
return TCAN28xx_SUCCESS;
}
The code snippet can seem rather long – but the overall goal of the read function is very simple. The first step is to create the device ID by reading from the ID registers as well as checking if the application is using standard ID or the extended ID. After the ID has been constructed and saved in the partial networking struct the DLC value is set next. After that the data mask enable bit is checked followed by the moving all data registers over to the struct – if the data mask is not enabled the function still loads all the data register values – which if not configured can just be 0. After that the final steps are to collect the SWFDPassive bit, CAN_DR, FD_DR, and the frame error threshold. Finally, after the structure has been fully filled out the function returns an ENUM that indicates success with this part – the ENUM is not necessary to function (a simple int can work fine) - but this can add a layer of separation for SBC specific successes/failures and add a guardrail by creating this enumerated data type.
The next example w.r.t. partial networking is writing to the partial networking configuration registers using the partial networking data structure. The first step is to assign all appropriate values to the struct before passing this into the write function.
/*Writes Partial networking Configuration*/
TCAN28xx_STATUS_ENUM TCAN28xx_PN_Config_Write(TCAN28xx_PN_Config *pnConfig)
{
//Selective Wake Registers used for ID/XID
uint8_t sw_id[4] = {0};
//Selective Wake registers used for ID/XID Mask
uint8_t sw_idm[5] = {0};
//Data field if used
uint8_t data[8] = {0};
//Selective Wake Configuration register
uint8_t sw_config = 0;
uint8_t index = 0;
//If XID is enabled (IDE = 1)
if(pnConfig->IDE)
{
//SW_ID1[7:0] which is ID[17:10]
sw_id[0] = (uint8_t)(pnConfig->ID >> 10);
//SW_ID2[7:0] which is ID[9:2]
sw_id[2] = (uint8_t)(pnConfig->ID >> 24) & 0x1F);
//SW_ID3[4:0] which is ID[28:24]
sw_id[2] |= (uint8_t)((pnConfig->ID >> 24) & 0x1F);
//SW_ID3[7:6] which is ID[1:0]
sw_id[2] |= (uint8_t)(pnConfig->ID << 6);
//SW_ID3[5] which sets the IDE flag
sw_id[2] |= 0x20;
//SW_ID4[7:2] which is ID[23:18]
sw_id[3] = (uint8_t)(((pnConfig->ID >> 18)<<2) & 0xFC);
//SW_ID_MASK1[1:0] which is IDM[17:16]
sw_idm[0] = (uint8_t)((pnConfig->IDMask >> 16) & 0x03);
//SW_ID_MASK2[7:0] which is IDM[15:8]
sw_idm[1] = (uint8_t)(pnConfig->IDMask >> 8);
//SW_ID_MASK3[7:0] which is IDM[7:0]
sw_idm[2] = (uint8_t)(pnConfig->IDMask);
//SW_ID_MASK4[7:0] which is IDM[28:21]
sw_idm[3] = (uint8_t)(pnConfig->IDMask >> 21);
//SW_ID_MASK_DLC[7:5] which is IDM[20:18]
sw_idm[4] = (uint8_t)(((pnConfig->IDMask >> 18) << 5) & 0xE0);
} else {
//Else we build normal ID
//SW_ID3[5:0] which is ID[10:6]
sw_id[2] = (uint8_t)((pnConfig->ID >> 6) & 0x1F);
//SW_ID4[7:2] which is ID[5:0]
sw_id[3] = (uint8_t)((pnConfig->ID << 2) & 0xFC);
//SW_ID_MASK4[7:0] which is IDM[10:3]
sw_idm[3] = (uint8_t)(pnConfig->ID >> 3);
//SW_ID_MASK_DLC[4:1] which is the DLC
sw_idm[4] = (uint8_t)((pnConfig->ID << 5) & 0xE0);
}
//SW_ID_MASK_DLC[4:1] which is the DLC
sw_idm[4] |= (uint8_t)(pnConfig->DLC) << 1;
//Check if data mask is enabled
if(pnConfig->DataMaskEn)
{
//SW_ID_MASK_DLC[0] which is the DATA_MASK_EN
sw_idm[4] |= 1;
}
//If Data mask is enabled
if(pnConfig->DataMaskEN)
{
for(index = 0; index < 8; index++)
{
data[index] = pnConfig->DATA[index];
}
}
//All our temp. buffers have been created. Now to write to the device
uint8_t address = reg-tcan28xx_SW_ID1;
for(index = 0; index < 17; index++)
{
uint8_t buffer;
//if we are under 4, then we are on SW_ID
if(index < 4)
{
buffer = sw_id[index];
} else if (index < 9)
{
//If we are under index 9, then we are on ID_MASK
buffer = sw_idm[index-4];
} else {
//Then we are on the data mask
buffer = data[index-9];
}
TCAN_WRITE(address,&buffer);
#ifdef TCAN28xx_CONFIGURE_VERIFY_WRITES
uint8_t readBuffer;
TCAN_READ(address, &readBuffer);
if(readBuffer != buffer)
{
return TCAN28xx_FAIL;
}
#endif
address++; //Increment the address we are writing to
}
//Now to do the selective wake configuration registers
sw_config = pnConfig->SWFDPassive << 7;
sw_config |= pnConfig->CAN_DR << 4;
sw_config |= pnConfig->FD_DR << 2;
TCAN_WRITE(REG_TCAN28xx_SW_CONFIG_1, &sw_config)
#ifdef TCAN28xx_CONFIGURE_VERIFY_WRITES
uint8_t readBuffer;
TCAN_READ(REG_TCAN28xx_SW_CONFIG_1,&sw_config);
if(readBuffer != sw_config)
{
return TCAN28xx_FAIL;
}
#endif
sw_config = pnConfig->FrameErrorThreshold;
TCAN_WRITE(REG_TCAN28xx_SW_CONFIG_3,&sw_config);
#ifdef TCAN28xx_CONFIGURE_VERIFY_WRITES
uint8_t readBuffer;
TCAN_READ(REG_TCAN28xx_SW_CONFIG_1,&sw_config);
if(readBuffer != sw_config)
{
return TCAN28xx_FAIL;
}
#endif
//This write must be thge last one to enable the selective wake
//Otherwise another configuation write will clear the selective wake
//Configure Bit
sw_config = REG_BITS_TCAN28xx_SW_CONFIG_4_SWCFG;
TCAN_WRITE(REG_TCAN28xx_SW_CONFIG_4,&sw_config);
#ifdef TCAN28xx_CONFIGURE_VERIFY_WRITES
uint8_t readBuffer;
TCAN_READ(REG_TCAN28xx_SW_CONFIG_1,&sw_config);
if(readBuffer != sw_config)
{
return TCAN28xx_FAIL;
}
#endif
return TCAN28xx_SUCCESS;
}
The write procedure can look a little more intimidating than the read procedure at first glance – but ultimately the procedures are essentially extremely similar processes just in reverse as the read function reads registers and loads data into the struct while the write function takes data in the struct and organizes this and then writes the data to the correct registers in the correct configuration – please see 6.2 for more details on how to configure partial networking.
The other part of the firmware that can be a bit more complex than getters and setters is configuration of the watchdog timer when in question and answer mode. QA watchdog has a similar purpose to other watchdog configurations – this requires the controller to hit watchdog triggers to confirm that communication between controller and SBC remains intact – but with the extra layer of protection that requires the controller to answer a question posed by the SBC to confirm a proper watchdog trigger has been used. This can be wanted in a system that has a high robustness requirement as QA watchdog not only is checking if the communication is there but also making sure a level of accuracy to test more than a simple signaling.
In a Q/A watchdog the controller must read a question from the SBC and write the computed answers back to the SBC. The correct answer is a 4-byte response and each byte must by correct, written in the correct order, and meet the required timing constraints. There are 2 watchdog windows known as WD response window #1 and WD response window #2 where each window is 50% of the total watchdog window time which is selected from the WD_TIMER and WD_PRE register bits. In response window #1 the controller reads the question and responds with response bytes 3-1 where the final response byte, byte 0, happens during WD response window #2. If this is done without error the error counter is decremented if above 0 and a new question is generated and the cycle repeats. If anything is incorrect a new question cannot be generated, the error counter is incremented, and if the error threshold is surpassed the watchdog failure action is performed.
To start the QA watchdog, process the first step is to read the question from the SBC. This can be done simply by creating a void function that uses a pointer to the memory location of a uint8_t value, reading the register and then clearing bits 4-7 and only retaining bits 0-3.
/*Read Question*/
void TCAN2XXX_SBC_WDT_QA_READ_QUESTION(uint8_t *question)
{
//Set up unsigned int for read data
uint8_t read;
//Pseudo-Code for SPI read function for SBC
SPI_READ(REG_TCAN2XXX_WD_QA_QUESTION, &read);
//Save masked read data to question pointer
*question = read & 0x0F;
}
When this function is called the question pointer can contain the question posed by the SBC. After the question has been read the next part is answering the question – but there is one caveat – there are feedback settings for the answer generation that can change the answer. By default, this value is 0b00 but this can be changed to 0b01, 0b10, or 0b11 in the configuration settings – these values control the configuration multiplexers.
For simplicity the following explanation can assume feedback to be default and set to 0b00. There are three general forms used to calculate the answer bits. Bit 0 uses two 2-input XOR gates with 3 inputs; bits 4, 5, 6, and 7 use a single 2 input XOR gate with 2 inputs; bits 1,2, and 3 use 4 input signals and 2 XOR gates. This is advisable to calculate the answer 1 bit at a time starting at the LSB and building the byte and shifting bits to create the full response.
For bit zero there are two inputs to the final XOR gate and therefore can be directly XOR’d to find the answer bit – “var1 ^ var2”. The first input of the final XOR gate is just a bit from the question – since configuration 0b00 is used this is the LSB of the question. The second input of the final XOR gate is the output of another XOR gate which has the following inputs bit 1 of the response number, where response numbers start at 3 and decrement down to 0, and the second input is the MSB of the question. To compute the 0 bit in this, answer the bit 1 of response number can equal a, the question MSB can equal b, the LSB of the question can equal c, and the final design be set to f.
d = a ^ b
f = d ^ c
As this is shown – the actual first bit calculation is straight forward, bits 1-3 are a bit more involved and require a few more steps to tabulate. For bits 1-2, when using default feedback configuration, the LSB of the question can be a stand-alone input on final XOR gate – this value can still be referred to as c. The second XOR gate in bits 1-2 has two inputs – a bit from the question (2nd MSB for bit 1, MSB for bit 2), refer to as a, is XOR’d with the 2nd LSB of the question nibble, referred to as b, and this is fed into the final XOR gate. The third input is the MSB of the response count represented by e. The final answer of these bits is found by the following steps.
h = a^b
h += c //this is an addition sign not an OR sign
h += e //this is an addition sign not an OR sign
f = h%2 // the modulo ‘%’ operator gives the integer remainder after division
Bit 3 is very similar to bits 1 and 2 in form – but not in values. The value referred to as c for bit 3 is not the LSB of the question, but instead the 2nd MSB. The values referred to as a and b is the LSB of the question nibble and MSB of the question nibble respectively. The value known as c is no longer the LSB of the question and is instead the 2nd MSB of the question nibble. The value e is still the MSB of the response count.
h = a^b
h += c //this is an addition sign not an OR sign
h += e //this is an addition sign not an OR sign
f = h%2 // the modulo ‘%’ operator gives the integer remainder after division
Bits 4-7 all have the same form – this is just a single 2 input XOR gate where inputs a and b are XOR’d to get the necessary bits. The value a is going to be one of the bits of the question, for bits 4-7 with a default feedback setting the order is 2nd LSB, MSB, LSB, and 2nd MSB respectively for bits 4-7. The value b is always the same; this is the LSB of the response number.
f = a^b
The starter code that is available for both the TCAN24xx and TCAN28xx line of devices combines this functionality into 1 function that makes use of a couple private functions. The steps are as follows:
/*Generation of Feedback setting bit Array*/
/*Each Position of the bit Array has variable name associated with this*/
/*Saved data is the bit position for specific XOR gate*/
/* for example, XOR gate 0 is going to use wd0*/
typdef struct
{
uint8_t wd0 : 2;
uint8_t wd1 : 2;
uint8_t wd2 : 2;
uint8_t wd3 : 2;
uint8_t wd4 : 2;
uint8_t wd5 : 2;
uint8_t wd6 : 2;
uint8_t wd7 : 2;
uint8_t wd8 : 2;
uint8_t wd9 : 2;
uint8_t wd10 : 2;
uint8_t wd11 : 2;
} wdt_bits;
void TCAN28xx_WDT_QA_Generate_Question_Bit_Array(uint8_t config, wdt_bits *bitArray)
{
switch(config)
{
default: return;
case 0x00:
{
bitArray->wd0 = 0;
bitArray->wd1 = 3;
bitArray->wd2 = 0;
bitArray->wd3 = 2;
bitArray->wd4 = 0;
bitArray->wd5 = 3;
bitArray->wd6 = 2;
bitArray->wd7 = 0;
bitArray->wd8 = 1;
bitArray->wd9 = 3;
bitArray->wd10 = 0;
bitArray->wd11 = 2;
break;
}
case 0x01:
{
bitArray->wd0 = 1;
bitArray->wd1 = 2;
bitArray->wd2 = 1;
bitArray->wd3 = 1;
bitArray->wd4 = 3;
bitArray->wd5 = 2;
bitArray->wd6 = 1;
bitArray->wd7 = 3;
bitArray->wd8 = 0;
bitArray->wd9 = 2;
bitArray->wd10 = 3;
bitArray->wd11 = 1;
break;
}
case 0x02:
{
bitArray->wd0 = 2;
bitArray->wd1 = 1;
bitArray->wd2 = 2;
bitArray->wd3 = 0;
bitArray->wd4 = 1;
bitArray->wd5 = 1;
bitArray->wd6 = 0;
bitArray->wd7 = 2;
bitArray->wd8 = 2;
bitArray->wd9 = 1;
bitArray->wd10 = 2;
bitArray->wd11 = 0;
break;
}
case 0x03:
{
bitArray->wd0 = 3;
bitArray->wd1 = 0;
bitArray->wd2 = 3;
bitArray->wd3 = 3;
bitArray->wd4 = 1;
bitArray->wd5 = 0;
bitArray->wd6 = 3;
bitArray->wd7 = 1;
bitArray->wd8 = 3;
bitArray->wd9 = 0;
bitArray->wd10 = 1;
bitArray->wd11 = 3;
break;
}
}
}
/*Calculates WDT Questions Answer*/
void TCAN28xx_WDT_QA_Calculate_Answer(uint8_t *question, uint8_t answer[], uint8_t answerConfig)
{
//We'll need a way to map the configuration mux to the array of bits
wdt_bits bitArray = {0};
TCAN28xx_WDT_QA_Generate_Question_Bit_Array(answerConfig,&bitArray);
//Now we can generate teh answer
uint8_t i;
//Gets us the current WD_ANS_CNT value (starts at 3, and goes to 0)
for(i = 0; i < 4; i++)
{
uint8_t wd_cnt = 3-i;
uint8_t bitAnswer = 0;
uint8_t temp;
//Bit 0
temp = TCAN28xx_WDT_Get_Bit(wd_cnt,1);
temp ^= TCAN28xx_WDT_Get_Bit(*question, bitArray.wd1);
temp ^= TCAN28xx_WDT_Get_Bit(*question, bitArray.wd0);
bitAnswer = temp;
//Bit 1
temp = TCAN28xx_WDT_Get_Bit(*question,1);
temp ^= TCAN28xx_WDT_Get_Bit(*question, bitArray.wd3);
temp += TCAN28xx_WDT_QA_Get_Bit(wd_cnt,1);
temp += TCAN28xx_WDT_Get_Bit(*question, bitArray.wd2);
bitAnswer |= (TCAN28xx_WDT_QA_Return_XOR(temp)<<1);
//Bit 2
temp = TCAN28xx_WDT_Get_Bit(*question,1);
temp ^= TCAN28xx_WDT_Get_Bit(*question, bitArray.wd5);
temp += TCAN28xx_WDT_Get_Bit(wd_cnt,1);
temp += TCAN28xx_WDT_Get_Bit(*question,bitArray.wd4);
bitAnswer |= (TCAN28xx_WDT_QA_Return_XOR(temp) << 2);
//Bit 3
temp = TCAN28xx_WDT_Get_Bit(*question,3);
temp ^= TCAN28xx_WDT_Get_Bit(*question,bitArray.wd7);
temp += TCAN28xx_WDT_Get_Bit(wd_cnt,1);
temp += TCAN28xx_WDT_Get_Bit(*question,bitArray.wd6);
bitAnswer |= (TCAN28xx_WDT_QA_Return_XOR(temp) << 3);
//Bit 4
temp = TCAN28xx_WDT_Get_Bit(wd_cnt,0);
temp ^= TCAN28xx_WDT_Get_Bit(*question,bitArray.wd8);
bitAnswer |= (temp << 4);
//Bit 5
temp = TCAN28xx_WDT_Get_Bit(wd_cnt,0);
temp ^= TCAN28xx_WDT_Get_Bit(*question,bitArray.wd9);
bitAnswer |= (temp << 5);
//Bit 6
temp = TCAN28xx_WDT_Get_Bit(wd_cnt,0);
temp ^= TCAN28xx_WDT_Get_Bit(*question,bitArray.wd10);
bitAnswer |= (temp << 6);
//Bit 7
temp = TCAN28xx_WDT_Get_Bit(wd_cnt,0);
temp ^= TCAN28xx_WDT_Get_Bit(*question,bitArray.wd11);
bitAnswer |= (temp << 7);
answer[i] = bitAnswer;
}
}Most of the device is relatively straightforward where the firmware is not complicated with the only action needed is to read and or write registers. The part of the software that is not as simple just requires a bit more work to understand, but ultimately this is not much more complex than the getters and setters and TI does have pre-written firmware that can simplify this process. Next, a few example configurations can be shown to highlight the order in which the configurations need to be programmed, if any, to give a look into what to program.