模块配置一般有如下三个基本步骤:
1、查看原理图获得引脚号;
2、根据对应的引脚号再CUMBEMX中配置IO口为相应功能;
3、编写驱动代码提供模块入口供调用;
目录
一、按键模块
概述:利用定时器不断进入周期中断,在中断函数中判断按键是否被按下。记得勾选定时器中断使能,定时器周期设置为(80*10000) / 80M = 10ms,即每10ms进入一次中断,读取按键值判断是否有按键按下。
注意事项:需要在主函数中开启相应的定时器中断。每次使用完记得将按键标志位清0。
//声明按键
extern struct keys keys[4]; //使用外部声明的结构体
//开启定时器中断
HAL_TIM_Base_Start_IT(&htim17);
//按键的使用
void key_pro(void)
{
if(keys[0].single_flag == 1) //B1短按
{
led_disp(0x01);
keys[0].single_flag = 0; //清空标志位
}
if(keys[1].long_flag == 1) //B2长按
{
led_disp(0x02);
keys[1].long_flag = 0;
}
}
1、查看原理图,得按键B1、B2、B3、B4分别对应PB0、PB1、PB2、PA0;
2、根据引脚号配置CUBEMX;
3、驱动代码;
key.h
#ifndef _KEY_H_
#define _KEY_H_
#include "main.h"
#include "stdbool.h"
struct keys //定义按键结构体
{
bool long_flag;
bool single_flag;
bool key_sta;
u16 key_time;
u8 judge_flag;
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
#endif
key.c
#include "key.h"
struct keys keys[4] = {0}; //定义一个结构体,并且初始化
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //重写回调函数
{
if(htim->Instance == TIM17)
{
keys[0].key_sta = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
keys[1].key_sta = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
keys[2].key_sta = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
keys[3].key_sta = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(int i = 0;i < 4;i++)
{
switch(keys[i].judge_flag)
{
case 0:
{
if(keys[i].key_sta == 0)
{
keys[i].judge_flag = 1;
keys[i].key_time = 0;
}
}
break;
case 1:
{
if(keys[i].key_sta == 0)
{
keys[i].judge_flag = 2;
}
else
{
keys[i].judge_flag = 0; //remove shake
}
}
break;
case 2:
{
if(keys[i].key_sta == 1) //release
{
keys[i].judge_flag = 0;
if(keys[i].key_time < 50)
{
keys[i].single_flag = 1;
}
}
else
{
keys[i].key_time++;
if(keys[i].key_time >= 50)
{
keys[i].long_flag = 1;
}
}
}
break;
}
}
}
}
二、LED模块
概述:通过一个8位的变量左移八位到PC脚的高八位来控制PC8-PC15所控制的LED1-LED8。
注意事项:因为LCD和LED有公共引脚,所以在PD2配置时,选择配置为低电平,这样控制LCD时就不会导致LED跳变。
1、查看原理图,八位锁存器,得LED1-LED8分别对应PC8-PC15以及锁存器的控制引脚PD2;
2、根据引脚号配置CUBEMX;
3、驱动代码;
led.h
#ifndef _LED_H_
#define _LED_H_
#include "main.h"
void led_disp(u8 uled);
#endif
led.c
#include "led.h"
void led_disp(u8 uled)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,uled << 8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
三、LCD模块
概述:lcd模块比较简单,将官方给的驱动文件导入工程并且初始化LCD,然后一般会用sprintf函数(在"stdio.h"中)和LCD行显示函数即可。
1、将官方驱动文件导入工程中;
2、包含头文件、初始化LCD;
#include "lcd.h"
#include "stdio.h" //使用sprintf函数需要包含该头文件
LCD_Init();
3、使用sprintf和LCD行显示函数显示内容;
u8 text[50];
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
sprintf((char *)text,"this is lcd test");
LCD_DisplayStringLine(Line4 ,(unsigned char *)text);
四、主板AD模块
概述:配置两路ADC即可完成对电压的采集。
1、查看原理图,得R37、R38分别对应PB15、PB12;
2、根据引脚号配置CUBEMX;
3、驱动代码;
u16 R37_val,R38_val;
float R37_v,R38_v;
void get_v(void)
{
HAL_ADC_Start(&hadc2);
R37_val = HAL_ADC_GetValue(&hadc2);
HAL_ADC_Start(&hadc1);
R38_val = HAL_ADC_GetValue(&hadc1);
R37_v = R37_val * 3.3 / 4096 ;
R38_v = R38_val * 3.3 / 4096 ;
}
五、主板输入捕获
概述:配置两个对应定时器为直接捕获模式即可完成对频率的采集。
注意事项:跳线帽一定要记得接上。ARR(绿色箭头)越大越好。主函数中一定要记得开启捕获中断。选择通道为多少,对应的Trigger Source也要选对应的Tl1FP+通道数
extern u32 R39_freq,R40_freq,R1_freq,R2_freq;
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); //R39
HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1); //R40
//显示频率
sprintf((char *)text,"f1:%5d f2:%5d",R39_freq,R40_freq);
LCD_DisplayStringLine(Line0 ,(unsigned char *)text);
1、查看原理图,得R39、R40分别对应PB4、PA15;
2、根据引脚号配置CUBEMX;
3、驱动代码;
capture.h
#ifndef _CAPTURE_H_
#define _CAPTURE_H_
#include "main.h"
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
#endif
capture.c
#include "capture.h"
u16 R39_val,R40_val = 0;
u32 R39_freq,R40_freq = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
R39_val = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
R39_freq = 80000000 / (80 * R39_val);
}
else if(htim->Instance == TIM8)
{
R40_val = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
R40_freq = 80000000 / (80 * R40_val);
}
}
六、EEPROM模块
概述:导入官方给的驱动文件并配置对应IO为Output模式,然后添加eeprom的读写函数,在main.c中即可包含头文件即可使用。
注意事项:
u16 eeprom_data; //读取eeprom数据
u8 eeprom_h; //数据高八位
u8 eeprom_l; //数据低八位
u16 val = 61111;
//数据读取方式:
eeprom_h = eeprom_read(1);
HAL_Delay(10);
eeprom_l = eeprom_read(2);
eeprom_data = eeprom_h << 8;
eeprom_data |= eeprom_l; //或上低八位
//数据存储方式:
eeprom_h = val >> 8; //高八位
eeprom_l = val&0x00ff; //低八位
eeprom_write(1,eeprom_h);
HAL_Delay(10);
eeprom_write(2,eeprom_l);
1、将官方驱动文件导入工程中;
2、根据引脚号配置CUBEMX对应引脚为OUTPUT;
3、补充驱动的读写代码;
iic.h
#include "main.h" //宏定义uchar
uchar eeprom_read(uchar addr);
void eeprom_write(uchar addr,uchar data);
iic.c
uchar eeprom_read(uchar addr) //读函数
{
uchar data;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
data = I2CReceiveByte();
I2CWaitAck();
I2CStop();
return data;
}
void eeprom_write(uchar addr,uchar data) //写函数
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
}
七、PWM输出
概述:选取PA11作为PWM输出引脚输出频率为80M/(PSC预分频系数*ARR重装载值)。
注意事项:常用函数如下:具体用法可以自行查阅,两种方法达到的效果是一模一样的。
//须在main中开启定时器4通道1的PWM输出
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
//方法一:利用hal库提供的定时寄存器接口控制ARR和CCR
__HAL_TIM_SetAutoreload(&htim4,10000-1);
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,500);
//方法二:直接通过定时器实例控制寄存器
TIM4->ARR = 10000-1;
TIM4->CCR1 = 500;
1、选择PA11作为PWM输出引脚;
2、根据引脚号配置CUBEMX;
八、AD按键模块
概述:AD按键通过AD采集PA5的电压值,根据串联分压以及电压值的大小来判断哪个按键被按下。
注意事项:由于不是直接相连,AD采集的数据需要经过滤波处理再使用,否则可能会误判。
1、查看原理图,得AD按键对应PA5引脚;
2、根据引脚号配置CUBEMX;
3、驱动代码;
adkey.h
#ifndef _ADKEY_H_
#define _ADKEY_H_
#include "main.h"
#define ADKEY_BUF_MAXLENGTH 50
u16 read_adkey();
u8 scan_adkey();
#endif
adkey.c
#include "adkey.h"
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc2;
u16 get_val()
{
HAL_ADC_Start(&hadc2);
return HAL_ADC_GetValue(&hadc2);
}
u16 read_adkey()
{
u16 adkey_buf[ADKEY_BUF_MAXLENGTH];
//读50个数
for(int i = 0;i < ADKEY_BUF_MAXLENGTH;i++)
{
adkey_buf[i] = get_val();
}
//冒泡排序25次
for(int i = 0;i < ADKEY_BUF_MAXLENGTH / 2;i++)
{
for(int j = 0;j < ADKEY_BUF_MAXLENGTH - i - 1;j++)
{
if(adkey_buf[j] >adkey_buf[j+1])
{
u16 temp = 0;
temp = adkey_buf[j];
adkey_buf[j] = adkey_buf[j+1];
adkey_buf[j+1] = temp;
}
}
}
//返回中位数
if(ADKEY_BUF_MAXLENGTH % 2 == 0)
{
return (adkey_buf[ADKEY_BUF_MAXLENGTH / 2] + adkey_buf[(ADKEY_BUF_MAXLENGTH / 2) - 1])/2;
}
else return adkey_buf[ADKEY_BUF_MAXLENGTH / 2];
}
u8 scan_adkey()
{
u16 temp = 0;
temp = read_adkey();
if(temp < 0xfff / 14)
{
return 1;
}
else if((temp > 0xfff / 14 * 1) && (temp < 0xfff / 14 * 3))
{
return 2;
}
else if((temp > 0xfff / 14 * 3) && (temp < 0xfff / 14 * 5))
{
return 3;
}
else if((temp > 0xfff / 14 * 5) && (temp < 0xfff / 14 * 7))
{
return 4;
}
else if((temp > 0xfff / 14 * 7) && (temp < 0xfff / 14 * 9))
{
return 5;
}
else if((temp > 0xfff / 14 * 9) && (temp < 0xfff / 14 * 11))
{
return 6;
}
else if((temp > 0xfff / 14 * 11) && (temp < 0xfff / 14 * 13))
{
return 7;
}
else if((temp > 0xfff / 14 * 13) && (temp < 0xf6f))
{
return 8;
}
else
{
return 0;
}
}
九、DS18B20温度传感器模块
1、驱动代码;
u16 ds18b20_read(void)
{
u8 i , val[2];
ow_reset(); //复位并检测是否存在
ow_byte_wr(OW_SKIP_ROM); //跳过ROM
ow_byte_wr(DS18B20_CONVERT); //转化温度并纯如内部ram中
delay_us(750000);
ow_reset(); //复位并检测是否存在
ow_byte_wr(OW_SKIP_ROM); //跳过ROM
ow_byte_wr(DS18B20_READ); //读取内部暂存器中的内容,实际是九个内容
for(i=0; i<2; i++) //需要的内容就是前两个
{
val[i] = ow_byte_rd();
}
//低四位并上高四位
return ((val[1]<<8|val[0]));
}
2、在主函数中调用 ds18b20_init_x();
3、读取温度并显示;
sprintf((char *)text, "temper:%.1f", ds18b20_read() / 16.0);
LCD_DisplayStringLine(Line5,(unsigned char *)text);
十、温湿度传感器模块
1、驱动代码;
uint8_t recData(void)
{
uint8_t i,temp=0,j=220;
for(i=0; i<8; i++){
while(HAL_GPIO_ReadPin(GPIOA,HDQ)); //添加句
while(!HAL_GPIO_ReadPin(GPIOA,HDQ));
usDelay(40);
if(HAL_GPIO_ReadPin(GPIOA,HDQ))
{
temp=(temp<<1)|1;
while(HAL_GPIO_ReadPin(GPIOA,HDQ)&&(j--));
}
else
{
temp=(temp<<1)|0;
}
}
return temp;
}
void dht11_rst(void)
{
outDQ(0);
HAL_Delay(20);
outDQ(1);
usDelay(60);
}
uint8_t dht11_cheek(void)
{
uint8_t re = 0;
inDQ();
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) && re < 100)
{
re++;
usDelay(1);
}
if(re >= 100)
{
return 1;
}
else re = 0;
while(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) && re < 100)
{
re++;
usDelay(1);
}
if(re >= 100)
{
return 1;
}
else return 0;
}
void dht11Read(void)
{
uint8_t val[5];
dht11_rst();
if(dht11_cheek() == 0)
{
for(int i = 0; i < 5; i++)
{
val[i] = recData();
}
if(val[0] + val[1] + val[2] + val[3] == val[4])
{
dht11.humidity_high = val[0];
dht11.humidity_low = val[1];
dht11.temperature_high = val[2];
dht11.temperature_low = val[3];
}
}
}
2、在主函数中调用 DHT11_Init();
3、读取温湿度并显示;
extern dht11Data dht11;
dht11Read();
sprintf((char *)text, " temp1:%d, hmi:%d ", dht11.temperature_high, dht11.humidity_high);
LCD_DisplayStringLine(Line2, (uchar *)text);
十一、数码管模块
概述:配置PA1、PA2、PA3为输出引脚,并且初始化成低电平即可。
注意事项:数码管显示原理不多解释,可以百度一查就得到答案。
1、查看原理图,得RCLK、SCK、SER分别对应PA2、PA3、PA1;
2、根据引脚号配置CUBEMX;
3、驱动代码;
seg.h
#ifndef _SEG_H_
#define _SEG_H_
#include "main.h"
#define SER_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET)
#define SER_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET)
#define RCK_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET)
#define RCK_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET)
#define SCK_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET)
#define SCK_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET)
void set_seg(u8 bit1,u8 bit2,u8 bit3);
#endif
seg.c
#include "seg.h"
u8 seg[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x67,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00};
//0-9 a-f 以及关闭0x00
void set_seg(u8 bit1,u8 bit2,u8 bit3)
{
u8 temp = 0;
RCK_L;
temp = seg[bit3];
for(int i = 0;i < 8;i++)
{
SCK_L;
if(temp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
temp = temp << 1;
SCK_L;
SCK_H;
}
temp = seg[bit2];
for(int i = 0;i < 8;i++)
{
SCK_L;
if(temp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
temp = temp << 1;
SCK_L;
SCK_H;
}
temp = seg[bit1];
for(int i = 0;i < 8;i++)
{
SCK_L;
if(temp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
temp = temp << 1;
SCK_L;
SCK_H;
}
RCK_L;
RCK_H;
}
十二、NE555模块
方法一:
概述:通过直接测量PWM的上升沿到上升沿PA6_CCR1以及间接测量上升沿到下降沿之间的时间,再用CCR2时间(即高电平时间)除以单个周期(高低电平总时间)记得占空比。
注意事项:
// 在main.c中声明为其他源文件定义的变量
extern float PA6_DUTY;
extern u32 PA6_FREQ;
1、产看原理图,得PWM1对应PA6;
2、根据引脚号配置CUBEMX;
3、驱动代码;
capture.c
#include "capture.h"
u32 PA6_CCR1,PA6_CCR2 = 0;
float PA6_DUTY = 0;
u32 PA6_FREQ = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
PA6_CCR1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
PA6_CCR2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
PA6_FREQ = 80000000 / (80 * PA6_CCR1);
PA6_DUTY = (float)PA6_CCR2 * 100 / PA6_CCR1;
}
}
capture.h
#ifndef _CAPTURE_H_
#define _CAPTURE_H_
#include "main.h"
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
#endif
方法二:
概述:方法一需要两个不同的定时器通道才行,但是有时我们只有一个通道可用,于是根据法一的思路,我查阅资料想了第二种仅需一个通道就可以完成频率以及占空比测量的方法。原理跟法一相同。
原理:
1、产看原理图,得PWM2对应PA7;
2、根据引脚号配置CUBEMX;
3、驱动代码;
capture.c
u32 PA7_UP,PA7_DOWN = 0;
float PA7_DUTY;
u32 PA7_FREQ = 0;
bool start = true;
bool up_flag,down_flag = false;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM17)
{
if(start == true)
{
__HAL_TIM_SET_COUNTER(htim,0); //清空计数
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); //下降沿触发
start = false;
down_flag = true;
}
else if((start == false) && (down_flag == true))
{
PA7_DOWN= HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); //t1 - t0
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); //上升沿触发
down_flag = false;
up_flag = true;
}
else if((start == false) && (up_flag == true))
{
PA7_UP = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1); //t2 - t0
__HAL_TIM_SET_COUNTER(htim,0); //清空计数
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); //上升沿触发
//结算一次
PA7_FREQ = 80000000 / (80 * PA7_UP);
PA7_DUTY = (float)PA7_DOWN * 100 / PA7_UP;
up_flag = false;
start = true;
}
}
}
capture.h
#ifndef _CAPTURE_H_
#define _CAPTURE_H_
#include "main.h"
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
#endif
十三、光敏电阻模块
1、查看原理图,得TRDO、TRAO分别对应PA3、PA4;
2、根据引脚号配置CUBEMX;
3、驱动代码;
tr.h
#ifndef _TR_H_
#define _TR_H_
#include "main.h"
#include <stdbool.h>
bool get_trdo();
u16 get_trao();
#endif
tr.c
#include "tr.h"
extern ADC_HandleTypeDef hadc2;
bool get_trdo() //获取数字量
{
return HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3);
}
u16 get_trao() //获取模拟量
{
HAL_ADC_Start(&hadc2);
return (HAL_ADC_GetValue(&hadc2) * 3.3 / 4096);
}
十四、RTC时钟模块
概述:通过CUBEMX配置好RTC时钟,在工程中定义RTC时间、日期结构体存放读取到的时间,并调用显示在LCD屏上。
注意事项:获取RTC时间或者日期时,必须先获取时间,再获取日期,顺序不能颠倒,而且时间和日期调用的时候不能单独调用,必须两个同时调用,否则出错。
1、打开CUBEMX配置RTC时钟;
2、打开工程定义RTC时间和日期结构体用来储存从RTC时钟读取到的值;
RTC_TimeTypeDef T;
RTC_DateTypeDef D;
HAL_RTC_GetTime(&hrtc, &T, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &D, RTC_FORMAT_BIN);
3、LCD显示时间和日期信息观察是否读取成功;
u8 text[50];
sprintf((char *)text,"T%02d-%02d-%02d D%02d-%02d-%02d",T.Hours,T.Minutes,T.Seconds,D.Year,D.Month,D.Date);
LCD_DisplayStringLine(Line9 ,(unsigned char *)text);
十五、串口模块
常见的字符串处理函数
strstr() //字符串字串查找函数
strcmp() //字符串比较函数
strcpy() //字符串复制函数
strcat() //字符串连接函数
main.c中开启回调函数
HAL_UARTEx_ReceiveToIdle_IT(huart, rx_buf, sizeof(rx_buf));
空闲中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
// 再次开启,否则只会接收一次
HAL_UARTEx_ReceiveToIdle_IT(huart, rx_buf, sizeof(rx_buf));
HAL_UART_Transmit(huart, (uint8_t *)"666", sizeof("666"), 1000);
}
}
十六、单adc多通道采集
应用场景:试想如果ad按键和R37需要同时使用,那我们应该如何配置呢?它们是公用一个AD的。
配置步骤:
1、查看原理图得引脚分别为PA5、PB15、PB12、PA4;
2、配置cubemx的多通道采集;
3、使用代码;
HAL_ADC_Start(&hadc2);
pa5_val = HAL_ADC_GetValue(&hadc2);
HAL_Delay(10);
v1_val = HAL_ADC_GetValue(&hadc2);
HAL_Delay(10);
pa4_val = HAL_ADC_GetValue(&hadc2);
HAL_Delay(10);