MM32F0140

前提

Mini_F0140原理图上I2C连接着24C02:

1.png

一.IIC介绍

I2C(Inter-Integrated Circuit)是一种通用的总线协议,实现I2C需要两根信号线完成信息交换,SCL时钟作为信号线,SDA作为数据输入/输出线。I2C属于同步通信,由于输入输出数据均使用一根线,因此通信方向为半双工。

主要特征有:

1. I2C 总线协议转换器/并行总线

2. 半双工同步操作

3. 支持主从模式

4. 支持 7 位和 10 位地址格式

5. 支持起始(START)、停止(STOP)、重新起始(RESTART)以及应答(ACK)信号的生成和检测

6. 支持标准模式(最大 100Kbps)、快速模式(最大 400Kbps)

7. 分别有 2 字节的发送和接收缓冲

8. 支持过滤毛刺功能

9. 支持 DMA 操作

10. 支持中断和查询操作

11. 支持多个从地址(详细见用户手册)

下图为信号变化及其特定含义:

2.png

当主发送器如下图所示传输数据时,从接收器在接收到的每个字节后产生一个 ACK 来响应主发送器。

3.png

二.24C02

24C02是低工作电压的2K位串行电可擦除只读存储器,内部组织为256个字节,每个字节8位,该芯片被广泛应用于低电压及低功耗的工商业领域。

主器件通过发送一个起始信号启动发送过程,然后发送它所要寻址的从器件的地址。8位从器件地址的高4位固定为1010。接下来的3位(A2、A1、A0)为器件的地址位。当接收数据时,与I2C连接的从器件名为0xA0。

三.例程

向从机写数据后,再读取数据。

1. I2C配置

RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
I2C_StructInit(&I2C_InitStruct);    
//Configure I2C as master mode    
I2C_InitStruct.Mode = I2C_CR_MASTER;    
I2C_InitStruct.OwnAddress = 0;    
I2C_InitStruct.Speed = I2C_CR_STD;    
I2C_InitStruct.ClockSpeed = 100000;    
I2C_Init(I2Cx, &I2C_InitStruct);    
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);    
I2C_Cmd(I2Cx, ENABLE);

2. 定义24C02结构体

typedef struct {
    u8 busy;
    u8 ack;
    u8 fault;
    u8 opt;
    u8 sub;
    u8 cnt;
    u8* ptr;
    u8 sadd;
} gEepromTypeDef;

3. 写数据

EEPROM_WriteByte(sub);
while (cnt --) {
        EEPROM_WriteByte(*ptr);
        ptr++;
}
I2C_GenerateSTOP(I2C1, ENABLE);
while((I2C_GetITStatus(I2C1, I2C_IT_STOP_DET)) == 0);    
gEeprom.ack = true;    
gEeprom.busy = false;    
EEPROM_WaitEEready();    
return true;

4. 读数据

u8 i, flag = 0, _cnt = 0;
for (i = 0; i < gEeprom.cnt; i++) {
    while(1) {
        //Write command is sent when RX FIFO is not full
        if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) {
            I2C_ReadCmd(I2C1);
            _cnt++;
            if (_cnt == gEeprom.cnt)
                flag = 1;
        }
        //Check receive FIFO not empty
        if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) {
            gEeprom.ptr[i] = I2C_ReceiveData(I2C1);
            break;
        }
    }
}

5. 结果

4.png

来源:灵动MM32MCU

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

围观 21

前提 

查看MM32F0140的MiniBoard原理图,SPI挂载了W25Q80。

1.png

一、SPI介绍

串行外设接口,Serial Peripheral Interface。一种高速的、全双工、同步的串行通信总线。主要应用于EEPROM、FLASH、AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI 支持同时发送和接收数据,采用主从工作方式。SPI 通过 4 个引脚与外部器件相连,分别是:

MOSI:主设备输出、从设备输入引脚。传输方向为主设备发送到从设备。

MISO:主设备输入、从设备输出引脚。传输方向为从设备发送到主设备。

SCK:串行时钟信号,由主设备产生并提供给从设备。

NSS:片选信号,从设备选择。

下图示意主、从设备之间一对一通信时的连接情况:

2.png

主设备负责发起通信请求,从设备负责响应,从设备通过 SCK 引脚得到主设备提供的时钟信号,从而使得主、从设备均使用同一个时钟进行同步的全双工通信。对于从设备而言,MOSI 引脚输入来自主设备的发送数据,MISO 引脚输出响应数据传给主设备。

SPI 需要根据从机特性,选配时钟极性(CPOL)和时钟相位(CPHA),比如从机时钟空闲时为低电平,且在时钟信号的第一个跳变沿采样,此时时序见下图:

3.png

二、W25Q80介绍

W25Q80是一种串行闪存存储器,主要特点包括:

容量:8 Mbit(1 MB)的存储容量,可以存储大量数据。

SPI接口:采用SPI接口进行数据通信,具备高速的数据传输能力。

快速读取:支持快速的连续读取操作,可提供高效的读取性能。

块擦除:支持块擦除功能,可以批量擦除数据,提高擦除效率。

低功耗:采用低功耗设计,适用于对功耗要求较高的应用场景。

W25Q80可以划分为 4096 个扇区,每个扇区包含 256 个页,每个页的大小为 256 字节。

下图为W25Q80的指令:

4.png

三、例程

1.介绍

本例程实现SPI与W25Q80通信,SPI查询W25Q80的设备号,并全片擦除后写入数据,再读取出来,看写入的数据与读取的数据是否一致。

2.编码详情

1)初始化配置SPI,速度为PCLK1(36MHz)的32分频,约为1.125MHz;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_DataWidth = 8;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
exSPI_DataEdgeAdjust(SPI2, SPI_DataEdgeAdjust_FAST);
SPI_BiDirectionalLineConfig(SPI2, SPI_Direction_Tx);
SPI_BiDirectionalLineConfig(SPI2, SPI_Direction_Rx);
SPI_Cmd(SPI2, ENABLE);

2)编写SPI发送和读取数据的函数

uint32_t writeAndReadData(uint8_t data)
{
    SPI_SendData(SPI2, data);
    while (1) {
        if(SPI_GetFlagStatus(SPI2, SPI_FLAG_TXEPT)) {
            break;
        }
    }
    while (1) {
        if(SPI_GetFlagStatus(SPI2, SPI_FLAG_RXAVL)) {
            return SPI_ReceiveData(SPI2);
        }
    }
}

3)获取W25Q80的设备ID

SPI_CSInternalSelected(SPI2, ENABLE); // 片选
writeAndReadData(0x9F); // 读ID指令
ID |= writeAndReadData(0x00) << 16;
ID |= writeAndReadData(0x00) << 8;
ID |= writeAndReadData(0x00);
SPI_CSInternalSelected(SPI2, DISABLE); // 取消片选
printf("\n\nread device id: %X", ID);

4)全片擦除W25Q80,并检查状态等待擦除完成

SPI_CSInternalSelected(SPI2, ENABLE); 
writeAndReadData(0x06); // 写使能
SPI_CSInternalSelected(SPI2, DISABLE);

SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0xC7); // 全片擦除指令
SPI_CSInternalSelected(SPI2, DISABLE);

SPI_CSInternalSelected(SPI2, ENABLE); 
writeAndReadData(0x05); // 读状态寄存器
while(1) {
    temp = writeAndReadData(0x00);
  if((temp & 0x01) == 0x0) // 擦除完成
     break;
  }
SPI_CSInternalSelected(SPI2, DISABLE); 
printf("\n\nerase complete");

5)写数据,并检查状态等待写完

for (i = 0; i < 256; i++) txData[i] = i;
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x06);// 写使能
SPI_CSInternalSelected(SPI2, DISABLE); 

SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x02); // 页编程
writeAndReadData(0x00); // 页地址
writeAndReadData(0x00);
writeAndReadData(0x00);
for (i = 0; i < 256; i++)
{
  writeAndReadData(txData[i]);
}
SPI_CSInternalSelected(SPI2, DISABLE); 

SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x05); // 读状态寄存器
while(1) {
          temp = writeAndReadData(0x00);
        if((temp & 0x01) == 0x0) // 页编程完成
      break;
}

SPI_CSInternalSelected(SPI2, DISABLE);
printf("\n\npage programm complete");


6)读数据,并打印出来

SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x03); // 读数据
writeAndReadData(0x00); // 页地址
writeAndReadData(0x00);
writeAndReadData(0x00);
for (i = 0; i < 10; i++)
{
   rxData[i] = writeAndReadData(0x00);
}
SPI_CSInternalSelected(SPI2, DISABLE); 
printf("\n\nread data completely\n");
for (i = 0; i < 10; i++) 
  printf("\nrxDate[%d] = %d", i, rxData[i]);

3.串口输出结果

下图为串口输出数据,基本符合程序流程和结果。

5.png

4.逻辑分析仪

下图为逻辑分析仪截取的发送接收图片,可以清楚的看到当前SCK速度接近1.125MHz,以及发送数据也符合预期。

6.png

来源:灵动MM32MCU

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

围观 12

本文将介绍在使用 MM32F0140 系列 MCU 实现 UDS Bootloader 过程中涉及到的 FlexCAN、UDS 和 Bootloader 等相关基本概念。

MM32F0140 简介

MM32F0140 使用高性能的 Arm® Cortex-M0 内核的 32 位微控制器,最高工作频率可达 72MHz,内置 64KB Flash 和 8KB SRAM,有丰富的增强型 I/O 端口和包括 FlexCAN 在内等多种外设,适用于汽车诊断仪,后装汽车协控制器和消防监控等多种应用场合。

“图
图 1 MM32F1040 简介

什么是 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 所示:

“图
图 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 所示:

“图
图 3 UDS 服务概览

CAN、UDS 和 OSI 模型之间的关系

为了更好的理解 UDS, 让我们了解一下 CAN 总线、UDS 和 OSI 模型之间的关系。

CAN 对应于 OSI 模型中的数据链路层和物理层描述(根据 ISO 11898)。

与 CAN 相比, UDS (ISO 14229) 是一种 “更高层协议”, 在 OSI 模型中使用到会话层和应用层,如下图 4 所示:

“图
图 4 UDS 与 OSI 模型对应图

UDS 的消息结构

PDU

Network_Protocol Data Unit, 网络层协议数据单元

PDU 是用于建立对等实体间的通信,是一组信息和数据的集合,表示了发送发和接收方对等实体之间传递的信息和数据。由地址信息(CAN ID)、协议控制信息(PCI) 和数据构成。

图 5 为 UDS 消息结构示意图,图 6 为 UDS 消极响应示意图。

“图
图 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 所示。

“图
图 7 ISO-TP 帧类型

通过使用 ISO-TP 标准将 UDS 的消息结构 PDU 分为了四种类型:

SF (Single Frame, 单帧)

描述单帧传输。

FF (First Frame, 首帧)

描述多帧传输的起始。

CF (Consecutive Frame,连续帧)

用于在多帧传输中传输数据。

FC (Flow Control Frame,流控帧)

用于在多帧传输过程中,对报文流控制。

UDS 的单帧通信和多帧通信:

单帧通信如图 8 所示:

“图
图 8 单帧通信

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

“图
图 9 多帧通信

基于 UDS Bootloader 实现更新 APP 流程框图

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

“图10
图10 更新 APP 流程

结语

本文以 MM32F0140 系列 MCU 的 FlexCAN 为例,简要介绍了在使用 MM32F0140 系列 MCU 实现 UDS Bootloader 过程中涉及到的 FlexCAN、UDS 和 Bootloader 等相关基本概念,并介绍了 UDS 的消息结构和 ISO-TP 标准,以及展示了 MM32F0140 系列 MCU 使用 FlexCAN 实现 UDS Bootloader 更新 APP 的流程框图。

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

围观 438

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两种算法

● 支持输入数据和输出数据的大小端选择

功能框图

“MM32F0140学习笔记——CRC"

硬件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在运算执行时间对比如下图所示:

“MM32F0140学习笔记——CRC"

“MM32F0140学习笔记——CRC"

软件CRC相比于硬件CRC在执行速度上最少有5倍的差距,软件CRC的代码量以及占用SRAM的空间也比硬件CRC要多;所以在MCU带有硬件CRC功能时,通过硬件CRC的计算方式可以大大节省MCU的资源、提升CRC的运算速度,给实际应用带来流畅的检验。

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

围观 76

前面一个章节中我们和大家一起学习了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 周期发送报文直到恢复正常通讯的实测图:

“MM32F0140

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节点会处于不同的设备状态。根据计数值的变化进行状态转换,状态转换如下图所示:

“MM32F0140

接收、发送错误计数器对应的变动条件及数值变动情况:

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 后恢复时间的控制策略要求,对总线关闭状态的节点需要实现“快恢复”和“慢恢复”策略。

“MM32F0140

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

大致流程图如下:

“MM32F0140

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

“MM32F0140

在BusOff 恢复策略制定时,是需要通过软件流程去实施实现控制时长,IP 层面不能设置恢复的时长。至于如何判断是主动错误还是被动错误,可以根据 CAN_ESR1.FLTCONF 寄存器以及结合CAN_ECR 寄存器来分析。

我们通过CANScope总线综合分析仪对上述的测试项一一进行测试,MM32F0140的FlexCAN 能够满足相关的测试标准,能够满足汽车领域对通信总线的要求,保障整车CAN总线安全稳定。

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

围观 184

一直关注我们灵动微课堂的朋友们,想必通过前面的介绍已经掌握了 FlexCAN 的基本使用方法,也能够在自己的方案中运用自如了。今天小编想和大家借助 ZLG 的CAN Scope工具了解我们这颗 MCU 的 CAN 的特性,看看在汽车 ECU 应用上它是如何保证通讯的鲁棒性。

在整车网络调试中,各节点遵循 CAN 一致性测试是保证总线的稳定运行的重要前提, 当前CAN一致性测试已经电动汽车行业的必然趋势!

据了解,应用到 CAN 的 ECU 在实际装车之前都需要经历一系列的一致性测试方能保证整车通讯的稳定,每家车厂除了遵循 ISO 标准,他们也都有自己的规约和检测流程,但测试项目不外乎都包含在以下表中:

“MM32F0140

以上测试项中的被测内容,有些是属于板级设计阶段需要重点关注的,有些是与 MCU 本身功能和性能密切相关的,接下来我们分层剖析。

物理层一致性测试

该项测试主要针对各个节点 ECU 板级层面的电阻、电容特性,节点自身差分电阻以及总线终端电阻和CAN 通信物理电平做出充分评测,以验证 CAN 节点与系统网络在电路设计、物理电平等方面的性能。

1.1 终端电阻测试

在高速 CAN 通讯应用中,我们时常会看到信号出现反射、过冲、振铃等异常的现象,根据电磁场和微波理论分析,高频电路中负载阻抗需与传输线缆的特性阻抗一致,否则电信号在线束终端容易遭遇阻抗突变,发生反射现象,造成波形震荡,这将造成信号串扰、总线紊乱以及消息丢失等诸多通讯故障。下图为典型的信号振铃波形图:

“MM32F0140

所以在差分 CAN 网络中,终端匹配电阻是必不可少的器件,它一般安装在双绞线缆的 2 端,依照总线的拓扑结构而准确定位,需选择线束最远距离的 2 个节点处。

终端电阻的作用:

1.加快放电,使总线快速进入隐性状态,提高抗干扰能力。

2.消除阻抗不连续和不匹配,提高信号质量。

终端电阻的存在,一方面能提高总线抗干扰能力,让高频低能量的信号迅速泄放,一方面能确保总线快速进入隐性状态,让寄生电容的能量更快泄放,另一方面还能提高信号质量,降低了线缆上的反射能量。

终端电阻过小会造成信号幅值偏小,信号在采样时无法辨别高低电平,导致整个网络数据混乱,有时可以通讯,但极不稳定。

终端电阻过大会造成信号幅值偏大,驱动力过大,信号出现过冲现象,信号下降沿过缓,下降沿时间过长,严重的情况直接导致总线错误。

一般终端电阻的实际阻值选择需要根据实测得到,因为它受任何一根线缆的特征阻抗(这里包括系统的容抗+感抗)所影响。

有个粗犷的测试手法:采用两根汽车使用的典型线缆扭制成双绞线,线缆的一端接方波发生器,另一端接可调电阻,并通过示波器观察电阻上的波形,调整电阻阻值的大小,直到电阻上的信号是一个良好的无振铃的方波,此时的电阻值可以认为与线缆的特征阻抗一致从而得到匹配电阻的阻值数据。另外也可以直接找到车上的通讯线缆用万用表实测。终端电阻阻值和功率的选择在 ISO 11898-2 标准里面也有定义,可以作为首选参考。

“MM32F0140

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 标准,如下表描述:

“MM32F0140

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

“MM32F0140

1.3 跳变沿时间测试

主要为测试 ECU 的差分电平在隐性电平到显性电平、显性电平到隐性电平变化的时间,需要在指定的波特率条件下进行测试,接线长度需小于1米,波特率为 250k 的情况下进行通信,且将差分电平的上升、下降时间的起始点定为电压值的 20%~80% 的区间处,即从隐性到显性状态过渡的上升时间起始点为差分电压的 20% 处,终止点为差分电压的 80% 处;从显性向隐性状态过渡的下降时间起始点为差分电压的 80% 处,终止点为差分电压的 20% 处。

简单测量时可以选用示波器直接抓取波形测试,但一般需要每个边沿至少测量 1000 次,以确定边缘上升/下降时间的最小值和最大值,数据才更为可靠,这就需要使用专业的 CAN 测试仪器了,比如 CAN Scope、PICO Scope 等等。跳变沿的时间需要遵循 GMW3122 信号边沿标准:

“MM32F0140

MM32F0140 实测通信质量:

“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 的位时间精度功能实测,可以得出以下结果:

“MM32F0140

官方驱动中的 SJW 默认设置为 1 ,在实际允许的容忍偏差范围内,可以自行调整该参数,一般建议设置为 2~3 个 Tq 。

2.2 采样点测试

除了精准的波特率,采样点位置的匹配同样是要求即为严苛的,在不同的标准中对采样点要求各不相同,而且不同车厂对采样点的设置、时钟源的需求以及每个段所占的 Tq 数也都是不尽相同的。下图展示了 CiA 推荐的数据场采样点位置范围:

“MM32F0140

“MM32F0140

在汽车工程师协会 SAE class C 标准中将高速总线波特率定为 125K ~ 1 M 范围,中、低速总线波特率定为 125K 以下范围,高速总线推荐使用单次采样模式,而中低速总线推荐 3 倍采样模式。

网络中节点采样点的不一致会导致同样的采样频率出现采样错误,进而使整车网络出现故障问题,影响整车环境稳定。

官方例程中默认使用的 3 倍采样,且 CAN Scope 仅支持单次采样模式,所以需要按照以下方式修改采样点模式:

“MM32F0140

更改后,其它按照默认配置,则采样点设置成了 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% :

“MM32F0140

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

“MM32F0140

“MM32F0140

计算器中的参数与程序中位时序配置的对应关系如下:

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)。

围观 207

独立看门狗(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所示。

“MM32F0140学习笔记——独立看门狗(IWDG)"
图1

3、重装载寄存器

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

“MM32F0140学习笔记——独立看门狗(IWDG)"
图2

4、看门狗超时时间

IWDG的超时周期可以通过重装载寄存器(IWDG_RLR)的值和预分频寄存器(IWDG_PR)计算得到,公式如下:

Tout(ms)=((4×2^PR)×RLR)/40

当IWDG_RLR寄存器为最大值时,可以获得最长的超时时间,参考时间如表1:

“MM32F0140学习笔记——独立看门狗(IWDG)"
表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”。

“MM32F0140学习笔记——独立看门狗(IWDG)"
图3

针对需要短时间低功耗STOP模式唤醒的应用场景,可以使用该方式唤醒,同时针对LSI精度不高的问题,可以通过HSI对LSI进行校准方式,从而获取高精度的LSI时钟源。

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

围观 199

1、ADC简介

ADC全称为analog to digital converter,是12位的逐次逼近型(SAR)模拟数字转换器,能够将模拟信号转换为数字信号。

MM32F0140的ADC拥有高达1MSPS转换速率,支持最大输入时钟为15MHz,ADC1多达14路外部输入通道和2路内部通道,ADC2、ADC3多达16路外部输入通道。

ADC功能框图如图1所示,数据通过模拟输入信号(AIN)进行输入,将数据传输到12位的逐次逼近型模拟数字转换器,转换后将结果按指定方向对齐并存储到对应的通道数据寄存器中。

“图1.ADC功能框图"
图1.ADC功能框图

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所示。

“图2.ADC采样时间"
图2.ADC采样时间

时钟分频系数

ADC时钟的分频系数可通过操作配置寄存器(ADC_ADCFG)的ADCPRE位来选择,令ADC外设时钟的(ADCPRE+2)分频作为ADC时钟。

数据对齐

通过配置控制寄存器(ADCR)中的ALIGN位,可选择转换后数据储存为左对齐或右对齐,如图3所示,当采用右对齐,A/D转换结果的最低位与数据寄存器中DATA位的最低位对齐;当采用左对齐,A/D转换结果的最高位与数据寄存器中DATA位的最高位对齐。

“图3.ADC数据对齐"
图3.ADC数据对齐

通道选择

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");
    }
}

“图4.实验现象"
图4.实验现象

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

围观 114

1、WWDG简介

窗口看门狗(WWDG)是喂狗时间有上下限范围的看门狗,主要用于检测由外部干扰和不可预测的逻辑条件导致程序跑飞了所引起的软件问题,根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗或滞后刷新看门狗,可以用于检测出程序是否按照正常的路径运行或者是非正常的跳过某些程序段的情况。

复位

当WWDG计数器的值大于设定的上限窗口值时喂狗,将产生复位;当计数器的值从下限固定值0x40变成0x3F时,低于窗口看门狗的下限,也会产生复位。

计数器时钟

WWDG计数器时钟由CK计时器时钟经过预分频器分频得到,预分频器的时基由配置寄存器(WWDG_CFGR)的WDGTB位配置,如图1所示,CK计时器时钟=PCLK1/4096,可将CK计时器时钟进行2分频/4分频/8分频/不分频。

“图1.WWDG计数器时钟配置"
图1.WWDG计数器时钟配置

计数器

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

“图2.WWDG计数器位"
图2.WWDG计数器位

窗口值

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

“图3.WWDG上限窗口值"
图3.WWDG上限窗口值

提前唤醒中断

利用提前唤醒中断(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);
    }
}

“图4.实验现象"
图4.实验现象

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

围观 200

页面

订阅 RSS - MM32F0140