• Print

Author Topic: Direct Port Access  (Read 1361 times)

Galleon

  • Administrator
  • Hero Member
  • *****
  • Posts: 4664
  • QB Forever
    • Email
Direct Port Access
« on: July 09, 2011, 06:15:02 AM »
Step 1: Download and install NTport
http://www.zealsoftstudio.com/ntport/ntportev.zip

Step 2: Download the following files and place them in your QB64 folder:
http://www.qb64.net/ntport/ntport.lib
http://www.qb64.net/ntport/ntport.h
http://www.qb64.net/ntport/ntport.dll

Step 3: Add this to your QB64 program
Code: [Select]
DEFLNG A-Z
DECLARE LIBRARY "ntport"
' Returns a value indicating the last operation is successful or not.
    FUNCTION GetLastState (s$)
    ' Enable your application to read or write specific ports.
    SUB EnablePorts (BYVAL PortStart, BYVAL PortStop)
    ' Disable your application to read or write specific ports.
    SUB DisablePorts (BYVAL PortStart, BYVAL PortStop)
    ' Returns a value indicates whether the application is running under Windows NT/2000/XP/Server 2003 system.
    FUNCTION IsWinNT
    ' Returns a value indicates whether the application is running under 64-bit Windows system.
    FUNCTION IsWin64
    ' Returns a value from specific ports.
    FUNCTION Inpb ALIAS Inp (BYVAL PortNum)
    FUNCTION Inportb ALIAS Inport (BYVAL PortNum)
    FUNCTION Inpw (BYVAL PortNum)
    FUNCTION InportW (BYVAL PortNum)
    FUNCTION Inpd (BYVAL PortNum)
    FUNCTION InportD (BYVAL PortNum)
    ' Write a value to specific ports.
    SUB Outpb ALIAS Outp (BYVAL PortNum, BYVAL PortData)
    SUB Outportb ALIAS Outport (BYVAL PortNum, BYVAL PortData)
    SUB Outpw (BYVAL PortNum, BYVAL PortData)
    SUB OutportW (BYVAL PortNum, BYVAL PortData)
    SUB Outpd (BYVAL PortNum, BYVAL PortDData)
    SUB OutportD (BYVAL PortNum, BYVAL PortDData)
    ' Set the registration information.
    SUB LicenseInfo (sUserName$, BYVAL DdwKey)
    ' Returns the version of NTPort Library.
    FUNCTION GetNTPortVersion
    ' Set the setting of fast mode
    SUB SetFastMode (BYVAL bOption)
    ' Get the current setting of fast mode
    FUNCTION GetFastMode
    ' Get the base address of LPT port
    FUNCTION GetLPTPortAddress (BYVAL PortNo)
END DECLARE
CONST ERROR_NONE = 0
CONST ERROR_DRIVER = 2
CONST ERROR_SCM_CANT_CONNECT = 9998

Step 4: Try this example...
Code: [Select]
EnablePorts 1, 65535
PRINT GetNTPortVersion
DO
    PRINT Inpb(&H3DA);
    _LIMIT 30
LOOP
Something old... Something new... Something borrowed... Something blue...

Clippy

  • Hero Member
  • *****
  • Posts: 16440
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Direct Port Access
« Reply #1 on: July 09, 2011, 11:59:25 AM »
Tried to reverse my LPT port, and it works with a small delay for the switch over.

Code: [Select]
DEFLNG A-Z
DECLARE LIBRARY "ntport"
' Returns a value indicating the last operation is successful or not.
  FUNCTION GetLastState (s$)
  ' Enable your application to read or write specific ports.
  SUB EnablePorts (BYVAL PortStart, BYVAL PortStop)
  ' Disable your application to read or write specific ports.
  SUB DisablePorts (BYVAL PortStart, BYVAL PortStop)
  ' Returns a value indicates whether the application is running under Windows NT/2000/XP/Server 2003 system.
  FUNCTION IsWinNT
  ' Returns a value indicates whether the application is running under 64-bit Windows system.
  FUNCTION IsWin64
  ' Returns a value from specific ports.
  FUNCTION Inpb ALIAS Inp (BYVAL PortNum)
  FUNCTION Inportb ALIAS Inport (BYVAL PortNum)
  FUNCTION Inpw (BYVAL PortNum)
  FUNCTION InportW (BYVAL PortNum)
  FUNCTION Inpd (BYVAL PortNum)
  FUNCTION InportD (BYVAL PortNum)
  ' Write a value to specific ports.
  SUB Outpb ALIAS Outp (BYVAL PortNum, BYVAL PortData)
  SUB Outportb ALIAS Outport (BYVAL PortNum, BYVAL PortData)
  SUB Outpw (BYVAL PortNum, BYVAL PortData)
  SUB OutportW (BYVAL PortNum, BYVAL PortData)
  SUB Outpd (BYVAL PortNum, BYVAL PortDData)
  SUB OutportD (BYVAL PortNum, BYVAL PortDData)
  ' Set the registration information.
  SUB LicenseInfo (sUserName$, BYVAL DdwKey)
  ' Returns the version of NTPort Library.
  FUNCTION GetNTPortVersion
  ' Set the setting of fast mode
  SUB SetFastMode (BYVAL bOption)
  ' Get the current setting of fast mode
  FUNCTION GetFastMode
  ' Get the base address of LPT port
  FUNCTION GetLPTPortAddress (BYVAL PortNo)
END DECLARE
CONST ERROR_NONE = 0
CONST ERROR_DRIVER = 2
CONST ERROR_SCM_CANT_CONNECT = 9998



'PRINT "NTPort version:"; GetNTPortVersion

LPT = GetLPTPortAddress(1) 'LPT 1

PRINT "LPT base address: &H" + HEX$(LPT)

EnablePorts LPT, LPT + 2

Printer LPT
k$ = INPUT$(1)
CLS
OutportW LPT + 2, 32 'or 44 reverse port bit 5(32)
_DELAY 2
Printer LPT
k$ = INPUT$(1)
CLS
OutportW LPT + 2, 12

Printer LPT

SUB Printer (port)
PRINT "Base:"; Inpb(port)
PRINT "Status:"; Inpb(port + 1)
PRINT "Control:"; Inpb(port + 2)
PRINT GetLastState(s$)
END SUB

What kind of string value does the GetLastState function need?

I can reverse the port using my VB program. it uses INPOUT32.DLL. Perhaps we could use that one for free.

Ted
« Last Edit: July 09, 2011, 10:41:32 PM by Clippy »
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

Clippy

  • Hero Member
  • *****
  • Posts: 16440
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Direct Port Access
« Reply #2 on: July 09, 2011, 12:51:52 PM »
I got this to work and it is SIMPLE AND FREE! You must have a parallel port for this code to work!

Code: [Select]
DECLARE DYNAMIC LIBRARY "inpout32"
  FUNCTION Inp32% (BYVAL PortAddress AS INTEGER)
  SUB Out32 (BYVAL PortAddress AS INTEGER, BYVAL Value AS INTEGER)
END DECLARE

Printer
_DELAY 2

Out32 &H37A, 32
_DELAY 2
Printer

_DELAY 2

Out32 890, 12 'control port is normally set to 12
Out32 888, 227 'any value from 0 to 255

Printer

SUB Printer
basedata% = Inp32%(888)
PRINT "Data:"; basedata%; CHR$(basedata%) 'read or write
PRINT "Status:"; Inp32%(889) 'read only!
PRINT "Control:"; Inp32%(890) 'read or write 32 or over reverses port
PRINT
END SUB


Download this DLL and put it in the QB64 folder:

http://dl.dropbox.com/u/8440706/inpout32.zip

Try it on a COM port and see what happens. You should have a delay when reading ports after a change has been made in some cases. The above code will reverse the port so that there is no data in the base port. That is used to accept data from a device.

Ted
« Last Edit: July 09, 2011, 09:43:42 PM by Clippy »
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

Pete

  • Moderator
  • Hero Member
  • *****
  • Posts: 6240
  • Cuz I sez so varmint!
Re: Direct Port Access
« Reply #3 on: July 09, 2011, 01:48:36 PM »
Two of you working on port? That's okay, I'll go off in the opposite direction, and take a bow! Oh well, I'll stop talking ship now.

Pete  ;D

 - Hey, besides the robotics people, what other uses will this port ability open, if the port opens, of course? I mean we already have TCP/IP, and we can use USB printers, right? So I'm not seeing the possibilities related to this addition.
It's only rocket science; it's not Linux!

Clippy

  • Hero Member
  • *****
  • Posts: 16440
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Direct Port Access
« Reply #4 on: July 09, 2011, 02:12:25 PM »
Actually it started out as a need to read a COM port using INP and OUT for a program that reads the port registers.

The LPT port is easier to read if you have one. COM port registers all return 255 when nothing is connected to them. LPT doesn't.

So we need a COM port user that has something connected to it.

QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

Clippy

  • Hero Member
  • *****
  • Posts: 16440
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Direct Port Access
« Reply #5 on: July 10, 2011, 10:58:34 AM »
Members with a parallel port should try this DLL. It even has a 64 bit version here:

http://qb64.net/wiki/index.php?title=Port_Access_Libraries


Screenshot of demo without a printer on port
« Last Edit: July 10, 2011, 11:04:24 AM by Clippy »
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

Catclaw

  • Jr. Member
  • **
  • Posts: 71
    • Major Media & Entertainment
    • Email
Re: Direct Port Access
« Reply #6 on: March 30, 2012, 06:25:44 PM »
I've got Arduinos connected to my COM port and they work fine with OPEN COM.
Anything u want me to test out?!
“Discovery consists in seeing what everyone else has seen and thinking what no one else has thought.”  --Albert Szent-Gyorgyi

TerryRitchie

  • Hero Member
  • *****
  • Posts: 2264
  • FORMAT C:\ /Q /U /AUTOTEST (How to repair Win8)
    • Email
Re: Direct Port Access
« Reply #7 on: March 30, 2012, 06:28:59 PM »
Quote from: Catclaw on March 30, 2012, 06:25:44 PM
I've got Arduinos connected to my COM port and they work fine with OPEN COM.
Anything u want me to test out?!

Code please! Show some examples of the code.  I have a few Arduinos and can get my hands on an older Sony Vaio laptop with WinXP and COM ports.

Catclaw

  • Jr. Member
  • **
  • Posts: 71
    • Major Media & Entertainment
    • Email
Re: Direct Port Access
« Reply #8 on: March 31, 2012, 05:10:59 AM »
Quote from: TerryRitchie on March 30, 2012, 06:28:59 PM
Quote from: Catclaw on March 30, 2012, 06:25:44 PM
I've got Arduinos connected to my COM port and they work fine with OPEN COM.
Anything u want me to test out?!

Code please! Show some examples of the code.  I have a few Arduinos and can get my hands on an older Sony Vaio laptop with WinXP and COM ports.

Ok... Here comes a temp.logger för SHT-15 temp./hum./dewp. sensor.

QB64 code first:

Code: [Select]
'Screen variables
Xres = 1024
Yres = 768
Cdepth = 32

DesktopX = Xres
DesktopY = Yres

MaxProcess = 255
MaxObjects = 255
NewProcess = 0
NewObject = 0


SensorRes = 100

PlotStep = SensorRes / Yres
ColorStep = SensorRes / 255

i& = _NEWIMAGE(Xres, Yres, Cdepth)
SCREEN i&


'Change this to the Arduino Port settings
port$ = "3"
speed$ = "115200"
DIM bytestr AS STRING * 1
OPEN "COM" + port$ + ":" + speed$ + ",N,8,1,BIN" FOR RANDOM AS #1
DO 'main loop

    IF LOC(1) THEN
        GET #1, , bytestr

        IF bytestr = "|" THEN

            w = 1
            FOR p = 1 TO LEN(sensin$)

                rc$ = MID$(sensin$, p, 1)

                IF rc$ = ":" THEN w = w + 1: rc$ = ""
                word$(w) = word$(w) + rc$

            NEXT p


            LOCATE 1, 1
            COLOR _RGB(255, 0, 0)
            PRINT "Sensor1: Temperature: "; word$(1); "øC ";: COLOR _RGB(0, 255, 0): PRINT "Humidity: "; word$(2); "%";: COLOR _RGB(0, 0, 255): PRINT " Dewpoint: "; word$(3); "øC  ": COLOR _RGB(255, 255, 255)
            COLOR _RGB(255, 0, 0)
            PRINT "Sensor2: Temperature: "; word$(4); "øC ";: COLOR _RGB(0, 255, 0): PRINT "Humidity: "; word$(5); "%";: COLOR _RGB(0, 0, 255): PRINT " Dewpoint: "; word$(6); "øC  ": COLOR _RGB(255, 255, 255)
            tempY = VAL(word$(1)) * 10
            humY = VAL(word$(2)) * 10
            dewY = VAL(word$(3)) * 10

            x = x + 1
            IF x >= Xres THEN
                x = 0
                CLS
                old.x = 0
            END IF
            LINE (old.x, old.tempY)-(x, tempY), _RGB(255, 0, 0)
            LINE (old.x, old.humY)-(x, humY), _RGB(0, 255, 0)
            LINE (old.x, old.dewY)-(x, dewY), _RGB(0, 0, 255)
            old.x = x
            old.tempY = tempY
            old.humY = humY
            old.dewY = dewY

            sensin$ = ""
            FOR n = 1 TO w
                word$(n) = ""
            NEXT n
            bytestr = ""
        END IF

        sensin$ = sensin$ + bytestr

    END IF

    k$ = INKEY$
LOOP UNTIL k$ = CHR$(27)
CLOSE #1: PRINT "Finished!"


Arduino C-code:
Compile and upload the binary to the Arduino device.

Code: [Select]

#include <Sensirion.h>

// Sensor 1
const uint8_t dataPin =  9;              // SHT serial data
const uint8_t sclkPin =  8;              // SHT serial clock

// LED
const uint8_t ledPin  = 13;              // Arduino built-in LED
const uint32_t TRHSTEP   = 500UL;       // Sensor query period
const uint32_t BLINKSTEP =  250UL;       // LED blink period

// INIT
Sensirion sensor1 = Sensirion(dataPin, sclkPin);

uint16_t rawData;

float temperature;
float humidity;
float dewpoint;


byte ledState = 0;
byte measActive = false;
byte measType = TEMP;

unsigned long trhMillis = 0;             // Time interval tracking
unsigned long blinkMillis = 0;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  delay(15);                             // Wait >= 11 ms before first cmd

// First call
  sensor1.measTemp(&rawData);                // sht.meas(TEMP, &rawData, BLOCK)
  temperature = sensor1.calcTemp(rawData);
  sensor1.measHumi(&rawData);                // sht.meas(HUMI, &rawData, BLOCK)
  humidity = sensor1.calcHumi(rawData, temperature);
  dewpoint = sensor1.calcDewpoint(humidity, temperature);

  logData();
}

void loop() {
  unsigned long curMillis = millis();          // Get current time

  // Rapidly blink LED.  Blocking calls take too long to allow this.
  if (curMillis - blinkMillis >= BLINKSTEP) {  // Time to toggle the LED state?
    ledState ^= 1;
    digitalWrite(ledPin, ledState);
    blinkMillis = curMillis;
  }

  sensor1.measTemp(&rawData);                // sht.meas(TEMP, &rawData, BLOCK)
  temperature = sensor1.calcTemp(rawData);
  sensor1.measHumi(&rawData);                // sht.meas(HUMI, &rawData, BLOCK)
  humidity = sensor1.calcHumi(rawData, temperature);
  dewpoint = sensor1.calcDewpoint(humidity, temperature);

// Output data to serial
  logData();

}

void logData() {
  Serial.print(temperature);  Serial.print(":"); 
  Serial.print(humidity);  Serial.print(":"); 
  Serial.print(dewpoint);  Serial.print(":");
 //END OF TRANSMISSION CHARACTER
  Serial.print("|");
}

Sensirion.h (You can also find this in the Arduino lib.)

Code: [Select]
/* ========================================================================== */
/*  Sensirion.h - Library for Sensirion SHT1x & SHT7x family temperature      */
/*    and humidity sensors                                                    */
/*  Created by Markus Schatzl, November 28, 2008                              */
/*  Released into the public domain                                           */
/*                                                                            */
/*  Revised (v1.1) by Carl Jackson, August 4, 2010                            */
/*  Rewritten (v2.0) by Carl Jackson, December 10, 2010                       */
/*    See README.txt file for details                                         */
/* ========================================================================== */


#ifndef Sensirion_h
#define Sensirion_h

#include <stdint.h>

// Enable CRC checking
#define CRC_ENA

// Enable ('1') or disable ('0') internal pullup on DATA line
// Commenting out this #define saves code space but leaves internal pullup
//   state undefined (ie, depends on last bit transmitted)
#define DATA_PU 1

// Clock pulse timing macros
// Lengthening these may assist communication over long wires
#define PULSE_LONG  delayMicroseconds(3)
#define PULSE_SHORT delayMicroseconds(1)

// Useful macros
#define measTemp(result)  meas(TEMP, result, BLOCK)
#define measHumi(result)  meas(HUMI, result, BLOCK)

// User constants
const uint8_t TEMP     =     0;
const uint8_t HUMI     =     1;
const bool    BLOCK    =  true;
const bool    NONBLOCK = false;

// Status register bit definitions
const uint8_t LOW_RES  =  0x01;  // 12-bit Temp / 8-bit RH (vs. 14 / 12)
const uint8_t NORELOAD =  0x02;  // No reload of calibrarion data
const uint8_t HEAT_ON  =  0x04;  // Built-in heater on
const uint8_t BATT_LOW =  0x40;  // VDD < 2.47V

// Function return code definitions
const uint8_t S_Err_NoACK  = 1;  // ACK expected but not received
const uint8_t S_Err_CRC    = 2;  // CRC failure
const uint8_t S_Err_TO     = 3;  // Timeout
const uint8_t S_Meas_Rdy   = 4;  // Measurement ready

class Sensirion
{
  private:
    uint8_t _pinData;
    uint8_t _pinClock;
    uint16_t *_presult;
    uint8_t _stat_reg;
#ifdef CRC_ENA
    uint8_t _crc;
#endif
    uint8_t getResult(uint16_t *result);
    uint8_t putByte(uint8_t value);
    uint8_t getByte(bool ack);
    void startTransmission(void);
    void resetConnection(void);
#ifdef CRC_ENA
    void calcCRC(uint8_t value, uint8_t *crc);
    uint8_t bitrev(uint8_t value);
#endif

  public:
    Sensirion(uint8_t dataPin, uint8_t clockPin);
    uint8_t measure(float *temp, float *humi, float *dew);   
    uint8_t meas(uint8_t cmd, uint16_t *result, bool block);
    uint8_t measRdy(void);
    uint8_t writeSR(uint8_t value);
    uint8_t readSR(uint8_t *result);
    uint8_t reset(void);
    float calcTemp(uint16_t rawData);
    float calcHumi(uint16_t rawData, float temp);
    float calcDewpoint(float humi, float temp);
};

#endif  // #ifndef Sensirion_h

And the Sensirion.cpp file:

Code: [Select]
/* ========================================================================== */
/*  Sensirion.cpp - Library for Sensirion SHT1x & SHT7x family temperature    */
/*    and humidity sensors                                                    */
/*  Created by Markus Schatzl, November 28, 2008                              */
/*  Released into the public domain                                           */
/*                                                                            */
/*  Revised (v1.1) by Carl Jackson, August 4, 2010                            */
/*  Rewritten (v2.0) by Carl Jackson, December 10, 2010                       */
/*    See README.txt file for details                                         */
/* ========================================================================== */


/******************************************************************************
 * Includes
 ******************************************************************************/

extern "C" {
  // AVR LibC Includes
  #include <stddef.h>
  #include <stdint.h>
  #include <math.h>

  // Wiring Core Includes
  #include "WConstants.h"
}

#include "Sensirion.h"


/******************************************************************************
 * Definitions
 ******************************************************************************/

// Sensirion command definitions:      adr command r/w
const uint8_t MEAS_TEMP   = 0x03;   // 000  0001   1
const uint8_t MEAS_HUMI   = 0x05;   // 000  0010   1
const uint8_t STAT_REG_W  = 0x06;   // 000  0011   0
const uint8_t STAT_REG_R  = 0x07;   // 000  0011   1
const uint8_t SOFT_RESET  = 0x1e;   // 000  1111   0

// Status register writable bits
const uint8_t SR_MASK     = 0x07;

// getByte flags
const bool noACK  = false;
const bool ACK    = true;

// Temperature & humidity equation constants
  const float D1  = -40.1;          // for deg C @ 5V
  const float D2h =   0.01;         // for deg C, 14-bit precision
  const float D2l =   0.04;         // for deg C, 12-bit precision

//  const float C1  = -4.0000;        // for V3 sensors
//  const float C2h =  0.0405;        // for V3 sensors, 12-bit precision
//  const float C3h = -2.8000E-6;     // for V3 sensors, 12-bit precision
//  const float C2l =  0.6480;        // for V3 sensors, 8-bit precision
//  const float C3l = -7.2000E-4;     // for V3 sensors, 8-bit precision
  const float C1  = -2.0468;        // for V4 sensors
  const float C2h =  0.0367;        // for V4 sensors, 12-bit precision
  const float C3h = -1.5955E-6;     // for V4 sensors, 12-bit precision
  const float C2l =  0.5872;        // for V4 sensors, 8-bit precision
  const float C3l = -4.0845E-4;     // for V4 sensors, 8-bit precision

  const float T1  =  0.01;          // for V3 and V4 sensors
  const float T2h =  0.00008;       // for V3 and V4 sensors, 12-bit precision
  const float T2l =  0.00128;       // for V3 and V4 sensors, 8-bit precision


/******************************************************************************
 * Constructors
 ******************************************************************************/

Sensirion::Sensirion(uint8_t dataPin, uint8_t clockPin) {
  // Initialize private storage for library functions
  _pinData = dataPin;
  _pinClock = clockPin;
  _presult = NULL;                  // No pending measurement
  _stat_reg = 0x00;                 // Sensor status register default state

  // Initialize CLK signal direction
  // Note: All functions exit with CLK low and DAT in input mode
  pinMode(_pinClock, OUTPUT);

  // Return sensor to default state
  resetConnection();                // Reset communication link with sensor
  putByte(SOFT_RESET);              // Send soft reset command
}


/******************************************************************************
 * User functions
 ******************************************************************************/

// All-in-one (blocking): Returns temperature, humidity, & dewpoint
uint8_t Sensirion::measure(float *temp, float *humi, float *dew) {
  uint16_t rawData;
  uint8_t error;
  if (error = measTemp(&rawData))
    return error;
  *temp = calcTemp(rawData);
  if (error = measHumi(&rawData))
    return error;
  *humi = calcHumi(rawData, *temp);
  *dew = calcDewpoint(*humi, *temp);
  return 0 ;
}

// Initiate measurement.  If blocking, wait for result
uint8_t Sensirion::meas(uint8_t cmd, uint16_t *result, bool block) {
  uint8_t error, i;
#ifdef CRC_ENA
  _crc = bitrev(_stat_reg & SR_MASK);  // Initialize CRC calculation
#endif
  startTransmission();
  if (cmd == TEMP)
    cmd = MEAS_TEMP;
  else
    cmd = MEAS_HUMI;
  if (error = putByte(cmd))
    return error;
#ifdef CRC_ENA
  calcCRC(cmd, &_crc);              // Include command byte in CRC calculation
#endif
  // If non-blocking, save pointer to result and return
  if (!block) {
    _presult = result;
    return 0;
  }
  // Otherwise, wait for measurement to complete with 720ms timeout
  i = 240;
  while (digitalRead(_pinData)) {
    i--;
    if (i == 0)
      return S_Err_TO;              // Error: Timeout
    delay(3);
  }
  error = getResult(result);
  return error;
}

// Check if non-blocking measurement has completed
// Non-zero return indicates complete (with or without error)
uint8_t Sensirion::measRdy(void) {
  uint8_t error = 0;
  if (_presult == NULL)             // Already done?
    return S_Meas_Rdy;
  if (digitalRead(_pinData) != 0)   // Measurement ready yet?
    return 0;
  error = getResult(_presult);
  _presult = NULL;
  if (error)
    return error;                   // Only possible error is S_Err_CRC
  return S_Meas_Rdy;
}

// Get measurement result from sensor (plus CRC, if enabled)
uint8_t Sensirion::getResult(uint16_t *result) {
  uint8_t val;
#ifdef CRC_ENA
  val = getByte(ACK);
  calcCRC(val, &_crc);
  *result = val;
  val = getByte(ACK);
  calcCRC(val, &_crc);
  *result = (*result << 8) | val;
  val = getByte(noACK);
  val = bitrev(val);
  if (val != _crc) {
    *result = 0xFFFF;
    return S_Err_CRC;
  }
#else
  *result = getByte(ACK);
  *result = (*result << 8) | getByte(noACK);
#endif
  return 0;
}

// Write status register
uint8_t Sensirion::writeSR(uint8_t value) {
  uint8_t error;
  value &= SR_MASK;                 // Mask off unwritable bits
  _stat_reg = value;                // Save local copy
  startTransmission();
  if (error = putByte(STAT_REG_W))
    return error;
  return putByte(value);
}

// Read status register
uint8_t Sensirion::readSR(uint8_t *result) {
  uint8_t val;
  uint8_t error = 0;
#ifdef CRC_ENA
  _crc = bitrev(_stat_reg & SR_MASK);  // Initialize CRC calculation
#endif
  startTransmission();
  if (error = putByte(STAT_REG_R)) {
    *result = 0xFF;
    return error;
  }
#ifdef CRC_ENA
  calcCRC(STAT_REG_R, &_crc);       // Include command byte in CRC calculation
  *result = getByte(ACK);
  calcCRC(*result, &_crc);
  val = getByte(noACK);
  val = bitrev(val);
  if (val != _crc) {
    *result = 0xFF;
    error = S_Err_CRC;
  }
#else
  *result = getByte(noACK);
#endif
  return error;
}

// Public reset function
// Note: Soft reset returns sensor status register to default values
uint8_t Sensirion::reset(void) {
  _stat_reg = 0x00;                 // Sensor status register default state
  resetConnection();                // Reset communication link with sensor
  return putByte(SOFT_RESET);       // Send soft reset command & return status
}


/******************************************************************************
 * Sensirion data communication
 ******************************************************************************/

// Write byte to sensor and check for acknowledge
uint8_t Sensirion::putByte(uint8_t value) {
  uint8_t mask, i;
  uint8_t error = 0;
  pinMode(_pinData, OUTPUT);        // Set data line to output mode
  mask = 0x80;                      // Bit mask to transmit MSB first
  for (i = 8; i > 0; i--) {
    digitalWrite(_pinData, value & mask);
    PULSE_SHORT;
    digitalWrite(_pinClock, HIGH);  // Generate clock pulse
    PULSE_LONG;
    digitalWrite(_pinClock, LOW);
    PULSE_SHORT;
    mask >>= 1;                     // Shift mask for next data bit
  }
  pinMode(_pinData, INPUT);         // Return data line to input mode
#ifdef DATA_PU
  digitalWrite(_pinData, DATA_PU);  // Restore internal pullup state
#endif
  digitalWrite(_pinClock, HIGH);    // Clock #9 for ACK
  PULSE_LONG;
  if (digitalRead(_pinData))        // Verify ACK ('0') received from sensor
    error = S_Err_NoACK;
  PULSE_SHORT;
  digitalWrite(_pinClock, LOW);     // Finish with clock in low state
  return error;
}

// Read byte from sensor and send acknowledge if "ack" is true
uint8_t Sensirion::getByte(bool ack) {
  uint8_t i;
  uint8_t result = 0;
  for (i = 8; i > 0; i--) {
    result <<= 1;                   // Shift received bits towards MSB
    digitalWrite(_pinClock, HIGH);  // Generate clock pulse
    PULSE_SHORT;
    result |= digitalRead(_pinData);  // Merge next bit into LSB position
    digitalWrite(_pinClock, LOW);
    PULSE_SHORT;
  }
  pinMode(_pinData, OUTPUT);
  digitalWrite(_pinData, !ack);     // Assert ACK ('0') if ack == 1
  PULSE_SHORT;
  digitalWrite(_pinClock, HIGH);    // Clock #9 for ACK / noACK
  PULSE_LONG;
  digitalWrite(_pinClock, LOW);     // Finish with clock in low state
  PULSE_SHORT;
  pinMode(_pinData, INPUT);         // Return data line to input mode
#ifdef DATA_PU
  digitalWrite(_pinData, DATA_PU);  // Restore internal pullup state
#endif
  return result;
}


/******************************************************************************
 * Sensirion signaling
 ******************************************************************************/

// Generate Sensirion-specific transmission start sequence
// This is where Sensirion does not conform to the I2C standard and is
// the main reason why the AVR TWI hardware support can not be used.
//       _____         ________
// DATA:      |_______|
//           ___     ___
// SCK : ___|   |___|   |______
void Sensirion::startTransmission(void) {
  digitalWrite(_pinData, HIGH);  // Set data register high before turning on
  pinMode(_pinData, OUTPUT);     // output driver (avoid possible low pulse)
  PULSE_SHORT;
  digitalWrite(_pinClock, HIGH);
  PULSE_SHORT;
  digitalWrite(_pinData, LOW);
  PULSE_SHORT;
  digitalWrite(_pinClock, LOW);
  PULSE_LONG;
  digitalWrite(_pinClock, HIGH);
  PULSE_SHORT;
  digitalWrite(_pinData, HIGH);
  PULSE_SHORT;
  digitalWrite(_pinClock, LOW);
  PULSE_SHORT;
  // Unnecessary here since putByte always follows startTransmission
//  pinMode(_pinData, INPUT);
}

// Communication link reset
// At least 9 SCK cycles with DATA=1, followed by transmission start sequence
//      ______________________________________________________         ________
// DATA:                                                      |_______|
//          _    _    _    _    _    _    _    _    _        ___     ___
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______|   |___|   |______
void Sensirion::resetConnection(void) {
  uint8_t i;
  digitalWrite(_pinData, HIGH);  // Set data register high before turning on
  pinMode(_pinData, OUTPUT);     // output driver (avoid possible low pulse)
  PULSE_LONG;
  for (i = 0; i < 9; i++) {
    digitalWrite(_pinClock, HIGH);
    PULSE_LONG;
    digitalWrite(_pinClock, LOW);
    PULSE_LONG;
  }
  startTransmission();
}


/******************************************************************************
 * Helper Functions
 ******************************************************************************/

// Calculates temperature in degrees C from raw sensor data
float Sensirion::calcTemp(uint16_t rawData) {
  if (_stat_reg & LOW_RES)
    return D1 + D2l * (float) rawData;
  else
    return D1 + D2h * (float) rawData;
}

// Calculates relative humidity from raw sensor data
//   (with temperature compensation)
float Sensirion::calcHumi(uint16_t rawData, float temp) {
  float humi;
  if (_stat_reg & LOW_RES) {
    humi = C1 + C2l * rawData + C3l * rawData * rawData;
    humi = (temp - 25.0) * (T1 + T2l * rawData) + humi;
  } else {
    humi = C1 + C2h * rawData + C3h * rawData * rawData;
    humi = (temp - 25.0) * (T1 + T2h * rawData) + humi;
  }
  if (humi > 100.0) humi = 100.0;
  if (humi < 0.1) humi = 0.1;
  return humi;
}

// Calculates dew point in degrees C
float Sensirion::calcDewpoint(float humi, float temp) {
  float k;
  k = log(humi/100) + (17.62 * temp) / (243.12 + temp);
  return 243.12 * k / (17.62 - k);
}

#ifdef CRC_ENA
// Calculate CRC for a single byte
void Sensirion::calcCRC(uint8_t value, uint8_t *crc) {
  const uint8_t POLY = 0x31;   // Polynomial: x**8 + x**5 + x**4 + 1
  uint8_t i;
  *crc ^= value;
  for (i = 8; i > 0; i--) {
    if (*crc & 0x80)
      *crc = (*crc << 1) ^ POLY;
    else
      *crc = (*crc << 1);
  }
}

// Bit-reverse a byte (for CRC calculations)
uint8_t Sensirion::bitrev(uint8_t value) {
  uint8_t i;
  uint8_t result = 0;
  for (i = 8; i > 0; i--) {
    result = (result << 1) | (value & 0x01);
    value >>= 1;
  }
  return result;
}
#endif
“Discovery consists in seeing what everyone else has seen and thinking what no one else has thought.”  --Albert Szent-Gyorgyi

  • Print