STM32单片机知识整理
本文最后更新于592 天前,其中的信息可能已经过时,如有错误请发送邮件到525382782@qq.com

第1章.STM32单片机入门知识介绍

1.1什么是嵌入式系统?

嵌入式系统是以应用为中心、以计算机技术为基础、软硬件可裁剪、对功能、可靠性、成本、体积和功耗有严格要求的专用计算机系统。

嵌入式系统和通用计算机系统都属于计算机系统。如图。从系统组成上它们都是软件和硬件组成,工作原理也相同。从硬件上看,两者都是由CPU、存储器、I/O接口和中断系统等部件组成;从软件上看,两者都可以换分为系统软件和应用软件。

嵌入式系统和通用计算机系统也是有很多不同点

1.2嵌入式系统的应用领域

广泛应用于工业控制,交通管理,信息家电,环境工程等

1.3 单片机基本概念

单片机,全称单片微型计算机(Single-Chip Microcomputer),是指集成在一个芯片上的微型计算机。这种计算机系统的体积较小,内部包含中央处理器CPU、随机存储器RAM、只读存储器ROM、定时/计数器和多种I/O接口电路,并在一块集成电路芯片上集成了微型计算机的各种功能部件,具有高性能、低价格的优点。国内经常使用“单片机”这个称呼,国外通常称为“微控制器”,英文缩写MCU(Microcontroller Unit)。

1.4 ARM简介

1.4.1 ARM公司介绍

ARM,全称为Advanced RISC Machines,既指ARM公司,也指ARM处理器内核。ARM公司是全球领先的半导体知识产权(IP)提供商,是一家在微处理器设计领域具有重要影响力和广泛应用的公司,ARM的总部位于英国的剑桥。
ARM设计了大量高性能、廉价、低功耗的RISC(Reduced Instruction Set Computer,精简指令集计算机)处理器及芯片。ARM作为知识产权供应商,本身不直接从事芯片的生产,其将知识产权授权给世界各大半导体厂商后,各大生产商再根据不同的应用领域,加入适当的外围电路,形成自己的ARM微处理器芯片。ARM架构其高效、节能的特性,使得它非常适合在移动设备和嵌入式系统中使用,它的技术广泛应用于各种嵌入式系统设计,从无线通讯、网络和消费娱乐产品到成像、汽车、安全和存储解决方案等领域。ARM架构已经成为当今使用最广泛的32位嵌入式RISC指令集架构,ARM公司已经成为全球RISC标准的缔造者。目前全球超过95%的智能手机和平板电脑都采用了ARM架构

1.4.2 ARM处理器简介 

ARM公司开发了很多系列的处理器内核,以前都是以ARMx(x是数字)进行命名,比如经典的ARM7,ARM9,ARM11等。在早期,还在数字后面加字母后缀,用来进一步明确该处理器支持的特性。比如,ARM7TDMI,T代表Thumb指令集,D代表支持JTAG调试(Debugging),M代表快速乘法器,I代表一个嵌入式ICE模块。

到了ARMv7架构时代,ARM改革了一度使用的冗长命名方法,转为另一种看起来比较整齐的命名法:按照应用等级分成3个类别,并以Cortex作为前缀,而且每一个大的系列又换分若干小的系列。ARM体系结构与ARM内核的对应关系如图

ARM架构进化史

ARM的Cortex系列处理器分为三种:Cortex-ACortex-RCortex-M,每种系列都有其特定的使用场景。

Cortex-A系列处理器:主要用于高性能计算设备,如智能手机、平板电脑、个人电脑和服务器等。它们通常具有较高的时钟频率和更大的存储容量,面向尖端的基于虚拟内存的操作系统和用户应用,因此也被称为应用程序处理器。

Cortex-R系列处理器:专为实时应用程序设计,如实时嵌入式系统中的自动驾驶、工控系统和医疗设备等。这些处理器针对实时系统,面向深层的嵌入式实时应用,能够处理需要快速响应和高可靠性的任务。

Cortex-M系列处理器:专为嵌入式系统设计,用于低功耗、实时控制和物联网设备。它们被广泛应用于各种应用,包括智能家居、汽车电子、医疗设备、工业自动化等领域。Cortex-M系列处理器具有低功耗、高性能、实时性、易于开发等特点,能够满足微控制器领域对于快速且具有高确定性的中断管理,以及低功耗和低成本的需求。

目前市场上比较流行的几大系列微处理器,按性能、功能和处理能力划分如图

STM32F103型号属于cortex-M3系列。

1.5 STM32简介

1.5.1基于Cortex内核的MCU

ARM-Cortex处理器内核是微控制器的中央处理单元(CPU)。完整的基于Cortex的MCU(MicroController Unit)还需要很多其他组件。在芯片制造商得到Cortex处理器内核的使用授权后,它们就可以把Cortex内核用在自己的硅片设计中,添加存储器、外设、I/O及其他功能块,即为基于Cortex的微控制器。不同厂家设计出的MCU会有不同的配置,包括存储器容量、类型、外设等都各具特色。以STM32的Cortex-M3内核为例,Cortex-M3内核和基于Cortex-M3的MCU关系如图。

1.5.2 什么是stm32

STM32是意法半导体(STMicroelectronics)公司推出的32位ARM Cortex-M内核微控制器系列。从字面上来理解,ST代表意法半导体,M是Microelectronics的缩写,而32则表示32位。

1.5.3 stm32产品线介绍

图为STM32产品线 

STM32还可以用于无人机和机器人的控制系统开发,提供高性能的实时控制和传感器处理能力。同时,它也可以用于开发各种嵌入式设备,如测试仪器、智能卡等。

但stm32也存在局限,如在程序代码较大基于linux或android需要极高计算能力的场景下,使用起来不适合。

stm32的简单分类图

根据上表,可见STM32 主要分两大类,MCU 和 MPU,MCU 就是我们常见的STM32微控器,不能跑 Linux,而 MPU 则是 ST 在 19 年才推出的微处理器,可以跑 Linux

由于 STM32 系列有很好的兼容性,我们只要能够熟练掌握其中一任何一款 MCU,就可以很方便的学会并使用其他系列的 MCU。比如学好了 STM32F103,再去学 F4/F7/H7 就比较容易学会,由于 STM32F103 系列最早推向市场,资料和教程都是最多的,在市场上的使用也是最为广泛,所以对于没有接触过 STM32 的初学者来说,建议先学习 STM32F103,再去学习其他的 STM32 系列。
STM32  的产品名字里面包含了:家族、类别、特定功能、引脚数、闪存容量、封装、温度范围等重要信息,这些信息可以帮助我们识别和区分 STM32 不同芯片。

图为stm32的命名规则

第2章.STM32开发C语言常用知识点 

2.1. STM32嵌入式开发C语言编程的不同

根据嵌入式的需要,stm32应用于各种场景,需要自己设计一套c语言来适配。

2.2. C语言常用知识点

常见的位操作运算符有&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。建议学习stm32前补习一下c的知识。

2.2.1.如何改变某些特定位,而不改变其他位?

GPIOA->CRL &= 0XFFFFFF0F;    /* 将高 4~7位清 0 */ 
/*然后再与需要设置的值进行|或运算:*/ 
GPIOA->CRL |= 0X00000040;    /* 设置相应位的值(4),不改变其他位的值 */ 

2.2.2.移位操作还提高代码的可读性

SysTick->CTRL |= 1 << 1; /*这是delay库函数里面的一段代码*/
SysTick->CTRL |= 0X0002; /*上面与下面对比可以看见,上面的代码逻辑是设置最低位为1,向左高位移动一位,让第二位置1。我们还可以修改左移值,改变想要置1的位置*/

2.2.3.~按位取反操作使用技巧

SysTick->CTRL &= ~(1 << 0) ;    /* 关闭 SYSTICK 注意这里寄存器的初始值为1*/ 
SysTick->CTRL &= 0XFFFFFFFE;        /* 关闭 SYSTICK 上面的代码,可以对想要清零的操作位进行设置*/ 

2.2.4.^按位异或操作使用技巧

GPIOB->ODR ^= 1 << 5;/*让操作位状态翻转,常用于LED闪烁*/

2.2.5.define 宏定义

define 是 C 语言中的预处理命令,它用于宏定义(定义的是常量),后续如果想修改π的值,可以直接在宏定义的地方修改,

#define PIE 3.14159f  //"标识符"为所定义的宏名;"字符串"可以是常数、表达式、格式串等

2.2.6.条件编译

2.2.6.1.#ifdef

当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句,这种语句常常在嵌入式中使用。其中#else 部分也可以没有。

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

2.2.6.2.#ifndef

#ifndef SOME_MACRO  
// 如果 SOME_MACRO 没有被定义,则编译以下代码  
#endif

2.2.6.2.#if !defined

#if !defined(SOME_MACRO)  
// 如果 SOME_MACRO 没有被定义,则编译以下代码  
#endif

这也是检查是否没有定义某个宏的方法,但它使用了!defined操作符.

2.2.7 extern 变量声明

C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。但注意,对于extern声明变量可以多次,但定义只有一次

extern uint16_t speed_x; 

字节=XX

半字=XXXX

字=XXXXXXXX

单片机内部的flash可擦除,常用于代码泄露保护

2.2.8 typedef 类型别名

typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义,例如C99标准中引入的头文件,定义了一组具有固定宽度的整数类型,包括有符号和无符号的8位、16位、32位和64位整数。这些类型分别命名为int8_t、int16_t、int32_t、int64_t(以及对应的无符号类型uint8_t、uint16_t、uint32_t、uint64_t)。在STM32F10x的标准库函数stm32f10x.h中又对这些数据类型进行了重新定义,代码如下:

typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;
typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;

typedef在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO 
{ 
__IO uint32_t CRL; 
__IO uint32_t CRH; 
… 
}; 

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

struct  _GPIO  gpiox;       /* 定义结构体变量 gpiox */ 

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

typedef struct 
{ 
__IO uint32_t CRL; 
__IO uint32_t CRH; 
… 
} GPIO_TypeDef; 

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

GPIO_TypeDef gpiox;

这里的 GPIO_TypeDef 就跟 struct  _GPIO 是等同的作用了,但是 GPIO_TypeDef 使用起来方便很多。

2.2.9结构体

 在C语言中,结构体(struct)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合成一个单独的数据结构。结构体可以用来表示一个具有复杂属性的实体,比如一个人(具有姓名、年龄、性别等属性)或者一本书(具有书名、作者、出版日期等属性)。

2.2.9.1 结构体的声明和定义

/*声明结构体类型: */
    struct 结构体名 
    { 
        成员列表; 
    }变量名列表; 

你可以在声明结构体的时候直接创建结构体变量,也可以先定义结构体类型,然后再创建变量,如下面几种方式都是可以的:

// 直接定义并创建结构体变量  
struct {  
    int age;  
    char name[50];  
} person1;  
 
// 直接定义并创建结构体变量  
struct Person{  
    int age;  
    char name[50];  
} person2; 
  
// 先定义结构体类型,再创建变量  
struct Person {  
    int age;  
    char name[50];  
};  
struct Person person3;

2.2.9.2 结构体的声明和定义

要访问结构体变量的成员,你需要使用.运算符(对于结构体变量)或->运算符(对于指向结构体的指针)。

/*接前面章节2.2.9.1的示例代码*/
// 访问结构体变量的成员  
person1.age = 25;    
  
// 如果有一个指向结构体的指针  
struct Person *ptr = &person2;  
ptr->age = 30; 

2.2.9.3 结构体的作用

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

 void usart_init(uint8_t usartx, uiut32_t BaudRate, uint32_t Parity,  
uint32_t Mode); 

这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里面再传入一个/几个参数,那么势必我们需要修改这个函数的定义,重新加入新的入口参数,随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?
我们使用结构体参数,就可以在不改变入口参数的情况下,只需要改变结构体的成员变量就可以达到改变入口参数的目的。
结构体就是将多个变量组合为一个有机的整体,上面的函数usartx,BaudRate,Parity,Mode等这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个。MDK中是这样定义的:

typedef struct 
{  
uint32_t BaudRate; 
uint32_t WordLength; 
uint32_t StopBits; 
uint32_t Parity; 
uint32_t Mode; 
uint32_t HwFlowCtl; 
uint32_t OverSampling;    
} UART_InitTypeDef; 

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

void usart_init(UART_InitTypeDef *huart); 

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

2.2.9.4 结构体成员的内存分布与对齐

  • 声明一个结构体类型的时候是没有为它分配任何存储空间的,只有在定义结构体变量的时候,才会为变量分配存储空间。
  • 结构体中可以有不同的数据类型成员,成员在定义时依次存储在内存连续的空间中,结构体变量的首地址就是第一个成员的地址,内存偏移量就是各个成员相对于第一个成员地址的差(即,把低位内存分配给最先定义的变量)。
  • 理论上,结构体所占用的存储空间是各个成员变量所占的存储空间之和,但是为了提高CPU的访问效率,采用了内存对齐方式
  • ①结构体的每一个成员起始地址必须是自身类型大小的整数倍,若不足,则不足部分用数据填充至所占内存的整数倍。
  • ②结构体大小必须是结构体占用最大字节数成员的整数倍,这样在处理数组时可以保证每一项都边界对齐根据上面的说明,我们举例子分析如下:
    struct test 
        { 
            char a; 
            int b; 
            float c; 
            double d; 
        }mytest; 

这个结构体所占用的内存怎么算呢?理论结果为17,实际上并不是17,而是24。为什么会这样呢?这个就是前面我们说的内存对齐。

char型变量占1个字节(并不是4的倍数),所以它的起始地址是0。int类型占用4个字节,它的起始地址要求是4的整数倍数,那么内存地址1、2、3就需要被填充(被填充的内存不适于变量),b从4开始。float类型也是占用4个字节,起始地址要求是4的倍数,所以c的起始地址就是8。double类型变量占用8个字节,起始地址为16,12~15被填充。这里,第一个成员a的地址首地止,第二个成员b的偏移量为4,第三个成员c的偏移量是8,以此类推,是如下图所示:

2.2.10 关键字

在STM32的一些库函数头文件中,经常会看到如下代码, 表示将 volatile 或者 volatile  const 来代替某一个符号。

#define   __I     volatile 
#define   __O     volatile    
#define   __IO    volatile            
#define   __IM     volatile const    
#define   __OM     volatile            
#define   __IOM    volatile 
2.2.10.1 volatile

volatile 表示强制编译器减少优化,告诉编译器必须每次去内存中取变量值。如果程序改变了内存里面的值,寄存器里的值却未改变,加了volatile可以告诉编译器去内存中取改变值,这样确保了数据的准确性,却降低了效率。

2.2.10.2 const

const称为常量限定符,限制变量只读。const修饰的变量存储在只读数据段,在程序结束时释放,而const局部变量存储在栈中,代码块结束时释放。用const定义变量时就要初始化该变量:       

 const int a = 1; 
2.2.10.3 static

这个叫做静态变量,存储在全局区(也叫静态区),如果声明变量时使用未赋值,那么编译器就会自动赋初值为0。

函数内被static声明的变量,仅能在本函数中使用,也叫静态局部变量

在文件内(函数体外)被static声明的变量,仅能被本文件内的函数访问,不能被其他文件中的函数访问,也叫静态全局变量

静态全局变量和普通的全局变量不同,静态全局变量仅限于本文件中使用,在其它文件中可以定义一个与静态全局变量名字相同的变量。普通的全局变量可以通过extern外部声明后被其他文件使用,也就是整个工程可见,而且其他文件不能再定义一个与普通全局变量名字相同的变量了。

局部静态变量
void func() {  
    static int count = 0; // 只在整个程序开始时初始化一次  
    count++;  
    printf("%d\n", count);  
}
全局静态变量
// file1.c  
static int file_scope_var = 42; // 只能在file1.c中访问  
 
// file2.c  
extern int file_scope_var; // 错误:无法在其他文件中访问file_scope_var
静态函数

当在文件级别使用static关键字声明一个函数时,该函数将具有内部链接,即它只能在其定义的文件内被调用。这提供了另一种封装机制,允许你隐藏函数的实现细节,只暴露需要被其他文件使用的函数。

// file1.c  
static void internal_function() {  
    // ...  
}  
 
// file2.c  
extern void internal_function(); // 错误:无法在其他文件中调用internal_function
静态初始化

指针

在STM32这样的嵌入式系统开发中,指针的使用与底层硬件的联系尤为密切。STM32库开发中,我们对寄存器进行了封装,将寄存器放入到结构体(如GPIOX)当中。通过指针,我们可以指向这些结构体的地址,从而访问和操作寄存器,完成对寄存器的配置。

STM32F1x系统架构及资源介绍

补充

什么是轮询?

https://blog.csdn.net/superSmart_Dong/article/details/134065816

微机原理入门

https://blog.csdn.net/2401_84247058/article/details/141968925

https://www.bilibili.com/read/cv18440288

byte

https://blog.csdn.net/ChineseSoftware/article/details/122533596

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
颜文字
Emoji
上一篇
下一篇