在有衆多的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驅動,就完成了具體的驅動功能實現。