CAN总线收发节点设计

写在前面

这是微机接口的一个项目作业。
这段时间一直在宿舍隔离,没办法进行焊接和测试,但原理和代码已经在学习板子上经过验证。

设计目标

CAN在工业现场大量应用,尤其是汽车工业,设计一种CAN总线收发节点,该节点自身带8路模拟信号采集,采集结果通过CAN总线发送到上位机并显示。

技术要求

(1)系统以51单片机为控制器,和具有CAN接口的器件能通信;

(2)CAN控制器用SJA1000,总线收发器用PCA82C250;

(3)节点带8路模拟信号采集,信号范围0-5V;

(4)用USB转CAN模块,通过USB口接收CAN节点发送的数据,验证结果是否正确。

提交材料

(1)提交纸质版设计报告1份(包括测量原理、主要电路设计、主要器件选型、程序设计原理、关键程序设计举例等);

(2)电子版设计报告、系统电气原理图、完整的程序代码

(3)提交实物1套,能现场演示(所需的can调试助手 can总线分析仪可以找我);

(4)该题目4人完成(1人负责硬件设计、1人负责单片机软件设计、1人负责调试、1人负责设计报告的整理编辑以及答辩PPT制作),在设计报告上写清楚每人所承担的工作。

项目实现

设计成果展示

实物展示

原理图设计

PCB设计

上位机效果

测量原理

ADC数模转换原理,这里采用PCF8591AD采样芯片,测量原理如下:

通过引脚AIN0、AIN1、AIN2、AIN3输入的模拟信号(电压),经过模拟信号多路复用器、采样与保持、比较器,把处理后的数据放入ADC数据寄存器中,通过I2C总线接口传递给51主控芯片。

主要电路设计

供电与程序烧录电路

考虑到板子尺寸的限制以及器件的选型,这里采用UCB转串口芯片CH340,在USB供电的同时,又可通过串口进行程序的烧录,一路双用。

VCC直接作为5V电源输入,在串口芯片那边有一个保险丝12V/1000mA的保险丝进行保护,并通过电容进行滤波与稳压,来防止热插拔效应的干扰。

拨动电源开关,可看到电源指示灯亮起。

51主控芯片附近电路

主控芯片选取的是STC90C51RD+,国产51MCU芯片,简单易学,入门容易。工作电压:5.5V-3.4V,工作温度范围:-40-+85°C,工作频率范围:0-40MHz,用户应用程序空间4K,片上集成1280字节RAM,32个通用I/O口,4个外部中断。基本可以满足项目要求,实现对应的功能。

复位电路

采用阻容复位电路,电容C7是10μF,电阻R7是10K。

晶振电路

使用外部晶振12MHz,此时电容选取47pF。

AD采样电路

采用PCF8591这款芯片,该芯片具有4路模拟输入通道,8位AD采样精度,以及一个DA输出。

电路部分,使用两个电位器作为采样目标,通过改变电位器阻值来改变采样的数值,并通过开关来切换AD采样的通道。

把采集到的数据存放至8位的数据寄存器中,通过I2C总线传输到51主控芯片中。

利用该芯片的一个DA输出通道,可以连接一个LED的灯,转动电位器,可以观察灯的的亮度发生细微的变化,更加的直观。

数码管驱动电路

为了便于调试,这里使用8位共阴数码管进行实时显示,数码管驱动芯片选择74HC573,作为最常见的锁存芯片,在这里一个作为位锁存,一个作为段锁存。

数码管这里采用的是共阴数码管,两个4位的数码管,合成一个8位的数码管。

CAN总线通讯电路

根据实际的项目需求,CAN总线电路采用SJA1000作为CAN控制器,PCA82C250作为CAN总线收发器。

但因单独购买芯片没有相关渠道,转而选择集成化的CAN通讯模块。

主要器件选型

器件选型方面本着有现成的就使用现成的、能简单实现的功能就不做的复杂的原则。器件选型上可以分为两类:芯片类,其他电子器件类。

芯片类

51主控芯片STC90C516RD+

USB转串口芯片CH340G

AD采样芯片PCF8591

锁存器芯片74HC573

CAN通讯模块

其他器件

开关

按键

四位共阴数码管

晶振

供电USB接口

电阻、电容、二极管、LED

程序设计原理

程序框架

整个程序框架包含了:main.c、display.c、i2c.c、uart.c、delay.c。

主函数模块main.c,延时函数模块delay.c、数码管驱动函数模块display.c、i2c驱动函数模块i2c.c、串口通讯函数模块uart.c。相关函数都用.h文件进行封装,提供相关的接口,供主函数调用。

主函数模块main.c
/*
Date:2022.03.22
Author:
Target:主函数
*/
#include <reg52.h>
#include "i2c.h"
#include "delay.h"
#include "display.h"
#include <uart.h> #define AddWr 0x90 //写数据地址
#define AddRd 0x91 //读数据地址 extern bit ack;
bit ReadADFlag; unsigned char VoltData[5]; //存储电压的全局变量,用于串口通讯 unsigned char numback(unsigned char s);
unsigned char ReadADC(unsigned char Chl);
bit WriteDAC(unsigned char dat);
/*------------------------------------------------
主程序
------------------------------------------------*/
main()
{
unsigned char num=0,num0=0,num1=0,num2=0,num3=0,i;
Init_Timer0();
DelayMs(20); InitUART(); while (1) //主循环
{
if(ReadADFlag)
{
ReadADFlag=0;
//连续读5次,输入通道后多读几次,取最后一次值,以便读出稳定值
for(i=0;i<5;i++)
num0=ReadADC(0);
num0=num0*5*10/256;// x10表示把实际值扩大10,如4.5 变成 45 方便做下一步处理 x5 表示基准电压5V
TempData[0]=dofly_DuanMa[num0/10]|0x80;
TempData[1]=dofly_DuanMa[num0%10]; for(i=0;i<5;i++)
num1=ReadADC(1);
num1=num1*5*10/256; // x10表示把实际值扩大10,如4.5 变成 45 方便做下一步处理
TempData[2]=dofly_DuanMa[num1/10]|0x80;
TempData[3]=dofly_DuanMa[num1%10]; for(i=0;i<5;i++)
num2=ReadADC(2);
num2=num2*5*10/256; // x10表示把实际值扩大10,如4.5 变成 45 方便做下一步处理
TempData[4]=dofly_DuanMa[num2/10]|0x80;
TempData[5]=dofly_DuanMa[num2%10]; for(i=0;i<5;i++)
num=ReadADC(3);
num3=num3*5*10/256; // x10表示把实际值扩大10,如4.5 变成 45 方便做下一步处理
TempData[6]=dofly_DuanMa[num3/10]|0x80;
TempData[7]=dofly_DuanMa[num3%10];
//主循环中添加其他需要一直工作的程序 VoltData[0]=num0;
VoltData[1]=num1;
VoltData[2]=num2;
VoltData[3]=num3;
VoltData[4]=0xff; SendStr1(VoltData);
DelayMs(240);//延时循环发送
DelayMs(240);
}
/*
SendStr1(VoltData);
DelayMs(240);//延时循环发送
DelayMs(240);
*/
}
}
/*------------------------------------------------
读AD转值程序
输入参数 Chl 表示需要转换的通道,范围从0-3
返回值范围0-255
------------------------------------------------*/
unsigned char ReadADC(unsigned char Chl)
{
unsigned char Val;
Start_I2c(); //启动总线
SendByte(AddWr); //发送器件地址
if(ack==0)return(0);
SendByte(Chl); //发送器件子地址
if(ack==0)return(0);
Start_I2c();
SendByte(AddRd);
if(ack==0)return(0);
Val=RcvByte();
NoAck_I2c(); //发送非应位
Stop_I2c(); //结束总线
return(Val);
}
/*------------------------------------------------
写入DA转换数值
输入参数:dat 表示需要转换的DA数值,范围是0-255
------------------------------------------------*/
/*bit WriteDAC(unsigned char dat)
{
Start_I2c(); //启动总线
SendByte(AddWr); //发送器件地址
if(ack==0)return(0);
SendByte(0x40); //发送器件子地址
if(ack==0)return(0);
SendByte(dat); //发送数据
if(ack==0)return(0);
Stop_I2c();
}*/ ```c ##### 延时函数模块delay.c ```c
/*
Date:2022.03.22
Author:
Target:提供延时
*/ #include<delay.h> //uS延时函数,输入参数t,无返回值,延时时间=t*2+5 uS
void DelayUs2x(unsigned int t)
{
while(--t);
} //mS延时函数,输入参数t,无返回值,延时时间1mS
void DelayMs(unsigned int t)
{
while(t--)
{
DelayUs2x(245);
DelayUs2x(245);
}
}
#ifndef _DELAY_H_
#define _DELAY_H_
void DelayUs2x(unsigned int t);
void DelayMs(unsigned int t);
#endif
数码管驱动函数模块display.c
/*
Date:2022.03.22
Author:
Target:数码管驱动
*/ #include<display.h>
#include<delay.h> #define DataPort P0 //定义数据端口 程序中遇到DataPort 则用P0 替换
//sbit LATCH1 = P2^0;//定义锁存使能端口 段锁存
//sbit LATCH2 = P2^3;// 位锁存
extern bit ReadADFlag;//extern声明,不是定义,外部变量
unsigned char code dofly_DuanMa[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};// 显示段码值0~9
unsigned char code dofly_WeiMa[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//分别对应相应的数码管点亮,即位码
unsigned char TempData[8]; //存储显示值的全局变量 /*
显示函数,动态扫描数码管,
参数FirstBit 表示需要显示的是第一位,比如0就是从第一个数码管显示,2就是从第三个数码管显示,
参数Num表示要显示的位数,也就是几个数码管显示,如要显示两位数,就应该输入2
*/
void Display(unsigned char FirstBit,unsigned char Num)
{
static unsigned char i = 0; DataPort=0; //清空数据,防止有交替重影
LATCH1=1; //段锁存
LATCH1=0; DataPort=dofly_WeiMa[i+FirstBit]; //取位码
LATCH2=1; //位锁存
LATCH2=0; DataPort=TempData[i]; //取显示数据,段码
LATCH1=1; //段锁存
LATCH1=0; i++;
if(i==Num)
i=0;
} /* 定时器初始化 */
void Init_Timer0(void)
{
TMOD |= 0x01; //使用模式1,16位定时器,使用"|"符号可以在使用多个定时器时不受影响
EA=1; //总中断打开
ET0=1; //定时器中断打开
TR0=1; //定时器开关打开
} /* 定时器中断子程序 */
void Init_Timer0_isr(void) interrupt 1
{
static unsigned int num;
TH0=(65536-2000)/256; //重新赋值 高位 低位
TL0=(65536-2000)%256; //可以理解成,提前减去2000,就是2ms倒计时 Display(0,8); // 调用数码管扫描
num++;
if(num==50) //中断50次,大致100ms
{
num = 0;
ReadADFlag=1;//AD标志位1
}
}
#include<reg52.h>

#ifndef __DISPLAY_H__
#define __DISPLAY_H__ #define DataPort P0 //定义数据端口 程序中遇到DataPort 则用P0 替换
sbit LATCH1=P2^2;//定义锁存使能端口 段锁存
sbit LATCH2=P2^3;// 位锁存 extern unsigned char TempData[8]; //存储显示值的全局变量
extern unsigned char code dofly_DuanMa[10]; void Display(unsigned char FirstBit,unsigned char Num); void Init_Timer0(void); #endif
i2c驱动函数模块i2c.c
/*
Date:2022.03.22
Author:
Target:i2c驱动
*/ #include <i2c.h>
#include <delay.h> #define _Nop() _nop_() //定义空指令 一个空指令大致为1us bit ack; sbit SDA=P2^1;//数据线
sbit SCL=P2^0;//时钟线 /* 启动i2c总线 */
void Start_I2c()
{
SDA=1; //发送起始条件的数据信号
_Nop();
SCL=1;
_Nop(); //起始条件建立时间大于4.7us,延时
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; //发送起始信号
_Nop(); //起始条件锁定时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; //钳住I2C总线,准备发送或接收数据
_Nop();
_Nop();
} /* 关闭i2c总线 */
void Stop_I2c()
{
SDA=0; //发送结束条件的数据信号
_Nop(); //发送结束条件的时钟信号
SCL=1; //结束条件建立时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; //发送I2C总线结束信号
_Nop();
_Nop();
_Nop();
_Nop();
} /*
发送字节数据
将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0 假)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
*/
void SendByte(unsigned char c)
{
unsigned char BitCnt; for(BitCnt=0;BitCnt<8;BitCnt++) //要传送的数据长度为8位
{
if((c<<BitCnt)&0x80) //把c左移7位,但c本身的值是不会变的
SDA=1; //判断发送位
else SDA=0;
_Nop();
SCL=1; //置时钟线为高,通知被控器开始接收数据位
_Nop();
_Nop(); //保证时钟高电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0;
} _Nop();
_Nop();
SDA=1; //8位发送完后释放数据线,准备接收应答位
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)
ack=0;
else ack=1; //判断是否接收到应答信号
SCL=0;
_Nop();
_Nop();
} /*
接受字节数据
用来接收从器件传来的数据,并判断总线错误(不发应答信号),发完后请用应答函数。
*/
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt; retc=0;
SDA=1; //置数据线为输入方式
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; //置时钟线为低,准备接收数据位
_Nop();
_Nop(); //时钟低电平周期大于4.7us
_Nop();
_Nop();
_Nop();
SCL=1; //置时钟线为高使数据线上数据有效
_Nop();
_Nop();
retc=retc<<1;
if(SDA==1)retc=retc+1; //读数据位,接收的数据位放入retc中
_Nop();
_Nop();
}
SCL=0;
_Nop();
_Nop();
return(retc);
} /*----------------------------------------------------------------
应答子函数
原型: void Ack_I2c(void); ----------------------------------------------------------------*/
/*void Ack_I2c(void)
{ SDA=0;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); //时钟低电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0; //清时钟线,钳住I2C总线以便继续接收
_Nop();
_Nop();
}*/
/*----------------------------------------------------------------
非应答子函数
原型: void NoAck_I2c(void); ----------------------------------------------------------------*/
void NoAck_I2c(void)
{ SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); //时钟低电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0; //清时钟线,钳住I2C总线以便继续接收
_Nop();
_Nop();
} /*----------------------------------------------------------------
向无子地址器件发送字节数据函数
函数原型: bit ISendByte(unsigned char sla,ucahr c);
功能: 从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit ISendByte(unsigned char sla,unsigned char c)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
*/ /*----------------------------------------------------------------
向有子地址器件发送多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit ISendStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;
for(i=0;i<no;i++)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(suba); //发送器件子地址
if(ack==0)return(0); SendByte(*s); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
DelayMs(1); //必须延时等待芯片内部自动处理数据完毕
s++;
suba++;
}
return(1);
}
*/
/*----------------------------------------------------------------
向无子地址器件读字节数据函数
函数原型: bit IRcvByte(unsigned char sla,ucahr *c);
功能: 从启动总线到发送地址,读数据,结束总线的全过程,从器件地
址sla,返回值在c.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit IRcvByte(unsigned char sla,unsigned char *c)
{
Start_I2c(); //启动总线
SendByte(sla+1); //发送器件地址
if(ack==0)return(0);
*c=RcvByte(); //读取数据
NoAck_I2c(); //发送非就答位
Stop_I2c(); //结束总线
return(1);
} */
/*----------------------------------------------------------------
向有子地址器件读取多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit IRcvStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i; Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(suba); //发送器件子地址
if(ack==0)return(0); Start_I2c();
SendByte(sla+1);
if(ack==0)return(0); for(i=0;i<no-1;i++)
{
*s=RcvByte(); //发送数据
Ack_I2c(); //发送就答位
s++;
}
*s=RcvByte();
NoAck_I2c(); //发送非应位
Stop_I2c(); //结束总线
return(1);
}
*/

#ifndef __I2C_H__
#define __I2C_H__ #include <reg52.h> //头文件的包含
#include <intrins.h> #define _Nop() _nop_() //定义空指令 /*------------------------------------------------
启动总线
------------------------------------------------*/
void Start_I2c();
/*------------------------------------------------
结束总线
------------------------------------------------*/
void Stop_I2c();
/*----------------------------------------------------------------
字节数据传送函数
函数原型: void SendByte(unsigned char c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0 假)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
------------------------------------------------------------------*/
void SendByte(unsigned char c);
/*----------------------------------------------------------------
字节数据传送函数
函数原型: unsigned char RcvByte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数。
------------------------------------------------------------------*/
unsigned char RcvByte();
/*----------------------------------------------------------------
应答子函数
原型: void Ack_I2c(void);
----------------------------------------------------------------*/
void Ack_I2c(void);
/*----------------------------------------------------------------
非应答子函数
原型: void NoAck_I2c(void);
----------------------------------------------------------------*/
void NoAck_I2c(void);
/*----------------------------------------------------------------
向无子地址器件发送字节数据函数
函数原型: bit ISendByte(unsigned char sla,ucahr c);
功能: 从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit ISendByte(unsigned char sla,unsigned char c); /*----------------------------------------------------------------
向有子地址器件发送多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit ISendStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no); /*----------------------------------------------------------------
向无子地址器件读字节数据函数
函数原型: bit IRcvByte(unsigned char sla,ucahr *c);
功能: 从启动总线到发送地址,读数据,结束总线的全过程,从器件地
址sla,返回值在c.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit IRcvByte(unsigned char sla,unsigned char *c);
/*----------------------------------------------------------------
向有子地址器件读取多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit IRcvStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no); #endif
串口通讯函数模块uart.c
#include <reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include "delay.h"
#include <uart.h> //串口初始化
void InitUART (void)
{ SCON = 0x50; // SCON: 模式 1, 8-bit UART, 使能接收
TMOD |= 0x20; // TMOD: timer 1, mode 2, 8-bit 重装
TH1 = 0xFD; // TH1: 重装值 9600 波特率 晶振 11.0592MHz
TR1 = 1; // TR1: timer 1 打开
EA = 1; //打开总中断
//ES = 1; //打开串口中断
} //发送一个字节
void SendByte1(unsigned char dat)
{
SBUF = dat;
while(!TI);
TI = 0;
} //发送一个字符串
void SendStr1(unsigned char *s)
{
while(*s!=0xff)// \0 表示字符串结束标志,通过检测是否字符串末尾
{
SendByte1(*s);
s++;
}
}
#ifndef __uart_H__
#define __uart_H__ void InitUART (void); void SendByte1(unsigned char dat); void SendStr1(unsigned char *s); #endif
上位机部分程序
namespace 微机上位机
{
public partial class Form1 : Form
{
//初始化
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.Text = "COM1";
comboBox2.Text = "9600";
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(senddata);
} //接受数据
private void senddata(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
length = serialPort1.BytesToRead; //获取缓冲区字节数
serialPort1.Read(R_data, 0, length);
this.Invoke(new EventHandler(display));
} //显示数据
private void display(object sender, EventArgs e)
{
double[] sample = new double[8];
sample[0] = (Convert.ToDouble(R_data[0])) / 10;
sample[1] = (Convert.ToDouble(R_data[1])) / 10;
sample[2] = (Convert.ToDouble(R_data[2])) / 10;
sample[3] = (Convert.ToDouble(R_data[3])) / 10;
sample[4] = (Convert.ToDouble(R_data[4])) / 10;
sample[5] = (Convert.ToDouble(R_data[5])) / 10;
sample[6] = (Convert.ToDouble(R_data[6])) / 10;
sample[7] = (Convert.ToDouble(R_data[7])) / 10; textBox1.Text = sample[0].ToString();
textBox2.Text = sample[1].ToString();
textBox3.Text = sample[2].ToString();
textBox4.Text = sample[3].ToString();
textBox5.Text = sample[0].ToString();
textBox6.Text = sample[1].ToString();
textBox7.Text = sample[2].ToString();
textBox8.Text = sample[3].ToString();
textBox9.Text = sample[4].ToString();
textBox10.Text = sample[5].ToString();
textBox11.Text = sample[6].ToString();
textBox12.Text = sample[7].ToString();
ovalShape1.FillColor = Color.LightGreen;
ovalShape2.FillColor = Color.LightGreen;
ovalShape3.FillColor = Color.LightGreen;
ovalShape4.FillColor = Color.LightGreen;
ovalShape5.FillColor = Color.LightGreen;
ovalShape6.FillColor = Color.LightGreen;
ovalShape7.FillColor = Color.LightGreen;
ovalShape8.FillColor = Color.LightGreen;
ovalShape9.FillColor = Color.LightGreen;
ovalShape10.FillColor = Color.LightGreen;
ovalShape11.FillColor = Color.LightGreen;
ovalShape12.FillColor = Color.LightGreen; } //开启串口
private void button1_Click_1(object sender, EventArgs e)
{
R_Flag = 1;
serialPort1.ReceivedBytesThreshold = 4;
serialPort1.RtsEnable = true;
if (serialPort1.IsOpen)
{
try
{
timer1.Stop();
serialPort1.Close();
button1.Text = "打开串口";
}
catch
{
MessageBox.Show("端口错误", "Error");
button1.Text = "关闭串口";
}
}
else
{
try
{
serialPort1.PortName = comboBox1.Text;
serialPort1.BaudRate = Convert.ToInt16(comboBox2.Text, 10);
serialPort1.Parity = System.IO.Ports.Parity.None;
serialPort1.StopBits = System.IO.Ports.StopBits.One;
serialPort1.DataBits = 8;
serialPort1.Open();
timer1.Start();
button1.Text = "关闭串口";
}
catch
{
MessageBox.Show("端口错误", "Error");
serialPort1.Close();
button1.Text = "打开串口";
}
}
} //配置报文长度
private void button2_Click(object sender, EventArgs e)
{
serialPort1.ReceivedBytesThreshold = Convert.ToInt16(textBox13.Text, 10);
} }
}

关键程序设计

延时模块

12MHz晶振,一个指令周期大约是1μs,这里封装了两个函数,一个μs级别的,一个ms级别的。

//uS延时函数,输入参数t,无返回值,延时时间=t*2+5 uS
void DelayUs2x(unsigned int t)
{
while(--t);
} //mS延时函数,输入参数t,无返回值,延时时间1mS
void DelayMs(unsigned int t)
{
while(t--)
{
DelayUs2x(245);
DelayUs2x(245);
}
}

数码管驱动模块

段码位码的的编写
unsigned char code dofly_DuanMa[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};// 显示段码值0~9
unsigned char code dofly_WeiMa[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//分别对应相应的数码管点亮,即位码
unsigned char TempData[8]; //存储显示值的全局变量

这里是使用到了一个小工具:段码值分别编写0-9的数值。

位码则是8位,比如fd代表1111 1101

使用的芯片属于锁存器,打开或者关闭制定锁存器,就可实现数据的显示。

定时器模块的调用
/*  定时器初始化  */
void Init_Timer0(void)
{
TMOD |= 0x01; //使用模式1,16位定时器,使用"|"符号可以在使用多个定时器时不受影响
EA=1; //总中断打开
ET0=1; //定时器中断打开
TR0=1; //定时器开关打开
} /* 定时器中断子程序 */
void Init_Timer0_isr(void) interrupt 1
{
static unsigned int num;
TH0=(65536-2000)/256; //重新赋值 高位 低位
TL0=(65536-2000)%256; //可以理解成,提前减去2000,就是2ms倒计时 Display(0,8); // 调用数码管扫描
num++;
if(num==50) //中断50次,大致100ms
{
num = 0;
ReadADFlag=1;//AD标志位1
}
}

定时器初始化,定时器有四个模式,这里选择模式1,十六位定时器/计数器。

把数码管扫描函数,放到中断函数中,每隔100ms扫描一次。

i2c模块的编写

数据线,时钟线,主要参考时序图,什么时候开始发送数据,什么时候结束发送数据。一个空指令是1μs。

/*  启动i2c总线  */
void Start_I2c()
{
SDA=1; //发送起始条件的数据信号
_Nop();
SCL=1;
_Nop(); //起始条件建立时间大于4.7us,延时
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; //发送起始信号
_Nop(); //起始条件锁定时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; //钳住I2C总线,准备发送或接收数据
_Nop();
_Nop();
} /* 关闭i2c总线 */
void Stop_I2c()
{
SDA=0; //发送结束条件的数据信号
_Nop(); //发送结束条件的时钟信号
SCL=1; //结束条件建立时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; //发送I2C总线结束信号
_Nop();
_Nop();
_Nop();
_Nop();
}

串口通讯模块的编写

发送字符串,要在最后设置一个校验位,就是告诉计算机,这次的数据发完了,停下吧。

//发送一个字符串
void SendStr1(unsigned char *s)
{
while(*s!=0xff)// ff表示数据发完了
{
SendByte1(*s);
s++;
}
}

主函数模块的数据数据处理

读取到的数据是一个0~256(二的八次方)之间的数,参考电压这里是5V,所以要把读取到的数带入公式中计算,然后分小数点前的数据,因为要在数码管显示,所以|0x80,加上小数点,小数点后直接保留就好。

//连续读5次,输入通道后多读几次,取最后一次值,以便读出稳定值
for(i=0;i<5;i++)
num0=ReadADC(0);
num0=num0*5*10/256;// x10表示把实际值扩大10,如4.5 变成 45 方便做下一步处理 x5 表示基准电压5V
TempData[0]=dofly_DuanMa[num0/10]|0x80;
TempData[1]=dofly_DuanMa[num0%10];

通讯部分数据处理,十六进制的数据报文。

 VoltData[0]=num0;
VoltData[1]=num1;
VoltData[2]=num2;
VoltData[3]=num3;
VoltData[4]=0xff; SendStr1(VoltData);
DelayMs(240);//延时循环发送
DelayMs(240);

CAN总线收发节点设计的更多相关文章

  1. TX2平台CAN总线收发功能的测试

    前言 项目实现过程中需要将获取的数据信息通过CAN总线传输到控制规划模块,本文主要介绍如何在TX2平台测试CAN总线的收发功能. TX2是英伟达旗下为嵌入式平台人工智能应用开发出的一个硬件平台,TX1 ...

  2. CAN总线多节点通信异常分析及解决

    一.CAN物理层特征 CAN收发器的作用是负责逻辑电平和信号电平之间的转换.即从CAN控制芯片输出逻辑电平到CAN收发器,然后经过CAN收发器内部转换将逻辑电平转换为差分信号输出到CAN总线上,CAN ...

  3. CAN总线中节点ID相同会怎样?

    CAN-bus网络中原则上不允许两个节点具有相同的ID段,但如果两个节点ID段相同会怎样呢? 实验前,我们首先要对CAN报文的结构组成.仲裁原理有清晰的认识. 一.CAN报文结构 目前使用最广泛的CA ...

  4. 由RS-232串口到PROFIBUS-DP总线的转换接口设计

    转自:http://gongkong.ofweek.com/2013-08/ART-310007-11001-28716256_2.html 1.PROFIBUS-DP网络协议 PROFIBUS的网络 ...

  5. 基于 FPGA 的 PCIE 总线 Linux 驱动设计

    硬件平台 Kintex ®-7 family of FPGAs Intel X86 软件平台 Linux 4.15.0-36-generic #39~16.04.1-Ubuntu Xilinx xap ...

  6. SOA实践之基于服务总线的设计

    在上文中,主要介绍了SOA的概念,什么叫做“服务”,“服务”应该具备哪些特性.本篇中,我将介绍SOA的一种很常见的设计实践--基于服务总线的设计. 基于服务总线的设计 基于总线的设计,借鉴了计算机内部 ...

  7. SOA服务总线设计

    背景 基于总线的设计,借鉴了计算机内部硬件组成的设计思想(通过总线传输数据).在分布式系统中,不同子系统之间需要实现相互通信和远程调用,比较直接的方式就是“点对点”的通信方式,但是这样会暴露出一些很明 ...

  8. 基于AHB总线的master读写设计(Verilog)

    一.AHB总线学习 1. AHB总线结构 如图所示,AHB总线系统利用中央多路选择机制实现主机与从机的互联问题.从图中可以看出,AHB总线结构主要可分为三部分:主机.从机.控制部分.控制部分由仲裁器. ...

  9. 探讨CAN总线的抗干扰能力

    探讨CAN总线的抗干扰能力 CAN总线经近20年的发展已步入壮年期,它不仅在汽车领域的应用占据一定优势,在其他工业应用上也生机勃勃.枝繁叶茂.究竟是什么原因使它这么成功?当人们发现它的局限性,又面临新 ...

随机推荐

  1. PMP之挣值管理(PV、EV、AC、SV、CV、SPI、CPI)的记忆方法

    挣值管理法中的PV.EV.AC.SV.CV.SPI.CPI这些英文简写相信把大家都搞得晕头转向的.在挣值管理法中,需要记忆理解的有三个参数:PV.AC.EV. PV:计划值,在即定时间点前计划完成活动 ...

  2. 嵌入式Linux应用开发完全手册之环境搭建

    @ 目录 0.下载源配置 1.电脑BIOS启动虚拟化 2.linux网卡查看与IP设置 3.windows NAT虚拟网络配置 4.修改 Ubuntu 的 mountd 端口 5.vim设置 6.Mo ...

  3. MapReduce: Simplified Data Processing on Large Clusters 翻译和理解

    MapReduce: Simplified Data Processing on Large Clusters 概述 MapReduce 是一种编程模型,用于处理和生成大型数据集的相应实现.用户定义一 ...

  4. 磁盘管理--如何在VMware上给centos7增加一块磁盘

    一. 实验环境 VMware Workstaion + Centos7 二.实验步骤 1.关闭虚拟机,添加磁盘 2.添加硬件向导 硬件类型 你要安装哪类硬件? 点击完成观察到已经成功添加磁盘 3.开启 ...

  5. Mod_Security 安装配置

    Mod_Security 安装配置 OS平台:Ubuntu 16 服务器:Apache2 一.更新Ubuntu apt-get更新源 sudo mv /etc/apt/sources.list /et ...

  6. loj6271 「长乐集训 2017 Day10」生成树求和 加强版(矩阵树定理,循环卷积)

    loj6271 「长乐集训 2017 Day10」生成树求和 加强版(矩阵树定理,循环卷积) loj 题解时间 首先想到先分开三进制下每一位,然后每一位分别求结果为0,1,2的树的个数. 然后考虑矩阵 ...

  7. Material Design with the Android Design Support Library

    Material Design with the Android Design Support Library 原文http://www.sitepoint.com/material-design-a ...

  8. ServletConfig对象和ServletContext对象有什么区别?

    一个Servlet对应有一个ServletConfig对象,可以用来读取初始化参数. 一个webapp对应一个ServletContext对象. ServletContext对象获取初始化定义的参数. ...

  9. 请解释Spring Bean的生命周期?

    首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy: Spring上下文中的Bean生命周期也类似,如下: (1)实例化Bean: 对于BeanFac ...

  10. innodb和myisam

    在Mysql数据库中,常用的引擎主要就是2个:Innodb和MyIASM.这篇文章将主要介绍这两个引擎,以及该如何去选择引擎,最后在提一下这2种引擎所使用的数据结构是什么. 首先介绍一下Innodb引 ...