功能描述
1-数据采集,单通道数据采集,采用核心板ADC功能。
2-信号衰减倍率可调,通过电阻分压,可以50X,10X等,建议10X即可。
3-支持PWM方波输出,1K,2K,4KHz输出,并且占空比可调,5%步进。
4-波形显示,通过1.8TFT屏幕显示,屏幕分辨率128X160。
5-兼容CW32、GD32、STM32多种核心板。
6-支持频率测量,通过MCU的GPIO的capture功能实现。
7-预留传感器接口,比如温湿度、气压计等
设计背景
示波器是电子工程师必不可少的工具,按照信号的不同分为模拟示波器以及数字示波器两种,模拟示波器采用模拟电路和电子枪,通过电子束打在屏幕上显示波形,而数字示波器采用数字电路和数字存储技术,将波形数字化并显示出来。
模拟示波器 数字示波器(普源精电DS6104)
在示波器升级迭代过程中,传统的模拟示波器以及难以满足现代电子测量的需求,相比于模拟示波器,数字示波器的测量进度更高、显示稳定以及信号。随着技术的发展,示波器的性能和功能不断提高。现代示波器采用高速ADC和FPGA等技术,具有高带宽、高采样率和深存储深度等特点。此外,数字示波器还支持多种触发方式和信号分析功能,如FFT变换、频谱分析等功能。
为了学习核心板CW32为主控,特学习一下简易示波器的制作方法。
实现过程
电路原理分析
数字示波器是一种用于显示电信号波形的仪器,主要由模拟前端处理电路、单片机电路、电源电路、控制电路、触发电路、校准电路等电路组成。由于该项目为示波器入门项目,在电路设计上选择了一些核心电路,帮助初学者更好的了解示波器的原理和设计方法,主要包括了以下电路:
-
模拟前端处理电路:负责将输入的检测模拟信号进行处理后给单片机进行识别,具体电路包括了交直流耦合选择电路、电压衰减电路、信号处理电路以及频率检测电路,是整个电路的核心。
-
电源电路:负责给运放提供正负电源以及系统供电,是保障电路正常运行的基础;
-
单片机电路:给系统提供控制核心,负责对输入信号的采集与处理输出工作;
-
人机交互电路:用于控制示波器功能,包括按键、旋钮、LED灯、显示屏以及其它输入输出接口,为示波器功能的开发提供基础。
原理图设计
模拟前端处理电路
在整个示波器电路设计过程中,模拟前端处理电路是最为重要的,其中大量应用了模拟电路的知识,其中包括输入交直流耦合切换电路、输入信号衰减电路、以及信号调理电路所组成,
交直流耦合切换电路
信号类型可以分为直流信号和交流信号,现实中的信号往往都是都不是理想波形。比如直流电源信号应该是一条水平的直流信号,但都会存在电源纹波(交流信号);在采集交流信号时也可能混入直流信号对波形的峰峰值造成影响。为了保障对输入交流信号的准确测量,利用电容通交隔直的特性,将电容串联到电路中就可以过滤到信号中的直流分量,这就是交流耦合的概念。而直流耦合就是不对输入信号做任何处理。
输入信号衰减电路
信号经过交直流耦合选择电路后由开关SW3选择两个通道,开关2和3接到一起时,输入信号直接流入后级的电压跟随器电路;当开关2和1接到一起时,输入信号经过R8、R12、R15三个电阻构成的电阻分压网络后将信号衰减到了1/50倍,即:
由此可知,当输入信号幅值较小时,可优先选择低压档位,如果测量时不确定输入信号幅值可先用高压档位测量后如满足低压范围内,可用低压档位测量以得到更为精准的测量结果,同时保护电路。

信号调理电路
在信号调理电路中包含了一个电压跟随器以及由运放构成的信号放大电路,在分析该这部分电路时需要掌握运放的虚断与虚短原理。
在电路U6.2芯片中运放反向输入引脚2脚接到运放输出引脚1脚,结合运放的虚短特性,V+=V-=Vout。根据虚断可知运算放大器的输入阻抗比较大,所以运算放大器正向输入电流很小,运放输出阻抗小所以输出的电流很大,说这里的电压跟随器起着一个阻抗匹配的作用。
在对U6.1运放构成的电路进行分析时,可以将其拆解为一个同相比例放大电路和一个反相比例放大电路进行单独分析后合到一起。
低压档位测量范围:-1.6V~5V,高压档位测量范围:-80V~250V
比较器测频电路
为了实现频率检测的功能,将ADC输入信号通过一个滞回比较器对输入信号进行比较,实现频率的测量功能。滞回比较器是属于电压比较器中的一种,常规的电压比较器是一个单限比较器,电路中只有一个阈值电压,但在输入电压在阈值附近有微小变化时都会引起输出电压的越变。为了增强电路的抗干扰能力,在单限比较器的基础上引入了正反馈,保障了在一定范围内信号的稳定性。通过滞回比较器电路后输出一个方波信号,使用单片机的定时器捕获功能计算出输入波形的周期大小。
旋转编码器电路
旋转编码器属于一种特殊的按键,该项目使用的EC11旋转编码器有五个引脚,其中DE两个引脚类似于普通按键引脚,按下导通,松手断开,其余ABC三个引脚用于检测旋钮的转动方向,C脚为公共端,直接接地就行。
在旋转编码器时,A和B两个信号引脚存在相位差,也就是有一个引脚信号变化后另一个引脚信号再跟着变化,即两个引脚不同时变化,通过检测哪个引脚先变就能判断是正转还是反转功能。
LED灯指示电路
LED指示电路设计比较简单,采用低电平驱动的方式,当单片机引脚输出为低电平时,LED两端存在电势差,LED点亮;当单片机引脚输出为高电平时,LED灯熄灭。
按键输入检测电路
除了旋转编码器外,该项目还使用了4个独立按键对系统进行控制,4个按键一侧直接接地,另一侧连接到单片机引脚,当单片机引脚检测到按键按下时,单片机引脚直接接到GND接地,单片机收到该引脚接地信号的反馈后再去实现对应的功能,为节约硬件成本,可以在软件设计时引入消抖功能,避免机械按键抖动时的误触发。
主控选型
立创·地文星CW32F030C8T6开发板,是一款基于ARM Cortex-M0内核的微控制器开发板,由立创商城(立创网站)提供。以下是对该开发板的详细介绍:
基本信息
- 品牌与型号:立创开发板,型号为LCKFB-DWX-CW32F030C8T6。
- 处理器:搭载CW32F030C8T6微控制器,这是一款基于ARM Cortex-M0内核的32位MCU,适用于低功耗、高性能的嵌入式应用。
特性与功能
- 扩展性:提供丰富的扩展接口,如SMT扩展库,方便用户根据需求进行二次开发和功能拓展。
- 调试与编程:支持使用J-Link等调试工具进行高效的调试和程序烧录,提升开发效率。同时,也支持在Keil等开发环境中进行编程。
- 低功耗:由于采用Cortex-M0内核,该开发板具有低功耗特性,适合对功耗有严格要求的应用场景。
配件与文档
- 配件:购买该开发板时,通常会附带1x20P直插排针2件、1x4P弯插排针1件以及说明书1张。请注意,这些配件可能不参与售后。
- 文档:提供详细的数据手册(PDF格式),用户可以通过立创商城等渠道下载查阅,以便更好地了解和使用该开发板。
购买与库存
- 价格:根据立创商城的信息,该开发板的价格较为亲民,具体价格可能因促销活动等因素而有所变动。
- 库存:立创商城在江苏和广东设有仓库,库存量较为充足,用户可以放心购买。
开发与使用
- 开发环境:推荐使用Keil等集成开发环境(IDE)进行程序开发,这些环境提供了丰富的功能和工具,有助于用户快速上手和高效开发。
- 应用场景:由于地文星开发板具有低功耗、高性能等特点,因此适用于各种嵌入式应用场景,如智能家居、工业自动化、物联网设备等。
综上所述,立创·地文星CW32F030C8T6开发板是一款性价比较高、功能丰富的微控制器开发板,适合广大嵌入式开发者使用。

液晶屏显示电路
1.8 TFT 是一款彩色显示屏,具有 128 x 160 个彩色像素,使用四线SPI通信方式与单片机进行连接,一共有八根引脚,模块引脚说明及与单片机连接情况如下所示:
引脚序号
|
引脚名称
|
引脚说明
|
1.8寸TFT屏电路图
|
1
|
GND
|
电源接地引脚
|
2
|
VCC
|
接电源VCC
|
3
|
SCL
|
SPI串行时钟信号
|
4
|
SDA
|
SPI数据引脚(MOSI)
|
5
|
RES
|
复位引脚
|
6
|
DC
|
命令和数据选择引脚
|
7
|
CS
|
LCD片选引脚
|
8
|
BL
|
背光引脚
|
1.8寸TFT液晶屏引脚说明图
|
电源设计
支持锂电池供电和USB供电。USB进行充电。

其他电路
除了示波器检测功能外,单独引出了一个PWM信号用于模拟一个简易的函数发生器功能,可以通过改变输出PWM的频率和占空比输出一个简易方波信号输出。
还预留了传感器接口,支持AHT21和BMP280接入。
PCB设计
采用LCEDA开源软件进行设计。
布局设计
布局紧凑,方便实用。

走线设计
尽可能的短,根据EMC、EMI等原则,设计线径、过孔等,根据负载能力,载流能力,电源走线设计。

地分割设计
因为模拟地和数字地最好分开原则,进行了地铺铜分割。

外壳设计
采用LCEDA开源软件进行设计,尤其开孔位置要精准和预留公差,否则装不上哦。


面板设计
采用LCEDA开源软件进行设计,尤其开孔位置要精准和预留公差,否则错位很不美观,安装也很麻烦哦。
标识要清晰,位置要准确,一眼就明白哦。

程序设计
主控代码实现如下:
#include "main.h"
#include "tft.h"
#include "tft_init.h"
#include "led.h"
#include "adc.h"
#include "timer.h"
#include "freq.h"
#include "key.h"
void RCC_Configuration(void);
volatile struct Oscilloscope oscilloscope={0};
void Init_Oscilloscope(volatile struct Oscilloscope *value);
extern uint16_t adc_value[ADC_VALUE_NUM];
int main()
{
uint16_t i=0;
//中间值
float median=0;
//峰峰值
float voltage=0,vpp=0;
//触发电压值
float max_data=2.5f;
//波形放大倍数
float gainFactor=15.0f;
//触发沿标记
uint16_t Trigger_number=0;
//初始化示波器参数
Init_Oscilloscope(&oscilloscope);
//时钟初始化
RCC_Configuration();
//LED初始化
Init_LED_GPIO();
//屏幕初始化
TFT_Init();
//初始化ADC引脚
Init_ADC(oscilloscope.ADC_ClkDiv,oscilloscope.ADC_SampleTime);
//ADC DMA初始化
ADC_DMA_Init();
//填充黑色
TFT_Fill(0,0,160,128,BLACK);
//初始化PWM输出
Init_PWM_Output(oscilloscope.timerPeriod-1,oscilloscope.pwmOut);
//初始化EC11引脚
Init_EC11_GPIO();
//初始化按键引脚
Init_Key_GPIO();
//初始化频率定时器
Init_FreqTimer();
//初始化静态UI
TFT_StaticUI();
while(1)
{
//按键扫描处理函数
Key_Handle(&oscilloscope);
if(oscilloscope.showbit==1)
{
oscilloscope.showbit=0;
oscilloscope.vpp=0;
//转换电压值
for(i=0;i<250;i++)
{
oscilloscope.voltageValue[i]=(adc_value[i]*3.3f)/4096.0f;
//获取整段数据的峰值电压
vpp=(5-(2.0f*oscilloscope.voltageValue[i]));
if((oscilloscope.vpp) < vpp)
{
oscilloscope.vpp = vpp;
}
if(oscilloscope.vpp <= 0.3)
{
oscilloscope.gatherFreq=0;
}
}
//找到起始显示波形值
for(i=0;i<200;i++)
{
if(oscilloscope.voltageValue[i] < max_data)
{
for(;i<200;i++)
{
if(oscilloscope.voltageValue[i] > max_data)
{
Trigger_number=i;
break;
}
}
break;
}
}
//如果幅值过小,会出现放大倍数过大导致波形显示异常的问题
if(oscilloscope.vpp > 0.3)
{
//计算中间幅度,输入最低点是2.5V,最高是(5-vpp)/2,中间幅度就是最低值减去最高值/2,
median = (2.5-(5 - oscilloscope.vpp)/2.0f)/2.0f;
//放大倍数,需要确定放大之后的区间,我将波形固定显示在(18.75~41.25中),(41.25-18.75)/2=11.25f
gainFactor = 11.25f/median;
//最低的减去中间幅度等于中间值
median = 2.5 - median;
}
//依次显示后续100个数据,这样可以防止波形滚动
for(i=Trigger_number;i
{
if(oscilloscope.keyValue == KEYD)
{
oscilloscope.keyValue=0;
do
{
if(oscilloscope.keyValue == KEYD){
oscilloscope.keyValue=0;
break;
}
//暂停后,对波形频率进行判断,改变ADC采样率,使得波形最佳显示
if(oscilloscope.gatherFreq<=600)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div128;
}
else if(oscilloscope.gatherFreq<=2000)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div64;
}
else if(oscilloscope.gatherFreq<=4000)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div32;
}
else if(oscilloscope.gatherFreq<=8000)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div16;
}
else if(oscilloscope.gatherFreq<=16000)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div8;
}
else if(oscilloscope.gatherFreq<=32000)
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div4;
}
else
{
oscilloscope.ADC_ClkDiv=ADC_Clk_Div2;
}
}while(1);
Init_ADC(oscilloscope.ADC_ClkDiv,oscilloscope.ADC_SampleTime);
}
voltage=oscilloscope.voltageValue[i];
if(voltage >= median)
{
voltage = 30 - (voltage - median)*gainFactor;
}
else
{
voltage = 30 + (median - voltage)*gainFactor;
}
drawCurve(80,voltage);
}
ADC_DMA_Init();
}
//参数显示UI
TFT_ShowUI(&oscilloscope);
}
}
void RCC_Configuration(void) //外部时钟8M,PLL:64M
{
RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_FLASH, ENABLE); //打开FLASH时钟
RCC_HSE_Enable(RCC_HSE_MODE_OSC, 8000000, RCC_HSE_DRIVER_NORMAL, RCC_HSE_FLT_CLOSE); //开启外部高速时钟HSE,实际频率需要根据实际晶体频率进行填写
RCC_PLL_Enable(RCC_PLLSOURCE_HSEOSC, 8000000, RCC_PLL_MUL_8); //开启PLL,PLL时钟来源为HSE
FLASH_SetLatency(FLASH_Latency_3);
FirmwareDelay(100000); //延时足够,保证时钟稳定
if (RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL) == 0x0U) //切换系统时钟成功
{
RCC_HSI_Disable(); //切换时钟到PLL后关闭源时钟HSI
FirmwareDelay(400000);
}
}
/*
* 函数内容:初始化示波器参数结构体
* 函数参数:volatile struct Oscilloscope *value--示波器参数结构体指针
* 返回值:无
*/
void Init_Oscilloscope(volatile struct Oscilloscope *value)
{
(*value).showbit =0; //清除显示标志位
(*value).ADC_ClkDiv =ADC_Clk_Div64; //ADC时钟
(*value).ADC_SampleTime=ADC_SampTime10Clk;//ADC时钟周期
(*value).keyValue =0; //清楚按键值
(*value).ouptputbit =0; //输出标志位
(*value).gatherFreq =0; //采集频率
(*value).outputFreq =1000; //输出频率
(*value).pwmOut =500; //PWM引脚输出的PWM占空比
(*value).timerPeriod =1000; //PWM输出定时器周期
(*value).vpp =0.0f; //峰峰值
}
其它代码详见附件。
效果展示
具体见B站演示哦
https://www.bilibili.com/video/BV15FWretEfv/?vd_source=e36622a05269c0356d6cd566056a2488

总结
CW32的ADC比STM32、GD32测量更准,噪声更低。国产芯片确实做的不错。这个必须点赞。
通过这次训练营,也了解到CW32这小众产品,也确实有惊艳的一面,比如ADC的参考电压看选择内部的1.5V,2.5V等。之前为了电源更稳还需要选择外部基准电源,又贵又不好设计。
本次学习认识不少大佬,学到了非常多的知识。
感谢立创,感谢芯源。
评论