群里交流,有人问用IO口怎么模拟UART,19年做过一个项目,其中就有这个功能,测试后,
效果还可以,做下记录吧,希望能够对大家有所帮助。
一、设计思路
由于我的项目需要模拟UART能够同时实现RX和TX,因此利用两个Timer和GPIO去实现:
Timer1中断+GPIO 实现TX;
Timer8中断+GPIO中断 实现RX。
采用模块化设计,把驱动层和上层应用分离,采用基于接口变成,把驱动层分离:
驱动需要提供接口:
typedef void timer_stop(void);
typedef void timer_start(uint8_t chPrescaler);
typedef void gpio_irq_stop(void);
typedef void gpio_irq_start(void);
#define SOFTWARE_UART_TX_X(__VALUE) LOG_TXD_X(__VALUE)
#define SOFTWARE_UART_RX_X() LOG_RXD_READ()
提供给上层的接口:
void software_uart_tx_init(software_uart_parameter_t* ptUartParameter,timer_stop* ptTimerStop,timer_start* ptTimerStart);
void software_uart_rx_init(software_uart_parameter_t* ptUartParameter,timer_stop* ptTimerStop,timer_start* ptTimerStart,gpio_irq_stop* ptGpioIrqStop,gpio_irq_start* ptGpioIrqStart);
bool software_uart_rx_is_busy(software_uart_parameter_t* ptUartParameter);
bool software_uart_tx_is_busy(software_uart_parameter_t* ptUartParameter);
void software_uart_tx(software_uart_parameter_t* ptUartParameter,uint8_t* chBuffer,uint16_t hwSize);
void software_uart_tx_irq(software_uart_parameter_t* ptUartParameter);
void software_uart_rx_gpio_irq(software_uart_parameter_t* ptUartParameter);
fsm_rt_t software_uart_rx_timer_irq(software_uart_parameter_t* ptUartParameter,uint8_t* pchData);
二、TX实现
UART格式:115200/8/1/N,
UART格式:开始位(低电平),数据位(8位,先发低位),校验位(没有),停止位(高电平)。
1》计算定时器定时时间:
定时器的频率为:f1
波特率为:baudrate
发送一个 bit 时间:1s/baudrate
定时器计一个数时间:1/f1
定时器计数=发送一个 bit 时间/定时器计一个数时间 - 1 = (f1/baudrate) - 1
注意:定时器计数范围不能超过其最大值。
2》发送原理
(1)首先用户查询现在UART是否发送忙,如过忙,则等待;不忙,则调用
software_uart_tx(software_uart_parameter_t* ptUartParameter,uint8_t* chBuffer,uint16_t hwSize);
把要发送的数据和长度给底层驱动;software_uart_tx同时启动定时器;
(2)在定时器中断里面处理发送逻辑:
a. 首先发送起始位;
b. 然后依次发送数据位(先发低bit);
c. 再发送停止位(没有校验位);
d. 判断是否发送完成,如果完成,则停止timer;如果没有发送完成则继续a b c流程。
switch(this.tSoftwarUartTxState){
case UART_TX_START:
this.tTxByte.chByte = this.pchuartTxBuffer[this.hwUartTxCnt];
this.hwUartTxCnt++;
this.chTxUartBitCnt=0;
this.tSoftwarUartTxState = UART_TX_TXING_START_BIT;
case UART_TX_TXING_START_BIT:
SOFTWARE_UART_TX_X(0);
this.tSoftwarUartTxState = UART_TX_TXING_BYTE;
break;
case UART_TX_TXING_BYTE:
switch(this.chTxUartBitCnt){
case 0:
SOFTWARE_UART_TX_X(this.tTxByte.tBit0);
this.chTxUartBitCnt++;
break;
case 1:
SOFTWARE_UART_TX_X(this.tTxByte.tBit1);
this.chTxUartBitCnt++;
break;
case 2:
SOFTWARE_UART_TX_X(this.tTxByte.tBit2);
this.chTxUartBitCnt++;
break;
case 3:
SOFTWARE_UART_TX_X(this.tTxByte.tBit3);
this.chTxUartBitCnt++;
break;
case 4:
SOFTWARE_UART_TX_X(this.tTxByte.tBit4);
this.chTxUartBitCnt++;
break;
case 5:
SOFTWARE_UART_TX_X(this.tTxByte.tBit5);
this.chTxUartBitCnt++;
break;
case 6:
SOFTWARE_UART_TX_X(this.tTxByte.tBit6);
this.chTxUartBitCnt++;
break;
case 7:
SOFTWARE_UART_TX_X(this.tTxByte.tBit7);
this.chTxUartBitCnt++;
break;
default:
SOFTWARE_UART_TX_X(1);
this.chTxUartBitCnt=0;
this.tSoftwarUartTxState = UART_TX_TXING_END_BIT;
}
break;
case UART_TX_TXING_END_BIT:
if(this.hwUartTxCnt < this.hwUartTxNum){
this.tSoftwarUartTxState = UART_TX_START;
}else{
this.bTxBusy = 0;
this.ptUartTxTimerStop();
}
break;
default:
SOFTWARE_UART_TX_X(1);
this.bTxBusy = 0;
this.ptUartTxTimerStop();
}
三、接收原理
接收相对复杂些,因为不知道是什么时候来数据,因此不能用查询模式,只能用接收模式。
(1)GPIO配置
由于UART的TX空闲态是高电平,起始位是低电平,因此设置为下降沿触发。
在中断里面处理,也很简单:停止GPIO中断;以计算出定时时间的一半启动定时器;
this.tSoftwareUartRxState = UART_RX_START;
this.ptUartRxGpioStop();
this.ptUartRxTimerStart(0);
(2)定时器中断处理
a. 首先判断当前电平是不是低电平;如果是,则以计算出定时时间初始化定时器;如果否,则关闭定时器,启动GPIO中断;
b. 接收8个bit;
c. 如果接收完成8bit,则关闭定时器,启动GPIO中断;
switch(this.tSoftwareUartRxState){
case UART_RX_START:
if(SOFTWARE_UART_RX_X()){
this.ptUartRxTimerStop();
this.ptUartRxGpioStart();
}else{
this.ptUartRxTimerStart(1);
}
this.chRxUartBitCnt=0;
this.tSoftwareUartRxState = UART_RX_RXING_BYTE;
break;
case UART_RX_RXING_BYTE:
switch(this.chRxUartBitCnt){
case 0:
this.tRxByte.tBit0 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 1:
this.tRxByte.tBit1 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 2:
this.tRxByte.tBit2 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 3:
this.tRxByte.tBit3 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 4:
this.tRxByte.tBit4 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 5:
this.tRxByte.tBit5 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 6:
this.tRxByte.tBit6 = (SOFTWARE_UART_RX_X())?1:0;
break;
case 7:
this.tRxByte.tBit7 = (SOFTWARE_UART_RX_X())?1:0;
break;
}
this.chRxUartBitCnt++;
if(this.chRxUartBitCnt >= 8){
this.tSoftwareUartRxState = UART_RX_END;
}
break;
case UART_RX_END:
this.ptUartRxTimerStop();
this.ptUartRxGpioStart();
if(NULL != pchData){
*pchData = this.tRxByte.chByte;
}
return fsm_rt_cpl;
default:
this.ptUartRxTimerStop();
this.ptUartRxGpioStart();
}