element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Test & Tools
  • Technologies
  • More
Test & Tools
Blog SCPI on a Linux Board - Part 2b: PiFace Digital C++ programming
  • Blog
  • Forum
  • Documents
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Test & Tools to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 22 Jun 2018 5:09 PM Date Created
  • Views 3561 views
  • Likes 6 likes
  • Comments 18 comments
  • piface_digital
  • pi
  • raspberry_pi
  • piface_digital_2
  • c++
  • piface
Related
Recommended

SCPI on a Linux Board - Part 2b: PiFace Digital C++ programming

Jan Cumps
Jan Cumps
22 Jun 2018

I'm building a SCPI electronics lab instrument for Linux.

This post is an object oriented one: porting the PiFace Digital API to C++.

image

If you aren't interested in object oriented design, you may want to walk away from this blog.

 

 

API and Abstraction

 

I've used exactly the same level of abstraction as the PiFace official procedural API.

The OO API you talk to is the one that knows the PiFace Digital hat inputs and outputs.

That API depends on a separate more low level OO API, the one of the SPI port expander used on the hat (MCP23S17).

 

I've turned each of these into a class. The PiFace Digital functions are served by the PifaceDigital class.

The mcp23s17 class supports the SPI port expander's functions.

 

What's different is that the in the original PiFace API, all functions are static. They have no context.

In my OO API, both objects are aware of the hardware address of the PiFace Digital hat (more subtile: the mcp23s17 object knows, and the PifaceDigital object is hard linked to that object).

For the programmer, the difference is that, in the traditional API, you pass the hardware address of the board to each function.

If you have multiple hats, you pass the correct hardware address whenever you want to address a particular one.

In my OO API, you create a PifaceDigital object for each hat. In the constructor, you give the hardware address so that the object knows which hat it represents.

From then on, to talk to a particular hat, you use the object that presents that one. No need to deal with addresses anymore.

 

 

image

 

I like it when my classes represent real world things. In this case the match is 100%.

 

image

image source: the icon of one of Robert Peter Oakes' youtube videos.

 

The hat (purple rectangle) is the PifaceDigital class. The SPI port expander (yellow rectangle) is the mcp23s17 class.

Just like in my API, a hardware hat owns exactly one port expander, and the hat routes the traffic to-and-from it.

Because the physical hat can't work without one single SPI expander (the mcp23s17s IC is soldered on it, you can't get more entangled than that) , I construct them together in the API and destruct them together too.

They share the same lifespan.

 

C++ Code

 

I attached the ARM DS-5 project to this post. Check that out for the complete code. This section contains some highlights.

 

The PifaceDigital class

 

class PifaceDigital {
public:
  //constants declarations
  static const unsigned int OUTPUT; // GPIOA
  static const unsigned int INPUT; // GPIOB
  // * @param hw_addr The hardware address (configure with jumpers: JP1 and JP2).
  PifaceDigital(uint8_t hwAddr);
  virtual ~PifaceDigital();


  void open();
  void openNoinit();
  void close();
  uint8_t readReg(uint8_t reg);
  void writeReg(uint8_t data, uint8_t reg);
  uint8_t readBit(uint8_t bit_num, uint8_t reg);
  void writeBit(uint8_t data, uint8_t bitNum, uint8_t reg);
  int enableInterrupts(void);
  int disableInterrupts(void);
  int waitForInput(uint8_t *data, int timeout);

protected:
  mcp23s17 *_mcp23s17;
  /* MCP23S17 SPI file descriptor. All PiFace Digitals are connected to
   * the same SPI bus, only need 1 fd. Keeping this hiden from novice
   * PiFace Digital users.
   * If you want to make raw SPI transactions to the device then try using the
   * mcp23s17 class directly.
   */
  static int _mcp23s17_fd;


private:
  // PiFace Digital is always at /dev/spidev0.0
  static const int bus;
  static const int chip_select;
  static int pfd_count; // number of PiFace Digitals


};

 

You can see that the class does not store the hardware address. Instead, it dynamically creates an mcp23s17 object and passes the hardware address to that.

Because our class only has one mcp23s17 object, the right commands will go to the right hat.

The connection between a PifaceDigital object and its mcp23s17 is made at construction time:

 

PifaceDigital::PifaceDigital(uint8_t hwAddr) {
_mcp23s17 = new mcp23s17(hwAddr);
}

 

From that moment on (and there is no earlier moment in this object's life cycle), the two objects are bound together.

The PifaceDigital object can talk to only one mcp23s17 object.

 

The mcp23s17 class

 

class mcp23s17 {
public:
  //constants declarations
  static const unsigned int WRITE_CMD;
  static const unsigned int READ_CMD;

  // Register addresses
  static const unsigned int IODIRA;  // I/O direction A
  static const unsigned int IODIRB;  // I/O direction B
// ... many more, removed in this blog
  static const unsigned int GPIO_INTERRUPT_PIN;


  mcp23s17(uint8_t hw_addr);
  virtual ~mcp23s17();
  virtual int open(int bus, int chipSelect);
  virtual uint8_t readReg(uint8_t reg, int fd);
  virtual void writeReg(uint8_t data, uint8_t reg, int fd);
  virtual uint8_t readBit(uint8_t bitNum, uint8_t reg, int fd);
  virtual void writeBit(uint8_t data, uint8_t bitNum, uint8_t reg, int fd);
  virtual int enableInterrupts();
  virtual int disableInterrupts();
  virtual int waitForInterrupt(int timeout);


private:
  static const uint8_t spi_mode;
// ... many more, removed in this blog

  virtual uint8_t getSpiControlByte(uint8_t rwCmd, uint8_t hwAddr);
  virtual int initEpoll(void);

  uint8_t _hw_addr;

};

 

In this class, you can see that the hardware address is stored for later use. Each mcp23s17 object will talk to the hat with the address that it's bound to.

Under the hood, the class will use the SPI mechanism of linux to talk to that chip.

 

uint8_t PifaceDigital::readReg(uint8_t reg) {
    return _mcp23s17->readReg(reg, _mcp23s17_fd);
}

 

Check the attached zip for more info on the implementation.

 

For the programmer

 

The firmware developer only has to deal with the PifaceDigital class. He never talks to the mcp23s17.

Te PifaceDigital object will ideal with the downstream relation (see previous section).

 

Talking to a hat is easy. Create it, open it and then talk to it.

When done, dispose of the object.

 

    PifaceDigital *pf = new PifaceDigital(0);


    pf->open();
    pf->writeBit(1, 0, PifaceDigital::OUTPUT);
    pf->close();

    delete pf;

 

In the above exampe, the program creates an object to represent (i.e.: binds to the hardware of) a PiFace Digital with address 0.

It then opens (initialises)  the communication, sends a command (sets output #0 to high), then closes the communication.

It then disposes of the object. The program doesn't hold any resources to that particular hat anymore.

 

Under the hood, the new() command also creates an mcp23s17 object and binds it to our pf object.

All commands that we send to pf are relayed to its mcp23s17 object, until we're finihed.

Then, when we destruct pf, the mcp23s17 object gets destructed too.

 

 

 

not 100% pure C++

 

I have done half the exercise. The API from Piface is converted to objects, but in the implementation I'm still using many C constructs.

The file operations jump out. Instead of using C++ streams, I'm using the typical C file handler and functions.

The stdout and stderr communication has switched to C++ streams.

 

Some functions that aren't depending on the hardware address of the PiFace digital board could be turned into static ones.

I'm using C arrays instead of the STL containers. I am an STL fan since forever. I get a lot of flack for that.

fixed: I modified the original underscore_type API calls to camelCase ones, but didn't do the parameters.

I did not fully analyse what functions should be virtual or not.

I did not make abstract classes to define the API. There are arguments for doing that.

I could have kept the original procedural API and just build a wrapper around it.

 

I've fully encapsulated the SPI device file handler. The original library allows power users to get the handle and manipulate it.

I've closed that gate. If a power user wants to have this functionality, she can use the mcp23s17 class. That one is low level and gives explicit access to the device file handler.

 

fixed: Although I boast about using multiple hats, the current OO implementation doesn't handle sharing the SPI resource well in a single program. I need to my edit open() and close() methods (in particular because I broke the integrity althoug it looks ok-ish when viewing the code). It's all in my hands. The original API handles it well.

 

Feel free to criticise my approach. What would you change if you were to turn the API into objects?

 

 

 

related blog
SCPI on a Linux Board - Letter of Intent

SCPI on a Linux Board - Part 1: Proof of Concept

SCPI on a Linux Board - Part 2a: PiFace Digital C programming

SCPI on a Linux Board - Part 2b: PiFace Digital C++ programming
SCPI on a Linux Board - Part 3: TCP/IP Socket C++ programming
SCPI on a Linux Board - Part 4: TCP/IP SCPI and Instrument Service
SCPI on a Linux Board - Part 4b: TCP/IP SCPI and Instrument Service 100% Working
SCPI on a Linux Board - Part 5a: LabVIEW Driver for LAB Switch: Open, Close and Switch functions
Attachments:
piface_digital_cpp.zip
  • Sign in to reply

Top Comments

  • jomoenginer
    jomoenginer over 7 years ago +2
    I applaud your use of C++ in your project. There still seems to be some hesitation to move to more C++ in Embedded development which Michael Barr's recent study has shown indicating an actual decline in…
  • DAB
    DAB over 7 years ago +2
    While there are some merits to OO design, it does come with a lot of code overhead. In most embedded designs you are looking for efficiency in every aspect. Code bloat you do not need, which is why C has…
  • DAB
    DAB over 7 years ago in reply to Jan Cumps +2
    Agreed, I was just making the point of what works best for me. If you find OO intuitive and useful, by all means do so. When I created a true OO application, the OO people did not recognize it as it did…
Parents
  • jomoenginer
    jomoenginer over 7 years ago

    I applaud your use of C++ in your project. There still seems to be some hesitation to move to more C++ in Embedded development which Michael Barr's recent study has shown indicating an actual decline in the use of C++ in Embedded dev. It does seem to pop up quite a bit in interview questions though.

     

    However, with the code you presented, I wonder if it might be best served to use the mcp23s17 class as a Base class for PifaceDigital and just inherent the methods from mcp23s17.  Also, your member variables are left as public meaning they can be accessed by anyone creating an object of PifaceDigital.  Is there a reason why you did not hide these and implement getter and setters for these variables? Moving into C++11 and above land with constexp, auto and templates might be interesting.

     

    Thanks for sharing this.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • jomoenginer
    jomoenginer over 7 years ago

    I applaud your use of C++ in your project. There still seems to be some hesitation to move to more C++ in Embedded development which Michael Barr's recent study has shown indicating an actual decline in the use of C++ in Embedded dev. It does seem to pop up quite a bit in interview questions though.

     

    However, with the code you presented, I wonder if it might be best served to use the mcp23s17 class as a Base class for PifaceDigital and just inherent the methods from mcp23s17.  Also, your member variables are left as public meaning they can be accessed by anyone creating an object of PifaceDigital.  Is there a reason why you did not hide these and implement getter and setters for these variables? Moving into C++11 and above land with constexp, auto and templates might be interesting.

     

    Thanks for sharing this.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • Jan Cumps
    Jan Cumps over 7 years ago in reply to jomoenginer

    jomoenginer  wrote:

     

    I wonder if it might be best served to use the mcp23s17 class as a Base class for PifaceDigital

    I have thought about that. But the board is not a descendant/refinement of the IC. It uses the IC. So I chose that "USE/OWN" paradigm.

    I think the hat uses the mcpxx and calls  its behaviour.. It doesn't inherit and expand its behaviour (it is not a refinement of that chip).

    Coincidentally, because this is the only smart chip on the hat, the APIs are very similar.

    If there was more than one smart chip on the hat, the inherit relation would not hold.

     

    jomoenginer  wrote:

     

    Is there a reason why you did not hide these and implement getter and setters for these variables

    I believe I don't have any getters and setters. All variables are private in the .h and I've not exposed them in the API.

    All other thingies (members) are non-mutable constants (in a C++ paradigm, so no #define, but declaration in the public part of the header, and assigned in the not-public cpp file). Because they aren't changeable at runtime, I do not need getters and setters.

    I've been wrong before though, so any pointers to encapsulation breaches are welcome.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube