C语言是单片机开发必备的基础知识,本文列举了一些STM32学习中会遇到的C语言基础知识点,希望对大家有所帮助。
01
位操作
接下来我们先讲解几个位运算符,然后再讲解位运算的用法。C语言支持以下6种位运算:
六位运算
接下来我们来重点介绍一下单片机开发中位操作的一些实用技巧。
1.1 设置某些位,不改变其他位的值
这种场景在单片机开发中经常用到,方法是先用&操作符把需要设置的位清零,再用|操作符设置值。
例如,如果我想改变 GPIOA 的状态,我可以先清除寄存器的值:
然后与要设置的值执行或运算:
1.2 移位运算提高代码的可读性
移位操作在单片机开发中非常重要,下面是delay_init函数中的一行代码:
SysTick->CTRL |= 1 << 1;
这个操作就是把CTRL寄存器的第一位(从0开始)设置为1,为什么需要左移,而不是直接设置一个固定的值呢?
其实这是为了提高代码的可读性和复用性,很直观的就能知道这行代码就是把第一位设置为1,如果写成:
SysTick->CTRL |= 0X0002;
这样虽然能达到同样的效果,但是可读性较差,并且修改起来也比较困难。
1.3 ~ 使用按位反转的技巧
位反转在设置寄存器的时候经常用到,也经常用来清除某一个或者几个位,下面是delay_us函数的一行代码:
SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */
这段代码可以理解为仅将CTRL寄存器的第0位(最低位)设置为0,其他位的值保持不变。
类似地,我们不使用按位反转,代码如下:
SysTick->CTRL &= 0XFFFFFFFE; /* 关闭SYSTICK */
可以看出前者的可读性和可维护性要比后者好很多。
1.4 ^按位异或运算使用技巧
该函数非常适合控制位翻转,一个常见的应用场景是控制LED闪烁,如:
GPIOB->ODR ^= 1 << 5;
执行一次此代码将翻转 PB5 的输出状态。如果我们的 LED 连接到 PB5,我们可以看到 LED 闪烁。
02
定义宏定义
define 是 C 语言中的预处理命令,用于宏定义(定义常量),可以提高源代码的可读性,为编程提供方便。常用格式:
“Identifier”是定义的宏的名称。“String”可以是常量、表达式、格式字符串等。例如:
标识符HSE_VALUE的值定义为8000000,数字后面的U表示无符号。
至于关于define宏定义的其他一些知识,比如带参数的宏定义等,这里就不再讲解了。
03
ifdef 条件编译
在单片机程序开发过程中,我们经常会遇到这样的情况:当满足某个条件时,编译一组语句,当不满足该条件时,编译另一组语句。
最常见的条件编译命令形式是:
#ifdef 标识符
程序段1
#else
程序段2
#endif
其功能为:当该标识符已经定义(通常用#define命令定义)时,编译程序段1,否则编译程序段2。#else部分也可以省略,即:
#ifdef
程序段1
#endif
条件编译在HAL库中使用得比较多,在头文件stm32mp1xx_hal_conf.h中,经常会看到这样的语句:
#if !defined (HSE_VALUE)
#define HSE_VALUE 24000000U
#endif
如果HSE_VALUE宏没有定义,那么就定义HSE_VALUE宏,HSE_VALUE的值为24000000U。条件编译也是C语言的基础知识。
这里需要提一下,24000000U中的U代表无符号整数,一般UL代表无符号长整数,F代表浮点数。
这里加上U之后,系统在编译的时候就不会进行类型检查,直接把值以U的形式赋给某个对应的内存,如果超出了定义变量的范围,就会被截取。
04
外部变量声明
在C语言中,extern可以放在变量或函数之前,表示该变量或函数的定义在另一个文件中,提示编译器在遇到这个变量或函数时去其他模块中寻找它的定义。
请注意,您可以多次声明外部变量,但只能定义一次。在我们的代码中,您将看到如下语句:
extern uint16_t g_usart_rx_sta;
该语句声明 g_usart_rx_sta 变量已在其他文件中定义,并将在此处使用。
因此,你肯定可以在某处找到变量定义:
uint16_t g_usart_rx_sta;
extern的使用比较简单,但是经常用到,需要掌握。
05
typedef 类型别名
Typedef 用于为现有类型创建一个新名称,或类型别名,以简化变量的定义。HAL 库中 typedef 最常见的用途是为结构和枚举类型定义类型别名。
struct _GPIO
{
__IO uint32_t CRL;
__IO uint32_t CRH;
…
};
定义了一个结构体GPIO,因此我们定义结构体变量如下:
struct _GPIO gpiox; /* 定义结构体变量gpiox */
但是这样很繁琐,HAL库中有很多这样的结构体变量需要定义。这里我们可以给结构体定义一个别名GPIO_TypeDef,这样我们在其他地方就可以通过别名GPIO_TypeDef来定义该结构体变量了,如下:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
…
} GPIO_TypeDef;
Typedef为结构体定义了一个别名GPIO_TypeDef,因此我们可以通过GPIO_TypeDef定义结构体变量:GPIO_TypeDef gpiox;
这里的GPIO_TypeDef和struct _GPIO功能相同,但是GPIO_TypeDef使用起来方便得多。