• [技术干货] 如何编写【GPIO】设备的驱动程序?
    目录示例程序目标编写驱动程序编写应用程序卸载驱动模块别人的经验,我们的阶梯!大家好,我是道哥。在前几篇文章中,我们一块讨论了:在 Linux 系统中,编写字符设备驱动程序的基本框架,主要是从代码流程和 API 函数这两方面触发。这篇文章,我们就以此为基础,写一个有实际应用功能的驱动程序:1. 在驱动程序中,初始化 GPIO 设备,自动创建设备节点;2. 在应用程序中,打开 GPIO 设备,并发送控制指令设置 GPIO 口的状态;示例程序目标编写一个驱动程序模块:mygpio.ko。当这个驱动模块被加载的时候,在系统中创建一个 mygpio 类设备,并且在 /dev 目录下,创建 4 个设备节点:/dev/mygpio0/dev/mygpio1/dev/mygpio2/dev/mygpio3因为我们现在是在 x86 平台上来模拟 GPIO 的控制操作,并没有实际的 GPIO 硬件设备。因此,在驱动代码中,与硬件相关部分的代码,使用宏 MYGPIO_HW_ENABLE 控制起来,并且在其中使用printk输出打印信息来体现硬件的操作。在应用程序中,可以分别打开以上这 4 个 GPIO 设备,并且通过发送控制指令,来设置 GPIO 的状态。编写驱动程序以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。创建驱动目录和驱动程序$ cd linux-4.15/drivers/$ mkdir mygpio_driver$ cd mygpio_driver$ touch mygpio.cmygpio.c 文件的内容如下(不需要手敲,文末有代码下载链接):相对于前几篇文章来说,上面的代码稍微有一点点复杂,主要是多了宏定义 MYGPIO_HW_ENABLE 控制部分的代码。比如:在这个宏定义控制下的三个与硬件相关的函数:gpio_hw_init()gpio_hw_release()gpio_hw_set()就是与GPIO硬件的初始化、释放、状态设置相关的操作。代码中的注释已经比较完善了,结合前几篇文章中的函数说明,还是比较容易理解的。从代码中可以看出:驱动程序使用 alloc_chrdev_region 函数,来动态注册设备号,并且利用了 Linux 应用层中的 udev 服务,自动在 /dev 目录下创建了设备节点。另外还有一点:在上面示例代码中,对设备的操作函数只实现了 open 和 ioctl 这两个函数,这是根据实际的使用场景来决定的。这个示例中,只演示了如何控制 GPIO 的状态。你也可以稍微补充一下,增加一个read函数,来读取某个GPIO口的状态。控制 GPIO 设备,使用 write 或者 ioctl 函数都可以达到目的,只是 ioctl 更灵活一些。创建 Makefile 文件$ touch Makefile内容如下:编译驱动模块$ make得到驱动程序: mygpio.ko 。加载驱动模块在加载驱动模块之前,先来检查一下系统中,几个与驱动设备相关的地方。先看一下 /dev 目录下,目前还没有设备节点( /dev/mygpio[0-3] )。$ ls -l /dev/mygpio*ls: cannot access '/dev/mygpio*': No such file or directory再来查看一下 /proc/devices 目录下,也没有 mygpio 设备的设备号。$ cat /proc/devices为了方便查看打印信息,把dmesg输出信息清理一下:$ sudo dmesg -c现在来加载驱动模块,执行如下指令:$ sudo insmod mygpio.ko当驱动程序被加载的时候,通过 module_init( ) 注册的函数 gpio_driver_init() 将会被执行,那么其中的打印信息就会输出。还是通过 dmesg 指令来查看驱动模块的打印信息:$ dmesg可以看到:操作系统为这个设备分配的主设备号是 244,并且也打印了GPIO硬件的初始化函数的调用信息。此时,驱动模块已经被加载了!来查看一下 /proc/devices 目录下显示的设备号:$ cat /proc/devices设备已经注册了,主设备号是: 244 。设备节点由于在驱动程序的初始化函数中,使用 cdev_add 和 device_create 这两个函数,自动创建设备节点。所以,此时我们在 /dev 目录下,就可以看到下面这4个设备节点:现在,设备的驱动程序已经加载了,设备节点也被创建好了,应用程序就可以来控制 GPIO 硬件设备了。应用程序应用程序仍然放在 ~/tmp/App/ 目录下。$ mkdir ~/tmp/App/app_mygpio$ cd ~/tmp/App/app_mygpio$ touch app_mygpio.c文件内容如下:以上代码也不需要过多解释,只要注意参数的顺序即可。接下来就是编译和测试了:$ gcc app_mygpio.c -o app_mygpio执行应用程序的时候,需要携带2个参数:GPIO 设备编号(0 ~ 3),设置的状态值(0 或者 1)。这里设置一下/dev/mygpio0这个设备,状态设置为1:$ sudo ./app_mygpio 0 1[sudo] password for xxx: <输入用户密码>/dev/mygpio0: open success!如何确认/dev/mygpio0这个GPIO的状态确实被设置为1了呢?当然是看 dmesg 指令的打印信息:$ dmesg通过以上打印信息可以看到:确实执行了【设置 mygpio0 的状态为 1】的动作。再继续测试一下:设置 mygpio0 的状态为 0:$ sudo ./app_mygpio 0 0当然了,设置其他几个GPIO口的状态,都是可以正确执行的!卸载驱动模块卸载指令:$ sudo rmmod mygpio此时,/proc/devices 下主设备号 244 的 mygpio 已经不存在了。再来看一下 dmesg的打印信息:可以看到:驱动程序中的 gpio_driver_exit( ) 被调用执行了。并且,/dev 目录下的 4 个设备节点,也被函数 device_destroy() 自动删除了!
  • [问题求助] stm32l431rct6单片机坏了
    开发板和电脑连接是亮灯的,但单片机突然发烫,再重启,lcd屏也是黑屏状态了,怎么修呢
  • [技术干货] Linux驱动开发_倒车影像项目介绍[转载]
    原文链接:https://bbs.huaweicloud.com/blogs/353235倒车影像项目模拟: 汽车中控台---倒车影像。组成部分:1.​ LCD屏: 实时显示摄像头采集的数据。2.​ 摄像头: 放在车尾,采集图像传输给LCD屏进行显示。3.​ 倒车雷达: 超声波测距--->测量车尾距离障碍物的距离。4.​ 蜂鸣器: 根据倒车雷达测量的距离,控制频率。1.1 超声波测距模块声波测距: 已知声音在空气中传播的速度。​ 硬件接线:ECHO------->GPX1_0 (开发板第9个IO口): 中断引脚----->检测回波----输入TRIG ------->GPB_7 (开发板第8个IO口): 输出触发信号。1.2 PWM方波控制蜂鸣器​ PWM方波:​ 内核自带的PWM方波驱动1.3 UVC免驱摄像头编程框架: V4L2编程的框架: v4l2--->全称: video4linux2V4L2 : 针对UVC免驱USB设备设计框架。专用于USB摄像头的数据采集。免驱 : 驱动已经成为标准,属于内核自带源码的一部分。V4L2框架本身注册的也是字符设备,设备节点: /dev/videoXV4L2 框架: 提供ioctl接口,提供了有很多命令,可以通过这些命令对摄像头做配置。比如: 输出的图像尺寸,输出图像格式(RGB、YUV、JPG),申请采集数据的缓冲区。​ 配置摄像头采集队列步骤:Mmap函数映射。超声波驱动读取距离:#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/delay.h> #include <linux/workqueue.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/timer.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/fcntl.h> #include <linux/interrupt.h> #include <linux/ktime.h> static unsigned int distance_irq; /*存放中断号*/ static u32 *GPB_DAT=NULL; static u32 *GPB_CON=NULL; static u32 distance_time_us=0; /*表示距离的时间*/ /* 工作队列处理函数: */ static void distance_work_func(struct work_struct *work) { u32 time1,time2; time1=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*/ /*等待高电平时间结束*/ while(gpio_get_value(EXYNOS4_GPX1(0))){} time2=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*/ distance_time_us=time2-time1; //printk("us=%d\n",time2-time1); /*us/58=厘米*/ } /*静态方式初始化工作队列*/ static DECLARE_WORK(distance_work,distance_work_func); /* 中断处理函数: 用于检测超声波测距的回波 */ static irqreturn_t distance_handler(int irq, void *dev) { /*调度工作队列*/ schedule_work(&distance_work); return IRQ_HANDLED; } static void distance_function(unsigned long data); /*静态方式定义内核定时器*/ static DEFINE_TIMER(distance_timer,distance_function,0,0); /*内核定时器超时处理函数: 触发超声波发送方波*/ static void distance_function(unsigned long data) { static u8 state=0; state=!state; /*更改GPIO口电平*/ if(state) { *GPB_DAT|=1<<7; } else { *GPB_DAT&=~(1<<7); } /*修改定时器的超时时间*/ mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100)); } static int distance_open(struct inode *inode, struct file *file) { return 0; } #define GET_US_TIME 0x45612 static long distance_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long argv) { u32 *us_data=(u32*)argv; int err; u32 time_us=distance_time_us; switch(cmd) { case GET_US_TIME: err=copy_to_user(us_data,&time_us,4); if(err!=0)printk("拷贝失败!\n"); break; } return 0; } static int distance_release(struct inode *inode, struct file *file) { return 0; } /*定义文件操作集合*/ static struct file_operations distance_fops= { .open=distance_open, .unlocked_ioctl=distance_unlocked_ioctl, .release=distance_release }; /*定义杂项设备结构体*/ static struct miscdevice distance_misc= { .minor=MISC_DYNAMIC_MINOR, .name="tiny4412_distance", .fops=&distance_fops }; static int __init tiny4412_distance_dev_init(void) { int err; /*1. 映射GPIO口地址*/ GPB_DAT=ioremap(0x11400044,4); GPB_CON=ioremap(0x11400040,4); *GPB_CON&=~(0xF<<4*7); *GPB_CON|=0x1<<4*7; /*配置输出模式*/ /*2. 根据GPIO口编号,获取中断号*/ distance_irq=gpio_to_irq(EXYNOS4_GPX1(0)); /*3. 注册中断*/ err=request_irq(distance_irq,distance_handler,IRQ_TYPE_EDGE_RISING,"distance_device",NULL); if(err!=0)printk("中断注册失败!\n"); else printk("中断:超声波测距驱动安装成功!\n"); /*4. 修改定时器超时时间*/ mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100)); /*杂项设备注册*/ misc_register(&distance_misc); return 0; } static void __exit tiny4412_distance_dev_exit(void) { /*5. 注销中断*/ free_irq(distance_irq,NULL); /*6. 停止定时器*/ del_timer(&distance_timer); /*7. 取消IO映射*/ iounmap(GPB_DAT); iounmap(GPB_CON); /*注销杂项设备*/ misc_deregister(&distance_misc); printk("中断:超声波测距驱动卸载成功!\n"); } module_init(tiny4412_distance_dev_init); module_exit(tiny4412_distance_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("tiny4412 wbyq");摄像头代码,读取摄像头画面:#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <poll.h> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <poll.h> #include <signal.h> #include <pthread.h> #include <linux/videodev2.h> #include <stdlib.h> #include <sys/mman.h> #include <string.h> #include "framebuffer.h" #define PWM_DEVICE "/dev/pwm" /*PWM方波设备文件*/ #define DISTANCE_DEVICE "/dev/tiny4412_distance" /*超声波测距设备文件*/ #define UVC_VIDEO_DEVICE "/dev/video15" /*UVC摄像头设备节点*/ #define GET_US_TIME 0x45612 /*获取超声波测量的距离: ioctl命令*/ #define PWM_IOCTL_SET_FREQ 1 /*控制PWM方波频率: ioctl命令*/ #define PWM_IOCTL_STOP 0 /*停止PWM方波输出: ioctl命令*/ int distance_fd; /*超声波设备的文件描述符*/ int pwm_fd; /*PWM方波设备的文件描述符*/ int uvc_video_fd; /*UVC摄像头设备文件描述符*/ int Image_Width; /*图像的宽度*/ int Image_Height; /*图像的高度*/ unsigned char *video_memaddr_buffer[4]; /*存放摄像头映射到进程空间的缓冲区地址*/ /* 函数功能: 用户终止了进程调用 */ void exit_sighandler(int sig) { //停止PWM波形输出,关闭蜂鸣器 ioctl(pwm_fd,PWM_IOCTL_STOP,0); close(pwm_fd); close(distance_fd); exit(1); } /* 函数功能: 读取超声波数据的线程 */ void *distance_Getpthread_func(void *dev) { /*1. 打开PWM方波驱动*/ pwm_fd=open(PWM_DEVICE,O_RDWR); if(pwm_fd<0) //0 1 2 { printf("%s 设备文件打开失败\n",PWM_DEVICE); /*退出线程*/ pthread_exit(NULL); } /*2. 打开超声波测距设备*/ distance_fd=open(DISTANCE_DEVICE,O_RDWR); if(distance_fd<0) //0 1 2 { printf("%s 设备文件打开失败\n",DISTANCE_DEVICE); /*退出线程*/ pthread_exit(NULL); } /*3. 循环读取超声波测量的距离*/ struct pollfd fds; fds.fd=distance_fd; fds.events=POLLIN; int data; while(1) { poll(&fds,1,-1); ioctl(distance_fd,GET_US_TIME,&data); printf("距离(cm):%0.2f\n",data/58.0); data=data/58; if(data>200) /*200厘米: 安全区域*/ { //停止PWM波形输出,关闭蜂鸣器 ioctl(pwm_fd,PWM_IOCTL_STOP,0); } else if(data>100) /*100厘米: 警告区域*/ { printf("警告区域!\n"); ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,2); } else /*小于<100厘米: 危险区域*/ { printf(" 危险区域!\n"); ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,10); } //ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,pwm_data); /*倒车影像: 测距有3个档位*/ } } /* 函数功能: UVC摄像头初始化 返回值: 0表示成功 */ int UVCvideoInit(void) { /*1. 打开摄像头设备*/ uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR); if(uvc_video_fd<0) { printf("%s 摄像头设备打开失败!\n",UVC_VIDEO_DEVICE); return -1; } /*2. 设置摄像头的属性*/ struct v4l2_format format; memset(&format,0,sizeof(struct v4l2_format)); format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/ format.fmt.pix.width=800; /*预设的宽度*/ format.fmt.pix.height=480; /*预设的高度*/ format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*预设的格式*/ format.fmt.pix.field=V4L2_FIELD_ANY; /*系统自动设置: 帧属性*/ if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*设置摄像头的属性*/ { printf("摄像头格式设置失败!\n"); return -2; } Image_Width=format.fmt.pix.width; Image_Height=format.fmt.pix.height; printf("摄像头实际输出的图像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height); if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV) { printf("当前摄像头支持YUV格式图像输出!\n"); } else { printf("当前摄像头不支持YUV格式图像输出!\n"); return -3; } /*3. 请求缓冲区: 申请摄像头数据采集的缓冲区*/ struct v4l2_requestbuffers req_buff; memset(&req_buff,0,sizeof(struct v4l2_requestbuffers)); req_buff.count=4; /*预设要申请4个缓冲区*/ req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申请缓冲区*/ { printf("申请摄像头数据采集的缓冲区失败!\n"); return -4; } printf("摄像头缓冲区申请的数量: %d\n",req_buff.count); /*4. 获取缓冲区的详细信息: 地址,编号*/ struct v4l2_buffer buff_info; memset(&buff_info,0,sizeof(struct v4l2_buffer)); int i; for(i=0;i<req_buff.count;i++) { buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_QUERYBUF,&buff_info)) /*获取缓冲区的详细信息*/ { printf("获取缓冲区的详细信息失败!\n"); return -5; } /*根据摄像头申请缓冲区信息: 使用mmap函数将内核的地址映射到进程空间*/ video_memaddr_buffer[i]=mmap(NULL,buff_info.length,PROT_READ|PROT_WRITE,MAP_SHARED,uvc_video_fd,buff_info.m.offset); if(video_memaddr_buffer[i]==NULL) { printf("缓冲区映射失败!\n"); return -6; } } /*5. 将缓冲区放入采集队列*/ memset(&buff_info,0,sizeof(struct v4l2_buffer)); for(i=0;i<req_buff.count;i++) { buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.index=i; /*缓冲区的节点编号*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info)) /*根据节点编号将缓冲区放入队列*/ { printf("根据节点编号将缓冲区放入队列失败!\n"); return -7; } } /*6. 启动摄像头数据采集*/ int Type=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(uvc_video_fd,VIDIOC_STREAMON,&Type)) { printf("启动摄像头数据采集失败!\n"); return -8; } return 0; } /* 函数功能: 将YUV数据转为RGB格式 函数参数: unsigned char *yuv_buffer: YUV源数据 unsigned char *rgb_buffer: 转换之后的RGB数据 int iWidth,int iHeight : 图像的宽度和高度 */ void yuv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight) { int x; int z=0; unsigned char *ptr = rgb_buffer; unsigned char *yuyv= yuv_buffer; for (x = 0; x < iWidth*iHeight; x++) { int r, g, b; int y, u, v; if (!z) y = yuyv[0] << 8; else y = yuyv[2] << 8; u = yuyv[1] - 128; v = yuyv[3] - 128; r = (y + (359 * v)) >> 8; g = (y - (88 * u) - (183 * v)) >> 8; b = (y + (454 * u)) >> 8; *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); if(z++) { z = 0; yuyv += 4; } } } int main(int argc,char **argv) { int data; /*1. 注册将要捕获的信号*/ signal(SIGINT,exit_sighandler); /*2. 创建线程: 采集超声波测量的距离*/ pthread_t threadID; pthread_create(&threadID,NULL,distance_Getpthread_func,NULL); pthread_detach(threadID); //设置分离属性 /*3. 初始化摄像头*/ UVCvideoInit(); /*4. 初始化LCD屏*/ framebuffer_Device_init(); /*5. 循环采集摄像头的数据*/ struct pollfd fds; fds.fd=uvc_video_fd; fds.events=POLLIN; struct v4l2_buffer buff_info; memset(&buff_info,0,sizeof(struct v4l2_buffer)); int index=0; /*表示当前缓冲区的编号*/ unsigned char *rgb_buffer=NULL; /*申请空间:存放转换之后的RGB数据*/ rgb_buffer=malloc(Image_Width*Image_Height*3); if(rgb_buffer==NULL) { printf("RGB转换的缓冲区申请失败!\n"); exit(0); } while(1) { /*1. 等待摄像头采集数据*/ poll(&fds,1,-1); /*2. 取出一帧数据: 从采集队列里面取出一个缓冲区*/ buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ ioctl(uvc_video_fd,VIDIOC_DQBUF,&buff_info); /*从采集队列取出缓冲区*/ index=buff_info.index; //printf("采集数据的缓冲区的编号:%d\n",index); /*3. 处理数据: YUV转RGB\显示到LCD屏*/ //video_memaddr_buffer[index]; /*当前存放数据的缓冲区地址*/ /*3.1 将YUV数据转为RGB格式*/ yuv_to_rgb(video_memaddr_buffer[index],rgb_buffer,Image_Width,Image_Height); /*3.2 将RGB数据实时刷新到LCD屏幕上*/ framebuffer_DisplayImages((800-Image_Width)/2,0,Image_Width,Image_Height,rgb_buffer); /*4. 将缓冲区再次放入采集队列*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.index=index; /*缓冲区的节点编号*/ ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info); /*根据节点编号将缓冲区放入队列*/ } return 0; }
  • [技术干货] GB2312、GBK汉字取模与字库偏移地址的计算-单片机上常用
    一、 GB2312编码GB2312 码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集--基本集》, 由国家标准总局发布, 1981 年 5 月 1 日 实施,通行于大陆。新加坡等地也使用此编码。GB2312 收录简化汉字及符号、字母、 日文假名等共 7445 个图形字符,其中汉字占 6763 个。GB2312 规定<对任意一个图形字符都采用两个字节表示,每个字节均采用七位编码表示>,习惯上称第一个字节为<高字节>, 第二个字节为<低字节>。GB2312-80 包含了大部分常用的一、二级汉字, 和 9 区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位 0xa1- 0xfe, 低位也是 0xa1-0xfe;汉字从 0xb0a1 开始,结束于 0xf7fe。GB2312 将代码表分为 94 个区,对应第一字节( 0xa1 -0xfe);每个区 94 个位(0xa1-0xfe),对应第二字节,两个字节的值分别为区号值和位号值加 32(20H), 因此也称为区位码。01-09 区为符号、数字区, 16-87 区为汉字区(0xb0-0xf7),10-15 区、88-94 区是有待进一步标准化的空白区。 GB2312 将收录的汉字分成两级: 第一级是常用汉字计 3755 个,置于 1 6-55 区, 按汉语拼音字母/笔形顺序排列;第二级汉字是次常用汉字计 3008 个,置于 56-87 区,按部首/笔画顺序排列。故而 GB2312 最多能表示 6763 个汉字。二、GBK编码全国信息技术化技术委员会于 1995 年 12 月 1 日《汉字内码扩展规范》。GBK 向下与 GB2312 完全兼容,向上支持 ISO 10646 国际标准,在前者向后者过渡过程中起到的承上启下的作用。GBK 亦采用双字节表示,总体编码范围为 8140-FEFE 之间,首字节在 81-FE 之间,尾字节在 40-FE 之间,剔除 XX7F 一条线。 GBK 共收入 21886 个汉字和图形符号,包括: GB2312 中的全部汉字、非汉字符号。 BIG5 中的全部汉字。 与 ISO 10646 相应的国家标准 GB13000 中的其它 CJK 汉字,以上合计 20902 个汉字。 其它汉字、部首、符号,共计 984 个。 GBK 编码区分三部分: 1) 汉字区,包括: GBK/2:OXBOA1-F7FE, 收录 GB2312 汉字 6763 个,按原序排列; GBK/3:OX8140-AOFE,收录 CJK 汉字 6080 个; GBK/4:OXAA40-FEAO,收录 CJK 汉字和增补的汉字 8160 个。 2) 图形符号区,包括: GBK/1:OXA1A1-A9FE,除 GB2312 的符号外,还增补了其它符号 GBK/5:OXA840-A9AO,扩除非汉字区。 3) 用户自定义区: 即 GBK 区域中的空白区,用户可以自己定义字符  每个 GBK 码由 2 个字节组成:第一个字节为 0X81~0XFE第二个字节分为两部分:0X40~0X7E2.0X80~0XFE。其中与 GB2312 相同的区域,字完全相同。 我们把第一个字节代表的意义称为区,那么 GBK 里面总共有 126 个区( 0XFE-0X81+1),每个区内有 190 个汉字( 0XFE-0X80+0X7E-0X40+2),总共就有 126*190=23940 个汉字。我们的点阵库只要按照这个编码规则从 0X8140 开始,逐一建立, 每个区的点阵大小为每个汉字所用的字节数*190。这样,我们就可以得到在这个字库里面定位汉字的方法: 当 GBKL<0X7F 时: Hp=((GBKH-0x81)*190+GBKL-0X40)*(size*2); 当 GBKL>0X80 时: Hp=((GBKH-0x81)*190+GBKL-0X41)*(size*2);其中 GBKH、 GBKL 分别代表 GBK 的第一个字节和第二个字节(也就是高位和低位), size代表汉字字体的大小(比如 16 字体, 12 字体等), Hp 则为对应汉字点阵数据在字库里面的起始地址(假设是从 0 开始存放)。这样我们只要得到了汉字的 GBK 码,就可以显示这个汉字了。从而实现汉字在液晶上的显示。简化公式:                 if(L<0x7f)L=L-0x40;        else L=L-0x41;        H=H-0x81;           Addr=(190*H+L)*size;L 是汉字的低字节,H是汉字的高字节。Addr 是该汉字在字库里的偏移量。Size 是该汉字的应点阵集所占的字节数量。 汉字的高字节大于0x80 ,才是汉字。高字节小于0X80就是英文字符。字库在FLASH寻址过程:首先得到该汉字点阵码在FLASH里的存储偏移量,然后在加上该汉字库在FLASH里的存放起始地址,就得到了该汉字的点阵数据位置。得到绝对位置之后,就可以读出点阵码,进行打点显示。 BIG5 编码 BIG5 是通行于台湾、香港地区的一个繁体字编码方案。虽然存在一些瑕疵,但广泛应用于电脑行业,尤其是互联网中,从而成为一种事实上的行业标准。 1983 年 10 月,台湾国家科学委员会、教育部国语推行委员会、中央标准局、行政院共同制定了《通用汉字标准交换码》,后经修订于 1992 年 5 月公布,更名为《中文标准交换码》,BIG5 是台湾资讯工业策进会根据以上标准制定的编码方案。 BIG5 码是双字节编码方案,其中第一个字节的值在 OXAO-OXFE 之间,第二个字节在 OX40-OX7E 和 OXA1-OXFE 之间。 BIG5 收录 13461 个汉字和符号,包括: 符号 408 个,编码位置 A140-A3BE 常用字 5401 个,编码位置 A440-C67E,包括台湾教育部颁布的《常用国字标准字体表》的全部汉字 4808 个,台湾教科书常用字 587 个,异体字 6 个。 次常用字 7652 个,编码位置 C940-F9D5,包括台湾教育部颁布的《次常用国字标准字体表》的全部汉字 6341 个,《罕用国字标准字体表》中使用频率较高的字 1311 个。 GB13000 编码 GB13000 等同于国际标准的《通用多八位编码字符集 (UCS)》 ISO10646.1,就是等同于 Unicode 的标准,代码页等等的都使用 UTF 的一套标准。 从 ASCII、GB2312、GBK 到 GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为 0。按照程序员的称呼,GB2312、GBK 到 GB18030 都属于双字节字符集 (DBCS)。三、偏移量计算GB2312收录简化汉字及符号、字母、日文假名等共7445 个图形字符,其中汉字占6763 个。GB2312 规定“对任意一个图形字符都采用两个字节表示,每个字节均采用七位编码表示”,习惯上称第一个字节为“高字节”,即所谓的区码。第二个字节为“低字节”,即所谓的位码。GB2312―80包含了大部分常用的一、二级汉字,和9区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位0xa1~0xfe,低位也是0xa1~0xfe;汉字从0xb0a1开始,结束于0xf7fe。GB2312将代码表分为94个区,对应第一字节(0xa1~0xfe);每个区94 个位(0xa1~0xfe),对应第二字节。两个字节的值分别为区号值和位号值加32(20H),因此也称为区位码。01~09区为符号、数字区,16~87区为汉字区(0xb0~0xf7),10~15区、88~94区是有待进一步标准化的空白区。GB2312将收录的汉字分成两级:第一级是常用汉字计3755个,置于16~55区,按汉语拼音字母/笔形顺序排列:第二级汉字是次常用汉字计3008 个,置于56~87 区,按部首/笔画顺序排列。故而GB2312 最多能表示6763 个汉字。而GBK内码完全兼容GB2312,同时支持繁体字,总汉字数有2万多个,编码格式如下,每个GBK 码由2 个字节组成,第一个字节为0X81~0XFE,第二个字节分为两部分,一是0X40~0X7E,二是0X80~0XFE。其中与GB2312相同的区域,字完全相同。把第一个字节代表的意义称为区,那么GBK里面总共有126个区(0XFE~0X81+1),每个区内有190 个汉字(0XFE~0X80+0X7E~0X40+2),总共就有126x190=23940 个汉字。点阵库只要按照这个编码规则从0X8140开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数乘以190。这样,就可以得到在这个字库里面定位汉字的方法:当GBKL<0X7F 时:Hp=((GBKH-0x81)×190+GBKL-0X40)×(sizex2);当GBKL>0X80 时:Hp=((GBKH-0x81)×190+GBKL-0X41)×(sizex2);其中GBKH、GBKLL 分别代表GBK 的第一个字节和第二个字节(也就是高位和低位),size 代表汉字字体的大小(比如16 字体,12 字体等),Hp 则为对应汉字点阵数据在字库里面的起始地址。对于GBK 字库和GB2312 字库,他们的解码部分部分略有不同,这个区别主要是由于他们的编码方式不同引起的,对于GBK 字库,解码的方式如下:qh=*code;ql=*(++code);if(ql<0x7f)    ql -= 0x40;else    ql -= 0x41;qh -= 0x81;foffset = ((unsigned long)190*qh + ql)*(size * 2); 对于GB2312 字库,解码的方式如下:qh=*code;ql=*(++code);ql -= 0xa1;qh -= 0xa1;foffset = ((unsigned long)94*qh + ql)*(size * 2);其中qh、ql 分别代表GBK 的第一个字节和第二个字节(也就是高位和低位),size代表汉字字体的大小(比如16字体,12字体等),foffset 则为对应汉字点阵数据在字库里面的起始地址。四、ASCII字符集取模方式需要使用的工具软件:\LCD屏资料\汉字取模工具\PCtoLCD2002\exeASCII字符集:!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~注意:前面第一个字符是空格。每个字符点阵码所占用的字节数为:(size/8+((size%8)?1:0))*(size/2),其中size:是字库生成时的点阵大小(12/16/24...)PC2LCD2002取模方式设置:阴码+逐列式+顺向+C51格式以下是16*16的字模示例将取出的ASCII字模使用二维数组保存,方便访问。//16*16 ASCII字符集点阵const unsigned char asc2_1608[95][16]={      {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/{0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/{0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/{0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/{0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/{0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/{0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/{0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/{0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/{0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/{0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/{0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/{0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/{0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/{0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/{0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/{0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/{0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/{0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/{0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/{0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/{0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/{0x00,0x00,0x0E,0x38,0x11,0x44,0x10,0x84,0x10,0x84,0x11,0x44,0x0E,0x38,0x00,0x00},/*"8",24*/{0x00,0x00,0x07,0x00,0x08,0x8C,0x10,0x44,0x10,0x44,0x08,0x88,0x07,0xF0,0x00,0x00},/*"9",25*/{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0C,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/{0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/{0x00,0x00,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x00,0x00},/*"<",28*/{0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x00,0x00},/*"=",29*/{0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40,0x00,0x80,0x00,0x00},/*">",30*/{0x00,0x00,0x0E,0x00,0x12,0x00,0x10,0x0C,0x10,0x6C,0x10,0x80,0x0F,0x00,0x00,0x00},/*"?",31*/{0x03,0xE0,0x0C,0x18,0x13,0xE4,0x14,0x24,0x17,0xC4,0x08,0x28,0x07,0xD0,0x00,0x00},/*"@",32*/{0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x11,0x04,0x0E,0x88,0x00,0x70,0x00,0x00},/*"B",34*/{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x1C,0x10,0x00,0x00},/*"C",35*/{0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"D",36*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x17,0xC4,0x10,0x04,0x08,0x18,0x00,0x00},/*"E",37*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x17,0xC0,0x10,0x00,0x08,0x00,0x00,0x00},/*"F",38*/{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x44,0x1C,0x78,0x00,0x40,0x00,0x00},/*"G",39*/{0x10,0x04,0x1F,0xFC,0x10,0x84,0x00,0x80,0x00,0x80,0x10,0x84,0x1F,0xFC,0x10,0x04},/*"H",40*/{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x00,0x00,0x00,0x00},/*"I",41*/{0x00,0x03,0x00,0x01,0x10,0x01,0x10,0x01,0x1F,0xFE,0x10,0x00,0x10,0x00,0x00,0x00},/*"J",42*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x03,0x80,0x14,0x64,0x18,0x1C,0x10,0x04,0x00,0x00},/*"K",43*/{0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x0C,0x00,0x00},/*"L",44*/{0x10,0x04,0x1F,0xFC,0x1F,0x00,0x00,0xFC,0x1F,0x00,0x1F,0xFC,0x10,0x04,0x00,0x00},/*"M",45*/{0x10,0x04,0x1F,0xFC,0x0C,0x04,0x03,0x00,0x00,0xE0,0x10,0x18,0x1F,0xFC,0x10,0x00},/*"N",46*/{0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"O",47*/{0x10,0x04,0x1F,0xFC,0x10,0x84,0x10,0x80,0x10,0x80,0x10,0x80,0x0F,0x00,0x00,0x00},/*"P",48*/{0x07,0xF0,0x08,0x18,0x10,0x24,0x10,0x24,0x10,0x1C,0x08,0x0A,0x07,0xF2,0x00,0x00},/*"Q",49*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x11,0xC0,0x11,0x30,0x0E,0x0C,0x00,0x04},/*"R",50*/{0x00,0x00,0x0E,0x1C,0x11,0x04,0x10,0x84,0x10,0x84,0x10,0x44,0x1C,0x38,0x00,0x00},/*"S",51*/{0x18,0x00,0x10,0x00,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x00,0x18,0x00,0x00,0x00},/*"T",52*/{0x10,0x00,0x1F,0xF8,0x10,0x04,0x00,0x04,0x00,0x04,0x10,0x04,0x1F,0xF8,0x10,0x00},/*"U",53*/{0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",54*/{0x1F,0xC0,0x10,0x3C,0x00,0xE0,0x1F,0x00,0x00,0xE0,0x10,0x3C,0x1F,0xC0,0x00,0x00},/*"W",55*/{0x10,0x04,0x18,0x0C,0x16,0x34,0x01,0xC0,0x01,0xC0,0x16,0x34,0x18,0x0C,0x10,0x04},/*"X",56*/{0x10,0x00,0x1C,0x00,0x13,0x04,0x00,0xFC,0x13,0x04,0x1C,0x00,0x10,0x00,0x00,0x00},/*"Y",57*/{0x08,0x04,0x10,0x1C,0x10,0x64,0x10,0x84,0x13,0x04,0x1C,0x04,0x10,0x18,0x00,0x00},/*"Z",58*/{0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x40,0x02,0x40,0x02,0x40,0x02,0x00,0x00},/*"[",59*/{0x00,0x00,0x30,0x00,0x0C,0x00,0x03,0x80,0x00,0x60,0x00,0x1C,0x00,0x03,0x00,0x00},/*"\",60*/{0x00,0x00,0x40,0x02,0x40,0x02,0x40,0x02,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00},/*"]",61*/{0x00,0x00,0x00,0x00,0x20,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00},/*"^",62*/{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01},/*"_",63*/{0x00,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/{0x00,0x00,0x00,0x98,0x01,0x24,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xFC,0x00,0x04},/*"a",65*/{0x10,0x00,0x1F,0xFC,0x00,0x88,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"b",66*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x00},/*"c",67*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x11,0x08,0x1F,0xFC,0x00,0x04},/*"d",68*/{0x00,0x00,0x00,0xF8,0x01,0x44,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xC8,0x00,0x00},/*"e",69*/{0x00,0x00,0x01,0x04,0x01,0x04,0x0F,0xFC,0x11,0x04,0x11,0x04,0x11,0x00,0x18,0x00},/*"f",70*/{0x00,0x00,0x00,0xD6,0x01,0x29,0x01,0x29,0x01,0x29,0x01,0xC9,0x01,0x06,0x00,0x00},/*"g",71*/{0x10,0x04,0x1F,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"h",72*/{0x00,0x00,0x01,0x04,0x19,0x04,0x19,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"i",73*/{0x00,0x00,0x00,0x03,0x00,0x01,0x01,0x01,0x19,0x01,0x19,0xFE,0x00,0x00,0x00,0x00},/*"j",74*/{0x10,0x04,0x1F,0xFC,0x00,0x24,0x00,0x40,0x01,0xB4,0x01,0x0C,0x01,0x04,0x00,0x00},/*"k",75*/{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"l",76*/{0x01,0x04,0x01,0xFC,0x01,0x04,0x01,0x00,0x01,0xFC,0x01,0x04,0x01,0x00,0x00,0xFC},/*"m",77*/{0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"n",78*/{0x00,0x00,0x00,0xF8,0x01,0x04,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0xF8,0x00,0x00},/*"o",79*/{0x01,0x01,0x01,0xFF,0x00,0x85,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"p",80*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x05,0x01,0xFF,0x00,0x01},/*"q",81*/{0x01,0x04,0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x04,0x01,0x00,0x01,0x80,0x00,0x00},/*"r",82*/{0x00,0x00,0x00,0xCC,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x98,0x00,0x00},/*"s",83*/{0x00,0x00,0x01,0x00,0x01,0x00,0x07,0xF8,0x01,0x04,0x01,0x04,0x00,0x00,0x00,0x00},/*"t",84*/{0x01,0x00,0x01,0xF8,0x00,0x04,0x00,0x04,0x00,0x04,0x01,0x08,0x01,0xFC,0x00,0x04},/*"u",85*/{0x01,0x00,0x01,0x80,0x01,0x70,0x00,0x0C,0x00,0x10,0x01,0x60,0x01,0x80,0x01,0x00},/*"v",86*/{0x01,0xF0,0x01,0x0C,0x00,0x30,0x01,0xC0,0x00,0x30,0x01,0x0C,0x01,0xF0,0x01,0x00},/*"w",87*/{0x00,0x00,0x01,0x04,0x01,0x8C,0x00,0x74,0x01,0x70,0x01,0x8C,0x01,0x04,0x00,0x00},/*"x",88*/{0x01,0x01,0x01,0x81,0x01,0x71,0x00,0x0E,0x00,0x18,0x01,0x60,0x01,0x80,0x01,0x00},/*"y",89*/{0x00,0x00,0x01,0x84,0x01,0x0C,0x01,0x34,0x01,0x44,0x01,0x84,0x01,0x0C,0x00,0x00},/*"z",90*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x3E,0xFC,0x40,0x02,0x40,0x02},/*"{",91*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/{0x00,0x00,0x40,0x02,0x40,0x02,0x3E,0xFC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"}",93*/{0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/*"~",94*/};   显示一个字符:void LcdShowChar(u16 x,u16 y,u8 num,u8 size){                                                           u8 temp,t1,t;        u16 y0=y;        u8 csize=size/8+((size%8)?1:0))*(size/2);             //得到字体一个字符对应点阵集所占的字节数           num=num-' ';   //得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)        for(t=0;t<csize;t++)        {                    if(size==12)temp=asc_1206[num][t];            //调用1206字体                 else return;                 for(t1=0;t1<8;t1++)                 {                                                    if(temp&0x80)LcdDrawPoint(x,y,0x0);                         else LcdDrawPoint(x,y,0xFFFF);                         temp<<=1;                         y++;                         if((y-y0)==size)                         {                                  y=y0;                                  x++;                                  break;                         }                 }             }                              } 
  • [交流分享] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
  • [技术干货] 基于STM32+华为云IOT设计智能称重系统
    伴随着网络技术,各种通讯技术,传感器技术的飞速发展,物联网技术成为了当今技术领域发展为迅速的技术。而物联网技术的核心仍然是以互联网技术为基础的,物联网是新一代信息技术的重要组成部分,也是信息化时代的重要发展阶段。物联网通过智能感知、识别技术与普适计算等通信感知技术,广泛应用于网络的融合中,也因此被称为继计算机、互联网之后世界信息产业发展的第三次浪潮。 本设计的模型来源于物流、矿山、高速公路等场合,车辆称重地螃的智能化升级要求,设计基于物联网的智能在线称重方案,开发智能称重控制器,合理选择部署多个重量传感器和必要的算法、通过WIFF通信模块、GPS定位模块,采集车辆重数据一地理位置信息,并通过网络发送至云平台,设计图形化UI界面展示称重、地图位置等重要信息,实现对称重系统的远程监测。 随着物联网技术的逐步发展和日趋成熟,物联网技术是一个大而广的应用技术,并非仅仅局限于延伸应用。相信对地磅来说必然会有更多创新的应用实践。总的来说,地磅现代化、信息化、智能化一定紧随物联网技术的发展,而物联网技术的发展也必将促使地磅兴起新的技术革命。 **设计的技术与硬件选项总结:** (1)云端通信模块采用ESP8266-WIFI (2)联网通信模块采用:ESP8266 (3)GPS模块:采用ATGM336H双模GPS模块 (4)电子秤模块:用于称重 (5)物联网云平台:采用华为云物联网平台 **设计总结:** (1)采用ESP8266连接OneNet上传称重数据和GPS数据到云端(采用HTTP协议) 云端上显示2个数据:GPS定位数据--地图显示,称重传感器的数据值 (2)3个称重传感器接一个秤面称重计算平均值 (3)本地OLED显示屏显示GPS经纬度数据、称重传感器的数据值。 (4)OLED设计一个页面显示并设置当前的报警上限。通过按键进行加减 当称重的阀值超出了设置阀值,蜂鸣器报警。 ![image-20220423172247041.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020128427486656.png) ![image-20220423172310175.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020135683368177.png) ![image-20220423172332897.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020141844889788.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652022329353186292.png) ## 2. 硬件选型 ### 2.1 STM32F103C8T6 STM32F103C8T6是一款基于ARM Cortex-M 内核STM32系列的32位的微控制器,程序存储器容量是64KB,需要电压2V~3.6V,工作温度为-40°C ~ 85°C。 ![image-20220420111635569.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020229092711056.png) ### 2.2 电子秤传感器 ![image-20220421100325942.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020255478493467.png) HX711 是一款专为高精度称重传感器而设计的24位A/D 转换器芯片。 ### 2.3 ESP8266-wifi ![image-20220420112202167.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020297602604338.png) ### 2.4 GPS模块 ![image-20220420131853905.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020324373448974.png) ### 2.5 蜂鸣器 ![image-20220415203419349.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020347821373925.png) ## 3. 创建云端产品与设备 ### 3.1 创建产品 地址:https://www.huaweicloud.com/?locale=zh-cn ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220507/1651905286055616975.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220507/1651905310302336085.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021001536388848.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021018047130415.png) ### 3.2 创建设备 地址: https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/device/all-device ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021132507517846.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021145835569023.png) ### 3.3 自定义模型数据 链接:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-dev/all-product/7211833377cf435c8c0580de390eedbe/product-detail/6277d70223aaf461a0f72a56 这个模型数据就是设备要上传的数据。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021221965736093.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021248776991904.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021285369409031.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021310305104272.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021321134502897.png) ```cpp { "device_id": "6277d70223aaf461a0f72a56_weigh", "secret": "12345678" } 服务ID: weigh 属性名称 数据类型 访问方式 描述 weigh int(整型) 可读 重量 GPS string(字符串) 可读 GPS定位信息 ``` ### 3.4 MQTT密匙生成 创建完产品、设备之后,接下来就需要知道如何通过MQTT协议登陆华为云服务器。 官方的详细介绍在这里: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112 属性上报格式: https://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651816169416838509.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651816116025678829.png) MQTT设备登陆密匙生成地址: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/ ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220507/1651905876017870520.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021454982918234.png) ```cpp DeviceId 6277d70223aaf461a0f72a56_weigh DeviceSecret 12345678 ClientId 6277d70223aaf461a0f72a56_weigh_0_0_2022050814 Username 6277d70223aaf461a0f72a56_weigh Password 0a3d097c6449b8526a562006a74c8c5e61ce63d6c831ea291560736a3332cf77 ``` 华为云物联网平台的域名是: `161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com` 华为云物联网平台的IP地址是:`121.36.42.100` 在软件里参数填充正确之后,就看到设备已经连接成功了。 接下来打开设备页面,可以看到设备已经在线了。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021565800517640.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021599682219816.png) ### 3.5 主题订阅与发布 ```cpp //订阅主题: 平台下发消息给设备 $oc/devices/6277d70223aaf461a0f72a56_weigh/sys/messages/down //设备上报数据 $oc/devices/6277d70223aaf461a0f72a56_weigh/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "weigh","properties":{"GPS":"lat:12.345,lng:45.678"}}]} ``` 通过MQTT客户端软件模拟上报测试: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021698659941021.png) 查看控制台页面,数据已经上传成功了。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652021719767713266.png) ### 3.6 应用侧开发 为了更方便的展示设备数据,与设备完成交互,还需要开发一个配套的上位机,官方提供了应用侧开发的API接口、SDK接口,为了方便通用一点,我这里采用了API接口完成数据交互,上位机软件采用QT开发。 帮助文档地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html 设备属性就是设备上传的传感器状态数据信息,应用侧提供了API接口,可以主动向设备端下发请求指令;设备端收到指令之后需要按照约定的数据格式上报数据;所以,要实现应用层与设备端的数据交互,需要应用层与设备端配合才能完成。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817418902323890.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817464627894213.png) 在使用接口时,最好先使用华为自己的调试接口测试。 https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ListProperties ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652022024953561550.png) 上位机软件采用Qt框架设计,Qt是一个跨平台的C++图形用户界面应用程序框架。Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单来说,QT可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。 **QT官网:** https://www.qt.io/ ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815486180496130.png) ## 4. STM32设备端开发 ### 4.1 程序下载 ![image-20220423172434570.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020552999979066.png) ### 4.2 原理图 ![image-20220423172622099.png](https://bbs-img.huaweicloud.com/blogs/img/20220508/1652020573736651598.png) ### 4.3 硬件接线 ```cpp (1)OLED显示屏接线: D0----(SCK)------------------->>PB14 D1----(MOSI)------------------>>PB13 RES—(复位脚低电平有效)-------->>PB12 DC--(数据和命令控制管脚)------>>PB1 CS--(片选引脚)---------------->>PA7 GND--------------------------->>GND VCC--------------------------->>3.3V或者5V (2)ATK-ESP8266 WIFI接线 PA2(TX)--RXD 模块接收脚 PA3(RX)--TXD 模块发送脚 GND---GND 地 VCC---VCC 电源(3.3V~5.0V) (3)外接蜂鸣器模块: 高电平响 BEEP----->PB8 (4)外接按键: KEY1 -PB3 按下是低电平 清零 KEY2 -PB2 按下是低电平 翻页 KEY3 -PB6 按下是低电平 加 KEY4 -PB7 按下是低电平 减 (5)外接LED灯模块: LED1-PB4 低电平亮 LED2-PB5 低电平亮 (6)称重传感器1 VCC--->5V SCK--->PA4 时序控制脚--对STM32--输出模式 DT---->PA5 输出输出脚-对STM32--输入模式 GND--->GND (7)称重传感器2 VCC--->5V SCK--->PA11 时序控制脚--对STM32--输出模式 DT---->PA12 输出输出脚-对STM32--输入模式 GND--->GND (8)称重传感器3 VCC--->5V SCK--->PA6 时序控制脚--对STM32--输出模式 DT---->PA8 输出输出脚-对STM32--输入模式 GND--->GND (9)GPS模块接线说明 GND----GND VCC---3.3V PB11----GPS_TX PB10----GPS_RX (--)板载LED灯:低电平亮 LED1--PC13 BEEP2--PC14 (--)板载按键: KEY1--PA0 按下为高电平 ``` ### 4.4 MQTT连接代码 ```cpp #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include #include "timer.h" #include "bluetooth.h" #include "esp8266.h" #include "mqtt.h" //华为物联网服务器的设备信息 #define MQTT_ClientID "61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510" #define MQTT_UserName "61b9ba3a2b2aa20288c1e7f1_QQ1126626497" #define MQTT_PassWord "385ce91dfe7da5b7431868d5d87e7998163c493344040935d5a00024d6324242" //订阅与发布的主题 #define SET_TOPIC "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/properties/report" //发布 char mqtt_message[200];//上报数据缓存区 int main() { u32 time_cnt=0; u32 i; u8 key; LED_Init(); BEEP_Init(); KEY_Init(); USART1_Init(115200); TIMER1_Init(72,20000); //超时时间20ms USART2_Init(9600);//串口-蓝牙 TIMER2_Init(72,20000); //超时时间20ms USART3_Init(115200);//串口-WIFI TIMER3_Init(72,20000); //超时时间20ms USART1_Printf("正在初始化WIFI请稍等.\n"); if(ESP8266_Init()) { USART1_Printf("ESP8266硬件检测错误.\n"); } else { //非加密端口 USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("CMCC-Cqvn","99pu58cb","121.36.42.100",1883,1)); } //2. MQTT协议初始化 MQTT_Init(); //3. 连接华为服务器 while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)) { USART1_Printf("服务器连接失败,正在重试...\n"); delay_ms(500); } USART1_Printf("服务器连接成功.\n"); //3. 订阅主题 if(MQTT_SubscribeTopic(SET_TOPIC,0,1)) { USART1_Printf("主题订阅失败.\n"); } else { USART1_Printf("主题订阅成功.\n"); } ......... ``` ### 4.5 ESP8266代码 ```cpp #include "esp8266.h" u8 ESP8266_IP_ADDR[16]; //255.255.255.255 u8 ESP8266_MAC_ADDR[18]; //硬件地址 /* 函数功能: ESP8266命令发送函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_SendCmd(char *cmd) { u8 i,j; for(i=0;i10;i++) //检测的次数--发送指令的次数 { USARTx_StringSend(USART3,cmd); for(j=0;j100;j++) //等待的时间 { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"OK")) { return 0; } } } } return 1; } /* 函数功能: ESP8266硬件初始化检测函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_Init(void) { //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(50); return ESP8266_SendCmd("AT\r\n"); } /* 函数功能: 一键配置WIFI为AP+TCP服务器模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) u16 port 创建的服务器端口号 函数返回值: 0表示成功 其他值表示对应错误值 */ u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port) { char *p; u8 i; char ESP8266_SendCMD[100]; //组合发送过程中的命令 /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=2\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 设置WIFI的AP模式参数*/ sprintf(ESP8266_SendCMD,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 开启多连接*/ if(ESP8266_SendCmd("AT+CIPMUX=1\r\n"))return 7; /*8. 设置服务器端口号*/ sprintf(ESP8266_SendCMD,"AT+CIPSERVER=1,%d\r\n",port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 查询本地IP地址*/ if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 9; //提取IP地址 p=strstr((char*)USART3_RX_BUFFER,"APIP"); if(p) { p+=6; for(i=0;*p!='"';i++) { ESP8266_IP_ADDR[i]=*p++; } ESP8266_IP_ADDR[i]='\0'; } //提取MAC地址 p=strstr((char*)USART3_RX_BUFFER,"APMAC"); if(p) { p+=7; for(i=0;*p!='"';i++) { ESP8266_MAC_ADDR[i]=*p++; } ESP8266_MAC_ADDR[i]='\0'; } //打印总体信息 USART1_Printf("当前WIFI模式:AP+TCP服务器\n"); USART1_Printf("当前WIFI热点名称:%s\n",ssid); USART1_Printf("当前WIFI热点密码:%s\n",pass); USART1_Printf("当前TCP服务器端口号:%d\n",port); USART1_Printf("当前TCP服务器IP地址:%s\n",ESP8266_IP_ADDR); USART1_Printf("当前TCP服务器MAC地址:%s\n",ESP8266_MAC_ADDR); return 0; } /* 函数功能: TCP服务器模式下的发送函数 发送指令: */ u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len) { u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d,%d\r\n",id,len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"SEND OK")) { return 0; } } } } } } } return 1; } /* 函数功能: 配置WIFI为STA模式+TCP客户端模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) char *p 将要连接的服务器IP地址 u16 port 将要连接的服务器端口号 u8 flag 1表示开启透传模式 0表示关闭透传模式 函数返回值:0表示成功 其他值表示对应的错误 */ u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag) { char ESP8266_SendCMD[100]; //组合发送过程中的命令 //退出透传模式 //USARTx_StringSend(USART3,"+++"); //delay_ms(50); /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 配置将要连接的WIFI热点信息*/ sprintf(ESP8266_SendCMD,"AT+CWJAP=\"%s\",\"%s\"\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 设置单连接*/ if(ESP8266_SendCmd("AT+CIPMUX=0\r\n"))return 7; /*8. 配置要连接的TCP服务器信息*/ sprintf(ESP8266_SendCMD,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ip,port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 开启透传模式*/ if(flag) { if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 9; //开启 if(ESP8266_SendCmd("AT+CIPSEND\r\n"))return 10; //开始透传 if(!(strstr((char*)USART3_RX_BUFFER,">"))) { return 11; } //如果想要退出发送: "+++" } //打印总体信息 USART1_Printf("当前WIFI模式:STA+TCP客户端\n"); USART1_Printf("当前连接的WIFI热点名称:%s\n",ssid); USART1_Printf("当前连接的WIFI热点密码:%s\n",pass); USART1_Printf("当前连接的TCP服务器端口号:%d\n",port); USART1_Printf("当前连接的TCP服务器IP地址:%s\n",ip); return 0; } /* 函数功能: TCP客户端模式下的发送函数 发送指令: */ u8 ESP8266_ClientSendData(u8 *data,u16 len) { u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d\r\n",len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"SEND OK")) { return 0; } } } } } } } return 1; } ```
  • [技术干货] 基于STM32+华为云IOT设计的智能防盗单车锁
    ## 一、前言 近年来随着国民经济的发展,交通拥堵和环境污染问题越来越突出,而自行车对改善交通与环境起到了重要作用。中国本身是一个自行车使用大国,随着自行车的发展,自行车的科技含量越来越高,然而自行车安防问题突出。目前市场上自行车锁大多是传统机械结构车锁,没有实现智能化,急需解决。本文提出一种基于STM32单片机的智能自行车锁(马蹄锁)的设计方法,来提高自行车锁的智能化及安防等级。 硬件选项说明:单片机采用STM32F103RCT6,GSM模块采用SIM800C,完成网络连接、数据上传,GPS经纬度解析,短信发送,物联网平台采用华为云IOT,作为数据存储端,蓝牙模块采用正点原子低功耗BLE蓝牙,支持蓝牙开锁解锁,车辆的状态使用ADXL345三轴加速度传感器检测,密码键盘采用电容矩阵键盘。 ![image-20220506111652277.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815189851243230.png) ![image-20220506111846674.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815206575210903.png) ![image-20220506112053867.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815213170238564.png) ![image-20220506112117540.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815218614447139.png) ![image-20220506114446223.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815230920891084.png) ## 二、设计思路总结 需要设计一款Android手机APP,可以远程开锁解锁,手机APP对接华为云物联网平台,实现远程与自行车锁完成数据交互,命令下发。智能锁与华为云IOT服务器之间的通信协议采用MQTT协议,手机APP与华为云IOT服务器之间采用HTTP协议。智能锁除了支持远程开锁关锁之外,还支持蓝牙解锁和输入密码开始,设计的APP支持蓝牙功能,可以连接智能锁上的蓝牙完成开锁和关锁,如果没有带手机,可以输入密码完成开锁。 车辆的状态检测通过ADXL345三轴加速度计检测,如果车辆处于锁定状态,发现车辆被移动了会触发报警,锁里的蜂鸣器会持续响,并且SIM800C会向指定的手机号码发送短信,提示车辆可能被盗,同时上传GPS经纬度到云端服务器,手机APP上可以获取智能锁上传的GPS经纬度,调用百度地图显示车辆的位置,方便寻车。 ## 三、硬件选型 ### (1) 加速度计传感器 ADXL345是一款小尺寸、薄型、低功耗、完整的三轴加速度计,提供经过信号调理的电压输出。 说明:CS接高电平则选择IIC通信,反之则SPI通信。SDO(地址引脚)接高电平,根据手册器件的7位I2C地址是0x1D,后面跟上读取/写位(R/W),则写寄存器为0x3A,读寄存器为0x3B;接低电平,则7位I2C地址是0x53,同理,跟上读写标志位后写寄存器为0xA6,读寄存器为0xA7; ![image-20220430220306841.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815333299849118.png) ### (2) STM32开发板 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815437136353907.png) STM32F103RCT6的芯体规格是32位,速度是72MHz,程序存储器容量是256KB,程序存储器类型是FLASH,RAM容量是48K。 ### (3) BLE低功耗蓝牙模块 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815448456540319.png) ### (4) SIM800C ![image-20220430220908634.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815351317425637.png) **模块特点:** 1、支持极限DC5V-18V宽电压输入 2、有电源使能开关引脚EN 3、支持锂电池供电接口VBAT3.5-4.5V 4、输入支持移动和联通手机卡Micro SIM卡 5、送51/STM32/ARDUINO驱动例程 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815521722932876.png) **1、DC 5V-18V电源输入,推荐使用DC 9V** 2、电源开始使能引脚默认使能 **3、电源地** **4、GSM模块的TXD引脚接其它模块的RXD** **5、GSM模块的RXD引脚接其它模块的TXD** 6、数据终端准备 7、内核音频输出引脚 8、内核音频输出引脚 9、锂电池输入引脚,DC 3.5 - 4.5V 10、电源地 **11、启动引脚和GND短路可实现开机自启动** **12、电源地** 13、RTC外置电池引脚 14、内核振铃提示引脚 15、内合音频输入引脚 16、内核音频输入引脚 **加粗的引脚一般都用到。** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815536446176839.png) 建议使用V_IN单独供电DC5-18V输入(推荐使用9V),或者VBAT供电锂电池两种供电方式这两种供电方式最稳定。如果只是简单调试,也可使用USB-TTL或者开发板的5V直接给模块供电。不过一般电脑或者开发板的功率有限,可能会不稳定。请根据具体情况自己取舍选择合适电源。 ## 3. 手机APP软件设计 ### 3.1 通信说明 上位机与设备之间支持通过BLE低功耗串口蓝牙进行通信,支持通过网络连接华为云服务器进行通信,手机APP下发open_lock和close_lock实现关锁开锁。 ### 3.2 搭建开发环境 上位机软件采用Qt框架设计,Qt是一个跨平台的C++图形用户界面应用程序框架。Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单来说,QT可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。 **QT官网:** https://www.qt.io/ ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815486180496130.png) QT学习入门实战专栏文章: https://blog.csdn.net/xiaolong1126626497/category_11400392.html QT5.12.6的下载地址: https://download.qt.io/archive/qt/5.12/5.12.6/ ## 4. 创建云端设备 ### 4.1 创建产品 登录官网: https://www.huaweicloud.com/product/iothub.html 直接搜索物联网,打开页面。 ![image-20220506132534421.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815574985423712.png) ![image-20220506132613820.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815581280850715.png) ![image-20220506132738694.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815586586619046.png) ![image-20220506132756569.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815591742247358.png) ### 4.2 自定义模型 ![image-20220506132829473.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815596895420897.png) ![image-20220506132903658.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815614460754948.png) ![image-20220506132939945.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815630981652822.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815643051290333.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815712178860623.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815770356615200.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815780985260977.png) ### 4.3 注册设备 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815832374544881.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815871840221837.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651815884155645877.png) 设备创建成功: ```cpp { "device_id": "6274b1d62d5e854503d3a67e_lock", "secret": "12345678" } ``` ### 4.4 MQTT设备密匙 创建完产品、设备之后,接下来就需要知道如何通过MQTT协议登陆华为云服务器。 官方的详细介绍在这里: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112 属性上报格式: https://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651816169416838509.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651816116025678829.png) MQTT设备登陆密匙生成地址: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651816792620239054.png) ```cpp DeviceId 6274b1d62d5e854503d3a67e_lock DeviceSecret 12345678 ClientId 6274b1d62d5e854503d3a67e_lock_0_0_2022050605 Username 6274b1d62d5e854503d3a67e_lock Password 334dd7c0c10e47280880e9dd004ae0d8c5abc24dbbc9daa735315722707fe13b ``` ### 4.5 使用MQTT客户端软件登录 所有的参数已经得到,接下来采用MQTT客户端登录华为云进行测试。 华为云物联网平台的域名是: `161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com` 华为云物联网平台的IP地址是:`121.36.42.100` 在软件里参数填充正确之后,就看到设备已经连接成功了。 接下来打开设备页面,可以看到设备已经在线了。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817042634504679.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817061024360231.png) ### 4.6 数据上报测试 ```cpp //订阅主题: 平台下发消息给设备 $oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down //设备上报数据 $oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "lock","properties":{"lock":1}}]} ``` ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817197214584353.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817214615621746.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817296504914445.png) ```cpp //订阅主题: 平台下发消息给设备 $oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down //设备上报数据 $oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "lock","properties":{"GPS信息":"lat:12.345,lng:45.678"}}]} ``` ### 4.7 应用侧开发 为了更方便的展示设备数据,与设备完成交互,还需要开发一个配套的上位机,官方提供了应用侧开发的API接口、SDK接口,为了方便通用一点,我这里采用了API接口完成数据交互,上位机软件采用QT开发。 帮助文档地址: ttps://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html 设备属性就是设备上传的传感器状态数据信息,应用侧提供了API接口,可以主动向设备端下发请求指令;设备端收到指令之后需要按照约定的数据格式上报数据;所以,要实现应用层与设备端的数据交互,需要应用层与设备端配合才能完成。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817418902323890.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220506/1651817464627894213.png) ## 5. STM32开发 ### 5.1 ADXL345.c ``` cpp #include "app.h" /* 函数功能: 各种硬初始化 继电器模块--DAT--->PA4 PB12-----输入引脚,检测模块是否连接或者断开 */ void Hardware_Init(void) { RCC->APB2ENR|=12; GPIOA->CRL&=0xFFF0FFFF; GPIOA->CRL|=0x00030000; RCC->APB2ENR|=13; GPIOB->CRH&=0xFFF0FFFF; GPIOB->CRH|=0x00080000; } ////////////////////////////////////////////////////////////////////////////////// //初始化ADXL345. //返回值:0,初始化成功;1,初始化失败. u8 ADXL345_Init(void) { IIC_Init(); //初始化IIC总线 if(ADXL345_RD_Reg(DEVICE_ID)==0XE5) //读取器件ID { ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); return 0; } return 1; } //写ADXL345寄存器 //addr:寄存器地址 //val:要写入的值 //返回值:无 void ADXL345_WR_Reg(u8 addr,u8 val) { IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 IIC_Wait_Ack(); IIC_Send_Byte(val); //发送值 IIC_Wait_Ack(); IIC_Stop(); //产生一个停止条件 } //读ADXL345寄存器 //addr:寄存器地址 //返回值:读到的值 u8 ADXL345_RD_Reg(u8 addr) { u8 temp=0; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 temp=IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 temp=IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 temp=IIC_Wait_Ack(); temp=IIC_Read_Byte(0); //读取一个字节,不继续再读,发送NAK IIC_Stop(); //产生一个停止条件 return temp; //返回读到的值 } //读取ADXL的平均值 //x,y,z:读取10次后取平均值 void ADXL345_RD_Avval(short *x,short *y,short *z) { short tx=0,ty=0,tz=0; u8 i; for(i=0;i10;i++) { ADXL345_RD_XYZ(x,y,z); delay_ms(10); tx+=(short)*x; ty+=(short)*y; tz+=(short)*z; } *x=tx/10; *y=ty/10; *z=tz/10; } //自动校准 //xval,yval,zval:x,y,z轴的校准值 void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval) { short tx,ty,tz; u8 i; short offx=0,offy=0,offz=0; ADXL345_WR_Reg(POWER_CTL,0x00); //先进入休眠模式. delay_ms(100); ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); delay_ms(12); for(i=0;i10;i++) { ADXL345_RD_Avval(&tx,&ty,&tz); offx+=tx; offy+=ty; offz+=tz; } offx/=10; offy/=10; offz/=10; *xval=-offx/4; *yval=-offy/4; *zval=-(offz-256)/4; ADXL345_WR_Reg(OFSX,*xval); ADXL345_WR_Reg(OFSY,*yval); ADXL345_WR_Reg(OFSZ,*zval); } //读取3个轴的数据 //x,y,z:读取到的数据 void ADXL345_RD_XYZ(short *x,short *y,short *z) { u8 buf[6]; u8 i; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(0x32); //发送寄存器地址(数据缓存的起始地址为0X32) IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 IIC_Wait_Ack(); for(i=0;i6;i++) { if(i==5)buf[i]=IIC_Read_Byte(0);//读取一个字节,不继续再读,发送NACK else buf[i]=IIC_Read_Byte(1); //读取一个字节,继续读,发送ACK } IIC_Stop(); //产生一个停止条件 *x=(short)(((u16)buf[1]8)+buf[0]); *y=(short)(((u16)buf[3]8)+buf[2]); *z=(short)(((u16)buf[5]8)+buf[4]); } //读取ADXL345的数据times次,再取平均 //x,y,z:读到的数据 //times:读取多少次 void ADXL345_Read_Average(short *x,short *y,short *z,u8 times) { u8 i; short tx,ty,tz; *x=0; *y=0; *z=0; if(times)//读取次数不为0 { for(i=0;i/连续读取times次 { ADXL345_RD_XYZ(&tx,&ty,&tz); *x+=tx; *y+=ty; *z+=tz; delay_ms(5); } *x/=times; *y/=times; *z/=times; } } //得到角度 //x,y,z:x,y,z方向的重力加速度分量(不需要单位,直接数值即可) //dir:要获得的角度.0,与Z轴的角度;1,与X轴的角度;2,与Y轴的角度. //返回值:角度值.单位0.1°. short ADXL345_Get_Angle(float x,float y,float z,u8 dir) { float temp; float res=0; switch(dir) { case 0://与自然Z轴的角度 temp=sqrt((x*x+y*y))/z; res=atan(temp); break; case 1://与自然X轴的角度 temp=x/sqrt((y*y+z*z)); res=atan(temp); break; case 2://与自然Y轴的角度 temp=y/sqrt((x*x+z*z)); res=atan(temp); break; } return res*1800/3.14; } //初始化IIC void IIC_Init(void) { RCC->APB2ENR|=13; //先使能外设IO PORTB时钟 GPIOB->CRL&=0X00FFFFFF; //6/7 推挽输出 GPIOB->CRL|=0X33000000; GPIOB->ODR|=36; //6,7 输出高 } //产生IIC起始信号 void IIC_Start(void) { SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号 void IIC_Stop(void) { SDA_OUT();//sda线输出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号 delay_us(4); } //等待应答信号到来 //返回值:1,接收应答失败 // 0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; } //产生ACK应答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不产生ACK应答 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输 for(t=0;t8;t++) { IIC_SDA=(txd&0x80)>>7; txd=1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();//发送nACK else IIC_Ack(); //发送ACK return receive; } ``` ### 5.2 sim800.c ```cpp #include "sim800c.h" /* 函数功能:向SIM800C模块发送指令 函数参数: char *cmd 发送的命令 char *check_data 检测返回的数据 返回值: 0表示成功 1表示失败 */ u8 SIM800C_SendCmd(char *cmd,char *check_data) { u16 i,j; for(i=0;i5;i++) //测试的总次数 { USART2_RX_FLAG=0; USART2_RX_CNT=0; memset(USART2_RX_BUFFER,0,sizeof(USART2_RX_BUFFER)); USARTx_StringSend(USART2,cmd); //发送指令 for(j=0;j500;j++) //等待的时间(ms单位) { if(USART2_RX_FLAG) { USART2_RX_BUFFER[USART2_RX_CNT]='\0'; if(strstr((char*)USART2_RX_BUFFER,check_data)) { return 0; } else break; } delay_ms(20); //一次的时间 } } return 1; } /* 函数 功能:GSM模块初始化检测 函数返回值:1表示模块检测失败,0表示成功 */ u8 SIM800C_InitCheck(void) { if(SIM800C_SendCmd("AT\r\n","OK"))return 1; else printf("SIM800模块正常!\r\n"); if(SIM800C_SendCmd("ATE0\r\n","OK"))return 2; else printf("设置模块不回显成功!\r\n"); if(SIM800C_SendCmd("AT+CGMI\r\n","OK"))return 3; else printf("查询制造商名称成功!%s\r\n",USART2_RX_BUFFER); if(SIM800C_SendCmd("AT+CGMM\r\n","OK"))return 4; else printf("查询模块型号成功!%s\r\n",USART2_RX_BUFFER); DelayMs(1000); DelayMs(1000); if(SIM800C_SendCmd("AT+CNUM\r\n","+CNUM:"))return 5; else printf("获取本机号码成功!%s\r\n",USART2_RX_BUFFER); /* 返回格式如下: +CNUM: "","+8613086989413",145,7,4 OK */ return 0; } /* 函数 功能:GSM模块短信模式设置 函数返回值:0表示模块设置成功 */ u8 SIM800C_SetNoteTextMode(void) { if(SIM800C_SendCmd("AT+CSCS=\"GSM\"\r\n","OK"))return 1;// "GSM"字符集 else printf("短信GSM字符集设置成功!\r\n"); if(SIM800C_SendCmd("AT+CMGF=1\r\n","OK"))return 2; //文本模式 else printf("短信文本模式设置成功!\r\n"); return 0; } /* 函数功能:发送短信 函数参数: num:电话号码 text:短信内容 函数返回值:0表示发送成功 */ u8 SIM800C_SendNote(u8 *num,u8 *text,u16 len) { char data[50]; char send_buf[2]; sprintf(data,"AT+CMGS=\"%s\"\r\n",num); if(SIM800C_SendCmd(data,">"))return 1; //设置发送的手机号 USARTx_DataSend(USART2,text,len); //发送短信内容 send_buf[0] = 0x1a; send_buf[1] = '\0'; if(SIM800C_SendCmd(send_buf,"+CMGS"))return 2; //发送结束符号 return 0; } ``` ### 5.3 MQTT信息 ```cpp u8 *mqtt_rxbuf; u8 *mqtt_txbuf; u16 mqtt_rxlen; u16 mqtt_txlen; u8 _mqtt_txbuf[256];//发送数据缓存区 u8 _mqtt_rxbuf[256];//接收数据缓存区 typedef enum { //名字 值 报文流动方向 描述 M_RESERVED1 =0 , // 禁止 保留 M_CONNECT , // 客户端到服务端 客户端请求连接服务端 M_CONNACK , // 服务端到客户端 连接报文确认 M_PUBLISH , // 两个方向都允许 发布消息 M_PUBACK , // 两个方向都允许 QoS 1消息发布收到确认 M_PUBREC , // 两个方向都允许 发布收到(保证交付第一步) M_PUBREL , // 两个方向都允许 发布释放(保证交付第二步) M_PUBCOMP , // 两个方向都允许 QoS 2消息发布完成(保证交互第三步) M_SUBSCRIBE , // 客户端到服务端 客户端订阅请求 M_SUBACK , // 服务端到客户端 订阅请求报文确认 M_UNSUBSCRIBE , // 客户端到服务端 客户端取消订阅请求 M_UNSUBACK , // 服务端到客户端 取消订阅报文确认 M_PINGREQ , // 客户端到服务端 心跳请求 M_PINGRESP , // 服务端到客户端 心跳响应 M_DISCONNECT , // 客户端到服务端 客户端断开连接 M_RESERVED2 , // 禁止 保留 }_typdef_mqtt_message; //连接成功服务器回应 20 02 00 00 //客户端主动断开连接 e0 00 const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00}; const u8 parket_disconnet[] = {0xe0,0x00}; const u8 parket_heart[] = {0xc0,0x00}; const u8 parket_heart_reply[] = {0xc0,0x00}; const u8 parket_subAck[] = {0x90,0x03}; /* 函数功能: 登录服务器 函数返回值: 0表示成功 1表示失败 */ u8 MQTT_Connect(char *ClientID,char *Username,char *Password) { u8 i,j; int ClientIDLen = strlen(ClientID); int UsernameLen = strlen(Username); int PasswordLen = strlen(Password); int DataLen; mqtt_txlen=0; //可变报头+Payload 每个字段包含两个字节的长度标识 DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2); //固定报头 //控制报文类型 mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT //剩余长度(不包括固定头部) do { u8 encodedByte = DataLen % 128; DataLen = DataLen / 128; // if there are more data to encode, set the top bit of this byte if ( DataLen > 0 ) encodedByte = encodedByte | 128; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可变报头 //协议名 mqtt_txbuf[mqtt_txlen++] = 0; // Protocol Name Length MSB mqtt_txbuf[mqtt_txlen++] = 4; // Protocol Name Length LSB mqtt_txbuf[mqtt_txlen++] = 'M'; // ASCII Code for M mqtt_txbuf[mqtt_txlen++] = 'Q'; // ASCII Code for Q mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T //协议级别 mqtt_txbuf[mqtt_txlen++] = 4; // MQTT Protocol version = 4 对于 3.1.1 版协议,协议级别字段的值是 4(0x04) //连接标志 mqtt_txbuf[mqtt_txlen++] = 0xc2; // conn flags mqtt_txbuf[mqtt_txlen++] = 0; // Keep-alive Time Length MSB mqtt_txbuf[mqtt_txlen++] = 100; // Keep-alive Time Length LSB 100S心跳包 保活时间 mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen); mqtt_txlen += ClientIDLen; if(UsernameLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen); //username length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen); //username length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen); mqtt_txlen += UsernameLen; } if(PasswordLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen); //password length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen); //password length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen); mqtt_txlen += PasswordLen; } memset(mqtt_rxbuf,0,mqtt_rxlen); MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); for(j=0;j10;j++) { delay_ms(50); if(USART2_RX_FLAG) { memcpy((char *)mqtt_rxbuf,USART2_RX_BUFFER,USART2_RX_CNT); //memcpy for(i=0;i/CONNECT if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功 { return 0;//连接成功 } } return 1; } ```
  • [行业资讯] eMTC,你所不了解的基础
    eMTC,全称是 LTE enhanced MTO,是基于LTE演进的。为了更加适合物与物之间的通信,也为了更低的成本,对LTE协议进行了裁剪和优化。eMTC基于蜂窝网络进行部署,其用户设备通过支持1.4MHz的射频和基带带宽,可以直接接入现有的LTE网络。eMTC支持上下行最大1Mbps的峰值速率,可以支持丰富、创新的物联应用。eMTC的基本特性窄带LTE其中最主要的几个特性。第一,系统复杂性地大幅度降低,复杂程度及成本得到了极大的优化。第二,功耗极度降低,电池续航时间大幅度增强。第三,网络的覆盖能力大大加强。第四,网络覆盖的密度增强。eMTC具备LPWA基本的四大能力:一是广覆盖,在同样的频段下,eMTC比现有的网络增益15dB,极大地提升了LTE网络的深度覆盖能力;二是具备支撑海量连接的能力,eMTC一个扇区能够支持近10万个连接;三是更低功耗,eMTC终端模块的待机时间可长达10年;四是更低的模块成本,大规模的连接将会带来模组芯片成本的快速下降,eMTC芯片目标成本在1~2美金左右 。除此之外,eMTC还具有四大优势:一是速率高,eMTC支持上下行最大1Mbps的峰值速率,远远超过GPRS、 ZigBee等物联技术的速率,eMTC更高的速率可以支撑更丰富的物联应用,如低速视频、语音等;二是移动性,eMTC支持连接态的移动性,用户可以无缝切换保障用户体验;三是可定位,基于TDD的eMTC可以利用基站侧的PRS测量,在无须新增GPS芯片的情况下就可进行位置定位,低成本的定位技术更有利于eMTC在物流跟踪、货物跟踪等场景的普及:四是支持语音,eMTC从LTE协议演进而来,可以支持 VOLTE语音,未来可被广泛应用到可穿戴设备中。原理与关键技术1、物理层资源结构eMTC作为LTE一个特性, 基本沿用LTE设计,占原有LTE系统的6个PRB,其中一个RB占12个子载波 (子载波带宽15kHz, 间隔为15kHz) 。时域结构上eMTC帧结构与LTE一致;频域结构上,3GPP将系统带宽划分成若干NB (不重叠的6个PRB) ,eMTC UE的调度受NB限制,不能跨NB调度,不同eMTC UE可以共享一个NB的资源。2、信号与信道eMTC不重用LTE的PDCCH、PCFICH和PHICH下行信道, 新增MPDCCH信道, 用于发送eMTC UE的PDSCH和PUSCH信道的调度指示以及公共消息的指示, 比如寻呼、RAR响应、上行ACK反馈。eMTC重用LTE的下行数据信道PDSCH, 支持传输模式为TM1/2/6/9;eMTC重用LTE的下行导频信号RS;重用LTE的物理同步信号PSS/SSS, 其中PSS映射到时隙0和时隙10的最后一个OFDM符号, SSS映射到时隙0和时隙10的倒数第二个OFDM符号,均以5ms为周期重复发送;eMTC重用LTE的物理广播信道PBCH, 新增一套SIB消息, 包括SIB1-BR、SIB2、SIB3、SIB4、SIB5和SIB14共6条,MIB消息新增一个IE用于携带SIB1-BR的调度信息, 在每个系统帧的0#子帧和9#子帧发送, 周期为40ms。eMTC的PRACH和LTE的PRACH分开 (使用相同频率, 时域上区分) , 可以采用时分, 频分, 码分方式;eMTC的PUCCH和LTE的PUCCH分开, eMTC的PUCCH支持跨子帧跳频, 不支持子帧内跳频;eMTC使用LTE传统的PUSCH信道上传数据资源, 其PUSCH资源受NB限制。3、资源共享与调度eMTC作为小区特性, 与LTE共小区部署, 不占用独立小区, 但是需要占用空口的RB资源和基带的处理资源, 为保证MBB业务优先, 系统会预留一定的资源给LTE, 即使LTE没有任何业务, eMTC也不能使用预留。通过配置参数EmtcDlRbTargetRatio和EmtcUlR bTargetRatio, 可以控制LTE和e MTC资源占用比例, 在LTE和eMTC负载均很高时, 依据两者目标利用率, 动态共享LTE的PRB资源, 如图3-case1;当eMTC负载较高, 而LTE有空闲RB资源时, 这些空闲RB资源可以给eMTC使用, 如图3-case2;因e MTC采用跨子帧调度和重复技术, 会长期占用RB资源, 为了避免LTE控制消息和VoIP等高优先级业务被长期阻塞, 通过DlLteRvsNbNum和UlLteRvsNbNum参数给LTE预留RB资源, 保证LTE业务的需求, 即LTE负载较而eMTC负载较低时, LTE可以占用全部带宽。4、峰值速率与LTE下行异步HARQ, 上行同步HARQ不同, eMTC上行下行都是异步HARQ。下行调度, 设MPDCCH重复的最后一个子帧编号n, 则MPDCCH调度的PDSCH起始子帧编号为n+2;设PDSCH重复的最后子帧编号为n, 则PUCCH 1 Ack/Nack子帧编号为n+4。上行调度, 设MPDCCH重复的最后一个子帧编号n, 则MPDCCH调度的PUSCH起始子帧编号为n+4;设PUSCH重复的最后子帧编号为n, 则MPDCCH Ack/Nack子帧编号为n+4。在无重复及重传的情况下,以ModeA对eMTC速率进行估算:下行调度周期为10ms, 全双工时可以下行连续MPDCCH和PDSCH调度, 10ms周期内能发送8个下行TB (传输块) , 每个TB最大1000bits,因此下行峰值速率为8*1000* (1000/10) =800kbps;同理下行半双工峰值速率为300kbps;上行行调度周期为8ms, 同理推算出全双工上行峰值速率1000kbps, 半双工上行峰值速率375kbps。5、功耗eMTC采用PSM和e DRX技术以节约功耗。PSM是一种新增的比Idle态更省电的省电模式, 由MME通过NAS配置给UE, UE发送完数据后在Idle态停留一段时间后进入深度睡眠态, 不监听任何空口消息, 只在主动发送数据和周期TAU时才退出PSM模式, 如图5 (a) ;eDRX通过延长Idle态或连接态的DRX周期, 减少UE侦听网络的信令处理, UE只在每个e DRX周期只在寻呼窗口内监听PDCCH, 其它时间处于睡眠状态, 从而达到UE节电的目的。应用场景eMTC是爱立信提出的无线物联网解决方案。eMTC基于LTE接入技术设计了无线物联网络的软特性,主要面向中低速率、低功耗、大连接、移动性强、具有定位需求的场景。eMTC无线物联网技术可支持语音、移动、定位业务,适合进行速率为100kbit/s~1Mbit/s范围内的中速小包数据或语音业务,模组市场价约10美元每块,典型应用为智能电梯、行车、物流跟踪、穿戴设备等。
  • [技术干货] 采用ENC28J60以太网模块移植UIP、LWIP协议完成上云过程
    一、前言目前联网物联网云平台的都是采用ESP8266、BC20、BC26、SIM800C 等这些WIFI、NBIOT、GSM模块居多,本篇文章利用ENC28J60移植协议栈栈完成,联网操作。二、ENC28J60要进行以太网通信需要用到ENC28J60 以太网控制器和 uIP 1.0 以太网协议栈。下面将介绍这两个部分。3.1  ENC28J60 简介 3.1.1 ENC28J60芯片介绍ENC28J60 是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网 控制器。它可作为任何配备有 SPI 的控制器的以太网接口。ENC28J60 符合 IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。 它还提供了一个内部 DMA 模块, 以实现快速数据吞吐和硬件支持的 IP 校验和计算。 与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达 10 Mb/s。两个专用的引脚用于连接 LED,进行网络活动状态指示。ENC28J60 总共只有 28 脚,提供 QFN/TF。  ENC28J60 的主要特点如下: 兼容3 协议的以太网控制器 集成 MAC 和 10 BASE-T 物理层 支持全双工和半双工模式 数据冲突时可编程自动重发 SPI 接口速度可达 10Mbps 8K 数据接收和发送双端口 RAM 提供快速数据移动的内部 DMA 控制器 可配置的接收和发送缓冲区大小 两个可编程 LED 输出 带7个中断源的两个中断引脚 TTL 电平输入 提供多种封装:SOIC/SSOP/SPDIP/QFN 等。ENC28J60 的典型应用电路如下图:ENC28J60 由七个主要功能模块组成: 1) SPI 接口,充当主控制器和 ENC28J60 之间通信通道。 2) 控制寄存器,用于控制和监视 ENC28J60。 3) 双端口 RAM 缓冲器,用于接收和发送数据包。 4) 判优器,当 DMA、发送和接收模块发出请求时对 RAM 缓冲器的访问进行控制。 5) 总线接口,对通过 SPI 接收的数据和命令进行解析。 6) MAC(Medium Access Control)模块,实现符合 IEEE 802.3 标准的 MAC 逻辑。 7) PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。  ENC28J60 还包括其他支持模块,诸如振荡器、片内稳压器、电平变换器(提供可以接受 5V 电压的 I/O 引脚)和系统控制逻辑。ENC28J60 的功能框图如下图所示:ENC28J60实物图: 3.1.2 ENC28J60以太网模块介绍ENC28J60 网络模块采用 ENC28J60 作为主芯片,单芯片即可实现以太网接入, 利用该模块,基本上只要是个单片机,就可以实现以太网连接。模块实物图如下: 模块的主要引脚功能:其中 GND 和 V3.3 用于给模块供电,MISO/MOSI/SCK 用于 SPI 通信,CS 是片选信号,INT 为中断输出引脚,RST 为模块复位信号。3.2 嵌入式以太网协议栈简介uIP是一个简单好用的嵌入式网络协议栈,易于移植且消耗的内存空间较少,非常适合学习和使用。可以肯定的说uIP是嵌入式以太网学习的好起点,但不一定是终点。uIP的功能远不如LwIP强大,但两者并没有孰优孰劣之分,uIP和LwIP的作者同为Adam Dunkels,LwIP开发较早uIP开发较晚,uIP经过这几年的发展从IPV4迁移到IPV6,最终可以适用于无线传感网络。总的来说,uIP是一个很好的起点,学好uIP可以迁移到LwIP,也可以迁移到uIPV6。3.3 UIP 简介uIP 由瑞典计算机科学学院(网络嵌入式系统小组)的Adam Dunkels 开发。其源代码由C 语 言编写,并完全公开,uIP 的最新版本是 1.0 版本。 uIP 协议栈去掉了完整的 TCP/IP 中不常用的功能,简化了通讯流程,但保留了网络通信 必须使用的协议,设计重点放在了 IP/TCP/ICMP/UDP/ARP 这些网络层和传输层协议上,保证 了其代码的通用性和结构的稳定性。官网:https://github.com/adamdunkels/uip 由于 uIP 协议栈专门为嵌入式系统而设计,因此还具有如下优越功能:1) 代码非常少,其协议栈代码不到 6K,很方便阅读和移植。 2) 占用的内存数非常少,RAM 占用仅几百字节。 3) 其硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据的拷贝,且发送 和接收都是依靠这个缓存区,极大的节省空间和时间。4) 支持多个主动连接和被动连接并发。 5) 其源代码中提供一套实例程序:web 服务器,web 客户端,电子邮件发送程序(SMTP 客 户端),Telnet 服务器, DNS 主机名解析程序等。通用性强,移植起来基本不用修改就可以通过。 6) 对数据的处理采用轮循机制,不需要操作系统的支持。 由于 uIP 对资源的需求少和移植容易,大部分的 8 位微控制器都使用过uIP 协议栈, 而且很多的著名的嵌入式产品和项目(如卫星,Cisco 路由器,无线传感器网络)中都在使用 uIP 协议栈。 uIP 相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序的通讯,对于 整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。uIP 协议栈与系统底层和高层应用之间的关系如图:从上图可以看出,uIP 协议栈主要提供 2 个函数供系统底层调用:uip_input 和 uip_periodic。 另外和应用程序联系主要是通过 UIP_APPCALL 函数。 当网卡驱动收到一个输入包时,将放入全局缓冲区uip_buf 中,包的大小由全局变量uip_len 约束。同时将调用 uip_input()函数,这个函数将会根据包首部的协议处理这个包和需要时调用 应用程序。当 uip_input()返回时,一个输出包同样放在全局缓冲区 uip_buf 里,大小赋给 uip_len。 如果 uip_len 是 0,则说明没有包要发送。否则调用底层系统的发包函数将包发送到网络上。 uIP 周期计时是用于驱动所有的 uIP 内部时钟事件。当周期计时激发,每一个 TCP 连接都 会调用 uIP 函数 uip_periodic()。类似于 uip_input()函数。uip_periodic()函数返回时,输出的 IP 包 要放到 uip_buf 中,供底层系统查询 uip_len 的大小发送。 由于使用 TCP/IP 的应用场景很多,因此应用程序作为单独的模块由用户实现。uIP 协议 栈提供一系列接口函数供用户程序调用,其中大部分函数是作为 C 的宏命令实现的,主要是为 了速度、代码大小、效率和堆栈的使用。用户需要将应用层入口程序作为接口提供给uIP 协议 栈, 并将这个函数定义为宏UIP_APPCALL()。这样,uIP 在接受到底层传来的数据包后,在需要送到上层应用程序处理的地方,调用 UIP_APPCALL( )。在不用修改协议栈的情况下可以适配不同的应用程序。 uIP 协议栈给我们提供了很多接口函数,这些函数在 uip.h 中定义,为了减少函数调用造成的额外支出,大部分接口函数以宏命令实现的。uIP 提供的接口函数有:1.初始化 uIP 协议栈:uip_init()2.处理输入包:uip_input() 3.处理周期计时事件:uip_periodic() 4.开始监听端口:uip_listen() 5.连接到远程主机:uip_connect() 6.接收到连接请求:uip_connected() 7.主动关闭连接:uip_close()8.连接被关闭:uip_closed() 9.发出去的数据被应答:uip_acked() 10.在当前连接发送数据:uip_send() 11.在当前连接上收到新的数据:uip_newdata() 12.告诉对方要停止连接:uip_stop()13.连接被意外终止:uip_aborted()3.4 UIP移植说明Uip源码分布如图:移植第一步:实现在 unix/tapdev.c 里面的三个函数。首先是 tapdev_init 函数,该函数用于 初始化网卡(也就是我们的 ENC28J60),通过这个函数实现网卡初始化。其次,是 tapdev_read 函数,该函数用于从网卡读取一包数据,将读到的数据存放在 uip_buf 里面,数据长度返回给 uip_len。最后,是 tapdev_send 函数,该函数用于向网卡发送一包数据,将全局缓存区 uip_buf 里面的数据发送出去(长度为 uip_len)。其实这三个函数就是实现最底层的网卡操作。 第二步:因为uIP协议栈需要使用时钟,为TCP和ARP的定时器服务,因此我们需要STM32 提供一个定时器做时钟,提供 10ms 计时(假设 clock-arch.h 里面的 CLOCK_CONF_SECOND 为 100),通过 clock-arch.c 里面的 clock_time 函数返回给 uIP 使用。 第三步:配置 uip-conf.h 里面的宏定义选项。主要用于设置 TCP 最大连接数、TCP 监听端口数、CPU 大小端模式等,这个大家根据自己需要配置即可。 通过以上 3 步的修改,我们基本上就完成了 uIP 的移植。在使用uIP 的时候,一般通过如下顺序: 1)实现接口函数(回调函数)UIP_APPCALL。 该函数是我们使用 uIP 最关键的部分,它是 uIP 和应用程序的接口,我们必须根据自己的需要,在该函数做各种处理,而做这些处理的触发条件,就是前面提到的 uIP 提供的 那些接口函数,如 uip_newdata、uip_acked、uip_closed 等等。另外,如果是 UDP,那么还需要实现 UIP_UDP_APPCALL 回调函数。 2)调用tapdev_init函数,先初始化网卡。 此步先初始化网卡,配置 MAC 地址,为 uIP 和网络通信做好准备。 3)调用 uip_init 函数,初始化 uIP 协议栈。 此步主要用于 uip 自身的初始化,我们直接调用就是。4) 设置 IP 地址、网关以及掩码这个和电脑上网差不多,只不过我们这里是通过 uip_ipaddr、uip_sethostaddr、 uip_setdraddr 和 uip_setnetmask 等函数实现。 5) 设置监听端口 uIP 根据你设定的不同监听端口,实现不同的服务,比如我们实现 Web Server 就监听 80 端口(浏览器默认的端口是 80 端口),凡是发现 80 端口的数据,都通过 Web Server 的 APPCALL 函数处理。根据自己的需要设置不同的监听端口。不过 uIP 有本地端口(lport)和远程端口(rport)之分,如果是做服务端,我们通过监听本地端口(lport)实现;如果是做客户端,则需要去连接远程端口(rport)。 6) 处理 uIP 事件 最后,uIP 通过 uip_polling 函数轮询处理 uIP事件。该函数必须插入到用户的主循环里 面(也就是必须每隔一定时间调用一次)。ENC28J60模拟时序接口:   
  • [技术干货] 基于STM32+华为云IOT设计的酒驾监控系统
    1. 前言随着人们生活水平的提高,为了减少和杜绝车的现象越来越多,所引发的交通事故也引起了人们的重视,为了减少和杜绝酒后驾车导致事故发生,当前设计了一种安装在驾驶室内,能根据具体功能进行鉴别酒精浓度的系统,当识别到酒驾之后会发出警报,并发送短信给指定紧急联系人,通知家人通知酒驾,一起监督行车安全。并将GPS数据上传到华为云物联网平台,调用地图模块显示具体位置,主控芯片采用STM32单片机,GSM模块采用IM800C。系统还带了一块LCD显示屏,可以实时显示检测的酒精含量,酒精超标后,本地蜂鸣器也会发出警报,提醒驾驶员。测量酒驾的标准是:(1)饮酒驾车是指车辆驾驶人员血液中的酒精含量大于或者等于20mg/100ml,小于80mg/100ml的驾驶行为。(2)醉酒驾车是指车辆驾驶人员血液中的酒精含量大于或者等于80mg/100ml的驾驶行为。需要使用的硬件如下:(1)STM32系统板(2)酒精检测传感器(3)LCD显示屏(4)供电模块(5)SIM800C模块(6)蜂鸣器模块(7)GPS模块--采用SIM800C内部的GPS功能获取GPS数据。为了测试,搞了几口二锅头。上位机采用QT开发,调用华为云的应用侧接口获取设备上传的数据。再调用百度接口,获取GPS定位的位置。2. 登录华为云创建云端设备2.1 创建产品华为运官网: https://www.huaweicloud.com/接下来继续创建产品,点击产品页面,点击右上角创建产品: 2.2 产品模型定义 2.3 生成MQTT登录密匙创建完产品、设备之后,接下来就需要知道如何通过MQTT协议登陆华为云服务器。官方的详细介绍在这里: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112 MQTT设备登陆密匙生成地址: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/ 2.4 使用MQTT客户端软件登录所有的参数已经得到,接下来采用MQTT客户端登录华为云进行测试。华为云物联网平台的域名是: a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com华为云物联网平台的IP地址是: 121.36.42.100在软件里参数填充正确之后,就看到设备已经连接成功了。接下来打开设备页面,可以看到设备已经在线了。//订阅主题: 平台下发消息给设备 $oc/devices/625cd29cecf9c41c38215ba5_1126626497/sys/messages/down //设备上报数据 $oc/devices/625cd29cecf9c41c38215ba5_1126626497/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "MQ3","properties":{"MQ3":50}}]}3. STM32代码实现酒精浓度传感器的预热时间要20秒~1分钟左右,这段时间之后采集的数据才会稳定。预热模块会发热,烫手,这个是正常现象。3.1 系统原理图3.2 代码#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include <string.h> #include "timer.h" #include "oled.h" #include "adc.h" #include <string.h> #include <stdlib.h> #include "font.h" #include "sim800c.h" #include "mqtt.h" /* 硬件连接方式: 1. TFT 1.44 寸彩屏接线 GND 电源地 VCC 接5V或3.3v电源 SCL 接PC8(SCL) SDA 接PC9(SDA) RST 接PC10 DC 接PB7 CS 接PB8 BL 接PB11 2. 蜂鸣器 VCC--->3.3V DAT--->PA6 GND--->GND 3. 酒精检测传感器MQ3 VCC--->3.3V GND--->GND DAT--->PA1 4. 板载LED灯接线 LED1---PA8 LED2---PD2 5. 板载按键接线 K0---PA0 K1---PC5 K2---PA15 6. SIM800C--GSM模块 GND----GND VCC--->3.3V PA2----SIM800C_RXD PA3----SIM800C_TXD */ //华为物联网服务器的设备信息 #define MQTT_ClientID "62381267575fb713ee164ad2_xl_1_0_0_2022032106" #define MQTT_UserName "62381267575fb713ee164ad2_xl_1" #define MQTT_PassWord "124344feff3e3d96ff6af13cf36af36766619ff1eeee40e99cbae9b7b9739fe4" //订阅与发布的主题 #define SET_TOPIC "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/properties/report" //发布 //设置连接的路由器信息 #define CONNECT_WIFI "Xiaomi_meizi6" //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define CONNECT_PASS "12170307yu" //将要连接的路由器密码 #define CONNECT_SERVER_IP "a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com" //服务器IP地址 #define CONNECT_SERVER_PORT 1883 //服务器端口 //JTAG模式设置,用于设置JTAG的模式 //mode:jtag,swd模式设置;00,全使能;01,使能SWD;10,全关闭; #define JTAG_SWD_DISABLE 0X02 #define SWD_ENABLE 0X01 #define JTAG_SWD_ENABLE 0X00 void JTAG_Set(u8 mode) { u32 temp; temp=mode; temp<<=25; RCC->APB2ENR|=1<<0; //开启辅助时钟 AFIO->MAPR&=0XF8FFFFFF; //清除MAPR的[26:24] AFIO->MAPR|=temp; //设置jtag模式 } u16 MQ3_data=0; //酒精浓度 float MQ3_data_float=0; //mg/l char data_buff[100]; //酒精超标提示 u8 sim800c_buff[100]="Drunk driving reminder"; int main() { u8 state=0; u8 key=0; u32 time_cnt=0; JTAG_Set(JTAG_SWD_DISABLE); //释放PA15 LED_Init(); //LED灯初始化 BEEP_Init(); //蜂鸣器初始化 KEY_Init(); //按键初始化 USART1_Init(115200); //串口1初始化-打印调试信息 Lcd_Init(); //LCD初始化 Lcd_Clear(0); //清屏为黑色 LCD_LED_SET; //通过IO控制背光亮 AdcInit(); //ADC初始化--检测酒精传感器的值 TIMER2_Init(72,20000);//辅助串口2接收,超时时间为20ms USART2_Init(115200); //可能的波特率(测试): 57600 、9600、115200 USART3_Init(115200);//串口-WIFI TIMER3_Init(72,20000); //超时时间20ms //预热视频 LCD_ShowChineseFont(0,16*3,16,HZ_FONT_16[20],RED,0); LCD_ShowChineseFont(16,16*3,16,HZ_FONT_16[21],RED,0); LCD_ShowChineseFont(16*2,16*3,16,HZ_FONT_16[22],RED,0); LCD_ShowChineseFont(16*3,16*3,16,HZ_FONT_16[23],RED,0); LCD_ShowChineseFont(16*4,16*3,16,HZ_FONT_16[24],RED,0); LCD_ShowChineseFont(16*5,16*3,16,HZ_FONT_16[25],RED,0); LCD_ShowChineseFont(16*6,16*3,16,HZ_FONT_16[26],RED,0); LCD_ShowChineseFont(16*7,16*3,16,HZ_FONT_16[27],RED,0); //delay_ms(30000); //初始化时间30秒 delay_ms(5000); //初始化时间5秒 Lcd_Clear(0); //清屏为黑色 //采集数据的真实数据 LCD_ShowChineseFont(0,16*2,16,HZ_FONT_16[0],RED,0); LCD_ShowChineseFont(16,16*2,16,HZ_FONT_16[1],RED,0); LCD_ShowChineseFont(16*2,16*2,16,HZ_FONT_16[2],RED,0); LCD_ShowChineseFont(16*3,16*2,16,HZ_FONT_16[3],RED,0); //酒精含量计算结果 LCD_ShowChineseFont(0,16*3,16,HZ_FONT_16[4],RED,0); LCD_ShowChineseFont(16,16*3,16,HZ_FONT_16[5],RED,0); LCD_ShowChineseFont(16*2,16*3,16,HZ_FONT_16[6],RED,0); LCD_ShowChineseFont(16*3,16*3,16,HZ_FONT_16[7],RED,0); //酒精超标显示 Gui_DrawFont_GBK16(0,16*4,RED,0,(u8*)" "); LCD_ShowChineseFont(0,16*4,16,HZ_FONT_16[16],RED,0); LCD_ShowChineseFont(16,16*4,16,HZ_FONT_16[17],RED,0); LCD_ShowChineseFont(16*2,16*4,16,HZ_FONT_16[18],RED,0); LCD_ShowChineseFont(16*3,16*4,16,HZ_FONT_16[19],RED,0); //初始化SIM800C state=SIM800C_InitCheck(); printf("SIM800C初始化状态:%d\r\n",state); DelayMs(1000); //设置文本模式 state=SIM800C_SetNoteTextMode(); printf("设置文本模式状态:%d\r\n",state); DelayMs(1000); // printf("正在初始化WIFI请稍等.\r\n"); for(i=0;i<5;i++) { if(ESP8266_Init()==0) { esp8266_state=1; break; } else { esp8266_state=0; printf("ESP8266硬件检测错误.\n"); } } if(esp8266_state) { printf("准备连接服务器....\r\n"); //非加密端口 printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode(CONNECT_WIFI,CONNECT_PASS,CONNECT_SERVER_IP,CONNECT_SERVER_PORT,1)); //2. MQTT协议初始化 MQTT_Init(); //3. 连接服务器 for(i=0;i<5;i++) { if(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)==0) { esp8266_state=1; break; } esp8266_state=0; printf("服务器连接失败,正在重试...\n"); delay_ms(500); } printf("服务器连接成功.\n"); //3. 订阅主题 if(MQTT_SubscribeTopic(SET_TOPIC,0,1)) { printf("主题订阅失败.\n"); } else { printf("主题订阅成功.\n"); } } while(1) { key=KEY_Scan(); if(key) { printf("key=%d\r\n",key); } //控制LED灯 if(key==2) { LED2=!LED2; } //手动控制蜂鸣器测试 //手动发送短信测试 if(key==1) { BEEP=1; delay_ms(100); BEEP=0; sprintf((char*)sim800c_buff,"Drunk driving reminder:%d",MQ3_data); //发送短信 if(SIM800C_SendNote((u8*)"13800138000",sim800c_buff,strlen((char*)sim800c_buff))==0) printf("短信发送成功\r\n"); else printf("短信发送失败\r\n"); } //轮询时间到达 if(time_cnt>300) { time_cnt=0; LED1=!LED1; //获取空气质量 MQ3_data=GetAdcCHxDATA(1); printf("采集MQ3数据:%d\r\n",MQ3_data); //上报数据 sprintf(data_buff,"{\"services\": [{\"service_id\":\"STM32\",\"properties\":{\"MQ3\":%d}]}", MQ3_data); MQTT_PublishData(POST_TOPIC,data_buff,0); //上报数据 sprintf(data_buff,"{\"services\": [{\"service_id\":\"STM32\",\"properties\":{\"MQ3\":%d}]}", MQ3_data); MQTT_PublishData(POST_TOPIC,data_buff,0); //LCD屏实时显示酒精浓度 //采集数据 sprintf(data_buff,"%d",MQ3_data); Gui_DrawFont_GBK16(72,16*2,RED,0,(u8*)" "); Gui_DrawFont_GBK16(72,16*2,RED,0,(u8*)data_buff); //酒精含量 //MQ3_data_float=((float)MQ3_data*(5.0/4096))*0.36-1.08; MQ3_data_float=((float)MQ3_data*(5.0/4096))*0.36; sprintf(data_buff,"%0.2f mg/L",MQ3_data_float); printf("计算酒精浓度:%s\r\n",data_buff); Gui_DrawFont_GBK16(72,16*3,RED,0,(u8*)" "); Gui_DrawFont_GBK16(72,16*3,RED,0,(u8*)data_buff); //判断酒精浓度是否超标,设置阀值 if(MQ3_data>2000) { sprintf((char*)sim800c_buff," :%d",MQ3_data); //发送短信 if(SIM800C_SendNote((u8*)"18171571217",sim800c_buff,strlen((char*)sim800c_buff))==0) printf("短信发送成功\r\n"); else printf("短信发送失败\r\n"); } //采集的值越小,表明酒精浓度越高。反之越大。 //根据阀值显示不同的文字提示 if(MQ3_data>2000) { //你已酒驾 Gui_DrawFont_GBK16(0,16*4,RED,0,(u8*)" "); LCD_ShowChineseFont(0,16*4,16,HZ_FONT_16[8],RED,0); LCD_ShowChineseFont(16,16*4,16,HZ_FONT_16[9],RED,0); LCD_ShowChineseFont(16*2,16*4,16,HZ_FONT_16[10],RED,0); LCD_ShowChineseFont(16*3,16*4,16,HZ_FONT_16[11],RED,0); } //正常稳定范围采集的值是500左右 else { //安全范围 Gui_DrawFont_GBK16(0,16*4,RED,0,(u8*)" "); LCD_ShowChineseFont(0,16*4,16,HZ_FONT_16[16],RED,0); LCD_ShowChineseFont(16,16*4,16,HZ_FONT_16[17],RED,0); LCD_ShowChineseFont(16*2,16*4,16,HZ_FONT_16[18],RED,0); LCD_ShowChineseFont(16*3,16*4,16,HZ_FONT_16[19],RED,0); } } DelayMs(10); time_cnt++; } }
  • [行业资讯] 单片机未来趋势瞻仰,出货量说明了一切
    单片机未来趋势瞻仰,出货量说明了一切从数据来看,出货量为什么会持续增长,主要是物联网和5G的技术在逐渐成熟,带动了很多传统产品和新型应用的转型,比如智慧停车场、门禁、共享充电宝、共享单车、充电桩、智能家居等等等等。现在其实还不是正在爆发的时候,因为物联网现在也还存在着许多技术瓶颈没有突破,还没有非常成熟,比如延迟的问题、安全性的问题、功耗的问题。等到这些都成熟的的时候,或许就是单片机的寒冬真正过去,春天即将到来,所以奠定了一定的基础,对未来的发展会有一定的帮助。对单片机感兴趣的朋友可以找我,我录制了一些关于单片机的入门教程,有需要的童鞋找我拿就像,免费的,私信我“林老师”就可以拿~点击打开我的头像就能领取下面分享一些关于红外遥控的应用的知识,以单片机为例来给大家介绍红外遥控的编码和解码,专用的红外遥控器的编码芯片就不再做介绍了,这里主要是用单片机用I/O口来仿真的办法来做红外遥控的发射,同时会给再讲解一下,怎么样用单片机来做红外接收的解码,先介绍用单片机来做红外遥控的发射,然后再给介绍红外遥控的解码。红外遥控的发射,可以选用专用的编码芯片,我这主要是用单片机的I/O口来模拟,大家学习的时候,对于理解红外遥控的原理,个人觉得用这种办法的话,会更容易理解一点,利用单片机来制作简单的红外遥控器,先给大家简单的介绍一下红外的概念,然后对这个硬件电路的设计做简单的介绍,然后再给大家简单的介绍一下我们经常使用的协议,也就是红外协议,有兴趣的可以找我聊天,教大家如何用单片机来编写代码,然后来实现简单的红外遥控,之后再给大家介绍如何来做红外的解码。首先来简单的看一下红外遥控的简介,什么是红外线?红外线实际上就是一种肉眼看不见的光,就是红外光,红外线是介于波长在760纳米到1毫米之间的,它是介于微波和可见光之间的一种电磁波,是非可见光,人肉眼也看不到,家里空调或者是电视机的遥控器,当按下按键的时候,你可以拿手机打开手机的摄像头,可以把遥控器对着手机的摄像头,那你可以在屏幕当中是可以看到这个红外光的。红外遥控它是一种无线的非接触的控制技术,具有抗感染能力比较强,传输比较可靠,然后功耗低成本比较容易实现,所以在家用电器当中,包括现在的一些物联网的设备当中,以及在现在的工业设备当中都得到了广泛的应用,所以我们有必要来了解一下红外的概念,以及如何来通过单片机来做简单的红外的发射和红外的接收。如何产生红外光?可以采用红外发射二极管,红外发射二极管它可以发出红外线的一种光,当然了红外发射二极管实际上和普通的发光二极管差不多,它的使用的办法也差不多,最常用的红外发射管是发出940纳米红外光的,还有一种是850纳米红外光的,大家可以去某宝上或者一些气电商城去搜这种红外线的管子,可以看到它们常见的这种管子,基本上都是940纳米的红外管,大家可以选用这种类型的管子。这种管子,它和我们普通的发光二极管差不多,但是它发射出来的是红外光,普通的发光二极管,发出来的可能是红光绿光,这个它发出的是红外光,红外光人也看不到,但是也可以做实验,刚才说了可以拿手机的摄像头对准它,在屏幕当中能够看到这个红外光,同样道理,发光二极管的话,它要通过一定的电流才能导通,它要有一定的导通压降,同时要有一定的电流它才能够发光。
  • [问题求助] 【温湿度远程监测】【温湿度上传到华为云】华为云平台下发命令esp8266接收不到
    【功能模块】华为云平台下发命令,stm32+ESP8266利用MQTT协议接收,然后做出对应的操作【操作步骤】1.用esp8266连接wifi,连接华为云平台服务器  【测试无误】2.订阅相关主题,准备向云平台索求命令mqtt的订阅主题Topic测试了共两个:①$oc/devices/624a6b8e2d0897328702a1f6_weason-agriculture/sys/commands/#  【核心问题】 参考依据是②$oc/devices/624a6b8e2d0897328702a1f6_weason-agriculture/user/dingyue 【核心问题】这个是自定义topic经测试,这两个Topic均无法下达命令 订阅主题函数为【此函数经测试可以订阅其他服务器的主题】【经测试,ESP8266_GetIPD函数没有get到任何值】华为云平台返回的数据的格式是那种【子问题1】3.向服务器上传温湿度数据(pub),经验证无误,可正常上传。【问题汇总】核心问题为:为什么收不到华为云平台下发的命令子问题为:①华为云平台返回的数据的格式是那种【子问题1】②我所订阅的主题是否正确③观察 消息跟踪 得到其Topic与我所订阅的一致,但确实未收到消息。综上问题,我将STM32的程序源码添加至附件了,Readme.txt中有硬件的连接。望诸位同志斧正。急急急
  • [行业资讯] 物联网专业真的很坑吗?浅谈物联网的未来发展趋势和方向
    人啊,最怕就是人云亦云,有时你看到的不一定是真的,其实最好的办法就是看一下这个行业的从业者,他们混得怎么样,多找几个综合对比,比你看任何文章都要来得靠谱!我仔细看了一些回答者以及一些评论者,基本可以判断,这些人要么是在校学生,要么就是外行人。真正这个行业的资深人士都在研究怎么做产品,怎么融资,怎么闷身发财。一、电子展让我学到很多在2014年的时候,当时在一家公司做物联网产品线的项目经理兼研发,经常会参加一些展会,以便自己熟悉行业的人都在做什么产品。大家现在知道那些发展得还不错的平台和公司,其实在那个时候还是个小公司。也正是抓住了物联网这波红利,一飞冲天,特别是现在的物联网云平台。他们快速起飞的秘诀呢,就是不断路演,然后融资。其实当时的所谓物联网产品真的很鸡肋。拿当时wifi插座来说,就开关这么简单的功能,我要先花几分钟配置wifi让插座连上家里的wifi,连上以后打开app,等app连上服务器以后才能控制。有时出问题搞个10几分钟都开不了一个插座,还不如自己走过去手动打开关闭来得快,碰到这种情况你可能会崩溃想把它砸了。我记得当时做wifi插座的时候,不管是wifi模块厂家,还是云平台,都是求着一些传统硬件厂家去使用他们模块和接入他们平台的。当时传统硬件接入平台也不用license费用的,不像现在每接入一个硬件设备到他们平台都要3-10块钱不等。当时大家都在做实验,即便只是一个wifi插座,远程控制开关这么简单的功能,很多都做不稳定。我当时做得也非常痛苦,对这种产品又爱又恨,我们硬件端基本已经很成熟了,因为产品都是经过大批量验证,只不过在原来的基础上加上wifi模组给产品赋能实现远程控制而已。但是中间涉及的环节太多,坑也多,有碰到因为协议不成熟导致不稳定的,也有因为app有问题导致不稳定的。所以做物联网产品是整个体系,其中一个环节不给力,你的努力可能都会白费,这也是为什么很多所谓的远程控制产品都是鸡肋的原因。经历了这么多年的研发和产品验证,我基本上也熟悉了硬件端实现物联网整个通讯流程和协议,后面也和一家企业联合从零搭建了物联网云平台。随着后面对接的平台越来越多,发现各大平台的通讯协议,其实都是相互模仿改进的。二、为什么很多人觉得物联网专业很坑?说了这么多,如果你是物联网工程专业,你可能会觉得很懵,我说的这些怎么我都没接触过?难道我是读的假专业?你有这种问题实在太正常了,不是你学的假专业,而是学校学得太杂了。比如说C/C++、java、C#、单片机原理与应用、传感器原理与应用、ZigBee无线网络技术等等。所学这些很多完全是不同产业链,不同行业方向的知识,这样学,你根本不知道物联网专业出来是干嘛的,跟四不像似的。很多人学完出来甚至连自己找什么职位都不知道,找不到工作也是正常的,所以觉得这个专业坑。如果你单靠学校学出来找工作,未免太牵强了,物联网涉及的领域和行业太多了,要先定位好方向,然后针对性深造。想从事物联网行业,我认为嵌入式是一个不错的切入点。记得在2014年,也就是物联网开始火爆的时候,大量的人都涌去学java做app,当时需求量也确实很大。当时做产品经常要和app工程师联调,有时候一个功能怎么做都做不稳定,我都觉得他们技术很水,但是工资都比我们做单片机开发的要高几千块。那段时间让我非常郁闷,我和很多从业人员一样,一遍靠着这个行业吃饭,一遍骂着这个行业辣鸡,软硬件都要懂,工资还比做app的低这么多。不过最近两年我和一个做app的朋友对比,发现做app也没以前这么香了,反而是做嵌入式的一直处于上涨趋势,很多已经超过app。嵌入式也分很多方向的,主流的是就是单片机开发或者嵌入式linux开发。所以,如果你想从事物联网这个行业,一定把方向定得越细越好,比如说我只做单片机开发。然后用单片机技术去从事物联网的产品开发,这样你才能真正切入物联网这个行业。比如说无线通讯技术(315/433/868M、Lora、Zigbee、蓝牙、nb-iot、wifi等等).这些无线通讯技术怎么应用到我们产品上呢?比如说我们无际单片机编程带领学员做的智能防盗报警系统。主机再通过wifi模块与云平台连接,实现远程监控,这就是一套典型的物联网产品。通过这种项目切入物联网是最接地气的,只有当你做过这样的产品,你才会深刻理解自己学的东西到底有啥用。三、物联网未来发展趋势和方向从目前看的话,虽然这个行业已经发展了很多年,但是我认为依然算得上是蓝海行业。因为目前技术还没有完全成熟,很多东西,没有基础铺垫,即便你知道未来一定会这样去发展,你当下也实现不了。我举个例子,比如说美团、滴滴,包括很多线上的生意,如果线上支付没出来,这种些肯定也很难做起来,因为没有支付工具的基础铺垫。我经常说,我们未来的生活场景就是物联网技术的体现。很多人可能还不知道,物联网到底有啥用,能给我们带来什么便利。下面我还是通过大家肯定接触过的一个例子来讲解:共享单车。共享单车就是一个很典型的物联网应用,并且也确实解决了我们短途出行的效率,这个不可否认。大家想象一下,如果每个个体的出行效率提高了,那必定会提高整体的效率,其中能产生的潜在价值细思极恐。还有其他的智慧停车场之类的物联网应用就不说了,相信经常开车出行的朋友都试过自主扫码付费出停车场,真的是太方便了。所以,物联网目前的应用最大的价值在于提高效率的同时降低成本,只要能让人类更进步的技术我都认为是趋势。而现在物联网仍然处于蓝海阶段,还有很多技术瓶颈尚未解决,lora、nb-iot之类技术的出现都是为了解决物联网某个环节的痛点。等真正实现万物互联那一天,估计就没我们啥事了。当然,如果你能在红利前期布局好,不管是技术还是产品,沉淀几年,别人想超越你也是没那么容易的,这才是核心竞争力。
  • [技术干货] eMTC,你所不了解的基础
    eMTC,全称是 LTE enhanced MTO,是基于LTE演进的物联网技术。为了更加适合物与物之间的通信,也为了更低的成本,对LTE协议进行了裁剪和优化。eMTC基于蜂窝网络进行部署,其用户设备通过支持1.4MHz的射频和基带带宽,可以直接接入现有的LTE网络。eMTC支持上下行最大1Mbps的峰值速率,可以支持丰富、创新的物联应用。eMTC的基本特性窄带LTE其中最主要的几个特性。第一,系统复杂性地大幅度降低,复杂程度及成本得到了极大的优化。第二,功耗极度降低,电池续航时间大幅度增强。第三,网络的覆盖能力大大加强。第四,网络覆盖的密度增强。eMTC具备LPWA基本的四大能力:一是广覆盖,在同样的频段下,eMTC比现有的网络增益15dB,极大地提升了LTE网络的深度覆盖能力;二是具备支撑海量连接的能力,eMTC一个扇区能够支持近10万个连接;三是更低功耗,eMTC终端模块的待机时间可长达10年;四是更低的模块成本,大规模的连接将会带来模组芯片成本的快速下降,eMTC芯片目标成本在1~2美金左右 。除此之外,eMTC还具有四大优势:一是速率高,eMTC支持上下行最大1Mbps的峰值速率,远远超过GPRS、 ZigBee等物联技术的速率,eMTC更高的速率可以支撑更丰富的物联应用,如低速视频、语音等;二是移动性,eMTC支持连接态的移动性,物联网用户可以无缝切换保障用户体验;三是可定位,基于TDD的eMTC可以利用基站侧的PRS测量,在无须新增GPS芯片的情况下就可进行位置定位,低成本的定位技术更有利于eMTC在物流跟踪、货物跟踪等场景的普及:四是支持语音,eMTC从LTE协议演进而来,可以支持 VOLTE语音,未来可被广泛应用到可穿戴设备中。原理与关键技术1、物理层资源结构eMTC作为LTE一个特性, 基本沿用LTE设计,占原有LTE系统的6个PRB,其中一个RB占12个子载波 (子载波带宽15kHz, 间隔为15kHz) 。时域结构上eMTC帧结构与LTE一致;频域结构上,3GPP将系统带宽划分成若干NB (不重叠的6个PRB) ,eMTC UE的调度受NB限制,不能跨NB调度,不同eMTC UE可以共享一个NB的资源。2、信号与信道eMTC不重用LTE的PDCCH、PCFICH和PHICH下行信道, 新增MPDCCH信道, 用于发送eMTC UE的PDSCH和PUSCH信道的调度指示以及公共消息的指示, 比如寻呼、RAR响应、上行ACK反馈。eMTC重用LTE的下行数据信道PDSCH, 支持传输模式为TM1/2/6/9;eMTC重用LTE的下行导频信号RS;重用LTE的物理同步信号PSS/SSS, 其中PSS映射到时隙0和时隙10的最后一个OFDM符号, SSS映射到时隙0和时隙10的倒数第二个OFDM符号,均以5ms为周期重复发送;eMTC重用LTE的物理广播信道PBCH, 新增一套SIB消息, 包括SIB1-BR、SIB2、SIB3、SIB4、SIB5和SIB14共6条,MIB消息新增一个IE用于携带SIB1-BR的调度信息, 在每个系统帧的0#子帧和9#子帧发送, 周期为40ms。eMTC的PRACH和LTE的PRACH分开 (使用相同频率, 时域上区分) , 可以采用时分, 频分, 码分方式;eMTC的PUCCH和LTE的PUCCH分开, eMTC的PUCCH支持跨子帧跳频, 不支持子帧内跳频;eMTC使用LTE传统的PUSCH信道上传数据资源, 其PUSCH资源受NB限制。3、资源共享与调度eMTC作为小区特性, 与LTE共小区部署, 不占用独立小区, 但是需要占用空口的RB资源和基带的处理资源, 为保证MBB业务优先, 系统会预留一定的资源给LTE, 即使LTE没有任何业务, eMTC也不能使用预留。通过配置参数EmtcDlRbTargetRatio和EmtcUlR bTargetRatio, 可以控制LTE和e MTC资源占用比例, 在LTE和eMTC负载均很高时, 依据两者目标利用率, 动态共享LTE的PRB资源, 如图3-case1;当eMTC负载较高, 而LTE有空闲RB资源时, 这些空闲RB资源可以给eMTC使用, 如图3-case2;因e MTC采用跨子帧调度和重复技术, 会长期占用RB资源, 为了避免LTE控制消息和VoIP等高优先级业务被长期阻塞, 通过DlLteRvsNbNum和UlLteRvsNbNum参数给LTE预留RB资源, 保证LTE业务的需求, 即LTE负载较而eMTC负载较低时, LTE可以占用全部带宽。4、峰值速率与LTE下行异步HARQ, 上行同步HARQ不同, eMTC上行下行都是异步HARQ。下行调度, 设MPDCCH重复的最后一个子帧编号n, 则MPDCCH调度的PDSCH起始子帧编号为n+2;设PDSCH重复的最后子帧编号为n, 则PUCCH 1 Ack/Nack子帧编号为n+4。上行调度, 设MPDCCH重复的最后一个子帧编号n, 则MPDCCH调度的PUSCH起始子帧编号为n+4;设PUSCH重复的最后子帧编号为n, 则MPDCCH Ack/Nack子帧编号为n+4。在无重复及重传的情况下,以ModeA对eMTC速率进行估算:下行调度周期为10ms, 全双工时可以下行连续MPDCCH和PDSCH调度, 10ms周期内能发送8个下行TB (传输块) , 每个TB最大1000bits,因此下行峰值速率为8*1000* (1000/10) =800kbps;同理下行半双工峰值速率为300kbps;上行行调度周期为8ms, 同理推算出全双工上行峰值速率1000kbps, 半双工上行峰值速率375kbps。5、功耗eMTC采用PSM和e DRX技术以节约功耗。PSM是一种新增的比Idle态更省电的省电模式, 由MME通过NAS配置给UE, UE发送完数据后在Idle态停留一段时间后进入深度睡眠态, 不监听任何空口消息, 只在主动发送数据和周期TAU时才退出PSM模式, 如图5 (a) ;eDRX通过延长Idle态或连接态的DRX周期, 减少UE侦听网络的信令处理, UE只在每个e DRX周期只在寻呼窗口内监听PDCCH, 其它时间处于睡眠状态, 从而达到UE节电的目的。应用场景eMTC是爱立信提出的无线物联网解决方案。eMTC基于LTE接入技术设计了无线物联网络的软特性,主要面向中低速率、低功耗、大连接、移动性强、具有定位需求的物联网应用场景。eMTC无线物联网技术可支持语音、移动、定位业务,适合进行速率为100kbit/s~1Mbit/s范围内的中速小包数据或语音业务,模组市场价约10美元每块,典型应用为智能电梯、行车、物流跟踪、穿戴设备等。
  • [交流分享] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
总条数:248 到第
上滑加载中