知识回顾

我们已经学习了CAN协议和CAN通信的基本原理。这篇文章就是就简单的做一些小实验,写一写代码。但是在此之前我们要先回顾一下之前学习的相关内容。

  1. 首先CAN的底层实现和I2C也是依靠逻辑线与,所以它也是用非破坏性仲裁进行仲裁,即先发送显性位的的设备会赢得总线控制权。基于此,CAN中的优先级设置以及解决冲突,包括不同帧类型的优先级都是依靠该机制实现。
  2. CAN与I2C不同的是,CAN是多主机模式,每个设备都是平等,但是总线资源有限,关于总线资源的争夺也是通过I2C的。但是当总线上有数据传输的时候,高优先级设备是不可以抢夺总线资源的。
  3. CAN总线的资源不可抢夺不是绝对的。在出现错误的时候,其它设备是可以像总线发送数据,来作废当前帧。错误帧形式就是连续6个相同的bit位。也就是说当有设备发送数据时,其它设备也可以发送数据。
  4. 在CAN总线中,有一个信用管理机制,根据某个设备的数据发送出错情况来改变该设备的对总线操作的权限。
  5. 在STM32中的发送数据的流程是:先找一个空邮箱——> 把数据放入邮箱——> 等待总线空闲——> 发送数据。在STM32f103c8t6中有三个邮箱,每个数据帧能携带8字节的数据,也就是一次性可以稳定传输数据最多是24字节。
  6. 在STM32中数据接收的流程是:接收数据——> 放到FIFO中——> 读取数据——> 释放FIFO。在STM32中有一个三级深度的FIFO来接收数据,这是和发送邮箱个数是一致的。
  7. 过滤器的存在可以使得数据的接收管理更加方便,直接过滤掉自己不需要的数据,极大节省数据帧判断的开销。
  8. 因为CAN是多主机通信,所以出现问题时,很难确定是发送方的问题还是接收方的问题亦或是总线或收发器的问题。所以提供了3种测试模式,可以更容易的验证。

CAN的库函数

以下是我在标准库中复制下来的部分关于CAN的相关函数,下面就逐个部分看一下。

/*  Function used to set the CAN configuration to the default reset state *****/ 
void CAN_DeInit(CAN_TypeDef* CANx);

复位初始化操作
就是恢复外设为未初始化状态,起初我是不理解这种函数存在的意义的,但是我们如果是低功耗模式,那么进入低功耗模式之前就需要关闭所有外设。
另外就是可以在初始化之前调用该函数,保证外设状态,但个人认为这样做的意义不大。

/* Initialization and Configuration functions *********************************/ 
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
void CAN_StructInit(CAN_InitTypeDef* CAN_InitStruct);
void CAN_SlaveStartBank(uint8_t CAN_BankNumber); 
void CAN_DBGFreeze(CAN_TypeDef* CANx, FunctionalState NewState);
void CAN_TTComModeCmd(CAN_TypeDef* CANx, FunctionalState NewState);

初始化和配置相关的函数

这部分我们就只看前三个函数,后面的是与双CAN和事件触发相关的函数,与我们之后演示的代码不相关,感兴趣的同学可以自己研究下。

CAN_Init()是很常见的了,进行外设初始化。
CAN_FilterInit()就是过滤器配置初始化函数,在这里设置标识符过滤
CAN_StructInit()就是初始化一个默认参数

/* Transmit functions *********************************************************/
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);
void CAN_CancelTransmit(CAN_TypeDef* CANx, uint8_t Mailbox);

发送函数
根据名字我们也就知道了都是什么含义,发送和检查发送状态以及取消发送,取消发送肯定是数据还在邮箱时候才可以取消。

/* Receive functions **********************************************************/
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
void CAN_FIFORelease(CAN_TypeDef* CANx, uint8_t FIFONumber);
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber);

接收数据
数据接收,那么也是与发送相关函数同理

/* Operation modes functions **************************************************/
uint8_t CAN_OperatingModeRequest(CAN_TypeDef* CANx, uint8_t CAN_OperatingMode);
uint8_t CAN_Sleep(CAN_TypeDef* CANx);
uint8_t CAN_WakeUp(CAN_TypeDef* CANx);

CAN的操作方式
这里我们也涉及不到,感兴趣可以自己了解下,但其实我们也都讲到过


/* Error management functions *************************************************/
uint8_t CAN_GetLastErrorCode(CAN_TypeDef* CANx);
uint8_t CAN_GetReceiveErrorCounter(CAN_TypeDef* CANx);
uint8_t CAN_GetLSBTransmitErrorCounter(CAN_TypeDef* CANx);

错误管理
这里我们也涉及不到,感兴趣可以自己了解下

/* Interrupts and flags management functions **********************************/
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
void CAN_ClearFlag(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
ITStatus CAN_GetITStatus(CAN_TypeDef* CANx, uint32_t CAN_IT);
void CAN_ClearITPendingBit(CAN_TypeDef* CANx, uint32_t CAN_IT);

中断相关
这里也是老朋友了,开中断,标志位检查,清除标志位。

实验:实现CAN的收发

实验设计

考虑到可能手中设备有限,我们只使用环回模式测试就可以了,定义五种数据帧,然后屏蔽一个数据帧,我们就使用串口进行控制发送哪一种数据帧,接收之后再使用串口打印接收的数据帧。

代码展示

// USART外设相关初始化
#include "USART.h"

#define USART_TX GPIO_Pin_9
#define USART_RX GPIO_Pin_10

char buffer[128];
uint8_t usart_flag = 0;
uint8_t idx = 0;



static void init_pin(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = USART_TX;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin = USART_RX;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void init_USART(void){
	init_pin();
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 115200;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1, &USART_InitStruct);
	USART_Cmd(USART1, ENABLE);
}

void USART_SendStr(char *str){
	while(*str){
		while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
		USART_SendData(USART1, *str++);
	}
}


void init_USART_NVIC(void){
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStruct);
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){
		char temp = USART_ReceiveData(USART1);
		
		if(temp == '\n' || temp == '\r'){
			buffer[idx] = '\0';
			idx = 0;
			usart_flag = 1;
		}
		else buffer[idx++] = temp;
		
	}
}

#include "CAN.h"

#define CAN_TX GPIO_Pin_12
#define CAN_RX GPIO_Pin_11

static void init_pin(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = CAN_TX;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = CAN_RX;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
}

static void init_can(void){
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
	CAN_InitTypeDef CAN_InitStruct;
	// 错误处理和离线恢复(1-自动恢复 0-手动恢复)
	CAN_InitStruct.CAN_ABOM = ENABLE;
	// 唤醒策略(1-自动唤醒 0-手动唤醒)
	CAN_InitStruct.CAN_AWUM = ENABLE;
	// 分频
	//波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量(这里不考虑同步段)
	//       = 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))
	CAN_InitStruct.CAN_Prescaler = 8;
	// 同步段(数据段前的TQ段) (1-4tq)
	CAN_InitStruct.CAN_SJW = 1;
	// 相位缓冲段 1	 (1-8tq)
	CAN_InitStruct.CAN_BS1 = 5;
	// 相位缓冲段 2	 (2-8tq)
	CAN_InitStruct.CAN_BS2 = 3;
	// CAN模式CAN_Mode_LoopBack
	CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack;
	// 自动重传
	CAN_InitStruct.CAN_NART = DISABLE;
	// 接收FIFO策略(1-锁定(丢弃) 0-禁用锁定(覆盖))
	CAN_InitStruct.CAN_RFLM = ENABLE;
	// 时间触发通信
	CAN_InitStruct.CAN_TTCM = DISABLE;
	// 发送优先级(1-FIFO 0-根据ID)
	CAN_InitStruct.CAN_TXFP = DISABLE;
	
	CAN_Init(CAN1, &CAN_InitStruct);
	
}

static void init_fileter(void){

	CAN_FilterInitTypeDef CAN_FilterInitStruct;
	// 过滤器ID(0~13)
	CAN_FilterInitStruct.CAN_FilterNumber = 0;
	// 标识符屏蔽模式(掩码,列表)
	CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdList;
	// 位宽(16, 32)
	CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_16bit;
	// 关联FIFO
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_FilterFIFO0;
	// ID高16位
	CAN_FilterInitStruct.CAN_FilterIdHigh = 0x111 << 5;
	// ID低16位
	CAN_FilterInitStruct.CAN_FilterIdLow = 0x222 << 5;
	// 掩码高16位
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x333 << 5;
	// 掩码低16位
	CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x000 << 5;
	// 激活过滤器
	CAN_FilterInitStruct.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&CAN_FilterInitStruct);
}

void init_CAN(void){
	init_pin();
	init_can();
	init_fileter();
}

在这里可以看到有两个32位的过滤器,如果我们使用16位的话,那么就可以存储4个标识符ID

#include "stm32f10x.h"
#include "SysTick_Delay.h"
#include "CAN.h"
#include "USART.h"
#include <string.h>
CanTxMsg CAN_Msg[5] = {
	//StdId		 EXtId			   IDE				 RTR		DLC	 Data[8]
	{0x000		,0x1111		,CAN_Id_Standard	,CAN_RTR_Data	,8	,"led:1"},
	{0x111		,0x1111		,CAN_Id_Standard	,CAN_RTR_Data	,8	,"ser:1"},
	{0x222		,0x1111		,CAN_Id_Standard	,CAN_RTR_Data	,8	,"mot:1"},
	{0x333		,0x1111		,CAN_Id_Standard	,CAN_RTR_Data	,8	,"buz:1"},
	{0x444		,0x1111		,CAN_Id_Standard	,CAN_RTR_Data	,8	,"sen:1"},
};

CanRxMsg CANRx;
extern uint8_t usart_flag;
uint8_t can_send = 4;

extern char buffer[];

int main(void){
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	init_USART();
	init_USART_NVIC();
	init_CAN();
	USART_SendStr("Init...\n");
	
	while(1){
		
		if(usart_flag){
			USART_SendStr("USART Receive Str\n");
			if(!memcmp(buffer, "led", 3)){
				can_send = 0;
			}
			else if(!memcmp(buffer, "ser", 3)){
				can_send = 1;
			}
			else if(!memcmp(buffer, "mot", 3)){
				can_send = 2;
			}
			else if(!memcmp(buffer, "buz", 3)){
				can_send = 3;
			}
			else if(!memcmp(buffer, "sen", 3)){
				can_send = 4;
			}
			else{
				USART_SendStr(buffer);
				memset(buffer, 0, 128);
				usart_flag = 0;
				continue;
			}
			
			CAN_Msg[can_send].Data[4] = buffer[3];
			CAN_Transmit(CAN1, &CAN_Msg[can_send]);
			can_send = 4;
			memset(buffer, 0, 128);
			usart_flag = 0;
		}
		
		if(CAN_MessagePending(CAN1, CAN_FIFO0) > 0){
			CAN_Receive(CAN1, CAN_FIFO0, &CANRx);
			USART_SendStr("CAN Receive:");
			USART_SendStr((char*)CANRx.Data);

		}
	}
}

在这里需要注意一下我使用的是CAN_MessagePending(CAN1, CAN_FIFO0)用来接收数据帧,如果使用CAN_Receive()接收的话,他会等到邮箱满了才会触发条件,也就是说你要发第四条消息之后,才可以取第一条数据。

运行结果

在这里插入图片描述
可以看到我们没有把0x444加入过滤器,所以就接收不到数据

写在最后

当然了这只是演示它的功能,正常我们还是需要配置中断来接收消息的这样会更合理,另外就是我们现在是环回模式只是用来自测,那么如果实现真正的通信需要修改什么呢?

//只需要修改这个参数就好了
CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack;

CAN_Mode_LoopBack  ——> CAN_Mode_Normal 
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐