Wednesday, April 15, 2015

Bi-directional communication between Android and Arduino in USB Host Mode, example 3 - work with 8x8 LED Matrix

It's the 3rd example to implement Bi-directional communication between Android and Arduino in USB Host Mode, modified from another example "Android + Arduino Uno: display bits and ASCII char on LED Matrix".


On the Android to Arduino Uno direction, user tap on the custom view (8x8 squares) to turn ON/OFF LEDs on Arduino, or select character from the spiner.
On the Arduino Uno to Android direction, tap on the "Load from Arduino" button on Android, Android send a command to Arduino, then Arduino send back 64 bytes back to Android, represent (inverted) ON/OFF state of each dots.
The right hand side TextView show the data received on Android.


Roughly, to implement receiving on Android
- MainActivity implement Runnable
- override run() method to monitor UsbRequest
- start background thread after openDevice() to run the run() method

Connection:
Android - USB OTG Cable - USB Cable - Arduino Uno - 8x8 LED Matrix


Android side:

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidtouchview.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/textstatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Status"
        android:textStyle="bold" />

    <Spinner
        android:id="@+id/asciispinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/load"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Load from Arduino" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical" >

            <GridLayout
                android:id="@+id/mygrid"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:background="@android:color/background_light"
                android:columnCount="8"
                android:orientation="horizontal"
                android:rowCount="8" >
            </GridLayout>
        </LinearLayout>

        <TextView
            android:id="@+id/logtext"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textColor="#FFFFFF"
            android:background="#A0A0A0" />
    </LinearLayout>

</LinearLayout>

MainActivity.java
package com.example.androidusbmatrix;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;

import com.example.androidusbmatrix.MyView.OnToggledListener;

import android.support.v7.app.ActionBarActivity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.Spinner;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity 
 implements OnToggledListener, Runnable{

 Button btnLoad;
 TextView textStatus, logText;
 volatile MyView[] myViews;
 GridLayout myGridLayout;
 
 private static final int targetVendorID = 9025; //Arduino Uno
 private static final int targetProductID = 67; //Arduino Uno, not 0067
 private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
 PendingIntent mPermissionIntent;
 UsbDevice deviceFound = null;
 UsbInterface usbInterfaceFound = null;
 UsbInterface usbInterface;
 UsbDeviceConnection usbDeviceConnection;
 UsbEndpoint endpointIn = null;
 UsbEndpoint endpointOut = null;
 
 Spinner spAscii;
 AsciiCode Asciis[];
 ArrayAdapter<AsciiCode> asciiArrayAdapter;
 
 final byte SYNC_WORD   = (byte) 0xff;
 final byte CMD_01_Bits    = 0x01;
 final byte CMD_02_SingleChar  = 0x02;
 final byte CMD_03_Load   = 0x03;
 
 String logString = "";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  textStatus = (TextView) findViewById(R.id.textstatus);
  logText = (TextView) findViewById(R.id.logtext);
  myGridLayout = (GridLayout) findViewById(R.id.mygrid);

  int numOfCol = myGridLayout.getColumnCount();
  int numOfRow = myGridLayout.getRowCount();
  myViews = new MyView[numOfCol * numOfRow];
  for (int yPos = 0; yPos < numOfRow; yPos++) {
   for (int xPos = 0; xPos < numOfCol; xPos++) {
    MyView tView = new MyView(this, xPos, yPos);
    tView.setOnToggledListener(this);
    myViews[yPos * numOfCol + xPos] = tView;
    myGridLayout.addView(tView);
   }
  }

  myGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(
    new OnGlobalLayoutListener() {

     @Override
     public void onGlobalLayout() {

      int pLength;
      final int MARGIN = 5;

      int pWidth = myGridLayout.getWidth();
      int pHeight = myGridLayout.getHeight();
      int numOfCol = myGridLayout.getColumnCount();
      int numOfRow = myGridLayout.getRowCount();

      // Set myGridLayout equal width and height
      if (pWidth >= pHeight) {
       pLength = pHeight;
      } else {
       pLength = pWidth;
      }
      ViewGroup.LayoutParams pParams = myGridLayout
        .getLayoutParams();
      pParams.width = pLength;
      pParams.height = pLength;
      myGridLayout.setLayoutParams(pParams);

      int w = pLength / numOfCol; // pWidth/numOfCol;
      int h = pLength / numOfRow; // pHeight/numOfRow;

      for (int yPos = 0; yPos < numOfRow; yPos++) {
       for (int xPos = 0; xPos < numOfCol; xPos++) {
        GridLayout.LayoutParams params = (GridLayout.LayoutParams) myViews[yPos
          * numOfCol + xPos].getLayoutParams();
        params.width = w - 2 * MARGIN;
        params.height = h - 2 * MARGIN;
        params.setMargins(MARGIN, MARGIN, MARGIN,
          MARGIN);
        myViews[yPos * numOfCol + xPos]
          .setLayoutParams(params);
       }
      }

      // deprecated in API level 16
      myGridLayout.getViewTreeObserver()
        .removeGlobalOnLayoutListener(this);
      // for API Level >= 16
      // myGridLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
     }
    });
  
  // register the broadcast receiver
  mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(
   ACTION_USB_PERMISSION), 0);
  IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
  registerReceiver(mUsbReceiver, filter);

  registerReceiver(mUsbDeviceReceiver, new IntentFilter(
   UsbManager.ACTION_USB_DEVICE_ATTACHED));
  registerReceiver(mUsbDeviceReceiver, new IntentFilter(
   UsbManager.ACTION_USB_DEVICE_DETACHED));

  connectUsb();
  
  spAscii = (Spinner)findViewById(R.id.asciispinner);
  initAsciis();
  asciiArrayAdapter = new ArrayAdapter<AsciiCode>(this,
    android.R.layout.simple_list_item_1, 
    android.R.id.text1, 
    Asciis);
  asciiArrayAdapter.setDropDownViewResource(
    android.R.layout.simple_spinner_dropdown_item);
  spAscii.setAdapter(asciiArrayAdapter);
  spAscii.setOnItemSelectedListener(new OnItemSelectedListener(){

   @Override
   public void onItemSelected(AdapterView<?> parent, View view,
     int position, long id) {
    AsciiCode item = (AsciiCode) parent.getItemAtPosition(position);
    
    if(deviceFound != null){
     byte[] bytesSend = new byte[3];
     
     bytesSend[0] = SYNC_WORD;
     bytesSend[1] = CMD_02_SingleChar; //CMD
     bytesSend[2] = (byte) item.charAscii;
     
     usbDeviceConnection.bulkTransfer(endpointOut,
       bytesSend, bytesSend.length, 0);
    }
   }

   @Override
   public void onNothingSelected(AdapterView<?> arg0) {
    // TODO Auto-generated method stub
    
   }});
  
  btnLoad = (Button)findViewById(R.id.load);
  btnLoad.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    logString = "";
    logText.setText(logString);
    LoadFromArduino();
   }});
 }
 
 private void LoadFromArduino(){
  if(deviceFound != null){
   indexRx = 0;
   
   byte[] bytesSend = new byte[2];
   
   bytesSend[0] = SYNC_WORD;
   bytesSend[1] = CMD_03_Load;
   usbDeviceConnection.bulkTransfer(endpointOut,
     bytesSend, bytesSend.length, 0);

  }
 }

 @Override
 public void OnToggled(MyView v, boolean touchOn) {
  if(deviceFound != null){
   byte[] bytesSend = setupCmd();

   usbDeviceConnection.bulkTransfer(endpointOut,
     bytesSend, bytesSend.length, 0);
  }
 }
 
 private byte[] setupCmd(){
  byte[] bytes = new byte[18];
  
  bytes[0] = SYNC_WORD;
  bytes[1] = CMD_01_Bits; //CMD
  
  int i = 2;
  int numOfRow = 8;
  int numOfCol = 8;
  int numOfHalfCol = 4;

  for (int yPos = 0; yPos < numOfRow; yPos++) {
   byte b = 0x00;
   byte mask = 0x01;

   for (int xPos = 0; xPos < numOfHalfCol; xPos++) {
    
    if(myViews[yPos * numOfCol + xPos].touchOn){
     b = (byte) (b | mask);
    }
    
    mask = (byte) (mask << 1);
   }
   bytes[i] = b;
   
   i++;
   b = 0x00;
   mask = 0x10;
   for (int xPos = numOfHalfCol; xPos < numOfCol; xPos++) {
    
    if(myViews[yPos * numOfCol + xPos].touchOn){
     b = (byte) (b | mask);
    }
    
    mask = (byte) (mask << 1);
   }
   bytes[i] = b;
   
   i++;
  }

  return bytes;
 }
 
 //usb related
 private boolean setupUsbComm() {

  // for more info, search SET_LINE_CODING and
  // SET_CONTROL_LINE_STATE in the document:
  // "Universal Serial Bus Class Definitions for Communication Devices"
  // at http://adf.ly/dppFt
  final int RQSID_SET_LINE_CODING = 0x20;
  final int RQSID_SET_CONTROL_LINE_STATE = 0x22;

  boolean success = false;

  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  Boolean permitToRead = manager.hasPermission(deviceFound);

  if (permitToRead) {
   usbDeviceConnection = manager.openDevice(deviceFound);
   if (usbDeviceConnection != null) {
    usbDeviceConnection.claimInterface(usbInterfaceFound, true);

    usbDeviceConnection.controlTransfer(0x21, // requestType
      RQSID_SET_CONTROL_LINE_STATE, // SET_CONTROL_LINE_STATE
      0, // value
      0, // index
      null, // buffer
      0, // length
      0); // timeout

    // baud rate = 9600
    // 8 data bit
    // 1 stop bit
    byte[] encodingSetting = new byte[] { (byte) 0x80, 0x25, 0x00,
      0x00, 0x00, 0x00, 0x08 };
    usbDeviceConnection.controlTransfer(0x21, // requestType
      RQSID_SET_LINE_CODING, // SET_LINE_CODING
      0, // value
      0, // index
      encodingSetting, // buffer
      7, // length
      0); // timeout

    //2015-04-15
                Thread thread = new Thread(this);
                thread.start();
   }

  } else {
   manager.requestPermission(deviceFound, mPermissionIntent);
   textStatus.setText("Permission: " + permitToRead);
  }

  return success;
 }
 
 private void connectUsb() {

  textStatus.setText("connectUsb()");

  searchEndPoint();

  if (usbInterfaceFound != null) {
   setupUsbComm();
  }

 }
 
 private void releaseUsb() {

  textStatus.setText("releaseUsb()");

  if (usbDeviceConnection != null) {
   if (usbInterface != null) {
    usbDeviceConnection.releaseInterface(usbInterface);
    usbInterface = null;
   }
   usbDeviceConnection.close();
   usbDeviceConnection = null;
  }

  deviceFound = null;
  usbInterfaceFound = null;
  endpointIn = null;
  endpointOut = null;
 }

 private void searchEndPoint() {

  usbInterfaceFound = null;
  endpointOut = null;
  endpointIn = null;

  // Search device for targetVendorID and targetProductID
  if (deviceFound == null) {
   UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
   HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
   Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

   while (deviceIterator.hasNext()) {
    UsbDevice device = deviceIterator.next();

    if (device.getVendorId() == targetVendorID) {
     if (device.getProductId() == targetProductID) {
      deviceFound = device;
     }
    }
   }
  }

  if (deviceFound == null) {
   textStatus.setText("device not found");
  } else {

   // Search for UsbInterface with Endpoint of USB_ENDPOINT_XFER_BULK,
   // and direction USB_DIR_OUT and USB_DIR_IN

   for (int i = 0; i < deviceFound.getInterfaceCount(); i++) {
    UsbInterface usbif = deviceFound.getInterface(i);

    UsbEndpoint tOut = null;
    UsbEndpoint tIn = null;

    int tEndpointCnt = usbif.getEndpointCount();
    if (tEndpointCnt >= 2) {
     for (int j = 0; j < tEndpointCnt; j++) {
      if (usbif.getEndpoint(j).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
       if (usbif.getEndpoint(j).getDirection() == UsbConstants.USB_DIR_OUT) {
        tOut = usbif.getEndpoint(j);
       } else if (usbif.getEndpoint(j).getDirection() == UsbConstants.USB_DIR_IN) {
        tIn = usbif.getEndpoint(j);
       }
      }
     }

     if (tOut != null && tIn != null) {
      // This interface have both USB_DIR_OUT
      // and USB_DIR_IN of USB_ENDPOINT_XFER_BULK
      usbInterfaceFound = usbif;
      endpointOut = tOut;
      endpointIn = tIn;
     }
    }

   }

  }
 }
 
 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {

  @Override
  public void onReceive(Context context, Intent intent) {
   String action = intent.getAction();
   if (ACTION_USB_PERMISSION.equals(action)) {

    textStatus.setText("ACTION_USB_PERMISSION");

    synchronized (this) {
     UsbDevice device = (UsbDevice) intent
       .getParcelableExtra(UsbManager.EXTRA_DEVICE);

     if (intent.getBooleanExtra(
       UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
      if (device != null) {
       connectUsb();
      }
     } else {
      textStatus.setText("permission denied for device ");
     }
    }
   }
  }
 };

 private final BroadcastReceiver mUsbDeviceReceiver = new BroadcastReceiver() {

  @Override
  public void onReceive(Context context, Intent intent) {
   String action = intent.getAction();
   if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {

    deviceFound = (UsbDevice) intent
      .getParcelableExtra(UsbManager.EXTRA_DEVICE);

    textStatus.setText("ACTION_USB_DEVICE_ATTACHED");

    connectUsb();

   } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {

    UsbDevice device = (UsbDevice) intent
      .getParcelableExtra(UsbManager.EXTRA_DEVICE);

    textStatus.setText("ACTION_USB_DEVICE_DETACHED");

    if (device != null) {
     if (device == deviceFound) {
      releaseUsb();
     }
    }
    
   }
  }

 };
 
 public void initAsciis() {
  Asciis = new AsciiCode[128];
  for (int i = 0; i < 128; i++) {
   Asciis[i] = new AsciiCode(i);
  }
 }
 
 class AsciiCode{
  int id;
  char charAscii;
  
  AsciiCode(int id){
   this.id = id;
   charAscii = (char)id;
  }
  
  public String toString(){
   return 
    String.format("%03d", id)
    + " /(hex) 0x" + String.format("%02x", id).toUpperCase()
    + " : " + charAscii;
  }

 }

 int indexRx;
 final int RX_STATE_0_IDLE = 0;
 final int RX_STATE_1_RXing = 1;
 int RxState;
 final byte PIXELON = 'A';
 final byte PIXELOFF = 'B';
 //Handle receiving from Arduino
 @Override
 public void run() {
  RxState = RX_STATE_0_IDLE;
  ByteBuffer buffer = ByteBuffer.allocate(1);
        UsbRequest request = new UsbRequest();
        request.initialize(usbDeviceConnection, endpointIn);
        while (true) {
         request.queue(buffer, 1);
         if (usbDeviceConnection.requestWait() == request) {
          final byte dataRx = buffer.get(0);
          
          
          logString += String.format("%02d", dataRx) + " ";
          runOnUiThread(new Runnable(){

           @Override
           public void run() {
            
            logText.setText(logString);
           }});

          if(indexRx < myViews.length){
           //only accept two value
           //ignore unaccept value
           //I found always have '0' received, 
           //I don't know why!
              if(dataRx == PIXELON){
               myViews[indexRx].touchOn = true;
               rqsRedrawMyView(indexRx);
               indexRx++;
              }else if(dataRx == PIXELOFF){
               myViews[indexRx].touchOn = false;
               rqsRedrawMyView(indexRx);
               indexRx++;
              }
              
          }
         }else{
          break;
         }
         
        }
 }
 
 private void rqsRedrawMyView(final int i){
  runOnUiThread(new Runnable(){

   @Override
   public void run() {
    myViews[i].invalidate();
   }});
 }

}

MyView.java, custom view for each square
package com.example.androidusbmatrix;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
 
 public interface OnToggledListener {
  void OnToggled(MyView v, boolean touchOn);
 }

 boolean touchOn;
 boolean mDownTouch = false;
 private OnToggledListener toggledListener;
 int idX = 0; //default
 int idY = 0; //default

 public MyView(Context context, int x, int y) {
  super(context);
  idX = x;
  idY = y;
  init();
 }
 
 public MyView(Context context) {
  super(context);
  init();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  touchOn = false;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
    MeasureSpec.getSize(heightMeasureSpec));
 }

 @Override
 protected void onDraw(Canvas canvas) {
  if (touchOn) {
   canvas.drawColor(Color.RED);
  } else {
   canvas.drawColor(Color.GRAY);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
             
             touchOn = !touchOn;
       invalidate();
       
       if(toggledListener != null){
        toggledListener.OnToggled(this, touchOn);
       }
             
                mDownTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (mDownTouch) {
                    mDownTouch = false;
                    performClick();
                    return true;
                }
        }
        return false;
 }

 @Override
 public boolean performClick() {
        super.performClick();
        return true;
 }
 
 public void setOnToggledListener(OnToggledListener listener){
  toggledListener = listener;
 }
 
 public int getIdX(){
  return idX;
 }
 
 public int getIdY(){
  return idY;
 }

}

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidusbmatrix"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
        </activity>
    </application>

</manifest>

/res/xml/device_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- idVendor=2341, idProduct=0043 for Arduino Uno R3 -->
    <usb-device 
        vendor-id="9025" 
        product-id="0067" />
</resources>


download filesDownload the files.

Arduino side:

UnoUsbMatrix.ino
/*
 *  http://android-er.blogspot.com
 *  http://arduino-er.blogspot.com/
 */

byte font[128][8] = {
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0000 (nul)
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0001
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0002
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0003
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0004
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0005
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0006
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0007
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0008
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0009
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000A
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000B
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000C
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000D
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000E
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+000F
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0010
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0011
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0012
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0013
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0014
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0015
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0016
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0017
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0018
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0019
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001A
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001B
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001C
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001D
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001E
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+001F
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0020 (space)
    { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00},   // U+0021 (!)
    { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0022 (")
    { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00},   // U+0023 (#)
    { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00},   // U+0024 ($)
    { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00},   // U+0025 (%)
    { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00},   // U+0026 (&)
    { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0027 (')
    { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00},   // U+0028 (()
    { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00},   // U+0029 ())
    { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00},   // U+002A (*)
    { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00},   // U+002B (+)
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+002C (,)
    { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00},   // U+002D (-)
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+002E (.)
    { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00},   // U+002F (/)
    { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00},   // U+0030 (0)
    { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00},   // U+0031 (1)
    { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00},   // U+0032 (2)
    { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00},   // U+0033 (3)
    { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00},   // U+0034 (4)
    { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00},   // U+0035 (5)
    { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00},   // U+0036 (6)
    { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00},   // U+0037 (7)
    { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00},   // U+0038 (8)
    { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00},   // U+0039 (9)
    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00},   // U+003A (:)
    { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06},   // U+003B (//)
    { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00},   // U+003C (<)
    { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00},   // U+003D (=)
    { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00},   // U+003E (>)
    { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00},   // U+003F (?)
    { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00},   // U+0040 (@)
    { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00},   // U+0041 (A)
    { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00},   // U+0042 (B)
    { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00},   // U+0043 (C)
    { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00},   // U+0044 (D)
    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00},   // U+0045 (E)
    { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00},   // U+0046 (F)
    { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00},   // U+0047 (G)
    { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00},   // U+0048 (H)
    { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0049 (I)
    { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00},   // U+004A (J)
    { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00},   // U+004B (K)
    { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00},   // U+004C (L)
    { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00},   // U+004D (M)
    { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00},   // U+004E (N)
    { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00},   // U+004F (O)
    { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00},   // U+0050 (P)
    { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00},   // U+0051 (Q)
    { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00},   // U+0052 (R)
    { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00},   // U+0053 (S)
    { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0054 (T)
    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00},   // U+0055 (U)
    { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0056 (V)
    { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00},   // U+0057 (W)
    { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00},   // U+0058 (X)
    { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00},   // U+0059 (Y)
    { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00},   // U+005A (Z)
    { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00},   // U+005B ([)
    { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00},   // U+005C (\)
    { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00},   // U+005D (])
    { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00},   // U+005E (^)
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF},   // U+005F (_)
    { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+0060 (`)
    { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00},   // U+0061 (a)
    { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00},   // U+0062 (b)
    { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00},   // U+0063 (c)
    { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00},   // U+0064 (d)
    { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00},   // U+0065 (e)
    { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00},   // U+0066 (f)
    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0067 (g)
    { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00},   // U+0068 (h)
    { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+0069 (i)
    { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E},   // U+006A (j)
    { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00},   // U+006B (k)
    { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00},   // U+006C (l)
    { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00},   // U+006D (m)
    { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00},   // U+006E (n)
    { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00},   // U+006F (o)
    { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F},   // U+0070 (p)
    { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78},   // U+0071 (q)
    { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00},   // U+0072 (r)
    { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00},   // U+0073 (s)
    { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00},   // U+0074 (t)
    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00},   // U+0075 (u)
    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00},   // U+0076 (v)
    { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00},   // U+0077 (w)
    { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00},   // U+0078 (x)
    { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F},   // U+0079 (y)
    { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00},   // U+007A (z)
    { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00},   // U+007B ({)
    { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00},   // U+007C (|)
    { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00},   // U+007D (})
    { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U+007E (~)
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}    // U+007F
};

// 2-dimensional array of row pin numbers:
const int row[8] = {
  2, 7, 19, 5, 13, 18, 12, 16
};

// 2-dimensional array of column pin numbers:
const int col[8] = {
  6, 11, 10, 3, 17, 4, 8, 9
};

// 2-dimensional array of pixels:
int pixels[8][8];

const int ST_0 = 0;       //waiting Sync word
const int ST_1_CMD = 1;   //Waiting CMD
const int ST_2_DATA= 2;  //Receiving Data for CMD_01_Bits
const int ST_3_CHAR= 3;  //Receiving Single Char for CMD_02_SingleChar
const int DATA_LENGTH = 16;  //length of data = 16
const byte SYNC_WORD = 0xFF;
const byte CMD_01_Bits = 0x01;        //Send as 8x8 bit map
const byte CMD_02_SingleChar = 0x02;  //single char
const byte CMD_03_Load = 0x03;        //Load from Arduino to Android
int cmdState;
int dataIndex;

byte data[DATA_LENGTH];

//represent pixel ON/OFF send back to Android
const byte PIXELON = 'A';
const byte PIXELOFF = 'B';

void setup() {
  
  Serial.begin(9600);

  // initialize the I/O pins as outputs
  // iterate over the pins:
  for (int thisPin = 0; thisPin < 8; thisPin++) {
    // initialize the output pins:
    pinMode(col[thisPin], OUTPUT);
    pinMode(row[thisPin], OUTPUT);
    // take the col pins (i.e. the cathodes) high to ensure that
    // the LEDS are off:
    digitalWrite(col[thisPin], HIGH);
  }
  
  for(int y=0; y<8; y++){
    for(int x=0; x<8; x++){
      pixels[x][y] = HIGH;
    }
  }
  
  cmdState = ST_0;
}

void loop() {
  refreshScreen();
  
  if(Serial.available()){
    cmdHandle(Serial.read());
  }

}

void cmdHandle(int incomingByte){
  
  //prevent from lost-sync
  if(incomingByte == SYNC_WORD){
    cmdState = ST_1_CMD;
    return;
  }
  
  switch(cmdState){
    case ST_0:
        if(incomingByte == SYNC_WORD){
          cmdState = ST_1_CMD;
        }
        break;
    case ST_1_CMD:{
          switch(incomingByte){
            case CMD_01_Bits:
                cmdState = ST_2_DATA;
                dataIndex = 0;
                break;
            case CMD_02_SingleChar:
                cmdState = ST_3_CHAR;
                break;
            case CMD_03_Load:
                //The program will block here
                //also no LED scan
                SendToAndroid();  
                cmdState = ST_0;
                break;
            default:
                cmdState = ST_0;
          }
        }
        break;
    case ST_2_DATA:{
          data[dataIndex] = incomingByte;
          dataIndex++;
        
          int iData = 0;
          if(dataIndex==DATA_LENGTH){
            cmdState = ST_0;
            for(int i=0; i<8; i++){
              byte mask = 0x01;
            
              for(int j=0; j<4; j++){
                  if (data[iData] & mask){
                    pixels[i][j] = LOW;
                  }else{
                    pixels[i][j] = HIGH;
                  }
                  mask = mask << 1;
              }
            
              iData++;
              for(int j=4; j<8; j++){
                  if (data[iData] & mask){
                    pixels[i][j] = LOW;
                  }else{
                    pixels[i][j] = HIGH;
                  }
                  mask = mask << 1;
              }
            
              iData++;
            }
          }
        }
        break;
      
      case ST_3_CHAR:
        setupChar((char)incomingByte);
        cmdState = ST_0;
        break;
        
  }
  
}

void SendToAndroid(){
  for(int i=0; i<8; i++){
    for(int j=0; j<8; j++){
      //send back inverted ON/OFF state
      if(pixels[i][j] == LOW){
        Serial.write(PIXELOFF);
      }else{
        Serial.write(PIXELON);
      }
      delay(10);
    }
  }
}

void setupChar(char c){

  for (int x = 0; x < 8; x++) {
    byte bitMask = 0x01;
    byte f = font[c][x];
    for (int y = 0; y < 8; y++) {
      if (f & bitMask){
        pixels[x][y] = LOW;
      }else{
        pixels[x][y] = HIGH;
      }
      bitMask = bitMask << 1;
    }
  }

}

void refreshScreen() {
  // iterate over the rows (anodes):
  for (int thisRow = 0; thisRow < 8; thisRow++) {
    // take the row pin (anode) high:
    digitalWrite(row[thisRow], HIGH);
    // iterate over the cols (cathodes):
    for (int thisCol = 0; thisCol < 8; thisCol++) {
      // get the state of the current pixel;
      int thisPixel = pixels[thisRow][thisCol];
      // when the row is HIGH and the col is LOW,
      // the LED where they meet turns on:
      digitalWrite(col[thisCol], thisPixel);
      // turn the pixel off:
      if (thisPixel == LOW) {
        digitalWrite(col[thisCol], HIGH);
      }
    }
    // take the row pin low to turn off the whole row:
    digitalWrite(row[thisRow], LOW);
  }
}

Download Arduino ino code HERE.

Connection between Arduino Uno and 8x8 LED Matrix:


Check on the right hand side TextView, the received data log: There are some unexpected '00' received. I don't know the reason! Please anybody can advise.


Related:
- More example of communication between Android to Arduino Uno, in USB Host Mode

No comments: