大家好,我是左工,在上一节Aurix英飞凌TC334芯片CAN模块配置中我们已经运用CAN模块实现了测试板的对外通讯。今天我们来聊聊嵌入式控制中非常非常关键的ADC采样模块。

前言

在汽车电子系统中,控制器通常会采集一些外界的物理量,如高度,角度和温度等。采集这些物理量的基本原理是通过传感器将这些物理量转换为电压,控制器的主控芯片采集传感器的电压值(模拟量Analog)将其转化为数字量(Digital)存储和处理,这个过程就是ADC采样(Analog-to-Digital Conversion Sampling)。该功能将连续变化的模拟信号(如电压、温度、压力)转换为离散的数字信号的过程,是嵌入式系统与物理世界交互的关键环节。

一、代码编写

利用文章“搭建英飞凌Aurix系列TC334芯片开源免费开发工具链”中所描述的方法创建一个名为“4_AdcSmp”的工程,如下图所示。
在这里插入图片描述
工程创建完成后,我们会在导引栏中看见如下文件和文件夹。其中文件“Cpu0_Main.c”就是我们本次需要操作的文件。
在这里插入图片描述
我们双击打开该文件,找到主函数“core0_main”,TC334运行后,将首先运行该函数。我们可以看见该函数里面有一个while循环。我们将把代码写在这个while循环附近。
在这里插入图片描述
用如下代码替换上图中被红色框标出的while循环。

GPTimer_init();
CAN_init();
ADC_init();
uint8 tx_buf[8]; 
while(1)
{
    if(TimeCount>1)
    {
        TimeCount = 0;
        adc_val = adc_group.group->RES[adc_group.group->CHCTR[adc_channel.channel].B.RESREG].B.RESULT;//读取ADC数据
        tx_buf[0] = (uint8)(adc_val >> 4); // ADC采样数据存放于低12位,取[11:4]位段作为要上报的数据,即上报数据为采样数据/16,4095的上传值为255,1600的上传值为100。
        CAN_send(0x123,tx_buf,8); // 报文发送
    }
}

添加完成后,我们本次实验对主函数的所有操作就结束了。下面我们来添加一些必要头文件。

#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"
#include "IfxEvadc_Adc.h" 

函数“GPTimer_init()”的定义可以从文章Aurix英飞凌TC334芯片GPT模块定时器配置中查看。函数“CAN_init()”的定义可以从文章Aurix英飞凌TC334芯片CAN模块配置中查看。

IFX_INTERRUPT(GPTimer_isr,0,10);  //声明定时中断服务函数
void GPTimer_isr(void)
{
     TimeCount++;
}

我们在主函数“core0_main()”的上面添加添加上“ADC_init()”函数的具体定义以及ADC模块设定需要的变量。

/************************ADC模块定义************************/
IfxEvadc_Adc adc_handle;  //ADC模块的操作句柄
IfxEvadc_Adc_Group adc_group;  //ADC模块下的转换组操作句柄
IfxEvadc_Adc_Channel adc_channel;  //ADC模块的单个通道的操作句柄
uint16 adc_val = 0;  //ADC采样数据
/*ADC初始化函数*/
void ADC_init(void)
{
    IfxEvadc_Adc_Config adc_config = {0};  //ADC模块的具体配置
    IfxEvadc_Adc_initModuleConfig(&adc_config,&MODULE_EVADC);  //填充ADC顶层模块的默认配置,并指定要对哪个EVADC外设进行初始化
    IfxEvadc_Adc_initModule(&adc_handle,&adc_config);  //根据ADC配置设置EVADC外设
    /*配置转换组0*/
    IfxEvadc_Adc_GroupConfig adc_group_config = {0};  //该转换组的配置
    IfxEvadc_Adc_initGroupConfig(&adc_group_config,&adc_handle);  //填充转换组默认配置
    adc_group_config.groupId = IfxEvadc_GroupId_0;  //使用第0号转换组
    /*配置该组的转换参数模板0*/
    adc_group_config.inputClass[0].sampleTime = 1.0f * 0.001f * 0.001f;  //采样时间设置为1us,采样率即1Msa/s
    /*配置组内转换仲裁*/
    adc_group_config.arbiter.requestSlotQueue0Enabled = TRUE;  //转换队列0的仲裁请求使能
    /*配置转换队列0*/
    adc_group_config.queueRequest[0].requestSlotPrio = IfxEvadc_RequestSlotPriority_high;  //该队列转换优先级设为高
    adc_group_config.queueRequest[0].triggerConfig.gatingMode = IfxEvadc_GatingMode_always;  //门控模式为常开
    IfxEvadc_Adc_initGroup(&adc_group,&adc_group_config);  //根据已设置的转换组参数进行实际配置转换组
    /*配置转换通道0*/
    IfxEvadc_Adc_ChannelConfig adc_channel_config = {0};  //转换组下的单转换通道配置
    IfxEvadc_Adc_initChannelConfig(&adc_channel_config,&adc_group);  //初始化完转换组后,完成通道配置变量的默认参数填充
    adc_channel_config.channelId = IfxEvadc_ChannelId_4;  //第4号转换通道
    adc_channel_config.resultRegister = IfxEvadc_ChannelResult_0;  //该通道转换完成后存入第0号结果寄存器中
    IfxEvadc_Adc_initChannel(&adc_channel,&adc_channel_config);  //根据前面的参数完成通道的实际配置
    /*添加并启动转换队列*/
    IfxEvadc_addToQueue(adc_group.group,adc_channel.channel,IfxEvadc_RequestSource_queue0,IFXEVADC_QUEUE_REFILL);  //添加到转换队列0,并设置该通道在转换完成后自动重新添加到转换队列
    IfxEvadc_startQueue(adc_group.group,IfxEvadc_RequestSource_queue0);  //启动转换队列0
}

现在所有代码编写完毕,整个Cpu0_Main.c的定义如下所示。

#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"
#include "IfxEvadc_Adc.h"  //包含ADC库函数文件
IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;
/************************GPT定时模块定义************************/
uint8 TimeCount = 0;
void GPTimer_init(void)
{
    /*端口初始化*/
    IfxPort_setPinMode(&MODULE_P02,5,IfxPort_Mode_outputPushPullGeneral );  //P02.5配置为输入模式
    /*GPT120配置*/
    IfxGpt12_enableModule(&MODULE_GPT120);  //使能GPT120模块
    IfxGpt12_setGpt1BlockPrescaler(&MODULE_GPT120,IfxGpt12_Gpt1BlockPrescaler_8);  //GPT模块预分频,使用SPB总线时钟为100MHz
    /*配置T3定时器用于产生周期性中断*/
    IfxGpt12_T3_setMode(&MODULE_GPT120,IfxGpt12_Mode_timer);  //设置T3定时器的模式为定时器模式
    IfxGpt12_T3_setDirectionSource(&MODULE_GPT120,IfxGpt12_TimerDirectionSource_internal);  //内部控制计数方向
    IfxGpt12_T3_setTimerDirection(&MODULE_GPT120,IfxGpt12_TimerDirection_up);  //T3的计数方向为向上计数
    IfxGpt12_T3_setTimerPrescaler(&MODULE_GPT120,IfxGpt12_TimerInputPrescaler_128);  //定时器计数时钟为模块时钟的128分频
    IfxGpt12_T3_setTimerValue(&MODULE_GPT120,0xFFFF-50000+1);  //计数50000个脉冲即会发生溢出事件
    /*配置T3的溢出中断*/
    IfxSrc_init(&SRC_GPT120T3,IfxSrc_Tos_cpu0,10);   //配置GPT120模块的T3定时器溢出中断服务,中断优先级设为10
    IfxSrc_enable(&SRC_GPT120T3);  //使能GPT120模块的T3定时器溢出中断服务
    /*启动定时器*/
    IfxGpt12_T3_run(&MODULE_GPT120,IfxGpt12_TimerRun_start);  //启动T3
}
IFX_INTERRUPT(GPTimer_isr,0,10);  //声明定时中断服务函数
void GPTimer_isr(void)
{
     TimeCount++;
}
/************************CAN模块定义************************/
volatile uint16 rx_id = 0;  //存储接收报文ID
volatile uint8 rx_len = 0;  //存储接收报文负载数据长度
volatile uint8 rx_buf[8] = {0};  //报文接收数据
/*CAN模块初始化*/
IfxCan_Can can_handle = {0};  //CAN模块的操作句柄
IfxCan_Can_Node node0_handle = {0};  //CAN第0节点的操作句柄
/*CAN模块初始化函数*/
void CAN_init(void)
{
     /*模块配置*/
    IfxCan_Can_Config can_config = {0};  //模块配置参数结构体变量
    IfxCan_Can_initModuleConfig(&can_config,&MODULE_CAN0);  //填充CAN模块配置参数结构体
    IfxCan_Can_initModule(&can_handle,&can_config);  //初始化CAN模块
    /*端口配置*/
    IfxCan_Node_initTxPin(&IfxCan_TXD00_P20_8_OUT,IfxPort_OutputMode_pushPull,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    IfxCan_Node_initRxPin(&MODULE_CAN0.N[IfxCan_NodeId_0],&IfxCan_RXD00B_P20_7_IN,IfxPort_InputMode_noPullDevice,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    /*节点0配置*/
    IfxCan_Can_NodeConfig node_config = {0};
    IfxCan_Can_initNodeConfig(&node_config,&can_handle);  //填充节点默认配置
    node_config.nodeId = IfxCan_NodeId_0;  //要配置的节点为第0号节点
    node_config.frame.type = IfxCan_FrameType_transmitAndReceive;  //允许节点能接收和发送数据帧
    node_config.baudRate.baudrate = 500000;//波特率
    /*节点接收中断*/
    node_config.interruptConfig.messageStoredToDedicatedRxBufferEnabled = TRUE;  //使能接收成功中断
    node_config.interruptConfig.reint.interruptLine = IfxCan_InterruptLine_0;  //中断信号映射到0号中断线
    node_config.interruptConfig.reint.priority = 11;  //接收成功中断的优先级设为11
    IfxCan_Can_initNode(&node0_handle,&node_config);  //完成节点配置
    /*节点0过滤配置*/
    IfxCan_Filter node0_filter0 = {0};
    node0_filter0.number = 0;  //配置第0号过滤器
    node0_filter0.elementConfiguration = IfxCan_FilterElementConfiguration_storeInRxBuffer;  //过滤通过存到指定缓冲区
    node0_filter0.id1 = 0x010;  //在指定缓冲区模式下,配置id1为过滤id
    node0_filter0.rxBufferOffset = IfxCan_RxBufferId_0;  //过滤完成后存入0号接收缓冲区
    IfxCan_Can_setStandardFilter(&node0_handle,&node0_filter0);  //完成过滤器配置
}
/*根据报文ID发出报文,返回已完成发送的数据长度*/
void CAN_send(uint16 id,uint8 *buf,uint32 len)
{
    IfxCan_Message tx_msg = {0};
    IfxCan_Can_initMessage(&tx_msg);  //向消息结构体填充初始参数
    tx_msg.bufferNumber = IfxCan_TxBufferId_0;  //使用第0号发送缓冲区发送
    tx_msg.frameMode = IfxCan_FrameMode_standard;  //此为标准CAN协议帧
    tx_msg.dataLengthCode = len;  //设定要发送的报文的负载数据长度
    tx_msg.messageId = id;  //设置本次报文的id
    tx_msg.messageIdLength = IfxCan_MessageIdLength_standard;  //本次报文ID长度为11位的ID
    IfxCan_Can_sendMessage(&node0_handle,&tx_msg,(uint32 *)buf);  //传入负载数据启动发送
}
/*CAN模块的接收中断处理函数*/
IFX_INTERRUPT(CAN_rx_isr,0,11);  //声明CAN接收中断服务函数
void CAN_rx_isr(void)
{
    IfxCan_Message rx_msg = {0};
    uint32 data_buf[2] = {0};  //存数据的临时缓冲区
    IfxCan_Node_clearInterruptFlag(node0_handle.node,IfxCan_Interrupt_messageStoredToDedicatedRxBuffer);  //清除接收完成中断
    rx_msg.bufferNumber = IfxCan_RxBufferId_0;  //指定获取第0号接收缓冲区
    IfxCan_Can_readMessage(&node0_handle,&rx_msg,data_buf);  //读取消息,将数据存入缓冲区便于程序处理
    rx_id = (uint16)rx_msg.messageId;  //将接收到的报文ID保存下来
    rx_len = (uint8)rx_msg.dataLengthCode;  //获取接收到的数据长度
    /*取出接收到的报文数据*/
    for(uint32 cnt = 0; cnt < rx_len; cnt ++)
    {
        rx_buf[cnt] = ((uint8 *)data_buf)[cnt];  //以字节来读取数据
    }
}
/************************ADC模块定义************************/
IfxEvadc_Adc adc_handle;  //ADC模块的操作句柄
IfxEvadc_Adc_Group adc_group;  //ADC模块下的转换组操作句柄
IfxEvadc_Adc_Channel adc_channel;  //ADC模块的单个通道的操作句柄
uint16 adc_val = 0;  //ADC采样数据
/*ADC初始化函数*/
void ADC_init(void)
{
    IfxEvadc_Adc_Config adc_config = {0};  //ADC模块的具体配置
    IfxEvadc_Adc_initModuleConfig(&adc_config,&MODULE_EVADC);  //填充ADC顶层模块的默认配置,并指定要对哪个EVADC外设进行初始化
    IfxEvadc_Adc_initModule(&adc_handle,&adc_config);  //根据ADC配置设置EVADC外设
    /*配置转换组0*/
    IfxEvadc_Adc_GroupConfig adc_group_config = {0};  //该转换组的配置
    IfxEvadc_Adc_initGroupConfig(&adc_group_config,&adc_handle);  //填充转换组默认配置
    adc_group_config.groupId = IfxEvadc_GroupId_0;  //使用第0号转换组
    /*配置该组的转换参数模板0*/
    adc_group_config.inputClass[0].sampleTime = 1.0f * 0.001f * 0.001f;  //采样时间设置为1us,采样率即1Msa/s
    /*配置组内转换仲裁*/
    adc_group_config.arbiter.requestSlotQueue0Enabled = TRUE;  //转换队列0的仲裁请求使能
    /*配置转换队列0*/
    adc_group_config.queueRequest[0].requestSlotPrio = IfxEvadc_RequestSlotPriority_high;  //该队列转换优先级设为高
    adc_group_config.queueRequest[0].triggerConfig.gatingMode = IfxEvadc_GatingMode_always;  //门控模式为常开
    IfxEvadc_Adc_initGroup(&adc_group,&adc_group_config);  //根据已设置的转换组参数进行实际配置转换组
    /*配置转换通道0*/
    IfxEvadc_Adc_ChannelConfig adc_channel_config = {0};  //转换组下的单转换通道配置
    IfxEvadc_Adc_initChannelConfig(&adc_channel_config,&adc_group);  //初始化完转换组后,完成通道配置变量的默认参数填充
    adc_channel_config.channelId = IfxEvadc_ChannelId_4;  //第4号转换通道
    adc_channel_config.resultRegister = IfxEvadc_ChannelResult_0;  //该通道转换完成后存入第0号结果寄存器中
    IfxEvadc_Adc_initChannel(&adc_channel,&adc_channel_config);  //根据前面的参数完成通道的实际配置
    /*添加并启动转换队列*/
    IfxEvadc_addToQueue(adc_group.group,adc_channel.channel,IfxEvadc_RequestSource_queue0,IFXEVADC_QUEUE_REFILL);  //添加到转换队列0,并设置该通道在转换完成后自动重新添加到转换队列
    IfxEvadc_startQueue(adc_group.group,IfxEvadc_RequestSource_queue0);  //启动转换队列0
}
void core0_main(void)
{
    IfxCpu_enableInterrupts();

    /* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
     * Enable the watchdogs and service them periodically if it is required
     */
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

    /* Wait for CPU sync event */
    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
    GPTimer_init();
    CAN_init();
    ADC_init();
    uint8 tx_buf[8]; // 发送缓存
    while(1)
    {
        if(TimeCount>1)
        {
            TimeCount = 0;
            adc_val = adc_group.group->RES[adc_group.group->CHCTR[adc_channel.channel].B.RESREG].B.RESULT;//读取ADC数据
            tx_buf[0] = (uint8)(adc_val >> 4); // ADC采样数据存放于低12位,取[11:4]位段作为要上报的数据,即上报数据为采样数据/16,4095的上传值为255,1600的上传值为100。
            CAN_send(0x123,tx_buf,8); // 报文发送
        }
    }
}

二、代码解释

相比于前面文章Aurix英飞凌TC334芯片CAN模块配置中CAN模块的配置,ADC采样模块的配置比较简单。主要多了ADC模块的初始化。ADC模块初始化中绝大部分代码不需要修改,只关注几个重要的配置即可。下面我们就对这段代码就行简单的分析和讲解。

adc_group_config.inputClass[0].sampleTime = 1.0f * 0.001f * 0.001f;  //采样时间设置为1us,采样率即1Msa/s

这一行代码是设置采样频率,就是确定多长时间进行一次采样。

adc_channel_config.channelId = IfxEvadc_ChannelId_4;  //第4号转换通道

上面这行代码将采样通道设置为第4号转换通道,这与硬件原理图中的端口对应。我们接线的时候也是从AD_GP1+将传感器信号接进测试板。
在这里插入图片描述
主程序中还有一行代码,这行代码的主要作用就是将转换完成后的采样值从寄存器中提取出来。

adc_val = adc_group.group->RES[adc_group.group->CHCTR[adc_channel.channel].B.RESREG].B.RESULT;

我们可以看见,这段程序的主要作用是周期性的获取采样值,然后通过CAN报文发送出去。程序的结构如下图所示。
在这里插入图片描述

三、下载调试

程序编写完毕后,我们点击任务栏中的编译按钮。
在这里插入图片描述
编译完成后,在输出窗口可以看见“编译完成,无错误,无警告”的字样。
在这里插入图片描述
然后我们通过Type-C将测试板与你的电脑相连。
在这里插入图片描述
点击任务栏中的下载按钮。
在这里插入图片描述
下载完成后,在输出窗口可以看见“下载成功”的字样。
在这里插入图片描述
将测试板的5V_A,GP1+和AGND与传感器的供电,信号和地相连,如下图所示。
**加粗样式**
将测试板的CAN_H(红色)和CAN_L(蓝色)接出来通过DB9接头与CAN卡相连。
在这里插入图片描述
将CAN卡通过USB接口与电脑相连。
在这里插入图片描述
个人电脑的上位机我们使用TSMaster。
在这里插入图片描述
配置好TSMaster后,开发板已经在周期性的发送ID为0x123的报文了,且第0字节为采样值。当我们转动传感器的中间转轴的时候,第0字节的数值发生了相应的变化。至此我们让主控芯片TC334获取到了角度值,并将其发送了出来。


总结

今天我们介绍了如何运用TC334芯片ADC模块采集传感器的角度值,并通过CAN报文发送出去。后续我们将继续介绍如何运用该芯片捕获比较模块调制PWM实现直流电机的控制。敬请收藏关注,不迷路。

如需测试板,请点击。
https://store.weixin.qq.com/shop/b/feHVuMfOAmRCoHk?entrance_id=h5

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐