F103 mapspl · registers · flow · 10 modules
10 个模块
模块 / Modules 10 / 10

STM32F103 / peripheral quick map

STM32 F103 外设速查:SPL 入口、寄存器组与完整流程

10 个模块 · 同一页 偏移量相对各外设基地址 批注自动同步

十个模块一张卡:SPL 入口(结构体与常用函数)、F103 寄存器组与基地址、挂在哪条总线、完整数据流程的先后顺序、以及容易踩的备注。偏移量都是相对该外设基地址的十六进制偏移。

目录 / Contents10 个模块
  1. RCC
  2. GPIO
  3. EXTI
  4. USART
  5. SPI
  6. I2C
  7. ADC
  8. TIM
  9. DMA
  10. CAN

RCC

AHB;自身时钟固定

SPL 入口:无结构体;RCC_DeInit、RCC_HSEConfig、RCC_PLLConfig、RCC_SYSCLKConfig、RCC_APB2/APB1/AHBPeriphClockCmd、RCC_GetClocksFreq

寄存器组(基址 0x40021000):CR (00)、CFGR (04)、CIR (08)、APB2RSTR (0C)、APB1RSTR (10)、AHBENR (14)、APB2ENR (18)、APB1ENR (1C)、BDCR (20)、CSR (24)

完整数据流程

  1. 开 HSE 等 HSERDY
  2. 设 Flash 等待周期(FLASH_ACR)
  3. CFGR 设 HPRE / PPRE1 / PPRE2 分频
  4. 设 PLLSRC、PLLMUL
  5. 使能 PLL 等 PLLRDY
  6. SW 切系统时钟到 PLL,等 SWS
  7. 用各 ENR 给外设开时钟

备注:APB1 ≤ 36MHz、APB2 ≤ 72MHz;APB1 分频 ≠ 1 时挂其下的定时器时钟 ×2;外设时钟位查手册

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 27 行
C27 lines
/*
* 用途:把系统时钟从复位后的 8MHz HSI 切到 8MHz HSE ×9 = 72MHz,并给出第一条外设时钟。
* 逻辑:1) 先回复位态,避免别人已经改过时钟;2) 起 HSE 并等就绪;
* 3) Flash 先加等待周期,否则主频一上去取指就出错;4) 排 AHB/APB2/APB1 三条分频;
* 5) 配 PLL 源和倍频、开 PLL、等就绪;6) 切系统时钟并回读确认;7) 才给外设开时钟。
* 注意:APB1 最高 36MHz,所以 PCLK1 必须二分频;APB1 分频不为 1 时它下面的定时器时钟 ×2 回到 72MHz。
*/
void RCC_SystemClock72MHz(void)
{
RCC_DeInit(); /* 时钟寄存器回到复位值:HSI 当系统时钟 */
RCC_HSEConfig(RCC_HSE_ON); /* CR 的 HSEON=1,起 8MHz 外部晶振 */
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET)
; /* 等 CR 的 HSERDY,起振没成就往下走必炸 */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);/* 预取缓冲,减少取指等待 */
FLASH_SetLatency(FLASH_Latency_2); /* 72MHz 要 2 个等待周期(FLASH_ACR 的 LATENCY=10) */
RCC_HCLKConfig(RCC_SYSCLK_Div1); /* CFGR.HPRE:AHB = SYSCLK = 72MHz */
RCC_PCLK2Config(RCC_HCLK_Div1); /* CFGR.PPRE2:APB2 = 72MHz(GPIO/ADC1/USART1/SPI1/TIM1) */
RCC_PCLK1Config(RCC_HCLK_Div2); /* CFGR.PPRE1:APB1 = 36MHz(USART2/3、SPI2/3、I2C、TIM2~4、CAN) */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* CFGR:PLL 源=HSE/1,倍频 ×9 → 8×9=72MHz */
RCC_PLLCmd(ENABLE); /* CR.PLLON=1 */
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
; /* 等 CR.PLLRDY,PLL 锁定后才能切 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* CFGR.SW=10,把系统时钟切到 PLL */
while (RCC_GetSYSCLKSource() != 0x08)
; /* 回读 CFGR.SWS==10 才真切过去了 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/* APB2ENR.IOPAEN=1:后面才能写 GPIOA */
}

GPIO

APB2;`APB2ENR`:IOPAEN = 2、IOPBEN = 3、IOPCEN = 4

SPL 入口:结构体 GPIO_InitTypeDef(Pin / Speed / Mode);GPIO_Init、GPIO_SetBits、GPIO_ResetBits、GPIO_ReadInputDataBit、GPIO_Write

寄存器组(GPIOA 0x40010800,每路差 0x400):CRL (00)、CRH (04)、IDR (08)、ODR (0C)、BSRR (10)、BRR (14)、LCKR (18)

完整数据流程

  1. 开 GPIO 时钟
  2. CRL/CRH 每引脚配 4 位:MODE [1:0] + CNF [3:2]
  3. 输出:BSRR/BRR 或 ODR 改电平
  4. 输入:读 IDR

备注:上拉 / 下拉输入 CNF = 10,由 ODR 决定上拉 (1) / 下拉 (0);BSRR 单条写、原子;引脚位偏移 = 引脚号 × 4

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 26 行
C26 lines
/*
* 用途:PA8 推挽输出点灯、PB0 上拉输入读按键,走一遍 GPIO 的固定四步。
* 逻辑:1) 开 GPIO 时钟;2) 填 Pin/Mode/Speed 三个字段;3) GPIO_Init 把它写进 CRL 或 CRH 的 4 位字段;
* 4) 输出用 BSRR/BRR 改电平,输入读 IDR。
* 注意:输入模式不看 GPIO_Speed;上拉/下拉的开关值写在 ODR 里;置位清零用 BSRR 而不是读改 ODR,避免竞态。
*/
void GPIO_LedKeyInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); /* APB2ENR 的 IOPAEN、IOPBEN */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; /* bit8 → 落在 CRH(pin 8~15) */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* CNF=00 MODE=11:推挽输出 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* F103 只有 2/10/50 三档 */
GPIO_Init(GPIOA, &GPIO_InitStructure); /* 写 GPIOA->CRH 的 [19:16] 四位 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* bit0 → 落在 CRL */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* CNF=10 MODE=00:上拉输入,松开读 1 */
GPIO_Init(GPIOB, &GPIO_InitStructure); /* 输入模式不需要 Speed,沿用上次的值也无所谓 */
GPIO_SetBits(GPIOA, GPIO_Pin_8); /* BSRR 低半字写 1:置位,单条指令原子完成 */
GPIO_ResetBits(GPIOA, GPIO_Pin_8); /* BRR(或 BSRR 高半字):清零 */
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == Bit_SET)
; /* 读 IDR 的 bit0:判断按键是否松开 */
}

EXTI

APB2;必须开 AFIO 时钟(`APB2ENR` bit0)

SPL 入口:结构体 EXTI_InitTypeDef(Line / Mode / Trigger / LineCmd);EXTI_Init、GPIO_EXTILineConfig、NVIC_Init、EXTI_GetITStatus、EXTI_ClearITPendingBit

寄存器组:EXTI(基址 0x40010400)IMR (00)、EMR (04)、RTSR (08)、FTSR (0C)、SWIER (10)、PR (14);AFIO(基址 0x40010000)EVCR、MAPR、EXTICR1~4

完整数据流程

  1. 开 GPIO + AFIO 时钟
  2. GPIO 配输入(上拉 / 下拉 / 浮空)
  3. AFIO_EXTICR 选该线对应哪个端口
  4. EXTI:IMR 使能、RTSR / FTSR 选触发沿
  5. NVIC 设优先级并使能
  6. ISR 处理后写 PR 清标志

备注:Line0~4 各占一个中断;Line5~9 共用、Line10~15 共用;不重映射但用 EXTI 也要开 AFIO

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 40 行
C40 lines
/*
* 用途:PB0 按键下降沿进中断,串起 GPIO→AFIO→EXTI→NVIC→ISR 这条完整链路。
* 逻辑:1) 开 GPIO 和 AFIO 时钟;2) 引脚先配成输入;3) AFIO_EXTICR 告诉硬件"Line0 来自 PB0";
* 4) EXTI 配触发沿并解除屏蔽;5) NVIC 开中断源和优先级;6) ISR 里处理完必须清 PR。
* 注意:只要用 EXTI 就得开 AFIO 时钟,哪怕不做重映射;Line5~9 共用 EXTI9_5_IRQn、Line10~15 共用 EXTI15_10_IRQn。
*/
void EXTI_KeyInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); /* AFIOEN=bit0,EXTI 选端口靠它 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 按键接 GND:平时上拉到高,按下拉低 */
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); /* 写 AFIO->EXTICR1:把 Line0 接到 PB */
EXTI_InitStructure.EXTI_Line = EXTI_Line0; /* 对应 IMR/RTSR/FTSR/PR 的 bit0 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; /* IMR=1 走中断(EMR 才是事件模式) */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;/* FTSR=1:下降沿触发 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; /* 内核侧的中断号 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET) /* 先判 PR 与 IMR,确认是这条线 */
{
EXTI_ClearITPendingBit(EXTI_Line0); /* 写 PR=1 清挂起:不清就反复进中断 */
}
}

USART

USART1 → APB2(bit14);USART2 / 3 → APB1(bit17 / 18)

SPL 入口:结构体 USART_InitTypeDef(BaudRate / WordLength / StopBits / Parity / Mode / HWFlow);USART_Init、USART_Cmd、USART_SendData、USART_ReceiveData、USART_GetFlagStatus、USART_ITConfig

寄存器组(USART1 0x40013800,USART2 0x40004400,USART3 0x40004800):SR (00)、DR (04)、BRR (08)、CR1 (0C)、CR2 (10)、CR3 (14)、GTPR (18)

完整数据流程

  1. 开 GPIO + USART 时钟
  2. TX 配复用推挽,RX 配浮空 / 上拉输入
  3. BRR 按总线时钟算波特率
  4. CR1 设字长 / 校验 / TE / RE,最后 UE = 1
  5. 发:等 TXE → 写 DR → 等 TC
  6. 收:等 RXNE → 读 DR

备注:BRR = USARTDIV(整数 + 小数);中断方式用 CR1 的 RXNE / TXE 使能;printf 重定向 fputc

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 48 行
C48 lines
/*
* 用途:USART1 配 115200 8N1、开 RXNE 中断,并把 printf 重定向到串口。
* 逻辑:1) 开 GPIOA 与 USART1 时钟(都在 APB2);2) PA9 复用推挽当 TX、PA10 浮空输入当 RX;
* 3) BRR 按 PCLK2 和波特率算分频;4) CR1 配字长/校验/收发并最后置 UE;5) 开 RXNE 中断与 NVIC;
* 6) 发送等 TXE 再写 DR。
* 注意:USART1 挂 APB2(72MHz),USART2/3 挂 APB1(36MHz),同一个波特率算出来的 BRR 不一样。
*/
void USART1_InitBaud(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); /* APB2ENR 的 USART1EN=bit14 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; /* TX */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; /* 复用推挽:输出交给外设驱动 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; /* RX */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; /* 输入浮空即可 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baud; /* 内部换算 BRR = USARTDIV(整数 12 位 + 小数 4 位) */
USART_InitStructure.USART_WordLength = USART_WordLength_8b; /* CR1.M=0:8 位数据 */
USART_InitStructure.USART_StopBits = USART_StopBits_1; /* CR2.STOP */
USART_InitStructure.USART_Parity = USART_Parity_No; /* CR1 的 PCE=0 */
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; /* CR3 的 CTSE/RTSE 不开 */
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; /* CR1 的 TE|RE */
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); /* CR1.RXNEIE=1:收到一个字节就进中断 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE); /* CR1.UE=1,放最后 */
}
int fputc(int ch, FILE *f) /* 标准库的弱函数,重写它 printf 就走串口 */
{
USART_SendData(USART1, (uint8_t)ch); /* 写 DR,硬件自动开始移位 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
; /* 等 SR.TXE=1(数据已进移位寄存器)再写下一字节 */
return ch;
}

SPI

SPI1 → APB2(bit12);SPI2 / 3 → APB1(bit14 / 15)

SPL 入口:结构体 SPI_InitTypeDef(Direction / Mode / DataSize / CPOL / CPHA / NSS / BaudRatePrescaler / FirstBit);SPI_Init、SPI_Cmd、SPI_I2S_SendData、SPI_I2S_ReceiveData、SPI_GetFlagStatus

寄存器组(SPI1 0x40013000,SPI2 0x40003800,SPI3 0x40003C00):CR1 (00)、CR2 (04)、SR (08)、DR (0C)、CRCPR (10)、RXCRCR (14)、TXCRCR (18)、I2SCFGR (1C)、I2SPR (20)

完整数据流程

  1. 开 GPIO + SPI 时钟
  2. SCK / MOSI 复用推挽,MISO 浮空 / 上拉输入
  3. CR1:MSTR、CPOL / CPHA、BR 分频、DFF、SSM + SSI、LSBFIRST
  4. SPE = 1
  5. 等 TXE → 写 DR;等 RXNE → 读 DR;收尾等 BSY = 0

备注:全双工发 1 字节同时收 1 字节;NSS 常用软件 SSM + SSI;mode0 = CPOL0 / CPHA0,mode3 = CPOL1 / CPHA1;SCK = 总线时钟 / 2^(BR+1)

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 47 行
C47 lines
/*
* 用途:SPI1 做主机、模式 0、9MHz 时钟,配好四根线并给出单字节收发函数(W25Q64 就靠它)。
* 逻辑:1) 开 GPIOA 与 SPI1 时钟;2) SCK/MOSI 复用推挽、MISO 浮空输入、CS 软件推挽;
* 3) CR1 配主从/CPOL+CPHA/分频/帧格式/NSS/MSB;4) SPE=1 使能;5) 收发都靠判 SR 的标志位。
* 注意:全双工——发一个字节必然同时收一个字节,所以读完要判 RXNE;SCK = 72MHz / 2^(BR+1),BR 分频 8 → 9MHz。
*/
void SPI1_MasterInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); /* APB2ENR.SPI1EN=bit12 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; /* SCK 与 MOSI:都交给外设驱动 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; /* MISO:从机发回来的线 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; /* 片选用软件控制,配成普通推挽 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4); /* 不选中:CS 拉高 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; /* 两线全双工 */
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; /* CR1.MSTR=1 */
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; /* CR1.DFF=0:8 位帧 */
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; /* 模式 0:时钟空闲为低 */
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; /* 第一个边沿采样 */
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; /* SSM=1 + SSI=1:硬件不管片选 */
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; /* CR1.BR:72/8 = 9MHz */
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; /* 高位先行 */
SPI_InitStructure.SPI_CRCPolynomial = 7; /* 不用 CRC 也要给个值 */
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE); /* CR1.SPE=1 */
}
uint8_t SPI1_SendByte(uint8_t byte)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
; /* SR.TXE=1 才写得进 DR */
SPI_I2S_SendData(SPI1, byte); /* 写 DR 启动移位 */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
; /* 等 SR.RXNE:同时收到的那个字节 */
return SPI_I2S_ReceiveData(SPI1); /* 读 DR(不读会一直置着 RXNE) */
}

I2C

APB1;I2C1EN = 21、I2C2EN = 22

SPL 入口:结构体 I2C_InitTypeDef(ClockSpeed / Mode / DutyCycle / OwnAddress1 / Ack / AckAddr);I2C_Init、I2C_Cmd、I2C_GenerateSTART、I2C_Send7bitAddress、I2C_SendData、I2C_ReceiveData、I2C_CheckEvent

寄存器组(I2C1 0x40005400,I2C2 0x40005800):CR1 (00)、CR2 (04)、OAR1 (08)、OAR2 (0C)、DR (10)、SR1 (14)、SR2 (18)、CCR (1C)、TRISE (20)

完整数据流程

  1. 开 GPIO + I2C 时钟
  2. SCL / SDA 配复用开漏(需外部上拉)
  3. CR2 填 APB1 频率;CCR 算时序、TRISE;DutyCycle
  4. OAR1 自身地址、ACK、PE = 1
  5. 发:START → 等 EV5 → 发地址 + W → 等 EV6 → 发数据 → STOP
  6. 收:START → 地址 + R → 配置 ACK / STOP → 读 DR

备注:标准 100kHz / 快速 400kHz;用事件 EV5 / EV6 / EV7 / EV8 检查;硬件 I2C 易卡死(BUSY 异常),工程常改用软件 I2C

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 48 行
C48 lines
/*
* 用途:I2C1 做主机跑 400kHz,并给出对 AT24C02 写一个字节的完整事件流。
* 逻辑:1) 开 GPIOB 与 I2C1 时钟(I2C 在 APB1);2) PB6/PB7 复用开漏 + 外部上拉;
* 3) 配模式/占空比/自身地址/ACK/7 位地址/速率;4) 使能;5) 传输按 START→地址→数据→STOP 逐事件等标志。
* 注意:必须开漏(线与),推挽会打架;硬件 I2C 卡在 BUSY 是常见故障,所以先等 BUSY 清;工程上常退到软 I2C。
*/
void I2C1_MasterInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); /* APB1ENR.I2C1EN=bit21 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; /* SCL / SDA */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; /* 复用开漏:靠外部上拉拉高 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2C1); /* 复位外设,避免上次异常留下的状态 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; /* CR1.SMBUS=0:标准 I2C 模式 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /* CCR 计算用的占空比(快速模式才有意义) */
I2C_InitStructure.I2C_OwnAddress1 = 0x0A; /* OAR1:主机也要有个地址位 */
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; /* CR1.ACK=1:收数据时回 ACK */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000; /* 由 CR2.FREQ + TRISE + CCR 算出 */
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE); /* CR1.PE=1 */
}
void I2C1_WriteOne(uint8_t reg, uint8_t value)
{
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET)
; /* SR1.BUSY:总线没让开就先等 */
I2C_GenerateSTART(I2C1, ENABLE); /* CR1.START=1 */
while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == ERROR)
; /* EV5:SB 置起,模式已选 */
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); /* 器件地址 0xA0 + W 位 */
while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR)
; /* EV6:ADDR+TXE,从机 ACK 了 */
I2C_SendData(I2C1, reg); /* 先写 EEPROM 内部地址 */
while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR)
; /* EV8:BTF,这一字节被对方收走 */
I2C_SendData(I2C1, value); /* 再写数据 */
while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR)
;
I2C_GenerateSTOP(I2C1, ENABLE); /* CR1.STOP=1 释放总线 */
}

ADC

APB2;ADC1EN = 9、ADC2EN = 10、ADC3EN = 15;ADCK 由 `CFGR` 的 ADCPRE 分频

SPL 入口:结构体 ADC_InitTypeDef(Mode / ScanConv / ContinuousConv / ExternalTrig / DataAlign / NbrOfChannel);ADC_Init、ADC_Cmd、ADC_RegularChannelConfig、ADC_ResetCalibration、ADC_StartCalibration、ADC_SoftwareStartConvCmd、ADC_GetConversionValue

寄存器组(ADC1 0x40012400,ADC2 0x40012800,ADC3 0x40013C00):SR (00)、CR1 (04)、CR2 (08)、SMPR1 (0C)、SMPR2 (10)、SQR1~3 (2C / 30 / 34)、DR (4C)

完整数据流程

  1. 开 GPIO(模拟输入)+ ADC 时钟
  2. ADCPRE 分频使 ADCK ≤ 14MHz(72 / 6 = 12)
  3. GPIO 配模拟输入 CNF = 00 / MODE = 00
  4. CR1 模式 / 扫描,CR2 触发 / 连续 / 对齐 / DMA
  5. SMPR 设采样时间,SQR 设转换顺序
  6. ADON 后做复位校准 + 校准并等待
  7. 软件启动,等 EOC,读 DR

备注:12 位;必须校准;多通道常用 DMA 读 DR;双 ADC 模式由 CR1 配置;参考电压 VREF

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 45 行
C45 lines
/*
* 用途:ADC1 用软件触发读 PA0(通道 0),把 12 位码值取出来。
* 逻辑:1) 开 GPIOA 与 ADC1 时钟,并把 ADC 时钟分频到 ≤14MHz;2) PA0 配模拟输入;
* 3) 配独立模式/单通道/连续/软件触发/右对齐;4) 排通道与采样时间;5) 开 ADON 后必须做两次校准;
* 6) 软件启动,等 EOC 读 DR。
* 注意:ADCPRE 在 RCC->CFGR 里,72/6=12MHz 才合规;不校准读数会飘;多通道连续采集要配 DMA,否则 DR 会被覆盖。
*/
void ADC1_SoftwareStartInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); /* APB2ENR.ADC1EN=bit9 */
RCC_ADCCLKConfig(RCC_PCLK2_Div6); /* CFGR.ADCPRE=101 → ADCK = 72/6 = 12MHz */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; /* CNF=00 MODE=11:模拟输入,数字输入缓冲关掉 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; /* CR1.DUALMOD=0:不用双 ADC */
ADC_InitStructure.ADC_ScanConvMode = DISABLE; /* 单通道,不需要扫描 */
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; /* CR2.CONT=1:转完自动接着转 */
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; /* CR2.EXTSEL/EXTSWSTART:软件触发 */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; /* CR2.ALIGN=0 右对齐 */
ADC_InitStructure.ADC_NbrOfChannel = 1; /* SQR1.L=0:规则组 1 个通道 */
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); /* SMPR2.SMP0 + SQR3 的顺序 */
ADC_Cmd(ADC1, ENABLE); /* CR2.ADON=1,需要稳定时间 */
ADC_ResetCalibration(ADC1); /* CR2.RSTCAL=1 复位校准 */
while (ADC_GetResetCalibrationStatus(ADC1) == SET)
; /* 等它自己清 0 */
ADC_StartCalibration(ADC1); /* CR2.CAL=1 开始校准 */
while (ADC_GetCalibrationStatus(ADC1) == SET)
; /* 等 CAL 清 0,没校准完就转换等于白读 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); /* CR2.SWSTART=1 */
}
uint16_t ADC1_ReadOne(void)
{
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
; /* SR.EOC=1 表示这一通道转完 */
return ADC_GetConversionValue(ADC1); /* 读 DR:0~4095,读后 EOC 自动清 */
}

TIM

TIM2~4 → APB1(bit0 / 1 / 2);TIM1 → APB2(bit11)

SPL 入口:结构体 TIM_TimeBaseInitTypeDef(Prescaler / CounterMode / Period / ClockDivision / RepetitionCounter);TIM_TimeBaseInit、TIM_Cmd、TIM_ITConfig、TIM_OC1Init、TIM_SetCompare1、TIM_CtrlPWMOutputs

寄存器组(TIM2 0x40000000、TIM3 0x40000400、TIM4 0x40000800,TIM1 0x40012C00):CR1 (00)、CR2 (04)、SMCR (08)、DIER (0C)、SR (10)、EGR (14)、CCMR1/2 (18 / 1C)、CCER (20)、CNT (24)、PSC (28)、ARR (2C)、CCR1~4 (34~40)

完整数据流程

定时中断:

  1. 开 TIM 时钟
  2. PSC 分频、ARR 重装值
  3. CR1 计数模式 / ARRPE,DIER 使能更新中断
  4. NVIC 使能
  5. CEN = 1
  6. ISR 清 UIF

PWM:另把 GPIO 配复用推挽 → CCMR 设 OCxM / 预装载 → CCER 使能 → CCR 设占空比

备注:周期 = (PSC + 1) × (ARR + 1) / TIM_CLK;定时器时钟在 APB1 分频 ≠ 1 时 ×2(= 72MHz);TIM1 高级定时器必须开 BDTR.MOE 才有 PWM

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 60 行
C60 lines
/*
* 用途:TIM2 出 1ms 周期中断当系统节拍;TIM3 的 PA6 输出 1kHz、50% 占空比的 PWM。
* 逻辑:1) 开定时器时钟;2) PSC 把 72MHz 分下来、ARR 定周期(都要减一);3) 中断版开 UIF 中断 + NVIC,
* PWM 版把引脚配复用推挽、CCMR 设 OCxM=PWM1、CCER 使能输出、CCR 给比较值;4) CEN=1 开跑。
* 注意:APB1 分频≠1 时定时器时钟是 72MHz 而不是 36MHz;TIM1 是高级定时器,还要 BDTR.MOE=1 才出得来波。
*/
void TIM2_TickInit(void) /* 72MHz / 7200 / 10 = 1kHz → 1ms */
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); /* APB1ENR.TIM2EN=bit0 */
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; /* PSC:72MHz/7200 = 10kHz 计数 */
TIM_TimeBaseStructure.TIM_Period = 10 - 1; /* ARR:数 10 个 → 1ms 一次更新事件 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; /* CR1.DIR=0 向上计数 */
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; /* 普通定时器忽略这个字段 */
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); /* DIER.UIE=1 */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE); /* CR1.CEN=1,CNT 开始走 */
}
void TIM3_PWMInit(void) /* PA6 = TIM3_CH1,1kHz / 50% */
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; /* 波形由定时器输出,引脚必须是复用 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; /* 计数时钟 1MHz */
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; /* 数 1000 个 → 1kHz 周期 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; /* CCMR1.OC1M=110:比较匹配时输出有效电平 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; /* CCER.OC1E=1 */
TIM_OCInitStructure.TIM_Pulse = 500; /* CCR1=500 / ARR=1000 → 占空比 50% */
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /* CCER.OC1P=0 */
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); /* CCMR1.OC1PE=1:CCR 走影子寄存器 */
TIM_ARRPreloadConfig(TIM3, ENABLE); /* CR1.ARPE=1:ARR 也走预装载 */
TIM_Cmd(TIM3, ENABLE);
}

DMA

AHB;`AHBENR`:DMA1EN = 0、DMA2EN = 1

SPL 入口:结构体 DMA_InitTypeDef(外设 / 内存地址、DIR、BufferSize、Inc、DataSize、Mode、Priority、M2M);DMA_Init、DMA_Cmd、DMA_GetFlagStatus、DMA_ClearFlag、DMA_ITConfig

寄存器组(DMA1 0x40020000,DMA2 0x40020400):ISR (00)、IFCR (04),每通道 CCR (+0)、CNDTR (+4)、CPAR (+8)、CMAR (+C)

完整数据流程

  1. 开 DMA + 外设时钟
  2. CPAR 填外设地址、CMAR 填内存地址
  3. CCR:方向、数据宽度、地址自增、正常 / 循环、优先级
  4. CNDTR 填传输数量
  5. 外设侧使能 DMA 请求(ADC / DMA、SPI CR2、USART CR3)
  6. EN = 1 启动
  7. 等 TCIF 或进中断

备注:DMA1 共 7 通道、DMA2 共 5 通道(大容量);通道与外设固定映射,必须查手册表;ADC 连续采集用循环模式

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 31 行
C31 lines
/*
* 用途:让 ADC1 的转换结果自动搬进数组,CPU 不参与搬运(连续采集的标准做法)。
* 逻辑:1) 开 DMA 时钟(在 AHB);2) 选通道并复位——ADC1 固定是 DMA1 通道 1,不能挑;
* 3) 填外设地址/内存地址/方向/长度/自增/数据宽度/循环/优先级;4) 开 DMA;5) 外设侧也要放行 DMA 请求。
* 注意:DMA1 只有 7 个通道且与外设是硬映射,必须查手册那张表;循环模式用于连续采集,单次模式搬完就停。
*/
uint16_t adc_buffer[8];
void ADC1_DMARoundRobinInit(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* AHBENR.DMA1EN=bit0 */
DMA_DeInit(DMA1_Channel1); /* 通道寄存器回到复位值,清掉上次残留 */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; /* CPAR:源地址 = ADC 数据寄存器 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer; /* CMAR:目的地址 = 数组 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* CCR.DIR=0:外设→内存 */
DMA_InitStructure.DMA_BufferSize = 8; /* CNDTR:一次搬 8 个 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* DR 是同一个地址,不自增 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* 数组往后走 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /* 16 位 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* CCR.CIRC=1:搬完自动重装 CNDTR */
DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* CCR.PL */
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /* 不是内存到内存 */
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE); /* CCR.EN=1,先让通道待命 */
ADC_DMACmd(ADC1, ENABLE); /* ADC1->CR2.DMA=1:外设侧把请求放出来 */
}

CAN

APB1;CAN1EN = 25、CAN2EN = 26

SPL 入口:结构体 CAN_InitTypeDef(Prescaler / Mode / SJW / BS1 / BS2 / ABOM / NART…);过滤器 CAN_FilterInitTypeDef;CAN_Init、CAN_FilterInit、CAN_Transmit、CAN_TransmitStatus、CAN_Receive、CAN_MessagePending

寄存器组(CAN1 0x40006400):MCR (00)、MSR (04)、TSR (08)、RF0R (0C)、RF1R (10)、IER (14)、ESR (18)、BTR (1C);发送 / 接收邮箱、过滤器 FMR / FM1R / FS1R / FFA1R / FA1R

完整数据流程

  1. 开 GPIO(RX 上拉、TX 复用推挽)+ CAN 时钟
  2. AFIO 重映射(可选 PA11/12 → PB8/9)
  3. 进初始化模式,BTR 配波特率
  4. 配过滤器(组号 / ID / 掩码 / 位宽 / FIFO)
  5. 退出初始化进入正常模式
  6. 发:填邮箱 ID / DLC / 数据 → TXRQ → 等完成
  7. 收:FIFO 有报文 → 读邮箱 → RFOM 释放

备注:波特率 = APB1 / [分频 × (1 + tBS1 + tBS2)];不过滤则收不到;标准帧 11 位 ID、扩展帧 29 位;CAN2 与 CAN1 共用过滤器

标准库初始化:上面那条流程的库写法,逐行对着 F11 跟一遍。

展开代码 · 75 行
C75 lines
/*
* 用途:先不接收发器做板内回环(发一条自己收回来),把波特率和过滤器跑通。
* 逻辑:1) 开 GPIO/AFIO/CAN 时钟并做引脚重映射(PA11/12 与 USB 冲突 → PB8/PB9);
* 2) RX 上拉输入、TX 复用推挽;3) 配位时序进初始化模式;4) 配一个过滤器组(ID/掩码/位宽/FIFO);
* 5) 回环模式下发一条,判发送状态;6) 收:查 FIFO 再读邮箱。
* 注意:波特率 = APB1 / (Prescaler × (1 + BS1 + BS2)),这里 36MHz/(4×12) = 750kbps,要 500k 把 Prescaler 改 6;
* 过滤器一条都不匹配就等于收不到;回环只验证控制器,上总线还得接收发器和两端 120Ω。
*/
void CAN1_LoopbackInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;
NVIC_InitTypeDef NVIC_InitStructure;
uint8_t mailbox;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); /* APB1ENR.CAN1EN=bit25 */
GPIO_PinRemapConfig(GPIO_Remap2_CAN1, ENABLE); /* PB8=CAN_RX、PB9=CAN_TX,避开 USB 的 PA11/PA12 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; /* RX:输入上拉,总线空闲为高 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; /* TX:复用推挽 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
CAN_InitStructure.CAN_Prescaler = 4; /* BTR.BRP:一个时间量子 tq 的长度 */
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; /* 板内回环,不驱动引脚 */
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; /* 重同步跳转宽度 */
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq; /* 位段 1 */
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; /* 位段 2:一位 = 1+8+3 = 12tq */
CAN_InitStructure.CAN_TTCM = DISABLE; /* 不用时间触发模式 */
CAN_InitStructure.CAN_ABOM = DISABLE; /* 自动离线管理:先关,自己看 ESR */
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE; /* 允许自动重发 */
CAN_InitStructure.CAN_RFLM = DISABLE; /* FIFO 满时释放,新报文覆盖旧的 */
CAN_InitStructure.CAN_TXFP = ENABLE; /* 发送优先级由请求顺序决定 */
CAN_Init(CAN1, &CAN_InitStructure); /* 内部完成 MCR.INRQ 进出初始化模式 */
CAN_FilterInitStructure.CAN_FilterNumber = 0; /* 0~13 组,CAN2 只能用后面的 */
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; /* 标识符 + 掩码 */
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; /* 一组一个 32 位过滤器 */
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; /* 全 0 ID + 全 0 掩码 = 全收 */
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; /* 命中的报文进 FIFO0 */
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; /* FA1R 里置对应 bit */
CAN_FilterInit(&CAN_FilterInitStructure);
TxMessage.StdId = 0x12; /* 标准帧 11 位 ID(TIR 里) */
TxMessage.ExtId = 0x00;
TxMessage.IDE = CAN_Id_Standard;
TxMessage.RTR = CAN_RTR_Data; /* 数据帧,不是远程帧 */
TxMessage.DLC = 3; /* 3 字节有效 */
TxMessage.Data[0] = 1;
mailbox = CAN_Transmit(CAN1, &TxMessage); /* 填邮箱并置 TXRQ,返回用了哪个邮箱 */
while (CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok)
; /* 等 TSR 里这个邮箱报完成 */
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0) /* 回环模式下自己发的会回到 FIFO0 */
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); /* 读出来,RF0R 的 FMP 自动减一 */
}
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; /* CAN1 FIFO0 的中断号(与 USB 共用向量) */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); /* IER.FMPIE0=1:FIFO0 有报文就进中断 */
}

没有匹配的模块。

src="../../assets/sw-register.js?v=d45e0dfff6">