一切的起源是发现嵌入式课设使用的STM32F746-DISCO开发板自带的LINK很好用。首先它比一般能买到的stlinkv2的SWD速度更快,其次其支持SWO、虚拟串口、虚拟U盘下载,尤其是这个虚拟串口功能,虽然手边的DAP Link同样支持虚拟串口,但是发现 CLion 对其没有原生支持,还需要配置openOCD,这一步非常麻烦。
刚好,ST官网上可以下载到的F746-disco的原理图: https://www.st.com/resource/en/schematic_pack/mb1191-f746ngh6-c03-schematic.pdf
里面可以看到板载stlinkv2.1的原理图,课设结束之后便立即开干。
第一版
既然都自己做link了,肯定不能只做现有的功能,所以我打算再加一个负载检测的功能,参考立创开源平台上的一个USB Hub的项目:
https://oshwhub.com/fanhuacloud/ch334h_dev
其使用INA199A1DCKR放大10mΩ两端的电压来检测电流大小,并且其使用的135*240tft彩屏也很扎眼,大小刚刚合适,一并采用。
主控方面,我选用了目前能买到的最便宜也封装最小的主控,stm32f030f4p6,拥有16kB的FLASH和8kB的RAM,分配完给tft屏幕的引脚,又引出四条adc引脚,对输出电压分压结果、INA电流检测结果进行采样,刚好充分利用所有引脚。
剔除了一部分不使用的stlink功能之后,绘制原理图如下:

刚好手头有一个xh2.54 4p的探针夹子,link本体和f030的烧录可以使用焊盘,没有找到合适的封装,便自己做了一个:

对自己连线功底不够自信所以选用了4层板,中间两层用来共地和配电,顶层底层用来走线,很方便很快得到第一版pcb:

(4层板不能打我心心念念的白色阻焊,只能打绿绿的板子了,不过免费就也不说啥)
板子到了之后立即进行焊接,因为最新的stlinkv2.1固件已经超过了64kB,需要stm32f103cbt6,但是从废弃的芯片框中找到了一些丝印是stm32f103c8t6但是内部FLASH是128kB的芯片,查找之后了解到cbt6和

焊上之后,成功读到芯片,烧录stlinkv2.1固件J28.M18.bin,
通过网盘分享的文件:STLinkV2.J28.M18.bin 链接: https://pan.baidu.com/s/1P9NuIUzoRcOPS6OJajy0vg?pwd=2ygy 提取码: 2ygy
连接电脑后可以弹出虚拟U盘UNDEFINED,打开STLinkUpgrade,可以读到,且可以进行固件升级,至此stlink部分调试完成

接下来焊接电流检测部分全部搞定之后,使用f030的dma连续采样四个通道,采样发现电压可以正常采样,电流一直为0,后来发现使用的INA199放大倍数不对,选用的A1,放大倍数50倍,跨接在10mΩ电阻两端时,可以检测到的最小电流为: $$ \frac{3.3V}{4095 \times 50 \times 10m\Omega}=0.0016117A=1.6mA $$ 显然是精度不够的,考虑到电压检测没问题,就将屏幕粘上了,效果如下:

结果不小心把屏幕摁碎了,好吧……摆烂了,这版bug太多,再做一版好了,把板背的晶振拆下来备用。
第二版
制作第一版出现的首要问题是屏幕驱动的F030的flash太小了,这时发现了一片更合适的片子:STM32G030F6P6,有32kB的FLASH,16kB的RAM,可以实现简单的好看一点的屏幕了。
同时,stlink原先的原理图中有一个电子开关的控制引脚,连接在一片ST890CDR上,这个功能还没有用到,我选用了一个MT9700来实现电子开关的功能。
原理图如下:

因为上次板背放了太多的器件,导致屏幕贴合的不牢固,这次稍微修改将一部分元器件放在正面,并绘制二层板子。
pcb如下:

此次选择的是INA199A3,放大倍数为200倍,选用的采样电阻增加到50mΩ,那么电流检测最小分辨率就是: $$ \frac{3.3V}{4095\times 200 \times 0.05\Omega}=0.00008059A=0.08mA $$ 完全符合预期要求,且最大可以检测到: $$ \frac{3.3V}{200\times 0.05\Omega} = 330mA $$ 考虑到一般电脑的带载能力和目标单片机的静态电流,这个值是完全可以接受的(其实是买的1206的30mΩ的电阻还没到,当然电阻越小,精确度越低,但是检测范围越大)
依旧是先测试link的功能,焊接后没有问题,接下来焊接电压检测和电流检测部分,然后进行程序编写。
使用了四个外部ADC,同时多配置一个通道用来采样G030内部的Vrefint,cubeMX配置如下:

需要确保校准电压准确,所以我们将采样Vrefint的五通道设置为第二个采样定时器,并将其采样周期调到最大160.5Cycles,ADC外设挂载在APB时钟总线上,频率是64MHz,设置分频系数为4,此时1-5通道的采样率可计算为: $$ \frac{64MHz}{4\times 4Channels \times 19.5Cycles+160.5Cycles}\approx 486kSPS $$ 因为是采样直流,所以这个采样率可接受,且不太影响主程序的运行。
通过G030的数据手册可以看到下面内容:

我们主要关心 $$ V_{REFINT}=1.212,T_{Coeff_vrefint}=50ppm/\degree C $$ 这意味着我们可以直接使用1.212来标定我们的ADC的参考电压值。
思路为:我们使用ADC测量Vrefint,因为已知它的电压为1.212V,我们将采样结果反推即可得到ADC的供电电压值,即参考Vref+。代码如下:
adc_flag = 0;
for (j = 0; j < 5; j++) {
adcSum[j] = 0;
}
HAL_ADCEx_Calibration_Start(&hadc1);
for (i = 0; i < 20; i++) {
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcDmaData[0], 5);
while (!adc_flag) {}
for (j = 0; j < 5; j++) {
adcSum[j] += adcDmaData[j];
}
adc_flag = 0;
}
adcSum[4] /= 20;
nowVoltageMv = 4963.14f / (float)adcSum[4]; // VoltageNow = 1.212(typical mv value) * 4095 / adcValue.
但是我们继续阅读手册即可读到ST在每片芯片出厂的时候以3V电压基准测量了内部ref的采样值,存在内部FLASH的一个地址上,这个地址只能读不能写,手册详情如下:

那么根据这个我们就能得到一个比1.212V更加准确的参考值,思路为通过Vref+ = 3.0V和芯片中存储的采样值可以求得内部LDO降压出的电压值在出厂时到底输出的是多大。然后就可以通过这个电压值求得当前实际的Vref+。详细代码如下:
adc_flag = 0;
for (j = 0; j < 5; j++) {
adcSum[j] = 0;
}
HAL_ADCEx_Calibration_Start(&hadc1);
for (i = 0; i < 20; i++) {
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcDmaData[0], 5);
while (!adc_flag) {}
for (j = 0; j < 5; j++) {
adcSum[j] += adcDmaData[j];
}
adc_flag = 0;
}
adcSum[4] /= 20;
uint32_t VREFINT_CAL_ADC = *((uint16_t*)0x1fff75aa);
uint32_t nowVoltageMv = (uint32_t)(3000UL * (uint32_t)VREFINT_CAL_ADC / (uint32_t)adcSum[4]);
当然,这样依旧可能出错,没关系,HAL库也为我们提供了一个宏,专门用来计算当前电压,其定义如下:
/**
* @brief Helper macro to calculate analog reference voltage (Vref+)
* (unit: mVolt) from ADC conversion data of internal voltage
* reference VrefInt.
* @note Computation is using VrefInt calibration value
* stored in system memory for each device during production.
* @note This voltage depends on user board environment: voltage level
* connected to pin Vref+.
* On devices with small package, the pin Vref+ is not present
* and internally bonded to pin Vdda.
* @note On this STM32 series, calibration data of internal voltage reference
* VrefInt corresponds to a resolution of 12 bits,
* this is the recommended ADC resolution to convert voltage of
* internal voltage reference VrefInt.
* Otherwise, this macro performs the processing to scale
* ADC conversion data to 12 bits.
* @param __VREFINT_ADC_DATA__ ADC conversion data (resolution 12 bits)
* of internal voltage reference VrefInt (unit: digital value).
* @param __ADC_RESOLUTION__ This parameter can be one of the following values:
* @arg @ref ADC_RESOLUTION_12B
* @arg @ref ADC_RESOLUTION_10B
* @arg @ref ADC_RESOLUTION_8B
* @arg @ref ADC_RESOLUTION_6B
* @retval Analog reference voltage (unit: mV)
*/
#define __HAL_ADC_CALC_VREFANALOG_VOLTAGE(__VREFINT_ADC_DATA__,\
__ADC_RESOLUTION__) \
__LL_ADC_CALC_VREFANALOG_VOLTAGE((__VREFINT_ADC_DATA__),\
(__ADC_RESOLUTION__))
最终,我们的校准参考电压毫伏值得程序如下:
adc_flag = 0;
adcSum = 0;
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_Delay(1);
for (i = 0; i < 100; i++) {
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcDmaData[0], 5);
while (!adc_flag) {}
adcSum += adcDmaData[4];
adc_flag = 0;
HAL_Delay(1);
}
adcSum /= 100;
nowVoltageMv = __HAL_ADC_CALC_VREFANALOG_VOLTAGE(adcSum, ADC_RESOLUTION_12B);
使用万用表校准,发现与实际值相差不超过10mV,已经满足使用要求。简单试采一下电压,完全没问题,接下来开始采样电流。
之前提到,我们选用的INA199倍数是200,采样电阻是0.05Ω,那么电流结果可按如下计算: $$ I=\frac{Sample\times V_{REF+}}{4095}\times\frac{1}{200\times 50m\Omega}(A) $$ 找几个电阻测试一下,通过测量电压电流值反算电阻值误差不超过10%,完美,驱动一下小屏幕,从github上找一个ST7789V的驱动,稍微移植一下,去掉不需要的图片和字库,对字库重新取模。
参考的仓库:https://github.com/mrudy4/STM32_ST7789V
最后可以得到效果:

对外输出,发现没有电压,仔细一看,完,买的MT9700是高电平使能,STLINK控制引脚使用的是低电平使能,一直拉低对应引脚让MT9700没有输出,好嘛,刮掉铜皮,成功输出。看来还需要修改一版pcb。同时也买一些MT9700L备用,至少这一版在大部分功能都是正常的~