Software Handles Defined
You can think of a software handle as a suitcase (with a physical handle) containing a document, sometimes more than one, each in a foreign language. You carry it by the handle, and you can open it to see the contents, but you can’t understand its contents unless you speak the language of the document inside. For a more detailed description of handles refer to the Wikipedia article here.
The MPLAB® Harmony framework uses handles (pointers) to reference a collection of objects (structures) in RAM. The objects only have meaning to the driver code, which understands how the objects are arranged and what their internal data means. Only dynamic drivers (not static drivers) use handles to reference their objects. See “Static vs Dynamic” in the MPLAB Harmony Help file for more information.
Handles in MPLAB® Harmony
To understand how MPLAB Harmony uses handles, it helps to understand exactly what happens when a dynamic driver is initialized. Typically there are a few connected data structures initialized when a driver is opened, and the index to them is through a driver handle. Take for example the USART. When a DRV_USART_Initialize() function is called, three objects are initialized as shown in the following image.
The Client Object contains information related to driver status from the client’s perspective, information about whether or not the driver is in use, event handler function information, and buffer references for the client. It is pointed to by the handle to the driver.
The Instance Object contains system level status information (like whether or not the driver is initialized), the driver operational mode, the number of clients that have opened the driver, the driver interrupt sources, and more.
The Common Data Object tracks data that is common to all instances of a driver, like information used for protecting resources shared by the driver.
Note the particular objects used by each driver are not necessarily the same. The objects shown previously are a convenient example due to their simplicity.
The following image shows a typical handle stored in memory. When the USART driver is opened, the user’s appData variable contains a handle (in this case called usartHandle) returned by DRV_USART_Open(). The handle points to address 0xA0000284.
The 0xA0000284 address is only one of the collection of objects defined globally, in RAM, in drv_usart.c. In this case, gDrvUSARTObj, gDrvUSARTClientObj, and gDrvUSARTCommonDataObj are aLL data objects used by the dynamic USART driver. The usartHandle simply points to one of the objects (gDrvUSARTClientObj), as shown in the following image.
The question then, is if the handle returned by an Open() function references only one of these three data objects related to the driver, how do the driver level functions know about the other driver objects and what they contain?
The answer to this is that the structure referred to in the driver handle contains references (pointers) to the other objects used by the driver instance (see the following image). Pointers to the related structures to the driver object reside within the top level structure pointed to by the driver handle returned by an Open() function.
Understanding how data connects within an open driver is useful because with a particular driver instance, some objects/data fields may be used, some may not be used, and all the objects may need to be reviewed to understand the current context of the driver. For example, within the USART, some driver instances may have an event handler call-back function registered (a function which automatically executes when a USART event completes). Other instances may not have this, if their event handler field points to NULL.
Now that you understand how driver data connects, you need to understand how to check the data initialized by the Open() function in a dynamic driver. You can do this by placing data objects associated with the driver handle in the MPLAB X watch window to understand the impact of DRV_USART_Open(). Moreover, adding all these objects to the watch window will make it easier for you to understand a driver’s complete context. However, it is often most useful to start off with watching objects related to the Client because they are the most likely to be useful from an application layer perspective.
Objects used by the driver have to be connected through the top level driver object referenced through the driver handle. You can put these structures in the watch window to check the status of the driver while debugging and developing, since they help you understand what is going on with the driver. However, you should not access these objects directly in the application layer code.
The reason you should not access driver data directly is that such accesses violate the protection that a driver offers. Drivers can prevent against access to shared resources by channeling client/driver interaction through controlled access functions. Reaching around these functions (bypassing access via a function call taking an access handle) breaks the intended purpose of the driver, and ends in less robust designs. Reaching around these functions also includes writing PLIB layer code from APP layer code in app.c after the driver has been initialized in app.c, and this is a frequent user mistake.
For example, you can see the status of the USART driver shown in the following image.
Instead of reading gDrvUSARTClientObj->status in the app.c source, use the driver interface functions to retrieve data using the appropriate driver handle (the USART handle in this case) because you can’t be sure that the status field is accurate unless it’s accessed through the appropriate driver API. The driver interface functions are documented for each driver in the harmony_help.chm file under “Library Interface.”
The following image shows a driver interface function being used to check the status of the USART driver. The USART driver handle is passed to the function. Internally, this is reading the gDrvUsartClientObj->status.
As you can see in the previous image, identifying the particular driver instance to the underlying driver is the primary use of a handle. The first order of business after a driver is opened is to see if it is still open, and it is ready for subsequent operations. From this point, other driver layer functions can be called from the application layer to provide a safe and scalable mechanism for interfacing with the device peripherals.