【STM32】复习—以MDK为背景的C语言基础复习

推荐阅读:

1、STM32 | STM32中一些非常重要的C语言知识点汇总_嵌入式大杂烩的博客-CSDN博客

2、STM32学习第一课:STM32 编程的C语言基础_lijin111222的博客-CSDN博客_stm32c语言

【本文摘抄自电子书:《STM32F4开发指南-HAL库版本_V1.2》】

【本文仅作为个人学习记录,不出于任何商业目的。】

目录

字节类型

位操作

1) 不改变其他位的值的状况下,对某几个位进行设值

2) 移位操作提高代码的可读性

 3) ~取反操作使用技巧

define宏定义

ifdef条件编译

extern变量申明

typedef类型别名

结构体


字节类型

  • 1字节对应uint8_t、int8_t、char和portCHAR类型
  • 2字节对应uint16_t、int16_t、short和portSHORT类型
  • 4字节对应uint32_t、int32_t、int、long和float类型
  • 8字节对应uint64_t、int64_t和double类型

位操作

运算符含义运算符含义
&按位与~取反
|按位或<<左移
^按位异或>>右移

1) 不改变其他位的值的状况下,对某几个位进行设值

        这个场景单片机开发中经常使用,方法就是先对需要设置的位 用&操作符进行清零操作,然后用|操作符设值。比如我要改变GPIOA -> BSRRL的状态,可以先对寄存器的值进行&清零操作

GPIOA-> BSRRL &=0XFF0F; //将第4-7位清0

        然后再与需要设置的值进行|或运算

GPIOA-> BSRRL |=0X0040; //设置相应位的值,不改变其他位的值

关于->:

c++中 -> 是什么意思,如何使用_YoungFor_You的博客-CSDN博客_-> c++

 ->在C语言或C++中的含义-CSDN博客_c++中->

2) 移位操作提高代码的可读性

        移位操作在单片机开发中也非常重要,我们来看看下面一行代码

GPIOx->ODR = (((uint32_t)0x01) << pinpos);

        这个操作就是将ODR寄存器的第pinpos位设置为1,为什么要通过左移而不是直接设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第pinpos位设置为1。如果你写成

GPIOx->ODR =0x0030;

        这样的代码就不好看也不好重用了。

 3) ~取反操作使用技巧

        SR寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为0,同时其他位都保留为1,简单的作法是直接给寄存器设置一个值:

TIMx->SR=0xFFF7;

        这样的作法设置第3位为0,但是这样的作法同样不好看,并且可读性很差。看看库函数代码中怎样使用的:

TIMx-->SR = (uint16_t)~TIM_FLAG;

        而TIM_FLAG是通过宏定义定义的值:

#define TIM_FLAG_Update         ((uint16_t)0x0001)
#define TIM_FLAG_CC1             ((uint16_t)0x0002)

        看这个应该很容易明白,可以直接从宏定义中看出TIM_FLAG_Update就是设置的第0位了,可读性非常强。

define宏定义

        define是C语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:

#define 标识符 字符串

        “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:

#define PLL_M 8

        定义标识符PLL_M的值为8

ifdef条件编译

        单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

#ifdef 标识符
程序段 1
#else
程序段 2
#endif

         它的作用是:当标识符已经被定义过一般是用 #define 命令定义 )),则对程序段 1 进行编译,否则编译程序段 2 。 其中 #else 部分也可以没有,即:

#ifdef
    程序段 1
#endif

        这个条件编译在MDK 里面是用得很多的,在 stm32f4xx.h 这个头文件中经常会看到这样的语句:

#if defined (STM32F40_41xxx)
    STM32F40x系列和 STM32F41x系列 芯片需要的一些变量定义
#end

        而(STM32F40_41xxx)则是我们通过 #define 来定义的。

extern变量申明

        C语言中extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义 。 这里面要注意,对于extern申明变量可以多次但定义只有一次。在我们的代码中你会看到看到这样的语句:

extern u16 USART_RX_STA;

        这个语句是申明USART_RX_STA变量其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:

u16 USART_RX_STA;

        的出现。下面通过一个例子说明一下使用方法。

        在Main.c定义的全局变量id,id的初始化都是在Main.c里面进行的。

        Main.c文件

u8 id;//定义只允许一次
main() 
{
    id=1;
    printf("d%",id);//id=1
    test();
    printf("d%",id);//id=2
}

        但是我们希望在main.c的 changeId(void)函数中使用变量id,这个时候我们就需要在main.c里面去申明变量id是外部定义的了,因为如果不申明,变量id的作用域是到不了main.c文件中。看下面main.c中的代码:

extern u8 id;//申明变量id是在外部定义的,申明可以在很多个文件中进行
void test(void)
{
    id=2;
}

        在main.c中申明变量id在外部定义,然后在main.c中就可以使用变量id了。

typedef类型别名

【关于结构体:c语言结构体学习整理(结构体初始化,结构体指针)_Z小旋的博客-CSDN博客_结构体指针】 

        typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef在MDK用得最多的就是定义结构体的类型别名枚举类型了。

struct _GPIO 
{  
    __IO uint32_t MODER;
    __IO uint32_t OTYPER;
    …
};

        定义了一个结构体GPIO,这样我们定义变量的方式为:

struct _GPIO GPIOA;//定义结构体变量 GPIOA

        但是这样很繁琐,MDK中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了。方法如下:

typedef struct 
{
    __IO uint32_t MODER;
    __IO uint32_t OTYPER;
    …
} GPIO_TypeDef;

        Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体变量:

GPIO_TypeDef _GPIOA,_GPIOB;

        这里的GPIO_TypeDef就跟struct GPIO是等同的作用了。

结构体

        声明结构体类型:

Struct 结构体名{
    成员列表;
    }变量名列表;

        例如:

Struct U_TYPE {
    Int BaudRate
    Int WordLength;
    }usart1,usart2;

        在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:

Struct 结构体名字 结构体变量列表 ;

        例如:

struct U_TYPE usart1,usart2;

        结构体成员变量的引用方法是:

结构体变量名字.成员名

        比如要引用usart1的成员BaudRate,方法是:

usart1.BaudRate;

        结构体指针变量定义也是一样的,跟其他变量没有啥区别。
        例如:

struct U_TYPE *usart3;//定义结构体指针变量usart1;

        结构体指针成员变量引用方法是通过“->”符号实现,比如要访问usart3结构体指针指向的结构体的成员变量BaudRate,方法是:

Usart3->BaudRate;

        有人会问,结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过一个实例回答一下这个问题。

        在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态是由几个属性来决定的,比如串口号,波特率,极性,以及模式等。对于这种情况,在我们没有学习结构体的时候,我们一般的方法是:

void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);

        这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里面再传入一个参数,那么势必我们需要修改这个函数的定义重新加入字长这个入口参数。于是我们的定义被修改为:

void USART_Init (u8 usartx,u32 BaudRate, u8 parity,u8 mode,u8 wordlength );

        但是如果我们这个函数的入口参数是随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?

        这样如果我们使用到结构体就能解决这个问题了。我们可以在不改变入口参数的情况下,只需要改变结构体的成员变量,就可以达到上面改变入口参数的目的。
        结构体就是将多个变量组合为一个有机的整体。上面的函数,BaudRate,wordlength,Parity,mode,wordlength这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个。MDK中是这样定义的:

typedef struct
{
    uint32_t USART_BaudRate;
    uint16_t USART_WordLength;
    uint16_t USART_StopBits;
    uint16_t USART_Parity;
    uint16_t USART_Mode;
    uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;

        于是,我们在初始化串口的时候入口参数就可以是USART_InitTypeDef类型的变量或者指针变量了,MDK中是这样做的:

void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

        这样,任何时候,我们只需要修改结构体成员变量往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义就可以达到增加变量的目的。
        在以后的开发过程中,如果你的变量定义过多,如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可以提高你的代码的可读性

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值