搜档网
当前位置:搜档网 › STM32学习笔记

STM32学习笔记

常用英文单词
remap:重映射
partialremap:部分重映射
pend:挂起



工程模板的建立可以套用标准模板,只把Main函数改一下就可以了,不需要加入头文件路径、声明宏定义变量、设置调试参数等。一般模板中都含有USER、CORE、FWLIB、OBJ、HARDWARE五个文件夹。USER中放置main函数c文件。CORE中放置启动c和s文件。FWLIB放置固件库C文件。OBJ放置编译后的乱七八糟的生成文件。HARDWARE放置自己写的硬件相关函数的c文件。

赋值操作:
与1或操作可以置某位为1,与0与操作可以置某位为0,要保持其他没操作的位不变才可以。如:
GPROA_>BSRR |=0X01 //不改变前7位的值,将第零位置为1
GPROA_>BSRR &=~0X01 //也不改变前七位的值,将第零位置为0
TIMx->SR = (uint16_t)~TIM_FLAG; //也是把某位置零的操作

写法上的小技巧:
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了。

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

单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的作用是:当 标识符 已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段2。 其中#else部分也可以没有,即:
#ifdef
程序段1
#endif

关于结构体的定义:
标准格式为:
struct _GPIO
{
__IO uint32_t CRL;
__IO uint32_t CRH;

};
使用时用 struct _GPIO GPIOA 来定义其他同样结构的结构体,这里定义了GPIOA结构体,但是觉得使用struct _GPIO XXXX 来定义比较麻烦,所以给结构体重新取了一个名字,格式为:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;

} GPIO_TypeDef;
原来写结构体名的地方不写名字了,在结构体最后写结构体的名字,这样就可以直接使用简单的格式来定义结构体了,如GPIO_TypeDef _GPIOA,_GPIOB 这里定义了两个结构体_GPIOA,_GPIOB
有一个情况跟

上面的在形式上很接近,但实质上不同,如下:
struct _GPIO
{
__IO uint32_t CRL;
__IO uint32_t CRH;

}_GPIOA,_GPIOB;
这是在定义结构体后紧接着定义了结构体变量_GPIOA,_GPIOB,并不是给结构体取了一个新的名称,所以与上面的作用是不同的。

结构体指针变量定义也是一样的,跟其他变量没有啥区别。
例如:struct U_TYPE *usart3;//定义结构体指针变量 usart1;
结构体指针成员变量引用方法是通过“->”符号实现,比如要访问usart3结构体指针指向的结构体的成员变量BaudRate,方法是: Usart3->BaudRate;

使用USB模块时,PLL必须使能

设置系统时钟的理解:
只需调用SetSysClock()函数即可,至于设置成了多少是由系统的宏定义来决定的。如下:
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
}
可以理解为如果宏定义了SYSCLK_FREQ_HSE,那么就执行SetSysClockToHSE()函数,把频率设置为这种频率,如果宏定义了defined SYSCLK_FREQ_24MHz,那么就执行SetSysClockTo24(),把频率设成24M,以此类推。
例如程序中出现了#define SYSCLK_FREQ_72MHz 72000000 ,那么系统在执行完SetSysClock()函数后就会设置成72M
要注意的是,当我们设置好系统时钟后,可以通过变量 SystemCoreClock 获取系统时钟
值,如果系统是72M时钟,那么SystemCoreClock=72000000。这是在 system_stm32f10x.c 文件中设置的:
#ifdef SYSCLK_FREQ_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_HSE;
#elif defined SYSCLK_FREQ_36MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_36MHz;
#elif defined SYSCLK_FREQ_48MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz;
#elif defined SYSCLK_FREQ_56MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz;
#elif defined SYSCLK_FREQ_72MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;
#else
uint32_t SystemCoreClock = HSI_VALUE;
#endif
从这里可以看到,SystemCoreClock变量在程序进行编译时就已经确定赋什么值了。这种在C文件中写宏定义的方式是很常见的。

APB1上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2上面连接的是高速外设包括UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。

我们在使用复用功能的是时候,最少要使能2 个时钟:
1) GPIO时钟使能
2) 复用的外设时钟使能

关于中断:
STM32F103系列只有

60个可屏蔽中断。定义了IPR(中断优先级控制寄存器)组,共15个IPR[x]平均依次分给60各中断作为该中断的设置寄存器。因为IPR是32位寄存器,所以每个中断可以分得8位作为设置寄存器(15*32/60=8)。每个中断只用分得的一个字节的高4位。接下来要确定所有中断的统一的工作模式,包括抢占优先级、响应优先级。这两种工作模式的设置合起来使用高4位。可以分为:
抢占优先级用0位,响应优先级用4位
抢占优先级用1位,响应优先级用3位
抢占优先级用2位,响应优先级用2位
抢占优先级用3位,响应优先级用1位
抢占优先级用4位,响应优先级用0位 共计5中工作模式。模式的设置是通过
AIRCR[]的第八到第十位,即AIRCR[8:10]来设置的,具体见《STM32开发指南-库函数版本》P120有讲解。
每个中断在工作模式确定后便分得了抢占优先级和响应优先级各自的位数,可以设置数字来确定这个中断的优先级。数字越小则优先级越高。
这里需要注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。

中断优先级设置的步骤:
1. 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和子优先级的分配位数。调用函数为NVIC_PriorityGroupConfig();
2. 设置所用到的中断的中断优先级别。对每个中断调用函数为NVIC_Init();

FWLIB固件库包含每个外设所用到的代码,一个外设对应一个c文件和h文件。有时为了程序不要太大,将FWLIB固件库中的部分外设代码有选择的添加到工程。这时需要对加入到工程中的c文件中的stm32f10x_conf.h子文件做适当修改,即注释掉没有添加进工程的外设的头文件包含。参照《STM32开发指南-库函数版本》P147

在给模块写初始化函数时,可以直接在固件库相应模块的c文件中找到其初始化函数(一般都是xxx_Init)之类的。


关于枚举类型enum和typedef enum的理解:
在程序中,可能需要为某些整数定义一个别名,我们可以利用预处理指令#define来完成这项工作,您的代码可能是:
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
在此,我们定义一种新的数据类型,希望它能完成同样的工作。这种新的数据类型叫枚举型。
1. 定义一种新的数据类型 - 枚举型
以下代码定义了这种新的数据类型 - 枚举型
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
(1) 枚举型是一个集合,集合中的元素(枚

举成员)是一些命名的整型常量,元素之间用逗号,隔开。
(2) DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
(3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
(4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
(5) 枚举型是预处理指令#define的替代。
(6) 类型定义以分号;结束。
3. 使用枚举类型的变量
3.1 对枚举型的变量赋值。
实例中将枚举类型的赋值与基本数据类型的赋值进行了对比:
方法一:先声明变量,再对变量赋值
#include<stdio.h>
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
int x, y, z;
x = 10;
y = 20;
z = 30;
enum DAY yesterday, today, tomorrow;
yesterday = MON;
today = TUE;
tomorrow = WED;
printf("%d %d %d \n", yesterday, today, tomorrow);
}
由上面的函数可以知道,枚举类型定义的变量与结构体类型定义的变量十分不同。枚举类型的变量并不是结构体,他只是一个普通的变量,只不过他的取值只能从定义的枚举类型的成员中取值,并且其值只能跟成员相同。比如:yesterday = MON;
那么只能相当于yesterday=1,不能改成yesterday=2;如果想赋值为2,那么应该为:yesterday=TUE;

3.2 对枚举型的变量赋整数值时,需要进行类型转换。
#include <stdio.h>
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
enum DAY yesterday, today, tomorrow;
yesterday = TUE;
today = (enum DAY) (yesterday + 1); //类型转换
tomorrow = (enum DAY) 30; //类型转换
//tomorrow = 3; //错误
printf("%d %d %d \n", yesterday, today, tomorrow); //输出:2 3 30
}

枚举中的成员用逗号“,”隔开,结构体成员用分号“;”隔开

文件及函数的组织结构:
在自己写的文件里,最好要同时写c文件和h文件。在c文件中只写一个#include "xx.h"。其中xx.h就是与此c文件对应的头文件。这样在编译c文件时,编译到xx.c文件时会找到xx.h头文件进行编译。在xx.h文件中一定不能少的是xx.c文件中的自定义的函数的函数声明,即void abc(void);之类的。如果自己写的函数中调用到了库函数,那么就要包含进这部分库函数对应的.h头文件。如果怕麻烦找或者怕找不全,就直接把库函数头文件全部加上,即#include stm32f10x.h。

编译时什么时候找头文件?
应该是编译到某个c文件时,如果该文件中定义了#include “xx.h”,那么系统会从设置的所有头文件路径中找,所以一定要把工程中所有的头文件所在的目录都加入进工程路径中。
如果c文件中调用了某个函数,那么在编译该c文件时就要求在函数调用的位置之前进行声明。因

为函数声明一般(按照习惯)都放在头文件里,所以在c文件中要预先包含进该函数所在c文件对应的h头文件。例如自己建了一个light.c和light.h文件。c文件中有open()函数,h文件中有open()函数的声明。那么如果在main.c文件中调用了open()函数,那就一定要在main.c文件的开头写上#include “light.h”。这样就不会导致在编译时警告函数没有声明了。(declared implicitly)

点开某个c文件的下拉列表,通常可以看到非常多的

函数结尾处要重起一行,否则会有警告



#if 0(判断条件)
...
#endif
用于屏蔽注释中间的代码,如果判断条件为0,则下面的代码相当于注释掉了,不会编译,如果条件成立,则会进行编译,是一种预处理指令,为了缩小代码尺寸。

delay_ms函数要在初始化之后才能使用。

重映射需要设置复用时钟使能和复用功能开启

SPI的时钟具有极性、相位和频率的设置

NSS作用如下:
1.作为输入(SSOE=0),由主机普通I/O口驱动,这是作为从机的片选信号。
2.设备作为输出引脚(使能SSOE位)时,如果配置为主机,则输出低电平。与之相连的设备如果配置为NSS硬件模式(即从NSS引脚输入的模式),则自动成为从机。
3.当配置为主设备,同时NSS不作为输出,而是作为输入(MSTR=1,SSOE=0)时,如果NSS被其他设备拉低,则这个设备进入主机模式失败。处理结果为自动将MSTR位清除,进入从机模式。可以用于广播。
4.硬件模式下,NSS可以作为输出也可以作为输入。软件模式下,只能作为输入,而且是软件的输入,外部NSS引脚无效了。
5.输入输出由SSOE位控制,硬件(使用引脚)软件(使用位)由SSM位控制。

清除SPE位可以关闭SPI通讯

可以通过将整型数赋值给浮点数来进行浮点运算,在运算中如果有浮点数和整数,结果使用浮点数

利用卡死语句让程序在某个地方等待时不可以直接利用while();这样会使得卡死语句运行太快,没有时间进入中断函数。最好的方法是while(){__NOP();} .注意空语句__NOP()前面是两条下划线“_”。















相关主题