目录
一 项目背景
二 原理说明
三 设计实现--GPIO部分
四 设计实现--定时器部分
五 总结
一 项目背景
项目需要使用485串口编码器,编码器的数据以波特率9600持续向外发送。接收端计划使用485转换芯片+MCU串口。但是片上的外设资源已经被占用了,没有多余的串口外设。所以这边只能考虑用GPIO中断+定时器中断模拟UART串口的接收。
二 原理说明
【1】UART原理:
UART 为全双工通信,通常需要三条线: TX(发送)、 RX(接收)和 GND(地线)。数据串行一位一位的传送。
发送数据:空闲状态TX处于高电平,将TX拉为低电平,宽度为1bit时间,接着数据按低位到高位依次发送,数据发送完毕后,如果有校验位,则发送奇偶校验位,最后发送停止位,这样一帧数据就发送完成了。(若发送多字节数据时,就连续发送多帧数据即可。)
接收数据:RX线路处于高电平(空闲态)时,当RX检测到下降沿(高电平变为低电平)时,说明线路有数据要传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,如果有校验位,则接收奇偶校验位,最后接收停止位。
UART的数据格式如下,其构成为空闲位+起始位+数据位+停止位:
空闲状态为高电平,当接收到第一个起始位时,变为低电平,持续时间为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<<pinInt);
LPC_GPIO_PIN_INT->IENR |= (1<<pinInt);
break;
case 1://falling edge
LPC_GPIO_PIN_INT->ISEL &= ~(1<<pinInt);
LPC_GPIO_PIN_INT->IENF |= (1<<pinInt);
break;
case 2://active High level
LPC_GPIO_PIN_INT->ISEL |= (1<<pinInt);
LPC_GPIO_PIN_INT->IENR |= (1<<pinInt);
LPC_GPIO_PIN_INT->SIENF |= (1<<pinInt);
break;
case 3://active Low level
LPC_GPIO_PIN_INT->ISEL |= (1<<pinInt);
LPC_GPIO_PIN_INT->IENR |= (1<<pinInt);
LPC_GPIO_PIN_INT->CIENF |= (1<<pinInt);
break;
default:
break;
}
}
/*********************************************************************//**
* @brief Get GPIO Interrupt Status
* @param[in] pintNum Pin interrupt number, should be: 0 to 7
* @return Function status, could be:
* - ENABLE :Interrupt has been generated
* - DISABLE :Interrupt has not been detected
**********************************************************************/
FunctionalState GPIO_GetIntStatus(uint32_t pintNum)
{
return (((LPC_GPIO_PIN_INT->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<<pintNum);
}
四 设计实现--定时器部分
【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可以看到接收到的数组:
本文只列举了模拟串口的接收过程,发送的过程是逆过程,原理上也差不多。