SmartRobot扫地机器人

1 项目需求分析

1.1 项目背景介绍

随着科技的快速发展和人们生活水平的不断提高,智能家居设备正逐渐成为现代家庭的重要组成部分。在快节奏的生活中,人们对于家务自动化的需求日益增长,尤其是日常清洁工作,既耗时又费力。传统的清洁方式(如手动扫地、拖地)已无法满足现代家庭对高效、便捷生活的追求。扫地机器人作为智能清洁领域的代表性产品,凭借其自主导航、智能避障、自动回充等先进功能,能够有效减轻用户的清洁负担,提升生活品质。近年来,随着人工智能(AI)、传感器技术、路径规划算法以及物联网(IoT)技术的进步,扫地机器人的性能与智能化水平显著提升,市场需求持续扩大。

本项目基于市场上成熟的海尔扫地机器人硬件方案,通过复刻和优化现有设计,重点研究其核心功能模块,包括运动控制、传感器数据处理、路径规划算法等。在复刻过程中,我们将深入分析现有扫地机器人的优缺点,并结合实际应用场景进行针对性改进,例如优化清洁路径、提升避障精度、降低功耗等。该项目的实施不仅有助于深入理解扫地机器人的关键技术,还能为后续的二次开发和功能拓展奠定基础。通过复刻与优化,我们期望在降低研发成本的同时,提升产品的稳定性和用户体验,为智能家居设备的本地化应用提供参考方案。

图1.1:项目用海尔扫地机器人产品效果图

1.2 设计需求与相关指标确定

基于以上项目背景,结合硬件实机情况与初步调研结果,总结出了以下几条功能设计需求,并本着定量化的思想,在每一条功能需求上都提出了对应的指标:

  1. 良好的避障功能:扫地机器人应集成有效传感器,以实现对障碍物的检测和避让,确保在复杂环境中的自主导航。除此之外,为保证最大程度的清洁,不放过任何一个清洁死角,我们希望机器人在触碰到墙体后,能够尽可能地以低速(不超过0.1m/s)贴近墙体行进。

    具体而言,在碰撞传感器触发时,机器人需要在50ms内作出反应,先后退离开墙体区域,在原地进行小角度旋转后继续向前行进,再次碰到墙体时继续做出这样的反应。

  2. 防坠落能力:设备应具备悬崖检测传感器(本项目中硬件设备为红外传感器),以识别楼梯边缘或高度差,防止从高处坠落。

    具体而言,我们希望在悬崖传感器检测到机器人位于高度垂直落差大于5cm的界面时,能够在50ms内作出反应,先紧急制动,接着低速后退离开悬崖区域,在原地旋转改变运动方向后继续行进,以最大限度地保护机器人地工作安全,避免损坏。

  3. 运动功能:扫地机器人应能根据家庭环境尺寸,实现高精度的运动控制。

    具体而言,我们希望基于编码器利用PID控制器实现高精度的电机调控与运动控制,在直线行驶1m距离的测试中总偏角应不超过5°、距离误差不超过2cm.

  4. 用户通讯控制:设备应配备直观的用户界面,包括按键和指示灯,以便用户轻松启动、暂停和控制扫地机器人。应支持通过蓝牙连接,实现电脑端应用程序的远程控制,包括启动/停止自动清扫、遥控机器人(基于实时遥控信号调整运动状态)、监控电池状态等。

    具体而言,希望在中低速运动条件下(0.05~0.15m/s)实现频率至少10Hz的实时位置坐标与姿态(偏转角度)的数据解算(位置坐标各方向误差<5cm、偏转角度误差<5°),并将数据传至用户界面,根据已有的行进位姿数据进行实时路径可视化,基于已有建图结果实现全覆盖路径规划。

1.3 项目分工

PCB设计及硬件维护:余虹鋈 学号:20222957
嵌入式程序:王冲 学号:20224841
上位机及嵌入式程序:肖范熠 学号:20223984
算法及嵌入式程序:许晶华 学号:20224546

1.4 本章小结

本章对于扫地机器人项目的应用背景进行了介绍,并基于实机测试与调研结果确定了产品的功能需求指标,这为后续的项目开发起到了良好的引领和指导作用,后续的项目功能研发也将紧密围绕这些功能需求点与性能指标进行展开。

2 PCB电路设计

2.1 设计目标

本次PCB设计面向智能扫地机器人控制系统,目标是实现电源管理、电机驱动、传感器输入处理、MCU外围支持、IMU通信等功能模块的稳定集成,并兼顾成本控制、布局合理性与后期调试便利性。

2.2 PCB总体布局与功能划分

PCB采用双面布局,充分利用板面空间,模块化划分如下:

  • 电源管理区:提供从锂电池到各类电压等级(如5V/3.3V)的转换;

  • 单片机核心区:部署STM32及其基本外围电路;

  • 电机驱动区:负责控制左右驱动电机及各个毛刷电机和吸尘电机;

  • 传感器接口区:用于悬崖、碰撞等传感器信号接入与处理;

  • 辅助保护与调试区:如状态指示灯、串口接口等。

图2.1:单片机外围电路

图2.2:传感器、电机电路

图2.3:电源相关电路

2.3 电源管理电路设计

2.3.1 充电电路设计

电源模块采用多级稳压设计,满足不同子系统电压需求。在芯片选型方面,我们选择使用SLM6900降压模块,其特性如下:

  • 输出固定16.8V,无需外围分压,适合三到四节锂电池充电;

  • 高效率(5A负载),封装易焊,适配性好;

  • 可通过ADC分压获得充电状态,便于MCU监控;

  • NCHRG(充电中低电平)和NSTDBY(充满低电平)引脚可直接驱动LED简化状态监控,红绿LED指示灯直观反馈。

相较于另一常见的充电芯片BQ24610RGER而言,拥有更易焊接的封装方式和更便宜的价格。

图2.4:充电保护电路

图2.5:降压模块选型:SLM6900

图2.5:降压模块选型:BQ24610RGER

2.3.2 降压电路设计

在降压电路设计中,我们选择采用TPS54531作为降压芯片,该芯片具有以下优点:

  • 支持从低至锂电池单节电压到24V工业电源的广泛输入,适配多种电源场景(如车载系统、分布式电源);

  • 轻载时自动切换至脉冲跳跃模式,静态电流低至1μA(关断状态),显著提升电池供电设备的待机时长;

  • 逐周期电流限制、频率折返、热关断(165℃触发)三重保护。过压瞬态保护抑制启动电压过冲;

  • 封装焊接简易;

  • 成本低,三元一个,单个可售。

图2.6:16.8v降5v电路

在选型时,我们还考虑了其他型号的降压芯片,其中TPS56637适用于4.5V至28V,价格五元一个,与选用的TPS54531价格相近,但是其封装在焊接过程中出问题的概率更大,综合考虑不予选用;而LM2679S性能强悍,但是价格高昂,五十元一个,不予选用。

图2.7:降压芯片选型:TPS54531

图2.7:降压芯片选型:TPS56637

图2.7:降压芯片选型:LM2679S

线性稳压器方面,由于在这一步仅需5v降到3.3v压差小,因此选用合适、最廉价的芯片即可,不必追求过高性能。这里我们选用AMS1117-3.3线性稳压器,其主要特性如下:

  • 为MCU提供3.3V电压;

  • 精度±1%,外围电路极简,仅需输入/输出电容。

图2.8:15v降3.3v电路

2.4 电机驱动与控制逻辑设计

2.4.1 运动电机驱动芯片选型对比

由于驱动直流电机需要的电流很大,单片机I/O的驱动能力是远远达不到的。因此需要使用专用的电机驱动芯片。芯片驱动能力及使用复杂度简单对比如下:

  • A4950:8~40V,高压大电流,外围适中;

  • DRV8833:2.7~10.8V,体积小,适合低压,使用简单;

  • TB6612FNG:2.5~13.5V,性能稳定,体积小;

  • L298N:3~48V,体积大,驱动强,但外围复杂。

图2.9:DRV8833芯片

DRV8833电机驱动芯片是基于H桥电路的,芯片中共有两个全H桥,因此最多可以同时驱动两个直流电机或一个步进电机。电源供电电压2.7~10.8V,每个H桥输出的均方根(RMS)电流为1.5A,峰值可达2A。内置过热保护和用户可调的限流保护电路。

图2.10:DRV8833芯片功能框图

框图中也包含了DRV8833芯片外部所需要的元件,主要是三个电容以及两个电流检测电阻(电阻可不接)。当温度过高,温度检测保护模块会使nFAULT所接的FET导通拉到低电平,同时H桥转成衰减模式,不再给电机供电。

图2.11:TB6612FNG芯片

TB6612FNC是东芝半导体公司的一款电机驱动芯片,也是集成了两个全H桥。在应用上基本与DRV8833相似,但性能更好,价格也相对较高。电源供电电压2.5~13.5V,H桥输出的平均电流1.2A,最大可到3.2A。(可见驱动能力比DRV8833略强)内置过热保护和低压检测关断电路,PWM控制的频率可达100kHZ。

图2.12:TB6612FNG芯片功能框图

上面框图中画出了使用该芯片需要外接的元件(4个滤波电容)。从中可发现,其与DRV8833最大不同即在输入控制上,除了输入1和输入2,还有一个PWM输入脚。

图2.13:A4950芯片

A4950是美国埃戈罗公司生产的一款单H桥电机驱动芯片。因此网上卖的模块多是使用两块芯片以达到可以控制两个直流电机的能力。电机驱动电压:8~40V,输出最大电流可达3.5A;内置过温保护,短路保护和可选择的过流保护。

图2.14:A4950芯片功能框图

通过引脚说明和功能框图可看出,此芯片不同之处有:只有单H桥,因此引脚较少;限流比较的参考电压由外部给出(VREF脚);因此限流值Isense=Vref/10/Rsense。如上面的模块中,Vref接5V,Rsense为R250精密检测电阻(0.25Ω),因此限流值为2A。当IN1和IN2均保持低电平1ms,芯片进入待机模式。而不是通过引脚直接控制。经过对比发现,此芯片的驱动逻辑与上述的DRV8833PWP芯片完全一致。

图2.15:L298N电机驱动模块

L298N是ST公司的一款电机驱动芯片,也是集成了双H桥,但与上面两个略有不同。该芯片适配的电机驱动电压为3~48V;可持续工作的输出电流为2A,峰值可达3A。如上图,L298N模块明显比前两个芯片模块外接的元件多,这与L298N的内部结构有关。如上图,由于该芯片在H桥上的损耗严重发热较明显(饱和压降大),需要加装散热片,因此在使用上比前两个芯片复杂,体积也相对较大。

图2.16:L298N芯片功能框图

如上图所示:L298N的内部功能很多都类似,比如电流检测,H桥驱动,外接电容等。主要区别在于L298N的H桥采用了BJT而不是MOSFET。这就直接导致没有寄生二极管,无法像前两个芯片一样实现续流。因此需要外接8个续流二极管。因为频率不高,选用普通的整流二极管即可(如1N4007)。如下图所示:

图2.17:L298N芯片连接示意图

此芯片的电流检测脚Sense-X并不像前面的芯片,其没有在内部进行电压比较从而限流,从数据手册上看,需要一个L297芯片配合进行限流。因此一般直接接地,不进行限流。

综上所述,对比四款常用的电机驱动芯片,可以得到如下结论:

  • 三款芯片的内部原理和控制方式大同小异;

  • 可通过两个H桥输出的并联控制一个直流电机,这样最大驱动电流可翻倍,这在芯片的数据手册中均有说明;

  • 以上三种芯片驱动能力排序:DRV8833<BT6612<A4950≈L298N;

  • DRV8833、TB6612和A4950的体积小,外接元件少,使用简单;L298N体积大,外接元件多,使用相对复杂;

在选择这种集成H桥芯片时,需要考虑的参数有:可承受的工作电流要大于电机的堵转电流,防止堵转时驱动芯片烧毁;导通电阻尽可能小,减少芯片的发热损耗。综合这些因素考虑,本项目最终选用A4950作为主驱动芯片,其适配性与功率支持表现均较为优异。

2.4.2 其余电机驱动控制逻辑设计

在刷子电机和风机驱动方面,选择使用74HC14D来对单片机信号进行增强,从而达到控制的目的,因为这些电机不需要特别精细的控制,这样的选择可以最低限度实现功能的同时,减少PCB版面空间的消耗。

这一部分的驱动电路设计主要有以下要点:

  • 74HC14D 施密特反相器用于整形PWM信号,防抖处理;

  • NCE6020AK MOSFET 用作电机功率开关管;

  • 二极管续流保护,防止感性反冲。

图2.18:74HC14D驱动电机电路

在上述电路中,MCU输出的PWM信号经R178/R188限流后输入74HC14D,整形后的反相信号驱动NCE6020AK栅极。同时,74HC14D输出端应通过栅极电阻(典型值10-100Ω)连接MOSFET栅极,连接形式为:VCC15V→ MOSFET漏极 → 源极 → 100mΩ采样电阻 → 电机 →GND。当电机停转时,电感能量经B340A续流二极管释放。该电路还包含过流保护与电压尖峰抑制的功能,将采样电阻压差送ADC6,MCU触发关断,B340A续流二极管吸收反向电动势。

2.5 单片机外围电路

本项目的MCU采用STM32系列芯片,外围电路主要包括以下几个部分:

  • 晶振电路(8MHz或16MHz);

  • 上拉/下拉电阻;

  • 下载接口(SWD);

  • 启动配置引脚;

  • 调试指示LED、UART通讯接口等。

图2.19:单片机外围电路

2.6 PCB布局设计说明

2.6.1 正面布局

以MCU为核心,周边辐射式布局传感器与电源模块,整体走线遵循横平竖直的原则,下半区集中走线,保证PCB美观。

图2.20:PCB正面布局

2.6.2 反面布局

主电源走线、地平面铺铜,布线优化短路径,增强抗干扰能力,布局1206封装的电阻,充分保证正面的布局空间。

图2.21:PCB反面布局

2.6.3 实物图展示

实际焊接效果良好;各模块标识清晰,便于调试。

图2.22:PCB实物图

2.7 本章小结

本次PCB设计实现了功能完备、结构合理的控制系统硬件支持。后续根据产品实际需要,若需要进一步改进,可以从如下方面入手:

1、优化板面尺寸,提升布局紧凑性;

2、考虑EMC设计与测试,提升抗干扰性能;

3、引入模块化可插拔接口,便于维护升级。

3 硬件外设程序设计

3.1 设计目标

本章节内容主要介绍PCB上的硬件外设的程序设计以及功能接口的使用方法,包含LED、按键、IMU、电机驱动、编码器、悬崖传感器、电压读取以及碰撞传感器的相关内容。

3.2 LED

项目中共有四颗可供使用的空闲LED(LED0~1),为方便使用、扩展进行了统一的封装。目前四颗均为上拉连接低电平触发。

图3.1:LED部分电路原理图

3.2.1 硬件描述

为了程序的规范性和可拓展性,我们需要在main文件中使用上述函数对LED进行硬件描述:

1
2
3
4
5
typedef struct {
GPIO_TypeDef \*GPIOx; // GPIO 端口
uint16_t GPIO_Pin; // GPIO 引脚
LED_ActiveMode ActiveMode; // LED 工作模式(主动低或主动高)
} LED_HandleTypeDef;

其中工作模式如下:

1
2
3
4
typedef enum {
LED_ACTIVE_LOW = 0, // LED 在低电平时点亮(即连接到 VCC)
LED_ACTIVE_HIGH // LED 在高电平时点亮(即连接到 GND)
} LED_ActiveMode;

示例:LED0连接在PD端口0号引脚,低电平触发,则如下:

1
2
3
4
5
LED_HandleTypeDef LED0 = {
.GPIOx = GPIOD,
.GPIO_Pin = GPIO_PIN_0,
.ActiveMode = LED_ACTIVE_LOW
};

3.2.2 初始化

效果为根据描述的硬件连接匹配对应的电平输出。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
/* LED 初始化函数 */
void LED_Init(LED_HandleTypeDef *led) {
// 根据LED的工作模式(主动低或主动高)初始化LED
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,初始化时将LED设置为关闭(对应的GPIO引脚输出高电平)
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
} else {
// 如果是主动高模式,初始化时将LED设置为关闭(对应的GPIO引脚输出低电平)
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
}
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Init (&LED0);

3.2.3 点亮LED

效果为点亮LED。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
/* 点亮LED */
void LED_On(LED_HandleTypeDef *led) {
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,点亮LED需要将GPIO引脚输出低电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
} else {
// 如果是主动高模式,点亮LED需要将GPIO引脚输出高电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
}
}

示例:点亮LED0,则可直接调用该函数:

1
LED_On (&LED0);

3.2.4 熄灭LED

效果为熄灭LED。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
/* 熄灭LED */
void LED_Off(LED_HandleTypeDef *led) {
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,熄灭LED需要将GPIO引脚输出高电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
} else {
// 如果是主动高模式,熄灭LED需要将GPIO引脚输出低电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
}
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Off (&LED0);

3.2.5 翻转LED

效果为转换当前LED的亮灭状态。对应函数代码如下:

1
2
3
4
5
/* 切换LED状态(开/关) */
void LED_Toggle(LED_HandleTypeDef *led) {
// 切换LED状态(如果是开就关,反之亦然)
HAL_GPIO_TogglePin(led->GPIOx, led->GPIO_Pin);
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Toggle (&LED0);

3.3 按键

如下图所示,项目有两个可供使用的微动按键(KEY0、KEY1),均为上拉连接低电平触发,配置为GPIO的Input模式。

图3.2:按键部分电路原理图

3.3.1 硬件描述

硬件描述结构体如下:

1
2
3
4
5
6
7
typedef struct {
GPIO_TypeDef *GPIOx; // GPIO端口(如GPIOD)
uint16_t GPIO_Pin; // GPIO引脚(如GPIO_PIN_11)
Key_ActiveMode ActiveMode; // 有效电平模式
Key_State CurrentState; // 当前状态
uint32_t LastTick; // 最后一次状态变化时的时间戳
} Key_HandleTypeDef;

其中,按键状态又有如下枚举:

1
2
3
4
5
6
typedef enum {
KEY_STATE_RELEASED = 0, // 松开状态
KEY_STATE_PRESSED, // 按下状态
KEY_STATE_JUST_PRESSED, // 刚刚按下(边沿)
KEY_STATE_JUST_RELEASED // 刚刚松开(边沿)
} Key_State;

示例:直接描述两个按钮如下,均为低电平触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Key_HandleTypeDef key[] = {
{
// KEY0(PD11)
.GPIOx = GPIOD,
.GPIO_Pin = GPIO_PIN_11,
.ActiveMode = KEY_ACTIVE_LOW
},
{
// KEY1(PE15)
.GPIOx = GPIOE,
.GPIO_Pin = GPIO_PIN_15,
.ActiveMode = KEY_ACTIVE_LOW
}
};

3.3.2 按键初始化

效果为重置按键状态,并且更新时间戳。对应函数代码如下:

1
2
3
4
5
/* 按键初始化 */
void Key_Init(Key_HandleTypeDef *key) {
key->CurrentState = KEY_STATE_RELEASED;
key->LastTick = HAL_GetTick();
}

示例:初始化key0:

1
Key_Init (&key[0]);

3.3.3 按键更新

效果为检测并更新按键的按下状态。对应函数代码如下:

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
/* 按键更新(需要定期调用) */
void Key_Update(Key_HandleTypeDef *key) {
uint32_t current_tick = HAL_GetTick();
// 读取当前按键引脚状态
GPIO_PinState pin_state = HAL_GPIO_ReadPin(key->GPIOx, key->GPIO_Pin);
uint8_t is_pressed = (key->ActiveMode == KEY_ACTIVE_LOW) ?
(pin_state == GPIO_PIN_RESET) :
(pin_state == GPIO_PIN_SET);
// 按键状态机
switch (key->CurrentState) {
case KEY_STATE_RELEASED:
if (is_pressed) {
if (current_tick - key->LastTick > DEBOUNCE_DELAY_MS) {
key->CurrentState = KEY_STATE_JUST_PRESSED;
key->LastTick = current_tick;
}
}
break;
case KEY_STATE_JUST_PRESSED:
key->CurrentState = KEY_STATE_PRESSED;
break;
case KEY_STATE_PRESSED:
if (!is_pressed) {
if (current_tick - key->LastTick > DEBOUNCE_DELAY_MS) {
key->CurrentState = KEY_STATE_JUST_RELEASED;
key->LastTick = current_tick;
}
}
break;
case KEY_STATE_JUST_RELEASED:
key->CurrentState = KEY_STATE_RELEASED;
break;
}
}

示例:检测并更新key0:

1
Key_Update (key[0]);

3.3.4 获取按键状态

效果为配合按键状态更新函数,会返回Key_State中的状态,进行逻辑编写。

1
2
3
4
/* 获取当前按键状态 */
Key_State Key_GetState(Key_HandleTypeDef *key) {
return key->CurrentState;
}

示例:读取key1:

1
Key_State state = Key_GetState(&key[1]);

3.4 IMU

本次项目采用JY901S九轴姿态传感器,连接到USART2与单片机通信,使用中断的方式来接收数据,配置如下,根据与算法同学沟通,共需用到加速度、角速度、欧拉角每组三个共九个数据。

图3.3:MCU与IMU间连接示意图

3.4.1 IMU初始化

功能为配置缓冲区并开启首次接收中断。对应函数代码如下:

1
2
3
4
5
void JY901S_Init(UART_HandleTypeDef *huart){
jy_uart = huart;
static uint8_t rx_buf[FRAME_LEN];
HAL_UART_Receive_IT(jy_uart, rx_buf, FRAME_LEN); // 开启首次接收中断
}

示例:初始化IMU:

1
JY901S_Init(&huart2);

3.4.2 IMU数据解析

功能为根据官方文档解算IMU回传的数据。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void JY901S_UART_RxHandler(uint8_t *data){
if (data[0] == 0x55) {
switch (data[1]) {
case 0x51: // 加速度
imu_data.ax = (short)(data[3] << 8 | data[2]) / 32768.0f * 16;
imu_data.ay = (short)(data[5] << 8 | data[4]) / 32768.0f * 16;
imu_data.az = (short)(data[7] << 8 | data[6]) / 32768.0f * 16;
break;
case 0x52: // 角速度
imu_data.gx = (short)(data[3] << 8 | data[2]) / 32768.0f * 2000;
imu_data.gy = (short)(data[5] << 8 | data[4]) / 32768.0f * 2000;
imu_data.gz = (short)(data[7] << 8 | data[6]) / 32768.0f * 2000;
break;
case 0x53: // 欧拉角
imu_data.roll = (short)(data[3] << 8 | data[2]) / 32768.0f * 180;
imu_data.pitch = (short)(data[5] << 8 | data[4]) / 32768.0f * 180;
imu_data.yaw = (short)(data[7] << 8 | data[6]) / 32768.0f * 180;
break;
default:
break;
}
}
HAL_UART_Receive_IT(jy_uart, data, FRAME_LEN); // 重新开启下一次接收中断
}

示例:定义中断回调

1
2
3
4
5
6
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if (huart->Instance == USART2) {
JY901S_UART_RxHandler(jy_rx_buf);
HAL_UART_Receive_IT(&huart2, jy_rx_buf, 11);
}
}

3.4.3 获取IMU数据

1
2
3
IMU_Data_t* JY901S_GetData(void){
return &imu_data;
}

示例:在主循环中获取数据:

1
imu = JY901S_GetData();

此处imu作全局变量定义(IMU_Data_t *imu;),便于debug监视。

3.5 电机驱动

左右电机分别由一个A4950电机驱动来进行驱动,该芯片需要两路PWM来差值控制,左右驱动分别接至TIM1和TIM9的两路通道,设置如图所示。

图3.4:电机驱动部分电路原理图

3.5.1 电机初始化

图3.5:CubeMX中对两路PWM的配置

基于以上配置的两路PWM通道,编写如下函数进行左右电机驱动的初始化:

1
2
3
4
5
6
7
8
void A4950_Init(void){
// 启动左电机 PWM(TIM1_CH1=PE9, TIM1_CH2=PE11)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
// 启动右电机 PWM(TIM9_CH1=PE5, TIM9_CH2=PE6)
HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_2);
}

3.5.2 左(右)轮驱动

根据CubeMX的设置,PWM调速绝对值上限为99。分别编写左轮与右轮的PWM驱动函数如下:

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
// 左轮
void A4950_SetLeft(int16_t speed){
uint16_t duty;
if (speed >= 0) {
duty = (speed > A4950_PWM_MAX ? A4950_PWM_MAX : speed);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, duty);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
} else {
duty = ((-speed) > A4950_PWM_MAX ? A4950_PWM_MAX : -speed);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty);
}
}

// 右轮
void A4950_SetRight(int16_t speed){
uint16_t duty;
if (speed >= 0) {
duty = (speed > A4950_PWM_MAX ? A4950_PWM_MAX : speed);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, duty);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, 0);
} else {
duty = ((-speed) > A4950_PWM_MAX ? A4950_PWM_MAX : -speed);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, 0);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, duty);
}
}

3.5.3 制动

1
2
3
4
5
6
7
void A4950_Brake(void){
// 两输入同时高 → 主动制动
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, A4950_PWM_MAX);
}

3.6 编码器

3.6.1 结构体关联句柄

1
2
3
4
typedef struct {
TIM_HandleTypeDef *htim; ///< 关联的定时器句柄(如 &htim2 或 &htim5)
int32_t count; ///< 累计脉冲计数(带符号,可正可负)
} Encoder_HandleTypeDef;

示例:在主程序中定义如下结构体,分别代表左编码器与右编码器:

1
Encoder_HandleTypeDef encL, encR;

3.6.2 编码器初始化

效果为关联定时器的编码器接口。对应函数代码如下:

1
2
3
4
5
6
void Encoder_Init(Encoder_HandleTypeDef *enc, TIM_HandleTypeDef *htim){
enc->htim = htim;
enc->count = 0;
// 启动 TIM 的 TI1 和 TI2 通道以进入编码器模式
HAL_TIM_Encoder_Start(enc->htim, TIM_CHANNEL_1 | TIM_CHANNEL_2);
}

示例:绑定左右编码器并启动对应的接口,可按照如下方式调用该函数,需输入对应的编码器结构体与相关联的定时器句柄:

1
2
Encoder_Init(&encL, &htim2);
Encoder_Init(&encR, &htim5);

3.6.3 获取计数

1
2
3
4
5
6
7
8
9
int32_t Encoder_GetCount(Encoder_HandleTypeDef *enc){
int16_t raw = __HAL_TIM_GET_COUNTER(enc->htim);
// 计算与上次读取的差值(考虑 16 位上下溢出)
int32_t delta = (int32_t)raw - (int32_t)(enc->count & 0xFFFF);
if (delta > 32767) delta -= 65536;
if (delta < -32768) delta += 65536;
enc->count += delta;
return enc->count;
}

示例:

1
2
leftCountGlobal = Encoder_GetCount(&encL);
rightCountGlobal = Encoder_GetCount(&encR);

3.6.4 编码器清零

1
2
3
4
void Encoder_Reset(Encoder_HandleTypeDef *enc){
__HAL_TIM_SET_COUNTER(enc->htim, 0);
enc->count = 0;
}

示例:

1
2
Encoder_Reset(&encL);
Encoder_Reset(&encR);

3.7 悬崖传感器

共有四个悬崖传感器(0~3),使用ADC和DMA进行读取并由TIM3定时触发,ADC的配置,TIM3配置如下图所示:

图3.6:CubeMX中对TIM3的配置

图3.7:CubeMX中对ADC的配置

3.7.1 初始化

编写以下函数,以启动定时器3的定时触发以及DMA:

1
2
3
4
void CliffSensor_Init(void){
HAL_TIM_Base_Start(&htim3); // 启动 TIM3 基本定时器
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, CLIFF_SENSOR_COUNT); //启动 ADC + DMA,读取 CLIFF_SENSOR_COUNT 个通道
}

3.7.2 读取四个传感器值

1
2
3
4
void CliffSensor_GetValues(uint16_t *out){
for (int i = 0; i < CLIFF_SENSOR_COUNT; i++)
out[i] = adc_values[i];
}

示例:

1
CliffSensor_GetValues(sensor_vals);

3.7.3 判断单个传感器的状态

1
2
3
4
bool CliffSensor_IsCliff(uint8_t idx){
if (idx >= CLIFF_SENSOR_COUNT) return false; // 越界保护
return (adc_values[idx] < cliff_thresholds[idx]);
}

使用时传入对应传感器编号(0~3)即可。

3.7.4 用掩码返回所有传感器状态编码器清零

1
2
3
4
5
6
7
8
uint8_t CliffSensor_GetMask(void){
uint8_t mask = 0;
if (CliffSensor_IsCliff(0)) mask |= CLIFF_1;
if (CliffSensor_IsCliff(1)) mask |= CLIFF_2;
if (CliffSensor_IsCliff(2)) mask |= CLIFF_3;
if (CliffSensor_IsCliff(3)) mask |= CLIFF_4;
return mask;
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
if (mask == 0) {
LED_Toggle(&LED0);
LED_Toggle(&LED1);
LED_Toggle(&LED2);
LED_Toggle(&LED3);
HAL_Delay(200);
} else {
if (mask & CLIFF_1) LED_On(&LED0);
if (mask & CLIFF_2) LED_On(&LED1);
if (mask & CLIFF_3) LED_On(&LED2);
if (mask & CLIFF_4) LED_On(&LED3);
}

3.8 碰撞传感器

机器人左前方右前方各有一个碰撞传感器,为数字器件,采用与按钮类似的逻辑编写即可,此处和悬崖一样使用了掩码方式来进行统一的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool CrashSensor_Left(void){
// 微动开关闭合时将GPIO拉低,读取GPIO_PIN_RESET表示碰撞
return (HAL_GPIO_ReadPin(CRASH_L_GPIO_Port,CRASH_L_Pin)==GPIO_PIN_RESET);
}

bool CrashSensor_Right(void){
return (HAL_GPIO_ReadPin(CRASH_R_GPIO_Port,CRASH_R_Pin)==GPIO_PIN_RESET);
}

uint8_t CrashSensor_GetStatus(void){
uint8_t status = 0;
if (CrashSensor_Left())
status |= (1U << 0);
if (CrashSensor_Right())
status |= (1U << 1);
return status;
}

3.9 电压读取

如下图所示,R124和R128阻值分别为18kΩ和1kΩ,根据分压原理,电池电压为连接ADC处的19倍,据此我们只需要用ADC读取该处模拟值还原为电压值再乘19就可以得到电池电压。此处ADC也为ADC1,与悬崖传感器一同读取。用于读取电压值的回调函数如下:

1
2
3
4
5
6
7
8
9
10
float BT15V_GetVoltage(void){
// BT15V通道采样值(最后一个)
uint16_t raw = adc_values[CLIFF_SENSOR_COUNT];
float v_adc = ((float)raw) * ADC_REF_VOLTAGE / ADC_RESOLUTION;
float v_battery = v_adc * DIVIDER_RATIO;
float battery = (v_battery - MIN_V) / V_Range * 100;
if (battery > 100)
return 100;
return (v_battery - MIN_V) / V_Range * 100;
}

3.10 本章小结

至此完成了单片机所需调用的所有外设的基础配置工作,并已经过基本的功能验证测试,确认各外设配置与调用均正常。负责嵌入式开发的成员将上述内容整合成使用文档,提供给负责算法的成员进行控制算法等的二次开发工作。此外,为方便版本管理,将嵌入式程序推至远程Github仓库以便于远程协同工作,仓库地址:PyConqueror-16/Sweeping

4 上位机监控系统开发

4.1 系统架构

4.1.1 模块化设计思路

本系统采用”核心-功能-接口”三级模块化架构(如图1所示),各模块通过标准化接口进行通信。

具体设计原则如下:

  1. 功能解耦设计
  • ControlProtocol:模块独立处理指令编码,不依赖具体通信方式;

  • KeyboardController:将输入事件转换为标准速度指令;

  • BluetoothProtocol:输出结构化数据,界面模块负责可视化;

  1. 接口标准化
  • 控制协议接口

    1
    2
    class ContrilProtocol:
    def create_command(mode: int, speed_left: int, speed_right: int) -\> bytes:
  • 键盘控制接口

    1
    2
    class KeyboardController(QObject):
    def eventFilter(self, obj: QObject, event, QEvent) -\> bool:
  • 数据解析接口

    1
    2
    class BluetoothProtocol:	
    def parse_data(self, raw_data: bytes) -\> List[Dict]:
  • 串口通信接口

    1
    2
    class SerialThread(QThread):
    def send_command(self, command: bytes):
  1. 状态管理机制
  • 采用”发布-订阅”模式处理模块间状态同步;

  • 关键状态变更通过PyQt信号通知相关模块;

  • 共享数据通过队列实现线程安全访问。

  1. 异常隔离设计
  • 通信异常限制在SerialThread模块内处理;

  • 界面卡顿不影响后台数据处理;

  • 协议解析错误自动触发数据重传机制。

4.1.2 主要功能组件划分

系统功能组件及其相互关系:

表4.1 系统功能组件及其相互关系

组件类型 核心组件 功能描述 依赖关系
控制核心 ControlCenter 协调各模块运行,维护系统状态机 依赖所有功能组件
通信链 SerialThread 管理串口连接,实现数据收发多线程处理 →BluetoothProtocol
BluetoothProtocol 解析原始数据帧,校验数据完整性 ← SerialThread
人机交互 KeyboardController 将键盘事件转换为标准控制指令 → ControlProtocol
GUIComponents 提供速度调节、模式切换等交互控件 → ControlCenter
数据处理 DataProcessor 提供速度调节、模式切换等交互控件 ←BluetoothProtocol
可视化 TrajectoryVisualizer 实时绘制机器人轨迹和姿态 ← DataProcessor
StatusDashboard 显示速度、电量等实时参数 ← DataProcessor
高级功能 PathPlanner 实现牛耕式覆盖路径规划算法 → ControlProtocol
DataRecorder 支持轨迹记录与回放功能 ← DataProcessor

图4.1 组件交互图

4.1.3 数据流与控制流分析

表4.2 通信数据流一览表

数据类型 方向 频率 数据量 处理方式
控制指令 上位→下位 5-10Hz 6Byte 立即发送
状态数据 下位→上位 10Hz 14Byte 队列缓冲+定时处理
轨迹坐标 内部传输 5Hz 12Byte 环形缓冲区存储
配置参数 双向 事件触发 ≤1KB JSON序列化

实时控制流(周期≤100ms)为:

  1. 键盘输入 → KeyboardController生成标准化速度值(-100~100)

  2. ControlProtocol将速度值编码为机器指令(0xAA 0x01…)

  3. SerialThread通过串口发送指令

  4. 下位机响应指令并返回状态数据

  5. BluetoothProtocol校验解析数据帧

  6. DataProcessor进行数据平滑处理

  7. GUI线程定时获取处理后的数据更新界面

此外,我们还针对多线程任务进行了线程安全设计,采用”生产者-消费者”模式处理串口数据,并使用QTimer实现跨线程定时触发。其中的共享资源通过QMutex进行保护。

4.2 技术选型

4.2.1 开发框架:Python+PyQt5

本系统选用Python 3.8 + PyQt5作为基础技术栈,主要基于以下考量:

  • Python原型能够快速开发,具有很强的开发效率优势;

  • Python动态特性支持实时调试,界面修改可热更新;

  • Python具有丰富的标准库,os/sys/json等模块简化文件操作和数据处理;

  • PyQt5有特有完善的GUI组件,提供超过620个可直接使用的类,对比Tkinter仅约150个;

  • PyQt5的信号槽机制能实现低耦合的模块通信;

  • PyQt5样式定制能力支持CSS语法美化界面。

4.2.2 Matplotlib用于数据可视化

采用FigureCanvasQTAgg后端实现与PyQt5无缝集成。优化后的轨迹绘制可处理10000+数据点无卡顿。

表4.3 Matplotlib功能实现方案与性能需求对应一览表

需求 Matplotlib实现方案 性能指标
机器人位姿显示 Arrow+Circle补丁组合 渲染耗时<15ms
历史轨迹覆盖 fill_between+透明度控制 支持5000点实时绘制
动态坐标轴 **set_xlim()**智能缩放 自适应刷新率

4.2.3 PySerial串口通信库

选用PySerial作为串口通信解决方案,主要基于其简洁高效的API设计以及稳定的通信性能。通过非阻塞读取和双缓冲机制优化,有效避免了数据堆积问题,配合定时发送控制策略(100ms间隔),在Windows平台上保持稳定表现。相较于Qt原生QSerialPort,PySerial虽然与Qt集成度稍逊,但其更简洁的异常处理机制和无需额外依赖的特性,使其成为本项目中平衡性能与开发效率的最佳选择,最终实现了与STM32下位机10Hz稳定通信、500ms内快速校正的优异表现。

4.3 用户界面设计

4.3.1 主界面布局

在主界面布局上,采用经典的”三明治”布局设计,如下图所示:

图4.2:整体界面结构

可以看到,界面中主要包含以下三块主要的功能区域:

  • 顶部控制区(设备连接组、控制设置组)

  • 中部数据显示区(左侧实时数据面板、右侧轨迹图)

  • 底部功能区(辅助控制按钮组、帮助信息栏)

4.3.2 交互设计

本项目用户GUI界面的交互设计主要体现在以下几个板块:

  • 控制面板交互:主要包含速度调节交互(滑块与数值标签联动)与设备连接(流程图如下图所示)

图4.3:设备连接流程

  • 数据可视化:

图4.4:轨迹可视化示意图

  • 状态反馈机制:

图4.5:状态反馈图

在评价交互的响应性能时,我们设定了以下指标,保证用户在界面交互过程中能够得到即时且准确的反馈:

表4.4 交互性能响应指标

操作类型 响应延迟 视觉反馈方式
按钮点击 <50ms 按钮下沉动画
滑块拖动 即时 数值标签实时更新
键盘控制 <30ms 轨迹即时更新
数据加载 <200ms 进度条+旋转等待图标

4.4 核心功能模块

4.4.1 通讯协议模块(ControlProtocol)

实现上下位机通讯时,我们采用帧通讯的方式实现即时通讯。其中,协议帧具有双极性速度编码(正/负方向独立处理),校验和简化算法(求和取低8位),固定6字节帧长保证传输效率等特点。

图4.6:协议帧结构示意图

4.4.2 键盘控制模块(KeyboardController)

在该模块中定义并实现了基本的按键与遥操作功能,并引入了防抖机制。

图4.7:按键控制模块

4.4.3 蓝牙协议解析(BluetoothProtocol)

图4.8:蓝牙协议解析器

4.4.4 串口通信线程(SerialThread)

图4.9:通信线程工作流程图

图4.10 串口通信

4.5 高级功能实现

4.5.1 轨迹记录与回放

图4.11 记录回放系统架构

图4.12 回放JSON数据示例

数据中包含:记录动作创建时间,传送指令总量,总持续时间,控制模式;时间,指令间时间差,指令,模式。

4.5.2 地图数据管理

JSON文件具有加载速度快、压缩率高、可读性强的特点,所以地图存储同样使用JSON文件的不同格式来保存地图信息。

图4.13:加载地图控制面板UI及效果

4.5.3 路径规划功能

此处仅展示路径规划算法可视化效果,具体算法细节在第五章详细阐述。

图4.14:路径规划效果

4.6 本章小结

本章详细介绍了上位机监测与控制系统的设计流程,从整体架构到功能实现效果,有效实现了上下位机间的相互通信,并能成功进行遥控功能与数据可视化。界面交互设计上充分考虑人因学因素,布局合理,简单易用,能够方便用户实现对机器人即时而准确的移动控制与状态监测。

5 控制逻辑与算法设计

5.1 下位机整体底盘控制逻辑

根据需求分析部分提出的设计需求与性能指标,在下位机的底盘控制上主要分为如下几个控制功能逻辑模块:

  • 模式指令读取/基于按键切换

  • 基于编码器获取实时电机转速并利用PID控制器调节电机转速

  • 机器人实时位姿解算与惯导定位

  • 针对碰撞/悬崖等传感器感知数据做出实时响应

这些模块间的交互关系如下面的逻辑框图所示。接下来将依次介绍各模块的实现细节。

图5.1:下位机整体底盘控制逻辑

接下来将依次介绍各模块的实现细节。

5.1.1 开/关机按键与模式指令接受逻辑设计

在开关机按键与模式切换逻辑设计上,我们采用了如下图所示的框图逻辑,充分利用电路板上预留的两个按键,实现机器人的开关机:

图5.2:模式切换逻辑

扫地机器人一共具有三种状态模式,模式代码及其对应功能如下:

  • 0 ROBOT_OFF:关机状态,位姿和里程均重置为0

  • 1 REMOTE_CONTROL:遥控模式,左右轮速度通过上位机指令获取

  • 2 AUTO_MAPPING:自主建图模式,按设定速度(左轮15cm/s,右轮3cm/s)结合碰撞传感器实现贴墙行进

其中,模式1和模式2由接收到的上位机数据包经函数解析后得到的模式码决定,上位机GUI中可自由切换。同时,为便于用户日常使用,我们将PCB电路板上预留的两个按钮KEY0与KEY1使能,通过上述逻辑使得:按下KEY0后为关机状态,此时置为模式0且不会接收上位机发送的模式信息;按下KEY1后为开机状态,此时开始接收上位机发送的模式信息,并按照上位机指令进行后续运动。

该模块实现的主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 更新按键状态
Key_Update(&key[0]); // 检测KEY0:按下关机
Key_Update(&key[1]); // 检测KEY1:按下开机

// 模式切换
if (Key_GetState(&key[0]) == KEY_STATE_JUST_PRESSED){
robot_mode = ROBOT_OFF;
start_get_mode = 0;
current_mode = 0;
}

if (Key_GetState(&key[1]) == KEY_STATE_JUST_PRESSED)
start_get_mode = 1;

if (start_get_mode)
robot_mode = HC05_GetControlMode();

其中调用了用于重置机器人状态的函数ResetRobotState,在关机时会运行该函数,重置当前存储的偏角、里程等数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 重置机器人状态
void ResetRobotState(float* x, float* y, float* distance_all, uint32_t*round, int crash_flag) {
imu = JY901S_GetData();
*x = *y = *distance_all = 0;
*round = 0;
A4950_SetLeft(0);
A4950_SetRight(0);
target_speed_left = 0;
target_speed_right = 0;
crash_flag = 0;
cliff_flag = 0;
memory_deg = imu->yaw;
yaw_deg = imu->yaw - memory_deg;
}

5.1.2 基于编码器的电机转速PID控制算法设计

针对电机转速的精准控制,我们选择采用PID控制器,基于经编码器解析而来的实时转速信息实现反馈调节,使得电机转速能够在较短的时间响应达到设定的速度。

在电机转速的获取上,我们基于编码器的读取数据进行解算,换算关系如下:

查阅电机的产品手册,可以获得该电机与编码器对应的相关参数:

  • 减速比𝑀𝑂𝑇𝑂𝑅_𝑅𝐸𝐷𝑈𝐶𝑇𝐼𝑂𝑁_𝑅𝐴𝑇𝐼𝑂 = 1 : 63

  • 每转过一圈的脉冲数𝑃𝑈𝐿𝑆𝐸_𝑃𝐸𝑅_𝑅𝑂𝑈𝑁𝐷 = 20

  • 单位时间𝑡𝑖𝑚𝑒 = 50ms

  • 轮子周长CIRCLES_OF_TIRE = 34 * 2 * 3.14 mm

图5.3:项目中使用的电机与编码器

基于上述公式,编写以下函数代码,用于计算电机实时转速:

1
2
3
4
5
6
7
float getLeftSpeed(Encoder_HandleTypeDef *enc){
leftCount = leftCountGlobal;
leftCountGlobal = Encoder_GetCount(enc);
leftCount -= leftCountGlobal;
leftSpeed = leftCount / time / PULSE_PRE_ROUND / MOTOR_REDUCTION_RATIO * CIRCLES_OF_TIRE / 10; // cm/s
return leftSpeed;
}
1
2
3
4
5
6
7
float getRightSpeed(Encoder_HandleTypeDef *enc){
rightCount = rightCountGlobal;
rightCountGlobal = - Encoder_GetCount(enc);
rightCount -= rightCountGlobal;
rightSpeed = rightCount/ time / PULSE_PRE_ROUND / MOTOR_REDUCTION_RATIO * CIRCLES_OF_TIRE / 10; // cm/s
return rightSpeed;
}

在电机转速控制器上,我们选择最为经典的PID控制器用于实现电机转速的实时反馈调节,PID控制器主要具有以下几个特点:

  • 为系统指定一个目标值;

  • PID将目标值与被控对象当前的反馈量作差得到误差;

  • PID将误差值分别经过三个环节计算得到输出分量,再将三个分量加起来得到PID的输出;

  • 将PID的输出施加到被控对象上,使反馈量向目标值靠拢。

图5.4:PID控制算法流程框图与对应数学公式

图5.4:PID控制算法流程框图与对应数学公式

基于上述PID算法原理,我们将其封装成了如下的函数,实现对指定转速的快速动态响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void PID_Control_Left(float target_speed_left){
error_left = target_speed_left - leftSpeed;
integral_left += error_left;
if (integral_left > 99.0f)
integral_left = 99.0f;
else if (integral_left < - 99.0f)
integral_left = -99.0f;
derivative_left = error_left - previous_error_left;
pid_output_left = kp_left * error_left + ki_left * integral_left + kd_left * derivative_left;
if (pid_output_left > 99.0f)
pid_output_left = 99.0f;
else if (pid_output_left < - 99.0f)
pid_output_left = -99.0f;
A4950_SetLeft(pid_output_left * 7); //驱动电机
previous_error_left = error_left;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void PID_Control_Right(float target_speed_right){
error_right = target_speed_right - rightSpeed;
integral_right += error_right;
if (integral_right > 99.0f)
integral_right = 99.0f;
else if (integral_right < - 99.0f)
integral_right = -99.0f;
derivative_right = error_right - previous_error_right;
pid_output_right = kp_right * error_right + ki_right * integral_right + kd_right * derivative_right;
if (pid_output_right > 99.0f)
pid_output_right = 99.0f;
else if (pid_output_right < - 99.0f)
pid_output_right = -99.0f;
A4950_SetRight(pid_output_right * 7); //驱动电机
previous_error_right = error_right;
}

在main函数中,我们可以根据接收到的上位机速度指令,直接调用上述函数读取实时转速并利用PID控制器进行速度调节:

1
2
3
4
5
// 运动控制
left_speed = getLeftSpeed(&encL);
right_speed = getRightSpeed(&encR);
PID_Control_Left(target_speed_left);
PID_Control_Right(target_speed_right);

其中,经过实机测试与调整,我们最终采用的PID控制器参数为:KP=0.1,KI=0.1,KD=0.001。经测试,该组参数在目标电机转速为5至15cm/s时具有高灵敏度与高精度的动态响应特性。

5.1.3 机器人实时位姿解算与惯导定位

在机器人的位姿解算上,我们采用了综合电机实时转速与IMU惯导模块的方式,通过电机转速计算单个时间步内的分段里程(根据时间步长可以累加得到总里程),同时结合IMU直接读取到当前机器人的面朝偏角(启动时标志为0,范围为-180°~180°),可以得到单个时间步内机器人的近似位移,进而计算出机器人的实时位置坐标(相对于摁下开机键后的起始点而言,起始点坐标(0,0),面朝方向为y轴正方向)。

在实际计算与调试时,为降低调速过程中的距离计算误差,对达到指定速度前后的距离计算进行不同倍率的调整,以尽可能保证距离计算的精准度。

基于以上思想,编写如下代码实现机器人的实时位姿解算与惯导定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 位姿计算(融合IMU+编码器)
imu = JY901S_GetData();
if (rounds == 10)
memory_deg = imu->yaw;
yaw_deg = imu->yaw - memory_deg;

if (left_speed < target_speed_left - 1.0f) // 调速过程优化
distance = (left_speed + right_speed) / 2 * ROUND_TIME / 1000.0f;
else // 通过轮速获取位移
distance = ((left_speed + right_speed) / 2 * 1.25) * ROUND_TIME / 1000.0f;
if (target_speed_left == -target_speed_right)
distance = 0;

x += distance * sin(-yaw_deg * M_PI / 180.0f);
y += distance * cos(-yaw_deg * M_PI / 180.0f);
distance_all += fabs(distance);

后续可将实时总里程、位置坐标与偏角等数据发送至上位机,进行进一步的建图可视化处理。

5.1.4 碰撞/悬崖传感器感知数据处理逻辑设计

在碰撞与悬崖传感器反馈的感知数据的处理上,我们设计了如下图所示的框图逻辑(以碰撞为例,逻辑上设置碰撞传感器的处理优先级高于悬崖,事实上两者往往互斥),让机器人实现贴墙/悬崖行进,以尽可能地清扫到所有的清洁死角:

  • 遥控模式下,为避免机器人进入视野盲区(如床底等区域)而产生误操作,设定该模式下机器人遇到障碍物时(即碰撞传感器触发)先后退再原地掉头。

  • 自主运动(建图)模式下,为使得机器人尽可能稳定地贴墙行进,针对两侧碰撞传感器共三种不同的触发状态,分别设置了不同的转角提供给turning_left函数执行左转,并在达到指定转角后重置碰撞状态。

图5.5:碰撞传感器感知数据处理逻辑设计

显然,处于保护机器人自身安全的原则,其对于传感器的感知数据做出的任何反应的优先级均应高于上位机的控制命令。

为对于不同的碰撞情形进行区分,我们对碰撞状态码进行了区分,并设定了机器人在不同碰撞状态下应该做出的反应,以尽可能完成贴墙行走的任务:

  • crash_status = 1 :左侧碰撞 —— 左转60°

  • crash_status = 3 :两侧均碰撞 —— 左转40°

  • crash_status = 2 :右侧碰撞 —— 持续左转直至不再碰撞(直行)

  • crash_status = 0 :无碰撞,按照上位机发送速度或设定速度继续行进

针对悬崖传感器,其感知数据的处理逻辑与碰撞类似,均为先后退再原地掉头(偏转一定角度后继续行进)。不同的是,由于悬崖传感器本质上是红外传感器,因此我们需要事先测定其阈值(由于不同传感器的性能差异,其阈值也有所不同)为{245,145,190,130}。本项目使用的扫地机器人硬件设备上共有四个悬崖传感器,其中任一触发时(大于阈值)其对应的二进制掩码cliff_mask均不为0,此时可认为机器人已到达悬崖边缘,需要做出对应的响应。

针对遥控模式,处理碰撞与悬崖传感器感知数据的代码如下:

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
// 优先处理碰撞
if (crash_status == 0)
if (crash_flag == 1)
turning_left(175);
else {
imu = JY901S_GetData();
if (crash_flag == 0) {
turn_start_deg = imu-\>yaw;
target_speed_left = -5;
target_speed_right = -5;
crash_flag = 1;
}
}

// 其次处理悬崖
if (cliff_mask == 0)
if (cliff_flag == 1)
turning_left(30);
else {
imu = JY901S_GetData();
if (cliff_flag == 0) {
turn_start_deg = imu-\>yaw;
A4950_SetLeft(0);
A4950_SetRight(0);
target_speed_left = -5;
target_speed_right = -5;
cliff_flag = 1;
}
}

针对自主运动(建图)模式,处理碰撞与悬崖传感器感知数据的代码如下:

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
// 优先处理碰撞
if (crash_status == 0){
if (crash_flag == 0){
target_speed_left = 15.0f;
target_speed_right = 3.0f;
}
else if (crash_flag == 1)
turning_left(60);
else if (crash_flag == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
crash_flag = 0;
}
else if (crash_flag == 3)
turning_left(40);
}
else{
imu = JY901S_GetData();
if (crash_flag == 0){
turn_start_deg = imu->yaw;
if (crash_status == 1)
crash_flag = 1;
else if (crash_status == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
}
else if (crash_status == 3)
crash_flag = 3;
}
else if (crash_flag == 1)
turning_left(60);
else if (crash_flag == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
crash_flag = 0;
}
else if (crash_flag == 3)
turning_left(40);
}

// 其次处理悬崖
if (cliff_mask != 0) {
target_speed_left = -5;
target_speed_right = -5;
}

5.2 全覆盖路径规划算法设计:牛耕式扫描Boustrophedon

针对扫地这一具体应用场景,希望尽可能不放过区域内的每一个清洁死角,这实际是一个全覆盖路径规划(Complete Coverage Path Planning, CCPP)任务,需要确定一条路径,在避开障碍物的情况下通过该区域范围内的所有点。

我们选择采用离线式的牛耕式扫描法,基于静态环境信息,通过不断地覆盖分割后的子区域实现整个区域的全覆盖。牛耕式扫描(Boustrophedon Coverage)是一种高效的全覆盖路径规划算法,其核心思想是模拟农耕时的犁地模式,通过往复式运动实现无遗漏覆盖。算法首先将工作区域划分为若干条带,条带宽度w由机器人清扫半径(经测量机器人机身直径为32cm)决定,相邻条带间距通常取d≤w以确保覆盖连续性。机器人沿初始方向(y轴)移动,到达边界后垂直偏移d并反向移动,形成如下的路径模式:
$$
\begin{cases}x_{k+1}=x_k+(-1)^k\cdot L\y_{k+1}=y_k+d&\end{cases}
$$
其中L为条带长度,k为往返计数。当遇到障碍物时,算法会将区域分解为若干子多边形,在每个子区域内独立执行牛耕式扫描。该方法的覆盖率理论上可达100%,其路径总长度可表示为:
$$
D\approx\frac{A}{w}\cdot(L+2t)
$$
式中A为区域面积,t为转向损耗距离。

实际应用中,该算法常结合SLAM技术动态调整w,并通过匈牙利算法优化子区域访问顺序以提升效率。这种平衡了覆盖率和能耗的特性,使其成为扫地机器人等清洁设备的首选方案。

图5.6 牛耕式扫描全覆盖路径规划算法效果示意图

在建图过程中,由于没有采用激光雷达(电路板上未预留对应接口),我们通过机器人自主巡航模式下的贴墙碰撞,实现对封闭区域边界的建图。

基于以上算法原理,编写如下Python函数,根据建图过程中采集到的边界点云数据,调用牛耕式扫描算法规划全覆盖路径与关键节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def generate_coverage_path(x_coords, y_coords, robot_width):
# 生成覆盖路径:矩形区域 + Y方向牛耕式扫描,从右下角开始
min_x, max_x = min(x_coords), max(x_coords)
min_y, max_y = min(y_coords), max(y_coords)
path = []
path.append((max_x, min_y)) # 起点
path.append((max_x, max_y)) # 起点后的第一个点,先沿y轴正方向直行到底
# 计算条带位置(沿X方向从右往左移动),排除已经走过的最右侧条带
num_strips = int((max_x - min_x) / robot_width) + 1
x_strips = np.linspace(max_x, min_x, num_strips)
# 第一个条带(x = max_x - robot_width/2)已经部分覆盖,直接跳过第一条带
for i, x in enumerate(x_strips[1:], start=1):
if i % 2 == 1: # 奇数条带:从上到下走
path.append((x, max_y))
path.append((x, min_y))
else: # 偶数条带:从下到上走
path.append((x, min_y))
path.append((x, max_y))
path.append((max_x, min_y)) # 回到起点
return path

基于这些关键节点,可以进一步生成各时间步的左右轮速度:

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
def calculate_wheel_velocities(x_path, y_path, robot_diameter, dt):
# 计算左右轮速度和时间步
v_left, v_right = [], []
time_steps = []
straight_speed = 10 # cm/s
turn_speed = 5 # cm/s
current_time = 0
for i in range(1, len(x_path)):
dx = x_path[i] - x_path[i-1]
dy = y_path[i] - y_path[i-1]
if dx != 0 and dy == 0:
dist = dx
elif dy != 0 and dx == 0:
dist = dy
duration = dist / straight_speed
num_steps = int(duration / dt) + 1 # 计算该段的时间步数
if i == 1 or i == len(x_path)-1: # 开始或结束段 直行
vl = straight_speed
vr = straight_speed
elif i % 2 == 0: # 转弯段
# 左右轮速度相反
vl = -turn_speed
vr = turn_speed
else: # 直行段
direction = 1 if dy >= 0 else -1 # 判断方向
vl = direction * straight_speed
vr = direction * straight_speed
# 为该段添加速度值和时间步
for _ in range(num_steps):
v_left.append(vl)
v_right.append(vr)
time_steps.append(current_time)
current_time += dt
return v_left, v_right, time_steps

以上所有的计算均在上位机完成,将这些速度信息按照时间步顺序依次发送至下位机,从而可以实现规划路径的运动控制。由于建图时仅扫描边界,下位机实际控制时仍然需要结合传感器反馈做出响应。

5.3 本章小结

本章主要介绍了机器人下位机整体底盘控制的基本逻辑框架,并对其中主要的功能模块实现细节进行介绍。该部分为基于已配置好各种外设的单片机工程的二次开发,由负责运动控制算法开发的成员从负责嵌入式的成员建立的Github仓库中Fork出来并在Algorithm分支中进行后续算法部分的开发更新,仓库地址:微电路设计课程项目

除此之外,本章还介绍了我们采用的全覆盖路径规划算法——牛耕式扫描算法的基本原理与部分实现细节,这也与扫地机器人这一实际应用场景相契合。

6 项目实现效果与总结

演示视频链接:https://www.bilibili.com/video/BV1HkKNzDEjV

6.1 项目实现效果

项目最终圆满完成了第一章需求分析中给出的所有功能设计需求,并基本达到了设定的需求指标。下面将给出部分主要功能的测试结果。

6.1.1 直线行驶精度测试

结合高精度的位姿解算与电机转速调节,我们可以对扫地机器人的运动实现高精度的控制。下图为扫地机器人直线行驶固定距离100cm的精度测试情况:

图6.1:机器人行进固定距离100cm精度测试

可以看到,在该测试中无论是距离精度还是行驶偏角均满足设定的性能需求(偏角<5°,距离偏差<2cm),这一方面说明PID控制器对于电机转速的控制具有良好的动态响应特性,另一方面也说明了位姿解算补偿算法的高精度。

6.1.2 指定转角精度测试

图6.2:机器人原地旋转固定角度90°精度测试

为便于应对碰撞/悬崖等突发情况,编写了固定转角旋转函数以根据不同情形做出灵活反应:

void turning_left(float CONTROL_DEG){

1
2
3
4
5
6
7
8
9
10
11
void turning_left(float CONTROL_DEG){
target_speed_left = -5.0f;
target_speed_right = 5.0f;
imu = JY901S_GetData();
if ((imu->yaw + fabs(imu->gy) > (CONTROL_DEG + turn_start_deg)) ||((imu->yaw + fabs(imu->gy) > (turn_start_deg + CONTROL_DEG - 360)) && (imu->yaw + fabs(imu->gy) < (CONTROL_DEG - 180)) && (turn_start_deg > (180 -CONTROL_DEG)))){
crash_flag = 0;
cliff_flag = 0;
target_speed_left = 0;
target_speed_right = 0;
}
}

其中,基于当前角度制,根据几何关系指定如下规则,判定机器人是否转过指定角度(需记录开始旋转时偏角turn_start_deg)【满足以下任一条即可】:

  • 当前偏角 > 起始偏角 + 指定转角

  • 起始偏角 > 180° - 指定转角 且 当前偏角 > 起始偏角 + 指定转角 – 360° 且 当前偏角 < 指定转角 – 180°

为提高旋转角度精度,考虑响应滞后性与运动惯性等问题,还在角度判标中加入转动加速度gy。

调用该函数进行测试,结果如上图所示。可以看到,在设定旋转角度为90°的情况下,机器人转过了91.7°,与实际设定角度相差小于2°,说明在基于IMU的偏角位姿结算上也具有较高的精度。当然,若增大原地旋转时的左右轮速度,该偏差值会由于惯性而有所增大,因此后续优化时可以考虑进一步采用高阶的转动加速度函数对转角控制进行修正。

6.1.3 贴墙行进与建图效果测试

图6.3:机器人贴墙行进与建图效果测试(完整过程见演示视频)

可以看到,在自制的约150cm*100cm的封闭多边形区域内,机器人能够很好地完成贴墙行进的任务,同时在上位机客户端的建图精度也较高,基本还原了整个封闭区域的地形轮廓。

6.1.4 全覆盖路径规划效果测试

图6.4:全覆盖路径规划效果演示

基于上述贴墙建图的测试结果,将路径点云数据以JSON文件形式导出存储。在路径规划测试时,将点云数据再次导入至客户端,并点击“路径规划”,效果如上图所示。可以看到,生成的路径与算法原理相一致。在实机部署实现的过程中,结合路径规划的关键点与实时的碰撞感知数据,机器人能够良好地扫过地图内的所有区域并避开障碍物,这进一步说明了路径规划与运动控制算法的有效性。

6.2 总结与收获

在本次微电路设计课程项目中,我们组的四位成员合理分工、通力合作,成功完成了SmartRobot扫地机器人的设计与开发工作。从项目需求分析、PCB电路设计、硬件外设程序设计,到上位机监控系统开发、控制逻辑与算法设计,再到最终的测试与优化,每一个环节都凝聚了团队成员的心血与智慧。通过复刻和优化现有扫地机器人的硬件方案,我们深入研究了其核心功能模块,包括运动控制、传感器数据处理、路径规划算法等,并结合实际应用场景进行了针对性改进。

在项目过程中,我们不仅掌握了PCB设计、嵌入式编程、传感器集成、电机控制等核心技术,还深刻体会到团队协作的重要性。通过分工合作,每位成员充分发挥了自己的专长,共同攻克了技术难题。例如,PCB设计中的电源管理优化、电机驱动选型,嵌入式程序中的PID控制算法实现,上位机系统的模块化设计与交互优化,以及全覆盖路径规划算法的开发与部署,都是团队智慧的结晶。

此外,项目还锻炼了我们的工程实践能力和问题解决能力。从理论到实践,我们学会了如何将课堂知识应用于实际项目,如何通过调试与测试不断优化系统性能。最终,扫地机器人实现了良好的避障功能、防坠落能力、高精度运动控制以及用户远程监控等设计目标,圆满完成了各项性能指标。

通过这次项目,我们不仅提升了专业技能,还增强了团队协作与沟通能力,为未来的工程实践与科研工作积累了宝贵经验。我们深刻认识到,技术创新需要扎实的理论基础、严谨的工程态度和高效的团队合作。这段经历将成为我们成长道路上难忘的一课,激励我们在未来的学习和工作中继续追求卓越。

6.3 本章小结

本章对于机器人功能与性能指标的测试结果进行了展示与分析,并对于项目成果与收获进行了总结。

心电信号采集与处理

1 实验需求分析

1.1 项目背景介绍

心电信号(Electrocardiogram, ECG)是反映心脏活动电生理变化的重要生物电信号,其特征包括心率、节律、波形等参数,能够直观反映心脏健康状况,在临床医学、健康监测和疾病预防中具有不可替代的作用。通过心电信号的测量与分析,可以检测心律失常、心肌缺血、心脏传导阻滞等异常,为心脏疾病的诊断和治疗提供关键支持;借助便携式和可穿戴设备,实时心电监测已成为健康管理的重要手段,为心血管疾病高危人群提供预警,有助于降低发病率和致死率。此外,心电信号还是生物医学研究的重要工具,为心血管药物开发、人工心脏研究等领域提供了基础数据。在当前人口老龄化加剧和心血管疾病高发的背景下,心电信号测量与分析技术显得尤为重要。本项目旨在开发高精度的心电信号采集系统,结合课程中介绍的数字信号处理等专业知识,为心脏健康提供更加便捷和智能的监测方案,推动精准医疗与个性化健康管理的发展。

1.2 心电信号特征与设计需求

心脏内部产生的一系列非常协调的电刺激脉冲,使得心脏肌肉细胞有节奏的舒张和收缩,这些信号传递到人体表面的不同部位形成不同的电位差。通过仪器设备可以从体表检测到这些微弱的电位差信号,称之为心电信号。换言之,心电信号即为人体心脏细胞细胞膜产生的电势差。在医学上,医生往往需要通过心率与幅值等参数来初步判断患者的健康状况,因此实现高精度的心率与幅值测量是本项目中设计的心电信号采集与处理系统的核心功能。

图1.1:心电信号简介

正常的心电信号频率范围为0.05Hz-100Hz,其能量集中在低频段,其中99%的能量集中在0Hz-35Hz。在其采集过程中容易受到各种干扰,主要分为三种:

  • 工频和工频的谐波频率干扰,工频频率在我国为50Hz;

  • 肌颤噪声和采样电路参考电压引入的电源纹波等高频噪声,频率通常在100Hz以上;

  • 呼吸基线漂移和采样引入的直流分量,频率一般分布在0-0.7Hz。

以上的各种干扰会对心电信号采集结果产生较大的影响,使得采集到的心电信号中出现许多杂波与噪声,这是我们所不希望看到的。因此,为提高心电信号的测量精度,需要设计相应的滤波器对传感器采集到的信号进行滤波,从而减小信号中的噪声震荡,提高心率与幅值测量的准确程度。特别的,由于参考电压受环境温度变化会产生一定的温漂,以及人的呼吸活动和电极滑动也导致基线漂移。这些干扰的频率很低,通常在几Hz以内,但和心电信号的有效频谱非常接近,因此需要过渡带较窄的IIR直流陷波器来消除干扰。

基于心电信号的以上特性,对于该心电信号采集与处理系统,提出如下的技术指标需求:

  • 0频处的缓变直流衰减不低于30dB;

  • 降噪滤波器以35Hz为3dB通带截止频率,过渡带不超过10Hz,阻带衰减不低于40dB;

  • 心率估算误差不超过10%。

2 实现方案论证

2.1 系统框架设计

本项目的核心目标是实现心电信号的采集与滤波以及心率测量,同时需要在屏幕上绘制时域波形与频谱图。具体而言,细分的功能如下:

  • 实现ADS1292获取心电信号原始数据,并通过串口传输至PC电脑;

  • 实现PC电脑中通过MATLAB对原始数据进行时域和频域分析;

  • 实现PC电脑中通过MATLAB对原始数据进行降噪和提取心率;

  • 实现STM32单片机中对原始数据进行降噪和提取心率;

  • TFT屏幕中绘制心电信号曲线和显示心率数值。

为实现以上功能,采用如下的系统设计流程:

  1. 调试ADS1292R_PowerOnInit函数中的ADS1292芯片读取,通过读取芯片device_id验证硬件功能正常且连接正确;

  2. 在中断驱动下,读取ADS1292的原始数据,并存储在单片机的存储器中;

  3. 把原始数据传输到PC;

  4. 在PC中分析原始数据的时域和频域;

  5. 在PC中设计滤波器对原始数据进行处理,并提取心率等;

  6. 把PC中的滤波器移植到单片机中;

  7. 在单片机中把心电波形和心率等数据显示到TFT屏幕。

图2.1:系统设计流程

根据如上设计流程,结合目前提供的材料,设计了如下图所示的心电信号采集与分析系统:

图2.2:系统框架

系统的工作流程如下:

  1. 首先,STM32控制器向心电传感器发送采集指令,传感器随后采集来自人体或模拟信号源的心电信号,并将数据反馈至控制器;

  2. 接着,控制器将采集到的数据传输至PC端,供进一步分析处理;

  3. 然后,根据PC端的分析结果,控制器会调整参数并优化心电信号处理;

  4. 最终,处理后的结果将在TFT屏幕上实时显示,供用户查看。

可以看到,该系统主要涉及到STM32主控芯片、ADS1292R传感器、TFT显示屏、心电信号模拟器以及PC端分析软件MATLAB等关键组件。接下来将对于本项目涉及的各硬件组件进行介绍。

2.2 STM32主控芯片

本项目选用的微控制器STM32F407ZG是系统的核心控制单元,负责协调各个模块的工作。其不仅负责信号的采集,还管理信号传输、滤波器应用、以及与TFT屏幕的显示操作。其强大的处理能力和灵活的控制方式使其成为整个系统的”大脑”。该控制器目前搭载在”正点原子”探索者STM32F407开发板V3上,负责完成系统的信号采集、处理与传输任务。

图2.3:STM32F407探索者开发板V3实物图正面

该单片机具备高性能的ARM Cortex-M4内核,主频高达168MHz,同时集成了丰富的外设接口,包括多个ADC通道、DMA(直接存储器访问)、定时器以及USART串口等,为心电信号的实时采集、处理与传输提供了强有力的硬件支持。它通过SPI协议与ADS1292传感器进行数据交换,采集来自人体或模拟信号源的心电信号,并进行初步处理。

2.3 心电信号模拟器

在实验的过程中,无法总是以人体作为心电信号源,因此在缺乏人体数据的情况下,本项目采用SKX-2000心电信号模拟仪作为测试时的模拟信号源。它能够生成不同类型的心电波形,广泛用于测试系统性能和验证心电信号采集、处理的稳定性。此设备对确保系统在实际使用前达到预期的性能标准至关重要。

图2.4:SKX-2000心电信号模拟仪

图2.5:SKX-2000心电信号模拟仪使用说明书图2.5:SKX-2000心电信号模拟仪使用说明书

在本项目中,主要使用到的是正常的心电波形与标准的心率信号,可以通过操作该模拟仪控制其产生心电信号的心率以及幅度。在连接方面,如图2.3所示,采用三导联接法,RA连接右手(红色),LA连接左手(黄色),LL连接左脚(绿色);通过3.5耳机动态导联线,将模拟仪产生的心电信号输入ADS1292S传感器中以供进一步采集。

2.3 信号采集传感器:ADS1292R

ADS1292R作为一款多通道同步采样的24位模数转换器(ADC),被广泛应用于生物电势测量领域,其独特的设计集成了可编程增益放大器、内部基准源和板载振荡器,确保了心电信号的精准采集。在本系统中,ADS1292R作为信号采集模块的核心组件,负责从心电电极捕获原始心电信号。这些信号随后通过SPI接口传输至STM32微控制器,供后续处理和分析。

图2.6:ADS1292R传感器实物图与电路原理图
图2.6:ADS1292R传感器实物图与电路原理图该模块共有12个输出引脚(传感器模块右侧),各引脚接口功能说明如下:

  • GND:接供电电源地;

  • CLK:提供给ADS1292R工作的外部时钟,由于本项目采用内部时钟,故该引脚无需连接;

  • GPIO1、GPIO2:本项目中未使用,无需连接

  • SPI_SCK:接入由STM32单片机提供的SPI时钟信号;

  • SPI_MISO:向STM32单片机发送SPI数据输入信号;

  • SPI_MOSI:接收STM32单片机发出的数据输出信号;

  • SPI_CS0:接入STM32单片机GPIO,为ADS1292R提供片选选中信号;

  • ADS_DRDY:向STM32单片机发送外部中断输入,告知单片机可以通过SPI接收采集信号;

  • ADS_START:接入STM32单片机GPIO,为ADS1292R提供开始采集信号;

  • ADS_PWDN:接入STM32单片机GPIO,为ADS1292R提供复位信号RESET;

  • +5V:接供电电源+5V(由STM32输出)。

除此之外,由于选用的STM32单片机只允许接收0~3.3V的输出电压,故单片机通过SPI通信接收的信号电压必须限制在该范围内,因此需要将ADS1292R传感器模块中间的VDD与3.3V引脚通过跳线帽相连以实现电压转换(与图2.5中左图的连接方式相反)。

2.4 SPI通信读取采集数据

在本系统中,传感器将数字化后的原始心电信号数据通过SPI接口传输到STM32微控制器。SPI通信具有高速度、全双工传输的优势,非常适合用于实时性要求较高的应用场景。SPI接口确保了心电信号数据的快速、稳定传输,保证了信号的实时处理和准确性。通过SPI,ADS1292R和STM32微控制器能够高效地交换数据,从而提升系统的整体性能和响应速度。

SPI通信采用主从结构,其中STM32微控制器作为主设备,ADS1292R作为从设备。具体来说,SPI通信包括四个主要信号线:时钟信号(SCK)、主输出从输入(MOSI)、主输入从输出(MISO)和片选信号(CS)。时钟信号由STM32微控制器提供,用于同步数据传输。数据通过MOSI线从ADS1292R传输到STM32微控制器,而传感器通过MISO线将必要的反馈信息传回STM32。

图2.7:SPI内部结构简图

为了提高系统的响应速度和处理效率,在单片机端选择了基于中断驱动的数据采集机制。当ADS1292R传感器完成一组心电数据的采集后,通过中断信号通知STM32F407进行数据读取和处理。这种机制有效减少了CPU的空闲时间,提高了系统的整体效率。

图2.8:中断驱动SPI通信采集数据的系统框架简图

在单片机程序工程中,通过调用驱动库文件中的ADS1292_Read_Data函数,可以实现单片机与ADS1292R的数据交互以进行实时数据监测,并通过函数ADS1292_Send_CMD将信号数据通过UART串口以115200的波特率传输至PC端并保存。在此过程中,原始信号数据被保存为.dat格式的文件,后续将通过MATLAB读取和显示这些信号数据,并针对这些数据进行滤波器等模块的设计,对信号进行进一步验证与调试。

图2.9:单片机实现读取原始信号数据的函数ADS1292_Read_Data

图2.10:单片机实现串口输出原始信号数据的函数ADS1292_Send_CMD

图2.11:PC端可视化软件中显示采集到的原始心电信号(解码后)

3 理论推导与MATLAB计算

PC端分析软件(本项目中使用MATLAB)用于对采集到的原始心电信号进行深入分析。为验证滤波器对心电信号的作用和效果,可以先在MATLAB上设计算法对心电信号进行一系列处理,包括数据读取、绘制原始数据时域波形、分析原始数据的频谱、设计数字直流陷波器、设计FIR数字低通滤波器、分析并比较滤波前后数据的频谱以及估算心率。通过这些分析,系统能够设计出合适的滤波器以去除噪声并提取出心率等重要参数,为后续移植到单片机上提供理论验证。

3.1 原始心电信号数据的读取与频谱分析

为了研究信号特征和评估处理效果,首先需要导入心电信号数据并进行基本分析。当心电信号传入单片机后,根据ADS1292R的数据手册,还需在单片机中对数据进行解码处理。在2.4节中,直接存入.dat文件的是未经过解码的原始数据,而图2.11展示的则是经过解码转换后的波形数据可视化结果(由单片机内部程序完成解码转换,将在4.2节中详细展开)。

图3.1:MATLAB从串口读取信号数据并写入.dat文件的程序代码

本章中的MATLAB分析均基于未解码的.dat文件,因此要想获得真实的波形数据(电压值),需要先根据以下规则对数据进行解码(4.2中的解码基于同样的原理,后续不再赘述):

由于原始数据格式是24bits的二进制补码(7FFFFF对应的模拟信号是2.4V。800000对应的模拟信号是-2.4V),若考虑保留高16bits,需要注意此时为补码,同时也要考虑到16bits相比于24bits相当于右移了8位。所以此时7FFF的数值对应模拟信号相当于2.4V,8000对应模拟信号-2.4V。

图3.2:原始信号电压与读取信号数据间的关系

基于这样的规则,可以在MATLAB编写对应的接收数据代码,从文件中读取数据(注意把负数的二进制补码转换为原码),其中Off可以为0或1(因为MATLAB接收的起始数据可能是16bits的高8位或者低8位,用off作为偏移调整,这样组合的数据才不会出现错位);同时通过plot函数绘制出原始心电信号的时域波形,并使用快速傅里叶变换(FFT)分析信号频谱,绘制出频谱图。

图3.3:原始心电信号数据解码读取、时域波形绘制与频谱分析绘制MATLAB代码

图3.4:原始心电信号时域波形与频谱图3.4:原始心电信号时域波形与频谱

从频谱图(上右图)中可以明显观察到信号中的低频基线漂移和工频干扰(与1.2节中的分析基本一致),这些成分需要通过滤波器加以去除。

3.2 IIR数字直流陷波器设计与滤波效果

工程中使用高精度传感器采集动态信号,采集的原始数据会因为环境变化、量化字长和参考电压等因素,包含较强的缓变直流分量,如果不予以消除,会导致在降噪等处理中出现运算饱和溢出。由于在采集心电信号时,人的呼吸活动和电极滑动会导致基线漂移,而这些干扰的频率又与心电信号的有效频谱非常接近,因此需要设计窄带IIR直流陷波器来消除该干扰。

直流陷波器的传递函数Η(z)为:
$$
Η(z) = \frac{z - 1}{z - a}
$$
该数字系统的极点为z = a,零点为z=1,其中参数a决定了陷波器的过渡带宽和衰减性能,为接近1的正实数。因为零点对应的幅角ω = 0,所以数字系统在零频处的增益显著衰减(如图3.5所示),其衰减的程度与过渡带的宽度和极点a的数值有关:a越大衰减变小,同时过渡带变窄,反之则衰减增大和过渡带增宽。通过实验调整,选择a=0.992可在基线漂移和信号完整性之间取得平衡。

图3.5:a=0.992时直流陷波器H(z)的增益和相位特性

图3.6:IIR数字直流陷波器设计及滤波后频谱分析绘制MATLAB代码及其运行结果
图3.6:IIR数字直流陷波器设计及滤波后频谱分析绘制MATLAB代码及其运行结果

可以看到,在加入了直流陷波器进行滤波后,0频处的基线漂移基本被消除,说明该滤波器的设计有效并起到了很好的效果。

3.3 FIR数字低通滤波器设计与滤波效果

根据阻带衰减不低于40dB且过渡带不超过10Hz的要求,利用MATLAB中自带的filterDesigner滤波器设计工具对于FIR数字低通滤波器进行设计。在该滤波器中,采用固定窗口,通过对当前点及其前160个点的加权求和来计算输出(指定滤波器阶数为160)。

在固定窗口的选择上,由于不同窗函数的阻带最小衰减各不相同,过渡带应与对应窗函数的”精确过渡带宽”相等。以下是常用窗函数的对比表:

表3.1 不同窗函数的阻带最小衰减

窗函数 第一旁瓣衰减A/dB 近似过渡带宽Bw 精确过渡带宽 旁瓣峰值衰减/(dB * oct^(- 1))
矩形窗 -13 4π/N 1.8π/N 21
汉宁窗 -31 8π/N 6.2π/N 44
海明窗 -41 8π/N 6.6π/N 53
布莱克曼窗 -57 12π/N 11π/N 74
凯泽窗(β = 7.865) -57 10π/N 80

在实际设计中,选择Chebyshev(切比雪夫)窗,其主要特点为:在给定窗口长度的情况下,能够提供最小的主瓣宽度。因此,通过选用Chebyshev窗,在频域上最小化主瓣的振幅波动,以实现滤波器的设计。

除此之外,设计时还指定采样频率为500Hz,阻带频率设计为35Hz,确定好参数后可在filterDesigner工具中导出为MATLAB函数:

图3.7:基于filterDesigner工具的FIR数字低通滤波器设计及其生成的MATLAB代码图3.7:基于filterDesigner工具的FIR数字低通滤波器设计及其生成的MATLAB代码

在主程序中调用生成的滤波器函数构造一个FIR数字低通滤波器实例,并利用filter函数对于直流陷波后的波形数据进行FIR滤波并通过plot函数绘制滤波后的频谱,代码与结果如下所示:

图3.8:信号经FIR数字低通滤波后频谱分析绘制MATLAB代码及其运行结果图3.8:信号经FIR数字低通滤波后频谱分析绘制MATLAB代码及其运行结果

可以看到,在加入了数字低通滤波器进行滤波后,50Hz处的工频干扰完全被消除,说明该滤波器的设计有效并起到了很好的效果。

3.4 滤波结果综合分析

通过IIR和FIR滤波器的级联处理,最终获得了较为清晰的心电信号波形(如图3.9所示)。此过程有效去除了干扰,为后续心率计算和信号分析提供了可靠的基础。

图3.9 滤波后心电图结果图3.9 滤波后心电图结果

图3.10 信号频谱对比图图3.10 信号频谱对比图

为了更直观地展示信号处理过程的效果,对心电信号在各个处理阶段的频谱进行了对比分析。图3.10中显示了原始心电信号以及经过直流陷波器和FIR低通滤波器联合处理后的信号频谱图。

从原始心电信号频谱可以看出,信号中存在显著的工频干扰(约50Hz)和低频基线漂移(频率较低)。此外,信号还包含较多高频噪声,影响了心电信号的质量。经过直流陷波器处理后,低频基线漂移明显被抑制,低频成分显著减少但同时工频干扰和高频噪声依然存在,但其幅值未发生明显变化,表明直流陷波器主要对低频干扰起到作用;在应用FIR低通滤波器后,信号中50Hz附近的工频干扰被有效抑制,同时高频噪声也显著减弱。

最终处理后的频谱显示出清晰的心电信号主频成分(低于30Hz),为后续的R波检测与心率计算提供了可靠的基础。

3.5 心率计算

在对原始心电信号完成滤波处理后,可进一步估算心率这一关键生理参数。心率的计算基于心电信号中的R波峰值检测,通过分析相邻R波之间的R-R间期推算心率值。R波的检测是心率估算的关键环节。在此实验中,采用MATLAB函数findpeaks来检测心电信号中的峰值。为提高检测精度,将滤波后的信号进行缩放,并设置峰值检测的阈值为信号均值加一倍标准差。

检测到R波位置后,可通过计算相邻峰值位置之间的时间间隔(R-R间期)来估算心率。使用diff函数计算相邻R波的时间间隔并计算R-R间期的平均值,并根据如下公式计算出心率:

$$
心率 = \frac{60}{平均R - R间期}
$$
图3.11 MATLAB心率计算代码

最终得出结果为60.29bpm,对比实际信号发生仪60bmp的心率,可以较为准确地估算心率,其结果符合误差小于10%的预期。

4 STM32程序设计与参数选择

项目代码已推送至Github远程仓库:Asgard-Tim/ECG_Final_Project

4.1 单片机程序架构设计

为实现2.1节所提出的基本功能,除了要采集心电数据并对其进行滤波、显示时域波形外,还需要对滤波后的波形进行频谱分析并显示。为了更高效地完成这一过程,系统引入了快速傅里叶变换(FFT)算法,用于将时域信号转化为频域信号,便于分析信号的频谱特性。然而,由于FFT计算量较大,尤其是在较高采样率下,需要处理大量的点,因此为了加快运行速度,程序采用了FreeRTOS实时操作系统,分别对信号滤波和频谱分析进行任务划分和并行处理。

图4.1 单片机程序工程基本工作框架

在系统的实现中,FreeRTOS将任务分为信号采集与滤波任务ECG和FFT计算任务。如上图所示,信号采集任务负责从ADS1292R中读取原始心电信号,并完成IIR和FIR滤波的处理,将滤波后的数据存储到循环缓冲区中,通过中断驱动的方式与传感器交互,确保数据的实时性和可靠性;而FFT计算任务则专注于对缓冲区中的数据进行频谱分析,每当缓冲区填满指定数量的数据点时,FFT计算任务将被启动,这虽然会导致实际运行时FFT频谱更新会产生一定的延时,但这样的设计将使得整个系统的工作更加稳定可控。

通过引入FreeRTOS,系统充分利用了单片机的多任务并行能力,使信号滤波和频谱分析的运行效率大幅提升,同时保证了心电信号处理的实时性。滤波和频谱分析的结合,不仅提高了信号的可用性,还为后续的心率计算和心电异常检测提供了更加丰富和可靠的数据支持。

4.2 采集原始数据解码

通过SPI接口,STM32微控制器不断与ADS1292R传感器进行交互,读取其输出的24位数字信号数据。基于3.1节提到的价码规则,将读取到的数据经过必要的解码和处理后,可将其转换为实际的电压值,并进一步用于实时显示与分析。其核心代码如下所示:

图4.2 ADS1292R原始数据解码核心代码图4.2 ADS1292R原始数据解码核心代码

在具体实现中,通过将ADS1292R的24位原始数据解码为32位有符号整数,消除了符号位可能引起的误差。随后,将解码后的信号根据增益和参考电压转换为实际的电压值。这一过程中,左导联和右导联的连接状态也会被实时检测,以确保数据采集的可靠性。如果导联连接异常(如电极松动),系统会发出提示并暂停后续的采集。

4.3 处理算法移植:滤波器

经过STM32接收心电信号后,系统根据PC端设计好的滤波器方案对原始信号进行双重滤波处理,先通过IIR滤波器消除直流分量,然后利用FIR滤波器进一步去除高频噪声,从而得到更为清晰的心电信号。经过IIR和FIR滤波后的信号显著改善,直流漂移、工频干扰以及高频噪声均被有效抑制,这一改进不仅提高了信号的可分析性,也为后续的特征提取与心率计算打下了坚实基础。

滤波后,处理得到的心电信号被传输至PC端,并通过可视化工具显示其改进的波形。如下图所示,信号的基线漂移已完全消除,高频噪声也显著减弱。滤波后的心电信号为后续的心率估算和其他心电特征分析提供了高质量的数据输入,从而提升了系统的整体性能和实用性。

图4.3:PC端可视化软件中显示采集到的滤波后心电信号(解码后)

4.3.1 IIR数字直流陷波器

在IIR滤波阶段,STM32程序参考了PC端的滤波器设计,实现了高效的实时滤波。IIR滤波器的核心算法通过递归关系计算当前输出值,其中参数a=0.992控制直流分量的衰减程度。算法以较少的存储空间完成了对信号直流漂移的有效抑制,使用上一输入与上一输出值的递归关系更新当前输出值,从而达到实时滤波的目的。

图4.4:IIR数字直流陷波器单片机C语言代码

4.3.2 FIR数字低通滤波器

经过IIR滤波后,信号中的直流分量被有效去除,但仍可能含有工频干扰和其他高频噪声。因此,信号会进一步通过FIR滤波器进行处理。FIR滤波器利用一个固定长度的滑动队列实现,其滤波系数矩阵B由MATLAB中的filterDesigner工具生成并导出到C代码中。该滤波运算采用定点方式,在初始化时需要将滤波器系数乘以66536(如图4.5中右图所示),再取整保存为16bits整数。该部分的核心算法是,通过遍历队列数据与滤波器系数进行逐项乘积累加,实现对信号的精准滤波。代码设计充分考虑了队列操作的效率,通过队列循环的方式减少内存消耗并保证实时性。

图4.5:filterDesigner导出系数矩阵B的部分结果及FIR滤波器参数初始化
图4.5:filterDesigner导出系数矩阵B的部分结果及FIR滤波器参数初始化

图4.6:FIR数字低通滤波器核心算法单片机C语言代码

4.4 处理算法移植:心率与幅值计算

系统的心跳检测功能基于心电信号的峰谷变化,通过heartbeat_check函数实现对R波峰值的实时检测。该函数采用简单高效的阈值法,以波峰和波谷之间的差值判断是否发生心跳事件。函数的核心逻辑包括记录当前波形的上升和下降趋势,以及动态调整波峰(up_value)和波谷(down_value)的值。

具体来说,当检测到波形从下降趋势转为上升趋势,且波峰与波谷的差值超过设定的阈值(经过调试设置为0.55较为合适),即判定为一次心跳事件。此时,波峰和波谷会重新初始化,为下一次心跳检测做好准备。如果当前波形变化幅度未达到阈值,则认为是噪声或非心跳波形,函数返回0。

在实际应用中,该函数与DWT模块配合使用,每次心跳事件发生时记录时间戳,通过计算相邻两次事件的时间间隔(R-R间期)估算心率。高精度的DWT计数器和简洁的心跳检测算法相结合,使得系统能够在保证实时性的同时,准确识别心跳并计算心率。

图4.7:单次心跳检测函数heartbeat_check代码

图4.8:心率计算函数calc_heartbeat_rate代码(基于单次心跳的检测结果)

为衡量心率信号的幅值范围,编写函数 Cal_PeakToPeak 以计算一组浮点数数据的峰峰值,即数据中的最大值与最小值之间的差值,用以反映数据的动态范围或振幅。函数通过接收一个浮点数数组 samples 和数组中的元素个数 sample_count 作为输入,首先将数组的第一个元素初始化为当前的最大值和最小值,然后从第二个元素开始逐一遍历整个数组。在遍历过程中,函数逐步更新最大值 max 和最小值 min,确保能够捕获数据中的真实极值。遍历完成后,函数通过计算 max - min 得到峰峰值,并将结果作为返回值。整个过程采用单次遍历的方法,计算效率较高,适合处理较大规模的采样数据。

图4.9:幅值检测函数Cal_PeakToPeak代码

4.5 TFT屏幕绘制采集波形

TFT屏幕是系统的重要输出模块,负责实时显示处理后的心电波形和心率数据。通过高分辨率和快速刷新率,TFT屏幕能够清晰呈现心电信号的变化,供用户随时监控自己的心脏健康状况。它不仅显示心电波形,还提供当前的心率和可能的异常信号提示,帮助用户及时发现问题。

图4.10 2.8寸TFT LCD电阻触摸屏模块320\*240实物图

对于已经滤波后的心电信号,在移植并调用原有LCD驱动库的基础上,编写了drawCurve函数实现实时的波形显示。该函数根据输入的心电信号数值,将其映射到屏幕的像素坐标,并绘制出连续的曲线。

图4.11:TFT屏幕波形绘制函数drawCurve代码

可以看到,对于滤波后的心电信号值,除了基本的指定区域绘制波形外,为避免波形超出屏幕边界,还对计算得到的y坐标进行上下限约束,使其始终位于屏幕的有效显示区域内:如果y坐标超过屏幕范围,则自动截断至边界位置;当x坐标超出屏幕宽度时,程序清空屏幕并从起始位置重新开始绘制新的波形。通过这种循环显示方式,确保波形在屏幕上以滚动形式连续更新,为实时心电信号的监测提供直观的显示效果。

4.6 频谱分析及其TFT屏幕绘制

在频谱FFT计算的具体实现中,程序使用CMSIS-DSP库提供的高效FFT算法,首先将心电信号数据打包为复数输入,其中实部为滤波后的心电数据,虚部置为零。随后调用arm_cfft_radix4_f32函数完成快速傅里叶变换,并通过arm_cmplx_mag_f32函数计算复数频谱的幅值。计算结果存储在FFT输出缓冲区中,用于后续的频谱绘制。

图4.12:频谱计算(FFT)函数FFT_Calculate代码

对于频域波形,系统通过FFT算法计算心电信号的频率分量,并将其可视化为频谱图。频谱图的x轴代表频率,y轴代表频率分量的幅值,能够直观反映心电信号的频率特性。频域波形的可视化主要依赖Draw_Spectrum函数:

图4.13:频谱绘制函数Draw_Spectrum代码

由于屏幕高度有限,为避免绘图时坐标溢出,函数将FFT输出的幅值限制在合理范围内(0到150,其余位置留给时域波形)。当幅值超过上限或低于下限时,进行截断处理。每个频率分量对应一条垂直线,从屏幕底部开始绘制到计算得到的y坐标。

5 程序测试方法

5.1 ADS1292R驱动移植及引脚配置

首先,在通过STM32CubeMX创建工程时,需要配置单片机的时钟速率,SPI接口,中断以及UART串口通信等。为实现与ADS1292R传感器的数据传输,选用SPI1并对引脚进行如下配置:

图5.1:SPI1引脚与参数配置

其中与ADS1292R传感器通信的SPI接口配置为主模式,时钟极性为CPOL=LOW,时钟相位为CPHA=2EDGE,数据帧格式为8位,通信速率依据传感器要求设置在8MHz以下,此处设置为656.25Kbit/s。

此外,还需要根据ADS1292R库文件中的说明,对UART通信进行设置,其中波特率设置为115200,8位数据位,1位停止位,无校验。

图5.2:UART引脚与参数配置

完成引脚配置后就可以进行初始代码生成。在生成的初始代码工程中,引入ADS1292R的相关库文件(ADS1292R.c与ADS1292R.h),根据对应的配置代码将STM32F407开发板与ADS1292R传感器模块进行连线:

图5.3:ADS1292R.h与spi.c代码文件中关于连线的相关说明图5.3:ADS1292R.h与spi.c代码文件中关于连线的相关说明

图5.4:连线实物图
图5.4:连线实物图

连线完成后,要通过SPI接口实现与ADS1292R的数据交互,还需要在主程序中完成相应的初始化,即调用ADS1292R驱动库中的相关函数,由单片机向ADS1292R发送相应的命令以启动数据采集,并配置工作模式、采样速率、增益等参数。根据ADS1292R数据手册及项目要求,以下是对ADS1292R的主要配置过程:

图5.5:函数ADS1292_PowerOnInit中对于ADS1292R的基本配置代码

首先,将ADS1292R的CONFIG2寄存器配置为0xA3,以启用内部参考电压。内部参考电压的稳定性对于后续心电信号的准确采集至关重要,因此在配置完成后需通过DWT_Delay_ms(10)添加延时,确保内部参考电压稳定。随后,将CONFIG1寄存器设置为0x02,配置心电采集的采样速率为500SPS(Samples Per Second),这一速率能够兼顾信号的时域分辨率和数据量。

接下来,对通道1和通道2进行配置。其中,通道1的CH1SET寄存器设置为0x00,表示通道1工作于正常采集模式,且未启用测试信号输入。通道2的CH2SET寄存器配置为0x05,用于指定通道2采集来自内部测试信号的方波,方便在调试阶段验证系统的采集和传输功能。

此外,为了进一步优化信号的质量,对右腿驱动(RLD)电极进行了配置,通过将RLD_SENS寄存器设置为0x2C,使右腿驱动电极同时连接到通道1和通道2,增强了共模信号的抑制能力。在呼吸阻抗测量相关功能中,将RESP1和RESP2寄存器分别配置为0x02和0x03,根据手册要求开启适当的工作模式。

通过上述配置,ADS1292R可以稳定运行于双通道心电采集模式,通道1用于实时采集患者的心电信号,通道2可用于采集ADS1292R传感器的测试信号以验证该传感器模块是否被正常驱动。

5.2 ADS1292R驱动移植验证:读取传感器设备ID

事实上,在ADS1292_PowerOnInit函数中,图5.5所示初始化代码之前还有一段代码:

图5.6:ADS1292_PowerOnInit初始化函数部分代码

可以看到,要实现5.1节中对于ADS1292R传感器模块的正确驱动,就必须要顺利运行该段代码,其中调用了ADS1292_ReadDeviceID这一函数并设置了判断条件,若读取到的设备ID不为83(ADS1292)或115(ADS1292R)则会一直循环读取ID而不进行后续的初始化,以确保读取的心电信号不会出现无效数据且采集数据符合预期格式。由于本次项目使用的传感器芯片为ADS1292R,故成功读取到并在串口助手中打印的device_id为115,这也证明驱动库文件的移植是正确的且能正常驱动该传感器模块。

5.3 ADS1292R驱动移植验证:测试信号采集及其TFT屏幕绘制

在ADS1292R的芯片数据手册中,给出了其内置测试信号的相关参数:

图5.7:ADS1292R芯片手册测试信号部分

可以看到,当PGA增益倍数设置为1时,芯片将输出测试方波信号,通过MATLAB读取并解码原始数据,得到(含直流)范围在-20到8;而去除直流后,该方波动态范围为-14到14。由于参考电压为2.4V(16bits),此时14/32768*2400mv
约等于1mv,说明此时方波动态范围转换为电压是正负1mv,和数据手册说明一致。方波测试信号的成功读取也进一步验证了ADS1292R驱动库移植的正确性且能正常驱动该传感器模块。

图5.8:MATLAB读取测试方波结果

图5.8:MATLAB读取测试方波结果

6 实验数据记录与分析

演示视频链接:https://www.bilibili.com/video/BV1HWCkYzETe/

6.1 测试信号采集

在测试ADS1292的内部方波信号时,设置PGA增益为1,测试结果显示方波波形良好,基本无误差。系统能够准确读取方波信号的峰峰值和幅度值,其效果如图所示:

图6.1:采集测试信号在LCD屏幕上的显示效果(测量幅度值为155)

此外,系统新增了按键功能,按压KEY_0即可实现在显示测试信号波形与显示心电信号波形间切换。

6.2 模拟器心电信号采集

在实际运行中,drawCurve函数结合系统滤波模块的输出数据和心跳检测结果,将心电信号时域与频域的动态变化以实时曲线的形式绘制在屏幕上。同时,在屏幕上显示心率、波幅等关键参数,进一步增强了系统的直观性和信息量。其效果如下图所示:

图6.2:BPM为60的心电信号(模拟器产生)在LCD屏幕上的显示效果

经过测试,测算出的心率值与心电模拟仪中给出的心率值误差不超过3 BPM(每分钟心跳数),满足系统设计需求;但此时滤波器的滤波效果有点过度,也可能是显示的波形幅度调整的不好,导致最终显示的波形丢失了过多的有用信息。

6.3 模拟器心率信号采集

将模拟器调至2档(心率档),产生的心率信号默认为75BPM,此时时域与频域波形的显示效果如下:

图6.3:BPM为75的心率信号(模拟器产生)在LCD屏幕上的显示效果

可以看到,此时测得的心率基本准确,与心电模拟仪中给出的心率值误差不超过3 BPM,满足系统设计需求,且频谱图也有较明显的尖峰且噪声较少;此时滤波器的滤波效果虽然也有点过度了,但效果明显由于6.2节中对于心电信号的测量,有明显的波峰。

心跳检测功能的设计不仅能够适应动态变化的心电信号,还通过灵活的阈值调整提高了对不同信号幅度的适应性。将模拟器调至8档,以调整产生心率信号的幅值(心率BPM仍为75),采集与显示效果如下:

图6.4:幅值为1mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

图6.5:幅值为2mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

图6.6:幅值为3mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

可以看到,随着心率信号幅值的成倍变化,测量出的波形幅值也在成倍变化,且频谱较为干净,这说明该心电信号采集系统对于不同的信号幅度具有较高的灵敏性,但未对测量的幅值数值进行对应电压的换算导致结果并不直观;同时由于测量心率程序中对于检测跳变幅度的阈值设置较小,导致此时当幅值明显增大时,震荡信号的跳变幅度也随之增加,导致心率测量出现一定程度的偏差。

6.4 人体心电信号采集与测量

未接入人体心电信号时,采集到信号的时域波形与频谱如下:

图6.7:未接入任何心率信号时LCD屏幕上显示的时域与频域波形(白板)

对于人体心电信号检测,由于心电电极对接触状态的敏感性,最佳效果仅在涂抹酒精并佩戴三分钟内获得,因此系统仅能在心电电极贴附于人体的短时间内实现精确检测

在实际测试时,邀请室友将电极片分别贴于手腕和脚部,并分别与数据线连接,此时系统采集到的心电波形如下所示(详细测试过程在演示视频中):

图6.8:人体心电信号测试过程掠影
图6.8:人体心电信号测试过程掠影

图6.9:采集到的人体心电信号时域与频域波形显示结果

从图中可以看出,检测人体心电信号时,波形与心电信号模拟仪的输出相比仅存在轻微差别,整体表现良好,但仍然存在滤波过度的问题。

除了将电极贴于手腕和脚部进行测试外,系统还测试了将电极贴在左右锁骨中线第一肋间和胸骨左缘第四肋间的位置。测试结果显示,这些位置的信号稳定性优于手脚部位,并且能够维持更长时间的良好效果。

7 实验结果总结和心得体会

7.1 实验结果

在本项目中,基于 STM32F407ZG 主控芯片和 ADS1292R 传感器模块,成功实现了人体心电信号的采集与分析功能。针对心电信号易受工频干扰和低频基线漂移影响的问题,设计并实现了 IIR 数字直流陷波器 和 FIR 数字低通滤波器,有效消除了干扰并获得了较为清晰的心电波形。

在开发过程中,利用心电信号模拟仪对单片机程序进行了调试,使系统能够稳定采集和显示心电波形。尽管滤波器在MATLAB仿真中效果良好,但移植到单片机工程中后,出现了滤波过度的问题。初步分析认为,这可能与信号采样频率、显示波形的幅度比例调节以及滤波算法的具体实现有关,仍需进一步优化。然而,系统总体性能令人满意,其采集效果、频域分析结果、心电峰峰值及心率测算结果均较为准确。

此外,系统还实现了对 ADS1292R模块自带测试方波信号的读取、显示以及峰峰值测量功能,基本满足了所有的设计需求。最终也成功采集并显示了实际人体心电信号的时域波形与频谱。

7.2 心得体会

通过本次项目的实践,我对数字信号的采集与处理以及滤波器的设计与实现有了更加深刻的理解。这不仅加深了我对数字信号处理理论知识的掌握,还让我在理论与实践的结合中得以进一步巩固相关技能。在单片机的编程实践与调试过程中,我对STM32尤其是F4系列开发板的硬件和软件开发有了更深入的认识,这大大提升了我的开发效率和调试能力。

本项目也是我首次尝试使用 FreeRTOS操作系统搭建整个工程框架。在此过程中,我学习并掌握了实时操作系统的基本原理和任务调度机制,初步理解了如何优化系统资源管理,成功地将操作系统的使用融入到嵌入式项目开发中。这一尝试不仅丰富了我的开发经验,也让我更好地认识到实时操作系统在复杂工程中的价值。

尽管最终心电信号的显示结果并未完全达到预期,但我从问题中发现了自身在信号采集频率、滤波算法实现以及系统显示优化方面的不足,并明确了未来改进的方向。整个项目开发的过程充满了挑战,同时也伴随着大量的收获。我深刻体会到硬件开发与数字信号处理是一个不断探索和优化的过程,而这次项目实践无疑为我在这些领域的技能提升打下了坚实基础。

此外,本次项目还培养了我独立分析和解决问题的能力。从模块功能的实现到整体工程的构建,我逐步熟悉了完整的开发流程,对系统的设计、调试和优化有了更加系统化的认识。我相信,这些经验和能力将在今后的学习和工作中发挥重要作用,为更高层次的开发任务奠定基础。

参考资料

[1] 朱冰莲,方敏编著.数字信号处理[M].电子工业出版社,2014:276.

[2] 程佩青编著.数字信号处理教程[M].清华大学出版社,2015:524.

[3] 任勇,曾浩编著.单片机原理及应用[M].清华大学出版社,2023.

[4] 德州仪器. ADS1292 数据手册

[5] 任勇. CQU_S12XDEV开发板原理图 微电子与通信工程学院

[6] 任勇. ADS1292-心电信号采集原理图及接口说明-RY

基于直流电源调控的自动调光控制设计

摘要

本项目围绕直流电源调控的自动调光控制系统展开研究与设计,系统性地探讨了Buck变换器的基本原理、建模方法、性能分析及其实验验证过程。在硬件设计方面,基于STM32处理器,选择了高性能的元器件并通过合理的电路拓扑实现高效的能量转换;在软件控制算法方面,采用PID闭环控制,并结合自动控制原理中的经典控制理论,利用PSIM与MWorks等仿真与科学计算工具,对控制系统的时域响应、频域特性和稳定性进行了详尽分析,进而通过参数优化与校正环节设计显著提升了系统的响应速度和稳态性能,同时也验证了闭环控制系统在动态性能、抗干扰能力和输出精度方面的显著优势。此外,通过实验测量与仿真结果对比,探讨了电路寄生参数对系统性能的影响,为后续优化提供了理论依据。在基于光敏电阻的自动调光功能模块中,结合蓝牙通信接口实现了系统的智能化控制,同时对于自动调光系统进行外观设计,赋予产品更多的人文关怀与实用价值;在光伏板最大功率点跟踪(MPPT)功能模块中,根据MPPT的原理与基本思想设计了相应的控制算法,并在实验中成功控制光伏板输出功率,使其约等于负载消耗功率,完成了不同光照强度下最大功率点的跟踪。最后,对于该自动控制系统的设计成果及其在实际应用中的可行性与局限性进行总结,并对未来可能的优化方向和工程实现前景提出了展望。

关键词:Buck变换器;PID闭环控制;自动调光;光伏MPPT

1 课程涉及理论基础和STM32简介

1.1 自动控制原理简介

在科学技术飞速发展的今天,自动控制技术和理论已经成为现代社会不可缺少的组成部分。自动控制技术的应用不仅使生产过程实现自动化,从而提高了劳动生产率和产品质量,降低了生产成本,提高了经济效益,改善了劳动条件,使人们从繁重的体力劳动和单调重复的脑力劳动中解放出来;而且在人类征服大自然、探索新能源、发展空间技术和创造人类社会文明等方面都具有十分重要的意义。

自动控制理论是研究关于自动控制系统组成、分析和综合的一般性理论,是研究自动控制共同规律的技术科学。自动控制是在人不直接参与的情况下,利用外加的自动控制设备或装置(控制装置或控制器),使机器、设备或生产过程(统称为被控对象)的某个工作状态或参数(被控量)自动地按照预定的规律运行,使机器的动作、设备的运转、生产过程的状态能够自动地在一定的精度范围内按照给定的规律变化。学习和研究自动控制理论是为了探索自动控制系统中变量的运动规律和改变这种运动规律的可能性和途径,为建立高性能的自动控制系统提供必要的理论依据。

1.2 本项目所涉及的经典控制理论内容

图1.1:项目涉及的经典控制理论框图

本项目从经典控制理论的基本原理与概念出发,以Buck变换器这一单输入-单输出的线性系统作为研究对象,利用微分方程、Laplace变换与传递函数等数学工具建立系统的数学模型,并基于时域分析、频域分析以及根轨迹法等多种分析方法对于系统的稳定性与响应特性进行详细分析,从而针对特定的性能指标进行对应的校正设计,通过引入PID控制器并调控其参数以改变系统的频率特性从而满足给定的各项性能指标,使得整个闭环控制系统能够兼具稳定性、快速性与准确性。

1.3 STM32处理器介绍

控制核心是控制系统中的重要组成部分,用于计算、解析各种数据,并执行相应的控制算法。芯片选型的设计直接决定了控制板的性能和功能。STM32是由意法半导体公司(ST)推出的基于Arm Cortex-M处理器内核的32位微控制器,专为要求高性能、低成本、低功耗的嵌入式应用设计,集实时功能、数字信号处理、低功耗/低电压操作、连接性等特性于一身,同时还保持了集成度高和易于开发的特点,基于行业标准内核,提供了大量工具和软件选项以支持工程开发,非常适用于小型项目或端到端平台。

本项目选用的处理器STM32F103C8T6作为中等容量高性能系列MCU,集成了工作频率为72MHz的高性能Arm Cortex-M3 32位RISC内核、高速嵌入式存储器(高达128KB的Flash存储器和20KB的SRAM存储器),以及大量连接至2条APB总线的增强型I/O与外设,具有36引脚至100引脚等6种不同的封装类型。所有器件均提供2个12位ADC、3个16位通用定时器、2个PWM定时器以及标准和高级通信接口:多达2个I2C和SPI、3个USART、1个USB和1个CAN。器件的工作电压为2.0V至3.6V。该处理器的工作温度范围为-40℃到+85℃,可扩展至-40℃到+105摄氏度。这些特性使得该处理器成为各种应用的理想之选,也能很好满足本项目对于控制器的性能需求。

图1.2:本项目选用的处理器STM32F103C8T6

1.4 本章小结

本章主要介绍了本课程相关的自动控制理论基础,针对本项目涉及到的经典控制理论框架进行了简要概述,同时对于本项目所选用的控制核心——STM32处理器进行简单介绍,重点分析了我们采用的STM32F103C8T6处理器的性能特性并给出选型原因。这为本课程项目提供了整体框架,并从理论上对后续项目的具体实施给出了方向性的指引。

2 直流Buck变换器设计与调试

2.1 Buck变换器拓扑原理分析

Buck(降压式)变换器是一种输出电压≤输入电压的非隔离直流DC-DC变换器,其中输入电流为脉冲式的,而输出电流为连续的低纹波直流电压。Buck变换器实现的稳态输入输出关系为:
$$
U_{0} = DU_{in}
$$
Buck变换器的主电路由开关管Q,二极管D,输出滤波电感L和输出滤波电容C构成。

图2.1:Buck开关功率变换器基本电路

可以看到,在能量缓冲变换电路中,主要由如下三个部分组成:

  1. 电感L与电容C实质上构成了一个二阶低通滤波器,通过滤除开关频率交流分量而仅保留其直流分量,得到平直的输出电压U0;

  2. 脉冲宽度调制(Pulse Width Modulation,PWM)产生方波电压控制开关管Q的导通;

  3. 二极管D为电感电流提供续流回路。

Buck变换器主电路整体的工作逻辑如下:

  1. 当开关管Q驱动为高电平时,开关管导通,储能电感L被充磁,流经电感的电流线性增加,同时给电容C充电,给负载R提供能量;

图2.2:开关管导通时电流环路

  1. 当开关管Q驱动为低电平时,开关管关断,储能电感L通过续流二极管D放电,电感电流线性减少,输出电压靠输出滤波电容C放电以及减小的电感电流维持。

图2.3:开关管关断时电流环路

事实上,对于该电能变换器,可以通过更改个别元器件的种类、接入方式与顺序,实现搭建具有不同功能的电能变换电路,即Buck变换器的拓扑原理。下面列举几种常见的拓扑电路:

  1. 升压变换器:

图2.4:Buck变换器拓扑:降压--->升压

  1. 降压同步整流变换:采用互补工作模式,可减小损耗

图2.5:Buck变换器拓扑:同步整流

  1. H桥DC-AC逆变器:开关管部分串联构成双极性交流电压源

图2.6:Buck变换器拓扑:DC-DC --->DC-AC

  1. 闭环PWM控制:可以在原有Buck电路基础上增加闭环环路,通过PWM调配开关管Q的导通与否,从而实现对于输出电压的控制,使系统能够更加”稳”、”快”、”准”地得到期望的输出。目前的控制器选择主流为PID控制器,根据不同的指标又可将闭环控制系统分为不同类别:若根据控制对象分类,则可分为电压控制与电流控制;若根据接收调控信息的时间先后分类,又可分为反馈控制与前馈控制;根据其他的分类标准,还可分为线性/非线性控制、平均/纹波控制、模拟/数字控制……在此不一一列举。

2.2 Buck变换器元器件参数选择

在Buck变换主电路中,对电路参数进行如下设定:

  • 输入电压
    $$
    U_{in} = 15V
    $$

  • 输出电压
    $$
    U_{o} = 8V
    $$

  • 占空比
    $$
    d = 50%
    $$

  • 电源转换电压:12V、15V、5V、5V(隔离)

  • 电感
    $$
    L = 100\mu F
    $$

  • 电容
    $$
    C = 660\mu F
    $$

  • 工作频率
    $$
    f = 25kHz
    $$

首先,为满足电源转换与单片机供电的需求,需要在电源直接引入Buck电路前先接入电源模块,涉及到的元器件及相关参数如下:

  1. URB2412YMD-10WR3电源模块:降压模块,将电源提供的输入电压(最大35V,本实验中为15V)转换为12V输出,提供0.83A的电流;

图2.7:URB2412YMD-10WR3电源模块原理图与实物图图2.7:URB2412YMD-10WR3电源模块原理图与实物图

  1. CW7805线性稳压器:将12V输入电压转换为稳定的5V输出电压,并将输出电流转换成1A,分输入、输出与接地三端,主要用于使线性的输出电压稳定;

图2.8:CW7805线性稳压器原理图与实物图图2.8:CW7805线性稳压器原理图与实物图

  1. A1215S-2WR3电源模块:升压模块,将12V输入转换为±15V,适合供给双电源运放电路,本实验中主要用于为采样电路(滤波器)供电;

图2.9:A1215S-2WR3电源模块原理图与实物图图2.9:A1215S-2WR3电源模块原理图与实物图

  1. IB1205S-2W电源模块:降压模块,将12V输入转换为5V,通常用于低功耗电路的供电,本实验中主要用于光耦(与驱动)电路的供电。

图2.10:IB1205S-2W电源模块原理图与实物图图2.10:IB1205S-2W电源模块原理图与实物图其次,在Buck变换主电路中,为满足设定元件参数,选取如下元器件:

  1. 贴片MOS管NCE0130KA:VDS=100V,VGS=10V

图2.11: 贴片MOS管NCE0130KA实物图

  1. 贴片电感:色环直插型,100uH

图2.12:贴片电感实物图

  1. 贴片铝电解电容:330uF,±20%,耐压50V,两个并联达到设定660uF

图2.13:贴片铝电解电容实物图

  1. 电流传感器CC6920SO-5A:初级电流测量范围-5A~5A,供电电压5V

图2.14:电流传感器CC6920SO-5A实物图

  1. 电压传感器LV25-NP:初级电流测量范围10-500V,供电电压±15V

图2.15:电压传感器LV25-NP实物图

其中,电容与电感均采用贴片形式的原因主要有以下几点:

【1】 体积小且便于贴装,适合电路的高集成度需求;

【2】 贴片元件引线较短,寄生电感和电容较小,可提高电路的高频性能;

【3】 贴片元件的散热性能通常较好,有助于提高电路的可靠性;

【4】 贴片元件可以有效减小电磁干扰,提高电路的稳定性。

除此之外,由于本实验采用的主控STM32输出能力有限,无法直接驱动管子开关,因此还需要采用光耦和驱动电路为管子提供驱动信号,涉及到的元器件及相关参数如下:

  1. IR21844S驱动:栅极驱动供电范围10-20V

图2.16:IR21844S驱动实物图

  1. 光耦-逻辑输出6N135:5V供电,光耦隔离

图2.17:光耦-逻辑输出6N135实物图

  1. 运算放大器AD823ARZ:最大输入偏置电流25pA,低失真−108dBc

图2.18:运算放大器AD823ARZ实物图

2.3 Buck变换器实物设计与调试

在Buck变换主电路设计时,选择不对Buck变换器的电路部分进行拓扑,而在闭环PWM控制电路中采用平均电压模式进行控制。本项目采用的实验电路板主要包括Buck电路基本器件、开关管驱动、辅助电源以及采样电路(信号调理电路),实验时将元器件焊接至电路板上并分别调试各模块功能。

图2.19:组员焊接电路板过程图2.19:组员焊接电路板过程

图2.20:焊接后Buck变换器整体电路实物图图2.20:焊接后Buck变换器整体电路实物图

2.3.1 主电路设计与调试

图2.21:Buck变换器主电路原理图

该电路是一个典型的降压型 DC-DC 转换器,其工作原理是通过开关管 Q3 的高速开关动作,将输入电压V_IN转换为期望的输出电压V_OUT。当开关管Q3导通时,输入电压通过 L2、L3和负载形成电流回路,电感存储能量,同时为负载供电;当 Q3关断时,续流二极管 D4 提供电流通路,电感释放能量维持负载电流的连续性。控制器 U6根据反馈电压(通过分压电阻 R8 反馈的V_OUT)与参考电压之间的误差,实时调节 Q3的导通时间(占空比),从而实现输出电压的稳定调节。两级电感 L2 和 L3以及滤波电容 C9、C15进一步平滑输出电流和电压,减少高频纹波,确保输出电压的稳定性和低噪声特性。

经过调试,Buck降压模块主电路可正常实现功能,在参考输入方波幅值为8V、占空比为50%的情况下能够输出占空比为50%、幅值在8V左右(实际约为8.5V)的方波。

图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果

2.3.2 控制电路供电设计与调试

图2.23:STM32主控芯片供电电路及预留引脚接口

如图所示为STM32主控芯片供电电路(电源模块)以及单片机内部所使用的接口引脚图,同时将大部分未使用的引脚通过排针引出以供后续拓展功能开发。

在STM32主控芯片供电电路中,包含两个级联的线性稳压器,用于将高电压逐级稳压到所需的5V 和 3.3V。上半部分采用 CJ7805稳压器,将输入电压V_IN转换为稳定的5V输出,通过输入电容C2和C1滤波降低输入纹波,稳压器通过内部反馈电路调节输入电压,使输出稳定在5V,同时通过输出电容C3滤除高频噪声,进一步平滑输出电压。下半部分采用
AMS1117-3.3 稳压器,将上一级提供的 5V 电压进一步稳压为3.3V,通过输入滤波电容 C5和 C6减少输入噪声,并通过输出滤波电容C4提高输出电压的稳定性。整个电路通过分级稳压设计,既提高了稳压效率,又能为负载提供低噪声、高稳定性的5V和3.3V电压,适用于嵌入式系统和低功耗数字电路的电源需求。

调试流程:取下单片机核心板—>接入负载100欧姆—>单片机输出PWM—>观测PA8端口波形—>观测驱动芯片输出端口波形—>上主电24V—>检测辅助电源输出电压—>检测输出电压—>根据占空比计算输出电压是否正常—>完成

经调试,该部分模块可正常工作,为STM32主控芯片提供稳定的5V电压:

图2.24:控制模块供电电路调试输出结果图2.24:控制模块供电电路调试输出结果

2.3.3 驱动电路设计与调试

图2.25:光耦隔离(上)与驱动电路(下)原理图

如图所示,驱动电路为已有的STM32输出提供了合适的电压和电流驱动功率器件,而如果直接使用STM32输出驱动,可能会超出其输出能力或不能实现良好的电气隔离,导致驱动失败或损坏器件,输出信号不稳定。使用光耦和驱动电路则可以更好地实现电气隔离,从而防止高压或大电流对控制电路的干扰或损坏,保护STM32免受高电压或大电流的影响,提高系统工作可靠性。

调试时,先切断仿真器供电,将单片机供电切换为主电路辅助电源供电;单片机烧录输出电压控制程序后,接入后端负载,再上主电,观测输出电压。经调试,驱动电路可正常工作。

图2.26:驱动电路调试后输出结果图2.26:驱动电路调试后输出结果

2.3.4 电压和电流采样调理滤波设计与调试

图2.27:电感电流(上)与输出电压(下)采样调理滤波电路原理图

该电路是一个基于运算放大器的电压比较和分压检测电路,主要功能是将输入电压V_OUT通过电阻分压后与基准电压比较,并输出相应的信号V_S。该电路可用于电压监测或过压保护等场景,通过调整R1 和 R2 的比例,可以设置分压电压,从而灵活设定输入电压的触发阈值。

具体工作原理如下:输入电压$V_{OUT}$经由电阻R1和R2分压后,产生一个分压电压,该电压送入运算放大器U5的反相输入端(引脚2)。运算放大器的非反相输入端(引脚 3)通过稳压二极管 D2提供一个固定的基准电压(3.3V)。当分压后的电压低于基准电压时,运算放大器输出高电平;当分压电压高于基准电压时,运算放大器输出低电平。电容C7和C16用于滤除输入和输出的高频噪声,保证比较过程的稳定性。

调试时,使用仿真器给单片机供电,以调试PWM波形输出是否正常;烧录开环测试程序之后,使用示波器或者上位机观测电路板PA8端口是否正常输出PWM波形。经调试,可对输入信号正确采样并输出对应波形,说明采样模块正常工作:

图2.28:采样模块调试后输出结果图2.28:采样模块调试后输出结果

2.4 本章小结

本章主要介绍了直流Buck变换器的基本工作原理及其拓扑变换,并根据选定的主控芯片STM32F103C8T6以及设定的电路参数进行了基本元器件的选型与电路原理图及PCB电路板的设计,确定电路主要包括Buck降压变换主电路、控制电路供电辅助电源模块、驱动电路以及采样电路(信号调理电路)四个模块;在焊接时对各个模块依次进行焊接与调试,保证各模块均可以正常工作,以便于后续闭环控制实现时STM32主控控制模块与代码的设计与测试。

3 直流Buck变换器建模

3.1 Buck变换器闭环控制原理分析

Buck变换器闭环控制系统主要由以下几个部分组成:

  1. 误差放大器:将参考电压V_ref与实际输出电压V_OUT比较,生成误差信号;

  2. 补偿网络:对误差信号进行处理(例如,PI或PID控制),以提高系统稳定性和动态性能;

  3. PWM调制器:将补偿后的控制信号转换为开关元件的占空比D;

  4. 采样电路:对输出电压 V_OUT进行实时采样。

图3.1:Buck变换器闭环控制电路简图

整个闭环控制系统的工作过程如下:

  1. 输出电压采样:通过分压电路对输出电压V_OUT进行采样,得到反馈电压V_fb;

  2. 误差检测:误差放大器将参考电压V_ref与反馈电压V_fb比较,产生误差信号
    $$
    e(t) = V_{ref} - V_{fb}
    $$

  3. 误差调节:误差信号经过补偿网络调节,生成调节信号V_ctrl,此信号决定PWM占空比;

  4. PWM调制:调制器根据调节信号V_ctrl生成控制信号D,驱动开关元件;

  5. 电感电流调节:开关元件的导通时间决定电感电流的充电时间,从而控制输出电压。

图3.2:Buck变换器闭环控制系统框图

从系统传递函数的角度考虑,根据系统框图与元件特性,可计算其开环传递函数与特征方程:

(1)G_c(s)为PI环节, PI调节器为:
$$
\left{ \begin{aligned}
& \dot{x} = K_{I}v \
& y = K_{p}v + x
\end{aligned} \right.
$$
可得PI环节传递函数G_c(s):

$$
G_{c}(s) = K_{p} + K_{I}\frac{1}{s} = \frac{K_{p}(\tau s + 1)}{\tau s},\tau = \frac{K_{p}}{K_{I}}
$$
(2)PWM脉宽调制环节:由大信号关系
$$
v_{con}(dT) = v_{ramp}(dT) = V_{M}\frac{dT}{T} = dV_{M}
$$
做微分可得小信号的线性关系
$$
\mathrm{\Delta}v_{con} = \mathrm{\Delta}dV_{M}
$$
从而实现传递函数的线性化:

$$
G_{pwm} = \frac{\mathrm{\Delta}d}{\mathrm{\Delta}v_{con}} = 1/V_{M}
$$
(3)PWM脉宽控制开关电路:由大信号关系
$$
v_{D} = dv_{g}
$$
做全微分可得小信号线性关系
$$
\mathrm{\Delta}v_{D} = \mathrm{\Delta}dV_{g} + \mathrm{\Delta}v_{g}D
$$
零初始条件下,对应的工作点为
$$
V_{g} = V_{in},\mathrm{\Delta}v_{g} = 0
$$
于是有传递函数:
$$
G_{vg} = \frac{\mathrm{\Delta}v_{D}}{\mathrm{\Delta}d} = V_{g}
$$
(4)RLC并联二阶输出滤波器:由电路图与元件特性可得各元件间电流电压关系:

$$
\left{ \begin{array}{r}
u_{0} = Ri_{2} \
u_{L} = L\frac{di}{dt} \
i_{1} = C\frac{du_{0}}{dt} \
i = i_{1} + i_{2} \
u_{D} = u_{L} + u_{0}
\end{array} \right.
$$
从而可得该滤波器对应的微分方程:
$$
u_{D} = LC{u_{0}}^{‘’} + \frac{L}{R}{u_{0}}^{‘} + u_{0}
$$
对两边同时进行拉普拉斯变换,化简整理后可得二阶滤波器的传递函数G_vd(s):

$$
G_{vd}(s) = \frac{U_{0}(s)}{U_{D}(s)}V_{in} = \frac{\frac{1}{LC}}{s^{2} + \frac{1}{RC}s + \frac{1}{LC}}V_{in}
$$
又因输出信号v_0直接接入PI控制器,故
$$
H(s) = 1
$$
综上所述:有开环传递函数:

$$
T(s) = G_{c}(s)G_{pwm}G_{\text{vd}}H(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC}}{s^{2} + \frac{1}{RC}s + \frac{1}{LC}}V_{in} = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
进而可以得到系统的特征方程
$$
T(s) + 1 = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{g}}{LCs^{2} + \frac{L}{R}s + 1} + 1 = 0
$$
化简后可得:
$$
\Delta(s) = V_{g}K_{p}(\tau s + 1) + \tau sV_{M}\left( LCs^{2} + \frac{L}{R}s + 1 \right) = \tau V_{M}LCs^{3} + \tau V_{M}\frac{L}{R}s^{2} + (V_{g}K_{p} + V_{M})\tau s + V_{g}K_{p} = 0
$$

3.2 Buck变换器PSIM仿真(开环+闭环)

利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将电路各元件实际参数代入(PI控制器参数:K_p = 0.1,tao = 0.004):

图3.3:Buck变换器开环控制PSIM仿真电路图

图3.4:Buck变换器闭环控制PSIM仿真电路图

运行仿真程序,可得到输出电压的仿真结果:

图3.5:Buck变换器开环控制PSIM仿真结果------输出电压

图3.6:Buck变换器闭环控制PSIM仿真结果------输出电压

对比开环与闭环控制系统的输出电压仿真结果可以发现,尽管两个系统都能在短时间内达到稳定的输出电压,但显然闭环控制系统到达稳态的速度更快且震荡更小,稳定后的电压也更接近参考电压8V(约为7.95V)。这说明闭环控制系统具有更快的响应速度与更好的稳定性和准确性。

3.3 输出电压纹波计算(仿真+实验)

图3.7:Buck变换器开环控制实验输出电压纹波波形图3.7:Buck变换器开环控制实验输出电压纹波波形

首先进行Buck变换器的开环控制实验,并通过示波器观察其输出电压的纹波波形。可以看到,输出波形峰值
$$
V_{omax} = 88mV
$$
谷值
$$
V_{omin} = - 76mV
$$
则波形的震荡幅度为
$$
\Delta V_{o} = V_{omax} - V_{omin} = 164mV
$$
随后利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将电路各元件实际参数代入:

图3.8:Buck变换器开环控制PSIM仿真电路图

可得到输出电压纹波的仿真结果:

图3.9:Buck变换器开环控制PSIM仿真结果------输出电压纹波

可以看到,输出电压信号的震荡波形与实际电路示波器显示的纹波形状一致且震荡幅度大致相同。

3.4 电容寄生电阻计算(仿真+实验)

在Buck变换器开环控制实验中,通过万用表测量得到输入电压V_i= 15.304V,输出电压均值V_o = 7.537V:

图3.10:Buck变换器开环控制实验输入、输出电压测量结果图3.10:Buck变换器开环控制实验输入、输出电压测量结果

除了给定的元件参数之外,为对直流Buck变换器进行精确建模,考虑到电容的寄生参数可能对系统有较大影响,故特别计算其寄生电阻阻值ESR:

图3.11:寄生电阻阻值计算理论依据

结合
$$
U_{esr} = \Delta I_{L}*ESR
$$
一式,考虑到电流因流过电容的寄生电阻而产生的压降U_esr应不大于(实际一般处理为等于)波形的震荡幅度,即:
$$
U_{esr} \leq \Delta V_{o}
$$
可以得到在同步Buck电路中(V_d = 0)电容寄生电阻阻值ESR的计算公式:

$$
ESR \leq \frac{\Delta V_{o}fLV_{i}}{V_{o}\left( V_{i} - V_{o} \right)}
$$
结合Buck变换器开环控制实验结果,代入参数
$$
\Delta V_{o} = 164mV,L = 100\mu F,f = 25kHz,V_{i} = 15.304V,V_{o} = 7.537V
$$
可以计算得出该电路中实际的电容寄生电阻阻值
$$
ESR \approx 107.185m\Omega
$$
这里计算的是接入单个电容的寄生电阻阻值,在实际电路中使用了两个330μF的电容等效替代原设计电路中的660μF电容,为使仿真尽可能接近实际,采取了与实际电路相同的结构,因此需要把寄生电阻同样进行等效,根据电阻并联的等效电阻计算可以得到两个330μF电容的寄生电阻值均为
$$
2ESR \approx 214.371m\Omega
$$
利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将计算出的寄生电阻结果代入仿真电路中:

图3.12:引入寄生电阻后的Buck变换器开环控制PSIM仿真电路图

得到的输出电压仿真结果如下图所示:

图3.13:引入寄生电阻后的Buck变换器开环控制PSIM仿真结果------输出电压

图3.14:引入寄生电阻后的Buck变换器开环控制PSIM仿真结果------输出电压纹波

可以看到,仿真结果中输出电压的均值约为7.65V,与实际的测量结果
$$
V_{o} = 7.537V
$$
较为接近;同时输出电压信号的震荡波形也与实际电路示波器显示的纹波形状一致且震荡幅度大致相同。这也印证了寄生电阻的计算以及电路建模与仿真的正确性。

3.5 本章小结

本章主要介绍了直流Buck控制器及其闭环控制系统的建模过程,通过分析Buck电路中的元件特性及闭环控制的各个环节,实现控制系统的数学建模,得到系统的开环传递函数与特征方程;同时结合Buck控制器开环控制实验的实际测量结果,关注到电容寄生电阻对于系统输出的重要影响,并通过输出电压纹波的相关特性对其进行计算,搭建PSIM电路仿真模型观察修正前后的仿真结果,发现引入寄生电阻后的仿真结果与实际实验波形输出基本一致从而说明考虑寄生电阻的必要性。除此之外,还分别搭建了Buck变换器的开环与闭环控制系统PSIM仿真电路并对比输出电压仿真结果,可以发现闭环控制系统具有更好的动态响应性能,其稳定性、快速性与准确性均优于开环控制系统。

4 直流Buck变换器控制性能分析

4.1 直流Buck变换器劳斯稳定判据分析

基于3.1节得到的开环传递函数与系统特征方程,可利用劳斯判据给出系统稳定的PI控制器比例系数K_p临界条件:

根据系统特征方程可给出如下劳斯表:

劳斯表

根据劳斯判据,要使得系统稳定,需同时满足如下条件:

$$
\left{ \begin{array}{r}
\ \tau V_{M}LC > 0 \
\tau V_{M}\frac{L}{R} > 0 \
\left( V_{in}K_{p} + V_{M} \right)\tau - V_{in}K_{p}RC > 0 \
{\ \ V}{in}K{p} > 0
\end{array} \right.\
$$
这是关于PI控制器比例系数K_p的不等式组,可解得其边界条件为:

$$
0 < K_{p} < \frac{V_{M}\tau}{V_{in}(RC - \tau)}
$$
若选定控制环路的时间常数
$$
\tau = 0.004$
$$
三角载波幅值
$$
V_{M} = 0.5V
$$
则可代入数值解得:比例系数K_p的稳定边界为
$$
\frac{V_{M}\tau}{V_{in}(RC - \tau)} \approx 0.0513
$$
而在实际情况下,需要考虑电容的寄生电阻
$$
ESR \approx 107.185m\Omega
$$
这意味着需要对3.1节建立的数学模型进行修正。显然电路结构中除二阶滤波器外的其他环节没有发生改变,针对考虑电容寄生电阻ESR的RLC并联二阶输出滤波器,由电路图与元件特性可得各元件间电流电压关系:

$$
\left{ \begin{array}{r}
u_{0} = Ri_{2} \
u_{L} = L\frac{di}{dt} \
i_{1} = C\frac{du_{C}}{dt} \
i = i_{1} + i_{2} \
u_{0} = u_{C} + ESRi_{1} \
u_{D} = u_{L} + u_{0}
\end{array} \right.\
$$
从而可得该滤波器对应的微分方程,对两边同时进行拉普拉斯变换,化简整理后可得二阶滤波器的传递函数G_vd(s):

$$
G_{vd}(s) = \frac{U_{0}(s)}{U_{D}(s)}V_{in} = \frac{\frac{1}{LC} + \frac{ESR}{L}s}{(1 + \frac{ESR}{R})s^{2} + (\frac{1}{RC} + \frac{ESR}{L})s + \frac{1}{LC}}V_{in}
$$
其他环节保持不变,于是有开环传递函数:
$$
T(s) = G_{c}(s)G_{pwm}G_{\text{vd}}H(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC} + \frac{ESR}{L}s}{\left( 1 + \frac{ESR}{R} \right)s^{2} + \left( \frac{1}{RC} + \frac{ESR}{L} \right)s + \frac{1}{LC}}V_{in} \approx \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{1 + C \bullet ESRs}{LCs^{2} + \frac{L}{R}s + 1}V_{in}
$$
因此近似后系统的特征方程
$$
T(s) + 1 = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{(1 + C \bullet ESRs)V_{in}}{LCs^{2} + \frac{L}{R}s + 1} + 1 = 0
$$
化简后可得:

$$
\Delta(s) = {(1 + C \bullet ESRs)V}{in}K{p}(\tau s + 1) + \tau sV_{M}\left( LCs^{2} + \frac{L}{R}s + 1 \right) = V_{M}LC\tau s^{3} + (V_{M}\frac{L}{R} + C \bullet ESR \bullet V_{in}K_{p})\tau s^{2} + (V_{in}K_{p}\tau + V_{M}\tau + C \bullet ESR \bullet V_{in}K_{p})s + V_{in}K_{p} = 0
$$
接下来利用劳斯判据给出系统稳定的PI控制器比例系数K_p临界条件:

根据系统特征方程可给出如下劳斯表:

劳斯表

根据劳斯判据,要使得系统稳定,需同时满足如下条件:

$$
\left{ \begin{array}{r}
\ \tau V_{M}LC > 0 \
\tau V_{M}\frac{L}{R} > 0 \
V_{in}K_{p}\tau + V_{M}\tau + C \bullet ESR \bullet V_{in}K_{p} - \frac{V_{M}LCV_{in}K_{p}}{V_{M}\frac{L}{R} + C \bullet ESR \bullet V_{in}K_{p}} > 0 \
{\ \ V}{in}K{p} > 0
\end{array} \right.\
$$
这是关于PI控制器比例系数K_p的不等式组,其中第1、2、4个不等式均指向K_p > 0,而第三个不等式是一个关于K_p的一元二次不等式,代入电路元件参数可解得
$$
K_{p} < - 0.0232或K_{p} > - 0.0067
$$
因此可得到K_p的稳定边界为:
$$
K_{p} > 0
$$
即该情况下系统始终稳定。

4.2 直流Buck变换器系统根轨迹分析(手绘+MWorks绘制)

基于3.1节得到的开环传递函数,代入数值可得:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{30K_{p}(s + 250)}{s(6.6*10^{- 8}s^{2} + 10^{- 5}s + 1)}
$$
根据该开环传递函数可知,该系统:

具有1个零点:
$$
z_{1} = 250
$$
具有3个极点:
$$
p_{0} = 0,p_{1} + p_{2} = \frac{5}{33}*10^{3} \approx 151.5,p_{1} = \frac{5}{66}*10^{3} + a_i,p_{1} + p_{2} = \frac{5}{66}*10^{3} - a_i
$$
故有3条根轨迹,1条止于开环零点,2条止于无穷远;

根轨迹的渐近线与实轴的夹角
$$
\varphi_{a} = \pm \frac{2k + 1}{2}\pi(k = 0,1,2\ldots) = \pm 90{^\circ}、180{^\circ}
$$
渐近线与实轴交点的坐标值
$$

  • \delta_{a} = \frac{\sum_{}^{}\left( - p_{i} \right) - \sum_{}^{}\left( - z_{i} \right)}{2} = \frac{- \frac{5}{33}*10^{3} + 250}{2} \approx 49.24
    $$

基于以上结果,可手绘根轨迹草图如下:

图4.1:不考虑寄生电阻------手绘根轨迹草图

编写如下MWorks代码:

1
2
3
4
using TyControlSystems
s=tf('s');
G=(s+250)/(s*(6.6*10^(-8)*s*s+10^(-5)*s+1));
rlocus(G);

运行该段代码,得到MWorks绘制的根轨迹图如下:

图4.2:不考虑寄生电阻------MWorks绘制根轨迹图

根轨迹本质上反映的随着比例系数K_p的变化,特征方程根的变化情况;当两个共轭根恰好位于虚轴上时,此时对应的比例系数K_p(可将此时特征根带回特征方程求出)即为其稳定的边界值(大于该值不稳定,小于该值稳定)。

编写如下MWorks程序,寻找根轨迹与虚轴的交点并带回特征方程,求出比例系数K_p稳定边界值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% 找寻与虚轴交点
k = linspace(0, 10, 1000); % 增加点数,1000个点
[r, k]=rlocus(G,k);
real_part = real(r);
imag_part = imag(r);
% 查找实部接近零的索引
tolerance = 1e-1; % 设定阈值
idx = find(abs(real_part) < tolerance); % 找到所有交点的索引
intersections = r(idx); % 交点的复数值
% 求解临界kp
s = intersections(2);
% 系统参数
tao = 0.004;
vm = 0.5;
l = 10^(-4);
c = 6.6*10^(-4);
vin = 15;
r = 10;
syms kp; % 定义符号变量
eq = tao*vm*l*c*s*s*s + tao*vm*l*s*s/r + vin*kp*tao*s + vm*tao*s + vin*kp == 0;
solutions = solve(eq, kp); % 求解
real_solutions = real(solutions); % 取实部
decimal_solutions = double(real_solutions); % 转换为小数
disp(decimal_solutions); % 显示结果

运行上述MWorks代码,得到的比例系数K_p稳定边界值结果为:0.0514,这与先前依据劳斯判据得到的结果
$$
\frac{V_{M}\tau}{V_{in}(RC - \tau)} \approx 0.0513
$$
大致一致。

而在实际情况下,需要考虑电容的寄生电阻;基于4.1节修正后的开环传递函数,代入数值可得:

$$
T(s) \approx \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{1 + C \bullet ESRs}{LCs^{2} + \frac{L}{R}s + 1}V_{in} \approx \frac{30K_{p}(s + 250)(1 + 7.0710^{- 5}s)}{s(6.610^{- 8}s^{2} + 10^{- 5}s + 1)}
$$
根据该开环传递函数可知,该系统:

具有2个零点:
$$
z_{1} = 250,z_{2} \approx 1.41*10^{5}
$$
具有3个极点:
$$
p_{0} = 0,p_{1} + p_{2} = \frac{5}{33}*10^{3} \approx 151.5,p_{1} = \frac{5}{66}*10^{3} + bi,p_{1} + p_{2} = \frac{5}{66}*10^{3} - bi
$$
故有3条根轨迹,2条止于开环零点,2条止于无穷远;

根轨迹的渐近线与实轴的夹角
$$
\varphi_{a} = \pm (2k + 1)\pi(k = 0,1,2\ldots) = 180{^\circ}
$$
基于以上结果,可手绘根轨迹草图如下:

图4.3:考虑寄生电阻------手绘根轨迹草图

编写MWorks代码如下:

1
2
3
4
5
6
using TyControlSystems
s=tf('s');
c = 6.6*10^(-4);
esr = 0.107185;
G=(s+250)*(1+c*esr*s)/(s*(6.6*10^(-8)*s*s+10^(-5)*s+1));
rlocus(G);

运行该段代码,得到MWorks绘制的根轨迹图如下:

图4.4:考虑寄生电阻------MWorks绘制根轨迹图

可以看到,在考虑电容寄生电阻的情况下,根轨迹完全位于虚轴左侧,这意味着无论比例系数K_p(>0)如何变化,系统特征方程的根均位于虚轴左侧,即此情况下系统始终稳定,这与先前依据劳斯判据得到的结果也是一致的。这样的结果也充分说明,寄生电阻的加入使得系统的稳定性提高。

4.3 直流Buck变换器奈奎斯特稳定判据分析

在不考虑寄生电阻的情况下,基于3.1节得到的开环传递函数,取负载电阻R = 2欧姆,代入PI控制器参数:
$$
K_{p} = 0.05、\tau = 1\text{/}5000
$$
与三角载波幅值
$$
V_{M} = 3V
$$
可得:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{0.05 \times \left( \frac{1}{5000}s + 1 \right)}{\frac{1}{5000}s} \cdot \frac{1}{3} \cdot \frac{15 \times 10^{8}}{6.6s^{2} + 5000s + 10^{8}} = \frac{2.5 \times 10^{7}(s + 5000)}{s(6.6s^{2} + 5000s + 10^{8})}
$$
根据该开环传递函数,编写MWorks代码如下:

1
2
3
using TyControlSystems
H = tf([2.5*10^7 2.5*10^7*5000],[6.6 5000 10^8 0]);
nyquist(H);

运行该段代码,得到MWorks绘制的奈氏图如下:

图4.5:不考虑寄生电阻------MWorks绘制奈氏图图4.5:不考虑寄生电阻------MWorks绘制奈氏图

观察奈氏图图像可得:正穿越次数N+=1,负穿越次数N- =1

又由系统开环传递函数可知:系统开环右极点数P=0

故由奈奎斯特稳定判据可知:该闭环系统稳定。

图4.6:奈奎斯特稳定判据

4.4 直流Buck变换器系统波特图分析(MWorks绘制)

与4.3节使用相同参数,即开环传递函数可写为:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{2.5 \times 10^{7}(s + 5000)}{s(6.6s^{2} + 5000s + 10^{8})}
$$
根据该开环传递函数,编写如下MWorks代码绘制波特图:

1
2
3
4
using TyControlSystems
s=tf('s');
G=2.5*10^7*(s+5000)/(s*(6.6*s^2+5000*s+10^8));
bode(G);

运行该段代码,得到MWorks绘制的波特图如下:

图4.7:不考虑寄生电阻------MWorks绘制波特图

从图中可读出:剪切频率
$$
\omega_{c}≈1.49*10^{3}rad/s
$$
相位裕度
$$
γ≈102{^\circ}
$$
根据对数频率特性稳定判据可知:当相位裕度γ与增益裕度Lg同时大于零时,闭环系统稳定;而对于最小相位系统,相位裕度γ>0与增益裕度Lg>0是同时发生或同时不发生的,因此只需通过相位裕度γ即可判定闭环系统稳定情况。显然此时相位裕度大于零,表明闭环系统稳定,且稳定性较好(相位裕度较大)。

4.5 直流Buck变换器闭环负载稳定边界计算及仿真验证

基于2.2节中的电路元件参数以及输入电压恒为15V的客观事实,在给定PI控制器参数:
$$
K_{p} = 0.1、\tau = 0.004
$$
与三角载波幅值
$$
V_{M} = 0.5V
$$
的情况下,若不考虑电容寄生电阻,根据4.1节劳斯判据化简后的结果:
$$
RCV_{g}K_{p} \leq (\tau V_{M} + V_{g}K_{p}\tau)
$$
可推得系统处于稳定状态的电阻值范围应为:
$$
R \leq \frac{\tau}{C}\left( \frac{V_{M}}{V_{g}K_{p}} + 1 \right) \approx 8.08\Omega
$$
即直流Buck变换器闭环系统的负载电阻稳定边界约为8.1Ω。

为进一步通过仿真验证计算结果,建立PSIM仿真电路图如下:

图4.8:直流Buck变换器闭环控制负载电阻稳定边界PSIM仿真验证电路图

  1. 取负载电阻值R = 5Ω(小于临界值)时:

图4.9:负载电阻值R=5Ω时输出电压PSIM仿真结果

图4.10:负载电阻值R=5Ω时输出电压纹波PSIM仿真结果

  1. 取负载电阻值R = 8.1欧姆(约等于临界值)时:

图4.11:负载电阻值R=8.1Ω时输出电压PSIM仿真结果

图4.12:负载电阻值R=8.1Ω时输出电压纹波PSIM仿真结果取负载电阻值R=10Ω(大于临界值)时:

图4.13:负载电阻值R=10Ω时输出电压PSIM仿真结果

图4.14:负载电阻值R=10Ω时输出电压纹波PSIM仿真结果

通过对比三组仿真结果可以发现:取不同的负载电阻值并不会影响系统的响应速度与响应瞬时超调量,而是影响输出电压趋于稳定后的纹波波形:

  1. 当负载电阻值小于临界值时,稳定后的输出电压会有较大的震荡(负载越小,震荡幅度越大),但该震荡上没有纹波,系统处于稳定状态;

  2. 当负载电阻接近临界值(实际临界值略小于8.1Ω)时,稳定后输出电压的震荡幅度减小,但开始出现纹波,系统处于临界稳定状态;

  3. 当负载电阻大于临界值时,稳定后输出电压的震荡幅度进一步减小,但纹波幅度有所增大,系统处于不稳定状态。

4.6 考虑寄生参数直流Buck变换器波特图分析(MWorks绘制)

在实际情况下,为实现对直流Buck变换器闭环控制系统更加精准的建模,需要考虑电容的寄生电阻,基于4.1节修正后的开环传递函数(取未近似结果):

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC} + \frac{ESR}{L}s}{\left( 1 + \frac{ESR}{R} \right)s^{2} + \left( \frac{1}{RC} + \frac{ESR}{L} \right)s + \frac{1}{LC}}V_{in}
$$
取PI控制器参数:
$$
K_{p} = 1、\tau = 0.01
$$
三角载波幅值
$$
V_{M} = 2V
$$
负载电阻
$$
R = 2\Omega
$$
代入电路各元件参数与3.4节测算得到的寄生电阻
$$
ESR \approx 107.185m\Omega
$$
编写如下MWorks程序绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
using TyControlSystems
s=tf('s');
vm=2;
r=2;
esr=0.107185;
c=6.6\*10\^(-4);
l=10\^(-4);
vg=15;
kp=1;
tao=0.01;
G=kp\*(tao\*s+1)\*(esr\*c\*s+1)\*vg/(tao\*s\*vm\*(l\*c\*(1+esr/r)\*s\*s+(l/r+esr\*c)\*s+1));
bode(G);

运行该段代码后绘制出的波特图如下:

图4.15:考虑寄生电阻------MWorks绘制波特图

从图中可读出:剪切频率
$$
\omega_{c}≈1.29*10^{4}rad/s
$$
相位裕度
$$
γ≈50{^\circ}
$$
根据对数频率特性稳定判据可知,此时相位裕度γ>0,表明闭环系统稳定。

4.7 PSIM仿真扫频波特图

取与4.6节相同的参数,利用PSIM搭建扫频电路进行仿真得到波特图:

图4.16:考虑寄生电阻------PSIM扫频仿真电路图

图4.17:考虑寄生电阻------PSIM扫频仿真结果

从图中可读出:剪切频率
$$
\omega_{c} \approx 1997Hz \approx 1.25*10^{4}rad/s
$$
相位裕度
$$
γ≈50{^\circ}
$$
扫频仿真结果与MWorks根据系统开环传递函数绘制的波特图基本一致。

若在仿真电路图中加入锁存器与零阶保持器:

图4.18:考虑寄生电阻------加入锁存器与零阶保持器后PSIM扫频仿真电路图

图4.19:考虑寄生电阻------加入锁存器与零阶保持器后PSIM扫频仿真结果

图4.19:考虑寄生电阻——加入锁存器与零阶保持器后PSIM扫频仿真结果

从图中可读出:剪切频率
$$
\omega_{c}≈1997Hz
$$
相位裕度
$$
γ≈18{^\circ}
$$
可以发现,在仿真中加入锁存器与零阶保持器后,剪切频率基本未发生改变,图线也大致与MWorks通过传递函数得到的波特图相同,但相位裕度有所降低,系统仍然保持稳定。

4.8 本章小结

本章主要对于直流Buck变换器闭环控制系统的控制性能进行了详细的分析,特别是针对系统的稳定性问题,从系统的开环传递函数入手,对于考虑电容寄生电阻与否的两种不同情况,在时域上利用劳斯稳定判据与根轨迹法,讨论在负载电阻R与PI控制器时间常数τ给定的情况下,PI控制器比例系数K_p的稳定边界;在频域上依据奈奎斯特稳定判据(基于奈奎斯特图)与对数频率特性稳定判据(基于波特图,由MWorks绘制与PSIM扫频仿真得到,两者结果高度吻合),通过系统相位裕度γ判断闭环系统稳定性,同时在PI控制器参数给定的情况下讨论负载电阻R的稳定边界,并搭建PSIM仿真电路对不同负载电阻下闭环系统的稳定性进行验证,仿真结果与理论推导结论基本一致。

5 直流Buck变换器闭环控制实验

5.1 KEIL5软件环境安装及创建

Keil 5是一款集成开发环境(IDE),它以其强大的功能和丰富的特性,在嵌入式系统开发领域占据了重要的地位。在本项目中,需要借助该IDE编写主控代码并烧录至STM32芯片中,使其能够在闭环控制系统中正常发挥控制作用。

下载助教在课程群内上传的安装程序压缩包并解压,文件夹内包含如下文件:

图5.1:Keil5 MDK安装程序压缩包解压结果

双击安装程序”MDK524a.EXE”,一路点击”NEXT>>“(注意勾选同意许可证条款与指定软件安装位置)即可完成Keil5软件安装;软件安装完成后会自动运行,关闭后需要重新以管理员身份运行Keil5(右键->以管理员身份运行),随后进入keygen_new2032文件夹,双击运行破解程序”keygen_new2032.exe”,输入Keil5软件内”File->Licence Management…”里的CID并选择ARM,点击”Generate”并将生成的序列号复制到Keil5软件内”File->Licence Management…”下方的LIC一栏,点击”ADD LIC”即可完成破解。

图5.2:Keil5 MDK软件破解流程图5.2:Keil5 MDK软件破解流程

图5.2:Keil5 MDK软件破解流程图5.2:Keil5 MDK软件破解流程

除此之外,针对本项目选用的STM32F103C8T6芯片,还需要安装相关芯片库:双击”支持包”文件夹中的”Keil.STM32F1xx_DFP.2.2.0.pack”文件并点击”NEXT>>“即可完成安装。

完成软件环境的安装后,需要进入Keil5软件,点击上侧菜单栏中”Project->New μVision Project”新建工程,随后会进入到芯片设备的选择环节,选择芯片”STM32F103C8”并点击”OK”,在弹出的Manage Run-Time Environment对话框中再次点击”OK”即可完成工程创建。

图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程

当然事实上这样建立的工程仍然无法正常使用,还需要引入一系列启动文件与库函数文件等,具体流程在此不详细赘述,可以参考博客:如何在keil中建立一个STM32F10x完整工程。至此Keil5的软件环境安装及工程创建过程已经全部完成,再额外导入一些系统辅助函数文件(如”Delay.c”等),即可在”main.c”文件中进行闭环控制程序的编写了。

5.2 ADC采样及PWM生成原理

ADC(Analog-to-Digital Converter),即模拟到数字转换器,主要用于将连续传输的模拟信号转换为数字信号,便于数字系统(如中央处理器CPU、微控制器MCU等)对传输信息进行快速处理和分析。

图5.4:STM32F103C8T6芯片参考手册中ADC相关模块

采样是指ADC在一定时间间隔内对连续变化的模拟信号进行取样,得到一系列离散的样本点,实现在有限采样率条件下,无失真还原信号波形信息。采样率决定了每秒采集的样本量,通常单位为Hz;其必须满足奈奎斯特采样定理(大于信号最高频率的两倍),否则会产生混叠。

由于数字信号本身不具有实际意义,仅仅表示一个相对大小,故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小,而输出的数字量则表示输入信号相对于参考信号的大小。在STM32单片机中,ADC为12位,即单片机读取的ADC值应在0~4095范围内,这样的ADC值与0~3.3V的输入电压值之间存在线性对应关系(若输入电压范围超出0~3.3V,则需要在输入ADC引脚前加入电阻分压和放大器等外围电路,在2.3.4小节中有详细介绍该部分采样电路)。

PWM(Pulse Width Modulation),即脉冲宽度调制,是一种通过调节脉冲信号的宽度(即占空比,高电平持续时间占整个周期的比例)来控制输出信号平均值的方法。在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量。简单而言,PWM可以视为一种DA(数字->模拟)转换,通过产生PWM波形这一数字信号等效地实现了模拟信号的输出。

图5.5:PWM信号示意图

PWM实现的原理是:通过锯齿波/三角波(载波)所需要合成的波形(调制波)进行比较,然后确定PWM所需要输出的极性,锯齿波从比较器的反相端端输入,当大于参考电压时输出与锯齿波相反的极性,而当锯齿波从比较器同相端输入,当大于参考电压时输出与锯齿波相同极性。

图5.6:PWM实现流程:黄色线------计算器溢出频率(ARR),即PWM更新频率;红色线------捕获比较寄存器(CRR)的值;蓝色线------计数器(CNT)的值

在Keil工程中,基于以上PWM生成原理编写函数文件”PWM.c”,其中包含了生成PWM波形所需的全部函数。实际运行时,PWM波形是通过定时器的计数和输出比较模块的配合生成的,其具体过程如下:

(1)计数器计数:TIM2定时器按照内部时钟驱动,从0计数到ARR(2999),然后重新清零,重复循环。

(2)比较与输出:定时器每次计数到CCR(1500)时,切换PWM输出的电平状态:从计数开始到计数器值为CCR时,输出高电平;从计数器值为CCR到ARR,输出低电平。

(3)占空比:由CCR与ARR的比值决定。程序中初始设定CCR=1500,ARR=2999,故可计算得出占空比为
$$
\frac{CCR}{ARR + 1} = 50%
$$
同时在主程序的控制过程中还可通过函数PWM_SetCompare1动态设置CCR值,从而实时改变占空比,进而影响输出的等效电压值。

5.3 基本PI控制理论及程序

PI控制器是比例-积分控制器的一种,其核心是通过调节控制变量(输出电压),使系统输出接近目标值,是在控制工程中技术成熟、理论完善、应用最为广泛的一种控制策略。

对于PI控制器而言,其控制量的计算公式为:
$$
u(t) = K_{P} \cdot \mathbb{e}(t) + K_{I}\int\mathbb{e}(t)\mathbb{d}t
$$
其中:

(1)e(t):当前时刻系统输出电压与参考电压之间的误差值;

(2)K_P:比例增益系数,用于快速响应;

(3)K_I:积分增益系数,用于消除稳态误差;

(4)u(t):待控制变量——系统输出电压。

写成微分方程形式则为:
$$
\left{ \begin{aligned}
& \dot{x} = K_{I}v \
& y = K_{p}v + x
\end{aligned} \right.
$$
根据传递函数定义,其传递函数可表示为:

$$
G_{c}(s) = K_{p} + K_{I}\frac{1}{s} = \frac{K_{p}(\tau s + 1)}{\tau s}
$$
其中积分时间常数
$$
\tau = \frac{K_{p}}{K_{I}}
$$
PI控制器具有以下特点:

  1. 从时域上看,只要存在偏差,积分就会不停对偏差积累,因此稳态时误差一定为零;

  2. 比例与积分动作都是对过去控制误差进行操作,不对未来控制误差进行预测,限制了控制性能;

  3. PI调节将比例调节的快速反应与积分调节消除静差的特点结合,主要用于改善控制系统的稳态性能。

图5.7:具有PI控制器的控制系统控制框图

在Keil工程中的主函数文件”main.c”中,其main函数在进行了PWM、ADC与串口等必要的初始化后,在while循环中反复运行update_voltage_reference与control_buck两个函数,其中前者主要是进行参考电压的动态更新,而后者中则包含了PI控制的主要逻辑,部分核心代码如下:

1
2
3
4
error = voltage_ref \* 2500 \* 1000 / 1050 / 20000 - voltage_fb;
integral += error;
control_signal = KP \* error + KI \* integral;
last_error = error;

该段代码主要按照如下流程实现PI控制:

  1. 误差计算:error
    $$
    e(t) = V_{ref} - V_{fb}
    $$

其中V_ref(voltage_ref)为函数update_voltage_reference中设定的目标电压,而V_fb(voltage_fb)为系统输出并反馈至控制器的实际电压。

  1. 积分计算:
    $$
    integral = \int\mathbb{e}(t)\mathbb{d}t
    $$
    为累加误差,用于消除系统的稳态误差(输出电压voltage_fb与参考电压voltage_ref的差值error)。

  2. PI控制量:control_signal
    $$
    u(t) = K_{P} \cdot \mathbb{e}(t) + K_{I}\int\mathbb{e}(t)\mathbb{d}t
    $$
    为最终控制信号,用于调节PWM占空比。

除此之外,为防止占空比超出合理范围,还对控制信号control_signal进行约束:

1
2
3
4
if (control_signal \> 0.8)
control_signal = 0.8;
else if (control_signal \< 0.2)
control_signal = 0.2;

除了PI控制的核心逻辑外,在主控函数control_buck中还实现了其他功能:

  1. 模拟信号采集得到系统输出并反馈至控制器的实际电压V_fb(voltage_fb):
1
2
voltage_fb = (float)AD_GetValue()\*3.3/4096;
trueVoltage = voltage_fb \* 1050 \* 20000 / 2500 / 1000;
  1. 根据PI控制结果,实时更新PWM占空比:
1
2
3
duty_cycle = control_signal \* PWM_PERIOD;
i = (int)duty_cycle;
PWM_SetCompare1(i);
  1. 通过串口发送调试信息,用于监控采样值和控制效果:
1
Serial_Printf(\"%d,%.2f\\r\\n\", sample_index, trueVoltage);

5.4 闭环PI稳压调控输出(8V、10V)

5.3节中对于主控函数control_buck进行了详细的解析,整个闭环PI调控过程都由此函数完成,在此不重复赘述;而对于函数update_voltage_reference而言,该函数实现了电压的切换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void update_voltage_reference(void){
time_counter += 40; // 每次调用增加计时器值
if (time_counter \>= 10000) // 达到10秒{
time_counter = 0; // 重置计时器
if (voltage_state == 0){
voltage_ref = 10.0; // 切换到10V
voltage_state = 1; // 更新状态
}
else{
voltage_ref = 8.0; // 切换到8V
voltage_state = 0; // 更新状态
}
}
}

可以看到,该函数每隔10秒就对于目标电压voltage_ref进行一次切换,由10V切换为8V,再由8V切换回10V,循环往复。该函数主要用于模拟动态负载或参考值变化的情景,以测试控制器再目标电压变化时的响应性能。

事实上,在main函数中,在进行各项初始化(PWM、ADC、串口等)后,while循环中只有参考电压切换函数update_voltage_reference与PI主控函数control_buck反复作用,也正是这两个函数使得该直流Buck变换器闭环控制PI系统能够交替稳压输出8V或10V的电压。

5.5 闭环参考电压8V-10V连续跳变实验与分析(仿真+实验)

在直流Buck变换器闭环PI控制系统参考电压8V-10V连续跳变实验时,重点对于输出电压分别为8V与10V的两种情况下单片机的PWM输出波形进行测试,通过观察其占空比反映其输出电压情况:

  1. 先使用仿真器给单片机供电,以调试PWM波形输出是否正常;

  2. 将上述闭环控制程序放入Keil工程中,成功编译后烧录至单片机内,将单片机(最小系统板)插入电路板上预留的引脚接口处并上电测试,使用示波器或者上位机观测电路板PA8端口输出PWM波形的占空比。

图5.8:参考电压设置为10V时的PWM波形

图5.9:参考电压设置为8V时的PWM波形

可以看到,在参考电压设置为8V时,单片机输出的PWM占空比明显小于参考电压为10V时的结果。

为进一步观察参考电压连续跳变时的动态响应过程,进行了对应的PSIM电路仿真(仿真参数:kp=0.12,τ_i=1/5000,R=2Ω),其中跳变效果通过在直流参考电压后接入一个周期方波信号实现,跳变周期为0.1s:

图5.10:参考电压8V-10V连续跳变PSIM仿真电路图

图5.11:参考电压8V-10V连续跳变PSIM仿真动态响应结果

可以看到,跳变瞬间系统的动态响应较快(约为0.01s)且输出电压稳定后震荡幅度较小,说明该参数下系统具有较好的动态响应性能。

5.6 本章小结

本章主要介绍了对于直流Buck变换器PI闭环控制的整体实验流程,从STM32单片机编程环境的搭建,到根据PWM生成原理编写PWM的初始化函数及占空比实时调控函数,再到根据PI控制的基础理论编写对应的主控代码,在参考电压连续跳变(8V-10V)的情况下调控闭环PI的稳压输出,最后将代码烧录进行实际实验,测试单片机的PWM输出调控情况,并通过仿真观察参考电压连续跳变时的动态响应情况。通过该闭环控制实验,充分证明了PI闭环控制系统对于直流Buck变换器具有良好的控制效果。

6 复合PI控制直流Buck变换器

6.1 PI+超前校正(复合PI)分析设计

为提高PI闭环控制系统暂态响应速度,可以在原有的控制系统中加入一些其参数可以根据需要而改变的控制器,即对于系统进行校正,从而使整个系统的频率特性发生变化。本项目中针对直流Buck变换器的PI闭环控制系统,采用PI+串联超前校正的复合PI控制器,以进一步提升系统性能指标。

图6.1:串联超前校正环节示意图

串联超前校正的核心思想是,将补偿中心ω_ m设计为期望剪切频率ω_ c处,从而提升剪切频率(补偿中心)所对应相位,并利用补偿处增益放大(+20)来改善剪切频率处斜率。具体的设计流程如下:

  1. 根据静态性能指标,确定开环增益K

  2. 为确定校正环节的设计方向,根据所确定的开环增益,画出系统固有部分G_s(s)的波特图,并计算其剪切频率ω_ c1与相位裕度γ_ 0;

  3. 根据要求的相位裕度γ,确定
    $$
    \varphi_{m} = \mathrm{\Delta}\varphi = \gamma - \gamma_{0} + \varepsilon,\varepsilon =5° - 25°
    $$

    $$
    \varphi_{m} < 60{^\circ}
    $$
    说明可采用串联超前校正,即
    $$
    \varepsilon \approx \varphi(\omega_{c1})-
    \varphi(\omega_{c2}),\omega_{c1}为原穿越频率,\omega_{c2}为校正后穿越频率
    $$

  4. 由φ_m确定α:
    $$
    $\alpha = \frac{1 + \sin\varphi_{m}}{1 - \sin\varphi_{m}} > 1
    $$

  5. 令校正后剪切频率
    $$
    \omega_{c2} = \omega_{m} = \frac{1}{T\sqrt{\alpha}}
    $$

  6. 画出校正后系统的波特图,并验算相位裕度是否满足要求:若满足要求,则需要在原有PI控制器前增加环节
    $$
    G_{c}(s) = \frac{\alpha Ts + 1}{Ts + 1}
    $$
    若不满足要求,需增大ε的值,从第(3)步重新开始计算。

图6.2:超前校正环节一般设计流程

对于比例积分PI控制器,一般采用如下的超前校正方式:

图6.3:PI控制器超前校正电路原理图

图6.3:PI控制器超前校正电路原理图

针对直流Buck变换器的PI闭环控制系统,给定PI控制器参数:
$$
K_{p} = 0.8、\tau = 1/5000
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻):
$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
在MWorks中编写如下代码绘制校正前的波特图:

1
2
3
4
5
6
7
8
9
10
11
using TyControlSystems
s=tf('s');
vm=1;
r=2;
c=10^(-4);
l=10^(-4);
vg=20;
kp=0.8;
tao=1/5000;
G=kp*(tao*s+1)*vg/(tao*s*vm*(l*c*s*s+(l/r)*s+1));
bode(G);

绘制出校正前的波特图:

图6.4:校正前MWorks绘制波特图

从图中可读出:校正前系统的剪切频率
$$
\omega_{c1}≈4.20*10^{4}rad/s
$$
相位裕度
$$
\gamma_{0}≈0{^\circ}
$$
此时系统处于临界稳定状态。

为使得校正后的相位裕度
$$
\gamma > 30{^\circ}
$$
可确定需增加的相位裕度
$$
\varphi_{m} = \mathrm{\Delta}\varphi = \gamma - \gamma_{0} + \varepsilon = 30{^\circ} - 0{^\circ} + 15.5{^\circ} = 45.5{^\circ} < 60{^\circ}
$$
满足超前校正要求,进而可以确定

$$
\alpha = \frac{1 + \sin\varphi_{m}}{1 - \sin\varphi_{m}} \approx 6
$$
又因为系统周期
$$
T = \frac{1}{70000}s
$$
从而可以确定增加的超前校正环节的开环传递函数为:

$$
H(s) = \frac{\frac{6}{70000}s + 1}{\frac{1}{70000}s + 1}
$$
这意味着校正后整个闭环控制系统的传递函数变为
$$
T^{‘}(s) = T(s)H(s)
$$
可在上述MWorks代码的基础上增加下列代码以实现对于校正后闭环控制系统波特图的绘制:

1
2
3
H=(6*s/70000+1)/(s/70000+1);
G1=G*H;
bode(G1);

绘制出校正后的波特图:

图6.5:校正后MWorks绘制波特图

从图中可读出:校正后系统的剪切频率
$$
\omega_{c2}≈8.71*10^{4}rad/s
$$
相位裕度
$$
\gamma≈31{^\circ} > 30{^\circ}
$$
相位裕度较校正前有明显提升且满足系统对品质指标的要求,校正正确。

6.2 复合PI控制PSIM仿真(校正前后参考电压变化时输出电压分析)

首先通过PSIM扫频仿真对先前波特图的MWorks绘制结果进行验证:

图6.6:超前校正控制PSIM扫频仿真电路图

图6.7:校正前PSIM扫频仿真结果

图6.8:校正后PSIM扫频仿真结果

从图中可读出:校正前系统的剪切频率
$$
\omega_{c1}≈6554Hz
$$
相位裕度
$$
\gamma_{0}≈0{^\circ}
$$
校正后系统的剪切频率
$$
\omega_{c2}≈14126Hz
$$
相位裕度
$$
\gamma≈35{^\circ}
$$
与MWorks绘制的波特图结果基本一致。

为反映超前校正的引入对于系统暂态响应速度的提升效果,搭建PSIM仿真电路,分析校正前后参考电压跳变瞬间的输出电压响应情况:

图6.9:超前校正控制参考跳变PSIM仿真电路图

图6.10:校正前系统暂态响应PSIM仿真结果

图6.11:校正后系统暂态响应PSIM仿真结果

对比校正前后参考电压跳变瞬间的输出电压暂态响应PSIM仿真结果,可以发现:校正前系统暂态响应时间约为0.034s,而加入超前校正环节后系统暂态响应时间为0.0006s,较校正前有显著缩短,说明超前校正的引入有效提升了系统的动态响应特性(快速性)。

6.3 PI+微分环节D(PID)分析设计

除增加串联超前校正环节设计外,还可以通过在原有PI控制器基础上加入微分环节D实现PID闭环控制来提升系统的稳定性与暂态响应速度。事实上,在PID控制器的三个环节中,比例环节P主要负责动态性能的提升,积分环节I主要负责稳态精度的提升,而引入微分环节D则可进一步改善系统的稳定裕度以提升系统稳定性。

图6.12:PID控制器电路原理图

与PI控制器类似,通过对PID控制器的微分方程组进行拉普拉斯变换,可以得到其传递函数为:
$$
G_{c}(s) = \frac{K_{p}(\tau_{i}s + 1)}{\tau_{i}s} + \tau_{d}s
$$
其中τ_ i为积分时间常数,而τ_ d为微分时间常数。

接下来将通过MWorks绘制波特图,配合PSIM的扫频仿真结果,从相位裕度的角度来反映微分环节D的引入对于系统稳定性的提升:

对于PI控制器,给定控制器参数:
$$
K_{p} = 0.12、\tau = 1/5000
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻):
$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}$
$$
在MWorks中编写如下代码绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
using TyControlSystems
s=tf('s');
vm=1;
r=2;
esr=0.107185;
c=6.6*10^(-4);
l=10^(-4);
vg=15;
kp=0.12;
tao=1/5000;
G=kp*(tao*s+1)\*(esr*c*s+1)*vg/(tao*s*vm*(l*c*(1+esr/r)*s*s+(l/r+esr*c)*s+1));
bode(G);

图6.13:PI控制器MWorks绘制波特图

同时在PSIM中搭建扫频仿真电路,通过扫频仿真结果验证MWorks绘制波特图的正确性:

图6.14:PI控制器PSIM扫频仿真电路图

图6.15:PI控制器PSIM扫频仿真结果

从图中可读出:PI闭环控制系统剪切频率
$$
\omega_{c}≈6.78*10^{3}rad/s \approx 1105Hz
$$
相位裕度
$$
\gamma$≈$10{^\circ}
$$
且MWorks绘制波特图与PSIM扫频仿真结果基本一致。

对于加入微分环节D后的PID控制器,给定控制器参数:
$$
K_{p} = 0.12、\tau_{i} = 1/5000、\tau_{d} = 0.00001
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻)并将其中PI控制器的传递函数替换为PID控制器的传递函数,可得到PID闭环控制系统的开环传递函数:
$$
T(s) = (\frac{K_{p}(\tau_{i}s + 1)}{\tau_{i}s} + \tau_{d}s)\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
在MWorks中编写如下代码绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
13
using TyControlSystems
s=tf('s');
vm=1;
r=2;
esr=0.107185;
c=6.6*10^(-4);
l=10^(-4);
vg=15;
kp=0.12;
tao=1/5000;
taod=0.00001;
G=(kp*(tao*s+1)+taod*tao*s*s)*(esr*c*s+1)*vg/(tao*s*vm*(l*c*(1+esr/r)*s*s+(l/r+esr*c)*s+1));
bode(G);

图6.16:PID控制器MWorks绘制波特图

同时在PSIM中搭建扫频仿真电路,通过扫频仿真结果验证MWorks绘制波特图的正确性:

图6.17:PID控制器PSIM扫频仿真电路图

图6.18:PID控制器PSIM扫频仿真结果

从图中可读出:PID闭环控制系统剪切频率
$$
\omega_{c}≈6.78*10^{3}rad/s \approx 1026Hz
$$
相位裕度
$$
\gamma≈36{^\circ}
$$
且MWorks绘制波特图与PSIM扫频仿真结果基本一致。

通过对比PI和PID控制下的波特图与扫频结果可以发现,相比于PI控制器,PID控制下的直流Buck变换器闭环系统具有更高的相位裕度,这意味着在加入微分环节D之后,系统具有更好的稳定性。

6.4 PID控制PSIM仿真(校正前后R变化时输出电压分析)

为反映微分环节D的引入对于系统稳定性与响应速度的提升效果,搭建PSIM仿真电路,分析PI与PID控制器在负载电阻R不同时输出电压响应情况:

图6.19:PI控制器动态响应PSIM仿真电路图

图6.20:PI控制器负载电阻R=2Ω时动态响应PSIM仿真结果

图6.21:PI控制器负载电阻R=5Ω时动态响应PSIM仿真结果

图6.22:PID控制器动态响应PSIM仿真电路图

图6.23:PID控制器负载电阻R=2Ω时动态响应PSIM仿真结果

图6.24:PID控制器负载电阻R=5Ω时动态响应PSIM仿真结果

对比PI与PID控制器在负载电阻R不同时的输出电压暂态响应PSIM仿真结果,可以发现:当负载电阻R=2Ω时,在原有的PI控制器闭环控制下,系统的暂态响应时间约为0.008s,而加入微分环节D后,系统在PID控制器的闭环控制下暂态响应时间为0.004s,这意味着在加入微分环节D后,系统的动态响应特性得到一定提升(响应时间缩短50%);除此之外,在相同的控制器作用下,随着负载电阻阻值由2Ω增大到5Ω,系统的输出电压在稳定后的振荡幅度明显减,但此时暂态响应时间也明显增加(增加近两倍)。

6.5 本章小结

本章主要介绍了基于PI控制器的校正设计,对于直流Buck变换器的闭环控制而言,分别采用在原有控制系统上增加超前校正环节与微分环节D的两种校正方式实现复合PI控制,通过MWorks绘制波特图与PSIM扫频仿真,反映校正环节的引入对于相位裕度即系统稳定性的提升效果,并结合PSIM电路仿真的输出电压结果,观察校正环节的引入对于系统暂态响应速度即动态响应特性的提升效果。仿真实验结果表明,对于PI控制器的校正设计(超前校正/引入微分环节形成PID控制)在提升系统稳定性与动态响应快速性方面取得了良好的效果。

7 基于直流电源调控的自动调光控制设计

该系统的主要功能是将灯的电压通过直流Buck变换器完成闭环PID控制,将输出电压控制在0-15V范围内。系统主要包含电量控制与亮度控制两个部分,其中电量控制由STM32数字控制器通过电压控制信号调节Buck变换器输出电压;亮度控制则通过光敏传感器感知外界光强,并将反馈信号传递给STM32,实现对LED灯源亮度的动态调整,从而形成电压与亮度的双闭环调节系统。

图7.1:基于直流电源调控的自动调光系统整体架构

基于该架构,系统设计的基本流程如下:

  1. 搭建Buck电路控制系统,用于调控LED灯源;

  2. 使用开发工具Keil5编写STM32控制程序,实现PWM信号生成和调节;

  3. 将光敏传感器的输出连接到STM32的ADC(模数转换器)端口;

  4. 设计闭环控制系统并编写主控程序,实现根据光照强度自动调节PWM占空比,从而控制LED灯光的亮度;

  5. 调试系统,过程中实时监测电路工作状态,确保电路安全稳定运行且光照强度变化时LED灯亮度能够迅速进行响应调整;

  6. 对系统功能进行测试与优化,验证系统的响应速度和调光精度。

图7.2:基于直流电源调控的自动调光系统设计流程

除此之外,在系统基本功能的设计实现基础上,还进行了基于无线通信的远程控制这一拓展功能设计,将调光系统与蓝牙无线通信模块结合,实现通过手机应用对LED灯进行调光控制,增加系统的便携性和灵活性。

接下来将分模块具体阐述自动调光控制系统各功能设计的详细过程。

7.1 默认电压值设置

在系统上电之后,输出电压默认设置为9V。之所以选择9V作为默认值,是因为这一电压能够在不消耗过多能量的情况下提供足够的亮度,同时避免由于过高或过低电压对系统性能的影响。

具体来说,9V的默认值具有以下几个优点:

(1)能耗平衡:相较于较低电压(5V),9V能够提供更高的亮度输出,同时不会像满电压(15V)那样带来显著的能量消耗,适合作为常规环境下的起始亮度。

(2)避免电压冲击:在系统刚启动时,设定较高的默认电压可能会导致电流冲击,影响电源和负载的寿命。9V作为中间值,有效降低了这种风险。

(3)用户体验优化:默认电压为9V时,灯光亮度适中,避免了开机过亮或过暗对用户造成的不适,同时为后续手动或自动调节提供了便利。

(4)环境适应性:在普通家庭或办公室环境下,9V的亮度通常能满足基本照明需求,而无需立即调整,增强了系统的即用性。

此外,9V的默认值通过主程序固化设置,确保每次系统启动时都能快速恢复到该默认电压值。这一设置不仅提高了系统的稳定性,还为后续用户操作提供了可靠的初始状态。设计中对默认电压的选择经过多次实验验证,综合考虑了实际照明需求和电源性能,最终确定了这一合理数值。

7.2 蓝牙接口通信控制

在本系统中,蓝牙模块被用作与手机或其他移动设备的通信桥梁,用户可通过蓝牙实现对灯光的远程控制,增强系统的操作便利性和用户体验。蓝牙模块选用低功耗蓝牙(Bluetooth Low Energy, BLE)技术,保证了通信的可靠性和功耗优化。具体而言,蓝牙通信具有以下优点:

  • 便捷性:用户可通过手机远程调节灯光,无需物理接触,尤其适合家庭场景。

  • 可扩展性:蓝牙接口支持更多功能指令的扩展,如灯光模式切换等。

  • 低成本实现:蓝牙模块硬件成本低,结合STM32可轻松实现稳定通信。

以下是蓝牙接口通信功能的详细设计:

  1. 硬件接口设计
  • 蓝牙模块选型:选用HC-05蓝牙模块,该模块具有支持串行通信(UART)功能,能与STM32单片机直接连接;模块通过UART接口与STM32的USART1通道连接,波特率设置为9600bps以平衡数据传输速率与稳定性。

  • 引脚连接:蓝牙模块的TX和RX引脚分别连接STM32的RX和TX引脚,模块的VCC和GND引脚分别接单片机供电端和地线,保证模块稳定供电。

  1. 软件功能设计

蓝牙通信通过STM32主程序实现对蓝牙模块接收指令的解析和响应。指令的解析流程大致是这样的:主程序中设定一个蓝牙接收缓冲区,用于存储用户发送的指令;当蓝牙模块接收到数据时,触发中断,将数据写入缓冲区;程序定时轮询缓冲区,并根据指令类型解析执行以下指令以实现功能,包括灯的开关、亮度百分比调整,以及环境光自动调节的触发:

  • “on”/“off”指令:通过电压直接跳转的方式实现即时开关

“on”:将输出电压设定为最大值15V,点亮灯光;

“off”:将输出电压设定为最小值0V,关闭灯光。

  • 亮度调整指令:接收一个0-100的整数,表示灯光亮度为15V的百分比。程序计算目标电压时依据公式:
    $$
    V_{target} = \frac{百分比指令}{100} \times 15
    $$
    调用PWM控制函数调整Buck变换器的输出电压至目标值,实现实时亮度调节。

  • “LL”指令:触发光敏传感器,启动环境光度自动调节模式(详见7.3节)。

  1. 通信协议设计

蓝牙通信基于简单的ASCII协议,用户可通过手机APP或终端工具发送指令。每条指令均以换行符\n结尾,便于解析。以下是通信协议的具体格式:

  • “on\n”:开启灯光。

  • “off\n”:关闭灯光。

  • “50\n”:将亮度设置为50%。

  • “LL\n”:启动环境光度自动调节模式。

通过蓝牙接口通信,系统不仅支持手动调节灯光,还为后续的智能化功能扩展奠定了基础。

7.3 基于光敏传感器的调光功能

本系统采用光敏传感器对环境光强进行实时检测,并基于检测值动态调整灯光的亮度,提供适应不同场景的自动调光功能。通过光敏传感器实现调光,既可以减少用户手动调整灯光的频率,还可以自动优化输出电压,适用多种场景需求。通过闭环控制机制,确保灯光的输出电压与环境光强度的变化相匹配,为用户提供更舒适、高效的照明体验。以下为光敏传感器调光功能的详细设计与实现过程:

  1. 硬件接口设计
  • 光敏传感器选型:选用光敏电阻模块,输出光强的模拟信号值。传感器将环境光强信号转换为电压信号,供STM32单片机的ADC模块读取。

  • 传感器连接方式:光敏传感器的模拟输出端(AO口)连接到STM32单片机的PA5引脚,PA5被配置为ADC输入通道。供电引脚(VCC)连接单片机的3.3V供电,地线(GND)连接系统地,保证传感器的稳定工作。

  1. 软件功能设计
  • 数据采集与转换:STM32通过ADC模块以一定采样频率读取光敏传感器的模拟信号,模拟值范围为0到4095,对应电压范围为0到3.3V;可通过公式
    $$
    V_{target} = \frac{ADC值}{4095} \times 15
    $$
    将光强模拟值映射为输出电压目标值,该线性映射关系保证了灯光亮度的平滑过渡,避免突然变化对用户产生视觉不适。

  • 调光逻辑与策略:当ADC值低于预设阈值(如1024)时,认为环境光较暗,此时系统逐步增加输出电压,以提高灯光亮度补偿环境光;当ADC值高于预设阈值(如3072)时,认为环境光较亮,此时系统逐步降低输出电压,以避免浪费电能或造成视觉不适;当光强处于中等范围时(ADC值1024到3072),输出电压以线性比例动态调节,保持环境与灯光亮度的适应性平衡。

  • PID闭环控制:根据光敏电阻值计算目标电压,并作为参考值输入PID控制器;PID控制器实时计算实际输出电压与目标电压之间的误差,并调整PWM信号占空比控制Buck变换器,确保输出电压快速稳定地收敛到目标值。

  • 状态反馈与异常处理:当光敏传感器信号异常(如ADC值恒定不变或超出有效范围)时,系统进入保护模式,将输出电压设定为安全值9V,并通过蓝牙模块通知用户;传感器数据每次读取后均存储在缓冲区中,并定期更新,避免因单次采样噪声造成调光不稳定。

为进一步验证调光功能的灵敏度,需要测试光敏传感器在不同光强条件下的响应时间和精度,确保其采集的光强信号与实际环境光强相符。经过调试,光敏电阻灵敏度较高,但光敏电阻的朝向会对空间中同一点的光敏传感有不同的值。解决方式为固定光敏电阻朝向位置,使其主要接收来自上方的环境光。除此之外,还需要验证在自动调光模式下,灯光亮度调整是否与环境光变化相匹配。经过我们的实验,该灯泡在8V电压以下不会发光,且为了保护电路和用户,我们将最高输出电压15V通过程序限制在12V,于是我们将光亮百分比线性映射公式修改为:
$$
V_{target} = \frac{百分比指令}{100} \times 4 + 8
$$
从而使得亮度百分比更符合实际;由于百分比电压现在被限制在8-12V,光敏需要更多的外边界,从原先0-15V电压的计算,改为5-15V电压的线性映射,光敏电阻映射公式也需要对应修改为:
$$
V_{target} = \frac{ADC值}{4095} \times 10 + 5
$$
此时由于光敏电阻在一般的环境光下位于1024-3072而并不会趋于极端值,且程序有保护设计,故一定可以限制输出电压在8-12V范围内。

为进一步优化用户的使用体验,还开展了用户测试实验为产品优化提供参考。实验记录了光敏传感器自动调光时用户的视觉体验,并通过调整PID参数和映射公式提高舒适性。通过光敏传感器的引入,本系统实现了基于环境光强的自动调光功能,显著提升了灯光控制的智能化水平与用户体验。

7.4 闭环PID控制原理

闭环PID控制是本系统的核心功能之一,负责根据目标输出电压值和实际输出电压值的误差,动态调节PWM信号,从而控制Buck变换器的输出电压,实现稳定、精准的调光效果。通过PID控制算法,可以使Buck变换器的输出电压始终接近目标值,无论输入电压波动、负载变化,还是环境光强条件改变,都能够保持系统的高稳定性和快速响应性。

PID控制算法主要由三部分组成:比例(P)、积分(I)、微分(D),可通过公式
$$
u(t) = K_{p}e(t) + K_{i}\int_{0}^{t}{e(\tau)d\tau} + K_{d}\frac{de(t)}{dt}
$$
计算其控制输出,其中u(t)为控制信号(用于调节PWM占空比);e(t)为当前误差值,定义为目标值与实际值之差,即
$$
e(t) = V_{target} - V_{actual}
$$
K_p,K_i,K_d分别为比例、积分、微分的调节系数,分别控制系统的响应速度、稳态误差消除能力和动态性能。

通过调节K_p,K_i,K_d三个参数,可以调整控制器的性能,从而实现系统响应特性的优化,提升系统控制效果。具体而言,三个参数对于响应输出的影响如下:

  • 比例(P)参数K_p:比例项主要控制误差对输出的直接影响,增大K_p会使系统响应更迅速,但过大可能引起震荡。本系统初始设置
    $$
    K_{p} = 0.1
    $$
    通过实验验证实现了较平滑的响应。

  • 积分(I)参数K_i:积分项通过累积误差消除稳态误差,确保输出精度。由于积分过大会导致超调或积分饱和,本系统设置
    $$
    K_{i} = 0.01
    $$

  • 微分(D)参数K_d:微分项对误差变化率进行调节,用于改善动态性能并抑制震荡。为避免过分灵敏的微分效应引入噪声,D参数设置为较小值
    $$
    K_{d} = 0.01
    $$

在本项目的自动调光系统中,PID控制器的控制流程主要分为如下几个步骤:

  1. 误差计算:STM32单片机实时采集目标电压值 V_target 和实际输出电压值 V_actual,计算误差:
    $$
    e(t) = V_{target} - V_{actual}
    $$

  2. 控制信号计算:根据误差值,通过PID公式计算控制信号u(t),调整PWM信号的占空比:
    $$
    u(t) = K_{p}e(t) + K_{i}\sum_{k = 0}^{t}{e(k)\mathrm{\Delta}t} + K_{d}\frac{e(t) - e(t - 1)}{\mathrm{\Delta}t}
    $$
    其中Δt为控制周期;∑e(k)Δt为误差累积值;(e(t)-e(t-1))/Δt为误差变化率。

  3. PWM调节:将计算得到的u(t)映射为PWM信号的占空比,直接控制Buck变换器的输出电压。占空比范围为10%至80%,对应输出电压范围为0V至15V。

  4. 反馈调整:系统持续监测实际输出电压,更新误差值并重复上述步骤,形成闭环控制。

在基于STM32单片机编写程序具体实现PID控制器算法时,还需要特别注意以下两点:

  • 采样频率:ADC采样频率需要设置为与main函数主循环频率同步,以保证控制系统对输入误差的快速响应。同时PID计算周期也应同步于ADC采样,以避免数据滞后影响调节效果。

  • 保护机制:若误差值持续过大(如超出安全范围),系统触发保护模式,因此实际实现时将PWM占空比最小设为10%,最大设为80%。

在实际调试验证时,主要对于控制系统的动态性能与抗干扰能力进行了测试与优化,以进一步提升自动调光功能的灵敏性与稳定性,从而优化用户体验:

  • 动态性能测试:在快速切换目标电压值时,观察系统的响应时间和稳定性,确保输出电压能够迅速收敛到目标值。在负载变化的情况下,验证系统能否保持输出电压的稳定。

  • 抗干扰能力测试:模拟输入电压波动和环境光强突变,测试系统的鲁棒性和PID调节效果。

最终经过多次调试与不断优化,该闭环PID控制系统实现了以下目标:

  • 高精度输出:在误差范围内快速稳定输出目标电压。

  • 快速响应:对目标值或负载变化的响应时间快,满足实际调光需求。

  • 稳定性强:在输入波动、负载变化和环境干扰下,系统保持良好的稳定性。

7.5 外观设计制作

为了使本系统不仅具有实用性,还能满足美观性和设计感的需求,我们对外观部分进行了重新设计和制作。本节详细介绍了外观设计的灵感来源、制作过程以及技术实现中的注意事项。

本系统的外观设计灵感来源于游戏《无畏契约》(Valorant)中的角色”尚博勒”(Chamber)的道具”摄像头”。这一设计概念契合科技感与现代感的视觉效果,适合科技爱好者的审美需求,同时其独特的造型也为灯具的装饰性增色。我们力求将其视觉元素融入本项目,打造一款既具功能性又有极高辨识度的灯具设备。同时在外壳设计时也结合了灯泡的散热需求,保证了灯具长时间工作时的安全性和稳定性。设计中注重可拆卸性,用户可以方便地调整灯泡朝向和光照亮度。

图7.3:产品外观设计原型

详细的制作过程如下:

  1. 原型拆解

我们首先将旧灯具的灯罩部分锯下来,仅保留其内部核心组件,包括灯泡、散热装置、电路板和连接线。为确保灯具正常运行,对保留的灯泡和电路板进行功能检测,确认其性能稳定,并清理了原型中多余的固定结构,为后续的外壳重新设计预留空间。

图7.4:灯具原型拆解部分过程图7.4:灯具原型拆解部分过程

  1. 3D建模与设计

使用Fusion 360软件,根据”尚博勒摄像头”的设计特点,建模了一个以球体和多面体造型的外壳结构。模拟摄像头的结构,在灯具上设计了圆形凹陷部分,提升灯具的科技感。外壳设计了散热格栅,与灯具散热需求相结合;外壳底部设计了支撑脚架,便于放置和移动。摄像头部分设计轨道凹槽,可旋转控制摄像头方向。

图7.5:产品外形3D建模效果图

  1. 材料选择与加工

外壳部分采用轻质耐用的 PLA 材料,利用3D打印技术制作。打印分为两个部分:上半部分为灯具主壳,下半部分为安装支架,组装后固定。3D打印完成后,外壳表面进行手工打磨并喷涂金色亚光涂料,灯罩边缘则涂上金属漆,营造高科技感。

  1. 组件安装与调试

将灯泡、电路板、散热器嵌入3D打印外壳中,通过卡扣、限位固定,确保结构稳固。为避免外壳对散热产生影响,在装配后对设备进行长时间运行测试,确保温度稳定在安全范围内。重新调整灯光的投射方向和亮度,使其与设计的外壳结构匹配,确保在不同环境下具备良好的照明效果。

图7.6:最终产品样机实物图

为进一步提升用户体验,后续将持续对产品进行深度优化,包括添加更多灯光模式(如动态光效或多色渐变)以进一步增强装饰性和互动性,或引入语音控制功能并将其与蓝牙通信相结合以提升智能化体验。

7.6 本章小结

本章介绍了基于直流电源调控的自动调光控制设计,通过蓝牙模块完成手动输出电压控制,通过光敏传感器进行环境光度自动调节,最终通过Buck变换器闭环PID控制完成灯的电压和光亮度稳定。

8 基于直流电源调控的光伏MPPT控制设计

8.1 设计背景与系统架构

光伏发电是当前清洁能源的主流之一,最大功率点跟踪(Maximum Power Point Tracking, MPPT)是光伏系统中提高效率的关键技术。由于光伏板的输出功率受到光照强度影响,不同的光照强度下最大功率点对应的输出电压不同,需要不断通过改变输出电压来寻找最大功率点(MPP)。因此,希望通过搭建一个基于直流电源调控的光伏MPPT控制系统,结合STM32单片机和Buck变换器实现光伏阵列的最大功率点跟踪,从而使得系统维持在最大功率工作,减少不必要的能量损失,有效储存其他能量转化成的电能。

图8.1:光伏发电板

在该系统中,外界光照被太阳能电池板转化为电能,经功率变换电路处理后向负载供电。STM32微控制器实时采集电池板电压、电流等参数,运行MPPT算法,生成控制信号优化功率变换电路的工作状态,确保太阳能电池板始终在最大功率点运行,提高能源利用效率。

图8.2:基于直流电源调控的光伏MPPT控制系统整体架构

8.2 实验原理

光伏板的输出功率会受到环境条件(如光照、温度)影响,这一点无法改变,但是输出功率同时也受到工作电流、电压的因素影响,因此可以通过调节负载电压,使光伏系统的功率维持在最大值,即工作在最大功率点(实现输入侧阻抗匹配)。在本课程使用的Buck变换器中,可以根据STM32输出PWM波的占空比来控制输出的电压,同时由于电路中在不断采集输出电压,从而可以通过设计相应的控制算法算法实现对于输出电压的精确闭环控制。

为了找到最大功率点,可采用扰动观察法(Perturb and Observe, P&O)算法。通过降低与增加占空比来调节输出电压,由于电阻一定,可以通过计算算出变化后的功率,同时将其与变化前的功率进行比较,若功率增大,则沿当前方向继续调整占空比;否则反向调整。在每次调整占空比后,计算功率并记录趋势,从而找到最大功率点。在本项目中,采用改进的扰动观察法,通过记录功率变化并自动调整占空比,从而快速收敛到最大功率点。

图8.3:不同光照强度对光伏板的P -V曲线

8.3 代码逻辑

在Keil5中编写的STM32主控代码主要分为如下4个主要的功能模块:

  1. 系统初始化:初始化系统时钟与ADC、PWM模块,为数据采集及控制做好准备。
1
2
3
4
SystemInit(); // 初始化系统时钟
PWM_Init(); // 初始化PWM模块
AD_Init(); // 初始化ADC模块
_enable_irq(); // 开启全局中断
  1. PWM控制模块:通过实时调控PWM输出占空比实现Buck变换器输出电压的改变,从而影响光伏阵列的工作点。值得注意的是,需要调整PWM输出频率与Buck变换器相适应,同时确保输出占空比范围在0~1范围内。
1
2
3
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 配置为PWM模式1
TIM_OCInitStructure.TIM_Pulse = 0; // 设置初始占空比为0
TIM_SetCompare1(TIM2, (uint16_t)(dutyCycle * PWM_PERIOD)); // 调整PWM占空比
  1. ADC采样模块:将光伏阵列输出的电压信号通过ADC采样转换为数字信号并读取,再通过与电阻的计算得出电流值,以用于功率计算。值得注意的是,ADC通道需与光伏阵列信号对应,同时为确保采样精度还需事先对ADC模块进行校准。

图8.4:ADC采样模块相关函数代码

  1. MPPT算法实现:根据扰动观察法原理,通过电流与电压相乘得到当前输出功率,与上个点的功率相比较,若大于上个点的功率,则继续增大占空比,增加输出电压,从而增加输出功率,若小于,则减小占空比,并在每次调整占空比后再次计算输出功率。循环往复进行上述流程,同时记录功率变化趋势及对应占空比,以寻找最大功率点。值得注意的是,程序中设置的步长STEP_SIZE值应适中,过大会导致振荡,而过小则会影响跟踪的速度,同时还应避免控制过程中PWM波形的占空比超出0~1范围。

图8.5:MPPT算法实现相关函数代码

在主循环main函数中编写总控代码对上述代码逻辑进行整合,每次循环都会测量电压与电流从而得到功率,再通过进行扰动观察法寻找最大功率点。

图8.6:main函数总控代码

8.4 实验现象

实验时,将Buck变换器、STM32开发板与光伏阵列进行连接,并确保采样与控制电路正常工作。将代码烧录到STM32开发板,并通过调整光照强度,观察功率点变化及最大功率点跟踪效果。接下来需要进行逐步调试:

  1. 使用示波器观察PWM信号及Buck变换器输出电压,验证占空比调节是否正常,并使用万用表验证电压、电流采样值是否准确;

图8.7:PWM输出占空比调试结果

图8.8:Buck变换器输出电压调试结果

可以看到,此时PWM输出占空比为50%,占空比调节正常;光伏板输出电压为5.1V,电流为0.025A,输入功率约为0.1275W。

  1. 通过调整光照强度,观察功率点变化及最大功率点跟踪效果。

图8.9:负载电压图

图8.10:负载消耗功率图

可以看到,此时所接负载为200Ω,输出电压为4.5V,输出功率约为0.12W。考虑功率损耗,光伏板输出功率约等于负载消耗功率,完成了对该光照强度下最大功率点(MPP)的准确寻找。

随后又对于该控制系统的动态跟踪性能进行进一步的测试,可以观察到,当外界环境的光照强度改变时,控制系统能快速做出响应并使光伏板输出功率达到最大(约为负载消耗功率),说明控制系统对于最大功率点的实时跟踪性能良好。

8.5 本章小结

本章主要介绍了基于直流电源调控的光伏板最大功率点追踪(MPPT)控制系统的设计,设计时主要采用递归算法,每次通过扰动当前功将率与变换前功率作比较,若大,则继续增加占空比,若小,则减少占空比,以此类推,不断循环,从而找到最大功率点。每次变化占空比的步长与延时时间影响找到最大功率点的效率,需要调试找到最佳的相应效率。同时,在步长固定的情况下,通过自适应步长控制算法也可以更有效率的寻找最大功率。通过动态追踪,显著提高了光伏发电效率。

9 总结与展望

9.1 课程小结

《自动控制原理》课程作为一门项目制课程,重点围绕经典控制相关理论知识及应用实践,有效地将理论教学与设计实践结合起来。通过课程的理论学习与项目实践,我们在实践操作中对于经典控制理论的知识有了更加深刻的理解,建立了”控制”工程观与系统性分析思维,也锻炼了自己将理论应用于工程实践的能力,以及对于控制工程问题的分析与解决能力。

该项目制课程主要分为两个项目板块:

控制基础实践项目为”直流电源控制分析与系统设计”,以直流电源这一经典工程案例串联起经典控制理论知识各版块,涵盖控制系统建模(微分方程与传递函数)、时域分析、根轨迹分析、频域分析,频域校正与PID控制等,从理论分析、仿真、实验三维度强化同一理论知识点的学习,完成直流电源控制系统的分析设计以及调试。这一部分项目在理论课程学习的同时穿插完成,主要涉及到经典控制理论的学习,并将其应用到实际的Buck变换器PI闭环控制系统中、结合实际电路参数完成的PSIM电路仿真与相应的MWorks分析计算。

综合应用实践项目为”基于电源的综合应用系统”,以直流电源驱动系统应用为综合实践项目,运用传感、闭环控制、先进控制等硬件与算法,完成基于直流电源控制的LED自动调光与光伏最大功率跟踪(MPPT)等项目实践。这一部分项目主要以实验方式开展,最终本小组顺利完成实验与设计内容并进行课程项目汇报。

图9.1:小组答辩风采展示

回顾这门课程一路走来,从课程引入与理论教学,到实际实验调试与项目设计,再到最终的项目测试与汇报,我们在理论与实践的结合中扎实掌握了经典控制理论的相关知识,并锻炼了自己的电路设计分析与实践能力。我们能取得如此的进步与成就离不开两位老师与四位助教的辛勤付出,在此再次向各位老师与助教们送上最真诚的感谢,也衷心祝愿这门课程在未来能建设得越来越好。

图9.2:课程汇报班级合影留念

9.2 课程收获及建议

通过《自动控制原理》这门课程的学习,我了解到了许多经典控制理论的相关知识:从系统的数学建模入手,微分方程与传递函数是描述一个控制系统性能的基本工具;为调整系统参数以提高控制系统的稳定性,可从时域与频域两个角度分别进行分析,时域上可以使用劳斯判据进行系统稳定性的判定并根据根轨迹找到系统的临界稳定状态,频域上可以使用奈奎斯特稳定判据或依据Bode图对系统稳定性进行分析;基于频率特性,还可以从频域校正环节的设计层面调节系统的动态响应特性。理论学习之余,课程紧密穿插了相应的仿真与电路调试实验,理论与实践的结合使得我对于这些枯燥的理论知识有了更加生动而深刻的理解。

在课程项目中,我主要负责对于闭环控制系统的理论分析、仿真验证与参数调试,以及课程项目报告绝大部分的撰写。令我印象最深刻的是,在理论部分对系统进行频率特性分析时,使用MWorks根据系统开环传递函数绘制Bode图,得到了穿越频率与相位裕度;而在根据实际电路结构在PSIM中搭建了相应的扫频电路之后,当看到运行扫频仿真得到的结果与MWorks绘制的Bode图几乎完全一致时,我深刻体会到了理论的有效性,这代表理论的分析确实可以真实地指导实际控制系统的设计,在后续项目设计与调试时也充分利用了这一点,有效提高了设计与调试的效率。最终我们也顺利完成了基于直流电源调控的自动调光与MPPT控制系统的设计与实现,并将自动调光系统实例化为一个具有实际应用意义的产品。相信在这门课程中学到的知识与技能能够为我们未来的硬件产品开发中起到重要的作用。

最后感谢两位老师耐心的教学与指导以及四位助教的辛苦付出,特别是几位助教,耐心地回答我们的问题、批改我们的作业并协助我们进行硬件实物的调试,为我们的项目实践提供了丰富的参考资料,帮助我们快速上手项目。在此提出一点小小的建议,这次项目中在电路焊接方面浪费了许多时间,希望未来的课程项目设计能够在各个实现细节上更加完善,在设计与测试时考虑的更加全面;同时希望在理论课程中穿插的各种仿真作业能够与实际的控制系统结合的更加紧密,完善一些逻辑不严谨的地方(如提供的报告框架中,是否考虑电容寄生电阻这点体现的略显混乱)。但总的来说,这门课程确实是已有项目制课程中的精品,也希望能够在未来建设得越来越好。

参考文献

[1] 胡寿松主编.自动控制原理[M].科学出版社,2019:a670.

[2] 卢京潮主编.自动控制原理习题解答[M].清华大学出版社,2013:195.

[3] 王天威编著.控制之美[M].清华大学出版社,2022.

[4] 李昆.智能技术在室内LED照明系统中的应用研究[J].光源与照明,2024,(10):51-53.

[5] 郑盛梅.恒照度自动调光台灯的设计[J].光源与照明,2023,(01):69-71.

[6] 侯耀华,陈萍,於崇干.直流电源系统高级功能在无人值班变电站的应用[J].供用电,2015,(02):19-24.

[7] 许桂敏,解皓月,张子泉,等.基于Simulink的光伏电池特性及MPPT算法仿真研究[J].智能建筑电气技术,2024,18(06):36-39+60.

[8] 王青苗.PLC模糊PID控制系统在隧道照明节能控制中的应用[J].微型电脑应用,2024,40(06):114-118+122.

[9] 陈礼俊,兰志勇.单片机控制的双调控高压直流电源[J].现代电子技术,2017,40(12):165-168.

[10] 樊战亭,万欣,王超.太阳能电池板自动追光控制系统设计[J].咸阳师范学院学报,2024,39(06):12-16.

[11] Gene F. Franklin, J. David Powell, Abbas Emami-Naeini, et al. Feedback control of dynamic systems[M].Publishing House of Electronics Industry,2013:14,590.

[12] Karl Johan Astrom, Richard M. Murray.自动控制多学科视角[M].人民邮电出版社,2010:310.

附录

1 STM32闭环PI控制程序

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "AD.h"
#include "Serial.h"

// 定义全局变量
#define PWM_FREQUENCY 10000 // PWM频率
#define MAX_PWM_VALUE 1000 // PWM最大值
#define PWM_PERIOD 3000 // PWM周期为1000个计数单位
#define KP 0.1 // 比例增益
#define KI 0.01 // 积分增益
#define KD 0.01
float voltage_ref = 10; // 设定的输出电压
float voltage_fb = 0.0; // 反馈的实际输出电压
float duty_cycle = 0.0; // 占空比
float error = 0.0; // 误差
float integral = 0.0; // 积分项
float control_signal = 0.0; // 控制信号
float last_error = 0.0; // 上一次的误差
float derivative = 0.0; // 微分项
int sample_index = 0; // 样本计数器
float trueVoltage=0.0; // 实际输出电压值
float VREF;
uint32_t time_counter = 0; // 用于记录时间的计数器
uint8_t voltage_state = 0; // 0 表示电压为8V,1 表示电压为10V
uint16_t ADValue;
float Voltage;
float i;

void control_buck(void){
voltage_fb = (float)AD_GetValue()*3.3/4096; //读取ADC的值
trueVoltage = voltage_fb*1050*20000/2500/1000; // 计算实际电压 (根据外部电路参数)
error = voltage_ref*2500*1000/1050/20000 - voltage_fb; // 计算误差
integral += error; // 积分计算
derivative = error - last_error; // 微分计算
control_signal = KP * error + KI * integral + KD * derivative; // PID控制输出
if (control_signal > 0.8)
control_signal = 0.8;
else if (control_signal < 0.2)
control_signal = 0.2;
duty_cycle = control_signal * PWM_PERIOD; // 更新占空比
i = (int)duty_cycle;
PWM_SetCompare1(i);
}
int main(void){
OLED_Init();
PWM_Init();
AD_Init();
Delay_ms(10);
NVIC_Configuration();
Serial_Init(); // 初始化串口
while (1){
control_buck();
}
}

2 自动调光程序

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "AD.h"
#include "Serial.h"
#include "HC05.h"
#include <string.h>
#include <stdlib.h>

// 定义全局变量
#define PWM_FREQUENCY 10000 // PWM频率
#define MAX_PWM_VALUE 1000 // PWM最大值
#define PWM_PERIOD 3000 // PWM周期为1000个计数单位
#define KP 0.1 // 比例增益
#define KI 0.01 // 积分增益
#define KD 0.01
float Light_Intensity = 0.0; // 光敏实际模拟量
float voltage_ref = 5 * 1.38; // 设定的输出电压
float voltage_fb = 0.0; // 反馈的实际输出电压
float duty_cycle = 0.0; // 占空比
float error = 0.0; // 误差
float integral = 0.0; // 积分项
float control_signal = 0.0; // 控制信号
float last_error = 0.0; // 上一次的误差
float derivative = 0.0; // 微分项
//int sample_index = 0; // 样本计数器
float trueVoltage=0.0; // 实际输出电压值
float VREF;
uint32_t time_counter = 0; // 用于记录时间的计数器
uint8_t voltage_state = 0; // 0 表示电压为8V,1 表示电压为10V
uint16_t ADValue;
float Voltage;
float i;
uint8_t RxSTA = 1;
char RxData[100] = "N";

void control_buck(void){
voltage_fb = (float)AD_GetValue(ADC_Channel_4)*3.3/4096; //读取ADC的值
trueVoltage = voltage_fb*1050*20000/2500/1000; // 计算实际电压 (根据外部电路参数)
error = voltage_ref*2500*1000/1050/20000 - voltage_fb; // 计算误差
integral += error; // 积分计算
derivative = error - last_error; // 微分计算
control_signal = KP * error + KI * integral + KD * derivative; // PID控制输出
if (control_signal > 0.8)
control_signal = 0.8;
else if (control_signal < 0)
control_signal = 0;
duty_cycle = control_signal * PWM_PERIOD; // 更新占空比
i = (int)duty_cycle;
PWM_SetCompare1(i);
}

int main(void){
OLED_Init();
PWM_Init();
AD_Init();
Delay_ms(10);
NVIC_Configuration(); // NVIC初始化
Serial_Init(); // 初始化串口
HC05_Init();

while (1){
// 蓝牙读取开关
HC05_GetData(RxData);
if (RxSTA == 0){
if (strcmp(RxData, "LL") == 0){
RxSTA = 1;
while(1){
// 读取光敏模拟输入量
Light_Intensity = (float)AD_GetValue(ADC_Channel_5);
voltage_ref = (Light_Intensity * 2 / 3500 + 8) * 1.38;
control_buck();
HC05_GetData(RxData);
if (RxSTA == 0)
break;
}
}
else if (strcmp(RxData, "on") == 0)
voltage_ref = 10 * 1.38;
else if (strcmp(RxData, "off") == 0)
voltage_ref = 5 * 1.38;
else{
int num = atoi(RxData);
if (num >= 0 && num <= 100)
voltage_ref = ((float)num / 100 * 2 + 8) * 1.38;
else if (num > 100)
voltage_ref = 10 * 1.38;
else
voltage_ref = 5 * 1.38;
}
memset(RxData, 0, sizeof(RxData));
strcpy(RxData, "N");
RxSTA = 1;
}
control_buck(); // 控制输出电压
}
}

基于STM32F407实现的信号发生与采集分析系统

演示视频已上传至Bilibili视频平台:https://www.bilibili.com/video/BV1wUiRYxE8z


一、系统功能与整体架构设计

系统实现功能

(1)单片机在按键控制下,产生1kHz的正弦波或方波;

(2)单片机能够采集波形,并且显示;

(3)单片机能够分析采集波形的频谱,并且显示频谱与基波频率。

整体架构设计图

系统主页与按键对应功能简介

每次启动系统都会默认直接进入该主页面:

(1)蓝色部分的文字为系统名称与作者姓名,这会在后续的每个功能页面中都有显示;

(2)黑色部分的文字为各按键对应的功能介绍。

正如主页的功能介绍栏所示:

(1)按下KEY0:PA4引脚开始持续输出1kHz的正弦波信号,并在屏幕上实时显示从PA5引脚采集到的输入信号波形;

(2)按下KEY1:PA4引脚开始持续输出1kHz的方波信号,并在屏幕上实时显示从PA5引脚采集到的输入信号波形;

(3)按下KEY2:在屏幕上实时显示从PA5引脚采集到的输入信号的频谱分析结果(幅值谱,频率范围为0~1000Hz);

(3)按下KEY3(KEY_UP):在屏幕上实时显示从PA5引脚采集到的输入信号的频谱分析结果(幅值谱,频率范围为0~8000Hz)。


二、各部分功能实现

1、1kHz正弦波与方波的产生

模块功能架构设计

在实际单片机编程实现时,导入并调用DSP库加速信号数组(正弦波)的计算,并通过时钟TIM6(分频)控制DMA的数据搬运过程,并设置DAC数模转换将搬运后的信号数字数据在PA4引脚以模拟信号形式输出。

模块功能实现依据

为使用单片机产生指定频率的波形,需要根据上述架构设置对应的参数,基本的设置逻辑如下:

(1)首先,这里使用定时器TIM6来控制DMA搬移数据的过程,在CubeMX中已预先设置其时钟频率为84MHz;

(2)在生成信号数组时,C语言程序中设定数组长度为1024(与后续采集一致,为4的整数次幂以便于进行快速傅里叶变换FFT);

(3)事实上,对于信号数组长度N、定时器频率fT与信号基波频率f而言存在如下关系式:f = fT / N,这意味着以输出基波频率f = 1kH的信号为例,经过时钟分频后的定时器频率fT是可以直接确定的,进而可以确定分频倍数(时钟频率/分频后定时器频率)。

经过计算,当分频倍数设置为82时(实际单片机控制程序中为两次分频,取第一次分频倍数为41、第二次分频倍数为2即二分频),输出的信号基波频率f约为1000(由于数组长度为1024,在分频倍数必须取整的情况下,基波频率无法精准等于1000Hz,实际约为1000.38Hz)。

在MATLAB中,可以编写简单的测试程序模拟这一过程:

1
2
3
4
5
6
7
8
TIM6_Frequency = 84000000; %DAC_DMA时钟TIM6频率
DAC_DMA_Divide1 = 41; %DAC_DMA时钟一次分频
DAC_DMA_Divide2 = 2; %DAC_DMA时钟二次分频
DAC_DMA_Frequency = TIM6_Frequency / (DAC_DMA_Divide1 * DAC_DMA_Divide2); %分频后时钟频率

N = 1024; %数组长度与采样点数

f = DAC_DMA_Frequency / N; %产生信号频率(期望值1000)

模块功能实现效果

启动系统后按压按键KEY_0启动正弦波生成,将示波器的通道正极与信号输出引脚PA4连接,示波器的通道负极与单片机的地GND连接,可在示波器上显示出如下波形:

可以看到输出的波形形状为标准的正弦波,输出电平范围为03.3V(对应生成的正弦信号数组振幅为2048、偏置为2047即数据点范围位于04095),均值为1.6V,且周期约为1kHz(示波器显示1.00045kHz;一个周期大致占据五格、每格代表200us即一个周期为1ms)。

按压按键KEY_1切换为生成方波,可在示波器上显示出如下波形:

可以看到输出的波形形状为标准的方波(占空比50%),输出低电平为0V、高电平为3.3V(对应生成的方波信号数组前一半值为0、后一半值为4095),均值为1.6V,且周期约为1kHz(示波器显示1.00043kHz;一个周期大致占据五格、每格代表200us即一个周期为1ms)。

2、波形信号的采集与显示

模块功能架构设计

在实际单片机编程实现时,通过定时器控制从PA5引脚读入模拟信号,通过ADC模数转换为数字数组并通过DMA搬运将其存入长度为1024(为4的整数次幂以便于进行快速傅里叶变换FFT)的数组中,存满一次数组即中断一次DMA搬运并将该数组数据(即采集波形)显示在显示屏上,短暂延迟(控制屏幕刷新速度合适)后进行新一轮的信号采集、搬运与波形显示。

模块功能实现依据

为使用单片机采集信号数据并以合适的形式将波形显示在显示屏上,需要根据上述架构设置对应的参数,基本的设置逻辑如下:

(1)首先,控制ADC1的定时器在CubeMX中已预先设置其时钟频率为84MHz,但根据相关手册与文档,硬件上对于分频后的ADC实际频率有限制,不能高于30MHz,在这样的条件下一般取四分频(仅分频一次,以对应结构体参数hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4实现),即分频后定时器频率为21MHz;

(2)其次,根据相关手册与文档,完成一次采样至少会花费12个时钟周期,为调控实际采样频率通常还可以设置额外的时钟周期(库函数限制只能为特定的几个值),即实际的采样频率Fs应为:分频后定时器频率(21MHz)/一次采样花费的时钟周期数(12+额外设置的时钟周期);

(3)事实上,要想控制屏幕上显示的波形不过于松散/密集,需要控制一次采样(填满数组,DMA中断)内包含的信号周期数量,这可以通过将信号产生的定时器频率fT除以采样频率Fs得到;

(4)另一方面还需要注意为使得采集到的波形没有失真(频域混叠)现象,要求采样频率Fs与待采集波形频率f满足:Fs≥2f。

经过计算与测试,当额外设置的时钟周期设置为112时(sConfig.SamplingTime = ADC_SAMPLETIME_112CYCLES),一次采样中包含(屏幕上显示)的信号周期约为6,这样的显示效果较为合理;同时此时的采样频率Fs约为42683Hz,远大于待采集波形频率f = 1000Hz的两倍,不会发生频谱混叠。

在MATLAB中,可以编写简单的测试程序模拟这一过程(以正弦信号为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ADC_Timer_Frequency = 84000000; %ADC时钟频率
%硬件限制:要求ADC实际时钟频率不能超过30MHz
ADC_Divide = 4; %取四分频,分完后达到21MHz满足要求
ADC_Frequency = ADC_Timer_Frequency / ADC_Divide; %分频后ADC时钟频率

%完成一次采样需要多个时钟周期
Collect1 = 12; %固定消耗12次循环,无法更改
Collect2 = 112; %可设置额外消耗循环数以调整采样频率
Fs = ADC_Frequency / (Collect1 + Collect2); %ADC采样频率

Cycle = DAC_DMA_Frequency / Fs; %一次采样采出多少个周期

A = 2047; %幅值
B = 2048; %直流偏置分量
t = 0 : 1 / Fs : (N - 1) / Fs;
x = A * sin(2 * pi * f * t) + B;

% 绘制原始信号
figure;
subplot(2,1,1);
plot(t, x);
title('正弦波信号');
xlabel('时间 (秒)');
ylabel('幅值');

运行该MATLAB程序,绘制出一次采样采集到的波形如下图所示:

模块功能实现效果

启动系统后按压按键KEY_0启动正弦波信号的生成与采集,将信号输出引脚PA4与信号输入引脚PA5连接,屏幕上显示采集波形效果如下:

按压按键KEY_1切换为生成方波信号并采集,屏幕上显示采集波形效果如下:

图上的横坐标单位为ms;可以看到屏幕上显示的即为6个周期的信号波形,这与MATLAB的模拟计算结果是完全一致的,且波形无失真。

3、采集波形信号的频谱分析

模块功能实现依据

在频谱分析与频谱图显示方面,有如下要点需要注意:

(1)首先,频谱分析依赖于对于信号的傅里叶变换,在数字信号层面对于离散的数据点则需要采用离散傅里叶变换,但这样的变换计算速度往往很感人,因此需要利用其快速算法,即快速傅里叶变换FFT,MATLAB可直接调用fft函数实现,单片机编程中在DSP库中也有相应的函数可以实现完全相同的过程,但要求信号数组的长度应为4的整数次幂,故先前均选取1024作为发生与采集信号的数组长度;

(2)其次,经过FFT变换后会得到一个长度相同(1024)的新数组,其中每一个数字的下标index对应的实际频率应为index*Fs/1024,这意味着如果直接将整个FFT变换结果数组作为频谱图显示到屏幕上,横坐标的跨度实际上为Fs≈42683Hz,为使得频谱图更加直观,需要限制绘制频谱图的频率范围,并对应控制绘制数组中的部分数据;

(3)事实上,FFT变换结果的数组中各数值并不是期望的对应频率的幅值,还需要除以数组长度1024(单片机程序中对于起始点只需要除以一半的数组长度即512)才可得到正确的幅值。

由于涉及到信号的基波频率检测以及方波的频谱分析,在KEY_2和KEY_UP按键分别设置了频谱频率范围为01000Hz与08000Hz两种模式。在MATLAB中,可以编写简单的测试程序模拟频谱分析过程并在0~8000Hz的频段上展示频谱:

1
2
3
4
5
6
7
8
9
f_range = linspace(0, Fs, N);%频域横坐标,注意奈奎斯特采样定理,最大原信号最大频率不超过采样频率的一半
xk = fft(x) / N; %用fft得出离散傅里叶变换

% 计算并绘制频谱
subplot(2,1,2);
plot(f_range(1:50),abs(xk(1:50)));%画双侧频谱幅度图
title('正弦波频谱');
xlabel('频率 (Hz)');
ylabel('幅度');

运行该MATLAB程序,绘制出一次采样采集到的波形如下图所示(以正弦信号为例):

可以看到该信号具有直流分量(频率为0)以及1000Hz除的正弦分量,两者幅值均为2048(与产生波形时的一致)。

除此之外,在单片机编程中,为寻找并在屏幕上打印出信号的基波频率,还需要在显示频率波形的同时完成对于除直流分量外最高幅值对应频率的计算(数组返回最大值对应下表,算法较简单在此省略实现过程)。

模块功能实现效果

启动系统后按压按键KEY_0启动1000Hz正弦波信号的生成与采集,将信号输出引脚PA4与信号输入引脚PA5连接,并按压按键KEY_2可启动短频段0~1000Hz的频谱显示如下:

按压按键KEY_UP可启动长频段0~8000Hz的频谱显示如下:

可以看到此时只有直流分量和1000Hz的正弦分量两个尖峰,与MATLAB模拟计算结果一致。

按压按键KEY_1,切换为1000Hz方波波信号的生成与采集,将信号输出引脚PA4与信号输入引脚PA5连接,并按压按键KEY_2可启动短频段0~1000Hz的频谱显示如下:

按压按键KEY_UP可启动长频段0~8000Hz的频谱显示如下:

可以看到此时在01000Hz频段只有直流分量和1000Hz的正弦分量两个尖峰,但在08000Hz频段,由于方波实质上是不同频率的正弦信号的叠加,所以频谱会在基波的奇数倍(1、3、5……)处也有尖峰,但尖峰的幅值会远小于基波1000Hz处,且倍数越大幅值越小,这使得按照先前的算法也能识别出基波频率约为1000Hz。

4、补充测试

由于还需要对于基波频率在0~1000Hz范围内的任意输入信号进行频谱分析,经过调试后,当输入信号频率为200Hz时,为使得显示波形合理,将ADC环节设置的额外时钟周期由112调整至480,结果如下所示:

200Hz正弦波:

时域:

频域:

短频段(0~1000Hz):

长频段(0~8000Hz):

200Hz方波:

时域:

频域:

短频段(0~1000Hz):

长频段(0~8000Hz):

在ADC环节额外时钟周期设置为480的情况下,可以计算得出,对于频率为1000Hz的信号,一次采样(即屏幕内显示)包含21个周期(正好为整数),结果如下所示:

1000Hz正弦波:

时域:

频域:

长频段(0~8000Hz):

1000Hz方波:

时域:

频域:

长频段(0~8000Hz):

可以看到此时虽然时域上波形显示更加狭窄密集,但是频域上尖峰的变化过程也有了迅速的提升,且测得的基波频率也更加精准。


三、总结

通过本次项目实践,不仅在实验中进一步加深了对于数字信号的产生、采集与频谱分析处理过程的理解,特别是通过期望发生信号频率去计算定时器分频系数、采样频率的计算过程以及FFT计算与频谱图像绘制的过程;而且也增加了对于STM32F407单片机开发的实战经验,在巩固了引脚GPIO与时钟配置相关内容的同时,又对于DMA内存搬运及其中断以及DAC数模转换输出与ADC模数转换输入等功能模块有了更深刻的认识,包括定时器对于这些过程的调控也涉及到相关频率的计算,所有模块的配置之间都有着密切的联系。

小车路径规划

本项目全部代码已同步上传至Github,仓库链接:Asgard-Tim/Path-Planning: 重庆大学明月科创实验班定量工程设计方法课程项目 (github.com)

一、设计要求


在本部分的课程项目中,要求我们运用LD14雷达扫描地图,在MATLAB中进行人工势场添加并对STM32小车进行路径规划,在一规定的场地中让小车避开两个矩形障碍物并以尽可能短的路径最终抵达圆形目标位置。


二、系统方案


2.1 移动底盘分析

小车为履带式小车,左右履带分别由一枚直流电机进行驱动,运动模式类似常规双轮小车,通过左右两枚电机转动的差速实现转向。该小车相对来说较为容易进行控制,只需要控制两个驱动轮的速度存在差异,即两轮差速,即可控制机器人实现无滑动摩擦的旋转,也可实现零半径转弯。

图2-1 双轮履带小车外观图

1

图2-2 两轮差速式机器人运动学分析图

2

对小车移动底盘的运动学分析(如上图2-2所示):

小车的速度控制主要是控制 X 轴(前后方向)和 Z 轴(旋转方向)的速度, 以 Vx 和 Vz 来指代,单位分别是 m/s 和弧度/s。X 轴方向以前进记为正,Z 轴方向以右转记为正。车轮速度是使用编码器来计算和得出,读取编码器计数后再转化成车轮的速度。Vz则是通过左右电机转动的差速计算得到的。

图中参数分别代表:

3

2.2 电机特性分析

在本次项目中,我们采用带有减速器与编码器的直流电机驱动小车前进。

直流电机的物理模型图如下图2-3所示。其中,固定部分有磁铁,这里称作主磁极;固定部分还有电刷。转动部分有环形铁心和绕在环形铁心上的绕组。 (其中 2 个小圆圈是为了方便表示该位置上的导体电势或电流的方向而设置的) 它的固定部分(定子)上,装设了一对直流励磁的静止的主磁极 N 和S,在旋转部分(转子)上装设电枢铁心。在电枢铁心上放置了两根导体连成的电枢线圈, 线圈的首端和末端分别连到两个圆弧形的铜片上,此铜片称为换向片。换向片之间 互相绝缘,由换向片构成的整体称为换向器。换向器固定在转轴上,换向片与转轴 之间亦互相绝缘。在换向片上放置着一对固定不动的电刷 B1 和 B2,当电枢旋转时,电枢线圈通过换向片和电刷与外电路接通。在电刷上施加直流电压 U,电枢线圈中的电流流向为:N 极下的有效边中的电流总是一个方向,而 S 极下的有效边中的电流总是另一个方向。这样两个有效边所受的洛伦兹力的方向一致(可以根据左手法则判定),电枢开始转动。具体来说就是,把上图中的+和-分别接到电池的正极和负极,电机即可转动;如果是把上图中的+和-分别接到电池的负极和正极,则电机会反方向转动。电机的转速可以理解为和外接的电压是正相关的(实际是由电枢电流决定)。

总而言之,如果我们可以调节施加在电机上面的直流电压大小,即可实现直流 电机调速,改变施加电机上面直流电压的极性,即可实现电机换向。

图2-3 直流电机的物理模型

4

在具体的使用过程中,我们需要通过在特定的引脚之间(如本次项目所用电机为1/6引脚)接上一个直流电源,电机即可转动,且改变电压大小即可改变电机转速。接线方式说明如下图2-4所示。

5

2.3 电机控制策略以及PID特性分析

小车电机驱动器芯片使用 AT8236,具有过流保护功能,并可设置电流阈值。驱动芯片只需两个逻辑输入,便可达到调速和正反转的功能,本小车中,每个电机使用两路PWM进行调速(实际上一个普通 IO 和一路 PWM 即可进行正反转 和调速)。

电机的速度使用 13 线霍尔编码器输出 AB 相进行测量,电机减速比为 1:30,使用 STM32 的编码器测量功能,并初始化为脉冲上升沿和下降沿都进行计数,可实现轮子转一圈输出 1560 个计数。

电机调速框图如下图2-5所示。

图2-5 电机调速框图

6

基于2.1节提到的小车移动地盘的运动学分析,我们可以进一步讨论这些物理量之间的关系,并求出其运动学正逆解公式(如下图2-6),结合PID实现控制。

图2-6 两轮差速式机器人的运动学正逆解公式

7

8

9

10

下图2-7为PID的控制框图,每个方块代表控制系统的一个组成部分,从图中可看出系统中各组成部分的相互关系和影响,即 PID 调节系统是具有被调参数负反馈的闭环系统。当被控量 Y 受到干扰的影响而升高时,反馈信号将高于给定值 X,经过比较而到放大元件去的偏差信号 e 将为负值,控制器将发出信号而使执行元件动作,其作用方向为负,使被控量下降,这就达到了自动控制的目的。

图2-7中的目标速度一般我们可以通过按键或者开关等方式编程实现改变目标值,测量速度就是通过单片机定时去采集编码器的数据并清零。目标速度和测量速度之间做差这个就是目前系统的偏差。在控制过程中,需要将目标速度、测量速度与偏差三者送入 PID 控制器进行计算输出,然后再经过电机驱动的功率放大控制电机的转动去减小偏差,最终达到目标速度。

图2-7 PID控制框图

11

对于上述PID控制的基于C语言的实现,我们给出以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
`int Incremental_PI (int Encoder,int Target)` 

`{`

`static float Bias,Pwm,Last_bias;`

`Bias=Encoder-Target; //计算偏差`

`Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias;`

`//增量式 PI 控制器`

`Last_bias=Bias; //保存上一次偏差`

`return Pwm; //增量输出`

`}`

其中,入口参数为编码器的速度测量值和速度控制的目标值,返回值为电机控制 PWM。第一行是相关内部变量的定义。第二行是求出速度偏差,由测量值减去目标值。第三行使用增量 PI 控制器求出电机 PWM。第四行保存上一次偏差,便于下次调用。最后一行是返回。

2.4 嵌入式控制系统总结分析

总体而言,我们基于小车本身的轮距等基本参数编写了一整套嵌入式PID控制流程与系统(基于C语言,面向STM32编程),结合PWM波控制电压,从而控制电机的转速以实现对于车轮速度的反馈调节机制,能够保证小车始终保持相对稳定的速度前进,便于后面进一步规划算法,使小车的运动更加可控。此部分编写的KEIL工程详见附件“PID.zip”。

2.5 传感系统总结分析

本次项目所采用的传感器主要为激光雷达,在此我们选用LD14雷达。LD14 主要由激光测距核心,无线传电单元,无线通讯单元,角度测量单元、电机驱动单元和机械外壳组成。LD14测距核心采用三角测量法技术,可进行每秒 2300 次的测距。每次测距时,LD14从一个固定的角度发射出红外激光,激光遇到目标物体后被反射到接收单元。通过激光、目标物体、接收单元形成的三角关系,从而解算出距离。获取到距离数据后,LD14 会融合角度测量单元测量到的角度值组成点云数据,再进行导出。

雷达扫描点云数据形式如下图2-8所示:

图2-8 雷达扫描点云极坐标数据形式(下图2数据为角度(角度制),右侧为距离)

12

13

在项目的实践过程中,我们需要通过对STM32单片机进行编程,从激光雷达读取数据并通过串口将处理后的数据传输至电脑中(用特定的软件读取串口输出信息)。STM32中烧录的KEIL工程见附件“Lidar.zip”,接线方式如下图所示。

图2-9 激光雷达与单片机接线方式对应

14

15

这里给出KEIL工程中main.c中的主干代码(图2-10),最终会输出数据θ和r,分别代表偏转的角度(AvoidData[i].angle)和距离原点的距离(AvoidData[i].distance)。

图2-10 读取激光雷达数据的主干代码

16


三、硬件电路设计


主要运用模块电路图如下图3-1所示:

图3-1 主要模块电路图

17

其中,小车的主控为STM32 F103RCT6芯片,其主控板实物图如下图3-2所示:

图3-2 STM32主控板实物图

18

由于本次项目需要完成的目标较为单一,仅使用其中少部分接口和器件。


四、软件算法设计


4.1 任务描述

在本项目中,我们需要通过激光雷达对于小车所处的地图环境(2m*2m,放置有三个正方体障碍物与一个圆柱体目标物)进行扫描,并对扫描得到的数据结果进行处理。通过一定的算法对于目标物与障碍物进行识别后,我们还需要通过人工势场法进行路径规划,使得小车能够以最优的路径绕开障碍物到达目标物。

本次项目任务分为静态和动态两个部分,其中动态测试过程中会对障碍物的位置进行人为的随机改动。测试过程中,小车的起点位置与目标物的位置始终不变,且可自由选定小车的初始面对方向。

4.2 技术路径和策略

通过对于任务描述的分析,我们大致可以将静态情况下的路径规划问题拆解为以下几个部分,并给出相应的处理工具与解决策略:

  • (1) 地图扫描:需要通过对STM32单片机进行编程(利用软件工具KEIL),利用串口通信读取激光雷达扫描所获取的周围地图环境点云的极坐标数据
  • (2) 处理数据:将串口所得到的数据导入MATLAB中,利用坐标变换将不同点位扫描的极坐标数据统一到同一个笛卡尔坐标系中以便后续处理与识别
  • (3) 识别物体:在获取扫描后得到的地图后,需要利用RANSAC算法(随机抽样一致算法)识别点云数据中的直线与圆形,并得到相应的障碍物与目标物的二维坐标
  • (4) 路径规划:在MATLAB中编程,利用得到的出发点、障碍物和目标点各自的坐标建立势场,并用梯度下降法寻找避开障碍物、到达目标点的最优路径

实际上,在动态情况下的路径规划问题中,实现的步骤也与上述大致相同,只是并没有MABLAB等电脑端的辅助软件帮助其进行数据处理与路径规划,需要将这些算法通过对STM32单片机编程集成到小车上,从而实现在测试地图环境随机发生改变的情况下仍然能够按照局部最优的路线避开障碍物到达目标点。


4.3 核心程序逻辑
4.3.1 激光雷达极坐标点云数据的处理与变换——得到二维地图

在2.5节,我们给出了我们的KEIL工程。通过将其烧入至STM32内,并按照特定接线方式将激光雷达与主控板连接,利用串口通信及相关软件成功在电脑上读取了激光雷达扫描得到的点云极坐标数据(如图2-8右图)。在实际测试时,我们选取了五个坐标点分别放置小车(控制朝向相同)对周围地图环境进行扫描,获得了“data1.txt”等一系列数据文件(详见附件)。

在MATLAB程序的第一部分,我们首先利用importdata函数从数据文本文件中读取相应数据,并将其按列分割为“angle”和“distance”两部分。随后,我们利用MATLAB中自带的坐标变换函数pol2cart将一系列的极坐标数据转换为笛卡尔坐标系下的x-y数据(算法原理如下图4-1)。事实上,由于小车每次扫描的位置均不同,因此还需要将多次扫描的结果结合每次小车放置位置的坐标进行一系列的拼接与变换,才可得到最终的场地二维地图(效果如下图4-2)。

图4-1 利用pol2cart函数进行坐标变换的原理示意图

19

图4-2 经拼接后得到的场地二维地图

20

该部分的代码给出如下:

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
65
`%Step1:处理激光雷达扫描数据,绘制二维坐标系地图`

`ans=importdata("data1.txt");`

`angle=ans(:,1);`

`angle=angle.*2.*3.1415926./360;`

`distance=ans(:,2);`

`[x,y] = pol2cart(angle,distance);`

`ans1=importdata("data2.txt");`

`angle1=ans1(:,1);`

`angle1=angle1.*2.*3.1415926./360;`

`distance1=ans1(:,2);`

`[x1,y1] = pol2cart(angle1,distance1);`

`ans2=importdata("data3.txt");`

`angle2=ans2(:,1);`

`angle2=angle2.*2.*3.1415926./360;`

`distance2=ans2(:,2);`

`[x2,y2] = pol2cart(angle2,distance2);`

`ans3=importdata("data4.txt");`

`angle3=ans3(:,1);`

`angle3=angle3.*2.*3.1415926./360;`

`distance3=ans3(:,2);`

`[x3,y3] = pol2cart(angle3,distance3);`

`ans5=importdata("data5.txt");`

`angle5=ans5(:,1);`

`angle5=angle5.*2.*3.1415926./360;`

`distance5=ans5(:,2);`

`[x5,y5] = pol2cart(angle5,distance5);`

`x4 = [x;x1-100;x2+1850;x3+1400;x5+1500]/100;`

`y4 = [y;y1+1750;y2+100;y3+1650;y5+1000]/100;`

`figure(1);`

`scatter(x4,y4,1);`

`hold on`

`axis equal`

`axis( [ -3, 22, -3, 22 ] )`
4.3.2 利用RANSAC算法识别地图中的直线和圆——获得目标点与障碍物的坐标

在成功利用激光雷达的扫描数据建立了二维地图后,我们需要让机器人知道哪里能走哪里不能走,要走向哪里,即明确目标点以及障碍物的具体坐标。在本测试项目中,设定圆柱为目标物而正方体为障碍物,所以问题的关键即为如何从二维地图中识别出圆形以及正方形(本质上为直线的拼接)并获得其坐标。

在之前的课程中,介绍了RANSAC这一算法。随机样本一致性(Random Sample Consensus RANSAC) 是一种迭代方法,用于从包含异常值的观察数据中估计出数学模型参数,因此也可以理解为一种异常值检测方法。RANSAC的一个基本假设是,数据由内点(“inliers”)和外点(“outliers”)组成,其中内点是在一定误差范围内可以通过一些模型参数来解释的数据,外点是不符合模型的数据。RANSAC的另一个假设是,随机选取的样本数据都是内点,存在一个可以估计模型参数的过程,该模型可以最佳地解释或拟合该数据。通过该算法,我们可以有效地从已有的地图(本质上是二维坐标系下的点集数据)中拟合出直线与圆的轮廓,并获取相应图形的对应坐标。

该算法的实现步骤如下:

(1) 从原始数据集S中随机选择子集s,s为假设的内点(子集s一般为最小子集,如:直线选取两个点,圆选择三个点)

(2) 依据子集s估计模型参数

(3) 遍历数据集S中除子集s外的所有数据,如果数据点在给定误差e以内,则标记为内点,否则标记为外点

(4)所有内点组成一致集,如果一致集中点的个数满足给定阈值T,则用一致集中所有内点重新估计模型参数,然后结束算法

(5)如果一致集中内点个数少于阈值T,则重新选择新的子集s,并重复步骤(1)-(4)

(6) 经过K次迭代,选择一个内点数量最多的一致集,用一致集中所有内点重新估计模型参数,然后结束算法

基于上述基本思想与步骤,我们编写了一段MATLAB代码,用于二维坐标地图中直线的识别与拟合。在此基础之上,我们根据算法原理,从点集中随机取出三个点,利用三点成圆获得圆的方程(利用自己编写的函数ThreePoint2Circle)。再对圆的轨迹赋予一个宽度,统计落入这个宽度中的点的数量,对所有的点进行逐个取点,获得最优的圆的方程。值得注意的是,由于待识别的正方形是由多条直边构成,这要求我们需要重复对于该图像进行扫描拟合,且需要在一次拟合之后将该次拟合中涉及的数据点删除以防影响下次拟合。下面将给出这一部分的代码实现以及拟合效果(如图4-3、4-4)。

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
`%Step2:利用RANSAC算法识别直线和圆`

`%(1)圆的识别`

`a = [x4,y4];`

`% RANSCA参数:`

`% 迭代次数`

`iter = 0;`

`% 查看圆数据的大小`

`[m,n] = size(a);`

`% 误差参数`

`berr = 0.02;`

`% 拟合参数`

`bfit = [];`

`% 内点个数为点数的1/3`

`t = floor(m/3);`

`% 开始循环迭代`

`while iter<100`

`% 随机挑选三个点,三个点不重复`

`% 拟合圆最少需要三个点,拟合直线最少需要两个`

`% ran为索引编号`

`ran = randperm(m,3)';`

`% b为索引得到的点`

`b = a(ran,:);`

`% 根据随机得到的三个点,计算圆的半径和圆心`

`[r1,p1] = ThreePoint2Circle(b(1,1:2), b(2,1:2), b(3,1:2));`

`% 选择除了随机得到的三个点外的其他点`

`c = setdiff(a,b,"rows");`

`% 计算每个点到圆心的距离dis`

`dis = sqrt(sum((c(:,1:2)-p1).^2,2));`

`% 计算 dis和拟合圆的误差`

`res = dis - r1;`

`% 选择小于误差的点,进入到内点中`

`d = c(res<berr,:);`

`len = length(d(:,1));`

`% 判断内点数量是否满足条件`

`if len > t`

​ `% 满足条件时,多点拟合圆,这里用平均值计算圆心`

​ `p = mean(d);`

​ `r = mean(sqrt(sum((d(:,1:2)-p(:,1:2)).^2,2)));`

​ `% 多点拟合的圆和随机点拟合的圆的误差`

​ `err = sqrt(sum((p-p1).^2))+sqrt((r-r1)^2);`

​ `% 如果误差满足条件,则可以结束循环`

​ `% 不满足则继续`

​ `if err < berr`

​ `bfit = [p,r];`

​ `berr = err;`

​ `break`

​ `else`

​ `iter = iter+1;`

​ `continue`

​ `end`

`else`

​ `iter = iter+1;`

`end`

`end`

`%绘图`

`para = [p(1)-r, p(2)-r, 2*r, 2*r];`

`rectangle('Position', para, 'Curvature', [1 1]);`

`%(2)直线的识别`

`iter = 100;`

`data1=transpose(x4);`

`data2=transpose(y4);`

`data=[data1;data2];`

`for t=1:10`

`number = size(data,2); % 总点数`

`bestParameter1=0; bestParameter2=0; % 最佳匹配的参数`

`sigma = 1;`

`pretotal=0; %符合拟合模型的数据的个数`

`for i=1:iter`

`%随机选择两个点`

`idx = randperm(number,2);`

`sample = data(:,idx);`

`%拟合直线方程 y=kx+b`

`line = zeros(1,3);`

`x = sample(:, 1);`

`y = sample(:, 2);`

`k=(y(1)-y(2))/(x(1)-x(2)); %直线斜率`

`b = y(1) - k*x(1);`

`line = [k -1 b];`

`mask=abs(line*[data; ones(1,size(data,2))]); %求每个数据到拟合直线的距离`

`total=sum(mask<sigma); %计算数据距离直线小于一定阈值的数据的个数`

`if total>pretotal %找到符合拟合直线数据最多的拟合直线`

​ `pretotal=total;`

​ `bestline=line; %找到最好的拟合直线`

`end`

`end`

`%显示符合最佳拟合的数据`

`mask=abs(bestline*[data; ones(1,size(data,2))])<sigma;`

`hold on;`

`k=1;`

`index=[];`

`for i=1:length(mask)`

`if mask(i)`

​ `inliers(1,k) = data(1,i);`

​ `k=k+1;`

​ `index=[index i];`

`end`

`end`

`%删除完成拟合的点以进行下一次拟合`

`for i=1:length(index)`

`data(:,index(i))=[];`

`for j=1:length(index)`

​ `if(index(j)>index(i))`

​ `index(j)=index(j)-1;`

​ `end`

`end`

`end`

`% 绘制最佳匹配曲线`

`bestParameter1 = -bestline(1)/bestline(2);`

`bestParameter2 = -bestline(3)/bestline(2);`

`xAxis = min(inliers(1,:)):max(inliers(1,:));`

`yAxis = bestParameter1*xAxis + bestParameter2;`

`plot(xAxis,yAxis,'r-','LineWidth',2);`

`end`

`function [R,P0] = ThreePoint2Circle(P1, P2, P3)`

`%% 求圆心和半径,三个点可以求圆心和半径`

`x1 = P1(1); x2 = P2(1); x3 = P3(1);`

`y1 = P1(2); y2 = P2(2); y3 = P3(2);`

`z1 = x2^2 + y2^2 - x1^2 - y1^2;`

`z2 = x3^2 + y3^2 - x1^2 - y1^2;`

`z3 = x3^2 + y3^2 - x2^2 - y2^2;`

`A = [(x2-x1), (y2-y1); (x3-x1), (y3-y1); (x3-x2), (y3-y2)];`

`B = 0.5*[z1; z2; z3];`

`P0 = (A'*A)\A'*B;`

`R1 = sqrt( (P0(1) - P1(1))^2 + (P0(2) - P1(2))^2 );`

`R2 = sqrt( (P0(1) - P2(1))^2 + (P0(2) - P2(2))^2 );`

`R3 = sqrt( (P0(1) - P3(1))^2 + (P0(2) - P3(2))^2 );`

`R = (R1 + R2 + R3)/3;`

`P0 = P0';`

`End`

图4-3(上) 对于二维地图中一条直线的拟合(红线为拟合结果)

(可以看到拟合效果相对良好)

21

图4-4(右) 对于给定圆坐标数据的RANSAC拟合(上图为给定的圆,下图为拟合出的圆)

(说明该算法实现的有效性)

22

4.3.3 建立势场并利用梯度下降法确定最优路径(人工势场法)

人工势场法引入了物理中斥力场和引力场的思想,把工作环境抽象为一个电磁场,而机器人则是其中的一个电荷,机器人在磁场力的作用下移动。人工势场法会在障碍物周围构建斥力场、在目标点周围构建引力场;这样,机器人便能够在斥力场和引力场的作用下向目标点移动。同时,当障碍物和目标点太近时,机器人很可能会因为刹不住车而出现无法到达目标点等问题,这也就出现了一堆相应的优化算法。

通过利用RANSAC算法对于地图中具有特定形状的边界、障碍物与目标物进行识别,我们成功获得了障碍物与目标点的坐标。在此基础之上,我们基于原型函数23(a,b即为障碍物/目标点的x,y坐标)建立势场。通过观察不难发现,在以(a,b)为圆心、半径为1的圆之外的地方该势函数均为正,反之为负。事实上,对于场地内的3个障碍物以及1个目标物而言,所形成的是一个叠加场,由原型函数作用于不同的点叠加而成。在此,我们不妨认为势场为正处具有排斥力而势场为负处具有吸引力,需要吸引小车向目标点走去而花费尽量少的能量。在这样的算法理念基础上,我们需要在代表目标点的原型函数部分加上负号;更进一步的,我们还希望这个吸引力足够大而防止被障碍物阻断,因此在建立势场时,不妨在代表目标点的原型函数前乘上一定的系数以保证其足够强大的吸引力。最终,我们根据地图实际情况,建立了整个势场叠加后的函数方程:F=log(sqrt((x-4.75).^2+(y-12.5).^2))+log(sqrt((x-12).^2+(y-7.6).^2))+log(sqrt((x-11.2).^2+(y-13).^2))-5*log(sqrt((x-16.5).^2+(y-18.5).^2)),并根据该函数绘制了势能图(如图4-5)与等势线图(如图4-6)。

图4-5 势场函数势能图

24

图4-6 势场函数等势线图

25

该部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
`%Step3:通过识别得到的障碍物和目标坐标建立势场`

`v=-2:1:22;`

`[x,y]=meshgrid(v);`

`F=log(sqrt((x-4.75).^2+(y-12.5).^2))+log(sqrt((x-12).^2+(y-7.6).^2))+log(sqrt((x-11.2).^2+(y-13).^2))-5*log(sqrt((x-16.5).^2+(y-18.5).^2));`

`[px,py]=gradient(F,1,1);`

`contour(x,y,F);`

`hold on;`

`title('人工势场法路径规划');`

`quiver(x,y,px,py,0);`

`figure(2);`

`surf(x,y,-F);`

在建立完势场之后,由于我们需要寻找的是避开障碍物而通往目标点的最优路径,实际上即为所耗费能量最少的路径,我们引入了梯度下降法,通过间隔相同距离的不断迭代,在每一处都寻找能量下降最快的方向(即为梯度方向)前进(在MATLAB中通过调用函数文件path_plan.m与computP.m实现该功能),最终得到了如下图4-7绿色线所示的最优路径。

图4-7 人工势场法路径规划结果(绿色即为规划出的最优路径)

26

其中,path_plan函数是整个算法过程中的关键,也是梯度下降思想的集中体现,其大致实现思路流程如下:

1)起点、终点 、障碍物、迭代次数、取点半径等参数的设定

2)以起点为中心,作半径为r的圆,从圆上取八个均布的点

3)分别计算八个点的前进“代价”—— 终点对其的引力+所有障碍物对其的斥力

4)取“代价”最小的点的坐标,结合现有起点,计算得到新的起点,然后重复上述内容

5)当发现 一个点距离终点很近 or 迭代的次数计算完 程序停止。

该部分的实现代码如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
`%Step4:通过人工势场法确定最优路径`

`axis([-3 22 -3 22]);`

`begin=[0;0]%起始点坐标`

`over=[16.5;18.5];%目标点坐标`

`figure(1);`

`hold on;`

`plot(begin(1),begin(2),'*b','MarkerSize',10);`

`plot(over(1),over(2),'*b','MarkerSize',10);`

`obstacle=[4.75,12,11.35;12.75,7.5,12];%障碍物坐标`

`point= path_plan(begin,over,obstacle);`

`function [ point ] = path_plan(begin,over,obstacle)`

`iters=1; %迭代次数`

`curr=begin;`

`testR=0.1; %测试8点的圆的半径为0.1`

`while (norm(curr-over)>0.2) && (iters<=2000)`

`point(:,iters)=curr;`

`% attr=attractive(curr,over);`

`% repu=repulsion(curr,obstacle);`

`%curoutput=computP(curr,over,obstacle);`

`%计算当前点附近半径为0.2的8个点的势能,然后让当前点的势能减去8个点的势能取差值最大的,确定这个方向,就是下一步迭代的点`

`%先求这八个点的坐标`

`for i=1:8 testPoint(:,i)=[testR*sin((i-1)*pi/4)+curr(1);testR*cos((i-1)*pi/4)+curr(2)];`

​ `testOut(:,i)=computP(testPoint(:,i),over,obstacle);`

​ `%找出来最小的就可以了`

`end`

`[temp num]=min(testOut);`

`%迭代的距离为0.1`

`curr=(curr+testPoint(:,num))/2;`

`plot(curr(1),curr(2),'og');`

`iters=iters+1;`

`end`

`end`

`function [ output ] = computP( curr,over,obstacle )`

`k_att=1;`

`repu=0;`

`k_rep=100;`

`Q_star=2;`

`%计算当前点距离终点的引力`

`attr=1/2*k_att*(norm(curr-over))^2;`

`%计算障碍点与当前点的斥力`

`%设定障碍的斥力作用半径为2`

`for i=1:size(obstacle,2)`

`if norm(curr-obstacle(:,i))<=Q_star`

​ `repu=repu+1/2*k_rep*(1/norm(curr-obstacle(:,i))-1/Q_star)^2;`

`else`

​ `repu=repu+0;`

`end`

`end`

`output=attr+repu;`

`end`
4.4 实现的实际效果

事实上,尽管RANSAC算法在理论上已经具备足够的拟合精度,但在实际的识别过程中,由于激光雷达扫描获取的数据过多而导致干扰噪点的数量达到了一定规模,以及在算法参数设置上考虑到算力有限等因素而没有采用精确度最高的设置,诸如此类的干扰因素导致在多条直线识别时出现了互相覆盖与识别错误的情况,识别圆形时也并未识别出目标点的圆柱所在处,因此在实际的测试过程中,利用RANSAC算法识别圆与直线以获取目标点和障碍物坐标这一过程并未取得特别良好的效果。为了后面的路径规划算法顺利开展,我们最终采用人工识别的方式,分别给出了起点、障碍物以及目标点的大致坐标,并顺利实现了利用人工势场法进行路径规划的算法,合理规划出了从起点避开障碍物到达目标点停下的最优路径,并通过STM32单片机编程成功驱动了小车按照规划好的路径进行运动,顺利完成开环测试。


五、实验结果及分析


经过几次测试,在进行4至5个位置的扫描之后,通过将数据进行变换与拼接,可以得到一张较为完整的二维地图,再将地图中通过RANSAC算法识别出的特定点位数据读入程序运行,可以得到一条较为合理的最优路径。

事实上,在前面的嵌入式控制系统设计部分,我们计划采用PID方式对于小车与电机进行反馈调节控制,但在实际的测试中,PID的控制方式实现的效果并不尽如人意,无法合理利用MATLAB路径规划所得到的数据结果顺利完成测试。于是我们果断选择了重新使用PWM的方式,依托于MATLAB程序运行规划出的路径对应的相关数据计算所需要的PWM以及延时的时间。最终采用的代码如下图5-1所示(具体KEIL工程内容详见Run.zip附件)。

图5-1 PWM电机驱动部分实现代码

27

28

通过调整PWM的方式对小车进行开环运动控制,最终可以较好达到项目要求。(实现效果见下图5-2及视频附件“测试.mp4”)

图5-2 静态路径规划实现效果实地测试

29

30

虽然静态路径规划部分完成情况相对较好,但遗憾的是,由于对于C语言编程不是特别熟悉,包括受限于对库的了解、算法的时间复杂度较高、实现繁琐以及对于STM32内部利用效率的不完全开发等因素,最终并没有能够成功完成动态部分的路径规划。事实上,动态情况下的路径规划更符合我们在日常生活中常见的应用场景,不论是扫地机器人还是汽车导航,本身所应对的环境都在时刻发生着改变,因此动态的路径规划问题仍然值得在课程结束之后继续进行深挖和探索。希望在未来的工程实践中,我们能够以更加定量化的思维去分析和思考问题,同时更加熟练的掌握相关的算法设计,提高自己的硬件嵌入式编程能力。


六、个人总结


在路径规划这一阶段的课程中,我在前半段主要负责的是STM32单片机的一些基础开发,对于其基本的开发流程以及GPIO等基本的功能模块有了一定的了解并能进行一些简单的32单片机编程;在后半段,我主要负责整体路径规划项目的思路整理与算法设计,结合课堂上介绍的RANSAC识别算法以及人工势场法规划路径,课下积极结合概念的巩固以及相关资料的查询,阅读了相关的示例代码,并根据算法的整体思路自己动手实现了RANSAC算法对于直线与圆的识别拟合以及在建立的势场中利用梯度下降法实现路径规划的MATLAB程序,积极将自己的算法实现与队友编写的STM32 C语言程序融合在一起,在与队友的充分交流沟通与合作的基础上共同完成了该项目。在死亡之桥的测试项目中,我在一个人调试了单片机程序许久未果后,与队友进行了积极的沟通与合作,也基本确定了由我负责编写MATLAB算法程序给队友的单片机编程提供数据支持的合作模式,对于我们团队的所有人来说都是一次难忘的经历。在利用激光雷达扫描地图的过程中,我们也充分信任彼此,在他们编写好了读取雷达数据的相关程序后,我结合着MATLAB程序的需求对于KEIL代码的输出格式部分进行了一定的修改,在通力合作下最终圆满完成了该项任务。除此之外,也非常感谢整个课程阶段过程中凌睿老师在算法思路方面对我们进行的一系列教学与指导以及助教学长们在答疑时的倾力相助。