在項目中需要用到多路的串口使用,而自己的單片機目前來講沒法滿足我們項目所需要的串口需求,因此要對普通的GPIO進行轉換為UART進行使用。從而使得我們單片機能夠得到多一路的串口。
既然我們要進行對串口的模擬,因此我們要先了解uart相關的通信協(xié)議:帶你快速對比SPI、UART、I2C通信的區(qū)別與應用!,這篇文章進行了詳細介紹,此處不再贅述。
UART的通信方式是由1個起始位,8個數據位,包含一個奇偶校驗位,和結束位構成。因此我們將使用單片機中的兩個普通的IO口電平的高低進行對相應時序的模擬。
接下來,讓我們一起學習如何實現(xiàn)IO模擬串口通信。
添加頭文件
首先我們先添加相應的頭文件。
#include "stm32f10x.h" #include "vuart2.h"
宏定義
使用到的io口為:
#define OI2_TXD PDout(6) #define OI2_RXD PDin(7) #define BuadRate2_9600 104 #define Recive2_Byte 19 //接收緩沖器的個數 u8 len2 = 0; //接收計數 u8 USART2_buf[Recive2_Byte]; //接收緩沖區(qū)
將IO口相應的位帶操作函數進行宏定義從而使得在對不同的電平的進行轉換的時候更為方便,并且減少了調用其他函數的過程所消耗的時間,程序執(zhí)行效率更高。
在本次的傳輸過程中我選用的是使用波特率速率為9600bps,也就是1s中發(fā)送9600個數據位(bit),因此對每個位數據進行計算1000000us/9600可以得出,發(fā)一個bit的數據需要進行大概需要 104.16us,并且對于相應的電平持續(xù)時間要求誤差不能超過±5%,因此對我們進行時間的控制要求就顯得比較重要了。
枚舉出各個位
enum{
COM_START_BIT,
COM_D0_BIT,
COM_D1_BIT,
COM_D2_BIT,
COM_D3_BIT,
COM_D4_BIT,
COM_D5_BIT,
COM_D6_BIT,
COM_D7_BIT,
COM_STOP_BIT,
};
u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;
IO——TXD進行模擬
void IO2_TXD(u8 Data)
{
u8 i = 0;
OI2_TXD = 0;
delay_us(BuadRate2_9600);
for(i = 0; i < 8; i++)
{
if(Data&0x01)
OI2_TXD = 1;
else
OI2_TXD = 0;
delay_us(BuadRate2_9600);
Data = Data>>1;
}
OI2_TXD = 1;
delay_us(BuadRate2_9600);
}
由于發(fā)送的信號是將TXD信號進行拉低處理,因此在拉低TXD相應的IO口之后進行延時處理,再進行循環(huán)對我們需要發(fā)送的各個位的數據繼續(xù)進行發(fā)送循環(huán)發(fā)送完成之后將電平拉高代表停止位。
構建發(fā)送函數
void USART2_Send(u8 *buf, u8 len2)
{
u8 t;
for(t = 0; t < len2; t++)
{
IO2_TXD(buf[t]);
}
}
其中的*buf為需要發(fā)送的數據,len2為數據長度,進行循環(huán)調用IO_TXD進行一個字節(jié)一個字節(jié)的數據發(fā)送。
IO口初始化
void IO2Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//初始化gpio
NVIC_InitTypeDef NVIC_InitStructure;//中斷初始化函數
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE); //使能PD,PC端口時鐘
//SoftWare Serial TXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //選擇io口6
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度為50MHz
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_6); //TXD默認電平拉高
//SoftWare Serial RXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7); //對D7的下降沿進行中斷采樣,當接收到下降沿時代表接收到數據觸發(fā)中斷處理函數
EXTI_InitStruct.EXTI_Line = EXTI_Line7;//用到了中斷7
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿觸發(fā)中斷
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStruct);//初始化中斷
NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; //中斷發(fā)生于9-5的中斷之中
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
定時器初始化
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //時鐘使能
//定時器TIM5初始化
TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根據指定的參數初始化TIMx的時間基數單位
TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中斷,允許更新中斷
//中斷優(yōu)先級NVIC設置
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占優(yōu)先級1級
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //從優(yōu)先級1級
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
}
對TIM5進行初始化操作使得定時器可以檢測到各個位的電平持續(xù)性時間從而對接收到的數據進行分析。計時結束后進入中斷TIM5處理。
外部中斷處理函數
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)//對中斷標志位進行采集
{
if(OI2_RXD == 0)
{
if(recvStat2 == COM_STOP_BIT)
{
recvStat2 = COM_START_BIT;//將當前的狀態(tài)設置為開始位
TIM_Cmd(TIM5, ENABLE);//開啟定時器計數
}
}
EXTI_ClearITPendingBit(EXTI_Line7); //清除中斷標志
}
}
定時器中斷處理函數
void TIM5_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
{
TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update); //清除中斷標志位
recvStat2++; //將位置移動到第一位的數據
if(recvStat2 == COM_STOP_BIT)//當運行到停止位時進入
{
TIM_Cmd(TIM5, DISABLE);//停止tim5
USART2_buf[len2++] = recvData2;//將采集到的各個數據傳遞給USART2_buf
if(len2 > Recive2_Byte-1)//將數據通過回顯到串口調試助手中
{
len2 = 0;
USART2_Send(USART2_buf,Recive2_Byte);
}
return;
}
if(OI2_RXD)//采集RXD各個電平
{
recvData2 |= (1 << (recvStat2 - 1));
}else{
recvData2 &= ~(1 << (recvStat2 - 1));
}
}
}
整體代碼
vuart2.c:
#include "stm32f10x.h"
#include "vuart2.h"
/**
*軟件串口的實現(xiàn)(IO模擬串口)
* 波特率:9600 1-8-N
* TXD : PD6
* RXD : PD7
* 使用外部中斷對RXD的下降沿進行觸發(fā),使用定時器5按照9600波特率進行定時數據接收。
* Demo功能: 接收11個數據,然后把接收到的數據發(fā)送出去
*/
#define OI2_TXD PDout(6)
#define OI2_RXD PDin(7)
#define BuadRate2_9600 104
#define Recive2_Byte 19 //接收緩沖器的個數
u8 len2 = 0; //接收計數
u8 USART2_buf[Recive2_Byte]; //接收緩沖區(qū)
enum{
COM_START_BIT,
COM_D0_BIT,
COM_D1_BIT,
COM_D2_BIT,
COM_D3_BIT,
COM_D4_BIT,
COM_D5_BIT,
COM_D6_BIT,
COM_D7_BIT,
COM_STOP_BIT,
};
u8 recvStat2 = COM_STOP_BIT;
u8 recvData2 = 0;
void IO2_TXD(u8 Data)
{
u8 i = 0;
OI2_TXD = 0;
delay_us(BuadRate2_9600);
for(i = 0; i < 8; i++)
{
if(Data&0x01)
OI2_TXD = 1;
else
OI2_TXD = 0;
delay_us(BuadRate2_9600);
Data = Data>>1;
}
OI2_TXD = 1;
delay_us(BuadRate2_9600);
}
void USART2_Send(u8 *buf, u8 len2)
{
u8 t;
for(t = 0; t < len2; t++)
{
IO2_TXD(buf[t]);
}
}
void IO2Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PC端口時鐘
//SoftWare Serial TXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度為50MHz
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_6);
//SoftWare Serial RXD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);
EXTI_InitStruct.EXTI_Line = EXTI_Line7;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿觸發(fā)中斷
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //時鐘使能
//定時器TIM5初始化
TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根據指定的參數初始化TIMx的時間基數單位
TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中斷,允許更新中斷
//中斷優(yōu)先級NVIC設置
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM4中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占優(yōu)先級1級
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //從優(yōu)先級1級
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
}
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)
{
if(OI2_RXD == 0)
{
if(recvStat2 == COM_STOP_BIT)
{
recvStat2 = COM_START_BIT;
TIM_Cmd(TIM5, ENABLE);
}
}
EXTI_ClearITPendingBit(EXTI_Line7);
}
}
void TIM5_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
{
TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
recvStat2++;
if(recvStat2 == COM_STOP_BIT)
{
TIM_Cmd(TIM5, DISABLE);
USART2_buf[len2++] = recvData2;
if(len2 > Recive2_Byte-1)
{
len2 = 0;
USART2_Send(USART2_buf,Recive2_Byte);
}
return;
}
if(OI2_RXD)
{
recvData2 |= (1 << (recvStat2 - 1));
}else{
recvData2 &= ~(1 << (recvStat2 - 1));
}
}
}
vuart2.h:
#ifndef __VUART2__H #define __VUART2__H #include "stm32f10x.h" void IO2_TXD(u8 Data); void USART2_Send(u8 *buf, u8 len); void IO2Config(void); void TIM5_Int_Init(u16 arr,u16 psc); #endif
審核編輯:劉清
-
單片機
+關注
關注
6072文章
45271瀏覽量
661565 -
串口通信
+關注
關注
34文章
1655瀏覽量
57505 -
GPIO
+關注
關注
16文章
1308瀏覽量
55520 -
UART接口
+關注
關注
0文章
124瀏覽量
16261
原文標題:在STM32中如何通過IO口模擬串口通信
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
IO模擬UART實現(xiàn)
串口通信的原理,IO口模擬UART串口通信
【妙招】一份 " IO口模擬串口 " 獨門秘籍
關于STM32 利用IO口模擬串口實現(xiàn)數據通信
STM8S103系列IO口模擬串口通信(實現(xiàn)真正串口)
單片機IO口模擬UART串口通信

如何實現(xiàn)IO模擬串口通信?
評論