UART、I2C、SPI

1 串口API

在这里插入图片描述
在Linux中,操作设备的统一接口就是:open/read/ioctl。
行规层(Line discipline):接受驱动程序的数据,再传递给应用程序;它可以对数据进行处理,如回写等。默认情况下,行规层收到数据就保存下来,直到收到回车再传给数据

UART编程套路:
1、open。
2、设置行规层:波特率、数据位、停止位、校验位、RAW模式,有数据就返回。
3、read/write。

串口结构体termios该结构体控制行规层,控制硬件

struct termios {
	tcflag_t c_iflag;		/* input mode flags */
	tcflag_t c_oflag;		/* output mode flags */
	tcflag_t c_cflag;		/* control mode flags */
	tcflag_t c_lflag;		/* local mode flags */
	cc_t c_cc[NCCS];		/* control characters */
	cc_t c_line;			/* line discipline (== c_cc[19]) */
	speed_t c_ispeed;		/* input speed */
	speed_t c_ospeed;		/* output speed */
};

可以使用ioctol,但是已经封装好了函数。
在这里插入图片描述

2 应用程序部分

2.1 open(set)_port:打开串口并设置属性

这里面传的参数com是设备节点;因此需要先通过驱动程序创建设备节点。

fd = open_port(argv[1]);
int open_port(char *com)
{
	int fd;
	//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
	fd = open(com, O_RDWR|O_NOCTTY);
    if (-1 == fd){
		return(-1);
    }
	
	  if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
	  {
			printf("fcntl failed!\n");
			return -1;
	  }
  
	  return fd;
}


iRet = set_opt(fd, 115200, 8, 'N', 1);
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
	struct termios newtio,oldtio;
	
	if ( tcgetattr( fd,&oldtio) != 0) { 
		perror("SetupSerial 1");
		return -1;
	}
	
	bzero( &newtio, sizeof( newtio ) );
	newtio.c_cflag |= CLOCAL | CREAD; 
	newtio.c_cflag &= ~CSIZE; 

	newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
	newtio.c_oflag  &= ~OPOST;   /*Output*/

	switch( nBits )
	{}

	switch( nEvent )
	{}
	
	newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
	newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
	                         * 比如VMIN设为10表示至少读到10个数据才返回,
	                         * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
	                         * 假设VTIME=1,表示: 
	                         *    10秒内一个数据都没有的话就返回
	                         *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
	                         */

fcntl(fd, F_SETFL, FNDELAY) 读数据时不等待,没有数据就返回0;
fcntl(fd, F_SETFL, 0) 读数据时,没有数据阻塞。
newtio.c_cc[VMIN] 读到VMIN个数据才返回,如果为0,有数据就返回。

2.2 main 函数

int main(int argc, char **argv)
{
	int fd;
	int iRet;
	char c;
	
	if (argc != 2)
	{
		printf("Usage: \n");
		printf("%s </dev/ttySAC1 or other>\n", argv[0]);
		return -1;
	}

	fd = open_port(argv[1]);
	if (fd < 0)
	{
		printf("open %s err!\n", argv[1]);
		return -1;
	}

	iRet = set_opt(fd, 115200, 8, 'N', 1);
	if (iRet)
	{
		printf("set port err!\n");
		return -1;
	}

	printf("Enter a char: ");
	while (1)
	{
		scanf("%c", &c);
		iRet = write(fd, &c, 1);
		iRet = read(fd, &c, 1);
		if (iRet == 1)
			printf("get: %02x %c\n", c, c);
		else
			printf("can not get data\n");
	}

	return 0;
}

3 行规层问题

如果最小字节数设置0:

newtio.c_cc[VMIN]  = 0; 

那么read的时候不管有没有数据都会返回,由于串口速度慢与程序执行速度,所以这个时候经常读不到数据;

需要大最小字节数设置为1

newtio.c_cc[VMIN]  = 1; 

这样,read函数起码需要收到1个数据才会返回。言外之意就是会等待串口通讯。

补充引脚、电路图等

确定设备节点对应的是那个引脚

在这里插入图片描述
可以查到ttymxc5的主次设备号,但是不能确定它是哪一个UART。

/proc 目录
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口,可以在运行时访问内核内部数据结构、改变内核设置。
用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。
另外,在/proc下还有三个很重要的目录:net,scsi和sys。Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi目录不存在。

在这里插入图片描述
可以通过proc文件系统查询到tty设备的信息,可以发现ttymcx0、tymcx2、tymcx5三个UART设备的映射地址;查阅芯片手册
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
它们分别对应UART1、3、6。

ttymxc5对应的引脚

在这里插入图片描述
通过查芯片手册UART6是这两个引脚的复用;
在这里插入图片描述
查看电路原理图找到对应的外部引脚(实际上还要往上查到核心板,但是这里省略了)。

  1. 这些引脚为什么叫这个名字?外部引脚在电路图上所用名字是该引脚的默认功能
  2. 当你借由驱动使用这个引脚时,驱动程序会改变这个引脚的寄存器,从而呈现UART6的特性
  3. UART的驱动在设备树中完成,如果想设置新的UART串口需要在设备树中编译。

SMbus

I2C的子集,相较于I2C有更高的要求。
SMbus必须要回应信号。

3 I2C

3.1 I2C主要的结构体

在这里插入图片描述
1、需要确定是哪一个I2C控制器;控制器 (I2C_adpater) 提供读写能力,有传输函数。
2、从设备结构i2c_client,主要是 设备地址addr,和主设备I2C_adpater
3、传输的数据i2c_msg结构体,主要是buf、length、addr(设备地址信息),一共两个msg,一个接一个收

3.2 i2ctools

三个问题:使用那个控制器、访问那个设备、读写什么数据

  • 怎么指定I2C控制器?
    i2c-dev.c提供为每个I2C控制器(I2C Bus、I2CAdapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等
    open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备

  • 怎么指定I2C设备:通过ioctl指定I2C设备的地址?
    1、ioctl(file, I2C_SLAVE, address)如果该设备已经有了对应的设备驱动程序,则返回失败
    2、ioctl(file, I2C_SLAVE_FORCE, address)如果该设备已经有了对应的设备驱动程序,但是还是想通过i2c-dev驱动来访问它则使用这个ioctl来指定I2C设备地址

  • 怎么传输数据?
    两种方式
    一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
    SMBus方式:ioctl(file, I2C_SMBUS, &args)

在这里插入图片描述

3.3 举个栗子AT24C20

  1. 确定设备的地址,查芯片手册
    在这里插入图片描述
    在这里插入图片描述
    因此地址为**1010000 0x50**

2、写操作
在这里插入图片描述
开始-设备地址-写的地址-数据
在这里插入图片描述
对应

3、读操作在这里插入图片描述
开始-设备地址-写-读的地址-开始-设备地址-数据
符合SMBus协议:i2c_smbus_write_byte_data()。

int main(int argc, char **argv)
{
	unsigned char dev_addr = 0x50; 设备地址
	unsigned char mem_addr = 0;	   读取的位置
	unsigned char buf[32];

	int file;
	char filename[20];
	unsigned char *str;

	/* 打开设备节点 */
	file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);

	/* 设置地址 */
	if (set_slave_addr(file, dev_addr, 1))
	{
		printf("can't set_slave_addr\n");
		return -1;
	}

4 SPI 使用GPIO模拟

在这里插入图片描述
在这里插入图片描述

  1. SS:片选引脚,确定选择到哪一个从设备。选定时保持低电平并传输数据;
  2. SCLK:时钟,上升沿还是下降沿采样;
  3. MOSI:输出,MISO:输入。都是对于主设备而言的输入还是输出

4.1 举个例子

解决问题:

  1. 使用GPIO模拟SPI
  2. 如何传输数据

4.1.1 GPIO模拟SPI

在这里插入图片描述
spi-gpio.c

#include"xxxx.h" /* 头文件包含寄存器信息 */

/* 使用GPIO模拟SPI */

static void SPI_GPIO_Init(void)
{
	/* 把所涉及的GPIO引脚设置为输入输出模式 */
	/* GPFCON即是GPIO的控制寄存器 */
	GPFCON &= ~(3<<(1*2));  //设置GPG2 为输出
	GPFCON |= ~(1<<(1*2));  //设置GPG4 为输入
	/* GPG2 FLASH_CSn	output
	GPG4 OLED_DC 		output
	GPG5 SPIMISO		input
	GPG6 SPIMOSI		output
	GPG7 SPICLK 		output
	 */
	 GPFCON |= ~(3<<(2*2) | 3<<(2*2)| 3<<(2*2)|3<<(2*2));
	.......
}

void SPIInit(void)
{
	SPI_GPIO_Init();
}

void SPISendByte(unsigned char val)
{
	int i;
	for(i = 0; i< 8; i++)
	{
		SPI_Set_CLK(0);  // 数据锁存是上升沿触发的,需要自己给时钟引脚创造上升沿
		SPI_Set_DO(val & 0x80); //保留第一位
		SPI_Set_CLK(1);	// 数据锁存是上升沿触发的,需要自己给时钟引脚创造上升沿
		val <<= 1;
	}
}

static void SPI_Set_DO(char val)
{
	/* GPGDAT第六位是输入,主机的输出  */
	if(val)
		GPGDAT |= (1<<6); //或1 保证绝对为1
	else
		GPGDAT &= ~(1<<6); //与0 保证绝对为0
	
}

关于静态函数:

  1. 静态函数它只能在声明它的文件当中可见,不能被其他文件可用。
  2. 可以直接使用类名+静态成员名访问此静态成员,因为静态成员先于类的声明而存在于内存,也可以根据类声明的对象来访问。而非静态成员必须实例化之后才会分配内存。

oled.c

void OLEDInit(void)
{
	/* 如何初始化OLED屏幕需要查看OLED芯片手册,里面会提供 */
	.......
}

static void OLEDwrite(void)
{
	/* OLED多出一个DC引脚,高电平表示传入的是命令,低电平为数据 
	   因此一定要看看芯片手册里面怎么写,芯片引脚干嘛的。
	*/
	OLED_Set_DC(0);/* 说明传入的是命令 */
	OLED_Set_CS(0);/* 片选引脚为低电平,表示芯片被选用上 */
	......
	OLED_Set_DC(1)
	OLED_Set_CS(1);
}

/* 本质上就是改变GPIO数据寄存器的值,来改变引脚输出数据 */
/* 将相关的输出GPIO引脚赋值,因此是GPFDAT寄存器组 */
static void OLED_Set_CS(char val)
{
	if(val)
		GPFDAT |= (1<<1);
	else
		GPFDAT &= ~(1<<1);
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值