嵌入式分享合集40

周末日常来卷

一、电路相关

1 二极管篇

    (1)暗恋就像根二极管,总是单向的电流。除非你运气好,表白时二极管反向击穿了,否则你就一直这样毫无回报的付出吧,别抱怨,谁让你选二极管呢。

    (2)爱情里的背叛就像用来消除交越失真的那根二极管,刚知道时你怎么也想不明白为什么是那个人抢了你爱人,可是后来才明白,原来交流和直流是不一样的,所以,无论男人女人,在结婚以前,都别把你想托付一生的那个人介绍给你最好的哥们姐们,因为往往最后,问题就出在这里。

2 三极管篇

    (1)三角恋就像三极管,总能把电路搞的不一样,三角恋也会把生活变得热闹,但是,毕竟,生活不是电路,还是别那么戏剧化的好。

    (2)爱情就像是三极管,放大倍数越高的,越不稳定。

    (3)人生就像三极管的输出特性曲线一样,有付出就有回报的永远只是饱和区里那一小块明显,大部分人都生活在放大区,当基极电流定下来后,由于存在着巨大的厄尔利电压,即使你在努力,起伏也不大,只能在那个小范围里慢慢前进有限的距离,等到啥时候时间把你带到击穿区,看似你飞升了,确实你也飞升了,直接飞到了天堂。所以,当你觉得委屈时,看看你脚下,还有很多更低的电压线呢。有的人连导通电压都没过呢,当你没房子时,想想非洲人民,连水都喝不上呢。

3 放大器篇

    (1)初恋男生的心就像个最简单的信号放大器,女生控制着输入端。她一个最微小的快乐信号都能给他带来极大的幸福感,同样,她不小心的小伤害也会被他单纯的放大成无比伤痛。但是,男生不会一直这么蠢,当她和他越来越接近时,他慢慢就会给自己加上滤波器,当他们结婚后,他没准还会给她加上负反馈。

    (2)热恋中的女生的心就像被加了一个差动放大器,男朋友的一切优点都被当成差模信号被放大,而他的缺点都被选择为共模信号抑制掉了。

    (3)男生的心就像是三极管放大器,恋爱时是共基极的,她的付出总可以在他那得到几百倍的回报,但是结婚后,就改成了共集电极了,往往她的付出都是得不到等价的输出了,兴许过了七年之痒,没心肺的那部分男生还会变成共射极,这时候的输出虽然放大很多,却是反相了,和她的期望完全不同了。

    (4)爱情就像功率放大器,失真小的电流周期长而且稳定,没什么激情,失真大的,导通角又小,只适合高频,不适合咱们低频。所以,只能折中一下,用个甲乙类放大器。所以最后,可能过一辈子的都是经济适用男和简单方便女。

    (5)人生啊,就像放大器,无论多牛,都得有个接地端,所以,你这一生,总得有个归宿,老是飘着,虽然潇洒,但不是那么舒服,客死异乡,总是件有点凄凉感觉的事,除非你把自己献给梦想了。

4 场效应管篇

    (1)如果你爱一个男生,非常笃定的话就和他结婚吧,男生的爱情就像绝缘栅场效应管,一般不可以测,所以,你千万别用你的闺蜜好友啥的来试他,男生的防线就如模电老师形容那管子一样,一测试就坏,而且,就算坏了,你还不知道他是什么时候坏的。就像你不知道男生什么时候变心一样。

    (2)人生就像绝缘栅场效应管,虽然已经很小心的使用了,可是,还是不知道什么时候就会在没想到的因素中挂了。

5 电桥篇

    爱情就像电桥一样,需要沟通,当无法沟通时,想方设法也要沟通,面对面永远好于背对背。因为造成爱情失败的本质原因往往不是缺乏了解,而是理解错了。只有沟通了,才能知道对方与自己到底需要什么。

6 PN结篇

    (1)生活就像PN结一样,怎么造都会有电容影响,生活也都会有坎坷与不顺。你希望生活顺心如意,希望爱情一直甜蜜,希望婚姻幸福,对不起,这和消除PN结电容一样,是个世界性难题,当然也需要我们发现和探索。

    (2)爱情就是维系男女的PN结,老师说PN结改变了这个世界。同样,这个世界里爱情也创造着它的奇迹。爱情是文明的产物,PN结也是,爱情里需要一个男人与一个女人,PN结也需要两个不同的半导体。人类不能没有爱情就像这个时代不能没有PN结一样,PN结主导了电子世界,爱情主导了我们的文明历史。

7 集成运放篇

    (1)人生就像双极型集成运放F007,虽然很经典,但却要被更好的替代了,就像那些历史人物,那些过去的生活,虽然很精彩,可是也只能放在课本里做教材,作为后人学习之用。长江后浪推前浪,前浪死在课本上。

    (2)人生就像集成运放,总有些人是来提供社会前进动力的,就像那些电源。

    (3)人生就像集成运放,总是很难找到电流走向,就像你总是会迷茫一样。总是说要注重过程,可是大多数时候要用集成运放的人只关心结果。

总结   whaosoft aiot http://143ai.com

    模电和爱情一样,都很难懂,但不同的是,不懂模电,是挂科,失去的是奖学金,爱情你要是没懂,那就得失去一个人了。你要是把模电搞的很懂,你可以考个高分,可是,你要是把爱情搞的很懂,估计只能出家了。所以生活中对待某些事该认真的时候认真,有时候马虎一点也没什么不好。

二、电路板GND与外壳GND之间,接一个电阻一个电容会更好

    外壳是金属的,中间是一个螺丝孔,也就是跟大地连接起来了。这里通过一个1M的电阻跟一个0.1uF的电容并联,跟电路板的地连接在一起,这样有什么好处呢?

  • 外壳地如果不稳定或者有静电之类的,如果与电路板地直接连接,就会打坏电路板芯片,加入电容,就能把低频高压,静电之类的隔离起来,保护电路板。电路高频干扰之类的会被电容直接接外壳,起到了隔直通交的功能。

  • 那为什么又加一个1M的电阻呢?这是因为,如果没有这个电阻,电路板内有静电的时候,与大地连接的0.1uF的电容是隔断了与外壳大地的连接,也就是悬空的。这些电荷积累到一定程度,就会出问题,必须要与大地连接才行,所以这里的电阻用于放电。

  • 1M的电阻这么大,如果外面有静电,高压之类的,也能有效降低电流,不会对电路内的芯片造成损坏。

三、嵌入式设备的通信协议特点

为嵌入式设备而设计的通信协议,通常有如下三种:

    考虑到嵌入式设备的内存、算力有限,固定二进制是首选通信协议。

    下文简析嵌入式设备通信协议应该有的特点。

简单性

    保证协议是一个简单的方案,晦涩难懂往往意味着实现困难和容易出错。协议的结构宜采用平面方式,每个域作用明确,数据域尽可能设计得长度和位置固定,注释详尽,文档清晰,实例丰富,让人尽快上手和理解。

    协议一般都需要以下域:帧头,长度,帧类型,目标地址,源地址,数据,校验,帧尾。

    串口通信数据包格式如下图所示。

可扩展

    必须保证将来增加功能和更改硬件后协议仍能胜任工作,这往往是通过预留空间来实现,协议的变更应该只是量的增加,不至于引起协议结构的变化。

低耦合

    理想情况下每个协议包是原子信息,即本协议包不与其他协议包牵连,以防止通讯丢帧和设置牵连带来的错误。

稳定性

    协议包长度适宜:太小包含的信息过少,协议包的种类繁多,容易引起通讯混乱和牵连错误;太大包含的信息过多,可读性较差,组帧和解帧的工作困难,还会带来通讯易受干扰的缺陷,一般协议长度以最小原子性信息为标尺。

    协议必须包括校验机制,以便于接收方判别协议包正确完整接收,如果出错需要较好的机制来确保通讯成功(如重传)。

高效率

    按信息类型区分协议包类别,如:设置网络信息参数,设置当前运行参数,可以区分开来,方便程序处理。

    将同种操作编码为一个子集是一种高效手段,如Read操作,编码为0x0010,Write操作,编码为0x0020。

    数据尽可能设计成同构模式,如果实在有差异,至少将同类型数据放置在一起,这样程序可以充分利用指针和线性寻址加速处理。

易实现

    尽量减少复杂算法的使用,如,通讯链路稳定,数据帧的校验码可以由CheckSum代替CRC。除非资源非常紧张,否则不要将过多的信息挤压在一个数据里,因为它会带来可读性差和实现困难。

    尽可能地让硬件ISR完成驱动工作,不要让“进程”参与复杂的时序逻辑,否则处理器将步履蹒跚且逻辑复杂!如:

    接收固定长度的数据帧,可以使用DMA,每接收完一帧DMA_ISR向进程发消息。小心处理DMA断层异常(接收的数据帧长度正常但数据错误,数据为上帧的后半部分+本帧的前半部分)。

    接收不定长的数据帧,可以使用状态机,当接收到“帧尾数据”时向进程发消息。小心数据紊乱和超时异常(数据紊乱时需要将状态机及时复位,超时一般使用定时器监控)。

兼容硬件

    如果通信链路是高速总线(如SPORT可达100Mbps),一般设计成一帧产生一次中断,它通过长度触发的DMA来实现,需要将协议设计成固定长度,如附录A。它具备高效率,但灵活性较差。

    如果通信链路是低速总线(如UART一般100kbps),一般接收一字节产生一次中断,可以将协议设计成变长帧,一个基于变长格式的UART通信协议实例:

    它具备高灵活性,但效率较低。

    上图显示了PC发送数据帧的格式,总长为64字节,是4字节的整倍数,符合绝大部分32位处理器结构体对齐的特性。

  • 0x3C:INT8U,帧头,可见字符’<’

  • Len:INT8U,本帧的总数据长度,在图4即为64

  • Dst:INT8U,标识目标设备的ID号

  • Src:INT8U,标识源设备的ID号

  • Data:56字节的存储区,内容依赖于具体的通信帧(实例见表2)

  • Cmd:INT16U,数据帧的类别

  • CS:INT8U, 对它前面所有数据(62字节)进行8位累加和校验

  • 0x7D:INT8U, 帧尾,可见字符’}’

    Data域数据结构实例:

四、获取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;}

    检验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);    }

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

#include "systick.h"#include "gpio.h"
/**  * @brief  主函数  * @param  无    * @retval 无  */int main(void){        GPIO_Config();
/* 配置SysTick定时周期为1us */    SysTick_Init();
for(;;)    {
   
           TX(HIGH);         Delay_us(1);        TX(LOW);        Delay_us(100);    }     }

    示波器的观察结果:

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

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

int main(void){    /* LED 端口初始化 */    GPIO_Config();
/* 配置SysTick定时周期为1us */    SysTick_Init();
for(;;)    {
   
           TX(HIGH);         Delay_us(10);        TX(LOW);        Delay_us(100);    }     }

    示波器的观察结果:

    可见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);}

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

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

    可以看到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以上的程序段,计时效果不是很理想。但是,通常的单片机程序实时性要求很高,一般不会出现程序段时间超过秒级的情况。

猜你喜欢

转载自blog.csdn.net/qq_29788741/article/details/126454554