目录

一.LED灯操作

1.LD不闪函数

2.LD闪烁

1.闪烁亮度低

2.不闪,秒定时

3.闪,但只闪几次后灭

4.流水灯

5.呼吸灯

二.按键的使用

1.用延时实现按键单击

2.uwtick实现长短按和双击

3.用定时器实现按键功能

按键单击

按键长短按

按键长短按和单击

三.UART串口收发

1.串口发送数据

 2.串口接收数据

四.IIC通信

1.EEPROM和MCP读写时序

2.读写代码

3.复位或上电时数据处理

五.ADC电压采集

1.配置和代码

2.滤波处理

六.RTC时钟

七.PWM生成

1.配置,原理和代码

2..注意点

八.输入捕获

1.测定555产生的频率​编辑

2.用引脚测频率

九.LCD显示

1.页面切换和高亮行

2.同行闪烁

3.突出行字体显示

4.高亮字符

5.翻转屏幕


比赛时可以直接复制提供的液晶驱动程序项目,省去很多引脚配置

一.LED灯操作

1.LD不闪函数

板子上LD1--LD8需要使用PC8--PC15引脚,另外有锁存器引脚PD2(CubeMx中配置成output)

/*直接传入对应引脚和电平控制LED*/
void led_contrl(uint16_t GPIO_Pin,GPIO_PinState PinState)
{
    HAL_GPIO_WritePin(GPIOC,GPIO_Pin,PinState );
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET );
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET );
}
/*直接对位操作,0x01位led1亮,0x00为全灭*/
void led_pro(uint8_t ucled)                    //一般用这个
{
    HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOC,ucled<<8,GPIO_PIN_RESET);
    HAL_Delay(1);                    //确保锁存器数据稳定
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
/*传入n=1,则为Pin8,类推*/
void led_proc(uint8_t n)                   
{
    HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOC,GPIO_PIN_7<<n,GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

2.LD闪烁

以第十四届省赛为例

uint8_t lednum = 0;
u32 led_tick = 0;
void led_pro(void)
{
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
				//处于第一个页面					   //LD1不闪
	if(page_index == 1)HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
		            //其他页面
	else HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);		
				
									//占空比锁定	//LD3不闪
	if(adc_lock == 1)HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_RESET);     
                                   //占空比未锁定
	else HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_SET);					
	
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
	
	if(uwTick - led_tick < 100)        //LD2以0.1s闪烁
		return;		
	led_tick = uwTick;
	
	if(mode_lock == 1)lednum |= 0x02;			
	else lednum &= ~0x02;
		
	led_disp(lednum);
}

1.闪烁亮度低

遇到LED闪烁亮度很低的问题,不过很好解决,只需要把GPIO的output level改成high,然后speed改成very high,最后在led_disp中加入1ms延时确保锁存器的数据稳定(重点),就可以解决

2.不闪,秒定时

如果是不闪,要求几秒后灭的话,可以配置一个定时器1ms,到达时间熄灭就好

3.闪,但只闪几次后灭

可以参考以下代码,来着第七届省赛

void Led_Disp(uint8_t ucled)
{
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,ucled << 8,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

u32 led_cnt[3] = {0};
uint8_t lednum = 0;
u32 led_tick = 0;
int led2_flag = 0;
int led3_flag = 0;
void led_contrl(void)
{	
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
	
	if(uwTick - led_tick < 200)
		return ;
	led_tick = uwTick ;
	
	led_cnt[0] ++;
	if(led_cnt[0] % 5 == 0)					//cnt等于5时
	{
		lednum ^= 0x01;						//运行指示灯
	}
	else lednum &= ~0x01;					//不等于5时
	
	if(led2_flag == 1)
	{
		led_cnt[1] ++;
		if(led_cnt[1] % 2 == 1)				//1,3,5,7,9亮,闪5下
		{
			lednum ^= 0x02;					//液位变化指示灯
		}
		else lednum &= ~0x02;
		if(led_cnt[1] == 10)
		{
			led2_flag = 0;
			led_cnt[1] = 0;
		}
	}

	if(led3_flag == 1)
	{		
		led_cnt[2] ++;
		if(led_cnt[2] % 2 == 1)				//1,3,5,7,9亮,闪5下
		{
			lednum ^= 0x04;					//通讯指示灯
		}
		else lednum &= ~0x04;
		if(led_cnt[2] == 10)
		{
			led3_flag = 0;
			led_cnt[2] = 0;
		}			
	}

	Led_Disp(lednum);
}

4.流水灯

待续

5.呼吸灯

待续

二.按键的使用

在CubeMx中需要配置以下引脚为input。按键又分单击,双击和长按,代码较多。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

1.用延时实现按键单击

缺点:编写代码时,容易把程序卡死,而且难排查出。CPU会在延时的时候什么都没干,浪费CPU资源

uint8_t key_scan(void)            //单击
{
	if(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_0 )==RESET )
	{
		HAL_Delay (10);
		while(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_0 )==RESET );
		HAL_Delay (10);
		return 1;
	}
	if(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_1 )==RESET )
	{
		HAL_Delay (10);
		while(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_1 )==RESET );
		HAL_Delay (10);
		return 2;
	}
	if(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_2 )==RESET )
	{
		HAL_Delay (10);
		while(HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_2 )==RESET );
		HAL_Delay (10);
		return 3;
	}
	if(HAL_GPIO_ReadPin (GPIOA,GPIO_PIN_0 )==RESET )
	{
		HAL_Delay (10);
		while(HAL_GPIO_ReadPin (GPIOA,GPIO_PIN_0 )==RESET );
		HAL_Delay (10);
		return 4;
	}
	else return 0;
}

2.uwtick实现长短按和双击

                                 非常推荐的方法

uint8_t key_value,key_down,key_up,key_old = 0;
void key_scan(void)
{
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == RESET)
		key_value = 1;
	else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == RESET)
		key_value = 2;
	else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == RESET)
		key_value = 3;
	else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET)
		key_value = 4;
	else key_value = 0;
	
	key_down = key_value & (key_value ^ key_old);
	key_up = ~key_value & (key_value ^ key_old);
	key_old = key_value;
}

u32 key_tick = 0;				//用于消抖
u32 key_long_tick = 0;			//长按计时
u32 double_tick = 0;			//双击计时
u8 key3_cnt = 0;				//记录按键3按下的次数
int i = 0;						//长按时一直++,用于长按操作
void key_pro(void)
{
	if(uwTick - key_tick < 20)	 //20ms消抖
		return;
	key_tick = uwTick;
	key_scan();
	
	//单击
	if(key_down == 1)				//第一个按键
	{
		//实现单击,单击的操作
		LCD_DisplayStringLine(Line6,(u8 *)"			one		");
	}
	
	//长按
	if(key_down == 2)			//第二个按键
	{
		key_long_tick = uwTick;			//开始计时
	}
	if(key_value == 2 &&( uwTick - key_long_tick) > 700)        //按下中判定
	{
		//实现长按,长按的操作
		i ++;
		sprintf(buf," i=%d   long   ",i);
		LCD_DisplayStringLine(Line7,(u8 *)buf);
	}
	else if(key_up == 2 && uwTick - key_long_tick <= 700)  //松手判定,不然长按前会触发短按
	{
		//短按的操作
		LCD_DisplayStringLine(Line7,(u8 *)"           short   ");
	}
	
	//双击						//第三个按键
	if(key_down == 3 && key3_cnt == 0)		//代表第一次按下
	{
		key3_cnt = 1;						//记录第一次按下
		double_tick = uwTick;				//记录按下的时刻
	}
	else if(key3_cnt ==1)
	{
		if(key_down == 3 && (uwTick - double_tick) < 300)
		{
			key3_cnt = 0;
			//双击实现,双击的操作
			LCD_DisplayStringLine(Line8,(u8 *)"			two		");
		}
		else if(uwTick -double_tick >=  300)
		{
			key3_cnt = 0;
			//单击操作
			LCD_DisplayStringLine(Line8,(u8 *)"			one		");
		}
	}
}

3.用定时器实现按键功能

以下是用定时器实现按键单击.长按和双击功能。用定时器实现记得在CubeMx中需要把PSC配置为80-1,Period配置为10000-1,打开中断,具体如下。而且在main中使用HAL_TIM_Base_Start_IT(&htim2);打开中断。10ms进入一次中断,又可以消抖

struct keys 				//结构类型	//写在.h文件好些					//按键的结构体
{
  char judge_sta;				//判断硬件的状态,0表示按下,1表示确认按下,2表示对按键功能判断
  bool key_sta;						//读到低电平0表按下,1则反之        //原子类型
  uint8_t key_shuangclick_sta;		//0表示单击,1表示双击
  bool key_shuangclick_flag;		//双击的标志位
  bool key_chang_flag;				//长按键的标志位,记得清除		
  bool key_one_flag;				//单击(短按)
  uint16_t key_shuangclick_time;	//两次按下的时间间隔
  uint16_t key_time;				//记录按键按下到松开的时间  
};

按键单击

按键所需的结构体变量。实现单击功能只需要用到judge_sta,key_sta和key_one_flag

struct keys key[4] = {0};        //定时器实现按键单击
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM2)
	{
		key[0].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_0 );
		key[2].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_1 );
		key[3].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_2 );
		key[4].key_sta  = HAL_GPIO_ReadPin (GPIOA,GPIO_PIN_0 );
		for(int i = 0;i<4;i++)
		{
			switch(key[i].judge_sta )
			{
				case 0:
				{
					if(key[i].key_sta == 0)
					{
						key[i].judge_sta = 1;
					}
				}break;
				case 1:
				{
					if(key[i].key_sta ==0)
					{
						key[i].key_one_flag = 1;
						key[i].judge_sta = 2;
					}
					else key[i].judge_sta = 0;
				}break;
				case 2:
				{
					if(key[i].key_sta == 1)
					{
						key[i].judge_sta = 0;
					}
				}break;
			}
		}		
	}
}

按键长短按

实现单击和长按功能除了key_shuangclick_flag和key_shuangclick_time不用,其他都用到

struct keys key[4] = {0};            //实现按键的单击和长按功能
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM2)
	{
		key[0].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_0 );
		key[2].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_1 );
		key[3].key_sta  = HAL_GPIO_ReadPin (GPIOB,GPIO_PIN_2 );
		key[4].key_sta  = HAL_GPIO_ReadPin (GPIOA,GPIO_PIN_0 );
		for(int i = 0;i<4;i++)
		{
			switch(key[i].judge_sta )
			{
				case 0:
				{
					if(key[i].key_sta == 0)
					{
						key[i].judge_sta = 1;
					}
				}break;
				case 1:
				{
					if(key[i].key_sta ==0)
					{
						key[i].judge_sta = 2;
					}
					else key[i].judge_sta = 0;
				}break;
				case 2:              /*只设置长短按功能*/
				{
					if(key[i].key_sta==1)
					{
						key[i].judge_sta = 0;
						if(key[i].key_time<70)
						{
							key[i].key_one_flag = 1;				//单击
						}
					}
					else
					{
						key[i].key_time++;
						if(key[i].key_time>70)
						{
							key[i].key_chang_flag=1;				//长按
						}
					}
				}break;		
			}		
		}
	}
}

按键长短按和单击

实现单击,长按和双击则都用(三sta,三flag和两time)

/*只设定一个按键为长短按和双击的话就不用循环
结构
*/
struct keys key[4]={0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance==TIM2)
  {
    key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
    key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
    key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
    key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
    
    for(char i=0;i<4;i++)
    {
      switch (key[i].judge_sta)
      {
        case 0:								//硬件按下
        {
          if(key[i].key_sta==0)
          {
            key[i].judge_sta=1;
            key[i].key_time=0;					
          }
        }
        break;
        case 1:								//确认硬件按下并且通过定时器10ms的检测消抖
        {
          if(key[i].key_sta==0)
          {
           key[i].judge_sta=2;   
          }
          else
          {
            key[i].judge_sta=0;
          }
        }
        break;
        case 2:										//对按键的功能判断
        {
          if((key[i].key_sta==1)&&(key[i].key_time<70))		//按键松开并且时间小于70ms
          {
			   key[i].judge_sta=0;						  //松开后一定要置零
            if(key[i].key_shuangclick_sta==0)			//0未双击
            {
              key[i].key_shuangclick_sta=1;				//1双击
              key[i].key_shuangclick_time=0;            
            }
            else										
            {
			  key[i].key_shuangclick_sta=0;
              key[i].key_shuangclick_flag=1;			//双击的标志位             
            }           
          }
		  
         else if((key[i].key_sta==1)&&(key[i].key_time>=70))     //按键松开并且时间大于70ms
          {			
            key[i].judge_sta=0;							//松开后一定要置零			
          }
		  
          else											//按键未松开
          {
			key[i].key_time++;
            if((key[i].key_time>=70))
            {
              key[i].key_chang_flag=1;				//长按键的标志位
            }         
          }
        }
        break;       
      }
	  
      if(key[i].key_shuangclick_sta==1)
      {
        key[i].key_shuangclick_time++;
        if(key[i].key_shuangclick_time>35)
        {
          key[i].key_one_flag=1;					//单击的标志位
          key[i].key_shuangclick_sta=0;
        }
      }
    }
	
  }
}

三.UART串口收发

需要在CubeMx中对串口1(PA9和PA10)进行如下配置:波特率,打开中断,工作模式,其他默认

1.串口发送数据

int fputc(int ch,FILE *p)				//printf的重定向,UART发送,只需要调用printf即可
{
	uint8_t c = (uint8_t)ch;
	HAL_UART_Transmit(&huart1,&c,1,50);
	return ch;
}

 2.串口接收数据

以第六届省赛为例。需要手动使用HAL_UART_Receive_IT(&huart1,&rxdata,1);开启接收中断,一次只能接收一个数据,不然会乱码

#define RX_BUF_SIZE 64
#define RX_TIMEOUT 100  //100ms延时

uint8_t rx_pointer = 0;
uint8_t rx_data;
uint8_t rx_buf[RX_BUF_SIZE];
uint32_t last_rx_time = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(rx_pointer < RX_BUF_SIZE-1){
    rx_buf[rx_pointer++] = rx_data;
    last_rx_time = uwTick; // 更新最后接收时间
  }
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
 
//main.c的while(1)中
if(rx_pointer!=0)//有数据到来
	  {
		  int temp = rx_pointer;		//判断数据接收正确
		  if(temp == rx_pointer) uart_rx_proc();        //也可以直接调用这个函数
	  }


void uart_rx_pro(void)
{
	// 超时处理
    if((uwTick - last_rx_time > RX_TIMEOUT) && rx_pointer >0)
	{
		if(rx_pointer >=6)
		{
		    if(rx_buf[0]=='k' && rx_buf[1]=='0' && rx_buf[2]=='.' &&             
             (rx_buf[3]>='1'|| rx_buf[3]<='9') && rx_buf[4]=='\\' && rx_buf[5]=='n')
			{
				printf("ok\n");
				eeprom_write(0x10,rx_buf[3]-'0');//将k值写入eeprom,注eeprom不能直接存入小数
                k = (rx_buf[3]-'0')/10.0;        //小数在这
			}
		}
    rx_pointer = 0;
    memset(rx_buf,0,sizeof(rx_buf));        //清空,为下一次接收做准备。要包含string.h
  }
}

优化效果:
        1.响应延迟从50ms降低到实时响应
        2.支持连续命令处理
        3.抗干扰能力提升(异常数据自动超时清除)
        4.缓冲区溢出风险完全消除

这里是优化后的代码,优化前的代码参考我的文章中第十五届省赛模拟题代码,在user.c中注释有解释

四.IIC通信

编写代码时需要把比赛提供的iic.c和iic.h文件导入自己的工程,该文件已经对GPIO引脚进行初始化。注意读是1,写是0。eeprom的7位地址加读写位为0xa1,0xa0。mcp是0x5F,0x5e

补充:要用ADC1同时测R38和mcp电压时,只需要开启相应通道后,设置规则组转换数量为2,对相应的rank设置相应采样周期,编写代码即可

1.EEPROM和MCP读写时序

2.读写代码

/********************I2C读写EEPROM************************/
/*设备地址位7位加读写位1或0(1010 A2 A1 A0 R/W)*/
uint8_t  eeprom_read(u8 address)
{      
	I2CStart();					//开启总线
	I2CSendByte (0xa0);		//联系芯片1010 0000,发送从机地址,读写位为0,表示即将写入
	I2CWaitAck ();				//等待应答
	I2CSendByte (address);		//写入地址
	I2CWaitAck ();				//等待应答
	
	/*第一轮通信写入*/
	I2CStart ();				//开启总线
	I2CSendByte (0xa1);		//联系芯片1010 0001,发送从机地址,读写位为1,表示即将读取
	I2CWaitAck ();				//等待应答
	u8 data = I2CReceiveByte ();	//接收返回值
	I2CSendNotAck ();				//发送非应答信号
	I2CStop ();
	
	return data;	
}	

void eeprom_write(unsigned char addr,u8 dat)
{	
    I2CStart();			
    I2CSendByte (0xa0);			//联系芯片,1010 0000
    I2CWaitAck();
	I2CSendByte(addr);			//写入地址
	I2CWaitAck();
	I2CSendByte(dat);			//传入的数据
	I2CWaitAck();
	I2CStop();
}

 
/********************I2C设置/读取MCP4017的电阻值************************/
//读取可变电阻当前的阻值
uint8_t mcp_read(void)			//真实电阻值要*0.7874
{   
	uint8_t value; 
	I2CStart();
	I2CSendByte(0x5F);                  //联系芯片 0101 1110
	I2CWaitAck();
	
	value = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();

	return value;
}

//设置可变电阻当前的阻值   真实电阻值:0.7874 * value(k)
void mcp_write(uint8_t value)//0-127对应0-100k的阻值
{
	I2CStart();//开启总线
	I2CSendByte(0x5e);                //联系芯片 0101 1110
	I2CWaitAck();//等待应答
	I2CSendByte(value);  
	I2CWaitAck();//等待应答
	I2CStop();//停止总线
}

3.复位或上电时数据处理

以第六届省赛为例                   (第十三届省赛也有参考价值)

在mian函数,while前添加K_Init();即可


void Para_Init(void)
{
    /*因为开发板上电后存的值不是k*/	
	if(eep_read (123) != 123)  //随便读一个地址,读一个数,只要不等,就代表第一次写		
	{
		eep_write(123,123);		//不等于就写进去
		HAL_Delay(2);			//读写都要加一点延时,不然读或写都进不去
		k = 0.1;				//写了之后将k = 0.1后写进相应地址
		eep_write(0x10,k * 10);	//不能存小数
	}
	else k = eep_read(0x10)/10.0;		//不是第一次写。等于则让k等于第一次写的
}

五.ADC电压采集

1.配置和代码

 只需要把R37和R38对应ADC通道勾选上即可编写代码,注意ADC1还可以测mcp电压,在IIC通信中有补充

/********************ADC************************/
extern ADC_HandleTypeDef hadc2;		//hadc2这个结构体指针在其它文件,需要extern
uint16_t  getADC2(void)
{
	uint16_t R37_Value = 0;
	HAL_ADC_Start (&hadc2);
	R37_Value = HAL_ADC_GetValue (&hadc2);
	return R37_Value ;
}

extern ADC_HandleTypeDef hadc1;		//hadc1这个结构体指针在其它文件,需要extern
uint16_t  getADC1(void)
{
	uint16_t R38_Value = 0;
	HAL_ADC_Start (&hadc1);
	R38_Value = HAL_ADC_GetValue (&hadc1);
	return R38_Value ;          //计算时:double R38_volt = (R38_Value * 3.3)/4095
}                                               
uint16_t  getADC1_mcp(void)                      
{
	uint16_t mcp_Value = 0;
	HAL_ADC_Start (&hadc1);
	mcp_Value = HAL_ADC_GetValue (&hadc1);
	return mcp_Value ;                
}                                            

 注意计算时电压要是浮点数,0~3.3对应0~4096,线性关系

谜点:ADC1采集PB12(R38)和PB14(mcp)后,R38调节不畅,注释getADC1_mcp()函数后,R38转换很快,估计采样周期不支持

2.滤波处理

知识点来自第七届省赛真题。求平均即可

u32 adc_tick = 0;
double R37_Vlot;
uint32_t ADC_Value_Sum = 0;
uint32_t ADC_Value = 0;
extern ADC_HandleTypeDef hadc2;
void GetR37(void)
{
	HAL_ADC_Start(&hadc2);
	if(uwTick - adc_tick <1000)				//1秒钟采集电压
		return ;
	adc_tick = uwTick ;
	
	ADC_Value = HAL_ADC_GetValue(&hadc2);
	for(int i = 1;i <=10;i++)				//取平均值,相当于滤波(减小误差)
	{
		ADC_Value_Sum = ADC_Value_Sum + ADC_Value;
	}	
	R37_Vlot = ((ADC_Value_Sum/10.0) * 3.3)/4096.0;
	ADC_Value_Sum = 0;                                //一定要有
}

六.RTC时钟

主要是激活,时钟分频和设置时间

extern RTC_HandleTypeDef hrtc;
RTC_TimeTypeDef Time = {0};
RTC_DateTypeDef Date = {0};
char lcd_buf[30];
void rtc_pro(void)
{	
	HAL_RTC_GetTime(&hrtc,&Time,RTC_FORMAT_BIN);
	HAL_RTC_GetDate(&hrtc,&Date,RTC_FORMAT_BIN);	
	sprintf(lcd_buf ,"       %02d-%02d-%02d     ",Time.Hours ,Time.Minutes ,Time.Seconds);
	LCD_DisplayStringLine (Line0 ,(u8 *)lcd_buf);
}

七.PWM生成

1.配置,原理和代码

一般是生成频率和占空比可调的PWM波

    HAL_TIM_PWM_Start (&htim16,TIM_CHANNEL_1);
	TIM16 -> ARR = 10000-1;				//__HAL_TIM_SetAutoreload
	TIM16 -> CCR1 = 5000;					//__HAL_TIM_SetCompare

2..注意点

调整频率和占空比的时候应该避开浮点数计算,先乘后除,例如:(duty  * (1e6 /pa1_frq))/ 100,100原本是除在duty的位置,但在最后再除,因为先除的话duty/100 = 小数,例:duty/100 =0.3,这时0.3 * 1e6 /pa1_frq = 小数,但结果为整数,就会出现TIM2 -> CCR2 = duty/100  * (1e6 /pa1_frq) = 0 ;就会一直输出低电平。以第十四届省赛为例,第十一届省赛也是这样

extern TIM_HandleTypeDef htim2;
u32 pwm_tick = 0;
uint16_t pa1_frq = 4000;
void pwm_pro(void)
{
	if(uwTick - pwm_tick < 30)		//4000 / 50 = 80 ,80 * 30 = 2400,即2.4s可以升4000
		return;
	pwm_tick = uwTick;
	
	if(mode_sta == 0)
	{
		if(pa1_frq > 4000)pa1_frq -= 50;		//步进值为50
		else pa1_frq = 4000;
	}
	else if(mode_sta == 1)
	{
		if(pa1_frq < 8000)pa1_frq += 50;
		else pa1_frq = 8000;
	}
	
	TIM2 -> ARR = 1e6 / pa1_frq - 1;
	TIM2 -> CCR2 = (duty  * (1e6 /pa1_frq))/ 100;
	
	//调试用
//	if(1e6 / pa1_frq - 1 > 0)
//		LCD_DisplayStringLine(Line6,(u8 *)"          ok       ");
//	else 
//		LCD_DisplayStringLine(Line6,(u8 *)"          flase       ");
//	
//	if((duty  * (1e6 /pa1_frq))/ 100 > 0)
//		LCD_DisplayStringLine(Line7,(u8 *)"          ok       ");
//	else 
//		LCD_DisplayStringLine(Line7,(u8 *)"          flase       ");
	//调试用
//	sprintf(buf,"  ARR:%d  CCR2:%d   ",TIM2 -> ARR,TIM2 -> CCR2);
//	LCD_DisplayStringLine(Line8,(u8 *)buf);		
}

八.输入捕获

 

1.测定555产生的频率

extern TIM_HandleTypeDef htim16;
uint32_t frq,ccr;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim -> Instance == TIM16)
	{
		ccr = HAL_TIM_ReadCapturedValue(&htim16,TIM_CHANNEL_1) + 1;//实测总有1的误差
		TIM16 -> CNT = 0;
		frq = 1e6 / (ccr + 1);
		HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1);
	}

}

HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1);                //在main函数开启即可

2.测频率和占空比

注意:测占空比时需要用pwm做为信号,而不是用函数信号发生器作为信号(我问一起备赛的群友也这么说,函数信号发生器的占空比改不了,而我用函数信号发生器多次测占空比得到的效果也不理想)      

这里以PA6做为输入捕获引脚,测PA1输出PWM的频率和占空比

extern TIM_HandleTypeDef htim3;
uint32_t frq_pa1,duty_pa1;
uint32_t ccr1,ccr2;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim -> Instance == TIM3)
	{
		if(htim -> Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		{
			ccr1 = HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_1) + 1;//实测总有1的误差
			TIM3 -> CNT = 0;
			frq_pa1 = 1e6 / (ccr1 +1);
			duty_pa1 = ccr2 * 100/ (ccr1 + 1);
		}
		else if(htim -> Channel == HAL_TIM_ACTIVE_CHANNEL_2)
		{
			ccr2 = HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_2) + 1;//实测总有1的误差
		}
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
	}
}

         //主函数while前开启
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);

九.LCD显示

1.页面切换和高亮行

以第十届省赛为例。高亮主要用LCD_SetBackColor 函数,在对应行使用后,再用LCD_SetBackColor 函数切回原本颜色,如下

      int page_index = 1;
      int line_index = 1;

	  if(key_num ==1)			//页面切换
	  {
		 page_index ++;
         LCD_Clear(White);        //清屏
         if(page_index == 3)page_index = 1;
	  }

	  if(page_index ==1)			//Main页面
	  {	 
	       //显示的内容
	  }
	  else if(page_index ==2)				//Setting页面
	  {				  		 		 		  
		  if(key_num ==2)	//选择高亮行
		  {
		     line_index ++;
		     if(line_index >4)line_index =1;
		  }	
	  	  		  
		  if(line_index ==1) LCD_SetBackColor (Green );
		  //高亮行编写的代码
		  
		  LCD_SetBackColor (Black);	
	  
		  if(line_index ==2) LCD_SetBackColor (Green );
		  //高亮行编写的代码
		  
		  LCD_SetBackColor (Black);	  	  		  	 
	  }

2.同行闪烁

以第六届省赛为例

int light_flag = 0;             //时间更改时用于设置闪烁
uint32_t ctick = 0;        
void Time_Lineproc(void)
{
	if(uwTick - ctick < 200)			//不然闪太快了
		return ;
	ctick = uwTick;
	
	if(page_index == 2)
	{
		if(time_index == 1)
		{
			if(light_flag == 0)
			{
				light_flag = 1;
				sprintf(buf,"      %02d-%02d-%02",ctime.Hours,
                                        ctime.Minutes,ctime.Seconds );
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
			else 
			{
				light_flag = 0;
				sprintf(buf,"        -%02d-%02d     ",ctime.Minutes,ctime.Seconds );
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
		}
		
		if(time_index == 2)
		{
			if(light_flag == 0)
			{
				light_flag = 1;
				sprintf(buf,"      %02d-%02d-%02d     ",ctime.Hours,ctime.Minutes,ctime.Seconds );
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
			else 
			{
				light_flag = 0;
				sprintf(buf,"      %02d-  -%02d     ",ctime.Hours,ctime.Seconds);
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
		}
		if(time_index == 3)
		{
			if(light_flag == 0)
			{
				light_flag = 1;
				sprintf(buf,"      %02d-%02d-%02d     ",ctime.Hours,ctime.Minutes,ctime.Seconds );
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
			else 
			{
				light_flag = 0;
				sprintf(buf,"      %02d-%02d-       ",ctime.Hours,ctime.Minutes);
				LCD_DisplayStringLine (Line5 ,(u8 *)buf);
			}
		}
		if(time_index == 4)					//表示退出上报时间设置
		{
			sprintf(buf,"      %02d-%02d-%02d     ",ctime.Hours,ctime.Minutes,ctime.Seconds );
			LCD_DisplayStringLine (Line5 ,(u8 *)buf);
		}
	}	
}

3.突出行字体显示

与高亮行一样,用LCD_SetTextColor函数改一行的颜色后,再用LCD_SetTextColor函数改回原本颜色

	 LCD_SetTextColor(Green);
	 sprintf (buf ,"   Volt(R38):%.2fV   " ,R38_Volt);
     LCD_DisplayStringLine (Line1 ,( u8 *)buf );
	 LCD_SetTextColor(White);

4.高亮字符

解析:

1.开发板上的LCD是240RGB * 320分辨率,10行20列,一个字符实际高度为24 * 16,故可以这么算,320 - ( 16 * i ),i表示列,从左往右

2.一个字符占16个像素点,从右往左数第13和14个字符的位置高亮。     

      LCD_SetBackColor(Green);
      LCD_DisplayChar(Line4,16*14,'2');        //LCD_DisplayChar(Line4,320 - ( 16 * 6),'2');
      LCD_DisplayChar(Line4,16*13,'5');        //LCD_DisplayChar(Line4,320 - ( 16 * 7),'5');
      LCD_SetBackColor(White);

	  LCD_SetBackColor(Green);
	  LCD_DisplayChar(Line4,16*14,'2');    //LCD_DisplayChar(Line4,320 - ( 16 * 6),'2');
	  LCD_DisplayChar(Line4,16*13,'5');    //LCD_DisplayChar(Line4,320 - ( 16 * 7),'5');
	  LCD_SetBackColor(White);
	  
	  LCD_DisplayChar(Line4,16*12,':');
	  
	  LCD_DisplayChar(Line4,16*11,'0');
	  LCD_DisplayChar(Line4,16*10,'3');
	  
	  LCD_DisplayChar(Line4,16*9,':');
	  
	  LCD_DisplayChar(Line4,16*7,'0');
	  LCD_DisplayChar(Line4,16*6,'4');

效果图:

5.翻转屏幕

修改R1实现上下翻转,修改R96,实现左右翻转。下面通过一个按键实现屏幕翻转

	 key_num = key_scan();
	  if(key_num ==1)		
	  {
		  page_sta ++;
		  LCD_Clear(White);
		  if(page_sta > 1)page_sta = 0;
	  }

	  if(page_sta == 0)				//原代码,修改前的打印方式
	  {
		  LCD_WriteReg(R1,0x0000);  //从上往下打印
		  LCD_WriteReg(R96,0x2700);	//从左往右打印
	  }
	  else 							//修改后的打印方式
	  {
		  LCD_WriteReg(R1,0x0100);	//从下往上打印
		  LCD_WriteReg(R96,0xA700); //从右往左打印
	  }

	  LCD_SetBackColor(Green);
	  LCD_DisplayChar(Line4,320 - ( 16 * 6),'2');
	  LCD_DisplayChar(Line4,320 - ( 16 * 7),'5');
	  LCD_SetBackColor(White);
	  
	  LCD_DisplayChar(Line4,16*12,':');
	  
	  LCD_DisplayChar(Line4,16*11,'0');
	  LCD_DisplayChar(Line4,16*10,'3');
	  
	  LCD_DisplayChar(Line4,16*9,':');
	  
	  LCD_DisplayChar(Line4,16*7,'0');
	  LCD_DisplayChar(Line4,16*6,'4');

效果图:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐