AddressOf command

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

AddressOf command

Post by Gunplumber » Tue Sep 12, 2023 6:22 am

Hi guys

I am after a little more info on the 'AddressOf" command.

Some background...

I have the below sub routine. It's job is to check a series of sensors and if each sensor is active then it sends some data to a speaker.
The issue i have is that the order in which the sensors are checked is hard coded. IE Sensor A will always report before sensor B for example. What i would like to do is to be able to configure the sensors in any order that the users wishes.

Code: Select all

Sub Timer_sensors()
    If SensorA_Alarm_Timer_speak And SensorA_Sensor Then
        Speak_data(17,SensorA.byte0,SensorA.byte1)
    End If 
    If SensorB_Alarm_Timer_speak And SensorB_Sensor Then
        Speak_data(17,SensorB.byte0,SensorB.byte1)
    End If    
    If SensorC_Alarm_Timer_speak And SensorC_Sensor Then
        Speak_data(17,SensorC.byte0,SensorC.byte1)
    End If 
    If SensorD_Alarm_Timer_speak And SensorD_Sensor Then
        Speak_data(17,SensorD.byte0,SensorD.byte1)
    End If 
    If SensorE_Alarm_Timer_speak And SensorE_Sensor Then
        Speak_data(17,SensorE.byte0,SensorE.byte1)
    End If 
End Sub
My fist thought is to assign each sensor a Priority number (1-5), then pass the priority as an Subroutine argument and then check each case. Code sample below..
The issue with this is that the Subroutine must now be called 5 times (instead of just once) in order to ensure each sensor is covered and there is not enough time to complete this task before it needs to be back into another control loop. It is exacerbated by the fact that there is actually 11 sensors to monitor not just 5 in this example.

Code: Select all


SensorA_priority=5
SensorB_priority=2
SensorC_priority=1
SensorD_priority=3
SensorE_priority=4

Sub Timer_sensors( Priority as byte )
    If SensorA_Alarm_Timer_speak And SensorA_Sensor and SensorA_priority=Priority Then
        Speak_data(17,SensorA.byte0,SensorA.byte1)
    End If 
    If SensorB_Alarm_Timer_speak And SensorB_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorB.byte0,SensorB.byte1)
    End If    
    If SensorC_Alarm_Timer_speak And SensorC_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorC.byte0,SensorC.byte1)
    End If 
    If SensorD_Alarm_Timer_speak And SensorD_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorD.byte0,SensorD.byte1)
    End If 
    If SensorE_Alarm_Timer_speak And SensorE_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorE.byte0,SensorE.byte1)
    End If 
End Sub

So after some thought i came up with an idea of spliting out each sensor check in its own subroutine, then using "addressof" to capture the starting address of each sensors Subroutine and storing those addresses in a structured array along with a field for priority. Then i could sort that structured array in the order desired ahead of time then just call each address once in turn in the (Now) sorted order.

Code: Select all


Sub A_sensor(  )
    If SensorA_Alarm_Timer_speak And SensorA_Sensor and SensorA_priority=Priority Then
        Speak_data(17,SensorA.byte0,SensorA.byte1)
    End If 
end sub   
   
   
Sub B_sensor(  )   
    If SensorB_Alarm_Timer_speak And SensorB_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorB.byte0,SensorB.byte1)
    End If    
end sub   

Sub C_sensor(  )   
    If SensorC_Alarm_Timer_speak And SensorC_Sensor and SensorB_priority=Priority Then
        Speak_data(17,SensorC.byte0,SensorC.byte1)
    End If 
end sub  

etc etc.. 

Structure Address_type
	Address as word
	Priority as byte
end structure
Dim Sub_Address(3) as Address_type

Dim Temp_address as word

Sub_address.address(0) = Addressof (A_sensor)
Sub_address.Priority(0)=3
Sub_address.address(1) = Addressof (B_sensor)
Sub_address.Priority(1)=1
Sub_address.address(2) = Addressof (C_sensor)
Sub_address.Priority(2)=2

//Here call subroutine that sorts the structured array by Priority. 

For Loop = 0 to 2
	Temp_address=Sub_address.address(Loop)
	asm 
		call Temp_address
	end asm
next
So with that long winded background, is using addressof as above then using an inline ASM CALL the correct way to do this?
Will the ASM call cause issues with context saving for example?
IS the a better way to do it?

Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: AddressOf command

Post by Jerry Messina » Tue Sep 12, 2023 1:39 pm

Calling routines from asm code "hides" the routine from the compiler.
In any case, there's no such thing as an indirect asm CALL instruction, so the following doesn't do what you think it does

Code: Select all

dim tempaddr as word
tempaddr = addressof(c_sensor)
asm
    call tempaddr
end asm
An asm CALL translates to a 20-bit immediate address value, not the contents of some variable.

There are ways to do this, but it's messy, very messy.
How about using Events? They are a form of indirect function call and it's built into the language.

Code: Select all

// max number of events/sensor routines
#option MAX_SENSORS = 3

// dummy variables just to compile the example
dim sensor as byte
dim ix as byte

// event handler type... 
type TEvent = event() 

// the event handlers... one for each sensor
public event A_sensor()
    sensor = 1      // just some dummy code
{
    If SensorA_Alarm_Timer_speak And SensorA_Sensor Then
        Speak_data(17,SensorA.byte0,SensorA.byte1)
    End If 
}
end event
   
public event B_sensor()
    sensor = 2      // just some dummy code
{
    If SensorB_Alarm_Timer_speak And SensorB_Sensor Then
        Speak_data(17,SensorB.byte0,SensorB.byte1)
    End If
}
end event

public event C_sensor()
    sensor = 3      // just some dummy code
{
    If SensorC_Alarm_Timer_speak And SensorC_Sensor Then
        Speak_data(17,SensorC.byte0,SensorC.byte1)
    End If 
}
end event

// etc, etc up to sensor 11
 
//---------------------------------------------------
// main code
//---------------------------------------------------
// declare an array of events
// the list will hold the sensor events in the order you want to call them
dim SensorList(MAX_SENSORS) as TEvent 

// init the list
clear(SensorList)

// assign the order
SensorList(0) = C_sensor
SensorList(1) = A_sensor
SensorList(2) = B_sensor

// call all the event handlers on the list
for ix = 0 to bound(SensorList)
    SensorList(ix)
next

// now change the order, this time skipping sensor A
SensorList(0) = B_sensor
SensorList(1) = C_sensor
SensorList(2) = 0           // a null event will be skipped

// call all the event handlers on the list
for ix = 0 to bound(SensorList)
    SensorList(ix)
next

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: AddressOf command

Post by Gunplumber » Wed Sep 13, 2023 12:33 pm

Hi Jerry

Thanks for the reply. I knew there had to be a suitable way to do it.. :D
I have copied and modified your code sample to suit and it does appear work except for some reason if the event calls the sub

Code: Select all

Speak_data(17,SensorN.byte0,SensorN.byte1)
then the Speak_data () sub is called correctly only once after which the events stop working. In fact if any sub is called from inside the event then the rest of the events appear to stop working.

My code.

Code: Select all


Type Switch_sensor=Event()

Public Event A_sensor_Switch()    
    If A_Speak_Switch And A_Sensor Then         
        Speak_data(17,SensorA.byte0,SensorA.byte1)
    End If 
End Event

Public Event B_sensor_Switch()    
     LCD.WriteAt(1,13,"RP")
     If B_Speak_Switch And B_Sensor Then         
        Speak_data(18,BSensor.byte0,Bsensor.byte1)
    End If
End Event
//etc etc
//rest (8 in total 0-7 ) of events for each sensor here. 


Dim Sensor_list_Switch(8) As Switch_sensor

Sensor_list_Switch(0)=A_sensor_Switch
Sensor_list_Switch(1)=B_sensor_Switch
Sensor_list_Switch(2)=C_sensor_Switch
// etc etc up to 7 


//subroutine from which the events are called. 

Sub Speak_switch_sensors()
     Dim List_counter As Byte
     For List_counter=0 To 7               
        Sensor_list_Switch(List_counter)
     Next 
end sub    


Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: AddressOf command

Post by Jerry Messina » Wed Sep 13, 2023 12:46 pm

What happens if they don't call a sub? Does it work ok?
Does it matter if they don't call the Speak_data sub?

I'll need more details to see what's going on, but I'm not really going to be available much for the next few days.

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: AddressOf command

Post by Gunplumber » Wed Sep 13, 2023 1:09 pm

Hi Jerry
The For-Next loop executes to completion and counts through each event but it appears as though once an event calls a Sub then the rest of the events in the For-next loop are not actually executed..
I’ll have to test it but I assume for now that if no subs are called then the events execute correctly..
Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: AddressOf command

Post by Jerry Messina » Fri Sep 15, 2023 7:28 pm

Lee, check your PM's

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: AddressOf command

Post by Gunplumber » Fri Sep 15, 2023 10:58 pm

Jerry

Thanks for getting back to me.. I managed to get it working by adding some code the check that each event was being executed..

Code: Select all

Public Event A_sensor_Switch()    
    Inc(Test_event_counter)
    If A_Speak_Switch And Sensor_A Then         
        Speak_data(17,A_Sensor.byte0,A_Sensor.byte1)
    End If 
End Event
Adding the INC() command somehow made it all work.. If i remove them it stops working.
In any case i am happy to leave them there..
Just for my own edification I do have some questions about how to declare events and how it all fits together.
Can you check what i say here and tell me if i have if right..

Code: Select all

Type Switch_sensor=Event()
This creates a Variable "Switch_sensor" with an "event" type data structure.

Code: Select all

Public Event A_sensor_Switch()    
    Inc(Test_event_counter)
    If A_Speak_Switch And Sensor_A Then         
        Speak_data(17,A_Sensor.byte0,A_Sensor.byte1)
    End If 
End Event
This creates an event routine with an indirect address stored in "A_Sensor_switch"

Code: Select all

Dim Sensor_list_Switch(9) As Switch_sensor
This creates an structured array of "event" types

Code: Select all

Sensor_list_Switch(0)=A_sensor_Switch
This stores the address of the "A_Sensor_switch" event in the "Event" type Array of "Sensor_list_switch(0)".

If i have that right then is it possible to convert between an event type and say a word or longword type to store and recall the address in rom?

IE does the following code work..

Code: Select all

Dim Temp_address as word.

Sensor_list_Switch(0)=A_sensor_Switch      //assign the event address

Temp Address=Sensor_list_switch(0)         //copy event address to temp variable

EE.Write ( Temp address)   //store it in EEprom

EE.Read (Temp_address)    //Read it back from EEprom

Sensor_list_switch(0) = Temp Address    // copy back to event array

Sensor_List_switch(0)  // execute the event

Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: AddressOf command

Post by Jerry Messina » Sat Sep 16, 2023 1:09 pm

Close, but not quite.

When you have this:

Code: Select all

type TEvent = event() 
you're declaring a new user type called 'TEvent', and that type is a pointer to an event() routine.

When you define an instance of that type you create a variable, and the variable can hold the address of an event() procedure
which you can then set to any defined event() and call using the variable. The compiler will take the
variable contents (which is an address in program memory) and do an indirect call.

Code: Select all

dim MyEventHandle as TEvent

// get the address of the event routine Sensor_A()
MyEventHandle = Sensor_A()

// and invoke the event (ie indirect call) 
MyEventHandle()
Checkout the online help 'Language Reference' sections 'User Types' and 'Events'... that has a better description than the pdf manual.

As far as using eeprom or saving a TEvent type variable into another, one thing to note is that the size of TEvent will change
depending on the rom size of the device (see device file '_maxrom'). For a device with >64K it's 4 bytes (longword),
otherwise it's 2 bytes (word). You can use 'sizeof(TEvent)' to get the required size if needed.

You can directly use one of the overloaded eeprom read/write routines to save a TEvent type without assigning it to a temp first...

Code: Select all

Sensorlist(0) = A_sensor      //assign the event address

EE.Write(0, Sensorlist(0))   //store it in EEprom at addr 0
EE.Read(0, Sensorlist(0))    //read it back from EEprom
That'll automatically adjust for the sizeof(TEvent) for you, otherwise make sure your temp variable is the proper size;

You can store an entire list using something like:

Code: Select all

dim SensorList(11) as TEvent
dim ix as byte

// save the list into eeprom starting at addr 100
for ix = 0 to bound(SensorList)
  EE.Write(100 + (ix * sizeof(TEvent)), SensorList(ix))
next
One caveat... since a TEvent is a program memory address if you modify the code, recompile it, use a bootloader, etc then there's a good chance the data stored in the eeprom is no longer valid and if you recall the data and use it you'll go off into never-never land.

Post Reply