The native Peripheral I/O (PIO) APIs let you write C/C++ code to control GPIO, PWM, I2C, SPI and UART peripherals that access the same underlying peripheral service as the standard PIO APIs. These APIs utilize the Android Native Development Kit.
Adding the required permission
Add the required permission for this API to your app's manifest file:
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
Creating a client
The APeripheralManagerClient
class is defined in the Android Things native
library. Use
this class to create a client to communicate with the manager for each type of
interface (for example, GPIO). The client sets up the connection to the
interface:
APeripheralManagerClient* client = APeripheralManagerClient_new();
AGpio* gpio;
int openResult = APeripheralManagerClient_openGpio(client, BUTTON_GPIO, &gpio);
// Check openResult since native commands return non-zero values on error (see Error handling section below)
When the app is destroyed, be sure to delete the connection and the client:
AGpio_delete(gpio);
APeripheralManagerClient_delete(client);
Reading from an input
To read a GPIO port as an input:
- Configure it as an input using
AGpio_setDirection()
with the modeAGPIO_DIRECTION_IN
. Optional: Configure the pin voltage represented by an input value of
true
(active) withAGpio_setActiveType()
:AGPIO_ACTIVE_HIGH
: High voltage (near IOREF) is active. This is the default value.AGPIO_ACTIVE_LOW
: Low voltage (near zero) is active.
Access the current state with the
AGpio_getValue()
function.
int setDirectionResult = AGpio_setDirection(gpio, AGPIO_DIRECTION_IN);
//AGPIO_ACTIVE_HIGH is the default so no need to use AGpio_setActiveType()
int gpioValue;
if (AGpio_getValue(gpio, &gpioValue) != 0) {
LOGE("failed to get value for GPIO: %s", BUTTON_GPIO);
}
Interrupt
You could set up a loop to poll (as above) the GPIO interface continuously, but that might be a waste of system resources. You might also miss some events, depending on the poll interval. Consider the case of a button wired as an input to a GPIO interface. Most of the time, the button will not be pressed but you still need to constantly check it.
A better way to check the interface would be to set up an interrupt. The interrupt fires when an external event occurs, like a button press.
Declare the state changes that trigger an interrupt event using the AGpio_setEdgeTriggerType()
function. The edge trigger supports the following four types:
AGPIO_EDGE_NONE
: No interrupt events. This is the default value.AGPIO_EDGE_RISING
: Interrupt on a value transition from false to trueAGPIO_EDGE_FALLING
: Interrupt on a value transition from true to falseAGPIO_EDGE_BOTH
: Interrupt on all transitions
This example shows how to set up an interrupt that will fire on the falling edge of the input signal (assuming the button was connected to a pull-up resistor):
int setEdgeTriggerResult = AGpio_setEdgeTriggerType(gpio, AGPIO_EDGE_FALLING);
Create a file descriptor for this interrupt:
int fd;
int getPollingFdResult = AGpio_getPollingFd(gpio, &fd);
Next, return the looper associated with the calling thread and add the file descriptor to it. A looper efficiently polls a file descriptor to see if it has data available on it.
ALooper* looper = ALooper_forThread();
int addFdResult = ALooper_addFd(looper, fd, LOOPER_ID_USER, ALOOPER_EVENT_INPUT, NULL, NULL);
Now poll the looper with an indefinite timeout. Acknowledge any interrupt events (in this case, print out the pin/signal name connected to the button). Note that execution will block until there is an event on the file descriptor or the app is exited.
while (!app->destroyRequested) {
android_poll_source* source;
// wait indefinitely for an interrupt or a lifecycle event.
int pollResult = ALooper_pollOnce(-1, NULL, NULL, (void**)&source);
if (pollResult >= 0) {
if (source != NULL) {
// forward event to native-app-glue to handle lifecycle and input event
// and update `app` state.
source->process(app, source);
}
if (pollResult == LOOPER_ID_USER) {
int ackInterruptResult = AGpio_ackInterruptEvent(fd);
LOGI("GPIO \"%s\" changed: button pressed", BUTTON_GPIO);
}
}
}
Error handling
Many of the native GPIO-related commands return non-zero integer values on error. You can use these return values in conditionals:
int gpioValue;
if (AGpio_getValue(gpio, &gpioValue) != 0) {
LOGE("failed to get value for GPIO: %s", BUTTON_GPIO); // Retry later
}
ASSERT
statements are useful for catching fatal errors. If the condition (specified in
the first parameter of the function) does not evaluate to true, then the program
logs the debug statement and terminates:
ASSERT(client, "failed to open peripheral manager client");
Since the following command returns an integer value for errors, you can use
it as the condition in an ASSERT
statement:
int openResult = APeripheralManagerClient_openGpio(client, BUTTON_GPIO, &gpio);
ASSERT(openResult == 0, "failed to open GPIO: %s", BUTTON_GPIO);