手头有两块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必须都接到总线上。