Controlling an IoT Yunshan ESP8266 WIFI Network Relay via an Amazon Echo/Alexa

shoulders of giants

Easy-Peasy, if you’re standing on the shoulders of giants! Here’s the demo code:

/*************************************************************************
 * Title: Simple ESP-8266 Amazon Echo/yunshan wifi relay control demo
 * File: esp8266_yunshan_echo_demo.ino
 * Author: James Eli
 * Date: 1/15/2017
 *
 * This program controls a Yunshan wifi relay module communicating through 
 * an amazon echo to the onboard esp-8266-12e module. 
 * 
 * Notes:
 *  (1) Requires the following arduino libraries:
 *      ESP8266
 *      ESPAsyncTCP
 *      FauxMoESP
 *  (2) Compiled with arduino ide 1.8.1
 *  (3) As of 11/27/16 the ESP8266 release core v2.3.0 did not have the right lwip 
 *      code. If you get a complaint about udp_set_multicast_ttl not being defined, 
 *      you'll need to uninstall the ESP8266 board support, then manually install 
 *      the most recent core from https://github.com/esp8266/arduino. Essentialy 
 *      you'll need to git clone https://github.com/esp8266/Arduino.git into your 
 *      Arduino sketchbook folder under hardware/esp8266com/esp8266 and then in a 
 *      terminal shell in hardware/esp8266com/esp8266/tools run python get.py
 *************************************************************************
 * Change Log:
 *   1/15/2017: Initial release. JME
 *************************************************************************/
#include 
#include 
#include "fauxmoESP.h"

// Esp8266 pins.
#define ESP8266_GPIO2    2 // Blue LED.
#define ESP8266_GPIO4    4 // Relay control.
#define ESP8266_GPIO5    5 // Optocoupler input.
#define LED_PIN          ESP8266_GPIO2
#define WIFI_SSID       "…"
#define WIFI_PASS       "…"
#define SERIAL_BAUDRATE 9600
fauxmoESP fauxmo;

// Wifi setup
void wifiSetup() {
  // Set WIFI module to STA mode
  WiFi.mode( WIFI_STA );
  // Connect
  Serial.printf( "[WIFI] Connecting to %s ", WIFI_SSID );
  WiFi.begin( WIFI_SSID, WIFI_PASS );
  // Wait
  while ( WiFi.status() != WL_CONNECTED ) {
    Serial.print( "." );
    delay( 100 );
  }
  Serial.println();
  // Connected!
  Serial.printf( "[WIFI] STATION Mode, SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str() );
}

void callback( uint8_t device_id, const char * device_name, bool state ) {
  Serial.print( "Device " ); 
  Serial.print( device_name ); 
  Serial.print( " state: " );
  if ( state ) {
    digitalWrite( ESP8266_GPIO4, 1 ); // Relay control pin on.
    Serial.println( "ON" );
  } else  {
    digitalWrite( ESP8266_GPIO4, 0 ); // Relay control pin off.
    Serial.println( "OFF" );
  }
}

void setup() {
  // Init serial port and clean garbage
  Serial.begin( SERIAL_BAUDRATE );
  Serial.println( "FauxMo demo sketch" );
  Serial.println( "After connection, ask Alexa/Echo to 'turn  on' or 'off'" );
  pinMode( ESP8266_GPIO4, OUTPUT );       // Relay control pin.
  pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
  pinMode( LED_PIN, OUTPUT );             // ESP8266 module blue LED.
  digitalWrite( LED_PIN, LOW );           // Turn on LED.
  // Wifi
  wifiSetup();
  digitalWrite( LED_PIN, HIGH );          // Turn off LED.
  // Fauxmo
  fauxmo.addDevice( "relay" );
  fauxmo.onMessage( callback );
}

void loop() {
  fauxmo.handle();
}

Details

The following libraries are required:
1. ESPAsyncTCP library
2. FauxMoESP library (to download, select download repository)

References

Emulate a WeMo device with ESP8266
Easy Alexa (or Echo) Control of your ESP8266 Huzzah

Posted in iot | Tagged , , | Leave a comment

Blynking an IoT Yunshan ESP8266 250V 10A AC/DC WIFI Network Relay Module

wifi_relay1

I purchased a few of these Yunshan Wifi Relays through ebay for approximately $7.50US. The device should be perfect for use in simple IOT projects which require controlling household AC power. The onboard JQC-3FF relay is rated to 250VAC or 30VDC at up to 12A. There are routered slots between the high voltage PCB traces for circuit isolation and arc-over protection. Transient voltage suppression is incorporated on both the board power supply and the photocoupler (see description below) input line.

The device requires a power supply between 7 and 30V DC. I unsuccessfully attempted to run it with an inexpensive 5V, 2A wall-wort, even though the onboard MP2303 buck converter is rated down to 4.8V. I did get it to operate successfully using a 9VDC wall-wort.

The device contains an integrated ESP8266-12E, but appears to only use the GPIO4 and GPIO5 pins. That was a disheartening discovery because it discards a significant amount of functionality inside the ESP8266 WIFI module. Hence the ESP8266 low power, wake from sleep provisions (where GPIO16 and RESET need to be linked together) would require some skillful soldering of the module’s exposed pins.

The good news is, programming the module is very easy, as I discuss later. I also found the overall build quality of my device to be above the typical level found on ebay-sourced Chinese electronics.

The ebay listing contained a link to a zip file, entitled U4648-datasheet, which contained example programs, schematics, and a Chinese manual. Through the Google translation service I managed to translate the manual, but there’s no reason to do that, as there isn’t much there. More information can be learned from a quick study of the schematic and the board itself.

Module Description
The Chinese manual presents the following limited module description:

wifi_relay3

1 – The power input terminals.
2 – The relay output terminals.
3 – IO input terminal.
4 – Enter the status indicator, IO input high when lit, blue light.
5 – The relay output status indicator, the relay is turned on, the red light.
6 – TTL serial output.
7 – Boot mode selection jumper.

Board Connectors

Here are the connections on my board:

wifi_relay2

A: 7-30V+ DC power supply
B: Power supply ground
C: Normally closed (NC) relay contact
D: Common (COM) relay contact
E: Normally open (NO) relay contact
F: 5V+ out
G: ESP8266 GPIO5 Optocoupler Input
H: Ground (isolated optocoupler input)

AP MODE Webpage

I was easily able to connect a 9V power supply to the A-B connector (see above picture and connector description) and control the device via WIFI. To do this, simply connect your computer or phone to the yunshan_wifi_xx_xx_xx network (where it appears the xx are hexadecimal numbers pulled from the ESP8266 MAC address). My device responded to the supplied password of yunshan123456789. Once a connection was established, I simply entered the IP address of 192.168.4.1 into my browser. Once there, I was greeted by a Chinese web page, the translation of which appears below. From this webpage, I was able to open and close the relay. The status of the GPIO5 optocoupler input is also displayed on this webpage.

wifi_relay4

Since I have big IOT home automation plans for these devices, my next task was to attempt a re-program of the onboard ESP8266 module. For a quick test, I uploaded the traditional Arduino IDE ESP8266 blink program, and was rewarded with a 1Hz blinking blue LED on the ESP8266 module.

Program Upload

On the lower left portion of the PCB is a section that grants access to the ESP8266 pins for programming (see the above photo). These same pins are also useful for TTL serial output purposes (debugging, etc.). Separate 2 and 3-pin headers will need to be soldered into these connector holes (labeled P5 and P6). The ESP8266 GPIO4 controls the relay through a 2N3904 transistor. Setting GPIO4 high, causes the relay to close the NO contact with Common and the NC contact to open. Additionally, taking connector “G” high causes GPIO5 to also go low isolated via a PC817 photocoupler. On my board the blue LED is connected to GPIO2, and can be illuminated by pulling the pin low.

To program the ESP8266 module, I connected the TX, RX and ground pins of connector P6 to a SparkFun USB FTDI programmer, and jumped the two pins of connector P5 together when I was ready to upload. Connector P5 grounds GPIO0 and GPIO15, sending the device into bootloader mode. If you have trouble programming the ESP8266 like I did on the first attempt, ensure you also ground your FTDI device through the P6 connector.

A very good introduction to the ESP8266 module can be found here. Excellent programming information for the individual ESP8266 modules is also widely available (two examples: ESP8266-01 and ESP8266-12e).

Board Schematic

wifi_relay_sch

Blynk Relay Control Application

/*************************************************************************
 * Title: Simple ESP-8266 blynk/yunshan wifi relay control
 * File: esp8266_yunshan_relay.ino
 * Author: James Eli
 * Date: 12/25/2016
 *
 * This program controls a Yunshan wifi relay module communicating through 
 * the onboard esp-8266-12e module. The module is controlled from the
 * internet via the Blynk cloud app. 
 * 
 * Notes:
 *  (1) Requires the following arduino libraries:
 *      ESP8266
 *      Blynk
 *  (2) Compiled with arduino ide 1.6.12
 *  (3) Uses three Blynk app widgets:
 *       V0: button configured as a switch.
 *       V1: led.
 *       V2: led.
 *************************************************************************
 * Change Log:
 *   12/25/2016: Initial release. JME
 *   12/31/2016: Added input pin status. JME
 *   01/15/2017: Added volatile. JME
 *************************************************************************/
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

// Esp8266 pins.
#define ESP8266_GPIO2    2 // Blue LED.
#define ESP8266_GPIO4    4 // Relay control.
#define ESP8266_GPIO5    5 // Optocoupler input.
#define LED_PIN          ESP8266_GPIO2
// Blynk app authentication code.
char auth[] = "***";
// Wifi SSID.
const char ssid[] = "***";
// Wifi password.
const char password[] = "***";    
// Flag for sync on re-connection.
bool isFirstConnect = true; 
volatile int relayState = LOW;    // Blynk app pushbutton status.
volatile int inputState = LOW;    // Input pin state.

void setup() {
  pinMode( ESP8266_GPIO4, OUTPUT );       // Relay control pin.
  pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
  pinMode( LED_PIN, OUTPUT );             // ESP8266 module blue LED.
  digitalWrite( LED_PIN, LOW );           // Turn on LED.
  Blynk.begin( auth, ssid, password );    // Initiate Blynk conection.
  digitalWrite( LED_PIN, HIGH );          // Turn off LED.
}

// This function runs every time Blynk connection is established.
BLYNK_CONNECTED() {
  if ( isFirstConnect ) {
    Blynk.syncAll();
    isFirstConnect = false;
  }
}

// Sync input LED.
BLYNK_READ( V2 ) {
  CheckInput();
}

// Blynk app relay command.
BLYNK_WRITE( V0 ) {
  if ( param.asInt() != relayState ) {
    relayState = !relayState;                  // Toggle state.
    digitalWrite( ESP8266_GPIO4, relayState ); // Relay control pin.
    Blynk.virtualWrite( V1, relayState*255 );  // Set Blynk app LED.
  }
}

// Debounce input pin.
int DebouncePin( void ) {
  // Read input pin.
  if ( digitalRead( ESP8266_GPIO5 ) == HIGH ) {
    // Debounce input.
    delay( 25 );
    if ( digitalRead( ESP8266_GPIO5 ) == HIGH )
      return HIGH;
  }
  return LOW;
}

// Set LED based upon state of input pin.
void CheckInput( void ) {
  if ( DebouncePin() != inputState ) {
    Blynk.virtualWrite( V2, inputState*255 );
    inputState = !inputState;
  }
}

// Main program loop.
void loop() {
  Blynk.run();
  CheckInput();
  yield();
}

TCP Client Demo

Here is a basic server which responds to TCP client HTTP GET commands (added 1/8/17):

#include <ESP8266WiFi.h>

// Esp8266 pinouts
#define ESP8266_GPIO2    2  // Blue LED.
#define ESP8266_GPIO4    4  // Relay control. 
#define ESP8266_GPIO5    5  // Optocoupler input.
#define LED_PIN          ESP8266_GPIO2
// WiFi Definitions.
const char ssid[] = "***";
const char pswd[] = "***";
WiFiServer server( 80 );
volatile int relayState = 0;      // Relay state.

void setup() {
  initHardware();
  connectWiFi();
  server.begin();
}

void GetClient( WiFiClient client ) {
  // Read the first line of the request.
  String req = client.readStringUntil( '\r' );
  Serial.println( req );
  client.flush();

  String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n";

  if ( req.indexOf( "OPTIONS" ) != -1 ) {
    s += "Allows: GET, OPTIONS";

  } else if ( req.indexOf( "GET" ) != -1 ) {
    if ( req.indexOf( "open" ) != -1 ) {
      // relay on!
      s += "relay on!";
      relayState = 1;
      digitalWrite( ESP8266_GPIO4, 1 ); // Relay control pin.
    
    } else if ( req.indexOf( "close" ) != -1 ) {
      // relay off!
      s += "relay off!";
      relayState = 0;
      digitalWrite( ESP8266_GPIO4, 0 ); // Relay control pin.
    
    } else if ( req.indexOf( "relay" ) != -1 ) {
      if ( relayState == 0 )
        // relay off!
        s += "relay off!";
      else
        // relay on!
        s += "relay on!";

    } else if ( req.indexOf( "io" ) != -1 ) {
      if ( digitalRead( ESP8266_GPIO5 ) == 0 )
        s += "input io is:0!";
      else
        s += "input io is:1!";
    
    } else if ( req.indexOf( "MAC" ) != -1 ) {
      uint8_t mac[WL_MAC_ADDR_LENGTH];
      WiFi.softAPmacAddress( mac );
      String macID = String( mac[WL_MAC_ADDR_LENGTH - 5], HEX) + String( mac[WL_MAC_ADDR_LENGTH - 4], HEX) +
                     String( mac[WL_MAC_ADDR_LENGTH - 3], HEX) + String( mac[WL_MAC_ADDR_LENGTH - 2], HEX) +
                     String( mac[WL_MAC_ADDR_LENGTH - 1], HEX) + String( mac[WL_MAC_ADDR_LENGTH], HEX);
      macID.toUpperCase();
      s += "MAC address: " + macID;

    } else
      s += "Invalid Request.<br> Try: open/close/relay/io/MAC";

  } else 
    s = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n";
         
  client.flush();
  s += "</html>\n";

  // Send the response to the client.
  client.print( s );
  delay( 1 );
  Serial.println( "Client response sent." );
}

void loop() {
  // Check if a client has connected.
  WiFiClient client = server.available();
  if ( client ) 
    GetClient( client );
}

void connectWiFi() {
  byte ledStatus = LOW;
  Serial.println();
  Serial.println( "Connecting to: " + String( ssid ) );
  // Set WiFi mode to station (as opposed to AP or AP_STA).
  WiFi.mode( WIFI_STA );

  // WiFI.begin([ssid], [passkey]) initiates a WiFI connection.
  // to the stated [ssid], using the [passkey] as a WPA, WPA2, or WEP passphrase.
  WiFi.begin( ssid, pswd );

  while ( WiFi.status() != WL_CONNECTED ) {
    // Blink the LED.
    digitalWrite( LED_PIN, ledStatus ); // Write LED high/low.
    ledStatus = ( ledStatus == HIGH ) ? LOW : HIGH;
    delay( 100 );
  }

  Serial.println( "WiFi connected" );  
  Serial.println( "IP address: " );
  Serial.println( WiFi.localIP() );
}

void initHardware() {
  Serial.begin( 9600 );
  pinMode( ESP8266_GPIO4, OUTPUT );       // Relay control pin.
  pinMode( ESP8266_GPIO5, INPUT_PULLUP ); // Input pin.
  pinMode( LED_PIN, OUTPUT );             // ESP8266 module blue LED.
  digitalWrite( ESP8266_GPIO4, 0 );       // Set relay control pin low.
}
Posted in iot | Tagged , | 41 Comments

ATMEL Studio 7 Does Blink in Assembly Language

as7b

See the previous post (here) for detailed information on AS7 installation and simulating of an Arduino program execution. As an exercise to gain familiarity with AS7, lets make an assembly language project using the below Blink code:

• Select “File>New>Project”.

• Assembler.

• AVR Assembler Project.

• Give it a unique name.

• Select the proper device.

Remember to select the Simulator in order to run the program inside the AS7 IDE. See the previous post on how to do this. Here is a screen shot of the assembly code running in AS7:

as7-a

Here is the code for a very basic assembly language blink program:

.include "m168pdef.inc"

.org 0x0000
   rjmp start
start:
    ldi r16, 0           ; reset system status
    out SREG, r16        ; init stack pointer
    ldi r16, low(RAMEND)
    out SPL, r16
    ldi r16, high(RAMEND)
    out SPH, r16

    sbi DDRB, DDB5       ;pinMode(13, OUTPUT);
_loop:
    sbi PORTB, PORTB5    ;turn LED on
    rcall _delay
    cbi PORTB, PORTB5    ;turn LED off
    rcall _delay
    rjmp _loop

_delay:
    ldi r24, 0x00        ;one second delay iteration
    ldi r23, 0xd4 
    ldi r22, 0x30 
_d1:                     ;delay ~1 second
    subi r24, 1   
    sbci r23, 0   
    sbci r22, 0
    brcc _d1
    ret

Now, start learning inline assembly language:

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

Posted in arduino, assembly language | Tagged , , , , , , , | Leave a comment

Using ATMEL Studio 7 for Arduino Development

as7a

While installing ATMEL Studio 7 (AS7) is not required in order to learn inline assembly language programming, it has worthwhile advantages. The ability to compile code for the Arduino, run it inside the included Simulator and immediately debug it will greatly speed your learning process. This seamless and iterative process would take several long minutes to complete if using the Arduino IDE. In fact, the Arduino IDE alone cannot perform the disassembly and debug functions.

AS7 now features seamless, one-click importation of projects created in the Arduino development environment. Your sketch, including any libraries it references, can be imported into AS7 as a C++ project. Once imported, you can leverage many additional capabilities of AS7 to fine-tune and debug your design. For most Arduino boards, shield-adapters that expose debug connectors are available, or one could use an ATMEL-ICE debug wire interface with a standard Arduino (slight modification of the Arduino is required).

More information on DebugWire with the Arduino can be found here:
Debugging Arduino using debugWire
Debugging with the new ATMEL-ICE
Modify an Arduino for DebugWire

Best of all, AS7 is free of charge. It is also important to note, that most of the functionality of AS7 is also available in the older Atmel Studio 6 IDE (AS6).

Install ATMEL Studio 7

I’m not going to go through the whole installation process (get the book), just navigate to the ATMEL Studio 7 website download page, and click the DOWNLOAD NOW link:

http://www.atmel.com/Microsite/atmel-studio/

Direct link to the download page:

http://www.atmel.com/tools/atmelstudio.aspx#download

Select the web installer unless you have a specific requirement to install off-line.

ATMEL Studio 7 Blinks

Congratulations if you have completed the installation. Let’s run the Studio for the first time.

On initial start, AS7 will ask you to select a user interface profile. I suggest the “Advanced” version, however either option is satisfactory. If desired, the profile can always be changed later.

as7-1

Be patient, it will seem like a long period of time for the program to load, but eventually you will be greeted with the IDE and the startup page populated with an ATMEL announcement internet feed. It is possible to disable this if it is unwanted (checkbox on the bottom left).

as7-2

From the file menu select NEW->Project.

as7-3

The New Project dialog will open and make the following selections:

• Insure “Installed” and “C/C++” is highlighted on the far left.
• Highlight “Create project from Arduino sketch”.
• Give the project an appropriate name like, “AS7_Blink” (note, spaces are not allowed in the project name).
• The default location should fill automatically.
• The solution name defaults to the project name, and this is acceptable for now.

as7-4

The next dialog will ask for the existing Arduino sketch location. Click on the button containing the ellipsis to the right of the Sketch File. Navigate to the Arduino Blink example sketch, which should be located inside your Arduino installation folder (similar location to mine on Windows):

C:\Program Files (x86)\Arduino\examples\01.Basics\Blink\Blink.ino

Continuing along:

• Insure your Arduino IDE path is properly filled in.
• Make the appropriate selections for your board (Arduino/Genuino Uno) and device (atmega328p).
• Click “Ok”.

AS7 will take some time creating the folder(s) for the project and pulling in the Blink sketch with all of its dependencies (all core and variant include files, Arduino core source files and libraries). It will eventually finish by creating and opening an editable “sketch.cpp” file inside the IDE.

as7-5

The sketch.cpp file is primarily the code from the example Arduino blink program including some additional automatically generated code by the ATMEL studio. Your solution should look like this:

as7-6

Ready, Set, Simulate!

We can now run the Blink program without an actual Arduino board using the simulator included with AS7. But first we need to inform AS7 to use the Simulator. Under the Project menu, select “Blink Properties…”.

as7-7

On the properties screen that appears, on the far left, select the “Tool” item, and under the heading of “Selected debugger/programmer”, select “Simulator” from the drop down list.

as7-8

Now, in order to run the program in the simulator, simply press “Alt+F5” or select “Debug->Start Debugging and Break” using the menu. AS7 will build the Blink project, execute the C-runtime startup code and then halt at the first line of the Arduino program. If you examine your screen, you’ll notice the debugger (on the far left) is pointing to and highlighting the line of code it has paused on:

init();

as7-9

At this point, you might think to yourself, this code wasn’t in the Blink program. What gives?

All of this is actually code that comes from the core Arduino wiring files which silently gets inserted into every Arduino sketch as needed. If you look further down the screen you will eventually see the call to the Blink Setup() function.

Hitting the “F10” key (Step Over) twice should bring the simulator to the function call to Setup. At this time, pressing the “F11” key (Step Into) will cause the simulator to jump into the Blink program and pause it’s execution on the first line inside the Setup function:

pinMode(led, OUTPUT);

Now things should start to look familiar. The last feature I want to demonstrate is the ability to drill down into the underlying assembly language that was created by the compiler. We do this by selecting an option from the Debug menu, “Debug>Windows>Disassembly”.

as7-10

You should now see a mixture of C and assembly code which makes up the executable Blink program. The simulator should be paused on the following line:

0000007B LDI R22,0x01 Load immediate

as7-11

I can not overstate the importance of this feature. The ability to examine the assembly code, compare it to the source, and to step through it line-by-line is an enormous asset to the inline assembly programmer. You can, among other features:

• Set breakpoints
• Watch variables
• Alter register values
• Time sections of code.

You will want to remember how to do this.

ATMEL has uploaded several informative videos on youtube.com demonstrating the use of the Studio, how to debug and how to use the simulator. A good example is located here:

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

Here’s a partial look at the disassembly listing (mixed C and assembly) produced by AS7:

void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
0000007B  LDI R22,0x01		Load immediate 
0000007C  LDS R24,0x0100		Load direct from data space 
0000007E  JMP 0x000001A5		Jump 
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
00000080  PUSH R28		Push register on stack 
00000081  PUSH R29		Push register on stack 
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
00000082  LDI R28,0x00		Load immediate 
00000083  LDI R29,0x01		Load immediate 
00000084  LDI R22,0x01		Load immediate 
00000085  LDD R24,Y+0		Load indirect with displacement 
00000086  CALL 0x000001E1		Call subroutine 
  delay(5000);               // wait for a second
00000088  LDI R22,0x88		Load immediate 
00000089  LDI R23,0x13		Load immediate 
0000008A  LDI R24,0x00		Load immediate 
0000008B  LDI R25,0x00		Load immediate 
0000008C  CALL 0x00000119		Call subroutine 
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
0000008E  LDI R22,0x00		Load immediate 
0000008F  LDD R24,Y+0		Load indirect with displacement 
00000090  CALL 0x000001E1		Call subroutine 
  delay(1000);               // wait for a second
00000092  LDI R22,0xE8		Load immediate 
00000093  LDI R23,0x03		Load immediate 
00000094  LDI R24,0x00		Load immediate 
00000095  LDI R25,0x00		Load immediate 
}
00000096  POP R29		Pop register from stack 
00000097  POP R28		Pop register from stack 
  delay(1000);               // wait for a second
00000098  JMP 0x00000119		Jump 
Posted in arduino, avr | Tagged , , , , , , , | Leave a comment

My Cup Overflows

overflow

When performing math (even basic addition and subtraction) with signed numbers an overflow problem sometimes arises. The Arduino microcontroller indicates the existence of an overflow error by setting the overflow flag in the SREG. Here’s a demonstration of the overflow problem with a simple addition operation:

volatile int8_t n1=0x70; //112
volatile int8_t n2=0x35; //53
volatile int8_t answer;

void setup() {
  Serial.begin(9600);
  
  asm(
    "add %1, %2 \n"
  
    : "=r" (answer) : "r" (n1), "r" (n2)
  );
  
  Serial.print("answer = "); Serial.println(answer);
}

The result to the above addition is, “answer = -91”, or 0xA5 hexadecimal. That’s wrong! The reason the answer turns out wrong is because the result is larger than an 8-bit register can hold.

The largest “signed 8-bit number” is +127, or 0x7f hexadecimal. However, this operation did set the Status Register Overflow Flag (V flag) to warn us that the result is erroneous. But, it’s completely up to us, the programmer to deal with this issue.

What’s Your Sign?

In “8-bit signed number” operations, the overflow flag is set when either of the following two conditions occur:

• There is a carry from bit 6 to bit 7, but no carry out of bit 7 (C flag not set).
• There is a carry out of bit 7 (C flag set), but no carry from bit 6 to bit 7.

I bring these two cases to your attention, because we can perform addition on two negative numbers with the sign bit remaining correct, yet the addition could still overflow. For example, when adding -2 (0x80) and -128 (0xFE), the result becomes 0x7E (+126), which again is incorrect.

When adding two numbers with different signs, the absolute value of the result is a smaller number than the absolute value of the operands prior to the addition. In this case, an overflow is impossible.

Therefore, an overflow is only possible when adding two numbers with the same sign. Furthermore, when adding two “same-signed numbers”, the sign of the result must be the same. The conclusion here is, for signed number addition, if the overflow flag is set, the result is invalid, and in unsigned addition, if the carry flag is set, the result is invalid. In signed number operations, overflow is possible, and overflow corrupts the result and negates the sign bit.

See my tutorial on Arduino Inline Assembly Math here.

Posted in arduino, assembly language, avr, avr inline assenbly | Tagged , , , , , | Leave a comment

Arduino Inline Assembly Port & Pin Compendium

compendium

The following is a compendium of inline assembly functions dealing with ports and pins. Use these at your own risk. These functions have been trimmed of most bounds checking, so they can easily be abused. The Arduino Inline Assembly Tutorial explains most of the details starting here.

analogWrite

This inline code writes an analog value (in the form of a PWM wave) to a particular pin. After executing, the pin will generate a steady square wave of the specified duty cycle until the next call (or call to digitalRead() or digitalWrite() on the same pin). The frequency of the PWM signal on most pins is approximately 490 Hz. On the Uno and similar boards, pins 5 and 6 have a frequency of approximately 980 Hz. On Arduino boards with the ATmega168/328, this function works on pins 3, 5, 6, 9, 10, and 11. The analogWrite function has nothing to do with the analog pins or the analogRead function.

A pinMode() call is included inside this function, so there is no need to set the pin as an output before executing this code.

This version of AnalogWrite, with no frills saves ~542 bytes over the built-in function:

//analogWrite requires a PWM pin 
//PWM pin/timer table:
//3:  (TIMER2B) PD3/TCCR2A/COM2B1/OCR2B
//5:  (TIMER0B) PD5/TCCR0A/COM0B1/OCR0B
//6:  (TIMER0A) PD6/TCCR0A/COM0A1/OCR0A
//9:  (TIMER1A) PB1/TCCR1A/COM1A1/OCR1A
//10: (TIMER1B) PB2/TCCR1A/COM1B1/OCR1B
//11: (TIMER2A) PB3/TCCR2A/COM2A1/OCR2A
//set below 6 defines per above table
#define ANALOG_PORT         PORTB
#define ANALOG_PIN          PORTB3
#define ANALOG_DDR          DDRB
#define TIMER_REG           TCCR2A
#define COMPARE_OUTPUT_MODE COM2A1
#define COMPARE_OUTPUT_REG  OCR2A

volatile uint8_t val = 128; //0-255

  asm (
    "sbi  %0, %1   \n" //DDR set to output (pinMode)

    "cpi  %6, 0    \n" //if full low (0)
    "breq _SetLow  \n"
    "cpi  %6, 0xff \n" //if full high (0xff)
    "brne _SetPWM  \n"

    "sbi  %2, %1   \n" //set high
    "rjmp _SkipPWM \n"

  "_SetLow:        \n"
    "cbi  %2, %1   \n" //set low
    "rjmp _SkipPWM \n"

  "_SetPWM:        \n"
    "ld   r24, X   \n"
    "ori  r24, %3  \n"
    "st   X, r24   \n" //connect pwm pin timer# & channel
    "st   Z, %6    \n" //set pwm duty cycle (val)

  "_SkipPWM:       \n"
    : : "I" (_SFR_IO_ADDR(ANALOG_DDR)), "I" (ANALOG_PIN),
    "I" (_SFR_IO_ADDR(ANALOG_PORT)), "M" (_BV(COMPARE_OUTPUT_MODE)),
    "x" (_SFR_MEM_ADDR(TIMER_REG)), "z" (_SFR_MEM_ADDR(COMPARE_OUTPUT_REG)), "r" (val)
    : "r24"
  );

analogRead

The Arduino board contains a 6 channel, 10-bit analog to digital converter which is the brains beneath the analogRead function. It maps input voltages between 0 and 5 into integer values between 0 and 1023, thus yielding a resolution between readings of: 5/1024 units or, 0.0049 volts (4.9 mV) per unit. The input range and resolution can be changed through the ANALOG_V_REF define. This code reads the value from the specified analog channel (0-7), which correspond to the analog pins (note, do NOT use A0-A7 for the channel number in this code). Further information about the underlying ADC can be found here.

While this version of analogRead (aRead) saves a few bytes (~50), it also gives the option of changing the speed via the ADC prescaler. However, don’t arbitrarily change the prescale without understanding the consequences. ATMEL advises the slowest prescale should be used (PS128). A higher speed (smaller prescale) reduces the accuracy of the AD conversion. The arduino sets the prescale to 128 during initiation, just as the code below does.

//Define various ADC prescales
#define PS2   (1<<ADPS0)                             //8000kHz ADC clock freq
#define PS4   (1<<ADPS1)                             //4000kHz
#define PS8   ((1<<ADPS0) | (1<<ADPS1))              //2000kHz
#define PS16  (1<<ADPS2)                             //1000kHz
#define PS32  ((1<<ADPS2) | (1<<ADPS0))              //500kHz
#define PS64  ((1<<ADPS2) | (1<<ADPS1))              //250kHz
#define PS128 ((1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)) //125kHz
#define ANALOG_V_REF     DEFAULT //INTERNAL, EXTERNAL, or DEFAULT
#define ADC_PRESCALE     PS128   //PS16, PS32, PS64 or P128(default)

uint16_t aRead(uint8_t channel) {
  uint16_t result;
  
  asm (
    "andi %1, 0x07    \n" //force pin==0 thru 7
    "ori  %1, (%6<<6) \n" //(pin | ADC Vref)
    "sts  %2, %1      \n" //set ADMUX

    "lds  r18, %3             \n" //get ADCSRA
    "andi r18, 0xf8           \n" //clear prescale bits
    "ori  r18, ((1<<%5) | %7) \n" //(new prescale | ADSC)
    "sts  %3, r18             \n" //set ADCSRA

    "_loop:       \n" //loop until ADSC cleared
    "lds  r18, %3 \n"
    "sbrc r18, %5 \n"
    "rjmp _loop   \n"

    "lds  %A0, %4   \n" //result = ADCL 
    "lds  %B0, %4+1 \n" //ADCH

    : "=r" (result) : "r" (channel), "M" (_SFR_MEM_ADDR(ADMUX)),
    "M" (_SFR_MEM_ADDR(ADCSRA)), "M" (_SFR_MEM_ADDR(ADCL)),
    "I" (ADSC), "I" (ANALOG_V_REF), "M" (ADC_PRESCALE)
    : "r18"
  );
  
  return result;
}

pinMode(OUTPUT)

The arduino pinMode function configures pin behavior. The code presented from here on, has been previously explained inside the Arduino Inline Tutorial Series.

asm (
  "sbi %0, %1 \n" //1=OUTPUT
    : : "I" (_SFR_IO_ADDR(DDRB)), "I" (DDB5)
);

pinMode (INPUT PULLUP)

asm (
  "cbi %0, %2 \n"
  "sbi %1, %2 \n"
    : : "I" (_SFR_IO_ADDR(DDRB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (DDB5)
);

pinMode (INPUT)

asm (
  "cbi %0, %2 \n"
  "cbi %1, %2 \n"
    : : "I" (_SFR_IO_ADDR(DDRB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (DDB5)
);

pinMode with Multiple Pins

#define PIN_DIRECTION 0b00101000 //PIN 3 & 5 OUTPUT
//#define PIN_DIRECTION (1<<DDB3) | (1<<DDB5)
asm (
  "out %0, %1 \n"
  : : "I" (_SFR_IO_ADDR(DDRB)), "r" (PIN_DIRECTION)
);

digitalWrite HIGH

If a pin has been configured as an OUTPUT, its voltage will be set to the corresponding value: 5V (or 3.3V on 3.3V boards) for HIGH, 0V (ground) for LOW. However, if the pin is configured as an INPUT, digitalWrite enables (HIGH) or disables (LOW) the internal pullup on the input pin.

asm (
  "sbi %0, %1 \n"
  : : "I" (_SFR_IO_ADDR(PORTB)),"I" (PORTB5)
);

digitalWrite LOW

asm (
  "cbi %0, %1 \n"
  : : "I" (_SFR_IO_ADDR(PORTB)), "I" (PORTB5) 
);

digitalWrite(output)

volatile uint8_t output = HIGH; //LOW or HIGH
asm (
  "cpi %2, 0     \n"
  "breq 1f       \n"
  "sbi %0, %1    \n"
  "rjmp 2f       \n"
  "1: cbi %0, %1 \n"
  "2:            \n"
  : : "I" (_SFR_IO_ADDR(PORTB)), "I" (PORTB5), "r" (output)
);

digitalToggle

Try to find this one in the Arduino wiring code:

//toggle pin
asm (
  "in r24, %0  \n"
  "eor r24, %1 \n"
  "out %0, r24 \n"
  : : "I" (_SFR_IO_ADDR(PORTB)), "r" ((uint8_t)_BV(PORTB5)) : "r24"
);

digitalRead

digitalRead simply reads the value from a specified digital pin, either HIGH or LOW.

volatile uint8_t status;
 
asm (
  "in __tmp_reg__, __SREG__  \n"
  "cli                       \n"                     
  "ldi %0, 1                 \n" //high 
  "sbis %1, %2               \n" //skip next if pin high
  "clr %0                    \n" //low
  "out __SREG__, __tmp_reg__ \n"
  : "=r" (status) : "I" (_SFR_IO_ADDR(PINB)), "I" (PINB5)  
);

digitalRead Alternative

This is a generic alternative, which can be called programmatically. Note it must be called using a pointer to the PIN (&PINB), otherwise the compiler emits incorrect code:

//call like so:
//uint8_t status = dRead(&PINB, PINB5);

__attribute__ ((noinline)) uint8_t dRead(volatile uint8_t *port, uint8_t pin) {
  uint8_t result, mask=1;

  asm (
    "movw  r30, %1 \n" //port reg addr in Z
  "1:              \n"
    "cpi  %2, 0    \n" //loop until pin==0
    "breq 2f       \n" //leave loop
    "lsl  %3       \n" //shift (mask) left 1 position
    "dec  %2       \n" //decrement loop counter
    "rjmp 1b       \n" //repeat
  "2:              \n"
    "in   __tmp_reg__, __SREG__ \n" //preserve sreg
    "cli           \n" //disable interrupts
    "ld   r18, Z   \n" //fetch port data
    "and  r18, %3  \n" //compare pin with mask
    "ldi  %0, 1    \n" //set return high
    "brne 3f       \n" 
    "clr  %0       \n" //set return low
  "3:              \n"
    "out  __SREG__, __tmp_reg__ \n"
    : "=&r" (result) : "r" (port), "a" (pin), "r" (mask) : "r18", "r30", "r31"
  );

  return result;
}

Example of turning off PWM for arduino digital pin #11

//digital PWM pin registers:
//3:  (TIMER2B) PD3/TCCR2A/COM2B1/OCR2B
//5:  (TIMER0B) PD5/TCCR0A/COM0B1/OCR0B
//6:  (TIMER0A) PD6/TCCR0A/COM0A1/OCR0A
//9:  (TIMER1A) PB1/TCCR1A/COM1A1/OCR1A
//10: (TIMER1B) PB2/TCCR1A/COM1B1/OCR1B
//11: (TIMER2A) PB3/TCCR2A/COM2A1/OCR2A

asm (
  "ld  r16, Z \n"
  "ldi r17, 0xff \n"
  "eor r17, %1 \n"
  "and r16, r17 \n"
  "st  Z, r16 \n"
  : : "z" (_SFR_MEM_ADDR(TCCR2A)), "d" (COM2A1) : "r16", "r17"
);

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

Posted in arduino, assembly language, avr, avr inline assenbly | Tagged , , , , , , , | 1 Comment

Arduino Inline Assembly Tutorial (Examples)

case study

As the final tutorial in this series, we present four example inline assembly functions for the arduino. Specifically, these cover the conversion of a byte to a hexadecimal string, SPI Mode 0 hardware transfer, SPI Mode 0 Bit-banging, and the C library atoi function. Do not take these functions as archetypical examples of high-quality coding practice or brilliantly efficient inline code. They are neither.

Most of the previous examples in this series were simple “snippets of code”, and as such gave a myopic view of inline assembly. The goal here is to show complete and working demonstrations of how to include inline assembly into the typical arduino program. Each example includes explanatory comments covering the key portions of code.

In addition to these examples, have a look at the Arduino Inline Assembly Blink Program.

Stringing Hexadecimals

The following code converts a byte value into a hexadecimal string. Notice at the start of the code, that the constraint #0 value (val) is temporarily saved in the r25 register. The function then converts the first nibble. When the conversion process is complete, the function loops back and converts the second nibble. Note how the code uses the SREG T-bit to flag the first vs. second nibble.

void ByteToHexStr(uint8_t val, char *str) {
  asm (
    "set           \n" //flag first nibble
    "mov r25, %0   \n" //save val
    "swap %0       \n" //swap for correct nibble order
  "1:              \n"
    "andi %0, 0xf  \n" //mask a nibble
    "cpi  %0, 0xa  \n" //>10?
    "brcc 2f       \n" //yes
    "subi %0, 0xd0 \n" //convert numeral (0-9) 
    "rjmp 3f       \n" //skip next
  "2:              \n"
    "subi %0, 0xc9 \n" //convert letter (A-F)
  "3:              \n"
    "st Z+, %0     \n" //put into string
    "brtc 4f       \n" //upper nibble?
    "clt           \n" //clear nibble flag
    "mov %0, r25   \n" //get upper nibble
    "rjmp 1b       \n" //repeat conversion
  "4:              \n" //exit
    : : "r" (val), "z" (str) : "memory"
  );
}

I SPI With My Little Eye…

Serial Peripheral Interface (SPI) is a synchronous serial data protocol used by microcontrollers for communicating with one or more peripheral devices, or for communication between two microcontrollers. The SPI standard is loose and each device implements it a little differently, which means you must pay close attention to the device’s datasheet when implementing the protocol. Generally speaking, there are four modes of transmission, defined by the clock phase and polarity.

Here are two versions of the SPI transfer function. The first of these programs incorporates the arduino hardware SPI. The second is a bit-bang version using different pins. More information on SPI can be found here and here.

SPI Mode 0 Hardware Transfer

static __attribute__ ((noinline)) uint8_t SpiXfer(uint8_t data) {
  asm (
    "out  %1, %0          \n" //put data out SPDR register
    "nop                  \n" //pause
  "1:                     \n"
    “in   __tmp_reg__, %2 \n" //check xmit complete
    "sbrs __tmp_reg__, %3 \n"
    "rjmp 1b              \n"
    "in   %0, %1          \n" //get incoming data
    : "+r" (data) : "M" (_SFR_IO_ADDR(SPDR)),
    "M" (_SFR_IO_ADDR(SPSR)), "I" (SPIF)
  );

  return data;
}

SPI Bit-Bang

#define MOSI_PORT  PORTD
#define MOSI_BIT   PORTD5
#define MISO_PORT  PIND
#define MISO_BIT   PIND6
#define CLOCK_PORT PORTD
#define CLOCK_BIT  PORTD7

static __attribute__ ((noinline)) uint8_t SpiBitBang(uint8_t data) {
  register uint8_t tmp, i=8;
  
  //save and restore sreg because t-bit is utilized
  asm (
    "in __tmp_reg__, __SREG__ \n"
  "1:               \n"
    "sbrs %0, 0x07  \n" //is output data bit high?
    "rjmp 2f        \n" //no
    "sbi  %3, %4    \n" //output a high bit
    "rjmp 3f        \n"
  "2:               \n"
    "cbi  %3, %4    \n" //output a low bit
  "3:               \n"
    "lsl  %0        \n" //shift to next bit
    "in   %1, %5    \n" //get input
    "tst  %1        \n" //anything here?
    "breq 4f        \n" //nope
    "bst  %1, %6    \n" //set t-bit if input bit is high
    "clr  %1        \n" //zeroize register
    "bld  %1, 0     \n" //set bit 0
    "or   %0, %1    \n" //or low bit with data for return value
  "4:               \n"
    "sbi  %7, %8    \n" //toggle clock bit high
    "nop            \n" //pause
    "cbi  %7, %8    \n" //toggle clock bit low
    "subi %2, 1     \n" //more bits?
    "brne 1b        \n" //do next bit
    "out __SREG__, __tmp_reg__ \n"
    : "+r" (data), "=&r" (tmp): "a" (i),
    "M" (_SFR_IO_ADDR(MOSI_PORT)), "I" (MOSI_BIT),
    "M" (_SFR_IO_ADDR(MISO_PORT)), "I" (MISO_BIT),
    "M" (_SFR_IO_ADDR(CLOCK_PORT)),  "I" (CLOCK_BIT)
  );

  return data;
}

A Toy

Atoi is a function in the that converts a string into an integer numerical representation (atoi stands for ASCII to integer). It is included in the C standard library header file stdlib.h. It is prototyped as follows:

int atoi(const char *str);

The str argument is a string, represented by an array of characters, containing the characters of a signed integer number. The string must be null-terminated.

Here is the basic idea of the atoi function implemented in C language:

int16_t atoi(char s[]) {
  uint8_t i, sign;
  int16_t n;
  
  //skip white space
  for (i=0; s[i]<=' '; i++);
  
  //sign
  sign = 0;
  if (s[i] == '-') {
    sign = 1;
    i++;
  }
  
  //convert
  for (n=0; s[i]>='0' && s[i]<='9'; i++)
    n = 10*n + s[i] - '0';
  
  if (sign)
    return (-1*n);
  else
    return n;
}

Atoi Inline

Here is our implementation, which is only 64 bytes in length. By comparison, the arduino AVR libc atoi() function is 76 bytes long. This version is basically functionally equivalent, however there are a few detail differences (this function steps over all leading ASCII characters 0x2F and below, not just whitespace):

int16_t _atoi(const char *s) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
  //sign & c are initialized inside inline asm code
  register uint8_t sign, c;
#pragma GCC diagnostic pop
  //force result into return registers
  register int16_t result asm("r24"); 
  
  asm (
    "ldi  %A0, 0x00         \n" //result = 0
    "ldi  %B0, 0x00         \n"

  "1:                       \n"
    "ld   %2, Z+            \n" //fetch char
    "cpi  %2, '-'           \n" //negative sign?
    "brne 2f                \n"
    "ldi  %3, 0x01          \n" //sign = TRUE

  "2:                       \n"
    "cpi  %2, '/' + 1       \n" //step over whitespace/garbage
    "brcc 3f                \n"
    "rjmp 1b                \n"

  "3:                       \n"
    "rjmp 5f                \n"

  "4:                       \n"
    "ldi  r23, 10           \n" //result *= 10
    "mul  %B0, r23          \n"
    "mov  %B0, r0           \n"
    "mul  %A0, r23          \n"
    "mov  %A0, r0           \n"
    "add  %B0, r1           \n"
    "clr  __zero_reg__      \n" //r1 trashed by mul
    "add  %A0, %2           \n" //result += new digit
    "adc  %B0, __zero_reg__ \n"
    "ld   %2, Z+            \n" //fetch next digit char
  
  "5:                       \n"
    "subi %2, '0'           \n" //convert char to 0-9
    "cpi  %2, 10            \n" //end of string?
    "brlo 4b                \n"

    "cpi  %3, 0             \n" //negative?
    "breq 6f                \n"
    "com  %B0               \n" //negate result
    "neg  %A0               \n"
    "sbci %B0, -1           \n"
  
  "6:                       \n"
    : "+r" (result) : "z" (s), "a" (c), "a" (sign) : "memory"
  );

  return result;
}

Conclusion

While there are countless more topics to cover, and many more rabbit-holes to dive down, I believe I have covered enough of the basics in this series. I sure enjoyed researching and writing these tutorials. And, hopefully you gained a few insights into the funky world of arduino (AVR) inline assembly programming. Now, get inline with your programming!

[updated: 4.11.16]

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

Posted in arduino, assembly language, avr, avr inline assenbly | Tagged , , , , , | Leave a comment