目录
一、2019 电子设计大赛 F 题介绍
全国大学生电子设计竞赛是教育部和工业和信息化部共同发起的大学生学科竞赛之一,是面向大学生的群众性科技活动,每两年举办一次 ,目的在于推动高等学校促进信息与电子类学科课程体系和课程内容的改革及其课程教学和实验室建设工作。自 1994 年创办以来,参赛学生增长到每年约 4 万人,已成为中国规模最大、参赛范围最广、极具影响力的针对在校本专科大学生的电子设计竞赛。
2019 年的电子设计大赛吸引了全国 1096 所高校、17178 支代表队、51534 名同学参赛,可谓盛况空前。在众多富有挑战性的题目中,F 题脱颖而出,吸引了众多参赛者的目光。这道题聚焦于设计并制作纸张计数显示装置,看似平常的任务,实则蕴含着对电子设计综合能力的深度考察。
该装置由两块平行极板(极板 A、极板 B)和测量显示电路构成。极板 A 和极板 B 上的金属电极部分均为边长 50mm±1mm 的正方形,导线 a 和导线 b 长度均为 500mm±5mm。测量显示电路需具备 “自校准” 功能,在正式测试前,要对置于两极板间不同张数的纸张进行测量,以获取测量校准信息。同时,还能自检并报告极板 A 和极板 B 电极之间是否短路。
在基本要求方面,需要测量置于两极板之间 1 - 10 张不等的给定纸张数。每次在极板间放入被测纸张并固定后,一键启动测量,显示被测纸张数并发出一声蜂鸣。并且每次测量从按下同一测量启动键到发出蜂鸣的时间不得超过 5 秒钟,在此期间对测量装置不得有任何人工干预。
而发挥部分则进一步提高了难度,要求在极板、导线均不变的情况下,测量置于两极板之间 15 - 30 张,甚至 30 张以上的给定纸张数,其他相关要求同基本要求中的测量启动键、显示蜂鸣、测量时间限制以及不得人工干预等规定 。
二、解题思路分析
(一)整体思路
这道题目的核心在于利用电容变化来测量纸张数量。根据电容的基本公式\(C=\frac{\epsilon S}{d}\)(其中\(C\)表示电容,\(\epsilon\)表示介电常数,\(S\)表示极板面积,\(d\)表示极板间距离),当极板面积\(S\)固定,极板间插入纸张时,由于纸张的介电常数\(\epsilon\)与空气不同,会导致电容\(C\)发生变化 。并且随着纸张数量的增加,相当于增加了极板间的介电物质厚度,进一步改变电容值。我们的目标就是通过测量这种电容变化,来准确推算出纸张的数量。具体实现过程中,需要将电容变化转化为电信号,再通过微控制器进行处理和计算,最终在显示模块上呈现出纸张的数量。
(二)硬件选择与分析
- 主控芯片:我们选用了 STM32F103ZET6 作为主控芯片,它基于 ARM Cortex-M3 内核,工作频率可达 72MHz,拥有 512KB 的闪存和 64KB 的 SRAM。丰富的资源使得它能够高效地处理各种数据和任务。在处理电容测量数据时,其强大的运算能力可以快速进行数据转换和计算。而且,它还具备多个通用定时器、串口通信接口(USART)、SPI 接口、I2C 接口等,这些丰富的外设接口为与其他模块的通信和控制提供了极大的便利。比如,通过 SPI 接口可以与电容测量模块进行高速数据传输,获取准确的电容数据;利用 USART 接口能够方便地与显示模块进行通信,将处理后的纸张数量信息发送过去进行显示 。此外,它的低功耗设计也符合我们对装置能耗的要求,即使长时间运行也不会消耗过多电量。
- 电容测量模块:电容测量模块选用了 FDC2214 芯片,它是一款高精度的电容检测传感器芯片,激励频率范围为 10kHz - 10MHz,拥有 4 个通道,分辨率高达 28 位,电源电压要求在 2.7V - 3.6V,采用 I²C 接口进行通信。其工作原理基于差分电容传感,能够精确测量 LC 谐振器的振荡频率,然后将频率测量值转换为等效电容输出。在测量纸张数量的过程中,FDC2214 能够快速、准确地检测到因纸张数量变化而引起的电容微小变化,为后续的数据处理提供可靠的数据基础。而且,它的多个通道可以让我们同时进行多组测量,提高测量的效率和准确性,也便于我们进行一些拓展功能的开发,比如同时测量不同类型纸张的电容变化等。
- 显示模块:显示模块采用 3.2 寸 LCD 串口屏,其分辨率为 320*240px,通讯方式为 TTL UART。这种屏幕具有良好的显示效果,能够清晰地呈现出测量的纸张数量。而且串口通信方式使得它与主控芯片之间的连接和通信非常方便,只需要简单的几根线就可以实现数据传输。在硬件设计和软件编程过程中,串口通信的接口便利性大大降低了开发难度,减少了开发时间。同时,它还支持电阻触摸屏,这为用户操作提供了更多的交互方式,比如可以通过触摸屏幕进行测量启动、参数设置等操作,提升了用户体验 。
(三)硬件设计优化历程
在整个项目的开发过程中,硬件设计经历了多次优化,从第一代到第六代,每一代都在不断改进和完善。
第一代硬件设计较为简单,直接采用铜板夹纸的方式来构建电容极板。在实际测试中,发现这种设计存在诸多问题。一方面,夹纸操作非常不便,每次放置纸张都需要小心翼翼,否则容易导致纸张位置不稳定,影响测量结果;另一方面,测量偏差较大,由于铜板的固定方式不够稳固,以及纸张放置的不均匀性,使得测量得到的电容值波动很大,无法准确反映纸张的数量。
为了解决这些问题,第二代硬件在固定方式上进行了改进,采用了螺丝固定铜板的方法,一定程度上提高了稳定性,但仍然无法完全消除因纸张放置和外界干扰带来的测量误差。
第三代硬件则在电路部分进行了优化,增加了滤波电路,减少了外界干扰对电容测量的影响,数据的稳定性有了一定提升,但在实际使用中,还是会出现一些异常数据。
第四代硬件开始尝试更换不同的材料和结构,使用了亚克力板来代替部分铜板,减轻了装置的重量,同时也改善了一些因材料引起的问题,但在测量精度上提升并不明显。
到了第五代硬件,加入了滑轨设计,使得纸张的取放更加规范,能够保持每次取放纸张时的相似度,减少了人为因素对测量的影响。同时,进一步优化了亚克力板的结构和尺寸,提高了整体的稳定性。然而,在实际测试中发现,当纸张数量较多时,会出现纸张下坠的情况,影响测量精度,而且焊点的微小变化也会对测量结果产生一定的影响。
经过多次尝试和改进,第六代硬件在前面几代的基础上,对滑轨进行了加固和优化,增加了防止纸张下坠的装置。同时,对焊点进行了特殊处理,采用了更加稳定的焊接工艺和材料,大大减少了焊点对测量结果的影响。经过这一系列的优化,第六代硬件在测量精度和稳定性上都有了质的飞跃,能够满足比赛的各项要求 。
三、软件代码实现
(一)整体架构
软件代码的整体架构围绕着实现纸张计数显示装置的各项功能展开,主要包含数据采集、数据处理、显示以及按键与蜂鸣器控制等几个核心功能模块,各个模块相互协作,共同完成从电容数据采集到纸张数量显示的全过程。
数据采集模块负责与 FDC2214 电容测量芯片进行通信,通过 I²C 协议读取芯片采集到的电容值数据 。这是整个系统的前端数据获取部分,其稳定性和准确性直接影响后续的处理结果。
数据处理模块则对采集到的电容值进行一系列处理。首先,根据电容值与纸张数量之间的关系,将电容值转换为对应的纸张数量。在这个过程中,为了提高数据的准确性和稳定性,引入了卡尔曼滤波算法。卡尔曼滤波算法能够对含有噪声的测量数据进行优化处理,通过不断地预测和更新,减少数据的波动,从而得到更准确的纸张数量估计值。
显示模块主要负责将处理后得到的纸张数量以及相关信息,如测量状态等,在 3.2 寸 LCD 串口屏上进行清晰显示,为用户提供直观的结果展示 。
按键与蜂鸣器控制模块实现了对按键操作的检测和响应。当用户按下测量启动键时,系统触发测量流程;而在测量完成后,控制蜂鸣器发出一声鸣叫,告知用户测量已完成,增强了系统的交互性和用户体验。
(二)关键代码解析
- FDC2214 驱动代码
-
- 初始化:在初始化 FDC2214 时,需要配置其 I²C 通信参数以及相关寄存器。首先,要设置 I²C 的时钟速率等基本通信参数,以确保与 FDC2214 之间能够稳定、准确地进行数据传输。在 STM32 平台上,使用如下代码进行 I²C 初始化:
void I2C_Init(void)
{
I2C_InitTypeDef I2C_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
接着,对 FDC2214 的寄存器进行配置,设置测量模式、激励频率等参数。例如,设置为连续测量模式,并选择合适的激励频率:
void FDC2214_Init(void)
{
// 配置测量模式寄存器
FDC2214_Write_Reg(0x01, 0x01);
// 设置激励频率相关寄存器(假设设置为某一合适频率)
FDC2214_Write_Reg(0x03, 0xXX);
}
其中,FDC2214_Write_Reg函数用于向 FDC2214 的指定寄存器写入数据,其实现如下:
void FDC2214_Write_Reg(uint8_t reg, uint8_t data)
{
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, FDC2214_ADDR, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, reg);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
}
- 数据读取:读取 FDC2214 采集到的电容值数据时,需要按照其数据读取协议进行操作。先发送读取指令,然后接收数据。
uint32_t FDC2214_Read_Data(void)
{
uint8_t data[4];
uint32_t result;
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, FDC2214_ADDR, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, 0x08);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, FDC2214_ADDR, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
for (int i = 0; i < 4; i++)
{
if (i == 3)
{
I2C_AcknowledgeConfig(I2C1, DISABLE);
}
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
data[i] = I2C_ReceiveData(I2C1);
}
I2C_GenerateSTOP(I2C1, ENABLE);
result = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
return result;
}
- 数据处理代码
-
- 电容值到距离值,再到纸张数量的转换:根据电容的计算公式以及实验标定的参数,将采集到的电容值转换为距离值。假设已经通过实验得到电容值与距离值的关系公式为distance = a * capacitance + b(其中a和b为标定系数),代码实现如下:
float CapacitanceToDistance(uint32_t capacitance)
{
float cap = (float)capacitance;
float distance = a * cap + b;
return distance;
}
接着,根据距离值与纸张数量的关系,将距离值转换为纸张数量。同样,通过实验标定得到距离值与纸张数量的关系,假设关系公式为paper_num = c * distance + d(其中c和d为标定系数),代码如下:
int DistanceToPaperNum(float distance)
{
int paper_num = (int)(c * distance + d);
return paper_num;
}
- 卡尔曼滤波算法的应用:在数据处理过程中,为了减少噪声干扰,提高数据的稳定性和准确性,引入了卡尔曼滤波算法。首先定义卡尔曼滤波器的结构体:
typedef struct
{
float Q;
float R;
float X_hat;
float P;
float K;
} KalmanFilter;
然后实现卡尔曼滤波的初始化函数:
void Kalman_Init(KalmanFilter *kf, float q, float r, float initial_value)
{
kf->Q = q;
kf->R = r;
kf->P = 1.0f;
kf->X_hat = initial_value;
}
接着是预测和更新函数。预测函数根据上一时刻的状态估计值预测当前时刻的状态:
void Kalman_Predict(KalmanFilter *kf)
{
kf->X_hat = kf->X_hat;
kf->P = kf->P + kf->Q;
}
更新函数则根据当前的测量值对预测值进行修正:
void Kalman_Update(KalmanFilter *kf, float measurement)
{
kf->K = kf->P / (kf->P + kf->R);
kf->X_hat = kf->X_hat + kf->K * (measurement - kf->X_hat);
kf->P = (1 - kf->K) * kf->P;
}
在主程序中,使用卡尔曼滤波对采集到的电容值进行处理:
KalmanFilter kf;
float capacitance;
float filtered_capacitance;
Kalman_Init(&kf, 0.01f, 0.1f, 0.0f);
while (1)
{
capacitance = (float)FDC2214_Read_Data();
Kalman_Predict(&kf);
Kalman_Update(&kf, capacitance);
filtered_capacitance = kf.X_hat;
float distance = CapacitanceToDistance(filtered_capacitance);
int paper_num = DistanceToPaperNum(distance);
// 后续处理纸张数量,如显示等
}
- 显示驱动代码
显示驱动代码负责将处理后的数据在 3.2 寸 LCD 串口屏上进行显示。首先,需要配置串口通信参数,以实现与 LCD 串口屏的通信。在 STM32 平台上,配置串口 1 用于与 LCD 通信:
void USART1_Init(void)
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
然后,实现向 LCD 发送数据的函数。例如,发送一个字符串:
void LCD_SendString(char *str)
{
while (*str)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, *str++);
}
}
在主程序中,当得到处理后的纸张数量后,将其转换为字符串并发送到 LCD 进行显示:
int paper_num;
char display_str[20];
// 假设已经得到纸张数量paper_num
sprintf(display_str, "Paper Num: %d", paper_num);
LCD_SendString(display_str);
(三)代码优化要点
在软件代码实现过程中,有几个关键的优化要点需要注意。
滤波是提高数据稳定性和准确性的关键步骤。由于 FDC2214 电容测量芯片非常灵敏,采集到的数据容易受到各种噪声的干扰,如电磁干扰、电源噪声等,导致数据跳动大。除了使用卡尔曼滤波算法外,还可以考虑增加均值滤波等其他滤波方法。均值滤波通过对多次采集的数据进行平均,能够有效减少数据的波动 。例如,在采集电容值时,连续采集 N 次数据,然后计算这 N 次数据的平均值作为最终的采集值:
uint32_t Get_Average_Capacitance(void)
{
uint32_t sum = 0;
for (int i = 0; i < N; i++)
{
sum += FDC2214_Read_Data();
}
return sum / N;
}
解决电压不稳定问题也至关重要。电压不稳定可能会导致 FDC2214 工作异常,进而影响电容测量的准确性。一方面,可以采用单独的稳压电源为 FDC2214 供电,避免与其他模块共用电源导致的电压波动。另一方面,在代码中可以增加对电源电压的监测功能。当检测到电源电压超出正常范围时,进行相应的处理,如提示用户检查电源或者暂停测量,待电压恢复正常后再继续 。
在数据处理过程中,合理优化算法的时间复杂度和空间复杂度也能提高系统的性能。例如,在进行电容值到纸张数量的转换计算时,可以预先计算一些常量值,避免在每次计算时重复计算,从而提高计算效率。同时,合理使用内存,及时释放不再使用的变量所占用的内存空间,防止内存泄漏和内存碎片化问题,确保系统能够稳定、高效地运行。
四、总结与经验分享
完成 2019 电子设计大赛 F 题的过程,对我来说是一次极具挑战性但又收获满满的经历。从最初对题目的深入剖析,到硬件的选型、设计与不断优化,再到软件代码的编写、调试与完善,每一个环节都充满了困难与惊喜 。
在硬件设计方面,经过多代的改进,我深刻体会到了细节对于整个系统性能的重要性。从最初简单的铜板夹纸设计,到后来加入滑轨、亚克力板以及对焊点等细节的处理,每一次的改进都让我更加明白,一个稳定、可靠的硬件基础是实现准确测量和系统稳定运行的关键。在选择硬件设备时,不仅要考虑其性能指标,还要结合实际的使用场景和需求,综合评估其适用性和稳定性。例如,FDC2214 电容测量芯片虽然精度高,但对工作环境和电源稳定性要求也较高,这就需要我们在设计硬件电路时充分考虑这些因素,采取相应的措施来保证其正常工作。
在软件开发过程中,也遇到了不少难题。如何实现高效的数据采集、准确的数据处理以及稳定的显示驱动,每一个功能模块都需要精心设计和反复调试。滤波算法的选择和优化、电压稳定性的监测与处理,以及算法复杂度的控制等,这些经验都让我在今后的软件开发中能够更加注重细节,提高代码的质量和系统的性能。同时,在代码编写过程中,要注重代码的可读性和可维护性,合理地进行模块化设计,这样不仅便于调试和修改,也有利于团队协作开发。
通过这次比赛,我不仅在电子设计的技术层面有了很大的提升,更重要的是培养了自己解决问题的能力和团队协作精神。在遇到困难时,不轻易放弃,通过查阅资料、请教他人、反复尝试等方式,最终找到解决问题的方法。团队成员之间的相互协作、沟通和支持也是成功的关键,大家分工明确,各自发挥自己的优势,共同攻克了一个又一个的难题。
如果你也对电子设计感兴趣,我强烈建议你积极参与各类电子设计竞赛。这不仅是一个检验自己所学知识的平台,更是一个提升自己能力、拓展视野的绝佳机会。在竞赛中,你将遇到各种各样的挑战,也会结识许多志同道合的朋友,大家相互学习、相互交流,共同进步。相信你在参与的过程中,也会像我一样收获满满,为自己的学习和未来的职业发展积累宝贵的经验 。