一、问题:
之前用dwt测试了一下lcd刷新一次大概需要40-60ms,lcd放在main跑,只要这个50-60ms期间出现了任何中断就会出现噪点,4t测评出来是0分,所以lcd适合不能被打断的环节,可以临时关闭中断,但是有概率出现其他中断刚好被漏了,风险很大,有没有一个解决办法呢?
二、方案:
定时器就特别适合它,我只要开一个定时器优先级为1(串口的中断优先级设置为0!!!),不就解决被打断的问题了吗?我一般会选择定时器1来做这个事情,
我设置的20ms触发一次中断,在这个定时器的回调函数里面跑赛题上面的服务
这样就实现了一个简单的时间片。
有人会问了,你刚才不是说了吗?lcd刷新一次再这么也需要40ms,你给20ms的定时器咋跑?上一个中断还没跑完呢,下一个中断就触发了,不会乱跑吗?
完全不会有问题,我们设置定时器跑程序,目的就是写出一个不会被打断的main函数,并且一直循环。
就只解决一个lcd不出噪点的痛点吗?这个写法可没那么弱!
看到上面的key_judge没有,猜猜这是啥,里面有key_scan扫描状态
我们的机械抖动区间是多少毫秒呢?很垃圾的品牌也小于30ms!平时用的一般在10到20ms,我们这一轮程序需要多少ms呢?那些代码逻辑不会超过10ms,lcd最小40ms最大60ms,加起来的区间在50ms到60ms是吧?拿来给按键滤波简直完美,2次判断key的值的时间差只要不超过100ms,不可能出现漏判,并且响应还算比较灵敏只要不在里面放delay,不可能一个周期超过100ms,那我们的key要求的是啥呢?我见过最快的一套题是0.1s内反应,完美解决!
三、代码模板分享与讲解
1.key_scan
GPIO_TypeDef * key[4]={GPIOB,GPIOB,GPIOB,GPIOA}; uint8_t key_offset[4]={0x01,0x01<<1,0x01<<2,0x01}; uint8_t key_old_value[4]={0}; uint8_t key_new_value[4]={0}; void key_scan() { for(uint8_t i=0;i<4;i++) { key_old_value[i]=key_new_value[i]; key_new_value[i]=key[i]->IDR&key_offset[i]?1:0; } }可以把这几个key的端口和偏移量存起来,用for一把梭,
IDR这个寄存器是啥呢?就是输入捕获寄存器,可以用它来检测输入状态,我这个代码的意思是如果这个按钮被按下了,那就是0,没按下就是1
如何判断按钮刚按下呢?old_value是1,new_value是0,也就是刚按下的瞬间,
old_value是0,new_value是1,也就是刚释放的瞬间
old_value是0,new_value是0,也就是持续按住
void key_judge() { key_scan(); if(key_old_value[0]==1&&key_new_value[0]==0) { } else if(key_old_value[1]==1&&key_new_value[1]==0) { } else if(key_old_value[2]==1&&key_new_value[2]==0) { } else if(key_old_value[3]==1&&key_new_value[3]==0) { }在里面填入逻辑就行了,题目让干啥就在里面写啥
2.下面是ui_refresh(ui刷新)
uint8_t page_index=1;//初始化page_index,题目肯定会让默认上电后在哪个界面,那个界面就是index为1 void ui_refresh() { switch(page_index) { case 1: page1(); break; case 2: page2(); break; case 3: page3(); break; } }还是完形填空那一套,看题目有几个page(界面)就写几个,每写了一个小功能立马下进去看看,测试完成才往下走,这样你出错了,错误永远在你的上一步!!!!这一步是所有里面最重要的,并且最好是下载得越频繁越好,我一般写几句话就编译看看,然后下载进去测试,反正5个小时,这样做实际上3个小时不到就能做完。
3.adc_judge
float adc_value=0.0f; void adc_judge() { adc_value=ADC1->DR/65535.0*3.3;//读取出来,你们一般, //看你们设置的adc精度,你们一般用的12位除以4095.0就行了,我开了过采样是16位精度,需要除以65535.0 //这里放题目要求的任务,比如adc小于1v那就写个if放这里处理 HAL_ADC_Start_IT(&hadc1);//读取完成后记得再次打开 }4.led
(1)这个是我的led的驱动
void led_show(uint16_t temp) { HAL_GPIO_WritePin(le_GPIO_Port,le_Pin,0); GPIOC->ODR&=0x00ff; GPIOC->ODR|=~(temp<<8); HAL_GPIO_WritePin(le_GPIO_Port,le_Pin,1); HAL_GPIO_WritePin(le_GPIO_Port,le_Pin,0); }
我的le是PD2引脚,第一行你们写GPIOD,GPIO_PIN_2,0就行了,最后两行一样的,
ODR是输出寄存器,16个bit,我们的led在高八位,所以要偏移过去,
因为led板子上接的低电平点亮(这样能提高灌电流能力,也就是更亮)
所以GPIOC->ODR|=~(temp<<8); 对temp进行了取反,写起来更爽
0x01就是led1亮,聪明的你会问led8咋办呢?
0x01<<7!
(2)这个是led_judge
void led_judge() { static uint8_t led_out=0x00;//头 if(MODE==1){led_out|=0x01;}else {led_out&=~(0x01);} if(LOCK==1){led_out|=0x01<<1;}else {led_out&=~(0x01<<1);} if(!(adc_value<PU_yes&&PD_yes<adc_value)){led_out|=0x01<<2;}else {led_out&=~(0x01<<2);} led_show(led_out);//尾 }利用static创建一次后,后续不会再次创建的原理(也就是 static uint8_t led_out=0x00;这句话不管函数执行多少次,只有第一次执行了,其他时候直接绕过)
我们可以写一个流水线,先写个头,通过逻辑加工后
在尾巴送给led
5.dwt!!!
void DWT_Init(void) { CoreDebug->DEMCR |= (1UL << 24); // 开启调试时钟 DWT->CYCCNT = 0; // 清零计数器 DWT->CTRL |= (1UL << 0); // 使能 CYCCNT }为什么我会强调这个东西呢?
定时器我们设置的20ms,但是跑一次需要大于50ms,那么用定时器计数来实现一个倒计时是不是不太准确啊?我之前也遇到过这个坑,要求我3s后跳转到另外一个界面,我等了10来秒才跳转,
有没有办法解决呢?
dwt就是破局之法
dwt这玩意是独立计时的,我们开机的时候把dwt_init放在main里面初始化一次就行,
后续使用的时候像我这样写
//80mhz就是80后面跟6个0,80mhz就是1秒来这么多个脉冲,我要定时1.5秒就只用把他乘以1.5,这里必须要用大于等于,不要等于,这个东西计时贼快,判断的时候总会大于的,我们执行周期是ms,它的计数周期可是纳秒级别 DWT->CYCCNT>=0;//刚刚开始使用的时候,把计数器清空,这个计数器是一个32位的,大概在42秒后就溢出了,我们的按键或者事件谁发神经长按或者等待大于半分钟啊 if(DWT->CYCCNT>=80000000*1.5) { LOCK=1; } else { LOCK=0; }