diff --git a/README.md b/README.md index 59ffba1..1134077 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ dependencies { Driver | Type | Usage (add to your gradle dependencies) | Note :---:|:---:| --- | --- -[driver-sh1106](driver-sh1106) | OLED display | `implementation 'com.leinardi.androidthings:driver-sh1106:0.1'` | [![Bintray](https://img.shields.io/bintray/v/leinardi/androidthings/driver-sh1106.svg)](https://bintray.com/leinardi/androidthings/driver-sh1106) [changelog](driver-sh1106/CHANGELOG.md) +[driver-pcf8574-hd44780](driver-pcf8574-hd44780) | LCD | `implementation 'com.leinardi.androidthings:driver-pcf8574-hd44780:0.1'` | [![Bintray](https://img.shields.io/bintray/v/leinardi/androidthings/driver-pcf8574-hd44780.svg)](https://bintray.com/leinardi/androidthings/driver-pcf8574-hd44780) [changelog](driver-pcf8574-hd44780/CHANGELOG.md) [sample](sample-pcf8574-hd44780) +[driver-sh1106](driver-sh1106) | OLED display | `implementation 'com.leinardi.androidthings:driver-sh1106:0.1'` | [![Bintray](https://img.shields.io/bintray/v/leinardi/androidthings/driver-sh1106.svg)](https://bintray.com/leinardi/androidthings/driver-sh1106) [changelog](driver-sh1106/CHANGELOG.md) [sample](sample-sh1106) ## License diff --git a/driver-pcf8574-hd44780/.gitignore b/driver-pcf8574-hd44780/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/driver-pcf8574-hd44780/.gitignore @@ -0,0 +1 @@ +/build diff --git a/driver-pcf8574-hd44780/CHANGELOG.md b/driver-pcf8574-hd44780/CHANGELOG.md new file mode 100644 index 0000000..173ff99 --- /dev/null +++ b/driver-pcf8574-hd44780/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +## [0.1] - 2017-12-19 +- initial version diff --git a/driver-pcf8574-hd44780/README.md b/driver-pcf8574-hd44780/README.md new file mode 100644 index 0000000..1947ac2 --- /dev/null +++ b/driver-pcf8574-hd44780/README.md @@ -0,0 +1,87 @@ +# HD44780 LCD driver for Android Things + +[![Bintray](https://img.shields.io/bintray/v/leinardi/androidthings/driver-pcf8574-hd44780.svg?style=plastic)](https://bintray.com/leinardi/androidthings/driver-pcf8574-hd44780) +[![Build Status](https://img.shields.io/travis/leinardi/androidthings-drivers/master.svg?style=plastic)](https://travis-ci.org/leinardi/androidthings-drivers) +[![GitHub license](https://img.shields.io/github/license/leinardi/androidthings-drivers.svg?style=plastic)](https://github.com/leinardi/androidthings-drivers/blob/master/LICENSE) + +This driver supports LCD peripherals built on the HD44780 chip and controlled with the PCF8574 chip. + +NOTE: these drivers are not production-ready. They are offered as sample +implementations of Android Things user space drivers for common peripherals +as part of the Developer Preview release. There is no guarantee +of correctness, completeness or robustness. + +This driver is based on the [lcd-pcf8574-androidthings driver from Nilhcem](https://github.com/Nilhcem/lcd-pcf8574-androidthings) +and the [hd44780-i2c driver from gorskima](https://github.com/gorskima/hd44780-i2c) + +## How to use the driver + +### Gradle dependency + +To use the `driver-pcf8574-hd44780` driver, simply add the line below to your project's `build.gradle`, +where `` matches the last version of the driver available on [jcenter][jcenter]. + +``` +dependencies { + implementation 'com.leinardi.androidthings:driver-pcf8574-hd44780:' +} +``` + +### Sample usage + +```java +import com.leinardi.androidthings.driver.pcf8574.hd44780.Hd44780; + +// Access the LCD: +Hd44780 mLcd; + +try { + mLcd = new Hd44780(BoardDefaults.getI2CPort(), Hd44780.I2cAddress.PCF8574AT, Hd44780.Geometry.LCD_20X4); +} catch (IOException e) { + // couldn't configure the LCD... +} + +// Draw on the screen: + +try { + mLcd.setBacklight(true); + mLcd.cursorHome(); + mLcd.clearDisplay(); + mLcd.setText("Hello LCD"); + int[] heart = {0b00000, 0b01010, 0b11111, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000}; + mLcd.createCustomChar(heart, 0); + mLcd.setCursor(10, 0); + mLcd.writeCustomChar(0); +} catch (IOException e) { + // error setting LCD +} + +// Close the LCD when finished: + +try { + mLcd.close(); +} catch (IOException e) { + // error closing LCD +} +``` + +## License + +Copyright 2017 Roberto Leinardi + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +[jcenter]: https://bintray.com/leinardi/androidthings/driver-pcf8574-hd44780/_latestVersion diff --git a/driver-pcf8574-hd44780/build.gradle b/driver-pcf8574-hd44780/build.gradle new file mode 100644 index 0000000..03dbd08 --- /dev/null +++ b/driver-pcf8574-hd44780/build.gradle @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.library' +apply from: rootProject.file('checkstyle.gradle') + +android { + compileSdkVersion build_versions.target_sdk + buildToolsVersion build_versions.build_tools + + apply from: 'mavenConfig.gradle' + + defaultConfig { + minSdkVersion build_versions.min_sdk + targetSdkVersion build_versions.target_sdk + versionCode build_versions.version_code + versionName mvn_config.version as String + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility build_versions.java_version + targetCompatibility build_versions.java_version + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + compileOnly deps.androidthings + implementation deps.support.annotations + + testImplementation deps.androidthings + testImplementation deps.powermock.module_junit4 + testImplementation deps.powermock.api_mockito +} diff --git a/driver-pcf8574-hd44780/docs/HD44780.pdf b/driver-pcf8574-hd44780/docs/HD44780.pdf new file mode 100644 index 0000000..5cd5f79 Binary files /dev/null and b/driver-pcf8574-hd44780/docs/HD44780.pdf differ diff --git a/driver-pcf8574-hd44780/docs/PCF8574.pdf b/driver-pcf8574-hd44780/docs/PCF8574.pdf new file mode 100644 index 0000000..06f8854 Binary files /dev/null and b/driver-pcf8574-hd44780/docs/PCF8574.pdf differ diff --git a/driver-pcf8574-hd44780/docs/PCF8574_PCF8574A.pdf b/driver-pcf8574-hd44780/docs/PCF8574_PCF8574A.pdf new file mode 100644 index 0000000..52e6e40 Binary files /dev/null and b/driver-pcf8574-hd44780/docs/PCF8574_PCF8574A.pdf differ diff --git a/driver-pcf8574-hd44780/maven.gradle b/driver-pcf8574-hd44780/maven.gradle new file mode 100644 index 0000000..763b54d --- /dev/null +++ b/driver-pcf8574-hd44780/maven.gradle @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.github.dcendents.android-maven' + +install { + repositories.mavenInstaller { + pom.project { + name 'driver-pcf8574-hd44780' + description 'Android Things driver for SH1106' + url 'https://github.com/leinardi/androidthings-drivers' + inceptionYear '2017' + + packaging 'aar' + groupId 'com.leinardi.androidthings' + artifactId 'driver-pcf8574-hd44780' + version '0.1' + + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + connection 'https://github.com/leinardi/androidthings-drivers.git' + url 'https://github.com/leinardi/androidthings-drivers' + + } + developers { + developer { + name "Roberto Leinardi" + email "roberto@leinardi.com" + } + } + } + } +} diff --git a/driver-pcf8574-hd44780/mavenConfig.gradle b/driver-pcf8574-hd44780/mavenConfig.gradle new file mode 100644 index 0000000..88c1327 --- /dev/null +++ b/driver-pcf8574-hd44780/mavenConfig.gradle @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +def mvn_config = [:] +mvn_config.repository = 'androidthings' +mvn_config.group_id = 'com.leinardi.androidthings' +mvn_config.artifact_id = 'driver-pcf8574-hd44780' +mvn_config.version = '0.1' +mvn_config.licenses = 'Apache-2.0' // Comma separated +mvn_config.website = 'https://github.com/leinardi/androidthings-drivers' +mvn_config.issue_tracker_url = 'https://github.com/leinardi/androidthings-drivers/issues' +mvn_config.vcs_url = 'https://github.com/leinardi/androidthings-drivers.git' +mvn_config.description = 'PCF8574-HD44780 LCD driver for Android Things' +mvn_config.tags = 'pcf8574,hd44780,android-things,android,raspberry-pi' // Comma separated +mvn_config.inception_year = '2017' +mvn_config.dryRun = false +mvn_config.publish = false +mvn_config.override = true + +ext.mvn_config = mvn_config + +apply from: rootProject.file('maven.gradle') +apply from: rootProject.file('bintray.gradle') diff --git a/driver-pcf8574-hd44780/proguard-rules.pro b/driver-pcf8574-hd44780/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/driver-pcf8574-hd44780/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/driver-pcf8574-hd44780/src/main/AndroidManifest.xml b/driver-pcf8574-hd44780/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7f2faf1 --- /dev/null +++ b/driver-pcf8574-hd44780/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/driver-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/driver/pcf8574/hd44780/Hd44780.java b/driver-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/driver/pcf8574/hd44780/Hd44780.java new file mode 100644 index 0000000..be23774 --- /dev/null +++ b/driver-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/driver/pcf8574/hd44780/Hd44780.java @@ -0,0 +1,549 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.androidthings.driver.pcf8574.hd44780; + +import android.os.SystemClock; +import android.support.annotation.IntDef; + +import com.google.android.things.pio.I2cDevice; +import com.google.android.things.pio.PeripheralManagerService; + +import java.io.IOException; + +/** + * Driver for controlling the hd44780 LCD via the PCF8574's I2C. + */ +public class Hd44780 implements AutoCloseable { + private static final String TAG = Hd44780.class.getSimpleName(); + private static final int COL_INDEX = 0; + private static final int ROW_INDEX = 1; + private static final int[][] GEOMETRIES = new int[][]{{8, 1}, {16, 2}, {20, 2}, {20, 4}}; + // commands + private static final int HD44780_CLEAR_DISPLAY = 0x01; + private static final int HD44780_RETURN_HOME = 0x02; + private static final int HD44780_ENTRY_MODE_SET = 0x04; + private static final int HD44780_DISPLAY_CTRL = 0x08; + private static final int HD44780_CURSOR_SHIFT = 0x10; + private static final int HD44780_FUNCTION_SET = 0x20; + private static final int HD44780_CGRAM_ADDRESS = 0x40; + private static final int HD44780_DDRAM_ADDRESS = 0x80; + // flags for function set + private static final int HD44780_DL_8BITS = 0x10; + private static final int HD44780_DL_4BITS = 0x00; + private static final int HD44780_N_2LINES = 0x08; + private static final int HD44780_N_1LINE = 0x00; + private static final int HD44780_5X10DOTS = 0x04; + private static final int HD44780_5X8DOTS = 0x00; + // flags for display on/off control + private static final int HD44780_D_DISPLAY_ON = 0x04; + private static final int HD44780_D_DISPLAY_OFF = 0x00; + private static final int HD44780_C_CURSOR_ON = 0x02; + private static final int HD44780_C_CURSOR_OFF = 0x00; + private static final int HD44780_B_BLINK_ON = 0x01; + private static final int HD44780_B_BLINK_OFF = 0x00; + // flags for display entry mode + private static final int HD44780_ENTRY_RIGHT = 0x00; + private static final int HD44780_ENTRY_LEFT = 0x02; + private static final int HD44780_ENTRY_SHIFT_INCREMENT = 0x01; + private static final int HD44780_ENTRY_SHIFT_DECREMENT = 0x00; + // flags for display/cursor shift + private static final int HD44780_DISPLAY_MOVE = 0x08; + private static final int HD44780_CURSOR_MOVE = 0x00; + private static final int HD44780_MOVE_RIGHT = 0x04; + private static final int HD44780_MOVE_LEFT = 0x00; + private static final int LOW = 0x0; + // These are Bit-Masks for the special signals and background light + private static final int PCF_RS = 0x01; + private static final int PCF_RW = 0x02; + private static final int PCF_EN = 0x04; + private static final int PCF_BACKLIGHT = 0x08; + // Definitions on how the PCF8574 is connected to the LCD + // These are Bit-Masks for the special signals and Background + private static final int RSMODE_CMD = 0; + private static final int RSMODE_DATA = 1; + private static final int MAX_CGRAM_LOCATIONS = 8; + private static final int[] ROW_OFFSETS = {0x00, 0x40, 0x14, 0x54}; + private final int[] mLcdGeometry; + private boolean mBacklight; + private byte mDisplayControl; // cursor, display, blink flags + private byte mDisplayMode; // left2right, autoscroll + private I2cDevice mI2cDevice; + private int mCurrentCol; + private int mCurrentRow; + + /** + * Create a new Hd44780 driver connected to the named I2C bus and address + * with the given geometry. + * + * @param i2cName I2C bus name the display is connected to + * @param i2cAddress I2C address of the display + * @param geometry geometry of the LCD. See {@link Geometry}. + */ + public Hd44780(String i2cName, int i2cAddress, @Geometry int geometry) throws IOException { + this(i2cName, i2cAddress, geometry, false); + } + + /** + * Create a new Hd44780 driver connected to the named I2C bus and address + * with the given geometry. + * + * @param i2cName I2C bus name the display is connected to + * @param i2cAddress I2C address of the display + * @param geometry geometry of the LCD. See {@link Geometry}. + * @param use5x10Dots True to use a 10 pixel high font, false for the 8 pixel (default). It only works + * for some 1 line displays. + * @throws IOException + */ + public Hd44780(String i2cName, int i2cAddress, @Geometry int geometry, boolean use5x10Dots) throws IOException { + mLcdGeometry = GEOMETRIES[geometry]; + I2cDevice device = new PeripheralManagerService().openI2cDevice(i2cName, i2cAddress); + try { + init(device, use5x10Dots); + } catch (IOException | RuntimeException e) { + try { + close(); + } catch (IOException | RuntimeException ignored) { + } + throw e; + } + } + + /** + * Recommended start sequence for initializing the communications with the LCD. + * WARNING: If you change this code, power cycle your display before testing. + * + * @throws IOException + */ + private void init(I2cDevice device, boolean use5x10Dots) throws IOException { + mI2cDevice = device; + + byte displayFunction = 0; // lines and dots mode + + if (mLcdGeometry[ROW_INDEX] > 1) { + displayFunction |= HD44780_N_2LINES; + } else { + displayFunction |= HD44780_N_1LINE; + } + + if ((use5x10Dots) && (mLcdGeometry[ROW_INDEX] == 1)) { + displayFunction |= HD44780_5X10DOTS; + } else { + displayFunction |= HD44780_5X8DOTS; + } + + // initializing the display + write2Wire((byte) 0x00, LOW, false); + // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! + // according to datasheet, we need at least 40ms after power rises above 2.7V + // before sending commands. + delayMicroseconds(50000); + + // Put the LCD into 4 bit mode according to the Hitachi HD44780 datasheet figure 26, pg 46 + commandHighNibble(HD44780_FUNCTION_SET | HD44780_DL_8BITS); + delayMicroseconds(5000); + commandHighNibble(HD44780_FUNCTION_SET | HD44780_DL_8BITS); + delayMicroseconds(150); + commandHighNibble(HD44780_FUNCTION_SET | HD44780_DL_8BITS); + commandHighNibble(HD44780_FUNCTION_SET | HD44780_DL_4BITS); + + // finally, set # lines, font size, etc. + command(HD44780_FUNCTION_SET | displayFunction); + + // turn the display on with no cursor or blinking default + mDisplayControl = HD44780_D_DISPLAY_ON | HD44780_C_CURSOR_OFF | HD44780_B_BLINK_OFF; + setDisplayOn(true); + + // clear it off + clearDisplay(); + + // Initialize to default text direction (for romance languages) + mDisplayMode = HD44780_ENTRY_LEFT | HD44780_ENTRY_SHIFT_DECREMENT; + // set the entry mode + command(HD44780_ENTRY_MODE_SET | mDisplayMode); + } + + @Override + public void close() throws IOException { + if (mI2cDevice != null) { + try { + mI2cDevice.close(); + } finally { + mI2cDevice = null; + } + } + } + + /** + * Clears display and returns cursor to the home position (address 0). + * + * @throws IOException + */ + public void clearDisplay() throws IOException { + command(HD44780_CLEAR_DISPLAY); + delayMicroseconds(2000); // this command takes a long time! + + // CLEAR_DISPLAY instruction also returns cursor to home, + // so we need to update it locally. + mCurrentCol = 0; + mCurrentRow = 0; + } + + /** + * Clear the specified line and return the cursor to the beginning of the current row. + * + * @param row the row of the line to clear + * @throws IOException + */ + public void clearLine(int row) throws IOException { + if (row < 0 || row > mLcdGeometry[ROW_INDEX]) { + throw new IndexOutOfBoundsException("Row: " + row); + } + setCursor(0, row); + for (int col = 0; col < mLcdGeometry[COL_INDEX]; col++) { + write(' '); + } + setCursor(0, row); + + } + + private void handleNewLine() throws IOException { + clearLine((mCurrentRow + 1) % mLcdGeometry[ROW_INDEX]); + } + + private void handleCarriageReturn() throws IOException { + setCursor(0, mCurrentRow); + } + + /** + * Returns cursor to home position. + * Also returns display being shifted to the original position.DDRAM content remains unchanged. + * + * @throws IOException + */ + public void cursorHome() throws IOException { + command(HD44780_RETURN_HOME); + delayMicroseconds(2000); // this command takes a long time! + } + + /** + * Set the cursor to a new position. + * + * @param col Desired column of the cursor + * @param row Desired row of the cursor + * @throws IOException + */ + public void setCursor(int col, int row) throws IOException { + if (col < 0 || col > mLcdGeometry[COL_INDEX]) { + throw new IndexOutOfBoundsException("Col: " + col); + } + if (row >= mLcdGeometry[ROW_INDEX]) { + row = (row + 1) % mLcdGeometry[ROW_INDEX]; + } + + command(HD44780_DDRAM_ADDRESS | (col + ROW_OFFSETS[row])); + mCurrentCol = col; + mCurrentRow = row; + } + + /** + * Turns the display on and off. + * + * @param on Set to true to enable the display; set to false to disable the display. + * @throws IOException + * @throws IllegalStateException + */ + public void setDisplayOn(boolean on) throws IOException { + if (on) { + mDisplayControl |= HD44780_D_DISPLAY_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } else { + mDisplayControl &= ~HD44780_D_DISPLAY_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } + } + + /** + * Turns the blinking cursor on and off. + * + * @param on Set to true to enable the blinking cursor; set to false to disable the blinking cursor. + * @throws IOException + * @throws IllegalStateException + */ + public void setBlinkOn(boolean on) throws IOException { + if (on) { + mDisplayControl |= HD44780_B_BLINK_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } else { + mDisplayControl &= ~HD44780_B_BLINK_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } + } + + /** + * Turns the underline cursor on and off. + * + * @param on Set to true to enable the underline cursor; set to false to disable the underline cursor. + * @throws IOException + * @throws IllegalStateException + */ + public void setCursorOn(boolean on) throws IOException { + if (on) { + mDisplayControl |= HD44780_C_CURSOR_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } else { + mDisplayControl &= ~HD44780_C_CURSOR_ON; + command(HD44780_DISPLAY_CTRL | mDisplayControl); + } + } + + // These commands scroll the display without changing the RAM + public void scrollDisplayLeft() throws IOException { + command(HD44780_CURSOR_SHIFT | HD44780_DISPLAY_MOVE | HD44780_MOVE_LEFT); + } + + public void scrollDisplayRight() throws IOException { + command(HD44780_CURSOR_SHIFT | HD44780_DISPLAY_MOVE | HD44780_MOVE_RIGHT); + } + + /** + * Set for text that flows Left to Right + * + * @throws IOException + */ + public void setLeftToRight() throws IOException { + mDisplayMode |= HD44780_ENTRY_LEFT; + command(HD44780_ENTRY_MODE_SET | mDisplayMode); + } + + /** + * Set for text that flows Right to Left + * + * @throws IOException + */ + public void setRightToLeft() throws IOException { + mDisplayMode &= ~HD44780_ENTRY_LEFT; + command(HD44780_ENTRY_MODE_SET | mDisplayMode); + } + + public void setShiftIncrement(boolean increment) throws IOException { + if (increment) { + mDisplayMode |= HD44780_ENTRY_SHIFT_INCREMENT; + command(HD44780_ENTRY_MODE_SET | mDisplayMode); + } else { + mDisplayMode &= ~HD44780_ENTRY_SHIFT_INCREMENT; + command(HD44780_ENTRY_MODE_SET | mDisplayMode); + } + + } + + /** + * Switches the backlight on and off. + * + * @param enable Set to true to enable the backlight; set to false to disable the backlight. + * @throws IOException + */ + public void setBacklight(boolean enable) throws IOException { + // The current brightness is stored in the private backlight variable to have it available for further data transfers. + mBacklight = enable; + // send no data but set the background-pin right; + write2Wire((byte) 0x00, RSMODE_DATA, false); + } + + /** + * Allows us to fill the first 8 CGRAM locations with custom characters + * + * @param charmap an int[8] array containing the custom character + * @param location the location where to store the custom character [0-8) + * @throws IOException + */ + public void createCustomChar(int[] charmap, int location) throws IOException { + if (location < 0 || location > MAX_CGRAM_LOCATIONS) { + throw new IndexOutOfBoundsException("Location must be between 0 and 7. Location: " + location); + } + location &= 0x7; // we only have 8 locations 0-7 + command(HD44780_CGRAM_ADDRESS | (location << 3)); + for (int i = 0; i < 8; i++) { + write(charmap[i]); + } + } + + /** + * Print the custom character stored in the CGRAM location to the current cursor position + *

+ * See also {@link #createCustomChar(int[], int)} + * + * @param location the CGRAM location containing the custom character + * @throws IOException + */ + public void writeCustomChar(int location) throws IOException { + if (location < 0 || location > MAX_CGRAM_LOCATIONS) { + throw new IndexOutOfBoundsException("Location must be between 0 and 7. Location: " + location); + } + writeChar((char) location); + } + + private void writeChar(char c) throws IOException { + write(c); + mCurrentCol++; + if (mCurrentCol >= mLcdGeometry[COL_INDEX]) { + clearLine((mCurrentRow + 1) % mLcdGeometry[ROW_INDEX]); + } + } + + private void write(int value) throws IOException { + send(value, RSMODE_DATA); + } + + /** + * Sets the text to the begin of the specified line + * + * @param text The text to set + * @param line Line number where to insert the text + * @throws IOException + */ + public void setText(String text, int line) throws IOException { + if (line < 0 || line > mLcdGeometry[ROW_INDEX]) { + throw new IndexOutOfBoundsException("Line:" + line); + } + clearLine(line); + setText(text); + } + + /** + * Sets the text to the current cursor position + * + * @param text The text to set + * @throws IOException + */ + public void setText(String text) throws IOException { + for (char c : text.toCharArray()) { + switch (c) { + case '\r': + handleCarriageReturn(); + break; + case '\n': + handleNewLine(); + break; + default: + writeChar(c); + } + } + } + + private void commandHighNibble(int value) throws IOException { + sendNibble((byte) (value >> 4 & 0x0F), RSMODE_CMD); + } + + private void command(int value) throws IOException { + send(value, RSMODE_CMD); + } + + /** + * Writes either command or data + * + * @param value the value to send + * @param mode {@link #RSMODE_CMD} for commands, {@link #RSMODE_DATA} for data + * @throws IOException + */ + private void send(int value, int mode) throws IOException { + // separate the 4 value-nibbles + byte valueLo = (byte) (value & 0x0F); + byte valueHi = (byte) (value >> 4 & 0x0F); + + sendNibble(valueHi, mode); + sendNibble(valueLo, mode); + } + + /** + * Writes a nibble / halfByte with handshake + * + * @throws IOException + */ + private void sendNibble(byte halfByte, int mode) throws IOException { + write2Wire(halfByte, mode, true); + delayMicroseconds(1); // enable pulse must be >450ns + write2Wire(halfByte, mode, false); + delayMicroseconds(37); // commands need > 37us to settle + } + + /** + * Changes the PCF8674 pins to the given value + * + * @throws IOException + * @throws IllegalStateException + */ + private void write2Wire(byte halfByte, int mode, boolean enable) throws IOException, IllegalStateException { + if (mI2cDevice == null) { + throw new IllegalStateException("I2C Device not open"); + } + // map the given values to the hardware of the I2C schema + byte i2cData = (byte) (halfByte << 4); + if (mode > 0) { + i2cData |= PCF_RS; + } + // PCF_RW is never used. + if (enable) { + i2cData |= PCF_EN; + } + if (mBacklight) { + i2cData |= PCF_BACKLIGHT; + } + mI2cDevice.write(new byte[]{i2cData}, 1); + } + + private void delayMicroseconds(long us) { + if (us % 1000 == 0) { + SystemClock.sleep(us / 1000); + } else { + long start = System.nanoTime(); + long ns = us * 1000; + // SUPPRESS CHECKSTYLE EmptyBlock + while (System.nanoTime() - start < ns) { + } + } + } + + @IntDef({Geometry.LCD_8X1, Geometry.LCD_16X2, Geometry.LCD_20X2, Geometry.LCD_20X4}) + public @interface Geometry { + int LCD_8X1 = 0; + int LCD_16X2 = 1; + int LCD_20X2 = 2; + int LCD_20X4 = 3; + } + + /** + * I2C addresses for this peripheral + */ + public static class I2cAddress { + public static final int PCF8574AT = 0x3F; + public static final int PCF8574AT_A0 = 0x3E; + public static final int PCF8574AT_A1 = 0x3D; + public static final int PCF8574AT_A0_A1 = 0x3C; + public static final int PCF8574AT_A2 = 0x3B; + public static final int PCF8574AT_A0_A2 = 0x3A; + public static final int PCF8574AT_A1_A2 = 0x39; + public static final int PCF8574AT_A0_A1_A2 = 0x38; + public static final int PCF8574T = 0x27; + public static final int PCF8574T_A0 = 0x26; + public static final int PCF8574T_A1 = 0x25; + public static final int PCF8574T_A0_A1 = 0x24; + public static final int PCF8574T_A2 = 0x23; + public static final int PCF8574T_A0_A2 = 0x22; + public static final int PCF8574T_A1_A2 = 0x21; + public static final int PCF8574T_A0_A1_A2 = 0x20; + } +} diff --git a/sample-pcf8574-hd44780/.gitignore b/sample-pcf8574-hd44780/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample-pcf8574-hd44780/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample-pcf8574-hd44780/README.md b/sample-pcf8574-hd44780/README.md new file mode 100644 index 0000000..344cbec --- /dev/null +++ b/sample-pcf8574-hd44780/README.md @@ -0,0 +1,48 @@ +## OLED Screen sample for Android Things + +This sample demonstrates how to control the HD44780 LCD using PCF8574's I2C with +Android Things. + +## Pre-requisites + +- Android Things compatible board +- Android Studio 3.0+ +- 1 [HD44780 LCD](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) +- 1 [PCF8574 I2C 8-bit expander](https://www.ti.com/lit/ds/symlink/pcf8574.pdf) +- jumper wires +- 1 breadboard + + +# Build and install + +On Android Studio, click on the "Run" button. + +If you prefer to run on the command line, from this repository's root directory, type + +```bash +./gradlew sample-pcf8574-hd44780:installDebug +adb shell am start com.leinardi.androidthings.driver.pcf8574.hd44780.sample/.LcdActivity +``` + +If you have everything set up correctly, you will some example text on the LCD showing +different features of the driver (print text, custom characters, turning the backlight +and display on and of, etc). + +## License + +Copyright 2017 Roberto Leinardi. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. diff --git a/sample-pcf8574-hd44780/build.gradle b/sample-pcf8574-hd44780/build.gradle new file mode 100644 index 0000000..79a0af3 --- /dev/null +++ b/sample-pcf8574-hd44780/build.gradle @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.application' +apply from: rootProject.file('checkstyle.gradle') + +android { + compileSdkVersion build_versions.target_sdk + buildToolsVersion build_versions.build_tools + + defaultConfig { + applicationId "com.leinardi.androidthings.driver.pcf8574.hd44780.sample" + minSdkVersion build_versions.min_sdk + targetSdkVersion build_versions.target_sdk + versionCode build_versions.version_code + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + compileOptions { + sourceCompatibility build_versions.java_version + targetCompatibility build_versions.java_version + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + compileOnly deps.androidthings + implementation deps.support.annotations + implementation project(':driver-pcf8574-hd44780') +} diff --git a/sample-pcf8574-hd44780/proguard-rules.pro b/sample-pcf8574-hd44780/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/sample-pcf8574-hd44780/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/sample-pcf8574-hd44780/src/main/AndroidManifest.xml b/sample-pcf8574-hd44780/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e738082 --- /dev/null +++ b/sample-pcf8574-hd44780/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/BoardDefaults.java b/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/BoardDefaults.java new file mode 100644 index 0000000..802bf54 --- /dev/null +++ b/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/BoardDefaults.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.androidthings.pcf8574.hd44780.sample; + +import android.os.Build; + +@SuppressWarnings("WeakerAccess") +public class BoardDefaults { + private static final String DEVICE_RPI3 = "rpi3"; + private static final String DEVICE_IMX6UL_PICO = "imx6ul_pico"; + private static final String DEVICE_IMX7D_PICO = "imx7d_pico"; + + private BoardDefaults() { + } + + /** + * Return the preferred I2C port for each board. + */ + public static String getI2CPort() { + switch (Build.DEVICE) { + case DEVICE_RPI3: + return "I2C1"; + case DEVICE_IMX6UL_PICO: + return "I2C2"; + case DEVICE_IMX7D_PICO: + return "I2C1"; + default: + throw new IllegalStateException("Unknown Build.DEVICE " + Build.DEVICE); + } + } +} diff --git a/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/LcdActivity.java b/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/LcdActivity.java new file mode 100644 index 0000000..6d7a116 --- /dev/null +++ b/sample-pcf8574-hd44780/src/main/java/com/leinardi/androidthings/pcf8574/hd44780/sample/LcdActivity.java @@ -0,0 +1,144 @@ +/* + * Copyright 2017 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.androidthings.pcf8574.hd44780.sample; + +import android.app.Activity; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.Log; + +import com.leinardi.androidthings.driver.pcf8574.hd44780.Hd44780; + +import java.io.IOException; + +/** + * Activity that tests the HD44780 LCD. + */ +public class LcdActivity extends Activity { + private static final String TAG = LcdActivity.class.getSimpleName(); + private static final int LCD_COLS = 20; + private static final int LCD_ROWS = 4; + private Hd44780 mLcd; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + mLcd = new Hd44780(BoardDefaults.getI2CPort(), Hd44780.I2cAddress.PCF8574AT, Hd44780.Geometry.LCD_20X4); + } catch (IOException e) { + Log.e(TAG, "Error while opening LCD", e); + throw new RuntimeException(e); + } + Log.d(TAG, "LCD activity created"); + + showText(); + } + + private void showText() { + new Thread(new Runnable() { + @Override + public void run() { + try { + while (true) { + mLcd.setBacklight(true); + mLcd.cursorHome(); + mLcd.clearDisplay(); + mLcd.setText("Hello LCD"); + int[] heart = {0b00000, 0b01010, 0b11111, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000}; + mLcd.createCustomChar(heart, 0); + mLcd.setCursor(10, 0); + mLcd.writeCustomChar(0); // write :heart: custom character previously stored in location 0 + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Backlight Off"); + mLcd.setBacklight(false); + delay(2); + mLcd.clearDisplay(); + mLcd.setText("Backlight On"); + mLcd.setBacklight(true); + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Cursor On"); + mLcd.setCursorOn(true); + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Cursor Blink"); + mLcd.setBlinkOn(true); + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Cursor OFF"); + mLcd.setBlinkOn(false); + mLcd.setCursorOn(false); + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Display Off"); + mLcd.setDisplayOn(false); + delay(2); + + mLcd.clearDisplay(); + mLcd.setText("Display On"); + mLcd.setDisplayOn(true); + delay(2); + + mLcd.clearDisplay(); + for (int i = 0; i < LCD_ROWS; i++) { + mLcd.setCursor(0, i); + mLcd.setText("-+* line " + i + " *+-"); + } + delay(2); + + mLcd.scrollDisplayLeft(); + delay(2); + + mLcd.scrollDisplayLeft(); + delay(2); + + mLcd.scrollDisplayLeft(); + delay(2); + + mLcd.scrollDisplayRight(); + delay(2); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } + + private void delay(long s) { + SystemClock.sleep(s * 1000); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Close the device. + try { + mLcd.close(); + } catch (IOException e) { + Log.e(TAG, "Error closing Hd44780", e); + } finally { + mLcd = null; + } + } +} diff --git a/sample-pcf8574-hd44780/src/main/res/values/strings.xml b/sample-pcf8574-hd44780/src/main/res/values/strings.xml new file mode 100644 index 0000000..0323754 --- /dev/null +++ b/sample-pcf8574-hd44780/src/main/res/values/strings.xml @@ -0,0 +1,19 @@ + + + + Sample SH1106 + diff --git a/sample-pcf8574-hd44780/src/main/res/values/styles.xml b/sample-pcf8574-hd44780/src/main/res/values/styles.xml new file mode 100644 index 0000000..48b9daf --- /dev/null +++ b/sample-pcf8574-hd44780/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + +