您可以使用预处理指令来创建条件编译,也就是说,您可以使用这些指令来告诉编译器根据编译时的条件执行或忽略代码块。
#ifdef、郭敬和恩迪夫说明书
让我们举一个例子来看看这些说明:
#ifdef你好/*如果使用#定义如果定义了符号hi,请执行以下语句*/
#包括<;斯特迪奥。h>;
#定义STR";你好世界";
#else/*如果符号hi没有用#define定义,请执行以下语句*/
#包括";mychar。h";
#定义STR";你好,中国";
#恩迪夫
AndyLau指令说明:如果预处理器定义了以下标识符,则执行#else或#endif指令之前的所有指令,并编译所有C代码。如果没有定义且有#elif指令,则执行#else和#endif指令之间的代码。
#ifdef和#else与C和ifelse相似。它们之间的主要区别在于,预处理器不识别用于标记块的花括号{},因此它使用#else(如果必要)和#endif(必须存在)来标记指令块。
#Efndif指令
#Ifndef指令在用法上类似于AndyLau指令,也可以与#else和#endif一起使用,但其逻辑与AndyLau指令相反。
#如果和#elif
#if指令非常相似C语言Ifin#If后面跟一个整型常量表达式。如果表达式非零,则表达式为真。您可以在指令中使用C的关系运算符和逻辑运算符:
#如果MAX==1
printf("1");
#恩迪夫
你可以用if-else的形式使用#if#elif:
#如果MAX==1
printf(";1";);
#elifMAX==2
printf(";2";);
#恩迪夫
条件编译的另一个用途是使程序更易于移植。更改文件开头的几个键定义可以根据不同的系统设置不同的值并包含不同的文件。
指针用法什么是指针?从根本上说,指针是内存的值住址变量正如char类型变量的值是字符一样,int类型变量的值是整数,指针变量的值是地址。
由于计算机或嵌入式设备的硬件指令在很大程度上依赖于地址,指针以某种程度上更接近机器的方式表达程序员想要表达的指令。因此,使用指针的程序效率更高。特别是,指针可以有效地处理数组,而数组表示实际上是以伪装的形式使用指针。例如,数组名是数组第一个元素的地址。
要创建指针变量,首先声明指针变量的类型。如果要将PTR声明为存储int类型变量地址的指针,则需要使用间接运算符*来声明它。
假设已知PTR指向bah,则表示如下:
ptr=&bah;
然后使用间接运算符*查找存储在bah中的值:value=*ptr;此运算符有时被称为解引用运算符。语句PTR=&bah;值=*ptr;把它们放在一起的效果相当于:value=bah;
那么如何声明指针变量呢?是这样吗?
指针ptr;//不能像这样声明指针变量
为什么不声明这样的指针变量呢?因为在声明指针变量时必须指定指针指向的变量的类型。不同的变量类型占用不同的存储空间。一些指针操作需要知道操作对象的大小。此外,程序必须知道存储在指定地址的数据类型。例如:
整数*圆周率;//PI是指向int类型变量的指针
char*str;//STR是指向char类型变量的指针
浮动*pf,*pg;//PF和PG是只希望浮点型变量的指针
类型说明符表示指针指向的对象的类型,取消引用符号*表示声明的变量是指针。Int*PI声明意味着PI是指针,*PI是Int类型,如图5.3.4所示。
这只是指针的简单用法。指针的实际世界是不断变化和丰富多彩的。即使是那些在C语言开发方面有多年经验的人,有时也会在使用指针时出错,继任者应该更加谨慎。稍后,将介绍指针的常见应用和注意事项。
指针和数组
如前所述,您可以使用address操作符&获取变量的地址,而在数组中,您还可以使用address操作符获取数组成员中任何成员的地址,例如
整数周[7]={1,2,3,4,5,6,7};
int*pw;
pw=&周[2];
printf(";周为:%d";,*PW);
输出为:第三周。有关本规范的解释,请参考上图5.3.3。
指针和函数
在函数中使用指针的最简单方法是将其用作函数形参,例如:
智力总和(国际*pdata)
{
inti=0;
inttemp=0;
对于(i=0;i<;10;i++){
温度=温度+(*pdata);
pdata++;}
返回温度;
}
在这个例子中有几点值得解释。第一个点指针pdata作为形参的函数存在,指向存储int类型变量的地址;第二点是指pdata++;语句执行后,pdata只想增加地址,而不是1,而是inttype的大小。pdata添加的初始值为0,int-type占2字节,然后是pdata++;语句执行后,pdata的值由1变为2,*pdata的值是地址2的值,而不是地址1的值;第三点:这个函数有一个危险,就是从pdata最初指向的地址开始,函数实现了10个int变量的总和。如果我们这样使用它:
intdata[5]={1,2,3,-1,-2};
intx=总和(数据);
可以看到,数组数据的数组名,即数组的第一个地址,作为参数输入到函数sum中,数组的大小只有5个整数,但函数sum计算10个数字的和。因此,会发生地址溢出,无法获得正确的结果,甚至程序也会跑掉。为了避免这个问题,通常的解决方案是添加一个数量,形参:
整数和(整数*pdata,整数长度)
{
inti=0;
inttemp=0;
对于(i=0;i<;长度;i++){
温度=温度+(*pdata);
pdata++;}
返回温度;
}x=总和(数据,5);
或者给出指针范围:
整数和(整数*pStart,整数*pEnd)
{
inti=0;
inttemp=0;
intlength=(pEnd-pStart)/2;//假设一个int占2字节
对于(i=0;i<;长度;i++){
温度=温度+(*pdata);
pdata++;}
返回温度;
}x=总和(数据和数据[4]);
指针和函数之间的关系除了作为函数的形参之外,还有另一个重要的应用函数指针,例如,typedef用法部分中的示例:
typedefvoid(*P功能)(void);
在本例中,首先,*表示pfunction是指针变量。其次,前面的void表示该指针变量返回void类型的值。最后,括号中的void表示此函数指针的形参属于void类型。如何使用函数指针调用函数?
以下面的例子为例:
intmax(inta,intb)
{
退货((a>;b)?a:b);
}
内大街(void){
int(*pfun)(int,int);
inta=-1,b=2,c=0;
pfun=最大值;
c=pfun(a,b);
printf(";最大值:%d";c);
返回0;}
输出结果为:2。
指针和硬件地址
指针和硬件地址之间的联系在“易失性使用”一章的例子中令人震惊。没有详细介绍。下文将对此进行详细描述。例如,在stm32f103zet6中,内部SRAM的基址是0x20000000。如果我们想将数据写入这个空间的前256字节,我们可以用指针指向基址并开始写入:
易失性无符号字符*pData=(易失性无符号字符*)(0x20000000);
内大街(void){
inti=0;
对于(i=0;i<;256;i++){
pData[i]=i+10;}
返回0;}
除了内存地址,它还可以指向硬件外围设备的寄存器地址。操作模式与上述示例类似。
指针应用的基本原则:
- 首先,必须指定指针的类型;
- 如果是普通指针变量、非函数JetLi或函数指针,则必须指定指针变量的地址,以避免成为“野生指针”;
在C语言中,回调函数是函数指针的高级应用。所谓回调函数的一个简单介绍是作为参数传递的函数。从字面上看,回调函数的意思是:一个回调函数。如何理解这个句子?从逻辑上讲,要“返回”,必须有一个已知的目的地,然后在某个时间访问它;那么回调函数就是存在一个已知的函数体a。将该函数体a的地址通知另一个函数B,即函数名“a”(函数名是该函数体的函数指针,指向该函数的地址)。当函数B执行到某一步时,它将执行函数a。
回调函数有很多应用程序,因为后续程序是在STM32的Hal库下编写的,所以这里我们只看Hal库中的回调函数。
我们只看GPIO的Hal库函数。文件名为“stm32f1x_hal_gpio.C”。让我们通过反向分析来看看这个回调函数。
首先,GPIO的回调函数声明:
__疲软的void哈尔GPIO退出(uint16GPIO退出)
你可以看到函数名是Hal_ujiabaoyu_ujiexti_jicallback,形参是JiaBaoyu_ji;Pin表示Pin码(px0~px15,x=a,B,C,D,e,F,g)。从该函数的名称开始,可以大致清楚地看出,这是引脚外部中断(exti)的回调函数。然后你可以看到前面有一个“_-weak”,它是虚函数的修饰符。它告诉编译器,如果用户在别处使用voidHal_uujiabaoyu_ujiexti_u回调(uint16_tJiaBaoyu_pin)重新定义这个回调函数,那么首先调用用户定义的回调函数,否则调用这个虚函数修改的回调函数。
接下来,让我们看看这个回调函数的调用位置:
void·哈尔GPIO(uint16GPIO)
{
/*检测到EXTI线路中断*/
如果(_haljiaBaoyu_exti_get_it(JiaBaoyupin)=0x00u){
__哈尔GPIO(GPIO饰);
哈尔GPIOuuuextiuuu回调(GPIOupin);}
可以看出,它是在GPIO的外部中断服务函数中调用的,这与上述外部pin中断回调函数是一致的。
GPIO的回调函数到此结束。实际上,STM32的Hal库中大多数其他外设的回调函数基本相同。如果用户设计需求,他们会重新定义需求本身的回调函数,然后在中断中调用它们。
位操作位运算是指二进制位之间的运算。在嵌入式系统的设计中,我们经常要处理二进制问题,例如在寄存器中设置位置1或值0,将数据左移设置为5位等。常用的位运算符如表5.3.1所示。
位与运算符(&)
对于操作中涉及的两个操作数,每个二进制位执行“与”操作。如果两者都为1,则结果为1,否则为0。例如,1011-1001,第一位为1,结果为1;第二位为0,结果为0;第三位为1,一位为0,结果为0;第四个数字是1,结果是1。最终结果是1001。
按位OR运算符(|)
对于运算中涉及的两个操作数,每个二进制位执行“或”运算。如果两者都为0,则结果为1。如果不是,则为1。例如,1011|1001,第一位为1,结果为1;第二位为0,结果为0;第三位是1,一位是0,结果是1;第四个数字是1,结果是1。最终结果是1011。
位求反运算符(~)
位求反运算符用于二进制数的位求反。
例如,~1011,第一位是1,逆位是0;第二个数字是0,倒数是1;第三个数字是1,倒数是0,结果是1;第四位是1,倒数是0。最终结果是0100。
左移gt;然后右移操作员
左移运算符用于将数字向右移位(>;)运算符用于将数字右移几位。例如,假设Val是无符号字符数据,对应的二进制数是10111001。如果Val=VA<<;3.代表Val和左移的三位,然后分配给Val,在左移的过程中,高位移出后丢弃,低位补0。最后,Val结果为1100100;如果Val=Val>>;3.表示Val右移3位,然后分配给Val,右移过程中,低位移出后丢弃,高位补0。最后,Val结果为00010111。
重置或设置为1
在嵌入式系统中,位预算符号通常用于清除或设置为1。
例如,MCU的ODR寄存器控制引脚的输出电平。寄存器为32位,每个位控制一个引脚的电平。假设需要控制GPIOB引脚1的输出电平,将寄存器的0位设置为1,输出高电平,将寄存器的0位设置为0,输出低电平。
#定义GPIOBODR(*(易失性无符号整数*)(0x40010C0C))
GPIOB(1<;<;0);
GPIOB(1<;<;0);
第1行:GPIOB用#define_u定义,ODR对应的内存地址为0x40010c0c。该地址是MCU的ODR寄存器地址。
第三行:GPIOBODR&=~(1<;<;0)实际上是GPIOBODR=GPIOBODR&(1<;<;0),将GPIOB放在ODR和(1<;<;0)的执行和操作之前,并将操作结果分配给GPIOBODR.1<;<书信电报;0的值为00000000000000000000000001,然后逆为11111111111111111111111111110,然后GPIO将ODR的0位和0位运算,结果必须是0,其他位和1位运算,由GPIO将ODR的原始值确定结果。这是实现的。只有GPIO——ODR的第0位被清除为0,其他位保持不变的效果实现了对相应引脚电平输出低的单独控制。
第4行:GPIOB|ODR|=(1<;<;0)实际上GPIOB|ODR=GPIOB|(1<;<;0),将GPIOB放在ODR和(1<;<;0)的操作或操作之前,操作结果分配给GPIOB|ODR.1<;<书信电报;如果0的值是00000000000000000000000000000001,那么GPIO的ODR位0和0或运算,结果必须是1,其他位和0运算,由GPIO的ODR原始值决定结果。这是实现的。ODR只有GPIO_uu0位为1,其他位保持不变,实现了单独控制相应引脚电平输出高的效果。
最新评论