基于stm32,qt开发的信号采集与波形显示(示波器)

本文详细介绍如何使用STM32实现125Hz采样率的ADC数据采集并通过串口发送给PC端,利用Qt的QTchart库在PC端实时绘制波形。文中包括STM32配置、串口通信、Qt界面设计及数据处理等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.目标

125hz采样,qt实时显示,不失真,不丢点

2.stm32篇

  • 配置ADC寄存器(这里使用了中断进行触发采集)
static void ADC_GPIO_config(void)
{
	
	
      GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
	//A1模拟输入,p1用于采集
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	

}

static void ADCx_Mode_Config(void)
{
	
	ADC_InitTypeDef ADC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2ENR_ADC1EN,ENABLE);//打开ADC1时钟
	ADC_InitStructure.ADC_Mode= ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None ;
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel=1;
	ADC_Init(ADC1,&ADC_InitStructure);//这里记得添加ADC.C文件
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);//8分频9兆
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_55Cycles5);
	 ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
	ADC_Cmd(ADC1, ENABLE);//使能ADC
	ADC_StartCalibration(ADC1);//开始校准
	
	while(ADC_GetCalibrationStatus(ADC1));//等待校准完成
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
  • 2.配置串口(串口同样使用中断触发)
void USART_Config(void)
	{
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                                   //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                          	  //复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);                                      //初始化GPIOA.10
   
  //USART1_RX	  GPIOA.10初3始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                                  //PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                       //浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);                                      //初始化GPIOA.10

   //USART 初始化设置

	USART_InitStructure.USART_BaudRate =9600;                                  //串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;                //字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;                     //一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;                        //无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;            //收发模式
  USART_Init(USART1, &USART_InitStructure);                                  //初始化串口
	USART_Cmd(USART1, ENABLE);                                                 //使能串口1 
	…..
		
}
	
  • 配置定时器(目的是为了达到125hz采样率)
初始化:
void TIM3_Int_Init(u16 arr,u16 psc){
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	TIM_TimeBaseInitStruct.TIM_Period=arr;
	TIM_TimeBaseInitStruct.TIM_Prescaler=psc;
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;..
	

	
}
写中断服务函数
void TIM3_IRQHandler(void){
	
	
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET){
		
		Usart_sendhalfword(USART1,ADC_ConvertedValue);	
		
		if(LED1==1)
			LED1=0;
		else
			LED1=1;
		}

		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	
		
	
}
  • 撰写发送函数
    这里需要使用通信协议;加上帧头,数据长度,数据位,帧尾,检验位,这里使用的是和校验;
    1.打包数据,并发送
void Usart_sendhalfword(USART_TypeDef* pUSARTx, u16 Data)
	{
//ADC值为16位,拆分为高八位第八位进行发送
		uint8_t head1,head2,len,temp_h,temp_l,end,sum;
		u8 buf[5];
		head1=0xAA;
		len=0x02;
		temp_h=(Data&0xff00)>>8;
		temp_l=(Data&0xff);
		end=0xAB;
		buf[0]=head1;
		buf[1]=len;
		buf[2]=temp_h;
		buf[3]=temp_l;
		buf[4]=end;
		sum=Check_Sum(buf,5);
	
		USART_SendData(pUSARTx, head1);
		while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);                
		USART_SendData(pUSARTx, len);
		while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
		USART_SendData(pUSARTx,  temp_h);
		while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
		USART_SendData(pUSARTx,  temp_l);
		while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET;


这里
发送数据如图;使用串口调试助手。

TIM3_Int_Init(79,7199);//8MS

将定时器设置为8ms,使得采样率达到125hz;

3.qt篇

这里的绘图工具使用qt自带的图形库QTchart;

  • 01.UI界面

  • 这里不作介绍,要注意的是图形显示区域要使用QTchartview类。

  • 关联槽函数之类的就不讲了
    在这里插入图片描述
    如图:

  • 02.配置串口接受数据

QSerialPort::BaudRate baudRate; /*波特率*/
  QSerialPort::DataBits dataBits;
  QSerialPort::StopBits stopBits;
  QSerialPort::Parity checkbits; //校验位
  if (ui->baundrate->currentText() == "9600")
  {
    baudRate = QSerialPort::Baud9600;
  }
  else if (ui->baundrate->currentText() == "4800")
  {
    baudRate = QSerialPort::Baud4800;
  }
  else if (ui->baundrate->currentText() == "115200")
  {
    baudRate = QSerialPort::Baud115200;
  }
  数据位。。。。一样的操作

配置好后,串口就可以进行接受数据了。

  • 03.根据通信协议解析数据
  • 在接受到数据后,下一步就是检验数据是否出错,以及如何提取数据(这部分是项目中比较棘手的问题),所以需要注意哦!
  • 接受十分容易,直接用个qbytearray 接受就可以。
  • 这里特别需要注意的是,尽量不要独立关联一个槽函数进行readall,因为后面的绘图同样需要关联槽函数,两者会起冲突。
 QByteArray buf,
  QString show_receive;
  unsigned char arr1[2] = {'0'}; //用于保存两个字节的数据
  buf = serialport ->readAll(); //从第二次readall()开始进行拼接.第一次拼接空qbytearray

接下来接受了后,就是进行数据处理了。这里特别点出一点
处理数据过程中,除了对通信协议进行校验,还有一个很重要的是前后帧数据的拼接。
意思是说比如前一帧读到的是 0x…AA020809FF45AA02 下一帧0x0809FF45… 第一帧后面数据不完整,如果不想办法把不完整的一帧拼回来,这里就丢失一个点,画出来的波形就会不准确。
(完整一帧为帧头aa,数据长度02,两节8位数据08 09帧尾FF,和校验45)
故解决如下:

 uint8_t sum = 0;
  uint8_t temp_1 = 0;
  int temp = 0;
  index = 0;        // arr存储数据数组下标,每次开始置零
  static int A = 0; //用于标志readall进行了多少次

  QByteArray buf,
      buf_return; // buf用于接受readall(),buf_return用于接受imcomplete_data.
  QString show_receive;
  unsigned char arr1[2] = {'0'}; //用于保存两个字节的数据
  buf = serialport ->readAll(); //从第二次readall()开始进行拼接.第一次拼接空qbytearray

  buf_return = imcomlete_data(
      buf,
      imcomlete_data1); //触发拼接函数,防止readall()出来的前后两帧数据丢点。(必备)
  buf_return.append(buf);                //将前一帧尾部不完整的数据,拼接在下一帧数据中,再进行处理
  imcomlete_data1.clear(); //拼接完成后,用于存放不完整数据的数组清空

  //数据解析,检验数据是否有效;
  for (int i = 0; i < buf_return.size(); i++)
  {
    sum = 0; //注意校验和在每次写完数据后置零
    if (i < buf_return.size())
    {
      if (buf_return.at(i) == (char)0xaa)
      { //判断stm32发送的帧头
        sum += buf_return.at(i);
        if (i + 1 < buf_return.size())
        {
          if (buf_return.at(i + 1) == (char)0x02)
          { //判断stm32发送的数据位
            sum += buf_return.at(i + 1);
            if (i + 4 < buf_return.size())
            {
              if (buf_return.at(i + 4) == (char)0xab)
              { //判断stm32发送的尾部
                sum += buf_return.at(i + 4);
                sum += buf_return.at(i + 2); // check-sum和校验。注意数组越界
                sum += buf_return.at(i + 3);
                temp_1 = (int8_t)buf_return.at(i + 5); //强转比较
                if (sum == temp_1)
                {
                  arr1[0] = buf_return.at(i + 2);
                  arr1[1] = buf_return.at(i + 3);
                  temp = arr1[0] * 256 + arr1[1]; //这里就比较妙了,自己体会。
                  VOTE = temp * (3.3 / 4096);
                  arr.append(VOTE);
                  show_receive.sprintf("%s=%0.4f", "v=", VOTE);
                  ui->receive_pannel->appendPlainText(
                      show_receive); //将电压值显示在接受框里面。
                }
              }
            }
          }
        }
      }
    }
  }

数据拼接函数:

QByteArray Widget::imcomlete_data(QByteArray buf, QByteArray incomplete_data1)
{

  if (buf.size() >=
      5)
  { //特别注意这里一定要判断接收到的数据长度是否够,或者是否为空
    for (int i = 1; i <= 5; i++)
    {
      if (buf.at(buf.size() - i) == (char)0xaa)
      {
        if (i == 1)
        {
          incomplete_data1.append(buf[buf.size() - 1]);
        }
        if (i == 2)
        {
          incomplete_data1.append(buf[buf.size() - 2]);
          incomplete_data1.append(buf[buf.size() - 1]);
        }
        if (i == 3)
        {
          incomplete_data1.append(buf[buf.size() - 3]);
          incomplete_data1.append(buf[buf.size() - 2]); //写入保存不完整的数据
          incomplete_data1.append(buf[buf.size() - 1]);
        }
        if (i == 4)
        {
          incomplete_data1.append(buf[buf.size() - 4]);
          incomplete_data1.append(buf[(buf.size() - 3)]);
          incomplete_data1.append(buf[(buf.size() - 2)]);
          incomplete_data1.append(buf[(buf.size() - 1)]);
        }
        if (i == 5)
        {
          incomplete_data1.append(buf[buf.size() - 5]);
          incomplete_data1.append(buf[buf.size() - 4]);
          incomplete_data1.append(buf[(buf.size() - 3)]);
          incomplete_data1.append(buf[(buf.size() - 2)]);
          incomplete_data1.append(buf[(buf.size() - 1)]);
        }
      }
    }
  }

  return incomplete_data1;
}

通过上述操作,我们就可以得到数据,进行绘图了;

  • 04.将解析的数据进行绘图
  • 定义对象
private:
  Ui::Widget *ui;
  QChart *chart;         //画布
  QSplineSeries *series; //线
  QValueAxis *axisX;
  QValueAxis *axisY; // y轴

关联画图槽函数

private slots:
  void start_button();
  void stop_button();
  void serialPortReadyReady();
  void DrawLine(); //划线,这里可以考虑使用数组,关联画图槽函数
  void on_send_clicked();
  void on_clear_clicked();
  void on_line_shape_clicked();
};

初始化画布:

void Widget::initDraw()
{
  QPen penY(Qt::darkBlue, 3, Qt::SolidLine, Qt::RoundCap,
            Qt::RoundJoin); //设置画笔
  chart = new QChart();
  series = new QSplineSeries; //实例化对象
  axisX = new QValueAxis();
  axisY = new QValueAxis();
  chart->legend()->hide();
  chart->addSeries(series);
  axisY->setTickCount(5);
  axisY->setMin(0); //最小值
  axisY->setMax(4); //最大值
  axisX->setMin(0); //最小值

  axisX->setMax(xlen); //最大值
  axisX->setTitleText("实时时间");
  axisY->setTitleText("实时电压监测");
  axisY->setLinePenColor(QColor(Qt::darkBlue));
  axisY->setGridLineColor(QColor(Qt::darkBlue));
  axisY->setGridLineVisible(false);
  axisY->setLinePen(penY);
  axisX->setLinePen(penY);
  chart->addAxis(axisX, Qt::AlignBottom);
  chart->addAxis(axisY, Qt::AlignLeft); //设置坐标轴位置
  series->attachAxis(axisX);
  series->attachAxis(axisY);
  //将chart显示到窗口上
  ui->Widget::main_Draw->setChart(chart);
  ui->Widget::main_Draw->setRenderHint(QPainter::Antialiasing);
}

append追加画图;

for (int i = 0; i < arr.size(); i++)
  {
    series->append(x_count, arr.at(i));
    x_count++; //每次x增加1
  }
  if (x_count >= xlen)
  {
    x_count = 0;
    series->clear(); //清空屏幕
  }
  arr.clear();        //每次画完图后,清空Vector.
  buf_return.clear(); //清空原始数据
}

在这里插入图片描述
使用信号发生器产生正弦信号,测试成功绘图,这里上不了视频,其实采集绘图十分顺滑,不存在丢点卡顿的现象。

评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Rangers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值