-
一、项目介绍在一些设备的使用过程中,需要对设备的使用次数进行统计和记录。这可以用于评估设备的实际使用寿命、确定维护周期、预测故障风险等方面,对于提高设备的稳定性和可靠性具有重要意义。当前项目采用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作为主控芯片,使用步进电机控制铁路围栏的开启和闭合,使用红外感应器来监测列车的通过情况。在公路与铁路的交叉路口,若在远处感应到有列车即将通过,则公路信号灯变为红灯,蜂鸣器报警,铁路两侧围栏自动闭合;直至感应到列车彻底离开,公路信号灯变为绿灯,蜂鸣器关闭,围栏打开。系统具有结构简单、性能可靠等优点,在实际应用中取得了良好的效果。
-
一、项目背景随着人们生活水平和健康意识的提高,越来越多的人开始注重自己的饮食健康。在此背景下,智能营养秤系统应运而生,成为了一种非常实用的工具。本项目基于51单片机设计和实现一种智能营养秤系统,通过该系统可准确地测量食物的重量并计算其热量、蛋白质、脂肪、碳水化合物等营养成分含量。当前系统采用了STC89C52单片机作为主控芯片,预置了多种食材的营养成分数据。用户只需要使用矩阵键盘输入食材编号,将需要称重的食材放置在重力传感器上进行依次称重,系统就可以自动计算出所有食材的各类营养含量总值,并通过液晶屏显示出来。同时,系统根据预设的营养指标,对不达标或超标的食材进行对应的声光提示,提醒用户注意饮食健康。当前系统还配备了无线WIFI模块,可以将当前营养数据上传到手机端实时显示,并给出营养建议。这使得用户可以随时1了解自己的饮食情况,及时进行调整,从而达到更好的健康效果。本项目的设计和实现是为了满足人们对于饮食健康的需求,帮助人们更好地控制自己的饮食,达到健康瘦身的目的。同时,由于采用了51单片机的设计方案,具有成本低、易于制作、易于维护等优点,具有广泛的应用前景。二、系统设计过程2.1 硬件组成【1】STC89C52单片机作为主控芯片。【2】4x4电容矩阵键盘用于输入食材编号。【3】HX711重力传感器用来进行多种食材的称重。【4】1.44寸LCD显示屏用来显示所有食材的各类营养含量总值。【5】ESP8266无线WIFI模块用于将当前营养数据上传到手机端实时显示。2.2 系统框架组成【1】输入:使用4x4电容矩阵键盘输入食材编号,触发称重功能。【2】称重:根据输入的食材编号,通过HX711重力传感器对多种食材依次称重。【3】计算:系统自动计算所有食材的各类营养含量总值,并在1.44寸LCD显示屏上显示。【4】判断:根据系统预设的营养指标,判断当前营养数据是否达标或超标。【5】提示:若不达标或超标,系统进行相应的声光提示。【6】数据上传:通过ESP8266无线WIFI模块将当前营养数据上传到手机端实时显示,并给出营养建议。2.3 系统模块设计【1】系统硬件设计采用了51单片机作为主控芯片,重力传感器用于称重,矩阵键盘用于输入食材编号,液晶屏用于显示数据。同时,为了实现无线上传功能,还需要添加WIFI模块。【2】系统软件设计系统的软件设计主要包括两个方面,即驱动程序和应用程序。其中,驱动程序负责与各个硬件模块进行通信,读取和处理相关数据;应用程序则负责实现具体的计算和控制逻辑。【3】食材营养成分数据预置预先测量并记录多种食材的重量和营养成分含量,并将这些数据存储在系统中供后续使用。【4】食材识别和称重当用户输入食材编号后,系统自动从预置的数据中查找对应的营养成分信息。然后,用户将需要称重的食材放置在重力传感器上,系统开始进行称重并输出重量数据。【5】营养计算和指标判断系统根据已知的食材重量和营养成分数据,计算出当前食物的各类营养含量总值。同时,根据预设的营养指标,判断当前食物是否达标或超标,并进行相应的声光提示。【6】数据传输和显示将当前的营养数据通过WIFI模块上传到手机端实时显示,并根据用户的身体数据和运动情况,推荐合适的饮食方案。【7】整体测试和优化:对系统进行整体测试和优化,确保系统能够正常工作并满足设计要求。2.4 程序设计思路【1】定义多种食材的营养成分数据,存储在程序中。【2】初始化电容矩阵键盘和HX711重力传感器。【3】等待用户输入食材编号。一旦检测到有效输入,记录食材编号并触发称重功能。【4】根据输入的食材编号,依次使用HX711重力传感器进行称重,并根据对应的营养成分数据进行计算,得出每种营养成分的总值。【5】将所有食材的营养成分总值通过1.44寸LCD显示屏展示给用户。【6】根据系统预设的营养指标,判断当前营养数据是否达标或超标。如果不达标或超标,则进行相应的声光提示。【7】通过ESP8266无线WIFI模块将当前营养数据上传到手机端实时显示,并给出营养建议。三、程序代码实现3.1 HX711称重传感器代码下面是STC89C52单片机读取HX711称重传感器的值,得到最终的重量,打印到串口的完整代码: #include <reg52.h> #include <intrins.h> // HX711引脚定义 sbit HX711_DOUT = P1^0; // 数据输出引脚 sbit HX711_SCK = P1^1; // 时钟输入引脚 typedef unsigned char uchar; typedef unsigned int uint; uchar WeiLai, OldData; // 定义两个变量,用于保存数据 uchar Data[3]; // 存放读取的数据 long result = 0; // 定义长整型变量,用于存放最终的重量值 void delay_us(uint us) // 延时函数(微秒级) { while(us--) { _nop_(); // 空操作语句,延时一微秒 _nop_(); _nop_(); _nop_(); } } void Read_HX711() // 读HX711函数 { uchar i; HX711_DOUT = 1; // 先将DOUT置为高电平 delay_us(1); // 延时1微秒 HX711_SCK = 0; // 将SCK置为低电平 delay_us(1); // 延时1微秒 for(i=0;i<24;i++) // 循环24次,读取数据 { HX711_SCK = 1; // 将SCK置为高电平 delay_us(1); // 延时1微秒 WeiLai = HX711_DOUT; // 读取DOUT引脚上的数据 result <<= 1; // 左移一位 if(WeiLai == 1) // 如果DOUT为1,将result的最低位赋值为1 { result++; } HX711_SCK = 0; // 将SCK置为低电平 delay_us(1); // 延时1微秒 } WeiLai = OldData; // 将OldData的值赋给WeiLai Data[2] = result; // 存储重量值的最高字节 Data[1] = result>>8; // 存储重量值的中间字节 Data[0] = result>>16; // 存储重量值的最低字节 } void main() { TMOD = 0x20; // 定时器T1工作模式设置 TH1 = 0xfd; // 波特率9600 TL1 = 0xfd; // 波特率9600 TR1 = 1; // 启动定时器T1 SCON = 0x50; // 设置串口工作方式 while(1) { Read_HX711(); // 调用读HX711函数 // 将读取到的数据打印到串口 SBUF = Data[0]; while(TI == 0); TI = 0; SBUF = Data[1]; while(TI == 0); TI = 0; SBUF = Data[2]; while(TI == 0); TI = 0; } }3.2 ESP82660-WIFI配置代码以下是STC89C52单片机控制ESP8266,配置成AP模式,开启TCP服务器,等待客户端连接上来的完整代码: #include <reg52.h> #include <intrins.h> #define RXD P3_0 // 串口接收引脚 #define TXD P3_1 // 串口发送引脚 typedef unsigned char uchar; typedef unsigned int uint; bit rcvflag; // 接收标志位 uchar idata RcvBuf; // 存储接收到的数据 uchar len; // 存储接收到的数据长度 uchar AT_OK; // 存储AT指令执行结果 /* 延时函数 */ void Delayms(uint ms) { uchar i, j; for(i=0;i<ms;i++) { for(j=0;j<110;j++); } } /* 发送一个字节的数据 */ void SendByte(uchar dat) { SBUF = dat; while(!TI); TI = 0; } /* 发送字符串 */ void SendString(char *str) { while(*str) { SendByte(*str++); } } /* 发送AT指令 */ void SendATCmd(char *str) { SendString(str); SendString("\r\n"); Delayms(20); // 延时20ms等待返回数据 } /* 接收一个字节的数据 */ void Uart_Rcv() interrupt 4 using 1 { if(RI) // 判断是否接收到数据 { RI = 0; // 清除RI标志位 RcvBuf = SBUF; // 存储接收到的数据 rcvflag = 1; // 置接收标志位 } } /* 检查AT指令执行结果 */ void Check_AT_Cmd() { uchar i; if(rcvflag == 0) // 如果没有接收到数据 { AT_OK = 0xff; // 执行AT指令失败 return; } len = 0; do // 接收完整的一行数据 { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; // 遇到回车符则结束接收 if(i != '\n') // 跳过换行符 { len++; } }while(1); if(len != 0) // 如果有接收到数据 { AT_OK = 0; // 执行AT指令成功 } rcvflag = 0; // 清除接收标志位 } /* 配置ESP8266模块为AP模式 */ void Set_AP_Mode(char *ssid, char *pwd) { SendATCmd("AT+RST"); // 复位ESP8266模块 Check_AT_Cmd(); SendATCmd("AT+CWMODE=2"); // 配置模块为AP模式 Check_AT_Cmd(); SendATCmd("AT+CWSAP=""); SendString(ssid); // 设置SSID SendString("",""); SendString(pwd); // 设置密码 SendString("",5,3"); // 设置加密方式为WPA2_PSK Check_AT_Cmd(); } /* 开启TCP服务器 */ void TCP_Server_Open() { SendATCmd("AT+CIPMUX=1"); // 开启多连接模式 Check_AT_Cmd(); SendATCmd("AT+CIPSERVER=1,8888"); // 开启TCP服务器,端口号为8888 Check_AT_Cmd(); } /* 等待客户端连接 */ void Wait_For_Client_Connect() { uchar i; do { if(rcvflag == 0) continue; // 没有接收到数据则继续等待 len = 0; do { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; if(i != '\n') { len++; } }while(1); rcvflag = 0; // 清除接收标志位 if(len != 0 && RcvBuf == '+') { SendString("AT+CIPSEND=0,12"); // 发送数据,长度为12 SendString("\r\n"); Delayms(10); if(rcvflag && RcvBuf == '>') // 等待">"符号 { SendString("Hello World!"); // 发送数据 SendByte(0x1a); // 发送结束符 } while(!rcvflag); // 等待数据发送完成 len = 0; do { i = RcvBuf; RcvBuf = 0; if(i == '\r') break; if(i != '\n') { len++; } }while(1); rcvflag = 0; // 清除接收标志位 if(len != 0 && RcvBuf == 'S') // 如果接收到"SEND OK"说明数据发送成功 { SendString("Data send success!"); SendString("\r\n"); } } }while(1); } /* 主函数 */ void main() { TMOD = 0x20; // 定时器T1工作模式设置 TH1 = 0xfd; // 波特率9600 TL1 = 0xfd; // 波特率9600 TR1 = 1; // 启动定时器T1 SCON = 0x50; // 设置串口工作方式 EA = 1; // 允许中断 ES = 1; // 允许串口中断 Set_AP_Mode("MyAP", "12345678"); // 配置模块为AP模式,设置SSID和密码 TCP_Server_Open(); // 开启TCP服务器 Wait_For_Client_Connect(); // 等待客户端连接 }
-
简介CodeArts IDE是一个集成开发环境(IDE),它提供了开发语言和调试服务。本文主要介绍CodeArts IDE for C/C++的基本功能。1.下载安装CodeArts IDE for C/C++ 已开放公测,下载获取免费体验2.新建C/C++工程CodeArts IDE for C/C++ 提供了创建C或C++工程的能力,可参考以下步骤进行创建:1. 点击顶部菜单 File -> New -> Project...2. 选择 C/C++3. 填写表单并点击创建按钮4. 等待工程创建完成并打开项目3.C/C++代码编写3.1编码基础操作CodeArts IDE for C/C++ 包含了内置的语法着色,定义预览,跳转定义,类继承关系图,调用关系图等一些编码基础功能。语法着色 - 该功能可对函数,类型,局部变量,全部变量,宏,枚举,成员变量等上色。跳转定义 - Ctrl+点击或者F12跳转到定义,或者使用Ctrl+Alt+点击会打开定义到旁边。定义预览 - 当光标移至符号处,则会有符号定义的悬停预览,也可以用alt+F12的快捷键进行文件内的符号预览。查找所有引用 - 当光标点击或者选择到需要查找的符号,右键菜单->查找所有引用或者使用快捷键Shift+Alt+F12会打开定义在页面左侧。调用关系图 - 当光标点击或选中需要调用关系图的函数时,右键菜单->调用关系图,或可以使用快捷键Shift+Alt+H调出。在关系图中,也可以点击需要查看的函数并导航到该函数,同时也能够查看子类和基类。符号大纲 - 左侧工具->右上角三个点->大纲即可打开符号大纲,或者使用快捷键Ctrl+Shift+B打开工具栏。打开大纲后,双击函数即可到达函数定义的位置,并且当前符号大纲可跟随光标移动(此功能需要在大纲菜单栏中打开跟随光标选项)。3.2 代码编写操作CodeArts IDE for C/C++ 包含了内置的符号重命名,提取重构,代码补全/提示,实时语法检查等一些高级代码编写功能。符号重命名(Rename symbol)最基础的重构之一,但是变量或方法名字的可读性非常重要。在光标选中某个变量或方法后,右键单击以调出编辑器上下文菜单并且选择重命名符号或直接按F2,来重命名整个 C/C++ 项目中所有用到该命名的地方。提取重构(Extraction refactoring)CodeArts IDE for C/C++ 支持将字段,方法和参数提取到新类中,根据提取的内容会提供不同的重构类型。可用的 C/C++ 重构类型包括:提取函数/方法(Extract method)- 将选定的语句或表达式提取到文件中的新方法或新函数。在选择提取方法(Extract method)重构后,输入提取的的方法/函数的名称。提取表达式到变量(Extract subexpression to variable)- 将选定的表达式提取为文件中的新变量。代码补全/提示(Code Completion/Hinting)CodeArts IDE for C/C++ 代码补全包含了各种代码编辑功能,包括:代码完成,快速信息,成员列表以及参数信息。当您输入字符时,代码补全若知道可能的补全选项,则会自动弹出成员列表。如果您继续输入字符,成员列表(变量,方法等)将被过滤为仅包含您输入字符的成员。您可通过光标点击或者按Enter或Tab键插入选定的成员名称。该功能会提供各种提示信息帮助您更加方便快速的编辑代码。全局符号搜索(Global Symbol Search)Ctrl+T导出搜索框,输入需要查找的符号,页面会显示出当前文件夹所有包含此符号的文件,点击即可跳转。或者按向上或向下选择并按Enter导航到您想要的位置。实时检查编译错误(该功能依赖compile_commands.json文件)实时检查编译错误是解决编码错误的建议编辑,包括自动补全,实时语法检查等。当编译错误时,会在错误处出现波浪线。可将光标移动或点击到C/C++的代码错误上时,会显示黄色灯泡,表示可以使用快速修复。点击灯泡或按Ctrl+。会显示可用的快速修复和重构列表。Compile_commands.json 管理功能Compiler 模式功能全面,但需要compile_commands.json文件编译数据库才能正常工作,可使用三种方式获取该文件。使用内置 CMake Build Tool 插件(推荐)。构建 CMake 项目,会自动生成cmake-build-debug/compile_commands.json文件, 并且插件会自动将该文件导入到 .arts文件夹。使用 CMake 生成。 如果当前工程是 CMake 工程,可以通过添加参数-DCMAKE_EXPORT_COMPILE_COMMANDS=1生成 compile_commands.json,并通过帮助->显示所有命令->Huawei C/C++:导入编译数据库文件命令导入。使用 Huawei C/C++ 提供的Generate命令。可通过帮助->显示所有命令->Huawei C/C++:生成编译数据库文件,并选择存放源文件的文件夹,该方法分析头文件生成对应的编译数据库。同时 Huawei C/C++也支持以下功能:通过命令或 API 导入compile_commands.json文件(帮助->显示所有命令->Huawei C/C++:导入编译数据库文件)合并多个 compile_commands.json 文件.移除 compile_command.json 文件中重复的命令.导入时为 clangd 提供额外的参数设置.索引更新命令同步工程索引(帮助->显示所有命令->Huawei C/C++:同步工程索引)同步文件夹索引(资源管理器右键菜单->Huawei C/C++:同步文件夹索引)同步文件索引(资源管理器右键菜单->Huawei C/C++:同步当前文件索引)重置工程索引(帮助->显示所有命令->Huawei C/C++:重建全项目索引)编辑源文件的编译选项并刷新索引(右键菜单->编辑编译参数)以上命令和功能在 Compiler 模式或 Hybrid 模式均有效。3.3 代码重构操作重构是通过改变现有程序结构而不改变其功能和用途来提高代码的可重用性和可维护性。CodeArts IDE 支持重构操作,提供了多种重要的重构类型,来改变编辑器中的代码库。CodeArts IDE for C/C++ 内置了对 C/C++ 重构的支持,在本专题中,我们将展示 C/C++ 语言服务的重构支持。定义构造函数(Define constructor)在每次创建类时,可以自动定义类的构造函数,并且初始化成员。当点击或选中类名时,可以点击左侧黄色灯泡选择定义构造函数。根据声明顺序排序函数(Sort functions to declarations)根据头文件中的声明顺序,排序当前定义函数/方法的顺序。当点击或选中当前函数/方法定义时,重构选项可用。将定义添加到实现文件(Add definition to implementation file)将头文件的定义添加到实现文件中。当点击或选中当前函数/方法时,重构选项可用。交换 if 分支(Swap if branches)若当前条件只有if和else分支,选中代码片段后,选择交换 if 分支(Swap if branches),可自动交换if和else分支。内联变量(Inline variable)该功能可以用相应的值替换所有引用。假设计算值总是产生相同的结果。选中需要替换的内容,重构选项可用。内联函数(Inline function)该功能尝试使用适当的代码内联所有函数用法。它只能处理简单的功能,不支持内联方法、函数模板、主函数和在系统头文件中声明的函数。该功能可以内联所有函数引用。生成 getter 和 setter(Generate getter and setter)通过为其生成getter和setter(Generate getter and setter)来封装选定的类属性。同时也可以选择只生成getter(Generate getter)或者生成setter(Generate setter)选项。声明隐式成员(Declare implicit members)此选项会将类的隐式成员在类中声明,当选中类名时,重构选项可用。填充 switch 语句(Populate switch)该功能可以自动填充switch语句。选中任意switch字段,并且点击黄色灯泡,选择填充switch语句。移除 namespace(Remove using namespace)移除namespace功能,会自动移除所有使用到的namespace。当光标点击或选中namesapace关键字时,重构选项可用。移动函数体到声明处(Move function body to out-of-line)将函数/方法定义移动到它声明的位置。在内部添加定义(Add definition in-place)在当前函数/方法并且在类内部生成函数定义。当光标移动到函数/方法时,点击黄色灯泡,重构选项可用。在外部添加定义(Add definition out-of-place)在类外部生成当前函数/方法的函数定义。当光标移动到函数/方法时,点击黄色灯泡,重构选项可用。展开宏(Expand macro)在页面上添加展开宏(Expand macro),以便在可扩展/可折叠的部分提供内容。展开 auto(Expand auto type)展开 auto type所隐藏的变量类型。函数定义外移(Move function body to declaration)该功能会将函数/方法的定义移动到声明的位置。函数定义内移(Move function body to out-of-line)该功能会将函数/方法的定义移动到对应的文件中。转为原始字符串(Convert to raw string)此方法可以将转义后的字符串转换为原始的字符串。当点击或选择了当前字符串,点击黄色灯泡,重构选项可用。快速修复(Quick fixes)快速修复是解决简单编码错误的建议编辑,包括自动补全,实时语法检查等。当光标移动或点击到C/C++的代码错误上时,会显示黄色灯泡,表示可以使用快速修复。点击灯泡或按Ctrl+.会显示可用的快速修复和重构列表。
HuaweiCloud开发工具 发表于2023-05-12 15:42:44 2023-05-12 15:42:44 最后回复 albertbanda 2024-11-19 13:07:24
3300 11 -
一、STM32在物联网产品里的开发作用STM32在物联网产品中具有重要的开发作用,主要包括以下几个方面:嵌入式控制:STM32作为一款强大的嵌入式微控制器,可用于控制各种不同类型的物联网设备。它可以轻松读取传感器数据、实现数据采集、进行控制操作等,从而实现物联网设备的智能化控制。 低功耗设计:STM32在能耗方面表现出色,采用它进行的物联网设备开发可以极大地降低设备的功耗,从而延长电池寿命,提高设备的运行稳定性。 连接网络:为了实现硬件设备的联网功能,STM32在物联网产品中扮演了重要的角色。它可以通过网络接口以太网、Wi-Fi、蓝牙等方式实现无线连接,从而实现物联网设备与互联网的连接。 丰富的软件支持:STM32的开发板提供了丰富的软件支持,包括各种开发工具、SDK以及驱动程序等,这些软件工具能够帮助开发人员快速进行开发,大大简化了物联网产品的开发流程。 二、开发案例STM32配合W5500网卡连接华为云物联网平台通信 https://bbs.huaweicloud.com/forum/thread-0214117966487710039-1-1.html 基于STM32设计的智能灌溉控制系统 https://bbs.huaweicloud.com/forum/thread-0258117964156424037-1-1.html C语言代码封装MQTT协议报文,了解MQTT协议通信过程 https://bbs.huaweicloud.com/forum/thread-0262117963321259034-1-1.html STM32单片机上RGB数据转为JPEG格式办法 https://bbs.huaweicloud.com/forum/thread-0221117960587791024-1-1.html STM32+DHT11监测环境的温湿度 https://bbs.huaweicloud.com/forum/thread-0214117959718389037-1-1.html STM32通过ADC1读取光敏电阻的值转换光照强度 https://bbs.huaweicloud.com/forum/thread-0287117959317499038-1-1.html STM32读取MQ2烟雾浓度数据判断烟雾是否超标 https://bbs.huaweicloud.com/forum/thread-02104117958892070025-1-1.html STM32读取BH1750光照强度数据打印到串口 https://bbs.huaweicloud.com/forum/thread-0214117958650297035-1-1.html ESP8266调用NTP服务器进行时间校准 https://bbs.huaweicloud.com/forum/thread-02104117957509416024-1-1.html ESP8266获取天气预报信息,并使用CJSON解析天气预报数据 https://bbs.huaweicloud.com/forum/thread-0201117956617429025-1-1.html 基于STM32+华为云IOT设计的智能温室大棚监控系统 https://bbs.huaweicloud.com/forum/thread-0262117947916012031-1-1.html 基于STM32+NBIOT+华为云IOT设计的智能井盖 https://bbs.huaweicloud.com/forum/thread-0201117947554019022-1-1.html 基于STM32+华为云设计的智慧烟感系统 https://bbs.huaweicloud.com/forum/thread-0201117944388992019-1-1.html 基于STM32+RC522设计的门禁系统 https://bbs.huaweicloud.com/forum/thread-0262117944289191030-1-1.html
-
【1】W5500网卡W5500是一种基于TCP/IP协议的网络通讯芯片,可以提供网络连接功能,相当于是一种嵌入式以太网控制器,具有低功耗、高速传输、易于集成等特点。W5500芯片能够支持TCP、UDP、IPv4、ARP、ICMP、IGMP等协议,使得它变得非常适合用于嵌入式设备与互联网之间的通信需求,例如智能家居、工业控制、远程监控等场景。W5500网卡还有一个特点是它支持硬件协议堆栈,这意味着它可以非常快地执行协议栈中的操作,从而大大提高了数据传输的效率。同时,W5500还具有较低的功耗,因此非常适合嵌入式设备这种资源受限的场景。W5500芯片通过SPI总线与MCU进行通信,MCU需要实现SPI总线协议来控制W5500进行数据交互。【2】SPI协议SPI(Serial Peripheral Interface)协议是一种串行外设接口协议,是一种全双工、同步的接口技术,通常用于连接微控制器和外设,例如传感器、存储器、显示器等。SPI协议传输效率高,使用简单,开销较小,因此被广泛应用于嵌入式系统中。SPI协议使用主从模式,主设备可以控制多个从设备,从设备不能主动向主设备发送数据或信息。SPI协议具有以下几个重要的信号线:SCLK:时钟线,由主设备提供,用于同步主从设备之间的数据传输。MOSI(Master Out Slave In):主输出从输入线,由主设备提供,用于向从设备发送数据。MISO(Master In Slave Out):主输入从输出线,由从设备提供,用于向主设备发送数据。SS(Slave Select):从设备选择信号线,由主设备提供。当主设备需要与某个从设备通信时,将该线电平拉低,以选择需要通信的从设备。SPI协议的数据传输是基于数据字节的传输,主设备每次通过MOSI线发送一个字节,从设备通过MISO线接受该字节,并回传一个字节。数据的传输顺序可以根据时钟线(SCLK)的极性和相位配置为四种不同的模式。SPI协议支持的模式受闪存、RAM、I/O和模拟/数字转换器等外设和类型的限制。【3】W5500建立TCP协议通信以下是STM32通过W5500建立TCP通信,并访问TCP服务器,完成数据收发的示例代码。代码中使用了STM32 HAL库,W5500的IP地址和端口号需要根据实际情况进行设置。 #include "main.h" #include "stdio.h" #include "stm32f1xx_hal.h" #include "wizchip_conf.h" #include "socket.h" #include "dhcp.h" /* Private variables */ SPI_HandleTypeDef hspi1; /* Private function prototypes */ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_SPI1_Init(void); void W5500_Init(void); uint8_t socket; uint8_t buf[1024]; int main(void) { /* MCU Configuration */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); /* W5500 Initialization */ W5500_Init(); /* Connect to TCP Server */ uint8_t server_ip[4] = {192, 168, 1, 100}; uint16_t server_port = 5000; uint8_t connected = 0; while (!connected) { if (getSn_SR(socket) == SOCK_CLOSED) { socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket == 0xFF) { /* Error: Failed to create socket */ } else { /* Configure socket */ uint8_t dest_ip[4] = {192, 168, 1, 200}; uint16_t dest_port = 5000; uint8_t buf[4]; IINCHIP_WRITE(Sn_DIPR(socket), dest_ip); IINCHIP_WRITE(Sn_DPORT(socket), dest_port); IINCHIP_SOCKET_CONTROL(socket, Sn_CR_OPEN); HAL_Delay(10); /* Try to connect to server */ IINCHIP_SOCKET_CONTROL(socket, Sn_CR_CONNECT); HAL_Delay(1000); if (getSn_SR(socket) == SOCK_ESTABLISHED) { connected = 1; } else { /* Connection failed */ IINCHIP_SOCKET_CONTROL(socket, Sn_CR_CLOSE); HAL_Delay(10); } } } } /* Send Data to Server */ uint8_t tx_data[4] = {0x01, 0x02, 0x03, 0x04}; write(socket, tx_data, sizeof(tx_data)); /* Receive Data from Server */ int rx_len = 0; while (1) { rx_len = getSn_RX_RSR(socket); if (rx_len > 0) { read(socket, buf, rx_len); /* Data received from server, do something */ } = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi1); } /* GPIO Initialization */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin : PA4 */ GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } /* System Clock Configuration */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } }【4】封装MQTT协议报文下面使用MQTT client library for Contiki来连接MQTT服务器。这个库适用于不同的平台,包括STM32。在使用前,需要根据需求进行一些配置,例如: 指定MQTT服务器的地址和端口号,配置MQTT客户端ID和主题等。 #include "contiki.h" #include "contiki-net.h" #include "mqtt.h" #include "stm32f1xx_hal.h" #include "wizchip_conf.h" #include "w5500.h" /* MQTT Configuration */ #define SERVER_IP_ADDR "192.168.1.100" #define SERVER_PORT 1883 #define MQTT_CLIENT_ID "mqtt_stm32" #define MQTT_TOPIC "example_topic" /* Network Configuration */ static wiz_NetInfo gWIZNETINFO = { .mac = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, .ip = {192, 168, 1, 200}, .sn = {255, 255, 255, 0}, .gw = {192, 168, 1, 1}, .dns = {8, 8, 8, 8}, .dhcp = NETINFO_STATIC }; /* W5500 Buffer */ static uint8_t buf[2048]; /* Prototypes */ static void MX_SPI1_Init(void); static void MX_GPIO_Init(void); void SystemClock_Config(void); void Error_Handler(void); void W5500_Select(void); void W5500_UnSelect(void); uint8_t W5500_WriteByte(uint8_t b); uint8_t W5500_ReadByte(void); void MQTT_Callback(struct mqtt_connection *m, void *userdata, mqtt_event_t event, mqtt_data_t *data); /* MQTT Connection */ static struct mqtt_connection mqtt_conn; static struct mqtt_message *msg_ptr = NULL; static uint8_t mqtt_connected = 0; PROCESS(mqtt_process, "MQTT Process"); AUTOSTART_PROCESSES(&mqtt_process); /* MQTT Process */ PROCESS_THREAD(mqtt_process, ev, data) { PROCESS_BEGIN(); /* Initialize W5500 */ reg_wizchip_cs_cbfunc(W5500_Select, W5500_UnSelect); reg_wizchip_spi_cbfunc(W5500_ReadByte, W5500_WriteByte); wizchip_init(buf, buf); /* Configure Network */ ctlnetwork(CN_SET_NETINFO, (void*)&(gWIZNETINFO)); /* DHCP Initialization */ uint8_t /* Enable DHCP */ dhcp_client_start(); /* Wait for DHCP to finish */ while (gWIZNETINFO.dhcp == NETINFO_DHCP) { HAL_Delay(1000); // wait for DHCP to finish } /* Print IP Address */ printf("IP address: %d.%d.%d.%d\n", gWIZNETINFO.ip[0], gWIZNETINFO.ip[1], gWIZNETINFO.ip[2], gWIZNETINFO.ip[3]); /* Configure MQTT Connection */ memset(&mqtt_conn, 0, sizeof(mqtt_conn)); mqtt_conn.state = MQTT_INIT; mqtt_conn.host = SERVER_IP_ADDR; mqtt_conn.port = SERVER_PORT; mqtt_conn.client_id = MQTT_CLIENT_ID; mqtt_conn.user_data = NULL; mqtt_conn.user_name = NULL; mqtt_conn.password = NULL; mqtt_conn.protocol_version = MQTT_VERSION_3_1_1; mqtt_conn.keep_alive = 60; /* Connect to MQTT Server */ mqtt_connect(&mqtt_conn); /* Wait for MQTT Connection to Finish */ while (!mqtt_connected) { PROCESS_PAUSE(); } /* Publish Message to MQTT Server */ static char msg[100] = "Hello from STM32 using MQTT protocol!"; msg_ptr = mqtt_msg_publish_init(msg, strlen(msg), MQTT_TOPIC, MQTT_QOS_LEVEL_0, MQTT_RETAIN_OFF); mqtt_publish(&mqtt_conn, msg_ptr); /* Wait for Message to be Sent */ while (mqtt_conn.out_buffer_sent == 0) { PROCESS_PAUSE(); } /* Subscribe to MQTT Topic */ mqtt_subscribe(&mqtt_conn, MQTT_TOPIC, MQTT_QOS_LEVEL_0); /* Loop Forever */ while (1) { PROCESS_PAUSE(); } PROCESS_END(); } /* SPI Initialization / static void MX_SPI1_Init(void) { / SPI1 parameter configuration*/ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi1); } /* GPIO Initialization */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin : PA4 */ GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } /* System Clock Configuration */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* Error Handler */ void Error_Handler(void) { while (1) { // error } } /* W5500 Select */ void W5500_Select(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); } /* W5500 Unselect */ void W5500_UnSelect(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } /* W5500 Write Byte */ uint8_t W5500_Writebyte(uint8_t b) { uint8_t res; HAL_SPI_TransmitReceive(&hspi1, &b, &res, 1, HAL_MAX_DELAY); return res; } /* W5500 Read byte */ uint8_t W5500_Readbyte(void) { uint8_t b = 0xff; HAL_SPI_TransmitReceive(&hspi1, &b, &b, 1, HAL_MAX_DELAY); return b; } /* MQTT Callback */ void MQTT_Callback(struct mqtt_connection *m, void *userdata, mqtt_event_t event, mqtt_data_t *data) { switch (event) { case MQTT_EVENT_CONNECTED: printf("MQTT connected\n"); mqtt_connected = 1; break; case MQTT_EVENT_DISCONNECTED: printf("MQTT disconnected\n"); mqtt_connected = 0; break; case MQTT_EVENT_PUBLISHED: printf("MQTT message published\n"); break; case MQTT_EVENT_SUBACK: printf("MQTT subscribed to topic\n"); break; case MQTT_EVENT_UNSUBACK: printf("MQTT unsubscribed from topic\n"); break; case MQTT_EVENT_DATA: printf("MQTT received message\n"); printf("Topic: %.*s\n", data->topic_name_size, data->topic_name); printf("Message: %.*s\n", data->data_size, (char *)data->data); break; default: break; } }
上滑加载中
推荐直播
-
空中宣讲会 2025年华为软件精英挑战赛
2025/03/10 周一 18:00-19:00
宸睿 华为云存储技术专家、ACM-ICPC WorldFinal经验 晖哥
2025华为软挑赛空中宣讲会重磅来袭!完整赛程首曝+命题天团硬核拆题+三轮幸运抽奖赢参赛助力礼包,与全国优秀高校开发者同台竞技,直通顶尖赛事起跑线!
即将直播
热门标签