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

STM8S103系列IO口模拟串口通信(实现真正串口)

发布时间:2020-06-17 发布时间:
|

STM8s103f3p3单片机只有一个串口,有时候在实际项目中,我们需要用到多个串口来实现项目要求,此时,我们可以通过IO口来模拟串口通信,在实现项目需求的同时,还大大降低了成本。


实验原理


默认串口空闲状态为高电平,开始位为0,然后发送8个数据位,然后是奇偶校验位,停止位为高电平。数字电路中只有0、1两种状态,这是我们用IO口可以实现的,我们认为高电平是1,低电平是0。也就是说我们用只到了IO口的输出功能(对于实现TX功能的IO口而言),那么到底发多长时间的高电平呢?这是由TIMx定时器决定的,TIMx定时器通过计数器实现。这个时间取决于什么呢?取决于波特率。也就是说,只要我们初始化设置好了TIMx定时器我们就不需要考虑时间问题了。为方便我们现发送低位,然后逐渐右移来发送高位。如何判断发送完一个字节呢?我们认为10位为一个字节。如何判断发送玩所有的字节呢?我们使用一个缓存区(也就是数组)缓存区的字节发送完了我们就认为发送完了。


IO口配置


串口通信需要发送(TX)和接收(RX)两根通信线(半双工模式)。因此,我们需要将两个IO端口分别配置成TX和RX。


1.IO_TX发送配置:IO基本模式配置(具体请看前几期IO口配置博客),开启外部中断,触发模式设置为下降沿触发;

代码如下:


#define VM_UART_TXD_PORT_OUT       PC_DDR|=1<<5 ;PC_CR1|=(1<<5 );PC_CR2 &=~(1<<5)//设定为输出

#define VM_UART_TXD_PORT_IN        PC_DDR&=~(1<<5 );PC_CR1|=(1<<5 );PC_CR2&=~(1<<5 )//设定为输入


2.IO_RX发送配置:IO基本模式配置(具体请看前几期IO口配置博客),开启外部中断,触发模式设置为上升沿和下降沿触发

代码如下:


#define VM_UART_RXD_PORT_IN        PB_DDR&=~(1<<4);PB_CR1|=(1<<4);PB_CR2&=~(1<<4) //设置只上拉输入 不中断

#define VM_UART_RXD_PORT_INT_IN    PB_DDR&=~(1<<4);PB_CR1|=(1<<4);PB_CR2|=(1<<4) //设置只上拉输入 中断


3.初始化IO口,并配置中断及编写中断服务函数

代码如下:


/**************************************************************************

全局变量

**************************************************************************/

u8 vm_UART_RX_P;//接收缓存指针

u8 vm_UART_RX_BUF[vm_UART_RX_BUF_L];//接收缓存


u8 vm_UART_RX_byte;//扩展串口 字节缓冲区

u8 vm_UART_RX_bit;//扩展串口 计算位数


u8 vm_UART_TX_byte;//扩展串口 字节缓冲区

u8 vm_UART_TX_bit;//扩展串口 计算位数


u8 vm_uart_tx_flag;//正在发送标志

u8 vm_uart_rx_flag;//正在接收标志

/**************************************************************************/

void IO_EXTI_init(void)

{

    VM_UART_TXD_PORT_IN;//发送初始化

   //外部中断

    EXTI_CR1 &=~(3<<4);//清零

    EXTI_CR1 |=2<<4;//下降沿触发

    VM_UART_RXD_PORT_INT_IN;//接收初始化

   //外部中断

    EXTI_CR1 &=~(3<<2);//清零

    EXTI_CR1 |=3<<2;//上升沿和下降沿触发

}

//中断服务函数

#pragma vector=7

__interrupt void EXTI_PORTC_IRQHandler(void)

{

  //外中断一次收一个字节,只识别起始位

     if((PB_IDR &0x10) == 0)

    {

        TIM1_START;//启动定时器

        vm_UART_RX_byte = 0;

        vm_UART_RX_bit = 0;

        VM_UART_RXD_PORT_IN; //只上拉输入 不中断

        vm_uart_rx_flag = 1;//开启发送

    }

}


波特率配置


由实验原理我们可以知道波特率可以通过配置定时器来设置,具体计算公式如下所示:


stm8波特率计算:主时钟频率/分频系数/波特率=装载值

例:波特率9600 主时钟频率16MHz 分频系数1

初值=16x10^6/9600=1667


根据公式,我们可以求得初值,然后配置定时器自动装载值,分频系数,计数器模式,开启中断!开启中断!开启中断!重要事情说三遍!


发送


定时器配置(此处初始化TIM2定时器实现发送功能),中断服务函数及串口发送函数

代码部分:


//1.定时器2初始化

void TIM2_Configuration(u16 time, u8 en)

{

    TIM2_CR1|=1<<7;////允许自动装载值

    TIM2_PSCR = 0;//预分频系数


    TIM2_ARRH = (uint8_t)(time >> 8);

    TIM2_ARRL = (uint8_t)(time & 0xff); //自动装载值


    TIM2_IER = 1;//中断使能

    if(en != 0)

    {

        TIM2_CR1 |= 1<<0;//使能计数器

    }

//2.定时器2中断服务函数

#pragma vector=0xF

__interrupt void TIM2_UPD_OVF_BRK_IRQHandler(void)

{

     TIM2_SR1&=~(1<<0);//清空标志位 


    if(vm_UART_TX_bit < 8)//判断数据位数

 

    {

        if((vm_UART_TX_byte & 0x1) == 0x1)

        {

            VM_UART_TXD_PORT_WriteHigh;

        }

        else

        {

            VM_UART_TXD_PORT_WriteLow;

        }

        vm_UART_TX_byte /= 2;

    }

    else

    {

        VM_UART_TXD_PORT_WriteHigh;

        if(vm_UART_TX_bit > 8)//判断数据位数

        {

            vm_uart_tx_flag = 0; //设置为发送完毕

            VM_UART_TXD_PORT_IN;

            TIM2_STOP;//

        }

    }

    vm_UART_TX_bit++;

}

//3.模拟串口发送函数,此处编写了三个发送函数,根据自己需求调用

void vm_UARTsend_byte(u8 byte)

{

    while(vm_uart_tx_flag == 1); //检查是否发送完毕


    vm_uart_tx_flag = 1;

    VM_UART_TXD_PORT_OUT;

    VM_UART_TXD_PORT_WriteLow;

    vm_UART_TX_bit = 0;

    vm_UART_TX_byte = byte;

    TIM2_START; //计数器置零,启动定时器

}

void vm_UART_SendString(u8 *Data, u16 len)

{

    u16 i;

    for(i=0; i < len; i++)

        vm_UARTsend_byte(Data[i]);


}

void vm_UART_SendStr(u8 *str)

{

    u16 i = 0;

    while((*(str+i)) != 0)

    {

        vm_UARTsend_byte(*(str+i));

        i++;

    }

}


接收


配置定时器1(TIM1),中断服务函数(包含接收部分)

代码如下:


//定时器1初始化

//通过设计算定时器初值来设置波特率

 /*stm8波特率计算:主时钟频率/分频系数/波特率=装载值*/

void TIM1_Configuration(u16 time, u8 en)

{

    TIM1_CR1 |= 1<<7;//允许自动装载值

    /*设置自动装载值*/

    TIM1_ARRH = (uint8_t)(time >> 8);//取高八位

    TIM1_ARRL = (uint8_t)(time)&0x00ff;//取低八位,高八位清零

  

    /* 设置分频系数*/

    TIM1_PSCRH = (uint8_t)(0x0000 >> 8);

    TIM1_PSCRL = (uint8_t)(0x0000)&0x00ff;

    /* 选择计数器模式 */

    TIM1_CR1&= ~(1<<4);//向上计数

    TIM1_CR1&=~(1<<3);//循环计数

    TIM1_CR1&=~(1<<2);//更新请求源

    TIM1_CR1 = 0;//清零

    /* 设置重复计数模式 */

    TIM1_RCR = 0x00;

    /* 设置允许中断 */

    TIM1_IER = 1;//中断使能

    if(en != 0)

    {

        TIM1_CR1 |= 1<<0;//使能计数器

    }

}

//2.定时器1中断服务函数

#pragma vector=0xD

__interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void)

{

    TIM1_SR1&=~(1<<0);//清空标志位 

    if((PB_IDR &0x10) == 0)

    {

        vm_UART_RX_byte /= 2;

    }

    else

    {

        vm_UART_RX_byte /= 2;

        vm_UART_RX_byte |= 0X80;

    }

    vm_UART_RX_bit++;

    if(vm_UART_RX_bit >= 8)

    {

        (uint8_t)PC_ODR_ODR4>0?0:1;//高取反,低取高,必须配置为输出

        TIM1_STOP;//停止定时器

        VM_UART_RXD_PORT_INT_IN;

        vm_uart_rx_flag = 0;

        vm_UART_RX_bit = 0;//

        vm_UART_RX_BUF[vm_UART_RX_P] = vm_UART_RX_byte; //一个字节时接收完毕

        vm_UART_RX_P++;

        if(vm_UART_RX_P >= vm_UART_RX_BUF_L)vm_UART_RX_P = 0; //接收指针

    }

}


主函数部分


1.时钟及所有外设初始化

2.开启全局中断

3自行添加代码部分


总结


本教程详细讲解了STM8单片机IO口模拟串口通线,实习那真正串口,大大降低了成本。以下是串口发送的效果图。

写得不好,多多指教。

工程代码:https://download.csdn.net/download/weixin_43184208/11120674




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

热门文章 更多
关于LED大屏幕保养你搞知道这些