此笔记是我在备考蓝桥杯嵌入式时所记录的。标**的意为最终方案,可直接跳转学习
现在(6月15日)已经考完了。考到了我笔记中的时间转换时间戳以及修改rtc时间和字符串处理的函数。不算难,但是量太多了!!最后十分钟写完了没来得及全部验证,最后五分钟看到好多bug,改改又交了三次。大心脏差点承受不住。希望有个好成绩!—>老三
新建工程
当前单片机芯片:STMG431RBT6
比赛开始时,解压提供的库文件。
在工具-Cube-中可以找到不同版本的
安装提供的芯片包
Cube打开后,选择芯片
打开RCC的外部时钟 选择Debug调试方式为串口
外部晶振选择为24MHZ(具体看手册),系统时钟选择80MHZ(HSE、PLL)
设置MDK,设置项目名称,勾选分离.c.h文件
取消勾选使用默认的库文件位置,选择赛事方提供的解压好的库文件。
设置,下载器为DAP;设置下载后自动运行。
点个灯

使用寄存器的原因是:因为LCD也用了这几个引脚,为了防止在使用LCD的时候改变LED
初始化时,将所有置为1,PD和PC都是,让LED默认为熄灭
核心代码:
使用LCD模块
初始化
复制资料的lcd.c 、 lcd.h、fonts.h
到Code文件中
在main.c中初始化LCD
核心代码
高亮部分字符函数
闪烁部分字符函数
解决LCD和LED引脚冲突的问题
- 在GPIO
修改
LCD_DisplayStringLine的函数,在第一行和最后一行加上如下代码同理,例如
LCD_Init、 LCD_Clear 都需要加上这两行代码。 注意是uint16_t闪个灯
定时器中断实现
开启定时器, 计算中断时间, psc arr
开启NVIC中断
在tim.c中找到tim.h, 找到中断回调函数(2531 行),
核心代码:
注意不要在中断中写控制LED的函数,否则可能会在LCD函数中,打开PD2的使能,导致在复原之前修改了GPIOC的输出。
所以一般把
show_led(1, led_mode); 写在LCD后按键长按短按双击检测
下面的太麻烦了, 简单的如下:
- 仅支持抬起和按下、长按检测(极简版)
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == 0)
key_value = 3;
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0)
key_value = 4;
else
key_value = 0;
key_down = key_value & (key_value ^ key_old);
key_up = ~key_value & (key_value ^ key_old);
key_old = key_value;
if(key_down == 4) //按下
{
key_long_tick = uwTick;//开始计时
}
else if(key_vlaue == 4 && uwTick - key_long_tick > 800) //按下,长按生效
{
Naozhong_Time.AlarmTime.Hours++;
}
}
#define DOUBLE_TIME 300
#define SHORT_TIME 500
#define LONG_TIME 1000
uint8_t key_value, key_old , key_up, key_down;
uint32_t key_down_time = 0;
uint32_t key_up_time = 0;
uint8_t key_count = 0;
uint8_t key_last = 0;
uint8_t key_short, key_double, key_long;
uint32_t key_tick = 0;
void key_proc(void)
{
if(uwTick - key_tick < 20)
return;
key_tick = uwTick;
uint8_t key_mask = 0;
key_mask |= (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0x01 : 0;
key_mask |= (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET) ? 0x02 : 0;
key_mask |= (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET) ? 0x04 : 0;
key_mask |= (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0x08 : 0;
uint8_t key_new_vlaue = 0;
if(key_mask == (0x01 + 0x02)) //key 1 + 2
key_new_vlaue = 5;
else if(key_mask == (0x01 + 0x04)) //key 1 + 3
key_new_vlaue = 6;
else{
if(key_mask == 0x01) key_new_vlaue = 1;
else if(key_mask == 0x02) key_new_vlaue = 2;
else if(key_mask == 0x04) key_new_vlaue = 3;
else if(key_mask == 0x08) key_new_vlaue = 4;
}
key_down = key_new_vlaue & (key_new_vlaue ^ key_old);
key_up = ~key_new_vlaue & (key_new_vlaue ^ key_old);
key_old = key_value = key_new_vlaue;
if(key_down)
{
key_down_time = uwTick;
key_last = key_value;
if(uwTick - key_up_time < DOUBLE_TIME && key_last == key_value)
{
key_double = key_value; //触发双击
key_count = 0;
}
}
if(key_up)
{
if(uwTick - key_down_time < SHORT_TIME && key_double == 0)
{
key_up_time = uwTick;
key_count++;
}
}
if(key_value && (uwTick - key_down_time > LONG_TIME))
{
key_long = key_value; //触发长按
key_count = 0;
}
if(!key_value && (uwTick - key_up_time > DOUBLE_TIME)) //将此处DOUBLE_TIME 替换为0则双击无效,短按触发极快
{
if(key_count == 1)
key_short = key_last; //触发短按
else
key_short = 0;
key_count = 0;
key_double = 0;
key_long = 0;
}
// if(key_double || key_long)
// key_short = 0;
}
/ 按键处理 /
// 按键状态结构体
typedef struct {
GPIO_TypeDef* port; // 端口
uint16_t pin; // 引脚
int time; // 周期计时(2s清零)
uint8_t timeEN; // 计时使能
uint8_t count; // 按下次数
uint8_t press_flag; // 按下标志
int press_time; // 按下时长
} Key;
// 初始化四个按键(B1-B4)
Key keys[] = {
{GPIOB, GPIO_PIN_0, 0, 0, 0, 0, 0}, // B1
{GPIOB, GPIO_PIN_1, 0, 0, 0, 0, 0}, // B2
{GPIOB, GPIO_PIN_2, 0, 0, 0, 0, 0}, // B3
{GPIOA, GPIO_PIN_0, 0, 0, 0, 0, 0} // B4
};
//定义按键个数
#define KEY_COUNT (sizeof(keys)/sizeof(keys[0]))
// 按键扫描逻辑
void key_scan(void) {
for(uint8_t i=0; i<KEY_COUNT; i++) { //逐个扫描按键
Key* k = &keys[i];
if(k->time > 1800) { // 检测2s周期末端
if(k->count == 1) { //如果只按了一下
if(k->press_time < 1000) {
sprintf(text, "Btn%d: Click", i+1); //并且时间小于1s,那么是短按
} else {
sprintf(text, "Btn%d: Long ", i+1); //并且时间大于1s,那么是长按
}
LCD_DisplayStringLine(Line2, (uint8_t*)text); //显示出来
}
else if(k->count == 2 && k->press_time < 1000) { //如果按了两下,并且时间小于1s
sprintf(text, "Btn%d: Double", i+1);
LCD_DisplayStringLine(Line2, (uint8_t*)text); //那么显示双击
}
// 重置状态
k->time = k->count = k->press_time = k->timeEN = 0;
}
}
}
//定时器终端扫描处理
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//定时器3中断函数 1ms一次
else if(htim->Instance == TIM3)
{
for(uint8_t i=0; i<KEY_COUNT; i++) { //逐个扫描按键
Key* k = &keys[i];
// 检测按键动作
uint8_t pinState = HAL_GPIO_ReadPin(k->port, k->pin);
if(pinState == GPIO_PIN_RESET && !k->press_flag) { // 按下
k->press_flag = 1; //标记按下
k->timeEN = 1; //开始计时
k->count++; //记录按下次数
}
else if(pinState == GPIO_PIN_SET && k->press_flag) { // 释放
k->press_flag = 0; //标记松开
}
// 更新按下时长
if(k->press_flag) k->press_time++;
// 周期计时
if(k->timeEN) {
if((k->time++) > 2000) { // 满2s自动重置
k->time = k->count = k->press_time = k->timeEN = 0;
}
}
}
}
}
if(lcd_highshow == 4) lcd_highshow = 1;
if(lcd_highshow == 0) lcd_highshow = 3;
if(lcd_highshow == 1)
{
LCD_SetBackColor(Blue);
sprintf(text, "Hello world"); //将字符串复制到字符数组
LCD_DisplayStringLine(Line0, (uint8_t*)text);
LCD_SetBackColor(Black);
sprintf(text, "count:%d ", count);
LCD_DisplayStringLine(Line1, (uint8_t*)text);
sprintf(text, "Teeeeest ");
LCD_DisplayStringLine(Line2, (uint8_t*)text);
}
else if(lcd_highshow == 2)
{
sprintf(text, "Hello world"); //将字符串复制到字符数组
LCD_DisplayStringLine(Line0, (uint8_t*)text);
LCD_SetBackColor(Blue);
sprintf(text, "count:%d ", count);
LCD_DisplayStringLine(Line1, (uint8_t*)text);
LCD_SetBackColor(Black);
sprintf(text, "Teeeeest ");
LCD_DisplayStringLine(Line2, (uint8_t*)text);
}
else if(lcd_highshow == 3)
{
sprintf(text, "Hello world"); //将字符串复制到字符数组
LCD_DisplayStringLine(Line0, (uint8_t*)text);
sprintf(text, "count:%d ", count);
LCD_DisplayStringLine(Line1, (uint8_t*)text);
LCD_SetBackColor(Blue);
sprintf(text, "Teeeeest ");
LCD_DisplayStringLine(Line2, (uint8_t*)text);
LCD_SetBackColor(Black);
}
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); //启动TIM的CHANNEL2的PWM
TIM2->CCR2 = 500; //设置CCR为500
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM17)
{
//capture_value = HAL_TIM_ReadCapturedValue(&htim17, TIM_CHANNEL_1); //捕获CNT值(实际读取CCR,捕获是把CNT赋值给CCR
capture_value = TIM17->CCR 1; //也可以这样捕获,更简洁
TIM17->CNT = 0; //清零
pwm_f = 80000000/(capture_value) - 1; //(手动修正,哈哈哈)
}
}
char text[20]; //一行刚好显示20个字符
uint32_t capture1_value = 0; //捕获值
uint32_t capture2_value = 0;
uint32_t pwm1_f = 0;
uint32_t pwm2_f = 0;
void show_lcd(void)
{
sprintf(text, "Hello world"); //将字符串复制到字符数组
LCD_DisplayStringLine(Line0, (uint8_t*)text);
//显示输出频率1
sprintf(text, " fre1:%d ", pwm1_f);
LCD_DisplayStringLine(Line1, (uint8_t*)text);
//显示输出频率2
sprintf(text, " fre2:%d ", pwm2_f);
LCD_DisplayStringLine(Line2, (uint8_t*)text);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM16)
{
capture1_value = TIM16->CCR1;
TIM16->CNT = 0; //清零
pwm1_f = 80000000/(80*capture1_value);
}
else if(htim->Instance == TIM2)
{
capture2_value = TIM2->CCR1;
TIM2->CNT = 0; //清零
pwm2_f = 80000000/(80*capture2_value);
}
}
uint32_t R39_fre, R40_fre, R39_dut, R40_dut,R39_High, R40_High, R39_OK, R40_OK;
uint8_t R39_state, R39_flag, R40_state, R40_flag;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
if(R39_flag == 0)
{
switch(R39_state)
{
case 0: / 捕获第一个上升沿 /
TIM3->CNT = 0;
R39_High = 0;
R39_OK = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
R39_state = 1;
break;
case 1: / 捕获第一个下降沿 /
R39_High = TIM3->CCR1;
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
R39_state = 2;
break;
case 2: / 捕获第二个上升沿 /
R39_OK = TIM3->CCR1;
R39_flag = 1;
R39_state = 0;
break;
}
}
}
else if(htim->Instance == TIM8)
{
if(R40_flag == 0)
{
switch(R40_state)
{
case 0: / 捕获第一个上升沿 /
TIM8->CNT = 0;
R40_High = 0;
R40_OK = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(&htim8, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
R40_state = 1;
break;
case 1: / 捕获第一个下降沿 /
R40_High = TIM8->CCR1;
__HAL_TIM_SET_CAPTUREPOLARITY(&htim8, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
R40_state = 2;
break;
case 2: / 捕获第二个上升沿 /
R40_OK = TIM8->CCR1;
R40_flag = 1;
R40_state = 0;
break;
}
}
}
}
void Read_fre_dut_proc(void)
{
if(R39_flag == 1)
{
R39_flag = 0;
R39_fre = (double)1000000 / (R39_OK + 1);
R39_dut = (double)R39_High / R39_OK *100;
}
if(R40_flag == 1)
{
R40_flag = 0;
R40_fre = (double)1000000 / (R40_OK + 1);
R40_dut = (double)R40_High / R40_OK *100;
}
}
uint32_t R38_value;
double R38_V;
void Get_R38_ADC_Value_proc(void)
{
HAL_ADC_Start(&hadc1);
R38_value = HAL_ADC_GetValue(&hadc1);
R38_V = R38_value * 3.3 / 4095;
}
uint32_t DAC_Data = 0;
void DAC_Out_proc(void)
{
DAC_Data = R37_value;
HAL_DAC_Start(&hdac1,DAC_CHANNEL_1);
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_Data);
}
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 50);
return ch;
}
uint8_t u1_rxbuff[30];
uint8_t u1_rxcount = 0;
extern uint8_t u1_rxdata;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
u1_rx_tick = uwTick; //防止提前刷新
u1_rxbuff[u1_rxcount++] = u1_rxdata;
HAL_UART_Receive_IT(&huart1, &u1_rxdata, 1);
}
}
uint32_t u1_rx_tick = 0;
void U1_Rx_proc(void)
{
if(uwTick - u1_rx_tick < 50)
return;
u1_rx_tick = uwTick;
if(u1_rxbuff[0] == '#' && u1_rxcount == 1)
{
printf("成功反转屏幕rn");
}
u1_rxcount = 0;
memset(u1_rxbuff, 0, sizeof(u1_rxbuff));
}
uint8_t u1_rxdata; //全局变量哦
main()
{
HAL_UART_Receive_IT(&huart1, &u1_rxdata, 1);
while(1)
{
...
}
}
#include "sys.h"
char text[20];
uint8_t uart_rxdata;
uint8_t uart_flag = 0;
uint8_t uart_r_count = 0;
uint8_t uart_r_buff[20];
char uart_sendbuff[20];
void show_lcd(void)
{
sprintf(text, "Hello world"); //将字符串复制到字符数组
LCD_DisplayStringLine(Line0, (uint8_t*)text);
}
//接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
TIM4->CNT = 0;
uart_flag = 1;
uart_r_buff[uart_r_count] = uart_rxdata; //把接收到的数据存入
uart_r_count++;
HAL_UART_Receive_IT(&huart1, &uart_rxdata, 1);
}
}
void uart_rx_fun(void)
{
if(uart_flag)
{
if(TIM4->CNT > 15)
{
/------------逻辑处理部分------------/
// 长度判断 && 字符串比较
if(uart_r_count == 3 && memcmp(uart_r_buff, "lan", 3) == 0)
{
sprintf(uart_sendbuff, "lanrn");
HAL_UART_Transmit(&huart1, (uint8_t *)uart_sendbuff, strlen(uart_sendbuff), 50);
}
/------------逻辑处理部分------------/
// 清除缓冲区(修复循环条件)
uint8_t temp_count = uart_r_count;
uart_r_count = 0;
uart_flag = 0;
memset(uart_r_buff, 0, temp_count); // 仅清除实际接收的部分
}
}
}
int is_leap(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 验证日期合法性
int is_valid_date(int year, int month, int day) {
if (month < 1 || month > 12) return 0;
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (is_leap(year)) days[2] = 29; // 闰年2月天数
return day >= 1 && day <= days[month];
}
// 判断时间(HHmm)是否合法
int is_valid_time(uint8_t hh, uint8_t mm, uint8_t ss) {
// 拆分小时和分钟
// 验证范围:小时 0-23,分钟 0-59
return (hh >= 0 && hh <= 23) && (mm >= 0 && mm <= 59) && (ss >= 0 && ss <= 59);
}
//计算日期
struct tm //一个结构体
time_t current_time; //存储时间戳
localtime()
功能:将时间戳转换为本地时间的 struct tm 结构体。
实例:
time_t current_time;
struct tm *local = localtime(¤t_time);
printf("当前本地时间: %d-%02d-%02d %02d:%02d:%02dn",
local->tm_year + 1900, local->tm_mon + 1, local->tm_mday,
local->tm_hour, local->tm_min, local->tm_sec);
mktime()
功能:将 struct tm 结构体表示的时间转换为时间戳。
实例:
time_t timestamp = mktime(&tm_info);
printf("转换后的时间戳: %ldn", timestamp);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
//输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
R39_t_count = TIM3->CCR1; //记录一个周期记录的数量
}
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
R39_up_count = TIM3->CCR2; //记录高电平 记录的数量
}
}
}
uint32_t ADC_Value;
HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);
HAL_ADC_Start_DMA(&hadc1, &ADC_Value, 1);
V = 3.3/65535*ADC_Value;

- 配置DAC


波形buff生成函数
在main中启用的函数
修改DAC频率
TIM15->ARR = ( 80000000 / (TIM15->PSC + 1) / 目标频率 - 1 ) ;
得到DAC通道1当前电压
DAC_A = 3.3 * (DAC1->DOR1 / 4095.0)
** eeprom 连续 读写
芯片型号 M24C02
eeprom,非易失性存储器(掉电不丢失)
主机和从从机发消息,都需要回应。
设备地址


E1对应A0
E2对应A1
E3对应A2
可见,全为0.
R/W位,为1,是R读,
R/W位,为0,是W写;
地址:0xa0
读 0xa1
底层程序为官方提供
main.c
核心读写代码:
fun.c
使用方法
eeprom_w(0, (uint8_t*)&test, sizeof(test));** MCP4017
MCP4017 的IIC地址:0101111 (0x5e)
100K 分为127份 0 为 0Ω , 127 为10K

最后一位为R/W 读为1 写为0
一般只用写, 也就是0101 1110 = 0x5e
100K 分为127份 0 为 0Ω , 127 为10K

I2CStop();
}
uint8_t mcp4017_r()
{
uint8_t data;
I2CStart();
I2CSendByte(0x5f);
I2CWaitAck();
data = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return data;
}
RTC_DateTypeDef rtc_date;
RTC_TimeTypeDef rtc_time;
uint32_t rtc_tick = 0;
void RTC_proc(void)
{
if(uwTick - rtc_tick < 100)
return;
rtc_tick = uwTick;
HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BIN);
}
RTC_AlarmTypeDef Temp;
HAL_RTC_GetAlarm(&hrtc, &Temp, RTC_ALARM_A, RTC_FORMAT_BIN);
Temp.AlarmTime.Seconds += 5;
HAL_RTC_SetAlarm_IT(&hrtc, &Temp, RTC_FORMAT_BIN);
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
struct keys{
uint32_t up_time; //抬起累计时间
uint32_t down_time; //按下累计时间
uint8_t key_short; //短按触发
uint8_t key_double; //双击触发
uint8_t key_long; //长按触发
uint8_t key_swt; //状态机
uint8_t key_state; //当前按键状态
};
extern struct keys key[];
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); //回调函数
#endif
#include "key.h"
struct keys key[4] = {0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM17)
{
// 获取当前状态 0 为按下
key[0].key_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key[1].key_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key[2].key_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key[3].key_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
// 处理四个按键状态机
uint8_t i = 0;
for(i = 0; i < 4; i++)
{
switch(key[i].key_swt)
{
case 0: //初始状态
{
if(key[i].key_state == 0) //检测到按下
{
key[i].down_time = 0; //清除按下时间
key[i].up_time = 0; //清除抬起时间
key[i].key_swt = 1; //进入确定个状态
}
}
break;
case 1: //确定状态
{
if(key[i].key_state == 0) //(10ms后)仍然为按下
key[i].key_swt = 2; //进入逻辑判断状态
else
key[i].key_swt = 0;
}
break;
case 2:
{
if(key[i].key_state == 0) //如果当前按下
{
key[i].down_time++; //累计按下时间
if(key[i].down_time > 100) //按下超过1s
key[i].key_long = 1; //触发长按
if(key[i].up_time < 20 && key[i].up_time > 0) //抬起时间在0-200ms按下
key[i].key_swt = 3; //进入双击确认状态
}
if(key[i].key_state == 1) //如果在状态2抬起
{
key[i].up_time++; //记录抬起时间
if(key[i].up_time > 20 && key[i].up_time < 100){
key[i].key_short = 1; //如果大于200且小于1000ms 则为短按
key[i].key_swt = 0; //回到初始状态
}
if(key[i].down_time > 100) //如果按下时间大于1s
key[i].key_swt = 0; //回到初始状态(重要,必须在抬起时回到初始状态,否则出错)
}
}
break;
case 3: // 双击确认状态
{
if(key[i].key_state == 0) //确定按下
key[i].key_swt = 4; //进入双击状态
else
key[i].key_swt = 2;
}
break;
case 4:
{
if(key[i].key_state == 1) //如果当前抬起(重要,必须 抬起 才算)
{
key[i].key_double = 1; //触发双击
key[i].key_swt = 0; //回到初始状态(重要,必须在抬起时回到初始状态,否则出错)
}
}
}
}
}
}
// 首先处理所有按键的独立短按事件,这些事件优先级最高
// MCP4017 ++
if(key[0].key_short && !key[1].key_short) {
//----执行操作-----
key[0].key_short = 0;
}
// MCP4017 --
if(key[1].key_short && !key[0].key_short) {
//----执行操作-----
key[1].key_short = 0;
}
// 处理组合按键事件
// 同时处于抬起状态
if(key[0].key_state && key[1].key_state) {
// 按键1和2同时双击触发
if(key[0].key_double && key[1].key_double) {
//----执行操作-----
// 清除状态
key[0].key_double = key[1].key_double = 0;
key[0].key_short = key[1].key_short = 0; // 确保清除短按状态
}
// 按键1和2同时短按触发
else if(key[0].key_short && key[1].key_short) {
//----执行操作-----
// 清除状态
key[0].key_short = key[1].key_short = 0;
}
}
// 两个按键长按 一直触发
if(key[0].key_long && key[1].key_long) {
//----执行操作-----
key[0].key_long = key[1].key_long = 0;
}
// 两个按键长按,只触发一次
static uint8_t key23_Flag = 1;
if(key[2].key_long && key[3].key_long) {
if(key23_Flag) {
key23_Flag = 0;
//----执行操作-----
key[2].key_long = key[3].key_long = 0;
}
} else if(key[2].key_state && key[3].key_state) {
// 当两个按键都释放时重置标志
key23_Flag = 1;
}
// EEPROM W
if(key[2].key_short)
{
//----执行操作-----
key[2].key_short = 0;
}
// EEPROM R
if(key[3].key_short)
{
//----执行操作-----
key[3].key_short = 0;
}
if(key[3].key_double)
{
//----执行操作-----
key[3].key_double = 0;
}
uint32_t u1_rx_tick = 0;
void U1_Rx_proc(void)
{
if(uwTick - u1_rx_tick < 50)
return;
u1_rx_tick = uwTick;
if(u1_rxbuff[0] == '#' && u1_rxcount == 1)
{
printf("成功反转屏幕rn");
static uint8_t lcd_mode = 0;
if(lcd_mode != 0){
lcd_mode = 0;
LCD_Clear(Black);
LCD_WriteReg(R1, 0x0000); // set SS and SM bit //0x0100
LCD_WriteReg(R96, 0x2700); // Gate Scan Line 0xA700
}else{
lcd_mode = 1;
LCD_Clear(Black);
LCD_WriteReg(R96, 0xA700); //将替换值代入后~
LCD_WriteReg(R1, 0x0100);
}
}
else if(u1_rxcount > 0)
{
printf("%srn", u1_rxbuff);
printf("输入#反转屏幕rn");
}
u1_rxcount = 0;
memset(u1_rxbuff, 0, sizeof(u1_rxbuff));
}
uint8_t Rx_Size = 0;
extern uint8_t rx_buff[100];
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
Rx_Size = Size;
uart_rx_flag = 1;
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, 100);
}
}
void rx_data_chuli(void)
{
if(uart_rx_flag)
{
if(rx_buff[0] == '#')
{
static uint8_t lcd_mode = 0;
if(lcd_mode != 0){
lcd_mode = 0;
LCD_Clear(Black);
LCD_WriteReg(R1, 0x0000); // set SS and SM bit //0x0100
LCD_WriteReg(R96, 0x2700); // Gate Scan Line 0xA700
}else{
lcd_mode = 1;
LCD_Clear(Black);
LCD_WriteReg(R96, 0xA700); //将替换值代入后~
LCD_WriteReg(R1, 0x0100);
}
}
else
{
HAL_UART_Transmit(&huart1, rx_buff, Rx_Size, 20);
}
Rx_Size = 0;
uart_rx_flag = 0;
memset(rx_buff, 0, 100);
}
}
#include "scheduler.h"
//任务数量
uint8_t task_num = 0;
//任务结构体
typedef struct{
void (*taskRun)(void); //函数指针
uint32_t tick; //任务运行间隔
uint32_t last; //上一次运行的时间(开机后初次运行的时间)
}task_t;
//任务数组
task_t Tasks[]={
{key_proc, 20, 0},
{lcd_proc, 50, 0},
{uart_proc, 10, 0},
{led_proc, 100, 0},
{rtc_prco, 50, 0}
};
//调度器初始化
void scheduler_Init(void)
{
task_num = sizeof(Tasks) / sizeof(task_t);
}
//调度器运行
void scheduler_Run(void)
{
uint8_t i = 0;
for(i = 0; i < task_num; i++)
{
if(uwTick >= Tasks[i].last + Tasks[i].tick)
{
Tasks[i].last = uwTick;
Tasks[i].taskRun();
}
}
}
- SUB外设:HSE时钟
- DMA可以和MCU并行工作、不需要i经过MCU访问内存
- 二极管温度升高时,反向饱和电流增大
- ADC主要技术指标包括:量化误差、转换速率、分辨率。(不包含频率)
- G431是3级流水线(取指解码和执行)
- 8421BCD计数器需要至少4个触发器
- 共集电极、发射机和基极的特点
参数 | 共集电极(CC) | 共发射极(CE) | 共基极(CB) |
电压增益 | ≈1(无放大) | 高(几十~几百) | 高 |
电流增益 | 高(β+1) | 高(β) | 低(≈1) |
输入阻抗 | 高 | 中 | 低 |
输出阻抗 | 低 | 高 | 高 |
相位关系 | 同相 | 反相 | 同相 |
带宽 | 窄 | 中 | 宽 |
典型应用 | 缓冲器、阻抗匹配 | 通用放大器 | 高频放大 |
- stm32G431芯片有111个中断,包括9个内核中断和102个可屏蔽中断,具有16级可编程的中断优先级(NVIC管理的优先级分五组,两个层次)
- ADC转换过程:采样量化编码
- G431->GPIO 速度配置对应关系
配置值(二进制) | 速度模式 | 典型输出速度 |
00 | 低速(Low) | ≤ 2 MHz |
01 | 中速(Medium) | ≤ 10 MHz |
10 | 高速(High) | ≤ 50 MHz |
11 | 超高速(Very High) | ≤ 100 MHz |
- G431 仅支持小端模式即, 数据的低字节存储在低地址,高字节存储在高地址。
- DMA每个通道有3个事件:传输完成、半完成、错误
- 三角波转换为矩形波:用施密特触发器
- PCM = ICM x UCEO
- 此运放电路直接带入公式算:Uo = -Rf * (U1/R1 + U2/R2)

- 晶体管制造工艺最简单
- G431RBT6不待LCD控制器与FSMC外设
- 总线是信号线的集合:数据总线、控制总线、地址总线
- 全双工、半双工、单工的区别
类型 | 数据传输方向 | 能否同时双向通信 | 典型应用场景 |
单工 | 仅能单向传输 | ❌ | 广播、电视信号、遥控器、摄像头输出视频 |
半双工 | 可双向传输,但某一时刻仅能单向 | ❌ | 对讲机、默认无流控的 UART 串口、早期以太网 |
全双工 | 可双向且能同时传输 | ✅ | 电话、网络通信(TCP/IP )、USB、SPI(多数实现 ) |
- 电容器的主要参数:标称容量、允许偏差、额定工作电压、绝缘电阻、温度系数、频率特性等
- EXTI
EXTI16 | PVD(可编程电压监测器)输出 | 用于电压异常等监测中断 |
EXTI17 | RTC(实时时钟)闹钟事件 | 响应 RTC 闹钟触发中断 |
EXTI18 | USB OTG FS 唤醒事件 | USB 高速设备唤醒系统 |
EXTI19 | 以太网唤醒事件 | 以太网相关唤醒场景用 |
EXTI20 | USB OTG HS(FS 配置下)唤醒事件 | USB 高速相关唤醒 |
EXTI21 | RTC 入侵和时间戳事件 | RTC 入侵检测、记录事件时间戳 |
EXTI22 | RTC 唤醒事件 | RTC 定时唤醒系统 |
- 积分电路:电阻平,电容竖;微分相反
- 积分电路可以用来将矩形波变成三角波输出
- 微分电路可以把矩形脉冲变换为尖脉冲输出
- 功能简单但频繁调用:内敛函数
- 内联函数(Inline Function)是一种编译器优化技术,通过在调用点直接展开函数体代码来减少函数调用开销。
inline关键字声明的函数,编译器会尝试将函数体直接嵌入到调用处,而非通过常规的函数调用机制(如栈帧创建、参数压栈、跳转等))
- 模数转换器的分辨率可以通过输出二进制数字信号的位数来判断。
- stm32g431xx.h定义了各类外设的寄存器结构体和相关位定义
- 时序 数字电路的输出与电路的原状态和当前输入有关
- 实现A/D的方法有:计数法、双积分法、逐次逼近法
- stm32的片内FLASH,一次可以写入16bit的数据
- USB和UART没有同步时钟(CLK)
- 二极管的伏安特性曲线(正向部分)在温度下降时右移
- 8个触发器能保存2^8次方状态
- TTL电路悬空则 = 高电平
- 贴片电阻,4位表示则精度1%,3位精度百分之5. 例如1002 = 10K 精度1%, 103表示,10K精度百分之5%
- 定时器的分类和编号
高级定时器 | 基本定时器 | 通用定时器 | 特殊定时器 |
TIM1、TIM8 | TIM6、TIM7 | 其他都是通用 | 低功耗定时器(LPTIM) |
- I2C起始条件: SCL 保持高电平期间,SDA 从高电平跳变到低电平
- I2C停止条件:SCL 保持高电平期间,SDA 从低电平跳变到高电平
- 555定时器频率和占空比计算公式


</column>
</columns>
- 可以作为注释中MCO输出的时钟源是:SYSCLK、HSI、HSE、PLL/2、
- USB:串行通信、 支持热插拔、 速度比RS232快、 级联星型拓扑结构(分为主机、集线器和设备)
- STM32内核级外设有
NVIC和SysTick
- 睡眠、停止和待机状态特点
模式 | 区别(功耗、状态、特性 ) | 唤醒方法 |
睡眠模式 | 内核停,
外设(时钟、定时器等)运行;寄存器、RAM 数据保留;功耗较低,唤醒最快 | 任意中断(如外部中断、定时器中断等 ) |
停止模式 | 内核、外设时钟关,
保留 1.8V 供电;寄存器、RAM 数据保留;功耗比睡眠低,唤醒稍慢 | 外部中断(如按键触发 EXTI )、RTC 闹钟中断等 |
待机模式 | 关闭所有时钟、内核 1.8V 供电;
仅备份寄存器数据保留,其他丢失;功耗最低,唤醒最慢 | 唤醒引脚(如 PA0 )上升沿、RTC 闹钟中断、复位(外部复位、独立看门狗复位等 ) |
- 一个DMA请求至少占用2个周期的CPU访问系统总线时间。
- STM32 APB2 IO的最大反转速度为18MHZ
- 两个相同的电压放大器,对同一个信号源进行放大,A电路输出电压小,则A电路输入电阻小
- DMA外设是逻辑或请求, 同一时刻只有一个有效。
- DMA的传输最大数目是65535
- 要提升电压比较器ide抗干扰能力:滞回比较器
- Cortex-M3系列处理器 支持Thumb-2 指令集 不支持Thumb指令集, Cortex-M4 在 Thumb-2 基础上增加了DSP 指令集、FPU 指令集。
- 启动模式说明
启动模式选择引脚 | 启动模式 | ||
BOOT1 | BOOT0 | ||
X | 0 | 主闪存存储器 | 主闪存存储器被选为启动区域 |
0 | 1 | 系统存储器 | 系统存储器被选为启动区域 |
1 | 1 | 内置 SRAM | 内置 SRAM 被选为启动区域 |
- RLC串联电路,谐振频率F0=1000HZ,当频率为800HZ时,正弦电压源激励时该电路呈容性
- 当时,电路容性;
- 当时,电路阻性;
- 当时,电路感性。
- 当时,电路感性;
- 当 时,电路阻性;
- 当时,电路容性。
RLC 并联电路
RLC 串联电路
- 5个D触发器构成的环形计数器,计数长度为5
- CPU寄存器
名称 | 寄存器 | 介绍 |
通用寄存器(R0-R12) | R0-R12(32 位) | 临时存储数据、参与运算及传递参数,R0-R7 适配 16 位指令,R8-R12 适配 32 位指令。 |
堆栈指针(SP) | R13 | 含主栈指针 MSP(内核 / 中断)和进程栈指针 PSP(用户任务),确保栈隔离与对齐。 |
链接寄存器(LR) | R14 | 存储函数 / 子程序返回地址,中断时赋值为 EXC_RETURN,嵌套调用需手动压栈。 |
程序计数器(PC) | R15 | 指向当前 / 下一条指令地址,读操作返回 “当前地址 + 4”,写操作实现跳转。 |
应用程序状态寄存器 | APSR | 记录 ALU 运算标志(Z/C/N/V),用于条件判断指令。 |
执行程序状态寄存器 | EPSR | 记录指令执行状态(如 Thumb 状态位 T),确保指令按模式执行。 |
中断程序状态寄存器 | IPSR | 只读,记录当前服务的中断号,用于识别中断源。 |
PRIMASK | PRIMASK | 置 1 时屏蔽除 NMI 和 HardFault 外的所有中断,适用于高实时性任务。 |
FAULTMASK | FAULTMASK | 置 1 时屏蔽所有异常 / 中断(含 HardFault),中断返回后自动清零。 |
BASEPRI | BASEPRI | 按优先级屏蔽等于 / 低于设定值的中断,灵活控制中断响应范围。 |
控制寄存器(CONTROL) | CONTROL | 模式控制(SPSEL 选 MSP/PSP、权限位区分特权级 / 用户级);M4 中关联 FPU 功能。 |
- 两个逻辑函数恒等,他们必然有唯一的真值表
- 差分信号:要求等间距等长、差分信号。(RS232不是差分信号,差分信号不用共地)
- NAND Flash 和 NOR Flash的区别(总的来说是Nor更贵更好,但更小)
对比维度 |
存储结构 |
读写特性 |
容量与成本 |
<td>容量大(1Gb+),单位成本低</td>
<td>容量小(2Gb 内),单位成本高</td>
</tr>
<tr>
</tr>
</tr>
</tr>
<td>应用场景</td>
<td>数据存储(SSD/U 盘 / SD 卡)、大容量存储</td>
<td>程序存储(BIOS / 固件)、XIP 直接执行</td>
</tr>
<tr>
</tr>
</tr>
</tr>
<td>管理复杂度</td>
<td>需要垃圾回收、磨损均衡等复杂机制</td>
<td>接口简单,无需额外管理机制</td>
</tr>
<tr>
</tr>
</tr>
</tr>
<td>执行方式</td>
<td>需加载到 RAM 运行</td>
<td>支持片上执行(XIP)</td>
</tr>
<tr>
</tr>
</tr>
</tr>
<td>典型功耗</td>
<td>擦写功耗低,待机功耗高</td>
<td>随机读功耗低,待机功耗低</td>
</tr>
</table>
- 具有压电效应的滤波器有:石英晶体滤波器, 声表面波滤波器
- Cortex-M3、M4处理器均支持2个堆栈,别为主堆栈(Main Stack, MSP) 和进程堆栈(Process Stack, PSP)。
堆栈类型 | 主堆栈(MSP) | 进程堆栈(PSP) |
默认使用场景 | • 系统启动时的默认堆栈。- 处理异常(中断、Fault 等)时自动切换使用。- 操作系统内核、关键任务或特权模式下的代码使用。 | • 应用程序(用户任务)在非特权模式下的默认堆栈。- 由操作系统调度任务时切换使用,实现任务隔离。 |
寄存器关联 | 由 MSP寄存器直接控制,异常处理时硬件自动加载 / 保存上下文到 MSP。 | 由 PSP寄存器控制,需软件(如 OS 调度器)显式切换,用于任务级上下文切换。 |
访问权限 | 特权模式下可访问,用户模式下不可直接访问(需通过异常处理切换)。 | 特权模式和用户模式均可访问,但用户模式下仅能操作自己的 PSP 区域。 |
典型应用 | 系统初始化、中断处理、内核服务例程等。 | 应用任务的局部变量、函数调用栈、中断嵌套时的临时存储(若 OS 支持)。 |
- RTC时钟源:HSE/128、LSE、LSI
- I2C协议中设备地址有7和10位模式。
- 电压增益为-20dB, 因为:, 所以A = 0.1。
- 多级放大电路的通频带会变窄
- 直流电源滤波用低通滤波, 有用信号为固定频率用带通滤波
- 运放+若干电阻,无法构成乘法器和振荡器(但可以构成跟随器和比较器)
- 超声波传感器基于压电效应
- 二段网络的等效电动势,等于端口开路时测的电压。
- 知道就好

DMA专项
DMA简介
- DMA 全称DirectMemoryAccess,即直接存储器访问。
- DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。
- DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
- DMA作用:为CPU减负。
- STM32 最多有 2 个 DMA 控制器(DMA2 仅在大容量、互联型产品中存在 )。DMA1 有 7 个通道,DMA2 有 5 个通道 。每个通道专门管理一个或多个外设对存储器访问的请求,搭配仲裁器协调请求优先级。
STM32 的DMA有以下一些特性:
- 每个通道都直接连接专用的硬件DMA请求,都支持软件触发,这些通过软件来配置。
- 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在 相等优先权时由硬件决定(请求0优先于请求1,依此类推) 。
- 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理
- 每个通道都有3个事件标志(DMA 半传输,DMA传输完成和DMA传输出错),这3个 事件标志逻辑 或 成为一个单独的中断请求。
- 外设和存储器,存储器和外设的传输,存储器和存储器间的传输。
- 闪存、SRAM、外设的SRAM、APB1APB2和AHB外设均可作为访问的源和目标。
- 可编程的数据传输数目:最大为65536
- 从外设(TIMx、ADC、SPIx、I2Cx 和 USARTx)产生的 DMA 请求,通过逻辑或输入到 DMA 控制器,这就意味着同时只能有一个请求有效。外设的 DMA 请求,可以通过设置 相应的外设寄存器中的控制位,被独立地开启或关闭。
总线分类专项
- 总线按功能和规范可分为五大类型:数据总线、地址总线、控制总线、扩展总线及局部 总线。
- 数据总线、地址总线和控制总线也统称为系统总线,即通常意义上所说的总线。 常见的数据总线为ISA、EISA、VESA、PCI等。
- 地址总线:是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所 以地址总线总是单向三态的,这与数据总线不同,地址总线的位数决定了CPU可直接寻址 的内存空间大小。
- 控制总线:用来传送控制信号和时序信号。控制信号中,有的是微处理器送往存储器和I/O 接口电路的;也有是其它部件反馈给CPU的,比如:中断申请信号、复位信号、总线请求 信号、设备就绪信号等。
- 按照传输数据的方式划分,可以分为串行总线和并行总线。
- 串行总线中,二进制数据逐位通过一根数据线发送到目的器件;
- ++并行总线的数据线通常超过2根。常见的串行总线有SPI、I2C、USB及RS232等。
- 按照时钟信号是否独立,可以分为同步总线和异步总线。
- 同步总线的时钟信号独立于数据,而异步总线的时钟信号是从数据中提取出来的。SPI、I2C 是同步串行总线,
- RS232采用异步串行总线。
- 微机中总线一般有内部总线、系统总线和外部总线。
- 内部总线是微机内部各外围芯片与处理器之间的总线,用于芯片一级的互连,有I2C,SCI, IIS,SPI,UART,JTAG,CAN,SDIO,GPIO;
- 系统总线是微机中各插件板与系统板之间的总线,用于插件板一级的互连,有ISA,EISA, VESA,PCI;
- 外部总线则是微机和外部设备之间的总线,微机作为一种设备,通过该总线和其他设备进行 信息与数据交换,它用于设备一级的互连,有RS-232-C,RS-485,IEEE-488,USB。
信号转换专项
- RC桥式正弦波振荡电路,产生正弦波,利用自激震荡

- 单限比较器可产生矩形波(大于Ut为负,小于Ut为正)


</column>
</columns>
- 滞回比较器

- 积分电路

- 加减运算电路


一些函数专项
- 重载函数
同一个函数完成不同的功能,常用来实现功能类似而所处理的数据类型
不同的问题。允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参
数(指参数的个数、类型或者顺序)必须不同。
- 内联函数
其目的是为了提高函数的执行效率,用关键字
inline 放在函数定义(注意是定义而非声明)的前面即可将函数指定为内联函数,内联函数一般都是1-5行的小函数。调用:
cout<<max(a, b)<<endl;
则在编译时展开为:cout<<(a>b?a:b)<<endl; 因为调用函数比求解等价表达式要慢得多,所以用内联函数,从而消除了把 max写成函数的额外执行开销
- 递归函数
一种计算过程,其中每一步都要用到前一步或前几步的结果,例如连加、连乘及阶乘等。
递归函数必须要有明确的功能和边界条件。
通信外设专项
一、串行通信接口与总线协议分类
1. 异步串行通信接口(UART 及其衍生标准)
- UART(通用异步收发器)
- 性质:异步串行通信的总称,属于物理层接口。
- 包含标准:RS232、RS499、RS423、RS422、RS485 等。
- RS 系列接口标准(物理层规范)
标准 | 传输方式 | 电平特性 | 传输线数 | 应用场景 |
RS-232 | 全双工 | 逻辑 1:-15V~-5V,逻辑 0:+3V~+15V | 最少 3 根 | MCU 与 PC 机通信(短距离) |
RS-485 | 半双工 | 逻辑 1:+2V~+6V,逻辑 0:-6V~-2V | 2 根 | 多设备总线(长距离、抗干扰) |
RS-422 | 全双工 | 差分信号(类似 RS-485) | 4 根 | 工业控制(较少见) |
RS-423 | 单端传输 | 类似 RS-232 但电平范围更宽 | - | 过渡标准,已较少使用 |
RS-499 | 高速版 RS-422 | 更高传输速率 | - | 高速串行通信 |
- TTL 电平(逻辑电平信号)
- 传输方式:全双工
- 电平特性:逻辑 1:2.4V~5V,逻辑 0:0V~0.5V
- 应用:MCU 间直接通信(短距离,需电平转换芯片与 RS-232 互联)。
2. 同步串行总线协议
- I2C(Inter-Integrated Circuit)
- 性质:同步、半双工、二线制总线协议(数据链路层)。
- 特点:
- 仅需 SDA(数据线)和 SCL(时钟线),支持多主机仲裁。
- 传输速率较低(标准模式 100kbps,高速模式 3.4Mbps)。
- 应用:MCU 与传感器、存储器等低速外设通信。
- SPI(Serial Peripheral Interface)
- 性质:同步、全双工、高速总线协议。
- 特点:
- 4 根线:SCK(时钟)、MOSI(主发从收)、MISO(主收从发)、CS(片选)。
- 传输速率可达几十 Mbps,支持单主机多从机。
- 应用:高速外设通信(如 Flash、ADC、显示屏驱动)。
3. 差分信号总线
- CAN(Controller Area Network)
- 性质:独立差分总线,物理层与数据链路层集成。
- 特点:
- 电平定义:CAN_High 与 CAN_Low 的电压差(逻辑 1:-1.5V~0V,逻辑 0:+1.5V~+3V)。
- 抗干扰能力强,支持多节点实时通信(汽车电子、工业控制)。
- 硬件:独立引脚,可与 USB 复用。
- RS-485(属于差分信号接口)
- 见 “RS 系列接口标准” 部分。
4. 通用外部总线(USB)
- USB(Universal Serial Bus)
- 性质:高速串行总线标准,独立于串口体系。
- 特点:
- 4 根线:GND、5V 电源线、D + 和 D-(差分信号线,3.3V 电平)。
- 支持热插拔、高速传输(USB 2.0 最高 480Mbps,USB 3.0 达 5Gbps)。
- 单主机架构,最多连接 127 个设备(通过 Hub 扩展)。
- 应用:电脑与外设(键盘、鼠标、存储设备等)。
二、核心对比维度总结
分类维度 | 异步串行(UART/RS) | 同步总线(I2C/SPI) | 差分总线(CAN/RS-485) | USB |
时钟同步 | 无需(异步) | 需要(同步) | 部分需要(如 CAN) | 内置时钟同步机制 |
传输方式 | 全双工 / 半双工 | 全双工(SPI)/ 半双工(I2C) | 半双工(RS-485)/ 全双工(CAN) | 全双工 |
抗干扰性 | 单端信号(RS-232 差) | 单端信号(抗干扰性一般) | 差分信号(抗干扰性强) | 差分信号 + 屏蔽线(抗干扰性强) |
典型应用 | 低速串口通信 | 芯片间低速 / 高速通信 | 工业控制、汽车电子 | 外设通用接口 |
电平标准 | TTL/RS-232 等 | TTL 兼容 | 差分电平 | 专用 3.3V 差分电平 |
三、关键术语补充
- 物理层与数据链路层:RS 系列、TTL、CAN 等属于物理层规范(定义电气特性);I2C、SPI 等属于数据链路层协议(定义通信时序与逻辑)。
- 全双工与半双工:
- 全双工:可同时收发数据(如 RS-232、SPI)。
- 半双工:同一时刻只能收或发(如 RS-485、I2C)。
- 差分信号:通过两根线的电压差传输数据,抗共模干扰能力强(如 CAN、RS-485),适合长距离通信。
C 语言字符串函数详解
在 C 语言中,字符串处理是非常基础且重要的操作,下面对代码中演示的字符串函数进行系统总结:
1. 字符串查找函数 - strstr
- 功能:在主字符串中查找子字符串首次出现的位置
- 原型:
char *strstr(const char *haystack, const char *needle);
- 参数:
haystack:主字符串(被查找的字符串)needle:子字符串(要查找的字符串)
- 返回值:子字符串首次出现的地址,若未找到返回 NULL
- 示例:
strstr("nihao beijing", "jing")会返回 "jing" 的起始地址
- 注意:查找时区分大小写,且子字符串不能是空字符串
2. 字符串转数值函数 - atoi
- 功能:将字符串转换为整数
- 原型:
int atoi(const char *nptr);
- 参数:
nptr为要转换的字符串
- 返回值:转换后的整数值
- 示例:
atoi("100 100 100")会返回 100(遇到非数字字符停止转换)
- 注意:
- 跳过字符串前的空白字符
- 遇到第一个非数字字符时停止转换
- 结果范围受 int 类型限制
3. 字符串分割函数 - strtok
- 功能:按指定分隔符分割字符串
- 原型:
char *strtok(char *str, const char *delim);
- 参数:
str:要分割的字符串(首次调用时传入,后续调用传 NULL)delim:分隔符字符串
- 返回值:分割后的子字符串,若无更多子串则返回 NULL
- 示例:
strtok("(101,102,103,104)", "(),")可解析出整数列表
- 注意:
- 函数会修改原字符串(在分隔符处替换为 '\0')
- 首次调用后,后续调用需传入 NULL 以继续分割
4. 格式化字符串读取 - sscanf
- 功能:从字符串中按指定格式读取数据
- 原型:
int sscanf(const char *str, const char *format, ...);
- 参数:
str:要读取的字符串format:格式控制字符串...:接收数据的变量指针
- 返回值:成功匹配的参数个数
- 示例:
sscanf("(1,2,3,4)", "(%d,%d,%d,%d)", &a,&b,&c,&d)
- 注意:与 scanf 功能类似,但数据源是字符串而非标准输入
5. 字符串比较函数 - strcmp 与 strncmp
函数 | 功能 | 原型 | 返回值解释 |
strcmp | 比较两个字符串 | int strcmp(const char *s1, const char *s2); | 若 s1<s2 返回负数,若 s1==s2 返回 0,若 s1>s2 返回正数 |
strncmp | 比较两个字符串的前 n 个字符 | int strncmp(const char *s1, const char *s2, size_t n); | 前 n 个字符的比较结果 |
- 示例:
strcmp("abc", "abd")返回负数,因为 'c'<'d'
- 注意:
- 按字符 ASCII 值逐个比较
- 遇到 '\0' 或比较完 n 个字符时停止(strncmp)
6. 字符串连接函数 - strcat 与 strncat
函数 | 功能 | 原型 | 注意事项 |
strcat | 连接两个字符串 | char *strcat(char *dest, const char *src); | dest 必须有足够空间容纳连接后的字符串 |
strncat | 连接两个字符串的前 n 个字符 | char *strncat(char *dest, const char *src, size_t n); | 最多添加 n 个字符,自动添加 '\0' |
- 示例:
strcat("nihao", " beijing")结果为 "nihao beijing"
- 注意:
- 源字符串 (src) 的 '\0' 会被复制到目标字符串 (dest)
- 目标字符串必须有足够的空间(至少为 dest 长度 + src 长度 + 1)
7. 字符串复制函数 - strcpy 与 strncpy
函数 | 功能 | 原型 | 注意事项 |
strcpy | 复制字符串 | char *strcpy(char *dest, const char *src); | dest 必须有足够空间 |
strncpy | 复制字符串的前 n 个字符 | char *strncpy(char *dest, const char *src, size_t n); | 若 src 长度小于 n,dest 不会自动补 '\0' |
- 示例:
strcpy(dest, "Hello")将 "Hello" 复制到 dest
- 注意:
- strcpy 会复制 src 的 '\0' 到 dest
- strncpy 若 src 长度 >=n,dest 不会自动添加 '\0',需手动处理
math 库常用函数
在蓝桥杯嵌入式比赛中,math.h 库的函数也是重点考察内容,以下是需要熟练掌握的常用函数:
1. 数值计算函数
函数 | 功能 | 原型 | 示例 |
fabs | 计算浮点数的绝对值 | double fabs(double x); | fabs(-3.14) 返回 3.14 |
floor | 向下取整 | double floor(double x); | floor(3.8) 返回 3.0 |
ceil | 向上取整 | double ceil(double x); | ceil(3.2) 返回 4.0 |
round | 四舍五入取整 | double round(double x); | round(3.6) 返回 4.0 |
fmod | 计算浮点数取模 | double fmod(double x, double y); | fmod(7.0, 3.0) 返回 1.0 |
2. 幂指对数函数
函数 | 功能 | 原型 |
pow | 计算 x 的 y 次方 | double pow(double x, double y); |
sqrt | 计算平方根 | double sqrt(double x); |
<td>
sqrt(25.0) 返回 5.0</td></tr>
<tr>
<td>
exp</td><td>计算自然指数 (e^x)</td>
<td>
double exp(double x);</td><td>
exp(1) 返回 e≈2.71828</td></tr>
<tr>
<td>
log</td><td>计算自然对数 (lnx)</td>
<td>
double log(double x);</td><td>
log(e) 返回 1.0</td></tr>
<tr>
<td>
log10</td><td>计算常用对数 (log₁₀x)</td>
<td>
double log10(double x);</td><td>
log10(100) 返回 2.0</td></tr>
</table>
3. 三角函数
函数 | 功能 | 原型 | 注意事项 |
sin | 计算正弦值 | double sin(double x); | x 为弧度值 |
cos | 计算余弦值 | double cos(double x); | x 为弧度值 |
tan | 计算正切值 | double tan(double x); | x 为弧度值 |
asin | 计算反正弦值 | double asin(double x); | 返回值为弧度,范围 [-π/2,π/2] |
acos | 计算反余弦值 | double acos(double x); | 返回值为弧度,范围 [0,π] |
atan | 计算反正切值 | double atan(double x); | 返回值为弧度,范围 [-π,π] |
atan2 | 计算二维反正切 | double atan2(double y, double x); | 返回值为弧度,范围 [-π,π] |
4. 类型转换函数
函数 | 功能 | 原型 | 示例 |
fabsf | 单精度浮点数绝对值 | float fabsf(float x); | 与 fabs 类似,但处理 float 类型 |
floorf | 单精度浮点数向下取整 | float floorf(float x); | 与 floor 类似,但处理 float 类型 |
roundf | 单精度浮点数四舍五入 | float roundf(float x); | 与 round 类似,但处理 float 类型 |
trunc | 截断取整(直接舍去小数部分) | double trunc(double x); | trunc(3.8) 返回 3.0 |
冒泡排序
- 作者:L_Z_J
- 链接:https://www.mcoi.top/article/Post-lanqiaobei
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。










