一、LED
1.基本点亮
void LED_Disp(unsigned char dsled)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,dsled<<8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
2.闪烁灯
void LED_Pro()
{
if(uwTick-uwTick_LED<100) return; //用systick实现100ms执行一次函数
uwTick_LED=uwTick;
static unsigned char dsled;
dsled|=0x01; //点亮LED1
dsled&=~0x02; //熄灭LED2
dsled^=0x04; //用逻辑异或来进行取反操作实现LED3的闪烁
LED_Disp(dsled);
}
3.流水灯
void LED_Pro2()
{
if(uwTick-uwTick_LED1<100) return;
uwTick_LED1=uwTick;
static unsigned char dsled,i;
dsled|=(0x01<<i); //用逻辑左移<<实现移位操作
i=(i+1)%8; //每八次一个循环
LED_Disp(dsled);
dsled=0x00; //熄灭之前点亮的灯
}
二、LCD
1.初始化
void LCDInit()
{
LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
}
2.屏幕显示
u8 page=0;
char LineString[30];
void LCD_Pro()
{
switch(page)
{
case 0:{
sprintf(LineString," Hello World! ");
LCD_DisplayStringLine(Line5,(u8 *)LineString);
break;
}
//----------------------------//普通显示
case 1:{
LCD_SetBackColor(Blue);
sprintf(LineString," Hello World! ");
LCD_DisplayStringLine(Line5,(u8 *)LineString);
LCD_SetBackColor(Black);
sprintf(LineString," ni hao! ");
LCD_DisplayStringLine(Line6,(u8 *)LineString);
break;
}
//----------------------------//让某一行高亮显示
}
}
有时候可能会让某一行的某一个字符高亮显示:
LCD_DisplayChar(Line4,320-16*5,'H');
LCD_DisplayChar(Line4,320-16*6,'e');
LCD_SetBackColor(Blue);
LCD_DisplayChar(Line4,320-16*7,'l');
LCD_DisplayChar(Line4,320-16*8,'l');
LCD_SetBackColor(Black);
LCD_DisplayChar(Line4,320-16*9,'o');
/**
如果需要高亮一行中的某些字符,需要用到LCD_DisplayChar()
第一个变量为行,第二个变量为列,所用LCD屏一共20列,一列占320/16=20
最左边是320,所以第i列为320-16*i
第三个变量为ASCII码字符
**/
补充:使用自定义字符显示
/* '!' */
u16 string[24]={0x0000, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0000, 0x0000,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}
/*这块LCD屏幕一个小格子的大小为24*16(高*宽)*/
LCD_DrawChar(Line5,320-16*i,string);
LCD不同方向显示(文章参考)
//--------------正向显示------------------//
LCD_WriteReg(R1 , 0x0000); //从上往下
LCD_WriteReg(R96 , 0x2700); //从左往右
//--------------左右翻转-----------------//
LCD_WriteReg(R1 , 0x0000); //从上往下
LCD_WriteReg(R96 , 0xA700); //从右往左
//--------------------------------------//
LCD_WriteReg(R1 , 0x0100); //从下往上
LCD_WriteReg(R96 , 0x2700); //从左往右
//--------------180°倒向显示-----------------//
LCD_WriteReg(R1 , 0x0100); //从下往上
LCD_WriteReg(R96 , 0xA700); //从右往左
三、ADC
1.ADC单通道
u32 ADCtick=0;
float voltage,voltage_temp=0;
void ADC_Pro()
{
if(uwTick-ADCtick<100) return; //个人认为100ms采集一次比较合适
ADCtick=uwTick;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
for(int i=0;i<10;i++)
voltage_temp+=HAL_ADC_GetValue(&hadc2)*3.3f/4095.0f;
voltage=voltage_temp/10; //采取10个值求平均
voltage_temp=0;
}
可能有些板子上的ADC值达不到3.3V,在初始化中加入ADC校准:
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);
2.ADC多通道
cubemx配置:
先开启13、17的单通道,选择单次转换模式,再选择通道数为2,采样周期选择最大640.5Cycles就行
拓展板上将PA4、PA5分别与AO1、AO2连接
void ADC_Pro()
{
if(uwTick-ADCtick<100) return;
ADCtick=uwTick;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50); //最好加上
voltage=HAL_ADC_GetValue(&hadc2)*3.3f/4095.0f;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
voltage1=HAL_ADC_GetValue(&hadc2)*3.3f/4095.0f;
}
3.ADC按键
cubemx中配置PA5为ADC输入即可
u8 keyflag=0;
unsigned char ADC_Key()
{
static u8 keyValue,keystate;
HAL_ADC_Start(&hadc2);
ADCValue=HAL_ADC_GetValue(&hadc2);
switch(keystate)
{
case 0:if(ADCValue<3990) keystate=1;break; //按键按下
case 1:{
if(ADCValue<200) keyValue=1;
else if(ADCValue<800) keyValue=2;
else if(ADCValue<1400) keyValue=3;
else if(ADCValue<2000) keyValue=4;
else if(ADCValue<2600) keyValue=5;
else if(ADCValue<3200) keyValue=6;
else if(ADCValue<3800) keyValue=7;
else if(ADCValue<3990) keyValue=8;
keystate=2;
break;
}
case 2:{
if(ADCValue>3990) {keystate=0;keyflag=1;} //按键松开
break;
}
}
return keyValue;
}
四、串口
cubemx配置如下:
1.串口发送
HAL_UART_Transmit(&huart1,(u8 *)"hello world",strlen("hello world"),0xff);
2.串口接收
char rx_data[30];
u8 rx_point=0;
u8 rx_temp;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart1)
{
rx_data[rx_point++]=rx_temp;
HAL_UART_Receive_IT(&huart1,&rx_temp,1);
}
}
void UART_Rx()
{
if(rx_point)
{
if(rx_data[0]=='1')
HAL_UART_Transmit(&huart1,(u8 *)"hello world",strlen("hello world"),0xff);
memset(rx_data,0,30);
rx_point=0;
}
}
main函数中:
HAL_UART_Receive_IT(&huart1,&rx_temp,1); //初始化时调用一次即可
if(rx_point)
{
temp=rx_point;
HAL_Delay(1);
if(temp==rx_point)
UART_Rx();
}
补充:
(1)memset的使用
#include <string.h>
//**可以用于对数组以及结构体快速清零**//
/*
param1:数组名称
param2:通常为0,进行清零操作
param3:数组大小
*/
memset(rx_data,0,30); //对数组清零
memset(&car[location],0,sizeof(car[location])); //对结构体清零
详细的用法可以参考这篇博客
(2)字符串扫入
typedef struct
{
char type[5]; //定义字符数组大小时比实际字符长度大1,用于存放“\0”结束符
char label[5];
char time[13];
}Car_F;
sscanf(rx_data,"%4s:%4s:%12s",car_temp.type,car_temp.label,car_temp.time);
五、RTC
cubemx配置:
代码:
#include "rtc.h"
RTC_TimeTypeDef H_M_S_Time;
RTC_DateTypeDef Y_M_D_Date; //分别定义两个结构体
HAL_RTC_GetTime(&hrtc,&H_M_S_Time,RTC_FORMAT_BIN); //注意格式为BIN
HAL_RTC_GetDate(&hrtc,&Y_M_D_Date,RTC_FORMAT_BIN);
/*这里两个必须都要调用而不能只调用一个*/
sprintf(LineString," Time:%2d:%2d:%2d",
H_M_S_Time.Hours,H_M_S_Time.Minutes,H_M_S_Time.Seconds);
LCD_DisplayStringLine(Line5,(u8 *)LineString);
sprintf(LineString," Date:%2d:%2d:%2d",
Y_M_D_Date.Year,Y_M_D_Date.Month,Y_M_D_Date.Date);
LCD_DisplayStringLine(Line4,(u8 *)LineString);
六、EEPROM
#include "i2c_hal.h"
I2CInit(); //注意在main函数中调用进行初始化
void I2C_WriteByte(u8 address,u8 data)
{
I2CStart();
I2CSendByte(0xA0); //0xA0为写指令
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
HAL_Delay(5); //延时5ms防止写入不完全
}
unsigned char I2C_ReadByte(u8 address)
{
u8 data;
I2CStart();
I2CSendByte(0xA0); //先用写指令建立与给定地址的连接
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CStart();
I2CSendByte(0xA1); //再用0xA1读指令读出地址中的数据
I2CWaitAck();
data=I2CReceiveByte();
I2CWaitAck();
I2CStop();
return data;
}
/*该24C02的最大地址为0xFF*/
注意:
如果是V6编译器,记得把代码优化给取消掉,不然读出来始终是255
如果需要赋予初值:
void EEPROM_process(void)
{
if(EEPROM_read(0x99)!=0xaa) //随便定义
{
EEPROM_write(0x99,0xaa);
EEPROM_write(0x01,10); //设定初值
EEPROM_write(0x02,10); //设定初值
EEPROM_write(0x03,10); //设定初值
EEPROM_write(0x04,10); //设定初值
}
else
{
X_rep=EEPROM_read(0x00);
Y_rep=EEPROM_read(0x01);
X_price=EEPROM_read(0x02);
Y_price=EEPROM_read(0x03);
}
}
代码来源于https://blog.csdn.net/dijddnd/article/details/129820235
七、按键
1.短按键
u32 Keytick=0;
u8 temp1[4]={1,1,1,1},temp2[4]={1,1,1,1};
u8 key[4]={0};
void Key_Disp()
{
if(uwTick-Keytick<20) return; //20ms扫描除按键抖动
Keytick=uwTick;
for(int i=0;i<4;i++)
{
temp2[i]=temp1[i];
switch(i)
{
case 0:temp1[0]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);break;
case 1:temp1[1]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);break;
case 2:temp1[2]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);break;
case 3:temp1[3]=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);break;
}
if(temp1[i]==1&&temp2[i]==0) //读取上升沿
key[i]=1;
}
}
void Key_Pro()
{
Key_Disp();
/*-------按键1*---------/
if(key[0]==1){
key[0]=0;
}
/*-------按键2*---------/
if(key[1]==1){
key[1]=0;
}
/*-------按键3*---------/
if(key[2]==1){
key[2]=0;
}
/*-------按键4*---------/
if(key[3]==1){
key[3]=0;
}
}
2.长按键
u8 key_count[4]={0};
u8 key_long[4]={0};
void Key_Disp()
{
if(uwTick-Keytick<20) return;
Keytick=uwTick;
for(int i=0;i<4;i++)
{
temp2[i]=temp1[i];
switch(i)
{
case 0:temp1[0]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);break;
case 1:temp1[1]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);break;
case 2:temp1[2]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);break;
case 3:temp1[3]=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);break;
}
if(temp1[i]==1&&temp2[i]==0)
key[i]=1;
}
if(temp1[0]==0)
key_count[0]++;
else
key_count[0]=0;
if(key_count[0]>50) //超过1s为长按键
{
key_count[0]=0;
key_long[0]=1;
}
}
void Key_Pro()
{
Key_Disp();
if(key[0]==1)
{
if(key_long[0]==0)
//短按键
else{
//长按键
key_long[0]=0;
}
key[0]=0;
}
}
3.双击
if(temp1[0]==1&&temp2[0]==0)
{
uwTick_temp2=uwTick_temp1;
uwTick_temp1=uwTick;
if(firstClick==0)
firstClick=1;
else
{
if(uwTick_temp1-uwTick_temp2<200)
{
firstClick =0;
//双击
}
else
{
//单击
}
}
}
else
{
if(firstClick==1&&uwTick - uwTick_temp1 > 200)
{
//单击
firstClick=0;
}
}
八、数码管
cubemx配置:
代码:
#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)
u8 seg_buff[3];
u8 seg_display[]={
// 0 1 2 3 4 5 6 7 8 9
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Seg_Disp()
{
for(int i=0;i<8;i++)
{
if(seg_buff[2]&(0x80>>i)) SER_H; //因为是移位寄存器,所以高位要先行
else SER_L;
SCK_L; //给上升沿进行移位操作
SCK_H;
}
for(int i=0;i<8;i++)
{
//此处不要用"==1"来判断
if(seg_buff[1]&(0x80>>i)) SER_H;
else SER_L;
SCK_L;
SCK_H;
}
for(int i=0;i<8;i++)
{
if(seg_buff[0]&(0x80>>i)) SER_H;
else SER_L;
SCK_L;
SCK_H;
}
RCK_L; //最终给一个上升沿进行输出
RCK_H;
}
void Seg_Pro()
{
seg_buff[2]=seg_display[0];
seg_buff[1]=seg_display[1];
seg_buff[0]=seg_display[2];
Seg_Disp();
}
注意:1、在拓展版上要将PA1、PA2、PA3分别通过跳线帽接上SER、RCK、SCK
2、u8 seg_display[]可以用stc-isp得到
九、光敏电阻
原理: 把RP7(电位器)处得到的电压值与3处的电压值进行比较器输出得到DO.AO则直接进行adc采样再数模转换得到电压。
拓展板上分别将PA3、PA4连接到TRDO、TRAO
void LDR_Pro()
{
HAL_ADC_Start(&hadc2);
voltage=HAL_ADC_GetValue(&hadc2)*3.3f/4095.0f;
sprintf(LineString," V:%.1fV ",voltage);
LCD_DisplayStringLine(Line7,(u8 *)LineString);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3))
LCD_DisplayChar(Line8,320-16*8,'1'); //大于阈值显示1
else
LCD_DisplayChar(Line8,320-16*8,'0'); //小于阈值显示0
}
/*阈值可由RP7电位器控制*/
十、DS18B20
拓展板上将PA6与TDQ连接
float ds18b20_read(void)
{
float temper;
unsigned char low,high;
ow_reset(); //开启信号传输
ow_byte_wr(OW_SKIP_ROM); //跳过ROM(0xCC)
ow_byte_wr(DS18B20_CONVERT); //温度转化(0x44)
ow_reset();
ow_byte_wr(OW_SKIP_ROM);
ow_byte_wr(DS18B20_READ); //温度读取(0xBE)
low=ow_byte_rd();
high=ow_byte_rd();
temper=((high<<8)|low)*0.0625; //返回16位数据,且精度为0.0625
return temper;
}
/*需要在ds18b20.c中自行编写*/
#include "ds18b20.h"
ds18b20_init_x(); //ds18b20初始化
while(ds18b20_read()==85); //防止上电后的一段时间显示默认值85
十一、DHT11
返回数据格式:
温度整数部分(8位)+温度小数部分(8位)+湿度整数部分(8位)+湿度小数部分(8位)+校验位(8位)
void DHT11_Process()
{
if(uwTick - humTick < 500) return; //500ms读取一次
humTick = uwTick;
dht11_val = dht11_read();
}
sprintf((char*)display_buf,"T:%d.%d ",(dht11_val>>8)&0xff,dht11_val&0xff);//温度
LCD_DisplayStringLine(Line6 ,(unsigned char *)display_buf);
sprintf((char*)display_buf,"H:%d ",dht11_val>>24); //湿度
LCD_DisplayStringLine(Line7 ,(unsigned char *)display_buf);
注意:
DS18B20与DHT11编译时要用V5编译器
十二、PWM
1.单通道测量频率
u16 frq=0;
u32 ccr=0;
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2); //main函数初始化
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
ccr=TIM3->CNT;
TIM3->CNT=0; //一定要将CNT的值置零
frq=80000000/80/ccr;
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
}
}
2.测量占空比
(1)双通道
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2); //main函数初始化
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
{
ccr1=HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_2);
if(ccr1)
{
ccr2=HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_1);
TIM3->CNT=0;
frq=80000000/80/ccr1;
duty=(ccr2+1)*100/(ccr1+1);
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
}
}
}
}
(2)单通道
u16 ccr1,ccr2;
float frq;
float duty;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
{
if(tim3_state==0){
TIM3->CNT=0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);
tim3_state=1;
}
else if(tim3_state==1){
ccr2=HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_2);
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);
tim3_state=2;
}
else if(tim3_state==2){
ccr1=HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_2);
frq=80000000.0f/80.0f/(float)(ccr1+1);
duty=(float)(ccr2+1)/(float)(ccr1+1)*100.0f;
tim3_state=0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
}
}
}
(3)测双路PWM占空比
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1) //PA6
{
if(time_state[0]==0){
ccr1[0]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);
time_state[0]=1;
}
else if(time_state[0]==1){
ccr2[0]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
if(ccr2[0]>ccr1[0])
ccr_h[0]=ccr2[0]-ccr1[0];
else
ccr_h[0]=ccr2[0]+0xFFFF-ccr1[0];
ccr1[0]=ccr2[0];
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);
time_state[0]=2;
}
else if(time_state[0]==2){
ccr2[0]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
if(ccr2[0]>ccr1[0])
ccr_l[0]=ccr2[0]-ccr1[0];
else
ccr_l[0]=ccr2[0]+0xFFFF-ccr1[0];
duty[0]=(float)(ccr_h[0])/(float)(ccr_h[0]+ccr_l[0])*100.0f;
time_state[0]=0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
}
else if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2) //PA7
{
if(time_state[1]==0){
ccr1[1]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);
time_state[1]=1;
}
else if(time_state[1]==1){
ccr2[1]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
if(ccr2[1]>ccr1[1])
ccr_h[1]=ccr2[1]-ccr1[1];
else
ccr_h[1]=ccr2[1]+0xFFFF-ccr1[1];
ccr1[1]=ccr2[1];
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);
time_state[1]=2;
}
else if(time_state[1]==2){
ccr2[1]=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
if(ccr2[1]>ccr1[1])
ccr_l[1]=ccr2[1]-ccr1[1];
else
ccr_l[1]=ccr2[1]+0xFFFF-ccr1[1];
duty[1]=(float)(ccr_h[1])/(float)(ccr_h[1]+ccr_l[1])*100.0f;
time_state[1]=0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
}
}
}
/*
这里用差值来计算频率以及占空比,不用清零法是因为两个通道会互相影响
*/
小知识
#include "math.h"
float a;
a=fabs(a); //对浮点数取绝对值
int a=100;
printf("%X",a); //将a以16进制输出(大写字母)
printf("%x",a); //将a以16进制输出(小写字母)
printf("%#x",a); //在16进制数前面加上0x