IO modules are all very similar, except for the way they access the hardware, and the format of how that hardware's IO addresses are specified in the configuration file. That is why Mario wrote an IO library to be used by IO modules. It essentially has everything required by an IO module (including the main() function), except for the functions to access the hardware and interpret the hardware's addresses specified in the config file.
What you need to do is write the functions listed below and link everything together - and presto, you have an IO module!
Some of the functions won't be relevant to your module; just write blank functions for those.
io_hw_parse_config()
- this function will be called
first of all; if the module has any global options, this would be the
place to read them from config. io_hw_parse_io_addr()
- will be called once for each
point. a function to translate a string (char *) into an address; if
your inputs and outputs are numbered, you can just use
atoi()
; if it's more complicated, it'll depend on what it
looks like.
io_hw_init()
- the rest of initialization - anything
that needs to be done after all the addresses have been translated. If
the module needs to open devices or similar, this should also be done
here.
io_hw_read()
- takes the address of an
input, and reads it.io_hw_write()
- takes the address of an output and a
value, and writes the value to the output. io_hw_read_end()
- called after each bunch of
reads.io_hw_write_end()
- called after each bunch of
writes - useful if you need to flush a buffer.io_hw_done()
- clean-up should go here, but since
the module will terminate directly afterwards, it's not so important.
You may assume that the plc_init()
, plc_update()
,
plc_scan_beg/end()
and plc_done()
functions
will be called for you at the appropriate times.
Your module will be given 8 bytes per I/O address to store the
details;
if this is insufficien, feel free to malloc(3)
more space
in
the io_hw_parse_config()
and/or
io_hw_parse_io_addr()
functions.
The I/O library will call the your functions in the following order:
io_hw_parse_config()
io_hw_parse_io_addr()
io_hw_init()
while (1)
io_hw_read()
io_hw_read_end()
plc_update()
is called at this
point) io_hw_write()
io_hw_write_end()
io_hw_done()
lib/io/io_hw.h
file; if
there's a clash, please let us know so we can fix this manual.)
const char *IO_MODULE_DEFAULT_NAME
int io_hw_parse_config(void)
For instance, it might parse a list of devices with device parameters. The individual I/O addresses would then use the names of these devices, as given in this list.
It should also check that the size of whatever you're casting io_addr to is no more than the size of io_addr_t, like this:
if (sizeof(my_io_addr_t) > sizeof(io_addr_t)) {
plc_log_errmsg(1,"Datatype size problem.");
return -1;
}
Should return 0 on success, -1 on failure.
int io_hw_parse_io_addr(io_addr_t *io_addr, const char
*addr_stri, dir_t dir, int pt_len)
addr_stri
, and store it in *io_addr
,
which is 8
bytes long. Should return 0 on success, -1 on failure.
The direction and point-length values may be used for
consistency
checking. The definition of dir_t is
enum { dir_in,
dir_out,
dir_none }
int io_hw_init(void)
int io_hw_write(io_addr_t *io_addr, u32 value)
int io_hw_read (io_addr_t *io_addr, u32 *value)
Should return 0 on success, -1 on failure.
int io_hw_write_end(void)
int io_hw_read_end (void)
return 0;
Should return 0 on success, -1 on failure.
int io_hw_done(void)
int io_hw_dump_config(int debug_level)
Should return 0 on success, -1 on failure.
char *io_hw_ioaddr2str(io_addr_t *io_addr)
malloc()
'd, and will be free()
'd
by the calling function in the io library.
If the module is not suited to the standard scan cycle, as for
instance
many network and bus slaves aren't, this can be over-ridden by setting
the
run_loop
callback.
Just write a function the way you want it, and in one of the setup
functions, set run_loop to point to it, thus:
int my_run_loop(void*foo) {
...
}
int io_hw_init(void) {
run_loop = my_run_loop;
...
}
The function my_run_loop()
should return a negative
value
on error. It is not expected to return on success, but if it does, the
value should be non-negative. The parameter is an internal structure,
access to which has not been thought through at this point :-) We
apologise
for the inconvenience.
plc_pt_t io_status_pt(const char *base, const char *suffix, int
loglevel)
This function looks for a point called base.suffix. If it
exists, it's returned. If it doesn't exist, a warning is logged at
loglevel
, and a null point is returned.
The upshot is that if an optional status point is desired, one simply calls this function and uses the returned point as the status point. The user will take advantage of it, or not.
The base
should be the name of the point to which the
status point will refer. The suffix
should indicate what
kind
of status is reported: it might be "ok"
, "err"
,
"timeout"
, "errno"
, or similar, depending on
what
condition is to be indicated by the status point.
To run in slave mode, where it responds to requests from the bus... FIXME
$Date: 2005/08/20 06:11:42 $