应用)
立创·地奇星RA6E2开发板ADC模块实战从基础配置到温度传感器(TSN)应用最近在用立创·地奇星开发板做一个小型数据采集器发现很多刚接触瑞萨RA6E2的朋友对ADC模数转换器的配置和内部温度传感器的使用有点摸不着头脑。这很正常ADC是连接模拟世界和数字世界的桥梁配置起来确实比点亮一个LED要复杂一些。今天我就以地奇星开发板为例带大家从零开始手把手搞定ADC的配置和编程最后我们还会用它内置的温度传感器来测量芯片温度整个过程我会把原理和操作细节都讲清楚保证你看完就能上手。1. ADC是什么为什么需要它咱们先来聊聊ADC到底是干什么的。我们生活的世界是“模拟”的比如温度的变化、声音的强弱、光线的明暗这些都是连续变化的信号。但单片机MCU是个“数字”家伙它只认识0和1。ADC就像一个翻译官它的工作就是把连续的模拟信号比如一个变化的电压转换成单片机可以理解的数字信号一串二进制数。举个例子你想用单片机测量一个电池的电压。电池电压是模拟量比如3.7V。ADC会把这个电压值“测量”出来并转换成一个数字比如对于3.3V参考电压、12位精度的ADC3.7V可能会被转换成某个接近满量程的数字值。单片机拿到这个数字后就能通过计算知道实际的电压是多少了。地奇星开发板的核心是瑞萨的R7FA6E2BB3CNE微控制器它内部集成了一个性能不错的ADC模块。这个ADC支持三种分辨率12位、10位和8位。位数越高精度就越高能把电压分得更细但转换速度可能会慢一点。你需要根据实际应用来权衡选择。几个关键参数你得知道分辨率就是ADC的“尺子”有多精细。12位分辨率意味着它能把参考电压分成 2^12 4096 份。如果参考电压是3.3V那么每一份也就是1个LSB就代表大约 3.3V / 4096 ≈ 0.0008V (0.8mV)。这个值越小测量就越准。采样率指ADC每秒能进行多少次转换。地奇星开发板的ADC转换时钟ADCLK源是PCLKC最大频率50MHz。根据分频设置每通道的转换时间最快可以达到0.52微秒us。采样范围ADC能测量的电压范围。对于地奇星开发板这个范围是VREFH0 ≥ 输入电压 ≥ VREFL0。在原理图上VREFL0接了地0VVREFH0接了VDDA模拟电源3.3V。所以你能测量的电压范围就是0V到3.3V。千万别输入超过3.3V的电压可能会损坏芯片2. 手把手配置ADC模块FSP配置篇理论说完了咱们动动手。地奇星的开发环境是瑞萨的e2 studio配合FSP灵活配置软件包来配置外设非常方便。这里假设你已经建好了一个基础工程比如从串口工程复制过来的。2.1 引脚与堆栈配置首先打开工程的FSP Configuration视图。配置引脚功能切换到Pins标签页。在Peripherals下找到Analog: ADC-ADC0。在这里你需要把要用作模拟输入的普通IO口配置为ADC功能。比如你想用通道0AN000就找到对应的引脚例如P000将其功能选择为AN000。添加ADC驱动堆栈切换到Stacks标签页。点击New Stack-Analog- 选择ADC (r_adc)。这会在你的工程中添加一个ADC的驱动模块我们后续的代码会调用它提供的API函数。2.2 关键参数详解添加完堆栈后点击新出现的g_adc0模块打开它的属性进行详细配置。这里有几个关键设置我结合自己的经验给你讲讲Operation Mode选择Custom这样我们才能自由配置所有参数。Parameter Checking建议先保持Default等项目调试稳定后再考虑设为Bypass以节省一点代码空间和运行时间。General通用设置UnitADC单元号就选0。Resolution分辨率。根据你的需求选追求精度就选12位追求速度可以选10或8位。咱们实验先用12位。Alignment采样结果对齐方式。选Right右对齐最常用也最直观。Clear after read读数据后是否清空寄存器。建议选Enabled避免读到旧数据。Mode工作模式这是重点Single Scan单次扫描每次触发软件或硬件进行一次指定通道的转换转换完就停止。适合非连续、按需采集的场景。Continuous Scan连续扫描触发一次后ADC会不停地循环扫描设定的通道直到你用R_ADC_ScanStop()函数让它停止。适合需要持续监控的场合。Group Scan分组扫描可以把通道分成A、B两组分别配置触发源和扫描顺序更灵活。咱们第一次用先从简单的单次扫描开始。Double-trigger双触发使能。先保持关闭高级应用再用。Input输入设置Channel Scan Mask通道扫描掩码。在这里勾选你想要ADC去采集的模拟输入通道。在单次或连续扫描模式下这里勾选的通道会被依次转换。比如你勾选了通道0和通道1ADC就会先转换AN000再转换AN001。Interrupts中断设置Normal/Group A Trigger选择触发ADC开始转换的方式。有三种Software软件触发在代码里调用R_ADC_ScanStart()函数来启动转换。最常用也最简单。External外部触发通过特定的外部引脚ADTRG0输入一个上升沿或下降沿信号来触发转换适合与外部事件同步。ELC事件链接控制器用芯片内部其他外设如定时器产生的事件来触发不需要CPU干预非常高效。Callback回调函数名。当一次扫描所有选定通道转换完成时会产生一个中断并自动调用这个函数。我们填adc_callback后面需要自己实现这个函数。Scan End Interrupt Priority扫描结束中断的优先级。根据系统整体中断情况设置一个合适的优先级。配置完成后记得按CtrlS保存然后点击Generate Project Content按钮生成代码。FSP会根据你的配置自动在工程里生成相应的初始化代码和驱动实例g_adc0_ctrl,g_adc0_cfg等我们直接调用就行。3. 编写代码读取ADC电压值配置好了接下来就是写代码了。为了工程结构清晰我习惯把不同外设的驱动代码分开放。3.1 创建工程文件结构在src目录下我新建了两个文件夹adc里面放ADC的驱动文件bsp_adc.c和bsp_adc.h。Apply里面放应用层的主逻辑文件app.c和app.h。你的工程目录看起来应该是这样的你的工程/ ├── src/ │ ├── adc/ │ │ ├── bsp_adc.c │ │ └── bsp_adc.h │ ├── Apply/ │ │ ├── app.c │ │ └── app.h │ └── hal_entry.c (自动生成的主入口文件) └── 其他工程文件...3.2 驱动层代码 (bsp_adc.c / bsp_adc.h)先看头文件bsp_adc.h声明我们提供的函数#ifndef __BSP_ADC_H_ #define __BSP_ADC_H_ #include hal_data.h #include stdint.h // ADC初始化函数 void ADC_Init(void); // 读取ADC值并转换为电压的函数 double ADC_read_Value(void); #endif然后是核心的实现文件bsp_adc.c。这里我写了一个完整的、带错误处理的版本#include bsp_adc.h #include r_adc_api.h // ADC的API头文件 #include hal_data.h #include stdio.h // 定义一个标志位用于在回调函数中通知主循环转换完成 volatile bool g_adc_scan_complete_flag false; // ADC初始化函数 void ADC_Init(void) { fsp_err_t err FSP_SUCCESS; // 用于接收FSP API的返回状态 // 1. 打开ADC设备 err R_ADC_Open(g_adc0_ctrl, g_adc0_cfg); if (FSP_SUCCESS ! err) { printf(错误ADC初始化失败! 错误码: %d\r\n, err); return; // 初始化失败直接返回 } // 2. 配置ADC扫描关联通道配置 err R_ADC_ScanCfg(g_adc0_ctrl, g_adc0_channel_cfg); if (FSP_SUCCESS ! err) { printf(错误ADC扫描配置失败! 错误码: %d\r\n, err); R_ADC_Close(g_adc0_ctrl); // 关闭已经打开的ADC return; } printf(ADC初始化成功\r\n); } // ADC扫描完成回调函数 // 当我们在FSP里配置了中断和回调函数名后这个函数会在转换完成时被自动调用 void adc_callback(adc_callback_args_t *p_args) { // 这个参数p_args可能包含事件信息我们这个简单例子先不用 FSP_PARAMETER_NOT_USED(p_args); // 避免编译器警告 // 置位标志位告诉主循环“我转换完啦” g_adc_scan_complete_flag true; } // 读取ADC通道0的值并计算为电压 double ADC_read_Value(void) { uint16_t adc_raw_data 0; // 存放ADC读出的原始数字值0-4095 double voltage 0.0; // 计算后的电压值 // 1. 启动一次ADC扫描我们配置的是软件触发 fsp_err_t err R_ADC_ScanStart(g_adc0_ctrl); if (FSP_SUCCESS ! err) { printf(错误ADC扫描启动失败! \r\n); return -1.0; // 返回一个错误值 } // 2. 等待转换完成通过回调函数设置的标志位 // 注意在实际产品代码中最好用超时机制避免死等 while (false g_adc_scan_complete_flag) { // 这里可以插入一些其他任务或者简单的延时 } g_adc_scan_complete_flag false; // 清除标志为下一次读取做准备 // 3. 读取ADC数据 // ADC_CHANNEL_0 对应我们之前在Channel Scan Mask里勾选的通道0 err R_ADC_Read(g_adc0_ctrl, ADC_CHANNEL_0, adc_raw_data); if (FSP_SUCCESS ! err) { printf(错误ADC数据读取失败!\r\n); return -1.0; } // 4. 将原始值转换为电压值 // 公式电压 (原始值 / 最大数字量) * 参考电压 // 12位分辨率最大数字量是 2^12 - 1 4095 // 参考电压VREFH0我们接的是3.3V voltage ((double)adc_raw_data * 3.3) / 4095.0; return voltage; }注意代码中的g_adc0_ctrl,g_adc0_cfg,g_adc0_channel_cfg这些变量都是FSP根据你的配置自动生成的全局实例直接在代码里用就行。3.3 应用层代码 (app.c / app.h)应用层就很简单了主要是调用我们写好的驱动。app.h:#ifndef __APP_H #define __APP_H #include hal_data.h #include stdio.h void Run(void); // 主运行函数 #endifapp.c:#include Apply/app.h #include adc/bsp_adc.h void Run(void) { // 初始化调试串口假设你已经有一个bsp_uart.c的驱动 Debug_UART0_Init(); // 初始化ADC ADC_Init(); printf(欢迎使用立创·地奇星RA6E2开发板\r\n); printf(ADC电压采集实验开始\r\n); while (1) { // 读取电压并打印 double current_voltage ADC_read_Value(); printf(ADC通道0电压 %.3f V\r\n, current_voltage); // 延时1秒 R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); } }最后在自动生成的hal_entry.c文件中调用我们的Run()函数#include hal_data.h #include Apply/app.h void hal_entry(void) { Run(); // ... 其他安全构建相关的代码 }3.4 实验结果与调试编译下载程序后打开串口助手波特率要和你的调试串口配置一致你应该能看到每秒打印一次的电压值。常见问题串口没打印检查串口初始化代码和接线。在e2 studio的Debug Configurations里确保Debugger-Startup标签页下的Run to main被勾选这样程序才能正确运行到你的main或hal_entry。电压值不对或为0检查你的模拟输入引脚比如P000/AN000是否正确连接到了要测量的电压源。确认电压源在0-3.3V之间。在FSP配置中再次确认Channel Scan Mask里勾选了对应的通道例如Channel 0。检查参考电压VREFH0是否确实为3.3V。4. 进阶实战使用内部温度传感器(TSN)RA6E2内部集成了一个温度传感器TSN可以用来测量芯片本身的温度对于监测系统工作状态很有用。它的原理是芯片内部的温度变化会引起一个PN结的电压变化ADC通过测量这个电压再根据公式换算成温度。4.1 硬件与公式原理使用TSN不需要接任何外部电路它已经连接到ADC的一个特殊内部通道。关键在于换算公式。根据瑞萨的用户手册温度计算公式如下T (Vs - V1) / Slope T1其中T我们要计算的当前温度°C。Vs温度传感器在当前温度下输出的电压值V这就是ADC读到的原始值转换后的电压。V1在某个已知温度T1时传感器输出的电压V。T1那个已知的温度°C。Slope温度传感器的“斜率”即温度每变化1°C输出电压变化多少伏V/°C。芯片出厂时已经在特定条件下通常是T1127°C对传感器进行了校准并将一个校准值CAL127存储在芯片的特定只读区域。这个CAL127对应的就是ADC在127°C时读到的原始数字值不是电压。同时手册给出该温度传感器的典型斜率Slope是0.004 V/°C即4mV/°C。所以我们的计算步骤就变成了让ADC去读取内部温度传感器通道得到原始值adc_raw。从芯片中获取出厂校准值CAL127。将两者都转换为电压值或直接使用原始值进行计算因为公式中电压差与原始值差成正比。代入公式温度 ( (adc_raw - CAL127) * 3.3 / 4096 ) / 0.004 1274.2 FSP配置修改在之前的ADC配置基础上只需要修改一个地方在FSP配置界面找到g_adc0的Input设置。将Channel Scan Mask中原来勾选的普通通道如Channel 0取消。勾选上Temperature Sensor通道。保存并重新生成工程代码。4.3 代码修改我们只需要修改bsp_adc.c中的ADC_read_Value函数让它从读取电压变为读取温度。其他初始化部分完全一样。double ADC_read_Value(void) // 这个函数现在用来读温度了 { uint16_t adc_raw_data 0; int32_t cal127_value 0; adc_info_t adc_info; double slope 0.004; // 斜率单位 V/°C double temperature 0.0; fsp_err_t err FSP_SUCCESS; // 1. 启动ADC扫描扫描温度传感器通道 err R_ADC_ScanStart(g_adc0_ctrl); if (FSP_SUCCESS ! err) { printf(错误ADC扫描启动失败! \r\n); return -999.0; } // 2. 等待转换完成 while (false g_adc_scan_complete_flag) { // 等待 } g_adc_scan_complete_flag false; // 3. 读取ADC数据 // 注意通道号要改为 ADC_CHANNEL_TEMPERATURE err R_ADC_Read(g_adc0_ctrl, ADC_CHANNEL_TEMPERATURE, adc_raw_data); if (FSP_SUCCESS ! err) { printf(错误ADC温度数据读取失败!\r\n); return -999.0; } // 4. 获取芯片出厂时存储的校准信息 err R_ADC_InfoGet(g_adc0_ctrl, adc_info); if (FSP_SUCCESS ! err) { printf(错误ADC校准数据获取失败!\r\n); return -999.0; } // 从信息结构体中取出127°C时的校准原始值 cal127_value (int32_t)adc_info.calibration_data; // 5. 套用公式计算温度 // 公式: T [ (adc_raw - CAL127) * Vref / 4096 ] / Slope 127 // Vref是3.3V Slope是0.004 V/°C temperature ((double)(adc_raw_data - cal127_value) * 3.3 / 4096.0) / slope 127.0; return temperature; }提示R_ADC_InfoGet这个API非常有用它除了能获取温度校准值还能拿到ADC模块的其他信息。adc_info.calibration_data就是我们在公式里需要的CAL127。修改完代码后编译下载。再次打开串口助手你现在看到的就是芯片的实时温度了。用手触摸芯片注意防静电可以看到温度值逐渐上升。这个功能非常适合用来做产品的过热保护或者环境温度监测。好了关于地奇星RA6E2的ADC基础使用和内部温度传感器应用我就先分享到这里。最关键的是理解ADC的配置流程FSP里配引脚、选通道、设模式、选触发源代码里按“打开-配置-启动-等待-读取”这个顺序来操作。内部温度传感器的使用核心在于理解那个换算公式并找到正确的校准值。多动手试几次遇到问题仔细检查配置和接线你一定能熟练掌握这个嵌入式开发中最常用的模块之一。