STM32F103C8T6 学习笔记摘要(二)

第一节 STM32F103C8T6最小系统

1. 复位电路

单片机在正常运行时,由于外界干扰等因素可能会使单片机程序陷入死循环状态或"跑飞"状态。若要使其进入正常状态,唯一的办法是将单片机复位,以重新启动。复位就是把单片机当前的运行状态恢复到起始状态的操作,其作用是复位单片机的程序计数器PC,使单片机从代码存储器0x00000000单元重新开始执行程序,并将相关寄存器复位到默认初始值

  • 开关:C318938
  • 电容:C61119867
  • 电阻:C25805
  • 这个芯片是:STM32F103C8T6

2. 时钟电路

晶振电路为单片机提供时钟信号。STM32有两组晶振

OSC_IN/OSC_OUT: 外部HSE晶振引脚,用于给STM32提供高精度系统时钟。高速外部时钟(HSE)可以使用一个4~16MHz的晶体/陶瓷谐振器构成的振荡器产生。为了程序内部倍频方便,一般选用8MHz的晶振

**注意:**在PCB布线时晶振应尽量靠近芯片。晶振底部不要进行布线,防止其他信号干扰晶振

准备电子元器件:

  • 电容:C1648 高速外部晶振:C21263 低速外部晶振:C2236

3. boot启动电路

在STM32单片机里,可以通过BOOT[1:0]引脚选择三种不同启动模式,如下所示:

准备电子元器件: 电阻:C23162 开关:C318938


注意: BOOT1对应芯片上的引脚位置

4. 滤波电容/添加电源

5. swd调试

6. 5v转3.3

7. usb转串口

TYPE-C

  • 这个5v是我们从电脑输入板子的,就是那根板子和电脑连接的线
  • 但这个元器将还要连个小芯片

CH340

  • 输入输出交叉连接

  • 加上转接符表示,这根线即可以当串口,又可以当GPIO使用
  • 这2根线需要根芯片上的那2个反着连

  • 加上这个二极管表示,防止数据回流

8. 引出导线

9. 添加按键与LED

第二节 了解GPIO/STM32 芯片

GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能 。不过GPIO 最简单的应用还属点亮 LED 灯了,只需通过软件控制 GPIO 输出高低电平即可。当然GPIO 还可以作为输入控制,比如在引脚上接入一个按键,通过电平的高低判断按键是否按下。我们开发板上使用的 STM32 型号是 STM32F103C8T6,此芯片共有48 引脚,芯片引脚图

如下图所示 :

那么是不是所有引脚都是 GPIO 呢?当然不是,STM32 引脚可以分为这么几大类:

  • 电源引脚:引脚图中的 VDD、VSS、VSSA、VDDA 等都属于电源引脚。
  • 晶振引脚:引脚图中的 PC14、PC15 和 OSC_IN、OSC_OUT 都属于晶振引脚,不过它们还可以作为普通引脚使用。
  • 复位引脚:引脚图中的 NRST 属于复位引脚,不做其他功能使用。
  • 下载引脚:引脚图中的 PA13、PA14、PA15、PB3 和PB4 属于JTAG或SWD 下载引脚。不过它们还可以作为普通引脚或者特殊功能使用,具体的功能可以查看芯片数据手册,里面都会有附加功能说明。当然,STM32 的串口功能引脚也是可以作为下载引脚使用。
  • BOOT 引脚:引脚图中的 BOOT0 和 PB2(BOOT1)属于BOOT 引脚,PB2还可以作为普通管脚使用。在 STM32 启动中会有模式选择,其中就是依靠着BOOT0和 BOOT1 的电平来决定。 (6)GPIO 引脚:引脚图中的 PA、PB、PC、PD 等均属于GPIO 引脚。从引脚图可以看出,GPIO 占用了 STM32 芯片大部分的引脚。并且每一个端口都有16个引脚,比如 PA 端口,它有 PA0-PA15。其他的 PB 等端口是一样的。

1. GPIO的结构框图

2. GPIO的主要特点

  1. 不同型号,IO口的数量可能不一样
  2. 快速翻转。最快可以达到每2个时钟周期翻转一次。(STM32F1系列最快可以达到50MHz的翻转速度)。
  3. 每个IO都可以作为外部中断
  4. 支持8种工作模式

3. GPIO的8种工作模式

GPIO端口的每个位(引脚)可以由软件分别配置成8种模式,当然对同一个引脚同一时间只能处于某一种模式中

输入浮空(Input floating)

说明一下:

  • 如果VDD上面的那个开关闭合了,就是输入上拉(Input pull-up),此时内部是高电压
  • 如果VSS下面的那个开关闭合了,就是输入下拉(Input-pull-down),此时内部是低电压
  • 模拟输入(Analog)一般和ADC/DAC有关

处理输入,还有输出:

  • 通用开漏输出(Output open-drain)
  • 通用推挽式输出(Output push-pull)
  • 推挽式复用功能(Alternate function push-pull)
  • 开漏复用功能(Alternate function open-drain)

4. 与GPIO相关的7个寄存器

每个GPIO端口有7个相关的寄存器:

  • Ø 2个32位配置寄存器(GPIOx_CRLGPIOx_CRH
  • Ø 2个32位数据寄存器(GPIOx_IDRGPIOx_ODR
  • Ø 1个32位置位/复位寄存器(GPIOx_BSRR
  • Ø 1个16位复位寄存器(GPIOx_BRR
  • Ø 1个32位锁定寄存器(GPIOx_LCKR

4.1 GPIOx_CR端口配置低寄存器

GPIOx_CRL(Port configuration register low),x可以是A-G

该寄存器配置的每个GPIO的 0-7 这个8个位,所以叫低寄存器

4.2 GPIOx_CRH端口配置高寄存器

GPIOx_CRH(Port configuration register high)。

该寄存器配置的是每个端口的 8-15引脚,配置方式和低位寄存器完全一

4.3 GPIOx_IDR端口输入数据寄存器

Port input data register

保留位始终读为0。剩下的分别对应每个引脚的输入值

4.4 GPIOx_ODR端口输出数据寄存器

Port output data register

保留位始终读为0。剩下的分别对应每个引脚的输出值。

4.5 GPIOx_BSRR端口位设置/清除寄存器

Port bit set/reset register

(1)高16位是用清除对应的数据输出寄存器的位(0-15)的值:设置为0不影响,设置为1会清除ODR对应的位的值(置为0)。

(2)低16位是用设置对应的数据输出寄存器的位(0-15)的值:设置为0不影响,设置为1会设置ODR对应的位的值(置为1)

4.6 GPIOx_BRR端口位清除寄存器

这个寄存器具有了GPIOx_BSRR一半的功能:清除

4.7 GPIOx_LCKR端口配置锁定寄存器

Port configuration lock register

该寄存器用来锁定端口位的配置。位[15:0]用于锁定GPIO端口的配置。在规定的写入操作期间,不能改变LCKR[15:0]。当对相应的端口位执行了LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。

每个锁定位锁定控制寄存器(CRL,CRH)中相应的4个位(CNF2位和MODE2位)。

第16位用来激活锁定寄存器,必须按照规定的时序来操作才行: 写1 -> 写0 -> 写1 -> 读0 -> 读1。

对0-15位:

  • Ø 0:不锁定对应端口的配置。
  • Ø 1:锁定对应端口的配置

第三节 第一个LED灯 - 标准库版本

1. 给APB2 GPIOC 时钟使能

cpp 复制代码
#include "stm32f10x.h"

int main()
{
  //step1: 给APB GPIOC 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	while(1){
	}
	
	return 0;
}

2. 将GPIO13的工作模式 设置为推挽输出50MHZ

cpp 复制代码
#include "stm32f10x.h"

int main()
{
  //step1: 给APB GPIOC 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	//step2:将GPIO13的工作模式 设置为推挽输出50MHZ
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Pin = GPIO_Pin_13;
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC,&initDef);
	
	
	while(1){
	}
	
	return 0;
}

3. 将GPIO13设置为底电平

cpp 复制代码
#include "stm32f10x.h"

int main()
{
  //step1: 给APB GPIOC 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	//step2:将GPIO13的工作模式 设置为推挽输出50MHZ
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Pin = GPIO_Pin_13;
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC,&initDef);
	
	//step3: 将GPIO13设置为底电平
	//GPIO_SetBits(GPIOC,GPIO_Pin_13);// 设置高电平
	//GPIO_ResetBites(GPIOC,GPIO_Pin_13);// 清除高电平
	GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
	
	while(1){
	}
	
	return 0;
}

说明一下:

  • GPIO_WriteBit的第三个参数为BIt_RESET时,为重置bit-电平
  • GPIO_WriteBit的第三个参数为BIt_SET时,为设置bit-电平

第四节 了解时钟树

在STM32中有3种不同的时钟源用来驱动系统时钟(SYSCLK):

(1)HSI振荡器时钟(High Speed Internal oscillator,高速内部时钟)

(2)HSE振荡器时钟(High Speed External(Oscillator / Clock),高速外部时钟)

(3)PLL时钟(Phase Locked Loop 锁相环/倍频器)

还有2种2级时钟

(4)LSI时钟(Low Speed Internal,低速内部时钟)

(5)LSE时钟(Low Speed External oscillator,低速外部时钟)

为什么提供这么多的时钟?**节能!**高速设备接高速时钟,低速设备接低速时钟,可以最大程度的达到节能效果。详见下图时钟树

时钟初始化配置函数: SystemInit()

时钟使能配置函数:

cpp 复制代码
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

修改系统时钟:RCC_HSE_Config()

注意: 不要把 STM32 系统时钟设置超过72M使用,否则很容易崩溃

第五节 位带操作

简单来说就是直接操作寄存器中得到bit位

修改对某引脚设置对于值时。有两种方案:

  • 方案1: 调用对于库函数
  • 方案2 : 通过位带操作

就比如说我们的第一个LED灯中的第3步:将GPIO13设置为底电平

本质上就是将那个寄存器中的第13位的bit位改成了0

1. 新建文件/文件夹

2. system.h

cpp 复制代码
#ifndef _H_SYSTEM
#define _H_SYSTEM
#include "stm32f10x.h"
//IO 口地址映射
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C

//把 addr 地址强制转换为 unsigned long 类型的指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
	
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))

//把位带别名区内地址转换为指针 ,获取地址内的数据
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO 口操作,只对单一的 IO 口!
//确保 n 的值小于 16!
#define PAin(n) 	BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBin(n) 	BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCin(n) 	BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDin(n) 	BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEin(n) 	BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFin(n) 	BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGin(n) 	BIT_ADDR(GPIOG_IDR_Addr,n) //输入
#define PAout(n) 	BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PBout(n) 	BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PCout(n) 	BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PDout(n) 	BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PEout(n) 	BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PFout(n) 	BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PGout(n) 	BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#endif

说明一下: 知道怎么用就行了

3. main.c

cpp 复制代码
#include "stm32f10x.h"
//#include "led.h"
#include "system.h"

#define LED1 PCout(13)
int main()
{
	//step1: 给APB GPIOC 时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	//step2:将GPIO13的工作模式 设置为推挽输出50MHZ
	GPIO_InitTypeDef initDef;
	initDef.GPIO_Pin = GPIO_Pin_13;
	initDef.GPIO_Speed = GPIO_Speed_50MHz;
	initDef.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC,&initDef);
	
	//step3:设置为低电平
	PCout(13) = 0;

	while(1){
	}
	
	//return 0;
}

第六节 SysTick 系统定时器

SysTick(System Tick Timer)是 ARM Cortex-M 系列内核中集成的一个简单定时器,主要用于操作系统中的时间片轮转调度(如 RTOS 中的时基)。以下是 SysTick 系统定时器的原理简要说明:

1. 基本组成

SysTick 是一个 24 位 向下递减的计数器,包含以下几个寄存器:

  1. LOAD:装载值寄存器,设定定时器的初始值(最大值为 0xFFFFFF)。

  2. VAL:当前计数值寄存器。

  3. CTRL:控制与状态寄存器,用于启动定时器、中断控制等。

  4. CALIB:校准值寄存器(只读,用于提供基准时间,比如 1ms 对应多少个时钟周期)

2. 工作原理

  • 启动后,SysTick 将 LOAD 中的值交给VAL寄存器开始递减(多线程情况)

  • 每当计数器减到 0:

    • 计数器会自动重装(重新从 LOAD 开始倒数)。

    • 如果开启了中断,会产生一个中断信号(SysTick_Handler)。

  • 中断可以用于实现周期性任务(如每 1ms 调用一次调度器或系统计时器

3. 操作步骤

SysTick 定时器的操作可以分为 4 步: ​

  1. 设置 SysTick 定时器的时钟源
  2. 设置 SysTick 定时器的重装初始值(如果要使用中断的话,就将中断使能打开)。
  3. 清零SysTick 定时器当前计数器的值。
  4. 打开 SysTick 定时器

systick.h

cpp 复制代码
#ifndef _H_SYSTICK
#define _H_SYSTICK
#include "stm32f10x.h"
#include "misc.h"

//初始化延迟函数
void SysTick_Init(u8 SYSCLK);

//延迟多少微秒
void delay_us(u32 us_num);

//延迟多少毫秒
void delay_ms(u16 ms_num);

#endif

SysTick_Init 函数形参 SYSCLK 表示的系统时钟大小,默认配置我们使用的系统时钟是 72M,所以调用这个函数时,形参值即为 72。函数内部调用了一个库函数 SysTick_CLKSourceConfig,此函数用来对 SysTick 定时器时钟的选择,我们使用的 SysTick 定时器时钟是系统时钟的8 分频 ,所以参数是SysTick_CLKSource_HCLK_Div8。如果使用系统时钟作为SysTick 定时器时钟,那么参数即为 SysTick_CLKSource_HCLK。这个函数在misc.c 库文件内,如何查找我们前面介绍过方法。 ​

下面的一条语句是用来求取 SysTick 定时器在 1us 时间内和1ms 时间内的计数次数

systick.c

cpp 复制代码
#include "systick.h"
u8	fac_us;
u16	fac_ms;
//初始化延迟函数
void SysTick_Init(u8 SYSCLK){
	//设置时钟源
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
	
	//计算us,ms滴答次数
	fac_us = SYSCLK/8;
	fac_ms = SYSCLK/8*1000;
}

//延迟多少微秒
void delay_us(u32 us_num){
	
  //初始加载
	SysTick->LOAD = us_num*fac_us; //获取us_num时间内要技术次数
	//初始值
  SysTick->VAL = 0x00; //默认最开始位0
	//开启计数器
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	u32 temp;
	do{
		temp = SysTick->CTRL;
		//temp的第16为为1计数结束
	}while(temp&0x01&&!(temp&1<<16));  
	
	//关闭计数器
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	//清空初始值
	SysTick->VAL = 0x00;
	
}

//延迟多少毫秒
void delay_ms(u16 ms_num){
	//初始加载
	SysTick->LOAD = ms_num*fac_ms; //获取us_num时间内要技术次数
	//初始值
  SysTick->VAL = 0x00; //默认最开始位0
	//开启计数器
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	u32 temp;
	do{
		temp = SysTick->CTRL;
		//temp的第16为为1计数结束
	}while(temp&0x01&&!(temp&1<<16));  
	
	//关闭计数器
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	//清空初始值
	SysTick->VAL = 0x00;
}

说明一下:

  • SysTick_CTRL_ENABLE_Msk就是1

4. main.c

相关推荐
qq_401700411 小时前
stm32移植freemodbus
stm32·单片机·嵌入式硬件
李明一.1 小时前
Java 全栈开发学习:从后端基石到前端灵动的成长之路
java·前端·学习
crary,记忆2 小时前
微前端MFE:(React 与 Angular)框架之间的通信方式
前端·javascript·学习·react.js·angular
星空寻流年2 小时前
javaScirpt学习第七章(数组)-第一部分
前端·javascript·学习
西岭千秋雪_2 小时前
计算机网络学习笔记:应用层概述、动态主机配置协议DHCP
笔记·学习·计算机网络
weixin_448119943 小时前
Datawhale 网络爬虫技术入门第2次笔记
笔记·爬虫
华科易迅4 小时前
人工智能学习57-TF训练
人工智能·学习·人工智能学习57-tf训练
虚!!!看代码4 小时前
【秒杀系统设计】
学习
虾球xz5 小时前
CppCon 2017 学习:Howling at the Moon: Lua for C++ Programmers
开发语言·c++·学习·lua