文章目录
- 嵌入式开发常识汇总
- 1、嵌入式Linux和stm32之间的区别和联系
- 2、stm32程序下载方式
- 3、Keil5安装芯片包
- 4、芯片封装种类
- 5、STM32命名
- 6、数据手册和参考手册
- 7、什么是寄存器、寄存器映射和内存映射
- 8、芯片引脚顺序
- 9、stm32芯片里有什么
- 10、存储器空间的划分
- 11、如何理解寄存器说明
- 12、如何操作寄存器的某一位
- 13、Doxygen注释规范
- 14、stm32位带操作
- 15、最小系统
- 16、超时时间
- STM32F407芯片学习
- 1、stm32单片机启动流程
- stm32启动模式选择
- 启动流程
- 2、新建一个工程
- 1、本地目录设置
- 2、Keil新建工程
- 3、编译项目,看有无错误
- 3、GPIO寄存器分析和使用
- 1、GPIO寄存器
- 2、GPIO四种模式怎样配置及使用场景
- 3、 使用GPIO点亮LED灯
- 4、按键检测
- 4、启动文件
- 1、启动文件干了什么
- 2、如何选择启动文件
- 3、启动文件分析
- 5、配置时钟
- 1、相关名词
- 2、时钟树
- 3、系统时钟APB、AHB总线时钟分析
- 4、时钟与复位寄存器RCC
- 5、其他时钟
- 6、系统如何设置的时钟
- 6、STM32的中断
- 1、中断概述
- 2、中断编程管理机构NVIC
- 3、中断编程
- 1、中断优先级
- 2、NVIC的配置
- 3、中断编程流程
- 7、EXTI—外部中断/事件控制器
- 1、EXTI概述
- 2、如何将EXTI设置为GPIO的某端口
- 3、外部中断编程顺序
- 4、外部中断实验
- 问题2:为什么要手动清除中断标志位(wait)
- 8、SysTick
- 1、SysTick是什么
- 2、SysTick寄存器
- 3、SysTick的使用
- 4、SYsTick实现延时
- 9、STM32常见通讯协议
- 如何学习一个通讯协议
- 常见通讯概念
- 1、RS232
- 1、用途
- 2、物理层
- 3、协议层
- 10、STM32USART
- 1、简介
- 2、使用USART
- 3、问题记录
- 4、USART向电脑发送数据并使用中断接收数据
- 5、重定向C库printf函数
- 7、软件模拟USART 通信(待完成)
- 11、DMA(Direct Memory Access, 直接存储区访问)
- 1、DMA简介
- 2、存储器到存储器的传输
- 1、DMA使用注意
- 2、代码及现象
- 3、存储器到外设USART1
- 1、注意事项
- 2、代码
- 12、IIC读写EEPROM
- 1、IIC(Inter - Integrated Circuit)
- 物理层
- 协议层
- 复合I2C通讯
- 协议各信号电平表现
- 2、STM32IIC
- 3、I2C读写EEPROM
- 1、野火霸天虎开发板EEPROM((型号:AT24C02))硬件连接
- 2、I2C通讯为啥目标引脚为啥为开漏模式
- 3、接收数据时等待ev7之后再调用接收函数,接收数据之前检测总线是否空闲
- 4、代码
嵌入式开发常识汇总
1、嵌入式Linux和stm32之间的区别和联系
1)嵌入式MPU:Miroc Processor Unit 微处理器单元,一般指性能更高的芯片,可以运行Linux+ARM架构,一般跑Linux或者Android系统,可以移植Qt。
2)嵌入式MCU:Micro Control Unit 微控制器单元,常见的有stm32、51、ESP8266等,芯片资源较少,性能较低。
3)嵌入式DSP:Digital Signal Procrssor :专门用于数字信号处理的芯片。
MPU和MCU都是芯片,只不过性能和资源不同,在MCU比如stm32Fxx系列的单片机性能较低,不能支持Linux这些较大的系统运行,只能运行FreeRTOS等实时系统,这些系统内核较为简单,运行起来并不需要太多的资源。在MCU上可以使用emXGUI、emWIN等图形框架。在MPU上可以运行Linux、Android等系统,界面开发一般选用Qt。在stm系列中有也有性能强大的芯片可以支持Linux系统。所以,MCU和MPU并不是以芯片公司或名称区分,只看芯片的性能。
2、stm32程序下载方式
1)串口下载:此方法需要USB转TTL下载器。stm32从系统存储器启动,boot0=1,boot1=0,需要串口转TTl,电脑安装CH340驱动,使用串口下载助手,将USB 转 TTL下载器(CH340)的TXD、RXD、GND与开发板的RXD、TXD、GND连接好,下载器另一头插电脑,下载文件即可(工具软件:FlyMcu)
2)STLINK:此方法需要STLINK下载器。电脑安装STLINK驱动,将ST-Link V2的SWDIO、GND、SWCLK、3.3V接到开发板的DIO、GND、CLK、3.3引脚上,有些板的丝印标法不同,但都能对应的看出来,然后将ST-Link插电脑上,在Keil软件上设置好Debug选项,直接下载。此方法下载程序到stm32 的Flash,所以boot引脚和串口下载不同。
3、Keil5安装芯片包
为了可以支持对相应芯片的开发,在安装好Keil软件之后还需要自己安装芯片包,否则在新建工程时找不到对应芯片。keil软件官方提供各种芯片所需要的包。地址:http://www.keil.com/dd2/pack/
4、芯片封装种类
参考文档:https://blog.csdn.net/ffdia/article/details/116222777
5、STM32命名
6、数据手册和参考手册
数据手册主要用于芯片选型和设计原理图时参考,参考手册主要用于在编程的时候查阅外设的功能和寄存器说明
7、什么是寄存器、寄存器映射和内存映射
STM32有32根地址线,理论上其寻址空间为2的32次方,所以STM32的有效地址最多有4G个,每个地址代表1B,STM32的存储地址就有4GB。
然而上述都是理论上的,STM32可以使用的存储空间远远没有4GB。
映射的意思就是将理论上存在的地址对应到具体的设备,如:0x1234 5678 ~ 0x2234 5678 ==>flash,在硬件上将对应的线路连接至Flash,这样在访问这些地址时就是在访问Flash设备,通过指针对这些地址读写就是对Flash的读写。
内存映射(存储器映射): 将理论上的地址对应到具体的外设上。
寄存器映射 :给对应的内存地址取个别名,便于记忆和访问内存地址 例如:define REGISTER 0x1111 2222
寄存器:为不同内存地址取的别名,别名与内存映射的外设有关,如上面的REGISTER,若定义 (uint_16*)REGISTER,则REGISTER寄存器就代表 0x1111 2222 往后的16个地址,对寄存器的操作就是对这16个地址操作,一般使用定义结构体,这样只需要将结构体首地址对应到寄存器基地址,结构体中的成员就可以按照对应的基地址往后占位,操作结构体成员,就是在对基地址之后的地址操作。
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
8、芯片引脚顺序
一般在芯片的一角有一个缺口或者小圆点,此处为1脚,引脚按逆时针排序。
9、stm32芯片里有什么
1)ContexM内核====>相当于CPU,由ARM公司设计
2)片上外设====>ISP、USART、ADC、DAC、FSMC、Flash、RAM等,这些由ST厂家设计
3)系统总线矩阵==>连接外设和内核,由ST厂家设计。
10、存储器空间的划分
存储器空间被ARM公司打开划分为8块(block0~block7),每块大小为512MB。最重要就是Block0 ~ 2 这三块,Block0 为内部Flash,用作程序存放,Block1为内部RAM,Block2为外设地址,ST公司将Block2划分为AHB、APB,APB又划分为APB1、APB2,AHB又划分为AHB1、AHB2.
11、如何理解寄存器说明
12、如何操作寄存器的某一位
使用逻辑或或逻辑与,例如操作寄存器32位reg的第六位
置1:( * reg)|=( 0x01<<6);
置0:( * reg)&=~(0x01<<6)
13、Doxygen注释规范
14、stm32位带操作
1)位带区:正常的存储空间,可以正常访问,但是不能以位(1Bty为8位)访问。
2)位带别名区:正常的存储空间,可以正常访问,以32位访问,最低位的操作会一一对应到位带区的一位
3)位带区和位带别名区的联系
4)位带操作:对位带区的某一位A的操作转化为对位带别名区的32位B的操作,B的最低位的值会写入A。
5)stm32f407的位带区:一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低1MB 空间,所以这两个位带区分别有32MB的对应位带别名区
6)位带区与对应位带别名区的地址的转换:不同单片机不同,百度搜索。
15、最小系统
最小系统应该包括电源电路、晶振电路、下载电路、复位电路及BOOT启动电路,这一样芯片才可以被使用
16、超时时间
在使用USART I2C等外设通讯时需要等到SR寄存器状态位来确定下一步操作,这时应该设置超时时间,防止一直等待不到想要的状态导致程序卡死。
STM32F407芯片学习
1、stm32单片机启动流程
stm32启动模式选择
复位后,在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值。BOOT0 为专用引脚,而 BOOT1 则与 GPIO 引脚共用。一旦完成对 BOOT1 的采样,相应 GPIO 引脚即进入空闲状态,可用于其它用途。BOOT0与BOOT1引脚的不同值指向了三种启动方式:
从主Flash启动。主Flash指的是STM32的内置Flash。选择该启动模式后,内置Flash的起始地址将被重映射到0x00000000地址,代码将在该处开始执行。一般我们使用JTAG或者SWD模式下载调试程序时,就是下载到这里面,重启后也直接从这启动。
从系统存储器启动。系统储存器指的是STM32的内置ROM,选择该启动模式后,内置ROM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。ROM中有一段出厂预置的代码,这段代码起到一个桥的作用,允许外部通过UART/CAN或USB等将代码写入STM32的内置Flash中。这段代码也被称为ISP(In System Programing)代码,这种烧录代码的方式也被称为ISP烧录。关于ISP、ICP和IAP之间的区别将在后续章节中介绍。
从嵌入式SRAM中启动。显然,该方法是在STM32的内置SRAM中启动,选择该启动模式后,内置SRAM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。这种模式由于烧录程序过程中不需要擦写Flash,因此速度较快,适合调试,但是掉电丢失。
原文链接
启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。
值得注意的是STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。
启动流程
1)进入启动文件(厂家提供)
2)设置内存地址
3)设置向量表
4)设置时钟
5)初始化寄存器
6)设置堆栈
7)设置各指针值
8)进去main函数
以上操作在启动文件中的顺序可以参考启动文件中的汇编代码。
2、新建一个工程
1、本地目录设置
1)在本地新建项目文件夹
2)新建子目录用于存放对应文件
3)将厂家提供的文件放至对应新建目录
CMSIS下的文件
启动文件
寄存器配置文件、系统初始化文件
内核外设文件,拷贝至Lib
下图stm32f4xx_conf.h为系统资源管理文件,用以管理外设头文件,存放之User,设置默认宏USE_STDPERIPH_DRIVER启用该文件。
2、Keil新建工程
1)创建项目
2)设置项目目录
3)向项目目录添加文件
4)设置项目,使用C库文件,在使用串口时可以使用printf函数。
默认宏
设置USE_STDPERIPH_DRIVER,STM32F40XX这两个宏之后,在编译程序时会根据宏添加所需要的文件。
设置Debug,选择要使用的调试器,如STLINK等
设置调试器选项
3、编译项目,看有无错误
根据错误添加头文件路径或头文件
再次编译,出现错误,f407没该外设,项目中删除该外设的文件
再次编译,报错
TimingDelay_Decrement();函数在系统SysTick的中断服务函数中调用,但是并没有实现此函数,所以报错,该函数应该提供的内容可以是操作全局变量计时。先在还没有定义此函数,注释即可。再次编译。
未定义SystemInit函数,该函数在启动文件中被引用,但是并没有实现,可以在其他地方实现该函数,后者在启动文件中删除SystemInit函数相关语句。在使stm32f4xx_it.c中实现该函数。再次编译。
使用MDK5.XX以后的版本,用户不需要写上面的预定义,因为在选择相应器件以后,编译器已经将相应的头文件加入了。取消默认宏,再次编译。
无错误,无告警。下载程序至开发板成功
至此,新建项目完成。
3、GPIO寄存器分析和使用
1、GPIO寄存器
参考手册中又详细内容
1)端口模式寄存器 GPIOx_MODER:配置端口模式,有四种:输入、输出、复用模拟
2) 端口输出类型寄存器 GPIOx_OTYPER:输出模式时需要配置,推挽或开漏
开漏输出,通过一个NMOS管和上拉电阻实现
推挽输出,通过两个mos管实现
3)GPIO 端口输出速度寄存器 GPIOx_OSPEEDR:配置端口电平转换速度,即IO口输出速度
4) 端口上拉/下拉寄存器 GPIOx_PUPDR:无上拉下拉、上拉、下拉、保留。
当GPIO处于output模式,一般选择no pull,引脚能够正确地输出输出高电平低电平信号
当GPIO处于input模式,需要根据默认的输入值来确定配置模式,如果默认输入的值为1时,最好配置为pull up,默认值为0,则选择下拉。
所以上下拉就是选择在默认情况下gpio端口的电平高低。
5)端口输入数据寄存器 GPIOx_IDR:端口输入的数据,即电平高低,该寄存器只能读,低16位有效。
6)端口输出数据寄存器 GPIOx_ODR:端口输出寄存器,低16位有效,可读可写,用以获取或设置端口的输出电平。
7) 端口置位/复位寄存器 GPIOx_BSRR:只能写,高16位BRy(复位),这些位置1,则对应端口复位。低16位BSy(置位),这些位置1,则对应端口置位。复位时,复位位复位值,置位值与复位值相反。对ODR操作。同时对BSx和BRx操作,BSx优先级更高,在对BSSR写后会自动清零。
8)端口配置锁定寄存器 GPIOx_LCKR:可读可写,低17位有效。由于锁定某GPIO端口的配置,每一位锁定某个寄存器的配置。锁定后在复位之前无法更改该端口位的值。
LCKR[16] = 0,未锁定,可以对LCKR[15:0]操作;
LCKR[15:0]:对应一个GPIO端口位,对应位置1,则锁定配置。
9)复用功能低位寄存器 GPIOx_AFRL:四位一组,确定一个端口的复用功能(0~7位端口)
10)复用功能高位寄存器 GPIOx_AFRL:四位一组,确定一个端口的复用功能(8~15位端口)
2、GPIO四种模式怎样配置及使用场景
1)输出模式:此模式需要配置推挽或开漏,对于是否上下拉或者悬空其实没啥影响,输出速度按情况分析。
2)输入模式:此模式需要指定上拉或是下拉,用以确定端口空闲(即外部无输入)时端口电平状态,输入速度按情况分析。
3)模拟模式:模拟输入用作ADC电压采集,模拟输出用作DAC数模转换。配置为模拟模式。
4)复用模式:配置为复用模式,配置复用寄存器即可
3、 使用GPIO点亮LED灯
1)确定LED的端口和连接方式
共阳极连接,GPIO默认输出高电平,则灯灭,控制使出低电平则灯亮,连接端口位PF6、7、8(红、蓝、绿)
2)配置GPIOF6、7、8位上拉、推挽、高速模式(对于LED并不重要)
3)写头文件和C文件
问题记录:
问题1:写好后,LED无反应
原因:再新建工程时编译报错未定义SystemInit函数,所以自己在stm32f4xx_it.c中自己写了一个空函数,实际上该函数在system_stm32f4xx.c中,但新建文件时并没有将该文件加入工程,SystemInit函数中设置了时钟等系统资源,将此函数变为空函数则系统时钟等系统资源未初始化,所以代码无法执行。
解决:将system_stm32f4xx.c文件加入项目,删除自己写的空函数
问题2:初始化GPIO后,LED点亮.
原因:ODR复位值为0,LED为共阳极连接,所以导致电压一侧高,GPIO端口处电压低,导致电流流过LED。
解决:初始化GPIO后将将端口置位。
代码
#include "bsp_led.h"
void BSP_LEDInit(void)
{
RCC_AHB1PeriphClockCmd(LED_RAD_CLK,ENABLE);
RCC_AHB1PeriphClockCmd(LED_BLUE_CLK,ENABLE);
RCC_AHB1PeriphClockCmd(LED_GREE_CLK,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = LED_RAD_PIN;
GPIO_InitStruct.GPIO_Mode = LED_RAD_Mode;
GPIO_InitStruct.GPIO_Speed = LED_RAD_Speed;
GPIO_InitStruct.GPIO_OType = LED_RAD_OType;
GPIO_InitStruct.GPIO_PuPd = LED_BLUE_PuPd;
GPIO_Init(LED_RAD_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = LED_GREE_PIN;
GPIO_InitStruct.GPIO_Mode = LED_GREE_Mode;
GPIO_InitStruct.GPIO_Speed = LED_GREE_Speed;
GPIO_InitStruct.GPIO_OType = LED_GREE_OType;
GPIO_InitStruct.GPIO_PuPd = LED_BLUE_PuPd;
GPIO_Init(LED_GREE_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = LED_BLUE_PIN;
GPIO_InitStruct.GPIO_Mode = LED_BLUE_Mode;
GPIO_InitStruct.GPIO_Speed = LED_BLUE_Speed;
GPIO_InitStruct.GPIO_OType = LED_BLUE_OType;
GPIO_InitStruct.GPIO_PuPd = LED_BLUE_PuPd;
GPIO_Init(LED_BLUE_PORT,&GPIO_InitStruct);
LED_SetSatus(LED_RAD,DISABLE);
LED_SetSatus(LED_GREE,DISABLE);
LED_SetSatus(LED_BLUE,DISABLE);
}
void LED_SetSatus(LEDx led,FunctionalState status)
{
if(status)
{
GPIO_ResetBits(GPIOF,(uint32_t)(0x01<<led));
}
else{
GPIO_SetBits(GPIOF,(uint32_t)(0x01<<led));
}
}
头文件
#ifndef __BSP_LED_H
#define __BSP_LED_H
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#define LED_RAD_PORT GPIOF
#define LED_RAD_PIN GPIO_Pin_6
#define LED_RAD_Mode GPIO_Mode_OUT
#define LED_RAD_Speed GPIO_Low_Speed
#define LED_RAD_OType GPIO_OType_PP
#define LED_RAD_PuPd GPIO_PuPd_NOPULL
#define LED_RAD_CLK RCC_AHB1Periph_GPIOF
#define LED_GREE_PORT GPIOF
#define LED_GREE_PIN GPIO_Pin_7
#define LED_GREE_Mode GPIO_Mode_OUT
#define LED_GREE_Speed GPIO_Low_Speed
#define LED_GREE_OType GPIO_OType_PP
#define LED_GREE_PuPd GPIO_PuPd_UP
#define LED_GREE_CLK RCC_AHB1Periph_GPIOF
#define LED_BLUE_PORT GPIOF
#define LED_BLUE_PIN GPIO_Pin_8
#define LED_BLUE_Mode GPIO_Mode_OUT
#define LED_BLUE_Speed GPIO_Low_Speed
#define LED_BLUE_OType GPIO_OType_PP
#define LED_BLUE_PuPd GPIO_PuPd_UP //猜想:设置为下拉,初始化后LED亮
#define LED_BLUE_CLK RCC_AHB1Periph_GPIOF
typedef enum
{
LED_RAD = 6,
LED_GREE = 7,
LED_BLUE = 8
}LEDx;
void BSP_LEDInit(void);
void LED_SetSatus(LEDx led,FunctionalState status);
#endif /*__BSP_LED_H*/
在头文件中将端口参数重定义是为了以后移植方便
4、按键检测
1)确定硬件连接:
两个按键分别连接到了PA0和PC13,按键没有按下时外部为3.3V,按下后电路接地为0V,所以只要检测按键连接的端口是否为低电平即可确定按键是否按下,而且外部电路确定,配置为上拉、下拉、或者浮空并没有影响(但是若是端口用于模拟通信时,上下拉就有作用了,应为模拟通信时用电平高低确认通信线路是否空闲)
2)配置PA0、PC13为输入模式,浮空状态,快速模式
3)问题记录:无
4)C代码及头文件
头文件
#ifndef __BSP_KEY_H
#define __BSP_KEY_H
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#define KEY_1_PORT GPIOA
#define KEY_1_PIN GPIO_Pin_0
#define KEY_1_Mode GPIO_Mode_IN
#define KEY_1_Speed GPIO_Fast_Speed
#define KEY_1_OType GPIO_OType_PP
#define KEY_1_PuPd GPIO_PuPd_NOPULL
#define KEY_1_CLK RCC_AHB1Periph_GPIOA
#define KEY_2_PORT GPIOC
#define KEY_2_PIN GPIO_Pin_13
#define KEY_2_Mode GPIO_Mode_IN
#define KEY_2_Speed GPIO_Fast_Speed
#define KEY_2_OType GPIO_OType_PP
#define KEY_2_PuPd GPIO_PuPd_NOPULL
#define KEY_2_CLK RCC_AHB1Periph_GPIOC
typedef enum
{
KEY_OFF = 0,
KEY_ON = !KEY_OFF
}KEY_Status;
void BSP_KEYInit(void);
KEY_Status KEY_Scan(GPIO_TypeDef* KEY_x_PORT,uint16_t KEY_x_PIN);
#endif /*__BSP_KEY_H*/
C文件
#include "bsp_key.h"
void BSP_KEYInit(void)
{
RCC_AHB1PeriphClockCmd(KEY_1_CLK|KEY_2_CLK,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = KEY_1_PIN;
GPIO_InitStruct.GPIO_Mode = KEY_1_Mode;
GPIO_InitStruct.GPIO_Speed = KEY_1_Speed;
GPIO_InitStruct.GPIO_OType = KEY_1_OType;
GPIO_InitStruct.GPIO_PuPd = KEY_1_PuPd;
GPIO_Init(KEY_1_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = KEY_2_PIN;
GPIO_InitStruct.GPIO_Mode = KEY_2_Mode;
GPIO_InitStruct.GPIO_Speed = KEY_2_Speed;
GPIO_InitStruct.GPIO_OType = KEY_2_OType;
GPIO_InitStruct.GPIO_PuPd = KEY_2_PuPd;
GPIO_Init(KEY_2_PORT,&GPIO_InitStruct);
}
KEY_Status KEY_Scan(GPIO_TypeDef* KEY_x_PORT,uint16_t KEY_x_PIN)
{
if(GPIO_ReadInputDataBit(KEY_x_PORT, KEY_x_PIN) == 0)
{
while(GPIO_ReadInputDataBit(KEY_x_PORT, KEY_x_PIN) == 0);
return KEY_ON;
}
return KEY_OFF;
}
4、启动文件
1、启动文件干了什么
- 初始化堆栈指针 SP=_initial_sp
- 初始化 PC 指针 =Reset_Handler
- 初始化中断向量表
- 配置系统时钟
- 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数,进去我们写得函数
2、如何选择启动文件
1)stm32不同系列官方会提供不同的固件库包
2)根据提供的启动文件按照芯片类型选择,如野火霸天虎V2,使用stm32f407系列芯片,选用后缀相同的文件即可,有些芯片同系列内部falsh大小不同,还需要根据内部flash大小选择,如stm32f1系列
3、启动文件分析
启动文件由汇编语言编写,在汇编语言中注释用** ;**,其中的指令均为ARM指令,常见的ARM指令如下
;******************** (C) COPYRIGHT 2016 STMicroelectronics ********************
;* File Name : startup_stm32f40_41xxx.s
;* Author : MCD Application Team
;* @version : V1.8.0
;* @date : 09-November-2016
;* Description : STM32F40xxx/41xxx devices vector table for MDK-ARM toolchain.
;* This module performs:
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Configure the system clock and the external SRAM mounted on
;* STM324xG-EVAL board to be used as data memory (optional,
;* to be enabled by user)
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>
;*******************************************************************************
;
; Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
; You may not use this file except in compliance with the License.
; You may obtain a copy of the License at:
;
; http://www.st.com/software_license_agreement_liberty_v2
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
;*******************************************************************************
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;在文件开头定义了栈的大小0x00000400 0x400 = 0100 0000 0000 = 2^10 = 1K 栈大小为 1KB
Stack_Size EQU 0x00000400
;告诉汇编器汇编一个新的代码段或者数据段。STACK 表示段名,这个可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 ;2^3 对齐,即 8 字节对齐。
AREA STACK, NOINIT, READWRITE, ALIGN=3
;分配栈空间,单位为字节,大小为Stack_Size= 0x00000400
Stack_Mem SPACE Stack_Size
;标号 __initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的
__initial_sp
;栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部 SRAM 的大小(栈空间在SRAM内),如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;定义堆的大小
Heap_Size EQU 0x00000200 = 0010 0000 0000 = 2^9 = 512B
;开始一个新的代码段,不初始化,可读可写,以2的3次方即8字节对齐
AREA HEAP, NOINIT, READWRITE, ALIGN=3
;堆的起始地址
__heap_base
;分配堆空间
Heap_Mem SPACE Heap_Size
;堆的结束地址,这里和栈不同,因为栈是逆向生长所以只制定了大小和起始地址,即使栈使用的过多也会在最小地址处,不会越限而栈是正向生长所以指定了大 ;小,结束地址和起始地址防止空间越限
__heap_limit
;指定当前文件的堆栈按照 8 字节对齐
PRESERVE8
;表示后面指令兼容 THUMB 指令
THUMB
; Vector Table Mapped to Address 0 at Reset
;开启一个新的代码段RESET,数据段,只读,并声明了三个全局变量__Vectors __Vectors_End __Vectors_Size
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
;上面声明的全局变量__Vectors,即向量表,将各个中断服务函数的地址写入向量表
;;__Vectors向量表开始地址,DCD为分配空间指令分配四字节大小,DCD __initial_sp 表示分配四字节地址并初始化,内容为__initial_sp,栈顶地址
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
;以上为系统异常,一下为外部中断
; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
DCD EXTI3_IRQHandler ; EXTI Line3
DCD EXTI4_IRQHandler ; EXTI Line4
DCD DMA1_Stream0_IRQHandler ; DMA1 Stream 0
DCD DMA1_Stream1_IRQHandler ; DMA1 Stream 1
DCD DMA1_Stream2_IRQHandler ; DMA1 Stream 2
DCD DMA1_Stream3_IRQHandler ; DMA1 Stream 3
DCD DMA1_Stream4_IRQHandler ; DMA1 Stream 4
DCD DMA1_Stream5_IRQHandler ; DMA1 Stream 5
DCD DMA1_Stream6_IRQHandler ; DMA1 Stream 6
DCD ADC_IRQHandler ; ADC1, ADC2 and ADC3s
DCD CAN1_TX_IRQHandler ; CAN1 TX
DCD CAN1_RX0_IRQHandler ; CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; External Line[9:5]s
DCD TIM1_BRK_TIM9_IRQHandler ; TIM1 Break and TIM9
DCD TIM1_UP_TIM10_IRQHandler ; TIM1 Update and TIM10
DCD TIM1_TRG_COM_TIM11_IRQHandler ; TIM1 Trigger and Commutation and TIM11
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; External Line[15:10]s
DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line
DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line
DCD TIM8_BRK_TIM12_IRQHandler ; TIM8 Break and TIM12
DCD TIM8_UP_TIM13_IRQHandler ; TIM8 Update and TIM13
DCD TIM8_TRG_COM_TIM14_IRQHandler ; TIM8 Trigger and Commutation and TIM14
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD DMA1_Stream7_IRQHandler ; DMA1 Stream7
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0
DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1
DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2
DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3
DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4
DCD ETH_IRQHandler ; Ethernet
DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line
DCD CAN2_TX_IRQHandler ; CAN2 TX
DCD CAN2_RX0_IRQHandler ; CAN2 RX0
DCD CAN2_RX1_IRQHandler ; CAN2 RX1
DCD CAN2_SCE_IRQHandler ; CAN2 SCE
DCD OTG_FS_IRQHandler ; USB OTG FS
DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5
DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6
DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7
DCD USART6_IRQHandler ; USART6
DCD I2C3_EV_IRQHandler ; I2C3 event
DCD I2C3_ER_IRQHandler ; I2C3 error
DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out
DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In
DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
DCD CRYP_IRQHandler ; CRYP crypto
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU
;向量表结束地址
__Vectors_End
;计算栈大小,综上来看向量表就是一个数组,元素大小为4字节,数组下标即为异常编号,元素内容为中断服务函数名,即中断服务函数入口
__Vectors_Size EQU __Vectors_End - __Vectors
;定义一个名称为.text 的代码段,可读。
AREA |.text|, CODE, READONLY
; Reset handler
;PROC(定义一个子函数)复位函数
Reset_Handler PROC
;声明全局变量Reset_Handler,并且是弱引用,即若是用户在其他地方实现该函数则使用用户定义的 Reset_Handler 函数
EXPORT Reset_Handler [WEAK]
;引用外部函数 SystemInit,__main
;SystemInit() 是一个标准的库函数,在 system_stm32f4xx.c 这个库文件总定义。主要作用是配置系统时钟.
IMPORT SystemInit
;__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,最终调用 main 函数.
IMPORT __main
LDR R0, =SystemInit
BLX R0
;若修改为 LDR R0, =user_main,则主函数为user_main而不是main
LDR R0, =__main
BX R0
;定义子程序
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
;定义子函数NMI_Handler(不可屏蔽中断)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Stream0_IRQHandler [WEAK]
EXPORT DMA1_Stream1_IRQHandler [WEAK]
EXPORT DMA1_Stream2_IRQHandler [WEAK]
EXPORT DMA1_Stream3_IRQHandler [WEAK]
EXPORT DMA1_Stream4_IRQHandler [WEAK]
EXPORT DMA1_Stream5_IRQHandler [WEAK]
EXPORT DMA1_Stream6_IRQHandler [WEAK]
EXPORT ADC_IRQHandler [WEAK]
EXPORT CAN1_TX_IRQHandler [WEAK]
EXPORT CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_TIM9_IRQHandler [WEAK]
EXPORT TIM1_UP_TIM10_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_TIM11_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTC_Alarm_IRQHandler [WEAK]
EXPORT OTG_FS_WKUP_IRQHandler [WEAK]
EXPORT TIM8_BRK_TIM12_IRQHandler [WEAK]
EXPORT TIM8_UP_TIM13_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_TIM14_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT DMA1_Stream7_IRQHandler [WEAK]
EXPORT FSMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_DAC_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Stream0_IRQHandler [WEAK]
EXPORT DMA2_Stream1_IRQHandler [WEAK]
EXPORT DMA2_Stream2_IRQHandler [WEAK]
EXPORT DMA2_Stream3_IRQHandler [WEAK]
EXPORT DMA2_Stream4_IRQHandler [WEAK]
EXPORT ETH_IRQHandler [WEAK]
EXPORT ETH_WKUP_IRQHandler [WEAK]
EXPORT CAN2_TX_IRQHandler [WEAK]
EXPORT CAN2_RX0_IRQHandler [WEAK]
EXPORT CAN2_RX1_IRQHandler [WEAK]
EXPORT CAN2_SCE_IRQHandler [WEAK]
EXPORT OTG_FS_IRQHandler [WEAK]
EXPORT DMA2_Stream5_IRQHandler [WEAK]
EXPORT DMA2_Stream6_IRQHandler [WEAK]
EXPORT DMA2_Stream7_IRQHandler [WEAK]
EXPORT USART6_IRQHandler [WEAK]
EXPORT I2C3_EV_IRQHandler [WEAK]
EXPORT I2C3_ER_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_OUT_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_IN_IRQHandler [WEAK]
EXPORT OTG_HS_WKUP_IRQHandler [WEAK]
EXPORT OTG_HS_IRQHandler [WEAK]
EXPORT DCMI_IRQHandler [WEAK]
EXPORT CRYP_IRQHandler [WEAK]
EXPORT HASH_RNG_IRQHandler [WEAK]
EXPORT FPU_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMP_STAMP_IRQHandler
RTC_WKUP_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Stream0_IRQHandler
DMA1_Stream1_IRQHandler
DMA1_Stream2_IRQHandler
DMA1_Stream3_IRQHandler
DMA1_Stream4_IRQHandler
DMA1_Stream5_IRQHandler
DMA1_Stream6_IRQHandler
ADC_IRQHandler
CAN1_TX_IRQHandler
CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_TIM9_IRQHandler
TIM1_UP_TIM10_IRQHandler
TIM1_TRG_COM_TIM11_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTC_Alarm_IRQHandler
OTG_FS_WKUP_IRQHandler
TIM8_BRK_TIM12_IRQHandler
TIM8_UP_TIM13_IRQHandler
TIM8_TRG_COM_TIM14_IRQHandler
TIM8_CC_IRQHandler
DMA1_Stream7_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_DAC_IRQHandler
TIM7_IRQHandler
DMA2_Stream0_IRQHandler
DMA2_Stream1_IRQHandler
DMA2_Stream2_IRQHandler
DMA2_Stream3_IRQHandler
DMA2_Stream4_IRQHandler
ETH_IRQHandler
ETH_WKUP_IRQHandler
CAN2_TX_IRQHandler
CAN2_RX0_IRQHandler
CAN2_RX1_IRQHandler
CAN2_SCE_IRQHandler
OTG_FS_IRQHandler
DMA2_Stream5_IRQHandler
DMA2_Stream6_IRQHandler
DMA2_Stream7_IRQHandler
USART6_IRQHandler
I2C3_EV_IRQHandler
I2C3_ER_IRQHandler
OTG_HS_EP1_OUT_IRQHandler
OTG_HS_EP1_IN_IRQHandler
OTG_HS_WKUP_IRQHandler
OTG_HS_IRQHandler
DCMI_IRQHandler
CRYP_IRQHandler
HASH_RNG_IRQHandler
FPU_IRQHandler
B . ;B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环。,所以若开启了中断又没有自己实现中断服务函数,中断就会跳 ;转到启动文件中定义的中断服务函数,陷入死循环,导致程序卡死
ENDP
;编译器指令,使地址对齐,后面不带参数默认为四字节对齐
ALIGN
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;; 用户栈和堆初始化, 由 C 库函数_main 来完成
IF :DEF:__MICROLIB DEF ;DEF:__MICROLIB 这个宏在 KEIL 里面开启,在配置里勾选使用微库
;若使用微库,则声明一下变量变量为全局变量,__initial_sp __heap_base __heap_limit在前面堆栈定义时声明过,不过并不 :是全局的
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
;若没有使用微库,则引用外部函数__use_two_region_memory(自己实现),并声明全局变量__user_initial_stackheap
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
;实现这个__user_initial_stackheap的内容
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
5、配置时钟
1、相关名词
1)RCC:reset clock control 复位和时钟控制器
2)SYSCLK:系统时钟
3)HSE: 高速外部时钟信号,由有源或者无源晶振提供信号(野火霸天虎开发板为25M)
4)HSI:高速的内部时钟时钟信号 ,作为备选时钟,在外部晶振故障时提供时钟信号
5) PLL:锁相环,主要作用是对时钟进行倍频,然后把时钟输出到各个功能部件,PLL 有两个,一个是主PLL,另外一个是专用的 PLLI2S,他们均由 HSE 或者 HSI 提供时钟输入信号。主 PLL 有两路的时钟输出,第一个输出时钟 PLLCLK 用于系统时钟,二个输出用于 USB OTG FS 的时钟(48M)、RNG 和 SDIO 时钟(<=48M)。专用的 PLLI2S 用于生成精确时钟,给 I2S 提供时钟。HSE故障时PLL也会关闭,并使用HSI直到HSE恢复。SYSCLK=HSI=16M,如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施,使用 HSI,重新设置系统频率为 168M,让系统恢复正常使用。
6)分频因子和倍频因子:将输入的时钟信号按因子成倍放大或成倍减小(指时钟频率:如分频因子25,输入时钟频率25M,分频后时钟信号频率就变为1M)后输出。
7)HCLK:AHB总线时钟
8)PCLK1:APB1总线时钟,属于低速总线(最大42M)
9)PCLK2:APB2总线时钟,属于高速总线
2、时钟树
3、系统时钟APB、AHB总线时钟分析
4、时钟与复位寄存器RCC
RCC寄存器较多,使用时参考 参考手册
5、其他时钟
- RTC 时钟
- 独立看门狗时钟
- I2S 时钟
- PHY 以太网时钟:F407 要想实现以太网功能,除了有本身内置的 MAC 之外,还需要外接一个 PHY 芯片
- USB PHY 时钟:用做USB高速传输
- MCO 时钟输出:相当于一个有源晶振,可以为外部提供时钟信号
6、系统如何设置的时钟
当程序来到 main 函数之前,启动文件:startup_stm32f40xxx.s 已经调用 SystemInit() 函数,在SystemInit() 函数中调用d SetSysClock()函数,通过对相关rcc寄存器的操作,设置M、P、Q、N等因子,把系统时钟初始化成 168MHZ,HCLK=SYSCLK=168M,PCLK2 = HCLK /2= 84M,PCLK1= HCLK/4 = 42M。SystemInit() 在库文件:system_stm32f4xx.c 中定义。
6、STM32的中断
1、中断概述
在STM32中所有的外设都可以产生中断,其中系统异常(内核产生)有 10 个,外部中断(外设)有 82 个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。有关具体的系统异常和外部中断可在标准库文件 stm32f4xx.h 这个头文件查询到,在IRQn_Type 这个结构体里面包含了 F4 系列全部的异常声明。
由上图可以看到IRQn使用一个枚举类型,名称对应中断服务函数的名字,编号代表硬件编号,与启动文件中的中断向量表一一对应
2、中断编程管理机构NVIC
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M4 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC 是 Cortex-M4 的 NVIC 的一个子集。想要使用中断就需要配置NVIC,配置NVIC就需要使用固件库中的NVIC结构体
typedef struct {
__IO uint32_t ISER[8]; // 中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; // 中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; // 中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; // 中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; // 中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; // 中断优先级寄存器 (8Bit wide)
uint32_t RESERVED5[644];
__O uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;
NVIC结构体中的每一个寄存器都对应一个NVIC相关的硬件。在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。不过使用固件库时不需要关注这些寄存器,直接点用函数即可。
在固件库文件 core_cm4.h 的最后,还提供了 NVIC 的一些函数,这些函数遵循 CMSI 规则,只要是
Cortex-M4 的处理器(stm32官方也提供了NVIC函数)都可以使用,具体如下:
ARM官方提供的NVIC函数
STM32官方提供的函数
我们一般使用STM32官方提供的NVIC函数。
3、中断编程
1、中断优先级
在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx(在 F407 中,x=0…981)用来配
置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越
小,优先级越高。但是绝大多数 CM4 芯片都会精简设计,以致实际上支持的优先级数减少,在
F407 中,只使用了高 4bit,而这四位又被分为主优先级和子优先级,如下所示:
比如选择分组100 ,主要优先级占3位,子优先级占4位(注意这是指IPR 宽度为 8bit),但在f407中,只使用高四位,于是就成了主优先级3位,有8中主优先级,子优先级1位,有两种子优先级。若是选择主优先级占7~0位,那么只有主优先级没有子优先级。
当主优先级和子优先级都相同时就以中断编号作为优先级评判标准。
主优先级(抢占优先级) > 子优先级 > 中断编号
NVIC中中断优先级分组只需配置一次,全局使用,列如:先初始化串口中断,这时NVIC结构体需要指定中断源为串口并设置中断优先级(主和子)和中断优先级分组,接着又要使用DMA中断,这时NVIC结构体需要指定中断源为DMA并设置中断优先级(主和子),但是不需要指定优先级分组,因为在前面已经设置过了
2、NVIC的配置
先初始化NVIC结构体
typedef struct {
uint8_t NVIC_IRQChannel; // 中断源
uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级
uint8_t NVIC_IRQChannelSubPriority; // 子优先级
FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能
} NVIC_InitTypeDef;
调用 NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
设置中断优先级分组 NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
3、中断编程流程
1)设置中断优先级分组(全局优先,仅一次)
2)初始化NVIC使能中断(有些外设的库函数中有使能中断的函数)
3)编写中断服务函数(在函数中有时需要手动清除中断标志位)
7、EXTI—外部中断/事件控制器
1、EXTI概述
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。EXTI管理了控制器的 23 个中断/事件线。可以从启动文件设置的向量表中查看对应中断函数。占用 EXTI0 至 EXTI15,还有另外七根用于特定的外设事件
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
DCD EXTI3_IRQHandler ; EXTI Line3
DCD EXTI4_IRQHandler ; EXTI Line4
DCD EXTI9_5_IRQHandler ; External Line[9:5]s
DCD EXTI15_10_IRQHandler ; External Line[15:10]s
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line
DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line
DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line
每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
看图 EXTI 功能框图 中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内,编号 1 是输入线,EXTI 控制器有 23 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。由这个图来看,中断和事件可以同时发生。
2、如何将EXTI设置为GPIO的某端口
将exti线(0~15)连接至某个GPIO的某个端口,需要使用寄存器SYSCFG_EXTICRx(x=1 ~ 4)
SYSCFG_EXTICR1配置Pin0~Pin3(exit_line0 ~ exti_line3),使用那个GPIO的Pin0 ~ 3 在寄存器中配置。
SYSCFG_EXTICR有四个每个管理4各EXTI,一共16个(EXTI0 ~ 15),需要注意的是5~9、10 ~ 15 exti中断的处理放在EXTI9_5_IRQHandler和EXTI15_10_IRQHandler中,处理这几个中断时要在中断服务函数中判断具体的中断来源。
3、外部中断编程顺序
1)设置中断优先级分组
2)设置NVIC
3)设置EXTI初始化结构体(使用EXTI要使能RCC_APB2Periph_SYSCFG时钟)
4)初始化exti连接的外设,使能外设时钟(GPIO应使用输入、浮空模式)。
5)连接中断源到引脚(设置SYSCFG_EXTICR寄存器)
6)完善中断服务函数
4、外部中断实验
1)设计:按键为PA0和PC13,启用这两个中断,控制LED红灯状态
2)问题记录:
问题1:stm32f4xx_exti提供的函数EXTI_GetITStatus和EXTI_GetFlagStatus的区别
解答:这两个函数是十分相似的,EXTI_GetFlagStatus 的作用是 Checks whether the specified EXTI line flag is set or not. 即检查指定的外部中断线的标志是否被置位;而 EXTI_GetITStatus 的作用是 Checks whether the specified EXTI line is asserted or not. 即检查指定外部中断线的状态是否有效。也就是说前者是检查中断标志的,后者是检查中断状态的。我们追踪一下这两个库函数的实现即可发现区别。
问题2:为什么要手动清除中断标志位(wait)
3)代码
中断设置
头文件
#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H
#include "stm32f4xx.h"
#include "misc.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_exti.h"
#include "stm32f4xx_syscfg.h"
#include "bsp_key.h"
#include "bsp_led.h"
void BSP_EXTIInit(void);
#endif /*__BSP_EXTI_H*/
源文件
#include "bsp_exti.h"
static void BSP_KeyNVICInit(void)
{
/*使能外部中断*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
/*PC13*/
NVIC_Init(&NVIC_InitStruct);
/*PA0*/
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_Init(&NVIC_InitStruct);
}
static void BSP_KeyEXTIInit(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
/*设置EXTI细节*/
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
/*EXTI线与具体GPIOx Pinx连接*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
EXTI_InitStruct.EXTI_Line = EXTI_Line13;
EXTI_Init(&EXTI_InitStruct);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource13);
}
void BSP_EXTIInit(void)
{
BSP_KeyNVICInit();
BSP_KeyEXTIInit();
/*初始化GPIO*/
BSP_KEYInit();
BSP_LEDInit();
}
中断服务函数
//中断服务函数名称可以在启动文件中查看
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0))
{
/*KEY1*/
LED_SetSatus(LED_RAD,ENABLE);
/*清除中断标志位*/
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI15_10_IRQHandler(void)
{
/*判断中断是否来自Line 13*/
if(EXTI_GetITStatus(EXTI_Line13))
{
/*KEY2*/
LED_SetSatus(LED_RAD,DISABLE);
/*清除中断标志位*/
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
主函数
#include "stm32f4xx.h"
#include "bsp_pub.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_exti.h"
#include "misc.h"
void Main_Init(void);
int main(void)
{
Main_Init();
BSP_EXTIInit();
while(1)
{
}
}
void Main_Init(void)
{
/*设置中断分组优先级*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
}
4)现象:按KEY1和KEY2可以控制LED_RAD的亮灭
8、SysTick
1、SysTick是什么
SysTick—系统定时器是属于 CM4 内核中的一个外设,内嵌在 NVIC 中。系统定时器是一个 24bit
的向下递减的计数器,计数器每计数一次的时间为 1/SYSCLK,一般我们设置系统时钟 SYSCLK
等于 180M。当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环
往复。
因为 SysTick 是属于 CM4 内核的外设,所以所有基于 CM4 内核的单片机都具有这个系统定时器,
使得软件在 CM4 单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维
持操作系统的心跳。
因为 SysTick 属于内核外设,跟普通外设的中断优先级有些区别,并没有抢占优先级和子优先
级的说法。在 STM32F407 中,内核外设的中断优先级由内核 SCB 这个外设的寄存器:SHPRx
(x=1.2.3)来配置。SPRH1-SPRH3 是一个 32 位的寄存器,但是只能通过字节访问,每 8 个字段控制着一个内核外设
的中断优先级的配置。
所以设置异常(内核外设的中断)需要使用ARM提供的内核文件core_cm4.h中提供的函数
/** \brief Set Interrupt Priority
The function sets the priority of an interrupt.
\note The priority cannot be set for every core interrupt.
\param [in] IRQn Interrupt number.
\param [in] priority Priority to set.
*/
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if((int32_t)IRQn < 0) {
SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
else {
NVIC->IP[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
2、SysTick寄存器
3、SysTick的使用
1)配置SysTick,使用函数SysTick_Config;
/** \brief System Tick Configuration
The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\param [in] ticks Number of ticks between two interrupts.
\return 0 Function succeeded.
\return 1 Function failed.
\note When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
must contain a vendor-specific implementation of this function.
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
//操作SysTick->CTRL寄存器,设置时钟源AHBCLK=168M、使能中断、使能systick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
4、SYsTick实现延时
1)计算:时钟为168MHZ,所以VAL寄存器每 1/168MHZ 秒递减一次,0.059微秒。1000微秒 = 1毫秒, 1000÷0.059 = 16949.1,即当VAL加载的值为16949时,当VAL递减为0,产生一次中断,所消耗的时间为1ms,转换为2进制16807 = 0100 0001 1010 0111 = 0x 00 41A7(24bit) 算错了
此值并没有超出SysTick的计数范围,SysTick初始化时LOAD指定为 0x00 4235,则1ms产生一次中断。在中断函数中累加一个全局变量,累加1000次,时间为1s。
代码:
2)问题记录:
问题1:SysTick中断时间计算错误
3)代码
头文件
#ifndef __BSP_PUB_H
#define __BSP_PUB_H
#include "stm32f4xx.h"
#define BSP_TICK (SystemCoreClock / 1000)
#define MS ((uint32_t)0x01)
#define S ((uint32_t)MS*1000)
void Delay(__IO uint32_t delay);
C文件
#include "bsp_pub.h"
#include "core_cm4.h"
uint32_t SysTick_Count;
static void BSP_SysTickInit(void)
{
SysTick_Config(BSP_TICK);
}
void Delay(__IO uint32_t delay)
{
SysTick_Count = 0;
BSP_SysTickInit();
while(SysTick_Count != delay);
/*关闭SysTick*/
SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
}
中断服务函数
extern uint32_t SysTick_Count;
void SysTick_Handler(void)
{
SysTick_Count++;
}
main.c
#include "stm32f4xx.h"
#include "bsp_pub.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_exti.h"
#include "misc.h"
void Main_Init(void);
int main(void)
{
Main_Init();
BSP_EXTIInit();
while(1)
{
Delay(S);
LED_SetSatus(LED_GREE,ENABLE);
Delay(S);
LED_SetSatus(LED_GREE,DISABLE);
}
}
void Main_Init(void)
{
/*设置中断分组优先级*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
}
9、STM32常见通讯协议
如何学习一个通讯协议
1)知道这个协议是干嘛的,应用在哪里
2)物理层:有哪些线路,接口的电器标准
3)协议层:如何约定通讯的开始、停止、如何解析信号等等
5)在单片机中如何实现
4)实际应用一下
常见通讯概念
1)串行通讯和并行通讯:一次发送一个信号位的是串行,一次发送多个位的是并行
2)同步通讯和异步通讯:通信时接收和发送双方用同一个时钟线为同步,否则为异步
3)全双工通信:支持同一时刻既能接收又能发送数据
4)半全双工通信:可以发送也能接收数据,但同一时刻只能发送或者接收,不能同时进行。
5)单工通信:只能接收数据或者发送数据。
6)比特率 (Bitrate):即每秒钟传输的二进制位数,单位为比特每秒 (bit/s)。
7)波特率(Baudrate):它表示每秒钟传输了多少个码元。
8)码元:通讯中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如果一个码元中0V表示0,3V表示1,那么这个码元就代表一个二进制位(0或者1),但是如果一个码元中0V代表00,1V代表01,2V代表10,3V代表11,那么这个码元就代表两个个二进制位(两个二进制位四种状态,00、01、10、11),*比特率 = 波特率 ( 一个码元代表几个二进制位)
9)RS232与TTL:RS232是串口通讯的一种协议它规定了通讯时电平的含义,TTL是用于单片机的电平标准,两种不同电平标准在通讯时要转换。
1、RS232
1、用途
RS-232 串口标准常用于计算机、路由与调制调解器 (MODEN,俗称“猫”) 之间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由) 和数据通讯设备DCE(调制调解器)。
2、物理层
1)接口标准:RS-232 标准的 COM 口 (也称 DB9 接口)
2)信号线:在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信号线,直接传输数据信号。而 RTS、CTS、DSR、DTR 及 DCD 信号都被裁剪掉了
3、协议层
协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据
1)波特率:是串口异步通讯以两个通讯设备之间需要约定好波特率,常见的波特率为 4800、9600、115200 等
2)起始位和停止位:起始信号由一个逻辑 0 的数据位表示,而数据包的停止信号可由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示
3)有效数据位:的起始位之后紧接着的就是要传输的主体数据内容,有效数据的长度常被约定为 5、6、7 或 8 位长
4)校验位:在有效数据之后,有一个可选的数据校验位,校验方法有奇校验 (odd)、偶校验(even)、0 校验 (space)、1 校验 (mark) 以及无校验 (noparity)
10、STM32USART
1、简介
USART:Universal Synchronous Asynchronous Receiverand Transmitter,串口,可同步或异步通讯,全双工。
UART: Universal Asynchronous Receiver and Transmitter,串口,异步,全双工通讯
USART和UART作为STM32片上外设搭载。
2、使用USART
1)配置:
由开发板硬件原理图可以看到开发板usb转串口最终连接了USART1,RXDPA9,TXDPA10,这两个端口配置为复用、高速模式。
3、问题记录
1)USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)函数在USART初始化之前调用无效
2)在中断中只能接收一个字节的数据
将接收到的数据保存在数组中
3)USART中断标志位为什么不同手动清除
查阅手册中的寄存器描述了解各标志位的时序和如何清除标志位等,某些标志位优先以手册中有描述
的软件清除流程而可以不使用 ClearFlag 函数。
4、USART向电脑发送数据并使用中断接收数据
头文件
#ifndef __BSP_USART_H
#define __BSP_USART_H
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
#define USART_IT 1 //是否使能UASRT1中断
#define USART_TX_PORT GPIOA
#define USART_TX_PIN GPIO_Pin_9
#define USART_TX_Mode GPIO_Mode_AF
#define USART_TX_Speed GPIO_High_Speed
#define USART_TX_OType GPIO_OType_PP
#define USART_TX_PuPd GPIO_PuPd_NOPULL
#define USART_TX_CLK RCC_AHB1Periph_GPIOA
#define USART_TX_AF GPIO_AF_USART1
#define USART_TX_PinSource GPIO_PinSource9
#define USART_RX_PORT GPIOA
#define USART_RX_PIN GPIO_Pin_10
#define USART_RX_Mode GPIO_Mode_AF
#define USART_RX_Speed GPIO_Low_Speed
#define USART_RX_OType GPIO_OType_PP
#define USART_RX_PuPd GPIO_PuPd_NOPULL
#define USART_RX_CLK RCC_AHB1Periph_GPIOA
#define USART_RX_AF GPIO_AF_USART1
#define USART_RX_PinSource GPIO_PinSource10
#define USART_CLK RCC_APB2Periph_USART1
void BSP_USARTInit(void);
void USART_SendByte(USART_TypeDef* USARTx,uint8_t data);
void USART_SendString(USART_TypeDef* USARTx,char * str);
#endif /*__BSP_USART_H*/
C文件
#include "bsp_usart.h"
static void BSP_UsartGPIOConfig(void)
{
RCC_AHB1PeriphClockCmd(USART_TX_CLK,ENABLE);
RCC_AHB1PeriphClockCmd(USART_RX_CLK,ENABLE);
GPIO_PinAFConfig(USART_TX_PORT, USART_TX_PinSource,USART_TX_AF);
GPIO_PinAFConfig(USART_RX_PORT, USART_RX_PinSource,USART_RX_AF);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = USART_TX_PIN;
GPIO_InitStruct.GPIO_Mode = USART_TX_Mode;
GPIO_InitStruct.GPIO_Speed = USART_TX_Speed;
GPIO_InitStruct.GPIO_OType = USART_TX_OType;
GPIO_InitStruct.GPIO_PuPd = USART_TX_PuPd;
GPIO_Init(USART_TX_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = USART_RX_PIN;
GPIO_InitStruct.GPIO_Mode = USART_RX_Mode;
GPIO_InitStruct.GPIO_Speed = USART_RX_Speed;
GPIO_InitStruct.GPIO_OType = USART_RX_OType;
GPIO_InitStruct.GPIO_PuPd = USART_RX_PuPd;
GPIO_Init(USART_RX_PORT,&GPIO_InitStruct);
}
static void BSP_UsartNVIC(void)
{
/*使能外部中断*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
/*使能USART中断*/
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
static void BSP_UsartConfig(void)
{
RCC_APB2PeriphClockCmd(USART_CLK,ENABLE);
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStruct);
/*关闭8倍采样 OVER8 = 0*/
USART_OverSampling8Cmd(USART1, DISABLE);
/*使能USART1接收中断*/
USART_Cmd(USART1, ENABLE);
}
void BSP_USARTInit(void)
{
BSP_UsartGPIOConfig();
BSP_UsartConfig();
#if USART_IT
BSP_UsartNVIC();
#endif
}
void USART_SendByte(USART_TypeDef* USARTx,uint8_t data)
{
//发送数据
USART_SendData(USARTx,data);
//等待TDR为空
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
}
void USART_SendString(USART_TypeDef* USARTx,char * str)
{
uint32_t flag=0;
do
{
USART_SendByte(USARTx,*(str+flag));//USART_SendByte参数为uint8_t,(str+flag)被解释为uint8_t指针
flag++;
}while(*(str + flag)!='\0');//不理解为啥是==>char * str ,char = uint8_t,*(str + flag)为数组下一个元素
//即下一个字符
//等待TDR为空
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
}
中断函数
char USART_RECV_ARR[128];
void USART1_IRQHandler(void)
{ int i=0;
USART_SendString(USART1,"进入USART1_IRQHandler\n");
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
{
USART_SendString(USART1,"触发USART_IT_RXNE中断\n");
do
{
USART_RECV_ARR[i] = USART_ReceiveData(USART1);
i++;
}while((i<128)&&(USART_RECV_ARR[i-1] != '\0'));
USART_SendString(USART1,USART_RECV_ARR);
}
}
main.c
#include "stm32f4xx.h"
#include "bsp_pub.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_exti.h"
#include "bsp_usart.h"
#include "misc.h"
void Main_Init(void);
int main(void)
{
Main_Init();
BSP_LEDInit();
BSP_USARTInit();
USART_SendByte(USART1,'1');
USART_SendString(USART1,"USART通信实验\n");
USART_SendString(USART1,"Hello World!\n");
while(1)
{
// Delay(S);
// LED_SetSatus(LED_GREE,ENABLE);
// Delay(S);
// LED_SetSatus(LED_GREE,DISABLE);
}
}
void Main_Init(void)
{
/*设置中断分组优先级*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
}
5、重定向C库printf函数
重定义fgetc和fputc,printf和scanf等函数调用重定义的这两个函数,还需要配置项目使用微库,这样调用printf等C函数就可以通过串口打印到电脑上
//重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USART1, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、getchar 等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
7、软件模拟USART 通信(待完成)
11、DMA(Direct Memory Access, 直接存储区访问)
1、DMA简介
1)相当于一个外设
2)无需CPU就可以完成数据传输
3)数据传输方向:DMA–>存储器 、 存储器–>外设、存储器–>存储器(存储器:内部FLASH、SRAM、外部存储器。外设指外设的寄存器,如GPIO的数据寄存器DR)
4)功能框图
DMA2
5)使用分析:STM32具有两个 DMA 控制器,同时外设繁多,为实现正常传输,DMA 需要通道选择控制。每个 DMA 控制器具有 8 个数据流,每个数据流对应 8 个外设请求。在实现 DMA传输之前,DMA 控制器会通过 DMA 数据流 x 配置寄存器 DMA_SxCR(x 为 0~7,对应 8 个 DMA数据流) 的 CHSEL[2:0] 位选择对应的通道作为该数据流的目标外设.
外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的 源地址 或者 目标地址。
2、存储器到存储器的传输
1、DMA使用注意
1)存储器到存储器模式通道选择没有具体规定,源地址和目标地址使用之前定义的数组首地址,只
能使用一次传输模式不能循环传输
2)DMA使用前需要复位标志位(清除标志位),复位 DMA 数据流之前需要调用该DMA_GetCmdStatus来确保 DMA 数据流复位完成。
3)DMA_Cmd 函数使能DMA后DMA才能传输数据。
4)开启 DMA 传输后需要使用 DMA_GetCmdStatus 函数获取 DMA 数据流状态,确保 DMA 数据流配置有效,为防止程序卡死,添加了超时检测功能。
5)存储器到存储器只能使用DMA2,通道任意
2、代码及现象
头文件
#ifndef __BSP_DMA_H
#define __BSP_DMA_H
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_dma.h"
#define BSP_DMATest1 1
#define BSP_DMATest2 0
#define TIMEOUT_MAX 1000
void BSP_DMAInit(void);
#endif /*__BSP_DMA_H*/
C文件
#include "bsp_dma.h"
#include "bsp_led.h"
#include "bsp_pub.h"
const uint32_t SEND_Buffer[5]={1998,1999,2000,2001,2002};
uint32_t RECV_Buffer[5]={0};
#if BSP_DMATest1
/*测试1,存储器到存储器使用DMA2*/
void BSP_DMAInit(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitTypeDef DMA_InitStruct;
DMA_DeInit(DMA2_Stream0);
/*等待上一次数据流传输完成*/
while(DMA_GetCmdStatus(DMA2_Stream0) !=DISABLE);
DMA_InitStruct.DMA_Channel = DMA_Channel_0;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)SEND_Buffer;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)RECV_Buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToMemory;
DMA_InitStruct.DMA_BufferSize = (uint32_t)32;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0,&DMA_InitStruct);
/*清除流传输完成标志*/
DMA_ClearFlag(DMA2_Stream0,DMA_FLAG_TCIF0);
/*使能DMA 数据开始传输*/
DMA_Cmd(DMA2_Stream0, ENABLE);
int timeout = TIMEOUT_MAX;
/*判断是否完成*/
while((DMA_GetCmdStatus(DMA2_Stream0) ==ENABLE ) && (timeout-- > 0));
/*判断超时时间*/
if(timeout > 0)
{
/*未超时*/
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
}
else
{
/*超时*/
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
}
}
#endif
main.c
#include "stm32f4xx.h"
#include "bsp_pub.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_exti.h"
#include "bsp_usart.h"
#include "misc.h"
#include "stdio.h"
#include "bsp_dma.h"
extern uint32_t RECV_Buffer[5];
/*经常要初始化的设备如:串口、NVIC分组等*/
void Main_Init(void);
int main(void)
{
Main_Init();
BSP_LEDInit();
int leng = 0;
printf("DMA传输前 RECV_Buffer值打印\n");
for(leng = 0;leng<5;leng++)
{
printf("[ %d ] ",RECV_Buffer[leng]);
}
printf("\n");
BSP_DMAInit();
printf("DMA传输后 RECV_Buffer值打印\n");
for(leng = 0;leng<5;leng++)
{
printf("[ %d ] ",RECV_Buffer[leng]);
}
printf("\n");
while(1)
{
}
}
void Main_Init(void)
{
/*设置中断分组优先级*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/*初始化串口*/
BSP_USARTInit();
}
实验现象:绿灯闪烁,数据传输成功
3、存储器到外设USART1
1、注意事项
1)需要开启USART1的DMA传输
2)循环发送
3)先配置好DMA(存储器到USART1_TX)并使能,想要传输时使用:
USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);
向USART向DMA发送传输请求。
4)DMA循环模式:数据发送完后再次发送。
2、代码
头文件
#ifndef __BSP_DMA_H
#define __BSP_DMA_H
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_dma.h"
#define BSP_DMATest1 0
#define BSP_DMATest2 0
#define TIMEOUT_MAX 1000
void BSP_DMAInit(void);
#endif /*__BSP_DMA_H*/
C文件
#include "bsp_dma.h"
#include "bsp_led.h"
#include "bsp_pub.h"
#include "stm32f4xx_usart.h"
uint8_t SEND_Buffer[200]={0};
uint32_t RECV_Buffer[5]={0};
#if BSP_DMATest1
/*测试1,存储器到存储器使用DMA2*/
void BSP_DMAInit(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitTypeDef DMA_InitStruct;
DMA_DeInit(DMA2_Stream0);
/*等待上一次数据流传输完成*/
while(DMA_GetCmdStatus(DMA2_Stream0) !=DISABLE);
DMA_InitStruct.DMA_Channel = DMA_Channel_0;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)SEND_Buffer;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)RECV_Buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToMemory;
DMA_InitStruct.DMA_BufferSize = (uint32_t)32;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0,&DMA_InitStruct);
/*清除流传输完成标志*/
DMA_ClearFlag(DMA2_Stream0,DMA_FLAG_TCIF0);
/*使能DMA 数据开始传输*/
DMA_Cmd(DMA2_Stream0, ENABLE);
int timeout = TIMEOUT_MAX;
/*判断是否完成*/
while((DMA_GetCmdStatus(DMA2_Stream0) ==ENABLE ) && (timeout-- > 0));
/*判断超时时间*/
if(timeout > 0)
{
/*未超时*/
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_GREE,DISABLE);
}
else
{
/*超时*/
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,ENABLE);
Delay_mS(500);
LED_SetSatus(LED_RAD,DISABLE);
}
}
#else
/*测试2,存储器到外设寄存器USART1 DR使用DMA2*/
/*DMA2 数据流7 通道4 USART1_TX*/
void BSP_DMAInit(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitTypeDef DMA_InitStruct;
DMA_DeInit(DMA2_Stream7);
/*等待上一次数据流传输完成*/
while(DMA_GetCmdStatus(DMA2_Stream7) !=DISABLE);
DMA_InitStruct.DMA_Channel = DMA_Channel_4;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(USART1_BASE+0x04);
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)SEND_Buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
/*DMA_BufferSize=SENDBUFF_SIZE* DMA_MemoryDataSize*/
DMA_InitStruct.DMA_BufferSize = (uint32_t)200;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream7,&DMA_InitStruct);
/*清除流传输完成标志*/
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF0);
/*使能DMA 数据开始传输*/
DMA_Cmd(DMA2_Stream7, ENABLE);
}
#endif
main.c
#include "stm32f4xx.h"
#include "bsp_pub.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_exti.h"
#include "bsp_usart.h"
#include "misc.h"
#include "stdio.h"
#include "bsp_dma.h"
extern uint8_t SEND_Buffer[200];
/*经常要初始化的设备如:串口、NVIC分组等*/
void Main_Init(void);
int main(void)
{
Main_Init();
BSP_LEDInit();
int leng = 0;
printf("DMA传输前 SEND_Buffer初始化未W\n");
for(leng = 0;leng<200;leng++)
{
if(leng == 199)
{
SEND_Buffer[leng] = '#';
}
else
{
SEND_Buffer[leng] = 'A';
}
}
BSP_DMAInit();
/*请求传输*/
printf("DMA传输 开始\n");
USART_DMACmd(USART1, USART_DMAReq_Tx,ENABLE);
while(1)
{
LED_SetSatus(LED_GREE,ENABLE);
Delay_mS(2000);
LED_SetSatus(LED_GREE,DISABLE);
Delay_mS(2000);
}
}
void Main_Init(void)
{
/*设置中断分组优先级*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/*初始化串口*/
BSP_USARTInit();
}
12、IIC读写EEPROM
1、IIC(Inter - Integrated Circuit)
引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 (IC) 间的通讯。
物理层
1)I2C总线支持多主机多从机,每个设备有独立的设备地址,用以指定通讯设备
2)空闲时SDA呈现高电平
3)连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制
4)具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达
3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式
协议层
1)S:起始位,总线上所有从机都会接收到。(主机发送)
2)SLAVE ADDRESS:从机地址,总线设备接收到S后等待SLAVE ADDRESS(主机发送)
3)R|W:读写位,0,主机向从机写数据,1,主机向从机读数据(主机发送),读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。
4)A:确认位,从机接收到SLAVE ADDRESS和R|W,若从机匹配SLAVE ADDRESS,则发送A,并做好接收数据或者发送数据的准备。发送完数据后发送非确认信号。(读或写时确认信号由主机或从机发送),表示非应答信号 (NACK),低电平表示应答信号 (ACK)。
5)P:停止位,主机发送,结束此次通讯。
6)接收到应答信号后,主机开始正式向从机传输数据 (DATA),数据包的大小为 8 位,主机每发送完一个字节数据,都要等待从机的应答信号 (ACK),重复这个过程,可以向从机传输 N 个数据,**这个 N 没有大小限制。**当数据传输结束时,主机向从机发送一个停止传输信号 §,表示不再传输数据。
复合I2C通讯
该传输过程有两次起始信号 (S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址 (注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
协议各信号电平表现
1)S、P
2)ACK
2、STM32IIC
STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10位设备地址,支持 DMA 数据传输,并具有数据校验功能。可以为STM32 I2C外设设置两个地址,在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器 (SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、DMA 请求及各种 I2C 的通讯信号(起始、停止、响应信号等)。
STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚
3、I2C读写EEPROM
1、野火霸天虎开发板EEPROM((型号:AT24C02))硬件连接
EEPROM 芯片中还有一个 WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,此处直接接地,不使用写保护功能
EEPROM I2C地址:EEPROM 芯片的设备地址一共有 7 位,其中高 4 位固定为:1010 b,低 3 位则由 A0/A1/A2 信号线的电平决定,见图EEPROM 设备地址 ,图中的 R/W 是读写方向位,与地址无关。按照我们此处的连接,A0/A1/A2 均为 0,所以 EEPROM 的 7 位设备地址是:101 0000b,即 0x50。由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1时,表示读方向,加上 7 位地址,其值为“0xA1”,常称该值为“读地址”。
2、I2C通讯为啥目标引脚为啥为开漏模式
第一个原因,如果设计成推挽输出,就会造成短路情况。两个从设备一个拉高,一个拉低,就会造成短路。而开漏输出,有上拉电阻限流,这样都不会造成短路。综上所述,双总线接口外设一般要设计成开漏输出,一是防短路,而是做线与逻辑,方便仲裁。
3、接收数据时等待ev7之后再调用接收函数,接收数据之前检测总线是否空闲
4、代码
头文件
#ifndef __BSP_EEPROM_H
#define __BSP_EEPROM_H
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_i2c.h"
/*I2C1 APB1*/
#define EEPROM_I2C I2C1
#define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
#define EEPROM_I2C_SPEED 400000
#define EEPROM_I2C_OwnAddress1 0xF8 /*与从设备地址不同即可*/
/*I2C1 SCL PB8 */
#define EEPROM_SCL_PORT GPIOB
#define EEPROM_SCL_PIN GPIO_Pin_8
#define EEPROM_SCL_Mode GPIO_Mode_AF
#define EEPROM_SCL_Speed GPIO_Fast_Speed
#define EEPROM_SCL_OType GPIO_OType_OD
#define EEPROM_SCL_PuPd GPIO_PuPd_NOPULL
#define EEPROM_SCL_CLK RCC_AHB1Periph_GPIOB
#define EEPROM_SCL_AF GPIO_AF_I2C1
#define EEPROM_SCL_PinSource GPIO_PinSource8
/*I2C1 SDA PB8 */
#define EEPROM_SDA_PORT GPIOB
#define EEPROM_SDA_PIN GPIO_Pin_9
#define EEPROM_SDA_Mode GPIO_Mode_AF
#define EEPROM_SDA_Speed GPIO_Fast_Speed
#define EEPROM_SDA_OType GPIO_OType_OD
#define EEPROM_SDA_PuPd GPIO_PuPd_NOPULL
#define EEPROM_SDA_CLK RCC_AHB1Periph_GPIOB
#define EEPROM_SDA_AF GPIO_AF_I2C1
#define EEPROM_SDA_PinSource GPIO_PinSource9
/*EEPROM*/
#define EEPROM_ADDR 0xA0
#define EEPROM_TIMEOUT_MAX 0x0FFF
void BSP_EEPROM_Init(void);
uint8_t EERROM_WriteByByte(uint8_t writeAddr,uint8_t * buffer);
uint8_t EERROM_ReadByByte(uint8_t readAddr,uint8_t *rbuffer);
#endif /*__BSP_EEPROM_H*/
单字节读写函数
#include "bsp_eeprom.h"
#include "bsp_pub.h"
static void BSP_EEPROMGPIOConfig(void)
{
RCC_AHB1PeriphClockCmd(EEPROM_SDA_CLK,ENABLE);
RCC_AHB1PeriphClockCmd(EEPROM_SCL_CLK,ENABLE);
GPIO_PinAFConfig(EEPROM_SDA_PORT, EEPROM_SDA_PinSource,EEPROM_SDA_AF);
GPIO_PinAFConfig(EEPROM_SCL_PORT, EEPROM_SCL_PinSource,EEPROM_SCL_AF);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = EEPROM_SCL_PIN;
GPIO_InitStruct.GPIO_Mode = EEPROM_SCL_Mode;
GPIO_InitStruct.GPIO_Speed = EEPROM_SCL_Speed;
GPIO_InitStruct.GPIO_OType = EEPROM_SCL_OType;
GPIO_InitStruct.GPIO_PuPd = EEPROM_SCL_PuPd;
GPIO_Init(EEPROM_SCL_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = EEPROM_SDA_PIN;
GPIO_InitStruct.GPIO_Mode = EEPROM_SDA_Mode;
GPIO_InitStruct.GPIO_Speed = EEPROM_SDA_Speed;
GPIO_InitStruct.GPIO_OType = EEPROM_SDA_OType;
GPIO_InitStruct.GPIO_PuPd = EEPROM_SDA_PuPd;
GPIO_Init(EEPROM_SDA_PORT,&GPIO_InitStruct);
}
static void BSP_EEPROM_I2C_Config(void)
{
RCC_APB1PeriphClockCmd(EEPROM_I2C_CLK,ENABLE);
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = EEPROM_I2C_SPEED;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = EEPROM_I2C_OwnAddress1;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(EEPROM_I2C, &I2C_InitStruct);
I2C_Cmd(EEPROM_I2C, ENABLE);
}
void BSP_EEPROM_Init(void)
{
BSP_EEPROMGPIOConfig();
BSP_EEPROM_I2C_Config();
}
uint8_t EERROM_WriteByByte(uint8_t writeAddr,uint8_t * wbuffer)
{
/*
思路:
1、发送起始位,等待事件5(作为发送或接收放等待的事件不同)发生并超时检测
2、发送设备地址并等待应答信号,等待事件6和事件8(有应答),
3、发送数据并等待应答信号,等待事件8,
4、产生停止位,等待事件2,表示通讯停止
*/
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("起始信号未应答",1);
}
}
/*填写设备地址*/
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("发送地址后未回应",2);
}
}
/*发送写入地址*/
I2C_SendData(EEPROM_I2C, writeAddr);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("发送写入地址后未回应",3);
}
}
I2C_SendData(EEPROM_I2C, *wbuffer);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("发送数据后未回应",4);
}
}
/*产生停止位*/
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
return ERROR_TimeOut("写入数据成功",0);
}
uint8_t EERROM_ReadByByte(uint8_t readAddr,uint8_t *rbuffer)
{
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))
{
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("总线地址繁忙",10);
}
}
/*读EEPROM为复合通讯*/
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("起始信号未应答",5);
}
}
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Transmitter);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("发送设备地址后未回应(写方向)",6);
}
}
// I2C_Cmd(EEPROM_I2C, ENABLE);
/*发送读入地址*/
I2C_SendData(EEPROM_I2C, readAddr);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("发送读入地址后未回应",7);
}
}
/*#####################################################################*/
/*复合通讯,二次START*/
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("二次起始信号未应答",8);
}
}
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_ADDR,I2C_Direction_Receiver);
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("二次发送设备地址后未回应(读方向)",9);
}
}
/*检测到事件I2C_EVENT_MASTER_BYTE_RECEIVED后接收数据,而不是之前*/
I2C_TIMEOUT = EEPROM_TIMEOUT_MAX;
while(!I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED)){
if((I2C_TIMEOUT--) == 0)
{
return ERROR_TimeOut("接收数据失败",10);
}
}
*rbuffer = I2C_ReceiveData(EEPROM_I2C);
/*产生非应答信号*/
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
/*产生停止位*/
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
/*应答信号使能,不影响下次使用*/
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
return ERROR_TimeOut("读入数据成功",0);
}