0%

在STM32中,集成了12位逐次逼近型ADC。
12位:指ADC的采样深度,使用12位2进制数来表示一个采样点。例如4位ADC,就是把0-3.3V分成了2^4 -1份,每份为0.22V。参考下表

二进制数对应电压
00000V
00010.22V
00110.44V
11113.30V

逐次逼近型(SAR):每次比较输入电压与内部基准电压,逐步逼近输入电压。
默认的ADC时钟为4Mhz(最大为14Mhz)。
假设输入到ADC的时钟周期为14Mhz,时钟周期为$\frac{1}{14}MHz$ us
采样时间$0.1 us = 1.5 \times \frac{1}{14} us = 1.5 cycle$
转换时间$0.9 us = 12.5 \times \frac{1}{14} us = 12.5 cycle$

最优采样时间计算:

$$
T_S = (R_{AIN} + R_{ADC}) \times C_{ADC} * ln(2^{N+2})
$$

$R_{AIN}$ 表示模拟输入的内阻
$R_{ADC}$表示采样保持电路的电阻,为1k欧姆
$C_{ADC}$表示采样保持电路的电容,为8pF
$ln(2^{N+2})$里面的N表示采样深度,为12

带入计算,得到:
$$
T_S = (R_{AIN} + 1000) \times 77.6 * 10^{-12}
$$
只和模拟输入的内阻有关。

例如,当$R_{AIN}$ = 400欧姆时
$$
T_S = (400 + 1000) \times 77.6 * 10^{-12} = 0.11us
$$
当$R_{AIN}$ = 10k欧姆时
$$
T_S = (10e3 + 1000) \times 77.6 * 10^{-12} = 0.85us
$$
当$R_{AIN}$ = 50k欧姆时
$$
T_S = (50e3 + 1000) \times 77.6 * 10^{-12} = 3.96us
$$

计算cycle还要在后面乘上$f_{ADC}$,例如设置的$f_{ADC} = 14MHz$
当$R_{AIN}$ = 400欧姆时
$$
T_S = 0.11us \times f_{ADC} = 0.11us \times 14MHz = 1.54 cycle
$$
当$R_{AIN}$ = 10k欧姆时
$$
T_S = 0.85us \times f_{ADC} = 0.85us \times 14MHz = 11.9 cycle
$$
当$R_{AIN}$ = 50k欧姆时
$$
T_S = 3.96us \times f_{ADC} = 3.96us \times 14MHz = 55.4 cycle
$$

常用函数

1
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hADC);

作用:启动常规序列

1
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hADC, uint32_t Timeout);

作用:以轮询(标志位)的方式等待(常规序列)转换完成

1
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hADC);

作用:获取常规序列的转换结果

示例:单通道转换ADC,光敏电阻点灯

光敏电阻的阻值为10k欧姆,计算采样时间
$$
T_S = (1e4 + 1000) \times 77.6 * 10^{-12} = 0.85us
$$
设定的ADC时钟为4MHz,计算cycle
$$
T_S = 0.85us \times 4MHz = 3.4cycle
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  while (1)
{
//1.启动常规序列
HAL_ADC_Start(&hadc1);
//2.等待转换完成
HAL_ADC_PollForConversion(&hadc1,HAL_MAX_DELAY);
//3.把转换结果保存在DR中
uint32_t dr = HAL_ADC_GetValue(&hadc1);
//4.把结果转换为电压
float voltage = dr * (3.3f - 0.0f) / 4095.0f;
if(voltage > 1.5f)//光线暗
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
}
else//光线亮
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
}
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

示例:定时器触发ADC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	HAL_TIM_Base_Start(&htim3);//启动TIM3
HAL_ADC_Start(&hadc1);//启动ADC
while (1)
{
//1.等待转换结束
HAL_ADC_PollForConversion(&hadc1,HAL_MAX_DELAY);
//2.读取转换结果
uint32_t dr = HAL_ADC_GetValue(&hadc1);
//3.将结果转换成电压
float voltage = dr * (3.3f - 0.0f) / 4095.0f;
//4.串口发送出去
char buffer[32];
sprintf(buffer,"%.3f\n",voltage);
HAL_UART_Transmit(&huart1,(uint8_t*)buffer,strlen(buffer), HAL_MAX_DELAY);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

从模式控制器

从模式控制器
从模式控制器可以作为从机被控制(控制定时器的启、停、复位、增、减)
也可以作为主机控制别人(比如另一个定时器、ADC、DAC等)
两种模式可以同时使用。

从模式控制器的工作模式

作为从机(被控制)作为主机(控制别人)
Slave Mode Disable
从模式禁止
Reset
复位
Encoder Mode 1
编码器模式1
Enable
使能
Encoder Mode 2
编码器模式2
Update
更新
Encoder Mode 3
编码器模式3
Compare Pulse
输出比较脉冲
Reset Mode
复位模式
Compare OC1Ref
输出比较参考信号1
Gated Mode
门模式
Compare OC2Ref
输出比较参考信号2
Trigger Mode
触发模式
Compare OC3Ref
输出比较参考信号3
External Clock Mode 1
外部时钟模式1
Compare OC4Ref
输出比较参考信号4
1、从模式禁止

不使用从机功能
从模式禁止

2、复位模式

使用TRGI的上升沿来复位CNT,同时产生Update事件
复位模式

3、门模式

使用TRGI控制时基单元的开关
门模式

4、触发模式

使用TRGI上升沿来启动定时器
触发模式

5、外部时钟模式1

把TRGI作为定时器的时钟
外部时钟模式1

6、使能

通过TRGO把时基单元的开关状态输出出去
使能

7、更新

每产生一个Update时基就向TRGO输出一个脉冲
更新

8、编码器模式

模式1:在A相的边沿计数(Counting On TI1 Edge)
模式2:在B相的边沿计数(Counting On TI2 Edge)
模式3:双边沿计数(Counting On Both Edge)

常用函数

HAL_StatusTypeDef HAL_TIM_Encoder_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
作用:启动编码器

HAL_StatusTypeDef HAL_TIM_Encoder_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)
作用:停止编码器

示例:占空比测量

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
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);//TIM3发送PWM
while (1)
{
//1、清除CC1标志位
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC1);
//2、启动定时器(CH1和CH2的输入捕获)
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_2);
//3、等待CC1标志位并再次清除
while(__HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1) == 0);
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC1);
//4、再次等待CC1标志位
while(__HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1) == 0);
//5、关闭定时器
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_2);
/*
6、计算测量结果
周期 = CCR1 * 分辨率
占空比 = (CCR2 / CCR1) * 100%
*/
uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_1);
uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_2);

float period = ccr1 * 1e-6f;
float pulseWidth = ccr2 * 1e-6f;
float duty = pulseWidth / period;

//串口发送周期、脉宽、占空比
char ch_period[8];
sprintf(ch_period, "%.1f", period*1e6f);
HAL_UART_Transmit(&huart1, (const uint8_t *)"周期=", 7, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (uint8_t*)ch_period, strlen(ch_period), HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (const uint8_t *)"us", 2, HAL_MAX_DELAY);
char ch_pulsewidth[8];
sprintf(ch_pulsewidth, "%.1f", pulseWidth*1e6f);
HAL_UART_Transmit(&huart1, (const uint8_t *)"脉宽=", 7, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (uint8_t*)ch_pulsewidth, strlen(ch_pulsewidth), HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (const uint8_t *)"us", 2, HAL_MAX_DELAY);
char ch_duty[8];
sprintf(ch_duty, "%.1f", duty*100.0f);
HAL_UART_Transmit(&huart1, (const uint8_t *)"占空比=", 10, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (uint8_t*)ch_duty, strlen(ch_duty), HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (const uint8_t *)"%", 1, HAL_MAX_DELAY);

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

编码器实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  /* USER CODE BEGIN 2 */
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_1);//启动TIM3计时器编码器,通道1
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_2);//启动TIM3计时器编码器,通道2
OLED_Init();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */

while (1)
{
uint8_t cnt = __HAL_TIM_GET_COUNTER(&htim3);//读取TIM3计时器CNT的值
OLED_ShowString(1,1,"cnt:");
OLED_ShowNum(1,5,cnt,2);

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

输入捕获:捕捉信号变化的时间点,保存起来

常用函数

HAL_StatusTypeDef HAL_TIM_IC_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
作用:启动输入捕获
*htim:定时器句柄的指针
Channel:通道编号,TIM_CHANNEL_1~`TIM_CHANNEL_4`

HAL_StatusTypeDef HAL_TIM_IC_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)
作用:停止输入捕获
*htim:定时器句柄的指针
Channel:通道编号,TIM_CHANNEL_1~`TIM_CHANNEL_4`

__HAL_TIM_CLEAR_FLAG(__HANDLE__, __FLAG__)
作用:清除标志位
例如:__HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1),清除TIM1计时器的CC1标志位

__HAL_TIM_GET_FLAG(__HANDLE__, __FLAG__)
作用:查询标志位的值
返回值(uint32_t):0 - 标志位等于0; 非零标志位等于1
例如查询TIM1计时器的CC1的标志位:uint32_t cc1Flag = __HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1);

示例:超时波测距

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
  /* USER CODE BEGIN WHILE */
OLED_Init();
OLED_ShowString(1,1,"Distance:");
while (1)
{
//1、向CNT计数器写0
__HAL_TIM_SET_COUNTER(&htim1, 0);

//2、清除CC1、CC2标志位
__HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1);
__HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC2);

//3、启用定时器
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_2);

//4、向Trig口发送脉冲,不少于10us
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
for(uint32_t i=0; i<10; i++);//每执行一次for循环认为是一个指令周期,8MHz.因此每次1us
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);

//5、等待cc1、cc2标志位
uint32_t expireTime = HAL_GetTick() + 50;//超时时间50ms
uint32_t success = 0;
while(HAL_GetTick() < expireTime )//如果没有超时
{
uint32_t cc1Flag = __HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1);
uint32_t cc2Flag = __HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC2);
if(cc1Flag && cc2Flag)
{
success = 1;//成功测量
break;
}
}

//6、关闭定时器
HAL_TIM_IC_Stop(&htim1, TIM_CHANNEL_1);
HAL_TIM_IC_Stop(&htim1, TIM_CHANNEL_2);

//7、计算结果
if(success == 1)
{
//读取cc1、cc2寄存器的值
uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1);
uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2);
//计算脉宽
float pulseWidth = (ccr2 - ccr1) * 1e-6f;
//计算距离 = 声速 * 传播时间 / 2
float distance = 340.0f * pulseWidth/2.0f;
//距离小于0.2,点灯
if(distance < 0.2)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_0,GPIO_PIN_RESET);
OLED_ShowNum(1,10,distance*1000.0,5);//显示距离
}
else
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_0,GPIO_PIN_SET);
OLED_ShowNum(1,10,distance*1000.0,5);//显示距离
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

输出比较的8钟模式

输出比较的8钟模式

输出时的CH1,为正极性,信号原样输出
输出时的CH1N,为负极性,信号取反输出

通道的使用方法

通道的使用方法
最上面的三个选项都是Input Capture(输入捕获)
PWM Generation 代表PWM的产生,和PWM1和PWM2有关
Forced Output 代表强制输出,和Force Active或Force Inactive有关
Output Compare代表除PWM外的其他6种模式

模式含义
No Output两种输出都禁止
CHx只使能正常输出
CHxN只使能互补输出
CHx CHxN两种输出都使能

常用函数

HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t CHANNEL)
作用:启动PWM正常输出
参数htim:填写定时器句柄指针。例如:&htim1
参数CHANNEL:通道,例如TIM_CHANNEL_1

HAL_StatusTypeDef HAL_TIMEx_PWM_Start(TIM_HandleTypeDef *htim, uint32_t CHANNEL)
作用:启动PWM互补输出
参数htim:填写定时器句柄指针。例如:&htim1
参数CHANNEL:通道,例如TIM_CHANNEL_1

__HAL_TIM_GET_PRESCALER(__HANDLE__)
作用:读PSC预分频器

__HAL_TIM_SET_PRESCALER(__HANDLE__, __VAL__)
作用:写PSC预分频器

__HAL_TIM_GET_COUNTER(__HANDLE__)
作用:读CNT计数器
__HAL_TIM_SET_COUNTER(__HANDLE__, __VAL__)
作用:写CNT计数器

__HAL_TIM_GET_COMPARE(__HANDLE__, __CHANNEL__)
作用:读CCR重复计数器
__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __VAL__)
作用:写CCR重复计数器

示例:呼吸灯正常输出和互补输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* USER CODE BEGIN WHILE */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);//启动正常输出
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);//启动互补输出
while (1)
{
float t = HAL_GetTick() * 0.001;//获取当前时间
float duty = 0.5 * sin(2*3.14*t)+0.5;//计算占空比
uint16_t arr = __HAL_TIM_GetAutoreload(&htim1);
uint16_t ccr = duty * (arr + 1);//计算CCR寄存器的值
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr);//计算结果写入ccr

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

以中断方式启动时基单元

HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
参数htim:填写定时器句柄指针。例如:&htim1

回调函数

1
2
3
4
5
6
7
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1)//多个定时器共用此回调函数,需要对句柄进行判断
{
currentMiliSeconds++;//时间自增
}
}

CubeMX

由于APB2的时钟频率为8MHZ,想要设置时基单元,产生1ms的update事件。
CubeMX
预分频器PSC的值,设置为7,因此分频系数为8。8MHz÷8=1MHz。因此经过分频后得到的时钟频率为1MHz。
计数器的奇数方向为上计数。
自动重装寄存器ARR,设置为999,此时定时器的定时周期为1000。
因此溢出的频率为1MHZ÷1000=1kHz
重复计数器RCR的值设置为0,这样每1ms产生一次update事件。

保险起见,auto-reload preload(ARR寄存器的自动重装预加载)最好使能。

NVIC配置
在NVIC选项卡,设置NVIC中断。
使能TIM1 update interrupt中断。

示例:自制延时函数

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
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim1;//计时器的句柄

/* USER CODE BEGIN PV */
static volatile uint32_t currentMiliSeconds = 0;//定义一个变量,当前的毫秒数
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
/* USER CODE BEGIN PFP */
static void MyDelay(uint32_t Delay);
static uint32_t MyGetTick(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void MyDelay(uint32_t Delay)
{
uint32_t expireTime = MyGetTick() + Delay;
while(MyGetTick() < expireTime){}
}
static uint32_t MyGetTick(void)
{
return currentMiliSeconds;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//回调函数
{
if(htim == &htim1)//多个定时器共用此回调函数,需要对句柄进行判断
{
currentMiliSeconds++;//每次中断,加1,每1s产生一次更新事件
}
}

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* USER CODE BEGIN WHILE */
HAL_TIM_Base_Start_IT(&htim1);//以中断方式启动时基单元
while (1)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
MyDelay(1000);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
MyDelay(1000);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

使用中断方式接收数据

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
参数huart:串口句柄的指针,比如&huart1
参数pData:接收数据缓冲区
参数Size:要接收数据的量

回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
参数huart:串口句柄的指针,比如&huart1

示例:串口接收数据控制LED闪烁频率

声明变量

1
2
3
4
5
6
7
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;//串口的句柄

/* USER CODE BEGIN PV */
static uint32_t blinkInterval = 1000;//闪灯频率变量
static uint8_t dataRcvd;//声明接收数据变量
/* USER CODE END PV */

回调函数,进行数据处理,并再次调用HAL_UART_Receive_IT函数接收下一字节数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)//确认接收数据的串口时huart1
{
if(dataRcvd == '1')
{
blinkInterval = 1000;
}
if(dataRcvd == '2')
{
blinkInterval = 300;
}
if(dataRcvd == '3')
{
blinkInterval = 50;
}
HAL_UART_Receive_IT(&huart1, &dataRcvd, 1);//为下一次接收准备
}
}
/* USER CODE END 0 */

main

1
2
3
4
5
6
7
8
9
10
11
12
13
/* USER CODE BEGIN WHILE */
HAL_UART_Receive_IT(&huart1, &dataRcvd, 1);//开启USART1的中断接收模式,串口1接收到1字节的数据时,保存在dataRcvd变量中
while (1)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_Delay(blinkInterval);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
HAL_Delay(blinkInterval);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

向从机发送数据

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数hspi:SPI句柄的指针,例如&hspi1
参数pData:要发送数据的指针
参数Size:要发送数据的量,单位字节
参数Timeout:超时时间,单位ms。HAL_MAX_DELAY表示无限长超时时间

从从机接收数据

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数hspi:SPI句柄的指针,例如&hspi1
参数pData:接收缓冲区的指针
参数Size:要接收数据的量,单位字节
参数Timeout:超时时间,单位ms。HAL_MAX_DELAY表示无限长超时时间

发送同时接收数据

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
参数hspi:SPI句柄的指针,例如&hspi1
参数pTxData:要发送数据的指针
参数pRxData:接收缓冲区的指针
参数Size:要发送=接收数据的量,单位字节
参数Timeout:超时时间,单位ms。HAL_MAX_DELAY表示无限长超时时间

示例:LED亮灭状态保存在Flash中

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
void SaveLEDStatus(uint8_t ledstatus)//保存LED状态
{
//1.写使能
uint8_t writeEnableCmd[] = {0x06};//写使能命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);//向CS引脚发送低电压
HAL_SPI_Transmit(&hspi1, writeEnableCmd, 1, HAL_MAX_DELAY);//发送写使能命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);//向CS引脚发送高电压
//2.扇区擦除
uint8_t sectorEraseCmd[] = {0x20,0x00,0x00,0x00};//擦除扇区命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);//向CS引脚发送低电压
HAL_SPI_Transmit(&hspi1, sectorEraseCmd, 4, HAL_MAX_DELAY);//发送擦除扇区命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);//向CS引脚发送高电压
//3.延迟100ms
HAL_Delay(100);
//4.写使能
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);//向CS引脚发送低电压
HAL_SPI_Transmit(&hspi1, writeEnableCmd, 1, HAL_MAX_DELAY);//发送写使能命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);//向CS引脚发送高电压
//5.页编程
uint8_t pageProgCmd[5];//页编程
pageProgCmd[0] = 0x02;//页编程指令码
pageProgCmd[1] = 0;//24位地址
pageProgCmd[2] = 0;//24位地址
pageProgCmd[3] = 0;//24位地址
pageProgCmd[4] = ledstatus;//要写入的数据
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);//向CS引脚发送低电压
HAL_SPI_Transmit(&hspi1, pageProgCmd, 5, HAL_MAX_DELAY);//发送页编程命令
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);//向CS引脚发送高电压
//6.延迟10ms
HAL_Delay(100);
}

uint8_t LoadLEDStatus(void)//加载LED状态,开机时使用
{
uint8_t readDataCmd[] = {0x03, 0x00, 0x00, 0x00};//读数据指令码
uint8_t ledState = 0xff;//用于接收读取到的数据
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);//向CS引脚发送低电压

HAL_SPI_Transmit(&hspi1, readDataCmd, 4, HAL_MAX_DELAY);//发送读数据命令
HAL_SPI_Receive(&hspi1, &ledState, 1, HAL_MAX_DELAY);//读取数据

HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);//向CS引脚发送高电压
return ledState;
}
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
HAL_Delay(1000);   // 等待外部 Flash 上电稳定
uint8_t pre = 1, cur = 1;
uint8_t LEDState = 0;//LED亮灭状态
LEDState = LoadLEDStatus();//从Flash读取LED关机前状态
if(LEDState == 1)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
}

while (1)
{
pre = cur;//把当前电平给到上一次记录的电平
if (HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) == GPIO_PIN_SET)//按下按钮,当前值是1,否则为0
{
cur = 1;
}
else
{
cur = 0;
}

if (cur != pre)//当前值和上一次记录的值不相等
{
HAL_Delay(10);//延迟10ms,消抖
if(cur == 0)//当前值是0,还处于按下没松手的状态
{
//不做任何操作
}
else//当前值是1,松手了
{
if(LEDState == 1)//LED记录值为1,现在是点亮状态,要熄灭
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
LEDState = 0;//更新LED记录值
}
else//LED记录值是0,现在是熄灭状态,要点亮
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
LEDState = 1;//更新LED记录值
}
SaveLEDStatus(LEDState);
}
}

向从机写数据

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数hi2c:写i2c的句柄指针,例如&hi2c1
参数DevAddress:从机地址
参数pData:要发送的数据的地址
参数Size:发送数据的量,单位字节
参数Timeout:超时时间,单位ms。永久等待写HAL_MAX_DELAY
返回值HAL_StatusTypeDef:HAL_OK - 成功、HAL_ERROR - 发送出错、HAL_BUSY - i2c接口忙、HAL_TIMEOUT - 发送超时

从从机读数据

HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数hi2c:写i2c的句柄指针,例如&hi2c1
参数DevAddress:从机地址
参数pData:保存接收数据的地址
参数Size:接收数据的量,单位字节
参数Timeout:超时时间,单位ms。永久等待写HAL_MAX_DELAY
返回值HAL_StatusTypeDef:HAL_OK - 成功、HAL_ERROR - 接收出错、HAL_BUSY - i2c接口忙、HAL_TIMEOUT - 接收超时

示例:点亮显示屏和灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 /* USER CODE BEGIN 2 */
uint8_t commands[] = {0x00, 0x8d, 0x14, 0xaf, 0xa5};//点亮屏幕的指令
HAL_I2C_Master_Transmit(&hi2c1, 0x78, commands, sizeof(commands) / sizeof(commands[0]), HAL_MAX_DELAY);//发送数据

uint8_t dataRcvd;
HAL_I2C_Master_Receive(&hi2c1, 0x78, &dataRcvd, 1, HAL_MAX_DELAY);//接收数据

if((dataRcvd & (0x01 << 6)) == 0)//如果屏幕被点亮,点亮灯;屏幕没有点亮,熄灭灯
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2,GPIO_PIN_SET);
}

CubeMX会自动生成串口的句柄,包含串口的全部数据,比如:UART_HandleTypeDef huart1;

串口发送数据

常用函数

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
作用:通过串口向外发送数据
参数huart:填写串口的句柄,比如&huart1
参数pData:填写要发送数据的指针
参数Size:填写要发送数据的数量,单位字节
参数Timeout:超时时间,单位ms,比如HAL_MAX_DELAY无限期等待
返回值:返回数据发送的结果。比如HAL_OK(成功)、HAL_ERROR(发送出错)、HAL_BUSY(串口忙)、HAL_TIMEOUT(发送超时)

示例:串口发送数据

1
2
3
4
5
6
7
8
9
10
11
 /* USER CODE BEGIN 2 */
uint8_t byteNumber = 0x5a;
uint8_t byteArray[] = {1,2,3,4,5};
char ch = 'a';
char *str = "Hello World";

HAL_UART_Transmit(&huart1, &byteNumber, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, byteArray, 5, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
/* USER CODE END 2 */

串口接收数据

常用函数

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
作用:通过串口接收数据
参数huart:填写串口的句柄,比如&huart1
参数pData:填写要接收数据的指针
参数Size:填写要接收数据的数量,单位字节
参数Timeout:超时时间,单位ms,比如HAL_MAX_DELAY无限期等待
返回值:返回数据发送的结果。比如HAL_OK(成功)、HAL_ERROR(发送出错)、HAL_BUSY(串口忙)、HAL_TIMEOUT(发送超时)

示例:串口接收数据控制LED灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* USER CODE BEGIN WHILE */
while (1)
{
uint8_t dataReceive;
HAL_UART_Receive(&huart1, &dataReceive, 1, HAL_MAX_DELAY);

if(dataReceive == '1')
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
}
else if(dataReceive == '0')
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
}

/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}

GPIO输出

常用函数

void HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState)
作用:向IO写0/1
参数GPIOx:组编号,x取A到D。
参数GPIO_Pin:引脚编号,GPIO_PIN_0到15(GPIO_PIN_13)
参数PinState:要写的值,GPIO_PIN_RESET或GPIO_PIN_SET(写0或写1)。
例如,向PC13写0:HAL_GPIO_WritePin(GPIOC, GPIO_Pin_13, GPIO_PIN_RESET)

void HAL_Delay(uint32_t Delay)
作用:延时一段时间
参数Delay:要延迟的时间长度,单位ms。例如延迟0.5秒,就写500。

示例:闪灯实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_SET);

HAL_Delay(500);

HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_RESET);

HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}

GPIO输入

常用函数

GPIO_PinState HAL_GPIO_ReadPin(GPIOx,GPIO_PIN)
作用:读取IO的当前值。
参数GPIOx:组编号,x取A到D
参数GPIO_Pin:引脚编号,GPIO_PIN_0到15(例如GPIO_PIN_13)
返回值:IO引脚的读数,GPIO_PIN_RESET代表0,GPIO_PIN_SET代表1。

示例:按下按钮点亮灯,松开灭灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* USER CODE BEGIN WHILE */
while (1)
{
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) == GPIO_PIN_SET)//PE2按钮没有被按下
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_3,GPIO_PIN_SET);//熄灭PE3的灯
}
else//PE2按钮被按下
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_3,GPIO_PIN_RESET);//点亮PE3的灯
}

/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}