一篇文章了解系统眼中的键盘--以一个简单的系统分析从按键的输入到字符的显示

news2024/11/14 6:47:09

键盘输入

实现使用的设备

intel架构32位CPU, 思路为嵌入式系统工程师,使用的操作系统是《30天自制操作系统》里面的系统进行讲解

硬件实现

按键

image-20231014094206723

使用单片机等的引脚可以获取电平状态从而获得按键的状态(单片机是一种集成到一块硅片上构成的一个小而完善的微型计算机系统, 用在一些不需要很高的性能的地方, 比如遥控器, 手表等)

image-20231011232437371

image-20231011232350591

一般使用轮询的方式获取矩阵键盘上面的按键的状态

例: 在上方第一个端口通高电平同时检测右侧四个端口, 这时候如果某一个端口为低电平表示按键按下, 检测之后再在上侧端口2进行通电然后检测, 以此类推, 快速循环即可获得按键状态

单片机的速度很快, 所以你看来就会认为这些按键是同时检测的

hw2

实际的键盘原理图(稚晖君作品), 会加入一些其他的处理

如何让电脑识别为键盘

电脑识别按钮为键盘按键

  1. 获取到的信号会通过USB HID协议传输到电脑, 从调试数据分析USB通信协议——USB键盘鼠标【HID类设备】(四)_usb键盘协议-CSDN博客, 由于USB协议很复杂不过多讲解, 感兴趣同学自行了解(可以直接使用现成的键盘驱动板)
  2. 电脑采集信号后使用软件进行转化, 例如通过串口发送一个数据, 电脑上启动一个程序, 接收到这个数据以后就会转化为对应的按键
import pyautogui
if(按键按下)
    # 模拟按下'A'键
    pyautogui.press('a')
    # 模拟按下组合键Ctrl+C
    pyautogui.hotkey('ctrl', 'c')
  1. Linux在设备树上面添加节点时候进行设置(在操作系统中进行专门设置)

注: 以下解释的是使用USB进行通讯的方式

软件

一般来说电脑的硬件会自带一些处理的设备, 我们不需要关心底层的时序, 只需要对处理过的信息进行采集就行了

不同情况下获取按键的状态的方式

  • 方式1使用BIOS(16位模式), 在电脑刚上电的时候没有加载操作系统时候使用, 类似于C语言函数的调用
; 相当于int get_status(int data);
MOV		AH,0x02			;设置参数, 把参数保存在AH寄存器里面
INT		0x16 			; 调用BIOS函数获取状态,使用的是16号函数
MOV		[LEDS],AL		; 进行存储, 返回的信息保存在AL寄存器里面

这里的AL, AH等是Intel架构下面的寄存器, 是一块可以和CPU的速度相匹配的内存, 用于在计算机计算的时候暂时保存数据

这一个调用主要用于获取键盘的指示灯的状态(大小写切换等)

BIOS: 可以理解为一个固定在主板上面的程序, 帮助初始化一些内容, 在电脑刚上电的时候使用的是16位的模式, 需要在初始化一些内容以后切换为32位模式, 这样做的主要目的是兼容之前的CPU

在示例使用的系统中此时候读取的信息会被记录, 用于之后的按键输入判断

  • 其他时候获取按键的按下情况(32位), 使用汇编指令读取对应的端口获取信息
_io_in8:	; int io_in8(int port);实际调用的时候相当于这一个C语言代码调用
		    ;传入读取的端口, 返回读取到的值
		MOV		EDX,[ESP+4]		; port, 从栈中获取传进来的参数
		MOV		EAX,0			;对于另一个参数进行设置
		IN		AL,DX			;使用IN汇编指令对对应的端口进行读取, EAX会保存返回值
		RET

_io_out8:	; void io_out8(int port, int data);
		MOV		EDX,[ESP+4]		; port
		MOV		AL,[ESP+8]		; data
		OUT		DX,AL			; 向DX保存的端口写入AL的数值
		RET

使用的两个外设端口读取的函数, 一般会在中断中进行调用, 检测是哪一个按键按下

中断
image-20231024231022592

中断发生后, 原先的程序执行会被打断, 之后CPU会检查发生的是哪一个中断, 从中断向量表中获取到对应的处理函数, 跳转到对应的函数进行执行

中断向量表: 可以理解一个记录有各个中断处理函数的数组, CPU会根据发生的中断的编号到对应的位置进行查找对应的处理函数

Intel使用的中断处理使用的是PIC(这里不进行过多解释)一文读懂多架构的中断控制器 - 知乎 (zhihu.com)

_asm_inthandler21:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX				;对使用的寄存器保存数据, 防止中断返回的时候程序无法正常运行
		MOV		AX,SS			
		MOV		DS,AX			
		MOV		ES,AX			;准备C语言需要的环境(这几个段寄存的数值要求一样)
		CALL	_inthandler21	; 调用实际的处理函数
		POP		EAX				;复原使用的寄存器
		POPAD
		POP		DS
		POP		ES
		IRETD

中断发生后会自动运行这一个函数(地址保存在中断向量表里面), 我们需要在里面进行中断事件的处理

首先需要将之前程序运行的状态保存在栈里面, 之后调用对键盘数据的读取函数

这里使用的PUSH以及POP指令是对于栈的操作

(Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。使用这一种结构对数据进行保存, 在调用其他函数之间会将数据入栈, 返回以后出栈

image-20231024230846603
void inthandler21(int *esp)
{
	int data;
	io_out8(PIC0_OCW2, 0x61);	/* IRQ-01接收完毕的通知 */
	data = io_in8(PORT_KEYDAT);	//向键盘端口发送一个信号
	fifo32_put(keyfifo, data + keydata0);	//从键盘的端口获取一个键盘的数据,之后保存起来
	return;
}

实际的中断处理函数

多按键先后输入处理

由于可能在短时间内有多个按键进行输入, 事件短于处理函数, 所以使用FIFO(First in First out)的数据结构对传入的数据进行存储(实际的操作系统中用来进行多个事件先后的处理)

/* fifo.c */
struct FIFO8 {
	unsigned char *buf;		//一个数组, 实际数据存储的位置
	int p, q, size, free, flags;	//记录读指针, 写指针以及总大小, 剩余空间, 是否初始化的标志 
};

实际的C语言实现

image-20231026123344012 image-20231026123401203

image-20231019104538913

具体实现的代码(仅参考不进行讲解)
/* FIFO实现 */

#include "bootpack.h"

#define FLAGS_OVERRUN		0x0001

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* FIFO的初始化 */
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; /* 为空 */
	fifo->flags = 0;
	fifo->p = 0; /* 写指针记录初为始位置 */
	fifo->q = 0; /* 读指针记录为初始位置 */
	return;
}

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* FIFO写入一个数据 */
{
	if (fifo->free == 0) {
		/* 没有位置进行写入了 */
		fifo->flags |= FLAGS_OVERRUN;
		return -1;
	}
	fifo->buf[fifo->p] = data;
	fifo->p++;
	if (fifo->p == fifo->size) {
		fifo->p = 0;
	}
	fifo->free--;
	return 0;
}

int fifo8_get(struct FIFO8 *fifo)
/* FIFO获取一个数据 */
{
	int data;
	if (fifo->free == fifo->size) {
		/* 没有可以获取的数据 */
		return -1;
	}
	data = fifo->buf[fifo->q];
	fifo->q++;
	if (fifo->q == fifo->size) {
		fifo->q = 0;
	}
	fifo->free++;
	return data;
}

int fifo8_status(struct FIFO8 *fifo)
/* 获取当前没有读取的数量数据 */
{
	return fifo->size - fifo->free;
}

信号的处理

  • 在中断中获取信号
  • 对信号进行解析

使用按键数值表进行获取实际按下的按键

IMG_20231012_091035

系统需要做的就是回上面的信息进行处理

static char keytable0[0x80] = {
0,   0,   '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0,   0,
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0,   0,   'A', 'S',
'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0,   0,   ']', 'Z', 'X', 'C', 'V',
'B', 'N', 'M', ',', '.', '/', 0,   '*', 0,   ' ', 0,   0,   0,   0,   0,   0,
0,   0,   0,   0,   0,   0,   0,   '7', '8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   0,   0x5c, 0,  0,   0,   0,   0,   0,   0,   0,   0,   0x5c, 0,  0
};
static char keytable1[0x80] = {
0,   0,   '!', 0x22, '#', '$', '%', '&', 0x27, '(', ')', '~', '=', '~', 0,   0,
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '`', '{', 0,   0,   'A', 'S',
'D', 'F', 'G', 'H', 'J', 'K', 'L', '+', '*', 0,   0,   '}', 'Z', 'X', 'C', 'V',
'B', 'N', 'M', '<', '>', '?', 0,   '*', 0,   ' ', 0,   0,   0,   0,   0,   0,
0,   0,   0,   0,   0,   0,   0,   '7', '8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   0,   '_', 0,   0,   0,   0,   0,   0,   0,   0,   0,   '|', 0,   0
};

使用两个数组对这些数据进行判断, 有两组的原因是, 在Shift和Capslock状态不同的输入的数据是不同的

  • 处理
  1. 对于普通的按键获取到实际的按键数值, 之后系统进行处理或者传递给应用
  2. 对于切换状态的按键, 对之前保存的状态进行切换, 在之后的输入的时候对事件进行处理

实际按键的处理函数(修改版)

//key_shift等是全局变量, 用来保存一些按键是不是按下了
i = fifo8_get($Key_FIFO);		//获取一个按键
if (i < 0x80) { /* 判断输入的是什么 */
    if (key_shift == 0) {//判断shift按键的状态, 进而获取实际输入的字符
        //获取字母以及数字
        s[0] = keytable0[i];
    } else {
        //获取字母以及标点符号
        s[0] = keytable1[i];
    }
} else {
    s[0] = 0;
}
if ('A' <= s[0] && s[0] <= 'Z') {	/* 分析输入的文字 */
    if (((key_leds & 4) == 0 && key_shift == 0) ||
        ((key_leds & 4) != 0 && key_shift != 0)) {
        s[0] += 0x20;	/* 大小写字母的换算 */
    }
}
if (s[0] != 0 && key_win != 0) { /* 通常文字、显示文字的窗口存在、Enter */
		//对于任务存在时候发送数据到任务
    		...
}
if (i == 0x0f && key_win != 0) {	/* Tab */
	//某个按键有特殊作用时候单独处理
    ...
}
//状态类的按键的处理
if (i == 0x2a) {	/* 左侧Shift ON */
    key_shift |= 1;
}
....


if (i == 0x45) {	/* NumLock */
	...
}
//组合键的处理
if (i == 0x3b && key_shift != 0 && key_win != 0) {	/* Shift+F1 */
    ...
}

文字的显示

  • 字符编码

ASCII只有255个字符并且只能显示英文, 中文也有自己的字符编码

补充常用的中文字符集

GB2312: 兼容ASCII前127位, 两个大于127的数字连用表示一个汉字, 一共有八千多个符号, 包含6763个汉字, ASCII之中的的有的也是用两个字节进行编码, 叫做全角, 一般使用半角, 一般使用0xa1开始表示

GBK: 在GB2312的基础上增加了14240个新的汉字, 由于原来的格式原来的格式已经存放不下, 所以要求第一个编码大于127就可以了

GB18030: 在GBK上面进一步扩展, 第二个字节未使用的0x30-0x39编码表示扩充为四个字节, 兼容前几个, 只要增加了中日韩统一汉字编码扩充

Unicode: 各个国家制作的统一的字符集, 兼容ASCII, 还有一些表情emoji, 只是进行编号, 没有进行编码, 就是只是用一个数字代表一个字符, 但是没有使用具体的电脑解析的规范

utf-32: 编码和编号一样, 每一个字符使用4个字节进行表示, 前面的0不能省略, 会导致ASCII等编码变长

utf-16: 使用两个或者四个字节进行编写, 由于发现Unicode没有使用0xD800到0xDBFF所以利用这片空间表示映射 , 使用这一空间达到变长的目的

utf-8: 网页上使用的比较多, 也是一种变长的编码格式, 第一位设置为0, 就可以兼容ASCII的0-127, 其他的字符, 两个字节的时候第一个字节使用110开头, 第二个字节使用10开头, 三个字节的时候会使用1110开头, 之后是使用10开头, 以此类推, 将Unicode编码填入空位, 从最后一个字节开始填写, 不够的在前面加0

具体获取你输入的是哪一个字符是输入软件的工作

  • 字模数据

只有数字编码你只知道这是某一个汉字, 但是这个字是怎么显示的呢?

实际上字符可以是一个图形, 可以保存在内存中, 记录了每一个像素的显示

使用取模软件获取一个字符的字模

image-20230713154917469

使用这个软件进行生成,

GB2312: Addr = (((CodeH - 0xA0-1)*94)+(CodeL - 0xA0 -1))*16*16/8

(0)
{0x09,0x00,0x08,0x80,0x1F,0xFC,0x10,0x80,0x30,0x80,0x5F,0xF8,0x90,0x80,0x10,0x80,0x1F,0xF8,0x10,0x80,0x10,0x80,0x1F,0xFC},
{0x10,0x00,0x48,0x88,0x44,0x44,0x84,0x44},/*"焦",0*/

image-20231014094421615

0x09 => 0000 1001 0x00 => 0000 0000

详细讲解: 【单片机】野火STM32F103教学视频 (配套霸道/指南者/MINI)【全】(刘火良老师出品) (无字幕)-虚神疯-稍后再看-哔哩哔哩视频 (bilibili.com)

编码和字模数据的使用关系, 一般会可以按照字符编码的顺序进行保存字模数据, 在之后进行显示的时候按照字符编码对对应的字模数据进行寻找, 可以使用这个软件进行生成

image-20231026090753019

**注: **之后使用的时候可以按照字符编码进行一些运算以后在字模表里面寻找对应的字模

  • 示例GB2312的解析

实际使用的编码位是0xA1A1-FEFE, 汉字的区域是B0A1-F7FE, 原因是为了兼容ASCII, 最高位为1, 并且预留0x20个控制位

GB2312编码对收录的字符进行分区, 分为94个区, 每一个区有94个位, 一共有8836个码位

第16个区开始是汉字, 0-9是682个汉字以外的字符

10-15是空白区域没有使用

16-55是一级汉字, 按照拼音进行排序

56-87区收录3008个二级汉字, 按照部首进行排序

88-94是空白区没有使用

实际使用的时候就是: 区码加位码+A0A0, 比如’‘啊’'是16区第一个, 就是0x1001+0xA0A0

实际在使用字模库的时候由于没有进行兼容偏移, 所以使用的是字符的在总个数的排序

Addr = (((ColdH - 0xA0 - 1)* 94) + (CodeL - 0xA0-1))*32*32/8

Addr 地址偏移

CodeH区码

CodeL位码

32: 字模的长和宽

  • 显存

一块用于记录屏幕信息的内存, 直接堆内存进行读写就可以控制一块屏幕的显示

此模式下使用8位表示一个像素的颜色

由于8只能表示255种颜色=>使用色盘(一个保存颜色的数组, 比如0位置保存红色, 我向显存写入0之后会先从色盘获取红色, 然后把红色显示在对应的像素上面)

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
    //参数1: 显存的位置
    //参数2: 屏幕的宽度
    //参数3,4: 显示的位置
    //参数5: 显示的颜色
    //参数6: 显示的字的字模
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {
		p = vram + (y + i) * xsize + x;//计算某一行数据的实际对应的显存位置
		d = font[i];	//获取某一行的字模
		if ((d & 0x80) != 0) { p[0] = c; }	//按照字模数据依次写入对应的显存
		if ((d & 0x40) != 0) { p[1] = c; }
		if ((d & 0x20) != 0) { p[2] = c; }
		if ((d & 0x10) != 0) { p[3] = c; }
		if ((d & 0x08) != 0) { p[4] = c; }
		if ((d & 0x04) != 0) { p[5] = c; }
		if ((d & 0x02) != 0) { p[6] = c; }
		if ((d & 0x01) != 0) { p[7] = c; }
	}
	return;
}

通过循环获取字模的每一个位, 之后通过这一个位的保存的数据进行判断对显存写入的颜色

显示总结(官方术语)

外码=>内码=> 交换码=>字形码

外码: 输入码, 平常键盘拼音输入的字母, 是一个索引码

内码: 计算机存储的二进制, 输入的汉字要转化为二进制形式, 集合叫做字符集

交换码: 国标码, 和别的计算机交换信息的时候使用的编码

字形码: 汉字字模, 二进制转化为可视化的图形, 集合叫做字库

实际效果演示

image-20231026100316991

上面使用的操作系统没有保存照片, 使用一个我写的嵌入式操作系统展示字符显示的结果(由上述操作系统移植过来的ARM架构下的图形化操作系统)

XuSenfeng/jiaoOS: 一个基于stm32f103的图形化操作系统 (github.com)

总结

  • 读取按键是否按下
  • 将按键的信号传递给电脑
  • 电脑对信号进行处理获得输入的数据
  • 将信号显示在屏幕上

版权补充

引用资料:

  1. peng-zhihui/HelloWord-Keyboard (github.com)稚晖君翰文键盘部分图纸
  2. 《30天自制操作系统》部分代码
  3. 自制17键数字机械键盘(2)——电路原理图及PCB设计、元器件焊接 - 知乎 (zhihu.com)
  4. XuSenfeng (github.com)我自己的笔记
  5. 野火stm32相关教程

其他资料

Expanded Main Page - OSDev Wiki

intel编程手册

XuSenfeng (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1430229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统卸载重装JDK

CentOS 系统是开发者常用的 Linux 操作系统&#xff0c;安装它时会默认安装自带的旧版本的 OpenJDK&#xff0c;但在开发者平时开发 Java 项目时还是需要完整的 JDK&#xff0c;所以我们部署 CentOS 开发环境时&#xff0c;需要先卸载系统自带的 OpenJDK&#xff0c;再重新安装…

《国色芳华》爆红网络,杨紫的“唐妆”惊艳四座。

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 在中国的电视剧市场近几年的趋势中&#xff0c;仙侠剧的热度逐…

real-time-emotion-detection 排坑记录

real-time-emotion-detection 排坑记录 任务踩坑回顾CV2包版本问题症状描述解决方法 模型文件路径问题症状描述解决办法 tensorflow版本问题症状描述解决办法 其他 任务 我之前跑了一个CNN情绪识别的开源代码&#xff0c;现在我想尝试把他用到我的另一个项目里。但当时那个项目…

lava学习-多态-final-抽象类

一.多态 1.什么是多态&#xff1f; 现象&#xff1a;对象多态&#xff1a;比如说一个人 类&#xff0c;他可以是一个老师&#xff0c;也可以是一个同学 行为多态&#xff1a;多个对象同一类行为的不同表现形式&#xff0c;比如两个人&#xff0c;一个人跑得快&#xff0c;另一个…

1895_分离进程的能力

1895_分离进程的能力 全部学习汇总&#xff1a; g_unix: UNIX系统学习笔记 (gitee.com) 有些理念可能在控制类的嵌入式系统中不好实施&#xff0c;尤其是没有unix这样的系统搭载的情况下。如果是考虑在RTOS的基础上看是否有一些理念可以做尝试&#xff0c;我觉得还是可以有一定…

Unity C#高级特性 Partial 详细使用案例

文章目录 实例 1&#xff1a;分隔UI逻辑实例 2&#xff1a;Unity编辑器自动生成代码实例 3&#xff1a;数据模型分割实例 4&#xff1a;序列化扩展实例 5&#xff1a;多视图架构实例 6&#xff1a;Unity编辑器自定义 inspectors 在Unity中&#xff0c;部分类&#xff08;Partia…

java基础:带参数的成员方法

上一篇博客中的成员方法是无参的&#xff0c;但成员方法其实是可以有参数的&#xff0c;可以增加代码的灵活性和健壮性。 本文以带一个参数的成员方法和带2个参数的成员方法为案例&#xff0c;加深对知识点的理解。 第一个成员方法&#xff08;带一个参数&#xff09;&#xf…

UE4学习笔记 FPS游戏制作2 制作第一人称控制器

文章目录 章节目标前置概念Rotator与Vector&#xff1a;roll与yaw与pitch 添加按键输入蓝图结构区域1区域2区域3区域4 章节目标 本章节将实现FPS基础移动 前置概念 Rotator与Vector&#xff1a; Vector是用向量表示方向&#xff0c;UE中玩家的正前方是本地坐标系的(1,0,0)&…

定义HarmonyOS IDL接口

HarmonyOS IDL简介 HarmonyOS Interface Definition Language&#xff08;简称HarmonyOS IDL&#xff09;是HarmonyOS的接口描述语言。HarmonyOS IDL与其他接口语言类似&#xff0c;通过HarmonyOS IDL定义客户端与服务端均认可的编程接口&#xff0c;可以实现在二者间的跨进程…

【C++】C++入门— 类与对象初步介绍

C入门 1 认识面向对象2 类的引入3 类的定义类的定义方式 4 类的访问限定符及封装访问限定符封装 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 认识面向对象 C语言是面向过程的&#xff0c;关注…

睿尔曼6自由度机械臂ROS驱动包功能拓展之查询指令

1&#xff1a;主要环境预览 1&#xff1a;系统&#xff1a;Ubuntu 20.04 2&#xff1a;ROS&#xff1a;noetic 3&#xff1a;对于系统要求需根据相关手册完成机械臂相关依赖安装&#xff0c;能够运行机械臂本身基本功能&#xff0c; 包括 moveit。 4&#xff1a;准备资料…

星宸科技SSC8826Q 驾驶辅助(ADAS)行车记录仪方案

星宸科技SSC8826Q 驾驶辅助&#xff08;ADAS&#xff09;行车记录仪方案 一、方案描述 SSC8826Q是高度集成的行车记录仪、流媒体后视镜解决方案&#xff0c;主芯片为ARM Cortex A53&#xff0c;dual core&#xff0c;主频高达1.2GHz&#xff0c;集成了64-bit dual-core RISC 处…

向日葵案例解析:无外网接入,医疗设备如何进行远程售后运维

随着医学科学以及生物工程技术的高速发展&#xff0c;医院对于高端医疗设备如MR、CT、B超等高科技成像设备和放射治疗设备的需求激增。医学影像检查作为一种重要的手段&#xff0c;在许多疾病确诊过程中发挥着至关重要的作用。检查结果正确与否&#xff0c;直接影响临床医生对疾…

Java设计模式 – 四大类型

设计模式 – 四大类型 创建型模式结构型模式行为型模式J2EE模式 设计模式&#xff08;Design pattern&#xff09;是重构解决方案 根据书Design Patterns – Elements of Reusable Object-Oriented Software&#xff08;中文译名&#xff1a;设计模式 – 可复用的面向对象软件元…

STM32 HAL NTC(3950 10k)查表法

NTC&#xff08;Negative Temperature Coefficient&#xff09;是指随温度上升电阻呈指数关系减小、具有负温度系数的热敏电阻现象和材料。该材料是利用锰、铜、硅、钴、铁、镍、锌等两种或两种以上的金属氧化物进行充分混合、成型、烧结等工艺而成的半导体陶瓷&#xff0c;可制…

python绘图指南—Bokeh库从基础到高级打造交互式数据可视【第51篇—python:Bokeh库】

文章目录 Bokeh库深度解析&#xff1a;从基础到高级&#xff0c;打造交互式数据可视化安装Bokeh库Bokeh绘图基础基础图形绘制完善图形 实例演示案例&#xff1a;股票走势图 Bokeh库高级功能探索1. 工具栏和交互性2. 高级图形元素3. 数据链接和动态更新 Bokeh库与其他库的整合1.…

C# ONNX使用入门教程

背景 有新入坑的老哥不太了解C# onnx 运行的机理&#xff0c;我这边详细介绍一下&#xff0c;之前直接放官方的样例有点草率了。 准备[python环境] 1、要使用onnx&#xff0c;首先我们就自己生成一个onnx文件&#xff0c;请大家准备一下以下需要的[python]环境 python 版本…

linux 文件查看 head 、 cat 、 less 、tail 、grep

查看文件详细信息 stat 文件 cat 》》适合显示小文件【行数比较少】&#xff0c;如果行数较多&#xff0c;屏幕显示不完整&#xff08;如果虚拟操作&#xff0c;是无法上下键的&#xff0c;或者滚动鼠标的&#xff0c;第三方 xsheel&#xff0c;crt 可以方向键查看&#xf…

MySQL 多表查询

重点&#xff1a; MySQL 的 三种安装方式&#xff1a;包安装&#xff0c;二进制安装&#xff0c;源码编译安装。 MySQL 的 基本使用 MySQL 多实例 DDLcreate alter drop DML insert update delete DQL select 3.5&#xff09;DDL 语句 表&#xff1a;二维关系 设计表&…

【微服务】skywalking自定义链路追踪与日志采集

目录 一、前言 二、自定义链路追踪简介 2.1 自定义链路追踪应用场景 2.2 链路追踪几个关键概念 三、skywalking 自定义链路追踪实现 3.1 环境准备 3.2 集成过程 3.2.1 导入核心依赖 3.2.2 几个常用注解 3.2.3 方法集成 3.2.4 上报追踪信息 四、skywalking 自定义日志…