STM32编程的c语言基础学习笔记

一、位操作

    运算符  含义  运算符  含义

       &   按位与    ~    取反

       |   按位或    <<   左移

       ^  按位异或   >>   右移

1) 不改变其他位的值的状况下,对某几个位进行设值。
        这个场景单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,然后用|操作符设值。比如我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作:GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0    
        然后再与需要设置的值进行|或运算:GPIOA->CRL|=0X00000040;  //设置相应位的值,不改变其他位的值
2) 移位操作提高代码的可读性。
        移位操作在单片机开发中也非常重要,下面让我们看看固件库的 GPIO 初始化的函数里面的一行代码:    
        GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
        这个操作就是将 BSRR 寄存器的第 pinpos 位设置为 1,为什么要通过左移而不是直接设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第 pinpos 位设置为 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位了,

二、define  宏定义

    define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:
        #define 标识符 字符串
    “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:
        #define SYSCLK_FREQ_72MHz 72000000

三、 ifdef  条件编译(#ifdef和#endif)

       单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
        #ifdef 标识符
        程序段 1
        #else
        程序段 2
        #endif
       它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,即:
        #ifdef
        程序段 1
        #endif
    这个条件编译在MDK里面是用得很多的,在stm32f10x.h这个头文件中经常会看到这样的语句:
        #ifdef STM32F10X_HD
        大容量芯片需要的一些变量定义
        #end

四、 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;
        }

五、 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;

又如:typedef struct

    {

        u16 seq_num;

        u16 len;

        u8 dev_id[GPRS_PRO_DEVICE_ID];

        u16 command_id;

        u8 *pro_data;

    }gprs_pro_cmd_t;

    然后我们就可以直接使用gprs_pro_cmd_t定义一个结构体变量。

六、  结构体

    声明结构体类型:
        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);

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

七、static关键字

所有未加 static 前缀的全局变量(这里的全局变量指在源文件的开头处,不包含在源文件的任何函数内)和函数都具有全局可见性,其它的源文件也能访问。static声明的函数和变量不能在另一个文件中引用,也就是说,如果加了 static,就会对其它源文件隐藏,无法进行调用。

利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static 可以用作函数和变量的前缀,对于函数来讲,static 的作用仅限于隐藏,而对于变量,static 还有下面两个作用。

  1. 存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。在下次使用静态变量时,则不会在去初始化。
  2. 默认初始化初值为0。在静态存储区共有两种变量存储:全局变量和 static 变量,而且在静态数据区,内存中所有的字节默认值都是0x00

总结:首先 static 的最主要功能是隐藏,其次因为 static 变量存放在静态存储区,所以它具备持久性和默认值0。

    函数分为内部函数和外部函数。当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。

    内部函数(又称静态函数
    如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
    定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
        static 函数类型 函数名(函数参数表){……}
    关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
    使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
    外部函数
    外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
        [extern] 函数类型 函数名(函数参数表){……}
    调用外部函数时,需要对其进行说明:
        [extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
        [案例]外部函数应用。
        ⑴文件mainf.c
        main()
        {
            extern void input(…),process(…),output(…);
            input(…);
            process(…);
            output(…);
        }
        ⑵文件subf1.c
        ……extern void input(……) /*定义外部函数*/{……}
        ⑶文件subf2.c
        ……extern void process(……) /*定义外部 函数*/{……}
        ⑷文件subf3.c
        ……extern void output(……) /*定义外部函数*/{……}
    static的作用
    在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
    (1)先来介绍它的第一条也是最重要的一条:隐藏。
    当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。
    下面是a.c的内容
        char a = 'A'; // global variable
        void msg() {
        printf("Hello\n");
        }
    下面是main.c的内容    

1

2

3

4

5

6

7

int main(void)

{

extern char a; // extern variable must be declared before use

printf("%c ", a);

(void)msg();

return 0;

}

    程序的运行结果是:
        A Hello
    你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
    如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
    (2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <stdio.h>

int fun(void)

{

static int count = 10; // 此语句只在函数第一次调用时执行,后续函数调用此变量的初始值为上次调用后的值,每次调用后存储空间不释放

return count--;

},

int count = 1;

int main(void)

{

printf("global\t\tlocal static\n");

for(; count <= 10; ++count)

printf("%d\t\t%d\n", count, fun());

return 0;

}

    程序的运行结果是:
    global local static
        1 10
        2 9
        3 8
        4 7
        5 6
        6 5
        7 4
        8 3
        9 2
        10 1
    (3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。不妨做个小实验验证一下。

1

2

3

4

5

6

7

8

#include <stdio.h>

int a;int main(void)

{

int i;

static char str[10];

printf("integer: %d; string: (begin)%s(end)", a, str);

return 0;

}

    程序的运行结果如下
        integer: 0; string: (begin)(end)
    最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。

八、C 语言中全局变量、局部变量、静态全局变量、静态局部变量的区别

从作用域看:

1、全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

2、静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

3、局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

4、静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。

从分配内存空间看:

1、全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间

2、全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

  •  1)静态变量会被放在程序的静态数据存储区(全局可见)中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
  •  2)变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

从以上分析可以看出把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。

Tips:

  •  A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
  •  B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
  •  C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;
  •  D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带"内部存储器"功能的的函数)
  •  E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

STM32(三)C语言基础复习

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用内容,STM32C8T6是一款通用增强型的48脚单片机,具有64K闪存和LQFP封装。它适用于工业级温度范围-40~85度。下面是一些关于STM32C8T6学习笔记: 1. 学习资料:可以从ST官方网站下载STM32C8T6的数据手册和参考手册,这些手册包含了该单片机的详细信息和使用方法。 2. 开发环境:为了开始学习STM32C8T6,你需要安装相应的开发环境。ST官方提供了一款免费的集成开发环境(IDE)——STM32CubeIDE,它可以帮助你进行代码编写、调试和下载。 3. 编程语言:STM32C8T6可以使用多种编程语言进行开发,包括C语言和汇编语言。C语言是最常用的编程语言,它可以通过STM32CubeIDE进行编写和调试。 4. 引脚配置:在使用STM32C8T6之前,你需要了解每个引脚的功能和配置。数据手册中有一张引脚功能表,可以帮助你了解每个引脚的用途和配置方法。 5. 时钟配置:STM32C8T6具有多个时钟源和时钟分频器,你需要根据自己的需求配置正确的时钟。时钟配置对于外设的正常工作非常重要。 6. 中断和定时器:STM32C8T6支持中断和定时器功能,这些功能可以帮助你实现各种任务和功能。你可以通过配置中断和定时器来实现外设的响应和定时操作。 7. 外设驱动:STM32C8T6具有丰富的外设,包括GPIO、UART、SPI、I2C等。你可以根据自己的需求选择合适的外设,并学习如何配置和驱动这些外设。 8. 调试和下载:在开发过程中,你可以使用STM32CubeIDE提供的调试功能来调试你的代码。一旦代码调试完成,你可以使用ST-Link或其他下载器将代码下载到STM32C8T6上运行。 希望以上笔记对你学习STM32C8T6有所帮助!如果你有任何进一步的问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值