这里写目录标题
1.stdint.h简介(了解)
stdint.h 是从 C99 中引进的一个标准 C 库的文件,可以在MDK5的安装路径:D:\MDK5.34\ARM\ARMCC\include中找到。
stdint.h 定义了很多类型别名,将有符号的char类型定义别名为int8_t等,使用此套别名有易于移植。
在MDK中需要配置才能支持使用S99标准,默认是勾选的。
2.位操作(掌握)
只要相与的两位,有一位是0结果则是0;只要相或的两位,有一位是1结果则是1;只要相异或的两位不同,一位是0一位是1结果则为1;按位取反,0变成1,1变成0;如果左移,右边补0;如果右移,需要分有无符号位,无符号左边直接补0,有符号的话最左边的是符号位,0为正数,1为负数,正数左侧空出的补0,负数左侧空出的补1。
2.1给寄存器某个位赋值
举个例子:uint32_t temp = 0;
如果将位6赋值1,一种方法就是,先将位6与0相与(&)清0,其余位为1,再用或的方式,将位6置一;方法2是将位6与1左移6位取反的值相与进行清0,然后再与1左移六位进行或运算。按位异或可用于某个位的翻转,将位6与1异或,位6本身为1将变成0,本身为0则变成1。
3.宏定义(掌握)
宏定义可以:提高效率、可读性、易改性,核心是替换。
#define 标识符 字符串
标识符:宏定义的名字,变量一般为小写,为了区分宏定义和变量,标识符中的英文字母建议大写,标识符是一个临时的符号,不占用内存,宏定义属于在预处理过程中需要处理的代码,。
字符串:常数、表达式、格式串等
【举例】:
#define PI 3.14159
#define HSE_VALUE 8000000U
3.1带参数的宏定义
下面的do-while语句中用了一个三目运算符,x?A:B,如果x成立,即非0,则返回A,否则返回B。
#define LED1(x) do{ x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
建议大家使用 do{ … }while(0) 来 构造宏定义,这样不会受到大括号、分号、运算符优先级等的影响,总是会按你期望的方式调用运行!
4.条件编译(掌握)
条件编译也属于程序预处理阶段处理的代码,让编译器只对满足条件的代码进行编译,不满足条件的不参与编译!
【举例1】:头文件编译,可以避免头文件被重复引用。
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
code
#endif
【举例2】:代码条件编译
#if SYS_SUPPORT_OS
code
#endif
5.extern声明 (掌握)
放在函数/变量前,表示此函数/变量在其他文件定义,以便本文件引用。
用法举例:
extern uint16_t g_usart_rx_sta;
extern void delay_us(uint32_t nus);
6.类型别名(typedef)(掌握)
为现有数据类型创建一个新的名字,或称为类型别名,用来简化变量的定义。
【格式】:
typedef 现有类型 新名字
【举例】:
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
以后定义unsigned char
数据类型,就可直接用uint8_t
来定义数据类型。
【类型别名应用】:
Struct GPIO_TypeDef
{
__IO uint32_t CRL;
__IO uint32_t CRH;
…
};//定义结构体类型
Struct GPIO_TypeDef gpiox//定义结构体变量
没有使用typedef定义结构体变量的格式:Struct+结构体名+变量名。使用typedef struct定义结构体类型,定义结构体变量时不用再写struct。
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
…
} GPIO_TypeDef;
GPIO_TypeDef gpiox
7.结构体(掌握)
由若干基本数据类型集合组成的一种自定义数据类型,也叫聚合类型。
【格式】:
struct 结构体名
{
成员列表;
} 变量名列表(可选);
【应用举例1】:
struct student
{
char *name; /* 姓名 */
int num; /* 学号 */
int age; /* 年龄 */
char group; /* 所在学习小组 */
float score; /* 成绩 */
}stu1, stu2;
void main()
{
struct student stu3,stu4;
stu3.name = "张三";
stu3.num = 1;
stu3.age = 18;
stu3.group = 'A';
stu3.score = 80.9;
}
【应用举例2】:ST源码,使用类型别名(摘自:stm32f1xx_hal_gpio.h)
typedef struct
{
uint32_t Pin ; /* 引脚号 */
uint32_t Mode ; /* 工作模式 */
uint32_t Pull ; /* 上下拉 */
uint32_t Speed ; /* IO速度 */
} GPIO_InitTypeDef;
8.指针(掌握)
指针就是内存的地址,指针变量是保存了指针的变量。
【格式】:
类型名 * 指针变量名
【应用举例】:
char * p_str = “This is a test!”;
*p_str:取p_str 变量的值,也就是‘T’
&p_str:取p_str变量本身的地址,而不是‘T’的地址,指针变量的地址是处理器最大位数,比如说32位,和数据类型无关。
【应用举例】:
uint8_t buf[5] = {1, 3, 5, 7, 9};
uint8_t * p_buf = buf;//取数组名首地址0x0002
*p_buf取指针变量地址上的值,也就是1
p_buf[0]将指针变量当数组用下标是0值为1
p_buf[1]值为3
p_buf++;指向的地址0x0002地址加1,变成0x0003,不能buf[++]
*p_buf取指针变量地址上的值,也就是3
p_buf[0]值为3,指针指向的地址第一个元素
指针使用的2大最常见问题:1,未分配(申请)内存就用;2,越界使用。下面的左图定义指针变量并没有赋初始值,初始值为随机数,指向了一个随机的地址,可能指向一个真实的内存,也可能指向一个不真实存在的内存,指向真实地址可能编译过程中不会报错,运行也不会有问题,也可能指向关键参数地址,重新赋值操作会造成程序运行结果不正常,指向不真实的地址,会报内存错误。右图为越界使用,越界也可能不会报错,可能导致内存冲突。
9.代码规范(熟悉)
正点原子做了代码规范,可以在路径:A盘 ->1,入门资料 -> 《嵌入式单片机 C代码规范与风格.pdf》找到。
规范的关键点:
- 所有函数/变量名字非特殊情况,一般使用小写字母;
- 注释风格使用 doxgen 风格,除屏蔽外,一律使用 /* */ 方式进行注释;
- TAB 键统一使用 4 个空格对齐,不使用默认的方式进行对齐;
- 每两个函数之间,一般有且只有一个空行;
- 相对独立的程序块之间,使用一个空行隔开;
- 全局变量命名一般用 g_开头,全局指针命名一般用 p_开头;
- if、 for、 while、 do、 case、 switch、 default 等语句单独占一行,一般无论有多少行执行语句,都要用加括号: {}。