Minecraft 1.20.1 Forge Mod 开发笔记0:新建文件夹

项目的创建

谷歌搜索Forge mdk,然后从Forge官网下载即可。下载的时候必须留意Forge的版本,
否则有可能开发出来的Mod和游戏不兼容。还要注意和Optifine是否兼容,版本太新的话是没有对应的Op的。
Forge不同版本之间的差异比较明显,至少中间版本号一样才比较保险。这里用的是Forge 47.2.18

下载完毕后解压,在目录中执行gradlew genIntellijRun可以创建Idea的环境,方便在IDE中运行和调试。

PS: Idea的构建功能有时候会抽风,导致在代码没有问题的情况下,游戏运行的时候报错。其次,它的构建功能不会输出最终的jar。
解决方案是在终端中使用gradlew buildgradlew runClient,不通过Idea

参考

https://www.mcjty.eu/docs/1.20/ep1


STM32 HAL库GPIO初始化函数分析

函数代码及其注释

HAL库的封装非常完善,在可以使用HAL_GPIO_Init函数初始化端口用于外部中断,无需自己手动修改寄存器什么的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
uint32_t position = 0x00u; // 记录当前初始化的是第几个管脚
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister; /* Store the address of CRL or CRH register based on pin number */
uint32_t registeroffset; /* offset used during computation of CNF and MODE bits placement inside CRL or CRH register */

/* Check the parameters */
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));

/* Configure the port pins */
while (((GPIO_Init->Pin) >> position) != 0x00u)
{
/* Get the IO position */
/* 记录当前初始化的是哪个管脚(二进制形式,1为初始化的管脚)*/
ioposition = (0x01uL << position);

/* Get the current IO position */
/* 中间变量,用于检查position指定的管脚是否被指定要初始化 */
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;

if (iocurrent == ioposition)
{
/* Check the Alternate function parameters */
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));

/* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
/* 根据输入变量生成对应寄存器的值 */
switch (GPIO_Init->Mode)
{
/* If we are configuring the pin in OUTPUT push-pull mode */
/* 推挽输出配置 */
case GPIO_MODE_OUTPUT_PP:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
break;

/* If we are configuring the pin in OUTPUT open-drain mode */
/* 开漏输出配置 */
case GPIO_MODE_OUTPUT_OD:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
break;

/* If we are configuring the pin in ALTERNATE FUNCTION push-pull mode */
/* 特殊功能推挽输出配置 */
case GPIO_MODE_AF_PP:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_PP;
break;

/* If we are configuring the pin in ALTERNATE FUNCTION open-drain mode */
/* 特殊功能开漏输出配置 */
case GPIO_MODE_AF_OD:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_OD;
break;

/* If we are configuring the pin in INPUT (also applicable to EVENT and IT mode) */
/* 普通输入、中断输入和事件输入 */
case GPIO_MODE_INPUT:
case GPIO_MODE_IT_RISING:
case GPIO_MODE_IT_FALLING:
case GPIO_MODE_IT_RISING_FALLING:
case GPIO_MODE_EVT_RISING:
case GPIO_MODE_EVT_FALLING:
case GPIO_MODE_EVT_RISING_FALLING:
/* Check the GPIO pull parameter */
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
if (GPIO_Init->Pull == GPIO_NOPULL)
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_FLOATING;
}
else if (GPIO_Init->Pull == GPIO_PULLUP)
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;

/* Set the corresponding ODR bit */
GPIOx->BSRR = ioposition;
}
else /* GPIO_PULLDOWN */
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;

/* Reset the corresponding ODR bit */
GPIOx->BRR = ioposition;
}
break;

/* If we are configuring the pin in INPUT analog mode */
case GPIO_MODE_ANALOG:
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_ANALOG;
break;

/* Parameters are checked with assert_param */
default:
break;
}

/* Check if the current bit belongs to first half or last half of the pin count number
in order to address CRH or CRL register*/
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);

/* Apply the new configuration of the pin to the register */
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

/*--------------------- EXTI Mode Configuration ------------------------*/
/* 外部中断配置 */
/* Configure the External Interrupt or event for the current IO */
if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
{
/* Enable AFIO Clock */
__HAL_RCC_AFIO_CLK_ENABLE();
/* EXTICR 16位 总共四个,每个EXITCR分别控制四个中断,总共 4 * 4 = 16个中断
position >> 2 ==> position / 4 ,把 0-15 转换为 0-4 即找到对应的寄存器
*/
temp = AFIO->EXTICR[position >> 2u];
/* position & 3 ==> position % 4 ,即找到在当前EXITCR寄存器中,position对应的是哪一个中断。
乘4是因为左移的单位是 “中断数”,一个中断对应4位,我要左移 n个中断 意味着要左移 n * 4位。
*/
CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
AFIO->EXTICR[position >> 2u] = temp;


/* Enable or disable the rising trigger */
/* 设置上升沿触发 */
if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
{
SET_BIT(EXTI->RTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->RTSR, iocurrent);
}

/* Enable or disable the falling trigger */
/* 设置下降沿触发 */
if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
{
SET_BIT(EXTI->FTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->FTSR, iocurrent);
}

/* Configure the event mask */
if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
{
SET_BIT(EXTI->EMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->EMR, iocurrent);
}

/* Configure the interrupt mask */
/* 设置中断屏蔽 为0时不允许对应中断 */
if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
{
SET_BIT(EXTI->IMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->IMR, iocurrent);
}
}
}
// 配置下一位
position++;
}
}

附:各寄存器的结构

EXTICR 外部中断控制寄存器

总共有四个,结构都类似
EXTICR

EXTI_IMR 外部中断屏蔽寄存器

EXTICR


STM32F103的RCC时钟及其设置

STM32F103的系统时钟源

和C51不同STM32F103的系统时钟源有好几个,根据官方手册,截取一个比较重要的部分如下:
时钟截图
从图中可以看出,系统时钟SYSCLK的来源包括HSI,PLLCLK,HSE,可以通过软件选择它们中的其中一个。

HSE 高速外部时钟信号

这个时钟信号直接来源于外部晶振(或者外部时钟),通过 OSC_IN 和 OSC_OUT 两个端子输入。晶振的大小介于4-16MHz。

HSI 内部RC振荡信号

由内部 8MHz RC振荡器生成。

PLL 锁相环

PLL 允许以 HSE 或者 HSI 作为原始信号,经过处理后得到系统时钟信号。由图可知,HSI 信号频率减半后,输入到 PLLSRC 。HSE则直接输入 PLLSRC 。可以通过软件选择用哪一个作为原始信号。PLLMUL 可以把输入信号的频率乘某个倍数,例如 8MHz 的 HSE 信号,输入到PLLMUL,用软件设置乘4倍后,就能得到 32MHz 的 PLLCLK 信号。

设置方法

主要利用 HAL_RCC_OscConfigHAL_RCC_ClockConfig 函数进行设置。
这个函数原本是用STM32CubeMX生成的,在其基础上进行了修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.
*/
// 这里同时配置HSE和PLL
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 要设置的信号源
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 设置为启用
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 设置启用PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 设置PLL原始信号为HSE
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4; // 设置PLLMUL倍率为4

if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
...
...
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 设置系统时钟源为PLLCLK
...
...
}

其它类型的时钟源也类似。

测试是否成功设置

调用 HAL_RCC_GetSysClockFreq 函数,在调试器中查看其返回值即可知道系统的时钟频率是否和预期的一致。


STM32CubeIDE Windows版配置

网上的教程都说,安装好了STM32CubeIDE直接创建工程就可以了,然而实际测试下来却完全不是这样。

汉化

安装完毕后,首先要把界面显示语言更改为中文。因为这个ide是基于eclipse的,因此导入eclipse的汉化包就行。

Help -> Install New Software... 打开安装窗口
安装窗口

在下拉选单上选择官方的软件源,选中中文包(Babel Language Pack for eclipse in Chinese),打勾,点击下一步,
窗口会显示准备安装的所有包,然后点击完成即可。

由于软件源在国外,所以可能会出现下载缓慢、失败的情况。只需要在Work With框输入国内镜像源地址,点击Add...即可。
如南京大学镜像源地址:https://mirrors.nju.edu.cn/eclipse/technology/babel/update-site/latest/

字符、图标大小调整

默认情况下,IDE的图标很小,编辑器里面的字符也非常的小。图标小的问题要通过 主程序右键 -> 属性 -> 兼容性
-> 更改高DPI设置… -> 勾选替代高DPI缩放行为,并改为系统(增强)

字符大小和样式的调整在窗口 -> 首选项 -> 常规 -> 外观 -> 颜色和字体 中调整。

后来还发现C代码中的注释是灰色的,在黑暗模式下几乎看不清,因此改为了绿色。修改选项在
首选项 -> C/C++ -> 编辑器(Editor) -> 语法高亮颜色(Syntax Coloring)中

安装固件包

旧版本似乎是可以在创建项目的时候自动下载相应固件包的,但是创建的时候却显示需要登录。不想登录的话,可以在github上下载固件包自行安装。
如STM32F1系列的固件包地址是:https://github.com/STMicroelectronics/STM32CubeF1

根据对应的tag下载合适的版本,并解压到对应文件夹,一般是 %homepath%/Stm32Cube/Repository/
在帮助 -> Manager Embedded Software Packages 里面可以检查安装成功与否

然后再重新创建项目,就能正常生成初始代码了。


C语言long类型的位数

测试程序

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <limits.h>

int main()
{
long l = LONG_MAX + 1;
printf("%lld,%lld\n",LONG_MAX,l);
return 0;
}

测试结果

在 Windows 上的测试结果

1
2147483647,2147483648

在 Linux 上的测试结果

1
9223372036854775807,-9223372036854775808

注:测试操作系统均为64位操作系统,程序也是64位程序,都使用gcc进行编译,使用默认参数进行编译

解读

显然在 x64 Windows上,long类型被定义为32位整数,相当于int。而在 x64 Linux 上,
则被定义为64位整数,相当于long long。有说法认为在32位Linux上,long是32位的,但暂未实际测试。


C51单片机时钟周期/计时器初值/串口通信波特率

C51时钟周期、机器周期、晶振频率

C51一般使用内部晶振,即外接晶振元件,内部驱动。晶振频率常取12MHz或11.0592MHz。

晶振频率的倒数是时钟周期,两个时钟周期为一个,C51规定,六个拍,
即12个时钟周期是一个机器周期

因此,

1
12 / 晶振频率 = 机器周期

计时器初始值计算

以方式1为例。方式1是16位计时器,每个机器周期计时器+1,当计数值为 2^16 + 1= 65535 + 1 的时候,
计时器溢出。因此可以得到,如果要计时器n个机器周期的时间后溢出,则须设置初值为 65536 - n。
总的来说,如果已知晶振频率f,计时器计时的时间t,则方式1的计时器初始值 Vinit 为

1
Vinit = 65536 - t / (12 / f)

串口波特率计算

波特率通常是人为规定的一系列数值,通常我们使用串口的工作方式1。其公式为:

1
baud = (2^(SMOD) / 32) * (T1of)

SMOD是一个bit型值,是人为设置的。T1of是计时器T1的溢出率(此时T1是工作方式2),其计算公式为:

1
T1of = f /{12×[256 - (TH1)]}

TH1为计时器1高八位初值,f是晶振频率,单位Hz

例子

如果已知串口规定的波特率为 9600 baud,SMOD == 0,则

1
2
T1of = baud * 32 = 9600 * 32 = 30720
TH1 = 256 - (f / T1of / 12) = 256 - (11059200 / 30720 / 12) = 253 = 0xfd

std::memory_order

这是一个enum类型,在头文件<atomic>中定义。

1
2
3
4
5
6
7
8
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;

std::memory_order 指定内存访问,包括常规的非原子内存访问,如何围绕原子操作排序。在没有任何约束的多处理器系统上,多个线程同时读或写数个变量时,一个线程能观测到变量值更改的顺序不同于另一个线程写它们的顺序。实际上,更改的顺序甚至能在多个读取线程间相异。一些类似的效果还能在单处理器系统上出现,因为内存模型允许编译器进行变换。

库中所有原子操作的默认行为提供序列一致定序(见后述讨论)。该默认行为可能有损性能,不过可以给予库的原子操作额外的 std::memory_order 实参,以指定确切的约束,在原子性外,编译器和处理器还必须强制该操作。

可以用这个类型的变量来规定“内存访问顺序”。
假设现在有n个atomic变量,编号为1,2……n,并且有多个线程同时访问这些变量。
有些线程读取,有些线程写入,假设写入线程不断按编号顺序依次更新这些变量,
无法保证在读取线程中,观测到的变量更改也是按顺序的。(尤其在多处理器系统中)

因此可以在进行原子操作时传入一个memory_order变量,来规定应这些变量读取顺序。

个人理解

memory_order_relaxed 无限制

memory_order_acquire 确保先进行(标记为relax的操作)再进行这个操作

memory_order_consume ?

memory_order_seq_cst ?

memory_order_seq_cst ?