使用定时器模拟串口UART的实现

最近在调的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主动离线)。

好了就记录这么多。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个嵌入式系统开发的问题。在嵌入式系统中,常常需要使用串口通信来实现与外部设备的通信。其中,UART是一种常用的串口通信协议,它可以通过IO口模拟实现。下面是一个基本的IO模拟UART发送的流程: 1. 配置IO口的输入输出方向,将IO口配置为输出模式。 2. 配置定时器以一定的时间间隔触发中断,用于控制发送的时间间隔。 3. 在定时器中断服务程序中,将需要发送的数据逐位写入IO口,并控制发送时间间隔。 下面是一个简单的示例代码,实现了通过IO口模拟UART发送字符串的功能: ```c #include <reg52.h> #define FREQ_OSC 12000000UL #define BAUD_RATE 9600UL // 波特率 #define TIMER1_VALUE (65536UL - (FREQ_OSC / 12 / BAUD_RATE)) // 定时器初值 void uart_send_char(unsigned char c) { unsigned int i; unsigned char mask; // 发送起始位 P1 &= ~0x01; for (i = 0; i < 10; i++); // 逐位发送数据位 for (mask = 0x01; mask != 0x00; mask <<= 1) { if (c & mask) { P1 |= 0x02; } else { P1 &= ~0x02; } for (i = 0; i < 10; i++); } // 发送停止位 P1 |= 0x01; for (i = 0; i < 10; i++); } void uart_send_string(char *str) { while (*str != '\0') { uart_send_char(*str); str++; } } void timer1_isr() interrupt 3 { static unsigned int count = 0; static unsigned char index = 0; static char *str = "Hello, world!\r\n"; count++; if (count >= 1000) { // 发送间隔为 1s count = 0; uart_send_char(str[index]); index++; if (str[index] == '\0') { index = 0; } } } void main() { TMOD = 0x10; // 设置定时器1为模式1 TH1 = TIMER1_VALUE >> 8; TL1 = TIMER1_VALUE & 0xFF; TR1 = 1; // 启动定时器1 ET1 = 1; // 允许定时器1中断 EA = 1; // 允许总中断 while (1); } ``` 在这个示例代码中,使用定时器1中断来控制发送的时间间隔,每隔1秒钟发送一个字符。其中,`uart_send_char()`函数用于发送单个字符,`uart_send_string()`函数用于发送字符串。在`timer1_isr()`函数中,每隔1秒钟发送字符串中的下一个字符。需要注意的是,在实际应用中,需要根据具体的需求进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值