之前介绍了芯圣HC18M003的AD外设,今天在测试IIC LCD1602显示数据的基础上,加入AD转换的测试程序。在PORTA0的外部加一个电位器分压电路,将可变端电压输入到PORTA0,通过设置PORTA0为模拟输入,利用芯片的AD外设,获得转换结果,显示到LCD1602上。使用12位模式,提供的参考电压标准为4V。
由于无法在程序中声明Float和Double类型数据(编译报错)。所以为了实时显示电压,就把AD转换结果*1000,变成无符号长整形数,再除以4096,获得的结果,等于是人为放大了1000倍,只需要在实际显示的时候,在第一位数据后加上小数点,进而实现小数的计算。计算公式为:
Vo = ((转换结果+1) * 1000 / 4096) * 4;
电压检测电路如下
程序如下,
/** * 模块性能介绍 * 1、双线通讯,支持主机以及从机模式 * 2、支持多主机通讯时钟仲裁功能 * 3、支持地址可编程 * 4、支持标准速率(最多100kbps)和快速(最多400kbps) * * 以IIC方式驱动LCD1602,无视中断时IIC设备的状态值,只是按顺序发送数据 * ************************************************************************************ * 代码配置注意事项 * 1、当CPU运行在8M时,BOR必须使能为4.2V; * 2、当CPU运行在4M时,BOR必须使能为3.0V及以上; * 3、当CPU运行在2M时,BOR必须使能为2.0V及以上,常规情况下不建议关闭BOR; * 4、使能BOR时除程序中修改,建议OPTION也配置相同电压。 * ************************************************************************************ * LCD1602模块: VCC - 5V * (PCF8547) GND * SCL - P3.2 * SDA - P3.3 * PCF8574T LCD1602 *================================================================================ * P7 DB7 * P6 DB6 * P5 DB5 * P4 DB4 * P3 控制背光灯 * P0 RS * P1 RW * P2 CS *================================================================================ * LCD1602的操作 * 写指令: RS=0, RW=0, CS(E)上升沿 * 写数据: RS=1, RW=0, CS(E)上升沿 *================================================================================ * LCD1602的操作指令 * Bit RS RW 7 6 5 4 3 2 1 0 *----------------------------------------------------------------------------------- * 1.清除显示 0 0 0 0 0 0 0 0 0 1 * 2.光标返回 0 0 0 0 0 0 0 0 1 * * 3.设输入模式 0 0 0 0 0 0 0 1 I/D S I/D:光标移动方向,1-右移;0-左移 * S:屏幕上所有文字是否左移或右移,1表示有效,0表示无效 * 4.显示开关 0 0 0 0 0 0 1 D C B D=1开显示,D=0关显示 * C=1显示光标,C=0关闭光标 * B=1光标闪烁,B=0光标不闪烁 * 5.光标字符移位 0 0 0 0 0 1 S/C R/L * * S/C 1-显示移动的文字,0-移动光标 * R/L 左右方向 * 6.设置功能 0 0 0 0 1 DL N F * * DL: 1-4位总线;0-8位总线 * N: 1-双行显示;0-单行显示 * F: 1-5X10点阵, 0-5x7点阵 * 7.设置字符发生器地址 0 0 0 1 字符发生器RAM * 8.设置数据存储器地址 0 0 1 显示数据存储器地址 *----------------------------------------------------------------------------------- * 9.读忙标志或地址 0 1 BF 计数器地址 * 10.写数到CGRAM或DDRAM 1 0 要写的数据内容 * 11.从CGRAM或DDRAM读 1 1 读出的数据 *=================================================================================== * 数据指针设置 80H + 地址码(0-27H(第一行),40H-67H(第二行)) ** LCD1602 四线驱动方式下,一个字节数据传输2回 **/ #include"holychip_define.h" // Debug用定义 //#define DEBUG //--------------OLED参数定义--------------------- #define LINE_1_ADDR 0x40 #define LINE_2_ADDR 0x80 #define WIDTH 128 #define HEIGHT 32 #define OLED_CMD 0 //写命令 #define OLED_DATA 1 //写数据 #define IIC_SLAVE_ADDR 0x4E //IIC器件地址: #define LCD_ADDR_W 0x4E #define LCD_ADDR_R 0x4F #define IIC_STATUS_START_OK 0x08 // START发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(1,0,0,X) #define IIC_STATUS_REPEAT_START_OK 0x10 // START发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(1,0,0,X) #define IIC_STATUS_WADDR_ACK_OK 0x18 // 向从机发送写指令+地址,发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(X,0,0,X) #define IIC_STATUS_DATA_ACK_OK 0x28 // 向从机发送数据,完成后的状态(ACK应答)(STA,STO,SI,AA)=(0,0,0,X) #define IIC_STATUS_WADDR_NACK_OK 0x20 // 向从机发送写指令+地址,发送完成后的状态(NACK应答)(STA,STO,SI,AA)=(X,0,0,X) #define IIC_STATUS_DATA_NACK_OK 0x30 // 向从机发送数据,完成后的状态(NACK应答)(STA,STO,SI,AA)=(0,0,0,X) //unsigned char HEXCHR[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; unsigned int gms=0; // 延时变量 /******************************************************* * @说明 1毫秒单位的延时 * @参数 ms 毫秒数 * @返回值 无 * @注 比较准确, *******************************************************/ void delay_ms(unsigned int ms) { gms=ms; while(gms > 0) { // 等待gms = 0 } } /******************************************************* * @说明 10微秒单位的延时 * @参数 ms 毫秒数 * @返回值 无 * @注 不准确, *******************************************************/ void delay_10us(unsigned int wm) { unsigned char a,b,c; for(c=0; c<wm; c++) { for(b=0; b<40; b++) { } } } /******************************************************* * @说明 取得十六进制字符 * @参数 chr 字符 * h_l_flag 0 - 低四位对应16进制字符;1 - 高四位对应字符 * @返回值 无 * @注 无 *******************************************************/ unsigned char toHexChar(unsigned char chr, unsigned char h_l_flag) { unsigned char val = chr; unsigned char result=0; if (h_l_flag) { val = (val & 0xF0)>>4; } else { val = val & 0x0f; } switch (val) { case 0: result = '0';break; case 1: result = '1';break; case 2: result = '2';break; case 3: result = '3';break; case 4: result = '4';break; case 5: result = '5';break; case 6: result = '6';break; case 7: result = '7';break; case 8: result = '8';break; case 9: result = '9';break; case 10: result = 'A';break; case 11: result = 'B';break; case 12: result = 'C';break; case 13: result = 'D';break; case 14: result = 'E';break; case 15: result = 'F';break; default: result = '-';break; } return result; } /******************************************************* * @说明 判断字符是否能显示 * @参数 chr * @返回值 0 - 不可显示; 1 - 可显示 * @注 *******************************************************/ unsigned char isDispChar(unsigned char chr) { if (chr >= 32 && chr <= 126) { return 1; } return 0; } /******************************************************* * @说明 串口发送一个字符 * @参数 chr 字符 * @返回值 无 * @注 无 *******************************************************/ void send_char(unsigned char chr) { SBUF = chr; //发送8位串口数据 while(!TXIF); TXIF = 0; //清除发送中断标志位 } /******************************************************* * @说明 串口发送一个字符,以十六进制方式表示 * @参数 chr 字符 * @返回值 无 * @注 无 *******************************************************/ void send_char_hex(unsigned char chr) { SBUF = '0'; while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = 'x'; while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = toHexChar(chr, 1); while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = toHexChar(chr, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = ' '; while(!TXIF); TXIF = 0; //清除发送中断标志位 } /******************************************************* * @说明 串口发送一个字符,以十进制方式表示 * @参数 chr 字符 * @返回值 无 * @注 无 *******************************************************/ void send_char_dec(unsigned char chr) { if (chr > 99) { SBUF = toHexChar(chr/100, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } if (chr > 9) { SBUF = toHexChar((chr%100)/10, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } SBUF = toHexChar(chr%10, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } /******************************************************* * @说明 串口发送一个字符串 * @参数 str 字符串 * @返回值 无 * @注 无 *******************************************************/ void send_str(unsigned char *str) { unsigned char chr = '\0'; while (chr=*str) { send_char(chr); str++; } } /******************************************************* * @说明 中断服务函数 * @参数 无 * @返回值 无 * @注 包含定时器2中断和IIC中断 *******************************************************/ void interrupt all_isr(void) { // IIC中断 if (IICIF) { } // // 串口接收中断,收到的数据存入数据缓冲区(对应两行32个字符) // if(RXIF) { // guc_Uartbuf = SBUF; // 转存8位串口接收数据 // guc_Uartflag = 1; // 接收中断标志 // // // 如果是可显示的字符,保存 // if (isDispChar(guc_Uartbuf)) { // guc_lcdchar[lcdchar_addr] = guc_Uartbuf; // lcdchar_addr = (lcdchar_addr+1)%32; // 显示位置指向下一个 // } // // SCON &= ~0x10; //禁止串口继续继续接收数据 // RXIF = 0; //清除接收中断标志位 // } // 定时器0中断 if(T0IF) { T0 = 0xF0; //T0定时时间1ms if (gms>0) { gms=gms-1; } T0IF = 0; //清除T0中断标志位 } } /******************************************************* * @说明 通过IIC发送一个字节给PCF8574T,PCF8574T是作为IIC从机存在的 * @参数 dat 呈现到 P7 ~ P0管脚上,连接到LCD引脚 * P7 -- LCD160-DB7 * P6 -- LCD160-DB6 * P5 -- LCD160-DB5 * P4 -- LCD160-DB4 * P3 -- LCD1602-背光灯 * P2 -- LCD1602-E * P1 -- LCD1602-RW * P0 -- LCD1602-RS * @返回值 无 * @注 发送给LCD1602,要通过PCF8574T实现 *******************************************************/ unsigned char send_pcf8574Byte(unsigned char dat) { unsigned char iic_status = 0; // 每次发送完成后获得的IIC状态码 unsigned char result = 0; // 返回值 //send_str((unsigned char *)"\r\n<send_pcf8574Byte>"); // 清除IIC中断标志 IICIF = 0; //启动IIC模块 IICCON |= 0x40; delay_10us(1); // 使IIC外设向IIC总线发送“START”信号 IICCON |= 0x20; // 产生开始信号 #ifdef DEBUG send_char_hex(0xA1); #endif // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x08) { #ifdef DEBUG send_char_hex(0xA2); #endif // 发送子机地址 IICCON &=~ 0x20; // STA=0,禁止继续发START信号 IICCON |= 0x04; // AA=1,确认子机回复ACK信号? IICDAT = IIC_SLAVE_ADDR; IICIF = 0; } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 1; goto send_pcf8574Byte_end; } // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x18) { #ifdef DEBUG send_char_hex(0xA3); #endif // 发送数据 IICCON &=~ 0x20; // STA=0,禁止继续发START信号 IICCON |= 0x04; // AA=1,确认子机回复ACK信号? IICDAT = dat; IICIF = 0; } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 2; goto send_pcf8574Byte_end; } // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x28) { #ifdef DEBUG send_char_hex(0xA9); #endif } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 3; goto send_pcf8574Byte_end; } IICCON |= 0x10; // 停止信号 IICCON &= ~0x40; // 关闭IIC return 0; send_pcf8574Byte_end: IICCON |= 0x10; // 停止信号 delay_ms(1); IICCON &= ~0x40; // 关闭IIC delay_ms(1); return result; } /********************************************************* * @说明 向LCD1602发送指令 * @参数 cmd 指令 * @返回值 无 * @注 通过PCF8574T转成引脚输出,适配LCD1602方式 * ----------------------------------- * dat PCF8574T LCD1602 *------------------------------------ * D7 P7 DB7 * D6 P6 DB6 * D5 P5 DB5 * D4 P4 DB4 * D3 P3 控制背光灯 * D2 P0 RS * D1 P1 RW * D0 P2 E **********************************************************/ void write_lcd1602_cmd(unsigned char cmd) { unsigned char cmd1, cmd2; //send_str((unsigned char *)"\r\n<write_lcd1602_cmd>"); cmd1=cmd|0x0f; // 发送高4位 send_pcf8574Byte(cmd1 & 0xfc); // 11111100 : CS=1,RW=0, RS=0 delay_10us(1); send_pcf8574Byte(cmd1 & 0xf8); // 11111000 : CS=0,RW=0, RS=0 // 发送低4位,要移位到高4位位置上 cmd2=cmd<<4; cmd2=cmd2|0x0f; send_pcf8574Byte(cmd2 & 0xfc); // 11111100 : CS=1,RW=0, RS=0 delay_10us(1); send_pcf8574Byte(cmd2 & 0xf8); // 11111000 : CS=0,RW=0, RS=0 } /************************************************************ * @说明 向LCD1602发送数据 * @参数 dat 指令 * @返回值 无 * @注 通过PCF8574T转成引脚输出,适配LCD1602方式 * ----------------------------------- * dat PCF8574T LCD1602 *------------------------------------ * D7 P7 DB7 * D6 P6 DB6 * D5 P5 DB5 * D4 P4 DB4 * D3 P3 控制背光灯 * D2 P0 RS * D1 P1 RW * D0 P2 E **************************************************************/ void write_lcd1602_data(unsigned char dat) { unsigned char dat1, dat2; // send_str((unsigned char *)"\r\n<write_lcd1602_data>"); dat1=dat|0x0f; // 发送高4位 send_pcf8574Byte(dat1 & 0xfd); // 11111101 : CS=1,RW=0, RS=1 delay_10us(1); send_pcf8574Byte(dat1 & 0xf9); // 11111001 : CS=0,RW=0, RS=1 // 发送低4位,要移位到高4位位置上 dat2=dat<<4; dat2=dat2|0x0f; send_pcf8574Byte(dat2 & 0xfd); // 11111101 : CS=1,RW=0, RS=1 delay_10us(1); send_pcf8574Byte(dat2 & 0xf9); // 11111001 : CS=0,RW=0, RS=1 } /************************************************************** * @说明 初始化LCD * @参数 无 * @返回值 无 * @注 无 *****************************************************************/ void lcd_init(void) { // send_str((unsigned char *)"\r\n<lcd_init>"); // write_lcd1602_cmd(0x38); // 设置显示 4 (0x38:8) // delay_ms(5); write_lcd1602_cmd(0x33); // 设置显示 4 (0x38:8) delay_ms(5); write_lcd1602_cmd(0x32); // 设置4线控制 delay_ms(5); write_lcd1602_cmd(0x28); // 设置16*2, 5*7, 4线初始化 delay_ms(5); write_lcd1602_cmd(0x06); // 地址加1,数据不移动,地址移动 delay_ms(5); write_lcd1602_cmd(0x0C); // 不显示光标,光标不闪烁 delay_ms(5); // write_lcd1602_cmd(0x0f); // 显示光标,光标闪烁 // delay_ms(5); write_lcd1602_cmd(0x01); // 清屏 delay_ms(5); write_lcd1602_cmd(0x80); // 起始地址 delay_ms(100); } //Y为显示指针的位置,即为各行的第几个位置,X选行 //col:0-15 //row:0-1 /******************************************************************** * @说明 设置显示位置 * @参数 row 垂直位置(0~1) * col 水平位置(0~15) * @返回值 无 * @注 无 ********************************************************************/ void lcd1602_GotoXY(unsigned char row, unsigned char col) { if(row == 0) write_lcd1602_cmd(0x80 + col); if(row == 1) write_lcd1602_cmd(0x80 + 0x40 + col); } /********************************************************************* * @说明 使LCD在当前位置显示字符串 * @参数 str 字符串 * @返回值 无 * @注 无 *********************************************************************/ void LCD1602_Display_NoXY(unsigned char *str) { //send_str((unsigned char *)"\r\n<LCD1602_Display_NoXY>str="); //send_str(str); while(*str != '\0') { write_lcd1602_data(*str); delay_ms(5); str++; } } /******************************************************* * @实现效果 IIC主机发送与接收 *******************************************************/ void main() { unsigned char sta=0; unsigned int adc_val = 0; //获取ADC数据 unsigned long vol = 0; unsigned char val_chr[] = {'0', '\0', '\0', '\0', '\0', '\0', '\0', '0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}; // AD转换结果 /************************************系统初始化****************************************/ PCON = 0x00; //禁止WDT复位 OSCCON = 0x04; //Fosc=32M Fcpu=4M(Fosc4分频 2T) /************************************IO初始化*****************************************/ ANSELA = 0x04; // PA4设为数字形式 , PA0为模拟口 TRISA = 0x04; // PA4为输出口-中断指示 ANSELC = 0xFF; //PC口的B0、B1、B2、B4、B6、B7为数字模式,对TSSOP20封装,只有PC0,PC1,PC2,PC4 // 对TSSOP20封装,PORTC2、PORTD4、PORTB6、可对应于IIC_SDA; // PORTC2和PORTB7可对应于IIC_SCK; // 此处使用PORTC2和PORTC0映射为IIC的SCK和SDA IICMAP = 0x11; // PORTC2为输出模式 //TRISC = 0x04; TRISC = 0x05; // PC2和PC0输出模式 /***********************IIC初始化**************************/ // IICCON构成:CR2\IICEN\STA\STO\-\AA\CR1\CR0 // 0x40: CR2~CR0:000,Fcpu=4M模式下,通讯速度=15.63KHz // IICEN=1,启动IIC模块 // STA=0,不发送起始信号 // STO=0,不发送停止信号 // AA=1,回复ACK(SDA上为低电平) IICCON = 0x44; //启动IIC模块 /********************T0配置初始化********************/ OPTION = 0X07; // 分频寄存器配置256分频 T0CS = 0; // T0 模式选择寄存器:定时器模式,计数时钟Fcpu,休眠和绿色模式下停止 T0OSCEN = 0; // 禁止定时器模块0使用计数时钟 T0SE = 0; // 定时器模式,计数时钟Fcpu /********************UART配置初始化********************/ ANSELB = 0xC0; //PB7 PB6设为数字模式 TRISB = 0x40; //PB7设为输入模式 PB6设为输出模式 INTMAP = 0x20; UARTMAP = 0x50; //映射 PB7口作为RX PB6口作为TX // 波特率等设置 BRTH = 0xFF; BRTL = 0xE6; //Baudrate_9600 SCON2 = 0x24; //UART功能选择 8位 SCON |= 0x10; //使能UART接收 // ADCON0设置,ADON(B1=1)启动转换 // 启动转换时,ADCIF需要先软件清0,ADCIF位为1时,置ADCST不能启动新的转换。 // 在转换过程中,若ADCST位软件清0将终止转换 // ADEN位置1将使能ADC模块,ADON位置1将启动一次ADC转换。 // ADC转换完成,ADON位硬件清零,ADIF中断标志位置1,ADRESH/ADRESL寄存器值被更新。 ADCON0 = 0x01; // 使能ADC模块B0=1,延时20微秒再启动AD转换 ADCON0 |= 0x00; // AN4通道 B5~B2=0000:AN0 // ADCON1设置 ADCON1 = 0x20; // ADC时钟分频Fosc/4 B6~B4=010:Fosc/4 ADCON1 |= 0x00; // 12位数据 B7=0:12位;B7=1:10位 ADCON1 |= 0x02; // ADC内部参考电压 4V B3~B1:001:4.0V // ADCLK设置,选择时钟 B2~B0=001:1MHz ADCLK = 0x01; // ADC转换频率为1MHz /********************中断设置********************/ T0IE = 1; //打开T0中断 //IICIE = 1; //允许IIC中断 PEIE = 1; //允许未屏蔽中断 GIE = 1; //允许总中断 send_str((unsigned char *)"\r\n<main>"); // 初始化LCD lcd_init(); // 在第1行第一列显示字符串:Start test ... lcd1602_GotoXY(0,0); LCD1602_Display_NoXY("Start A/D : "); while(1) { ADCON0 |= 0x02; //启动转换 // ADC转换完成,ADON位硬件清零,ADIF中断标志位置1,ADRESH/ADRESL寄存器值被更新 while(!ADIF); // 等待AD转换完的标志 // 获取ADC数据,12位:0-4095 vol = (ADRESH<<8)|ADRESL; // 显示转换结果 // 显示结果 val_chr[0]=vol/1000+'0'; // 千位 if (val_chr[0]=='0') { val_chr[0]=' '; } val_chr[1]=(vol%1000)/100+'0'; // 百位 if (val_chr[1] == '0') { val_chr[1]=' '; } val_chr[2]=(vol%100)/10+'0'; // 十位 if (val_chr[2]=='0') { val_chr[2]=' '; } val_chr[3]=vol%10+'0'; // 各位 val_chr[4]=' '; val_chr[5]='V'; val_chr[6]='='; // 转换为电压值 // 放大1000倍。将小数变整数,精确到小数点后3位,4为4V电压 vol = (vol+1)*1000*4/4096; val_chr[7]=vol/1000 + '0'; val_chr[8]='.'; val_chr[9]=(vol%1000)/100 + '0'; val_chr[10]=(vol%100)/10+'0'; val_chr[11]=vol%10+'0'; val_chr[12]=0; val_chr[13]=0; val_chr[14]=0; val_chr[15]=0; lcd1602_GotoXY(1,0); LCD1602_Display_NoXY(val_chr); // 延迟10ms,使LCD1602正常显示 ADIF = 0; } }
之前发的IIC LCD测试例程中,因为在发送完数据给LCD1602后,没有预留处理时间,在频繁显示数据时,会导致屏幕显示乱码,这次加以改正。
从测试结果看,数据比较稳定,变化不大,说明芯片的AD处理部分挺好的。发上一个测试截图,由于测试周期比较小,数据变化频繁,所以显示上有频闪。