一、设计背景
ADC(Analog-to-Digital Converter,即模拟-数字转换器)是电子系统中不可或缺的关键组件,它将连续的模拟信号转换为数字信号,为数字处理和分析提供了可能。ADC在信号转换、测量与数据采集、控制系统输入以及通信与信号处理等方面发挥着重要作用,其广泛的应用促进了各行业电子设备的智能化和精确控制,是推动现代科技进步的关键因素之一。
数字电压电流表结合了ADC的技术与电路测量原理,能够精确地将模拟的电压电流信号转换为数字显示,便于电子工程师直观读取和分析。这种设备不仅提高了电路测量的准确性和效率,还帮助工程师更好地理解电路行为,是进行电子设计和故障排查的得力助手,对电子工程师的工作具有重要的辅助作用。在产品应用上,数字电压电流表确保了电路设计的准确性和安全性,同时也为产品的质量控制和后期维护提供了有力支持。
学习设计和制作一个数字电压电流表对于个人专业技能的提升是非常有益的。数字电压电流表项目涵盖了微控制器电路的设计与实现、信号采集与处理电路的设计、用户界面的开发与优化以及产品外观的设计等多个方面,融合了电子技术、微控制器编程、电路设计以及工业设计等多领域知识。考虑到初学者的学习进度与知识吸收能力,我们特别推出了这一入门级的数字电压电流表项目,非常适合电子技术的初学者以及想要深入学习微控制器应用的人群。该项目具备以下几个亮点:
- 采用核心板加扩展板设计理念,采用插件器件设计,让学习更能简单,让探索能更深入;
- 核心板选用国产武汉芯源半导体CW32为主控,同时兼容同类型其他款式开发板;但CW32更有优势。
- 项目综合程度高,实用性强,设计完成后可作为桌面日常仪表使用;
- 项目学习资料丰富,包括电路设计教学、PCB设计、代码编程的学习以及工程师调试能力的培养。
二、硬件设计
1.项目原理图
本项目电路图采用模块化绘制,在电路图中对不同模块使用线条进行了分区以便于读图,下面将分模块对电路图进行分析。
2.电路分析
1、供电电路
LDO(低压差线性稳压器)选型
本项目使用LDO作为电源,考虑到实际的电压表头产品多在24V或36V供电的工业场景中应用,本项目选择了最高输入电压高达40V的SE8550K2作为电源。本项目没有使用DCDC降压电路来应对大压差的主要原因为避免设计过程中引入DCDC的纹波干扰,次要原因为降低项目成本。
封装选型
从数据手册截图的表格中,可以看出,同SOT23封装的280℃/W相比,SOT89封装的165℃/W有更好的散热能力(Thermal Resistance,Junction-to-Ambient),单位:摄氏度/瓦。
LDO型号选择
由于项目的主控核心板为5V供电,所以根据下表订购信息描述,选择SE8550K2-HF,输出5V,K即为SOT89封装,±2%精度即可,省成本。HF或LF与环保要求有关,在实际产品设计中需关注。本项目无需关注。
电容器的设计
手册中给出了该LDO的应用原理图:
几乎所有的LDO器件,在设计电路时的原理是基本一致的。所以这张图的参考意义,并不是很大,而往往不一样的考量,是C1、C2应当如何设计,如果你的LDO有EN(enable),还需额外考虑使能引脚的设计,保证芯片正常工作。此外,还有些LDO是可调输出的,他有一个专门的引脚(adj),需要配置相应的外围电阻器来实现输出指定的电压。
而LDO外围电路设计,最核心的是电容器的设计。
LDO外围的电容器在电路中扮演着关键的角色,其作用主要包括以下几个方面:
- 滤波作用 :LDO外围的电容器,特别是输入电容,能够有效地滤除前级电源带来的纹波干扰。
- 改善负载瞬变响应 :输出电容在改善负载瞬变响应方面起着重要作用。当负载电流急剧变化时,LDO存在调整时间,此时输出电容作为临时电源,为电路提供所需的电流,防止输出电压被拉得过低。较大的输出电容值可以进一步改善LDO对大负载电流变化的瞬态响应。
- 相位补偿 :对于可调输出的LDO,并联在上电阻(R1)的电容器(称为反馈电容器CFB)提供超前相位补偿,增加振荡裕度并改善负载瞬态响应。CFB和R1归零,有助于环路稳定性。
- 防止振荡 :适当的电容器配置可以帮助防止LDO电路中的振荡现象,确保电路的稳定运行。
- 纹波抑制 :电容器在LDO中也有助于提高纹波抑制比(PSRR),即电源抑制比,减少电源噪声对电路性能的影响。
- 启动浪涌电流控制 :输入电容在LDO启动过程中充当启动浪涌电流的临时电源,防止输入电压被拉低,影响前级电源稳定性。
归纳:LDO外围的电容器在滤波、改善负载瞬变响应、相位补偿、防止振荡、纹波抑制和启动浪涌电流控制等方面都发挥着重要作用。通过合理配置这些电容器,可以确保LDO电路的稳定性和性能。
如果你认真的翻阅了数据手册,就会发现官方已经给了一些详细的建议和参考,还有Layout设计建议。这避免了工程师去做大量理论计算的复杂工作。
值得注意的是,我在电源设计中,使用电解电容和陶瓷电容前后顺序并联的方式设计。当电解电容和陶瓷电容并联时,它们能够“高低搭配”,使得电路在高频和低频区域都具有较好的滤波作用。电解电容滤除低频干扰,而陶瓷电容滤除高频干扰,两者结合可以获得更好的滤波效果。
在实际电子产品中,电解电容并联陶瓷电容的主要作用是通过两者的互补特性,实现高低频滤波和耦合作用,从而改善电路性能,提高稳定性和抗干扰能力。这种并联方式在电子设备中得到了广泛应用。
扩展知识: 电解电容和陶瓷电容前后顺序并联
- 当电解电容和陶瓷电容并联使用时,电解电容首先过滤掉输入电压中的低频部分,为电路提供稳定的直流电压。这有助于确保电路在低频时的稳定性和可靠性。
- 随后,陶瓷电容进一步补偿高频波动,滤除高频噪声和脉冲危害。这种高低搭配的滤波方式能够更有效地滤除整个频段的噪声和波动,提高电路的整体性能。
- 此外,陶瓷电容还可以消除电解电容在高频时产生的感性特性(电容的寄生电感),进一步提高滤波效果。
新手在试图理解电容的隔直、耦合、旁路、滤波、调谐回路、能量转换、控制等作用时,应先返璞归真,理解电容器的本质:电容器用于存储电荷和电能,并能够在电路中释放这些存储的电荷。
提前简单讲一下,MCU外围的电容器为何要就近放置:远水救不了近火。MCU等器件在使用电的时候,线路上的电流值可能是突变的,有时为了维持MCU上突变的电流(即电荷的定向移动形成的物理量)的需求,需要就近的电容释放电荷。
电流的大小反映了单位时间内通过导体横截面的电荷量,描述电路中电荷运动状态的重要物理量。
二极管防反接
考虑到高电压反接将会给模块带来不可逆的损坏,电压表头供电电路采用了串联二极管的方案进行防反接。
注:本项目使用串联二极管进行防反接考虑到了本设备供电电压通常高于5V的使用场景,二极管的0.7V压降将不会供电造成影响。当供电电压较低时,由于项目的总体功耗较低,实测供电电流较低(20mA),由于肖特基二极管 D4(1N5819)独特的结构,相比于通用开关二极管等,VF更低,由下图可见,压降约为0.2V以内。
常规的电路设计中,使用反向并联二极管+串接保险丝的方案也可达成防反接和电路保护目的。
串联小电阻(10Ω)的作用
本项目额外使用了串联小电阻(10Ω)来进行分压操作,一方面减少在高电压情况下LDO由于较大的压差导致发热严重的问题。另一方面,利用了串联的10欧姆小功率电阻过电流小的原理,充当低阻值保险丝,具有电路过流保护或者短路保护作用。(电阻做保险丝这个点,因为电阻在过流状态,处于发热状态,99%都是开路,它基本不会短路。它的故障分析就决定了它基本上以开路为主。也就是烧断掉,不会短在一起。)
串联的小电阻(10Ω)还可降低上电冲击的峰值,避免冲击过高损坏LDO。
如果没有使用电解电容,串联的小电阻(10Ω)也可避免热插拔的时候,导线电感和陶瓷电容形成谐振,因为陶瓷电容具有非常小的ESR,导致LC网络中的阻尼很少,谐振点的增益会很高,加入外部电阻提供阻尼后就可以抑制谐振点的增益。
电路设计要点及规范
在电源电路绘制时,无论是原理图还是PCB,应当注意几点问题:
原理图规范性:GND朝下,电源在上,不要出现地朝天的情况。
电容器设计:无论原理图还是PCB,电解电容在前,陶瓷电容在后
地线设计:单点接地,当前电源的地,汇总到当前电源的主电解电容的GND上,各级电源的主电解电容,汇总到前级电源的主电解电容的GND上。
扩展知识:PCB设计的单点接地
PCB设计的单点接地是指在PCB线路板上只有一个地点与整个系统的地相连接。这意味着所有的电路地线都将连接到这个公共的接地点上,形成一个统一的接地系统。 PCB设计的单点接地是一种重要的电路设计技术,其主要目的是为了简化电路设计,提高系统的稳定性和抗干扰能力。
单点接地的好处:
- 简化电路设计:单点接地可以大大简化电路设计,减少连接线路的长度和复杂性,降低电磁干扰的可能性。
- 提高系统稳定性:通过单点接地,可以更好地控制接地回路的大小,减少接地环路对信号传输和系统稳定性的影响,从而提高系统的稳定性和可靠性。
- 降低噪声和干扰:单点接地可以减少信号和电流的回流路径,降低系统中产生的回流噪声,提高信号传输的质量和可靠性。
- 减少地环路产生:单点接地可以有效减少接地环路的产生,避免产生接地回路的非线性效应,降低系统中产生的干扰和噪声。
在PCB设计中,单点接地可以通过以下几种方式实现:
- 串联单点接地:将各个需要接地的点通过一条公共的地线串联起来,然后接到公共接地点上。这种方式简单易行,但地线长度较长时可能会引入较大的阻抗。
- 并联单点接地:将各个需要接地的点分别通过短的地线直接接到公共接地点上。这种方式可以减小地线阻抗,但可能需要更多的接地点和更复杂的布线。
- 混合接地:将电路按照信号特性分组,相互不会产生干扰的电路放在一组,一组内的电路采用串联单点接地,不同组的电路采用并联单点接地。这种方式可以灵活应对不同电路的需求,提高系统的整体性能。
注意事项:
- 选择合适的接地点:接地点应该选择在电路板上电位最稳定的点,通常选择在电源滤波电容的负极附近。
- 控制地线长度:地线长度应该尽量短,以减小地线阻抗和引入的噪声。
- 避免地环路:在设计过程中应该尽量避免形成地环路,以减少地环路产生的噪声和干扰。
- 分开处理数字和模拟信号:由于数字信号变化速度快,容易在数字地上产生噪声,因此应该尽量将模拟地和数字地分开处理,减少它们之间的干扰。
综上所述,PCB设计的单点接地是一种重要的电路设计技术,它可以通过简化电路设计、提高系统稳定性和降低噪声干扰等方式提高电路的整体性能。在实际应用中,需要根据具体的系统需求和性能要求来选择合适的单点接地实现方式,并注意控制地线长度、避免地环路和分开处理数字和模拟信号等问题。
2、MCU的选型分析
章节资料下载链接
链接:https://pan.baidu.com/s/1erHWOS6Gej_82cJVNF_jdw?pwd=LCKF 提取码:LCKF
为了降低大家的学习成本,本项目使用立创·地文星CW32F030C8T6开发板(核心板)作为主控,但这并不意味着我们会对这一板块讲的更少。从培养工程师的角度来讲,正确的主控器件选型是十分重要的,这关系到项目的整体优势。
关于电压电流表,笔者用STM32/CW32和一些其他32做了一些调试和测试。在此仅与STM32F103C8T6做比对,作为学习器件选型的参考,主要以提供思路,改善认知为主。
不要盲目的选型
在对本项目进行MCU(微控制器单元)选型时,需要综合考虑多个方面以确保选择的MCU能够满足项目需求。
- 明确自己的项目需求:清晰地了解项目需要多少计算能力,包括时钟速度、处理器核心的类型、是否需要浮点运算单元等。
- 明确项目所需的I/O端口和重要外设,如ADC外设。由于本项目为开发板项目,主要目的为调试学习,在硬件上,对I/O数量不做严格限制:即不考虑此带来的成本等问题。
CW32在本项目中的重要优势
- 宽工作温度:-40~105℃的温度范围
- 宽工作电压:1.65V~5.5V (STM32仅支持3.3V系统)
- 超强抗干扰:HBM ESD 8KV 全部ESD可靠性达到国际标准最高等级(STM32 ESD2KV)
- 本项目重点-更好的ADC:12位高速ADC 可达到±1.0LSB INL 11.3ENOB 多种Vref参考电压... ...(STM32仅支持VDD=Vref)
- 稳定可靠的eFLASH工艺。(Flash0等待)
关于优势的详细解读,我会放在有关ADC采样的章节和拓展章节中详解。
CW32的ADC主要特性
本项目需重点关注 4路参考电压源。内容来自《CW32x030 用户手册》
扩展:如何开始最小系统或核心板的设计
作为一个新人电子工程师,如何快速进行MCU最小系统或核心板的设计,保证自己的项目能正常工作,是很重要的技术。
关于硬件设计要讲的很多内容的核心,多来自于学会用好数据手册、用户手册和其他官方资料,本章节也不例外。思路也是通用的,不仅仅局限于CW32。
在武汉芯源半导体官网上(www.whxy.com)获取相应芯片的开发板资料,首先去看他的原理图:
对于你要进行的项目,便会有另一个成熟的项目做参照和指导,此外,在数据手册中,也会有对外围器件选型的推荐及注意事项。如下图:
而武汉芯源官方还很贴心的给出了《CW32 系列微控制器量产前检查清单》,避免各位工程师因为粗心大意,没有认真看手册导致的细节错误。在此不多赘述,有兴趣的工程师自行查阅。
3、电压采样电路
本项目采用分压电路实现高电压采集,设计可采集电压100V,当前配置采集电压为0-30V。
本项目设计分压电阻为220K+10K,因此分压比例为22:1(ADC_IN11)
分压电阻选型
设计测量电压的最大值,出于安全考虑,本项目为30V(实际最大可显示99.9V或100V);
ADC参考电压,本项目中为1.5V,该参考电压可以通过程序进行配置;
功耗,为了降低采样电路的功耗,通常根据经验值将低侧电阻(R7)选择为10K;
随后便可以通过以上参数计算出分压电阻的高侧电阻:
计算所需的分压比例:即ADC参考电压:设计输入电压,通过已知参数可以计算出1.5V/30V=0.05
计算高侧电阻:即低侧电阻/分压比例,通过已知参数可以计算出10K/0.05=200K
选择标准电阻:选择一颗略高于计算值的电阻,计算值为200K,通常我们选择E24系列电阻,因此本项目中选择大于200K且最接近的220K。
如果在实际使用中,需要测量的电压低于2/3的模块设计电压,即66V,则可以根据实际情况更换分压电阻并修改程序从而提升测量的精度,下面将进行案例说明:
假设被测电压不高于24V,其他参数不变
通过计算可以得到1.5V/24V=0.0625,10K/0.0625=160K,160K为标准E24电阻可以直接选用,或适当留出冗余量选择更高阻值的180K
如果在实际使用中,需要测量的电压若高于模块99V的设计电压,可以选择更换分压电阻或通过修改基准电压来实现更大量程的电压测量范围,下面将进行案例说明:
假设被测电压为160V,选择提升电压基准的方案扩大量程
已知选用电阻的分压比例为0.0145,通过公式反推,我们可以计算出160V*0.0145=2.32V,因此我们可以选择2.5V的电压基准来实现量程的提升(扩大量程将会降低精度)
考虑到被测电源可能存在波动,在电路设计时,在低侧分压电阻上并联了10nF的滤波电容提高测量稳定性。
二极管钳位保证MCU安全
我在设计本项目的时候,额外在采样电路中增加了一个1N4148(D1等)作为钳位二极管。尽可能避免在学习和调试使用中由于接入不正确的电压,导致芯片引脚损坏。 二极管钳位是一种重要的电子电路设计技术,它的主要作用是通过限制电压的幅度来保护电路,避免信号过大或过小导致的损坏或故障。
钳位在电路中是指限制电压的意思,而二极管钳位特指利用二极管将电路中的某点电位进行限制的技术。
二极管钳位主要利用了二极管的单向导电性。当二极管的正极电压大于负极电压并且导通后,二极管两端的电压被限制在其管压降上,通常硅管的管压降约为0.7V。
钳位过程:通过二极管的钳位作用,将被钳位的电位强制拉向参考端,从而实现电位的限制。钳位并不改变原信号的波形,只是抬高或降低了信号的基准电位。
根据二极管连接方式的不同,钳位电路可分为正向钳位电路和负向钳位电路。本项目仅设计了正向钳位。
- 正向钳位电路:当二极管的正极接地时,为正向钳位电路。在正半周时,二极管截止;在负半周时,二极管导通,电容被充电至一定电压,使输出电压限制在一定范围内。
- 负向钳位电路:当二极管的负极接地时,为负向钳位电路。工作原理与正向钳位电路相反。
增加一组电压采样电路实现换挡
在本项目中,额外增加了一组电压采样电路,因此,我们可以探讨一下换挡对于提高测量精度的意义。万用表想要测的更准确,往往设置了多个档位。通过对不同档位的调整,获得被测点位在相应量程下的最佳的测量精度。
本项目实现此功能需要实现软硬件结合。当我们首先使用前文所讲的ADC_IN11通道测量30V以内电压时。若所测得电压在0~3V以内,则使用ADC_IN9通道测量。此时,由于分压比减小,测量精度大大提高。
实现换挡的思路有很多种,开发板的设计给大家提供了更多设计的可能。
用来模拟电压的测量、测量的标定、和测量校准辅助的电路
用来模拟电压的测量、测量的标定、和测量校准辅助电路
旁边标注为:T_V、T_GND的器件为开发板上的2mm香蕉座接口,用来连接万用表表笔。可插入万用表或高精度台式数字万用表的表笔探头验证开发板测量是否准确。也可插入2mm香蕉头的万用表表笔,代替CH1端口,进行手持式测量。
VP引脚为开发板供电引脚,在使用DC端口时,不接。在没有使用DC端口供电,且测量值大于5V小于30V时,可接入被测电源,也可由此独立供电。
在学习相应电路测量原理时,考虑到用户可能无法便捷的搭建测试和调试的外围电路,本着开发板易于开发的原则,特设置用来模拟电压的测量、测量的标定、和测量校准辅助电路。无需使用CH1外接电压。使用多圈可调电位器(RP1)对开发板电源电压进行分压,通过开发板内部电路连接入+V网络。此时注意,需要短接JP1,使用跳线帽即可,推荐使用长柄跳线帽。不使用此功能,请勿短接JP1。
4、电流采样电路
本项目采用低侧电流采样电路进行电流检测,采样电路的低侧与开发板表头接口共地
学习时,请不要焊接R0!!!
设计分析:
本项目设计的采样电流为3A,选择的采样电阻(R0)为100mΩ
采样选型主要需要参考以下几个方面:
- 预设计测量电流的最大值,本项目中为3A
- 检流电阻带来的压差,一般不建议超过0.5V
- 检流电阻的功耗,应当根据该参数选择合适的封装,本项目考虑到大电流时的功耗(温度)问题,选择了1W封装的金属绕线电阻
- 检流电阻上电压的放大倍数:本项目中没有使用运放搭建放大电路,因此倍率为1
随后便可以通过以上参数计算出检流的阻值选择:
- 由于本项目没有使用放大电路,因此需要选择更大的采样电阻获得更高的被测电压以便于进行测量
- 考虑到更大的电阻会带来更大的压差、更高的功耗,因此也不能无限制的选择更大的电阻
- 本项目选用了1W封装的电阻,对应的温升功率为1W
综合以上数据,本项目选择了100mΩ的检流电阻,根据公式可以计算出3A*100mΩ=300mV,900mW
如需应对不同的使用环境,尤其是电流较大的场景,可以将R0电阻更换为康铜丝或者分流器,可以更具实际使用场景,选择替代。出于安全和学习用途考虑,本项目对超出3A量程不做过多探讨,但原理一致。
串联1K电阻(R9)的作用
- 保护ADC引脚:
- 在ADC引脚前串联电阻可以起到一定的限流作用,防止在特殊情况下(如高电压输入或短路)电流过大而损坏ADC引脚。
- 减少瞬态电流对ADC转换器性能的影响。
- 滤波和降噪:
- 电阻与ADC引脚后端的电容可以构成一个RC低通滤波器,有助于减缓信号的快速边沿,降低高频噪声对ADC采样的影响。
- 在高精度应用中,这种滤波效果对于提高信噪比(SNR)和确保测量准确性至关重要。
PCBLayout注意事项
在PCB进行Layout也需要特别注意,虽然I-网络与GND网络在电气上为同一网络,但是需要注意的是I-会有大电流通过,属于“功率地”,即使该点已经接地也会因为电流的波动造成网络电平变化,因此我们可以将该网络视为一个“干扰源”;而GND网络为表头电源负极,即“信号地”,同时,由于单片机的AGND与表头GND并未进行隔离,那此时可以将表头GND视为“敏感地”,因此需要避免被干扰。在电路设计中,切勿将所有的GND笼统的连接在一起。这也是在设计该项目时,并未铺铜的原因。
在上图中,黄色箭头标注的即为大电流流通路径,通过接口的I+流入、流经采样电阻、通过接口的I-流出。此时有小伙伴可能会疑惑,为什么我设置了CN1\CN2两个电流采样接口?
电流采样是串联进被测电路的,设置两个接口是为了应对我们的调试(即开发板学习需求)。正常测量仅需接入CN1即可。当项目中需要串联进万用表等设备进行对比验证时,需要同时用CN2的1脚(红线-电流in)和CN1的2脚(黑线-电流OUT)
题外话:若觉得同时使用两个端子太麻烦,在电路设计时,请将CN2的2脚网路,由I+改为GND 。此时,正常测量仍仅需接入CN1即可,而当项目中需要串联进万用表等设备进行对比验证时,仅需要接入CN2。
用来模拟电流的测量、测量的标定、和测量校准的辅助电路
用来模拟电流的测量、测量的标定、和测量校准辅助电路
使用此功能时,请不要焊接R0采样电阻。不使用此功能,请断开JP2。
电流采样的实质,是采集采样电阻流过电流时电阻两端的电压降,即采集电压值。该电路使用RP2提供了一个在0~0.238V(5V÷210K*10K)范围内的电压值,经由I﹢网络,接入到芯片用于电流采样的引脚上。
在实际使用时,I﹢处的电压,模拟成了不焊的那个100mΩ采样电阻的电压降,此时,模拟测得的电流值I测=该电压值Vi+ ÷ 100mΩ 也正巧等于测得电压数值乘以10。即,提供了模拟出0~2.38A的电流测量。
将万用表或高精度台式数字万用表调至电压测量端口,量程3V以内。将其表笔探头,黑色负极插入电压测量端子旁的T_GND接口,红色正极表笔插入电流测量的 TI+ 端口,即可测量I﹢的实际电压值。由此可见,该电路可以除了可以完成上述设计任务,也可以直观通过测试体验到MCU的ADC外设的精度。可以自行编写程序进行验证。
5、数码管驱动
本项目采用了数码管作为显示单元。
在本项目中使用了两颗0.28寸的三位共阴数码管作为显示器件,相较于显示屏,数码管在复杂环境中拥有更好的识别度,可以根据实际使用环境的需求,改为更小的限流电阻实现更高的数码管亮度;在另一方面,数码管拥有较好的机械性能,不会像显示屏一样容易被外力损坏。在工业等有稳定可靠性应用中,多被采用。从开发版学习的角度来看,更易有目的的学习电子测量原理相关开发。
在本项目中,经过实际测试,数码管的限流电阻(R1~R6)被配置为300Ω,对应的亮度无论是红色还是蓝色数码管,均具有较好的识别度,且亮度柔和不刺眼。
严格来讲,限流电阻应该加在段上,加在位上,会影响显示效果。我们实际设计加在位上,省几个电阻,但对显示影响并不突出。所以还是加在位上,图个方便。
数码管的驱动原理
数码管的驱动原理主要涉及到通过控制数码管的各个灯段的开关状态来显示数字、字母或符号。以下是详细的驱动原理说明:
数码管的基本构成:
- 数码管通常由七段或八段LED(本项目为8段)组成,每个段代表数码管的一部分,可以显示数字0-9、字母A-F等字符。
- 数码管有共阴极和共阳极两种类型,它们的区别在于LED的公共端COM(即连接所有LED的一端)是连接到电源的负极还是正极。
驱动方式:
- 段选:通过控制数码管的各个灯段的开关状态来显示所需的数字或字符。每个灯段对应一个控制信号,当控制信号开启时,该段会显示点亮,反之则灭掉。(a、b、c、d、e、f、g、dp)
- 位选:通过控制数码管的位线来选择需要显示的数码管。位线控制是将需要显示的数码管的位线设置为高电平,其他数码管的位线设置为低电平。通过不断地切换位线的状态,可以实现多个数码管之间的显示切换。
驱动电路:
- 数码管驱动电路可以通过硬件电路实现,如使用数字信号处理器(DSP)、微控制器(MCU)或移位寄存器等集成电路来生成适合LED的控制信号。
- 这些控制信号可以是脉冲宽度调制(PWM)信号、串行数据信号等形式。通过控制这些信号的频率、宽度和幅度,可以实现数码管的亮暗控制,从而显示出所需的数字或字母。
软件控制:
- 除了硬件驱动电路,还可以通过软件控制来实现数码管的驱动。通过编程生成适合数码管的控制信号,可以实现更加灵活和复杂的显示效果,如数字的滚动显示、交替显示等。
共阴极与共阳极数码管的驱动:
- 对于共阴极数码管,共阴极引脚连接到电源的负极,控制引脚连接到控制芯片的输出引脚。当需要显示某个数字时,控制芯片会输出相应的编码信号到控制引脚,使得对应的LED段点亮。
- 对于共阳极数码管,工作原理与共阴极数码管相似,只是共阳极引脚连接到电源的正极,控制引脚连接到控制芯片的输出引脚。
编码显示:
- 为了使数码管显示出相应的数字或字符,必须使段数据口输出相应的字形编码。例如,要显示数字“0”,共阳极数码管的字型编码为11000000B(即C0H),而共阴极数码管的字型编码为00111111B(即3FH),具体编码以实际数码管为准。
动态显示与静态显示:
- 数码管可以采用静态显示或动态显示方式。静态显示时,每个数码管的8个字段分别与一个8位I/O口地址相连,I/O口只要有段码输出,相应字符即显示出来并保持不变。动态显示则是一位一位地轮流点亮各位数码管,通过快速切换实现人眼视觉上的同时显示。
总结来说,数码管的驱动原理是通过控制数码管的各个灯段的开关状态来显示数字、字母或符号,并通过段选和位选的方式实现多个数码管之间的显示切换。同时,可以通过硬件电路或软件控制来实现数码管的驱动,并根据需要选择共阴极或共阳极数码管进行驱动。
本项目实际采用动态扫描显示驱动数码管。
推算一下数码管所需电流
本项目实际采用动态扫描显示驱动数码管,因而在同一时刻,最多仅有8个段的数码管(或理解为LED)被点亮,或者说有某一位被点亮。根据设计,所需驱动电流即为IO口高电平电压3.3V÷300Ω≈11mA。
此时应注意选型的MCU是否有足够的拉电流/灌电流的能力。
由数据手册分析可知,CW32么得问题。(有些芯片是不行的)
数码管显示的限流电阻的选型
在立创商城找到所用数码管的数据手册,一般同类型数码管参数大致相近。
根据数据手册标示,限流电阻限制电流最大不超过直流顺电流 35mA,驱动电压大于2.9V(电流20mA)。 我们使用300Ω限流电阻将If做到11mA,由于CW32及CW32核心板可以使用5V系统,届时则为16mA,在保证正常显示的情况下,300Ω即可,若再小,则更亮,由于电流功耗减小,电源端LDO也可引入更低的输入电压。
6、指示灯
本项目额外设计了一个电源指示灯和IO工作指示灯。
由于芯片I/O往往灌电流的能力大于拉电流的能力,所以,LED1设计为I/O低电平有效(亮)。出于减少LED对电流的消耗的考量,放弃部分LED亮度,减少器件参数类型,将LED的限流电阻选择为10K。
以本项目所使用的插件F5白发白(白光)LED为例,下表是其电性参数,由表格参数可知,限流电阻的设置要保证电流在20mA以内。(欧姆定律算一下即可)
几个名词解释:
- 白发白LED:第一个白,即为白色或透明外壳,发白,LED发白光。此外还有 红发红 白发红等,白发红,即为白色或透明灯珠外壳的红光LED。
- F5:φ5 灯珠外壳直径D=5mm。市面上绝大多数F3/F4/F5的LED,引脚间距是一致的,均为P=100mil(2.54mm)。
从数码管和LED引申出的亮度问题思考
限流电阻和供电电压相同时,同一系列的不同颜色的LED(或数码管),亮度会有区别,这取决于不同颜色的LED对工作电压和工作电流需求的差异造成的。
7、按键电路设计
按键控制电路有多种设计方式,得益于CW32的I/O口内部可以配置上下拉电阻,在芯片外围的按键控制电路则无需配置。按键一端接入MCU的I/O上,另一端接地。按键按下,I/O被拉低。
8、用于电压测量校准的TL431电路设计
本项目额外增加了一个TL431电路用来提供一个2.5V的基准电压,可用于给芯片一个用于校准AD的外部电压基准,从产品设计角度来讲,由于CW32本身的ADC性能优势,可以不需要此电路。在开发板上设计此电路,用于学习相关应用原理。
由于我本人实际使用的是友台半导体的TL431封装TO-92,其产品手册为英文,所以找来TI公司的TL431产品的中文手册,方便大家学习理解。
TL431相关知识
TL431算是一个比较“老”的器件了,很经典,应用很广泛,现在在很多电子产品中仍然有其身影。
可能很多新手初次接触此器件,我们简单的讲讲此产品的原理,方便大家更好的应用TL431。
TI从名称上,将其定义为:精密可编程基准,我们在参考文献的第一页上,可以重点关注几个特性。
精密:精密,说明其输出电压非常准。我使用的为±0.5%精度的TL431,在室温下,板上实测2.495V。相较于常见的稳压二极管,精度天差地别。在应用电路图中,TL431内部以一个稳压管的符号做示意。 可调输出电压:可调输出电压在Vref到36V之间,我们在项目中使用输出Vref电压。Vref电压约为2.5V。所以我们在描述中用2.5V,实际是约等于的。 灌电流能力:也就是输出电压的引脚可以提供多少电流,这与在应用电路中的电阻(R13)的阻值有很大关系。不能低于1mA。如果没有灌电流的需求,则不要将电流设计过大,造成不必要的功耗影响。
TL431工作原理
理解TL431的工作原理,有益于快速理解其不同应用。
在TI的手册中找到功能方框图,我们仅需分析其等效原理图即可。
431的核心是一个运放,在电路中充当比较器。芯片内部有一个Vref电压(约为2.5V),作用在比较器的反相端。比较器的同相端会输入一个电压给REF,当这个电压大于Vref时,比较器输出高电平,使能三极管,使CATHODE(阴极)端与 ANODE(阳极)端导通,此时,若REF和CATHODE处于同一电位(连接在一起),则REF处的电位被拉低,当REF处的电位被拉低至低于Vref时,比较器输出低电平,三极管关断,REF处的电位回升,当高于Vref时,继续执行以上描述,如此循环。由于硬件的响应速度是极其快的。所以REF处的电压几乎等于Vref。
注:ANODE处的电位可以理解为GND 0V。
电阻R13的选型
在项目中,电阻R13的值 1K 是如何来的?
由于在本项目中,需要REF输出一个约2.5V的基准参考电压给MCU的ADC引脚,而MCU的ADC内部通常阻抗很大,所以对REF引脚没有灌电流需求,灌电流设置应当减小,但不可小于1mA。以1mA为例计算:
根据基尔霍夫电流定律,通过REF的电流忽略不计,则电流全部穿过TL431。
根据欧姆定律:R13=(5V-2.5V)÷1mA = 2.5KΩ
实际项目使用1k电阻,减少使用电阻的料号。将灌电流设置为:I=2.5mA
**R13还能再选小一些吗?**可以,但是不推荐,因为不必要的电流流入TL431,会造成器件发热,产生不良影响。
拓展:TL431输出电压带载
根据基尔霍夫电流定律,通过R13(以R13为例)的电流等于通过REF和431的电流之和。
由于TL431最大能承受100mA的电流,而负载的电流可能是动态变化的。所以,流经R13的电流不得超过100mA,此外为保证431正常工作,流经431的电流不得小于1mA,即TL431应用为驱动不大于99mA的负载。
实际应用的计算用初中所学欧姆定律即可。
以100mA为例,计算得出R13=(5V-2.5V)÷100mA = 25Ω 。
此时应注意电阻的功率选型,
此时功率 P=UI=2.5V * 100mA = 0.25W
所以应当选择大于0.25W功率封装的电阻,即至少选用0.5W功率封装的电阻。
拓展:TL431输出任意电压
前文提到,TL431可以输出2.5V~36V的电压,那他是怎么实现的呢?
TI在手册中给了相应的原理图作为参考:
根据公式:Vref=R2/(R1+R2) *Vout
Vref已知,R1、R2由我们在电路中设计,可以得出输出电压Vout
根据基尔霍夫电流定律,由于实际电路中流经R1的电流要大于流经R2的电流(流入REF),如需精确计算,应当把多余未计算的电流产生的电压一并算入。
基准输入电流Iref已在手册的电气特性中给出(图为示例):
拓展:电阻器选型的注意事项
除了上文所讲的一些电阻器选型问题外,还要额外就这个项目说明一下电阻器的选型细节。
本项目涉及到AD采样等,需要通过欧姆定律等公式带入电路中的电阻阻值。在实际电路中,电阻的阻值并不是就绝对等于我们原理图所标注的那样,并且, 电阻的阻值通常会随着温度的变化而变化。这种变化称为电阻的温度系数。不同类型的电阻器具有不同的温度系数。
为了便于项目调试与学习,在平衡成本与精度的考量下,本项目的全部电阻均使用1%精度电阻(百一)。由于使用的是插件的金属膜电阻,除了电流采样电阻R0外,均使用1/8W功率封装。 电阻一般精度越高,价格越贵,实际本项目一些地方的电路设计,电阻使用百五精度即可。
9、原理图及PCB的设计要点
由于,立创EDA已经有了完善的使用教程,故在本文,不再详细讲解具体操作的硬件设计,而是分享一些要点和设计思路,有些知识点也在前文有所提及,在此综合讲一下。
做好标注,规范作图
养成良好的作图习惯,电路原理图应在一定程度上表现出正确的电源工作顺序和正确的电路板上器件排布规律,如电容滤波器的排布顺序,也尽量不要出现地朝天的现象。 某位朋友所绘制电路图的一部分:
其中,关于电阻,其标注明确,不仅标注了阻值、封装,还标注了电阻的精度信息。在一些产品设计中,需要明确标注出电阻精度,并需要在BOM中做好区分,在KK级以上的大批量生产中,保证产品成本可控。
器件设计
本项目电路图中80%以上器件符号和封装是作者从0开始绘制的,从作者设计这一开发板产品的角度上来看,希望所用器件符合作者使用习惯,也便于设计出的成品更易手工焊接。
建议新手不要直接ctrl+C我的作品,嘉立创EDA提供了强大的器件库支持,可以使用其官方丰富的器件和封装库完成设计。
焊盘和补强
在PCB设计中,我对于插件焊盘,多使用长圆形焊盘,增大焊锡、烙铁与焊盘的接触面积,更易于焊接。同时,在互相接近的焊盘中,设计丝印层作为分割,在实际电路板上,由于丝印层在线路板上凸起的存在,可大大避免焊接时的连锡,在实际产品设计中多有此类方法应用,以提高生产时的良率。
对于焊盘和部分走线,利用实心填充做了补强设计,避免在使用过程中划伤线路板损坏线路,避免焊盘脱落。其实,在双层板设计上,加之嘉立创PCB板材的优良品质,可以不用进行此类设计,笔者设计初衷更多只为展示,拓宽使用者视野。在设计之初,笔者曾想考虑使用单层板完成布局布线设计,奈何经历有限,加之可能会对学员造成困扰,故而放弃。
双层板设计可以使用泪滴工具进行补强,不用像我这样麻烦。
电流采样电阻的开尔文接法
利用开尔文接法消除线路电阻和接触电阻对测量结果的影响。在高精度采样场合,也有专门的四线制采样电阻供使用,本文不做探讨。
丝印与工艺
一般PCB生产的丝印是喷上去的,所以当字体字号较小,线宽较粗时,可能会发生模糊,导致标注不清。建议合理选择丝印字体大小和线宽,不同丝印字体之间的效果也有区别。选择合适的字体,在同样线宽字号下,会有更好的打印表现。
DRC设计的注意事项
电源走线应尽可能宽,约在20~60mil之间。
普通信号线:10mil左右
ADC信号走线:10mil或8mil。太宽则在线路过长时,有可能影响信号的完整性。
由于不涉及高速电路设计,3W原则在此不做强调。
规范器件的引脚间距:如规范使用英制单位、利用好EDA软件的网格和栅格,合理排布器件位置,合理走线。
补充说明
2mm香蕉头
对应器件位号:TA,TGND,TI+,T_V,此物淘宝有售卖。
2mm香蕉头插座K2A33的尺寸规格
在电路板上放置此器件时,应注意位置,留出足够空间,因为要插万用表表笔探头,空间太紧可能会导致存在干涉,影响使用。
穿插内容:审核时发现大家的共性问题记录
- 电流采样的问题
很多同学使用铺铜,没问题,但是,焊盘链接方式(与铜箔)不要使用默认的发散,否则怎么过大电流?
三、软件设计
3.1. 软件开发概述
3.1.1. 嵌入式必备知识点
嵌入式软件开发作为计算机科学和电子工程的交叉领域,要求开发人员具备一系列的专业知识和技能。 而基于CW32的嵌入式软件开发必备知识包括以下部分:
- 编程语言:
- 熟练掌握C(C++)语言,这是嵌入式系统中最常用的编程语言,因为它们提供了直接访问硬件的能力,并且代码执行效率高。
- 了解汇编语言,用于编写底层驱动、中断处理程序以及性能要求极高的代码段。
- 对其他编程语言如Python、Java等有一定了解,以便在特定情况下使用。
- 了解CW32标准库的用法。
- 数据结构与算法:
- 熟悉各种数据结构,如数组、链表、栈、队列等,以及常用的算法,如排序、查找、递归等。
- 能够根据嵌入式系统的资源限制选择合适的数据结构和算法。
- 计算机体系结构:
- 了解处理器架构,如ARM、x86等,以及指令集和内存管理。
- 熟悉嵌入式系统的硬件组成,如微控制器、FPGA、DSP等。
- 可以很熟练的根据CW32嵌入式芯片的数据手册及用户手册,查找所需外设工作原理。
- 嵌入式操作系统(深入知识点):
- 掌握常用的嵌入式操作系统,如μC/OS、FREERTOS等,了解其内核、进程管理、内存管理、设备管理和文件系统等基本原理。
- 能够进行操作系统的任务设计、任务管理等,以满足特定应用的需求。
- 硬件接口与外设:
- 熟悉常用的硬件接口,如GPIO、串口、SPI、I2C等,并能编写相应的驱动程序。
- 了解嵌入式系统常用的硬件设备,如传感器、执行器、通信模块等,并能够与之进行交互。
- 开发工具与环境:
- 熟练使用集成开发环境(IDE),如KEIL、IAR、VISUAL STUDIO等,进行软件开发和调试。
- 掌握交叉编译器的使用,以便在开发计算机上编译出能在目标硬件上运行的程序。
- 熟悉调试器的使用,能够进行软件的断点调试、单步执行、变量查看等。
- 系统分析与设计:
- 能够进行项目需求分析,将需求转化为软件功能需求。
- 掌握软件架构设计的原则和方法,能够设计出高效、可维护的软件系统。
- 了解嵌入式系统的实时性要求,能够设计出满足实时性要求的软件系统。
- 测试与验证:
- 掌握单元测试、集成测试和系统测试的方法和技术,能够对软件进行全面的测试,确保软件功能的正确性和稳定性。
- 了解嵌入式系统的可靠性要求,能够进行软件的可靠性测试和验证。 只有全面掌握这些知识并具备持续学习的能力,才能成为一名优秀的嵌入式软件开发人员。
3.1.2 入手准备
开发环境安装(建议MDK5.33版本)、DAPLINK下载器、固件库。 固件库可以在官方网上进行下载:www.whxy.com 调试器可以选用常见的STLINK、DAPLINK、PWLINK、WCHLINK、JLINK等等,能支持CORTEX-M的即可。
3.2. 开发环境搭建
3.2.1 安装keil
最近,Keil官方推出了一则消息:Keil MDK新增了一个版本,MDK社区版(MDK_Community edition)。 该版本主要有以下几个特点:
- 可供电子爱好者、学生、学者等群体非商业免费评估和使用。
- 没有代码大小限制。
- 支持Arm Compiler 6:可为所有基于Arm Cortex-M的产品提供精简的代码和强大的性能。
- 可访问超过9500款支持基于Cortex-M处理器的微控制器器件。
- 可用于基于Arm架构的微处理器的CMSIS标准框架。
- 大量可免费使用的中间件(keil RTX5、lwIP、CMSIS-FreeRTOS等)。 MDK概述内容如下图所示。
1. Keil下载安装
keil社区版下载链接:https://www.keil.arm.com/mdk-community/ 打开上面这个链接之后会进入到登录界面,如下图所示。
已经注册过账号的可以直接登录,没有注册的需要注册一下。
2. 注册
点击右下角的Sign up进行注册。之后可以填写自己的邮箱地址,点击Send verification code去获取验证码,如图3-2-3所示。
等待一会之后邮箱会收到验证码,如图3-2-4所示。
把邮箱中收到的Verification code填入,如图3-2-5所示。
根据自己的情况把内容填写完成后点击Create,如图3-2-6所示。
3. 登录
输入注册的账号和密码,然后点击Log in进行登录,如图3-2-7所示。
登录后点击Download Keil MDK下载,如图3-2-8所示。
注意:这个网页千万不要关,因为后面会回来复制这里的PSN号,如图3-2-9所示。
4. 安装
下载完成后大家就可以进行安装了。打开刚刚下载的路径文档,如图3-2-10所示。
点击安装包右键以管理员身份运行,如图3-2-11所示。
点击下一步,如图3-2-12所示。
勾选以后继续下一步,如图3-2-13所示。
选择安装路径,可以默认,也可以安装到D盘等其他目录,但是注意一定不要有中文路径。如图3-2-14所示。
这里填写一下自己的个人信息,可随便填写,如图3-2-15所示。
等待一段时间,如图3-2-16所示。
完装好之后,点击完成,如图3-2-17所示。
点击完成之后会自动弹出下面这个界面,关闭这个窗口,如图3-2-18所示。
5. 激活
在桌面或者,win10开始位置找到MDK并以为管理员方式运行,如图3-2-19所示。
打开后,点击File下的License Management,如图3-2-20所示。
点击Get LIC via Internet按钮,如图3-2-21所示。
点击确定,如图3-2-22所示。
点击确定之后会弹出一个网页,然后填写一些自己的信息,如图3-2-23所示。
打开刚才下载的那个页面找到PSN序列号并复制到这个网页,如图3-2-24所示。
注:邮箱一定要正确因为会通过邮箱方式发送激活码,如图3-2-25所示。
然后拉到最下面,点击Submit,如图3-2-26所示。
点击后,网页就会变成此页面,表示激活成功,将发送验证码到你的邮箱,如图3-2-27所示。
接收到邮件后,打开并复制License,如图3-2-28所示。
将复制的License粘贴到Keil里面的New License ID Code(LIC)下,然后点击Add LIC,如图3-2-29所示。
激活之后如果出现日期就是激活成功,如图3-2-30所示。
如果出现下面问题,关闭重新使用管理员模式运行一下,如图3-2-31所示。
6. 注意事项
1 安装目录不能有中文。
2 运行Keil的时候用管理员模式运行。
3 开发者使用MDK,建议安装5.36版本,这个版本有AC5编译器,之后的版本不再集成,关于此问题,下面会有一些材料供参考;
7. MDK C251 C51共存
可以参考文件里的教程
通过百度网盘分享的文件:文档教程-MDK-C251-C51共存 链接:https://pan.baidu.com/s/1HXgMF2s0cz8wyeoF5WAn_g?pwd=LCKF 提取码:LCKF
3.2.2 器件包安装
前提条件
本Pack包适用于Keil V5.27及以上版本。
特别说明:请参考常见问题一览
安装pack包
安装CW32的pack包,可以从武汉芯源官网下载固件库
https://www.whxy.com/gujianku.html?keyword=f030
或直接下载下方固件库的压缩文件:
通过百度网盘分享的文件:文档教程-CW32F030固件包 链接:https://pan.baidu.com/s/1nZdiPW7E2jKwaPW_RS4hew?pwd=LCKF 提取码:LCKF
pack包在压缩包里,直接双击安装即可,如图3-2-33所示。
之后弹出这个页面,单击“Next”按钮进行安装,如图3-2-34所示。
注:其他CW32芯片包的安装方式均相同。
可能遇到的报错及解决办法
双击pack包安装时,会弹出这个报错:
原因可能是 KEIL 版本更新,对器件包格式有新的限定。 解决办法:使用低版本的KEIL MDK软件,或使用低版本的PACK包。或者直接更改文件名,从1.0.3改为1.0.0即可。 也可在CW32生态社区QQ群内向我们反馈,等待我们将PACK包更新到最新。
3.2.3 新建CW32工程
建议小白先从3.2.4开始,本篇内容略为进阶。
新建keil工程文件夹
创建一个文件夹为CW32_new_pro,注意避免出现中文路径和特殊符号
创建keil工程
打开keil,选择Project->New uVision Project,选择CW32_new_pro文件夹目录,输入项目名称,例子使用CW32_new_pro
选择芯片型号
芯片型号为CW32F030F4C8,如下:
选择CMSIS固件
复制CW32库文件和启动文件
复制CW32库文件和启动文件到CW32_new_pro
库文件路径:CW32-F030固件库\Libraries\src,
启动文件路径:CW32-F030固件库\IdeSupport\MDK
创建main.c
在keil中右键Source Group1(根据自己的group名称还选择),选择Add New Item toGroupxxx,创建main.c文件.
添加库文件和启动
在Groups,新建group取名为cw32_lib
选择cw32_lib,点击AddFiles,找到CW32_new_pro\Libraries\src目录,把所有.c选中,点击Add->Close->OK,完成添加。
添加启动文件
添加头文件路径
选择编译器
main.c文件编写
示例如下:
#include "base_types.h"
#include "cw32f0030.h"
#include "system_cw32f030.h"
int32_t main(void)
{
CW_SYSCTRL->AHBEN_f.GPIOC = 1U; // Open GPIOC Clk
CW_GPIOC->ANALOG_f.PIN0 = 0U; // Digital
CW_GPIOC->BRR_f.BRR0 = 1U; // Reset PA00
CW_GPIOC->DIR_f.PIN0 = 0U; // Output
while (1)
{
CW_GPIOC->TOG = bv0;//电平发名字卷
FirmwareDelay(100000);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
编译
点击上图所示的编译选项,会出现下图所示的错误,原因是重复定义了。
打开cw32f003_systick.c文件将164行的__weak void SysTick_Handler(void)修改为void SysTick_Handler(void)。即可解决14点的错误
再次点击编译,无错误,完成工程创建。
可以看到0个错误,0个警告,至此,工程模板创建完成。
工程模板下载
通过百度网盘分享的文件:文档教程-从头新建工程 链接:https://pan.baidu.com/s/1loVIKSNA-Ldvzgo_fJuL4Q?pwd=LCKF 提取码:LCKF
探讨编译报错原因:
CW32要用AC5编译器,用AC6编译会报错,可能是这样的错误,还有一堆的警告
KEIL从MDK5.37版本开始已经不集成AC5编译器了,想用最新版本有两个方法: 方法1: 最简单的方法是先安装5.36再安装最新版本,这样AC5可以自动集成到MDK中。 方法2: 安装最新版本后手动添加AC5编译器 下载这个文件并解压出来:
链接:https://pan.baidu.com/s/19JCNeSF4W4SKtjFrmsTELw?pwd=LCKF 提取码:LCKF
安装完MDK后把ARMCC文件夹手动放到keil安装目录的ARM文件夹中
然后在keil中手动添加,选择刚刚添加的文件夹
如果不想折腾安装AC5编译器,可以对现有工程代码修改以适应AC6的编译规则 CW32的例程直接用AC6编译的话一般会报这个错误
AC6中weak这个关键字不能被识别,改为WEAK就好了(或如上删去),关键字的不同可以看这个图:
更多的关于MDK适配AC6的内容可以下载KEIL官方的文档进行查看
https://developer.arm.com/documentation/kan298/latest/
或者看ARM的AC6介绍,里面也有说明如何从AC5转向AC6
https://developer.arm.com/documentation/100068/latest/
补充
用AC6编译有中文的代码会报-Winvalid-source-encoding的警告
可以在下方如图位置填入-Wno-invalid-source-encoding 将该警告信息屏蔽
3.2.4 使用模板新建工程
1. 拷贝模板工程和库文件
模板路径:CW32F030_StandardPeripheralLib_V2.1\Examples\Template
库文件路径:CW32F030_StandardPeripheralLib_V2.1\Libraries
启动文件路径:CW32F030_StandardPeripheralLib_V2.1\IdeSupport\MDK\startup_cw32f003.s
将CW32F030_StandardPeripheralLib_V2.1\Examples\Template下的Template文件夹拷贝出来重命名为CW32_Tem_Pro
把Libraries拷贝到CW32_Tem_Pro
把启动文件拷贝到CW32_Tem_Pro,如下:
2. 重新添加库文件和启动文件
打开CW32_Tem_Pro\MDK下的Project.uvprojx,可以看到很多文件带有黄色感叹号的标志,这些文件都是需要重新添加.
将原来的文件先删除,再重新添加。
Driver里的文件可以根据需要自行添加,例子把所有库文件都添加。
3. 修改头文件路径
模板头文件路径如下:
只需要把相对路径都改成..,前面多的..\都删除掉即可,如下:
4. 编译
5. 工程模板文件
链接:https://pan.baidu.com/s/1vRX-hHcUfR5V6HGfZwXQUQ?pwd=LCKF 提取码:LCKF
3.3. 实验一:LED灯驱动
3.3.1 LED灯驱动原理
LED 驱动指的是通过稳定的电源为 LED 提供合适的电流和电压,使其正常工作点亮。LED 驱动方式主要有恒流和恒压两种。限定电流的恒流驱动是最常见的方式,因为 LED 灯对电流敏感,电流大于其额定值可能导致损坏。恒流驱动保证了稳定的电流,从而确保了 LED 安全。
LED 灯的驱动比较简单,只需要给将对应的正负极接到单片机的正负极即可驱动。LED的接法也分有两种,灌入电流和输出电流。
- 灌入电流指的是LED的供电电流是由外部提供电流,将电流灌入我们的MCU;风险是当外部电源出现变化时,会导致MCU的引脚烧坏。
- 输出电流指的是由MCU提供电压电流,将电流输出给LED;如果使用 MCU的GPIO 直接驱动 LED,则驱动能力较弱,可能无法提供足够的电流驱动 LED。
需要注意的 是 LED 灯的颜色不同,对应的电压也不同。电流不可过大,通常需要接入220欧姆到10K欧姆左右的限流电阻,限流电阻的阻值越大,LED的亮度越暗。
3.3.2 LED灯原理图
CW32F030电压电流表中关于LED灯的原理图如图3-2所示:
3.3.3 LED灯驱动流程
LED1的正极经限流电阻 R10 接到电源正极,LED1 的负极连接到单片机的 GPIO 口上,通过 LED 灯的驱动原理,只需要将相应 GPIO(PC13)配置为低电平即可点亮LED1。CW32F030 电压电流表的 LED 部分实物图如图3-3所示:
3.3.3.1 配置流程
一般我们使用GPIO的端口,都需要有以下几个步骤。
- 开启GPIO的端口时钟
- 配置GPIO的模式
- 配置GPIO的输出
从开发板原理图了解到 LED1 接的是单片机的 PC13。我们要使能 LED 就需要配置 GPIOC 端口。下面我们就以LED1 接的 PC13 进行介绍。
3.3.3.2 开启内部时钟
CW32 的系统内部时钟默认并不是我们想要的运行频率,在使用 CW32 之前我们需要先配置内部时钟树。查找CW32F030 的用户手册可以找到系统内部时钟树如下图:
在CW32的库函数中找到cw32f030_rcc.h,头文件中有时钟树相关配置的函数,如下图:
void RCC_Configuration(void)
{
RCC_HSI_Enable(RCC_HSIOSC_DIV6);
/* 1. 设置HCLK和PCLK的分频系数 */
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
/* 2. 使能PLL,通过HSI倍频到64MHz */
RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8);
// PLL输出频率64MHz
/*< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
< 当使用的时钟源HCLK大于48M,小于等于72MHz:设置FLASH 读等待周期为3 cycle */
__RCC_FLASH_CLK_ENABLE();
FLASH_SetLatency(FLASH_Latency_3);
/* 3. 时钟切换到PLL */
RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
RCC_SystemCoreClockUpdate(64000000);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3.3.3.3 开启GPIO时钟
CW32 的外时钟默认是全部关闭,使用 GPIO 外设之前我们需要先开启对应的时钟。
在 CW32 提供的库函数中找到 cw32f030_rcc.h,这个头文件包含了所有时钟相关的函数接口。外设时钟的接口如下图所示:
LED2 的控制 IO 是 PC13,因此需要打开 GPIOC 对应的时钟,代码如下:
__RCC_GPIOC_CLK_ENABLE();//打开GPIOC的时钟
3.3.3.4 配置GPIO初始化
GPIO初始化包含了模式,中断使能,io引脚位号。CW32的GPIO初始化是先把所有初始化项写在一个结构体里面,然后把结构体传入带初始化函数里,完成初始化功能。
与GPIO相关的函数接口都在cw32f030_gpio.h头文件里面,我们先看GPIO模式有哪些,如下图所示:
GPIO模式包含了模拟输入,数字输入,数字上拉输入,数字下拉输入,数字推挽输出,数字开漏输出模式。
根据发光二极管控制原理,PC13 接在 LED 的反向端,因此 PC13 为低电平时,LED 点亮。我们需要 PC13 输出高低电平,模式需要选择数字输出模式,由于开漏模式需要外部有上拉或者下拉电阻才可输出高低电平,所以我们选择推挽模式。代码如下:
GPIO_Init_Struct.Mode = GPIO_MODE_OUTPUT_PP;
中断使能包含了:无中断,上升沿,下降沿,低电平,高电平中断使能。
因为我们需要的是GPIO输出,因此不需要中断使能,代码如下:
GPIO_Init_Struct.IT = GPIO_IT_NONE;
GPIO位号如下图所示:
LED的控制端为PC13,最后的这个13就是位号,配置代码如下:
GPIO_Init_Struct.Pins = GPIO_PIN_13;
引脚输出还需要配置GPIO的输出速度,有高低速两种速度设置,我们设置高速:
GPIO_Init_Struct.Speed = GPIO_SPEED_HIGH;
以上配置就完成了GPIO的结构体初始化,然后我们需要调用初始化函数,在cw32f030_rcc.h里找到void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);包含了两个参数,第一个参数是GPIO的基地址,第二个参数是初始化配置结构体。调用代码如下:
GPIO_Init(CW_GPIOC, &GPIO_Init_Struct);
结合以上配置,完整代码如下:
GPIO_InitTypeDef GPIO_Init_Struct;
__RCC_GPIOC_CLK_ENABLE();
GPIO_Init_Struct.IT = GPIO_IT_NONE;
GPIO_Init_Struct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init_Struct.Pins = GPIO_PIN_13;
GPIO_Init(CW_GPIOC, &GPIO_Init_Struct);
2
3
4
5
6
3.3.3.5 配置LED输出
配置好GPIO之后,就可以进行点灯了。就是让PC13输出高低电平。在cw32f030_gpio.h头文件中可以找到函数:
GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pins, GPIO_PinState PinState);
传参有三个,第一个是第一个参数是GPIO的基地址,第二个参数是引脚位号,第三个参数是输出状态。
GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET); //PC13输出高电平
GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_RESET);//PC13输出低电平
2
我们要点亮LED2根据上文的电路图可知要将PC13置低。
3.3.3.6 配置LED输出
我们使用DAP-Link作为程序下载媒介,DAP-Link的接线图如下。
3.3.4 实验效果
最终的实验效果如下图所示:
3.3.5 实验工程代码
链接:https://pan.baidu.com/s/1_Bp2aMDVo4oCAdDNNd18gw?pwd=LCKF 提取码:LCKF
3.4 实验二:按键检测
3.4.1 独立按键原理图
CW32F030电压电流表算上核心板一共有四个按键,一个复位和三个用户按键,复位作为单片机的特殊功能,不可以作为按键使用,故只有用户按键可以作为按键使用。
CW32F030电压电流表关于用户按键的原理图如图3-12所示。
3.4.2 独立按键驱动流程
通过上面的原理图可以了解到,按键的一端接到了地,另一端接到单片机的GPIO口上。通过检测GPIO引脚的电平状态,判断按键是否按下。当按键松开的时候,GPIO检测到的电平为低电平,当按键按下的时候,GPIO检测到的电平为高电平。
外部电路不含上下拉电阻,对IO而言是浮空输入,因此需要使用单片机内部的上下拉电阻;电路不含消抖电容,故编程上需要对按键进行软件消抖,在本次实验中我们使用K3 按键来控制LED1的亮灭。
3.4.2.1 配置流程
一般我们使用GPIO的输入功能,都需要有以下几个步骤。
- 开启GPIO的端口时钟
- 配置GPIO的模式
- 配置GPIO的输入
- 编写消抖函数
从开发板原理图了解到K3按键接的是单片机的PB14。我们要使能按键就需要配置GPIOB端口。下面我们就以按键连接的PB14进行介绍。
3.4.2.2 GPIO初始化
由于时钟的配置在之前的章节已有说明,故不再赘述,我们直接对端口进行初始化。初始化的代码与上文GPIO输出的配置略有不同,完整代码如下:
void KEY_Configuration(void)
{
__RCC_GPIOB_CLK_ENABLE();//打开GPIOB的时钟,PB14控制按键输入
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; //没有输入时PB14默认为高电平
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
}
void LED_Configuration(void)
{
__RCC_GPIOC_CLK_ENABLE();//打开GPIOC的时钟,PC13控制LED亮灭
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOC, &GPIO_InitStruct);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3.4.2.3 编写消抖函数
本次实验采用软件消抖,消抖函数的编写思路为:设置按键检测标志位(Flag_Key),当单片机检测到按键按下,即PB14为低电平时,将标志位置1;在后续的条件判断中如果标志位为1,则检测按键是否松开,若已松开则完成本次判断,认为按键已经按下过一次。这种方式可以不用延时判断,节约软件资源。
完整的按键检测程序如下:
uint8_t Flag_Key = 0; //按键标志位
uint8_t Flag_LED = 0; //LED显示标志位
void Key_Scan(void) //按键扫描
{
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_14) == GPIO_Pin_RESET) //检测PB14是否为低电平
{
Flag_Key = 1;
}
if(Flag_Key) //接着判断标志位
{
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_14) == GPIO_Pin_SET)//如果按键已经松开
{
Flag_Key = 0; //清零标志位,等待下一次按键检测
Flag_LED = 1 - Flag_LED; //按键按下该变LED显示标志位的值,由显示标志位控制LED
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.4.2.4 LED显示函数
在按键按下更改LED显示标志位后,需要根据显示标志位的值来控制LED灯的亮灭。
#define LED_ON GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_RESET)
#define LED_OFF GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_SET)
void LED_Ctrl(void)
{
if(Flag_LED == 1)LED_ON; //亮
else LED_OFF; //灭
}
2
3
4
5
6
7
8
最终主函数里只需要运行相应的初始化函数和上面的函数:
int main()
{
RCC_Configuration();
KEY_Configuration();
LED_Configuration();
while(1)
{
Key_Scan();
LED_Ctrl();
}
}
2
3
4
5
6
7
8
9
10
11
12
3.4.3 实验工程代码
链接:https://pan.baidu.com/s/1yZTJBB3CLjEV1fUYAFA_nQ?pwd=LCKF 提取码:LCKF
3.5 实验三:数码管显示数字
3.5.1 数码管显示原理
(来源:CSDN,原文链接:https://blog.csdn.net/qq_42189951/article/details/133347707)
数码管的显示原理是由多个发光的二极管共阴极或者共阳极组成的成“8”字形的显示器件。数码管通过不同的组合可用来显示数字0~9、字符A ~ F及小数点“.”。数码管的工作原理是通过控制外部的I/O端口进行驱动数码管的各个段码,使用不同的段码从而形成字符显示出我们要的数字。数码管实际上是由七个发光管组成8字形构成的,加上小数点就是8个。这些段分别由字母A、B、C、D、E、F、G、DP来表示。
当数码管特定的引脚加上高电平后,这些特定的发光二极管就会发亮,以形成我们眼睛看到的字样了。如:在一个共阴极数码管上显示一个“8”字,那么就对A、B、C、D、E、F、G对应的引脚置高电平。发光二极管的阳极共同连接至电源的正极称为共阳极数码管,这种类型的数码管点亮需要对引脚置低电平;发光二极管的阴极共同连接到电源的负极称为共阴极数码管,点亮共阴极数码管需要对相应的引脚置高电平。常用LED数码管显示的数字和字符是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F。
共阳极数码管的8个发光二极管的阳极(二极管正端)连接在一起。通常,公共阳极接高电平(一般接电源),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为低电平时,则该端所连接的字段导通并点亮。根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能吸收额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
共阴极数码管的8个发光二极管的阴极(二极管负端)连接在一起。通常,公共阴极接低电平(一般接地),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为高电平时,则该端所连接的字段导通并点亮,根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能提供额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
3.5.2 数码管原理图与实物图
如果数码管可以显示多位数字,如我们的电压电流表所示。那么除了控制段码来选择要显示的内容,还要选择位码来控制某一个数码管的亮灭。
数码管的原理图如下,可以看出除了上述的段码引脚之外,还有COM1、COM2、COM3的位码引脚,三个位码引脚分别控制三个数码管的亮灭情况,且低电平有效。
3.5.3 数码管驱动显示
驱动显示数码管的思路是:先将A、B、C、D、E、F、G所代表的引脚从低到高编号,列出数码要显示数字的段码值。比如要显示数字5,则段码值为0x6d,二进制表示为01101101,这说明G置1,F置1,E置0,D置1,C置1,B置0,A置1,最高位则是DP的值。将要显示的数字以段码值的方式储存在数组里以供调用,可以简化程序。接着以循环的方式结合switch语句对A、B、C、D、E、F、G的亮灭情况进行单独计算,先将段码值确定后再进行位码的选择,可以避免因单片机执行程序的时间而造成显示效果的不足。
具体程序如下,将所有与数码管显示相关的函数保存在新建的 Seg_Display.c 文件中:
/* 共阴数码管编码表:
0x3f 0x06 0x5b 0x4f 0x66 0x6d 0x7d 0x07 0x7f 0x6f
0 1 2 3 4 5 6 7 8 9
0xbf 0x86 0xdb 0xcf 0xe6 0xed 0xfd 0x87 0xff 0xef
0. 1. 2. 3. 4. 5. 6. 7. 8. 9. */
//0x3f, 0011 1111; 0x5b 0101 1011
uint8_t Seg_Table[20] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f,
0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef};
void Seg_Configuration(void) //查找原理图对数码管相关引脚进行初始化
{
__RCC_GPIOA_CLK_ENABLE(); //打开GPIOA的时钟
__RCC_GPIOB_CLK_ENABLE(); //打开GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_PIN_2 | GPIO_PIN_0 | GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_1 | GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_15;
//A:PA02;B:PA00;C:PA04;D:PA06;E:PA07;F:PA01;G:PA03;DP:PA05;
//COM1:PA08;COM2:PA11;COM3:PA12;COM4:PA15;COM5:PB03;COM6:PB04;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pins = GPIO_PIN_3 | GPIO_PIN_4; //COM5:PB03;COM6:PB04
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
}
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
void Seg_Dis(uint8_t Pos,uint8_t Num)
{
//电压电流表上有两个数码管,我们本次使用上面的管子,所以下面的数码管关上
GPIO_WritePin(CW_GPIOA,GPIO_PIN_15,GPIO_Pin_SET); //PA15,COM4
GPIO_WritePin(CW_GPIOB,GPIO_PIN_3,GPIO_Pin_SET); //PB03,COM5
GPIO_WritePin(CW_GPIOB,GPIO_PIN_4,GPIO_Pin_SET); //PB04,COM6
int i;
uint8_t Dis_Value = Seg_Table[Num];
for(i = 0; i < 8; i++)
{
switch(i)
{
case 0:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_2,(Dis_Value >> i) & 0x01); //PA02,A
break;
case 1:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_0,(Dis_Value >> i) & 0x01); //PA00,B
break;
case 2:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_4,(Dis_Value >> i) & 0x01); //PA04,C
break;
case 3:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_6,(Dis_Value >> i) & 0x01); //PA06,D
break;
case 4:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_7,(Dis_Value >> i) & 0x01); //PA07,E
break;
case 5:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_1,(Dis_Value >> i) & 0x01); //PA01,F
break;
case 6:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_3,(Dis_Value >> i) & 0x01); //PA03,G
break;
case 7:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_5,(Dis_Value >> i) & 0x01); //PA05,DP
break;
default:
break;
}
}
switch(Pos)
{
case 1:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_8,GPIO_Pin_RESET); //PA08,COM1
GPIO_WritePin(CW_GPIOA,GPIO_PIN_11,GPIO_Pin_SET); //PA11,COM2
GPIO_WritePin(CW_GPIOA,GPIO_PIN_12,GPIO_Pin_SET); //PA12,COM3
break;
case 2:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_8,GPIO_Pin_SET); //PA08,COM1
GPIO_WritePin(CW_GPIOA,GPIO_PIN_11,GPIO_Pin_RESET); //PA11,COM2
GPIO_WritePin(CW_GPIOA,GPIO_PIN_12,GPIO_Pin_SET); //PA12,COM3
break;
case 3:
GPIO_WritePin(CW_GPIOA,GPIO_PIN_8,GPIO_Pin_SET); //PA08,COM1
GPIO_WritePin(CW_GPIOA,GPIO_PIN_11,GPIO_Pin_SET); //PA11,COM2
GPIO_WritePin(CW_GPIOA,GPIO_PIN_12,GPIO_Pin_RESET); //PA12,COM3
break;
default:
break;
}
}
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
在主函数里调用Seg_Dis函数即可在对应位置显示相应数字(别忘了初始化),各位学员熟练之后可以通过define定义每个引脚的写入,使代码更加简洁美观。
程序下载接线如图3-16所示:
3.5.4 实验效果
最终的实验效果如下图所示:
3.5.5 实验工程代码
链接:https://pan.baidu.com/s/1y-mlOgOIGsEFkSR_bjhCfQ?pwd=LCKF 提取码:LCKF
3.6 实验四:数码管动态显示
3.6.1 数码管动态显示原理
所谓动态扫描显示即轮流向各位数码管送出段码和位码,利用发光管的余辉和人眼视觉暂留作用,使人眼的感觉好像各位数码管同时都在显示。明确了原理,我们要使电压电流表的三个位同时显示不同的值需要用到CW32的定时器功能,在定时器的中断服务程序里面执行显示刷新的动作。有关CW32的定时器和中断的相关知识请查看链接:【CW32F003E4核心板】入门学习教程。本文只讲述如何配置定时器中断并执行数码管刷新函数。
3.6.2 定时器中断配置
在配置定时器之前需要注意是否将系统时钟树配置完成,定时器利用了高级外设时钟 PCLK,在之前的时钟配置中,我们将高级外设时钟 PCLK的频率配置成了8MHz,这将影响我们对定时器的预分频和装填计数值的配置。
在本次实验中,我们使用定时器BTIM1进行中断配置,代码如下:
void Btim1_Configuration(void)
{
BTIM_TimeBaseInitTypeDef BTIM_TimeBaseInitStruct;
__RCC_BTIM_CLK_ENABLE();
/* NVIC Configuration */
NVIC_EnableIRQ(BTIM1_IRQn); //配置BTIM1中断
BTIM_TimeBaseInitStruct.BTIM_Mode = BTIM_Mode_TIMER;
BTIM_TimeBaseInitStruct.BTIM_Period = 8000 - 1; // 1ms触发1次
BTIM_TimeBaseInitStruct.BTIM_Prescaler = BTIM_PRS_DIV1; // 8MHZ
BTIM_TimeBaseInitStruct.BTIM_OPMode = BTIM_OPMode_Repetitive;
BTIM_TimeBaseInit(CW_BTIM1, &BTIM_TimeBaseInitStruct);
BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);
BTIM_Cmd(CW_BTIM1, ENABLE);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.6.3 数码管动态显示
在上一节的数码管显示数字的数码管显示模块Seg_Display.c文件中,我们还需要添加三个函数和定义一个数组 Seg_Reg 来完成动态显示的功能。
uint8_t Seg_Reg[3] = {0,0,0}; //这个数组存放数码管显示三位的数字,0~9
/**
* @brief 关闭所有公共端
*
*/
void Close_Com(void)
{
GPIO_WritePin(CW_GPIOA,GPIO_PIN_8,GPIO_Pin_SET); //COM1:PA08
GPIO_WritePin(CW_GPIOA,GPIO_PIN_11,GPIO_Pin_SET); //COM2:PA11
GPIO_WritePin(CW_GPIOA,GPIO_PIN_12,GPIO_Pin_SET); //COM3:PA12
GPIO_WritePin(CW_GPIOA,GPIO_PIN_15,GPIO_Pin_SET); //COM4:PA15
GPIO_WritePin(CW_GPIOB,GPIO_PIN_3,GPIO_Pin_SET); //COM5:PB03
GPIO_WritePin(CW_GPIOB,GPIO_PIN_4,GPIO_Pin_SET); //COM6:PB04
}
/**
* @brief 计算储存要显示的数字在Seg_Reg中
*
*/
void Dynamic_Display(uint32_t value)
{
uint8_t Hundreds;
uint8_t Tens;
uint8_t Units; // 个位数
Units = value % 10;
Tens = value / 10 % 10;
Hundreds = value / 100 % 10;
Seg_Reg[0] = Hundreds;
Seg_Reg[1] = Tens;
Seg_Reg[2] = Units;
}
/**
* @brief 数码管扫描显示函数,定时器周期性调用
*
*/
void Display_Refresh(void)
{
static uint8_t num;
Close_Com();//先关闭公共端,防止重影
Seg_Dis(num,Seg_Reg[num]);
num++;
if(num > 2)
{
num = 0;
}
}
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
最后在定时器中断里不断调用显示刷新函数完成数码管的刷新显示:
/**
* @brief This funcation handles BTIM1
*/
void BTIM1_IRQHandler(void)
{
static uint32_t Cnt;
/* USER CODE BEGIN */
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Cnt++;
if (Cnt > 2)
{
//3ms显示一个数码管
Cnt = 0;
Display_Refresh();//数码管扫描显示
}
}
/* USER CODE END */
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3.6.4 实验效果
最终的实验效果如下图所示,人眼已经看不出闪烁,但实际上数码管是依次刷新显示。
3.6.5 实验工程代码
链接:https://pan.baidu.com/s/1qvaiHRyza_Pw9WhpBqSewQ?pwd=LCKF 提取码:LCKF
3.7 实验五:ADC采样及显示
3.7.1 什么是ADC
模拟数字转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。
3.7.2 CW32的ADC介绍
CW32F030 内部集成一个 12 位精度、最高 1M SPS 转换速度的逐次逼近型模数转换器 (SAR ADC),最多可将 16 路模拟信号转换为数字信号。现实世界中的绝大多数信号都是模拟量,如光、电、声、图像信号等,都要由 ADC 转换成数字信号,才能由 MCU 进行数字化处理。
主要特性
- 12 位精度
可编程转换速度,最高达 1M SPS
16 路输入转换通道
- 13 路外部引脚输入
- 内置温度传感器
- 内置 BGR 1.2V 基准
- 1/3 VDD 电源电压
4 路参考电压源(Vref)
- VDD 电源电压
- ExRef(PB04)引脚电压
- 内置 1.5V 参考电压
- 内置 2.5V 参考电压
采样电压输入范围:0 ~ Vref
多种转换模式,全部支持转换累加功能
- 单次转换
- 多次转换
- 连续转换
- 序列扫描转换
- 序列断续转换
支持单通道、序列通道两种通道选择,最大同时支持 4 个序列
支持输入通道电压阈值监测
内置信号跟随器,可转换高阻抗输入信号
支持片内外设自动触发 ADC 转换
3.7.3 ADC基本参数
分辨率: 表示ADC转换器的输出精度,通常以位数(bit)表示,比如8位、10位、12位等,位数越高,精度越高。
采样率: 表示ADC对模拟输入信号进行采样的速率,通常以每秒采样次数(samples per second,SPS)表示,也称为转换速率,表示ADC能够进行多少次模拟到数字的转换。
采样范围: 指ADC可以采集到的模拟输入信号的电压范围,范围见下:
Vref 为参考电压,CW32F030 有四路电压参考源见上文。
3.7.4 基本原理
CW32F030采用的是逐次逼近型的12位ADC,逐次逼近型ADC是一种常见的ADC工作原理,它的思想是通过比较模拟信号与参考电压之间的大小关系来逐步逼近输入信号的数字表示。在逐次逼近型ADC中,输入信号和参考电压被加入一个差分放大器中,产生一个差分电压。然后,这个差分电压被输入到一个逐步逼近的数字量化器中,该量化器以逐步递减的方式将其与一系列参考电压进行比较。具体来说,在每个逼近阶段,量化器将输入信号与一个中间电压点进行比较,将该电压点上方或下方的参考电压作为下一个逼近阶段的参考电压。这个过程一直持续到量化器逼近到最终的数字输出值为止。
以一个采样电路原理图举例:
如果使用1.5V作为参考电压,根据R8和R7的阻值配比可以得到最高采样电压为:
电流采样的电路原理图见图3-20,对电流采样本质上是对检流电阻的电压进行采样。
3.7.5 ADC采样显示
在下面我们对CW32F030的ADC通道进行配置,输入电压给电压表,CW32将采样得到的值输入数码管显示,对ADC通道的配置代码如下:
void ADC_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure; //ADC配置结构体
ADC_SerialChTypeDef ADC_SerialChStructure; //ADC序列通道结构体
GPIO_InitTypeDef GPIO_Init_Struct;
__RCC_GPIOB_CLK_ENABLE(); //打开ADC对应引脚时钟
__RCC_ADC_CLK_ENABLE(); // 打开ADC时钟
GPIO_Init_Struct.IT = GPIO_IT_NONE;
GPIO_Init_Struct.Mode = GPIO_MODE_ANALOG;//将GPIO的模式配置成模拟功能
GPIO_Init_Struct.Pins = GPIO_PIN_10; // PB10是电压采集引脚
GPIO_Init(CW_GPIOB, &GPIO_Init_Struct);
PB10_ANALOG_ENABLE(); //使能模拟引脚
ADC_StructInit(&ADC_InitStructure); // ADC默认值初始化
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div4; //ADC工作时钟配置 PCLK/4 = 6/4 = 1.5Mhz
/*信号电压较低时,可以降低参考电压来提高分辨率。 改变参考电压后,同样二进制表示的电压值就会不一样,
最大的二进制(全1)表示的就是你的参考电压,在计算实际电压时,就需要将参考电压考虑进去。*/
ADC_InitStructure.ADC_VrefSel = ADC_Vref_BGR1p5; //参考电压设置为1.5V
ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; //由于电压信号为慢速信号,ADC采样时间为十个ADC采样周期以确保准确
ADC_SerialChStructure.ADC_SqrEns = ADC_SqrEns0; //Sqr为序列配置寄存器,这里只用到了序列0的通道,所以配置成0表示只转换Sqr0序列
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh11; //配置ADC序列,PB10是ADC的第11通道
ADC_SerialChStructure.ADC_InitStruct = ADC_InitStructure; //ADC初始化
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure); //ADC序列连续转换模式配置
ADC_ClearITPendingAll(); //清除ADC所有中断状态
ADC_Enable(); // ADC使能
ADC_SoftwareStartConvCmd(ENABLE); //ADC转换软件启动命令
}
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
在主函数中初始化ADC后在BTIM1的中断服务程序中调用 ADC_GetSqr0Result(&Volt_Buffer); 得到ADC采样的值,再在主函数的 while 循环中调用数码管显示函数 Dynamic_Display 将ADC采样值显示到数码管上。这时显示的是 ADC 采样的原始值,原始值与实际值的计算公式与参考电压选定的大小和原理图中的电阻分压比有关,以原理图中的 0-30V 的挡位举例:
其中 4096 代表 CW32 的 ADC 采样精度12位为2^12=4096,1.5 为 ADC 配置时设置的 1.5V 参考电压,220和 10 分别表示采样电阻中的 220 kΩ 和 10 kΩ 电阻的大小。
得到 ADC 的原始采样值后,要正确显示在数码管上还需要对其进行进一步的处理。由于实际值的大小为 0-30V,而数码管有三位,所以我们在实际值低于 10V 时显示 x.xx V;在实际值大于 10V 时显示 xx.x V。再结合上文的 Seg_Reg 数组只存储三位的整形数,我们将实际值扩大一百倍用于方便显示,修改后的Dynamic_Display函数如下:
/* 共阴数码管编码表:
0x3f 0x06 0x5b 0x4f 0x66 0x6d 0x7d 0x07 0x7f 0x6f
0 1 2 3 4 5 6 7 8 9
0xbf 0x86 0xdb 0xcf 0xe6 0xed 0xfd 0x87 0xff 0xef
0. 1. 2. 3. 4. 5. 6. 7. 8. 9. */
uint8_t Seg_Table[20] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f,
0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef};
/**
* @brief 计算储存要显示的数字在Seg_Reg中
*
*/
void Dynamic_Display(uint32_t value)
{
uint8_t Thousands = 0;
uint8_t Hundreds = 0;
uint8_t Tens = 0;
uint8_t Units = 0; // 个位数
Thousands = value / 1000;
if(Thousands > 0)
{
Units = value % 10;
value = Units > 5 ? (value + 10) : value; // 根据后一位四舍五入
Thousands = value / 1000 % 10;
Hundreds = value / 100 % 10;
Tens = value / 10 % 10;
// 显示xx.x伏
Seg_Reg[0] = Thousands;
Seg_Reg[1] = Hundreds + 10; // 加dp显示
Seg_Reg[2] = Tens;
}
else
{
Units = value % 10;
Tens = value / 10 % 10;
Hundreds = value / 100 % 10;
// 显示x.xx伏
Seg_Reg[0] = Hundreds + 10; // 加dp显示
Seg_Reg[1] = Tens;
Seg_Reg[2] = Units;
}
}
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
在 while 循环中调用数码管显示函数 Dynamic_Display 之前先计算 ADC采样值,将其转化为扩大一百倍后的实际值。
int main()
{
RCC_Configuration();
Seg_Configuration();
Btim1_Configuration();
ADC_Configuration();
while(1)
{
Cal_Buffer = (Volt_Buffer * ADC_REF_VALUE >> 12) * (R2 + R1)/R1;
//R2:220;R1:10;ADC_REF_VALUE:1500; >> 12等效于除以4096
if(Cal_Buffer % 10 >= 5)Cal_Buffer = Cal_Buffer / 10 + 1; // 四舍五入
else Cal_Buffer = Cal_Buffer / 10; //此时的值为标准值的100倍
Dynamic_Display(Cal_Buffer);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
再附上 BTIM1 的中断服务程序
/**
* @brief This funcation handles BTIM1
*/
void BTIM1_IRQHandler(void)
{
static uint32_t Cnt;
/* USER CODE BEGIN */
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
ADC_GetSqr0Result(&Volt_Buffer);
Cnt++;
if (Cnt > 2)
{
//3ms显示一个数码管
Cnt = 0;
Display_Refresh();//数码管扫描显示
}
}
/* USER CODE END */
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
调节左侧电位器可以更改内部的采样电压值作为测试参考,最终的结果展示如下:
由于此时的电压电流表还未校准,由于系统误差等存在,所以显示的数据还存在一定的偏差,校准与标定的调试教程见后文。
3.7.6 实验工程代码
链接:https://pan.baidu.com/s/12eB_02H6dI1J7pwZ_0llhg?pwd=LCKF 提取码:LCKF
3.8 实验六:均值滤波
3.8.1 常见的滤波算法
在嵌入式软件开发中,常用的软件滤波信号算法多种多样,每种算法都有其特定的应用场景和优缺点。以下是一些常见的软件滤波算法:
- 限幅滤波法(又称程序判断滤波法)
- 方法:根据经验判断,确定两次采样允许的最大偏差值(设为A)。每次检测到新值时判断,如果本次值与上次值之差小于等于A,则本次值有效;如果大于A,则本次值无效,放弃本次值,用上次值代替本次值。
- 优点:能有效克服因偶然因素引起的脉冲干扰。
- 缺点:无法抑制周期性干扰,平滑度差。
- 中位值滤波法
- 方法:连续采样N次(N取奇数),把N次采样值按大小排列,取中间值为本次有效值。
- 优点:能有效克服因偶然因素引起的波动干扰,对温度、液位等变化缓慢的被测参数有良好的滤波效果。
- 缺点:对流量、速度等快速变化的参数不宜。
- 算术平均滤波法
- 方法:连续取N个采样值进行算术平均运算。N值较大时,信号平滑度较高但灵敏度较低;N值较小时,信号平滑度较低但灵敏度较高。
- 优点:适用于对一般具有随机干扰的信号进行滤波。
- 缺点:对于测量速度较慢或要求数据计算速度较快的实时控制不适用,且比较浪费RAM。
- 递推平均滤波法(又称滑动平均滤波法)
- 方法:把连续取N个采样值看成一个队列,队列的长度固定为N。每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则)。把队列中的N个数据进行算术平均运算,即可获得新的滤波结果。
- 优点:对周期性干扰有良好的抑制作用,平滑度高,适用于高频振荡的系统。
- 缺点:灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差,不适用于脉冲干扰比较严重的场合,且比较浪费RAM。
- 中位值平均滤波法(又称防脉冲干扰平均滤波法)
- 方法:相当于“中位值滤波法”+“算术平均滤波法”。连续采样N个数据,去掉一个最大值和一个最小值,然后计算N-2个数据的算术平均值。
- 优点:融合了两种滤波法的优点,对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
- 缺点:测量速度较慢,比较浪费RAM。
- 限幅平均滤波法
- 方法:相当于“限幅滤波法”+“递推平均滤波法”。每次采样到的新数据先进行限幅处理,再送入队列进行递推平均滤波处理。
- 优点:融合了两种滤波法的优点,对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
- 缺点:比较浪费RAM。
- 一阶滞后滤波法
- 方法:取a=0~1,本次滤波结果=(1-a)本次采样值+a上次滤波结果。
- 优点:对周期性干扰具有良好的抑制作用,适用于波动频率较高的场合。
- 缺点:相位滞后,灵敏度低,滞后程度取决于a值大小,不能消除滤波频率高于采样频率的1/2的干扰信号。
- 加权递推平均滤波法
- 方法:是对递推平均滤波法的改进,即不同时刻的数据加以不同的权。通常是越接近现时刻的数据,权取得越大。
- 优点:适用于有较大纯滞后时间常数的对象和采样周期较短的系统。
- 缺点:对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号,不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
- 消抖滤波法
- 方法:设置一个滤波计数器,将每次采样值与当前有效值比较。如果采样值等于当前有效值,则计数器清零;如果不等,则计数器加1,并判断计数器是否大于等于上限N(溢出)。如果计数器溢出,则将本次值替换当前有效值,并清计数器。
- 优点:对于变化缓慢的被测参数有较好的滤波效果,可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动。
- 缺点:对于快速变化的参数不宜,如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统。
- 其他滤波方法
除了上述常见的滤波方法外,还有如移动平均滤波、卡尔曼滤波等算法。
3.8.2 均值滤波
均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=∑f(x,y)/m,m为该模板中包含当前像素在内的像素总个数。
这本是数字图像处理的一种方法,但也可以用在我们数字电压电流表的ADC采样数据上。我们选取二十次的ADC采样值存储在数组 Volt_Buffer 中,然后去除掉数组中的最大值和最小值后再取平均,得到的值作为结果显示在数码管上,这样可以较大程度获得准确的、不易波动的数据。
程序在实验五的基础上略作修改即可,首先是增加和修改变量:
#define ADC_SAMPLE_SIZE (20) //选取20个值来计算均值
uint16_t Volt_Buffer[ADC_SAMPLE_SIZE]; //存储ADC采样值
uint16_t Cal_Buffer = 0; //计算电压时的中间变量
uint32_t Led_Dis_Time; //运行函数的时间计数,300ms运行一次
2
3
4
5
接下来是 ADC 的采样函数,该函数在 BTIM1 的定时器中断服务程序中执行,该中断服务程序 1ms 执行一次,函数运行效果为 1ms 获得一次 ADC 的采样原始值储存进 Volt_Buffer[ADC_SAMPLE_SIZE] 数组中。
void Get_ADC_Value(void)
{
static uint8_t adc_cnt; //定义静态局部变量
ADC_GetSqr0Result(&Volt_Buffer[adc_cnt]); //取得 ADC 的原始采样值
adc_cnt++;
if(adc_cnt >= ADC_SAMPLE_SIZE) //采样 20 次后从 0 开始继续
{
adc_cnt = 0;
}
}
2
3
4
5
6
7
8
9
10
void BTIM1_IRQHandler(void)
{
static uint32_t Cnt;
/* USER CODE BEGIN */
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Get_ADC_Value(); //运行采样函数
ADC_GetSqr0Result(&Volt_Buffer);
Cnt++;
if (Cnt > 2)
{
//3ms显示一个数码管
Cnt = 0;
Display_Refresh();//数码管扫描显示
}
}
/* USER CODE END */
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
接下来是均值滤波的主体函数:
uint32_t Mean_Value_Filter(uint16_t *value, uint32_t size) //均值滤波
{
uint32_t sum = 0;
uint16_t max = 0;
uint16_t min = 0xffff;
int i;
for(i = 0; i < size; i++) //遍历数组找到最大值和最小值
{
sum += value[i];
if(value[i] > max)
{
max = value[i];
}
if(value[i] < min)
{
min = value[i];
}
}
sum -= max + min; //减去最大和最小值后求平均
sum = sum / (size - 2);
return sum;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在主函数中重新计算采样和显示值,并且让该计算 300ms 运行一次,以防数码管频繁改变显示值使得人眼无法分辨其显示的数字。
int main()
{
RCC_Configuration(); //时钟初始化
Seg_Configuration(); //数码管初始化
Btim1_Configuration();//BTIM1初始化
ADC_Configuration(); //ADC初始化
while(1)
{
if(GetTick() >= (Led_Dis_Time + 300)) //300ms改变一次数码管显示值
{
Led_Dis_Time = GetTick();
Cal_Buffer = Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);
Cal_Buffer = (Cal_Buffer * ADC_REF_VALUE >> 12) * (R2 + R1)/R1;
if(Cal_Buffer % 10 >= 5)Cal_Buffer = Cal_Buffer / 10 + 1; // 四舍五入
else Cal_Buffer = Cal_Buffer / 10; //此时的值为标准值的100倍
Dynamic_Display(Cal_Buffer);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
需要注意的是,调用 GetTick() 得到系统计数值需要在时钟初始化处加上初始化函数,否则无法取得计数值。
InitTick(64000000); // SYSTICK 的工作频率为64MHz,每ms中断一次
最终的结果展示如下,与之前的结果相比,数码管显示的值更加稳定,滤除了干扰对 MCU 工作的影响。
3.8.3 实验工程代码
链接:https://pan.baidu.com/s/1EZYiXBcDvlZBYspcixOB2Q?pwd=LCKF 提取码:LCKF
3.9 实验七:六位数显的电流通道采集电压显示
3.9.1 实验目的
简单来说就是用两个数码管显示电压,这个电压是通过电流通道采集而来。
当我们需要检测MCU AD采样精度时,往往从MCU AD引脚上输入一个确定的可调电压,再通过编程对引脚电压进行采集计算,与实际进行比较,可以观察ADC转换的性能。 在开发板中,我们可以通过电流通道采集电压进行显示,来判断AD转换性能。
3.9.2 实验分析
电流采样电路如下:
电流采样通过ADC_IN12通道完成。
我们可以使用PR2电位器调节模拟I+输入的电压值。该电压值的实际值,可使用万用表测试TI+端与GND端的电压值可以得到。数码管可以显示MCU测量计算的电压值。实际值与测理值做比较即分析Ad转换处理的配置或性能是否需要改进。
注意:该实验中,R0不焊。JP2跳线短接。
使用RP2提供的电压范围为:在0~0.238V(5V÷210K*10K)范围内的电压值(理想值,实际可能由电阻精度决定),经由I﹢网络,接入到芯片用于电流采样的引脚上。
3.9.3 重要代码讲解
工程代码文件如下:
链接:https://pan.baidu.com/s/1uKXCaQhElJfKU3FJsjP23Q?pwd=LCKF 提取码:LCKF
其中:ADC初始化代码如下。该代码主要完成电流通道ADCH_IN12的初始化配置。配置为内部1.5V为参考。
void ADC_init(void)
{
ADC_InitTypeDef ADC_InitStructure; //ADC配置结构体
ADC_SerialChTypeDef ADC_SerialChStructure; //ADC序列通道结构体
GPIO_InitTypeDef GPIO_Init_Struct;
__RCC_GPIOB_CLK_ENABLE(); //打开ADC对应引脚时钟 __RCC_ADC_CLK_ENABLE(); // 打开ADC时钟
PB01_ANALOG_ENABLE(); //使能模拟引脚
PB00_ANALOG_ENABLE(); //使能模拟引脚
PB10_ANALOG_ENABLE(); //使能模拟引脚
PB11_ANALOG_ENABLE();
ADC_StructInit(&ADC_InitStructure); // ADC默认值初始化
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div128; //ADC工作时钟配置 PCLK/4 = 6/4 = 1.5Mhz
/*信号电压较低时,可以降低参考电压来提高分辨率。 改变参考电压后,同样二进制表示的电压值就会不一样,
最大的二进制(全1)表示的就是你的参考电压,在计算实际电压时,就需要将参考电压考虑进去。*/
ADC_InitStructure.ADC_VrefSel = ADC_Vref_BGR1p5; //参考电压设置为1.5V
ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; //由于电压信号为慢速信号,ADC采样时间为十个ADC采样周期以确保准确
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh12; //配置ADC序列,PB01是ADC的第9通道
ADC_SerialChStructure.ADC_SqrEns = ADC_SqrEns0;
ADC_SerialChStructure.ADC_InitStruct = ADC_InitStructure; //ADC初始化
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure); //ADC序列连续转换模式配置
ADC_ClearITPendingAll(); //清除ADC所有中断状态
ADC_Enable(); // ADC使能
ADC_SoftwareStartConvCmd(ENABLE); //ADC转换软件启动命令
}
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
其中,ADC电压计算如下。其中,Volt_Buffer为采集到的电压通道代码值,或电压计算值,电压0.1MV为单位。
void Volt_Cal(void)
{
float t,KT1;
V_Buffer = Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);//使用均值滤波
V_Buffer = V_Buffer * ADC_REF_VALUE * 10>> 12; //0.1mV为单位 Ad精度应为1*4096*15000 即0.3mV 一个代码偏差0.3mV
}
2
3
4
5
6
工程运行效果图如下。显示数据表示进入引脚电压为0.1977V。
3.10 实验八:压流同采显示
电压电流同时采集显示
3.10.1 电流采样电路分析与计算
在实验七基础上,增加电压电流同时显示。
本项目设计的采样电流为 3A,选择的采样电阻为 100mΩ。如上图所示。可以将R0取样电阻焊为100毫欧。在I+端接入被波信号。
电流计算方法为(A 为单位):ADC_IN12通道 电压/0.1 欧。
同样可以使用1.5V为参考电压,以提高测量精度。
在本实验,可以断开JP2跳线,同时焊接R0为100mΩ。
电压电流分别通过两个ADC通道进行采集转换。数码管为六位显示,其中上一行三位可以显示电压值,下一行三位可以显示电流值。
在程序中可以使用序列连续采集模式。
3.10.2 重要代码讲解
工程代码文件如下:
链接:https://pan.baidu.com/s/1ExWP4fKwc6O8zjcyVymRVA?pwd=LCKF 提取码:LCKF
其中:ADC初始化代码如下。该代码主要完成电流通道ADCH_IN11、ADCH_IN12的初始化配置。配置为内部1.5V为参考,采用续列采样模式。
void ADC_init(void)
{
ADC_InitTypeDef ADC_InitStructure; //ADC配置结构体
ADC_SerialChTypeDef ADC_SerialChStructure; //ADC序列通道结构体
GPIO_InitTypeDef GPIO_Init_Struct;
__RCC_GPIOB_CLK_ENABLE(); //打开ADC对应引脚时钟 __RCC_ADC_CLK_ENABLE(); // 打开ADC时钟
PB01_ANALOG_ENABLE(); //使能模拟引脚
PB00_ANALOG_ENABLE(); //使能模拟引脚
PB10_ANALOG_ENABLE(); //使能模拟引脚
PB11_ANALOG_ENABLE();
ADC_StructInit(&ADC_InitStructure); // ADC默认值初始化
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div128; //ADC工作时钟配置 PCLK/4 = 6/4 = 1.5Mhz
/*信号电压较低时,可以降低参考电压来提高分辨率。 改变参考电压后,同样二进制表示的电压值就会不一样,
最大的二进制(全1)表示的就是你的参考电压,在计算实际电压时,就需要将参考电压考虑进去。*/
ADC_InitStructure.ADC_VrefSel = ADC_Vref_BGR1p5; //参考电压设置为1.5V
ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; //由于电压信号为慢速信号,ADC采样时间为十个ADC采样周期以确保准确
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh11; //配置ADC序列,PB01是ADC的第9通道
ADC_SerialChStructure.ADC_Sqr1Chmux = ADC_SqrCh12;
ADC_SerialChStructure.ADC_SqrEns = ADC_SqrEns01;
ADC_SerialChStructure.ADC_InitStruct = ADC_InitStructure; //ADC初始化
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure); //ADC序列连续转换模式配置
ADC_ClearITPendingAll(); //清除ADC所有中断状态
ADC_Enable(); // ADC使能
ADC_SoftwareStartConvCmd(ENABLE); //ADC转换软件启动命令
}
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
其中,ADC电压电流计算如下。其中,Volt_Buffer为采集到的电压通道代码值,或电压计算值,电压10MV为单位。电流通过通过0.1R电阻换算电流。
void Volt_Cal(void)
{
float t,KT1;
V_Buffer = Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);//使用均值滤波
I_Buffer = Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE); //使用均值滤波
V_Buffer = (V_Buffer * ADC_REF_VALUE >> 12) * (R2 + R1)/R1;
// 四舍五入
if(V_Buffer % 10 >= 5)
{
V_Buffer = V_Buffer / 10 + 1;
}
else
{
V_Buffer = V_Buffer / 10;
}
ADC_GetSqr1Result(&I_Buffer); ///当前采集值显示出来
I_Buffer=I_Buffer * ADC_REF_VALUE >> 12;
/**mv =I_Buffer * ADC_REF_VALUE >> 12,
R = 100mr,
10ma = mv/R/10=mv/0.1/10 = mv
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
主程序如下:
int main()
{
RCC_Configuration(); //系统时钟64M
KEYGPIO_Init();
GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_RESET);
Seg_Init();
Btim1_Init();
ADC_init();
while(1)
{
if(BrushFlag==1)
{
Display(V_Buffer);
DisplayI(I_Buffer);
BrushFlag=0;
}
if(timecount>= 300) //300ms改变一次数码管显示值//
{
timecount=0;
Volt_Cal();
BrushFlag=1;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
工程运行效果图如下。显示数据表示进入电压被测端电压为0.78V,电流为0.41A。
3.11 实验九:带有标定功能的数字电压电流表
3.11.1 标定的概念
标定是通过测量标准器的偏差来补偿仪器系统误差,从而改善仪器或系统准确度、精度的操作。为了提高电压电流表在测量时的测量精度和准确度,需要对电压电流进行标定校准。
常见的标定原理如下:
假设一个采样系统,AD部分可以得到数字量,对应的物理量为电压(或电流);
- 若在“零点”标定一个AD值点Xmin,在“最大处”标定一个AD值点Xmax,根据“两点成一条直线”的原理,可以得到一条由零点和最大点连起来的一条直线,这条直线的斜率k很容易求得,然后套如直线方程求解每一个点X(AD采样值),可以得到该AD值对应的物理量(电压值):
上图中的斜率k:
(因为第一点为“零点”,故上面的Ymin = 0)
所以,上图中任一点的AD值对应的物理量:
- 上面的算法只是在“零点”和“最大点”之间做了标定,如果使用中间的AD采样值会带来很大的对应物理量的误差,解决的办法是多插入一些标定点。
如下图,分别插入了标定点(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4) 四个点:
这样将获得不再是一条直线,而是一条“折现”(相当于分段处理),若欲求解落在x1和x2之间一点Xad值对应的电压值:
由上看出,中间插入的“标定点”越多,得到物理值“精度”越高。
在电压电流表测量可以使用“电压电流标定板”“万用表”等配合适合,对采集的电压电流进行标定处理。标定点越多,测量越精确。
3.11.2 重要代码讲解
工程代码文件如下:
链接:https://pan.baidu.com/s/12OWqnW4XG8EGxlk9wCQoqw?pwd=LCKF 提取码:LCKF
参考例程中,使用了3点标定。其中,电压标定点为0V、5V、15V。电流标定点为0A、0.5A、1.5A。
主程序代码如下:
int main()
{
RCC_Configuration(); //系统时钟64M
KEYGPIO_Init();
GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_RESET);
Seg_Init();
Btim1_Init();
ADC_init();
read_vol_cur_calibration();
ComputeK();
while(1)
{
if(BrushFlag==1)
{
DisplayBuff();
BrushFlag=0;
}
if(timecount>= 300) //300ms改变一次数码管显示值//
{
timecount=0;
Volt_Cal();
BrushFlag=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
标定代码计算斜率函数如下:
void ComputeK(void)
{
K=(Y15-Y05);
K=K/(X15-X05);
KI=(IY15-IY05);
KI=KI/(IX15-IX05);
}
2
3
4
5
6
7
8
校准存储函数如下:
void save_calibration(void)
{
uint16_t da[5];
da[0]=0xaa;
da[1]=X05;
da[2]=X15;
da[3]=IX05;
da[4]=IX15;
flash_erase();
flash_write(0,da,5);
}
2
3
4
5
6
7
8
9
10
11
定时器BTIM1中处理按键检测及响应,代码如下:
void BTIM1_IRQHandler(void)
{
static uint32_t keytime=0,keytime2=0,keytime3=0,ledcount=0;
/* USER CODE BEGIN */
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Get_ADC_Value();
ledcount++; //LED闪
if(ledcount>=1000)
{PC13_TOG();ledcount=0;}
timecount++;
Dis_Refresh();//数码管扫描显示
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_12)==GPIO_Pin_RESET)
{
keytime++;
if(keytime>=100 )
{
keytime=0; //切换模式
Mode++;
if(Mode>=5)Mode=0;
BrushFlag=1; //更新数码管
}
}
else keytime=0;
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13)==GPIO_Pin_RESET&&Mode!=0)
{
keytime2++;
if(keytime2>=100 )
{
keytime2=0; //切换模式
if(Mode==1)
{
X05=Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==2)
{
X15=Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==3)
{
IX05=Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==4)
{
IX15=Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
}
}
else keytime2=0;
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13)==GPIO_Pin_RESET)
{
keytime3++;
if(keytime3>=100 )
{
keytime3=0; //切换模式
Mode=0;
BrushFlag=1; //更新数码管
}
}
else keytime3=0;
}
/* USER CODE END */
}
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
3.11.3 本实验的标定操作方法
该例程使用按键操作来标定。具体操作方法如下:
定义5个工作模式,K1键用于切换显示模式。K2键设置对应模式下的参数值,并保存到FLASH。K3键返回到模式0。
模式0: 显示正常的电压电流值(上一排数码管显示电压值*.V或.*V自动切换,下一排显示电流值,_.**A)
模式1: 电压5V标定值设置。上一排数码管显示5.05. 。下一排显示当前电压值_.V或._V。在该模式下,应将万用表测量被测位,调到5.00V。 按下K2键后,将当前值标定为5V电压值。
模式2: 电压15V标定值设置。上一排数码管显示5.15. 。下一排显示当前电压值_.V或._V。在该模式下,应将万用表测量被测位,调到15.0V。 按下K2键后,将当前值标定为15V电压值。
模式3: 电流0.5A标定值设置。上一排数码管显示A.0.5 。下一排显示当前电流值_.**A。按下K2键后,将当前值标定为0.5A电流值。
模式4: 电流1.5A标定值设置。上一排数码管显示A.1.5 。下一排显示当前电流值*.**A。按下K2键后,将当前值标定为1.5A电流值。