模拟串口UART的实现

我所祷告的,就是要你们的爱心,在知识和见识上,多而又多,使你们能分辨是非,做诚实无过的人,直到基督的日子。
——腓立比书【1:9~10】

最近在调的MCU的型号为STM32F030,配置芯片相较之前的MCU都比较简单,功能配置很顺利。但是在写串口程序的时候,发现串口一直不通,使用示波器也没有波形。因为基本的串口通讯线只有Tx和Rx两根线,配置也相对简单,8位数位,1位停止位,9600波特率。协议结构为 起始位(低电平)+8位数据(低位在前)+1位停止位(高电平),例如发送字节0x55,即电平为低 高低高低高低高低 高。电平转换的间隔时间为1s/9600 = 104us

以上均为理论分析过程,检查代码对串口的配置都没有发现错误。最终排查的结果是硬件工程师画原理图和PCB图时将串口的Tx和Rx画反了!由于某些原因板子已经量产了,故只能通过改软件来实现串口的功能,在网上找了一下发现模拟串口可行性可以,故动手写了一下模拟串口。

串口通讯需要模拟两根线(Tx和Rx)的时序,模拟串口的主要思路如下:

发送部分比较简单,按照 起始位(低电平)+8位数据(低位在前)+1位停止位(高电平),间隔时间104us,即可。

接收部分有点复杂,需要配置一个外部中断,用于检测低电平信号,还需要一个定时器,用于读取有效数据。

下面将代码附上:

发送IO口初始化

/*!
* @brief 模拟串口1 TX IO口配置
* @param none
* @return none
* @note Tx(PA10)
*/
void MUSART1_TX_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_10);
}

接收IO口初始化

/*!
* @brief 模拟串口1 RX IO口配置
* @param none
* @return none
* @note Rx(PA9)
*/
void MUSART1_RX_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);    //!<外部中断时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource9);

EXTI_InitStructure.EXTI_Line=EXTI_Line9;
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿中断
    EXTI_InitStructure.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel=EXTI4_15_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority=0x01;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
定时器初始化

/*!
* @brief 定时器14初始化
* @param
* @return NONE
* @note 103us定时器,用于串口数据采样
*/
void Time14Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimerBaseStruct;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);   //!<时钟使能
    TIM_DeInit(TIM14);     //!<Time1定时器重设缺省值
    TIM_TimerBaseStruct.TIM_Period=103;          //!<设置重载寄存器初值 (设置为103,即:定时104us)
    TIM_TimerBaseStruct.TIM_Prescaler=7;      //!<使用内部8M时钟,分频8(7+1),8M/8 = 1000000,故数1000000(999999+1)下,达1秒
    TIM_TimerBaseStruct.TIM_ClockDivision=0;     //!<不分频
    TIM_TimerBaseStruct.TIM_CounterMode=TIM_CounterMode_Up; //!<设置计数器向上计数模式
    TIM_TimeBaseInit(TIM14,&TIM_TimerBaseStruct);

NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=TIM14_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    TIM_ClearITPendingBit(TIM14, TIM_FLAG_Update);
    TIM_ITConfig(TIM14,TIM_IT_Update,ENABLE);     //!<使能TIM1中断源
    TIM_Cmd(TIM14,DISABLE);     //!<禁能TIM1定时器

}
发送数据函数

uint32 delayTime =99; //!<9600,理论值为104但实际测下来99时效果最好
/*!
* @brief 模拟串口1发送一个字节
* @param
* @return none
* @note 数据低位在前高位在后
*/
void MUSART1_SendData(uint8 data)
{
uint8 i = 0;
TX_L(); //!<起始位
delay_us(delayTime);
for(i = 0; i < 8; i++){
if(data & 0x01)
TX_H();
else
TX_L();
delay_us(delayTime);
data >>= 1;
}
TX_H(); //!<停止位
delay_us(delayTime);
}
接收数据,外部中断起始接收

/*!
* @brief 串口接收IO中断处理函数
* @param none
* @return NONE
* @note none
*/
void EXTI4_15_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line9) != RESET){
if(RX_READ() == 0x00){
if(rx_state >= STATE_STOP){
recvData = 0;
rx_state = STATE_START;
delay_us(50);
TIM_Cmd(TIM14, ENABLE); //!<打开定时器,接收数据
}
}
EXTI_ClearITPendingBit(EXTI_Line9);
}
}
接收数据,定时器中断接收数据

/*!
* @brief 定时器1中断处理函数
* @param
* @return NONE
* @note
*/
void TIM14_IRQHandler(void)
{
if(TIM_GetITStatus(TIM14, TIM_IT_Update) != RESET){
rx_state++;                         //!<改变状态机
if(rx_state == STATE_STOP){
TIM_Cmd(TIM14, DISABLE);    //!<关闭定时器
usart_getByte();            //!<接收到停止位之后,处理数据recvData
return;                     //!<返回
}
if(RX_READ()){
recvData |= (1 << (rx_state - 1));
}else{
recvData &= ~(1 <<(rx_state - 1));
}
TIM_ClearITPendingBit(TIM14, TIM_FLAG_Update);
}
}
其他说明
typedef enum{
STATE_START=0,
STATE_BIT0,
STATE_BIT1,
STATE_BIT2,
STATE_BIT3,
STATE_BIT4,
STATE_BIT5,
STATE_BIT6,
STATE_BIT7,
STATE_STOP
}RX_STATE;

RX_STATE rx_state = STATE_STOP;
uint8 recvData=0;    //!<接收的一个字节数据,全局变量
至此,模拟串口的代码及原理均已描述完成。单独的串口通讯并没有问题,但是在实际应用中采取了一种特殊的“总线”形式。

本次写的是从机部分的代码,从机接收数据并没有问题,但是在发送数据时,由于所有的从机Tx都挂载在同一根Tx上,并且从机Tx空闲状态时一直是高电平,导致指定从机的起始信号发不出去。故需要再做以下处理,解决以上问题。

当接收到的数据包中的ID为本从机ID时将Tx拉高,否则拉低,这样能够保证当指定ID的从机发送数据时有且只有一个从机再总线上发送数据(其他从机的Tx主动离线)。

好了就记录这么多。

原文地址:https://www.cnblogs.com/jack-hzm/p/10158882.html

时间: 2024-08-29 01:40:00

模拟串口UART的实现的相关文章

模拟串口输出

最近在调试hardfault handler输出日志信息时候获得了Vincent帮助,感谢 1 void Delay_UART5(uint16_t cnt) 2 { 3 for(;cnt;cnt--); 4 } 1 //void HardFault_Handler(void) //uart5 38400 2 //{ 3 // 4 // uint32_t addrbegin=__get_MSP() ; //???·?· 5 uint32_t addr: 6 // uint8_t i, j, str

STM32之模拟串口设计

一.设计用途: 公司PCB制成板降成本,选择的MCU比项目需求少一个串口,为满足制成板成本和项目对串口需求,选择模拟一路串口. 二.硬件电路: 三.设计实现: 工具&软件:STM32F030R8    KEIL5    STM32CubeMX 1. 串口通信 串口是一种很常用的通信接口,按位(bit)发送和接收字节,串口通信是异步传输,端口能够在一根线上发送数据同时在另一根线上接收数据.串口通信最重要的参数是比特率.数据位.停止位和奇偶校验.对于两个进行通信的端口,这些参数必须匹配,在我们单片机

用IO模拟串口协议发送数据

<pre name="code" class="cpp">//文件usend.h #ifndef _USEND_H_ #define _USEND_H_ //====红外接收相关定义============================= #define PuTx_High (P_uTx = 1) //数据高 #define PuTx_Low (P_uTx = 0) //数据低 #define V_SendDatNum 6//6 //发送数据字节数 /

51单片机GPIO口模拟串口通信

51单片机GPIO口模拟串口通信 标签: bytetimer终端存储 2011-08-03 11:06 6387人阅读 评论(2) 收藏 举报 本文章已收录于: 分类: 深入C语言(20) 作者同类文章X 1 #include "reg52.h" 2 #include "intrins.h" 3 #include "math.h" 4 #include "stdio.h" 5 sbit BT_SND =P1^5; 6 sbit

模拟串口--基于STM8普通IO口的模拟串口驱动程序

基于STM8普通IO口的模拟串口驱动程序 标准串口通讯数据的格式为:起始位(1) + 数据位(8) + 校验位(1) + 停止位(1) 串口通讯另外一个重要的的部分是设置波特率,波特率就是1秒钟内串口所传输的Bit(位)数. 关于采样频率:为了较小读取或者发送串行数据的误差,我们采取了在N(我用的是4次)次中断中,取固定位置的读取的数据. 我以stm8中9600波特率计算的过程为例:(1秒钟传输9600位) 可以计算出传输1位所需要的时间 T1 = 1/9600 约为104us 由此可知,发送一

stc15f104w模拟串口使用

stc15f104w单片机体积小,全8个引脚完全够一般的控制使用,最小系统也就是个电路滤波----加上一个47uf电容和一个103电容即可,但因为其是一个5V单片机,供电需要使用5V左右电源. 该款单片机视乎没有硬件串口,所以想要使用串口完成开发则应该使用软件模拟串口实现,其中P3.0为单片机RX,P3.1为单片机TX,下载时也适用.最好的学习质料莫过于官网,使用可以链接:http://www.stcmcudata.com/STC-LIB/STC15%E7%B3%BB%E5%88%97%E5%B

教你如何在51单片机上模拟串口通信!!!

我们可以不使用单片机本身带有的串口,而自己用程序去模拟一个串口并达到和本身的串口具有同样的功能, 首先,我们需要用到CH340串口模块,大家可以上某宝自行购买. 正面: 反面: 然后我们需要了解一下这串口模块上的引脚: 5V  :与VCC短路为5V TLL输出(电源和信号输出都是5V) VCC:可以与3.3V和5V用跳帽连接 3.3V:与VCC短路为3.3V TLL输出(电源和信号输出都是3.3V) TXD:发送数据端口(与单片机上的接收引脚用杜邦线连接) RXD:接收数据端口(与单片机上的发送

第011课_串口(UART)的使用

from: 第011课_串口(UART)的使用 第001节_辅线1_硬件知识_UART硬件介绍 1. 串口的硬件介绍 UART的全称是 Universal Asynchronous Receiver and Transmiter ,即异步发送和接收. 串口在嵌入式中用途非常广泛,主要的用途有: 1. 打印调试信息: 2. 外接各种模块:GPS.蓝牙等: 串口因为结构简单.稳定可靠,广受欢迎.通过三根线即可,发送.接收.地线. 通过TxD -> RxD 把ARM开发板要发送的信息发送给PC机.通过

stm32 普通IO口模拟串口通信

普通IO口模拟串口通信 串口通信协议 串口传输 默认 波特率9600 1起始位 1停止位 其他0 数据位是8位(注意图上的给错了). 传输时,从起始位开始,从一个数据的低位(LSB)开始发送,如图从左向右的顺序,对电平拉高或拉低,最后停止位时拉高. 波特率大小,改变延时时间即可.例如9600 波特率    根据公式 : 1/9600=0.000104s(大致) 也就是说每发送1bit延时104us (下面我用9600波特率来说,代码用的是19200) 串口发送       将电平拉低 延时104