Re - Semaphores in RobotC

In response to the question in the RobotC Tech Support Q&A

An old example (hopefully still works) here.

https://vexforum.com/showpost.php?p=227893&postcount=31

1 Like

Thanks much, I’ll run through it. We brute forced it this year with HogCPU, etc… but I wanted to teach the kids an elegant (and correct) way to handle resources in multi-tasking.

1 Like

There’s another example of their use in this thread (in the ime library).

https://vexforum.com/t/ime-technical-description-and-software-workarounds/23218/1

1 Like

Thanks very much for that one. I like the general I2C discussion as well. In industry, I have seen repeated issues with I2C, namely brown out issues with slave devices where a master is in the middle of reading a device and brown’s out. When the master comes back the slave may still be waiting for clocks from the master to complete the read but the slave holds the data bus. Possibly an issue with the ESD as well.

One way out of this as a master is to check the polarity of the data bus, if it’s low, you don’t own it, clock the clock until it releases. The other is to implement the SMBus protocol which gives the bus back after 30 or so ms.

Sorry, off topic :slight_smile:

1 Like

From another thread

In the case of using a single variable, lets call it “targetPosition” as an example, that’s used to convey information from one task to another (ie. interprocess communication using shared memory) then there is almost never any reason to use a semaphore. For example;

int targetPosition = 0;

task foo()
{
    while(1) {
        // This is not useful code, just an example
        if( targetPosition > 100 )
            motor port1 ] = 127;
        else
        if( targetPosition < 100 )
            motor port1 ] = -127;
        else
            motor port1 ] = 0;
        
        wait1Msec(10);
        }
}

task main() 
{
    StartTask( foo );
    
    while(1)
        {
        if( vexRT Btn8U ] == 1 )
            targetPosition = 200;
        if( vexRT Btn8D ] == 1 )
            targetPosition = 50;
        if( vexRT Btn8R ] == 1 )
            targetPosition = 100;
        wait1Msec(25);
    }
}

In this code targetPosition is written by one function and read by another. Task switching can only occur between opcodes, lets take a look at the assembly for the task foo.

    while(1) {
        // This is not useful code, just an example
        if( targetPosition > 100 )
0000: 21010064000600000900 L0000: TestAndBranchSShort(100 >= targetPosition:G00(short), L0011)
            motor port1 ] = 127;
000A: 542A007F00               SetProperty(propertyMotorPower, port1:port1:0, 127)
000F: 7917                     BranchNear(L0027)                   // If end
        else
        if( targetPosition < 100 )
0011: 21000064000600000900 L0011: TestAndBranchSShort(100 <= targetPosition:G00(short), L0022)
            motor port1 ] = -127;
001B: 542A0081FF               SetProperty(propertyMotorPower, port1:port1:0, -127)
0020: 7906                     BranchNear(L0027)                   // If end
        else
            motor port1 ] = 0;
0022: 542A000000        L0022: SetProperty(propertyMotorPower, port1:port1:0, 0)
        
        wait1Msec(10);
0027: 47000A00          L0027: wait1Msec(10)                      
        }
002B: 79D4                     BranchNear(L0000)                   // Jump to end Expression
}

The “if” statements become a single opcode “TestAndBranchSShort”, the variable is used in an “atomic” fashion and cannot be corrupted by the main task.

If both tasks were doing something like a read modify write then that would be an issue, for example;

int counter = 0;

task foo()
{
    int  tmp;
    while(1) {
        tmp = counter;
        tmp = tmp + 2;
        counter = tmp;
        
        // Some time consuming code here
        wait1Msec(1);
        
        // counter should be tmp 
        
        if( counter != tmp )
            writeDebugStreamLine("oops, what happened !");
        
        wait1Msec(10);
        }
}

task main() 
{
    StartTask( foo );
    
    while(1)
        {
        counter = counter + 1;              
        wait1Msec(2);
        }
}

Would have unexpected results when the test in foo was used. The answer is use of the SemaPhore, although in reality the code would just be structured in a different way to avoid that need.

int counter = 0;

TSemaphore  s;

task foo()
{
    int  tmp;
    while(1) {
        SemaphoreLock(s, 5);
              
        tmp = counter;
        tmp = tmp + 2;
        counter = tmp;
        
        // Some time consuming code here
        wait1Msec(1);
        
        // counter should be tmp 
        
        if( counter != tmp )
            writeDebugStreamLine("oops, what happened !");
        
        SemaphoreUnlock(s);
        
        wait1Msec(10);
        }
}

task main() 
{    
    SemaphoreInitialize(s);
 
    StartTask( foo );
    
    while(1)
        {
        SemaphoreLock(s, 5);
        counter = counter + 1;        
        SemaphoreUnlock(s);
        wait1Msec(2);
        }
}
1 Like

I wrote a nice long reply and the forum logged me out :frowning:
Guess I need to get used to the board.

In any event, the short answer is that I would have to disagree that, “there is almost never any reason to use a semaphore”. I believe the converse to be true. While there are certainly times when it will work, there are many times that it will not.

I’m not re-typing my long answer, but I do believe that, unless accesor functions are used (which is preferred), then any shared variables in your above examples need to be volatile. Although that does not guarantee atomicity at all, it does help with code being completely removed during optimization. As well, those shared variables used within a logical construct if-else, switch, equation, etc… need to be moved to a local variable for use within the construct and re-sampled each time through the loop.

Some more info on this, about interrupts but very similar, can be found here.

https://dl.dropboxusercontent.com/u/77158737/17030_ADC.pdf

1 Like

We are not dealing with interrupts, the issue of interrupt code modifying a variable is completely different from two threads in an RTOS wanting to access the same resource.

Making a variable volatile is just giving a hint to the compiler that the variable may be changed by code that it is not aware of.

code being optimized has nothing to do with using semaphores.

Feel free to use semaphores if it makes you more comfortable.

1 Like

Although there are some housekeeping differences, shared global variables between interrupts and a pre-emptive OS are very similar.

Volatile is more than a hint. It is necessary to prevent caching, hoisting (re-order) and optimization issues. Otherwise, especially on the consumer side of producer/consumer tasks, the optimizer may throw code out since it does not know the variable is changed outside of the context of the present function. From a caching perspective it may not re-load when it needs to since it doesn’t understand that it could change.

True code being optimized has little to do with semaphores, but it has a lot to do with shared global variables amongst tasks (which is really the intent of the thread) and the volatile qualifier.

I’m not using semaphores because I would like to, I am trying to teach the kids that they are necessary in these constructs and why.

We can agree to disagree, no problem.

1 Like

I don’t really disagree with you, everything I’m saying is targeted at ROBOTC, other compilers targeting different hardware may behave exactly as you explain.

ROBOTC accepts the volatile keyword but I don’t know if it uses it during compilation, I’ve not been able to see a difference between code generated with and without using it, guess I can ask the developers. I think they may have taken the approach that all variables should be treated as volatile.

Code can still be optimized away when making it a critical section using semaphores, I don’t think using a semaphore is really a substitute for making a variable volatile.

The problem I’ve faced when trying to explain these concepts to students is figuring out how to demonstrate it. To try and show why more complex code is necessary some code that exhibits unwanted behavior should be created. This can be done with other development environments, it’s just not as easy with ROBOTC.

1 Like

Thanks,

It may be that they are treating it all as volatile, I can’t really see what’s going on internally so it’s frustrating that I have to assume the worst behavior. For RobotC it has been all anecdotal for us. We used tasks and would have odd operation so we brute forced hogCPU, which resolved the issues, since we were just getting started as a team.

Semaphores are definitely not a replacement for volatile. Unfortunately, a lot of people think making something volatile also handles atomicity, which it obviously doesn’t.

When I teach the class that I provided the link for at Microchip MASTERs conference, I start with how important volatile is, and then end with the statement that it should be avoided whenever possible by using accessor functions. The reason being the chart at the end of the presentation which shows how poorly most compilers handle the volatile qualifier.

My whole goal is to teach the kids programming skills, so I think we are going straight to PROS. I’d like them to leave HS understanding Eclipse, etc…(useable industry stuff) and minimize some of the bad habits I see in these “easier” packages. Although these are probably great for elementary and middle school.

1 Like