This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Avoiding HardFault and Optimizing Code Size with TWI Transaction Manager

I recently experimented with the TWI Transaction Manager and found the following:

This function would cause HardFault Error

void _BAD_read_func(app_twi_t* param_p_app_twi, 
                      uint8_t* param_p_reg_addr, 
                      uint8_t* param_p_buffer, 
                      uint8_t byte_cnt)
{
  app_twi_transfer_t        const transfer_1[] = 
  {
    HTS221_READ(param_p_reg_addr, param_p_buffer, byte_cnt)
  };

  app_twi_transaction_t     const transaction_1 =
  {
      .callback             = custom_app_twi_callback,
      .p_user_data          = NULL,
      .p_transfers          = transfer_1,
      .number_of_transfers  = sizeof(transfer_1)/sizeof(transfer_1[0])
  };

  APP_ERROR_CHECK(app_twi_schedule(param_p_app_twi, &transaction_1));
}

But this would not

uint8_t reg_addr = HTS221_WHO_AM_I_REG_ADDR;
uint8_t m_buffer[50];

app_twi_transfer_t          transfer_2[] = 
{
  HTS221_READ(&reg_addr, m_buffer, 1)
};

app_twi_transaction_t       transaction_2 =
{
    .callback               = custom_app_twi_callback,
    .p_user_data            = NULL,
    .p_transfers            = transfer_2,
    .number_of_transfers    = sizeof(transfer_2)/sizeof(transfer_2[0])
};

void _GOOD_read_func(app_twi_t* param_p_app_twi, 
                      uint8_t* param_p_reg_addr, 
                      uint8_t* param_p_buffer, 
                      uint8_t byte_cnt)
{
  APP_ERROR_CHECK(app_twi_schedule(param_p_app_twi, &transaction_2));
}

I am guessing that the TWI peripheral does not touch the app_twi_transaction_t struct nor the app_twi_transfer_t struct before the TWI operation is actually carried out. That is why if I store the variable in the stack as in the first function, when the TWI operation look up the values, the stack variables have already been popped and that is why it caused a HardFault.

Is my guess correct?


Another question is: I need to optimize my firmware for code size. Therefore I really prefer not having to declare a global or static local app_twi_transfer_t array and a app_twi_transaction_tfor every TWI operation I have.

With the second approach I posted above, I could modify the global variable depending on what I want to read/write. But then there is the case of two consecutive transactions. When I set up the second transaction and modify the global variables, would that potentially mess up the first transaction?

  • Your guess is correct. If you want to declare the transfer or transaction structs inside a function other than main() you have to make them static, otherwise the data will be overwritten by the time the TWI driver uses them. The TWI driver runs various asserts on the data to ensure it is valid, and this is likely to fail if the data has been overwritten.

    Is it code size, RAM size or both that you need to optimize for? Having all your structures global or static will only affect your RAM consumption, not your code consumption. Even if you only define one structure at a time you still need to have that data in the code somewhere.

    If you do want to pass one transaction at a time to the TWI library you probably need some kind of state machine in the application, where you wait for TWI events and feed one transaction at a time into the library. That being said I think this will have a larger impact on code usage than simply having all the transactions global/static, so I wouldn't recommend it.

  • Hi Torbjørn,

    I must first admit that I am not well versed on how memory is allocated on flash and on RAM while running. So please advise me. From what you said I already feel like some of what I understood is wrong.

    There are two size constraints that I need to consider.

    First we use an nRF51822 QFAB, with S110, and plan to have Dual Bank DFU and some persistent storage, so I think I need to use less than 12kB of flash to be safe. Regarding this, I have little knowledge and am now starting to research how my code impact Flash and RAM consumption. So any tips or keywords to push me in the right direction is really appreciated.

  • The second is the code size limit by MDK-Lite. For this I believe I only need to make sure (Code + RO Data) to be less than 32kB, and I believe that global and static local variables increase RO Data size.

    I think I understand what you meant by converting multiple global/static variables into one managed by a state machine. That should be just converting size taken by RW Data into taken by Code, and thus actually badly impact me for MDK-Lite limitation. I didn't think that through.

  • I just read this question and I think I grasp things a little better now.

    So overall trying to avoid global/static variable is not going to benefit me in anyway because both Code, RO and RW Data takes Flash space...

  • Keil defines the following data types: RO (read only), RW (read write) and ZI (zero initialized) The only difference between RW and ZI is that RW data is initialized to something different than zero.

    RO variables only take up flash space RW variables take up both RAM and flash (the flash is used to store the init values) ZI variables only take up RAM, and are initialized to 0 automatically at startup.

    How critical is it to use dual bank DFU? The only difference between single and dual bank is that if something fails when doing single bank update you have to retry the DFU operation until it succeeds.

Related