MDK 下 C 语言基础复习

1.1 位操作
C 语言支持如下 6 中位操作
运算符 含义 运算符 含义
& 按位与 ~ 取反
| 按位或 << 左移
^ 按位异或 >> 右移

  1. 不改变其他位的值的状况下,对某几个位进行设值。
    这个场景单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操
    作,然后用|操作符设值。比如我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零
    操作
    GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0
    然后再与需要设置的值进行|或运算
    GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值
  2. 移位操作提高代码的可读性。
    移位操作在单片机开发中也非常重要,下面让我们看看固件库的 GPIO 初始化的函数里
    面的一行代码
    GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
    这个操作就是将 BSRR 寄存器的第 pinpos 位设置为 1,为什么要通过左移而不是直接
    设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可
    以很直观明了的知道,是将第 pinpos 位设置为 1。如果你写成
    GPIOx->BSRR =0x0030;
    这样的代码就不好看也不好重用了。
    类似这样的代码很多:
    GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位
    这样我们一目了然,5 告诉我们是第 5 位也就是第 6 个端口,1 告诉我们是设置为 1
    了。
  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 位
    了,可读性非常强。
    1.2 define 宏定义
    define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供
    方便。常见的格式:
    #define 标识符 字符串
    “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:
    #define SYSCLK_FREQ_72MHz 72000000
    定义标识符 SYSCLK_FREQ_72MHz 的值为 72000000。
    1.3 ifdef 条件编译
    单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而
    当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
    #ifdef 标识符
    程序段 1
    #else
    程序段 2
    #endif
    它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,
    否则编译程序段 2。 其中#else 部分也可以没有,即:
    #ifdef
    程序段 1
    #endif
    这个条件编译在 MDK 里面是用得很多的,在 stm32f10x.h 这个头文件中经常会看到这样的语
    句:
    #ifdef STM32F10X_HD
    大容量芯片需要的一些变量定义
    #end
    而 STM32F10X_HD 则是我们通过#define 来定义的。
    1.4 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
    }
    但是我们希望在 test.c 的 changeId(void)函数中使用变量 id,这个时候我们就需要在
    test.c 里面去申明变量 id 是外部定义的了,因为如果不申明,变量 id 的作用域是到不了
    test.c 文件中。看下面 test.c 中的代码:
    extern u8 id;//申明变量 id 是在外部定义的,申明可以在很多个文件中进行
    void test(void){
    id=2;
    }
    在 test.c 中申明变量 id 在外部定义,然后在 test.c 中就可以使用变量 id 了。
    对于 extern 申明函数在外部定义的应用,这里我们就不多讲解了。
    1.5 typedef 类型别名
    typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。
    typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。
    struct _GPIO
    {
    __IO uint32_t CRL;
    __IO uint32_t CRH;

    };
    定义了一个结构体 GPIO,这样我们定义变量的方式为:
    struct _GPIO GPIOA;//定义结构体变量 GPIOA
    但是这样很繁琐,MDK 中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别
    名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变量了。
    方法如下:
    typedef struct
    {
    __IO uint32_t CRL;
    __IO uint32_t CRH;

    } GPIO_TypeDef;
    Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过 GPIO_TypeDef 来定义结构
    体变量:
    GPIO_TypeDef _GPIOA,_GPIOB;
    这里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。 这样是不是方便很多?
    1.6 结构体
    声明结构体类型:
    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);
    这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需
    要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义
    就可以达到增加变量的目的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智商已欠费.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值