【嵌入式系统】 期末复习提纲

这篇具有很好参考价值的文章主要介绍了【嵌入式系统】 期末复习提纲。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

18级嵌入式系统复习提纲

第1讲 STM32F4体系结构

1、嵌入式系统的概念
以应用为中心、以计算机技术为基础、软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。
2、了解ARM体系结构的演变进程
1991年开始共推出7个版本,V1~V3未用于商业授权。V4T开始商业授权,T表示16位Thumb指令,ARM7TDMI,冯诺依曼结构,三级流水线,0.9MIPS/MHz。V5增强ARM和Thumb指令切换的支持,增加了DSP指令支持(后缀E)、Java支持(后缀J)V6版增强DSP和多媒体处理指令,增加SIMD指令扩展,对音、视频处理性能有极大提升,Thumb-2指令。2004年发布V7版,ARM首次为其体系结构命名,命名为Cortex,分为A(应用)、R(实时)、M(微控制器)三个系列
3、掌握STM32F4的时钟树:有哪几个时钟源、特点,正点原子战舰开发板时钟配置
5个时钟源
LSI:低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC、自动唤醒单元使用
LSE:低速外部时钟,接频率为32.768kHz 的石英晶体。主要是 RTC 的时钟源
HSI:高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作 PLL输入
HSE:高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围4MHz~26MHz。开发板接8M晶振。HSE也可以直接做为系统时钟或者 PLL 输入。
PLL:锁相环倍频输出 。主 PLL:由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。PLLP:生成高速的系统时钟(最高168MHz)PLLQ:生成 USB OTG FS 的时钟(48MHz)、随机数发生器的时钟和 SDIO时钟。专用 PLL(PLLI2S):生成精确时钟,从而在 I2S 接口实现高品质音频性能
PLL输出频率计算方法:输入时钟先 M M M分频再 N N N倍频最后 P P P(或 Q Q Q分频)
f o u t = f i n ∗ N M ∗ P f_{out}=\frac{f_{in}*N}{M*P} fout=MPfinN
开发板时钟配置: f i n = 8 M H z f_{in}=8MHz fin=8MHz,设置 M = 8 , N = 336 , P = 2 M=8,N=336,P=2 M=8N=336P=2
4、时钟相关函数在哪个.h和.c文件定义(其他模块同样),时钟源使能函数、外设时钟使能函数(要知道哪个外设挂在哪个总线上,截图打印下来参考)、时钟源选择函数,分频因子配置函数,外设复位函数
文件:stm32f4xx_rcc.h、stm32f4xx_rcc.c
时钟源使能函数:

void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_PLLI2SCmd(FunctionalState NewState);
void RCC_PLLSAICmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);

外设时钟使能函数:

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);

使能:ENABLE.失能:DISABLE
【嵌入式系统】 期末复习提纲
【嵌入式系统】 期末复习提纲

时钟源选择函数:

void RCC_HSEConfig(uint8_t RCC_HSE);
void RCC_LSEConfig(uint8_t RCC_LSE);
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t PLLM, uint32_t PLLN, uint32_t PLLP, uint32_t PLLQ);//分频因子配置
void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);//系统时钟源选择
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
void RCC_PCLK1Config(uint32_t RCC_HCLK);//外设(APB1)配置
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);//设置系统时钟源为HSI
RCC_PCLK1Config(RCC_HCLK_Div2);//设置APB1时钟为HCLK的2分频

外设复位函数:

void RCC_AHB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphResetCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);

状态参数获取清除函数

uint8_t  RCC_GetSYSCLKSource(void);
void  RCC_GetClocksFreq(RCC_ClocksTypeDef*RCC_Clocks);         
FlagStatus  RCC_GetFlagStatus(uint8_t RCC_FLAG);
void  RCC_ClearFlag(void);

5、ARM存储空间,大端排序、小端排序
ARM体系的存储空间: 2 32 B 2^{32}B 232B的单一、线性地址空间。地址: 0 ∼ 2 32 − 1 0 \sim 2^{32}-1 02321.可以看作由 2 30 2^{30} 230个32位的字组成的字地址空间.也可以看作由 2 31 2^{31} 231个16位的半字组成的半字地址空间
ARM体系的存储器格式:大端格式(Big Endian)高字节存储在低地址中,低字节则存放在高地址中;小端格式(Little Endian)高字节存储在高地址中,低字节则存放在低地址.
6、位带操作的含义、哪些区域可以进行位带操作,位带地址计算方法
含义:使用普通的加载/存储指令(字)对单一比特(位)进行读写的方式
区域:内部SRAM的最低1MB(0x20000000 ~ 0x200FFFFF)片内外设的最低1MB(0x40000000 ~ 0x400FFFFF)
内存SRAM区:
A l i a s A d d r = 0 x 22000000 + ( ( A − 0 x 20000000 ) × 8 + n ) × 4 = 0 x 22000000 + ( A − 0 x 20000000 ) × 32 + n × 4 AliasAddr=0x2200 0000+((A-0x2000 0000)×8+n)×4\\ =0x2200 0000+(A-0x2000 0000)×32+n×4 AliasAddr=0x22000000+((A0x20000000)×8+n)×4=0x22000000+(A0x20000000)×32+n×4
外设区:
A l i a s A d d r = 0 x 42000000 + ( ( A − 0 x 40000000 ) × 8 + n ) × 4 = 0 x 42000000 + ( A − 0 x 40000000 ) × 32 + n × 4 AliasAddr=0x4200 0000+((A-0x4000 0000)×8+n)×4\\ =0x4200 0000+(A-0x4000 0000)×32+n×4 AliasAddr=0x42000000+((A0x40000000)×8+n)×4=0x42000000+(A0x40000000)×32+n×4
A:SRAM或外设地址;AliasAddr:别名地址
n:位序号,若A为字节地址,则n为0…7,若A为字地址,则n为0…31

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) 
#define BIT_ADDR(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum)) 

第2讲 GPIO

1、STM32F4ZGT6共有多少组多少位GPIO(其他资源也同样,比如有多少USART,这个知识点后面就不单独列了)
7组16位IO端口(GPIOAGPIOG,PAPG)
1组2位端口GPIOH(GPIOH.0/PH0、GPIOH.1/PH1)
7×16+2=114
2、GPIO的工作方式、特点
4种输入模式
浮空输入:输出关闭,上下拉关闭
上拉输入:下拉电阻关闭,上拉电阻打开。无输入时,默认高电平。
下拉输入:上拉电阻关闭,下拉电阻打开。无输入时,默认低电平。
模拟输入:一定是浮空输入,不能上下拉。
4种输出模式
开漏输出、推挽式输出、开漏复用功能、推挽式复用功能(均可上、下拉)
上电复位后,GPIO默认为浮空状态(上下拉电阻都不加),部分特殊功能引脚为特定状态。
推挽输出:可以输出强高、低电平,连接数字器件
开漏输出:只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)
复用输出:数据来自复用外设的输出脚
3、打开相应GPIO的时钟(其他资源同样要求,后面不单独列出)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
4、GPIO相关寄存器的作用
端口模式寄存器(GPIOx_MODER)、端口输出类型寄存器(GPIOx_OTYPER)、端口输出速度寄存器(GPIOx_OSPEEDR)、端口上拉下拉寄存器(GPIOx_PUPDR)、端口输入数据寄存器(GPIOx_IDR)、端口输出数据寄存器(GPIOx_ODR)、端口置位/复位寄存器(GPIOx_BSRR)、端口配置锁存寄存器(GPIOx_LCKR)、复用功能寄存器(GPIOx_AFRL & GPIOx_AFRH)
5、库函数的操作方法:GPIO初始化;输出0、1;读取等

GPIO_InitTypeDef  GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;//IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIOF9,F10
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//输出1
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//调用示例
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//输出0
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,BitAction BitVal);//按位输出
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//输出
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);//按位读输入
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//读取输入
GPIO_ReadlnputDataBit(GPIOF,GPIO_Pin_9);//示例
GPIO_ReadlnputData(GPIOF);//示例
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);//按位读输出
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);//读取输出

6、寄存器方式操作方法
【嵌入式系统】 期末复习提纲

【嵌入式系统】 期末复习提纲

RCC->AHB1ENR|= 1<<5;//打开GPIOF时钟,RCC_AHB1ENR.5
GPIOF->MODER &= ~(3<<2*9);//GPIOF.9通用输出模式
GPIOF->MODER |= 1<<(2*9);
GPIOF->OSPEEDR &= ~(3<<2*9);//GPIOF.9速度100MHz
GPIOF->OSPEEDR |= 3<<(2*9);
GPIOF->PUPDR &= ~(3<<2*9);//GPIOF.9上拉
GPIOF->PUPDR |=1<<(2*9);
GPIOF->OTYPER &= ~(1<<9);//GPIOF.9推挽输出模式
GPIOF->OTYPER |=0<<9;
GPIOF->ODR|= 1<<9;//GPIOF.9=1,熄灭DS0
GPIOF->ODR&=~(1<<9);    //第九位输出0
GPIOF->ODR|=1<<9; //第九位输出1
GPIOF->BSRRH = 0x0200;     //第9位输出0
GPIOF->BSRRL = 0x0400;      //第10位输出1
uint32_t data=GPIOF->IDR //读取输入
uint32_t data=GPIOF->ODR //读取输出

7、位带操作

//位带区别名地址计算公式的c语言宏定义
#define BITBAND(addr, bitnum) ((addr&0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) 
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+20)    //0x40020014
#define GPIOF_ODR_Addr (GPIOF_BASE+20)    //0x40021414    
#define GPIOA_IDR_Addr (GPIOA_BASE+16)     //0x40020010 
#define GPIOF_IDR_Addr (GPIOF_BASE+16)      //0x40021410 
//IO口位带操作的c语言宏定义
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n)  //位带输出(写) 
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n)    //位带输入(读)
PFout(9)=1;//输出1
PFout(9)=0;//输出0

8、寄存器开发与固件库开发的优缺点
寄存器开发方式的特点
运行速度快、占用存储空间小;需要学习、记忆各寄存器的功能、格式、每位作用含义,STM32F4有数百个寄存器
固件库开发方式的特点
直接调用固件库函数,无须深入了解各寄存器的具体格式
运行速度较慢、占用存储空间较大

9、理解CMSIS标准
使不同芯片公司Cortex-M4内核的芯片在软件上基本兼容。Cortex Microcontroller Software Interface Standard,Cortex微控制器软件接口标准
10、GPIO与其他模块结合的应用
驱动LED、按键,中断,USART等
11、GPIO端口复用:
注意初始化GPIO时一定选择****复用功能****,即GPIO_InitStructure.GPIO_Mode = ****GPIO_Mode_AF;*并用*GPIO_PinAFConfig();****进行复用功能配置。要知道哪个资源可以复用哪些引脚,把我们学过的资源都整理出来备查
【嵌入式系统】 期末复习提纲
【嵌入式系统】 期末复习提纲
【嵌入式系统】 期末复习提纲
【嵌入式系统】 期末复习提纲
【嵌入式系统】 期末复习提纲

第3讲NVIC与EXTI

EXTI挂载在APB2上。
1、NVIC与EXTI的概念
NVIC:Nested Vectored Interrupt Controller,嵌入向量中断控制器
EXTI:External Interrupt/event controller,外部中断/事件控制器
2、NVIC相对VIC的优点
快,ns级响应速度,实时性强。标准,只要是Cortex内核,不同公司用法一样。
3、NVIC的中断管理规模(多少级中断、内核中断、外部中断各多少)、STM32F407的中断管理规模
256级可编程中断设置(8位),16个内核中断,240个可屏蔽中断,每个IO口都可以作为中断源。
管理中断:非屏蔽请求NMI、可屏蔽请求IRQ。中断源是IO口、外设、内核中断、系统异常中断
stm32只支持92个(10个内核中断,82个可屏蔽中断)4位16级优先管理,5组分组(没有使用Cortex内核IP的全部8位)
4、NVIC中断的优先级管理规则,理解抢占优先级、响应优先级,中断优先顺序、中断嵌套规则、中断优先级分组。STM32F4对NVIC中断优先管理上的实现
优先级分为抢占优先级(主优先级)、响应优先级(从优先级)。
中断嵌套规则:高抢占优先级嵌套低抢占优先级,同级不可嵌套,小号优先。
响应优先级不嵌套,只决定多个相同抢占优先级、不同响应优先级同时申请时的相应顺序,小号优先。
两种优先级都相同的多个中断源同时申请时,首先响应中断地址(向量)低(小号)
优先级规则针对中断通道而非中断源。
中断优先级分组:9种组合。
分组0:8位全用于响应优先级(0:8)
分组x:x位抢占优先级,8-x位响应优先级(x:8-x)
STM32对NVIC中断优先管理实现:只用高4位,16个可编程优先级
5、NVIC相关寄存器的含义
在core_cm4.h
ISER[8]:中断使能寄存器组
ICER[8]:中断失能寄存器组
ISPR[8]:中断挂起寄存器组
ICPR[8]:中断解挂寄存器组
IABR[8]:中断激活标志位寄存器组
(stm32只用3个)
IP[240]:中断优先级寄存器组(stm32只用82个,高4位)
STIR:软件触发中断寄存器组
6、固件库方式对NVIC初始化、中断优先级分组设定方法

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
typedef struct  {
	uint8_t NVIC_IRQChannel;                       //通道
	uint8_t NVIC_IRQChannelPreemptionPriority;  //抢占优先级
	uint8_t NVIC_IRQChannelSubPriority;     //响应优先级
	FunctionalState NVIC_IRQChannelCmd    //使能、失能
} NVIC_InitTypeDef;
//示例
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 分组2
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//0抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//2响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能
NVIC_Init(&NVIC_InitStructure);//NVIC初始化

中断分组配置在寄存器SCB->AIRCR[10..8]中进行。
固件库函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
例如:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)
(在初始化时配置一次,一般不改)
7、中断与事件区别
中断:需CPU执行ISR(中断服务程序)完成相关操作
事件:靠脉冲发生器产生一个脉冲,不需CPU干预,由硬件自动完成相关操作,如触发AD转换
可以这样简单认为,事件机制提供了一个完全由硬件自动完成触发到产生结果的通道,不要软件的参与,降低了CPU的负荷,节省了中断资源,提高了响应速度,是利用硬件来提升CPU芯片处理能力的一个有效方法。
8、EXTI中断线的映射规则
23根(23个通道,16个针对输入输出端口,每个I/O都可以映射为外部中断线,同一编号的I/O口同时只能由一个映射为外部中断线)
配置:SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource2)//PE几连到中断线几
9、外部中断、内部中断的中断服务函数名
(固定的,编程用到,外部中断线9-5、10-15共用一个)
在stm32f4xx_it.c定义
void EXTI0_IRQHandler(void)
void EXTI1_IRQHandler(void)
void EXTI9_5_IRQHandler(void)
void EXTI15_10_IRQHandler(void)
外部中断:0~4各一个,5~9共用,10~15共用
10、EXTI相关寄存器含义
中断屏蔽寄存器 (EXTI_IMR)
事件屏蔽寄存器 (EXTI_EMR)
上升沿触发选择寄存器 (EXTI_RTSR)
下降沿触发选择寄存器 (EXTI_FTSR)
软件中断事件寄存器 (EXTI_SWIER)
挂起寄存器 (EXTI_PR)
11、EXTI固件库:
stm32f4xx_exti.h,stm32f4xx_exti.c
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//初始化中断线类型、触发方式
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);//判断中断线中断状态是否发生
EXTI_ClearITPendingBit()//清除中断线上标志位

EXTI_InitStructure.EXTI_Line = EXTI_Line2;	 
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

中断服务函数。
EXTIx_IRQHandler();
中断服务函数最后一步:清除中断标志位
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
示例:

void EXTI3_IRQHandler(void){
	if (EXTI_GetITStatus(EXTI_Line3)!=RESET){//判断某个中断是否发生
		//中断逻辑
		EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE上的中断标志位
	}
}

12、根据电路连接图判别外部中断的触发方式
【嵌入式系统】 期末复习提纲

按键连接高电平,按下接通电路。则需要默认输出低电平,下拉,高有效,上升沿触发。
按键连接低电平,按下接通电路,则需要默认输出高电平,上拉,低有效,下降沿触发。

第4讲WDG

窗口看门狗挂在APB1。独立看门狗内部自带时钟,不挂载总线。
1、WDG的作用
递减器计数(定时器),溢出时,使系统复位
2、IWDG、WWDG的特点、工作原理
IWDG(独立看门狗):由专用的低速时钟(LSI,32kHz)驱动,即使主时钟发生故障它仍有效,但精度不高(15~47kHz )应用于需要看门狗作为一个在主程序之外能够完全独立工作,并且对时间精度要求较低的场合。
在键值寄存器(IWDG_KR)中写入0xCCCC(启动),开始启用独立看门狗。此时计数器开始从其复位值0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。无论何时,只要往键值寄存器IWDG_KR中写入0xAAAA(俗称喂狗), 自动重装载寄存器IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。如果程序异常,就无法正常喂狗,则导致系统复位。

WWDG(窗口看门狗):由APB1分频(4096)后的时钟驱动,精度高。通过可配置的时间窗口来检测应用程序非正常的过迟或过早操作。最适合那些要求看门狗在精确计时窗口起作用的程序。可触发中断(提前唤醒中断)

3、IWDG、WWDG寄存器的含义
IWDG_KR:钢铁雄心4mod KaiserReich键值寄存器,只写
IWDG_RLR:每次喂狗时重载入计数器的初值,具有写保护。
IWDG_SR:状态寄存器,为1时表示正在更新,为0时表示复位
IWDG_PR:预分频寄存器。

WWDG_CR:控制寄存器,第7位为激活位,低位为计数器
WWDG_CFR:第9位为是否使能提前唤醒中断EWI(在计数值0x40时唤醒并用中断服务程序提醒主程序去喂狗),第8位第7位为定时器时基,低7位为上窗口值。
WWDG_SR:提前唤醒中断标志,计数器值达到0x40时硬件置1
4、IWDG几个键值的作用:
aaaah:喂狗
5555h:去除写保护,使能对IWDG_PR和IWDG_RLR访问
cccch:启动(使能)看门狗
0000h:加写保护对IWDG_PR和IWDG_RLR

5、了解IWDG、WWDG库函数、配置流程
IWDG库函数:

void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//写保护:0x5555使能写;0x0000失能写
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR
void IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR
void IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR
void IWDG_Enable(void);//使能(启动)看门狗:写0xCCCC到KR
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新

配置流程:

IWDG_WriteAccessCmd();//取消寄存器写保护:
IWDG_SetPrescaler(prer);//设置独立看门狗的预分频系数,确定时钟:
IWDG_SetReload(rlr);//设置看门狗重装载值,确定溢出时间:
IWDG_Enable();//使能(启动)看门狗
IWDG_ReloadCounter(); //应用程序喂狗

独立看门狗溢出时间 T o u t = ( 4 ∗ 2 p r e r ∗ r l r ) / 32 T_{out}=(4*2^{prer}*rlr)/32 Tout=(42prerrlr)/32
WWDG库函数:

溢出时间 T w w d g = t p c l k 1 ∗ 4096 ∗ 2 w d g t b ∗ ( T [ 5 : 0 ] + 1 ) ( m s ) T_{wwdg}=t_{pclk1}*4096*2^{wdgtb}*(T[5:0]+1)(ms) Twwdg=tpclk140962wdgtb(T[5:0]+1)(ms),其中 t p c l k 1 t_{pclk1} tpclk1为APB1时钟周期, w d g t b wdgtb wdgtb为预分频系数, T [ 5 : 0 ] T[5:0] T[5:0]为WWDG计数值低6位

void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);//预分频
void WWDG_SetWindowValue(uint8_t WindowValue); //窗口设置
void WWDG_EnableIT(void);//使能EWI中断
void WWDG_SetCounter(uint8_t Counter);//设置计数值,喂狗
void WWDG_Enable(uint8_t Counter);//使能WWDG,并写入计数值
FlagStatus WWDG_GetFlagStatus(void);//获取EWIF
void WWDG_ClearFlag(void);//清除EWIF

配置步骤:

RCC_APB1PeriphClockCmd();//使能看门狗时钟
WWDG_SetPrescaler(fprer);//设置分频系数
WWDG_SetWindowValue(wr);//设置上窗口值
WWDG_EnableIT();NVIC_Init();//开启提前唤醒中断并对NVIC初始化(可选):
WWDG_Enable(WWDG_CNT);//使能看门狗:
WWDG_SetCounter();//喂狗,一般放在ISR(中断服务程序)
WWDG_IRQHandler();//编写中断服务函数

第5-7讲TIM

TIM1和TIM8在APB2,TIM2~TIM5在APB1,TIM9~TIM11在APB2,TIM12~TIM14、TIM6和TIM7在APB1
1、STM32F4有哪几类定时器、特点、计数模式
STM32F40x系列总共有最多14个定时器:
通用定时器TIM2~TIM5
通用定时器TIM9~TIM14
基本定时器TIM6、TIM7
高级定时器TIM1、TIM8
看门狗(IWDG、WWDG)本质上也是定时器
TIM2、TIM5为32位,其他16位
Cortex内核的SysTick计时器(24位递减)
2、定时器原理(基本定时模块、输入捕捉模块、输出比较模块)、输入捕捉通道数量、产生PWM的通道数量
16/32位向上、向下、向上/向下(中心对齐)计数模式,自动重装载计数器。16位可编程预分频器,分频系数为1~65536,可实时修改。4个独立通道(TIMx_CH1 ~4),可用作:输入捕获/输出比较/PWM生成/单脉冲输出,可对外部信号(TIMx_ETR)计数,可以定时器级连.
【嵌入式系统】 期末复习提纲

输入捕捉通道数量:TIM6、7无此功能,10、11、13、14一个通道,9、12二个通道,其他4个通道
PWM输出通道数量:TIM1/TIM8产生7路PWM输出,TIM2~5同时4路PWM输出。TIM9、TIM12同时2路PWM输出,TIM10/11/13/14产生1路PWM输出
3、定时时间的计算
若APB1的分频系数是1,则TIMx的时钟等于APB1时钟(×1),否则TIMx的时钟等于APB1时钟的2倍(×2)。
(T1/T8/T9~11时钟信号来自APB2)
CK_PSC为时钟周期。
CK_INT=84M
f C K _ I N T = f C K _ P S C / ( P S C [ 15 : 0 ] + 1 ) f_{CK\_INT} = f_{CK\_PSC} / (PSC[15:0] + 1) fCK_INT=fCK_PSC/(PSC[15:0]+1)
T = ( T I M _ P e r i o d + 1 ) ∗ ( T I M _ P r e s c a l e r + 1 ) / f C K _ P S C T=(TIM\_Period+1)*(TIM\_Prescaler+1)/f_{CK\_PSC} T=(TIM_Period+1)(TIM_Prescaler+1)/fCK_PSC
4、相关寄存器的含义、作用
TIMx_CNT:实现计数功能
TIMx_PSC:对CK_PSC预分频,
TIMx_ARR:自动重载寄存器。
TIMx_CR1:控制寄存器
TIMx_DIER:中断控制
TIMx_SR:状态寄存器,低位表示中断,其他位用于捕获/比较
TIMx_CCER:捕获比较使能寄存器
TIMx_CCMR1:PWM输出模式
5、库函数的使用、各方式的配置流程
stm32f4xx_tim.c、stm32f4xx_tim.h

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//定时器参数(基本定时功能)初始化
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//定时器使能函数
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//定时器中断使能函数
//状态标志位获取和清除
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//获取中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//中断标志位挂起清除
TIM_TimeBaseStructure.TIM_Prescaler=7199;//预分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数模式
TIM_TimeBaseStructure.TIM_Period = 4999;//周期(重装载值)
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 

定时器中断

RCC_APBxPeriphClockCmd();//使能定时器时钟
TIM_TimeBaseInit();//配置ARR、PSC、CR1,初始化定时器
TIM_ITConfig()NVIC_Init();//开启定时器中断,配置NVIC
TIM_Cmd();    //配置CR1的CEN位,使能定时器
TIMx_IRQHandler();//编写中断服务函数

输入捕获通道初始化
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

TIM5_ICInitStructure.TIM_Channel=TIM_Channel_1;//通道
TIM5_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//边沿选择
TIM5_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; //通道映射
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//预分频(DIV1不分频)
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);

通道极性设置独立函数
void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
获取通道捕获值函数uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx);
输入捕获的一般配置步骤

//1.使能定时器和通道对应IO的时钟。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  
//2.初始化IO口,模式为复用:GPIO_Init();
GPIO_PinAFConfig();//3.设置引脚复用映射
TIM_TimeBaseInit();//4.初始化定时器ARR,PSC
TIM_ICInit();//5.初始化输入捕获通道(time的时基和输入捕获都要初始化)
TIM_ITConfig();    NVIC_Init(); 
NVIC_PriorityGroupConfig();//6.如果要开启捕获中断
TIM_Cmd();//7.使能定时器
TIMx_IRQHandler();//8.编写中断服务函数

PWM输出通道初始化函数:
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

示例:
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;              //PWM模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure. TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高(CC1P)
TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //初始化TIM3_OC2

设置比较值函数
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Comparex);
使能输出比较预装载
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
使能自动重装载的预装载寄存器允许位
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
PWM输出配置步骤

RCC_APB1PeriphClockCmd();
RCC_AHB1PeriphClockCmd ();//使能定时器14和相关IO口时钟。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//初始化IO口为复用功能输出。//复用功能
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);//GPIOF9复用映射到定时器14
TIM_TimeBaseInit();//初始化定时器时基:ARR,PSC等:
TIM_OC1Init();//初始化输出比较参数
TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);//使能预装载寄存器: 
TIM_ARRPreloadConfig(TIM14,ENABLE);//使能自动重装载的预装载寄存器允许位
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)//使能定时器
TIM_SetCompare1();//不断改变比较值CCRx,达到不同的占空比效果:

6、输入捕捉的原理,如何测量脉冲的宽度和周期
输入阶段:消除抖动、边沿选择、通道选择、分频,得到的信号直接控制主电路。得到捕获控制信号后,读取捕获值。
上升沿捕获得到计数值1,下降沿捕获得到计数值2,计算得到脉宽。(中间可能有多次溢出)
T = n ∗ T 溢出 + ( C N T 下降 − C N T 上升 ) ∗ T 计数 T=n*T_{溢出}+(CNT_{下降}-CNT_{上升})*T_{计数} T=nT溢出+CNT下降CNT上升)T计数
捕获到上升沿立即清除cnt时, T = n ∗ T 溢出 + C N T 下降 ∗ T 计数 T=n*T_{溢出}+CNT_{下降}*T_{计数} T=nT溢出+CNT下降T计数
7、输入捕捉中的硬件滤波原理
在CK_INT的DTS时钟下控制滤波。假如DTS采样频率=计数器的时钟(CK_INT)频率,采样次数N=4,则在上升沿时采样信号进行倒计数,没记到N次时电平又低了,则不认为是有效信号。若上升沿持续了N个DTS周期,认为有效。在输入信号变低后,经过N个DTS周期,滤波后的信号也变低。时间延迟了N个DTS周期,宽度不变。
8、PWM的原理、作用,占空比、周期如何改变
PWM(Pulse Width Modulation)即脉冲宽度调制,也就是占空比和周期均可变的脉冲波形。
原理:冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。
作用:PWM脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
改变周期可达到调频效果,改变脉宽或占空比可达到调压效果。故采用适当的控制方法即可使电压电流与频率协调变化。
改变重载值ARR可以改变周期,改变比较值CCRx可以改变脉宽(占空比)
PWM频率=计数频率/重装载值
9、影子寄存器与原寄存器的关系,作用
物理上对应两个寄存器(缓冲作用),一个程序可直接操作的,另一个叫影子寄存器(真正起作用的).由TIMx_CR1.APRE控制,APRE=0两者直通,APRE=1,每次更新事件(UEV)时接通。
void TIM_ARRPreloadConfig(TIM_TypeDef *TIMx,FunctionalState NewState);

第9讲USART

USART1、USART6挂在APB2,其他APB1。
1、几种常见串行通信标准(UART、USART、SPI、I2C、1-wire)的特点

【嵌入式系统】 期末复习提纲

USART:引脚同UART,异步通信,全双工。

2、USART主要寄存器
USART_SR:状态寄存器
TXE:发送数据寄存器的数据是否传到移位寄存器,1表示完成
TC:0表示发送未完成,1表示发送完成
RXNE:0表示未接受,1表示已准备好读取接收到的数据
USART_DR:数据寄存器
USART_CR:控制寄存器
USART_BRR:波特率寄存器
15~4位存储USARTDIV的整数部分(PPT有误),3~0存放USART小数。小数部分的数值*16后以二进制存储。
3、波特率计算
波特率 = f P C L K 8 ∗ ( 2 − O V E R 8 ) ∗ U S A R T D I V f C L K 为串口总线上的时钟频率 , O V E R 8 为过采样率 波特率=\frac{f_{PCLK}}{8*(2-OVER8)*USARTDIV}\\ f_{CLK}为串口总线上的时钟频率, OVER8为过采样率 波特率=8(2OVER8)USARTDIVfPCLKfCLK为串口总线上的时钟频率,OVER8为过采样率

4、常用库函数,中断方式工作、查询方式工作,接收、发送方法(含库函数与寄存器两种方式)

void USART_Init();//串口初始化:波特率、数据字长、奇偶校验、硬件流、收发使能
void USART_Cmd();//串口使能
void USART_ITConfig();//相关中断使能

查询

FlagStatus USART_GetFlagStatus();    //获取状态标志位
void USART_ClearFlag();                      //清除状态标志位
while(USART_GetFlagStatus(USART1, UART_FLAG_TC) == RESET); //等待发送完成
while((USART1->SR&0x40)==0);//等待发送完成(寄存器方式)
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); 
while(USART1->SR & Ox20 == 0);//等待接收完成

中断

if(USART_GetITStatus(USART1,USART_IT_RXNE)){
	res=USART_ReceiveData(USART1);//读取接收到的数据
	//USART_SendData(USART1,res);//把接收到的数据发回去
	USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
ITStatus USART_GetITStatus();           //获取中断状态标志位
void USART_ClearFlag();//清楚状态标志位
void USART_ClearITPendingBit();       //清除中断状态标志位

接收

uint16_t USART_ReceiveData();  //接受数据,读DR(RDR)
ch=USART_ReceiveData(USART1);//读取一个数据存到ch
ch=USART1->DR;//寄存器方式

发送

void USART_SendData();             //发送数据,库函数
USART_SendData(USART1,ch);//发送ch
USART1->DR=ch;//寄存器方式发送库函数

5、USART的应用,与主机串口调试助手通信,结合其他模块,如GPIO、NVIC等
主机(串口调试助手)发送数据到STM32的USART1, USART1将收到的数据直接再发送到主机。

void My_USART1_Init(void){
	GPIO_InitTypeDef    GPIO_InitStructure;
	USART_InitTypeDef   USART_InitStructure; 
	NVIC_InitTypeDef   NVIC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	USART_InitStructure.USART_BaudRate=115200;
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStructure.USART_Parity=USART_Parity_No;
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);
	USART_Cmd(USART1,ENABLE);
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void){
	u8 res;
	if(USART_GetITStatus(USART1,USART_IT_RXNE)){
		res=USART_ReceiveData(USART1);
		USART_SendData(USART1,res);
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}
int main(void){
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	My_USART1_Init();
	while(1);
}
struct __FILE { 
	int  handle; 
}; 
FILE __stdout; 
int fputc(int ch, FILE *f){ 	
	while((USART1->SR&0x40)==0);  //等待发送完成
	USART1->DR = (u8) ch;      
	return ch;
}

6、STM32F407支持多少UART、多少USART
2个UART。挂在APB1:UART4、UART5.
4个USART。挂在APB1:USART2、3,挂在APB2:USART1、USART6

第10讲I2C

1、I2C简介
两线式串行总线,连接微控制器及外围设备。双向同步半双工。每个器件有唯一地址,工作在主模式或从模式,可以发送或接收。主控器能产生时钟信号,总线上开漏模式上拉。
2、I2C协议内容:空闲、开始、停止、应答、数据有效性、数据传输等
空闲:SDA和SCL都是高电平,则空闲。
开始:SCL为高期间,SDA由高到低跳变
停止:SCL为高期间,SDA由低到高跳变
应答:发送器每发送一个字节,就在第9时钟脉冲期间释放数据线,由接收器反馈一个应答信号。有效应答信号为低电平,非应答信号为高电平。对有效应答ACK要求:接收器在第9个SCL信号前的低电平期间拉低SDA,确保SCL的高电平期间为稳定的低电平。
数据有效性:SCL上升沿时采集数据,且在SCL下降沿前SDA上的数据必须稳定。
数据传输:I2C总线上传输的每个字节均为8位,首先传输MSB,每次启动一次I2C总线,其后传输的字节数是没有限制的。每传输一个字节后都必须由接收器回应一个应答(ACK或NACK)。SDA上,第一帧为寻址字节,包括7位被控器的地址和1位读写方向位,接着是被控器的应答位。紧接着是主控器和被控器的数据传输和应答,传输结束后,主控器要发停止信号。
3、用GPIO口软件仿真I2C方法

void IIC_Init(void){//初始化			
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	//GPIOB8、9初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	IIC_SCL=1;
	IIC_SDA=1;
}
void IIC_Start(void){//开始
	SDA_OUT();     //sda线输出
	IIC_SDA=1;	  	  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;     //START:SCL高,SDA从高到低跳变
	delay_us(4);
	IIC_SCL=0;     //钳住I2C总线,准备发送或接收数据
}
void IIC_Stop(void){//IIC结束信号
	SDA_OUT();     //sda线输出
	IIC_SCL=0;
	IIC_SDA=0;     
 	delay_us(4);
	IIC_SCL=1;     //STOP:SCL高,SDA从低到高跳变
	IIC_SDA=1; 
	delay_us(4);							   
}	 
u8 IIC_Wait_Ack(void){//等待应答
	u8 ucErrTime=0;
	SDA_IN();      //SDA线输入
	IIC_SDA=1;   delay_us(1);	   
	IIC_SCL=1;   delay_us(1);	 
	while(READ_SDA) {
		ucErrTime++;
		if(ucErrTime>250){
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;	   
	return 0;  
}
void IIC_Ack(void){//应答
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;//ACK
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}
void IIC_NAck(void){//非应答
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;//NACK
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}	
void IIC_Send_Byte(u8 txd){//发送数据                    
	u8 t;   
	SDA_OUT(); 	    
	IIC_SCL=0;       //拉低时钟准备发送
	for(t=0;t<8;t++){              
		IIC_SDA=(txd&0x80)>>7;     //先发送MSB(最高有效位)
		txd<<=1; 	  
		delay_us(2); 
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;   //时钟低电平才允许数据改变	
		delay_us(2);
    }	 
} 	
u8 IIC_Read_Byte(unsigned char ack) {//接收数据
	unsigned char i,receive=0;//接受字节
	SDA_IN();    //SDA线输入
	for(i=0;i<8;i++ ){
		IIC_SCL=0; 
		delay_us(2);
		IIC_SCL=1;//上升沿采集数据
		receive<<=1;
		if(READ_SDA)    receive++;   
		delay_us(1); 
	}					 
	if (!ack)
		IIC_NAck();   //发送NACK
	else
		IIC_Ack();     //发送ACK   
	return receive;
}

第11讲SPI

SPI2/SPI3挂载在APB1上,其余挂载在APB2上。
1、SPI简介
高速、全双工、同步串行通信总线,只占4根线,任何时候只能有一个主机和从机通信。发送数据时必然接收到数据
2、SPI从设备的选择方法
SPI_CR1的SSM位设置硬件或软件管理NSS(从器件选择),为1时软件管理,NSS由SPI_CR1寄存器的SSI位驱动。为0时是硬件管理,根据SPI_CR2的SSOE位控制模式。
3、SPI主要寄存器
SPI_CR1:控制寄存器1.
SPI_CR2:控制寄存器2,与中断相关。
SPI_SR:位1TXE,发送缓冲区空。位0RXNE,接收缓冲区非空。位7BSY,忙标志。
SPI_DR:数据寄存器。物理上分两个缓冲区:发送缓冲区和接收缓冲区。
SPI_I2SCFGR:I2S模式配置。
4、SPI常用库函数

void SPI_Init(SPI_TypeDef* SPIx,SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);//使能
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);//设置中断
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);//传送数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);//接收数据
void SPI_DataSizeConfig(SPI_TypeDef* SPIx,uint16_t SPI_DataSize);
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);//清除状态标志位
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);//获取中断标志位
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

5、如何判断SPI一个数据发送完成、如何判断SPI接收到一个数据
数据先于时钟,选择第一边沿采集。数据时钟同时,选择第二边沿。
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//一个数据没发完,等待发送完成
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);//没有接受到数据,等待接收到1帧数据
SPI_I2S_SendData(SPI1,ch);//库函数方式发送数据
SPI1->DR=ch;//寄存器方式

第12讲ADC

挂载在APB2上
1、STM32F4 ADC简介:特点、性能、分辨率、数据对齐、通道数量等
特点:模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。 在转换结束、注入转换结束以及发生模拟看门狗溢出事件时产生中断。可独立设置各通道采样时间 外部触发器选项,可为规则转换和注入转换配置极性
性能:ADC电源要求:全速运行2.4V ~ 3.6V,慢速运行1.8V
分辨率:可以配置为12位、10位、8位或6位。
对齐:左对齐或右对齐的方式存储在16位数据寄存器中。
通道数量:3个AD转换器,每个AD转换器有16个外部通道,共24个外部通道。3个内部通道(ADC1)
2、规则通道、注入通道的概念
规则通道组:相当于正常运行的程序,最多16个通道。
注入通道组:相当于中断,最多4个通道。
3、工作模式:单次、连续、扫描、间断
单次:将ADC_CR2的CONT位置0,则处于单次转换模式。该模式下ADC仅执行一次转换,然后ADC停止。此模式下,可通过以下方式启动转换:将ADC_CR2的SWSTART位置1(规则通道),将JSWSTART位置1(注入通道)、外部触发。所选通道转换结束后结果保存在16位ADC_DR(ADC_JDR1)中EOC(JEOC)标志置1.EOCIE(JEOCIE)位置1时将产生中断
连续:将ADC_CR2的CONT位置1,则处于连续转换模式。该模式下ADC结束一个转换后立即启动一个新的转换。此模式下,可通过以下方式启动转换:将ADC_CR2的SWSTART位置1、外部触发。每次转换结束后转换结果存储在16位 ADC_DR中.EOC标志置1;EOCIE位置1时将产生中断
扫描:扫描一组通道。将ADC_CR1中的SCAN位置1选择扫描模式。ADC会扫描在ADC_SQRx寄存器(规则通道)或ADC_JSQR寄存器(注入通道)中选择的所有通道。为组中的每个通道都执行一次转换。自动注入模式下需要禁止外部事件触发,EXTEN/JEXTEN[1:0]=00
间断:将ADC_CR1的DISCEN位置1使能此模式。该模式可用于转换n(n≤8)个转换的短序列,该短序列是在ADC_SQRx中选择的转换序列的一部分。出现外部触发时,将启动在ADC_SQRx寄存器中选择的接下来n个转换,直到序列中的所有转换均完成为止。
4、转换时间计算
可使用ADC_SMPR1和ADC_SMPR2的SMPx[2:0]位修改采用周期数。ADC会在数个ADCCLK周期内对输入电压进行采样。
总转换时间 T c o n v = 采样时间 + 周期数 ( 分辨率 12 ) ∗ 每个 A D C C L K 的时长 T_{conv}=采样时间+周期数(分辨率12)*每个ADCCLK的时长 Tconv=采样时间+周期数(分辨率12)每个ADCCLK的时长
采样时间最少3周期,定义在ADC_SMPR1
5、如何启动ADC
ADC启动可由软件控制,将SWSTART/JSWSTART的位置1开始转换。也可由外部事件触发。
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC_Cmd(ADC1, ENABLE); //使能ADC1
void ADC_SoftwareStartConv(ADC_TypeDef* ADCx)
ADC_SoftwareStartConv(ADC1); //软件启动ADC1规则组转换
ADC1->CR2|=1<<30//寄存器方式
ADC_SoftwareStartInjectedConv(ADC1);//注入组
ADC1->CR2|=1<<22//寄存器方式
6、如何读取转换结果(注入组、规则组,库函数、寄存器)
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
return ADC_GetConversionValue(ADC1); //获取ADC1转换结果

u16 Get_Adc(u8 ch){
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_480Cycles );
	ADC_SoftwareStartConv(ADC1);	
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待ADC1规则组转换结束
	while((ADC1->SR&(1<<1))==0)//等待ADC1规则组转换结束,寄存器
	return ADC_GetConversionValue(ADC1);//规则组读取
	return ADC1->DR;//规则组读取,寄存器
	return ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1);//注入组读取
	return ADC1->JDR1;//注入组读取,寄存器
}

7、常用寄存器
ADC_SR:状态寄存器,STRT和JSTRT中断无关,其余位中断有关。
ADC_CR1:控制寄存器。
ADC_CR2:控制寄存器。CONT位=1位连续,CONT=0为单次
ADC_SMPR1/2:采样时间寄存器。每个通道可以设置不同采样时间。
ADC_JOFRx:注入通道数据偏移寄存器,转换注入通道时从原始转换数据减去偏移量
ADC_HTR:上阈值寄存器。
ADC_LTR:下阈值寄存器。
ADC_SQR1/2/3:定义通道顺序。
ADC_JSQR:注入通道顺序
ADC_DR:数据寄存器。AD转换结果。规则组数据寄存器低16位有用,可设置左右对齐。
ADC_JDR1/2/3/4:注入通道转换结果。
ADC_CSR:3个ADC公用的状态寄存器,只读。
ADC_CCR:公用控制寄存器,可以读写。
8、常用库函数

void ADC_CommonInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct);//通用控制寄存器ADC_CCR初始化
void ADC_Init(ADC_TypeDef* ADCx,ADC_InitTypeDef* ADC_InitStruct);//ADC_CR初始化
void ADC_DeInit(ADC_TypeDef* ADCx)//取消初始化
void ADC_Cmd(ADC_TypeDef* ADCx,FunctionalState NewState);
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT,FunctionalState NewState);//中断配置
void ADC_SoftwareStartConv(ADC_TypeDef* ADCx);//开始转换
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);//规则组通道配置
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

9、ADC数据采集程序,单通道连续转换
整体流程

RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  
GPIO_Init();//1.开启PA口时钟和ADC1时钟,设置PA1(ADC1通道1)为模拟输入
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);//2.复位ADC1
ADC_CommonInit();//3.初始化ADC_CCR寄存器,同时设置ADC1分频因子
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)//4.初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息
ADC_RegularChannelConfig();//5.配置规则通道参数
ADC_Cmd(ADC1, ENABLE);//6.使能ADC1
ADC_SoftwareStartConv(ADC1);//7.软件开启转换
ADC_GetConversionValue(ADC1);//8.等待转换完成读取ADC
NVIC_Init();
ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);
ADC_IRQHandler();//9.如采用中断方式,还需
void  Adc_Init(void){    
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);  
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); 
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; 
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 
    ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4   //不能超过36MHz
    ADC_CommonInit(&ADC_CommonInitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; 
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfConversion = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    ADC_Cmd(ADC1, ENABLE);	
}		

第13讲DAC

挂载在APB1上
1、STM32F4 DAC简介:特点、数据格式、分辨率等
特点:12位数字输入,电压输出型的DAC
数据格式/分辨率:
8 位右对齐:必须将数据加载到 DAC_DHR8Rx [7:0] 位(存储到DHRx[11:4] 位)。
12 位左对齐:必须将数据加载到 DAC_DHR12Lx [15:4] 位(存储到DHRx[11:0] 位)。
12 位右对齐:必须将数据加载到 DAC_DHR12Rx [11:0] 位(存储到DHRx[11:0] 位)。

2、输出电压计算方法
D A C O U T P U T = V R E F + ∗ D O R 4095 DAC_{OUTPUT}=V_{REF+}*\frac{DOR}{4095} DACOUTPUT=VREF+4095DOR
V R E F + V_{REF+} VREF+为参考电压输入, D O R DOR DOR为数据输出寄存器的值
3、主要寄存器
DAC_CR:控制寄存器
DAC_SWTRIGR:软件触发寄存器
DAC_DHR yyyx:数据输入寄存器,如DAC_DHR12R1,DAC_DHR12L1,DAC_DHR8R1
DAC_DORx:数据输出寄存器,通道1的数据为DAC_DOR1,通道2的数据为DAC_DOR2
DAC_SR:状态寄存器

4、常用库函数
void DAC_Init(uint32_t DAC_Channel,DAC_InitTypeDef* DAC_InitStruct);//初始化
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);//使能
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data);//写入DHR数据
uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel); //回读输出数据

5、如何启动DAC
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);//CR对应位置1
定时器 TRGO 输出或外部中断线 9 上检测到上升沿时,DAC_DHRx中的数据即会加载到DAC_DORx 中。再经过三个APB1 周期,DAC_DORx 将会得到更新。
如果选择软件触发,一旦 SWTRIG 位置 1, 转换即会开始.DAC_DHRx 加载到DAC_DORx 后,SWTRIG 即由硬件复位。

6、如何将一个数据送DAC转换(库函数、寄存器)
DAC_SetChannel1Data(DAC_Align_12b_R, 500);//将500送入,库函数
DAC->DHR12R1 = 500;//将500送入,寄存器
num=DAC->DOR1;
7、DAC产生相应波形输出的编程

void Dac1_Init(void){  
	GPIO_InitTypeDef  GPIO_InitStructure; DAC_InitTypeDef DAC_InitType;	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);	   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	DAC_InitType.DAC_Trigger=DAC_Trigger_None;
	DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;
	DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
	DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ;
	DAC_Init(DAC_Channel_1,&DAC_InitType);	
	DAC_Cmd(DAC_Channel_1, ENABLE);  
	DAC_SetChannel1Data(DAC_Align_12b_R, 0); 
}	 
void Dac1_Set_Vol(u16 vol){//设置通道1输出电压
	double temp=vol;
	temp/=1000;
	temp=temp*4096/3.3;//vol:0~3300,代表0~3.3V
	DAC_SetChannel1Data(DAC_Align_12b_R,temp);//12位右对齐数据格式DAC值
}	 	 
int main(void){ 
	u16 adcx;
	float temp;
 	u8 t=0;	 
	u16 dacval=0;
	u8 key;	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	delay_init(168);    
	uart_init(115200);
	LED_Init();		
 	Adc_Init(); 
	KEY_Init(); 
	Dac1_Init(); 
	DAC_SetChannel1Data(DAC_Align_12b_R,dacval);
	while(1){
		t++;
		key=KEY_Scan(0);			  
		if(key==WKUP_PRES){		 
			if(dacval<4000)
				dacval+=200;
			DAC_SetChannel1Data(DAC_Align_12b_R, dacval);
		}else if(key==KEY1_PRES)	{
			if(dacval>200)
				dacval-=200;
			else dacval=0;
			DAC_SetChannel1Data(DAC_Align_12b_R, dacval);
		}	 
		if(t==10||key==KEY1_PRES||key==WKUP_PRES) {	  
			adcx=DAC_GetDataOutputValue(DAC_Channel_1);
			printf(“DAC寄存器的值是:%d\n”,adcx);   
			temp=(float)adcx*(3.3/4096);
			printf(“DAC的输出电压是:%f\n”,temp);
			adcx=Get_Adc_Average(ADC_Channel_5,10);
			temp=(float)adcx*(3.3/4096);
			printf(“ADC采集到的电压是:%f\n”,temp); 
			LED0=!LED0;	   
			t=0;
		}	    
		delay_ms(10);	 
	}
}

第14讲 RTOS

1、理解嵌入式应用软件的设计的两种方式
前后台系统方式(裸机方式):没有嵌入式操作系统支持,直接操作裸机,在裸机上写程序,比如用51单片机进行设计,基本就没有操作系统的概念。通常把程序分为两部分:前台系统和后台系统。
RTOS方式:在嵌入式操作系统的支持下进行应用程序设计,由嵌入式操作系统进行内存、任务、外围设备等管理,减轻开发人员负担。

2、对RTOS有两个基本要求:功能正确、时间正确,硬实时、软实时
能使计算机系统及时响应外部事件的请求,并能及时控制所有实时设备与实时任务协调运行,且能在规定的时间内完成对事件的处理,逻辑或功能正确,时间正确.
硬实时操作系统:必须在极严格的时间内完成任务
软实时操作系统:对完成任务的截止时间要求不是十分严格,对超时有一定的容忍度

3、RTOS内核的两种类型
不可剥夺型内核(合作型多任务内核):总是优先级别高的任务最先获得CPU使用权,每个任务必须能主动放弃CPU使用权
可剥夺型内核:CPU总是运行多个任务中优先级别最高的任务,高级别任务可以剥夺正在运行任务的CPU使用权

4、任务的组成
由三部分组成:任务堆栈、任务控制块和任务函数
任务堆栈:上下文(任务)切换的时候用来保存任务的工作环境,即STM32的内部寄存器值
任务控制块:用来记录任务的各个属性
任务函数:由用户编写的任务处理代码
5、μCOSIII的特点
μC/OS III是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。μC/OS III提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。
μC/OS III是用C和汇编写的,其中绝大部分都是用C语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,结构简洁,可读性很强。文章来源地址https://www.toymoban.com/news/detail-479483.html

到了这里,关于【嵌入式系统】 期末复习提纲的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式期末复习题(一)选择题

    1.ARM Cortex-M3不可以通过( D  )唤醒CPU。 A.I/O端口    B.RTC 闹钟    C.USB唤醒事件 D.PLL 2 .   STM32F103 微控制器是基于(   A   )内核的 32 位微处理器。 A.Cortex-M3   B. Cortex-M4   C. Cortex-M6  D . Cortex-M8 3 .下面 哪种开发方式不适合STM32F103系列微处理器的开发?( 

    2024年02月03日
    浏览(42)
  • 嵌入式原理与应用期末复习汇总(附某高校期末真题试卷)

    1、为保证在启动服务器时自动启动DHCP进程,应对( B )文件进行编辑。 A、 /etc/rc.d/rc.inet2 B、/etc/rc.d/rc.inet1 C、/etc/dhcpd.conf D、/etc/rc.d/rc.S 2、文件exer1的访问权限为rw-r–r–,现要增加所有用户的执行权限和同组用户的写权限,下列命令正确的是( A )。 A 、chmod a+x g+w exer1

    2024年02月08日
    浏览(44)
  • 嵌入式学习stm32基础知识(期末复习)

    1. 计算机的体系架构 冯诺依曼架构 ​ 在完整的计算机系统中,包含五个部分,储存器,运算器,控制器输入设备和输出设备。 改进的冯诺依曼架构 改进型架构的各模块的高速数据交换中心利用储存器这个大容量,极大的提高了效率。 哈佛架构 ​ 哈佛结构数据空间和地址

    2024年02月07日
    浏览(65)
  • 嵌入式系统复习要点

    目录 1、嵌入式系统的核心部分主要由硬件和软件两部分组成: 2、嵌入式系统硬件: 3、嵌入式处理器从体系上分类,可以分为冯·诺依曼结构和哈佛结构两种: 4、几类常见的嵌入式处理器类型: 5、MCU组成结构: 7、DSP组成结构: 8、SoC组成结构: 9、MPU组成结构: 10、嵌入

    2024年02月12日
    浏览(39)
  • 西邮嵌入式系统复习

    可以私聊我获取pdf版本! 提示:这里可以添加本文要记录的大概内容: 期末复习知识点总结 第一次整理 提示:以下是本篇文章正文内容,下面案例可供参考 1.1.0 嵌入式系统发展 (选择 4个阶段) 无操作系统的单片机阶段–以微控制器为基础、以简单操 作系统为核心的嵌入

    2024年02月15日
    浏览(38)
  • 【嵌入式系统】课程复习资料整理

    【嵌入式系统】课程复习资料整理 一、绪论 1.定义 从技术的角度定义:以应用为中心、以计算机技术为基础、软件硬件可裁剪、对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。 从系统的角度定义:嵌入式系统是设计完成复杂功能的硬件和软件,并使其紧密

    2024年02月03日
    浏览(44)
  • 嵌入式系统设计师考试笔记之操作系统基础复习笔记二

    目录 3、任务管理 (1)嵌入式操作系统的任务管理可以分为 (2)进程 (3)线程 (4)任务 (5)任务的创建与中止 (6)任务的状态任务有三中基本状态: (7)任务控制块 TCB (8)任务的切换 (9)任务的调度 (10)实时系统调度 (11)任务互斥 (12)信号量 (13)任务同

    2024年02月08日
    浏览(48)
  • 南邮-嵌入式复习附录/习题

    附录1-6 内容可能有误,欢迎大家指正。 (1)ARM汇编程序由 机器 指令、 汇编 指令和 伪 指令构成。 (2)ARM伪指令可以分为以下几类:符号定义伪指令、数据定义伪指令、汇编控制伪指令和宏指令等。 (3)假设存储数据0x12345678于ARM微处理器内存0X30000000开始的位置,则0X3

    2024年02月05日
    浏览(36)
  • 嵌入式学习之C语言指针部分复习

    今天主要把C语言的指针部分再次认真的复习了一下,对于指针的整体框架有了更加深刻的理解,特别要重点区分函数指针,指针函数,数组指针,指针数组部分,对于这部分的应用非常重要,而且C语言指针部分是面试的重中之重,所以要加强练习才行。 今天的学习是充满动

    2024年02月12日
    浏览(38)
  • 【嵌入式总复习】Linux管道详解——管道通信、无名管道、有名管道、具体应用示例

    进程间的通信方式有五种,分别为:管道,信号量,共享内存,消息队列和套接字 把一个程序的输出直接连接在另外一个程序的输入。 管道分为有名管道和无名管道两种,它们的区别是: 无名管道只能在父子进程之间进行通信。 有名管道又称为命名管道,可以在任意两个进程之间进行

    2024年02月08日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包