STM32 Development

Thanks again @dBC for taking the time to write the tutorial, I think I have it working! :slight_smile: I had to change ADC2 to ADC1, bool to unit16_t with a #define true 1 & #define false 0 as I kept getting error: ‘false’ undeclared and I commented out HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); it may not be available on the f401re?..

I also set these:

hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;

The LED is flashing at a visible rate

I have uploaded my code here: https://github.com/TrystanLea/STM32Dev

TBH @pb66, the cards are stacked against us Windows users when it comes to serious development, that’s why I tend to do most of my code work within a Oracle Virtualbox container running Debian 64 bit Linux, which runs pretty well on my Windows 10 laptop, and gives me the full range of developers tools, and helps me follow tutorials which have been prepared in Linux.
By installing vboxadditions it gives you full graphic support, USB access, copy/paste to/from Windows, plus lots of other features (I have a very easy install command for this if anyone is interested - much easier than the guide!).

Paul

debian

2 Likes

I just added:

  sprintf(log_buffer,"%i\r\n",adc1_dma_buff[800]);
  debug_printf(log_buffer);
  sprintf(log_buffer,"%i\r\n",adc1_dma_buff[801]);
  debug_printf(log_buffer);
  sprintf(log_buffer,"%i\r\n",adc1_dma_buff[802]);
  debug_printf(log_buffer);
  sprintf(log_buffer,"%i\r\n",adc1_dma_buff[803]);
  debug_printf(log_buffer);

in the adc1_full_conv_complete section in the main loop and I have some sensible readings (made possible with the slowed down ADC)

2038
1
2041
1
2037
0
...

The voltage input is at mid rail and there is no CT plugged in, the socket pulls the input to ground with no CT plugged in hence the near zero values.

Excellent… driving the inputs and looking at the data is one part I skipped in my testing :wink: And yes, slowing that ADC clock way down is the way to go once you’re trying to make real measurements… better to tune later from a low setting, than puzzle why readings don’t make sense from a fast setting.

Yes, I was a bit slack documenting some of the more mundane stuff. In my main.h I’d added the following, but didn’t mention it in the tutorial:

/* USER CODE BEGIN Includes */

#include <stdbool.h>
#include <inttypes.h>

/* USER CODE END Includes */

I think you’re correct, there’s no mention of ADC self calibration in the reference manual for your processor (RM0368). When I copied some of my old F091 ADC code to kickstart this F303 demo, I noticed there was a second parameter to that function on the F303. I just had a look at your stm Arduino pointer above and they’ve ifdef’d that call based on CPU type to 3 variants… 1 argument, 2 arguments, and no call at all.

And if you want to avoid the walking-across-the-screen syndrome without the \r on the end of all your strings, minicom has a ‘U’ option to automatically add a CR:

1 Like

So I wasn’t going crazy after all…

This is from the RS page for the Nucleo-64 and more specifically the STM32F303RET6, looking closer it does say “F3 family” but that one item is the only thing that doesn’t apply to the F303

Fast 12-Bit ADCs (5 MSPS per channel with up to 18MSPS in interleaved mode)
16-Bit sigma-delta ADCs
Fast 144MHz motor control timers, timing resolution better 7ns

Here are some pictures of what’s going on under the bonnet for those that way inclined. I’ve made some slight changes to the OEM demo code above:

  1. divide the ADC clock by 2, so now running at 36MHz
  2. increase the sampling time to 601.5 cycles
  3. removed the replications of channel ‘1’ in the scan sequence
  4. moved the LED toggle into the actual ISR to reduce latency

So the sampling maths for this set up is:

One conversion takes 601.5 cycles to charge the S&H cap plus another 12.5 cycles to do the conversion, 614 cycles at 36 MHz is 17 usecs per conversion.

The sequence is now only 11 channels rather than 15, because I now treat Ch1 the same as all the other channels (i.e. just sample it once). So a sequence now takes 11 x 17 usecs which is 188 usecs. That sequence period also gives us our per-channel sampling frequency, namely 5330 Hz which is much closer to where you want to be for measuring mains power, compared with the crazy high sampling speeds in the tutorial.

I’ve left the buffer at 100 sequences so we still get an interrupt every 50 sequences, which is now at a much more leisurely 9.4 msecs. The buffer is now 100x11 long rather than 100x15 long. I’ve added a note to the Tutorial above to ensure the buffer is always a multiple of the scan sequence length, otherwise channels don’t turn up in the same array position each time through.

These three pictures are all of the same thing, just at different timebases.

Blue - LED output toggled in data-ready ISR
Red - ADC input pin corresponding to slot 1 in the sequence
Yellow - ADC input pin corresponding to slot 11 (last) in the sequence

I did my trick of putting a 1M pull-up on the two analog input pins, but they’re otherwise undriven. That way you can easily see when they get selected for sampling… the voltage drops like a brick.

In this one you can see the interrupts are coming in at a leisurely 9.4 msecs and that the sampling rate on a given channel is a respectable 5.3 kHz:

This next one zooms in to show that time between the sample-start of the last slot (Yellow) and the sample-start of the following first-slot (Red) is 17 usecs:

And finally, the third one zooms in even further again to show that the interrupt latency after the sample-start of the first slot (which corresponds to the end of sampling of the last slot) is just 520 nsecs, and a good chunk of that will be the call to
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);


To achieve that I did away with the callback routines and toggled it right in the ISR which is found in Src/stm32f3xx_it.c:

/**
* @brief This function handles DMA2 channel1 global interrupt.
*/
void DMA2_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Channel1_IRQn 0 */

  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

  /* USER CODE END DMA2_Channel1_IRQn 0 */

@Robert.Wall
I guess the next step is to start up a second ADC and use the same techniques to determine how precisely we can offset their start times, and also ensure they then stay in lock step at that timeshift. Are you still thinking 0.1° is sufficient resolution? So somewhere around 4-6 usecs resolution, depending on line frequency. I’ve not been paying enough attention but what is the approximate phase shift correction (in degrees) you currently need for your collection of VTs and CTs? Always less than say 5° for the worst case combo? Does Voltage need to be advanced or retarded?

The ADC in this F303 just gets better and better, it has h/w offset removal. You can program up a 12-bit value you want subtracted from all readings on a channel. My 1M pull-up was reading in the mid 3100’s:

dma_buff[0]:3165                                                                                                                  
dma_buff[0]:3151                                                                                                                  
dma_buff[0]:3155                                                                                                                  
dma_buff[0]:3156                                                                                                                  
dma_buff[0]:3163                                                                                                                  
dma_buff[0]:3155  

Then I programmed up an offset of 3000:

dma_buff[0]:156                                                                                                                   
dma_buff[0]:159                                                                                                                   
dma_buff[0]:171                                                                                                                   
dma_buff[0]:158                                                                                                                   
dma_buff[0]:151     

You can program it on the fly … -ish. You need to STOP continuous conversions while you write in a new value, and then set them going again. Once enabled, the ADC result goes from being unsigned to signed so can go negative. Sounds perfect for h/w assistance with the removal of the mid-rail voltage. The CPU is going to have nothing to do at this rate.

4 Likes

Thanks @dBC, should have a F303 arriving tomorrow, so I can move over to continuing learning and development with that. Interesting to see ‘under the bonnet’! its great to see that detail

Cool.

Yep, and it was kinda’ necessary learning in order to experiment with @Robert.Wall’s excellent suggestion which I finally got around to trying out:

That turned out to be surprisingly easy to implement on the F303 (probably all the families with multiple ADCs I suspect). The approach I took is to set up one of the timers (TIM8) in one-pulse PWM mode which allows you to output a precisely timed pulse. I’ve configured mine to generate a pulse of anywhere from 1usec to 1msec.

You then internally route TIM8’s output to all the ADCs via TRGO2 and tell them to trigger on that signal. Set one to trigger on the negative edge of the pulse and other to trigger on the positive edge of the pulse and voila… you can precisely control the time shift between the ADCs starting. Having done so, they stay lock-step at that displacement because they’re all running off the same clock.

In the following scope pictures, Green is the pulse output, Red is an analog pin mapped to first channel in ADC2’s sequence, Yellow is the same for ADC3. There’s no requirement to run the pulse output to a pin as it all get routed internally, but it makes debugging a lot easier. The analog pins just have 1M pull-ups again to ensure their start-of-sample is clearly visible.


1 usec displacement


100 usec displacement


1 msec displacement.

You can see in that last one ADC2 has got almost 4 samples in before ADC3 starts.

The sampling maths for this one is:
1 conversion takes 614 cycles @ 36MHz is 17 usecs
16 conversions per sequence takes 272 usecs
That gives a per-channel sampling frequency of 3.68 kHz.
50 sequences per half buffer gives an interrupt arrival rate of 13.6 msecs per ADC

I figured this time I should actually feed it a signal and look at the results to see it was doing as intended. I wired the signal generator to both input pins (so they’re both sampling the exact same signal) and generated a 50Hz sinewave. Here’s what I found in the dma buffers at the various timeshift settings:


0 usecs shift.

Those two are so close together you can barely tell there are two plots there, but a look at the data says both ADCs are getting very similar results:

0_usecs_data


1 usec displacement


10 usec displacement


100 usec displacement


1 msec displacement

So we can do phase-shifts on the fly entirely in the analog domain down to 1usec resolution (~0.02 degrees at 50Hz). Actually, you could go lower but that seemed a bit over the top. And it’s programmable on the fly… -ish. You need to stop the ADCs, reconfig the timer, and then restart the ADCs, so it’s not something you want to be doing a lot of if you’re into continuous sampling, but certainly it could be a runtime configuration parameter, it’s not as if it needs to be baked into the image.

Credit to Robert for the suggestion, and credit to STM32MXCube for the code. I mostly hit “Generate Source”.

3 Likes

So it looks as if a chunk of processing and the amplitude errors associated with interpolation will disappear. :+1:

And if that delay was stretched to about 90°; then, given a resistive load, it could accurately measure its own phase error and self-calibrate. (The big problem there is knowing you truly have a resistive load.)

Going back some way to your (@dBC ) question about phase errors, here are some collated figures for a.c. adapters, the later (black cable, moulded plug) SCT-013-000 and the SCT006 c.t’s. All show a phase lead.

You might like to think about the capacitive “transformer” (borrowing the term from the high voltage - 66kV upwards - fraternity) that should have, if the op.amp. circuit is designed correctly, a very small phase shift that’s independent of voltage (Old forum: https://openenergymonitor.org/forum-archive/node/1568.html.), but as it needs to be “home made”, there’s a big problem with input scale calibration.

%20Adapters%20Phase


I’ve previously noted the large variations over time in both the v.t’s and c.t’s; the real problem where the a.c. adapters are concerned is we’re specifying a part that is designed as a general purpose power supply, not a measuring instrument.

Yep, it’s almost like having your own programmable precision delay line on the signal.

Nice.

That and having a stable known line frequency. Unfortunately, the delay is measured in usecs rather than degrees.

Thanks for the refresher on the various sensor phase error performance.

While playing around with this phase-shift idea (via precisely timed ADC triggers) I came to the conclusion I needed a bit of “power maths” in order to see it in action. So I knocked up some basic Vrms, Irms, Papp, Preal, PF calculations and used the offset-removal techniques described in @Robert.Wall’s excellent paper here: emonLib Maths(extended).pdf.

I tried to make it emonTxShield friendly because I figured that would be a common starting point for everyone, so it supports 4 current inputs and 1 voltage input. I scheduled the ADC conversions as follows:

adc_schedule

Which means in the first slot in the sequence it does 3 CT conversions and a VT conversion. In the second slot it does the 4th CT, with a new VT conversion. I had to give ADC1 and ADC3 something to do during those slots, so I selected a couple of internal channels: Temperature and Vref although I’ve not even looked at the data that comes back in those slots.

At this stage there’s just single phase difference setting between V and all the I’s. In other words, I trigger ADC4 on the rising edge and the other three on the falling edge. Having different phase settings on different CT channels may prove challenging.

One slight hiccup is I need to get the V input onto an ADC of its own (ADC4) so I moved it across to PB14 like so:


The four CT inputs line up perfectly with the emonTxShield usage and to get V to work you just need to run a jumper wire from CN7.28 (PA0) to CN10.28(PB14):

The fact that V is also connected to PA0 doesn’t matter, as in this example it’s configured as an unused analog input, so doesn’t get in the way. So a single wire with a female jumper connector on each end should get you going with the shield.

The early results look promising, this is specifying a 3333 usec delay between ADC4 and all the others like this:

  start_ADCs(3333);                 // start ADC with x usec lag

That should give ~60° phase shift. And with my signal generator sinewave feeding all 5 inputs the results look good:

0: Vrms: 1234.22, Irms: 1234.44, Papp: 1523560.12, Preal: 761240.69, PF: 0.500                               
1: Vrms: 1234.22, Irms: 1234.06, Papp: 1523093.12, Preal: 761020.94, PF: 0.500                               
2: Vrms: 1234.22, Irms: 1234.21, Papp: 1523286.88, Preal: 761084.25, PF: 0.500                               
3: Vrms: 1234.22, Irms: 1234.03, Papp: 1523063.00, Preal: 760992.19, PF: 0.500   

All numbers are in raw ADC units.

I’ll attach the Src/Inc/build directories here after a bit of a clean up, feel free to use as much or as little of them as you like. I was not sure whether the quietness on this thread was a good sign (everyone’s madly coding) or a bad sign (everyone’s stuck in the mysteries of HAL calls). If the former, feel free to ignore this code, if the latter, it’ll hopefully get you unstuck. If you just want to play with your own power calculation libraries, it should be hopefully easy enough to hook it in instead of my cheap-n-cheery power calculations.
txshield_demo.tar.gz (240.3 KB)

2 Likes

Sorry for disappearing, I’ve been to hell and back trying to get the toolchain sorted, with numerous obstacles coming between me and what I had hoped to be a straight forward and well documented task, well I guess it is was “well documented” there are literally thousands of guides and possibly no 2 are the same. But I’m there now.

I think there might be a minor omission in the tutorial above as I was getting some errors about log_buffer undeclared, I see in both your (@dBC) ADC_demo.tar.gz and @Trystan’s repo there is

/* Private variables ---------------------------------------------------------*/

char log_buffer[100];

/* USER CODE BEGIN PV */

but I missed that in the tutorial, I won’t stick my neck out and say it’s not there, but I have failed to find it despite looking many times.

So I’ve caught up to the end of 3. ADC Tutorial Part3, adding user code to make it useful. and I have added both the “log_buffer fix” above and the “bools fix” 4 posts on from the end of Part3 .

but from there I’m unsure if you and Trystan are collaborating on shared code or doing separate things, I’m sure with some trial and error I might work it out (although I’ve had enough trial and error this week to last a lifetime) but it would be useful to know if this is still one example or not.

Yeh, sorry about that. I decided to just point out the really important stuff in the tutorial and include the tar file for the detailed stuff. Perhaps that wasn’t a wise choice.

I’ve not been collaborating with anyone outside of this thread, so not sure what other progress has been made, or whether there’s been duplication. But in playing with the phase-shift stuff I sorta’ accidentally wrote a full energy monitor for the F303/shield combo (although I’ve only tested it on a signal generator, not a real emonTxShield… I believe there’s one in the post).

If you want to hold off for my next source tree post (hopefully this weekend) it’ll probably be a much more useful starting point than the last one. Sampling frequencies are a lot more sensible: each I is sampled continuously at 14.66kHz… higher than I would have liked but I haven’t yet worked out how to slow that down and keep them synchronous and it’s got processing power to burn. It samples all 4 I inputs continuously (grabbing a V to go with each) and accumulates the stats and spits the results out every 10 seconds.

I don’t want to steal your project, and the code is at best “example” quality, so don’t feel obliged to use it, or even read it, but if you get stuck and want a working example it’ll hopefully help.

4 Likes

No you steal away! It’s not really my project, I might of got the ball rolling but I’m more a straggler than a lead at this point until I find my feet. I have to say the odds seem to have been stacked against me thus far, “stuck” is a good word for it, “suicidal” was a better word for it a couple of days ago :slight_smile:

1 Like

I’m trying to compile txshield_demo and there appears to be a file missing: stm32f3xx_hal.h (& presumably the .c) ?

I turned to that to try to find the source of the multiple declarations in the Tutorial (that’s before adding the custom code). It seems there’s no middle ground between multiple and missing. :confused:

Are you seeing those errors too? I know @Paul is, but I did not see those errors and I assume @TrystanLea and @dBC haven’t either as they are able to compile error free.

If you are can you show the files you have? I have checked my code/files and can find no source of duplication, but I didn’t expect to as I haven’t seen the error. Are they actual duplication’s or are they perceived duplication’s due to a duplicated “include” line?

I have known good code, so if someone can provide the code that is giving the errors we can easily compare. Or now I lnow my toolchain is functional, If I can compile the code that others cannot, we can look at their toolchains. That’s why I offered my code that’s confirmed does compile (using both make and PIO) so you and @Paul could test your toolchains.

Attached here also stm32tests-master.zip (653.8 KB)

Besides, looking in revision 2 for the source of the errors given in revision 1 might not be fruitful if the offending code has been revised.

Oops… my bad, apologies. I didn’t include the Drivers directory in the tar file because I hadn’t been in there. It’s created by the GUI when you hit “Generate Source Code” as per the selection in the first panel here Project->ProjectSettings->CodeGenerator:

There’s two ways to proceed. You can either use this tar file: txshield_demo_2.tar.gz (803.7 KB) which should be the same as the last but with Drivers included.

Or… you can fire up the GUI, and hit Generate Code and have it create the Drivers directory.

EDIT: And yet another to fix an issue Paul just reported. Those tar files forgot to include emonTxshield.mak, which enables gnu99 extensions, and %f printfs. This one includes it: txshield_demo_3.tar.gz (803.7 KB)

1 Like

Just unzipped your file @pb66 , and that seemed to compile OK, I just got a few warnings about ‘incompatible implicit declarations’, but largely OK, so at least the toolchain is installed and working correctly!!

Considering all the grief over the past few days about trying to get the toolchain installed in my Debian VM, actually it couldn’t be easier (and a little embarrassing!);
sudo apt-get install gcc-arm-none-eabi

Paul

2 Likes