之前介绍了芯圣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处理部分挺好的。发上一个测试截图,由于测试周期比较小,数据变化频繁,所以显示上有频闪。


我要赚赏金
