Go Down

Topic: Yun versions of digitalRead(), digitalWrite(), analogRead() & analogWrite() (Read 343 times) previous topic - next topic

robertics

Hi, I'm new to the Arduino Yun and Python and I'm trying develop a usable way of programming the two Yun processors.

My mental picture of the Bridge is a Python dictionary with read/write access from both processors. That makes passing static text between the two a fairly simple matter, but needs a bit of work if the two processors have to function in lock step.

The lack of documentation for the Python side of the Bridge means that its designers only really expect the vast majority of people to use the MPU as a means of supplying internet services to an Arduino Leonardo. That is a great shame because the Leonardo side of things, while great for interfacing to digital i/o, sensors and things can run out of resources very quickly. Meanwhile, the MPU has masses of resource spare after managing the internet services, but using it is difficult because the Python side of the Bridge lacks documentation.

So it seems to me that I would get more out of my Yun if I pushed the bulk of the processing out of the Leonardo and into the MPU, leaving the Leonardo to manage digital and analogue i/o and manage I2C sensors, etc., plus any real world stuff that might require immediate attention.

With that in mind, I have defined Python versions of my old Arduino programming favourites:

   digitalRead()
   digitalWrite()
   analogRead()
   analogWrite()

Together with some service functions for the Leonardo side.

I can't help thinking that this is already defined somewhere, but I can't find it. If it is, please tell me where.

It could do with a few more functions, for pin mode setting, etc. It also needs to be bundled into libraries, but I'll worry about that later.

The full code for both processor is below. It seems to work ok. I thought I'd get an opinion or two before developing it any further. Ideas would be welcome.


Arduino Sketch

/*
   This sketch demonstrates how Yun commands received over
   the Bridge may be seriviced to implement digitalRead(),
   digitalWrite(), analogRead() and analogWrite()

   Robert Smart. 28/04/2019.
*/

#include <Bridge.h>

void setup() {

  // Initialize digital pins 12 and 13 as output.
  pinMode(13, OUTPUT);
 
  // Start using the Bridge.
  Bridge.begin();

  // Initialize the Bridge and the Serial
  Serial.begin(115200);
 
  while (!SerialUSB); // wait for Serial port to connect.
  SerialUSB.println("buongiorno!\n");
 
  pinMode(LED_BUILTIN, OUTPUT);

}

void loop() {
 
  serviceDigitalWrite();
  serviceDigitalRead();
  serviceAnalogRead();
  serviceAnalogWrite();
 
  delay(100);
}

// Service a Digital Write request from Linux over the Bridge.
// Request is packed into the key value, cmdValue = P * 2 + V
//   P  - pin to write to
//   V  - value to write in the range 0 to 1
// If the key value is negative, the write is aborted
void serviceDigitalWrite(){
  char cmdStr[8];
 
  Bridge.get("DigWr", cmdStr, 8);
  int cmdValue = atoi(cmdStr);
  if (cmdValue >= 0) {
    digitalWrite(cmdValue>>1, cmdValue & 0x1);
    Bridge.put("DigWr", "-1"); // Mark service as complete
  }
}

// Service a Digital Read request from Linux over the Bridge.
// Request is packed into the key value, cmdValue = P
//   P - pin to write to
// If the key value is negative, the write is aborted
void serviceDigitalRead(){
  char cmdStr[8];
  int digPin;
 
  Bridge.get("DigRd", cmdStr, 8);
  digPin   = atoi(cmdStr);
  if (digPin >= 0) {
    if (digitalRead(digPin)==0)
      Bridge.put("DigRdVal","0");
    else
      Bridge.put("DigRdVal","1");
    Bridge.put("DigRd","-1");
  }
}

// Service an Analogue Read request from Linux over the Bridge.
// Request is packed into the key value, cmdValue = A
//   A - 2 digit decimal representation of the analog channel to read, in the range 0 to 11 [for A0 to A11]
//   Analogue ADC channels are mapped to pins thus: A0 = pin 18, A1 = pin 19, etc.
// If the key value is negative, the read is aborted
void serviceAnalogRead(){
  char cmdStr[3];
  int analoguePin;
 
  Bridge.get("AnaRd", cmdStr, 3);
  analoguePin = atoi(cmdStr);
  if (analoguePin >= 0) {
    Bridge.put("AnaRdVal", String(analogRead(analoguePin)));
    Bridge.put("AnaRd","-1");
  }
}

// Service an Analogue Write request from Linux over the Bridge.
// Request is packed into the key value, cmdValue = A * 256 + V
//   A is the analogue output in the range 0 to 13 [Only 3, 5, 6, 9, 10, 11, and 13 support PWM]
//   V is the value to write is the range 0 to 256
// If the key value is negative, the write is aborted
void serviceAnalogWrite(){
  char cmdStr[8];
  int cmdValue;

  Bridge.get("AnaWr", cmdStr, 8);
  cmdValue = atoi(cmdStr);
  if (cmdValue >= 0){
    analogWrite(cmdValue >> 8, cmdValue & 0xFF);
    Bridge.put("AnaWr", "-1"); // Mark service as complete
  }
 
}



Python Script

#!/usr/bin/python

import sys
sys.path.insert(0, '/usr/lib/python2.7/bridge')

from time import sleep

from bridgeclient import BridgeClient as bridgeclient
bridgec = bridgeclient()

# Define the Arduino digital pins to allow them to be addressed symbolically
D0 = 0
D1 = 1
D2 = 2
D3 = 3
D4 = 4
D5 = 5
D6 = 6
D7 = 7
D8 = 8
D9 = 9
D10 = 10
D11 = 11
D12 = 12
D13 = 13

A0 = 18
A1 = 19
A2 = 20
A3 = 21
A4 = 22
A5 = 23
A6 = 24
A7 = 25
A8 = 26
A9 = 27
A10 = 28
A11 = 29

HIGH = 1
LOW = 0

# Function to write a value to a digital pin of the 32U16.
# The write can be either blocking [default] or non-blocking.
# Select the pin using predefined values D0, D1,...D13.
# and the value using predifed values HIGH or LOW
def digitalWrite(pin, pinValue, blocking = True):
    """Write a value to a digital pin. Mimics the Arduino function of the same name."""
    packedValue = pin * 2 + pinValue     # Pack the parameters into a single integer
    bridgec.put('DigWr',str(packedValue))  # Write the command to the Bridge
    if (blocking):
        while(bridgec.get('DigWr')!="-1"): # Wait for command to be completed
            {}

# Function to read a digital pin of the 32U16.
# The read is necessarilly blocking.
# Select the pin using predefined values D0, D1,...D13.
# The returned value is either HIGH or LOW.
def digitalRead(pin):
    """Read the state of a digital pin. Mimics the Arduino function of the same name."""
    bridgec.put('DigRd',str(pin))          # Write the command to the Bridge
    while(bridgec.get('DigRd')!="-1"):     # Wait for command to be completed
        {}
    return int(bridgec.get('DigRdVal'))

# Function to read an ADC value for a specific pin of the 32U16.
# The read is necessarilly blocking.
# Select the pin using predefined values A0, A1,...A11.
# The returned value is an integer in the range 0 to 1023.
def analogRead(pin):
    """Read the voltage of a analog pin. Mimics the Arduino function of the same name."""
    bridgec.put('AnaRd',str(pin))          # Write the command to the Bridge
    while(bridgec.get('AnaRd')!="-1"):     # Wait for command to be completed
        {}
    return int(bridgec.get('AnaRdVal'))

# Function to write an analog value to a digital pin of the 32U16.
# The analog value will be implemented as a PCM in 256 steps.
# The write can be either blocking [default] or non-blocking.
# Select the pin using predefined values D0, D1,...D13.
# and the analog value as an integer in the range 0 to 255.
def analogWrite(pin, pinValue, blocking = True):
    """Write an analog value to a digital pin. Mimics the Arduino function of the same name."""
    packedValue = pin * 256 + pinValue   # Pack the parameters into a single integer
    bridgec.put('AnaWr',str(packedValue))  # Write the command to the Bridge
    if (blocking):
        while(bridgec.get('AnaWr')!="-1"): # Wait for command to be completed
            {}


if True:
    # Test digitalRead() and digitalWrite()
    # LED on pin 13 will flash
    for idx in range(0, 10000):
        digitalWrite(D13,HIGH)
        pinValue = digitalRead(D13)
        print "expected a 1, got a ", pinValue
        sleep(0.5)
        digitalWrite(D13,LOW)
        pinValue = digitalRead(D13)
        print "expected a 0, got a ", pinValue
        sleep(0.50)
else:
    # Test analogRead() and analogWrite()
    # LED on pin 13 will increase in brightness then go out - repeat
    # Link A0 to GND, 3.3V or 5V to get a sensible result from the analog read
    for idx in range(0, 10000):
        cmdValue = (idx % 11)*25
        analogWrite(D13,cmdValue)
        print "Analog Value = ", analogRead(A0)
        sleep(0.50)



robertics

Note that the smiley faces were original an 8 followed by a ).

ShapeShifter

Note that the smiley faces were original an 8 followed by a ).
If you enclose the code in "code" tags, it becomes much more readable: the text is monospaced so it lines up better, it isn't processed for smiley codes or other code formatting, and the code is in a clearly delineated box with its own scroll bars. You can easily add code tags by clicking the editor button that looks like "</>" and pasting the code between the tags. Or, in the IDE, you select the code than choose the "Edit | Copy for Forum" menu item, and it will automatically enclose the copied code in code tags.

My mental picture of the Bridge is a Python dictionary with read/write access from both processors. That makes passing static text between the two a fairly simple matter, but needs a bit of work if the two processors have to function in lock step.
That's an interesting way to look at it. However, in my mind, the get/put features of the Bridge library are the least useful and most limited functions of the library.

Pros:
  • Easy to implement on the sketch side
  • Easy to read/write remotely with http requests


Cons:
  • No documentation for using it on the Linux side
  • Slow
  • Only saves current value, no notification that the value changed (writing same value twice cannot be detected.)


I only use get/put in limited circumstances, and only when I want easy remote http access. For communications between the sketch and Linux side, I prefer to use the Process class.

Quote
The lack of documentation for the Python side of the Bridge means that its designers only really expect the vast majority of people to use the MPU as a means of supplying internet services to an Arduino Leonardo.
It certainly does give that impression, and I agree that it's unfortunate. the Yun has so much more potential, as you go on to explain.

Quote
So it seems to me that I would get more out of my Yun if I pushed the bulk of the processing out of the Leonardo and into the MPU, leaving the Leonardo to manage digital and analogue i/o and manage I2C sensors, etc., plus any real world stuff that might require immediate attention.
I completely agree. Like you, I believe the most powerful Yun design pattern is to make the sketch side into a slightly intelligent I/O processor, and push most of the processing down to the Linux side. But I have a different approach, which I will get to in a moment.

Quote
With that in mind, I have defined Python versions of my old Arduino programming favourites:

digitalRead()
digitalWrite()
analogRead()
analogWrite()
That's one way to approach it, but I personally think that using the Bridge get/put functions is not a particularly efficient way to do so. Not only is it slow, you run into the synchronization issues you have tried to address by putting in the handshaking and waiting for a response from the sketch. I can't help think that you are taking the power of the Linux side and throttling it down to match the sketch, pushing data through the slow bridge get/put functions.

Quote
I can't help thinking that this is already defined somewhere, but I can't find it. If it is, please tell me where.
If all you want to do is directly control the I/O pins from the Linux side, you may want to look at the Firmata sketches that are available in the IDE in the Firmata group. This is essentially a set of routines that read commands from the serial port, and controls the I/O pins as requested. This doesn't use the Bridge library on either side of the serial link.

----------

Now, for my favorite way to design a Yun project: use the Process class. I think this is the most powerful feature of the Bridge library. On the sketch side, it works just like a Serial port: you can use the exact same methods to read and write data to/from the Process object as you would use to talk over a serial port. The big difference between the Serial and Process classes is that while you specify a baud rate in Serial.begin(), with Process.begin() you specify a Linux command.

Now, in the examples, they use simple and short lived Linux commands. In the Process example, for example, it runs a "cat /proc/cpuinfo" command, reads and displays the results of the command, and then is done. But there is so much more that the Process class can do.

The way I like to use it is to write a Python script on the Linux side that does most of the processing for the application. Then, I run that script from the sketch side using a command like "python -u /mnt/sda1/script.py". This sets up a two-way communications path between the sketch and the python script, both of which runs as an infinite loop:
  • The sketch reads the I/O pins, performs a minimum amount of processing on the data, and writes the data to the Process object. It also checks for data to be read from the Process object, decodes the commands, and updates the status of the I/O pins accordingly.
  • The Python script does whatever processing it needs to do, and writes commands to stdout (it simply uses print statements) to control the sketch and I/O pins. It also checks for input from stdin to get information from the sketch.


The sketch starts by calling Process.begin() which starts the Python script. The sketch then enters the loop which repeatedly reads I/O pins and writes updates to the Process object (the Python script) and also reads updates from the Process object (the Python script) and the updates the I/O pins.

The Python script is started by the sketch. It does whatever initialization/startup that is required, and then enters it's own loop that reads any pending updates from stdin (the data written by the script) does the required processing, and then writes to stdout (data sent to the script.) It's as if there were a serial connection between the script and the sketch (the script reads stdin and writes to stdout) and the sketch reads and writes the Process object as if it were a serial port.)

From the sketch's point of view, it's exactly as if it were reading/writing from a serial port, the only difference is that it's reading/writing from the process object instead of the Serial or Serial1 object.

From the script's point of view, writing to the sketch is simply a matter of using print statements. However, there is a slight glitch that you should be aware of: normally, output from the sketch is buffered: you might see that the script is not getting the expected output from the sketch for a long time, and then suddenly it gets a lot of output all at once. What's happening is that for effeiciency's sake, the Python interpreter is buffering up data and sending it once the buffer is full. While this is a more efficient way of handling the output, it's not what we want. The answer is to run Python in unbuffered mode, by including the "-u" option between the Python command name and the script name, for example "python -u script.py"

While writing to the process object from Python is simple, reading is a little more work. First, the script needs to import the sys module, and then in the infinite loop it calls raw_input() to read a line of text. What I like to do is in the sketch, I format the data as a JSON object. Then in the script, I can easily convert it to a Python dictionary. For example:

Code: [Select]
import sys
import json

# Process until the end of the world (or reset, whichever comes first.)
while True:
    # wait for and read a line of text from the sketch (stdin)
    # strip any whitespace characters
    line = raw_input().strip()

    data = json.loads(line)

Once the dictionary is in the data object, then the script can do whatever it needs to do with the data and write a response to the sketch. The infinite loop lets it keep processing data from the sketch, forever.

robertics

Thanks for your extensive reply. There's a lot to take in, but the communications via the Process class looks to be what I'm after.

I've completed a couple of projects on an Uno that use a simple, serial packet structure for commands and replies between the Arduino and a control/test dashboard on a PC written in Processing (no relation). What you describe doesn't seem a million miles from that at a communications level, though the devil will be in the detail - it ALWAYS is.

I'll also check out the Firmata group as you suggest. It may be that they have a solution that will suit my current needs.

My bridge attempt seems to maintain correctly the order of events, which is very handy in any real time application. It also ensures that every command is actioned, but it is a bit slow. Having said that, it does provide a very instinctive interface for a Yun/Python beginner like me if you can live with the performance.

Thanks also for the tip about the code tags. This was my first post with code and I was disappointed to say the least when it filled up with smiley faces. How unserious can you get?



 

Go Up