嵌入式C语言基础(STM32)

本文介绍了嵌入式开发中STM32库函数的使用,特别是位操作在直接操作芯片寄存器时的重要性。通过#define宏定义简化代码,以及GPIO结构体指针操作寄存器,如GPIOG->CRH。还探讨了Typedef在变量重命名和结构体定义中的作用,以及GPIO初始化函数的参数分析。此外,提到了static关键字的用途和寄存器地址的直接操作。文章以实例讲解了C语言在嵌入式系统中的应用。
摘要由CSDN通过智能技术生成

前言:一条混迹嵌入式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位

(1)把变量的某位清零

//对 bit2 清零
a  &= ~  (1<<2);

(2)把变量的某几位清零

//对bit 2、3清零

a &= ~ (3<<2*1); 

(3)对变量的某几位进行赋值

//对bit 4置1

a |= (1<<2*2);

(4)对变量的某位取反

//对bit 6取反

a ^=(1<<6);

二、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效果也是一样的呀,也可以访问其程序变量。传数据拷贝的是内存单元的数据,如果数据很多的话拷贝过来都要为它们分配内存。而且传数据非常消耗效率,为形参分配内存需要时间,拷贝需要时间,最后结束了返回还是需要时间。问题是传值会让函数重新开辟一个结构体,而传指针可以不用开辟新的地址。大大减少了内存消耗,提高程序运行速率。如果这里直接使用结构体的地址,可以直接对该地址的结构体进行赋值,换句话说,就是直接对寄存器进行操作。

ps:STM32内部CPU、外设、RAM、FLASH都是分开的,为了对外设进行操作,需要CPU将值读取后传输给外设。芯片通过映射的方法来读取外设地址。

所以使用结构体指针作为形参,不仅可以读取结构体保存的数据,还可以提高效率,还有一个知识点。

(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,则该全局变量被定义成一个静态全局变量,其作用范围只在定义该变量的源文件内有效,其他源文件不能引用该全局变量(static变量被定义在堆中,就是FLASH),这样就避免了在其他源文件中因引用相同名字的变量而引发的错误,有利于模块化程序设计。

在自己写程序时,很少会用到,了解其用法就行。如果是写linux的设备驱动,那就另当别论。

五、寄存器地址

找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x40010C0C, ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。我们通过 C 语言指针的操作方式,让 GPIOB的 16 个 IO 都输出高电平,代码如下:
 

*(unsigned int*)(0x4001 0C0C) = 0xFFFF;

注:本质上,指针存放的是一个地址。0x4001 0C0C 在我们看来是 GPIOB 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针(一个地址),即 (unsigned int *)0x4001 0C0C。再加上*后,变成了* 地址=0xffff的形式,和指针最本质的形式相似,相当于直接对0x4001 0C0C这个地址进行赋值。
 

六、数组指针和指针数组

指针数组:指向数组的指针,它的声明形式为type *arrayName[size],其中type是指针数组中元素的类型,arrayName是指针数组的名称,size是指针数组中元素的数量。例如,int *ptrArray[5]声明了一个包含5个指向整型数据的指针的数组。

数组指针:数组中存放指针,数组指针的声明形式为type (*pointerName)[size],其中type是数组中元素的类型,pointerName是数组指针的名称,size是数组中元素的数量。例如,int (*ptr)[5]声明了一个指向包含5个整型元素的数组的指针,这个数组中存放的都是指针。

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

### 回答1: STM32是一种常用的嵌入式系统开发平台,其开发语言主要使用C语言STM32嵌入式C语言是在C语言基础上进行了一些适配和优化,以便于在STM32芯片上进行嵌入式系统的开发。 使用STM32嵌入式C语言开发,首先要了解STM32系列芯片的特性和寄存器架构。然后,可以使用C语言中的各种语法和函数来编写嵌入式系统的程序。在C语言基础上,还需要了解一些特定的STM32相关的库函数和驱动程序,以便更好地利用STM32芯片的功能和资源。 STM32嵌入式C语言开发具有以下特点和优势。首先,C语言作为一种高级语言,具有丰富的语法和强大的数据处理能力,能够满足嵌入式系统开发的需求。其次,STM32嵌入式C语言支持直接访问芯片寄存器和外设,使开发者可以更好地控制和管理硬件资源。此外,使用C语言进行开发,能够更好地编写可移植、可维护和可扩展的嵌入式系统程序。 在STM32嵌入式C语言开发中,开发者可以使用各种开发工具和环境,如Keil、IAR等,来进行代码编写、调试和下载。同时,还可以利用丰富的开发资料和示例代码,快速上手和学习STM32嵌入式C语言开发技术。 总之,STM32嵌入式C语言是一种常用的嵌入式系统开发语言,通过熟练掌握C语言的语法和STM32芯片的特性,可以高效地开发出功能强大的嵌入式系统应用。 ### 回答2: STM32是一种基于Cortex-M系列内核的嵌入式微控制器的系列产品,它广泛应用于各种嵌入式系统的开发中。C语言是一种高级编程语言,具有简洁、高效、可移植的特点,非常适合嵌入式系统的开发。 在STM32嵌入式开发中,C语言扮演着重要的角色。首先,C语言可以直接访问硬件资源,比如外设寄存器和内部存储器,方便了对嵌入式系统的底层控制。其次,C语言具有丰富的数据类型和操作符,在算法设计和数据处理方面非常灵活。此外,C语言还提供了丰富的库函数,可以方便地完成各种常用的功能,比如串口通信、定时器控制、中断处理等。 对于STM32嵌入式C语言开发开发者一般需要掌握以下几个方面的知识和技能。首先是C语言的基本语法和程序结构,包括变量、运算符、条件语句、循环语句等。其次是对于STM32微控制器的架构和外设的了解,比如GPIO、ADC、UART、SPI等。然后是掌握相关的开发工具和调试技巧,比如Keil、IAR等集成开发环境和调试器。最后是对于嵌入式系统的特性和应用场景的了解,以便根据具体需求进行系统设计和优化。 综上所述,STM32嵌入式C语言开发是一项非常重要且有挑战性的技术工作。掌握C语言基础知识和对STM32系列产品的了解是必不可少的,通过不断学习和实践,开发人员可以更好地利用STM32的特性和功能,开发出高效、可靠的嵌入式系统。 ### 回答3: STM32是一种广泛应用于嵌入式系统开发的微控制器系列产品,而C语言则是一种面向过程的计算机编程语言STM32嵌入式C语言是指在STM32系列芯片上使用C语言进行嵌入式系统开发的方式。 使用嵌入式C语言开发STM32可以实现各种功能,如控制IO口、定时器、中断等。通过编写C语言代码,可以直接对STM32的内部寄存器进行操作,从而实现对外设的控制。同时,C语言具有跨平台性,编写的代码可以在不同型号的STM32芯片上运行,提高了开发效率。 嵌入式C语言相对于其他编程语言的优势在于其轻量级、高效性和灵活性。由于嵌入式系统往往对资源有限制,需要占用较少的内存和处理器时间,而C语言作为一种底层语言,具有较高的运行效率和内存控制能力,非常适合嵌入式系统的开发。 在STM32嵌入式C语言开发过程中,通常需要掌握基本的C语言语法、数据类型和运算符等基本知识。此外,还需要了解STM32的外设寄存器、内部时钟和编程接口等硬件相关知识,才能更好地进行嵌入式系统的开发和调试。 总之,STM32嵌入式C语言是一种在STM32芯片上使用C语言进行嵌入式系统开发的方式。它具有快速、高效、灵活等优势,能够满足嵌入式系统对资源的限制,是嵌入式系统开发中常用的编程语言
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值