MM32

在前面的章节中我们介绍了MM32 USB各种功能类型,也介绍了如何通过串口或者J-Link RTT方式实现shell辅助调试方式,但是其都需要依赖额外的工具,比如串口方式就需要USB转TTL,J-Link RTT需要使用J-Link下载器,所以希望有新的方法实现shell,本次我们介绍USB CDC的方式来实现shell功能。

本次我们采用MM32L373 MiniBoard作为测试开发板,验证USB CDC的方式来实现shell功能。

前面已经为大家讲解了shell的串口方法,其实原理一样,只是用MM32 USB枚举成串口设备替代USB转TTL,直接从USB获取数据到MCU,也不需要额外占用MCU的串口,节省资源和硬件,相关的代码都可以从之前的文章获取,本次只是融合两者,改变实现接口,具体代码参考如下:

对于CDC部分,其函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER 0

#define USBD_MAX_PACKET0 64

#define USBD_DEVDESC_IDVENDOR 0x2F81 //0x0D28

#define USBD_DEVDESC_IDPRODUCT 0x0001 //0x0204

以上是定义的MM32 MCU CDC设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的CDC设备,如图1所示:

图1 PC设备管理器列表

对于MM32 MCU的CDC功能来说,

在使用CDC功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured()) // Wait for USB Device to configure
{
}
while (1)
{
}
}

对于shell部分其函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct

{

char *command; // shell命令提示符

char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer

unsigned short length; // shell命令长度大小

unsigned short cursor; // shell光标位置偏移

char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量

char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域

unsigned short historyCount; // 历史记录数量

short historyFlag; // 当前记录偏移位置

short historyOffset; // 历史记录偏移大小

SHELL_CommandTypeDef *commandBase; // 命令表基地址

unsigned short commandNumber; // 命令数量

int keyFuncBase; // 按键响应表基地址

unsigned short keyFuncNumber; // 按键响应数量

SHELL_InputMode status; // shell输入状态

unsigned char isActive; //是不是当前激活的shell

shellRead read; // shell读函数接口

shellWrite write; // shell写函数接口

}SHELL_TypeDef;

如上所示,为对象的定义接口,移植的步骤先定义一个shell对象,即:SHELL_TypeDef cdc_shell,然后实例化对象的操作接口,具体说明看注释,对于其中我们需要关注的是shell的读写接口。由于本次我们使用USB CDC接收和发送数据,所以我们只需要在USB CDC的函数中处理接收到的数据即可,我们使用shellHandler(&cdc_shell, EP2RXBuff[i]);来处理数据的交互,具体函数代码参考串口shell代码。

shell的发送接口,只需要把数据拷贝到buffer即可。

shell的读写接口移植到CDC上,代码如下:

void USBD_CDC_TASK(void)

{

uint8_t i, count;

NotifyOnStatusChange();

if (CDC_UART ->ISR & 0x08)

{

CDC_UART ->GCR &= ~(3 << 3);

CDC_UART ->GCR = 3 << 3;

UART_ClearITPendingBit(CDC_UART, UART_OVER_ERR);

}

// USB -> UART

if (EP2ReceiveFlag == 1)

{

EP2ReceiveFlag = 0;

for (i = 0; i < RxBufLen; i++)

shellHandler(&cdc_shell, EP2RXBuff[i]);

}

// UART -> USB

if (EP2TransferFlag == 1)

{

if (TxBufLen > 0)

{

while (USB->rEP2_CTRL & 0x80);

if (TxBufLen > 64)

{

UART_ReadData(EP2TXBuff, 64);

count = 64;

TxBufLen -= 64;

}

else

{

UART_ReadData(EP2TXBuff, TxBufLen);

count = TxBufLen;

TxBufLen = 0;

}

usb_buf_busy_flag = 1;

for (i = 0; i < count; i++)

{

USB->rEP2_FIFO = *(EP2TXBuff + i);

}

if ((USB ->rEP2_AVIL & 0x3f) == count)

{

USB->rEP2_CTRL = 0x80 | count;

}

else

{

USB->rTOP |= 1 << 3;

USB->rTOP &= ~(1 << 3);

}

USB->rEP2_CTRL = 0x80 | count;

if (0 == TxBufLen)

EP2TransferFlag = 0;

}

}

}

如上,我们就完成通过MM32 MCU的CDC实现shell调试功能,用串口助手打开虚拟串口,用CDC shell测试发送数据,结果如下:

图2 功能演示

以上就是MM32 MCU USB的CDC shell功能。

本文转自:灵动MM32MCU

围观 9

前面我们介绍了新出USB设备类型WebUSB,其中使用MM32 MCU实现WebUSB功能。既然可以通过网页与USB设备通信,那是否可以做别的功能,比如USB-DFU,当然是可以的,我们通过网页进行DFU功能,即WebDFU功能。因此我们本节我们讲解如何在MM32 MCU实现WebDFU功能。

DFU是使用USB作为微控制器和编程工具之间的通信信道,通常是PC。在DFU类规格书说明中指出所有的DFU命令、状态和数据交换都需要通过端点0进行。命令集和基本协议都定义好的,但是上层协议(数据格式,错误信息等)是客户相关的。也就是说DFU类并没有定义数据传输格式(s19,16进制,纯2进制等等)

由于一个设备同时进行DFU操作和正常运行功能活动是不现实的,因此在DFU操作期间必须停止正常运行活动,这就意味着设备必须改变运行模式——也就是说我们在进行固件更新时比如打印机不再是打印机了,它是一个flash存储器编程器。但是支持DFU的设备不能自主改变模式,这需要接受外部(人或者主机操作系统)的干预。

对于DFU功能,其完成实现固件升级可以分为4个不同阶段。

01、枚举

设备把自身的一些特性告知主机,嵌入在设备正常运行描述符中的一个DFU类接口描述符和相关的函数符能够完成这个目的,并且能够为通过控制管道的类专用的请求提供目标。

02、DFU枚举

主机和设备同意开始固件升级,主机向设备发出USB复位,设备发出第二个描述符集合,并且为传输阶段做准备,这会是相应设备的运行时驱动无效,并使得DFU驱动不受其他目标为该设备通信妨碍,重编程设备的固件。

03、传输

主机将固件映像传输给设备,功能描述符中的参数用于确保非易失性存储器编程的块大小和时序的正确性。状态请求用于保持主机和设备之间的同步。

04、显示

一旦设备向主机报告重新编程完成,主机箱设备则发送usb复位,设备重枚举并执行升级后的固件。为了保证只有DFU驱动加载,有必要的在枚举DFU描述符集合改变id-product字段。

本节我们来讲解如何在MM32 MCU实现WebDFU设备功能,对于MM32 MCU来说,实现WebDFU只需要在之前程序基础上修改添加部分代码即可,按照开源的WebDFU协议加入功能。

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的WebDFU设备功能,我们重新封装好全部代码,用户不需要自己配置那些麻烦的描述符等参数,只需要知道用之前的单一设备函数即可。

软件资源如下:

对于MM32 MCU的WebDFU,我们可以配置WebDFU的参数。

#define USBD_DFU_DNLOAD_ENABLE      1

#define USBD_DFU_UPLOAD_ENABLE      0

#define USBD_DFU_STRDESC              L"USB_DFU"

#define USBD_DFU_XFERBUF_SIZE        1024

#define USBD_WEBUSB_VENDOR_CODE   0x21

#define USBD_WEBUSB_BASE_LANDING_URL "devanlai.github.io/webdfu/dfu-util/?vid="

#define USBD_WEBUSB_LANDING_URL     CONCAT_MACRO_TO_STRING(USBD_WEBUSB_BASE_LANDING_URL, USBD_DEVDESC_IDVENDOR)

#define USBD_WEBUSB_ORIGIN_URL      "devanlai.github.io/"

#define USBD_WEBUSB_IF_NUM          USBD_DFU_IF_NUM

参数设置如上。当进行DFU升级时候,可以看到电脑上显示的设备名称为USB_DFU,就是配置的USBD_DFU_STRDESC参数。

在使用MM32 WebDFU功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured())     // Wait for USB Device to configure

{

}

while (1)

{     

……

}

}

然后依然和之前一样只是在WebUSB基础上修改添加WebDFU相关参数函数接口即可,代码如下:

//DFU初始化

void usbd_dfu_init(void)

{

DFU_Reset();

current_write_addr = 0;

}

//USB DFU开始升级

BOOL USBD_DFU_StartUpgrade(void) {

error_t err = flash_manager_init(target_device);

current_write_addr = target_device.flash_start;

switch (err) {

case ERROR_SUCCESS:

initialized = true;

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}



return (err == ERROR_SUCCESS) ? (__TRUE) : (__FALSE);

}

//复位目标

static bool reset_target(bool error_condition) {

current_write_addr = 0;

if (initialized) {

error_t err = flash_manager_uninit();

switch (err) {

case ERROR_SUCCESS:

if (config_get_auto_rst()) {

// Target is reset and run by the uninit

} else if (!error_condition) {

// Reset and run the target at the end of a successful upgrade

target_set_state(RESET_RUN);

}

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}

initialized = false;

return (err == ERROR_SUCCESS);

}



return true;

}

//USB DFU结束升级

BOOL USBD_DFU_FinishUpgrade(void) {

return reset_target(false) ? (__TRUE) : (__FALSE);

}

//USB DFU写数据

BOOL USBD_DFU_WriteBlock(const U8 *buffer, U16 blockSize) {

error_t err = flash_manager_data(current_write_addr, (U8*)buffer, blockSize);

switch (err) {

case ERROR_SUCCESS:

current_write_addr += blockSize;

break;

case ERROR_RESET:

case ERROR_ALGO_DL:

case ERROR_ALGO_DATA_SEQ:

case ERROR_INIT:

case ERROR_SECURITY_BITS:

case ERROR_UNLOCK:

DFU_SetStatus(DFU_STATUS_ERR_PROG);

break;

case ERROR_ERASE_SECTOR:

case ERROR_ERASE_ALL:

DFU_SetStatus(DFU_STATUS_ERR_ERASE);

break;

case ERROR_WRITE:

DFU_SetStatus(DFU_STATUS_ERR_WRITE);

break;

case ERROR_FAILURE:

case ERROR_INTERNAL:

default:

DFU_SetStatus(DFU_STATUS_ERR_UNKNOWN);

break;

}

return (err == ERROR_SUCCESS);

}

这样我们就完成MM32 MCU的WebDFU功能,将程序下载到板子中,USB插上电脑,电脑上会枚举出USB DFU。在USB DFU枚举成功后,我们需要检查是否真的可以被WebDFU网页识别。

打开https://devanlai.github.io/webdfu/dfu-util/通过该网页检测WebDFU工作状态,网页如下图所示:


通过点击Connect就可以像之前WebUSB设备一样弹出窗口选择我们的USB DFU设备,然后就可以在Vendor ID(hex)窗口看到序列号。可以通过网页上的Detach DFU和Download以及Upload进行DFU升级动作。

以上就是MM32 MCU USB的WebDFU功能,具体的WebDFU协议过程想详细了解看dapboot,大家可以自由发挥修改底层和上层的代码实现自己的WebDFU网页端和设备端。

本文转自:灵动MM32MCU

围观 5

转眼间来到了2020年,新年伊始,小编将和大家一起学习使用MM32 MCU的USB功能。对于USB来说,主要应用是HID、CDC、MSC以及WINUSB等功能,此讲先介绍如何使用MM32 MCU的HID功能。

对于USB设备来说,其中有一大类就是HID设备,即Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,其主要用于人与计算机进行交互。它是USB协议最早支持的一种设备类。HID设备可以作为低速、全速、高速设备用。由于HID设备要求用户输入能得到及时响应,所以其传输方式通常采用中断方式,而且无需安装驱动就能进行交互,简单方便。

在USB通信协议中,HID设备的定义放置在接口描述符中,USB的设备描述符和配置描述符中不包含HID设备的信息。所以对于某些特定的HID设备,我们可以定义多个接口,只要其中一个接口为HID设备类即可,在学习HID之前,先来复习一下USB协议的相关内容。

一、USB设备描述符-概述

当插入USB设备后,主机需要发送比较短的请求来确认设备的身份、类型、速度等信息,这个过程称之为枚举。

那什么是设备描述符呢?Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。

描述符的作用就是通过命令操作作来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作。

USB有那些标准描述符呢?对于 USB来说有5种标准描述符:设备描述符、配置描述符、字符描述符、接口描述符、端点描述符 。

描述符之间有一定的关系,一个设备只有一个设备描述符,而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。由此我们可以看出来,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,然后是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。

枚举的过程:

1、等待稳定:主机通过电平差检测到设备,等待100ms让设备电平趋于稳定;

2、首次复位:HUB发起复位,让设备进入初始的地址0模式;

3、首次查询设备描述符:GET_DESCRIPTOR 主机查询设备描述符,只要前8字节 ==> 80 06 01 00 00 00 12 00 ;

4、二次复位:在接收到设备描述符前8个字节后,再次重启设备;

5、设置地址:SET_ADDRESS 主机下发设置地址命令,设备获取新地址 ==> 00 05 01 00 00 00 00 00 ;

6、二次查询设备描述符:GET_DEVICE_DESCRPTOR获取整个18字节的设备描述符 ==> 80 06 01 00 00 00 12 00 ;

7、获取配置描述符:GET_CONFIGURATION 获取9字节配置描述符 ==> 80 06 02 00 00 00 09 00 ;

8、完成配置:SET_CONFIGURATION;

二、HID设备简述

2.1 HID设备的特点

交换的数据储存在称为报表(Report)的结构内,设备的固件必须支持HlD报表的格式。主机通过控制和中断传输中的传送和请求报表来传送和接收数据。报表的格式非常灵活。

每一笔事务可以携带小量或中量的数据。低速设备每一笔事务最大是8B ,全速设备每一笔事务最大是64B,高速设备每一笔事务最大是1024B,一个报表可以使用多笔事务。

设备可以在未预期的时间传送信息给主机,例如键盘的按键或是鼠标的移动。所以主机会定时轮询设备,以取得最新的数据。

HID 设备的最大传输速度有限制。主机可以保证低速的中断端点每10ms 内最多 1笔事务,每一秒最多是 800B,保证全速端点每1ms 一笔事务,每一秒最多是64000B,保证高速端点每125 us 三笔事务,每一秒最多是 24.576MB。

HID 设备没有保证的传输速率。如果设备是设置在 10ms 的时距,事务之间的时间可能等于或小于10ms。除非设备是设置在全速时在每个帧传输数据,或是在高速时在每个微帧传输数据。这个是最快的轮询速率,所以端点可以保证有正确的带宽可供使用。

HID 设备除了传送数据给主机外,它也会从主机接收数据。只要能够符合HlD 类别规范的设备都可以是HID 设备。设备除了HlD 接口之外,它可能同时还包含有其他的USB 接口。

2.2 HID设备的硬件要求

HID 接口必须要符合 Device Class Definition for Human interface Devices 规范内所定义的 HID 类别的需求。在此文件内描述了所需的描述符、传输的频率以及传输的类型等。为了符合规范,HID 接口的端点与描述符都必须符合数个要求。所有的 HID 传输都是使用默认控制管道或是一个中断管道,HID设备必须有一个中断输入端点来传送数据到主机,中断输出端点则不是必需的。Control管道用于接收和响应USB控制和类数据的请求,在由HID类驱动程序轮询时传输数据(使用Get_Reportrequest),从主机接收数据。

对于主机与设备之间所交换的数据,可以分成两种类型:低延迟的数据,必须尽快地到达目的;配置或其他的数据,没有严格时间限制的需求。中断管道是控制管道之外的另一种数据交换的方式,特别适合使用在接收端需要定时或是尽可能及时收到数据的时候。中断输入管道携带数据到主机,中断输出管道则是携带数据到设备。在总线忙的时候,控制管道可能会被延迟,而中断管道保证会有可得到的带宽。HID不需要一定有中断输出管道。如果没有中断输出管道,主机会在控制管道上使用HID 设备特有的 Set_Report 请求来传送所有的报表。

2.3 HID的程序要求

主机的驱动程序要与 HID 设备通信,其设备的固件必须符合如下几个需求,设备的描述符必须识别该设备包含有 HID 接口(描述符)。除了默认控制管道外,固件必须另外支持一个中断输入管道。固件必须包含一个报表描述符来定义要传送与接收的设备数据。如果要传送数据,固件必须支持 Get_Report 控制传输与中断输入传输。如果要接收数据,固件必须支持 Set_Report 控制传输与选择性的中断输出传输。所有的 HID 数据都必须使用定义过的报表格式来定义报表中数据的大小与内容。设备可以支持一个或多个报表。在固件中的一个报表描述符用来描述此报表,以及如何使用报表数据的信息。在每一个报表中的一个数值,定义此报表是一个输入(Input )、输出(Output )或是特征(Feature )报表。主机在输入报表中接收数据,在输出报表中传送数据,特征报表可以在任何方向传递。

三、HID 描述符

HID 设备除了支持 USB 设备的 5 种标准描述符之外,还支持 HID 设备特有的 3 种描述符。这些描述符是:1、USB 标准描述符:设备、配置、接口、端点和字符串描述符;2、HID 特有的描述符: HID 、报表(Report )和实体(Physical )描述符。从描述符的关联关系看, HID 描述符是关联于接口。所以如果一个 HID 设备有 2 个端点,设备不需要每个端点有一个 HID 描述符,具体参考如下代码:

设备描述符

struct _DEVICE_DEscriptOR_STRUCT

{

BYTE   bLength;      //设备描述符的字节数大小

BYTE   bDescriptorType;   //描述符类型编号,为0x01

WORD  bcdUSB;      //USB版本号

BYTE  bDeviceClass;   //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型,0x00不是在设备描述符中定义的,如HID

BYTE   bDeviceSubClass;  //USB分配的子类代码,同上,值由USB规定和分配的,HID设备此值为0

BYTE  bDeviceProtocl;  //USB分配的设备协议代码,同上HID设备此值为0

BYTE   bMaxPacketSize0;  //端点0的最大包的大小

WORD   idVendor;  //厂商编号

WORD   idProduct;  //产品编号

WORD  bcdDevice;  //设备出厂编号

BYTE   iManufacturer;   //描述厂商字符串的索引

BYTE   iProduct;   //描述产品字符串的索引

BYTE   iSerialNumber;  //描述设备序列号字符串的索引

BYTE   bNumConfiguration;   //可能的配置数量

}

配置描述符 

struct _CONFIGURATION_DEscriptOR_STRUCT

{

BYTE  bLength;   //配置描述符的字节数大小

BYTE   bDescriptorType;   //描述符类型编号,为0x02

WORD   wTotalLength;   //配置所返回的所有数量的大小

BYTE   bNumInterface;  //此配置所支持的接口数量

BYTE   bConfigurationVale;   //Set_Configuration命令需要的参数值

BYTE   iConfiguration;  //描述该配置的字符串的索引值

BYTE  bmAttribute;  //供电模式的选择

BYTE   MaxPower;   //设备从总线提取的最大电流

}

字符描述符 

struct _STRING_DEscriptOR_STRUCT

{

BYTE bLength;     //字符串描述符的字节数大小

BYTE bDescriptorType;    //描述符类型编号,为0x03

BYTE SomeDescriptor[36];   //UNICODE编码的字符串

接口描述符

struct _INTERFACE_DEscriptOR_STRUCT

{

BYTE bLength;     //接口描述符的字节数大小

BYTE bDescriptorType;    //描述符类型编号,为0x04

BYTE bInterfaceNunber;    //接口的编号

BYTE bAlternateSetting;   //备用的接口描述符编号

BYTE bNumEndpoints;    //该接口使用端点数,不包括端点0

BYTE bInterfaceClass;    //接口类型 HID设备此值为0x03

BYTE bInterfaceSubClass;   //接口子类型 HID设备此值为0或者1

BYTE bInterfaceProtocol;   //接口所遵循的协议

BYTE iInterface;   //描述该接口的字符串索引值

}

端点描述符

struct _ENDPOIN_DEscriptOR_STRUCT

{

BYTE bLength;     //端点描述符的字节数大小

BYTE bDescriptorType;     //描述符类型编号,为0x05

BYTE bEndpointAddress;    //端点地址及输入输出属性

BYTE bmAttribute;      //端点的传输类型属性

WORD wMaxPacketSize;    //端点收、发的最大包的大小

BYTE bInterval;     //主机查询端点的时间间隔

}

四、MM32 MCU HID代码实现

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCU的HID功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要了解MM32 MCU HID的VID和PID以及如何处理HID的数据接收和发送即可。

软件资源如下:

以下为函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER                    0

#define USBD_MAX_PACKET0             64

#define USBD_DEVDESC_IDVENDOR      0x2F81

#define USBD_DEVDESC_IDPRODUCT     0x0001

以上是定义的MM32 MCU HID设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的HID设备,如图1所示:


图1 PC设备管理器列表

对于MM32 MCU的HID功能来说,在使用HID功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured())   // Wait for USB Device to configure

{

}

while (1)

{      

}

}

 

然后就是HID数据收发处理函数,USB数据处理函数如下:

static volatile uint8_t  USB_ResponseIdle;

static HID_queue HID_Cmd_queue;

 

void hid_send_packet()

{

uint8_t *sbuf;

int slen;

if (HID_queue_get_send_buf(&HID_Cmd_queue, &sbuf, &slen))

{

if (slen > USBD_HID_OUTREPORT_MAX_SZ)

{

util_assert(0);

}

else

{

usbd_hid_get_report_trigger(0, sbuf, USBD_HID_OUTREPORT_MAX_SZ);

}

}

}

 

// USB HID Callback: when system initializes

void usbd_hid_init(void)

{

USB_ResponseIdle = 1;

HID_queue_init(&HID_Cmd_queue);

}

 

// USB HID Callback: when data needs to be prepared for the host

int usbd_hid_get_report(U8 rtype, U8 rid, U8 *buf, U8 req)

{

uint8_t *sbuf;

int slen;

switch (rtype)

{

case HID_REPORT_INPUT:

switch (req)

{

case USBD_HID_REQ_PERIOD_UPDATE:

break;

 

case USBD_HID_REQ_EP_CTRL:

case USBD_HID_REQ_EP_INT:

if (HID_queue_get_send_buf(&HID_Cmd_queue, &sbuf, &slen))

{

if (slen > USBD_HID_OUTREPORT_MAX_SZ)

{

util_assert(0);

}

else

{

memcpy(buf, sbuf, slen);

return (USBD_HID_OUTREPORT_MAX_SZ);

}

}

else if (req == USBD_HID_REQ_EP_INT)

{

USB_ResponseIdle = 1;

}

break;

}

 

break;

 

case HID_REPORT_FEATURE:

break;

}

 

return (0);

}

 

// USB HID override function return 1 if the activity is trivial or response is null

__attribute__((weak))

uint8_t usbd_hid_no_activity(U8 *buf)

{

return 0;

}

 

// USB HID Callback: when data is received from the host

void usbd_hid_set_report(U8 rtype, U8 rid, U8 *buf, int len, U8 req)

{

uint8_t *rbuf;

main_led_state_t led_next_state = MAIN_LED_FLASH;

switch (rtype)

{

case HID_REPORT_OUTPUT:

if (len == 0)

{

break;

}

if (buf[0] == ID_HID_TransferAbort)

{

HID_TransferAbort = 1;

break;

}

 

// execute and store to HID_queue

if (HID_queue_execute_buf(&HID_Cmd_queue, buf, len, &rbuf))

{

if (usbd_hid_no_activity(rbuf) == 1)

{

//revert HID LED to default if the response is null

led_next_state = MAIN_LED_DEF;

}

if (USB_ResponseIdle)

{

hid_send_packet();

USB_ResponseIdle = 0;

}

}

else

{

util_assert(0);

}

 

break;

 

case HID_REPORT_FEATURE:

break;

}

}

 

void HID_queue_init(HID_queue *queue)

{

queue->recv_idx = 0;

queue->send_idx = 0;

queue->free_count = FREE_COUNT_INIT;

queue->send_count = SEND_COUNT_INIT;

}

 

BOOL HID_queue_get_send_buf(HID_queue *queue, uint8_t **buf, int *len)

{

if (queue->send_count)

{

queue->send_count--;

*buf = queue->USB_Request[queue->send_idx];

*len = queue->resp_size[queue->send_idx];

queue->send_idx = (queue->send_idx + 1) % HID_PACKET_COUNT;

queue->free_count++;

return (__TRUE);

}

return (__FALSE);

}

 

BOOL HID_queue_execute_buf(HID_queue *queue, const uint8_t *reqbuf, int len, uint8_t **retbuf)

{

uint32_t rsize;

if (queue->free_count > 0)

{

if (len > HID_PACKET_SIZE)

{

len = HID_PACKET_SIZE;

}

queue->free_count--;

memcpy(queue->USB_Request[queue->recv_idx], reqbuf, len);

rsize = HID_ExecuteCommand(reqbuf, queue->USB_Request[queue->recv_idx]);

queue->resp_size[queue->recv_idx] = rsize & 0xFFFF; //get the response size

*retbuf = queue->USB_Request[queue->recv_idx];

queue->recv_idx = (queue->recv_idx + 1) % HID_PACKET_COUNT;

queue->send_count++;

return (__TRUE);

}

return (__FALSE);

}

如上,我们只需要实现修改如下HID_ExecuteCommand可处理我们收到的收据,并且填入我们发送数据出去队列即可发送出去。

本次我们使用HID工具V1.3.3测试我们的HID功能,打开软件如图2所示:


图2 HID工具连接

点击选择HID设备,选择我们MM32 MCU的HID设备,如图3:


图3 HID工具设备选择

点击连接,发送数据,可以看到图4结果,发送两次共128字节,收到两次数据,共128字节。


图4 HID工具数据收发

以上就是MM32 MCU USB的HID功能,下一节我们继续介绍MM32 MCU USB的WINUSB功能。

来源: 灵动MM32MCU

围观 88

最近有部分刚接触MM32 MCU的用户朋友们碰到了MCU无法进行下载的情况,然后跟我们反馈芯片有问题,最后经过技术工程师跟进,其实都是用户程序使用错误或者操作不规范等原因造成的,并非芯片有问题,小编表示很无奈很无辜(T_T)。如果大家有碰到这种情况,请不要着急,我们今天这篇文章专门来讲解如何解决烧写失败的情况及有可能出现该类问题的原因。

MM32无法进行烧写原因有多种情况,我们从硬件和软件两个方面分析:

硬件原因:

1、使用的调试器不支持调试下载MM32 MCU,IAR/KEIL上仿真器选择/配置不正确,MM32 MCU已经获得Segger官方认证。在Segger官网J-Link驱动6.40以上版本支持MM32 MCU系列,MM32 MCU支持如MM32-Link、U-link、J-Link、DAP-Link等使用SWD/JTAG(M3)等方式对内核、外设、FLASH进行访问。

2、MM32 MCU最小系统不完整,复位电路设计错误、MCU供电不正常等也会出现无法识别设备Device等情况。MM32 MCU支持宽电压2.0-5.5v供电,在用户自己设计产品PCB时,建议用户预留VCC、SWDIO、SWCLK、GND、NRST五线下载口方式。大家在进行电路设计时请参考我们demo板进行设计,demo板资料在我们官网链接如下:http://www.mm32mcu.com/getfile.aspx?id=386

3、SWD的两个引脚PA13、PA14引脚虚焊,外部硬件有上拉或下拉,与调试器连接不正确或未连通等情况。

软件原因:

1、程序下载错误,MM32MCU有F、L、SPIN、W、P五大系列,对于不同的MCU我们有不同的库、启动文件与例程,大家下载程序前请查看自己MCU版本型号是否与要下载的程序对应,例如MM3F031C6T6分q版和n版,两个型号由于时钟域配置不同,程序不能直接共用。MM32MCU选型表料在官网链接如下:http://www.mm32mcu.com/getfile.aspx?id=955,库和例程等资料在官网链接如下:http://www.mm32mcu.com/download.aspx?cid=2542

2、MCU处于读/写保护状态,当MCU处于读保护状态时,FLASH空间处于写保护状态,此时无法使用SWD协议对内核、外设、FLASH进行访问,无法进行下载调试。

3、MCU处于低功耗状态,当MCU处于不同模式低功耗状态时,MCU的外设区域会处于断电状态,调试器识别MCU处于断电状态,无法进行下载调试。

4、SWD的两个引脚PA13、PA14被复用为其他功能或通用IO,PA13、PA14被配置为其他工作模式,此时PA13、PA14无法正常工作在SWD模式,调试器无法通过SWD协议进行下载调试。

5、程序下载过程中出错,由于下载速度和调试器版本特性等原因造成下载过程中出错,导致MCU进入HardFault状态(程序跑飞),此时无法进行下载调试。

下面以J-Link为例,列举两种具体表现形式与解决方案:

一、表现形式:下载报错No Cortex-M SW Device Found。此时调试器未识别到MCU(在keil中无法读到芯片ID),无法下载程序。


可能原因:硬件问题:1、2、3;软件问题:1、2、3、4、5;

解决方式:先检查硬件,确保使用的调试器支持仿真调试MM32MCU,查看MM32MCU最小系统是否完整、复位电路(建议电阻上拉100k,接100nF电容到地)与MCU供电是否正常,测量PA13、PA14引脚是否虚焊,有无上拉或下拉,确认与调试器连接正确并联通。

确认硬件无问题后,仿真方式选择SWD、速度设置建议在1M-10M以内,方法1:NRST脚拉低(按住复位键或短接复位电容,此时在KEIL中可读到ID),然后在IAR/KEIL中擦除程序(在keil的flash工具栏下的Erase操作),在IAR/KEIL出现进度条时释放NRST下拉电平,将芯片程序擦除后,重新上电就可以对MCU进行下载调试;方法2:将BOOT0接高,重新断电上电让MCU从SRAM启动,然后将芯片程序擦除,将BOOT0接低,重新上电就可以对MCU进行下载调试。

二、表现形式:下载报错Error:Flash Download failed – “Cortex - M0”。此时调试器能识别到MCU(在keil中可以读到芯片ID),可以擦除程序,无法下载程序。


可能原因:软件问题:1、5;

解决方式:方法1:调整下载速度,速度设置建议在1M-10M以内,可多调节几个速度试下,Download Fuction选择Erase Full Chip;方法2:如果还是无法下载则进行恢复出厂设置操作,资料在官网链接如下:http://www.mm32mcu.com/getfile.aspx?id=772

下面介绍使用MM32-Link的解决方案:

如果手上有MM32-Link则可以直接使用MM32-Link+Program来进行恢复操作,MM32-Link支持包与文档资料链接如下:http://www.mm32mcu.com/getfile.aspx?id=963

使用MM32-Link解决方式操作如下:

1、参照文档连接MM32-Link与MM32MCU,建议使用五线下载方式:VCC、SWDIO、SWCLK、GND、NRST。

2、打开MM32-Link Program,新建workspace,根据芯片型号新建project。

3、确保ICP Program模式下,左下角MM32-Link处圆点为绿色(不为绿色时检查MM32-Link是否连接电脑并正确识别设备,为黄色时双击左下角圆点),芯片连接处圆点为黄色或绿色(为灰色时检查硬件连接并手动对芯片进行一次复位操作,拉低NRST引脚)。

4、点击Erase Chip按钮,等待擦除完成即可重新下载。


以上方式下载均在我们官网例程上进行测试,如果有使用自己新建工程无法下载的情况,请检查自己工程配置与IDE版本,建议使用IAR版本在7.4以上、KEIL版本在5.13及以上,IAR与KEIL上新建MM32 MCU工程方式请参照官网应用文档,在KEIL下新建MM32 MCU工程官网链接如下:http://www.mm32mcu.com/getfile.aspx?id=341,在IAR下新建MM32 MCU工程官网链接如下:http://www.mm32mcu.com/getfile.aspx?id=396

本文转自: 灵动微电MMCU(MindMotion-MMCU),转载此文目的在于传递更多信息,版权归原作者所有。

围观 642

一、WWDG 简介

窗口看门狗通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。除非递减计数器的值在T6 位变成 0前被刷新,看门狗电路在达到预置的时间周期时,会产生一个MCU 复位。在递减计数器达到窗口寄存器数值之前,如果 7位的递减计数器数值(在控制寄存器中)被刷新,那么也将产生一个MCU 复位。这表明递减计数器需要在一个有限的时间窗口中被刷新。

二、WWDG 主要特征

  •   可编程的自由运行递减计数器

  •   条件复位

      - 当递减计数器的值小于 0x40,(若看门狗被启动)则产生复位。
      - 当递减计数器在窗口外被重新装载,(若看门狗被启动)则产生复位。

  •   如果启动了看门狗并且允许中断,当递减计数器等于0x40 时产生早期唤醒中断(EWI),它可以被用于重装载计数器以避免WWDG 复位。

三、WWDG功能描述

如果看门狗被启动(WWDG_CR寄存器中的WDGA 位被置1),并且当 7位(T[6:0])递减计数器从0x40 翻转到0x3F(T6位清零)时,则产生一个复位。如果软件在计数器值大于窗口寄存器中的数值时重新装载计数器,将产生一个复位。

看门狗框图
MM32之窗口看门狗(WWDG)

应用程序在正常运行过程中必须定期地写入WWDG_CR 寄存器以防止MCU 发生复位。只有当计数器值小于窗口寄存器的值时,才能进行写操作。储存在WWDG_CR 寄存器中的数值必须在0xFF 和0xC0 之间:

1、启动看门狗

在系统复位后,看门狗总是处于关闭状态,设置WWDG_CR 寄存器的WDGA 位能够开启看门狗,随后它不能再被关闭,除非发生复位。

2、控制递减计数器

递减计数器处于自由运行状态,即使看门狗被禁止,递减计数器仍继续递减计数。当看门狗被启用时,T6 位必须被设置1,以防止立即产生一个复位。

T[5:0]位包含了看门狗产生复位之前的计时数目;复位前的延时时间在一个最小值和一个最大值之间变化,这是因为写入WWDG_CR寄存器时,预分频值是未知的。

配置寄存器(WWDG_CFR)中包含窗口的上限值:要避免产生复位,递减计数器必须在其值小于窗口寄存器的数值并且大于0x3F 时被重新装载,上图描述了窗口寄存器的工作过程。

另一个重装载计数器的方法是利用早期唤醒中断(EWI)。设置WWDG_CFR 寄存器中的WEI 位开启该中断。当递减计数器到达0x40 时,则产生此中断,相应的中断服务程序(ISR)可以用来加载计数器以防止WWDG 复位。

在WWDG_SR 寄存器中写0可以清除该中断。注:T6 位可以被用来产生一个软件复位(WDGA 位被置位,T6 位清零)

四、如何编写看门狗超时程序

下图显示了装载到看门狗计数器(CNT)中的 6 位计数值和看门狗的延迟时间之间的线性关系(以 mS为单位)。此图可用来做为快速计算的参考,而未将时间的偏差考虑在内。如果需要更高的精度,可以使用下图提供的计算公式。

当写入 WWDG_CR 寄存器时,始终置 T6 位为1以避免立即产生一个复位。

窗口看门狗时序图
MM32之窗口看门狗(WWDG)

五、设置WWDG实验分析

  •   实验内容简介
环境MM32L073PF Miniboard、MDK,设置WWDG,通过观察喂狗与不喂狗的复位现象。

  •   实验代码分析
首先看main函数
MM32之窗口看门狗(WWDG)
先初始化一个串口,方便我们观察复位现象,然后通过函数Wwdg_reset_ON 进行设置WWDG。最后在循环里通过WWDG_SetCounter函数不停的喂狗。
MM32之窗口看门狗(WWDG)
在函数Wwdg_reset_ON中设置窗口值,和计数器的初值。

  •   实验现象
1、当注释掉喂狗函数时,通过串口在不断打印可以看出,MCU一直在复位。
2、当不注释喂狗函数时,串口只打印一次,MCU没有复位。

来源:灵动微电子

围观 417

一、MM32 BKP简介及功能描述

在使用MCU的过程中,当系统在待机模式下被唤醒,或者系统复位或电源复位时,会导致我们在RAM中的一些重要数据丢失,此时该怎么处理呢?MM32为我们提供了备份寄存器(BKP), 备份寄存器是 10 个 16 位的寄存器,可用来存储 20 个字节的用户应用程序数据。他们处在备份域里,当 1.5V 电源被切断,他们仍然由 VDD维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
复位后,对备份寄存器的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。通过设置寄存器 RCC_APB1ENR 的 PWREN 位来打开电源和后备接口的时钟,可以用半字(16 位)或字(32 位)的方式操作这些外设寄存器。

二、BKP实验分析

  •   实验内容简介

以MM32L073PF为例,往BKP写数据,如果成功则LED慢闪,如果失败则LED快闪。

  •   实验代码分析

首先我们来看main函数:

MM32 MCU之BKP备份寄存器

1、 初始化delay_init, LED_Init 函数;
2、 通过函数BKP_DATA往BKP写数据,成功则返回0;
3、 写入成功则LED慢闪,写入失败则快闪。
MM32 MCU之BKP备份寄存器

在BKP_DATA函数中打开PWR时钟,使能BKP, 先通过WriteToBackupReg函数写入数据,在通过CheckBackupReg读取写入的数据是否正确。

两个函数代码如下:

MM32 MCU之BKP备份寄存器

MM32 MCU之BKP备份寄存器

  •   实验现象

向MM32 Miniboard里下载好程序后,启动板子,发现LED快闪,说明写入成功。

转自:灵动微电子

围观 403

MM32L0系列MCU内嵌两个通用比较器 COMP1 和 COMP2,为通用的可编程电压比较器,可独立使用(适用所有终端上的 I/O 口),也可与定时器结合使用, 支持两个独立的比较器。它们可用于多种功能,包括:由模拟信号触发低功耗模式唤醒事件调节模拟信号与 DAC 和定时器输出的 PWM 相结合,组成逐周期的电流控制回路。

比较器框图
MM32 定时器捕获比较器输出

COMPx信号宽度需要测量的输入信号连接到 PA0-PA7。参考信号可通过以下方式供电:

● 内部参考电压(VREFINT、 3/4 VREFINT、 1/2 VREFINT 或 1/4 VREFINT)

● PA4\ PA5\ PA0\ PA7的外部引脚(COMP1),PA4\ PA5\ PA2\ PA7的外部引脚(COMP2)COMPx 输出重映射通过COMP1_CSR[13:10],COMP2_CSR[13:10]比较器 x 输出选择位实现。

在 MM32L0系列MCU中,COMP1 \COMP2 输出可以重映射到内置定时器 TIM1 的 BKIN(刹车输入)和IC4上。重映射 COMP21\COMP2 输出时,可以测量具有特定高/低电平的信号宽度或频率(例如,移位信号)。

定时器输入捕获通道应配置为同时在上升沿和下降沿保存定时计数器值。当输入信号高于参考电压时, COMPx 输出处于高电平,此时会在定时器输入捕获上生成一个上升沿。当输入信号低于参考电压时, COMPx输出处于低电平,此时会生成一个下降沿。两个连续事件之间经过的时间(下降沿到上升沿或者上升沿到下降沿)表示脉冲宽度。因此,只需对计数器值做减法即可测量脉冲宽度。

COMPx输出重映射到定时器
MM32 定时器捕获比较器输出

1、将定时器输入捕获通道配置为仅在上升沿或下降沿保存计数器值,即可获得信号频率。

2、参考电压可以使用内部参考电压(0.4v、0.6v、0.9v和1.2v),也可以使用外部 GPIO 接到某一个电压值(0-5v)作为反相输入。

脉冲宽度测量应用:

将 COMPx 输出连接到定时器的输入捕获通道,则可以测量电容值,电容测量过程包括通过电阻对电容进行充电和放电。测量原理基于电阻、电容 (RC) 网络充电时间的测量过程,方法如下:

● 测量充电时间
● 已知充电电阻 (R)
● 可计算未知的电容 (C)

测量电容的电路图
MM32 定时器捕获比较器输出

通过 PWM 模式下配置的定时器输出比较通道 (TIMx OC),可确保 RC 网络周期性的充电和放电过程。

输入电压连接到 COMPx 同相输入,而阈值连接到 COMPx 反相输入。当输入电压超过阈值时, COMPx 输出切换为高电平,同时发生保存计数器值的捕获事件。

MM32 定时器捕获比较器输出

充电函数如下所示:

Input voltage =VDD(1- exp(- t /T))

其中:

● VDD 为正电源电压
● t 为时间
● T 为 RC 常数

根据上述公式可以推算:

C = -t / ( R x In( 1-threshold /(VDD) ))

比较器配置程序:

void Comp_Config(uint32_t COMP_Selection_COMPx)
{
COMP_InitTypeDef COMP_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_COMP, ENABLE);//开比较器时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);//开GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_7);// 复用AF7,比较器输出

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//内部参考电压1.2v(可以内部基准设置0.4v、0.6v、0.9v和1.2v)
COMP_InitStructure.COMP_InvertingInput = COMP_InvertingInput_VREFINT;
//PA5为正相输入引脚
COMP_InitStructure.COMP_NonInvertingInput = COMP_NonInvertingInput_IO6;
//输出重印射到TIM1的输入捕获通道4
COMP_InitStructure.COMP_Output = COMP_Output_TIM1IC1;
//比较器不上锁
COMP_InitStructure.COMP_BlankingSrce = COMP_BlankingSrce_None;
//比较器输出极性:同相输出
COMP_InitStructure.COMP_OutputPol = COMP_OutputPol_NonInverted;
//比较器迟滞电压:0mV(可设置迟滞电压0-27mV)
COMP_InitStructure.COMP_Hysteresis = COMP_Hysteresis_No;
//比较器模式:中等速率(可设置极低速率、低速率、中等速率、高速率)
COMP_InitStructure.COMP_Mode = COMP_Mode_MediumSpeed; COMP_Init(COMP_Selection_COMPx, &COMP_InitStructure);
//使能比较器
COMP_Cmd(COMP_Selection_COMPx, ENABLE);
}

定时器TIM1配置程序:

void TIM1_PWMINPUT_INIT(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM1_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//TIM1时钟使能
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);GPIOA时钟使能

TIM_TimeBaseStructure.TIM_Period = arr; //设置重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0;// TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

NVIC_InitStructure.NVIC_IRQChannel = TIM1_IRQn; //配置中断优先级
NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

TIM1_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1映射到TI1 上
TIM1_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM1_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM1_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //不分频
TIM1_ICInitStructure.TIM_ICFilter = 0x0; //不滤波
TIM_PWMIConfig(TIM1, &TIM1_ICInitStructure);

TIM_ITConfig(TIM1, TIM_IT_CC1|TIM_IT_Update, ENABLE); //使能捕获和更新中断
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1|TIM_IT_Update); //清中断
TIM_Cmd(TIM1, ENABLE); //定时器使能
}

定时器TIM1中断服务函数:

void TIM1_CC_IRQHandler (void)
{
if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET)
{
period = TIM_GetCapture1(TIM1);
duty = TIM_GetCapture2(TIM1);
CollectFlag = 1;
}
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1|TIM_IT_Update);
}

小结:

1、为保证恶劣环境下比较器设置不能被无效寄存器访问或者程序计数器破坏所改变,比较器控制和状态寄存器可以设为写保护(只读),一旦设置完成, LOCK 位必须设为 1,这导致整个 COMPx_CSR 寄存器变成只读,包括 LOCK 位在内。写保护只能被 MCU 复位所清除。

2、在某些用电池供电的应用中,MCU需要在环境明亮时上电;其它情况下,微控制器必须保持断电状态。对于此类应用,可以使用电阻随光强度变化的光敏电阻 (LDR) 来控制MCU的状态。使用 LDR 传感器时,MCU可根据 LDR 电阻提供的电压切换到低功耗模式或退出低功耗模式。MM32L0系列MCU 的比较器COMP1和COMP2的可在内部将该输出分别连接到 EXTI 线19和20。

转自:灵动微电子

围观 472

一、IWDG简介

MM32 MCU内置两个看门狗,提供了更高的安全性、时间的精确性和使用的灵活性。两个看门设备(独立看门狗和窗口看门狗)可用来检测和解决由软件错误引起的故障;当计数器达到给定的超时值时,触发一个中断(仅适用于窗口型看门狗)或产生系统复位。

独立看门狗(IWDG)由专门的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效。窗口看门狗从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作。

IWDG最适合应用于那些需要看门狗作为一个正在主程序外,能够完全独立工作,并且对时间精度要求低的场合。WWDG最适合那些要求看门狗在精确计时窗口起作用的应用程序。

二、IWDG主要性能

自由运行的递减计数器

时钟由独立的振荡器提供(可在停止和待机模式下工作)
看门狗被激活后,则在计数器计数至 0x0000 时产生复位。

三、IWDG 功能描述

下图为独立看门狗模块的功能框图。

在键寄存器(IWDG_KR)中写入 0xCCCC。开始启动独立看门狗;此时计数器开始从其复位值 0xFFF递减计数。当计数器计数到末尾 0x000 时,会产生一个复位信号(IWGD_RESET)。

无论何时,只要在键寄存器 IWDG_KR 中写入 0xAAAA, IWDG_RLR 中的值就会被重新加载到计数器,从而避免产生看门狗复位。

独立看门狗框图

独立看门狗框图

看门狗超时时间(40KHz 的输入时钟(LSI))

独立看门狗框图

此外,即使振荡器的频率是精确的,确切的时序仍然依赖于 APB 接口时钟与振荡器时钟之间的相位差,因此总会有一个完整的振荡器周期是不确定的。

IWDG_PR 和 IWDG_RLR 寄存器具有写保护功能。要修改这两个寄存器的值,必须先向 IWDG_KR 寄存器中写入 0x5555。以不同的值写入这个寄存器将会打乱操作顺序,寄存器将重新被保护。重装载操作(即写入 0xAAAA)也会启动写保护功能。

状态寄存器指示预分频值和递减计数器是否正在被更新。

当微控制器进入调试模式时(CPU 核心停止),根据调试模块中的 DBG_IWDG_STOP 配置位的状态,IWDG 的计数器能够继续工作或停止。

四、独立看门狗代码配置

MM32L073系列独立看门狗初始化和启动设置:
MM32 独立看门狗(IWDG)
MM32 独立看门狗(IWDG)
MM32 独立看门狗(IWDG)
喂狗函数:
MM32 独立看门狗(IWDG)

五、实验实现窗口看门狗

测试方法:在main函数中先进行串口打印后初始和打开IWDG,然后让程序进入while(1)的死循环。

测试代码main函数如下:
MM32 独立看门狗(IWDG)

测试结果:
1、当我们在while(1)里进行喂狗操作时,串口只会进行一次打印。。
2、当我们在while(1)里不进行喂狗操作时,串口每隔大约看门狗复位的时间1.6s进行一次打印。

实验总结:实现了看门狗复位的功能。

转自:灵动微电子

围观 339

一、应用简介

在实际应用的一些产品上可能需要使用到对脉冲的个数进行计数,本文小编将给大家介绍如何使用TIM来做一个脉冲计数的功能。在MM32 TIM中正好有一个外部时钟模式1可以来帮助我们实现这个功能。

二、外部时钟源模式1描述

首先我们来了解一下外部时钟源模式1,当 TIMx_SMCR 寄存器的 SMS = 111 时,此模式被选中。计数器可以在选定输入端的每个上升沿或下降沿计数。下图是TI2外部时钟连接例子。

如何使用定时器做脉冲计数

例如,要配置向上计数器在 T12 输入端的上升沿计数,使用下列步骤:
1. 配置 TIMx_CCMR1 寄存器 CC2S = 01,配置通道 2 检测 TI2 输入的上升沿。
2. 配置 TIMx_CCMR1 寄存 器的 IC2F[3: 0],选择输入滤波器带宽(如果不需要滤波器,保持 IC2F= 0000) 。
3. 配置 TIMx_CCER 寄存器的 CC2P = 0,选定上升沿极性。
4. 配置 TIMx_SMCR 寄存器的 SMS = 111,选择定时器外部时钟模式 1。
5. 配置 TIMx_SMCR 寄存器中的 TS = 110,选定 TI2 作为触发输入源。
6. 设置 TIMx_CR1 寄存器的 CEN = 1,启动计数器。

注:捕获预分频器不用作触发,所以不需要对它进行配置。

当上升沿出现在 TI2,计数器计数一次,且 TIF 标志被设置。

在 TI2 的上升沿和计数器实际时钟之间的延时取决于TI2输入端的重新同步电路。

外部时钟模式1下的控制电路

如何使用定时器做脉冲计数

三、定时器代码配置

如何使用定时器做脉冲计数

四、实验结果

实验信号发生器从PA1输入1HZ的方波,进入KEIL的调试模式观察TIM->CNT的变化,TIM的计数器以每秒加1的速度向上计数,停止输入方波,计数器停止计数。说明我们实现了使用TIM进行计数的功能

转自:灵动MM32

围观 405

针对电动马达方案中需要使用到六步PWM输出功能,本篇文章将向大家介绍如何使用MM32L0系列MCU实现六步PWM输出功能。

MM32高级控制定时器TIM1有互补输出的功能,我们便可以利用定时器 TIM1 来产生 3 对 6 路的互补 PWM 输出。MM32高级控制定时器TIM1产生六步PWM输出,用于驱动三相电机,对应着直流无刷电机的六步换相。

六步 PWM 产生:当在一个通道上应用了互补输出时, OCxM、CCxE 和 CCxNE位的预载位有效,这些预装载位被传送到影子寄存器,因此可以预先设置好下一步的配置,并在同一时间更改所有通道的配置。COM 事件可以通过硬件(在 TRGI的上升沿) 设置或者软件修改TIM1_EGR 寄存器的 COM 位来产生。

当 COM 事件发生时会设置一个标志位(TIM1_SR 寄存器中的 COMIF 位),这时如果已设置了TIM1_DIER 寄存器的 COMIE 位,则产生一个中断;如果已设置了 TIMx_DIER寄存器的COMDE位,则产生一个DMA请求。

下图显示当发生 COM 事件时,三种不同配置下OCx和OCxN 输出。

MM32 六步PWM输出

在本次实验中主要教大家如何配置PWM的输出状态以及输出有效电平设置,在主函数的循环中更新PWM状态输出,将不使用中断方式,用户在实际电机配置程序中可以直接采用该配置方式移植到TIM1_BRK_UP_TRG_COM_IRQHandler函数中。

程序配置:

MM32 六步PWM输出

1) 开启定时器TIM1的时钟
2) 初始化 TIM1,设置 TIM1 的 ARR 和 PSC,向上计数模式
3) 设置BDTR,使能刹车输入信号,高电平有效
4) 设置 TIM1_CH1/CH1N,TIM1_CH2/CH2N,TIM1_CH3/CH3N的 PWM 模式,PWM 模式2,使能 TIM1 的 CHx 输出
5) 使能TIM1_CR1的自动重装载预装载允许位
6) 使能定时器TIM1
MM32 六步PWM输出

main函数配置流程:
1) systick延时函数初始化
2) GPIO口配置,PA8/PB13,PA9/PB14,PA10/PB15,分别为TIM1的三组互补通道输出:CH1/CH1N,CH2/CH2N,CH3/CH3N,复用推挽输出,最大输出速度50MHz, 除此之外,还有一个引脚可以配置也可以不配置,那就是TIM1_BKIN对应的引脚PB12,TIM1_BKIN的功能是检测故障,如果当PB12检测到高电平(取决于刹车有效电平的设置)时,就表示检测到故障,然后它会自动关闭定时器。
3) 定时器TIM1初始化及输出配置
4) PWM输出模式配置,在我配置的程序中大家可以看到对TIM1_CCMR1\TIM1_CCMR2\TIM1_CCER三个寄存器进行操作即可实现需要的功能

实验结果:

MM32 六步PWM输出

从逻辑分析仪抓的波形可以看到在6个通道中,如果一个通道处在PWM输出模式,另外的5个通道处在关闭状态,依次轮询该过程。

需要弄清楚我对上述三个寄存器做了什么操作需要参考UM_MM32L0xx文档的第13.4.7章节、13.4.8章节、13.4.9章节三个章节寄存器

MM32 六步PWM输出

如上图所示:位3是输出比较1预装载使能位,TIMx_CCR1的预装载值在更新事件到来时加载到当前寄存器,该位需要置1,位6:4是PWM输出比较模式配置,如果CCMR1的输出比较1配置0x48表示强制为无效电平,0x58表示强制为有效电平,0x68表示PWM模式1。

定时器的配置模式如上所示,但是输出到GPIO口的电平状态还需要查表34,根据表中的定义配置所需要的电平状态。

MM32 六步PWM输出

用户在配置电机所需要的PWM功能时,只需要根据对TIM1_CCMR1 \ TIM1_CCMR2 \ TIM1_CCER 三个寄存器进行操作,如果对寄存器的操作值不是很清楚,建议大家在调试模式下,直接通过修改该三个寄存器的值,然后查看对应的GPIO得状态变化,然后记录下来,在程序中将测试值写入到相对应的寄存器中,重新下载程序到MM32L0系列 MCU中观察对应的GPIO的电平状态。

转自: 灵动微电子

围观 469

页面

订阅 RSS - MM32