文章目录
当我们用的ARM芯片UART串口数量不足时,有时需要使用IO口来模拟实现红外串口的发送与接收,关于串口的时序以及工作特点,这篇文章不做介绍,本篇文章主要描述如何实现IO口模拟实现串口功能。
硬件平台:ATMEL SAM9G25
kernel版本:Linux2.6.39
红外串口示意图
以下是红外串口的电路图,有图可知,要想用ATMEL芯片的IO口模拟实现串口功能,需要以下4个条件:
1、38K调制方波
2、RXD接收引脚
3、TXD发射引脚
4、定时器,用于bit位计时
IO模拟红外串口设计思路
串口时序参照上图所示。这里我们以红外串口通信波特率为1200bps为例,进行实现。
波特率为1200bps时,每个比特位的占用的时间为833us
接收功能实现
- 1、RXD引脚必须作为IRQ中断引脚,当红外接收头接收到起始信号(下降沿)时,产生中断,进入接收状态,此时可以关闭RXD引脚的中断功能
- 2、在RXD引脚中断服务程序中开启一个定时器,从第一个下降沿产生的中断开始计时,时长为416us,为什么是416us呢,这个时长大约是1个bit位时长的一半,这样在416us计时完毕时,产生一个计时中断,我可以再次检测RXD引脚是否为低电平,我这样做是为了检测一个确切的起始信号,滤除一些干扰引起的下降沿中断;
- 3、第一次计时完毕时,正好处于start位的中间,这样以后检测bit0—bit7以及parity、stop数据位时,正好计时1个bit位对应的833us,检测每个数据位都处于bit位的中间位置。
- 4、接收其它位时,每计时833us,产生一个计时中断,根据接收位计数,按照计时中断连续推进,直到接收到停止位,停止位接收完毕之后可以判断parity位于计算出的校验位是否匹配。
- 5、1个字节的接收完毕之后,再次开启RXD引脚的下降沿中断,等待中断接收下一个字节;进入步骤1
发射功能实现
- 1、首先要配置一个定时器,通过一个PWM口输出38KHz方波信号,可以在发送数据前打开,发送完毕后关闭。
- 2、进入发送状态后,开启一个bit位计时器,计时时长为833us,即1个bit位的时长,先发送start位,低电平;然后根据待发送字节的bit位,按照bit0—bit7 的顺序发送,bit位为0时输出低电平,否则输出高电平
- 3、根据计时器中断,以及发送bit位计数连续推进,直到发送完所有的数据位,根据计算出的parity位,输出相应的电平信号
- 4、输出parity校验位后,最后一个计时输出stop位,高电平;计时完毕时,即发送完1个字节,若还有剩余字节,则回到步骤2,发送剩余的字节;
- 5、所有字节发送完毕后退出发送状态,关闭38KHz方波信号。
IO模拟红外串口驱动代码
驱动代码实现采用platform_device与platform_driver那一套,字符设备驱动框架。
数据结构及相关宏定义
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <asm/types.h>
#include <asm/uaccess.h>
#include <mach/at91_pio.h>
#include <mach/at91sam9x5.h>
#include <mach/at91_pmc.h>
#include <mach/at91_tc.h>
#include <mach/gpio.h>
#include <mach/io.h>
#include "at91sam9g25_pwm.h"
#define AT91_PWM_MR 0x00 /*Mode Register*/
#define PWM_CLK_DIVA(n) (n)
#define PWM_PREA(n) (n << 8)
#define AT91_PWM_ENA 0x04 /*Enable Register*/
#define AT91_PWM_DIS 0x08 /*Disable Register*/
#define AT91_PWM_SR 0x0C /*Status Register*/
#define AT91_PWM_IER 0x10 /*Interrupt Enable Register*/
#define AT91_PWM_IDR 0x14 /*Interrupt Disable Register*/
#define AT91_PWM_IMR 0x18 /*Interrupt Mask Register*/
#define AT91_PWM_ISR 0x1C /*Interrupt Status Register*/
#define AT91_PWM_CMR(ch_num) (0x200 + ch_num * 0x20 + 0x00) /*Channel Mode Register*/
#define PWM_CPRE_MCKDIV1 0x00
#define PWM_CALG_LEFT 0x00
#define PWM_CPOL_HIGH (1 << 9)
#define PWM_CPD (1 << 10)
#define AT91_PWM_CDTY(ch_num) (0x200 + ch_num * 0x20 + 0x04) /*Channel Duty Cycle Register*/
#define AT91_PWM_CPRD(ch_num) (0x200 + ch_num * 0x20 + 0x08) /*Channel Period Register*/
#define AT91_PWM_CCNT(ch_num) (0x200 + ch_num * 0x20 + 0x0C) /*Channel Counter Register*/
#define AT91_PWM_CUPD(ch_num) (0x200 + ch_num * 0x20 + 0x10) /*Channel Update Register*/
/*错误类型*/
#define DRV_NO_ERROR (0) //正常
#define DRV_ERROR_DEV (-1) //设备打开失败
#define DRV_ERROR_INPUT_ARGS (-2) //输入参数错误
#define DRV_ERROR_OPT_FAILED (-3) //操作失败
#define DRV_ERROR_OPT_NOT_OVER (-4) //操作未结束
#define DRV_ERROR_DEV_BUSY (-5) //设备忙
/*操作类型*/
#define DRV_OPTTYPE_SET (0x1000) //设置
#define DRV_OPTTYPE_GET (0x1100) //读取
#define DRV_OPTTYPE_INIT (0x1200) //初始化
/*常用类型*/
#define DRV_TRUE 1
#define DRV_FALSE 0
/*设备状态定义*/
#define DRV_STATUS_IDLE 0x10
#define DRV_STATUS_BUSY 0x11
#define DRV_STATUS_TX 0x12
#define DRV_STATUS_RX 0x13
/*其他*/
#ifndef NULL
#define NULL ((void*)0)
struct iouart_platform_data
{
u32 tx_pin;
u32 rx_pin;
u32 tx_pin_base;
u32 rx_pin_base;
u32 tx_pin_port;
u32 rx_pin_port;
u32 rx_pin_irq;
u32 tc_irq;
u32 flags;
u32 pwm_pin;
u32 pwm_port;
void __iomem *tc_base;
void __iomem *pwm_base;
struct resource *tc_res;
struct resource *pwm_res;
};
#define IOUART_BUF_LEN (4*1024) //发送、接收缓冲区最大长度
struct ring_t
{
unsigned char buf[IOUART_BUF_LEN];
unsigned int index; //对于rx来说,index表示待读取数据首地址索引,对于tx来说,表示待发送数据首地址索引
unsigned int len; //对于rx来说,len表示当前未读取的数据长度,对于tx来说,表示未发送的数据长度
unsigned char bitStep;
unsigned char parity;
unsigned char byte;
};
struct iouart_dev_t
{
struct ring_t rx; //接收缓冲区
struct ring_t tx; //发送缓冲区
spinlock_t lock; //自旋锁
wait_queue_head_t tx_waitqueue; //发送等待队列
int trans_status; //通讯状态
struct platform_device *pdev;
};
驱动资源以及变量信息
/*****************irda设备定义****************/
static struct resource irda_uart_resource[] =
{
[0] =
{
.start = AT91SAM9X5_BASE_TC3,
.end = AT91SAM9X5_BASE_TC3 + 0x40 - 1,
.flags = IORESOURCE_MEM,
},
[1] =
{
.start = AT91SAM9X5_BASE_PWMC,
.end = AT91SAM9X5_BASE_PWMC + SZ_16K - 1,
.flags = IORESOURCE_MEM,
},
};
static struct iouart_platform_data irda_uart_data =
{
.tx_pin = AT91_PIN_PC2,
.rx_pin = AT91_PIN_PB13,
.tx_pin_base = AT91_PIN_PC0,
.rx_pin_base = AT91_PIN_PB0,
.tx_pin_port = AT91_PIOC,
.rx_pin_port = AT91_PIOB,
.flags = TX_ENABLE | RX_ENABLE | PWM_ENABLE ,//| TX_PIN_OPENDRAIN,
.pwm_pin = AT91_PIN_PB18,
.pwm_port = PWM1,
.rx_pin_irq = AT91SAM9X5_ID_PIOAB,
.tc_irq = AT91SAM9X5_ID_TCB,
};
/****************平台设备与驱动************************/
static struct platform_device iouart_device[] =
{
[0] =
{
.name = "irda",
.id = 0,
.dev =
{
.platform_data = &irda_uart_data,
.release = iouart_release,
},
.resource = irda_uart_resource,
.num_resources = ARRAY_SIZE(irda_uart_resource),
},
};
static struct platform_driver iouart_driver[] =
{
[0] =
{
.probe = iouart_probe,
.remove = iouart_remove,
.driver =
{
.name = "irda",
.owner= THIS_MODULE,
},
},
};
static struct file_operations iouart_ops =
{
.owner = THIS_MODULE,
.open = iouart_open,
.release = iouart_close,
.read = iouart_read,
.write = iouart_write,
};
static int major = 0; //主设备号
static struct class *iouart_class; //class
static long l_rxIrqIsr[4] = {
0,0,0,0}; //针对rx pin中断的isr保留值?
驱动的入口与出口
static int __init iouart_init(void)
{
int ret = 0,m,n,k;
/*注册字符设备*/
ret = register_chrdev(major,DEV_NAME,&iouart_ops);
if(ret < 0)
{
printk(KERN_INFO "iouart_init: register char dev failed !\n");
return -1;
}
/*获取动态分配主设备号*/
if(major == 0)
{
major = ret;
}
/*注册dev class*/
iouart_class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(iouart_class))
{
printk(KERN_INFO "iouart_init: class create failed !\n");
goto fail0;
}
/*注册platform_device*/
for(m = 0; m < ARRAY_SIZE(iouart_device); m++)
{
ret = platform_device_register(&iouart_device[m]);
if(ret < 0)
{
printk(KERN_INFO "iouart_init: platform register device failed !\n");
goto fail1;
}
}
/*注册platform driver*/
for(n = 0; n < ARRAY_SIZE(ioua