目录
一 项目背景
目前使用了TI的ADC采样芯片ADS1018实现模拟量4-20mA/0-20mA的采样,原理是将外部输入的模拟量信号4-20mA,经由并联的两个100Ω电阻,转换为0.2-1V的电压信号传递到模数转换芯片的入口,再由ADS1018(全量程设置为FS=±1.024V,对应码为0x7FF)将0.2-1V电压信号转换为数据码0x190-0x7D0(400-2000),即可以将外部输入4-20mA与内部采集数据400-2000对应上,如下图所示:
但是限于成本和货期的问题,考虑将该款ADC换成国产的其他方案。
又因为这边AD采样的是DCS或者PLC发出来的4-20mA/0-20mA信号,必须需要外加隔离,所以不能采用MCU自带的ADC外设直接采样的方案(MCU的ADC外设没有隔离功能,必须外接隔离芯片才行),所以该方案舍弃。
找到一款客益电子(http://www.guestgood.com/)的APC/PAC芯片,可以将输入的模拟信号转化为PWM波的占空比输出,这边选用的APC芯片为GP9101。
二 原理说明
【1】APC/PAC芯片原理:
A=Analog,P=PWM,C=Convertor。
APC=Analog to PWM Convertor 是一种模拟信号转PWM信号的专用芯片,PAC=PWM to Analog Convertor是一种PWM信号转模拟信号的专用芯片。
在信号调理领域,经常需要面对模拟量信号的传输、采集、控制等问题,传统的信号链芯片包括模数转换器(ADC)、数模转换器(DAC)、运算放大器(OpAmp)、比较器(Comparator)等等,它们扮演着模数混合信号处理的主要角色。信号链芯片的功能基础而强大,经过精心的设计后能形成多种多样优秀的信号处理电路。但即便如此,在很多应用领域,传统的信号链芯片依然存在瓶颈和制约,无法达到理想的电路性能和技术指标,尤其在一些需要PWM信号的领域,传统的方法遇到许多困难。
客益电子发明了一种新型的模拟信号处理的专用芯片,它实现了模拟信号与PWM信号间的高精度转换功能,我们称它为APC(Analog to PWM Convertor)和PAC(PWM to Analog Convertor)。
【2】芯片特性:
这边采用的芯片GP9101将输入0-VCC的信号转化为占空比0-100%,频率默认为1KHz的PWM波输出:
【3】采样原理:
参考上面ADC采样的思路,将外部输入的模拟量信号4-20mA,经由并联的两个400Ω电阻,转换为0.8-4V的电压信号传递到APC芯片的入口,再由GP9101(供电Vcc=5V)将0.8-4V电压信号转换为占空比为16-80%的PWM交由MCU进行捕获,即可以将外部输入4-20mA与内部采集数据对应上,如下图所示:
三 设计实现——定时器初始化
由上面的方案,首先需要实现的是定时器捕获PWM功能的初始化,我这边使用的主控芯片为小华的HC32F460,选用其高级控制定时器Timer6进行PWM捕获,端口选用PB13,其功能是TIMER6_1_PWMB(Timer6的1单元B通道):
初始化程序如下:
/* TIMER6 unit and clock definition */
#define TIMER6_UNIT1 (M4_TMR61)
#define TIMER6_UNIT1_CLOCK (PWC_FCG2_PERIPH_TIM61)
/* TIMER6 channel B Port/Pin definition */
#define TIMER6_UNIT1_CHB (Timer6GenCompareB)
#define TIMER6_UNIT1_CHB_PORT (PortB)
#define TIMER6_UNIT1_CHB_PIN (Pin13)
#define TIMER6_UNIT1_CHB_FUNC (Func_Tim6)
void Timer6_APC_Config(void) //APC测试,采样0.8V-4V电压,APC输出16%-80%占空比,经过64分频定时器捕获,输出比较值为420-2100(输入K/B需要调整为2100/400)
{
stc_timer6_basecnt_cfg_t stcTIM6BaseCntCfg;
stc_timer6_port_input_cfg_t stcTIM6CapxCfg;
MEM_ZERO_STRUCT(stcTIM6BaseCntCfg);
MEM_ZERO_STRUCT(stcTIM6CapxCfg);
PWC_Fcg2PeriphClockCmd(TIMER6_UNIT1_CLOCK, Enable);
PORT_SetFunc(TIMER6_UNIT1_CHB_PORT, TIMER6_UNIT1_CHB_PIN, TIMER6_UNIT1_CHB_FUNC, Disable); //Timer61 PWMA
stcTIM6BaseCntCfg.enCntMode = Timer6CntSawtoothMode; //Sawtooth wave mode
stcTIM6BaseCntCfg.enCntDir = Timer6CntDirUp; //Counter counting up
stcTIM6BaseCntCfg.enCntClkDiv = Timer6PclkDiv64; //Count clock: pclk0/64(256分频也可以试一试)
Timer6_Init(TIMER6_UNIT1, &stcTIM6BaseCntCfg); //timer6 PWM frequency, count mode and clk config
Timer6_SetPeriod(TIMER6_UNIT1, Timer6PeriodA, 0xFFFFu); //Period set
stcTIM6CapxCfg.enPortSel = Timer6xCHB; //Capture Input Port: PWM B port
stcTIM6CapxCfg.enPortMode = Timer6ModeCaptureInput; //Capture input function
stcTIM6CapxCfg.bFltEn = true; //Input filter enable
stcTIM6CapxCfg.enFltClk = Timer6FltClkPclk0Div16; //Filter clock
Timer6_PortInputConfig(TIMER6_UNIT1, &stcTIM6CapxCfg); //Input config
Timer6_ConfigHwCaptureB(TIMER6_UNIT1, Timer6HwTrigPWMBRise); //HW Capture: Timer6 PWMB port rise trig
Timer6_ConfigHwClear(TIMER6_UNIT1, Timer6HwTrigPWMBFall); //HW Clear: Timer6 PWMB port fall trig
Timer6_EnableHwClear(TIMER6_UNIT1);
/*start timer6*/
Timer6_StartCount(TIMER6_UNIT1);
}
【注】也可以使用中断进行捕获:
/* TIMER6 unit and clock definition */
#define TIMER6_UNIT1 (M4_TMR61)
#define TIMER6_UNIT1_CLOCK (PWC_FCG2_PERIPH_TIM61)
/* TIMER6 channel B Port/Pin definition */
#define TIMER6_UNIT1_CHB (Timer6GenCompareB)
#define TIMER6_UNIT1_CHB_PORT (PortB)
#define TIMER6_UNIT1_CHB_PIN (Pin13)
#define TIMER6_UNIT1_CHB_FUNC (Func_Tim6)
uint16_t u16CaptureA;
void Timer61_CapInputCallBack(void)
{
u16CaptureA = Timer6_GetGeneralCmpValue(TIMER6_UNIT1, TIMER6_UNIT1_CHB);
}
void Timer6_APC_Config(void) //APC测试,采样0.8V-4V电压,APC输出16%-80%占空比,经过64分频定时器捕获,输出比较值为420-2100(输入K/B需要调整为2100/400)
{
stc_timer6_basecnt_cfg_t stcTIM6BaseCntCfg;
stc_timer6_port_input_cfg_t stcTIM6CapxCfg;
stc_irq_regi_conf_t stcIrqRegiConf;
MEM_ZERO_STRUCT(stcTIM6BaseCntCfg);
MEM_ZERO_STRUCT(stcTIM6CapxCfg);
MEM_ZERO_STRUCT(stcIrqRegiConf);
PWC_Fcg2PeriphClockCmd(TIMER6_UNIT1_CLOCK, Enable);
PORT_SetFunc(TIMER6_UNIT1_CHB_PORT, TIMER6_UNIT1_CHB_PIN, TIMER6_UNIT1_CHB_FUNC, Disable); //Timer61 PWMA
stcTIM6BaseCntCfg.enCntMode = Timer6CntSawtoothMode; //Sawtooth wave mode
stcTIM6BaseCntCfg.enCntDir = Timer6CntDirUp; //Counter counting up
stcTIM6BaseCntCfg.enCntClkDiv = Timer6PclkDiv64; //Count clock: pclk0/64(256分频也可以试一试)
Timer6_Init(TIMER6_UNIT1, &stcTIM6BaseCntCfg); //timer6 PWM frequency, count mode and clk config
Timer6_SetPeriod(TIMER6_UNIT1, Timer6PeriodA, 0xFFFFu); //Period set
stcTIM6CapxCfg.enPortSel = Timer6xCHB; //Capture Input Port: PWM B port
stcTIM6CapxCfg.enPortMode = Timer6ModeCaptureInput; //Capture input function
stcTIM6CapxCfg.bFltEn = true; //Input filter enable
stcTIM6CapxCfg.enFltClk = Timer6FltClkPclk0Div16; //Filter clock
Timer6_PortInputConfig(TIMER6_UNIT1, &stcTIM6CapxCfg); //Input config
Timer6_ConfigHwCaptureB(TIMER6_UNIT1, Timer6HwTrigPWMBRise); //HW Capture: Timer6 PWMB port rise trig
Timer6_ConfigHwClear(TIMER6_UNIT1, Timer6HwTrigPWMBFall); //HW Clear: Timer6 PWMB port fall trig
Timer6_EnableHwClear(TIMER6_UNIT1);
/*config interrupt*/
Timer6_ConfigIrq(TIMER6_UNIT1, Timer6INTENB, true);
stcIrqRegiConf.enIRQn = Int003_IRQn; //Register INT_TMR61_GUDF Int to Vect.No.002
stcIrqRegiConf.enIntSrc = INT_TMR61_GCMB; //Select Event interrupt function
stcIrqRegiConf.pfnCallback = &Timer61_CapInputCallBack; //Callback function
enIrqRegistration(&stcIrqRegiConf); //Registration IRQ
NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn); //Clear Pending
NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_04);//Set priority
NVIC_EnableIRQ(stcIrqRegiConf.enIRQn); //Enable NVIC
/*start timer6*/
Timer6_StartCount(TIMER6_UNIT1);
}
四 设计实现——PWM捕获
因为输入到MCU的PWM频率为1KHz,所以捕获定时器的周期值为:
捕获到的PWM比较值即为:
所以由上面方案中采样到的4-20mA对应的占空比16-80%,计算得到的比较值即为:
程序中的PWM捕获采用Timer6_GetGeneralCmpValue接口:
/**************************************************************************
* 函数名称: analogInputHandle
* 功能描述: 模拟量输入处理
* 输入参数:
* 输出参数:
* 返 回 值:
* 其它说明:
**************************************************************************/
float analogInputHandle(STRU_ANALOG_IO_CTRL *p_analog_ctrl)
{
//...
//这边捕获到的比较值是根据输入进来的PWM计算得到的
remote_ctrl.AI1_read = Timer6_GetGeneralCmpValue(TIMER6_UNIT1, TIMER6_UNIT1_CHB);
//...
}
采集得到的remote_ctrl.AI1_read再经过公式(本例中,K=2100,B=420),对应得到4-20:
五 梳理总结
这个APC替代ADC用于采样模拟量输入的方案,使用比较简单方便,但是精度并没有之前使用的ADS1018(12位精度)那么好,适用于对精度要求不高的场景。