笔者在19年,有写过一篇《基于CAN总线的汽车诊断协议UDS,上位机下位机开发》文章,后面陆陆续续有读者询问相关问题,接下来分两章分别介绍上下位机工程基础搭建。本章内容,介绍ECU开发的基本流程。
1. 芯片选用
nxp MIMXRT1062芯片,为什么选用这款作为demo,有以下优点:
2.bootloader 和 App 中断向量重映射
bootloadr 向量表操作方法,从Flash地址0x60002000拷贝到ITCM–0x00000000,向量表寄存器设置为:SCB->VTOR = (uint32_t )0x00000000;
void xBSP_VectorInit(void)
{
int *pS = (int*)0x60002000;
int *pT = (int*)0x00000000;
__asm
{
CPSID i
}
SCB->VTOR = (uint32_t )0x00000000;
__asm
{
CPSIE i
}
for(int i = 0 ; i < (0x400 / 4) ; i++)
{
pT[i] = pS[i];
}
}
App 向量表操作方法,从Flash地址0x60010000拷贝到ITCM–0x00000000,向量表寄存器设置为:SCB->VTOR = (uint32_t )0x00000000;
void xBSP_VectorInit(void)
{
int *pS = (int*)0x60010000;
int *pT = (int*)0x00000000;
__asm
{
CPSID i
}
SCB->VTOR = (uint32_t )0x00000000;
__asm
{
CPSIE i
}
for(int i = 0 ; i < (0x400 / 4) ; i++)
{
pT[i] = pS[i];
}
}
综上Bootloader和App,向量表映射都在0x00000000,原因是ITCM响应执行速度更快。另外需要注意修改链接脚本,明白bootloader和app是处于ROM的哪个位置。笔者设计的是 bootloader:0x6000_0000 app:0x6001_0000
3.FlexCan初始化
/*------------------------------------------------------------------------------------------------------------------
Macros
*/
#define EXAMPLE_CAN CAN2
#define FLEXCAN_BAUDRATE (500000U)
/* Select 60M clock divided by USB1 PLL (480 MHz) as master flexcan clock source */
#define FLEXCAN_CLOCK_SOURCE_SELECT (0U)
/* Clock divider for master flexcan clock source */
#define FLEXCAN_CLOCK_SOURCE_DIVIDER (2U)
/* Get frequency of flexcan clock */
#define EXAMPLE_CAN_CLK_FREQ ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8) / (FLEXCAN_CLOCK_SOURCE_DIVIDER + 1U))
//Message Box 定义 0~63
#define RX_MESSAGE_BUFFER_A (8)
#define TX_MESSAGE_BUFFER_NUM (9)
/*------------------------------------------------------------------------------------------------------------------
Variables
*/
flexcan_handle_t flexcanHandle;
uint32_t txIdentifier = 0x721;
uint32_t rxIdentifier = 0x710;
flexcan_frame_t txFrame,rxFrame;
flexcan_mb_transfer_t txXfer, rxXfer;
/*
********************************************************************************************************************
@ Brief : Can Init
@ Param : None
@ Return : NONE
@ Author : LYC
@ Date : 2019 - 04 - 03
********************************************************************************************************************
*/
void xBSP_CanInit(void)
{
flexcan_config_t flexcanConfig;
flexcan_rx_mb_config_t mbConfig;
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_02_FLEXCAN2_TX, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_03_FLEXCAN2_RX, 1U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_02_FLEXCAN2_TX, 0x10B0u);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_03_FLEXCAN2_RX, 0x10B0u);
/*Clock setting for FLEXCAN*/
CLOCK_SetMux(kCLOCK_CanMux, FLEXCAN_CLOCK_SOURCE_SELECT);
CLOCK_SetDiv(kCLOCK_CanDiv, FLEXCAN_CLOCK_SOURCE_DIVIDER);
/* Get FlexCAN module default Configuration. */
/*
* flexcanConfig.clkSrc = kFLEXCAN_ClkSrc0;
* flexcanConfig.baudRate = 1000000U;
* flexcanConfig.baudRateFD = 2000000U;
* flexcanConfig.maxMbNum = 16;
* flexcanConfig.enableLoopBack = false;
* flexcanConfig.enableSelfWakeup = false;
* flexcanConfig.enableIndividMask = false;
* flexcanConfig.disableSelfReception = false;
* flexcanConfig.enableListenOnlyMode = false;
* flexcanConfig.enableDoze = false;
*/
FLEXCAN_GetDefaultConfig(&flexcanConfig);
flexcanConfig.enableLoopBack = false;
flexcanConfig.baudRate = FLEXCAN_BAUDRATE;
FLEXCAN_Init(EXAMPLE_CAN, &flexcanConfig, EXAMPLE_CAN_CLK_FREQ);
/* Create FlexCAN handle structure and set call back function. */
FLEXCAN_TransferCreateHandle(EXAMPLE_CAN, &flexcanHandle, flexcan_callback, NULL);
txFrame.id = FLEXCAN_ID_STD(txIdentifier);
txFrame.format = (uint8_t)kFLEXCAN_FrameFormatStandard;
txFrame.type = (uint8_t)kFLEXCAN_FrameTypeData;
/* Setup Tx Message Buffer. */
FLEXCAN_SetTxMbConfig(EXAMPLE_CAN, TX_MESSAGE_BUFFER_NUM, true); //设置发送邮箱
/* Set Rx Masking mechanism. */
FLEXCAN_SetRxMbGlobalMask(EXAMPLE_CAN, FLEXCAN_RX_MB_STD_MASK(0x7FC, 0, 0)); //rxIdentifier 0x710 0x711 都有效
/* Setup Rx Message Buffer. */
mbConfig.format = kFLEXCAN_FrameFormatStandard;
mbConfig.type = kFLEXCAN_FrameTypeData;
FLEXCAN_SetRxIndividualMask(EXAMPLE_CAN, RX_MESSAGE_BUFFER_A, FLEXCAN_RX_MB_STD_MASK(0x7FC,0,0)); //rxIdentifier 0x710 0x711 都有效
mbConfig.id = FLEXCAN_ID_STD(rxIdentifier);
FLEXCAN_SetRxMbConfig(EXAMPLE_CAN, RX_MESSAGE_BUFFER_A, &mbConfig, true);
rxFrame.id = FLEXCAN_ID_STD(rxIdentifier);
rxXfer.mbIdx = RX_MESSAGE_BUFFER_A;
rxXfer.frame = &rxFrame;
FLEXCAN_TransferReceiveNonBlocking(EXAMPLE_CAN, &flexcanHandle, &rxXfer);
NVIC_SetPriority(CAN2_IRQn, CAN_ISR_PRE);
}
重点需要注意的是帧过滤,如下图,MB0-63 都可用于发送接收的Buffer,在Rx中是需要Match的,也就是ID的过滤,过滤OK产生中断,执行相关操作。
笔者代码中使用了MB[8]作为接收,所以MASK需要写在对应的RXIMR[8]上,FLEXCAN_SetRxIndividualMask设置为0x7FC,即最后两bit都为0,这两个位是不会校验的,那此时MB[8]会接收ID范围为0x710-0x713的数据。更多过滤细节大家可翻阅手册实验,这里不展开讲解。
4.Can接收发送处理
extern TaskHandle_t Handle_CanMessageTask;
volatile bool txComplete = false,rxComplete = false;
static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData)
{
switch (status)
{
case kStatus_FLEXCAN_RxIdle:
if (RX_MESSAGE_BUFFER_A == result)
{
#if 0
rxComplete = true;
#else /*任务通知*/
BaseType_t xHigherPriorityTaskWoken = pdTRUE;
vTaskNotifyGiveFromISR(Handle_CanMessageTask, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
#endif
}
break;
case kStatus_FLEXCAN_TxIdle:
case kStatus_FLEXCAN_TxSwitchToRx:
if (TX_MESSAGE_BUFFER_NUM == result)
{
txComplete = true;
}
break;
case kStatus_FLEXCAN_WakeUp:
// wakenUp = true;
break;
default:
break;
}
}
/*
********************************************************************************************************************
@ Brief : Can send message
@ Param : pBuffer: len:
@ Return : NONE
@ Author : LYC
@ Date : 2019 - 01 - 22
********************************************************************************************************************
*/
void xBSP_CAN_TX(uint8_t* pBuffer,uint8_t len)
{
txFrame.length = len;
txFrame.dataByte0 = pBuffer[0];
txFrame.dataByte1 = pBuffer[1];
txFrame.dataByte2 = pBuffer[2];
txFrame.dataByte3 = pBuffer[3];
txFrame.dataByte4 = pBuffer[4];
txFrame.dataByte5 = pBuffer[5];
txFrame.dataByte6 = pBuffer[6];
txFrame.dataByte7 = pBuffer[7];
txXfer.mbIdx = (uint8_t)TX_MESSAGE_BUFFER_NUM;
txXfer.frame = &txFrame;
FLEXCAN_TransferSendNonBlocking(EXAMPLE_CAN, &flexcanHandle, &txXfer);
}
/*
********************************************************************************************************************
@ Brief : Can 接收8Byte 处理
@ Param : None
@ Return : NONE
@ Author : LYC
@ Date : 2019 - 01 - 22
********************************************************************************************************************
*/
void xBSP_CanReceive(uint8_t* pRxCanBuf)
{
FLEXCAN_TransferReceiveNonBlocking(EXAMPLE_CAN, &flexcanHandle, &rxXfer);
pRxCanBuf[0]=rxFrame.dataByte0;
pRxCanBuf[1]=rxFrame.dataByte1;
pRxCanBuf[2]=rxFrame.dataByte2;
pRxCanBuf[3]=rxFrame.dataByte3;
pRxCanBuf[4]=rxFrame.dataByte4;
pRxCanBuf[5]=rxFrame.dataByte5;
pRxCanBuf[6]=rxFrame.dataByte6;
pRxCanBuf[7]=rxFrame.dataByte7;
}
flexcan_callback 是can中断事件,这个callback实际是在中断模式,不建议接收处理放在中断中做,这里给了两种方案,一个是看标志位(裸机方式),另一种是中断中发起任务通知(操作系统)。如下函数为,接收处理线程,当然裸机可以放在while中处理一样。
/*
***************************************************************************************************************
@ Brief : Can 接收解包任务
@ Param : None
@ Return : None
@ Author : Lyc
@ Date : 2019 - 04 - 03
***************************************************************************************************************
*/
void xAPP_DealCanMessageTask(void* argument)
{
uint8_t rxCanBuf[8];
uint8_t txCanBuf[8];
while(1)
{
if(ulTaskNotifyTake(pdTRUE, portMAX_DELAY))
{
/*0.接收8Byte*/
xBSP_CanReceive(rxCanBuf);
/*1.解包分析*/
if(xSYS_CANUnpackFrame(rxCanBuf) == true)
{
/*2.处理具体的帧数据*/
switch(ServiceUnpackData.frameType)
{
/*服务接收完毕时*/
case 0:
case 2:
{
xSYS_CANAppService(ServiceUnpackData.validData,ServiceUnpackData.validSize - 1);
}
break;
case 1: //当前发送流控帧
{
memset(txCanBuf,0xff,8);
txCanBuf[0] = 0x30;
xBSP_CAN_TX(txCanBuf,8);
}
break;
default:
break;
}
}
}
//vTaskDelay(1);
}
}
5.基于UDS解包思路
UDS是一套完整的标准协议,其实只需要熟透ISO 15765-2 和 ISO 14229-1 用代码形式表达出来就可以了。因为有保密协议,笔者就不展示核心代码了,
//** Service running state **
#define SrvStat_Run_Req 0x00
#define SrvStat_Run_Resp 0x01
#define SrvStat_ALLPhase_Over 0x02
//** SubFunc of Diag Ser $10 Session Define **
#define Srv10_DefaultMod 0x01
#define Srv10_ProgramMod 0x02
#define Srv10_ExternMod 0x03
//** SubFunc of Diag Ser $11 ECUReset**
#define Srv11_HardReset 0x01
#define Srv11_KeyOffOnReset 0x02
#define Srv11_SoftReset 0x03
//** SubFunc of Diag Srv 19 ReadDTCInfo **
#define Srv19_REPORT_DTC_NUM_BY_MASK 0x01
#define Srv19_REPORT_DTC_BY_MASK 0x02
#define Srv19_REPORT_DTC_EXT_BY_DTCNUM 0x06
#define Srv19_REPORT_SUPPORTED_DTC 0x0A
//** Service $22: phase define **
#define Srv22_Phase_ReadProductNum 0x00
#define Srv22_Phase_ReadProductor 0x01
#define Srv22_Phase_ReadSerialNum 0x02
//** Service $2E: phase define **
#define Srv2E_Phase_WriteProductNum 0x00
#define Srv2E_Phase_writeProductor 0x01
#define Srv2E_Phase_writeSerialNum 0x02
//** Service $27: Security Level define **
#define Srv27_SecLevel_AskSeed 0x03
#define Srv27_SecLevel_ChkKey 0x04
//** Service $31: Rountin define **
#define Srv31_CheckAppValidation 0xDA04
#define Srv31_EraseFlash 0xFF00
#define Srv31_CheckComplete 0xFF01
#define Srv31_CheckBootCondition 0xFF02
#define Srv31_IndependCheck 0xFF03
//** SubFunc of Routine control type $31 Routine Ctrl **
#define Srv31_ROUTINE_START 0x01
#define Srv31_ROUTINE_STOP 0x02
#define Srv31_ROUTINE_REQRESULT 0x03
//** SubFunc of Diag Srv 28 CommunicationCtrl **
#define Srv28_COMM_EN_RX_AND_TX 0x0
#define Srv28_COMM_EN_RX_ONLY 0x1
#define Srv28_COMM_EN_TX_ONLY 0x2
#define Srv28_COMM_DISABLE_RX_AND_TX 0x3
//** SubFunc of Diag Srv 85 CtrlDTCSetting **
#define Srv85_CTRL_DTC_ON 0x01
#define Srv85_CTRL_DTC_OFF 0x02
///
#define DIAG_SESSION_CTRL_10 0x10
#define DIAG_ECUREST_11 0x11
#define DIAG_CLEARIFO_14 0x14
#define DIAG_READ_DTC_INFO_19 0x19
#define DIAG_READDATABYID_22 0x22
#define DIAG_WRITEDATABYID_2E 0x2E
#define DIAG_SECURITY_27 0x27
#define DIAG_COMMCTRL_28 0x28
#define DIAG_ROUTINECTRL_31 0x31
#define DIAG_REQ_DOWNLOAD_34 0x34
#define DIAG_TRANS_DATA_36 0x36
#define DIAG_REQ_TRANS_EXIT_37 0x37
#define DIAG_TESTERPRESENT_3E 0x3E
#define DIAG_CTRL_DTC_SET_85 0x85
#define DIAG_NAGETIVE_7F 0x7F
bool xSYS_CANAppService(uint8_t* buf,uint32_t size)
{
bool respRlt = false;
uint8_t serviceType = buf[0];
switch(serviceType)
{
case DIAG_SESSION_CTRL_10:
{
respRlt = xCAN_App10Service(&buf[1],size);
}
break;
case DIAG_ECUREST_11:
{
respRlt = xCAN_App11Service(&buf[1],size);
}
break;
case DIAG_CLEARIFO_14:
{
respRlt = xCAN_App14Service(&buf[1],size);
}
break;
case DIAG_READ_DTC_INFO_19:
{
respRlt = xCAN_App19Service(&buf[1],size);
}
break;
case DIAG_READDATABYID_22:
{
respRlt = xCAN_App22Service(&buf[1],size);
}
break;
case DIAG_WRITEDATABYID_2E:
{
respRlt = xCAN_App2EService(&buf[1],size);
}
break;
case DIAG_SECURITY_27:
{
respRlt = xCAN_App27Service(&buf[1],size);
}
break;
case DIAG_COMMCTRL_28:
{
respRlt = xCAN_App28Service(&buf[1],size);
}
break;
case DIAG_ROUTINECTRL_31:
{
respRlt = xCAN_App31Service(&buf[1],size);
}
break;
case DIAG_REQ_DOWNLOAD_34:
{
respRlt = xCAN_App34Service(&buf[1],size);
}
break;
case DIAG_TRANS_DATA_36:
{
respRlt = xCAN_App36Service(&buf[1],size);
}
break;
case DIAG_REQ_TRANS_EXIT_37:
{
respRlt = xCAN_App37Service(&buf[1],size);
}
break;
case DIAG_TESTERPRESENT_3E:
{
respRlt = xCAN_App3EService(&buf[1],size);
}
break;
case DIAG_CTRL_DTC_SET_85:
{
respRlt = xCAN_App85Service(&buf[1],size);
}
break;
default:
{
respRlt = xCAN_AppOthersService(&buf[1],size);
}
break;
}
return respRlt;
}
6.bootloader跳转app
笔者这里只是简单做个demo,所以跳转要求没做那么苛刻,具体如下:
AREA JUMP, CODE, READONLY
JMP_APP PROC
EXPORT JMP_APP
LDR R0, =0x60010000
LDR R0, [R0]
MOV SP, R0
LDR R0, =0x60010004
LDR R0, [R0]
BX R0
ENDP
END
int main(void)
{
//该模式表示,固件跳转区域是否有固件
readJumpFlag = *(uint32_t*)(0x60000000+FLASH_ADDR_JUMP_FIRM_FLAG);
xBSP_Config();
xBSP_GPIO_Init();
if(readJumpFlag == 0x04030201 && Read_WAKEUP())
{
JMP_APP();
}
xBSP_MPUInit();
/*Create Start Task*/
if(xTaskCreate(xTask_Start, "Task_Start", TASK_STACK_SIZE_StartTask, NULL, TASK_PRIORITY_StartTask, &Handle_StartTask) != pdPASS)
{
_Error_Handler(__FILE__, __LINE__);
}
vTaskStartScheduler(); //开始系统调度
while(1)
{
}
}