专业版
#第九届立创电赛#桌面温湿度仪+425272I

创建时间:3个月前

描述

<p>本次温湿度检测仪训练营,笔者基于示例项目《嘉立创EDA课程项目204:桌面温湿度仪项目》进行复刻与稍微延拓。进行参与 <strong>温湿度检测仪训练营</strong> 暨参与 <strong>“盛思锐传感器”第九届立创电子设计开源大赛</strong>。</p> <p>本开源将从硬件、软件理解学习到按笔者个人立项内容进行系统性重建。内容包括硬件设计、外观设计、驱动调试/软件设计、系统任务调度设计 和 产品延展思考。主要功能:属于一款桌面级板级小系统,使用7号电池* 2 供电,系统处于低功耗模式,当用户使用对应按钮,进行不同的功能激活,分别显示来自SHT40-AD1B超高精度温湿度传感器的温、湿度测量数据,来自电池的分压检测还原电池余电压,来自XGZP68xx系列传感器测量的大气压强数据。显示由HC595驱动两组3位7段带点的数码管,分别进行不同的挡位显示数据,在默认模式下,第一组数码管显示%2.1f的环境温度数据,单位为℃;第二组数码管显示%2.1f的环境湿度数据,单位为%H。</p> <p>keywords: SHT40-AD1B,温湿度检测,低功耗,电池供电产品</p> <p>修订: 2024年7月26日15点15分补充ADC+DMA部分说明;</p> <h1><strong>一、实物展示</strong></h1> <p><img src="//image.lceda.cn/oshwhub/14834fd096a34afa81067119b4233a5f.png" alt="image.png"></p> <h1><strong>二、学习和理解《嘉立创EDA课程项目204:桌面温湿度仪项目》</strong></h1> <p>本次训练营所提供的原理图如下: <img src="//image.lceda.cn/oshwhub/437b085f3b7248679b3d52e679e2782c.png" alt="微信图片_20240703165657.png"> 硬件组成部分由: (1)STM32G030K6T6作为主控,负责:通过I2C协议接收SHT40-AD1B的温湿度数据;通过驱动3枚75HC595芯片驱动共计6位数码管进行温湿度显示; (2)MCU外围电路简单,保留:R12和C27组成的上电复位程序(<strong>埋下伏笔,后续将会重重提及</strong>);外部低速晶振产生各工况下更可靠的RTC时钟参考;SWD调试接口用于程序烧录; (3)WAKE按键,通过外部中断唤醒处于睡眠模式下的MCU; (4)LED指示灯,用作GPIO例程; (5)防反接电路,防止安装电池时反向导致系统反电压极性接入; (6)电池电压采样电路,用作ADC例程; (7)SHT40-AD1B温湿度传感器接入,使用模块,对新手朋友更加友善。</p> <p>通过上述对硬件的分析,可以理解大部分的电路设计原理。接下来将开始对软件部分进行分析,边分析软件工程,边继续理解余下的电路设计原理: 在最终例程中,示例产品最终功能:产品处于低功耗模式,当且仅当WAKE唤醒,进行温湿度数据的显示。</p> <p>基于这个功能的实现,软件部分可以分为三部分:主控芯片STM32G030K6T6 (1)如何进入低功耗; (2)如何通过I2C协议读取温湿度传感器的数据; (3)如何通过控制74HC595进而控制数码管进行数据的显示。 (<strong>tips:这也是笔者在本次训练营所习得或得到强化的重要部分</strong>)</p> <p>---(1)低功耗,笔者通过研读主控芯片的数据手册(P 16/93) <a href="https://atta.szlcsc.com/upload/public/pdf/source/20200511/C529329_A1CB615556C746C4EA827FCAD1461480.pdf" target="_blank">STM32G030K6T6数据手册</a> <img src="//image.lceda.cn/oshwhub/22a64577da774cde847001e177575a84.png" alt="image.png"> 可以知道该主控芯片含有多种低功耗模式,其中本项目示例进入Sleep Mode,我们可能对哗啦啦一堆描述并没有太多的感冒,而更加关注该低功耗模式下,系统的功耗情况如何,翻到对应的章节中(P 45/93): <img src="//image.lceda.cn/oshwhub/51bc21afda8c41cab04b3cfc1bc53e99.png" alt="image.png"> 在Sleep模式下,STM32G030K6T6的功耗在满64MHz主频的情况下,约为1.4~2.4mA,是一个极其极其小的功耗(<strong>可以反过来思考笔者对主控的选型(STM32F103C8T6)其实并不恰当,这也是为什么本次开源笔者将学习和分析放到第二章的原因</strong>)。</p> <p>软件程序上如何进入低功耗呢? 若在使用HAL库的基础上,既可以便捷通过下列三个相关API函数,进入或退出Sleep模式:</p> <table> <tr> <th>goto/exit Sleep Mode</th> <th></th> </tr> <tr> <td>API/函数</td> <td>描述</td> </tr> <tr> <td>HAL_SuspendTick();</td> <td>挂起(暂停)systick中断,防止systick中断唤醒系统,也是主要减少功耗的地方</td> </tr> <tr> <td>HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);</td> <td>通过WFI指令, 进入睡眠模式,此时将等待任意一个中断的触发来唤醒系统</td> </tr> <tr> <td>HAL_ResumeTick();</td> <td>恢复systick中断,systick又叫系统滴答定时器,就是常说的MCU的心脏跳动</td> </tr> </table> <p>---(2)通过I2C协议读取温湿度传感器的数据 同样的,手册是了解器件的最好的工具:(P 3/24) <a href="https://atta.szlcsc.com/upload/public/pdf/source/20240722/3BD094C483E6DD673F5061D49CA37888.pdf" target="_blank">SHT40-AD1B传感器模块</a> <img src="//image.lceda.cn/oshwhub/41a429b07c7e411eab7da63f0d4eadfe.png" alt="image.png"> 在手册中,我们可以看到Quick Start Guide章节中,Sensirion(瑞士盛思锐)厂商给出了标准的使用方法,可以归纳为:</p> <table> <tr> <th>从SHT40-AD1B获取温湿度的步骤</th> <th></th> </tr> <tr> <td>步骤</td> <td>描述</td> </tr> <tr> <td>1</td> <td>往SHT40-AD1B对应的I2C地址写0xFD</td> </tr> <tr> <td>2</td> <td>稍作延时(10ms)</td> </tr> <tr> <td>3</td> <td>从SHT40-AD1B对应的I2C地址读取响应数据</td> </tr> <tr> <td>4</td> <td>将获取到的数据按照协议格式进行解码还原</td> </tr> </table> <p>---(3)通过控制74HC595进而控制数码管进行数据的显示 依然是数据手册:(P 12/43) <a href="https://www.ti.com/cn/lit/ds/symlink/sn74hc595.pdf?ts=1721856373111" target="_blank">SN74HC595PWR</a> 对数电类器件,首先参阅真值表或说功能表,从表8-1中,可以分析出如下内容: SN74HC595 是一款具有三态输出寄存器的 8 位移位寄存器,简单说就是串行输入 转 并行输出:可以将从PIN14(SEP)输入的串行数据,参考移位时钟(PIN11 SRCLK),进行移位;参考锁存时钟(PIN12 RCLK),进行锁存。参考项目中没有使用到的OE引脚是控制芯片使能,保持低电平,启用并口8位寄存器Q;没有使用到的SRCLR引脚是控制芯片移位寄存器清零,可用于快速初始化,保持高电平,屏蔽清零功能。 <img src="//image.lceda.cn/oshwhub/9baa164b6ad943fb8f5a68970f98c325.png" alt="image.png"></p> <p>那么通过该方法,就可以 ①节约控制6位数码管的IO口,正常单独控制6位需要22个IO口,而通过这种方式,<strong>按训练营提供的参考原理图只需要9个IO口就可以完成控制</strong>; ②隔离和增强IO口的输出能力,将从MCU变更为74HC595器件驱动负载,获得更高的输出驱动,对MCU的负担大大降低。</p> <h1><strong>三、设计和重构系统硬件</strong></h1> <p>笔者将在本章中阐述:本人从前面章节中习得内容并重新构建系统硬件的思路和具体硬件设计原理。 笔者不久前,在“沙漠中捡到了”一款7号电池盒子,非常小巧,并且是贴片的,基于该电池盒进行包饺子: <img src="//image.lceda.cn/oshwhub/aacb37eca87f454f86affd41f6a21a00.png" alt="image.png"> 这个将成为设计中最大的一个器件,作为该项目的外形限制: <img src="//image.lceda.cn/oshwhub/73b5405c665843dba47627ff98cd3770.png" alt="image.png"> 获得项目外型轮廓: <img src="//image.lceda.cn/oshwhub/27f867123fcb46949bbeb7608e762a55.png" alt="image.png"> 显示器件的选型就应该恰好可以叠放2个3位的数码管,用作数据显示:(C252220对应的尺寸长度非常合适,两个叠放占用22mm,算上误差在23.6mm范围内) <img src="//image.lceda.cn/oshwhub/07764dc7ceab4fa4bb53cdf960cfafca.png" alt="image.png"> 对数码管器件进行放置,获得项目主体外型及其布局:(注意叠放间距,从数码管尺寸图中可以知道引脚间距2.5可以完美放下两个3位数码管,此时不会产生间隙,也不会影响焊接) <img src="//image.lceda.cn/oshwhub/54e0e2074cdb468f85f2f7314784917e.png" alt="image.png"></p> <p>在驱动数码管的硬件设计中,理解第二章之后,笔者结合原身所学对74HC595控制数码管的部分进行稍微修改: (1)驱动方式从3枚595变更为2枚595,其中一枚595负责控制数码管的<strong>位</strong>,另一枚595负责控制数码管的<strong>段</strong>,如图中所示,该好处是可以节约1枚595,这就意味着,MCU也节约了IO口,并且控制逻辑更加严谨,同时为了便捷控制,将 负责位码控制的595的PIN9连接到 负责段码控制的PIN14(即<strong>从位码595溢出给段码595</strong>),也就是将数据进行菊花链式相连,在时序上更便捷管控,同时可以更好控制扫描显示不同数码管位的间隙,使得显示更加良好(<strong>不至于产生阴影/残缺/爆闪等不良显示</strong>),这更加意味着需要控制的IO口更少了,该方案已经经过验证,2枚595级联,最多可以控制8位数码管,只需要3 ~ 5个IO口,<strong>如果不需要控制使能和清零,最少只需要3个IO口即可(SER、SRCLK、RCLK)</strong>。 (tips1:笔者这里IO余下太多了,就把清零和使能都连到了MCU中,以备后续头脑风暴奠定硬件基础) (tips2:鉴于笔者以前对数码管和OLED的噩梦阴影——烧烧烧烧烧烧烧烧烧,,,这里必须给预留一个限流电阻的位置,若大家需要更高亮度,<strong>可以直接焊接0R,推荐电阻值范围100Ω ~ 300Ω</strong>) <img src="//image.lceda.cn/oshwhub/021b69fdfc894c069d4c2f4cdc56c762.png" alt="image.png"> 驱动数码管的程序部分将会在后续章节中教会你。</p> <p>(2)主控选STM32F103C8T6 这个呢,笔者就不是在沙漠捡的了,而是在海里捞的。库存的STM32F103C8T6,还余一枚,将用在本项目上,先偷偷看一眼STM32CubeMX软件中该主控的资源,并进行简单规划: (tips3:本次HAL编程使用STM32CubeMX软件进行项目基础框架生成,也是本训练营的重点内容!) 2.1 配置:程序下载方式为SWD,占用PA13-SWDIO,PA14-SWCLK <img src="//image.lceda.cn/oshwhub/87b1cc55b0804f42a46acc0fc5e7d16a.png" alt="image.png"> 2.2 配置:时钟,HSE和LSE 都配置为外部(到时候如果不需要可以不焊) <img src="//image.lceda.cn/oshwhub/9138225776f449b1829fcc0e7c245aae.png" alt="image.png"> 2.3 配置:ADC+DMA 测量电池分压(注意测量精度与Sampling Time挂钩,这里设最大值239.5 Cycles) <img src="//image.lceda.cn/oshwhub/61d53801488e49ea8fbd0f69babc8c85.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/ae41d553073742e7b2db706cdcdcdef2.png" alt="image.png"> 2.4 配置:TIM1+NVIC 72MHz @7200分频,计数100,即为100Hz(后续详细计算) <img src="//image.lceda.cn/oshwhub/b789f6ddcb1e418cba4ddf43bf9ca966.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/991ab76d72d144c3a8e6cef52f42add6.png" alt="image.png"> 2.5 配置:I2C,占用PB6-I2C1_SCL,PB7-I2C1_SDA <img src="//image.lceda.cn/oshwhub/faf2685068874e3cac50c9ca904c3704.png" alt="image.png"> 2.6 配置:USART1,占用PA9-USART1_TX,PA10-USART1_RX(用作开发调试DEBUG-printf) <img src="//image.lceda.cn/oshwhub/ac3a59e898234edf91bdb3e08e36a9eb.png" alt="image.png"> 2.7 配置:EXTI+NVIC,WAKE按钮,占用PA0(同时PA0还是SYS-WAKE,深度睡眠时可以唤醒系统) <img src="//image.lceda.cn/oshwhub/0f753a6e5a17473195b334643956c811.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/f1d119d43ebc47ceabb341e711ade812.png" alt="image.png"> 2.8 配置:若干GPIO,用作LED驱动,用作74HC595驱动 <img src="//image.lceda.cn/oshwhub/ed1db27f902a40f68821dda160d977e6.png" alt="image.png"></p> <p>规划完必须设置的引脚,类似2.8的配置,可以任选位置,到时候视layout难度进行调整,规划好的原理图如图所示: <img src="//image.lceda.cn/oshwhub/454d3f029e1c45f8a4f7efae5db80b8e.png" alt="image.png"> (tips4:如BOOT0,NRST这些进行必要的外围简单搭建一下,方便后续继续补全原理图)</p> <p>-----------------------<strong>紧接着,就可以放飞自我了!!!</strong>----------------------- 2.9 再来一组I2C,随便读点什么! <img src="//image.lceda.cn/oshwhub/386a4637745b455693a45b81384741a8.png" alt="image.png"> 2.10 再来几个按键,用来干什么都行! <img src="//image.lceda.cn/oshwhub/a207e605aa9e4d58a3667bd80e842d31.png" alt="image.png"></p> <p>完整的芯片部分原理图就被设计出来了:(对需要注意的部分进行简单说明备注) <img src="//image.lceda.cn/oshwhub/2c64283c3e354e58b78b462882a59d7f.png" alt="image.png"> 磁珠在这里的作用是对MCU的电源设置一级隔离,使得采样电压参考输入更加稳定。</p> <p>(3)设计电源电路 7号电池盒+防反接+分压测量 <img src="//image.lceda.cn/oshwhub/adddb21907014b68a5d1b6fd069a62ca.png" alt="image.png"> 使用P-MOS设计防反接电路,利用MOS管导通内阻小的优势(通常为mΩ级别),所采用的型号RDSON=44mΩ,所以输出电压几乎等于输入电压,笔者在栅极(G)和(GND)之间串加了一枚电阻,防止高压反接直接咔穿掉MOS。 <img src="//image.lceda.cn/oshwhub/89fce20e2bed4c70854bf66acd73d203.png" alt="image.png"> 假设输入电压 VBAT = 3V: 当正常极性接入时,3V通过P-MOS的体二极管从D级到达S极,肯定会有压降此时,那么S极的压降约为2.7V,则Vgs = -2.7V,看手册中的开门电压,最大值为-1.2V,则使得P-MOS饱和导通,到通知后,D-->S之间的压降降低,直逼0Ω,使得体二极管戒指,从而电源通过DS直通后方,故输出约等于并理想趋近于 3V。 当反接时,G极为3V,使得Vgs = 3V >0,不满足开门电压,P-MOS不导通,从而保护了后面的电路,也因此命名为防反接。 <img src="//image.lceda.cn/oshwhub/abdcf7e42cad4055b6dd0284d1f184ab.png" alt="image.png"></p> <p><strong>这里特别说明一下采样电阻,这里使用R1和R2进行分压,注意采样电路的电压精度要高,至少1%,笔者这里使用0.1%的电阻,同时这个电路是不管是否低功耗,都会工作耗电的电路,因此后续还需要对该路进行处理,后续分析将会重提此点!!!</strong></p> <p>分压计算公式,由于R1 = R2,故理想采样电压为电池电压的一半,还原时,将ADC的值转换为电压值,再x2即可。</p> <p>(4)设计传感器接口电路 这个为什么要设计呢,因为大赛为了考验一下大家,没有放出引脚图;而传感器模块极其小巧,引脚丝印难以在模块中进行印制,以至于如果不设计核对接口引脚,就会导致反接电源(VCC and GND exchange!) 核对手册传感器的引脚定义:对应的,如图标注一下实物 <img src="//image.lceda.cn/oshwhub/9512a48a7a344580a2a5ea8f2741ca1a.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/54ba5f9bc3fe40f787f69a5721482a0a.png" alt="微信截图_20240716104849.png"> 进行原理图绘制:(引脚方向为排针方向) <img src="//image.lceda.cn/oshwhub/21db1520d7114435850371d13fbd45b2.png" alt="image.png"></p> <p>看一眼全部设计好的原理图: <img src="//image.lceda.cn/oshwhub/94197134cf5c4b7a938f8ff826ece874.png" alt="SCH_Schematic1_1-P1_2024-07-25.png"></p> <h1><strong>四、PCB设计</strong></h1> <p>接下来就是layout作业,layout前需要对PCB所用元器件进行合理布局。 <img src="//image.lceda.cn/oshwhub/a158e5955ca44bbe90f13257445e6dd7.png" alt="image.png"> (1)对器件布局进行合理分布,使得美观紧凑; (2)设计关键部分的丝印框选,如图中,左侧黄色框为防反接电路,是最先焊接和测试的部分,需要特别指出; (3)对传感器部分进行丝印框选,提示用户安装位置,笔者设计在中部,这是为了:</p> <table> <tr> <th>传感器模块放在中间部分的考虑说明</th> <th></th> </tr> <tr> <td>序号</td> <td>说明</td> </tr> <tr> <td>1</td> <td>防摔,不必多言,插座模块一幢就坏</td> </tr> <tr> <td>2</td> <td>此处距离MCU的硬件I2C总线最近,线路短,方便layout,同时提高传输鲁棒性</td> </tr> <tr> <td>3</td> <td>在其底部设置红、绿两个LED,用作模块状态显示,刚好挡光,不刺眼</td> </tr> <tr> <td>4</td> <td>正面朝上,使得传感器重分接触空气</td> </tr> </table> <table> <tr> <th>传感器模块放在中间部分的坏处</th> <th></th> </tr> <tr> <td>序号</td> <td>说明</td> </tr> <tr> <td>1</td> <td>距离按键较近,测量湿度的时候,容易受手部湿度影响</td> </tr> </table> <p>(tips:这个坏处可以在编程时避免,让最远处的按键用来显示湿度,即最靠近左侧的WAKE按钮)</p> <p>(4)layout完成图示: <img src="//image.lceda.cn/oshwhub/9a0dcf0d1a6d442ca3eedeb5511aad12.png" alt="image.png"></p> <p>(5)<strong>进行大赛丝印放置</strong> 和 必要引脚丝印说明: <img src="//image.lceda.cn/oshwhub/21a4edf688164e01bc0ddd20b6fa2dc7.png" alt="image.png"> 2D预览: <img src="//image.lceda.cn/oshwhub/cbe146b870444577808895cfa8b07f8a.png" alt="image.png"> 3D预览: <img src="//image.lceda.cn/oshwhub/2991eecda0294c1294e75a9af480ea21.png" alt="3D_PCB1_2024-07-24.png"></p> <h1><strong>五、实物PCB组装</strong></h1> <p><img src="//image.lceda.cn/oshwhub/bdce8483024a4c25bb5267e57d8aafca.png" alt="组装对照.png"> (1)正面元器件较多,<strong>请注意优先焊接无胶体的元器件,类似按键、数码管等容易融化不耐热的器件后焊接</strong>、注意芯片类的1PIN,莫要焊反向; (2)注意模块插入方向,<strong>本项目,朝上插入</strong>! (3)背面就一个电池,这里就不放出来了。</p> <h1><strong>六、调试与驱动编写</strong></h1> <p>先放一个图给各位看一下笔者调试过程中,实物的状态: <img src="//image.lceda.cn/oshwhub/50e833d30a134b4fb8ba5ee3a827d054.jpg" alt="ad6effed8a27abe7e2c731ca1f6013c.jpg"> (tips:特别注意,调试时,不要安装电池噢,不要问为什么了,试试看嘿嘿嘿)</p> <p><strong>本章主讲程序搭建,一步步从零开始,教你进化完备工程:</strong> (1)STM32CubeMX底层框架配置: 信我的,第一次就尽量把所有的能想到的,需要用到的 配置 一次性给配好,不推荐用一个配一个。 (tips:配了可以不用,但是不配的话就用不了) <strong>Pinout view</strong> 本项目按照这个来配置: <img src="//image.lceda.cn/oshwhub/df5ccadc297b49f1b017beef7dde59fd.png" alt="image.png"></p> <h2><strong>6.1 配置顺序或说步骤</strong></h2> <p>6.1.1 配置Debug方式为SWD,将通过SWD方式编程或者仿真 <img src="//image.lceda.cn/oshwhub/4dff694a688649c6bfb46845cb77da40.png" alt="image.png"> 6.1.2 配置RCC,配置成外部晶振,并在时钟树中将主频设置为72MHz <img src="//image.lceda.cn/oshwhub/6384bc3c7693442da496ffb2b883a628.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/a9db81e2a88348d9a8d18e4d53ba82d0.png" alt="image.png"> (tips:后续<strong>配置ADC时</strong>可能会因为协议和上限,自动将配置好的72MHz变更,此时<strong>重新将主频设置为72MHz</strong>即可!) 6.1.3 配置LED-GPIO、74HC595-GPIO 和配置EXTI-KEY <img src="//image.lceda.cn/oshwhub/d89652d09df44ad69de837cbec8bf169.png" alt="image.png"> (tips:注意将KEYs的外部中断触发源设置为下降沿,这是由硬件电路决定的!) <img src="//image.lceda.cn/oshwhub/040e5a444ba0481992e7499dae6a299e.png" alt="image.png"> (tips:注意将KEYs的外部中断使能!) <img src="//image.lceda.cn/oshwhub/750000d704514f809b8a0dc371991167.png" alt="image.png"> 6.1.4 配置ADC+DMA,这里使用内部VREF校准采样值 <img src="//image.lceda.cn/oshwhub/dad06e6fd5c04f12845f795f97c29ac7.png" alt="image.png"> (tips:注意将采样周期尽可能增加,至少保证VREF通道是最大的,使得采样误差LSB更小) <img src="//image.lceda.cn/oshwhub/32920b556d574f24a48b9e69b13dd04e.png" alt="image.png"> (tips:注意DMA配置,本例程需要配置Circular,程序中自动循环DMA请求)</p> <p>6.1.5 配置定时器(这里配置了几个定时器,实际上可以配2个,各位可以看喜欢来删减) <img src="//image.lceda.cn/oshwhub/70da6ed729154210b1903219008128ba.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/0d1ae2b54b40423aba35163687513c9c.png" alt="image.png"> TIM1和TIM2需要开启中断,只展示TIM2,TIM1同理: <img src="//image.lceda.cn/oshwhub/32dbd49bd9854196a855f2265f396859.png" alt="image.png"></p> <p>==================================================================</p> <p>TIM3笔者这里是用来编程us延时函数了,可以配,可以不配,看自身需要 <img src="//image.lceda.cn/oshwhub/6011273e7d704699a4817404d3439eeb.png" alt="image.png"></p> <p>==================================================================</p> <p>6.1.6 配置I2C1和I2C2 <img src="//image.lceda.cn/oshwhub/93489808826342bb8e10768587bff5c2.png" alt="image.png"> (tips:注意速度和地址,地址比较重要,若无法通信,可以查阅I2C速度是不是超了,大部分I2C器件都满足这个速度(100k),I2C2是一样的,这里就不放图,自行配置)</p> <p>6.1.7 配置USART1 <img src="//image.lceda.cn/oshwhub/43d4cc81693a4f50a8be41065873c61c.png" alt="image.png"> (tips:配置为异步,这里的串口参数,调试时需要和软件匹配,否则上位机串口助手等无法正常通信!)</p> <p>6.1.8 配置时钟树 请重点检查主频;和ADC采样时钟,不要超过14MHz。 <img src="//image.lceda.cn/oshwhub/ce8705d093554c33a28d4434e45884c0.png" alt="image.png"> <a href="https://atta.szlcsc.com/upload/public/pdf/source/20220812/8363ACEC51AD55ECEB84799464F7CB85.pdf" target="_blank">STM32F103C8T6数据手册</a> <img src="//image.lceda.cn/oshwhub/10033c0a9cfd4c5c8347a6d2288235cb.png" alt="image.png"> (tips:超过了又如何??,嘿嘿嘿)</p> <p>6.1.9 配置工程(路径别有中文,否则不能生成stm32的启动文件!) <img src="//image.lceda.cn/oshwhub/0806b16cdbc64f3d82528e1a1969936e.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/e7579bc98d634bb1ae5e67ca895237b2.png" alt="image.png"></p> <p>ok了到这里,接下来生成工程: <img src="//image.lceda.cn/oshwhub/7795c12c17e9437b8792be0e61528ad4.png" alt="image.png"></p> <h2><strong>6.2 调试咯!</strong></h2> <p><strong>调试所有阶段的工程,笔者都放在附件中了,想要哪个阶段的,就下载哪个来!</strong> 6.2.1 点个灯例程 (1)新生成的工程先设置: <img src="//image.lceda.cn/oshwhub/ce01c88d95f44172895a1b01d0e6884b.png" alt="image.png"> (2)再编译一次,什么都不要动,优先编译后,再进行自己的工程逻辑代码编写! <img src="//image.lceda.cn/oshwhub/bc59c24a91134fba86da7442d499c43c.png" alt="image.png"> (3)直接写一个led.c,方便后续移植: <img src="//image.lceda.cn/oshwhub/ea230cc3600e465dbc8f8803db318065.png" alt="image.png"> 单独的.c文件,若和main.c不同路径,需要增加C/C++头: <img src="//image.lceda.cn/oshwhub/fb93a6a38e49469a93fe16d12590cb1a.png" alt="image.png"> 在头文件中,进行宏定义,方便主程序中调用: <img src="//image.lceda.cn/oshwhub/013b0d7af1c1413aa621c7f946ee1ac4.png" alt="image.png"> 将初始化LED的程序段,封装为函数,用户直接调用API(参数)进行初始化LED <img src="//image.lceda.cn/oshwhub/4cb7a88c813045e9b5ce04d9e4ec3b0b.png" alt="image.png"> (4)在main.c中包含LED头文件 <img src="//image.lceda.cn/oshwhub/37ee5d52c2cf4e72a4c785d715cd276e.png" alt="image.png"> 调用函数初始化LED(0x01的意思是,一个灯亮,一个灯灭) <img src="//image.lceda.cn/oshwhub/011ac0b147f34185921714335b0eb67d.png" alt="image.png"> (5)编写测试逻辑,<strong>程序功能:初始化LED一个亮,一个灭;每隔500ms,翻转两个LED的电平,实现切换亮灭颜色。</strong> <img src="//image.lceda.cn/oshwhub/bafe74cfe1ad45a096c19029e6fae522.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/9f1e5cfd26c2410cb3b74877e616ce24.gif" alt="2024-07-26-02-28-31.gif"></p> <p>6.2.2 调试用USART例程 直接用上一个工程继续往下编程:(下面的例程也是一样的,接下来就不赘述此说明。) (1)包含含printf函数的头文件 <img src="//image.lceda.cn/oshwhub/cb2c7366ddf545a7902e90a64a1a0b95.png" alt="image.png"> (2)对串口1进行printf重定向(之后就可以用printf打印,而不需要用HAL_UART_Transmit()接口) <img src="//image.lceda.cn/oshwhub/8a2a41d177b04e988795b5fb74edbf4d.png" alt="image.png"> (3)初始化串口1,将串口1缓冲区中的数据清空,防止上一次系统启动残留缓冲数据 <img src="//image.lceda.cn/oshwhub/2922ab7e5f8144a49a53f31bfdf357f8.png" alt="image.png"> (4)编写测试逻辑,<strong>程序功能:每次翻转LED,都打印一句提示。</strong> <img src="//image.lceda.cn/oshwhub/c0d2c9abd5f84b23b3172c9c6305ccb7.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/15641bf51db3403592b7b2eaab875b01.png" alt="image.png"></p> <p>6.2.3 I2C协议读取XGZP6828D例程 这个,在笔者上一个开源中,有了特别详细的介绍,移植到STM32来: (1)将笔者在STC上成功的例程,封装为.c文件,移植到此工程使用 <img src="//image.lceda.cn/oshwhub/251660844fc647148e7ce4dcee44c26c.png" alt="image.png"> (2)读数据手册,获取关键信息,并写入到驱动文件中 <img src="//image.lceda.cn/oshwhub/9525b225cf0b42e3ba4bb37c28bddfd7.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/e036ebd6a73e485c9945f579a5a1fe61.png" alt="image.png"> (3)其他的部分,可以参考笔者STC开源例程,且本项目重点在温湿度,这里不再过多赘述,直接在main中增加测试I2C读取XGZP6828D数据的测试逻辑。<strong>程序功能:每次翻转LED,都打印一句提示;然后读取XGZP6828D的数据,并打印到串口中。</strong> <img src="//image.lceda.cn/oshwhub/81266bb552764ef5b639e95efe012de0.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/79cd3525753b4bda854c9b064d10a6b7.png" alt="image.png"></p> <p>6.2.4 I2C协议读取SHT40-AD1B例程 (1)将封装一个独立的.c文件,方便移植 <img src="//image.lceda.cn/oshwhub/f45377f011fa455a857a8b78c6a7386b.png" alt="image.png"> (2)按照数据手册中提供的示例代码,使用HAL_API进行实现: <img src="//image.lceda.cn/oshwhub/72a40d9ccadd4afeb4667ff1190b40bf.png" alt="image.png"> 根据Quick Start Guide ① 首先发送0xFD给SHT40-AD1B; ② 再稍作延时(10ms); ③ 读取6个bytes; ④ 按照数据格式,2个bytes数据1个bytes校验和,2组分别为温度和湿度 ⑤ 进行解码,将数据按照厂商给出的转换公式,换算真实值! <strong>特别注意,因为精度转换问题,tempData是整型的,为了保留小数精度,需要对参与运算的数据进行.0f后缀,否则将会被MDK5的C语言优化掉!!!</strong> <img src="//image.lceda.cn/oshwhub/3413b9e48fe241f39310a7eab5a1e3df.png" alt="image.png"> (3)编写测试逻辑,<strong>程序功能:每次翻转LED,都打印一句提示;然后读取XGZP6828D的数据,并打印到串口中;然后读取SHT40-AD1B的数据,并打印到串口中。</strong> <img src="//image.lceda.cn/oshwhub/0816eecc13f644739ae86bd8945014ea.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/6f62568b39f24dec9242223a153ce5f9.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/9f4c61ce6cb54b74ba409720f219b4ee.png" alt="image.png"></p> <p>6.2.5 TIM1例程 接下来,为了做任务调度,单纯使用延时函数,容易阻塞进程,更好的方法是使用定时器中断来做调度: (1)重定义定时器中断回调函数,方便处理定时中断,并申请标志位,用来做时间触发调度: <img src="//image.lceda.cn/oshwhub/f5dcc62ce86043b7aedd639187abfd61.png" alt="image.png"> (2)要注意启动定时器中断!使能TIM1-NVIC <img src="//image.lceda.cn/oshwhub/8f455f20e5104f639978242db870e9ce.png" alt="image.png"> (3)编写测试逻辑,<strong>程序功能:每隔500ms读取一次SHT40-AD1B;每隔700ms读取一次XGZP6828D;每隔900ms将系统内的各个参数打印到串口中。</strong> <img src="//image.lceda.cn/oshwhub/4851c0c0fbfc4259af8b6d718dbde6d4.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/135c4b453a164cca9c7ea137b2ef12cf.png" alt="image.png"></p> <p>6.2.6 74HC595驱动数码管例程 编程之前需要查看手册,看一下实物是共阳还是共阴极,程序中已经做了兼容,参考该开源工程是,可以根据自己焊接实物的极性,选择不同的程序段驱动数码管:(如图,笔者这里是共阴极的数码管) <img src="//image.lceda.cn/oshwhub/2945af567e7243059f41c3522061aa0c.png" alt="image.png"> (1)单独编写一个.c文件,方便移植使用: <img src="//image.lceda.cn/oshwhub/afdb1e05559d4b51ad0f6a474f1d0fca.png" alt="image.png"> (2)编码标准字库数组: <img src="//image.lceda.cn/oshwhub/bbf4c144931a4bda83a76a6c5cdaeaf8.png" alt="image.png"> --(3)如果不需要us函数,可以不定义,这是为了兼容和测试HAL通过通用定时器实现us的部分: <img src="//image.lceda.cn/oshwhub/fd8cca15b00e4c10ac0108f7b610d090.png" alt="image.png"> (4)根据74HC595的真值表,可以知道,需要移位,则SCK来一个时钟;若需要锁存,则RCK来一个时钟 <img src="//image.lceda.cn/oshwhub/4a8270db0c254160a1eac7028adba500.png" alt="image.png"> 那么这个项目有2枚595,并且,从位溢出给段,则需要: <strong>①先发送段码; ②再发送位码; ③再锁存输出!</strong> <img src="//image.lceda.cn/oshwhub/dcee820bed0742c48e815ec74869fe47.png" alt="image.png"></p> <p>程序实现: <img src="//image.lceda.cn/oshwhub/c3089918f8d4468faa1365c8b3110034.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/c94f520ecca24aad9e62ae09f7f896ae.png" alt="image.png"> 那么74HC595驱动SIG显示的函数就可以如下编程: <strong>①先发送段码; ②再发送位码; ③再锁存输出!</strong> <img src="//image.lceda.cn/oshwhub/d07b2a633ccd4c47a2ebd261861f1f02.png" alt="image.png"> (tips:而如果焊接是阳极的,请使用保留的注释中的阳极的代码,同时注释掉阴极的!<strong>核心就是取非,翻转控制方向即可!</strong>)</p> <p>(5)main函数中,while(1)之前做初始化: <img src="//image.lceda.cn/oshwhub/6f1737d9f4204d319528cbc50165e91d.png" alt="image.png"> (6)编写测试逻辑,<strong>程序功能:每隔500ms读取一次SHT40-AD1B;每隔700ms读取一次XGZP6828D;每隔900ms将系统内的各个参数打印到串口中;每隔1100ms刷新一次数码管的值。</strong> <img src="//image.lceda.cn/oshwhub/114cbce39b574554aaf59a7816ea2ac4.png" alt="image.png"> (tips:这里刷新数码管显示,定时扫描的方式,使用TIM2,进行控制,中断方式与TIM1类似,回调和初始化请直接参考源工程【E6_74HC595_SIG】,此处不过多赘述) <img src="//image.lceda.cn/oshwhub/e3626faffac04c6c85ccd836f9ec566f.png" alt="image.png"></p> <p>观察实验效果: <img src="//image.lceda.cn/oshwhub/fa99f4c8f5f64323b1e568f469b36457.gif" alt="2024-07-26-03-37-02.gif"> <img src="//image.lceda.cn/oshwhub/accd5f80c6ef4daaa5603aec05980d5a.png" alt="image.png"></p> <p>6.2.7 按键触发外部中断 和 ADC+DMA 例程 首先定义按键,如下图所示:(其中NRST是硬件复位MCU,无需编程) <img src="//image.lceda.cn/oshwhub/381e0c4aeeaf4ee381880d8d5fddbfd2.png" alt="微信截图_20240724225209.png"> 定义按键事件,可叠加事件,系统闲时处理事件内容: <img src="//image.lceda.cn/oshwhub/d7a3e6a12b9f458e8968930bf5632a7a.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/2c4b444d6558489eaa0610c8c9291eb8.png" alt="image.png"> 笔者设计了三个挡位,分别对应三个编程按键:</p> <table> <tr> <th>按键事件@显示挡位</th> <th></th> </tr> <tr> <td>WAKE</td> <td>温湿度挡位</td> </tr> <tr> <td>USER1</td> <td>电池电压挡位</td> </tr> <tr> <td>USER2</td> <td>大气压强挡位</td> </tr> </table> <p><img src="//image.lceda.cn/oshwhub/2e2bb9e7bff14314a7945be9dc7c297f.png" alt="image.png"> 对应的,定义一个函数,用来处理挡位显示,编写测试逻辑,<strong>更新定时1100ms事件</strong>: <img src="//image.lceda.cn/oshwhub/4c2a03e0e6cd45259dc77f14981fe686.png" alt="image.png"> <img src="//image.lceda.cn/oshwhub/6dbd5408368341339f08df4c387ad6b6.png" alt="image.png"></p> <p><strong>2024年7月26日15:00 p.m. 补充:ADC+DMA的实现说明</strong>,这里使用了内部参考电压通道校准其它采样通道,具体方法: (1)定义所需要用的通道和变量,这里为了做案例把MCU内部温度通道也一并读取, <img src="//image.lceda.cn/oshwhub/c030dd9a5ef94e289f037782eb508878.png" alt="image.png"> (tips:温度转换公式需要参阅参考手册<a href="https://www.st.com.cn/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf" target="_blank">STM32F103C8T6_RM0008</a>(P 236/1136),或直接看下图) <img src="//image.lceda.cn/oshwhub/63dfc1cad2424fd885988396b72af9b2.png" alt="image.png"> (2)需要初始化ADC,启用DMA传输通道,<strong>这里必须要注意DMA通道数,必须是ADC设置通道数量的整数倍,这里是1倍,若要做滤波前置,可以是N倍,N必须是正整数。</strong> <img src="//image.lceda.cn/oshwhub/5e1164dd3aed4521a597ba922f6de127.png" alt="image.png"> (3)还需要将读取到的ADC值(LSB单位)的转换为电压值(Voltage单位),笔者提供一个函数:该函数中,<strong>首先读取VREF的电压对应的ADC值,又已知STM32F103C8T6内部电压值默认为1.2V,因此读取到的值和默认值做比例算术,可以获得一个比值,这个比值将作为其他通道的换算比例,实现使用内部参考电压校准其他ADC采样通道!</strong> <img src="//image.lceda.cn/oshwhub/44a56388c6a2406bbc87e9a06980898e.png" alt="image.png"> (为了让大家尝试一下MCU内部温度,这里重建温度通道的电压值为华摄氏度单位,具体转换公式参考上述RM008手册中的换算公式!)</p> <p>(4)由于使用了ADC+DMA的方式,无需在main的while中反复发送读取ADC通道的指令,<strong>系统自动将读取后的ADC值通过内存到内存的方式从ADC出口直接到指定的内存中(例程中申请的adcValue[])!</strong>,因此在主程序中,只需要定时对其内的ADC值进行转换为电压值,就可以获得所测量的电池电压: <img src="//image.lceda.cn/oshwhub/93c02c6ee61b4c7c8fa483a352fb5ccd.png" alt="image.png"> (5)别忘了读取到的是两个电阻的分压,因此还需要1:2映射为真实的VBAT电压值: <img src="//image.lceda.cn/oshwhub/755d1ae31ccf4c17a5b2a1e99646e208.png" alt="image.png"></p> <p>观察实验效果: <a href="https://www.bilibili.com/video/BV1R5eeecEwz/" target="_blank">https://www.bilibili.com/video/BV1R5eeecEwz/</a></p> <p><img src="//image.lceda.cn/oshwhub/819619294cc943dbac83bc0f50b5a546.png" alt="image.png"></p> <p>6.2.8 低功耗 SLEEP 例程 低功耗的实现API原理,HAL库的,参考笔者本文第二章,有详细说明,此处不再赘述。 (1)心中有数,看下主控MCU-STM32F103C8T6 SLEEP 模式下功耗多少:(确实是G0的7倍多,这也是笔者在第二章中提到的,本开源工程或许需要更深度的休眠才能媲美G0低功耗模式的功耗) <img src="//image.lceda.cn/oshwhub/d94b23c2072449e487cfa6b906a1293f.png" alt="image.png"> (2)重新架构一下程序,程序功能参见下图或者例程中r-69开始的说明: <img src="//image.lceda.cn/oshwhub/e89ad6f1a86646799cd19b761364f5cd.png" alt="image.png"> (3)按照新的架构,重新编写测试逻辑, <strong>开机不进入低功耗,先按默认挡位显示温湿度3轮;</strong> 同时,每一轮刷新都记录次数 <img src="//image.lceda.cn/oshwhub/4c74d261619a4c39a733fb8b52fd8a6d.png" alt="image.png"> <strong>在定时回调中,如果显示达到或超过了3轮,则允许系统进入休眠</strong> <img src="//image.lceda.cn/oshwhub/eb4c3788038747b1a231eb6b043453fc.png" alt="image.png"> <strong>闲时进行休眠判断,若标志位为1,立刻进入休眠</strong> 休眠之前,做这么些事情:(请看图中代码注释,超详细) <img src="//image.lceda.cn/oshwhub/01bca00a47f142c2a22c1e6b7ff698be.png" alt="image.png"> <strong>休眠时,若任意外部按键按下,退出休眠模式,进入按键外部中断回调中</strong> 在回调中,做鲁棒性考虑,<strong>防止用户一直按下未休眠又反复处理唤醒后只需要执行一次的事情</strong> 合法唤醒,则做这么些事情:(请看图中代码注意,超详细) <img src="//image.lceda.cn/oshwhub/b803111be3214103a0e2ce529caa6609.png" alt="image.png"></p> <p>观察实验效果: <a href="https://www.bilibili.com/video/BV1J1eiemEqt/" target="_blank">https://www.bilibili.com/video/BV1J1eiemEqt/</a></p> <p>6.2.9 功耗考虑 前面提到实际上,该电路一直工作,<strong>意味着:不管MCU是否进入SLEEP状态,该外围电路都持续耗电。</strong> <img src="//image.lceda.cn/oshwhub/6f6128abeb8042a98df97c2fb53a1ab5.png" alt="image.png"> 那么持续电流值是多少呢?如果按照参考原理图, 假设VBAT = 3.0V <strong>则 I_adc = 3.0V/(10K x 2) = 0.15mA)</strong></p> <p>笔者将这两个电阻更改为51k ±0.1% <strong>则 I_adc = 3.0V/(51K x 2) = 0.029mA)</strong></p> <p>那么必然的,我们需要将这里处理好: <strong>参考方法1:R2不焊接,直接采集VBAT电压;注意修改程序中公式,此时1:1,不需要映射比例! 参考方法2:将R1和R2的值,至少放大10倍,如510k ±0.1%,此时静态电流将会进一步降低 约为0.003mA,当然过低的电流值实际上对ADC采样不友好(很多书和文献不推荐这么做),而电流过小时,需要取消C1-100nF的采样电容。</strong></p> <h1><strong>七、产品测试(功耗测试)</strong></h1> <p>将上述写好的固件烧录到产品中,卸下电池,用固定 电源 设置3V供电,使用电流表进行电流测量。这里用到DMM6500. 把DMM6500当电流表串入电路中,进行采集电流值即可,如下为测试数据曲线: <img src="//image.lceda.cn/oshwhub/1c9dcf834a6649cbaabab832534fee85.png" alt="image.png"></p> <table> <tr> <th>标注说明</th> <th></th> </tr> <tr> <td>PWR ON</td> <td>BAT = 3V 上电动作</td> </tr> <tr> <td>NRST</td> <td>系统复位动作</td> </tr> <tr> <td>WAKE</td> <td>温湿度挡位唤醒动作</td> </tr> <tr> <td>USER1</td> <td>电压挡位唤醒动作</td> </tr> <tr> <td>USER2</td> <td>大气压强挡位唤醒动作</td> </tr> <tr> <td>PWR OFF</td> <td>BAT = 0 掉电动作</td> </tr> </table> <p>从测试图来看,功耗还是比预计的SLEEP MODE要大,除了标准的最大8mA与电池电压采样电路的3mA,还有大约9mA的其他电流存在,后续还需要继续优化。</p> <h1><strong>八、总结</strong></h1> <p>本次训练营复刻和延拓,学习了如何使用STM32CubeMX进行项目工程搭建,STM32CubeMX+HAL库 比之直接使用HAL库或者使用标准库的方式更加便捷,软件配置外设,排除了人工配置可能出错的环节。是一种较好的嵌入式开发方式(当项目开发需要使用STM系列芯片时)。</p> <p>在本次训练中,学习到如何使用硬件I2C读取传感器模块的数据并进行解码还原真实数据;学习到如何使用74HC595级联方式控制最高8位数码管,学习到了如何使SMT32进入低功耗以及电流和功耗的测试方法。<strong>本次训练收获颇丰,上述文稿均为本人训练中所得,在此原创分享,希望能够在往后的某一刻帮助到亟须解决相似问题的你!</strong></p> <p>------------------本文到此-------------------------------------------------------END--LINE----- 相关文件可以在附件中获取哦,如果本开源项目可以帮到路过的你,掏个赞鼓励作者~</p>
  • video_E7_EXTI_KEY_ADC_.mp4

  • video_E8_SLEEP_.mp4

文档

BOM

暂无

附件

附件名 下载
E1_GPIO_LED.zip
E2_USART1_.zip
E3_I2C2_XGZP6828D.zip
E4_I2C1_SHT40.zip
E5_TIM1_.zip
E6_74HC595_SIG.zip
E7_EXTI_KEY_ADC_.zip
E8_SLEEP_.zip
video_E7_EXTI_KEY_ADC_.mp4
video_E8_SLEEP_.mp4

评论(3)

  • 表情
    emoji
    小嘉工作篇
    小嘉日常篇
  • 图片
成功
工程所有者当前已关闭评论
jbliu 回复
<p>你的确是我们电子竞赛新手的典范,作品靓丽,行文流畅,解释详细,作品内涵深意。值得我们学习。</p>
geng_yan_wang 回复
<p>大佬牛的,这就学习</p>
geng_yan_wang 回复
<p>越看越发现大哥功力深厚!!</p>
goToTop
svg-battery svg-battery-wifi svg-books svg-more svg-paste svg-pencil svg-plant svg-ruler svg-share svg-user svg-logo-cn svg-double-arrow