- 任務概況
- stm32篇
- qt篇
1.任務概況
單片機
(1)單片機型別可選用STM32系列或MSP430系列單片機;
(2)單片機硬體可自購或自行制作核心板;
(3)使用125Hz的采樣率進行1個通道的ADC采樣,每次采樣結果保存為2位元組整形;
(4)單片機與上位機之間的通訊方式不限;
(5)使用的外圍硬體模塊不限,
3、上位機
(1)上位機程式要求使用QT框架撰寫,
(2)上位機程式需要提供一個GUI,實時繪制單片機采集到的波形資料,
(3)界面中提供“開始采集”和“停止采集”按鈕,
4、其他
(1)單片機與下位機之間應制定簡單的通信協議,保證上位機顯示的資料與單片機采集的資料沒有過大的失真;
(2)單片機和上位機程式都必須穩定、流暢運行,不能出現記憶體泄露等問題,
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);
}
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;
-
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(); //清空原始資料
}

使用信號發生器產生正弦信號,測驗成功繪圖,這里上不了視頻,其實采集繪圖十分順滑,不存在丟點卡頓的現象,
專案原始碼已上傳GitHub:
https://github.com/LUCKY-SEN2002/QTchart.git
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287001.html
標籤:其他
上一篇:RS-485通信協議簡介
下一篇:關于公轉與自轉的相關代碼
