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
Personal Blogs
  • Community Hub
  • More
Personal Blogs
Michael Kellett's Blog A simple frequency response analyser
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: michaelkellett
  • Date Created: 10 Mar 2020 3:13 PM Date Created
  • Views 5291 views
  • Likes 16 likes
  • Comments 26 comments
  • signal generator
  • dds
  • dsp
  • analog
Related
Recommended

A simple frequency response analyser

michaelkellett
michaelkellett
10 Mar 2020

ORIGINAL POST 10/03/202

UPDATED 11/03/2020 : Filter response plot added.

UPDATED 17/03/2020: Board design notes added.

 

 

 

This idea has been at the back of my mind for while, but got pushed to the top of the stack by a post from amiranghi  who wanted to set up an Analog Devices DDS chip. This reminded me of a half started project to use an ST32H7xx type ARM processor to generate sine waves by DDS.

 

For those who are unfamiliar with the idea, DDS stands for Direct Digital Synthesis and it's a way of generating sine waves (actually more generally any periodic waveform). There's a Wiki page here: https://en.wikipedia.org/wiki/Direct_digital_synthesis

 

You can buy nice little chips from Analog Devices and others. It's also easy to get little pcbs with a variety of the AD chips and some support circuits from Ebay and other sources of cheap Chinese boards. The AD chips are fine, but not that cheap and they only generate signals :-  for a Frequency Response Analyser we want to measure signals as well.

 

By now you should be wondering just exactly what is an FRA - unless you already know, in which case skip this bit !

 

A Frequency Response Analyser is an instrument which measures the output of the device under test while the DUT is driven by a sine wave signal from the FRA.

 

So we might use one to measure the frequency response of a filter, if it has two measuring ports we will be able to measure the phase response as well.

 

Rather than me go on for ages , here's another link to a nice paper from Solatron whcih describes FRAs in a lot more detail.

https://www.ameteksi.com/-/media/ameteksi/download_links/documentations/library/solartonanalytical/materials/technical%2…

 

Solatron still make nice FRAs but they are probably outside the typical home lab budget.

https://www.ameteksi.com/products/frequency-response-analyzers/1255b-frequency-response-analyzer

 

The plot here is to see how much of an FRA we can get for no more than £30 (not counting the pcb).

 

And that's where the STM32H7 family of micros come in - they offer 480MHz clock speed, on chip double precision floating point accelerator, dual or triple 16 bit ADCs, dual 12 bit DAC and lots of memory.

There's a very cheap one for £5.67 (one off STM32H750VBT6), OK for limited work but only has 128k of RAM and that isn't enough for everything we would like to do.

The best bet for this job is the STM32H743ZIT6U which has 1Mbyte of RAM. It's supported by a Nucleo board, NUCLEO-H743ZI2 at about £21 (all Farnell prices).

 

I wanted the maximum frequency range I could squeeze from the processor and from past experience (I've done this before at lower speeds) I expected the H7s to be good for a 500kHz sampling rate. I want to be able to do what I call continuous wave sweeping. Pretty much any FRA has the ability to work over a range of frequencies. Many allow you to sweep the frequency but there are two ways this can be done. Often the simplest is to step the frequency from one discrete frequency to another - this is simple to do but requires that you allow settling time between steps. The other way is to sweep the frequency smoothly with no steps between the start and stop frequencies. And just to make it a little bit harder what you really need is to be able to sweep logarithmically - I can't find a DDS chip that can do continuous wave log sweeps.

 

The concept is simple enough but we need some tricks to make even a fast processor do what we need at 500k samples/second.

 

If you can't cope with maths you can skip this bit.

 

The DDS relies on a register called the phase accumulator (in code this is just a variable), the pa represents the angle of the sine wave sample we are making.

Make pa a 32 bit unsigned variable in the code so pa = 2^32 is an angle of 2pi radians.

For 500k samples per second we need to calculate a new sample every 2us, if we want to generate a sine wave at 125000 Hz each cycle takes 1/125000 = 8us, so there are 4 samples in each cycle.

So if we add 1/4 of 2^32 to pa each sample time we can get the value of the sample by calculating sin((pa/2^32) *2 * pi).

The pa will conveniently wrap round to zero when it reaches 2^32 - this is all very nice and it's how the chips work.

The value added to pa each sample time is the phase increment phi and for constant frequency it's a constant and we can work it out once:

phi = (2^32 * freq)/fs

If the frequency is to sweep then phi must change, and if we want continuous wave sweeping it must change every sample time (not every sine cycle but every digital sample).

For a linear sweep it's not too bad, just add a little to the phi every sample time, what you add will be (phi end - phi start)/(sample rate * sweep time)

To put some numbers in that; for 10Hz to 100kHz with a sweep time of 1000 seconds, phi_start = 8.58993459E4, phi_end = 8.58993459E8, so the phi_adder = 1.71781512

Obviously it will need more than 32 bit resolution - but the H7 with its 64 bit floating point hardware should be OK.

Log sweeps are a bit harder:

We need to multiply the phi by a constant for the sweep at each sample time. For equivalent resolution the sweep time can be reduced, we'll use 100 seconds per decade.

phi_mult = (phi_end/phi_start)^(1/(sample_rate * sweep_time)) - for the example above that's 10000^(1/1.5E8) = 1.0000000614, once again we will need 64 bit precision

 

The H7 floating point engine doesn't do sines or cosines so they need to be calulated in software and even on the H7 it's much too slow to do in the sample time, so we use a look up table.

The H7 DAC has only 12 bit resolution, I satisfied myself, in a not very rigorous way, that a 14bit sine table is quite enough to drive a 12 bit DAC.

The sine table is accessed by right shifting the phase accumulator by 18.

 

End of maths.

 

Implementation on the Processor

 

The Nucleo board I had to hand is a very early one with the Y version of the processor which has several faults:

max clock speed is only 400MHz

max AHB bus speed is restricted to 100MHz for the ADC to work correctly

the ADC is a bit duff (complex interaction problems between ADCs)

 

There are many ways in which the code may be constructed  - I've started with the very simplest where a timer generates an interrupt every 2us and the timer interrupt code refreshes the DAC, and does all the calculations required for each sample. This isn't the most efficient way to do things because the intererrupt call and returns are wasting a lot of time and it gets very hard to write the control sections of the code  because no other interrupts may be used at all. (Which makes trying to talk to a display or UART a bit horrible.)

Eventually I shall re-cast the code to use DMA to transfer data from ADCs and to DACs  - which should improve the performance but will make things a bit more complicated.

 

At first I just want to get the DDS part working and see how well it goes.

 

 

Now for some simple code - this isn't the complete project (if any one wants it I'll post the complete Keil project on Dropbox). Please don't cut and paste this code - it's been edited for readability for this blog and I may have introduced errors - if you want to run it just let me know and I'll give a known to work version.

 

//------------------------------------------------------------------------------------
// h7synth_fast.c
//------------------------------------------------------------------------------------
// Copyright 2020 MK ELECTRONICS LTD
//
// AUTH: Michael Kellett
// DATE 23/02/2020
//
// H7 Synth
//
// Target  STM32H743Zi on NUCLEO H743Zi board
//
// Tool chain  KEIL
//

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <https://www.gnu.org/licenses/>.

#include "h7synth_project.h"
#include "h7synth_fast.h"
#include "h7synth_adc.h"
#include "stm32h7xx_hal.h"
#include <stm32h7xx_hal_dac.h>
#include <math.h>


double pi = 3.14159265359;
double twopi = 0.0;
double fs = 500000.0;
uint32_t phase_acc_1 = 0;
uint32_t phase_acc_2 = 0;
double f1 = 99000.0;
double f2 = 25000.0;
uint32_t phase_step_1 = 0;
uint32_t phase_step_2 = 0;
double half_dac = 2048.0;
double scale = 1800.0;
int16_t sin_table[16384];

double start_phase_step;
double stop_phase_step;
double step_multiplier;

double fphase_acc_1 = 0.0;
double fphase_acc_2 = 0.0;
double fphase_step_1 = 0;
double fphase_step_2 = 0;

double f2p32 = 0x100000000;



uint16_t adc_idx = 0;

void synth_init(void)
{
int a;
double astep;
    
twopi = (double)2.0 * pi;
astep = twopi / (double)(16384);
phase_step_1 = ((double)0xffffffff  / fs * f1);
phase_step_2 = ((double)0xffffffff  / fs * f2);
for(a = 0; a < 16384; a++)
    {
    sin_table[a] = half_dac + (sin(astep * (double)a) * scale);
    }
}

void sweep_init(double fstart, double fstop, double seconds_per_decade)
{
adc_idx = 0;
TIM2->CR1 &= 0xfffffffe;                                                                // stop timer
start_phase_step = ((double)0x100000000 / fs * fstart);        
stop_phase_step = ((double)0x100000000 / fs * fstop);        
step_multiplier = pow((double)10.0, (1/(fs *    seconds_per_decade)));
fphase_acc_1 = 0.0;
fphase_acc_2 = 0.0;
fphase_step_1 = start_phase_step;
fphase_step_2 = start_phase_step;
TIM2->CR1 |= 0x1;
NVIC_EnableIRQ(TIM2_IRQn);
}

void TIM2_IRQHandler(void)
{
uint16_t s1,s2;

//PIN_SET(DB0);

    
TIM2->SR &= 0xfffe;

    
DAC1->DHR12R1 = sin_table[phase_acc_1 >> 18];            // write the new sample to the DAC buffer
fphase_acc_1 += fphase_step_1;                            // increment the phase accumulator
if (fphase_acc_1 > f2p32) fphase_acc_1 -= f2p32;        // floating point accumulator won't warp round on it's own at max so we have to do it
phase_acc_1 = fphase_acc_1;                                // phase_acc_1 is the uint32_t copy of the floating point "actual" accumulator
    
if (fphase_step_1 < stop_phase_step)                    // if sweep is still sweeping
    {
    fphase_step_1 *= step_multiplier;                    // modify the phase step
    }
//PIN_CLR(DB0);
    
}

 

All the real action happens in TIM2_IRQHandler, this is called once every 2us by hardware, the DAC is set up so that the data transfer from the buffer to the actual DAC is triggered by TIM2.

 

The H7 DAC needs a reconstruction filter - which looks like this:

 

image

The dual amplifier is an LM7322 and the filter is a 4th order Chebyschev wit a cut off at 100kHz, designed using TI's FilterCad programme.

I built it on Veroboard using a little bit of the nice TI sm -> DIP converter (Farnell 3125705).

image

The filter works fine and the performance of the sine wave generator isn't at all bad for a 12 bit DAC.

 

Filter response measured by Picoscope. A bit of fine tuning could recover that 0.5dB droop but it's not at all bad for 1% standard value resistors and 5% caps.

 

image

 

Here a some spectra measured using a Picoscope, sine wave frequencies 2k, 25k, 37k5, 99k, 100k:

 

image

image

image

 

image

This is about as bad as the spectrum gets, harmonic distortion is low at about -60dB (0.1%) but there are some nasty spuriae close to the fundamental and only about 50dB down.

 

image

 

The next plot shows the output of the DAC and the output of the filter at 99kHz (in case you were wondering if we needed the filter !):

 

image

 

The next stage of the work is to get the ADCs working (which actually I have done)  but I'm going to break off from blogging to design a pcb which can use the latest version of the processor.

 

MK

17/03/2020

PCB Design is complete, I'm using the 100 pin TQFP STM32H743VI processor which no one in the uK has in stock. I've ordered some from the US but with the Covid situation I'll wait until the processor actually arrive before I order the boards.

I'm going to give www.quick-teck.co.uk a go - prices nearly as keen as direct China buying but with the advantage of local currency and shipping, so no mystery about eventual price.

 

The board has the following features:

2 DC coupled input channels with relay switched 10:1 attenuator, buffer amp and 4th order anti-alias filter.

2 DC coupled output channels with 4th order anti-alias filter.

512 byte EEPROM for calibration info

Full debug connector on processor supporting serial or parallel trace (you'll need Keil Ulink PRO or ULINKplus) to get trace.

Power supply on board (almost - it doesn't have a transformer  - needs about 6V AC).

Connector for Bridgetek display (SPI to intelligent display controller).

USB COM port type FTDI chip with USB connector.

120mm x 80mm

 

Board top side:

 

image

 

 

The schematics are in .pdf files, when I work out how to add those I will.

 

MK

  • Sign in to reply

Top Comments

  • DAB
    DAB over 5 years ago +6
    Nice project, I look forward to your build out and testing. DAB
  • shabaz
    shabaz over 5 years ago +5
    Hi Michael, Very interesting project! It could have great VNA-like or impedance meter capabilities with the phase measurement too. Looks like the ST parts are a nice alternative to the AD DDS chips for…
  • fmilburn
    fmilburn over 5 years ago +4
    Hi Michael, Nice project. Sometime back I entered a Project14 contest and won the grand prize which included the STM32 Nucleo board with the STM32H743ZIT6U and 1MB ram. I have never used it other than…
Parents
  • michaelkellett
    michaelkellett over 5 years ago

    If you want performance then forget the Hal and mbed libraries and most of all forget the Arduino IDE.

    The HAL stuff is OK for working out how to drive the hardware, but usually too slow and cumbersome for actual use.

    Much as I'd like to help you I can't give detailed tutorials about using DMA.

    This is how I set up the ADC and DMA, it will make no sense unless you have the chip reference manual handy.

    It would help me to help you if you explained the end goal of your project.

     

    //------------------------------------------------------------------------------------
    // h7synth_adc.c
    //------------------------------------------------------------------------------------
    // Copyright 2020 MK ELECTRONICS LTD
    //
    // AUTH: Michael Kellett
    // DATE 03/03/2020
    //
    // H7 Synth
    //
    // Target  STM32H743Zi on MKE H7SYNTH board
    //
    // Tool chain  KEIL
    //
    
    #include "h7synth_project.h"
    #include "h7synth_fast.h"
    #include "h7synth_adc.h"
    #include "h7synth_dds.h"
    #include "h7synth_sigproc.h"
    
    #include "stm32h7xx_hal.h"
    #include <stm32h7xx_hal_dac.h>
    #include <stm32h7xx_hal_adc.h>
    
    uint16_t adc_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000")));    // in SRAM1 for DMA friendly
    uint16_t dac_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000"))); // in SRAM1 for DMA friendly
    float dds_sines[2][TRIG_BUFFER_SIZE];     //__attribute__((section(".ARM.__at_0x24000000")));
    float dds_cosines[2][TRIG_BUFFER_SIZE]; //__attribute__((section(".ARM.__at_0x24000000")));
    uint32_t dds_pa[2][TRIG_BUFFER_SIZE];     //__attribute__((section(".ARM.__at_0x24000000")));
    
    float fsin_table_16[65536] __attribute__((section(".ARM.__at_0x24000000")));
    float capture[65536] __attribute__((section(".ARM.__at_0x24000000")));
    
    
    
    bool acq_running = false;
    
    
    
    
    
    
    // CH1 is ADC1 INP3 (pins 30,31)
    // CH2 is ADC2 INP4 (pins 32,33)
    
    void adc_init(void)
    {
    __HAL_RCC_ADC12_CLK_ENABLE();
    ADC12_COMMON->CCR = 0x30000;            // ADCs independent clock mode sync divde adc_hclk by 4, (for fADC = 25MHz)
    ADC1->CR = 0x50000100;                        // diff mode for cal, out of deep power down and boost on
    ADC2->CR = 0x50000100;                        // diff mode for cal, out of deep power down and boost on
    delay_us(50);                                            // 10 should be enough
    ADC1->CFGR = 0x1583;                            // overwrite mode, external trigger, rising edge, TRG0 TIM4, DMA circ mode 0001 0101 0110 0011
    ADC2->CFGR = 0x1583;                            // overwrite mode, external trigger, rising edge, TRG0 TIM4, DMA circ mode 0001 0101 0110 0011 
    ADC1->SMPR1 = 0x0800;                            // sample time 32.5fADC on INP3
    ADC2->SMPR1 = 0x4000;                            // sample time 32.5fADC on INP4
    ADC1->SQR1 = 0xc0;                                // reg sequence of 1 channel at input 3
    ADC2->SQR1 = 0x0100;                            // reg sequence of 1 channel at input 4
    ADC1->CR |= 0x00010000;                        // linearity 
    ADC2->CR |= 0x00010000;                        // linearity 
    ADC1->CR |= 0x80000000;                        // do cal
    ADC2->CR |= 0x80000000;                        // do cal
    delay_us(10);                                            // give ADC time to notice request
    while((ADC1->CR & 0x80000000) != 0){};
    while((ADC2->CR & 0x80000000) != 0){};        // let them both finish
    ADC1->DIFSEL = 0x08;                            // set differential mode on INP3
    ADC2->DIFSEL = 0x10;                            // set differential mode on INP4
    ADC1->PCSEL = 0x088;                            // set the pre select registers for input 3, and INP7 for INP 3 diff
    ADC2->PCSEL = 0x110;                            // set the pre select registers for input 4, and INP8 for INP 4 diff
        
    ADC1->CR |= 1;                                        // enable ADC    
    ADC2->CR |= 1;                                        // enable ADC    
    while((ADC1->ISR & 1) == 0){};
    while((ADC2->ISR & 1) == 0){};        // wait for both to be ready
    //ADC1->CR |= 4;                                        // start the ADC
    //ADC2->CR |= 4;                                        // start the ADC
        
    
    }
    
    void init_dma(void)
    {
    // DMA1, stream 0  ADC1
    // DMA1, stream 1  ADC2
    // DMA2, stream 0  DAC1
    // DMA2, stream 1  DAC2
        
    __HAL_RCC_DMA1_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
        
    DMA1_Stream0->CR = 0x00022d00;        // dbm = off priority = high,mize = psize = 01 for 16 bit, minc = 1,pinc = 0, circ = 1, dir = 00 pfctrl = 0, no interrupts, disabled  0000 0000 0000 0010 0010 1101 0000 0000
    DMA1_Stream1->CR = 0x00022d00;
    DMA2_Stream0->CR = 0x00032d40;        // dbm off, priority = vhigh,mize = psize = 01 for 16 bit, minc = 1,pinc = 0, circ = 1, dir = 01 pfctrl = 0, no interrupts, disabled  0000 0000 0000 0111 0010 1101 0100 0000
    DMA2_Stream1->CR = 0x00032d40;
    
    DMA1_Stream0->NDTR = 2048;
    DMA1_Stream1->NDTR = 2048;
    DMA2_Stream0->NDTR = 2048;
    DMA2_Stream1->NDTR = 2048;
        
    DMA1_Stream0->PAR = (uint32_t) &(ADC1->DR);
    DMA1_Stream1->PAR = (uint32_t) &(ADC2->DR);
    DMA2_Stream0->PAR = (uint32_t) &(DAC1->DHR12R1);
    DMA2_Stream1->PAR = (uint32_t) &(DAC1->DHR12R2);
    
    DMA1_Stream0->M0AR = (uint32_t) &adc_buffer[0][0];
    DMA1_Stream1->M0AR = (uint32_t) &adc_buffer[1][0];
    DMA2_Stream0->M0AR = (uint32_t) &dac_buffer[0][0];
    DMA2_Stream1->M0AR = (uint32_t) &dac_buffer[1][0];
    //DMA2_Stream0->M1AR = ((uint32_t) dac1_buffer) + 2048;
    //DMA2_Stream1->M1AR = ((uint32_t) dac2_buffer) + 2048;
    
    // fifo control left at default (fifo not used)
    
    
    DMAMUX1_Channel0->CCR = 9;                // sync disabled , just select the basic mpx input
    DMAMUX1_Channel1->CCR = 10;
    DMAMUX1_Channel8->CCR = 67;
    DMAMUX1_Channel9->CCR = 68;
    
    DMA1_Stream0->CR |= 1;                        // Enable DMA
    DMA1_Stream1->CR |= 1;
    DMA2_Stream0->CR |= 1;        
    DMA2_Stream1->CR |= 1;
    
    }
    
    // DELAY FUNCTION
    // Calibrated for STM32H743Zi at 400MHz internal RC clock 
    // Optimisation level 0 Compiler: 'V5.06 update 6 (build 750)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin'
    // Driven by timer 5 so pretty much as good as uP clock
    // call for 1     , get 1.25
    // call for 10    , get 10
    // call for 100    , get 100.5
    // call for 500 , get 500
    // call for 5000, get 5000 
    
    
    #define DELAY_US_MIN 1
    #define DELAY_MULT 200
    #define DELAY_DIV  1
    #define DELAY_TRIM 50
    
    void delay_us(uint32_t delay)
    {
    volatile uint32_t d;
    uint32_t dm;
    
    if (delay < DELAY_US_MIN)
        {
        return;
        }
    dm = delay * DELAY_MULT;
    //dm /= DELAY_DIV;
    dm -= DELAY_TRIM;
    TIM5->CNT = 0;
    while(TIM5->CNT < dm){};
    }
    
    void delay_test(void)
    {
    PIN_SET(DB0);
    delay_us(1);
    PIN_CLR(DB0);    
    delay_us(1);
    
    PIN_SET(DB0);
    delay_us(10);
    PIN_CLR(DB0);    
    delay_us(10);
    
    PIN_SET(DB0);
    delay_us(100);
    PIN_CLR(DB0);    
    delay_us(100);
    
    PIN_SET(DB0);
    delay_us(500);
    PIN_CLR(DB0);    
    delay_us(500);
    
    PIN_SET(DB0);
    delay_us(5000);
    PIN_CLR(DB0);    
    delay_us(5000);
    }
    
    
    
    void init_acquisition(void)
    {
    TIM4->CR1 &= 0xfffffffe;                    // stop timer
    TIM2->CR1 &= 0xfffffffe;                    // stop sample counter
    TIM2->CNT = 0;                                        // sample count set to zero
    TIM4->CNT = 240;                                    // preset sample timer
    n_written[0] = 0;
    n_written[1] = 0;
    n_read[0] = 0;
    n_read[1] = 0;
    while((ADC1->CR & 4) != 0)                // wait for any pending conversion to end
        {}
    if ((ADC1->CR & 1) != 0)                    // if ADC is enabled
        {
        ADC1->CR |= 2;                                    // disable it
        while ((ADC1->CR & 1) != 0)            // wait for disable to work
            {}        
        }
    while((ADC2->CR & 4) != 0)                // wait for any pending conversion to end
        {}
    if ((ADC2->CR & 1) != 0)                    // if ADC is enabled
        {
        ADC2->CR |= 2;                                    // disable it
        while ((ADC2->CR & 1) != 0)            // wait for disable to work
            {}        
        }
    DMA1_Stream0->CR &= 0xfffffffe;        // Disable all DMA
    DMA1_Stream1->CR &= 0xfffffffe;
    DMA2_Stream0->CR &= 0xfffffffe;        
    DMA2_Stream1->CR &= 0xfffffffe;
    
            
    }
    
    void start_acqusition(void)
    {
    DMA1->LIFCR = 0xf7d;                            // clear stream 0 and 1 flags
    DMA2->LIFCR = 0xf7d;                            // necessaary before re-starting DMA
        
    DMA1_Stream0->CR |= 1;                        // Enable DMA
    DMA1_Stream1->CR |= 1;
    DMA2_Stream0->CR |= 1;        
    DMA2_Stream1->CR |= 1;
    
    ADC1->CR |= 1;                                        // enable ADCs
    ADC2->CR |= 1;                                        
    
    while(((ADC1->ISR & 1) == 0)||((ADC1->ISR & 1) == 0))
    {}                                                                // wait for them to bee ready
        
        
    
    TIM2->CR1 |= 0x1;
    TIM4->CR1 |= 0x1;
    DAC1->CR |= 0x10001;                             // dacs enabled
    ADC1->CR |= 4;                                        // start the ADC
    ADC2->CR |= 4;                                        // start the ADC
    
    acq_running = true;
    }
    
    void stop_acquisition(void)
    {
    TIM4->CR1 &= 0xfffffffe;
    acq_running = false;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    //------------------------------------------------------------------------------------
    // h7synth_adc.h
    //------------------------------------------------------------------------------------
    // Copyright 2020 MK ELECTRONICS LTD
    //
    // AUTH: Michael Kellett
    // DATE 03/03/2020
    //
    // H7 Synth tester
    //
    // Target  STM32H743Zi on MKE H7SYNTH board
    //
    //
    //
    // Tool chain  KEIL
    //
    
    
    // Define to prevent recursive inclusion 
    #ifndef H7SYNTH_ADC_H
    #define H7SYNTH_ADC_H
    
    #include <stdint.h>
    #include <stdbool.h>
    
    // GLOBAL DEFNITIONS
    #define DAT_BUFFER_SIZE     2048
    #define TRIG_BUFFER_SIZE    4096
    
    // FUNCTION DECLARATIONS
    
    extern void adc_init(void);
    extern void delay_us(uint32_t delay);
    extern void delay_test(void);
    extern void init_tim5(void);
    extern void init_dma(void);
    extern void init_acquisition(void);
    extern void start_acqusition(void);
    extern void stop_acquisition(void);
    
    
    
    // GLOBAL VARIABLES
    
    extern uint16_t adc_buffer[2] [DAT_BUFFER_SIZE]__attribute__((section(".ARM.__at_0x30000000")));
    extern uint16_t dac_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000")));
    extern float dds_sines[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    extern float dds_cosines[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    extern uint32_t dds_pa[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    
    extern float fsin_table_16[65536] __attribute__((section(".ARM.__at_0x24000000")));
    extern float capture[65536] __attribute__((section(".ARM.__at_0x24000000")));
    
    
    
    extern bool acq_running;
    
    
    #endif //  H7SYNTH_ADC_H

     

     

    Once every two ms a function checks the DMA buffers for content and processes whatever is there.

     

    This code is work in progress - be very careful when using it that you fully undertsnad what it actually does , which may not be quite what the comments say image

     

    MK

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • michaelkellett
    michaelkellett over 5 years ago

    If you want performance then forget the Hal and mbed libraries and most of all forget the Arduino IDE.

    The HAL stuff is OK for working out how to drive the hardware, but usually too slow and cumbersome for actual use.

    Much as I'd like to help you I can't give detailed tutorials about using DMA.

    This is how I set up the ADC and DMA, it will make no sense unless you have the chip reference manual handy.

    It would help me to help you if you explained the end goal of your project.

     

    //------------------------------------------------------------------------------------
    // h7synth_adc.c
    //------------------------------------------------------------------------------------
    // Copyright 2020 MK ELECTRONICS LTD
    //
    // AUTH: Michael Kellett
    // DATE 03/03/2020
    //
    // H7 Synth
    //
    // Target  STM32H743Zi on MKE H7SYNTH board
    //
    // Tool chain  KEIL
    //
    
    #include "h7synth_project.h"
    #include "h7synth_fast.h"
    #include "h7synth_adc.h"
    #include "h7synth_dds.h"
    #include "h7synth_sigproc.h"
    
    #include "stm32h7xx_hal.h"
    #include <stm32h7xx_hal_dac.h>
    #include <stm32h7xx_hal_adc.h>
    
    uint16_t adc_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000")));    // in SRAM1 for DMA friendly
    uint16_t dac_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000"))); // in SRAM1 for DMA friendly
    float dds_sines[2][TRIG_BUFFER_SIZE];     //__attribute__((section(".ARM.__at_0x24000000")));
    float dds_cosines[2][TRIG_BUFFER_SIZE]; //__attribute__((section(".ARM.__at_0x24000000")));
    uint32_t dds_pa[2][TRIG_BUFFER_SIZE];     //__attribute__((section(".ARM.__at_0x24000000")));
    
    float fsin_table_16[65536] __attribute__((section(".ARM.__at_0x24000000")));
    float capture[65536] __attribute__((section(".ARM.__at_0x24000000")));
    
    
    
    bool acq_running = false;
    
    
    
    
    
    
    // CH1 is ADC1 INP3 (pins 30,31)
    // CH2 is ADC2 INP4 (pins 32,33)
    
    void adc_init(void)
    {
    __HAL_RCC_ADC12_CLK_ENABLE();
    ADC12_COMMON->CCR = 0x30000;            // ADCs independent clock mode sync divde adc_hclk by 4, (for fADC = 25MHz)
    ADC1->CR = 0x50000100;                        // diff mode for cal, out of deep power down and boost on
    ADC2->CR = 0x50000100;                        // diff mode for cal, out of deep power down and boost on
    delay_us(50);                                            // 10 should be enough
    ADC1->CFGR = 0x1583;                            // overwrite mode, external trigger, rising edge, TRG0 TIM4, DMA circ mode 0001 0101 0110 0011
    ADC2->CFGR = 0x1583;                            // overwrite mode, external trigger, rising edge, TRG0 TIM4, DMA circ mode 0001 0101 0110 0011 
    ADC1->SMPR1 = 0x0800;                            // sample time 32.5fADC on INP3
    ADC2->SMPR1 = 0x4000;                            // sample time 32.5fADC on INP4
    ADC1->SQR1 = 0xc0;                                // reg sequence of 1 channel at input 3
    ADC2->SQR1 = 0x0100;                            // reg sequence of 1 channel at input 4
    ADC1->CR |= 0x00010000;                        // linearity 
    ADC2->CR |= 0x00010000;                        // linearity 
    ADC1->CR |= 0x80000000;                        // do cal
    ADC2->CR |= 0x80000000;                        // do cal
    delay_us(10);                                            // give ADC time to notice request
    while((ADC1->CR & 0x80000000) != 0){};
    while((ADC2->CR & 0x80000000) != 0){};        // let them both finish
    ADC1->DIFSEL = 0x08;                            // set differential mode on INP3
    ADC2->DIFSEL = 0x10;                            // set differential mode on INP4
    ADC1->PCSEL = 0x088;                            // set the pre select registers for input 3, and INP7 for INP 3 diff
    ADC2->PCSEL = 0x110;                            // set the pre select registers for input 4, and INP8 for INP 4 diff
        
    ADC1->CR |= 1;                                        // enable ADC    
    ADC2->CR |= 1;                                        // enable ADC    
    while((ADC1->ISR & 1) == 0){};
    while((ADC2->ISR & 1) == 0){};        // wait for both to be ready
    //ADC1->CR |= 4;                                        // start the ADC
    //ADC2->CR |= 4;                                        // start the ADC
        
    
    }
    
    void init_dma(void)
    {
    // DMA1, stream 0  ADC1
    // DMA1, stream 1  ADC2
    // DMA2, stream 0  DAC1
    // DMA2, stream 1  DAC2
        
    __HAL_RCC_DMA1_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
        
    DMA1_Stream0->CR = 0x00022d00;        // dbm = off priority = high,mize = psize = 01 for 16 bit, minc = 1,pinc = 0, circ = 1, dir = 00 pfctrl = 0, no interrupts, disabled  0000 0000 0000 0010 0010 1101 0000 0000
    DMA1_Stream1->CR = 0x00022d00;
    DMA2_Stream0->CR = 0x00032d40;        // dbm off, priority = vhigh,mize = psize = 01 for 16 bit, minc = 1,pinc = 0, circ = 1, dir = 01 pfctrl = 0, no interrupts, disabled  0000 0000 0000 0111 0010 1101 0100 0000
    DMA2_Stream1->CR = 0x00032d40;
    
    DMA1_Stream0->NDTR = 2048;
    DMA1_Stream1->NDTR = 2048;
    DMA2_Stream0->NDTR = 2048;
    DMA2_Stream1->NDTR = 2048;
        
    DMA1_Stream0->PAR = (uint32_t) &(ADC1->DR);
    DMA1_Stream1->PAR = (uint32_t) &(ADC2->DR);
    DMA2_Stream0->PAR = (uint32_t) &(DAC1->DHR12R1);
    DMA2_Stream1->PAR = (uint32_t) &(DAC1->DHR12R2);
    
    DMA1_Stream0->M0AR = (uint32_t) &adc_buffer[0][0];
    DMA1_Stream1->M0AR = (uint32_t) &adc_buffer[1][0];
    DMA2_Stream0->M0AR = (uint32_t) &dac_buffer[0][0];
    DMA2_Stream1->M0AR = (uint32_t) &dac_buffer[1][0];
    //DMA2_Stream0->M1AR = ((uint32_t) dac1_buffer) + 2048;
    //DMA2_Stream1->M1AR = ((uint32_t) dac2_buffer) + 2048;
    
    // fifo control left at default (fifo not used)
    
    
    DMAMUX1_Channel0->CCR = 9;                // sync disabled , just select the basic mpx input
    DMAMUX1_Channel1->CCR = 10;
    DMAMUX1_Channel8->CCR = 67;
    DMAMUX1_Channel9->CCR = 68;
    
    DMA1_Stream0->CR |= 1;                        // Enable DMA
    DMA1_Stream1->CR |= 1;
    DMA2_Stream0->CR |= 1;        
    DMA2_Stream1->CR |= 1;
    
    }
    
    // DELAY FUNCTION
    // Calibrated for STM32H743Zi at 400MHz internal RC clock 
    // Optimisation level 0 Compiler: 'V5.06 update 6 (build 750)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin'
    // Driven by timer 5 so pretty much as good as uP clock
    // call for 1     , get 1.25
    // call for 10    , get 10
    // call for 100    , get 100.5
    // call for 500 , get 500
    // call for 5000, get 5000 
    
    
    #define DELAY_US_MIN 1
    #define DELAY_MULT 200
    #define DELAY_DIV  1
    #define DELAY_TRIM 50
    
    void delay_us(uint32_t delay)
    {
    volatile uint32_t d;
    uint32_t dm;
    
    if (delay < DELAY_US_MIN)
        {
        return;
        }
    dm = delay * DELAY_MULT;
    //dm /= DELAY_DIV;
    dm -= DELAY_TRIM;
    TIM5->CNT = 0;
    while(TIM5->CNT < dm){};
    }
    
    void delay_test(void)
    {
    PIN_SET(DB0);
    delay_us(1);
    PIN_CLR(DB0);    
    delay_us(1);
    
    PIN_SET(DB0);
    delay_us(10);
    PIN_CLR(DB0);    
    delay_us(10);
    
    PIN_SET(DB0);
    delay_us(100);
    PIN_CLR(DB0);    
    delay_us(100);
    
    PIN_SET(DB0);
    delay_us(500);
    PIN_CLR(DB0);    
    delay_us(500);
    
    PIN_SET(DB0);
    delay_us(5000);
    PIN_CLR(DB0);    
    delay_us(5000);
    }
    
    
    
    void init_acquisition(void)
    {
    TIM4->CR1 &= 0xfffffffe;                    // stop timer
    TIM2->CR1 &= 0xfffffffe;                    // stop sample counter
    TIM2->CNT = 0;                                        // sample count set to zero
    TIM4->CNT = 240;                                    // preset sample timer
    n_written[0] = 0;
    n_written[1] = 0;
    n_read[0] = 0;
    n_read[1] = 0;
    while((ADC1->CR & 4) != 0)                // wait for any pending conversion to end
        {}
    if ((ADC1->CR & 1) != 0)                    // if ADC is enabled
        {
        ADC1->CR |= 2;                                    // disable it
        while ((ADC1->CR & 1) != 0)            // wait for disable to work
            {}        
        }
    while((ADC2->CR & 4) != 0)                // wait for any pending conversion to end
        {}
    if ((ADC2->CR & 1) != 0)                    // if ADC is enabled
        {
        ADC2->CR |= 2;                                    // disable it
        while ((ADC2->CR & 1) != 0)            // wait for disable to work
            {}        
        }
    DMA1_Stream0->CR &= 0xfffffffe;        // Disable all DMA
    DMA1_Stream1->CR &= 0xfffffffe;
    DMA2_Stream0->CR &= 0xfffffffe;        
    DMA2_Stream1->CR &= 0xfffffffe;
    
            
    }
    
    void start_acqusition(void)
    {
    DMA1->LIFCR = 0xf7d;                            // clear stream 0 and 1 flags
    DMA2->LIFCR = 0xf7d;                            // necessaary before re-starting DMA
        
    DMA1_Stream0->CR |= 1;                        // Enable DMA
    DMA1_Stream1->CR |= 1;
    DMA2_Stream0->CR |= 1;        
    DMA2_Stream1->CR |= 1;
    
    ADC1->CR |= 1;                                        // enable ADCs
    ADC2->CR |= 1;                                        
    
    while(((ADC1->ISR & 1) == 0)||((ADC1->ISR & 1) == 0))
    {}                                                                // wait for them to bee ready
        
        
    
    TIM2->CR1 |= 0x1;
    TIM4->CR1 |= 0x1;
    DAC1->CR |= 0x10001;                             // dacs enabled
    ADC1->CR |= 4;                                        // start the ADC
    ADC2->CR |= 4;                                        // start the ADC
    
    acq_running = true;
    }
    
    void stop_acquisition(void)
    {
    TIM4->CR1 &= 0xfffffffe;
    acq_running = false;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    //------------------------------------------------------------------------------------
    // h7synth_adc.h
    //------------------------------------------------------------------------------------
    // Copyright 2020 MK ELECTRONICS LTD
    //
    // AUTH: Michael Kellett
    // DATE 03/03/2020
    //
    // H7 Synth tester
    //
    // Target  STM32H743Zi on MKE H7SYNTH board
    //
    //
    //
    // Tool chain  KEIL
    //
    
    
    // Define to prevent recursive inclusion 
    #ifndef H7SYNTH_ADC_H
    #define H7SYNTH_ADC_H
    
    #include <stdint.h>
    #include <stdbool.h>
    
    // GLOBAL DEFNITIONS
    #define DAT_BUFFER_SIZE     2048
    #define TRIG_BUFFER_SIZE    4096
    
    // FUNCTION DECLARATIONS
    
    extern void adc_init(void);
    extern void delay_us(uint32_t delay);
    extern void delay_test(void);
    extern void init_tim5(void);
    extern void init_dma(void);
    extern void init_acquisition(void);
    extern void start_acqusition(void);
    extern void stop_acquisition(void);
    
    
    
    // GLOBAL VARIABLES
    
    extern uint16_t adc_buffer[2] [DAT_BUFFER_SIZE]__attribute__((section(".ARM.__at_0x30000000")));
    extern uint16_t dac_buffer[2][DAT_BUFFER_SIZE] __attribute__((section(".ARM.__at_0x30000000")));
    extern float dds_sines[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    extern float dds_cosines[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    extern uint32_t dds_pa[2][TRIG_BUFFER_SIZE]; // __attribute__((section(".ARM.__at_0x24000000")));
    
    extern float fsin_table_16[65536] __attribute__((section(".ARM.__at_0x24000000")));
    extern float capture[65536] __attribute__((section(".ARM.__at_0x24000000")));
    
    
    
    extern bool acq_running;
    
    
    #endif //  H7SYNTH_ADC_H

     

     

    Once every two ms a function checks the DMA buffers for content and processes whatever is there.

     

    This code is work in progress - be very careful when using it that you fully undertsnad what it actually does , which may not be quite what the comments say image

     

    MK

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • abadialali
    abadialali over 5 years ago in reply to michaelkellett

    my project include two main things, that's generating a signal with the maximum sampling frequency I can get from the DAC (one channel needed) and read that signal back using the ADC.

     

    Thank you for the code and your notes, so from what I understand is that I should flash the processor with your project code. I am not exactly sure how though? Can I still use the Portenta board and flash it with this code? and you mentioned not being able to use the arduino IDE. please correct me if I am wrong but I thought that if it is only about using the registers should be able to still use it without using its libraries (and use your libraries instead) since it does support C and C++, which makes me think that there should be a way to flash your code using it.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • michaelkellett
    michaelkellett over 5 years ago in reply to abadialali

    It's nothing like as simple as that.

    The code I posted is not a complete workable project but an example of how to set up ADC and DMA.

    To make this thing work you will need to write your own code for the Portenta and I don't recommend the Portenta for learning this stuff.

     

    If your project is just an exercise then the actual maximum frequency doesn't really matter, and you could learn a lot from trying to do it in Arduino.

     

    To get the most out of the processor I suggest you use ST or Keil tools and an ST Nucleo board.

    You need a board with proper debugging for this sort of work and I don't think the Arduino IDE provides that.

     

    MK

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • abadialali
    abadialali over 5 years ago in reply to michaelkellett

    Thank you very much.

     

    It is actually part of prototyping for my PhD, so the speed does matter. I think getting an st board is the best way and it would give me the ability to go to lower level of programing and the chance to also go on and program a chip and build my own board later on.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • michaelkellett
    michaelkellett over 5 years ago in reply to abadialali

    Are you able to tell us the purpose in a bit more detail - it's often much easier to help out when we know the background.

    The dev board I've used is the ST Nucleo-H743zi - its very cheap and has a lot of processor pins accessible.

    I used it to prototype this FRA.

     

    MK

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • abadialali
    abadialali over 5 years ago in reply to michaelkellett

    yes for sure, I am interested in generating a random white noise signal with different frequency components and apply it as a current to a device to measure it's impedance by monitoring the voltage on it I need the DAC to generate the signal and the ADC to measure it, this means I will need to get the ADC to sample right after the DAC generates and the extra resolution in the ADC is needed so that I can get the output voltage with high precision since it is most likely lower than the generated signal.

     

    So in short while you are using your code for an FRA I would like to use it for an Impedance analyzer, after changing your signal from sin to sth I will create using matlab and then save in an array in the controller.

     

    this reference might help, https://ieeexplore.ieee.org/document/9052456

     

     

    Thank you for mentioning the chip you are using, I will look into it and mostly order it. However, for someone who has lots of experience programing Arduino and python, will it be easy to transit to ST boards and use StCube. I will also be interested in adding bluetooth, wifi and/serial to transfer the data to my PC for further processing or just to save it.

     

    Thanks again,

    • Cancel
    • Vote Up 0 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