MM32F0140
本文将介绍在使用 MM32F0140 系列 MCU 实现 UDS Bootloader 过程中涉及到的 FlexCAN、UDS 和 Bootloader 等相关基本概念。
MM32F0140 简介
MM32F0140 使用高性能的 Arm® Cortex-M0 内核的 32 位微控制器,最高工作频率可达 72MHz,内置 64KB Flash 和 8KB SRAM,有丰富的增强型 I/O 端口和包括 FlexCAN 在内等多种外设,适用于汽车诊断仪,后装汽车协控制器和消防监控等多种应用场合。

什么是 FlexCAN?
CAN 是控制器域网 (Controller Area Network, CAN) 的简称,是一种功能丰富的车用总线标准,被设计用于在不需要主机(Host)的情况下,允许网络上的单芯片和仪器相互通信。是由研发和生产汽车电子产品著称的德国 BOSCH 公司开发了,并最终成为国际标准(ISO11898)。是国际上应用最广泛的现场总线之一。FlexCAN 是 CAN 协议的一个高完成度版本。
MM32F0140 系列 MCU 内嵌的 FlexCAN,符合 ISO 11898-1 标准,支持 CAN 2.0B 版本协议,位速率高达 1 Mbps,具有非常灵活的用于传输和接收的邮箱系统,可以接收和发送 11 位标识符的标准帧,也可以接收和发送 29 位标识符的扩展帧,主要被设计用作车载串行总线,可满足实时处理、车辆在电磁干扰环境下的可靠操作、成本效益、带宽等要求。
什么是 UDS?
UDS(Unified Diagnostic Services,统一诊断服务)是一种用于汽车电子控制器 ECU (Electronic Control Units) 环境下的一种诊断通信协议,可实现诊断、固件更新、日常测试等功能,在 ISO 14229 中规定了其实现标准。
在本实例中,UDS 通信是在客户端-服务端关系中执行的。客户端是上位机下载软件运行于 PC 机,服务端是 MM32F0140 系列 MCU。例如,将 CAN 总线接口连接到 MCU,并将 UDS 请求发送到 MCU。当 MCU 支持 UDS 服务时,它将根据客户端发出的请求做出相应的响应。
为什么用 Bootloader?
对于 ECU 而言,如果程序内置有基于FlexCAN Bootloader,则每次更新 ECU 的固件可不必再使用烧录器进行烧录,而可直接通过 CAN 总线来更新程序,而且随着汽车智能化的普及,甚至可以对 ECU 进行远程升级。有无 Bootloader 功能程序结构对比如图 2 所示:

为什么要基于 UDS?
为了规范 Bootloader 的全过程。因 UDS 在设计时考虑了 Bootloader 的需求,并为 Bootloader 提供了相关服务以供使用,故主机厂普遍会要求在 UDS 规范的基础上完成 Bootloader 功能。
使用到哪些 UDS 服务?
● 在 Bootloader 中,使用到 UDS 的 $10、$11、$27 和 $3E 基础诊断服务,$22、$2E 读写 DID 服务,$31、$34、$36 和 $37 固件数据传输相关服务。
● 在 APP 中,使用到 UDS 的 $85 和 $28 服务,保证暂停 CAN 正常通信,暂停记录 DTC,让被升级设备升级。
UDS 提供的服务概览如图 3 所示:

CAN、UDS 和 OSI 模型之间的关系
为了更好的理解 UDS, 让我们了解一下 CAN 总线、UDS 和 OSI 模型之间的关系。
CAN 对应于 OSI 模型中的数据链路层和物理层描述(根据 ISO 11898)。
与 CAN 相比, UDS (ISO 14229) 是一种 “更高层协议”, 在 OSI 模型中使用到会话层和应用层,如下图 4 所示:

UDS 的消息结构
PDU
Network_Protocol Data Unit, 网络层协议数据单元
PDU 是用于建立对等实体间的通信,是一组信息和数据的集合,表示了发送发和接收方对等实体之间传递的信息和数据。由地址信息(CAN ID)、协议控制信息(PCI) 和数据构成。
图 5 为 UDS 消息结构示意图,图 6 为 UDS 消极响应示意图。


PCI
Protocol Control Information,协议控制信息
PCI 字段本身与 UDS 请求本身没有关系,但是对于在 CAN 总线上发出的诊断 UDS 请求是必需的。PCI 字段可以长达 1 ~ 3 字节,并且包含与传输不适合单个 CAN 帧的消息有关的信息。
SID
Service ID,服务标识符
当希望使用特定的 UDS 服务时,UDS 请求消息应该在数据有效负载中包含 UDS 服务标识符 (SID)。标识符分为请求 SID 和响应 SID。
SFB
Sub Function Byte,子函数字节
在一些 UDS 请求帧中使用,在一些 UDS 服务中,如 0x22,子函数字节没有使用。一般来说,当一个请求被发送到 ECU 时,ECU 可以做出正向或负向的响应。在响应为正向的情况下,测试人员可能想要抑制响应(因为它可能是不相关的)。这是通过在子函数字节中将第 1 位设置为 1 来完成的。负向的反应是无法被抑制的。剩下的7位可以用来定义最多 128 个子函数值。例如,当通过 SID 0x19(读取诊断信息)读取 DTC 信息时,子函数字节可用于控制报告类型。
DID
Data Identifier,数据标识符
在大多数 UDS 请求服务中,各种类型的请求数据参数用于提供 SID 和可选子函数字节以外的请求进一步配置。
ISO-TP 标准
ECU 固件更新通常涉及大量有效载荷的通信,而 ISO-TP 标准(ISO 15765 )就是为了解决基于 CAN 的车辆诊断的大量有效载荷问题而提出。该标准指定了基于CAN 的车辆网络传输协议和网络层服务,最常见的用例就有 UDS (ISO 14229-1)。
ISO-TP 标准概述了如何通过分段、流量控制和重组来传输高达 4096 字节的 CAN 数据有效载荷。ISO-TP 定义了用于通信的 CAN 帧,如下图 7 所示。

通过使用 ISO-TP 标准将 UDS 的消息结构 PDU 分为了四种类型:
SF (Single Frame, 单帧)
描述单帧传输。
FF (First Frame, 首帧)
描述多帧传输的起始。
CF (Consecutive Frame,连续帧)
用于在多帧传输中传输数据。
FC (Flow Control Frame,流控帧)
用于在多帧传输过程中,对报文流控制。
UDS 的单帧通信和多帧通信:
单帧通信如图 8 所示:

多帧通信过程如图 9 所示:

基于 UDS Bootloader 实现更新 APP 流程框图
MM32F0140 系列 MCU 使用 FlexCAN 实现基于 UDS Bootloader 更新 APP 的流程框图如10所示:

结语
本文以 MM32F0140 系列 MCU 的 FlexCAN 为例,简要介绍了在使用 MM32F0140 系列 MCU 实现 UDS Bootloader 过程中涉及到的 FlexCAN、UDS 和 Bootloader 等相关基本概念,并介绍了 UDS 的消息结构和 ISO-TP 标准,以及展示了 MM32F0140 系列 MCU 使用 FlexCAN 实现 UDS Bootloader 更新 APP 的流程框图。
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。










CRC校验(循环冗余校验Error Correcting Code)是数据通讯中最常采用的校验方式之一,它是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误,它是利用除法及余数的原理来作错误侦测的。
MM32F0140系列MCU带有一个硬件CRC计算单元,它采用一个固定的多项式发生器来计算8位、16位或者是32位数据的CRC校验值,对数据传输或数据存储的一致性、完整性进行验证。
主要特性如下所示:
● 固定计算多项式 0x04C11DB7:
X32 + X26 + X23 + X22 + X16 + X12 + X11 + X10 + X8 + X7 + X5 + X4 + X2 + X + 1
● 支持8、16、32位宽的数据输入寄存器、32位宽的数据输出寄存器
● 硬件计算时间为3个HCLK周期
● 带有可存放中间计算过程的32位宽的数据寄存器
● 支持CRC-32和CRC-32/MPEG-2两种算法
● 支持输入数据和输出数据的大小端选择
功能框图

硬件CRC计算操作步骤
STEP1、使能CRC模块时钟,并复位CRC模块
STEP2、配置CRC_CR寄存器,选择CRC-32或者CRC-32/MPEG-2算法,配置输入数据位宽为8位、16位或者是32位,选择输入、输出数据的大小端
STEP3、通过配置CRC控制寄存器的RST位,将CRC恢复到初始状态
STEP4、依次将数据写入CRC数据寄存器中,MCU自动完成CRC计算过程
STEP5、读取CRC数据寄存器,得到CRC计算结果
硬件CRC示例程序
/* 使能CRC模块时钟,并复位CRC模块 */
RCC->AHBENR |= RCC_AHBENR_CRC; RCC->AHBRSTR |= RCC_AHBENR_CRC; RCC->AHBRSTR &= ~RCC_AHBENR_CRC;
/* 配置输入输出数据跟随MCU小端,输入数据选择32位宽,使用CRC-32/MPEG-2算法 */
CRC->CR = 0;
/* 将CRC恢复到初始状态 */
CRC->CR |= CRC_CR_RESET;
/* 计算CRC */
for(uint8_t i = 0; i < length; i++) { CRC->DR = buffer[i]; }
/* 读取CRC计算结果 */
return CRC->DR;
软件CRC的实现是将硬件CRC的计算过程用软件代码的形式体现出来,通过MCU运行功能代码实现CRC的计算过程,与硬件CRC相比,需要根据多项式产生CRC表,在计算过程中还需要考虑数据的大小端选择等参数,实现过程相比硬件CRC要复杂很多、计算速度相比硬件CRC要慢很多。
软件CRC示例程序
/* 根据多项式产生查表数据 */
void CRC_MPEG2_GenerateCRCTable(void) { uint32_t i = 0, j = 0, Data = 0, Temp = 0; for(i = 0; i < 256; i++) { Data = 0; Temp = (i << 24); for(j = 0; j < 8; j++) { if((Data ^ Temp) & 0x80000000) { Data = ( Data << 1 ) ^ 0x04C11DB7; } else { Data <<= 1; } Temp <<= 1; } CRC_MPEG2_Table[i] = Data; } }
/* 大小端处理 */
uint32_t CRC_MPEG2_SwapEndian(uint32_t data) { return (((data << 24) & 0xFF000000) | ((data << 8) & 0x00FF0000) | ((data >> 8) & 0x0000FF00) | ((data >> 24) & 0x000000FF)); }
/* 软件CRC计算过程 */
uint32_t CRC_MPEG2_CalcCRC(uint32_t *Buffer, uint16_t Length) { uint8_t i = 0; uint32_t Temp = 0, Result = 0xFFFFFFFF; uint32_t *pData; /* Dynamic generate CRC-32/MPEG-2 table */ CRC_MPEG2_GenerateCRCTable(); while(Length--) { Temp = CRC_MPEG2_SwapEndian(*Buffer++); pData = &Temp; for(i = 0; i < 4; i++) { Result = (Result << 8) ^ CRC_MPEG2_Table[((Result >> 24) ^ *((uint8_t *)pData + i)) & 0xFF]; } } return Result; }
对于同一组32位114个数值数据分别进行软件CRC和硬件CRC计算,在不考虑软件CRC自动生成查表数据所消耗时间的情况下,软件CRC与硬件CRC在运算执行时间对比如下图所示:


软件CRC相比于硬件CRC在执行速度上最少有5倍的差距,软件CRC的代码量以及占用SRAM的空间也比硬件CRC要多;所以在MCU带有硬件CRC功能时,通过硬件CRC的计算方式可以大大节省MCU的资源、提升CRC的运算速度,给实际应用带来流畅的检验。
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。



前面一个章节中我们和大家一起学习了CAN的物理层和链路层的一致性测试项,本章节我们将和大家一起了解CAN的应用层一致性测试。
应用层一致性测试
该测试项主要为了验证 ECU 在网络中通信的完整性,一般还包括上层应用层协议的测试,网络管理功能的测试以及故障诊断等方面。
1.1 发送报文测试
报文的发送一般分为周期性的、紧急事件触发型的以及软件使能型,周期性的报文需要测验周期时长的偏差是否满足规约,另外 2 种类型的报文需要在干扰条件下进行看是否会造成发送失败情况。一般以固定 ID 发送至 CAN 工具(一个 CAN 网络至少需要有 2 个节点),通过上位机的时间戳功能查验。
报文发送周期测试主要是测试 DUT 期性报文的间隔时间是否小于允许误差。在DS301通讯规范中,要求由测试设备触发 DUT 进入周期性发送状态,测试设备接收 1 分钟报文后,进行统计,查看是否有间隔超过 20% 的误差的报文。
另外,一般需要 CAN 具备自动重发功能,可以通过 CAN_MCR.AEN 位实现,官方库例程中默认不使能 CAN_MCR.AEN位。
在发送时还有一点需要注意,FlexCAN具有接收自己发出的报文功能,且库例程中是默认开启该功能的,具体可参见 CAN_CTRL1.SRXDIS 寄存器描述,视情况决定是否禁止启用该功能。
1.2 CAN_H/L 对电源/对地短路容错性测试
测试 ECU 在不同错误状态下的容错性能,看错误条件解除后是否能够自动恢复通讯。一般测试时需要使 ECU 处于周期性发送报文的状态,且 CAN_H 或者 CAN_L 对电源/对地短路和自己与总线断路的维持时间要超过 1 min 钟保证错误的积累数致使 ECU 处于被动错误状态。
依据 GMW3122 定义,可分为7 种物理错误类型,包括地线漂移、地线丢失、电源丢失、CAN 线中断、CAN 线各短接到地、CAN 线各短接到电源、CAN 线短路等错误状态。如果恢复后,都可以恢复通讯,则通过测试。
实际测试发现,这几种短路和断路条件只会使 FlexCAN 的 Tx error 计数器增加到超过 127 ,进入被动错误状态而不会发生 Busoff ,期间会一直尝试重发,在恢复总线正常后,计数器的值会逐一递减同时由于能够收到正确的 ACK 响应而恢复正常通讯了,恢复的时长也符合标准。下图展示的是 2s 周期发送报文直到恢复正常通讯的实测图:

CAN_H 与 CAN_L 短接会导致 FlexCAN 进入 Busoff 状态,由于官方库例程中默认设置开启了自动从总线关闭状态中恢复的功能,所以在解除总线短路后的一段时间内,ECU 重新恢复正常通讯,自动恢复时长 = 128 x 11 x 位时间(即检测到 128次11个连续空闲状态位电平则恢复,符合 CAN2.0B 标准)。
1.3 BusOff快恢复/慢恢复策略测试
当 CAN 通信出现故障时,CAN 控制器会让故障节点从主动错误状态进入被动错误状态,甚至进入总线关闭(BusOff)状态,使故障节点脱离总线的通信,使其不影响正常节点的通信,但该控制方案将导致在系统重新上电之前,进入总线关闭状态的节点会持续无法与其他节点做数据的交互,如若节点只是暂时的故障,那让节点实现自恢复的功能,则是更为上乘的控制方法。所以 CAN 总线设计规范对于 CAN 节点的 BusOff 自恢复方式做了严格的规定,充分考虑了偶发故障与持续故障的处理。
为了避免某个设备因为自身原因(例如硬件损坏)导致无法正常收发数据而不断地破坏数据帧,从而影响其他正常节点通讯,CAN-bus规范中规定每个CAN控制器都有一个发送错误计数器和一个接收错误计数器。根据计数值不同CAN节点会处于不同的设备状态。根据计数值的变化进行状态转换,状态转换如下图所示:

接收、发送错误计数器对应的变动条件及数值变动情况:
1、接收单元检测出错误时,检测到错误标识符或过载标志的“位错误”除外,此时REC+1、TEC不变;
2、接收单元在发送完错误标志后检测到第一位为显性电平,此时REC+8、TEC不变;
3、发送单元输出错误标志,此时REC不变、TEC+8;
4、发送单元发送主动错误标志或过载标志,检测出位错误,REC不变、TEC+8;
5、接收单元发送主动错误标志或过载标志,检测出位错误,REC+8、TEC不变;
6、各单元从主动错误标志、过载标志的开始检测出连续14个显性位,之后每检测出连续8个显性位,发送时REC+8、接收时TEC+8;
7、检测出被动错误标志后追加连续8个位的显性位,发送时REC+8、接收时TEC+8;
8、发送单元正常接收数据结束时(返回ACK且到帧结束位检测到错误),REC不变、TEC-1;
9、接收单元正常接收数据结束时(到CRC未检测出错误且正常返回ACK),REC<127时,rec-1,rec>127时,REC=127;TEC不变;
10、处于总线关闭的单元,检测到128次连续11个位的隐形位,错误计数器归零,REC、TEC=0;CAN总线错误处理功能属于是链路层功能,此功能由CAN控制器决定。
汽车内部挂有很多的 ECU 节点,当其中一个节点发生故障进入总线关闭状态时,会很大程度上影响整车 CAN 网络的通讯。所以一方面需要迅速找到引发节点总线关闭的物理故障,另一方面需要遵循一定准则合理设计恢复策略,主要为了控制节点从总线关闭状态恢复到错误主动状态的等待时间,太快或者太慢地恢复都将可能影响到网络其它节点的正常通讯,快恢复和慢恢复两种策略一般同时应用。
整车厂对其系统供应商的设备也都提出了相应的 BusOff 后恢复时间的控制策略要求,对总线关闭状态的节点需要实现“快恢复”和“慢恢复”策略。

制定策略大致思路是:将默认配置的自动从总线关闭状态中恢复的功能关闭(CAN_CTRL1.BOFFREC = 1),再实时检查 CAN_ESR1.BOFFINT 寄存器的值来判断是否进入 BusOff状态,当检测到总线关闭后,开启快恢复计时,时间到达后进行一次 CAN 控制器的时钟、驱动及相关寄存器进行初始化操作,初始化完成后如果物理条件恢复正常那么即可完成快恢复,反之循环 10 次前面操作,如果仍旧无法消除 d BusOff 状态,那进入慢恢复策略,同样在开启慢恢复计时并且到达后进行复位 FlexCAN操作, 此时 CAN 总线关闭故障如果解除了,为了避免该节点在 CAN 网络中频繁发生总线关闭问题,此时不立即对外发送 CAN 报文。
大致流程图如下:

至于自定义的 BusOff后的恢复时间策略,可以根据 CAN_CTRL1.BOFFREC 的寄存器描述来控制关闭自动恢复机制,然后根据自己需要的时间间隔完成后开启自动恢复,官方例程默认为开启了BusOff 自动恢复功能,自动恢复时长 = 128 x 11 x 位时间(即检测到 128次11个连续空闲状态位电平则恢复,符合 CAN2.0B 标准)。

在BusOff 恢复策略制定时,是需要通过软件流程去实施实现控制时长,IP 层面不能设置恢复的时长。至于如何判断是主动错误还是被动错误,可以根据 CAN_ESR1.FLTCONF 寄存器以及结合CAN_ECR 寄存器来分析。
我们通过CANScope总线综合分析仪对上述的测试项一一进行测试,MM32F0140的FlexCAN 能够满足相关的测试标准,能够满足汽车领域对通信总线的要求,保障整车CAN总线安全稳定。
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。





一直关注我们灵动微课堂的朋友们,想必通过前面的介绍已经掌握了 FlexCAN 的基本使用方法,也能够在自己的方案中运用自如了。今天小编想和大家借助 ZLG 的CAN Scope工具了解我们这颗 MCU 的 CAN 的特性,看看在汽车 ECU 应用上它是如何保证通讯的鲁棒性。
在整车网络调试中,各节点遵循 CAN 一致性测试是保证总线的稳定运行的重要前提, 当前CAN一致性测试已经电动汽车行业的必然趋势!
据了解,应用到 CAN 的 ECU 在实际装车之前都需要经历一系列的一致性测试方能保证整车通讯的稳定,每家车厂除了遵循 ISO 标准,他们也都有自己的规约和检测流程,但测试项目不外乎都包含在以下表中:

以上测试项中的被测内容,有些是属于板级设计阶段需要重点关注的,有些是与 MCU 本身功能和性能密切相关的,接下来我们分层剖析。
物理层一致性测试
该项测试主要针对各个节点 ECU 板级层面的电阻、电容特性,节点自身差分电阻以及总线终端电阻和CAN 通信物理电平做出充分评测,以验证 CAN 节点与系统网络在电路设计、物理电平等方面的性能。
1.1 终端电阻测试
在高速 CAN 通讯应用中,我们时常会看到信号出现反射、过冲、振铃等异常的现象,根据电磁场和微波理论分析,高频电路中负载阻抗需与传输线缆的特性阻抗一致,否则电信号在线束终端容易遭遇阻抗突变,发生反射现象,造成波形震荡,这将造成信号串扰、总线紊乱以及消息丢失等诸多通讯故障。下图为典型的信号振铃波形图:

所以在差分 CAN 网络中,终端匹配电阻是必不可少的器件,它一般安装在双绞线缆的 2 端,依照总线的拓扑结构而准确定位,需选择线束最远距离的 2 个节点处。
终端电阻的作用:
1.加快放电,使总线快速进入隐性状态,提高抗干扰能力。
2.消除阻抗不连续和不匹配,提高信号质量。
终端电阻的存在,一方面能提高总线抗干扰能力,让高频低能量的信号迅速泄放,一方面能确保总线快速进入隐性状态,让寄生电容的能量更快泄放,另一方面还能提高信号质量,降低了线缆上的反射能量。
终端电阻过小会造成信号幅值偏小,信号在采样时无法辨别高低电平,导致整个网络数据混乱,有时可以通讯,但极不稳定。
终端电阻过大会造成信号幅值偏大,驱动力过大,信号出现过冲现象,信号下降沿过缓,下降沿时间过长,严重的情况直接导致总线错误。
一般终端电阻的实际阻值选择需要根据实测得到,因为它受任何一根线缆的特征阻抗(这里包括系统的容抗+感抗)所影响。
有个粗犷的测试手法:采用两根汽车使用的典型线缆扭制成双绞线,线缆的一端接方波发生器,另一端接可调电阻,并通过示波器观察电阻上的波形,调整电阻阻值的大小,直到电阻上的信号是一个良好的无振铃的方波,此时的电阻值可以认为与线缆的特征阻抗一致从而得到匹配电阻的阻值数据。另外也可以直接找到车上的通讯线缆用万用表实测。终端电阻阻值和功率的选择在 ISO 11898-2 标准里面也有定义,可以作为首选参考。

1.2 显、隐性输出电压测试
CAN 总线设计规范对于 CAN 节点的输出电压有着严格的规定, 单个节点的输出电压如果不符合规范,则在现场组网后容易出现信号电平不可靠的情况,导致错误帧的出现,各节点间无法进行通信。
差分电压值决定了 CAN 的显、隐性位(显性电平的差分电平为高,TTL 逻辑电平为低;隐性电平的差分电平为低,TTL 逻辑电平为高),而 CAN_H / CAN_L 电压情况又由收发器芯片的质量决定。
在数据传输的空闲期间 CAN 总线差分电压为 0v,CAN_H / CAN_L 对地电压为 2.5v ,这个电压标准也不是绝对的,但一般 CAN_H高于 2.5v,CAN_L 低于 2.5v,两者加起来约为 5v 即算作正常。对于 CAN 信号电平有两套不同标准做了界定, ISO 11898 高速 CAN 标准和 ISO11519-2 低速容错 CAN 标准,如下表描述:

需要使用示波器多次测量 CAN 总线空闲时期以及通讯时期的显性位的 CAN_H 和 CAN_L 电压值,来分析电平情况是否满足标准要求,同时也可检测出收发器芯片是否存在故障,下图为实际抓取的测试图:

1.3 跳变沿时间测试
主要为测试 ECU 的差分电平在隐性电平到显性电平、显性电平到隐性电平变化的时间,需要在指定的波特率条件下进行测试,接线长度需小于1米,波特率为 250k 的情况下进行通信,且将差分电平的上升、下降时间的起始点定为电压值的 20%~80% 的区间处,即从隐性到显性状态过渡的上升时间起始点为差分电压的 20% 处,终止点为差分电压的 80% 处;从显性向隐性状态过渡的下降时间起始点为差分电压的 80% 处,终止点为差分电压的 20% 处。
简单测量时可以选用示波器直接抓取波形测试,但一般需要每个边沿至少测量 1000 次,以确定边缘上升/下降时间的最小值和最大值,数据才更为可靠,这就需要使用专业的 CAN 测试仪器了,比如 CAN Scope、PICO Scope 等等。跳变沿的时间需要遵循 GMW3122 信号边沿标准:

MM32F0140 实测通信质量:

数据链路层一致性测试
该项测试覆盖的内容主要用于保证各个节点的通讯参数能够保持一致性,所组成的网络能够正常有效的工作,除了评估板级使用到的元器件参数,更多的还涉及到 FlexCAN底层驱动库的调试,不同寄存器配置所带来的影响是非常直观的。
2.1 位时间测试
汽车 CAN 总线设计规范对于 CAN 节点的差分信号位时间(即波特率)有着严格的规定,同一总线上每个节点的信号位时间必须保持较高的一致性,否则节点组合到一起后,会导致错误帧的出现,各节点间无法进行通信。
位时间偏差主要发生在如下情况:使用了非整数值的晶振(比如 11.0592MHZ)、极端温度导致晶振偏差、CAN 控制器内部波特率发生器偏差。针对MCU的晶振器件选型包括晶振电路的调试在我们另外的文档中进行介绍,本次位时间测试是在已经调试好晶振电路的电路上进行验证测试。
MM32F0140 系列的 FlexCAN控制器将一个位时间分为同步段、传播时间段、相位段1、相位段2等若干个 Tq 时间序列,每个段所占的 Tq 数可根据对应寄存器所配置,合理配置位时序的 Tq 数极为重要,具体可以参照 UM 手册中 23.3.10.4 协议时序章节描述去理解每个段和 Tq 配置。
官方驱动例程中,选取的系统时钟源为 HSI 倍频到 72M,再经过 2 分频后得到 CAN 系统时钟(CHI) 36M,也就是 FlexCAN通信时钟源中的同步时钟(MM32F0140 系列只能选择该时钟源,而不能选择外部晶振作为时钟源,即 PE 时钟等于 CHI 时钟),由于内部时钟精度要比外部晶振低,所以在测试位时间时需要将 RCC 时钟外设改写配置,选择 HSE 倍频到 72M 其它不变。其它按照默认配置,得到的波特计算公式为:
Baud = Fsclock / (CAN_CTRL1.PSEG1+ CAN_CTRL1.PSEG2 + CAN_CTRL1.PROPSEG+ 4)
Fsclock = Fcanclk / (CAN_CTRL1.PRESDIV+1)
Fsclock 为 FlexCAN串行时钟频率, Fcanclk 为 PE 时钟频率,即为 CHI 时钟频率 Fsys
通过以上公式计算得出波特率为 500KHz,与实际程序配置是相符合的。位时间计算公式为:
Tcanbit = 1 / Baud
通过以上公式计算得出位时间为 2us,FlexCAN最小时间单元 Tq 个数为 canTiming[0].timeQuanta = 8(即为 canTiming[0] 中的 PROPSEG/PSEG1/PSEG2 之和再加1),Tq 时长计算公式为:
Tq = Tcanbit / Tq num
Tq = (CAN_CTRL1.PRESDIV+1) / Fsys
通过以上 2 公式都可以计算得 Tq 时间为 0.25us。经过理论计算后,需要实际使用示波器抓取一个位波形来测量位时间宽度,一般需要多次测量一帧数据中的位宽波形直至超过 100 次,或者截取一串数据帧中偏差最大的那一帧总时长再除以位数即可得出位时间,再或者就是直接使用 CAN Scope 的位时间精度功能实测,可以得出以下结果:

官方驱动中的 SJW 默认设置为 1 ,在实际允许的容忍偏差范围内,可以自行调整该参数,一般建议设置为 2~3 个 Tq 。
2.2 采样点测试
除了精准的波特率,采样点位置的匹配同样是要求即为严苛的,在不同的标准中对采样点要求各不相同,而且不同车厂对采样点的设置、时钟源的需求以及每个段所占的 Tq 数也都是不尽相同的。下图展示了 CiA 推荐的数据场采样点位置范围:


在汽车工程师协会 SAE class C 标准中将高速总线波特率定为 125K ~ 1 M 范围,中、低速总线波特率定为 125K 以下范围,高速总线推荐使用单次采样模式,而中低速总线推荐 3 倍采样模式。
网络中节点采样点的不一致会导致同样的采样频率出现采样错误,进而使整车网络出现故障问题,影响整车环境稳定。
官方例程中默认使用的 3 倍采样,且 CAN Scope 仅支持单次采样模式,所以需要按照以下方式修改采样点模式:

更改后,其它按照默认配置,则采样点设置成了 75% ,采样点计算公式如下:
Sample-Point = (1 + (canTiming[0].propSeg + canTiming[0].phaseSeg1)) / (1 + (canTiming[0].propSeg + canTiming[0].phaseSeg1) + (canTiming[0].phaseSeg2))
Sample-Point = (1 + (CAN_CTRL1.PROPSEG + CAN_CTRL1.PSEG1+2)) /
(1 +(CAN_CTRL1.PROPSEG+CAN_CTRL1.PSEG1+2) + (CAN_CTRL1.PSEG2+1))
清楚采样点计算公式后,实际使用 CAN Scope 的采样点计算功能测试真实采样点,需要注意,在上位机设置中需要将终端电阻选择为 60Ω ,且在采样点设置向导中需要勾选输入 TQS 并且设置 ABT TQS 总个数(这里与默认配置一致,即为 8),实际测试得出真实采样点精度偏差小于 0.5% :

由于例程中不能涵盖所有需要的采样点,这里推荐使用 ARM 波特率计算器来计算出实际需要填写的各个寄存器参数,按照下图中的配置可以得出实测的采样点为 83%,落在设置的要求范围内:


计算器中的参数与程序中位时序配置的对应关系如下:
TSEG1 +1 = phaseSeg1 + propSeg
TSEG2 + 1 = phaseSeg2
系统时钟 9M = PE 时钟 / preDivider
2.3 其它测试
报文 DLC 测试实际上可以通过具有数据解析功能的 CAN 工具来进行测试,主要评估 ECU 发送和接收报文的正确性。关于负载监测和压力测试,可以评估 ECU 处理紧急包的能力,理论上 CAN 是可以满负载运行工作的,这也是 CAN 的优势所在,但一般情况下会需要保证运行在 30% 以下的负载率。
小编未在新能源汽车领域深耕技术,所以以上内容旨在为抛砖引玉,如果需要了解更详细、更专业的学习,大家可以参阅 ZLG 致远电子整理的多年来的宝贵经验,链接如下:
https://www.zlg.cn/can/article/jszt.html
在下一章节中,我们将和大家一起学习测试 FlexCAN 应用层一致性。
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。














独立看门狗(IWDG)的设计初衷是为了检测和解决由软件错误所引起的故障,与窗口看门狗的主要区别在于独立看门狗可以作为一个处于主程序之外,由内部低速时钟(LSI)驱动,能够完全独立工作的模块,当主时钟发生故障或芯片处在低功耗模式的时候,独立看门狗依旧可以继续工作。
它的原理可以简述为:当独立看门狗计数器不断递减达到给定数值时,将产生一个系统复位信号使系统复位或产生中断信号。
MM32F0140的独立看门狗有一个特色功能,用户可以通过配置选择IWDG产生复位还是产生中断功能,比如在stop模式下,用户可以选择中断方式唤醒从而不用复位MCU,SRAM数据不用因为看门狗唤醒而被清除。
1、产生复位或者中断
MM32F014x的独立看门狗内部是自由运行的12位递减计数器,当设置IWDG复位方式时,当计数达到0x0000时,会产生一个系统复位;当设置IWDG中断方式时,当设置看门狗中断生成值IGEN,每当计数器值递减等于该值时,会产生一个中断信号。
2、计数器时钟
IWDG是由低速时钟源(LSI)驱动,经过IWDG_PR预分频器分频得到,预分频因子可以被设置为4,8,16,32,64,128,256,在开启IWDG前需要先开启LSI,如图1所示。

3、重装载寄存器
每次执行喂狗操作,就会将重装载寄存器(IWDG_RLR)的值重新加载到计数器中,从而避免产生复位或者中断信号,该操作通常叫做喂狗操作。复位时重装载寄存器(IWDG_RLR)的值为0xFFF,如图2。

4、看门狗超时时间
IWDG的超时周期可以通过重装载寄存器(IWDG_RLR)的值和预分频寄存器(IWDG_PR)计算得到,公式如下:
Tout(ms)=((4×2^PR)×RLR)/40
当IWDG_RLR寄存器为最大值时,可以获得最长的超时时间,参考时间如表1:

5、寄存器保护
独立看门狗中的IWDG_PR,IWDG_RLR,IWDG_IGEN寄存器具有访问保护功能,只能在向键值寄存器(IWDG_KR)写入0x5555,才能修改以上被保护的寄存器的值。向键值寄存器写入其他值或者重载操作时,寄存器依旧出在保护状态。
6、看门狗中断
当开启独立看门狗后,计数器开始从其复位值0xFFF开始递减,当IWDG_CR控制寄存器中的IRQ_SEL位置1时,计数器递减到IWDG_IGEN设定的值后会产生一个中断。独立看门狗中断被连接到EXTI24上,所以看门狗中断可以使MCU从低功耗模式下唤醒,结合IWDG_IGEN寄存器的设定,可以模拟低功耗定时器来使用。
7、部分库函数参考
PVU_CheckStatus();
IWDG_WriteAccessCmd(0x5555);
IWDG_SetPrescaler(IWDG_Prescaler);
修改预分频寄存器(IWDG_PR),修改前需要先向键值寄存器(IWDG_KR)写入0x5555。
RVU_CheckStatus();
IWDG_WriteAccessCmd(0x5555);
IWDG_SetReload(Reload & 0xfff);
修改重装载寄存器(IWDG_RLR),修改前需要先向键值寄存器(IWDG_KR)写入0x5555。
IVU_CheckStatus();
IWDG_WriteAccessCmd(0x5555);
IWDG_SetIGen(0x7ff);
修改中断生成寄存器(IWDG_IGEN),修改前需要先向键值寄存器(IWDG_KR)写入0x5555。
IWDG_EnableIT();
开启看门狗中断,如果需要看门狗复位方式需要设置:IWDG_Reset();
IWDG_ReloadCounter();
IWDG_Enable();
重载计数器、开启IWDG计数器。
IWDG_ClearITPendingBit();
清除看门狗中断标志位。
8、程序配置
8.1 开启独立看门狗
开启看门狗前需要先打开LSI,配置预分频寄存器,配置重装载寄存器,然后开启IWDG计数器,以下示例代码对IWDG进行初始化,配置预分频因子为16,重装载寄存器从最大值(0xFFF)开始计数,最大看门狗超时时间大概为1.6秒,代码如下:
void IWDG_Init(void) { //开启低速时钟,等待时钟稳定 RCC_LSICmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); //设置预分频寄存器 PVU_CheckStatus(); IWDG_WriteAccessCmd(0x5555); IWDG_SetPrescaler(0x02); //选择对LSI进行16分频 //设置重装载寄存器 RVU_CheckStatus(); IWDG_WriteAccessCmd(0x5555); IWDG_SetReload(0xfff); //重装载寄存器设置为0xFFF //将重装载寄存器的值加载到计数器,并开启计数器 IWDG_ReloadCounter(); IWDG_Enable(); }
8.2 重装载计数器(喂狗)
在任何时候向IWDG_KR寄存器写入0xAAAA,就会将重装载寄存器(IWDG_RLR)中的值加载到计数器中,避免产生复位或者中断,可以使用如下库函数:
IWDG_ReloadCounter();
或者直接操作寄存器,但要特别注意,在喂狗后最多需要5个LSI的振荡周期。
IWDG->KR = 0xAAAA;
8.3 开启看门狗中断
如需要开启看门狗中断,在配置IWDG时需要配置IWDG_CR中的IRQ_SEL和IWDG_IGEN寄存器,在开启看门狗之前加入如下代码:
1)配置中断生成寄存器(IWDG_IGEN),并开启看门狗中断
IVU_CheckStatus(); IWDG_WriteAccessCmd(0x5555); IWDG_SetIGen(0x7FF); //将IWDG_IGEN配置为0x7FF,当计数器到该值时会产生中断 IWDG_EnableIT();
2)使能NVIC和外部中断源
{ EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR, ENABLE); // Enable the IWDG Interrupt NVIC_InitStruct.NVIC_IRQChannel = WWDG_IWDG_IRQn; NVIC_InitStruct.NVIC_IRQChannelPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); RCC_APB2PeriphClockCmd(RCC_APB2RSTR_SYSCFG, ENABLE); EXTI_StructInit(&EXTI_InitStruct); EXTI_ClearITPendingBit(EXTI_Line24); // IWDG map to EXTI_Line24 EXTI_InitStruct.EXTI_Line = EXTI_Line24; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); }
3)编写看门狗中断服务函数,由于和窗口看门狗共用一个中断源,所以库中函数名和窗口看门狗一致。
void WWDG_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line24) != RESET) { EXTI_ClearITPendingBit(EXTI_Line24); IWDG_ClearIT(); IWDG_ReloadCounter(); //可以在中断中喂狗或者置标志位 } }
9、功能验证
在测试验证程序中在看门狗中断服务函数添加printf("IWDG IRQ Mode\r\n");下载程序可以看到MCU上电完成后会一直循环打印“IWDG IRQ Mode”。

针对需要短时间低功耗STOP模式唤醒的应用场景,可以使用该方式唤醒,同时针对LSI精度不高的问题,可以通过HSI对LSI进行校准方式,从而获取高精度的LSI时钟源。
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。




1、ADC简介
ADC全称为analog to digital converter,是12位的逐次逼近型(SAR)模拟数字转换器,能够将模拟信号转换为数字信号。
MM32F0140的ADC拥有高达1MSPS转换速率,支持最大输入时钟为15MHz,ADC1多达14路外部输入通道和2路内部通道,ADC2、ADC3多达16路外部输入通道。
ADC功能框图如图1所示,数据通过模拟输入信号(AIN)进行输入,将数据传输到12位的逐次逼近型模拟数字转换器,转换后将结果按指定方向对齐并存储到对应的通道数据寄存器中。

2、ADC功能
ADC进行数据转换需要配置ADC的分辨率、采样时间、时钟分频系数及工作模式,可选择通道0 ~ 通道15转换数据,存储转换后的数据需提前配置数据对齐方向。
分辨率
ADC转换数据分辨率决定了ADC的转换精度,设置的有效位数越多,量化单位越小,对输入信号的分辨能力就越高。可通过操作配置寄存器(ADC_ADCCFG)的RSLTCTL[2:0]位控制ADC转换数据分辨率,能够配置8/9/10/11/12位有效。
通道采样时间
ADC转换采样时间可通过采样配置寄存器(ADC_SMPRx)中的SAMPCTLx位配置每个通道的采样时间,通道0到通道7的采样时间由ADC_SMPR1控制,通道8到通道15的采样时间由ADC_SMPR2控制,可选择的周期如图2所示。

时钟分频系数
ADC时钟的分频系数可通过操作配置寄存器(ADC_ADCFG)的ADCPRE位来选择,令ADC外设时钟的(ADCPRE+2)分频作为ADC时钟。
数据对齐
通过配置控制寄存器(ADCR)中的ALIGN位,可选择转换后数据储存为左对齐或右对齐,如图3所示,当采用右对齐,A/D转换结果的最低位与数据寄存器中DATA位的最低位对齐;当采用左对齐,A/D转换结果的最高位与数据寄存器中DATA位的最高位对齐。

通道选择
ADC的每个外部输入通道都具有独立的使能位,可通过设置通道选择寄存器(ADC_ADCHS)的CHENx位使能对应的模拟输入通道。
普通工作模式
● 单次转换模式
单周期扫描模式下,可通过配置控制寄存器(ADC_ADCR)的SCANDIR位选择扫描通道方向,运行时将按使能的通道顺序进行一次A/D转换;软件/外部触发置位ADST位,开始A/D转换,外部触发可软件配置触发延时,方向设置默认从最小序号通道到最大序号通道的A/D转换,也可按照程序设置,从最大序号通道到最小序号通道的A/D转换。A/D转换完成后,转换数值将有序装载到对应通道的数据寄存器中。当最后一个A/D通道采样结束后,ADST位硬件清零,进入空闲状态。
● 连续扫描模式
连续扫描模式下,A/D转换连续执行单周期扫描模式直到软件停止A/D转换。若想修改转换通道,不必停止转换,可配置通道选择寄存器(ADC_ADCHS)的CHENx位选择需要使能的新通道,在下一个扫描周期开始将进行新通道转换。
任意通道工作模式
● 单次转换模式
A/D转换在指定通道完成一次转换,然后进入空闲模式。
● 连续扫描模式
在连续扫描模式下,A/D转换连续执行单周期扫描模式直到软件停止A/D转换。通过软件/外部触发置位ADC_ADCR寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY_SEL0到CHANY_SELx,软件设置寄存器ADC_ANY_CFG、ADC_CHANY0、ADC_CHANY1,将需要转换的通道、数量设置好,然后置位CHANY_MDEN。
若在A/D转换过程中,软件更新ADC_ANY_CFG,ADC_CHANY0,ADC_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,即下一个扫描周期开始新的通道转换。
每路A/D转换完成时,A/D转换的数据值将有序装载到相应通道的数据寄存器中。只要ADST位保持为1,就持续进行A/D转换。当ADST位被清0,当前A/D转换完成后停止,A/D转换器进入空闲状态。
3、实验
本实验演示ADC串口接收中断服务的使用,ADC通道1对应引脚连接VCC,按下任意按键,A/D转换开始,串口打印转换值。初始化ADC,配置ADC精度为12位有效,ADC时钟分频为16分频,使用单次转换模式,转换后数据右对齐;使用模拟输入通道1,设置扫描通道的顺序为从低到高的顺序扫描,配置采样时间为240.5周期,使能A/D非注入通道组转换中断,在中断处理中获取转换后的数值并清除中断标志。
启用ADC外设时钟 enable_clock()
实验使用ADC1,使用的ADC通道1对应引脚为PA1,串口打印输出结果,串口使用引脚属于GPIOA组,因此需要启用ADC1、UART1及GPIOA的外设时钟。
void enable_clock() { /* Enable ADC1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_ADC1; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable GPIOB clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOB; /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; }
配置引脚 pin_init()
ADC1使用通道1,对应引脚为PA1,设置为模拟输入;由于实验现象通过串口显示,故配置UART的TX(PA9)与RX(PA10)引脚。
void pin_init() { /* PA1 - ADC1 channel 1. */ GPIOA->CRL &= ~GPIO_CRL_MODE1_MASK; GPIOA->CRL &= ~GPIO_CRL_CNF1_MASK; GPIOA->CRL |= GPIO_CRL_MODE1(GPIO_Speed_50MHz); GPIOA->CRL |= GPIO_CRL_CNF1(GPIO_PinMode_In_Analog); /* PA9 - UART_TX. */ GPIOA->CRH &= ~GPIO_CRH_MODE9_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF9_MASK; GPIOA->CRH |= GPIO_CRH_MODE9(GPIO_Speed_50MHz); GPIOA->CRH |= GPIO_CRH_CNF9(GPIO_PinMode_AF_PushPull); GPIOA->AFRH &= ~GPIO_AFRH_AFRY_MASK GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* PA10 - UART_RX. */ GPIOA->CRH &= ~GPIO_CRH_MODE10_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF10_MASK; GPIOA->CRH |= GPIO_CRH_CNF10(GPIO_PinMode_In_Floating); GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); }
UART初始化 uart_init()
初始化UART,配置时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR &= ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR &= ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
ADC初始化 adc_init()
ADC初始化操作配置寄存器(ADC_ADCFG)的RSLTCTL位,配置ADC精度为12位有效,配置ADCPRE位为16分频;操作控制寄存器(ADC_ADCR)的ADMD位,选择转换模式为单次转换,配置ALIGN位为数据右对齐;设置ADC_ADCFG寄存器的ADEN位为1,使能A/D转换;将任意通道控制寄存器(ADC_ANY_CR)的CHANY_MDEN位清零,禁止任意通道配置模式;操作通道选择寄存器(ADC_ADCHS)的CHEN1位置1,使能模拟输入通道1;配置ADC_ADCR寄存器的SCANDIR位,ADC通道选择寄存器按从低到高的顺序扫描,配置ADIE位为1,使能A/D非注入通道组转换中断。
void adc_init() { /* Setup the converter. */ uint32_t cfg; cfg = ADC1->ADCFG & ~( ADC_ADCFG_ADCPREH_MASK | ADC_ADCFG_ADCPREL_MASK | ADC_ADCFG_RSLTCTL_MASK | ADC_ADCR_ALIGN_MASK ) ; /* Prescaler & Resolution. */ cfg |= ADC_ADCFG_ADCPREL(ADC_ClockDiv_16) /* ADC_ClockDiv_16 = 14u. */ | ADC_ADCFG_ADCPREH((ADC_ClockDiv_16)>>1) | ADC_ADCFG_RSLTCTL(ADC_Resolution_12b) ; ADC1->ADCFG = cfg; /* ADC conversion mode and conversion data result align. */ ADC1->ADCR = (ADC1->ADCR & ~( ADC_ADCR_ADMD_MASK | ADC_ADCR_ALIGN_MASK) ) | ADC_ADCR_ADMD(ADC_ConvMode_SingleSlot) | ADC_ADCR_ALIGN(ADC_Align_Right) ; ADC1->ADCFG |= ADC_ADCFG_ADEN_MASK; /* Enable ADC1. */ /* Setup one regular channel. */ ADC1->ANYCR &= ~ADC_ANYCR_CHANYMDEN_MASK; /* Enable regular channels. */ ADC1->ADCHS = 1u << ADC_CHN_NUM; /* ADC_CHN_NUM = 1u. */ ADC1->ADCR = (ADC1->ADCR & ~ADC_ADCR_SCANDIR_MASK) | ADC_ADCR_SCANDIR(ADC_RegSeqDirection_LowFirst) ; /* Set channel sample time. */ ADC1->SMPR1 = (ADC1->SMPR1 & ~(0xF << 4u)) | (ADC_SampleTime_Alt7 << 4u ); /* Period is 240.5. */ /* Enable ADC interrupt. */ ADC1->ADCR |= ADC_ADCR_ADIE_MASK; NVIC_EnableIRQ(ADC_COMP_IRQn); }
启动A/D转换 adc_doswtrigger()
本实验设置按下任意按键后,软件触发A/D转换开始,配置控制寄存器(ADC_ADCR)的ADST位为1。
void adc_doswtrigger() { ADC1->ADCR |= ADC_ADCR_ADST_MASK; }
编写中断服务程序 ADC_COMP_IRQHandler()
中断服务程序中,读状态寄存器(ADC_ADSTA)获取当前ADC传输状态,当ADIF位置1,即A/D转换完成,将adc传输完成标志adc_conv_done设置为true,读数据寄存器(ADC_ADDATA)的DATA位获取转换结果,放入conv_val变量中,向ADIF位写1,清零标志。
void ADC_COMP_IRQHandler() { if ( 0u != (ADC_ADSTA_EOSIF_MASK & ADC1->ADSTA) ) /* ADC convert complete. */ { adc_conv_done = true; conv_val = (ADC1->ADDATA & ADC_ADDATA_DATA_MASK ) >> ADC_ADDATA_DATA_SHIFT; } ADC1->ADSTA |= ADC_ADSTA_EOSIF_MASK; /* Clear flag. */ }
main()函数
main()函数结合上述操作,ADC通道1对应引脚连接VCC,运行程序,串口打印"adc_interrupt example.",初始化ADC,串口打印"press any key to start the conversion.",按下任意按键,A/D转换开始,ADC一个通道转换完成后,进入ADC中断服务函数,获取转换数值,串口打印转换值,实验结果如图4所示。
int main() { enable_clock(); pin_init(); uart_init(); printf("adc_interrupt example. \r\n"); adc_init(); printf("press any key to start the conversion.\r\n"); while (1) { getchar(); printf("adc conversion start...\r\n"); adc_doswtrigger(); adc_conv_done = false; while (!adc_conv_done) {} printf("value=%u\r\n", (unsigned)(conv_val & 0xFFF)); printf("adc interrupt done...\r\n"); } }

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。




1、WWDG简介
窗口看门狗(WWDG)是喂狗时间有上下限范围的看门狗,主要用于检测由外部干扰和不可预测的逻辑条件导致程序跑飞了所引起的软件问题,根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗或滞后刷新看门狗,可以用于检测出程序是否按照正常的路径运行或者是非正常的跳过某些程序段的情况。
复位
当WWDG计数器的值大于设定的上限窗口值时喂狗,将产生复位;当计数器的值从下限固定值0x40变成0x3F时,低于窗口看门狗的下限,也会产生复位。
计数器时钟
WWDG计数器时钟由CK计时器时钟经过预分频器分频得到,预分频器的时基由配置寄存器(WWDG_CFGR)的WDGTB位配置,如图1所示,CK计时器时钟=PCLK1/4096,可将CK计时器时钟进行2分频/4分频/8分频/不分频。

计数器
WWDG的计数器是一个递减计数器,每4096x2WDGTB个PCLK1周期减1,WDGTB的值如图1所示。控制寄存器(WWDG_CR)的T位用于存储看门狗的7位计数器值,如图2所示,计数器所有位置1时为最大值0x7F,其下限固定值为0x40,当计数器值从0x40下降到0x3F时,产生看门狗复位。WWDG的喂狗操作必须在计数器值小于窗口寄存器值时操作,只有该情况下喂狗不会产生复位。

窗口值
如图3所示,WWDG的上限窗口值由配置寄存器(WWDG_CFGR)的W位控制,为保证窗口的价值,上限窗口值必须大于0x40且小于等于0x7F。

提前唤醒中断
利用提前唤醒中断(EWI) 可以避免复位并且重载计数器。操作配置寄存器(WWDG_CFGR)中的EWI位开启中断,当计数器递减到0x40时,产生提前唤醒中断,可以在中断处理函数中向控制寄存器(WWDG_CR)重载计数器的值来达到喂狗的目的,从而防止复位。
2、实验
本实验配置WWDG计数器时钟的预分频系数为8分频,配置重载计数器窗口的上限值为0x7F,时钟初始化后,串口打印"wwdg_basic example.",初始化WWDG后使能提前唤醒中断(EWI),在中断处理函数中判断是否已经重装载计数器到指定次数,若未达到则重装载一次WWDG计数器,并记录重装载次数,达到指定次数则不再重装载计数器。
初始化WWDG后进入死循环,每经过一段延时,串口打印一个".",表示CPU仍在工作,直到WWDG复位,串口再次打印"wwdg_basic example."。
启用WWDG外设时钟 enable_clock()
实验使用WWDG,串口打印输出结果,串口使用引脚属于GPIOA组,因此需要启用WWDG、UART1及GPIOA的外设时钟。
void enable_clock() { /* Enable WWDG clock. */ RCC->APB1ENR |= RCC_APB1_PERIPH_WWDG; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; }
配置引脚 pin_init()
由于实验现象通过串口显示,故配置UART的TX(PA9)与RX(PA10)引脚。
void pin_init() { /* PA9 - UART_TX. */ GPIOA->CRH &= ~GPIO_CRH_MODE9_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF9_MASK; GPIOA->CRH |= GPIO_CRH_MODE9(GPIO_Speed_50MHz); GPIOA->CRH |= GPIO_CRH_CNF9(GPIO_PinMode_AF_PushPull); GPIOA->AFRH &= ~GPIO_AFRH_AFR_MASK; GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* PA10 - UART_RX. */ GPIOA->CRH &= ~GPIO_CRH_MODE10_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF10_MASK; GPIOA->CRH |= GPIO_CRH_CNF10(GPIO_PinMode_In_Floating); GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); }
UART初始化 uart_init()
初始化UART,配置时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR &= ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR &= ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
WWDG初始化 wwdg_init()
操作配置寄存器(WWDG_CFGR)的WDGPB位配置预分频系数为8分频,W位配置重载计数器窗口的上限值为0x7F,EWI位置1使能提前唤醒中断,操作控制寄存器(WWDG_CR)的WDGA位置1,启动看门狗。
void wwdg_init() { WWDG->CFGR = WWDG_CFGR_WDGTB(WWDG_Prescaler_8) | WWDG_CFGR_W(WWDG_UPPER_LIMIT); /* WWDG_UPPER_LIMIT = 0x7F, WWDG_Prescaler_8 = 3u. */ NVIC_EnableIRQ(WWDG_IWDG_IRQn); WWDG->CFGR |= WWDG_CFGR_EWI_MASK; WWDG->CR |= WWDG_CR_WDGA_MASK; }
中断服务程序 WWDG_IWDG_IRQHandler()
中断服务程序中判断是否已经重装载计数器到指定次数,若未达到则重装载一次WWDG计数器,并记录重装载次数,若达到则不再重装载计数器。向状态寄存器(WWDG_SR)的EWIF位写0,清除提前唤醒中断标志。
void WWDG_IWDG_IRQHandler(void) { uint32_t status = WWDG->SR; if ( 0 != (WWDG_SR_EWIF_MASK & status) ) { WWDG->SR &= ~WWDG_SR_EWIF_MASK;; } /* Stop reload WWDG counter aftert reload WWDG counter BOARD_WWDG_RELOAD_TIMES times. */ if (100u > reload_times) /* uint32_t reload_times = 0u. */ { reload_times++; WWDG->CR = (WWDG->CR & ~WWDG_CR_T_MASK) | WWDG_CR_T(WWDG_RELOAD_VALUE); /* WWDG_RELOAD_VALUE = 0x7f. */ } }
延时 soft_delay()
本实验设置初始化后进入while循环,循环中隔一段延时进行一次串口打印,表示当前CPU仍在工作,故编写软件延时。
void soft_delay(uint32_t delay) { for(uint32_t i = 0u; i < delay; i++) { for(uint32_t j = 0u; j < 10000u; j++) { __NOP(); } } }
main()函数
main()函数结合上述操作,串口打印"wwdg_basic example.",初始化WWDG并使能提前唤醒中断后进入死循环,循环中每经过一段延时串口将打印一个 “.” ,表示CPU仍在工作,直到WWDG复位,实验现象如图4所示。
void main() { enable_clock(); pin_init(); uart_init(); printf("\r\nwwdg_basic example.\r\n"); wwdg_init(); while (1) { printf("."); soft_delay(100u); } }

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。




1、I2C简介
I2C总线是一个两线串行接口,包含串行数据线(SDA)与串行时钟线(SCL),能够在连接到总线的器件间传递信息,每一个连接总线的设备都有独立的地址,主机可以通过该地址选择连接总线的设备并与之通信。I2C通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号,从而进行数据传输,微控制器可通过I2C总线接口实现芯片间串行互联。
I2C的功能框图如图1所示,当I2C采用主模式进行数据传输时,主机先发送从机设备地址与读写位数据,在从机地址匹配时可进行数据传输;I2C采用从模式时,从设备等待接收由主机发送来的地址数据,地址匹配时可进行数据传输。

主机与从机
主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机,在主模式下只支持一个主机。
设备地址
在进行数据传输前需要发送从设备地址,只有当主机的发送设备地址与从机的设备地址匹配时才能进行数据传输。总线的每个器件都有一个唯一的地址识别,而且都可以作为一个发送或接收器。
I2C信号
● 起始信号
当总线处于空闲状态时,SCL和SDA同时被外部上拉电阻拉为高电平。当主机启动数据传输时,必须先产生一个起始信号。在SCL线是高电平时, SDA线从高电平向低电平切换表示起始信号,如图2所示。在起始信号产生后,总线处于忙碌状态,除当前数据传输的主从设备外,其他I2C器件无法访问总线。

● 停止信号
停止信号如图2所示,当主机结束传输时要发送停止信号,在SCL线为高电平、SDA线由低电平向高电平切换时,产生停止信号,从而释放总线,总线再次处于空闲状态。
● 应答信号
I2C具有完善的应答机制,当主机发送完成地址与读写位或者主机发送一个字节的数据到从机上时,从机会回应一个应答信号(ACK),即在第9个SCL时钟周期时拉低SDA,如图3所示。

● 非应答信号
如图3所示,当第9个SCL时钟周期时拉高SDA,表示为非应答信号,主机或从机将产生一个停止信号中止传输。
2、I2C配置
I2C的配置包括传输速率、从设备地址、数据传输方向及主从模式选择的配置,从而进行数据传输。
工作速率
I2C存在两种工作速率模式:标准模式(数据传输速率为0 ~ 100Kbps),快速模式(数据传输速率最大为400Kbps);可通过I2C控制寄存器(I2C_CR)的SPEED位进行控制,由于I2C从机速率跟随主机速率,因此只需在I2C为主模式下配置工作速率,如图5所示。

地址格式
I2C有两种地址格式:7位地址格式和10位地址格式。
● 7位地址
如图6所示,在起始条件(S)后发送的一个字节的前7位(A6 ~ A0)为从机地址,最低位(R/W)是数据方向位,当R/W位为0,表示主机写数据到从机,R/W位为1表示主机从从机读数据。

● 10位地址
在10位的地址格式中,发送2个字节来传输10位地址。发送的第一个字节的起始位后的5位(bit7:3)用于告示从机接下来是10位的传输,第一个字节的后两位(bit2:1)是从机地址的bit9:8,最低位(bit 0)是数据方向位(R/W)。传输的第二个字节为10位地址的低八位,如图7所示。

写操作
I2C进行写操作时,先通过I2C目标地址寄存器(I2C_TAR)配置从设备地址,再配置读写位,令控制数据命令寄存器(I2C_DR)的CMD位为0,所需传输数据放入I2C_DR寄存器的DR位,进行写操作。
读操作
I2C进行读操作时,先由主机写从设备地址及设备内寄存器地址,当从设备地址及设备内寄存器地址发送完成后,将控制数据命令寄存器(I2C_DR)的CMD位为1,进行读操作,读出设备内寄存器地址中的数值。

主模式
I2C主模式配置需在I2C未使能状态下进行,将I2C使能寄存器(I2C_IMR)的ENABLE位置0。通过配置控制寄存器(I2C_CR)的SPEED位选择速率模式,MASTER10位选择地址格式,DISSLAVE位为1使从模式禁止,MASTER位为1选择主模式。向目标地址寄存器(I2C_TAR)写入从设备地址,I2C_IMR寄存器的ENABLE位置1,使能I2C。
I2C 接口支持读写的动态切换,当发送数据时,写数据到I2C的RX/TX数据缓冲及命令寄存器(DR)的低字节中,配置CMD位为0产生写操作。接下来的读命令,需要确保CMD位为1。如果发送FIFO为空,I2C模块拉低SCL直到下个命令写入到发送FIFO中。
从模式
I2C做从机时,等待其他主机的访问,只有其他主机发送了I2C从机对应的设备地址,才能进行数据传输。配置I2C为从模式,将I2C使能寄存器(I2C_IMR)的ENABLE位置0,禁止I2C使能,将I2C自身从地址写入从机地址寄存器(I2C_SAR),配置控制寄存器(I2C_CR)的SLAVE10选择寻址格式,DISSLAVE位为0,选择从模式,MASTER位为0,禁止主模式;I2C_IMR寄存器的ENABLE位置1,使能I2C。
当其他主机发送地址与 SAR 寄存器中的从机地址匹配,I2C接口响应发送的地址,并识别传输方向;当I2C产生读请求,从机将数据发送至主机;当主机为写操作时,I2C从机接收到主机的数据并将数据存储在接收缓冲中,软件通过读DR寄存器中的bit7:0来获得接收到的数据。
3、实验
本实验使用轮询进行数据的写操作与读操作,初始化配置I2C为主模式,从设备地址为0x50,速率为100KHz,使用7位地址格式。I2C初始化后,按下一次任意按键,进行8字节数据的写操作,其中第一字节写入数据设置为设备内的寄存器地址,串口显示写入数据;写操作完成后,再次按下任意按键,进行7位数据的读操作,从设备的寄存器地址中读出相关数据。
启用I2C外设时钟 enable_clock()
实验使用I2C1,串口打印输出结果,串口使用引脚属于GPIOA组,I2C使用引脚属于GPIOB组,因此需要启用I2C1、UART1、GPIOA及GPIOB的外设时钟。
void enable_clock() { /* Enable I2C1 clock. */ RCC->APB1ENR |= RCC_APB1_PERIPH_I2C1; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable GPIOB clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOB; /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; }
配置引脚 pin_init()
配置I2C1引脚,I2C1_SCL为PB6引脚,I2C1_SDA为PB7引脚,复用通道为AF1;由于实验现象通过串口显示,故配置UART的TX(PA9)与RX(PA10)引脚。
void pin_init() { /* PB6 - I2C1_SCL. */ GPIOB->CRL &= ~GPIO_CRL_MODE6_MASK; GPIOB->CRL &= ~GPIO_CRL_CNF6_MASK; GPIOB->CRL |= GPIO_CRL_MODE6(GPIO_Speed_50MHz); GPIOB->CRL |= GPIO_CRL_CNF6(GPIO_PinMode_AF_OpenDrain); GPIOB->AFRL &= ~GPIO_AFRL_AFRY_MASK; GPIOB->AFRL |= (GPIO_AF_1 << GPIO_CRL_MODE6_SHIFT); /* PB7 - I2C1_SDA. */ GPIOB->CRL &= ~GPIO_CRL_MODE7_MASK; GPIOB->CRL &= ~GPIO_CRL_CNF7_MASK; GPIOB->CRL |= GPIO_CRL_MODE7(GPIO_Speed_50MHz); GPIOB->CRL |= GPIO_CRL_CNF7(GPIO_PinMode_AF_OpenDrain); GPIOB->AFRL |= (GPIO_AF_1 << GPIO_CRL_MODE7_SHIFT); /* PA9 - UART_TX. */ GPIOA->CRH &= ~GPIO_CRH_MODE9_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF9_MASK; GPIOA->CRH |= GPIO_CRH_MODE9(GPIO_Speed_50MHz); GPIOA->CRH |= GPIO_CRH_CNF9(GPIO_PinMode_AF_PushPull); GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* PA10 - UART_RX. */ GPIOA->CRH &= ~GPIO_CRH_MODE10_MASK; GPIOA->CRH &= ~GPIO_CRH_CNF10_MASK; GPIOA->CRH |= GPIO_CRH_CNF10(GPIO_PinMode_In_Floating); GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); }
UART初始化 uart_init()
初始化UART,配置时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR &= ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR &= ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
I2C初始化 i2c_init()
I2C通过操作配置寄存器(I2C_CR)实现初始化,配置I2C为主模式、速率为100KHz、7位地址格式、时钟频率为60000000u,设置从设备地址为0x50,使能I2C。
void i2c_init() { I2C1->ENR &= ~I2C_ENR_ENABLE_MASK; uint32_t tmp = BOARD_I2C_FREQ / I2C_BaudRate_100K; /* BOARD_I2C_FREQ = 60000000u, I2C_BaudRate_100K = 100000u. */ I2C1->SSHR = tmp / 2u - 12u; /* Configure high level count in normal speed. */ I2C1->SSLR = tmp / 2u - 1u; /* Configure low level count in normal speed. */ I2C1->CR = I2C_CR_SPEED(1u); I2C1->CR &= ~I2C_CR_MASTER10_MASK; /* Address format. */ I2C1->CR |= I2C_CR_RESTART_MASK /* Generate restart signal. */ | I2C_CR_DISSLAVE_MASK /* Disable slave module. */ | I2C_CR_REPEN_MASK /* Enable sending restart condition. */ | I2C_CR_EMPINT_MASK /* Control tx_empty interrupt generation. */ | I2C_CR_MASTER_MASK; /* Enable master module. */ I2C1->RXTLR = 0u; /* Configure the sending receive value. */ I2C1->TXTLR = 0u; /* Configure the sending threshold value. */ /* Setup target address. */ I2C1->TAR = I2C_TAR_ADDR(APP_I2C_TARGET_ADDR); /* APP_I2C_TARGET_ADDR = 0x50. */ I2C1->ENR |= I2C_ENR_ENABLE_MASK; /* Enable I2C. */ }
I2C写操作 i2c_write()
设置I2C发送数组i2c_tx_buf及发送数据长度i2c_tx_len,将数组i2c_tx_buf的第一个数值设置为寄存器地址,发送8字节数据。数据发送后读取状态寄存器(I2C_SR),等待发送缓冲空,再进行下一次发送;数据传输完成,操作使能寄存器的ABORT位为1,使I2C停止数据传输;读清除TX_ABRT中断寄存器(I2C_TXABRT),将TX FIFO从刷新/复位状态中释放。
void i2c_write() { I2C1->DR = I2C_DR_DAT(i2c_tx_buf[0]); while (0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) ) { } for (uint32_t i = 1u; i < i2c_tx_len; i++) { I2C1->DR = I2C_DR_DAT(i2c_tx_buf[i]); while ( 0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) ) /* Wait to tx fifo empty. */ { } } I2C1->ENR |= I2C_ENR_ABORT_MASK; /* Prepare for the stop. */ I2C1->TXABRT; /* Read register to release tx fifo. */ while (I2C1->SR & I2C_STATUS_ACTIVE) /* Wait to I2C not active, which means stop is taking effect. */ { } }
I2C读操作 i2c_read()
设置I2C接收数组i2c_rx_buf及接收长度i2c_rx_len,在进行数据读取前,需要发送从设备地址及寄存器地址,本实验设置发送数组的第一个数据为寄存器地址,发送完成后再将I2C_DR寄存器的读写位(CMD)置1,读取I2C_DR寄存器进行读操作。读操作结束后,操作使能寄存器的ABORT位为1,使I2C停止数据传输。
void i2c_read() { I2C1->DR = I2C_DR_DAT(i2c_tx_buf[0]); while (0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) ) { } /* Read data from target device. */ for (uint32_t i = 0u; i < i2c_rx_len; i++) { I2C1->DR = I2C_DR_CMD_MASK; /* Swich read-write bit to read, prepare to get data. */ while ( 0u == (I2C1->SR & I2C_STATUS_RX_NOTEMPTY) ) /* Wait to rx fifo not empty. */ { } i2c_rx_buf[i] = (uint8_t)I2C1->DR; } I2C1->ENR |= I2C_ENR_ABORT_MASK; /* Prepare for the stop. */ I2C1->TXABRT; /* Read register to release tx fifo. */ while (I2C1->SR & I2C_STATUS_ACTIVE) /* Wait to I2C not active, which means stop is taking effect. */ { } }
main()函数
main()函数结合上述操作,串口打印"i2c_master_basic example",初始化I2C后,将发送数组设置为0到7,按下一次任意按键,进行数据发送,串口显示发送数据;再次按下任意按键,进行数据读取,从寄存器地址中读出数值,设置寄存器地址设置为发送数据的首个数据,串口显示接收数据,实验结果如图9所示。
int main() { enable_clock(); pin_init(); uart_init(); printf("\r\ni2c_master_basic example\r\n"); i2c_init(); for (uint32_t i = 0u; i < i2c_tx_len; i++) { i2c_tx_buf[i] = i; } while (1) { printf("press any key to write i2c-eeprom.\r\n"); getchar(); i2c_write(); printf("write data: "); for (uint32_t i = 0u; i < i2c_tx_len; i++) { printf("0x%02X, ", i2c_tx_buf[i]); } printf("\r\n"); printf("press any key to read i2c-eeprom.\r\n"); getchar(); i2c_read(); printf("read data from device register address: 0x%02X\r\n", i2c_tx_buf[0u]); printf("read data: "); for (uint32_t i = 0u; i < i2c_rx_len; i++) { printf("0x%02X, ", i2c_rx_buf[i]); } printf("\r\n"); }

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。









1、FlexCAN 简介
FlexCAN 控制器局域网模块是符合 ISO 11898-1 标准和 CAN 2.0B 规范的通信控制器,支持 CAN 总线协议。FlexCAN 模块框图如 图1 所示。FlexCAN 模块包括 CAN 收发器(CAN Transceiver)、协议引擎(Protocol Engine)、控制接口(Controller Host Interface)、总线接口单元(Bus Interface Unit),以及多个支持独立收发功能的信息缓冲区(Message Buffer)。其中 CAN 收发器负责将收发引脚的 TTL 信号转换为 CAN 总线的电平信号,协议引擎负责处理对信息缓冲区的读写请求,控制接口负责传输帧的发送仲裁以及接收匹配,总线接口单元处理 FlexCAN 和内部总线的交互,信息缓冲区用于存储 FlexCAN 的传输帧。

MM32F0140 的 FlexCAN 模块支持标准帧和扩展帧两种帧格式,支持数据帧和远程请求帧两种帧类型,其中数据帧的最大有效数据长度可达8比特。FlexCAN 模块支持最高可达 1Mps 的可编程比特率,支持对前 16 个信息缓冲区的中断,支持局部和全局的接收帧过滤机制,支持可选择的 FIFO 接收功能。
FlexCAN 模块支持三种工作模式:回环模式、只听模式和正常工作模式。因为在回环模式下,FlexCAN 的传输帧并不会通过其收发引脚发送到 CAN 总线,所以通常用于测试单块芯片的 FlexCAN 模块是否工作正常。只听模式下,FlexCAN 模块将只接收总线上的帧,而不能发送帧,并且也无法发送接收应答。正常工作模式下,FlexCAN 模块既可发送帧,也可接收帧。
2、FlexCAN 帧格式
FlexCAN 帧传输过程

图2 是 CAN 帧的发送流程,首先是帧起始位 SOF,SOF 值规定为 1,标识此帧的开始。在 CAN 总线协议中约定,值 0 为显性位,值 1 为隐性位。FlexCAN 采取多主机、基于优先级的总线传输方式,总线帧的收发顺序在 Arbitration field 仲裁段确定。仲裁成功的节点会继续发送帧,仲裁失败的节点会转为接收状态。如果不设置使用帧的优先级进行仲裁,则使用帧的 ID 号进行仲裁比较,越小优先级越高。结合 图2 CAN 帧发送流程 和 图3 CAN 标准数据帧格式可知,Arbitration field 仲裁段存放 CAN 报文的 ID 号以及 RTR 标识,RTR 用于表示帧类型,在下文讲述 FlexCAN 使用信息缓冲区发送报文时,会进一步解释其含义。Control filed 控制段在 MM32F0140 的 FlexCAN 中,主要涉及两个部分,IDE 字段和 DLE 字段。IDE 字段用于标识帧格式,为数据帧还是远程帧。DLC 字段用于标识此帧的有效字节数,DLC 字段值将影响后续的 Data field 字段长度。FDF 字段表示此帧是否为 CAN FD 帧,在MM32F0140 的 FlexCAN 模块中暂时未使用到。Data field 有效传输数据负载段,如果此帧为远程帧,将不携带任何数据,所以 DLC 字段对应为 0;如果此帧为数据帧,此段长度由 DLC 字段控制。CRC field 循环冗余码段,用于在发送和接收流程中检查此帧是否出现比特错误。CRC 后,为 ACK field 检测段,发送方会发送隐性位 1,然后在此段期间回读总线上信号。如果读到为显性位 0,则说明有其他 CAN 接收器接收到此帧,发送方发送成功。最后是 EOF 结束位,标识发送结束。

以下是 MM32F0140 FlexCAN 在上述 CAN 发送流程要求下,使用信息缓冲区寄存器进行 CAN 帧的收发的详细操作流程。信息缓冲区寄存器的结构如图 4 所示。

FlexCAN 发送帧
FlexCAN 发送帧时,首先将帧相关信息写入预计使用的信息缓冲区寄存器中。如果此帧的 ID 号可用 11 个比特位进行表示,则将此帧的 ID 号填入 ID 段,并将 IDE 位置 0,标识为标准帧。如果此帧的 ID 号超过 11 位,则将剩余低位部分填入 ID(Extended) 段,并将 IDE 位置 1,标识为扩展帧。FlexCAN 的帧 ID 长度不能超过 29 位。
然后根据此帧的帧类型,如果是携带数据的数据帧,则将 SRR 替代远程请求位和 RTR 远程请求位都设置为 0,并将对应需要发送的数据写入 DataByte 区,将数据长度写入 DLC 字段;如果是不携带数据的远程请求帧,则将上述的 SRR 位和 RTR 位都设置为 1。
如果 MCR[LPRIOEN] 位被设置为 1,则在发送前还需要将此帧的优先级写入 PRIO 位,PRIO 位值越小,优先级越高。
当上述各字段都填充完毕后,将 CODE 值 12 填入 CODE 区,此 CODE 值表示将要发送此帧,发送完毕后 CODE 值变为 8,表示发送完毕。回读 TIME STAMP 段,可以得到发送成功时的时间戳。
FlexCAN 接收帧
FlexCAN 接收帧前,需要设置此接收信息缓冲区的 ID 号、帧格式 IDE 和帧类型 SRR 和 RTR,参考上述发送过程的设置。然后将 CODE 区值设置为值 4,表示等待接收。如果需要帧过滤,还需要设置局部帧过滤器 RXIMR 和全局帧过滤器 RXMGMASK、RX14MASK和RX15MASK。当 CAN 总线上有其他 FlexCAN 模块发送的帧时,信息缓冲区会将总线上的帧 ID 与自己的 ID 段以及掩码值进行匹配,接收帧,此时 TIME STAMP 段的值为接收时的时间戳。
在传输中会常使用到的 CODE 值如下表 1 所示。
表格 1 信息缓冲区 CODE 值含义

FlexCAN 功能测试流程
首先使用 FlexCAN 的回环模式测试开发板上的 FlexCAN 是否工作正常。回环测试成功以后可以进行 FlexCAN 正常工作模式的测试。如果使用的是两块开发板或使用 CAN 分析仪进行测试,需要注意总线两端必须连接 120Ω 终端电阻。在配置 FlexCAN 过程中,要保证收发方的波特率都配置成相同值。测试 FlexCAN 正常工作模式下收发的实验过程中,要确保至少有一个接收节点是正常工作模式,而非只听模式。
3、FlexCAN 配置
配置时钟
首先需要使能 FlexCAN 时钟,根据所使用的外设对 RCC 的 RCC_APB1ENR 寄存器进行赋值,将对应外设位置 1 即可使能时钟,详细外设如图 5 所示。

使能并初始化模块
使能 FlexCAN 模块,通过配置 FLEXCAN_MCR[MDIS] 为 0,可以使能 FlexCAN 模块。使能后,FlexCAN 模块会自动进入冻结模式。在冻结模式下,设置 FLEXCAN_MCR_SOFTRST 为 1,软件重置 FlexCAN 模块寄存器。然后将所有的信息缓冲区的独立掩码寄存器和全局掩码寄存器的掩码值都设置为 1,表示接收信息缓冲区将只接收 ID 号和缓冲区提前配置的 ID 号完全相同的报文,同时将每个信息缓冲区寄存器也全部清 0。
配置工作模式
如果 FlexCAN 需要进入正常工作模式,仅需在使能 FlexCAN、初始化信息缓冲区后退出冻结模式即可进入,即将 FLEXCAN_MCR[FRZ] 设置为 0。因为退出冻结模式需要一定时间,通过轮询 FLEXCAN_MCR[FRZACK] 是否等于 1 判断是否退出成功。如果 FlexCAN 需要配置为只读模式,则需要将 FLEXCAN_CTRL1[LOM] 置为 1;如果 FlexCAN 需要配置为回环模式,则需要将 FLEXCAN_CTRL1[LPB] 设置为 1。值得注意的是,当配置为回环模式时,FlexCAN 的 FLEXCAN_MCR[SRXDIS] 不能被设置为 1,否则 FlexCAN 将无法收到回环报文。
配置波特率
首先通过设置 FLEXCAN_CTRL1[CLKSRC] 指定 FlexCAN 协议引擎的时钟源,设置为 0 时使用振荡器时钟,设置为 1 时使用外设时钟。可详见图 6 FlexCAN 模块协议引擎时钟框图。

对 FlexCAN 引擎时钟频率进行可编程分频后得到 FlexCAN 串行时钟频率,FlexCAN 串行时钟频率的倒数被称为时间份额,用于衡量 FlexCAN 传输过程中各个阶段所需的时间长度,详见图 7 1 bit time 下 FlexCAN 传输段。

SYNC_SEG 表示使用 1 个时间份额用于同步;Time Segment 1 段包括 PROPSEG 段和 PSEG1 段,前者用于补偿实际网络传输延时,后者和 PSEG2 都将用于补偿在 FlexCAN 传输过程中潜在的边缘相位误差。上述三项可以分别通过 FLEXCAN_CTRL1[PROPSEG]、FLEXCAN_CTRL1[PSEG1] 和 FLEXCAN_CTRL1[PSEG2] 进行设置。下图 8 是 Bosch CAN 2.0B 标准中对于 PSEG1 和PSEG2 的设置表。

在上述定义下,假定此时引擎时钟源的时钟频率为 ClockFreqHz,PROPSEG 中指定值为 PropSegLen,PSEG1 和PSEG2 中指定值分别为 PhaSegLen1 和 PhaSegLen2,所需波特率为 BaudRate 时,我们可以得到预期的分频数值 Div 为:

得到的 Div 值通过配置 FLEXCAN_CTRL1[PreDiv] 实现。这里尤其要注意各项和 ClockFreqHz 之间是否满足整除关系。
配置发送帧
首先需要将发送帧的内容填入等待发送的 i 号信息缓冲区的寄存器 FLEXCAN_MB[i] 的 CS、ID、WORD0 和WORD1 寄存器。然后将发送 CODE 值 (12) 填入 FLEXCAN_MB[i].CS[CODE] 中,即可发送。此时,如果将 FLEXCAN_ IMASK1 中对应中断位设置为 1,则发送成功后将会唤起中断。
配置接收帧格式
接收匹配需要设置对应的 i 号信息缓冲区 FLEXCAN_MB[i].CS[CODE] 值为 4,表示当前信息缓冲区接收为空,可以接收信息并安全存储,配置 FLEXCAN_MB[i].ID 为预期接收的帧 ID 号。设置 FLEXCAN_MB[i].CS[IDE] 配置接收扩展帧或标准帧,接收扩展帧则配置为 1,接收标准帧则配置为 0。设置 FLEXCAN_MB[i].CS[RTR] 配置接收远程帧或数据帧,接收远程帧则此位配置为 1,否则配置为 0。
如果需要对帧进行过滤接收,则需额外考虑配置全局帧过滤器或局部帧过滤器。配置 FLEXCAN_MCR[IRMQ] 为 1 则采用局部帧过滤,配置为 0 则采用全局帧过滤。当使用局部帧过滤方法时,掩码需要通过 FLEXCAN_RXIMRN[i] 寄存器配置,全局则通过 FLEXCAN_RXMGMASK、FLEXCAN_RX14MASK 和FLEXCAN_RX15MASK 寄存器进行配置。
4、FlexCAN 实验
在 SDK 中已有支持的 pokt-f0140 开发板上,在 driver example 下的 flexcan_loopback 样例中,使用 FlexCAN 模块进行回环测试,演示 FlexCAN 的初始化设置、接收和发送设置以及中断处理。
初始化外设时钟
FlexCAN 模块在 APB1 总线上,因此对 RCC_ APB1ENR 寄存器的 FLEXCAN 对应位设置为 1。
RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN, true);
初始化FlexCAN
使用外设时钟作为 FlexCAN 的时钟源,则 FlexCAN 的时钟频率为 pokt-f0140 上 APB1 总线时钟频率,为48MHz。此时将传输阶段的时钟配置 PhaseSegLen1,PhaseSegLen2,PropSegLen 分别配置为 2, 1, 1;将波特率APP_FLEXCAN_XFER_BAUDRATE 设置为 1MHz。则根据先前所述的波特率计算公式,可以满足整除关系。在实际调整波特率,需要考虑传输段的时钟设置是否满足整除关系。
/* Setup the flexcan module. */ void app_flexcan_init(void) { /* Set the baudrate and bit time. */ FLEXCAN_TimConf_Type flexcan_tim_conf; flexcan_tim_conf.EnableExtendedTime = false;/* No need to use extended time setting register. */ flexcan_tim_conf.PhaSegLen1 = 2u; flexcan_tim_conf.PhaSegLen2 = 1u; flexcan_tim_conf.PropSegLen = 1u; /* Setup flexcan. */ FLEXCAN_Init_Type flexcan_init; flexcan_init.MaxXferNum = APP_FLEXCAN_XFER_MaxNum; /* The max mb number to be used. */ flexcan_init.ClockSource = FLEXCAN_ClockSource_Periph; /* Use peripheral clock. */ flexcan_init.BaudRate = APP_FLEXCAN_XFER_BAUDRATE; /* Set baudrate. */ flexcan_init.ClockFreqHz = CLOCK_APB1_FREQ; /* Set clock frequency. */ flexcan_init.SelfWakeUp = FLEXCAN_SelfWakeUp_BypassFilter; /* Use unfiltered signal to wake up flexcan. */ flexcan_init.WorkMode = FLEXCAN_WorkMode_LoopBack; /* Normal workmode, can receive and transport. */ flexcan_init.Mask = FLEXCAN_Mask_Global; /* Use global mask for filtering. */ flexcan_init.EnableSelfReception = true; /* Must receive mb frame sent by self. */ flexcan_init.EnableTimerSync = true; /* Every tx or rx done, refresh the timer to start from zero. */ flexcan_init.TimConf = &flexcan_tim_conf; /* Set timing sychronization. */ /* Enable FlexCAN. */ FLEXCAN_Init(BOARD_FLEXCAN_PORT, &flexcan_init); }
配置接收信息缓冲区
配置接收 MB 的接收帧类型为标准数据帧,ID 为 APP_FLEXCAN_XFER_ID。通过配置接收 MB 的 CODE 区,将其配置为接收为空的状态。
/* Set rx mb. */ FLEXCAN_RxMbConf_Type flexcan_mb_conf; flexcan_mb_conf.Id = APP_FLEXCAN_XFER_ID; /* Id for receiving. */ flexcan_mb_conf.MbType = FLEXCAN_MbType_Data; /* Only receive standard data frame. */ flexcan_mb_conf.MbFormat = FLEXCAN_MbFormat_Standard; FLEXCAN_SetRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &flexcan_mb_conf); /* Set for receiving frames. */ FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_MbCode_RxEmpty);
配置发送信息缓冲区
将发送 MB 的寄存器清空。通过设置该 MB 的 CODE 区,将该 MB 设置为发送空闲。
/* Reset tx mb. */ FLEXCAN_ResetMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH); FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxInactive);
配置接收中断
使能 FlexCAN 的接收中断,设置 NVIC。
/* Enable intterupts for rx mb. */ FLEXCAN_EnableMbInterrupts(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_INT, true); NVIC_EnableIRQ(BOARD_FLEXCAN_IRQn);
中断处理函数
中断处理函数检查接收中断,设置接收全局标志位为真。
/* Interrupt request handler. */ void BOARD_FLEXCAN_IRQHandler(void) { /* Check mb status. If received. */ if (0u!= (FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT) & BOARD_FLEXCAN_RX_MB_STATUS) ) { /* Clear flexcan mb interrupt flag. */ FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_STATUS); /* Update the flag. */ app_flexcan_rx_flag = true; } }
发送数据
设置标准数据帧的 ID 号,帧类型和帧格式以及优先级;然后填入数据;设置数据长度。将上述配置写入 MB 相关寄存器,然后修改 MB 的 CODE 区进行发送。
/* Send a message frame. */ void app_flexcan_tx(uint8_t * tx_buf) { /* Prepare sending mb frame. */ FLEXCAN_Mb_Type mb; mb.ID = APP_FLEXCAN_XFER_ID; /* Indicated ID number. */ mb.TYPE = FLEXCAN_MbType_Data; /* Data frame type. */ mb.FORMAT = FLEXCAN_MbFormat_Standard; /* Standard frame format. */ mb.PRIORITY = APP_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */ /* Set the information. */ mb.BYTE0 = tx_buf[0]; mb.BYTE1 = tx_buf[1]; mb.BYTE2 = tx_buf[2]; mb.BYTE3 = tx_buf[3]; mb.BYTE4 = tx_buf[4]; mb.BYTE5 = tx_buf[5]; mb.BYTE6 = tx_buf[6]; mb.BYTE7 = tx_buf[7]; /* Set the workload size. */ mb.LENGTH = APP_FLEXCAN_XFER_BUF_LEN; /* Send. */ FLEXCAN_WriteTxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, &mb); FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxDataOrRemote); /* Write code to send. */ }
读取数据
读取对应的 MB 寄存器,将帧数据段内容解析出来。
/* Receive a message frame. */ void app_flexcan_read(uint8_t *rx_buf) { /* Read the info from mb and reconstruct for understanding. */ FLEXCAN_ReadRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &app_flexcan_rx_mb); rx_buf[0] = app_flexcan_rx_mb.BYTE0; rx_buf[1] = app_flexcan_rx_mb.BYTE1; rx_buf[2] = app_flexcan_rx_mb.BYTE2; rx_buf[3] = app_flexcan_rx_mb.BYTE3; rx_buf[4] = app_flexcan_rx_mb.BYTE4; rx_buf[5] = app_flexcan_rx_mb.BYTE5; rx_buf[6] = app_flexcan_rx_mb.BYTE6; rx_buf[7] = app_flexcan_rx_mb.BYTE7; }
main() 函数
主函数会在键入之后,准备好数据并通过发送 MB 发送;等待中断处理函数将全局标志位设置为真后,解析接收MB 收到的帧,并打印。然后再等待下一次键入。
int main(void) { BOARD_Init(); printf("\r\nflexcan_loopback example.\r\n"); /* Setup the flexcan module.*/ app_flexcan_init(); printf("press any key to send loop back frame with id %u.\r\n", (unsigned)APP_FLEXCAN_XFER_ID); while (1) { getchar(); /* Send a message through flexcan. */ for (uint8_t i = 0u; i < APP_FLEXCAN_XFER_BUF_LEN; i++) { app_flexcan_tx_buf[i] = ( app_flexcan_tx_buf[i] + i) % 256u; } app_flexcan_tx(app_flexcan_tx_buf); printf("app_flexcan_tx() done.\r\n"); /* Wait for reception. */ while (!app_flexcan_rx_flag) /* This flag will be on when the Rx interrupt is asserted. */ { } app_flexcan_rx_flag = false; /* Read the message. */ app_flexcan_read(app_flexcan_rx_buf); /* Send frame. */ printf("app_flexcan_read(): "); for (uint8_t i = 0u; i < APP_FLEXCAN_XFER_BUF_LEN; i++) { printf("%u ", (unsigned)app_flexcan_rx_buf[i]); } printf("\r\n\r\n"); } }
实验结果

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。












页面
