您现在的位置是:首页 >技术教程 >嵌入式C语言基础(STM32)网站首页技术教程

嵌入式C语言基础(STM32)

憨猪在度假 2023-07-24 12:00:02
简介嵌入式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的设备驱动,那就另当别论。

(未完待续......)
 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。