使用PIC单片机下降沿中断脚和定时器扩展串口接收

这是使用PIC单片机的下降沿中断脚,结合定时器扩展串口接收。

一、串口发送的原理

1、假定串口波特率为9600位/秒,则传送一位值的时间为:

1000000(us)/9600=104.16us

2、假定要发送的数据data,其位格式如下:

   bit7,bit6,bit5,bit4,bit3,bit2,bit1,bit0

   bit7是data的最高位,bit0是data的嘴低位,则:

   data= bit7 * 2^7 + bit6 * 2^6 + bit5 * 2^5 + bit4 * 2^4

 + bit3 * 2^3 + bit2 * 2^2 + bit1 * 2^1 + bit0 * 2^0

3、串口在发送数据时,总是先发送起始位start位,start是个“低电平位值”,所以TXD脚由高到低发生跳变,其低电平的时间为104.16us。接着发送数据data的bit0,bit1,bit2,bit3,bit4,bit5,bit6,bit7,最后发送停止位stop位,stop是个高电平位值,其高电平的时间为104.16us

4、对于8位数据通讯(N8),则有10位值要被发送出去,即发送顺序如下:

start + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + stop

我们扩展模拟接收串口,主要是讲这个通讯。

5、对于9位数据通讯(N8、1),则有11位值要被发送出去,即发送顺序如下:

start + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + TX8 + stop

上面的TX8有3种意思:

① TX8是从机地址/数据识别位,TX8=1,表示data的值为地址,TX8=0,表示data值为数据。

TX8是奇偶校验位。

对于偶校验,校验位就定义为1对于校验,校验位就定义为0

若是奇校验,则发送数据的格式为:

start + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + 0 + stop

若是偶校验,则发送数据的格式为:

start + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + 1 + stop

奇偶校验能够检测出信息传输过程中的部分误码(1位误码能检出,2位及2位以上误码不能检出),同时,它不能纠错。但由于其实现简单,仍得到了广泛使用。

TX8是停止位,这样就有两个停止位(stop1,stop2)。也就是我们通常所说的8位数据通讯(N8)这样发送数据的格式为:

start + bit0 + bit1 + bit2 + bit3 + bit4 + bit5 + bit6 + bit7 + stop1 + stop2

注意:stop1=1,stop2=1。

使用两个停止位,硬件串口发送位数为11位,但硬件串口接收位数是10位的,也是可以收到正确的数据

二、模拟串口接收的原理:

通过对串口发送的数据格式,分析可以知道,每一位值的发送时间都要占用104.16us。所以串口在接收时,总是先收到start位,接着是bit0、bit1、bit2 、bit3、bit4、bit5、bit6、bit7,最后是stop位。我们可以设想模拟硬件串口“位采集”情况,假如“位采集”发生在104.16us的中间位置,是不是模拟串口就可以接收数据了呢?显然是可行的。我的想法是这样的:

  1. 当start到来时,下降沿产生中断,进入“下降沿中断服务程序”,立即打开定时器,并设置定时器在第52us时发生中断,将bit_counter=1,再设置不使能下降沿中断,退出“下降沿中断服务程序”。
  2. 当定时器产生中断时,设置定时器为每104us时间产生一次中断。将收到的位值temp_bit右移入data中。
  3. bit_counter左移一位。
  4. 如果bit_counter=0,则将保存接收到数据data,关闭定时器,使能下降沿中断,本次接收完成。

注意:停止位不接收。若要接收TX8位,在内部指令周期比较高的情况下是可以的。

  1. 退出“定时器中断服务程序”。

注意:

硬件串口发送位数为11位/10位,模拟串口接收位数是9位,好处是串口接收中断服务程序的执行时间被加宽了

三、流程图设计:

1、波特率为9600bps,下降沿中断服务程序流程图:

2、波特率为9600bps,不接收TX8的位值,定时器中断服务程序流程图:

本流程图在指令周期小于等于0.5us的情况下,实现是可以的。

3、波特率为9600bps,接收TX8的位值,定时器中断服务程序流程图:

本流程图在指令周期小于等于0.25us的情况下,实现是可以的。

4、在指令周期为0.5us的情况下,举例:

#define Simulate_RXD2_Pin   PIN_A2 //定义模拟接收中断脚;

#define SSP2_pwr            PIN_C2

#byte TRISA=getenv("sfr:TRISA") //定义数据方向寄存器TRISA的地址;

#bit  TRISA2 = TRISA.2

#byte PORTA=getenv("sfr:PORTA") //定义数据方向寄存器TRISA的地址;

#bit  RA2 = PORTA.2

#byte TMR1H=getenv("sfr:TMR1H") //定义Timer1高8位计数器TMR1H的地址;

#byte TMR1L=getenv("sfr:TMR1L") //定义Timer低8位计数器TMR1L的地址;

#byte T1CON=getenv("sfr:T1CON") //定义Timer1控制寄存器T1CON的地址;

#bit  TMR1ON = T1CON.0

#byte PIR1=getenv("sfr:PIR1") //定义PIR1的地址;

#bit  TMR1IF = PIR1.0

#byte INTCON=getenv("sfr:INTCON") //定义INTCON3的地址;

#bit  INTIF = INTCON.1    //INT脚中断标志位

///CPU的晶振为8MHz///

#define FOSC2  8000000L      //模拟串口执行要用43.5us~53.5us;

#define RXD2_Baud  9600L

#define Load_us52    256-(FOSC2>>3)/RXD2_Baud+80  //设置Timer1为52us中断一次;

#define Load_us104   256-(FOSC2>>2)/RXD2_Baud+6  //设置Timer1为104us中断一次;

#INT_EXT

void INT0_interrupt_program()

{ TMR1H=0xFF;

  //TMR1L=Load_us52;

  TMR1L=INT_value;

  TMR1ON=1;

  RXD2_bit_cnt=1; //移位计数器,RXD2_bit_cnt=0表示接收完成;

  clear_interrupt(INT_EXT);

  //enable_interrupts(INT_EXT);  //使能外部中断0

  disable_interrupts(INT_EXT);   //不使能外部中断0

}

//在指令周期为0.5us时,模拟串口执行要用43.5us~53.5us,

//其他硬件中断的服务程序执行时间要小于50.5us;

#INT_TIMER1

void Timer1_Counter()

{ unsigned int8 temp;

  RXD2_temp_bit_value=RA2; //记忆RA2的电平;

  //RXD2_temp_bit_value=~RXD2_temp_bit_value;

  TMR1H=0xFF;

  TMR1_temp_value = TMR1L;

  TMR1_temp_value=TMR1_temp_value+TMR1_value;

  TMR1L = TMR1_temp_value;

  shift_right( &RXD2_Receiver_temp_Data,1, RXD2_temp_bit_value );

/*

  RXD2_Receiver_temp_Data=RXD2_Receiver_temp_Data>>1;

  if(RXD2_temp_bit_value)

    { RXD2_Receiver_temp_Data=RXD2_Receiver_temp_Data|0x80;

    }

  else RXD2_Receiver_temp_Data=RXD2_Receiver_temp_Data&0x7F;

*/

///0.5us指令周期,模拟串口执行要用43.5us~53.5us,TX8位和停止位不能被接收///

  if(RXD2_bit_cnt==0x00) //接收完成,停止位不接收,直接处理数据;

    { TMR1ON=0; //接收完成;

      SSP2_RCV_buffer[SSP2_in]=RXD2_Receiver_temp_Data; //保存数据;

      temp=SSP2_in;          //将接收缓冲区的下标值暂存放在temp中;

      SSP2_in++; //SSP2_RCV_buffer[]下标加1;

      if(SSP2_in>=SSP2_RCV_buffer_Size-1) SSP2_in=0;

      //若接收缓冲区满,则将SSP2_in设置为0;

      if(SSP2_in==SSP2_out)  SSP2_in=temp;

      //若"输入下标值"同"输出下标值"相等,则将"输入下标值"改为原值;

      delay_us(57); //跳过对TX8的检测;

      clear_interrupt(INT_EXT);    //清除下降沿标志位;

      enable_interrupts(INT_EXT);  //使能外部中断0

    }

  RXD2_bit_cnt=RXD2_bit_cnt<<1;

}

四、在没有串口调试的情况下,用示波器看串口波形,使用人工方式读取接收到的数据。

1、串口发送一个字节的波形图如下:

T的单位是us,可以看处,串口发送的数据是0x55。

2串口发送一个字节的波形图如下:

T的单位是us,可以看处,串口发送的数据是0xAA

注意:串口在发送时,总是先发最低位,因此,根据波形图读数据,在有些公司面试时,可能遇到,因为他们想知道你对串口掌握的是不是很精通。其实,回答错了,没有关系,但是,你可能会失去这次机会。

3、看串口发送的波形,主要是为了测量波特率是不是正确。

在上图中,波特率是多少呢?

通过:T=1000000(us)/BPS;可以计算波特率的值。

4、没有示波器时,我们可以将数据发给PC机,通讯终端或是串口调试助手,看看我们发送的数据是不是正确,由此而来判断我们的波特率设置是不是正确。

只有先设置好波特率,才能进行正确的数据交换,否则,串口接收到的数据都是没有意义的。

五、编写串口通讯的经验交流:

1、只有先设置好波特率,才能进行正确的数据交换,否则,串口接收到的数据都是没有意义的

2、确认能否进入串口接收中断服务程序。

我的做法是,当串口中断时,将接收到的数据保存接收缓冲区RCV_Buffer[]中。在主程序中查询RCV_Buffer[]中的数据是否有新数据被收到,若收到,通过串口发送脚发送出去,用示波器查看,或是发给PC机。

//全局变量定义

#define GSM_RCV_buffer_Size 600         //定义GSM接收缓冲区的长度;

unsigned int8 GSM_RCV_buffer[GSM_RCV_buffer_Size]; //用来存放硬件串口接收到的数据;

unsigned int16 GSM_in = 0;  //GSM接收缓冲区的输入下标;

unsigned int16 GSM_out = 0; //GSM接收缓冲区的输出下标;

#define GSM_RCV_Flag  (GSM_in!=GSM_out)

//GSM_RCV_Flag=1,表示GSM的接收缓冲区有新的数据输入;

//函数功能:GSM串口的接收中断服务函数;

#int_rda2 HIGH

void GSM_Serial_Interface_Interrupt_Program()

{ unsigned int8 temp;

restart_wdt();

temp=fgetc(GSM);  //从GSM串口读取一个字节;

GSM_RCV_buffer[GSM_in]=temp;

temp=GSM_in;          //将接收缓冲区的下标值暂存放在temp中;

GSM_in=GSM_in+1;     //修改GSM_in的值,为下1次接收做准备;

if(GSM_in>=GSM_RCV_buffer_Size-1) GSM_in=0;

//若接收缓冲区满,则将GSM_in设置为0;

if(GSM_in==GSM_out)  GSM_in=temp;

//若"输入下标值"同"输出下标值"相等,则将"输入下标值"改为原值;

//GSM_RCV_buffer[]满了;

}

3、中断服务程序写得短小的原因:

在做中断服务程序时,力求所有的中断服务程序越短小越好。当一个中断服务程序太长时,就会影响其它中断服务程序的执行质量。滥用中断,CPU运行时,会出现各种莫名其妙的错误。如果想用好所有的中断服务程序,就将中断服务程序写得越短小越好,分配好中断服务程序的执行时间大小,就可以了。

例如:

① 如果一个程序,有串口中断,还有其他中断,并且串口中断服务程序执行时间为1000(ms)/波特率,则其它中断的服务程序的总执行时间就要小于1000(ms)*8/波特率。这就要求在分配中断服务程序的执行时间时,就要我们注意了。

② 如果一个程序,只有一个串口接收中断服务程序程序,没有其他中断了,则串口中断服务程序执行时间要小于1000(ms)*9/波特率。

也就是是说在下一个数据到来之前,必须读一次串口,才不会导致串口接收错误。

通过上面的举例说明,我们知道为什么要将中断服务写得短小的原因了吧。

不要过分追求完美,因为在我们的生活中,很多东西都不是完美的。希望大家一起进步。.

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 04:22:04       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 04:22:04       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 04:22:04       57 阅读
  4. Python语言-面向对象

    2024-07-15 04:22:04       68 阅读

热门阅读

  1. 如何评价一个AI系统

    2024-07-15 04:22:04       20 阅读
  2. 查找运行中 sql中bind variable value 绑定变量值

    2024-07-15 04:22:04       22 阅读
  3. appium 实战问题 播放视频时无法定位到元素

    2024-07-15 04:22:04       28 阅读
  4. Django模板语言(简略教程)

    2024-07-15 04:22:04       25 阅读
  5. std::async和std::future异步编程

    2024-07-15 04:22:04       23 阅读
  6. C++智能指针

    2024-07-15 04:22:04       20 阅读
  7. Python面试题:如何在 Python 中进行单元测试?

    2024-07-15 04:22:04       24 阅读
  8. git使用

    2024-07-15 04:22:04       21 阅读