概述
仿真数据加工
MFC入门初级者,很多东西都还不会,如果有写的不对的地方望各位批评指正。
一、任务要求:
1.每1帧数据有128个字节
2.B0至B3头4个字节是时间(无符号整型),当天累计毫秒值单位为(0.1ms),B9计数(范围是0~255),每发一帧加1
3.B第一帧是“0x11”第二帧是“0x22”第三帧是“0x33”以此类推
4.最后一个字节是B127 存放核校验B9~B126(单字节相加取低,不考虑溢出)
5.1秒(1000ms)产生1帧,定时器
6.记盘(顺序存放)指定路径的.dat文件
7.显示:产生一帧显示在文本框中4*32
附加:输出到EXCEL表格(还未做)
卫星测控软件开发需要对卫星从发射、入轨、以及着陆过程进行系统仿真,仿真的“真”包含了卫星状态以及数据传输等多个模块,因此数据加工是一个极其基础而又重要的模块,将仿真的数据进行加工后一方面打包成数据帧格式,另一方面可进行可视化便于工作人员查看。
二、任务分析
- 定时器模块:该模块能实现每1000ms触发一次数帧的生成。
- 当日累计秒模块:该模块能够实现获取系统的时间,并按照给定单位进行换算。
- 加工数据模块:根据上述2~4的要求填写数据
- 数据存盘模块:该模块能够将数据存放在指定文件下,要求保存在二进制文件.dat中。
- 数据格式化输出模块:将每帧数据按照432 ,每8个字节间隔两个空格,32个字节为一行。
三、任务详解
1.定时器设计
定时器的意义在于定时完成某项任务,给系统传入一个给定的时间间隔,系统会在该时间间隔到达后自动触发某一事件,以实现周期性的自动操作。它的逻辑类似于,“每多长时间就要怎样”。
主要有两种方法:第一种是使用MFC的CWnd类提供的成员函数SetTimer实现定时器功能,第二种是使用Windows API函数SetTimer来实现。CWnd类的SetTimer成员函数只能在CWnd类或其派生类中调用,而API函数SetTimer则没有这个限制,这是一个很重要的区别。
步骤:
1.启动定时器
UINT_PTR SetTimer(
UINT_PTR nIDEvent,
UINT nElapse,
void (CALLBACK lpfnTimer
)(HWND,
UINT,
UINT_PTR,
DWORD
)
);
双击定时启动指定控件,进入 对应函数,在函数内添加SetTimer();参数 nIDEvent指定一个非零定时器ID,nElapse指定间隔时间,参数lpfnTimer指定一个回调函数的地址,如果该参数为NULL,则WM_TIMER消息被发送到应用程序的消息队列,并被CWnd对象处理。
void CMFCApplication5Dlg::OnBnClickedOk()//确定按钮对应函数
{
SetTimer(1,1000,NULL);
}
2、为WM_TIMER消息添加消息处理函数
如果调用CWnd::SetTimer函数时最后一个参数为NULL,则通过WM_TIMER的消息处理函数来处理定时事件。添加消息处理函数OnTimer(),添加方法是在解决方案资源管理器下选中对应项目标题,右键点击“类向导”——“消息”栏下——“WM_TIMER”——添加句柄,然后在.cpp文件中就会出现OnTimer(),在该函数下可进一步编写相应的功能。
2.当日累积秒
起初错误的理解为程序启动的时间累计,后面老师讲了我才知道是当天的累计时间,比如下午的15点04分7分58秒23毫秒,这就是当日累计时间,一般在卫星测控中要将时间换算成以毫秒或者0.1ms的单位。换算公式为:
a时b分c秒d毫秒换算为单位为0.1ms(1秒=10000(0.1ms))
(a×3600+b×60+c)×10000+d×10 (2.1)
这个时间实际就是系统的时间
新的问题来了,如何获得系统的时间呢
SYSTEMTIME t;
GetLocalTime(&t);//得到系统时间
m_jsTime = (t.wHour*3600 + t.wMinute*60 + t.wSecond) * 10000 + t.wMilliseconds * 10;
注意:时间在系统中存放时是低位在前高位在后
3.加工数据模块
刚开始做的第一版有一种奇怪的想法,总是想把数据转换成16进制再去保存到某一个变量,但是后来老师点了下我,其实任何数据在计算机内存中都是以二进制的形式存放的,至于我们说的各种进制都是在展示给人看的。题目中要求的数据帧是按照每个字节存放的,这里就可以直接用CByteAarry定义一个数据帧变量,然后按要求存放。
CByteArray是MFC中BYTE一种集合类,他以字节为单位建立数组,要求在使用前用SetSize()函数设置数组大小,如果没有使用SetSize,在数组中增加元素会引起数组内存空间频繁的重新分配以及数据拷贝。内存频繁的重新分配和数据拷贝会影响性能并造成内存碎片。讨厌的debug failed就总会出现。
其次就是按字节赋值,有一个很重要的函数memcpy,它用来做内存拷贝,你可以拿它拷贝任何数据类型的对象。
void * memcpy (void * dst,const void * src,size_t count);
此函数用于对内存进行复制,按照字节复制。第一个参数是目标内存地址,第二个参数是源内存地址,第三个参数是字节数。返回目标地址的指针。
通常使用memcpy_s更加稳定,一般加了_s的都是安全版本, 函数 memcpy_s( void *dest, size_t numberOfElements, const void *src, size_t count ), 先申请了一个缓冲区大小,为numberofelements,当count超过这个缓冲区的时候就开始溢出可能影响内存中下一个地址。
void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。这个用法要记住
四、完整代码
1.定时器设置
void CMFCApplication5Dlg::OnBnClickedOk()
{
SetTimer(1,1000,NULL);
}
2.内容
void CMFCApplication5Dlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_zjData += 1;//每发一帧自动加一,帧计数
if (m_zjData == 255)
{
m_zjData = 0;//指定时刻置零
KillTimer(1);//255秒时溢出
}
SetDlgItemInt(IDC_EDIT3, m_zjData);
CByteArray baData;//存放数据帧0~127
baData.SetSize(128);//设置帧大小
//1.计算今日累计秒
SYSTEMTIME t;
GetLocalTime(&t);
m_jsTime = (t.wHour*3600 + t.wMinute*60 + t.wSecond) * 10000 + t.wMilliseconds * 10;//单位为0.1ms可能存在溢出要注意
SetDlgItemInt(IDC_EDIT1, m_jsTime);//在文本框中输出累计秒
//2.加工数据dataProcess
DataProcess(m_jsTime, m_zjData, baData);
//3.数据存盘
Exception(baData);
//4.数据显示
m_sCode = FormatOutput(baData);
SetDlgItemText(IDC_EDIT2, m_sCode);
CDialogEx::OnTimer(nIDEvent);
}
3.格式输出函数
CString CMFCApplication5Dlg::FormatOutput(CByteArray& Data)//这里还可以用首地址,但是要给长度
{
CString strtemp, strPackage;
for(int i = 0; i < Data.GetSize(); i++)
{
strtemp.Format(_T("%02X "), (BYTE)Data[i]);
strPackage += strtemp;
if ((i+1) %8 == 0)
strPackage += " ";
if ((i+1) % 32 == 0)
strPackage += "rn";//必须要加r
}
return strPackage;
}
4.MFC 异常捕获
void CMFCApplication5Dlg::Exception(CByteArray& Data)
{
//这里必须要加一个文件是否打开的预判,MFC的异常处理,一般都放在try~catch中
try
{
CFile file(_T(".\test.dat"), CFile::modeCreate|CFile::modeNoTruncate|CFile::modeWrite);
file.SeekToEnd();//必须有这个才能保证每次生成一帧的数据不会被前一次覆盖
file.Write(Data.GetData(), Data.GetSize());
file.Close();
}
catch (CFileException* theException)
{
if(theException->m_cause == CFileException::fileNotFound)
TRACE("File not foundn");
theException->Delete();
}
}
5.数据加工函数
void CMFCApplication5Dlg::DataProcess(int m_jsTime, int m_zjData, CByteArray& Data)
{
BYTE sum=0;//校验和,单字节存放
//B0~B3存放时间
memcpy_s(&Data[0], Data.GetSize(), &m_jsTime, sizeof(m_jsTime));
//B4~B7放置0补位
for (int i = 4; i < 8; i++)
{
Data.SetAt(i, 0x00);
}
//B8位放置数据生成次数
Data[8] = BYTE(m_zjData);//在第8位数据进行赋值
//B9~B126循环存放0x11,0x22,0x33,0x44
switch(m_zjData % 4 )
{
case 0:
memset(&Data[9], 0x44, 127-9);
break;
case 1:
memset(&Data[9], 0x11, 127-9);//赋值函数,效率相对较高
break;
case 2:
memset(&Data[9], 0x22, 127-9);
break;
case 3:
memset(&Data[9], 0x33, 127-9);//memset适合一长串的赋值
break;
}
//B127存放校验和
for (int i = 8; i <= 126; i++)
{
sum += Data[i];//高字节在后sum有只是一个字节
}
Data[127] = sum;
}
注意:在MFC中所有自己定义的函数都需要进行函数的声明,因此编写函数的时候默念口决“定义,声明,调用”缺一不可。文件声明放在XXDlg.h文件中,如下图4.1,函数定义放在XXDlg.cpp文件中,即可在上面任意一处进行调用。
图4.1
五、实验遇到的问题
- 数据读取
在任何工程中但凡涉及到文件读取的操作都需要进行异常捕获,虽然程序运行正常,但是在实际的工程应用中很难保证文件一定能够正常打开,因此需要在打开文件时进行异常捕获,避免文件打不开的可能。 - int a=0x12整形不是放的是十进制数吗?
这个问题有点低级,但是当时遇到了后我对程序一下豁然开朗。int是整型,整型我们可以理解为没有小数的一个数字,不论是十进制,十六进制还是二进制都表示的是一个整数,所以要清楚“整型”≠“十进制”。 - MFC异常捕获时总会弹出debug failed的报错
查了程序发现文件写操作放在了try catch之外,try时文件打开,到catch文件就是关闭的状态,这个时候再进行写操作,内存溢出报错了。 - 文件写入值上一次的值总是被覆盖
解决该问题先要看open函数打开的模式是不是CFile::modeCreate|CFile::modeNoTruncate|CFile::modeWrite,CFile::modeNoTruncate表示的是不清除已有的文件在原有的文件上继续填写,但是这不代表不会覆盖文件里面的内容,因此必须要在写入前将文件指针指向每一次文件尾,确保是在上一次数据后补充填写。 - 积日积秒
年积日表示在一年中使用的连续计时法,当日积秒表示从凌晨0点开始采用累积计时法。一般秒数定义的时候都是double型
六、一点感悟
由于原来没用过C++做开发,研究生“养”成了用Matlab的“坏习惯”,所以工作了才发现,掌握一门语言真的很重要。开始很心急,什么都不会感觉很有压力,着急自己能不能快点掌握,所以那几天心情很郁闷。到后来我看到旁边盖楼的我就想,他们盖了一个星期也看不出来有什么变化,但后来仔细想,哪个房子又不是从地下地基打起,一砖一瓦盖起来的,什么事只要你肯做,一天进步一点,总会有质的变化的。告诫自己:“莫要心急,莫要急于求成,踏实走好每一步才是最重要的”
最后
以上就是真实红酒为你收集整理的MFC 入门基础:仿真数据加工基础的全部内容,希望文章能够帮你解决MFC 入门基础:仿真数据加工基础所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复