提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文介绍一下蓝桥杯单片机采集任务考点,详细介绍了底层代码如何通过datasheet编写,对底层有疑问可以看底层代码讲解文章或b站up柳离风视频。
一、采集模块总结
1.ds18b20(中断里代码尽可能少,DS18B20时序太容易被打断了,中断里以后不要对unsigned long类型的变量%,用时很久)
没把握默写的同学一定要看手册写
程序总体框架
具体指令
1.跳过ROM
2.温度转换指令
3.温度读取指令
注意上电读的温度寄存器里的值是85
温度转换的最大时间是750ms
所以上电第一次读到的温度一定是85摄氏度
为了不影响参数判断,上电必须开始温度转换,并且到下次采集温度的时间间隔一定要至少750ms,如果上电第一个界面就显示温度的话,一定要先开启转换,delay_ms(750),再开EA,另外col_dly初始化设置的值大一点,保证第一次采集比显示快,这样显示的第一个温度为正常温度
如果上电第一个界面不是温度,就没必要delay_ms(750),但最开始还是需要转换一次。
//由于新版资源包底层已经时间扩大十二倍了,所以不需要再*12
//===================================================
//函数名称:uint rd_temperature(uchar dot)
//函数功能:读取ds18b20检测到的温度
//入口参数:dot (0:保留0位小数 1: 保留一位小数 2:保留两位小数)
//出口参数:temp (温度值)
//===================================================
unsigned int rd_temperature(uchar dot)
{
uint temp;
uchar tml,tmh;
init_ds18b20();
Write_DS18B20(0xcc); //跳过ROM
Write_DS18B20(0x44); //温度转换
init_ds18b20();
Write_DS18B20(0xcc); //跳过ROM
Write_DS18B20(0xbe); //温度读取
tml = Read_DS18B20(); //先读第八位
tmh = Read_DS18B20(); //再读高八位
temp = (tmh << 8) |tml;
switch(dot)
{
case 0 : temp = temp * 0.0625;break; //不保留小数
case 1 : temp = temp * 0.625;break; //保留一位小数
case 2 : temp = temp * 6.25;break; //保留两位小数
}
return temp;
}
void main()
{
system_init();
rd_temperature(1);
delay_ms(750);
EA = 1;
while(1)
{
key_task();
display_task();
collect_task();
}
}
2.ds1302
看手册把读写的地址写出来(头顶横杠代表低电平有效,否则高电平有效)
//如果想用十二小时制,需要注意第三行地址和数据,例如你要往进写上午八点,bit7就是1,bit5就是0,整体是10001000,对应BCD码是0x88,写进去就可以了。注意读的时候不能和之前一样,需要把bit7,bit5分别归零 ,再对16做除法和取余数。
code uchar write_addr[] = {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c}; //写地址
code uchar read_addr[] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d}; //读地址
uchar Time[] = {0x00,0x26,0x20,0x12,0x02,0x07,0x23}; //实时时间(用到几个写几个)
//===================================================
//函数名称:void ds1302_init()
//函数功能:初始化时间数据
//入口参数:无
//出口参数:无
//===================================================
void ds1302_init()
{
uchar i;
Write_Ds1302_Byte(0x8e,0x00);
for(i = 0;i < 7 ;i++) //这里的i也是用到几个写几个,否则时钟会乱跑
{
Write_Ds1302_Byte(write_addr[i],Time[i]);
}
Write_Ds1302_Byte(0x8e,0x80);
}
//===================================================
//函数名称:void ds1302_read()
//函数功能:读取时间数据
//入口参数:无
//出口参数:无
//===================================================
void ds1302_read()
{
uchar i;
for(i = 0;i < 7 ;i++)//这里的i也是用到几个写几个,否则时钟会乱跑
{
Time[i] = Read_Ds1302_Byte(read_addr[i]);
}
}
//如果题目有时钟调节需求,需要变为下面的代码
//===================================================
//函数名称:void ds1302_init()
//函数功能:初始化时间数据
//入口参数:无
//出口参数:无
//===================================================
void ds1302_init()
{
uchar i;
Write_Ds1302_Byte(0x8e,0x00);
for(i = 0;i < 3 ;i++) //这里的i也是用到几个写几个,否则时钟会乱跑
{
Write_Ds1302_Byte(write_addr[i],Time[i]);
}
Write_Ds1302_Byte(0x8e,0x80);
}
//===================================================
//函数名称:void ds1302_read()
//函数功能:读取时间数据
//入口参数:无
//出口参数:无
//===================================================
void ds1302_read(unsigned char * temp)
{
unsigned char i;
for(i = 0;i < 3;i++)
{
temp[i] = Read_Ds1302_Byte(read_addr[i]);
}
}
//===================================================
//函数名称:void Time_control(unsigned char * buf,unsigned char sign)
//函数功能:时间调节
//入口参数:buf(指针变量,传递数组首地址) sign(0 : 加 1: 减)
//出口参数:无
//===================================================
void Time_control(unsigned char * buf,unsigned char index,unsigned char sign)
{
buf[index] = (buf[index]/16)*10 + buf[index]%16;
if(sign == 0)
{
if(index == 2)
{
buf[index] = (buf[index] + 1)%24;
}
else
{
buf[index] = (buf[index] + 1)%60;
}
}
else
{
if(index == 2)
{
if(buf[index] == 0) buf[index] = 23;
else buf[index] = buf[index] - 1;
}
else
{
if(buf[index] == 0) buf[index] = 59;
else buf[index] = buf[index] - 1;
}
}
buf[index] = (buf[index]/10)*16 + buf[index]%10;
}
3.NE555(跳线帽记得把SIGNAL和P34接一起)
void Timer0Init_Count(void) //计数模式
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x05; //16位不自动重装
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计数
//一定不要开中断即ET0 = 1; 切记 切记 切记
}
void timer1() interrupt 3
{
if(++count==1000) //1秒算一次频率
{
fre=(TH0<<8)|TL0;
TH0=TL0=0;
count=0;
}
systick_ms++;
key_dly++;
display_dly++;
collect_dly++;
smg(SMG,dot,pos);
led(LED,pos);
if(++pos == 8) pos = 0;
relay(state_relay);
}
4.PCF8591(测电压时用万用表,调到电压档,红表笔接D/A,黑表笔接GND)
这里有个注意事项ADC上电初值是0x80,并且ADC读取的值是上次转换后的值,所以当多通道ADC读取时就会出现两个通道数据相反,解决办法就是每个通道各读两遍。
1.ADC
//===================================================
//函数名称:uchar PCF8591_Read(uchar control)
//函数功能:PCF8591 AD输入读取输入
//入口参数:control(控制字)
//出口参数:dat(数据)
//===================================================
uchar PCF8591_Read(uchar control)
{
uchar dat;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(control);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
dat = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return dat;
}
2.DAC
//===================================================
//函数名称:void PCF8591_Write(uchar control,dat)
//函数功能:PCF8591 DA输出电压
//入口参数:control(控制字) dat(数据)
//出口参数:无
//===================================================
void PCF8591_Write(uchar control,dat)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(control);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
5.超声波 (两个跳线帽都要接好)
由于比赛的时候可能会NE555和超声波一起考,我们将超声波使用PCA定时器来实现
#include "ultrasonic.h"
sbit TX = P1^0; // 发射引脚
sbit RX = P1^1; // 接收引脚
uchar read_distance(void)
{
uchar distance,num = 10;
TX = 0;
CL = 0xF3; // 设置定时初值
CH = 0xFF; // 设置定时初值
CR = 1; // 定时器0计时
// TX引脚发送40KHz方波信号驱动超声波发送探头
while(num--)
{
while(!CF);
TX ^= 1;
CL = 0xF3; // 设置定时初值
CH = 0xFF; // 设置定时初值
CF = 0;
}
CR = 0;
CL = 0; // 设置定时初值
CH = 0; // 设置定时初值
CR = 1;
while(RX && !CF); // 等待收到脉冲
CR = 0;
if(CF) // 发生溢出
{
CF = 0;
distance = 255;
}
else // 计算距离
distance = ((CH<<8)+CL)*0.017;
return distance;
}
6.AT24C02
//===================================================
//函数名称:void AT24C02_Write(uchar addr,dat)
//函数功能:AT24C02字节写
//入口参数:addr(地址) dat(数据)
//出口参数:无
//===================================================
void AT24C02_Write(uchar addr,dat)
{
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
//===================================================
//函数名称:void AT24C02_Write_Page(uchar * buf,uchar addr,uchar num)
//函数功能:AT24C02页写
//入口参数:buf(指针变量,传递数组的首地址) addr(内存地址,必须是八的整数倍,可以写十进制) num(数据个数)
//出口参数:无
//===================================================
void AT24C02_Write_Page(uchar * buf,uchar addr,uchar num)
{
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
while(num--)
{
IIC_SendByte(*buf++);
IIC_WaitAck();
}
IIC_Stop();
}
//===================================================
//函数名称:uchar AT24C02_Read(uchar addr)
//函数功能:AT24C02字节读
//入口参数:addr(地址)
//出口参数:dat(数据)
//===================================================
uchar AT24C02_Read(uchar addr)
{
uchar dat;
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0xa1);
IIC_WaitAck();
dat = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return dat;
}
//===================================================
//函数名称:void AT24C02_Read_Page(uchar * buf,uchar addr,uchar num)
//函数功能:AT24C02页读
//入口参数:buf(指针变量,传递数组的首地址) addr(内存地址,必须是八的整数倍,可以写十进制) num(数据个数)
//出口参数:无
//===================================================
void AT24C02_Read_Page(uchar * buf,uchar addr,uchar num)
{
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0xa1);
IIC_WaitAck();
while(num--)
{
*buf++ = IIC_RecByte();
if(num)
IIC_SendAck(0);
else
IIC_SendAck(1);
}
IIC_Stop();
}
7.串口通信
我们这里用定时器2来做波特率发生器,这里暂时不要求手算波特率了
题目要求多少波特率设置多少就行,需要打开ES
uchar Uart_Rxindex;
uchar Uart_Rxbuf[10];
void UartInit(void) //4800bps@12.000MHz
{
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x01; // 串口1选择定时器2为波特率发生器
AUXR |= 0x04; // 定时器2时钟为Fosc, 即1T
T2L = 0x8F; // 设定定时初值
T2H = 0xFD; // 设定定时初值
AUXR |= 0x10; // 启动定时器2
ES = 1; // 允许串口中断
}
void Uart0(void) interrupt 4
{
if(RI)
{
if(Uart_Rxindex == 10)
Uart_Rxindex = 0;
Uart_Rxbuf[Uart_Rxindex++] = SBUF;
RI = 0;
}
}
char putchar(char ch) //使用putchar重定向后,单片机就可以用printf串口输出数据了
{
SBUF = ch;
while (TI == 0); // 等待发送完成
TI = 0; // 发送完成标志位置0
return ch;
}
在ISP打开串口助手,选好端口,设置好波特率
二、常见考点
1.采集量映射(只要是可以采集的数据都可能考)
映射分为两种:线性映射、非线性映射
1.线性映射(y = kx+b,先求斜率,带入坐标再求b)
第十三届国赛
//横坐标就直接用0-255数字量就行
//k = 100/255.0,因为单片机非float变量除法会自动舍弃小数,当然也可以按计算器把他算出来 k = 0.39216867.
//采集任务
void collect_task()
{
uchar adc;
if(collect_dly<500)return;
collect_dly = 0;
adc = PCF8591_Read(0x03);
hum = adc * (100/255.0);
}
第十四届省赛
//k = (90-10)/(2000-200) = 8 / 180.0,因为单片机非float变量除法会自动舍弃小数,当然也可以按计算器把他算出来 k = 0.044.
//b = 10 - (8/180.0) * 200 用计算器计算的结果为 b = 1.112.
hum = fre * 0.044 +1.112;
if(hum<90&&hum>10)
有效数据
2.非线性映射
第九届省赛
//采集任务
void collect_task()
{
uchar adc;
if(collect_dly<500)return;
collect_dly = 0;
adc = PCF8591_Read(0x03);
if(adc < 64) level = 1;
else if(adc >=64 && adc < 128) level = 2;
else if(adc >=128 && adc < 1192) level = 3;
else if(adc >=192 && adc < 256) level = 4;
}
2.DA输出
第十三届国赛
//分段函数,这十几年DAC只有这一种考法,DAC与各种模拟量之间的函数关系
//采集任务
void collect_task()
{
uchar adc;
if(collect_dly<500)return;
collect_dly = 0;
humidity=PCF8591_Read(0x43)/2.55;
if(humidity==100) humidity=99; //数码管只有两位
if(humidity<humidity_set) dac=51;
else if(humidity>80) dac=255;
else dac=(204.0/(80-humidity_set))*humidity+(4080.0-255*humidity_set)/(80.0-humidity_set);
PCF8591_Write(0x43,dac);
}
3.参数比较
//注意我们的电压因为要显示两位小数,所以放大了一百倍,参数要显示一位小数,放大十倍,电压要和参数做比较一定要到同一数量级
//比如现在参数要和电压做比较
if((vol / 10.0 < vol_parmax) && (vol / 10.0 > vol_parmin))
if(distance >= distance_set-5 && distance <= distance_set+5)
alarm_count++;
else
alarm_count=0;
if(alarm_count>2)
{
flag_alarm=1;
}
else
flag_alarm=0;
if(count_tri >= 2 && hum > hum_old && temp > temp_old) //触发次数大于二且温湿度都升高
L6_state = 1;
else
L6_state = 0;
4.触发(明天更新)
1.触发方式
2.触发计数
3.触发记录数据
5.数据回显(明天更新)
6.最值、平均值
第十二届国赛
求平均值:每次采集的数据求和/count (如果题目要显示一位小数,那么每次采集的数据求和*10/count)
最大值:每次采到的数据和之前的最大值作比较,如果比最大值大就把新采到的数据赋给最大值
最小值:每次采到的数据和之前的最小值作比较,如果比最小值小就把新采到的数据赋给最小值
distance_max = (distance>distance_max) ? distance : distance_max;
distance_min = (distance<distance_min) ? distance :distance_min;
distance_sum = distance * 10 + distance_sum; //在显示的时候除count,就可以保留一位小数
7.数据召测
第十届国赛
void uart_pro()
{
if(index==0) return;
if(uart_stick>=10)// ʱ
{
uart_stick=uart_flag=0;
if(index>0)
{
if(index==4||index==6)
{
if(rec_buf[0]=='S'&&rec_buf[1]=='T'&&rec_buf[2]=='\r'&&rec_buf[3]=='\n')
printf("$%2d,%.2f\r\n",(uint)distance,temperature);
else if(rec_buf[0]=='P'&&rec_buf[1]=='A'&&rec_buf[2]=='R'&&rec_buf[3]=='A'&&rec_buf[4]=='\r'&&rec_buf[5]=='\n')
printf("#%2d,%2d\r\n",(uint)distance_set,(uint)temperature_set);
else
printf("ERROR\r\n");
}
else
printf("ERROR\r\n");
index=0;
}
}
}
更多资料关注B站UP柳离风
学习交流群
总结
以上就是蓝桥杯单片机采集任务的全部考点,希望大家全部掌握。