MM32SPIN2x

本章节将与大家一起使用CRC模块进行数据校验。

在数据传输过程中,无论传输系统的设计再怎么完美,差错总会存在,这种差错可能会导致在链路上传输的一个或者多个帧被破坏(出现比特差错,0变为1,或者1变为0),从而接受方接收到错误的数据。为尽量提高接受方收到数据的正确率,在接受方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。检测的方式有多种,常见的有奇偶校验、因特网校验和循环冗余校验等。

其中循环冗余校验(CRC)原理实际上就是在一个p位二进制数据序列之后附加一个r位二进制检验码(序列),从而构成一个总长为n=p+r位的二进制序列;附加在数据序列之后的这个检验码与数据序列的内容之间存在着某种特定的关系。如果因干扰等原因使数据序列中的某一位或某些位发生错误,这种特定关系就会被破坏。因此,通过检查这一关系,就可以实现对数据正确性的检验。只要经过严格的挑选,并使用位数足够多的除数 P,那么出现检测不到的差错的概率就很小很小。CRC是一种常用的检错码,无法检测出错误在哪里,因此并不能用于自动纠错,一般的做法是丢弃接收的数据。

从上面的程序框图,我们可以发现,多项式的幂次越高,校验效果越好,但所花费的时间也越长。因此常用查表法或专用的硬件CRC模块来提高效率。

其中查表法,是事先根据特定的校验多项式,算出1字节数据范围所对应的256个余数,将其作为表格,编程写到程序存储器中查询而避免在线运算,已是非常通用的做法。但如果是CRC16校验,存储表格要占512字节(CRC32则需要1 KB),对于有限的单片机ROM资源来说所占比例不小,往往只因为多装了此表,就不得不升级单片机的型号。

在SPIN2x系列MCU中,加入了一个CRC计算单元,使用1个32位数据寄存器作为输入和输出,在执行写操作时输入CRC计算的新数据,在执行读操作时返回上一次CRC计算的结果。每一次写入数据寄存器,都会对整个32位字进行CRC计算,而不是逐字节地计算,从而节省大量的时间。

• 使用CRC-32(以太网)多项式: 0x4C11DB7
• 每次CRC计算需要4个AHB时钟周期
• 在 CRC 计算期间会暂停写操作,因此可以对寄存器 CRC_DR 进行背靠背写入或者连续地写-读操作。
• 可以通过设置寄存器CRC_CTRL的RESET位来重置寄存器 CRC_DR 为 0xFFFFFFFF,该操作不影响8位独立数据寄存器CRC_IDR内的数据。


图2 CRC计算单元框图

下面我们来看一下在程序中软件CRC与硬件CRC的配置。

CRC模块的配置步骤如下:

• CRC模块时钟使能
• CRC_CR的第一位RESET位复位(可选)
• 将数据写入CRC_DR寄存器
• 从CRC_DR寄存器中读出计算结果

程序中配置如下:

uint32_t Hardware_CRC(u32*addr, int num) 

{

CRC->CR|=1; //复位

for (; num > 0; num--)             

CRC->DR = (*addr++);

return CRC->DR;

}

我们可以使用软件算法来检验计算结果,对比两种方式花费的时间。

软件算法如下:

u32 Software_CRC (u32 *ptr,u32 len) 

{ 

u32 xbit; 

u32 data; 

u32 CRC32 = 0xFFFFFFFF; 

u32 bits; 

const u32 dwPolynomial =0x04C11DB7 ; 

u32 i; 

             

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

{ 

xbit = (unsigned)1 <<31; 

data = ptr[i]; 

for (bits = 0; bits < 32;bits++)  

{ 

if (CRC32 & 0x80000000)

{ 

CRC32 <<= 1; 

CRC32 ^= dwPolynomial; 

} 

else 

CRC32 <<= 1; 

if (data & xbit) 

CRC32 ^= dwPolynomial; 

xbit >>= 1; 

} 

} 

return CRC32; 

}

下面我们用两个简单的数组,一个数组从0x00递增到0x7F,另一个数组从0x7F递减到0x00,分别使用硬件CRC和软件CRC计算,同时使用TIM1进行计时,最后通过UART输出得到的校验码和花费的时间。

计算和输出程序:

void CRCTest()

{

unsigned int i;      

u32 CRCtime,CRCresault;

u32 crc1[128];

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

crc1[i] = i;

printf("CRC_test\r\n");

             

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC,ENABLE);//使能CRC时钟

CRC->CR|=1; //复位

printf("\r\nCRC_DR=%x\t\r\n",CRC->DR);//输出复位值

      

TIM1->CNT &= 0;

CRCresault=Hardware_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Hardware_CRC1:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

TIM1->CNT &= 0;

CRCresault=Software_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Software_CRC1:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

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

crc1[i] = 0xFF-i;

TIM1->CNT &= 0;

CRCresault=Hardware_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Hardware_CRC2:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

             

TIM1->CNT &= 0;

CRCresault=Software_CRC(crc1,128);

CRCtime =TIM1->CNT;

printf("Software_CRC2:resault=%08x\ttime=%d\r\n",CRCresault,CRCtime);

}

定时器配置程序:

void Tim1_UPCount_test(u16Prescaler,u16 Period)

{

TIM_TimeBaseInitTypeDefTIM_StructInit;

 

/*使能TIM1时钟,默认时钟源为PCLK2(PCLK2未分频时不倍频,否则由PCLK2倍频输出),可选其它时钟源*/

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

 

TIM_StructInit.TIM_Period=Period;                                         //ARR寄存器值

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_Cmd(TIM1, ENABLE);  

/*更新定时器时会产生更新时间,清除标志位*/

TIM_ClearFlag(TIM1,TIM_FLAG_Update);

}

Main函数:

int main(void)

{ 

Tim1_UPCount_test(48-1,0xffff); //APB2时钟为48M,48分频后TIM1时钟为1MHz

delay_init();

uart_initwBaudRate(9600);    //初始化UART

CRCTest();

while(1) { }

}


图3 软件CRC与硬件CRC计算结果

从结果中,我们可以看到,对于这两个数组,硬件CRC与软件CRC所得到的结果一致,但是硬件CRC每个数组只花费了52us,远小于软件CRC的2.5ms,由此可见,在进行大量数据处理的时候,使用硬件CRC模块可以节省大量的时间,同时保证了计算结果的正确。

来源:灵动MM32MCU

围观 97

本章节将通过使用采集红外测距模块测量阻挡物的距离与大家一起学习配置ADC和DMA模块。

ADC介绍与配置

ADC,Analog-to-Digital Converter的缩写,指模数转换器,是一种将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。ADC可以实现这个功能,不同的应用对ADC的精度有着不同的要求。

在MM32SPIN27系列芯片中,内嵌 2 个 12 位的模拟/数字转换器 (ADC),每个 ADC 可用多达 16 个外部通道,可以满足大部分产品需求。


图1 ADC框图

MM32SPIN27中的12 位 ADC 是逐次逼近式的模拟-数字转换器 (SAR A/D 转换器),拥有高达 1Msps 转换速率,可以软件配置通道采样时间和分辨率。每个通道拥有独立的数据寄存器和一个共用的数据寄存器。

MM32SPIN27的ADC支持多种工作模式,包括单次转换模式、单周期扫描模式和连续扫描模式。扫描模式中可以选择通道扫描的顺序,选择采样顺序从高到低或从低到高。

A/D转换的启动方式有软件设定、外部引脚触发以及各个定时器启动。在触发信号产生后,ADC最多可以延时512个PLCK2时钟周期再开始采样。

在比较模式下提供了上限和下限两个比较寄存器。可通过软件设定 CMPCH 位选择监控通道。

ADC 的输入时钟由 PCLK2 经分频产生,通过正确配置ADC_ADCFG寄存器的ADCPRE位和ADCPRE位,可以自由地配置2分频到17分频。注意,输入时钟不得超过15MHz。

下面是一个简单的ADC配置函数:

void ADCInit(void)

{

ADC_InitTypeDef ADC_InitStructure;      

ADC_StructInit(&ADC_InitStructure);

GPIO_InitTypeDef GPIO_InitStructure;

 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);                         

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);         

 

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2;                                

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;                          

GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;                              

GPIO_Init(GPIOA, &GPIO_InitStructure);

                                 

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;   //12位分辨率

ADC_InitStructure.ADC_PRESCARE =ADC_PCLK2_PRESCARE_8;//96M时钟8分频

ADC_InitStructure.ADC_Mode =ADC_Mode_Continuous_Scan;//连续扫描模式

ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right;   //数据对齐方式,右对齐     

ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_CC1;

//选择触发源,需要ADC_ExternalTrigConvCmd函数开启外部触发模式        

ADC_Init(ADC1, &ADC_InitStructure);

   

ADC_RegularChannelConfig(ADC1,ADC_Channel_DisableAll, 0, ADC_SampleTime_13_5Cycles);

ADC_RegularChannelConfig(ADC1, ADC_Channel_2,0, ADC_SampleTime_239_5Cycles);

//仅开启ADC1的通道2,采样时间为239.5个周期

ADC_DMACmd(ADC1,ENABLE);   //启用DMA传输功能                                                  

ADC_Cmd(ADC1, ENABLE);                                                        

}

这样就把PA2配置成了ADC1通道2的输入,每次转换完成后,数据将会保存在独立寄存器ADC1->ADDR2和共用寄存器ADC1->ADDATA中,同时ADC将会发出DMA传输请求。

下面我们来配置DMA传输。

DMA介绍与配置

DMA(DirectMemory Access,直接内存存取)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 任何干预,通过 DMA 数据可以快速地移动。这就节省了 CPU 的资源来做其他操作。

MM32SPIN27的DMA 控制器有 5个通道,每个通道都直接连接专用的硬件 DMA 请求(具体可以在用户手册中有描述),每个通道都同样支持软件触发。每个通道都有 3个事件标志:DMA 半传输, DMA 传输完成和 DMA 传输出错。

在5个通道的请求之间的优先权可以通过软件编程设置 (共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定 (请求 0 优先于请求 1,依此类推)。


图2 DMA功能框图

下面我们来配置DMA将ADC采集的数据传输到数组中:

void DMAInit(void)

{

DMA_InitTypeDef DMA_InitStructure;

NVIC_InitTypeDef NVIC_InitStruct;

   

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

DMA_DeInit(DMA1_Channel1);

      

DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&(ADC1->ADDATA);//外设地址

DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&ADCValue;     //内存地址

DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralSRC;             //方向

DMA_InitStructure.DMA_BufferSize = 20;                                     //传输数量

DMA_InitStructure.DMA_PeripheralInc =DMA_PeripheralInc_Disable;//外设地址固定

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址自增

DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord;

DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_HalfWord;

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                 //循环传输

DMA_InitStructure.DMA_Priority =DMA_Priority_High;                 //传输优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                   //外设与内存交互

DMA_Init(DMA1_Channel1,&DMA_InitStructure);

DMA_Cmd(DMA1_Channel1, ENABLE);

   

//NVIC中断设定

DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);                              

NVIC_InitStruct.NVIC_IRQChannel =DMA1_Channel1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPriority = 0;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);

}

void DMA1_Channel1_IRQHandler(void)

{

ADC_SoftwareStartConvCmd(ADC1, DISABLE);

//DMA传输20个数据完成,标志置1

DMA_ClearITPendingBit(DMA1_IT_TC1);                                        

ADCflag = 1;                                                                

}

配置完成后,DMA控制器收到ADC产生的请求后,将会传输一次数据到ADCValue数组中,并执行一次 DMA_CNDTRx 寄存器的递减操作和内存地址自动增量操作,直到传输完成20个数据,关闭ADC转换,并将标志ADCflag置1。

接下来就需要根据不同的应用来处理数据了。

简单应用:2Y0A21YK0F红外测距模块

2Y0A21YK0F是一个有效距离为10cm~80cm的红外测距模块,由PSD(位置敏感探测器)、IRED(红外发射二极管)和信号处理电路组成。采用三角测距法,目标反射率、环境温度和工作时间的变化对距离检测影响较小,在V0输出与检测距离相对应的电压。


图3 2Y0A21YK0F红外测距模块


图4 输出电压与距离倒数的关系

根据图4中给出的关系,将ADC中的数据进行滤波和计算,就可以得到所需的距离信息了。

程序配置:

void ADCFilter(void)                                                           

{

static u16 cntFilter;

static u32 lADCFilterValue = 0;

for(cntFilter = 0; cntFilter < 20;cntFilter++)

{

lADCFilterValue += ADCValue[cntFilter];

}

cntFilter = 0;

   

ADCFilterflag = 1;

ADCFilterValue = (lADCFilterValue /20 * 30 +ADCFilterValue * 70) / 100;

lADCFilterValue = 0;

}

void Get_ADCVolatge(void)                                                       

{ 

ADCVolatge = ((float)ADCFilterValue / 4095) * 5;//使用5V电源

}

int main(void)

{  

char printBuf[64];

float Distance;

DMAInit();                                                                 

ADCInit(ADC_Channel_2);                                                    

ADC_SoftwareStartConvCmd(ADC1, ENABLE);   //软件使能AD转换

Uart_ConfigInit(9600);   //UART初始化

while(1){

if(ADCflag){

ADCflag = 0;

ADCFilter();  //滤波

if(ADCFilterflag){

ADCFilterflag = 0;

Get_ADCVolatge();       //寄存器度数转为电压

Distance = 0.3/ADCVolatge;//由电压计算距离,需要根据具体环境调整

UartSendGroup((u8*)printBuf,sprintf(printBuf,"Distance = %fm\r\n",Distance));

ADC_SoftwareStartConvCmd(ADC1, ENABLE);    //软件使能AD转换                   

}

}

}

}

程序运行结果:


图5 程序运行结果

连接实验板与红外测距模块,编译下载运行程序,按照模块说明,使用一本白色封面的书本,接近再远离模块,通过不断变化距离,我们可以从UART串口看到程序将计算得到的结果(图5)。在这个过程中,ADC将模拟信号转换成数字信号,通过DMA传输到内存中,经过MCU的处理,最终得到了一个相对准确的距离数据。

来源: 灵动MM32MCU

围观 396

本章节将与大家一起配置I2C接口来控制一个OLED模块。

I2C介绍

IIC 即Inter-Integrated Circuit(集成电路总线),是由飞利浦半导体公司在八十年代初设计出来的,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。


图1 I2C主设备与从设备

I2C总线采用一条数据线(SDA),加一条时钟线(SCL)来完成数据的传输及外围器件的扩展。每个器件都有一个唯一的地址识别,而且都可以作为一个发送或接收器。除了发送器和接收器外,器件在执行数据传输时也可以被看做是主机或者从机。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机。

在SPIN27上的I2C拥有如下特性:

• 半双工同步操作
• 支持主从模式
• 支持 7 位地址和 10位地址
• 支持标准模式 100Kbps,快速模式 400Kbps
• 产生 Start、 Stop、重新发 Start、应答 Acknowledge信号检测
• 在主模式下只支持一个主机
• 分别有 2字节的发送和接收缓冲
• 在 SCL和 SDA上增加了无毛刺电路
• 支持 DMA 操作
• 支持中断和查询操作

I2C协议

在I2C中,当总线处于空闲状态时,SCL和SDA同时被外部上拉电阻拉为高电平。在SCL线是高电平时,SDA线从高电平向低电平切换表示起始条件。当主机结束传输时要发送停止条件。在SCL线是高电平,SDA线由低电平向高电平切换表示停止条件。下图显示了起始和停止条件的时序图。


图2 起始和停止条件

I2C总线的数据都是以字节(8位)的方式传送的,每个数据字节在传送时都是高位(MSB)在前。数据传输过程中,当SCL为1时,SDA必须保持稳定。发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(把数据总线的电平拉低)来表示数据成功接收;接收器不拉低数据总线表示一个NACK,NACK有两种用途:在主机发送从机接收时表示未成功接收数据字节,在从机发送主机接收时表示传送数据结束。

下面是主机对从机的读写过程:

写通讯过程:

1、主机在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2、发送一个地址字节(包括7位地址码和一位R/W);
3、当从机检测到主机发送的地址与自己的地址相同时发送一个应答信号(ACK);
4、主机收到ACK后开始发送第一个数据字节;
5、从机收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束
6、主机发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

读通讯过程:

1、主机在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2、发送一个地址字节(包括7位地址码和一位R/W);
3、当从机检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4、主机收到ACK后释放数据总线,开始接收第一个数据字节;
5、主机收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;
6、主机发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

I2C有两种地址格式: 7位的地址格式和 10位的地址格式。

7位的地址格式:

在起始条件(S)后发送的一个字节的前7位(bit 7:1)为从机地址,最低位(bit 0)是数据方向位,当bit 0为0,表示主机写数据到从机,1表示主机从从机读数据。


图3 7位的地址格式

10 位的地址格式:

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


图4 10位的地址格式

下面我们一起来配置MM32SPIN27的I2C模块进行OLED屏的显示功能:

I2C配置:

void I2CInitMasterMode()

{

I2C_InitTypeDef I2C_InitStructure;

GPIO_InitTypeDef  GPIO_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE); //开启GPIO时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); //开启I2C1时钟

 

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8 | GPIO_Pin_9;  //I2C1重映射IO口

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//保持总线空闲即CLK&DATA为高

GPIO_Init(GPIOB, &GPIO_InitStructure);   

 

I2C_InitStructure.I2C_Mode = I2C_Mode_MASTER; //主模式

I2C_InitStructure.I2C_OwnAddress =FLASH_DEVICE_ADDR; //从机地址

I2C_InitStructure.I2C_Speed =I2C_Speed_STANDARD;  //标准速率

I2C_InitStructure.I2C_ClockSpeed = 100000; //100K

I2C_Init(I2C1, &I2C_InitStructure);

 

NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;//I2C中断设置

NVIC_InitStructure.NVIC_IRQChannelPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

 

I2C_ClearITPendingBit(I2C1, I2C_IT_RX_FULL |I2C_IT_TX_EMPTY);//请中断标志位

I2C_ITConfig(I2C1,I2C_IT_RX_FULL|I2C_IT_TX_EMPTY, ENABLE);//开启I2C中断

 

I2C_Cmd(I2C1, ENABLE);    //使能I2C

 

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //需要外加上拉

GPIO_Init(GPIOB, &GPIO_InitStructure);

 

GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_1);//设置GPIO的复用功能

GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_1);

}

目标地址设置函数:

void I2CSetDeviceAddr(unsigned chardeviceaddr) 

{

GPIO_InitTypeDef  GPIO_InitStructure;

   

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8 | GPIO_Pin_9;  //I2C1重映射IO口

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//保持总线空闲即CLK&DATA为高

GPIO_Init(GPIOB, &GPIO_InitStructure);   

   

I2C_Cmd(I2C1,DISABLE);

I2C_Send7bitAddress(I2C1, deviceaddr ,I2C_Direction_Transmitter);//设置从机地址

I2C_Cmd(I2C1, ENABLE);

   

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; 

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

发送与接收检查:

void I2CTXEmptyCheck(I2C_TypeDef *I2Cx)

{

while(1)

if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TX_EMPTY))

break;

}

void I2CRXFullCheck(I2C_TypeDef *I2Cx)

{

while(1)

if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RX_FULL))

break;

}

库函数中还有一些常用的操作函数,如发送函数I2C_SendData()和停止函数I2C_GenerateSTOP()等,下面我们连接上一个I2C从机,使用上面几个函数发送几个数据,从逻辑分析仪,可以得到下图所示波形


图5 数据发送

可以看到,图中,从机的地址为0x78(包含读/写位),发送的几个数据也得到了ACK回应。

I2C的简单配置就完成了。在实际应用中我们需要根据不同器件,对数据进行处理,以实现所需的功能。

下面,我们以市面上常见的基于SSD1303的OLED显示模块为例,介绍一个SPIN27的I2C接口应用。

OLED应用

SSD1303是一个带有单芯片的单色OLED显示模块,支持最大显示分辨率132x64。

模块内嵌了对比度控制、显存和振荡器,减少了外部组件的数量和功耗。它适用于许多小型便携式应用,例如手机子显示屏、计算器、MP3播放器等显示类应用。

I2C接口应用代码:

void WriteCmdStart()

{

I2C_SendData(I2C1,0x80);//寄存器地址

I2CTXEmptyCheck(I2C1);

}

 

void WriteDataStart()

{

I2C_SendData(I2C1,0x40);//寄存器地址

I2CTXEmptyCheck(I2C1);

}

 

void WriteEnd()

{

delay_us(10);

I2C_GenerateSTOP( I2C1, ENABLE );

}

 

void WriteCmd(u8 command)

{

I2C_SendData(I2C1,command);

I2CTXEmptyCheck(I2C1);

}

 

void WriteData(u8 data)

{

I2C_SendData(I2C1,data);

I2CTXEmptyCheck(I2C1);

}

 

void OLED_Init(void)

{

delay_ms(100); //延时

 

WriteCmdStart();

WriteCmd(0xAE); //display off

WriteCmd(0x20); //Set Memory AddressingMode   

WriteCmd(0x10); //00,Horizontal AddressingMode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid

WriteCmd(0xb0); //Set Page Start Address forPage Addressing Mode,0-7

WriteCmd(0xc8); //Set COM Output Scan Direction

WriteCmd(0x00); //---set low column address

WriteCmd(0x10); //---set high column address

WriteCmd(0x40); //--set start line address

WriteCmd(0x81); //--set contrast controlregister

WriteCmd(0xff); //亮度调节 0x00~0xff

WriteCmd(0xa1); //--set segment re-map 0 to 127

WriteCmd(0xa6); //--set normal display

WriteCmd(0xa8); //--set multiplex ratio(1 to64)

WriteCmd(0x3F); //

WriteCmd(0xa4); //0xa4,Output follows RAMcontent;0xa5,Output ignores RAM content

WriteCmd(0xd3); //-set display offset

WriteCmd(0x00); //-not offset

WriteCmd(0xd5); //--set display clock divideratio/oscillator frequency

WriteCmd(0xf0); //--set divide ratio

WriteCmd(0xd9); //--set pre-charge period

WriteCmd(0x22); //

WriteCmd(0xda); //--set com pins hardwareconfiguration

WriteCmd(0x12);

WriteCmd(0xdb); //--set vcomh

WriteCmd(0x20); //0x20,0.77xVcc

WriteCmd(0x8d); //--set DC-DC enable

WriteCmd(0x14); //

WriteCmd(0xaf); //--turn on oled panel

WriteEnd();

}

 

void OLED_ON(void)

{

WriteCmdStart();

WriteCmd(0X8D); //设置电荷泵

WriteCmd(0X14); //开启电荷泵

WriteCmd(0XAF); //OLED唤醒

WriteEnd();

}

void OLED_Fill(unsigned char fill_Data)//全屏填充

{

unsigned char m,n;

for(m=0;m<8;m++)

{

WriteCmdStart();

WriteCmd(0xb0+m);       //page0-page1

WriteCmd(0x00);     //low column start address

WriteCmd(0x10);     //high column start address

WriteEnd();

WriteDataStart();

for(n=0;n<128;n++){

WriteData(fill_Data);

}

WriteEnd();    

}

}

 

void OLED_CLS(void)//清屏

{

OLED_Fill(0x00);

}

int OLED_ShowStr(unsigned char x, unsigned chary, unsigned char ch[], unsigned char TextSize)

{

unsigned char c = 0,i = 0,j = 0;

switch(TextSize){

case 1:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

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

WriteData(Font_6x8_h[c*8+i]);

WriteEnd();    

y--;

j++;

}

}break;

 

case 2:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

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

WriteData(Font_8x12_h[c*12+i]);

WriteEnd();    

y--;

j++;

}

}break;

case 3:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

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

WriteData(Font_8x16_h[c*16+i]);

WriteEnd();    

y--;

j++;

}

}break;

 

default:

return 1;

}

return 0;

}

void OLED_SetPos(unsigned char x, unsigned chary) //设置起始点坐标

{

WriteCmdStart();

WriteCmd(0xb0+(y&0x0f));

WriteEnd();    

WriteCmdStart();

WriteCmd(((x&0xf0)>>4)|0x10);

WriteEnd();    

WriteCmdStart();

WriteCmd((x&0x0f)|0x01);

WriteEnd();    

}

//Main函数:

int main(void)

{

unsigned char str1[]="Hello!";

unsigned char str2[]="MM32";

unsigned char str3[]="SPIN27";

delay_init();//启动外部晶振

uart_initwBaudRate(115200);

printf("uart ok\r\n");

 

I2CInitMasterMode() ;

I2CSetDeviceAddr(OLED_DEVICE_ADDR);

OLED_Init();//关闭显示并初始化

OLED_ON();//OLED唤醒

OLED_Fill(0xFF);//点亮所有像素点

OLED_CLS();//清屏

OLED_ShowStr(5,6,str1,3);

OLED_ShowStr(21,5,str2,3);

OLED_ShowStr(37,6,str3,3);

while(1)             

{

}

}

由于篇幅限制,在本篇文章中字库部分没有提供对应的代码。连接好线路(VCC\GND\SCL\SDA4线),编译程序下载,我们就可以看到屏幕亮起来了,显示“Hello! MM32 SPIN27”。


图6 OLED显示效果

同样,我们也可以通过逻辑分析仪,分析芯片与模块之间的数据交互,进一步了解I2C通信协议,进一步熟悉I2C的使用。

来源:灵动MM32MCU

围观 135

本章节将与大家一起配置SPI使用GY-63气压传感器模块。

SPI介绍

SPI(Serial Peripheral Interface, 同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,该总线大量用在与EEPROM、ADC、FRAM和显示驱动器之类的慢速外设器件通信。

SPI 通信原理比 I2C要简单,它主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,如图1,标准的 SPI 是 4 根线,分别是 SSEL(片选,也写作 SCS)、SCLK(时钟,也写作 SCK)、MOSI(主机输出从机输入Master Output/Slave Input)和 MISO(主机输入从机输出 Master Input/Slave Output)。


图1 SPI单主单从应用


图2 SPI时序图

SPI接口有四种不同的数据传输时序,取决于CPOL和CPHL这两位的组合。图2中表现了这四种时序,时序与CPOL、CPHA的关系也可以从图中看出。在通信时,主机和从机必须要配置成相同的时序模式。

CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

在SPIN27上的SPI拥有如下特性:
• 完全兼容 Motorola 的 SPI 规格

• 支持各 8 个对应配置数据位(Data size)的发送缓冲器和接收缓冲器
• 支持 DMA 请求
• 在 3 根线上支持全双工同步传输
• 16 位的可编程波特率生成器
• 支持主机模式和从机模式

• 支持一个主机多个从机操作
• 主机模式下SPI的时钟最快可高达36M,从机模式下SPI的时钟最快可高达18M
• 可编程的时钟极性和相位
• 可编程的数据顺序, MSB在前或者LSB在前
• 支持 1 ∼ 32 位的数据位长度同时发送和接收注:除了 8 位数据收发,其余 1 ∼ 32 位数据收发只支持 LSB 模式,不支持 MSB 模式。

下面我们简单的配置一下MM32SPIN27的SPI主机模式:

SPI主机模式配置:

void SPIM_Init(SPI_TypeDef* SPIx,unsigned short spi_baud_div)

{

SPI_InitTypeDef SPI_InitStructure;

GPIO_InitTypeDef  GPIO_InitStructure;

      

if(SPIx==SPI1)

{

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

 

GPIO_PinAFConfig(GPIOA, GPIO_PinSource4,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOA, GPIO_PinSource5,GPIO_AF_0);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource6,GPIO_AF_0); 

GPIO_PinAFConfig(GPIOA, GPIO_PinSource7,GPIO_AF_0);

               

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4;               //spi1_cs  pa4

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5;               //spi1_sck  pa5

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

      

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;               //spi1_mosi  pa7

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;               //spi1_miso  pa6

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;              //上拉输入  

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

if(SPIx==SPI2)

{

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

 

GPIO_PinAFConfig(GPIOB, GPIO_PinSource12,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOB, GPIO_PinSource13,GPIO_AF_0);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource14,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOB, GPIO_PinSource15,GPIO_AF_0);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;            

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13;             //spi2_sck  pb13

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;             //spi2_mosi  pb15

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

                    

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;              //spi2_miso  pb14

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;              //上拉输入  

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_DataWidth = SPI_DataWidth_8b;  

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_baud_div;     //时钟分频

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;         

SPI_Init(SPIx, &SPI_InitStructure);

      

SPI_Cmd(SPIx, ENABLE);         

SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Tx);            //启用发送功能

SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Rx);           //启用接收功能

}

下面是一些常用操作函数:

void SPIM_CSLow(SPI_TypeDef* SPIx)

{

SPI_CSInternalSelected(SPIx, SPI_CS_BIT0,ENABLE);

}

void SPIM_CSHigh(SPI_TypeDef* SPIx)

{

SPI_CSInternalSelected(SPIx, SPI_CS_BIT0,DISABLE);

}

unsigned int SPIMReadWriteByte(SPI_TypeDef* SPIx,unsigned char tx_data)

{

SPI_SendData(SPIx, tx_data);   

while(!SPI_GetFlagStatus(SPIx, SPI_FLAG_RXAVL));

return SPI_ReceiveData(SPIx);

}

接下来,调用上面的SPIM_Init()函数,就可以初始化SPI主机模式了

按照SPI标准,发送和接收数据的时候,需要先用SPIM_CSLow()或SPIM_CSHigh()设置片选信号,再用SPIMReadWriteByte()函数同时收发数据,完成操作后再设置片选信号为空闲,以此完成一次通信。


图3 数据收发

可以看到,图中,CS输出低电平,开始一次通信,主机发送的数据为0xA2,0x00,0x00,从机发送的数据为0xFE,0xB9,0xB9,CS输出高电平,此次通信结束。

下面,我们以市面上常见的基于MS5611的GY-63气压传感器为例,介绍一个SPIN27的SPI接口应用。

气压传感器应用

MS5611气压传感器是由MEAS(瑞士)推出的一款SPI和I²C总线接口的新一代高分辨率气压传感器,分辨率可达到10cm。MS5611提供了一个精确的24位数字压力值和温度值以及不同的操作模式,可以提高转换速度并优化电流消耗。5.0毫米×3.0毫米×1.0毫米的小尺寸可以集成在移动设备中。

可以从下面的网址获取详细数据手册:

https://www.alldatasheetcn.com/datasheet-pdf/pdf/880802/TEC/MS5611-01BA0...


图4 MS5611和GY-63模块

GY-63程序代码:

u16 PROMData[8]={0};  //实际使用6个u16

uint32_t D1_Pres,D2_Temp; // 存放数字压力和温度

float Pressure; //温度补偿大气压

float dT,Temperature,Temperature2;//实际和参考温度之间的差异,实际温度,中间值

double OFF,SENS;  //实际温度抵消,实际温度灵敏度

float Aux,OFF2,SENS2;  //温度校验值

 

void SPIM_Test(SPI_TypeDef* SPIx)

{

int i =0;

SPIM_Init(SPIx,0x4);//12MHz

      

Reset_GY_63(SPIx);     //复位,0x1E

ReadPROM(SPIx,PROMData);

GetTemperature(SPIx,Convert_D2_OSR_4096); //0x58

GetPressure(SPIx,Convert_D1_OSR_4096);             //0x48

      

printf("\nPROM Data: \r\n");

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

printf("PROMData%d = %4x\r\n",i,PROMData[i]);

printf("\r\nReadTemperature = %x\r\n",D2_Temp);

printf("ReadPressure = %x\r\n",D1_Pres);

printf("CalculateTemperature = %f\r\n",Temperature/100);

printf("CalculatePressure = %f\r\n",Pressure);

}

void SetConvertMode(SPI_TypeDef*SPIx,u8 command)

{

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,command);

SPIM_CSHigh(SPIx);

delay_ms(10);              //d1_4096 ≥ 8.22ms

}

u32 ReadADCData(SPI_TypeDef*SPIx)

{

u32 ADCData=0;

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,ADC_Read);

ADCData=SPIMReadWriteByte( SPIx,0);

ADCData=ADCData<<8 |SPIMReadWriteByte( SPIx,0);

ADCData=ADCData<<8 |SPIMReadWriteByte( SPIx,0);

SPIM_CSHigh(SPIx);

return ADCData&0xFFFFFF;

}

 

void Reset_GY_63(SPI_TypeDef*SPIx)

{

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,GY_63_RESET);

SPIM_CSHigh(SPIx);

delay_ms(5);  //等待复位,≥2.8ms

}

 

void ReadPROM(SPI_TypeDef*SPIx,u16 * PROMData_t)

{

int PROM_Addr = 0;

for (PROM_Addr=0xA0;PROM_Addr<=0xAE;PROM_Addr+=2)

{

SPIM_CSLow(SPIx);

SPIMReadWriteByte(SPIx,PROM_Addr);//PROM

*PROMData_t = SPIMReadWriteByte( SPIx,0);//PROM

*PROMData_t = *PROMData_t<<8|SPIMReadWriteByte( SPIx,0);//PROM

SPIM_CSHigh(SPIx);

PROMData_t++;

}

}

 

void GetTemperature(SPI_TypeDef*SPIx,u16 OSRTemp)

{

SetConvertMode(SPIx,OSRTemp);

D2_Temp= ReadADCData(SPIx);

      

dT=D2_Temp - (((uint32_t)PROMData[5])<<8);

Temperature=2000+dT*((uint32_t)PROMData[6])/8388608;       //算出温度值的100倍

}

void GetPressure(SPI_TypeDef*SPIx,u16 OSRTemp)

{

SetConvertMode(SPIx,OSRTemp);

D1_Pres = ReadADCData(SPIx);

OFF=(uint32_t)(PROMData[2]<<16)+((uint32_t)PROMData[4]*dT)/128.0;

SENS=(uint32_t)(PROMData[1]<<15)+((uint32_t)PROMData[3]*dT)/256.0;

//温度补偿

if(Temperature < 2000)

{

Temperature2 = (dT*dT) / 0x80000000;

Aux = (Temperature-2000)*(Temperature-2000);

OFF2 = 2.5*Aux;

SENS2 = 1.25*Aux;

if(Temperature < -1500)

{

Aux = (Temperature+1500)*(Temperature+1500);

OFF2 = OFF2 + 7*Aux;

SENS2 = SENS + 5.5*Aux;

}

}else  //(Temperature > 2000)

{

Temperature2 = 0;

OFF2 = 0;

SENS2 = 0;

}

Temperature = Temperature - Temperature2;

OFF = OFF - OFF2;

SENS = SENS - SENS2;    

 

Pressure=(D1_Pres*SENS/2097152.0-OFF)/32768.0;

}

int main(void)

{

Uart_ConfigInit(9600);

delay_init();

      

SPIM_Test(SPI1);        

while(1)

{

}

}

程序运行结果:

按照数据手册,连接MCU和模块对应的引脚,模块的PS引脚接地选择SPI模式,编译下载运行程序,在UART输出如下结果:


图5 程序运行结果

从上图,我们可以看到MCU成功读取到模块的PROM和两种模式下ADC的数据,并且通过初步计算得到了温度和气压。

同样,我们也可以通过逻辑分析仪,分析芯片与模块之间的数据交互,进一步了解SPI通信,进一步熟悉芯片的使用。

来源:灵动MM32MCU

围观 407

在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,因此产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。MM32内置两个看门狗(独立看门狗和窗口看门狗),提供了更高的安全性、时间的精确性和使用的灵活性,可以用来检测和解决由软件错误引起的故障,其中可以使用独立看门狗在stop低功耗模式下进行MCU不复位唤醒功能。

独立看门狗与窗口看门狗的区别

同样是看门狗,独立看门狗(IWDG)和窗口看门狗(WWDG)十分相似,但还是有些不同之处需要注意:

独立看门狗(IWDG)由专门的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效,可在停止(STOP)和待机(STANDBY)模式下工作。

窗口看门狗(WWDG)则由从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作。

图1 独立看门狗框图

IWDG介绍

IWDG 最适合应用于那些需要看门狗作为一个正在主程序外,能够完全独立工作,并且对时间精度要求低的场合,可以在低功耗的停机和待机模式下唤醒或复位MCU。当计数器达到给定的超时值时,触发一个产生系统复位。在SPIN2x系列中,加入了IWDG中断功能。

• 自由运行的递减计数器

• 时钟由独立的振荡器提供(可在停止和待机模式下工作)

• 看门狗被激活后,则在计数器计数至 0x0000 时产生复位或外部中断信号。

注:MM32不同型号IWDG中断功能和中断线的设置不同,本文仅介绍MM32SPIN2x系列。

MM32SPIN2x系列芯片的独立看门狗的时钟来源为LSI(40kHz),根据预分频寄存器的值,LSI可以最多256分频,最少4分频,配合重装载寄存器,超时时间可以设置为最少0.1ms,最多26.2s,如下图

图2 看门狗出超时时间

IWDG使用

使用IWDG的基本步骤如下:
1、(可选)配置看门狗外部中断线和中断函数;
2、配置独立看门狗的预分频系数和重装载值;
3、重载计数值喂狗;
4、启动看门狗。

在SPIN2x系列中,IWDG使用外部中断线24,与WWDG使用同一个中断处理函数,若使用IWDG中断,再退出中断之后需要重新配置IWDG。

下面我们简单的配置一下MM32SPIN27的IWDG中断模式:

1、(可选)配置看门狗外部中断线和中断函数

void Iwdg_Interrupt_Config()
{
NVIC_InitTypeDef NVIC_InitSturcture;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能外部中断
EXTI_InitStructure.EXTI_Line = EXTI_Line24;                      //IWDG中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;                  //上升沿触发
EXTI_Init(&EXTI_InitStructure);

NVIC_InitSturcture.NVIC_IRQChannel = WWDG_IRQn; 
NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitSturcture.NVIC_IRQChannelPriority = 0;
NVIC_Init(&NVIC_InitSturcture);
GPIO_LED_Config();//使用LED显示看门狗是否进入中断
}

void WWDG_IRQHandler()//IWDG与WWDG使用同一个中断函数
{
GPIOB->ODR ^= GPIO_Pin_4;
iwdg_flag = 1;
EXTI->PR |= 0x1 << 24;
IWDG_ClearIT();
UartSendGroup((u8 *)printBuf, sprintf(printBuf, "interrupt\r\n"));

}

2、配置独立看门狗

void PVU_CheckStatus(void)//查询看门狗预分频更新标志
{
while (1)
if (IWDG_GetFlagStatus(IWDG_FLAG_PVU) == RESET)
break;
}

void RVU_CheckStatus(void)//查询看门狗计数器重载值更新标志
{
while (1)
if (IWDG_GetFlagStatus(IWDG_FLAG_RVU) == RESET)
break;
}

void Interrput_Iwdg_ON(unsigned short int IWDG_Prescaler, unsigned short int Reload)
{
RCC_LSICmd(ENABLE);//启用LSI并等待时钟就绪
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
PVU_CheckStatus();//配置时钟分频
IWDG_WriteAccessCmd(0x5555);
IWDG_SetPrescaler(IWDG_Prescaler);
RVU_CheckStatus();//配置重装载值
IWDG_WriteAccessCmd(0x5555);
IWDG_SetReload(Reload & 0xfff);
IWDG_EnableIT(); //启用中断模式
IWDG_ReloadCounter();//使能IWDG前先喂狗
IWDG_Enable();
}

3、时钟配置函数

void HSI_SYSCLK(void)//设置为HIS 8分频
{
RCC_HSICmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY)==RESET);
RCC->CFGR &=~ 0xf;
while((RCC->CFGR&0xf) != 0x0);
}

4、main函数

int main(void)
{
HSI_SYSCLK();        //设置为HIS 8分频
Uart_ConfigInit(9600);
UartSendGroup((u8 *)printBuf, sprintf(printBuf, "sprintf ok\r\n"));

Iwdg_Interrupt_Config();//中断配置
Interrput_Iwdg_ON(IWDG_Prescaler_16, 0xFFF);     //配置并使能IWDG

PWR_EnterSTOPMode(0, PWR_STOPEntry_WFI);//进入STOP模式
while (1)
{
if (iwdg_flag)
{
iwdg_flag = 0;
Interrput_Iwdg_ON(IWDG_Prescaler_16, 0xFFF);           //退出中断函数后重新配置IWDG,建议在清除中断标志位之后有一个延迟在执行这一函数
PWR_EnterSTOPMode(0, PWR_STOPEntry_WFI);        //进入STOP模式
}
// IWDG_ReloadCounter(); //取消注释开启喂狗
}
}

运行结果

编译下载并运行程序,在GPIO的B4引脚上接上一个LED灯,我们可以看到LED灯在闪烁,在UART定时输出”interrupt”,如图3。使用设备记录MCU的供电电流,可以得到图4,在STOP模式下电流仅为3.8uA,持续1.36s唤醒,电流增加为1.65mA。我们可以改变Interrput_Iwdg_ON()函数中的分频值和重装载值来得到想要的唤醒间隔。

图3 UART输出

到这里,一个简单的IWDG中断程序就配置好了。

相比复位模式,在IWDG的中断模式中,我们可以根据需要,在超时的时候向外界发送一些信息,比如MCU的运行状态,更加方便的调试程序。

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

围观 202

现在的采用电池供电方式电机控制类的小型化电子产品,因为电池容量和体积等限制对功耗控制的要求越来越高。电池的容量和体积限制如何让用户能够更长时间使用产品,产品功耗的问题是经常让产品设计者头痛而又不得不面对的一个问题。包括:剃须刀、电动牙刷、洁面仪等。

电机类产品功耗主要是电机驱动模块占据大部分,其他外设部分的电流消耗占小部分,以单片机为核心的系统,其功耗主要由单片机功耗和单片机外围电路功耗组成。MM32SPIN2x有高达两个高级定时器,能够驱动双电机应用方案,可以提高总功率效率和性能。今天主要从MCU部分讲解如何降低单片机系统的功耗,需要从硬件和软件两方面入手。本章节将从MM32SPIN2x系列MCU的配置来介绍如何降低MCU的功耗。

如何降低MCU功耗

在系统或电源复位以后,微控制器处于运行状态。当CPU 不需继续运行时,可以利用多种低功耗模式来节省功耗,例如等待某个外部事件时。用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。

降低功耗方法:

• 睡眠模式(CPU 停止,所有外设包括CPU 的外设,如NVIC、系统时钟(SysTick) 等仍在运行)

• 停机模式(所有的时钟都已停止,寄存器和SRAM 的内容依然保存)

• 待机模式(1.5V电源关闭,除了备用电路和备份域外,寄存器和SRAM 的内容全部丢失)

此外,在运行模式下,可以通过以下方式中的一种降低功耗:

• 降低系统时钟

• 关闭APB 和AHB 总线上未被使用的外设时钟

• 降低GPIO速度或者其他外设的速率

• 对与未使用的GPIO配置成模拟输入模式,针对有外部上拉或者下拉的GPIO口,在进入低功耗禁止设置相反的电平状态。

图1 低功耗模式一览

运行模式低功耗

1、降低系统时钟

在运行模式下,系统的主频越高,MCU处理指令的速度越快,但是同时功耗也会越大,设计人员需要跟实际应用对MCU处理性能和功耗之间找一个平衡点。MM32SPIN2x可以根据PLL倍频器设置最高96MHz系统时钟,可以通过对预分频寄存器进行编程,可以降低任意一个时钟(HCLK、PCLK1、PCLK2) 的速度。

2、外设时钟的控制

在运行模式下,任何时候都可以通过关闭外设时钟 (HCLK 和 PCLKx)来减少功耗,在不需要使用的外设建议不要开启外设时钟。

3、睡眠模式

通过执行 WFI或 WFE 指令进入睡眠状态。根据 CPU系统控制寄存器中的 SLEEPONEXIT位的值,有两种选项可用于选择睡眠模式进入机制:
• SLEEP-NOW:如果 SLEEPONEXIT 位被清除,当 WFI或 WFE被执行时,微控制器立即进入睡眠模式。
• SLEEP-ON-EXIT:如果 SLEEPONEXIT 位被置位,系统从最低优先级的中断处理程序中退出时,微控制器就立即进入睡眠模式。

在睡眠模式下,所有的 I/O 引脚都保持它们在运行模式时的状态。

对于M0内核的MCU,有两个通用的函数__WFI()和__WFE()可以进入睡眠模式。

图2 睡眠模式下的最大功耗,代码运行在FLASH中

4、停机模式
停机模式是在 CPU 的深睡眠模式基础上结合了外设的时钟控制机制,在停机模式下电压调节器可运行在正常模式。此时在1.5V 供电区域的的所有时钟都被停止, PLL、 HSI 和HSE 振荡器的功能被禁止, SRAM和寄存器内容被保留下来。

在停机模式下,所有的 I/O 引脚都保持它们在运行模式时的状态。

可以通过对独立的控制位进行编程,可选择以下功能:
•独立看门狗 (IWDG):可通过写入看门狗的键寄存器或硬件选择来启动 IWDG。
•内部振荡器 (LSI振荡器):通过控制/状态寄存器 (RCC_CSR)的 LSION位来设置。

在停机模式下,如果在进入该模式前 ADC 没有被关闭,那么这些外设仍然消耗电流。通过设置寄存器 ADC_CR2的 ADON位可关闭这个外设。其他没有使用的GPIO 需要设置模拟输入,否则有电流消耗。

如何进入和退出停机模式可以参考图1。在库函数中,MM32SPIN2x定义了一个PWR_EnterSTOPMode()函数,方便快速配置停机模式。

注:当一个中断或唤醒事件导致退出停机模式时,HSI 振荡器被选为系统时钟。时钟频率为 HSI的 6 分频。

5、待机模式

待机模式可实现系统的最低功耗。该模式是在 CPU 深睡眠模式时关闭电压调节器。整个1.5V供电区域被断电。 PLL、 HSI 和 HSE 振荡器也被断电。 SRAM和寄存器内容丢失。只有备份的寄存器和待机电路维持供电。

可以通过设置独立的控制位,选择以下待机模式的功能:
• 独立看门狗 (IWDG):可通过写入看门狗的键寄存器或硬件选择来启动 IWDG。
• 内部振荡器 (LSI振荡器):通过控制/状态寄存器 (RCC_CSR)的 LSION位来设置

如何进入和退出待机模式可以参考图1。在库函数中,MM32SPINx定义了一个PWR_EnterSTANDBYMode ()函数,方便快速配置停机模式。

退出待机模式后,除了:电源控制/状态寄存器 (PWR_CSR),所有寄存器被复位。

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

图3 停机和待机模式下的典型和最大电流消耗

小结

常用方法:降低系统时钟、不打开不使用的外设时钟、进入睡眠模式、进入停机模式、进入待机模式;

低功耗模式函数入口:
1) 睡眠模式:__WFI()、__WFE();
2) 停机模式:PWR_EnterSTOPMode();
3) 待机模式:PWR_EnterSTANDBYMode ();

如何退出低功耗模式:
1)睡眠模式:任意中断/唤醒事件;
2)停机模式:任意外部中断、IWDG不复位方式唤醒;
3)待机模式:WKUP引脚的上升沿、 NRST引脚上外部复位、 IWDG复位;

需要注意的:
1)睡眠模式:无唤醒延时;
2)停机模式:PLL、HSI和HSE被禁止,唤醒或中断退出时,HSI/6被选为系统时钟;
3)待机模式:SRAM和寄存器内容丢失,唤醒后的代码执行等同于复位;

通过合理地配置低功耗模式,关闭不使用的外设,降低I/O口速度,我们可以在不影响正常使用的同时,降低MCU运行功耗,延长电池供电设备的使用时间。

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

围观 643
订阅 RSS - MM32SPIN2x