您现在的位置是:首页 >技术教程 >嵌入式C语言基础(STM32)网站首页技术教程
嵌入式C语言基础(STM32)
前言:一条混迹嵌入式3年的老咸鱼,想到自己第一次接触到stm32的库函数时,c语言稀碎,痛不欲生的场景,该文章为萌新指条明路。
一、位操作
位操作在嵌入式中常用于直接对芯片的寄存器进行操作,当时作为初学者的我看着一脸懵逼,至于为什么这样修改,下面好好分析一下。
#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}
一句话有很多知识点!
1.#define 宏定义 (预处理)
#define是C语言的预处理命令,它用于宏定义,用来将一个标识符定义为一个字符串,该标识符称为宏名,被定义的字符串称为替换文本,采用宏定义的目的主要是方便程序编写。一般放在源文件的前面,称为预处理部分。这个仅仅只是程序文本进行替换,通常用于修改某个数据较为麻烦,使用#define。如果要重命名一个变量,正确操作应该是使用typedef。
用法:#define [别名] [字符串]
其中,字符串可以是常数、字符串和表达式等。
例如:#define Temp 25
该语句表示在程序中使用Temp的地方,编译时全部替换为25.
例如:#define RCC AHBPeriph_DMA1 ((uint32_t)0x00000001)
该语句表示使用RCC_AHBPeriph_DMA1,编译时全部替换为32位的无符号数据0x00000001.
2.GPIOG->CRH(结构体指针)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
有了前面的基础,应该可以看懂这里使用了宏定义,((GPIO_TypeDef *) GPIOG_BASE)定义了一个结构体指针,GPIOG_BASE是一个地址(实际上是GPIO_TypeDef 这个结构体的首地址),本质上是在GPIOG_BASE地址(APB2总线的基地址+0x2000),在该地址上创建了一个类型为GPIO_TypeDef的结构体指针,结构体如下:
#define __IO volatile /*!< defines 'read / write' permissions */
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
在结构体中定义了7个32位的寄存器,且使用volatile关键词,目的是为了减少程序运行的错误。以后会讲到。结构体访问成员变量的方法是结构体->成员变量。通过结构体指针访问寄存器的方式来提高程序运行效率。
3.&=、|=、>>、<<(仔细思考)
&=0xffff0fff目的是把32位寄存器中的第17-20位置为0
|=0x3<<12目的是左移12bit,把0x3赋予17-20位
这几个就是常见的位操作
二、Typedef的妙用(变量重命名)
typedef signed char int8_t;//为数据类型signed char起别名int8_t
typedef signed int int32_t;//为数据类型signed int起别名int32_t
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
typedef struct
{
uint16_t GPIO_ Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode TypeDef GPIO_Mode;
}GPIo_InitTypeDef;
1. typedef的基本应用
为已知的数据类型起一个简单的别名,如上例int32_t。
2. typedef 与结构体struct结合使用
该用法用于自定义数据类型。0如 stm32f10x_gpio.h头文件中的GPIO初始化结构体GPIO_InitTypeDef。因此GPIO_InitTypeDef成为一个结构体变量名词,可以创造新的变量来完成初始化操作。结构体访问成员变量的方式不同于结构体指针,所以GPIO_InitTypeDef其实是一个结构体
方法:结构体.成员变量
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); /
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //
GPIO_Init(GPIOB, &GPIO_InitStructure); //¸
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 Êä³ö¸ß
三、初始化函数分析
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
(1)形参指针
成员变量是GPIO_TypeDef类型的指针和GPIO_InitTypeDef类型的指针,为什么这里要使用指针呢?因为如果使用GPIO_TypeDef GPIOx效果也是一样的呀,也可以访问其程序变量。传数据拷贝的是内存单元的数据,如果数据很多的话拷贝过来都要为它们分配内存。而且传数据非常消耗效率,为形参分配内存需要时间,拷贝需要时间,最后结束了返回还是需要时间。问题是传值会让函数重新开辟一个结构体,而传指针可以不用开辟新的地址。大大减少了内存消耗,提高程序运行速率。
所以使用结构体指针作为形参,不仅可以读取结构体保存的数据,还可以提高效率,还有一个知识点。
(2)什么时候需要使用&(取址符号)
结论:如果变量是数组或者结构体,就不需要使用取址符号;若变量是一个数,就需要加取址符号,至于为什么,自己可以好好想想指针的意义。
(3)感受C语言的魅力(结构体和结构体指针)
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
}
四、static关键词
static关键字可以用来修饰变量,使用static关键字修饰的变量,称为静态变量。
静态变量的存储方式与全局变量一样,都是静态存储方式。全局变量的作用范围是整个源程序,当一个源程序由多个源文件组成时,全局变量在各个源文件中都是有效的,即一个全局变量定义在某个源文件中,若想在另一个源文件中使用该全局变量,则只需要在该源文件中通过 extern关键字声明该全局变量就可以使用了。若在该全局变量前加上关键字static,则该全局变量被定义成一个静态全局变量,其作用范围只在定义该变量的源文件内有效,其他源文件不能引用该全局变量,这样就避免了在其他源文件中因引用相同名字的变量而引发的错误,有利于模块化程序设计。
在自己写程序时,很少会用到,了解其用法就行。如果是写linux的设备驱动,那就另当别论。
(未完待续......)