【AT89C52】用结构体进行数据定义

接着上面的话题,我们将数据通过串口接收中断接收到我们事先定义好的数组里面,数据也经过帧头和校验和的校验了,接下来就要将这组数据进行解析。传统的方法我们直接提取数组的内容,接收的数据定义有uint8或者int8还有uint16,int16这种数据,如果分大端小端的话,我们还要进行转换。有的为了节约数据长度,例如开关量,只是利用到了一个字节某个位。例如我这次接收的数据是一组环境温度数据。内容如下:

1. 多参数传感器数据类型表

字 节

名称

说明

B1

帧头1

固定值 07h

B2

帧头2

固定值 17h

B3

数据

eCO2 高字节

B4

数据

eCO2 低字节

B5

数据

eCH2O 高字节

B6

数据

eCH2O 低字节

B7

数据

TVOC 高字节

B8

数据

TVOC 低字节

B9

数据

PM2.5 高字节

B10

数据

PM2.5 低字节

B11

数据

PM10 高字节

B12

数据

PM10 低字节

B13

数据

Temperature 整数部分

B14

数据

Temperature 小数部分

B15

数据

Humidity 整数部分

B16

数据

Humidity 小数部分

     B17

数据

illumination高字节

     B18

数据

illumination 低字节

     B19

数据

气压高字节

     B20

数据

气压低字节

     B21

数据

                                      状态1
     B22

数据

                                      状态2

B23

校验和

校验和

也就是说接收的数据帧就是由上述数据组成。

我们再来看一下结构体的定义:我们声明的结构体只是描述了对象是由什么组成,并未创建实际的数据对象。因此我们也可以把结构声明为模板,该模板勾勒出这个结构是如何存储数据的。

2. 结构体基础知识

关键字:struct 它表明了跟在其后的是一个结构,后面是一个可选择的标记。所以在申明结构体标量时可以这样申明:

struct        UploadFormat        deviceuploadformat;

我们将deviceuploadformat声明为UploadFormat结构布局的结构变量。

在结构体中花括号括起来的是结构成员列表,成员都用自己的声明来描述,可以是任意一种C的数据类型,也可以是其他结构。右花括号后面的分号表示结构局部定义结束。同样将结构体也可以声明在函数的外部和内部,外部所有函数都可以使用他的标记UploadFormat,内部只有当前函数可以使用标记UploadFormat。

2.1 定义结构变量

结构布局只是告诉编译器如何表示数据,并未让编译器为数据分配空间。

struct UploadFormat
{
 uint8 head[2];
 uint16 eco2;
 uint8 checksum;
};

而定义结构变量则是为该变量分配空间。

struct        UploadFormat        deviceuploadformat; 

而就计算机而言,上述声明等同于下述声明:

 struct UploadFormat
{
 uint8 head[2];
 uint16 eco2;
 uint8 checksum;
} deviceuploadformat; 

同样声明结构体和定义结构体变量也可以合成一步:

struct 
{
 uint8 head[2];
 uint16 eco2;
 uint8 checksum;
}deviceuploadformate;

注意:如果要多次使用该结构体模板就需要进行标记,或者使用typedef。

2.2 结构体初始化

我们初始化变量和数组的时候是直接赋值,那么初始化结构体也是一个道理:

typedef struct{
    __IO uint16_t  SetTemp;     //设定目标 Desired Value
    __IO uint16_t  SumError;    //误差累计
    __IO uint16_t  Proportion;  //比例常数 Proportional Const
    __IO uint16_t  Integral;    //积分常数 Integral Const
    __IO uint16_t  Derivative;  //微分常数 Derivative Const  
    __IO float  Proportion_Current;  //比例常数 Proportional Const
    __IO float  Integral_Current;    //积分常数 Integral Const
    __IO float  Derivative_Current;  //微分常数 Derivative Const 
    __IO float  LastError;   //Error[-1]
    __IO float  PrevError;   //Error[-2]     
}PID_TypeDef; 
PID_TypeDef PidChannel_0 = {
    .SetTemp = 50,     //设定目标 Desired Value
    .LastError = 0,   //Error[-1]
    .PrevError = 0,   //Error[-2] 
    .Proportion = 80,  //比例常数 Proportional Const
    .Integral = 0,    //积分常数 Integral Const
    .Derivative = 80,  //微分常数 Derivative Const 
    .Proportion_Current = 50,
    .Integral_Current = 0,
    .Derivative_Current = 1  
};

简而言之,在使用一对花括号括起来初始化,各初始化项用逗号分隔 

因此对于上述数据描述的23个字节,我们可以将其定义为这样一个结构体。

struct UploadFormat
{
 //帧头
 uint8 head[2];
 uint16 eco2;
 uint16 ech2o;
 uint16 tvoc;
 uint16 pm25;
 uint16 pm10;
 uint16 temperature;
 uint16 humidity;
 uint8  conrtol1;
 int8   control2;
 uint8  eco2alarm:1;
 uint8  ech2oalarm:1;
 uint8  tvocalarm:1;
 uint8  pm25alarm:1;
 uint8  pm10alarm:1;
 uint8  tempalarm:1;
 uint8  humidityalarm:1;
 uint8  : 1;
 uint8  light0:1;
 uint8  light1:1;
 uint8  light2:2;
 uint8  light3:3;
 uint8  :4;
 //校验位
 uint8 checksum;
};

2.3 嵌套结构

如果我还有一个同样的设备呢?这就需要嵌套结构。也就是在结构中包含另一个结构。

struct UploadFormat
{
 //帧头
 uint8 head[2];
 struct DEVICE_UPLOAD
    {
         uint16 eco2;
         uint16 ech2o;
         uint16 tvoc;
         uint16 pm25;
         uint16 pm10;
         uint16 temperature;
         uint16 humidity;
         uint8  conrtol1;
         int8   control2;
         uint8  eco2alarm:1;
         uint8  ech2oalarm:1;
         uint8  tvocalarm:1;
         uint8  pm25alarm:1;
         uint8  pm10alarm:1;
         uint8  tempalarm:1;
         uint8  humidityalarm:1;
         uint8  : 1;
         uint8  light0:1;
         uint8  light1:1;
         uint8  light2:2;
         uint8  light3:3;
         uint8  :4;
        }device1,device2;
 //校验位
 uint8 checksum;
};

上述是结合起来的,拆分开就是如下形态:

struct DEVICE_UPLOAD
    {
         uint16 eco2;
         uint16 ech2o;
         uint16 tvoc;
         uint16 pm25;
         uint16 pm10;
         uint16 temperature;
         uint16 humidity;
         uint8  conrtol1;
         int8   control2;
         uint8  eco2alarm:1;
         uint8  ech2oalarm:1;
         uint8  tvocalarm:1;
         uint8  pm25alarm:1;
         uint8  pm10alarm:1;
         uint8  tempalarm:1;
         uint8  humidityalarm:1;
         uint8  : 1;
         uint8  light0:1;
         uint8  light1:1;
         uint8  light2:2;
         uint8  light3:3;
         uint8  :4;
        };

struct UploadFormat
{
 //帧头
 uint8 head[2];
 struct DEVICE_UPLOAD  device1;//嵌套结构
 struct DEVICE_UPLOAD  device2;//嵌套结构
 //校验位
 uint8 checksum;
};

如果访问结构体中的一个成员就要如下:

struct        UploadFormat        deviceuploadformat; 
struct        DEVICE_UPLOAD       device1;
deviceuploadformat.device1.eco2;

2.4 位域

位域
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度

2.5 指向结构的指针

我们主要了解以下问题:

1. 如何定义指向结构的指针;

2.如何用这样的指针访问结构体成员;

2.5.1 声明和初始化结构指针

struct UploadFormat *msg;

声明结构体指针主要有关键字struct结构体标记星号(*)指针名称组成。

这个申明并没有创建一个新的结构,但是其指针可以指向任意的现有的UploadFormat类型结构。如果deviceuploadformat为UploadFormat类型的结构变量。那么

msg = &deviceuploadformat;

2.5.2 使用指针访问成员

msg -> head[0];

它等同于:

deviceuploadformat.head[0];

也等同于:

(msg*).head[0];

3. 向函数传递结构信息

3.1 对形参使用const

至于为什么要产地数组的指针呢? 如果编写的是类似int型的函数,通常直接传递数值就可以了,但是对于数组的传递,也只能传递指针,这也是必须的,因为传递指针的效率要更高。因此如果一个函数按值传递数组,那就需要分配足够的空间来存储原数组的副本,然后把原数组所有数据拷贝至新的数组中。而把数组的地址传递给函数,让函数直接处理原数组的值效率更高。

但是传递地址又会产生一些问题,如果按值传递,他的优点是可以保证数据的完整性,如果函数使用的是原始数据的副本,就不会产生原始数据被修改的情况。但是,处理数组的函数通常都是用来处理原始数据,因此传地址有可能修改原始数据。

有时可能需要对原始数据进行修改,但不需要修改原始数据的时候,错误的编程很有可能破坏原始数据。对此,为了避免类似的错误,我们可以对不想改变原始数据的数组添加关键字const。

使用const可以保护数组的数据不被破坏,因此如果编写的函数需要修改数组,在声明数组形参时不使用const,如果编写的函数不用修改数组,则声明数组形参时最好加上const。

向函数传递数据帧也就是数组的地址,再将数组变为结构体指针。

void Uart0FrameHandle(uint8 idata *line)
{


}

如果要编写一个结构体相关的函数,使用结构作为参数还是使用指针作为参数呢?

首先使用指针作为参数的优点是:执行效率快,只需要传递地址;缺点是不能够保护数据,被调函数容易影响原来结构中的数据。通过增加const限定符可以解决。const只能读不能写。加了const限定后,程序运行过程中就不能修改数组的内容了。

传递结构直观清晰,但是浪费时间和存储空间,对于较大的结构,传递指针或者传递函数所需要的成员更合理。

3.2 将数组强制转换为结构体指针

	struct   UploadFormat *msg = (struct  UploadFormat *)line;

我们将数组line强制转化为结构体UploadFormat类型,只是从语法上来说类型进行了改变而已,用新的类型的方式来解释原来内存中的值,即是让结构体按照自己的属性重新读取数组中的数据。

上面的语句分开写就是这样:

​
void Uart0FrameHandle(uint8 idata *line)
{
    struct   UploadFormat *msg; //定义一个结构体指针
    msg = (struct  UploadFormat *)line;//将数组指针的地址强制转换为结构体指针,并赋值给msg
}

同时应该注意的是:由于计算机存储的模式(大端模式/小端模式)以及计算机中字节对齐的问题可能会导致读出来的数据不对。这个是需要大家注意的地方。

这样的操作好处是将接收的数据帧与定义的结构体数据类型进行了匹配。因此我调用结构体数值的同时也就意味着对其数据帧的调用。例如

void Uart0FrameHandle(uint8 idata *line)
{
    struct   UploadFormat *msg = (struct  UploadFormat *)line;
    //将数组指针的地址强制转换为结构体指针,并赋值给msg
    int16 co2parameter;
    int8  checksum;
    bit light0state;
    co2parameter = msg -> device1.eco2;
    checksum = msg -> checksum;
    light0stste = msg -> device2.lightstate;  
}

这样做的好处是,当数据增添,或者数据类型修改,只需要修改结构体数据类型以及接收数据帧的数据长度即可。不必再纠结以提取数组时数据位置的人工编排,大大提高了数据的可读性和可操作性,非常值得推荐。

4. 联合体介绍

4.1 需求说明

介绍完数据的接收,在介绍一下数据的发送。发送数据帧定义为六个字节:

帧头1  帧头2  功能帧  数据帧1  数据帧2  校验和

我们可以针对上述数据帧的类型,抽象出一个结构体。

但是数据帧1和数据帧2在不同功能下的数据类型是不同的。可能是int8、uint8、int16和uint16。

针对这种情况就需要用到联合体来共同构建我们想要的数据类型。

4.2 联合体介绍

联合union是一个数据类型,它能够在同一个存储空间存储不同的数据类型(不是同时储存)。

union DATA{
	int16 i16;
	uint8 u8[2];
}cdata;

或者这样申明:

union DATA{
	int16 i16;
	uint8 u8[2];
};
union DATA cdata

上面形式声明的结构可以存储一个int16类型,两个uint8类型。应该注意的是,所声明的联合体只能存储一个int16类型,或者两个uint8类型。

因此在结构体中引入联合体就会使数据类型变得丰富。

struct SendFormat
{
	uint8 head[2];
	uint8 cmd;
	union DATA{
		int16 i16;
		uint8 u8[2];
	}cdata;
	uint8 checksum;
};

或者把联合体放在外面:

union DATA{
	int16 i16;
	uint8 u8[2];
}cdata;

struct SendFormat
{
	uint8 head[2];
	uint8 cmd;
    UNION DATA cdata; 
	uint8 checksum;
};

对于发送数据我们对该结构体定义一个结构体指针。

void send_cmd(struct SendFormat *msg)
{
    msg->head[0] = 0x17;
    msg->head[1] = 0x07;
    msg->checksum = CheckSum((uint8 *)msg, 5);
    Uart0SendStr((uint8 *)msg, 6);
    memset(msg,0,sizeof(struct SendFormat));
}

memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。

 首先建立结构体类型变量。

struct SendFormat idata sendmsg;

调用的话就如下调用即可。 

sendmsg.cmd = Order_MOTOY;
sendmsg.cdata.i16 = EN_PARA_MidStep;
sendmsg.cdata.u8[0] = 0;
sendmsg.cdata.u8[1] = EN_AUTO_SCAN;
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
目录 第一章 多媒体概念介绍 6 1.1视频格式 6 1.1.1常见格式 6 1.2音频格式 9 1.2.1常见格式 9 1.2.2比较 15 1.3字幕格式 15 1.3.1外挂字幕与内嵌字幕的阐述 15 1.3.2外挂字幕视频与内嵌字幕视频的画面比较 15 1.3.3外挂字幕的三种格式 15 1.4采集录制和播放渲染 16 1.4.1视频采集 16 1. 4.2视频录制 17 1.4.3视频渲染 17 1.5编解码器 18 1.6容器和协议 19 1.6.1容器格式和编码格式 19 1.6.2协议 26 1.6.2.1 视频协议 26 1.6.2.2 音频协议. 26 1.6.2.3 上层通讯协议 27 1.7常用概念介绍 27 1.7.1硬解 27 1.7.2 IBP帧 28 1.7.3 DTS和PTS 31 1.7.4 分辨率 31 1.7.5 码率 32 1.7.6 帧率 32 1.7.7 RGB和YUV 32 1.7.8 实时和非实时 32 1.7.9 复合视频和s-video 32 1.7.10 硬件加速 32 1.7.11 FFmpeg Device 32 第二章 FFmpeg框架 34 2.1 FFmpeg概述 34 2.1.1简介 34 2.1.2功能 34 2.1.3模块组成 35 2.1.4命令集 35 2.2 媒体播放器三大底层框架 37 第三章 编译及简单应用 41 3.1 FFmpeg库编译和入门介绍 41 41 3.2 流媒体数据流程讲解 49 3.3 简单应用 51 3.4 SDL( Simple Direct Layer) 55 3.4.1 SDL显示视频 55 3.4.2 SDL显示音频 55 3.5 ffmpeg程序的使用(ffmpeg.exe,ffplay.exe,ffprobe.exe) 56 3.5.1 ffmpeg.exe 56 3.5.2 ffplay.exe 56 3.5.3 ffprobe.exe 56 第四章 数据结构 57 4.1 AVCodec结构体 59 4.2 AVCodecContext结构体 59 4.3 AVInputFormat结构体 60 4.4 AVFormatContext结构体 61 4.5 MovContext结构体 62 4.6 URLProtocol结构体 62 4.7 URLContext结构体 63 4.8 AVIOContext结构体(老版本为:ByteIOContext) 63 4.9 AVStream结构体 64 4.10 MOVStreamContext 结构体 65 4.11 AVPacket 结构体 66 4.12 AVPacketList 结构体 67 4.13 AVFrame结构体 67 第五章 重要模块 76 5.1 libavutil公共模块 76 1 文件列表 76 2 common.h 文件 76 3 bswap.h 文件 78 4 rational.h 文件 79 5 mathematics.h 文件 80 6 avutil.h 文件 80 5.2 libavcodec编解码模块 82 1 文件列表 82 2 avcodec.h 文件 82 3 allcodec.c 文件 87 4 dsputil.h 文件 87 5 dsputil.c 文件 88 6 utils_codec.c 文件 88 7 imgconvert_template.h 文件 99 8 imgconvert.c 文件 121 9 msrle.c 文件 164 10 turespeech_data.h 文件 171 11 turespeech.c 文件 174 5.3 libavformat容器模块 184 1 文件列表 184 2 avformat.h 文件 184 3 allformat.c 文件 190 4 cutils.c 文件 190 5 file.c 文件 192 6 avio.h 文件 194 7 avio.c 文件 196 8 aviobuf.c 文件 200 9 utils_format.c 文件 209 10 avidec.c 文件 220 5.4 libswscale视频色彩空间转换 243 5.5 libswresample音频重采样 243 5.6 libavfilter音视频滤器 243 5.7 libavdevice设备输入和输出容器 243 5.8 libpostproc视频后期处理 243 第六章 播放器 243 6.1 视频播放器 243 6.1.1 ffmpeg库的配置 243 6.1.2 一个简单的视频播放器 244 6.2 音频播放器 247 6.3 一个完整的播放器--ffplay 253 6.3.1 ffplay流程图 253 6.3.2 ffplay源码剖析 254 第七章 应用开发 275 7.1 ffmpeg库的使用:编码 275 第八章 关键函数介绍 280 8.1 avformat_open_input 280 8.2 avcodec_register_all() 281 8.3 av_read_frame() 283 8.4 avcodec_decode_video2() 283 8.5 transcode_init() 283 8.6 transcode() 294 第九章 ffmpeg相关工程 301 9.1 ffdshow 301 ffdshow 源代码分析1 : 整体结构 302 ffdshow 源代码分析 2: 位图覆盖滤镜(对话框部分Dialog) 304 ffdshow 源代码分析 3: 位图覆盖滤镜(设置部分Settings) 312 ffdshow 源代码分析 4: 位图覆盖滤镜(滤镜部分Filter) 317 ffdshow 源代码分析 5: 位图覆盖滤镜(总结) 322 ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec) 322 ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec) 344 ffdshow 源代码分析 9: 编解码器有关类的总结 352 9.2 LAV filters 357 LAV Filter 源代码分析 1: 总体结构 357 LAV Filter 源代码分析 2: LAV Splitter 358 LAV Filter 源代码分析 3: LAV Video (1) 382 LAV Filter 源代码分析 4: LAV Video (2) 400 9.3 MPlayer 427 9.3.1 Mplayer支持的格式 427 9.3.2 Mplayer 头文件的功能分析 427 9.3.3 MPlayer.main 主流程简要说明 428 9.3.4 Mplayer源码分析 429 第十章 开发实例 436 第十一章 mp4文件封装协议分析 436 11.1 概述 436 11.2 mp4的物理结构 436 11.3 数据的组织结构 437 11.4 mp4的时间结构 437 11.5 文件结构分析 438 11.5.1 File Type Box(ftyp) 438 11.5.2 Movie Box(moov) 438 第十二章 flv 文件格式分析 457 12.1 概述 457 12.2 文件总体结构 457 12.3 文件结构分析 458 12.3.1 flv文件头的结构 458 12.3.2 body主体结构 459 附录A:常见问题 465 1 ffmpeg 从内存读取数据 465 2 MFC使用SDL播放音频没有声音的解决方法 465 附录B:经典代码示例 466 附录C:ffmpeg参数文详细解释 477 附录D:ffplay的快捷键以及选项 479 附录E: ffmpeg处理rtmp流媒体 481

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

米杰的声音

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

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

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

打赏作者

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

抵扣说明:

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

余额充值