单片机关于I2C的ack问题

uchar I2C_SendByte(uchar dat, uchar ack)函数其中与ack相关的部分是

while(I2C_SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
{
b++;
if(b > 200) ** //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
**{
I2C_SCL = 0;
I2C_Delay10us();
return 0;
}
}

I2C_SCL = 0;
I2C_Delay10us();

在main中有
I2C_SendByte(0xa0, 1);//发送写器件地址
I2C_SendByte(addr, 1);//发送要写入内存地址
I2C_SendByte(dat, 0)_; //发送数据

为什么发送数据时 ack为0了不为1,以及这个ack代表什么意义,ack不是从机将sda下拉的信号吗?

8个回答

while(I2C_SDA && (ack == 1)) 这里的含义是 根据传入的 ack 来决定是否需要 应答, I2C_SendByte(dat, 0)_; 传入的 参数是 0 , 就是不需要对方给应答 , 前面2条 指令需要对方应答 。

主机和从机是相对的,如果这里是单片机和E2PROM通信的话,MCU就是主机,E2PROM就从机。
ACK就应答的意思,/* while(I2C_SDA && (ack == 1))*/ 的意思就是等待数据线为高,即单片机把一个字节的数据发完了,再把数据线拉低,然后等待E2PROM把数据线拉高,如果拉高了就是从机(E2PROM)有应答,如果等待时间到了后没有拉高就是从机(E2PROM)无应答。
出现无应答的情况就是I2C总线出错或者从机(E2PROM)正在忙碌状态。

你说的是否是发送数据开始的时序电平,建议用示波器抓波形分析,除了开始和结束时序外,8位一应答。

这里是 ack 是传入的 参数, 属于 变量 , 不是 硬件 ack , 我也是很努力的在指导你哦, 希望采纳我的答案 。

1.从写入器件地址到写入内存地址再到最后写入数据,从程序上看这是一个同步IO操作,及你的I2C_SendByte()是一个同步IO操作函数,
前面MCU与外挂E2PROM进行的只是外挂E2PROM的寄存器操作(表明器件地址与内存地址),外挂E2PROM执行时间很快,所以代码作者
是可以也有必要确认ACK的,但是到了最后进行的外挂E2PROM写操作,是涉及到E2PROM擦除和写入的,一般NVM的擦除时间都比较长,
MCU一直在这里等待外挂E2PROM擦写数据是不明智的,所以作者可以选择做好的擦写数据不确认ACK,直接跳过。
题者可以考虑去了解下嵌入式相关异步IO,就明白了。【以上属于个人愚见,请参考】

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
其他相关推荐
模拟i2c收不到应答的问题

最近在用模拟i2c控制APDS9960芯片 但是i2c一直收不到应答信号 可以确定的是8位数据(高7位从机地址+写位)应该是正确的,但是第9位释放总先后就是收不到ack 测过波形  图如下 之前有过使用单片机的i2c模块控制同样的芯片,同样测过波形(ack后面还有其他数据,先不管) 实在不明白哪有不对的地方,希望各位大神能够指点指点 第一幅图是使用模拟i2c收不到ack的请况 第二幅图是原先使用单片机i2c模块控制有ack的情况 硬件平台有所变化![图片](https://img-ask.csdn.net/upload/201707/10/1499680060_138506.jpg)![图片](https://img-ask.csdn.net/upload/201707/10/1499680101_667516.jpg)

51单片机通过IIC向EEPROM存储和读取数据

我用51单片机通过IIC向EEPROM存储数据,然后再把数据读出来,再通过串口发送出去, 再用串口调试工具(eaglecom)查看数据。。。最后发现一次只能发送或者读取12个 字节,大于12个字节之后的数据全是0xff。不知道原因是什么。 ```#include <reg52.h> #include "delay.h" #include "Uart.h" #define ERROR 0 #define SUCCESS 1 #define MAX 50 sbit SDA = P1^7; sbit SCK = P1^6; unsigned char ack = 0; void IIC_Start() { SDA = 1; SCK = 1; delay_us(1); SDA = 0; delay_us(1); SCK = 0; } void IIC_Stop() { SDA = 0; SCK = 1; delay_us(1); SDA = 1; delay_us(1); SCK = 0; } void IIC_ACK() { SDA = 0; SCK = 1; delay_us(1); SCK = 0; } void IIC_NOACK() { SDA = 1; SCK = 1; delay_us(1); SCK = 0; } void IIC_SendByte(unsigned char temp) { unsigned char i; for(i = 0; i < 8; i++) { SDA = temp & 0x80; SCK = 1; delay_us(1); SCK = 0; delay_us(1); temp <<= 1; } SCK = 1; SDA = 1; delay_us(1); if(0 == SDA) { ack = 1; } else { ack = 0; } SCK = 0; } unsigned char IIC_RecvByte() { unsigned char i, temp; SDA = 1; for(i = 0; i < 8; i++) { SCK = 0; delay_us(1); SCK = 1; temp <<= 1; if(1 == SDA) { temp = temp + 1; } } SCK = 0; return temp; } unsigned char AT24C02_SendStr(unsigned char deceviceaddr, unsigned char romaaddr, unsigned char *s, unsigned char num) { unsigned char i; IIC_Start(); IIC_SendByte(deceviceaddr); if(0 == ack) { return ERROR; } IIC_SendByte(romaaddr); if(0 == ack) { return ERROR; } for(i = 0; i < num; i++) { IIC_SendByte(*s); if(0 == ack) { return ERROR; } s++; } IIC_Stop(); return SUCCESS; } unsigned char AT24C02_RecvStr(unsigned char deceviceaddr, unsigned char romaaddr, unsigned char *s, unsigned char num) { unsigned char i; IIC_Start(); IIC_SendByte(deceviceaddr); if(0 == ack) { return ERROR; } IIC_SendByte(romaaddr); if(0 == ack) { return ERROR; } IIC_Start(); IIC_SendByte(deceviceaddr + 1); if(0 == ack) { return ERROR; } for(i = 0; i < num - 1; i++) { *s = IIC_RecvByte(); IIC_ACK(); s++; } *s = IIC_RecvByte(); IIC_NOACK(); IIC_Stop(); return SUCCESS; } void main() { unsigned char i = 0; unsigned char temp[MAX + 1]; unsigned char str[2]; Uart_init(); for(i = 0; i < MAX; i++) { temp[i] = 7; } if(!AT24C02_SendStr(0xae, 100, temp, MAX)) { return; } delay_ms(200); for(i = MAX; i < (MAX + 1) ;i++) { temp[i] = 0; } delay_ms(200); if(!AT24C02_RecvStr(0xae, 100, temp, MAX)) { return; } delay_ms(200); Uart_SendStr(temp); while(1); } ``` 如果用IICSendByte()和RecvByte(),一个字节一个字节发送和读取的话是不会出错的,但是封装成IICSendStr()和RecvByte()的话,发送或者是接收的字符串长度不能超过12。这是为什么?

单片机,用AT24C02扩展,代码有问题。 存四个数到指定的AT24c02连续的4个单元地址中,再从此地址中连续读出4个数据显示到数码管上。

#include<reg51.h> #include<intrins.h> #define uchar unsigned char #define Addwr 0xa0; #define AddRd 0xa1; sbit SDA=P1^7; sbit SCL=P1^6; sbit WP=P1^5; void Delay(int len) { int i,j; for(i=0;i<len;i++) for(j=0;j<100;j++); } void Start(void) //起始 { SDA=1; SCL=1; _Nop();_Nop();_Nop();_Nop();_Nop();//延时5us SDA=0; _Nop();_Nop();_Nop();_Nop();_Nop();//延时5us SCL=0; } void Stop(void)//终止 { SDA=0; SCL=1; _Nop();_Nop();_Nop();_Nop();_Nop();//延时5us SDA=1; _Nop();_Nop();_Nop();_Nop();_Nop();//延时5us SDA=0; } void Ack(void) //应答位函数 { uchar i; SDA=1; SCL=1; _Nop();_Nop();_Nop();_Nop();//延时4us SCL=0; _Nop();_Nop();_Nop();_Nop();//延时4us } void NoAck(void)//非应答位函数 { SDA=1; SCL=1; _Nop();_Nop();_Nop();_Nop();//延时4us SCL=0; SDA=0; } void send(uchar Data) { uchar BitCounter=8; uchar ack; uchar temp; do { temp=Data; SCL=0; _Nop();_Nop();_Nop();_Nop(); if((temp&0x80)==0x80)//最高位是1 SDA=1;//发送的数据的左移1位 else SDA=0; SCL=1; temp=Data<<1; Data=temp; BitCounter--; } while(BitCounter); SCL=0; _Nop();_Nop();_Nop();_Nop(); SDA=1; _Nop();_Nop();_Nop();_Nop(); SCL=1; _Nop();_Nop();_Nop();_Nop(); if(SDA==1) ack=0; else ack=1; SCL=1; _Nop();_Nop(); } Read(void)//读一个字节的函数 { uchar temp=0; uchar temp1=0; uchar BitCounter=8; SDA=1; do { SCL=0; _Nop();_Nop();_Nop();_Nop(); SCL=1; _Nop();_Nop();_Nop();_Nop(); if(SDA) temp=temp|0x01; else temp=temp&0xfe; if(BitCounter=1) { temp=temp<<1; temp=temp1; } BitCounter--; } while(BitCounter); return(temp); } void WriteROM(uchar Data[],uchar Address,uchar Num) { uchar i; uchar ack; uchar *PData; PData=Data; for(i=0;i<Num;i++) { Start(); send(0xa0); if(ack==0) send(Address+i); if(ack==0) send(*(PData+i)); if(ack==0) Stop(); Delay(20); } } void main(void) { uchar Number[4]={9,9,7,5}; TMOD=0x11; TH0=0xee; TL0=0x00; TR0=1; ET0=1; EA=1; WP=1; WriteROM(Number[4],4); //将数组中的数据写入存储器 Delay(20); Numder[0]=0; Numder[1]=0; Numder[2]=0; Numder[3]=0;//将数组清0 ReadROM(Number[4],4); //写入存储器的数据读回到数组 while(1); } void WriteROM(uchar Data[],uchar Address,uchar Num) { uchar i; uchar *PData; PData=Data; for(i=0;i<Num;i++) { Start(); send(0xa0); if(ack==0) return(0); send(Address+i); if(ack==0) return(0); send(*(PData+i)); if(ack==0) return(0); Stop(); Delay(20); } } void ReadROM(uchar Data[],uchar Address,uchar Num) { uchar i; uchar *PData; PData=Data; for(i=0;i<Num;i++) { Start(); send(0xa0); if(ack==0) return(0); send(Address+i); if(ack==0) return(0); Star(); send(0xa1); if(ack==0) return(0); *(PData+i)=Read(); SCL=0; NoAck(); Stop(); } } void timer0() interrupt 1 { static unsigned char Bit=0; TH0=THCO; TL0=TLCO; Bit++; if(Bit>=4) Bit=0; P2|=0xf0; P0=Duan[Data[Bit]]; switch(Bit) { case 0: P24=0; break; case 1: P25=0; break; case 2: P26=0; break; case 3: P27=0; break; } }

C51单片机程控滤波器的程序

#include <reg52.h> #include "i2c.h" #include "delay.h" #include "display.h" #include "key.h" #define AddWr 0x90 #define AddRd 0x91 sbit P00=P0^0; sbit P01=P0^1; sbit P02=P0^2; sbit P03=P0^3; sbit P04=P0^4; extern bit ack; unsigned char ReadADC(unsigned char Chl); /*------------------------------------------------ 读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(0x40|Chl); if(ack==0)return(0); Start_I2c(); SendByte(AddWr+1); if(ack==0)return(0); Val=RcvByte(); NoAck_I2c(); Stop_I2c(); return(Val); } /*------------------------------------------------ 主程序 ------------------------------------------------*/ main() { unsigned char Chl; unsigned char num=0; P35=0; Init_Timer0(); P3 = 0x0F; //按键初始化 0000 1111 while (1) //主循环 { KeyScan(); /*按键控制电压输出0~2V变化 并在数码管上显示*/ DelayMs(100); Display(0,4); num=ReadADC(0);/*AD转换 I/O口输出实时电压变化*/ DelayMs(100); ShowData(ReadADC(Chl),TempData); Display(0,4); P00=SetDACOut(ReadADC(Chl)); /*5个I/O口分别输出5v电压*/ P01=SetDACOut(ReadADC(Chl)); P02=SetDACOut(ReadADC(Chl)); P03=SetDACOut(ReadADC(Chl)); P04=SetDACOut(ReadADC(Chl)); } } #include "i2c.h" #include "delay.h" #define _Nop() _nop_() //定义空指令 bit ack; //应答标志位 sbit SDA=P3^6; sbit SCL=P3^7; /*------------------------------------------------ 启动总线 ------------------------------------------------*/ void Start_I2c() { SDA=1; //发送起始条件的数据信号——SCL为高电平期间,SDA由高电平变为低电平 _Nop(); SCL=1; _Nop(); //起始条件建立时间大于4.7us,延时 _Nop(); _Nop(); _Nop(); _Nop(); SDA=0; //发送起始信号 _Nop(); //起始条件锁定时间大于4μ _Nop(); _Nop(); _Nop(); _Nop(); SCL=0; //钳住I2C总线,准备发送或接收数据 _Nop(); _Nop(); } /*------------------------------------------------ 结束总线 ------------------------------------------------*/ void Stop_I2c() { SDA=0; //发送结束条件的数据信号——SCL为高电平期间,SDA由低电平变为高电平 _Nop(); //发送结束条件的时钟信号 SCL=1; //结束条件建立时间大于4μ _Nop(); _Nop(); _Nop(); _Nop(); _Nop(); SDA=1; //发送I2C总线结束信号 _Nop(); _Nop(); _Nop(); _Nop(); } /*---------------------------------------------------------------- 字节数据传送函数 函数原型: void SendByte(unsigned char c); 功能: 将数据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)SDA=1; //判断发送位&是二进制运算符,c左移后和1000000相与, else SDA=0; //例如C=10101010,那么 1 0 1 0 1 0 1 0&1 0 0 0 0 0 0 0=1 0 0 0 0 0 0 0 此时if语句判断为真,则sda =1;发送出去 _Nop();//接着执行循环,C在左移一位后为 0 1 0 1 0 1 0 0和0x80相与结果为 0 0 0 0 0 0 0 0 if语句判断为0,则sda=0 发送出去。 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(); } /******************************************* * 设置DAC输出值 * DA转换 * ********************************************/ bit SetDACOut(unsigned char Val) { Start_I2c(); //启动总线 SendByte(0x90); //发送器件地址 if(ack==0) return 1; SendByte(0x40); //写入控制字节 if(ack==0) return 1; SendByte(Val); if(ack==0) return 1; Stop_I2c(); //结束总线 return 0; } /*---------------------------------------------------------------- 字节数据传送函数 函数原型: unsigned char RcvByte(); 功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号), 发完后请用应答函数。 ------------------------------------------------------------------*/ 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); } */ #include"key.h" #include"delay.h" #include"i2c.h" #include"display.h" sbit S17=P3^3; //独立按键 sbit S18=P3^2; sbit S19=P3^1; sbit S20=P3^0; unsigned char TempData[8]; /*------------------------------------------------ 按键扫描函数,返回扫描键值 程控放大倍数 ------------------------------------------------*/ unsigned char KeyScan() { static unsigned char volt = 0; if(!S20) //如果检测到低电平,说明按键按下 { /*sbit S20=P3^0; 依次-6*/ DelayMs(10); //延时去抖,一般10-20ms if(!S20) //再次确认按键是否按下,没有按下则退出 { while(!S20);//如果确认按下按键等待按键释放,没有释放则一直等待 { if(volt<255) /*不同按键输出的不同电压不懂*/ { volt=volt+500; SetDACOut(volt*0.51);//转换为AD输出值 TempData[8]=volt*0.51; ShowData((unsigned char)(volt*0.51),TempData); Display(0,4); } } return (1); } } if(!S19) //如果检测到低电平,说明按键按下 { /*依次+6*/ DelayMs(10); //延时去抖,一般10-20ms if(!S19) //再次确认按键是否按下,没有按下则退出 { while(!S19);//如果确认按下按键等待按键释放,没有释放则一直等待 { if(volt>0) { volt=volt-500; SetDACOut(volt*0.51); TempData[8]=volt*0.51; ShowData((unsigned char)(volt*0.51),TempData); Display(0,4); } } return (2); } } if(!S18) //如果检测到低电平,说明按键按下 { /*乱跳????*/ DelayMs(10); //延时去抖,一般10-20ms if(!S18) //再次确认按键是否按下,没有按下则退出 { while(!S18);//如果确认按下按键等待按键释放,没有释放则一直等待 { if(volt<255) { volt=volt+100; SetDACOut(volt*0.51); TempData[8]=volt*0.51; ShowData((unsigned char)(volt*0.51),TempData); Display(0,4); } } return (3); } } if(!S17) //如果检测到低电平,说明按键按下 { /*乱跳????*/ DelayMs(10); //延时去抖,一般10-20ms if(!S17) //再次确认按键是否按下,没有按下则退出 { while(!S17);//如果确认按下按键等待按键释放,没有释放则一直等待 { if(volt>0) { volt=volt-100; SetDACOut(volt*0.51); TempData[8]=volt*0.51; ShowData((unsigned char)(volt*0.51),TempData); Display(0,4); } } return (4); } } return 0; } 下载程序到单片机后,数码管显示值自动跳转 且无规律 不知道哪里出错了?菜鸟求大神解答 万分感谢!

AT24Cxx 读写问题 写出现不明白的问题

我用的是AT24C512来存储数据,但写地址的时候,写完16位的地址,然后再写一个8位数据; 在读出来的时候,读到的是高8位地址所指向的地址的数据,且数据等于地址的低8位,怎么也读不到16位地址所指向地址的数据。 //向指定地址写一个字节数据 Write_IIC(0x00,0x00,0x10); //向0x0000地址写数据0x10 Delay(5); Write_IIC(0x01,0x01,0x11); //向0x0101地址写数据0x11 Delay(5); Write_IIC(0x02,0x02,0x12); //向0x0202地址写数据0x12 Delay(5); Write_IIC(0x03,0x03,0x13); //向0x0303地址写数据0x13 Delay(5); Write_IIC(0x04,0x04,0x14); //向0x0404地址写数据0x14 Delay(5); //读指定地址的数据 Send_Data(Read_ICC(0x00,0x00)); //读0x0000地址的数据 Delay(1); Send_Data(Read_ICC(0x01,0x01)); //读0x0101地址的数据 Delay(1); Send_Data(Read_ICC(0x02,0x02)); //读0x0202地址的数据 Delay(1); Send_Data(Read_ICC(0x03,0x03)); //读0x0303地址的数据 Delay(1); Send_Data(Read_ICC(0x04,0x04)); //读0x0404地址的数据 Delay(1); 我是从串口接收数据,读出来的数据为: 01 02 03 04 14 证明只有最后一次写,才把0x14写到0x0404里面;或者最后一次是往地址0x04写了一次0x04,然后再写0x14,0x14把0x04覆盖了。 第一次往0x0000写0x10不成功,第二次把地址的低8位0x01写到高8位地址0x01里去了,第三次,第四次与第二次一样。 为什么会这样 uchar Read_ICC(uchar ADD_H,uchar ADD_L) { uchar dat; I2C_Start(); Send_Byte(0xa0); //发器件地址,并通知准备发送地址 Response(); //应答 Send_Byte(ADD_H); //发器件内部存储器地址高位 Response(); //应答 Send_Byte(ADD_L); //发器件内部存储器地址低位 Response(); //应答 I2C_Start(); Send_Byte(0xa1); //发器件地址,并通知准备读取数据 Response(); //应答 dat=Rcv_Byte(); //读取数据 I2C_Stop(); //停止I2C总线 return (dat); } void Write_ICC(uchar ADD_H,uchar ADD_L,uchar dat) { I2C_Start(); Send_Byte(0xa0); //发器件地址,并通知准备发送地址,要对应芯片地址 Response(); //应答 Send_Byte(ADD_H); //发器件内部存储器地址高位 Response(); //应答 Send_Byte(ADD_L); //发器件内部存储器地址低位 Response(); //应答 Send_Byte(dat); //写入数据 Response(); //应答 I2C_Stop(); //停止I2C总线 }

STC89C52利用PCF8591如何采集pulse sensor模拟信号

本人是单片机初学者 买了个pulse sensor,不过买家配套的单片机例程是STC12的(这个单片机有自带AD的),而我的STC89C52好像是没有自带AD的!而且我的学习板上的AD模块使用PCF8591驱动的,我不知道要利用PCF8591来采集模拟信号![图片说明](https://img-ask.csdn.net/upload/201604/27/1461725000_880078.png) ![图片说明](https://img-ask.csdn.net/upload/201604/27/1461725048_861594.png) 我有在网上查找了PCF8591AD转换代码,不过看不懂他里面哪里是从外部采集模拟信号的程序代码 下面的程序是我找的PCF8591的AD转换程序,还望大神帮我解释解释! #include<reg51.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int #define Write_PCF8591 0x90 #define Read_PCF8591 0x91 #define Control_Byte 0x40 #define LED P3 sbit SCL=P2^0; sbit SDA=P2^1; uchar num,temp; void delayms(uint z) { uint x,y; for(x=0;x<z;x++) for(y=0;y<110;y++); } void delay() { _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); } void init_IIC() { SDA=1; delay(); SCL=1; delay(); } void start() { SDA=1; delay(); SCL=1; delay(); SDA=0; delay(); } void stop() { SDA=0; delay(); SCL=1; delay(); SDA=1; delay(); } void ack() { uchar i=0; SCL=1; delay(); while((SDA==1)&&(i<250)) i++; SCL=0; delay(); } void noack() { SDA=1; delay(); SCL=1; delay(); SCL=0; delay(); } void Write_Byte(uchar dat) { uchar i; for(i=0;i<8;i++) { SCL=0; delay(); if(dat&0x80) { SDA=1; } else { SDA=0; } dat=dat<<1; SCL=1; delay(); } SCL=0; delay(); SDA=1; delay(); } uchar Read_Byte() { uchar i,dat; SCL=0; delay(); SDA=1; delay(); for(i=0;i<8;i++) { SCL=1; delay(); dat=dat<<1; if(SDA) { dat++; } SCL=0; delay(); } return dat; } void DAC_PCF8591(uchar add,uchar dat) { start(); Write_Byte(add); ack(); Write_Byte(Control_Byte); ack(); Write_Byte(dat); ack(); stop(); } uchar ADC_Read(uchar add0,uchar add1,uchar chl) { uchar dat; start(); Write_Byte(add0); ack(); Write_Byte(Control_Byte|chl); ack(); start(); Write_Byte(add1); ack(); dat=Read_Byte(); noack(); stop(); return dat; } void main() { init_IIC(); while(1) { DAC_PCF8591(Write_PCF8591,num); num++; delayms(30); temp=ADC_Read(Write_PCF8591,Read_PCF8591,0); LED=temp; } } 希望能告诉我以上程序哪里是接收模拟信号,我才能让Pulse sensor的信号传到STC89C52里! 小弟在这里感激不尽!

使用uc1701x的12864能驱动起来,为什么换为st7567的就不显示了?

这两款芯片的驱动程序好像是兼容的呀,而且是按照st7567液晶屏的使用说明写的 #include <reg52.H> #include <intrins.h> #include <Ctype.h> #include<stdio.h> #include "iic.h" sbit cs1=P1^1; sbit reset=P1^0; sbit rs=P3^0; sbit sclk=P3^1; sbit sid=P3^2; sbit Rom_IN=P1^2; //字库IC 接口定义:Rom_IN 就是字库IC 的SI sbit Rom_OUT=P1^3; //字库IC 接口定义:Rom_OUT就是字库IC 的SO sbit Rom_SCK=P1^4; //字库IC 接口定义:Rom_SCK就是字库IC 的SCK sbit Rom_CS=P1^5; //字库IC接口定义Rom_CS就是字库IC的CS# #define uchar unsigned char #define uint unsigned int #define ulong unsigned long char xianshi[5]; int court=0; uchar p;/*记录按键次数的变量*/ extern bit ack; void delay_us(int i) { int j,k; for(j=0;j<i;j++) for(k=0;k<1;k++); } void delay(int i) { int j,k; for(j=0;j<i;j++) for(k=0;k<110;k++); } void transfer_command(int data1) /*写指令*/ { char i; cs1=0; rs=0; for(i=0;i<8;i++) { sclk=0; delay_us(2); if(data1&0x80) sid=1; else sid=0; sclk=1; delay_us(2); data1=data1<<=1; } cs1=1; } void transfer_data_lcd(int data1)/*写数据*/ { char i; cs1=0; rs=1; for(i=0;i<8;i++) { sclk=0; delay_us(1); if(data1&0x80) sid=1; else sid=0; sclk=1; delay_us(1); data1=data1<<=1; } cs1=1; } void initial_lcd()/*液晶屏初始化*/ { //cs1=0; reset=0; //低电平复位 delay(100); reset=1; //复位完毕 delay(100); transfer_command(0xe2); //软复位 delay(5); transfer_command(0x2c); //升压步聚1 delay(5); transfer_command(0x2e); //升压步聚2 delay(5); transfer_command(0x2f); //升压步聚3 delay(5); transfer_command(0x24); //粗调对比度,可设置范围0x20~0x27 transfer_command(0x81); //微调对比度 transfer_command(0x1f); //0x1a,微调对比度的值,可设置范围0x00~0x3f 与上一条是双指令上一条的0x81不动,下面的才是值 transfer_command(0xa2); //1/7偏压比(bias)一般不改0xa2是1/9 transfer_command(0xc8); //行扫描顺序:从上到下 transfer_command(0xa0); //列扫描顺序:从左到右 transfer_command(0x40); //起始行:第一行开始 //transfer_command(0xA7); //反显 transfer_command(0xaf); //开显示 } void lcd_address(uchar page,uchar column) /*显示位置,行和列的设置*/ { column=column-1; //我们平常所说的第 1 列,在 LCD 驱动 IC 里是第 0 列。所以在这里减去 1. page=page-1; transfer_command(0xb0+page); //设置页地址。每页是 8 行。一个画面的 64 行被分成 8 个页。我们平常所说的第 1 页,在 LCD 驱动 IC 里是第 0 页,所以在这里减去1*/ transfer_command(((column>>4)&0x0f)+0x10); //设置列地址的高4 位 transfer_command(column&0x0f); //设置列地址的低4 位 } /*全屏清屏*/ void clear_screen() { unsigned char i,j; for(i=0;i<9;i++) { lcd_address(1+i,1); for(j=0;j<132;j++) { transfer_data_lcd(0x00); } } } //送指令到晶联讯字库IC void send_command_to_ROM( uchar datu ) { uchar i; for(i=0;i<8;i++ ) { Rom_SCK=0; delay_us(10); if(datu&0x80)Rom_IN = 1; else Rom_IN = 0; datu = datu<<1; Rom_SCK=1; delay_us(10); } } //从晶联讯字库IC 中取汉字或字符数据(1 个字节) static uchar get_data_from_ROM( ) { uchar i; uchar ret_data=0; for(i=0;i<8;i++) { Rom_OUT=1; Rom_SCK=0; //delay_us(1); ret_data=ret_data<<1; if( Rom_OUT ) ret_data=ret_data+1; else ret_data=ret_data+0; Rom_SCK=1; //delay_us(1); } return(ret_data); } //从指定地址读出数据写到液晶屏指定(page,column)座标中 void get_and_write_16x16(ulong fontaddr,uchar page,uchar column) { uchar i,j,disp_data; Rom_CS = 0; send_command_to_ROM(0x03); send_command_to_ROM((fontaddr&0xff0000)>>16); //地址的高8 位,共24 位 send_command_to_ROM((fontaddr&0xff00)>>8); //地址的中8 位,共24 位 send_command_to_ROM(fontaddr&0xff); //地址的低8 位,共24 位 for(j=0;j<2;j++) { lcd_address(page+j,column); for(i=0; i<16; i++ ) { disp_data=get_data_from_ROM(); transfer_data_lcd(disp_data); //写数据到LCD,每写完1 字节的数据后列地址自动加1 } } Rom_CS=1; } void get_and_write_8x16(ulong fontaddr,uchar page,uchar column) { uchar i,j,disp_data; Rom_CS = 0; send_command_to_ROM(0x03); send_command_to_ROM((fontaddr&0xff0000)>>16); //地址的高8 位,共24 位 send_command_to_ROM((fontaddr&0xff00)>>8); //地址的中8 位,共24 位 send_command_to_ROM(fontaddr&0xff); //地址的低8 位,共24 位 for(j=0;j<2;j++) { lcd_address(page+j,column); for(i=0; i<8; i++ ) { disp_data=get_data_from_ROM(); transfer_data_lcd(disp_data); //写数据到LCD,每写完1 字节的数据后列地址自动加1 } } Rom_CS=1; } //**************************************************************** ulong fontaddr=0; void display_GB2312_string(uchar page,uchar column,uchar *text) { uchar i= 0; while((text[i]>0x00)) { if(((text[i]>=0xb0) &&(text[i]<=0xf7))&&(text[i+1]>=0xa1)) { //国标简体(GB2312)汉字在晶联讯字库IC 中的地址由以下公式来计算: //Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;BaseAdd=0 //由于担心8 位单片机有乘法溢出问题,所以分三部取地址 fontaddr = (text[i]- 0xb0)*94; fontaddr += (text[i+1]-0xa1)+846; fontaddr = (ulong)(fontaddr*32); get_and_write_16x16(fontaddr,page,column); //从指定地址读出数据写到液晶屏指定(page,column)座标中 i+=2; column+=16; } else if(((text[i]>=0xa1) &&(text[i]<=0xa3))&&(text[i+1]>=0xa1)) { //国标简体(GB2312)15x16 点的字符在晶联讯字库IC 中的地址由以下公式来计算: //Address = ((MSB - 0xa1) * 94 + (LSB - 0xA1))*32+ BaseAdd;BaseAdd=0 //由于担心8 位单片机有乘法溢出问题,所以分三部取地址 fontaddr = (text[i]- 0xa1)*94; fontaddr += (text[i+1]-0xa1); fontaddr = (ulong)(fontaddr*32); get_and_write_16x16(fontaddr,page,column); //从指定地址读出数据写到液晶屏指定(page,column)座标中 i+=2; column+=16; } else if((text[i]>=0x20) &&(text[i]<=0x7e)) { fontaddr = (text[i]- 0x20); fontaddr = (unsigned long)(fontaddr*16); fontaddr = (unsigned long)(fontaddr+0x3cf80); get_and_write_8x16(fontaddr,page,column); //从指定地址读出数据写到液晶屏指定(page,column)座标中 i+=1; column+=8; } else i++; } } void timer0_chushihua()/*定时器初始化*/ { TMOD=0x01; TH0=0x00; TL0=0x00; EA=1; ET0=1; TR0=1; } uchar read(uchar ch ) { uchar temp=0; iic_start(); iic_writebyte(0x90);//确认芯片 if(ack==0)return(0); //iic_respons(); iic_writebyte(0x00|ch);//确认通道 iic_respons(); //读出数据,放进temp iic_start(); iic_writebyte(0x91); if(ack==0)return(0); //iic_respons(); /*如果采用iic_respons(),而不要if(ack==0)return(0),则显示是从0001开始,而不是0000,为什么?*/ temp=readbyte(); NoAck_I2c(); iic_stop(); return temp; } void dis(void)/*将采集的电压信号变为字符串,以便LCD显示*/ { int H, M, S, W; p=read(0); H=((p/1000)%10); M=(p/100)%10; S=(p/10)%10; W=(p%10); sprintf(xianshi, "%d%d%d%d", H, M, S,W); // 将整数转换成字符串 } void timer0() interrupt 1 { TH0=(65536-45536)/256;/*设定定时器初始值,定时20ms*/ TL0=(65536-45536)%256; dis(); court++; } void main(void) { iic_init(); initial_lcd();/*液晶屏初始化*/ clear_screen();/*液晶屏清屏*/ timer0_chushihua(); while(1) { if(court==1) { display_GB2312_string(7,33,xianshi);/*显示按键按过的次数*/ display_GB2312_string(5,1,"∑ 000000");/*显示固定字符和数字*/ //display_GB2312_string(1,1,"dshyhuf");/*显示固定字母*/ court=0; } } }

基于STC89c52cpu,以MLX90615传感器测温,无法读出温度数据

我要做以STC89C52单片机为基础,MLX90615为测温传感器的一个测温仪,我下载了网上的程序,一开始连接硬件,拷入程序后可以直接测温,但是有一天程序突然测不了温了,做了很多尝试,找了其他人写的程序,但是接上温度传感器单片机还是没有反应,原来能测温的程序最后lcd分别显示-273.150,727.970,77.730,67.490数值,然后就不动了 做过改动有改变SDA,SCL的上拉电阻,修改延时程序,使用其他的STC89C52开发板,更换传感器,重新焊接面包板连电路。 ![图片说明](https://img-ask.csdn.net/upload/201905/13/1557746533_515554.jpg) ![图片说明](https://img-ask.csdn.net/upload/201905/13/1557746580_853217.jpg) 只能求助大家了。以下是我找的程序,做了些修改,先只粘上有关MLX90615的程序: ``` #include "LCD1602.h" #include "reg52.h" #include "MLX90615.h" #include "delay.h" #include "stdio.h" sbit KEY=P3^2; // 定义按键 unsigned char s[20]; // 待显示的字符串 float temp; // 温度值 void main() { unsigned char RunStatus=0; // 是否测量标志 unsigned char SlaveAddress; // Contains device address unsigned char command; // Contains the access command unsigned int tdata; // Contains data value SlaveAddress=SA<<1; // Set device address command=RAM_Access|RAM_To; // Form RAM access command + RAM address MLX90615_init(); LCD1602_init(); write_string(0,0,"Press the KEY"); write_string(1,0,"to start"); while(1) { if(RunStatus) { tdata=MemRead(SlaveAddress,command); //Read memory temp = (float)tdata*0.02-273.15; clean_screen(); write_string(0,0,"Measuring"); sprintf(s,"%.3f C"); s[6]=0xDF; write_string(1,0,s); delay_ms(400); } if(!KEY) { delay_ms(20); if(!KEY) { if(RunStatus) { RunStatus=0; clean_screen(); write_string(0,0,"Press the KEY"); write_string(1,0,"to start"); } else { RunStatus=1; clean_screen(); write_string(0,0,"Measuring"); } while(!KEY); } } } } ``` ``` #include "MLX90615.h" #include "intrins.h" #include "delay.h" #include "UART.h" #define _NOP() _nop_() // 5us void delay_Tbuf() { unsigned char a,b; for(b=1;b>0;b--) for(a=1;a>0;a--); } void delay_Thd() { _nop_(); } void MLX90615_init(void) { mSDA_OUT; // Set SDA as Output mSCL_OUT; // Set SCL as Output mSDA_HIGH(); // bus free mSCL_HIGH(); } void START_bit(void) { mSDA_OUT; mSDA_HIGH(); // Set SDA line delay_Tbuf(); // Wait a few microseconds mSCL_HIGH(); // Set SCL line delay_Tbuf(); // Generate bus free time between Stop // and Start condition (Tbuf=4.7us min) mSDA_LOW(); // Clear SDA line delay_Tbuf(); // Hold time after (Repeated) Start // Condition. After this period, the first clock is generated. //(Thd:sta=4.0us min) mSCL_LOW(); // Clear SCL line delay_Tbuf(); // Wait a few microseconds } void STOP_bit(void) { mSDA_OUT; mSCL_LOW(); // Clear SCL line delay_Tbuf(); // Wait a few microseconds mSDA_LOW(); // Clear SDA line delay_Tbuf(); // Wait a few microseconds mSCL_HIGH(); // Set SCL line delay_Tbuf(); // Stop condition setup time(Tsu:sto=4.0us min) mSDA_HIGH(); // Set SDA line } unsigned char TX_byte(unsigned char Tx_buffer) { unsigned char Bit_counter; unsigned char Ack_bit; unsigned char bit_out; for(Bit_counter=8; Bit_counter; Bit_counter--) { if(Tx_buffer&0x80) bit_out=1; // If the current bit of Tx_buffer is 1 set bit_out else bit_out=0; // else clear bit_out send_bit(bit_out); // Send the current bit on SDA Tx_buffer<<=1; // Get next bit for checking } Ack_bit=Receive_bit(); // Get acknowledgment bit return Ack_bit; }// End of TX_bite() unsigned char RX_byte(unsigned char ack_nack) { unsigned char RX_buffer; unsigned char Bit_Counter; for(Bit_Counter=8; Bit_Counter; Bit_Counter--) { if(Receive_bit()) // Get a bit from the SDA line { RX_buffer <<= 1; // If the bit is HIGH save 1 in RX_buffer RX_buffer |=0x01; } else { RX_buffer <<= 1; // If the bit is LOW save 0 in RX_buffer RX_buffer &=0xfe; } } send_bit(ack_nack); // Sends acknowledgment bit return RX_buffer; } //--------------------------------------------------------------------------------------------- void send_bit(unsigned char bit_out) { mSDA_OUT; if(bit_out) mSDA_HIGH(); else mSDA_LOW(); delay_Thd(); // Tsu:dat = 250ns minimum mSCL_HIGH(); // Set SCL line delay_Tbuf(); // High Level of Clock Pulse------------------ mSCL_LOW(); // Clear SCL line delay_Tbuf(); // Low Level of Clock Pulse---------------------- // mSDA_HIGH(); // Master release SDA line , return; }//End of send_bit() //--------------------------------------------------------------------------------------------- unsigned char Receive_bit(void) { unsigned char Ack_bit; mSDA_IN; // SDA-input _NOP();_NOP();_NOP(); mSCL_HIGH(); // Set SCL line delay_Tbuf(); // High Level of Clock Pulse // if(P2Input(BIT2)) SDA=1; if(SDA) Ack_bit=1; // \ Read acknowledgment bit, save it in Ack_bit else Ack_bit=0; // / mSCL_LOW(); // Clear SCL line delay_Tbuf(); // Low Level of Clock Pulse return Ack_bit; }//End of Receive_bit unsigned int MemRead(unsigned char SlaveAddress,unsigned char command) { unsigned int tdata; // Data storage (DataH:DataL) unsigned char Pec; // PEC byte storage unsigned char DataL; // Low data byte storage unsigned char DataH; // High data byte storage unsigned char arr[6]; // Buffer for the sent bytes unsigned char PecReg; // Calculated PEC byte storage unsigned char ErrorCounter; // Defines the number of the attempts for communication with MLX90614 ErrorCounter=0x00; // Initialising of ErrorCounter do{ repeat: STOP_bit(); //If slave send NACK stop comunication --ErrorCounter; //Pre-decrement ErrorCounter if(!ErrorCounter){ //ErrorCounter=0? break; //Yes,go out from do-while{} } START_bit(); //Start condition if(TX_byte(SlaveAddress)){ //Send SlaveAddress goto repeat; //Repeat comunication again } if(TX_byte(command)){ //Send command goto repeat; //Repeat comunication again } START_bit(); //Repeated Start condition if(TX_byte(SlaveAddress)){ //Send SlaveAddress-------------------??? goto repeat; //Repeat comunication again } DataL=RX_byte(ACK); //Read low data,master must send ACK DataH=RX_byte(ACK); //Read high data,master must send ACK Pec=RX_byte(NACK); //Read PEC byte, master must send NACK STOP_bit(); //Stop condition arr[5]=SlaveAddress; // arr[4]=command; // arr[3]=SlaveAddress; //Load array arr arr[2]=DataL; // arr[1]=DataH; // arr[0]=0; // PecReg=PEC_calculation(arr);//Calculate CRC }while(PecReg != Pec); //If received and calculated CRC are equal go out from do-while{} *((unsigned char *)(&tdata))=DataH; // *((unsigned char *)(&tdata)+1)=DataL; //data=DataH:DataL return tdata; } unsigned char PEC_calculation(unsigned char pec[]) { unsigned char crc[6]; unsigned char BitPosition=47; unsigned char shift; unsigned char i; unsigned char j; unsigned char temp; do{ crc[5]=0; /* Load CRC value 0x000000000107 */ crc[4]=0; crc[3]=0; crc[2]=0; crc[1]=0x01; crc[0]=0x07; BitPosition=47; /* Set maximum bit position at 47 */ shift=0; //Find first 1 in the transmited message i=5; /* Set highest index */ j=0; while((pec[i]&(0x80>>j))==0 && i>0){ BitPosition--; if(j<7){ j++; } else{ j=0x00; i--; } }/*End of while */ shift=BitPosition-8; /*Get shift value for crc value*/ //Shift crc value while(shift){ for(i=5; i<0xFF; i--){ if((crc[i-1]&0x80) && (i>0)){ temp=1; } else{ temp=0; } crc[i]<<=1; crc[i]+=temp; }/*End of for*/ shift--; }/*End of while*/ //Exclusive OR between pec and crc for(i=0; i<=5; i++){ pec[i] ^=crc[i]; }/*End of for*/ }while(BitPosition>8);/*End of do-while*/ return pec[0]; }/*End of PEC_calculation*/ ``` ``` #ifndef __MLX90615_H #define __MLX90615_H #include "reg52.h" sbit SDA = P2^0; sbit SCL = P2^1; #define mSDA_IN do{} while(0) #define mSDA_OUT do{} while(0) #define mSCL_IN do{} while(0) #define mSCL_OUT do{} while(0) #define ACK 0 #define NACK 1 //MLX90614 constants #define SA 0x00 // Slave address #define DEFAULT_SA 0x5B // Default Slave address #define RAM_Access 0x20 // RAM access command #define EEPROM_Access 0x10 // EEPROM access command #define RAM_Ta 0x06 // Ta address in the ram #define RAM_To 0x07 // To address in the ram //*PROTOTYPES********************************************************************************** void start_bit(void); void STOP_bit(void); void TX_byte(unsigned char Tx_buffer); unsigned char RX_byte(unsigned char ack_nack); void send_bit(unsigned char bit_out); unsigned char Receive_bit(void); void MLX90615_init(void); unsigned int MEM_READ(unsigned char slave_addR, unsigned char cmdR); void SendRequest(void); void DummyCommand(unsigned char byte); unsigned int *CALTEMP(unsigned long int TEMP); unsigned char PEC_cal(unsigned char pec[],int n); void Delay(unsigned int N); #endif ```

基于lpc2114做sht11在lcd1602显示代码调试驱执行不了,求大神指教

/**************************************************************************** * 基于LPC2114做的一个SHT11温湿度传感器在LCD1602显示 ****************************************************************************/ #include "config.h" #include <math.h> #define RS (1<<8) #define RW (1<<9) #define E (1<<10) #define BUSY (1<<7) #define SCK (1<<16) #define DATA (1<<17) //#define DBPort 0x80 #define FIRSTLINE 0x80 #define SECONDLINE 0xc0 #define uchar unsigned char enum {TEMP,HUMI}; #define noACK 0 //继续传输数据,用于判断是否结束通讯 #define ACK 1 //结束数据传输; //地址 命令 读/写 #define STATUS_REG_W 0x06 //000 0011 0 #define STATUS_REG_R 0x07 //000 0011 1 #define MEASURE_TEMP 0x03 //000 0001 1 #define MEASURE_HUMI 0x05 //000 0010 1 #define RESET 0x1e //000 1111 0 unsigned char str1[]="TEMP: %C"; unsigned char str2[]="HUMI: %RH"; unsigned char CRC_Table[]; //长软件延时,时间为ms void DelayMs(unsigned char ms) { unsigned int i; for(;ms>0;ms--) for(i=0;i<50000;i++); } void delay_us(unsigned int nus) { unsigned int i; unsigned char j; for(i = 0; i < nus; i++) { for(j = 0; j < 6; j++){} } } void delay_ms(unsigned int nms) { unsigned int i; for(i = 0; i < nms; i++) { delay_us(2); } } /******************************************************* * LCD16021函数 ********************************************************/ // 检查LCD是否忙碌,忙碌等待,不忙了返回 void CheckBusy() { IO0DIR = 0x700;//0-7输入,8-10输出 ,读状态 while(1) { //读状态,RS=L,RW=H,E=H IO0CLR = RS; IO0SET = RW; IO0SET = E; if(!(IO0PIN & BUSY)) //如果不忙了,返回 { break; } IO0CLR = E;//如果忙了,清空E标志,继续等待 } IO0DIR = 0x7ff;//IO口全部恢复为输出 } //写命令(指令)函数 void WriteCommand(unsigned char command) { CheckBusy(); //检查是否BUSY ,忙碌一直在此等待 //写指令,RS=L,RW=L,然后D0~D7=指令码,E=高脉冲发送命令 IO0CLR = RS; IO0CLR = RW; IO0CLR = 0xff;//清空端口 IO0SET = command; IO0SET = E; IO0CLR = E; } //写数据函数 //显示数据之前要先设定显示位置 void WriteData(unsigned char data) { CheckBusy(); //检查是否BUSY ,忙碌一直在此等待 //写指令,RS=H,RW=L,然后D0~D7=指令码,E=高脉冲发送命令 IO0SET = RS; IO0CLR = RW; IO0CLR = 0xff;//清空端口 IO0SET = data; IO0SET = E;//发送数据 IO0CLR = E;//恢复,以便继续使用 } //功能:设置显示位置 //坐标自动加1 void Set_XY(unsigned char x,unsigned char y) { switch(x) { case 0: y+=FIRSTLINE; break; case 1: y+=SECONDLINE; break; } WriteCommand(y); //显示坐标更新 设置数据指针 } //功能:在指定位置显示单个字符 void DisplayChar(unsigned char x,unsigned char y,unsigned char data) { Set_XY(x,y); WriteData(data); } // 功能:在指定位置显示字符串 void DisplayString(unsigned char x,unsigned char y,unsigned char *str) { Set_XY(x,y); while(*str) { WriteData(*str); str++; } } // 功能:初始化LCD1602函数 void InitLcd() { //写命令 WriteCommand(0x01);//显示清屏,数据指针、所有显示清零 WriteCommand(0x38);//设置16*2显示,5*7点阵,8位数据接口 WriteCommand(0x0f);//显示开及光标设置显示器开、光标开、闪烁开 } /******************************************************* * SHT11函数 ********************************************************/ //启动传输 void START_SHT11() { IO1SET = SCK; IO1SET = DATA; delay_us(5); IO1CLR = DATA; delay_us(5); IO1CLR = SCK; delay_us(5); IO1SET = SCK; delay_us(5); IO1SET = DATA; } //应答信号 void SHT11_answer() { IO1CLR = SCK; delay_us(5); IO1SET = SCK; delay_us(5); //while( IO1SET = DATA); IO1DIR = (~0x3ffff); //为输入功能 //IO1PIN = IO1PIN | 0x00020000 while(IO1PIN & 0x00020000); IO1CLR = SCK; delay_us(5); IO1SET = DATA; } //单片机向SHT11发送应答信号 void MCU_answer() { IO1CLR = SCK; delay_us(5); IO1CLR = DATA; delay_us(5); IO1SET = SCK; delay_us(5); IO1CLR = SCK; delay_us(5); IO1SET = DATA; } void SHT11_DATA_READY() { IO1DIR = (~0x3ffff); //为输入功能 //IO1PIN = IO1PIN | 0x00020000 while(IO1PIN & 0x00020000); //while( IO1SET = DATA); } //连接复位 void reconnect() { unsigned char i; IO1SET = DATA; IO1SET = SCK; //准备 for(i = 0; i < 9; i++) //DATA保持高,SCK时钟触发9次,发送启动传输,通迅即复位 { IO1SET = SCK; IO1CLR = SCK; } START_SHT11(); //启动传输 } //写字节程序 void write_sht11(unsigned char cmd) { unsigned char i; //IO1DIR = DATA; for (i = 0; i < 8; i++) { IO1CLR = SCK; if(cmd & 0x80) IO1SET = DATA; else IO1CLR = DATA; delay_us(5); IO1SET = SCK; cmd = cmd << 1; delay_us(5); IO1CLR = SCK; } IO1SET = DATA; //释放总线 } //读字节程序 unsigned char read_sht11(void) { unsigned char j, temp = 0, data = 0; //IO1DIR = (~DATA); IO1SET = DATA; //释放数据线 for(j = 0; j < 8; j++) { IO1SET = SCK; data = data << 1; delay_us(5); IO1DIR = (~0x3ffff); //为输入功能 IO1PIN = IO1PIN | 0x00020000; temp = ((IO1PIN & 0x00020000) >> 17); if(temp) data = (data | 0x01); //读一位数据线的值 else data = (data & 0xfe); IO1CLR = SCK; delay_us(5); } IO1SET = DATA; return(data); } //通信结束 void SHT11_end() { IO1SET = DATA; delay_us(5); IO1SET = SCK; delay_us(5); IO1CLR = SCK; delay_us(5); } //寄存器设置函数 void SHT11_Write_Register() { START_SHT11(); write_sht11(0x06); SHT11_answer(); write_sht11(0x00); //12 and 14 bit temperrature SHT11_answer(); } //温湿度测量 unsigned short SHT11_Measure(unsigned char command) { unsigned short dat_1=0; unsigned char data_high,data_low,CRC; unsigned char val,tt,i; START_SHT11(); write_sht11(command); SHT11_answer(); SHT11_DATA_READY(); data_high = read_sht11(); MCU_answer(); data_low = read_sht11(); MCU_answer(); CRC = read_sht11(); SHT11_end(); dat_1 = ( dat_1 | data_high); dat_1 = ((dat_1<<8) | data_low); val = CRC_Table[(command^0x00)]; val = CRC_Table[(val^data_high)]; val = CRC_Table[(val^data_low)]; for( i = 0; i < 8; i++) { tt = tt << 1; tt |= val & 0x01; val=val >> 1; } val = tt; if(val == CRC) return(dat_1); else return(2); } //温度计算校正 float SHT11_Convert_Tempeture14bit(unsigned int dat_1) { float tempeture1,flag_tempeture; tempeture1=-40+0.01*dat_1; if(tempeture1>100.0) { flag_tempeture = 1; } else if(tempeture1 < 0.0) { flag_tempeture = 1; } else { flag_tempeture = 0; } return(tempeture1); } //湿度计算校正 float SHT11_Convert_Humidity12bit(unsigned int dat,float temp) { float RHline,RHtrue,flag_humidity; RHline=-4+0.0405*dat-0.0000028*dat*dat; RHtrue=(temp-25)*(0.01+0.00008*dat)+RHline; if(RHtrue<10.0) { flag_humidity=1; } else { flag_humidity=0; } return(RHtrue); } void main() { unsigned int temp,humi; unsigned char wendu[6]; //用于记录温度 unsigned char shidu[6]; //用于记录湿度 float tempeture1,RHtrue; unsigned short dat_1=0; PINSEL0 &= 0xff00000; //低11位为IO功能 IO0DIR = 0x7ff; //为输出功能 IO0CLR = 0x7ff; //清零 PINSEL1 &= 0xfffffff0; //低11位为IO功能 IO1DIR = 0x3ffff; //P1.16为输出功能 InitLcd(); //初始化LCD1602 //显示数据 DisplayString(0,0,str1); DisplayString(1,0,str2); reconnect(); //复位启动传输 while(1) { dat_1 = SHT11_Measure(MEASURE_TEMP); //温度测量 if( dat_1 = 2) { reconnect(); //如果发生错误,系统复位 tempeture1 = SHT11_Convert_Tempeture14bit(dat_1); } else { temp = tempeture1 * 10; wendu[0]=temp%10+'0'; //温度百位 wendu[1]=temp/10+'0'; //温度十位 wendu[2]=temp%100/10+'0'; //温度个位 wendu[3]=0x2E; //小数点 wendu[4]=temp%10+'0'; //温度小数点后第一位 DisplayString(0,5,wendu); //设置温度显示位置,输出温度 } dat_1 = SHT11_Measure(MEASURE_HUMI); //湿度测量 if( dat_1 = 2) { reconnect(); //如果发生错误,系统复位 RHtrue=SHT11_Convert_Humidity12bit(dat_1,tempeture1); } else { humi = RHtrue * 10; shidu[0]=humi%10+'0'; //湿度百位 shidu[1]=humi/10+'0'; //湿度十位 shidu[2]=humi%100/10+'0'; //湿度个位 shidu[3]=0x2E; //小数点 shidu[4]=humi%10+'0'; //湿度小数点后第一位 DisplayString(1,5,shidu); //设置湿度显示位置,输出湿度 } DelayMs(80); //等待足够长的时间,以现行下一次转换 } return 0; }

大学四年自学走来,这些私藏的实用工具/学习网站我贡献出来了

大学四年,看课本是不可能一直看课本的了,对于学习,特别是自学,善于搜索网上的一些资源来辅助,还是非常有必要的,下面我就把这几年私藏的各种资源,网站贡献出来给你们。主要有:电子书搜索、实用工具、在线视频学习网站、非视频学习网站、软件下载、面试/求职必备网站。 注意:文中提到的所有资源,文末我都给你整理好了,你们只管拿去,如果觉得不错,转发、分享就是最大的支持了。 一、电子书搜索 对于大部分程序员...

在中国程序员是青春饭吗?

今年,我也32了 ,为了不给大家误导,咨询了猎头、圈内好友,以及年过35岁的几位老程序员……舍了老脸去揭人家伤疤……希望能给大家以帮助,记得帮我点赞哦。 目录: 你以为的人生 一次又一次的伤害 猎头界的真相 如何应对互联网行业的「中年危机」 一、你以为的人生 刚入行时,拿着傲人的工资,想着好好干,以为我们的人生是这样的: 等真到了那一天,你会发现,你的人生很可能是这样的: ...

程序员请照顾好自己,周末病魔差点一套带走我。

程序员在一个周末的时间,得了重病,差点当场去世,还好及时挽救回来了。

ArrayList源码分析(入门篇)

ArrayList源码分析 前言: 写这篇博客的主要原因是,在我上一次参加千牵科技Java实习生面试时,有被面试官问到ArrayList为什么查找的速度较快,插入和删除的速度较慢?当时我回答得不好,很大的一部分原因是因为我没有阅读过ArrayList源码,虽然最后收到Offer了,但我拒绝了,打算寒假学得再深入些再广泛些,下学期开学后再去投递其他更好的公司。为了更加深入理解ArrayList,也为

我以为我学懂了数据结构,直到看了这个导图才发现,我错了

数据结构与算法思维导图

String s = new String(" a ") 到底产生几个对象?

老生常谈的一个梗,到2020了还在争论,你们一天天的,哎哎哎,我不是针对你一个,我是说在座的各位都是人才! 上图红色的这3个箭头,对于通过new产生一个字符串(”宜春”)时,会先去常量池中查找是否已经有了”宜春”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”宜春”对象的拷贝对象。 也就是说准确答案是产生了一个或两个对象,如果常量池中原来没有 ”宜春” ,就是两个。...

技术大佬:我去,你写的 switch 语句也太老土了吧

昨天早上通过远程的方式 review 了两名新来同事的代码,大部分代码都写得很漂亮,严谨的同时注释也很到位,这令我非常满意。但当我看到他们当中有一个人写的 switch 语句时,还是忍不住破口大骂:“我擦,小王,你丫写的 switch 语句也太老土了吧!” 来看看小王写的代码吧,看完不要骂我装逼啊。 private static String createPlayer(PlayerTypes p...

和黑客斗争的 6 天!

互联网公司工作,很难避免不和黑客们打交道,我呆过的两家互联网公司,几乎每月每天每分钟都有黑客在公司网站上扫描。有的是寻找 Sql 注入的缺口,有的是寻找线上服务器可能存在的漏洞,大部分都...

讲一个程序员如何副业月赚三万的真实故事

loonggg读完需要3分钟速读仅需 1 分钟大家好,我是你们的校长。我之前讲过,这年头,只要肯动脑,肯行动,程序员凭借自己的技术,赚钱的方式还是有很多种的。仅仅靠在公司出卖自己的劳动时...

上班一个月,后悔当初着急入职的选择了

最近有个老铁,告诉我说,上班一个月,后悔当初着急入职现在公司了。他之前在美图做手机研发,今年美图那边今年也有一波组织优化调整,他是其中一个,在协商离职后,当时捉急找工作上班,因为有房贷供着,不能没有收入来源。所以匆忙选了一家公司,实际上是一个大型外包公司,主要派遣给其他手机厂商做外包项目。**当时承诺待遇还不错,所以就立马入职去上班了。但是后面入职后,发现薪酬待遇这块并不是HR所说那样,那个HR自...

女程序员,为什么比男程序员少???

昨天看到一档综艺节目,讨论了两个话题:(1)中国学生的数学成绩,平均下来看,会比国外好?为什么?(2)男生的数学成绩,平均下来看,会比女生好?为什么?同时,我又联想到了一个技术圈经常讨...

副业收入是我做程序媛的3倍,工作外的B面人生是怎样的?

提到“程序员”,多数人脑海里首先想到的大约是:为人木讷、薪水超高、工作枯燥…… 然而,当离开工作岗位,撕去层层标签,脱下“程序员”这身外套,有的人生动又有趣,马上展现出了完全不同的A/B面人生! 不论是简单的爱好,还是正经的副业,他们都干得同样出色。偶尔,还能和程序员的特质结合,产生奇妙的“化学反应”。 @Charlotte:平日素颜示人,周末美妆博主 大家都以为程序媛也个个不修边幅,但我们也许...

MySQL数据库面试题(2020最新版)

文章目录数据库基础知识为什么要使用数据库什么是SQL?什么是MySQL?数据库三大范式是什么mysql有关权限的表都有哪几个MySQL的binlog有有几种录入格式?分别有什么区别?数据类型mysql有哪些数据类型引擎MySQL存储引擎MyISAM与InnoDB区别MyISAM索引与InnoDB索引的区别?InnoDB引擎的4大特性存储引擎选择索引什么是索引?索引有哪些优缺点?索引使用场景(重点)...

如果你是老板,你会不会踢了这样的员工?

有个好朋友ZS,是技术总监,昨天问我:“有一个老下属,跟了我很多年,做事勤勤恳恳,主动性也很好。但随着公司的发展,他的进步速度,跟不上团队的步伐了,有点...

我入职阿里后,才知道原来简历这么写

私下里,有不少读者问我:“二哥,如何才能写出一份专业的技术简历呢?我总感觉自己写的简历太烂了,所以投了无数份,都石沉大海了。”说实话,我自己好多年没有写过简历了,但我认识的一个同行,他在阿里,给我说了一些他当年写简历的方法论,我感觉太牛逼了,实在是忍不住,就分享了出来,希望能够帮助到你。 01、简历的本质 作为简历的撰写者,你必须要搞清楚一点,简历的本质是什么,它就是为了来销售你的价值主张的。往深...

玩转springboot启动banner定义所得

最近接手了一个springboot项目,不是不熟悉这个框架,启动时打印的信息吸引了我。 这不是我熟悉的常用springboot的打印信息啊,我打开自己的项目: 还真是的,不用默认的感觉也挺高大上的。一时兴起,就去研究了一下源代码,还正是有些收获,稍后我会总结一下。正常情况下做为一个老程序员,是不会对这种小儿科感兴趣的,不就是一个控制台打印嘛。哈哈! 于是出于最初的好奇,研究了项目的源代码。看到

带了6个月的徒弟当了面试官,而身为高级工程师的我天天修Bug......

即将毕业的应届毕业生一枚,现在只拿到了两家offer,但最近听到一些消息,其中一个offer,我这个组据说客户很少,很有可能整组被裁掉。 想问大家: 如果我刚入职这个组就被裁了怎么办呢? 大家都是什么时候知道自己要被裁了的? 面试软技能指导: BQ/Project/Resume 试听内容: 除了刷题,还有哪些技能是拿到offer不可或缺的要素 如何提升面试软实力:简历, 行为面试,沟通能...

离职半年了,老东家又发 offer,回不回?

有小伙伴问松哥这个问题,他在上海某公司,在离职了几个月后,前公司的领导联系到他,希望他能够返聘回去,他很纠结要不要回去? 俗话说好马不吃回头草,但是这个小伙伴既然感到纠结了,我觉得至少说明了两个问题:1.曾经的公司还不错;2.现在的日子也不是很如意。否则应该就不会纠结了。 老实说,松哥之前也有过类似的经历,今天就来和小伙伴们聊聊回头草到底吃不吃。 首先一个基本观点,就是离职了也没必要和老东家弄的苦...

男生更看重女生的身材脸蛋,还是思想?

往往,我们看不进去大段大段的逻辑。深刻的哲理,往往短而精悍,一阵见血。问:产品经理挺漂亮的,有点心动,但不知道合不合得来。男生更看重女生的身材脸蛋,还是...

为什么程序员做外包会被瞧不起?

二哥,有个事想询问下您的意见,您觉得应届生值得去外包吗?公司虽然挺大的,中xx,但待遇感觉挺低,马上要报到,挺纠结的。

当HR压你价,说你只值7K,你该怎么回答?

当HR压你价,说你只值7K时,你可以流畅地回答,记住,是流畅,不能犹豫。 礼貌地说:“7K是吗?了解了。嗯~其实我对贵司的面试官印象很好。只不过,现在我的手头上已经有一份11K的offer。来面试,主要也是自己对贵司挺有兴趣的,所以过来看看……”(未完) 这段话主要是陪HR互诈的同时,从公司兴趣,公司职员印象上,都给予对方正面的肯定,既能提升HR的好感度,又能让谈判气氛融洽,为后面的发挥留足空间。...

面试:第十六章:Java中级开发(16k)

HashMap底层实现原理,红黑树,B+树,B树的结构原理 Spring的AOP和IOC是什么?它们常见的使用场景有哪些?Spring事务,事务的属性,传播行为,数据库隔离级别 Spring和SpringMVC,MyBatis以及SpringBoot的注解分别有哪些?SpringMVC的工作原理,SpringBoot框架的优点,MyBatis框架的优点 SpringCould组件有哪些,他们...

面试阿里p7,被按在地上摩擦,鬼知道我经历了什么?

面试阿里p7被问到的问题(当时我只知道第一个):@Conditional是做什么的?@Conditional多个条件是什么逻辑关系?条件判断在什么时候执...

终于懂了TCP和UDP协议区别

终于懂了TCP和UDP协议区别

你打算用Java 8一辈子都不打算升级到Java 14,真香

我们程序员应该抱着尝鲜、猎奇的心态,否则就容易固步自封,技术停滞不前。

无代码时代来临,程序员如何保住饭碗?

编程语言层出不穷,从最初的机器语言到如今2500种以上的高级语言,程序员们大呼“学到头秃”。程序员一边面临编程语言不断推陈出新,一边面临由于许多代码已存在,程序员编写新应用程序时存在重复“搬砖”的现象。 无代码/低代码编程应运而生。无代码/低代码是一种创建应用的方法,它可以让开发者使用最少的编码知识来快速开发应用程序。开发者通过图形界面中,可视化建模来组装和配置应用程序。这样一来,开发者直...

面试了一个 31 岁程序员,让我有所触动,30岁以上的程序员该何去何从?

最近面试了一个31岁8年经验的程序猿,让我有点感慨,大龄程序猿该何去何从。

大三实习生,字节跳动面经分享,已拿Offer

说实话,自己的算法,我一个不会,太难了吧

程序员垃圾简历长什么样?

已经连续五年参加大厂校招、社招的技术面试工作,简历看的不下于万份 这篇文章会用实例告诉你,什么是差的程序员简历! 疫情快要结束了,各个公司也都开始春招了,作为即将红遍大江南北的新晋UP主,那当然要为小伙伴们做点事(手动狗头)。 就在公众号里公开征简历,义务帮大家看,并一一点评。《启舰:春招在即,义务帮大家看看简历吧》 一石激起千层浪,三天收到两百多封简历。 花光了两个星期的所有空闲时...

《经典算法案例》01-08:如何使用质数设计扫雷(Minesweeper)游戏

我们都玩过Windows操作系统中的经典游戏扫雷(Minesweeper),如果把质数当作一颗雷,那么,表格中红色的数字哪些是雷(质数)?您能找出多少个呢?文中用列表的方式罗列了10000以内的自然数、质数(素数),6的倍数等,方便大家观察质数的分布规律及特性,以便对算法求解有指导意义。另外,判断质数是初学算法,理解算法重要性的一个非常好的案例。

《Oracle Java SE编程自学与面试指南》最佳学习路线图(2020最新版)

正确选择比瞎努力更重要!

Java岗开发3年,公司临时抽查算法,离职后这几题我记一辈子

前几天我们公司做了一件蠢事,非常非常愚蠢的事情。我原以为从学校出来之后,除了找工作有测试外,不会有任何与考试有关的事儿。 但是,天有不测风云,公司技术总监、人事总监两位大佬突然降临到我们事业线,叫上我老大,给我们组织了一场别开生面的“考试”。 那是一个风和日丽的下午,我翘着二郎腿,左手端着一杯卡布奇诺,右手抓着我的罗技鼠标,滚动着轮轴,穿梭在头条热点之间。 “淡黄的长裙~蓬松的头发...

面试官:你连SSO都不懂,就别来面试了

大厂竟然要考我SSO,卧槽。

终于,月薪过5万了!

来看几个问题想不想月薪超过5万?想不想进入公司架构组?想不想成为项目组的负责人?想不想成为spring的高手,超越99%的对手?那么本文内容是你必须要掌握的。本文主要详解bean的生命...

我说我懂多线程,面试官立马给我发了offer

不小心拿了几个offer,有点烦

爬虫(九十七)不爬妹子图的爬虫不是一只好爬虫

不爬妹子图的爬虫不是一只好爬虫。----鲁迅还是一样,我们在爬取妹子图片的时候,首先要分析一下 DOM这里的img是就封面,如果只抓取封面的话,到这就可以了,但是我们取的是所有图片,所...

自从喜欢上了B站这12个UP主,我越来越觉得自己是个废柴了!

不怕告诉你,我自从喜欢上了这12个UP主,哔哩哔哩成为了我手机上最耗电的软件,几乎每天都会看,可是吧,看的越多,我就越觉得自己是个废柴,唉,老天不公啊,不信你看看…… 间接性踌躇满志,持续性混吃等死,都是因为你们……但是,自己的学习力在慢慢变强,这是不容忽视的,推荐给你们! 都说B站是个宝,可是有人不会挖啊,没事,今天咱挖好的送你一箩筐,首先啊,我在B站上最喜欢看这个家伙的视频了,为啥 ,咱撇...

代码注释如此沙雕,会玩还是你们程序员!

某站后端代码被“开源”,同时刷遍全网的,还有代码里的那些神注释。 我们这才知道,原来程序员个个都是段子手;这么多年来,我们也走过了他们的无数套路… 首先,产品经理,是永远永远吐槽不完的!网友的评论也非常扎心,说看这些代码就像在阅读程序员的日记,每一页都写满了对产品经理的恨。 然后,也要发出直击灵魂的质问:你是尊贵的付费大会员吗? 这不禁让人想起之前某音乐app的穷逼Vip,果然,穷逼在哪里都是...

2020春招面试了10多家大厂,我把问烂了的数据库事务知识点总结了一下

2020年截止目前,我面试了阿里巴巴、腾讯、美团、拼多多、京东、快手等互联网大厂。我发现数据库事务在面试中出现的次数非常多。

前端还能这么玩?(女朋友生日,用前端写了一个好玩的送给了她,高兴坏了)

前端还能这么玩?(女朋友生日,用前端写了一个好玩的送给了她,高兴坏了)

立即提问
相关内容推荐