在刚开始学习单片机写程序的时候,大多数人都比较喜欢使用全局变量。因为这样写程序写起来比较简单,也容易理解。但是看官方例程的时候,大多数都喜欢使用结构体和指针。感觉指针和结构体看起来麻烦,写起来更麻烦,往往都是一长串字母。但是为什么官方都爱这样用呢?这样用的好处是什么,自己写程序怎么才能写成这种方式。

  下面通过一个实际的工程例子来说明,如何一步一步将全局变量改为指针和结构体的方式。

  系统需求是,通过一个控制板控制系统的输出功率、工作时间、启动/暂停。控制板上有4个按键,第一个按键用来设置系统的输出功率,第二个按键增加系统工作时间,第三个按键减小系统工作时间,第四个按键设置系统的启动和停止。

使用全局变量

  接下里使用全局变量开始编写程序

 u8 level;                   //功率等级u8 count_down;                //倒计时计数u8 work;                 //是否工作

  首先定义三个全局变量来存储系统的输出功率等级,系统工作时间,系统处于启动状态还是停止状态。

  接下来初始化这个三个全局变量。‘

void sys_init( void )
{level = LEVEL_DEF;                        //功率等级 0--10 <---> 0---1000W  一个档位100Wcount_down = COUNT_DOWN_DEF;           //倒计时1---15分钟work = 1;                             //0 工作  1 停止
}

  给三个变量分别设置初始值,设置功率等级为5,也就是半功率,工作时间15分钟,工作模式为停止。

  下面就需要在按键程序中判断按键,根据不同的按键来改变这三个值。

void read_key( void )
{u8 key;key = KEY_Sacn( 1 );switch( key ){case KEY1_PRES:      //功率等级set_value( powerAdd );break;case KEY2_PRES:       //时间加set_value( timeAdd );break;case KEY3_PRES:     //时间减set_value( timeSub );break;case KEY4_PRES:     //启动/停止set_value( start_pause );break;default:break;}
}//通过按键值设置参数
void set_value( u8 value )
{switch( value )                        //只有在工作状态下才能设置功率和时间{case powerAdd:                      //功率加if( work == 0 ){if(  level < LEVEL_MAX ){level++;}elselevel = LEVEL_MIN;}break;case timeAdd:                       //时间加if( work == 0 ){if( count_down < COUNT_DOWN_MAX ){count_down++;}}break;case timeSub:                        //时间减if( work == 0 ){if(  count_down > 1  ){count_down--;}}break;case start_pause:                 //启动work = ! work;if( work == 1 ){timer.ms = 0;timer.second = 0;timer.minute = 0;}break;default:break;}
}

  在按键读取函数read_key()中判断哪个按键按下,如果有按键按下就调用按键处理函数set_value()对系统的这三个全局变量进行设置。比如功率等级按键按下一次,记录功率等级的全局变量level的值就加1,当level值超过最大值之后,它的值就设置为最小值1,这样通过一个按键循环设置系统的输出功率。

  这样通过全局变量就可以实现需要的功能,但是现在观察这三个全局变量。

 u8 level;                   u8 count_down;          u8 work;

  单纯从名字上来看,很难发现这三个变量之间的关系,他们是不同系统的变量还是同一个系统的变量?这三个变量是描述了一个系统还是三个系统?如果代码中没有注释,理解起来就比较费劲。 还有另外一种情况,假如现在系统需要升级,一个控制板需要控制两个系统,两个系统除了名字不一样之外,其他的参数都是一样的。那么这时候这三个全局变量就需要修改为下面这种。

 u8 level1,level2;                   u8 count_down1,count_down2;         u8 work1,work2;

  此时程序中以前使用的三个全局变量,全部需要修改了。同样在处理按键的时候,还需要判断是修改系统1的变量还是修改系统2的变量。这样程序修改起来修改量就会比较大。一个按键处理函数还得分成两个按键处理函数,分别处理系统1和系统2的全局变量。

  那么有没有什么办法,可以把这三个独立的变量组合成一个,这时就需要用到结构体了。使用结构体可以参考库函数的用法。

  将GPIO的端口的三个属性组成一个结构体,这样在初始化端口的时候,就可以直接通过结构体来操作了。

  不管是哪个端口在初始化的时候,只需要定义一个结构体,这个结构体里面的成员就会是相同的,不管是GPIOA还是GPIOC,在初始化的时候,都需要设置引脚,模式,速度这三个属性。

使用结构体

  下面就模仿库函数,将全局变量修改为结构体形式。

  首先将全局变量修改为结构体形式。

typedef struct
{u8 level;                  //功率等级u8 count_down;                //倒计时计数u8 work;                 //是否工作
} Sys_TypeDef;Sys_TypeDef sys_info;

  按照库函数的那种方式,重新定义一个结构体类型Sys_TypeDef,它里面有三个成员,这三个成员的名字还是和全局变量的名字一样。这时这个结构体就成了一个数据类型了,不能直接使用。相当于 int类型一样了,是一个新的数据类型。要使用它,必须用这种类型,声明一个新的变量,像 int a;一样。此时用这个新的结构体类型,定义一个变量结构体变量 sys_info,这个变量里面就有三个成员了,这三个成员和上面的全局变量一样,代表了系统的三个属性。
  下来初始化系统。

 //系统参数初始化
void sys_init( void )
{sys_info.level = LEVEL_DEF;   //电源功率等级 0--10 <-> 0---1000W  一个档位100Wsys_info.count_down = COUNT_DOWN_DEF; //倒计时1---15分钟sys_info.work = 1;                  //0 工作  1 停止
}

  要设置系统属性的时候,通过结构体变量名后面加一个点来访问系统的属性。同样通过按键设置的时候,也是这种方式来访问。

//通过按键值设置参数
void set_value( u8 value )
{switch( value )                        //只有在工作状态下才能设置功率和时间{case powerAdd:                      //功率加if( sys_info.work == 0 ){if(  sys_info.level < LEVEL_MAX ){sys_info.level++;}elsesys_info.level = LEVEL_MIN;}break;case timeAdd:                       //时间加if( sys_info.work == 0 ){if( sys_info.count_down < COUNT_DOWN_MAX ){sys_info.count_down++;}}break;case timeSub:                     //时间减if( sys_info.work == 0 ){if(  sys_info.count_down > 1  ){sys_info.count_down--;}}break;case start_pause:                  //启动sys_info.work = ! sys_info.work;if( sys_info.work == 1 ){timer.ms = 0;timer.second = 0;timer.minute = 0;}break;default:break;}
}void read_key( void )
{u8 key;key = KEY_Sacn( 1 );switch( key ){case KEY1_PRES:      //功率等级set_value( powerAdd );break;case KEY2_PRES:       //时间加set_value( timeAdd );break;case KEY3_PRES:     //时间减set_value( timeSub );break;case KEY4_PRES:     //启动/停止set_value( start_pause );break;default:break;}
}

  通过这种方式,把原来的三个全局变量封装到了一个盒子中的。这样直接通过名字就可以清晰的看出,这个三个变量是一起的,他们共同来描述了这个系统的属性。相当于单打独斗的三个全局变量,现在组成了一个小团队。此时如果一个控制板需要控制两个系统,那么定义变量的时候就可以通过结构体来直接定义。

Sys_TypeDef sys_info1,sys_info2;

  通过这种方式定义之后,表面上看起来是两个变量,但是每个变量中还包含了三个成员。这就相当于将描述系统属性的三个全局变量全部拷贝的一份。在访问不同系统的内部属性时,可以按照下面的方式操作。

 sys_info1.level = 6;sys_info2.level = 8;

  结构体变量的名字不一样,但是变量内部成员的名字都是一样的。通过这个就可以很简单的看出来第一条语句是设置系统1的功率等级为6,第二天语句是设置系统功率等级为8。

  这样通过结构体来设置,代码上看起来更加的简洁明了。这时虽然可以通过再定义一个结构体变量来控制系统2,但是按键处理函数,依然得写两个,因为在按键处理函数中,直接调用的是结构体的全局变量。每次处理的都是指定系统的按键。如果有两个系统,那么就得写两个按键处理函数。

  有没有什么办法,可以只写一个按键处理函数,就可以处理两个系统的按键。这时就需要用到指针了。

使用指针

  观察系统函数可以发现,初始化不同的IO口时,调用的都是同一个初始化函数。

  这个函数的原型如下:

  这个函数的参数是两个指针,将端口和端口属性的初始化都通过指针传递进来。这样传递的是GPIOA的指针,就初始化GPIOA口,传递的是GPIOC的指针,就初始化GPIOC口。
下面就模仿库函数这种方法,将结构体全局变量修改为通过指针传递。

typedef struct
{u8 level;                  //功率等级u8 count_down;                //倒计时计数u8 work;                 //是否工作
} Sys_TypeDef;Sys_TypeDef sys_info;
//系统参数初始化
void sys_init( void )
{sys_info.level = LEVEL_DEF;   //电源功率等级 0--10 <-> 0---1000W  一个档位100Wsys_info.count_down = COUNT_DOWN_DEF;  //倒计时1---15分钟sys_info.work = 1;               //0 工作  1 停止
}

  结构体的定义和初始化不变,需要修改按键设置函数。

//通过按键值设置参数
void set_value( u8 value,Sys_TypeDef *Sys_InitStruct)
{switch( value )                        //只有在工作状态下才能设置功率和时间{case powerAdd:                      //功率加if( Sys_InitStruct->work == 0 ){if(  Sys_InitStruct->level < LEVEL_MAX ){Sys_InitStruct->level++;}elseSys_InitStruct->level = LEVEL_MIN;}break;case timeAdd:                       //时间加if( Sys_InitStruct->work == 0 ){if( Sys_InitStruct->count_down < COUNT_DOWN_MAX ){Sys_InitStruct->count_down++;}}break;case timeSub:                       //时间减if( Sys_InitStruct->work == 0 ){if(  Sys_InitStruct->count_down > 1  ){Sys_InitStruct->count_down--;}}break;case start_pause:                    //启动Sys_InitStruct->work = ! Sys_InitStruct->work;if( Sys_InitStruct->work == 1 ){timer.ms = 0;timer.second = 0;timer.minute = 0;}break;default:break;}
}

  原来是在按键处理函数中直接调用结构体的全局变量,现在要将结构体作为一个参数传递到按键处理函数中。由于结构体的类型为Sys_TypeDef ,那么就在按键处理函数的参数中定义 一个 Sys_TypeDef 类型的指针,将结构体的地址直接传递到函数中。在函数中访问结构体的成员时,不能向以前一样 直接通过 .符号来访问,而是需要通过->符号来访问。当调用这个函数的时候,就需要将结构体的地址传递进来。

void read_key( void )
{u8 key;key = KEY_Sacn( 1 );switch( key ){case KEY1_PRES:      //功率等级set_value( powerAdd ,&sys_info);break;case KEY2_PRES:     //时间加set_value( timeAdd ,&sys_info);break;case KEY3_PRES:       //时间减set_value( timeSub,&sys_info );break;case KEY4_PRES:       //启动/停止set_value( start_pause,&sys_info );break;default:break;}
}

  在调用按键处理函数的时候,需要将结构体的地址传递到函数中去,所以要在结构体名称前面添加上&符号。此时系统参数都在sys_info这个结构体中包含着,在传递参数的时候在这个结构体名称前加上&符号就行。就相当于将这个结构体的地址传递了过去。在按键处理函数中,直接通过地址去操作这个结构体。此时如果需要同时处理两个系统,那么系统的结构体定义就改为:

Power_TypeDef sys_info1;
Power_TypeDef sys_info2;

  在按键处理的时候,这两个结构体就可以共用一个按键处理函数了,假如此时系统1的功率等级按键按下,就将系统1的结构体地址传递过去。

set_value( powerAdd ,&sys_info1);

  假如此时系统2的功率等级按键按下,就将系统2的结构体地址传递过去。

set_value( powerAdd ,&sys_info2);

  这样两个不同的系统就可以共享一个按键处理函数了。当哪个系统需要处理按键的时候,只需要将系统结构体的地址传递到按键处理函数中就行,而按键处理函数在处理的过程中就不需要关心当前处理的是哪个系统的参数。相当于按键处理函数就完全脱离了对系统的依赖,只有系统的框架一样,这个函数就成了一个通用的函数。

  通过上面三个例子可以看到,从按键处理函数直接调用全局变量,到调用结构体,最后到调用结构体的地址。按键处理函数对具体系统的依赖逐渐减少。按键处理函数从处理一个具体的系统参数抽象为了处理一类相似系统的参数。函数看起来更加抽象了,同时函数的功能也更加强大了。

  通过上面的例子就可以明白,为什么官方系统使用的接口函数全部是这种抽象的函数了,函数的参数都是各种结构体和指针,这样不同型号的单片机,只要是处理方法类似,那么就可以统一调用这一个函数。不同的单片机不同的GPIO口,处理的方法基本都是一样的。这样在写单片机的程序时,只要学会了一种单片机的使用方法,那么其他类似的单片机就全部会使用了,极大的降低了单片机的学习成本。使用起来也更加方便了。

为什么要在单片机程序中使用结构体和指针相关推荐

  1. c语言中的结构体定义和常见用法

    1.结构体简述和概念 结构体是C语言中一种重要的数据类型,该数据类型由一组称为成员(或称为域,或称为元素)的不同数据组成,其中每个成员可以具有不同的类型.结构体通常用来表示类型不同但是又相关的若干数据 ...

  2. c++ 结构体初始化_单片机C语言 - 基于结构体的面向对象编程技巧

    单片机C语言 - 基于结构体的面向对象编程技巧 一.面向对象 面向对象是软件开发方法,是相对于面向过程来讲的.通过把数据与方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式 ...

  3. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针...

    1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 1 2 3 4 5 6 ...

  4. C语言中的结构体,联合,链表和枚举,位域(上)

    结构名只能表示一个结构形式, 编译系统并不对它分配内存空间. 只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间. 一.结构的定义 定义一个结构的一般形式为: struct 结构名 { 成员 ...

  5. C++工作笔记-结构体与类的进一步探究(在C++中的结构体,非C语言结构体)

    今天把Qt Creator中的项目放到VS上,使用MSVC编译器发现跑不动链接错误,报的是如下的这个错误: 我在Qt上用MinGW明明不会报错,而他却说链接不到,日了个狗. 后面根据报错提示,我把如下 ...

  6. python中的记录指针_使用Python向C语言的链接库传递数组、结构体、指针类型的数据...

    使用python向C语言的链接库传递数组.结构体.指针类型的数据 由于最近的项目频繁使用python调用同事的C语言代码,在调用过程中踩了很多坑,一点一点写出来供大家参考,我们仍然是使用ctypes来 ...

  7. 成员变量隐藏c语言,C语言中隐藏结构体的细节

    我们都知道,在C语言中,结构体中的字段都是可以访问的.或者说,在C++ 中,类和结构体的主要区别就是类中成员变量默认为private,而结构体中默认为public.结构体的这一个特性,导致结构体中封装 ...

  8. c++中的结构体_C ++中的结构

    c++中的结构体 介绍 (Introduction) In this tutorial, we are going to learn the basics of Structures in C++, ...

  9. c中的结构体嵌套问题_C中的结构

    c中的结构体嵌套问题 Structures in C language are basically user-defined data types that enables the user to c ...

  10. 结构体复数相乘c语言,复数乘法中的结构体赋值实现代码

    复数乘法中的结构体赋值实现代码 废话不多说,直接上代码 复制代码 代码如下: #include using namespace std; typedef struct { double real; d ...

最新文章

  1. linux 更新yum源 改成阿里云源
  2. python怎么用matplotlib_Python Matplotlib 绘图使用指南 (附代码)
  3. 研究一个新的功能的时候,如何获取该资源的文档
  4. 98. Validate Binary Search Tree
  5. mysql ini配置文件分组排序_MySQL配置文件mysql.ini参数详解
  6. 浅析:提升手机APP开发和运营成效的经验分享
  7. dvwa学习笔记之xss
  8. 快解析:管家婆C9异地访问解决方案
  9. IOS AVPlayer视频播放器 AVPlayerViewController视频播放控制器
  10. IDEA 导入项目中文注释乱码如何解决
  11. gauscoor软件怎么用_高斯坐标经纬度转换器
  12. C#中Panel控件和GroupBox控件(未完成)
  13. 常犇_武汉大学管理学院2019年工商管理硕士(MBA)第三批复试通知
  14. 第五章-对单词进行分类和标记
  15. 说说我是如何拿下腾讯offer的
  16. DOS的建文件夹,移动图片,多级文件夹建立
  17. AD18如何制作logo
  18. 用JAVA 做一个简易版的坦克大战(只实现基本功能)
  19. 芯海科技2022数字芯片笔试题
  20. 华为HiLink智慧家庭生态发布 引领未来智能生活

热门文章

  1. SOLIDWORKS 2023出详图和工程图新增功能
  2. Thematic Contests CodeForces - 1077E
  3. 系统架构师笔记——数据库
  4. 5V转3.3V,你学会了吗?
  5. 【星门跳跃】解题报告
  6. iOS 15:如何查找丢失或失窃的 iPhone,就算它已关机也可远程定位
  7. 初学者该掌握的计算机知识,初学者该如何学习电脑知识
  8. 这5个是不是元宇宙游戏遗珠?
  9. GitHub 上最适合初学者的开源项目——Python 篇
  10. Shapefile属性操作之增