• [技术干货] 单片机与C语言编程基础_数组知识
    当前文章复盘C语言的数组: 数组定义规则、数组空间类型、数组下标使用、数组存放的数据类型、数组数据替换、插入、输入、输出、字符串输入、输出、长度计算等等。一、数组概念数组是一个相同类型元素的集合。定义数组语法: <数据类型><数组名称>[数组的大小]; int data [100];解释:定义一个int类型数组名称是data,可以存放100int类型的数据,总大小字节: 100*4。<数据类型> 可以填充那些参数?基本数据类型(char 。。float、int。。。。。)、结构体数据类型、指针类型等。数组概念说明(1)[]称为下标符号,用来操作数组。比如: b=a[10]; //将a数组里第10个元素取出来赋值给b变量。(2).访问数组时,下标的范围是从0开始。比如: int a[10]; 下标范围: 0~9(3).数组在定义时,[]下标里填充的大小必须是常量。(C89标准) 比如: int a[b]; //错误 int a[10];(4).数组只有在定义的时候,才可以整体赋值。比如: int a[10]={12,34,56,78,90};(5).数组在定义的时候,如果赋了值,[]下标里可以不用指定大小。 比如: int a[]={12,34,56};(6).数组在定义的时候,至少给一个下标赋了初始值,其他下标默认置为0。比如: int a[10]={0};​#include <stdio.h>​int main(void){ int buff_i[10]={10,20,30}; //定义整数类型数组 float buff_f[10]={10.123,20.123,30.123}; //定义浮点数类型数组 char buff_c[10]="123456";//定义字符串类型数组,结尾多了一个结束符"\0" //说明: 在C语言里面没有专用字符串类型,字符串本身还是使用char类型定义。Qstring Sting char buff[10]={'1','2','3','4'}; //定义字符类型数组 int i; //打印整型 for(i=0;i<10;i++) { printf("%d ",buff_i[i]); } printf("\n"); //打印浮点类型 for(i=0;i<10;i++) { printf("%f ",buff_f[i]); } printf("\n"); //打印字符串 printf("%s\n",buff_c); //打印字符类型 for(i=0;i<10;i++) { printf("%c ",buff[i]); } printf("\n"); return 0;}二、数组输入输出#include <stdio.h>​//从键盘上录入一串数据,倒序输出int main(void){ int i; int buff[5]; printf("请输入5个数据:"); for(i=0;i<5;i++) { scanf("%d",&buff[i]); //从键盘上录入数据 } for(i=5-1;i>=0;i--) { printf("%d ",buff[i]); //将数据打印到控制台终端 } printf("\n"); return 0;}三、数组的练习题目(1). 从键盘上录入一串数据(整数),将数组里的10替换成66,并统计替换的数量。​#include <stdio.h>int main(void){ //将数组里的10替换成66,并统计替换的数量。 int buff[10]={10,34,10,347,23,10,123,56,78,10}; int i,cnt=0; for(i=0;i<10;i++) { if(buff[i]==10) { cnt++; //记录数量 buff[i]=66; } } printf("替换的数量%d\n",cnt); for(i=0;i<10;i++) { printf("%d ",buff[i]); } printf("\n"); return 0;}(2) 计算数组的大小int main(void){ int buff[]={10,34,10,347,23,10,123,56,78,10,45,10,10}; //34,347,23,123,56,78 int len=10; printf("buff数组的总大小=%d\n",sizeof(buff)); printf("buff数组有%d个成员\n",sizeof(buff)/sizeof(buff[0])); return 0;}(3)从键盘上输入一串数据(整数),将数组里大于10的数据删除掉。//比如: char buff[]={12,34,8,4,7,100};//最终的结果:{8,4,7,0,0,0}#include <stdio.h>​/*从键盘上输入一串数据(整数),将数组里大于10的数据删除掉。*/int main(void){ int buff[13]={1,34,2,347,23,3,123,56,78,4,45,5,6}; int len=13; int i,j; for(i=0;i<len;i++) { if(buff[i]>10) //当if条件成立的时候,i就是当前需要覆盖数据下标 { //将后面的所有数据向前移动一位 for(j=i;j<len-1;j++) { buff[j]=buff[j+1]; } i--; //继续判断上一次的位置,是否需要删除 len--; //总长度-- } } printf("删除之后的数据:"); for(i=0;i<len;i++) { printf("%d ",buff[i]); } printf("\n"); return 0;}四、字符串(1)字符串大小计算示例 char str1[]={'A','B','c'}; //3 char str2[]="ABC"; //4 printf("str1=%d\n",sizeof(str1)); //3 printf("str2=%d\n",sizeof(str2)); //4 //为什么是4 ?因为字符串结尾自带一个'\0' // '\0'表示是字符串的结束符号(2)字符串的长度计算#include <stdio.h>int main(void){ char str[100]; //字符串如何从键盘上录入? scanf("%s",str); //从键盘上录入一个字符串 //str为什么不加&符号? 因为数组的名称就是地址(表示是数组首地址)。 //字符串如何输出? printf("str=%s\n",str); //如何知道从键盘上输入了多少个字符? //首先得知道一点: 字符串结尾符'\0' 字符 int i=0; while(str[i]!='\0') //判断字符串的结束符 { i++; } printf("字符串的长度=%d\n",i); return 0;}(3)字符串的衔接符号#include <stdio.h>int main(void){ char str[100]="3847tr45gt\ // \表示字符串的衔接符号 rhonrbngfbn\ gf"; return 0;}
  • [技术干货] 单片机与C语言编程基础_数组与函数传参
    字符串标准处理函数介绍(string.h)、指针和数组当做函数形参,指针定义、函数返回指针、void类型定义指针、类型强制转换、常量声明、extern外边引用声明关键字。1. 总结字符串相关的处理函数string.h里常用的字符串处理相关函数。字符串: string.hvoid *memcpy(void *restrict, const void *restrict, size_t);int memcmp(const void *, const void *, size_t);void *memset(void *, int, size_t);char *strcat(char *restrict, const char *restrict);int strcmp(const char *, const char *);char *strcpy(char *restrict, const char *restrict);size_t strlen(const char *);char *strncat(char *restrict, const char *restrict, size_t);int strncmp(const char *, const char *, size_t);char *strncpy(char *restrict, const char *restrict, size_t);char *strstr(const char *, const char *);​格式化打印(转换)函数:int sprintf(char *str, const char *format, ...);int snprintf(char *str, size_t size, const char *format, ...);int sscanf(const char *str, const char *format, ...);说明: char p; // a=aa; //乘法 char *p; //定义指针 指针用于代表一个地址。 指针可以当做数组使用,数组无法当做指针使用。数组的名称: 就是首地址 在C语言里任何类型的指针(地址)是4个字节2. 函数参数: 指针与数组类型函数的形参: 指针类型与数组类型 示例代码: ()#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int my_strlen1(char buff[]);int my_strlen2(char *buff);​int main(int argc,char **argv){ char str[]="1234567890"; int len1=my_strlen1(str); int len2=my_strlen2(str); printf("%d\n",len1); printf("%d\n",len2); return 0;}​/*函数功能:计算字符串的长度char buff[] :传入是数组类型,也就是传入的是地址*/int my_strlen1(char buff[]){ int cnt=0; while(buff[cnt]!='\0') { cnt++; } return cnt;}​/*函数功能:计算字符串的长度char *str :传入是字符指针类型,也就是传入的是地址*/int my_strlen2(char *buff){ int cnt=0; while(buff[cnt]!='\0') { cnt++; } return cnt;}3. 数组当做函数形参的问题示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​void my_func1(char buff[100]);void my_func2(char *buff);void sum(int a);​int main(int argc,char **argv){ int a=100; char str[100]="12345"; my_func1(str); my_func2(str); sum(a); return 0;}​//char buff[100] 函数形参里是地址不会申请空间void my_func1(char buff[100]){ char buff1[100]; printf("sizeof=%d\n",sizeof(buff)); printf("sizeof1=%d\n",sizeof(buff1));}​void my_func2(char *buff){ printf("sizeof=%d\n",sizeof(buff));}4. 指针定义: 存放地址#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ int a=100; char str[100]="123456"; char *p; //定义的是一个指针(地址) p=str; //可以赋值 //p=&a; //可以赋值​ printf("%p\n",&a); printf("%p\n",str); printf("%p\n",p); printf("%s\n",str); printf("%s\n",p); return 0;}5. 函数形参和返回值: 都是地址(1)数组类型可以当做函数形参。void func(char buff[]){}(2)数组类型不能当做函数返回值类型。char buff[] func(void){} 错误的 函数如果要返回地址类型: 必须使用指针类型。 函数形参如果要传入地址类型: 可以使用指针类型或者数组类型。示例代码:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件char *func(char buff[]);int main(int argc,char **argv){ char str[]="12345"; char *str1=func(str); printf("%p\n",str); printf("%p\n",str1); printf("%s\n",str1); return 0;}​//形参是一个数组类型(char的地址)//返回值是一个char类型的地址char *func(char buff[]){ return buff;}​示例代码(局部地址是不能返回的)#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件char *func(void);int func1(void);int main(int argc,char **argv){ char *str=func(); printf("str=%s\n",str); printf("str2=%p\n",str); int a=func1(); printf("a=%d\n",a); return 0;}​char *func(void){ char str[]="5thgtrgbtfbfgbgf"; //static char str[]="5thgtrgbtfbfgbgf"; //加了静态关键字之后,数据空间永久保留(与main函数生命周期一样) printf("str1=%p\n",str); return str; //将数据空间的地址返回,将地址赋值给接收返回值的指针变量}​int func1(void){ int a=88; return a; //a变量里的数据取出来返回去,赋值给接收返回值的变量。}6. void*类型指针#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void func(void *p);​int main(int argc,char **argv){ int buff[100]; char str[100]; func(buff); func(str); return 0;}​/*void *p:传入的形参自适应*/void func(void *p){ }//void a; //语法是错误的//int a; //定义一个int类型变量,空间是4个字节//void *p //指针本身是存放地址,任何类型的指针都是4个字节7. 强制转换强制转换只是欺骗编译器,消除警告,不会改变本身的数据类型。 示例代码: 指针之间的强制转换#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void func(int *p);​int main(int argc,char **argv){ char str[]="123456789"; func((int*)str); //强制转换语法 return 0;}​/*void *p:传入的形参自适应*/void func(int *p){ char *str=(char*)p; printf("str=%s\n",str);}8. 常量声明关键字: const#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ char buff[100]="123"; const char *p=buff; //表示p指向的buff的空间数据是不能修改的 //*p='A'; //不能修改 赋值是错误的 p[0]='A'; //不能修改 赋值是错误的 //const int a=99;//定义a是只读变量。 //a=88; //赋值是错误的 return 0;}9. extern外部声明关键字主要是用在头文件里,多文件编程中。#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件extern void func(void);//外部引用声明extern int data; //外部引用声明int main(int argc,char **argv){ func(); printf("%d\n",data); return 0;}int data=888; //定义变量void func(void){ printf("123\n");}
  • [技术干货] 单片机与C语言编程基础___数组全方位练习
    复盘C语言数组的练习题:涉及到数组插入、数组删除、数组下标数据的左移右移、数组排序、数组排序优化、数组的数据拼接等等。(1)函数基本使用: 函数基本定义、封装、传参、调用、局部变量、全局变量、只读变量、静态变量 (2)数组基本使用: 数组基本定义、使用赋值、字符串。1. 数组基本知识1. 概念: 同一个类型的集合。2. 定义: int data[100]; //数组data里可以存放100个int类型 100必须是常量(C89)。定义数组时,如果没有赋初始值[]一定要填大小。 int data[]={1};3.数组赋值: 只有在定义的使用才可以整体赋值。int data[10]={12,34,56,7,89};4.数组下标[]访问从0开始。 比如: buff[100]; 下标范围: 0~995.数组的名称就是首地址。int buff[100];1.2 字符串数组1. 字符串录入: scanf(“%s”); gets() fgets2. 头文件: string.h3. 从键盘录入字符串的方法 scanf("%s",str); //从键盘上录入字符串,字符串结尾: '\0'gets(str); //从键盘上录入字符串fgets(str,100,stdin); //从键盘上录入字符串 (标准输入)​#include <stdio.h>int main(int argc,char **argv){ int int_data[100]={12,34,56,78}; //整型数组 char char_data[]={'A','B','C'}; //字符数组 char str1_data[]={'A','B','C','\0'}; //字符串数组 char str2_data[]="ABC";//字符串数组 printf("char_data=%d\n",sizeof(char_data)); //3 printf("str1_data=%d\n",sizeof(str1_data)); //4 printf("str2_data=%d\n",sizeof(str2_data)); //4 return 0;}字符串数组特性: 结尾有’\0’2. 数组相关的题目(分析)2.1 数组的数据插入 int a[]={1,2,3,4,5,6,7}; 在第2位数据后插入一个888 结果: 1,2,888,3,4,5,6,7#include <stdio.h>int main(int argc,char **argv){ int data[10]={1,2,3,4,5,6,7,8,9}; //1,2,3,4,4,5,6,7,8,9 int addr; int i; printf("输入插入的位置:"); scanf("%d",&addr); /*1. 向后移动数据,空出位置*/ for(i=8;i>=addr;i--) { data[i+1]=data[i]; } /*2. 查看移动之后的效果*/ for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); data[addr]=888; //插入的数据 /*3. 最终效果*/ for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); return 0;}2.2 数组数据的删除 int a[]={1,2,3,4,2,6,7}; 将第2位数据删除掉结果: 1,3,4,5,6,7示例:#include <stdio.h>int main(int argc,char **argv){ int data[10]={1,2,3,4,5,6,7,8,9,10}; //1,2,4,5,6,7,8,9,10,10 int addr; int i; printf("输入删除的位置:"); scanf("%d",&addr); //2​ /*1. 向前移动,将数据覆盖掉*/ for(i=addr-1;i<10-1;i++) { data[i]=data[i+1]; } /*2. 查看移动之后的效果*/ for(i=0;i<9;i++)printf("%d ",data[i]); printf("\n"); return 0;}2.3 变量排序数组数据排序。从键盘上输入一串数据(整数),从大到小排序,再输出。 int a[]={12,67,45,13,1,5}; 排序之后: 67,45,13,12,5,1排序: 计算平均数。班级成绩、温度数据......例子: 从键盘上随机输入3个数,将3个数按照从大到小的顺序输出。#include <stdio.h>int main(int argc,char **argv){ int a,b,c; int tmp; //存放临时数据 printf("请输入3个数:"); scanf("%d%d%d",&a,&b,&c); //以回车作为结束符号,以空格作为间隔符号 printf("源数据:%d,%d,%d\n",a,b,c); //从大到小 if(a<b) //b,a,c { tmp=a; a=b; b=tmp; } if(a<c) //b,c,a { tmp=a; a=c; c=tmp; } if(b<c) //c,b,a { tmp=b; b=c; c=tmp; } printf("从大到小排序:%d,%d,%d\n",a,b,c); return 0;}2.4 数组冒泡排序#include <stdio.h>int main(int argc,char **argv){ int data[10]; int tmp; //存放临时数据 int i,j; int cnt=0; //排序比较的总次数 /*1. 录入数据*/ printf("请输入10个数:"); for(i=0;i<10;i++)scanf("%d",&data[i]); /*2. 打印源数据:*/ printf("源数据:"); for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); /*3. 冒泡排序*/ for(i=0;i<10-1;i++) //比较轮数 { for(j=0;j<10-1-i;j++) //每一轮比较的次数 { if(data[j]>data[j+1]) //从小到大 { tmp=data[j]; data[j]=data[j+1]; data[j+1]=tmp; } cnt++; } } /*4. 打印结果数据:*/ printf("排序之后的数据:"); for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); printf("排序的总次数:%d\n",cnt); return 0;}分析:/*data[]={1,2,3}第一轮: [0]和[1] [1]和[2] 2 3 1 每一轮保证最小的数在最后第二轮: [0]和[1] 3 2 1*/​排序: 从大到小1 2 3 4 5 第一轮:(1) 2 1 3 4 5(2) 2 3 1 4 5(3) 2 3 4 1 5(4) 2 3 4 5 1 第二轮:(1) 3 2 4 5 1(2) 3 4 2 5 1(3) 3 4 5 2 1(4) 3 4 5 2 1​第三轮:(1) 4 3 5 2 1(2) 4 5 3 2 1(3) 4 5 3 2 1(4) 4 5 3 2 1​第四轮:(1) 5 4 3 2 1(2) 5 4 3 2 1(3) 5 4 3 2 1(4) 5 4 3 2 12.5 冒泡排序优化#include <stdio.h>int main(int argc,char **argv){ int data[10]; int tmp; //存放临时数据 int i,j; int cnt=0; //排序比较的总次数 45: int flag=0; //标志位置 /*1. 录入数据*/ printf("请输入10个数:"); for(i=0;i<10;i++)scanf("%d",&data[i]); /*2. 打印源数据:*/ printf("源数据:"); for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); /*3. 冒泡排序*/ for(i=0;i<10-1;i++) //比较轮数 { for(j=0;j<10-1-i;j++) //每一轮比较的次数 { if(data[j]>data[j+1]) //从小到大 { tmp=data[j]; data[j]=data[j+1]; data[j+1]=tmp; flag=1; } cnt++; } if(flag==0) //不需要继续排序 { break; //跳出最外层循环 } flag=0; //清除标志位,方便第2次判断 } /*4. 打印结果数据:*/ printf("排序之后的数据:"); for(i=0;i<10;i++)printf("%d ",data[i]); printf("\n"); printf("排序的总次数:%d\n",cnt); return 0;}分析:data[]={1,2,3}第一轮: [0]和[1] [1]和[2] 2 3 1 每一轮保证最小的数在最后第二轮: [0]和[1] 3 2 12.6 数组数据的拼接(整数) int a[]={1,3,4,5,6,7}; int b[]={11,13,14,15,16,17};将a和b拼接到c数组里。 最终int c[]={1,3,4,5,6,7, 11,13,14,15,16,17} 必须保证c数组空间足够大。示例代码:#include <stdio.h>int main(int argc,char **argv){ int src_data[10]={1,2,3,4,5}; int new_data[]={6,7,8,9}; // int i; for(i=0;i<sizeof(new_data)/sizeof(new_data[0]);i++) { src_data[i+5]=new_data[i]; } for(i=0;i<10;i++) { printf("%d ",src_data[i]); } printf("\n"); return 0;}
  • [技术干货] OpenCV4.x(C++)人脸检测(眼睛、侧脸、正脸)
    一、前言OpenCV是一款广泛使用的计算机视觉库,提供了许多强大的功能,包括人脸检测和识别。人脸分类器是OpenCV中用于人脸检测的关键工具之一,能够快速准确地检测出图像中的人脸。本文将介绍如何使用OpenCV自带的人脸分类器,并对比不同分类器的精度。在日常生活中,人脸检测的应用非常广泛,例如安防、人机交互、智能交通等领域。而在计算机视觉领域,人脸检测也是一个非常热门的研究方向。OpenCV作为一款免费、开源的计算机视觉库,为我们提供了一种方便快捷的人脸检测方法。使用OpenCV的人脸分类器,可以快速地检测出图像中的正脸、侧脸和眼睛等部位,进而实现更加智能的应用。OpenCV自带的Haar级联分类器模型:haarcascade_eye.xml: 这个模型用于检测眼睛。 haarcascade_eye_tree_eyeglasses.xml: 这个模型用于检测眼镜。 haarcascade_frontalcatface.xml: 这个模型用于检测猫脸。 haarcascade_frontalcatface_extended.xml: 这个模型用于扩展的猫脸检测。 haarcascade_frontalface_alt.xml: 这个模型是一个备用的面部检测模型。 haarcascade_frontalface_alt2.xml: 这个模型是另一个备用的面部检测模型。 haarcascade_frontalface_alt_tree.xml: 这个模型是用于面部检测的备用树模型。 haarcascade_frontalface_default.xml: 这个模型是用于面部检测的默认模型。 haarcascade_fullbody.xml: 这个模型用于全身检测。 haarcascade_lefteye_2splits.xml: 这个模型用于检测左眼。 haarcascade_licence_plate_rus_16stages.xml: 这个模型用于检测俄罗斯车牌。 haarcascade_lowerbody.xml: 这个模型用于下半身检测。 haarcascade_profileface.xml: 这个模型用于侧面脸部检测。 haarcascade_righteye_2splits.xml: 这个模型用于检测右眼。 haarcascade_russian_plate_number.xml: 这个模型用于检测俄罗斯车牌号码。 haarcascade_smile.xml: 这个模型用于微笑检测。这些文件在OpenCV的安装目录下。二、代码实现2.1 人脸分类器检测人脸#include <opencv2/opencv.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/objdetect/objdetect.hpp>#include <opencv2/imgproc/imgproc.hpp>​int main(){ // 加载人脸分类器 cv::CascadeClassifier faceCascade;​ //分类器文件下载地址: https://github.com/opencv/opencv/tree/master/data/haarcascades //在OpenCV的源码目录下其实也有(opencv\build\etc\haarcascades)。 //下载后放到C盘根目录即可. faceCascade.load("C:/haarcascade_frontalface_alt2.xml");​ // 打开摄像头 cv::VideoCapture capture(0); if (!capture.isOpened()) { std::cout << "无法打开摄像头" << std::endl; return -1; }​ // 创建窗口 cv::namedWindow("Face Detection", cv::WINDOW_NORMAL);​ while (true) { cv::Mat frame; capture >> frame; // 读取视频帧​ // 将彩色图像转换为灰度图像以加快处理速度 cv::Mat grayFrame; cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);​ // 对图像进行人脸检测 std::vector<cv::Rect> faces; faceCascade.detectMultiScale(grayFrame, faces, 1.1, 3, 0, cv::Size(30, 30));​ // 在图像上绘制人脸边界框 for (size_t i = 0; i < faces.size(); i++) { cv::rectangle(frame, faces[i], cv::Scalar(0, 255, 0), 2); }​ // 显示结果图像 cv::imshow("Face Detection", frame);​ // 按下ESC键退出循环 if (cv::waitKey(1) == 27) break; }​ // 释放摄像头和窗口资源 capture.release(); cv::destroyAllWindows();​ return 0;}​​运行效果:2.2 侧脸分类器检测人脸#include <opencv2/opencv.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/objdetect/objdetect.hpp>#include <opencv2/imgproc/imgproc.hpp>​int main(){ // 加载人脸分类器 cv::CascadeClassifier faceCascade;​ //分类器文件下载地址: https://github.com/opencv/opencv/tree/master/data/haarcascades //在OpenCV的源码目录下其实也有(opencv\build\etc\haarcascades)。 //下载后放到C盘根目录即可. faceCascade.load("C:/haarcascade_profileface.xml");​ // 打开摄像头 cv::VideoCapture capture(0); if (!capture.isOpened()) { std::cout << "无法打开摄像头" << std::endl; return -1; }​ // 创建窗口 cv::namedWindow("Face Detection", cv::WINDOW_NORMAL);​ while (true) { cv::Mat frame; capture >> frame; // 读取视频帧​ // 将彩色图像转换为灰度图像以加快处理速度 cv::Mat grayFrame; cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);​ // 对图像进行人脸检测 std::vector<cv::Rect> faces; faceCascade.detectMultiScale(grayFrame, faces, 1.1, 3, 0, cv::Size(30, 30));​ // 在图像上绘制人脸边界框 for (size_t i = 0; i < faces.size(); i++) { cv::rectangle(frame, faces[i], cv::Scalar(0, 255, 0), 2); }​ // 显示结果图像 cv::imshow("Face Detection", frame);​ // 按下ESC键退出循环 if (cv::waitKey(1) == 27) break; }​ // 释放摄像头和窗口资源 capture.release(); cv::destroyAllWindows();​ return 0;}三、OpenCV安装3.1 OpenCV下载OpenCV下载地址:cid:link_1目前最新的版本是4.3,那么就下载最新的版本。下载下来是一个exe文件,双击就可以安装,实际就是解压,可以选择解压的路径,解压出来的文件包含源文件、库文件一大堆,比较大,可以直接放在一个固定的目录,后面程序里直接填路径来调用即可。 这个下载下来的库文件里只包含了X64的库,适用于MSVS 64位编译器。解压完成。解压后在build目录下看到有VC14和VC15的目录。这表示什么含义呢?OpenCV VC14和VC15的区别在于它们所使用的编译器版本不同。VC14使用的是Visual Studio 2015的编译器,而VC15使用的是Visual Studio 2017的编译器。这意味着VC15可以利用更先进的编译器技术,从而提高代码的性能和效率。此外,VC15还支持更多的C++11和C++14特性,使得开发更加方便和灵活。3.2 VS2022环境我这里介绍下我用的环境安装过程。 所有版本的VS都可以的,OpenCV只是个第三方库,哪里调用都行。我当前环境是在Windows下,IDE用的是地表最强IDEVS2022。下载地址:cid:link_0因为我这里只需要用到C++和C语言编程,那么安装的时候可以自己选择需要安装的包。安装好之后,创建项目。3.3 新建工程这是创建好的空工程,我写了一段OpenCV的代码。工程创建好之后需要添加OpenCV头文件的引用和OpenCV库文件的引用。点击这个属性。第一步在C++、常规 选项里添加用到的OpenCV头文件路径。这个路径具体在哪里,要看自己的OpenCV安装路径。为了方便大家粘贴,我这里贴出来。C:/opencv_4.x/opencv/build/include/opencv2C:/opencv_4.x/opencv/build/include/opencvC:/opencv_4.x/opencv/build/include第二步就是设置库文件的路径。 在链接器-输入 选项里,添加依赖选项。这个库在哪里,根据自己OpenCV解压的路径进行填。这是我的路径:C:/opencv_4.x/opencv/build/x64/vc15/lib/opencv_world430.lib3.4 运行库的拷贝如果写好了OpenCV代码,直接按下Ctrl + F5 运行程序,如果第一次运行,会报错。提示如下:这个提示是告诉我们,程序运行时找不到OpenCV的运行库。 只要使用了第三方库都需要知道这一点,运行的时候需要把用到的库拷贝到生成的exe同级目录下。把OpenCV解压目录下的opencv_world430.dll文件拷贝到编译出来的exe运行同级目录下。 否则程序运行因为找不到库而导致异常结束。拷贝到这里。再次运行,程序就正常的运行了。
  • [技术干货] STM32单片机采用环形缓冲区实现串口中断数据接收管理
    一、前言在嵌入式系统开发中,与上位机进行串口通信是非常常见的场景。上位机可以通过串口发送指令或者数据给嵌入式设备,而嵌入式设备需要可靠地接收并解析这些数据,以执行相应的操作。然而,在串口通信过程中,上位机发送数据的速率往往与嵌入式设备接收和处理数据的速率不一致,这就可能导致数据的丢失或者误解析。为了解决这个问题,决定设计并实现一个环形缓冲区来进行数据接收管理。环形缓冲区是一种高效的数据结构,适用于数据产生速率快于消费速率的场景。它具有固定大小的缓冲区,并且可以循环利用空间,保证数据的连续存储和有效利用。在本项目中,选择使用STM32微控制器来实现串口数据接收功能。STM32具有丰富的外设资源和强大的性能,非常适合用于串口通信和数据处理。通过在STM32上实现环形缓冲区,可以实现以下目标:(1)数据稳定接收:通过使用环形缓冲区,确保即使在接收数据速率慢于发送速率的情况下,数据也能够得到稳定的接收,避免数据丢失。(2)数据缓存和管理:环形缓冲区可以作为一个数据缓存区,将接收到的数据暂时存储起来,以便后续处理。这样可以降低数据处理的延迟和复杂性。(3)数据解析和应用:通过从环形缓冲区中读取数据,并进行解析和处理,嵌入式设备可以根据接收到的数据执行相应的操作,如控制外部设备或响应上位机指令。通过使用环形缓冲区管理串口接收的数据,可以实现可靠的数据接收和处理,并提高系统的稳定性和可靠性。同时,该方案也适用于其他嵌入式系统和通信场景。二、实现思路(1)定义环形缓冲区的结构体:首先,需要定义一个表示环形缓冲区的结构体,其中包含以下成员变量:缓冲区的大小(capacity):表示环形缓冲区的容量,即可以存储的最大元素数量。写指针(write_ptr):表示当前可写入数据的位置。读指针(read_ptr):表示当前可读取数据的位置。数据数组(buffer):用于存储实际的数据。(2)初始化环形缓冲区:在使用环形缓冲区之前,需要进行初始化。初始化时,将缓冲区的大小、写指针和读指针都设置为初始位置,通常都是0。(3)写入数据:当有新的数据要写入缓冲区时,需要执行以下操作:检查缓冲区是否已满,如果已满则无法写入新的数据。将数据写入当前写指针所指向的位置。更新写指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。(4)读取数据:当需要从缓冲区中读取数据时,需要执行以下操作:检查缓冲区是否为空,如果为空则无数据可读取。读取当前读指针所指向的数据。更新读指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。(5)判断缓冲区状态:为了方便使用和管理缓冲区,可以实现一些用于判断缓冲区状态的函数,例如:is_full():判断缓冲区是否已满。is_empty():判断缓冲区是否为空。实现环形缓冲区时,需要注意:写指针和读指针的位置计算要考虑到环形特性,即超过缓冲区容量时需要进行取模运算。缓冲区大小要合理选择,根据实际需求确定,以充分利用内存资源并避免数据丢失。多线程或中断环境下的并发访问要考虑数据同步和互斥操作,以避免竞争条件和数据不一致的问题。通过以上思路,可以在C语言中实现一个简单高效的环形缓冲区,用于存储和管理数据,在数据收发过程中提高系统的稳定性和可靠性。三、 C语言实现验证思路#include <stdio.h>#include <stdlib.h>​#define BUFFER_SIZE 10​typedef struct { int* buffer; // 缓冲区数组指针 int size; // 缓冲区大小 int head; // 头部索引 int tail; // 尾部索引} CircularBuffer;​// 创建环形缓冲区CircularBuffer* createCircularBuffer(int size) { CircularBuffer* cb = (CircularBuffer*)malloc(sizeof(CircularBuffer)); // 分配内存空间 cb->buffer = (int*)malloc(sizeof(int) * size); // 分配缓冲区数据的内存空间 cb->size = size; // 设置缓冲区大小 cb->head = 0; // 初始化头部索引为0 cb->tail = 0; // 初始化尾部索引为0 return cb;}​// 销毁环形缓冲区void destroyCircularBuffer(CircularBuffer* cb) { free(cb->buffer); // 释放缓冲区数据的内存空间 free(cb); // 释放缓冲区结构体的内存空间}​// 判断环形缓冲区是否已满int isCircularBufferFull(CircularBuffer* cb) { return ((cb->tail + 1) % cb->size == cb->head);}​// 判断环形缓冲区是否为空int isCircularBufferEmpty(CircularBuffer* cb) { return (cb->head == cb->tail);}​// 写入数据到环形缓冲区void writeData(CircularBuffer* cb, int data) { if (isCircularBufferFull(cb)) { // 如果缓冲区已满,则无法写入数据 printf("Circular buffer is full. Data cannot be written.\n"); return; } cb->buffer[cb->tail] = data; // 将数据写入缓冲区的尾部 cb->tail = (cb->tail + 1) % cb->size; // 更新尾部索引,循环利用缓冲区空间}​// 从环形缓冲区读取数据int readData(CircularBuffer* cb) { if (isCircularBufferEmpty(cb)) { // 如果缓冲区为空,则无数据可读取 printf("Circular buffer is empty. No data to read.\n"); return -1; // 返回一个默认值表示读取失败 } int data = cb->buffer[cb->head]; // 从缓冲区的头部读取数据 cb->head = (cb->head + 1) % cb->size; // 更新头部索引,循环利用缓冲区空间 return data;}​int main() { CircularBuffer* cb = createCircularBuffer(BUFFER_SIZE); // 创建大小为BUFFER_SIZE的环形缓冲区​ writeData(cb, 1); // 写入数据1 writeData(cb, 2); // 写入数据2 writeData(cb, 3); // 写入数据3​ printf("Read data: %d\n", readData(cb)); // 读取数据并打印 printf("Read data: %d\n", readData(cb));​ writeData(cb, 4); writeData(cb, 5);​ printf("Read data: %d\n", readData(cb)); printf("Read data: %d\n", readData(cb)); printf("Read data: %d\n", readData(cb));​ destroyCircularBuffer(cb); // 销毁环形缓冲区​ return 0;}​四、STM32串口接收#define BUFFER_SIZE 256​typedef struct { uint8_t buffer[BUFFER_SIZE]; uint16_t head; uint16_t tail;} CircularBuffer;​// 初始化环形缓冲区void CircularBuffer_Init(CircularBuffer* cb) { cb->head = 0; cb->tail = 0;}​// 判断环形缓冲区是否已满bool CircularBuffer_IsFull(const CircularBuffer* cb) { return (cb->head + 1) % BUFFER_SIZE == cb->tail;}​// 判断环形缓冲区是否为空bool CircularBuffer_IsEmpty(const CircularBuffer* cb) { return cb->head == cb->tail;}​// 向环形缓冲区写入数据bool CircularBuffer_Write(CircularBuffer* cb, uint8_t data) { if (CircularBuffer_IsFull(cb)) { // 缓冲区已满,无法写入 return false; } cb->buffer[cb->head] = data; cb->head = (cb->head + 1) % BUFFER_SIZE; return true;}​// 从环形缓冲区读取数据bool CircularBuffer_Read(CircularBuffer* cb, uint8_t* data) { if (CircularBuffer_IsEmpty(cb)) { // 缓冲区为空,无数据可读取 return false; } *data = cb->buffer[cb->tail]; cb->tail = (cb->tail + 1) % BUFFER_SIZE; return true;}​// 获取环形缓冲区剩余大小uint16_t CircularBuffer_GetRemainingSize(const CircularBuffer* cb) { if (cb->head >= cb->tail) { return BUFFER_SIZE - (cb->head - cb->tail); } else { return cb->tail - cb->head - 1; }}​// 获取环形缓冲区已写入大小uint16_t CircularBuffer_GetWrittenSize(const CircularBuffer* cb) { if (cb->head >= cb->tail) { return cb->head - cb->tail; } else { return BUFFER_SIZE - (cb->tail - cb->head - 1); }}​// 从环形缓冲区读取指定长度的数据bool CircularBuffer_ReadData(CircularBuffer* cb, uint8_t* data, uint16_t length) { if (CircularBuffer_GetWrittenSize(cb) < length) { return false; // 缓冲区中的数据不足 } for (uint16_t i = 0; i < length; ++i) { if (!CircularBuffer_Read(cb, &data[i])) { return false; // 读取数据出错 } } return true;}​// 向环形缓冲区写入指定长度的数据bool CircularBuffer_WriteData(CircularBuffer* cb, const uint8_t* data, uint16_t length) { if (CircularBuffer_GetRemainingSize(cb) < length) { return false; // 缓冲区剩余空间不足 } for (uint16_t i = 0; i < length; ++i) { if (!CircularBuffer_Write(cb, data[i])) { return false; // 写入数据出错 } } return true;}​​​// 示例:STM32串口接收中断处理函数void USART_Receive_IRQHandler(void) { uint8_t data = USART_ReceiveData(USART1); // 获取接收到的数据 if (!CircularBuffer_Write(&rxBuffer, data)) { // 缓冲区已满,处理错误 }}​​在代码中,定义了一个名为CircularBuffer的结构体来表示环形缓冲区。包含了一个具有固定大小的数组buffer用于存储数据,以及头部指针head和尾部指针tail用于管理数据的读写位置。接下来,实现了一些函数来对环形缓冲区进行操作。CircularBuffer_Init函数用于初始化环形缓冲区;CircularBuffer_IsFull和CircularBuffer_IsEmpty函数分别判断缓冲区是否已满和是否为空;CircularBuffer_Write函数用于向缓冲区写入数据;CircularBuffer_Read函数用于从缓冲区读取数据。CircularBuffer_GetRemainingSize函数用于获取环形缓冲区的剩余大小,即还能写入多少个字节的数据;CircularBuffer_GetWrittenSize函数用于获取已经写入到缓冲区的字节数;CircularBuffer_ReadData函数用于从环形缓冲区读取指定长度的数据,将其存储到提供的数据数组中;CircularBuffer_WriteData函数用于向环形缓冲区写入指定长度的数据,从提供的数据数组中复制相应的字节。使用这些方便函数,可以更方便地管理环形缓冲区,实现数据的读取和写入。最后,给出了一个示例,展示在STM32串口接收中断处理函数中将接收到的数据写入环形缓冲区。在中断处理函数中,通过USART_ReceiveData函数获取接收到的数据,调用CircularBuffer_Write函数将数据写入缓冲区。
  • [技术干货] 基于单片机设计的智慧农业大棚检测系统
    一、设计目标本项目基于单片机设计一个智慧农业大棚检测系统,以提供实时监测和管理大棚环境的关键参数。系统支持环境温度、湿度检测,光照强度检测,并能根据预设的阀值进行报警提示。为了实现数据的显示和管理,该系统还利用Qt开发了一款对应的Android手机APP,通过蓝牙传输模块将单片机采集到的数据传递到手机APP上进行显示和管理。具体功能如下:【1】环境温度和湿度检测:系统采用SHT30温湿度传感器,能够实时监测大棚内的温度和湿度,并将数据传输给单片机进行处理。【2】光照强度检测:系统采用BH1750光照传感器,能够实时监测大棚内的光照强度,并将数据传输给单片机进行处理。【3】报警阀值设置:系统支持按键操作,用户可以通过按键调整报警阀值,以适应不同的农业环境需求。【4】报警声音提示:当温度、湿度或光照强度超过设定的阀值时,系统将触发报警,通过连接的蜂鸣器发出声音提示,提醒用户注意。【5】数据显示和管理:利用Qt开发的Android手机APP能够接收通过蓝牙传输模块从单片机端传递过来的数据,并在手机上进行实时显示和管理。用户可以通过手机APP查看当前的温度、湿度和光照强度数据,同时也可以设置报警阀值。通过上述设计,该智慧农业大棚检测系统能够为农业生产提供实时的环境监测和报警功能,帮助农民有效管理大棚环境,提高农作物的产量和质量。同时,通过手机APP的使用,用户可以方便地查看和管理数据,实现远程监控和控制,提高农业生产的便捷性和智能化水平。二、总体设计方案2.1 硬件设计主控芯片采用STM32F103RCT6,具有较高的性能和丰富的外设接口。温湿度检测模块采用SHT30传感器,可准确采集环境温度和湿度数据。光照强度检测模块采用BH1750传感器,能够实时监测大棚内的光照情况。本地报警提示采用蜂鸣器,发出声音提醒农民进行处理。通过蓝牙模块HC05,将STM32采集到的数据传输到Android手机APP上。2.2 软件设计在STM32中编写固件程序,实现温湿度传感器和光照强度传感器的数据采集和处理。设计蜂鸣器的驱动程序,根据设定的阈值判断是否触发报警。使用蓝牙模块HC05与Android手机APP进行通信,将采集到的数据传输到手机APP上。在Android手机APP中,利用Qt开发界面,实现数据显示、阈值设定和报警提示等功能。2.3 整体流程STM32通过SHT30和BH1750传感器采集环境温度、湿度和光照强度数据。处理采集到的数据,判断是否触发报警条件。如果达到报警条件,则通过蜂鸣器发出声音提示。将数据通过蓝牙模块HC05传输到Android手机APP上。在Android手机APP上,实时显示大棚内的温湿度和光照强度数据。农民可以通过手机APP设置报警阈值,当超过或低于设定的阈值时,会触发报警提示。该方案实现了温湿度、光照强度的实时检测和数据传输,并提供了报警功能。通过这个智慧农业大棚检测系统,农民可以方便地监测大棚内的环境状况,及时调控和管理,提高农作物的生长效果和产量。三、硬件电路设计本项目的硬件电路设计主要涉及主控芯片STM32F103RCT6的连接及传感器模块的接口设计。以下是项目的硬件电路设计概述:3.1 主控芯片连接STM32F103RCT6作为主控芯片,负责整个系统的控制和数据处理。它与其他模块通过引脚连接进行数据的接收和发送。 需要为主控芯片提供适当的电源供电,包括正常工作电压和逻辑电压。3.2 温湿度传感器连接温湿度传感器SHT30通过I2C总线连接到主控芯片。主控芯片上的I2C接口引脚(如SDA和SCL)与传感器的对应引脚相连,以实现数据的读取和控制。3.3 光照传感器连接光照传感器BH1750通过I2C总线连接到主控芯片。主控芯片上的I2C接口引脚与传感器的对应引脚相连,以实现数据的读取和控制。3.4 报警蜂鸣器连接报警蜂鸣器通过一个GPIO引脚与主控芯片相连。当报警条件触发时,主控芯片控制该引脚输出高电平信号,以激活蜂鸣器发出声音提示。3.5 HC05蓝牙模块连接HC05蓝牙模块用于实现单片机与Android手机APP之间的数据传输。它通过串口通信与主控芯片相连,主控芯片上的对应串口引脚(如UART_TX和UART_RX)与蓝牙模块的对应引脚相连。四、软件设计4.1 主控芯片模块主控芯片(如STM32F103RCT6)负责整个系统的控制和数据处理。它与其他硬件模块相连接,接收传感器数据,进行数据处理和报警判断,并控制蜂鸣器的发声。4.2 温湿度传感器模块温湿度传感器(如SHT30)通过I2C总线与主控芯片相连,负责实时监测大棚内的温度和湿度。传感器模块将采集到的数据传输给主控芯片进行处理。实现代码如下:以下是STM32标准库驱动SHT30传感器读取温湿度,并将数据通过串口打印出来:#include "stm32f10x.h"#include "stdio.h"​#define SHT30_ADDR 0x44​void I2C1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure;​ // 使能I2C1和GPIOB时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);​ // I2C1引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出 GPIO_Init(GPIOB, &GPIO_InitStructure);​ // I2C1配置 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 50%占空比 I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机模式下无效 I2C_InitStructure.I2C_Ack = I2C_Ack_Disable; // 禁止应答 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz的速度 I2C_Init(I2C1, &I2C_InitStructure);​ // 使能I2C1 I2C_Cmd(I2C1, ENABLE);}​void I2C1_Start(void){ // 发送起始信号 I2C_GenerateSTART(I2C1, ENABLE);​ // 等待起始信号发送完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) ;}​void I2C1_Stop(void){ // 发送停止信号 I2C_GenerateSTOP(I2C1, ENABLE);}​void I2C1_WriteByte(uint8_t byte){ // 发送一个字节的数据 I2C_SendData(I2C1, byte);​ // 等待发送完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) ;}​uint16_t I2C1_ReadByteAck(void){ uint16_t data;​ // 使能应答 I2C_AcknowledgeConfig(I2C1, ENABLE); I2C_GenerateACK(I2C1, ENABLE);​ // 等待接收完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) ;​ // 读取接收到的数据 data = I2C_ReceiveData(I2C1);​ return data;}​uint16_t I2C1_ReadByteNack(void){ uint16_t data;​ // 禁止应答 I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateACK(I2C1, DISABLE);​ // 等待接收完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) ;​ // 读取接收到的数据 data = I2C_ReceiveData(I2C1);​ return data;}​void SHT30_Init(void){ // 发送软件复位指令 I2C1_Start(); I2C1_WriteByte(SHT30_ADDR << 1); I2C1_WriteByte(0x30); I2C1_WriteByte(0xA2); I2C1_Stop();​ delay_ms(100);}​void SHT30_Measure(float *temperature, float *humidity){ uint8_t buf[6]; uint16_t temperature_raw, humidity_raw;​ // 发送测量指令 I2C1_Start(); I2C1_WriteByte(SHT30_ADDR << 1); I2C1_WriteByte(0x2C); I2C1_WriteByte(0x06); I2C1_Stop();​ delay_ms(20);​ // 读取测量结果 I2C1_Start(); I2C1_WriteByte((SHT30_ADDR << 1) | 0x01); buf[0] = I2C1_ReadByteAck(); buf[1] = I2C1_ReadByteAck(); buf[2] = I2C1_ReadByteAck(); buf[3] = I2C1_ReadByteAck(); buf[4] = I2C1_ReadByteAck(); buf[5] = I2C1_ReadByteNack(); I2C1_Stop();​ // 计算温度值 temperature_raw = (buf[0] << 8) | buf[1]; *temperature = -45.0 + 175.0 * (float)temperature_raw / 65535.0;​ // 计算湿度值 humidity_raw = (buf[3] << 8) | buf[4]; *humidity = 100.0 * (float)humidity_raw / 65535.0;}​void USART1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure;​ // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);​ // USART1引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure);​ // USART1配置 USART_InitStructure.USART_BaudRate = 115200; // 波特率为115200 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure);​ // 使能USART1 USART_Cmd(USART1, ENABLE);}​void USART1_SendChar(char ch){ // 等待发送缓冲区为空 while (!(USART1->SR & USART_FLAG_TXE)) ;​ // 发送一个字符 USART_SendData(USART1, ch);}​int fputc(int ch, FILE *f){ // 将数据通过串口发送 USART1_SendChar((char)ch); return ch;}​void delay_ms(uint32_t ms){ while (ms--) { uint32_t count = 12000; while (count--) ; }}​int main(void){ float temperature, humidity;​ I2C1_Init(); USART1_Init();​ SHT30_Init();​ printf("SHT30 Temperature and Humidity Test\n");​ while (1) { SHT30_Measure(&temperature, &humidity); printf("Temperature: %.2f°C, Humidity: %.2f%%\n", temperature, humidity);​ delay_ms(1000); // 每隔1秒测量一次温湿度 }}以上代码通过I2C总线驱动STM32F103读取SHT30温湿度传感器的数据,并通过USART1串口打印出温度和湿度值。代码设计流程介绍:【1】引入头文件:#include "stm32f10x.h"#include "stdio.h"此处引入了STM32F10x系列微控制器的相关头文件以及标准输入输出库的头文件。【2】定义宏和函数:#define SHT30_ADDR 0x44定义了SHT30传感器的I2C地址为0x44。void I2C1_Init(void);void I2C1_Start(void);void I2C1_Stop(void);void I2C1_WriteByte(uint8_t byte);uint16_t I2C1_ReadByteAck(void);uint16_t I2C1_ReadByteNack(void);定义了一系列用于控制I2C总线的函数。void SHT30_Init(void);void SHT30_Measure(float *temperature, float *humidity);定义了初始化SHT30传感器和测量温湿度的函数。void USART1_Init(void);void USART1_SendChar(char ch);int fputc(int ch, FILE *f);定义了初始化USART1串口和发送字符的函数,以及重定向标准输出流的函数。void delay_ms(uint32_t ms);定义了延时函数,用于在实现中添加延时。【3】初始化函数:void I2C1_Init(void)该函数用于初始化I2C1总线和相关的引脚。使能了I2C1和GPIOB的时钟,然后配置了I2C1的引脚,包括引脚的速度和模式等。接着对I2C1进行配置,包括模式、占空比、从机地址等参数,并最后使能I2C1总线。void USART1_Init(void)该函数用于初始化USART1串口和相关的引脚。使能了USART1和GPIOA的时钟,然后配置了USART1的引脚,包括引脚的速度和模式等。接着对USART1进行配置,包括波特率、字长、停止位、校验位等参数,并最后使能USART1串口。【4】I2C总线控制函数:void I2C1_Start(void)void I2C1_Stop(void)void I2C1_WriteByte(uint8_t byte)uint16_t I2C1_ReadByteAck(void)uint16_t I2C1_ReadByteNack(void)这些函数用于控制I2C总线的起始、停止、写数据和读数据操作。具体实现可以参考STM32的标准库函数。【5】SHT30传感器初始化和测量函数:void SHT30_Init(void)void SHT30_Measure(float *temperature, float *humidity)SHT30_Init函数用于初始化SHT30传感器。在函数中,发送软件复位指令给传感器,然后延时一段时间等待传感器重置。SHT30_Measure函数用于测量温湿度数据。在函数中,发送测量指令给传感器,然后延时等待传感器完成测量。接着从传感器读取温湿度数据,并通过指针参数返回给主程序。【6】串口控制函数:void USART1_SendChar(char ch)int fputc(int ch, FILE *f)USART1_SendChar函数用于通过USART1串口发送一个字符。在函数中,通过轮询USART状态寄存器的空闲标志位,判断发送缓冲区是否为空,然后把字符写入数据寄存器进行发送。fputc函数是C库函数的重定向函数,用于将标准输出的字符发送到USART1串口。这里对于每个调用printf函数输出的字符,都会通过USART1_SendChar函数发送出去。【7】延时函数:void delay_ms(uint32_t ms)这个函数用于实现毫秒级的延时。在函数中,通过循环等待的方式实现了延时。【8】主函数:int main(void)在主函数中,调用I2C1_Init和USART1_Init函数初始化I2C总线和USART1串口。然后调用SHT30_Init函数初始化SHT30传感器。进入主循环后,通过循环调用SHT30_Measure函数测量温湿度数据,并通过printf函数打印出来。最后通过delay_ms函数延时1秒。4.3 光照传感器模块光照传感器(如BH1750)通过I2C总线与主控芯片相连,负责实时监测大棚内的光照强度。传感器模块将采集到的数据传输给主控芯片进行处理。实现代码如下:使用STM32标准库编写代码驱动BH1750读取环境光照强度。#include "stm32f10x.h"#include "stdio.h"#define BH1750_ADDR 0x23void I2C1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; // 使能I2C1和GPIOB时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // I2C1引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出 GPIO_Init(GPIOB, &GPIO_InitStructure); // I2C1配置 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 50%占空比 I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机模式下无效 I2C_InitStructure.I2C_Ack = I2C_Ack_Disable; // 禁止应答 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz的速度 I2C_Init(I2C1, &I2C_InitStructure); // 使能I2C1 I2C_Cmd(I2C1, ENABLE);}void I2C1_Start(void){ // 发送起始信号 I2C_GenerateSTART(I2C1, ENABLE); // 等待起始信号发送完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) ;}void I2C1_Stop(void){ // 发送停止信号 I2C_GenerateSTOP(I2C1, ENABLE);}void I2C1_WriteByte(uint8_t byte){ // 发送一个字节的数据 I2C_SendData(I2C1, byte); // 等待发送完成 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) ;}uint16_t BH1750_ReadData(void){ uint16_t data = 0; // 设置BH1750模式(连续高分辨率测量模式) I2C1_Start(); I2C1_WriteByte(BH1750_ADDR); I2C1_WriteByte(0x10); I2C1_Stop(); // 延时等待测量结束 delay_ms(20); // 读取光照强度数据 I2C1_Start(); I2C1_WriteByte(BH1750_ADDR | 1); data = (I2C_ReceiveData(I2C1) << 8); I2C_AcknowledgeConfig(I2C1, DISABLE); I2C1_Stop(); delay_ms(2); I2C_AcknowledgeConfig(I2C1, ENABLE); I2C1_Start(); I2C1_WriteByte(BH1750_ADDR | 1); data |= I2C_ReceiveData(I2C1); I2C1_Stop(); return data;}void USART1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // USART1引脚配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); // USART1配置 USART_InitStructure.USART_BaudRate = 115200; // 波特率为115200 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE);}void USART1_SendChar(char ch){ // 等待发送缓冲区为空 while (!(USART1->SR & USART_FLAG_TXE)) ; // 发送一个字符 USART_SendData(USART1, ch);}int fputc(int ch, FILE *f){ // 将数据通过串口发送 USART1_SendChar((char)ch); return ch;}void delay_ms(uint32_t ms){ while (ms--) { uint32_t count = 12000; while (count--) ; }}int main(void){ uint16_t lightIntensity; I2C1_Init(); USART1_Init(); printf("BH1750 Light Intensity Test\n"); while (1) { lightIntensity = BH1750_ReadData(); printf("Light Intensity: %d lux\n", lightIntensity); delay_ms(1000); // 每隔1秒读取一次光照强度 }}【1】上面代码里定义了BH1750的地址BH1750_ADDR为0x23,这是BH1750的默认地址。【2】在I2C1_Init()函数中,初始化了I2C1总线和相关的GPIO引脚。通过RCC_APB1PeriphClockCmd和RCC_APB2PeriphClockCmd函数使能I2C1和GPIOB时钟,然后配置GPIOB的引脚6和7为开漏输出模式(GPIO_Mode_AF_OD)。接着,使用I2C_Init函数初始化I2C1,并设置其工作模式为I2C_Mode_I2C、占空比为50%、禁止应答、7位地址模式以及100kHz的通信速率。最后,通过I2C_Cmd函数使能I2C1。【3】I2C1_Start()函数用于发送I2C总线的起始信号。调用I2C_GenerateSTART函数发送起始信号,并使用I2C_CheckEvent函数等待起始信号发送完成。【4】I2C1_Stop()函数用于发送I2C总线的停止信号。调用I2C_GenerateSTOP函数发送停止信号。【5】I2C1_WriteByte()函数用于向I2C设备发送一个字节的数据。通过I2C_SendData函数发送数据,并使用I2C_CheckEvent函数等待发送完成。【6】BH1750_ReadData()函数用于读取光照强度数据。,发送启动测量指令和模式设置指令(连续高分辨率测量模式)。然后等待测量结束的延时时间(20ms)。接着,通过两次读取数据寄存器的方式获取光照强度数据,并将其拼接为一个16位的无符号整数。【7】USART1_Init()函数用于初始化USART1串口和相关的GPIO引脚。使用RCC_APB2PeriphClockCmd函数使能USART1和GPIOA时钟,然后配置GPIOA的引脚9为复用推挽输出模式(GPIO_Mode_AF_PP)。接着,通过USART_Init函数初始化USART1,并设置其波特率为115200、数据位长度为8位、停止位为1位、无奇偶校验、无硬件流控制。最后,通过USART_Cmd函数使能USART1。【8】USART1_SendChar()函数用于发送一个字符到USART1串口。使用USART_SR寄存器的USART_FLAG_TXE标志位检查发送缓冲区是否为空,然后通过USART_SendData函数发送字符数据。【9】fputc()函数重定向了输出流,使得通过printf函数打印的字符可以发送到USART1串口。在该函数中调用USART1_SendChar函数发送字符数据,并返回该字符。【10】delay_ms()函数用于进行延时,单位为毫秒。该函数使用嵌套循环实现了简单的延时功能,不同的系统时钟频率可能需要适当调整。【11】在main()函数中,依次调用I2C1_Init()和USART1_Init()函数进行初始化操作。然后,通过printf函数向串口发送初始信息。【12】在主循环中,通过调用BH1750_ReadData()函数读取光照强度数据,并使用printf函数将其打印到串口。然后通过delay_ms函数进行1秒的延时,等待下一次读取。4.4 报警蜂鸣器模块报警蜂鸣器通过一个GPIO引脚与主控芯片相连。当报警条件满足时,主控芯片控制该引脚输出高电平信号,以激活蜂鸣器发出声音提示。使用STM32F103标准库编写的蜂鸣器控制代码:#include "stm32f10x.h"#define BEEP_GPIO_PORT GPIOA#define BEEP_GPIO_PIN GPIO_Pin_8void BEEP_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置蜂鸣器引脚为推挽输出模式 GPIO_InitStructure.GPIO_Pin = BEEP_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStructure);}void BEEP_On(void){ GPIO_SetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN);}void BEEP_Off(void){ GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN);}int main(void){ BEEP_Init(); while (1) { // 控制蜂鸣器开启和关闭 BEEP_On(); Delay_ms(500); BEEP_Off(); Delay_ms(500); }}在代码中,使用了GPIOA的第8个引脚作为蜂鸣器的控制引脚。BEEP_Init函数用于初始化蜂鸣器引脚,将其配置为推挽输出模式。BEEP_On和BEEP_Off函数分别用于开启和关闭蜂鸣器。在main函数中,通过循环控制蜂鸣器以500ms的间隔进行开启和关闭操作。4.5 HC05蓝牙模块模块HC05蓝牙模块通过串口通信与主控芯片相连,负责实现与Android手机APP之间的数据传输和通信。它接收主控芯片发送的数据,并通过蓝牙与手机APP进行交互。使用STM32标准库编写代码,用于通过串口2驱动HC05模块,并进行配置和数据通信:#include "stm32f10x.h"#include "stdio.h"// HC05配置指令#define AT_CMD_MODE "AT+CMODE=0\r\n" // 配置为从模式#define AT_CMD_PW "AT+PSWD=1234\r\n" // 配对密码设置为1234// 函数声明void USART2_Init(void);void USART2_SendChar(char ch);void USART2_SendString(char* str);char USART2_Receive(void);int main(void){ // 初始化USART2串口 USART2_Init(); // 发送AT指令配置HC05为从模式 USART2_SendString(AT_CMD_MODE); for(int i = 0; i < 1000000; i++); // 等待一段时间 // 发送AT指令配置配对密码 USART2_SendString(AT_CMD_PW); for(int i = 0; i < 1000000; i++); // 等待一段时间 while(1) { // 接收手机发送的数据 char data = USART2_Receive(); // 处理接收到的数据,例如发送回应等 // ... }}void USART2_Init(void){ // 使能USART2和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 配置USART2的引脚 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // USART2_TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO速度为50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // USART2_RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART2的参数 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 9600; // 波特率为9600 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位长度为8位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位为1位 USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 支持收发模式 USART_Init(USART2, &USART_InitStructure); // 使能USART2 USART_Cmd(USART2, ENABLE);}void USART2_SendChar(char ch){ while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET); // 等待发送缓冲区为空 USART_SendData(USART2, (uint16_t)ch); // 发送数据}void USART2_SendString(char* str){ while(*str) { USART2_SendChar(*str++); }}char USART2_Receive(void){ while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET); // 等待接收缓冲区非空 return (char)USART_ReceiveData(USART2);}以上代码通过USART2串口与HC05模块进行通信,并发送AT指令对其进行配置。其中,AT_CMD_MODE用于将HC05配置为从模式,AT_CMD_PW用于设置配对密码为1234。代码设计思路介绍:【1】引入头文件:#include "stm32f10x.h"#include "stdio.h"引入了STM32F10x系列微控制器的相关头文件和标准输入输出库的头文件。【2】定义宏和函数:#define AT_CMD_MODE "AT+CMODE=0\r\n"#define AT_CMD_PW "AT+PSWD=1234\r\n"void USART2_Init(void);void USART2_SendChar(char ch);void USART2_SendString(char* str);char USART2_Receive(void);定义了用于配置HC05模块的AT指令,以及用于初始化USART2串口、发送字符和字符串、接收字符的函数。【3】USART2串口初始化函数:void USART2_Init(void)该函数用于初始化USART2串口和相关引脚。使能了USART2和GPIOA的时钟,然后配置了USART2的引脚,包括引脚的模式和速度等。接着对USART2进行配置,设置波特率、数据位长度、停止位、奇偶校验位等参数,并最后使能USART2串口。【4】发送字符和字符串函数:void USART2_SendChar(char ch)void USART2_SendString(char* str)这些函数用于通过USART2串口发送字符和字符串。对于USART2_SendChar函数,它会等待发送缓冲区为空,然后将字符写入数据寄存器进行发送。对于USART2_SendString函数,它会遍历字符串中的每个字符,并调用USART2_SendChar函数进行发送。【5】接收字符函数:char USART2_Receive(void)该函数用于从USART2串口接收一个字符。它会等待接收缓冲区非空,然后读取数据寄存器的值并返回接收到的字符。此函数在主循环中可以用于接收HC05模块发送的数据。【6】主函数:int main(void)在主函数中,调用USART2_Init函数初始化USART2串口。然后使用USART2_SendString函数依次发送配置指令AT_CMD_MODE和AT_CMD_PW给HC05模块。发送完指令后,通过循环调用USART2_Receive函数接收HC05模块发送的数据。五、调试过程在项目设计完成后,进行测试和调试是非常重要的,以确保系统的正常运行和功能的有效性。下面是本项目的测试和调试过程流程:5.1 硬件测试检查硬件组装和连接是否正确,包括主控芯片STM32F103RCT6、传感器模块SHT30和BH1750以及蓝牙模块HC05的连接。使用示波器或多用途测试仪检测各个模块的电源供应和信号线连接是否正常。测试温湿度传感器(SHT30)和光照强度传感器(BH1750)是否能够正确采集环境数据。测试蜂鸣器是否能够发出合适的声音提示。5.2 固件程序测试在STM32开发环境中编译程序,将固件程序烧录到主控芯片STM32F103RCT6上。使用串口调试助手等工具,与STM32建立通信连接,检查数据的传输和接收是否正常。对温湿度传感器和光照强度传感器进行数据采集测试,观察是否能够准确读取传感器数据。设置阈值并测试报警功能,确保报警触发条件和报警提示的准确性。5.3 Android手机APP测试安装开发好的Android手机APP到测试设备上,确保安装过程顺利。打开APP,并与蓝牙模块HC05进行连接,观察是否能够成功建立通信。测试数据的传输和接收功能,确保从STM32接收到的数据能够在APP界面上正确显示。设置阈值并触发报警测试,确认报警提示(声音、震动、弹窗等)是否按照设定的条件正常工作。5.4 系统整体测试模拟实际环境,调整大棚温湿度和光照强度,观察系统是否能够准确检测并显示环境参数。调整报警阈值,触发报警条件,验证报警提示是否按照预期工作。进行长时间运行测试,确保系统的稳定性和可靠性。六、关键问题讨论6.1 本项目的核心与技术难点【1】硬件集成:将主控芯片、传感器模块、蓝牙模块和蜂鸣器等多个硬件模块进行正确的连接和集成是一个挑战。需要仔细设计电路连接、通信协议和接口定义,确保各个模块能够正常协同工作。【2】数据处理与算法:在主控芯片的固件程序中,需要对传感器采集到的数据进行处理和分析,判断是否触发报警条件。这可能涉及到数据滤波、阈值判定、异常检测等算法的设计和实现。【3】蓝牙通信:与手机APP之间的蓝牙通信是一个难点。需要实现稳定可靠的数据传输和通信协议,确保数据的准确性和实时性。6.2 本项目的实用性【1】实时监测和报警功能:该项目能够实时监测大棚内的温度、湿度和光照强度,并在超过设定阀值时触发报警。这对于农业生产者来说非常实用,能够帮助他们及时发现问题并采取措施,以保证大棚内环境的稳定和作物的健康生长。【2】远程监控和管理:通过与手机APP的蓝牙通信,用户可以远程监控大棚内的数据并进行管理。他们可以随时查看温湿度和光照强度的实时数据,设置报警阀值,接收报警通知,并对大棚环境进行远程调整和控制。【3】自动化和智能化:该项目利用传感器和自动化控制技术,实现了对大棚环境的智能监测和控制。这不仅提高了农业生产的效率和质量,还减轻了人工管理的负担,具有较高的实用性和应用价值。6.3 关键点总结【1】传感器选择和集成:选择适合的温湿度传感器和光照强度传感器,并合理集成到硬件设计中。需要考虑传感器的精度、响应速度以及与主控芯片的通信协议等因素。【2】数据采集和处理算法:设计合适的数据采集和处理算法,确保从传感器获取的数据准确可靠,并能够根据设定的阈值判断是否触发报警条件。【3】报警机制:设计报警机制,根据设定的阈值和实时采集的数据进行比较,当达到报警条件时,触发报警提示,如声音、震动或弹窗等方式。【4】蓝牙通信:确保蓝牙模块能够与主控芯片稳定通信,并能够成功传输采集的数据到Android手机APP上。需要考虑通信的稳定性、数据传输的速率和安全性等因素。【5】Android手机APP设计:设计用户友好的界面,在手机APP上实时显示大棚内的温湿度和光照强度数据,并提供设置界面,允许农民设置报警阈值。同时,实现报警提示的功能,保证报警条件的准确性和报警方式的有效性。【6】系统稳定性和可靠性:在测试和调试阶段,需要对整个系统进行全面的测试,包括硬件和软件的各个模块,以确保系统的稳定性和可靠性。同时,考虑到长时间运行的情况,还需进行长时间测试,以验证系统能够持续运行并正常工作。七、设计总结与体会本项目的目的是设计实现对农业大棚环境的智能监测和管理,通过传感器采集数据、主控芯片处理和判断、蓝牙通信与手机APP交互,以及报警蜂鸣器的控制,实现了对温度、湿度和光照强度等参数的实时监测和报警功能。在项目开发过程中,也遇到了一些挑战和难点,比如:硬件的连接和集成、数据处理算法的设计和实现,以及与手机APP之间的蓝牙通信。通过细致的计划和分工,最终也是成功地克服了这些困难,得以完成了整个系统的开发。在实施过程中,深刻认识到模块化设计的重要性。通过将系统划分为多个硬件模块和软件模块,能够更好地管理和调试每个模块,并且在需要时进行模块的替换和升级,提高了系统的可扩展性和可维护性。在测试阶段,也意识到用户体验的重要性。通过与潜在用户的沟通和反馈,不断优化和改进系统的界面设计和功能实现,力求使用户能够轻松使用和管理该系统。这种用户导向的设计理念提高了系统的实用性和用户满意度。总的来说,本项目的设计与实施过程对软硬件的协同工作、数据处理与算法设计、用户体验等方面有了更深入的理解。通过克服挑战和不断优化,成功地将智能农业大棚监测系统带入实际应用,并为农业生产者提供了一种方便、高效且可靠的解决方案。相信随着技术的不断发展,智慧农业将在未来发挥更大的作用,为农业生产带来更多的创新和改进。
  • [互动交流] 是否有交叉编译的支持
    主要是指嵌入式系统的开发,这个是否有计划支持呢?
  • [交流吐槽] 请问何时能够推出交叉编译,支持嵌入式系统?
    请问何时能够支持交叉编译?有计划么? 这样嵌入式的工程师就也能用上CodeArts IDE了。
  • [专题汇总] 12月嵌入式项目开发专题总汇
    一、前言在这个数字化、智能化的时代,技术的发展日新月异。单片机和嵌入式系统作为电子设备的“大脑”,在我们的生活中扮演着越来越重要的角色。它们广泛应用于各种领域,如智能家居、工业自动化、环境监测等。与此同时,编程语言,特别是C语言,是实现这些系统功能的核心工具。本文将介绍一系列基于单片机设计的创新型项目,涵盖了激光测距仪、智能水泵控制器、自动门控制系统、城市环境监测看板、指纹锁等多个领域。同时,还深入探讨了C语言字符串处理、库函数功能及其用法详解,以及与华为云IOT相关的技术内容,例如对接华为云IOT的过程和MQTT协议入门知识。这些文章所包含的丰富内容,能够让初学者对单片机的应用、C语言库函数的使用以及MQTT协议和华为云IOT有更深入的了解。二、项目列表【1】基于单片机设计的激光测距仪(采用XKC-Kl200模块)cid:link_3随着科技的不断进步和应用需求的增加,测距仪成为了许多领域必备的工具之一。传统的测距仪价格昂贵、体积庞大,使用起来不够方便。本项目采用STC89C52单片机作为主控芯片,结合XKC-KL200激光测距模块和LCD1602显示器,实现了一个简易且高效的激光测距仪。这个测距仪可以帮助用户快速准确地测量目标与测距仪之间的距离,并将结果通过LCD1602显示器直观地展示出来。目前很多测距仪主要采用超声波或红外线等技术进行测量,但这些方法存在一定的局限性,比如受到环境干扰、测量距离有限等问题。而激光测距技术在测量精度和稳定性方面具有显著优势。XKC-KL200模块是一款基于激光测距原理的模块,具有高精度、快速测量等特点。通过与STC89C52单片机相结合,能够利用模块提供的数据和功能,快速实现一个功能完善的激光测距仪。LCD1602显示器作为输出设备,能够直观地显示测得的距离信息,具有体积小巧、低功耗、易于集成等特点,非常适合作为测距仪的显示屏。通过将测量结果转换为字符串,并利用LCD1602的命令和数据写入函数,可以在显示器上清晰地展示出测得的距离值。基于STC89C52单片机和XKC-KL200激光测距模块的激光测距仪项目,结合了激光测距技术和单片机控制技术,通过LCD1602显示器直观地展示出测得的距离信息。这个项目不仅满足了测距需求,而且具有成本低、体积小、使用方便等优势,无论是在建筑、工程、地理测量还是运动、航空等领域,这个激光测距仪都可以正常使用。【2】基于单片机设计的智能水泵控制器cid:link_4在一些场景中,如水池、水箱等水体容器的管理中,保持水位的稳定是至关重要的。传统上,人们通常需要手动监测水位并进行水泵的启停控制,这种方式不仅效率低下,还可能导致水位过高或过低,从而对水体及相关设备造成损坏。为了解决这个问题,设计了一个基于单片机的智能水泵控制器,主控芯片采用STC89C52,并利用L298N电机驱动模块来驱动小型水泵电机。此控制器可以通过水位传感器的反馈信号实现对水泵的自动启停控制,以维持水池或水箱的水位稳定。该项目的目标是实现以下几个关键功能:(1)水位监测:通过安装水位传感器,可以即时获取水池或水箱内的水位信息。传感器会将水位信息转化为电信号,并传输给主控芯片STC89C52,供后续处理和控制使用。(2)自动控制:主控芯片STC89C52根据水位传感器的反馈信号,通过编写相应的程序代码,可以实现对水泵的自动启停控制。当水位低于设定的阈值时,控制器会自动启动水泵电机进行供水;当水位达到或超过设定的阈值时,控制器会自动停止水泵电机,避免过流或溢出。(3)稳定维护:通过自动控制水泵的启停,可以保持水池或水箱的水位在一个稳定的范围内,避免了水体过高或过低所带来的问题。这有助于保护设备免受损坏,并确保水源的可靠供应。通过以上设计和功能实现,智能水泵控制器提供了一种方便、高效且智能化的水位管理解决方案。它可以广泛应用于家庭、农田灌溉、工业生产等领域,提供稳定和可靠的水源管理,提高工作效率,减少人工干预,同时也节约了能源和资源。L298N 是一款常用的双路直流电机驱动器芯片,可用于控制直流电机的转速和方向。该芯片由 STMicroelectronics 公司生产,被广泛应用于机器人、小车、机械装置等领域。下面是对 L298N 的详细介绍:(1)功能:L298N 芯片具有两个独立的驱动通道,每个通道可用于控制一个直流电机。它提供了正转、反转和制动功能,可以精确地控制电机的转向和速度。L298N 还具有过流保护和过热保护功能,可以保护电机和芯片免受损坏。(2)驱动能力:L298N 芯片具有较高的驱动能力,可支持高达2A 的电流输出,并且能够承受较高的峰值电流。这使得它适用于许多中小功率的直流电机驱动需求。(3)工作电压:L298N 芯片的工作电压通常为5V 到 35V,可以通过外部电源供电。电机的电压范围通常与芯片的工作电压相匹配。(4)控制接口:L298N 芯片的控制接口相对简单,通常通过逻辑电平或脉冲宽度调制(PWM)来控制电机的转速和方向。每个驱动通道都有使能引脚,用于启用或禁用对应通道的输出。(5)接线方式:L298N 芯片需要与外部电源和电机连接。它通常包含一个电源引脚(VCC 和 GND),用于供电;两个电机输出引脚(OUT1/OUT2 和 OUT3/OUT4),用于连接电机;以及控制引脚(IN1、IN2、IN3 和 IN4),用于接收控制信号。(6)应用领域:L298N 芯片广泛应用于需要控制直流电机的各种项目和设备。它常见于机器人车辆、智能小车、自动门、机械臂、CNC 设备等,用于实现精确的电机控制和运动。L298N 是一款常用的双路直流电机驱动芯片,具有高驱动能力、多种保护功能和简单的控制接口。可以用于控制直流电机的转向和速度,广泛应用于机器人、小车、机械装置等领域。【3】基于单片机设计的自动门控制系统cid:link_5自动门控制系统是一种智能化的应用,能够根据人体接近信号自动完成门的打开和关闭操作。在传统的门控系统中,通常需要人手动进行门的开启和关闭,不仅费时费力,还不够智能高效。本项目采用了STC89C52作为主控芯片,并结合红外热释电模块和28BYJ-48步进电机,实现了门的自动打开和关闭。通过红外热释电模块,系统可以感知到人体的靠近,当有人靠近门时,红外热释电模块会检测到人体的热量,并发送信号给主控芯片。主控芯片接收到信号后,会通过控制28BYJ-48步进电机的正反转来模拟门的打开和关闭过程。这种自动门控制系统在日常生活中有着广泛的应用。例如,它可以被广泛应用于商场、写字楼、医院、机场等公共场所的出入口,能够方便人们出入,提高安全性和便利性。同时,自动门控制系统也可以用于一些特殊场合,如无障碍通行的门禁系统,为残障人士提供更加便捷的出入途径。通过采用STC89C52作为主控芯片,可以充分发挥其强大的性能和功能,实现自动门控制系统的智能化。这款主控芯片具有高性能、低功耗、丰富的接口资源等特点,为系统的稳定运行提供了可靠保障。基于单片机设计的自动门控制系统在提升生活质量、提高安全性方面具有重要意义。这个项目的实现不仅展示了在单片机应用和硬件设计方面的技术水平,还为人们提供了更加智能化、便利化的生活体验。【4】基于STM32设计的城市环境监测看板(环境噪声、环境温湿度、粉尘浓度)cid:link_6随着城市人口规模的不断扩大和城市化进程的不断加速,城市环境污染问题越来越受到人们的关注。环境监测是评估环境污染状况、制定环保政策、维护人民身体健康的重要手段之一。传统的环境监测手段需要人工参与,成本高、效率低,不能满足大规模的实时监测需求。本项目基于STM32设计的城市环境监测看板,实现对城市环境噪声、温湿度和粉尘浓度的实时监测和数据采集。 主控芯片采用STM32F103C8T6,具有较高的性能和稳定性,能够有效处理各种传感器采集的数据;环境噪声传感器采用ADC输出模拟信号表示环境噪声强度,能够准确测量城市环境噪声;环境温湿度传感器采用DHT11,具有高精度、快速响应和成本低廉等优点,能够准确测量城市环境的温湿度;粉尘浓度模块采用PM2.5粉尘浓度检测模块GP2Y10,能够实时快速地检测城市空气中的PM2.5粉尘浓度,为环境污染控制提供数据支持。在本项目中,通过对不同传感器和主控芯片的选择和应用,成功设计了一款城市环境监测看板,能够实时监测和记录城市环境的噪声、温湿度和粉尘浓度等信息,为城市环保管理部门和公众提供了有力的数据支持。【5】 基于单片机设计的指纹锁(读取、录入、验证指纹)cid:link_0指纹识别技术是一种常见的生物识别技术,利用每个人指纹的唯一性进行身份认证。相比于传统的密码锁或者钥匙锁,指纹锁具有更高的安全性和便利性,以及防止钥匙丢失或密码泄露的优势。基于单片机设计的指纹锁项目是利用STC89C52作为主控芯片,结合AS608光学指纹识别模块和LCD1602显示屏,实现了指纹的读取、录入和验证功能。用户可以通过按键来进行指纹的录入和删除操作,并通过LCD显示屏来查看指纹识别的状态。在该项目中,AS608光学指纹识别模块是核心部件。它使用光学传感器采集指纹图像,然后通过算法进行特征提取和比对,最终判断指纹是否匹配。AS608模块具有高精度和高速的指纹识别能力,并且支持多种功能指令,如添加指纹、删除指纹和验证指纹等。另外,LCD1602显示屏提供了可视化的界面,能够直观地显示指纹识别的状态信息。用户可以通过观察LCD显示屏上的提示信息,了解指纹录入、删除和验证的结果。步进电机是用于模拟开锁操作的部件,通过正反转来实现门锁的解锁和上锁。当指纹验证成功时,步进电机会进行适当的旋转,使门锁打开,允许用户进入。该项目的背景是为了满足人们对安全性和便利性的需求,提供一种高效且可靠的门禁系统。通过指纹识别技术,可以确保只有授权的人员才能够进入特定区域,避免了传统钥匙的遗失和密码的泄露问题。同时,单片机作为主控芯片,具有低功耗、稳定性强等特点,非常适合用于嵌入式系统的设计与开发。这个项目的实施背景可以是家庭门禁系统、办公场所门禁系统、学校宿舍门禁系统等各种需要实现安全控制的场景。【6】 C语言字符串处理提取时间(ffmpeg返回的时间字符串)cid:link_7需求:有一个 "00:01:33.90" 这样格式的时间字符串,需要将这个字符串的时间值提取打印出来(提取时、分、秒、毫秒)。这个时间字符串从哪里来的? 是ffmpeg返回的时间,也就是视频的总时间。【7】 C语言实例_生成6位数的随机密码cid:link_8随着数字化时代的到来,人们在各个方面需要使用密码来保护个人隐私和敏感信息的安全。为了确保密码的安全性,密码应该是足够强大和难以猜测的,这就需要密码生成器来帮助用户生成高强度的随机密码。随机密码生成器是一种计算机程序,通过使用随机化算法和密码字符集合,生成具有预定长度和复杂性的密码。它可以用于创建密码保护的账户、加密文件、访问控制等场景。本项目使用 C 语言实现一个简单而实用的随机密码生成器。该生成器将生成包含字母、数字和特殊字符的随机密码,并提供自定义密码长度选项。生成的密码将是强大和难以破解的,从而增加了用户资料和关键信息的安全性。实现过程中,使用了随机数生成器函数 rand() 来获取随机数,并与自定义的密码字符集合进行结合,以生成密码的不同字符。同时,为了确保每次生成的密码都是独一无二的,使用当前系统时间作为种子来初始化随机数生成器。用户可以在程序运行时选择所需的密码长度,并且程序将输出所生成的密码。这样用户就可以轻松获得一个符合安全要求的随机密码,而无需自己思考和构建。使用随机密码生成器可以提高密码的复杂性和安全性,减少密码被猜测和破解的风险,从而保护用户的个人隐私和敏感信息。在日常生活和工作中都是非常有用的工具。【8】 人机验证码生成与验证:提升系统安全性cid:link_9为了防止机器人或脚本程序自动化攻击和滥用系统资源,很多网站和应用程序需要使用验证码来判断用户是否为真人。 一般登录都要求用户手动输入以验证身份的安全措施。验证码是一种通过生成包含随机字符的图像或文本,通常包含了不同大小写字母、数字或特殊符号,具有一定的复杂性和随机性,使机器难以识别和破解。本项目使用 C 语言实现一个简单的人机验证码生成和验证程序。程序生成一个由4位随机字符组成的验证码,并要求用户在控制台中手动输入该验证码。如果用户输入与生成的验证码匹配,则输出"验证成功";否则输出"验证失败"。在生成验证码的过程中,使用了随机数生成函数 rand() 来获取随机数,并结合字符集合来生成随机字符。为了确保每次生成的验证码都是独一无二的,使用当前系统时间作为种子来初始化随机数生成器。这个项目可以应用于各种需求,如注册页面的人机验证、防止暴力破解密码的登录页面、限制自动化爬虫等。通过要求用户手动输入验证码,可以有效防止机器人或脚本程序的自动化攻击,提高系统安全性和用户隐私保护。【9】 C语言实例_stdlib.h库函数功能及其用法详解cid:link_10C语言作为一种高效、灵活的编程语言,标准库的使用对于开发人员来说是不可或缺的。其中,stdlib.h是C语言中一个重要的标准库头文件,提供了许多常用的函数和工具,以便开发人员能够更加便捷地进行内存管理、字符串处理、随机数生成等操作。本文将对stdlib.h中的各个函数进行全面介绍,包括它们的功能和使用方法,以帮助开发者更好地理解和利用该标准库。【10】C语言实例_time.h库函数功能及其用法详解cid:link_11时间在计算机编程中扮演着重要的角色,C语言的time.h头文件提供了一系列的函数和工具,用于处理时间和日期相关的操作。这些函数包括获取当前时间、日期格式化、时间间隔计算等功能,为开发人员提供了强大的时间处理能力。本文将对time.h头文件中的所有函数进行全面介绍,包括功能和使用方法,以帮助大家更好地理解和利用该头文件。【11】 C语言实例_math.h库函数功能及其用法详解cid:link_12数学在计算机编程中扮演着至关重要的角色,C语言的math.h头文件提供了一系列的函数和工具,用于数学计算和常用数学函数的实现。这些函数包括数值运算、三角函数、指数对数函数等,为开发人员提供了强大的数学处理能力。本文将对math.h头文件中的所有函数进行全面介绍,包括功能和使用方法,以帮助大家更好地理解和利用该头文件。在 C 语言中,math.h 头文件提供了许多与数学运算相关的函数和宏。以下是该头文件中常用的函数和宏及其功能的详细介绍。【12】C语言实例_string.h库函数功能及其用法详解cid:link_1在计算机编程中,字符串处理是一项常见而重要的任务。C语言的string.h头文件提供了一系列函数和工具,用于对字符串进行操作和处理。这些函数包括字符串复制、连接、比较、查找等功能,为开发人员提供了强大的字符串处理能力。本文将对string.h头文件中的所有函数进行全面介绍,包括它们的功能和使用方法,以帮助大家更好地理解和利用该头文件。【13】BearPi-HM_Nano(Hi3861)对接华为云IOT过程介绍cid:link_2BearPi-HM Nano开发板是一款功能齐全、易于使用、专门为HarmonyOS设计的开发板,适合用于开发基于HarmonyOS的应用程序。板载NFC电路及标准的E53接口,标准的E53接口可扩展智能加湿器、智能台灯、智能安防、智能烟感等案例。 主控MCU是Hi3861,Hi3861芯片是一款高度集成的2.4GHz WLAN SoC芯片,专门为物联网应用而设计。集成了高性能的CPU、2.4GHz无线基带和射频前端以及多种外设接口,如UART、SPI、I2C、GPIO等,可以满足各种物联网设备的需求。这篇文章演示在BearPi-HM_Nano开发板上使用MQTT协议连接华为IoT平台,使用的是E53_IA1 智慧农业扩展板与 BearPi-HM_Nano 开发板实现。【14】 MQTT 协议入门:基础知识cid:link_13MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信。MQTT有多个版本,其中3.0、3.1.1和5.0是比较常见的版本。以下是它们之间的主要区别:MQTT 3.0: 这是MQTT协议的早期版本,奠定了MQTT的基础。 它具有基本的发布/订阅功能,但相对于后续版本,其功能较为有限。MQTT 3.1.1: 在3.0版本的基础上进行了改进和优化。 增加了对QoS(服务质量)级别的支持,提供了消息传递的可靠性保证。 引入了持久化连接,减少了网络连接的开销。 提供了更好的跨平台支持,适用于各种设备和操作系统。MQTT 5.0: 这是MQTT协议的最新版本,对之前的版本进行了大量的改进和扩展。 引入了新的特性,如共享订阅(Shared Subscriptions),增强了负载均衡和容错能力。 增加了对消息属性的支持,使得消息更加灵活和可扩展。 提供了更好的安全性,支持TLS/SSL加密和身份验证。 改进了QoS级别的实现,提供了更高的消息传递可靠性。从MQTT 3.0到3.1.1再到5.0,MQTT协议在功能、性能、安全性和可扩展性方面不断进行改进和扩展,以满足物联网应用的需求。随着版本的升级,MQTT协议逐渐变得更加成熟和完善,为物联网应用提供了更强大的支持。
  • [技术干货] C语言实例_math.h库函数功能及其用法详解
    一、前言数学在计算机编程中扮演着至关重要的角色,C语言的math.h头文件提供了一系列的函数和工具,用于数学计算和常用数学函数的实现。这些函数包括数值运算、三角函数、指数对数函数等,为开发人员提供了强大的数学处理能力。本文将对math.h头文件中的所有函数进行全面介绍,包括功能和使用方法,以帮助大家更好地理解和利用该头文件。在 C 语言中,math.h 头文件提供了许多与数学运算相关的函数和宏。以下是该头文件中常用的函数和宏及其功能的详细介绍:二、函数功能介绍【1】基本数学运算函数double sin(double x):计算正弦函数的值。double cos(double x):计算余弦函数的值。double tan(double x):计算正切函数的值。double asin(double x):计算反正弦函数的值。double acos(double x):计算反余弦函数的值。double atan(double x):计算反正切函数的值。double atan2(double y, double x):计算给定直角坐标系中点(x,y)的反正切函数值。double sinh(double x):计算双曲正弦函数的值。double cosh(double x):计算双曲余弦函数的值。double tanh(double x):计算双曲正切函数的值。double exp(double x):计算指数函数的值。double log(double x):计算自然对数函数的值。double log10(double x):计算以 10 为底的对数函数的值。double pow(double x, double y):计算 x 的 y 次幂。double sqrt(double x):计算平方根函数的值。double ceil(double x):向上取整,返回不小于 x 的最小整数值。double floor(double x):向下取整,返回不大于 x 的最大整数值。double fabs(double x):计算绝对值函数的值。【2】其他常用函数double fmod(double x, double y):计算 x 除以 y 的余数。double round(double x):四舍五入,返回最接近 x 的整数。double trunc(double x):截断,返回 x 的整数部分。double modf(double x, double* intpart):将 x 拆分为整数部分和小数部分,并将整数部分存储在 intpart 中。double hypot(double x, double y):计算两个参数的平方和的平方根。double ldexp(double x, int exp):计算 x 乘以 2 的指定次幂。double frexp(double value, int* exp):将 value 分解为尾数和指数,并将指数存储在 exp 中。int isnan(double x):检查 x 是否是非数值(NaN)。int isinf(double x):检查 x 是否是无穷大。int isfinite(double x):检查 x 是否是有限数。double copysign(double x, double y):返回与 x 绝对值相同但符号与 y 相同的值。double erf(double x):计算误差函数的值。double erfc(double x):计算余误差函数的值。【3】宏、常量和特殊值除了上述函数,math.h 头文件还定义了一些宏、常量和特殊值,例如:宏:M_PI、M_E 等代表数学常量。常量:INFINITY、NAN 等表示特殊数值的常量。特殊值:HUGE_VAL、HUGE_VALF、HUGE_VALL 等特殊数值。这些函数和宏可以在 C 语言中进行各种数学运算和数值处理。三、用法示例【1】基本数学运算函数:#include <stdio.h>#include <math.h>​int main() { double x = 1.5; double y = 2.0;​ double result1 = sin(x); printf("sin(%.2f) = %.2f\n", x, result1);​ double result2 = pow(x, y); printf("%.2f^%.2f = %.2f\n", x, y, result2);​ int result3 = ceil(x); printf("ceil(%.2f) = %d\n", x, result3);​ return 0;}输出结果:sin(1.50) = 0.991.50^2.00 = 2.25ceil(1.50) = 2【2】其他常用函数:#include <stdio.h>#include <math.h>​int main() { double x = 3.7; double intpart; double fractpart;​ fractpart = modf(x, &intpart); printf("x = %.2f, integer part = %.2f, fractional part = %.2f\n", x, intpart, fractpart);​ double distance = hypot(3.0, 4.0); printf("Distance from origin to point (3, 4) is %.2f\n", distance);​ double value = 1234.56; int exp; double mantissa = frexp(value, &exp); printf("Value = %.2f, mantissa = %.2f, exponent = %d\n", value, mantissa, exp);​ return 0;}输出结果:x = 3.70, integer part = 3.00, fractional part = 0.70Distance from origin to point (3, 4) is 5.00Value = 1234.56, mantissa = 0.97, exponent = 11
  • [技术干货] 基于单片机设计的自动门控制系统
    一、前言自动门控制系统是一种智能化的应用,能够根据人体接近信号自动完成门的打开和关闭操作。在传统的门控系统中,通常需要人手动进行门的开启和关闭,不仅费时费力,还不够智能高效。本项目采用了STC89C52作为主控芯片,并结合红外热释电模块和28BYJ-48步进电机,实现了门的自动打开和关闭。通过红外热释电模块,系统可以感知到人体的靠近,当有人靠近门时,红外热释电模块会检测到人体的热量,并发送信号给主控芯片。主控芯片接收到信号后,会通过控制28BYJ-48步进电机的正反转来模拟门的打开和关闭过程。这种自动门控制系统在日常生活中有着广泛的应用。例如,它可以被广泛应用于商场、写字楼、医院、机场等公共场所的出入口,能够方便人们出入,提高安全性和便利性。同时,自动门控制系统也可以用于一些特殊场合,如无障碍通行的门禁系统,为残障人士提供更加便捷的出入途径。通过采用STC89C52作为主控芯片,可以充分发挥其强大的性能和功能,实现自动门控制系统的智能化。这款主控芯片具有高性能、低功耗、丰富的接口资源等特点,为系统的稳定运行提供了可靠保障。基于单片机设计的自动门控制系统在提升生活质量、提高安全性方面具有重要意义。这个项目的实现不仅展示了在单片机应用和硬件设计方面的技术水平,还为人们提供了更加智能化、便利化的生活体验。二、项目整体设计2.1 硬件设计思路(1)主控芯片选择:选择STC89C52作为主控芯片,它是一款功能强大且成本相对较低的单片机,具有较高的性能和稳定性。(2)人体接近检测:采用红外热释电模块来检测人体的接近情况。该模块可以感知到人体的热量,并将信号传输给主控芯片。(3)电机驱动模块:选择28BYJ-48步进电机作为门的驱动器。通过控制电机的正反转,可以模拟门的打开和关闭过程。(4)门控制机构:设计一个合适的机械结构,使得步进电机的输出能够转换为门的运动。这个机构需要稳定可靠,并且能够适应不同门的尺寸和重量。(5)电源管理:为了保证整个系统的稳定运行,需要设计合适的电源管理电路,包括电源适配器、稳压模块等设备,确保各个电子元件都能够正常工作。2.2 软件设计思路(1)硬件初始化:在程序的开始部分,进行主控芯片和相关硬件模块的初始化设置,包括IO口配置、定时器设置等。(2)红外热释电模块的接收:通过主控芯片的IO口接收红外热释电模块的信号,判断是否有人靠近门。(3)判断门的状态:根据红外热释电模块的信号,判断门当前的状态,包括门的开启状态和关闭状态。(4)控制步进电机:根据门的状态,选择适当的步进电机的正反转来模拟门的运动。通过控制电机的步进角度和脉冲频率,可以实现门的平滑打开和关闭。(5)状态监测和保护:设计合适的状态监测功能,检测电机的运行状态、门的位置等,以及相应的保护机制,避免过载和故障。(6)电源管理:对于整个系统的电源管理,需要设计合适的电源管理算法,包括低功耗模式、电池电量检测等功能,以延长电池寿命或节约能源。三、硬件连线(1)红外热释电模块引脚连接:模块信号输出引脚(OUT)连接到单片机的某个IO口(P1.0)。(2)步进电机驱动模块引脚连接:电机控制线IN1连接到单片机的某个IO口(P2.0)。电机控制线IN2连接到单片机的某个IO口(P2.1)。电机控制线IN3连接到单片机的某个IO口(P2.2)。电机控制线IN4连接到单片机的某个IO口(P2.3)。四、项目代码设计#include <reg51.h>​sbit infrared = P1^0; // 红外热释电模块信号引脚连接到P1.0sbit motorIN1 = P2^0; // 电机控制线IN1连接到P2.0sbit motorIN2 = P2^1; // 电机控制线IN2连接到P2.1sbit motorIN3 = P2^2; // 电机控制线IN3连接到P2.2sbit motorIN4 = P2^3; // 电机控制线IN4连接到P2.3​void main(){ motorIN1 = 0; // 设置电机初始状态为停止 motorIN2 = 0; motorIN3 = 0; motorIN4 = 0; while (1) { if (infrared == 1) // 检测到人体接近门 { // 电机控制信号,使门打开 motorIN1 = 1; motorIN2 = 0; motorIN3 = 0; motorIN4 = 0; // 停顿一段时间,模拟门打开过程 delay_ms(2000); // 电机停止,保持门打开状态 motorIN1 = 0; motorIN2 = 0; motorIN3 = 0; motorIN4 = 0; } else { // 电机控制信号,使门关闭 motorIN1 = 0; motorIN2 = 1; motorIN3 = 0; motorIN4 = 0; // 停顿一段时间,模拟门关闭过程 delay_ms(2000); // 电机停止,保持门关闭状态 motorIN1 = 0; motorIN2 = 0; motorIN3 = 0; motorIN4 = 0; } }}​五、总结自动门控制系统,基于STC89C52单片机和红外热释电模块的检测,以及28BYJ-48步进电机的控制,实现了门的自动打开和关闭功能。该系统能够识别人体的接近,并根据情况自动作出相应的反应,提供了便捷和智能化的门控解决方案。通过使用红外热释电模块,系统能够高效地检测到人体的接近,并通过单片机程序控制步进电机的正反转,从而模拟门的打开和关闭过程。这种设计不仅增加了门的自动化程度,也提高了使用安全性和舒适性。在实际使用中,该自动门控制系统可以广泛应用于公共场所、商业建筑、住宅小区等场景中。它能够有效减少人员因为频繁开关门而产生的不便,提高了出入口的流畅性和效率。同时,它也满足了无障碍通行的需求,为老年人、残障人士等特殊人群提供了更好的便利。这个自动门控制系统结合了STC89C52单片机、红外热释电模块和步进电机的技术,实现了智能化的门控功能。它不仅提供了便利和舒适的使用体验,还展示了物联网技术在日常生活中的实际应用。随着科技的不断发展,相信自动门控制系统会在未来的智能建筑领域发挥更加重要的作用。
  • [技术干货] 基于单片机设计的智能水泵控制器
    一、前言在一些场景中,如水池、水箱等水体容器的管理中,保持水位的稳定是至关重要的。传统上,人们通常需要手动监测水位并进行水泵的启停控制,这种方式不仅效率低下,还可能导致水位过高或过低,从而对水体及相关设备造成损坏。为了解决这个问题,设计了一个基于单片机的智能水泵控制器,主控芯片采用STC89C52,并利用L298N电机驱动模块来驱动小型水泵电机。此控制器可以通过水位传感器的反馈信号实现对水泵的自动启停控制,以维持水池或水箱的水位稳定。该项目的目标是实现以下几个关键功能:(1)水位监测:通过安装水位传感器,可以即时获取水池或水箱内的水位信息。传感器会将水位信息转化为电信号,并传输给主控芯片STC89C52,供后续处理和控制使用。(2)自动控制:主控芯片STC89C52根据水位传感器的反馈信号,通过编写相应的程序代码,可以实现对水泵的自动启停控制。当水位低于设定的阈值时,控制器会自动启动水泵电机进行供水;当水位达到或超过设定的阈值时,控制器会自动停止水泵电机,避免过流或溢出。(3)稳定维护:通过自动控制水泵的启停,可以保持水池或水箱的水位在一个稳定的范围内,避免了水体过高或过低所带来的问题。这有助于保护设备免受损坏,并确保水源的可靠供应。通过以上设计和功能实现,智能水泵控制器提供了一种方便、高效且智能化的水位管理解决方案。它可以广泛应用于家庭、农田灌溉、工业生产等领域,提供稳定和可靠的水源管理,提高工作效率,减少人工干预,同时也节约了能源和资源。L298N 是一款常用的双路直流电机驱动器芯片,可用于控制直流电机的转速和方向。该芯片由 STMicroelectronics 公司生产,被广泛应用于机器人、小车、机械装置等领域。下面是对 L298N 的详细介绍:(1)功能:L298N 芯片具有两个独立的驱动通道,每个通道可用于控制一个直流电机。它提供了正转、反转和制动功能,可以精确地控制电机的转向和速度。L298N 还具有过流保护和过热保护功能,可以保护电机和芯片免受损坏。(2)驱动能力:L298N 芯片具有较高的驱动能力,可支持高达2A 的电流输出,并且能够承受较高的峰值电流。这使得它适用于许多中小功率的直流电机驱动需求。(3)工作电压:L298N 芯片的工作电压通常为5V 到 35V,可以通过外部电源供电。电机的电压范围通常与芯片的工作电压相匹配。(4)控制接口:L298N 芯片的控制接口相对简单,通常通过逻辑电平或脉冲宽度调制(PWM)来控制电机的转速和方向。每个驱动通道都有使能引脚,用于启用或禁用对应通道的输出。(5)接线方式:L298N 芯片需要与外部电源和电机连接。它通常包含一个电源引脚(VCC 和 GND),用于供电;两个电机输出引脚(OUT1/OUT2 和 OUT3/OUT4),用于连接电机;以及控制引脚(IN1、IN2、IN3 和 IN4),用于接收控制信号。(6)应用领域:L298N 芯片广泛应用于需要控制直流电机的各种项目和设备。它常见于机器人车辆、智能小车、自动门、机械臂、CNC 设备等,用于实现精确的电机控制和运动。L298N 是一款常用的双路直流电机驱动芯片,具有高驱动能力、多种保护功能和简单的控制接口。可以用于控制直流电机的转向和速度,广泛应用于机器人、小车、机械装置等领域。二、项目设计思路【1】硬件设计思路(1)主控芯片选择:选择了STC89C52作为主控芯片。STC89C52是一款高性能、低功耗的单片机,具有较大的存储容量和丰富的外设接口,非常适合用于本项目的控制需求。(2)水位传感器:选用合适的水位传感器来监测水位。选择浮子开关。该传感器将水位信息转化为电信号,并通过引脚连接到主控芯片。(3)电机驱动模块:采用L298N电机驱动模块驱动小型水泵电机。L298N模块具有双H桥驱动功能,可以控制电机的正转、反转和速度调节。通过连接到主控芯片的数字引脚,实现对水泵电机的启停控制。(4)按键和显示屏:添加了按键和显示屏组件,用于用户设置充氧时间和充氧力度。通过按键进行参数的调整,并在显示屏上显示相关信息,提供更好的用户交互体验。【2】软件设计思路(1)初始化设置:在启动时,主控芯片首先对各个引脚进行初始化设置,包括水位传感器的模拟引脚、L298N电机驱动模块的数字引脚、按键和显示屏的IO口等。(2)水位监测:主控芯片定时读取水位传感器的模拟信号,并将其转换为相应的数值。通过比较当前水位与设定阈值的大小,判断是否需要启动或停止水泵电机。(3)自动控制:根据水位监测结果,主控芯片通过控制L298N电机驱动模块的引脚实现对水泵电机的启停控制。当水位低于设定的阈值时,控制器会启动水泵电机;当水位达到或超过设定的阈值时,控制器会停止水泵电机。(4)按键设置:用户可以通过按键来设置充氧时间和充氧力度。通过在代码中编写相应的按键处理函数,实现按键事件的捕捉和处理。用户按下不同的按键,可以调整充氧时间和充氧力度的参数。(5)显示屏交互:主控芯片通过控制显示屏的IO口,将参数信息显示在屏幕上。用户可以通过屏幕上的提示信息来了解当前的设置状态和工作状态。通过以上软件设计思路,能够实现基于单片机的智能水泵控制器的自动控制和用户交互功能。主控芯片通过读取水位传感器的信号,并根据用户设置及阈值判断,自动控制水泵的启停来维持水池或水箱的水位稳定。同时,用户可以通过按键和显示屏进行参数的调整和状态的查看,提高了用户的便利性和操作体验。三、硬件连线说明模块功能引脚名称连接建议水位传感器检测水位IO口P1.0(数字输入)L298N电机驱动控制电机启停和速度IN1、IN2、ENP2.0、P2.1、P2.2(数字输出)按键参数调整和操作IO口P3.2(数字输入)四、项目代码设计以下代码用于实现基于STC89C52单片机和L298N驱动的智能水泵控制器。通过读取水位传感器的数字输入口的状态,控制水泵的启停以维持水池或水箱的稳定水位。#include <reg52.h>​// 定义IO口连接sbit Sensor = P1^0; // 水位传感器连接到P1.0口sbit PumpIN1 = P2^0; // L298N电机驱动的IN1口连接到P2.0口sbit PumpIN2 = P2^1; // L298N电机驱动的IN2口连接到P2.1口sbit PumpEN = P2^2; // L298N电机驱动的EN口连接到P2.2口​// 定义水泵状态#define PUMP_ON 1#define PUMP_OFF 0​void delay(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) for(j = 0; j < 120; j++);}​void pumpControl(unsigned char state) { if (state == PUMP_ON) { PumpIN1 = 1; PumpIN2 = 0; PumpEN = 1; // 启动水泵 } else { PumpEN = 0; // 停止水泵 }}​void main() { while(1) { if (Sensor == 0) { pumpControl(PUMP_ON); // 检测到缺水,启动水泵 } else { pumpControl(PUMP_OFF); // 水位正常,停止水泵 } delay(1000); // 延时1秒 }}​这段代码中,在主函数main()中通过不断检测水位传感器的状态来控制水泵的启停。当水位传感器输出为低电平(0表示缺水)时,调用pumpControl(PUMP_ON)函数启动水泵;当水位传感器输出为高电平(1表示不缺水)时,调用pumpControl(PUMP_OFF)函数停止水泵。通过使用delay()函数进行延时,可以控制水泵状态的更新频率。五、总结通过本次项目的设计和实现,成功地搭建了一个基于单片机的智能水泵控制器,利用STC89C52主控芯片和L298N电机驱动模块实现了对小型水泵电机的自动控制。本项目通过水位传感器的反馈信号,实现了对水泵的智能控制。当水位传感器检测到水位下降时,控制器自动启动水泵,将水池或水箱的水位维持在稳定状态;当水位传感器检测到水位达到设定的上限时,控制器自动停止水泵,避免水泵过度运行。这种自动化的水泵控制系统极大地提高了水资源的利用效率,并减轻了人工管理的负担。这个智能水泵控制器在实际应用中具有广泛的用途和重要的意义。它可以被广泛应用于农田灌溉、城市供水、工业运输等领域,为水资源的合理利用和节约提供了有效的手段。通过自动化控制,不仅可以降低人力成本,还能够减少水资源的浪费,保护环境,实现可持续发展。
  • [专题汇总] 2023年11月嵌入式项目开发专题总汇
    一、前言在科技飞速发展的时代,单片机技术已成为创新项目的重要支撑。本文将介绍基于单片机设计的十五个项目,这些项目涵盖了太阳能跟踪器、自动门控制系统、步进电机控制、舵机控制、短信发送、智能风扇、电子钟、电子秤、水平仪、电子指南针、智能车库管理系统、气压与海拔高度检测计、大气气压检测装置以及超声波测距仪等领域。这些项目充分展示了单片机在自动化、物联网、传感器技术等领域中的广泛应用,体现了科技与生活的深度融合。这些项目不仅代表着技术的创新,更是对未来智能化生活的一种探索和期待。通过单片机的智能化控制,能够实现更高效、更便捷、更人性化的服务,让生活变得更加美好。二、项目列表【1】基于单片机设计的太阳能跟踪器cid:link_5随着对可再生能源的需求不断增长,太阳能作为一种清洁、可持续的能源形式,受到越来越多的关注和应用。太阳能光板通常固定在一个固定的角度上,这限制了它们对太阳光的接收效率。为了充分利用太阳能资源,提高太阳能光板的收集效率,需要设计一个能够自动跟踪太阳光的系统。本项目采用基于单片机的设计方案,主控芯片选择STC89C52。在太阳能光板的四个角上,安装了四个光敏电阻,它们用于检测四个方向太阳光的最强位置。每个光敏电阻通过PCF8591模块与主控芯片相连,利用模数转换器(ADC)采集各个通道的数据值。通过对四个光敏传感器采集到的数据进行处理和比较,主控芯片能够确定太阳光的最强位置所在。然后,通过控制两个28BYJ-48-5V步进电机的运动,太阳能光板可以实现左右和上下方向的旋转。通过调整太阳能光板的倾斜角度,使其与太阳光保持垂直,以获得最大的太阳能收集效率。该太阳能跟踪器的设计旨在实现自动化的太阳光追踪,以提高太阳能光板的能源收集效率。通过使用光敏电阻、ADC转换和步进电机控制等技术手段,系统能够准确地确定太阳光的位置,并自动调整太阳能光板的朝向。这将大大提高太阳能系统的能源输出,并为可再生能源的利用做出贡献。【2】基于单片机设计的自动门控制系统cid:link_6随着科技的不断发展,自动门成为公共场所、商业建筑和住宅社区等地的常见设施。自动门的出现使得进出门的操作更加便捷,提高了人们的生活质量和工作效率。为了实现自动门的开关控制,本项目基于单片机设计了一套自动门控制系统。本项目的主控芯片选择了STC89C52,这是一款性能稳定且广泛应用于嵌入式系统的单片机。具有较高的计算能力和丰富的外设接口,非常适合用于本项目中的自动门控制。自动门的开关控制通过红外热释电传感器实现。红外热释电传感器是一种能够检测人体红外辐射的传感器,当有人靠近时,传感器会感知到人体的存在。本项目中,红外热释电传感器被安装在自动门的控制区域,用于检测人体的接近。为了实现自动门的开关动作,本项目采用了SG90舵机进行控制。SG90舵机是一种小型直流电机,具有较高的转动精度和响应速度。通过模拟控制方式,根据控制信号的脉冲宽度来控制门的开关状态。在系统运行时,红外热释电传感器不断检测周围的人体活动。当传感器检测到人体接近时,会向主控芯片发送信号。主控芯片接收到信号后,会控制SG90舵机执行开门动作,使门自动打开。当人体离开控制区域时,传感器再次发送信号,主控芯片控制舵机执行关门动作,实现自动门的关闭。自动门控制系统具有以下优点:(1)通过红外热释电传感器实现人体接近检测,无需人工干预,使门的开关更加智能化。(2)采用SG90舵机进行控制,具有较高的转动精度和响应速度,门的开关动作更加准确和迅速。(3)通过使用STC89C52主控芯片,系统具有良好的扩展性和可靠性,可以方便地进行功能扩展和故障排除。自动门控制系统可以广泛应用于各种场所,如商场、酒店、医院、办公楼、住宅小区等,为人们提供便捷、安全的出入门体验,提高生活和工作的效率。【3】通过51单片机控制28byj48步进电机按角度正反转旋转cid:link_7本项目基于STC89C52单片机,通过控制28BYJ-48步进电机实现按角度正反转旋转的功能。28BYJ-48步进电机是一种常用的电机,精准定位和高扭矩输出,适用于许多小型的自动化系统和机械装置。在这个项目中,使用STC89C52单片机作为控制器,这是一款强大而常用的8位单片机芯片,具有丰富的外设和强大的计算能力。通过编写适当的程序,可以通过单片机的IO口来控制步进电机的运动。28BYJ-48步进电机是一种低成本、低功耗的步进电机,拥有精确的定位能力和较高的转矩输出。将使用单片机与步进电机之间的接口信号来驱动电机旋转,并通过控制电流脉冲的频率和顺序来控制电机前进或后退以及旋转的角度。本项目的目标是实现根据用户输入的角度值,控制28BYJ-48步进电机按指定角度进行正反转旋转。通过灵活调整步进电机的控制信号,可以实现不同角度范围内的精确旋转。在接下来的内容将介绍所需的硬件和软件资源,包括STC89C52单片机的基本特性、28BYJ-48步进电机的工作原理,以及编写控制程序的关键步骤。【4】通过51单片机控制SG90舵机按角度正反转转动cid:link_8本文介绍如何通过51单片机控制SG90舵机实现角度的正反转转动。SG90舵机是一种常用的微型舵机,具有体积小、重量轻、结构简单等特点,被广泛应用于机器人、遥控模型和各种自动控制系统中。使用51单片机(STC89C52)作为控制器,利用其强大的IO口和定时器功能来实现对SG90舵机的控制。通过编程控制,可以精确地控制舵机按指定的角度进行正转或反转运动。舵机的控制是通过脉冲宽度调制(PWM)来实现的。在控制舵机时,需要向舵机发送一系列的脉冲信号,脉冲的宽度决定了舵机的角度位置。通常情况下,SG90舵机的控制脉冲周期为20毫秒,脉冲宽度在0.5毫秒到2.5毫秒之间,对应的角度范围为0度到180度。为了实现舵机的正反转转动,需要控制脉冲的宽度在不同的范围内,以达到不同的角度位置。通过调整脉冲的宽度和周期,我们可以控制舵机按照我们的要求进行旋转。下面将介绍如何通过51单片机的IO口和定时器来生成适用于SG90舵机的PWM信号。编写相应的程序,通过调整脉冲宽度来实现舵机的正反转转动,并提供示例代码。【5】51单片机+SIM800C(GSM模块)实现短信发送功能cid:link_0本项目利用51单片机和SIM800C GSM模块实现短信发送功能。短信作为一种广泛应用的通信方式,在许多领域具有重要的作用,如物联网、安防系统、远程监控等。通过将51单片机与SIM800C GSM模块相结合,可以实现在各种应用场景下的短信通信功能。本项目的核心组件是51单片机,是一种低成本、低功耗的单片机,广泛应用于嵌入式系统开发。利用51单片机的串口功能来控制SIM800C GSM模块的通信。SIM800C是一款功能强大的GSM模块,支持GSM/GPRS通信,具有发送和接收短信的能力。在本项目中,搭建51单片机和SIM800C GSM模块的硬件连接。使用C语言编写程序,在51单片机上实现与SIM800C的通信控制。通过串口通信向SIM800C发送AT指令,实现短信的发送功能。为了实现短信发送功能,需要熟悉SIM800C的AT指令集,了解如何设置短信参数、编写短信内容并发送。还需要处理SIM800C返回的响应,以确保短信发送的成功与否。【6】 基于单片机设计的智能风扇(红外线无线控制开关调速定时)cid:link_9在炎热的夏季,风扇成为人们室内生活中必不可少的电器产品。然而,传统的风扇控制方式存在一些不便之处,比如需要手动操作开关、无法远程控制和调速,以及缺乏定时功能等。为了解决这些问题,设计了一款基于单片机的智能风扇,利用红外线无线控制开关、调速和定时功能,使用户能够更加便捷和舒适地使用风扇。主控芯片采用STC89C52,这是一款功能强大且性能稳定的单片机,具备足够的计算和控制能力。通过支持红外线NEC协议,该单片机能够接收遥控器发送的控制指令,并根据指令完成对风扇的开关、调速和定时切换。为了实现风扇的控制,采用了L298N驱动模块来驱动小型的直流电机,模拟真实风扇的工作原理。L298N驱动模块具有高电流和高电压的特点,可以有效地控制电机的转速和方向。通过单片机的IO口与L298N驱动模块进行连接,可以精确控制电机的转速,并且支持正转、反转和停止等操作。这个智能风扇项目具备多项实用的功能。利用红外线遥控器,用户可以随时随地对风扇进行开关操作,无需手动接触开关,提高了使用的便捷性。通过调速功能,用户可以根据需要调整风扇的转速,以获得理想的风速效果,增加了舒适感。还有定时功能,用户可以设置定时关闭风扇,避免长时间运行造成不必要的能耗,同时也提供了更多的节能选项。此外,基于单片机设计的智能风扇还具备一定的智能化潜力。通过进一步的开发和改进,可以引入温湿度传感器,实现自动调节风速的功能,根据环境温湿度情况自动调整风扇转速,为用户带来更加智能、个性化的使用体验。智能风扇的设计背景源于对人们日常生活的需求和对智能化家居的追求。通过采用单片机控制和红外线无线控制技术,结合驱动模块的应用,成功地打造了一款功能强大、操作便捷的智能风扇,让人们在炎热夏季享受到更为舒适和智能化的生活体验。【7】51单片机+DS1302设计一个电子钟(LCD1602显示时间)cid:link_1电子钟是一种能够准确显示时间的设备,广泛应用于家庭、办公场所和公共场所,为人们提供了方便和准确的时间信息。本项目设计一个基于51单片机的电子钟,使用DS1302作为RTC时钟芯片,LCD1602作为显示屏,并通过串口方式连接上位机进行时间设置和闹钟设置。STC89C52作为主控芯片,具有较高的性能和稳定性,可完成对外设的控制和数据处理。DS1302是一款低功耗的实时时钟芯片,能够提供准确的时间计数和日期功能。LCD1602是一款常用的字符型液晶显示屏,具有两行16列的显示区域,能够清晰显示时间和其他相关信息。通过串口连接上位机,用户可以方便地设置电子钟的时间和闹钟时间,实现个性化需求。此外,电子钟还带有一个蜂鸣器,可以根据设置的闹钟时间进行响铃,提醒用户。【8】STC89C52+HX711完成电子秤设计cid:link_10电子秤是一种通过传感器测量物体质量的设备,被广泛应用于商业和工业领域。传统的机械秤已经逐渐被电子秤取代,因为电子秤具有更高的精度、更方便的使用和更多的功能。本项目利用STC89C52单片机和HX711模块完成电子秤设计,具有去皮功能、累加功能以及LCD1602显示屏显示实时称重数据。通过HX711模块实现对物品重量的精确测量;支持去皮功能,可以在测量前将容器的重量减去,得到净重;具备累加功能,可以记录多次测量结果并进行累加;使用LCD1602显示屏实时显示当前称重数据,方便用户查看。该电子秤实现了基本称重功能和去皮功能。基本称重功能指的是通过传感器测量物体质量并显示结果。HX711模块连接到电子秤上的传感器,通过读取HX711模块输出的数字信号来获取物体的重量数据。STC89C52单片机通过串行通信协议与HX711模块进行通信,并从中获取到称重数据。然后,通过控制LCD1602显示器,将称重结果以可读的方式显示在LCD屏幕上。用户可以清晰地看到物体的重量。除了基本称重功能,该电子秤还具有去皮功能。当用户按下独立按键时,系统将自动记录当前的称重值为皮重。之后,无论在物体放置前还是放置后按下独立按键,系统都会自动去除皮重,并显示净重。这样可以方便地进行物体重量的测量,而无需手动计算。整个系统的核心是STC89C52单片机,负责与HX711模块和LCD1602显示器进行通信,并控制显示内容和去皮功能。该设计不仅简单实用,而且具有良好的可扩展性和稳定性,适用于各种实际应用场景,如商业称重、食品加工等。【9】 基于单片机设计的水平仪(STC589C52+MPU6050)cid:link_11 水平仪是一种常见的测量工具,用于检测物体或设备的水平姿态。在许多应用中,如建筑、制造和航空等领域,保持设备的水平姿态是非常重要的。为了实现实时的水平检测和显示,基于单片机设计的水平仪是一个常见的解决方案。数字水平仪是一种用于测量物体相对于水平面的角度的仪器。它基于单片机设计,主控芯片为STC89C52,姿态检测采用MPU6050六轴传感器,显示屏用于显示水平姿态数据,锂电池供电。该仪器具有高精度、低功耗、易操作等特点,广泛应用于建筑、工程、测绘等领域。整个系统的设计思路是通过MPU6050获取设备的姿态数据,然后利用STC89C52进行数据处理和计算,最后将计算得到的水平偏移值通过SPI接口传输到0.96寸的OLED显示屏上进行实时显示。【10】基于单片机设计的水平仪(STC589C52+MPU6050)cid:link_11水平仪是一种常见的测量工具,用于检测物体或设备的水平姿态。在许多应用中,如建筑、制造和航空等领域,保持设备的水平姿态是非常重要的。为了实现实时的水平检测和显示,基于单片机设计的水平仪是一个常见的解决方案。数字水平仪是一种用于测量物体相对于水平面的角度的仪器。它基于单片机设计,主控芯片为STC89C52,姿态检测采用MPU6050六轴传感器,显示屏用于显示水平姿态数据,锂电池供电。该仪器具有高精度、低功耗、易操作等特点,广泛应用于建筑、工程、测绘等领域。整个系统的设计思路是通过MPU6050获取设备的姿态数据,然后利用STC89C52进行数据处理和计算,最后将计算得到的水平偏移值通过SPI接口传输到0.96寸的OLED显示屏上进行实时显示。基于单片机设计的数字水平仪具有以下功能特点:主控芯片:本设计采用STC89C52单片机作为主控芯片,具有强大的处理能力和丰富的外设接口,能够满足数字水平仪的功能需求。姿态检测:通过MPU6050六轴传感器实现对物体姿态的实时检测,包括加速度计、陀螺仪和磁力计等,能够精确测量物体在三维空间中的倾斜角度。显示屏显示:采用液晶显示屏实时显示水平姿态数据,用户可以通过显示屏直观地了解物体的倾斜情况。锂电池供电:采用锂电池作为电源,具有高能量密度、长寿命和环保等优点,能够满足数字水平仪长时间工作的需求。低功耗设计:通过合理的硬件设计和软件优化,实现低功耗运行,降低能耗,延长电池使用寿命。数据存储与传输:内置存储器可存储大量姿态数据,支持USB接口进行数据传输,方便用户进行数据分析和处理。易于操作:数字水平仪具有简洁明了的操作界面,用户只需简单设置即可开始测量,无需复杂的操作步骤。稳定性高:通过高精度的姿态检测和数据处理算法,实现对物体倾斜角度的准确测量,保证测量结果的稳定性和可靠性。【11】基于单片机设计的电子指南针(LSM303DLH模块(三轴磁场 + 三轴加速度)cid:link_2本项目是基于单片机设计的电子指南针,主要利用STC89C52作为主控芯片和LSM303DLH模块作为指南针模块。通过LCD1602液晶显示屏来展示检测到的指南针信息。在日常生活中,指南针是一种非常实用的工具,可以帮助我们确定方向,特别是在户外探险、航海、定位等场景中。传统的磁罗盘指南针存在一些不便之处,如体积较大、不易携带、容易受到外界干扰等。设计一款基于单片机的电子指南针是比较有意义的项目。为了实现这个项目,选择了STC89C52作为主控芯片。STC89C52是一款功能强大且成本较低的单片机,具有丰富的接口和强大的处理能力,非常适合用于嵌入式应用。同时,为了获得准确的指南针数据,采用了LSM303DLH模块作为指南针模块。该模块集成了三轴磁场传感器和三轴加速度传感器,能够提供高精度和稳定的指南针数据。在项目的具体实现中,通过STC89C52与LSM303DLH模块进行通信,获取指南针传感器的原始数据。对这些原始数据进行处理和计算,通过磁场数据确定方向,并结合加速度数据来提高测量的准确性。最后,将计算得到的指南针信息通过LCD1602液晶显示屏展示出来,用户可以直观地查看当前的方向。通过该电子指南针,用户可以方便地获得当前的方向信息,无论是在户外旅行、徒步探险还是其他需要导航的场景中,都能提供实时准确的方向指引。该项目不仅具有一定的技术挑战性,也能为用户带来便利和实用性。【12】 基于STM32+华为云IOT设计的智能车库管理系统cid:link_3随着城市化进程和汽车拥有率的不断提高,停车难的问题也日益凸显。在城市中,停车场是一个非常重要的基础设施,但是传统的停车场管理方式存在很多问题,比如车位难以管理、停车费用不透明等。为了解决这些问题,智能车库管理系统应运而生。本项目基于STM32+华为云IOT设计的智能车库管理系统,通过红外感应传感器检测停车位的占用情况,将数据上传到华为云物联网平台,并通过微信小程序展示车库的实时停车位情况,包括总停车数量、当前剩余空位数量和车位的编号,并在二维立体图中标注出空闲停车位,方便用户快速找到空闲停车位进行停车。本系统还包括车牌识别自动计费部分,通过车牌识别技术识别车辆进出停车场的时间,并自动计算停车费用,提高了停车场的管理效率和用户体验。本项目的实现将大大提高停车场的管理效率和客户体验,为城市交通管理和用户出行提供了更加便捷和高效的解决方案。【13】基于单片机设计的气压与海拔高度检测计(采用MPL3115A2芯片实现)cid:link_12随着科技的不断发展,在许多领域中,对气压与海拔高度的测量变得越来越重要。例如,对于航空和航天工业、气象预报、气候研究等领域,都需要高精度、可靠的气压与海拔高度检测装置。针对这一需求,基于单片机设计的气压与海拔高度检测计应运而生。本项目采用了MPL3115A2芯片作为气压与温度传感器,能够实现高精度、高分辨率的气压与海拔高度测量。主控芯片采用STC89C52,具有强大的控制能力和丰富的资源,能够更好地满足系统设计的要求。此外,通过LCD1602显示检测到的信息,可以使用户更加方便地查看和使用系统。【14】基于单片机设计的大气气压检测装置(STC89C52+BMP180实现)cid:link_13本项目设计一个大气气压检测装置,该装置以单片机为基础,采用STC89C52作为核心控制芯片,结合BMP180模块作为气压传感器。大气气压,也就是由气体重力在大气层中产生的压力,其变化与天气预报、气象观测以及高度测量等方面密切相关。在这个设计中,STC89C52作为主控芯片,其强大的功能和广泛的应用,特别是丰富的外设资源和稳定可靠的性能,使得它能够与BMP180模块通信,从而获取精确的大气气压数据。BMP180模块是一种高精度、低功耗的数字式气压传感器,可以测量大气压强,也能通过计算得到气温和海拔高度等信息。它将这些信息传输给STC89C52主控芯片,进行后续处理和显示。为了使用户更直观地读取大气气压信息,这个设计采用了LCD1602显示器来实时显示气压数据。LCD1602是一种常见的字符型液晶显示器,可以文本形式展示信息。通过适当的程序设计,我们可以将BMP180模块获取的大气气压数据转换为人类可读的字符,并实时在LCD1602上显示。这个大气气压检测装置结合了STC89C52主控芯片和BMP180气压传感器模块,实现了对大气气压的精确检测,并通过LCD1602显示器以清晰易读的方式展示结果。这个装置可以在气象观测、环境监测和高度测量等领域应用。【15】 基于单片机设计的超声波测距仪(采用HC-SR04模块)cid:link_4本项目是基于单片机设计的超声波测距仪,主要采用了STC89C52单片机和HC-SR04超声波测距模块。通过LCD1602液晶显示屏来展示测量的距离信息。超声波测距技术是一种常见的非接触式测距方法,利用超声波的传播速度测量物体与测距器之间的距离。它具有测量范围广、精度高、反应迅速等特点,在自动控制、机器人导航、无人驾驶等领域得到广泛应用。本项目选用了STC89C52作为主控芯片,它是一款常见且功能强大的8051系列单片机,具有较强的计算和控制能力,适合用于各种嵌入式系统设计。同时,采用了HC-SR04超声波测距模块作为测距模块。HC-SR04模块能够向前发射超声波信号,并接收回波信号,通过测量发射到接收之间的时间差来计算出距离。通过STC89C52单片机与HC-SR04超声波测距模块的连接和控制,可以实时获取测得的距离,并通过LCD1602液晶显示屏进行显示。LCD1602液晶显示屏具有简单、直观、易于读取的优点,可以方便地展示测量的距离信息。本项目目的是设计一款简单而实用的超声波测距仪,为用户提供准确、可靠的距离测量功能,以满足不同领域对测距需求的应用。
  • [技术干货] 基于单片机设计的超声波测距仪(采用HC-SR04模块)
    一、前言本项目是基于单片机设计的超声波测距仪,主要采用了STC89C52单片机和HC-SR04超声波测距模块。通过LCD1602液晶显示屏来展示测量的距离信息。超声波测距技术是一种常见的非接触式测距方法,利用超声波的传播速度测量物体与测距器之间的距离。它具有测量范围广、精度高、反应迅速等特点,在自动控制、机器人导航、无人驾驶等领域得到广泛应用。本项目选用了STC89C52作为主控芯片,它是一款常见且功能强大的8051系列单片机,具有较强的计算和控制能力,适合用于各种嵌入式系统设计。同时,采用了HC-SR04超声波测距模块作为测距模块。HC-SR04模块能够向前发射超声波信号,并接收回波信号,通过测量发射到接收之间的时间差来计算出距离。通过STC89C52单片机与HC-SR04超声波测距模块的连接和控制,可以实时获取测得的距离,并通过LCD1602液晶显示屏进行显示。LCD1602液晶显示屏具有简单、直观、易于读取的优点,可以方便地展示测量的距离信息。本项目目的是设计一款简单而实用的超声波测距仪,为用户提供准确、可靠的距离测量功能,以满足不同领域对测距需求的应用。二、项目设计思路2.1 硬件设计本项目的硬件设计思路主要包括以下几个方面:主控芯片选择、超声波测距模块选择、电源设计与连接方式。(1)主控芯片选择:在本项目中,选用STC89C52单片机作为主控芯片。STC89C52是一款基于8051架构的单片机,具有丰富的外设资源和强大的计算能力,适合用于嵌入式系统设计。(2)超声波测距模块选择:在本项目中,选用HC-SR04超声波测距模块作为测距模块。HC-SR04模块具有发送超声波信号和接收回波信号的功能,能够准确测量物体与测距器之间的距离。(3)电源设计与连接方式:为了给单片机和超声波测距模块提供稳定的工作电源,可以选择使用直流电源适配器或者电池供电。通常情况下,将电源正极连接到单片机和超声波测距模块的VCC引脚上,将电源负极连接到GND引脚上。2.2 软件设计(1)初始化设置:在程序的开始部分,需要对单片机进行初始化设置,包括引脚模式设置、定时器设置等。(2)发送超声波信号:通过单片机控制超声波测距模块的发送引脚,发射一定频率和持续时间的超声波信号。(3)接收回波信号:单片机通过控制超声波测距模块的接收引脚,监听回波信号并计时,记录回波信号的持续时间。(4)距离计算:根据回波信号的持续时间,可以计算出物体与测距器之间的距离。在软件中进行相关的计算,并将计算结果保存在变量中。(5)数据显示:通过LCD1602液晶显示屏,将测量得到的距离信息显示出来。可以通过单片机控制液晶显示屏的引脚,向其发送相应的数据和命令。2.3 硬件模块与单片机的连接方式将HC-SR04超声波测距模块和LCD1602液晶显示屏连接到STC89C52单片机的IO口:HC-SR04模块引脚连接:(1)Trig引脚连接到单片机的P1.0口(2)Echo引脚连接到单片机的P1.1口(3)VCC引脚连接到单片机的VCC引脚(4)GND引脚连接到单片机的GND引脚LCD1602液晶显示屏引脚连接:(1)RS引脚连接到单片机的P2.0口(2)RW引脚连接到单片机的P2.1口(3)E引脚连接到单片机的P2.2口(4)D4引脚连接到单片机的P2.4口(5)D5引脚连接到单片机的P2.5口(6)D6引脚连接到单片机的P2.6口(7)D7引脚连接到单片机的P2.7口(8)VCC引脚连接到单片机的VCC引脚(9)GND引脚连接到单片机的GND引脚三、HC-SR04 模块介绍HC-SR04 是一种常用的超声波测距模块,能够通过发射超声波脉冲并接收其回波来测量物体与模块之间的距离。以下是 HC-SR04 超声波测距模块的详细介绍:(1)原理:HC-SR04 模块利用超声波的回波时间来计算物体与模块之间的距离。它由两个主要部分组成:超声波发射器和超声波接收器。发射器向前方发射短脉冲的超声波,然后接收器接收到回波。通过测量发射和接收之间的时间差,可以计算出物体到模块的距离。(2)测量原理:HC-SR04 模块使用的超声波是不可闻的高频声波,通常工作频率为40 kHz。模块通过发送一个10微秒的脉冲来触发超声波的发射,然后等待接收回波。当接收到回波时,模块会停止计时,并将测量到的时间转换为距离。(3)测量范围:HC-SR04 模块的测量范围通常在2cm到400cm之间,具体取决于环境条件和模块质量。较远距离的测量可能会有一定的误差。(4)工作电压:HC-SR04 模块的工作电压通常为5V,可以通过连接到微控制器或其他适配器来供电。(5)接口:HC-SR04 模块通常使用四个引脚进行连接。其中,两个引脚用于电源供电(VCC 和 GND),一个引脚用于触发超声波发射(Trig),另一个引脚用于接收超声波回波并输出测量结果(Echo)。(6)使用方法:使用 HC-SR04 模块进行测距通常需要以下步骤:将模块连接到适当的电源和控制器。发送一个短脉冲触发信号到 Trig 引脚,使模块发射超声波。检测 Echo 引脚上的回波信号,并计算回波时间。根据回波时间和声速的关系,计算出物体到模块的距离。可以通过适当的算法和校准来提高测量的准确性。(7)应用领域:HC-SR04 模块被广泛应用于距离测量、避障、无人机高度控制、智能车辆导航、自动门控制等领域。它具有简单易用、成本低廉和较好的测距精度等特点,适用于许多电子和机器人项目。HC-SR04 超声波测距模块利用超声波的发射和接收来测量物体到模块之间的距离。它是一种常用的测距模块,具有简单易用、成本低廉和较好的测量精度等特点,适用于各种距离测量和避障应用。四、项目完整代码设计#include <reg52.h>#include <intrins.h>​#define LCD_RS P2_0 // 液晶显示屏的RS引脚连接到P2.0口#define LCD_RW P2_1 // 液晶显示屏的RW引脚连接到P2.1口#define LCD_E P2_2 // 液晶显示屏的E引脚连接到P2.2口#define LCD_D4 P2_4 // 液晶显示屏的D4引脚连接到P2.4口#define LCD_D5 P2_5 // 液晶显示屏的D5引脚连接到P2.5口#define LCD_D6 P2_6 // 液晶显示屏的D6引脚连接到P2.6口#define LCD_D7 P2_7 // 液晶显示屏的D7引脚连接到P2.7口​#define TRIG P1_0 // 超声波测距模块的Trig引脚连接到P1.0口#define ECHO P1_1 // 超声波测距模块的Echo引脚连接到P1.1口​sbit RS = LCD_RS;sbit RW = LCD_RW;sbit E = LCD_E;sbit D4 = LCD_D4;sbit D5 = LCD_D5;sbit D6 = LCD_D6;sbit D7 = LCD_D7;​void delay(unsigned int time) { while (time--) { for (int i = 0; i < 120; i++); }}​void lcd_enable() { E = 1; _nop_(); E = 0;}​void lcd_write_cmd(unsigned char cmd) { RS = 0; RW = 0; P0 = cmd; lcd_enable();}​void lcd_write_data(unsigned char dat) { RS = 1; RW = 0; P0 = dat; lcd_enable();}​void lcd_init() { lcd_write_cmd(0x38); // 初始化8位数据接口,2行显示,5x7点阵字符 lcd_write_cmd(0x0c); // 开启显示,不显示光标 lcd_write_cmd(0x06); // 光标右移,不移动显示 lcd_write_cmd(0x01); // 清屏}​void lcd_clear() { lcd_write_cmd(0x01);}​void lcd_print(const unsigned char *str) { while (*str) { lcd_write_data(*str++); }}​unsigned int measure_distance() { unsigned int distance; TRIG = 0; // 发送触发信号 delay(10); TRIG = 1; delay(12); TRIG = 0; while (!ECHO); // 等待回波信号 TH0 = 0; // 初始化定时器初值 TL0 = 0; TR0 = 1; // 启动定时器 while (ECHO); // 等待回波信号结束 TR0 = 0; // 停止定时器 distance = TH0 * 256 + TL0; // 计算距离 distance = distance / 58; // 将时间转换为距离(单位:厘米) return distance;}​void main() { unsigned int distance; lcd_init(); lcd_clear(); while (1) { distance = measure_distance(); // 测量距离 lcd_clear(); lcd_print("Distance: "); if (distance >= 100) { lcd_write_data(distance / 100 % 10 + '0'); lcd_write_data(distance / 10 % 10 + '0'); lcd_write_data('.'); lcd_write_data(distance % 10 + '0'); lcd_print(" m"); } else { lcd_write_data(distance / 10 % 10 + '0'); lcd_write_data('.'); lcd_write_data(distance % 10 + '0'); lcd_print(" cm"); } delay(200); }}​五、总结本项目成功地设计和实现了一个基于STC89C52单片机和HC-SR04超声波测距模块的超声波测距仪。实现了距离测量和LCD显示的功能。这个简单而实用的设备可以广泛应用于距离测量和自动控制领域,为生活带来了便利。通过项目的实施,可以获得丰富的经验和知识。学会如何正确地连接硬件设备,理解和操作单片机的IO口,以及使用超声波测距模块进行距离测量。同时,可以提高C语言编程的技巧,掌握LCD1602液晶显示屏的控制方法。
  • [技术干货] 基于单片机设计的大气气压检测装置(STC89C52+BMP180实现)
    一、前言本项目设计一个大气气压检测装置,该装置以单片机为基础,采用STC89C52作为核心控制芯片,结合BMP180模块作为气压传感器。大气气压,也就是由气体重力在大气层中产生的压力,其变化与天气预报、气象观测以及高度测量等方面密切相关。在这个设计中,STC89C52作为主控芯片,其强大的功能和广泛的应用,特别是丰富的外设资源和稳定可靠的性能,使得它能够与BMP180模块通信,从而获取精确的大气气压数据。BMP180模块是一种高精度、低功耗的数字式气压传感器,可以测量大气压强,也能通过计算得到气温和海拔高度等信息。它将这些信息传输给STC89C52主控芯片,进行后续处理和显示。为了使用户更直观地读取大气气压信息,这个设计采用了LCD1602显示器来实时显示气压数据。LCD1602是一种常见的字符型液晶显示器,可以文本形式展示信息。通过适当的程序设计,我们可以将BMP180模块获取的大气气压数据转换为人类可读的字符,并实时在LCD1602上显示。这个大气气压检测装置结合了STC89C52主控芯片和BMP180气压传感器模块,实现了对大气气压的精确检测,并通过LCD1602显示器以清晰易读的方式展示结果。这个装置可以在气象观测、环境监测和高度测量等领域应用。二、项目设计过程2.1 硬件设计思路(1)主控芯片选择:本项目选择STC89C52作为主控芯片。STC89C52是一款功能强大且广泛使用的单片机,具有丰富的外设资源和稳定可靠的性能。(2)气压传感器选择:选用BMP180模块作为气压传感器。BMP180是一种高精度、低功耗的数字式气压传感器。它能够测量大气压强,并通过相关算法计算出气温和海拔高度等信息。(3)显示模块选择:采用LCD1602显示器作为显示模块。LCD1602是一种常用的字符型液晶显示器,能够以文本形式显示信息。通过适当的程序设计,将测量得到的大气气压数据转换成可读的字符,并显示在LCD1602上。(4)连接方式:将BMP180模块与STC89C52单片机通过I2C总线连接。I2C总线是一种串行通信协议,适合连接多个从设备。通过I2C总线,STC89C52能够与BMP180模块进行数据交互。2.2 软件设计思路(1)硬件初始化:在软件开头进行硬件的初始化工作,包括串口初始化、I2C总线初始化和LCD1602显示器初始化。(2)I2C通信控制:编写相应的函数来实现与BMP180模块之间的I2C通信。通过读取模块的寄存器,获取气压、温度等原始数据,并将其转换为可用的数据格式。(3)数据处理与显示:对读取到的气压数据进行处理,如单位转换,计算出精确的大气压强值。同时,根据需要,可以通过BMP180模块提供的算法计算气温、海拔高度等信息。将处理后的数据以字符形式显示在LCD1602上。(4)循环运行:在主程序中设置一个循环,使系统能够实时更新气压值,并将其显示在LCD1602上。可以根据需要设置采样率和刷新频率。2.3 硬件模块与单片机连接(1)将BMP180模块的引脚连接到STC89C52单片机的相应IO口:BMP180模块引脚STC89C52单片机引脚VCC5V电源GNDGNDSCLP2.0 (I2C时钟线)SDAP2.1 (I2C数据线)在这个连接方式中,选择了STC89C52单片机的P2口作为I2C总线的引脚。也可以根据自己的需要和硬件设计来选择其他IO口作为I2C总线的引脚。连接后,需要在软件中初始化I2C总线,并使用相应的I2C通信函数与BMP180模块进行数据交互。(2)将LCD1602模块的引脚连接到STC89C52单片机的相应IO口:LCD1602模块引脚STC89C52单片机引脚VSSGNDVDD5V电源VO通过电位器调节LCD显示对比度RSP3.4RWGNDEP3.5D0~D7不连接或者接地A5V电源KGND在这个连接方式中,选择了STC89C52单片机的P3口作为LCD1602的控制引脚。也可以根据自己的需要和硬件设计来选择其他IO口作为LCD1602的控制引脚。连接后,需要在软件中初始化LCD1602,并使用相应的函数在LCD上显示数据。三、BMP180 模块介绍BMP180 模块是一种集成式数字大气压力传感器模块,由Bosch Sensortec 公司生产。它基于微电机系统(MEMS)技术,能够测量大气压力和温度,提供高精度的气压和温度测量功能。以下是 BMP180 模块的主要特点和功能:(1)气压测量:BMP180 可以测量大气压力,并提供绝对压力、相对压力和海拔高度等数据。它支持广泛的压力测量范围,通常为 300 hPa 至 1100 hPa。这使得它适用于气象监测、高度测量、天气预报和气压趋势分析等应用。(2)温度测量:BMP180 还具有温度测量功能,可以提供环境温度数据。这对于需要考虑温度变化对压力测量的影响的应用非常重要。(3)高精度测量:BMP180 模块提供高精度的压力和温度测量。它使用24位的ADC(模数转换器)进行测量,并提供高分辨率的数据输出。这使得它能够提供准确的大气压力和温度数据。(4)数字输出接口:BMP180 通过I2C接口与主控制器通信。这种数字接口使得与微控制器、单片机或其他数字设备的集成变得简单。(5)低功耗:BMP180 设计为低功耗模式,可以在低功耗下运行。它具有多种省电模式,可根据应用需求进行配置,以延长电池寿命。(6)自动补偿和校准:BMP180 模块具有自动温度补偿和校准功能,以提高测量的准确性和稳定性。它可以根据环境条件自动调整并校准传感器输出,以减少温度和其他因素对测量结果的影响。(7)应用领域:BMP180 模块适用于许多应用领域,如气象测量、高度计、室内导航系统、气压计算设备等。它在无人机、天气站、汽车导航和气象预报等领域具有广泛的应用。BMP180 是一种功能强大的集成数字大气压力传感器模块,具有高精度测量、低功耗和数字接口等特点,适用于多种大气压力和温度测量应用。四、项目代码设计#include <reg52.h>#include <intrins.h>​#define LCD_RS P3_4 // LCD1602 RS引脚连接的单片机IO口#define LCD_E P3_5 // LCD1602 E引脚连接的单片机IO口#define BMP180_ADDR 0xEE // BMP180的I2C地址​// 函数声明void delay(unsigned int count);void I2C_Start();void I2C_Stop();void I2C_SendByte(unsigned char dat);unsigned char I2C_ReceiveByte();void LCD_Init();void LCD_WriteCmd(unsigned char cmd);void LCD_WriteData(unsigned char dat);void LCD_DisplayString(unsigned char row, unsigned char col, unsigned char *str);void BMP180_Init();int BMP180_ReadPressure();float BMP180_CalculateTemperature(int ut);​// 主函数void main() { unsigned char str[16]; // 存储字符串的数组 int pressure; // 读取到的气压值 float temperature; // 计算得到的温度值 LCD_Init(); // 初始化LCD1602 BMP180_Init(); // 初始化BMP180 while (1) { pressure = BMP180_ReadPressure(); // 读取气压数据 temperature = BMP180_CalculateTemperature(123); // 计算温度(示例数值) // 将气压和温度转换为字符串 sprintf(str, "Pressure: %d", pressure); LCD_DisplayString(0, 0, str); sprintf(str, "Temperature: %.1f", temperature); LCD_DisplayString(1, 0, str); delay(1000); // 延时1秒 }}​// 延时函数void delay(unsigned int count) { while (count--) { _nop_(); _nop_(); }}​// I2C起始信号void I2C_Start() { SDA = 1; delay(1); SCL = 1; delay(1); SDA = 0; delay(1); SCL = 0; delay(1);}​// I2C停止信号void I2C_Stop() { SDA = 0; delay(1); SCL = 1; delay(1); SDA = 1; delay(1);}​// I2C发送一个字节void I2C_SendByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) >> 7; dat <<= 1; delay(1); SCL = 1; delay(1); SCL = 0; delay(1); } SDA = 1; delay(1); SCL = 1; delay(1); SCL = 0; delay(1);}​// I2C接收一个字节unsigned char I2C_ReceiveByte() { unsigned char i, dat = 0; SDA = 1; for (i = 0; i < 8; i++) { dat <<= 1; SCL = 1; delay(1); dat |= SDA; SCL = 0; delay(1); } return dat;}​// LCD1602初始化void LCD_Init() { delay(15000); LCD_WriteCmd(0x38); // 8位数据接口,2行显示,5x7字符 LCD_WriteCmd(0x0C); // 显示开,光标不显示 LCD_WriteCmd(0x06); // 入口模式,不移动光标 LCD_WriteCmd(0x01); // 清屏 delay(2000);}​// 向LCD1602写入命令void LCD_WriteCmd(unsigned char cmd) { LCD_RS = 0; _nop_(); LCD_E = 1; _nop_(); P0 = cmd; _nop_(); LCD_E = 0; _nop_();}​// 向LCD1602写入数据void LCD_WriteData(unsigned char dat) { LCD_RS = 1; _nop_(); LCD_E = 1; _nop_(); P0 = dat; _nop_(); LCD_E = 0; _nop_();}​// 在LCD1602上显示字符串void LCD_DisplayString(unsigned char row, unsigned char col, unsigned char *str) { unsigned char i = 0; if (row == 0) { LCD_WriteCmd(0x80 + col); // 第一行 } else { LCD_WriteCmd(0xC0 + col); // 第二行 } while (str[i] != '\0') { LCD_WriteData(str[i]); i++; }}​// BMP180初始化void BMP180_Init() { // 初始化代码 I2C_Start(); I2C_SendByte(BMP180_ADDR); I2C_SendByte(0xF4); // 控制寄存器地址 I2C_SendByte(0x2E); // 气压转换命令 I2C_Stop(); delay(10000);}​// 读取气压数据int BMP180_ReadPressure() { unsigned char msb, lsb, xlsb; int pressure; I2C_Start(); I2C_SendByte(BMP180_ADDR); I2C_SendByte(0xF6); // 数据寄存器地址 I2C_Stop(); I2C_Start(); I2C_SendByte(BMP180_ADDR | 1); // 读模式 msb = I2C_ReceiveByte(); I2C_SendByte(ACK); // 发送应答位 lsb = I2C_ReceiveByte(); I2C_SendByte(ACK); // 发送应答位 xlsb = I2C_ReceiveByte(); I2C_SendByte(NO_ACK); // 发送非应答位 I2C_Stop(); pressure = (msb << 16) | (lsb << 8) | xlsb; return pressure;}​// 计算温度float BMP180_CalculateTemperature(int ut) { long x1, x2, temp; float temperature; x1 = ((ut - AC6) * AC5) >> 15; x2 = (MC << 11) / (x1 + MD); temp = x1 + x2; temperature = ((temp + 8) >> 4) / 10.0; return temperature;}​五、总结文章中介绍了基于STC89C52单片机和BMP180传感器的大气气压检测装置,并利用LCD1602显示器展示了气压信息。通过这个项目,实现了以下功能:初始化设置、气压数据读取、温度计算、显示功能以及实时更新。这些功能的结合使得我们能够准确地监测和显示大气压力的变化。通过这个项目,了解到BMP180传感器可以提供准确的气压和温度数据,并且通过一系列计算和校准实现了真实温度值的计算。
总条数:641 到第 页
上滑加载中