跳转到内容

[STM32]-ADC读取滑动电阻器代码实现_哔哩哔哩_bilibili

  • 图文件中引入ADC图文件
  • 初始化部分新增ADC初始化
  • 参数模式设置来源于TuberS配置
  • 与前面配置一致
  • ADC图文件包含初始化相关和触发相关函数
  • 可查看相关AP函数
  • 第一个图文件为触发相关
  • 校准原因:ADC制造过程存在误差,建议采样前执行校准,提高数据精度
  • 函数调用HAL_ADC_Calibration_Start(&hadc1)
  • 放置位置:从ADC扩展函数库copy该函数,放到MX_ADC1_Init()等初始化代码前面
  • 忽略回调部分,直接放置
  • 新增函数MX_ADC1_Calibration(从ADC扩展库复制)
  • 调用HAL_ADC_Calibration_Start(hadc1)
  • 参数:hadc1(ADC实例句柄)
  • 启动转换MX_ADC1_Start函数
  • 调用HAL_ADC_Start(hadc1)
  • 参数:hadc1
  • 等待转换完成(主循环中):HAL_ADC_PollForConversion(hadc1, HAL_MAX_DELAY)
  • 第一个参数:hadc1(句柄)
  • 第二个参数:超时时间(如HAL_MAX_DELAY
  • 用于判断转换是否完成
void MX_ADC1_Calibration(void)
{
if (HAL_ADC_Calibration_Start(hadc1) != HAL_OK)
{
Error_Handler();
}
}
void MX_ADC1_Start(void)
{
if (HAL_ADC_Start(hadc1) != HAL_OK)
{
Error_Handler();
}
}
  • 采样时间:1.5周期(之前设置)
  • 总转换时间:采样时间1.5周期 + 12.5周期 = 14周期
  • 参考芯片手册11.6节
  • 实际时间估算:14周期 ≈ 1微秒(时钟12MHz)
  • 实际稍大,但不会超太多
  • 超时时间建议:10-50ms
  • 只要大于转换时间即可(如HAL_MAX_DELAY或50ms)
// 示例:在主循环中使用
HAL_ADC_PollForConversion(hadc1, 50); // 50ms超时
  • 主循环中读取流程:先轮询等待转换完成,再获取ADC值
  • 轮询函数:HAL_ADC_PollForConversion(&hadc1, 50)(50ms超时,足够覆盖转换时间)
  • 超时设置:只需大于转换时间(如10ms),单位ms
  • 获取值:uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
  • ADC为12位,用uint32_tuint16_t存储
while (1)
{
if (HAL_ADC_PollForConversion(&hadc1, 50) == HAL_OK)
{
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
// 通过串口打印观察ADC值变化
}
}
  • 打印ADC值:重写printf函数,通过串口窗口输出,便于观察变化
  • 打印ADC值:在主循环中使用printf("ADC value = %lu\r\n", adc_value)观察变化
  • 通过重写printf函数,经串口输出
while (1)
{
HAL_ADC_PollForConversion(&hadc1, 100);
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
printf("ADC value = %lu\r\n", adc_value);
}
  • 电压转换原理:12位ADC(0~4095),参考电压3.3V
  • 分辨率:$$3.3 / 4096 \approx 0.00080566V$$
  • 公式:电压 = adc_value × (3.3 / 4096)
模拟信号Vin ──► 采样保持 ──► 比较器 ──► SAR控制器 ──► 输出
└─► DAC
参考电压 Vref = 3.3V
0x0000 = 0V
0x0001 ≈ 0.0008V
...
0xFFF = 3.3V
  • 示例:
  • 0x0100 ≈ 0.20252V
  • 0x0101 ≈ 0.20505V
  • 实际电压计算:12位ADC,分辨率 3.3V / 4096
  • 公式:(float)adc_value * 3.3f / 4096.0f
  • 3.3f:浮点数后缀,确保浮点运算
while (1)
{
HAL_ADC_PollForConversion(&hadc1, 100);
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
printf("adc_value = %d\r\n", adc_value);
printf("voltage = %f\r\n", (float)adc_value * 3.3f / 4096.0f);
HAL_Delay(1000);
}
  • 打印设置:小数点后4位,HAL_Delay(1000) 观察变化
  • 超时调整为100ms(轮询)
  • 编译烧录与串口观察:配置debug为SWD模式,编译烧录后连接USB转串口线到电脑
  • 打开串口助手,选择对应COM口
  • 观察打印的ADC value和转换电压,每秒打印一次(延时控制)
  • 信号变化测试:用十字螺丝刀调节电位器,观察串口窗口中ADC值和电压实时变化
  • 验证ADC采样正常工作
// 主循环中添加延时,每秒打印一次
HAL_Delay(1000);
  • 电位器演示:旋转电位器改变PA0 (PA03) 输入电压
  • 中间位置:接近3.3V,ADC值最大
  • 往一侧旋转:电压接近3.3V,ADC值增大
  • 往另一侧旋转:电压接近0V,ADC值减小
  • 验证效果:通过串口观察ADC值和电压变化,确认0~3.3V范围采样正确
  • 对应前面代码打印的adc_value和voltage
  • 电路工作:电位器模拟信号 → PA0 → ADC采样 → 串口输出观察
  • UART:支持半双工或全双工3根线(DS线、S线、D线),用于两个设备间通信
  • I2C半双工2根线(SCL时钟线、SDA数据线)
UART: 设备A ── DS ── S ── D ── 设备B
I2C: 主控 ── SCL ── SDA ── 从设备
  • UART:处理器与处理器通信时常用
  • I2C:处理器与外设通信时常用
  • 闪存芯片
  • 屏幕
  • 传感器
  • EEPROM(如24C02
主控
SCL (时钟线)
SDA (数据线)
├─ 用户手册
├─ 闪存
├─ 24C02
└─ 传感器
  • 51单片机学习中用IO模拟I2C
  • STC51特点:无硬件I2C模块,必须用IO口模拟时序
  • 逐个实现基础信号:起始信号结束信号、发送数据、读取数据、应答信号
  • 实现流程:编写各基础信号 → 组合成完整I2C通信
  • 51单片机I2C实现:用IO模拟,手动编写
  • 起始信号、结束信号
  • 数据通讯信号
  • 应答信号(硬答/软答)
  • 封装读写函数驱动外设(如温湿度传感器)
  • 实现繁琐:效率低,所有信号手动控制
  • STM32优势:提供硬件I2C功能
  • 自动处理信号,封装更高效
  • 便于驱动外设
  • STM32硬件I2C:底层芯片已封装起始信号、结束信号、数据发送/读取、应答信号
  • 使用时只需调用读写函数,轻松操作外设实现驱动
  • 51单片机:无硬件I2C,需IO模拟各时序信号后组合
  • STM32也可模拟:用两个IO模拟信号并封装,但今天重点学硬件I2C
STM32硬件I2C: 封装信号 → 直接调用读写函数 → 驱动外设
51模拟I2C: IO时序 → 组合信号 → 驱动外设
  • STM32 I2C驱动:使用芯片提供的硬件I2C外设直接驱动外设
  • 比51模拟更高效,无需手动编写时序
  • 完整I2C通信过程(51中复习):
  • 起始信号SCL为高时,SDA从高到低跳变
  • 通知从设备开始通信
SCL ───────┐──────────────
│ │
SDA ──────┘ └────── (起始后)
  • 后续步骤:数据传输、应答、结束信号(之前已详述)
  • 起始信号SCL高电平期间,SDA由高变低
  • 结束信号SCL高电平期间,SDA由低变高
  • 通信流程:起始信号后发送从设备地址
  • 示例:操作温湿度传感器,发送其7位地址
  • byte有8位,地址占7位(注意位宽)
  • 设备地址字节组成:操作设备地址 + 读写标志位 = 1 byte(8位)
  • 写操作:读写位 = 0
  • 读操作:读写位 = 1
  • 通信流程:发送设备地址后,等待从设备应答
  • 应答后:
  • :发送数据 → 等待应答
  • :接收数据
设备地址 ──(读写位 0/1)──► 等待应答
├─ 写:发送数据
└─ 读:接收数据

I2C完整通信过程 - 写操作与读操作细节

Section titled “I2C完整通信过程 - 写操作与读操作细节”
  • 写操作:发送完所有数据后,等待从设备应答,然后发送结束信号完成通信
  • 结束信号:SCL高SDA由低变高
  • 读操作:等待从设备发送数据
  • 数据传输过来后,主机发送应答
  • 继续接收下一个数据,直至所有数据传输完成
  • 最后:主机返回应答 + 发送停止信号
  • 从设备停止发送数据,总线释放
写: 地址(R/W=0) ── ACK ── DATA ── ACK ── ... ── STOP
读: 地址(R/W=1) ── ACK ── DATA ── ACK ── DATA ── ACK ── STOP
  • 时序要点:每个数据后都有ACK(应答),传输完发STOP释放总线
  • 工程创建:复制demo001工程作为模板,重命名为demo013
  • 删除MDK配置文件夹
  • 打开CubeMX(cubemx)工具
  • CubeMX初始界面
  • 顶部菜单:Pinout & ConfigurationClock ConfigurationProject ManagerTools
  • 中心显示STM32F103RCTx LQFP64芯片图与引脚标签
  • 左侧配置面板:System CoreConnectivityTimersADC等类别
  • 点击System Core开始配置
  • 芯片支持:两个硬件iPhone C接口(iPhone C1iPhone C2
  • 先使能其中一个进行配置
  • 协议选项:三种模式
  • iPhone C:本节课主要学习
  • SMBus总线:与iPhone C硬件接口类似
  • 官方封装在一起
协议: iPhone C / SMBus
  • 配置步骤:设置成iPhone C模式
  • 配置位置:选择I2C后,下方显示参数配置
  • Master特性:单片机作为I2C主机时配置
  • 用于驱动外设(如温湿度传感器、屏幕)
  • 单片机一般作为主机
  • Lens特性:与master一起配置(具体含义待后续)
  • 主机模式下只需配置这两个参数
  • 作为主机驱动程序:配置I2C1参数,主要驱动温湿度传感器EEPROM
  • 只需配置关键参数
  • 第一个参数:模式
  • 标准模式 (Standard Mode)
  • 快速模式 (Fast Mode)
  • 影响I2C Clock Speed (Hz)(时钟速率)
  • 当前设置:100000 Hz
I2C1 配置:
模式 ──► 标准模式 / 快速模式
└─► I2C Clock Speed (Hz): 100000
  • 其他参数(屏幕显示,待讨论)
  • Clock Stretch Mode: Disabled
  • Primary Address Length: 7-bit
  • Primary Address: 0x0000
  • General Call: Disabled

STM32硬件I2C CubeMX配置参数 - 速率模式选择

Section titled “STM32硬件I2C CubeMX配置参数 - 速率模式选择”
  • I2C速率决定依据:根据外设传感器支持的最高速率
  • 示例:传感器最高100k → 选择标准速率
  • 支持快速速率 → 设置快速速率
  • 模式选择后:下方参数一般默认(按默认即可)
  • 驱动外设时,若外设不支持高速率 → 需调整速率
外设最高速率
标准(100k) / 快速
CubeMX配置 → 默认参数
  • 下方参数:时钟拉伸模式(Clock Stretch Mode)、地址长度(Primary Address Length)、主地址(Primary Address)等
  • 这些是从机(slave)的参数
  • 本节不关注
  • 主机驱动重点:了解主机如何驱动外设(如温湿度传感器)
  • 先掌握主机参数配置
  • 当前设置:保持默认值,先不修改速率(100000 Hz)
  • 实验内容:驱动EEPROM(本节重点)和温湿度传感器(51课程已讲)
  • 先查看原理图第9页的EEPROM电路
EEPROM电路:
[EEPROM芯片 + 电容 + 电阻]
SCL ──┐
SDA ──┤── VCC, GND
└─
  • 电路连接:包含EEPROM芯片电容电阻,关键引脚SCLSDAVCCGND
  • SIZE | PAGE 9 of 17
  • 生成代码:配置I2C1标准模式、速率100k后,直接生成代码
  • 查看API:检查生成的I2C相关函数

STM32硬件I2C工程编译与初始化函数查看

Section titled “STM32硬件I2C工程编译与初始化函数查看”
  • 编译工程:配置完成后直接编译
  • main函数变化:新增I2C1初始化调用
  • 包含CubeMX中配置的参数
  • 查看流程
  1. 进入MX_I2C1_Init()
  2. 跳转**HAL_I2C_Init()**硬件初始化函数
  3. 查看其头文件
  • 主机传输函数HAL_StatusTypeDef HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)0x78, pData, Size, Timeout);
  • 用于主机发送数据
  • 主机接收函数HAL_StatusTypeDef HAL_I2C_Master_Receive(&hi2c1, (uint16_t)0x78, pData, Size, Timeout);
  • 用于主机接收数据
  • 内存写函数HAL_StatusTypeDef HAL_I2C_Mem_Write(&hi2c1, (uint16_t)0x78, MemAddress, I2C_MEMADD_SIZE_8BIT, pData, Size, Timeout);
  • 写EEPROM等内存设备
  • 内存读函数HAL_StatusTypeDef HAL_I2C_Mem_Read(&hi2c1, (uint16_t)0x78, MemAddress, I2C_MEMADD_SIZE_8BIT, pData, Size, Timeout);
  • 读EEPROM等内存设备
  • 其他函数:带IT后缀的用于中断模式
  • 这些是I2C功能的核心接口

STM32硬件I2C主机通信API函数 - 参数详解

Section titled “STM32硬件I2C主机通信API函数 - 参数详解”
  • 四个核心函数:与中断/DMA函数类似,使用方式相似
  • 基础轮询函数为主,其他类似
  • HAL_I2C_Master_Transmit:主机发送函数
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • 第1参数I2C句柄I2C_HandleTypeDef *hi2c
  • 第2参数设备地址uint16_t DevAddress
  • 其他函数参数类似:
  • HAL_I2C_Master_Receive:主机接收
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • HAL_I2C_Mem_Write:内存写(EEPROM)
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint8_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • HAL_I2C_Mem_Read:内存读(EEPROM)
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint8_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

I2C主机API参数 - 设备地址与数据详解

Section titled “I2C主机API参数 - 设备地址与数据详解”
  • HAL_I2C_Master_Transmit 参数继续:
  • 第2参数设备地址 (uint16_t DevAddress)
  • 总线上多个从机时,每个从机地址唯一
  • 发送设备地址选择要操作的从机(如触摸屏)
  • 第3参数发送数据 (uint8_t *pData)
  • 第4参数发送数据长度 (uint16_t Size)
  • 其他函数参数类似:每个设备在总线上有唯一地址

STM32硬件I2C主机通信API函数 - HAL_I2C_Master_Receive参数详解

Section titled “STM32硬件I2C主机通信API函数 - HAL_I2C_Master_Receive参数详解”
  • HAL_I2C_Master_Receive:主机接收函数,参数与发送类似
  • 第1参数I2C句柄
  • 第2参数设备地址(从哪个设备接收数据)
  • 第3参数数据存储指针(接收数据存储位置)
  • 第4参数数据长度uint16_t Size
  • 第5参数超时时间uint32_t Timeout

STM32硬件I2C主机通信API函数 - 内存读写作用

Section titled “STM32硬件I2C主机通信API函数 - 内存读写作用”
  • 内存读写函数作用:用于操作EEPROM等存储芯片数据
  • 理解方式:主控作为主机,通过I2C与EEPROM通信
+----------------+
| 主控 |
+----------------+
+----------------+
| EEPROM |
+----------------+
  • EEPROM特点:存储芯片,可存储数据(后续详解具体作用)
  • EEPROM内部结构:有很多存储区域,像一间间房子
  • 可画表格表示
+--------+--------+--------+
| 区域1 | 区域2 | 区域3 |
+--------+--------+--------+
  • 每个区域大小根据芯片型号决定
  • I2C通信方式:两根线
  • 时钟线 (SCL)
  • 数据线 (SDA)
  • 通信过程:首先指定要操作的存储区域
  • 设备地址示例:如0x51,总线上可挂多个I2C设备
  • 只要地址不同即可(如第二个EEPROM地址0x52
  • 一个总线支持多个设备
  • 每个I2C设备特点:都有内部存储地址
  • 用于后续内存读写操作
+------------------+
| 主控 |
+------------------+
|
v
+------------------+ +------------------+
| EEPROM | | EEPROM |
| 0x51 | | 0x52 |
+------------------+ +------------------+
  • 内存读写函数应用:直接用于操作存储设备EEPROM
  • EEPROM示例:2kbit容量 = 256 byte(可存储256字节数据)
  • 内部有存储地址,用于指定操作位置
+-----------------+ +------------+
| System |----->| EEPROM OUT1|
+-----------------+ +------------+
|
v
+-----------------+ +------------+
| |----->| EEPROM OUT2|
+-----------------+ +------------+
  • 存储区域示意
+----+----+ +----+----+----+
| FF | 11 |
+----+----+ +----+----+----+
+----+----+----+
| | | |
+----+----+----+
| | | |
+----+----+----+
  • 实际写操作示例:向EEPROM指定地址写入数据
  • 例子0写入 0s ff
  • 例子10写入数据
  • 例子111写入数据
  • 例子2写入数据
  • 参数应用
  • 第1参数:I2C句柄
  • 第2参数设备地址(选择具体EEPROM)
  • EEPROM1:0s01
  • EEPROM2:0s02
  • 通过设备地址区分总线上的不同EEPROM

I2C内存读写函数 - MemAddress参数详解

Section titled “I2C内存读写函数 - MemAddress参数详解”
  • 第3参数内存地址 (uint16_t MemAddress)
  • 对应EEPROM内部存储位置
  • 示例:填0 表示第一个位置,写入FF
+------------------+
| EEPROM |
| 内存地址 0 |
+------------------+
| FF | | | |
+----+----+----+--+
| | | | |
+----+----+----+--+
  • EEPROM容量示例2kbit = 256 byte
  • 256个位置可存储数据
  • 通过地址指定操作位置(如地址1写入其他数据)

I2C内存读写函数 - MemAddSize参数详解

Section titled “I2C内存读写函数 - MemAddSize参数详解”
  • 第4参数MemAddSize (uint8_t MemAddSize)
  • 指定内存地址位宽(地址大小)
  • 8bit = $$2^8 = 256$$,刚好覆盖256 byte位置
  • 适用于2Kbit = 256 byte的EEPROM
  • 16bit = $$2^{16}$$,用于更大容量
  • 1024 byte,8bit不够用需16bit覆盖所有地址
  • 选择依据:根据EEPROM容量决定地址位宽
  • 确保能遍历所有存储位置
8 bit ──► 2^8 = 256 (2Kbit EEPROM)
16 bit ─► 2^16 (1024 byte EEPROM)
  • 示例
  • 16 byte容量:8bit足够(2^8=256 > 16)
  • 1024 byte:需16bit
  • 内存读写函数后序参数(第5~7参数,与Transmit/Receive类似)
  • 第5参数pDatauint8_t*),要写入/读出数据指针
  • 示例:存储数据10,直接写10(或放入数组)
  • 第6参数Sizeuint16_t),数据长度
  • 示例:单个数据10,长度为1
  • 第7参数Timeoutuint32_t),超时时间
  • 读操作参数相同:先选设备地址 + 内存地址,然后指定读出数据位置长度超时
  • 用于带内存地址I2C存储设备(如EEPROM)的读写操作
  • 内存读写函数剩余参数:用于从EEPROM指定内存地址读取数据
  • 读操作示例:从内存地址012读取数据
  • 参数顺序:内存地址(指定位置)、数据长度、读入数据存储位置读数据大小
  • 函数作用总结:完成I2C内存读写全过程
  • EEPROM电可擦可编程只读存储器(Electrically Erasable Programmable Read-Only Memory)
  • 特点:带电可擦写,断电后数据不丢失(非易失性)
  • 与 Flash 比较
  • 共同点:均存储数据,断电不丢失
  • 区别
  • EEPROM字节单位擦写
  • Flash扇区单位擦写

EEPROM 与 Flash 比较 - 擦写次数与单位

Section titled “EEPROM 与 Flash 比较 - 擦写次数与单位”
  • 擦写次数
  • EEPROM:可达百万次
  • Flash十万次
  • EEPROM耐久性更高
  • 擦写单位
  • EEPROM字节级擦除
  • Flash扇区级整块擦除
  • 应用场景:后续了解EEPROM与Flash的典型应用场合
  • EEPROM 应用
  • 写操作要求高(频繁擦写)
  • 少量数据
  • 示例:配置信息日志信息
  • Flash 应用
  • 写操作要求不高
  • 数据量大
  • 示例:大块数据存储
  • 核心区别
  • EEPROM:频繁擦写 + 小数据量
  • Flash:不频繁擦写 + 大数据量
  • 开发板EEPROM型号BL24C02
  • 容量2Kbit = 2048 bit
  • 换算2048 ÷ 8 = 256 byte
  • 最大存储256 个字节
BL24C02
2Kbit = 2048 bit
256 byte
  • BL24C32:EEPROM,32页,每页8 byte,数据在芯片手册中。
  • A0, A1, A2:配置器件地址,用于I2C总线上区分设备。
  • A0, A1, A2:三个IO引脚,用于配置器件地址
  • 通过不同高低电平组合,设置不同地址
  • 允许多个EEPROM挂载在同一I2C总线
  • 只要器件地址不同,即可区分
  • 原理图连接
  • SDASCL:I2C总线
  • 写保护(WP):保护功能
  • 电源:供电部分
I2C总线 (SDA, SCL)
+───+───+
| A0,A1,A2 | ← 高低电平配置地址
| EEPROM1 |
+───+───+
+───+───+
| A0,A1,A2 | ← 不同组合
| EEPROM2 |
+───+───+

EEPROM BL24C02 硬件连接与地址配置

Section titled “EEPROM BL24C02 硬件连接与地址配置”
  • 引脚连接
  • SCLPB6
  • SDAPB7
  • A0, A1, A2GND(标为D)
  • 作用:A0/A1/A2配置器件地址,用于I2C总线区分设备
  • 具体设备ID芯片手册中查找
BL24C02 EEPROM
SCL ── PB6
SDA ── PB7
A0 ── GND
A1 ── GND
A2 ── GND
  • 电源与滤波
  • 上拉电阻(两个)
  • 电源接电容(使电源更干净)
  • A0、A1、A2接地(均为0):设备地址为1010 0000x50
  • 前缀1010固定
  • 后三位A2 A1 A0 = 000
  • 改变地址示例:将A0接1(高电平),地址变为1010 001
  • 通过不同高低电平组合,设置不同地址
  • 实现同一I2C总线多个EEPROM区分
地址格式:1010 A2 A1 A0
A0=A1=A2=0 (接地) → 1010 000 = 0x50
A0=1, A1=A2=0 → 1010 001
  • A0/A1/A2 均接低电平(D/GND):对应001(三个0)
  • 地址格式10101010 + 三个0 + 读写标志位
  • 前7位1010100(器件地址)
  • 第8位:读写标志(0写、1读)
  • 作用:主机通过此地址识别设备,进行读写数据操作
A0=A1=A2=0 (低电平)
1010 1010 (完整字节)
└── 前7位: 1010100 (设备地址)
└── 第8位: R/W标志
  • 写操作:设备地址 + 读写位(0),如0xA0
  • 读操作:设备地址 + 读写位(1),如0xA1
  • 读写位的不同导致最终发送的byte不同。
  • 查看芯片手册获取读写时序图。
  • 写1字节步骤
  • 发送起始信号
  • 发送设备地址(前7位 1010100)+ 写标志位0
  • 完整字节:0x50(SA0,设备地址+写位)
  • 等待设备应答
起始信号
设备地址 (1010100) + 写位 (0) = 0x50
设备应答
  • 地址格式回顾:I2C从机地址前7位 + 第8位读写标志(写为0)
  • 内存地址8位uint8_t 类型)
  • 范围:00 到 FF
  • 原因:芯片最大存储 256 byte 数据
  • 256 = 2^8,故用8位地址表示
  • 作用:指定芯片内部具体字节位置进行读写操作
内存地址:8 bit
00 ──────┬─── FF
│ 256 bytes
└─ BL24C02 容量
  • 读1字节步骤:分为两次I2C通信
  • 第一次:发送设备地址 + 写标志位(0)
  • 先将要读取的内存地址写进设备
  • 设备地址后接内存地址
  • 作用:告知设备具体内存位置,准备后续读取
读操作起始:
起始信号
设备地址 (1010100) + 写位 (0)
内存地址 (要读取的位置)
设备应答
第二次I2C (读数据)
  • 读操作步骤
  • 先发送设备地址(前7位 1010100)+ 写标志位00xA0),写入内存地址
  • 设备知道要读哪个内存空间的数据
  • 发送完内存地址后,开始真正读操作
  • 发送设备地址 + 读标志位10xA1
写阶段:
起始 → 0xA0 (设备地址+写0) → 内存地址 → 设备应答
读阶段:
0xA1 (设备地址+读1) → 读取数据
  • 关键:先用写操作指定内存地址,再用读操作获取数据

EEPROM读操作I2C通信过程 - 读1字节与连续读

Section titled “EEPROM读操作I2C通信过程 - 读1字节与连续读”
  • 读1字节完成
  • 设备发送内存地址后,等待设备应答
  • 芯片将该内存位置数据发送给主机
  • 完成1字节读操作
  • 连续读操作
  • 前半部分与单字节读相同(发送地址、写内存地址)
  • 区别:读取多个数据时
  • 不发送停止信号
  • 主机持续发送应答(ACK)
  • 读完所有数据后:发送NACK + 停止信号
单字节读:
地址写完 → 设备应答 → 数据上传 → 停止
连续读:
地址写完 → 设备应答 → 数据1 → ACK
→ 数据2 → ACK
→ ... → NACK + 停止
  • 关键:连续读通过连续ACK维持通信,最后NACK结束读取

EEPROM读写函数准备与硬件连接确认

Section titled “EEPROM读写函数准备与硬件连接确认”
  • 读写函数:了解时序后,使用HAL_I2C_Master_TransmitHAL_I2C_Master_Receive操作EEPROM
  • 这两个函数带内存地址,用于数据读写实现
  • 硬件连接确认:芯片端连接PB6PB7
  • 手册对应:PB6(SCL)、PB7(SDA)
  • PB6/PB7对应I2C接口:PB6是SCL,PB7是SDA。
  • 硬件工程师设计:硬件工程师在设计时已确定芯片与PB6/PB7的连接。
  • I2C1使能后,PB6PB7引脚默认被选择。
  • I2C1支持端口重映射。
  • 可查阅芯片手册了解端口重映射信息。
  • P9点留意支持引脚重映射
  • 硬件设计时,若PB6/PB7已占用(备用于其他功能)
  • 可将PB8PB9重新设置为I2C1的SDA和SCL
  • 当前配置:默认PB6(SCL)和PB7(SDA)
  • 配置完成
  • 模式设为标准模式
  • 从机参数不用管
  • 点击生成代码

EEPROM读写函数代码实现 - main函数规划

Section titled “EEPROM读写函数代码实现 - main函数规划”
  • 实现步骤
  • 写入8个byte数据到芯片
  • 读取8个byte数据从芯片
  • 位置:在main函数中编写代码
  • 打开工程,返回main函数查看如何编写
// 规划:先写8字节,再读8字节
写入 → 芯片
读取 ← 芯片
  • 目的:通过实际代码验证EEPROM读写功能
  • 写1字节代码:使用HAL_I2C_Mem_Write
HAL_I2C_Mem_Write(hi2c1, 0x50, 0x00, 1, &data, 1, 0xFFFF);
  • 参数详解
  • hi2c1I2C句柄
  • 0x50设备地址(前7位 + 写标志位0)
  • 0x00内存地址
  • 1MemAddSize(8位地址)
  • &data数据指针
  • 1数据长度(1字节)
  • 0xFFFF超时时间
  • 验证写成功:写后读取数据,比较是否一致
  • 目的:确认数据正确写入EEPROM内存
  • 设备地址0x500xA0 >> 1(HAL库约定,地址左移1位,低位补写标志)
  • 数据准备:定义8字节数组用于ADC处理
int buffer_size = 8;
// 初始化数组为 0,1,2,3,4,5,6,7
  • 函数调用:传入数组 + buffer_size作为长度
HAL_ADC_NewWriteTable(buffer, buffer_size);

EEPROM写1字节函数实现 - 参数补充与调试

Section titled “EEPROM写1字节函数实现 - 参数补充与调试”
  • 超时时间:设为0xFFFF(毫秒)
  • 足够让写入完成
  • 内存地址:设为0x00
  • 从芯片内存地址开始处写入
  • 代码实现后:检查报错情况
  • 确认函数调用正确

EEPROM写1字节函数 - MemAddSize参数详解

Section titled “EEPROM写1字节函数 - MemAddSize参数详解”
  • 内存地址长度(MemAddSize):设为1(8bit)
  • 原因:EEPROM最大存储256字节(地址0~0xFF
  • 地址范围足够用8位表示
HAL_I2C_Mem_Write(hi2c1, 0x50, 0x00, 1, &data, 1, 0xFFFF);
// 第4个参数:1 表示 8bit 内存地址长度
  • 关键:MemAddSize根据设备地址位宽选择(BL24C02用8bit)

EEPROM写1字节函数实现 - 成功判断与打印

Section titled “EEPROM写1字节函数实现 - 成功判断与打印”
  • 函数执行后判断:执行HAL_I2C_Mem_Write后,添加if判断
  • 检查返回值等于0:表示写入成功
  • 成功时继续打印相关信息
  • 编译确认:调整地址参数后,编译无问题
  • 数据已写入EEPROM芯片
if (HAL_I2C_Mem_Write(...) == 0) {
// 打印成功信息
}

EEPROM写1字节函数 - 串口重定向与打印实现

Section titled “EEPROM写1字节函数 - 串口重定向与打印实现”
  • 写入成功判断后打印:返回值等于0表示写入成功
  • 添加printf打印成功信息
  • 串口重定向配置
  • 打开USART1(端口1)
  • 重新生成代码
  • 重写fputc函数支持printf
  • 示例打印
fputc('A', stdout);
fputc('B', stdout);
fputc('C', stdout);
fputc('\r', stdout);
fputc('\n', stdout);
  • 使用**HAL_Delay(1000)**控制打印间隔
  • 引入头文件与fputc重写
  • 加入头文件支持printf
  • 重写fputc函数(从窗口复制,已讲过)
  • 用于打印写入成功信息
  • 读取准备
  • 初始化buff数组,长度8字节
  • 使用HAL_I2C_Mem_Read函数读取
  • 先确认写入成功,再执行读取

EEPROM读1字节函数实现 - 参数补充与打印

Section titled “EEPROM读1字节函数实现 - 参数补充与打印”
  • HAL_I2C_Mem_Read参数补充
  • 第5参数:读取长度sizeof(buffer)buffer_size
  • 第6参数:超时时间0xFFFF
  • 读取完成后数据存入buffer
  • 验证读取结果:使用for循环打印buffer内容
  • 比较是否与写入数据(0,1,2,3,4,5,6,7)一致
for (i = 0; i < 10; i++) {
}
  • 循环范围i = 0i < 10(10次迭代)
  • 打印读取结果:使用for循环遍历r_buf数组,打印每个字节
  • 循环范围:for (i = 0; i < sizeof(r_buf); i++)
  • 打印格式:printf("%d ", r_buf[i]);,每个字节后加空格
  • 循环结束后,添加printf("\r\n");进行换行
  • 验证:检查打印出的数据是否与写入的0,1,2,3,4,5,6,7一致
  • 编译前优化:在主循环写入操作后添加延时
  • 原因:无延时读写过快,不便观察串口输出
  • 效果:每秒执行一次写+读操作
HAL_Delay(1000); // 1秒延时
  • 位置
  • 主循环末尾:控制整体读写频率
  • 写入后:确保写入完成再读取
  • 写入后延时原因:EEPROM设备需要时间完成数据写入
  • 无延时虽打印“写入成功”,实际数据未写入
  • 实验验证:不加延时读取数据不正确
  • 延时位置HAL_I2C_Mem_Write后立即添加
  • 等待写入完全完成
  • 确保后续读取数据准确
HAL_I2C_Mem_Write(...);
HAL_Delay(时间); // 等待写入完成
  • 烧录步骤:选择SWD模式,Flash下载路径选Load and Run
  • 添加对应下载算法,编译烧录程序
  • 验证观察:烧录完成后打开串口调试助手
  • 找到设备串口,点击打开
  • 输出显示**“write success”**确认写入成功
  • 下方打印读取数据:0,1,2,3,4,5,6,7(与写入一致)
  • 设备连线:先查看温湿度传感器的连接方式
  • SDA和SCL引脚:SDA连接PB11,SCL连接PB10
  • 用于连接温湿度传感器
  • 引脚确认:通过芯片手册查找PB10PB11的具体位置

温湿度传感器工程准备与特征介绍

Section titled “温湿度传感器工程准备与特征介绍”
  • PB10和PB11引脚对应:刚好对应I2C2接口
  • 新建工程:借助上几课工程,改为demo14
  • 先关闭demo13工程
  • 配置前准备:打开CubeMX前,先了解温湿度传感器的一些特征
  • I2C接口速率:支持高达1MHz(快速模式)
  • 前序配置使用标准速率(100kHz)
  • 配置选择
  • 高采样率需求 → 快速模式
  • 低采样率需求 → 标准模式(不影响使用)
  • I2C2模式调整
  • 当前禁用I2C2,本节课启用
  • 可改为快速模式,标准模式也行
  • 选择I2C2自动配置:选择I2C2后,PB10PB11自动改为I2C2对应接口
  • 代码生成:点击生成代码按钮
  • 芯片地址配置:通过ADDR引脚设置
  • 低电平:设备地址 0x44
  • 高电平:设备地址 0x45
  • 全中文文档:对初学者更易上手
  • 标注位置:银角处标出配置信息
  • 设备地址基础0x44(前7位bit0100 0100
  • 实际读写地址需后面加读写位
  • 地址计算:将最高位去掉后得出完整地址
  • 示例:抠出7bit 0x44,用于I2C通信
  • 7位地址 + 读写位:设备地址7位,最后一位为读写标识
  • 写操作:7位地址后加0(写位),完整8位地址为0x88
  • 读操作:7位地址后加1(读位),完整8位地址为0x89
// 写寄存器:0x88 + 写位(0)
// 读寄存器:0x89 + 读位(1)
  • 手写笔记确认7bit + 1bit(地址位 + 读写位)
  • 两种模式
  • 当次转换模式:单次测量
  • 周期性模式(连续转换):持续测量
  • 本节选择:使用周期性命令
  • 已从文档抠出相关时序图
  • 进入方式:首先发送特定命令进入周期性模式
  • 温湿度传感器的重复率和转换周期会影响其精确度。可参考数据手册进行参数配置。可参考高重复率(每秒测量一次)。
  • 首先配置起始信号

温湿度传感器周期性模式 - 重复率与精度关系

Section titled “温湿度传感器周期性模式 - 重复率与精度关系”
  • 重复率影响:重复率越高(每秒转换次数越多)
  • 转换时间越长
  • 功耗越高
  • 精度越高
  • 参数来源:数据手册详细列出具体数值
  • 示例表格:高/中/低重复率对应不同nps、MSB/LSB值和十六进制命令
| 操作 | nps | MSB | LSB | 十六进制值 |
|------|-----|-----|-----|------------|
| | 0.5 | 0x0 | 24 | 0x24 |
| | | 1/5 | 24 | 0x21 |
| | 1 | 0x0 | 26 | 0x26 |
  • 进入步骤:设置完模式后发送开始转换指令
  • 数据读取格式(周期性模式)
  • ADC地址:0x00(Command)
  • 温度:MSB + LSB + CRC
  • 湿度:MSB + LSB + CRC
Command | Fetch Data | Hex code
---|---|---
ADC address | 0x00 | 0x00
Temperature MSB | Temperature LSB | CRC
Humidity MSB | Humidity LSB | CRC

温湿度传感器读写代码实现 - 发送周期性模式命令

Section titled “温湿度传感器读写代码实现 - 发送周期性模式命令”
  • 命令值:发送 0x2130 进入周期性模式
  • 直接操作传感器本身,无内部内存地址
  • 函数选择:使用 HAL_I2C_Master_Transmit(之前EEPROM写函数)
  • copy EEPROM写函数代码过来调整
  • 代码位置main函数中实现
  • 读数据函数:使用 HAL_I2C_Master_Receive
  • 参数顺序:设备句柄、设备地址(0x88)、buff(数据缓冲区)、数据长度、超时时间(0xFFFF
  • 与写操作对应:第三个参数为数据接收缓冲区,后续参数同写函数
  • 读数据函数HAL_I2C_Master_Receive,参数:设备句柄、设备地址(0x88)、buff(数据缓冲区)、数据长度、超时时间(0xFFFF
  • 读操作地址0x89
  • 6字节数组:用于存储读取的数据
  • HAL_I2C_Master_Receive 参数顺序:
  • 设备句柄(hi2c2)
  • 设备地址 0x88
  • 数据缓冲区 buff
  • 数据长度
  • 超时时间 0xFFFF
  • 数据转换:读取后调用转换函数
  • 初始化两个变量:分别存储温度湿度数据
uint16_t data_buf[32]; // 示例缓冲区
HAL_I2C_Master_Receive(hi2c2, 0x88, data_buf, 数据长度, 0xFFFF);
  • HAL函数通用性:官方标准方式,直接使用即可
  • 当前实现:简单死代码,直接设置
  • 支持芯片手册多种模式
  • 不同重复率、采样率
  • 优化建议:封装配置函数
  • 参考手册参数
  • 提高驱动灵活性
  • TIM4配置:可配置到6、7、8等背景IO引脚
  • 通过通道输出进行引脚复用
  • 输出PWM信号
  • 后续使用:CubeMX (Q1S) 配置对应通道
  • 复制模板工程 → DEMO15
  • 实验目标:在原理图中找到支持PWM输出的IO
  • 逻辑分析仪接到IO上验证信号
  • 检查输出信号是否符合设置
  • 原理图选IO:PA7位于特定针脚,支持PWM输出
  • 逻辑分析仪接到该针脚验证信号波形
  • 手册确认:PA7对应TIM3通道2
  • 打开CubeMX,右侧定时器选择TIM3
  • 时钟源:设为内部时钟
  • 通道选择:通道2设为PWM输出模式
  • 配置截取:将TIM3配置块截取保存
// 示例:TIM3 CH2 -> PA7 PWM输出
TIM3_Channel2_PWM_Mode

CubeMX TIM3 PWM配置 - 通道输出特点

Section titled “CubeMX TIM3 PWM配置 - 通道输出特点”
  • PWM多通道特性:四路输出信号频率相同(与配置频率一致)
  • 每路占空比可独立不同
  • 与PWM模式区别:PWM只能输出相同频率信号
  • 配置频率统一控制所有通道
  • 定时器计数过程:CNT从0开始递增至ARR
  • CNT ≤ CCR:输出低电平
  • CNT > CCR:输出高电平
  • CNT > ARR:清零重置,重新开始
  • 波形形成:高电平持续时间由CCR值决定
  • CCR值越大,占空比越高
  • ARR决定周期频率
CNT: 0 ──→ ARR (溢出清零)
├─≤ CCR: 低电平
├─> CCR: 高电平
└─> ARR: 重置
示例波形:
──┬──────┴──────┐───
高电平持续 (CCR决定)
└─────────────────→ ARR重置
  • 50%占空比设置:ARR预分频值为500时,CCR设为250
  • CCR值决定高电平持续时间
  • 值越大,占空比越高
  • CCR初始值:先填0,后续用代码动态设置
  • 功能:使能或禁用输出比较预装载
  • 使能后:重新设置预装载值时,不会立即生效
  • 生效时机:等到下一次周期(当前周期输出完)
  • 好处:避免设置过程中波形异常
  • 确保新值在周期完整时切换
// 示例配置
ARR = 500;
CCR = 250; // 50%占空比
Preload_Enable(); // 输出比较预装载使能
  • 验证步骤:双击按钮连接无问题后显示knet状态,点击start启动
  • 第二个通道显示0到1信号
  • 明显观察标电平和高低电平各一半,即当前50%占空比
  • 频率确认:位置显示两个字,即定时器频率
  • 第一个实验:通过函数不停修改CCR值控制占空比
  • 定义uint16_t变量记录当前值
uint16_t var; // 记录当前CCR值
  • while循环控制:每次延时1毫秒,让CCR递增到最高500(ARR值)
  • 达到最高时跳出循环(溢出)
  • 递减操作:外层循环时进行减1操作
  • 或直接设为0
  • 判断条件:CCR > 1 为真
  • CCR与占空比同步:减1操作同时设置CCR值
  • 两个参数相同(CCR值控制占空比)
  • 延时设置:每次设置完延时200毫秒
  • 更清楚观察变化
// 示例代码框架
while (var <= 500) { // 递增到ARR
HAL_Delay(1); // 1ms延时
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, var++);
}
while (var > 1) { // 递减
HAL_Delay(200); // 200ms观察变化
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, --var);
}
  • 编译烧录验证:编译后烧录
  • 点击调试,按住Ctrl观察现象
  • 逻辑分析仪观察:缩小视图后,黑线(高电平)变多,低电平变少
  • CCR增大 → 高电平时间更长,占空比更高
  • 接近50%时:高低电平时间均衡
  • 动态修改:通过函数调用修改CCR参数
  • 往右拉 → 接近500,高电平时间显著延长
  • 两个关键函数控制占空比
  • 后续实验:频率修改
  • 频率控制参数:定时器分频值(PSC)或预装载值(ARR)决定频率
  • 修改任一值均可改变频率
  • 示例:将PSC改为1000或ARR改为36
  • 预分频值修改函数__HAL_TIM_SET_PRESCALER
  • 参数1:定时器句柄(如&htim3
  • 参数2:要设置的预分频值
// 修改PWM频率示例
__HAL_TIM_SET_PRESCALER(&htim3, 1000); // 设置预分频为1000
  • 实验步骤:注释掉动态占空比代码
  • 先复制函数查看位置
  • 修改后观察频率变为2kHz
  • 频率验证:逻辑分析仪显示新频率值
  • ARR值控制频率:CCR固定,修改ARR改变频率
  • 原ARR = 72-1(1kHz频率)
  • 改为36-1(0~35计数,频率变为2kHz
// 示例代码
__HAL_TIM_SET_AUTORELOAD(&htim3, 72-1); // 1kHz
HAL_Delay(100); // 100ms延时观察
__HAL_TIM_SET_AUTORELOAD(&htim3, 36-1); // 2kHz
HAL_Delay(100);
  • 编译警告处理:变量未使用警告可忽略
  • 直接编译烧录验证
  • 逻辑分析仪观察:100ms内频率切换明显
  • 1kHz:波形较疏
  • 2kHz:波形更密(高频特征)
  • 4kHz:密度进一步增加
  • 现象确认:波形密度越高,频率越高
  • 缩小视图观察变化过程
  • 常用作声音提示或报警装置
  • 电子玩具、报警器、电话机、打印机、复印机
  • 医疗设备、仪器仪表、各电子设备工作提示音
  • 示例:家用烧水壶水烧完发声提醒
  • 通过压电陶瓷片或电磁线圈振动产生声音
  • 有源蜂鸣器:内部集成振荡电路
  • 驱动方式:只需接通电源(高低电平)即可发固定频率声音
  • 特点:频率固定,只能发出一种声音
  • 无源蜂鸣器:内部无振荡电路
  • 驱动方式:需外部提供特定频率脉冲信号(如2KHz PWM
  • 特点:频率可调,能发出多种声音

有源只需高低电平,无源需PWM脉冲(如前面PWM实验)

无源蜂鸣器驱动电路 (原理图第12页)

Section titled “无源蜂鸣器驱动电路 (原理图第12页)”
  • 通过改变PA3端口的频率来控制发声。
  • 本开发板用的是P管
  • 低电平时发声。
  • 高电平时截止。
  • CCR设置示例:ARR预装载值为500时,设CCR=250实现50%占空比
  • 编译无误后烧录
  • 实验验证:烧录后蜂鸣器发出声音
  • 麦克风可能屏蔽声音,实际实验可清晰听到
  • 成功标志:蜂鸣器发声
  • 频率修改效果:改变PWM频率产生不同声音
  • 文档中提供合适频率范围
// 编译前检查:缺少通道参数需补充
// 如 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 250);
  • 注意:P管驱动,低电平时发声,高电平时截止
  • 金属氧化物半导体:人体半导体结构,人呼气时半导体接触到氧化物导致电阻变化,影响电流。
  • PT2324:一种气体传感器,提供模拟信号输出;原理图参考其应用电路设计。
  • Q1:TOC为0时,CMOS关断;TOC为1时,CMOS导通;AHLB控制上下拉电阻。
  • C17, C21:电源滤波电容,降低电源噪声,稳定电源。
  • 工程复制:复制现有工程作为新工程基础
  • 引脚选择:配置PC13输入模式
  • 外部已有上拉电阻,选择Pull-up(上拉)
  • 不影响功能,也可选择无上拉/下拉(浮空)
  • User Label修改:自定义标签,便于代码中使用
// GPIO配置参数
GPIO mode: Input
Pull-up/Pull-down: Pull-up
User label: (自定义)
  • 目的:判断按键当前状态(按下与否)
  • 读取函数HAL_GPIO_ReadPin(KEY_OTD_GPIO_Port, KEY_OTD_Pin)
  • 按下:返回**GPIO_PIN_RESET**(低电平)
  • 松开:返回**GPIO_PIN_SET**(高电平,上拉电阻作用)
  • 按键检测逻辑(主循环中):
if (HAL_GPIO_ReadPin(KEY_OTD_GPIO_Port, KEY_OTD_Pin) == GPIO_PIN_RESET) {
// 按键按下,等待松开
while (HAL_GPIO_ReadPin(KEY_OTD_GPIO_Port, KEY_OTD_Pin) == GPIO_PIN_RESET) {
// 等待松开
}
// 按键松开后执行动作
printf("Button pressed and released.\n");
}
  • 外层if:检测按下
  • 内层while:等待松开完成一次完整按键周期
  • 消抖处理:按下/松开检测后添加**HAL_Delay(20)**(20ms延时)
  • 确认状态稳定,避免抖动误判
  • 打印验证:使用printf输出按键状态变化
  • 配合串口重定向观察
  • 完整示例(while循环内):
/* USER CODE BEGIN 3 */
while (1) {
if (HAL_GPIO_ReadPin(KEY_OTD_GPIO_Port, KEY_OTD_Pin) == GPIO_PIN_RESET) {
while (HAL_GPIO_ReadPin(KEY_OTD_GPIO_Port, KEY_OTD_Pin) == GPIO_PIN_RESET) {}
printf("Button pressed and released.\n");
}
HAL_Delay(20);
}
/* USER CODE END 3 */
  • 编译烧录:编译,烧录程序。
  • 接线:连接USB转串口线至电脑。
  • 串口调试助手:打开,选择对应串口。
  • 触发:点击触发按键进行测试。
  • 触摸IC测试完成:点击后,触发一次。
  • 用于电机控制类产品的位置闭环
  • 计算编码器正反转转动圈数
  • 实现运动控制算法
  • 速度闭环控制
  • 位置闭环控制
  • 开发板左侧DDR位置集成本轮选装编码器
  • 本节实验使用此编码器
  • 学习编码器驱动方法
  • 通过读取值实现转动检测与控制
  • 后续图示解释原理
  • 贴图浅解工作机制
  • 四个引脚ABCS
  • C:公共端,连接到B
  • S:按键信号
  • 三种控制
  • 顺时针旋转
  • 逆时针旋转
  • 按下(当物理按键使用)
  • 按下信号:与之前按键操作相同
  • 本节暂不演示(已掌握按键驱动)
编码器引脚:
A ──┐
B ──┤ ENCODER
C ──┤ (公共端 → B)
S ──┘ (按键信号)

编码器A、B信号时序与旋转方向判断

Section titled “编码器A、B信号时序与旋转方向判断”
  • 顺时针旋转(CW):A信号先于B变高
  • A先高 → B再高
  • A先低 → B再低
  • A领先B
  • 反时针旋转(CCW):B信号先于A变高
  • B先高 → A再高
  • B先低 → A再低
  • B领先A
CW (顺时针):
A: ____/‾‾‾\____
B: __/‾‾‾\_____
↑A先高 ↑A先低
CCW (反时针):
B: ____/‾‾‾\____
A: __/‾‾‾\_____
↑B先高 ↑B先低
  • 判断原理:通过A、B相位差确定旋转方向
  • 信号连接:编码器A信号PB0B信号PB1
  • 通过读取PB0PB1引脚电平状态判断旋转方向
  • 编码器芯片引脚A1A2输入,OUT1OUT2输出
  • 连接电阻、电容等外部元件
  • VCCGND供电
编码器芯片连接:
A1 ── PB0 (STM32)
A2 ── PB1 (STM32)
OUT1, OUT2 ── 输出波形
VCC, GND ── 电源
  • SW引脚:按键信号(忽略,本节不涉及)

STM32CubeMX新工程配置与PB0、PB1引脚设置

Section titled “STM32CubeMX新工程配置与PB0、PB1引脚设置”
  • 工程准备:复制现有工程,命名为encoder_demo(或018demo
  • PB0配置(连接A1信号):
  • 模式外部中断模式Falling edge trigger detection,下降沿触发)
  • User Label:修改为A1
  • PB1配置(连接B信号):
  • 模式普通输入模式(Input mode)
  • 目的:读取PB0、PB1电平状态判断编码器旋转方向
// GPIO配置示例
PB0: External interrupt (Falling edge) + Label: A1
PB1: Input mode
  • 后续:编译生成代码,准备读取A、B信号实现方向判断
  • 引脚调整:将PB0改为PB1,使A1B1连接更清晰
  • 配置完成后打开中断部分
  • 开启串口(调试日志)观察效果
  • 代码生成:配置完生成代码,编译无误
  • 位置:在外部中断文件中重写回调函数
  • A通道中断触发时判断B电平状态
  • 代码实现:直接复制之前外部中断代码
// 外部中断回调函数(示例)
// 在中断服务函数中判断B引脚电平
// 实现旋转方向检测逻辑
  • 目的:通过中断方式捕获编码器信号变化
  • main函数修改:加入 <stdio.h> <stdlib.h>
  • 全局变量定义is_cwis_ccw,用于保存方向状态。
bool is_cw = false;
bool is_ccw = false;
  • 中断服务程序:判断当前旋转方向,并打印信息。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == A1_Pin) {
if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_RESET) {
is_ccw = true;
is_cw = false;
} else {
is_cw = true;
is_ccw = false;
}
}
}
  • 主循环:根据is_cwis_ccw标志位打印旋转方向。
while (1)
{
if(is_ccw){
is_ccw = false;
printf("is_ccw方向\r\n");
}
if(is_cw){
is_cw = false;
printf("is_cw方向\r\n");
}
}
  • 添加头文件:添加stdio.hstdlib.h头文件。
  • 编译与调试:编译代码,连接开发板,配置调试。
  • 验证:旋转编码器,观察串口输出,确认方向判断正确。
  • 支持条件:AB两个引脚刚好是支持编码器模式的引脚
  • 配置方式:直接配置该模式,根据文档进行设置
  • 读取函数
  • 一个函数读取方向
  • 一个函数读取数据
  • 优势:无需手动编写底层代码,简化实现
  • 本节编码器驱动简解结束
  • 下一节:电机相关知识,介绍电机种类
  • 有刷直流电机:两根线输出
  • 一根接正极、一根接负极 → 正转
  • 交换正负极 → 反转
  • 转速控制:由供电电压决定
  • 电压越高,转速越快
  • 不超过额定电压
  • 常见电机类型:步进电机直流电机
  • 本课使用有刷直流电机说明
  • 区别:步进电机(无刷/有刷),直流电机(有刷为主)
  • 优点:低速时扭矩大控制精度高,可实现精确控制
  • 缺点成本较高,结构复杂,控制较复杂,转速偏低
  • 主要用于精准位置控制
  • 优点:结构简单,成本低,运行平稳,噪音小
  • 缺点:低速时扭矩较小控制精度较低
  • 位置控制困难,除非加额外编码器
  • 问题:STM32 IO口直接接电机引线是否可行?
  • 需要思考原因(电流、功率等)
  • STM32 IO驱动能力有限:无法提供电机所需大电流
  • 直接连接IO到电机引线不可行
  • 参考蜂鸣器驱动电路:使用三极管开关电路
  • 电路组成:NPN三极管 + 蜂鸣器(或电机)
  • 工作原理:高电平(1)时,VCC → 蜂鸣器 → 三极管 → GND 通路导通
  • 实现大电流设备驱动
三极管开关电路:
VCC ── 蜂鸣器 ── 三极管(C)
│ (B) ← IO
└── E ── GND
  • 电路组成:替换蜂鸣器为电机,包含保护电机(续流二极管)和潜微电机(主电机)
  • 不确定状态:网口低,MOS管不导通,电机停止
  • 控制过程
  • 单片机输出高电平 → 三极管导通
  • 电流路径:电源 → 电机 → 流出 → 接地
  • 驱动能力增强:由电源提供,非IO直接驱动
  • 开关电路作用:提高IO驱动能力
  • 单片机仅控制信号,实际功率由外部电路承担
  • 实现要点:加开关电路即可驱动电机旋转

H桥电路 (H-Bridge)用于电机正反转控制

Section titled “H桥电路 (H-Bridge)用于电机正反转控制”
  • 使用四个三极管组成,形似字母H。
  • 通过控制三极管的开关状态改变电机电流方向,从而实现正反转。
  • 正转:打开Q1和Q4,关闭Q3和Q2。
  • 反转:打开Q3和Q2,关闭Q1和Q4。
  • 正转控制:打开Q1Q4,关闭Q2Q3
  • 电流路径:VCC → Q1 → 电机 → Q4 → GND
  • 电机顺时针旋转
  • 反转控制:打开Q2Q3,关闭Q1Q4
  • 电流路径:VCC → Q2 → 电机 → Q3 → GND
  • 电机逆时针旋转
  • 控制原理:通过控制四个三极管的开关状态改变电流方向
  • 需要H桥电路实现正反转
  • 注意事项不能同时打开Q1和Q2
  • 否则两个管导通导致短路情况
  • 短路风险:上管与下管同时导通时,VCC直接经管子短路至GND
  • 后果:烧毁电路板或元器件
  • 控制逻辑:确保不能让上下管同时导通
  • 搭建困难:四路需要多个三极管及外围元器件(电阻、电容等)
  • 手工焊接相对困难
  • 控制逻辑复杂
  • 改进方案:使用集成芯片驱动直流电机
  • 开发板使用TC118S芯片
  • 专用于驱动直流电机
  • 简化电路与控制
  • MOSFET驱动能力:输出电流1.8A/2.5A
  • 宽工作电压3.3V到24V
  • 两种封装16DIPSOP-8
序号 符号 I/O 说明
1 NC NC 空脚
2 NA - 混合输入A
3 NB - 混合输入B
4 VDD P 电源
5 GND G
6 OUTA O 输出A
7 PGND P 电源地
8 CUTA O 全桥输出A
  • NA/NB:接单片机IO控制信号
NA NB OUTA OUTB 方式
L L H-Z H-Z 刹车
H L L H 正转
L H H L 反转
H H L L 停止
  • 目标:电机正转5秒停止5秒反转5秒停止5秒
  • 控制引脚PMA(对应INA)、PMB(对应INB),接STM32 IO口
  • 输出高/低电平控制电机状态
  • 打开demo19工程
  • 查看原理图:确认PMA/PMB引脚连接
  • 配置PMA/PMBGPIO输出模式
  • 通过代码设置高低电平驱动电机
// 示例控制逻辑
// 正转:PMA=H, PMB=L
// 反转:PMA=L, PMB=H
// 停止:PMA=H, PMB=H 或其他状态
HAL_Delay(5000); // 延时5秒
  • 直流电机驱动电路:芯片NA连接PB14NB连接PB15
  • 控制PB14/PB15输出对应电平 → 驱动芯片 → 控制外围电机
  • 外围元件:保护管 + 电容(滤波/稳定)
  • 配置:PB14/PB15设为GPIO输出模式(控制高低电平)
  • PB14连接NA,标签改为DCMA
  • PB15连接NB,标签改为DCMB
  • 重新生成代码并编译验证无误
  • 正转:PB14高电平 + PB15低电平
  • 使用HAL_GPIO_WritePin函数设置
HAL_GPIO_WritePin(DCMA_GPIO_Port, DCMA_Pin, GPIO_PIN_SET); // 高
HAL_GPIO_WritePin(DCMB_GPIO_Port, DCMB_Pin, GPIO_PIN_RESET); // 低
  • 烧录程序后电机开始旋转
  • 接电脑配置烧录模式 → 烧录完成 → 电机正转
  • 贴标签观察效果
  • 正转5秒 → 停止5秒 → 反转5秒
  • 清晰验证方向控制完成
  • 速度控制需求:使用PWM
  • 两个引脚支持PWM,选择PB15
  • CubeMX配置
  • PB15设为TIM4 CH3N(互补通道)
  • 开启互补输出器
  • 时钟源:内部时钟源
  • TIM4参数设置:预分频器 72-1,自动重装载 500-1
  • 定时器时钟 72MHz,生成连续输出力
  • PWM模式配置:通道按 PWM模式
  • 后续通过HAL API修改占空比
  • 生成代码后新增定时器配置
  • 使用前启动:调用 HAL_TIM_PWM_Start 函数
  • 然后使用 PWM 函数
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3); // 示例启动

PWM占空比动态控制代码实现(续)

Section titled “PWM占空比动态控制代码实现(续)”
  • 外循环条件:pwm_value < 500
  • 函数:HAL_TIM_PWM_Start
  • 使用HAL_TIM_SET_COMPARE设置占空比
while(1){
while (pwm_value < 500) {
HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, pwm_value);
pwm_value++;
HAL_Delay(10);
}
HAL_Delay(1500);
while (pwm_value > 0) {
HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, pwm_value);
pwm_value--;
HAL_Delay(10);
}
HAL_Delay(1500);
}
  • 参数:定时器句柄、通道、占空比值
  • 右手法则:线圈通电产生磁场
  • 右手握拳,四指弯曲方向为电流方向
  • 拇指方向为磁感线(磁场方向)
  • 通电线圈:画出磁感线
  • 形成磁场
  • 用于解释步进电机驱动原理

步进电机转子与定子磁场吸引原理

Section titled “步进电机转子与定子磁场吸引原理”
  • 转子:中间磁铁,具有N极S极
  • 定子线圈通电:产生磁场(如一侧S极、另一侧N极
  • 吸引转子向磁场方向旋转一步
  • 下一步:同时通电两个线圈
  • 中间产生次级磁场
  • 继续吸引转子旋转
  • 通电线圈产生磁场(N极吸引转子S极)
  • 转子旋转45度到正面位置
  • 切换下一线圈通电
  • 转子继续旋转到90度垂直位置
  • 顺序通电周围线圈
  • 转子逐步旋转,实现连续转动
初始:转子45° ← 线圈磁场
下一步:转子90° ← 切换线圈
  • 五相步进电机5根线
  • 四线双极性步进电机4根线
  • 接法:A+/A-/B+/B-

![电机模型示意]

  • 外观区别:线数不同

步进电机类型 - 内部原理与电流流向

Section titled “步进电机类型 - 内部原理与电流流向”
  • 五相步进电机(5根线):有一根公共线
  • 电流单向:从VCC流入(如线1、2、3、4),经过公共线流出
  • 示例:通电线1或线2,电流从VCC → 对应线 → 公共线 → 地
  • 四线双极性步进电机(4根线)A+/A-/B+/B-
  • 电流双向流动:无公共线,内部结构允许正反电流
  • 与单相性区别:电流方向可逆,支持双极驱动
五相(单相性):
VCC ──► 线1 ──┐
线2 ──┤ → 公共线 ──► GND
线3 ──┤
线4 ──┘
四线(双极性):
A+ ──► [内部] ──► A-
B+ ──► [内部] ──► B-
(电流双向)
  • 五相步进电机(单极性):5根线,带公共线
  • 电流方向单一:VCC从公共线流入线圈
  • 四相双极性步进电机4根线A+/A-/B+/B-)或6线(带公共线)
  • 电流方向双向:通过交换VCC/GND位置改变电流流向
  • 示例:VCC→A+→A-→GND 或反向
五相四拍步进电机:
V
|
3 ---
|
2 ---
|
1 ---
|
4 ---
|
5 ---
四相双极性:
V
|
3 ---
|
2 ---
|
1 ---
|
4 ---
  • 区别总结
  • 线数:5线 vs 4/6线
  • 电流控制:单方向(公共线) vs 双方向(极性切换)
  • 单个线圈通电:产生磁场吸引转子
  • 示例:通电A线圈 → 转子被吸至对应位置
  • 同时通电两个线圈:中间产生次级磁场
  • 吸引转子至45度中间角度
  • 示例:A+B同时通电 → 转子旋转至45°
  • 顺序切换通电:逐步控制转子旋转
  • A通电 → 转子到位
  • 断A通B → 转子移至下一位置
  • C通电 → 继续吸引转子向下
线圈控制序列:
A通电 → 转子到位
A断+B通 → 45°位置
B通+C通 → 继续旋转
  • 公共线(公共桩):5根线中1根公共输出线
  • A、B、C、D对应4个线圈输入
  • 所有线圈通过公共线连接输出
  • 电流路径单向流动
  • VCC → 输入线(A/B/C/D) → 公共线 → GND
  • 控制简单:选择通电线圈即可产生磁场吸引转子
  • 四步控制方式
  1. 第一步A相线圈通电,产生励磁,转子旋转一个角度
  2. 第二步B相线圈通电,产生励磁,转子再转一个角度
  3. 第三步A相线圈反向通电,转子再转一个角度
  4. 第四步B相线圈反向通电,转子完成整步行程
  • 通过**A、B、A-、B-**端子适当电路控制,实现不同方向转动
  • 四根线A+、A-、B+、B-
  • **A+与A-**串联成一组线圈,**B+与B-**串联成另一组
  • 两组线圈中间串联连接
  • 第一步通电示例:特定组合通电产生磁场,驱动转子旋转
  • 八步控制方式:相比四步,采用通电+两个线圈通电交替,实现更细致控制
  • 四根线A+、A-、B+、B-
  • **A+与A-**一组线圈,**B+与B-**另一组,中间串联连接
  • 第一步B相线圈供电
  • B+接VCCB-接GND
  • 产生磁场(S极在上,N极在下),吸引转子到对应位置
  • 第二步B相反向供电
  • B+接GNDB-接VCC
  • 磁场反转(N极在上,S极在下),转子旋转到相反位置
  • 第三步A相供电
  • A+接GNDA-接VCC
  • 产生相反磁场,吸引转子到新位置
  • 第四步A相反向供电
  • A+接VCCA-接GND
  • 磁场再次反转,转子完成四步循环
  • 持续驱动:重复以上四步,实现连续旋转
  • 满步驱动:基本驱动方式(四步循环)
  • 后续讲解半步驱动对比
  • 满步驱动:基本驱动方式(4步循环)
  • 优点:扭矩更大,转速更高
  • 图示(一相通电,满步):
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
  • 半步驱动:两相通电交替(8步)
  • 优点:提高分辨率,转动角度更小、更精细
  • 缺点:输出扭矩变小(约满步的70%
  • 图示(两相通电,半步):
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
.--. .--. .--. .--.
| | | | | | | |
'--' '--' '--' '--'
  • 应用选择
  • 需要高精度角度控制 → 半步驱动
  • 需要更大扭矩/更高转速 → 满步驱动
  • 步进电机控制前置知识点到此完成
  • 步进数量:由脉冲个数决定
  • 发多少脉冲,转多少步
  • 转动速度:由脉冲频率决定
  • 频率越高,转速越快
  • 限制:不能超过额定最高频率
  • 超出(如几百kHz、1MHz、2MHz)无效
  • 实际调试:找到合适频率
  • 保证运行效率最高
  • 后续查看电机参数
  • 数据手册中提取相关规格
  • 布局角定义:单个线圈通电驱动转子旋转的角度
  • 典型值:1.8度
  • 减速箱作用:内部转子每步45度,经减速后输出1.8度
  • 实现精细控制
  • 无减速箱情况:每步90度
  • 角度控制较粗糙,无法实现小角度精确旋转
  • 两线圈结构:使用前面提到的双极性驱动方式
  • 每个线圈对应一组线(A+/A- 或 B+/B-)
  • 额定电压:电路设计时考虑
  • 与驱动电路相关,后续学习
  • 实验驱动芯片DRV8826(手册显示),相当于两个H桥
  • 拖出其中一个桥展示结构
  • 引脚功能
  • 电源相关:电源和地脚(略)
  • 睡眠模式:控制芯片休眠
  • 警报输出:电机出错时输出信号
  • 四个数字口:控制电机驱动
  • 连接单片机IO控制H桥输出
  • 分辨率提升:电机带减速箱
  • 输出呼吸角更小,分辨率更高

步进电机控制逻辑 (满步驱动与半步驱动)

Section titled “步进电机控制逻辑 (满步驱动与半步驱动)”
  • 满步驱动:逻辑简单
  • 半步驱动:步骤更多(比满步多四步)
  • 逻辑相似,只是步骤更多

步进电机驱动芯片 DRV8826 引脚连接

Section titled “步进电机驱动芯片 DRV8826 引脚连接”
  • DRV8826芯片:相当于两个H桥电路
  • 四个数字控制引脚连接STM32 PC7、PC8、PC9、PA8
  • 通过这些IO输入电平时序,控制芯片输出驱动电机旋转
  • 睡眠模式设置非睡眠模式,上拉高电平
  • 警报引脚:目前悬空(未使用)
  • 保护元件:保护二极管 + 输入保护二极管
  • 滤波电容:稳定供电
  • 均为常规配置
  • 通过代码控制PC7/8/9 + PA8输出对应电平时序
  • 实现电机步进驱动
  • 工程创建:复制demo00demo20
  • CubeMX配置
  • A8、C9、C8、C7设为GPIO输出
  • 起始电平:
  • 输出类型:推挽
  • 上拉/下拉:
  • 标签修改
  • A8 → BN2A8(对应原理图)
  • 其他引脚类似修改
  • 参数确认:无需额外修改,直接生成代码
  • 引脚标签重命名
  • PC9 → bn2(对应B相N2)
  • C9 → bn1(B相N1)
  • C8 → an2(A相N2)
  • C7 → an1(A相N1)
  • 目的:与原理图定义一致,代码阅读更清晰
  • 工程操作
  • 点击生成代码
  • 打开工程,编译验证无误
  • 电机控制函数定义
  • 函数名:ymotor_run(电机运行)
  • 初始无参数
  • 后续描述电平时序逻辑控制电机步进
// 示例函数声明
void ymotor_run(void);

步进电机满步驱动第一步代码实现

Section titled “步进电机满步驱动第一步代码实现”
  • 第一步时序:A、B输出高电平,A-、B-输出低电平
  • 使用 HAL_GPIO_WritePin 函数设置
// A引脚 (GPIOC, BN2A8_Pin)
HAL_GPIO_WritePin(GPIOC, BN2A8_Pin, GPIO_PIN_SET); // 高电平
// B引脚 (对应BN2)
HAL_GPIO_WritePin(GPIOC, BN2_Pin, GPIO_PIN_SET); // 高电平
  • 端口确认:A、B引脚均在GPIOC
  • 代码调试:检查引脚标签是否正确(如BN2A8、BN2),避免标签错误

步进电机满步驱动第二步代码实现

Section titled “步进电机满步驱动第二步代码实现”
  • 第二步时序BA-输出高电平AB-输出低电平
  • 第一步与第二步之间添加**HAL_Delay(1)**延时
  • 控制频率约1kHz
// 第二步
HAL_GPIO_WritePin(GPIOC, BN2_Pin, GPIO_PIN_RESET); // A低
HAL_GPIO_WritePin(GPIOC, BN2A8_Pin, GPIO_PIN_SET); // A-高
HAL_GPIO_WritePin(GPIOC, bn1_Pin, GPIO_PIN_SET); // B高
HAL_GPIO_WritePin(GPIOC, bn2_Pin, GPIO_PIN_RESET); // B-低
HAL_Delay(1);

步进电机满步驱动第三步代码实现

Section titled “步进电机满步驱动第三步代码实现”
  • 第三步时序A-B输出高电平AB-输出低电平
// 第三步
HAL_GPIO_WritePin(GPIOC, BN2_Pin, GPIO_PIN_RESET); // A低
HAL_GPIO_WritePin(GPIOC, BN2A8_Pin, GPIO_PIN_SET); // A-高
HAL_GPIO_WritePin(GPIOC, bn1_Pin, GPIO_PIN_RESET); // B低
HAL_GPIO_WritePin(GPIOC, bn2_Pin, GPIO_PIN_SET); // B-高
HAL_Delay(1);

步进电机满步驱动第四步代码实现

Section titled “步进电机满步驱动第四步代码实现”
  • 第四步时序AB-输出高电平A-B输出低电平
  • 完成4步循环后回到第一步,重复实现连续转动
// 第四步
HAL_GPIO_WritePin(GPIOC, BN2_Pin, GPIO_PIN_SET); // A高
HAL_GPIO_WritePin(GPIOC, BN2A8_Pin, GPIO_PIN_RESET); // A-低
HAL_GPIO_WritePin(GPIOC, bn1_Pin, GPIO_PIN_RESET); // B低
HAL_GPIO_WritePin(GPIOC, bn2_Pin, GPIO_PIN_SET); // B-高
HAL_Delay(1);

步进电机满步驱动函数完整调用

Section titled “步进电机满步驱动函数完整调用”
  • 函数位置ymotor_run()函数内实现4步循环
  • main函数调用:在无限循环中调用ymotor_run()实现连续旋转
int main(void) {
// 初始化代码...
while (1) {
ymotor_run(); // 调用电机驱动函数
}
}
  • 代码烧录后,电机正常转动,说明代码正确。
  • 修改ymotor_run函数控制电机转动。
  • 调整HAL_Delay值控制转速
  • 延时小 → 转速快
  • 延时大 → 转速慢
  • 正转时序:1→2→3→4→1循环
  • 反转时序:4→3→2→1→4循环
  • 代码只需逆序执行相同4步逻辑
  • 函数:ymotor_run(方向, 延时)
  • 方向:0=正转,1=反转
  • 延时:每步延时ms,控制转速
// 示例调用
ymotor_run(0, 1); // 正转,1ms延时
ymotor_run(1, 2); // 反转,2ms延时
  • 通过参数灵活控制方向速度
  • 函数参数:第一个参数为当前步数%4(0-3代表4步时序),第二个参数为方向(0为正转)
  • 调用ymotor_run(direction)实现对应电平输出
  • main循环实现:设置step=500direction=0
int step = 500;
int direction = 0;
while(running) {
for(i=1; i <= step; i++) {
motor_run(direction); // 根据step%4选择时序
HAL_Delay(1); // 每步1ms,频率约1kHz
}
}
  • 速度控制:减小HAL_Delay加快速度,增大延时减慢速度
  • 循环结束:500步后将所有HAL_GPIO口设为低电平(GPIO_PIN_RESET)
  • 电机停止条件:输出低电平(L/L),电机停止运转
  • 正反转与速度控制:通过时序顺序(1-2-3-4正转 / 4-3-2-1反转)和延时值实现
  • 直流电机与步进电机相关内容讲完
  • 下一节SPI串行外围设备接口,简称创口
  • 特点高速全双工同步通信总线
  • I2C类似,为串行通信接口
  • MOSIMASTER OUTPUT SLAVE INPUT(主机输出、从机输入)
  • 主机发送数据到从机
  • MISOMASTER INPUT SLAVE OUTPUT(主机输入、从机输出)
  • 从机发送数据到主机
  • 时钟线(SCK):同步信号
  • 提供时钟同步,确保数据传输时序一致
  • CS片选线:设备选择信号
  • 支持一对多通讯
  • 通过CS选择总线上特定设备进行通讯
  • 类似I2C的设备地址机制,但用硬件引脚实现
  • 片选控制:与设备A通信时,使能A(低电平),失能B;切换到B时失能A,使能B
  • 接线图:多个从设备共享总线,通过独立片选线控制
  • UART:异步(常用),或USART同步;通讯线3根(TX、RX、GND)
  • I2C:同步(有SCL时钟线)
  • SPI:同步(有SCLK时钟线)
  • 通讯线区别:UART 3根,其他更多(SPI典型4线:MOSI/MISO/SCLK/CS)
  • 引脚数量:UART两根(TX/RX),I2C两根(SDA/SCL),SPI四根(发送/接收 + SCL + 片选)
  • 通讯方式
  • UART:一对一(如单片机间或电脑与单片机)
  • I2C/SPI:总线方式(一对多)
  • 数据速率
  • UART:几十K到几M
  • I2C:标准100K,高速3.4M
  • SPI:10M到上百M(远高于I2C)
  • 选择依据:大数据高速通讯选SPI,串口通讯选UART
  • 通信参数对比表
项目 UART (异步) I2C (同步) SPI (同步)
通信线路 TXD发送
RXD接收
GND地
SDA数据
SCL时钟
MOSI主发送从接收
MISO主接收从发送
SCK时钟
CS片选
通信速率 几十Kbps 慢模式100Kbps
标准模式400Kbps
几十Mbps
场景 串口、蓝牙、RS232/485、鼠标键盘 短距离、传感器、EEPROM、Flash 短距离高速、传感器、EEPROM、Flash、屏幕
  • SPI应用:短距离高速通信,适合传感器、SPI Flash(如W25Q系列)、屏幕(如OLED)与MCU通信
  • 后续实验:点亮OLED屏幕
  • 新建工程查看CubeMX SPI参数
  • 左侧ConnectivitySPI
  • 后续课程实际应用演示
  • STM32芯片内置三路SPISPI1、SPI2、SPI3
  • 绑定到指定引脚组,可配置为全速Master模式
  • 配置后自动绑定四个IO(MOSI/MISO/SCK/CS)
  • SPI1示例:使用硬件绑定四个IO
  • 参考芯片手册31页引脚说明章节
  • 引脚支持确认:查找PA4/5/6/7
  • 手册显示支持SPI对应模式
  • 可用CubeMX直接使能某个SPI,自动绑定引脚。
  • 也可在数据手册中查找端口重映射信息。
  • SPI1:可用PA4/5/6/7,也可用PB3/4/5。
  • 打开CubeMX,查看SPI配置参数的含义。
  • 首先是模式选择。
  • 硬件NSS:设为Output模式
  • 主机自动控制片选信号
  • 通过库SPI API直接操作使能/失能
  • 软件NSS:选择其他GPIO作为片选
  • 手动设为Output模式
  • 通信时控制GPIO电平使能外设
  • 示例:拉低使能,高电平失能
  • 禁用NSS:自定义片选控制
  • 手动编写代码开启并配置
  • 灵活选择任意IO作为片选线
  • 配置完模式/参数后,继续查看下方SPI具体设置
  • 数据格式:固定摩托罗拉格式Motorola模式)
  • SPI标准协议,由摩托罗拉设计
  • 数据长度:可选8位16位
  • 通常选8位:方便字节传输
  • 若从机要求16位,则匹配设置
  • Bit Order (MSB/LSB):发送时先高位还是先低位
  • 根据从机要求匹配
  • 从机MSB First → 主机设MSB First
  • Byte Order:多字节时先高字节还是先低字节
  • 同样根据从机匹配
  • 配置原则:主机参数严格匹配从机,确保数据传输正确
  • 配置原则:主机参数严格匹配从机,确保数据传输正确。
  • SPI是同步通信,需要时钟线(SCK)来同步数据传输。
  • 时钟参数位于Clock Parameters区域。
  • SPI时钟来源:由系统时钟分频而来。
  • 预分频器(Prescaler):用于设置SPI时钟频率。
  • 示例:系统时钟72MHz,预分频2 → SPI时钟36MHz
  • 预分频256 → SPI时钟281.25kHz
  • 时钟频率选择:根据从设备支持的最高速率来设置。
  • 从设备支持10MHz → SPI时钟设为10MHz以下。
  • 从设备支持2MHz → SPI时钟设为2MHz以下。
  • 注意:SPI时钟不能超过从设备支持的最高速率,否则数据可能出错。
  • CPOL (Clock Polarity):时钟极性
  • 0:空闲时低电平
  • 1:空闲时高电平
  • CPHA (Clock Phase):时钟相位
  • 0第一个时钟沿采样数据
  • 1第二个时钟沿采样数据
  • 时钟极性与相位:两者组合形成4种工作模式(模式0、1、2、3)。
  • 主机和从机的CPOL/CPHA必须一致,才能正常通信。
  • 配置原则:根据从设备手册要求进行设置。
  • NSS (Slave Select):从机选择信号。
  • 模式选择
  • Hardware NSS Signal:硬件NSS信号。
  • Output Enable:NSS引脚作为输出,由硬件自动控制
  • Output Disable:NSS引脚作为输入,用于多主模式或独立控制。
  • Software NSS Signal:软件NSS信号。
  • 由软件(代码)控制NSS引脚的高低电平。
  • 适用于单主机单从机的简单场景。
  • 使用场景:通常选择软件NSS,通过GPIO操作来控制片选信号。
  • 这样可以更灵活地控制多个从设备。
  • CRC Calculation:CRC校验。
  • 功能:用于检查数据传输过程中的错误
  • 启用/禁用:通常选择禁用
  • 只有当从设备支持需要高可靠性时才启用。
  • 启用后会增加通信开销和复杂度。
  • 关键:SPI的所有参数(数据长度、位序、时钟极性、时钟相位、NSS模式)必须与从设备完全匹配
  • 配置流程
  1. 使能对应的SPI外设。
  2. 配置工作模式(Master/Slave)。
  3. 配置数据参数(Data Size、Bit Order)。
  4. 配置时钟参数(Prescaler、CPOL、CPHA)。
  5. 配置NSS模式(硬件/软件)。
  6. 根据需要决定是否启用CRC校验
  • 目的:确保主机和从机能够正确解析和交换数据。
  • 实验目标:使用SPI协议驱动OLED屏幕
  • 屏幕型号0.96寸OLED显示屏
  • 驱动芯片SSD1306
  • 通信方式:SPI接口。
  • 屏幕尺寸128x64像素
  • 硬件连接
  • VCC/GND:电源。
  • D0 (SCK):时钟线。
  • D1 (MOSI):数据线。
  • CS:片选线。
  • DC (Data/Command):数据/命令选择线。
  • RES:复位线。
  • D0 (SCK):对应STM32的SPI时钟引脚
  • D1 (MOSI):对应STM32的SPI主发从收引脚
  • CS:对应STM32的GPIO输出引脚(软件控制)。
  • DC (Data/Command):对应STM32的GPIO输出引脚
  • 高电平:发送数据
  • 低电平:发送命令
  • RES (Reset):对应STM32的GPIO输出引脚
  • 用于复位OLED屏幕。
  • 工程复制:复制现有工程,重命名为oled_demo
  • CubeMX配置
  • SPI1:全双工主模式。
  • GPIO输出:配置CS、DC、RES三个引脚。
  • SPI1引脚
  • PA5SCK (SPI1时钟)。
  • PA6MISO (SPI1主入从出)。
  • PA7MOSI (SPI1主出从入)。
  • PA4NSS (SPI1片选)。
  • CubeMX配置
  • PA5、PA7:配置为SPI1功能。
  • PA6:由于OLED屏是单向通信(主机只发数据),MISO引脚可以不配置或配置为GPIO
  • PA4:配置为GPIO输出,用于软件控制CS
  • DC引脚PB1,配置为GPIO输出
  • RES引脚PB0,配置为GPIO输出
  • User Label:为这些GPIO引脚添加有意义的标签,方便代码编写。
  • 例如:OLED_CS_PinOLED_DC_PinOLED_RES_Pin
  • 模式Full-Duplex Master
  • 数据格式Motorola
  • 数据长度8 Bits
  • MSB FirstMSB First (高位在前)。
  • 波特率预分频:根据屏幕支持的速率选择。
  • 示例:PCLK2/256
  • CPOL/CPHA:根据屏幕手册选择。
  • 示例:CPOL LowCPHA 1 Edge
  • NSS模式Software NSS Signal (软件控制)。
  • CRCDisabled

OLED屏幕驱动实验 - 代码生成与文件结构

Section titled “OLED屏幕驱动实验 - 代码生成与文件结构”
  • 生成代码:配置完成后,点击生成代码。
  • 文件结构
  • oled.c:OLED屏幕驱动源文件,包含初始化、显示函数等。
  • oled.h:OLED屏幕驱动头文件,包含函数声明、宏定义等。
  • 添加文件:将oled.coled.h文件添加到工程中。
  • oled.c放到Core/Src目录下。
  • oled.h放到Core/Inc目录下。
  • 编译验证:添加文件后,先编译一次工程,确保没有语法错误和文件路径问题。
  • SPI总线架构:SPI1挂载APB2总线,SPI2/3挂载APB1总线
  • 时钟来源:对应APB1/APB2时钟
  • 分频配置:通过时钟树调整APB时钟分频
  • STM32建议:SPI时钟不超过18MHz
  • 示例:选2分频超限时报错
  • 需增大分频系数避免超过限制
  • 时钟相位 (CPHA)时钟极性 (CPOL):决定SPI采样时刻与时钟同步
  • 配置CubeMX时钟分频后设置(4或8改为18)
  • 两个参数组合产生4种模式
  • 模式对照表
SPI模式 CPOL CPHA 数据采样时钟 数据发送时钟
模式0 0 0 CPOL=0, CPHA=0 CPOL=0, CPHA=0
模式1 1 0 CPOL=1, CPHA=0 CPOL=0, CPHA=0
模式2 0 1 CPOL=0, CPHA=1 CPOL=1, CPHA=1
模式3 1 1 CPOL=1, CPHA=1 CPOL=1, CPHA=1
  • 时序图示(手册第461页)示例:
CPOL=1, CPHA=0:
______ ______ ______ ______
SCK ______________________|
MOSI ____________________________________________________________
MISO ____________________________________________________________
CPOL=0, CPHA=0:
______ ______ ______ ______
SCK ______________________|
MOSI ____________________________________________________________
MISO ____________________________________________________________
  • 作用:控制何时采样数据(上升/下降沿),确保主从机时序匹配
  • 根据从机手册选择对应模式
  • CPOL (Clock Polarity,时钟极性):定义时钟线空闲时的电平
  • CPOL=0:空闲时低电平
  • CPOL=1:空闲时高电平
  • CPHA (Clock Phase,时钟相位):定义数据采样时刻
  • CPHA=0:在第一个时钟沿采样(即时钟变沿
  • CPHA=1:在第二个时钟沿采样
  • 模式0示例 (CPOL=0, CPHA=0):低速边缘采样
  • 低速边缘:时钟从空闲电平变沿时刻
  • 时钟空闲低电平上升沿时采样数据
模式0 (CPOL=0, CPHA=0):
_____ _____ _____ _____
SCK ________| |___| |___| |__ ← 上升沿采样
MOSI [数据位] [数据位] [数据位] ...
MISO [数据位] [数据位] [数据位] ...
  • 影响:CPOL/CPHA组合决定采样初始电平与时机,确保主从机同步
  • 采样时刻决定CPOL/CPHA共同控制何时采样数据
  • 第一个时钟沿:空闲电平后第一个上升/下降沿
  • 第二个时钟沿:空闲电平后第二个沿
  • 示例
  • CPOL=1, CPHA=0:空闲高电平第一个下降沿采样
  • CPOL=0, CPHA=1:空闲低电平第二个上升沿采样
  • 配置原则
  • 主机严格匹配从机参数
  • 根据从机要求选择对应模式,确保采样时机一致

STM32硬件SPI代码生成与初始化函数

Section titled “STM32硬件SPI代码生成与初始化函数”
  • 生成代码后,main函数自动添加**MX_SPI1_Init()**调用
  • 与其他外设初始化(如ADC、USART)顺序一致
int main(void) {
// ...
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_ADC1_Init();
MX_SPI1_Init(); // SPI初始化新增
// ...
}
  • SPI1 MSP初始化(HAL_SPI_MspInit调用)
  • 使能SPI1时钟__HAL_RCC_SPI1_CLK_ENABLE()
  • 使能GPIOA时钟__HAL_RCC_GPIOA_CLK_ENABLE()
  • 配置PA4/5/6/7为SPI1功能
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  • HAL_SPI_MspDeInit反初始化
  • 关闭SPI1时钟__HAL_RCC_SPI1_CLK_DISABLE()
  • 解除PA4/5/6/7 GPIO配置HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
  • 基本配置
  • hspi1.Instance = SPI1;
  • hspi1.Init.Mode = SPI_MODE_MASTER;
  • hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  • hspi1.Init.DataSize = SPI_DATASIZE_8BIT;(8位数据)
  • 初始化检查:if (HAL_SPI_Init(&hspi1) != HAL_OK) Error_Handler();
  • 回调函数(Callback):用于输出设备硬件无关的函数
  • 在调用HAL库发送/接收函数后自动触发
  • HAL_SPI_TxCpltCallback:传输完成回调(弱函数)
  • 函数末尾可见WEAK属性,可重写覆盖
  • 示例:发送完毕后执行自定义逻辑
  • 重写方法:在用户代码中重新定义该函数
  • 调用HAL函数时,内部自动调用重写版本
  • 用于自定义发送后处理(如数据校验)
  • 位于回调函数下方
  • 包含SPI相关发送/接收API函数
  • 中断与DMA:SPI全双工通信支持中断(IT结尾函数)和DMA传输
  • 与前面UART全双工通信类似
  • 实验内容:手把手操作开发板支持SPI的屏幕
  • 屏幕接口:支持三线SPI四线SPII2C
  • 本实验使用四线SPI设计
  • 资料准备:下载屏幕驱动手册,查看关键接口描述
  • 分辨率:128x64
  • 接口:4-wire SPI(MOSI/MISO/SCK/CS)或3-wire SPI(MOSI/SCK/CS)
  • 驱动IC:SH1106
  • 颜色:白色,尺寸28.0mm x 20.0mm,显示区23.5mm x 11.5mm
配置 数据信号 控制信号
4-wire SPI MOSI, MISO, SCK, CS DC, RST, VSS, VDD
3-wire SPI MOSI, SCK, CS DC, RST, VSS, VDD
  • 默认模式SPI mode 0
  • 选择依据:根据硬件连接和需求
  • DC (Data/Command):低电平发送命令,高电平发送数据
  • 命令:配置屏幕模式/参数
  • 数据:显示图像内容
  • RST (Reset):复位显示屏
  • CS (Chip Select):低电平选中设备,高电平取消选中
  • 通过SPI驱动实现画点、画线、显示图像
  • RES:复位信号,连接PB8
  • DC:指令/数据切换信号,连接PB4
  • CLK:时钟信号(对应SCK),连接PB3
  • SDIN:数据输入信号(对应MOSI),连接PB5
  • 连接方式:通过网络标签连接到STM32相应引脚
  • SPI3模式选择:全双工Master或半双工Master
  • 显示屏只需发送数据(无需接收),选半双工Master
  • 自动引脚绑定:使能SPI3后,默认PB3(时钟SCK)、PB5(MOSI主出从入)
  • NSS片选配置Disable硬件NSS
  • 原理图中CS引脚接地
  • 手册说明:CS接地后芯片默认使能
  • 上电即选中状态,无需额外控制
  • 后续:配置完成后调整数据类型参数
  • RES (复位引脚):低电平复位芯片,高电平正常工作模式
  • DC (A0, 数据/命令引脚):高电平发送显示数据,低电平发送命令数据
  • 配置方式:将RES和DC引脚设置为输出模式
  • 由STM32作为主机控制这两个引脚电平
  • 用于驱动屏幕显示和参数配置

OLED屏幕驱动实验 - SPI数据参数配置

Section titled “OLED屏幕驱动实验 - SPI数据参数配置”
  • 数据格式:选择摩托罗拉格式(Motorola)
  • SPI标准协议格式,与屏幕芯片匹配
  • 数据长度8位(8 Bits)
  • 根据芯片手册:发送D7~D0 8位数据
  • 位序 (Bit Order)MSB First
  • 先发D7,再发D6…高位先行
  • 初始波特率设置:预分频选较大值(如256)
  • 降低SPI频率,避免硬件走线不良导致刷新失败或点亮不了
  • 调试成功后再逐步提高频率
  • 配置CPOL/CPHA:根据芯片手册选择CPOL=1(高),CPHA=偶数边缘(第二个边缘)
  • 与之前模式设置一致
  • 生成代码验证:CubeMX生成后编译工程,无问题
  • 参考手册时序:芯片手册提供屏幕初始化时序图
  • 厂家示例代码:包含屏幕特定参数
  • 直接参考编写初始化和驱动函数
  • 参数针对该屏幕设计,确保兼容
  • 复位流程:拉低OLED_RST_PinGPIO_PIN_RESET) → HAL_Delay(1000) → 拉高(GPIO_PIN_SET) → HAL_Delay(1000)
  • 拉低:触发复位
  • 拉高:进入正常工作模式
// Reset the OLED display
HAL_GPIO_WritePin(GPIOH, OLED_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOH, OLED_RST_PIN, GPIO_PIN_SET);
HAL_Delay(1000);
  • 位置main函数 /* USER CODE BEGIN 2 */ 区域,紧跟外设初始化后执行
  • 实测简化:拉低OLED_RED_Pin复位 → HAL_Delay(1000) → 拉高 → HAL_Delay(1000),后续循环可省略,直接进入正常工作模式
HAL_GPIO_WritePin(GPIOB, OLED_RED_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, OLED_RED_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, OLED_RED_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
  • DC引脚控制:拉低OLED_DC_Pin进入命令发送模式
  • HAL_GPIO_WritePin(GPIOB, OLED_DC_Pin, GPIO_PIN_RESET)
  • SPI发送函数HAL_SPI_Transmit(&hspi3, (uint8_t*)&cmd, 1, HAL_MAX_DELAY)
  • 参数:SPI3句柄、8位命令数据指针(uint8_t类型)、长度1