STM32

STM32是STMicroelectronics(意法半导体)推出的一系列基于ARM Cortex-M内核的32位微控制器(MCU)产品。这些微控制器提供了广泛的产品系列,覆盖了多种不同的性能和功能需求,适用于各种应用领域,包括工业控制、汽车电子、消费类电子、医疗设备等。

STM32系列微控制器以其高性能、低功耗、丰富的外设接口和灵活的开发工具而闻名。它们通常具有丰富的存储器、多种通信接口(如UART、SPI、I2C、CAN等)、模拟数字转换器(ADC)、定时器、PWM输出等功能,以满足不同应用场景下的需求。

STM32微控制器通常使用标准的ARM Cortex-M内核,包括Cortex-M0、M0+、M3、M4和M7等,这些内核具有不同的性能和功耗特性,可根据具体应用的需求进行选择。此外,STM32系列还提供了多种封装和引脚配置,以满足不同尺寸和集成度的要求。

STMicroelectronics为STM32系列提供了丰富的开发工具和支持资源,包括基于ARM开发环境的集成开发环境(IDE)、调试器、评估板和参考设计等。这些工具和资源有助于开发人员快速开发和部署他们的应用,并提供了全面的技术支持和文档资料,帮助用户充分发挥STM32微控制器的性能和功能优势。

一个产品的功耗不光是硬件功耗的事,其实软件也是影响整个产品功耗的一个关键因素。进行STM32低功耗产品开发时,可以通过下文GPIO配置方式来减低功耗。

把闲置GPIO输入配置为模拟输入

GPIO始终有一个输入通道,可以是数字或模拟通道。

如果不需要读取GPIO数据,则优先配置为模拟输入。这节省了输入施密特触发器的消耗。

在STM32CubeMX配置中都有这么一个选项:将不用引脚配置为模拟状态。

“”

调节GPIO速度

上升时间,下降时间和最大频率可使用GPIOx_OSPEEDR配置寄存器进行配置。

这种调整对EMI(电磁干扰)和SSO(同时开关输出)有影响,因为开关电流峰值较高。因此必须平衡GPIO性能与噪声。

每个GPIO信号的上升时间和下降时间必须适应与相关信号频率和电路板容性负载兼容的最小值。

不使用时禁用GPIO寄存器时钟

如果某个GPIO组不需要长时间使用,禁用其时钟。

比如:

标准外设库,禁用GPIOA时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE);
br

HAL库,禁止GPIOA时钟:

__HAL_RCC_GPIOA_CLK_DISABLE();

br

进入低功耗模式时配置GPIO

进入低功耗模式时,所有引脚信号必须连接到VDD或接地。

如果GPIO连接到外部接收器(外部元件输入),则必须使用PP或PU/PD强制GPIO信号值。

当GPIO连接到驱动器(外部元件输出或总线)时,驱动器必须提供有效电平( VDD或接地)。如果未定义驱动器电平,则必须使用PU/PD强制GPIO上的信号。

出于实际原因,当GPIO是运行模式下的输入(模拟或数字)时,在低功耗模式下使用输入PU/PD可能更容易;当GPIO是运行模式下的输出时,则使用输出PP。这可以避免在进入或退出停止模式时管理更改。

退出关机模式

退出关机(shut down)模式时, GPIO会在上电复位时重新配置为默认值。

在将它们重新编程为正确值之前,这会需要额外的系统消耗。

如果这是应用程序的问题,则必须使用待机(standby)模式替代关机模式。

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

围观 375

分享这篇文章,谈一下STM32启动流程。如果读者朋友已经有过汇编相关基础,能够够好理解本文内容。汇编语言是比C语言更接近机器底层的编程语言,能让我们更好的理解和操纵硬件底层。

STM32三种启动模式

下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存,这就是所谓的启动过程。

STM32上电或者复位后,代码区始终从0x00000000开始,其实就是将存储空间的地址映射到0x00000000中。三种启动模式如下:

  • 从主闪存存储器启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。主闪存存储器是STM32内置的Flash,作为芯片内置的Flash,是正常的工作模式。一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。
  • 从系统存储器启动。首先控制BOOT0、BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码。系统存储器是芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM区。启动的程序功能由厂家设置。系统存储器存储的其实就是STM32自带的bootloader代码。
  • 从内置SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始。内置SRAM,也就是STM32的内存,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。假如我只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码,用于快速的程序调试,等程序调试完成后,在将程序下载到SRAM中。

用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。STM32三种启动模式对应的存储介质均是芯片内置的,如下图:

“STM32单片机的启动过程"

串口下载程序原理

从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序,这是一块ROM,出厂后无法修改。

一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中。

这个下载方式需要以下步骤:

  • 将BOOT0设置为1,BOOT1设置为0,然后按下复位键,这样才能从系统存储器启动BootLoader;
  • 在BootLoader的帮助下,通过串口下载程序到Flash中;
  • 程序下载完成后,又有需要将BOOT0设置为GND,手动复位,这样,STM32才可以从Flash中启动。

从汇编代码分析STM32启动过程

STM32的启动文件与编译器有关,不同编译器,它的启动文件不同。虽然启动文件(汇编)代码各有不同,但它们原理类似,都属于汇编程序。拿基于MDK-ARM的启动文件来举例,说一下要点内容。在基于MDK的启动文件开始,有一段汇编代码是分配堆栈大小的。

“STM32单片机的启动过程"

这里重点知道堆栈数值大小就行。还有一段AREA(区域),表示分配一段堆栈数据段。可以使用STM32CubeMX对上面的数值大小进行配置:

“STM32单片机的启动过程"

在IAR中,是通过工程配置堆栈大小:

“STM32单片机的启动过程"

看下面的汇编代码,程序上电之后,是跳到Reset_Handler这个位置。

“STM32单片机的启动过程"

知道代码是从Reset_Handler开始执行,再来看如下Reset_Handler汇编代码。在启动的时候,执行了SystemInit这个函数。

“STM32单片机的启动过程"

执行完SystemInit函数,初始化了系统时钟,之后跳转到main函数执行。

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

围观 335

用定时器生成PWM波

PWM全称是Pulse Width Modulation,通过控制高频信号的占空比,眼睛当成低通滤波器,可以控制亮暗。再循环更改pwm的阈值,就弄出了呼吸的效果。

这里采用一个比较简单的方法生成PWM波:设置定时器中断然后根据阈值判断置高和置低。

void TIM3_IRQHandler(void)  
{
        TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  
        if(counter==255)            
            counter = 0;
        else 
            counter +=1;
        if(mode == 0){
            if(counter < pwm)              
                GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1); 
            else 
                GPIO_ResetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);    
        }
        if(mode == 1)
        {
            if(counter < pwm)              
                GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2); 
            else 
                GPIO_ResetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2);     
        }  
        if(mode ==2){
            if(counter < pwm)              
                GPIO_SetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_0); 
            else 
                GPIO_ResetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_0); 
        }
}

程序流程

● 开启外设时钟(GPIO和TIM)

void RCC_Configuration(void)                
{
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);                                                       
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4|RCC_APB1Periph_TIM3, ENABLE); 
}

● 配置GPIO
● 配置时钟, 使能中断(计数阈值,预分频,时钟分频,计数模式)

void tim3()                           //配置TIM3为基本定时器模式 ,约10us触发一次,触发频率约100kHz
{
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;   //定义格式为TIM_TimeBaseInitTypeDef的结构体的名字为TIM_TimeBaseStructure  
TIM_TimeBaseStructure. TIM_Period =9;         //配置计数阈值为9,超过时,自动清零,并触发中断
TIM_TimeBaseStructure.TIM_Prescaler =71;     //    时钟预分频值,除以多少
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // 时钟分频倍数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 计数方式为向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);      //  初始化tim3
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIM3溢出中断标志
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //  使能TIM3的溢出更新中断
TIM_Cmd(TIM3,ENABLE);                     //           使能TIM3
}

● 配置中断优先级

void nvic()                                 //配置中断优先级
{    
 NVIC_InitTypeDef NVIC_InitStructure;  //    //   命名一优先级变量
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);    //     将优先级分组方式配置为group1,有2个抢占(打断)优先级,8个响应优先级
 NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //该中断为TIM4溢出更新中断
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//打断优先级为1,在该组中为较低的,0优先级最高
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级0,打断优先级一样时,0最高
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //  设置使能
 NVIC_Init(&NVIC_InitStructure);                        //  初始化
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //要用同一个Group
 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3 溢出更新中断
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//    打断优先级为1,与上一个相同,不希望中断相互打断对方
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     //  响应优先级1,低于上一个,当两个中断同时来时,上一个先执行
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);
}

● 写中断服务函数

代码实现

为了方便按键检测,除了TIM3配置PWM波之外,TIM4用来检测是否有输入。由于使用开漏输出,这里使用5V电源。

#include "stm32f10x.h"
#include "math.h"
#include "stdio.h"

u8  counter=0; 
int  pwm=100;
int flag=0;
int mode =0;
int velocity =0;
int turning=1;

void RCC_Configuration(void);    //时钟初始化,开启外设时钟
void GPIO_Configuration(void);   //IO口初始化,配置其功能
void tim3(void);                 //定时器tim4初始化配置
void tim4(void);                 //定时器tim4初始化配置
void nvic(void);                 //中断优先级等配置
void exti(void);                 //外部中断配置
void delay_nus(u32);           //72M时钟下,约延时us
void delay_nms(u32);            //72M时钟下,约延时ms
void breathing(int velocity){
        switch(velocity){
                case 0:
                    if(flag)
                            pwm +=1;
                            if(pwm>240) flag=0;
                    if(flag == 0){
                            pwm -=1;
                            if(pwm<10) flag=1;
                    }
                    break;
                case 1:
                    if(flag)
                            pwm +=2;
                            if(pwm>240) flag=0;
                    if(flag == 0){
                            pwm -=2;
                            if(pwm<10) flag=1;
                    }
                    break;
                case 2:
                    if(flag)
                            pwm +=3;
                            if(pwm>240) flag=0;
                    if(flag == 0){
                            pwm -=3;
                            if(pwm<10) flag=1;
                    }
                    break;
        }
}


void assert_failed(uint8_t* file, uint32_t line)
{
    printf("Wrong parameters value: file %s on line %d\r\n", file, line);
    while(1);
}

void TIM4_IRQHandler(void)   //TIM4的溢出更新中断响应函数 ,读取按键输入值,根据输入控制pwm波占空比
{
        u8 key_in1=0x01,key_in2=0x01;
        TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//     清空TIM4溢出中断响应函数标志位
        key_in1= GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_12);  // 读PC12的状态
        key_in2= GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13);// 读PC13的状态
        if(key_in1 && key_in2) turning =1;
        breathing(velocity);
        if(key_in1==0 && turning){
                turning =0;
        velocity = (velocity + 1) % 3;
    }//调速度
    if(key_in2==0 && turning){
                turning =0;
        mode = (mode + 1) % 3;
    }//调颜色
}   


void TIM3_IRQHandler(void)      //    //TIM3的溢出更新中断响应函数,产生pwm波
{
        TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //   //  清空TIM3溢出中断响应函数标志位
        if(counter==255)            //counter 从0到255累加循环计数,每进一次中断,counter加一
            counter = 0;
        else 
            counter +=1;
        if(mode == 0){
            if(counter < pwm)              //当counter值小于pwm值时,将IO口设为高;当counter值大于等于pwm时,将IO口置低
                GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1); //将PC14 PC15置为高电平
            else 
                        GPIO_ResetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1);     // 将PC14 PC15置为低电平
        }
        if(mode == 1)
        {
            if(counter < pwm)              //当counter值小于pwm值时,将IO口设为高;当counter值大于等于pwm时,将IO口置低
                GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2); //将PC14 PC15置为高电平
            else 
                        GPIO_ResetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2);     // 将PC14 PC15置为低电平
        }  
        if(mode ==2){
            if(counter < pwm)              //当counter值小于pwm值时,将IO口设为高;当counter值大于等于pwm时,将IO口置低
                GPIO_SetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_0); //将PC14 PC15置为高电平
            else 
                GPIO_ResetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_0); // 将PC14 PC15置为低电平
        }
}   


int main(void)
{
    RCC_Configuration();                                                                    
  GPIO_Configuration();                         
    tim4();
    tim3();
  nvic(); 
    while(1)
    { 
    }   
}   

void delay_nus(u32 n)       //72M时钟下,约延时us
{
  u8 i;
  while(n--)
  {
    i=7;
    while(i--);
  }
}


void delay_nms(u32 n)     //72M时钟下,约延时ms
{
    while(n--)
      delay_nus(1000);
}


void RCC_Configuration(void)                 //使用任何一个外设时,务必开启其相应的时钟
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);    //使能APB2控制外设的时钟,包括GPIOC, 功能复用时钟AFIO等,                                                                              
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4|RCC_APB1Periph_TIM3, ENABLE); //使能APB1控制外设的时钟,定时器tim3、4,其他外设详见手册             
}


void GPIO_Configuration(void)            //使用某io口输入输出时,请务必对其初始化配置
{
    GPIO_InitTypeDef GPIO_InitStructure;   //定义格式为GPIO_InitTypeDef的结构体的名字为GPIO_InitStructure  
                                          //typedef struct { u16 GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; } GPIO_InitTypeDef;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    //配置IO口的工作模式为上拉输入(该io口内部外接电阻到电源)
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //配置IO口最高的输出速率为50M
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13;  //配置被选中的管脚,|表示同时被选中
    GPIO_Init(GPIOC, &GPIO_InitStructure);                  //初始化GPIOC的相应IO口为上述配置,用于按键检测
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;       //配置IO口工作模式为 推挽输出(有较强的输出能力)
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //配置IO口最高的输出速率为50M
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;  //配置被选的管脚,|表示同时被选中
    GPIO_Init(GPIOA, &GPIO_InitStructure);        //初始化GPIOA的相应IO口为上述配置
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //失能STM32 JTAG烧写功能,只能用SWD模式烧写,解放出PA15和PB中部分IO口
}


void tim4()                           //配置TIM4为基本定时器模式,约10ms触发一次,触发频率约100Hz
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;   //定义格式为TIM_TimeBaseInitTypeDef的结构体的名字为TIM_TimeBaseStructure  
    TIM_TimeBaseStructure. TIM_Period =9999;          // 配置计数阈值为9999,超过时,自动清零,并触发中断
    TIM_TimeBaseStructure.TIM_Prescaler =71;         //  时钟预分频值,除以多少
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频倍数
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数方式为向上计数
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);      //  初始化tim4
    TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除TIM4溢出中断标志
    TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);   //  使能TIM4的溢出更新中断
    TIM_Cmd(TIM4,ENABLE);                //        使能TIM4
}


void tim3()                           //配置TIM3为基本定时器模式 ,约10us触发一次,触发频率约100kHz
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;   //定义格式为TIM_TimeBaseInitTypeDef的结构体的名字为TIM_TimeBaseStructure  
    TIM_TimeBaseStructure. TIM_Period =9;         //配置计数阈值为9,超过时,自动清零,并触发中断
    TIM_TimeBaseStructure.TIM_Prescaler =71;     //    时钟预分频值,除以多少
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // 时钟分频倍数
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 计数方式为向上计数
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);      //  初始化tim3
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIM3溢出中断标志
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //  使能TIM3的溢出更新中断
    TIM_Cmd(TIM3,ENABLE);                     //           使能TIM3
}


void nvic()                                 //配置中断优先级
{    
     NVIC_InitTypeDef NVIC_InitStructure;  //    //   命名一优先级变量
     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);    //     将优先级分组方式配置为group1,有2个抢占(打断)优先级,8个响应优先级
     NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //该中断为TIM4溢出更新中断
     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//打断优先级为1,在该组中为较低的,0优先级最高
     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级0,打断优先级一样时,0最高
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //  设置使能
     NVIC_Init(&NVIC_InitStructure);                        //  初始化
     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //要用同一个Group
     NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3 溢出更新中断
     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//    打断优先级为1,与上一个相同,不希望中断相互打断对方
     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     //  响应优先级1,低于上一个,当两个中断同时来时,上一个先执行
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);
}

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

围观 156

在用Keil对STM32进行仿真时,可能会遇到程序停在HardFault_Handler中断函数中。这说明STM32出现了硬件错误。

“怎么查找STM32的硬件错误HardFault_Handler"

STM32出现硬件错误可能有以下原因:

  • 内存溢出/数组越界;
  • 堆栈溢出;

有时候可以自己查找出内存或堆栈溢出的位置,从而解决问题。但程序比较复杂时,可能很难自己找到问题所在。遇到这种情况,可以通过以下几种方式来定位到出错代码段。

方法1

1、在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。

2、在Keil菜单栏点击“View”——“Registers Window”,在寄存器查看窗口查找R14(LR)的值。

如果R14(LR) = 0xFFFFFFF9,继续查看MSP(主堆栈指针)的值;
如果R14(LR) = 0xFFFFFFFD,继续查看PSP(进程栈指针)的值。

“怎么查找STM32的硬件错误HardFault_Handler"

3、在Keil菜单栏点击“View”->“Memory Windows”->“Memory1”,在“Address”地址栏中输入MSP的值:0x20008828,然后在对应的行里找到地址。地址一般以0x08开头的32位数。本例中,地址为0x0800BA68。

“怎么查找STM32的硬件错误HardFault_Handler"

4、在Keil菜单栏点击“View”->“Disassembly Window”,在“Disassembly”窗口中右击,在下拉菜单中选择“Show Disassembly at Address...”。在弹出框“Show Code at Adress”的地址框中输入地址0x0800BA68进行搜索,然后就会找到相对应的代码。这里的代码就是进入循环中断之前的情况。仔细查看附近区域的相关代码来排查错误具体原因。

“怎么查找STM32的硬件错误HardFault_Handler"

方法2

1、在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。

2、在Keil菜单栏点击“View”->“Call Stack Window”弹出“Call Stack + Locals”对话框。然后在对话框中右键选择“Show Caller Code”,就会跳转到出错之前的函数处,仔细查看这部分函数被调用或者数组内存使用情况。

方法3

使用CmBacktrace。CmBacktrace(Cortex Microcontroller Backtrace)是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。

主要特性如下:

1、支持的错误包括:

  • 断言(assert)
  • 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)

2、故障原因自动诊断:可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;

3、输出错误现场的函数调用栈(需配合 addr2line 工具进行精确定位),还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准。也可以在正常状态下使用该库,获取当前的函数调用栈;

4、支持 裸机 及以下操作系统平台:

  • RT-Thread
  • UCOS
  • FreeRTOS

5、根据错误现场状态,输出对应的 线程栈 或 C 主栈;

6、故障诊断信息支持多国语言(目前:简体中文、英文);

7、适配Cortex-M0/M3/M4/M7 MCU;

8、支持IAR、KEIL、GCC 编译器;

总结

一般情况下前两种方式都可以查找到程序的问题所在。但也有些时候不好查找问题,这时候就需要更高级的第三种方式,这种方式使用要相对复杂一些,但功能也更强大。以后再详细介绍。

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

围观 2568

STM32单片机的看门狗有独立看门狗和窗口看门狗之分,这两者的工作原理却完全不同,今天来看一下他们的具体区别和配置方法。

STM32独立看门狗

由专门的低速时钟(LSI)驱动,即便是主时钟发生故障它仍能够有效,所以此狗狗可以工作在与主时钟无关的要求下,或者待机模块下等,所以它叫独立看门狗,注意一旦开启此看门狗则只能由MCU复位后才清除,让它不再工作。

它的时钟是一个内部RC时钟,它会在30KHZ到60KHZ之间变化,并非是精确的40KHZ,而只是一般计算时取40KHZ。

独立看门狗需设置四个寄存器如下:

“STM32:独立看门狗、窗口看门狗的配置....."

其中,预分频寄存器(IWDG_PR),最低三位PR[2:0](Prescaler divider)有效,可设置有8种不同的计数器时钟预分频因子。

重装载寄存器(IWDG_RLR)低12位RL[11:0]: 看门狗计数器重装载值 (Watchdog counter reload value) 有效,用来设置计数器的重装载值。

注意要设置以上两个寄存器的值需满足两个条件,详见如下:

键寄存器(IWDG_KR),用来控制去除IWDG_PR和IWDG_RLR写保护功能以便正常写值,向此寄存器写入0x5555则暂时去除IWDG_PR和IWDG_RLR的写保护功能才可向两个寄存器中写值。

当向此寄存器写入0xAAAA则IWDG_RLR的值会重装载,防止MCU复位,向入0xCCCC是开启狗立看门狗动作。

状态寄存器(IWDG_SR)最低两位有效RVU: 看门狗计数器重装载值更新 (Watchdog counter reload value update) 标识位和PVU: 看门狗预分频值更新 (Watchdog prescaler value update) 标识位,分别用来指示此时是否可向IWDG_RLR 和 IWDG_PR写值,此寄存器由硬件置1与清0,只有当为0时才可向上面两个寄存器写值。

它的初始化过程大致如下 :

//时间计算(大概):Tout=((4*2^prer)*rlr)/40 (ms)
void IWDG_Init(u8 prer,u16 rlr)
{       
        IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); 
         IWDG_SetPrescaler(prer);   
         IWDG_SetReload(rlr); 
         IWDG_ReloadCounter();      
         IWDG_Enable(); 
}

喂狗可通过调用如下函数进行:

IWDG_ReloadCounter();//reload

另外要注意不要使用硬件时钟中断喂狗,因为硬件时钟中断一般都有较高优先级且独立于主控程序,这样有时会出现主控程序虽然跑飞了,但仍能够正常喂狗的现象。

独立看门狗能够在一定程度上监控着程序正常运行,然而我认为更加强大,应用更灵活及更能保证程序稳定运行的还属窗口看门狗,虽然它开始时不太好理解。

STM32窗口看门狗

共三个寄存器,如下图:

“STM32:独立看门狗、窗口看门狗的配置”

看似简单,但设置及应用起来有不少玄机。

控制寄存器(WWDG_CR)中的值必须在0xFF与0xC0之间, 因为它的第0至第6位为递减计数器CNT,在它的第6位变为0时将产生复位,所以在初始化时需要为1,第7位WDGA是用来设置启动或禁止窗口看门狗的,当为1进才会启动窗口看门狗,所以第6和第7位都需为1,即WWDG_CR 的值需要大于等于0xC0 。

配置寄存器(WWDG_CFR) 第0至第6位 是设置窗口边界值用的,只有当递减计数器CNT的值小于边界值时才可以喂狗,过早不行,狗还不饿,撑死了。

并且7位递减计数器CNT减少到0x3F时即T6位变为0,此时MCU也会复位,过晚了,狗饿死了。

所以必须在指定的时间范围喂狗,过早或过晚都将产生复位,而这样设计可以减少软件跑飞了却仍能够歪打正着地喂狗的发生概率。

状态寄存器(WWDG_CFR) 只用到了第0位,EWIF(Early wakeup interrupt flag )是提前唤醒中断标识,当递减计数器CNT的值到达0X40(若再减少一次则T6位变为0,产生复位)时此位由硬件置1,且需用软件清0,注意无论中断是否使能此位都会被硬件置1。

而提前唤醒中断使能设置是在配置寄存器(WWDG_CFR)第9位EWI(Early wakeup interrupt),此位需由软件置1,则会在当递减计数器CNT的值到达0X40时产生中断,并且与EWIF不同,此位是由硬件清0。

另外控制寄存器(WWDG_CR)中第7位WDGA(Activation bit)激活位,需用软件来置1,以启动窗口看门狗,并且一旦启动后,只能在复位或重启后由硬件来清0。

配置寄存器(WWDG_CFR)的第8位和第7位WDGTB[1:0]用来设置时基(Timer base)预分频数。

以上描述可参考下图以更清晰的理解:

“STM32:独立看门狗、窗口看门狗的配置”

窗口看门狗应用时还要注意算准最小与最大喂狗时间,以便正确地喂狗,如下:

“STM32:独立看门狗、窗口看门狗的配置”

在PCLK1频率为36MHz 时,则

上窗口时间:T_min = 4096 * (2^WDGTB)*(WWDG_CR[6:0] - WWDG_CFR[6:0])/36 (us)
下窗口时间:T_max = 4096 * (2^WDGTB)*(WWDG_CR[6:0] - 0x40)/36 (us) 。

喂狗动作需在这段时间之间进行,而喂狗动作为向控制寄存器(WWDG_CR)中写值。

窗口看门狗中断函数void WWDG_IRQHandler(void) 是用来做什么的呢?

窗口看门狗中断函数是在递减计数器减少到0x40是被调用,因为它本身计数就比较慢,所以离数到0x3F复位还有一段时间,我认为这样设计是为MCU复位之前留下一点时间,能够使工程设计人员根据需要在中断函数保存一些重要的数据,这样在复位后MCU可知道系统因异常复位的某此状态,以使系统有更高稳定性。

并且我觉得在窗口看门狗中断函数中喂狗没有什么意义,程序本来已经不按正常运行了,还在中断函数中喂狗防止复位只会错上加错,不好好利用它干点正事, 更是浪费资源。

这点上我个人认为不要被点原子示例代码误导哦,但其还是有部分借鉴意义的,以下为初始化相关代码:

  //窗口看门狗中断服务设置程序
    void WWDG_NVIC_Init()
    {
      NVIC_InitTypeDef NVIC_InitStructure;
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      
      NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;       //WWDG 中断
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占2 子优先级3 组2
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;    //抢占2,子优先级3,组2
      NVIC_Init(&NVIC_InitStructure);                       //NVIC 初始化
    }
//保存WWDG 计数器的设置值,默认为最大.
u8 WWDG_CNT=0x7f;
//初始化窗口看门狗
    //tr  :T[6:0],计数器值
    //wr   :W[6:0],窗口值
    //fprer:分频系数(WDGTB ),仅最低2 位有效
    //Fwwdg=PCLK1/(4096*2^fprer).
    void WWDG_Init(u8 tr,u8 wr,u32 fprer)
    {
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG 时钟使能
      WWDG_CNT=tr&WWDG_CNT;      //初始化WWDG_CNT.
      WWDG_SetPrescaler(fprer);        //设置IWDG 预分频值
      WWDG_SetWindowValue(wr);       //设置窗口值
      WWDG_Enable(WWDG_CNT);       //使能看门狗,设置counter
      WWDG_ClearFlag();               //清除提前唤醒中断标志位 (注:若没有此句则会在初始化后先进入中断一次)
      WWDG_NVIC_Init();               //初始化窗口看门狗NVIC
      WWDG_EnableIT();                //开启窗口看门狗中断
}

以上代码朋友们也可以跳到库函数代码中自己研究下,另外要说明下的是WWDG_EnableIT(); 函数相关代码

#define CFR_EWI_BB        (PERIPH_BB_BASE + (CFR_OFFSET * 32) + (EWI_BitNumber * 4))

用到位带操作,具体理解可参照《Cortex-M3权威指南》第五章的位带操作相关介绍(具体89页)。

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

围观 57

首先总结一下串口232,422,485

串口232:可双向传输,全双工,最大速率20Kbps,负逻辑电平,-15V~-3V逻辑“1”, 3V~ 15V逻辑“0”。

串口422:可双向传输,4线全双工,2线单工。

串口485:可双向传输,4线全双工,2线单工,最大速率10Mb/s,差分信号,发送端:2V~ 6V逻辑“1”,-2V~-6V逻辑“0”,接收端:200mV逻辑“1”,-200mV逻辑“0”。

对于串口的实现有以两个方案:

方案一,和原子的《例说STM32》一样,首先接收,然后处理,没有消息验证处理,这样就会出现消息覆盖,消息出错后死机,无法明确区分命令,无法及时应答握手信号。

方案二,借鉴uC/OSII的消息队列,进入中断服务函数之后,关闭中断,接收数据,如果没有数据接收,等待一段时间(时间和波特率有关)后开中断,出中断,然后在对接收到的数据进行处理,下面看代码:

消息队列及其初始化函数:

“STM32的串口应用总结”

这样就把数据一次性全部存储下来了,剩下的就是对消息缓冲器message_buff[]中的消息进行处理了,这样就解决了消息覆盖,消息出错无法纠正的问题,至于消息怎么处理就是依据不同的需求不同的处理,另外注意,握手信号好用定时器中断。

STM32的IO口基本操作

“STM32的串口应用总结”

1.初始化结构体

先来看下GPIO_InitTypeDef这个结构体,源代码如下

“STM32的串口应用总结”

结构体中包含了GPIO_Pin,GPIO_Speed和GPIO_Mode信息,GPIO_Pin在stm32f10x_gpio.h中有宏定义

“STM32的串口应用总结”

刚好对应16个端口。

GPIO_Speed是一个枚举类型的结构体,在stm32f10x_gpio.h中有宏定义:

“STM32的串口应用总结”

当STM32的GPIO端口设置为输出模式时,有三种速度可以选择:2MHz、10MHz和50MHz,这个速度是指I/O口驱动电路的速度,是用来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。

高频的驱动电路,噪声也高,当你不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。

当然如果你要输出较高频率的信号,但却选用了较低频率的驱动模块,你很可能会得到失真的输出信号。

关键是,GPIO的引脚速度跟应用匹配。

比如对于串口,假如最大波特率只需115.2k,那么用2M的GPIO的引脚速度就够了,既省电也噪声小。

对于I2C接口,假如使用400k波特率,若想把余量留大些,那么用2M的GPIO的引脚速度或许不够,这时可以选用10M的GPIO引脚速度。

对于SPI接口,假如使用18M或9M波特率,用10M的GPIO的引脚速度显然不够了,需要选用50M的GPIO的引脚速度。

GPIO_Mode也是一个枚举类型的结构体:

“STM32的串口应用总结”

2.设置系统时钟

在使用端口前必须要开启外设时钟,在开启外设时钟前我们首先要配置好系统时钟,系统时钟的设置主要在库函数SystemInit()中完成,在启动文件startup_stm32f10x_md.s中有如下一段代码:

“STM32的串口应用总结”

在调用main函数之前首先要调用SystemInit()函数,这个函数定义在system_stm32f10x.c中,函数原型为

“STM32的串口应用总结”

该函数的主要功能是将配置时钟相关的寄存器都复位为默认值,并调用SetSysClock(void)函数,

“STM32的串口应用总结”

对于SYSCLK_FREQ的宏定义在system_stm32f10x.c文件开头已经给出,默认的系统时钟为72M,当然前提条件是外接8M晶振。

“STM32的串口应用总结”

3.开启外设时钟

开启或者关闭外设时钟主要由以下函数设置

“STM32的串口应用总结”

不同的外设调用不同的函数,如果使用了io的引脚复用功能除了开启io功能时钟还需要开启复用功能时钟,例如GPIOC的Pin4还可以作为ADC1的输入引脚,当它作为ADC1来使用时,除了开启GPIOC的时钟外,还要开启ADC1的时钟

“STM32的串口应用总结”

其他操作函数参看库函数使用手册。

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

围观 123

NBiot模块一般都是串口接口,使用AT指令集,对接中国移动onenet平台。先用串口助手去测试,流程测试OK之后需要在MCU上重新写一遍。

STM32串口 IDLE中断

IDLE其实是空闲的意思。IDLE中断叫空闲中断,不叫帧中断。那么什么叫空闲,怎么定义空闲呢?在实际发送数据的时候,比如一串字符串,我们会采用如下方式发送:

 void uart1_putc(char dat)

{

         SBUF = dat;

         while (!TI);

         TI = 0;

}

void uart1_puts_n(char *str){

         while (*str)

                   uart1_putc(*str++);

}

void uart1_puts_n("i am handsome");

其实发送的两个字符之间间隔非常短,所以在两个字符之间不叫空闲。空闲的定义是总线上在一个字节的时间内没有再接收到数据,空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。

而总线在什么情况时,会有一个字节时间内没有接收到数据呢?一般就只有一个数据帧发送完成的情况,所以串口的空闲中断也叫帧中断。

要怎么开启帧中断呢?

其实其他串口配置不用改变,只需要在开启串口接收中断的时候加上一句话就Ok。

USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接收中断

USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//开启串口空闲中断

然后中断函数如下

void USART2_IRQHandler(void)

{                        //串口1中断服务程序

         int clear;

         if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)

{  //字符接收中断(接收到的数据必须是0x0d 0x0a结尾)

                   USART2_RX_BUF[length++] = USART2->DR & 0x0FF;

         }

else if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)

{//空闲帧中断

                   if(USART2_RX_BUF[length - 1] == 0xff)

{

                            clear = USART2->DR;

                            clear = USART2->SR;

                            length = clear;

                            length = 0;

                            USART2_RX_STA = 1;

                   }

else

{

                            ;

                   }

          }

}

在普通中断的时候仅仅保存数据,在帧中断的时候需要执行相应处理函数。

如果没有帧中断,必须在接收中断中判断每一个接收数据与帧头帧尾是否相符,效率极低。

之前一直以为天下串口都一样,还是有很多细节的,可以提升不少效率!

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

围观 103

页面

订阅 RSS - STM32