superw

  • 2024-12-15
  • 加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门

  • 2024-11-18
  • 发表了主题帖: 【X-NUCLEO-53L4A3】+驱动TOF传感器测距

    本帖最后由 superw 于 2024-11-18 09:01 编辑 此贴主要介绍如何使用X-CUBE-TOF1软件包基于自定义的VL53系列TOF传感器进行驱动,并进行基础测距功能,在OLED屏幕上可实时显示。正常使用VL53系列TOF传感器肯定是自己设计的板子,一个VL53L4ED传感器二十来块,一个X-NUCLEO-53L4A3扩展板将近三百,而且扩展板上也没什么其他特殊的IC,选哪个不用多说。   硬件清单 X-NUCLEO-53L4A3、NUCLEO-F401RE、0.96寸OLED屏幕(IIC接口)   测距工程创建 使用STM32CubeIDE创建NUCLEO-F401RE工程过程略过,非常简单,网上教程也一大堆。 配置VL53L4ED需要的IIC接口和GPIO,以及OLED需要的IIC接口。 根据X-NUCLEO-53L4A3原理图,板载的VL53L4ED与STM32F401RE的PB8(SCL)、PB9(SDA)连接,并且时钟线和数据线都通过上拉电阻拉高,可以使用GPIO软件模拟IIC时序驱动,也可以使用STM32F401RE片上的硬件IIC控制器驱动。VL53L4ED的XSHUT引脚与PB3连接,通过上拉电阻拉高,用做GPIO输出;GPIO1引脚与PA4连接,通过上拉电阻拉高,用做中断输入。OLED屏幕与STM32F401RE的PA8(SCL)、PB4(SDA)连接,并且时钟线和数据线都通过上拉电阻拉高,可以使用GPIO软件模拟IIC时序驱动,也可以使用STM32F401RE片上的硬件IIC控制器驱动。 下面对于VL53L4ED和OLED屏幕,都采用硬件IIC驱动,PB8、PB9对应IIC1,PA8、PB4对应IIC3。 引脚配置如上图所示,在创建工程时,STM32CubeF4软件包默认将下载接口、调试USART接口、板载按键、板载LED配置完成,剩下需要自己配置VL53L4ED和OLED屏幕用到的IO资源。 VL53L4ED使用I2C总线进行通信,最大支持1MHz,地址为0x52。开启STM32F401RE的I2C1,模式为Fast Mode,时钟频率为400KHz。 OLED使用I2C总线进行通信,最大支持1MHz,地址为0x52。开启STM32F401RE的I2C3,模式为Fast Mode,时钟频率为400KHz。 选择Middleware and Software Packs,接着选择X-CUBE-TOF1软件包,在里面选择器件为VL53L4ED,板子为custom,应用为53L4A3_SimpleRanging,之后点击Ok即可。 重新进入X-CUBE-TOF1软件包,将软件包需要的IP与STM32F401RE配置的一一对应即可,之后生成代码。 在Drivers/BSP目录下创建OLED文件夹,将OLED相关的I2C驱动复制进去,把相关I2C操作的句柄改为I2C3。 X-CUBE-TOF1软件包默认创建了app_tof.c、app_tof.h应用层代码,可以在main函数里,直接调用MX_TOF_Init、MX_TOF_Process函数完成测距。 MX_TOF_Init通过函数调用完成USART2打印串口初始化,VL53L4ED传感器初始化。 MX_TOF_Process通过获取VL53L4ED器件ID,获取属性,配置TOF后,开启TOF传感器,并间隔POLLING_PERIOD周期获取一次测距结果并打印出来。 测距结果包括目标NumberOFTargets数量,Distance距离,Status状态,Ambient环境光,Signal信号速率。需要注意,只有当Status状态值为0时,才代表TOF传感器捕捉到目标物体,当获取到状态值不为0的信息时,可以直接舍去,无意义。 static void display_result(RANGING_SENSOR_Result_t *Result) { uint8_t i; uint8_t j; char message[50]; for (i = 0; i < RANGING_SENSOR_MAX_NB_ZONES; i++) { OLED_NewFrame(); sprintf(message, "Targets = %lu", (unsigned long)Result->ZoneResult[i].NumberOfTargets); OLED_PrintString(0, 0, message, &font12x12, OLED_COLOR_NORMAL); for (j = 0; j < Result->ZoneResult[i].NumberOfTargets; j++) { sprintf(message, "Status = %ld", (long)Result->ZoneResult[i].Status[j]); OLED_PrintString(0, 20, message, &font12x12, OLED_COLOR_NORMAL); sprintf(message, "Distance = %ld mm", (long)Result->ZoneResult[i].Distance[j]); OLED_PrintString(0, 35, message, &font12x12, OLED_COLOR_NORMAL); } OLED_ShowFrame(); } } static void MX_VL53L4ED_SimpleRanging_Process(void) { uint32_t Id; CUSTOM_RANGING_SENSOR_ReadID(CUSTOM_VL53L4ED, &Id); CUSTOM_RANGING_SENSOR_GetCapabilities(CUSTOM_VL53L4ED, &Cap); Profile.RangingProfile = VL53L4ED_PROFILE_CONTINUOUS; Profile.TimingBudget = TIMING_BUDGET; Profile.Frequency = 0; /* Induces intermeasurement period, NOT USED for normal ranging */ Profile.EnableAmbient = 1; /* Enable: 1, Disable: 0 */ Profile.EnableSignal = 1; /* Enable: 1, Disable: 0 */ /* set the profile if different from default one */ CUSTOM_RANGING_SENSOR_ConfigProfile(CUSTOM_VL53L4ED, &Profile); status = CUSTOM_RANGING_SENSOR_Start(CUSTOM_VL53L4ED, RS_MODE_BLOCKING_CONTINUOUS); while (1) { /* polling mode */ status = CUSTOM_RANGING_SENSOR_GetDistance(CUSTOM_VL53L4ED, &Result); if (status == BSP_ERROR_NONE) { print_result(&Result); display_result(&Result); } HAL_Delay(POLLING_PERIOD); } }   前面已经将OLED显示驱动复制到工程里,需要重新构建一个显示测距结果的函数display_result,与print_result一块调用,就可以在串口和OLED显示屏上分别看到测距结果。   实现效果 [localvideo]8a527328aed246189c5dd9f2f4e0005f[/localvideo] 尺子长度为30cm,视频中可以看到实际测距效果误差在mm之内

  • 发表了主题帖: 【X-NUCLEO-53L4A3】+TOF传感器开箱

    本帖最后由 superw 于 2024-11-18 08:50 编辑 本次评测提供的开发套件由X-NUCLEO-53L4A3和NUCLEO-F401RE组成,其中NUCLEO-F401RE属于经典的NUCLEO-64系列开发板,X-NUCLEO-53L4A3属于TOF传感器扩展板。   一、X-NUCLEO-53L4A3扩展板 X-NUCLEO-53L4A3扩展板由扩展板本身、两个盖片和三个垫片组成。其中垫片的规格分别为0.25mm,0.5mm,1mm,用来模拟不同的空气间隙。盖片用来保护传感器内部的红外传感器不受灰尘侵袭和外部因素干扰,特别是在高粉尘环境下作用明显。 X-NUCLEO-53L4A3扩展板板载一个VL53L4ED TOF传感器,其衍生型号有VL53L4CD,看数据手册了解到,VL53L4ED应该是在VL53L4CD基础上进一步扩展了其工作温度范围,支持在-40°C~ 105°C范围内工作,其余特点二者一样。 此外,X-NUCLEO-53L4A3扩展板还支持外接两个SATEL-VL53L4ED模块和一个具有IIC接口的OLED模块。   二、NUCLEO-F401RE开发板 NUCLEO-F401RE开发板采用STM32F401RE作为MCU,板载ST-LINK v2下载器,MCU所有IO在开发板上以排针和Arduino连接器的形式引出。   三、装配 将X-NUCLEO-53L4A3扩展板与NUCLEO-F401RE开发板通过各自的Arduino连接器对插,并且把0.96寸OLED显示模块接入,默认提供的盖片贴到传感器表面上,用来防止灰尘,这样就得到上面图片的形式。接入OLED模块时,需要注意自己的OLED模块和X-NUCLEO-53L4A3扩展板上的OLED接口线序是否相符,X-NUCLEO-53L4A3扩展板上的OLED接口从下到上分别为GND、VCC、SCL、SDA。 通过STM32F401RE的IIC1控制VL53L4ED,对其进行初始化,发送测距指令等,IIC3控制OLED进行显示。   四、软件需求 对于VL53L4ED传感器的驱动,ST提供了X-CUBE-TOF1的软件包,支持全系列的TOF传感器和官方扩展板。 对于NUCLEO-F401RE开发板,打算使用STM32CubeIDE进行开发,具有代码编辑、编译、链接、调试、图形化配置等多种功能。 开发时需要注意VL53L4ED的IIC地址唯一且不可更改,为0x52。   五、相关资料汇总 X-NUCLEO-53L4A3扩展板介绍,包含原理图、用户手册、规格书等内容。 NUCLEO-F401RE开发板介绍,包含原理图、用户手册、规格书等内容。 X-CUBE-TOF1软件包下载地址https://www.st.com/en/ecosystems/x-cube-tof1.html?ecmp=tt9470_gl_link_feb2019&rt=um&id=UM3222#overview STM32CubeIDE下载地址https://www.st.com.cn/content/st_com/zh/stm32cubeide.html

  • 2024-11-14
  • 加入了学习《PX4固件二次开发课程》,观看 飞控软硬体系介绍1

  • 2024-10-08
  • 发表了主题帖: 【Follow me第二季第2期】+项目总结

    本帖最后由 superw 于 2024-11-6 02:00 编辑 文章中各任务均由图片中物料实现 物料清单: 厂商 厂商料号 名称 得捷链接 Arduino ABX00087  Arduino UNO R4 WiFi https://www.digikey.cn/zh/products/detail/arduino/ABX00087/20371539?s=N4IgTCBcDaIIICEAaAGNAOA7AApAXQF8g Adafruit 4991 Adafruit I2C STEMMA QT Rotary Encoder Breakout with Neopixel https://www.digikey.cn/zh/products/detail/adafruit-industries-llc/4991/14302511 Adafruit 5591 Adafruit LTR-329 Light Sensor https://www.digikey.cn/zh/products/detail/adafruit-industries-llc/5591/16733167   一、入门任务(必做):搭建环境并开启第一步Blink / 串口打印Hello EEWorld!   Arduino官方的板子,当然要用Arduino IDE来开发,在Arduino官网根据自己的操作系统下载对应的安装程序,一路next即可。当然电脑硬盘不够的也可以选择使用便携版IDE或者浏览器开发。 安装完成后,搭建环境。按照官方指南https://docs.arduino.cc/tutorials/uno-r4-wifi/r4-wifi-getting-started/,安装Board Package后,即可进行编译下载代码。 硬件分析:   板载DL4通过MOS管驱动,当P102输出高电平点亮DL4,当P102输出低电平熄灭DL4。 软件实现: 通过在loop函数中循环翻转P102电平,并附加相应延时,即可实现板载LED闪烁。Arduino官方开发板默认串口使用Serial.begin进行初始化,其他通信串口使用Serialx.begin初始化(x=1,2,...),串口初始化成功后,可通过print,println函数进行打印。 代码实现: void setup() { // put your setup code here, to run once: Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); } void loop() { // put your main code here, to run repeatedly: Serial.println("Hello EEWorld!"); digitalWrite(LED_BUILTIN, HIGH); delay(1000); digitalWrite(LED_BUILTIN, LOW); delay(1000); }   上面代码实现任务一,控制板载LED Blink,同时通过串口打印Hello EEWorld! 实现效果:   二、基础任务(必做):驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线 对于Arduino UNO R4 WiFi板上资源,官方提供了详细的指南。 对于点阵LED,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/ 对于片上DAC,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/dac/ 对于片上OPAMP,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/opamp/ 对于片上ADC,可参考https://docs.arduino.cc/tutorials/uno-r4-wifi/adc-resolution/ 综合根据上面指南,可以完成任务二。 硬件分析:本任务涉及到的硬件资源,开发板上均已具备,且对于DAC、OPAMP、ADC均为MCU片上资源,直接通过对应函数操作硬件即可。 对于OPAMP放大DAC信号,需要使用面包板,通过片上OPAMP运放单元搭建一个简易的运算放大器。原理图如下: 上面的实现是一个电压跟随器,但电压跟随器可以理解为一个特殊的运算放大器,增益为1,输出电压与输入电压同幅值,同频率。电压跟随器在电路中经常起到缓冲和隔离的作用。 将A0(DAC输出)接入A1(OPAMP的正向输入端),A2(OPAMP的反向输入端)和A3短接(OPAMP的输出端),此时便可在A3用示波器观察到与A0的输出波形一致。将A3(OPAMP的输出端)与A4(ADC采集)短接,便可以获取到ADC采集到的信号电压。   代码实现: #include "analogWave.h" #include "OPAMP.h" #include "ArduinoGraphics.h" #include "Arduino_LED_Matrix.h" analogWave wave(DAC); ArduinoLEDMatrix matrix; int freq = 10; void setup() { // put your setup code here, to run once: Serial.begin(115200); wave.sine(freq); OPAMP.begin(OPAMP_SPEED_HIGHSPEED); analogReadResolution(14); matrix.begin(); } void loop() { // put your main code here, to run repeatedly: int adc_value = analogRead(A4); Serial.println("current adc value is " + String(adc_value)); // Make it scroll! matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textScrollSpeed(50); // add the text const char text[] = " EEWorld & DigiKey "; matrix.textFont(Font_4x6); matrix.beginText(0, 1, 0xFFFFFF); matrix.println(text); matrix.endText(SCROLL_LEFT); matrix.endDraw(); } 实现效果: 对于12x8点阵LED,上电后会看到EEWorld & DigiKey的字样。 手机相机拍的不是很清,人眼视觉效果会相对好点。 用DAC生成正弦波,并经过OPAMP放大DAC信号,现象可通过示波器观察得到。 由于手里没有插件电阻,将片上的OPAMP运放配置为电压跟随器模式,即输入为什么信号,输出就是什么信号。图中黄色为原始DAC正弦波信号,绿色为经过电压跟随器后输出的正弦波信号,二者幅值频率均相等。 用ADC采集生成的正弦波信号,并打印到串口,借助Arduino IDE的串口上位机可将波形大致呈现出来。   三、进阶任务(必做):通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant) 四、扩展任务一:通过外部LTR-329 环境光传感器,上传光照度到HA,通过HA面板显示数据 首先,非常感谢这期老师给大家带来的直播任务讲解,里面清楚的介绍了如何在windows平台上搭建HomeAssistant环境。当然,我自己也尝试了在泰山派上通过docker安装HomeAssistant,但貌似是由于Docker Hub不稳定的问题,总是访问超时,在ubuntu上也不会魔法上网,只能放弃。后来又尝试安装CasaOS(系统本身已具有docker),但最终还是需要通过docker访问HomeAssistant的Docker Hub,也未能完整实现。最终选择了直播中老师讲解的方法,在windows上安装Docker Desktop,之后拉取HomeAssistant镜像,并创建容器。 具体搭建过程可参考直播讲解https://training.eeworld.com.cn/video/40793 HomeAssistant界面 EMQX(MQTT)服务器界面 硬件分析: 上图为任务推荐的LTR329光照传感器模块,板载Qwiic接口。对应Adafruit采购型号为5591。 上图为任务之外选购的编码器模块,板载Qwiic接口。对应Adafruit采购型号为4991。板载一个内置固件的MCU,采集板载的编码器信息,并且支持外部IIC总线访问来获取。 上图为Arduino开发板的J2接口,硬件形式为Qwiic,可通过SH1.0排线将上述两个模块串联到一块,共同挂载在IIC总线上,通过IIC主机寻址不同的设备地址,访问设备。 注意:LTR329的IIC地址为0x29,编码器模块的IIC地址为0x36。 软件实现: 打开Library Manager,搜索框输入LTR329,安装对应的Library。详细介绍及驱动代码可见https://learn.adafruit.com/adafruit-ltr-329-ltr-303/overview 本次活动选择的另一个器件是I2C QT ROTARY ENCODER,片上集成了一个编码器和一个WS2812,单片机下载seesaw固件,通过I2C接口获取命令,进一步通过单片机读取编码器的值,控制WS2812。搜索框输入seesaw,安装对应的Library。详细介绍及驱动代码可见https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder 当然,要通过WiFi接入HA平台,最重要的还需要一个home-assistant-integration库,通过这个库,可以创建各种设备类型,包括灯、按键、传感器、锁等等。具体库介绍可参考https://dawidchyrzynski.github.io/arduino-home-assistant/index.html 环境搭建和各种library安装好之后,就可以进行代码编写。 代码实现: #include "WiFiS3.h" #include "arduino_secrets.h" #include "Adafruit_LTR329_LTR303.h" #include <ArduinoHA.h> #include <seesaw_neopixel.h> #define MQTT_OBJECT_ID "followme" #define BROKER_ADDR IPAddress(192,168,10,246) #define MQTT_PORT 1883 #define MQTT_USERNAME "admin" #define MQTT_PASSWORD "123" #define SS_NEOPIX 6 #define SEESAW_ADDR 0x36 seesaw_NeoPixel sspixel = seesaw_NeoPixel(1, SS_NEOPIX, NEO_GRB + NEO_KHZ800, &Wire1); Adafruit_LTR329 ltr = Adafruit_LTR329(); WiFiClient client; HADevice device(MQTT_OBJECT_ID); HAMqtt mqtt(client, device); HAButton buttonR("myButtonR"); HAButton buttonG("myButtonG"); HAButton buttonB("myButtonB"); HASensorNumber lightSensor("mylight"); char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) int status = WL_IDLE_STATUS; unsigned long lastUpdateAt = 0; void onButtonCommand(HAButton* sender) { if (sender == &buttonR) { // button A was clicked, do your logic here sspixel.setPixelColor(0, sspixel.Color(255, 0, 0)); } else if (sender == &buttonG) { // button B was clicked, do your logic here sspixel.setPixelColor(0, sspixel.Color(0, 255, 0)); } else if (sender == &buttonB) { sspixel.setPixelColor(0, sspixel.Color(0, 0, 255)); } sspixel.show(); } void setup() { // put your setup code here, to run once: Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } wifi_init(); ltr329_init(); pixel_init(); device.setName("Arduino"); device.setSoftwareVersion("1.0.0"); buttonR.setIcon("mdi:fire"); buttonR.setName("RED"); buttonG.setIcon("mdi:home"); buttonG.setName("GREEN"); buttonB.setIcon("mdi:water"); buttonB.setName("BLUE"); buttonR.onCommand(onButtonCommand); buttonG.onCommand(onButtonCommand); buttonB.onCommand(onButtonCommand); lightSensor.setIcon("mdi:home"); lightSensor.setName("light value"); mqtt.begin(BROKER_ADDR, MQTT_PORT, MQTT_USERNAME, MQTT_PASSWORD); } void loop() { // put your main code here, to run repeatedly: bool valid; uint16_t visible_plus_ir, infrared; mqtt.loop(); if ((millis() - lastUpdateAt) > 1000) { // 1000ms debounce time if (ltr.newDataAvailable()) { valid = ltr.readBothChannels(visible_plus_ir, infrared); if (valid) { Serial.print("CH0 Visible + IR: "); Serial.print(visible_plus_ir); Serial.print("\t\tCH1 Infrared: "); Serial.println(infrared); lightSensor.setValue(visible_plus_ir); } } lastUpdateAt = millis(); } } void wifi_init() { // check for the WiFi module: if (WiFi.status() == WL_NO_MODULE) { Serial.println("Communication with WiFi module failed!"); // don't continue while (true); } String fv = WiFi.firmwareVersion(); if (fv < WIFI_FIRMWARE_LATEST_VERSION) { Serial.println("Please upgrade the firmware"); } // attempt to connect to WiFi network: while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.begin(ssid, pass); // wait 10 seconds for connection: delay(10000); } printWifiStatus(); } void ltr329_init() { Serial.println("Adafruit LTR-329 advanced test"); if ( ! ltr.begin(&Wire1) ) { Serial.println("Couldn't find LTR sensor!"); while (1) delay(10); } Serial.println("Found LTR sensor!"); ltr.setGain(LTR3XX_GAIN_2); ltr.setIntegrationTime(LTR3XX_INTEGTIME_100); ltr.setMeasurementRate(LTR3XX_MEASRATE_200); } void pixel_init() { Serial.println("Looking for seesaw!"); if (!sspixel.begin(SEESAW_ADDR)) { Serial.println("ERROR! seesaw not found"); while(1) delay(1); } Serial.println("seesaw started"); // set not so bright! sspixel.setBrightness(20); sspixel.show(); } /* -------------------------------------------------------------------------- */ void printWifiStatus() { /* -------------------------------------------------------------------------- */ // print the SSID of the network you're attached to: Serial.print("SSID: "); Serial.println(WiFi.SSID()); // print your board's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); // print the received signal strength: long rssi = WiFi.RSSI(); Serial.print("signal strength (RSSI):"); Serial.print(rssi); Serial.println(" dBm"); } 上面代码实现了两个功能,一个是将LTR-329获取到的光照强度上传到HA平台,另一个是通过HA平台创建三个按键,分别用来控制WS2812的红、绿、蓝三种颜色。 硬件连接如图 实现效果 在HA平台上可看见上传的光照强度值,具体现象可见视频。 五、心得体会 非常感谢官方提供的Arduino板卡,这块板子22年发布的,在技术创新和功能上都非常有特色,最感兴趣的还是上面12x8点阵LED了,可以生成表情、动物、字符等信息。另外,这期活动还跟着教程学会了home assistant的搭建和使用,后面也要继续尝试在泰山派上成功搭建起来。最后希望follow me活动越办越好,给大家带来更多更优秀的大厂开发板。 六、可编译下载的代码      

  • 加入了学习《【Follow me第二季第2期】+视频介绍》,观看 【Follow me第二季第2期】

  • 加入了学习《FollowMe 第二季:2 - Arduino UNO R4 Wi-Fi 及任务讲解》,观看 Arduino UNO R4 Wi-Fi 及任务讲解

  • 2024-09-12
  • 加入了学习《Digi-Key: Follow Me 系列(2) 直播回放》,观看 Adafruit ESP32-S3 TFT Feather开发板使用入门

  • 2024-03-02
  • 加入了学习《泰克MSO6B探索营》,观看 MSO6B强大的测试和分析功能

  • 加入了学习《泰克MSO6B探索营》,观看 MSO6B多通道同步频谱分析

  • 加入了学习《泰克MSO6B探索营》,观看 如何完成AI芯片电源噪声测试

  • 2024-03-01
  • 加入了学习《泰克MSO6B探索营》,观看 MSO6B系列低噪声演示

  • 加入了学习《泰克MSO6B探索营》,观看 MSO6B技术介绍

  • 加入了学习《泰克MSO6B探索营》,观看 MSO6B-360度介绍

  • 2024-02-26
  • 加入了学习《 得捷电子Follow me第4期》,观看 得捷电子Follow me第4期

  • 发表了主题帖: 【得捷电子Follow me第4期】+项目总结

    本帖最后由 superw 于 2024-2-26 11:28 编辑 内容一、演示视频   内容二、 项目总结报告 入门任务:开发环境搭建,BLINK,驱动液晶显示器进行显示(没有则串口HelloWorld) # 在这里写上你的代码 :-) import time import board import busio import digitalio import adafruit_sharpmemorydisplay def display_init(): #SPI1 Display SPI1_SCK = board.GP10 SPI1_MOSI = board.GP11 SPI1_CSn = board.GP13 #配置display的SPI display_spi = busio.SPI(SPI1_SCK, SPI1_MOSI) display_scs = digitalio.DigitalInOut(SPI1_CSn) display = adafruit_sharpmemorydisplay.SharpMemoryDisplay(display_spi, display_scs, 144, 168, baudrate=8000000) return display def led_init(): #配置LED led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT return led led=led_init() display=display_init() print("DigiKey & eeworld") print("Follow me") print("W5500-EVB-Pico with rp2040") display.fill(0) display.text(" DigiKey & eeworld", 0, 10, 1) display.text(" Follow me", 0, 20, 1) display.text(" W5500-EVB-Pico ", 0, 30, 1) display.text(" with rp2040 ", 0, 40, 1) display.show() while True: led.value = not led.value time.sleep(0.5)   通过使用adafruit_sharpmemorydisplay库完成液晶显示器的驱动,同时点亮板载LED。 基础任务一:完成主控板W5500初始化(静态IP配置),并能使用局域网电脑ping通,同时W5500可以ping通互联网站点;通过抓包软件(Wireshark、Sniffer等)抓取本地PC的ping报文,展示并分析。 # 在这里写上你的代码 :-) import board import busio import digitalio import time import adafruit_sharpmemorydisplay from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K def led_init(): #配置LED led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT return led def display_init(): #SPI1 Display SPI1_SCK = board.GP10 SPI1_MOSI = board.GP11 SPI1_CSn = board.GP13 #配置display的SPI display_spi = busio.SPI(SPI1_SCK, SPI1_MOSI) display_scs = digitalio.DigitalInOut(SPI1_CSn) display = adafruit_sharpmemorydisplay.SharpMemoryDisplay(display_spi, display_scs, 144, 168, baudrate=8000000) return display def w5500_init(): #SPI0 Ethernet SPI0_SCK = board.GP18 SPI0_TX = board.GP19 SPI0_RX = board.GP16 SPI0_CSn = board.GP17 #Ethernet reset W5500_RSTn = board.GP20 ethernetRst = digitalio.DigitalInOut(W5500_RSTn) ethernetRst.direction = digitalio.Direction.OUTPUT # Setup your network configuration below # random MAC, later should change this value on your vendor ID MY_MAC = (0x00, 0x01, 0x02, 0x03, 0x04, 0x05) IP_ADDRESS = (192, 168, 1, 100) SUBNET_MASK = (255, 255, 255, 0) GATEWAY_ADDRESS = (192, 168, 1, 1) DNS_SERVER = (8, 8, 8, 8) # For Adafruit Ethernet FeatherWing eth_cs = digitalio.DigitalInOut(SPI0_CSn) # For Particle Ethernet FeatherWing # cs = digitalio.DigitalInOut(board.D5) eth_spi = busio.SPI(SPI0_SCK, MOSI=SPI0_TX, MISO=SPI0_RX) # Reset W5500 first ethernetRst.value = False time.sleep(1) ethernetRst.value = True # Initialize ethernet interface with DHCP # eth = WIZNET5K(spi_bus, cs) # Initialize ethernet interface without DHCP eth = WIZNET5K(eth_spi, eth_cs, is_dhcp=False, mac=MY_MAC) # Set network configuration eth.ifconfig = (IP_ADDRESS, SUBNET_MASK, GATEWAY_ADDRESS, DNS_SERVER) print("Chip Version:", eth.chip) print("MAC Address:", [hex(i) for i in eth.mac_address]) print("My IP address is:", eth.pretty_ip(eth.ip_address)) display.text("Chip Version:"+eth.chip,0,20,0) display.text("MAC Address:",0,30,0) display.text(eth.pretty_mac(eth.mac_address),0,40,0) display.text("My IP address is:",0,50,0) display.text(eth.pretty_ip(eth.ip_address),0,60,0) display.show() return eth led = led_init() display = display_init() print("Wiznet5k Ping Test (no DHCP)") display.fill(1) display.text("Wiznet5k Ping Test ",0,10,0) display.show() eth = w5500_init() while True: if eth.link_status == False: print("PHY link is down...") display.text("PHY link is down...",0,70,0) display.show() time.sleep(0.1) else: print("PHY link is up") print("W5500 Connect Ethernet") display.text("PHY link is up",0,80,0) display.text("W5500 Connect Ethernet",0,90,0) display.show() break while True: led.value = not led.value time.sleep(0.5)   circuitpython下由于水平有限,目前只实现了PC端主动ping设备,设备主动实现ping电脑暂未实现,后面继续学习。     抓包如下:   基础任务二:主控板建立TCPIP或UDP服务器,局域网PC使用TCPIP或UDP客户端进行连接并发送数据,主控板接收到数据后,送液晶屏显示(没有则通过串口打印显示);通过抓包软件抓取交互报文,展示并分析。(TCP和UDP二选一,或者全都操作) import board import busio import digitalio import time import adafruit_sharpmemorydisplay from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket def led_init(): #配置LED led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT return led def display_init(): #SPI1 Display SPI1_SCK = board.GP10 SPI1_MOSI = board.GP11 SPI1_CSn = board.GP13 #配置display的SPI display_spi = busio.SPI(SPI1_SCK, SPI1_MOSI) display_scs = digitalio.DigitalInOut(SPI1_CSn) display = adafruit_sharpmemorydisplay.SharpMemoryDisplay(display_spi, display_scs, 144, 168, baudrate=8000000) return display def w5500_init(): #SPI0 Ethernet SPI0_SCK = board.GP18 SPI0_TX = board.GP19 SPI0_RX = board.GP16 SPI0_CSn = board.GP17 #Ethernet reset W5500_RSTn = board.GP20 ethernetRst = digitalio.DigitalInOut(W5500_RSTn) ethernetRst.direction = digitalio.Direction.OUTPUT # Setup your network configuration below # random MAC, later should change this value on your vendor ID MY_MAC = (0x00, 0x01, 0x02, 0x03, 0x04, 0x05) IP_ADDRESS = (192, 168, 1, 100) SUBNET_MASK = (255, 255, 255, 0) GATEWAY_ADDRESS = (192, 168, 1, 1) DNS_SERVER = (8, 8, 8, 8) # For Adafruit Ethernet FeatherWing eth_cs = digitalio.DigitalInOut(SPI0_CSn) # For Particle Ethernet FeatherWing # cs = digitalio.DigitalInOut(board.D5) eth_spi = busio.SPI(SPI0_SCK, MOSI=SPI0_TX, MISO=SPI0_RX) # Reset W5500 first ethernetRst.value = False time.sleep(1) ethernetRst.value = True # Initialize ethernet interface with DHCP # eth = WIZNET5K(spi_bus, cs) # Initialize ethernet interface without DHCP eth = WIZNET5K(eth_spi, eth_cs, is_dhcp=False, mac=MY_MAC) # Set network configuration eth.ifconfig = (IP_ADDRESS, SUBNET_MASK, GATEWAY_ADDRESS, DNS_SERVER) print("Chip Version:", eth.chip) print("MAC Address:", [hex(i) for i in eth.mac_address]) print("My IP address is:", eth.pretty_ip(eth.ip_address)) display.text("Chip Version:"+eth.chip,0,20,0) display.text("MAC Address:",0,30,0) display.text(eth.pretty_mac(eth.mac_address),0,40,0) display.text("My IP address is:",0,50,0) display.text(eth.pretty_ip(eth.ip_address),0,60,0) display.show() return eth def create_tcp_socket(mode): if mode == 1: #创建tcp server # Initialize a socket for our server socket.set_interface(eth) tcp_server = socket.socket() # Allocate socket for the server server_ip = None # IP address of server server_port = 5000 # Port to listen on tcp_server.bind((server_ip, server_port)) # Bind to IP and Port tcp_server.listen() # Begin listening for incoming clients conn, addr = tcp_server.accept() # Wait for a connection from a client. print("socket connected") return conn else: #创建tcp client print("create tcp client") def create_udp_socket(mode): if mode == 1: #创建udp server # Initialize a socket for our server socket.set_interface(eth) udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # Allocate socket for the server server_ip = '192.168.1.59' # IP address of server server_port = 5000 # Port to listen on port = 5000 udp_server.bind((eth.pretty_ip(eth.ip_address),port)) print("socket connected") else: #创建tcp client print("open socket") print("Wiznet5k SimpleServer Test ") led = led_init() display = display_init() display.fill(1) display.text("Wiznet5k SimpleServer ",0,10,0) display.show() eth = w5500_init() conn=create_tcp_socket(1) display.text("client connected ",0,80,0) display.text("recv data: ",0,90,0) display.show() while True: led.value = not led.value time.sleep(0.5) with conn: # data = conn.recv() # print(data) # conn.send(data) # Echo message back to client while True: data = conn.recv(10) if data: print(data) display.text(hex(i) for i in data,0,100,0) display.show() conn.send(data) # Echo message back to client break print("Done!")   通过在板端构建TCP服务器,抓包可以明显看到其三次握手、四次挥手的过程,以及每次数据传输中需要频繁ACK,证实了TCP属于一种可靠且稳定的网络通信协议。 现象如图:   进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示 import board import busio import digitalio import time import adafruit_requests as requests from adafruit_wiznet5k.adafruit_wiznet5k import * import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket from adafruit_wiznet5k.adafruit_wiznet5k_ntp import NTP import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns days = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") def led_init(): #配置LED led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT return led def display_init(): #SPI1 Display SPI1_SCK = board.GP10 SPI1_MOSI = board.GP11 SPI1_CSn = board.GP13 #配置display的SPI display_spi = busio.SPI(SPI1_SCK, SPI1_MOSI) display_scs = digitalio.DigitalInOut(SPI1_CSn) display = adafruit_sharpmemorydisplay.SharpMemoryDisplay(display_spi, display_scs, 144, 168, baudrate=8000000) return display def w5500_init(): #SPI0 Ethernet SPI0_SCK = board.GP18 SPI0_TX = board.GP19 SPI0_RX = board.GP16 SPI0_CSn = board.GP17 #Ethernet reset W5500_RSTn = board.GP20 ethernetRst = digitalio.DigitalInOut(W5500_RSTn) ethernetRst.direction = digitalio.Direction.OUTPUT # Setup your network configuration below # random MAC, later should change this value on your vendor ID MY_MAC = (0x00, 0x01, 0x02, 0x03, 0x04, 0x05) IP_ADDRESS = (192, 168, 1, 100) SUBNET_MASK = (255, 255, 255, 0) GATEWAY_ADDRESS = (192, 168, 1, 1) DNS_SERVER = (8, 8, 8, 8) # For Adafruit Ethernet FeatherWing eth_cs = digitalio.DigitalInOut(SPI0_CSn) # For Particle Ethernet FeatherWing # cs = digitalio.DigitalInOut(board.D5) eth_spi = busio.SPI(SPI0_SCK, MOSI=SPI0_TX, MISO=SPI0_RX) # Reset W5500 first ethernetRst.value = False time.sleep(1) ethernetRst.value = True # Initialize ethernet interface with DHCP # eth = WIZNET5K(spi_bus, cs) # Initialize ethernet interface without DHCP eth = WIZNET5K(eth_spi, eth_cs, is_dhcp=False, mac=MY_MAC) # Set network configuration eth.ifconfig = (IP_ADDRESS, SUBNET_MASK, GATEWAY_ADDRESS, DNS_SERVER) print("Chip Version:", eth.chip) print("MAC Address:", [hex(i) for i in eth.mac_address]) print("My IP address is:", eth.pretty_ip(eth.ip_address)) display.text("Chip Version:"+eth.chip,0,20,0) display.text("MAC Address:",0,30,0) display.text(eth.pretty_mac(eth.mac_address),0,40,0) display.text("My IP address is:",0,50,0) display.text(eth.pretty_ip(eth.ip_address),0,60,0) display.show() return eth print("Wiznet5k ntp Test ") led = led_init() display = display_init() display.fill(1) display.text("Wiznet5k ntp Test",0,10,0) display.show() eth = w5500_init() # Initialize a socket for our server #socket.set_interface(eth) # Set network configuration #eth.ifconfig = (IP_ADDRESS, SUBNET_MASK, GATEWAY_ADDRESS, DNS_SERVER) #NTP ntpserver_ip = eth.pretty_ip(eth.get_host_by_name("time.google.com")) print("NTP : %s" % ntpserver_ip) #DNS Domain ntp = NTP(iface = eth, ntp_address =ntpserver_ip ,utc=9) cal = ntp.get_time() print("The date is %s %d/%d/%d" %(days[cal.tm_wday], cal.tm_mday,cal.tm_mon,cal.tm_year)) print("The time is %d:%02d:%02d" %(cal.tm_hour,cal.tm_min,cal.tm_sec))   该任务官方已经提供了示例程序,基于此又重新编写学习一下,但是不知道为什么,跑官方还有自己重新编写的程序都总是报错,提示少传入一个参数,后来又跟论坛上坛友发的贴子实现过程对照了一下,没什么太大出入,copy坛友的程序也跑不下来,不清楚什么原因。 现象如下:   DNS服务器已经根据域名解析出了目标NTP服务器的IP地址,证明网络应该是没问题的。 终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。 使用一个SD NAND卡+一个TF卡的转接板,将SD NAND卡的信号引脚引出,用杜邦线通过SPI1接到SD NAND上。   根据坛友分享的帖子,大致了解了FTP的实现流程,分别需要两个socket,一个用来做控制,包括登录认证之类的,另一个用来做文件传输,并且FTP是基于TCP传输层协议的,另外还知道了TFTP是另一种基于UDP传输层协议的应用。 下面根据坛友分享的进行学习 # # Small ftp server for ESP8266 ans ESP32 Micropython # # Based on the work of chrisgp - Christopher Popp and pfalcon - Paul Sokolovsky # # The server accepts passive mode only. # It runs in foreground and quits, when it receives a quit command # Start the server with: # # import ftp # # Copyright (c) 2016 Christopher Popp (initial ftp server framework) # Copyright (c) 2016 Robert Hammelrath (putting the pieces together # and a few extensions) # Distributed under MIT License # import socket import network import uos import gc #from usocket import socket from machine import Pin,SPI,UART,PWM import time, network,framebuf ''' static netinfo ''' ip = '192.168.1.20' sn = '255.255.255.0' gw = '192.168.1.1' dns= '8.8.8.8' BL = 13 DC = 8 RST = 12 MOSI = 11 SCK = 10 CS = 9 netinfo=(ip, sn, gw, dns) localip = '' localport = 8000 listen_info = (localip, localport) conn_flag = False def w5x00_init(): global localip spi=SPI(0,2_000_000, mosi=Pin(19),miso=Pin(16),sck=Pin(18)) nic = network.WIZNET5K(spi,Pin(17),Pin(20)) nic.active(True) # use dhcp, if fail use static netinfo #try: # nic.ifconfig('dhcp') #except: nic.ifconfig(netinfo) localip = nic.ifconfig()[0] print('ip :', nic.ifconfig()[0]) print('sn :', nic.ifconfig()[1]) print('gw :', nic.ifconfig()[2]) print('dns:', nic.ifconfig()[3]) while not nic.isconnected(): time.sleep(1) # print(nic.regs()) print('no link') return nic def send_list_data(path, dataclient, full): try: # whether path is a directory name for fname in sorted(uos.listdir(path), key=str.lower): dataclient.sendall(make_description(path, fname, full)) except: # path may be a file name or pattern pattern = path.split("/")[-1] path = path[:-(len(pattern) + 1)] if path == "": path = "/" for fname in sorted(uos.listdir(path), key=str.lower): if fncmp(fname, pattern): dataclient.sendall(make_description(path, fname, full)) def make_description(path, fname, full): if full: stat = uos.stat(get_absolute_path(path, fname)) file_permissions = ("drwxr-xr-x" if (stat[0] & 0o170000 == 0o040000) else "-rw-r--r--") file_size = stat[6] description = "{} 1 owner group {:>10} Jan 1 2000 {}\r\n".format( file_permissions, file_size, fname) else: description = fname + "\r\n" return description def send_file_data(path, dataclient): with open(path, "rb") as file: chunk = file.read(512) while len(chunk) > 0: dataclient.sendall(chunk) chunk = file.read(512) def save_file_data(path, dataclient): with open(path, "wb") as file: chunk = dataclient.recv(512) while len(chunk) > 0: file.write(chunk) chunk = dataclient.recv(512) def get_absolute_path(cwd, payload): # Just a few special cases "..", "." and "" # If payload start's with /, set cwd to / # and consider the remainder a relative path if payload.startswith('/'): cwd = "/" for token in payload.split("/"): if token == '..': if cwd != '/': cwd = '/'.join(cwd.split('/')[:-1]) if cwd == '': cwd = '/' elif token != '.' and token != '': if cwd == '/': cwd += token else: cwd = cwd + '/' + token return cwd # compare fname against pattern. Pattern may contain # wildcards ? and *. def fncmp(fname, pattern): pi = 0 si = 0 while pi < len(pattern) and si < len(fname): if (fname[si] == pattern[pi]) or (pattern[pi] == '?'): si += 1 pi += 1 else: if pattern[pi] == '*': # recurse if (pi + 1) == len(pattern): return True while si < len(fname): if fncmp(fname[si:], pattern[pi+1:]): return True else: si += 1 return False else: return False if pi == len(pattern.rstrip("*")) and si == len(fname): return True else: return False def ftpserver(net, port=21, timeout=None): DATA_PORT = 13333 ftpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftpsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ftpsocket.bind(socket.getaddrinfo("0.0.0.0", port)[0][4]) datasocket.bind(socket.getaddrinfo("0.0.0.0", DATA_PORT)[0][4]) ftpsocket.listen(1) ftpsocket.settimeout(timeout) datasocket.listen(1) datasocket.settimeout(None) msg_250_OK = '250 OK\r\n' msg_550_fail = '550 Failed\r\n' addr = net.ifconfig()[0] print("FTP Server started on ", addr) try: dataclient = None fromname = None do_run = True while do_run: cl, remote_addr = ftpsocket.accept() cl.settimeout(300) cwd = '/' try: # print("FTP connection from:", remote_addr) cl.sendall("220 Hello, this is Micropython.\r\n") while True: gc.collect() data = cl.readline().decode("utf-8").rstrip("\r\n") if len(data) <= 0: print("Client disappeared") do_run = False break command = data.split(" ")[0].upper() payload = data[len(command):].lstrip() path = get_absolute_path(cwd, payload) print("Command={}, Payload={}".format(command, payload)) if command == "USER": cl.sendall("230 Logged in.\r\n") elif command == "SYST": cl.sendall("215 UNIX Type: L8\r\n") elif command == "NOOP": cl.sendall("200 OK\r\n") elif command == "FEAT": cl.sendall("211 no-features\r\n") elif command == "PWD" or command == "XPWD": cl.sendall('257 "{}"\r\n'.format(cwd)) elif command == "CWD" or command == "XCWD": try: files = uos.listdir(path) cwd = path cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "CDUP": cwd = get_absolute_path(cwd, "..") cl.sendall(msg_250_OK) elif command == "TYPE": # probably should switch between binary and not cl.sendall('200 Transfer mode set\r\n') elif command == "SIZE": try: size = uos.stat(path)[6] cl.sendall('213 {}\r\n'.format(size)) except: cl.sendall(msg_550_fail) elif command == "QUIT": cl.sendall('221 Bye.\r\n') do_run = False break elif command == "PASV": cl.sendall('227 Entering Passive Mode ({},{},{}).\r\n'. format(addr.replace('.', ','), DATA_PORT >> 8, DATA_PORT % 256)) dataclient, data_addr = datasocket.accept() print("FTP Data connection from:", data_addr) DATA_PORT = 13333 active = False elif command == "PORT": items = payload.split(",") if len(items) >= 6: data_addr = '.'.join(items[:4]) # replace by command session addr if data_addr == "127.0.1.1": data_addr = remote_addr DATA_PORT = int(items[4]) * 256 + int(items[5]) dataclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) dataclient.settimeout(10) dataclient.connect((data_addr, DATA_PORT)) print("FTP Data connection with:", data_addr) cl.sendall('200 OK\r\n') active = True else: cl.sendall('504 Fail\r\n') elif command == "LIST" or command == "NLST": if not payload.startswith("-"): place = path else: place = cwd try: cl.sendall("150 Here comes the directory listing.\r\n") send_list_data(place, dataclient, command == "LIST" or payload == "-l") cl.sendall("226 Listed.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "RETR": try: cl.sendall("150 Opening data connection.\r\n") send_file_data(path, dataclient) cl.sendall("226 Transfer complete.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "STOR": try: cl.sendall("150 Ok to send data.\r\n") save_file_data(path, dataclient) cl.sendall("226 Transfer complete.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "DELE": try: uos.remove(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "RMD" or command == "XRMD": try: uos.rmdir(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "MKD" or command == "XMKD": try: uos.mkdir(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "RNFR": fromname = path cl.sendall("350 Rename from\r\n") elif command == "RNTO": if fromname is not None: try: uos.rename(fromname, path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) else: cl.sendall(msg_550_fail) fromname = None elif command == "MDTM": try: tm=localtime(uos.stat(path)[8]) cl.sendall('213 {:04d}{:02d}{:02d}{:02d}{:02d}{:02d}\r\n'.format(*tm[0:6])) except: cl.sendall('550 Fail\r\n') elif command == "STAT": if payload == "": cl.sendall("211-Connected to ({})\r\n" " Data address ({})\r\n" "211 TYPE: Binary STRU: File MODE:" " Stream\r\n".format( remote_addr[0], addr)) else: cl.sendall("213-Directory listing:\r\n") send_list_data(path, cl, True) cl.sendall("213 Done.\r\n") else: cl.sendall("502 Unsupported command.\r\n") print("Unsupported command {} with payload {}".format( command, payload)) except Exception as err: print(err) finally: cl.close() cl = None except Exception as e: print(e) finally: datasocket.close() ftpsocket.close() if dataclient is not None: dataclient.close() if __name__ == "__main__": nic = w5x00_init() ftpserver(nic)   运行之后,SD NAND挂载正常,但是不知道为什么,在进行socket初始化的时候,又一个提示参数无效的报错,已经成功刷成micropython的固件,并且该导入的库我也导入了。 心得体会 本期是23年的第四期follow me活动,确实对得起这个第四期,个人感觉确实有些难度,刚开始不太懂网络协议栈确实有些吃力,后面跟着坛友们的分享,一步步走下去,虽然坑还是没少趟,但是收获也是有的,2024继续加油! 内容三、可编译下载的代码

  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】简易FTP文件服务器

  • 2024-02-04
  • 回复了主题帖: 共读入围名单:《原子嵌入式Linux驱动开发详解与实战(ARM Linux驱动)》

    个人信息无误,确认可以完成

  • 2024-02-02
  • 加入了学习《直播回放: FollowMe 4 W5500-EVB-Pico 使用入门》,观看 W5500-EVB-Pico 使用入门

最近访客

< 1/4 >

统计信息

已有80人来访过

  • 芯积分:1861
  • 好友:--
  • 主题:17
  • 回复:9

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言