This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP430F5505 and number of MCU cycle for istructions

Other Parts Discussed in Thread: MSP430F5505, CC1101

Hi!

I'm using a MSP430F5505.

how many MCU cycles does an assignment istruction last?

example 1:

unsigned char dummy;

....

dummy = 1;  // how many cycles will it last? one?

example 2:

unsigned int dummy2;

....

dummy2 = 1;  // how many cycles will it last? one?

I need to know this things because i want to know if a C assigment istruction can be interrupted by an interrupt or  if i can be sure that before serve the interrupt the assigment will be finished.

Thanks!

  • Generally, you cannot say or possibly know how many cycles it takes or how many instrucitons it is to execute a C statement.
    It's a compiler-internal thing and depends on many things.

    An assembly instruction like
    MOV.B #1, &dummy
    which on first glance looks equivalent to
    dummy=1;

    takes three MCLK cycles and won't be interrupted by an interrupt. Same if dummy is an (un)signed int.
    If dummy is a long, it will be at least two isntructions and an intrerrupt may happen in.between.

    However, depending on code, situation, compiler smartness and compiler configuration, the C instruciton may translate into somethign compeltely different.

    If you're using dummy right away in teh next isntruction and then never again, the compiler might optimize it away, or create a register variable, or the value is temporarily stored in a regsiter that isn't used otherwise, and only written to the variable memory location at the end of the current code block or function.

    If you declare

    volatile unsigned char dummy;

    then it will indeed be a single MOV instruction at exact the place you wrote it. At least that's how the MSP compilers I know handle it.

    Still, if the variable type is a long int, it will be at least two isntrucitons whcih can be interrupted. If you don't want this, you'll have to surround the assignment with a critical section. By clearign the GIE bit to disable interrupts (disabled does not mean forgotten/ignored - they are latched until GIE is set again), followed by a NOP (an interrupt might be triggered jsu tthe moment GIE is cleared, causing the interrupt to still happen after the next instruction, which woudl be the NOP, so no harm is done), do teh assignment, and then set GIE again.

  • Thank you very much for your reply!

    Indeed my variable was already declared as a global volatile variable.

    Since I read it and modify it both inside interrupts and in normal code, before your answear i disabled interrupts before every assignment (plus nop) and re-enable after the assignment. Something like this:

    #define HAL_INT_DISABLE()        __disable_interrupt(); asm(" NOP")    
    #define HAL_INT_ENABLE()        __enable_interrupt()

    volatile uint8 flgTx = 0;   // global variable

    // somewhere outside interrupts

    HAL_INT_DISABLE();    // atomic code

          flgTx = 0;

    HAL_INT_ENABLE();

    I'm happy to read that this is not necessary (it causes a lot of overhead in my code) and that i can avoid to disable and enable interrupts!

    Is it the same for a read istruction?

    if i do

    if(flgTx)

     {   ..... }

    or

    dummy = flgTx;

    can i be sure that as long as flgTx  is volatile, the evaluation of "flgTx" it's not interrupted by an interrupt (if flgTx it's an unsigned char or an unsigned int, not if it's a long)?

    Or in the case of a read do i need to disable and re-enable interrupts? Something like this:

    HAL_INT_DISABLE();    // atomic code

    if(flgTx)

    {  

         HAL_INT_ENABLE();

    .....

    }

    And what about if the volatile variable it's inside a struct?

    union SchedulerFlagUnion
    {
        uint8 Byte;           

        struct
        {
            volatile uint8     Trasmetti:1;
            volatile uint8    StartLetturaCentralina:1;
            volatile uint8     CentralinaTrigger:1;
            volatile uint8    dummy:5;
        }flg;
    };

    union SchedulerFlagUnion SchedulerFlag;

    if i want to write or read SchedulerFlag.flg.Trasmetti, is it the same case as flgTx? Can I avoid to disable and re-enalbe interrupt before the write/read ?

    Now i think i have covered all my doubt ! :-)


    Regards,

    Carloalberto

  • Carloalberto Torghele said:
    #define HAL_INT_DISABLE()        __disable_interrupt(); asm(" NOP")    

    On CCS and IAR, the __disable_interrupt() intrinsic already contains a NOP, at least AFAIK.

    However, disabling and enabling interrupts around an access is tricky - you may enable interrups after the acces while they had been intentionally disabled already.

    I posted a macro that allows nested usage of 'critical' or 'atomic' sections based on a global counter. Search for 'atomic' in the forum.

    It is, however, not necessary for char or int (8 or 16 bit) sized assignments to a volatile variable. Except if your assignment contains math of any kind that is not constant (then the compiler will resolve it to a sinle constant).

    X = Y +1; won't be atomic and may be critical if either x or y is altered in an interrupt.
    X |=Y (or X=X|Y), however, is atomic.

    Carloalberto Torghele said:
    can i be sure that as long as flgTx  is volatile, the evaluation of "flgTx" it's not interrupted by an interrupt (if flgTx it's an unsigned char or an unsigned int, not if it's a long)?

    Yes.

    The MSP instruction set supports instructions that allow an 8 or 16 bit move move from one memory location to another. And individual instructions are not interrupted by interrupts (however, a DMA transfer may interrupt them, but this is configurable).  Same for testing an 8 or 16 bit memory location and check for zero or non-zero (whcih actually is what an IF does)

    Carloalberto Torghele said:
    And what about if the volatile variable it's inside a struct?

    Calculating the memory address of the struct member before doing the read/write will usually require several instructions and may be interrupted. However, if the struct doesn't move by interrupt influence, the access to the member itself is atomic again. it's difficult on an access like
    typedef struct test { volatile int x; };
    volatile test * ptr;
    ptr->x = 0;

    Here, ptr might be changed by an itnerrupt after the address of ptr->x has been calculated but before x has been altered. Declaring ptr volatile is necessary (so it isn't buffered) but won't help avoiding this problem. Here a critical section must be created (interrupts disabled)

    Carloalberto Torghele said:
    if i want to write or read SchedulerFlag.flg.Trasmetti, is it the same case as flgTx?

    No. in this case things can go worng completely.

    altering a single bit in a bitfield requires the whole byte to be read, then soem bit-shifting, than a writeback. This isn't a a single instruciton anymore.
    The compiler might optimize it to a single instruction in some cases.
    e.g. flg.Trasmetti=1 -> flag|=0x80; and flg.Trasmetti=0 -> flg&=0x7f, but for dummy, any assignment requires clearign all 5 bits first, then setting th enewly required ones, which cannot be turned into one instruction. And since it is a volatile operation, there must only be one read and one write, so a register is required for holding the intermediate result.

    The more comfortable you make the code by using structs, unions and bitfields, the less control you have about what the compiler actually generates. And since interrupts are a machine-dependent thing and not covered at all in the C language specification, there is no way to express your 'intentions' in C. Other than explicitely place critical sections.

  • Jens-Michael Gross said:

    However, disabling and enabling interrupts around an access is tricky - you may enable interrups after the acces while they had been intentionally disabled already.

    do you mean the following?

    void function()

    {

    HAL_INT_DISABLE();

    critical code

    HAL_INT_ENABLE();

    ....
    }

    if somewhere you define the following critical section and you do not know that function() disable and re-enalbe interrupts:

    HAL_INT_DISABLE();

    assignment1;

    function();

    assignment2;

    HAL_INT_ENABLE();

    assignemnt2 it'executed with interrupts enabled even if a quick read would tell that interrupts are disabled.

    Is it this that you mean?

    Jens-Michael Gross said:

    I posted a macro that allows nested usage of 'critical' or 'atomic' sections based on a global counter. Search for 'atomic' in the forum.

    I foud your macro:

    unsigned int ATOMIC_CNT;
    #define ATOMIC(x) do{ATOMIC_CNT++;__disable_interrupts; x; ATOMIC_CNT--; if(!ATOMIC_CNT) __enable_interrupts;}while(0)

    In order to make it work, I suppose you have to init ATOMIC_CNT to 0 before everything.

    If i undestand correctly, this macro fixes the problem of nested critical section.

    And what about the following TI proposal solution (I found it in an CC1101 application note)?

    #define HAL_INT_LOCK(x)        do( (x) = __get_interrupt_state(); __disable_interrupt();)while(0)

    #define HAL_INT_UNLOCK(x)      do( __set_interrupt_state(x);)while(0)

    uint16 key;

    HAL_INT_LOCK(key);

         critical code

    HAL_INT_LOCK(key);

    The only problem that I still can see (every solution we discussed about) is if you disable GIE inside interrupts with an instrinsic like _bic_SR_register_on_exit.

    I never do that but I suppose someone may do it.

    Anyway I think this is something that should never be done.

  • Carloalberto Torghele said:
    assignemnt2 is executed with interrupts enabled even if a quick read would tell that interrupts are disabled.
    Is it this that you mean?

    Exactly.

    Carloalberto Torghele said:
    If i undestand correctly, this macro fixes the problem of nested critical section.

    Yes. And you're right, the count needs to be initialized with 0 on CCS (not necessary on IAR or MSPGCC as these initialize uninitialized variables with 0)

    Carloalberto Torghele said:
    And what about the following TI proposal solution (I found it in an CC1101 application note)?
    #define HAL_INT_LOCK(x)        do( (x) = __get_interrupt_state(); __disable_interrupt();)while(0)
    #define HAL_INT_UNLOCK(x)      do( __set_interrupt_state(x);)while(0)

    This basically circumvents the storage of the current state on the stack by using an external storage location. However, you need a different lock for each usage (or the 'inner' lock will overwrite the saved state from the outer lock). Also, there is no automatic pairing - you can lock inside an IF and unlock outside or never or whatever. And it requires the intrinsic 'get/set_interrupt_state', which isn't necessarily available on all compilers.

  • Jens-Michael Gross,

    I'd like to know how would you handle a case like the following:

    ....

        TIMEOUT_TIMER_START(); // start timeout

        // Disable global interrupts
        HAL_INT_LOCK(key);
     
        // Wait until tx is done (or timeout is expired).
        while (!TXinfo.complete)
        {
            halMcuSetLowPowerMode(HAL_MCU_LPM_3);
            
            // Disable global interrupts
           __disable_interrupt();
            
            if(0 == flgTimeoutRxTx)    // if 0 timeout is expired
            {
                halDigioIntDisable(&pinGDO0);
                halDigioIntDisable(&pinGDO2);
                
                // Set previous global interrupt state
                HAL_INT_UNLOCK(key);
                
                return TX_STATE_TIMEOUT;
            }
        }

        TIMEOUT_TIMER_STOP();
     
        // Set prevois global interrupt state
        HAL_INT_UNLOCK(key);

    ....

    I can not figure out how to use your atomic macro here. So I would let the code as it is. But i'd like to know what you would do.

    Regards,
    Carloalberto

  • I can think something like this:

    #define    ATOMIC_UNLOCK    do{ATOMIC_CNT--; if(!ATOMIC_CNT) __enable_interrupts;}while(0)

    #define ATOMIC(x)         do{ATOMIC_CNT++;__disable_interrupts; x; ATOMIC_CNT--; if(!ATOMIC_CNT) __enable_interrupts;}while(0)


        TIMEOUT_TIMER_START();

        ATOMIC
        (
            while (!TXinfo.complete)
            {
                halMcuSetLowPowerMode(HAL_MCU_LPM_3);
                
                __disable_interrupts;
                
                if(0 == flgTimeoutRxTx) 
                {
                    halDigioIntDisable(&pinGDO0);
                    halDigioIntDisable(&pinGDO2);
                    
                    ATOMIC_UNLOCK;
                    
                    return TX_STATE_TIMEOUT;
                }
            }


            TIMEOUT_TIMER_STOP();
          
            halDigioIntDisable(&pinGDO2);
        );


    But i'd like to know the "right philosophy" :-)

  • Indeed, this is a problem case. exiting a function from within a code block of course is a problem for any outer code, inlcuding the ATOMIC macro.

    I had similar problems when the compiler generated bogus 'code never reached' messages because it optimized the code in a way that I ha dthe same instructions twice, on exit of the inner loop if and on exit of the function. Optimization generated a direct jump to the function end and then generated this ugly warning on the instructions that were obsolete then. Only restructuring the code flow helped:

    unsigned int retval = TX_STATE_FINE;
    ATOMIC(
     while()
      {
      ...
        if()
        {
          ...
          retval = TX_STATE_TIMEOUT;
          break;
        }
      }
      if(retval==TX_STATE_FINE)
        TIMEOUT_TIMER_STOP();
    )
    ...
    return retval;

**Attention** This is a public forum