单片机

我们公司的产品会根据客户需求和建议,不定期升级(优化bug、增删功能),这个时候软件版本就显得很重要了。

不知道大家平时开发项目有没有在软件中加入版本信息?我看最近有小伙伴在讨论相关问题,就来简单分享一下。

方法其实有很多,但基本原理都是在指定存储区域(Flash)中写入软件版本信息,这里讲述其中一种比较常见的方法。

实现方法

本文分享一个常用,也是最基础的小技巧:在Keil MDK环境下,通过软件代码,直接映射到并存储到Flash指定地址。

包含:软件版本、编译日期、编译时间,代码如下:

#define VERINFO_ADDR_BASE   (0x0800FF00) //存放FLASH的地址

const char Software_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00)))  = "Software: 1.0.0";
const char Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Date: "__DATE__;
const char Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Time: "__TIME__;

这个代码大家能看懂么? 

原理很简单,也有类似其他写入Flash地址的方法(这里暂不讲述)。 

这里面包含几个重要知识点,下面给大家描述一下。

__attribute__ 语法

attribute,翻译为“属性”,在C语言中,是一个关键字,语法格式为:

__attribute__ ((attribute-list))

__attribute__ 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。 

这部分内容,大家可以不用深入理解,知道这么用即可。要深入理解,网上也有很多学习资源。 C语言标准定义

在代码中:

const char Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Date: "__DATE__;
const char Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Time: "__TIME__;

你会看到__DATE__ 和 __TIME__表示的日期和时间。 

其实,这两个是C语言特殊的标准定义。

__DATE__:编译时刻的日期字符串 如“Apr 13 2021”__TIME__:编译时刻的时间字符串  如”20:00:00“ 

除了这两个,其实还有很多类似的标准定义,比如:

__FILE__ :正在编译文件的文件名

__LINE__ :正在编译文件的行号

__STDC__:判断该文件是不是标准C程序 

在Keil MDK中,默认情况下,源文件不修改,只编译一次。

因此,为了编译版本、日期和时间正确,需要进行设置:总是编译。

如下设置:

1.png

固件大小

生成的Hex文件会对没有使用的Falsh用0x00进行填充,比如:

2.png

填充0x00之后,这个hex就相对很大,因此,有两种方法减少hex固件大小。

1、存放FLASH的地址,要设置在合适的位置,如果代码量只有1K,你这只在偏移50K地址,这样偏移太多。

#define VERINFO_ADDR_BASE   (0x0800FF00) //存放FLASH的地址

2、网上还有一个方法,修改“ROM大小”:

3.png

该小之后,发现真的把0x00去掉了:

4.png

这两种方法,其实有一定风险的,如果代码量不断增加,可能会出现问题。所以,大家要主要设置Flash地址。

来源:嵌入式专栏(作者 | strongerHuang)

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

围观 31

Holtek新推出触控Flash MCU具NFC读写器功能产品BS65F2042,具备充足的系统资源,使用I²C通信控制减少与主控MCU的接线数量,并且提供侦测时序调控功能,解决触控按键与NFC干扰问题。适合智能门锁、门禁应用、智能家电、玩具等应用产品。

1.jpg

BS65F2042提供16个高抗干扰能力的触控键,可通过CS (Conductive Susceptibility) 10V动态测试,最多26个I/O可整合Buzzer及LED显示功能。NFC读写器功能提供13.56MHz RFID可支持ISO14443A、ISO14443B协议,RF输出电流最大230mA可增加感应距离,并且支持低功耗的卡片侦测功能。

Holtek提供完整的函数库及软硬件开发工具,协助客户快速应用于各式产品。封装提供46-pin QFN。

来源:Holtek

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

围观 17

*实例1:使用P3口流水点亮8位LED

#include//包含单片机寄存器的头文件 / 函数功能:延时一段时间

void delay(void)
{
unsigned char i,j;
for(i=0;i<250;i++)
for(j=0;j<250;j++)
;
}
/ 函数功能:主函数
void main(void)
{
while(1)
{
P3=0xfe; //第一个灯亮
delay(); //调用延时函数
P3=0xfd; //第二个灯亮
delay(); //调用延时函数
P3=0xfb; //第三个灯亮
delay(); //调用延时函数
P3=0xf7; //第四个灯亮
delay(); //调用延时函数
P3=0xef; //第五个灯亮
delay(); //调用延时函数
P3=0xdf; //第六个灯亮
delay(); //调用延时函数
P3=0xbf; //第七个灯亮
delay(); //调用延时函数
P3=0x7f; //第八个灯亮
delay(); //调用延时函数
} / /
}

*实例2:通过对P3口地址的操作流水点亮8位LED

#include//包含单片机寄存器的头文件

sfr x=0xb0; //P3口在存储器中的地址是b0H, 通过sfr可定义8051内核单片机
//的所有内部8位特殊功能寄存器,对地址x的操作也就是对P1口的操作
/
函数功能:延时一段时间
/
void delay(void)
{
unsigned char i,j;
for(i=0;i<250;i++)
for(j=0;j<250;j++)
; //利用循环等待若干机器周期,从而延时一段时间
}
/
函数功能:主函数
/
void main(void)
{
while(1)
{
x=0xfe; //第一个灯亮
delay(); //调用延时函数
x=0xfd; //第二个灯亮
delay(); //调用延时函数
x=0xfb; //第三个灯亮
delay(); //调用延时函数
x=0xf7; //第四个灯亮
delay(); //调用延时函数
x=0xef; //第五个灯亮
delay(); //调用延时函数
x=0xdf; //第六个灯亮
delay(); //调用延时函数
x=0xbf; //第七个灯亮
delay(); //调用延时函数
x=0x7f; //第八个灯亮
delay(); //调用延时函数
}
}

*实例3:用不同数据类型控制灯闪烁时间

#include//包含单片机寄存器的头文件

/
函数功能:用整形数据延时一段时间
/
void int_delay(void) //延时一段较长的时间
{
unsigned int m; //定义无符号整形变量,双字节数据,值域为0~65535 for(m=0;m<36000;m++)
; //空操作
}
/
函数功能:用字符型数据延时一段时间
/
void char_delay(void) //延时一段较短的时间
{
unsigned char i,j; //定义无符号字符型变量,单字节数据,值域0~255 for(i=0;i<200;i++)
for(j=0;j<180;j++)
; //空操作
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
while(1)
{
for(i=0;i<3;i++)
{
P1=0xfe; //P1.0口的灯点亮
int_delay(); //延时一段较长的时间
P1=0xff; //熄灭
int_delay(); //延时一段较长的时间
}
for(i=0;i<3;i++)
{
P1=0xef; //P1.4口的灯点亮
char_delay(); //延时一段较长的时间
} P1=0xff; //熄灭 char_delay(); //延时一段较长的时间 } }

*实例4:用单片机控制第一个灯亮

#include//包含51单片机寄存器定义的头文件

void main(void)
{
P1=0xfe; //P1=1111 1110B,即P1.0输出低电平
}

*实例5:用单片机控制一个灯闪烁:认识单片机的工作频率 #include//包含单片机寄存器的头文件

*实例6:将 P1口状态分别送入P0、P2、P3口:认识I/O口的引脚功能

#include//包含单片机寄存器的头文件
/ 函数功能:主函数 (C语言规定必须有也只能有1个主函数) /
void main(void)
{
while(1) //无限循环
{
P1=0xff; // P1=1111 1111B,熄灭LED
P0=P1; // 将 P1口状态送入P0口
P2=P1; // 将 P1口状态送入P2口
P3=P1; // 将 P1口状态送入P3口
}
}

*实例7:用P0口、P1 口分别显示加法和减法运算结果 

#include

void main(void)
{
unsigned char m,n;
m=43; //即十进制数2x16+11=43
n=60; //即十进制数3x16+12=60
P1=m+n; //P1=103=0110 0111B,结果P1.3、P1.4、P1.7 口的灯被点亮 P0=n-m; //P0=17=0001 0001B,结果P0.0、P0.4的灯被熄灭 }

*实例8:用P0、P1口显示乘法运算结果

#include//包含单片机寄存器的头文件

void main(void)
{
unsigned char m,n;
unsigned int s;
m=64;
n=71;
s=m n; //s=64 71=4544,需要16位二进制数表示,高8位送P1口,低8位送P0口
//由于4544=17 256+192=H3 16 16 16+H2 16 16+H1 16+H0
//两边同除以256,可得17+192/256=H3 16+H2+(H1 16+H0)/256
//因此,高8位16进制数H3 16+H2必然等于17,即4544除以256的商
//低8位16进制数H1 16+H0必然等于192,即4544除以256的余数
P1=s/256; //高8位送P1口 ,P1=17=11H=0001 0001B, P1.0和P1.4口灭,其余亮
P0=s%256; //低8位送P0口 , P3=192=c0H=1100 0000B,P3.1,P3.6,P3.7口灭,其余亮
}

*实例9:用P1、P0口显示除法运算结果

#include//包含单片机寄存器的头文件

void main(void)
{
P1=36/5; //求整数
P0=((36%5) 10)/5; //求小数
while(1)
; //无限循环防止程序“跑飞”

}

*实例10:用自增运算控制P0口8位LED流水花样

#include//包含单片机寄存器的头文件

/
函数功能:延时一段时间
/
void delay(void)
{
unsigned int i;
for(i=0;i<20000;i++)
;
}
/ 函数功能?:主函数
/ void main(void)
{
unsigned char i;
for(i=0;i<255;i++) //注意i的值不能超过255
{
P0=i; //将i的值送P0口
delay(); //调用延时函数
}

}

*实例11:用P0口显示逻辑"与"运算结果 #include//包含单片机寄存器的头文件

void main(void)

{
P0=(4>0)&&(9>0xab);//将逻辑运算结果送P0口
while(1)
; //设置无限循环,防止程序“跑飞”
}

*实例12:用P0口显示条件运算结果

#include//包含单片机寄存器的头文件 void main(void)

{
P0=(8>4)?8:4;//将条件运算结果送P0口,P0=8=0000 1000B while(1)
; //设置无限循环,防止程序“跑飞”
}

*实例13:用P0口显示按位"异或"运算结果 

#include//包含单片机寄存器的头文件

void main(void)

{
P0=0xa2^0x3c;//将条件运算结果送P0口,P0=8=0000 1000B while(1)
; //设置无限循环,防止程序“跑飞”
}

*实例14:用P0显示左移运算结果

#include//包含单片机寄存器的头文件 void main(void)

{
P0=0x3b<<2;//将左移运算结果送P0口,P0=1110 1100B=0xec while(1)
; //无限循环,防止程序“跑飞”
}
*实例15:"万能逻辑电路"实验

#include//包含单片机寄存器的头文件

sbit F=P1^4; //将F位定义为 P1.4
sbit X=P1^5; //将X位定义为 P1.5
sbit Y=P1^6; //将Y位定义为 P1.6
sbit Z=P1^7; //将Z位定义为 P1.7
void main(void)
{
while(1)
{
F=((~X)&Y)|Z; //将逻辑运算结果赋给F
;
}
}

*实例16:用右移运算流水点亮P1口8位LED 

#include//包含单片机寄存器的头文件

/
函数功能:延时一段时间
/
void delay(void)
{
unsigned int n;
for(n=0;n<30000;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
while(1)
{
P1=0xff;
delay();
for(i=0;i<8;i++)//设置循环次数为8
{
P1=P1>>1; //每次循环P1的各二进位右移1位,高位补0 delay(); //调用延时函数
}
}
}

*实例17:用if语句控制P0口8位LED的流水方向

 #include//包含单片机寄存器的头文件

sbit S1=P1^4; //将S1位定义为P1.4
sbit S2=P1^5; //将S2位定义为P1.5
/
函数功能:主函数
/
void main(void)
{
while(1)
}
{ if(S1==0) //如果按键S1按下 P0=0x0f; //P0口高四位LED点亮 if(S2==0) //如果按键S2按下 P0=0xf0; //P0口低四位LED点亮 }

*实例18:用swtich语句的控制P0口8位LED的点亮状态 

#include//包含单片机寄存器的头文件

sbit S1=P1^4; //将S1位定义为P1.4
/
函数功能:延时一段时间
/
void delay(void)
{
unsigned int n;
for(n=0;n<10000;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
i=0; //将i初始化为0
while(1)
{
if(S1==0) //如果S1键按下
{
delay(); //延时一段时间
if(S1==0) //如果再次检测到S1键按下
i++; //i自增1
if(i==9) //如果i=9,重新将其置为1
i=1;
}
switch(i) //使用多分支选择语句
{
}
} case 1: P0=0xfe; //第一个LED亮 break; case 2: P0=0xfd; //第二个LED亮 break; case 3:P0=0xfb; //第三个LED亮 break; case 4:P0=0xf7; //第四个LED亮 break; case 5:P0=0xef; //第五个LED亮 break; case 6:P0=0xdf; //第六个LED亮 break; case 7:P0=0xbf; //第七个LED亮 break; case 8:P0=0x7f; //第八个LED亮 break; default: //缺省值,关闭所有LED P0=0xff; }

*实例19:用for语句控制蜂鸣器鸣笛次数

#include//包含单片机寄存器的头文件 sbit sound=P3^7; //将sound位定义为P3.7 / 函数功能:延时形成1600Hz音频

/ void delay1600(void)
{
unsigned char n;
for(n=0;n<100;n++)
;
}
/ 函数功能:延时形成800Hz音频
/ void delay800(void)
{
unsigned char n;
for(n=0;n<200;n++)
;
}
/ 函数功能:主函数
/ void main(void)
{
unsigned int i;
while(1)
{
for(i=0;i<830;i++)
{
sound=0; //P3.7输出低电平 delay1600();
sound=1; //P3.7输出高电平 delay1600();
}
for(i=0;i<200;i++)
{
sound=0; //P3.7输出低电平 delay800();
sound=1; //P3.7输出高电平 delay800();
}
}
}

*实例20:用while语句控制LED

#include//包含单片机寄存器的头文件 / 函数功能:延时约60ms (3 100 200=60000μs) / void delay60ms(void)

{
unsigned char m,n;
for(m=0;m<100;m++)
for(n=0;n<200;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
while(1) //无限循环
{
i=0; //将i初始化为0
while(i<0xff) //当i小于0xff(255)时执行循环体 {
P0=i; //将i送P0口显示
delay60ms(); //延时
i++; //i自增1
}
}
}

*实例21:用do-while语句控制P0口8位LED流水点亮 

#include//包含单片机寄存器的头文件

/
函数功能:延时约60ms (3 100 200=60000μs)
/
void delay60ms(void)
{
unsigned char m,n;
for(m=0;m<100;m++)
for(n=0;n<200;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
do
{
P0=0xfe; //第一个LED亮
delay60ms();
}
P0=0xfd; //第二个LED亮 delay60ms(); P0=0xfb; //第三个LED亮 delay60ms(); P0=0xf7; //第四个LED亮 delay60ms(); P0=0xef; //第五个LED亮 delay60ms(); P0=0xdf; //第六个LED亮 delay60ms(); delay60ms(); P0=0xbf; //第七个LED亮 delay60ms(); P0=0x7f; //第八个LED亮 delay60ms(); }while(1); //无限循环,使8位LED循环流水点亮

*实例22:用字符型数组控制P0口8位LED流水点亮 

#include//包含单片机寄存器的头文件

/
函数功能:延时约60ms (3 100 200=60000μs)
/
void delay60ms(void)
{
unsigned char m,n;
for(m=0;m<100;m++)
for(n=0;n<200;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
unsigned char code Tab[ ]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //定义无符号字符型数组
while(1)
{
for(i=0;i<8;i++)
{
P0=Tab[i];//依次引用数组元素,并将其送P0口显示
delay60ms();//调用延时函数
}
}
}

*实例23:用P0口显示字符串常量

#include//包含单片机寄存器的头文件

/
函数功能:延时约150ms (3 200 250=150 000μs=150ms
/
void delay150ms(void)
{
unsigned char m,n;
for(m=0;m<200;m++)
for(n=0;n<250;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char str[]={"Now,Temperature is :"}; //将字符串赋给字符型全部元素赋值
unsigned char i;
while(1)
{
i=0; //将i初始化为0,从第一个元素开始显示
while(str[i]!='\0') //只要没有显示到结束标志'\0'
{
P0=str[i]; //将第i个字符送到P0口显示
delay150ms(); //调用150ms延时函数
i++; //指向下一个待显字符
}
}
}

*实例24:用P0 口显示指针运算结果

#include

void main(void)
{
unsigned char p1, p2; //定义无符号字符型指针变量p1,p2 unsigned char i,j; //定义无符号字符型数据
i=25; //给i赋初值25
j=15;
p1=&i; //使指针变量指向i ,对指针初始化
p2=&j; //使指针变量指向j ,对指针初始化
P0= p1+ p2; // p1+ p2相当于i+j,所以P0=25+15=40=0x28
//则P0=0010 1000B,结果P0.3、P0.5引脚LED熄灭,其余点亮 while(1)
; //无限循环,防止程序“跑飞”
}

*实例25:用指针数组控制P0口8位LED流水点亮 

#include

/
函数功能:延时约150ms (3 200 250=150 000μs=150ms
/
void delay150ms(void)
{
unsigned char m,n;
for(m=0;m<200;m++)
for(n=0;n<250;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char code Tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
unsigned char p[ ]={&Tab[0],&Tab[1],&Tab[2],&Tab[3],&Tab[4],&Tab[5], &Tab[6],&Tab[7]};
unsigned char i; //定义无符号字符型数据
while(1)
{
for(i=0;i<8;i++)
{
P0= p[i];
delay150ms();
}
}
}

*实例26:用数组的指针控制P0 口8 位LED流水点亮

 #include

/
函数功能:延时约150ms (3 200 250=150 000μs=150ms
/
void delay150ms(void)
{
unsigned char m,n;
for(m=0;m<200;m++)
for(n=0;n<250;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
unsigned char Tab[ ]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF, 0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE, 0xFE,0xFC,0xFB,0xF0,0xE0,0xC0,0x80,0x00, 0xE7,0xDB,0xBD,0x7E,0x3C,0x18,0x00,0x81, 0xC3,0xE7,0x7E,0xBD,0xDB,0xE7,0xBD,0xDB}; //流水灯控制码
unsigned char p; //定义无符号字符型指针
p=Tab; //将数组首地址存入指针p
while(1)
{
for(i=0;i<32;i++) //共32个流水灯控制码
{
P0= (p+i); // (p+i)的值等于a[i]
}
delay150ms(); //调用150ms延时函数 } }

*实例27:用P0 、P1口显示整型函数返回值 

#include

/ 函数功能:计算两个无符号整数的和
/ unsigned int sum(int a,int b)
{
unsigned int s;
s=a+b;
return (s);
}
/ 函数功能:主函数
/ void main(void)
{
unsigned z;
z=sum(2008,2009);
P1=z/256; //取得z的高8位
P0=z%256; //取得z的低8位
while(1)
;
}

*实例28:用有参函数控制P0口8位LED流水速度 

#include

/ 函数功能:延时一段时间
/ void delay(unsigned char x)
{
unsigned char m,n;
for(m=0;m<x;m++)< span>
for(n=0;n<200;n++)
;
}
/
函数功能:主函数
/
void main(void)
{
unsigned char i;
unsigned char code Tab[ ]={0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F}; //流水灯控制码
while(1)
{
//快速流水点亮LED
for(i=0;i<8;i++) //共8个流水灯控制码
{
P0=Tab[i];
delay(100); //延时约60ms, (3 100 200=60 000μs) }
//慢速流水点亮LED
for(i=0;i<8;i++) //共8个流水灯控制码
{
P0=Tab[i];
delay(250); //延时约150ms, (3 250 200=150 000μs) }
}
}

来源:51单片机学习网

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

围观 13

单片机执行指令

我们来思考一个问题,当我们在编程器中把一条指令写进单片机内部,然后取下单片机,单片机就可以执行这条指令。那么这条指令一定保存在单片机的某个地方,并且这个地方在单片机掉电后依然可以保持这条指令不会丢失,这是个什么地方呢?

这个地方就是单片机内部的只读存储器即ROM(READ ONLY MEMORY)。为什么称它为只读存储器呢?刚才我们不是明明把两个数字写进去了吗?原来在89C51中的ROM是一种电可擦除的ROM,称为FLASH ROM,刚才我们是用的编程器,在特殊的条件下由外部设备对ROM进行写的操作,在单片机正常工作条件下,只能从那面读,不能把数据写进去,所以我们还是把它称为ROM。

单片机数的本质和物理现象

我们知道,计算机可以进行数学运算,这令我们非常难以理解,它们只是一些电子元器件,怎么可以进行数学运算呢?

我们人类做数学题如37+45是这样做的,先在纸上写37,然后在下面写45,然后大脑运算最后写出结果,运算的原材料是37和45,结果是82都是写在纸上的,计算机中又是放在什么地方呢?

为了解决这个问题,先让我们做一个实验:这里有一盏灯,我们知道灯要么亮,要么不亮,就有两种状态,我们可以用‘0’和‘1’来代替这两种状态:规定亮为‘1’、不亮为‘0’。

现在放上三盏灯,一共有几种状态呢?我们列表来看一下:000 / 001 / 010 / 011 / 100 / 101 / 110 / 111。我们来看,这个000 / 001 / 101 不就是我们学过的的二进制数吗?本来,灯的亮和灭只是一种物理现象,可当我们把它们按一定的顺序排好后,灯的亮和灭就代表了数字了。

让我们再抽象一步,灯为什么会亮呢?是因为输出电路输出高电平,给灯通了电。因此,灯亮和灭就可以用电路的输出是高电平还是低电平来替代了。这样,数字就和电平的高、低联系上了。

单片机数位的含义

通过上面的实验我们已经知道:一盏灯亮或者说一根线的电平的高低,可以代表两种状态:0和1,实际上这就是一个二进制位。

因此我们就把一根线称之为一“位”,用BIT表示。

单片机字节的含义

一根线可以表示0和1,两根线可以表达00 / 01 / 10 / 11四种状态,也就是可以表达0~3,而三根可以表达0~7,计算机中通常用8根线放在一起,同时计数,就可以表示0~255一共256种状态。

这8根线或者8位就称之为一个字节(BYTE)。

单片机存储器的构造

存储器就是用来存放数据的地方。它是利用电平的高低来存放数据的,也就是说,它存放的实际上是电平的高、低,而不是我们所习惯认为的1234这样的数字,这样,我们的一个谜团就解开了。

一个存储器就象一个个的小抽屉,一个小抽屉里有八个小格子,每个小格子就是用来存放“电荷”的,电荷通过与它相连的电线传进来或释放掉。至于电荷在小格子里是怎样存的,就不用我们操心了,你可以把电线想象成水管,小格子里的电荷就象是水,那就好理解了。存储器中的每个小抽屉就是一个放数据的地方,我们称之为一个“单元”。

有了这么一个构造,我们就可以开始存放数据了,想要放进一个数据12,也就是00001100,我们只要把第二号和第三号小格子里存满电荷,而其它小格子里的电荷给放掉就行了。

可是问题出来了,一个存储器有好多单元,线是并联的,在放入电荷的时候,会将电荷放入所有的单元中,而释放电荷的时候,会把每个单元中的电荷都放掉。这样的话,不管存储器有多少个单元,都只能放同一个数,这当然不是我们所希望的。因此,要在结构上稍作变化。

需要在每个单元上有个控制线,想要把数据放进哪个单元,就把一个信号给这个单元的控制线,这个控制线就把开关打开,这样电荷就可以自由流动了。而其它单元控制线上没有信号,所以开关不打开,不会受到影响。

这样,只要控制不同单元的控制线,就可以向各单元写入不同的数据了。同样,如果要从某个单元中取数据,也只要打开相应的控制开关就行了。

单片机存储器的译码

那么,我们怎样来控制各个单元的控制线呢?这个还不简单,把每个单元的控制线都引到集成电路的外面不就行了吗?

事情可没那么简单,一片27512存储器中有65536个单元,把每根线都引出来,这个集成电路就得有6万多个脚?不行,怎么办?要想法减少线的数量。

有一种方法称这为译码,简单介绍一下:一根线可以代表2种状态,2根线可以代表4种状态,3根线可以代表8种,256种状态又需要几根线代表?8根线,所以65536种状态我们只需要16根线就可以代表了。

单片机存储器的选片概念

至此,译码的问题解决了,让我们再来关注另外一个问题。送入每个单元的八根线是用从什么地方来的呢?它就是从计算机上接过来的,一般地,这八根线除了接一个存储器之外,还要接其它的器件。

这样问题就出来了,这八根线既然不是存储器和计算机之间专用的,如果总是将某个单元接在这八根线上,就有问题出现了:比如这个存储器单元中的数值是0FFH另一个存储器的单元是00H,那么这根线到底是处于高电平,还是低电平?怎样分辩?

办法很简单,当外面的线接到集成电路的引脚进来后,不直接接到各单元去,中间再加一组开关就行了。平时我们让开关打开着,如果确实是要向这个存储器中写入数据,或要从存储器中读出数据,再让开关接通就行了。

这组开关由三根引线选择:读控制端、写控制端和片选端。要将数据写入片中,先选中该片,然后发出写信号,开关就合上了,并将传过来的数据(电荷)写入片中。如果要读,先选中该片,然后发出读信号,开关合上,数据就被送出去了。

读和写信号同时还接入到另一个存储器,但是由于片选端不同,所以虽有读或写信号,但没有片选信号,所以另一个存储器不会“误会”而开门,造成冲突。那么会不同时选中两片芯片呢?

只要是设计好的系统就不会,因为它是由计算控制的,而不是我们人来控制的,如果真的出现同时出现选中两片的情况,那就是电路出了故障了,这不在我们的讨论之列。

单片机的总线概念

从上面的介绍中我们已经看到,用来传递数据的八根线并不是专用的,而是很多器件大家共用的。

所以我们称之为数据总线,总线英文名为BUS,总即公交车道,谁也可以走。而十六根地址线也是连在一起的,称之为地址总线。

来源:STM32嵌入式开发

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

围观 19

最近有网友大概问了这样的问题:单片机项目偶尔经常出现异常,不知道是程序跑飞了,还是进入某个死循环了。

因为发生概率比较低,也没有规律,所以没办法在线调试查找问题。

结合这个问题,给大家分享一下用ST-LINK Utility识别单片机程序是否跑飞。

利用ST-LINK Utility查看内核运行状态

做想要知道单片机是否跑飞,可以利用ST-LINK Utility查看内核运行状态。

利用STM32 ST-LINK Utility工具中Hot Plug热插拔模式,查看内核运行状态。

1.STM32 ST-LINK Utility模式说明

即设置中三种模式选择:

1.png

主要有三种,在这个工具的用户手册中可以看到:

2.jpg

大概意思是:

Normal:常规模式

连接目标(芯片)之后,芯片复位,然后暂停(halted)。

Connect Under Reset:连接复位模式

主要用于JTAG/SWD引脚被禁用的时候。

Hot Plug:热插拔模式

连接目标芯片,不复位/重置芯片,则此时可以查看芯片状态。

2.查看内核运行状态

硬件连接好:Target -> MCU Core

3.jpg

当你的程序跑飞,内核就处于异常状态,这里可以查看MCU内核的运行状态,简单的说就可以查看PC跑到哪儿去了(你应用程序跑如果超过相应区域,说明跑飞了)。

4.png

举个例:你应用程序存储在0x08010000 --- 0x08020000 这个范围内,正常运行应用程序应该在这个范围内,但是你发现PC的值为0x08025000(不在范围内),那就说明跑飞了。

这里可以执行的操作有:运行、暂停、系统复位、内核复位、单步运行、读内核寄存器。类似于IDE中的在线调试(IDE在线调试需要有源代码,而这里不需要源代码,可直接查看PC执行到的地址)。

提示:PC:Program Counter即程序计数器寄存器,指向当前执行程序的地址,如果修改它的值,就能改变程序的执行流。(具体可以参看Cortex-M3内核寄存器所在章节)

Option Bytes选项字配置

这里额外说一下选项字配置的内容。

对Option Bytes操作常用的一个就是对Flash加密(读/写保护)。当然,读写保护可以通过程序代码实现,也可以通过这里说的选项字设置来实现。

拿F401举例:Target ->Option Bytes

5.png
STM32 ST-LINK Utility工具还有些特殊功能,可能我们不常用,比如:command line interface命令行接口、external loader developing加载外部程序等,感兴趣的朋友可以参看用户手册研究一下。
好了,本文就分享以上几点内容,希望对你们有所帮助。

来源:strongerHuang

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

围观 28

其实,单片机就是个小计算机。大计算机少不了的数据存储系统,单片机一样有,而且往往和CPU集成在一起,显得更加小巧灵活。

直到90年代初,国内容易得到的单片机是8031:不带存储器的芯片,要想工作,必须外加RAM和ROM,于是单片机成了3片机......

而现在不同了,无论大的,还是小的,又是51,又是AVR,又是STC,还有什么430、PIC……都各说各的好,可谁也不敢说“我不要存储器”。

单片机的数据存储手段

程序存储器里面存放的是单片机的灵魂:工作程序。

小的可能只有1KB,最多只能装1024条8位数据,因为实际指令还有许多2字节,3字节指令,所以它还装不下1024条指令。大的也有128KB的。这些8位数据,要么在工厂里做模子光刻进去,要么一次性的烧写进去。

业余或开发,最多也就是用编程器这么一个特殊工具,把调试成功的机器码装载进去,或者像AVR单片机那样自己花几块钱做一条下载线,把电脑里这些东西灌进去(或许是AVR最吸引人之处)。

它一旦进驻电脑的程序存储器中,除了借助上述装置便不能自由改写,在单片机运行时,只是从其中读出指令或固定的数据,所以给程序存储器一个“只读存储器”的别名,简写为ROM,包括用编程器写紫外线擦除内容的EPROM、用电擦除的EEPROM和现在新兴的FLASH ROM。相关文章:EEPROM和Flash这样讲,我早就懂了。

一次性写入的ROM,仅用于电路和程序固定的批量产品中,实际工作起来,都是一样的。

为了定位ROM中的数据,每个8位存储单元都有一个固定的“地址”,通常用16进制数表示。例如,对于一个所谓4K的ROM,地址从0000H到0FFFH(即从0000,0001...4095),单片机运行时从哪个地址取数据,完全由程序本身决定,并不要我们干预。

记住,给单片机一通电,它经过一个短暂的复位过程,立即转向ROM的最低地址0000H,在这里面放置的往往是一条“跳转”指令,它从这里一步跳到另一个地址:程序的真正起始地址,例如51机的0080H。

ROM是程序存储器,除了指令外,还包括运行程序必须的某些固定数据,例如:数据表。假如,我们要求在单片机的接口上输出00H到FFH(255)按正弦半波变化的数值,每秒10000次。如果硬要它按照公式一个个计算,对于它来说未免力不从心。可是我们可以把预先计算好的数值存入ROM中,到时候直接取出不是好多了?

又如一个重要的应用:大家一定见过不少单片机的东西上面都有数码显示,那些个数字其实就是用单片机的口线控制数码管的字段电极电位。这些字形也是存放在ROM中的字模表,各个字模和0-9的数字(机器内当然是0000-0101二进制数)对应起来。常见的共阳极7段数码管,必须在阳极加正电,7个阴极都是地电位,才能显示数字"8",数字8对应的显示字码值是二进制数“10000000“(那个1对应的是小数点,高电位不让它显示)。

2. 数据存储器RAM

这是个可以随时存取数据的一块存储器,也就是可以读(取)也可以写(存)的存储器,简称RAM。

现在的单片机里面使用的RAM,属于静态RAM或SRAM,这个和电脑用的内存条有所不同。只要你把数据写入SRAM后,不断电或者不清除掉,这个数据就一直保存在那里。电脑用的是动态RAM,要不断给它加刷新脉冲才能保存数据。

因为单片机处理的信息量比电脑小很多,所以它带的RAM也比较少:从完全不带、带128、256、...1K、2K,到4K,比ROM少多了。

因为实际上RAM只是作为数据临时存放的地方,除非进行图像处理需要存放大量的数据外。一般对于执行较简单任务的单片机,有这么多也够用,如果实在不够用也只能采取外加SRAM如6116、6264等等来扩展。

1.jpg

为了对RAM单元存取8位二进制数,当然也得和ROM一样用“地址”来标示它的具体位置。假如某单片机有1K(1024)RAM,它的地址也是从0000到1024,或16进制数的0000H到03FFH。可见,和ROM的地址是一样的。

3. 会不会混淆不清?

答案是不会的,因为读ROM是由单片机的程序指针或转移指令或查表指令进行,而这些指令是不会进入RAM区的;读写RAM是另外的数据传送指令,也不会进入ROM区。

这点也是和电脑不同之处,后者程序和数据都在内存条里面,地址不同,如果窜位了就会造成不可预见后果。单片机的这种存储器结构也称为哈佛结构。

RAM在单片机里的用途

RAM在单片机里的用途,主要是存放临时数据。

例如用单片机测温,每秒测1次,显示1分钟的平均值(1分钟更新一次):

我们先通过传感器、放大电路、A/D转换,把温度这个模拟量转变为成比例的二进制数,然后每秒钟1次把数字量通过输入口顺序存入到单片机的RAM中,然后对他们进行两两求和再平均的计算,最后的数值显示出来,然后把这60个存储单元统统写0清除旧数据,下次又是如此循环进行。

总结

另外,在单片机里面还有若干寄存器,数量不多但是作用很大,除了暂存数据,还可以交换、加工、传递等等,以及随时记录单片机当前处于什么状态,输入输出口也是作为特殊功能的寄存器存在,具体各有不同,就不是随便说说可以搞清楚的,要看有关书籍了。

来源:ARM与嵌入式

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

围观 12

智能科技的迅速发展使得我们的日常生活变得更加便捷和舒适。智能马桶作为其中一种智能家居产品,通过单片机接受和处理来自传感器的数据,然后通过控制模块对智能马桶的各项功能进行控制,实现对智能马桶的全面控制和调节。本文将介绍CW32单片机在智能马桶的详细应用。

1.png

图:CW32的智能马桶控制板

CW32单片机在智能马桶的应用介绍

1.温度感应与控制

智能马桶内设有温度传感器,通过CW32单片机的处理,可以实时感知到用户所需的座圈温度并根据用户的设定进行调节。当温度过高或过低时,单片机将发出信号,通过控制模块对座圈的加热或制冷进行调节,提供更加舒适的使用体验。

2.png

图:CW32在智能马桶控制板的应用框图

2.智能冲洗功能控制

CW32单片机通过接收用户的操作指令,进行流量检测,根据用户所需的水流大小和冲洗时间,精确地控制水泵的工作和停止。

3.智能电量监控与节能功能

智能马桶内置电池或电源模块,CW32单片机可以实时监测电池的电量,并根据电量情况进行智能的调节。当电量不足时,单片机会通过控制模块对电源进行调节,以延长电池的使用寿命。例如,降低加热功率或减少冲洗时间等,通过智能的电量管理,实现了对电能的高效利用和节约。

这款智能马桶采用的是武汉芯源半导体32位低功耗CW32L083系列,该系列产品集成了主频高达64MHz的ARM® Cortex®-M0+ 内核、最多256KB FLASH 、最多 24KBRAM、最多87路GPIO,以及一系列增强型外设。

CW32L083系列产品优势介绍

1、系统特性

● ARM® Cortex®-M0+ 内核;

● 最高主频64MHz;

● 温度范围:-40℃ 至 85℃;

● 宽压供电:1.65V 至 5.5V;

2、高可靠性

● HBM ESD 8KV;MM ESD、CDM ESD、Latch up@105℃全面达到JEDEC较高等级;

● EFT 4KV,IEC61000-4-4 Class:4(Power)/4(IO);

● 支持奇偶校验;

● 符合IEC60730,IEC61508 功能安全设计规范;

3、超低功耗

● 支持0.6uA@3.3V深度休眠模式;支持1.7uA@3.3V深度休眠模式+IWDT工作;

● 深度休眠模式下电流0.6uA,超低功耗唤醒时间仅需4us;

● 支持灵活的中断唤醒及事件唤醒;

4、内部资源

● 最多87路GPIO;

● 1路12位ADC;

● 六路低功耗 UART,支持小数波特率;

● 两路 SPI 接口 12Mbit/s;

● 两路 I2C 接口 1Mbit/s;

● 16 位高级控制定时器;四组 16 位通用定时器;三组 16 位基本定时器;一组 16 位超低功耗定时器;窗口看门狗定时器;独立看门狗定时器;

● 1路低功耗定时器(LPTIM)

● 4×56、6×54 或 8×52 LCD 段码液晶驱动器

● TRNG 真随机数发生器

● AES 高级加密标准模块

CW32L083系列产品适用于对FLASH、RAM、GPIO等资源需求较大,且有LCD显示、低功耗要求的应用场合,该系列目前可提供LQFP64、LQFP80、LQFP100三种封装形式。

3.png

图:CW32L083系列封装展示

CW32L083系列产品非常适合各种小、中型电子产品的应用领域,比如医疗和手持设备、PC外围设备、游戏设备、运动装备、报警系统、智能门锁、有线和无线传感器模块、表计等产品。

来源: 武汉芯源半导体

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

围观 19

虽然现在通信方式有很多类型,但串口依然是嵌入式领域应用最广泛的通信方式之一。

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

1.简介

串口由于使用简单,价格低廉,配合RS485芯片可以实现长距离、抗干扰能力强的局域网络而被广泛使用。随着产品功能的增多,需要处理的任务也越来越复杂,系统任务也越来越需要及时响应。绝大多数的现代单片机(ARM7、Cortex-M3)串口都带有一定数量的硬件FIFO,本文将介绍如何使用硬件FIFO来减少接收中断次数,提高发送效率。在此之前,先来列举一下传统串口数据收发的不足之处:每接收一个字节数据,产生一次接收中断。不能有效的利用串口硬件FIFO,减少中断次数。应答数据采用等待发送的方法。由于串行数据传输的时间远远跟不上CPU的处理时间,等待串口发送完当前字节再发送下一字节会造成CPU资源浪费,不利于系统整体响应(在1200bps下,发送一字节大约需要10ms,如果一次发送几十个字节数据,CPU会长时间处于等待状态)。应答数据采用中断发送。增加一个中断源,增加系统的中断次数,这会影响系统整体稳定性(从可靠性角度考虑,中断事件应越少越好)。针对上述的不足之处,将结合一个常用自定义通讯协议,提供一个完整的解决方案。

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所示。

1.png

  • 帧首:通常是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_structslave_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)。

围观 22

讲述EMC的定义,EMC在单片机应用系统的测试方法,EMC新器件新材料的应用以及故障排除技术。只要从事电子产品的研发、生产或者供应,就必须进行EMC电磁兼容的检测工作。

引言

所谓EMC就是:设备或系统在其电磁环境中能正常工作,且不对该环境中任何事物构成不能承受的电磁骚扰的能力。EMC测试包括两大方面内容:对其向外界发送的电磁骚扰强度进行测试,以便确认是否符合有关标准规定的限制值要求;对其在规定电磁骚扰强度的电磁环境条件下进行敏感度测试,以便确认是否符合有关标准规定的抗扰度要求。

对于从事单片机应用系统设计的工程技术人员来说,掌握一定的EMC测试技术是十分必要的。EMC是电磁兼容(Electro-MagneTIc CompaTIbility)的缩写,它包括电磁干扰(EMI)和电磁敏感性(EMS)两部分。由于电器产品在使用时对其它电器有电磁干扰,或受到其它电器的电磁干扰,它不仅关系到产品工作的可靠性和安全性,还可能影响其它电器的正常工作,甚至导致安全危险。

单片机系统EMC测试

(1)测试环境


为了保证测试结果的准确和可靠性,电磁兼容性测量对测试环境有较高的要求,测量场地有室外开阔场地、屏蔽室或电波暗室等。


(2)测试设备


电磁兼容测量设备分为两类:一类是电磁干扰测量设备,设备接上适当的传感器,就可以进行电磁干扰的测量;另一类是在电磁敏感度测量,设备模拟不同干扰源,通过适当的耦合/去耦网络、传感器或天线,施加于各类被测设备,用作敏感度或干扰度测量。

(3)测量方法


电磁兼容性测试依据标准的不同,有许多种测量方法,但归纳起来可分为4类;传导发射测试、辐射发射测试、传导敏感度(抗扰度)测试和辐射敏感度(抗扰度)测试。


(4)测试诊断步骤


图1给出了一个设备或系统的电磁干扰发射与故障分析步骤。按照这个步骤进行,可以提高测试诊断的效率。


(5)测试准备

①试验场地条件:EMC测试实验室为电波半暗室和屏蔽室。前者用于辐射发射和辐射敏感测试,后者用于传导发射和传导敏感度测试。

②环境电平要求:传导和辐射的电磁环境电平最好远低于标准规定的极限值,一般使环境电平至少低于极限值6dB。

③试验桌。

④测量设备和被测设备的隔离。

⑤敏感性判别准则:一般由被测方提供,并实话监视和判别,以测量和观察的方式确定性能降低的程度。

⑥被测设备的放置:为保证实验的重复性,对被测设备的放置方式通常有具体的规定。


1.jpg

(6)测试种类

传导发射测试、辐射发送测试、传导抗扰度测试、辐射抗扰度测试。

(7)常用测量仪

电磁干扰(EMI)和电磁敏感度(EMS)测试,需要用到许多电子仪器,如频谱分析仪、电磁场干扰测量仪、信号源、功能放大器、示波器等。由于EMC测试频率很宽(20Hz~40GHz)、幅度很大(μV级至kW级)、模式很多(FM、AM等)、姿态很多(平放、斜放等),因此正确地使用电子仪器非常重要。测量电磁干扰的合适仪器是频谱分析仪。频谱分析仪是一种将电压幅度随频率变化的规律显示出来的仪器,它显示的波形称为频谱。频谱分析仪克服了示波器在测量电磁干扰中的缺点,能够精确测量各个频率上的干扰强度,用频谱分析仪可以直接显示出信号的各个频谱分量。

在解决电磁干扰问题时,最重要的一个问题是判断干扰的来源。只有准确将干扰源定位后,才能够提出解决干扰的措施。根据信号的频率来确定干扰源泉是最简单的方法,因为在信号的所有特征中,频率特征是最稳定的,并且电路设计人员往往对电路中各个部位的信号频率都十分清楚。因此,只要知道了干扰信号的频率,就能够推测出干扰是哪个部位产生的。对于电磁干扰信号,由于其幅度往往远小于正常工作信号,用频谱分析仪做这种测量是十分简单的。由于频谱分析仪的中频带宽较窄,因此能够将与干扰信号频率不同的信号滤除掉,精确地测量出干扰信号频率,从而判断产生干扰信号的电路。

电磁兼容故障排除技术

(1)传导型问题的解决

①通过串联一个高阻抗来减少EMI电流。

②通过并联一个低阻抗将EMI电流短路到地或引到其它回路导体。

③通过电流隔离装置切断EMI电流。

④通过其自身作用来抑制EMI电流。

(2)电磁兼容的容性解决方案


一种常见的现象是不把滤波电容的一侧看成直接与一个分离的阻抗相连,而看成与传输线相连。典型的情况是,当一条输入输出线的长度达到或超过1/4波长时,该传输线变“长”。

实际可以用下式近似表示这种变化:

l≥55/f

式中:l单元为m,f单位为MHz。这个公式考虑了平均传播速度,它是自由空间理论的0.75倍。

a. 电介质材料及容差

电磁干扰滤波使用的大部分电容是无极性电容。

b. 差模(线到线)滤波电容性电容。

c. 共模(线到地/机壳)滤波电容

共模(CM)去耦通常使用小电容(10~100nF)。小电容可以将不期望的高频电流在其进入敏感电路之前或在其离噪声电路较远时就将其短路到机壳上去。为了得到良好的高频衰减电路,减小或消除寄生电感是关键之所在。因此有必要使用超短导线,尤其希望使用无引线元器件。


(3)感性、串联损耗电磁兼容解决方案


就电容而言,Zs和Z1如果不是纯电阻的话,在计算频率时,要使用它们的实际值。电容器串联在电源或信号电路时,必须满足:

①流过的工作电流不应该引起电感过热或过大的有过之而无不及降;

②流过的电流不能引起电感磁饱和,尤其是对高导磁材料是毫无疑问的。
解决方案有以下几种:


  • 磁芯材料;
  • 铁氧体和加载铁氧体的电缆;
  • 电感、差模和共模;
  • 接地扼流圈;
  • 组合式电感电容元件。

(4)辐射型问题的解决

在很多情况下,辐射电磁干扰问题可能在传导阶段产生并被排除,还有些解决方案是可以抑制干扰装置在辐射传输通道上,就像场屏蔽那样工作。根据屏蔽理论,这种屏蔽的效果主要取决于电磁干扰源的频率、与屏蔽装置之间的距离以及电磁干扰场的特性——电场、磁场或者平面波。


①导体带。

使用铜或铝带要吧简单快速地建立一种直接的屏蔽和低阻连连接或总线。它们对于临时的解决方案和相对永久的解决方案来说是很方便的。厚度在0.035~0.1mm之间,并且背面带有导电黏合剂以便安装。如果使用铜导电带,其通过电阻约20mΩ/cm2。应用场合:电气屏蔽罩;发生故障时泄露点定位;作为一个应急的解决方案,将塑料连接器变成金属的、屏蔽普通的扁平电缆等。

②网状屏蔽带和拉链式外套。

涂锡的钢网带:主要用来安装在一个已经装配好的电费护套上作为一种易安装的绷带型的屏蔽罩。为了降低电费的磁场辐射或敏感问题,钢网带是一种有效的解决方案。

拉链式屏蔽外套:当有明显迹象表明电费是主要的引起EMI耦合的原因时使用。

③EMI密封垫。

应用场合:当下述条件存在,并且需要真正的SE时,EMI密封垫是最常用的解决辐射问题、敏感问题、ESD、电磁脉冲和TEMPEST问题的方法。

*已经把机箱泄漏确认为主要的辐射路径。

*啮合面不够光滑、平整或不够硬、本身无法提供良好的连接接触。

④窗口和通风板的EMI屏蔽:

适合对孔径的屏蔽。

平面波的大概模型是:
SE≈104(-20-lgl)-20lgf

式中,SE单位为dB;l为网格或网孔的尺寸,单位为mm;f单位为MHz。当然,随着频率的下降,网孔的屏蔽效率SE的上限受限于金属本身。在近区场,对H场的屏蔽,其屏蔽功率SHE不受频率的影响,可由下式近似得出:

SEH≈10lg(πr/l)

其中,r为源到屏蔽罩之间的距离,l为网孔尺寸,两者单位均为mm。


⑤导电涂料:

应用于在系统的塑料外壳建立EMI屏蔽罩、发送现有普通的或恶化的导电表面的屏蔽效能SE、防止ESD或静电积累现象、增大结合面或密封垫片的接触面积。


⑥导电箔:

铝是一种良导体,在10MHz以下没有吸收损耗,但它对于电场的任何频率都有较好的反射损耗。应用场合请参阅有关资料。

⑦导电布:

可应用于任何100kHz到GHz级频率范围需要达到30~30dB衰减的立体屏蔽场合中

结语

在实际EMC测试应用中,除了通过标准资格实验室的鉴定测试以外,还有两种可行的方法也是被业界所认可的:TCF(Technical ConstrucTIon File)和Self CeriTIfication(自检证明)。抗干扰能力测试是十分实用的测试项目。实现电磁兼容的最好办法是,将所有的数字及模拟电路均视为对高频信号响应的电路,用高频设计方法来处理电费屏蔽、PCB布线和共模滤波。采用整块地平面和电源面也很重要,对模拟电路也该如此,这样做有利于限制高频共模环环。大多数瞬态干扰均属高频,并产生很强的辐射能量。

来源:ST中文论坛

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

围观 23

页面

订阅 RSS - 单片机