【一天一课】从单片机到嵌入式,需要学点啥?(上篇)

今天一起讨论下,从单片机到嵌入式之路该学点啥,希望对你有所启发。


Part 1 简单的讨论一下C语言知识


一、C语言的好处

  1.可移植性强;

  2.开发速度快,效率高;

  3.结构清晰

  4....


二、C语言的位操作

  在单片机中C语言的位操作用的比较多,而且用的好对代码的执行效率都是很有帮助的,下面简单介绍一下在单片机中的应用,就算是抛砖引玉吧。

  定义一个8位的寄存器(0xF0是寄存器的地址):

  #define REG 0xDE

  1)对单个的位进行赋值

  (1)将寄存器REG的第2位置“1”

  REG |= (1 << 2);

  (2)将寄存器REG的第2位清零

  REG &= ~(1 << 2);

  (3)将寄存器REG的第2和第4位置“1”

  REG |= (1 << 4) | (1 << 2);

  (4)将寄存器REG的第3和5位清零

  REG &= ~( (1 << 5) | (1 << 3) );

  所以书上就出了一些口诀:

  位置1使用位运算"|"

  位置0使用位运算"&"

三、C语言的结构体在单片机中的应用

  结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构。但是有时候结构体会涉及一些比较麻烦的事情:结构体的大小与内存对齐。这个暂时不解释。下面主要讲解一下如何应用。

  比如在时钟程序中,经常就会用到结构体,结构体用的好,事半功倍。

  //定义一个时间结构体

  typedef struct

  {

  u16 Year;

  u8 Month;

  u8 Day;

  u8 Week;

  u8 Hour;

  u8 Min;

  u8 Sec;

  }Dtime;

  像上面这种太普通了,下面来个难点的。

  //时间结构体

  Dtime SetTM;

  typedef struct{

  short Min; //最小值

  short Max; //最大值

  short Tv; //变量

  }Limi; 这个结构体就可以通过按键来控制,按键加减可以调节时间。

  其实C语言*,这仅仅只是一个方面,像指针、函数、联合体等等都是重点,链表等等都是经典。


Part 2  单片机

一、单片机的广泛性

几乎很多从事控制代码编写的工程师,第一次接触的控制类项目都是单片机,不论是在学校还是工作了。有了单片机基础,学习其他的单片机和嵌入式都相对要容易些。

二、单片机的主要内容

1.先认识单片机,了解单片机的主要组成和各个管脚的定义及功能;

2.了解一些单片机汇编知识,方便后续调试;

3.掌握单片机中断机制,这个后续还会讲到;

4.掌握单片机定时和计数器;

5.掌握单片机的串口通信。

以上是对想从事单片机学习的人必须掌握的基本内容。

三、代码的规范性

代码书写一定要规范,这不仅对个人,对后人也是很有帮助。变量的取名一定要注意,最好见名知意,下面是个人的函数编写规范,仅供参考

/***************************************

  * 函数描述:关闭LED灯函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

****************************************/

  void LED_Close(void)

  {

  GPIO_SetBits(LED1_PORT, LED1_PIN );

  }

  文件名的格式:

  /*

  * Copyright (c) 2014, 鑫亮电子

  * All rights reserved.

  *

  * 文件名称:主函数

  * 摘 要:LED灯闪烁

  * 硬件平台:STM32F030探索套件

  * 当前版本:V0.0

  * 作 者:@量子CPU 

  * 完成日期:2013-12-23

  * 修改记录:

  */

以上都是仅供参考。格式可以不同,但是请考虑后续的维护和后人阅读你代码的痛苦性就可以了。

Part 3  苦逼的CPU

【事故起因】

单片机是单核的,所以在做多线程问题的时候,我们要考虑的太多。但是大部分人都会让单片机一直工作,比如while死循环,然后就抱怨单片机太简单了,只能点灯,其它的事情就不行了。

【现场分析】:

1.单片机是单核的;

2.做多线程,方法两种:a.上操作系统,b.仿操作系统;

3.自己想释放单片机的CPU,但是格局有限,就怪单片机;

4.要想马儿跑,必须给马儿吃草。要想单片机多工作,必须优化代码,多下功夫。


【案例列举】:单灯闪烁

  void main(void)

  {

  LED_Init(); //LED初始化

  while(1)

  {

  LED = ON; //LED亮

  Delay_Ms(1000); //延时1s

  LED = OFF; //LED灭

  Delay_Ms(1000); //延时1s

  }

  }


【案例分析】:单灯闪烁分析

声明几点:

1.如果你只用单片机作为单灯闪烁,那么单片机就完全可以;

2.如果你不想玩那么高级,那么单机这样也是OK的;

3.如果你想玩高级的,那么肯定不行。

分析:咋一看,程序没有什么问题,但是暗藏玄机。

 

1.程序被死循环卡死;

2.死循环里面就是一个产生2s周期的频率,那么灯就是2s闪烁一次;

3.单片机就这样被你“征服”了。

但是你这是把单片机浪费了。

这是单片机CPU的苦啊,他是没有口啊,有口要骂人的哦!


单片机CPU的苦衷——请释放CPU


1.硬件电路分析

用上面的电路来做分析,当P1口为低电平的时候,LED灯才亮,高电平则灭。


2.软件分析

由硬件可以知道,我们可以通过宏定义来对接口进行简单的定义

#define LED_Light P1 //LED灯端口

#define LED_ON() LED_Light = 0x00 //LED灯亮

#define LED_OFF() LED_Light = 0xFF //LED灯灭

现在端口也定义好了,下面单片机要哭诉了。


3.你误解了单片机

单片机其实可以干很多活,结果你误解了。

为了让单片机不白忙活我们可以通过两种方式来实现LED灯闪烁:a.定时器中断,b.计时+标志位。

A.定时器中断

定时器中断大家都知道,这里就不说了,就是产生xms的中断就可以了。

B.计时+标志位

计时+标志位太有用了,这一下帮单片机洗清沉冤了。我们只要产生一个对200ms的时间,然后对该时间进行计数到了5次,然后就清0该计数值,同时反转LED灯的状态即可。


代码部分:

  unsigned int LedTimeCount = 0 ; //LED计数器

  unsigned char LedStatus = 0 ; //LED状态标志, 0表示亮,1表示熄灭

  /***************************************

  * 函数描述:LED灯线程处理函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:通过标志位来实现LED的反转

  * 修改记录:

  ****************************************/

  void LEDThread_Process(void)

  {

  if(0 == LedStatus) //如果LED的状态为0,则点亮LED

  {

  LED_ON() ; //点亮LED灯

  }

  else //否则熄灭LED

  {

  LED_OFF() ;

  }

  }

  /***************************************

  * 函数描述:计时和标志位函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:状态标志位改变

  * 修改记录:

  ****************************************/

  void LEDStatus_Change(void)

  {

  if(Sys_200MS) //系统200ms时标到

  {

  Sys_200MS = 0 ;

  LedTimeCount ++ ; //LED计数器加1

  if(LedTimeCount >= 5) //计数达到5,即1s到了,改变LED的状态。

  {

  LedTimeCount = 0 ;

  LedStatus = ! LedStatus;

  }

  }

  }

  /***************************************

  * 函数描述:主函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

  ****************************************/

  void main(void)

  {

  while(1)

  {

  LEDThread_Process() ;

  LEDStatus_Change() ;

  }

  }

通过上面的程序就可以释放单片机的CPU。因为LED灯亮灭是有标志位(LedStatus)来决定,而标志位由计数器(LedTimeCount)来决定,两个函数都没有绑架单片机的CPU,所以单片机的CPU是自由的。终于洗冤了。


Part4 你的按键还活着么?

本节我们将学习让你恼火的按键,主要是从下面4个方面进行讲解:

  

1.按键触发方式迫在眉睫

2.按键扫描

3.中断扫描

4.按键状态机制

一、按键触发方迫在眉睫

如果你在公司上班,抑或你在学校,当你做的项目任务进度比较多的时候,你还在使用按键扫描,也许老板会说你SB,老师说你无能。这就是现实,单片机和ST序列的目前还是单核的,怎么可能一直在做扫描,你大才小用了吧。你把精华浪费在做无聊的事情上面,现在你评价一下你自己吧,我不多说。

亲,你的按键还活着么?你的按键会苦恼你么?如果你还在考虑按键用扫描方式,那可能有一天要被人骂了,如果想要面子,又让你的按键有活力。Follow me !!!

你不会按键的三种方式,你妈知道么?

下面以单个独立按键来讲解,矩阵按键可以思想是一样的。

二、按键扫描方式

按键扫描的方式,这应该是大家比较熟悉的,因为大部分单片机入门的时候,碰到按键的时候,都是通过扫描方式来实现。

按键扫描的原理:CPU需要不停的工作,来判断IO口是否被拉低或者置高,效率比较低。按键扫描主要是处理消抖。

  STM32为例讲解

  /***************************************

  * 函数描述:按键初始化函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:初始化按键

  * 修改记录:

  ****************************************/

  void KEY_Init(void)

  {

  GPIO_InitTypeDef GPIO_InitStruct;

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; //按键PA0

  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //输入模式

  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2;

  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉输入

  GPIO_Init(GPIOA, &GPIO_InitStruct);

  }

  /***************************************

  * 函数描述:按键扫描函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:扫描按键

  * 修改记录:

  ****************************************/

  uint8_t KEY_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)

  {

  if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 0 ) //检测是否有按键按下

  {

  Delay(10000); //延时消抖

  if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 0 )

  {

  while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 0); //等待按键释放 */

  return 0 ;

  }

  else

  return 1;

  }

  else

  eturn 1;

  }

  /***************************************

  * 函数描述:部分主函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

  ****************************************/

  while(1)

  {

至于电路分析这个其实也是分开看,基本的知识是要知道,这些都已经算单片机的基础知识了,在往深了就算是单纯的电路分析了,这就又是硬件那块了,也是不着急,做硬件没有牛人带着真是不好做(我之前实验室老师就是硬件大牛)

  if( KEY_Scan (GPIOA,GPIO_Pin_0) ==0)//判定按键是否按下

  {

  GPIO_WriteBit(GPIOC, GPIO_Pin_9,

  (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_9))));//反转led2灯

  }

  }

  这就是按键扫描,至于缺点不言而喻。这不就是苦逼的CPU的么?

  三、中断扫描

  在你对单片机比较熟悉的时候,在你考虑多任务的时候,你应该被按键扫描烦死了,这时候,你绞尽脑汁,终于想到了—————————— 中断扫描。

  中断扫描的原理:中断控制效率很高,一旦系统IO口出现上升或者下降沿电平就会触发执行中断内的程序。这样MCU就无需一直在扫描,这样你就可以干其他的时候,而且不会觉得按键不灵活。

  同样以STM32为例讲解:

  说明:STM32用IO口外部中断的一般步骤:

  1.初始化IO口为输入;

  2.开启IO口时钟,设置 IO 口与中断线的映射关系;

  3.初始化线上中断,设置触发条件等;

  4.配置中断分组(NVIC),并使能中断;

  5.编写中断服务函数。

  /***************************************

  * 函数描述:按键初始化函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:按键初始化

  * 修改记录:

  ****************************************/

  void KEY_Init(void)

  {

  GPIO_InitTypeDef GPIO_InitStruct;

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; //按键PA0

  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //输入模式

  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2;

  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉输入

  GPIO_Init(GPIOA, &GPIO_InitStruct);

  }

  /***************************************

  * 函数描述:外部中断初始化

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

  ****************************************/

  void EXTI_KEY_Init(void)

  {

  EXTI_InitTypeDef EXTI_InitStruct;

  NVIC_InitTypeDef NVIC_InitStruct;

  KEY_Init();

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能系统时钟配置

  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

  //连接EXTI0给GPIOA0

  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

  //配置GPIO与中断线的映射关系

  EXTI_InitStruct.EXTI_Line = EXTI_Line0; //中断线标号0

  EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; //外部中断模式

  EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断

  EXTI_InitStruct.EXTI_LineCmd = ENABLE; //中断线使能

  EXTI_Init(&EXTI_InitStruct);

  NVIC_InitStruct.NVIC_IRQChannel = EXTI0_1_IRQn;

  NVIC_InitStruct.NVIC_IRQChannelPriority = 0x00;

  NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStruct);

  }

  /***************************************

  * 函数描述:外部中断0服务程序

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

  ****************************************/

  void EXTI0_1_IRQHandler(void)

  {

  if(EXTI_GetITStatus(EXTI_Line0) != RESET)

  //判断线0上的中断是否发生,可以理解为标志位

  {

  /* Toggle LED1and LED2 */

  GPIO_WriteBit(GPIOC, GPIO_Pin_8,

  (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_8))));

  GPIO_WriteBit(GPIOC, GPIO_Pin_9,

  (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_9))));

  /* Clear the EXTI line 0 pending bit */

  EXTI_ClearITPendingBit(EXTI_Line0);//清除LINE0上的中断标志位

  }

  }

  /***************************************

  * 函数描述:主函数

  * 输入参数:No

  * 返 回 值:No

  * 说 明:

  * 修改记录:

  ****************************************/

  int main(void)

  {

  SystemInit(); //系统初始化

  LED_Init(); //LED灯初始化

  GPIO_ResetBits(GPIOC,GPIO_Pin_8);

  KEY_Init(); //按键初始化

  EXTI_KEY_Init(); //外部中断初始化

  while(1)

  {

  }

  }

  按键中断的有点是不是不言而喻!!!这不就拯救了苦逼的CPU么?

  四、按键状态机制

  网上看见一片讲状态机制很经典的文章。借用并转之!!!

  首先按键程序进入初始状态S1,在这个状态下,检测按键是否按下,如果有按下,则进入按键消抖状态2,在下一次执行按键程序时候,直接由按键消抖状态进入按键按下状态3,在此状态下检测按键是否按下,如果没有按键按下,则返回初始状态S1,如果有则可以返回键值,同时进入长按状态S4,在长按状态下每次进入按键程序时候对按键时间计数,当计数值超过设定阈值时候,则表明长按事件发生,同时进入按键连_发状态S5。如果按键键值为空键,则返回按键释放状态S6,否则继续停留在本状态。在按键连_发状态下,如果按键键值为空键则返回按键释放状态S6,如果按键时间计数超过连_发阈值,则返回连_发按键值,清零时间计数后继续停留在本状态。

看了这么多,也许你已经有一个模糊的概念了,下面让我们趁热打铁,一起来动手编写按键驱动程序吧。

下面是我使用的硬件的连接图。

硬件连接很简单,四个独立按键分别接在P3^0------P3^3四个I/O上面。

因为51单片机I/O口内部结构的限制,在读取外部引脚状态的时候,需要向端口写1.在51单片机复位后,不需进行此操作也可以进行读取外部引脚的操作。因此,在按键的端口没有复用的情况下,可以省略此步骤。而对于其它一些真正双向I/O口的单片机来说,将引脚设置成输入状态,是必不可少的一个步骤。

下面的程序代码初始化引脚为输入。

  void KeyInit(void)

  {

  io_key_1 = 1 ;

  io_key_2 = 1 ;

  io_key_3 = 1 ;

  io_key_4 = 1 ;

  }

  根据按键硬件连接定义按键键值

  #define KEY_VALUE_1 0x0e

  #define KEY_VALUE_2 0x0d

  #define KEY_VALUE_3 0x0b

  #define KEY_VALUE_4 0x07

  #define KEY_NULL 0x0f

  下面我们来编写按键的硬件驱动程序。

  根据第一章所描述的按键检测原理,我们可以很容易的得出如下的代码:

  static uint8 KeyScan(void)

  {

  if(io_key_1 == 0)return KEY_VALUE_1 ;

  if(io_key_2 == 0)return KEY_VALUE_2 ;

  if(io_key_3 == 0)return KEY_VALUE_3 ;

  if(io_key_4 == 0)return KEY_VALUE_4 ;

  return KEY_NULL ;

  }

  其中io_key_1等是我们按键端口的定义,如下所示:

  sbit io_key_1 = P3^0 ;

  sbit io_key_2 = P3^1 ;

  sbit io_key_3 = P3^2 ;

  sbit io_key_4 = P3^3 ;

  KeyScan()作为底层按键的驱动程序,为上层按键扫描提供一个接口,这样我们编写的上层按键扫描函数可以几乎不用修改就可以拿到我们的其它程序中去使用,使得程序复用性大大提高。同时,通过有意识的将与底层硬件连接紧密的程序和与硬件无关的代码分开写,使得程序结构层次清晰,可移植性也更好。对于单片机类的程序而言,能够做到函数级别的代码重用已经足够了。

  在编写我们的上层按键扫描函数之前,需要先完成一些宏定义。

  //定义长按键的TICK数,以及连_发间隔的TICK数

  #define KEY_LONG_PERIOD 100

  #define KEY_CONTINUE_PERIOD 25

  //定义按键返回值状态(按下,长按,连_发,释放)

  #define KEY_DOWN 0x80

  #define KEY_LONG 0x40

  #define KEY_CONTINUE 0x20

  #define KEY_UP 0x10

  //定义按键状态

  #define KEY_STATE_INIT 0

  #define KEY_STATE_WOBBLE 1

  #define KEY_STATE_PRESS 2

  #define KEY_STATE_LONG 3

  #define KEY_STATE_CONTINUE 4

  #define KEY_STATE_RELEASE 5

接着我们开始编写完整的上层按键扫描函数,按键的短按,长按,连按,释放等等状态的判断均是在此函数中完成。对照状态流程转移图,然后再看下面的函数代码,可以更容易的去理解函数的执行流程。

Part5 你的代码别人有耐心看么?

本节我们将从代码格式来审视你的代码,主要是从下面3个方面进行讲解:

1.代码格式的重要性

2.规范自己的代码格式

3.函数设计和变量定义格式

一、代码格式的重要性:

  好的代码格式犹如男工程师看见美女,女工程师看见帅哥一样,有种一见钟情的感觉。但是事实上很多人不注重代码的规范性,结果写出来的代码,没有连自己都不想看第二遍,这是工程师的大忌。

  不论是硬件工程师还是软件工程师,良好的代码格式是基本功。如果你写的代码别人就看了几行就感觉恶心、呕吐,你说你的代码别人还有耐心全部看完么?

  以前看见一个研究生,以为自己多牛逼,写的代码没有一行注释了。变量取名也是莫名其妙的,结果有一个bug。导致他检查了半个月。何必这样害自己呢?不要以为你写的代码多高级,不要加注释,不注重格式,怕别人复制。别人想复制你的代码,说明你的代码有价值。

  好了不多少了,直接进入下一个小点吧。O(∩_∩)O~


  二、规范自己的代码格式

  1.文件结构

  a) 版权和版本的声明

  这个是每个公司或者个人为了保护自己的代码而写的。格式也不是固定了,主要根据个人的爱好来写的。下面贴出本人喜欢的格式。

  /*

  * Copyright (c) 2014, 鑫亮电子

  * All rights reserved.

  * 文件名称:usart.c

  * 摘 要:串口驱动函数

  * 硬件平台:STM32F030探索套件

  * 当前版本:V0.0

  * 完成日期:2013-01-09

  * 修改记录:

  */

  很简单,仅仅作为参考吧。

  b) 头文件的结构

  头文件由三部分内容组成:

  (1)头文件开头处的版权和版本声明。

  (2)预处理块。

  (3)函数和类结构声明等。

  下面主要看头文件的类型,以个人平时的程序为例:

  /* Includes ----------------------------*/

  #ifndef __EXTI_H //防止重定义

  #define __EXTI_H

  //个人爱好

  #include "STM32F0xx.h" //包含的头文件

  void EXTI_KEY_Init(void); //外部中断初始化

  //个人爱好

  #endif

  c) 定义文件的结构

  定义文件有三部分内容:

  (1) 定义文件开头处的版权和版本声明。

  (2) 对一些头文件的引用。

  (3) 程序的实现体(包括数据和代码)。

  下面以常见的例子为例:

  // 全局函数的实现体

  void Function1(…)

  {

  …

  }

  2.程序的板式

  为了更好的讲解程序的版式,下面以一个例子为例来讲解:

  /***************************************

  * 函数描述:外部中断0服务程序

  * 输入参数:No

  * 返 回 值:No

  * 说 明:无

  * 修改记录:无

  ****************************************/

  void EXTI0_1_IRQHandler(void)

  {

  if(EXTI_GetITStatus(EXTI_Line0) != RESET)//判断线0上的中断是否发生,可以理解为标志位

  {

  /* Toggle LED3 and LED4 */

  GPIO_WriteBit(GPIOC,GPIO_Pin_8,

  (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_8))));

  GPIO_WriteBit(GPIOC, GPIO_Pin_9,

  (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_9))));

  /* Clear the EXTI line 0 pending bit */

  EXTI_ClearITPendingBit(EXTI_Line0);//清除LINE0上的中断标志位

  }

  }

  A.注释方式有有两种【//】 和 【/* */】.

  B.对齐方式要注意。

  C.函数的设计也很重要。

  D.变量的定义也很关键。

  E.拆行也很重要,关键在其可读性。

  F.必要的注释可以让读者理解的更快,对自己以后维护更便捷。

  三、变量的定义和函数的设计

  1.变量的定义

  a) 见名知意

  int width; //定义宽度

  #define MAX 100; /* 最大值为100*/

  等等,上面也是两种注释方式。

  游客,如果您要查看本帖隐藏内容请回复

  2.函数的设计

  函数的设计其实很关键,对读者相当重要。一个好的函数设计可以让读者看出设计者别出心裁。下面简单举几个例子来说明:

  void Object::SetValue(int width, int height)

  {

  m_width = width;

  m_height = height;

  }

  下面再讲述一下断言的使用:

  程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用。

  断言assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。下面例子是一个内存复制函数。在运行过程中,如果assert的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。

  void *memcpy(void *pvTo, const void *pvFrom, size_t size)

  {

  assert((pvTo != NULL) && (pvFrom != NULL)); // 使用断言

  byte *pbTo = (byte *) pvTo; // 防止改变pvTo的地址

  byte *pbFrom = (byte *) pvFrom; // 防止改变pvFrom的地址

  while(size -- > 0 )

  *pbTo ++ = *pbFrom ++ ;

  return pvTo;

  }

  assert不是一个仓促拼凑起来的宏。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。所以 assert不是函数,而是宏。程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段。如果程序在assert处终止了,并不 是说含有该assert的函数有错误,而是调用者出了差错,assert可以帮助我们找到发生错误的原因。

  很少有比跟踪到程序的断言,却不知道该断言的作用更让人沮丧的事了。你化了很多时间,不是为了排除错误,而只是为了弄清楚这个错误到底是什么。 有的时候,程序员偶尔还会设计出有错误的断言。所以如果搞不清楚断言检查的是什么,就很难判断错误是出现在程序中,还是出现在断言中。幸运的是这个问题很 好解决,只要加上清晰的注释即可。这本是显而易见的事情,可是很少有程序员这样做。这好比一个人在森林里,看到树上钉着一块“危险”的大牌子。但危险到底 是什么?树要倒?有废井?有野兽?除非告诉人们“危险”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。

  【注意】:

  A.使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。

  B.在函数的入口处,使用断言检查参数的有效性(合法性)。

  C.在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。

  D.一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。

  以上内容参考了林锐博士的【C/C++ 代码规范参考】一书。

  对于代码对齐 和 一些其它的内容,我希望大家可以多阅读别人的代码,多练习,只有多练习了才能掌握于心,才能运用自如。

  以上的内容大家可以作为参考,就作为抛砖引玉吧,希望大家能提出更好的方法,共同提高才是王道!!!

明天将继续发布单片机到嵌入式之路下篇。

文/量子CPU

来源:网络整理

> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

会使用仿真软件特别重要,把硬件电路设计好之后,可以先在仿真软件上跑起来看看。至于你说的单片机技术更重要了,单片机主要用来产生控制信号,处理数据,通信等功能。单片机是最基础的微处理器,学会了单片机技术之后,之后学习DSP和CPLD,FPGA就容易上手了。

生成海报
点赞 0

thePro

我还没有学会写个人说明!

相关推荐

基于STM32的指纹密码锁

设计简介: 本设计是基于单片机的指纹密码锁,主要实现以下功能: 矩阵按键输入密码,并通过按键显示*号可通过按键或手机开门密码可通过按键进行开门可通过蓝牙模块连接手机进行开门可通过指纹进

STM32串口发送接收数据

1.串口通信 我用的32是stm32f10x最小系统没有UART4和UART5 USART : 通用同步异步收发器 UART : 通用异步收发器 nRTS : 请求发送 nCTS : 请求接收 区别:USART指单片机的一个IO端

TCRT5000循迹模块原理及应用

前言 本文将讲述TCRT5000循迹模块的原理及应用。本文应用于STM32,对于使用循迹模块的你有一定的帮助。 以下是本篇文章的正文内容 一、TCRT5000循迹模块介绍 TCRT5000就是一个红外发射和接收器&#xff0