STM8S103系列IO口模擬串口通信(實現真正串口)

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

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章