一、获取STM32代码运行时间的技巧

    测试代码的运行时间的两种方法:

  • 使用单片机内部定时器,在待测程序段的开始启动定时器,在待测程序段的结尾关闭定时器。为了测量的准确性,要进行多次测量,并进行平均取值。
  • 借助示波器的方法是:在待测程序段的开始阶段使单片机的一个GPIO输出高电平,在待测程序段的结尾阶段再令这个GPIO输出低电平。用示波器通过检查高电平的时间长度,就知道了这段代码的运行时间。显然,借助于示波器的方法更为简便。

 借助示波器方法的实例

    Delay_us函数使用STM32系统滴答定时器实现:

#include "systick.h"
/* SystemFrequency / 1000    1ms中断一次
 * SystemFrequency / 100000     10us中断一次
 * SystemFrequency / 1000000 1us中断一次
 */
#define SYSTICKPERIOD                    0.000001
#define SYSTICKFREQUENCY            (1/SYSTICKPERIOD)
/**
  * @brief  读取SysTick的状态位COUNTFLAG
  * @param  无
  * @retval The new state of USART_FLAG (SET or RESET).
  */
static FlagStatus SysTick_GetFlagStatus(void) 
{
if(SysTick->CTRL&SysTick_CTRL_COUNTFLAG_Msk) 
    {
return SET;
    }
else
    {
return RESET;
    }
}
/**
  * @brief  配置系统滴答定时器 SysTick
  * @param  无
  * @retval 1 = failed, 0 = successful
  */
uint32_t SysTick_Init(void)
{
/* 设置定时周期为1us  */
if (SysTick_Config(SystemCoreClock / SYSTICKFREQUENCY)) 
    { 
/* Capture error */
return (1);
    }
/* 关闭滴答定时器且禁止中断  */
    SysTick->CTRL &= ~ (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);                                                  
return (0);
}
/**
  * @brief   us延时程序,10us为一个单位
  * @param
  *        @arg nTime: Delay_us( 10 ) 则实现的延时为 10 * 1us = 10us
  * @retval  无
  */
void Delay_us(__IO uint32_t nTime)
{     
/* 清零计数器并使能滴答定时器 */
    SysTick->VAL   = 0;  
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;     
for( ; nTime > 0 ; nTime--)
    {
/* 等待一个延时单位的结束 */
while(SysTick_GetFlagStatus() != SET);
    }
/* 关闭滴答定时器 */
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.

    检验Delay_us执行时间中用到的GPIO(gpio.h、gpio.c)的配置:

#ifndef __GPIO_H
#define    __GPIO_H
#include "stm32f10x.h"
#define     LOW          0
#define     HIGH         1
/* 带参宏,可以像内联函数一样使用 */
#define TX(a)                if (a)    \
                                            GPIO_SetBits(GPIOB,GPIO_Pin_0);\
else        \
                                            GPIO_ResetBits(GPIOB,GPIO_Pin_0)
void GPIO_Config(void);
#endif
#include "gpio.h"
/**
  * @brief  初始化GPIO
  * @param  无
  * @retval 无
  */
void GPIO_Config(void)
{        
/*定义一个GPIO_InitTypeDef类型的结构体*/
        GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED的外设时钟*/
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); 
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;    
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
        GPIO_Init(GPIOB, &GPIO_InitStructure);    
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

    在main函数中检验Delay_us的执行时间:

嵌入式分享合集117_嵌入式硬件

示波器的观察结果:

嵌入式分享合集117_嵌入式硬件_02

 可见Delay_us(100),执行了大概102us,而Delay_us(1)执行了2.2us。

    更改一下main函数的延时参数:

 

嵌入式分享合集117_嵌入式硬件_03

 示波器的观察结果:

 

嵌入式分享合集117_嵌入式硬件_04

   可见Delay_us(100),执行了大概101us,而Delay_us(10)执行了11.4us。

    结论:此延时函数基本上还是可靠的。

使用定时器方法的实例

    Delay_us函数使用STM32定时器2实现:

#include "timer.h"
/* SystemFrequency / 1000            1ms中断一次
 * SystemFrequency / 100000     10us中断一次
 * SystemFrequency / 1000000         1us中断一次
 */
#define SYSTICKPERIOD                    0.000001
#define SYSTICKFREQUENCY            (1/SYSTICKPERIOD)
/**
  * @brief  定时器2的初始化,,定时周期1uS
  * @param  无
  * @retval 无
  */
void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
/*AHB = 72MHz,RCC_CFGR的PPRE1 = 2,所以APB1 = 36MHz,TIM2CLK = APB1*2 = 72MHz */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Time base configuration */
    TIM_TimeBaseStructure.TIM_Period = SystemCoreClock/SYSTICKFREQUENCY -1;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    TIM_ARRPreloadConfig(TIM2, ENABLE);
/* 设置更新请求源只在计数器上溢或下溢时产生中断 */
    TIM_UpdateRequestConfig(TIM2,TIM_UpdateSource_Global); 
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}
/**
  * @brief   us延时程序,10us为一个单位
  * @param  
  *        @arg nTime: Delay_us( 10 ) 则实现的延时为 10 * 1us = 10us
  * @retval  无
  */
void Delay_us(__IO uint32_t nTime)
{     
/* 清零计数器并使能滴答定时器 */
    TIM2->CNT   = 0;  
    TIM_Cmd(TIM2, ENABLE);     
for( ; nTime > 0 ; nTime--)
    {
/* 等待一个延时单位的结束 */
while(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != SET);
     TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    }
    TIM_Cmd(TIM2, DISABLE);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

    在main函数中检验Delay_us的执行时间:

#include "stm32f10x.h"
#include "Timer_Drive.h"
#include "gpio.h"
#include "systick.h"
TimingVarTypeDef Time;
int main(void)
{    
    TIM2_Init();    
    SysTick_Init();
    SysTick_Time_Init(&Time);
for(;;)
    {
        SysTick_Time_Start(); 
        Delay_us(1000);
        SysTick_Time_Stop();
    }     
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

    怎么去看检测结果呢?用调试的办法,打开调试界面后,将Time变量添加到Watch一栏中。然后全速运行程序,既可以看到Time中保存变量的变化情况,其中TimeWidthAvrage就是最终的结果。

嵌入式分享合集117_下拉电阻_05

可以看到TimeWidthAvrage的值等于0x119B8,十进制数对应72120,滴答定时器的一个滴答为1/72M(s),所以Delay_us(1000)的执行时间就是72120*1/72M (s) = 0.001001s,也就是1ms。验证成功。

    备注:定时器方法输出检测结果有待改善,你可以把得到的TimeWidthAvrage转换成时间(以us、ms、s)为单位,然后通过串口打印出来,不过这部分工作对于经常使用调试的人员来说也可有可无。

两种方法对比

软件测试方法

    操作起来复杂,由于在原代码基础上增加了测试代码,可能会影响到原代码的工作,测试可靠性相对较低。由于使用32位的变量保存systick的计数次数,计时的最大长度可以达到2^32/72M = 59.65 s。

示波器方法

    操作简单,在原代码基础上几乎没有增加代码,测试可靠性很高。由于示波器的显示能力有限,超过1s以上的程序段,计时效果不是很理想。但是,通常的单片机程序实时性要求很高,一般不会出现程序段时间超过秒级的情况。

二、12V风扇转速控制电路

直流12V风扇转速控制电路

    利用这种电路可以控制小汽车内的12V直流风扇转速。电路主要元器件为555定时器。它连接成振荡器工作模式。振荡器的输出连接至场效应管IRF540(T1),风扇则连接在T1的漏极D和电池正端之间。

    C1并接在风扇两端以稳定转速,二极管D1用来保护T1免受反电动势的冲击。2A容量的保险丝保护电路过载。

    电位器VR1可以改变振荡器输出波形的占空比,从而改变风扇的转速。如果风扇转速的低速,高速范围太小,可以增加,减小 C2(0.47μF) 的值,来减少/增加风扇的转速。

    本例电路利用555定时器芯片,可实现对风扇的调速功能。

    本例电路中,555芯片用作典型的多谐振荡器来使用,发光二极管LED为风扇工作指示二极管,当风扇工作时,二极管LED被点亮。

    MOS管T1为风扇电机的开关管,M为风扇电机。

三、8种PLC常见常见错误类型

1、CPU反常

CPU反常报警时,应查看CPU单元衔接于内部总线上的一切器材。具体方法是顺次替换可能存在问题的单元,找出问题单元,并作相应处理。

2、存储器反常

存储器反常报警时,如果是程序存储器的问题,经过从头编程后还是无法解决,这种状况可能是噪声的搅扰引起程序的改变,否则应替换存储器。

3、输入/输出单元反常、扩展单元反常

发作这类报警时,应首要查看输入/输出单元和扩展单元衔接器衔接状况、电缆衔接状况,断定问题发作的某单元之后,再替换单元。

4、不执行程序

一般状况下可依照输入——程序执行情况——输出的过程进行查看。

· 输入查看是运用输入LED指示灯辨认,或用写入器构成的输入监视器查看。当输入LED不亮时,可开始断定是外部输入体系问题,再配合万用表查看。如果输出电压不正常,就可断定是输入单元问题。当LED亮而内部监视器无显现时,则可认为是输入单元、CPU单元或扩展单元的问题。

· 程序进行查看是经过写入器上的监视器查看。当梯形图的接点状况与成果不一致时,则是程序错误(例如内部继电器两层运用等),或是运算部分出现问题。

· 输出查看可用输出LED指示灯辨认。当运算成果正确而输出LED指示错误时,则可认为是CPU单元、I/O接口单元的问题。当输出LED是亮的而无输出,则可判别是输出单元问题,或是外部负载体系出现问题。

由于PLC机型不同,I/O与LED衔接方法的不一样(有的接于I/O单元接口上,有的接于I/O单元上)。所以,依据LED判别的问题规模也有不同。

5、部分程序不执行

检查方法与前一项相同,但是,如果计数器、步进控制器等的输入时刻过短,则会呈现无呼应问题,这时应该校验输入时刻是否足够大,校验可按输入时刻(输入单元的最大呼应时刻+运算扫描时刻乘以2)的联系进行。

6、电源短时掉电,程序内容也会消失

· 首先查看电池是否存在问题。

· 经过反复通断PLC本身电源来查看。为使微处理器正确启动,PLC中设有初始复位点电路和电源断开时的保存程序电路。这种电路发作问题时,就不能保存程序。所以可用电源的通、断进行查看。

· 如果在替换电池后依然呈现电池反常报警,就可判定是存储器或是外部回路的漏电流异常增大所造成的。

· 电源的通断总是与机器体系同步发作,这时可查看机器体系发作的噪声影响。由于电源的断开是常与机器体系工作同时出现的问题,绝大部分是因为电机或绕组所发作的强噪声所造成的。

7、PROM不能工作

先查看PROM连接是否良好,然后判断是否需要替换芯片。

8、电源重启或复位后,动作停止

嵌入式分享合集117_嵌入式硬件_06

四、STM32中的上/下拉电阻

STM32中的GPIO

 

嵌入式分享合集117_下拉电阻_07

 以STM32中的GPIO为例,如上图是GPIO的结构图。

    从上图中标号2处可以看到,上拉和下拉电阻上都有一个开关,通过配置上下拉电阻开关,可以控制引脚的默认电平,这里有三种状态:

  • 开启上拉时,引脚默认电压为高电平
  • 开启下拉时,引脚默认电压为低电平
  • 上拉和下拉不开启时,这种状态我们称为浮空模式

    关于STM32的GPIO文章,请移步此处:STM32的GPIO电路原理。STM32上下拉及浮空模式的配置是通过GPIOx_CRL和GPIOx_CRH寄存器控制的,可以通过《STM32F1xx 中文参考手册》查阅。

开启上拉电阻或下拉电阻的作用

    STM32内部的上拉其实是一个弱上拉,也就是说通过此上拉电阻输出的电流很小,如果想要输出一个大电流。那么就需要外接上拉电阻了,其实就是增加导线的输出电流。

    下拉电阻情况相反,让STM32的CPU引脚输出低电平,结果由于后续电路影响输出的低电平达不到GND。所以接个下拉电阻,其实就是为了降低导线的输出电流。

    另外当上下拉电阻都不开启,此时是浮空模式,引脚的电压是不确定的,此模式下的管脚电压会时不时改变。

    所以为了防止引脚悬空,产生积累电荷、静电荷,造成电路不稳定。一般情况下,我们都会给引脚设置成上拉或者下拉模式,使它有一个确定的默认电平状态。

    以上拉电阻举例,在STM32刚上电的时候,芯片引脚电平是不确定的。特别引脚是接按键的时候,必须给他个确定的电平。下拉电阻的作用就是,强制让电平保持在低电平。

上下拉电阻阻值的大小    

    根据拉电阻的阻值大小,可以分为强拉或弱拉(weak pull-up/down)。拉电阻阻值越小则表示电平能力越强,为强拉,可以抵抗外部噪声的能力也越强,相应的功耗也越大。

    举个例子:

    按键的上拉电阻可以选择3.3k、4.7k、5.1k、10k等,但是电阻越小,电流越大,功耗也越大。10k的上拉电阻带来的电流,是大多数芯片所能识别到的引脚电流,如果电阻太大,电流太小,引脚识别不了,所以10k是个折中的方案。这里的电流,简单来说是根据公式VDD/R拉电阻计算出来的。