LED

开发板上的电路图
1 点亮一个LED

芯片引脚配置

RCC和SYS的作用

RCC:模块负责管理单片机的时钟系统,控制外部和内部时钟源的选择,频率调节,时钟使能
SYS:SYS模块主要负责管理单片机的系统时钟,包括电源管理和复位控制等功能。

用到的HAL库函数

我在点亮一个流水灯的时候出现了一个问题,我给GPIOC8写低电平的时候,LED亮了四个灯

1
2
3
4
5
6
7
8
9
10
11

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
//功能:配置引脚电平状态
//参数说明:
//GPIOx:端口号 GPIOA,GPIOB,GPIOC
//GPIO_PinStats PinState : GPIO_PIN_SET 设置引脚为高电平

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//功能:反转引脚电平状态
void HAL_Delay(uint32_t Delay);
//功能:延迟 Delay 毫秒

位运算

&:两个都为1的时候,结果为1
|:有一个为1,结果为1
^:两个二进制位不同时,结果位为 1,否则为 0。
~:所有二进制位取反
<<:所有二进制位左移 n 位,低位补 0。
1>>:所有二进制位右移 n 位,高位补符号位(算术右移)或 0(逻辑右移)。

1
2
3
4

uint_32t ucled=0x02;//00000010
ucled<<8;//表示将ucled左移8位,变成00000010 00000000,对应位GPIOP9

关于为什么进行位运算向左移动八位就能实现点亮对应LED如图

GPIOC

位运算向左实现流水灯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

void Led_Disp(uint8_t ucLed)
{
HAL_GPIO_WritePin(GPIOC,0XFF<<8,GPIO_PIN_SET)//熄灭所有灯
HAL_GPIO_WritePin(GPIOC,ucLed<<8,GPIO_PIN_RESET)//点亮指定的灯

}



//解释:0xFF:1111 1111 ,ucled:写低对应的GPIO
//关于为什么他是GPIO点亮,因为是共阳极LED电路

//关于实现流水灯

uint8_t ucLed=0x01;

LED_Disp(ucLed);
HAL_Delay(100);
ucLed<<=1;//
IF(ucLed==0)//判断是否溢出
{ucLed=0x01}//重置

//每次点亮下一个LED,延迟100ms,然后左移一位

关于这个代码中if判断为什么当ucled=0x00重置我出现了一个问题
我在定义ucled的类型的时候,定义- uint32所以就会导致0x80向右移过程不会变成0x00

常用函数解释

led_flag ^= 1;实现反转

1
2
3
4
5
6
7
8
9
10
11
12
13

uint8_t led_flag;
uint8_t B1_data,B2_data,B3_data,B4_data,B1_last,B2_last,B3_last,B4_last;
void key_Proc(void)
{
B1_data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);B2_data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
B3_data=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);B4_data=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
if(!B1_data&B1_last)
{
LED_show(1,1);
}
B1_last=B1_data;B2_last=B2_data;B3_last=B3_data;B4_last=B4_data;
}

当按键一直处于高电平的时候,B1_data和B1_last就一直是1,去反后出现一个0所以在进行&运算后,仍然是0,所以就不会进入if判断
而当高电平转换成低电平的时候,B1_data变成了0,就会出现&运算后总体为1,然后进入if判断

系统滴答定时器(SysTick)

1
2
3
4
5
6
7
8
9
10
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
usled++;//实现usled每隔1ms自增1
/* USER CODE END SysTick_IRQn 1 */
}

这是一个嵌入式中断服务函数,在主函数调用HAl_Init()时,会自动配置SysTick定时器

关于如何解决LED引脚占用问题

观察LCD引脚图可以发现,LED和LCD共用一组引脚,所以我们需要将LED的引脚配置为输出模式,而LCD的引脚配置为输入模式。

所以就有了用到LCD时,我们需要把PD2初始状态修改为低电平,使锁存器两端不导通。需要配置LED时,将锁存器
设置为高电平,将PC8PC15端口状态传送到左侧1Q8Q,再配置为低电平

模块代码:

详细见真题总结

1
2
3
4
5
6
7
8
9
10
11
void display(uint8_t ucLed)
{
HAL_GPIO_WritePin(GPIOC,0xFF<<8,GPIO_PIN_SET); //熄灭所有LED
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET); //打开锁存器
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); //关闭锁存器

HAL_GPIO_WritePin(GPIOC,ucled<<8,GPIO_PIN_SET); //熄灭所有LED
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); //打开锁存器
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET); //关闭锁存器
}

这代码不能解决LED,LCD引脚占用的问题

按键

KEY按键电路图

按键电路图
按键原理图

1
2
3
4
5
6
7
8

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//功能:读取引脚电平状态
//参数说明:
//端口号:GPIOX,端口号 GPIOA,GPIOB,GPIOC
//GPIO_Pin:引脚号
//返回为引脚的高低电平

当按键按下的时候返回的是低电平,松开的时候返回的是高电平

按键模块代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

uint8_t Key_Scan(void)
{
uint8_t key_val=0;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET)
{
key_val=1;
}
f(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET)
{
key_val=2;
}
f(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET)
{
key_val=3;
}
f(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
key_val=4;
}
eturn key_val;
}

按键状态判断:

1
2
3
4
5

!B4_data & B4_last//表示下降沿
!B4_data & !B4_last//保持按住状态
B4_data & !B4_last//表示上升沿

关于输入输出上拉和下拉

上拉:默认电平为高电平
下拉:默认电平低电平
浮空:不连接,悬空状态

按键三行代码

按键的三种状态

1
2
3
4
5
6
7
8
9
10
11
12
13

void Key_Proc(void)
{
key_val = Key_Scan();
key_down = key_val & (key_val ^ key_old);//表示按键按下瞬间
key_up = ~key_val & (key_val ^ key_old);//表示按键松开瞬间
key_old = key_val;
if(key_down==1)
{
//按键1按下,执行相应操作
}
}

key_val ^ key_old 会计算当前按键状态与上一状态的异或结果

按键的长按和短按

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
void key_longandshort(void)
{
uint8_t key_val=key_ReadPin();
uint8_t key_down=key_val&(key_val^ key_old);
uint8_t key_up=~key_val&(key_val^key_old);
key_old=key_val;
if(key_down)
{
usled=0;
}
if(key_up)
{
if(usled<1000)
{
LEDdisplay(0x01);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOC,0XFF<<8,GPIO_PIN_SET);
}
else
{
LEDdisplay(0x02);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOC,0XFF<<8,GPIO_PIN_SET);
}
}

}

关于位运算^: 如果两个对应的位相同,则该位的结果为0;如果不同,结果为1。

LCD屏幕

sprintf函数可以将其他字符传化为字符串

LCD函数大全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

void LCD_Init(void);
//功能:清屏,设置整个屏幕颜色

void LCD_SetTextColor(vu16 Color);
//功能:设置字体颜色

void LCD_SetBackColor(vu16 Color);
//功能:设置背景颜色
void LCD_DisplayStringLine(uint16_t Line, uint8_t *ptr);
//功能:在LCD屏幕上显示字符串
//参数line 行号,共十行。0~9
//ptr要显示的字符串,长度为20

//sprintf函数的应用

//(用于显示变量)

关于按键的双击

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
void key_doubleandonce(void)
{
uint8_t key_val=key_ReadPin();
uint8_t key_down=key_val&(key_val^key_old);
uint8_t key_up=~key_val&(key_val^key_old);
uint8_t key_temp;
uint8_t key_flag=0;
if(key_up)
{
key_temp=key_up;
if(key_flag==0)
{
usled2=0;
key_flag=1;//表示已经松开按下
}
else
{
key_flag=0;
}
}
if(key_flag==1)
{
if(usled2<300)
{
if(key_down==1&&key_flag==1)
{
//进入这个if判断表示双击,亮起第二个灯
LEDdisplay(0x02);
}
if(key_down==2&&key_flag==2)
{

LEDdisplay(0x02);
}
if(key_down==3&&key_flag==3)
{

LEDdisplay(0x02);
}
if(key_down==4&&key_flag==4)
{
LEDdisplay(0x02);
}
}
else{
if(key_temp==1)
{
usled2=0x01;
}
if(key_temp==2)
{
}
if(key_temp==2)
{
}
if(key_temp==2)
{
}
}
}
}

LCD

LCD原理图

1
2
3
4
5
6
7
8
9

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t
Timeout)
功能:串口发送数据
参数:
UART_HandleTypeDef *huart UART句柄 huart1
uint8_t*pData 需要发送的数据
uint16_t Size 发送的字节数
uint32_t Timeout 最大发送时间,发送数据超时退出发送

串口接受函数:

1
2
3
4
5
6
7
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t*pData, uint16_t Size)
//功能:串口中断接收函数
//参数:
//UART_HandleTypeDef *huart UART句柄 huart1
//uint8_t *pData 接收到的数据存放地址
//uint16_t Size 发送的字节数 为1,每次中断只能接收一个字符

meset函数:
用于初始化一块内存,将数组全部清零,或者设置为某个特定值
例如:

1
2
3

memset(arr, 'A', sizeof(arr));//将arr数组的每个字节设置为'A'

sscanf函数:

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include <stdio.h>

int main() {
char str[] = "123 456.78 hello";
int i;
float f;
char s[20];

// 从字符串中解析数据
int result = sscanf(str, "%d %f %s", &i, &f, s);

// 输出解析的结果
if (result == 3) {
printf("整数: %d\n", i);
printf("浮点数: %.2f\n", f);
printf("字符串: %s\n", s);
} else {
printf("解析失败\n");
}

return 0;
}

LCD2

LCD函数基本功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

void LCD_Clear(u16 Color)
//功能:清屏

void LCD_SetTextColor(vu16 Color)
//功能:设置文本颜色

void LCD_SetBackColor(vu16 Color)

//功能:设置文本背景色

void LCD_DisplayStringLine(u8 Line,u8 *ptr)

//功能:在指定行显示字符串

//参数:line 行号,共十行,Line0-Line9,ptr要显示的字符串,长度为20

关于sprintf函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"
//定义变量
uint8_t B1_count,B2_count,B3_count,B4_count;
char buf[21];
void Lcd_Proc(void)
{
sprintf(buf,"B1_Count:%d",B1_count);
LCD_DisplayStringLine(Line1,(uint8_t*)buf);
sprintf(buf,"B2_Count:%d",B2_count);
LCD_DisplayStringLine(Line2,(uint8_t*)buf);
sprintf(buf,"B3_Count:%d",B3_count);
LCD_DisplayStringLine(Line3,(uint8_t*)buf);
sprintf(buf,"B4_Count:%d",B4_count);
LCD_DisplayStringLine(Line4,(uint8_t*)buf);
}

//解析:sprintf的三个参数:1.char类型的缓存空间,2.输出值,类似printf,3.输出变量类型
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

//在界面切换前面的写法

void LCD_Proc()
{
key_val=key_ReadPin()
key_down=key_val(key_val^key_old);
key_up=~key_val(key_val^key_old);
key_val=key_old;

if(key_down=1)
{
LCD_Clear(Black);
lcd_pag++;
if(lcd_pag==3)lcd_pag==0;
}
}



uint8_t buf[21];
uint8_t lcd_pag;
uint8_t count;
if(lcd_pag==1)
{
sprintf(buf,"count:%d",count);
LCD_DisplayStringLine(Line1,(uint8_t*)buf);

}

串口通信

USART1串口接受函数

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

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t
Timeout)
//功能:串口发送数据

HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);
while (1)
{
if(rate=='1')
{
LEDdispla(0x01);
sprintf(tx_buf,"LED1,OPEN");
HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);
}
else if(rate=='2')
{
LEDdispla(0x02);
sprintf(tx_buf,"LED2 OPEN");
HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);
}
else{
sprintf(tx_buf,"Erron");
HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);

}
HAL_UART_Receive_IT(&huart1,&rate,1);
}

3.5 关于锁存器占用问题:GPIOD2初始电平要为低电平才不会发生引脚占用

字符串函数strcmp

比较字符串:如果字符串1大于字符串2则返回复数
反之返回正数,否则则返回0

串口解释字符串

定时器

关于定时器ADCARR,CRRX,CNT的解释:
定时器

关于ADC

ADC测电压函数

1
2
3
4
5
6
7
8

uint16_t git_ADC2(void)
{
uint8_t ADC2=0;
HAL_ADC_Start(&hadc2);
ADC2=HAL_ADC_GetValue(&hadc2);
return ADC2;
}

关于PWM如何捕获占空比

PWM占空比计算公式

**关于CNT,CCR,ARR三个值的解释
CNT类似秒表,CCR某个阈值,低于这个阈值输出高电平,高于这个阈值输出低电平,ARR表示整个路程,即周期

通过寄存器去输入捕获测量频率

利用中断回调函数测量中断频率

1
2
3
4
5
6
7
8
9
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance=TIM17)
{
capture_vaL=HAL_TIM_ReadCapturedValue(&htim17,TIM_CHANNEL_1);
TIM17->CNT=0;//在第二次遇到上升沿的时候置零
PA17_frg=80000000/(800*capture_vaL);//计算公式
}
}

测量占空比函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)//中断消息来源,选择直接输入通道,如果产生上升沿则进入if判断

{
capture_val1=HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//捕获上升沿,表示两个上升沿之间的时间,即整个周期
capture_val2=HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_2);//捕获下降沿,表示两个下降沿之间的时间,即高电平时间
TIM2->CNT=0;
40_frq=80000000/(80*capture_val1);//计算频率
duty1 = ((1.0*capture_val1_2)/capture_val1)*100;//计算占空比
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim2,TIM_CHANNEL_2);
//这两个开启为了确保输入捕获后能重新开启
}
}
}

EEPROM模块

EEPROM模块可以用于长期保存,但偶尔会改动的数据

代码:

1
2
3
4
5
6
7
8
9
10
11
12

I2CStart(); // 发送 I2C 起始信号(START)
I2CSendByte(0xA0); // 发送 EEPROM 设备地址 + 写指令(假设 0xA0 是 EEPROM 器件地址)
I2CWaitAck(); // 等待 EEPROM 响应(ACK)

I2CSendByte(ucAddr); // 发送 EEPROM 内部地址(要读取的数据存储地址)
I2CWaitAck(); // 等待 EEPROM 响应(ACK)

I2CStart(); // **发送重复起始信号**(Restart,准备切换到读取模式)
I2CSendByte(0xA1); // 发送 EEPROM 设备地址 + 读指令(0xA1 = 0xA0 + 1)
I2CWaitAck(); // 等待 EEPROM 响应(ACK)

中断时钟回调函数

1
2
3
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
//定时器上的中断回调函数,触发条件为ARR溢出这个函数,比如我设置ARR=100,那就是每100个时间单位进入这个中断系统

时间中断回调函数在tim库5933行

真题总结

十四届真题总结

考点:LED,LCD,KEY(长按短按),PWM输出,测试频率,ADC

新学到了:可以两个LED模块换着用,然后呢按键的实现

1