12 x Analogue Input Channels

This is an early draft of a 12 x Analogue Input Channel board. 12 single ended inputs and 8 delta channels – all fully galvanic issolated, 24 bit sample precition and 64Kbps sample rate.

The board uses the same box as 3xCAN, but goes wall to wall – each M12-6 Female connector hold 3 analogue channels. The left side of the board is the same as used on 3xCAn so we re-use HW and all infrastructure SW. I have yet to test a bit on 3xCAN before I order this PCB – and as always on this early phase – nothing is written in stone.

I can support 8 x 0-20mA loops, or 12 0-10V Analogue sensors etc, but I need to figure out a few details on how to combine these and make the SW changeable, so this board will probably change a few times – right now it is perfect for 3 – axis vibration sensors etc.

3xCAN Project Box

What you see is my dev box with the new front and back panel attached. This has so far worked out very well. My solder station broke before I could assemble Ethernet components, but I have a new one next to me. As always – with a project this complex it is lots of mistakes to correct, but that is engineering for you.

This test kit is powered by USB – I have a separate Jumper to allow for that. You also see all three CAN ports connected to the same output for testing + I have attached a partly assembled M12 module just to test sub-modules. One of the things that needs to be changed are leds – I have plenty of leds, but I need them visible with the box closed. I have a few ideas, but I will get into that later.

One word about FreeCAD: I have so far done all my 3D work in FreeCAD, but I must admit that it has a lot of bugs and limitations making it very time-consuming to work with. It also becomes slow as the models grow, so I need to consider if FreeCAD is worth the time. That said I get the job done.

Just the back-side of the 3xCAN box this time showing holes for Ethernet, SD-Card, USB and Power. The power connector is not 100% yet as I lack components. Getting a 3D printed panel adapted is a lot of try & fail, but I got it right in the end.

The left side have room for the 6th (and 7th) DSUB-9 connector + I need to consider where to take out some leds to indicate status on the content. This back side will be used on a lot of modules. I am throwing the word “module” around a lot, but we have the entire box as a module and we can hav sub-modules inside each box as well.

3xCAN – First Prototype Box

I finally assembled my first prototype box using a 3D printed panel. The first change is that the front had to be reduced to 5 x DSUB9 connectors because I needed extra space between them for the connectors in front – even 5 might be ambigious, but we will see then the canles w/connectors arrive. In this case I just removed the aluminium panel and replaced it with a 3D printed plastic one for prototyping – I will get the aluminium mounted later. I might be able to fit the 6th DSUB9 on the back.

 

This next picture is just a mock-up as I added 4 empty boxes to illustrate a stack. In this case you connect all 5 using the CAN or Ethernet on the back and you have five boxes with different IO content on the front.

This can act as a system on your desctop, a PLC in a cabinet or a mobile PLC distributed on a vehicle. You can add as many modules as you need – no, real limitation and they can form distributed nodes to make the best use of available space.

CAN Pinout

The D-Sub 9 to right contains the pinout for CAN. And As I use a flat-cable to connect to the header to Left I also did the mistake of assuming that Pin 2 on the header is pin 2 on the D-Sub 9 so my pinout is completely messed up.

Looking at the left header the actual D-Sub 9 pin sequence is 1-6-2-7-3-8-4-9-5 due to the nature of a flat cable. The error is easy to correct and have no consequence for this prototype as I need a new PCB regardless. You see the error animated below as I attached correct pin numbers for the cable to match 1:1.

It is an annoying mistake to make, but I always expect some errors on my first PCB’s of some complexity. The alternative would be to double check forever using hirendous hours and still be left with errors – in my opinion it is better to get on with it, produce PCB, correct the errors and get a new PCB.

CPU Load

CPU load in out case is a definition – if I say that a cycle time of 100yS (0,00001 Sec) is 100% CPU Load then the data above indicate we have 10% CPU Load – if we say 1ms is 100% we are at 1% CPU load. We use in average 10 yS to execute a cycle where we check all tasks and max is 25-27 yS. This is with my own barebone linear RTOS.

I currently use Putty as terminal on the PC to communicate with CLI on the board. It also allows me to Print tekst directly from the embedded application. As USB is 12Mbps it is pretty fast – I can print sufficiently fast to basically get my PC stressed out printing. I will create a specialized terminal i BSA later so I can log printing as well and have several channels.

CLI is in this case connected through USB, but I will add TCP a bit later. The command set is rather simple, but I can read and write single variables as well as tables. A bit later I will add FW download this way as well.

The reason I do these stats is because I need to see how reliable I can expect to execute tasks, and the numbers above indicate 10yS with some cycles up in 30yS, meaning 30yS or lets say 100yS is a very reliable call frequency. I will double check my math a bit because I would like to understand why some cycles take 27yS. It is two reasons possible – (1) the tasks uses a bit of extra time on one cycle, or (2) I have interrupts influencing the cycle. In this case I have an USB interrupt that I suspect, but I will prove that. I don’t really need this level of timing schemes on this CAN Hub, but I like to understand the numbers I get. To verify this I will snapshot the cycle data then I detect I 27yS cycle and analyze that cycle. I have start/stop times in cycle and every single task so it’s not that difficult.

Understanding the performance I have is also key to design and understanding how design will affect the total performance because I still have a lot of software to add/activate.

Just a footnote that the extra long cycle is interrupts feeding USB. Also as I switched to Release the average cycle dropped to 4yS.

CAN Testing

The idea is to have 1:1 between connectors and D-SUB9, but for testing I just connected all three on the same cable with a D-SUB9 for Laptop connection. This is a stanadard 10x flat cable. Pin 10 is shield if needed. I have not connected 5V and GND on D-Sub9, but I will in the next version. Some people are fanatic about having Gnd because they do not have proper galvanic isolation. But, also 5V is usable in case you have a CAN device that needs to be powered through this cable. The 5V PSU supply 2A and the main board uses ca 200mA, so we have plenty to share within reason.

Starting testing I only switch on 120Ohm on one port at top and I start with a decent low bitrate – lets get basic CAN working before we start testing higher speeds and FD. These CAN FD ports should be capable of 12Mbps, but lets test that before we start braging πŸ™‚

The next piccture is an early mock-up of a possible extra CAN port. You connect this to module1,2 or 3 and you get an extra port. The box have room for these on the other side so the board will be upside down. The height is to fit into the box that will easely fit 3 of these. I already have M12 modules that can be tested, but to develop with that mistake of a SWD I used is hard. Also on the module below is a new, smaller SPI Flash – I am getting braver and starting to use components with connection on the sides – actually found them very easy to hand-solder to my surprice. The MCU in this case is STM32H503CB due to it’s low cost and support of CAN.

For 3 extra CAN it would be better with a scaled down version of the main board with connecting Link to Module. I was thinking of just not mounting the PSU, USB, SD-Card and Ethernet and use the same board, but it might be that a smaller board designet to stand upside down is better – lets see. One argument is that I hesitate starting on a 2nd MCU right now.

Needless to say – this module board can add anything we want – extra CAN port is only an example. Digital IO, Serial ports, analogue IO etc – you name it.

 

3xCAN – almost there

Got more parts today so I finally have connectors, USB and SD-Card mounted – lack Ethernet yet + discovered that the PSRAM has a more narrow footprint than I used. Not sure I am bothered with mounting PSRAM on this version because strapping another nCS will be difficult – one nice thing with narrow footprint is that PSRAM and FRAM will share the same size. SPI Memory use a JEDEC signature allowing you to detect what is mounted. I will have the LAN in a few days and by then have a complete board for testing/developing.

USB is working and so is power through USB meaning I can just power the board through my laptop. I must admit that soldering have been much faster than I expected, but I am serious on getting the PnP running next.

I also got the boxes I want to use and realize it will be tight with two boards and those connectors – I might need to use right-angle versions to get space and move the connectors a bit to left – we will see.

Dictionary

As you build a modular system you will implement a lot of variables in each module that you might want to change as part of configuration or see as they are sensor values. To do this I create a separate module called a “Dictionary” that is illustrated below.

The dictionary itself is a large Index table over variables and data you want to interface with a set of standard functionality. The primary interface is CLI over USB or Ehernet – CLI (Command Line Interface) is a text based interface allowing a serial console or TCP client to be your actual interface. Through CLI you can list, set or read all variables in the Dictionary.

Most variables are real-time and can be read/write at you discression.

A protocol like Modbus or CANopen can have their own map on top of the dictionary.

Some dictionary values can be tagged as “persistant” meaning they will be stored on SPI Flash then changed and read back as the system start. This includes Bitrate setting, baudrate settings, switching tables, IP/MAC settings etc – config we want the system to remember.

Data Integrity is also supported in the dictionary. Some protocols like CANopen and Modbus have the capbability to write single variables leaving an Entity (a collection of variables) with broken integrity – so we can protect those and ensure that the module only receive a complete entity.

Sensor arrays/filters are another feature. Some sensors sample faster than we are interested in so we offer techniques to store windows and filter values here rather than forcing every sensor to implement it separately.

And last, but not least – user modules use Dictionary as it’s interface according to a scheme you decide.

The nice thing about the dictionary is that the modules don’t know about it – it is the dictionary who need to know about modules – and to avoid lengthly index searching we can allow IO messages to be mapped, meanuing you do not search the index at all. At module start you can request direct access and store addresses – you can then write/read directly or through safe Dictionary functions.

CLI however will need to search the index always.

RTOS, CLI, Switch and Dictionary is a powerfully start for any embedded application. They do howether come at the price of a minimum of Flash and SRAM.

C++ CAN Interface

I am waiting on parts for the USB, SD-Card, Ethernet and Modules so the next task I can work on is the CAN interface. The actual barebone CAN interface is autogenerated by STM32CubeIDE in C, so all we need to do is to create a proper IO queue, deal with timing and error handling. Most of this is easiest done on HAL layer in C. The one you need to watch out for is error handling because ST’s CAN interface have error counters that will stop the interface unless you process that correctly and you might be running for a long time before reaching 255 Errors on send or receive.

And sorry to say – setting up CAN is a fiddle if you have never done it before – if I am lucky it work within a day, but I would estimate a weeks work to be sure knowing that I sometimes have used 2-3 weeks in the past.

Galvanic Isolation

As mentioned before all ports except USB are fully galvanic isolated . 24V, Ethernet and all CAN ports. The isolation level on CAN is actually 1500V for 1 minute. My weakest point here is the SSR used for 120Ohm, but I will look into that. The CAN Tranceiver itself handles 5000++V for 1 minute. What you need to know about galvanic isolation is that it will protect you ports against spikes and mistakes, but it will not protect them forever. I older equipment or very low cost equipment they still uses diodes and chokes which also protect to some level, but not even close to what a galvanic isolation will do. Also – with full galvanic isolation on CAN we can let CAN be a twisted pair and ignore ground. But, I have output Ground and Shield for those who insist (or need) on it. Ground was mostly needed because of diodes that ancored the H and L lines to 12V etc. Galvanic Isolation cost between 5-10USD rather than 1 USD, but these are the bucks you don’t want to save.

Noise

Galvanic isolation do not remove noise, so if you run wires close to a noisy motor etc you might need to shield the cable. You will detect noise as CRC errors on incoming messages. I accept to loose about 1 message per hour, but you need to evaluate your own cabling and situation.

Timing

To set up CAN you also need to set a few timers that decide then the CAN interface read bits. These decide your bitrate and you need to use a standard that is very close to what the rest of the CAN network uses to achieve arbitration. The recomendation that many uses is CANopen so I will stick to that. The tricky part here is that every MCU have their own clock system and you have a clock speed set by Clock Configuration in STM32CubeIDE in my case and a prescaler on each CAN interface – and to complicate things these are FD-CAN ports capable of 12Mbps speed. At precent I want to stick to “classic CAN” capable of 1Mbs. I notice that my maximum default speed setting is almost 46Mbps which will not work so we will need to configure the following correctly:

CAN Clock Speed. On some older MCU’s I needed to adjust the MCU speed itself for this to use a speed/precaler combination that generated a similar speed. I have not done this on H723 yet. I notice that my current CAN Clock Speed is 137.5Mhz (Maximum) and that H723 can adjust this separately from main clock speed.

Nominal Prescaler will set your bitrate by dividing the CAN Clock Speed and keep in mind that this needs to give a bitrate that is the same for the entire CAN network which will consist of devices with complete different clock settings. I notice that to achieve 1Mbps I will need a Prescaler between 45 and 46 – so I might need to look into CAN Clock Speed to get this accurate. Keep in mind that the CAN receiver most likely will work as long as you set a bitrate it can receive since the silicon is most likely to check CAN frames and ignore speed on receive – so if you have a 1:1 bus you can actually set one device to 1Mbps and the second to 500Kbps and it will “kind of” work as long as you don’t need arbitration – this is an actual error I corrected on a mobile vehicle for 1,5 years ago that took me by surprice.

Nominal Time Quantum is calculated for you.

Nominal Time Seq1 and Nominal Time Seq2 are the timers that is a bit tricky as they should follow CANopen standard.

Nominal Time for one Bit is calculated and should be 1ms for 1Mbps.

Nominal Baud Rate should be 1000000 bit/s for 1Mbps.

So to summarize on Timing – you will need to fiddle with CAN Clock Speed and Prescaler to get correct bitrate and once that is done set Seq1 and Seq2 correct. I have done this before, but every MCU is so different that I sit and fiddle every time – my tools are searching the internet and asking OpenAI + testing. And you have to do this for every bitrate you support. But, keep it simple (1) set bitrate through clock speed and prescaler and (2) set timers.

Testing

In my case I will create a CAN Network with 4 CAN Nodes (1) My Laptop through some standard CAN/USB Adapter – I hav access to Peak and IXXAT so I can use those as reference. The three others are the three CAN ports on the board. This allows me to do some clever arbitration tests as I can insert messages simultaneously and see whom comes out first.

Termination

I have added a 120Ohm termination resistor that is SW configurable on every port so I switch this on for every endpoint.

Barebone Queue

CAN interfaces always have a small queue for sending/receiving in Hardware that we need to service. I always read/write those with longer SW queues inside – that said CAN is usually so fast even on slow bitrates that it is seldom I use much queues. It is mostly if you want to support hard bursting. Bursting is the capability for one device to send continiously and expect that the receiver will cope – so we add a in/out SW queue on 100 or 1000 entries as per need. The only tricky part with this queue is that one end will be in HAL C Code and the other end in C++ code, so we use a C FIFO module as base. And this 2-way queue needs to be message orriented with age timers added.

Age timers are actually nonsence since we in most cases will send/process messages within 50ys, but they are there for the rare case that we don’t. We have control over receiving, but we might be delayed on sending due to arbitration – and we need error handling for arbitration errors (messages that do not send due to low priority on a busy system).

The CAN IO Queue should be in ISO 11892 format – I know what that is for CAN 2.0 A/B, but CAN-FD is new for me, so I need to check if it is more than extending from 8 byte to 64 byte payload.

The “other end” of a CAN queue is the switch which in reality will forward to host “as is” or to a protocol like CANopen, J1939 or custom depending on how you use this board.

Error handling

Auto Retransmission allows the CAN HW to re-transmit a few times before it gives up. We still need to handle the case where it gives up which most times are arbitration error. But we also need to remove messages that never send to avoid that our out queue overflows.

Send/Receive error counters will trigger callback if they reach thresholds.

And it might be some that I have forgotten – On most HAL interfaces I just use the HAL, but on CAN I read the registers looking for error settings due to past experiences with weird errors that are raised. You could say I have trust issues with ST HAL and CAN πŸ™‚

C++ Class Model

The diagram below is a simplified class model.

Barebone CAN

CAN is one of the exceptions where I read the barebone interface and check if I need to do something. HAL is usually good, but I have experienced issues with lack of proper error handling here in the past- usually I end up adding a few bits to HAL.

HAL CANΒ 

HAL implement an interface and I prefer to us this “as is”.

alCAN

I add my own CAN low level class that pick up barebone access, register settings and added HAL work. The interface to this is C++ SW queues and a seperate Send & Receive task called by RTOS.

RemoteIOCAN, CANopen, J1939 are just examples of the next level. Actuall the switch that is not shown here will receive messages from alCAN and forward them as requested.

One important note is message hand-shaking. Due to the nature of the RTOS I use that is most likely instant. Assume you receive a SDO Read request you will receive the message, process it and send the responce within 200ys or something.

Thanks for reading the mega rant πŸ™‚