蓝桥杯嵌入式模板
迷点:ADC1采集PB12(R38)和PB14(mcp)后,R38调节不畅,注释getADC1_mcp()函数后,R38转换很快,估计采样周期不支持。:要用ADC1同时测R38和mcp电压时,只需要开启相应通道后,设置规则组转换数量为2,对相应的rank设置相应采样周期,编写代码即可。只需要把R37和R38对应ADC通道勾选上即可编写代码,注意ADC1还可以测mcp电压,在IIC通信中有补充。按键
目录
比赛时可以直接复制提供的液晶驱动程序项目,省去很多引脚配置
一.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');
效果图:


更多推荐



所有评论(0)