前面一篇我们实现了IO模拟UART发送,我们继续来实现UART接收。对于接收底层资源需要一个输入IO且其可配置位下降沿中断,和一个定时器即可。
有了发送的实现,我们依葫芦画瓢,按照发送的模式进行实现,只是一个是发送一个是接收。状态机实现的过程是类似的。
启动接收即使能RX引脚的下降沿中断,用于检测起始位。在中断回调中启动定时器延迟1.5个位宽即可采样第一个bit数据。定时器中断回调中进行状态机处理,配置定时器每隔一个bit采样一次,继续处理后续位。直到处理完所有位,然后调用接收回调,并继续下一个循环。
和发送类似,只是TX输出引脚改为RX接收,并且需要提供下降沿中断配置接口。
整个数据结构如下
typedef uint8_t (*io_uart_rx_rd_pf)(void); /**< RX读接口 */
typedef void (*io_uart_rx_set_eint_pf)(uint8_t enable, void(*)(void*)); /**< RX接口设置下降沿回调 */
typedef void (*io_uart_rx_time_set_period_pf)(uint32_t t, void(*)(void*)); /**< 定时器设置周期接口,单位波特率 */
typedef void (*io_uart_rx_time_ctrl_pf)(uint8_t onoff); /**< 定时器启停控制接口 */
typedef void (*io_uart_rx_cb_pf)(uint8_t val); /**< 接收到字节回调 */
typedef void (*io_uart_rx_init_pf)(void); /**< 底层接口初始化 */
typedef void (*io_uart_rx_deinit_pf)(void); /**< 底层接口解除初始化 */
/**
* \struct io_uart_rx_e
* 接收状态枚举
*/
typedef enum
{
IO_UART_RX_STATE_DIS = 0, /**< 停止状态 */
IO_UART_RX_STATE_IDLE = 1, /**< 空闲等待RX下降沿阶段 */
IO_UART_RX_STATE_START = 2, /**< 起始位阶段 */
IO_UART_RX_STATE_DATA = 3, /**< 数据位阶段 */
IO_UART_RX_STATE_PARITY = 4, /**< 校验位阶段 */
IO_UART_RX_STATE_STOP = 5, /**< 停止位阶段 */
} io_uart_rx_e;
/**
* \struct io_uart_rx_patity_e
* 校验枚举
*/
typedef enum
{
IO_UART_RX_PARITY_NONE = 0, /**< 无校验 */
IO_UART_RX_PARITY_ODD = 1, /**< 奇校验 */
IO_UART_RX_PARITY_EVEN = 2, /**< 偶校验 */
IO_UART_RX_PARITY_0 = 3, /**< 固定0校验 */
IO_UART_RX_PARITY_1 = 4, /**< 固定1校验 */
} io_uart_rx_patity_e;
/**
* \struct io_uart_rx_dev_st
* 接收状态机结构体
*/
typedef struct
{
io_uart_rx_e state; /**< 接收主状态 */
uint8_t state_s; /**< 接收子状态 */
uint8_t data; /**< 待发送的字节数据 */
uint8_t cal_parity; /**< 计算的校验位 */
io_uart_rx_patity_e parity; /**< 校验 */
uint8_t stop; /**< 停止位 */
uint8_t data_len; /**< 数据长 */
uint32_t baud; /**< 波特率 */
io_uart_rx_rd_pf rx_rd; /**< RD读接口 */
io_uart_rx_set_eint_pf set_int; /**< 设置RX下降沿中断接口 */
io_uart_rx_init_pf init; /**< 接口初始化 */
io_uart_rx_deinit_pf deinit; /**< 接口解除初始化 */
io_uart_rx_time_set_period_pf time_set_period; /**< 定时器设置周期接口,单位uS */
io_uart_rx_time_ctrl_pf time_ctrl; /**< 定时器启停控制 */
io_uart_rx_cb_pf rx_cb; /**< 接收到字节回调 */
} io_uart_rx_dev_st;
只需要初始化和解除初始化接口
再加一个控制器启停的接口。
/**
* \fn io_uart_rx_ctrl
* 启动或停止接收
* \param[in] dev \ref io_uart_rx_dev_st
* \param[in] onoff 使能或者停止接收
* \retval 0 成功
* \retval 其他值 失败
*/
int io_uart_rx_ctrl(io_uart_rx_dev_st* dev, uint8_t onoff);
/**
* \fn io_uart_rx_init
* 初始化
* \param[in] dev \ref io_uart_rx_dev_st
*/
void io_uart_rx_init(io_uart_rx_dev_st* dev);
/**
* \fn io_uart_rx_deinit
* 解除初始化
* \param[in] dev \ref io_uart_rx_dev_st
*/
void io_uart_rx_deinit(io_uart_rx_dev_st* dev);
初始化解除初始化
void io_uart_rx_init(io_uart_rx_dev_st* dev)
{
if(dev == 0)
{
return;
}
if(dev->init == 0)
{
return;
}
dev->init();
}
void io_uart_rx_deinit(io_uart_rx_dev_st* dev)
{
if(dev == 0)
{
return;
}
if(dev->deinit == 0)
{
return;
}
dev->deinit();
}
启动接收,即使能下降沿中检测起始位,检测到下降沿回调io_uart_rx_start并停止外部中断检测,交给定时采样处理。
static void io_uart_rx_start(void* p)
{
io_uart_rx_dev_st* dev = (io_uart_rx_dev_st*)p;
/* 检测到下降沿中断时调用 */
dev->set_int(0,0); /* 关闭外部中间检测 */
dev->time_set_period(dev->baud*2/3,io_uart_rx_handle); /* 设置1.5位宽的周期,周期到了回调io_uart_tx_handle 采样第一个数据 */
dev->time_ctrl(1);
dev->state = IO_UART_RX_STATE_DATA; /* 更新状态到数据阶段,直接延迟跳过1个周期的开始位,并且延迟0.5个周期到位中间采样 */
dev->cal_parity = 0; /* 初始化信息 */
dev->state_s = 0;
dev->data = 0;
}
int io_uart_rx_ctrl(io_uart_rx_dev_st* dev, uint8_t onoff)
{
if(dev == 0)
{
return -1;
}
if(onoff != 0)
{
dev->set_int(1,io_uart_rx_start); /* 使能外部中间检测 */
dev->state = IO_UART_RX_STATE_IDLE; /* 更新状态 */
}
else
{
dev->set_int(0,0); /* 关闭外部中间检测 */
dev->time_ctrl(0); /* 停止定时器 */
dev->state = IO_UART_RX_STATE_DIS; /* 设置停止状态 */
}
return 0;
}
这个函数是核心,对照着注释和字节的阶段即可,不再赘述
和发送流程差不多
void io_uart_rx_handle(void* p)
{
uint8_t bit = 0;
uint8_t parity = 0;
io_uart_rx_dev_st* dev = (io_uart_rx_dev_st*)p;
/* 参数检查 */
if(dev == 0)
{
return;
}
switch(dev->state)
{
case IO_UART_RX_STATE_DATA:
dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_rx_handle */
dev->time_ctrl(1);
/* 更新一位,低位在前,所以先放在高位然后往低位移动 */
bit = dev->rx_rd();
dev->data >>= 1; /* 这里要先移动后更新数据,因为本次最后更新的是有效数据 */
if(bit != 0)
{
dev->data |= 0x80;
}
dev->cal_parity ^= bit;
dev->state_s++;
if(dev->state_s >= dev->data_len) /* state_s先加,因为进来一次就是收到一个bit数据 */
{
if(dev->parity == IO_UART_RX_PARITY_NONE)
{
/* 这里就开始等待停止位 */
dev->state = IO_UART_RX_STATE_STOP;
dev->state_s = 0; /**< 停止位要用到state_s计数停止位数需要清0 */
}
else
{
/* 这里就需要开始等待校验位 */
dev->state = IO_UART_RX_STATE_PARITY;
}
}
else
{
/* 不改变状态继续下一位 */
}
break;
case IO_UART_RX_STATE_PARITY:
/* 校验位时间到,进入停止位阶段 这里就需要开始发送停止位 */
dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_rx_handle */
dev->time_ctrl(1);
switch (dev->parity)
{
case IO_UART_RX_PARITY_ODD:
parity = dev->cal_parity ^ 0x01;
break;
case IO_UART_RX_PARITY_EVEN:
parity = dev->cal_parity;
break;
case IO_UART_RX_PARITY_0:
parity = 0;
break;
case IO_UART_RX_PARITY_1:
parity = 1;
break;
default:
break;
}
bit = dev->rx_rd();
if(parity != bit)
{
/* 校验错误处理 */
}
dev->state = IO_UART_RX_STATE_STOP;
dev->state_s = 0; /**< 停止位要用到sstate_s计数停止位数需要清0 */
break;
case IO_UART_RX_STATE_STOP:
dev->state_s++; /* 进入这里实际就已经接收了一个停止位了,所以是先加 */
if(dev->state_s >= dev->stop)
{
/* 完成一个字节的接收 */
dev->rx_cb(dev->data);
dev->set_int(1,io_uart_rx_start); /* 继续下一个字节接收处理 */
dev->state = IO_UART_RX_STATE_IDLE;
}
else
{
dev->time_set_period(dev->baud,io_uart_rx_handle); /* 设置一个位宽的周期,周期到了回调io_uart_tx_handle */
/* 不改变状态继续下个停止位 */
dev->time_ctrl(1);
}
break;
}
}
移植即实现数据结构中的接口依赖,即下面的接口函数
static io_uart_rx_dev_st uart_rx_dev=
{
.parity = IO_UART_RX_PARITY_ODD,
.stop = 2,
.data_len=8,
.baud = 115200,
.rx_rd = port_rx_rd,
.init = port_rx_init,
.deinit = port_rx_deinit,
.time_set_period = port_time1_set_period,
.rx_cb = rx_done_cb,
.time_ctrl = port_timer1_ctrl,
.set_int = port_set_int,
};
这里基于HPM53xx开发板实现,代码如下
Uart_rx_port.c
static void port_timer1_cb(void);
static void port_time1_set_period(uint32_t period, void(*cb)(void*));
static void port_set_int(uint8_t enable, void(*)(void*));
static void port_rx_init(void);
static void port_rx_deinit(void);
static uint8_t port_rx_rd(void);
static void port_timer1_ctrl(uint8_t onoff);
static void(*t1_cb)(void*) = 0;
static void(*int_cb)(void*) = 0;
static void rx_done_cb(uint8_t val);
static void (*rx_cb)(uint8_t val) = 0;
static io_uart_rx_dev_st uart_rx_dev=
{
.parity = IO_UART_RX_PARITY_ODD,
.stop = 2,
.data_len=8,
.baud = 115200,
.rx_rd = port_rx_rd,
.init = port_rx_init,
.deinit = port_rx_deinit,
.time_set_period = port_time1_set_period,
.rx_cb = rx_done_cb,
.time_ctrl = port_timer1_ctrl,
.set_int = port_set_int,
};
static void port_set_int(uint8_t enable, void(*cb)(void*))
{
int_cb = cb;
if(enable != 0)
{
HPM_GPIO0->PL[GPIO_DI_GPIOA].SET = (1u << 26); /* 低电平或者下降沿中断 */
HPM_GPIO0->TP[GPIO_DI_GPIOA].SET = (1u << 26); /* 边沿中断 */
HPM_GPIO0->IF[GPIO_DI_GPIOA].VALUE = (1u << 26); /* 写1清除标志 */
HPM_GPIO0->IE[GPIO_DI_GPIOA].SET = (1u << 26); /* 写1使能中断 */
}
else
{
HPM_GPIO0->IE[GPIO_DI_GPIOA].CLEAR = (1u << 26); /* 写1不使能中断 */
}
}
static void port_timer1_cb(void)
{
HPM_GPTMR1->SR = 0x1; /* 写1清除标志 */
if(t1_cb != 0)
{
t1_cb(&uart_rx_dev);
}
}
static void port_int_cb(void)
{
HPM_GPIO0->IF[GPIO_DI_GPIOA].VALUE = (1u << 26); /* 写1清除标志 */
if(int_cb)
{
int_cb(&uart_rx_dev);
}
}
static void port_time1_set_period(uint32_t baud, void(*cb)(void*))
{
t1_cb = cb;
HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 10); /* 停止 */
HPM_GPTMR1->CHANNEL[0].CR |= (1u << 14); /* 复位 */
HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 14); /* 释放复位 */
HPM_GPTMR1->CHANNEL[0].CR |= (1u << 3); /* 调试时定时器停止 */
HPM_GPTMR1->CHANNEL[0].RLD = (81000000/baud); /* 重载值 计数器从0开始运行到重载值恢复到0重新计数 1nS对应1/10个计数值 由于软件执行需要时间所以这里的值要小一点 */
HPM_GPTMR1->SR = 0x1; /* 写1清除标志 */
HPM_GPTMR1->IRQEN |= 0x01; /* 通道0重载中断使能 */
}
static void port_rx_init(void)
{
HPM_IOC->PAD[IOC_PAD_PA26].FUNC_CTL = IOC_PA26_FUNC_CTL_GPIO_A_26;
HPM_GPIO0->OE[GPIO_DI_GPIOA].CLEAR = 1u << 26;
install_isr(IRQn_GPTMR1,(uint32_t)port_timer1_cb);
intc_m_enable_irq(IRQn_GPTMR1);
install_isr(IRQn_GPIO0_A,(uint32_t)port_int_cb);
intc_m_enable_irq(IRQn_GPIO0_A);
}
static void port_rx_deinit(void)
{
//uninstall_isr(IRQn_GPTMR1);
intc_m_disable_irq(IRQn_GPTMR1);
HPM_GPIO0->IE[GPIO_DI_GPIOA].CLEAR = (1u << 26); /* 写1不使能中断 */
}
static uint8_t port_rx_rd(void)
{
if((HPM_GPIO0->DI[GPIO_DI_GPIOA].VALUE & (1u << 26)) != 0)
{
return 1;
}
else
{
return 0;
}
}
static void port_timer1_ctrl(uint8_t onoff)
{
if(onoff)
{
HPM_GPTMR1->CHANNEL[0].CR |= (1u << 10); /* 使能 */
}
else
{
HPM_GPTMR1->CHANNEL[0].CR &= ~(1u << 10); /* 停止 */
}
}
static void rx_done_cb(uint8_t val)
{
rx_cb(val);
}
void sw_uart_rx_init(void)
{
io_uart_rx_init(&uart_rx_dev);
}
void sw_uart_rx_deinit(void)
{
io_uart_rx_deinit(&uart_rx_dev);
}
void sw_uart_rx_start(uint8_t ctrl,void(*cb)(uint8_t val))
{
rx_cb = cb;
io_uart_rx_ctrl(&uart_rx_dev,ctrl);
}
Uart_rx_port.h
extern "C"{
void sw_uart_rx_init(void);
void sw_uart_rx_deinit(void);
void sw_uart_rx_start(uint8_t ctrl,void(*cb)(uint8_t val));
}
配置参数为115200-8-ODD-2
static io_uart_tx_dev_st uart_tx_dev=
{
.parity = IO_UART_TX_PARITY_ODD,
.stop = 2,
.data_len=8,
.baud = 115200,
.tx_wr = port_tx_wr,
.init = port_tx_init,
.deinit = port_tx_deinit,
.time_set_period = port_time0_set_period,
.tx_cb =tx_done_cb,
.time_ctrl = port_timer0_ctrl,
};
static io_uart_rx_dev_st uart_rx_dev=
{
.parity = IO_UART_RX_PARITY_ODD,
.stop = 2,
.data_len=8,
.baud = 115200,
.rx_rd = port_rx_rd,
.init = port_rx_init,
.deinit = port_rx_deinit,
.time_set_period = port_time1_set_period,
.rx_cb = rx_done_cb,
.time_ctrl = port_timer1_ctrl,
.set_int = port_set_int,
};
测试代码如下,收到数据原样返回。
uint8_t str[]={0xAA,0xAA};
volatile int rx_flag = 0;
void rx_cb(uint8_t val)
{
str[0]= val;
rx_flag = 1;
}
void thread_entry(void *arg)
{
sw_uart_tx_init();
sw_uart_rx_init();
sw_uart_rx_start(1,rx_cb);
while(1){
if(rx_flag)
{
rx_flag = 0;
sw_uart_tx_byte(str[0]);
}
}
sw_uart_tx_deinit();
sw_uart_rx_deinit();
}
使用串口调试助手发送,开发板返回,看到完全正确。使用示波器产看波形也完全正确。
以上超级简单的代码实现了串口数据的接收,具备良好的可移植性,这样我们就完成了IO模拟串口的全部实现。但是这还不够,以上示例代码仅作测试,并不适合实际应用。在实际应用中,我们还可以继续基于上面一篇的FIFO实现,实现基于环形缓冲区的串口收发驱动,这样才能实现高效方便应用调用的接口,见下一篇。