C语言是单片机开发必不可少的基础知识。 本文列出了STM32学习中一些常用的C语言基础知识。
1 位操作
下面我们先讲解几个位运算符,然后讲解使用位运算的技巧。 C语言支持以下六位运算:
下面,我们重点讲解一下单片机开发中位操作的一些实用技巧。
嵌入式物联网确实有很多东西需要学习。 不要学错路线和内容,导致你的薪资水涨船高!
我免费给大家分享一个数据包,差不多150G。 学习内容、面试、项目都比较新、全面! 据估计,在网上购买某种鱼至少要花费几十美元。
点此找助手0元获取:嵌入式物联网学习资料(今日头条)
1.1 设置某些位的值而不改变其他位的值
这种场景在单片机开发中经常使用。 方法是我们先使用&运算符清除需要设置的位,然后使用| 运算符来设置值。
例如,如果我想改变GPIOA的状态,我可以先清除寄存器值:
然后与需要设置的值进行|OR运算:
1.2 移位操作提高代码可读性
移位操作在微控制器开发中非常重要。 下面是delay_init函数的一行代码:
SysTick->CTRL |= 1 << 1;
这个操作就是将CTRL寄存器的第一位(从0开始计数)设置为1,为什么我们需要左移而不是直接设置一个固定值呢?
其实这是为了提高代码的可读性和可重用性。 这行代码可以非常直观清晰,将bit 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 闪烁。
2 定义宏定义
Define是C语言中的预处理命令。 用于宏定义(定义常量),可以提高源代码的可读性,为编程提供方便。 常见格式:
“标识符”是定义的宏名称。 “字符串”可以是常量、表达式、格式字符串等。例如:
定义的标识符HSE_VALUE的值为8000000,数字后面的U表示无符号。 至于关于define宏定义的其他知识,比如带参数的宏定义,这里不再赘述。
3 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的形式赋给对应的内存,如果超出了定义的变量范围,就会被拦截。
4 外部变量声明
在C语言中,extern可以放在变量或函数之前,表示该变量或函数的定义在另一个文件中,提示编译器遇到这个变量或函数时到其他模块中查找其定义。
这里需要注意的是,extern变量可以声明多次,但只能定义一次。 在我们的代码中你会看到这样的语句:
extern uint16_t g_usart_rx_sta;
该语句声明g_usart_rx_sta变量已在其他文件中定义,并将在此处使用。
所以,你肯定可以在某处找到带有变量定义的语句:
uint16_t g_usart_rx_sta;
extern的使用比较简单,但是使用频繁,需要掌握。
5 typedef 类型别名
Typedef 用于为现有类型创建新名称或类型别名,以简化变量的定义。 Typedef 在 HAL 库中最常用于定义结构的类型别名和枚举类型。
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 使用起来方便很多。