• CAN通信实验


    本实验通过 KEY_UP 按键选择 CAN 的工作模式(正常模式/环回模式),然后通过 KEY0 控制数据发送,并通过查询的办法,将接收到的数据通过串口打印出来。如果是环回模式,用一个开发板即可测试。如果是正常模式,就需要 2 个探索者 STM32F4 开发板,并且 将他们的 CAN 接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据通过串口打印出来。

     CAN 的初始化配置步骤:

    1)配置相关引脚的复用功能(AF9), 使能CAN 时钟。

    1. //使能相关时钟
    2. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能 PORTA 时钟
    3. RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
    4. //初始化 GPIO
    5. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
    6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    7. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
    8. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    9. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    10. GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 PA11,PA12
    11. //引脚复用映射配置
    12. GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);//PA11 复用为 CAN1
    13. 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;

    初始化实例为: 

    1. CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
    2. CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
    3. CAN_InitStructure.CAN_AWUM=DISABLE; //睡眠模式通过软件唤醒
    4. CAN_InitStructure.CAN_NART=ENABLE; //禁止报文自动传送
    5. CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
    6. CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
    7. CAN_InitStructure.CAN_Mode= CAN_Mode_LoopBack; //模式设置 1,回环模式;
    8. CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;//重新同步跳跃宽度为个时间单位
    9. CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; //时间段 1 占用 8 个时间单位
    10. CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;//时间段 2 占用 7 个时间单位
    11. CAN_InitStructure.CAN_Prescaler=5; //分频系数(Fdiv)
    12. 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 就很明了了,用来激活该过滤器。

    1. CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器 0
    2. CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
    3. CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32 位
    4. CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;32 位 ID
    5. CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
    6. CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位 MASK
    7. CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
    8. CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;// FIFO0
    9. CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0
    10. 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 等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。

    1. //can 发送一组数据(固定格式:ID 为 0X12,标准帧,数据帧)
    2. //len:数据长度(最大为 8) msg:数据指针,最大为 8 个字节.
    3. //返回值:0,成功; 其他,失败;
    4. u8 CAN1_Send_Msg(u8* msg,u8 len)
    5. {
    6. u8 mbox;
    7. u16 i=0;
    8. CanTxMsg TxMessage;
    9. TxMessage.StdId=0x12; // 标准标识符为 0
    10. TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)
    11. TxMessage.IDE=0; // 使用扩展标识符
    12. TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位
    13. TxMessage.DLC=len; // 发送两帧信息
    14. for(i=0;i
    15. TxMessage.Data[i]=msg[i]; // 第一帧信息
    16. mbox= CAN_Transmit(CAN1, &TxMessage);
    17. i=0;
    18. while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++;
    19. if(i>=0XFFF)return 1;
    20. return 0;
    21. }

    第三个函数,Can_Receive_Msg 函数。用来接受数据并且将接受到的数据存放到 buf 中。

    1. //can 口接收数据查询
    2. //buf:数据缓存区;
    3. //返回值:0,无数据被收到; 其他,接收的数据长度;
    4. u8 CAN1_Receive_Msg(u8 *buf)
    5. { u32 i;
    6. CanRxMsg RxMessage;
    7. if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;//没有接收到数据,直接退出
    8. CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
    9. for(i=0;i
    10. buf[i]=RxMessage.Data[i];
    11. return RxMessage.DLC; //返回接受到的数据长度
    12. }

    can.c 里面,还包含了中断接收的配置,通过 can.h 的 CAN1_RX0_INT_ENABLE 宏定义, 来配置是否使能中断接收,本章我们不开启中断接收的。

    can.h 头文件中,CAN1_RX0_INT_ENABLE 用于设置是否使能中断接收,本章我们不用中 断接收,故设置为 0。

    main函数

    1. int main(void)
    2. {
    3. u8 key;
    4. u8 i=0,t=0;
    5. u8 cnt=0;
    6. u8 canbuf[8];
    7. u8 res;
    8. u8 mode=1;//CAN工作模式;0,普通模式;1,环回模式
    9. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    10. delay_init(168); //初始化延时函数
    11. uart_init(115200); //初始化串口波特率为115200
    12. LED_Init(); //初始化LED
    13. KEY_Init(); //按键初始化
    14. CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,CAN_Mode_LoopBack);//CAN初始化环回模式,波特率500Kbps
    15. while(1)
    16. {
    17. key=KEY_Scan(0);
    18. if(key==KEY0_PRES)//KEY0按下,发送一次数据
    19. {
    20. for(i=0;i<8;i++)
    21. {
    22. canbuf[i]=cnt+i;//填充发送缓冲区
    23. }
    24. printf("发送的数据:\r\n");
    25. printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
    26. printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
    27. res=CAN1_Send_Msg(canbuf,8);//发送8个字节
    28. if(res)printf("Failed\r\n"); //提示发送失败
    29. else printf("OK \r\n"); //提示发送成功
    30. }else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
    31. {
    32. mode=!mode;
    33. CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,mode); //CAN普通模式初始化,普通模式,波特率500Kbps
    34. if(mode==0)//普通模式,需要2个开发板
    35. {
    36. printf("Nnormal Mode \r\n");
    37. }else //回环模式,一个开发板就可以测试了.
    38. {
    39. printf("LoopBack Mode\r\n");
    40. }
    41. }
    42. key=CAN1_Receive_Msg(canbuf);
    43. if(key)//接收到有数据
    44. {
    45. printf("接受的数据:\r\n");
    46. printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
    47. printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
    48. }
    49. t++;
    50. delay_ms(10);
    51. if(t==20)
    52. {
    53. LED0=!LED0;//提示系统正在运行
    54. t=0;
    55. cnt++;
    56. //printf("%d\r\n",cnt); //显示数据
    57. }
    58. }
    59. }

    运行视频

    CAN通讯实验

  • 相关阅读:
    c#基础入门
    Linux中安装Jenkins
    「中秋来袭」没想到,用OpenCV竟能画出这么漂亮的月饼「附源码」
    【kafka】消费者与分区
    学成在线_课程查询_前端分页条不显示
    包管理器pacman常用方法
    CRM客户管理软件对出海企业的帮助与好处
    给文件夹加密的两种方法
    y114.第六章 微服务、服务网格及Envoy实战 -- Envoy网格安全(二五)
    带你刷(牛客网)C语言百题(第三天)
  • 原文地址:https://blog.csdn.net/qq_64531765/article/details/126805264