电路分析
LaunchPad上有一个光电二极管(photodiode,PD)可以用来当作光传感器。
光电二极管在光线照射下,会产生很小的光电流,通常需要运放搭建跨阻放大器(TIA)将电流信号放大,并转变成便于ADC采集的电压信号。
跨阻放大器结构如下图所示:
接下来分析引脚排布。LaunchPad上搭载的芯片型号是MSPM0L1306SRHB,是32 VQFN封装。
PA22对应OPA0输出(OPA0_OUT),
PA24和PA25分别对应OPA0的反相和同相输入。
查阅OPA的方框图:
刚好可以用OPA0来实现跨阻放大器。
再查阅器件模拟连接图(在datasheet的8.24节),可以发现ADC0的12通道,对应了OPA0的输出。因此可以直接用ADC去采集这个放大后的电压信号。
软件设计
使用adc_to_uart模板,将adc转换结果输出到串口。
SysConfig工具需要重点关注OPA和ADC12。
ADC12模块需要选择输入通道为12,界面上会自动提示是OPA0 output
代码部分反而没有特别困难的地方,基本可以沿用热敏电阻检测的代码,将ADC的值输出到串口。
运行结果:
拉上窗帘,亮度比较低,ADC数值也较低:
拉开窗帘,亮度比较高,ADC数值也较高:
小结
善用片内模拟外设,特别是OPA和ADC联用,可以几乎不需要外部模拟器件,就可以搭建跨阻放大器等常见的运放基本电路。
跨阻放大器检测光电二极管的电流信号,除了用于环境光检测,还有很多用途。
例如在医学上,指夹式脉搏血氧仪(在2023年初抢购到断货的商品)的模拟接收部分就是这个结构,TI还给了一个参考设计。
https://www.ti.com.cn/cn/lit/ml/zhct423/zhct423.pdf
完整代码
C程序
#include "stdio.h"
#include "ti_msp_dl_config.h"
// #include <math.h>
volatile bool gCheckADC;
volatile uint16_t gADCResult;
int main(void) {
SYSCFG_DL_init();
NVIC_EnableIRQ(ADC12_0_INST_INT_IRQN);
gCheckADC = false;
while (1) {
DL_ADC12_startConversion(ADC12_0_INST);
while (false == gCheckADC) {
__WFE();
}
gADCResult = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
printf("Light ADC=%d\n", gADCResult);
gCheckADC = false;
DL_ADC12_enableConversions(ADC12_0_INST);
}
}
void ADC12_0_INST_IRQHandler(void) {
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST)) {
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
gCheckADC = true;
break;
default:
break;
}
}
SysConfig参考代码
/**
* These arguments were used when this file was generated. They will be automatically applied on subsequent loads
* via the GUI or CLI. Run CLI with '--help' for additional information on how to override these arguments.
* @cliArgs --device "MSPM0L130X" --package "VQFN-32(RHB)" --part "Default" --product "mspm0_sdk@1.20.00.06"
* [url=home.php?mod=space&uid=304333]@versions[/url] {"tool":"1.18.0+3266"}
*/
/**
* Import the modules used in this configuration.
*/
const ADC12 = scripting.addModule("/ti/driverlib/ADC12", {}, false);
const ADC121 = ADC12.addInstance();
const Board = scripting.addModule("/ti/driverlib/Board");
const OPA = scripting.addModule("/ti/driverlib/OPA", {}, false);
const OPA1 = OPA.addInstance();
const SYSCTL = scripting.addModule("/ti/driverlib/SYSCTL");
const UART = scripting.addModule("/ti/driverlib/UART", {}, false);
const UART1 = UART.addInstance();
/**
* Write custom configuration values to the imported modules.
*/
ADC121.$name = "ADC12_0";
ADC121.enabledInterrupts = ["DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED"];
ADC121.sampleTime0 = "125 us";
ADC121.sampClkSrc = "DL_ADC12_CLOCK_ULPCLK";
ADC121.sampClkDiv = "DL_ADC12_CLOCK_DIVIDE_8";
ADC121.adcMem0chansel = "DL_ADC12_INPUT_CHAN_12";
ADC121.peripheral.$assign = "ADC0";
OPA1.$name = "OPA_0";
OPA1.cfg0PSELChannel = "IN0_POS";
OPA1.cfg0NSELChannel = "IN0_NEG";
OPA1.cfg0OutputPin = "ENABLED";
OPA1.cfg0Gain = "N1_P2";
OPA1.In0PosPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric2";
OPA1.In0NegPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric3";
OPA1.OutPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric4";
UART1.$name = "UART_0";
UART1.enableFIFO = true;
UART1.direction = "TX";
UART1.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY";
UART1.targetBaudRate = 115200;
UART1.peripheral.$assign = "UART0";
UART1.peripheral.txPin.$assign = "PA8";
UART1.txPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric1";
/**
* Pinmux solution for unlocked pins/peripherals. This ensures that minor changes to the automatic solver in a future
* version of the tool will not impact the pinmux you originally saw. These lines can be completely deleted in order to
* re-solve from scratch.
*/
Board.peripheral.$suggestSolution = "DEBUGSS";
Board.peripheral.swclkPin.$suggestSolution = "PA20";
Board.peripheral.swdioPin.$suggestSolution = "PA19";
OPA1.peripheral.$suggestSolution = "OPA0";
OPA1.peripheral.In0PosPin.$suggestSolution = "PA25";
OPA1.peripheral.In0NegPin.$suggestSolution = "PA24/OPA0.IN0-";
OPA1.peripheral.OutPin.$suggestSolution = "PA22";
SYSCTL.peripheral.$suggestSolution = "SYSCTL";