-
一、项目介绍本项目实现了一个基于GTK和libvlc的视频播放器。使用GTK创建GUI界面,使用libvlc播放视频。用户可以通过选择视频文件,然后启动播放器来观看视频。二、VLC介绍VLC是一款自由、开放源代码的跨平台媒体播放器,支持播放几乎所有常见的音频和视频格式。最初于2001年由法国学生开发,现在已经成为了一个非常受欢迎的媒体播放器,在Windows、macOS、Linux等多个操作系统上都可用。libvlc是VLC media player使用的核心库之一。提供了一组应用程序接口(API),可以让开发人员轻松地将类似于VLC的媒体播放功能嵌入到他们自己的应用程序中。libvlc可以与多种编程语言和框架(如C、C++、Python、Java、.NET等)集成,因此被广泛应用于各种媒体相关的项目中。VLC是一个独立的媒体播放器软件,而libvlc是VLC media player使用的核心库之一,可以方便地嵌入到其他应用程序中,以实现类似于VLC的媒体播放功能。VLC软件下载(3.X) :cid:link_1libVLC最新4.0官网: cid:link_0三、GTK介绍文档学习地址:cid:link_2官网地址:cid:link_3GTK是一种开源的跨平台图形用户界面(GUI)工具包。最初是为GNU计划设计的,现在被广泛地用于Linux和其他Unix-like操作系统的各种应用程序中。GTK提供了一组用于创建图形用户界面的功能库,包括窗口、按钮、标签、文本输入框等控件,以及用于渲染这些控件的绘图引擎。GTK还支持国际化和主题定制,可以让开发者创建符合用户期望和风格的应用程序界面。在 Ubuntu 中安装最新的 GTK 依赖库的命令: sudo apt-get update sudo apt-get install libgtk-3-dev这个命令将会安装 GTK3 库的开发文件和依赖库。如果需要在程序中使用 GTK2 库,则需要安装 libgtk2.0-dev 包。除此之外,还可以安装一些其他的GTK扩展包,如GStreamer、WebKit 等。如果要编译一个基于 GTK 的程序,可以使用 gcc 或 g++ 来进行编译,同时需要链接 GTK 库。假设源代码文件为 example.c,编译命令可以如下: gcc -o example example.c `pkg-config --cflags --libs gtk+-3.0`其中,pkg-config 是一个用来管理编译时的依赖库的工具。--cflags 和 --libs 分别是输出 GTK 库的头文件路径和链接库路径,包含了命令行返回的路径。pkg-config --cflags --libs gtk+-3.0` 就是获取编译 GTK 程序时需要的参数。四、设计思路通过GTK调用libvlc来实现视频播放器,需要执行以下步骤:(1)下载和安装libvlc和相关依赖库,可以使用apt-get或者源码编译的方式安装。 sudo apt-get update sudo apt-get install libvlc-dev libgtk-3-devlibvlc-dev 是 libvlc 的开发库,包括头文件和链接库;libgtk-3-dev 是 GTK 库的开发库,也包括头文件和链接库。通过安装这两个开发库,就可以在 Ubuntu 下进行开发基于 GTK 和 libvlc 的视频播放器了。(2)在GTK程序中引入libvlc的头文件和库文件,以及GTK的头文件和库文件: #include <gtk/gtk.h> #include <vlc/vlc.h>(3)创建GTK窗口和控件: GtkWidget *window; GtkWidget *video_widget; GtkBuilder *builder;GtkBuilder用于动态加载UI文件,可以通过glade工具创建UI文件,然后在程序中使用GtkBuilder加载UI文件。video_widget是用于显示视频的GTK控件。(4)初始化libvlc,并创建libvlc_media_player对象和libvlc_media对象: libvlc_instance_t *vlc_instance; libvlc_media_t *media; libvlc_media_player_t *media_player; // ... vlc_instance = libvlc_new(0, NULL); media = libvlc_media_new_path(vlc_instance, "/path/to/video.mp4"); media_player = libvlc_media_player_new_from_media(media); libvlc_media_player_set_xwindow(media_player, GDK_WINDOW_XID(gtk_widget_get_window(video_widget))); libvlc_media_player_play(media_player); // ...第三行代码使用libvlc_media_new_path()函数创建一个libvlc_media对象,用于表示要播放的视频文件。第四行代码使用libvlc_media_player_new_from_media()函数创建一个libvlc_media_player对象,用于播放视频。第五行代码使用libvlc_media_player_set_xwindow()函数将video_widget的XID绑定到libvlc_media_player对象中,从而能够将视频显示在video_widget中。第六行代码使用libvlc_media_player_play()函数开始播放视频。(5)在GTK窗口中添加视频控件,并启动GTK主循环: builder = gtk_builder_new_from_file("ui.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); video_widget = GTK_WIDGET(gtk_builder_get_object(builder, "video_widget")); // ... gtk_container_add(GTK_CONTAINER(window), video_widget); gtk_widget_show_all(window); gtk_main();第一行代码通过GtkBuilder加载UI文件,并获取main_window和video_widget对象。第三行代码将video_widget添加到window中,第四行代码显示窗口和控件,最后一行代码启动GTK主循环。五、完整的设计代码【1】main.c代码 #include <gtk/gtk.h> #include <vlc/vlc.h> int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *video_widget; GtkBuilder *builder; libvlc_instance_t *vlc_instance; libvlc_media_t *media; libvlc_media_player_t *media_player; gtk_init(&argc, &argv); builder = gtk_builder_new_from_file("ui.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); video_widget = GTK_WIDGET(gtk_builder_get_object(builder, "video_widget")); vlc_instance = libvlc_new(0, NULL); media = libvlc_media_new_path(vlc_instance, "/path/to/video.mp4"); media_player = libvlc_media_player_new_from_media(media); libvlc_media_player_set_xwindow(media_player, GDK_WINDOW_XID(gtk_widget_get_window(video_widget))); libvlc_media_player_play(media_player); gtk_container_add(GTK_CONTAINER(window), video_widget); gtk_widget_show_all(window); gtk_main(); libvlc_media_player_stop(media_player); libvlc_media_player_release(media_player); libvlc_release(vlc_instance); return 0; }【2】ui.glade代码使用glade工具创建UI文件:<?xml version="1.0" encoding="UTF-8"?><!-- Generated with glade 3.22.1 --><interface> <requires lib="gtk+" version="3.20"/> <object class="GtkWindow" id="main_window"> <property name="can_focus">False</property> <child> <object class="GtkDrawingArea" id="video_widget"> <property name="can_focus">False</property> </object> </child> </object></interface>
-
希望支持Risc-v单片机的嵌入式开发IDE环境。国产化是个趋势,在这个大环境中risc-v芯片不错,可惜跟arm比起来生态太弱。比如国产risc-v界碎片化忒严重,没有一款通用好用的IDE。各个芯片厂家各显神通,重复造轮子,然而基于eclipse 的IDE体积大且不太好用。CodeArts编码体验不错,希望未来能也拿来作为单片机IDE的一种来用。至少考虑下这一可能性和选项,这样也更利于生态的扩大。另外还有就是希望别跟自家的华为云绑定太紧了,虽然这是特色。但是其他家不用这云呢或者用不到呢,能以插件形式提供最好。更开放些更好。
-
基于51单片机的智能营养秤系统设计与实现cid:link_2随着人们生活水平和健康意识的提高,越来越多的人开始注重自己的饮食健康。在此背景下,智能营养秤系统应运而生,成为了一种非常实用的工具。本项目基于51单片机设计和实现一种智能营养秤系统,通过该系统可准确地测量食物的重量并计算其热量、蛋白质、脂肪、碳水化合物等营养成分含量。当前系统采用了STC89C52单片机作为主控芯片,预置了多种食材的营养成分数据。用户只需要使用矩阵键盘输入食材编号,将需要称重的食材放置在重力传感器上进行依次称重,系统就可以自动计算出所有食材的各类营养含量总值,并通过液晶屏显示出来。同时,系统根据预设的营养指标,对不达标或超标的食材进行对应的声光提示,提醒用户注意饮食健康。当前系统还配备了无线WIFI模块,可以将当前营养数据上传到手机端实时显示,并给出营养建议。这使得用户可以随时1了解自己的饮食情况,及时进行调整,从而达到更好的健康效果。本项目的设计和实现是为了满足人们对于饮食健康的需求,帮助人们更好地控制自己的饮食,达到健康瘦身的目的。同时,由于采用了51单片机的设计方案,具有成本低、易于制作、易于维护等优点,具有广泛的应用前景。基于STM32的铁路自动围栏系统设计cid:link_3随着城市规模的不断扩大和交通运输方式的日益发展,铁路与公路的交叉口已经成为常见的场景。然而,这些交叉口往往存在一定的安全隐患,因为有时不易发现列车行进的情况,导致公路上的车辆或行人可能会无意中闯入铁路区域,从而引发重大交通事故。为了解决这个问题,当前开发了一款基于STM32的铁路自动围栏系统。该系统采用了STM32F103RCT6作为主控芯片,并使用步进电机来控制铁路围栏的开启和闭合。同时,系统还配备了红外感应器,以便能够及时监测到列车的通过情况。当系统监测到有列车即将通过铁路交叉口时,公路信号灯会立刻变为红灯,蜂鸣器也会发出警报声音,以提醒行人和车辆注意安全。同时,铁路两侧的围栏也会自动关闭,在列车通过后再次打开。这样,就能有效地防止公路车辆和行人误闯铁路区域,保障了路人的安全。基于STM32的无人售货机系统设计cid:link_4随着科技的发展和生活水平的提高,人们对于购物体验的要求越来越高。传统的商场、超市购物方式已经无法满足消费者的需求,因此无人售货机应运而生。本文针对现有售货机存在的缺陷,设计了一款基于STM32的无人售货机系统。该系统采用STM32作为主控芯片,使用液晶屏显示各种商品库存与售价,用户按下对应按键选择购买指定商品,在矩阵键盘输入账号密码付款。若付款成功,对应电机旋转一定角度使商品出库,同时修改库存;若余额不足,则进行声光提示。手机端还可查看消费流水、商品库存情况,并进行补货和充值操作。QML加载模块 WebView 与C++代码通信控制WebView模块的隐藏与显示cid:link_5在Qt Quick中,WebView模块可以用来显示网页内容。它提供了一个基于WebKit引擎的web浏览器组件,可以显示本地html文件或者加载外部网页。基于STM32的智能饮水机系统设计cid:link_0随着智能化的迅速发展,人们对于生活中的各类设备也越来越有智能化的需求,其中智能饮水机是一种比较常见的设备。智能饮水机不仅可以提供饮用水,还可以通过智能化的技术满足人们对于水质、水温、出水量等方面的需求。因此,当前设计了一种基于STM32的智能饮水机系统,以满足人们对智能化饮水机的需求。CC2530采用ESP8266与手机APP通信cid:link_6项目实现通过CC2530控制ESP8266将其配置成AP+TCP服务器模式,并通过手机APP连接到TCP服务器并完成数据传输。ESP8266将作为一个热点(AP)来工作,其WiFi模块被配置为建立一个TCP服务器并监听端口号。CC2530将使用其串口与ESP8266进行通信,并通过AT指令控制ESP8266的WiFi模块设置和数据传输。QDir拼接路径解决各种\//斜杠问题cid:link_1一般在项目中经常需要组合路径,与其他程序进行相互调用传递消息通信。 经常可能因为多加斜杠、少加斜杠等问题导致很多问题。 为了解决这些问题,我们可以使用QDir来完成路径的拼接,不要直接拼接字符串。基于CC2530设计智慧农业控制系统cid:link_7智慧农业是近年来发展迅速的领域,其目的是利用先进的传感技术、物联网技术和云计算技术等,实现自动化、智能化的农业生产管理,并提高农业生产效率和质量。本文基于CC2530设计了一种智慧农业控制系统,采用DHT11模块、BH1750模块和土壤湿度传感器等传感器,通过串口协议将采集的数据上传给上位机显示。基于Linux设计的倒车雷达系统cid:link_8随着社会的不断发展,人们对于汽车的安全性要求越来越高,而倒车雷达系统就是为了增强汽车驾驶者的安全性而被广泛使用。在这种情况下,我们开发了一个基于Linux设计的倒车雷达系统,该系统可以采用迅为4412主控板,运行Linux3.5内核,使用USB摄像头、TFT真彩显示屏、超声波测距模块和蜂鸣器等硬件。STM32采集传感器数据通过冒泡排序取稳定值cid:link_9在物联网、单片机开发中,经常需要采集各种传感器的数据。比如:温度、湿度、MQ2、MQ3、MQ4等等传感器数据。这些数据采集过程中可能有波动,偶尔不稳定,为了得到稳定的值,我们可以对数据多次采集,进行排序,去掉最大和最小的值,然后取平均值返回。C语言实现单链表-增删改查cid:link_10链表是由一连串节点组成的数据结构,每个节点包含一个数据值和一个指向下一个节点的指针。链表可以在头部和尾部插入和删除节点,因此可以在任何地方插入和删除节点,从而使其变得灵活和易于实现。链表通常用于实现有序集合,例如队列和双向链表。链表的优点是可以快速随机访问节点,而缺点是插入和删除操作相对慢一些,因为需要移动节点。此外,链表的长度通常受限于内存空间,因此当链表变得很长时,可能需要通过分页或链表分段等方式来管理其内存。STC89C52+DHT20设计的环境温湿度检测仪cid:link_11本项目基于STC89C52单片机和DHT20温湿度传感器,实现了一款环境温湿度检测仪。通过传感器采集环境的温度和湿度数据,利用IIC接口的OLED显示屏显示出来,便于用户实时监测环境温湿度状态。在现代社会,人们对环境温湿度的要求越来越高。无论是工作场所还是居住环境,都需要维持一个舒适的温湿度状态,以保证身体的健康和工作效率的提高。随着科技的不断进步和物联网技术的广泛应用,环境温湿度检测仪被广泛运用于各种领域,如制造业、医疗、农业等等,成为了一种重要的环境检测设备。而本项目所涉及的STC89C52单片机和DHT20温湿度传感器作为传统的嵌入式开发技术,在实现物联网设备方面有着广泛的应用前景。通过本项目的学习和实践,可以深入了解传感器技术的原理和应用,并掌握基于单片机的嵌入式开发技术,为实现更多物联网设备的开发和应用打下基础。STC89C52+AT24C02实现设备开机次数记录cid:link_12在一些设备的使用过程中,需要对设备的使用次数进行统计和记录。这可以用于评估设备的实际使用寿命、确定维护周期、预测故障风险等方面,对于提高设备的稳定性和可靠性具有重要意义。当前项目采用STC89C52作为主控芯片,AT24C02作为存储芯片,实现了设备的开机次数记录功能。每次设备上电启动时,程序会从AT24C02中读取之前的记录值并加1,然后再将新的记录值写入AT24C02中,从而完成一次开机次数的记录。通过这种方式,可以实时、准确地记录设备的使用次数,并且不受断电影响,数据可靠性高。基于STM32设计的炉温温度检测仪cid:link_13炉温检测在现代工业生产中十分重要,因为炉温过高或过低都会对产品质量产生影响,甚至影响工厂的正常运作。因此,设计一款能够精准测量炉温并显示结果的检测仪器具有很大的实用价值。 本项目采用了STM32F103C8T6作为主控芯片,该芯片拥有丰富的外设和性能较好的计算能力,能够满足该项目对计算和控制的需求。同时,铂电阻PT100作为测温传感器,能够提供更加精准的温度测量结果。
-
炉温检测在现代工业生产中十分重要,因为炉温过高或过低都会对产品质量产生影响,甚至影响工厂的正常运作。因此,设计一款能够精准测量炉温并显示结果的检测仪器具有很大的实用价值。 本项目采用了STM32F103C8T6作为主控芯片,该芯片拥有丰富的外设和性能较好的计算能力,能够满足该项目对计算和控制的需求。同时,铂电阻PT100作为测温传感器,能够提供更加精准的温度测量结果。一、项目背景随着工业生产的发展,炉温检测在现代化工、钢铁、电子、玻璃等行业中变得越来越重要。对于这些行业,稳定的生产环境和品质稳定的产品是必须的,而炉温是影响产品品质的重要因素之一。如果炉温过高或过低,都有可能导致产品结构改变、硬度变化、强度下降等质量问题,使得产品不能达到预期的性能指标。此外,炉温不仅会影响产品质量,还会影响设备的使用寿命和工作效率,有时甚至会对整个工厂的正常生产造成影响。为了防止这些问题的发生,现代化工、钢铁、电子、玻璃等行业需要精准测量炉温并实时地监测炉温变化情况。而本项目即是为了满足这些需求而设计的。采用STM32F103C8T6作为主控芯片,它是一款基于ARM Cortex-M3内核的微控制器,具有丰富的外设和良好的计算能力,并且易于控制和集成到系统中。同时,铂电阻PT100是一种高精度、稳定性好、线性度高的温度传感器,能够提供更加准确的温度测量结果。采用0.96寸IIC接口的OLED屏幕进行显示,操作简便、节省成本,并且具有较好的兼容性和可移植性。二、设计思路【1】硬件设计主控芯片采用STM32F103C8T6,其内置有多种外设,可满足该项目的需求。铂电阻PT100作为测温传感器,能够提供更加准确的温度测量结果。0.96寸IIC接口的OLED显示屏幕是本项目的显示工具,能够直观地显示测量结果。【2】软件设计软件设计分为数据采集、数据处理和数据显示三个部分。采用STM32的ADC进行数据采集,通过PT100将温度信号转换为电阻信号,再通过AD转换器转换成数字信号进行处理。在数据处理中,对ADC采样值进行数据校准、滤波处理和算法计算,得到准确的温度值。最后,通过IIC总线协议将温度值发送给OLED屏幕进行显示,实现实时显示检测结果的功能。三、代码实现【1】OLED显示屏代码以下是基于STM32F103C8T6主控芯片,通过IIC接口控制0.96寸OLED显示屏显示数字的代码: #include "stm32f10x.h" #include "i2c.h" #define OLED_ADDRESS 0x78 // OLED IIC地址 void oled_init(void) { OLED_Write_Command(0xAE); // 关闭显示 OLED_Write_Command(0xD5); // 设置时钟分频因子 OLED_Write_Command(0x80); // 重要参数,必须设置,不然屏幕无法上电 OLED_Write_Command(0xA8); // 设置驱动路数 OLED_Write_Command(0x3F); // 默认值 OLED_Write_Command(0xD3); // 设置显示偏移 OLED_Write_Command(0x00); // 默认值 OLED_Write_Command(0x40); // 设置起始行 OLED_Write_Command(0x8D); // 电荷泵设置 OLED_Write_Command(0x14); // 开启电荷泵 OLED_Write_Command(0x20); // 设置内存地址模式 OLED_Write_Command(0x00); // 水平模式 OLED_Write_Command(0xA1); // 段重新映射设置 OLED_Write_Command(0xC0); // 设置COM扫描方向 OLED_Write_Command(0xDA); // 设置COM引脚硬件配置 OLED_Write_Command(0x12); // 默认值 OLED_Write_Command(0x81); // 对比度设置 OLED_Write_Command(0xCF); // 默认值 OLED_Write_Command(0xd9); // 设置预充电周期 OLED_Write_Command(0xF1); // 默认值 OLED_Write_Command(0xDB); // 设置VCOMH OLED_Write_Command(0x40); // 默认值 OLED_Write_Command(0xA4); // 关闭全屏点亮 OLED_Write_Command(0xA6); // 设置显示方式 OLED_Write_Command(0xAF); // 开启屏幕显示 } void OLED_Write_Command(uint8_t cmd) { // 写命令 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x00); I2C1_SendByte(cmd); I2C1_Stop(); } void OLED_Write_Data(uint8_t data) { // 写数据 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x40); I2C1_SendByte(data); I2C1_Stop(); } void OLED_Set_Pos(uint8_t x, uint8_t y) { // 设置光标位置 OLED_Write_Command(0xb0+y); OLED_Write_Command(((x&0xf0)>>4)|0x10); OLED_Write_Command(x&0x0f); } void OLED_Show_Number(uint8_t x, uint8_t y, uint32_t num) { // 在指定位置显示数字 OLED_Set_Pos(x, y); while (num) { uint8_t temp = num % 10; OLED_Write_Data(temp + '0'); num /= 10; } } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C1_Init(); oled_init(); OLED_Show_Number(0, 0, 12345); //在第1行第1列显示数字12345 while (1) { } }首先,通过oled_init()函数初始化OLED屏幕,在函数中依次写入了一系列命令,来设置OLED的各种参数,例如驱动路数、扫描方向、预充电周期、对比度等。接着,在OLED_Show_Number()函数中,调用了OLED_Set_Pos()函数来设置数字显示的位置,然后通过循环取余数的方法将数字逐位分离,再将其转换为字符型并通过OLED_Write_Data()函数输出到OLED屏幕上,最终实现在屏幕上显示指定数字的功能。【2】温度测量代码以下是基于STM32F103C8T6主控芯片,通过IIC接口控制0.96寸OLED显示屏显示温度,并通过串口打印温度的代码: #include "stm32f10x.h" #include "i2c.h" #include "usart.h" #define OLED_ADDRESS 0x78 // OLED IIC地址 // PT100温度转换函数 float RTD2Temperature(float R) { float temperature = 0; float RTD_A = 3.9083e-003f; float RTD_B = -5.775e-007f; temperature = (-RTD_A + sqrtf(RTD_A * RTD_A - 4 * RTD_B * (1 - R / 100))) / (2 * RTD_B); return temperature; } void oled_init(void) { OLED_Write_Command(0xAE); // 关闭显示 OLED_Write_Command(0xD5); // 设置时钟分频因子 OLED_Write_Command(0x80); // 重要参数,必须设置,不然屏幕无法上电 OLED_Write_Command(0xA8); // 设置驱动路数 OLED_Write_Command(0x3F); // 默认值 OLED_Write_Command(0xD3); // 设置显示偏移 OLED_Write_Command(0x00); // 默认值 OLED_Write_Command(0x40); // 设置起始行 OLED_Write_Command(0x8D); // 电荷泵设置 OLED_Write_Command(0x14); // 开启电荷泵 OLED_Write_Command(0x20); // 设置内存地址模式 OLED_Write_Command(0x00); // 水平模式 OLED_Write_Command(0xA1); // 段重新映射设置 OLED_Write_Command(0xC0); // 设置COM扫描方向 OLED_Write_Command(0xDA); // 设置COM引脚硬件配置 OLED_Write_Command(0x12); // 默认值 OLED_Write_Command(0x81); // 对比度设置 OLED_Write_Command(0xCF); // 默认值 OLED_Write_Command(0xd9); // 设置预充电周期 OLED_Write_Command(0xF1); // 默认值 OLED_Write_Command(0xDB); // 设置VCOMH OLED_Write_Command(0x40); // 默认值 OLED_Write_Command(0xA4); // 关闭全屏点亮 OLED_Write_Command(0xA6); // 设置显示方式 OLED_Write_Command(0xAF); // 开启屏幕显示 } void OLED_Write_Command(uint8_t cmd) { // 写命令 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x00); I2C1_SendByte(cmd); I2C1_Stop(); } void OLED_Write_Data(uint8_t data) { // 写数据 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x40); I2C1_SendByte(data); I2C1_Stop(); } void OLED_Set_Pos(uint8_t x, uint8_t y) { // 设置光标位置 OLED_Write_Command(0xb0+y); OLED_Write_Command(((x&0xf0)>>4)|0x10); OLED_Write_Command(x&0x0f); } void OLED_Show_Temperature(uint8_t x, uint8_t y, float temperature) { // 在指定位置显示温度 OLED_Set_Pos(x, y); int temp = (int)(temperature * 10); for (int i = 0; i < 5; i++) { if (i == 2) { OLED_Write_Data('.'); } else { OLED_Write_Data(temp % 10 + '0'); temp /= 10; } } OLED_Write_Data('C'); } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C1_Init(); oled_init(); USART1_Init(); while (1) { float resistance = 100; // 铂电阻的电阻值 float temperature = RTD2Temperature(resistance); // 算出温度值 // OLED显示温度 OLED_Show_Temperature(0, 0, temperature); // 串口输出温度 char str[32]; sprintf(str, "Temperature: %.1f C\r\n", temperature); USART1_SendString(str); delay_ms(1000); // 延时1s } }首先,利用RTD2Temperature()函数将铂电阻的电阻值转换为温度值。接着,在OLED_Show_Temperature()函数中,调用了OLED_Set_Pos()函数来设置温度显示的位置,并将温度值逐位分离,通过OLED_Write_Data()函数输出到OLED屏幕上,最终实现在屏幕上显示测量的温度的功能。同时,也通过串口输出温度值。在主函数main()中,不断循环读取铂电阻的电阻值,并通过RTD2Temperature()函数转换为温度值。然后,调用OLED_Show_Temperature()函数将温度显示在OLED屏幕上,并调用USART1_SendString()函数通过串口输出温度值。最后,通过delay_ms()函数延时1秒,等待下一次测量。
-
一、项目介绍在一些设备的使用过程中,需要对设备的使用次数进行统计和记录。这可以用于评估设备的实际使用寿命、确定维护周期、预测故障风险等方面,对于提高设备的稳定性和可靠性具有重要意义。当前项目采用STC89C52作为主控芯片,AT24C02作为存储芯片,实现了设备的开机次数记录功能。每次设备上电启动时,程序会从AT24C02中读取之前的记录值并加1,然后再将新的记录值写入AT24C02中,从而完成一次开机次数的记录。通过这种方式,可以实时、准确地记录设备的使用次数,并且不受断电影响,数据可靠性高。二、AT24C02介绍AT24C02是一款由Atmel公司生产的串行EEPROM存储器芯片,可以存储2K(2048bit)数据,支持I2C总线通信协议,被广泛应用于各种电子设备中。AT24C02有8个地址引脚(A0~A2),可以通过这些引脚设置不同的设备地址,使得多个AT24C02芯片能够在同一I2C总线上同时使用而不会冲突。该芯片还具有擦写次数和保护功能,能够防止数据被误删或者未经授权的修改。AT24C02的工作电压范围为1.8V~5.5V,主要分为三个模式:写入模式、读取模式、休眠模式。写入模式和读取模式都需要先发送设备地址和命令字,然后才能进行数据操作。AT24C02对于输入输出电平都有严格的要求,如输入电压范围应在VSS-0.3V ~ VCC+0.3V之间,输出电压高电平应在0.4VCC ~ VCC之间,低电平应在0V ~ 0.1VCC之间,以确保数据传输的准确性和可靠性。由于AT24C02体积小巧,功耗低并且具有不易丢失数据的特点,被广泛应用于电子产品中,例如:数码相机、智能手表、智能家居、安全监控等领域。三、代码实现以下是STC89C52+AT24C02实现设备开机次数记录的代码: #include <reg52.h> #include <intrins.h> #define uint unsigned int #define uchar unsigned char sbit SCL = P1^0; // I2C总线时钟线 sbit SDA = P1^1; // I2C总线数据线 #define AT24C02_ADDR 0xA0 // AT24C02设备地址 /* 延时函数 */ void delay(uint i) { while(i--); } /* I2C总线起始信号 */ void I2C_Start() { SDA = 1; SCL = 1; delay(10); // 延时,确保数据稳定 SDA = 0; delay(10); SCL = 0; } /* I2C总线停止信号 */ void I2C_Stop() { SDA = 0; SCL = 1; delay(10); SDA = 1; delay(10); } /* I2C总线发送应答信号 */ void I2C_Ack() { SDA = 0; delay(5); SCL = 1; delay(5); SCL = 0; delay(5); SDA = 1; delay(5); } /* I2C总线发送非应答信号 */ void I2C_Nack() { SDA = 1; delay(5); SCL = 1; delay(5); SCL = 0; delay(5); } /* I2C总线发送一个字节 */ void I2C_SendByte(uchar dat) { uchar i; for(i=0; i<8; i++) { if(dat & 0x80) SDA = 1; else SDA = 0; delay(5); SCL = 1; delay(5); SCL = 0; dat <<= 1; } I2C_Ack(); } /* I2C总线读取一个字节 */ uchar I2C_ReadByte() { uchar i, dat = 0; SDA = 1; for(i=0; i<8; i++) { SCL = 1; delay(5); dat <<= 1; if(SDA) dat |= 0x01; SCL = 0; delay(5); } return dat; } /* AT24C02写入一个字节 */ void AT24C02_WriteByte(uint addr, uchar dat) { I2C_Start(); // 总线起始信号 I2C_SendByte(AT24C02_ADDR); // 发送设备地址和写模式命令 I2C_SendByte(addr>>8); // 发送待写入数据的高8位地址 I2C_SendByte(addr&0xFF); // 发送待写入数据的低8位地址 I2C_SendByte(dat); // 发送待写入的数据 I2C_Stop(); // 总线停止信号 delay(500); // 等待至少5ms,确保数据被写入芯片中 } /* AT24C02读取一个字节 */ uchar AT24C02_ReadByte(uint addr) { uchar dat; I2C_Start(); // 总线起始信号 I2C_SendByte(AT24C02_ADDR); // 发送设备地址和写模式命令 I2C_SendByte(addr>>8); // 发送待读数据的高8位地址 I2C_SendByte(addr&0xFF); // 发送待读数据的低8位地址 I2C_Start(); // 再次启动总线,为了切换到读模式 I2C_SendByte(AT24C02_ADDR | 0x01); // 发送设备地址和读模式命令 dat = I2C_ReadByte(); // 读取数据 I2C_Nack(); // 非应答信号 I2C_Stop(); // 总线停止信号 return dat; } /* 获取存储在AT24C02中的开机次数 */ uint GetBootCount() { uchar hi, lo; hi = AT24C02_ReadByte(0x00); lo = AT24C02_ReadByte(0x01); return (hi<<8) | lo; // 将高8位和低8位组合成一个16位数字 } /* 将开机次数写入AT24C02 */ void SetBootCount(uint count) { uchar hi, lo; hi = count >> 8; // 获取开机次数的高8位 lo = count & 0xFF; // 获取开机次数的低8位 AT24C02_WriteByte(0x00, hi); // 写入高8位 AT24C02_WriteByte(0x01, lo); // 写入低8位 } /* 主函数 */ void main() { uint boot_count = GetBootCount(); boot_count++; // 开机次数加1 SetBootCount(boot_count); // 将新的开机次数写入AT24C02 while(1) { // 程序不断循环,实时记录设备的开机次数 } }代码利用STC89C52控制芯片和AT24C02存储芯片,通过I2C总线通信协议实现了设备开机次数的记录功能。具体而言,程序读取AT24C02中存储的开机次数,将其加1并写入AT24C02中;每次开机时,程序执行该操作并将开机次数持续累加,从而实现了设备开机次数的精确、可靠记录。
-
一、项目背景本项目基于STC89C52单片机和DHT20温湿度传感器,实现了一款环境温湿度检测仪。通过传感器采集环境的温度和湿度数据,利用IIC接口的OLED显示屏显示出来,便于用户实时监测环境温湿度状态。在现代社会,人们对环境温湿度的要求越来越高。无论是工作场所还是居住环境,都需要维持一个舒适的温湿度状态,以保证身体的健康和工作效率的提高。随着科技的不断进步和物联网技术的广泛应用,环境温湿度检测仪被广泛运用于各种领域,如制造业、医疗、农业等等,成为了一种重要的环境检测设备。而本项目所涉及的STC89C52单片机和DHT20温湿度传感器作为传统的嵌入式开发技术,在实现物联网设备方面有着广泛的应用前景。通过本项目的学习和实践,可以深入了解传感器技术的原理和应用,并掌握基于单片机的嵌入式开发技术,为实现更多物联网设备的开发和应用打下基础。二、设计思路本项目的设计思路主要包括硬件和软件两个方面。【1】硬件设计思路本项目的硬件设计主要涉及到STC89C52单片机、DHT20温湿度传感器和OLED显示屏三个模块。其中,STC89C52单片机负责控制整个系统的运行,DHT20温湿度传感器用于采集环境的温湿度数据,OLED显示屏则负责将温湿度数据实时展示出来。具体的硬件设计流程如下:(1)选择合适的STC89C52单片机开发板,并根据需要添加外部电源、复位电路、晶振等元件。(2)选择合适的DHT20温湿度传感器,并根据其引脚定义将其连接到单片机的I/O口。(3)选择合适的OLED显示屏,并根据其接口定义将其连接到单片机的IIC总线上。(4)在单片机开发环境中编写程序,实现对DHT20传感器的温湿度数据读取和对OLED显示屏的控制。【2】软件设计思路本项目的软件设计主要涉及到单片机程序的编写和调试。根据硬件设计的思路,将实现对DHT20传感器的温湿度数据读取和对OLED显示屏的控制。具体的软件设计流程如下:(1)在单片机开发环境中编写程序,实现DHT20传感器的初始化、温湿度数据的读取和对OLED显示屏的控制。(2)通过串口调试助手,将DHT20传感器采集到的温湿度数据打印出来,检查程序是否正常运行。(3)连接OLED显示屏,并调试程序,实现温湿度数据的实时显示。三、设计代码【1】DHT20温湿度读取DHT20是一款数字式温湿度传感器,其采用了广受欢迎的I2C总线进行数据通信,可以方便地与各种微控制器和单片机进行连接和使用。该传感器具有高精度、低功耗、稳定性好等特点,被广泛应用于气象站、冷库、温室、恒温箱、智能家居等领域。DHT20的工作电压范围为2.1V至5.5V,并且其在测量过程中的功耗非常低,最大电流为1.3mA,平时仅需要几微安的待机电流,从而节省了能源并延长了电池寿命。该传感器采用了独特的校准技术,能够实现高精度的测量,温度测量精度为±0.2℃,湿度测量精度为±2%RH。DHT20是一款数字式温湿度传感器,其通过内部的ADC将模拟信号转换成数字信号,并使用CRC校验保证数据传输的可靠性。此外,该传感器还具有单次测量和连续测量两种模式,可以满足不同场景下的需求。DHT20传感器采用单线数字信号传输,读取数据过程中需要按照协议进行时序控制。下面是基于STC89C52单片机的DHT20温湿度数据读取代码示例,通过串口调试助手将读取到的数据打印出来: #include <reg52.h> #include <intrins.h> sbit DHT20 = P1^0; //定义DHT20连接的IO口 void delay_us(unsigned int us) //us级延时函数 { while(us--) { _nop_(); } } void DHT20_start(void) //开始信号 { DHT20 = 1; //先将数据线置高 delay_us(30); //延时30us DHT20 = 0; //拉低数据线 delay_us(25); //持续拉低25us DHT20 = 1; //释放数据线 delay_us(5); //延时5us } unsigned char DHT20_read(void) //读取一个字节的数据 { unsigned char i, dat = 0; for(i=0; i<8; i++) { while(!DHT20); //等待数据线变高 delay_us(4); //延时4us dat <<= 1; //左移一位 if(DHT20) //如果数据线为高 { dat |= 1; //在最低位写入1 while(DHT20); //等待数据线变低 } } return dat; } void main() { unsigned char humi_H, humi_L, temp_H, temp_L, check_sum; while(1) { DHT20_start(); //发送开始信号 if(!DHT20) //等待DHT20响应 { delay_us(80); if(DHT20) { delay_us(80); humi_H = DHT20_read(); //读取湿度高8位 humi_L = DHT20_read(); //读取湿度低8位 temp_H = DHT20_read(); //读取温度高8位 temp_L = DHT20_read(); //读取温度低8位 check_sum = DHT20_read(); //读取校验和 if((humi_H + humi_L + temp_H + temp_L) == check_sum) //校验和正确 { printf("湿度:%d.%d %%\r\n", humi_H, humi_L); printf("温度:%d.%d ℃\r\n", temp_H, temp_L); } } } delay_ms(5000); //延时5s再读取 } }【2】IIC接口的OLED显示屏的驱动代码0.96寸OLED(SSD1306驱动芯片)显示屏是一款常见的小型显示器件,具有高对比度、低功耗、快速响应等特点。其主要由OLED芯片和玻璃基板组成,可通过IIC或SPI接口控制,实现图形、文字、数字等内容的显示。SSD1306驱动芯片是最常用的OLED显示器驱动芯片之一,具有低功耗、高对比度、高分辨率等优点。它支持点阵图像显示、字符显示、图形显示等多种显示模式,可通过IIC/SPI接口进行通信控制,支持控制字体大小、显示位置、亮度等参数,且内置显存,方便多屏幕拼接显示。0.96寸OLED(SSD1306驱动芯片)显示屏通常采用128x64或者128x32的分辨率,显示效果清晰,可显示4行16列的字体信息。其内置控制器,占用极少的CPU资源和存储空间,适合于嵌入式系统、智能家居、手持设备等场景中使用。下面是基于STC89C52单片机控制IIC接口的0.96寸OLED(SSD1306驱动芯片)显示屏显示一个数字的详细代码: #include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int /* 定义IIC总线的SDA和SCL引脚 */ sbit SDA = P1^0; sbit SCL = P1^1; /* 定义OLED显示屏的地址,一般为0x78 */ #define OLED_ADDRESS 0x78 /* OLED显示屏的初始化命令 */ uchar init_cmd[] = { 0xAE, /* 关闭OLED显示 */ 0x00, /* 设置列地址低位 */ 0x10, /* 设置列地址高位 */ 0x40, /* 设置起始行 */ 0xB0, /* 设置页地址 */ 0x81, /* 设置对比度 */ 0xFF, /* 对比度值 */ 0xA1, /* 水平翻转 */ 0xA6, /* 正常显示 */ 0xA8, /* 设置多路复用率 */ 0x3F, /* 值越大,显示点越多,亮度越高 */ 0xC8, /* 垂直翻转 */ 0xD3, /* 设置显示偏移 */ 0x00, /* 偏移量为0 */ 0xD5, /* 设置时钟分频 */ 0x80, /* 分频值为80 */ 0xD9, /* 设置预充电周期 */ 0xF1, /* 默认值 */ 0xDA, /* 设置COM硬件配置 */ 0x12, /* 默认值 */ 0xDB, /* 设置VCOMH电压 */ 0x40, /* 默认值 */ 0x20, /* 设置内存地址模式 */ 0x00, /* 水平地址模式 */ 0xAF /* 打开OLED显示 */ }; /* IIC总线的延时函数 */ void Delay5ms() { uint i, j; for (i = 0; i < 5; i++) { for (j = 0; j < 110; j++); } } /* IIC总线的启动信号,SDA从高到低,SCL为高电平 */ void I2C_Start() { SDA = 1; SCL = 1; Delay5ms(); SDA = 0; Delay5ms(); SCL = 0; } /* IIC总线的停止信号,SDA从低到高,SCL为高电平 */ void I2C_Stop() { SDA = 0; SCL = 1; Delay5ms(); SDA = 1; Delay5ms(); } /* IIC总线的写数据函数 */ void I2C_Write(uchar dat) { uchar i; for (i = 0; i < 8; i++) { SDA = dat & 0x80; SCL = 1; Delay5ms(); SCL = 0; dat <<= 1; } } /* OLED显示屏的初始化函数 */ void OLED_Init() { uchar i; I2C_Start(); I2C_Write(OLED_ADDRESS); for (i = 0; i < sizeof(init_cmd); i++) { I2C_Write(init_cmd[i]); } I2C_Stop(); } /* OLED显示屏的写数据函数 */ void OLED_Write_Data(uchar dat) { I2C_Start(); I2C_Write(OLED_ADDRESS); I2C_Write(0x40); /* 写数据标志 */ I2C_Write(dat); I2C_Stop(); } /* OLED显示屏显示数字的函数 */ void OLED_Show_Number(uchar num) { uchar i; uint j; /* 在第1页、第5列显示数字 */ OLED_Write_Data(0xB0); OLED_Write_Data(0x00); OLED_Write_Data(0x10); for (i = 0; i < 8; i++) { OLED_Write_Data(0x00); } for (i = 0; i < 3; i++) { OLED_Write_Data(0xFF); } for (i = 0; i < 5; i++) { OLED_Write_Data(0x00); } for (i = 0; i < 3; i++) { OLED_Write_Data(0xFF); } for (j = 0; j < 5000; j++); /* 延时一段时间,让数字停留在屏幕上 */ } /* 主函数 */ void main() { /* 初始化OLED显示屏 */ OLED_Init(); /* 显示数字 */ OLED_Show_Number(5); }代码首先定义了IIC总线的SDA和SCL引脚,以及OLED显示屏的地址。然后定义了OLED显示屏的初始化命令和显示数字的函数。在主函数中调用初始化函数,并在OLED显示屏上显示数字5。
-
链表是由一连串节点组成的数据结构,每个节点包含一个数据值和一个指向下一个节点的指针。链表可以在头部和尾部插入和删除节点,因此可以在任何地方插入和删除节点,从而使其变得灵活和易于实现。链表通常用于实现有序集合,例如队列和双向链表。链表的优点是可以快速随机访问节点,而缺点是插入和删除操作相对慢一些,因为需要移动节点。此外,链表的长度通常受限于内存空间,因此当链表变得很长时,可能需要通过分页或链表分段等方式来管理其内存。下面是一套封装好的单链表框架,包括创建链表、插入节点、删除节点、修改节点、遍历节点和清空链表等常见操作,其中每个节点存储一个结构体变量,该结构体中包含一个名为data的int类型成员。 #include <stdio.h> #include <stdlib.h> // 链表节点结构体 typedef struct ListNode { int data; // 节点数据 struct ListNode *next; // 下一个节点的指针 } ListNode; // 创建一个新节点 ListNode *createNode(int data) { ListNode *node = (ListNode*) malloc(sizeof(ListNode)); node->data = data; node->next = NULL; return node; } // 在链表头部插入一个新节点 ListNode *insertNodeAtHead(ListNode *head, int data) { ListNode *node = createNode(data); node->next = head; return node; } // 在链表尾部插入一个新节点 ListNode *insertNodeAtTail(ListNode *head, int data) { ListNode *node = createNode(data); if(head == NULL) { return node; } else { ListNode *current = head; while(current->next != NULL) { current = current->next; } current->next = node; return head; } } // 删除链表中第一个值为data的节点 ListNode *deleteNode(ListNode *head, int data) { if(head == NULL) { return NULL; } if(head->data == data) { ListNode *current = head; head = head->next; free(current); return head; } ListNode *current = head; while(current->next != NULL && current->next->data != data) { current = current->next; } if(current->next != NULL) { ListNode *deleteNode = current->next; current->next = deleteNode->next; free(deleteNode); } return head; } // 修改链表中第一个值为oldData的节点的数据为newData void updateNode(ListNode *head, int oldData, int newData) { ListNode *current = head; while(current != NULL) { if(current->data == oldData) { current->data = newData; break; } else { current = current->next; } } } // 遍历链表 void traverseList(ListNode *head) { ListNode *current = head; while(current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n"); } // 清空链表,释放所有节点的内存空间 void clearList(ListNode *head) { while(head != NULL) { ListNode *current = head; head = head->next; free(current); } } // 示例程序 int main() { ListNode *head = NULL; head = insertNodeAtHead(head, 1); head = insertNodeAtHead(head, 2); head = insertNodeAtTail(head, 3); traverseList(head); head = deleteNode(head, 2); traverseList(head); updateNode(head, 1, 4); traverseList(head); clearList(head); return 0; }在上述代码中,定义了一个节点结构体ListNode,其中包含一个int类型的data成员和一个指向下一个节点的指针。接着定义了用于创建新节点、插入节点、删除节点、修改节点、遍历节点和清空链表等操作的子函数,并在main函数中演示了这些操作的使用例子。在使用完链表后一定要调用clearList函数释放内存空间。
-
一、前言在物联网、单片机开发中,经常需要采集各种传感器的数据。比如:温度、湿度、MQ2、MQ3、MQ4等等传感器数据。这些数据采集过程中可能有波动,偶尔不稳定,为了得到稳定的值,我们可以对数据多次采集,进行排序,去掉最大和最小的值,然后取平均值返回。二、排序算法【1】冒泡排序冒泡排序(Bubble Sort)是一种简单的排序算法,也是最基础、最容易理解的一种排序算法。它会遍历要排序的数组,依次比较相邻两个元素的大小,如果前一个元素比后一个元素大,就交换这两个元素的位置。冒泡排序的过程如下:从数组的第一个元素开始,依次比较相邻的两个元素,如果前一个元素比后一个元素大,则交换这两个元素的位置。继续比较相邻的元素,直到数组的最后一个元素。重复执行步骤1和步骤2,直到整个数组都按照从小到大的顺序排列好。冒泡排序的时间复杂度是O(N^2),其中N是数组中元素的数量。在实际应用中,由于其时间复杂度较高,冒泡排序很少被用于大规模数据的排序,但它仍然是一种优秀的教学工具,因为它容易理解和实现,并且可以帮助初学者理解排序算法的基本思想。以下是C语言代码的实现,封装为名为calculateAverage的函数。 #define ARRAY_SIZE 20 // 冒泡排序算法函数 void bubbleSort(int arr[], int n) { for(int i = 0; i < n-1; i++) { for(int j = 0; j < n-i-1; j++) { if(arr[j] > arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } // 计算平均值函数,去除最大值和最小值 int calculateAverage() { int arr[ARRAY_SIZE]; // 连续读取20次数据 for(int i = 0; i < ARRAY_SIZE; i++) { arr[i] = ReadADC(); } // 对数组进行排序 bubbleSort(arr, ARRAY_SIZE); // 去掉最大值和最小值 int sum = 0; for(int i = 1; i < ARRAY_SIZE-1; i++) { sum += arr[i]; } // 计算平均值并返回 return sum / (ARRAY_SIZE-2); }在函数中,首先定义了一个常量ARRAY_SIZE表示需要读取的数据的数量。然后,使用一个循环读取20次数据,并将它们存储到一个数组中。接着,用冒泡排序算法对数组进行排序。在排序完成后,计算数组中除去最大值和最小值的元素之和,并计算平均值。最后,返回计算得到的平均值。【2】插入排序插入排序(Insertion Sort)是一种简单直观的排序算法,它的基本思想是将一个元素插入到已排序好的序列中的适当位置,使得插入后仍然有序。插入排序的过程如下:假设第一个元素已经是排好序的序列,从第二个元素开始,依次将每个元素插入到已经排好序的序列中。每次从未排序的部分中取出一个元素,与已排序的序列中的元素从后向前依次比较,找到插入的位置,即找到一个比当前元素小的值或者已经到了开头位置。将当前元素插入到已排序序列的合适位置上,重新调整已排序的序列,继续对未排序的序列进行排序。重复执行步骤2和步骤3,直到整个数组都按照从小到大的顺序排列好。插入排序的时间复杂度是O(N^2),其中N是数组中元素的数量。在实际应用中,插入排序通常适用于处理小规模数据或者已经接近有序的数据,因为此时插入排序的效率高于其他排序算法。以下是C语言代码的实现,封装为名为calculateAverage的函数。 #define ARRAY_SIZE 20 // 插入排序算法函数 void insertionSort(int arr[], int n) { for(int i = 1; i < n; i++) { int key = arr[i]; int j = i-1; while(j >= 0 && arr[j] > key) { arr[j+1] = arr[j]; j--; } arr[j+1] = key; } } // 计算平均值函数,去除最大值和最小值 int calculateAverage() { int arr[ARRAY_SIZE]; // 连续读取20次数据 for(int i = 0; i < ARRAY_SIZE; i++) { arr[i] = ReadADC(); } // 对数组进行排序 insertionSort(arr, ARRAY_SIZE); // 去掉最大值和最小值 int sum = 0; for(int i = 1; i < ARRAY_SIZE-1; i++) { sum += arr[i]; } // 计算平均值并返回 return sum / (ARRAY_SIZE-2); }在函数中,首先定义了一个常量ARRAY_SIZE表示需要读取的数据的数量。然后,使用一个循环读取20次数据,并将它们存储到一个数组中。接着,用插入排序算法对数组进行排序。在排序完成后,计算数组中除去最大值和最小值的元素之和,并计算平均值。最后,返回计算得到的平均值。【3】希尔排序希尔排序(Shell Sort)是一种由Donald Shell在1959年发明的排序算法,它是插入排序的一种变体,旨在减少排序中元素的移动次数,从而使算法更快。希尔排序的基本思想是把数组中相距某个“增量”的元素组成一个子序列,对每个子序列进行插入排序,然后逐步缩小增量,重复进行上述操作,直到增量为1,最后再对整个数组进行一次插入排序。希尔排序的过程如下:选择一个增量序列,将待排序的数组按照这个增量序列分成若干组(子序列)。通常,在第一次排序时,增量取数组长度的一半,以后每次将增量减半,直到增量为1。对每个子序列进行插入排序,即将每个子序列中的元素按照递增的顺序插入到已排序好的序列中。重复执行步骤2,改变增量,直到增量为1。最后再对整个数组进行插入排序。希尔排序的时间复杂度与所选取的增量序列有关。最坏情况下的时间复杂度为O(N^2),其中N是数组中元素的数量。但在大多数情况下,希尔排序的时间复杂度优于O(N^2),可以达到O(N log N)的级别。希尔排序的空间复杂度为O(1),因为它在排序过程中只需要常数个额外的存储空间。以下是C语言代码实现,封装为名为calculateAverage的函数。 #define ARRAY_SIZE 20 // 希尔排序算法函数 void shellSort(int arr[], int n) { for(int gap = n/2; gap > 0; gap /= 2) { for(int i = gap; i < n; i++) { int temp = arr[i]; int j; for(j = i; j >= gap && arr[j-gap] > temp; j -= gap) { arr[j] = arr[j-gap]; } arr[j] = temp; } } } // 计算平均值函数,去除最大值和最小值 int calculateAverage() { int arr[ARRAY_SIZE]; // 连续读取20次数据 for(int i = 0; i < ARRAY_SIZE; i++) { arr[i] = ReadADC(); } // 对数组进行排序 shellSort(arr, ARRAY_SIZE); // 去掉最大值和最小值 int sum = 0; for(int i = 1; i < ARRAY_SIZE-1; i++) { sum += arr[i]; } // 计算平均值并返回 return sum / (ARRAY_SIZE-2); }在函数中,首先定义了一个常量ARRAY_SIZE表示需要读取的数据的数量。然后,使用一个循环读取20次数据,并将它们存储到一个数组中。接着,用希尔排序算法对数组进行排序。在排序完成后,计算数组中除去最大值和最小值的元素之和,并计算平均值。最后,返回计算得到的平均值。
-
一、项目背景介绍随着社会的不断发展,人们对于汽车的安全性要求越来越高,而倒车雷达系统就是为了增强汽车驾驶者的安全性而被广泛使用。在这种情况下,我们开发了一个基于Linux设计的倒车雷达系统,该系统可以采用迅为4412主控板,运行Linux3.5内核,使用USB摄像头、TFT真彩显示屏、超声波测距模块和蜂鸣器等硬件。二、创新点本项目的创新点包括:采用开源Linux系统:采用Linux系统,具有很好的可扩展性和灵活性,可以实现更为丰富和复杂的功能。多个模块的协同工作:本项目涉及到了摄像头模块、超声波测距模块、处理模块、告警模块和显示模块等多个模块,这些模块需要协同工作才能实现完整的功能。实现了告警功能:本项目采用了蜂鸣器来实现告警功能,根据距离调整PWM输出给出不同级别的告警声音,能够提醒驾驶者注意障碍物。三、使用技术介绍迅为4412主控板:本项目采用了迅为4412主控板,该主控板具有较高的性能和稳定性,能够满足系统的运行要求。Linux操作系统:本项目采用了开源的Linux操作系统,具有很好的可扩展性和灵活性,适合进行自定义开发。V4L2协议:V4L2协议是Linux下的一个视频设备驱动程序接口,可以方便地实现USB摄像头的图像采集。超声波测距模块:通过GPIO口与主控板相连,使用定时器产生超声波并计算与接收到回波之间的时间差,从而计算出与障碍物之间的距离。PWM输出控制:根据距离调整PWM输出给出不同级别的告警声音。TFT真彩显示屏:本项目采用了TFT真彩显示屏,能够实现高清晰度的图像显示。系统管理模块:负责管理整个系统的启动、配置和错误处理等操作。例如,可以将系统的启动脚本写在/etc/rc.local中,通过调用shell脚本来实现系统的初始化和启动。四、系统架构整个系统由以下几个模块组成:摄像头模块:负责采集车尾环境图像,并传输给处理模块。超声波测距模块:负责与障碍物之间的距离测量,并将结果传输给处理模块。处理模块:负责视频显示、距离信息的处理、告警功能的实现。告警模块:负责根据距离调整PWM输出给出不同级别的告警声音,并使用蜂鸣器输出告警信息。显示模块:负责将处理模块生成的图像显示在TFT真彩显示屏上。系统管理模块:负责管理整个系统的启动、配置和错误处理等操作。五、功能设计摄像头模块 由于Linux系统具有很好的驱动支持,因此可以直接使用V4L2协议来获取USB摄像头的图像。获取图像后,需要通过DMA方式将数据传输给处理模块进行处理。超声波测距模块 超声波测距模块可以通过GPIO口与主控板相连,使用定时器产生超声波并计算与接收到回波之间的时间差,从而计算出与障碍物之间的距离。处理模块 处理模块负责将摄像头采集到的图像和超声波测距模块测得的距离信息整合处理,并根据距离信息来控制告警模块。告警模块 告警模块根据处理模块传递过来的距离信息来控制PWM输出,并使用蜂鸣器输出告警信息。显示模块 显示模块负责将处理模块生成的图像显示在TFT真彩显示屏上,并实现显示屏的刷新和管理操作。系统管理模块 系统管理模块负责管理整个系统的启动、配置和错误处理等操作。例如,可以将系统的启动脚本写在/etc/rc.local中,通过调用shell脚本来实现系统的初始化和启动。六、摄像头图像显示应用代码 #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <unistd.h> #include <strings.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <pthread.h> #include <linux/fb.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <dirent.h> #include <stdlib.h> #include <fcntl.h> #include <poll.h> #include <linux/videodev2.h> #include <sys/time.h> #include <assert.h> #include <sys/select.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/fb.h> /* 图片的象素数据 */ typedef struct PixelDatas { int iWidth; /* 宽度: 一行有多少个象素 */ int iHeight; /* 高度: 一列有多少个象素 */ int iBpp; /* 一个象素用多少位来表示 */ int iLineBytes; /* 一行数据有多少字节 */ int iTotalBytes; /* 所有字节数 */ unsigned char *VideoBuf; //存放一帧摄像头的数据 //指向了存放摄像头数据的空间地址 }T_PixelDatas; T_PixelDatas Pixedata; //存放实际的图像数据 /* USB摄像头相关参数定义 */ struct v4l2_buffer tV4l2Buf; int iFd; int ListNum; unsigned char* pucVideBuf[4]; // 视频BUFF空间地址 void camera_pthread(void); //LCD屏相关的参数 unsigned char *lcd_mem=NULL; /*LCD的内存地址*/ struct fb_fix_screeninfo finfo; /*固定形参*/ struct fb_var_screeninfo vinfo; /*可变形参*/ void LCD_Init(void); void show_pixel(int x,int y,int color); int main(int argc ,char *argv[]) { if(argc!=2) { printf("./app /dev/videoX\n"); return -1; } LCD_Init(); //LCD屏初始化 camera_init(argv[1]); //摄像头设备初始化 //开始采集摄像头数据,并实时显示在LCD屏幕上 camera_pthread(); return 0; } //LCD屏初始化 void LCD_Init(void) { /*1.打开设备文件*/ int fd=open("/dev/fb0",O_RDWR); if(fd<0) { printf("/dev/fb0设备文件打开失败!\n"); return; } /*2. 读取LCD屏的参数*/ ioctl(fd,FBIOGET_FSCREENINFO,&finfo);//固定参数 printf("映射的长度:%d\n",finfo.smem_len); ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);//可变参数,32位 printf("分辨率:%d*%d,%d\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel); /*3. 映射LCD的地址到进程空间*/ lcd_mem=mmap(NULL,finfo.smem_len,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0); if(lcd_mem==NULL) { printf("lcd_mem映射失败!\n"); return; } memset(lcd_mem,0xFFFFFFFF,finfo.smem_len); } /*画点*/ void show_pixel(int x,int y,int color) { unsigned long *show32 = NULL; /* 定位到 LCD 屏上的位置*/ show32 =(unsigned long *)(lcd_mem + y*vinfo.xres*vinfo.bits_per_pixel/8 + x*vinfo.bits_per_pixel/8); *show32 =color; /*向指向的 LCD 地址赋数据*/ } //显示摄像头的数据 void Show_VideoData(int w,int h,unsigned char *rgb) { int i,j; int x0,x=0,y=0; int color; //颜色 值 unsigned char r,g,b; x0=x; for(i=0;i<h;i++) { for(j=0;j<w;j++) { b=rgb[0]; g=rgb[1]; r=rgb[2]; color=r<<16|g<<8|b; //合成颜色 show_pixel(x0++,y,color); //显示一个像素点 rgb+=3; } y++; x0=x; } } //摄像头设备的初始化 int camera_init(char *video) { int i=0; int cnt=0; //定义摄像头驱动的BUF的功能捕获视频 int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* 1、打开视频设备 */ iFd = open(video,O_RDWR); if(iFd < 0) { printf("摄像头设备打开失败!\n"); return 0; } struct v4l2_format tV4l2Fmt; /* 2、 VIDIOC_S_FMT 设置摄像头使用哪种格式 */ memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format)); tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获 //设置摄像头输出的图像格式 tV4l2Fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*设置输出的尺寸*/ tV4l2Fmt.fmt.pix.width = 1000; tV4l2Fmt.fmt.pix.height = 1000; tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY; //VIDIOC_S_FMT 设置摄像头的输出参数 ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); //打印摄像头实际的输出参数 printf("Support Format:%d\n",tV4l2Fmt.fmt.pix.pixelformat); printf("Support width:%d\n",tV4l2Fmt.fmt.pix.width); printf("Support height:%d\n",tV4l2Fmt.fmt.pix.height); /* 3、VIDIOC_REQBUFS 申请buffer */ /* 初始化Pixedata结构体,为转化做准备 */ Pixedata.iBpp =24; //高度 和宽度的赋值 Pixedata.iHeight = tV4l2Fmt.fmt.pix.height; Pixedata.iWidth = tV4l2Fmt.fmt.pix.width; //一行所需要的字节数 Pixedata.iLineBytes = Pixedata.iWidth*Pixedata.iBpp/8; //一帧图像的字节数 Pixedata.iTotalBytes = Pixedata.iLineBytes * Pixedata.iHeight; Pixedata.VideoBuf=malloc(Pixedata.iTotalBytes); //申请存放图片数据空间 //v412请求命令 struct v4l2_requestbuffers tV4l2ReqBuffs; memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers)); /* 分配4个buffer:实际上由VIDIOC_REQBUFS获取到的信息来决定 */ tV4l2ReqBuffs.count = 4; /*在内核空间里开辟4个空间*/ /*支持视频捕获功能*/ tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* 表示申请的缓冲是支持MMAP(内存映射) */ tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP; /* 为分配buffer做准备 */ ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs); printf("tV4l2ReqBuffs.count=%d\n",tV4l2ReqBuffs.count); for (i = 0; i < tV4l2ReqBuffs.count; i++) { memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); tV4l2Buf.index = i; // 0 1 2 3 tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /*支持视频捕获*/ tV4l2Buf.memory = V4L2_MEMORY_MMAP; /*支持内存映射*/ /* 6、VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap */ ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf); //映射空间地址 pucVideBuf[i] = mmap( /*返回用户空间的地址*/ 0, /*表示系统自己制定地址*/ tV4l2Buf.length, /*映射的长度*/ PROT_READ, /*空间数据只读*/ MAP_SHARED, /*空间支持共享*/ iFd, /*将要映射的文件描述符*/ tV4l2Buf.m.offset /*从哪个位置开始映射,表示起始位置*/ ); printf("mmap %d addr:%p\n",i,pucVideBuf[i]); } /* 4、VIDIOC_QBUF 放入队列*/ for (i = 0; i <tV4l2ReqBuffs.count; i++) { memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); tV4l2Buf.index = i; tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; tV4l2Buf.memory = V4L2_MEMORY_MMAP; ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); //放入队列 queue } /*5、启动摄像头开始读数据*/ ioctl(iFd,VIDIOC_STREAMON, &iType); return 0; } 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++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); if(z++) { z = 0; yuyv += 4; } } } /*采集摄像头数据*/ void camera_pthread(void) { int error; int cnt=0; int i=0; int ListNum; /* 8.1、等待是否有数据 */ fd_set readfds; while(1) { FD_ZERO(&readfds); FD_SET(iFd,&readfds); select(iFd+1,&readfds,NULL,NULL,NULL); /*检测文件描述符是否发生了读写事件*/ memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //类型 tV4l2Buf.memory = V4L2_MEMORY_MMAP; //存储空间类型 /* 9、VIDIOC_DQBUF 从队列中取出 */ error = ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf); //取出一帧数据 ListNum = tV4l2Buf.index; //索引编号 0 //将YUV转换为RGB yuv_to_rgb(pucVideBuf[ListNum],Pixedata.VideoBuf,Pixedata.iWidth,Pixedata.iHeight); //在LCD屏显示图像 Show_VideoData(Pixedata.iWidth,Pixedata.iHeight,Pixedata.VideoBuf); memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); tV4l2Buf.index = ListNum; tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; tV4l2Buf.memory = V4L2_MEMORY_MMAP; error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); /*将缓冲区再次放入队列*/ } } 七、超声波测距驱动代码 #include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/workqueue.h> #include <linux/timer.h> #include <linux/input.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/io.h> static int chaoshengbo_irq=0; #define GPB_CON 0x11400040 #define GPB_DAT 0x11400044 static u32 *gpb_con=NULL; static u32 *gpb_dat=NULL; /* 硬件连接: 输出脚: GPX1_0 ---ECHO 输入脚: GPB_7 */ static void tiny4412_work_func(struct work_struct *work) { printk("GPB=%d\n",*gpb_dat&1<<7); ktime_t my_time1,my_time2; unsigned int i,j; unsigned int time_cnt=0; my_time1=ktime_get(); //获取当前时间 i=ktime_to_us(my_time1); //转 us while(gpio_get_value(EXYNOS4_GPX1(0))){} //高电平卡住 my_time2=ktime_get(); //获取当前时间 j=ktime_to_us(my_time2); //转 us printk("us:%d\n",j-i); } static DECLARE_WORK(caoshengbotiny4412_work,tiny4412_work_func); /* 中断处理函数 */ irqreturn_t irq_handler_chaoshengbo(int irq, void *dev) { schedule_work(&caoshengbotiny4412_work); /*正常情况下: 是在中断服务函数里面*/ return IRQ_HANDLED; /*表示中断已经处理过了*/ } static void timer_function(unsigned long data); static DEFINE_TIMER(timer_caoshengbo, timer_function,0,0); static void timer_function(unsigned long data) { static u8 state; state=!state; if(state)*gpb_dat|=1<<7; else *gpb_dat&=~(1<<7); mod_timer(&timer_caoshengbo,jiffies+msecs_to_jiffies(500)); } static int __init tiny4412_chaoshengbo_init(void) { int i,err; /*获取中断号*/ chaoshengbo_irq=gpio_to_irq(EXYNOS4_GPX1(0)); printk("中断号:%d\n",chaoshengbo_irq); /*外部中断注册*/ err=request_irq(chaoshengbo_irq,irq_handler_chaoshengbo,IRQ_TYPE_EDGE_RISING,"tiny4412_chaoshengbo",NULL); if(err!=0)printk("中断注册失败!\n"); /*映射内存*/ gpb_con=ioremap(GPB_CON,4); gpb_dat=ioremap(GPB_DAT,4); /*配置模式*/ *gpb_con&=~(0xF<<4*7); //输出模式 *gpb_con|=0x1<<4*7; mod_timer(&timer_caoshengbo,jiffies+msecs_to_jiffies(100)); return 0; } static void __exit tiny4412_chaoshengbo_exit(void) { free_irq(chaoshengbo_irq,NULL); iounmap(gpb_con); iounmap(gpb_dat); del_timer(&timer_caoshengbo); } module_init(tiny4412_chaoshengbo_init); /*指定驱动的入口函数*/ module_exit(tiny4412_chaoshengbo_exit); /*指定驱动的出口函数*/ MODULE_LICENSE("GPL"); /*指定驱动许可证*/
-
一、项目背景智慧农业是近年来发展迅速的领域,其目的是利用先进的传感技术、物联网技术和云计算技术等,实现自动化、智能化的农业生产管理,并提高农业生产效率和质量。本文基于CC2530设计了一种智慧农业控制系统,采用DHT11模块、BH1750模块和土壤湿度传感器等传感器,通过串口协议将采集的数据上传给上位机显示。二、系统框架本系统主要包括一下的组成部分:【1】采集端:由CC2530单片机和各种传感器构成,负责测量环境温湿度、环境光照强度和土壤湿度等信息,并通过串口协议将采集的数据上传给上位机显示。【2】上位机:采用Qt进行开发,负责接收串口上传的数据并进行显示。【3】传输介质:采用串口协议进行数据传输,支持异步通信,具有数据帧结构。【4】传感器模块:包括DHT11模块、BH1750模块和土壤湿度传感器等,通过采集环境温湿度、环境光照强度和土壤湿度等信息,实现对农业生产环境的监测和控制。CC2530是德州仪器(Texas Instruments)推出的一款低功耗无线系统芯片,它是基于ZigBee协议的SoC系统,内置了ARM Cortex-M3处理器和IEEE 802.15.4标准无线通信模块,可以实现低速率的无线数据传输和网络互连。CC2530芯片的主要特点是低功耗、高可靠性、灵活性强、易于集成和开发,被广泛应用于物联网、智能家居、智能电表、智能照明等领域的无线数据传输和控制系统中。BH1750是一款数字式环境光传感器,可用于测量光强度。它具有高分辨率和灵敏度,并且与普通光敏电阻相比,具有更广泛的动态范围和线性性。BH1750可以通过I2C接口连接到微控制器或其他电子设备上,如Arduino、树莓派等。它被广泛应用于夜间照明系统、自动控制系统、安防监控等领域。DHT11是一种数字温湿度传感器,可以测量环境的温度和相对湿度。它通常被用于家庭和工业自动化等领域,可以在各种应用中监测环境条件。DHT11有四个引脚:VCC(电源正极)、GND(地)、DATA(数据信号)和NC(未使用)。它可以通过单一总线接口与微控制器连接,并以数字形式输出温度和湿度值。其温度测量范围为0℃至50℃,湿度测量范围为20%RH至90%RH。在使用DHT11传感器时,需要注意一些问题。例如,在读取数据之前,应将传感器加电并等待1至2秒钟,以使其稳定。此外,在读取数据后,还需要进行数据校验,以确保数据的准确性。三、系统设计【1】采集端设计采集端主要由CC2530单片机和各种传感器构成。其中,温湿度采用DHT11模块,光照强度采用BH1750模块,土壤湿度采用土壤湿度传感器。通过采集这些信息,可以了解农田的环境状态,并根据需要进行相应的调节和控制,保证作物的正常生长。【2】上位机设计上位机采用QT进行开发,支持串口通信和数据显示。在数据传输端口的配置上,串口通信采用异步通信方式,支持数据帧结构,即每个数据包由起始位、数据位、校验码、停止位等几部分组成,以确保数据传输的稳定性和可靠性。同时,上位机还实现了数据的动态显示和历史数据的查询导出功能,以方便用户对农田环境数据进行分析和处理。四、上位机源码实现以下是Qt串口读取数据的实现代码: #include <QCoreApplication> #include <QtSerialPort/QSerialPort> #include <QtSerialPort/QSerialPortInfo> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QSerialPort serial; serial.setPortName("COM1"); // 根据实际情况设置端口号 if (!serial.open(QIODevice::ReadWrite)) { // 打开串口 qDebug() << "Failed to open serial port!"; return 1; } // 设置串口参数 serial.setBaudRate(QSerialPort::Baud115200); serial.setDataBits(QSerialPort::Data8); serial.setParity(QSerialPort::NoParity); serial.setStopBits(QSerialPort::OneStop); serial.setFlowControl(QSerialPort::NoFlowControl); while (true) { if (serial.waitForReadyRead(1000)) { // 等待数据 QByteArray data = serial.readAll(); // 读取所有数据 qDebug() << "Received data:" << data; // 打印所有数据 // 解析数据 QStringList dataList = QString(data).split(","); if (dataList.size() == 4) { float temperature = dataList[0].toFloat(); float humidity = dataList[1].toFloat(); float illumination = dataList[2].toFloat(); float soilMoisture = dataList[3].toFloat(); // 打印解析后的数据 qDebug() << "Temperature:" << temperature << "°C"; qDebug() << "Humidity:" << humidity << "%"; qDebug() << "Illumination:" << illumination << "lux"; qDebug() << "Soil Moisture:" << soilMoisture << "%"; } } } return a.exec(); }读取数据时,采用了waitForReadyRead函数等待串口数据到达,该函数的参数表示最长等待时间,单位为毫秒。在解析数据时,使用了QString的split函数将数据按逗号分隔为多个字符串,再分别转换为对应的浮点数。五、CC2530设备端源码【1】BH1750数据读取以下是CC2530单片机读取BH1750光敏传感器的值并打印到串口的代码: #include "hal_board_cfg.h" #include "hal_types.h" #include "hal_defs.h" #include "hal_uart.h" #include "onboard.h" #include "hal_i2c.h" #define BH1750_ADDR 0x23 // BH1750默认地址 void initUART(); void sendStr(char *str); void BH1750_init(); uint16 BH1750_read(); void main() { // 初始化 halBoardInit(); initUART(); BH1750_init(); while (TRUE) { // 读取传感器数据 uint16 illumination = BH1750_read(); char str[16]; sprintf(str, "%d", illumination); // 将数据打印到串口 sendStr(str); // 延时等待1秒 halMcuWaitMs(1000); } } void initUART() { HAL_UART_CFG_T uartCfg; uartCfg.baudRate = HAL_UART_BR_115200; uartCfg.flowControl = FALSE; uartCfg.parity = HAL_UART_PARITY_NONE; uartCfg.stopBits = HAL_UART_STOP_BITS_1; uartCfg.startGuardTime = 0; HalUARTInit(); HalUARTOpen(HAL_UART_PORT_0, &uartCfg); } void sendStr(char *str) { while (*str != '\0') { HalUARTWrite(HAL_UART_PORT_0, (uint8*)str, 1); str++; } HalUARTWrite(HAL_UART_PORT_0, (uint8*)"\r\n", 2); } void BH1750_init() { uint8 pBuf[2]; pBuf[0] = 0x01; // 开始测量命令 halI2CWrite(BH1750_ADDR, pBuf, 1); pBuf[0] = 0x10; // 分辨率设置为1lx halI2CWrite(BH1750_ADDR, pBuf, 1); } uint16 BH1750_read() { uint8 pBuf[2]; halI2CRead(BH1750_ADDR, pBuf, 2); uint16 illumination = (pBuf[0] << 8) | pBuf[1]; return illumination; }上述代码中,initUART函数用于初始化串口,sendStr函数用于将字符串打印到串口。BH1750_init函数用于初始化BH1750传感器,包括发送开始测量指令和设置分辨率为1lx等操作。BH1750_read函数用于读取传感器数据并计算出光照强度值。在main函数中,使用了一个while循环不断从传感器读取数据,并通过串口打印输出。代码中的延时函数halMcuWaitMs是CC2530提供的延时函数,可以使用其他方式实现延时等待功能。【2】DHT11温湿度数据读取以下是CC2530单片机读取DHT11传感器的温度和湿度并打印到串口的代码: #include "hal_types.h" #include "hal_board.h" #include "hal_uart.h" #define DHT11_PORT 1 // DHT11连接到P1口 // 发送一个DHT11开始信号 void DHT11_Start(void) { // 设置引脚为输出模式 P1SEL &= ~(1 << DHT11_PORT); P1DIR |= (1 << DHT11_PORT); // 拉低引脚 P1_1 = 0; // 等待至少18ms HalDelayMs(18); // 拉高引脚 P1_1 = 1; // 等待20~40us,并切换到输入模式 HalDelayUs(30); P1DIR &= ~(1 << DHT11_PORT); } // 等待DHT11响应信号 uint8 DHT11_WaitResponse(void) { uint8 timeOut = 0; while(P1_1 && timeOut < 200) { // 等待低电平出现,超时返回1 HalDelayUs(1); timeOut++; } if(timeOut >= 200) return 1; timeOut = 0; while(!P1_1 && timeOut < 200) { // 等待高电平出现,超时返回1 HalDelayUs(1); timeOut++; } if(timeOut >= 200) return 1; return 0; } // 读取一个位 uint8 DHT11_ReadBit(void) { uint8 timeOut = 0; while(P1_1 && timeOut < 200) { // 等待低电平出现,超时返回1 HalDelayUs(1); timeOut++; } timeOut = 0; while(!P1_1 && timeOut < 200) { // 等待高电平出现,超时返回1 HalDelayUs(1); timeOut++; } HalDelayUs(40); // 等待40us if(P1_1) return 1; // 如果在40us内未出现低电平,返回1,表示数据错误 return 0; } // 读取一个字节 uint8 DHT11_ReadByte(void) { uint8 byte = 0; uint8 i; for(i=0;i<8;i++) { byte <<= 1; byte |= DHT11_ReadBit(); } return byte; } // 从DHT11读取温度和湿度数据 void DHT11_ReadData(uint8* temperature, uint8* humidity) { uint8 data[5]; // 发送开始信号 DHT11_Start(); // 等待响应信号 if(DHT11_WaitResponse()) { *temperature = 0; *humidity = 0; return; } // 读取数据 data[0] = DHT11_ReadByte(); // 湿度整数部分 data[1] = DHT11_ReadByte(); // 湿度小数部分 data[2] = DHT11_ReadByte(); // 温度整数部分 data[3] = DHT11_ReadByte(); // 温度小数部分 data[4] = DHT11_ReadByte(); // 校验和 // 计算校验和 uint8 sum = data[0] + data[1] + data[2] + data[3]; if(sum != data[4]) { *temperature = 0; *humidity = 0; return; } // 计算温度和湿度 *humidity = data[0]; *temperature = data[2]; } // 初始化串口 void UART_Init(void) { HalUARTInit(); HalUARTCfg_t uartConfig; uartConfig.configured = TRUE; uartConfig.baudRate = HAL_UART_BR_115200; uartConfig.flowControl = FALSE; uartConfig.flowControlThreshold = 64; // 设置串口传输格式 uartConfig.rx.maxBufSize = 128; uartConfig.tx.maxBufSize = 128; uartConfig.idleTimeout = 6; uartConfig.intEnable = TRUE; uartConfig.callBackFunc = NULL; HalUARTOpen(HAL_UART_PORT_0, &uartConfig); } // 打印温度和湿度到串口 void PrintData(uint8 temperature, uint8 humidity) { char buf[32]; sprintf(buf, "Temperature: %dC, Humidity: %d%%\r\n", temperature, humidity); HalUARTWrite(HAL_UART_PORT_0, (uint8*)buf, strlen(buf)); } void main(void) { uint8 temperature, humidity; // 初始化串口 UART_Init(); while(1) { // 从DHT11读取数据 DHT11_ReadData(&temperature, &humidity); // 打印数据到串口 PrintData(temperature, humidity); // 等待1秒钟 HalDelayMs(1000); } }这段代码使用CC2530单片机通过DHT11传感器读取环境温度和湿度,并将其打印到串口。具体实现过程为,首先发送一个开始信号,等待DHT11响应信号后,依次读取湿度整数、湿度小数、温度整数、温度小数和校验和。判断校验和是否正确后,计算得到温度和湿度,并通过串口输出。为了保证数据的准确性,每次读取数据前需要等待1秒钟。
-
一、案例介绍下面是一个基于CC2530和ESP8266的项目示例,它演示了如何使用CC2530与ESP8266通信以及使用AT指令控制其WiFi模块设置和数据传输。项目概述: 项目实现通过CC2530控制ESP8266将其配置成AP+TCP服务器模式,并通过手机APP连接到TCP服务器并完成数据传输。ESP8266将作为一个热点(AP)来工作,其WiFi模块被配置为建立一个TCP服务器并监听端口号。CC2530将使用其串口与ESP8266进行通信,并通过AT指令控制ESP8266的WiFi模块设置和数据传输。硬件组件:CC2530芯片ESP8266 WiFi模块USB转TTL串口转接板Android手机软件组件:IAR Embedded Workbench for 8051ESP8266 AT指令集Android Studio实现步骤:硬件连接: 将ESP8266模块与USB转TTL串口转接板相连,然后将串口转接板连接到PC上的USB接口。 在开发板上焊接CC2530,然后将其连接到ESP8266模块的TXD和RXD引脚上(即CC2530的P0.2与P0.3引脚,分别连接到ESP8266的RXD和TXD引脚)。配置ESP8266模块: 使用串口工具连接到ESP8266模块,然后根据AT指令集将其配置为AP+TCP服务器模式。例如,可以使用以下AT指令来配置ESP8266的WiFi模块: AT+CWMODE=3 AT+CWSAP="MyWiFi","12345678",1,0 AT+CIPMUX=1 AT+CIPSERVER=1,8080其中,“MyWiFi”和“12345678”分别是热点的名称和密码,“1”表示加密方式为WPA2-PSK,“0”表示不隐藏SSID,而“8080”则是TCP服务器监听的端口号。编写CC2530程序: 在IAR Embedded Workbench for 8051中创建一个新的工程,在其中添加串口驱动程序以及ESP8266通信所需的AT指令函数。然后,编写主程序代码来实现以下功能:初始化串口向ESP8266发送AT指令以配置其WiFi模块等待ESP8266向CC2530发送TCP连接请求接受从ESP8266传回的数据并将其显示在串口工具中以下是示例代码的一部分,用于初始化串口并向ESP8266发送AT指令: #include "uart.h" #include <string.h> // AT指令函数 void at_command(char* cmd) { uart_puts(cmd); uart_puts("\r\n"); delay_ms(1000); } int main() { // 初始化串口 uart_init(); // 发送AT指令以配置ESP8266的WiFi模块 at_command("AT+CWMODE=3"); at_command("AT+CWSAP="MyWiFi","12345678",1,0"); at_command("AT+CIPMUX=1"); at_command("AT+CIPSERVER=1,8080"); while (1) { // 接受从ESP8266传回的数据并将其显示在串口工具中 if (uart_available()) { char c = uart_read(); uart_putc(c); } } return 0; }二、CC2530与ESP8266科普CC2530是德州仪器(Texas Instruments,简称TI)公司推出的一款基于ZigBee协议的SoC单芯片解决方案,它集成了一个8051内核、硬件AES加密加速器、具备丰富外设的低功耗射频芯片和物理层。CC2530支持IEEE 802.15.4标准和ZigBee协议,并且具有低功耗、高可靠性和长距离等特点,广泛应用于物联网、智能家居、智能医疗、无线传感网和工业自动化等领域。ESP8266是一款由中国企业乐鑫(Espressif Systems)研发的超低功耗Wi-Fi芯片,被广泛应用于物联网相关设备的开发中。该芯片采用Tensilica L106 32位处理器,内置TCP/IP协议,可以实现Wi-Fi通信,同时也支持传统的UART协议、SPI协议等串行通信方式。ESP8266芯片集成了射频电路、天线、Flash存储器和电源管理等,体积小巧、功耗低,具有高度集成性和低成本的特点。ESP8266芯片的主要特点如下: 1. 支持802.11 b/g/n Wi-Fi协议,通信距离远,数据传输速度快; 2. 内置32位低功耗Tensilica L106 CPU,主频可达80MHz; 3. 支持UART、SPI、I2C等多种串行通信协议; 4. 集成了高速缓存和SRAM,具有强大的处理性能和存储能力; 5. 支持蓝牙4.2、BLE等无线通信协议(部分型号支持); 6. 能够与各种MCU和传感器等外设进行协同工作,大大降低了开发成本和门槛。ESP8266芯片具有成本低、功耗低、尺寸小和易于开发等优点,在物联网、智能家居、智能手环、智能家电等领域广泛应用。同时,ESP8266芯片也被视为低功耗Wi-Fi IoT领域中的杀手锏,为物联网设备的互联提供了更为简便、稳定、高效的解决方案。三、功能代码实现介绍在CC2530上实现控制ESP8266配置成AP+TCP服务器模式,与手机APP之间完成数据传输,需要使用CC2530的串口与ESP8266通信,以及使用ESP8266 AT指令控制ESP8266的WiFi模块设置和数据的发送,代码如下: #include <stdio.h> #include <stdint.h> #include <string.h> #define ESP_ON P1_0 // ESP8266电源控制引脚 #define ESP_RST P1_1 // ESP8266复位引脚 #define UART_TX P0_2 // CC2530串口发送引脚 #define UART_RX P0_3 // CC2530串口接收引脚 // ESP8266 AT指令常用指令 const char* AT_RST = "AT+RST"; const char* AT_CWMODE = "AT+CWMODE=3"; // 设置ESP8266为AP+STA模式 const char* AT_CWSAP = "AT+CWSAP="ssid","pass",1,3"; // 设置ESP8266 AP模式下的WiFi名称和密码 const char* AT_CIPMUX = "AT+CIPMUX=1"; // 设置ESP8266多路连接模式 const char* AT_CIPSERVER = "AT+CIPSERVER=1,8888"; // 设置ESP8266 TCP服务器端口 const char* AT_CIPSEND = "AT+CIPSEND=0,50"; // 设定ESP8266发送数据的长度,50字节 // ESP8266 AT指令回应标志 const char* RESPONSE_OK = "OK"; // AT指令执行成功 const char* RESPONSE_ERROR = "ERROR"; // AT指令执行失败 const char* RESPONSE_READY = "ready"; // ESP8266已经准备就绪 const char* RESPONSE_CONNECT = "CONNECT"; // ESP8266连接成功 const char* RESPONSE_CLOSED = "CLOSED"; // ESP8266连接关闭 // ESP8266的WIFI名称和密码 const char* SSID = "esp8266"; const char* PASSWORD = "wifipassword"; // 存储ESP8266返回的数据 char response[100]; // 延时函数 void delay(int ms) { while (--ms > 0) __delay_cycles(48000); } // 向ESP8266发送AT指令,并获取ESP8266的回应 void sendATCommand(const char* cmd, uint8_t wait) { uint8_t i = 0; memset(response, 0, sizeof(response)); printf("AT command: %s\n", cmd); printf("AT response:\n"); while (cmd[i]) { while (!(UCA0IFG & UCTXIFG)); UCA0TXBUF = cmd[i++]; } while (wait && !(UCA0IFG & UCRXIFG)); while (UCA0IFG & UCRXIFG) { if (response[0] == '\0' && UCA0RXBUF != '\r' && UCA0RXBUF != '\n') { response[0] = UCA0RXBUF; response[1] = '\0'; continue; } if (strlen(response) < sizeof(response) - 1) { int len = strlen(response); response[len] = UCA0RXBUF; response[len + 1] = '\0'; } } printf("%s", response); } void main(void) { uint8_t retry = 5; _BIS_SR(GIE); // 配置IO口 P1DIR |= BIT0 + BIT1; P1OUT &= ~(BIT0 + BIT1); P1OUT |= ESP_ON; // 打开ESP8266电源 P1OUT &= ~ESP_RST; // 复位ESP8266 delay(500); P1OUT |= ESP_RST; delay(1000); // 配置串口 P0SEL |= BIT2 + BIT3; UCA0CTL1 = UCSSEL_2; UCA0BR0 = 130; UCA0BR1 = 6; UCA0MCTL = UCBRS_4; // 逐步执行AT指令,确保每一步配置都执行成功 while (retry-- > 0) { sendATCommand(AT_RST, 1); sendATCommand(AT_CWMODE, 1); sendATCommand(AT_CWSAP, 1); sendATCommand(AT_CIPMUX, 1); sendATCommand(AT_CIPSERVER, 1); if (strstr(response, RESPONSE_OK) != NULL) break; } if (retry == 0) return; // 配置失败,退出程序 // 等待ESP8266准备就绪 while (1) { sendATCommand("", 1); if (strstr(response, RESPONSE_READY) != NULL) break; delay(500); } // 等待手机APP连接 while (1) { sendATCommand("", 1); if (strstr(response, RESPONSE_CONNECT) != NULL) { printf("Client connected.\n"); // 发送数据 sendATCommand(AT_CIPSEND, 0); while (!(UCA0IFG & UCTXIFG)); UCA0TXBUF = 'H'; UCA0TXBUF = 'e'; UCA0TXBUF = 'l'; UCA0TXBUF = 'l'; UCA0TXBUF = 'o'; UCA0TXBUF = ','; UCA0TXBUF = 'W'; UCA0TXBUF = 'o'; UCA0TXBUF = 'r'; UCA0TXBUF = 'l'; UCA0TXBUF = 'd'; UCA0TXBUF = '!'; delay(100); // 延时确保数据发送完成 // 关闭连接 sendATCommand("AT+CIPCLOSE=0", 1); printf("Client disconnected.\n"); } delay(10); } }这是CC2530的代码,其中ESP8266的控制使用了AT指令。也就是说,ESP8266作为网络模块,只是负责在指定的端口上监听客户端的连接和传输数据,真正控制数据传输的是CC2530,CC2530还要负责ESP8266的WiFi模块设置和TCP服务器的建立。这里只是给出了用CC2530控制ESP8266的代码,手机APP的代码需要自行开发。
-
一、项目背景随着智能化的迅速发展,人们对于生活中的各类设备也越来越有智能化的需求,其中智能饮水机是一种比较常见的设备。智能饮水机不仅可以提供饮用水,还可以通过智能化的技术满足人们对于水质、水温、出水量等方面的需求。因此,当前设计了一种基于STM32的智能饮水机系统,以满足人们对智能化饮水机的需求。智能饮水机系统其主要功能包括:【1】控制加热芯片:通过继电器模块控制加热芯片,在水烧开后自动断电。【2】液位感应:使用液位传感器感应水箱水位,当水位过低时通过语音模块进行播报提示。【3】移动端控制:Android手机端可以显示当前双水箱内的水温,设置出水温度及出水量,并且还可以控制出水操作。【4】主控芯片:采用STM32F103RCT6主控芯片,这款芯片有着强劲的处理能力和丰富的外设资源,可以满足饮水机系统的控制需求。【5】WIFI通信:选择ESP8266与手机端通信,可以实现远程控制。【6】水温测量:采用DS18B20实现水温测量,能够准确地测量水温。【7】出水开关控制:采用SG90电机实现出水开关控制,可以精准地控制出水量。【8】本地有2个指示灯,绿色和红色灯。可以表示加热状态。二、系统硬件设计【1】系统核心芯片选择STM32F103RCT6作为本系统的主控芯片,其具有较高的计算速度和稳定性,在众多STM32系列中也是使用比较广泛的型号之一。【2】温度测量模块温度测量采用DS18B20数字温度传感器,通过单总线协议与主控芯片进行通信,实现对水温的精准测量。【3】液位检测模块液位检测采用液位传感器,通过测量水箱内水位来判断是否需要进行添加水操作。【4】控制加热芯片模块继电器模块负责控制加热芯片,当水烧开后自动断电,以确保水的安全。【5】出水操作模块出水操作通过SG90电机实现,其可以控制水龙头的开关,实现出水的自动控制。【6】WIFI通信模块ESP8266作为WIFI模块,与手机端进行通信,实现了智能饮水机系统的远程操控和监测。三、系统软件设计【1】温度测量与显示模块STM32芯片通过单总线协议与DS18B20传感器进行通信,获取当前水温数据,并将其通过LCD1602液晶显示屏展示在饮水机面板上。【2】液位检测模块液位传感器负责检测水箱内水位情况,并将水位数据传递给主控芯片。当水位过低时,系统会通过语音提示模块向用户发出添加水的提醒。【3】控制加热芯片模块主控芯片通过继电器模块控制加热芯片的开关,在水烧开后自动断电,以保证水的安全性。【4】出水操作模块出水操作通过SG90电机控制,实现了对饮水机出水的自动控制。同时,在Android手机端,用户可以设置出水温度和出水量,使得出水操作更加便捷。【5】WIFI通信模块系统通过ESP8266与Android手机端进行通信,实现了智能饮水机系统的远程操控和监测功能。用户可以通过手机端查看当前双水箱内的水温并进行相应的操作。ESP8266配置成AP+TCP服务器模式,开启WIFI热点等待手机连接,手机连接之后使用TCP客户端模式连接饮水机完成数据通信。四、核心代码4.1 SG90控制代码SG90电机是一种小型舵机,用于模型航空、船模、车模和机器人等小型机械装置中,可以控制舵、飞控等运动部件的转动角度。其最大扭矩为1.6kg/cm(4.8V时),转速为0.12秒/60度(4.8V时),工作电压为4.8V~6V。SG90电机采用三线接口,其中红色接VCC(正极)、棕色接GND(负极)、橙色接PWM信号线,可以通过控制器的PWM信号控制电机的角度。以下是使用延时模拟PWM波形控制SG90电机旋转并封装成子函数的示例代码: cCopy Code#include "stm32f10x.h" #define SG90_PIN GPIO_Pin_5 #define SG90_PORT GPIOB void SG90_rotate(uint8_t angle); int main(void) { // 初始化GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置PB5为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = SG90_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SG90_PORT, &GPIO_InitStructure); while (1) { SG90_rotate(0); // 转到0度 delay_ms(1000); SG90_rotate(90); // 转到90度 delay_ms(1000); SG90_rotate(180); // 转到180度 delay_ms(1000); } } void SG90_rotate(uint8_t angle) { // 计算PWM波形高电平持续时间 uint32_t high_time = 500 + angle * 11.11; // 发送PWM波形 GPIO_SetBits(SG90_PORT, SG90_PIN); delay_us(high_time); GPIO_ResetBits(SG90_PORT, SG90_PIN); delay_us(20000 - high_time); }在上面的代码中,将SG90电机控制引脚连接到了STM32F103的PB5口,并通过计算PWM波形高电平持续时间来控制电机旋转角度。使用了SG90_rotate子函数来实现控制过程。当调用SG90_rotate函数并传入目标旋转角度时,函数会自动计算出对应的PWM波形高电平持续时间,并发送PWM波形来控制电机旋转到指定角度。使用了delay_ms和delay_us这两个函数来实现延时操作。4.2 DS18B20温度传感器DS18B20是一种数字温度传感器,它可以直接测量环境温度并转换为数字信号输出。DS18B20温度传感器采用一线式总线接口(也叫单总线接口),具有精度高、抗干扰能力强、可靠性高和使用方便等优点。DS18B20温度传感器的测量范围为-55℃~+125℃,精度为±0.5℃。传感器内置了温度补偿电路,可以自动补偿温度影响导致的测量误差。DS18B20温度传感器有多种封装形式,包括TO-92封装、SOIC封装和TO-263封装。其中TO-92封装是最常见的,也最容易使用,它的引脚分别为GND(负极)、DQ(数据线)和VDD(正极)。传感器可以通过单总线接口连接控制器,控制器通过发送指令读取传感器的数据。以下是接口函数的代码示例: #include "stm32f103xb.h" #include <stdint.h> #define DS18B20_GPIO_Port GPIOB #define DS18B20_GPIO_Pin GPIO_PIN_6 void delay_us(uint16_t us) { uint16_t i; for(i=0; i<us*8; i++); } void DS18B20_Init(void) { // 设置PB6为输出模式 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DS18B20_GPIO_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DS18B20_GPIO_Port, &GPIO_InitStruct); // 拉低总线500us-1000us复位DS18B20 HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_RESET); delay_us(600); HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_SET); delay_us(60); // 等待DS18B20拉低总线告知存在 while(HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin) == GPIO_PIN_SET); delay_us(240); // 发送SKIP ROM指令(跳过ROM应答) HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_SET); delay_us(60); // 等待DS18B20转换完成 while(HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin) == GPIO_PIN_SET); } float DS18B20_ReadTemperature(void) { float temperature = 0; // 发送START CONVERT指令(启动转换) HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_SET); delay_us(60); // 等待DS18B20转换完成 while(HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin) == GPIO_PIN_SET); // 发送READ SCRATCHPAD指令(读取温度值) HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_SET); delay_us(60); // 读取温度值 uint8_t data[9] = {0}; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin, GPIO_PIN_SET); delay_us(10); data[i] |= (HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_GPIO_Pin) << j); delay_us(50); } } // 计算温度值 int16_t rawTemperature = (data[1] << 8) | data[0]; if (rawTemperature & 0x8000) { rawTemperature = ~rawTemperature + 1; temperature = (float)rawTemperature * -0.0625; } else { temperature = (float)rawTemperature * 0.0625; } return temperature; }调用方式: DS18B20_Init(); // 初始化 float temperature = DS18B20_ReadTemperature(); // 读取温度值五、总结本项目是基于STM32的智能饮水机系统设计,实现了自动断电、液位感应、语音提示、手机远程控制等功能。其中,STM32主控芯片选择STM32F103RCT6,WIFI选择ESP8266与手机端通信,水温测量采用DS18B20,出水开关控制采用SG90电机实现。通过继电器模块控制加热芯片,在水烧开后自动断电,避免了过度烧水和安全隐患。同时,利用液位传感器感应水箱水位,当水位过低时通过语音模块进行播报提示,提醒用户及时加水。在Android手机端,用户可以方便地查看当前双水箱内的水温,设置出水温度及出水量,并控制出水操作。这极大地提高了用户的使用体验和方便性。本项目具有实用性和创新性,不仅满足了用户对智能化、便捷化的需求,也展示了STM32等技术在智能家居领域的应用前景。
-
要在 C++ 代码中控制 QML 中的 WebView 模块的显示和隐藏,可以使用信号和槽(signals and slots)机制来实现。首先,在 QML 中为 WebView 添加一个 visible 属性,并将其绑定到一个 C++ 的槽函数,如下所示:import QtWebView 1.1 WebView { id: myWebView visible: webViewVisible // 绑定 visible 属性到 C++ 槽函数 }然后,在 C++ 代码中,创建一个带有 Q_PROPERTY 的类,用于控制 WebView 的可见性。例如:class WebViewManager : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) public: explicit WebViewManager(QObject *parent = nullptr) : QObject(parent) , m_webView(new QQuickWidget) { // 设置 QQuickWidget 的属性... } bool isVisible() const { return m_visible; } public slots: void setVisible(bool visible) { if (m_visible != visible) { m_visible = visible; emit visibilityChanged(m_visible); } } signals: void visibilityChanged(bool visible); private: QQuickWidget *m_webView; bool m_visible = true; };在上述代码中,WebViewManager 类包含一个 visible 属性,以及相应的读写方法和通知信号。在 setVisible() 槽函数中,我们检查传入的 visible 参数是否与当前的可见性状态不同,如果是,则更新状态并发出 visibilityChanged 信号。最后,在应用程序的其他部分,可以创建一个 WebViewManager 的实例,并将其绑定到 QML 中的 WebView 模块:/ 创建 WebViewManager 实例... WebViewManager webViewManager; // 将 WebViewManager 实例绑定到 QML 中的 WebView 模块 QQmlEngine engine; QQmlComponent component(&engine, "myqml.qml"); QQuickItem *item = qobject_cast<QQuickItem*>(component.create()); QObject::connect(&webViewManager, &WebViewManager::visibilityChanged, item, [item](bool visible) { item->setProperty("webViewVisible", visible); }); // 显示 QML 界面...在上述代码中,首先创建了一个 WebViewManager 的实例,并将其连接到 QML 中的 WebView 模块。然后使用 QQmlComponent 类加载 QML 文件,并获取 QQuickItem 对象,该对象代表我们在 QML 文件中创建的 WebView。最后,通过 connect() 函数将 visibilityChanged 信号与 QML 中的 webViewVisible 属性绑定起来,从而控制 WebView 的可见性。
-
一、项目背景随着科技的发展和生活水平的提高,人们对于购物体验的要求越来越高。传统的商场、超市购物方式已经无法满足消费者的需求,因此无人售货机应运而生。本文针对现有售货机存在的缺陷,设计了一款基于STM32的无人售货机系统。该系统采用STM32作为主控芯片,使用液晶屏显示各种商品库存与售价,用户按下对应按键选择购买指定商品,在矩阵键盘输入账号密码付款。若付款成功,对应电机旋转一定角度使商品出库,同时修改库存;若余额不足,则进行声光提示。手机端还可查看消费流水、商品库存情况,并进行补货和充值操作。二、系统设计2.1 系统硬件设计该系统的核心部件是STM32主控芯片,它负责整个售货机的控制和管理。液晶屏用于显示商品信息、价格等,矩阵键盘用于用户输入账号密码进行支付。电机控制板用于控制商品出库。硬件组成:主控芯片选:STM32F103ZET6 液晶屏选择:2.8寸TFT-LCD屏 WIFI选择:ESP8266-WIFI 与手机APP之间通信。模式配置为STA模块。连接服务器。 电机旋转角度:28BYJ48步进电机。 控制出货机出货物。 矩阵键盘:4X4的矩阵键盘。2.2 系统软件设计软件部分主要包括STM32程序和手机APP程序。STM32程序是售货机的核心程序,负责控制各个部件的工作,实现售货机的基本功能。APP程序可以通过与STM32通信来实现商品库存查看、补货、充值等功能。STM32部分主要分为以下几个模块:(1)初始化模块:初始化各个部件的工作状态和参数。 (2)商品选择模块:根据用户按下的按钮,选择相应的商品。 (3)支付模块:通过矩阵键盘输入账号密码进行支付,并根据支付结果控制电机的工作状态。 (4)库存管理模块:根据商品销售情况,实时更新商品库存信息。 (5)声光提示模块:在用户付款失败或余额不足时,通过蜂鸣器和LED灯进行声光提示。手机APP程序主要分为以下几个模块:(1)用户登录模块:用户可以通过输入账号密码登录APP。 (2)商品查看模块:用户可以查看售货机内商品库存情况。 (3)补货模块:商家可以通过APP进行补货操作,将商品补充至指定数量。 (4)充值模块:用户可以通过APP进行账户充值操作。 (5)消费流水模块:用户和商家可以查看售货机的消费记录。以上各模块之间通过STM32和APP程序之间进行通信,实现整个系统的功能。三、核心代码实现【1】步进电机控制代码以下是28BYJ48步进电机的代码:(1)定义一些宏和变量以便于控制步进电机: #define IN1 GPIO_Pin_0 #define IN2 GPIO_Pin_1 #define IN3 GPIO_Pin_2 #define IN4 GPIO_Pin_3 #define STEPS_PER_REVOLUTION 2048 //步数每圈 #define DELAY_MS 5 //控制转速的延迟时间 GPIO_InitTypeDef GPIO_InitStructure; int step_count = 0; uint16_t steps[] = {IN1 | IN2 | IN3 | IN4, IN2 | IN3 | IN4, IN1 | IN2 | IN3, IN3 | IN4, IN1 | IN3 | IN4, IN2 | IN4, IN1 | IN2, IN4}; void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) { for (j = 0; j < 1141; j++); } } void setStep(int step) { GPIO_ResetBits(GPIOB, IN1 | IN2 | IN3 | IN4); GPIO_SetBits(GPIOB, steps[step]); } void forward(int steps_to_move) { int i; for (i = 0; i < steps_to_move; i++) { setStep(step_count % 8); step_count++; delay_ms(DELAY_MS); } } void backward(int steps_to_move) { int i; for (i = 0; i < steps_to_move; i++) { setStep(step_count % 8); step_count--; delay_ms(DELAY_MS); } }在上面的代码中,定义了四个引脚来控制步进电机,然后定义了一些函数来实现正反转控制。delay_ms函数用于延迟控制步进电机的转速。STEPS_PER_REVOLUTION宏定义了每圈的步数,DELAY_MS宏定义了控制转速的延迟时间。setStep函数根据传入的步数设置引脚状态,接着forward和backward函数分别根据需要移动的步数控制步进电机的转动方向,并调用setStep函数控制步进电机的步数。最后,将forward和backward函数封装成一个子函数来更方便地调用: void control_stepper_motor(int steps_to_move, int direction) { if (direction == 1) { forward(steps_to_move); } else { backward(steps_to_move); } }这样,就可以通过调用control_stepper_motor函数来实现正反转控制28BYJ48步进电机了。【2】矩阵键盘检测代码以下是4x4电容矩阵键盘的示例代码:(1)定义一些宏和变量以便于控制电容矩阵键盘: #define ROW1 GPIO_Pin_0 #define ROW2 GPIO_Pin_1 #define ROW3 GPIO_Pin_2 #define ROW4 GPIO_Pin_3 #define COL1 GPIO_Pin_4 #define COL2 GPIO_Pin_5 #define COL3 GPIO_Pin_6 #define COL4 GPIO_Pin_7 GPIO_InitTypeDef GPIO_InitStructure; const uint8_t keys[4][4] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} };在上面的代码中,定义了8个引脚来控制电容矩阵键盘,并使用一个二维数组来存储每个按键对应的字符。(2)需要编写一个函数来检测电容矩阵键盘是否有按下。该函数需要通过轮询扫描键盘来检测按键,如果有按键按下,则返回该按键对应的字符: char scan_keypad() { GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4); GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0); return keys[0][0]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0); return keys[1][0]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0); return keys[2][0]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0); return keys[3][0]; } GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4); GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0); return keys[0][1]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0); return keys[1][1]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0); return keys[2][1]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0); return keys[3][1]; } GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4); GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0); return keys[0][2]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0); return keys[1][2]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0); return keys[2][2]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0); return keys[3][2]; } GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4); GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0); return keys[0][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0); return keys[1][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0); return keys[2][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) { while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0); return keys[3][3]; } return '\0'; }在上面的代码中,使用轮询的方式扫描键盘。首先将所有行引脚都设为低电平,所有列引脚都设为高电平,并检测是否有按键按下。如果有按键按下,则返回该按键对应的字符。 接下来,可以在主函数中循环调用scan_keypad函数来读取键值: int main(void) { char key = '\0'; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = ROW1 | ROW2 | ROW3 | ROW4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = COL1 | COL2 | COL3 | COL4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); while (1) { key = scan_keypad(); if (key != '\0') { // 处理读取到的键值 } } }在上面的代码中,首先初始化了8个引脚,并通过循环调用scan_keypad函数来读取键值。如果读取到键值,则可以进行相应的处理。四、系统测试与验证为了验证系统的可行性和稳定性,在硬件搭建完成后,进行了一系列测试。(1)测试了系统的整体运行逻辑。通过模拟用户选择商品、支付、出货等情况,验证系统的基本功能。测试结果显示系统能够稳定运行,能够满足用户的购物需求。(2)测试了系统的库存管理功能。通过模拟商品销售情况,验证系统的库存信息是否能够实时更新。测试结果表明系统能够准确地处理库存信息。(3)测试了手机端APP程序的功能。通过模拟用户登录、查看商品库存、进行补货、充值和查看消费流水等操作,验证APP程序的功能。测试结果显示APP程序能够正常运行,并且与STM32主控芯片之间能够实现良好的通信。
-
一、项目背景随着城市规模的不断扩大和交通运输方式的日益发展,铁路与公路的交叉口已经成为常见的场景。然而,这些交叉口往往存在一定的安全隐患,因为有时不易发现列车行进的情况,导致公路上的车辆或行人可能会无意中闯入铁路区域,从而引发重大交通事故。为了解决这个问题,当前开发了一款基于STM32的铁路自动围栏系统。该系统采用了STM32F103RCT6作为主控芯片,并使用步进电机来控制铁路围栏的开启和闭合。同时,系统还配备了红外感应器,以便能够及时监测到列车的通过情况。当系统监测到有列车即将通过铁路交叉口时,公路信号灯会立刻变为红灯,蜂鸣器也会发出警报声音,以提醒行人和车辆注意安全。同时,铁路两侧的围栏也会自动关闭,在列车通过后再次打开。这样,就能有效地防止公路车辆和行人误闯铁路区域,保障了路人的安全。二、系统设计2.1 硬件部分STM32F103RCT6主铁路自动围栏系统的硬件部分主要包括:STM32F103RCT6主控芯片、步进电机、红外感应器、信号灯、蜂鸣器。 【1】STM32F103RCT6主控芯片是整个系统的核心,负责控制围栏的开启和闭合、监测红外感应器的状态、控制信号灯的变化以及控制蜂鸣器的报警声音。【2】步进电机是用来控制铁路围栏的开启和闭合的设备,其动力来源为驱动芯片ULN2003。【3】红外感应器是用来监测列车的通过情况,当感应到列车时输出高电平信号,否则输出低电平信号。【4】信号灯则用来提示道路行人和车辆当前状态,红灯表示停止,绿灯表示通行。【5】蜂鸣器则是用来发出报警声音,提醒行人和车辆注意安全。2.2 软件部分程序主要分为四部分:系统初始化、红外感应器检测、铁路围栏控制和信号灯控制。【1】系统初始化主要是对硬件进行初始化,包括设置STM32的时钟、GPIO口的初始化等。【2】红外感应器检测部分则是对红外感应器进行监测,当感应到列车时输出高电平信号,程序通过读取该信号实现对铁路围栏的控制和信号灯的变化。【3】铁路围栏控制部分主要是通过对步进电机的控制来实现围栏的开启和闭合。【4】信号灯控制部分则是通过对GPIO口的控制来实现信号灯的变化,当感应到列车时,将信号灯变为红色,否则为绿色。三、核心代码实现3.1 28BYJ48步进电机代码以下是使用STM32F103RCT6驱动28BYJ-48步进电机实现正反转控制并封装成子函数调用的完整代码实现过程。首先,需要定义相关引脚和变量: #include "stm32f10x.h" #define IN1_PIN GPIO_Pin_0 #define IN2_PIN GPIO_Pin_1 #define IN3_PIN GPIO_Pin_2 #define IN4_PIN GPIO_Pin_3 GPIO_InitTypeDef GPIO_InitStructure; uint8_t step = 0;然后,编写初始化GPIO的代码: void init_GPIO(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); }接着,编写正转和反转函数的代码: void forward(void) { switch(step) { case 0: GPIO_SetBits(GPIOA, IN1_PIN); GPIO_ResetBits(GPIOA, IN2_PIN | IN3_PIN | IN4_PIN); break; case 1: GPIO_SetBits(GPIOA, IN1_PIN | IN2_PIN); GPIO_ResetBits(GPIOA, IN3_PIN | IN4_PIN); break; case 2: GPIO_SetBits(GPIOA, IN2_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN3_PIN | IN4_PIN); break; case 3: GPIO_SetBits(GPIOA, IN3_PIN | IN2_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN4_PIN); break; case 4: GPIO_SetBits(GPIOA, IN3_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN | IN4_PIN); break; case 5: GPIO_SetBits(GPIOA, IN4_PIN | IN3_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN); break; case 6: GPIO_SetBits(GPIOA, IN4_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN | IN3_PIN); break; case 7: GPIO_SetBits(GPIOA, IN1_PIN | IN4_PIN); GPIO_ResetBits(GPIOA, IN2_PIN | IN3_PIN); break; } step++; if(step == 8) { step = 0; } } void backward(void) { switch(step) { case 0: GPIO_SetBits(GPIOA, IN1_PIN | IN4_PIN); GPIO_ResetBits(GPIOA, IN2_PIN | IN3_PIN); break; case 1: GPIO_SetBits(GPIOA, IN4_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN | IN3_PIN); break; case 2: GPIO_SetBits(GPIOA, IN3_PIN | IN4_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN); break; case 3: GPIO_SetBits(GPIOA, IN3_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN | IN4_PIN); break; case 4: GPIO_SetBits(GPIOA, IN2_PIN | IN3_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN4_PIN); break; case 5: GPIO_SetBits(GPIOA, IN2_PIN); GPIO_ResetBits(GPIOA, IN1_PIN | IN3_PIN | IN4_PIN); break; case 6: GPIO_SetBits(GPIOA, IN1_PIN | IN2_PIN); GPIO_ResetBits(GPIOA, IN3_PIN | IN4_PIN); break; case 7: GPIO_SetBits(GPIOA, IN1_PIN); GPIO_ResetBits(GPIOA, IN2_PIN | IN3_PIN | IN4_PIN); break; } step--; if(step == -1) { step = 7; } }最后,可以封装正转和反转函数成子函数: void rotate_motor(int steps, int direction) { int i = 0; for(i = 0; i < steps; i++) { if(direction == 0) { forward(); } else if(direction == 1) { backward(); } } void motor_stop(void) { GPIO_ResetBits(GPIOA, IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN); }最后,可以在主函数中使用这些封装好的子函数: int main(void) { init_GPIO(); // 正转200步 rotate_motor(200, 0); // 反转100步 rotate_motor(100, 1); // 停止电机 motor_stop(); while (1); }3.2 蜂鸣器报警代码 #include "stm32f10x.h" #define BUZZER_GPIO_PIN GPIO_Pin_7 #define BUZZER_GPIO_PORT GPIOC void buzzer_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = BUZZER_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BUZZER_GPIO_PORT, &GPIO_InitStructure); } void buzzer_on(void) { GPIO_SetBits(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN); // Buzzer on } void buzzer_off(void) { GPIO_ResetBits(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN); // Buzzer off }3.3 红外感应器代码 #include "stm32f10x.h" #define IR_GPIO_PIN GPIO_Pin_1 #define IR_GPIO_PORT GPIOA void ir_init(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = IR_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(IR_GPIO_PORT, &GPIO_InitStructure); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1); EXTI_InitStructure.EXTI_Line = EXTI_Line1; // 对应中断线 EXTI1 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 上升沿和下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 中断向量 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; // 抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; // 响应优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) != RESET) { // Do something when IR sensor detects the train EXTI_ClearITPendingBit(EXTI_Line1); } }3.4 信号灯控制代码 #include "stm32f10x.h" #define LED_GREEN_GPIO_PIN GPIO_Pin_6 #define LED_GREEN_GPIO_PORT GPIOB #define LED_RED_GPIO_PIN GPIO_Pin_7 #define LED_RED_GPIO_PORT GPIOB void led_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = LED_GREEN_GPIO_PIN | LED_RED_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); } void led_green_on(void) { GPIO_SetBits(LED_GREEN_GPIO_PORT, LED_GREEN_GPIO_PIN); // Green LED on } void led_green_off(void) { GPIO_ResetBits(LED_GREEN_GPIO_PORT, LED_GREEN_GPIO_PIN); // Green LED off } void led_red_on(void) { GPIO_SetBits(LED_RED_GPIO_PORT, LED_RED_GPIO_PIN); // Red LED on } void led_red_off(void) { GPIO_ResetBits(LED_RED_GPIO_PORT, LED_RED_GPIO_PIN); // Red LED off }四、总结当前设计的这种基于STM32的铁路自动围栏系统,通过对铁路交叉口进行有效的监测和控制,实现了对过往车辆和行人的有效防护。该系统采用STM32F103RCT6作为主控芯片,使用步进电机控制铁路围栏的开启和闭合,使用红外感应器来监测列车的通过情况。在公路与铁路的交叉路口,若在远处感应到有列车即将通过,则公路信号灯变为红灯,蜂鸣器报警,铁路两侧围栏自动闭合;直至感应到列车彻底离开,公路信号灯变为绿灯,蜂鸣器关闭,围栏打开。系统具有结构简单、性能可靠等优点,在实际应用中取得了良好的效果。
推荐直播
-
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中
热门标签