[MAT logo][Prev][Up][Next]

A custom I/O module

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.


Three functions to write here.

Input and output


These functions are useful if you need them, but you probably don't. Most likely they'll be blank.


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.

Order of calling

The I/O library will call the your functions in the following order:

Function prototypes

Here are the functions you need to define. (The actual definition is in the lib/io/io_hw.h file; if there's a clash, please let us know so we can fix this manual.)
The default name of the IO module.

int io_hw_parse_config(void)
This function is called first so the module gets a chance to parse any specific configuration before any other functions get called.

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)
This function must parse the I/O address described in the string 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)
Connect to hardware, etc. This function is called at the end of start-up, after all the I/O addresses have been parsed, so it can also do whatever housekeeping is required, allocate buffers depending on the number of I/O addresses used, and so on.

int io_hw_write(io_addr_t *io_addr, u32 value)
int io_hw_read (io_addr_t *io_addr, u32 *value)
These functions will be called to read/write a single point at a time. They should send/store the result in the 'value' variable

Should return 0 on success, -1 on failure.

int io_hw_write_end(void)
int io_hw_read_end (void)
These are useful when the hardware itself is double buffered - you may need to do some special stuff with the hardware to tell it to update the outputs with the values you have been writing, or similar. If nothing special needs to be done at this point, simply return 0;

Should return 0 on success, -1 on failure.

int io_hw_done(void)
Terminate connection to hardware and generally shut down. Should return 0 on success, -1 on failure.
int io_hw_dump_config(int debug_level)
Should dump to plc_log_trcmsg(debug_level, ...) any configuration parameters it will use. Used only for debugging purposes. Please use the debug_level specified.

Should return 0 on success, -1 on failure.

char *io_hw_ioaddr2str(io_addr_t *io_addr)
Should return a string description of the io_addr. Memory for the string must be malloc()'d, and will be free()'d by the calling function in the io library.

Overriding the scan loop

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.

Utility functions

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.

Slave mode

An IO module may work in one of two modes: master or slave. By default it will work in the master mode.

To run in slave mode, where it responds to requests from the bus... FIXME


$Date: 2005/08/20 06:11:42 $