前言
只有一块stm32单片机或两块的情况下,如何调试stm32 can通信,请见上文
链接: 单机回环与双机通信教程
之后又小伙伴评论说多机通信调试不同,有的单片机容易卡死,不知道是什么原因,我猜测可能是:
- id配置问题,id代表了优先级,如果总线总是被优先级高的子模块占用,那么其他模块可能会卡死
- 波特率配置问题,多个can在一根总线上时,必须使用相同波特率
总而言之,很可能是配置问题,于是我又翻出了一块stm32核心板,3个核心板用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接收数据
原理图
原理图中总线可以实现:
- 如果都不发数据,总线相当于直接接在3.3V高电平上,也就是逻辑1。
- 当AB同时发送数据,A_CAN_TX发0,B_CAN_TX发1。此时,A的二极管导通,总线上电压约0.5V(二极管压降为0.5V,stm32单片机手册写了,0至0.7V为低电平)。而B的二极管不能导通。A读取总线为逻辑0,与自身数据相同,继续发送。B读取总线数据为逻辑0,与自身不符,停止发送数据,转为接收数据。也就是实现线与。
实物图如下:
软件
程序
连接好三块板子电源后(如果不是使用同一个电源供电,需要共地),烧录程序如下,分别打开 #define A
、#define B
、#define C
宏定义,烧录到三个单片机中即可
#include "stm32f10x.h"
#include "stdio.h"
#define A
//#define B
//#define C
#ifdef A
#define my_id 0x00000001
#define other_id 0x00000002
#define my_dat0 0x00
#define my_dat1 0x00
#define my_dat2 0x00
#define my_dat3 0x00
#define my_dat4 0x00
#define my_dat5 0x00
#define my_dat6 0x00
#define my_dat7 0x01
#endif
#ifdef B
#define my_id 0x00000002
#define other_id 0x00000003
#define my_dat0 0x00
#define my_dat1 0x00
#define my_dat2 0x00
#define my_dat3 0x00
#define my_dat4 0x00
#define my_dat5 0x00
#define my_dat6 0x00
#define my_dat7 0x02
#endif
#ifdef C
#define my_id 0x00000003
#define other_id 0x00000001
#define my_dat0 0x00
#define my_dat1 0x00
#define my_dat2 0x00
#define my_dat3 0x00
#define my_dat4 0x00
#define my_dat5 0x00
#define my_dat6 0x00
#define my_dat7 0x03
#endif
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);
flag = 1; //接收成功
}
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("0x%08x发送:\n",my_id);
for(i=0;i<8;i++)
printf("%02x ",TxMessage.Data[i]);
printf("\n");
}
if(flag==1)//接收到数据
{
printf("接收0x%08x:\n",RxMessage.ExtId);
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_Normal; //普通收发模式 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)other_id<<3)|CAN_ID_EXT|CAN_RTR_DATA)>>16; //要筛选的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)other_id<<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)
{
// TxMessage->StdId=my_id; //使用标准ID 11bit
TxMessage->ExtId=my_id; //使用的扩展ID 29bit
TxMessage->IDE=CAN_ID_EXT; //扩展模式
TxMessage->RTR=CAN_RTR_DATA; //发送的是数据
TxMessage->DLC=8; //数据长度为8字节
TxMessage->Data[0]=my_dat0;
TxMessage->Data[1]=my_dat1;
TxMessage->Data[2]=my_dat2;
TxMessage->Data[3]=my_dat3;
TxMessage->Data[4]=my_dat4;
TxMessage->Data[5]=my_dat5;
TxMessage->Data[6]=my_dat6;
TxMessage->Data[7]=my_dat7;
}
注解
其中有几个点容易搞混淆或难以理解,程序中部分注解如下:
/*
* 函数名:CAN_SetMsg
* 描述 :CAN通信报文内容设置,设置一个数据内容为0-7的数据包
* 输入 :发送报文结构体
* 输出 : 无
* 调用 :外部调用
*/
void CAN_SetMsg(CanTxMsg *TxMessage)
{
// TxMessage->StdId=my_id; //使用标准ID 11bit
TxMessage->ExtId=my_id; //使用的扩展ID 29bit
TxMessage->IDE=CAN_ID_EXT; //扩展模式
TxMessage->RTR=CAN_RTR_DATA; //发送的是数据
TxMessage->DLC=8; //数据长度为8字节
TxMessage->Data[0]=my_dat0;
TxMessage->Data[1]=my_dat1;
TxMessage->Data[2]=my_dat2;
TxMessage->Data[3]=my_dat3;
TxMessage->Data[4]=my_dat4;
TxMessage->Data[5]=my_dat5;
TxMessage->Data[6]=my_dat6;
TxMessage->Data[7]=my_dat7;
}
源程序中,这段函数用于设置发送的报文信息
TxMessage->IDE=CAN_ID_STD; //标准模式
,是用于设置报文的id为标准id,标准id中,将使用StdId变量作为id,他的有效位数为11bit,0x00000000至0x000007ff有效,仅低11bit有效TxMessage->IDE=CAN_ID_EXT; //扩展模式
,是用于设置报文的id为扩展id,扩展id中,将使用ExtId变量作为id,他的有效位数为29bit,0x00000000至0x1fffffff有效,高位3bit为无效位置。- id越小优先级越高。
TxMessage->RTR=CAN_RTR_DATA; //发送的是数据
,表示发送的报文为数据类型TxMessage->DLC=8; //数据长度为8字节
,表示数据长度8个字节- 后续则是具体数据内容。
在实际调试中,波特率一般多个can模块配置相同即可,具体速度影响不大,如果不想改可以之间使用上面的CAN1_Config配置。
如果测试时,接收不到信息,往往是滤波器没有配置好,上述程序关于滤波器配置详细解释一下:
//滤波器参数配置******************************************************************
/*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)other_id<<3)|CAN_ID_EXT|CAN_RTR_DATA)>>16; //要筛选的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)other_id<<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_FilterInitStructure.CAN_FilterNumber=0;
,表示选择滤波器组0,stm32f103系列can过滤器有14组。可以配置多组滤波器。CAN_FilterInitStructure.CAN_FilterMode
如下:-
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
,表示滤波器工作在掩码模式,也就是只要特定的几位id符合,就接受数据。例如滤波器值为0x12345678;掩码为0x0000ffff,则接收的id只需要满足0Xxxxx5678,就可以被接收。
-
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdList;
,表示滤波器工作在列表模式,此时只有接收数据id等于滤波器值,或者接收数据id等于掩码值,才被接收。
CAN_FilterInitStructure.CAN_FilterScale
可以选择两种CAN_FilterScale_16bit,CAN_FilterScale_32bit。
模式 | CAN FilterldHigh | CAN FilterldLow | CAN FilterMaskIdHigh | CAN FilterMaskIdLow |
---|---|---|---|---|
32位列表模式 | ID1 的高16位 | ID1 的低16位 | ID2 的高16位 | ID2 的低16位 |
16位列表模式 | ID1 的完整数值 | ID2 的完整数值 | ID3 的完整数值 | ID4 的完整数值 |
32位掩码模式 | ID1 的高16位 | ID1 的低16位 | ID1 掩码的高16位 | ID1 掩码的低16位 |
16位掩码模式 | ID1 的完整数值 | ID2 的完整数值 | ID1 掩码的完整数值 | ID2 掩码完整数值 |
CAN_FilterInitStructure.CAN_FilterIdHigh
和CAN_FilterInitStructure.CAN_FilterIdLow
,表示滤波器值的高16bit和低16bit,CAN_FilterInitStructure.CAN_FilterMaskIdHigh
和CAN_FilterInitStructure.CAN_FilterMaskIdHigh
表示掩码值的高16bit和低16bit。使用扩展id时,他们和id的关系如下:
bit31 | bit30 | bit29 | bit28 | bit27 | bit26 | bit25 | bit24 |
---|---|---|---|---|---|---|---|
EXID28 | EXID27 | EXID26 | EXID25 | EXID24 | EXID23 | EXID22 | EXID21 |
bit23 | bit22 | bit21 | bit20 | bit19 | bit18 | bit17 | bit16 |
EXID20 | EXID19 | EXID18 | EXID17 | EXID16 | EXID15 | EXID14 | EXID13 |
bit15 | bit14 | bit13 | bit12 | bit11 | bit10 | bit9 | bit8 |
EXID12 | EXID11 | EXID10 | EXID9 | EXID8 | EXID7 | EXID6 | EXID5 |
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
EXID4 | EXID3 | EXID2 | EXID1 | EXID0 | IDE | RTR | 0 |
具体可以查看芯片手册:
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0
,则表示接收数据暂存fifo0,可选参数还有CAN_Filter_FIFO1;CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
,则表示使能滤波器。
实验现象
没有修改程序的情况下,分别烧录三块核心板,连接到制作的can总线上,现象为:
- 核心板PC13引脚是灯,1s一闪,说明程序跑起来了。
- A板子id为0x00000001,B板子id为0x00000002,C板子id为0x00000003,A板子只接受B板子信号,B板子只接收C板子信号,C板子只接收A板子信号。
- 上电后,所有板子每1s都会发送一次消息
- PA9是串口发送引脚,收到can数据都,会通过串口1的PA9引脚发送出去
- 在电脑上使用串口助手就可以看到数据了。
- 如果说你没有串口。。。。。。debug看看运行时能不能进接收中断。
- 注意总线要接上拉电阻到3.3v,两个单片机之间gnd需要用线连接起来共地。rx直接接总线 tx加二极管接到总线上。
连接A板子串口,可以看到以下信息
B板子收到消息如下
还有c板子懒得接了,一样的道理。