基于STM32的CAN总线协议控制步进电机详解

一、功能描述:

如上图所示,实现了以下功能:

1.两块stm32单片机通过CAN控制器与收发器进行半双工通信;
2.stm32主机通过检测按键,切换不同的模式,将不同模式的case值发送给stm32从机;
3.stm32从机根据收到的case值,控制步进电机进行不同的运动操作;
4.OLED用于显示收发内容与按键状态等信息。

二、CAN总线概述

1.CAN总线协议

        CAN总线(Controller Area Network)是一种串行通信协议,最初是由德国Bosch公司在1983年为汽车应用而开发的。然而,由于其高效、可靠的性能,CAN总线已经被广泛用于各种领域,包括工业控制、医疗设备、军事应用等。(来源:ChatGPT)

以下是CAN总线的一些关键特点和特性:

  1. 串行通信: CAN总线使用串行通信,通过两根导线进行数据传输,即CAN_H和CAN_L。这种方式有助于减小线缆数量,降低成本,同时提高系统的可靠性。

  2. 实时性: CAN总线的设计目标之一是提供高实时性,适用于需要及时响应和同步的应用。这使得它特别适用于汽车电子系统等需要高度协同工作的场景。

  3. 多主机网络: CAN总线支持多主机网络,允许多个节点同时发送和接收数据。这使得多个电子控制单元(ECU)能够并行工作,促进系统的分布式控制。

  4. 抗干扰性: CAN总线具有很强的抗干扰能力。它采用不同的电压水平来表示0和1,这使得CAN总线对电磁干扰具有一定的容忍性。

  5. 错误检测和冗余: CAN总线使用CRC(循环冗余校验)来检测传输中的错误,并能够在数据帧中添加冗余位,以提高错误检测和纠正的能力。

  6. 消息优先级: CAN总线使用标识符来给消息分配优先级。低标识符的消息拥有更高的优先级,这有助于确保紧急消息能够在较短的时间内被传输。

  7. 灵活性: CAN总线的协议允许节点通过标识符来识别和筛选消息,从而实现更灵活的通信结构。

        总体而言,CAN总线是一种可靠、高效的通信协议,特别适用于需要实时性和多节点通信的应用场景。在汽车领域,它被广泛用于车辆内部的电子控制系统,如发动机控制单元(ECU)、制动系统、空调系统等。

2.CAN总线网络

        CAN总线⽹络主要挂在CAN_H和CAN_L,各个节点通过这两条线实现信号的串⾏差分传输,为了避免信号的反射和⼲扰,还需要在CAN_H和CAN_L之间接上120欧姆的终端电阻。为什么是120Ω,因为电缆的特性阻抗为120Ω,为了模拟⽆限远的传输线。

3.CAN总线信号

        CAN总线采⽤不归零码位填充技术,信号有两种不同状态,分别为隐形电平和显性电平。CAN的数据总线有两条CAN_H和CAN_L,当没有数据发送时,两条线的电平⼀样都为2.5V,称为静电平,也就是隐性电平。当有信号发送时,CAN_H的电平升⾼1V,即3.5V,CAN_L的电平降低1V即1.5V,此时为显性电平。信号每⼀次传输完后不需要返回到显性电平。

定义 总线电平 逻辑电平
CAN_H-CAN_L<0.5V 隐性电平 1
CAN_H-CAN_L>0.9V 显性电平 0

4.CAN总线工作原理

        ①CAN总线没有主从节点之分,所有CAN总线上的节点都是地位等价的。当CAN总线上的⼀个节点发送数据时,它以报⽂的形式⼴播给⽹络中的⽽所有节点;这样对于每个节点来说,⽆论数据是否发给⾃⼰,都对其进⾏接收。
        ②CAN总线每组报⽂开头的11位字符为标识符,定义了报⽂的优先级;在同⼀个总线中,标识符是唯⼀的,不可能有两个节点发送具有相同标识符的报⽂。当然接收节点也会根据标识符来判断是否接收这帧信息,⼀般称这项技术为报⽂滤波技术。
        ③CAN总线接收节点可以通过远程数据请求发送远程帧请求发送节点发送相应的数据,回应节点传送的数据帧与请求数据的远程帧具有相同的标识符。(接收校对标识符)
        ④CAN总线的优先权由发送数据报⽂中的标识符决定报⽂占⽤总线的优先权;标识符越⼩,优先权越⾼。
        ⑤CAN总线的仲裁机制:只要总线空闲,任何节点都可以向总线发送报⽂。如果有两个或两个以上的节点同时发送报⽂,就会引起总线访问碰撞。通过使⽤标识符逐位仲裁可以解决这个碰撞问题。⽽且当具有相同标识符的数据帧和远程帧同时发送时,数据帧优先于远程帧。
        ⑥⽬前CAN总线通信协议仅仅包括OSI七层互联参考模型中的数据链路层和物理层。

三、代码实操

1.配置CAN总线:

        ①初始化GPIO两个收发引脚(stm32f103c8t6只有一个CAN1控制器,固定引脚)

        ②初始化NVIC中断向量表优先级

        ③初始化CAN相关参数配置

        ④初始化CAN过滤器(筛选器)

void CAN_init() {
	
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	CAN_InitTypeDef CAN_InitStructure;
	CAN_FilterInitTypeDef CAN_FilterInitStruct;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE);	//使能时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); 
/**********************************GPIO****************************************************************/
	//CAN_TX   
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PB9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	       //复用推挽输出,TX
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化PB9
  //CAN_RX	  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//PB8
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        //复用推挽输出,RX
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化PB8  
	

  GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
	
/***********************************CAN工作模式初始化****************************************************/
	
	CAN_DeInit(CAN1);
	CAN_StructInit(&CAN_InitStructure);
	
	//时间触发模式
	CAN_InitStructure.CAN_TTCM = DISABLE;
	//自动离线管理
	CAN_InitStructure.CAN_ABOM = ENABLE;
	//自动唤醒模式
	CAN_InitStructure.CAN_AWUM = ENABLE;
	//报文自动重传 (0为启用)
	CAN_InitStructure.CAN_NART = DISABLE;
	//FIFO溢出时报文覆盖原文件
	CAN_InitStructure.CAN_RFLM = DISABLE;
	//报文发送优先级取决于邮箱先后
	CAN_InitStructure.CAN_TXFP = DISABLE;
	
	//工作模式:普通模式
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
	//设置CAN波特率  (1MHz)
	CAN_InitStructure.CAN_SJW = CAN_SJW_4tq;
	CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
	CAN_InitStructure.CAN_Prescaler = 5;
	
	//初始化CAN
	CAN_Init(CAN1,&CAN_InitStructure);
	//接受到一条报文即可产生中断
	CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);
	
/***********************************NVIC****************************************************************/

  //CAN NVIC 配置   
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化NVIC寄存器
	//使能接收中断
	
/*************************************CAN过滤器******************************************************/


	//使能过滤器
	CAN_FilterInitStruct.	CAN_FilterActivation = ENABLE;
	//过滤器0关联到FIFO0
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_FilterFIFO0;
	//屏蔽滤波功能
	CAN_FilterInitStruct.CAN_FilterNumber = 0;
	//标识符屏蔽位模式
	CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdList;
	//32位寄存器
	CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_32bit;
	
	//高16位     
	//接收ID为CAN_EXID1的消息,使用拓展位以及遥控帧
	CAN_FilterInitStruct.CAN_FilterIdHigh = ( ((CAN_EXID1<<3)|CAN_ID_EXT|CAN_RTR_DATA) & 0xffff0000 )>>16;  
	//低16位
	CAN_FilterInitStruct.CAN_FilterIdLow = ( ((CAN_EXID1<<3)|CAN_ID_EXT|CAN_RTR_DATA) & 0x0000ffff );
	
	//屏蔽位高16位
	//屏蔽ID为CAN_EXID2的消息,使用拓展位以及数据帧
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh = ( ((CAN_EXID2<<3)|CAN_ID_EXT|CAN_RTR_DATA) & 0xffff0000 )>>16;
	//屏蔽位低16位
	CAN_FilterInitStruct.CAN_FilterMaskIdLow = ( ((CAN_EXID2<<3)|CAN_ID_EXT|CAN_RTR_DATA) & 0x0000ffff );
	
	
	//初始化过滤器
	CAN_FilterInit(&CAN_FilterInitStruct);
	
}

2.CAN收发函数

//发送 
void CAN_TransmitData()
{
	  extern uint8_t cntkey;
		extern CanTxMsg TX_Message;
	  TX_Message.Data[0]=cntkey;
		
		TX_Message.DLC = 8;         //数据包长度为8字节
		TX_Message.IDE = CAN_ID_EXT;
		TX_Message.ExtId = CAN_EXID1;
		TX_Message.RTR = CAN_RTR_DATA;
		TX_Message.StdId = 0;
		
		CAN_Transmit(CAN1,&TX_Message);
}
//接收中断,和USB共用
void USB_LP_CAN1_RX0_IRQHandler() 
{
	
	if(CAN_GetITStatus(CAN1,CAN_IT_FMP0)!=RESET)
	{
    CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0); 
		CAN_Receive(CAN1,CAN_FIFO0,&RX_Message);
		GPIOC->ODR^=GPIO_Pin_13;
		CAN_RX_Stat = 1;
		
  }
	
}

3.主函数main

/**
  ******************************************************************************
  * @file    main.c
  * @author  ZYS
  * @version V1.0
  * @date    2024-01-05
  * @brief   can通信步进电机协同控制
  ******************************************************************************
  */ 
 
#include "zys_headfile.h"

CanTxMsg TX_Message;
CanRxMsg RX_Message;

extern bool CAN_RX_Stat;
uint8_t rx_flag=0;

uint8_t		cntkey;
uint8_t rx_data;
uint8_t	keynum = 0;
uint8_t speed1 = 4;
uint8_t dir1 = 0;
uint8_t speed2 = 4;
uint8_t dir2 = 0;

int main ( void )
{

	/* 初始化 */
  Motor_Init();
  USART1_Config ();                 //初始化串口1
  CPU_TS_TmrInit();                 //初始化DWT计数器,用于延时函数
  LED_GPIO_Config();                //初始化 LED 灯
  OLED_Init();
  key_init();

  //初始化CAN配置
  CAN_init();	
	
	for(uint8_t i=0;i<8;i++)
		{
		  TX_Message.Data[i]=0;     
		}
		
  while ( 1 )
  {
		OLED_ShowString(4,1,"spd:");
		OLED_ShowString(3,1,"dir:");
		
		OLED_ShowNum(4,6,speed1,2);//speed1
		OLED_ShowNum(3,6,dir1,1);
		OLED_ShowNum(4,8,speed2,2);
		OLED_ShowNum(3,8,dir2,1);
		
		OLED_ShowString(1,1,"cntkey:");
		OLED_ShowString(2,1,"keynum:");
		
		OLED_ShowNum(1,9,cntkey,1);
		OLED_ShowNum(2,9,keynum,1);
		
		OLED_ShowString(2,12,"rx:");
		OLED_ShowNum(2,16,rx_data,1);
		OLED_ShowString(1,12,"tx:");
		OLED_ShowNum(1,16,TX_Message.Data[0],1);
		
		CAN_TransmitData();
		
		keynum = Key_GetNum();

		if(keynum == 1){cntkey ++;}
		if(cntkey == 6){cntkey = 0;}

		rx_data = RX_Message.Data[0];
		
			switch (rx_data){
			case 0: dir1 = 0;dir2 = 0;break;
			case 1: dir1 = 1;dir2 = 1;break;
			case 2: dir1 = 1;dir2 = 0;break;
			case 3: dir1 = 0;dir2 = 1;break;
			case 4: speed1 = 20;speed2 = 20;break;
			case 5: speed1 = 4;speed2 = 4;break;
			default: cntkey = 0;break;
		}
			
			if(dir1){MotorGO1(speed1);}
			else{MotorBack1(speed1);}

			if(dir2){MotorGO2(speed1);}
			else{MotorBack2(speed1);}
	}
}

四、实物演示

STM32基于CAN总线通信的多个步进电机协同控制

物联沃分享整理
物联沃-IOTWORD物联网 » 基于STM32的CAN总线协议控制步进电机详解

发表评论