单片机 C 语言基础(位操作、#define宏定义#ifdef条件编译、extern外部变量声明、enum枚举、static静态变量、const常量和u8u16u32)

22 篇文章 2 订阅


1.位操作

6种位操作运算符

在这里插入图片描述
&

口诀:有0出0,全1得1 (乘法)

0xAA = 1010 1010
0xFF = 1111 1111
1010 1010 & 1111 1111 = 1010 1010
GPIO->CRL &= 0xFF = 1010 1010

|

口诀:有1出1,全0得0(加法)

1010 1010 | 1111 1111 = 1111 1111

~

~ 1010 1010 = 0101 0101

按位异或^

口诀:值不一样为1,相同值为0

1010 ^ 1111 = 0101

左移<<

1010 左移 1位 0100

右移>>

1010 右移 1位 0101

实用技巧

例如:

GPIOA->CRL&=0XFFFFFF0F; //0XFFFFFF0F=1111 1111 1111 1111 1111 1111 0000 1111 将第 4-7 位清 0
GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值 0X00000040=0000 0000 0000 0000 0000 0000 0100 0000 

这个场景单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,然后用|操作符设值。比如我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作,然后再与需要设置的值进行|或运算。

GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位

5 告诉我们是第 5 位也就是第 6 个端口,1 告诉我们是设置为 1 了。

#define TIM_FLAG ((uint16_t)0x0001)
TIMx->SR = (uint16_t)~TIM_FLAG;

设置第 0 位为 0。

2. define宏定义关键词

在这里插入图片描述

语法:#define 标识符 字符串

  • “标识符”为所定义的宏名
  • “字符串”可以是常数表达式格式串

例如:

#define SYSCLK_FREQ_72MHz 72000000

3. ifdef条件编译

单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

#ifdef 标识符
程序段 1
#else
程序段 2
#endif

作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,即:

#ifdef
程序段 1
#endif

在 stm32f10x.h 这个头文件中经常会看到这样的语句:

#ifdef STM32F10X_HD
大容量芯片需要的一些变量定义
#endif

而 STM32F10X_HD 则是我们通过#define 来定义的。

扩展知识

if、ifdef、ifndef

//宏定义
#define WIN32 0
#define x64 1
#define SYSTEM WIN32

#if SYETEM == win32
printf(" win32\n");
#else
printf("x64\n");
#endif
#define DEBUG

#ifdef DEBUG
printf("输出调试信息\n");
#else
printf("不输出调试信息\n");
#endif

输出结果:输出调试信息

#define DEBUG

#ifndef DEBUG
printf("输出调试信息\n");
#else
printf("不输出调试信息\n");
#endif

输出结果:不输出调试信息

4. extern变量声明

C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

注意,对于 extern 申明变量可以多次,但定义只有一次

例如:

在 Main.c 定义的全局变量 id,id 的初始化都是在 Main.c 里面进行的。

u8 id;//定义只允许一次
main()
{
id=1;
printf("d%",id);//id=1
test();
printf("d%",id);//id=2
}

但是我们希望在test.c中使用变量id,这个时候我们就需要在test.c里面去申明变量 id 是外部定义的了,因为如果不申明,变量 id 的作用域是到不了 test.c 文件中。看下面 test.c 中的代码:

extern u8 id;//申明变量 id 是在外部定义的,申明可以在很多个文件中进行
void test(void){
id=2;
}

5. typedefe类型别名

typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。

typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

可以为结体定义一个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变量了。

方法如下:

typedef struct
{
 __IO uint32_t CRL;
 __IO uint32_t CRH;} GPIO_TypeDef;

Typedef 为结构体定义一个别名 GPIO_TypeDef。

GPIO_TypeDef _GPIOA,_GPIOB;

GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了

6. struct 结构体

声明结构体类型:

Struct 结构体名{
成员列表;
}变量名列表;

例如:

Struct U_TYPE {
Int BaudRate
Int WordLength;
}usart1,usart2;

在结构体申明的时候可以定义变量(如上),也可以申明之后定义。

例如:

struct U_TYPE usart1,usart2;

结构体成员变量的引用方法是:结构体变量名字.成员名

例如:

usart1.BaudRate;

结构体指针变量定义

例如:

struct U_TYPE *usart3;//定义结构体指针变量 usart1;

结构体指针成员变量引用方法是通过“->”符号实现,比如要访问 usart3 结构体指针指向的结构体的成员变量 BaudRate。

例如:

Usart3->BaudRate;

7. enum 枚举

参考资料:enum(枚举)

定义枚举类型的同时定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。

不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。

以下实例使用 for 来遍历枚举的元素:

#include <stdio.h>
 
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }
}

以上实例输出结果为:3

8. static关键字

参考:static变量及其作用

在 C 语言中,static 关键字不仅可以用来修饰变量,还可以用来修饰函数。

在使用 static 关键字修饰变量时,称此变量为静态变量

静态变量的存储方式与全局变量一样,都是静态存储方式。

但这里需要特别说明的是,静态变量属于静态存储方式属于静态存储方式的变量却不一定就是静态变量

例如,全局变量虽然属于静态存储方式,但并不是静态变量,它必须由 static 加以定义后才能成为静态全局变量。

静态变量的作用:保持变量内容的持久性

有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量”。

例如:

#include <stdio.h>
void count();
int main(void)
{
    int i=0;
    for (i = 0;i <= 5;i++)
    {
            count();
    }
    return 0;
}
void count()
{
    /*声明一个静态局部变量*/
    static num = 0;
    num++;
    printf("%d\n",num);
}

在该代码中,我们通过在 count() 函数里声明一个静态局部变量 num 来作为计数器。因为静态局部变量是在编译时赋初值的,且只赋初值一次,在程序运行时它已有初值。以后在每次调用函数时就不再重新赋初值,而是保留上次函数调用结束时的值。这样,count() 函数每次被调用的时候,静态局部变量 num 就会保持上一次调用的值,然后再执行自增运算,这样就实现了计数功能。同时,它又避免了使用全局变量。

9. const 、u8 u16 u32

C语言入门 —C语言基本数据类型

const 关键字

程序开发人员可以在变量定义后,在程序的其他位置引用和修改变量。但程序中定义的一些变量,如圆周率PI=3.14,黄金分割比例 g=0.618,这些变量只需要被引用,不应该被修改。C语言中可以使用 const关键字修饰变量。

const float pi = 3.141592612;
printf("%f",pi);

输出结果:3.141593

这是因为pi是单精度浮点型变量,它只提供7位有效数字,pi超出了取值范围,所以后面的三位被舍去了。

u8 u16 u32

int8_t、int16_t、int32_t、int64_t、uint8_t、size_t、ssize_t区别

在这里插入图片描述

在Keil MDK 开发环境里,一个 无符号32位整形数据会有很多种表示方法:

  • 1.unsigned int 32 (C语言标准表达方法)
  • 2.uint32_t
  • 3.u32

这三种方式都是在表达同一个意思,可为什么ST的开发人员要搞的这么乱呢?

还有其他好多你可能看起来很陌生 ,很不好理解的表达方式,如:

  • _IO int32_t
  • vs32
  • volatile int32_t
  • volatile signed int 32

原因: 其实ST 搞这么多花样,无非是想开发人员在写代码时定义数据类型能少写几个符号,然后又因为前后版本升级,为了兼容旧版本(主要是V2.0)才会出现这么多表示方法。不管他怎么换,都是基于标准C来的,看清楚以下几个文件你就OK了。

core_cm4.h ;stm32f4xx.h; stdint.h; 其中每个文件大概作用如下:

stdint.h中的声明:

typedef unsigned          char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;
typedef unsigned       __INT64 uint64_t;

在这里插入图片描述

在这里插入图片描述

stm32f4xx.h 中声明的 u8,u16,u32 :
在这里插入图片描述

取反:!和 ~ 的区别

在c语言中,!~均表示取反。

这两个符号的区别在于:

  • ! : 代表逻辑取反,即:把非0的数值变为0,0变为1;
  • ~ : 表示按位取反,即在数值的二进制表示方式上,将0变为1,将1变为0。
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断 RESET=0,  SET=!RESET ,也就是SET=1
	{
		LED1=!LED1;//DS1翻转
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
}

其中 SET

typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus; //逻辑取反

在这里插入图片描述

10. __IO

__IO解释
STM32得库函数中(HAL和LL库都有),存在一个__IO得宏定义

在这里插入图片描述

#define     __I         volatile const        /*!< defines 'read only' permissions      */
#define     __O     volatile                  /*!< defines 'write only' permissions     */
#define     __IO    volatile                  /*!< defines 'read / write' permissions   */

volatile得含义为 允许硬件改变变量得数值

在这里插入图片描述

__IO uint16_t ADC_ConvertedValue;  // 用于保存转换后的ADC值 
// 中断服务函数
void ADC_IRQHandler(void)
{
  if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET) 
	{
		// 读取ADC的转换值
		ADC_ConvertedValue = ADC_GetConversionValue(ADCx);
	}
	ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);//清除ADCx的中断
}

总 结
volatile 形变量可以被硬件改变,在需要硬件改变变量得场合中不可或缺!!!


开发环境和C语言入门电子书:https://download.csdn.net/download/Naiva/85030336

1.C常见的问题;
2.C8051的C语言的彻底应用;
3.我的第1本c语言编程书:C语言从入门到精通].国家863中部软件孵化器;
4.高质量C++编程指南。

  • 10
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naiva

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值