今天我学会了什么 2024-2

123 阅读4分钟

2024-2-20

Kotlin

混淆(Android/Kotlin+Java)

参见 stackoverflow.com/a/50819864。… app 必须也开启混淆才能有效。如果主模块开启了混淆但子模块没有开启,子模块也会被混淆。换言之,主模块的混淆开关会完全覆盖子模块的混淆开关。

对于子模块,混淆规则通常要写在 consumer-rules 里,因为 proguard-rules 是被当作单独发布的模块时才被使用的,一般并不这么做。

2024-2-23

C

STM32 ADC, PWM, TIM(STM32/C)

STM32 中,管脚要作为 ADC 工作,需要选择 ADCx_INy 模式。要轮询工作,可以用 Start、PollForConversion、GetValue、Stop。

const uint32_t INFINITY = 0xFFFFFFFF;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, INFINITY);
const uint32_t value = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1); // 实践表明这是可选的。

注意 ADC 可能只有一个,但通道有很多个。多个通道都需要采样时,只能依次进行。

PWM 实际是 TIM 提供的能力。管脚要作为 PWM 工作,需要选择 PWMx_CHy 模式,对应 TIM 的通道要选择 PWM Generation CHy 模式。设置占空比实际上是设置比较值,HAL 库提供了一个宏 __HAL_TIM_SET_COMPARE 可以一键设置。

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, counter_target);

本质是直接设置寄存器。

#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))

TIM 用作定时,需要将通道设置为 Output Compare No Output 模式。周期是 (Prescaler + 1) * (CounterPeriod + 1) 个时钟周期,时钟取决于 APB1 和 APB2,具体参见手册,看 TIMx 的时钟频率对应谁。

TIM 用作定时的中断通知函数名为 HAL_TIM_PeriodElapsedCallback。下面的代码只区分了一组定时器,没有区分各个通道。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if (htim == &htim1) {
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
	}
}

对于高级定时器,要激活以上中断,需要启用 TIM update interrupt 中断。普通定时器启用 TIM global interrupt 即可。

2024-2-24

C

STM32 I2C(STM32/C)

使用 STM32 的 HAL 库实现 Transmit 后不发 stop condition 并紧接着 Receive,需要用到 Seq 系列函数。Seq 系列函数不提供阻塞模式,只有中断和 DMA 模式。使用中断模式时,需要打开中断事件开关(I2C event interrupt)。为了方便,把任务交给中断处理后,也可以轮询等待传输结束。轮询代码需要手动编写,方法是检查 I2C 的状态是否为 Ready。

/* Send device address, with no STOP condition */
ret = HAL_I2C_Master_Seq_Transmit_IT(_hi2c, _address, (uint8_t*)&RegisterAddr, 1, I2C_LAST_FRAME_NO_STOP);
if(ret == HAL_OK) {
    /* Read data, with STOP condition  */
    while (HAL_I2C_GetState(_hi2c) != HAL_I2C_STATE_READY)
        ;
    ret = HAL_I2C_Master_Seq_Receive_IT(_hi2c, _address, pBuffer, NumByteToRead, I2C_LAST_FRAME);
    while (HAL_I2C_GetState(_hi2c) != HAL_I2C_STATE_READY)
        ;
}

参见 blog.csdn.net/NeoZng/arti… 系列函数通过指定额外参数,可以条件 I2C 单帧传输的具体时序。

可变参数(所有平台/C)

C 语言中的可变参数通常结合 vsprintf(或 vsscanf)使用。需要包含头文件:

#include <stdarg.h>
#include <stdio.h>

关键类型为 va_list,关键函数为 va_startvsprintfva_end

int huart2_printf(const char* format, ...) {
	static char buf[256];
	int ret;

	va_list list;
	va_start(list, format);
	ret = vsprintf(buf, format, list);
	va_end(list);

	const uint32_t INFINITY = 0xFFFFFFFF;
	HAL_UART_Transmit(&huart2, (const uint8_t*)buf, (uint16_t)ret, INFINITY);

	return ret;
}

C 语言中宏的可变参数用 __VA_ARGS__ 引用。

#define printf(...) huart2_printf(__VA_ARGS__)

2024-2-26

Android Studio

配置 JDK

参见 stackoverflow.com/a/76655993。… -> Build, Execution, Deployment -> Gradle 中,选择 Gradle JDK 下拉列表框,可以点击里面的 Download JDK 自动下载 JDK。

2024-2-27

硬件

布线

双面板可以其中一面全部铺地。铺地时尽可能保证地平面的完整。

2024-2-28

Kotlin

SharedPreferences(Android/Kotlin+Java)

参见 juejin.cn/post/684490… 可以做轻量级的配置存储,底层原理是将所有配置保存为单个 XML 文件。其内部有一系列机制保证数据完整性,之后再学习。