STM32之CAN

1. CAN协议

• CAN协议,全称为Controller Area Network(控制器局域网络)是一种广泛应用的串行 通信 协议。

1.1 CAN种类

• **低速CAN(**ISO11519)通信速率10~125Kbps,总线长度可达1000 米,抗干扰能力更强。

• **高速CAN(**ISO11898)通信速率125Kbps~1Mbps,总线长度≤40米,抗干扰能力比较弱(对比于低速CAN来说)。

• 当然,CAN的传输速率也和环境,配置,物理线等相关。

1.2 定义

• CAN协议是一种基于差分信号 ( 如果受到影响,那么将会是两根线都会受到影响,然后两条线相减去之后,实际上并没受到影响的,或者说影响没有单线的大,所以 CAN 的抗干扰能力强。 )异步 ( 没有时钟信号 ) 串行通信协议 ,采用双绞线 作为传输介质,具有高性能、高可靠性和独特的设计特点。

1.3 特点

多主控制 :支持多主方式 ,即任何一个节点都可以在总线上发送数据,其他节点根据需要进行接收。当两个以上的节点同时开始发送数据时,会根据标识符( ID 决定优先级。 补充:IIC也有多主特点,但是对比CAN的多主来说是,IIC的抗干扰能力,冲突解决机制 不如CAN,而且CAN还有错误检测机制等。

系统柔软性 :与总线相连的单元没有类似于 " 地址 " 的信息 ,因此在总线上增加单元时,连接在总线上的其他单元的软硬件及应用层都不需要改变

通信速度快、距离远:数据传输速率较高,标准速率为125kbps,扩展速率可达1Mbps,且通信距离远,最远可达1KM(速率低于5Kbps)。

错误检测与恢复:具有错误检测、错误通知和错误恢复功能,能够确保数据传输的可靠性。

如果发送单元发出的是错误信息,其他的接收单元会作为类似"裁判"那样,进行错误检测,一旦检测到错误的数据,发送错误帧来破环数据,保证其他数据的完整性。

故障封闭功能 :能够判断出错误的类型,并将引起故障的单元从总线上隔离出去

2. CAN控制层介绍

2.1 CAN网络通常由CAN 控制器、 CAN 收发器和双绞线组成

CAN控制器 :负责处理数据的收发和协议转换

CAN收发器 :负责将控制器的数字信号转换为差分信号 进行传输,同时也负责将总线上的差分信号转换为数字信号供控制器处理。(就是两个方向传输)。

2.2 终端电阻

• 在高速 CAN 总线两端 分别连接一个电阻(120欧),称为终端电阻。终端电阻的主要作用是匹配总线阻 提高信号质量 ,减少回波反射 ( 可以理解为点电压被挡了一下,然后调转方向,流动 )。一般来说,终端电阻的阻值为120Ω。

2.3 总线拓展图:

2.3.1 高速CAN,如图:

2.3.2 低速CAN,没有终端电阻,但是总线也连着两个电阻(电阻值为2.2k欧),如图:

2.4 电平标准

• CAN使用差分信号 进行数据传输,根据CAN_H和CAN_L上的电位差来判断总线电平 (CAN_H - CAN_L)

显性电平 表示逻辑 0,通常 CAN_H 和 CAN_L 有 2V 的压差。

隐性电平 表示逻辑 1,通常 CAN_H 和 CAN_L 有 0V 的压差。

显性电平在通信中具有优先权,能够覆盖隐性电平,确保数据的正确传输

• 隐性电平则作为总线的空闲或监听状态存在,等待有节点发送数据

• 而低速CAN的显性电平也是逻辑0,但是 CAN_H 和 CAN_L 有 3V 的压差

• 而低速CAN的隐性电平也是逻辑1,但是 CAN_H 和 CAN_L 有 -1.5V 的压差(没有终端电阻),如图:

2.5 CAN的收发器,如图:

• 电路图:

• 注意这里的tx tx rx rx不用交叉接线, vcc 5v。

• 上图是一个高速CAN的收发器,其电路图里有120R终端电阻。

3. CAN协议层介绍

• CAN帧种类介绍:帧就是数据包

数据帧(Data Frame :数据帧是CAN总线上用于传输用户数据 的帧,包括必要的帧头、标识符、控制位、数据长度代码、数据域、CRC校验码和应答域等部分,是CAN通信中最基本和最重要的帧类型。

遥控帧(Remote Frame :遥控帧用于向总线上的其他节点请求发送 具有相同标识符 的数据帧,它没有数据域,仅通过标识符来指定所需的数据。遥控帧的帧结构与数据帧相似,但缺少数据部分。

错误帧(Error Frame :当CAN总线上的任何节点检测到通信错误时 ,会发送错误帧来通知其他节点。错误帧包含错误标志和错误界定符,用于指示错误的存在和类型。

过载帧(Overload Frame :过载帧用于在连续的数据帧或远程帧 之间提供额外的延时,以指示接收节点尚未准备好接收下一个帧。当接收节点因内部条件限制而无法立即接收数据时,会发送过载帧来请求发送节点暂停发送。

帧间隔( Interframe Space :帧间隔用于隔离数据帧与前面的帧 ,确保它们之间的时间间隔足够长,以避免总线上的冲突和数据丢失。帧间隔包括连续三个隐性位(间隔段)和可能存在的空闲段,用于将数据帧或远程帧与前面的帧分隔开来。

3.1 数据帧与遥控帧的区别

• 数据帧:用于传输数据,包含实际的数据字段,RTR位为显性(0)

• 遥控帧:用于请求数据,不包含数据字段,RTR位为隐性(1)

3.2 CAN数据帧介绍

• 数据帧由7段组成。数据帧又分为标准帧 (CAN2.0A) 和扩展帧 (CAN2.0B) ,主要体现在仲裁段和控制段

• 如图,灰色是显性(0),白色是隐性(1),紫色是可以是1也可以是0。

  • 帧起始
    • 功能表示数据帧的开始
    • 特点 :由一个显性位构成,此时CAN_H为高电平(如3.5V),CAN_L为低电平(如1.5V),二者之间的电位差形成差分信号。
  • 仲裁段
    • 功能确定发送优先级,并包含标识符(Identifier)用于唯一标识发送者和接收者之间的通信关系。
    • 组成:
      • 标准数据帧的仲裁段由11位ID和1位RTR位(远程发送请求位)组成。RTR 位用于区分数据帧 ( 显性电平)和遥控帧(隐性电平)
      • 扩展数据帧中,还包含SRR 位( Substituted Remote Request ,替代的远程请求 )和IDE 位( Identifier Extension ,标识符扩展)。SRR位用于指示发送方是否发送了远程请求帧,IDE位用于指示标识符字段是否使用了扩展格式(29位)。
      • SRR位的作用,替代RTR位置,用来区别标准帧还是扩展帧,还有就是仲裁过程优先级控制,我们知道标准格式的优先级>扩展格式 ,在仲裁过程中,如果两个帧的标识符前 11 位相同,标准帧的优先级高于扩展帧。这是因为标准帧的 RTR 位可以是显性(逻辑 0),而扩展帧的 SRR 位始终为隐性(逻辑 1)。而显性位(逻辑 0)在仲裁中具有更高的优先级。
      • IDE位的作用:区分标准格式(显性)还是扩展格式(隐性)
  • 控制段
    • 功能:包含数据长度代码(DLC),用于定义数据帧中数据域的长度(有多少个字节)
    • 特点:DLC占4位,其取值范围为0到8个字节,表示数据帧中包含的数据字节数。
    • R0和r1都是保留位
  • 数据段
    • 功能:包含要传输的数据,是数据帧的主体部分。
    • 特点 :数据域的长度可以根据DLC 字段的值从0到8个字节不等,数据从最高位(MSB)开始传输。
  • CRC段
    • 功能:用于检测数据帧的传输错误。
    • 特点:CRC(循环冗余校验)是一种通过对数据进行计算生成的校验码,发送方在发送数据帧时会根据数据计算出CRC值,并将其添加到数据帧的CRC段中。接收方在接收到数据帧后会重新计算CRC值,并与接收到的CRC值进行比较,以确认数据在传输过程中是否发生错误。
  • 应答段
    • 功能:用来确认数据帧的正常接收。
    • 组成:由ACK槽(ACK Slot)和ACK界定符两个位构成。当接收节点成功解析了数据帧并确认无误后,会在ACK槽中发送一个显性位作为应答信号。
    • 在ACK槽前后两个都有界定符 ,其目的是给从机有足够的时间来处理数据,并返回信号。
  • 帧结束
    • 功能:表示数据帧的结束。
    • 特点 :主机发送结束帧,由7个连续的隐性位构成,标志着数据帧的传输完成。

3.3 CAN位时序:固定时间去采样总线的电平是怎样的,确保数据在总线上准确传输。

• CAN总线以**"** 位同步 " 机制,实现对电平的正确采样 。位数据都由四段组成:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每段又由多个位时序Tq组成。同时Tq的值也是可以配置的。

• 如图,采样点是在PBS1末尾。其中SS是确定只能占有1Tq,其他三个段的Tq是可以设置的。

  • 所谓采样点是读取总线电平 ,并将读到的电平作为位值的点。位置在 PBS1 结束处。
  • CAN总线通过时钟同步机制 来确保各个节点在通信过程中保持同步 。时钟同步机制包括硬同步和再同步两种。
  • 硬同步:
    • 硬同步只在帧的起始位(SOF)处进行。
    • 当接收节点检测到帧起始位的下降沿时,会将其与自身的位时间进行对齐 ,从而实现同步。
      • 如图,如果SS不在下降沿,那么会硬同步一次,让SS在下降沿处,然后每隔一段时间检查。

      • 再同步

        • 再同步在帧的后续数据位中进行
        • 如果接收节点检测到数据位的跳变沿不在自身的同步段内,则会通过延长或缩短相位缓冲段的时间来调整自身的位时间,以重新获得同步。
        • 再同步时,PBS1和PBS2中增加或者减少的时间被称为"再同步补偿宽度(SJW)",其范围:1~4Tq。
        • 如图
        • 如图

3.4 CAN的仲裁机制

  • CAN总线处于空闲状态,最先开始发送消息的单元获得发送权。(先到先得)。
  • 多个单元同时开始发送时,从仲裁段 ( 报文 ID) 的第一位开始进行仲裁。仲裁原理如下:
    • 标识符优先级:CAN总线中传输的数据帧的起始部分为数据的标识符(ID) 。这个标识符不仅用于区分消息,还 表示消息的优先级 。在CAN 2.0标准中,标识符可以是11位或29位(对于扩展帧)。标识符的数值越小,优先级越高
    • 逐位仲裁
      • 当两个或两个以上的节点同时开始传送报文时,就会产生总线访问冲突。此时,各节点会按照标识符的位顺序逐位进行仲裁。
      • 在仲裁过程中,每个节点都会将自己发送的电平与总线上的电平进行比较。如果电平相同,则节点继续发送下一位;如果电平不同,则优先级低的节点停止发送,而优先级高的节点继续发送。
      • 这种仲裁方式是非破坏性的,即优先级低的节点在仲裁过程中不会破坏总线上已经存在的数据。
    • 显性电平优先:显性电平(逻辑0)的优先级高于隐性电平(逻辑1)。

4. STM32的CAN控制器介绍

4.1 CAN控制器介绍

  • STM32的bxCAN,即基本扩展CAN(Basic Extend CAN),是STM32微控制器系列中集成的CAN 控制器模块
  • 协议支持:支持CAN协议2.0A(只认识标准模式)和2.0B(两个都认识)的主动模式。
  • 高波特率 :波特率最高可达1 兆位 /
  • 时间触发通信 :支持时间触发通信功能,CAN的硬件内部定时器可以在TX/RX的帧起始位的采样点位置生成时间戳
  • 发送功能
    • 具有3 个发送邮箱 ,发送报文的优先级特性可软件配置
    • 记录发送SOF(Start Of Frame,帧起始)时刻的时间
  • 接收功能:具有3级深度的2个FIFO(First In First Out,先进先出队列),每个FIFO都可以存放3个完整的报文,完全由硬件管理。
  • 共有14个位宽可变的过滤器组(设置过滤规则,接收想要的数据)(部分STM32型号可能支持更多),由整个 CAN 共享,用于筛选有效报文。
  • 记录接收SOF时刻的时间。
  • 支持禁止自动重传模式。

4.2 CAN控制器模式

  • CAN控制器的工作模式有三种:初始化模式、正常模式和睡眠模式。
  • 睡眠模式 :在睡眠模式下,CAN 控制器的时钟停止,以降低功耗。但软件仍然可以访问邮箱寄存器。
  • 初始化模式 :在初始化模式下,禁止报文的接收和发送 ,并且CANTX引脚输出隐性位(高电平)。此时,可以对CAN控制器的相关寄存器进行配置,如位时间特性(CAN_BTR)和控制(CAN_MCR)等。
  • 正常模式:作为总线的正常节点,可以向总线发送或接收数据。
  • 如图
  • CAN控制器的测试模式 有三种:静默模式、环回模式和环回静默模式,主要用于特定的测试或调试目的,以确保CAN控制器的功能正常。
  • 静默模式
    • 特点:
      • 在静默模式下,CAN控制器可以正常地接收数据帧和远程帧但只能发出隐性位,而不能真正发送报文。发送隐性位不会影响总线上的数据。
      • 这意味着,虽然CAN控制器在尝试发送数据,但实际上它并没有在CAN总线上产生任何显性位,因此不会对总线上的其他节点产生影响。
    • 应用场景
      • 静默模式通常用于分析 CAN 总线的活动,而不会对总线上的其他通信造成干扰
      • 开发人员可以使用此模式来观察总线上的数据流,而无需担心他们的测试设备会发送出不必要的报文(隐性位)。
  • 环回模式
    • 特点:
      • 在环回模式下,CAN控制器会把发送的报文当作接收的报文并保存(如果可以通过接收过滤)。
      • 这意味着,当CAN控制器发送一个报文时,它会立即在自己的接收缓冲区中看到这个报文,就像它是从总线上接收到的一样。
      • 并且不能从总线上收到数据
    • 应用场景:
      • 环回模式通常用于自测试 ,以验证CAN 控制器的发送和接收功能是否正常。
      • 通过发送一个报文并检查它是否被正确接收,开发人员可以确保CAN控制器的硬件和固件都按预期工作。
  • 环回静默模式:
    • 特点:

      • 环回静默模式结合了静默模式和环回模式的特点
      • 在该模式下,CANRX 引脚与 CAN 总线断开 ,同时CANTX 引脚被驱动到隐性位状态
      • 这意味着,虽然CAN控制器在尝试发送报文,但它实际上并没有在CAN总线上产生任何显性位,并且它会将发送的报文视为接收到的报文。
    • 应用模式:

      • 环回静默模式通常用于**"** 热自测试 " ,即可以在不影响CANTX CANRX所连接的整个CAN系统的情况下进行测试。
      • 这种模式允许开发人员在不干扰总线上的其他通信的情况下,验证CAN控制器的发送和接收功能。
      • 例如:假如接收器一开始是能正常工作,但是后面不能了,但又不能影响到总线上的其他设备,可以用这种模式。
  • 如图

4.3 CAN控制器框图

  • CAN控制内核:包含各种控制/状态/配置寄存器,用于配置CAN 控制器的模式、波特率等参数。
  • 发送邮箱:用来缓存待发送的 CAN 报文 。STM32等微控制器通常具有多个发送邮箱 (如3个),以支持同时缓存多个报文
  • 接收FIFO**:** 缓存接收到的有效 CAN 报文。CAN控制器通常具有多个接收FIFO(如2个),以提高接收效率。
  • 接收过滤器:筛选接收到的 CAN 报文,只将符合特定条件的报文保存到接收FIFO中。这有助于减少CPU的处理负担,提高系统的响应速度。
  • 如图,发送调度:可根据邮箱优先级,报文优先级,报文顺序来设置。

4.4 发送的处理过程

    • 大致过程:选择一个空的邮箱,把报文放进去,然后邮箱挂号,等待最高优先级,预定发送,等待总线的空闲。
    • 接收处理过程:
      • 大致过程:选择一个空的位置,放进有效报文,如果满了就不再放进,期间报文是可以取出来的。
    • 有效报文:有效报文指的是(数据帧直到 EOF 段的最后一位都没有错误 ),且通过过滤器组对标识符过滤
    • 接收过滤器:
      • 当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的存在,选择性接收有效报文,减轻系统负担
      • STM32的CAN控制器支持配置过滤器组,每个过滤器组包含2 32 位的寄存器 CAN_FxR1 CAN_FxR2 ,用于存储要筛选的 ID 或掩码 。对于STM32F103C8T6,如果只有一个CAN控制器,则可以配置14 个过滤器组,对应的编号为 0 13
      • 过滤器可以配置为不同的位宽,以适应不同长度的CAN ID。常见的位宽包括16位(用于标准帧)和32位 (用于扩展帧)。
      • 选择模式可设置屏蔽位模式标识符列表模式 ,寄存器内容的功能就有所区别。屏蔽位模式,可以选择出一组符合条件的报文。寄存器内容功能相当于是否符合条件 。标识符列表模式,可以选择出几个特定ID的报文。寄存器内容功能就是标识符本身。
      • 如图

4.5 CAN的位时序:只有三段时间。

  • 这就是相当于:
  • 例如:设TS1=8、TS2=7、BRP=3,波特率 = 36000 / [( 9 + 8 + 1 ) * 4] = 500Kbps。
  • 注意:通信双方波特率需要一致才能通信成功。
  • 注意:是APB时钟的时钟周期...36M的倒数。

5. CAN基本驱动

  • CAN参数初始化(工作模式,波特率,功能设置)
  • msp初始化(时钟,引脚,中断等)
  • 过滤器设置,
  • 发送,接收函数编写
  • 中断服务函数编写(可选)
  • 在can.c
cpp 复制代码
#include "can.h"
#include "stdio.h"
CAN_HandleTypeDef can_handle = {0};
void can_init(){

    can_handle.Instance = CAN1;
    can_handle.Init.Prescaler = 4;
    can_handle.Init.Mode = CAN_MODE_LOOPBACK;
    can_handle.Init.SyncJumpWidth = CAN_SJW_1TQ;//同步段
    can_handle.Init.TimeSeg1 = CAN_BS1_9TQ;//相位缓冲段1
    can_handle.Init.TimeSeg2 = CAN_BS2_8TQ;//相位缓冲段2
    
    can_handle.Init.AutoBusOff           = DISABLE;  /* 禁止自动离线管理 */
    can_handle.Init.AutoRetransmission   = DISABLE;  /* 禁止自动重发 */
    can_handle.Init.AutoWakeUp           = DISABLE;  /* 禁止自动唤醒 */
    can_handle.Init.ReceiveFifoLocked    = DISABLE;  /* 禁止接收FIFO锁定 */
    can_handle.Init.TimeTriggeredMode    = DISABLE;  /* 禁止时间触发通信模式 */
    can_handle.Init.TransmitFifoPriority = DISABLE;  /* 禁止发送FIFO优先级 */
    HAL_CAN_Init(&can_handle);
    filter_config();
    HAL_CAN_Start(&can_handle);
    
    
}
void filter_config(){
    CAN_FilterTypeDef can_filter = {0};
    can_filter.FilterActivation = CAN_FILTER_ENABLE;
    can_filter.FilterBank = 0;
    can_filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    
    can_filter.FilterIdHigh = 0;
    can_filter.FilterIdLow = 0;
    can_filter.FilterMaskIdHigh = 0;
    can_filter.FilterMaskIdLow = 0;
    //上述的标识符和掩码的配置,说明can所有数据都能接收
    
    can_filter.FilterMode = CAN_FILTERMODE_IDMASK;
    can_filter.FilterScale = CAN_FILTERSCALE_32BIT;
    can_filter.SlaveStartFilterBank = 14;
    /*将 SlaveStartFilterBank 设置为 14,意味着从 CAN 实例将使用从 14 开始的过滤器组。
    这样可以避免主从 CAN 实例之间的过滤器组冲突,确保每个实例都能正确地接收和处理自己的消息
        
        主 CAN 实例(CAN1):使用过滤器组 0 到 13。
        从 CAN 实例(CAN2):使用过滤器组 14 到 27
    */
    HAL_CAN_ConfigFilter(&can_handle,&can_filter);//配置过滤器
}

void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan){//这里的tx rx不用交叉接线
    if(hcan->Instance == CAN1){
        __HAL_RCC_CAN1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        GPIO_InitTypeDef gpio_init = {0};
        
        gpio_init.Mode = GPIO_MODE_AF_PP;
        gpio_init.Pin = GPIO_PIN_12;//推挽输出
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;
        gpio_init.Pin = GPIO_PIN_11;//推挽输出
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
    }
    
}

void can_send_data(uint32_t id,uint8_t* data,uint8_t len){
    uint32_t mail = CAN_TX_MAILBOX0;
    CAN_TxHeaderTypeDef tx_header = {0};
    tx_header.ExtId = id;
    tx_header.DLC = len;
    tx_header.IDE = CAN_ID_EXT;
    tx_header.RTR = CAN_RTR_DATA;

    //TransmitGlobalTime 时间戳
    HAL_CAN_AddTxMessage(&can_handle,&tx_header,data,&mail);
    
    while(HAL_CAN_GetTxMailboxesFreeLevel(&can_handle) != 3);//等着发完(所有邮箱)
    printf("发送数据:");
    uint8_t i = 0;
    for(i = 0;i < len;i++)
        printf("%X ",data[i]);
    printf("\r\n");
    
    
}

uint8_t can_recv_data(uint8_t* data){
    CAN_RxHeaderTypeDef rx_header = {0};
    if(HAL_CAN_GetRxFifoFillLevel(&can_handle,CAN_RX_FIFO0) == 0)//判断有没有数据
        return 0;
    HAL_CAN_GetRxMessage(&can_handle,CAN_RX_FIFO0,&rx_header,data);


    printf("接收数据:");
    uint8_t i = 0;
    for(i = 0;i < rx_header.DLC;i++)
        printf("%X ",data[i]);
    printf("\r\n");
    
    return rx_header.DLC;
}
  • 在key.c
cpp 复制代码
#include "key.h"
#include "delay.h"


void key_init(){
    GPIO_InitTypeDef gpio_init = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
    gpio_init.Mode = GPIO_MODE_INPUT;
    gpio_init.Pin = GPIO_PIN_0 | GPIO_PIN_1;//推挽输出
    gpio_init.Pull = GPIO_PULLUP;//默认上拉
    gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio

}

uint8_t key_scan(){
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){//判断按键是否被按下
        //延时一段时间,在判断按键是否被按下,达到软件消抖作用
        delay_ms(10);
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){//在判断按键是否被按下
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);//如果被按下,等待松开按键
            return 1;//返回按键值
        }
    }
    
    
    
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){//判断按键是否被按下
        //延时一段时间,在判断按键是否被按下,达到软件消抖作用
        delay_ms(10);
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){//在判断按键是否被按下
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);//如果被按下,等待松开按键
            return 2;//返回按键值
        }
    }
    
    return 0;

}
  • 在mian.c
cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "can.h"
#include "key.h"


uint8_t send_data[8] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
uint8_t recv_data[8];
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    key_init();
    can_init();
    printf("hello world\r\n");
    uint8_t i = 0;
    while(1)//流水灯实验
    { 
        if(key_scan() == 1){
            can_send_data(0x12345678,send_data,8);
            for(i = 0;i < 8;i++)
                send_data[i]++;
        }
        can_recv_data(recv_data);
        delay_ms(100);
    }
}
相关推荐
阿ฅ( ̳• ε • ̳)ฅ5 小时前
STM32串口打印printf没反应
stm32·单片机·嵌入式硬件
古译汉书6 小时前
串口模拟工具:com0com 介绍、下载、驱动感叹号解决
linux·运维·服务器·stm32·单片机·嵌入式硬件
沐欣工作室_lvyiyi7 小时前
基于单片机的数字调频式立体声收音机
stm32·单片机·嵌入式硬件·毕业设计·收音机·数字调频式
古译汉书9 小时前
【IoT死磕系列】Day 1:IOT物联网各个协议的整体了解
stm32·单片机·嵌入式硬件·物联网·iot
qqssss121dfd20 小时前
STM32H750XBH6的ETH模块的流控功能分析
stm32·单片机·嵌入式硬件
ChenYY~1 天前
手把手教你使用vscode开发stm32!
vscode·stm32·嵌入式·软件开发·学习经验
fly的fly1 天前
RT-Thread消息队列源码机制讲解
c语言·stm32·物联网
Hello_Embed1 天前
Modbus 传感器开发:从寄存器规划到点表设计
笔记·stm32·单片机·学习·modbus
小龙报1 天前
【51单片机】不止是调光!51 单片机 PWM 实战:呼吸灯 + 直流电机正反转 + 转速控制
数据结构·c++·stm32·单片机·嵌入式硬件·物联网·51单片机