0
\$\begingroup\$

I am currently working on the design of a handheld instrument for a specific biomedical application (from which I cannot disclose much information due to IP-related issues, sadly). I have a working version on a custom PCB and I am in the middle of massive firmware and hardware improvements for the next revision.

The microcontroller I use is a Teensy 3.2 (clocked at 120 MHz) which is in charge of multiple tasks, including (amongst others):

  1. Measuring a specific frequency from an oscillator via Pin 13 (using the FreqCount library from PJRC),
  2. Reading the user's input from a couple of push buttons,
  3. Writing to an I2C OLED screen (using the u8g2 library),
  4. Reading values from an I2C sensor (perhaps more in following revisions),
  5. Controlling an analog multiplexer,
  6. Send formatted values in the form of an alphanumeric string through the serial port to a host PC.

It might me relevant to note that the I2C is clocked at 400 kHz, restricted by the slowest of the sensors.

The blocks diagram from this system may be represented as follows: original design

During my initial evaluations noticed that writing to the OLED screen consumes considerable time (as expected, being an I2C device) which affects the overall performance of the instrument, mainly on the sample rate of my acquisitions from the MUX, frequency, and peripheral sensors. More importantly, I have noticed that when I disable all "writes" to the OLED, the multiplexing is also favorably affected, reducing the jitter on the output signal from the MUX and even the accuracy of the frequency measurements from Pin 13.

As part of my initial conclusions I realized that using an I2C OLED screen is NOT the best option and the new version will use an SPI module, however, I have considered the option of delegating the control of the OLED to a second microcontroller (say an ATMEGA328P) and establish a communication protocol between the Teensy and the OLED MCU via UART. This kind of approach is commonly used in some instruments like bench multimeters and similar ones, to reduce the load on the main controller unit.

This new proposal would look like this:

new overengineered idea

Any ideas? Would you overengineer the design this way?

I am totally aware that you might require more information regarding my project, however, I hope I have covered most of the important aspects of my current solution. I believe that my system has plenty of room for optimizations.

\$\endgroup\$
22
  • 3
    \$\begingroup\$ It's not overengineering if the original design doesn't meet performance specs. \$\endgroup\$ Feb 14, 2023 at 19:26
  • 4
    \$\begingroup\$ But if you switch to a more suitable MCU, or rewire the hardware differently, or rewrite the software to do things differently, you might not need a second MCU. Using two MCUs is just more difficult in many ways like firmware updates, and since it is a medical application, you likely need to document things detailedly. As we can't see your software, and you likely for IP reasons will refuse to show it to us, we can't say much what you should or should not do. I am surprised that you are using off the shelf hardware and off the shelf software libraries, due to legal issues. \$\endgroup\$
    – Justme
    Feb 14, 2023 at 19:34
  • 2
    \$\begingroup\$ I don't disagree that it's often possible to get data to a SPI display faster than it would be to get similar data to a similar I2C display, but unless the display refresh rate is something you're trying to improve then this is not your problem. Your problem is if you're having your code sit and do nothing while it's sending data to the display instead of having it do other things and offload the process of updating the display to interrupts and/or a separate task. \$\endgroup\$
    – brhans
    Feb 14, 2023 at 19:47
  • 2
    \$\begingroup\$ I don't know if it would be better for your project to get an expensive prototype done sooner, or to spend an extra 50 hours writing better firmware to allow you to use fewer and less expensive parts. ¯\_(ツ)_/¯ It's your project, but it's not the Teensy's fault if you choose to have it spinning its wheels in a while(I2CDataNotSent()) { // do nothing} loop... \$\endgroup\$
    – brhans
    Feb 14, 2023 at 19:53
  • 2
    \$\begingroup\$ The teensy3.2 should have more than enough performance to do the job - you need to run the display update as a separate task and have it not share the i2c bus with other devices. Use freeRtos or split the display update into smaller chunks and interleave the display update with your other tasks. \$\endgroup\$
    – Kartman
    Feb 14, 2023 at 23:39

2 Answers 2

3
\$\begingroup\$

This sounds like a pretty standard embedded system with no special real-time requirements at all. I don't know why your program is slow, but it sounds very likely to be related to bad software engineering practices such as busy-waits/busy-wait delays.

Also 400kHz is pretty dang fast. And you have a Cortex M4 at 120MHz and from what I can tell, it has basically nothing to do except a few sensor reads and updating a display. It's complete overkill to use a M4 for that.

So step 1 is to have a design review of your code, because from what I can tell with the little info given, this is 100% a programming problem caused by badly written code.

Step 2 is to gain awareness of a technology called Direct Memory Access (DMA), invented somewhere in the 1980s. It allows hardware peripherals to directly access memory without the CPU having to relay the data in between, so it is perfect for things like updating a display. DMA exists on most Cortex M parts out there.

I have considered the option of delegating the control of the OLED to a second microcontroller (say an ATMEGA328P) and establish a communication protocol between the Teensy and the OLED MCU via UART. This kind of approach is commonly used in some instruments like bench multimeters and similar ones, to reduce the load on the main controller unit.

Sorry but this is nonsense - it isn't over-engineering, it is bad engineering. Carefully designed software means you could likely run this whole program even on a lousy ATmega328 in place of the Teensy. I've written far more complex applications with far more things to do, using similar low performance 8-bitters and no DMA. Luckily, we don't have to use old 8-bitters any longer. Any Cortex M would probably do, just check what DMA peripherals it supports.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Ouch. I agree with @Lundin on several points, but still --- ouch! \$\endgroup\$ Feb 15, 2023 at 15:37
  • \$\begingroup\$ @Kripacharya Why the ouch? Sugar coating it ain't gonna make them a better engineer. Designing embedded system software is complex and it's largely learn by doing. Anyone with less than 5 years full-time experience is very unlikely to get everything right no matter how brilliant a programmer they are. Just as someone with less than 5 years of RF design experience simply isn't going to make a flawless radio transceiver. \$\endgroup\$
    – Lundin
    Feb 15, 2023 at 15:43
  • \$\begingroup\$ @Kripacharya ouch indeed...haha \$\endgroup\$ Feb 16, 2023 at 13:40
  • \$\begingroup\$ @Lundin thanks for your feedback. I will be redesigning many parts of my firmware for sure. However, I do not fully agree with this alternative idea being nonsense. Ask Fluke/Agilent/Keithley and many other engineers why they keep taking this nonsense approach on their instruments and/or other equipment. Just to clarify, while I might not be such an expert, as you may surely be, I am confident that my solution is good enough with plenty of room for improvement. You cannot expect to have a fully-fledged state-of-the-art device during the POC development stage. \$\endgroup\$ Feb 16, 2023 at 13:48
  • \$\begingroup\$ @DanielMelendrez There's a difference between this and having decentralized CPUs for other design reasons. You could for example have optional parts on the PCB or you want redundancy for safety reasons etc. However, you should not offload work from a Cortex M to some AVR because you somehow assume the Cortex can't handle it. And the scenario you describe with some serial bus causing lots of performance loss due to repeated interrupts could be solved with DMA. You could also consider custom boards instead of these pre-made ones, it is not that hard or expensive to get one made. \$\endgroup\$
    – Lundin
    Feb 16, 2023 at 16:13
2
\$\begingroup\$

I would not do this because it shouldn't be necessary. Instead I would use asynchronous I2C transfers.

  1. Ensure you are using an MCU with a built-in I2C interface circuit/module/peripheral/feature (words are hard). If it only works on certain pins, ensure the I2C bus is connected to those pins. If your MCU doesn't have one, change to a different one that does.

    The Teensy 3.2 has this feature. No need to change.

  2. The I2C module will have a FIFO buffer that can store a few bytes. The buffer works as fast as the MCU - you don't have to wait for it. Write the first few bytes you want to send into the buffer - as many as will fit - then tell it to start sending.

  3. The I2C module will have an interrupt that says it's done sending. When you get this interrupt, send the next few bytes and repeat.

Then the I2C module sends the bits while your MCU doesn't have to sit around and twiddle its thumbs.

I see you have a sensor on the same I2C bus. You should be able to interleave talking to the OLED, and talking to the sensor. Just make it so if your interrupt handler sees that it's time to talk to the sensor, it sends/receives the sensor bytes and leaves the OLED bytes in the buffer for next time, i.e. a priority system.

If this is still too slow you could consider having two separate busses. The Teensy 3.2 does have two separate copies of the I2C bus module so you could use one for each. Even without this, I understand the sensor does not require much communication so you could bit-bang the sensor bus (i.e. simulate I2C with GPIOs) and still have enough performance.

If you are using the Teensy libraries to do I2C communication you might need to bypass the libraries and use the registers and interrupts directly.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Bit banging I2C can get tougher than SPI because any device can clock stretch \$\endgroup\$ Feb 14, 2023 at 22:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.