SAE J1850 汽車匯流排協議 VPW 物理層驅動程式在STM32晶片上的實現

Leo-Lian發表於2018-04-22

VPW(Variable Pulse Width)是一種可變脈寬調製的汽車匯流排通訊方式,常用於美系的福特,通用,克萊斯勒等汽車上,主要用途為車用資訊中心、儀表顯示、故障檢測診斷等。

VPW – 以資料位為基本單位進行傳輸,定義了一個起始位(SOF):200us 的高電平代表開始進行位傳輸,定義了一個結束位(EOF):280us 的低電平表示位傳輸正常結束,起始位之後的資料位表示方式可認為為:電平不斷的翻轉,每次產生一次翻轉便產生一個新的資料位,這個資料位為“0”還是“1”由翻轉時電平的持續時間來決定,資料位“0”用 64us 的低電平或 128us 的高電平表示,資料位“1” 用 64us 的高電平或 128us 的低電平表示。另在網路節點多的時候VPW 針對鏈路層定義了有效資料域結束位(EOD),幀間仲裁時間(IFS),多資料域時接收節點的應答就緒響應時間(IFR)如下圖所示: 
VPW

以上為背景知識介紹,下面要根據vpw協議用stm32F429這顆晶片來實現這個協議。如果非要問為什麼要用這麼高階的晶片?我只能回答你:有錢,任性! 
看了上面那張圖,一般程式猿腦海裡面應該都冒出了不少實現方式,一般來說,有如下幾種:

1、普通程式猿: 
咦?這不是很簡單嘛,一根IO拉高或者拉低,中間delay(x us)即可實現傳送,一根IO設定為外部中斷引腳,有電平變化就進中斷,然後開啟一個TIM,電平翻轉再進中斷,讀時間,OK搞定收工!

2、文藝程式猿: 
嗯?樓上弱爆了,基本是小學生的水平,那個delay死迴圈那麼低階,怎麼能行呢,怎麼樣也得再開一個TIM用來定時才準嘛··· ···

3、二逼程式猿: 
你們都讓開,這逼我今天裝定了!··· ···

沒錯,我就是那個二逼程式猿,接手了公司經過N手離職同事的文藝青年程式碼,沒效率還容易出錯,實在看不下去了,今天就來一次不走尋常路,好好研究一番這個VPW驅動的實現。

先看電路圖,vpwin為輸入引腳,j1850+為輸出引腳,BUS P和N為實際匯流排,j1850-和pwmin用不著。

電路圖

再分析上面第一張的邏輯圖,vpw是通過邏輯的高低變化來表示0和1,因此,一個高低電平變化就能表示2bit資料,2bit資料有4種:00、01、10、11,通過觀察發現任意2bit組合成的一個脈衝有如下關係(注意這裡的表是根據實際匯流排電平來算的,cpu引腳經過上面的電路圖後訊號會反向,也就是高變低,低變高):

bit | 週期 | 脈寬 | 累加和

00 | 192 | 64 | 256

01 | 128 | 64 | 192

10 | 256 | 128 | 384

11 | 192 | 128 | 320

由上面的表可見,vpw不同於pwm的地方就是它的週期是會有變化的,pwm的週期是固定的,因此,想簡單的用pwm來實現輸出是不行的,不過,既然選擇了不走尋常路,那肯定不能用簡單的方法了,要不然,直接用文藝青年的方法就行了,還裝什麼逼!

說說總體思路: 
傳送部分: 
1、開一個定時器TIM1,配置成PWM比較輸出模式,通道我用的是CC1, 
2、開啟兩個DMA通道,觸發源為TIM1_DMA_update和TIM1_DMA_CC1 
3、開2個同樣大陣列,一個是週期,一個是脈寬,大小為你要傳送的最長資料的8倍,(一個資料為1個bit) 
4、將你要傳送的資料按順序拆分為2bit一組,對照上面的關係圖將週期和脈寬轉換為定時器的pwm週期和脈寬值,填進表內,別忘了起始和結束脈衝 
5、配置2個dma,傳送資料量和資料來源地址,地址為兩個陣列地址,開啟dma的傳送完成中斷,開啟TIM1 CC1,
6、剩下的事情就是讓TIM和DMA自己嗨,等到程式自己進入了dma傳送完成中斷就是傳送完成了

接收部分: 
1、開一個定時器TIM4,配置成輸入捕獲模式,通道CC1和CC2 
2、開啟兩個DMA通道,觸發源為TIM_DMA_CC1和TIM_DMA_CC2 
3、開啟兩個陣列,同傳送部分一樣 
4、啟動tim的輸入捕獲和dma,等著vpw引腳訊號到來,dma會自己將訊號的週期和脈寬放入開好的陣列內 
5、對照上面的關係圖,將捕獲到的脈寬和週期還原成原始資料,搞定收工!

以上方式除了資料的拆分和還原以外,所有傳送和接收過程均有硬體自己完成,只要定時器配置得精準,傳送出去的波形就非常精準漂亮,捕獲的精度也很高,並且不會死死霸佔cpu的時間

廢話多了,沒有程式碼你說個JB!按照國際範例還是上點程式碼吧,上個最主要的配置程式碼,其他的自己發揮了,僅供參考,後果不負責!

/*
*********************************************************************************************************
*                                          BSP_J1850_PWM_Init()
*
* Description : 在tim溢位的時候觸發DMA將新的佔空比數值自動傳入tim,傳送完成後產生dma中斷關閉tim
*
* Argument(s) : 
*
* Return(s)   : none.
*
* Caller(s)   : Application.
*
* Note(s)     : none.
*********************************************************************************************************
*/

void  BSP_J1850_VPW_Init (void)
{  
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    TIM_ICInitTypeDef  TIM_ICInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure ;                                       

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); 
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); 
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8 , GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7 , GPIO_AF_TIM4); 

    GPIO_InitStructure.GPIO_Pin          = GPIO_Pin_7 ;                         //  GPIOB7_VPW_IN
    GPIO_InitStructure.GPIO_Mode         = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed        = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType        = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd         = GPIO_PuPd_UP ;
    GPIO_Init(GPIOB, &GPIO_InitStructure);    

    GPIO_InitStructure.GPIO_Pin          = GPIO_Pin_8;                          //GPIOA8_J1850_P ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);  

    GPIO_InitStructure.GPIO_Pin          = GPIO_Pin_12 | GPIO_Pin_13;            //GPIOB12 PWM_VOL GPIOB13_J1850_N;
    GPIO_InitStructure.GPIO_Mode         = GPIO_Mode_OUT;    
    GPIO_Init(GPIOB, &GPIO_InitStructure); 

    NVIC_InitStructure.NVIC_IRQChannel                  = DMA2_Stream5_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority       = 2;  
    NVIC_InitStructure.NVIC_IRQChannelCmd               = ENABLE;  
    NVIC_Init(&NVIC_InitStructure); 

    NVIC_InitStructure.NVIC_IRQChannel                  = TIM4_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority       = 0;  
    NVIC_InitStructure.NVIC_IRQChannelCmd               = ENABLE;  
    NVIC_Init(&NVIC_InitStructure); 

    NVIC_InitStructure.NVIC_IRQChannel                  = TIM1_CC_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority       = 3;  
    NVIC_InitStructure.NVIC_IRQChannelCmd               = ENABLE;     
    NVIC_Init(&NVIC_InitStructure); 

    TIM4_IRQ    =       BSP_VPW_TIM4_ISR_Handler;
    TIM1_CC_IRQ =       BSP_VPW_TIM1_ISR_Handler;
    DMA2_Stream5_IRQ=   BSP_VPW_DMA_ISR_Handler;  

     //pwm out   
    RCC_TIMCLKPresConfig(RCC_TIMPrescActivated);

    TIM_DeInit(TIM1);
    TIM_TimeBaseStructure.TIM_Period      = PERIOD_SOF;
    TIM_TimeBaseStructure.TIM_Prescaler   = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_ARRPreloadConfig(TIM1, ENABLE);
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); 

    TIM_OCInitStructure.TIM_OCMode        = TIM_OCMode_PWM2; 
    TIM_OCInitStructure.TIM_OutputNState  = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_OutputState   = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse         = PULSE_SOF;
    TIM_OCInitStructure.TIM_OCNPolarity   = TIM_OCNPolarity_Low;
    TIM_OCInitStructure.TIM_OCNIdleState  = TIM_OCNIdleState_Reset;
    TIM_OCInitStructure.TIM_OCPolarity    = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState   = TIM_OCIdleState_Set;  

    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Disable);      

    //vpw out dma    
    DMA_DeInit(DMA2_Stream5);   //up
    DMA_DeInit(DMA2_Stream1);   //ch1
    while(DMA_GetCmdStatus(DMA2_Stream5)!=DISABLE);
    while(DMA_GetCmdStatus(DMA2_Stream1)!=DISABLE);

    DMA_InitStructure.DMA_Channel               = DMA_Channel_6;  
    DMA_InitStructure.DMA_PeripheralBaseAddr    = (uint32_t)0x40010034;         //TIM1_CC1
    DMA_InitStructure.DMA_Memory0BaseAddr       = (uint32_t)(&(VPW_Data.Pulse[vpw_buf][0]));
    DMA_InitStructure.DMA_DIR                   = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize            = VPW_Data.Num_Pulse[vpw_buf];
    DMA_InitStructure.DMA_PeripheralInc         = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc             = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize    = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize        = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode                  = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority              = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode              = DMA_FIFOMode_Enable;        
    DMA_InitStructure.DMA_FIFOThreshold         = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst           = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst       = DMA_PeripheralBurst_Single;

    DMA_Init(DMA2_Stream5, &DMA_InitStructure);  

    DMA_InitStructure.DMA_PeripheralBaseAddr    = (uint32_t)0x4001002C;  //TIM1_ARR 
    DMA_InitStructure.DMA_Memory0BaseAddr       = (uint32_t)(&(VPW_Data.Period[vpw_buf][0]));
    DMA_InitStructure.DMA_BufferSize            = VPW_Data.Num_Period[vpw_buf];  
    DMA_Init(DMA2_Stream1, &DMA_InitStructure);

    DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5);
    DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1);

    DMA_ITConfig(DMA2_Stream5, DMA_IT_TC, ENABLE);

    TIM_ClearITPendingBit(TIM1,TIM_IT_CC1 );
    TIM_ClearITPendingBit(TIM1,TIM_IT_Update );

    TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);//一個改變週期
    TIM_DMACmd(TIM1, TIM_DMA_CC1, ENABLE);   //一個改變佔空比       
 //   TIM_Cmd(TIM1, ENABLE);   

    //vpw in 
    TIM_DeInit(TIM4);
    TIM_TimeBaseStructure.TIM_Period        = 65535;             
    TIM_TimeBaseStructure.TIM_Prescaler     = 0;            
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;       
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up ; 
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_ARRPreloadConfig(TIM4, DISABLE);
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);  

    TIM_ICInitStructure.TIM_Channel     = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Falling; 
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;    
    TIM_ICInitStructure.TIM_ICFilter    = 0x2;   

    TIM_PWMIConfig(TIM4, &TIM_ICInitStructure);
    TIM_ITConfig(TIM4, TIM_IT_CC2, ENABLE);  

    TIM_DMACmd(TIM4, TIM_DMA_CC1, ENABLE); 
    TIM_DMACmd(TIM4, TIM_DMA_CC2, ENABLE);

    TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2); 
    TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);
    TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable); 
    TIM_Cmd(TIM4, ENABLE);

    //vpw in dma         
    DMA_DeInit(DMA1_Stream0);   //cc1
    DMA_DeInit(DMA1_Stream3);   //cc2
    while(DMA_GetCmdStatus(DMA1_Stream0)!=DISABLE);
    while(DMA_GetCmdStatus(DMA1_Stream3)!=DISABLE);

    DMA_InitStructure.DMA_Channel               = DMA_Channel_2;  
    DMA_InitStructure.DMA_PeripheralBaseAddr    = (uint32_t)0x40000834;         //TIM4_CCR1
    DMA_InitStructure.DMA_Memory0BaseAddr       = (uint32_t)(&(VPW_Data.Pulse[vpw_buf][0]));
    DMA_InitStructure.DMA_DIR                   = DMA_DIR_PeripheralToMemory ;
    DMA_InitStructure.DMA_BufferSize            = VPW_MAX;
    DMA_InitStructure.DMA_PeripheralInc         = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc             = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize    = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize        = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode                  = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority              = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode              = DMA_FIFOMode_Enable;        
    DMA_InitStructure.DMA_FIFOThreshold         = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst           = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst       = DMA_PeripheralBurst_Single;

    DMA_Init(DMA1_Stream0, &DMA_InitStructure); 

    DMA_InitStructure.DMA_PeripheralBaseAddr     = (uint32_t)0x40000838;        //tim4 CCR2
    DMA_InitStructure.DMA_Memory0BaseAddr        = (uint32_t)(&(VPW_Data.Period[0]));
    DMA_Init(DMA1_Stream3, &DMA_InitStructure);   

}

    需要實現的功能是PC串列埠以115200波特率與epm1270通訊,實現J1850協議的VPW和PWM兩種位編碼的匯流排收發。

 

注意:1、匯流排傳送仲裁

         2、匯流排資料幀的驗證遮蔽

         3、IFR的設定與使能

         4、位編碼VPW與PWM的選擇

         5、一幀資料的判正工作

 

外圍電路設計如下:

上圖是為PWM收發設計外圍電路

上圖是為VPW設計的外圍電路

CPLD的引腳連線如上圖

 

注:5V,12V電源產生的電路圖省略。

相關文章