完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
单片机 (1)单片机类型可选用STM32系列或MSP430系列单片机; (2)单片机硬件可自购或自行制作核心板; (3)使用125Hz的采样率进行1个通道的ADC采样,每次采样结果保存为2字节整形; (4)单片机与上位机之间的通讯方式不限; (5)使用的外围硬件模块不限。 3、上位机 (1)上位机程序要求使用QT框架编写。 (2)上位机程序需要提供一个GUI,实时绘制单片机采集到的波形数据。 (3)界面中提供“开始采集”和“停止采集”按钮。 4、其他 (1)单片机与下位机之间应制定简单的通信协议,保证上位机显示的数据与单片机采集的数据没有过大的失真; (2)单片机和上位机程序都必须稳定、流畅运行,不能出现内存泄露等问题。(漏点问题很蛋疼) 2.stm32篇
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); } static void ADC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel =ADC1_2_IRQn; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 NVIC寄存器 } void ADC1_Init(void) { //u16 ADC_ConvertedValue; ADC_NVIC_Config(); ADC_GPIO_config(); ADCx_Mode_Config(); }` 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 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接受中断 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ; //抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 } 配置定时器(目的是为了达到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; TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct); TIM_ITConfig(TIM3, TIM_IT_Update ,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 TIM_Cmd(TIM3,ENABLE); } 写中断服务函数 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); USART_SendData(pUSARTx, end); while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET); USART_SendData(pUSARTx, sum); while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET); 发送数据如图;使用串口调试助手。 TIM3_Int_Init(79,7199);//8MS 将定时器设置为8ms,使得采样率达到125hz; 3.qt篇 这里的绘图工具使用qt自带的图形库QTchart;
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(); //清空原始数据 } 使用信号发生器产生正弦信号,测试成功绘图,这里上不了视频,其实采集绘图十分顺滑,不存在丢点卡顿的现象。 |
|
|
|
数据拼接函数:
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(); //清空原始数据 } 使用信号发生器产生正弦信号,测试成功绘图,这里上不了视频,其实采集绘图十分顺滑,不存在丢点卡顿的现象。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1627 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1550 浏览 1 评论
984 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1601 浏览 2 评论
1867浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
650浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
518浏览 3评论
536浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
506浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-24 18:22 , Processed in 0.598056 second(s), Total 52, Slave 45 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号