【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收

目录

一 项目背景

二 原理说明

三 设计实现--GPIO部分

四 设计实现--定时器部分

五 总结


一 项目背景

        项目需要使用485串口编码器,编码器的数据以波特率9600持续向外发送。接收端计划使用485转换芯片+MCU串口。但是片上的外设资源已经被占用了,没有多余的串口外设。所以这边只能考虑用GPIO中断+定时器中断模拟UART串口的接收。

二 原理说明

【1】UART原理

        UART 为全双工通信,通常需要三条线: TX(发送)、 RX(接收)和 GND(地线)。数据串行一位一位的传送。

发送数据:空闲状态TX处于高电平,将TX拉为低电平,宽度为1bit时间,接着数据按低位到高位依次发送,数据发送完毕后,如果有校验位,则发送奇偶校验位,最后发送停止位,这样一帧数据就发送完成了。(若发送多字节数据时,就连续发送多帧数据即可。)

接收数据:RX线路处于高电平(空闲态)时,当RX检测到下降沿(高电平变为低电平)时,说明线路有数据要传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,如果有校验位,则接收奇偶校验位,最后接收停止位。

        UART的数据格式如下,其构成为空闲位+起始位+数据位+停止位

【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收_第1张图片

        空闲状态为高电平,当接收到第一个起始位时,变为低电平,持续时间为1/9600=104us,接着发送8位数据位,发送结束之后又转为高电平,由停止位过渡回空闲位。

        如下所示为实际逻辑分析仪接收到的数据,结合上面的分析即可明晰:

 

【2】模拟UART串口原理

        模拟UART串口既是对上述的这一串通信码进行解析。

        首先将数据接到MCU的GPIO口,GPIO口设置为下降沿触发中断,当接收到第一个下降沿时证明其进入了起始位,此时打开定时器中断,定时104us(1s/9600)进入中断,每次接收的数据就是一位。进入中断9次之后,说明一帧数据接收完成,进入停止位,此时关闭定时器中断,等待下一帧数据的到来。


三 设计实现--GPIO部分

【1】GPIO初始化:

void initEncoder485(void)
{
	scu_pinmux(0x0A ,2 , (MD_PLN|MD_EZI|MD_ZI), FUNC0);
    GPIO_SetDir(4, 9, 0);  //P4_9作为接收口,设置方向
    NVIC_SetPriority(PIN_INT0_IRQn, ((0x01<<3)|0x01));
    GPIO_IntCmd(0,4,9,1);  //下降沿触发中断
    NVIC_EnableIRQ(PIN_INT0_IRQn);  //使能GPIO中断
}

【2】GPIO中断:

uint8_t ecd485_rcv_stat = 9;  //接收状态初始化为停止位
//一帧数据:0--1--2--3--4--5--6--7--8--9--0

void GPIO0_IRQHandler(void)
{
	if(GPIO_GetIntStatus(0))
	{
		GPIO_ClearInt(1, 0);  //清中断标志
        if((ecd485_rcv_stat == 9) && (GPIO_PinRead(0x4, 9) == 0))  //若当前为停止位且收到数据0的下降沿
        {
            ecd485_rcv_stat = 0;  //状态设置为开始位
            TIM_Cmd(LPC_TIMER0,ENABLE);  //开启定时器
        }
	}
}

【3】附几个GPIO中断有关的库函数:

/*********************************************************************//**
 * @brief		Enable GPIO interrupt
 * @param[in]	pinInt		Pin interrupt number, should be: 0 to 7
 * @param[in]	portNum		Port number to read value, should be: 0 to 7
 * @param[in]	bitValue	Value that contains all bits on GPIO to enable,
 * 				should be in range from 0 to 0xFFFFFFFF.
 * @param[in]	intMode		interrupt mode, should be:
 * 					- 0: Rising edge interrupt mode
 * 					- 1: Falling edge interrupt mode
 * 					- 2: Active-High interrupt mode
 * 					- 3: Active-Low interrupt mode
 * @return		None
 **********************************************************************/
void GPIO_IntCmd(uint8_t pinInt, uint8_t portNum, uint32_t bitValue, uint8_t intMode)
{
	uint8_t  value;
	uint32_t pinSel;

	value = ((portNum&0x7)<<5)|(bitValue&0x1F);
	if (pinInt < 4) { //Using PINTSEL0
			pinSel = LPC_SCU->PINTSEL0;
			pinSel &= ~(0xFF<<(pinInt*8));
			pinSel |= (value<<(pinInt*8));
			LPC_SCU->PINTSEL0 = pinSel;
	} else { //Using PINTSEL1
		pinSel = LPC_SCU->PINTSEL1;
		pinSel &= ~(0xFF<<((pinInt-4)*8));
		pinSel |= (value<<((pinInt-4)*8));
		LPC_SCU->PINTSEL1 = pinSel;
	}
	switch(intMode)
	{
		case 0://rising edge
			LPC_GPIO_PIN_INT->ISEL &= ~(1<IENR |= (1<ISEL &= ~(1<IENF |= (1<ISEL |= (1<IENR |= (1<SIENF |= (1<ISEL |= (1<IENR |= (1<CIENF |= (1<IST)>>pintNum)& 0x1);
}


/*********************************************************************//**
 * @brief		Clear GPIO interrupt status
 * @param[in]	intMode Interrupt mode, should be:
 * 					- 0: Rising edge interrupt mode
 * 					- 1: Falling edge interrupt mode
 * 					- 2: Active-High interrupt mode
 * 					- 3: Active-Low interrupt mode
 * @param[in]	pintNum Pin interrupt number, should be: 0 to 7
 * @return		None
 **********************************************************************/
void GPIO_ClearInt(uint8_t intMode, uint32_t pintNum)
{
	if (!(intMode&(1<<1)))
			LPC_GPIO_PIN_INT->IST = (1<


四 设计实现--定时器部分

【1】定时器初始化:

void initTimer0(void)
{
    //Initialize timer 0, prescale count time of 2uS
    TIM_ConfigStruct.PrescaleOption = TIM_PRESCALE_USVAL;
    TIM_ConfigStruct.PrescaleValue	= 53;//国产485编码器(波特率9600,104us/2+1)

    //use channel 0, MR0
    TIM_MatchConfigStruct.MatchChannel = 0;
    //Enable interrupt when MR0 matches the value in TC register
    TIM_MatchConfigStruct.IntOnMatch   = TRUE;
    //Enable reset on MR0: TIMER will reset if MR0 matches it
    TIM_MatchConfigStruct.ResetOnMatch = TRUE;
    //Stop on MR0 if MR0 matches it
    TIM_MatchConfigStruct.StopOnMatch  = FALSE;
    //Toggle MR0.0 pin if MR0 matches it
    TIM_MatchConfigStruct.ExtMatchOutputType =TIM_EXTMATCH_TOGGLE;
    //Set Match value, count value of 1 (1 * 2uS = 2us = 0.000002s --> 500 kHz)
    TIM_MatchConfigStruct.MatchValue   = 1;

    //Set configuration for Tim_config and Tim_MatchConfig
    TIM_Init(LPC_TIMER0, TIM_TIMER_MODE,&TIM_ConfigStruct);
    TIM_ConfigMatch(LPC_TIMER0,&TIM_MatchConfigStruct);

    //preemption = 1, sub-priority = 1
    NVIC_SetPriority(TIMER0_IRQn, ((0x03<<3)|0x01));
    //Enable interrupt for timer 0
    NVIC_EnableIRQ(TIMER0_IRQn);
}

【2】定时器中断(注意:这边加入了通讯码数据头的校验(0x01 0x03 0x04),不需要的可以在接收到缓冲数组之后返回):

#define ENCODER_LEN      9         //国产编码器通讯码长
uint8_t encoder_buf[ENCODER_LEN];
uint8_t timer_read_data = 0;

void TIMER0_IRQHandler(void)
{
    static uint8_t cnt = 0;
    if (TIM_GetIntStatus(LPC_TIMER0, TIM_MR0_INT) == SET) 
	{
        TIM_ClearIntPending(LPC_TIMER0, TIM_MR0_INT);
        
        ecd485_rcv_stat ++;
    }
    
    if(ecd485_rcv_stat == 9)
    {
        TIM_Cmd(LPC_TIMER0,DISABLE);//关闭定时器计数
        encoder_buf[cnt] = timer_read_data;
        if(cnt == 0)  //数据头校验,数据头为0x01 0x03 0x04
        {
            cnt ++;
            if(encoder_buf[0] != 0x01)
                cnt = 0;
        }
        else if(cnt == 1)
        {
            cnt ++;
            if(encoder_buf[1] != 0x03)
                cnt = 0;
        }
        else if(cnt == 2)
        {
            cnt ++;
            if(encoder_buf[2] != 0x04)
                cnt = 0;
        }
        else
        {
            cnt ++;
            if(cnt > 8)
                cnt = 0;
        }
        return ;
    }
    if(GPIO_PinRead(0x4, 9))  //如果GPIO P4.9口检测到高电平,数据移位
        timer_read_data |= (1 << (ecd485_rcv_stat - 1));
    else
        timer_read_data &= ~(1 << (ecd485_rcv_stat - 1));
}


五 总结

        综上,便可以通过GPIO中断+定时器中断配合模拟UART串口进行接收了,debug可以看到接收到的数组:

【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收_第2张图片

         本文只列举了模拟串口的接收过程,发送的过程是逆过程,原理上也差不多。

你可能感兴趣的:(嵌入式,单片机,嵌入式硬件)