描述
<h1>USB功率计</h1>
<h2>硬件设计</h2>
<p>粗略阅读官方给出的原理图,可以发现的是,USB功率计是通过<strong>ADC检测电流、电压,从而通过功率等于电压乘以电流的公式得到功率值,最后则将该值显示到显示屏上面</strong>。因此我们将整体电路剖解成五个部分分别进行阐述。</p>
<pre><code class="language-c">1、主控电路:主控芯片+退耦电路+BOOT电路+复位电路+晶振电路+SWD电路+串口电路;
2、电流采样电路;
3、电压采样电路;
4、电源电路;
5、显示电路。</code></pre>
<hr>
<h3>1、主控电路</h3>
<p>本次USB功率计采用的主控为---国民技术家的芯片<strong>N32G430C8L7</strong>,在商城里面<strong>售价5元</strong>。在这个"缺芯"时代,五元M4内核的MCU,恐怕不多见了吧。虽然这颗物料非常强大,但是我们本次的功率计其实仅仅用到了<strong>USART、ADC、IIC</strong>功能,其他功能都没有使用到。其中,串口也不是为了打印信息,而是用来作为串口Boot下载用的,当然你既然使用了SWD电路接口,串口其实用不上,当然你说想使用串口下载程序,当然是没得问题的,不过值得注意的是<strong>如果使用串口程序下载,需要将BOOT0管脚拉高!!!</strong></p>
<p><img src="//image.lceda.cn/pullimage/AYgauG0qYa9bOvnVJPPR6svJiTLaJ3bkwXZue7LO.png" alt="1.串口程序下载.png"></p>
<h4>不过我个人还是倾向于使用SWD电路进行下载,这样方便很多,因此各位看官根据自己的个人喜好选择自己喜欢的下载模式吧。</h4>
<hr>
<p>主控电路部分主要是引出了<strong>串口引脚、SWD引脚、BOOT0引脚、晶振引脚、复位引脚、电压采样引脚、电流采样引脚、IIC引脚</strong>,值得说明的是,IIC通讯例程是采用软件IIC,也就是普通GPIO模拟IIC时序通讯进行OLED的通讯功能,因此关于IIC引脚的选择完全可以随便选择两个GPIO引脚。</p>
<p><img src="//image.lceda.cn/pullimage/u9RCeeDS0mGkclBVbruIpiQw3nceGDpfdjzDFi6A.png" alt="2.主控电路.png"></p>
<p>值得说明的是:<strong>并不是一定要某些引脚,还有其他引脚可以进行选择,具体请参考->参考文档 中的 数据手册<-</strong></p>
<hr>
<h3>2、退耦电路</h3>
<p>通过对主控电路MCU的观测可以看到<strong>VDD引脚有4个,因此至少放4个0.1uf的电容在旁边!</strong>有人会问问什么是0.1uf,因为我们知道在电路中频率f=1/(2×Π×根号下(LC)),因此,<strong>如果电容值小,则频率高,滤除高频杂波;如果电容值大,则频率低,滤除低频杂波。</strong></p>
<p><img src="//image.lceda.cn/pullimage/eYravJn27ikg1ONgcG5bGVL5zkacMWTiei98DzeX.png" alt="3.退耦电路.png"></p>
<h4>值得注意的是:官方硬件开发板的设计中尤其说明了<strong>N32G430C8L7的第一引脚必须保证放一个4.7uf电容和0.1uf电容,其余电容放0.1uf电容</strong>,因此我们需要额外的注意的是这一点,听说如果不这么做,MCU可能会不正常!为了保险起见,我还是加上了,哈哈哈,具体原理图如下所示。</h4>
<hr>
<h3>3、BOOT电路</h3>
<h4>相信学过单片机的人应该都知道,BOOT引脚是用来选择<strong>芯片MCU的启动模式</strong>的,主要是:1、从系统闪存存储器启动;2、从系统存储器启动;3、从内部SRAM启动。其中,最为常用的则是第一种方法,也就是我们常用SWD电路下载启动的模式;第二种模式是通过串口烧写bin文件,通过调节BOOT引脚,进行烧写的,稍微麻烦点。第三种方式我没试过,我记得以前看硬石科技讲解时,好像有提到这个,这个下载方式不建议来着,具体忘记了,有懂的同学可以在下面告知一二。综上所述,我选择了第一种模式,当然第二种模式我也是引出来了的,各位看官可以自行选择即可。具体原理图如下所示。</h4>
<p><img src="//image.lceda.cn/pullimage/o2oBOwbuEymcb6WbpflajJpcOTFj2BgwXilpNzF0.png" alt="4.BOOT电路.png"></p>
<h4>我这里采用两个0603封装的电阻进行模式的切换选择;默认状态下,我是只装了R4电阻的,R3电阻没有接,也就是BOOT0默认被拉低了,通过SWD电路接口进行程序的下载。有同学可能想要串口下载,这个时候就把R3的零欧电阻焊接上就好了,BOOT0引脚会被拉高,这样则可以进入串口下载模式,这个我在前面已经阐述过了,总体设计也是为了保证电路的小型化。</h4>
<hr>
<h3>4、复位电路</h3>
<h4>复位电路的目的是为了保证芯片重新启动,手动复位;电路特别简单,还是采用网上最常用的<strong>硬件消抖</strong>按键设计方案,具体原理图如下所示。值得注意的是:关于复位时长是有要求的 ,具体参考芯片的<strong>-> 数据手册 中的 4.3.13 NRST引脚特性<-</strong>,RC硬件消抖的时间计算公式是模拟电子技术中的<strong>t=RC</strong>,手册说了,产生复位脉冲持续时间至少10us,因此在选择电阻值和电容值时,各位可以考虑一下,以免复位不正常。</h4>
<p><img src="//image.lceda.cn/pullimage/gHyElKTZ3W6nY9F6tkVXvBRSBkC1kvGPvR9r9ahJ.png" alt="5.复位电路.png"></p>
<h4>我这里延时时间是<strong>t=10×10^3×100×10^-9=1ms,远远满足于系统所要求的时间</strong>。</h4>
<hr>
<h3>5、晶振电路</h3>
<h4>其实对于晶振的选择原理还是挺多的,网上也有很多讲解方法,我对这部分没有很深入的研究,一般常用大厂最常用的负载电容,一般取<strong>20pf</strong>来着,具体各位可以用示波器进行波形测试,可能更直观,我这里根据物料以及以前的使用情况来说,我实际上是采用了<strong>22pf</strong>,具体选型依据各位有兴趣的可以去网上多看看,具体原理图如下所示。</h4>
<h2><img src="//image.lceda.cn/pullimage/bk2Jgha7GTBCuL6FBljabw4ci8AjOE3ueOsKFACP.png" alt="6.晶振电路.png"></h2>
<h3>6、SWD电路</h3>
<h4>这个部分的引出主要是为了下载电路,通过查阅<strong> ->数据手册 中的 引脚定义<- </strong>可以看到PA13、PA14是作为SWDIO-JTMS和SWCLK-JTCK,我们只需要引出这两个接口即可!值得说明的是:<strong>有些文档建议在SWD电路中加入RSET引脚,防止芯片锁死</strong>,我这里考虑到产品的整体大小和对称性,我这里没有加,各位可以自己斟酌一下,具体原理图如下所示。</h4>
<h2><img src="//image.lceda.cn/pullimage/vVoHBAzsxss9iCkSJjSlj1ihTnZPp2qhnnLvWO46.png" alt="7.SWD电路.png"></h2>
<h3>7、串口电路</h3>
<p>串口的引出主要是作为<strong>串口下载程序用的</strong>,当然完全可以用作串口通讯,没有任何问题!具体原理图如下所示。</p>
<h2><img src="//image.lceda.cn/pullimage/d4usPsFERZqpLBKfsIWUGdyIe2eKa0klQ6IMwCPK.png" alt="8.串口电路.png"></h2>
<h3>8、电流采样电路</h3>
<p>首先是通过Type-A公头进行供电,然后通过Type-A木头进行输出。</p>
<p><img src="//image.lceda.cn/pullimage/BZuMHuLawLSfqxoRsCwTH7ucZqF9vQPYRLtYKi1B.png" alt="9.电流采样电路.png"></p>
<p>电流采样部分的是<strong>INA199B1DCKR</strong>电流感应放大器,固定增益为:50V/V,这个在程序代码中会使用到。如果你使用其他采样芯片,放大系数可能不同,程序就需要有所更改。
此采样使用的是<strong>低边采样</strong>的方式,也就是采样电阻接在GND的回路上,此设计可以在差分信号送入运放的时候,运算完整的差分、跟随、放大、输出。
如果使用高边采样,也就是采样电阻放置在电源和负载之间的高位,虽然这种放置方式不仅消除了低边检测方案中产生的地线干扰,还能检测到电池到系统地的意外短路,但是高边检测要求检测放大器处理接近电源电压的共模电压。这种共模电压值范围很宽,从监视处理器内核电压要求的电平(约1V)到在工业、汽车和电信应用常见的数百伏电压不等。应用案例包括典型笔记本电脑的电池电压(17到20V),汽车应用中的12V、24V或48V电池,48V电信应用,高压电机控制应用,用于雪崩二极管和PIN二极管的电流检测以及高压LED背光灯等。因此,高边电流检测的一个重要优势,那就是检测放大器具备处理较大共模电压的能力。
所以,<strong>采样电阻加运放的电流采样方法,最好是在低端进行</strong>。虽然,低端采样,由于共地干扰的原因会影响信号的纹波情况。但是相对高端来说,方案简单易行,成本低,可靠度高。</p>
<hr>
<h3>9、电压采样电路</h3>
<h4>硬件电路部分主要是采用最基本的<strong>电压分压公式</strong>求取系数值,这个系数值我会在程序部分详细的解释,这个电阻的选择也是很关键的,不同的电阻,到时候程序代码的因数值就需要改!官方给的是91K和10K,我这边物料只有110K和10K了,所以我就替代了官方的硬件图,在程序代码中会有所改变,具体计算公式我会在程序部分详细阐述,这里仅仅作为硬件设计的展示。</h4>
<p><img src="//image.lceda.cn/pullimage/Yh1EEOqpjW7u3S4Otyi7t9fZyNt7SJyVCkQMiJ2P.png" alt="10.电压采样电路.png"></p>
<hr>
<h3>10、电源电路</h3>
<p>电源电路就很简单了,就是一个LDO就行了,主要是为了给MCU芯片供电的,我们查阅芯片的<strong>->数据手册 中的 4.3.1 通用工作条件<-</strong>可以发现:芯片标准工作电压是2.4V-3.6V,因此我们需要将输入的5V电源电压变成3.3V给到芯片供电,相信大家最常用的就是<strong>AMS1117-3.3V</strong>了吧,我也一样,考虑到物料情况,我的硬件电路也是这颗物料。</p>
<h4>但是有一个问题是:这颗物料尺寸相对较大,所以我就把他布线到了反面,这一点对于贴片工艺可能比较不合适,所以各位自行考虑吧,我自己是手动焊接的(主要是没钱)。</h4>
<p><img src="//image.lceda.cn/pullimage/WDnWh7rOY9wufdPklWkK3kEC6vrnSnJLJ1iv3auH.png" alt="11.电源电路.png"></p>
<hr>
<h3>11、显示电路</h3>
<h4>显示部分是采用<strong>OLED的0.91寸屏幕</strong>,这里感谢<strong>XCC</strong>让我白嫖了一个屏幕,哈哈哈哈。电路部分是很简单的,就是用<strong>2.54mm间距的4P排母引出,然后插接OLED模块即可</strong>,具体原理图如下所示。</h4>
<p><img src="//image.lceda.cn/pullimage/YMxRw22cGZmg9oB55UK1BmpM1OjGEv3nXqmb3pZl.png" alt="12.显示电路.png"></p>
<hr>
<h3>12、整体3D模型</h3>
<p><img src="//image.lceda.cn/pullimage/7bY1PP4eQ1ZSc3fwjQwNWhHvNQ40Os9kJPjneyqN.png" alt="13.整体3D模型.png">
这里再阐述一下如何把这个排针的3D模型,加入了OLED。</p>
<p>第一步是选中H1端子,右边属性中(快捷键为:})封装,点一下,然后会进入到封装管理器。</p>
<p>第二步则是将H1端子的封装改成<strong>0.91OLED屏幕 v2</strong>。</p>
<h4>第三步则是调整OLED的位置,我的参数如下所示,具体请参考自己的硬件设计。</h4>
<p><img src="//image.lceda.cn/pullimage/vHtx87kzPMO02b7SSvZmdWAXVOgu1oqNMaS0PCKN.png" alt="14.OLED调整参数.png"></p>
<hr>
<h3>13、打样实物图</h3>
<h4>以上则是全部硬件电路设计的工艺,打样,实物图如下图所示。</h4>
<p><img src="//image.lceda.cn/pullimage/L4YZ6Svk4UvCw1zzjoEzU1RhHhJKAw6GhXWpUSwy.jpeg" alt="15.打样实物图.jpg"></p>
<hr>
<h2>软件设计</h2>
<p>最开始还想着把<strong>N32G430C8L7</strong>的外设测试测试的,但是!我发现我就引出了串口和OLED引脚,其他都没有引出来,那就没办法了。</p>
<p>我这里给了一个新建工程目录,后期各位设计可以自己加文件夹,然后运行即可。</p>
<p>加下来咋们就针对官方给出的代码进行分析,后期各位也方便针对不同的硬件电路修改程序代码。</p>
<hr>
<p>首先大致过一遍main.c,可以发现使用了看门狗、软件模拟IIC、ADC,看门狗和IIC是最基本的,我们这里无需过多介绍,比如IIC,他其实就是用了Lib/MonoScreen中的文件夹和Lib/SoftI2C中的文件夹实现OLED的显示功能,SoftI2C则是模拟IIC时序的函数,然后在MonoScrren中进行调用使用,fonts就更简单了,就是一个字库,所以这部分我们就不需要过多解释了,最重要的还是<strong>采样部分的代码</strong>。</p>
<pre><code class="language-c">// 定义ADC的1LSB与实际电压电流的比值
const float volFactor = 8.471446586200685F * VOLTAGE_FACTOR;
const float curFactor = 1.646382291543582F * CURRENT_FACTOR;</code></pre>
<p>首先是关于<strong>volFactor</strong>这个的意思是:电压因子,也就是通俗来讲,表示的是<strong>实际电压除以采样电压</strong>。</p>
<p>VOLTAGE_FACTOR这个量实际上是1,类似于一个滤波采样那样的作用~~(也许,我个人理解,哈哈。然后我们就得关注前面那个数值是如何得来的了。</p>
<pre><code class="language-c">我们知道的是,采样芯片的供电电压是3.3V,而采样是adc,模数转换器,且采样位数是12位,因此有这么一个对应公式存在:
3.3V 2^12-1=4095
那如果假设此时adc采集到的数值是adc,那请问此时的模拟量x是多少?因此则有:
x adc
根据对应关系有:
x = adc × (3.3/4095)
请记住这个时候x的值并不是实际值,因为什么呢?因为采样电路中,分压有个变换公式!我们这里解释的话还是根据官方的电阻进行分析即可。根据分压公式有:
Vadc = 10K/(10k+91k)×Vbus,其中Vbus为5V.
那如果现在已经把adc的电压采集到了,怎么求现在的Vbus值呢?
Vbus = Vadc / (10K/(10k+91k)) = Vadc×(10+91)/10
OK,我们既然能达到这个公式,那就好说了,我们现在根据adc采集到的Vadc=x,那这个时候的Vbus就和x有对应关系了,如下所示。
Vbus = Vadc×(10+91)/10 = x × (10+91)/10 = adc × (3.3/4095) × (10+91)/10
为了方便程序运行,我们将数值自己算出来,因此可的:
Vbus = adc × 8.139194139
至于官方为什么是8.47,可能他是根据实际电压测试的,因为说是3.3V实际上电不一定是正好3.3V.</code></pre>
<p>我们已经解决了volFactor数值的由来,再来看看curFacotr的由来。</p>
<pre><code class="language-c">我们知道的是,采样芯片的供电电压是3.3V,而采样是adc,模数转换器,且采样位数是12位,因此有这么一个对应公式存在:
3.3V 2^12-1=4095
那如果假设此时adc采集到的数值是adc,那请问此时的模拟量x是多少?因此则有:
x adc
根据对应关系有:
x = adc × (3.3/4095)
请记住这个时候x的值并不是实际值,因为什么呢?因为采样电路中,电流是经过了增益变换。
我们查阅电流采样芯片的数据手册可以发现的是,该芯片是10mV满量程分流压降,也就是意味着Iadc每变化1,▲I=10mV,因此有如下的对应公式:
1 10mV
那请问,如果此时电流采样值是Iadc的话,那应该显示的x是多少呢?
Iadc x × 50
根据对应关系有:
x × 50 = Iadc × 10
---> x = Iadc × 10 / 50
代入电流采样值,可得x与adc的关系式:
x = Iadc × 10 / 50 = adc × (3.3/4095) × 10 / 50
为了方便程序运行,我们将数值自己算出来,因此可的:
x = adc × 1.611721612
至于官方为什么是1.64,可能他是根据实际电压测试的,因为说是3.3V实际上电不一定是正好3.3V.</code></pre>
<hr>
<p>得到了对应系数因子后,然后定义了几个变量:</p>
<pre><code class="language-c">// 定义电压和电流的ADC采样值
uint32_t volRaw = 0, curRaw = 0;
// 定义电压和电流值,单位mV/mA
uint16_t voltage = 0, current = 0;
// 定义功率值,单位毫瓦
uint32_t power = 0;</code></pre>
<p>其中,volRaw、curRaw是通过adc采样得到的值;voltage、current、power则是实际值,也就是采样值乘以系数因子。</p>
<pre><code class="language-c">// 采样电压和电流的ADC值,16倍过采样
volRaw = 0;
curRaw = 0;
for (int i = 0; i < 16; i++)
{
volRaw += BSP_ADC_GetData(VOLTAGE_ADC_CHANNEL);
curRaw += BSP_ADC_GetData(CURRENT_ADC_CHANNEL);
SysTick_Delay_Ms(20);
}
volRaw >>= 4;
curRaw >>= 4;
// 计算实际的电压、电流和功率
voltage = volRaw * volFactor;
current = curRaw * curFactor;
power = current * voltage / 1000;</code></pre>
<p>十六倍采样,也是为了保证采样的稳定性,读取16次的adc采样值,然后除以16,求平均值代表此时的电压、电流值,保证得到的值的稳定性!</p>
<p>剩下的main.c代码就是显示的内容了,这里就没什么好说的了。整体而言,就是哪个系数因子比较难理解!希望对各位有帮助。</p>
<hr>
<h2>实物测试图</h2>
<h2><img src="//image.lceda.cn/pullimage/Iac6C4bcZtb6tkFYx1NHUwnSfrNyhBpFx3KHPcFe.jpeg" alt="16.实物测试图.jpg"></h2>
<h2>所遇问题</h2>
<h3>问题1</h3>
<p>问题1是:焊接完毕后,OLED不亮!</p>
<p>解决办法:官方IIC驱动OLED是<strong>PA4、PA5</strong>,而我设计的这个功率计的IIC驱动OLED引脚分别是:<strong>PB13、PB14</strong>,因此代码需要更改代码。我这里展示的是更改过后的!</p>
<p>更改第一处:MonoScreen.c文件中</p>
<pre><code class="language-c">#define I2Cx I2C1
#define I2Cx_SCL_PIN GPIO_PIN_13
#define I2Cx_SDA_PIN GPIO_PIN_14
#define GPIOx GPIOB</code></pre>
<hr>
<p>更改第二处:MonoScreen.c文件中,关于时钟开启函数以及引脚设置函数,都需要把改成GPIOB。</p>
<h3>问题2</h3>
<p>问题2:改完代码中的引脚定义后,OLED还是不亮。</p>
<p>解决办法:官方代码是采用<strong>芯片的内部高速时钟</strong>作为程序的系统时钟,而我们焊接了外部高速时钟,就会有Bug,解决办法有两种:1、拆卸外部高速时钟;2、该程序代码。</p>
<p>第一种方法我就不讲太多,把晶振和负债电容,拆掉就行!然后OLED会亮。</p>
<p>第二种方法则是用外部高速时钟作为系统时钟,这也是我最后反复测试,最终采用的方法。</p>
<p>方法一我也用了,没问题,但是为了开发板好看,我就还是把外部高速晶振都焊接上了,结果屏幕不亮了,于是我就开始改代码了。</p>
<p>主要就是加入了一行代码而已,具体代码如下所:</p>
<pre><code class="language-c">void SetSysClockToHSE(void)
{
RCC_Reset();
RCC_HSE_Config(RCC_HSE_ENABLE);
HSEStartUpStatus = RCC_HSE_Stable_Wait();
if (HSEStartUpStatus == SUCCESS)
{
FLASH_Prefetch_Buffer_Enable();
if (HSE_Value <= 18000000)
{
FLASH_Latency_Set(FLASH_LATENCY_0);
}
else
{
FLASH_Latency_Set(FLASH_LATENCY_1);
}
RCC_Hclk_Config(RCC_SYSCLK_DIV1);
RCC_Pclk2_Config(RCC_HCLK_DIV1);
RCC_Pclk1_Config(RCC_HCLK_DIV1);
RCC_Sysclk_Config(RCC_SYSCLK_SRC_HSE);
while (RCC_Sysclk_Source_Get() != RCC_CFG_SCLKSTS_HSE)
{
}
}
else
{
while (1)
{
}
}
}</code></pre>
<p>然后把这个函数放在main.c中进行调用即可,然后屏幕就亮啦!</p>
<hr>
<h3>问题3</h3>
<p>问题:上面的尝试都试过,OLED还是不亮。</p>
<p>解决办法:板子虚焊了,多加点焊锡把每个引脚再点了一下,这个我试过,后来没问题。</p>
<hr>
<h3>问题4</h3>
<p>问题:OLED显示的数值不标准,给5V,显示4点几伏特。</p>
<p>解决办法:这里的问题主要是有人的硬件跟官方采用的值不同,导致显示不准,解决办法就是更改程序的系数因子,我在前面已经详细解释了怎么算,这里不再赘述。</p>
<hr>
评论(3)