本实验通过 KEY_UP 按键选择 CAN 的工作模式(正常模式/环回模式),然后通过 KEY0 控制数据发送,并通过查询的办法,将接收到的数据通过串口打印出来。如果是环回模式,用一个开发板即可测试。如果是正常模式,就需要 2 个探索者 STM32F4 开发板,并且 将他们的 CAN 接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据通过串口打印出来。
CAN 的初始化配置步骤:
1)配置相关引脚的复用功能(AF9), 使能CAN 时钟。
- //使能相关时钟
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能 PORTA 时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
- //初始化 GPIO
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 PA11,PA12
- //引脚复用映射配置
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);//PA11 复用为 CAN1
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);//PA12 复用为 CAN1
2)设置 CAN 工作模式及波特率等。
CAN_Init()用来初始化 CAN 的工作模式以及波特率,CAN_Init() 函数体中,在初始化之前,会设置 CAN_MCR 寄存器的 INRQ 为 1 让其进入初始化模式,然后初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之后,会设置 CAN_MCR 寄存器的 INRQ 为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。CAN_Init()函数的定义:
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
第一个参数就是 CAN 标号,这里我们的芯片只有一个 CAN,所以就是 CAN1。
第二个参数是 CAN 初始化结构体指针,结构体类型是 CAN_InitTypeDef,结构体的定义:
typedef struct
{
uint16_t CAN_Prescaler;
uint8_t CAN_Mode;
uint8_t CAN_SJW;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
} CAN_InitTypeDef;
初始化实例为:
- CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
- CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
- CAN_InitStructure.CAN_AWUM=DISABLE; //睡眠模式通过软件唤醒
- CAN_InitStructure.CAN_NART=ENABLE; //禁止报文自动传送
- CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
- CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
- CAN_InitStructure.CAN_Mode= CAN_Mode_LoopBack; //模式设置 1,回环模式;
- CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;//重新同步跳跃宽度为个时间单位
- CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; //时间段 1 占用 8 个时间单位
- CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;//时间段 2 占用 7 个时间单位
- CAN_InitStructure.CAN_Prescaler=5; //分频系数(Fdiv)
- CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
3)设置滤波器。
函数 CAN_FilterInit ()用来初始化 CAN 的滤波器相关参数,CAN_Init() 函数体中,在初始化之前,会设置 CAN_FMR 寄存器的 INRQ 为 INIT 让其进入初始化模式, 然后初始化 CAN 滤波器相关的寄存器之后,会设置 CAN_FMR 寄存器的 FINIT 为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。CAN_FilterInit ()函数的定义:
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
CAN_FilterInitTypeDef类型定义:
typedef struct
{
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
uint16_t CAN_FilterFIFOAssignment;
uint8_t CAN_FilterNumber;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
FunctionalState CAN_FilterActivation;
} CAN_FilterInitTypeDef;
第 1 个至第 4 个是用来设置过滤器的 32 位 id 以及 32 位 mask id, 分别通过 2 个 16 位来组合的。
第 5 个成员变量 CAN_FilterFIFOAssignment 用来设置 FIFO 和过滤器的关联关系,我们的实验 是关联的过滤器 0 到 FIFO0,值为 CAN_Filter_FIFO0。
第 6 个成员变量 CAN_FilterNumber 用来设置初始化的过滤器组,取值范围为 0~13。
第 7 个成员变量 FilterMode 用来设置过滤器组的模式,取值为标识符列表模式 CAN_FilterMode_IdList 和标识符屏蔽位模式 CAN_FilterMode_IdMask。
第 8 个成员变量 FilterScale 用来设置过滤器的位宽为 2 个 16 位 CAN_FilterScale_16bit 还是 1 个 32 位 CAN_FilterScale_32bit。
第 9 个成员变量 CAN_FilterActivation 就很明了了,用来激活该过滤器。
- CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器 0
- CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
- CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32 位
- CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;32 位 ID
- CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
- CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位 MASK
- CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
- CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;// FIFO0
- CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0
- CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
4)发送接受消息
发送消息的函数是:
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
第一个参数是 CAN 标号,我们使用 CAN1。第二个参数是相关消息结构体 CanTxMsg 指针类型,CanTxMsg 结构体的成员变量用来设置标准标识符,扩展标识符,消息类型和消息帧长度等信息。 接受消息的函数是:
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
前面两个参数,CAN 标号和 FIFO 号。第二个参数 RxMessage 是用来存放接受到的消息信息。结构体 CanRxMsg 和结构体 CanTxMsg 比较接近,分别用来定义发送消息和描述接受消息。
5)CAN 状态获取 对于 CAN 发送消息的状态,挂起消息数目等等之类的传输状态信息,库函数提供了一些 列的函数,包括 CAN_TransmitStatus()函数,CAN_MessagePending()函数,CAN_GetFlagStatus() 函数等等,可以根据需要来调用。
源码讲解:
打开 can.c 文件,此部分代码总共 3 个函数,首先是:CAN_Mode_Init 函数。该函数用于 CAN 的初始化, 该函数带有 5 个参数,可以设置 CAN 通信的波特率和工作模式等。
第二个函数,Can_Send_Msg 函数。该函数用于 CAN 报文的发送,主要是设置标识符 ID 等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。
- //can 发送一组数据(固定格式:ID 为 0X12,标准帧,数据帧)
- //len:数据长度(最大为 8) msg:数据指针,最大为 8 个字节.
- //返回值:0,成功; 其他,失败;
- u8 CAN1_Send_Msg(u8* msg,u8 len)
- {
- u8 mbox;
- u16 i=0;
- CanTxMsg TxMessage;
- TxMessage.StdId=0x12; // 标准标识符为 0
- TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)
- TxMessage.IDE=0; // 使用扩展标识符
- TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位
- TxMessage.DLC=len; // 发送两帧信息
- for(i=0;i
- TxMessage.Data[i]=msg[i]; // 第一帧信息
- mbox= CAN_Transmit(CAN1, &TxMessage);
- i=0;
- while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++;
- if(i>=0XFFF)return 1;
- return 0;
- }
第三个函数,Can_Receive_Msg 函数。用来接受数据并且将接受到的数据存放到 buf 中。
- //can 口接收数据查询
- //buf:数据缓存区;
- //返回值:0,无数据被收到; 其他,接收的数据长度;
- u8 CAN1_Receive_Msg(u8 *buf)
- { u32 i;
- CanRxMsg RxMessage;
- if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;//没有接收到数据,直接退出
- CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
- for(i=0;i
- buf[i]=RxMessage.Data[i];
- return RxMessage.DLC; //返回接受到的数据长度
- }
can.c 里面,还包含了中断接收的配置,通过 can.h 的 CAN1_RX0_INT_ENABLE 宏定义, 来配置是否使能中断接收,本章我们不开启中断接收的。
can.h 头文件中,CAN1_RX0_INT_ENABLE 用于设置是否使能中断接收,本章我们不用中 断接收,故设置为 0。
main函数
- int main(void)
- {
- u8 key;
- u8 i=0,t=0;
- u8 cnt=0;
- u8 canbuf[8];
- u8 res;
- u8 mode=1;//CAN工作模式;0,普通模式;1,环回模式
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
- delay_init(168); //初始化延时函数
- uart_init(115200); //初始化串口波特率为115200
- LED_Init(); //初始化LED
- KEY_Init(); //按键初始化
- CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,CAN_Mode_LoopBack);//CAN初始化环回模式,波特率500Kbps
-
- while(1)
- {
- key=KEY_Scan(0);
- if(key==KEY0_PRES)//KEY0按下,发送一次数据
- {
- for(i=0;i<8;i++)
- {
- canbuf[i]=cnt+i;//填充发送缓冲区
- }
- printf("发送的数据:\r\n");
- printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
- printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
- res=CAN1_Send_Msg(canbuf,8);//发送8个字节
- if(res)printf("Failed\r\n"); //提示发送失败
- else printf("OK \r\n"); //提示发送成功
- }else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
- {
- mode=!mode;
- CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,mode); //CAN普通模式初始化,普通模式,波特率500Kbps
- if(mode==0)//普通模式,需要2个开发板
- {
- printf("Nnormal Mode \r\n");
- }else //回环模式,一个开发板就可以测试了.
- {
- printf("LoopBack Mode\r\n");
- }
- }
- key=CAN1_Receive_Msg(canbuf);
- if(key)//接收到有数据
- {
- printf("接受的数据:\r\n");
- printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
- printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
- }
- t++;
- delay_ms(10);
- if(t==20)
- {
- LED0=!LED0;//提示系统正在运行
- t=0;
- cnt++;
- //printf("%d\r\n",cnt); //显示数据
- }
- }
-
- }
运行视频
CAN通讯实验
-
相关阅读:
c#基础入门
Linux中安装Jenkins
「中秋来袭」没想到,用OpenCV竟能画出这么漂亮的月饼「附源码」
【kafka】消费者与分区
学成在线_课程查询_前端分页条不显示
包管理器pacman常用方法
CRM客户管理软件对出海企业的帮助与好处
给文件夹加密的两种方法
y114.第六章 微服务、服务网格及Envoy实战 -- Envoy网格安全(二五)
带你刷(牛客网)C语言百题(第三天)
-
原文地址:https://blog.csdn.net/qq_64531765/article/details/126805264