Calling a CANopenSDO function from AsCANopen library with different parameters doesn't change the output

Hi, I’m playing around with the AsCANopen lib, specifically with the CANopenSDORead8 and CANopenSDOWrite8. I want to read a few objects from my slave’s library, they are all at the same index but different subindex (e.g. idx: 0x1203 sub: 0x1-0xA). I thought I could do so by creating a function (e.g. SDOread) with type CANopenSDORead8 and use a FOR cycle to change the SDOread.subindex from 0x1 to 0xA; for each run of the FOR cycle I would call the function and read the parameter. However, if I try to do so I only read the first subindex 0x1 at each cycle despite changing the subindex. Any Idea why is this happening? It seems I need to create a function for each SDO read, which would require a lot more code. Thanks

Hello,

the SDOread function must be called until it returns status 0, at which point the received data is valid. After that, you can call it with a new subindex. I wouldn’t program this with a FOR loop, but rather with a state machine.

Gruss
Stephan

1 Like

Hi, can you give me a simple example of how you would do it? Just some pseudocode to understand

Use a state machine:

  • Step 1: Configure the FUB for the first index, go to step 2
  • Step 2: Check if the status output is ERR_OK, do so until you either get ERR_OK (that’s success) - go to the next step - or anything other than ERR_FUB_BUSY (that would then be an errror) - go to error handling step
  • Step 3: With the data received, do what you want to do; Increment the subindex of the FUB by 1, check if it’s already higher than 0x0A, if so, make it 1 again; Go back to step 2
    1:
        // insert SDOread config here
        SDOread.enable := TRUE;
        SDOread.index := 16#1203;
        SDOread.subindex := 1;

        step := 2;

    2:
       IF SDOread.status == ERR_OK THEN
            // We have finished reading
            SDOread.enable := FALSE;
            step := 3;
       ELIF SDOread.status <> ERR_FUB_BUSY THEN
            // Error Handling
       END_IF

    3:
        // Do whatever you like to do with the data here

        // increment subindex, check for "overflow" (> 0x0A), makeit subindex 1 if so
        // return to step 2
        step := 2;
END_CASE

// Call the FUB here
CANopenSDORead8();

Just like you would call every other function block that’s marked that way.

Reminder: The code is already run cyclically, which is why you use that as your loop
Reminder: Give the steps proper names using an enum, don’t use magic numbers!

Example untested
CANsdo.zip (101.0 KB)

Here you say the SDOread (and I suppose also the SDOwrite) must be callled until status=0; Is this true also for the NMT function?

The same applies to SDOwrite. For simplicity, I only used the SDOread function. The NMT function block must also be called repeatedly until it returns status 0.

1 Like

If I want to call one of the metioned SDOs funtions until it outputs status=0 would you suggest using a REPEAT-UNTIL? The function is called until it outputs status=0

In principle, you can also use REPEAT UNTIL. I use state machines instead of loops in the CANopen function blocks. The reason for this is the sometimes long response time of the CANopen slave to an SDO message.

I’ve noticed in my tests that often the status=ERR_OK takes way longer to be outputted than the time the FUB actually takes to give me my desired output. As an example I have tried sending the NMT to restart the slave and I can see the slave restarting while the function is still running. Another example is that I’ve tried to read a parameter via SDO and I could get the parameter and stop the function block very fast compared to waiting for the ERR_OK to be given.
To me it seems like I don’t really need to wait for that output as it will slow down the code a lot.

How fast task class cycle time are you using? If you are running the task in a 1s cycle time it will appear to work slowly.

At the moment the code is in task class #2 at 10ms; I’ve considered changing the task, but as said I can get the output of on of the FUB and do a fast check without using state machines and REPEAT.

For the case presented at the beginning of the question (FOR cycle throught subindexes) I guess it’s unavoidable to use them, but at the moment in other simpler commands it seems I can avoid to wait, just wanted to highlight this

One last question: what exactly is the difference between the output FUB.status and FUB.error? Some FUB of the AsCANopen library have both an they both seem to give info about errors

the Status output provides the internal state of the function block. The Error output provides the standardized CIA405 error status of the network layer.

Hi there,

while it may appear to you that the FUB is “too slow” in outputting I assure you that it’s never a good idea to ignore the status of any FUB. It may be that the data is received by the slave earlier than the output of the FUB indicates to you, but you don’t know the backend - maybe you only get “ERR_OK” after some acknowledgement by the slave has been received? Usually no FUB wastes time doing nothing and letting you wait.
Generally speaking I advise you to not use loops for any of such usecases either - you’ll soon get to know cycle time violations otherwise.

I want to emphasize again that the code is already running cyclically and thus providing THE loop you need already. State machines are the way to program on PLCs - rather than, what I have already seen, lots of code using various ANDs and ORs to connect inputs and outputs in a way in order to achieve some way of “if x happenened and is done and y already happened, do z” - that’s exactly what they (state machines) are for and I have yet to see code that’s not hard to read and maintain (on a PLC) that does not use a state machine…

Please note that the “wait for status to become ERR_OK” pattern is nothing specific to the CANopen library - it’s used basically “everywhere”. And you’ll always have to wait for ERR_OK.

Best regards
Michael

1 Like

Thanks a lot for your insight, I’m understanding better now and I’ll try to rewrite my code in the right way. For the code I’m writing I need to send a lot of SDOs (50+) and at the moment I find complex to understand how to implement so many state machines handling each one of them without creating a code extremely slow. If you have any tips it would be great, I’m still learning

Why exactly would you need to create separate state machines handling each one of them?
Going from my previous writeup for how to read all of the SDOs you want to read (Calling a CANopenSDO function from AsCANopen library with different parameters doesn't change the output - #4 by michael_w) - this is exactly the same for all the SDOs you want to write.

  • Step 1: Configure the SDO write for the first SDO you want to write
  • Step 2: Wait for the SDO to be written (ERR_OK), goto step 3
  • Step 3: Configure the SDO write for the next SDO, return to Step 2

This can be combined with the reading, so the same state machine could handle both

CASE step OF
  0:
    IF read THEN
		read := FALSE;
		step := 10;
	ELIF write THEN
		write := FALSE;
		step := 100;
	END_IF

  10:
        // insert SDOread config here
        SDOread.enable := TRUE;
        SDOread.index := 16#1203;
        SDOread.subindex := 1;

        step := 20;

    20:
       IF SDOread.status = ERR_OK THEN
            // We have finished reading
            SDOread.enable := FALSE;
            step := 30;
       ELIF SDOread.status <> ERR_FUB_BUSY THEN
            // Error Handling
       END_IF

    30:
        // Do whatever you like to do with the data here

        // increment subindex, check for "overflow" (> 0x0A)
        CANopenSDORead8.subindex := CANopenSDORead8.subindex + 1;
		IF CANopenSDORead8.subindex > 16#0A THEN
			CANopenSDORead8.subindex := 1;
			step := 0; // We are done
		ELSE
			step := 20;
		END_IF
		
		
		
	100:
		SDOwrite.enable := TRUE;
		// configure all other inputs here
		
		step := 110;
	
	
    110:
		if CANopenSDOWrite8.status = ERR_OK THEN
			CANopenSDOWrite8.enable := FALSE;
			step := 120;
		ÉLSIF CANopenSDOWrite8.status <> ERR_FUB_BUSY THEN
			// Error handling
		END_IF
		
	120:
		// Configure the write for the next SDO here
		
		// if all have been written, return to step 0, otherwise to 110
	
        
END_CASE

// Call the FUBs here
CANopenSDORead8();
CANopenSDOWrite8();

Again this is just a small example without error handling, without proper step naming and without looking at any needed parameterization of the FUBs.

BR

1 Like

I would like to implement something similar but that only activates when a contifion is met (e.d. IF A == TRUE THEN run this state machine), but I was wondering what happens if the condition becomes false while the machine is running. If it’s interrupted while the SDO FBU was running it will remain running I guess, can this create problem to the other SDO that will tra to run?

OFFTOPIC: I want to thanks you for the help here, my questions might be naive but I’m learning a lot and I think a long thread of questions and answers might be helpful for others in the future :slight_smile:

Well, my state machine’s step “0” already handles “when condition is met” - i just made up two conditions for reading and writing based on two boolean variables. You can always modify that to your needs.
Regarding interruption: The state machine itself doesn’t care about any change of states if you don’t check the state - your code will be in another step, won’t it?
So if you don’t want to interrupt it, don’t do anything. If you want to interrupt it, you have to implement code in each step (or somewhere outside of the state machine) and act accordingly. You can’t really interrupt the Sdo read and writes itself (if you already set “enable” to true, the FUB will continue doing what it’s supposed to do even if you don’t really care about it anymore), but you can ignore the result and/or leave the step where you care about it. You just have to make sure to wait for the fub to be ready again for your next call once the conditions are met again.

BR

At the moment my code is in the same program of this state machine, but I’m guessing it’s best to split them so that when the condition is met the state machine can run trouble free through a complete cycle even if other things change in the code