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(); //清空原始数据
}
使用信号发生器产生正弦信号,测试成功绘图,这里上不了视频,其实采集绘图十分顺滑,不存在丢点卡顿的现象。