原创 C语言学习总结-谈谈俺学单片机编程的几种新用法

2009-8-12 15:20 6120 10 18 分类: MCU/ 嵌入式

搞嵌入式的,大都用C语言写代码,本人从事单片机开发,也写了不少的代码,一直习惯用 if switch打天下,在定义数据结构的时候也只用到 字符型、整型、数组,位;很少用结构体,共用体,枚举,因为咱C语言学得不好,和它们不熟,总感觉它们不那么好招呼,重要的是自已觉得没必要用上它们。随着越来越多的积累,咱写代码的风格也在不断的发生变化,从以前的喜欢将所有的函数及数据的定义写在一个文件里到逐渐的将函数按功能模块化、从以前的习惯直接在程序里写常数到慢慢的开始用上宏来代替,咱编程的风格也逐渐开始正规化,编程水平也逐步提升,当然这些成绩都源于咱不断的学习,学习匠人的编程规范、学习herald的感悟设计、还有网上写得非常出色的代码以及STM32的固件函数库,在咱的不断领悟和思考下,总结了几点关于C语言的用法,与大家共同分享。


<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 


一、        学习匠人的头文件包含巧妙用法


当一个头文件被多个C文件包含,且该头文件中定义了这些C文件的公共变量,则在编译的时候会出现重复定义,导致编译通不过,通常我们会采用如下两种做法来解决上述问题。


(为了让问题表述得更清楚,我们假设两个C文件C1C2C3,一个头文件H1C1C2C3有两个公共变量V1V2


1、  C1文件中定义变量V1V2,在C2C3文件中对V1V2extern声明;


2、  C1文件中定义变量V1V2,在H1中对V1V2extern声明,然后在C2C3文件中包含H1


          很显然,以上两种方法都要对V1V2书写至少两次,一次定义,一次外部声明,且不是在同一文件下,这样不利于管理和修改,有没有一种方法可以让这些公用的变量放在一个文件里,且只要书写一次呢?


         偶在二姨那里无意中看到匠人的发帖,就是关于该问题的讨论,现在我转发一下,与大家同共分享。首先我们将要用到的公共变量全部书写到com.h文件中,每一个变量在定义前加一个符号EXT_,当该头文件被main.c函数包含时,定义EXT_为空,表示com.h中的变量在main.c中被定义,当被其它文件包含时,定义EXT_extern,表示外部声明,如:


Com.h文件:


//避免重复定义


#ifdef  root


        #define EXT_ 


#else


        #define EXT_  extern


#endif


        


//全局变量


EXT_ u8  variable1;   //该变量在三个C文件中都要用到


 


Main.c


#define root      //在包含com.h前定义root


#include "com.h"


 


二、        用结构体的方式来定义总线或外设地址


当一个整体包含不同类型的多个成员时,通常用结构体来定义结构体变量,这样内存会将这些变量按照递增的方式分配到相邻的地址(不对齐的地方会有填充),按“结构体名.成员名”的方式访问结构体内的成员,这是访问结构体变量的方式;但是还有一种指向结构体变量的指针,它可以将某个地址转换成该结构体类型的指针,比如寄存器的定义:


(以下是摘自STM32固件函数库,关于GPIO的定义)


typedef struct


{


  vu32 CRL;  //0


  vu32 CRH;  //偏移量4


  vu32 IDR;  //偏移量8


  vu32 ODR;


  vu32 BSRR;


  vu32 BRR;


  vu32 LCKR;


} GPIO_TypeDef;


 


#define GPIOA_BASE            ((u32)0x40010800)  //GPIOA的基地址为0x40010800


#define GPIOA   (GPIO_TypeDef *)  GPIOA_BASE;  //强制类型转换为GPIO_TypeDef类型的指针


 


这样在操作GPIOA的寄存器时只要这样写就可以了


读: X="GPIOA-">CRL;     写:GPIOA->CRL=X; 


  读: X=(*GPIOA).CRL;     写:(*GPIOA).CRL =X; 


 


当然,要达到上述目的也可以采用如下方式


#define GPIOA_ CRL  0x40010800


#define GPIOA_ CRH  0x40010804


#define GPIOA_ IDR  0x40010808


#define GPIOA_ ODR  0x<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />4001080C


#define GPIOA_ BSSR  0x40010810


#define GPIOA_ LCKR  0x40010814


 


很明显,第一种书写方式更加正规化,且当定义多个GPIO时,只要将其它GPIO的基地址强制转换为该结构类型的指针即可。


 


再来看看一个定义外部总线的例子


typedef struct


{


  vu8 CH375_DATA;


  vu8 CH375_CMD;  //偏移量1


} CH375_TypeDef;


 #define CH375             ((CH375_TypeDef *) 0x6c000000)


CH375-> CH375_DATA=data;  //0x6c000000地址处写数据


CH375-> CH375_CMD=cmd;  //0x6c000001地址处写命令


怎么样,是不是方便多了。重要的是代码的观赏和可读性提高了。


 


 


三、        用枚举数据类型来定义特定的状态


    在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个函数在操作过程中会返回几个特定的状态:操作成功,操作失败,忙,等等。如果我们直接在函数里用012来表示这三种状态,有时偶尔会出现数值与实际状态对不上号的情况,造成置状态和判断状态错误,那么我们可以在程序里用宏或者枚举来事先定义好这些状态。


如:用宏定义:


#define Sucess  0


#define Failure  1


#define Busy  2


 


用枚举


typedef enum { Sucess = 0, Failure , Busy } FlagStatus;


 


 


四、        用共用体类型定义共享内存空间


共用体类型定义的数据是将多个成员共享同一内存空间,该空间的大小为最大成员的大小,其用法与结构体完全相同,但值得注意的是不能同时引用多个成员,在某一时刻只能使用其中之一成员。


在程序中如果全局变量比较多,包含几个结构和数组,如果这些全部定义的话势必会占大量的内存,有可能还会导致单片机内存不够,如果能让几个不同时用到的数组和结构变量共享一段内存,则能省出很多的内存空间。


比如以下输入输出若不同时进行,则可以共享同一段内存空间


union {


              struct {


                     unsigned char   Flag;


unsigned char   Type;


                     unsigned char   State;


                     unsigned long   DataLen;                  


                     unsigned char   Buffer[64];        


              }DataOut;                                                


              struct {


                     unsigned char   Flag;


unsigned char   Type;


                     unsigned char   State;


                     unsigned long   DataLen;                  


                     unsigned char   Buffer[64]; 


              } DataIn;                                          


       } BOC;                                                    


 


C语言博大精深,丰富多彩,用得好能很好的发挥它的作用,同时学习好的编程方法养成良好的编程习惯对于一名设计人员来说也是极其的重要,以上四点都是本人自身积累和学习的一些总结,希望能够与大家一起共同交流,共同学习和提高。


 


以上若有不对或各网友觉得不妥之处还请不吝指出!


 


Liu_xf


2009-8-12

                                                                                                                Eeliu88@gmail.com
PARTNER CONTENT

文章评论8条评论)

登录后参与讨论

用户862916 2014-11-8 14:51

好的编程方法 学习了

用户862916 2014-11-8 14:50

谢谢 转载了

用户1635421 2010-7-8 13:24

不错。

用户212550 2010-3-17 22:19

学习了!

用户174417 2009-12-16 20:42

呵呵 今天在博主的那篇FT232的文章中做了广告,很不好意思,就逛了一下博主的文章!人才,我也喜欢匠人的编程风格,但是还不知道参考STM的库来提高自己!学习了,两次谢谢博主,第一次做广告,第二次学习了!

用户515414 2009-8-20 08:37

用户208176 2009-8-19 19:05

恩,受教了。

用户85187 2009-8-19 13:32

用户1370494 2009-7-2 21:54

不错,谢谢!

用户195098 2009-6-25 17:04

原文从“维库电子市场”论坛转帖过来,但相关网址总也不能通过关键字检测。转载文章,不敢掠人之美。
相关推荐阅读
用户1278632 2011-09-15 12:49
解决FPGA配置成功,但不能初始化运行的BUG
摘要:    遇到两次FPGA配置完成,却不能正常运行的问题,一次是ALTERA的A1C3,另一次是XILINX的XC3S700A。两次都是DONE信号的问题。问题虽不大,但却很折腾人,今天在这里作下...
用户1278632 2011-09-15 12:41
Code Edit的神器UltraEdit
摘要:      我不是一个专职的程序员,但经常会要写一些单片机底层的code和hdl code,起初用UE,是因为查找和批量修改很方便,而且不会额外的生成一些“垃圾”文件;现在用UE三年多了,一直都...
用户1278632 2010-12-29 09:29
ISE与EDK联合设计报错 ERROR:NgdBuild:604 logical block
做一个很简单的测试在ISE的SCH里调用EDK的symbol,EDK的功能也很简单,就是CPU通过串品打印一串字符首先,我建一个ISE工程再建一source  sch类型,并设置为顶层,取名为top....
用户1278632 2010-12-22 14:49
EDK12.2中 mch_emc IP的时序问题
mch_emc IP可以将PLB总线时序转为inter 8080时序 下面是我用chipscope抓到的波形Mem_DQ_O_In :  数据输入 Mem_OEN:      读信号  Mem_A: ...
用户1278632 2010-12-22 14:29
xilinx FPGA的配制与应用程序引导-范例
两个Xilinx FPGA应用程序引导的范例1、run_in_flash是直接在NOR FLASH里运行程序2、spi_flash_boot是将BIT和bootloader和APP全部固化到SPI F...
用户1278632 2010-12-19 13:48
EDK下sram IP的使用
EDK软件的memory and memory controller中有一个xps multi-channel external memory controller(sram/flash)的IP,用来...
我要评论
8
10
关闭 站长推荐上一条 /3 下一条