PUYA I2C — 主机轮询通信(HAL 库)

PUYA PY32F0xx_Firmware V1.5.0 I2C 主机轮询通信(I2C_TwoBoard_CommunicationMaster_Polling)示例教学

I2C — 主机轮询通信(HAL 库)

固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/I2C/I2C_TwoBoard_CommunicationMaster_Polling/Src/main.c


功能简介

I2C(Inter-Integrated Circuit)是一种两线制(SCL 时钟 + SDA 数据)、半双工、一主多从的串行总线,常用于连接 EEPROM、传感器、OLED 屏等。

本示例演示主机(Master)工作方式

  • 主机先向从机地址 0xA0 发送 15 字节;
  • 再向同一地址 接收 15 字节(示例用两块板回环对接,从机把收到的数据原样发回);
  • CPU 用轮询方式等待每笔传输完成(HAL_I2C_Master_Transmit/Receive 内部即轮询);
  • 收发一致则点亮绿灯,不一致则闪烁报警。

虽然工程里函数名带 Master,实际传输由 HAL_I2C_Master_Transmit/Receive 完成,内部是轮询等待,符合“主机轮询通信”的教学目标。

硬件准备

  • 两块 PY32F030 开发板(一主一从),或用一块板把 SDA/SCL 自连做回环
  • SCL、SDA 各接 4.7kΩ 上拉电阻到 VDD(I2C 开漏总线必须上拉)
  • 交叉连接:主 SCL↔从 SCL,主 SDA↔从 SDA,共地
  • 串口/LED 用于观察结果

完整代码(主机部分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "main.h"

#define DATA_LENGTH      15                /* 数据长度 */
#define I2C_ADDRESS      0xA0              /* 从机地址(7 位地址左移后的写地址) */
#define I2C_SPEEDCLOCK   100000           /* 通信速率 100K */
#define I2C_DUTYCYCLE    I2C_DUTYCYCLE_16_9 /* 快速模式占空比 */

I2C_HandleTypeDef I2cHandle;
uint8_t aTxBuffer[15] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
uint8_t aRxBuffer[15] = {0};

int main(void)
{
  HAL_Init();

  /* 初始化 LED 与按键 */
  BSP_LED_Init(LED_GREEN);
  BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_GPIO);

  /* I2C 参数配置 */
  I2cHandle.Instance           = I2C;             /* PY32F0xx 的 I2C 外设名 */
  I2cHandle.Init.ClockSpeed    = I2C_SPEEDCLOCK;   /* 100K */
  I2cHandle.Init.DutyCycle     = I2C_DUTYCYCLE;    /* 快速模式占空比 */
  I2cHandle.Init.OwnAddress1   = I2C_ADDRESS;      /* 自身地址(主模式可不严格) */
  I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  I2cHandle.Init.NoStretchMode   = I2C_NOSTRETCH_DISABLE; /* 允许时钟拉伸 */
  if (HAL_I2C_Init(&I2cHandle) != HAL_OK)
  {
    APP_ErrorHandler();
  }

  /* 等待用户按键按下,再开始通信(避免一上电就发) */
  while (BSP_PB_GetState(BUTTON_KEY) == 1) {}

  /* 主机发送 15 字节,超时 5000ms */
  while (HAL_I2C_Master_Transmit(&I2cHandle, I2C_ADDRESS,
                                 (uint8_t *)aTxBuffer, DATA_LENGTH, 5000) != HAL_OK)
  {
    APP_ErrorHandler();
  }
  while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY); /* 等发送完成 */

  /* 主机接收 15 字节 */
  while (HAL_I2C_Master_Receive(&I2cHandle, I2C_ADDRESS,
                                (uint8_t *)aRxBuffer, DATA_LENGTH, 5000) != HAL_OK)
  {
    APP_ErrorHandler();
  }
  while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);

  /* 校验收发数据 */
  APP_CheckEndOfTransfer();

  while (1) {}
}

/* 比较发送与接收缓冲;一致亮灯,否则闪烁 */
static void APP_CheckEndOfTransfer(void)
{
  if (APP_Buffercmp8((uint8_t*)aTxBuffer, (uint8_t*)aRxBuffer, DATA_LENGTH))
    APP_LedBlinking();
  else
    BSP_LED_On(LED_GREEN);
}

代码分段讲解

1. 地址说明

I2C_ADDRESS = 0xA0含读写位的 8 位地址(即 7 位地址 0x50 左移 1 位)。HAL 的 HAL_I2C_Master_Transmit/Receive 内部会自动根据读/写把最低位置 0/1,调用时直接传 0xA0 即可(底层会有兼容处理)。实际对接外部芯片时,请以其数据手册给出的 7 位地址为准。

2. 速率与占空比

  • ClockSpeed = 100000:标准模式 100kbps;可改 400000 进入快速模式。
  • 快速模式下 DutyCycle 控制 SCL 高低电平比(16_92)。

3. 轮询方式

HAL_I2C_Master_Transmit(..., Timeout) 内部不断查询状态寄存器直到完成或超时,期间 CPU 被占用,简单但效率低。对性能敏感的场景可改用 IT(中断)或 DMA 版本(同目录有 I2C_TwoBoard_CommunicationMaster_IT / _DMA)。

4. 时钟拉伸

NoStretchMode = DISABLE 表示允许从机在忙时拉低 SCL(时钟拉伸),这是 I2C 规范允许的流控机制,绝大多数从机需要。

实验现象

  • 按下用户键后,主机发送 1~15
  • 若从机把数据原样返回,主机收到一致数据,绿灯常亮
  • 若数据不一致或总线异常,绿灯以 500ms 周期闪烁报警。

常见问题

  1. 总线卡死 / 收不到数据?

    • SCL、SDA 必须有上拉电阻(4.7kΩ 到 VDD),开漏总线无上拉无法拉高。
    • 检查接线:SCL↔SCL、SDA↔SDA、主从共地
    • 确认从机地址与数据手册一致(区分 7 位 / 8 位地址)。
  2. HAL_I2C_Master_Transmit 返回 HAL_BUSY/HAL_TIMEOUT

    • 总线可能被某次异常拉低(SDA 卡在低)。可尝试复位或从机重新上电。
    • 适当增大 Timeout(如 5000ms)。
  3. 写 EEPROM 后马上读不到?

    • EEPROM 写周期约 5ms,需在写后延时再读。
  4. 如何只读单字节传感器(如温湿度)?

    • HAL_I2C_Master_Transmit 发寄存器地址,再 HAL_I2C_Master_Receive 读数据(即“写寄存器指针 + 读数据”组合)。

小结

  • I2C 主机轮询三步:初始化 HAL_I2C_InitMaster_Transmit 发 → Master_Receive
  • 总线必须上拉,地址要与从机匹配,主从共地。
  • 轮询版代码最简单,适合初学;追求效率改用 IT/DMA 版。

拓展应用

  • 驱动 OLED(SSD1306)、AT24Cxx EEPROM、各类 I2C 传感器(BMP280、SHT30)。
  • 一主多从:用不同从机地址挂多个设备,轮流访问。
  • HAL_I2C_Mem_Write/Read 直接读写带寄存器的器件(如 I2C_MEMADD_SIZE_8BIT)。
  • 把轮询升级为中断/DMA,释放 CPU。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计