引言

本文不涉及原理,只涉及模块的使用
南京工业大学浦江学院 22级自动化 姚道文

[TOC]

零、项目新建和LCD底层移植

链接: 项目新建和LCD底层移植

一、Systick系统滴答定时器

24位向下递减计数器,0~16,777,216

CubeMAX会自动将Systick配置成1ms中断的定时器,并将变量uwTick每1ms增加1

精确延时:HAL_Delay()函数,但会阻塞程序,在while中应控制在10s以内

1
2
3
4
5
/*程序每1ms进入1次该函数*/
void SysTick_Handler(void)
{
HAL_IncTick();
}

二、KEY模块

1.KEY模块CubeMX设置

img

2.KEY模块代码

1.main.c文件

1
#inlcude "key.h"

2.key.c文件

宏定义的括号不可省略,想想#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)和

KB1 = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);有什么区别!!!

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
#include "key.h"
//没有按下是高电平,按下时低电平
#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) //读取KB1电平
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) //读取KB2电平
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) //读取KB3电平
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) //读取KB4电平
#define KEYPORT KB1 | (KB2<<1) | (KB3<<2) | (KB4<<3) | 0xf0 //读取的电平数据整合

u8 Trg; // 全局变量,单次触发
u8 Cont; // 全局变量,长按
void Key_Read(void)
{
u8 ReadData = (KEYPORT)^0xff; //将读取的电平数据转变成按下数据,按下之后对应位的数据变1
//u8 ReadData = ~(KEYPORT); //可替代上行代码
Trg = ReadData & (ReadData ^ Cont); //
Cont = ReadData; //储存这次的按下数据,用于下次的比较
}
/* 代码解释
u8 ReadData = (KEYPORT)^0xff; //相当于取反操作,将读取的电平数据转变成亮灭数据
(ReadData ^ Cont) //将本次读数的按键与上次做对比,位不同则改位为1
ReadData & (ReadData ^ Cont)//将按下数据与改变情况做对比,位相同为1
*/

/*-----------------------------------------------------------------------------------*/
//Example:
if(Trg & 0x01) //单词触发
{
//do
}
if(Cont & 0x01) //长按
{
key_cnt++;
if(key_cnt == 100) //每10ms读取一次,此处判断是否长按1s
{
cnt_key = 0;
//do
}
}



u32 keyTick;
void Key_Process()
{
if(uwTick - keyTick < 20) return; //当小于20ms时,return出函数
keyTick = uwTick;
Key_Read();

if(Trg & 0x01) //B2单次
{

}
if(Trg & 0x02) //B2单次
{

}
if(Trg & 0x04) //B3单次
{

}
if(Trg &0x08) //B4单次
{

}
if(Trg == 0x0C) //B3和B4同时按下
{

}
}

3.key.h文件

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __KEY_H
#define __KEY_H

#include "main.h" //包含后可使用u8

extern u8 Trg; // 全局变量,单次触发
extern u8 Cont; // 全局变量,长按

void Key_Read(void); //声明函数

#endif
//最后必须留一空行

三、LED模块

1.LED模块CubeMX设置

LED和LCD引脚冲突,PD2为LED的锁存器

img

2.LED模块代码

1.main.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "led.h"

u8 LED_DATA ;
void LED_Process()
{
if()
{
LED_DATA |= 0x01; //开灯LED1
}

else if()
{
LED_DATA &= ~0x02; //灭灯LED2
}


LED_Control(LED_DATA );
}

点击并拖拽以移动

2.led.c文件(led驱动文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "led.h"
void LED_Control(u8 led_ctrl)
{
//先熄灭所有LED灯
HAL_GPIO_WritePin(GPIOC,0xff00,GPIO_PIN_SET); //关闭所有灯,PC8~PC15为8个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,led_ctrl << 8,GPIO_PIN_RESET);//开灯
//上一行是:0x01<<8变为0x0100,拉低PC8引脚,LED1点亮
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET); //打开锁存器
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); //打开锁存器
}

3.led.h文件

1
2
3
4
5
6
7
8
9
#ifndef __LED_H
#define __LED_H

#include "main.h" //包含后可使用u8

void LED_Control(u8 led_ctrl); //声明函数

#endif
//最后必须有一空行

四、LCD模块

1.LCD模块CubeMX设置

配置见第

2.LCD模块代码

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
//屏幕大小为20*10
#include <stdio.h> //sprintf需包含此头文件

u32 lcdTick; //读取计数器值
void LCD_Process()
{

if(uwTick - lcdTick < 200) return; //LCD每200ms刷新一次
uwTick = lcdTick;

u8 display_buf[20]; //定义缓冲区数组存储数据

LCD_DisplayStringLine(Line1,(u8*)" DATA");
//sprintf函数作用,第二2.3参数与print函数使用相同,第一个参数是printf打印出的值
sprintf((char*)display_buf," VR37:%4.2fV",VR37);
LCD_DisplayStringLine(Line3,(u8*)display_buf);

sprintf((char*)display_buf," VR38:%4.2fV",VR38);
LCD_DisplayStringLine(Line4,(u8*)display_buf);

sprintf((char*)display_buf," FR39:%05dHZ",FR39);
LCD_DisplayStringLine(Line5,(u8*)display_buf);

sprintf((char*)display_buf," FR40:%05dHZ",FR40);
LCD_DisplayStringLine(Line6,(u8*)display_buf);
}
int main(void
{
LCD_Clear(Blue); //设置背景颜色
LCD_SetBackColor(Blue); //设置字体背景颜色
LCD_SetTextColor(White); //设置字体颜色
}

​ ==以上部分为嵌入式必考模块,务必熟练!!!==

​ ==以下部分为嵌入式选考模块!!!==


五、ADC模块

1.ADC模块CubeMX设置

3cd136daad534bd48907ecb751b3fcf9.png

  • 设置引脚

efd7f43d3f52414fb7ca6ca3e0aabc63.png

  • 设置ADC1

d77692ab112c499eb5fac08be9c285a1.png

  • 设置ADC2

e3527d1a94d44343ba5274224aee9663.png

2.ADC多路采集模块代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float VR37,VR38,VMCP;      //定义电压变量
float val_R37,val_R38,val_MCP;//0-4095

void ADC_Process()
{
HAL_ADC_Start(&hadc1); //开启adc1_ch5
val_MCP = HAL_ADC_GetValue(&hadc1); //获取adc1_ch5的值
VMCP= val_MCP / 4095.0f * 3.3f; //计算VMCP的值

HAL_ADC_Start(&hadc1); //开启adc1_ch11
val_R38 = HAL_ADC_GetValue(&hadc1); //获取adc1_ch11的值
VR38= val_R38 / 4095.0f * 3.3f; //计算VR38的值

HAL_ADC_Start(&hadc2); //开启adc2
val_R37 = HAL_ADC_GetValue(&hadc2); //获取adc2的值
VR37 = val_R37 / 4095.0f * 3.3f; //计算VR37的值
}

3.ADC数字滤波代码(题目不要求,不用滤波)

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
/*数字滤波*/
float VR37; //定义R37电压变量
u32 sum_val_R37; //电压和变量
u8 val_37_buf; //每次读出的值
u8 adc_cnt = 0; //计数
void ADC_Process()
{
val_37_buf = HAL_ADC_GetValue(&hadc2); //获取值
sum_val_R37 += VR37; //和
adc_cnt++; //次数加
if(adc_cnt == 10) //判断否已经读取10次
{
VR37 = sum_val_R37 / 10.0f / 4095.0f * 3.3f; //求电压平均值
sum_val_R37 = 0; //求和清零
adc_cnt = 0; //计数清零
}
}

//遍历刷新数组中最老的值,求数组的平均值
void ADC_Process()
{
HAL_ADC_Start(&hadc2);
adc2_val = HAL_ADC_GetValue(&hadc2);

ADC_buf[ADC_cnt] = adc2_val / 4095.0f * 3.3f; //储存电压
for(cnt=0 ;cnt < 10;cnt++) //电压求和
{
ADC_sum = ADC_sum + ADC_buf[cnt];
}
VR37 = ADC_sum / 10.0f; //电压平均值
ADC_sum = 0; //求和清零
ADC_cnt++; //数组下标++
if(ADC_cnt == 9) //下标范围判断
ADC_cnt = 0;

}

学自此可做第十届省赛真题:此届只涉及到了ADC外设。
链接:百度网盘:第十届省赛真题 提取码: 6666

六、DAC输出模块

1.DAC双路输出模块CubeMX设置

bdf317270b204cecb17134591f7d3799.png

设置引脚

{3b89c9117fbf41ac8e0bfc75f1446a68.png}

DAC1设置

2.DAC双路输出模块代码

1
2
3
4
5
6
7
8
9
10
11
12
u16 dac_ch1_val,dac_ch2_val;
void DAC_Process()
{
dac_ch1_val = (1.1f/3.3f)*4095.0f; //CH1输出1.1V
dac_ch2_val = (2.3f/3.3f)*4095.0f; //CH2输出2.3V

HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,dac_ch1_val);//设置DAC1,CH1的值
HAL_DAC_Start(&hdac1,DAC_CHANNEL_1); //打开DAC1,CH1

HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_2,DAC_ALIGN_12B_R,dac_ch2_val);//设置DAC1,CH2的值
HAL_DAC_Start(&hdac1,DAC_CHANNEL_2); //打开DAC1,CH2
}

七、PWM捕获模块

1.PWM双路捕获模块CubeMX设置

228e9d0e491a42d5a14bbf9698c3b860.png

设置引脚

0dbd4b9828e04a32b829f1d03cadc0df.png

设置TIM2

075bf965a7bc46fcaabe7bc08f3720e2.png

设置TIM3

ecb7f96fc5fd4cbda32ab62e34ef15e0.png

打开TIM中断

2.PWM双路捕获模块代码

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
float VR37,VR38;      //定义电压变量
u16 FR39,FR40; //定义频率变量
float DR40,DR39; //占空比变量
float val_R37,val_R38;//0-4095
u8 PWM_R40_state =0,PWM_R39_state;//状态改变变量
u16 R40_cnt1 = 0,R40_cnt2 = 0,R39_cnt1 = 0,R39_cnt2 = 0; //存储中断发生时间变量
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //PWM捕获回调函数
{
if(htim == &htim2) //判断TIM2,R40
{
if(PWM_R40_state == 0) //第一个上升沿产生
{
TIM2->CNT = 0; //计数器清零
TIM2->CCER |= 0x02; //改为下降沿中断,CCER寄存器的第二位的值控制CH1的上下沿捕获
PWM_R40_state = 1; //切换状态
}
else if(PWM_R40_state == 1) //下降沿产生
{
R40_cnt1 = TIM2->CNT; //获取计数(高电平时间)
TIM2->CCER &= ~0x02; //改为上升沿中断
PWM_R40_state = 2; //切换状态
}
else if(PWM_R40_state == 2) //第二个上升沿产生
{
R40_cnt2 = TIM2->CNT; //获取计数(周期)
FR40 = 1000000 / R40_cnt2; //计算频率
DR40 = R40_cnt1 *100.0f / R40_cnt2 ; //计算占空比
PWM_R40_state = 0;
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//重新打开中断

}
if(htim == &htim3) //判断TIM3,R39
{
if(PWM_R39_state == 0) //第一个上升沿产生
{
TIM3->CNT = 0; //计数器清零
TIM3->CCER |= 0x02; //改为下降沿中断
PWM_R39_state = 1; //切换状态
}
else if(PWM_R39_state == 1) //下降沿产生
{
R39_cnt1 = TIM3->CNT; //获取计数(高电平时间)
TIM3->CCER &= ~0x02; //改为上升沿中断
PWM_R39_state = 2; //切换状态
}
else if(PWM_R39_state == 2) //第二个上升沿产生
{
R39_cnt2 = TIM3->CNT; //获取计数(周期)
FR39 = 1000000 / R39_cnt2; //计算频率
DR39 = R39_cnt1 *100.0f/ R39_cnt2; //计算占空比
PWM_R39_state = 0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//重新打开中断
}
}
1
2
3
//以下代码在main()中
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//开启PWM捕获中断
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//开启PWM捕获中断

八、PWM输出模块

1.PWM输出模块CubeMX设置

40f57283c6b34721ba805510e0f9f617.png

设置引脚

148c5a4ab14f4796b0f7cea266ac76c4.png

TIM16设置

3799c54ca0d74373a4edc908a878fdbb.png

TIM17设置

2.PWM输出模块代码

1
2
3
4
5
6
7
8
//ARR自动重装载值,CRR比较寄存器
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);//启用TIM16,通道1,PWM输出
TIM16->ARR = 999; //设置频率为1000HZ
TIM16->CCR1 = 400; //通道1,设置60%占空比

HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);//启用TIM17,通道1,PWM输出
TIM17->ARR = 1999; //设置频率为500HZ
TIM17->CCR1 = 100; //通道1,设置95%占空比

九、I2C模块

直接将官方的i2c.c,i2c.h添加到项目中

1.I2C模块代码

1.i2c.c文件

​ 在i2c.c后编写 各函数主体9-12-6-7行

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
//写24C02
void EEPROM_Write(u8 add,u8 dat)
{
I2CStart(); //发送开始信号

I2CSendByte(0xa0); //发送24c02地址,第一位0为写信号
I2CWaitAck(); //等待回应

I2CSendByte(add); //发送填写地址
I2CWaitAck(); //等待回应

I2CSendByte(dat); //发送要储存的数据
I2CWaitAck(); //等待回应

I2CStop(); //发送停止信号

HAL_Delay(5); //延时,两次写入的间隔要5ms
}
//读24C02
u8 EEPROM_Read(u8 add)
{
I2CStart(); //发送开始信号

I2CSendByte(0xa0); //发送24c02地址,第一位0为写信号
I2CWaitAck();

I2CSendByte(add); //发送要读取数据的储存地址
I2CWaitAck();

I2CStart(); //再次发送开始信号

I2CSendByte(0xa1); //发送24c02地址,第一位1为读信号
I2CWaitAck();

u8 dat = I2CReceiveByte(); //读取数据
I2CSendNotAck(); //不应答

I2CStop(); //发送停止信号
return(dat); //返回数据接收到的数据
}

//写MCP4017
void MCP4017_Write(u8 val)
{
I2CStart(); //发送开始信号

I2CSendByte(0x5E); //发送器件地址,写信号
I2CWaitAck(); //等待应答

I2CSendByte(val); //发送数据 //8位数据,但最高位为0
I2CWaitAck(); //等待应答

I2CStop(); //发送结束信号
}

//读MCP4017
u8 MCP4017_Read(void)
{
I2CStart(); //发送开始信号

I2CSendByte(0x5F); //发送器件地址,读信号
I2CWaitAck(); //等待应答

u8 val = I2CReceiveByte(); //接收数据
I2CSendNotAck(); //不应答

I2CStop(); //发送停止信号

return val; //返回接收到的数据
}

/*MCP4017数字电位器*/

2.main.c文件

1
2
3
4
5
6
7
#include "i2c.h"    


int main(void)
{
I2CInit(); //初始化IO口
}

Rs * 127 = 100k ->Rs = 100k / 127 Rs是接入电路的每段电阻,共127个

R = 8 *(100 / 127) R是接入电路的电阻

V = 3.3 * R / (R + 10) 输出的理论电压

1
EEPROM_Write(0x08);

aedc1195217f4e1ab2be933a6cc9aea4.png

91eecd4f6d85424597c59150f1aec333.png


十、UART模块

1.UART模块CubeMX配置

img

2.UART发送模块代码

查找fputs()的方法

img

务必勾选MicroLIB库,否则fputs()重映射无法使用

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*在"usart.h"中声明*/
#include "stdio.h" //包含后可使用FILE

/*printf重定向在"usart.c"中*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1,(unsigned char*)&ch,1,50); //串口发送函数
return ch;
}

/*在"main.c"中直接使用printf发出数据*/
u8 val = 123; //定义变量
u8 buf[50] = {"lanqiaobei\r\n"}; //定义数组
printf("HelloWorld!\r\n"); //发送字符串
printf("%d\r\n",666); //发送数字
printf("val:%d\r\n",val); //发送变量中的数据
printf("%s\r\n",buf); //发送数组

3.UART接收模块代码

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
#include <string.h>    //包含后可使用memset()函数
u8 uart_buf[20]; //uart接收数组
u32 uartTick; //uart定时器计数变量
u8 rx_cnt; //uart数据下表变量
u8 rx_buf[10]; //uart接收缓存数组

void RxIdle_Process() //拓展部分函数,用于清空储存数组中的垃圾值
{
if(uwTick - uartTick < 50)return; //50ms执行一次
uartTick = uwTick;

rx_cnt = 0; //计数下标清零
memset(rx_buf,'\0',sizeof(rx_buf)); //清空rx_buf中的数据,使用要引用<string.h>
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //uart接收回调中断
{
uartTick = uwTick; //重新计时50ms

rx_buf[rx_cnt++] = uart_buf[0];
if(rx_cnt == 3) //此处代码为:当接收到3个值时,执行亮灯操作
{
rx_cnt = 0;
LED_Control(rx_buf[1]);
}
HAL_UART_Receive_IT(&huart1,uart_buf,1); //重新打开中断
}

main.c

1
HAL_UART_Receive_IT(&huart1,uart_buf,1);

十一、RTC模块

1.RTC模块CubeMX配置

3bd079d3540a45a6aa1f6ab3f3300362.png

RTC配置

2.RTC模块代码

1
2
3
4
5
6
7
RTC_TimeTypeDef RTC_time;//定义时间结构体变量
RTC_DateTypeDef RTC_data;//定义日期结构体变量
void RTC_Process()
{
HAL_RTC_GetTime(&hrtc,&RTC_time,RTC_FORMAT_BIN);//获取时间,返回十进制
HAL_RTC_GetDate(&hrtc,&RTC_data,RTC_FORMAT_BIN);//获取日期,返回十进制
}