分类目录归档:嵌入式

上位机轮询串口获取下位机反馈报文的算法

以前写过一篇『串口数据传输的读/写时机问题』,提到上位机(Master)发送报文给下位机(Slave)后,不能马上去读取所需信息,而需要等待一段时间,等下位机将信息采集完毕(比如温湿度值、光照,也包括上述提到的设备信息)后放在串口缓冲区,然后上位机再去取。当时采用的策略是采用固定等待时间,对于下位机可接受的所有报文均采用相同的延时。这样做有1个好处和2个坏处。好处是程序代码简单,WriteFile()/Sleep(t)/ReadFile()三行核心代码就能搞定。坏处是:一、报文功能不同,下位机的备齐时间也不同,有些简单的报文,根本无需与复杂报文一样等待t毫秒,因此这样设计造成了时间上的浪费。二、一般下位机的效率还受到所在环境的因素和折旧的影响,在其生命周期内不一定都能保证间隔t内能返回结果。t间隔内,现在能正确返回并不保证今后也能正确返回。

因此更好的方法是上位机轮询串口,将多次读取到的数据进行拼接,从而得到一个有效的下位机反馈报文。这种做法的前提是反馈报文的首字节需表示报文的长度字段,不过一般下位的传感设备的报文格式中肯定会包含报文长度字段,所以保证这样的前提不成问题。

采用轮询串口算法的伪代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
初始化字节数组chars[];
WriteFile(com); //将请求命令发送至串口。
记录当前时刻begin_time;
while(当前时刻-begin_time< VALID_DELYAY){//本次轮询串口的超时时间。
    ReadFile(com, nByteRead, buffer); //nByteRead表示已读到的数据长度,
					//buffer是读到的字节数据的存储缓冲区。
    if(nByteRead == 0){
        Sleep(1)//隔1毫秒再读一次。
    }else{
        strcat(chars, buffer, nByteRead);//将buffer中内容拼接到chars数组末尾。
        if(chars[0] == 当前的chars数组长度){
            break;//已取到合法的有效的反馈报文,结束循环。   
        }
    }
}

以上伪代码仅演示了正确读取反馈报文的流程,未加错误处理,譬如经过某次拼接后chars数组的长度大于chars[0]的值了,譬如chars[0]的值因线路噪音发生了跳变……等等。

--EOF--

串口数据传输的读/写时机问题

最近忙于毕业设计,虽然前期浪费了很多时间,但是整体上项目进度还是往前推进的,很欣慰。

今天碰到了一个纠结的问题,情况是这样的,上位机通过指令读取下位机中的设备信息,两者通过串口通信。因为下位机存储有限,关于设备信息的可存储空间为16个字节1组,共12组,故可以通过0...11这12个Index去索引到具体哪16个字节。每个信息报文的数据部分长度为16个字节,这意味着要读取完整设备信息需要向下位机发送12个报文,再把收到的报文进行整合,展现在界面上。

举个简单的例子,设备信息包含设备名、生产商、调试日期和备注信息等等,假设设备名存储在index 0索引的16个字节处,生产商信息存储在index 1索引的16个字节处,调试日期存储于index 2,备注信息存储于index 3……一般而言,上位机(Master)发送报文给下位机(Slave)后,不能马上去读取所需信息,而需要等待一段时间,等下位机将信息采集完毕(比如温湿度值、光照,也包括上述提到的设备信息)后放在串口缓冲区,然后上位机再去取。这中间的等待时间太小了下位机来不及处理,太大的效率低。不同下位机的data sheet会提供一个区间,如图1。

1
2
3
4
5
6
 
                    Master          |<- t ->|          Slave
          +------+------+-//-+------+       +------+------+-//-+------+
          | Req1 | Req2 | .. | Reqn |  ...  | Res1 | Res2 | .. | Resn |
          +------+------+-//-+------+       +------+------+-//-+------+
                                    |       |

图1 Master和Slave数据交换的等待时间t示意
(Download ASCII Picture)

例如我采用的设备这个间隔时间是10ms < t < 80ms,写程序时我在WriteFile数据到COM1后,很自然的sleep了15个ms(效率当然越高越好啦),然后ReadFile。结果每次连续发送12个查询报文之后总是发现数据会“串”掉,比如原本属于设备名的信息被送往了生产商,原本属于生产商的信息被送往了调试日期,以此类推。在debug模式下又没问题,纠结了一天,经过各种调试,才开始怀疑是不是上位机发送指令后的等待时间设置得过短,于是把sleep参数t改为60ms,再经测试,结果正确。 出现上述原因现在想想很简单了。比如我发送第一个指令查询设备名,因为等待时间不足,下位机还没处理好,所以ReadFile出来的结果要么为NULL,要么CRC验证出错。这时上位机来了第二条指令查生产商,同样有等待时间不足的问题,但是这次上位机能取到数据,这个数据其实是第一条指令查询设备名的反馈信息,因为下位机在此之前把它处理好放在了缓冲区,等待上位机去取的……于是,查生产商信息返回了设备名,查调试日期信息返回了生产商。而在debug模式下没这个问题,因为用F10单步调试有足够的时间等下位机处理好数据放置在缓冲区了。 --EOF--

SHT 1x传感器demo程序

SHT 1x是一款性价比挺高的数字温湿度传感器,它的好处坏处就不多说了,选用它的总归有理由的。关于SHT 1x传感器本身的信息详见官网。官网已经给出了这款传感器的demo程序,里面的注释也已经足够详细。本文只是对demo程序进行分段解释,并对其英文注释作个汉化,同时也把自己前段时间在项目中所学的SHT 1x传感器知识作为补充添加进去。

源代码文件可在此处下载得到。这个程序兼容所有51系列单片机,其他CPU型号可能需要作出一定的修改才能正确运行。下面就正式开始了。

1、头文件包含

1
2
3
4
#include <AT89s53.h> //当前型号CPU头文件,不过这个demo中用到它的只有P1.0和P1.1脚的定义。 
#include <intrins.h> //_nop_()函数所需头文件   
#include <math.h>   
#include <stdio.h>   //标准I/O头文件,printf()需要。

2、全局变量、结构体和宏定义

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
/* 
  定义接收传感器数字信号的变量类型,
  可以存湿度,也可以存温度。
 */
typedef union  
{ unsigned int i; 
  float f; 
} value; 
 
/* TEMP为0, HUMI为1 */
enum {TEMP,HUMI};
 
/* 
  宏定义传感器的DATA线和SCK线
  此后的代码不再硬编码,方便移植。
 */
#define	DATA   	P1_1
#define	SCK   	P1_0
 
/*
  宏定义传感器和CPU的应答信号,
  用1表示需要应答,用0表示无需应答。
  这里的1和0与实际DATA线的应答形式无关。
  DATA线用1个下降沿表示应答。
*/
#define noACK 0
#define ACK   1
 
/*
  定义SHT 1x传感器的5个命令字,每个字的具体含义见传感器手册。
*/
                            //adr  command  r/w
#define STATUS_REG_W 0x06   //000   0011    0 写状态寄存器
#define STATUS_REG_R 0x07   //000   0011    1 读状态寄存器
#define MEASURE_TEMP 0x03   //000   0001    1 温度测量
#define MEASURE_HUMI 0x05   //000   0010    1 湿度测量
#define RESET        0x1e   //000   1111    0 软复位

3、读写传感器总线(CPU提供SCK信号,将1个字节写入DATA或从DATA中读出1个字节)

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
//------------------------------------------------------------------------------
char s_write_byte(unsigned char value) 
//------------------------------------------------------------------------------ 
// 写入1个字节内容(命令字)到总线(DATA),并等待传感器确认。  
{  
  unsigned char i,error=0;   
  for (i=0x80;i>0;i/=2)       //表示执行8次for循环体内容
  { if (i & value) DATA=1;  //从高位开始,如果value某位为1,为DATA置1, 
    else DATA=0;              //某位为0,为DATA置0。   
    _nop_();                    //observe setup time 
    SCK=1;                     //让SCK输入一个时钟信号(1高1低) 
    _nop_();_nop_();_nop_();    // 持续3个_nop_()的时间使SCK为高电平   
    SCK=0; 
    _nop_();
  } 
  DATA=1;                    //释放DATA线
  _nop_();                          
  SCK=1;           //CPU输出第9个时钟,等待传感器拉底DATA信号(表示成功接收了value值)  
  error=DATA;    //以DATA线的当前逻辑电位赋值给返回状态 
          //这里可以在error=DATA前面加几个_nop_()指令,以确保传感器有足够的时间返回确认信号
  SCK=0;         
  return error;    //error=1表示DATA未被传感器确认,写失败。error=0表示写成功。
} 
 
//------------------------------------------------------------------------------ 
char s_read_byte(unsigned char ack) 
//------------------------------------------------------------------------------ 
// 从DATA中读出1个字节(命令字)。如果要求确认(ack=ACK),则在第9个时钟拉低DATA线 。 
{  
  unsigned char i,val=0; 
  DATA=1;                           //初始化DATA线。 
  for (i=0x80;i>0;i/=2)             //这个循环体与s_write_byte()不同的地方在于:
  { SCK=1;  //s_write_byte()是先把状态字(1次1位)写到DATA,再送出时钟(SCK)。
    if (DATA) val=(val | i); //而s_read_byte是先送出时钟(SCK),再从DATA中读状态字。  
    SCK=0;         
  } 
  DATA=!ack;                        //如果要求确认,则拉低DATA线。 
  _nop_();                        
  SCK=1;                          
  _nop_();_nop_();_nop_();          //保持1段时间的SCK高电平信号,使传感器有时间接收。  
  SCK=0; 
  _nop_();                               
  DATA=1;                           //释放DATA线 
  return val; 
}

4、启动传输信号(Transmission Start)

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
//-----------------------------------------------------------------------------
void s_transstart(void) 
//-----------------------------------------------------------------------------
//CPU给出一个TS信号,表示要开始向传感器发送命令字了。
//TS信号定义为:初始化DATA为1,SCK为0,
//当SCK为高电平时拉低DATA,此后SCK变低,当SCK再次为高时释放DATA。
//       _____         ________ 
// DATA:      |_______| 
//           ___     ___ 
// SCK : ___|   |___|   |______ 
{   
   DATA=1; SCK=0;                   //初始化DATA和SCK
   _nop_(); 
   SCK=1;                    //当SCK为高电平时
   _nop_(); 
   DATA=0;                    //拉低DATA
   _nop_(); 
   SCK=0;                      //SCK变低
   _nop_();_nop_();_nop_(); 
   SCK=1;                     //SCK再次变高电平
   _nop_(); 
   DATA=1;                     //释放DATA,完成TS信号     
   _nop_(); 
   SCK=0;      
}

5、传感器复位时序

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
//----------------------------------------------------------------------------- 
void s_connectionreset(void) 
//----------------------------------------------------------------------------- 
// 传感器复位时序定义:DATA保持9个SCK时钟的高电平时间,然后后面紧跟一个TS启动传输信号。 
//       _____________________________________________________         ________ 
// DATA:                                                      |_______| 
//          _    _    _    _    _    _    _    _    _        ___     ___ 
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______|   |___|   |______ 
{   
  unsigned char i;  
  DATA=1; SCK=0;                    //初始化DATA和SCK
  for(i=0;i<9;i++)                  //9个SCK时钟 
  { SCK=1; 
    SCK=0; 
  } 
  s_transstart();                   //发出一个TS启动传输信号。 
}
 
//----------------------------------------------------------------------------- 
char s_softreset(void) 
//----------------------------------------------------------------------------- 
// 通过向传感器发送一个命令字RESET来实现软复位,能清空状态寄存器,恢复默认值。  
{  
  unsigned char error=0;   
  s_connectionreset();              //传感器复位(不会清空状态寄存器)。 
  error+=s_write_byte(RESET);       //如果传感器正确接收,会返回0(DATA被拉低)。 
  return error;                     //如果error=1则表示传感器未正确接收。 
}

6、读写传感器的状态寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//-------------------------------------------------------------------------------
char s_read_statusreg(unsigned char *p_value, unsigned char *p_checksum) 
//------------------------------------------------------------------------------- 
//根据协议从传感器分别读出1个字节的状态寄存器内容和1个字节的校验码 
{  
  unsigned char error=0; 
  s_transstart();                   //传输开始
  error=s_write_byte(STATUS_REG_R); //先向传感器发送一个“读状态寄存器”的命令字 
  *p_value=s_read_byte(ACK);//返回的第1字节表示状态寄存器内容,并需要确认(告知还要继续传输) 
  *p_checksum=s_read_byte(noACK);   //返回的第二字节表示校验码,无需确认,因为传输结束了。   
  return error;//如果error=1表示s_write_byte()失败,p_value和p_checksum值是废的 
}
 
//------------------------------------------------------------------------------- 
char s_write_statusreg(unsigned char *p_value) 
//------------------------------------------------------------------------------- 
//根据协议写一个字到状态寄存器。 
{  
  unsigned char error=0; 
  s_transstart();                   //传输开始
  error+=s_write_byte(STATUS_REG_W);//向传感器发送一个“写状态寄存器”的命令字 
  error+=s_write_byte(*p_value);    //向传感器发送一个状态寄存器值 
  return error; //如果error>=1表示上面的两个s_write_byte()至少1个执行失败,函数执行失败。 
}

7、温湿度测量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//---------------------------------------------------------------------------------- 
char s_measure(unsigned char *p_value, unsigned char *p_checksum, unsigned char mode) 
//---------------------------------------------------------------------------------- 
// 测量温湿度。 
{  
  unsigned char error=0; 
  unsigned int i; 
 
  s_transstart();                   //启动传输 
  switch(mode){                     //表示当前是测温度还是测湿度。 
    case TEMP : error+=s_write_byte(MEASURE_TEMP); break;//如果是测温度,就发测温度命令
    case HUMI : error+=s_write_byte(MEASURE_HUMI); break;//如果是测湿度,就发测湿度命令 
    default     : break;   
  } 
  for (i=0;i<65535;i++) if(DATA==0) break; //传感器测量好之后会拉低DATA,CPU结束等待。 
  if(DATA) error+=1; // 如果DATA还是高电平,表示上个循环不是break出来的,传感器未返回成功。 
  *(p_value)  =s_read_byte(ACK);    //从总线读测量结果的高字节。
  *(p_value+1)=s_read_byte(ACK);    //从总线读测量结果的低字节。
  *p_checksum =s_read_byte(noACK);  //读校验值 
  return error; //error>=1表示返回错误。
}

8、串口初始化

1
2
3
4
5
6
7
8
9
10
//-----------------------------------------------------------------------------
void init_uart() 
//-----------------------------------------------------------------------------
//这段程序,包括前面的stdio.h头文件,均不是必须,
//只是因为main函数里用到了printf()函数,所以才要初始化串口。
{SCON  = 0x52; //设置串口控制寄存器:串口1工作方式1(8位UART,波特率可变)    
 TMOD  = 0x20; //设置定时器工作模式寄存器:定时器1工作模式为8位自动重装定时器    
 TCON  = 0x69;//设置定时器控制寄存器:启动定时器1    
 TH1   = 0xfd;// 产生9600 bps @ 11.059 MHz波特率所需的初始值   
}

9、计算湿度和温度

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
//------------------------------------------------------------------------------ 
void calc_sth11(float *p_humidity ,float *p_temperature) 
//------------------------------------------------------------------------------ 
// 计算温度[°C]和湿度[%RH]
//s_measure()函数返回的只是2个字节的原始数据(raw data),要得到正确的温湿度值还需要转化。
//这里计算的是当传感器状态寄存器最低位为0的情况,即12位湿度和14位温度的情况。
//温湿度计算公式和补偿公式详见传感器的数据手册。  
// input :  humi [Ticks] (12 bit)  
//          temp [Ticks] (14 bit) 
// output:  humi [%RH] 
//          temp [°C] 
{
  /* C1,C2,C3,T1,T2是计算湿度所需的参数,如果是8位湿度,这些值会变。 */
  const float C1=-2.0468;           // 12位湿度的参数 
  const float C2=+0.0367;           // 12位湿度的参数 
  const float C3=-0.0000015955;     // 12位湿度的参数 
  const float T1=+0.01;             //12位湿度的参数 
  const float T2=+0.00008;          //12位湿度的参数  
 
  float rh=*p_humidity;             // rh: s_measure()函数返回的湿度值  12位
  float t=*p_temperature;           // t: s_measure()函数返回的温度值  14位
  float rh_lin;                     // rh_lin: 相对湿度值,不是最终的。 
  float rh_true;                    // rh_true: 考虑的温度补偿的最终湿度值 
  float t_C;                        // t_C   :  最终温度值
 
  t_C=t*0.01 - 40.1;              //温度值计算
  rh_lin=C3*rh*rh + C2*rh + C1;     //相对湿度计算 
  rh_true=(t_C-25)*(T1+T2*rh)+rh_lin;   //根据温度补偿,计算最终湿度值 
  if(rh_true>100)rh_true=100;       //对于超量程的湿度值,要设定成最大量程和最小量程 
  if(rh_true<0.1)rh_true=0.1;       
 
  *p_temperature=t_C;               //返回温度值 [°C] 
  *p_humidity=rh_true;              //返回湿度值[%RH] 
} 
 
//-------------------------------------------------------------------- 
float calc_dewpoint(float h,float t) 
//-------------------------------------------------------------------- 
// 计算露点 (露点的概念、原理和计算公式详见相关资料)
// input:  湿度 [%RH], 温度[°C] 
// output:  露点 [°C] 
{ float k,dew_point ; 
 
  k = (log10(h)-2)/0.4343 + (17.62*t)/(243.12+t); 
  dew_point = 243.12*k/(17.62-k); 
  return dew_point; 
}

10、主程序

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
//---------------------------------------------------------------------------------- 
void main() 
//---------------------------------------------------------------------------------- 
// 展示了以上函数调用的具体步骤: 
// 1. 传感器复位s_connectionreset()  
// 2. 测量温湿度s_measure()
// 3. 计算温湿度calc_sth11()
// 4. 计算露点calc_dewpoint() 
// 5. 打印温湿度和露点printf()   
 
{ value humi_val,temp_val; 
  float dew_point; 
  unsigned char error,checksum; 
  unsigned int i; 
 
  init_uart(); //初始化串口
  s_connectionreset(); //传感器复位
  while(1) //不停循环,因为单片机的main函数无返回(返回单片机就不工作了)。
  { error=0; 
    error+=s_measure((unsigned char*) &humi_val.i,&checksum,HUMI);//测量湿度
    error+=s_measure((unsigned char*) &temp_val.i,&checksum,TEMP);//测量温度
    if(error!=0) s_connectionreset();//如果测量温湿度出错,则重新复位传感器。 
    else 
    { humi_val.f=(float)humi_val.i;//将整数强制转换成浮点数,
      temp_val.f=(float)temp_val.i;//强制转换无需考虑整数和浮点数在寄存器中存储方式。
      calc_sth11(&humi_val.f,&temp_val.f);//计算温湿度
      dew_point=calc_dewpoint(humi_val.f,temp_val.f); //计算露点
      //打印输出值
      printf("temp:%5.1fC humi:%5.1f%% dew point:%5.1fC\n",temp_val.f,humi_val.f,dew_point); 
    } 
   for (i=0;i<40000;i++); //延迟一小会儿,使得传感器不至于持续工作而发热,影响测量结果。 
  } 
}

以上就是SHT 1x系列传感器demo程序的分段解释,相对也比较简单,只要熟读datasheet,搞清楚它的工作原理,理解和编写起来没什么大问题,demo中的main函数只是调用了几个最常用的测量函数,还有比如读写状态寄存器和软复位程序均没有用到,这些函数的调用方法也类似,没什么不同。

--EOF--

51单片机串口程序

这个程序实现了PC与单片机的简单交互,从键盘读取一个定长为4的字符串,通过PC串口发送给单片机,再由单片机通过串口程序将该字符串返回给PC。一般串口通信程序均可按此模板进行编程。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <REG51.h>
#include <stdio.h>
 
unsigned char c[5] = {0};  //缓冲区为4字节。一次从PC接收4个字符,
                        //之所以要分配5个字节,是为了下面的SendString()例程考虑。
int ready; //信号量,当ready为0,表示缓冲区尚未准备好,主程序阻塞。
int length = 0; //记录单片机当前已接收字符串长度。
 
//串口初始化例程。
void init_uart()
{
	SCON = 0x50;	//SCON: mode 1, 8-bit UART, enable rcvr
	TMOD = 0x20;	//TMOD: timer 1, mode 2, 8-bit reload
	TH1 = 0xFD;	//TH1:  reload value for 9600@11.0592MHz
	TCON = 0x69;
	TR1 = 1;	//启动定时器1
	ES = 1;	//允许串口中断位
	EA = 1;	//允许总中断位
}
 
//从串口发送一个字符
void SendOneChar(char c)
{
	ES=0; //发送数据时关串口中断
	TI=0; //清TI位
 
	SBUF = c; //将待发送字符放入串口数据缓冲寄存器,并开始传输。
 
	while(!TI); //等待传送结束。当字符发送结束后由硬件置位请求中断,
             //此时while循环结束,而串口中断处理函数需等到ES重新置1后才可能响应。
	TI=0; //软件清TI位,亦可在中断处理函数中清0
	ES = 1; //开串口中断
}
 
//向串口发送字符串
void SendString(char *st)
{
	while(*st)
	{
		SendOneChar(*st++);
	}
}
 
//串口中断服务程序。每执行1次,从串口接收1个字符。
void uart_interrupt() interrupt 4
{
	ready = 0; //当本中断服务程序接收到所需长度字符串后置ready为1,
	          //使主程序往下执行。
 
	if(RI) //当SBUF接收到1个字符,硬件置RI为1。
	{
		RI = 0; //清RI
		if(length < 4) //将接收到的字符缓存在c数组里。
		{
			c[length++] = SBUF;
		}
		if(length >= 4) //如果数组已满,表示已经接收到指定长度字符串。
		{
			ready = 1; //置ready
			length = 0; //清length
		}
	}
	if(TI) //当SBUF发送完1个字符后,由硬件置TI为1。
	{
		//字符发送中断处理,本例中没有涉及。
	}
}
 
void main()
{
	int i;
 
	init_uart();
	while(1)
	{
		ready = 0;
		while(!ready);//阻塞,等待串口接收4个字节的字符串。
		for(i = 0; i < 4000; i++);//延迟一小段时间
		SendString(c);//发送字符串到PC
	}
}

--EOF--

STC单片机串口通信波特率设置问题

        昨天以来一直为一个问题困扰:单片机要发送一个(或一串)即时状态(或数据)到PC端,PC端的串口助手总是取不到正确的值。我在PC端设置好串口为9600Baud,8位数据位,1位起始位,1位停止位,无奇偶校验位。在单片机程序中编程SCON,设置串口工作模式1,8位UART,波特率可变。同时采用定时器1作为波特率发生器,编程TMOD寄存器,设置为方式2,8位自动重装模式。因为STC单片机默认(未设置AUXR寄存器)是工作在12T模式的,因此根据波特率计算公式:Baudrate = Fosc / ( 32 * 12 * (256 - x) ), 其中x为要装入TH1和TL1的初始值, Fosc为外部晶振频率,这里是11.0592MHz。计算可得x = 0xFD。完整的初始化串口程序如下:

1
2
3
4
5
6
SCON = 0x50; /* 串口模式1,8位UART,波特率可变。 */
TMOD = 0x20; /* 定时器1方式2,8位自动重装模式。 */
TH1 = 0xFD; /* 波特率为9600@11.0592MHz,定时器初始值0xFD */
TR1 = 1; /* 启动定时器1 */ 
ES = 1; /* 允许串口中断 */
EA = 1; /* 允许总中断开关 */

        但是PC端串口助手接收到的值总是与单片机发送的值不同,两者之间毫无规律可循,而且PC端收到的数据长度总是为单片机发送长度的2倍。有一个例外,那就是发送0x00,这个数值能在PC端正确接收。
        解决方法可以尝试将PC端的窗口波特率设置为4800,即设置成单片发送波特率的一半。我估计原因是假如上面的串口初始化程序没错,PC端接收到的数据长度却是单片机发送出的长度的2倍,就表示接受波特率是发送波特率的2倍。降低接收端波特率,使两端串口仍旧处在同一波特率之下可正常工作。问题是可以这样解决,但是不是这个理由支撑就不清楚了,也有可能是上述的初始化串口程序有问题,本身就是将串口初始成4800波特率的。

--EOF--