jjzjj

基于STM32的正点原子LORA模块通信网络

程云流 2024-06-03 原文

LoRa是semtech公司开发的一种低功耗局域网无线标准,其名称“LoRa”是远距离无线电(Long Range Radio),它最大特点就是在同样的功耗条件下比其他无线方式传播的距离更远,实现了低功耗和远距离的统一,它在同样的功耗下比传统的无线射频通信距离扩大3-5倍。 距离往往可达10公里左右。
笔者在做基于无线通信的火灾网络报警系统时,了解到LORA这一优越的通信方式,想着可以直接拿市面上的来用。没想到在网上购买到了正点原子的实物和代码后发现。正点原子的代码是和其开发板高度绑定的,没有现成的自发自收的LORA代码。

于是想着所谓LORA通信也只是串口通信的一种,而且正点原子的LORA模块已经将其与射频芯片等封装好,并且提供了对应的上位机软件。
所以我要做的只是把STM32的串口部分完成即可。
自发自收:2个STM32最小系统板 ,2个LORA模块,2个LORA模块的上位机软件,1个串口通信的上位机软件 2个OLED显示屏(用于显示接收的数据)
通过串口通信上位机将信息由串口1发送给STM32芯片,芯片(可对其处理后)转发给串口3。串口3连着的LORA模块收到信息后发出。这里可以选择透明传输或者定向传输。透明传输是发送给所有其他的LORA模块,而定向传输是发送给指定的LORA模块,其他的LORA模块收到后不作理会。为了区分LORA模块,在使用之前需要对每个LORA模块做 模块基本参数配置。如下图中间所示。


这里重点讲解一下定向传输:
先将2个LORA模块都配置为定向传输模式,分配其相应的地址,信道等数据。将2个LORA模块的数据都存储到STM32中,最好是外接一个EEPROM,将数据记录在EEPROM中,每次启动STM32,只要将原来的数据从EERPOM中读取一遍就可以了。
如果需要外扩成网络状,如成百上千个网络节点,则需要在EEPROM中记录其相邻的LORA模块节点的数据,如地址。

程序思路:
1.打开串口通信软件界面,将数据通过数据包的形式发送给STM32最小系统板的串口1。数据包应当采取的格式为:目的地址+数据
2.串口1收到后,先根据EEPROM中的网络节点联系表,查出如果要传出给目的地址,则需要传输的下一地址。然后加上自身的地址,以便于数据返回给自身。
数据包的格式应当为:下一地址高位+下一地址低位+信道+目的地址高位+目的地址低位+源地址高位+源地址低位+数据
3.串口3发送此数据包

4.下一地址的LORA模块收到后,将数据通过串口3发给其STM32最小系统板。STM32芯片先比对目的地址与自身地址是否相同,若相同对其进行处理,通过串口3发回给源地址。并将目的地址与源地址进行调换。若不匹配,则重复上述步骤直到发给目的地址。

5.原LORA模块收到后,将数据包去掉目的地址,源地址,信道等数据,发送给串口1。于是串口1就会收到此数据。


温馨提示:这里数据包内部数据设计应当包含2个方面。
1.需要设定问答标志位。如果不设定,那么目的地址收到数据后就可能发给自身的串口1了,而不是发回给串口3。
2.数据的格式应当采取:命令+数据或者 命令
如命令号01 表示删除 ,命令号02表示修改某数据
则 命令号02+数据03 表示 将对应数据改为03

基于LORA的通信网络还应该开发一个上位机软件,此上位机软件应当实现增删改查以下信息。
此节点可以通信的网络节点,参考计算机网络。应当有上级节点,同级节点,下级节点,每个节点的级别。并且有地址类似于子网的概念。每个节点有对应的下属地址范围,下级极点不能超过此地址范围。这样的话,每次转发地址,只需要先查看是否在自身地址范围内,如果在的话,看是哪个下级节点。如果没有则查看同级节点,都没有的话,就转发给上级节点。
如果增删改查需要双方都进行修改,应当先以数据包的形式发送给那个节点,通过逻辑判断是否可行,如果可行,那个节点先修改自身。再将可以修改的信息发给源节点。如果不行的话,也应当指明原因。就像访问网页失败,只要返回个404,访问者就会知道无法找到此网页。

附件是我完成的基于LORA模块的无线火灾网络报警系统。可以实现网络中任意2个节点的通信。不过由于工作原因,关于上一自然段提到的软件一直没有时间去弄。不过完成简单的自收自发是没有问题的。有需要的可以私信我,我看到后会尽快回复。

#ifndef  __AT24C02_H
#define  __AT24C02_H
#include "stm32f10x.h"
 
 
#define AT24C02_I2Cx I2C1    //AT24C02???iic??
#define AT24C02_ADDR  0xA0   //????
 
extern void AT24C02_Init(void); //???
extern uint8_t AT24C02_ReadOneByte(uint8_t ReadAddr);	//??????????
extern void AT24C02_WriteOneByte(uint8_t WriteAddr,uint8_t DataToWrite);	//??????????
extern void AT24C02_Write(uint8_t WriteAddr,uint8_t *Buffer,uint16_t Num);	//????????????????
extern void AT24C02_Read(uint8_t ReadAddr,uint8_t *Buffer,uint16_t Num);   	//????????????????

extern void AT24C02_WriteTwoByte(uint8_t WriteAddr, uint16_t DataToWrite);
extern uint16_t AT24C02_ReadTwoByte(uint8_t ReadAddr);
 
#endif
#ifndef __NODE_H
#define __NODE_H


struct NODE {
	unsigned int father_ip;    //´æ´¢Æä¸¸½ÚµãµÄIPµØÖ·   E2PROM µØÖ· 0X00 0X01
	unsigned int father_range[2];  //¸¸½ÚµãµÄµØÖ··¶Î§               0X10 0X11  0X12 0X13
	unsigned int mate_ip[2];       //ͬʽڵãµÄµØÖ·                 0X20 0X21  0X22 0X23
	unsigned int mate_range[2][2];    //ͬʽڵãµÄµØÖ··¶Î§             0X30 0X31  0X32 0X33   0x34 0x35 0x36 0x37
	unsigned int ip;               //×Ô¼ºµÄµØÖ·                     0X40 0X41
	unsigned int range[2];         //×Ô¼ºµÄµØÖ··¶Î§                 0X50 0X51  0X52 0X53
	unsigned int not_use_ip;       //×Ô¼ºµØÖ··¶Î§ÖÐδÆôÓõijõʼµØÖ· 0X60 0X61
	unsigned int depth;				//×Ô¼ºµÄÉî¶È                          0X70 0X71
	unsigned int block_no;    //ÎïÀíµØÖ·Â¥¶°ºÅ   15                 0X80 0X81
	unsigned int house_no;    //ÎïÀíµØÖ·ÃÅÅÆºÅ   Èç102 203          0X90 0X91
	unsigned int son_ip[4];        //×Ó½ÚµãµÄµØÖ·                   0XA0 0XA1   0XA2 0XA3    0XA4 0xA5 0xA6 0XA7  0xA8 0XA9
	unsigned int son_range[4][2];   //×Ó½ÚµãµÄµØÖ··¶Î§	            0xB0 0XB1   0xB2 0XB3
	                                  //                              0xB4 0Xb5   0xb6 0xb7
	                                 //                               0xB8 0Xb9   0xBA 0XBB
	                                  //                              0XBC 0XBD   0XBE 0XBF
	//µ±Ö÷»úÏò×ӽڵ㴫ÊäÊý¾ÝµÄʱºò£¬ÓÅÏÈÑ¡ÔñÆä×ÔÉíÖ±½Ó°üº¬µÄ£¬Æä´ÎÑ¡Ôñ°üº¬Í¬Ê½ڵã¼ä½Ó´«´ïµÄ
};

#define Node_packet_length 12    //´®¿Ú3µÄÊý¾Ý°ü³¤¶È×Ϊ10¸ö£¬¼ÓÉÏÏÂÒ»¸öµØÖ·2¸öÔòΪ12¸ö

extern struct NODE mynode;

extern uint8_t NODE_Packet[Node_packet_length];

extern uint32_t  search(unsigned char command);

extern uint16_t execute(unsigned char command);

extern unsigned char IS_Current_addr(uint16_t addr);

extern uint16_t  Next_addr(uint16_t addr);

extern void Serial1_3_RxPacket_to_NODE_TXPacket(uint8_t packet[]);

extern uint8_t* deal_center(uint8_t packet[]);

extern void NODE_init();

extern uint8_t* deal_all_data();

extern uint8_t NODE_Packet[Node_packet_length];

extern uint8_t all_data_packet[20][4];

#endif
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);

void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);
void Clear_Serial_RxPacket();
void Serial_RxPacket_to_Serial3_TxPacket();
void Serial_RxPacket_to_Serial_TxPacket();

#endif

#ifndef __I2C_H 
#define __I2C_H
#include "stm32f10x.h"
 
#define IIC_NO_ACK  1
#define IIC_ACK     0
 
#define SCL_CLR()   GPIOB->BRR = GPIO_Pin_12
#define SCL_SET()   GPIOB->BSRR = GPIO_Pin_12
#define SDA_CLR()   GPIOB->BRR  = GPIO_Pin_13
#define SDA_SET()   GPIOB->BSRR  = GPIO_Pin_13
#define SCL_READ()  GPIOB->IDR  & GPIO_Pin_12
#define SDA_READ()  GPIOB->IDR  & GPIO_Pin_13
 
/*SCL???*/
#define AT24C02_SCL_PIN  GPIO_Pin_4
#define AT24C02_SCL_PORT GPIOC
#define AR24C02_SCL_CLK  RCC_APB2Periph_GPIOC
 
/*SDA???*/
#define AT24C02_SCL_PIN  GPIO_Pin_4
#define AT24C02_SCL_PORT GPIOC
#define AR24C02_SCL_CLK  RCC_APB2Periph_GPIOC
 
void I2C_Configuration(void);
extern void I2C_Start(void); //??????
extern void I2C_Stop(void);  //??????
extern void I2C_Send_Byte(uint8_t sebyte); // I2C??????? 
extern uint8_t I2C_Recieve_Byte(void);   // I2C???????
#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Serial3.h"
#include "Key.h"
#include "NODE.h"
#include "i2c.h"
#include "at24c02.h"



//ÔÚOLEDÉÏÏÔʾÊÕ·¢µÄÊý¾Ý°ü£¬µÚÒ»¸ö²ÎÊýΪ´ýÏÔʾµÄÊý¾Ý°ü£¬µÚ¶þ¸ö²ÎÊýΪÏÔʾµÄÆðʼÐУ¬¿ÉÒÔÑ¡Ôñ1»òÕß3
//rowΪ1ʱºò£¬aΪT±íʾ´®¿Ú1µÄ·¢ËÍÊý¾Ý°ü£¬aΪR±íʾ´®¿Ú1µÄ½ÓÊÕÊý¾Ý°ü¡£rowΪ3±íʾ´®¿Ú3µÄÊÕ·¢Êý¾Ý°ü
void OLED_show_Packet(uint8_t* array,uint8_t row,char a)
{
      OLED_ShowChar(row, 1, a);  
	    OLED_ShowHexNum(row, 3, array[0], 2);
			OLED_ShowHexNum(row, 5, array[1], 2);
			OLED_ShowHexNum(row, 8, array[2], 2);
			OLED_ShowHexNum(row, 10, array[3], 2);
			OLED_ShowHexNum(row++, 13, array[4], 2);
			OLED_ShowHexNum(row, 1, array[5], 2);
			if(!(array[6]==0x00 && array[7]==0x00))
   		{		OLED_ShowHexNum(row, 4, array[6], 2);
	      	OLED_ShowHexNum(row, 6, array[7], 2);
				  if(!(array[8]==0x00 && array[9]==0x00)) 
					{	 OLED_ShowHexNum(row, 9, array[8], 2);
	           OLED_ShowHexNum(row, 11, array[9], 2);
					}
			}	
}

int main(void)
{
	OLED_Init();

	Serial_Init();
	Serial3_Init();
	
	
	
	NODE_init();
	
	

//´®¿Ú1 µÄ½ÓÊÕ±ê־Ϊ'A'£¬·¢ËÍΪ'B' ´®¿ÚÈýµÄ½ÓÊÕ±ê־Ϊ'Y',·¢ËÍΪ'Z'
	
	
//	while (1)
//	{
//		if (Serial_GetRxFlag() == 1)    //Èç¹û´®¿Ú1½ÓÊܵ½ÁËÊý¾Ý£¬ÔòÈô®¿Ú3·¢³öÈ¥
//		{	
//			OLED_Clear();
//		  OLED_show_Packet(Serial_RxPacket,1,'R');      
//			Serial_RxPacket_to_Serial3_TxPacket();
//		  Serial3_SendPacket();
//			OLED_show_Packet(Serial3_TxPacket,3,'T');
//		}
//			if (Serial3_GetRxFlag() == 1)  //Èç¹û´®¿Ú3½ÓÊÕµ½ÁËÊý¾Ý£¬ÔòÈô®¿Ú1·¢³öÈ¥ 
//		{
//			OLED_Clear();
//		  OLED_show_Packet(Serial3_RxPacket,3,'R');
//			Serial3_RxPacket_to_Serial_TxPacket();
//			Serial_SendPacket();
//			OLED_show_Packet(Serial_TxPacket,1,'T');
//		}
//	}
//}


	while (1)
	{
	//	AT24C02_WriteTwoByte(0x40, 0x0002);
		uint16_t addr;
		
		if (Serial_GetRxFlag() == 1)    //Èç¹û´®¿Ú1½ÓÊܵ½ÁËÊý¾Ý
		{	
				OLED_Clear();
				OLED_show_Packet(Serial_RxPacket,1,'R');
				if(Serial_RxPacket[0]==Serial_RxPacket[2] && Serial_RxPacket[1]==Serial_RxPacket[3])
				{
					//Èç¹ûÄ¿µÄµØÖ·ÓëÔ´µØÖ·Ò»Ö£¬ÄǾÍÊÇ×Ô·¢×ÔÊÕ£¬ÐèÒªÊDzéѯÅäÖã¬Êý¾Ý½Ï¶à²»ÄÜ×ß´¦ÀíÖÐÐÄ
					//´¦ÀíºóÈô®¿Ú1·¢³öÊý¾Ý
					deal_all_data();
					for(int i=0;i<20;i++)
					  Serial_SendArray(all_data_packet[i],4);
					  
				}
				//
				else{
					//×ß´¦ÀíÖÐÐÄ£¬²¢ÇÒÈô®¿Ú3·¢³öÊý¾Ý
						deal_center(Serial_RxPacket);
						Serial3_SendPacket(NODE_Packet);
						OLED_show_Packet(Serial3_TxPacket,3,'T');
				}			
				
			
		}
		
			if (Serial3_GetRxFlag() == 1)  //Èç¹û´®¿Ú3½ÓÊÕµ½ÁËÊý¾Ý 
		{
			OLED_Clear();
		  OLED_show_Packet(Serial3_RxPacket,3,'R');
		
				addr=Serial3_RxPacket[0]<<8;
			  addr+=Serial3_RxPacket[1];
			
				//Åжϴ®¿Ú3ÊÕµ½µÄÊý¾ÝÊÇ·ñ¾­¹ý´¦ÀíÖÐÐÄ£¬ÒÔ¼°ÊÇÈô®¿Ú3·¢³öÈ¥»¹ÊÇÈô®¿Ú1·¢³öÈ¥
				if(IS_Current_addr(addr) && (Serial3_RxPacket[4]==0x10 ||  Serial3_RxPacket[4]==0x11) )
				{
					Serial3_RxPacket_to_Serial_TxPacket();
					Serial_SendPacket();
					OLED_show_Packet(Serial_TxPacket,1,'T');
				}else{
//					OLED_ShowHexNum(1, 1, mynode.ip , 2);
					deal_center(Serial3_RxPacket);
					Serial3_SendPacket(NODE_Packet);
					OLED_show_Packet(Serial3_TxPacket,3,'T');
				}
			
		}
	}
}



//#include "i2c.h"
//#include "at24c02.h"
// 
//uint8_t data[11],str[12]="hello world";
// 
//int main()
//{
//	uint8_t rece_data;
//	OLED_Init();

//	I2C_Configuration();
//	
//	//AT24C02_WriteOneByte(0x00,0xFE);
//	
//	//rece_data=AT24C02_ReadOneByte(0x00);
//	  
//	NODE_init();
//ÐèÒªÅäÖÃ×ÔÉíµØÖ·£¬×ÔÉíµØÖ··¶Î§
//µÚÒ»¸öÅäÖø¸½ÚµãµØÖ·¼°Æä·¶Î§£¬ÁíÒ»¸öÅäÖÃ×Ó½ÚµãµØÖ·¼°Æä·¶Î§
//µØÖ·Îª1 ·¶Î§Îª1-1024 
	//×Ó½ÚµãµØÖ·Îª2  ·¶Î§Îª2-255
	
//	OLED_ShowHexNum(1, 1, mynode.father_ip, 4);
//  AT24C02_WriteTwoByte(0x00, 0x0001);
//	AT24C02_WriteTwoByte(0x50, 0x0002);
//	AT24C02_WriteTwoByte(0x52, 0x00FF);
	AT24C02_WriteTwoByte(0xA0, 0x0002);
	AT24C02_WriteTwoByte(0xB0, 0x0002);
	AT24C02_WriteTwoByte(0xB2, 0x00FF);
//	//uint16_t data=AT24C02_ReadTwoByte(0x00);
//	AT24C02_WriteTwoByte(0x00, 0x0001);
//	AT24C02_WriteTwoByte(0x12, 0x0400);
	
//	OLED_ShowHexNum(1, 1, mynode.father_range[1], 4);
//	
//	while(1)
//	{
//	
//		
//	}
// 
//	
// 
//}






 
	

有关基于STM32的正点原子LORA模块通信网络的更多相关文章

  1. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  5. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  6. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  7. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  8. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

随机推荐