单片机学习及扫盲
1. 按键监听逻辑及相关操作用处
static uint32_t key_all = 0,key_now = 0,clean = 0,count = 0,long_cnt = 0;
void key_check_utral(void){
static uint8_t key[3] = {0};
key[0] = !GPIO_GetBit(PORT1,PIN0);//Encoder_P
key[1] = !GPIO_GetBit(PORT1,PIN3);//Key_Power
key[2] = !GPIO_GetBit(PORT3,PIN0);//Key_func 功能键
//=====普通按键
key_now = ((key[0]<<key_Enc_P)|(key[1]<<Key_Power)|(key[2]<<Key_func));
if(key_now != key_all){
if(count++ > delay_t){ //消抖
count = 0; //消抖
if(((key_all&key_now)<key_all)&&clean == 0){//短按松开
parse_key(key_all); //短按发送
clean = 1; //松开清除
long_cnt = 0;
}
key_all = key_now;
}
}else if(key_all > 0&&clean == 0){ //长按计时
if(long_cnt++ > delay_long){ //长按触发
parse_key(key_all|func_Key_L); //长按松开发送
clean = 1; //长按松开清除
long_cnt = 0;
}
}
if(key_now == key_all) count = 0; //消抖
if(key_all == 0) clean = 0;
//=====旋转编码器
if(key_Enc_dirt == -1) parse_key(func_key_Enc_A);
if(key_Enc_dirt == 1) parse_key(func_key_Enc_B);
key_Enc_dirt = 0;
}
消抖:消抖的意义在于有时候由于硬件质量或解除的问题,可能出现按下按键出现电平反复波动的情况,由于单片机非常敏感,因此可能会有误读;所以消抖就是在刚刚执行完操作后一小段时间内不进行读取,等待电平稳定后再读取,故本质上是等待操作。
逻辑:第一要监听按键,第二需要区分长短按,第三对按键的长短按需要作出不同的处理。
监听通过函数每一次启动时都会检查端口是否有因为按下按键导致的信号,同时对其作位运算然后相与,这样即可判断究竟有多少按键被按下;
长短按的区分是通过两个变量:key_all与key_now实现,key_now是实时读取存储,key_all是存储副本(注:这样做的方法是因为检测是一行代码驱动的,同时每个函数在短时间内会被调用多次即while循环,因此在一个函数内通过检测按键按下多长时间来实现的这种方法是不现实的)。除了key_now key_all再另起一个clean、两个计时变量,clean用于控制发送命令后不会再次进入程序并发送,两个计时变量分别用于判断短按计时与长按计时(比较对象是宏定义的值);其实这个逻辑略有奇怪,但是由于clean的存在使得向按键判断函数发送后不会再次进入,因此实际运行不会产生影响;
按键的长短按不同反应其实是放在按键判断函数中进行的,发送的都是变量key_all中存储的值。
2. uint16变量以及对其作的运算
#define unsigned int uint16 意为无符号16位二进制,因此uint16定义的变量是一个二字节大小的整型,从该变量作转换是从地址里取出该变量然后放入乘法运算器中,然后作*4096/9.9的方法变换成十进制电压值。我昨天认为的缺了的那一步就是在乘法运算器中进行的,是由计算机进行识别然后作运算。
3. 通过旋转编码器实现对LED灯的亮度切换
首先是按键监听:监听顺时针逆时针后做不同的标志位设置;
不同的标志位在按键处理里会调用LED_LOOP函数,由于调用是根据标志位进行,有标志位则灯光切换,无标志位灯光不切换,因此,循环函数里根据红绿灯值会不停改变,以此实现灯光颜色的变化。
4. 指令的实现(以关机为例)
通过长按电源按键调出ui展示选项,点按电源选择勾选关机------>通过串口发送FF……校验位…结尾到FPGA;
FPGA通过串口读到数据后调用中断程序,首先暂停中断防止取数据后仍在接收;
然后将中断的数据存储部分赋值给新的数组(包括数据长度),通过该数组进行指令分析;
通过指令分析、switch case语句得到宏定义的几个指令(其实指令宏定义即一些数字)------>以关机为例,FPGA关闭快门、延时、然后通过串口发送POWEROFF给单片机;
单片机收到串口数据后(收到的数据应当是AA开头……只有一个字节是指令数据)分析(同上),得知是关机指令于是给芯片的引脚上高低电平来关机(串口发送同样是通过高低电平实现的数据传输)。
/************************************* 2022.11.09 *********************************************/
1. 6'h08是什么,意为6位二进制数据写成16位的形式。
2. 内核时钟频率和外围硬件时钟频率是不是有点区别啊,肯定有区别啊。
3. fclk是什么
4. 通读:首先设置时钟频率(这里的时钟频率和外围硬件时钟频率是否有区别);然后初始化ADC;再读值(里面中断我还不是很明白);后面三个值读取模拟数据的通道改了,这是为什么?
int main()
{
MD_STATUS status;
uint32_t i;
//-----------------------------------------------------------------------
// Init UART0 for retarget printf/scanf etc.
//-----------------------------------------------------------------------
#if 1
SystemCoreClockUpdate();
status = UART0_Init(SystemCoreClock, 19200);
if(status == MD_ERROR)
{
while(1);
}
printf("Hello, UART\n");
#endif
//-----------------------------------------------------------------------
// ADC Samples the voltage of Temperature Sensor
//-----------------------------------------------------------------------
ADC_Init(); /* ADC inital according to macro defines in Macro Definitions section */
avg_value = ADC_Converse(ADC_TEMPERSENSOR0, sizeof(get_value)/sizeof(get_value[0]), get_value);
#if 1
printf("ADC Value of Temperature Sensor:\n");
for(i=0; i<sizeof(get_value)/sizeof(get_value[0]); i++)
{
printf("Reference Voltage = %3.2fV, ADC Get Value = %04d, Voltage = %3.2fV\n", voltage, get_value[i], (double)get_value[i] * voltage / 4096);
}
#endif
//-----------------------------------------------------------------------
// ADC Samples the voltage of Internal reference voltage (1.45V)
//-----------------------------------------------------------------------
avg_value = ADC_Converse(ADC_INTERREFVOLT, sizeof(get_value)/sizeof(get_value[0]), get_value);
#if 1
printf("ADC Value of Internal reference voltage (1.45V):\n");
for(i=0; i<sizeof(get_value)/sizeof(get_value[0]); i++)
{
printf("Reference Voltage = %3.2fV, ADC Get Value = %04d, Voltage = %3.2fV\n", voltage, get_value[i], (double)get_value[i] * voltage / 4096);
}
#endif
//-----------------------------------------------------------------------
// ADC Samples the voltage of REGC (1.50V)
// Please connect REGC to P22 for SOP28 chip
//-----------------------------------------------------------------------
avg_value = ADC_Converse(ADC_CHANNEL_2, sizeof(get_value)/sizeof(get_value[0]), get_value);
#if 0
printf("ADC Value of REGC:\n");
for(i=0; i<sizeof(get_value)/sizeof(get_value[0]); i++)
{
printf("Reference Voltage = %3.2fV, ADC Get Value = %04d, Voltage = %3.2fV\n", voltage, get_value[i], (double)get_value[i] * voltage / 4096);
#endif
ADC_Stop();
}
通读讨论结果:首先并不是设置时钟频率,而是获取当前系统的主频(通常为64MHZ),获取到时钟频率后根据适配的波特率选择初始化哪个串口(这个用于输出到电脑上显示log),如果配置错误,就会进while循环卡住,这种情况下除非配置了看门狗,否则不能退出来,就算配置了看门狗,估计因为配置信息错误的原因也无法成功退出吧);
第二步初始化AD转换器没有问题,中断的处理得阅读用户手册,那一步与我的猜测不符,并不是因为中断向量置1了所以设备开始温度检测,而是中断向量置1后清零后就将该中断发送给中断向量表等来判断是否应该执行还是挂起(根据中断优先级);不对他说错了,clear函数是吧IF中断请求标志位置0了,不是把中断允许寄存器MK置0了,而且下一次循环也有这种重复操作, 所以我推测是MK对应的中断允许位就是1,然后发生中断请求后它就开始检测,但是检测完后需要AD转换也完成,因此有一个while等待AD转换中断位置1即结束,结束后就把这个AD转换结束的中断请求标志位置0,然后再进行读取AD转换结果的操作;
哦也不对,实际上是之前把中断MK位置1也就是不需要系统通过中断来处理这个数据读取,转而继续跑这个循环,等待他出现中断请求标志之后,就说明AD转换完成了,可以读取数据了,这时候再把它置0,因为下一次循环还需要检测到它为1时来告诉我们它AD转换完成了。
串口的发送也很有意思,串口发送数据前要配置很多寄存器用来保证数据的传输,真正数据位其实只有几个。
任务的执行时钟是通过systick完成的,系统主频似乎是1秒之内晶振振动多少次,用主频除1000得到1ms的时间,举例64mhz也就是晶振振动64000次执行一次中断来检测是否有任务需要执行,真的是太有意思了;
按键只有电源键通过中断完成,我还需要看硬件电路图来知道哪个口对应哪个口,他还给我看了UART的RX和TX配置,TX属于输出配置,RX属于输入配置,但是那个兼用功能还是没搞太明白,不过问题到也不大。
哦对了有一个很关键的东西,Px口对应Pxcfg,意思是cfg这个寄存器里写值来控制Px端口的功能。例程里KR2对应到P72,说明P72是电源按键,KRM2控制KR2信号;
GPIOButtonCtrlInit是配置一些基础电源信息,配置按键检测的只有KeyInit,但是PMC7、PU7、PM7分别是些啥呢(就是那些各种寄存器,模式控制mode control,上拉电阻up,mode模式选择);哦,NOP是啥也不做的意思,对齐代码用的。
硬件上会设置上电复位的操作,这样所有用不到的寄存器就都是复位值,然后程序再开始跑设计好的那些需要设置的寄存器值。哦对了,还可以看看单片机上电后是如何跑到main函数的,ltf跟我说的可以了解了解。
中断里面这个IF和MK是什么,哦还有这个子弹头的硬件叫啥;
IFm和IFmL的区别是不是IFm存储的是IFmL的地址?不是,一个是中断屏蔽标志位一个是中断请求标志位。
8位存储器操作指令和32位存储器操作指令指的是啥?指的就是操作的寄存器是多少位的吧。
5. %3.2f是小数点前至少输出三位,不够3位以空格填充,小数点后至少输出两位,不够就补0 ;%d是左对齐输出,%4d是4位右对齐输出,多出4位显示总共位数,不够4位以空格来补,%04d不够4位以0补齐。
6. hz是一秒内出现脉冲(或者完整振动的次数),所以主频64mhz指的就是一秒内时钟振荡器振动6400w次。
1115记录单片机开关机部分问题与后续要学习的东西:
1 中断向量表的设置地址会影响程序的执行,我还得看一下那个上电复位究竟是怎么运行到中断向量表的,这个还挺关键的感觉。以及原来程序也是从0x00开始烧写,然后读取也是这样执行,那还是蛮有意思的啊,原理代码最终也是转化成数据流存储在嵌入式开发平台的内存里然后运行。
上电复位;给单片机一个可用的堆栈范围(堆栈和堆、栈什么区别?);是否是在栈顶烧写向量表?还是初始位置烧写向量表?栈到底放哪儿了;执行复位指令,在复位指令里进行一个初始化函数(通常是厂家编写)以及_main函数然后跳转到C程序里的main函数部分。
2 有几个地方的任务会卡住程序的循环,估计就是哪儿哪儿跑不通了
3 应该先把log调通再做调试,log不通根本不知道问题出在哪里
4 原来那个keyInit其实也不是很需要用到
5 今天那个串口线TC好像是接反了
6 这个程序居然还有个引导程序,而且看门狗不知道这个板子上有没有,感觉应该有,还是得配一下这样就会防止跑飞。不过是不是可以最后配啊,因为万一一卡死就重置,那不就发现不了问题了么?
1116
1. 高速系统时钟指的就是X1时钟或外部主系统时钟,时钟总结部分还需要和ltf讨论。以及内联函数这个定义到底是干嘛的也不知道。然后之前attribute等一些扫盲也忘了记录。
2. 看门狗定时器以低速内部时钟振荡,通过选项字节设置对看门狗及内部时钟的调用。
1117
1. 对于单片机来说,运算核心和外围硬件使用的都是一个时钟,这个时钟可以是主系统时钟也可以是副系统时钟。主系统时钟可以由高速内部振荡器、X1振荡或者外部时钟三种来提供,副系统时钟可以通过X1T振荡或者外部时钟来提供。一个时钟指的应该就是用HZ换算得到的1/频率,比如1/20khz。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!