STM32F407控制CANopen從站 前面我有篇文章——CAN&CANopen,講清楚了CAN通訊是怎麼一回事,沒有舉具體的例子。這篇文章我就用一個具體的例子,讓大家更好的理解具體是怎麼用。 硬體準備:STM32F407ZGT6開發板+ IXXAT CAN卡+支持CANopen通訊的驅動器 目標 ...
STM32F407控制CANopen從站
前面我有篇文章——CAN&CANopen,講清楚了CAN通訊是怎麼一回事,沒有舉具體的例子。這篇文章我就用一個具體的例子,讓大家更好的理解具體是怎麼用。
硬體準備:STM32F407ZGT6開發板+ IXXAT CAN卡+支持CANopen通訊的驅動器
目標效果:STM32通過CAN口控制驅動器完成PPM和CSP模式的運動控制,對PPM和CSP模式沒有概念的參我的另一篇文章——我理解的運動控制系統,裡面有詳細介紹。
首先,完成STM32的基本配置,我用的cubeMX,這個弄起來快。
第一步,系統時鐘配置,註意紅框標記的地方,我的HSE是8Mhz的,根據你的開發板修改。F407支持的最高頻率是168Mhz,不可超過,關於時鐘配置的細節可以參官方的參考手冊,這裡不展開講了。
第二步,配置HSE為陶瓷晶振。
第三步,配置下載和調試介面。
第四步,CAN控制配置,我設置的波特率是1Mbps,這也是CAN匯流排支持的最高通訊速率。CAN匯流排上的所有設備波特率必須一樣,這是能通訊的前提,不然解出來的都是錯誤幀。還要使能CAN的接收中斷。
第五步,配置USART,這個是為了調試方便和接收控制命令用的。
使能USART的中斷
USART的發送和接收都是DMA傳輸,網傳接收和發送的DMA不可以同時使用,實測可以解決,DMA非常方便。
第六步,配置TIMER,這個是CSP模式定時發送數據用的。匯流排是168Mhz的,168-1的預分頻之後就是1Mhz,向上計數10000就是10ms。
需要開啟TIM6的中斷
第七步,工程配置,我用的KEIL,在工具鏈中選擇MDK-ARM,版本V5。
代碼生成配置,選擇所有已用的庫到工程。每個外設配置生成單獨的.c/.h文件,方便查看和管理。再次生產代碼前先備份。再次生成前保留用戶代碼,這個一定要選,並且還要寫在用戶代碼區,不然重生成後代碼都被刪除了。不再需要的配置再重生成代碼的時候刪除,這個可選,文件少編譯的更快。把所有沒有使用的引腳都設置為模擬模式,這樣可以減少功耗。
第八步,點擊GENERATE CODE生成代碼,然後用你的IDE打開工程。
到這裡cubeMX的配置就已經完成了,接下來就直接開始在IDE中上代碼了。
CAN控制器的初始化中沒有配置CAN的濾波器,這個需要我們手動配置。代碼放在can.c的void MX_CAN1_Init(void)函數中,如下:
/* USER CODE BEGIN CAN1_Init 2 */
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterActivation = CAN_FILTER_ENABLE;
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.SlaveStartFilterBank = 14;
if(HAL_CAN_ConfigFilter(&hcan1,&sFilterConfig))
{
Error_Handler();
}
HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
/* USER CODE END CAN1_Init 2 */
這裡說下濾波器配置的兩種模式,一個是列表模式,一個是掩碼模式。列表模式就是ID在列表中的報文可以通過,其他的都不能通過,不在列表中的都不能通過,這個比較好理解。掩碼模式就是掩碼寄存器中對應bit為1的表示關心,報文中ID的對應bit位也必須為1;掩碼寄存器中對應bit為0的表示不關心,報文ID的對應bit位可為0或1。如現在配置的都是0x0000,表示任何ID的數據都接收,因為現在是把STM32做CANopen的master,需要接收匯流排上的所有數據。最後一行HAL_CAN_ActivateNotification是為了使能接收郵箱的中斷。
接下來就是初始化各個外設,這裡遇到兩個坑,第一個是DMA的初始化要在使用DMA的外設之前,不然就不會成功。第二個是使能定時器中斷的時候要先停止定時器中斷,追溯源代碼發現是函數有個狀態沒複位,停止中斷的函數裡面將這個狀態複位了。這個可能與固件版本有關,我這個V1.27.0是這樣,STM32G474的庫也不需要這樣操作。USART的DMA接收我用了擴展函數HAL_UARTEx_ReceiveToIdle_DMA,這個非常方便。
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
MX_CAN1_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Stop_IT(&htim6); // stop_IT function is necessary ,for reset state ,else TIMER won't work
HAL_TIM_Base_Start_IT(&htim6);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_rx_data,uart_rx_max);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
HAL_CAN_Start(&hcan1);
/* USER CODE END 2 */
到這裡,主要的配置就已經完成了,接下來就是細化各個功能函數了,不再詳細介紹,我直接把main.c的代碼全部複製到文章末尾了,可以直接複製去測試和研究。
本文只是拋磚引玉的介紹了怎樣配置和使用STM32F407的CAN控制器,怎樣發送CAN報文去控制從站驅動器,沒有涉及到整個CANopen主站的協議。如網路管理,錯誤處理這些都沒有做,對於特定的項目我覺得可以根據需要去設計功能,不一定要實現協議的全部細節。後續有時間再弄個完整的CANopen master協議棧。關於驅動器調試和配置部分這裡沒有涉及,參見對於驅動器廠家的調試和使用說明即可。文章中比較陌生的名詞,可以參見我的另外兩篇文章——我理解的運動控制系統和CAN&CANopen。
CAN與CANOPEN - Let'sDoSomething - 博客園 (cnblogs.com)
我理解的運動控制系統 - Let'sDoSomething - 博客園 (cnblogs.com)
1 /* USER CODE BEGIN Header */ 2 /** 3 ****************************************************************************** 4 * @file : main.c 5 * @brief : Main program body 6 ****************************************************************************** 7 * @attention 8 * 9 * Copyright (c) 2022 STMicroelectronics. 10 * All rights reserved. 11 * 12 * This software is licensed under terms that can be found in the LICENSE file 13 * in the root directory of this software component. 14 * If no LICENSE file comes with this software, it is provided AS-IS. 15 * 16 ****************************************************************************** 17 */ 18 /* USER CODE END Header */ 19 /* Includes ------------------------------------------------------------------*/ 20 #include "main.h" 21 #include "can.h" 22 #include "dma.h" 23 #include "tim.h" 24 #include "usart.h" 25 #include "gpio.h" 26 27 /* Private includes ----------------------------------------------------------*/ 28 /* USER CODE BEGIN Includes */ 29 #include "stdio.h" 30 #include "string.h" 31 /* USER CODE END Includes */ 32 33 /* Private typedef -----------------------------------------------------------*/ 34 /* USER CODE BEGIN PTD */ 35 36 /* USER CODE END PTD */ 37 38 /* Private define ------------------------------------------------------------*/ 39 /* USER CODE BEGIN PD */ 40 /* USER CODE END PD */ 41 42 /* Private macro -------------------------------------------------------------*/ 43 /* USER CODE BEGIN PM */ 44 45 /* USER CODE END PM */ 46 47 /* Private variables ---------------------------------------------------------*/ 48 49 /* USER CODE BEGIN PV */ 50 uint8_t testData[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}; 51 //CAN transmit data field, 8 bytes 52 uint8_t object_data[8]={0}; 53 //CSP mode conuts, work until CSP_num > CSP_MAX_NUM 54 uint8_t CSP_num = 0; 55 //csp mode max number, generate CSP_pos relatively 56 uint8_t CSP_MAX_NUM = 61; 57 //CSP_flag, 0 means unused/finished, 1 means CSP is working 58 uint8_t CSP_flag = 0; 59 //CAN transmit mailbox 60 uint32_t TxMailBox = CAN_TX_MAILBOX0; 61 //CAN transmit frame struct 62 CAN_TxHeaderTypeDef TxHeader; 63 //for CAN communication store data 64 uint8_t RxData[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 65 //store CAN receive data temporarily, convert byte to int used 66 uint8_t can_Frame_DataField[4] ={0}; 67 //store CAN communication PDO data temporarily ,length could be longer/shorter ,upon PDO length 68 uint8_t PDO_DataField[4] = {0}; 69 //CAN Receive frame struct 70 CAN_RxHeaderTypeDef RxHeader; 71 //CAN receive flag ,set by can_receive interrupt ,reset by other function 72 uint8_t can_receive_flag = 0; 73 //store uart receive data 74 uint8_t uart_rx_data[]={0}; 75 //max uart receive length 76 uint16_t uart_rx_max = 255; 77 //store result for ConvertInttoFourByte function 78 uint8_t byte_value[4] = {0}; 79 //store CSP mode position points 80 int32_t CSP_Pos[61]={0}; 81 //CSP mode acceleration, larger and faster , smaller and slower 82 uint32_t acc = 5; 83 84 /* USER CODE END PV */ 85 86 /* Private function prototypes -----------------------------------------------*/ 87 void SystemClock_Config(void); 88 /* USER CODE BEGIN PFP */ 89 void CAN_sendTxMessage(uint32_t std_id,uint32_t length,uint8_t data[]); 90 void ConvertIntTo4Byte(int32_t source); 91 int32_t ConvertByteToInt(uint8_t *byte_source); 92 void do_a_PPM_Motion(); 93 void do_a_CSP_Motion(); 94 void RPDO1_Mapping(); 95 96 /* USER CODE END PFP */ 97 98 /* Private user code ---------------------------------------------------------*/ 99 /* USER CODE BEGIN 0 */ 100 101 /* USER CODE END 0 */ 102 103 /** 104 * @brief The application entry point. 105 * @retval int 106 */ 107 int main(void) 108 { 109 /* USER CODE BEGIN 1 */ 110 111 /* USER CODE END 1 */ 112 113 /* MCU Configuration--------------------------------------------------------*/ 114 115 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 116 HAL_Init(); 117 118 /* USER CODE BEGIN Init */ 119 120 /* USER CODE END Init */ 121 122 /* Configure the system clock */ 123 SystemClock_Config(); 124 125 /* USER CODE BEGIN SysInit */ 126 127 /* USER CODE END SysInit */ 128 /* Initialize all configured peripherals */ 129 MX_GPIO_Init(); 130 MX_DMA_Init(); 131 MX_USART1_UART_Init(); 132 133 MX_TIM6_Init(); 134 MX_CAN1_Init(); 135 /* USER CODE BEGIN 2 */ 136 HAL_TIM_Base_Start(&htim6); 137 HAL_TIM_Base_Stop_IT(&htim6); // stop_IT function is necessary ,for reset state ,else TIMER won't work 138 HAL_TIM_Base_Start_IT(&htim6); 139 140 __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); 141 HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_rx_data,uart_rx_max); 142 __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); 143 144 HAL_CAN_Start(&hcan1); 145 146 HAL_Delay(50); //delay would be necessary ,shorter time is also OK 147 148 printf("This is CAN communication test program!\n"); 149 printf("After power up!Send 1 to activate PPM mode or send 2 to CSP mode\n"); 150 RPDO1_Mapping(); 151 HAL_Delay(10); 152 153 /* USER CODE END 2 */ 154 155 /* Infinite loop */ 156 /* USER CODE BEGIN WHILE */ 157 while (1) 158 { 159 /* USER CODE END WHILE */ 160 161 /* USER CODE BEGIN 3 */ 162 HAL_Delay(500); 163 HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_3); 164 165 if(1 == uart_rx_data[0]) 166 { printf("PPM TEST\n"); 167 do_a_PPM_Motion(); 168 uart_rx_data[0] = 0; 169 } 170 else if(2 == uart_rx_data[0]) 171 { printf("CSP TEST\n"); 172 //initiate CSP mode pos array 173 for(int i=0;i<CSP_MAX_NUM;i++) 174 { 175 //means explanation: physic formula S= 1/2*acc*(t*t) 176 // S= 1/2*a*t*t a=100, acceleration 30points ,dec 30 points 177 // Vmax= 30*a = 30*100 = 3000 178 if(i<=30) 179 CSP_Pos[i] = 0.5*acc*i*i; // S= 1/2*a*t*t a=100, 180 else 181 CSP_Pos[i] = CSP_Pos[i-1]+acc*(CSP_MAX_NUM/2)-0.5*acc*(2*(i - CSP_MAX_NUM/2)-1); 182 } 183 do_a_CSP_Motion(); 184 uart_rx_data[0] = 0; 185 } 186 187 } 188 /* USER CODE END 3 */ 189 } 190 191 /** 192 * @brief System Clock Configuration 193 * @retval None 194 */ 195 void SystemClock_Config(void) 196 { 197 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 198 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 199 200 /** Configure the main internal regulator output voltage 201 */ 202 __HAL_RCC_PWR_CLK_ENABLE(); 203 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); 204 205 /** Initializes the RCC Oscillators according to the specified parameters 206 * in the RCC_OscInitTypeDef structure. 207 */ 208 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; 209 RCC_OscInitStruct.HSEState = RCC_HSE_ON; 210 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 211 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; 212 RCC_OscInitStruct.PLL.PLLM = 4; 213 RCC_OscInitStruct.PLL.PLLN = 168; 214 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; 215 RCC_OscInitStruct.PLL.PLLQ = 4; 216 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 217 { 218 Error_Handler(); 219 } 220 221 /** Initializes the CPU, AHB and APB buses clocks 222 */ 223 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 224 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 225 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 226 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 227 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; 228 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; 229 230 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) 231 { 232 Error_Handler(); 233 } 234 } 235 236 /* USER CODE BEGIN 4 */ 237 void do_a_CSP_Motion() 238 { 239 //steps 1. disable motor, write 0x06 to object 0x6040 240 // 2. chang mode to CSP, write 0x08 to object 0x6060 241 // 3. check mode is CSP, read 0x6061 242 // 4. set CSP cycle-time, write 0x32 to object 0x6060,unit is ms 243 // 5. enable motor, write 0x0F to object 0x6040 244 // 6. check motor is enabled, read 0x6041 245 // 7. read current position, read 0x607A 246 // 8. CPS_Pos[]+current position as final target position 247 // 9. start remote node 248 // 10. send target position by PDO and follow SYNC command 0x80 249 // 11. CSP motion finished 250 // 251 //steps 1. disable motor, write 0x06 to object 0x6040 252 object_data[0]=0x2B;object_data[1]=0x40;object_data[2]=0x60;object_data[3]=0x00; 253 object_data[4]=0x06;object_data[5]=0x00;object_data[6]=0x00;object_data[7]=0x00; 254 can_receive_flag = 0; 255 CAN_sendTxMessage(0x601,8,object_data); 256 HAL_Delay(1); //HAL_DELAY() delay 1ms is necessary, else will stick here 257 while(1 != can_receive_flag) 258 {;} //HAL_DELAY() delay 1ms is necessary, else will stick here 259 // 2. chang mode to CSP, write 0x08 to object 0x6060 260 object_data[0]=0x2F;object_data[1]=0x60;object_data[2]=0x60;object_data[3]=0x00; 261 object_data[4]=0x08;object_data[5]=0x00;object_data[6]=0x00;object_data[7]=0x00; 262 can_receive_flag = 0; 263 CAN_sendTxMessage(0x601,8,object_data); 264 HAL_Delay(1); 265 while(1 != can_receive_flag) 266 {;} 267 // 3. check mode is CSP, read 0x6061 268 object_data[0]=0x40;object_data[1]=0x61;object_data[2]=0x60;object_data[3]=0x00; 269 object_data[4]=0x00;object_data[5]=0x00;object_data[6]=0x00;object_data[7]=0x00; 270 can_receive_flag = 0; 271 CAN_sendTxMessage(0x601,8,object_data); 272 HAL_Delay(1); 273 while(1 != can_receive_flag) 274 {;} 275 if(8 != RxData[4]) //8 means CSP mode 276 printf("Set CSP mode failed\n"); 277 // 4. set CSP cycle-time, write 0x32 to object 0x6060,unit is ms 278 object_data[0]=0x2F;object_data[1]=0xC2;object_data[2]=0x60;object_data[3]=0x00; 279 object_data[4]=0x32;object_data[5]=0x00;object_data[6]=0x00;object_data[7]=0x00; 280 can_receive_flag = 0; 281 CAN_sendTxMessage(0x601,8,object_data); 282 HAL_Delay(1); 283 while(1 != can_receive_flag) 284 {;} 285 // 5. enable motor, write 0x0F to object 0x6040 286 object_data[0]=0x2B;object_data[1]=0x40;object_data[2]=0x60;object_data[3]=0x00; 287 object_data[4]=0x0F;object_data[5]=0x00;object_data[6]=0x00;object_data[7]=0x00; 288 can_receive_flag = 0; 289 CAN_sendTxMessage(0x601,8,object_data); 290 HAL_Delay(1); 291 while(1 != can_receive_flag) 292 {;} 293 // 6. check motor is enabled, read 0x6041 294 object_data[0]=0x40;object_data[1]=0x41;object_data[2]=0x60;object_data[3]=0x00;