×
嵌入式 > 技术百科 > 详情

STM8S003F使用IO口模拟串口(三)使用中断方式发送和接收数据

发布时间:2020-12-28 发布时间:
|

在前两篇文章中我们介绍了IO口模拟串口发送数据和接收数据,前两种方法都是使用定时器来进行发送和接收,没有用到中断,优点是逻辑简单,但是缺点很明显,只能进行单个字节的发送和接收,而且不能同时工作。因此在实际工程中没有什么作用,仅供学习使用。使用中断方式我们可以发送和接收多个字节的数据。


1、使用中断方式进行IO口模拟串口发送和接收数据的原理

这篇文章我将使用中断的方式进行发送和接收,同样的,由于原理缺陷,这篇文章介绍的方法无法同时接收和发送,而且由于发送会延时,是一个不太好的方法,仅供学习使用。

注意:这篇文章实现的IO口模拟串口无法同时接收和发送数据!如有需要在实际项目中使用IO口模拟串口工作,请移步:


1.1、发送数据的原理

我们使用定时器更新中断来进行数据的发送,首先在发送函数中开启中断,然后在中断函数中逐位发送,直到发送10位(一个字节,我们暂时没有使用校验位)后关闭更新中断。

1.2、接收数据的原理

我们使用单片机的外部中断(IO中断)来开启比较中断,在比较中断中逐位接收,直到接收了10位后关闭比较中断,并保存接收的有效字节个数。


2、实现过程

同样的,在实现过程中,我们在工程文件夹SimUART中共分了4个文件夹(分别为System:存放系统文件;Project:存放项目文件;User:存放main.c和UserApp.c;My_Lib:存放其它常用的文件)。根据我们将用到的单片机的资源,我们在My_Lib中分了二个文件夹,分别是——IO:存放与IO口相关函数的文件;Time:与定时器和中断相关函数的文件。下面我贴出相关函数的.c文件,而.h文件省略不写,有需要的同学可以根据文章后面的网址下载使用。我的编程环境是IAR,需要自己建立IAR工程。下面详细介绍(Project和System省略不写,其中System只用了stm8s.h)。


2.1、一切从main()函数开始

同样的,我们建立完工程后需要从main()函数开始,为便于理解,我将使用逻辑伪代码,逻辑伪代码如下:


int main( void )

{

单片机时钟初始化;

IO口初始化;

定时器初始化;

中断初始化;

while(1)

{

if( 需要发送的数据数 > 0 )

{

发送;

需要发送的数据数 = 0;

}

}

}


我们首先需要进行初始化的配置All_Config()【在UserApp.c中】,代码如下:


//head file 

#include "UserApp.h"

#include "IO.h"

#include "User.h"

#include "Time.h"

#include "Delay.h"

 

u16 SimUART_SendData = 0xFF;

u8 SimUART_SendData_BitNum = 10;

u8  RxData_ValidNum = 0;

u16 RxDataValue_Temp = 0x0000;

 

//初始化函数

void All_Config( void )

{

    Clock_Config();

    IO_Init();  

    TIM2_Init();

    EXTI_Init();

}


其中User.h是我将自己常用的宏写在了一个文件里面,对应于main.c。在没有接外部时钟的时候,STM8S003F在启动时主时钟默认为HSI RC时钟的8分频,我们这里的初始化仅指定为16MHZ高速内部RC振荡器(HSI),也可以省略不写,Clock_Config()【在UserApp.c中】函数代码如下:


//初始化时钟 选择内部16M晶振

void Clock_Config()

{

    CLK->CKDIVR &= ~( BIT(4) | BIT(3) );

}


我选择单片机的PD2作为我的模拟串口的数据发送口,选择PD3作为我的模拟串口的数据接收口,IO_Init()【在IO.c中】函数代码如下:

//head file

#include "IO.h"

#include "User.h"

 

void IO_Init()

{

    //TXD:TXD位推挽输出  PD2

    SimUART_PORT->ODR |=  SimUART_PIN_TX; //0000 0100

    SimUART_PORT->DDR |=  SimUART_PIN_TX; //0000 0100

    SimUART_PORT->CR1 |=  SimUART_PIN_TX; //0000 0100  

    SimUART_PORT->CR2 &= ~SimUART_PIN_TX; //0000 0100

    

    //RXD:悬浮输入 高电平 PD3

    SimUART_PORT->IDR |=  SimUART_PIN_RX; //0000 1000

    SimUART_PORT->DDR &= ~SimUART_PIN_RX; //0000 1000

    SimUART_PORT->CR1 &= ~SimUART_PIN_RX; //0000 1000

    SimUART_PORT->CR2 &= ~SimUART_PIN_RX; //0000 1000

}

其中在IO.h中的宏定义为:

//宏定义

#define SimUART_PORT GPIOD

#define SimUART_PIN_TX 0X04   //PD2

#define SimUART_PIN_RX 0X08   //PD3

#define SimUART_PIN_RX_0 0X00 //PD3

#define SimUART_PIN_RX_1 0X08 //PD3

定时器的初始化和前面一样,具体操作可以见这里。代码如下:

void TIM2_Init()

{

    CLK->PCKENR1 |= CLK_PCKENR1_TIM2; //使能 TIM2 

    TIM2->PSCR    = 0x04;                //16分频 1MHZ 1us

    TIM2->ARRH    = ARRValue_9600 >> 8;  //自动装载 每52us复位一次TIM2

    TIM2->ARRL    = ARRValue_9600;        //每1us递减1

    TIM2->CNTRH = 0;              //定时器清零

    TIM2->CNTRL = 0; 

  

    TIM2->CR1 |= TIM2_CR1_CEN;    //开启定时器

}

其中Time.h中的宏定义为:

#define ARRValue_9600   104

中断初始化EXTI_Init()【在UserApp.c中】代码如下:

//初始化中断

void Interrupt_Init()

{

    //允许更新中断

    //TIM2->CR1 &= ~TIM2_CR1_UDIS;      //允许更新  可以不管默认为0

    TIM2->IER |= TIM2_IER_UIE;          //更新中断使能

 

    //IO口下降中断 初始化

    SimUART_PORT->CR2 |= 0x08;          //使能外部中断

    EXTI->CR1 = 0x80;                   //仅下降沿触发    

 

    //禁止比较中断

    TIM2->IER &= ~TIM2_IER_CC1IE;       //禁止捕获/比较1

}

根据上面的原理,我们知道:更新中断是在发送函数中打开的,因此更新初始化中使能;IO中断是通过下降沿(串口数据的起始位为低电平)打开的,因此设置成使能和下降沿触发;比较中断实在IO中断中打开的,因此设置成禁止。


2.2、模拟串口发送数据

完成时钟、IO口、定时器、中断的初始化以后我们就可以开始主体程序的设计了,逻辑伪代码如下:


//发送 函数

void SimUART_SendByte(u8 SendData)

{

等待一个字节发送完毕;

第一步:清除 更新更新中断标志位(保证不进入更新中断);

第二步:数据调整(起始位为0,数据位不变,停止位和其它位为1);

第三步: 开启更新中断;

}

 

//定时器更新中断  发送接收到的数据

#pragma vector = 对应向量标志位

__interrupt void SimUART_Update_IRQHandler(void)

{

第一步:清除 更新中断标志位(保证不进入更新中断);

发送一个位计数;

if( 发送位为1 )

{

发送高电平;

}

esle

{

发送低电平;

}

移位,发送下一个位;

//完成了一个位的发送

if( 发送了10个位 )

{

关闭中断;

}

}


进入发送函数,首先应该清除更新中断标志位,然后写功能代码,结束前需要打开更新中断,从而去执行更新中断的代码。我们需要考虑为何需要一个延时来等待一个字节完成发送。在中断函数中我们是让一个字节发送完成以后才关闭中断的,如果不延时,可能发生一个字节还没有发送完成,却进入下一个更新中断的情况,因此需要等待,我们直接用一个标志位就能解决。对应向量标志位通过查芯片手册和头文件可以得到。发送函数在UserApp.c中,更新中断在Time.c中,发送部分代码如下:


void SimUART_SendByte(u8 SendData)

{   

    while( SimUART_SendData_BitNum < 10 );

    

    //清 更新中断标志位

    TIM2->SR1 &= ~TIM2_SR1_UIF;

    

    //0000 0000 0000 0000 保证最低位(起始)为0,除数据位后全部为1

    SimUART_SendData = ( ( SendData << 1 )| (0xFE00) );

    SimUART_SendData_BitNum = 0;

    

    //开启更新中断

    TIM2-> IER |= TIM2_IER_UIE;    

}


//定时器更新中断  发送接收到的数据

#pragma vector = TIM2_Updata_vector

__interrupt void SimUART_Update_IRQHandler(void)

{

    //第一步,清中断标志位

    TIM2->SR1 &= ~TIM2_SR1_UIF;

    

    //发送一个位 计数

    SimUART_SendData_BitNum++;

    

    if( ((SimUART_SendData) & 0X0001) )  //如果是高电平,发高电平

    {

        SimUART_PORT->ODR |= SimUART_PIN_TX;

    }

    else                                        //如果是低电平,发低电平

    { 

        SimUART_PORT->ODR &= ~SimUART_PIN_TX;

    }

    

    //发送一个位 

    SimUART_SendData >>= 1;

        

    if( 10 <= SimUART_SendData_BitNum )

{



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
洪荒之力+无比专注=国内首款机器人离线编程软件