5696|9

5

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

关于工作一年和C编程心得 [复制链接]

 
首先不管建立任何新的工程都必须包含这几个文件夹
1 Code 存放你的代码文件夹
2 SCH  存放你的原理图和PCB图
3 DOC  工程说明文档
4 RESOURCE 包含在这个工程里面所使用的芯片说明,音源,或者烧录文件,外部存储档
5 RELEASE 包含最终程序烧录档,原理图,升级文件等等
并且在项目文件夹名字,必须对应的工程名字,末尾加上版本号如:GP6000_LED_V1.0
user_define.c//包含所使用的全局变量的声明,并包含所使用的系统控制变量的初始化函数Sys_VariableInit();


user_define.h//包含了整个工程中所使用的全局变量的外部声明和宏定义
在这个文件夹里面,我比较常用的有以下几个宏

#define         Glib_SetValueBit(Value,BitOffset)                (Value |= (1< #define         Glib_ClrValueBit(Value,BitOffset)                (Value &=~(1 << BitOffset))//清除某位
#define         Glib_ReadValueBit(Value,BitOffset)                (Value &(1 << BitOffset))//读取某位
#define         Glib_GetCountOf(a)          (sizeof(a) / sizeof(a[0]))//得出缓冲区元素个数
#define         Glib_GetMIN(a,b)            (((a) < (b)) ? (a) : (b))//读取最小元素
#define         Glib_GetMAX(a,b)            (((a) > (b)) ? (a) : (b))//读取最大元素
#define         Glib_GetZEROFILL(p, Size)   (memset(p, 0, Size))  //清除缓冲区
user_type.h//根据自己编写代码的风格定义数据类型
extern_fun.h//外部函数声明

第二,使用模块化思维,架构整个工程。
比如,在这个工程里面使用了GPS模块,那么在你的代码文件夹里面必须包含这两个文件Gps.c 和Gps.h


第三关于函数,变量,宏的命名规则
全局变量 g_VariableName 比如定义一个64Hz的定时器变量u32 g_Timer64HZ_Flag
        一看到这个变量就知道,这一个定时器标志位变量,而且频率为64Hz,标志的间隔为16ms
         定义一个指向串口接收缓冲区的指针如u8 *gP_Uart3RxBuffer;
         定义一个串口接收缓冲区 u8 g_Uart3RxBuffer,并定义偏移量u16 g_Uart3RxOffset;
局域变量 tw_variablename

宏定义   常数定义 #define c_ConstName                ConstValue
                                        函数操作 #define Handler_Name                {....;}
第三,关于C语言指针,数组

首先明确在使用指针两个核心点:
                                                                                                                1 所指向的地址是多少
                                                                                                                2 偏移量的基本单位
比如 要取出 一个u16 g_ADC3Value 的变量里面的,高八位,保存在变量u8 tw_calADC;里面
     看到这个,一般人的思维是这样的操作代码如下 tw_calADC = ((g_ADC3Value >> 8) && 0xFF);
     使用移位操作,用汇编的思维
     但是如果让我来写的话,
     代码如下:tw_calADC = ((u8 *)(&g_ADC3Value))[1];

                        要解释这段代码要知道下面这些东西
                        1,单片机内存的最基本的单位是byte,所以g_ADC3Value 这个变量在内存中存储的时候所占用的内存空间为2个Byte
                        2,明白了第一条,你还需要明白这样一个东西,任何变量在内存中都是有地址的,那么你就可以把g_ADC3Value 这个变量看成一个数组
                           数组名字为g_ADC3Value,元素为2,基本单位是Byte
                        明白了这两点那么,来看这段代码
                        (&g_ADC3Value) 此操作为取变量g_ADC3Value 在内存中的地址
                        (u8 *) 对上步所取得的地址以一个Byte的宽度去访问,实际上是把g_ADC3Value 分成两段
                        [1],使用数组下标的形式去提取这个数组内的元素
                        ((u8 *)(&g_ADC3Value))[1]; 整句话的意思就是,将变量g_ADC3Value所存储的东西强制转换为一个数据类型为unsigned char 数据宽度为2的数组
                        既然是数组,那么就可以使用下标的形式来对数组内存储的元素进行读写访问。
接着继续更新,关于指针,
代码入下:
         u32 gb_PlayFilesIndex;
         gb_PlayFilesIndex = 0x12345678;
         u8 temparray[4];
         for(u8 i = 0;i < 4;i++)
         {
                        temparray[i] = ((u8*)(&gb_PlayFilesIndex))[i];                 
         }
         看到这段代码,你能立即写出temparray数组里面所存的数据吗?
         如果不能,那就听我分析:
         比如运行这段代码的是一颗32位的ARM处理器,
         gb_PlayFilesIndex 它的内存地址 是0x20000000;
         谈到这里,你要明白不管它是什么处理器或者内存单元,最基本的单位是 BYTE,字节,所以如你自己写过内存操作函数的话,大概会这么写
         void MemSet(void *pBuffer,u8 data,u16 size)
         {
                         u8 *p_databuffer = *pBuffer;
                         u16 i = 0;
                         while(i < size)
                         {
                                 *(p_databuffer+(i++)) = data;
                         }
         }也许有人会说为什么不用for语句,一般的来说,从反汇编的角度来说,使用while比for的效率要高
         那么在存储gb_PlayFilesIndex,这个数据的时候,这个数据它所占据的内存单元就是有四个
         不管是什么变量,它都包含两个最基本的元素:它所代表的数据,和它所在内存中的地址,所以你在使用IDE的WATCH功能的时候,一般都有三个最基本的元素 变量名  地址 数据
         temparray[i] = (u8*)(&gb_PlayFilesIndex)[i];  看这句代码的等号后面的部分,根据C里面的运算符号的优先级,
         (u8*) 首先是这部分称之为A,是将在这个表达式后面的数据强制转换为 U8*类型的指针
         (&gb_PlayFilesIndex) 这部分称之为B,是取gb_PlayFilesIndex在内存中起始地址的地址,而这个地址就是0x20000000
         [i] 这部分称之为C,使用的是数组下标格式引用数据
         temparray[i] = (u8*)(&gb_PlayFilesIndex)[i];
         这句代码,首先是将gb_PlayFilesIndex 所在的内存地址,强制转换为 U8 类型的指针,而这个指针所指向的是内存地址是0x20000000的地址,那么这个地址内所存的数据
         就是temparray[0] = 0x78,一般是这个样子,而这中存储方式称为小端存储,如果是大端的话,那么这个0x20000000 地址内存的就是temparray[0] =0x12了,
         那么有人会想到这个,为什么要带个数组下标的方式[i]呢,
         那么可以打个比方,一个地址为0x20000000的盒子里面存放的数据是0x12345678,那么在这个0x20000000的盒子里面分为了4个最基本的小盒子
         你要明白不管是它是怎么存储数据的,但是必须尊崇的规则那就是所存储的数据方式都必须是连续存储的,存储单位是以BYTE为方式,如果不是连续的存储的话,那么C里面为什么还要加入指针的概念呢
         说到连续存储,那么你就会想到大学里面C语言老师在跟你讲数组的时候,数组在内存中的本质是什么,好好想想,,,
         想明白了,那么就会明白这段代码,所做的处理是什么,(u8*)(&gb_PlayFilesIndex)[i] 首先是将变量gb_PlayFilesIndex看成一个数据宽度是4,存储单位为一个BYTE的数组,而这个数组的名字就是gb_PlayFilesIndex
         当你把这个变量看成数组的时候,那么想访问数组里面的元素,就可以通过下标的方式来访问,而访问的单位为一个BYTE
         为什么弄这个代码呢,因为我们在写程序和处理数据的时候,很多时候要把数据拆开来,一般的方式就是通过移位来得到,如果用这种方法写的话,那么不需要什么移位操作,直接提取数据,就可以了,,这样做相对来说是做些小小的优化
         
         故以小端数据对齐方式的:
         0x20000000   temparray[0]=  0x78;
         0x20000001   temparray[1]=  0x56;
         0x20000002   temparray[2]=  0x34;
         0x20000003   temparray[3]=  0x12;
         大端的反推过来就是了,
         那么你再看看这段代码:
         typedef struct
         {
                         u8 num1;
                         u8 num2;
         }TEST_P;
         TEST_P test;
         test.num1 = 1;
         test.num2 = 2;
         printf("%d,%d",test.num1,test.num2);
         (TEST_P*)(&test)[1] = 7;
         printf("%d",test.num2);
         (TEST_P*)(&test)[1])[1] = 9;
         printf("%d,%d",test.num1,(TEST_P*)(&test)[1])[1]);
         你能回答出第二个打印函数 打印的数据是多少吗?
         第三个打印出来又是什么,
         当你把这部分解答出来了,那么你就可以想想结构体在内存中的本质是什么,,如果你想对结构体部分多了解些的话,你可以去看看STM32 USB库里面的架构,
         相信会给你带来很多灵感,

最新回复

但是很显然的一个问题就是 你后面也提到了,这种写法在处理大小端的问题上,是没法自动完成的。 当然,这个问题,移位会不会碰到麻烦,我没有就 大小端 问题测试过,也不好说,但感觉是 应该也回避不了。 一个很明显的地方就是 首先,论效率,移位至少不会比这个慢; 其次,为什么要强转呢? 华为编程规范里提到,如无必要别强转,这个其实是很有道理的,强转总是有风险的,也许是跟 编译器 的行为有关的。  详情 回复 发表于 2015-2-9 17:46

赞赏

1

查看全部赞赏

点赞 关注(1)

回复
举报

5

帖子

0

TA的资源

一粒金砂(中级)

沙发
 
111

33EW}L@74(~JFUG_G}JH}NH.jpg (28.68 KB, 下载次数: 0)

33EW}L@74(~JFUG_G}JH}NH.jpg
 
 

回复

1461

帖子

1

TA的资源

纯净的硅(中级)

板凳
 
很有用的东西,
 
 
 

回复

224

帖子

3

TA的资源

一粒金砂(中级)

4
 
呵呵,经验积累啊
 
 
 

回复

1658

帖子

1

TA的资源

纯净的硅(高级)

5
 
学习一下
 
 
 

回复

51

帖子

0

TA的资源

一粒金砂(中级)

6
 
不错,条理性很强
 
个人签名山中方七日,世上已千年。
 
 

回复

3

帖子

0

TA的资源

一粒金砂(初级)

7
 
经验之谈,,学习了,,留个标签
 
 
 

回复

1412

帖子

15

TA的资源

版主

8
 
  typedef struct
         {
                         u8 num1;
                         u8 num2;
         }TEST_P;
         TEST_P test;
         test.num1 = 1;
         test.num2 = 2;
         printf("%d,%d",test.num1,test.num2);
         (TEST_P*)(&test)[1] = 7;
         printf("%d",test.num2);
         (TEST_P*)(&test)[1])[1] = 9;
         printf("%d,%d",test.num1,(TEST_P*)(&test)[1])[1]);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
楼主这段代码能够运行?
(TEST_P*)(&test)[1] = 7;   这个的话按道理[]的优先级更高,也就是把7赋值给一个TEST_P的指针?这个会类型不对吧。我想楼主是不是这个意思啊:

        ((char *)(&test))[1] = 7;
 
个人签名https://bbs.eeworld.com.cn/thread-471646-1-1.html
欢迎加入我的团队
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

9
 
最后提到的这种按地址读,其实不是太清楚优势何在?
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

10
 
但是很显然的一个问题就是
你后面也提到了,这种写法在处理大小端的问题上,是没法自动完成的。
当然,这个问题,移位会不会碰到麻烦,我没有就 大小端 问题测试过,也不好说,但感觉是 应该也回避不了。

一个很明显的地方就是
首先,论效率,移位至少不会比这个慢;
其次,为什么要强转呢?
华为编程规范里提到,如无必要别强转,这个其实是很有道理的,强转总是有风险的,也许是跟 编译器 的行为有关的。
 
个人签名

强者为尊,弱者,死无葬身之地

 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表