stm32标准库学习笔记(基础外设)

stm32标准库学习笔记

——基于STM32F10x标准库中文数据手册、野火指南者库开发手册的学习笔记

/**
 * @file            stm32标准库系统学习笔记
 * @author          jUicE_g2R(qq:3406291309)————彬(bin-必应)
 *						某双流一大学通信与信息专业大一在读的技术彩笔	
 * 
 * @brief           研究stm32一些底层的东西(基于标准库与少量寄存器配置)
 * 
 * @note            参考STM32F10x标准库中文数据手册、野火指南者教程、CSDN部分blog内容
 * @note					 便于开发时资料的查询
 * 
 * @copyright       2023.5
 * @COPYRIGHT			 © 版权所有 原创技术笔记:转载需获得博主本人同意,且需标明转载源
 *
 * @Version         1.0还在学习中
 */
  • UpDate Log:2023.5.18 更新进行中…>>>更新至BxCAN
  • 上传CSDN链接:https://blog.csdn.net/qq_73928885/article/details/131344866?spm=1001.2014.3001.5501

目录

文章目录

0 工程建立

文件夹名存放的文件
DocReadMe,一些工程参考资料
Libraries官方库的source(.c文件)、include(.h文件),startup(.s启动的汇编文件)
HardWare用户编写的片外外设配置文件:如KEY_Confog.c(.h)
Peripheral用户编写的片上外设配置文件:如USART_Config.c(.h)
System用户编写的控制系统内核(外设)的配置文件SysTick.c(.h)与该项目的主控核心
Project存放HEX烧录文件和(.uvproj)工程启动文件
User主控文件main.c(或.h)与stm32f10x_config.h与stm32f10x_it.c(.h)
keilkill.bat

注1:startup根据容量(64k,128k,512k)选择

注2:keil建文件时对芯片的选型不要错了

注3:工程的文件存放具有我自己的个性,不一定代表正确,所以每个程序猿都应具有自己的想法!!!


1 存储器和总线构架

1-1 Icode总线:读指令

1-2 驱动单元

1-2-1 Dcode总线:

注:常量由const修饰的放在内部FLASH,只要是变量就放在内部SRAM

1-2-2 系统总线(S-bus):访问、读写寄存器

1-2-3 DMA总线(非cortex-M3引出,由DMA1与DMA2引出):传输来自外设的数据寄存器,SRAM,FLASH的数据

注:DMA总线与Dcode总线会经过总线矩阵进行仲裁

1-3 被动单元

1-3-1 内部闪存存储器FLASH:放指令的地方

1-3-2 内部SRAM:程序变量、堆栈

拓展:

1-RAM随机存储器random access memory:存储变量-给定一个地址, 可以立即访问到数据(存取的速度与存储单元的位置无关)
SRAM:静态-主要用作高速缓冲存储器,连接使用方便(不需要刷新电路)、工作稳定、存取速度快(约为动态随机存取储存器DRAM的3~5倍)、使用简单
DRAM:动态-内存条,存取慢,便宜

2-ROM只读存储器Read-Only Memory:掉电数据不丢失-如EEPROM

3-FLASH闪存

1-3-3 FSMC:Flexible static memory controller灵活的静态存储控制器

1-3-4 AHB到APB桥:时钟


2 使用指针操作寄存器

/* GPIOB 端口全部输出高电平*/
//1-直接操作地址,将地址强制转换为指针,进而操作指针来达到操作寄存器的目的
*(unsigned int*)(0x4001 0C0C) = 0xFFFF;

//2-宏定义对寄存器指针封装
#define GPIOB_ODR  *(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;

注:0x4001 0C0C 在我们看来是 GPIOB 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变 量,是一个立即数

*要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即 (unsigned int )0x4001 0C0C,然后再对这个指针进行 * 操作


3 GPIO通用输入\输出端口

3-0 GPIO寄存器操作

GPIO的寄存器以及对应功能和使用方法有必要记住

typedef struct { 
//每个GPIO端口有两个32位						配置寄存器
uint32_t CRL;//GPIO 端口配置低寄存器
uint32_t CRH;//GPIO 端口配置高寄存器
//每个GPIO端口有两个32位						数据寄存器
uint32_t IDR;//GPIO 数据输入寄存器
uint32_t ODR;//GPIO 数据输出寄存器

uint32_t BSRR;//GPIO 位设置/清除寄存器
uint32_t BRR;//GPIO  端口位清除寄存器
  
uint16_t LCKR;//GPIO 端口配置锁定寄存器
} GPIO_TypeDef;
寄存器组用途简介
CRL与CRH这一对配置寄存器存放输入输出模式、输出速度配置CRL存放低8位:GPIOx(x=0~7)的配置
IDR与ODR这一对数据寄存器IDR是查看引脚点平状态用的,ODR是引脚电平输出的寄存器I:只读;D:可读可写
BSRR端口位设置/清除寄存器

3-0-0 IDR数据寄存器

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读取存取在IDR寄存器中的引脚电平
//@retval	  		1(Bit_SET)或者 0(Bit_RESET)

3-0-1 ODR数据寄存器

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//向端口输出寄存器写入引脚的电平状态
//@arg					PortVal:操作某个端口的0-15这16个引脚的电平状态	

3-0-2 BRSS位设置/清除寄存器与BRR端口位清除寄存器

寄存器和 ODR 寄存器具有类似的作用,都可以用来设置 GPIO 端口的输出位是 1 还是 0

端口位设置端口位清除
0~15这低16位16~31这高16位

注:操作BRSS高16位效果等同操作BRR低16位(BRR高16位保留不使用),一般用BRR直接对位进行清除操作

//直接操作寄存器
GPIOA->BSRR=1<<1;//对低16位的位1(0~15)进行操作----->效果:使PIN1电平置高
GPIOA->BSRR=1<<(16+1);//对高16位的位1进行操作------>效果:使PIN1清除高电平(变为低电平)

//调用标准库的gpio.c的函数
GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//函数中置高操作用的是BSRR,置低操作用的是BRR
//@arg			BitVal:1(Bit_SET); 0(Bit_RESET)

注:对BRSS与BRR寄存器的操作效果可由标准库的GPIO_SetBits()和函数 GPIO_ResetBits()来完成(转3-2-1)

3-0-3 LCKR端口配置锁定寄存器:GPIO锁定机制——暂时没用到

锁定机制允许冻结IO配置。当在一个端口位上执行了锁定(LOCK)程序,在下一次复位之前,将不能再更改端口位的配置

3-1GPIO与其他

3-1-1 GPIO外设与RCC时钟控制

基于STM32F10x

相关性桥接1桥接2
AHB系统总线APB2APB1
ADC1~ADC3DAC
GPIOA-GPIOGI2C1、I2C2
EXTIIWDG、WWDG
AFIObxCAN
USART->USART1USART2、USART3
SPI->SPI1SPI3/I2S、SPI2/I2S
TIM->高级定时器TIM1、TIM8TIM2 ~ TIM7
UART->UART4、UART5
RTC
USB,PWR,BKP
电源控制PWR
备份寄存器BKP
实时时钟RTC

3-1-2 GPIO外设与EXTI外部中断

所有端口都有外部中断能力。为了使用外部中断线,端口必须配置成输入模式

3-1-3 GPIO的复用功能(AFIO)

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);

STM32上有很多I/O口,也有很多的内置外设如:I2C,ADC,ISP,USART等 ,为了节省引出管脚,这些内置外设基本上是与I/O口共用管脚的,也就是I/O管脚的复用功能。但是STM32还有一特别之处就是:很多复用内置的外设的I/O引脚可以通过 重映射功能 , 从不同的I/O管脚引出,即复用功能的引脚是可通过程序改变的

注1:重映射ReMap解决GPIO引脚占用冲突

/*来自stm32f10x_gpio.c*/
//@arg GPIO_Remap:选择需要重映射的引脚Pin
//@arg NewState:	ENABLE/DISABLE
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);//重映射函数

//调用函数进行重映射:使I/O口重映射开启
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
//注:一般为GPIO_Remap_xxx,特殊的找GPIO的source文件

程序中用到的USART2外设的TX,RX分别对应PA2,PA3,但是我的学习板上的PA2,PA3引脚接了其他设备,但是为了还要用USART2,“RCC_APB2Periph_GPIOD |RCC_APB2Periph_AFIO”就打开了GPIOD重映射功能把USART2设备的TX,RX映射到PD5,PD6上

注2:重映射有要求,不可随意映射!!!

思考:电磁车比赛用的STM32F103C8T6引脚较自己的野火指南者(VE)学习版引脚少了不少,由于前期分配不正确导致许多外设不能按原引脚使用,给硬件猿造成麻烦(其实有些可以重映射),初学者配置引脚别被CubeMX这个软件误导了,要去看 数据手册的引脚定义 !!!!!

3-1-3A 一些思考:

1、STM32中,USART2和TIM2是共用相同IO的,你如何决定这几个IO到底是做USART2还是做TIM2呢?如果你要同时使用USART2和TIM2,该怎么办?

2、不是说使用了IO的复用功能就一定要启动RCC_APB2Periph_AFIO的Clock只有使用了
AFIO的事件控制寄存器(AFIO_EVCR):配置EVENTOUT事件输出
复用重映射和调试I/O配置寄存器(AFIO_MAPR):配置复用功能重映射
外部中断配置寄存器x(AFIO_EXTICRx):配置外设中断线映射
才需要开启AFIO的时钟

3-1-3B¥¥¥ I/O端口重映射(不可随意映射!!!) ¥¥¥

查阅外设的重映射引脚在中文参考手册的P117-121

以重映射适用于多个不同引脚的封装的USART1举例

0=映射前,1=映射后;USART1仅能完全重映射

复用功能USART1_REMAP=0USART1_REMAP=1
TXpA9pB6
RXpA10pB7
部分重映射&完全重映射

部分重映射:功能外设的部分引脚重新映射,还有一部分的引脚是原来的默认引脚

完全重映射:功能外设的所有引脚都重新映射

引脚重映射的配置过程-以UASRT1重映射配置为例子

1-打开重映射AFIO时钟 和 USART1重映射后的I/O口的端口PORT的时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);

2-I/O口重映射开启

GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

3-.配置重映射的引脚

注1:对于需要复用的输入功能,端口必须配置成输入模式(浮空、上拉或下拉)且输入引脚必须由外部驱动

注2:对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏)

注3:对于双向复用功能,端口位必须配置复用功能输出模式(推挽或开漏)。这时,输入驱动器被 配置成浮空输入模式

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//USART_TX发射端
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//USART_RX接收端
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);

3-2 GPIO引脚初始化配置

typedef struct{
  uint16_t GPIO_Pin;  						//引脚           
  GPIOSpeed_TypeDef GPIO_Speed;    //输出速度,一般为50MHZ(输入模式不用配置)||对功耗有要求是须按需设置
  GPIOMode_TypeDef GPIO_Mode;      //模式(很重要,很容易弄混!)
}GPIO_InitTypeDef;

3-2-1控制GPIO输出高低电平(标准库)

//	1(Bit_SET);		0(Bit_RESET)
GPIO_SetBits(GPIO_PORT,GPIO_PIN)   //拉高引脚输出电平
GPIO_ResetBits(GPIO_PORT,GPIO_PIN) //拉低引脚输出电平

3-2-2 关键!!!输入输出模式GPIOMode_TypeDef

TIM配置看下边外设GPIO配置里的内容

//输入模式
- 浮空输入(GPIO_Mode_IN_FLOATING)//KEY,USART_RX,SPI_SCK(从),SPI_MOSI(从),SPI_MISO(主),BxCAN_RX
- 上拉输入(GPIO_Mode_IPU)  			 //默认高电平,当下降沿触发EXTI外部中断
- 下拉输入(GPIO_Mode_IPD)				 //默认低电平,当上升沿触发EXTI外部中断
- 模拟输入(GPIO_Mode_AIN)         //应用ADC模拟输入,或者低功耗下省电

//输出模式
- 开漏输出(GPIO_Mode_Out_OD)		//
- 推挽输出(GPIO_Mode_Out_PP)		//
- 复用开漏输出功能(GPIO_Mode_AF_OD)//I2C_SCL,I2C_SDA
- 复用推挽输出功能(GPIO_Mode_AF_PP)//USART_TX,SPI_SCK(主),SPI_MOSI(主),SPI_MISO(从),BxCAN_TX
3-2-2A 四种输入模式
“ 浮空输入 ”

1 易受干扰

2 芯片复位上电,默认就是浮空状态

浮空最大的特点就是电压的不确定性,它可能是0V,也可能是VCC,还可能是介于两者之间的某个值。多用于外部按键,这样可以减少上下拉电阻对结果的影响

“ 上拉输入,下拉输入 ”

1 有效防干扰

2 用于EXTI外部中断,易于检测与默认设置电平相反的外部输入信号(易于检测上升沿或下降沿这两种脉冲信号)

例1:电磁小车寻迹完成后,需要在布有磁铁的黑色斑马线停下,需要检测片外外设——干簧管的电平变化。认定正常跑车过程中没有外部信号传入,默认将干簧管下拉为低电平。当小车经过地面上的磁铁时,小车上的干簧管导通,单片只因接收到高电平——上升沿触发外部中断,执行停车命令

例2:外部按键KEY——设置为外部输入信号为高电平时,即上升沿触发按键的外部中断去执行用户设定的操作。没有按下时默认电平为低电平,需要设置为下拉输入(无需使用外部中断的按键一般设置为浮空输入模式)

“ 模拟输入 ”:ADC采集电压的输入通道

采集来自外部的0~Vss(即STM32芯片电压3.3V)的电压值

3-2-2B 四种输出模式
“ 开漏输出 ”
特性1:电平转化

注:正常情况高电平时没有驱动能力(即无法输出高电平)
解决方法:给予需要开漏输出的外部电路一个上拉电阻,这样的输出的高电平时Vcc的电压,是完全由用户决定的电压,实现了_电平转换_

特性2:'‘线与’'功能

适用于多个信号线直接连接在一起
只有当所有信号全部为高电平时,合在一起的总线为高电平;
只要有任意一个或者多个信号为低电平,则总线为低电平

“ 推挽输出 ”:推高挽低

1、提高引脚的驱动能力
2、提高电路的负载能力,例如音频放大器或电机驱动器等
3、提高开关速度

注1:开漏输出只连接Vss(0V),而推挽输出通过控制选择连接了VDD(3.3V)或Vss(0V)进行直接输出

注2:开漏具有‘线与’特性,而推挽没有(如果高电平和低电平连在一起,会出现电流倒灌,损坏器件)

“ 复用推挽/开漏输出(AF) ”

片内外设而生的专用输出模式

3-2-2C 片内外设GPIO模式配置一览表

中文参考手册p109

1 片内(上)与片外外设的区别

片内、外设是两个概念,片内指做成芯片的集成电路内部,简称**片内,**片外同理显而易见;外设是外部设备的简称,是指集成电路芯片外部的设备。现在许多芯片在制造时已经能够将部分接口电路和总线集成到芯片内部

1_1 片内外设:集成在芯片内,但是又不属于芯片本身

一些芯片必备的外设,如ADC、I2C、USART等

1_2 片外外设:独立于芯片的外部设备,是开发者设计应用系统添加的部分

满足用户需求的外部设备,如外部按键KEY等

2_1 高级定时器TIM1/TIM8
TIM1/TIM8引脚功能配置GPIO配置
CHx输入捕获通道x浮空输入
CHx输出比较通道x推挽复用输出
CHxN输出比较通道x推挽复用输出
BKIN刹车输入浮空输入
ETR外部触发时钟输入浮空输入
2_2 通用定时器TIM2/3/4/5
TIM2/3/4/5引脚功能配置GPIO配置
CHx输入捕获通道x浮空输入
CHx输出比较通道x推挽复用输出
ETR外部触发时钟输入浮空输入
2_3 基本定时器TIM6/7

基本定时器只具备最基本的定时功能,就是累加的时钟脉冲数超过预定值时,能触发中断或触发DMA请求

2_4 USART
USART引脚功能配置GPIO配置
TX全双工、半双工同步模式推挽复用输出
RX全双工模式浮空输入或带上拉输入
RX半双工同步模式未用,可作为通用I/O
CK同步模式推挽复用输出
RTS硬件流量控制推挽复用输出
CTS硬件流量控制浮空输入或带上拉输入
2_5 SPI
SPI引脚功能配置GPIO配置
SCK主模式推挽复用输出
SCK从模式浮空输入
MOSI全双工模式/模式推挽复用输出
MOSI全双工模式/从模式浮空输入或带上拉输入
MOSI简单的双向数据线/主模式推挽复用输出
MOSI简单的双向数据线/主模式未用,可作为通用I/O
MISO全双工模式/模式浮空输入或带上拉输入
MISO全双工模式/从模式推挽复用输出
MISO简单的双向数据线/主模式未用,可作为通用I/O
MISO简单的双向数据线/从模式推挽复用输出
NSS硬件主/从模式浮空输入或带上拉输入或带下拉输入
NSS硬件主模式/NSS输出使能推挽复用输出
NSS软件模式未用,可作为通用I/O
2_6 I2S
I2S引脚功能配置GPIO配置
WS主模式推挽复用输出
WS从模式浮空输入
CK主模式推挽复用输出
CK从模式浮空输入
SD发送器推挽复用输出
SD接收器浮空输入或带上拉输入或带下拉输入
MCK主模式推挽复用输出
MCK从模式未用,可作为通用I/O
2_7 I2C
I2C引脚功能配置GPIO配置
SCL时钟线开漏复用输出
SDA数据线开漏复用输出
2_8 BxCAN
BxCAN引脚功能配置GPIO配置
CAN_TX发送端推挽复用输出
CAN_RX接收端浮空输入或带上拉输入

3-3 GPIO输出速度GPIOSpeed_TypeDef

1、STM32配置的I/O引脚的输出速度不是输出信号的速度而是I/O口驱动电路的响应速度(即信号变换的速度——如:高电平转化为低电平的速度)

2、频率越高,电平改变的折线的越陡,电平改变的就越快

3、频率越高,功耗越高;例如:点亮一个LED灯没必要上50MHZ的输出速度,降低输出速度达到节能的效果
但是通信时,对引脚高低电平的变换要求高

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

4 RCC时钟(单片机的心脏)

4-1 时钟源

三种不同的时钟源可被用来驱动系统时钟(SysCLK)
● HSE振荡器时钟
● HSI振荡器时钟
● PLL时钟

4-2 RCC时钟的作用

  1. 设置系统时钟 SYSCLK
  2. 设置 AHB 分频因子(决定 HCLK 等于多少)
  3. 设置 APB2 分频因子(决定 PCLK2 等于多少)
  4. 设置 APB1 分频因子(决定 PCLK1 等于多少)
  5. 设置各个外设的分频因子
  6. 控制 AHB、APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启

注:时钟的一般配置为PCLK2 = HCLK = SYSCLK = PLLCLK = 72M;PCLK1 = HCLK / 2 = 36M(最高)
(高速时钟桥接线APB2设置为72MHZ,低速时钟桥接线APB1设置为36MHZ)

4-3 时钟树

时钟树的图丢失时见中文参考手册P56

4-3-1 HSE 高速 外部 时钟信号

HSE 最常使用的就是8M的无源晶振

//HSE作为时钟来源,经过PLL倍频作为系统时钟SYSCLK
void HSE_SetSysCLK(uint32_t RCC_PLLMul_x);//x为倍频因子
//注:PLLMul常为9:即8MHZ * 9(倍频) = 72MHZ(SYSCLK)

4-3-2 HSI 高速 内部 时钟信号

主要缺点是不稳定
频率为8M,根据温度和环境的情况频率会有漂移,一般不作为 PLL 的时钟来源,是HSE无法正常开启的无奈之选

4-3-3 PLL锁相环

4-3-3-A PLL时钟源

PLL 时钟来源可以有两个,一个来自 HSE,另外一个是 HSI / 2

注:如果HSI要作为PLL时钟的来源的话,必须二分频之后才可以,即HSI / 2,而PLL倍频因子最大只能是16;所以当使用HSI的时候,SYSCLK最大只能是4MHZ x 16=64MHZ

4-3-3-B PLLCLK锁相环时钟

经过HSE或HSI这两个时钟源倍频后的产物

4-3-4 SYSCLK 系统时钟

系统时钟来源可以是:HSI、PLLCLK、HSE

一般设置为SYSCLK = PLLCLK = 72M(将PLLCLK切换成SYSCLK)

4-3-5 接外设的时钟线

AHB系统总线(总线时钟 HCLK)

|————————>外设高速时钟线APB2(总线时钟PCLK2)
|
|————————>外设低速时钟线APB1(总线时钟PCLK1)

注:挂载在这些线上的外设详看3-1-1

4-3-5-A HCLK

系统时钟 SYSCLK 经过 AHB 预分频器分频之后得到时钟叫 APB 总线时钟,即 HCLK

4-3-5-B PCLK2

APB2 总线时钟 PCLK2 由 HCLK 经过高速 APB2 预分频器得到

4-3-5-C PCLK1

APB1 总线时钟 PCLK1 由 HCLK 经过低速 APB 预分频器得到

4-4 使用HSE时,设置系统时钟的步骤

void HSE_SetSysCLK(uint32_t RCC_PLLMul_x)//配置系统时钟
//做准备工作
RCC_DeInit();// 重置RCC时钟配置为默认复位状态,这句是必须的
  • 1、开启(使能)HSE ,并等待 HSE 稳定

    RCC_HSEConfig(RCC_HSE_ON);// 使能HSE,开启外部晶振,野火开发板用的是8M
    /* 判断HSE是否就绪 */
    HSEStartUpStatus=RCC_WaitForHSEStartUp();//返回成功/失败
    if(HSEStartUpStatus==SUCCESS)//HSE启动成功
    
    //在HSE启动成功的条件下执行操作FLASH的准备操作
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// 使能FLASH预存取缓冲区(使能预取指)
    FLASH_SetLatency(FLASH_Latency_2);// 设置为两个等待
    
  • 2、设置 AHB、APB2、APB1的预分频因子

    RCC_HCLKConfig(RCC_HCLK_Div1);// HCLK = SYSCLK = 72MHZ
    RCC_PCLK2Config(RCC_HCLK_Div1);// PCLK2 = HCLK = 72MHZ
    RCC_PCLK1Config(RCC_HCLK_Div2);// PCLK1 = HCLK/2 = 36MHZ
    
  • 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置

    /* 配置锁相环:
    1-配置锁相环的时钟来源的分频因子:HSE_Div1(HSE不分频为8MHZ);
    2-倍频因子PLLCLK = HSE * 9 = 72 MHz;*/
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_x);
    
  • 4、开启PLL,并等待PLL稳定

    RCC_PLLCmd(ENABLE);//使能锁相环PLL
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);//如果PLL的状态处于重置RESET就循环,SET准备就绪跳出循环
    
  • 5、把PLLCLK切换为系统时钟SYSCLK

    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//配置PLL锁相环作为系统时钟SYSCLK的source来源
    
  • 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟

//等待PLL锁相环切换为系统时钟
while(RCC_GetSYSCLKSource()!=0x08);//没成功设置为系统时钟就一直循环
//对于HSE启动失败的补救措施,即在:if(HSEStartUpStatus!=SUCCESS)条件下
else{//HSE没成功启动用户可以在下里添加处理错误的代码
	HSI_SetSysCLK(uint32_t RCC_PLLMul_x);//可以换用HSI作为时钟源
}
void HSI_SetSysCLK(uint32_t RCC_PLLMul_x);//略...

4-5 其他时钟

4-5-1 ADC时钟(重点!!!)

ADC1~ADC3都挂载在APB2(系统时钟PCLK2)上

ADC 时钟由 PCLK2 经过 ADC 预分频器【2/4/6/8分频】得到

ADC 时钟理论最高只能是14M,但是一般设置PCLK=72MHZ,则实际最大为12MHZ(6分频:72M(PCLK2) / 6 = 12M)

ADC转换时间公式

ADC 的转换时间跟 ADC 的采样时间输入时钟有关:
T c o n v = 采样时间 + 12.5 个周期 Tconv = 采样时间 + 12.5 个周期 Tconv=采样时间+12.5个周期
T = 1 / ADC_CLK = 1 / 12 us

采样时间最短为1.5个周期,则转换时间Tconv = 14个ADC周期 = 14 / 12 us =1.17us(常用转换时间)

4-5-2 USB时钟

USB 的时钟最高是 48M,根据分频因子反推过来算,PLLCLK 只能是 48M 或者是 72M。一般我们设置 PLLCLK=72M, USBCLK=48M

注:USB对时钟要求比较高,所以 PLLCLK 只能是由 HSE 倍频得到,不能使用 HSI 倍频

4-5-3 Cortex时钟

系统时钟由 HCLK 8分频得到,等于 9M,Cortex 系统时钟用来驱动内核的系统滴答定时器SysTick

4-5-4 RTC时钟、独立看门狗时钟

RTC 时钟可由 HSE/128 分频得到,也可由低速外部时钟信号 LSE 提供,频率为 32.768KHZ,也可由低速内部时钟信号 LSI 提供

独立看门狗的时钟只能由LSI提供,LSI是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取 40KHZ

4-5-5 MCO(微控制器时钟输出引脚)时钟输出

MCO 的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK

作用

1、引脚由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振

2、通过示波器监控 MCO 引脚的时钟输出来验证系统时钟配置是否正确

4-6 HSI 的使用

4-6-1 CSS(时钟安全系统)中断 :保证系统继续工作

当 HSE 故障的时候,如果 PLL 的时钟来源是 HSE,那么当 HSE 故障的时候,不仅 HSE 不能使用,连 PLL 也会被关闭,这个时候系统会自动切换 HSI 作为系统时钟,此时 SYSCLK=HSI=8M, 如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施, 使用 HSI,并把系统时钟设置为更高的频率,最高是 64M,64M 的频率足够一般的外设使用,如: ADC、SPI、I2C 等

4-6-2 HSI 的配置

HSI做PLLCLK的时钟源,必须Div2二分频

//第1/2/4/5步与HSE的配置完全一样
/* 第三步:配置锁相环时钟PLLCLK:
1-配置锁相环的时钟来源的分频因子:HSI(必须要二分频);
2-倍频因子PLLCLK = (8MHZ / 2) * PLLMul */
RCC_PLLConfig(RCC_PLLSource_HSI_Div2,RCC_PLLMul_x);

5 中断

注1: 每个外设都可以产生中断

注2:异常就是中断,中断就是异常

5-1 NVIC 嵌套向量中断控制器

中断的核心 片上外设
NVIC中断库函数查看misc.c

5-1-0 什么是嵌套向量中断,如何理解???

5-1-1 中断优先级分组

PreemptionPriority:(抢占优先级)优先级

SubPriority : 优先级

=============================================================================================================================
    NVIC_PriorityGroup   | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority  | Description
=============================================================================================================================
   NVIC_PriorityGroup_0  |                0                  |            0-15             |   0 bits for pre-emption priority
                         |                                   |                             |   4 bits for subpriority
-----------------------------------------------------------------------------------------------------------------------------
   NVIC_PriorityGroup_1  |                0-1                |            0-7              |   1 bits for pre-emption priority
                         |                                   |                             |   3 bits for subpriority
------------------------------------------------------------------------------------------------------------------------------   
   NVIC_PriorityGroup_2  |                0-3                |            0-3              |   2 bits for pre-emption priority
                         |                                   |                             |   2 bits for subpriority
------------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_3  |                0-7                |            0-1              |   3 bits for pre-emption priority
                         |                                   |                             |   1 bits for subpriority
------------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_4  |                0-15               |            0                |   4 bits for pre-emption priority
                         |                                   |                             |   0 bits for subpriority             
=============================================================================================================================

5-1-2 NVIC中断配置 NVIC_InitTypeDef

typedef struct {
uint8_t NVIC_IRQChannel; // 中断源
uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级
uint8_t NVIC_IRQChannelSubPriority; // 子优先级
FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能
} NVIC_InitTypeDef;

小心:中断源别搞错了,即使写错了程序也不会报错,只会导致不响应中断

中断源 IRQn_Type
typedef enum IRQn{
  //详见stm32f10x.h,枚举好了片上外设
}  IRQn_Type;

注:像外部按键KEY这种片外外设.h文件只是给出了EXTIx_IRQn(x = EXTI_ Line x,即中断源对应相应的中断/事件线)

用户编写的中断服务函数

统一写在 stm32f10x_it.c 这个库文件中

5-2 EXTI 外部中断/事件控制器

每个中断/事件都有独立的触发和屏蔽 每个中断线都有专用的状态位

5-2-1 EXTI两大功能

  • 产生中断

  • 产生事件

    这两个功能从硬件上就有所不同

5-2-2 EXTI 功能框图

框图丢失时查看中文参考手册P135或野火库开发教程P259

注1:图中20代表 在控制器内部类似的信号线路有 20 个这与 EXTI 总共有 20 个中断/事件线是吻合的

注2:EXTI挂载在APB2总线上!!!

注:红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内

产生事件的线路

绿色虚线:它是一个产生事件的线路,最终输出一个脉冲信号(这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器ADC等等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换)

产生中断的线路

红色虚线:产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能

5-2-3 中断 / 事件线

枚举体查看stm32f10x.h

Line输入源
EXTI 0-EXTI 15pX 0-pX 15(X=A~I)
EXTI 16PVD输出
EXTI 17RTC闹钟事件
EXTI 18USB唤醒事件
EXTI 19以太网唤醒事件(互联型)
5-2-4 外部中断的中断源

枚举体查看stm32f10x.h

中断源
EXTI0_IRQn ~ EXTI4_IRQn
EXTI9_5_IRQn
EXTI15_10_IRQn
5-2-5 EXTI 初始化配置
typedef struct{
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式:Interrupt产生中断模式,Event产生事件模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型:上升/下降沿触发,上或下都触发
FunctionalState EXTI_LineCmd; // EXTI 使能
} 	EXTI_InitTypeDef;
5-2-6 实现中断服务的编程步骤

以外部按键KEY的按下中断服务函数为例

电路图 ------按键------

GPIO引脚pXx—————— ——————3.3V

思考:KEY按下是不是仅有上升沿才可以触发中断服务

按下包括三个过程:按下按键未与左右两边接触->与两边接触->松开按键

GPIO的电平也是三个状态:浮空->3.3V->浮空(或GND)

在这之间有上升沿和下降沿,都代表了按键按下这个操作,需要执行中断服务函数

  1. 初始化用来产生中断的 GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  /* 配置为浮空输入 */
  1. 配置 NVIC

  2. 初始化 EXTI

//下面两种均可,按下即保持亮灯
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//按下检测到按键
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//松手检测到按键
//这种是按住才执行:亮灯
EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Rising_Falling;//很神奇吧

思考:下次可以给小车搞个上或下降沿都触发的按键,在持续按下这个按键的时候小车电机不转,松开就开始发车

  1. 编写中断服务函数

可以统一放在stm32f10x_it.c进行集成化管理,且不需要在它的.h声明该函数

void KEY1_IRQHandler(void)
{
    if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)//确保是否产生了EXTI Line中断 
    {
        LED1_TOGGLE;// LED1 取反
        EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);//清除中断标志位(很必要!!!)
    }  
}

6 SysTick与基本定时器(TIM6与TIM7)

6-0 二者的区别

定时器SysTickTIM6与TIM7
核心区别内核外设片上外设
移植性所有的Cortex-M内核单片机都有且一致各厂家百花齐放
特征功能简单,只能递减功能多,配置相对复杂
硬件优先级(属于系统内核)更高更低
抗干扰性强(除非系统异常,比如复位,否则滴答定时器将稳定运行)容易受外界干扰
面向对象一般用于操作系统时间,进程切换等用于用户需求

6-1 SysTick 系统滴答定时器 内核外设

  • SysTick—系统定时器是属于 CM3 内核中的一个外设,内嵌在 NVIC 中

  • SysTick像是维持操作系统的心跳

#include "core_cm3.h"//SysTick 属于内核的外设,有关的寄存器定义和库函数都在内核相关的库文件core_cm3.h
#include "misc.h"//

WARMING:上面这两个文件顺序错了会保错!!!

6-1-1 误区

  • 内核优先级一定比外设优先级高

    中断优先级的分组对 内核外设 和 片上外设 同样适用(即不存在内核优先级一定比外设优先级高的说法)

举个例子

软件划分的优先级

总体配置(主)抢占优先级子优先级
SysTick:(DEC)15;(BIN)1111(DEC)3;(BIN)11(DEC)3;(BIN)11
EXTI:(DEC)3;(BIN)1111(DEC)0;(BIN)00(DEC)3;(BIN)11

结论:SysTick中断优先级远小于EXTI(总体配置的十进制DEC数字越小中断优先级越高)

特例:由硬件划分的优先级来看

硬件优先级查看中文手册p132

当两者的抢占优先级与子优先级配置都相同时,向量表中显示:SysTick高于EXTI

结论:中断向量表中的硬件编号,编号越小,优先级越高

6-1-2 SysTick_Init 配置

void SysTick_Init(void)
{/* SystemCoreClock / 1000     1ms 中断一次
  * SystemCoreClock / 100000	 10us中断一次
  * SystemCoreClock / 1000000  1us 中断一次*/
    if (SysTick_Config(SystemCoreClock / 100000))	// ST3.5.0库版本
    { 
        /* Capture error */ 
        while (1);
    }
}
重装载值ticks

SysTick_Config()的形参 ticks 用来设置重装载寄存器的值,最大不能超过重装载寄存器的值 224,当重装载寄存器的值递减到 0 的时候产生中断, 然后重装载寄存器的值又重新装载往下递减计数

时钟源:AHBCLK=72M,将AHBCLK这个时钟源转化为SysTickCLK

SystemCoreClock = SYSCLK_FREQ_72MHz = 72000000

定时公式(传入形参Ticks到SysTick_Config得到t)

t = T i c k s ∗ 1 / f = ( 72000000 / 100000 ) ∗ ( 1 / 72000000 ) = 10 u s t = Ticks * 1/f = (72000000/100000) * (1/72000000) = 10us t=Ticks1/f=(72000000/100000)(1/72000000)=10us

t : 定时时间(延时单位);Ticks : 多少个时钟周期产生一次中断;f : 时钟频率 72000000(72MHZ)

6-1-3 用SysTick做延时Delay函数

void SysTick_Delay_MS( __IO uint32_t ms)
{
    uint32_t i;	
    SysTick_Config(SystemCoreClock/1000);
    for(i=0;i<ms;i++)
    {
        // 当计数器的值减小到0的时候,CRTL寄存器的位16会置1
        // 当置1时,读取该位会清0
        while( !((SysTick->CTRL)&(1<<16)) );
    }
    // 关闭SysTick定时器
    SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}
//SysTick_Delay_MS与SysTick_Delay_US
SysTick_Config(SystemCoreClock/1000);//中断(延时)单位为	  1ms
SysTick_Config(SystemCoreClock/1000000);//中断(延时)单位为	1us

6-1-4 延时函数封装技巧

#define 		    SysTick_DELAY_1S 			SysTick_Delay_MS(1000)
SysTick_DELAY_1S;//使用宏定义相当于  SysTick_Delay_MS(1000);  达到延时1秒的作用

6-2 TIM中的基本定时器

纯纯只有一个定时的作用

基本定时器的核心是时基

6-2-1 基本定时器的时钟源

定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1预分频器(TIM2 ~ TIM7)后分频提供

6-2-2 核心!!!定时器的时基

typedef struct
{//时基结构体
    uint16_t TIM_Prescaler;     		//预分频器     时钟源经该预分频器才是定时器计数时钟 CK_CNT
    uint16_t TIM_CounterMode;     	//计数模式     向上计数、向下计数以及中心对齐
    uint16_t TIM_Period;       		  //定时器周期   配置ARR(0 至 65535)
    uint16_t TIM_ClockDivision;     //时钟分频	   
    			//设置定时器时钟 CK_INT 频率与死区发生器以及数字滤波器采样时钟频率分频比

      uint8_t TIM_RepetitionCounter;  //重复计算器	只有 8 位,只存在于高级定时器
} TIM_TimeBaseInitTypeDef
  • 预分频器(PSC)

    定时器时钟 TIMxCLK(即CK_INT) 经过 PSC预分频器之后,即 CK_CNT,用来驱动计数器计数

    注:定时器的时钟源为内部时钟,频率高,则定时间隔短(不符合实际用途,也不便于计算),需要PSC预分频器来降低频率,让计数器使用降频后的 CK_CNT

    例子:为何分频后便于计算?

    如果我们想获取一个精确的1ms中断,如果不分频,72MHz的时钟对应每周期1/72us,十分不利于计算。这时候使用预分频器将其72分频后为1MHz,每周期1us,1000个计时周期即为1ms,这样既便于计算,定时也更加精确

    为何预分频计数器 Prescaler 的值为(目标值 - 1)

    如下面工作原理介绍:达到PSC设定值后**‘‘再tick一次后计数器归零’’**,定时器时钟 CK_CNT 频率 CK_INT / (PSC + 1)

    • 想对时钟源进行72分频,那么预分频器的值就应该设置为71
    PSC 工作原理

    定时器时钟源每tick一次,预分频器计数器值+1,直到达到预分频器的设定值,然后再tick一次后计数器归零,同时,CNT计数器值+1

    • 举个形象的比喻

    预分频计数器PSC相当于分针,CNT计数器相当于时针,假定PSC为(60 - 1)

    CK_INT tick一次,分钟+1(相当于PSC++),加到59min的时候,达到PSC设定的值,再Ticks一次达到60min,时钟+1(相当于CNT++)

注:预分频器值寄存器TIMx_PSC存在影子寄存器(官方翻译为缓冲功能),所以在定时器启动后更改TIMx_PSC的值并不会立即影响当前定时器的时钟频率。要等到下一个更新事件(UEV)发生时才会生效

  • 计数器(CNT)

    16位的计数器,只往上计数,最大计数值为65535(当计数达到自动重装 载寄存器的时候产生更新事件,并清零从头开始计数)

  • 自动重装载寄存器(ARR)

CNT++(即CNT向上计数)到ARR时,CNT清零

  • 定时时间的计算
    t 定时 = T 中断 ∗ N 中断 t定时 = T中断 * N中断 t定时=T中断N中断
    计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CNT 的倒数,即T = 1 /( CK_INT / (PSC+1) )

    产生一次中断的时间:T中断 = (ARR + 1) / CK_CNT:即一个自动重装载周期的时间

    果在中断服务程序里面设置一个变量 time(即需要中断的次数),那么就可以计算出我们需要的定时时间等于:t定时 = T中断 * time

6-2-3 基本定时器的配置

1.中断优先级的配置

//NVIC的配置...略

相对于SysTick来说 浪费了 一个优先级

2.开启定时器时钟,即内部时钟CK_INT=72M

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6);//RCC_APB1Periph_TIM6为宏定义,是标准库封装好的

注:挂载在 APB2的外设 不能 用 APB1 的RCC_APB1PeriphClockCmd时钟使能函数

3.基本定时器Timer的3项时基的配置

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;//结构体声明

TIM_TimeBaseStructure.TIM_Prescaler= (72 - 1);// 时钟源 CK_INT 被分频为 CK_CNT = 1MHZ	>>>>>T = 1us
TIM_TimeBaseStructure.TIM_Period = (1000 - 1);	// 自动重装载寄存器的值ARR	>>>>>T = 1us * 1K = 1ms  
		//默认向上计数,无需配置

4.初始化操作

TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);// 初始化定时器
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);// 清除计数器中断标志位
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);// 开启计数器中断
TIM_Cmd(BASIC_TIM, ENABLE);// 使能计数器

7 串口通信接口

  • 学通信的、一臭拉网线的你不把通信协议学好行吗?

7-0 串口简述

通信接口全称简述特性
UART通用异步接收发送器全双工异步串行通信
USART通用同步异步接收发送器全双工同步或异步串行通信
I2C集成电路总线半双工同步串行通信
SPI串行外设接口高速的全/半双工同步串行通信

注: USART还支持同步模式,因此USART 需要同步始终信号USART_CK(如STM32 单片机),通常情况同步信号很少使用,因此一般的单片机UART和USART使用方式是一样的,都使用异步模式

7-1 USART与UART

7-1-1 物理层

串口通讯结构图消失时查看野火库开发教程p295

电平标准

7-1-2 协议层

在串口通讯的协议层中,规定了数据包的内容(它由启始位、主体数据、校验位以及停止位组成),通讯双方的数据包格式要约定一致才能正常收发数据

A 串口数据包

串口数据包的基本组成图消失时查看野火库开发教程p300

B 波特率

UART 与 USART(异步下) 是 串口异步通讯 ,异步通讯中由于没有时钟信号,两个通讯设备之间需要事先约定好波特率,即每个码元(用虚线分开的每一格就是代表一个码元)的长度

C 通讯的起始和停止信号 —— 只要做到双方约定一致
  • 串口通讯的一个数据包从起始信号开始,直到停止信号结束
  • 数据包的起始信号由一个逻辑 0 的 数据位表示
  • 数据包的停止信号可由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示
D 有效数据
  • 起始位后的传输的主体数据内容
  • 有效数据的长度 常被约定为 5、6、7 或 8 位长
E 数据校验
校验方法简述例子
奇校验有效数据与校验位中“1”的个数为奇数有效01101001,尾跟一个‘1’的校验位
偶校验有效数据与校验位中“1”的个数为偶数数据帧11001010,尾跟一个’0’的校验位
0校验不管有效数据,校验位总为“0”置位逻辑低(0)校验
1校验不管有效数据,校验位总为“1”置位逻辑高(1)检验
无校验

7-1-3 USART的初始化结构体

typedef struct
{
    uint32_t USART_BaudRate;						//波特率
    uint16_t USART_WordLength;					//字长
    uint16_t USART_StopBits;						//停止位
    uint16_t USART_Parity;							//校验位模式
    uint16_t USART_Mode;								//USART的模式
    uint16_t USART_HardwareFlowControl;	 //硬件流的控制
} USART_InitTypeDef;
控制的结构体成员简介
波特率设置2400、9600、19200、115200
数据帧字长可选 8 位或 9 位(使能了奇偶校验)
停止位设置可选 0.5 个、1 个、1.5 个和 2 个停止位
校验模式选择
USART 模式选择USART_Mode_Rx 和 USART_Mode_Tx
硬件流控制选择只有在硬件流控制模式才有效

7-1-4 USART的配置

以USART3为例

1.NVIC配置

//NVIC优先级分组

2.使能时钟与串口

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB	挂载在APB2上
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);//USART3	挂载在APB1上

USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);// 使能	串口接收中断
USART_Cmd(USART3, ENABLE);// 使能	串口

注:挂载在不同时钟线上的外设需要单独配置时钟使能!!!

串口中断模式选择

USART_IT指定启用或禁用USART中断源
USART_IT_CTSCTS更改中断(不适用于UART4和UART5)
USART_IT_LBDLIN断路检测中断
USART_IT_TXE发送数据寄存器空时中断
USART_IT_TC发送完成后中断
USART_IT_RXNE接收数据寄存器不空中断
USART_IT_IDLE空闲线路检测中断
USART_IT_PE奇偶校验错误中断
USART_IT_ERR中断错误(帧错误、噪声错误、超限错误)
CTS与RTS 串口流控
  • 在两个设备正常通信时,由于处理速度不同,在某些情况下,就可能导致丢失数据的情况

    通过流控可以实现:当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据

  • RTS (发送请求)为输出信号,指示本设备准备好可接收数据低电平有效,低电平说明本设备可以接收数据

  • CTS (发送允许)为输入信号,用于判断是否可以向对方发送数据,低电平有效,低电平说明本设备可以向对方发送数据

  • 物理连接(交叉连接)

    ​ § 主机的RTS(输出)信号,连接到从机的CTS(输入)信号。
    ​ § 主机的CTS(输入)信号,连接到从机的RTS(输出)信号。

3.配置GPIO

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//RX

4.USART初始化结构体配置

USART_InitStructure.USART_BaudRate = 115200;// 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置 帧数据字长		为8位
USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置停止位 		为1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No ;// 配置校验位 		为无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 配置硬件流控制		为禁用
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 配置工作模式,收发一起
USART_Init(USART3, &USART_InitStructure);// 完成串口的初始化配置

5.发送操作

/*****************  发送一个字符 **********************/
void USATR_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
    USART_SendData(pUSARTx,ch);/* 发送一个字节数据到USART */
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	/* 等待发送数据寄存器为空 */
}
/*****************  发送字符串 **********************/
void USATR_SendString( USART_TypeDef * pUSARTx, char *str)
{
    unsigned int k=0;
    do{
        Usart_SendByte( pUSARTx, *(str + k) );
        k++;
    } while(*(str + k)!='\0');
    while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET);		/* 等待发送完成 */
}

6.重定向

#include <stdio.h>//注:在USART_Config.h中需要包含
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		USART_SendData(USART3, (uint8_t) ch);/* 发送一个字节数据到串口 */
		while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);		/* 等待发送完毕 */
		return (ch);
}
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		while (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) == RESET);	/* 等待串口输入数据 */
		return (int)USART_ReceiveData(USART3);
}

7.串口中断服务函数(放在stm32f10x_it.c)

void DEBUG_USART_IRQHandler(void) 
{
    uint8_t ucTemp;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
    {		
        ucTemp = USART_ReceiveData(DEBUG_USARTx);
        USART_SendData(DEBUG_USARTx,ucTemp);    
    }	 
}

7-2 I2C

IIC总线物理拓扑图

  • I2C是为低速设备的通信而生的,传输速度比不上SPI
  • 在小数据量场合使用,传输距离短
  • 任意时刻只能一个主机,但支持**多主控****

7-2-1 物理层

  • 只使用两条串行总线线路:数据线即用来表示数据,时钟线用于数据收发同步

  • 每个连接到总线的设备都有一个独立的地址,来确保不同设备之间访问的准确性

  • 高阻态性

    总线通过上拉电阻接到电源,当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平

  • 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线

    为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备

设备功能
主设备主要产生时钟,产生起始信号和停止信号
从设备可编程的IIC地址检测,停止位检测
  • 半双工

    同一时间单向通信

7-2-2 协议层

II总线C时序图

总线在传送数据过程中共有三种类型信号:开始信号、结束信号和应答信号

SDA与SCL空闲时

都被上拉置为高电平

信号类型简介
开始信号SCL 为高电平时,SDA 由高电平低电平跳变,开始传送数据
结束信号SCL 为高电平时,SDA 由低电平高电平跳变,结束传送数据
应答信号接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据

注1:应答信号:CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障

注2:起始信号是必需的,结束信号和应答信号,都可以不要

A 通讯起始与停止

  • 起始信号

    SCL线置高电平时,SDA线由高->低,本周期的通讯起始

  • 停止信号

    SCL线置高电平时,SDA线由低->高,本周期的通讯截止

B 数据有效性
  • SDA 数据线在 SCL 的 每个时钟周期传输一位 数据(1 bit)

  • 传输时,SCL 为高电平的时候 SDA 表示的数据有效, 即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0

  • 两个传输周期间:当 SCL 为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备

C 从机地址及数据方向
  • I2C 总线上的每个设备都有自己的独立地址

  • 通过 SDA 信号线发送设备地址 (SLAVE_ADDRESS) 来查找从机

  • I2C 协议规定设备地址可以是 7 位 或 10 位

  • 紧跟设备地址的一个数据位用来表示数据传输方向 ,它是数据方向位 (R/)

    数据方向位 为“1”时表示 主机由从机 数据,该位为“0”时表示 主机向从机 数据

SDA数据线 在数据位R/W 操纵的情况

读数据:从机控制SDA,主机接收

写数据:主机控制SDA,从机接收

D 应答(响应)

  • I2C 的数据和地址传输都带响应

  • 响应包括“应答 (ACK)”和“非应答 (NACK)”两种信号

  • 作为 数据接收端 时,当设备 (无论主从机) 接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答 (ACK)”信号,发送方会继续发送下一个数据

SDA控制权与应答情况

传输时主机产生时钟,在==第 9 个时钟(主机传输 8bit 后)【这是前提!!!】==时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号 (NACK),低电平表示应答信号 (ACK)

就是 ‘低应答’

共性:

时钟线置 高 时才能进行 起始或停止操作 或 数据传输有效

7-2-3 配置 OLED 与 stm32 通信

1.挂载两个总线的GPIO引脚初始化,开时钟,引脚电平初始化

//封装操纵 写入电平状态 的函数
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_15, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_5, (BitAction)(x))
//引脚电平初始化
OLED_W_SCL(1);//闲置为高阻态
OLED_W_SDA(1);//闲置为高阻态

2.IIC 开始

void OLED_I2C_Start(void)
{
  	OLED_W_SCL(1);//确保拉高(初始化)
    OLED_W_SDA(1);//确保拉高(初始化)
    OLED_W_SCL(0);//时钟给出:开始传输命令
  	OLED_W_SDA(0);//执行传输命令
}

3.IIC停止

void OLED_I2C_Stop(void)
{
  	OLED_W_SCL(1);//重返闲置的高阻态
    OLED_W_SDA(0);//确保应答信号返回 或 无新的数据传输
    OLED_W_SDA(1);//重返闲置的高阻态
}

注:

4.IIC发送 1bit

void OLED_I2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)//依次传入 8bit 数据
    {
        OLED_W_SDA(Byte & (0x80 >> i));
        OLED_W_SCL(1);
        OLED_W_SCL(0);
    }
    OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
    OLED_W_SCL(0);
}

5.OLED写命令

void OLED_WriteCommand(uint8_t Command)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);		//从机地址
    OLED_I2C_SendByte(0x00);		//写命令
    OLED_I2C_SendByte(Command); 
    OLED_I2C_Stop();
}

6.OLED写数据

void OLED_WriteData(uint8_t Data)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);		//从机地址
    OLED_I2C_SendByte(0x40);		//写数据
    OLED_I2C_SendByte(Data);
    OLED_I2C_Stop();
}

7.写命令或者数据

void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{
    if (cmd == OLED_Data)
    {
        OLED_WriteData(dat);
    }
    else if (cmd == OLED_Command)
    {
        OLED_WriteCommand(dat);
    }
}

8.OLED_ShowChar(),OLED_Clear()

OLED_WR_Byte();//显示函数 大量使用到了这个 写命令/数据的函数

7-2-3 深层一点的东西

如何实现串行传输?

那就要用到数据移位寄存器

A SDA线的 数据控制逻辑
1. 数据移位寄存器
  • I2C 的 SDA 信号主要连接到数据移位寄存器上(数据由 并行形式 依次进入 数据移位寄存器 转化为 串行形式 在一位位发送出去)

  • 数据移位寄存器的数据来源及目标是 数据寄存器 (DR) 、地址寄存器 (OAR)、PEC 寄存器以及 SDA 数据线

2. PEC寄存器(数据校验)

当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到“数据寄存器DR”中。 若使能数据校验,接收到的数据会经过 PCE 计算器运算,运算**结果存储在“PEC寄存器”**中

3. OAR地址寄存器
  • 当 STM32 的 I2C 工作在从机模式的时候

    接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32 的自身的“I2C 地址寄存器”的值作比较,以便响应主机的寻址

  • STM32 的自身 I2C 地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C 设备地址,两个地址分 别存储在 OAR1 和 OAR2 中

B 整体控制逻辑

I2C 特性及架构

STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来CPU 只要检测 该外设的状态 和 访问数据寄存器 ,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

  • 整体控制逻辑负责协调整个 I2C 外设

  • 控制逻辑的工作模式:配置“控制寄存器 (CR1/CR2)”的参数

  • 控制逻辑还根据要求,负责控制产生 I2C 中断信号、DMA 请求及各种 I2C 的通讯信号 (起始、停止、响应信号等)

  • 外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器 (SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C 的工作状态

I2C 架构图

理解工作原理

7-2-4 I2C读写EEPROM

A I2C初始化结构体
typedef struct {
uint32_t I2C_ClockSpeed; // 设置 SCL 时钟频率,此值要低于 400000
uint16_t I2C_Mode;       // 指定工作模式,可选 I2C 模式及 SMBUS 模式
uint16_t I2C_DutyCycle;  // 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式
uint16_t I2C_OwnAddress1; // 指定自身的 I2C 设备地址
uint16_t I2C_Ack; 			 // 使能或关闭响应 (一般都要使能)
uint16_t I2C_AcknowledgedAddress; // 指定地址的长度,可为 7 位及 10 位
} I2C_InitTypeDef;
B EEPROM
掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的 时候加载之

(1) 配置通讯使用的目标引脚为开漏模式;

(2) 使能 I2C 外设的时钟

(3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设;

(4) 编写基本 I2C 按字节收发的函数;

(5) 编写读写 EEPROM 存储内容的函数;

1.GPIO 配置与时钟开启
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //复用开漏输出
RCC_APB1PeriphClockCmd ( RCC_APB1Periph_I2C1, ENABLE ); //打开 I2C外设 的时钟
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, ENABLE ); //打开 引脚GPIO 的时钟
2. 配置 EEPROM 的 I2C 模式
static void I2C_Mode_Config(void)
{
  I2C_InitTypeDef I2C_InitStructure;
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;	/* I2C 配置 */
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//低 :高 = 2:1 
  I2C_InitStructure.I2C_OwnAddress1 = 0X0A; //与 STM32 外挂的 I2C 器件地址不一样即可
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ; //使能响应
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;/* I2C 的地址的长度 */
  I2C_InitStructure.I2C_ClockSpeed = 400000; //400k快速模式	/* 通信速率 */

  I2C_Init(I2C1, &I2C_InitStructure); /* I2C 初始化 */
	I2C_Cmd(I2C1, ENABLE); /* 使能 I2C */
}
2. I2C从机 EEPROM初始化
void I2C_EE_Init(void)
{
  I2C_GPIO_Config();
  I2C_Mode_Config();
  /* 根据头文件 i2c_ee.h 中的定义来选择 EEPROM 要写入的设备地址 */
  /* 选择 EEPROM Block0 来写入 */
  EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
}

7-3 SPI

  • 在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I2S音频协议(小或中的不具有I2S)

  • 串行外设接口(SPI)允许芯片与外部设备以 半/全双工同步串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)

  • 可以使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信

警告

由于 SPI3 / I2S3 的部分引脚与 JTAG 引脚共享 (SPI3_NSS/I2S3_WS 与 JTDI , SPI3_SCK/I2S3_CK与JTDO),因此这些引脚不受IO控制器控制,他们(在每次复位后) 被默认保留为JTAG用途。如果用户想把引脚配置给SPI3/I2S3,必须(在调试时)关闭 JTAG并切换至SWD接口,或者(在标准应用时)同时关闭JTAG和SWD接口。JTAG/SWD复用功能重映射详见:中文参考手册p117

7-3-1 物理层

SPI 通讯系统

A 片选线(从设备选择信号线)NSS 或叫 CS
  • 片选线如图中是SS1,SS2与SS3

  • 每个从设备都有独立的 这一条 NSS 信号线,本信号线独占主机的一个引脚

    有多少个从设备,就有多少条片选信号线

I2C与SPI在从设备地址判定上的区别

通信协议区别
I2C通过设备地址来寻址、选中总线上的某个设备并与其进行通讯
SPI没有设备地址,它使用 NSS 信号线来寻址,低电平选中(即片选有效)

注: SPI 通讯以NSS线置低电平 为 开始信号,以NSS线被拉高 作为 结束信号

B SCK 时钟信号线
  • 它由通讯主机产生,决定了通讯的速率,用于通讯数据同步

STM32 的 SPI 时钟频率最大为 fpclk / 2,两个设备之间通讯时,通讯速率受限于低速设备

C 设备输出与输入线
线数据传输方向
MOISMaster Output,Slave Input主设备输出/从设备输入引脚主->从
MISOMaster Input,,Slave Output主设备输入/从设备输出引脚从->主

7-3-2 协议层

SPI通讯时序图

A 通讯的起始和停止信号
  • 与I2C不同的是,不是SCL时钟线由高置低,而是NSS 信号线由高变低,是 SPI 通讯的起始信号

  • NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了

  • NSS 信号由低变高,是 SPI 通讯的停止信号

B 数据有效性
  • 用的是 SCK时钟线 进行数据同步

  • MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出

  • 在 SCK 的下降沿时被采样

  • SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制

C CPOL时钟极性 / CPHA时钟相位 及通讯模式

注:SPI 一共有四种通讯模式

主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻

两个概念简述
CPOL时钟极性SPI空闲时,SCK 信号线的电平信号 (即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)
CPHA时钟相位数据的采样时刻
  • CPOL = 0时,SCK空闲时为低电平
  • CPHA = 0时,MOSI 或 MISO 数据线上的信号将会在 **SCK 时钟线的“奇数边沿”**被采样
数据控制逻辑

SPI的架构

7-4 BxCAN控制器局域网


8 DMA


9 TIM 特殊定时器

输出PWM波的原理就是 从0开始计数到CCR 这个期间随计数增加不断置高电平,从CCR到自动重装载值Period期间不断置低电平

9-0 不同属性的TIM比较

差异

属性计数器类型捕获/比较通道互补输出
高级TIM1,TIM8向上/向下4
通用TIM2~TIM5向上/向下4
基本TIM6,TIM7只有向上0

共性

属性
计数器分辨率16位
预分频系数1~65535
产生DMA可以

9-1 高级定时器 TIM1与TIM8

9-2 通用定时器 TIM2 ~ TIM5

9-2-1 Servo舵机配置

Servo需要输出频率为 50HZ 的波

//以下CCR是在舵机可用占空比条件下的区间
//CCR可调范围(推荐):500 ~ 2500
TIM_TimeBaseStructure.TIM_Prescaler = (72-1);//72M / 72 =1M
TIM_TimeBaseStructure.TIM_Period    = (20000 - 1);//1M / 20K =50	
//CCR可调范围:5 ~ 25
TIM_TimeBaseStructure.TIM_Prescaler = (7200-1);//72M / 7.2K =10k
TIM_TimeBaseStructure.TIM_Period    = (200 - 1);//10k / 0.2k =50
舵机角度置高(等比缩小的CCR值)时间(一个周期为20ms)占空比
00.5ms2.5%
901.5ms7.5%
1802.5ms12.5%

注:实际用在小车上的舵机只会在大约70到120度改变


可以算一下舵机转1度需要改变多少CCR

9-3 输出SPWM波

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值