MM32

一、比较器简介

在实际应用过程中有时候我们需要去判断两个变化的电压大小,在不同变化时需要做出不同的反应,这时候我们就可以用到比较器。MM32系列芯片内嵌两个通用比较器COMP1和COMP2, 比较器为通用的可编程电压比较器,支持两个独立的比较器。可独立使用(适合所有终端上的I/O),也可与定时器结合使用。它们可用于多种功能,包括:
• 由模拟信号触发低功耗模式唤醒事件
• 调节模拟信号
• 与 DAC 和定时器输出的 PWM 相结合,组成逐周期的电流控制回路

本文主要介绍一下如何通过比较器产生中断。

二、比较器功能描述

1、比较器输入输出介绍:

比较器框图如下,以COMP1为例,从图中可以看出PA0 – PA7口可连接到比较器的正向输入端,PA4 - PA7口及内部参考电压和三个等分电压值(1/4, 1/2, 3/4)可连接到比较器的反向输入端。比较器输入的 I/O 引脚必须在 GPIO 寄存器中设置为模拟模式。输出端可以重定向到一个 I/O 端口或多个定时器输入端,从而触发不同事件。

MM32如何使用比较器产生中断

2、 比较器时钟:

COMP 时钟控制器提供的时钟与 PCLK 同步(APB2 时钟)。在使用比较器之前,要先使能 RCC 控制器中的时钟使能控制位。

3、 比较器的中断:

比较器的输出可以内部连接到外部中断和事件控制器。每个比较器有自己的 EXTI 信号,能产生中断或事件。COMP1对应外部中断线19,COMP2对应外部中断线20。

4、 功耗模式:

在具体应用中可以通过调整比较器功耗和响应时间得到最优的结果。

COMPx_CSR 寄存器的 MODE[1: 0]位有下面几种设置:
• 00:高速/高功耗
• 01:中速/中等功耗
• 10:低速/低功耗
• 11:极低速/极低功耗

5、 比较器锁定机制:

比较器能用于安全的用途,比如过流或者过热保护。在某些特定的安全需求的应用中,有必要保证比较器设置不能被无效寄存器访问或者程序计数器破坏所改变。为了这个目的,比较器控制和状态寄存器可以设为写保护(只读)。一旦设置完成, LOCK 位必须设为 1,这导致整个 COMPx_CSR 寄存器变成只读,包括 LOCK 位在内。写保护只能被 MCU 复位所清除。

6、 迟滞现象:

比较器的可配置迟滞电压能防止无效的输出变化产生的噪声信号。在不需要强制迟滞电压的情况下迟滞现象可以被禁止。通过配置COMPx_CSR 寄存器 HYST[1:0]可以设置比较器迟滞电压。

MM32如何使用比较器产生中断

比较器的迟滞现象如下图:
MM32如何使用比较器产生中断

三、比较器触发中断实验主要代码分析

本实验以MM32L073为例,比较器配置代码如下图:

MM32如何使用比较器产生中断

中断配置及中断服务子函数如下图所示:
MM32如何使用比较器产生中断

四、实验结果

理论分析:使用信号发生器通过PA1输入频率为1Hz,高电平1.2V,低电平0V的方波,在输入信号由低电平变化为高电平(大于1/4Vrefint)时比较器会产生一个上升沿信号输出高电平,在输入信号由高电平变化为低电平(小于/4Vrefint)时比较器会产生一个下降沿输出低电平,由于设置的外部中断为上升下降沿触发,所以Led会以每0.5S翻转一次。

实验现象:LED以0.5s闪烁,可以通过示波器观察时间,与理论分析符合。

转自: 灵动MM32

围观 477

有客户需要用到MM32L073,需要通过IAP进行固件升级,在FLASH里面要烧录两份代码:一个Boot loader,一个用户应用程序。在开发应用程序时,使用中断函数不能相应中断。

在开发IAP的用户应用程序时,必须得重新映射中断向量表,中断向量表即某个中断服务程序的入口地址的集合。

在Cortex-M3内核的MCU上可以通过设置SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;该寄存器的值来实现中断向量表的重定义。但用户反馈在MM32L0xx系列以Cortex-M0为内核的单片机中却怎么也找不到这个设置中断向量表的寄存器,用户可以通过以下方法来实现中断向量表重定义。

实现方法基本思想:

1、将中断向量表放入到RAM的起始地址(只需要在应用程序中保留RAM起始地址的0x100大小不使用即可)。
2、在bootload中将应用程序的中断向量表从Flash中拷贝到RAM中。
3、设置MM32L073中断向量表位于RAM中。

MM32 IAP中断向量表重定义

0x20000000是SRAM的起始地址,0x08010000是应用程序的起址地址,从0x08010000开始的字节,存放应用程序的中断向量表。

应用程序代码及自身中断矢量表存放在离0X08000000加某个地址偏移量的地方,即从0x08000000+偏移量的地址开始存放APP代码及中断矢量。这个偏移量要大于IAP的程序空间,防止程序覆盖重定义的中断向量表的数据。在本程序中的偏移量为0x10000,即APP程序的起始地址为0x08010000。

当应用程序发生中断时,内核就从地址0x00处的向量表取相应中断的入口地址,即相当于从0x20000000处的向量表取中断入口地址,当然也相当于从0x08010000处的向量表取中断入口地址,然后去执行相应中断程序。

可以根据startup_MM32L0xx.s的中断函数的入口地址数计算需要预留的空间大小。

MM32 IAP中断向量表重定义

如上图所示,每一个DCD都代表一个中断向量。
例如:
DCD WWDG_IRQHandler ; Window Watchdog
“WWDG_IRQHandler "其实就是WWDG中断服务函数WWDG_IRQHandler的入口地址。

中断向量的集合定义了一张中断向量表,这张表包括48个元素,每个元素是一个长度为4字节的地址。除了第一个地址是SP(堆栈指针)外,其它的地址都是某个中断服务程序的入口地址。中断向量表的所占内存大小为48*4=180(0xC0)个字节。

转自: 灵动MM32

围观 967

有客户需要用到高精度的DAC模块,MM32L0系列产品内部没有集成DAC模块,考虑到外接DAC芯片会增加成本,所以在本实验中将为大家介绍使用PWM输出,经过简单的变换电路即可实现DAC,这将大量降低电子设备的成本、减少体积,并提高精度。本实验在PWM到DAC转换关系的理论分析基础上,设计出输出为0~5V电压的DAC。

MM32L0系列产品包含1个高级控制定时器、5个通用定时器(1个32 位定时器和5个16 位定时器),以及 2个看门狗定时器和1个系统嘀嗒定时器。

每个定时器都有 PWM 输出或单脉冲模式输出,所以MM32L0系列产品任意一款型号都可以用PWM做DAC输出功能。

PWM波形的分段函数:

MM32 基于PWM做DAC输出设计

其中:k为谐波次数,N是PWM波一个周期的计数脉冲个数,T是单片机中计数脉冲的基本周期,即MCU每隔T时间记一次数(计数器的值增加或者减少1),t为时间, n是PWM波一个周期中高电平的计数脉冲个数,VH和VL分别是PWM波中高低电平的电压值。

PWM的高低电平分别为VH和VL,理想的情况VL等于0,但是实际中一般不等于0,所以用户在处理PWM的VL时需注意,出现较大误差一般都是因为这个地方。

将上述函数展开成傅里叶级数得到:

MM32 基于PWM做DAC输出设计

从上式可以看出,上式中第1个方括弧为直流分量,第2项为1次谐波分量,第3项为大于1次的高次谐波分量。上式中的直流分量与n成线性关系,并随着n从0到N,直流分量从VL到VL+VH之间变化,这正是电压输出的DAC所需要的。因此,如果能把式中除直流分量的谐波过滤掉,则可以得到从PWM波到电压输出DAC的转换,即:PWM波可以通过一个低通滤波器进行解调。式中的第2项的幅度和相角与n有关,频率为1/(NT),该频率是设计低通滤波器的依据。如果能把1次谐波很好过滤掉,则高次谐波就应该基本不存在了。

在DAC的应用中,分辨率是一个很重要的参数,傅里叶级数公式中的分辨率计算直接与N和n的可能变化有关:

MM32 基于PWM做DAC输出设计

从上式中可看出:
N越大DAC的分辨率越高,但是NT也越大,即 PWM的周期或者傅里叶级数公式中的1次谐波周期也越大,相当于1次谐波的频率也越低,需要截止频率很低的低通滤波器,DAC输出的滞后也将增加。为了使T减少,即减少单片机的计数脉冲宽度(这往往需要提高单片机的工作频率),达到不降低1次谐波频率的前提下提高精度。

MM32L0系列产品最高工作频率可达 48MHz,TIM2是32位的定时器,PWM频率计算公式:
Fpwm = 48M / ((arr+1)*(psc+1))(单位:Hz)
公式中psc就是分频系数,arr就是计数值。预分频器可以将计数器的时钟频率按 1 到 65536 之间的任意值分频。计数值可以从1-4294967295(2的32次方减1)中任意选取。

本次实验采用两阶RC滤波,使用两个电阻和两个电容组成一个具有DAC功能的引脚,PB10是32位的定时器TIM2_CH3通道,PA3和PA4两个通道分别去采集1阶RC滤波和2阶RC滤波后的电压值。

MM32 基于PWM做DAC输出设计

R29和C10的具体参数可根据傅里叶级数公式的第2部分的一次谐波频率来选择,实际应用中一般选择阻容滤波器的截止频率为傅里叶级数公式的基波频率的1/4左右。

RC滤波器的截止频率计算公式:

f = 1/(2πRC)

滤波器是频率选择电路,只允许输入信号中的某些频率成分通过,而阻止其他频率成分到达输出端。在电路中需要考虑到芯片引脚输出端到RC滤波电路之间的存在阻值等问题,上图中的电阻和电容值需要根据实际情况计算调整。

PB10引脚能将不同占空比的PWM信号转换为不同电压值的模拟信号。为了能更准确的获取DAC转换值,电路中还使用了2个ADC通道用来检测DAC转换值。在转换过程中PWM信号频率越快DAC输出的电压值越稳定,PWM位数越高DAC输出的电压值精度越高,32位PWM比16位PWM精度高。

实验程序:

TIM2定时器配置:
u32 OutCnt;

void InitTIM2_PWM(u16 t1, u16 t2, u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //TIM2_CH3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //TIM2_CH4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource10,GPIO_AF_2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11,GPIO_AF_2);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_Period = t1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_OC4Init(TIM2, &TIM_OCInitStructure);

TIM_OCInitStructure.TIM_Pulse = t2;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);

TIM_OCInitStructure.TIM_Pulse = t2;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);

TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}

获取 SysTick 计数器的值:
u32 GetSysTickCount(void)
{
return SysTick_Count;
}

设置 SysTick 重装载值:
void SysTick_Configuration(void)
{
SysTick_Config(48000);
}

SysTick中断配置:
u32 pwm = 150;
void SysTick_Handler(void)
{
if (SysTick_Count ++ > 500)
{
SysTick_Count = 0;
InitTIM2_PWM(1024, pwm, 1);
}
}

主函数:
int main(void)
{
SystemInit();
SysTick_Count = 0;
SysTick_Configuration();
while(1)
{
}
}

操作方法:按照上述硬件搭建实验环境后,上电接上调试器,进入Debug状态,在IAR的Live watch窗口修改pwm值可以实现占空比可调。

实验结果:

MM32 基于PWM做DAC输出设计MM32 基于PWM做DAC输出设计

根据实验现象:
从PWM到DAC输出的信号处理有许多电路实现方法,上述电路实现方法DAC输出的负载能力比较差,适合具有高输入阻抗的后续电路连接,对精度和负载能力要求较高的场合,建议增加基准电压、负载驱动等电路。在MCU的应用中还可以通过软件的方法进行精度调整和误差的进一步校正。

PWM 外设结合本电路所实现DAC 有非常好的差分非线性(DNL)、线性度(INL),8位分辨率的情况下,PWM 频率为50KHz,实测精度在 0.5LSB 以内,适合于输出低频、高精度的模拟信号。

转自: 灵动微电子

围观 780

呼吸灯,就是指电子产品上的LED灯的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减,有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的指示灯中。在使用MCU开发相关的应用产品中也可以加入呼吸灯功能,增强用户的体验感,在本实验中将介绍如何使用MM32L0系列产品芯片做呼吸灯功能。

在平时应用中可以知道,MCU的GPIO输出高低电平变化可以实现LED灯的亮、灭两个过程,如果GPIO的电平一直维持高电平或者低电平,LED灯就处于长亮或长灭的状态,呼吸灯就是通过较高频率的电平变化来实现亮灭的切换,由于人的视觉暂留效应,肉眼无法迅速捕捉快速亮灭变化的过程,所以在视觉中一直出现一直亮或者灭的状态,通过调整占空比可以控制LED灯的亮度,给人视觉上一种灯光由暗到亮逐渐增强,然后又由亮到暗逐渐衰减。

正常的成年人的吸气呼气时间整个过程持续大约3秒时间,即吸气时间(LED灯亮度逐渐变亮)时间为1.5S,吸、呼气时间(LED灯亮度逐渐变暗)时间为1.5S。

亮度随着时间逐渐变强再衰减,可以用两种算数方式实现:半周期的正弦函数曲线和指数上升曲线及对称的下降沿曲线。

指数方式曲线图
指数方式曲线图

要控制 LED 灯达到呼吸灯的效果,实际上就是要控制 LED 灯的亮度拟合呼吸特性曲线,在本次实验中,将采用指数上升曲线及对称的下降沿曲线方式,用户如果对正弦方式感兴趣也可以进行尝试。在本次实验中,我们使用MM32L0xx输出较高频率的PWM信号,通过调制信号的占空比,控制LED灯的亮度。

生成指数方式的曲线图主要因素:
TIMPeriod:定时器的计数周期,它的值必须与PWM 表中的极大值相等(应用中赋值需要减 1),而 PWM表的极大值决定了控制的分辨率。例如极大值为 10时,PWM 占空比只有10个等级,精确到0.1,当极大值为1000 时,PWM 占空比1000个等级,精确到0.001。在本次实验中设置定时器的计数周期值为255+1,即PWM表中的极大值也是256。

TIM_Prescaler:定时器时钟分频因子,它控制定时器计数器 CNT计数加1所需要的时间,它的值太大会导致输出的单个PWM波周期过长,影响控制的动态特性。如控制LED灯时,该值太大会导致LED灯开关时间变长,闪烁明显。一般来说,该值越小越好。在本次实验中设置定时器时钟分频因子为1757+1,即对时钟1758分频。

PWM 表的点数:PWM表的点数即对拟合曲线的采样点数,即把LED灯的亮度分为0-255个等级,采样点越多,能更好地还原拟合曲线,采样点太少,可能会导致失真。在本次实验中设置PWM 表的点数为40。

Period_class:周期倍数,即 PWM 表中每个元素的循环次数,它影响拟合曲线的周期。
在本次实验中设置设置周期倍数为8。

本次实验程序直接从MM32L073的定时器PWM输出的例程的基础上修改得来,TIM3配置成向上计数,PWM通道输出也被配置成当计数器 CNT 的值小于输出比较寄存CCR1的值时,PWM通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换CCR1 比较寄存器的值。

程序配置如下:
TIM3初始化配置
//LED亮度等级列表
uint8_t PWM_Wave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,107,143,191,255,255
,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};
TIM3的初始化、中断及PWM输出配置:
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能TIM3时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);//使能GPIOB时钟

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

NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断源
NVIC_InitStructure.NVIC_IRQChannelPriority = 0x01; //设置中断优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器

TIM_TimeBaseStructure.TIM_Period = arr;//设置自动装载寄存器值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=0; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//设置计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化TIM3寄存器

TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置PWM模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置初始脉冲宽度为0
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//小于CCR1值为低电平
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //初始化TIM3_OC1寄存器

TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //预装载使能

TIM_ARRPreloadConfig(TIM3, ENABLE); //使能 TIM 重载寄存器 ARR
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //使能TIM3的更新中断

TIM_Cmd(TIM3, ENABLE);//使能TIM3
}
中断服务函数:
void TIM3_IRQHandler(void)
{
static uint8_t a = 0;//PWM表的成员数,用于PWM查表
static uint8_t b = 0;//计算周期数
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//判断更新中断标志位
{
b++;
if(b >= 8) //周期倍数
{
TIM3->CCR1 = PWM_Wave [a]; //修改定时器比较寄存器值
a++;//查表指向下一个成员变量
if( a >= 40) //查表查到最后一位,重新指向表头
{
a=0;
}
b=0; //重置周期计数标志
}
TIM_ClearITPendingBit (TIM3, TIM_IT_Update);
}
}
主程序:
int main(void)
{
TIM3_PWM_Init(255,1757);
while(1)
{
}
}

本次实验配置拟合曲线周期计算:
TIMPeriod=255+1;
TIM_Prescaler=1757+1;
PWM 表的点数(a)=40;
Fpwm = 48M / ((arr+1)*(psc+1))(单位:Hz)
定时器 update 事件周期,即定时器中断周期:t1= 1/ Fpwm = 9376us
每个 PWM 点的时间:t2= t1*8= 75008us
遍历PWM表的周期时间为:t3=t2*40= 3000320us
通过公式的计算可知本工程的配置可使得输出的拟合曲线周期约等于3秒,符合成年人的吸气呼气时间整个过程持续大约3秒时间。

利用GPIO模拟PWM波形设计呼吸灯功能:
void LedOnOff(uint16_t t,uint16_t i)
{
LED1_ON();
delay_us(i);
LED1_OFF();
delay_us(t-i);
}
int main(void)
{
int i,j;
LED_Init();
TIM3_PWM_Init(255,1757);
while(1)
{
for(i=0;i for(j=0;j {
LedOnOff(100,i);
}
}
for(i=100;i>0;i--) {
for(j=0;j {
LedOnOff(100,i);
}
}
}
}

使用MM32L0系列MCU上述两种方式都可以实现呼吸灯功能,两种方法GPIO分别是PB4和PB5,download到MiniBoard中,可以对比测试两种实现方法的差异性,选择合适的呼吸灯实现方式。

来源: 灵动MM32

围观 734

1、定时器同步

在MM32L073一个定时器有4 通道 PWM 输出,有客户在应用中需要使用两个定时器控制6路PWM输出,为了使两个定时器的PWM输出相同的波形,所以需要两个定时器实现同步功能。

所有 TIMx 定时器在内部相连,用于定时器同步或链接。当一个定时器处于主模式时,它可以对另一个处于从模式的定时器的计数器进行复位、启动、停止或提供时钟等操作。

MM32L073的每个定时器都可以由另一个定时器触发启动定时器一般是通过软件设置而启动,MM32L073的每个定时器也可以通过外部信号触发而启动,还可以通过另外一个定时器的某一个条件被触发而启动。这里所谓某一个条件可以是定时到时、定时器超时、比较成功等许多条件。这种通过一个定时器触发另一个定时器的工作方式称为定时器的同步,发出触发信号的定时器工作于主模式,接受触发信号而启动的定时器工作于从模式。

主/从定时器的例子

MM32 定时器操作

为了实现两个定时器完全同步,使用一个定时器作为另一个定时器的预分频器。可以配置定时器 1 作为定时器 3 的预分频器。

参考上图,进行下述操作:
˜配置定时器 1 为主模式,它可以在每一个更新事件 UEV 时输出一个周期性的触发信号。在TIM1_CR2寄存器的MMS = ‗010时,每当产生一个更新事件时在 TRGO1上输出一个上升沿信号。
˜连接定时器 1 的 TRGO1 输出至定时器 3,设置 TIM3_SMCR 寄存器的 TS = ‗000 ,配置定时器 3为使用 ITR1 作为内部触发的从模式。
˜然后把从模式控制器置于外部时钟模式 1(TIM3_SMCR 寄存器的 SMS = 111);这样定时器 3即可由定时器 1 周期性的上升沿(即定时器 1 的计数器溢出)信号驱动。
˜最后,必须设置相应(TIMx_CR1 寄存器)的 CEN 位分别启动两个定时器。

注:如果 OCx 已被选中为定时器 1 的触发输出(MMS = 1xx),它的上升沿用于驱动定时器 3 的计数器。

void Tim1_Init(u16 Prescaler,u16Period)
{
TIM_TimeBaseInitTypeDefTIM_StructInit;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

TIM_StructInit.TIM_Period=Period;
TIM_StructInit.TIM_Prescaler=Prescaler;
TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;
TIM_StructInit.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM1,&TIM_StructInit);

TIM_ClearFlag(TIM1,TIM_FLAG_Update);
}

void TIM3_Init(u16 psc,u16 arr)
{
TIM_TimeBaseInitTypeDefTIM_StructInit;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

TIM_StructInit.TIM_Period=arr;
TIM_StructInit.TIM_Prescaler=psc;
TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;
TIM_StructInit.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_StructInit);

TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Enable);
TIM_SelectInputTrigger(TIM1,TIM_TS_ITR2);
TIM_SelectSlaveMode(TIM1,TIM_SlaveMode_Gated);
TIM_SelectMasterSlaveMode(TIM1,TIM_MasterSlaveMode_Enable);

TIM3->CNT=0;
TIM1->CNT=0;

TIM_Cmd(TIM1, ENABLE);
delay_ms(300);

TIM_Cmd(TIM3, ENABLE);
delay_ms(300);
}

2、定时器精准延时

在应用中,有的需要精准的定时功能,在客户支持过程中,发现有的客户对定时器的基本定时功能的理解有些偏差,今天将与大家一起使用定时器的基本定时功能。

我们把定时器设置自动重装载寄存器 ARR 的值为1000,设置时钟预分频器为47,则驱动计数器的时钟: CK_CNT = CK_INT / (47+1)=1M,则计数器计数一次的时间等于:1/CK_CNT=1us,当计数器计数到 ARR 的值1000 时,产生一次中断,则中断一次的时间为:1/CK_CNT*ARR=1ms。
void Tim2_UPCount_test(void)
{
TIM_TimeBaseInitTypeDefTIM_StructInit;
NVIC_InitTypeDef NVIC_StructInit;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

TIM_StructInit.TIM_Period=1000;
TIM_StructInit.TIM_Prescaler=47;
TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;
TIM_StructInit.TIM_RepetitionCounter=0;

TIM_TimeBaseInit(TIM2,&TIM_StructInit);
NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;
NVIC_StructInit.NVIC_IRQChannelPriority=1;
NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_StructInit);

TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);

TIM_Cmd(TIM2, ENABLE);
}

定时器中断一次的时间是 1ms,我们定义一个全局变量ucTim2Flag,每当进一次中断的时候,让 ucTim2Flag来记录进入中断的次数。如果我们想实现一个 1s 的定时,我们只需要判断time 是否等于1000 即可,1000 个 1ms 就是1s。然后把ucTim2Flag清 0,重新计数,以此循环往复。
void TIM2_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
ucTim2Flag++;
}

转自: 灵动MM32

围观 400

在我们应用开发时,经常会有一些程序运行参数需要保存,如一些修正系数或一些自定义数据。这些数据的特点是:数量少而且不需要经常修改,但又不能定义为常量,因为每台设备可能不一样而且在以后还有修改的可能。将这类数据存在指定的位置,需要修改时直接修改存储位置的数值,需要使用时则直接读取,会是一种方便的做法。考虑到这些数据量比较少,使用专门的存储单元既不经济,也没有必要,而MM32L0系列产品内部的Flash容量较大,且擦写次数以万为单位,使用部分的Flash空间存储用户数据实现起来既方便又实惠。

MM32L0系列产品嵌入式闪存特性:

• 高达 128K 字节闪存存储器
• 存储器结构:
- 主闪存模块:最大 32K 字(32K×32 位)
- 信息模块: 系统存储器:高达 1K 字节(1K x 8 位)
选项字节:高达 2 x 8 字节
保密空间:高达 3K 字节(3K x 8 位)
保护字节:高达 512 字节(512 x 8 位)
闪存接口的特性为:
• 带预取缓冲器的读接口(每字为 2 × 64 位 )
• 选择字节加载器
• 闪存编程/擦除操作
• 访问/写保护
• 低功耗模式

MM32 FLASH操作

闪存空间由 64 位宽的存储单元组成,既可以存代码又可以存数据。主闪存块按 128 页(每页 1K 字节)或 32 扇区(每扇区 4K 字节)分块,以扇区为单位设置写保护。

主存储器的起始地址就是0x0800 0000,结束地址是0x0801 FFFF,共128K字节, B0、B1都接GND的时候,就是从0x08000000开始运行代码的。

选项字节的地址空间是0x1FFF F800 - 0x1FFF F80F,选项字节为16个字节(有效数据为低 8 位,而高 8 位为低 8 位的反码)。用于配置读写保护、软件/硬件看门狗以及器件处于待机或停止模式下的复位。

1、 读保护

在产品发布时,如果想对MCU的程序做一些保护性措施,防止他人通过仿真器把Flash中的程序读取回来,得到bin文件或hex文件,然后去模仿产品。所以我们需要对程序进行保护,一种比较简单可靠的方法就是把Flash设置成读保护。

读保护后,只允许从用户代码中对主闪存存储器的读操作(以非调试方式从主闪存存储器启动),调试模式下(Sram boot 和 debug 模式)禁止对 flash 进行操作, flash 本身的程序禁止写低 4KB 空间。第 0 ~ 3 页被自动加上了写保护,其它部分的存储器可以通过在主闪存存储器中执行的代码进行编程(实现 IAP 或数据存储等功能),但不允许在调试模式下或在从内部 SRAM 启动后执行写或擦除操作(整片擦除除外)。

读保护操作方法:

FLASH_Unlock();//解锁
FLASH_ReadOutProtection(ENABLE);//读保护使能
FLASH_Lock();//上锁
注意:如果在设置了读保护时,调试器仍然连接到 JTAG/SWD 接口,需要执行一次上电复位,而不是(没有调试器时的)系统复位。

解除读保护操作方法:

FLASH_Unlock();//解锁
FLASH_ReadOutProtection(DISABLE);//读保护失能
FLASH_Lock();//上锁
注意:当解除读保护后MM32会自动擦除整片的Flash。

2、 FLASH写和擦除操作

只要MCU的供电正常,就可以完成烧写和擦除功能操作,但是在写/擦除 Flash 的同时不可以对它取指和访问数据,因为在对 Flash进行写/擦除操作的同时,任何对 Flash 的访问都会令总线停顿。

对FLASH进行写和擦除操作的功能主要由下列7个寄存器完成:
• 关键字寄存器(FLASH_KEYR)
• 选项字节关键字寄存器(FLASH_OPRKEYR)
• Flash 控制寄存器(FLASH_CR)
• Flash 状态寄存器(FLASH_SR)
• Flash 地址寄存器(FLASH_AR)
• 选项字节寄存器(FLASH_OBR)
• 写保护寄存器(FLASH_WRPR)

MM32 FLASH操作

第一步:FLASH解锁,访问闪存控制寄存器(FLASH_CR)的锁状态, Flash 存储器默认是受保护状态的,这样可以防范意外的擦除动作。 FLASH_CR 寄存器不允许被改写,除非执行一串针对 FLASH_KEYR 寄存器的解锁操作才能开启对 FLASH_CR 的访问权限。当LOCK位为“1”时表示FPEC和FLASH_CR被锁住,不能对FLASH进行读写或者擦除操作。在检测到正确的解锁序列后,硬件会自动清除此位为“0”,该位为“0”时才能对FLASH进行正常的操作。

第二步:清除相关标志位,清EOP(操作结束位)、WRPRTERR(写保护错误)和PGERR(编程错误)位,EOP(操作结束位)在需对FLASH进行读写操作时,需等存储器的操作结束和该位当闪存操作完成,然后硬件设置该位为“1”,再写“1”清除该状态位。 WRPRTERR(写保护错误)在试图对写保护的闪存地址编程时,硬件设置为“1”,写“1”清除状态。PGERR(编程错误)在试图对内容不是0XFFFF的地址编程时,该位置为“1”。

第三步:擦除FLASH(先擦除后写入),MM32的Flash 存储器可以按页(1K字节)为单位擦除,也可以整片擦除。
页(1K字节)擦除步骤:
• 检查 FLASH_SR 中的 BSY 位,以确认上一操作已经结束,当 FLASH_SR 中得 BSY 位为 1 的时候,这些寄存器不能写
• 置 FLASH_CR 寄存器中得 PER 位为 1
• 写 FLASH_AR 寄存器以选择待擦除的页
• 置 FLASH_CR 寄存器中的 STRT 位为 1
• 等待 FLASH_SR 中的 BSY 归零
• 读取已擦除页以校验
库函数操作函数:
FLASH_ErasePage(Page_Address);//页擦除,Page_Address表示需要擦除的页地址

整片擦除步骤:
可以用整片擦除命令一次擦除整个 Flash 用户区,但信息块不会受这个命令影响,注意用户在使用整片擦除时执行该函数会将写入的应用程序也会擦除,具体步骤如下:
• 检查 FLASH_SR 中的 BSY 位,以确认上一操作已经结束,当 FLASH_SR 中得 BSY 位为 1 的时候,这些寄存器不能写
• 置 FLASH_CR 寄存器中的 MER 位为 1
• 置 FLASH_CR 寄存器中的 STRT 位为 1
• 等待 BSY 位归零
• 读取全部页并校验
库函数操作函数:
FLASH_EraseAllPages();//整片擦除

选项字节的擦除步骤:
选项字节的编程与常规用户地址不同,总共 4 个字节(2 个写保护, 1 个读保护, 1 个硬件配置)。
• 检查 FLASH_SR 寄存器中的 BSY 位,以确保上一操作结束,当 FLASH_SR 中得 BSY 位为 1 的时候,这些寄存器不能写
• 解锁 FLASH_CR 寄存器中的 OPTWRE 位
• 置 FLASH_CR 寄存器中的 OPTER 位为 1
• 置 FLASH_CR 寄存器中的 STRT 位为 1
• 等待 BSY 位归零
• 读取并校验
库函数操作函数:
status = FLASH_EraseOptionBytes();//Option空间擦除

第四步:清除相关标志位,清EOP(操作结束位),等待擦除操作结束。

第五步:写入FLASH,存储器按页(1K字节)为单位写入,FLASH的写入地址必须是偶数(FLASH机制决定的FLASH写入的时候只能是偶数地址写入,必须写入半字或字,也就是2个字节或是4字节的内容)。
主闪存编程步骤:
• 检查 FLASH_SR 中的 BSY 位,以确认上一操作已经结束
• 置 FLASH_CR 寄存器中的 PG 位
• 以半字为单位向目标地址写入数据
• 等待 FLASH_SR 寄存器中的 BSY 归零
• 读数据以校验
在指定地址编写一个字库函数操作:
FLASH_ProgramWord(Address, Data);// Address表示待编写的地址,Data表示带写入数据

在指定地址编写半字库函数操作:
ProgramHalfWord(Address, Data); // Address表示待编写的地址,Data表示带写入数据

在指定FLASH选项字节地址(0x1FFF F800 - 0x1FFF F80F)编写半字库函数操作:
ProgramOptionByteData(Address, Data); // Address表示待编写的地址,Data表示带写入数据

第六步:FLASH上锁,设置Flash存储器保护状态,这样可以防范意外的擦除动作。

读取FLASH数据
我们要从地址addr,读取一个字(字节为8位,半字为16位,字为32位),可以通过如下的语句读取:
data= (*(__IO uint32_t*) addr));
将addr强制转换为u32指针,然后取该指针所指向的地址的值,即得到了addr地址的值。

小结:

1、 为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压,在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地设置等待周期数 (LATENCY),有很多用户在自己配置时钟时,配置等待周期数错误导致程序进入HardFault。

MM32 FLASH操作

2、有些用户在测试读保护功能后,发现无法Download程序,这个时候MCU是处在读保护状态,所以程序无法下载,所以有两个解决办法:(1)在设置读保护之前加一个延时函数为程序擦除预留时间,延时时间以秒为单位,在MCU复位后,使用仿真器迅速擦除MCU程序。(2)从内置 SRAM 解除读保护,选择BOOT0接GND,使MCU从SRAM启动,Download程序,对读保护进行解除,然后重新将BOOT0接高电平。

3、如选项字节块对应的地址值为非 0xFFFF, 需先执行擦除选项字节块的动作,执行擦除选项字节块的动作不会导致自动的整片擦除操作, 不会改变读保护状态。

4、MM32的Flash主闪存块按 128 页(每页 1K 字节)或 32 扇区(每扇区 4K 字节)分块,存储器可以按页(1K字节)为单位擦除,也可以整片擦除,存储器按页(1K字节)为单位写入。

5、如果在设置了读保护时,调试器仍然连接到 JTAG/SWD 接口,需要执行一次上电复位读保护功能才会起作用,而不是(没有调试器时的)系统复位。

转自: 灵动微电子

围观 886

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM最基本的调节就是频率和占空比,通过调节频率和占空比对外部元件进行控制或者捕捉外部信号源。

在大功率电机、变频器、开关电源等方案中,末端都是由大功率管、IGBT等元件组成的H桥或3相桥。每个桥的上半桥和下半桥是是绝对不能同时导通的,但高速的PWM驱动信号在达到功率元件的控制极时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。死区就是在上半桥关断后,延迟一段时间再打开下半桥或在下半桥关断后,延迟一段时间再打开上半桥,从而避免功率元件烧毁。

MM32L0系列MCU有多达9个定时器,高级定时器TIM1可以产生互补的PWM,高级定时器TIM1是1 个 16 位 4 通道高级控制定时器,有 4 通道 PWM 输出,以及死区生成和紧急停止功能,可以通过相关寄存器的设置使能或关闭PWM的输出。

高级控制定时器(TIM1)能够输出两路互补信号,并且能够管理输出的瞬时关断和接通。这段时间通常被称为死区,用户应该根据连接的输出器件和它们的特性(电平转换的延时、电源开关的延时等)来调整死区时间。

使用紧急停止功能,使能输出信号和无效电平都会被改变,关闭PWM输出,能够有效的预防突发事件造成外部电机等不可控的状态,刹车源既可以是刹车输入管脚又可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。在安全方面,你可以把刹车输入连到电源驱动的报警输出、热敏传感器或者其他安全器件上。

在编写电机的驱动程序时,需要利用TIM1的Channel1,2,3三个通道生成三路互补的PWM波形,TIM1需要配置的寄存器有:捕捉/比较模式寄存器 1(TIMx_CCMR1)、捕捉/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~3)和刹车和死区寄存器(TIMx_BDTR).
程序配置流程如下:

void TIM1_PWM_Init(u16 arr,u16 psc)
{ 
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_OCInitTypeDef  TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStruct;
      
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB , ENABLE);
 
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10; //TIM3_CH1  TIM3_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
      
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; //TIM3_CH1  TIM3_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
      
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //TIM3_CH1  TIM3_CH1N
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
      
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8,GPIO_AF_2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,GPIO_AF_2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,GPIO_AF_2);
             
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13,GPIO_AF_2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14,GPIO_AF_2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15,GPIO_AF_2);
             
GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_2);
      
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter =0;
TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
 
TIM_BDTRInitStruct.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStruct.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStruct.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
TIM_BDTRInitStruct.TIM_DeadTime = 0x99;
//TDTS = 125nS(8MHz)
//DTG[7: 5] = 0xx => DT = DTG[7: 0] * Tdtg, Tdtg = TDTS;
//DTG[7: 5] = 10x => DT =(64+DTG[5: 0]) * Tdtg, Tdtg = 2 * TDTS;
//DTG[7: 5] = 110 => DT =(32+DTG[4: 0]) * Tdtg, Tdtg = 8 * TDTS;
//DTG[7: 5] = 111=> DT =(32 + DTG[4: 0]) *  Tdtg, Tdtg = 16 * TDTS;
 
TIM_BDTRInitStruct.TIM_Break = TIM_Break_Enable;
TIM_BDTRInitStruct.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStruct.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig( TIM1, &TIM_BDTRInitStruct);     
      
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState=TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity=TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState=TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState=TIM_OCNIdleState_Reset;
             
TIM_OC1Init(TIM1, &TIM_OCInitStructure); 
TIM_OC2Init(TIM1, &TIM_OCInitStructure); 
TIM_OC3Init(TIM1, &TIM_OCInitStructure); 
 
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);  
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);  
 
TIM_ARRPreloadConfig(TIM1, ENABLE);
 
TIM_Cmd(TIM1, ENABLE);
      
}

第一步:开定时器TIM1的时钟和GPIOA\GPIOB的时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB , ENABLE);

第二步:配置GPIO的模式,根据UM_MM32L0系列产品手册的8.1.11外设的 GPIO 配置查表可以知配置CHx的输出比较通道x和互补输出通道x功能时,需将GPIO配置成推挽复用输出模式,配置定时器的刹车输入功能时GPIO需配置成浮空输入功能。

MM32 MCU互补PWM输出使能带死区、刹车功能

根据DS_MM32L0系列产品手册的表4和表5的端口功能复用列表可知,PA8\PA9\PA10的AF2是TIM1的CH1\CH2\CH3通道,PB13\PB14\PB15的AF2是TIM1的CH1N\CH2N\CH3N通道,所以在配置GPIO的复用功能选择时通过软件写入配置IO的复用功能为AF2。

在本试验中,通过配置PB12作为刹车信号源的输入脚。根据端口功能复用列表可知,PB12的AF2是TIM1的刹车输入功能。

MM32 MCU互补PWM输出使能带死区、刹车功能

MM32 MCU互补PWM输出使能带死区、刹车功能

第三步:配置定时器的基本功能,首先来了解定时器初始化参数结构体指针,结构体类型是TIM_TimeBaseInitTypeDef,该结构体主要包含5个成员变量:

typedef struct
{
        uint32_t TIM_Prescaler;
        uint32_t TIM_CounterMode;
uint32_t TIM_Period;
        uint32_t TIM_ClockDivision;
        uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;

第一个参数TIM_Prescaler是用来设置分频系数的,MM32L0系列产品的高级定时器TIM1是挂在APB2上。在MM32所有例程中,在每次一启动时,在程序中就已经帮助客户配置好了APB的时钟,在system_MM32L0系列.c中如果您配置相对应的系统时钟,在时钟配置函数中,您可以看到已经对APB1\APB2\AHB的时钟做了配置,默认APB2的时钟是48MHz,所以定时器的时钟需用户根据自己的实际需求进行分频或者不分频操作。

MM32 MCU互补PWM输出使能带死区、刹车功能

第二个参数是TIM_CounterMode用来配置PWM模式,MM32L0系列产品的PWM模式有三种:向上计数模式、向下计数模式和中央对齐模式。
1、向上计数模式:例:当 TIMx_CNT 否则低。如果 TIMx_CCRx 中的比较值大于自动重装载值(TIMx_ARR),则 OCxREF 保持为‘1’。
2、向下计数模式:当 TIMx_CNT > TIMx_CCRx 时参考信号 OCxREF 为低,否则为高。如果 TIMx_CCRx中的比较值大于 TIMx_ARR 中的自动重装载值,则 OCxREF 保持为‘1’。
3、中央对齐模式:中央对齐模式又分为三种模式,根据不同的 CMS 位的设置,比较标志可以在计数器向上计数时被置‘1’、在计数器向下计数时被置‘1’、或在计数器向上和向下计数时被置‘1’。

中央对齐模式选择
MM32 MCU互补PWM输出使能带死区、刹车功能

第三个参数是TIM_Period用来设置自动重载计数周期值,自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在 TIMx_CR1 寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被立即或在每次的更新事件 UEV时传送到影子寄存器。当计数器达到溢出条件(向下计数时的下溢条件)并当TIMx_CR1 寄存器中的 UDIS位等于 0 时,产生更新事件。

第四个参数是TIM_ClockDivision用来设置时钟分频因子,该变量与后面死区持续时间配置有关,该变量是设置控制寄存器1(TIMx_CR1)的时钟分频因子CKD[1:0],这 2 位定义在定时器时钟(CK_INT) 频率、死区时间和由死区发生器与数字滤波器(ETR,TIx) 所用的采样时钟之间的分频比例。

第五个参数是TIM_RepetitionCounter用来设置重复计数值,该功能在上一篇文章有详细讲解。

第四步:配置定时器的刹车和死区,首先来了解定时器刹车和死区参数结构体指针,结构体类型是TIM_BDTRInitTypeDef,共7个成员变量:

    typedef struct
    {
        uint16_t TIM_OSSRState;
        uint16_t TIM_OSSIState;
        uint16_t TIM_LOCKLevel;
        uint16_t TIM_DeadTime;
        uint16_t TIM_Break;
        uint16_t TIM_BreakPolarity;
        uint16_t TIM_AutomaticOutput;
} TIM_BDTRInitTypeDef;

第一个参数是TIM_OSSRState用来配置运行模式下“关闭状态”选择。
第二个参数是TIM_OSSIState用来配置空闲模式下“关闭状态”选择。
第三个参数是TIM_LOCKLevel用来定时器的寄存器锁定保护,锁定级别分为3个级别,只能写入一次LOCK位,一旦写入TIMx_BDTR寄存器,则内容冻结至复位。
第四个参数是TIM_DeadTime设置死区时间,用户可根据UM_MM32L0xx手册的刹车和死区寄存器(TIM1x_BDTR)寄存器计算死区时间。
第五个参数是TIM_Break设置是否使能刹车功能。
第六个参数是TIM_BreakPolarity用来设置刹车输入电平。
第七个参数是TIM_AutomaticOutput设置BDTR寄存器的主输出使能(MOE)位。

第五步:配置定时器的PWM输出功能,

 typedef struct
    {
        uint16_t TIM_OCMode;
        uint16_t TIM_OutputState; 
        uint16_t TIM_OutputNState;          
        uint16_t TIM_Pulse;
        uint16_t TIM_OCPolarity;
        uint16_t TIM_OCNPolarity;
        uint16_t TIM_OCIdleState;
        uint16_t TIM_OCNIdleState;
    } TIM_OCInitTypeDef;

第一个参数是TIM_OCMode用来设置模式是输入捕获功能或者输出比较功能。
PWM 模式 1 - 在向上计数时,一旦 TIMx_CNT TIMx_CCR1 时通道 1 为无效电平(OC1REF= 0) ,否 则为有效电平(OC1REF = 1)
PWM 模式 2 - 在向上计数时,一旦 TIMx_CNT TIMx_CCR1 时通道 1 为有效电平,否则为无效电平。
第二个和第三个参数TIM_OutputState和TIM_OutputNState是配置互补通道输出使能。
第四个参数是TIM_Pulse用来设置待装入捕获比较寄存器的脉冲值,它的取值必须在 0x0000 和 0xFFFF之间。
第五和第六个参数TIM_OCPolarity和TIM_OCNPolarity是设置互补通道的极性是高还是低。
第七和第八个参数是TIM_OCIdleState和TIM_OCNIdleState用来设置刹车后的互补通道的电平状态。

第六步:初始化TIM1的OC1\OC2\OC3通道,使能定时TIM1。

逻辑分析仪抓取PWM波形
MM32 MCU互补PWM输出使能带死区、刹车功能
(逻辑分析仪精度不高,建议使用示波器抓波性)

小结:

1、PWM的频率和占空比计算:
//Fpwm = 48M / ((arr+1)*(psc+1))(单位:Hz)
//duty circle = TIM1->CCR1 / arr(单位:%)
本次配置参数:arr=999,psc=47,Pulse=500
Fpwm = 48M / ((arr+1)*(psc+1))=48M/ ((999+1)*(47+1))=1KHz
duty circle = (TIM1->CCR1 / arr )*100= (500/999)*100 = 50%

2、PWM互补通道电平设置:
通过上面截图可以看到互补通道的输出电平极性是相反的,如果用户需要配置互补通道输出相同的电平极性,需要配置的TIMx_CCER寄存器,通过库函数设置TIM_OCPolarity和TIM_OCNPolarity即可改变互补通道的电平输出极性。

MM32 MCU互补PWM输出使能带死区、刹车功能

3、刹车电平配置:
在配置刹车输入有效电平时,需先配置刹车和死区寄存器(TIMx_BDTR)的BKP位,配置为0时表示刹车输入低电平有效,配置为1时表示刹车输入高电平有效,在库函数中通过设置TIM_BreakPolarity变量即可实现有效电平设置,用户需根据外面信号来源配置对应的极性。

MM32 MCU互补PWM输出使能带死区、刹车功能

如上图所示,中间表示有刹车信号输入,关断PWM输出。在刹车信号关闭时,PWM又恢复输出,用户可以根据实际需求在刹车信号后是否恢复PWM输出,需要配置寄存器刹车和死区寄存器(TIMx_BDTR)的AOE位,在设置AOE位为0的情况下,触发刹车时,MOE关断,不再开启。

4、死区时间设置:
用户如果需要配置插入互补输出之间的死区持续时,需要配置刹车和死区寄存器(TIMx_BDTR)的死区发生器设置DTG[7:0],如下图所示,DT表示死区持续时间,Tdts为系统时钟周期,Tdtg表示乘以倍数后死区设置时间步进值。

MM32 MCU互补PWM输出使能带死区、刹车功能

Tdts = 1/48M = 20.83ns,TIM1_BDTR = 0xFF,高三位是0,所以选择第一个公式:
DT=(32+0x1F)*16* Tdts=(32+32)*16*20.83 = 21.3us。

MM32 MCU互补PWM输出使能带死区、刹车功能
(逻辑分析仪精度不高,建议使用示波器抓波性)

转自: 灵动微电子

围观 1227

UART(Universal Asynchronous Receiver and Transmitter)通用异步收发器(异步串行通信口)是MCU的一个重要的数字接口,市面上很多的传感器、通信模块等外围器件都采用了UART接口,同时工程师在软件开发调试过程中UART打印输出作为一种最直观的输出方式可以检查程序的运行情况,所以UART在MCU中的作用不言而喻。

首先普及一下并行通信、串行通信(同步通信和异步通信)两种通信方式的特点:
并行通信:并行通信是指数据的各个位同时传送,可以字或字节为单位并行进行。
-传输原理:数据各个位同时传输。
-优点:速度快,位数多
-缺点:占用引脚资源多,线路复杂,成本高

串行通信:串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度,其只需要少数几条线就可以在系统间交换信息。
-传输原理:数据按位顺序传输。
-优点:占用引脚资源少,传输线少
-缺点:速度相对较慢,耗时长

串行通信的通信方式又分为:同步通信和异步通信两种方式
同步通信:带时钟同步信号传输,发送方和接收方时钟需要建立连接,使双方的时钟
到完全同步。比如:SPI,IIC通信接口等。
异步通信:接收器和发送器使用各自的时钟,不带时钟同步信号。每一个字符要用起始位和停止位作为字符开始和结束的标志,以字符为单位的一个个地发送和接收。比如:UART通信接口等。

MM32系列MCU的通用异步收发器(UART)提供了一种灵活的方法与使用工业标准 NRZ 异步串行数据格式的外部设备之间进行全双工数据交换。UART 利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,以及调制解调器(CTS/RTS)操作。

有很多工程师在使用UART时,在设计PCB的UART接口时,一直对UART采用两线、三线、四线的问题一直都是很模糊情况,在这里我简单讲一下这个问题。

有些客户采用两线主要是单工模式,两线分别是:GND, TX 或者 RX,相当于MCU是做发送或者接收功能,将接收设备和发送设备共地,是要把参考电压调节成一致,避免接收设备和发送设备双方对高低电平的判断不一致的情况。

采用两线主要是双工模式,三线分别是:GND,TX,RX,接收设备和发送设备都是双向通信设备,且都有各自的供电电源,只需要将双方的基准电压调节一致就可以实现双方的串口通信功能,客户在使用ISP下载程序时一般都采用这种方式,预留一个三线接口。

采用四线主要是通信双方有一方需要为另一方提供电源,供另一方芯片运行,所以四线分别为:GND,TX,RX,VDD。
GND:共地,提供基准电压。
RX:接收数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据串行输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
VDD:供电源。

一般的通信模块还会有另外两个引脚在硬件流控模式中需要使用:
nCTS:清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送。
nRTS:发送请求,若是低电平,表明 UART 准备好接收数据。

两个UART间的通信接线方法

MM32 UART中断通信

两个UART间的硬件流控
MM32 UART中断通信

UART特征:

字长可以通过编程 UART_CCR 寄存器中的 CHAR 位,选择 5 ~ 8 位。在起始位期间, TX 脚处于低电平,在停止位期间处于高电平。

空闲符号被视为完全由‘1’组成的一个完整的数据帧,后面跟着包含了数据的下一帧的开始位(‘1’的位数也包括了停止位的位数)。

断开符号被视为在一个帧周期内全部收到‘0’(包括停止位期间,也是‘0’)。在断开帧结束时,发送器再插入 1 或 2 个停止位(‘1’)来应答起始位。

发送和接收由一个共用的波特率发生器驱动,当发送器和接收器的使能位分别置位时,分别为其产生时钟。

UART时序

MM32 UART中断通信

串口设置的步骤可分为如下几个流程:

1) 串口复位,GPIO复位
2) 串口时钟使能, GPIO 时钟使能
3) GPIO 端口模式设置
4) 串口参数初始化
5) 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)
6) 编写中断处理函数

1、串口复位,GPIO复位

在系统开始配置外设时,建议先执行复位相对应的外设,然后重新配置该外设,使其达到自己所期望的工作模式。

UART_DeInit(UART1);//复位串口1
GPIO_DeInit(GPIOA);//复位GPIOA

2、串口时钟使能, GPIO 时钟使能

串口1(UART1)是挂载在 APB2 下面的外设,GPIOA的时钟是挂载在AHB上,所以使能函数为:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);//使能UART1
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);//使能GPIOA的时钟

3、GPIO 端口模式设置

在上一章节讲述了GPIO的使用,使用GPIO的UART功能需要配置端口复用功能,根据DS_MM32L073_Ver1.7手册表4.PA端口功能复用可知PA9和PA10的UART功能的复用配置AF1。
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_1);
外设的GPIO配置:

MM32 UART中断通信

接下来的两段代码就是将 TX(PA9)设置为推挽复用输出模式,将 RX(PA10)设置为浮空输入模式:
GPIO_InitTypeDef GPIO_InitStructure;
//UART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA.9

//UART1_RX GPIOA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);// 初始化 GPIOA.10

4、串口参数初始化

4.1、分数波特率发生器

接收器和发送器的波特率在 BRR 的整数寄存器和 FRA 的小数寄存器中的值应设置成相同。
Tx/Rx 波特率 = fCK /(16 *UARTDIV )

这里的 fCK 是给外设的时钟(PCLK1 用于 UART2, PCLK2 用于 UART1)。UARTDIV 是一个无符号的定点数。这 16 位的值设置在 UART_BRR 寄存器。

例:如果BaudRate=9600,PCLK2=72MHz
则UARTDIV= 468.75

4.2、字长配置

字长可以通过编程 UART_CCR 寄存器中的 CHAR 位,选择 5 ~ 8 位。

4.3、奇偶校验位

奇偶控制(发送时生成一个奇偶位,接收时进行奇偶校验)可以通过设置 UART_CCR 寄存器上的 PEN位而激活。如果奇偶校验出错,无效数据不会从移位寄存器传送到 UART_RDR 寄存器。

偶校验:校验位使得一帧中的数据以及校验位中‘1’的个数为偶数。

例如:数据 = 00110101,有 4 个‘1’,如果选择偶校验(在 UART_CCR 中的 PSEL = 0),校验位将是‘0’。

奇校验:此校验位使得一帧中的数据以及校验位中‘1’的个数为奇数。

例如:数据=00110101,有 4 个‘1’,如果选择奇校验(在 UART_CCR 中的 PSEL = 1),校验位将是‘1’。

传输模式:如果 UART_CCR 的 PEN 位被置位,写进数据寄存器的数据的 MSB 位被校验位替换后发送出去(如果选择偶校验偶数个‘1’,如果选择奇校验奇数个‘1’)。如果奇偶校验失败, UART_ISR 寄存器中的 RXPERR_INTF 标志被置‘1’,并且如果 RXPERREN 在被预先设置的话,中断产生。

4.4、硬件数据流流控

RTS 流控制

如果 RTS 流控制被使能,只要 UART 接收器准备好接收新的数据, nRTS 就变成有效(接低电平)。当接收寄存器内有数据到达时, nRTS 被释放,由此表明希望在当前帧结束时停止数据传输。下图是一个启用 RTS 流控制的通信的例子。

MM32 UART中断通信

CTS 流控制

如果 CTS 流控制被使能,发送器在发送下一帧前检查 nCTS 输入。如果 nCTS 有效(被拉成低电平),则下一个数据被发送(假设那个数据是准备发送的),否则下一帧数据不被发出去。若 nCTS 在传输期间被变成无效,当前的传输完成后停止发送。下图是一个 CTS 流控制被启用的通信的例子。

MM32 UART中断通信

UART_InitTypeDef UART_InitStructure;
UART_InitStructure.UART_BaudRate = 115200;//波特率设置
UART_InitStructure.UART_WordLength= UART_WordLength_8b;//字长为8
UART_InitStructure.UART_StopBits = UART_StopBits_1;//一个停止位
UART_InitStructure.UART_Parity = UART_Parity_No;//无奇偶校验位
UART_InitStructure.UART_HardwareFlowControl=UART_HardwareFlowControl_None;//无硬件数据流流控
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;//开启收发模式
UART_Init(UART1, &UART_InitStructure); //初始化串口
UART_Cmd(UART1, ENABLE); //UART1使能

4.5、开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)

NVIC_InitTypeDef NVIC_InitStructure;
UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);//开启串口接收中断
NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

4.6、编写中断处理函数

void UART1_IRQHandler(void) //中断服务函数
{
u8 Res;
if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) //接收中断产生
{
UART_ClearITPendingBit(UART1,UART_IT_RXIEN);//清中断标志
Res =UART_ReceiveData(UART1);
UART_SendData (Res);
}
}

按照上述配置完成后,在main函数中将上述函数调用,将程序下载到MiniBorad中,打开串口助手,在对话框中输入您需要输入的内容,MCU的UART将您发送的数据转发并且打印在串口助手对话框,可以在显示串口看到您输入的内容。打印结果如下图所示:

MM32 UART中断通信

转自: 灵动微电子

围观 610

前段时间有一个客户需要用到4线电阻触摸屏,为了节省一片触摸屏控制芯片,客户决定使用MCU直接控制4线电阻触摸屏,主要使用到了MM32的ADC外设资源,通过ADC采集触摸屏的X+\Y+的电压,算出相对应的坐标轴,并且显示在显示屏上。所以今天将结合该例程和大家一起熟悉、了解MM32的ADC配置流程。

一、触摸屏操作原理

电阻触摸屏利用压力感应进行控制。电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层,它的内表面也涂有一层涂层,在他们之间有许多细小的(小于1/1000英寸)的透明隔离点把两层导电层隔开绝缘。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理。

二、MM32驱动电阻触摸屏的实现

MM32系列MCU的I/O口可以编程配置成带上拉电阻的模式,这个特点可以用来检测有无触摸。MM32的GPIO管脚连接触摸屏的Y+和X- ,连接Y+的管脚配置成内部电阻上拉模式,连接X-的管脚配置成推挽输出低电平。如果Y+为高电平,那么就是没有触摸。如果Y+为低电平,触摸屏被压下。从高到低电平的跳变可以检测触摸,可以用外部中断,在本例程中将使用ADC的单次扫描模式采集X+\Y+电压。

图1. 4线电阻触摸屏坐标读取

 MM32通过ADC控制4线电阻触摸屏

三、MM32 ADC介绍

12 位 ADC 是逐次逼近式的模拟-数字转换器(SAR A/D 转换器),且ADC转换数据分辨率可设置8-12位有效。

高达1Msps转换速率,有很多用户在计算采样频率设置时会觉得很难理解,在这里将为大家讲一下采样频率计算方法,ADC 的时钟 ADCLK 由 PCLK2 分频得到ADC 的输入时钟(不得超过 15MHz,它是由 PCLK2 经分频产生)。分频系数可通过设置 ADCFG 寄存器的 ADCPRE 位来确定,即 PCLK2/(N+1)/2 分频后作为 ADC 时钟。设置 ADC 分辨率为 n 位(n=8,9,10,11,12),每个通道采样时间为 m, Fsample = FADCLK/(m + n + 1.5)。假设分辨率配置为 12bit,每个通道采样时间为 1.5T, 则 Fsample =FADCLK/15。

例如:设置 ADC 分辨率为 n =12,每个通道采样时间m =1.5个周期,FADCLK=15MHz
Fsample = FADCLK/(m + n + 1.5)=15/(12+1.5+1.5)=1Msps

根据此配置可以得到1Msps转换速率,也就是转换时间为1us,用户可以根据项目的需求配置相对应的转换速率。

A/D 转换器支持多种工作模式:单次转换、单周期扫描模式和连续转换模式。
- 单次转换模式: A/D 转换在指定通道完成一次转换。
- 单周期扫描模式: A/D 转换在所有指定通道完成一个周期(从低序号通道到高序号通道)转换。

- 连续扫描模式: A/D 转换连续执行单周期扫描模式直到软件停止 A/D 转换。

支持DMA传输,单周期扫描和连续扫描时通道转换的值存储在各自通道的数据寄存(ADDRn)中,最近一次转换的结果也会保存在 ADDATA 寄存器中。DMA 传输时可以选择传输某个特定通道的数据,或者传输所有扫描通道的结果。

A/D 转换的启动方式有软件设定(即在配置相关寄存器时,直接开启采样)、外部引脚触发(例如定时器捕获,EXTI线)以及各个定时器启动(Timer1/2/3/4 匹配或者 TRGO 信号,在配置电机应用时需要使用定时器触发ADC采样)。

窗口比较器(模拟看门狗)允许应用程序检测输入电压是否超出了用户设定的高/低阀值值,转换结果可和指定的值相比较,当转换值和设定值相匹配时,用户可设定是否产生中断请求。

四、用MM32L373读触摸屏参数
在硬件中使用了MM32L373的ADC单周期扫描功能,使用到的GPIO分别是:PA1\PA3\PA4\PA5,分两步读取X,Y坐标值。

//测量X+电压的GPIO配置
void XP_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//将X+配置模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将X-配置浮空模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将Y+、Y-配置为通用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);//Y-输出低电平
GPIO_SetBits(GPIOA,GPIO_Pin_5); //Y+输出高电平
}
//测量Y+电压的GPIO配置
void YP_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//将Y+ 配置模拟输入
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);
//将Y- 配置浮空模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将X+、X-配置为通用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_3);//X-输出低电平
GPIO_SetBits(GPIOA,GPIO_Pin_1); //X+输出高电平
}
第一步,驱动Y+为高电平,Y-为低电平,接X+管脚配置成AD输入模式,检测X+的电压,此电压与驱动电压的比例即Y坐标和整个屏的高度比率。
/***************************************************************************
** 函数信息:void XP_ADC1_SingleChannel(uint8_t ADC_Channel_x)
**功能描述:测量X+电压,配置ADC单次扫描通道
**输入函数:ADC_Channel_x , x为0~8
**输出函数:无
***************************************************************************/
void XP_ADC1_SingleChannel(uint8_t ADC_Channel_x)
{
ADC_InitTypeDef ADC_InitStructure;
XP_GPIO_Configuration();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启ADC1时钟
/* Initialize the ADC_PRESCARE values */
ADC_InitStructure.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16;//16分频
/* Initialize the ADC_Mode member */
ADC_InitStructure.ADC_Mode = ADC_Mode_Single;//配置单通道扫描模式
/* Initialize the ADC_ContinuousConvMode member */
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换模式
/* Initialize the ADC_DataAlign member */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
/* Initialize the ADC_ExternalTrigConv member */
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;//外部触发通道选择
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_x, 0, ADC_SampleTime_1_5Cycles); //设置换换顺序和采样时间
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件转换启动
if(ADC_Channel_x==ADC_Channel_8)
{
ADC1->ADCFG|=0x04;
}
}

第二步,驱动X+为高电平,X-为低电平,接Y+的管脚配置成AD输入模式,检测Y+的电压,此电压与驱动电压的比例即X坐标和整个屏的宽度比率。
/***************************************************************************
** 函数信息:void YP_ADC1_SingleChannel(uint8_t ADC_Channel_x)
**功能描述:测量Y+电压,配置ADC单次扫描通道
**输入函数:ADC_Channel_x , x为0~8
**输出函数:无
***************************************************************************/
void YP_ADC1_SingleChannel(uint8_t ADC_Channel_x)
{
ADC_InitTypeDef ADC_InitStructure;
YP_GPIO_Configuration();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1时钟
/* Initialize the ADC_PRESCARE values */
ADC_InitStructure.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16; //16分频
/* Initialize the ADC_Mode member */
ADC_InitStructure.ADC_Mode = ADC_Mode_Single; //配置单通道扫描模式
/* Initialize the ADC_ContinuousConvMode member */
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //关闭连续转换模式
/* Initialize the ADC_DataAlign member */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
/* Initialize the ADC_ExternalTrigConv member */
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;//外部触发通道选择
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_x,0,ADC_SampleTime_1_5Cycles); //设置换换顺序和采样时间
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件转换启动
if(ADC_Channel_x==ADC_Channel_8)
{
ADC1->ADCFG|=0x04;
}
}

第三步:根据ADC采集到的数据做校验算法,得到触摸点坐标,并且显示在显示屏对应的区域。

图2 实验效果

 MM32通过ADC控制4线电阻触摸屏

转自: 灵动微电子

围观 629

电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统稳定运行后,又有低功耗的要求。

在很多应用场合中,对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以很有必要从控制功耗入手,提高设备的续行时间。

在系统或电源复位以后,MCU处于运行状态。运行状态下的时钟源为 CPU 提供时钟,内核执行程序代码。当 CPU 不需继续运行时,可以利用多个低功耗模式来降低功耗,例如等待某个外部事件时。

MM32L0产品支持三种低功耗模式:睡眠模式、停止模式和待机模式,可以在要求低功耗、短启动时间和多种唤醒事件之间达到最佳的平衡,可以满足用户对低功耗的要求。

• 睡眠模式

在睡眠模式,只有 CPU 停止,所有外设处于工作状态并可在发生中断/事件时唤醒 CPU。

有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt)和 WFE(wait for event),即等待“中断”唤醒和“事件”唤醒。

进入睡眠模式例:
__WFE();//等待事件,等待事件是一个暂停执行指令暂停至任意事件产生后被唤醒。__WFI();//等待中断,等待中断是一个暂停执行指令暂停至任意中断产生后被唤醒。

关于退出睡眠模式:

如何使用MM32的三种低功耗模式?

配置外部事件唤醒函数例:
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init( GPIOA, &GPIO_InitStruct);
SYSCFG_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Event;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}

• 停止模式

在保持 SRAM 和寄存器内容不丢失的情况下,停机模式可以达到最低的电能消耗。在停机模式下,停止所有内部 1.8V 部分的供电, PLL 、HSI 的振荡器和 HSE 晶体振荡器被关闭,调压器可以被置于普通模式或低功耗模式。

有两种方式进入停止模式,可以通过设置独立的控制位,选择以下待机模式的功能:

独立看门狗(IWDG):可通过写入看门狗的键寄存器或硬件选择来启动 IWDG。一旦启动了独立看门狗,除了系统复位,它不能再被停止。

内部振荡器(LSI 振荡器):通过控制/状态寄存器 (RCC_CSR)的 LSION 位来设置。在停止模式下,如果在进入该模式前 ADC 和 DAC 没有被关闭,那么这些外设仍然消耗电流。

进入停止模式例:
void Sys_Stop(void)
{
PWR_EnterSTOPMode(0, PWR_STOPEntry_WFI);
}
注:MM32L0xx在进入停止模式前,需将系统时钟切换到HSI。

关于退出停止模式:

当一个中断或唤醒事件使MCU退出停止模式时, HSI 振荡器被选为系统时钟。当电压调节器处于低功耗模式下,当系统从停止模式退出时,将会有一段额外的启动延时。

如何使用MM32的三种低功耗模式?

配置中断唤醒函数例:
void WKUP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
PWR_WakeUpPinCmd(ENABLE);
}

• 待机模式

待机模式可实现系统的最低功耗。该模式是在 CPU 深睡眠模式时关闭电压调节器。整个 1.8V 供电区域被断电。 PLL、 HSI 和 HSE 振荡器也被断电。 SRAM 和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。在进入待机模式后,除了被用于唤醒 I/O,其余 I/O 都进入高阻态,而从待机模式唤醒后,相当于复位MM32芯片,程序重新从头开始执行。

有两种方式进入待机模式,可以通过设置独立的控制位,选择以下待机模式的功能:

独立看门狗(IWDG):可通过写入看门狗的键寄存器或硬件选择来启动 IWDG。一旦启动了独立看门狗,除了系统复位,它不能再被停止。

内部振荡器(LSI 振荡器):通过控制/状态寄存器(RCC_CSR)的 LSION 位来设置。

进入待机模式例:
void Sys_Standby(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
PWR_WakeUpPinCmd(ENABLE);
PWR_EnterSTANDBYMode();
}

关于退出待机模式:

当一个外部复位(NRST 引脚)、 IWDG 复位、 WKUP 引脚上的上升沿, 微控制器从待机模式退出。从待机唤醒后,除了电源控制/状态寄存器(PWR_CSR),所有寄存器被复位。

从待机模式唤醒后的代码执行等同于复位后的执行(采样启动模式引脚、读取复位向量等)。电源控制/状态寄存器(PWR_CSR)将会指示内核由待机状态退出。

如何使用MM32的三种低功耗模式?

配置WKUP引脚上升沿函数例:
u8 Check_WKUP(void)
{
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))
{
return 1;
}
else
{
return 0;
}
}
}

void WKUP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SYSCFG_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
if(Check_WKUP()==0) Sys_Standby();
}

当系统处于低功耗状态时,使用下载器是无法给芯片下载程序的。有很多用户使用低功耗功能,但是唤醒部分配置有问题,导致MCU无法唤醒,发现无法对芯片进行下载程序。在这里告诉大家一个调试低功耗模式的小秘密,您在测试低功耗模式时可在主函数启动时加一个较长的延时函数,按住板子的复位键,使系统处于复位状态,然后点击电脑端的下载按钮下载程序,这时再释放复位键,这样MCU上电后执行延时函数期间对低功耗程序进行擦除。

还有一种方法是选择启动方式,通过将BOOT0拉高,重新上电使MCU从SRAM启动,重新选择一个闪灯程序对flash进行擦除,擦除完成后将BOOT0拉低,然后重新上电即可重新下载程序。

转自: 灵动微电MMCU

围观 675

页面

订阅 RSS - MM32