很多stm32芯片里往往内置了专用的adc通道,比方用来测量vrefint,vbat的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以stm32g4系列为例,它内置了对应于vrefint,vbat的三分之一分压和温度传感器的输出电压的专用模拟通道。
下面的示例就是针对上述3个通道进行ad,并测量相关电压和片内温度,最终得到3个结果,分别是vrefint电压,vbat的电压,片内温度。
实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道】
1、timer1 更新事件触发adc的转换;
2、cpu基于eoc中断获取adc结果;
3、对adc结果进行换算,得到电压值和温度值存放在特定内存位置;
4、基于dma传输通过uart将最终结果在串口终端显示;
其中,timer1的ch1输出pwm波形,其更新事件做adc的转换启动信号。每次的timer更新事件触发adc,3个通道扫描方式转换。这里的uart使用片内lpuart,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
我使用stm32g474nucleo板来进行下面实验。其中vdd=3.3v,vbat与vdd相连。另外,adc模块的参考电压也是3.3v.
使用cubemx图形化工具进行配置,先看timer配置:
再看看adc的基本配置:
lpuart的基本配置:
因为要使用adc中断和uart的dma传输,记得做adc的中断响应使能配置和lpuart的dma配置,这里只使用uart的tx dma功能。
使用cubemx主要配置主要是上面这些。
在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。st公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定flash位置。
stm32g4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。
针对这个温度传感器的使用,st公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【ts_data代表某时刻测得的传感器输出电压对应的转换值,ts_cal1/ts_cal2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
另外,前面提过,st公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的adc模块所用参考电压跟我们实际应用时ad模块所用的参考基准电压可能不一致。如果不一致,就必须将adc值换算成同一基准参考电压条件下的数据。目前在st手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。
关于这点,我们也不难理解。同一待测信号、同一adc模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
完成各项配置后,创建软件工程。添加必需的用户代码:
#define tx_timeout (9999)#define ts_cal1_addr (0x1fff75a8) //用于计算温度传感器数据#define ts_cal2_addr (0x1fff75ca) //用于计算温度传感器数据#define size1 (40)char wdvol[size1],batvol[size1],invol[size1];uint16_t ts_c30,ts_c110;uint16_t adcresult[3],convcnt;volatile uint32_t completed,endofcon_flag;float vbatvolt;//存放bbat电压最终结果float vrefint; //存放vrefint电压最终结果float temperature;//存放片外温度℃最终结果int main(void){/* user code begin 1 *//* user code end 1 *//* mcu configuration--------------------------------------------------------*//* reset of all peripherals, initializes the flash interface and the systick. */ hal_init();/* user code begin init *//* user code end init *//* configure the system clock */ systemclock_config();/* user code begin sysinit *//* user code end sysinit *//* initialize all configured peripherals */ mx_gpio_init(); mx_dma_init(); mx_adc1_init(); mx_lpuart1_uart_init(); mx_tim1_init();/* user code begin 2 */ ts_c30 = *(uint16_t *)(ts_cal1_addr); //读取30℃时的adc校准值 ts_c110 = *(uint16_t *)(ts_cal2_addr);//读取110℃时的adc校准值 hal_adcex_calibration_start(&hadc1 , adc_single_ended);//adc校准 hal_adc_start_it(&hadc1);//启动adc并开启转换中断 hal_tim_pwm_start(&htim1, tim_channel_1);/* user code end 2 *//* infinite loop *//* user code begin while */while (1) {/* user code end while *//* user code begin 3 */if (endofcon_flag!=0) { vbatvolt=(adcresult[0]/4095.)* 3.3 * 3.; vrefint=(adcresult[1]/4095.) * 3.3; temperature = 30.+ (88.*(adcresult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30); endofcon_flag=0;//hal_uart_transmit(&hlpuart1, (uint8_t *)wdvol ,sizeof(wdvol), tx_timeout); hal_gpio_writepin( gpioc,gpio_pin_3,gpio_pin_reset); //for auxiliary test hal_uart_transmit_dma(&hlpuart1, (uint8_t *)wdvol ,sizeof(wdvol));while(completed==0){} completed =0; //hal_uart_transmit(&hlpuart1, (uint8_t *)invol ,sizeof(invol), tx_timeout); hal_uart_transmit_dma(&hlpuart1, (uint8_t *)invol ,sizeof(invol));while(completed==0) {} completed =0;//hal_uart_transmit(&hlpuart1, (uint8_t *)batvol ,sizeof(batvol), tx_timeout); hal_uart_transmit_dma(&hlpuart1, (uint8_t *)batvol ,sizeof(batvol)); while(completed==0){} completed =0; hal_gpio_writepin( gpioc,gpio_pin_3,gpio_pin_set); //for auxiliary test } }/* user code end 3 */}//adc eoc 中断处理函数void hal_adc_convcpltcallback(adc_handletypedef *hadc){ adcresult[convcnt]=hal_adc_getvalue(&hadc1); //获取转换结果并存入数组 convcnt++;if(convcnt==3) { convcnt=0; endofcon_flag=0xff;sprintf(wdvol,internal pn temperature: %5.3f ,temperature); sprintf(invol,internal reference volt: %5.3f ,vrefint); sprintf(batvol,current battery volt: %5.3f ,vbatvolt);}}//uart dma 传输完成处理函数void hal_uart_txcpltcallback(uart_handletypedef *huart){ completed=0xff;}
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发adc,每得到3个adc结果就进行数据处理,然后通过uart以dma方式传输到串口终端。注意vbat电压是测量结果再乘以3得到的。
针对上面的应用演示,最后给几点相关应用提醒:
1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。
2、使用timer的trgo触发adc,如果选择类似比较事件、更新事件来触发adc时,此时adc对触发极性的选择是无效的,或者说adc的转换仅依赖于触发事件时间点。如果是选择timer的ocref信号作为触发源,此时adc的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《stm32定时器触发adc的时序话题》】
3、这里使用uart的dma传输依次显示三个结果于串口终端,三个启动uart dma传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一dma通道。否则没法全部正常输出。比如上面3次uart dma传输的代码改成下面这样子:
hal_uart_transmit_dma(&hlpuart1, (uint8_t *)wdvol ,sizeof(wdvol)); hal_uart_transmit_dma(&hlpuart1, (uint8_t *)invol ,sizeof(invol)); hal_uart_transmit_dma(&hlpuart1, (uint8_t *)batvol ,sizeof(batvol))
这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的dma传输结果。
如果想省事点,直接在相邻2次dma传输间加上合适延时也行。我这里根据dma传输完成事件来决定执行下一次发送。在dma传输完成中断里设置completed变量为非0值表示当前一轮dma传输完成。
4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。
下图的三路波形是我调试时辅助使用的。
第一路表示计数器的计数变化,显然是单向向上计数模式。
第二路是timer1通道1的pwm输出波形。
第三路是我每次基于dma实现uart发送时拉高拉低的波形。平常管脚电平为高,在实现dma传输过程中拉低。
好,今天的分享就到这里,供君参考。下次再聊。
Surface Duo充电端口周围的塑料出现了裂缝
详细解读CAN总线原理和数据帧问题
恩智浦推出业界首款全新超快速I2C总线控制器
收发一体芯片TRF7970A的特点性能及的NFC模块设计介绍
德新交运拟以现金6.9亿元收购东莞致宏精密100%股权
基于STM32片内信号的ADC应用演示案例
利用ATmega16L设计的温度控制系统
我国宣布从文昌航天发射中心成功发射“长征5号”运载火箭
从有效到高效,UniPro特色功能帮研发团队加强沟通协作
MPL 物理层技术简介
DRV8x马达驱动方案驱动能力高至60V/60A
消息称苹果发布汽车招聘岗位,秘密组建造车团队
AMD Infinity Fabric升级后可支持CPU-GPU之间的连接
电子围栏日常运行、检修及故障排除
无磁芯变压器(CT)隔离驱动芯片技术优势及产品系列
起火事件频发,新能源汽车的未来该由谁决定?
嵌入式设计需要注意什么问题
关于非洲猪瘟荧光层析定量分析仪的原理及性能
空气开关十大品牌排行榜
中国电信近日宣布:成立一家全资子公司!