Low threshold DIY oscilloscope and CH32 oscilloscope are fully compatible with STM32, ADC+DMA+TIM+USB, and only one minimum system board is required

preface

You can't play electronics without an oscilloscope, and you can't go whoring with a school oscilloscope after graduation. As the saying goes: no conditions create conditions. If there is no oscilloscope, create an oscilloscope. It sounds tall, but it's not difficult. There are many open source oscilloscopes on the Internet, the most representative of which is Lao Liu oscilloscope . Although the circuit is simple and powerful, and the party is too lazy to do it, it also has a great threshold. It doesn't want to weld, draw PCB, or pick various components. It wants a simple and practical oscilloscope with low cost, rapid implementation and convenient expansion. If you don't bother to find it, you'd better make your own wheels.

Tip: this project only uses a treasure finished product CH32F103C8T6 development board. The hardware is simple. You can add software and hardware functions according to your needs.

1, Look at the results first



Note: A1 pin is the signal input pin, and A0 outputs 1KHz square wave signal for correction. It has the following functions and characteristics:

  1. A0 port has 3.3V 1KHz basic square wave output for calibration.
  2. The signal measurement range of A1 signal input port is 0~3.3V.
  3. 32 MCU supports 1M sampling rate at most, but when the system clock is 72M, it supports 857.1k sampling rate at most (related to ADC clock frequency division coefficient). In accordance with Nyquist sampling law, the sampling accuracy is 12 bits.
  4. The upper computer is developed in Python, compatible with Linux and Windows systems, and communicates through the USB virtual serial port of 32 single chip microcomputer.
  5. The maximum cache of USB virtual serial communication packet is 1200 bytes, so the number of single sampling points is limited to 512 in the program (one sampling point accounts for two bytes).
  6. It has basic measurement functions: measuring frequency, pulse width and duty cycle (self reading).
  7. Only automatic rising edge trigger function.
  8. add to Bessel interpolation function
  9. Project open source https://github.com/ClassmateXie/32Oscilloscopes

2, Use steps

1 run the software

1.1 Windows users directly run the packaged software

CH32 oscilloscope exe

1.2 install Python environment operation source code (pit entry)

Enter CH32 oscilloscope py file directory, right-click to open the terminal

Enter the command line

python .\CH32 Oscilloscope.py

If an error is reported, install the corresponding python library according to the prompt, for example:

pip install pyqtgraph
pip install numpy
pip install pyserial
pip install PyQt5
pip install scipy

2 select port

The initial interface is as follows:

Modify the virtual serial port to the corresponding port (viewed in the device manager, the default is COM7), and the communication baud rate is 1000000 by default

3, Source code analysis

Most of the time in the development process of the project is to consolidate the basic knowledge. Although the overall difficulty of the project is small, we have a deeper understanding of the functions of ADC, DMA, TIM, NVIC and USB of 32 single chip microcomputer in the development process. The development process is hereby recorded.

1. Overall flow chart of procedure

1.1 USB serial port interrupt service function

1.2 ADC sampling process

1.3 DMA transmission process

2 lower computer source code

2.1 TIM configuration function

Configure the frequency of PWM generated by TIM4 channel 4 as the trigger source of ADC rule group

/*******************************************************************************
* Function name: TIM_ReSet
* Function function 		   :  TIM restart	
* Input: Period reload value, Prescaler frequency division factor
* Output: None
*******************************************************************************/
void TIM_ReSet(u16 Period,u16 Prescaler)
{
	static TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	static TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_TimeBaseStructure.TIM_Period = Period-1; //Set TIM2 comparison cycle
	TIM_TimeBaseStructure.TIM_Prescaler = Prescaler-1;//The main frequency of the system is 72M, which is divided here
	TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//Detailed description below
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
	TIM_OCInitStructure.TIM_Pulse = Period>>1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//If PWM1 is Low, PWM2 is High
	TIM_OC4Init(TIM4, &TIM_OCInitStructure);
	TIM_Cmd(TIM4, ENABLE);
}

2.2 DMA configuration function

u16 buf_len = 512; //USB send cache Max 1200 bytes, buf_len must be less than 600
u16 ADC_Buf[512]={0};
/*******************************************************************************
* Function name: DMA_ReSet
* Function function 		   :  DMA restart	
* Input: len transmission data volume
* Output: None
*******************************************************************************/
void DMA_ReSet(u16 len)
{
	static DMA_InitTypeDef DMA_InitStructure;
	buf_len = len;
	//========DMA configuration=============/
	DMA_ClearFlag(DMA1_FLAG_TC1);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//ADC address
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_Buf; //Memory address
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //Direction (from peripheral to memory)
	DMA_InitStructure.DMA_BufferSize = len; //Size of transmitted content
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //Fixed peripheral address
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //Memory address increment
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //Peripheral data unit
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ;    //Memory data unit
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA mode: single transmission
	DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //Priority: high
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //Disable memory to memory transfer
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);  //Configure channel 1 of DMA1
	DMA_Cmd(DMA1_Channel1,ENABLE);
}

2.3 ADC configuration function

void ADC1_CH1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; //Define structure variables	
	ADC_InitTypeDef  ADC_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
  	/* Configure one bit for preemption priority */
  	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//Enable TIM4 clock
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//Set ADC frequency division factor 6 72m / 6 = 12, and the maximum ADC time cannot exceed 14M
	//==========Port settings====================//
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;	//Analog input
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	//==========ADC configuration====================//
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//Non scanning mode	
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//Turn off continuous conversion
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;//Timer 4 Channel 4 trigger detection
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//Right align	
	ADC_InitStructure.ADC_NbrOfChannel = 1;//One transformation is in the rule sequence, that is, only rule sequence 1 is transformed 
	ADC_Init(ADC1, &ADC_InitStructure);//ADC initialization
	
	ADC_DMACmd(ADC1, ENABLE);//Enable ADC1 module DMA
	
	//=========Timer configuration==============//
//	TIM_ReSet(100,72);//10K sampling rate
	//========DMA configuration=============/
	DMA_DeInit(DMA1_Channel1);
	DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);//Enable DMA transfer completion interrupt
//	DMA_DeInit(DMA1_Channel1);
//	DMA_ReSet(buf_len);
//	DMA_ITConfig(DMA1_Channel1,DMA_IT_HT,ENABLE);
//	DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
//	DMA_ITConfig(DMA1_Channel1,DMA_IT_TE,ENABLE);
	//=======NVIC configuration============//
	/* Configuring DMA1_Channel1 is the interrupt source */
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	ADC_Cmd(ADC1, ENABLE);//Turn on AD converter
	ADC_ResetCalibration(ADC1);//Resets the calibration register of the specified ADC
	while(ADC_GetResetCalibrationStatus(ADC1)){};//Gets the status of the ADC reset calibration register
	ADC_StartCalibration(ADC1);//Start specifying the calibration status of the ADC
	while(ADC_GetCalibrationStatus(ADC1));//Gets the calibration procedure for the specified ADC
	ADC_ExternalTrigConvCmd(ADC1, ENABLE);
	ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_1Cycles5);	//ADC1,ADC channel 1, serial number 1, 1.5 cycles
	
	TIM_InternalClockConfig(TIM4);
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
	TIM_UpdateDisableConfig(TIM4, DISABLE);
}

2.4 USB serial port interrupt service function

//Process data received from USB virtual serial port
//databuffer: data buffer
//Nb_bytes: number of bytes received
void USB_To_USART_Send_Data(u8* data_buffer, u16 Nb_bytes)
{
	u16 i,arr,div,num;
	static u8 temp,step,buf[6],cnt;
	for(i=0;i<Nb_bytes;i++)
	{
		temp = data_buffer[i];
		switch(step)
		{
			case 0:if(temp==0xa5)step=1;break;
			case 1:if(temp==0x5a)step=2;else if(temp==0xa5)step=1;else step=0;break;
			case 2:buf[cnt++]=temp;if(cnt>=6)step=3,cnt=0;break;
			case 3:if(temp==0xff)
						{
							arr=buf[0]*256+buf[1];
							div=buf[2]*256+buf[3];	
							num=buf[4]*256+buf[5];	
							DMA_ReSet(num);
							TIM_ReSet(arr,div);
							step=0;
						}
						else if(temp==0xa5)step=1;
						else step=0;
						break;
		}	
	}
} 

Received data frame format

Frame headerReload valueFrequency division factorSampling pointsEnd of frame
A5 5AXX XXXX XXXX XXFF

2.5 DMA transmission completion interrupt

/*******************************************************************************
* Function name: DMA1_Channel1_IRQHandler
* Function function 		   	 :  DMA channel 1 interrupt
* Input: None
* Output: None
*******************************************************************************/
void DMA1_Channel1_IRQHandler(void)
{
	u16 i;
	if(DMA_GetFlagStatus(DMA1_FLAG_TC1))
	{
		TIM_Cmd(TIM4, DISABLE);
		DMA_Cmd(DMA1_Channel1,DISABLE);
		for(i=0;i<buf_len;i++)USB_USART_SendData((ADC_Buf[i]>>8)),USB_USART_SendData(ADC_Buf[i]&0xff);
		DMA_ClearFlag(DMA1_FLAG_TC1); //Clear all interrupt flags
		LED=!LED;
	}
}

2.6 square wave generator and main function

u16 LED_Breathe_Buf[1000];
void Buf_Dataset(u16* buf)
{
	u16 temp;
	for(temp=0;temp<500;temp++)buf[temp]=temp*2+100;
	for(temp=0;temp<500;temp++)buf[temp+500]=999-temp*2+100;
}

void TIM2_Init(u16 Period,u16 Prescaler)
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//Enable TIM2 clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;	//Multiplexed push-pull output
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	TIM_TimeBaseStructure.TIM_Period = Period-1; //Set TIM2 comparison cycle
	TIM_TimeBaseStructure.TIM_Prescaler = Prescaler-1;//The main frequency of the system is 72M, which is divided here
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//Detailed description below
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
	TIM_OCInitStructure.TIM_Pulse = Period>>1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//If PWM1 is Low, PWM2 is High
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
	NVIC_Init(&NVIC_InitStructure);//Initialize the interrupt and set the priority of the interrupt
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//Start timer interrupt
	
	TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);//Timer 2 channel 1
	TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);//Timer 2 Channel 2
	
	TIM_Cmd(TIM2, ENABLE);
}

int main(void)
{	
	Buf_Dataset(LED_Breathe_Buf);
	delay_init();	  	 	//Delay function initialization
 	LED_Init();			   	//LED port initialization
	USB_Init();				 	//USB virtual serial port initialization
	ADC1_CH1_Init();		//ADC initialization
	TIM2_Init(1200,60); //72000000/1200/60=1000Hz
	while(1)
	{
		LED=PAin(2);
	}	 
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
	{
		static u16 cnt;
		TIM_SetCompare3(TIM2,LED_Breathe_Buf[cnt]);
		if(cnt++>=1000)cnt=0;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//Clear interrupt flag bit
	}
}

3 upper computer source code

  1. pyqtgraph Library for scientific mapping, official documents: https://www.pyqtgraph.org/
  2. pyserial Serial communication with library, official document: https://pyserial.readthedocs.io/
  3. PyQt5 Library design GUI

Full code:

import pyqtgraph as pg
import numpy as np
import serial
import time,struct
import pyqtgraph.parametertree as ptree
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, QT_LIB
from scipy import interpolate 
from PyQt5.QtWidgets import *

N = 512 # Sampling points
k = 3.3/4096 # Scale factor
arr = 400 # Reload value
div = 72 # Frequency division factor
Fs = 72000000/arr/div # sampling rate
Ts = 1/Fs # sampling period 
COM = 'COM7'

# GUI design
app = pg.mkQApp()

#============Waveform display window=====================#
w = pg.GraphicsLayoutWidget()
w.setWindowTitle("CH32 Oscilloscope-By pure")
# Coordinate system control
p = w.addPlot()
p.showGrid(x=True,y=True)
p.setRange(xRange=[-80,80],yRange=[-1,4],padding=0)
p.setLabels(left='Voltage / V',bottom="t / ms",title='CH32 Oscilloscope')
# Time baseline and trigger threshold
inf1 = pg.InfiniteLine(movable=True, angle=90, label='Time base={value:0.2f}ms', 
                       labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True})
inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='trigger={value:0.2f}V', 
                       labelOpts={'color': (0,200,200), 'movable': True, 'fill': (0, 0, 200, 100)})
inf1.setPos([0,0])
inf2.setPos([0,1])

p.addItem(inf1)
p.addItem(inf2)

curve = p.plot(pen='y') # Curve control
#============Parameter adjustment window=====================#
children=[
	dict(name='Sampling configuration', title='Sampling configuration', type='group', children=[
		dict(name='sampling rate', type='float', limits=[0.0001, 857.143], value=Fs/1000, units='kHz'),
        dict(name='Reload value', type='int', limits=[2, 65535], value=arr),
        dict(name='Frequency division factor', type='int', limits=[1, 65536], value=div),
		dict(name='Sampling points', type='int', limits=[0, 512], value=N),
		dict(name='Scale factor', type='float', value=1),
    ]),
	dict(name='Virtual serial port', type='str', value=COM),
	dict(name='Baud rate', type='int', limits=[4800, 20000000], value=1000000),
	dict(name='trigger', type='float', value=inf2.getYPos(), units='V'),
	dict(name='Time base', type='float', value=inf1.getXPos(), units='ms'),
	dict(name='Curve style', type='pen', value=pg.mkPen()),
	dict(name='Bessel interpolation', type='bool', value=True),
]
params = ptree.Parameter.create(name='Adjust parameters', type='group', children=children)

def onChanged0(param, val):
	global arr,div,Fs,Ts
	temp = int(72000/val/arr)
	if 1 < temp < 65536:
		params.child('Sampling configuration').child('Frequency division factor').setValue(temp)
	else:
		params.child('Sampling configuration').child('Frequency division factor').setValue(1)
		temp = int(72000/val)
		if 2 < temp < 65536:
			params.child('Sampling configuration').child('Reload value').setValue(temp)	


def onChanged1(param, val):
	global arr,div,Fs,Ts
	if 72000000/val/div > 857143:
		params.child('Sampling configuration').child('Reload value').setValue(arr)
		return
	arr = val
	Fs = 72000000/arr/div
	Ts = 1/Fs
	params.child('Sampling configuration').child('sampling rate').setValue(Fs/1000)

def onChanged2(param, val):
	global arr,div,Fs,Ts
	if 72000000/val/arr > 857143:
		params.child('Sampling configuration').child('Frequency division factor').setValue(div)
		return
	div = val
	Fs = 72000000/arr/div
	Ts = 1/Fs
	params.child('Sampling configuration').child('sampling rate').setValue(Fs/1000)

def onChanged3(param, val):
	global N
	N = val
def onChanged4(param, val):
	inf1.setPos([val,0])

def onChanged5(param, val):
	inf2.setPos([0,val])

def onPenChanged(param, pen):
    curve.setPen(pen)

params.child('Sampling configuration').child('sampling rate').sigValueChanged.connect(onChanged0)
params.child('Sampling configuration').child('Reload value').sigValueChanged.connect(onChanged1)
params.child('Sampling configuration').child('Frequency division factor').sigValueChanged.connect(onChanged2)
params.child('Sampling configuration').child('Sampling points').sigValueChanged.connect(onChanged3)

params.child('Time base').sigValueChanged.connect(onChanged4)
params.child('trigger').sigValueChanged.connect(onChanged5)
params.child('Curve style').sigValueChanged.connect(onPenChanged)

def On_inf1Changed():
	params.child('Time base').setValue(inf1.getXPos())
inf1.sigPositionChanged.connect(On_inf1Changed)

def On_inf2Changed():
	params.child('trigger').setValue(inf2.getYPos())
inf2.sigPositionChanged.connect(On_inf2Changed)

pt = ptree.ParameterTree(showHeader=False)
pt.setParameters(params)
# Button
StartBtn = QtGui.QPushButton('start')
StopBtn = QtGui.QPushButton('suspend')
ContinueBtn = QtGui.QPushButton('continue')
EndBtn = QtGui.QPushButton('stop it')

run_flag = False
def On_Start():
	global run_flag,ser
	try:
		ser = serial.Serial(params.child('Virtual serial port').value(),params.child('Baud rate').value())
		run_flag = True
		StartBtn.setEnabled(False)
		StopBtn.setEnabled(True)
		ContinueBtn.setEnabled(False)
		EndBtn.setEnabled(True)
	except:
		QtWidgets.QMessageBox(QMessageBox.Warning, 'warning', 'Failed to open virtual serial port').exec_()
	

def On_Continue():
	global run_flag
	run_flag = True
	StartBtn.setEnabled(False)
	StopBtn.setEnabled(True)
	ContinueBtn.setEnabled(False)
	EndBtn.setEnabled(True)

def On_Stop():
	global run_flag
	run_flag = False
	StartBtn.setEnabled(False)
	StopBtn.setEnabled(False)
	ContinueBtn.setEnabled(True)
	EndBtn.setEnabled(False)

def On_End():
	global run_flag,ser
	ser.close()
	run_flag = False
	StartBtn.setEnabled(True)
	StopBtn.setEnabled(False)
	ContinueBtn.setEnabled(False)
	EndBtn.setEnabled(False)

StartBtn.clicked.connect(On_Start)
StopBtn.clicked.connect(On_Stop)
ContinueBtn.clicked.connect(On_Continue)
EndBtn.clicked.connect(On_End)

StartBtn.setEnabled(True)
StopBtn.setEnabled(False)
ContinueBtn.setEnabled(False)
EndBtn.setEnabled(False)

#================main window=====================#
win = QtGui.QMainWindow()
win.resize(1000,600)
win.setWindowTitle("CH32 Oscilloscope-By pure")
#================Add control in main window=====================#
cw = QtGui.QWidget()
win.setCentralWidget(cw)

layout = QtGui.QGridLayout()
layout.addWidget(w, 1, 1, 6, 1)
layout.addWidget(pt, 1, 2, 1, 2)
layout.addWidget(StartBtn, 2, 2, 1, 2)
layout.addWidget(StopBtn, 3, 2, 1, 2)
layout.addWidget(ContinueBtn, 4, 2, 1, 2)
layout.addWidget(EndBtn, 5, 2, 1, 2)
cw.setLayout(layout)
win.show()

#================Open virtual serial port=====================#
# ser = serial.Serial(params.child('virtual serial port ') value(),params.child('baud rate ') value())

#=======Moving average filter======#
fps_buf = np.linspace(0,0,100)
fps_buf_ptr = 0
def fps_mean(fps):
	global fps_buf,fps_buf_ptr
	fps_buf[fps_buf_ptr] = fps
	fps_buf_ptr += 1
	if fps_buf_ptr >= 100:
		fps_buf_ptr = 0
	return np.mean(fps_buf)
#==========================#
#=======Moving average filter======#
fc_buf = np.linspace(0,0,20)
fc_buf_ptr = 0
def fc_mean(fc):
	global fc_buf,fc_buf_ptr
	fc_buf[fc_buf_ptr] = fc
	fc_buf_ptr += 1
	if fc_buf_ptr >= 20:
		fc_buf_ptr = 0
	return np.mean(fc_buf)
#==========================#

fps_t0 = cnt = data0 = data1 = 0
start_flag = False
data_buf = np.array([]) # Data cache

#Update data and display waveform
def display_data():
	global cnt,start_flag,data0,data1,data_buf,t0,fps_t0,run_flag
	if run_flag == False:
		return
	if start_flag:
		buf_len = ser.in_waiting
		if buf_len == N*2:
			num = buf_len>>1
			temp = struct.unpack('>%dH'%(num),ser.read(num*2)) # Read and parse data

			if temp[0] > 4096: # 12 bit ADC data is less than 4096
				ser.read() # Data error, empty receive cache
				return

			if len(data_buf) < N:
				data_buf = np.append(data_buf,temp)
			else:
				data_buf = np.append(data_buf,temp)
				data_buf = data_buf[-N:]

			output = data_buf*k*params.child('Sampling configuration').child('Scale factor').value() # Unit conversion to voltage
			output_len = len(output)
			t = np.linspace(-500*Ts*output_len,500*Ts*output_len,output_len) # Generating initial timeline unit: ms
			if params.child('Bessel interpolation').value():
				t_new = np.linspace(-500*Ts*output_len,500*Ts*output_len,output_len*10)
				tck = interpolate.splrep(t, output)
				output_bspline = interpolate.splev(t_new, tck)
				output_bspline_len = len(output_bspline)

				comparator = np.array(output_bspline > inf2.getYPos(),dtype='int8')
				rising = np.where(np.diff(comparator) == 1)[0]
				if len(rising) > 1:
					dt = np.mean(np.diff(rising))*Ts/10
					fc = fc_mean(1/dt)
					start_point = int(output_bspline_len/2+inf1.getXPos()/Ts/100) # Start trigger point index
					end_point_index = np.where( rising > start_point)[0]
					if len(end_point_index) > 0:
						# Trigger successfully generates a new timeline unit: ms
						t_new = np.linspace(-100*Ts*(rising[end_point_index[0]]+1)+inf1.getXPos(),
										 100*Ts*(output_bspline_len-rising[end_point_index[0]]-1)+inf1.getXPos(),output_bspline_len)
				else:
					fc = 0

				curve.setData(t_new,output_bspline) # draw a curve
			else:
				comparator = np.array(output > inf2.getYPos(),dtype='int8')
				rising = np.where(np.diff(comparator) == 1)[0]
				if len(rising) > 1:
					dt = np.mean(np.diff(rising))*Ts
					fc = fc_mean(1/dt)
					start_point = int(output_len/2+inf1.getXPos()/Ts/1000) # Start trigger point index
					end_point_index = np.where( rising > start_point)[0]
					if len(end_point_index) > 0:
						# Trigger successfully generates a new timeline unit: ms
						t = np.linspace(-1000*Ts*(rising[end_point_index[0]]+1)+inf1.getXPos(),
										 1000*Ts*(output_len-rising[end_point_index[0]]-1)+inf1.getXPos(),output_len)
				else:
					fc = 0
				curve.setData(t,output) # draw a curve

			fps_t = fps_mean(time.time()-fps_t0) # Frame rate mean filtering
			fps_t0 = time.time()

			p.setTitle('CH32 Oscilloscope %0.2f fps Fs = %0.3f kHz fc = %0.3f Hz' % (1/fps_t,Fs/1000,fc))
			cnt += num
		if cnt >= N or time.time() - t0 > N*Ts+0.01:# Receive data full or receive timeout
			cnt = 0
			start_flag = False

	else:
		ser.read(ser.in_waiting)
		data_buf = np.array([]) 
		result=ser.write(bytes([0xa5, 0x5a, arr>>8, arr&0xff, div>>8, div&0xff, N>>8, N&0xff, 0xff]))# send data

		t0 = time.time()
		while time.time() - t0 < N*Ts+0.01:# Waiting for response
			if ser.in_waiting:
				start_flag = True
				t0 = time.time()
				break


timer = pg.QtCore.QTimer()
timer.timeout.connect(display_data)
timer.start(0)
app.exec_()

4, Performance test

1 signal transmitter


Comparing STM32 with CH32, I was surprised to find that CH32 also has DAC function. According to the official library, there are two signal transmitter codes, as follows:

/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2019/10/15
* Description        : Main program body.
*******************************************************************************/ 
#include "debug.h"
#include "math.h"
/* Global define */
#define LED PCout(13)
#define buf_len 50
/* Global Variable */ 
u32 DAC_Buf[buf_len];

/*******************************************************************************
* Function Name  : Gpio_Init
* Description    : Initializes GPIO collection.
* Input          : None
* Return         : None
*******************************************************************************/ 
void Gpio_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 
	GPIO_Init(GPIOC, &GPIO_InitStructure);					 
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
}

/*******************************************************************************
* Function Name  : Dac_Init
* Description    : Initializes DAC collection.
* Input          : None
* Return         : None
*******************************************************************************/ 
void Dac_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	DAC_InitTypeDef DAC_InitType;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE );
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;				          
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		     
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA,GPIO_Pin_4);

	DAC_InitType.DAC_Trigger=DAC_Trigger_T3_TRGO;	                         
	DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;             
	DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ;	         
	DAC_Init(DAC_Channel_1,&DAC_InitType);

	DAC_Cmd(DAC_Channel_1, ENABLE); 
	DAC_DMACmd(DAC_Channel_1,ENABLE); 
}


/*******************************************************************************
* Function Name  : DAC1_DMA_INIT
* Description    : Initializes DMA of DAC1 collection.
* Input          : None
* Return         : None
*******************************************************************************/
void DAC1_DMA_Init(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        

	DMA_StructInit( &DMA_InitStructure);
	/* Note:DAC1--->DMA1.CH3   DAC2--->DMA1.CH4 */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(DAC->R12BDHR1);
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DAC_Buf;   
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
	DMA_InitStructure.DMA_BufferSize = buf_len*4;    
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              
	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  

	DMA_Init(DMA1_Channel3, &DMA_InitStructure);
	DMA_Cmd(DMA1_Channel3, ENABLE); 
}

/*******************************************************************************
* Function Name  : DAC_Data_Init
* Description    : Initializes Data of DMA.
* Input          : None
* Return         : None
*******************************************************************************/
void DAC_Data_Init(void)
{
	uint32_t Idx = 0;  
	for (Idx = 0; Idx < buf_len; Idx++)
	{
		DAC_Buf[Idx] = 2000*sin(Idx*3.14159265*2/buf_len)+2048;
	}
}

/*******************************************************************************
* Function Name  : TIM3_Init
* Description    : Initializes TIM3 collection.
* Input          : arr: TIM_Period
*                  psc: TIM_Prescaler
* Return         : None
*******************************************************************************/
void TIM3_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	GPIO_InitTypeDef GPIO_InitStructure; 
	TIM_OCInitTypeDef TIM_OCInitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);

	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;	//Multiplexed push-pull output
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	TIM_TimeBaseStructure.TIM_Period = arr-1;             
	TIM_TimeBaseStructure.TIM_Prescaler = psc-1;           
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 

	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);  

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//Detailed description below
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
	TIM_OCInitStructure.TIM_Pulse = arr>>1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//If PWM1 is Low, PWM2 is High
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);
	TIM_Cmd(TIM3, ENABLE); 				 
}


/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	Delay_Init();

	Gpio_Init();
	Dac_Init();
	DAC_Data_Init();
	DAC1_DMA_Init(); 
	TIM3_Init(10,36); //Overflow rate 200kHz generates 1kHz sine wave

	while(1)
	{	
		LED = !LED;
		Delay_Ms(500);
	}
}

2 square wave test

2.1 1kHz square wave quantity results

2.2 10kHz square wave quantity results

2.3 results of 100kHz square wave quantity

2.4 400kHz square wave quantity results

3 sine wave test

3.1 50Hz sine wave quantity results

3.2 1kHz sine wave quantity results

3.3 10kHz sine wave quantity results


Tip: CH32 DAC is inconvenient to generate high-frequency sine wave. The measurement results are for reference only

Keywords: Python Single-Chip Microcomputer stm32 USB

Added by chrille112 on Fri, 07 Jan 2022 03:12:31 +0200