主题 : CAN总线及SAE J1939协议应用程序分析 基于S3C2410 ARM9嵌入式平台 复制链接 | 浏览器收藏 | 打印
大笑笑大
级别: 骑士
UID: 25314
精华: 8
发帖: 184
金钱: 1320 两
威望: 264 点
贡献值: 8 点
综合积分: 528 分
注册时间: 2010-07-22
最后登录: 2014-10-11
楼主  发表于: 2010-12-13 10:01

 CAN总线及SAE J1939协议应用程序分析 基于S3C2410 ARM9嵌入式平台

管理提醒: 本帖被 xoom 执行加亮操作(2011-06-23)
以下是我做笔记在WORD上,直接COPY过来,有些图片没有显示出来,以及没有语法着色!详细文档在:
http://wenku.baidu.com/view/ef302e235901020207409cf1.html
附件为驱动文件和一个测试测试程序
/*********************************************************************************************/
最近在做汽车总线传输,一开始入手的就是CAN总线协议,而后才是高层SAE J1939协议.
废话不多说,有用的才是主要!
开发板使用的是三星S3C2440。因为开发板上没有CAN总线接口,所以采用SPI转换,利用的是Microchip公司的MCP2510
CAN总线采用MCP2510+TJA1050+HCPL2630模块,如下图:

第零步:要做的就是规划
1、    电路设计焊接
2、    驱动编写
3、    CAN数据测试
4、    J1939协议测试
第一步:电路设计
    MCP2510使用到SPI接口,我使用的S3C2440上的SPI1接口,相应的接口为:
    S3C2440:MCP2510电路图对应的节点
GPG0:RES
GPG3:CS
GPG5:MISO
GPG6:MOSI
GPG7:SPCK
GPG11:INT
测试阶段,均采用手动焊接,因为只有几个器件而已,电源也均采用5V,隔离芯片也可以不接上去,(*^__^*) 嘻嘻……
第二步:驱动编写
A、    SPI测试
B、    MCP2510测试
C、    CAN数据传输测试,自发自收以及双机测试
具体的测试不加以说明,下面通过对程序的解释,来逐步理解。
1、SPI编写
unsigned char CAN_SPI_CMD( unsigned char cmd, unsigned int addr, unsigned char arg1, unsigned char arg2 )
//该函数为CAN与SPI的接口函数,即MCP2510的SPI指令集

{
    unsigned char ret=0;
    unsigned char data=0;
    //printk("CAN_SPI_CMD\n");
    *(unsigned int *)rGPGDAT &=CHIP_SELECT_nSS1;
//此处为CS电平设置,设置为0,CHIP_SELECT_nSS1的值在MCP2510.H里面
    mdelay(1);
    //printk("rGPGDAT=%02X\n",*rGPGDAT);
    //CS = 0, ENABLE MCP
    switch( cmd ){
        case SPI_CMD_READ:
            //printk("SPI_CMD_READ\n");
            ret = SPI_SendByte(SPI_CMD_READ, &data);
            ret = SPI_SendByte(addr, &data);
            ret = SPI_SendByte(0xff, &data);
            break;
        case SPI_CMD_WRITE:
            //printk("SPI_CMD_WRITE\n");
            ret = SPI_SendByte(SPI_CMD_WRITE, &data);
            ret = SPI_SendByte(addr, &data);
            ret = SPI_SendByte(arg1, &data);
            break;
        case SPI_CMD_RTS:
            ret = SPI_SendByte(SPI_CMD_RTS, &data);
            break;
        case SPI_CMD_READSTA:
            ret = SPI_SendByte(SPI_CMD_READSTA, &data);
            ret = SPI_SendByte(0xff, &data);
            break;
        case SPI_CMD_BITMOD:
            //printk("SPI_CMD_BITMOD\n");
            ret = SPI_SendByte(SPI_CMD_BITMOD, &data);
            ret = SPI_SendByte(addr, &data);
            ret = SPI_SendByte(arg1, &data);
            ret = SPI_SendByte(arg2, &data);
            break;
        case SPI_CMD_RESET:
            //printk("SPI_CMD_RESET\n");
            ret = SPI_SendByte(SPI_CMD_RESET, &data);
            break;
        case SPI_CHECK:
            //printk("SPI_CHECK\n");
            ret = SPI_SendByte(0xff, &data);
            data= ret;
            break;
        default:
            ret = 0xff;
            // any value is ok
    }
    *(unsigned int *)rGPGDAT = (*(unsigned int *)rGPGDAT)|CHIP_DESELECT_nSS1;
//设置CS电平为高
    mdelay(1);
    //printk("rGPGDAT=%02X\n",*rGPGDAT);
    return data;
}
/***************************************
Module Name:    SPI.CPP
Abstract:            SPI Interface Routines for Samsung SC2410 CPU
Notes:                Presently, only the SPI Channel 1 is supported
Environment:    Samsung SC2410 CPU
Date:                 2010/11/03
By:                     PENGHUI XIAO
***************************************/
void Port_Init(void)
{
        //Ports  :    GPG5       GPG4    GPG3    GPG2    GPG1    GPG0
        //Signal : KBDSPIMISO LCD_PWREN EINT11 nSS_SPI IRQ_LAN IRQ_PCMCIA
        //Setting:  SPIMISO1  LCD_PWRDN EINT11   nSS0   EINT9    EINT8
        //Binary :     11         xx   ,  01      xx  ,  xx        01
        *(unsigned int *)rGPGCON |=0x0080FC41;
        *(unsigned int *)rGPGCON &=0xFFBFF7D;
        /*poll up MISO1 MOSI1,SCK1*/
        *(unsigned int *)rGPGUP  &=0xFF1F;
        *(unsigned int *)rGPGUP  |=0x0060;
        printk("Port_Init\n");
        printk("rGPGCON=%08X\n",*rGPGCON);
        printk("rGPGUP=%08X\n",*rGPGUP);
}
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:            SPI_Init()
Description:    Initializes the Serial Peripheral Interface (SPI)
Notes:                This routine assumes that the control registers (see
                            the globals section above) have already been initialized.
Returns:            Boolean indicating success.
-------------------------------------------------------------------*/
int SPI_Init(void)
//SPI接口初始化
{
    //int index;
    Port_Init();
//IO状态初始化
    *(unsigned int *)rGPGDAT &=CHIP_SET_RES_0;
    printk("rGPGDAT=%02X\n",*rGPGDAT);
    //RES=0 先复位下MCP2510
    //WAIT(100);
    mdelay(100);
    *(unsigned int *)rGPGDAT |=CHIP_SET_RES_1;
    printk("rGPGDAT=%02X\n",*rGPGDAT);
    //RES=1
    *(unsigned int *)rGPGDAT |=CHIP_DESELECT_nSS1;
    printk("rGPGDAT=%02X\n",*rGPGDAT);
    //CS=1 disable mcp
    //IMPORTANT: By default, the internal clock is disabled.
    //To configure the controller,we must first enable it.
    StartSPIClock();
    /*
    for( index = 0; index < 20; index++)
        *(unsigned char *)rSPTDAT1 =  0xFF;
    */
    return TRUE;
}
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:            SPI_Deinit()
Description:    Deinitializes the Serial Peripheral Interface (SPI)
Notes:                This routine DOES NOT unmap the control registers;
                            the caller is responsible for freeing this memory.
Returns:            Boolean indicating success.
-------------------------------------------------------------------*/
int SPI_Deinit(void)
{
    StopSPIClock();
    return TRUE;
}
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:            SPI_SendByte()
Description:    Sends the specified byte out onto the SPI bus.
Returns:            Boolean indicating success.
-------------------------------------------------------------------*/
int SPI_SendByte(char bData, char* pData)
{
    //----- Wait until the controller is ready to transfer -----
    if(SPI_WaitTxRxReady()==FALSE) return FALSE;
    //----- Put the byte out onto the SPI bus -----
    *(unsigned char *)rSPTDAT1 =  bData;
    //----- Delay a little bit so the byte finishes clocking out before the chip select line is deasserted -----
    if(SPI_WaitTxRxReady()==FALSE) return FALSE;
    *pData = *(unsigned char *)rSPRDAT1;
    return TRUE;
}
int SPI_ReadByte(char *pData)
{
    //----- Wait until the controller is ready to transfer -----
    if(SPI_WaitTxRxReady()==FALSE) return FALSE;
    //----- Put the byte out onto the SPI bus -----
    *pData = *(unsigned char *)rSPRDAT1;
    return TRUE;
}
int SPI_WaitTxRxReady(void)
{
    int waitCount;
    int i;
    //----- Wait until the controller is ready to transfer -----
    waitCount = 1000;
    while(!(*rSPSTA1 & SPI_TRANSFER_READY))
    {
        waitCount--;
        if(waitCount == 0)
        {
            printk("SPI_TRANSFER_READY_FAIL\n");
            printk("rSPSTA1=%02X\n",*rSPSTA1);
            return FALSE;
        }
        for(i=0;i<10;i++);
    }
    return TRUE;
}
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:        StartSPIClock()
Description:    Enables the SPI clock.
Returns:        N/A
-------------------------------------------------------------------*/
void StartSPIClock(void)
{
    *(unsigned int *)rCLKCON |=SPI_INTERNAL_CLOCK_ENABLE;
    printk("rCLKCON=%02X\n",*rCLKCON);
    // Enable the CPU clock to the SPI controller
    //SPI Baud Rate Prescaler Register,Baud Rate=PCLK/2/(Prescaler value+1)
    *(unsigned int *)rSPPRE1 =0x31;       //freq = 0.5M
    printk("rSPPRE1=%02X\n",*rSPPRE1);
    //polling,en-sck,master,low,format A,nomal = 0 | TAGD = 1
    *rSPCON1=(0<<6)|(0<<5)|(1<<4)|(1<<3)|(0<<2)|(0<<1)|(0<<0);
    printk("rSPCON1=%02X\n",*rSPCON1);
    //Multi Master error detect disable,reserved,rech=*spi_sprdat0;lease
    *rSPPIN1=(0<<2)|(0<<0);
    printk("rSPPIN1=%02X\n",*rSPPIN1);
}
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:            StopSPIClock()
Description:    Disables the SPI clock.
Returns:            N/A
-------------------------------------------------------------------*/
void StopSPIClock(void)
{
    *(unsigned int *)rCLKCON &=~SPI_INTERNAL_CLOCK_ENABLE;
    //Disable the CPU clock to the SPI controller
    //polling,dis-sck,master,low,format A,nomal = 0 | TAGD = 1
    *rSPCON1=(0<<6)|(0<<5)|(0<<4)|(1<<3)|(0<<2)|(0<<1)|(0<<0);
}

2、下面是对MCP2510内部初始化
在执行完insmod mcp2510.ko,即加载驱动模块后,调用的第一个程序为:
mcpcan_init_module()
在该函数对设备号进行注册且开辟内存空间,MCP_device_init()内开辟了100个数据缓存器MCP_device,分别用RecvHead和RecvTail标识当前使用到的内存地址和释放的内存地址,中间部分为缓存数据
在结构体MCP_device内:
struct MCP_device {
    volatile unsigned long RecvBuf;
//存储开辟空间的首地址
    volatile unsigned long RecvHead;
    volatile unsigned long RecvTail;
    volatile unsigned long RecvNum;
//存储数据个数,可以不用
    wait_queue_head_t inq;   //or use as a global variable
//等待队列变量 在read时,进入wait_event_interruptible等待
//在中断读取完数据后,唤醒wake_up_interruptible
    struct semaphore sem;    //semaphore used for mutex
//初始化信号量为互斥量。 互斥量为信号量的特例。只允许两个进程同步访问共享资源
// down_interruptible(&dev->sem)
//函数用来获取信号量,如果信号量大于或等于0,获取信号量,
//否则进入睡眠状态,等待信号量被释放后,激活该进程
//up (&dev->sem)
//释放信号量
    unsigned int      IrqNum;
    unsigned int      MinorNum;/*device minor number*/
}__attribute__((aligned(L1_CACHE_BYTES),packed));
3、接下来是驱动的打开
device_open
主要动作时初始化MCP2510和注册中断号
在初始化MCP2510的函数内,一开始的是对SPI进行测试
CAN_SPI_CMD( SPI_CMD_RESET, ARG_UNUSED, ARG_UNUSED, ARG_UNUSED);
mdelay(10);
canstat=CAN_SPI_CMD(SPI_CMD_READ,TOLONG(&(MCP2510_MAP->CANSTAT)),ARG_UNUSED, ARG_UNUSED);
canctrl=CAN_SPI_CMD(SPI_CMD_READ,TOLONG(&(MCP2510_MAP->CANCTRL)),ARG_UNUSED, ARG_UNUSED);
printk("CANSTAT80=: 0x%02X!\n",canstat);
printk("CANCTRLe7=: 0x%02X!\n",canctrl);
if((canstat != 0x80)  &&  (canctrl != 0xe7))
{
printk("Please check the spi PORT with MCU!! \n");
        return MCP2510_Init_fail;
}
以上程序返回的CANSTAT和CANCTRL复位后的初始值分别为0X80H和0XE7H,如果不是,说明SPI接口有问题,或SPI传输程序有问题
在初始化函数下:记得设置自己的配置模式是正常还是回环模式
//CAN_SPI_CMD( SPI_CMD_BITMOD, TOLONG(&(MCP2510_MAP->CANCTRL)), 0xe0, 0x00 ); // NORMAL OPERATION MODE
CAN_SPI_CMD( SPI_CMD_BITMOD, TOLONG(&(MCP2510_MAP->CANCTRL)), 0xe0, 0x40 );
// LOOK BACK MODE
3、发送数据函数device_write
该函数没有什么特别的地方,主要工作为填充数据到MCP2510的三个缓冲器内,缓冲器满返回满标识,写入错误,返回写入错误
Return    :    RC_SUCCESS     0
             RC_SPI_FAIL    2
             RC_FULL        1
By        :    xiaopenghui
4、读取数据函数device_read
也没有什么特别的地方,直接判断缓冲器是否有数据,没有数据进入睡眠,等待数据唤醒。
5、中断服务之程序mcpcan1_handler
在接收器MCP2510的接收缓冲器满时产生的中断,该程序直接读取相应的缓冲器值保持到数据缓冲区内并判断是否有消息错误

第三步:CAN数据测试
定义与驱动相同类型的报文结构
typedef struct mcpcan_data{
    unsigned int BufNo;//缓冲器标识
    unsigned int IdType;//报文类型
    unsigned int id;//报文
    unsigned int DataLen;//数据长度
    char data[8];
}mcpcan_data;
在cantxrx_pthread.c内利用多线程方式进行自发自收测试
cantxrx_pthread.c的地址:
http://hi.baidu.com/xphyym/blog/item/a7a8fbc8e1e02aec52664fa7.html?timeStamp=1292203774234
第四步:J1939协议测试
J1939协议和CAN协议不同的点为它们的数据结构不同,
union J1939_MESSAGE_UNION {
    struct {
        unsigned char    SourceAddress;
        unsigned char    PDUSpecific;
        unsigned char    PDUFormat;// CA should use only PDUFormat.
        unsigned int    DataPage    : 1;//表示分配1BIT空间
        unsigned int    Res        : 1;
        unsigned int    Priority    : 3;
        unsigned int    PDUFormat_Top    : 3;// This needs pre and post processing.
        unsigned char    Data[J1939_DATA_LENGTH];
    };
        unsigned int    J1939_HEAD;
};
#define GroupExtension     PDUSpecific
#define DestinationAddress     PDUSpecific
typedef union J1939_MESSAGE_UNION J1939_MESSAGE;
以上为J1939消息的结构体,其中J1939_HEAD与结构体struct公用4个字节的内存空间。
在cantxrx.c中简单的发送一个J1939的数据,自己接收并解码显示出来。
data_send.BufNo = 0;
data_send.IdType = 1;
data_send.id = 0X18FE6E00;
data_send.DataLen = 8;
strcpy(data_send.data, "00000000");
data_send.data[0]=temp&0xff;
data_send.data[1]=(temp>>8)&0xff;
解释以上发送的数据:
    18H:发送的数据优先级值6所对应的值
    FEH:PDU格式
    6EH:PDU指定,高分辨率车辆速度
字节[1:0]为车前轮车轴,左前轮速度。值=[1:0]/256 公里每小时。
输出结果如下:
****************send datagram.*******************
the data sended is 11000000
data_receive=Arbitra 8bits_Data
data_receive=18fe6e0011000000
BufNo=03
PDUFormat_Top=00
DataPage=00
Res=00
Priority=06
PDUFormat=fe
PDUSpecific=6e
SourceAddress=00
J1939_VEHICLE_SPEED=49.191406,48.187500,48.187500,48.187500

哦,测试结束,(*^__^*) 嘻嘻……完成了初步阶段
粗略的解释了以上程式,有什么错误和遗漏的地方还请指正和交流,谢谢!
我的邮箱xphyym@sina.com
QQ交流群:128126849
附件设置隐藏,需要回复后才能看到
自由,自强,共享,共创。
级别: 论坛版主
UID: 12573
精华: 27
发帖: 8881
金钱: 46490 两
威望: 9298 点
贡献值: 27 点
综合积分: 18302 分
注册时间: 2010-01-09
最后登录: 2016-03-18
1楼  发表于: 2010-12-13 10:40
多谢分享
新手如何向我们反馈有效的信息,以便解决问题,见此贴:
http://www.arm9home.net/read.php?tid-14431.html

[注]: 此处签名链接仅为指引方向,而非解答问题本身.
级别: 新手上路
UID: 20594
精华: 0
发帖: 22
金钱: 110 两
威望: 22 点
贡献值: 0 点
综合积分: 44 分
注册时间: 2010-05-04
最后登录: 2012-05-20
2楼  发表于: 2010-12-18 15:58
多谢分享
级别: 新手上路
UID: 15314
精华: 0
发帖: 27
金钱: 135 两
威望: 27 点
贡献值: 0 点
综合积分: 54 分
注册时间: 2010-03-03
最后登录: 2015-05-04
3楼  发表于: 2011-01-05 12:23
看看。。。。。。。。。。。
级别: 新手上路
UID: 38002
精华: 0
发帖: 1
金钱: 5 两
威望: 1 点
贡献值: 0 点
综合积分: 2 分
注册时间: 2011-02-20
最后登录: 2011-02-20
4楼  发表于: 2011-02-20 09:49
我的神啊!
级别: 新手上路
UID: 5185
精华: 0
发帖: 24
金钱: 165 两
威望: 85 点
贡献值: 0 点
综合积分: 48 分
注册时间: 2009-04-13
最后登录: 2012-06-14
5楼  发表于: 2011-02-23 18:52
牛 学习了
级别: 新手上路
UID: 15612
精华: 0
发帖: 4
金钱: 20 两
威望: 4 点
贡献值: 0 点
综合积分: 8 分
注册时间: 2010-03-07
最后登录: 2012-01-01
6楼  发表于: 2011-02-28 20:49
good ,very good
级别: 新手上路
UID: 38918
精华: 0
发帖: 2
金钱: 10 两
威望: 2 点
贡献值: 0 点
综合积分: 4 分
注册时间: 2011-03-02
最后登录: 2011-04-08
7楼  发表于: 2011-03-02 23:55
谢谢分享!
级别: 新手上路
UID: 20216
精华: 0
发帖: 49
金钱: 245 两
威望: 49 点
贡献值: 0 点
综合积分: 98 分
注册时间: 2010-04-28
最后登录: 2014-04-10
8楼  发表于: 2011-03-03 10:02
版主好贴子。
级别: 新手上路
UID: 11651
精华: 0
发帖: 38
金钱: 190 两
威望: 38 点
贡献值: 0 点
综合积分: 76 分
注册时间: 2009-12-15
最后登录: 2015-11-23
9楼  发表于: 2011-03-04 18:36
谢谢分享!