配套软件 I2C 库:soft_i2c.h / soft_i2c.c(直接拖进工程 Src/Inc 即可)。 通用前提:CubeMX 里先配好时钟树(RCC → HSE Crystal,Clock Config 里 SYSCLK 常拉到 72MHz),SYS → Debug 选 Serial Wire(否则烧一次就锁 SWD)。


0. 软件 I2C(本库)

CubeMX:只需把 SCL、SDA 两个引脚点成 GPIO_Output

在 GPIO 配置里设 Output Open Drain + Pull-up。(也可以不在 CubeMX 配,SI2C_Init() 里已经自己初始化了。)

调用

#include "soft_i2c.h"

SI2C_Init();
if (SI2C_ScanAck(0x28)) printf("device online\r\n");  // 传7位地址
uint8_t id;
SI2C_ReadReg (0x28, 0x00, &id);      // 读寄存器0x00
SI2C_WriteReg(0x28, 0x10, 0x55);     // 写0x55到寄存器0x10
uint8_t buf[4];
SI2C_ReadBuf (0x28, 0x20, buf, 4);   // 连读4字节

硬件 I2C 卡死排不掉时,切软件 I2C 是最快的救场手段。


1. GPIO(普通输入/输出)

CubeMX:Pinout 视图点引脚 → 选 GPIO_OutputGPIO_Input。GPIO 配置页设:

  • Output 型:Mode = Push-Pull(普通)/ Open-Drain(如 I2C、驱动开漏器件);Pull;Speed;起个 Label(生成 LED_GPIO_Port/LED_Pin 宏,好用)。
  • Input 型:Pull = No/Up/Down。

HAL

HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);    // 置高
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);  // 置低
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);                 // 翻转
GPIO_PinState s = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin); // 读

2. GPIO 外部中断 EXTI(Hall 笔在位/按键)

CubeMX

  1. 引脚点成 GPIO_EXTIx
  2. GPIO 配置页:Mode 选 External Interrupt Mode with Rising / Falling / Rising-Falling edge;设 Pull。
  3. NVIC 页勾上对应 EXTIx interrupt(不勾中断进不来!)。

同一 EXTI 线号(如 PA0/PB0 都是 EXTI0)只能用一个引脚。

HAL:IRQ 处理和分发 CubeMX 已生成,你只需在 main.c重写回调

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == HALL_Pin) {
        g_pen_present = HAL_GPIO_ReadPin(HALL_GPIO_Port, HALL_Pin);
        // 注意:中断里别做耗时事,置个标志到主循环处理;消抖也放主循环
    }
}

3. TIM 定时器(周期中断 / 时基)

CubeMX:Timers → 选 TIMx → Clock Source = Internal Clock;Parameter Settings 设 Prescaler(PSC)Counter Period(ARR);NVIC 页勾 TIMx global interrupt(要中断才勾)。

周期 = (PSC+1)·(ARR+1) / TIM时钟。例:72MHz 出 1kHz → PSC=71, ARR=999 → 72e6/72/1000=1kHz。

HAL

HAL_TIM_Base_Start_IT(&htim4);     // 启动带中断的定时器

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  // 溢出回调
{
    if (htim->Instance == TIM4) {
        // 1kHz 周期任务放这里
    }
}

只要计数不要中断:HAL_TIM_Base_Start(&htim4) 然后读 __HAL_TIM_GET_COUNTER(&htim4)


4. 多通道 PWM

4a. 普通多路 PWM(TIM2/3/4,如多路 LED/风扇)

CubeMX:TIMx → Clock Source = Internal;Channel1~4 各选 PWM Generation CH1~CH4;设 PSC、ARR,每通道 Pulse(CCR)、Mode = PWM1。

频率 = TIM时钟/((PSC+1)(ARR+1));占空比 = CCR/(ARR+1)。四个通道共用同一频率,各自 CCR 独立。

HAL

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 300);  // 改CH1占空比(改CCR)
__HAL_TIM_SET_AUTORELOAD(&htim3, 999);              // 改频率(改ARR),配preload

4b. 半桥互补 PWM + 死区(TIM1,无线充电发射端驱动)

CubeMX:TIM1 → Channel1 = PWM Generation CH1 CH1N(同时出互补); Break And Dead Time management 里设 Dead Time(死区,防上下管直通,务必设);设 PSC/ARR/Pulse。

例:72MHz 出 130kHz → ARR = 72e6/130e3 - 1 ≈ 553,CCR = ARR/2(50%)。

HAL

HAL_TIM_PWM_Start (&htim1, TIM_CHANNEL_1);   // 高侧 CH1
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 互补低侧 CH1N(注意是 PWMN!)
__HAL_TIM_SET_AUTORELOAD(&htim1, arr);       // 改频率做扫频调功率
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, arr/2);

改 ARR 会变频,CubeMX 里 TIM1 的 Auto-reload preload = Enable,否则改频率中途会出毛刺脉冲冲击 MOS。


5. ADC(多通道采样)

⚠️ F1 两个致命点:① ADC 时钟必须 ≤ 14MHz(Clock Config 里把 ADC Prescaler 设 /6,72MHz→12MHz);② 上电必须校准

5a. 单/多通道 轮询(不带 DMA)

CubeMX:ADC1 → 勾要用的通道 IN0/IN1...;Parameter:

  • 单通道:Scan = Disable,Continuous = Disable。
  • 多通道轮询:Scan = EnableNumber Of Conversion = N,逐个设 Rank 的 Channel 和 Sampling Time(高阻源选长,如 239.5 Cycles)。

HAL

HAL_ADCEx_Calibration_Start(&hadc1);   // 开机先校准(F1 必做)

// 单通道:
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
uint16_t v = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);

多通道且没用 DMA 时,切通道要用 HAL_ADC_ConfigChannel() 重配 Rank1,否则每次读的都是同一路(见踩坑文档坑位)。

5b. 多通道 + DMA(推荐,多路一次刷完)

CubeMX:ADC1 → Scan = Enable,Continuous = Enable,Number Of Conversion = N,配好各 Rank; DMA Settings → Add → DMA1 通道,Mode = Circular,Data Width = Half WordDMA Continuous Requests = Enable

HAL

uint16_t adc_buf[3];   // 长度=通道数,顺序对应 Rank1,2,3

HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3);   // 启动后自动循环填充

// 之后随时直接读 adc_buf[0/1/2],DMA 在后台刷新,无需再 Start/Stop
uint16_t vbus_raw = adc_buf[0];

6. DMA(通用)

CubeMX:一般不单独配,在具体外设(ADC/USART/SPI)的 DMA Settings 页 Add Request。要点:

  • Mode:Normal(传一次停)/ Circular(循环,ADC 连采、串口连收用它)。
  • Data Width:按外设选(ADC 半字 Half Word,USART 字节 Byte)。
  • Increment:Memory 侧勾 Increment(写进数组),Peripheral 侧一般不勾。
  • NVIC 里对应 DMA 中断通常自动勾上(用回调就需要)。

HAL:走外设的 _DMA 版函数,回调在对应外设的完成回调里:

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)buf, N);
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { /* 一轮采完 */ }

HAL_UART_Transmit_DMA(&huart1, data, len);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { /* 发完 */ }

7. 硬件 I2C

CubeMX:Connectivity → I2C1 → Mode = I2C;Parameter:Speed(Standard 100k / Fast 400k)、7-bit 地址。引脚(PB6=SCL, PB7=SDA)自动出,注意它俩是开漏,硬件上要有 4.7k 上拉。

⚠️ 地址左移:datasheet 给的是 7 位地址,HAL 要传 8 位(7位 << 1)。

HAL(寄存器型器件最常用 Mem 系列):

#define IC_ADDR (0x28 << 1)   // 7位地址左移1位!

uint8_t val;
HAL_I2C_Mem_Read (&hi2c1, IC_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); // 读寄存器
HAL_I2C_Mem_Write(&hi2c1, IC_ADDR, 0x10, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); // 写寄存器

// 探测在线(返回 HAL_OK 表示有应答):
if (HAL_I2C_IsDeviceReady(&hi2c1, IC_ADDR, 2, 100) == HAL_OK) { /* online */ }

// 裸收发(非寄存器型):
HAL_I2C_Master_Transmit(&hi2c1, IC_ADDR, txbuf, len, 100);
HAL_I2C_Master_Receive (&hi2c1, IC_ADDR, rxbuf, len, 100);

卡死恢复(SDA 被从机拉死)见备赛文档坑2:切 GPIO 手动打 9 个 SCL + STOP。实在排不掉就直接换上面的软件 I2C。


附:三个最容易忘的 F1 开机动作

HAL_ADCEx_Calibration_Start(&hadc1);   // 1. ADC 必校准
// 2. ADC 时钟 Prescaler /6 让它 ≤14MHz(在 CubeMX Clock Config 做)
// 3. 硬件 I2C 地址一律 (addr7 << 1)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐