• 售前

  • 售后

热门帖子
入门百科

低门槛DIY示波器,CH32示波器,完全兼容STM32,ADC+DMA+TIM+USB,仅需一块

[复制链接]
凡尘莲花1 显示全部楼层 发表于 2022-1-8 17:28:07 |阅读模式 打印 上一主题 下一主题
文章目次



  • 前言
  • 一、先看结果
  • 二、使用步调


    • 1 运行软件


      • 1.1 Windows用户直接运行打包好的软件
      • 1.2 安装Python情况运行源代码(入坑)
         
    • 2 选择端口
      
  • 三、源码分析


    • 1 步调总体流程图


      • 1.1 USB串口中断服务函数
      • 1.2 ADC采样过程
      • 1.3 DMA传输过程
         
    • 2 下位机源代码


      • 2.1 TIM设置函数
      • 2.2 DMA设置函数
      • 2.3 ADC设置函数
      • 2.4 USB串口中断服务函数
      • 2.5 DMA传输完成中断
      • 2.6 方波发生器及主函数
         
    • 3 上位机源代码
      
  • 四、性能测试


    • 1 再造信号发送器
    • 2 方波测试


      • 2.1 1kHz方波量结果
      • 2.2 10kHz方波量结果
      • 2.3 100kHz方波量结果
      • 2.4 400kHz方波量结果
         
    • 3 正弦波测试


      • 3.1 50Hz正弦波量结果
      • 3.2 1kHz正弦波量结果
      • 3.3 10kHz正弦波量结果
        



提示:本项目仅使用某宝制品CH32F103C8T6开发板,硬件简朴,可自行根据需要自行添加软硬件功能。
一、先看结果



阐明:A1管脚为信号输入管脚,A0输出1KHz方波信号举行校正,具有以下功能和特点:

  • A0口有3.3V 1KHz根本方波输出,用于校准。
  • A1信号输入口信号测量范围0~3.3V。
  • 32单片机最高支持1M采样率,但体系时钟为72M时最高支持857.1k采样率(跟ADC时钟分频系数有关)。符合奈奎斯特采样定律,采样精度12位。
  • 上位机采取Python开发,兼容Linux、Windows体系,通过32单片机的USB假造串口通信。
  • USB假造串口通信数据包缓存最大1200字节,故步调中限定单次采样点数为512个(一个采样点占两个字节)。
  • 具有根本测量功能:测量频率,脉宽,占空比(自己读数)。
  • 仅有自动上升沿触发功能。
  • 添加贝塞尔插值功能
  • 项目开源https://github.com/ClassmateXie/32Oscilloscopes
二、使用步调

1 运行软件

1.1 Windows用户直接运行打包好的软件

CH32示波器.exe
1.2 安装Python情况运行源代码(入坑)

进入CH32示波器.py文件所在目次,右键打开终端

输入下令行
  1. python .\CH32示波器.py
复制代码
若报错需根据提示自行安装对应的python库,比方:
  1. pip install pyqtgraph
  2. pip install numpy
  3. pip install pyserial
  4. pip install PyQt5
  5. pip install scipy
复制代码
2 选择端口

初始界面如下:

修改假造串口为对应的端口(装备管理器中检察,默认COM7),通信波特率默认1000000

三、源码分析

项目开发过程的大部分时间都在巩固根本知识,虽然项目团体难度不大,但是在开发过程中对32单片机的ADC、DMA、TIM、NVIC以及USB等功能有了更加深刻的明确,特此记录开发过程。
1 步调总体流程图

1.1 USB串口中断服务函数

                                                                                                                                                                                                                                                                                                                                                                                                                                                        USB接收中断                                                                                      状态机剖析数据                                                                                      重新设置TIM4和DMA                                                                                      启动TIM4和DMA                                              1.2 ADC采样过程

                                                                                                                                                                                                                           触发                                                                    请求                                                                    产生                                                                                     TIM4_CH4                                                                                      ADC采样                                                                                      DMA搬运                                                                                      DMA完成中断                                              1.3 DMA传输过程

                                                                                                                                                                                                                           搬运到                                                                    DMA传输完成中断                                                                                                                                                         ADC->DR数据寄存器                                                                                      ADC_Buf数组                                                                                      USB发送全部缓存                                                                                      清空缓存并关闭TIM4和DMA                                              2 下位机源代码

2.1 TIM设置函数

设置TIM4通道4产生PWM的频率,作为ADC规则组触发源
                                                                                                                       触发                                                                                     TIM4_CH4                                                                                      ADC采样                                             
  1. /*******************************************************************************
  2. * 函 数 名         : TIM_ReSet
  3. * 函数功能                   : TIM重启       
  4. * 输    入         : Period重装载值,Prescaler分频因子
  5. * 输    出         : 无
  6. *******************************************************************************/
  7. void TIM_ReSet(u16 Period,u16 Prescaler)
  8. {
  9.         static TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  10.         static TIM_OCInitTypeDef TIM_OCInitStructure;
  11.         TIM_TimeBaseStructure.TIM_Period = Period-1; //设置TIM2比较的周期
  12.         TIM_TimeBaseStructure.TIM_Prescaler = Prescaler-1;//系统主频72M,这里分频
  13.         TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
  14.         TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  15.         TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
  16.         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//下面详细说明
  17.         TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
  18.         TIM_OCInitStructure.TIM_Pulse = Period>>1;
  19.         TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//如果是PWM1要为Low,PWM2则为High
  20.         TIM_OC4Init(TIM4, &TIM_OCInitStructure);
  21.         TIM_Cmd(TIM4, ENABLE);
  22. }
复制代码
2.2 DMA设置函数

                                                                                                                       搬运到                                                                                     ADC->DR数据寄存器                                                                                      ADC_Buf数组                                             
  1. u16 buf_len = 512; //USB发送缓存最大1200字节,buf_len必须小于600
  2. u16 ADC_Buf[512]={0};
  3. /*******************************************************************************
  4. * 函 数 名         : DMA_ReSet
  5. * 函数功能                   : DMA重启       
  6. * 输    入         : len传输数据量
  7. * 输    出         : 无
  8. *******************************************************************************/
  9. void DMA_ReSet(u16 len)
  10. {
  11.         static DMA_InitTypeDef DMA_InitStructure;
  12.         buf_len = len;
  13.         //========DMA配置=============/
  14.         DMA_ClearFlag(DMA1_FLAG_TC1);
  15.         DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//ADC地址
  16.         DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_Buf; //内存地址
  17.         DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)
  18.         DMA_InitStructure.DMA_BufferSize = len; //传输内容的大小
  19.         DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定
  20.         DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
  21.         DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //外设数据单位
  22.         DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ;    //内存数据单位
  23.         DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA模式:单次传输
  24.         DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //优先级:高
  25.         DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //禁止内存到内存的传输
  26.         DMA_Init(DMA1_Channel1, &DMA_InitStructure);  //配置DMA1的1通道
  27.         DMA_Cmd(DMA1_Channel1,ENABLE);
  28. }
复制代码
2.3 ADC设置函数

                                                                                                                                                                                                                           触发                                                                    请求                                                                    产生                                                                                     TIM4_CH4                                                                                      ADC采样                                                                                      DMA搬运                                                                                      DMA完成中断                                             
  1. void ADC1_CH1_Init(void)
  2. {
  3.         GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量       
  4.         ADC_InitTypeDef  ADC_InitStructure;
  5.         NVIC_InitTypeDef NVIC_InitStructure;
  6.           /* Configure one bit for preemption priority */
  7.           NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  8.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
  9.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
  10.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4时钟
  11.         RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
  12.         //==========端口设置====================//
  13.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC
  14.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;        //模拟输入
  15.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  16.         GPIO_Init(GPIOA,&GPIO_InitStructure);
  17.         //==========ADC配置====================//
  18.         ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  19.         ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式       
  20.         ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
  21.         ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;//定时器4通道4触发检测
  22.         ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐       
  23.         ADC_InitStructure.ADC_NbrOfChannel = 1;//1个转换在规则序列中 也就是只转换规则序列1
  24.         ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
  25.        
  26.         ADC_DMACmd(ADC1, ENABLE);//使能ADC1模块DMA
  27.        
  28.         //=========定时器配置==============//
  29. //        TIM_ReSet(100,72);//10K采样率
  30.         //========DMA配置=============/
  31.         DMA_DeInit(DMA1_Channel1);
  32.         DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);//开启DMA传输完成中断
  33. //        DMA_DeInit(DMA1_Channel1);
  34. //        DMA_ReSet(buf_len);
  35. //        DMA_ITConfig(DMA1_Channel1,DMA_IT_HT,ENABLE);
  36. //        DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
  37. //        DMA_ITConfig(DMA1_Channel1,DMA_IT_TE,ENABLE);
  38.         //=======NVIC配置============//
  39.         /* 配置DMA1_Channel1为中断源 */
  40.         NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
  41.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  42.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  43.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  44.         NVIC_Init(&NVIC_InitStructure);
  45.        
  46.         ADC_Cmd(ADC1, ENABLE);//开启AD转换器
  47.         ADC_ResetCalibration(ADC1);//重置指定的ADC的校准寄存器
  48.         while(ADC_GetResetCalibrationStatus(ADC1)){};//获取ADC重置校准寄存器的状态
  49.         ADC_StartCalibration(ADC1);//开始指定ADC的校准状态
  50.         while(ADC_GetCalibrationStatus(ADC1));//获取指定ADC的校准程序
  51.         ADC_ExternalTrigConvCmd(ADC1, ENABLE);
  52.         ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_1Cycles5);        //ADC1,ADC通道1,序号1,1.5个周期
  53.        
  54.         TIM_InternalClockConfig(TIM4);
  55.         TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
  56.         TIM_UpdateDisableConfig(TIM4, DISABLE);
  57. }
复制代码
2.4 USB串口中断服务函数

  1. //处理从USB虚拟串口接收到的数据
  2. //databuffer:数据缓存区
  3. //Nb_bytes:接收到的字节数.
  4. void USB_To_USART_Send_Data(u8* data_buffer, u16 Nb_bytes)
  5. {
  6.         u16 i,arr,div,num;
  7.         static u8 temp,step,buf[6],cnt;
  8.         for(i=0;i<Nb_bytes;i++)
  9.         {
  10.                 temp = data_buffer[i];
  11.                 switch(step)
  12.                 {
  13.                         case 0:if(temp==0xa5)step=1;break;
  14.                         case 1:if(temp==0x5a)step=2;else if(temp==0xa5)step=1;else step=0;break;
  15.                         case 2:buf[cnt++]=temp;if(cnt>=6)step=3,cnt=0;break;
  16.                         case 3:if(temp==0xff)
  17.                                                 {
  18.                                                         arr=buf[0]*256+buf[1];
  19.                                                         div=buf[2]*256+buf[3];       
  20.                                                         num=buf[4]*256+buf[5];       
  21.                                                         DMA_ReSet(num);
  22.                                                         TIM_ReSet(arr,div);
  23.                                                         step=0;
  24.                                                 }
  25.                                                 else if(temp==0xa5)step=1;
  26.                                                 else step=0;
  27.                                                 break;
  28.                 }       
  29.         }
  30. }
复制代码
接收数据帧格式
帧头重装载值分频因子采样点数帧尾A5 5AXX XXXX XXXX XXFF2.5 DMA传输完成中断

                                                                                                                                                                                                                                                                                                                                  USB发送全部缓存                                                                                      清空缓存并关闭TIM4和DMA                                                                                      清晰中断标志位                                             
  1. /*******************************************************************************
  2. * 函 数 名         : DMA1_Channel1_IRQHandler
  3. * 函数功能                            : DMA通道1的中断
  4. * 输    入         : 无
  5. * 输    出         : 无
  6. *******************************************************************************/
  7. void DMA1_Channel1_IRQHandler(void)
  8. {
  9.         u16 i;
  10.         if(DMA_GetFlagStatus(DMA1_FLAG_TC1))
  11.         {
  12.                 TIM_Cmd(TIM4, DISABLE);
  13.                 DMA_Cmd(DMA1_Channel1,DISABLE);
  14.                 for(i=0;i<buf_len;i++)USB_USART_SendData((ADC_Buf[i]>>8)),USB_USART_SendData(ADC_Buf[i]&0xff);
  15.                 DMA_ClearFlag(DMA1_FLAG_TC1); //清除全部中断标志
  16.                 LED=!LED;
  17.         }
  18. }
复制代码
2.6 方波发生器及主函数

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     产生                                                                                                                                        产生                                                                                                                                        产生                                                                    改变                                                                    赋值给                                                                                                                                                         TIM2设置为1KHz                                                                                      1通道PWM方波                                                                                      A0口输出                                                                                      3通道PWM方波                                                                                      A2口输出                                                                                      定时器更新中断                                                                                      3通道占空比                                                                                      LED端口PC13                                                                                      呼吸灯结果                                             
  1. u16 LED_Breathe_Buf[1000];
  2. void Buf_Dataset(u16* buf)
  3. {
  4.         u16 temp;
  5.         for(temp=0;temp<500;temp++)buf[temp]=temp*2+100;
  6.         for(temp=0;temp<500;temp++)buf[temp+500]=999-temp*2+100;
  7. }
  8. void TIM2_Init(u16 Period,u16 Prescaler)
  9. {
  10.         GPIO_InitTypeDef GPIO_InitStructure;
  11.         TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  12.         TIM_OCInitTypeDef TIM_OCInitStructure;
  13.         NVIC_InitTypeDef NVIC_InitStructure;
  14.        
  15.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  16.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM2时钟
  17.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
  18.        
  19.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_2;
  20.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;        //复用推挽输出
  21.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  22.         GPIO_Init(GPIOA,&GPIO_InitStructure);
  23.        
  24.         TIM_TimeBaseStructure.TIM_Period = Period-1; //设置TIM2比较的周期
  25.         TIM_TimeBaseStructure.TIM_Prescaler = Prescaler-1;//系统主频72M,这里分频
  26.         TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  27.         TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  28.         TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  29.        
  30.         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//下面详细说明
  31.         TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
  32.         TIM_OCInitStructure.TIM_Pulse = Period>>1;
  33.         TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//如果是PWM1要为Low,PWM2则为High
  34.         TIM_OC1Init(TIM2, &TIM_OCInitStructure);
  35.         TIM_OC3Init(TIM2, &TIM_OCInitStructure);
  36.        
  37.         NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
  38.         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
  39.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
  40.         NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
  41.         NVIC_Init(&NVIC_InitStructure);//初始化中断,设置中断的优先级
  42.        
  43.         TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启定时器中断
  44.        
  45.         TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);//定时器2 通道1
  46.         TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);//定时器2 通道2
  47.        
  48.         TIM_Cmd(TIM2, ENABLE);
  49. }
  50. int main(void)
  51. {       
  52.         Buf_Dataset(LED_Breathe_Buf);
  53.         delay_init();                           //延时函数初始化
  54.         LED_Init();                                   //LED端口初始化
  55.         USB_Init();                                         //USB虚拟串口初始化
  56.         ADC1_CH1_Init();                //ADC初始化
  57.         TIM2_Init(1200,60); //72000000/1200/60=1000Hz
  58.         while(1)
  59.         {
  60.                 LED=PAin(2);
  61.         }         
  62. }
  63. void TIM2_IRQHandler(void)
  64. {
  65.         if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
  66.         {
  67.                 static u16 cnt;
  68.                 TIM_SetCompare3(TIM2,LED_Breathe_Buf[cnt]);
  69.                 if(cnt++>=1000)cnt=0;
  70.                 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除中断标志位
  71.         }
  72. }
复制代码
3 上位机源代码


  • pyqtgraph库举行科学画图,官方文档:https://www.pyqtgraph.org/
  • pyserial库举行串口通信,官方文档:https://pyserial.readthedocs.io/
  • PyQt5库计划GUI
完备代码:
  1. import pyqtgraph as pg
  2. import numpy as np
  3. import serial
  4. import time,struct
  5. import pyqtgraph.parametertree as ptree
  6. from pyqtgraph.Qt import QtCore, QtGui, QtWidgets, QT_LIB
  7. from scipy import interpolate
  8. from PyQt5.QtWidgets import *
  9. N = 512 # 采样点数
  10. k = 3.3/4096 # 比例因子
  11. arr = 400 # 重装载值
  12. div = 72 # 分频因子
  13. Fs = 72000000/arr/div # 采样率
  14. Ts = 1/Fs # 采样周期
  15. COM = 'COM7'
  16. # GUI设计
  17. app = pg.mkQApp()
  18. #============波形显示窗口=====================#
  19. w = pg.GraphicsLayoutWidget()
  20. w.setWindowTitle("CH32示波器-By纯粹")
  21. # 坐标系控件
  22. p = w.addPlot()
  23. p.showGrid(x=True,y=True)
  24. p.setRange(xRange=[-80,80],yRange=[-1,4],padding=0)
  25. p.setLabels(left='电压 / V',bottom="t / ms",title='CH32示波器')
  26. # 时基线和触发门限
  27. inf1 = pg.InfiniteLine(movable=True, angle=90, label='时基={value:0.2f}ms',
  28.                        labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True})
  29. inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='触发={value:0.2f}V',
  30.                        labelOpts={'color': (0,200,200), 'movable': True, 'fill': (0, 0, 200, 100)})
  31. inf1.setPos([0,0])
  32. inf2.setPos([0,1])
  33. p.addItem(inf1)
  34. p.addItem(inf2)
  35. curve = p.plot(pen='y') # 曲线控件
  36. #============参数调节窗口=====================#
  37. children=[
  38.         dict(name='采样配置', title='采样配置', type='group', children=[
  39.                 dict(name='采样率', type='float', limits=[0.0001, 857.143], value=Fs/1000, units='kHz'),
  40.         dict(name='重装载值', type='int', limits=[2, 65535], value=arr),
  41.         dict(name='分频因子', type='int', limits=[1, 65536], value=div),
  42.                 dict(name='采样点数', type='int', limits=[0, 512], value=N),
  43.                 dict(name='比例系数', type='float', value=1),
  44.     ]),
  45.         dict(name='虚拟串口', type='str', value=COM),
  46.         dict(name='波特率', type='int', limits=[4800, 20000000], value=1000000),
  47.         dict(name='触发', type='float', value=inf2.getYPos(), units='V'),
  48.         dict(name='时基', type='float', value=inf1.getXPos(), units='ms'),
  49.         dict(name='曲线样式', type='pen', value=pg.mkPen()),
  50.         dict(name='贝塞尔插值', type='bool', value=True),
  51. ]
  52. params = ptree.Parameter.create(name='调整参数', type='group', children=children)
  53. def onChanged0(param, val):
  54.         global arr,div,Fs,Ts
  55.         temp = int(72000/val/arr)
  56.         if 1 < temp < 65536:
  57.                 params.child('采样配置').child('分频因子').setValue(temp)
  58.         else:
  59.                 params.child('采样配置').child('分频因子').setValue(1)
  60.                 temp = int(72000/val)
  61.                 if 2 < temp < 65536:
  62.                         params.child('采样配置').child('重装载值').setValue(temp)       
  63. def onChanged1(param, val):
  64.         global arr,div,Fs,Ts
  65.         if 72000000/val/div > 857143:
  66.                 params.child('采样配置').child('重装载值').setValue(arr)
  67.                 return
  68.         arr = val
  69.         Fs = 72000000/arr/div
  70.         Ts = 1/Fs
  71.         params.child('采样配置').child('采样率').setValue(Fs/1000)
  72. def onChanged2(param, val):
  73.         global arr,div,Fs,Ts
  74.         if 72000000/val/arr > 857143:
  75.                 params.child('采样配置').child('分频因子').setValue(div)
  76.                 return
  77.         div = val
  78.         Fs = 72000000/arr/div
  79.         Ts = 1/Fs
  80.         params.child('采样配置').child('采样率').setValue(Fs/1000)
  81. def onChanged3(param, val):
  82.         global N
  83.         N = val
  84. def onChanged4(param, val):
  85.         inf1.setPos([val,0])
  86. def onChanged5(param, val):
  87.         inf2.setPos([0,val])
  88. def onPenChanged(param, pen):
  89.     curve.setPen(pen)
  90. params.child('采样配置').child('采样率').sigValueChanged.connect(onChanged0)
  91. params.child('采样配置').child('重装载值').sigValueChanged.connect(onChanged1)
  92. params.child('采样配置').child('分频因子').sigValueChanged.connect(onChanged2)
  93. params.child('采样配置').child('采样点数').sigValueChanged.connect(onChanged3)
  94. params.child('时基').sigValueChanged.connect(onChanged4)
  95. params.child('触发').sigValueChanged.connect(onChanged5)
  96. params.child('曲线样式').sigValueChanged.connect(onPenChanged)
  97. def On_inf1Changed():
  98.         params.child('时基').setValue(inf1.getXPos())
  99. inf1.sigPositionChanged.connect(On_inf1Changed)
  100. def On_inf2Changed():
  101.         params.child('触发').setValue(inf2.getYPos())
  102. inf2.sigPositionChanged.connect(On_inf2Changed)
  103. pt = ptree.ParameterTree(showHeader=False)
  104. pt.setParameters(params)
  105. # 按钮
  106. StartBtn = QtGui.QPushButton('开始')
  107. StopBtn = QtGui.QPushButton('暂停')
  108. ContinueBtn = QtGui.QPushButton('继续')
  109. EndBtn = QtGui.QPushButton('停止')
  110. run_flag = False
  111. def On_Start():
  112.         global run_flag,ser
  113.         try:
  114.                 ser = serial.Serial(params.child('虚拟串口').value(),params.child('波特率').value())
  115.                 run_flag = True
  116.                 StartBtn.setEnabled(False)
  117.                 StopBtn.setEnabled(True)
  118.                 ContinueBtn.setEnabled(False)
  119.                 EndBtn.setEnabled(True)
  120.         except:
  121.                 QtWidgets.QMessageBox(QMessageBox.Warning, '警告', '虚拟串口打开失败').exec_()
  122.        
  123. def On_Continue():
  124.         global run_flag
  125.         run_flag = True
  126.         StartBtn.setEnabled(False)
  127.         StopBtn.setEnabled(True)
  128.         ContinueBtn.setEnabled(False)
  129.         EndBtn.setEnabled(True)
  130. def On_Stop():
  131.         global run_flag
  132.         run_flag = False
  133.         StartBtn.setEnabled(False)
  134.         StopBtn.setEnabled(False)
  135.         ContinueBtn.setEnabled(True)
  136.         EndBtn.setEnabled(False)
  137. def On_End():
  138.         global run_flag,ser
  139.         ser.close()
  140.         run_flag = False
  141.         StartBtn.setEnabled(True)
  142.         StopBtn.setEnabled(False)
  143.         ContinueBtn.setEnabled(False)
  144.         EndBtn.setEnabled(False)
  145. StartBtn.clicked.connect(On_Start)
  146. StopBtn.clicked.connect(On_Stop)
  147. ContinueBtn.clicked.connect(On_Continue)
  148. EndBtn.clicked.connect(On_End)
  149. StartBtn.setEnabled(True)
  150. StopBtn.setEnabled(False)
  151. ContinueBtn.setEnabled(False)
  152. EndBtn.setEnabled(False)
  153. #================主窗口=====================#
  154. win = QtGui.QMainWindow()
  155. win.resize(1000,600)
  156. win.setWindowTitle("CH32示波器-By纯粹")
  157. #================主窗口内添加控件=====================#
  158. cw = QtGui.QWidget()
  159. win.setCentralWidget(cw)
  160. layout = QtGui.QGridLayout()
  161. layout.addWidget(w, 1, 1, 6, 1)
  162. layout.addWidget(pt, 1, 2, 1, 2)
  163. layout.addWidget(StartBtn, 2, 2, 1, 2)
  164. layout.addWidget(StopBtn, 3, 2, 1, 2)
  165. layout.addWidget(ContinueBtn, 4, 2, 1, 2)
  166. layout.addWidget(EndBtn, 5, 2, 1, 2)
  167. cw.setLayout(layout)
  168. win.show()
  169. #================打开虚拟串口=====================#
  170. # ser = serial.Serial(params.child('虚拟串口').value(),params.child('波特率').value())
  171. #=======滑动均值滤波器======#
  172. fps_buf = np.linspace(0,0,100)
  173. fps_buf_ptr = 0
  174. def fps_mean(fps):
  175.         global fps_buf,fps_buf_ptr
  176.         fps_buf[fps_buf_ptr] = fps
  177.         fps_buf_ptr += 1
  178.         if fps_buf_ptr >= 100:
  179.                 fps_buf_ptr = 0
  180.         return np.mean(fps_buf)
  181. #==========================#
  182. #=======滑动均值滤波器======#
  183. fc_buf = np.linspace(0,0,20)
  184. fc_buf_ptr = 0
  185. def fc_mean(fc):
  186.         global fc_buf,fc_buf_ptr
  187.         fc_buf[fc_buf_ptr] = fc
  188.         fc_buf_ptr += 1
  189.         if fc_buf_ptr >= 20:
  190.                 fc_buf_ptr = 0
  191.         return np.mean(fc_buf)
  192. #==========================#
  193. fps_t0 = cnt = data0 = data1 = 0
  194. start_flag = False
  195. data_buf = np.array([]) # 数据缓存
  196. #更新数据并显示波形
  197. def display_data():
  198.         global cnt,start_flag,data0,data1,data_buf,t0,fps_t0,run_flag
  199.         if run_flag == False:
  200.                 return
  201.         if start_flag:
  202.                 buf_len = ser.in_waiting
  203.                 if buf_len == N*2:
  204.                         num = buf_len>>1
  205.                         temp = struct.unpack('>%dH'%(num),ser.read(num*2)) # 读取并解析数据
  206.                         if temp[0] > 4096: # 12位ADC数据小于4096
  207.                                 ser.read() # 数据出错,清空接收缓存
  208.                                 return
  209.                         if len(data_buf) < N:
  210.                                 data_buf = np.append(data_buf,temp)
  211.                         else:
  212.                                 data_buf = np.append(data_buf,temp)
  213.                                 data_buf = data_buf[-N:]
  214.                         output = data_buf*k*params.child('采样配置').child('比例系数').value() # 单位转换 转化为电压
  215.                         output_len = len(output)
  216.                         t = np.linspace(-500*Ts*output_len,500*Ts*output_len,output_len) # 生成初始时间轴 单位ms
  217.                         if params.child('贝塞尔插值').value():
  218.                                 t_new = np.linspace(-500*Ts*output_len,500*Ts*output_len,output_len*10)
  219.                                 tck = interpolate.splrep(t, output)
  220.                                 output_bspline = interpolate.splev(t_new, tck)
  221.                                 output_bspline_len = len(output_bspline)
  222.                                 comparator = np.array(output_bspline > inf2.getYPos(),dtype='int8')
  223.                                 rising = np.where(np.diff(comparator) == 1)[0]
  224.                                 if len(rising) > 1:
  225.                                         dt = np.mean(np.diff(rising))*Ts/10
  226.                                         fc = fc_mean(1/dt)
  227.                                         start_point = int(output_bspline_len/2+inf1.getXPos()/Ts/100) # 开始触发点索引
  228.                                         end_point_index = np.where( rising > start_point)[0]
  229.                                         if len(end_point_index) > 0:
  230.                                                 # 触发成功生成新时间轴 单位ms
  231.                                                 t_new = np.linspace(-100*Ts*(rising[end_point_index[0]]+1)+inf1.getXPos(),
  232.                                                                                  100*Ts*(output_bspline_len-rising[end_point_index[0]]-1)+inf1.getXPos(),output_bspline_len)
  233.                                 else:
  234.                                         fc = 0
  235.                                 curve.setData(t_new,output_bspline) # 绘制曲线
  236.                         else:
  237.                                 comparator = np.array(output > inf2.getYPos(),dtype='int8')
  238.                                 rising = np.where(np.diff(comparator) == 1)[0]
  239.                                 if len(rising) > 1:
  240.                                         dt = np.mean(np.diff(rising))*Ts
  241.                                         fc = fc_mean(1/dt)
  242.                                         start_point = int(output_len/2+inf1.getXPos()/Ts/1000) # 开始触发点索引
  243.                                         end_point_index = np.where( rising > start_point)[0]
  244.                                         if len(end_point_index) > 0:
  245.                                                 # 触发成功生成新时间轴 单位ms
  246.                                                 t = np.linspace(-1000*Ts*(rising[end_point_index[0]]+1)+inf1.getXPos(),
  247.                                                                                  1000*Ts*(output_len-rising[end_point_index[0]]-1)+inf1.getXPos(),output_len)
  248.                                 else:
  249.                                         fc = 0
  250.                                 curve.setData(t,output) # 绘制曲线
  251.                         fps_t = fps_mean(time.time()-fps_t0) # 帧率均值滤波
  252.                         fps_t0 = time.time()
  253.                         p.setTitle('CH32示波器 %0.2f fps Fs = %0.3f kHz fc = %0.3f Hz' % (1/fps_t,Fs/1000,fc))
  254.                         cnt += num
  255.                 if cnt >= N or time.time() - t0 > N*Ts+0.01:# 接收数据满或接收超时
  256.                         cnt = 0
  257.                         start_flag = False
  258.         else:
  259.                 ser.read(ser.in_waiting)
  260.                 data_buf = np.array([])
  261.                 result=ser.write(bytes([0xa5, 0x5a, arr>>8, arr&0xff, div>>8, div&0xff, N>>8, N&0xff, 0xff]))# 发送数据
  262.                 t0 = time.time()
  263.                 while time.time() - t0 < N*Ts+0.01:# 等待回应
  264.                         if ser.in_waiting:
  265.                                 start_flag = True
  266.                                 t0 = time.time()
  267.                                 break
  268. timer = pg.QtCore.QTimer()
  269. timer.timeout.connect(display_data)
  270. timer.start(0)
  271. app.exec_()
复制代码
四、性能测试

1 再造信号发送器


对比STM32和CH32惊奇的发现CH32还有DAC功能,正好根据官方库撸个信号发送器代码,如下:
  1. /********************************** (C) COPYRIGHT *******************************
  2. * File Name          : main.c
  3. * Author             : WCH
  4. * Version            : V1.0.0
  5. * Date               : 2019/10/15
  6. * Description        : Main program body.
  7. *******************************************************************************/
  8. #include "debug.h"
  9. #include "math.h"
  10. /* Global define */
  11. #define LED PCout(13)
  12. #define buf_len 50
  13. /* Global Variable */
  14. u32 DAC_Buf[buf_len];
  15. /*******************************************************************************
  16. * Function Name  : Gpio_Init
  17. * Description    : Initializes GPIO collection.
  18. * Input          : None
  19. * Return         : None
  20. *******************************************************************************/
  21. void Gpio_Init(void)
  22. {
  23.         GPIO_InitTypeDef  GPIO_InitStructure;
  24.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
  25.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;                                 
  26.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  
  27.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                 
  28.         GPIO_Init(GPIOC, &GPIO_InitStructure);                                         
  29.         GPIO_SetBits(GPIOC,GPIO_Pin_13);
  30. }
  31. /*******************************************************************************
  32. * Function Name  : Dac_Init
  33. * Description    : Initializes DAC collection.
  34. * Input          : None
  35. * Return         : None
  36. *******************************************************************************/
  37. void Dac_Init(void)
  38. {
  39.         GPIO_InitTypeDef GPIO_InitStructure;
  40.         DAC_InitTypeDef DAC_InitType;
  41.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE );
  42.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );
  43.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;                                          
  44.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                      
  45.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  46.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  47.         GPIO_SetBits(GPIOA,GPIO_Pin_4);
  48.         DAC_InitType.DAC_Trigger=DAC_Trigger_T3_TRGO;                                 
  49.         DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;            
  50.         DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ;                
  51.         DAC_Init(DAC_Channel_1,&DAC_InitType);
  52.         DAC_Cmd(DAC_Channel_1, ENABLE);
  53.         DAC_DMACmd(DAC_Channel_1,ENABLE);
  54. }
  55. /*******************************************************************************
  56. * Function Name  : DAC1_DMA_INIT
  57. * Description    : Initializes DMA of DAC1 collection.
  58. * Input          : None
  59. * Return         : None
  60. *******************************************************************************/
  61. void DAC1_DMA_Init(void)
  62. {
  63.         DMA_InitTypeDef DMA_InitStructure;
  64.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        
  65.         DMA_StructInit( &DMA_InitStructure);
  66.         /* Note:DAC1--->DMA1.CH3   DAC2--->DMA1.CH4 */
  67.         DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(DAC->R12BDHR1);
  68.         DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DAC_Buf;   
  69.         DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  70.         DMA_InitStructure.DMA_BufferSize = buf_len*4;   
  71.         DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  
  72.         DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   
  73.         DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  74.         DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;
  75.         DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              
  76.         DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  77.         DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  
  78.         DMA_Init(DMA1_Channel3, &DMA_InitStructure);
  79.         DMA_Cmd(DMA1_Channel3, ENABLE);
  80. }
  81. /*******************************************************************************
  82. * Function Name  : DAC_Data_Init
  83. * Description    : Initializes Data of DMA.
  84. * Input          : None
  85. * Return         : None
  86. *******************************************************************************/
  87. void DAC_Data_Init(void)
  88. {
  89.         uint32_t Idx = 0;  
  90.         for (Idx = 0; Idx < buf_len; Idx++)
  91.         {
  92.                 DAC_Buf[Idx] = 2000*sin(Idx*3.14159265*2/buf_len)+2048;
  93.         }
  94. }
  95. /*******************************************************************************
  96. * Function Name  : TIM3_Init
  97. * Description    : Initializes TIM3 collection.
  98. * Input          : arr: TIM_Period
  99. *                  psc: TIM_Prescaler
  100. * Return         : None
  101. *******************************************************************************/
  102. void TIM3_Init(u16 arr,u16 psc)
  103. {
  104.         TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  105.         GPIO_InitTypeDef GPIO_InitStructure;
  106.         TIM_OCInitTypeDef TIM_OCInitStructure;
  107.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  108.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
  109.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
  110.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;        //复用推挽输出
  111.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  112.         GPIO_Init(GPIOA,&GPIO_InitStructure);
  113.         TIM_TimeBaseStructure.TIM_Period = arr-1;            
  114.         TIM_TimeBaseStructure.TIM_Prescaler = psc-1;           
  115.         TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  116.         TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
  117.         TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  118.         TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);  
  119.         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//下面详细说明
  120.         TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//TIM_OutputState_Disable;
  121.         TIM_OCInitStructure.TIM_Pulse = arr>>1;
  122.         TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//如果是PWM1要为Low,PWM2则为High
  123.         TIM_OC1Init(TIM3, &TIM_OCInitStructure);
  124.         TIM_Cmd(TIM3, ENABLE);                                  
  125. }
  126. /*******************************************************************************
  127. * Function Name  : main
  128. * Description    : Main program.
  129. * Input          : None
  130. * Return         : None
  131. *******************************************************************************/
  132. int main(void)
  133. {
  134.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  135.         Delay_Init();
  136.         Gpio_Init();
  137.         Dac_Init();
  138.         DAC_Data_Init();
  139.         DAC1_DMA_Init();
  140.         TIM3_Init(10,36); //溢出率200kHz产生1kHz正弦波
  141.         while(1)
  142.         {       
  143.                 LED = !LED;
  144.                 Delay_Ms(500);
  145.         }
  146. }
复制代码
2 方波测试

2.1 1kHz方波量结果


2.2 10kHz方波量结果


2.3 100kHz方波量结果


2.4 400kHz方波量结果


3 正弦波测试

3.1 50Hz正弦波量结果


3.2 1kHz正弦波量结果


3.3 10kHz正弦波量结果


提示:CH32 DAC未便产生高频正弦波。测量结果仅供参考

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作