当前位置:网站首页>[media controller] open source project learning notes (based on Arduino micro development board)
[media controller] open source project learning notes (based on Arduino micro development board)
2022-07-24 06:47:00 【Like warm know cold】
️ First of all : This project is based on Arduino Micro Developed by development board , Peripherals are only used EC11E1534408 Positionless rotary encoder .
Project source :【DIY】 self-control PC peripherals - Media controller , In the UK _ Bili, Bili _bilibili
Github:GitHub - xuan25/HIDMediaController-ArduinoMicroPro

HID
Human interface device(HID), Ergonomic interface device .
HID Generally refer to USB-HID standard .
stay USB On the official website of the agreement , given HID Documents :HID Usage Tables 1.3
Report descriptor about keyboard 、 Report descriptor of mouse , You can use the official website HID Descriptor tool (HID Descriptor tool) Generate ; It can also be modified using existing report descriptors ;HID Protocol and usage table document , There are also many ready-made examples .
️ About HID Device descriptor for , Looking up the relevant information , Make a conclusion . Because the first contact , There are bound to be mistakes !
//Button
0x05, 0x0c, // Usage Page (Consumer Devices)
0x09, 0x01, // Usage (Consumer Control)
0xa1, 0x01, // Collection (Application)
0x85, 0x04, // REPORT_ID (4)
0x09, 0xe9, // Usage (Volume Up)
0x09, 0xea, // Usage (Volume Down)
0x09, 0xe2, // Usage (Mute) // Mute
0x09, 0xcd, // Usage (Play) // Play
0x09, 0xb5, // Usage (Next)
0x09, 0xb6, // Usage (Previous)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x10, // Report Count (10)
0x81, 0x06, // Input (Data, Variable, Relative)
0xc0, // End CollectionDescriptor has no fixed length , There is no fixed data type , But by the entry (item) form . One entry occupies one line .
Short entry ( Most of them are short entries ) constitute : One byte prefix + Optional data bytes
Prefix structure :
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| bTag | bType | bSize | |||||
- bTag: Indicates the function of the item .
- bType: Indicates the type of the entry .0 Main entry ;1 For global entries ;2 For local entries .
- bSize: Indicates the number of data bytes of the entry .0~3 respectively 1~4 Bytes .
Main item (main item) situation
| The number | function | Binary system |
|---|---|---|
| 8 | input( Input ) | 1000 xxxx |
| 9 | output( Output ) | 1001 xxxx |
| b | feature( characteristic ) | 1011 xxxx |
0xa1 | collection( aggregate ) | 1010 0001 |
| 0xc0 | End collection( Closed set ) | 1100 0000 |
Global entries (global item) situation
| The number | function |
|---|---|
| 0 | Usage Page( Usage page ) |
| 1 | Logical Mini( Logical minimum ) |
| 2 | Logical Maxi( Logical maximum ) |
| 3 | Physical Mini( Minimum physical quantity ) |
| 4 | Physical Maxi( Maximum physical quantity ) |
| 7 | Report Size( Data field size ) |
| 8 | Report ID( The report ID) |
| 9 | Report Count( Number of data fields ) |
Partial entries (local item) situation
| The number | function |
|---|---|
| 0 | Usage( purpose ) |
| 1 | Usage Mini( Use minimum ) |
| 2 | Usage Maxi( Maximum usage ) |
analysis :
0x05, 0x0c, // Usage Page (Consumer Devices)
/* 0x05: 0000 0101 Express : Usage page , Global entries , The amount of data bytes is 2
* 0x0c: Consumer Page Express : User equipment 0x09, 0x01, // Usage (Consumer Control)
/* 0x09: 0000 1001 Express : Usage page , Partial entries , The amount of data bytes is 2
* 0x01:Consumer Control Express : User control
0x85, 0x04, // REPORT_ID (4)
/* 0x85:1000 0101 Express : The report ID, Global entries , The amount of data bytes is 20x09, 0xea, // Usage (Volume Down)
/* 0x09: 0000 1001 Express : Usage page , Partial entries , The amount of data bytes is 2
* 0xea:Volume Decrement Express : volume down
0x81, 0x06, // Input (Data, Variable, Relative)
/* 0x81: 1000 0001 Express : Input , Main item , The amount of data bytes is 2
Arduino Required report ID
Arduino Will report ID Some values are set to fixed . for example : 0: Unavailable ;1: mouse ;2: keyboard .
Write your own HID Equipment time , It needs to be set to other ID.
Set the report descriptor you want , Auxiliary software can be used to generate :HID Descriptor Tool | USB-IF
About HID That's all for the part of . If you learn USB Look at the equipment again HID Relevant knowledge !
How the Arduino Implemented on HID
Turned over. Keyboard Kuhe Mouse library .
When reading the library file, I found that all references HID.h file :#include "HID.h"
Can imitate Mouse and Keyboard Library to write their own HID library . Guess the original author thinks so .
By chance Arduino An article in the community , Just let me find out how the original author of this project wrote HID Library .
analysis Mouse Library HID The descriptor :
static const uint8_t _hidReportDescriptor[] PROGMEM = {
// Mouse
0x05, 0x01, // USAGE_PAGE (Generic Desktop) // The usage page is for general desktop devices
0x09, 0x02, // USAGE (Mouse) // Use for mouse
0xa1, 0x01, // COLLECTION (Application) //COLLECTION1
0x09, 0x01, // USAGE (Pointer) // Pointer devices
0xa1, 0x00, // COLLECTION (Physical) //COLLECTION2
0x85, 0x01, // REPORT_ID (1) // The report ID Set to 1
0x05, 0x09, // USAGE_PAGE (Button) // Key device
0x19, 0x01, // USAGE_MINIMUM (Button 1) // Use minimum 1
0x29, 0x03, // USAGE_MAXIMUM (Button 3) // Maximum usage 3:1, left-click ;2, Right click ;3, In the key
0x15, 0x00, // LOGICAL_MINIMUM (0) // Logical minimum 0
0x25, 0x01, // LOGICAL_MAXIMUM (1) // Logical maximum 1( Whether to press ,0-1)
0x95, 0x03, // REPORT_COUNT (3) // Number of reports ( The three key )
0x75, 0x01, // REPORT_SIZE (1) // The size of the report (1 bit)
0x81, 0x02, // INPUT (Data,Var,Abs) // Input , Property is a variable , The number , The absolute value
0x95, 0x01, // REPORT_COUNT (1) // Number of reports
0x75, 0x05, // REPORT_SIZE (5) // The size of the report
0x81, 0x03, // INPUT (Cnst,Var,Abs) // Input , Property is constant 0
0x05, 0x01, // USAGE_PAGE (Generic Desktop) // The purpose page is a general desktop
0x09, 0x30, // USAGE (X) // The purpose is X Axis
0x09, 0x31, // USAGE (Y) // The purpose is Y Axis
0x09, 0x38, // USAGE (Wheel) // Use for roller
0x15, 0x81, // LOGICAL_MINIMUM (-127) // Logical minimum -127
0x25, 0x7f, // LOGICAL_MAXIMUM (127) // Logical maximum 127
0x75, 0x08, // REPORT_SIZE (8) // The size of the report
0x95, 0x03, // REPORT_COUNT (3) // Number of reports
0x81, 0x06, // INPUT (Data,Var,Rel) // Input , Property is a variable , The number , Relative value
0xc0, // END_COLLECTION //END_COLLECTION2
0xc0, // END_COLLECTION //END_COLLECTION1
};see Mouse.cpp file , Will find HID Report descriptor The declaration process of is as follows ( stay Keyboard.cpp Almost the same in ):
Mouse_::Mouse_(void) : _buttons(0)
{
static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
HID().AppendDescriptor(&node);
}The function that sends the report :
void Mouse_::move(signed char x, signed char y, signed char wheel)
{
uint8_t m[4];
m[0] = _buttons;
m[1] = x;
m[2] = y;
m[3] = wheel;
HID().SendReport(1,m,4);
}
among , What matters is the last code
HID().SendReport(uint8_tid, const void* data, int len)
//ID data Data length Project code implementation
First of all to see HID Modelled on the Mouse and Keyboard Library written HIDDevice library .
HIDDevice relevant
HIDDevice.cpp
It is basically written in imitation of the official library .
#include "HIDDevice.h"
// His writing + Keyboard + Mouse It's all used
static const uint8_t HIDDevice::_hidReportDescriptor[] PROGMEM = {
// Button
0x05, 0x0c, // Usage Page (Consumer Devices)
0x09, 0x01, // Usage (Consumer Control)
0xa1, 0x01, // Collection (Application)
0x85, 0x04, // REPORT_ID (4)
0x09, 0xe9, // Usage (Volume Up)
0x09, 0xea, // Usage (Volume Down)
0x09, 0xe2, // Usage (Mute)
0x09, 0xcd, // Usage (Play)
0x09, 0xb5, // Usage (Next)
0x09, 0xb6, // Usage (Previous)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x10, // Report Count (10)
0x81, 0x06, // Input (Data, Variable, Relative)
0xc0, // End Collection
// Keyboard
0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 47
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x02, // REPORT_ID (2)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0, // END_COLLECTION
// Mouse
0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 54
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x85, 0x01, // REPORT_ID (1)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
};
HIDDevice::HIDDevice(){
}
// Declare report descriptor
void HIDDevice::begin(){
static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
HID().AppendDescriptor(&node);
}
// Media control sends reports
void HIDDevice::mediaControl(uint8_t c){
uint8_t m[2] = {c, 0};
HID().SendReport(4,m,2);
}
// Keyboard control send report
void HIDDevice::keyEvent(uint8_t modifiers, uint8_t key1, uint8_t key2, uint8_t key3, uint8_t key4, uint8_t key5, uint8_t key6){
uint8_t m[8] = {modifiers, 0, key1, key2, key3, key4, key5, key6};
HID().SendReport(2,m,8);
}
// Mouse control send report
void HIDDevice::mouseEvent(uint8_t buttons, uint8_t x, uint8_t y, uint8_t wheel){
uint8_t m[4] = {buttons, x, y, wheel};
HID().SendReport(1,m,4);
}HIDDevice.h
#ifndef HIDDevice_H
#define HIDDevice_H
#include "arduino.h"
#include <HID.h>
#define VOLUME_INCREMENT 0x01
#define VOLUME_DECREMENT 0x02
#define MUTE 0x04
#define PLAY_PAUSE 0x08
#define NEXT 0x10
#define PREVIOUS 0x20
#define MOUSE_LEFT 0x00
#define MOUSE_RIGHT 0x01
#define MOUSE_MIDDLE 0x04
#define KEY_LEFT_CTRL 1<<0x00
#define KEY_LEFT_SHIFT 1<<0x01
#define KEY_LEFT_ALT 1<<0x02
#define KEY_LEFT_GUI 1<<0x03
#define KEY_RIGHT_CTRL 1<<0x04
#define KEY_RIGHT_SHIFT 1<<0x05
#define KEY_RIGHT_ALT 1<<0x06
#define KEY_RIGHT_GUI 1<<0x07
#define KEY_UP_ARROW 0x52
#define KEY_DOWN_ARROW 0x51
#define KEY_LEFT_ARROW 0x50
#define KEY_RIGHT_ARROW 0x4f
#define KEY_BACKSPACE 0x2a
#define KEY_TAB 0x2b
#define KEY_RETURN 0x28
#define KEY_ESC 0x29
#define KEY_INSERT 0x49
#define KEY_DELETE 0x4c
#define KEY_PAGE_UP 0x4b
#define KEY_PAGE_DOWN 0x4e
#define KEY_HOME 0x4a
#define KEY_END 0x4d
#define KEY_CAPS_LOCK 0x39
#define KEY_F1 0x3a
#define KEY_F2 0x3b
#define KEY_F3 0x3c
#define KEY_F4 0x3d
#define KEY_F5 0x3e
#define KEY_F6 0x3f
#define KEY_F7 0x40
#define KEY_F8 0x41
#define KEY_F9 0x42
#define KEY_F10 0x43
#define KEY_F11 0x44
#define KEY_F12 0x45
#define KEY_A 0x04
#define KEY_B 0x05
#define KEY_C 0x06
#define KEY_D 0x07
#define KEY_E 0x08
#define KEY_F 0x09
#define KEY_G 0x0a
#define KEY_H 0x0b
#define KEY_I 0x0c
#define KEY_J 0x0d
#define KEY_K 0x0e
#define KEY_L 0x0f
#define KEY_M 0x10
#define KEY_N 0x11
#define KEY_O 0x12
#define KEY_P 0x13
#define KEY_Q 0x14
#define KEY_R 0x15
#define KEY_S 0x16
#define KEY_T 0x17
#define KEY_U 0x18
#define KEY_V 0x19
#define KEY_W 0x1a
#define KEY_X 0x1b
#define KEY_Y 0x1c
#define KEY_Z 0x1d
class HIDDevice{
private:static const uint8_t _hidReportDescriptor[] PROGMEM;
public:HIDDevice();
public:void begin();
public:void mediaControl(uint8_t c);
public:void keyEvent(uint8_t modifiers, uint8_t key1, uint8_t key2, uint8_t key3, uint8_t key4, uint8_t key5, uint8_t key6);
public:void mouseEvent(uint8_t buttons, uint8_t x, uint8_t y, uint8_t wheel);
};
#endifThe rest is the problem of detecting input .
Detection input
EC11 Introduce

A、B、C Used to detect the rotation direction of the knob .D、E Equivalent to ordinary keys .
EC11 Judgment method : Turn left : Anti-clockwise ; Turn right : Clockwise
When C When grounded !AB Level state relationship between
| Anti-clockwise ( Turn left ) | 11、01、00、10 |
| Clockwise ( Turn right ) | 11、10、00、01 |
Judgment method :
A When the signal is on the falling edge ,B Is a low level , It means clockwise .B Is a high level , It means counter clockwise .
Empathy :
B When the signal is on the falling edge ,A Is a high level , It means clockwise .A Is a low level , It means counter clockwise .
Encoder.cpp( And testing A、B、C relevant )
#include "Encoder.h"
int prePinA = 0;
int prePinB = 0;
int preDire = 0;
int a,b,c;
// register ABC Pin
Encoder::Encoder(int aPin, int bPin, int cPin){
a=aPin;
b=bPin;
c=cPin;
}
// initialization C Low level ,A、B Input port
void Encoder::begin(){
pinMode(c,OUTPUT);
digitalWrite(c,LOW);
pinMode(a,INPUT_PULLUP);
pinMode(b,INPUT_PULLUP);
prePinA = digitalRead(a); // Detection level
prePinB = digitalRead(b);
}
int Encoder::nextFrame(){
int pinA = digitalRead(a);
int pinB = digitalRead(b);
int result = 0;
// Detect positive and negative through the next frame
if(pinA > prePinA) //a up
{
if(pinB == 1) result = -1;//b high
else result = 1;//b low
}
else if(pinA < prePinA) //a down
{
if(pinB == 1) result = 1;//b high
else result = -1;//b low
}
else if(pinB > prePinB) //b up
{
if(pinA == 1) result = 1;//a high
else result = -1;//a low
}
else if(pinB < prePinB) //b down
{
if(pinA == 1) result = -1;//a high
else result = 1;//a low
}
prePinA = pinA;
prePinB = pinB;
return result;
}
// Is it reconfirmation or two actions and one operation ?
int Encoder::refresh(){
int dire = nextFrame();
if(dire == 1)
{
if(preDire != 1)
{
preDire = 1;
return 0;
}
else return 1;
}
else if(dire == -1)
{
if(preDire != -1){
preDire = -1;
return 0;
}
else return -1;
}
return 0;
}Encoder.h( And testing A、B、C relevant )
#ifndef Encoder_H
#define Encoder_H
#include "arduino.h"
class Encoder{
private:int prePinA;
private:int prePinB;
private:int preDire;
private:int a,b,c;
private:int nextFrame();
public:Encoder(int aPin, int bPin, int cPin);
public:void begin();
public:int refresh();
};
#endifMediaButton.cpp( And testing D、E relevant )
#include "MediaButton.h"
int d,e;
// register DE Pin
MediaButton::MediaButton(int dPin, int ePin){
d = dPin;
e = ePin;
}
// Initialization pin : d Low level ,e It is the inspection port
void MediaButton::begin(){
pinMode(d, OUTPUT);
digitalWrite(d, LOW);
pinMode(e, INPUT_PULLUP);
}
void MediaButton::refresh(HIDDevice hid1)
{
if(!digitalRead(e)) // Judge whether the key is pressed , If you press digitalRead(e) = 0;
{
int i = 0;
/* Press and hold the button to play the previous song */
while(!digitalRead(e)) // Press cycle
{
delay(10);
i++;
if(i>50) // Press timeout ( On a )
{
hid1.mediaControl(PREVIOUS); //# Press and hold the button #
hid1.mediaControl(0);
i = 0;
while(!digitalRead(e))
{
if(i>1)
{
hid1.mediaControl(PREVIOUS); //# Press and hold the button to keep the cycle #
hid1.mediaControl(0);
}
delay(500);
i++;
}
return;
}
}
i = 0;
/* Short press the button to pause the playback function */
while(digitalRead(e)) // Release cycle
{
delay(10);
i++;
if(i>50) // Release timeout ( Pause / Play )
{
hid1.mediaControl(PLAY_PAUSE);//# Press the button briefly #
hid1.mediaControl(0);
return;
}
}
i = 0;
/* Double click and long press the button to play the next song */
while(!digitalRead(e)) // Press cycle 2
{
delay(10);
i++;
if(i>50) // Press timeout ( The following piece )
{
hid1.mediaControl(NEXT); //# Double click the button to hold #
hid1.mediaControl(0);
i = 0;
while(!digitalRead(e))
{
if(i>1)
{
hid1.mediaControl(NEXT); //# Double click the button to keep the loop #
hid1.mediaControl(0);
}
delay(500);
i++;
}
return;
}
}
i = 0;
/* Double click the button to play the next song */
while(digitalRead(e)) // Release cycle 2
{
delay(10);
i++;
if(i>50) // Release timeout ( The following piece )
{
hid1.mediaControl(NEXT); //# Double click the button #
hid1.mediaControl(0);
return;
}
}
i = 0;
/* Press and hold the button three times to realize the function of collecting songs */
while(!digitalRead(e)) // Press cycle 3
{
delay(10);
i++;
if(i>50) // Press timeout ( like )
{
hid1.keyEvent(KEY_LEFT_CTRL | KEY_LEFT_ALT, KEY_L, 0, 0, 0, 0, 0); //# Button three click hold #
hid1.keyEvent(0, 0, 0, 0, 0, 0, 0);
while(!digitalRead(e))
{
delay(500); //# Press the button three times to keep the cycle #
}
return;
}
}
hid1.keyEvent(KEY_LEFT_CTRL | KEY_LEFT_ALT, KEY_L, 0, 0, 0, 0, 0); //# Click the button three times #
hid1.keyEvent(0, 0, 0, 0, 0, 0, 0);
return;
}
}This implementation is very interesting , Draw a flow chart

MediaButton.cpp( And testing D、E relevant )
#ifndef MediaButton_H
#define MediaButton_H
#include "arduino.h"
#include "HIDDevice.h"
class MediaButton{
private:int d,e;
public:MediaButton(int dPin, int ePin);
public:void begin();
public:void refresh(HIDDevice hid1);
};
#endifThe main function
#include "Encoder.h"
#include "MediaButton.h"
#include "HIDDevice.h"
static int a=8,b=6,c=7,d=16,e=15; //EC11 Pin definition
Encoder enc1(a,b,c); // Create examples
MediaButton btn1(d,e);
HIDDevice hid1;
void setup() // initialization
{
enc1.begin();
btn1.begin();
hid1.begin();
}
int total = 0;
int threshold = 5;
void loop()
{
int re = enc1.refresh(); // Detect positive and negative
total += re; // Forward and reverse offset problem
if(total == threshold)
{
//# The encoder rotates forward #
hid1.mediaControl(VOLUME_INCREMENT); // Increase the volume
hid1.mediaControl(0);
total = 0;
}
else if(total == -threshold)
{
//# The encoder is reversed #
hid1.mediaControl(VOLUME_DECREMENT); // Reduce the volume
hid1.mediaControl(0);
total = 0;
}
btn1.refresh(hid1); // Test button
}summary
Put the resources of the project Baidu online disk here , Make a backup .
The best way to download a project is to visit the front of the article GitHub link .
link :https://pan.baidu.com/s/1nXlvSr33GzwCzQ4frGQogg
Extraction code :o8r7
Reference resources :
HID Resolution of device descriptor
utilize Arduino Make your own usb hid equipment
Daily summary ~
C++ I'm still not familiar with it . But this time I learned Create an instance with parameters Methods , You can also ~
Encoder enc1(a,b,c);
MediaButton btn1(d,e);
HIDDevice hid1;Make progress together ~ friends ~

边栏推荐
- 磁盘管理和文件系统
- RAID configuration experiment
- JS - mouse and keyboard configuration and browser forbidden operation
- Breadth first search (template use)
- DNS domain name resolution service
- Special effects - when the mouse moves, stars appear to trail
- JS - calculate the side length and angle of a right triangle
- Write cookies, sessionstorage, localstorage and session at will
- [lvgl (3)]
- Visibility:hidden and display:none
猜你喜欢

DHCP principle and configuration

【小型物体测速仪】只有原理,无代码

Redis入门

【USB电压电流表】基于STM32F103C8T6 for Arduino

Special effects - bubble tailing occurs when the mouse moves

Directory and file management

RAID configuration experiment

Quick start of go language

Machine learning case: smoking in pregnant women and fetal health

Redis特殊数据类型-HyperLogLog
随机推荐
Redis分布式缓存学习笔记
Redis基本类型-结合Set
[small object velocimeter] only principle, no code
ES10 subtotal flat and flatmap
Experiment: creation, expansion, and deletion of LVM logical volumes
我有 7种 实现web实时消息推送的方案,7种!
MapReduce(一)
nodejs开启多进程并实现进程间通信
Special effects - cobweb background effects
Special effects - mouse click, custom DOM follow move
Random forest, lgbm parameter adjustment based on Bayesian Optimization
【LVGL(3)】设置对象大小、位置、盒子模型、状态
(静态,动态,文件)三个版本的通讯录
Rsync (I): basic commands and usage
Backup MySQL database with bat script under Windows
LM393 电压比较器及其典型电路介绍
创建WPF项目
adb交互-干掉难看的默认shell界面
Introduction, architecture and principle of kubernetes
STM32基于 FatFs R0.14b&SD Card 的MP3音乐播放器(也算是FatFs的简单应用了吧)