单片机

随着微电子技术和计算机技术的发展,原来以强电和电器为主、功能简单的电气设备发展成为强、弱电结合,具有数字化特点、功能完善的新型微电子设备。

在很多场合,已经出现了越来越多的单片机产品代替传统的电气控制产品。单片机其控制功能通过软件指令来实现,其硬件配置也可变、易变。因此,一旦生产过程有所变动,就不必重新设计线路连线安装,有利于产品的更新换代和订单式生产。

传统电气设备采用的各种控制信号,必须转换到与单片机输入/输出口相匹配的数字信号。用户设备须输入到单片机的各种控制信号,如限位开关、 操作按钮、选择开关、行程开关以及其他一些传感器输出的开关量等,通过输入电路转换成单片机能够接收和处理的信号。输出电路则应将单片机送出的弱电控制信 号转换、放大到现场需要的强输出信号,以驱动功率管、电磁阀和继电器、接触器、电动机等被控制设备的执行元件,能方便实际控制系统使用。

针对电气控制产品的特点,本文讨论了几种单片机I/O的常用驱动和隔离电路的设计方法,对合理地设计电气控制系统,提高电路的接口能力,增强系统稳定性和抗干扰能力有实际指导意义。

输入电路设计

一般输入信号最终会以开关形式输入到单片机中,以工程经验来看,开关输入的控制指令有效状态采用低电平比采用高电平效果要好得多,如图1所示。当按下开关S1时,发出的指令信号为低电平,而平时不按下开关S1时,输出到单片机上的电平则为高电平。该方式具有较强的耐噪声能力。
图片

“图1
图1 开关信号输入

若考虑到由于TTL电平电压较低,在长线传输中容易受到外界干扰,可以将输入信号提高到+24 V,在单片机入口处将高电压信号转换成TTL信号。这种高电压传送方式不仅提高了耐噪声能力,而且使开关的触点接触良好,运行可靠,如图2所示。其 中,D1为保护二极管,反向电压≥50 V。

图2
图2 提高输入信号电平

“图3
图3 输入端保护电路

为了防止外界尖峰干扰和静电影响损坏输入引脚,可以在输入端增加防脉冲的二极管,形成电阻双向保护电路,如图3所示。二极管D1、D2、 D3的正向导通压降UF≈0.7 V,反向击穿电压UBR≈30 V,无论输入端出现何种极性的破坏电压,保护电路都能把该电压的幅度限制在输入端所能承受的范围之内。
即:VI~VCC出现正脉冲时,D1正向导 通;VI~VCC出现负脉冲时,D2反向击穿;VI与地之间出现正脉冲时,D3反向击穿;VI与地之间出现负脉冲时,D3正向导通,二极管起钳位保护作用。缓冲电阻RS约为1.5~2.5 kΩ,与输入电容C构成积分电路,对外界感应电压延迟一段时间。若干扰电压的存在时间小于τ,则输入端承受的有效电压将远低于其幅度;若时间较长,则D1 导通,电流在RS上形成一定的压降,从而减小输入电压值。

此外,一种常用的输入方式是采用光耦隔离电路。如图4所示,R为输入限流电阻,使光耦中的发光二极管电流限制在10~20 mA。输入端靠光信号耦合,在电气上做到了完全隔离。

同时,发光二极管的正向阻抗值较低,而外界干扰源的内阻一般较高,根据分压原理,干扰源能馈送到输入 端的干扰噪声很小,不会产生地线干扰或其他串扰,增强了电路的抗干扰能力。

“图4
图4 输入端光耦隔离

在满足功能的前提下,提高单片机输入端可靠性最简单的方案是:在输入端与地之间并联一只电容来吸收干扰脉冲,或串联一只金属薄膜电阻来限制流入端口的峰值电流。

输出电路设计

单片机输出端口受驱动能力的限制,一般情况下均需专用的接口芯片。其输出虽因控制对象的不同而千差万别,但一般情况下均满足对输出电压、电流、开关频率、波形上升下降速率和隔离抗干扰的要求。在此讨论几种典型的单片机输出端到功率端的电路实现方法。

1、直接耦合

在采用直接耦合的输出电路中,要避免出现图5所示的电路。

“图5
图5 错误的输出电路

T1截止、T2导通期间,为了对T2提供足够的基极电流,R2的阻值必须很小。因为T2处于射极跟随器方式工作,因此为了减少T2损耗,必须将集射间电压降控制在较小范围内。

这样集基间电压也很小,电阻R2阻值很小才能提供足够的基极电流。R2阻值过大,会大幅度增加T2压降,引起T2发热严重。而在T2截止期间,T1必须导通,高压+15 V全部降在电阻R2上,产生很大的电流,显然是不合理的。

另外,T1的导通将使单片机高电平输出被拉低至接近地电位,引起输出端不稳定。T2基极被T1拉 到地电位,若其后接的是感性负载,由于绕组反电势的作用,T2的发射极可能存在高电平,容易引起T2管基射结反向击穿。

图6为一直接耦合输出电路,由T1和T2组成耦合电路来推动T3。T1导通时,在R3、R4的串联电路中产生电流,在R3上的分压大于T2 晶体管的基射结压降,促使T2导通,T2提供了功率管T3的基极电流,使T3变为导通状态。当T1输入为低电平时,T1截止,R3上压降为零,T2截止, 最终T3截止。R5的作用在于:一方面作为T2集电极的一个负载,另一方面T2截止时,T3基极所储存的电荷可以通过电阻R3迅速释放,加快T3的截止速度,有利于减小损耗。

“图6
图6 直接耦合输出电路

2、TTL或CMOS器件耦合

若单片机通过TTL或CMOS芯片输出,一般均采用集电极开路的器件,如图7(a)所示。集电极开路器件通过集电极负载电阻R1接至+15 V电源,提升了驱动电压。但要注意的是,这种电路的开关速度低,若用其直接驱动功率管,则当后续电路具有电感性负载时,由于功率管的相位关系,会影响波形 上升时间,造成功率管动态损耗增大。

为了改善开关速度,可采用2种改进形式输出电路,如图7(b)和图7(c)所示。图7(b)是能快速开通的改进电路,当TTL输出高电平 时,输出点通过晶体管T1获得电压和电流,充电能力提高,从而加快开通速度,同时也降低了集电极开路TTL器件上的功耗。图7(c)为推挽式的改进电路, 采用这种电路不但可提高开通时的速度,而且也可提高关断时的速度。输出晶体管T1是作为射极跟随器工作的,不会出现饱和,因而不影响输出开关频率。

“图7
图7 TTL或CMOS器件输出电路

3、脉冲变压器耦合

脉冲变压器是典型的电磁隔离元件,单片机输出的开关信号转换成一种频率很高的载波信号,经脉冲变压器耦合到输出级。由于脉冲变压器原、副边线圈间没有电路连接,所以输出是电平浮动的信号,可以直接与功率管等强电元件耦合,如图8所示。

“图8
图8 脉冲变压器输出电路

这种电路必须有一个脉冲源,脉冲源的频率是载波频率,应至少比单片机输出频率高10倍以上。脉冲源的输出脉冲送入控制门G,单片机输出信号 由另一端输入G门。当单片机输出高电平时,G门打开,输出脉冲进入变压器,变压器的副线圈输出与原边相同频率的脉冲,通过二极管D1、D2检波后经滤波还 原成开关信号,送入功率管。当单片机输出低电平时,G门关闭,脉冲源不能通过G门进入变压器,变压器无输出。

这里,变压器既传递信号,又传送能量,提高了脉冲源的频率,有利于减轻变压器的体重。由于变压器可通过调整电感量、原副边匝数等来适应不同 推动功率的要求,所以应用起来比较灵活。更重要的是,变压器原副边线圈之间没有电的联系,副线圈输出信号可以跟随功率元件的电压而浮动,不受其电源大小的影响。

当单片机输出较高频率的脉冲信号时,可以不采用脉冲源和G门,对变压器原副边电路作适当调整即可。

4、光电耦合

光电耦合可以传输线性信号,也可以传输开关信号,在输出级应用时主要用来传递开关信号。

如图9所示,单片机输出控制信号经缓冲器7407放大后送入光耦。R2为光耦输出晶体管的负载电阻,它的选取应保证:在光耦导通时,其输出晶体管可靠饱和;而在光耦截止时,T1可靠饱和。但由于光耦响应速度慢使开关延迟时间加长,限制了其使用频率。

“图9
图9 光耦输出电路

结语

单片机接口技术在很多资料中均有详细的介绍,但在对大量电气控制产品的改造和设计中,经常会碰到用接口芯片所无法解决的问题(如驱动电流大、开关速度慢、抗干扰差等),因此必须寻求另一种电路解决方案。

上述几种输入/输出电路通过广泛的应用表明,其对合理、可靠地实现单片机电气控制系统具有较高的工程实用价值。

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

围观 52

前言

UART是重要的片上资源,主流单片机基本上都有该功能,通过UART可以扩展出很多的通信接口,如RS232、RS485、LIN,甚至WIFI、蓝牙模组等,可以说只要搞通讯就会涉及到UART。下面和大家分享STM32的UART配置。

1、UART是什么

USART全称universal synchronous asynchronous receiver transmitter通用同步异步接收发送器;速率最高可达4.5Mbits/s,波特率460800;

数据按位顺序发送的串行通信接口简称串口,USART模块是采用串行通信接口最常见的模块,为了方便,就把USART简称为串口;

USART接口通过RX,TX,GND同其他设备相连;当TX引脚被禁止时,该引脚恢复GPIO的配置;当TX引脚使能且未发送数据时,该引脚处于高电平(空闲态);

USART接口的数据字长度可编程,停止位长度可编程;可配置为DMA多缓冲通信;

2、USART的帧格式

串口数据应该遵循USART帧的格式,才能被串口识别;

首先总线需要持续至少一个空闲帧,然后连续发送数据帧,数据帧与数据帧之间有时会有断开帧,断开帧后需要接1-2bit停止位,连接下个数据帧;

断开帧只能为10bit或11bit低电平的帧(CR1_SBK[0]);然后接1或2bit的高电平作为停止位,然后接下一个数据帧;

数据帧的数据字有两种格式,(1)8 bit 数据位;(2)8bit 数据位 + 1 bit 奇偶校验位;

“STM32单片机,UART的寄存器配置以及工作原理"

3、USART的寄存器使用

每个USART都有7个自己的寄存器;用来配置该USART的所有功能;

有许多功能诸如硬件流控制,LIN模式,智能卡模式等,由于没用过或是用不上,实在晦涩难懂费时费力,故在此全部跳过;

以下给出了USART作为常用串口收发数据的工作框图,以及相关的寄存器配置;

3.1 工作框图

“STM32单片机,UART的寄存器配置以及工作原理"

“STM32单片机,UART的寄存器配置以及工作原理"

3.2 相关寄存器配置

1)首先需要配置USART的6个参数:

波特率USART_BRR,字长M,停止位STOP,校验位PCE,PS,PEIE,USART的收发模式TE和RE和硬件流控制CTSIE,CTSE,RTSE;

2)USART提供了8个中断:TXEIE, TCIE, RXNEIE, PEIE, IDLEIE, CTSIE, LBDIE, EIE;8个中断使能均可以进入USART的中断函数,根据需要配置合适的中断使能位为1;通常为RXNEIE位;

3)然后使能接收器RE和发送器TE;

4)然后使能UE中断;

“STM32单片机,UART的寄存器配置以及工作原理"

4、USART的代码示例

4.1 标准库提供的常用USART接口

标准库为所有的外设都提供了封装寄存器的API接口函数,文件名为stm32f10x_peripheral.c;

以下为usart外设的常用函数;

//串口USARTx的参数配置初始化函数;
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
//使能串口,(主要是分频器和输出的设置)
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
//使能串口中断,(就是那8个中断,均可以进入中断函数)
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
//都是处理一个字节;
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
//读取SR寄存器的状态,SR的状态都是硬件设置的;
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
//读取SR寄存器和CRx控制寄存器的状态,和上面一个功能相同的;
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

//修改SR寄存器的状态,单功能通讯用不上;
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);

4.2 USART1使用代码

#include "usartDemo.h"  
u8 USART1_RX_BUF[256];    //接收缓存
u8 USART1_RX_CNT = 0;    //接收字节计数
u8 USART1_REV_0D = 0;    //收到\r
u8 USART1_REV_0A = 0;    //收到\r和\n

//usart1初始化之后,便可以通过串口读写了;
void Usart1_Init(u32 bound)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级   0-3;

    //USART1外设中断配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;    //抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;            //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);  

    //GPIO初始化 USART1_TX    PA9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //输出需要配置速率,输入不需要配置速率;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出,<中文..手册>8.1.11外设的GPIO配置
    GPIO_Init(GPIOA, &GPIO_InitStructure);

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

   //USART1初始化
    USART_InitStructure.USART_BaudRate = bound;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   //CR1中的TE,RE  
    USART_Init(USART1, &USART_InitStructure); 

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//CR1中的RXNEIE中断
    USART_Cmd(USART1, ENABLE);                    //CR1中的UE
}

void USART1_Send_Data(u8 *buf,u16 len)
{
    u16 t;
    for(t=0;t<len;t++)        
    {
        USART_SendData(USART1,buf[t]);
        while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); 
        //发送字节完成后,TC硬件置1;
    }    //先读SR,后写DR清除TC位;
    USART1_RX_CNT = 0;
    USART1_REV_0D = 0;
    USART1_REV_0A = 0;  
}

void USART1_IRQHandler(void)                    
{
    u8 Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
     {
         Res =USART_ReceiveData(USART1);        //读DR,硬件清0 RXNE位;
        USART1_RX_BUF[USART1_RX_CNT]=Res;    //接收数据  
        USART1_RX_CNT++; 
        if(Res==0x0d)
            USART1_REV_0D = 1;
        if(USART1_REV_0D&&(Res==0x0a))
            USART1_REV_0A = 1;                      
     }
    //RXNE为1,读数据的同时又来了数据,那么新的数据丢失;产生溢出错误,读完数据后RXNE为0,但ORE标志还在;
    //RXNE为1,又来了数据,产生接收溢出错误,置位ORE;
    if(USART_GetFlagStatus(USART1,USART_FLAG_ORE) == SET)
    {
        USART_ReceiveData(USART1);
    //    USART_ClearFlag(USART1,USART_FLAG_ORE);//先读SR,后读DR,可以复位ORE位;应该不用软件清除了;
    }
    // USART_ClearFlag(USART1,USART_IT_RXNE); //读DR可以清除RXNE,应该不用软件清除了;
}

int main(void)
{
    Usart1_Init(460800);
    while(1)
    {
        if(USART1_REV_0A)
        {
            USART1_Send_Data(USART1_RX_BUF,USART1_RX_CNT);
        }
    }    
}

4.2.1 在前面代码的基础上不使用串口中断,直接通过SR状态位来判断数据的收发;

将上面代码的usart1初始化代码中CR1的RXNEIE配置行注释掉,然后修改main函数如下即可;

int main(void)
{
    Usart1_Init(460800); 
    while(1)
    {
        if ((USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET))
        {
            USART1_RX_BUF[USART1_RX_CNT] = USART_ReceiveData(USART1);
            if(USART1_RX_BUF[USART1_RX_CNT]==0x0a)
                USART1_REV_0A = 1;
            USART1_RX_CNT++;
        }
        if(USART1_REV_0A)
        {
            USART1_REV_0A = 0;
            for(int i=0;i<USART1_RX_CNT;i++)
            {
                USART_SendData(USART1, USART1_RX_BUF[i]);
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
            }
            USART1_RX_CNT=0;
        }
    }
}

5、总结

USART的功能没想到还挺多的,寄存器看起来就有些费时了,很多概念都是新的,不好理解,直接拉低了效率;于是觉得这样不行,应该用什么看什么,用到再看,学海无涯,精力有限;

另外人家费心费力写好标准库不就是为了帮开发人员省时间吗?了解一下即可,以后没必要深入;

本文代码github:https://github.com/caesura-k/stm32f1_usart

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

围观 30

今天分析一个经典的单片机供电电路,电路的原理图如下图所示:

“▲
▲ 开关电路简化后的电路

在电路上电之前。开关"TEST"断开,单片机也没有通过VCC加电。此时,T1的基极通过R9(100k)接地,处于截止状态。T3的基级电阻R7所连接的Test,T1都处于截止状态,所以T3也处于截止状态。

电源+9V被T3隔离,没有加载稳压芯片IC2上,IC2的输出VCC保持低电平。

“▲
▲ 电路关闭状态

按动按钮“TEST”启动电路,T3的基极通过R7,Test,T2的b-e接地,从而使得T3导通。此时+9V通过T3加到IC2稳压芯片。IC2输出VCC是加到单片机上。

单片机工作后,通过IO2输出高电压,通过R8使得T1导通。此时即使Test松开,T3的基极也可以通过R7,LED1,T1接地,实现电源自锁打开。

“▲
▲ 按动TEST,启动电路

“▲
▲ 电路启动后,由MCU提供T1基极电压,从而维持T3导通

之后,单片机软件可以来使得IO2端口重新变成低电平,使得T1截止,进而使得T3截止。

可以根据IO1端口,读取T2的开关状态,进而判断用户是否按动功能键。判断用户按动Test之后,等到用户释放Test之后,便可以将IO2置低电平。

也可以根据软件功能,实现自动延迟掉电,进而减少对供电电源的消耗。

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

围观 16

单片机开发中,打印输出比较常见,也比较重要,今天就为大家分享一下常见的打印输出内容以及区别。

1、写在前面

在MCU项目中,printf主要用于打印输出一些调试信息。比如:程序执行出错,输出相关错误提示信息。

做的好的项目,会通过打印信息生成或保存日志信息,

2、printf输出方式

本教程主要是针对MCU的printf打印输出,常见的方式:

1)UART打印输出

2)仿真打印输出

3)SWO打印输出

4)JLink-RTT打印输出

除仿真之外,其它三种都是基于MCU硬件打印输出。

从打印效率来说:UART < SWO < JLink-RTT。

每一种printf打印输出方式应用场景不同,也各有各的特点。

3、实验现象

先让大家从实验现象了解一下printf的内容,后面文章讲述具体的配置。

3.1 UART打印输出

“单片机常见的打印输出方式及区别"

这种printf是最常见的使用UART串口输出方式,需要占用一个硬件UART串口。

3.2 仿真打印输出

“单片机常见的打印输出方式及区别"

只在集成开发环境中模拟printf仿真输出,不需要连接开发板(硬件MCU)即可实现。

3.3 SWO打印输出

SWO:Serial Wire Output,串行线输出

SWD:Serial Wire Debug,串行线调试

SWV:Serial Wire Viewer,串行线查看器

SWO输出,需要多一根SWO(引脚)线,同时需要借助SWV(查看器)查看数据,分享4种方法:

·基于Keil的『Debug(printf)Viewer』

·基于IAR的『Terminal IO』

·基于ST-LINK Utility的『Serial Wire Viewer』

·基于J-Link的『SWO Viewer』

1)基于Keil的『Debug(printf)Viewer』

“单片机常见的打印输出方式及区别"

“单片机常见的打印输出方式及区别"

3)基于ST-LINK Utility的『Serial Wire Viewer』

“单片机常见的打印输出方式及区别"

4)基于J-Link的『SWO Viewer』

“单片机常见的打印输出方式及区别"

说明:前面2种可以分别基于ST-Link和J-link工具查看,第三种基于ST-Link查看,第四种基于J-link查看。

3.4 JLink-RTT打印输出

“单片机常见的打印输出方式及区别"

这种方式不需要使用UART串口,也不需要额外SWO引脚,但需要在代码中添加相关代码。

4、软、硬件工具及其它说明

该教程使用的软件及工具有点多,默认大家已经安装并掌握使用方法。

4.1 软件工具

1)STM32CubeMX

2)Keil MDK-ARM

3)IAR EWARM

4)STM32 ST-LINK Utility

5)J-Link / J-Trace

下载地址:

https://www.segger.com/downloads/jlink

“单片机常见的打印输出方式及区别"

4.2 硬件

1)MCU开发板

原则上Cortex-M3,M4都行,本教程基于STM32F0、F1、F4系列进行讲述。

  • ST-Link

  • J-Link

3)PC电脑

当然推荐Windows电脑,因为MDK-ARM只支持WIndows系统。

4.3 其它说明

1)源代码工程下载

为方便大家学习,本系列教程将提供对应源代码工程,供大家下载。公众号后台回复关键字“printf”获取。

2)关于仿真输出

现在开发板很便宜(相比以前),而且仿真和实际可能存在差异,故我不推荐大家软件仿真。

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

围观 21

前言

单片机选型是一件重要而费心的事情,如果单片机型号选择合适,单片机应用系统经济性,可靠性较高;否则易造成经费高,系统性能到不到要求。因此掌握并正确运用单片机选型原则,选择出最能适用于应用系统的单片机,保证单片机应用系统有最高的可靠性,最优的性能价格比,最长的使用寿命和最好的升级换代的方案。

1、需求调研

选型前,首先对自己的需求有所了解,清楚自己需要哪些功能。具体便是确定电路板具有的硬件功能,如CAN、RS232、RS485、网口、USB通讯等,具体数量的IO口,具体类型的串口屏、LCD屏,板载FLASH,SD,音频输出等。

2、性能

如何选择单片机,首先也是最重要的一点就是考虑功能要求,即设计的对象是什么,要完成什么样的 任务,再根据设计任务的复杂程度来决定选择什么样的单片机。

在单片机的性能上有很多要考虑的因素,比如中断源的数量和优先级、工作温度范围、有没有低电压检测功能、单片机内部有无时钟振荡器、有无上电复位功能等等

3、存储器

单片机的存储器可分为程序存储器(ROM)和数据存储器(RAM)。

程序存储器是专门用来存放程序和常数的,有MASK (掩模)ROM、OTPROM、EPROM、FlashROM 等类型。掩模这种形式的程序存储器适用成熟的和大批量生产的产品,如使用到彩色电视机等家电产品中的单片机就采用这种方式,只要用户把应用程序代码交给半导体制造厂家,在生产相应的单片机时将程序固化到芯片中,这种芯片一旦生产出来,程序就无法改变了。

采用EPROM 的单片机具有可以灵活修改程序的优点,但存在需要紫外线擦除、较费时间的缺点。在自己做试验或样机的研发阶段,推荐使用Flash 单片机,它有电写入、电擦除的优点,使得修改程序很方便,可以提高开发速度。对于初具规模的产品可选用OTP 单片机,它不但能免去较长的产品掩模时间,加快产品的上市时间,而且方便程序的修改,能够对产品进行及时的调整和升级。

程序存储器的容量可根据程序的大小确定。对于8位单片机片内程序存储器的最大容量能达到64KB,不够时还可以扩展。选用时程序存储器的容量只要够用就行了,不然会增加成本。

数据存储器是程序在运行中存放临时数据的,掉电后数据即丢失,现在有些型号的单片机提供了EEPROM,可用来存储掉电后需要保护的关键数据,如系统的一些设置参数。

4、运行速度

单片机的运行速度首先看时钟频率,一般情况对于同一种结构的单片机,时钟频率越高速度越快。

其次看单片机CPU 的结构;采用CISC 结构(集中指令集)比采用RISC 结构(精简指令集)的速度要慢。就 是同一种结构、同一种时钟频率的单片机,有时候速度也不一样,比如Winbond (华邦)公司的W77 系列 的51 单片机1个机器周期只要4 个时钟周期,而一般的51 单片机1个周期是12个时钟周期,前者的速度是后者的3倍。

在选用单片机时要根据需要选择速度,不要片面追求高速度,单片机的稳定性、抗干扰性等参数基本上是跟速度成反比的,另外速度快功耗也大。

5、 I/O (输入/输出)口

I/O 口的数量和功能是选用单片机时首先要考虑的问题之一,要根据实际需要确定I/O 口的数量,I/O口多余了不仅芯片的体积增大,也增加了成本。选用时还要考虑I/O 口的驱动能力,驱动电流大的单片机可以简化外围电路。

51等系列的单片机下拉(输出低电平)时驱动电流大,但上拉(输出高电平)时驱动电流很小。而PIC 和AVR 系列的单片机每个I/O 口都可以设置方向,当输出口使用时以推挽驱动的方式输出高、低电平,驱动能力强,也使得I/O 口资源灵活、功能强大、可充分利用。当然我们也可以根据I/O 口的功能来设计外围电路,例如用51 单片机驱动数码管,我们选用共阳的数码管就能发挥其输出口下拉驱动电流大的特点。

6、 定时/计数器(I/O)

大部分单片机提供2~3 个定时/计数器还具有输入捕获、输出比较和PWM (脉冲宽度调制)功能,如AVR 单片机。有的单片机还有专门的PCA (可编程计数器阵列)模块和CCP (输入捕获/输出比较/PWM)模 块,如PIC 和Philips 的部分中高档单片机。利用这些模块不仅可以简化软件设计,而且能少占用CPU 的 资源。

现在还有不少单片机提供了看门狗定时器(WDT),当单片机“死机”后可以复位。

选用时可根据自己的需要和编程要求进行选择,不要片面追求功能多,用不上的功能就等于金钱的浪费。

7、 串行接口

单片机常见的串行接口有:标准UART 接口、增强型UART 接口、I2C 总线接口、CAN 总线接口、SPI接 口、USB 接口等。大部分单片机有串行接口。在没有特别说明的情况下我们常说的串行接口,简称串口, 指的就是UART。

如果系统只用一个单片机芯片时,UART 接口或USB 接口通常用来和计算机或其他芯片通信,不需要和其他设备/芯片通信时可以不用。

SPI接口可用来进行ISP编程,当你没有编程器时,尽量选用带这种接口的单片机,当然SPI接口也能用来和其它外设进行高速串行通信。

I2C 总线是一种两线、双向、可多主机操作的同步总线,IC 总线是一种工业标准,被广泛应用在各种电子产品中,如现在的彩色电视机就采用IC 总线进行参数的设置。具有 IC 总线接口的单片机在使用AT24C01 等串行EEPROM 时可以简化程序设计。

通常情况下使用最多的是UART 接口,其它接口可根据你的需要选择。

8、 模拟电路功能

现在不少单片机内部提供了A/D 转换器、PWM 输出和电压比较器,也有少量的单片机提供了D/A 转换器。单片机在集成片内A/D 转换器的同时,还集成了采样/保持电路,使用户容易建立精密的数据采集系统。

PWM 输出模块可用来产生不同频率和占空比的脉冲信号。利用PWM 输出模块配合RC 滤波电路即可方便实现D/A 输出功能。PWM 输出模块也可以用来实现直流电机的调速等功能。

单片机内部集成的电压比较器可以实现多种功能,例如作阀值检测,实现低成本的A/D 转换器等。

9、 工作电压、功耗

单片机的工作电压最低可以达到1.8V,最高6V,常用的单片机工作电压为4.5V~5.5V,低电压系列为2.7V~5.0V或2.4V~3.6V。选用时根据供电方式确定。

单片机的功耗参数主要是指正常模式、空闲模式、掉电模式下的工作电流,用电池供电的系统要选用电流小的产品,同时要考虑是否要用到单片机的掉电模式,如果要用的话必须选择有相应功能的单片机。

10、封装形式

单片机常见的封装形式有:DIP (双列直插式封装)、PLCC (PLCC 要对应插座)、QFP (四侧引脚扁平封装)、SOP (双列小外形贴片封装)等。

做实验时一般选用DIP 封装的,如果选用其它封装,用编程器编程时还配专用的适配器。如果对系统的体积有要求,如遥控器中用的单片机,往往选用QFP和SOP封装的。

11、抗干扰性能、保密性

选用单片机要选择抗干扰性能好的,特别是用在干扰比较大的工业环境中的尤应如此。单片机加密后的保密性能也要好,这样可保证你的知识产权不容易被侵犯。

12、单片机的可开发性

这也是一个十分重要的因素。所选择的单片机是否有足够的开发手段,直接影响到单片机能否顺利开发,以及开发的速度。对于被选择的单片机,应考虑下列问题。

13、开发工具、编程器

有没有集成的开发环境,在支持汇编语言的同时是否支持C 语言,使用C 语言可加快你的开发进度, 另外C语言的移值性也好。

你所选用的单片机有没有编程器支持,或能否采用ISP编程。

14、开发成本

你选择的单片机对应的编程器、仿真器价格是否高,是否要用专用设备,比如有时单片机需要选用专用的编程器,这样你的开发成本就高了。

15、开发人员的适用性

这也是一个很实际的问题,如果有两种单片机都能解决问题,当然选一种你熟悉的品种。在大多数情况下大家往往优先考虑选择51或STM32系列的单片机。

一般不选用类似小编在用的较冷门的瑞萨单片机,除了开发资料不够丰富外,厂家的技术支持服务也不给力。

16、技术支持和服务

技术支持和服务可以从下面几个方面进行考虑。

1、技术是否成熟

经大量使用被证明是成熟的产品你可以放心使用。

2、有无技术服务

国内有没有代理商和相应的技术支持,网站提供的资料是否丰富,包括芯片手册,应用指南,设计方案,范例程序等。

17、单片机的可购买性

单片机是否可直接购买到,这是指单片机能否直接从厂家或其代理商处买到,购买的途径是否顺畅。单片机是否有足够的供应量,以保证所选择的单片机能满足产品的生产需要。

选择单片机,还应注意选择那些仍然在生产中的型号,已经停产的单片机是不能使用的,因为它已无后续供货能力,直接影响到产品的继续生产和生命力。同时,也会给人以一种过时的感觉,从而影响产品的新颖性。

最好还要看一下所选用的单片机是否在改进之中,显然,对于准备推出新版本或有新版本的单片机,选择用于应用系统或产品具有较强的后劲。

18、产品价格

这也是一个重要的因素,在其它条件相当的情况下,当然选择价格低的产品,这样可以提高性价比。

19、总结

根据上面几个原则对单片机进行选择,就可以选择最能适用于你的应用系统的单片机,从而保证应用系统有最高的可靠性、最优的性价比、最长的使用寿命和最好的升级换代性。

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

围观 22

单片机的组成以及分类和指标

cathy的头像

单片机又称单片微控制器,它把一个计算机系统集成到一块芯片上,主要包括微处理器(CPU)、存储器(随机访问存储器RAM、只读存储器ROM)和各种输入/输出接口(包括定时器/计数器、并行I/O接口、串行口、A/D转换器以及脉冲宽度调制(PWM)等。

本文介绍如何使用带FIFO的串口来减少接收中断次数,通过一种自定义通讯协议格式,给出帧打包方法;之后介绍一种特殊的串口数据发送方法,可在避免使用串口发送中断的情况下,提高系统的响应速度。

1. 简介

串口由于使用简单,价格低廉,配合RS485芯片可以实现长距离、抗干扰能力强的局域网络而被广泛使用。随着产品功能的增多,需要处理的任务也越来越复杂,系统任务也越来越需要及时响应。

绝大多数的现代单片机(ARM7、Cortex-M3)串口都带有一定数量的硬件FIFO,本文将介绍如何使用硬件FIFO来减少接收中断次数,提高发送效率。在此之前,先来列举一下传统串口数据收发的不足之处:

(1)每接收一个字节数据,产生一次接收中断。不能有效的利用串口硬件FIFO,减少中断次数。

(2)应答数据采用等待发送的方法。由于串行数据传输的时间远远跟不上CPU的处理时间,等待串口发送完当前字节再发送下一字节会造成CPU资源浪费,不利于系统整体响应(在1200bps下,发送一字节大约需要10ms,如果一次发送几十个字节数据,CPU会长时间处于等待状态)。

(3)应答数据采用中断发送。增加一个中断源,增加系统的中断次数,这会影响系统整体稳定性(从可靠性角度考虑,中断事件应越少越好)。

(4)针对上述的不足之处,将结合一个常用自定义通讯协议,提供一个完整的解决方案。

2.串口FIFO

串口FIFO可以理解为串口专用的缓存,该缓存采用先进先出方式。数据接收FIFO和数据发送FIFO通常是独立的两个硬件。

串口接收的数据,先放入接收FIFO中,当FIFO中的数据达到触发值(通常触发值为1、2、4、8、14字节)或者FIFO中的数据虽然没有达到设定值但是一段时间(通常为3.5个字符传输时间)没有再接收到数据,则通知CPU产生接收中断;发送的数据要先写入发送FIFO,只要发送FIFO未空,硬件会自动发送FIFO中的数据。

写入发送FIFO的字节个数受FIFO最大深度影响,通常一次写入最多允许16字节。上述列举的数据跟具体的硬件有关,CPU类型不同,特性也不尽相同,使用前应参考相应的数据手册。

3.数据接收与打包

FIFO可以缓存串口接收到的数据,因此我们可以利用FIFO来减少中断次数。以NXP的lpc1778芯片为例,接收FIFO的触发级别可以设置为1、2、4、8、14字节,推荐使用8字节或者14字节,这也是PC串口接收FIFO的默认值。

这样,当接收到大量数据时,每8个字节或者14个字节才会产生一次中断(最后一次接收除外),相比接收一个字节即产生一个中断,这种方法串口接收中断次数大大减少。

将接收FIFO设置为8或者14字节也十分简单,还是以lpc1778为例,只需要设置UART FIFO控制寄存器UnFCR即可。

接收的数据要符合通讯协议规定,数据与协议是密不可分的。通常我们需要将接收到的数据根据协议打包成一帧,然后交由上层处理。下面介绍一个自定义的协议帧格式,并给出一个通用打包成帧的方法。
自定义协议格式如图3-1所示。

“单片机串口发送数据很慢?这种方法帮助你提高!"
  • 帧首:通常是3~5个0xFF或者0xEE
  • 地址号:要进行通讯的设备的地址编号,1字节
  • 命令号:对应不同的功能,1字节
  • 长度:数据区域的字节个数,1字节
  • 数据:与具体的命令号有关,数据区长度可以为0,整个帧的长度不应超过256字节
  • 校验:异或和校验(1字节)或者CRC16校验(2字节),本例使用CRC16校验

下面介绍如何将接收到的数据按照图3-1所示的格式打包成一帧。

3.1 定义数据结构

typedef struct 
{  
    uint8_t * dst_buf;                  //指向接收缓存  
    uint8_t sfd;                        //帧首标志,为0xFF或者0xEE  
    uint8_t sfd_flag;                   //找到帧首,一般是3~5个FF或EE  
    uint8_t sfd_count;                  //帧首的个数,一般3~5个  
    uint8_t received_len;               //已经接收的字节数  
    uint8_t find_fram_flag;             //找到完整帧后,置1  
    uint8_t frame_len;                  //本帧数据总长度,这个区域是可选的  
}find_frame_struct;

3.2 初始化数据结构,一般放在串口初始化中

/** 
* @brief    初始化寻找帧的数据结构 
* @param    p_fine_frame:指向打包帧数据结构体变量 
* @param    dst_buf:指向帧缓冲区 
* @param    sfd:帧首标志,一般为0xFF或者0xEE 
*/  
void init_find_frame_struct(find_frame_struct * p_find_frame,uint8_t *dst_buf,uint8_t sfd)  
{  
    p_find_frame->dst_buf=dst_buf;  
    p_find_frame->sfd=sfd;  
    p_find_frame->find_fram_flag=0;  
    p_find_frame->frame_len=10;       
    p_find_frame->received_len=0;  
    p_find_frame->sfd_count=0;  
    p_find_frame->sfd_flag=0;  
} 

3.3 数据打包程序

/** 
* @brief    寻找一帧数据  返回处理的数据个数 
* @param    p_find_frame:指向打包帧数据结构体变量 
* @param    src_buf:指向串口接收的原始数据 
* @param    data_len:src_buf本次串口接收到的原始数据个数 
* @param    sum_len:帧缓存的最大长度 
* @return   本次处理的数据个数 
*/  
uint32_t find_one_frame(find_frame_struct * p_find_frame,const uint8_t * src_buf,uint32_t data_len,uint32_t sum_len)  
{  
    uint32_t src_len=0;  
    while(data_len--)  
    {  
        if(p_find_frame ->sfd_flag==0)                        
        {   //没有找到起始帧首  
            if(src_buf[src_len++]==p_find_frame ->sfd)  
            {  
                p_find_frame ->dst_buf[p_find_frame ->received_len++]=p_find_frame ->sfd;  
                if(++p_find_frame ->sfd_count==5)          
                {  
                    p_find_frame ->sfd_flag=1;  
                    p_find_frame ->sfd_count=0;  
                    p_find_frame ->frame_len=10;  
                }  
            }  
            else  
            {  
                p_find_frame ->sfd_count=0;   
                p_find_frame ->received_len=0;   
            }  
        }  
        else   
        {   //是否是"长度"字节? Y->获取这帧的数据长度  
            if(7==p_find_frame ->received_len)                
            {  
                p_find_frame->frame_len=src_buf[src_len]+5+1+1+1+2; //帧首+地址号+命令号+数据长度+校验       
                if(p_find_frame->frame_len>=sum_len)  
                {   //这里处理方法根据具体应用不一定相同  
                    MY_DEBUGF(SLAVE_DEBUG,("数据长度超出缓存!\n"));  
                    p_find_frame->frame_len= sum_len;       
                }  
            }  
              
            p_find_frame ->dst_buf[p_find_frame->received_len++]=src_buf[src_len++];                
            if(p_find_frame ->received_len==p_find_frame ->frame_len)                  
            {  
                p_find_frame ->received_len=0;              //一帧完成    
                p_find_frame ->sfd_flag=0;  
                p_find_frame ->find_fram_flag=1;                   
                return src_len;  
            }  
        }  
    }  
    p_find_frame ->find_fram_flag=0;  
    return src_len;  
} 

使用例子:

定义数据结构体变量:

find_frame_struct slave_find_frame_srt;

定义接收数据缓冲区:

#define SLAVE_REC_DATA_LEN  128
uint8_t slave_rec_buf[SLAVE_REC_DATA_LEN];

在串口初始化中调用结构体变量初始化函数:

init_find_frame_struct(&slave_find_frame_srt,slave_rec_buf,0xEE);

在串口接收中断中调用数据打包函数:

find_one_frame(&slave_find_frame_srt,tmp_rec_buf,data_len,SLAVE_REC_DATA_LEN);

其中,rec_buf是串口接收临时缓冲区,data_len是本次接收的数据长度。

4.数据发送

前文提到,传统的等待发送方式会浪费CPU资源,而中断发送方式虽然不会造成CPU资源浪费,但又增加了一个中断源。在我们的使用中发现,定时器中断是几乎每个应用都会使用的,我们可以利用定时器中断以及硬件FIFO来进行数据发送,通过合理设计后,这样的发送方法即不会造成CPU资源浪费,也不会多增加中断源和中断事件。

需要提前说明的是,这个方法并不是对所有应用都合适,对于那些没有开定时器中断的应用本方法当然是不支持的,另外如果定时器中断间隔较长而通讯波特率又特别高的话,本方法也不太适用。

公司目前使用的通讯波特率一般比较小(1200bps、2400bps),在这些波特率下,定时器间隔为10ms以下(含10ms)就能满足。如果定时器间隔为1ms以下(含1ms),是可以使用115200bps的。

本方法主要思想是:定时器中断触发后,判断是否有数据要发送,如果有数据要发送并且满足发送条件,则将数据放入发送FIFO中,对于lpc1778来说,一次最多可以放16字节数据。之后硬件会自动启动发送,无需CPU参与。

下面介绍如何使用定时器发送数据,硬件载体为RS485。因为发送需要操作串口寄存器以及RS485方向控制引脚,需跟硬件密切相关,以下代码使用的硬件为lpc1778,但思想是通用的。

4.1 定义数据结构

/*串口帧发送结构体*/  
typedef struct 
{  
    uint16_t send_sum_len;          //要发送的帧数据长度  
    uint8_t  send_cur_len;          //当前已经发送的数据长度  
    uint8_t  send_flag;             //是否发送标志  
    uint8_t * send_data;            //指向要发送的数据缓冲区  
}uart_send_struct;  

4.2 定时处理函数

/** 
* @brief    定时发送函数,在定时器中断中调用,不使用发送中断的情况下减少发送等待 
* @param    UARTx:指向硬件串口寄存器基地址 
* @param    p:指向串口帧发送结构体变量 
*/  
#define FARME_SEND_FALG 0x5A          
#define SEND_DATA_NUM   12  
static void uart_send_com(LPC_UART_TypeDef *UARTx,uart_send_struct *p)  
{  
    uint32_t i;  
    uint32_t tmp32;  
      
    if(UARTx->LSR &(0x01<<6))                      //发送为空  
    {         
        if(p->send_flag==FARME_SEND_FALG)  
        {                          
            RS485ClrDE;                             // 置485为发送状态  
              
            tmp32=p->send_sum_len-p->send_cur_len;  
            if(tmp32>SEND_DATA_NUM)                 //向发送FIFO填充字节数据  
            {  
                for(i=0;i<SEND_DATA_NUM;i++)  
                {  
                    UARTx->THR=p->send_data[p->send_cur_len++];  
                }  
            }  
            else  
            {  
                for(i=0;i<tmp32;i++)  
                {  
                    UARTx->THR=p->send_data[p->send_cur_len++];  
                }  
                p->send_flag=0;                      
            }  
        }  
        else  
        {  
            RS485SetDE;  
        }  
    }  
}  

其中,RS485ClrDE为宏定义,设置RS485为发送模式;RS485SetDE也为宏定义,设置RS485为接收模式。

使用例子:

定义数据结构体变量:

uart_send_struct uart0_send_str;

定义发送缓冲区:

uint8_t uart0_send_buf[UART0_SEND_LEN];

根据使用的硬件串口,对定时处理函数做二次封装:

void uart0_send_data(void)
{
 uart_send_com(LPC_UART0,&uart0_send_str);
}

将封装函数uart0_send_data();放入定时器中断处理函数中;

在需要发送数据的地方,设置串口帧发送结构体变量:

uart0_send_str.send_sum_len=data_len;      //data_len为要发送的数据长度
uart0_send_str.send_cur_len=0;             //固定为0
uart0_send_str.send_data=uart0_send_buf;   //绑定发送缓冲区
uart0_send_str.send_flag=FARME_SEND_FALG;  //设置发送标志

5. 总结

本文主要讨论了一种高效的串口数据收发方法,并给出了具体的代码实现。在当前处理器任务不断增加的情况下,提供了一个占用资源少,可提高系统整体性能的新的思路。

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

围观 12

页面

订阅 RSS - 单片机