HALFRED  0.4.0
Typedefs | Functions
IODevice interface

Detailed Description

The most important IO module interface is the IODevice interface. It provides an abstraction of a generic input/output device general enough to represent peripherals such as UART or SPI allowing data buffering, signaling transaction events including transfer errors, utilizing DMA transfers and possible cooperation with an operating system.

IODevice implementation for each peripheral instance in use is provided by the port. Every IODevice shares the same unified application programming interface (API). This API is presented in table below. The picture shows a visualization of the abstract IODevice model.

Configuration
iodevice.png
IODEV_Init Initializes the IODevice
IODEV_Deinit Deinitializes the IODevice
IODEV_IOCtl

Allows additional device-specific control

Controlling flow of data
IODEV_EnableRead Enables reception for the specified IODevice
IODEV_EnableWrite Enables transmission for the specified IODevice
IODEV_DisableRead Disables reception for the specified IODevice
IODEV_DisableWrite

Disables transmission for the specified IODevice

Controlling state of transfer
IODEV_GetReadCount Gets the amount of data available for reading in the IODevice
IODEV_GetWriteSpace

Gets the amount of free buffering space in the IODevice

Reading and writing
IODEV_Read Reads data from IODevice
IODEV_Write

Writes data to IODevice

OS support
IODEV_Lock Locks the IODevice to prevent concurrent access
IODEV_Unlock Unlocks the IODevice

Advantages of using IODevices.

The IODevice provides an unified interface for several different peripherals. It makes it possible for the application or component code to provide generic functions that rely on the realization of the IODevice interface (supplied by the port). While being generic, the IODevice interface provides means to implement rich I/O functionality, including operating system support.

Configuring and initializing IODevices.

IODevice objects are supplied by the port. In case of microcontrollers, the port usually supplies the actual objects. For example the AVR family port supplies IODevice objects named IO_USART0, IO_USART1 etc., depending on how many actual USART ports are available in a particular microcontroller. The port may also supply a function that dynamically creates the IODevices. This is the case for example in the PC ports, where a function is provided to create an IODevice for a particular serial port, since the number of available ports is not constant.

Once the IODevice object is available, it must be initialized before it is used. To do that IODEV_Init function must be called. The responsibility of this function is to generally prepare the IODevice for operation. This usually means configuring it's power supply and clocking options, preparing buffers etc. A complementary function IODEV_Deinit deinitializes the IODevice, bringing it back to the initial state, usually freeing all the possible resources taken.

The following example shows the init-deinit pattern.

// initialize the IODevice
IODEV_Init(IO_USART0);
// here we can use the IODevice
// IODevice not needed anymore, deinitialize it
IODEV_Deinit(IO_USART0);

Controlling the flow of data.

Application can enable or disable the receive or transmit path for the IODevice. By default the both signal paths should be disabled, and must be enabled prior use. Calling IODEV_EnableWrite enables the transmit path, which means that the data written to the IODevice will be available to the outside world. Similarly, calling IODEV_EnableRead enabled the IODevice to receive input from the outside world. Disabling the transmit path by calling IODEV_DisableWrite results in no output from the microcontroller. That means there is no data flow. However, it does not mean that application cannot write to the IODevice. The application can write to it as long as there is enough buffering space. Application may check that space it by calling IODEV_GetWriteSpace. This is particularly useful when the underlying driver supports buffering, where data can be prepared before it is actually sent, especially if the preparing time is quite long. When disabling the receive path by calling IODEV_DisableRead, the external data will not be received any longer. However, the data that came before the disable call, but have not been processed can still be read from the IODevice. The IODEV_GetReadCount function always shows how much data is left in the IODevice to read.

Please note, that all the parameters and result values that indicate the amount of data are expressed as number of elements not bytes. In some cases a single element may be larger than one byte. One example is using 9-bit UART frames. In this case the IODevice implementation may expect a 16-bit element size. Please consult the documentation for the particular IODevice supplied by the port.

Writing to an IODevice.

The IODevice interface supplies only one function for writing called IODEV_Write. This function writes data to the IODevice. If the transmit path of the IODevice is enabled (see IODEV_EnableWrite) then the data is immediately available at the output. If the transmit path is disabled (by default or by the call to IODEV_DisableWrite) then the data is written to a possible internal IODevice output buffer. Note however, that not all IODevices have the capability to queue output data. For example, many IODevice implementations working in polling mode will not be able to buffer the data, unless the hardware supports this, as they operate directly on the peripheral registers. The available buffering capability can always be checked by a call to IODEV_GetWriteSpace.

The following example shows an efficient way of sending a data array. The example assumes that the IODevice named "iodev" is already initialized, and the "arraySize" is the size of the "array" expressed in characters.

void sendArray(IODevice iodev, const char* array, size_t arraySize)
{
size_t chunkSize;
// first some sanity checks
if ((NULL != iodev) && (NULL != array)) {
while (arraySize) {
// there is more to send, check how much data will still fit to the output buffer
chunkSize = IODEV_GetWriteSpace(iodev);
if (chunkSize > 0) {
// there is some space in the output buffer
// truncate the chunkSize if it's bigger than the data left to send
if (chunkSize > arraySize) {
chunkSize = arraySize;
}
// write the data to the IODevice
chunkSize = IODEV_Write(iodev, array, chunkSize, 0);
// the return value of the IODEV_Write indicates how many elements were actually written
// indicate the progress
array += chunkSize;
arraySize -= chunkSize;
} else {
// Depending on the particular case, this is the place where we can delay the operation
// and wait for the IODevice to flush at least part of the output buffer, using for
// example the \ref OS_Sleep method.
}
}
}
}

Reading from an IODevice.

Similar to the case of writing, the IODevice interface supplies only one function for reading called IODEV_Write. This function reads data from the IODevice. If the receive path of the IODevice is enabled (see IODEV_EnableRead) then the data comming from the outside world is immediately made available for reading. If the receive path is disabled (by default or by the call to IODEV_DisableRead) then the no new data can be gathered from the outside world. However the data acquired prior to the disable call is still available for reading depending on the buffering capabilities of the particular IODevice. Note however, that not all IODevices have the capability to queue input data. For example, many IODevice implementations working in polling mode will not be able to buffer the data, unless the hardware supports this, as they operate directly on the peripheral registers. In such case the data must be usually read before new data comes. The size of the data available for reading can always be checked by a call to IODEV_GetReadCount.

The following example shows the read pattern.

size_t readCount;
unsigned char buf[64];
// Check if there is something to read
readCount = IODEV_GetReadCount(IO_USART0);
if (readCount) {
// there is something in the input buffer
// make sure that we don't overflow the buffer
if (readCount > sizeof(buf)) {
readCount = sizeof(buf);
}
// read from IODevice
readCount = IODEV_Read(IO_USART0, &buf, readCount,0);
}

Timeouts.

Both the IODEV_Write and IODEV_Read functions have a timeout parameter. This parameter is used when operating system is present (see HAL_ENABLE_OS in HAL configuration file) and when OS integration mode is enabled for the IO module (see HAL_IO_OS_INTEGRATION definition in HAL configuration file). In this case the IO module can use OS module and implement the timeout mechanism based on the OSNotifier interface.

If OS integration mode is not available, the timeout parameter is omitted and should be set to 0 in all calls to IODEV_Read and IODEV_Write. If OS integration mode is enabled, the timeout parameter for IODEV_Read calls is used to specify how long should the function wait for the given amount of data. The same applies to IODEV_Write, where the timeout parameter is used to specify how long to wait until the given amount of data is actually sent out. Specifying timeout parameter as 0 means that there is no timeout applied and the operation may finish at any given time (may even possibly take forever to execute).

The timeout mechanism relies on the port implementation of the IODevice interface to properly signal write and read events. For details please refer to IODevice internals.

Please also note, that in OS integration mode every read and write operation with non-zero timeout require a dedicated OSNotifier interface instance. The HAL_OS_NOTIFIER_POOL_SIZE parameter in HAL configuration file specifies how many OS notifier instances are available to the application.

Concurrent access and thread-safety.

The IODevice API calls are not immediately thread safe and care should be taken not to let concurrent access to the IODevice. To make it easier, each IODevice has a built-in lock-unlock functionality utilizing the OSMutex interface. The basic pattern is to lock the IODevice (including checking if the lock was successful) before usage, and then unlock it. The following code shows such example

// try to lock the IODevice
if (HAL_SUCCESS(IODEV_Lock(IO_USART0, 1000))) {
// lock successful, we can now safely use the IODevice
IODEV_Write(IO_USART0, "hello", 5, 0);
// job is done, unlock the IODevice
IODEV_Unlock(IO_USART0);
}

Typedefs

typedef OSTime IOTime
 
typedef struct IODeviceDescIODevice
 

Functions

HALRESULT IODEV_Init (IODevice iodevice)
 
HALRESULT IODEV_Deinit (IODevice iodevice)
 
HALRESULT IODEV_IOCtl (IODevice iodevice, unsigned int request,...)
 
HALRESULT IODEV_EnableWrite (IODevice iodevice)
 
HALRESULT IODEV_DisableWrite (IODevice iodevice)
 
HALRESULT IODEV_EnableRead (IODevice iodevice)
 
HALRESULT IODEV_DisableRead (IODevice iodevice)
 
size_t IODEV_GetReadCount (IODevice iodevice)
 
size_t IODEV_GetWriteSpace (IODevice iodevice)
 
size_t IODEV_Read (IODevice iodevice, void *data, size_t size, IOTime timeout)
 
size_t IODEV_Write (IODevice iodevice, const void *data, size_t size, IOTime timeout)
 
int IODEV_Lock (IODevice iodevice, unsigned int timeout)
 
void IODEV_Unlock (IODevice iodevice)
 

Typedef Documentation

typedef OSTime IOTime

Definition of time type used in IO module. This evaluates to OSTime if HAL_IO_OS_INTEGRATION is defined or simple unsigned int otherwise.

typedef struct IODeviceDesc* IODevice

The actual definition of an IODevice type. This is actually a pointer to a private structure IODeviceDesc. For details see IODevice internals.

Function Documentation

HALRESULT IODEV_Init ( IODevice  iodevice)

Initializes the hardware peripheral, represented by the the IODevice.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_Deinit ( IODevice  iodevice)

Deinitializes the hardware peripheral, associated with the IODevice.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_IOCtl ( IODevice  iodevice,
unsigned int  request,
  ... 
)

Executes a low level control function on the provided IODevice.

Parameters
iodevicehandle of the IODevice
requestrequest code
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_EnableWrite ( IODevice  iodevice)

Enables the transmit path.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_DisableWrite ( IODevice  iodevice)

Disables the transmit path.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_EnableRead ( IODevice  iodevice)

Enables the receive path.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
HALRESULT IODEV_DisableRead ( IODevice  iodevice)

Disables the receive path.

Parameters
iodevicehandle of the IODevice
Returns
HALRESULT_OK on success. Other values indicate error (see HALRESULT).
size_t IODEV_GetReadCount ( IODevice  iodevice)

Returns the number of bytes available for reading from the device.

Parameters
iodevicehandle of the IODevice
Returns
number of bytes that the IODevice can currently provide
size_t IODEV_GetWriteSpace ( IODevice  iodevice)

Returns the number of bytes that can be written to the device.

Parameters
iodevicehandle of the IODevice
Returns
number of bytes that the IODevice can currently accept
size_t IODEV_Read ( IODevice  iodevice,
void *  data,
size_t  size,
IOTime  timeout 
)

Reads a specified number of elements from the iodevice.

Parameters
iodevicehandle of the IODevice
datapointer to the location where data will be stored
sizesize of data to read
timeouttimeout value (in miliseconds). This parameter is ignored and should be set to 0 when OS integration is disabled.
Returns
number of bytes actually read

Referenced by TXTDEV_ReadString().

size_t IODEV_Write ( IODevice  iodevice,
const void *  data,
size_t  size,
IOTime  timeout 
)

Writes a specified number of elements to the iodevice.

Parameters
iodevicehandle of the IODevice
datapointer to the data to write
sizesize of the data to write (in bytes)
timeouttimeout value (in miliseconds). This parameter is ignored and should be set to 0 when OS integration is disabled.
Returns
number of bytes actually written

Referenced by TXTDEV_ReadString(), TXTDEV_WriteData(), TXTDEV_WriteDate(), TXTDEV_WriteFLOAT(), TXTDEV_WriteINT32(), TXTDEV_WriteINT64(), TXTDEV_WriteNL(), TXTDEV_WriteString(), TXTDEV_WriteTime(), TXTDEV_WriteUINT32(), and TXTDEV_WriteUINT64().

int IODEV_Lock ( IODevice  iodevice,
unsigned int  timeout 
)

Locks access to a specified IODevice prohibiting concurrent access. This function is used to guard access to shared IODevice. After acquiring lock it must be released through a call to IODEV_Unlock. This function requires HAL_IO_OS_INTEGRATION to be set to non-zero value in hal_config.h

Parameters
iodevicehandle of the IODevice
timeoutmaximum time waiting for device
Returns
!0 (logical true) if IODevice was successfully locked, 0 (logical false) otherwise.
void IODEV_Unlock ( IODevice  iodevice)

Unlocks access to a specified IODevice re-enabling access to it. This function requires HAL_IO_OS_INTEGRATION to be set to non-zero value in hal_config.h

Parameters
iodevicehandle of the IODevice