element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Achievement Levels
    • Benefits of Membership
    • Feedback and Support
    • Members Area
    • Personal Blogs
    • What's New on element14
  • Learn
    Learn
    • eBooks
    • Learning Center
    • Learning Groups
    • STEM Academy
    • Webinars, Training and Events
  • Technologies
    Technologies
    • 3D Printing
    • Experts & Guidance
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Arduino Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Project Groups
    • Raspberry Pi Projects
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Or 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
Test & Tools
  • Technologies
  • More
Test & Tools
Blog SCPI on a Linux Board - Part 2b: PiFace Digital C++ programming
  • Blog
  • Forum
  • Documents
  • Polls
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Test & Tools requires membership for participation - click to join
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 22 Jun 2018 5:09 PM Date Created
  • Views 1634 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 peteroakes' 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 5 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 5 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 5 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…
  • jomoenginer
    jomoenginer over 5 years ago in reply to Jan Cumps

    Understood. 

    Thanks for the clarification.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago

    For the OO lovers, I've added the next subject: using C++ streams to handle TCP/IP traffic.

    I'll use them to get commands and send replies over a network port.

     

     

    SCPI on a Linux Board - Part 3: TCP/IP Socket C++ programming

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to jomoenginer

    jomoenginer  wrote:

     

    ... the use of 'virtual' in your class implies that it is a Base class and would have a Derived class associated with it. ...

    It allows the user to derive from the class. You can use both APIs (PifaceDigital and mcpxxx) as is, or derive from them.

    I could turn some of them to non-virtual, but haven't decided yet for what functions it makes sense to restrict override ...

     

    One reason to create a derived class could be to expose the internal linux SPI device file handle, an access that I did not take over (read: took over but then removed because that's a broad access to the internal working) from the original library.

    I'm turning the handle to that stream to protected, so that a derived class can decide to give access to it, and the advanced functionality of the original lib becomes available.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • jomoenginer
    jomoenginer over 5 years ago in reply to Jan Cumps

    A Pure Virtual function/method, is required to be overridden where as with a non pure virtual function it is an option.  However, the use of 'virtual' in your class implies that it is a Base class and would have a Derived class associated with it.  I was under the impress it was a no no to directly instantiate an Abstract class, one with virtual functions, and thus requires a derived class in order to use it.  I could be wrong though.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB over 5 years ago in reply to Jan Cumps

    That is better than it once was.

    As has been said, they have done a much better job optimizing the compilers since my days of trying to work with C++.

     

    It is not that I do not appreciate the idea of OO, it just came along after I was very proficient in structured methods. For my work structured was easier for me to use because of all the experience I had with it.

    In my case, it was better to use a method that I understood well than try to learn the latest methods which at the time were still evolving. Now that it is mature OO is probably more intuitive for most people to use.

     

    DAB

    • 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 © 2023 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