前言
上一级学会了如何新建一个工程,在文档中,下一章是一些MDK5的使用技巧,其实说实话,用惯了那些高级傻瓜式的开发工具,例如IDEA,Pycharm,Android Studio等等,用MDK5倒是有点不习惯了,有点像刚学C的时候,不过用惯之后,还是觉得挺好的。
闲话少说,这系列博客主要是用来记录学习,STM32F407中的一些基础知识,主要包括,C语言复习,STM32系统与时钟,IO,中断等等。
本篇主要复习C语言
这一部分极其枯燥,但是十分重要,望仔细学习
学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf
正点原子,感谢原子哥的开源奉献
正点原子资料下载中心
STM32单片机学习资料均来自 正点原子 ,仅用于学习,如有侵权请联系我删除
本博客内容原创,创作不易,转载请注明
本文链接
个人博客:https://ronglin.fun/?p=96
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/121306695
开发主要用C语言, 这一部分,主要是MDK 下 C 语言基础复习,那些基础语法不在复述,主要是一些在初学过程中用的少的部分。
位运算
有过oj刷题经验的童鞋对位运算应该不陌生,有很多算法基于位运算优化,同时由于计算机的数据存储是二进制,所以说用位操作二进制数很常见,这里简单的过一下
C中6种位操作如下
位设值
很多情况下,我们要不改变其他位的值的状况下,对某几个位进行设值。
方法就是先对需要设置的位用**&操作符进行清零操作,然后用|操作符设值。
比如要改变 GPIOA-> BSRRL
的状态,可以先对寄存器的值进行&**清零操作
GPIOA-> BSRRL &=0XFF0F; //将第 4-7 位清 0
然后再与需要设置的值进行**|**或运算
GPIOA-> BSRRL |=0X0040;//设置相应位的值,不改变其他位的值
移位
移位操作提高代码的可读性,移位操作在单片机开发中也非常重要,我们来看看下面一行代码
GPIOx->ODR = (((uint32_t)0x01) << pinpos);
这个操作就是将 ODR 寄存器的第 pinpos 位设置为 1,为什么要通过左移而不是直接设置一个固定的值呢?
其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第 pinpos 位设置为 1。
如果写成
GPIOx->ODR =0x0030;
这样的代码就不好看也不好重用了。
取反
**~**取反操作使用技巧
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。
至于 define 宏定义的其他一些知识,本质类似于查找替换,比如宏定义带参数这里我们就不多讲解。
ifdef 条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
条件编译命令最常见的形式为:
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有。
这个条件编译在MDK里面是用得很多的,在stm32f4xx.h这个头文件中经常会看到这样的代码。
关键字
extern 变量申明
extern在我的学习过程中用的很少,因为初学C的时候,就main.c一个文件,没有其他文件,而且代码量也很少,所以说这里简单的说明一下在STM32中C语言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 了。
再看如下网上的一个例子
#include <stdio.h>
int max(int x,int y);
int main(void)
{
int result;
/*外部变量声明*/
extern int g_X;
extern int g_Y;
result = max(g_X,g_Y);
printf("the max value is %d\n",result);
return 0;
}
/*定义两个全局变量*/
int g_X = 10;
int g_Y = 20;
int max(int x, int y)
{
return (x>y ? x : y);
}
代码中,全局变量 g_X 与 g_Y 是在 main 函数之后声明的,因此它的作用范围不在 main 函数中。如果我们需要在 main 函数中调用它们,就必须使用 extern 来对变量 g_X 与 g_Y 作“外部变量声明”,以扩展全局变量的作用域。也就是说,如果在变量定义之前要使用该变量,则应在使用之前加 extern 声明变量,使作用域扩展到从声明开始到本文件结束。
对于 extern 申明函数在外部定义的应用,这里我们就不多讲解了。
typedef 类型别名
typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。
就是给数据类型起一个另外的名字,例如,我们在c语言定义一个64位整型,用long long
,很长,这里我们就可这样写
typedef long long ll;
ll a = 0;
而在C中,结构体变量的定义前面要加了一个struct
关键字,很麻烦,用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
是等同的作用了。 在C中方便很多
结构体
在面向对象中,有类的概念,和C的结构体很像,大体上的思想类似,但是没有那么多的讲究
在MDK中太多地方使用结构体以及结构体指针,这里稍微提一下结构体的一些知识。
声明结构体类型:
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;
上面讲解了结构体和结构体指针的一些知识,其他的什么初始化这里就不多讲解了。
总结
没想到一个C语言复习模块写了这么多东西,不过还是很常用的,未完待续,=w=