在有众多的uart232,485以及422通信需求类的产品中,时常会出现主控板uart接口不够用的情况,而有些外设由于其特殊性,又必须单独占有一路uart的时候,我们能够做的就是使用众多的gpio去模拟uart进行数据通信。
linux下的uart模拟需要完成和涉及到一下几方面的内容:
1、gpio的选择和初始化
2、高精度内核定时器提供标准的波特率对应的时序控制。
3、接收引脚的中断控制以及数据处理信号反馈
4、增加内核fifo机制,用于缓存接受数据
5、select控制机制,将接收事件及时的反馈到应用层。
具体如下:
一、gpio的初始化
按部就班,按照所使用的主控芯片对应的linux内核api去做就ok,例如:
#define UART2_TX1 GPIO_TO_PIN(0,12)
#define UART2_TX1_OUT gpio_direction_output(UART2_TX1, 1)
#define UART2_TX1_IN gpio_direction_input(UART2_TX1)
#define UART2_TX1_GET_VALUE gpio_get_value(UART2_TX1)
#define UART2_TX1_L gpio_set_value(UART2_TX1, 0)
#define UART2_TX1_H gpio_set_value(UART2_TX1, 1)
#define UART2_RX1 GPIO_TO_PIN(0,13)
#define UART2_RX1_OUT gpio_direction_output(UART2_RX1, 1)
#define UART2_RX1_IN gpio_direction_input(UART2_RX1)
#define UART2_RX1_GET_VALUE gpio_get_value(UART2_RX1)
#define UART2_RX1_L gpio_set_value(UART2_RX1,0)
#define UART2_RX1_H gpio_set_value(UART2_RX1,1)
二、高精度内核定时器提供标准的波特率对应的时序控制
内核提供的api以及具体使用如下:
void tx_timer_init(void)
{
hrtimer_init(&hr_timer_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
hr_timer_tx.function = tx_vibrator_timer_func;
}
void tx_timer_start(void)
{
nsecs = (unsigned long)((1000000000/g_gpio_uart_config.baudrate));
if(!tx_timer_start_flag)
{
hrtimer_start(&hr_timer_tx,ktime_set(0,nsecs),HRTIMER_MODE_REL_PINNED);
tx_timer_start_flag = true;
}
}
void tx_timer_stop(void)
{
unsigned char ret = 0;
if(tx_timer_start_flag)
{
ret = hrtimer_cancel( &hr_timer_tx );
tx_timer_start_flag = false;
}
}
void rx_timer_start(void)
{
nsecs = (unsigned long)((1000000000/g_gpio_uart_config.baudrate));
if(!rx_timer_start_flag)
{
hrtimer_start(&hr_timer_rx,ktime_set(0,nsecs/6),HRTIMER_MODE_REL_PINNED);
rx_timer_start_flag = true;
}
}
void rx_timer_stop(void)
{
unsigned char ret = 0;
if(rx_timer_start_flag)
{
ret = hrtimer_cancel( &hr_timer_rx );
//if (ret)
//printk("hr_timer_rx was still in use...\n");
rx_timer_start_flag = false;
}
}
三、接收引脚的中断控制以及数据处理信号反馈
void set_rx_pin_interrupt_mode(void)
{
unsigned char retval = 0;
irq_num = gpio_to_irq(UART2_RX1);
retval = request_irq(irq_num, UART2_RX1_interrupt_handler, 0, "irq_uartrx", NULL);
if (retval) {
printk("Error requesting IRQ\n");
}
irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_FALLING);
disable_irq(irq_num);
rx_irq_en = false;
}
void rx_pin_interrupt_free(void)
{
free_irq(irq_num, NULL);
irq_num = -1;
}
void set_rx_pin_interrupt_disable(void)
{
if(rx_irq_en)
{
disable_irq_nosync(irq_num);
rx_irq_en = false;
}
//irq_set_irq_wake(irq_num, 0);
}
void set_rx_pin_interrupt_enable(void)
{
if(!rx_irq_en)
{
enable_irq(irq_num);
rx_irq_en = true;
}
//irq_set_irq_wake(irq_num, 1);
}
四、增加内核fifo机制,用于缓存接受数据
这里为了便于数据的控制,没有使用内核提供的fifo机制,使用了自己编写的fifo处理函数
bool fifo_init(app_fifo_t * p_fifo, uint8_t * p_buf, uint16_t buf_size)
bool fifo_put(app_fifo_t * p_fifo, uint8_t byte)
bool fifo_get(app_fifo_t * p_fifo, uint8_t * p_byte)
bool fifo_peek(app_fifo_t * p_fifo, uint16_t index, uint8_t * p_byte)
bool fifo_flush(app_fifo_t * p_fifo)
bool fifo_read(app_fifo_t * p_fifo, uint8_t * p_byte_array, uint32_t * p_size)
bool fifo_write(app_fifo_t * p_fifo, uint8_t const * p_byte_array, uint32_t * p_size)
五、select控制机制,将接收事件及时的反馈到应用层。
比较简单也就是实现一个poll函数调用
static unsigned int tty_drv_poll(struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
set_rx_pin_interrupt_enable();
poll_wait(file, &tty_uart_waitq, wait);
if(ev_read_world)
{
mask = POLLIN | POLLRDNORM
}
//printk("tty_drv_poll end ev_read_world = %d,mask = %d!\n",ev_read_world,mask);
return mask;
}
接下来的工作就是将上边提到的5大块内容装到linux字符设备通用框架里,生成属于自己的gpio模拟uart/ttl驱动,就完成了具体的驱动功能实现。