基于STM32F4的CANOpen移植教程(超级详细)
CANopen移植到STM32F4平臺(tái)
- 前言
- 1 物品準(zhǔn)備
- 2 相關(guān)軟件安裝
- 2.1 CAN上位機(jī)
- 2.2 對(duì)象字典生成工具objdictedit環(huán)境配置
- 3 將CANopen移植到STM32F407
- 3.1 基礎(chǔ)代碼移植
- 3.11 h文件移植
- 3.12 c文件移植
- 3.2 建立自己的底層驅(qū)動(dòng)文件
- 3.3 建立詞典
- 3.4工程配置
- 3.41 c文件添加
- 3.42 頭文件路徑添加
- 3.43 c99標(biāo)準(zhǔn)選擇
- 3.44 調(diào)試串口設(shè)置
- 3.45 程序啟動(dòng)
- 4 末尾
本專題相關(guān)教程:
基于STM32F4的CANOpen移植教程
基于STM32F4的CANopen快速SDO通信
linux下CANopen for python的使用
基于Linux C的CANopen移植
CANopen補(bǔ)充–時(shí)間計(jì)算出錯(cuò)
CANopen補(bǔ)充–主站檢測節(jié)點(diǎn)是否在線
前言
為了在STM32F4上能夠運(yùn)行CANopen(CanFestival),跟著網(wǎng)上的教程操作,發(fā)現(xiàn)總是不夠詳細(xì)。主要是配置和代碼運(yùn)行部分基本沒有解釋。為了后來者能夠少走彎路,便有了這篇教程。關(guān)于CANopen協(xié)議本身本文不做過多介紹,主要是介紹如何使用軟件和代碼修改。
本文配套資料下載地址:https://pan.baidu.com/s/1FMp7xuJ1r3gJTPB0wf3Yrw?pwd=osxs
提取碼:osxs
廢話不多說,GOGOGO。
1 物品準(zhǔn)備
| USB-CAN模塊/USB-CAN盒子 | 用以監(jiān)聽數(shù)據(jù)(如實(shí)在沒有的話,代碼里添加串口反饋也勉強(qiáng)能測試) |
| Canfestival- 3 源代碼 | CANopen源代碼 /本文資料里有 |
| STM32F4 裸工程 | 移植目標(biāo)平臺(tái)代碼,這里用正點(diǎn)原子的空白工程即可 /本文資料里有 |
| CANopen輕松入門.pdf-周立功 | pdf書籍,用以學(xué)習(xí)CANopen協(xié)議 /本文資料里有 |
USB-CAN模塊,比如下面這個(gè),買啥都行,有這個(gè)功能就ok。
USB-CAN盒子,如下,相比模塊,多了一些功能(我用的就這個(gè),不過好像多的功能我并沒有用上)
CANopen輕松入門.pdf-周立功鏈接 下載地址
2 相關(guān)軟件安裝
2.1 CAN上位機(jī)
如果使用USB-CAN盒子,找店家要上位機(jī)資料即可。比如我用的這款資料如下:
驅(qū)動(dòng):驅(qū)動(dòng)下載
驅(qū)動(dòng)安裝教程:驅(qū)動(dòng)安裝視頻
上位機(jī)軟件:上位機(jī)下載地址
打開設(shè)備-選擇設(shè)備-選擇對(duì)應(yīng)波特率即可。
如果是普通的USB-CAN模塊,找店家應(yīng)該也有資料。使用CANpro協(xié)議平臺(tái)分析軟件即可,這個(gè)網(wǎng)上搜很容易搜得到。附一個(gè)我隨便找的鏈接:CANPro協(xié)議分析平臺(tái)官方下載
同理,啟動(dòng)-選擇設(shè)備(不對(duì)就反復(fù)選)-選擇波特率。
2.2 對(duì)象字典生成工具objdictedit環(huán)境配置
? CANopen需要使用到字典,路徑:源代碼/objdictgen/objdictedit.py。這是個(gè)基于python2.7才能運(yùn)行的程序,因此我們先裝環(huán)境。
安裝環(huán)境,遇到了很多坑。主要是網(wǎng)上教程很多偏老,跟著操作,各種bug。最終成功的一個(gè)搭配是
| python-2.7.15.amd64.msi | |
| wxPython3.0-win64-3.0.2.0-py27.exe | 使用2.8會(huì)導(dǎo)致在安裝下邊軟件的時(shí)候,提示包缺失 |
| Gnosis_Utils-1.2.2.zip |
安裝教程參考:CanFestival中對(duì)象字典編輯器objdictedit的正確打開環(huán)境_lei_g的博客-CSDN博客_canfestival中對(duì)象字典編輯器的打開
備注:python2.7和自己之前安裝的如python3.7是不沖突的。
要使用objdictedit,可以使用這個(gè)方式固定到任務(wù)欄。方便以后打開。
選擇默認(rèn)程序–>更多應(yīng)用–>在這臺(tái)電腦上查找其他應(yīng)用–>選擇python2.7文件夾里的python.exe
當(dāng)打開下邊程序的時(shí)候,在桌面任務(wù)欄選擇:固定到任務(wù)欄。那么以后都可以右鍵這個(gè)圖標(biāo),點(diǎn)擊上邊的objdictedit.py即可打開軟件。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-8awyPdNN-1646308038808)(C:\Users\FEN\AppData\Roaming\Typora\typora-user-images\image-20220303104232531.png)]
3 將CANopen移植到STM32F407
首先,這是我給大家準(zhǔn)備的禮物,截圖如下:
| CanFestival-3源碼 | 內(nèi)含1個(gè)官方源碼與3個(gè)分支,我們使用Mongo-canfestival,因?yàn)樗袑?duì)于cm4內(nèi)核的支持 |
| CANopen裸工程 | 本教程所使用的空白代碼 |
| CANopen最終移植代碼 | 本教程所使用的移植好的最終代碼 |
| USBCAN調(diào)試軟件 | USB-CAN盒子的上位機(jī) |
| 字典工具安裝 | 字典工具安裝所需文件 |
3.1 基礎(chǔ)代碼移植
打開我們的空白工程,界面如圖,空空如也。需要說明的是,個(gè)人喜歡把所有頭文件放入main.h,這樣其他外設(shè)文件只用包含main.h即可。
文件夾如下:
我們新建一個(gè)文件夾,名為CANopen,用于存放所有與CANopen有關(guān)的代碼。
里面再新建幾個(gè)子文件夾。
說明如下:
| dictionary | 存放字典和其對(duì)應(yīng)的.c /.h 文件 |
| hardware | 外設(shè)的驅(qū)動(dòng)文件,如定時(shí)器,CAN,還有配置文件 |
| inc | 由CANopen源代碼移植過來的h文件 |
| src | 由CANopen源代碼移植過來的c文件 |
3.11 h文件移植
進(jìn)入源代碼/include目錄,先將該目錄下19個(gè)h文件,都復(fù)制到新工程/CANopen/inc 里,再復(fù)制cm4文件夾(內(nèi)含3個(gè)h文件)更名為stm32。如下:
修改一下stm32/canfestival.h文件,添加3行語句,防止遞歸調(diào)用。
進(jìn)入源代碼\examples\AVR\Slave目錄,把config文件,移植到新工程/CANopen/hardware
并對(duì)config做一點(diǎn)修改。
3.12 c文件移植
進(jìn)入源代碼/src目錄,將該目錄下除了symbols.c之外的12個(gè)c文件,復(fù)制到新工程/CANopen/src 里。
刪除dcf.c文件下第59、98行前面的“inline”關(guān)鍵字
3.2 建立自己的底層驅(qū)動(dòng)文件
? 在裸工程/CANopen/hardware下新建定時(shí)器、CAN的c/h文件。其中定時(shí)器用于時(shí)間獲取,CAN是通信基礎(chǔ)。
需要說明的是,CANopen源代碼里含有timer.c 文件,為了命名不沖突,我這里起名加了后綴。比如使用定時(shí)器3,就建立timer3.c。
如圖,我們使用了can1,timer2, config.h為之前移植的文件,不用管。
| can1 | 中斷優(yōu)先級(jí)為1(無所謂);波特率設(shè)置為1M(1M或者500K都行,要與config.h一致) |
| timer2 | 中斷優(yōu)先級(jí)1(無所謂);時(shí)鐘84M,分頻840,即基礎(chǔ)頻率為100K(要求與timerscfg.h里的配置即可),重裝載值為65535,即0.65s一次溢出中斷 |
can與timer的代碼移植自源代碼/drivers/cm4。cm4是基于stm32F3的,因此有些代碼需要修改
cm4.c里面包含can1與timer3的初始化代碼以及一些封裝好的代碼。我們將其各自復(fù)制到can1.c和timer3.c。并根據(jù)板子情況做修改。
大家可以到移植成功的工程里看看有啥修改。
can1.c
#include "can1.h"static CO_Data *co_data = NULL;//Initialize the CAN hardware unsigned char CAN1_Init(CO_Data * d, uint32_t bitrate) {GPIO_InitTypeDef GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;CAN_InitTypeDef CAN_InitStructure;CAN_FilterInitTypeDef CAN_FilterInitStructure;/* save the canfestival handle */ co_data = d;/* CAN GPIOs configuration **************************************************//* Enable GPIO clock */RCC_AHB1PeriphClockCmd(CAN_GPIO_CLK, ENABLE);/* Connect CAN pins to AF7 */GPIO_PinAFConfig(CAN_GPIO_PORT, CAN_RX_SOURCE, GPIO_AF_CANx);GPIO_PinAFConfig(CAN_GPIO_PORT, CAN_TX_SOURCE, GPIO_AF_CANx); /* Configure CAN RX and TX pins */GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN | CAN_TX_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);/* NVIC configuration *******************************************************/NVIC_InitStructure.NVIC_IRQChannel = CANx_RX0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);/* CAN configuration ********************************************************/ /* Enable CAN clock */RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);/* CAN register init */CAN_DeInit(CANx);CAN_StructInit(&CAN_InitStructure);/* CAN cell init */CAN_InitStructure.CAN_TTCM = DISABLE;CAN_InitStructure.CAN_ABOM = DISABLE;CAN_InitStructure.CAN_AWUM = DISABLE;CAN_InitStructure.CAN_NART = DISABLE;CAN_InitStructure.CAN_RFLM = DISABLE;CAN_InitStructure.CAN_TXFP = DISABLE;CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;/* CAN Baudrate (CAN clocked at 42 MHz) 42e6 / ( 6 * (1+BS1+BS2)) */CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;if(bitrate == 1000000){CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;}else { //除去1M頻率。剩下都配置為500KCAN_InitStructure.CAN_BS1 = CAN_BS1_6tq;CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq;}CAN_InitStructure.CAN_Prescaler = 6;CAN_Init(CANx, &CAN_InitStructure);/* CAN filter init */CAN_FilterInitStructure.CAN_FilterNumber = 0;CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;CAN_FilterInit(&CAN_FilterInitStructure);/* Enable FIFO 0 message pending Interrupt */CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);return 1; }// The driver send a CAN message passed from the CANopen stack unsigned char canSend(CAN_PORT notused, Message *m) {int i, res;CanTxMsg TxMessage = {0};TxMessage.StdId = m->cob_id;TxMessage.IDE = CAN_ID_STD;if(m->rtr)TxMessage.RTR = CAN_RTR_REMOTE;elseTxMessage.RTR = CAN_RTR_DATA;TxMessage.DLC = m->len;for(i=0 ; i<m->len ; i++)TxMessage.Data[i] = m->data[i]; res = CAN_Transmit(CANx, &TxMessage);if(res == CAN_TxStatus_NoMailBox)return 0; // errorreturn 1; // succesful }//The driver pass a received CAN message to the stack /* unsigned char canReceive(Message *m) { } */ unsigned char canChangeBaudRate_driver( CAN_HANDLE fd, char* baud) {return 0; }/*** @brief This function handles CAN1 RX0 interrupt request.* @param None* @retval None*/void CAN1_RX0_IRQHandler(void){int i;CanRxMsg RxMessage = {0};Message rxm = {0};CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);// Drop extended framesif(RxMessage.IDE == CAN_ID_EXT) //不處理擴(kuò)展幀return;rxm.cob_id = RxMessage.StdId;if(RxMessage.RTR == CAN_RTR_REMOTE)//遠(yuǎn)程幀rxm.rtr = 1;rxm.len = RxMessage.DLC;for(i=0 ; i<rxm.len ; i++)rxm.data[i] = RxMessage.Data[i];canDispatch(co_data, &rxm);//CANopen自身的處理函數(shù),因?yàn)榭焖賁DO不需要反饋,所以在上邊處理后就不需要調(diào)用這步了}can1.h
#ifndef __CAN1_H #define __CAN1_H#include "sys.h" #include "main.h" #include "data.h" // CAN bus defines for cortex-M4 STM32F407#define CANx CAN1 #define CAN_CLK RCC_APB1Periph_CAN1 #define CAN_RX_PIN GPIO_Pin_11 #define CAN_TX_PIN GPIO_Pin_12 #define CAN_GPIO_PORT GPIOA #define CAN_GPIO_CLK RCC_AHB1Periph_GPIOA #define CANx_RX0_IRQn CAN1_RX0_IRQn#define GPIO_AF_CANx GPIO_AF_CAN1 #define CAN_RX_SOURCE GPIO_PinSource11 #define CAN_TX_SOURCE GPIO_PinSource12unsigned char CAN1_Init(CO_Data * d, uint32_t bitrate);#endiftimer3.c
#include "timer3.h"TIMEVAL last_counter_val = 0; TIMEVAL elapsed_time = 0;// Initializes the timer, turn on the interrupt and put the interrupt time to zero void TIM3_Init(void) {NVIC_InitTypeDef NVIC_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;/* TIM3 clock enable */RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);/* Enable the TIM3 gloabal Interrupt */NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);/* Compute the prescaler value */uint16_t PrescalerValue =840-1; //84M頻率/840為100k(與timerscfg.h配置一致即可),即10us間隔/* Time base configuration */TIM_TimeBaseStructure.TIM_Period = 65535;TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);TIM_ClearITPendingBit(TIM3, TIM_SR_UIF);/* TIM3 enable counter */ //這里需要啟動(dòng)定時(shí)器TIM_Cmd(TIM3, ENABLE);/* Preset counter for a safe start */TIM_SetCounter(TIM3, 1);/* TIM Interrupts enable */TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); }//Set the timer for the next alarm. void setTimer(TIMEVAL value) {uint32_t timer = TIM_GetCounter(TIM3); // Copy the value of the running timerelapsed_time += timer - last_counter_val;last_counter_val = 65535-value;TIM_SetCounter(TIM3, 65535-value);TIM_Cmd(TIM3, ENABLE);//printf("setTimer %lu, elapsed %lu\r\n", value, elapsed_time); }//Return the elapsed time to tell the Stack how much time is spent since last call. TIMEVAL getElapsedTime(void) {uint32_t timer = TIM_GetCounter(TIM3); // Copy the value of the running timerif(timer < last_counter_val)timer += 65535;TIMEVAL elapsed = timer - last_counter_val + elapsed_time;//printf("elapsed %lu - %lu %lu %lu\r\n", elapsed, timer, last_counter_val, elapsed_time);return elapsed; }// This function handles Timer 3 interrupt request. void TIM3_IRQHandler(void) {//printf("--\r\n");if(TIM_GetFlagStatus(TIM3, TIM_SR_UIF) == RESET)return;last_counter_val = 0;elapsed_time = 0;TIM_ClearITPendingBit(TIM3, TIM_SR_UIF);TimeDispatch(); }timer3.h
#ifndef __TIMER3_H #define __TIMER3_H#include "sys.h" #include "main.h"void TIM3_Init(void);#endif2022年3月18日記:
定時(shí)器實(shí)現(xiàn)函數(shù)存在缺陷,當(dāng)超過一個(gè)功能需要調(diào)用時(shí)間時(shí),會(huì)存在干涉。各位如果除了心跳報(bào)文發(fā)送之外,沒用到其他需要時(shí)間的功能(節(jié)點(diǎn)掉線檢測/pdo之類),那么可以忽略。不然可以看一下這個(gè)CANopen補(bǔ)充–時(shí)間計(jì)算出錯(cuò)。
3.3 建立詞典
我們起名字為Master,使用心跳管理,這樣我們待會(huì)便可以通過心跳報(bào)文來判斷移植成功與否。
在字典里設(shè)置心跳報(bào)文間隔為1000ms(0x3E8)。這樣,它每隔1000ms就會(huì)發(fā)送一個(gè)心跳報(bào)文。
點(diǎn)擊保存,將生成的.od文件放入CANopen/dictionnary文件夾。
再點(diǎn)擊建立詞典,同樣將生成的.c文件放入CANopen/dictionnary文件夾。
效果如下:
| .od文件 | 詞典工程文件,用于配置,不會(huì)被工程調(diào)用 |
| .c .h | 詞典文件對(duì)應(yīng)的c和h文件。需要被工程調(diào)用 |
3.4工程配置
文件都弄好了,我們打開keil軟件,將這些文件都加入到工程。
3.41 c文件添加
在Groups里新建兩個(gè)文件夾。需要說明的時(shí)候,為了美觀,這里把詞典文件和外設(shè)驅(qū)動(dòng)文件放在一起了。
| CANopen | 含CANopen/src |
| CANopen_Driver | 含CANopen/hardware 和CANopen/dictionary。 |
3.42 頭文件路徑添加
3.43 c99標(biāo)準(zhǔn)選擇
由于源碼很多地方,把定義語句放在賦值語句之后,這只在C99標(biāo)準(zhǔn)之后允許,因此勾選C99模式。
3.44 調(diào)試串口設(shè)置
? 使用工程自帶的USART1。
? 警告,在項(xiàng)目中正常運(yùn)行后,一定要關(guān)閉調(diào)試功能,不然串口發(fā)送數(shù)據(jù)會(huì)嚴(yán)重降低相應(yīng)速度!!!!!
我們打開applicfg.h ,如果找不到,直接全局搜索:MSG(…) 便可定位到啦。
第一,添加debug的定義 再次警告,在項(xiàng)目中正常運(yùn)行后,記得關(guān)閉(把定義注釋掉);第二,把打印函數(shù)里的\n 改成\r\n。
如圖是串口反饋的效果,還是挺直觀的。沒有USB-CAN的同學(xué)可以通過串口調(diào)試助手來觀察。
3.45 程序啟動(dòng)
首次,在main.h里添加相關(guān)頭文件
main函數(shù)添加canopen初始化。包含定時(shí)器3、串口1、can1的初始化
#include "sys.h" #include "main.h" int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//設(shè)置系統(tǒng)中斷優(yōu)先級(jí)分組4delay_init(168); //初始化延時(shí)函數(shù)TIM3_Init();USART1_Init(115200);CAN1_Init(&Master_Data,1000000);unsigned char nodeID = 0x00; //主站IDsetNodeId(&Master_Data, nodeID);setState(&Master_Data, Initialisation); //節(jié)點(diǎn)初始化setState(&Master_Data, Operational); while(1){delay_ms(1000);} }下載,啟動(dòng)!
使用軟件觀察。
心跳沒有問題,nice
如果大家有需要讓主站檢測節(jié)點(diǎn)是否掉線的需要,可以看CANopen補(bǔ)充–主站檢測節(jié)點(diǎn)是否在線。
4 末尾
? 到這里便移植成功啦。下一篇教程基于STM32F4的CANopen快速SDO通信(超級(jí)詳細(xì))
?
總結(jié)
以上是生活随笔為你收集整理的基于STM32F4的CANOpen移植教程(超级详细)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uniswap合约解读和部署
- 下一篇: 更改MySQL密码并验证,及使用SQLy