启动文件的功能
启动文件由汇编编写,是系统上电复位后第一个执行的程序。
- 初始化堆栈指针
SP=_initial_sp - 初始化程序计数器指针
PC=Reset_Handler - 设置堆和栈的大小
- 设置中断向量表的入口地址,初始化中断向量表
- 配置外部
SRAM作为数据存储 - 配置系统时钟
- 调用
C库函数_main初始化用户栈,从而最终调用main函数转到C世界
startup_.s主要完成三部份工作:堆栈初始化、定位中断向量表、调用Reset Handler
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00003000 ;0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;指明8个字节对齐(ALIGN=3)
Stack_Mem SPACE Stack_Size
__initial_sp ;由于在arm中栈是满减堆栈,因此_initial_sp是栈顶的位置。
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8 ;指明了以下的代码都是8字节对齐
THUMB ;指定了以下的代码为Thumb指令集
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY ;声明了RESET数据段,只读
EXPORT __Vectors ;导出向量表标号
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SecureFault_Handler ; Secure Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Stack
用于局部变量、函数调用、函数形参的开销。
EQU: 宏定义的伪指令,相当于等于,类似与 C 中的 define
AREA :告诉汇编器汇编一个新的代码段戒者数据段
SPACE :用于分配一定大小的内存空间,单位为字节
标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。
Heap
堆用于动态内存的分配,malloc函数。
PRESERVE8 : 指定当前文件的堆栈按照 8 字节对齐
THUMB: 表示后面指令为 THUMB 指令。 THUBM 是ARM 以前的指令集, 16bit,现在 Cortex-M 系列的都使用THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32位的指令,是 THUMB 的超级。
EXPORT : 声明一个标号具有全局属性,可被外部的文件使用。如果是 IAR 编译器,则使用的是GLOBAL 这个指令。
DCD : 分配一个戒者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。
Vector table map
- 向量表实际上是一个
32位的整型数组,一个元素对应一个异常(ESR),数组元素存的就是ESR的入口地址。 - 向量表在复位后从
FLASH的0地址开始,具体的初始化值请查询参考手册的中断章节。
从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C语言中的函数名就是一个地址。
Reset handler
- 复位程序是上电后单片机执行的第一个程序
- 调用SystemInit函数配置系统时钟;调用C库函数_main,并最终进入C的世界
WEAK :表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT :表示该标号来自外部文件,跟 C 语言中的EXTERN 关键字类似。这里表示 SystemInit 和__main 这两个函数均来自外部的文件。
来自p45、p108-112
GPIO工作模式
- 输入模式(
上拉/下拉/浮空) - 输出模式(
推挽/开漏、上拉/下拉) - 复用功能(
推挽/开漏、上拉/下拉) - 模拟输入输出
来自p42-43
STM32的GPIO的配置模式有哪几种?工作场景?
| 模式名称 | 性质 | 场景 |
|---|---|---|
| 浮空输入 | 数字输入 | 外部按键输入/USART RX引脚 |
| 上拉输入 | 数字输入 | 需要IO内部上拉电阻输入时,器件的外部中断(IRQ)引脚触发中断条件为下降沿触发/低电平触发,这样在无信号输入时始终保持高电平,如果有事件触发中断IRQ可以输出一个低电平,进而可产生(下降沿/低电平)中断。例如单片无线收发器芯片NRF24L01的IRQ引脚的工作模式即为上拉输入模式 |
| 下拉输入 | 数字输入 | 需要IO内部下拉电阻输入时,器件的外部中断(IRQ)引脚触发中断条件为上升沿触发/高电平触发时,该端口可以选择下拉输入模式 |
| 模拟输入 | 模拟输入 | ADC模拟输入/低功耗下省电 |
| 开漏输出 | 数字输出 | IIC/SMBus |
| 推挽输出 | 数字输出 | 普通的GPIO用于驱动LED、数码管等电子元器件或输出控制某个信号 |
| 复用开漏输出 | 数字输出 | 常见片内外设(I2C/SMBus等等) |
| 复用推挽输出 | 数字输出 | 常见片内外设(USART TX引脚/SPI/PWM输出等等) |
来自# STM32 GPIO的8种工作模式与应用场合 和 中文使用手册 和 p40
栈和堆(启动文件)
- 栈的作用是用于局部变量、函数调用、函数形参等的开销,栈的大小不能超过内部
SRAM的大小。标号_initial_sp紧挨着SPACE语句(用于分配一定大小的内存空间,单位为字节)放置,表示栈的结束地址,即栈顶地址。栈,由高向低生长。 - 堆主要用于动态内存的分配,像
malloc()函数申请的内存就在堆中,由低向高生长。
来自p109-110
简述SPI和IIC的区别
| IIC | SPI |
|---|---|
半双工,2根线SCL SDA | 全双工,4根线SCK CS MOSI MISO |
多主机总线,通过SDA上的地址信息来锁定从设备 | 只有一个主设备,主设备通过CS片选来确定从设备 |
总线传输速度100Kbps-4Mbps | 达30Mbps以上 |
高电平时SDA下降沿标志传输开始,上升沿标志传输结束 | CS拉低标志传输开始,CS拉高标志传输结束 |
| 读写时序比较固定统一,设备驱动编写方便 | 不同从设备datasheet来实现读写,相对复杂一些 |
CPOL/CPHA及通信模式
SPI一般有4种通信模式,它们的主要区别是总线空闲时SCK的时钟状态及数据采样时刻。
时钟极性CPOL是指SPI通信设备处于空闲状态时,SCK信号线的电平信号(即SPI通信开始前、NSS线为高电平时SCK的状态)。CPOL=0时,SCK在空闲状态时为低电平,CPOL=1时,SCK在空闲状态时为高电平。
时钟相位CPHA是指数据的采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。
CPOL及CPHA的不同状态,SPI分为4种模式。主机与从机需要工作在相同的模式下才可以正常通信,实际中采用较多的是“模式0”和“模式3”
| SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 奇数边沿 |
| 1 | 0 | 1 | 低电平 | 偶数边沿 |
| 2 | 1 | 0 | 高电平 | 奇数边沿 |
| 3 | 1 | 1 | 高电平 | 偶数边沿 |
来自p237-238
IIC挂机设备的个数
由IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件。
但是IIC协议没有规定总线上device最大数目,但是规定了总线电容不能超过400pF。
管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件。
总线之所以规定电容大小是因为,IIC的OD要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。
传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。
来自# IIC总线最多可以挂多少个设备 和 p205 -206
时钟源与时钟和总线对应外设
在STM32中,可以用内部时钟,也可以用外部时钟,在要求进度高的应用场合最好用外部晶体震荡器,内部时钟存在一定的精度误差。
准确的来说有4个时钟源可以选分别是HSI、LSI、HSE、LSE(即内部高速,内部低速,外部高速,外部低速),高速时钟主要用于系统内核和总线上的外设时钟。低速时钟主要用于独立看门狗IWDG、实时时钟RTC。
HSI是高速内部时钟,RC振荡器,频率为8MHz,上电后默认的系统时时钟SYSCLK = 8MHz,Flash编程时钟HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHzLSI是低速内部时钟,RC振荡器,频率为40kHz,可用于独立看门狗IWDG、实时时钟RTCLSE是低速外部时钟,接频率为32.768kHz的石英晶体
PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。通过倍频之后作为系统时钟的时钟源( 有很多人说是5个时钟源,这种说法有点问题,学习之后就会发现PLL并不是自己产生的时钟源,而是通过其他三个时钟源倍频得到的时钟)。
从左到右可以简单理解为 各个时钟源 -> 系统时钟来源的设置 -> 各个外设时钟的设置
系统时钟SYSCLK3种时钟源
HSI振荡器时钟HSE振荡器时钟PLLCLK时钟
PLL锁相环倍频(输入和输出)
PLL的输入(3种)
PLLi = HSI /2PLLi = HSE /2PLLi = HSE
PLL的输出(15种)
PLLout = PLLi Xn (n = 2…16)
F4系列总线对应外设
APB2总线:高级定时器timer1、timer8,通用定时器timer9、timer10、timer11,UTART1、USART6
APB1总线:通用定时器timer2、timer5,通用定时器timer12、timer14,基本定时器timer6、timer7、UTART2~UTART5
F4系列的系统时钟频率最高能到168M
附
1、
STMF4xx系统共计有三个主要时钟源( HSI、HSE和 PLL)和两个次要时钟源( LSE、LSI)。
2、SYSCLK可以来自 HSI、HSE和 PLL,多数采用 PLL频率最高能达到 168MHz。
3、RTC时钟可以来自 LSE、LSI和 HSE,但只有用 LSE时,才能保证系统电源掉电时 RTC仍能正常工作。
4、可通过多个预分频器配置 AHB 频率、高速 APB (APB2) 和低速 APB (APB1) 。 AHB 域的最大频率为 168 MHz。高速 APB2 域的最大允许频率为 84 MHz。低速 APB1 域的最大允许频率为 42 MHz。
5、STM32F405xx/07xx 和 STM32F415xx/17xx 的定时器时钟频率由硬件自动设置。 如果 APB预分频器为 1,定时器时钟频率等于 APB 域的频率。 否则,等于 APB 域的频率的两倍。
6、除以下时钟外,所有外设时钟均由系统时钟 (SYSCLK) 提供:
- 来自于特定
PLL输出 (PLL48CLK) 的USBOTGFS时钟 (48 MHz)、基于模拟技术的随机数发生器 (RNG) 时钟 (<=48 MHz) 和 SDIO 时钟 (<= 48 MHz)。 I2S时钟- 由外部
PHY提供的USB OTG HS (60 MHz)时钟 - 由外部
PHY提供的以太网MAC时钟(TX、RX和RMII)。
AHB1:168Mhz MAX
1) GPIOA~K
2) RCC_AHB1Periph_CRC
3) FLITF
4) SRAM1
5) SRAM2
6) BKPSRAM
7) SRAM3
8) CCMDATARAMEN
9) DMA1
10) DMA2
11) DMA2D
12) ETH_MAC、ETH_MAC_Tx、ETH_MAC_Rx、ETH_MAC_PTP
13) OTG_HS、OTG_HS_ULPI
AHB2:168Mhz MAX
1)DCMI
2)CRYP
3)HASH
4)RNG
5)OTG_FS
APB1:42Mhz MAX
1)TIM2~14
2)WWDG
3)SPI2~3
4)USART2~3
5)UART4~5,7~8
6)I2C1~3
7)CAN1~2
8)PWR
9)DAC
APB2:84Mhz MAX
1)TIM1,8~11
2)USART1,6
3)ADC
4)ADC1~3
5)SDIO,1, 4,5,6
6)SYSCFG
7)SAI1
8)LTDC
来自# 图文并茂详解STM32时钟配置 和 p117-123 和 Cortex-M4时钟配置
串口定义
STM32F4系统控制器有4个USART和4个UART,其中USART1和USART6的时钟来源于APB2总线时钟,最大频率为90MHz,其它6个时钟来源于APB1总线时钟,其最大频率为45MHz。
来自p161
串口配置
/*串口参数配置*/
USART_InitStructure.USART_BaudRate = 115200; /*设置波特率为115200*/
USART_InitStructure.USART_WordLength = USART_WordLength_8b; /*设置数据位为8位*/
USART_InitStructure.USART_StopBits = USART_StopBits_1; /*设置停止位为1位*/
USART_InitStructure.USART_Parity = USART_Parity_No; /*无奇偶校验*/
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; /*没有硬件流控*/
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; /*发送与接收*/
/*完成串口COM1的时钟配置、GPIO配置,根据上述参数初始化并使能*/
STM32串口异步通信需要定义的参数
USART有专门控制发送的发送器、控制接受的接收器,还有唤醒单元、中断控制等。使用USART之前需要向USART_CR1寄存器的UE位置1,使能USART。发送或者接收数据字长可选8位或9位,由USART_CR1的M位控制。
来自p162-164
串口调试为什么收到一直乱码?
- 首先检查上下两机位是否一致:波特率、数据位、停止位、奇偶校验等等
- 其次检查线缆:线缆长度是否过长,引入串扰,阻抗是否匹配,信号畸变是否在可控范围内
- 地电位:因为串口调试一般只需要三线
RX、TX、GND,地电位也非常重要,务必使你调试用的电脑地和你要调试的电路地电位基本一致(最好共地)。 - 电平:如果你使用的
TTL串口,那么还需要考虑是不是5V和3.3V的差异导致上述情况。
抢占优先级与子优先级
假设STM32配置了3个中断向量
-
当
STM32响应中断时,中断A能打断中断B的中断服务函数吗? -
中断
C能打断中断A吗? -
如果中断
A和中断C中断同时到达,响应哪个中断?
| 中断向量 | 抢占优先级 | 子优先级 |
|---|---|---|
| A | 2 | 0 |
| B | 3 | 0 |
| C | 2 | 1 |
-
A可以打断B -
C不能打断A -
响应中断
A
原因略
三种定时器
TIM1和TIM8定时器的功能包括【高级】
16位向上、向下、向上/下自动装载计数器16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值- 多达
4个独立通道: 输入捕获、输出比较、PWM生成(边缘或中间对齐模式)、单脉冲模式输出 - 死区时间可编程的互补输出
- 使用外部信号控制定时器和定时器互联的同步电路
- 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
- 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
- 如下事件发生时产生中断/
DMA: 更新(计数器向上溢出/向下溢出)、计数器初始化(通过软件或者内部/外部触发) 、触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) 、输入捕获、输出比较、刹车信号输入 - 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
TIMx主要功能通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括【通用】
16位向上、向下、向上/向下自动装载计数器16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值4个独立通道: 输入捕获、输出比较、PWM生成(边缘或中间对齐模式)、单脉冲模式输出- 使用外部信号控制定时器和定时器互连的同步电路
- 如下事件发生时产生中断/
DMA:更新(计数器向上溢出/向下溢出)、计数器初始化(通过软件或者内部/外部触发)、触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)、输入捕获 、输出比较 - 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
TIM6和TIM7定时器的主要功能包括【简单】
16位自动重装载累加计数器16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频- 触发
DAC的同步电路 注:此项是TIM6/7独有功能 - 在更新事件(计数器溢出)时产生中断/
DMA请求
EXTI 外部中断/事件控制器
EXTI 外部中断/事件控制器管理了控制器的23个中断/事件线,EXTI可分为两部分功能:一是产生中断,另一个是产生事件。
来自p136
SysTick
SysTick 系统定时器是CM4内核中的一个外设,内嵌在NVIC中。系统定时器是一个24位的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK等于180MHz。
来自参考手册
RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。
通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。
也就是说SysTick时钟源可以来自两个地方:
AHB时钟8分频HCLK(内核)时钟
通过SysTick控制与状态寄存器的设置进行选择时钟源。
来自p145 和 STM32的SysTick时钟源来自哪里?
STM32开发板堆栈增长方向
//保存栈增长方向
//0,向下增长;1,向上增长.
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
我的理解:
堆向上生长;栈向下生长
1栈增长方向向上;0栈增加方向向下
那堆呢?。。。
欢迎纠错
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
//ALIENTEK战舰STM32开发板堆栈增长方向以及CPU大小端测试
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8 dummy; //用于获取栈地址
if(addr==NULL) //第一次进入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //递归
}else //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
printf("stack_dir:%x\r\n",&stack_dir);
printf("cpu_endian:%x\r\n",&cpu_endian);
find_stack_direction(); //获取栈增长方式
find_cpu_endian(); //获取CPU大小端模式
while(1)
{
if(stack_dir)printf("STACK DIRCTION:向上生长\r\n\r\n");
else printf("STACK DIRCTION:向下生长\r\n\r\n");
if(cpu_endian)printf("CPU ENDIAN:大端模式\r\n\r\n");
else printf("CPU ENDIAN:小端模式\r\n\r\n");
delay_ms(500);
LED0=!LED0;
}
}
STM32定时器可以实现哪些功能?
- 实现延时功能
- 实现波特率调整
- 实现PWM输出
STM32的开发模式有哪些?
- 基于寄存器开发
- 基于固件库开发
- 基于操作系统
什么是嵌入式系统?
是以应用为中心,计算机技术为基础、软硬件可剪裁、适用于应用系统,对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统的关键特性是处理特定的任务,因此工程师能对其进行优化,以降低产品的体积和成本,提升可靠性和性能。
嵌入式系统的物理形态包括便携设备如计步器、电子手表和MP3播放器,大型固定设备如交通灯、工厂控制器,大型复杂系统如混合动力汽车、磁共振成像设备、航空电子设备等。它们的复杂度低至单片机,高至大型底盘或外壳内安装有多个部件、外设和网络。
中断服务程序,程序怎么死的?
启动文件中的中断服务程序和其它的中断服务程序有所不同,函数都是空的,真正的中断服务函数需要在外部C文件中重写。如果我们使用某外设时开启了某中断,却忘了写配套的终端服务函数或者函数名有误,那么中断发生时,程序就会跳转到启动文件预先写好的中断服务程序中(空),并在这个空函数中无限循环,OVER~
来自p113
固件库文件目录和文件的作用
固件库就是函数的集合,固件是程序,操作最底层设备
Libraries | Project | Utilities |
|---|---|---|
1、core_cm4.c , core_cm4.h :CMSIS核心文件,通过进入Cortex_M4内核的接口 | 1、stm32f40x_it.c , stm32f40x_it.h :编写中断服务程序 | Utilities文件夹下是一些固件实例参考程序 |
2、startup文件夹下是系统启动文件,根据不同的芯片选用启动文件,403系列芯片选用startup_stm32f40x_hd.s | 2、stm32f40x_conf.h :配置工程所需头文件 | |
3、system_stm32f40x.c , system_stm32f40x.h :设置系统及总线时钟,其中SystemInit函数在系统启动时,设置时钟系统。 | 3、STM32F10x_StdPeriph_Examples文件夹下是官方提供的固件实例代码。 | |
4、stm32f40x.h :系统寄存器定义、声明、内存操作。 | 4、STM32F40x_StdPeriph_Template文件夹是工程模板 |
来自p68-74 和 # STM32F4-固件库开发 和# stm32: 固件库文件说明 和 PPT
波特率计算
时钟控制逻辑计算
SCL线的时钟信号,由IIC接口根据时钟控制寄存器(CCR)控制,控制参数主要为时钟频率。配置IIC的CCR寄存器可修改通信速率相关的参数
- 可选择
IIC通信的“标准/快速”模式,这两个模式分别对应100kbps/400kbps的通信速率。 - 在快速模式下可选择
SCL时钟的占空比TLOW/THIGH,可选2或16/9模式。 CCR寄存器中还有一个12位的配置因子CCR,它与IIC外设的输入时钟源共同作用,产生SCL时钟。STM32的IIC外设都挂载在APB1总线上,使用APB1的时钟源PCLK1。SCL信号线的输出时钟公式如下
由于CCR寄存器是无法配置小数参数,所以我们智能把CCR取值为38,所以SCL实际频率无法达到400kHz。
来自p211
程序题1
初始化LED_GPIO灯
#include "bsp_led.h"
// 0- 首先要开GPIO端口的时钟
// 1- 要先确定引脚号
// 2- 要确定是输入还是输出 MODER
// 3- 如果是输出,那么是推挽还是开漏输出 OTYPER
// 4- 是是上拉还是下拉
// 5- 那么输出的速度是多少呢
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(LED_R_GPIO_CLK,ENABLE);
GPIO_InitStruct.GPIO_Pin = LED_R_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
GPIO_Init(LED_R_GPIO_PORT,&GPIO_InitStruct);
}
来自bsp_led.h
/*******************************************************/
//R 红色灯
#define LED1_PIN GPIO_Pin_10
#define LED1_GPIO_PORT GPIOH
#define LED1_GPIO_CLK RCC_AHB1Periph_GPIOH
//G 绿色灯
#define LED2_PIN GPIO_Pin_11
#define LED2_GPIO_PORT GPIOH
#define LED2_GPIO_CLK RCC_AHB1Periph_GPIOH
//B 蓝色灯
#define LED3_PIN GPIO_Pin_12
#define LED3_GPIO_PORT GPIOH
#define LED3_GPIO_CLK RCC_AHB1Periph_GPIOH
//小指示灯
#define LED4_PIN GPIO_Pin_11
#define LED4_GPIO_PORT GPIOD
#define LED4_GPIO_CLK RCC_AHB1Periph_GPIOD
/************************************************************/
/** 控制LED灯亮灭的宏,
* LED低电平亮,设置ON=0,OFF=1
* 若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可
*/
#define ON 0
#define OFF 1
初始化BASIC_TIMx(NVIC和TIM)
by the way
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
这句代码实现的功能是什么?
实现开启外设
#include "bsp_basic_tim.h"
static void TIMx_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIMx_IRQn;
// 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置子优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void BASIC_TIMx_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(BASIC_TIMx_CLK, ENABLE);
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到4999,即为5000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Period = 5000-1;
//定时器时钟源TIMxCLK = 2 * PCLK1
// PCLK1 = HCLK / 4
// => TIMxCLK=HCLK/2=SystemCoreClock/2=90MHz
// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
TIM_TimeBaseStructure.TIM_Prescaler = 9000-1; //预分频系数
// 初始化定时器TIMx, x[2,3,4,5]
TIM_TimeBaseInit(BASIC_TIMx, &TIM_TimeBaseStructure);
// 清除定时器更新中断标志位
TIM_ClearFlag(BASIC_TIMx, TIM_FLAG_Update);
// 开启定时器更新中断
TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,ENABLE);
// 使能定时器
TIM_Cmd(BASIC_TIMx, ENABLE);
}
void BASIC_TIMx_Config(void)
{
TIMx_NVIC_Configuration();
BASIC_TIMx_Mode_Config();
}
中断函数
来自stm32f4xx_it.c
void BASIC_TIMx_IRQHandler (void)
{
if ( TIM_GetITStatus( BASIC_TIMx, TIM_IT_Update) != RESET )
{
LED1_TOGGLE;
TIM_ClearITPendingBit(BASIC_TIMx, TIM_IT_Update);
}
}
main函数
#include "stm32f4xx.h"
#include "bsp_led.h"
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
BASIC_TIMx_Config();
while (1)
{
}//进入循环等待中断
}
程序题2
初始化按键口
//宏定义WK_UP口
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
void KEY_Init(void)//定义 按键初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//PA时钟,使能
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//WK_UP
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);//WK_UP初始化
}
初始化LED接口
//LED0,LED1口宏定义
#define LED0 PFout(9)
#define LED1 PFout(10)
void LED_Init(void) //LED初始化函数定义
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
//注意:定义结构体 应在 时钟使能 之前,否则编译出现警告!
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//时钟,使能
//F9
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//第九位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHz
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
//F10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//第十位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHz
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
//设置LED初始不亮
LED0 = 1;
LED1 = 1;
}
初始化蜂鸣器
#define BEEP PFout(8)//位操作蜂鸣器
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//时钟,使能
//F8
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;//第八位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHZ
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
GPIO_ResetBits(GPIOF,GPIO_Pin_8);//置低电平,蜂鸣器不响
}
KEY_Scan
uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
if ( (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)) == KEY_ON )
{
while( (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)) == KEY_ON );
// 做相应的动作
return KEY_ON;
}
else
return KEY_OFF;
}
LED和蜂鸣器同时响应
按键按下一次,LED灯亮,同时蜂鸣器发出响声,再次按下按键,LED灯灭,同时蜂鸣器停止发声。
int main(void)
{
u8 key;//保存按键扫描返回值
//初始化部分
LED_Init();
BEEP_Init();
KEY_Init();
delay_init(168);
//循环部分
while(1)
{
key=KEY_Scan();//将扫描结果传给key
if(key)//key不为零,即有按键按下
{
LED0=!LED0;
LED1=!LED1;
BEEP=!BEEP;
}
delay_init(10);
}
}
k1控制LED,k2控制蜂鸣器
void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
// 打开KEY_GPIO的时钟
RCC_AHB1PeriphClockCmd(KEY1_GPIO_CLK,ENABLE);
// 选定KEY_GPIO,就是具体的引脚号
GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN;
// 配置为输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
// 配置下拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStruct);
// 打开KEY_GPIO的时钟
RCC_AHB1PeriphClockCmd(KEY2_GPIO_CLK,ENABLE);
// 选定KEY_GPIO,就是具体的引脚号
GPIO_InitStruct.GPIO_Pin = KEY2_GPIO_PIN;
// 配置为输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
// 配置下拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStruct);
}
int main(void)
{
LED_GPIO_Config();
Key_GPIO_Config();
BEEP_Init();
while(1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
LED=!LED; //LED_R_TOGGLE;
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
BEEP=!BEEP;
}
}
}