SAE J1850 汽車匯流排協議 VPW 物理層驅動程式在STM32晶片上的實現
VPW(Variable Pulse Width)是一種可變脈寬調製的汽車匯流排通訊方式,常用於美系的福特,通用,克萊斯勒等汽車上,主要用途為車用資訊中心、儀表顯示、故障檢測診斷等。
VPW – 以資料位為基本單位進行傳輸,定義了一個起始位(SOF):200us 的高電平代表開始進行位傳輸,定義了一個結束位(EOF):280us 的低電平表示位傳輸正常結束,起始位之後的資料位表示方式可認為為:電平不斷的翻轉,每次產生一次翻轉便產生一個新的資料位,這個資料位為“0”還是“1”由翻轉時電平的持續時間來決定,資料位“0”用 64us 的低電平或 128us 的高電平表示,資料位“1” 用 64us 的高電平或 128us 的低電平表示。另在網路節點多的時候VPW 針對鏈路層定義了有效資料域結束位(EOD),幀間仲裁時間(IFS),多資料域時接收節點的應答就緒響應時間(IFR)如下圖所示:
以上為背景知識介紹,下面要根據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電源產生的電路圖省略。
相關文章
- OBD-II Protocol -- SAE J1850 VPW PWMProtocol
- ARM 匯流排協議協議
- AHB匯流排協議協議
- 匯流排協議系列——USART協議初探協議
- 學習筆記-Verilog實現IIC匯流排協議筆記協議
- CAN匯流排協議 學習筆記協議筆記
- linux核心匯流排驅動模型-驅動篇Linux模型
- 基於匯流排裝置驅動模型的按鍵讀取驅動程式模型
- 【linux】驅動-6-匯流排-裝置-驅動Linux
- Flash驅動控制--晶片擦除(SPI協議)晶片協議
- 網路七層協議之物理層協議
- 事件匯流排的設計與實現事件
- Linux驅動之I2C匯流排裝置以及驅動Linux
- 在 .NET 中深入瞭解事件匯流排的使用與實現事件
- CAN匯流排協議簡介及其常見的應用領域協議
- CAN匯流排分析儀工具-CAN轉USB智慧協議轉換器協議
- AndroidEventBus (事件匯流排) 的設計與實現AndroidIDEdev事件
- linux驅動中實現上層select介面Linux
- 匯流排
- 實現一個clickhouse tcp協議客戶端驅動TCP協議客戶端
- 事件匯流排 + 函式計算構建雲上最佳事件驅動架構應用事件函式架構
- WebWorker與WebSocket實現前端訊息匯流排Web前端
- Otto - 安卓平臺上事件匯流排安卓事件
- 事件匯流排事件
- 前端匯流排前端
- linux平臺匯流排驅動裝置模型之點亮LEDLinux模型
- 如何在 JavaScript 中實現 Event Bus(事件匯流排)JavaScript事件
- 如何在 pyqt 中實現全域性事件匯流排QT事件
- 實現一個事件匯流排(vue.prototype.$bus)?事件Vue
- 元件間通訊--利用mitt實現事件匯流排元件MIT事件
- MAVLink通訊協議在STM32上移植,並自定義協議(這篇還寫了在STM32上怎麼收發資料,呼叫哪些函式)協議函式
- STM32驅動LCD實戰
- 自己動手寫事件匯流排(EventBus)事件
- 華清平臺匯流排驅動201208(不同平臺)
- 關於Realtek HD Audio音效卡驅動與系統HD匯流排驅動衝突的問題
- 基於事件匯流排EventBus實現郵件推送功能事件
- 事件匯流排demo事件
- javascript事件匯流排JavaScript事件