STM32 CAN通信(没有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必须都接到总线上。

在这里插入图片描述

  • 19
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值