在没有CAN芯片的情况下实现STM32 CAN通信

手头有两块stm32f103c8t6,但是没有can芯片转接,想尝试一下can通信,找不到相关例程,没办法,自己摸索,顺便写个程序供大家参考,记得点赞。搬运记得注明出处。

链接: 多机通信调试教程

特点:

  • stm32f103c8t6最小系统板

  • 不用can芯片

  • 一块板子回环通信或两块板子相互通信

  • 回环通信材料:

  • 一块stm32f103c8t6最小系统板
  • 两块板子通信材料:

  • 两块stm32f103c8t6最小系统板
  • 两个二极管
  • 一个电阻
  • 若干导线
  • 回环通信:

    啥也不说了,直接上程序,在你配置好编译下载环境的前提下,copy一下代码即可。程序里面有注释,可以自己琢磨一下。

    实验现象:

  • 核心板PC13引脚是灯,1s一闪,说明程序跑起来了。
  • 上电后,程序每1s都会发送一次消息,内容是0x00 0x01…0x07
  • PA9是串口发送引脚,收到can数据都,会通过串口1的PA9引脚发送出去
  • 在电脑上使用串口助手就可以看到数据了。
  • 如果说你没有串口。。。。。。debug看看运行时能不能进接收中断。
  • 该回环模式下,不用连接任何其他设备,也不用短接CAN_RX B8 CAN_TX B9,直接就能发送和接收
  • 上干货,记得点赞:

    #include "stm32f10x.h"
    #include "stdio.h"
    
    CanTxMsg TxMessage;			//can发送消息结构体
    CanRxMsg RxMessage;			//can接收消息结构体
    uint8_t ledstatu;			//led状态
    uint8_t flag;				//接收到数据标志
    
    void DelayUs(uint32_t us);	//延时1us
    void DelayMs(uint32_t ms);	//延时1ms
    
    void LED_Config(void);		//led配置			C13
    void USART1_Config(void);	//USART1配置		UART_TX A9	UART_RX A10
    void CAN1_Config(void);		//CAN1配置			CAN_TX	B9	CAN_RX B8
    
    void CAN_SetMsg(CanTxMsg *TxMessage);			//设置发送消息内容
    void Init_RxMes(CanRxMsg *RxMessage);			//清空接收消息内容
    void USB_LP_CAN1_RX0_IRQHandler(void);			//接收中断
    
    //接收中断,收到数据时触发这个函数
    void USB_LP_CAN1_RX0_IRQHandler(void)
    {
    	/*从邮箱中读出报文*/
    	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
    
    	/* 比较ID是否为0x1314 */ 
    	if((RxMessage.ExtId==0x1314) && (RxMessage.IDE==CAN_ID_EXT) && (RxMessage.DLC==8) )
    	{
    		flag = 1; 					       //接收成功  
    	}
    	else
    	{
    		flag = 0; 					   //接收失败
    	}
    }
    
    int main(void)
    {		
    	int8_t i;
    	
    	LED_Config();
    	USART1_Config();
    	CAN1_Config();
    	CAN_SetMsg(&TxMessage);
    	Init_RxMes(&RxMessage);
    	
    	printf("\r\n 扩展ID号ExtId:0x%x \r\n",TxMessage.ExtId);
    		
    	while (1)
    	{
    		static uint32_t u32Time100ms = 0;		//100ms 计数一次
    		
    		if(u32Time100ms%10 == 0)//1s定时
    		{
    			//闪灯
    			GPIO_WriteBit(GPIOC,GPIO_Pin_13,(BitAction)ledstatu);
    			ledstatu=!ledstatu;
    			
    			//发数据
    			CAN_Transmit(CAN1, &TxMessage);
    			
    			printf("发送数据为\n");
    			for(i=0;i<8;i++)
    				printf("%02x ",TxMessage.Data[i]);
    			printf("\n");
    		}
    		
    		if(flag==1)//接收到数据
    		{
    			printf("接收数据为\n");
    			for(i=0;i<8;i++)
    				printf("%02x ",RxMessage.Data[i]);
    			printf("\n");
    			
    			flag=0;
    		}
    		
    		DelayMs(100);
    		u32Time100ms++;
    	}
    }
    
    void DelayUs(uint32_t us)
    {
    	while(us--)
    	{
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
    	}
    	
    }
    void DelayMs(uint32_t ms)
    {
    	while(ms--)
    		DelayUs(1000);
    }
    
    void LED_Config(void)
    {		
    		GPIO_InitTypeDef GPIO_InitStructure;
    
    		RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
    		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;	
    		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
    		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
    		GPIO_Init(GPIOC, &GPIO_InitStructure);	
    }
    
    void USART1_Config(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    	// 将USART Tx的GPIO配置为推挽复用模式
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	// 将USART Rx的GPIO配置为浮空输入模式
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	// 配置串口的工作参数
    	USART_InitStructure.USART_BaudRate = 115200;
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;
    	USART_InitStructure.USART_Parity = USART_Parity_No ;
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    	USART_Init(USART1, &USART_InitStructure);
    	
    	// 使能串口
    	USART_Cmd(USART1, ENABLE);
    }
    
    ///重定向c库函数printf到串口,重定向后可使用printf函数
    int fputc(int ch, FILE *f)
    {
    	/* 发送一个字节数据到串口 */
    	USART_SendData(USART1, (uint8_t) ch);
    
    	/* 等待发送完毕 */
    	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);		
    	return (ch);
    }
    
    
    
    
    
    
    void CAN1_Config(void)
    {
     	GPIO_InitTypeDef GPIO_InitStructure;   	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	CAN_InitTypeDef        CAN_InitStructure;
    	CAN_FilterInitTypeDef  CAN_FilterInitStructure;
    
    	//引脚配置************************************************************************
    	/* Enable GPIO clock */
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE);
    
    	//重映射引脚
    	GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
    
    	/* Configure CAN TX pins */
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		         // 复用推挽输出
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    	/* Configure CAN RX  pins */
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	             // 上拉输入
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    
    
    	//中断配置************************************************************************
    	/* Configure one bit for preemption priority */
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    	
    	/*中断设置*/
    	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;	   //CAN1 RX0中断
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;		   //抢占优先级0
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			   //子优先级为0
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_Init(&NVIC_InitStructure);
    	
    	
    	//通信参数配置********************************************************************
    	/* Enable CAN clock */
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
    
    	/*CAN寄存器初始化*/
    	CAN_DeInit(CAN1);
    	CAN_StructInit(&CAN_InitStructure);
    
    	/*CAN单元初始化*/
    	CAN_InitStructure.CAN_TTCM=DISABLE;			   //MCR-TTCM  关闭时间触发通信模式使能
    	CAN_InitStructure.CAN_ABOM=ENABLE;			   //MCR-ABOM  自动离线管理 
    	CAN_InitStructure.CAN_AWUM=ENABLE;			   //MCR-AWUM  使用自动唤醒模式
    	CAN_InitStructure.CAN_NART=DISABLE;			   //MCR-NART  禁止报文自动重传	  DISABLE-自动重传
    	CAN_InitStructure.CAN_RFLM=DISABLE;			   //MCR-RFLM  接收FIFO 锁定模式  DISABLE-溢出时新报文会覆盖原有报文  
    	CAN_InitStructure.CAN_TXFP=DISABLE;			   //MCR-TXFP  发送FIFO优先级 DISABLE-优先级取决于报文标示符 
    	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;  //普通收发模式 CAN_Mode_Normal 、 回环工作模式 CAN_Mode_LoopBack
    	CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;		   //BTR-SJW 重新同步跳跃宽度 1个时间单元
    
    	/* ss=1 bs1=5 bs2=3 位时间宽度为(1+5+3) 波特率即为时钟周期tq*(1+3+5)  */
    	CAN_InitStructure.CAN_BS1=CAN_BS1_5tq;		   //BTR-TS1 时间段1 占用了5个时间单元
    	CAN_InitStructure.CAN_BS2=CAN_BS2_3tq;		   //BTR-TS1 时间段2 占用了3个时间单元	
    
    	/* CAN Baudrate = 1 MBps (1MBps已为stm32的CAN最高速率) (CAN 时钟频率为 APB1 = 36 MHz) */
    	CAN_InitStructure.CAN_Prescaler =4;		   		//BTR-BRP 波特率分频器  定义了时间单元的时间长度 36/(1+5+3)/4=1 Mbps
    	CAN_Init(CAN1, &CAN_InitStructure);
    	
    
    
    	//滤波器参数配置******************************************************************
    	/*CAN筛选器初始化*/
    	CAN_FilterInitStructure.CAN_FilterNumber=0;						//筛选器组0
    	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;	//工作在掩码模式
    	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;	//筛选器位宽为单个32位。
    	/* 使能筛选器,按照标志的内容进行比对筛选,扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */
    
    	CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;		//要筛选的ID高位 
    	CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要筛选的ID低位 
    	CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;			//筛选器高16位每位必须匹配
    	CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;			//筛选器低16位每位必须匹配
    	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;				//筛选器被关联到FIFO0
    	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;			//使能筛选器
    	CAN_FilterInit(&CAN_FilterInitStructure);
    	/*CAN通信中断使能*/
    	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
    	
    }
    
    /**
      * @brief  初始化 Rx Message数据结构体
      * @param  RxMessage: 指向要初始化的数据结构体
      * @retval None
      */
    void Init_RxMes(CanRxMsg *RxMessage)
    {
    	uint8_t ubCounter = 0;
    
    	/*把接收结构体清零*/
    	RxMessage->StdId = 0x00;
    	RxMessage->ExtId = 0x00;
    	RxMessage->IDE = CAN_ID_STD;
    	RxMessage->DLC = 0;
    	RxMessage->FMI = 0;
    	for (ubCounter = 0; ubCounter < 8; ubCounter++)
    	{
    		RxMessage->Data[ubCounter] = 0x00;
    	}
    }
    
    
    /*
     * 函数名:CAN_SetMsg
     * 描述  :CAN通信报文内容设置,设置一个数据内容为0-7的数据包
     * 输入  :发送报文结构体
     * 输出  : 无
     * 调用  :外部调用
     */	 
    void CAN_SetMsg(CanTxMsg *TxMessage)
    {	  
    	uint8_t ubCounter = 0;
    
    	//TxMessage.StdId=0x00;						 
    	TxMessage->ExtId=0x1314;					 //使用的扩展ID
    	TxMessage->IDE=CAN_ID_EXT;					 //扩展模式
    	TxMessage->RTR=CAN_RTR_DATA;				 //发送的是数据
    	TxMessage->DLC=8;							 //数据长度为8字节
    
    	/*设置要发送的数据0-7*/
    	for (ubCounter = 0; ubCounter < 8; ubCounter++)
    	{
    		TxMessage->Data[ubCounter] = ubCounter;
    	}
    }
    
    
    
    

    双机通信

    (理论上可以多机,目前只有两个板子)
    一个板子的回环教程,网上还是能搜到滴,真正有意思的当然是下面,不用can芯片的情况下,mcu怎么做到多机通信!!!

    一点点CAN知识

    正常的can通信应该是,单片机的can的rx tx(3.3V或0V的逻辑电路)接外部芯片,比如tja1050,然后再接到can总线上(差分信号)。

    两种信号区别:
    逻辑电路中,高电平表示1,低电平表示0
    差分型号中,信号由两条线的电位差将物理信号转换成数字信号,以高速CAN为例,CAN_H为3.5V,CAN_L为1.5V,电位差为2V,此时定义为显性,对应数字信号:0;CAN高与CAN低均为2.5V时,电位差为0V,此时定义为隐性,对应数字信号:1。

    很明显,必须需要一个转接芯片将两种信号转换。那我们能不能建立一个逻辑电路的can总线呢。当然可以。

    要想搞明白这点,还需要了解一下can通信底层收发逻辑:

  • 假设同时有2台或以上设备连接到总线
  • 都不发信号时,总线上逻辑为1(忽略逻辑电路与差分电路区别,逻辑电路中为高电平,差分电路中为隐性)
  • 只要有任何一个设备发送0,总线就会变成0(线与)
  • 突然两台设备同时发送数据,A设备发送01100111,B设备发送01110111
  • 我们逐位分析,第一位两个设备都发0,发送的同时设备会读取总线,发现总线的确是0,继续发送第二位。
  • 第二、三位数据都是1,读取到的总线也是1,继续
  • 第四位数据,A发送0,B发送1,总线为0(线与)。A设备读取总线为0,继续发送;B设备读取总线数据为0,一脸懵逼,发现和自己发送的数据1不一样,于是停止发送,改为接收数据。
  • A发送第五位数据,B接收数据
  • 硬件实现

    搞明白发送的底层之后,我们发现,只要实现线与功能,无所谓有没有can芯片。

    我们可不可以将两个设备的A_CAN_RX、A_CAN_TX、B_CAN_RX、B_CAN_TX这4根线全部都揉在一起呢?

    当然不行

  • 都不发数据时,总线上不知道是0还是1,
  • A,B设备同时发送数据,一个发送0,一个发送1,不就短路了吗
  • 解决以上问题:

  • 上拉电阻
  • Tx端口添加二极管

  • 再来看看刚刚两个问题,如果都不发数据,总线相当于直接接在高电平上,也就是逻辑1,当AB同时发送数据,A发0,B发1。此时,A的二极管导通,总线上电压约0.5V(二极管压降为0.5V,stm32单片机手册写了,0至0.7V为低电平)。而B的二极管不能导通。A读取总线为逻辑0,与自身数据相同,继续发送。B读取总线数据为逻辑0,与自身不符,停止发送数据,转为接收数据。

    完美收工
    图片如下

    这里焊了3组can,但是只用了两个。
    Rx接单片机的CAN_RX B8,TX接单片机的CAN_TX B9

    程序实现

    每个单片机都烧录程序,程序和之前相同,仅仅改一个地方
    CAN1_Config函数中,有一句

    CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;  //普通收发模式 CAN_Mode_Normal 、 回环工作模式 CAN_Mode_LoopBack
    

    改为

    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal ;  //普通收发模式 CAN_Mode_Normal 、 回环工作模式 CAN_Mode_LoopBack
    

    就可以啦,将程序分别烧录到两个单片机中,运行就可以了。

    实验现象

  • 核心板PC13引脚是灯,1s一闪,说明程序跑起来了。
  • 上电后,程序每1s都会发送一次消息,内容是0x00 0x01…0x07
  • PA9是串口发送引脚,收到can数据都,会通过串口1的PA9引脚发送出去
  • 在电脑上使用串口助手就可以看到数据了。
  • 如果说你没有串口。。。。。。debug看看运行时能不能进接收中断。
  • 注意总线要接上拉电阻到3.3v,两个单片机之间gnd需要用线连接起来共地。rx tx必须都接到总线上。
  • 物联沃分享整理
    物联沃-IOTWORD物联网 » 在没有CAN芯片的情况下实现STM32 CAN通信

    发表评论