2023-11-30
1.两个灯闪烁出现了问题
解决方法:程序中定时器分频系数和想要设置的分频系数少一位
经验:LED灯闪烁出现问题很有可能是定时器分频系数或者重装载值因为大意敲错了
2.软件模拟IIC
这些是硬件IIC的输入输出口,软件IIC只要使用一个空闲的GPIO口就可以实现IIC的通信
野火给出如何读多个字节
(也就是判断是否是最后一个字节如果是就给非应答)
1 for (i = 0; i < _usSize; i++) 2 { 3 _pReadBuf[i] = i2c_ReadByte(); /* 读1个字节 */ 4 5 /* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */ 6 if (i != _usSize - 1) 7 { 8 i2c_Ack(); /* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */ 9 } 10 else 11 { 12 i2c_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ 13 } 14 }
3.宏定义
如果一个对象
(1)在程序中多次出现,而且后续可能会进行改动(一旦更改就会改好多地方)
(2)这个对象的语义不够明确
那么这个时候就应该考虑宏定义(个人习惯如果这个宏在本.c文件用其他文件也用那么就定义到.h文件,如果这个宏定义仅在本.c文件中使用那么就定义到.c文件)
有参宏的使用
如果一个函数
(1)在程序中多次出现,而且它的一部分形参每次出现的时候都相同
那么这个时候就可以使用有参宏,将那些每次函数出现的时候都有不同值的形参作为有参宏的"参"
其中BitAction将有参宏的参数强转为形参的类型
宏定义的缺点
(1)不方便移植,比如STM32向引脚写入电平的函数是GPIO_WriteBit,但是51单片机直接就是P10=0/1
(2)如果芯片的主频很快也就是高低电平的持续时间较短,外接设备可能跟不上这么快的主频,这就需要延长高低电平的持续时间
综上,可以采用函数封装的方式解决上述问题(如果一个函数经常使用,且形参基本相同,为了方便移植可以采用这种方法)
4.IIC实现检查设备上是否有某一从机的功能
也就是起始位+从机地址+写标志位+主机接收从机应答的程序+停止位,如果主机接收到应答代表有这个设备否则没有
5.寄存器地址的问题
如果一个设备的寄存器较多,我们可以将所有寄存器的宏定义写在一个.h文件里,然后要使用这些寄存器的文件包含这个.h文件就可以了
2023-12-04
时间片轮询软件框架要求不能有死循环,但是SHT20的芯片手册有这么一段话:
也就是传感器没有测量完成,我们会使用迭代一直等它完成,同时我也看别人的代码在这个地方加入了死循环一直等待,但是万一传感器出现了什么问题一直也不应答这对于时间片轮询是不允许的。
参考文章:STM32F103ZET6+IIC+SHT20温湿度传感_gpio模拟iic通信+sht20温湿度采样_辰小夏的博客-CSDN博客
但是我在读数据手册的时候读到了:测量时间和分辨率有关,温度的默认分辨率是14bit,湿度的分辨率默认是12bit,同时手册提供了一个表格
同时这个分辨率是可以通过寄存器设置的(见手册5.6节)
因此测量温度的时候我等待90ms,测量湿度的时候我等待35ms,等不到应答我就将错误上报给上位机
1 if(cmd==0xf3) 2 { 3 Delay_ms(90); 4 } 5 else 6 { 7 Delay_ms(35); 8 } 9 if(Myiic_SlaveAck()) 10 { 11 Myiic_Stop(); 12 return Slave_Error; 13 }
但是最终还是遇到了问题,现在不知道怎么解决,就是我无论延时多久程序都会进入第9行的if中,可是在等待的过程中程序中也没有动SDA呀,SDA的应答信号应该被保留吧,我将这个地方换成网上的使用While死循环等待应答就可以了
2023-12-06
IIC通信一个很有趣的现象
当我把外围设备和单片机SDA拔掉此时接收到的结果一直是0,不会上报SHT20 Errorr,当我把SCL的拔掉此时就会上报SHT20 Errorr,仔细想想这也合理,如果从机不连接SDA的话,SDA一直是低电平,应答信号也是低电平,那么主机会认为从机给了应答,只不过这个时候没有数据传输,因此一直是0
2023-12-07
OLED问题
- 汉字不能写在OLED_Printf里,否则显示的汉字是乱的
- 后面的%不起任何作用
- 必须在OLED初始化中加入清屏代码,因为OLED上电后显示是随机的,不加入清屏代码就会出现乱码,显示到屏幕上就是花屏
1 void OLED_Init(void) 2 { 3 OLED_GPIO_Init(); //先调用底层的端口初始化 4 5 /*写入一系列的命令,对OLED进行初始化配置*/ 6 OLED_WriteCommand(0xAE); //设置显示开启/关闭,0xAE关闭,0xAF开启 7 8 OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率 9 OLED_WriteCommand(0x80); //0x00~0xFF 10 11 OLED_WriteCommand(0xA8); //设置多路复用率 12 OLED_WriteCommand(0x3F); //0x0E~0x3F 13 14 OLED_WriteCommand(0xD3); //设置显示偏移 15 OLED_WriteCommand(0x00); //0x00~0x7F 16 17 OLED_WriteCommand(0x40); //设置显示开始行,0x40~0x7F 18 19 OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常,0xA0左右反置 20 21 OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常,0xC0上下反置 22 23 OLED_WriteCommand(0xDA); //设置COM引脚硬件配置 24 OLED_WriteCommand(0x12); 25 26 OLED_WriteCommand(0x81); //设置对比度 27 OLED_WriteCommand(0xCF); //0x00~0xFF 28 29 OLED_WriteCommand(0xD9); //设置预充电周期 30 OLED_WriteCommand(0xF1); 31 32 OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别 33 OLED_WriteCommand(0x30); 34 35 OLED_WriteCommand(0xA4); //设置整个显示打开/关闭 36 37 OLED_WriteCommand(0xA6); //设置正常/反色显示,0xA6正常,0xA7反色 38 39 OLED_WriteCommand(0x8D); //设置充电泵 40 OLED_WriteCommand(0x14); 41 42 OLED_WriteCommand(0xAF); //开启显示 43 44 //OLED上电后显示内容是随机的,必须清一下屏幕,否则会出现乱码(花屏) 45 OLED_Clear(); 46 OLED_Update(); 47 }
- OLED如果想覆盖掉原来的内容显示其他内容的话(原内容不影响新内容的显示,一定要清一下显存数组,不然的话原数组的部分内容会留在显存数组里影响新内容的显示
1 void task1(void) 2 { 3 OLED_Clear();//清空显存数组 4 Humidity=SHT20_Read(RH_Measurement_NoHold); 5 if(Humidity==Slave_Error) 6 { 7 OLED_ShowString(0, 22, "SHT20 Error",OLED_8X16); 8 } 9 else 10 { 11 OLED_ShowChinese(0,0,"湿度:"); 12 OLED_Printf(34,0,OLED_8X16,"%05.2f",Humidity); 13 OLED_ShowChar(80,0,'%',OLED_8X16); 14 } 15 16 Temperature=SHT20_Read(T_Measurement_NoHold); 17 if(Temperature==Slave_Error) 18 { 19 OLED_ShowString(0, 39, "SHT20 Error",OLED_8X16); 20 } 21 else 22 { 23 OLED_ShowChinese(0,17,"温度:"); 24 OLED_Printf(35,17,OLED_8X16,"%05.2f",Temperature); 25 OLED_ShowChinese(80,17,"℃"); 26 } 27 OLED_Update(); 28 }
2023-12-09
程序逻辑问题
好多器件都用到了IIC协议,我总不能每个器件都写一遍IIC的流程吧,有什么方法可以解决呢,函数指针可以吗
使用循环实现延时可不可以使用delay函数替代呢,for循环的延时时间有待确定
2023-12-24我又重新反思了一下这里,其实这里不给延时也没什么影响,但是在从机地址+读标志位那里的响应略有不同得重写,具体查看文章IIC设备总结中“项目心得跳过来的看这里”
2023-12-10
IIC的应答改进
uint8_t Myiic_SHT20ACK(void) { uint8_t wait_time=0; while(Myiic_SlaveAck()) { wait_time++; if(wait_time>250) { Myiic_Stop(); return 1; } } return 0; }
如果while循环中是应答函数的话,每一次循环就会操作SCL和SDA,总体来看的话就会频繁的操作SCL和SDA,这样会影响效率,因此做如下改进:
1 uint8_t Myiic_SHT20WaitACK(void) 2 { 3 uint8_t wait_time=0; 4 Myiic_W_SDA(1); 5 Myiic_W_SCL(1); 6 while(Myiic_R_SDA()) 7 { 8 wait_time++; 9 if(wait_time>250) 10 { 11 Myiic_Stop(); 12 return 1; 13 } 14 } 15 Myiic_W_SCL(0); 16 return 0; 17 } 18 19 if(Myiic_SHT20WaitACK()==0)//实际使用的时候只会调用一次Myiic_SHT20WaitACK()函数,然后Myiic_SHT20WaitACK()一直循环等待,等待结束后,if条件执行完成开始执行if体中的代码或则不执行 20 { 21 }
鉴于while循环,每次循环都会执行一遍条件中的表达式或则函数,if循环只会执行一次条件中的表达式或则函数,函数如果返回值为真就执行体中的语句否则不执行(如果想要if语句也有等待的功能可以在条件中的函数加入等待语句),因此尽量使用if语句,如果必须使用while语句的话(如果在条件中加入一个函数,只有这个函数的返回值为某值才会退出while循环执行接下来的语句),就尽量使条件中的函数简洁,因此做出如下改动:
1 // 其他人的代码 2 // do 3 // { 4 // Delay_us(8); 5 // Myiic_Start(); 6 // Myiic_SendByte(SHT20_ReadAddress); 7 // } 8 // while(Myiic_SlaveAck()); 9 10 // 其他人的代码改进 11 do 12 { 13 Delay_us(8); 14 Myiic_Start(); 15 Myiic_SendByte(SHT20_ReadAddress); 16 Myiic_W_SDA(1); 17 Myiic_W_SCL(1); 18 } 19 while(Myiic_R_SDA()); 20 Myiic_W_SCL(0);
2023-12-15
Flash校验遇到的问题
校验程序如下
1 int Check(uint32_t Address,float *SRAMArr) 2 { 3 uint32_t num=1; 4 while(SuccessFlag != FAILED) 5 { 6 if((*(__IO float*) Address) != SRAMArr[num++]) 7 { 8 SuccessFlag = FAILED; 9 } 10 Address += 4; 11 } 12 return SuccessFlag; 13 }
但是当先向缓存中写256个数据(一个数据4个字节,Flash一页256*4=1024个字节)然后一起数据写到FLash一页中,再做上面的校验,做了两次都显示Flash写入数据有误,于是我向缓存中写4个数据就将它存到Flash中,发现将缓存中的每一个元素和Flash比较最后还是显示Flash读写有误,但是我将缓存的这4个数据和Flash的前4个数据比较显示写成功(没有比较标志位)
2023-12-19
OLED二级菜单
在做OLED二级菜单的时候开始采用的双向链表的方法,但是又想到Measure和Setting同级,MenuMain可以跳到Measure和Setting任意一个,Measure和Setting也可以跳回MenuMain,因此给链表中每个结点创建两个子节点,这是什么数据结构了?二叉树?二叉树子节点不能指向父节点呀!况且MainUI只会有MenuMain一个子节点,那么另外一个结点闲着不用?
1 typedef struct ListNode 2 { 3 Menu_t _Attibute; 4 Menu_t *_fatherMenu;//当前菜单的父级菜单 5 Menu_t *_childrenMenu1;//当前菜单的子级菜单 6 Menu_t *_childrenMenu2;//当前菜单的子级菜单 7 }ListNode;
2023-12-21
按键注意事项
由于我使用的按键程序原因,一个按键的任何设置出现问题都会影响其他按键,也就是说使用按键1的时候如果没有达到预期功能则问题可能不出现在按键1身上可能出现在按键2身上
1 uint8_t Key_GetNum(void) 2 { 3 static uint8_t key_up=1;//松手标志 4 if(key_up==1&&(Enter==0||Back==0||Right==0||Left==0||Up==0||Down==0)) 5 { 6 Delay_ms(20);//消抖 7 key_up=0; 8 if(Enter==0) return Enter_Press; 9 else if(Back==0) return Back_Press; 10 else if(Right==0) return Right_Press; 11 else if(Left==0) return Left_Press; 12 else if(Up==0) return Up_Press; 13 else if(Down==0) return Down_Press; 14 }else if(Enter==1&&Back==1&&Right==1&&Left==1&&Up==1&&Down_Press==1) key_up=1;//该问题是这句话相与导致的,比如这里Down写成了Down_Press(为2)那么这个if一直实现不了key_up一直为0,致使所有按键都不好使 15 return 0;//无按键按下 16 }
无符号数据类型的问题
将数据设置成无符号数据类型心里首先想到的是一定不能让它减到小于0,否则会出现意想不到的问题
1 uint8_t CursorPositon=0, FirstPositionIndex=0, CursorPositon_pre=0, FirstPositionIndex_pre=0;//开始我将它们设置成无符号数据类型了,而下面给了它们小于0的机会,然后就出现了意想不到的错误 2 case Up_Press: 3 { 4 CursorPositon--; 5 if(CursorPositon<0&&FirstPositionIndex!=0)//1 6 { 7 CursorPositon = 0; 8 FirstPositionIndex--; 9 } 10 else if(CursorPositon<0&&FirstPositionIndex==0)//2 11 { 12 CursorPositon = (MenuPoint->MenuProperty->MenuLen)-(MenuPoint->MenuProperty->HiddenMenuLen)-1; 13 FirstPositionIndex = MenuPoint->MenuProperty->HiddenMenuLen; 14 } 15 DisplayRefreash(MenuPoint,CursorPositon,FirstPositionIndex); 16 };break;
排查错误的小经验,首先我定位到10行的else if没有实现出现了,然后我注释掉这个else if发现还是出现了同样的问题,证明这个else if就是没有问题的,然后我带入了几个数据发现都没问题,这个时候就应该想想是不是数据类型出现了问题,会不会是将无符号数减到小于0了
2023-12-22
结构体遇到的问题
CursorPositon_pre和FirstPositionIndex_pre用于存储父菜单切换到子菜单时CursorPositon和FirstPositionIndex的值,但是Begin可以作为父菜单,MainMenu也可以作为父菜单因此MainMenu切换到子菜单时Begin的CursorPositon_pre和FirstPositionIndex_pre被覆盖掉了,因此我将Begin的CursorPositon_pre和FirstPositionIndex_pre加入了结构体,这样CursorPositon_pre和FirstPositionIndex_pre就分属于某个结构体对象了,相互不会影响
数组下标越界
数组下标越界是一个很隐藏的Bug,它会以各种意想不到的形式出现,因此出现Bug的时候想想是不是用到数组了,下标是不是越界了
IIC的错误
软件模拟IIC每一步都不能落下,在写程序时有个应答忘记写了,导致了错误的结果
1 void EEPROM_WriteWord(uint16_t RegAddress, uint32_t Data) 2 { 3 Myiic_Start(); //iic起始 4 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 5 // Myiic_SlaveAck(); //接收应答忘写了 6 7 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 8 Myiic_SlaveAck(); //接收应答 9 10 Myiic_SendByte(RegAddress); //发送寄存器地址 11 Myiic_SlaveAck(); //接收应答 12 13 Myiic_SendByte((0xff000000&Data)>>24); //发送寄存器地址 14 Myiic_SlaveAck(); //接收应答 15 Myiic_SendByte((0x00ff0000&Data)>>16); //发送寄存器地址 16 Myiic_SlaveAck(); //接收应答 17 Myiic_SendByte((0x0000ff00&Data)>>8); //发送寄存器地址 18 Myiic_SlaveAck(); //接收应答 19 Myiic_SendByte((0x000000ff&Data)); //发送寄存器地址 20 Myiic_SlaveAck(); //接收应答 21 22 Myiic_Stop(); //iic终止 23 }
OLED菜单可以改进的地方
1.用于加减数值的菜单页我赋予了它功能函数,加减按键按下时判断这个菜单页是否有功能函数,有的话就执行功能函数
1 case Add_Press: 2 { 3 //函数指针只有通过函数指针调用函数的时候才需要填入形参,判断值的时候不用 4 // if(MenuPoint[FirstPositionIndex+CursorPositon].Subs(KeyNum) != 0) 5 if(MenuPoint[FirstPositionIndex+CursorPositon].Subs != 0) 6 MenuPoint[FirstPositionIndex+CursorPositon].Subs(KeyNum); 7 DisplayRefreash(MenuPoint,CursorPositon,FirstPositionIndex); 8 };break; 9 10 case Minus_Press: 11 { 12 if(MenuPoint[FirstPositionIndex+CursorPositon].Subs != 0) 13 MenuPoint[FirstPositionIndex+CursorPositon].Subs(KeyNum); 14 DisplayRefreash(MenuPoint,CursorPositon,FirstPositionIndex); 15 };break;
功能函数如下,判断是菜单前3个菜单项还是菜单后3个菜单项,它们一次增加或减少的数值不同,这里是不是可以改进一下呢,如果换一个菜单前2个菜单项和后4个菜单项加减的数值不同呢,好几个菜单的菜单项的情况都不相同呢,有没有统一的程序呢
1 void ParaSetup_fun(uint8_t _KeyNum) 2 { 3 switch(_KeyNum) 4 { 5 case Add_Press: 6 { 7 if(CursorPositon+FirstPositionIndex<=2) 8 { 9 MenuPoint[CursorPositon+FirstPositionIndex].Para+=1.00; 10 } 11 12 if(CursorPositon+FirstPositionIndex>2) 13 { 14 MenuPoint[CursorPositon+FirstPositionIndex].Para+=10.00; 15 } 16 };break; 17 18 case Minus_Press: 19 { 20 if(CursorPositon+FirstPositionIndex<=2) 21 { 22 MenuPoint[CursorPositon+FirstPositionIndex].Para-=1.00; 23 if(MenuPoint[CursorPositon+FirstPositionIndex].Para<0) 24 MenuPoint[CursorPositon+FirstPositionIndex].Para=0; 25 } 26 27 if(CursorPositon+FirstPositionIndex>2) 28 { 29 MenuPoint[CursorPositon+FirstPositionIndex].Para-=10.00; 30 if(MenuPoint[CursorPositon+FirstPositionIndex].Para<0) 31 MenuPoint[CursorPositon+FirstPositionIndex].Para=0; 32 } 33 };break; 34 } 35 }
2.结构体定义了一个用来装参数的成员,显示的时候判断这个菜单页是否有需要改变的参数,如果有就利用sprintf函数将参数和菜单项抬头("Tem:)合并在一起,但是为了满足链表的特性不需要改变参数的菜单页也必须有用来装参数的成员,这样是不是有点浪费
1 void DisplayRefreash(MenuItem_t *nowMenu,int8_t CursorPositon,int8_t FirstPositionIndex) 2 { 3 OLED_Clear();//清楚上次结果 4 if(nowMenu==Begin)//当回到主菜单时,由于没有全占屏,所以全部清屏,再画 5 { 6 //一个菜单页如果菜单项小于4则菜单项全部显示出来,如果大于等于4则显示出4项 7 for(uint8_t i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->HiddenMenuLen);i++) 8 { 9 OLED_ShowString(8,i*WordHeight,nowMenu[i].DisplayString,OLED_6X8);//起始页只是一个界面要把全部菜单项显示出来,没有隐藏菜单项 10 } 11 } 12 else if(nowMenu==MainMenu) 13 { 14 OLED_ShowChar(0,CursorPositon*WordHeight,'>',OLED_6X8);//画出这次位置上的光标 15 for(uint8_t i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->HiddenMenuLen);i++) 16 { 17 OLED_ShowString(8,i*WordHeight,nowMenu[i+FirstPositionIndex].DisplayString,OLED_6X8); 18 } 19 } 20 else 21 { 22 OLED_ShowChar(0,CursorPositon*WordHeight,'>',OLED_6X8);//画出这次位置上的光标 23 for(uint8_t i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->HiddenMenuLen);i++) 24 { 25 sprintf(str,"%s%05.2f",nowMenu[i+FirstPositionIndex].DisplayString,nowMenu[i+FirstPositionIndex].Para); 26 OLED_ShowString(8,i*WordHeight,str,OLED_6X8); 27 } 28 } 29 OLED_Update(); 30 }
3.时间片轮询架构方面,每加一个任务就得重新定义结构体数组这样是不是不大方便
1 task_info_t task_arr[tasknum]; 2 void task_Init(void) 3 { 4 task_arr[0].task_interval=ParaSetup[3].Para; 5 task_arr[0].task_entry=task1; 6 7 task_arr[1].task_interval=ParaSetup[4].Para; 8 task_arr[1].task_entry=task2; 9 10 task_arr[2].task_interval=ParaSetup[5].Para; 11 task_arr[2].task_entry=task3; 12 }
float类型存储问题
我想将float类型数据写入EEPROM再将它取出来于是我的程序如下:
1 void EEPROM_WriteWord(uint16_t RegAddress, uint32_t Data) 2 { 3 Myiic_Start(); //iic起始 4 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 5 Myiic_SlaveAck(); //接收应答 6 7 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 8 Myiic_SlaveAck(); //接收应答 9 10 Myiic_SendByte(RegAddress); //发送寄存器地址 11 Myiic_SlaveAck(); //接收应答 12 13 Myiic_SendByte((0xff000000&Data)>>24); //发送寄存器地址 14 Myiic_SlaveAck(); //接收应答 15 Myiic_SendByte((0x00ff0000&Data)>>16); //发送寄存器地址 16 Myiic_SlaveAck(); //接收应答 17 Myiic_SendByte((0x0000ff00&Data)>>8); //发送寄存器地址 18 Myiic_SlaveAck(); //接收应答 19 Myiic_SendByte((0x000000ff&Data)); //发送寄存器地址 20 Myiic_SlaveAck(); //接收应答 21 22 Myiic_Stop(); //iic终止 23 } 24 25 /** 26 * 函 数:EEPROM读取float类型数据 27 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 28 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF 29 */ 30 float EEPROM_ReadWord(uint16_t RegAddress) 31 { 32 uint32_t Data; 33 34 Myiic_Start(); //iic起始 35 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 36 Myiic_SlaveAck(); //接收应答 37 38 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 39 Myiic_SlaveAck(); //接收应答 40 41 Myiic_SendByte(RegAddress); //发送寄存器地址 42 Myiic_SlaveAck(); //接收应答 43 44 Myiic_Start(); //iic重复起始 45 Myiic_SendByte(EEPROM_READADDRESS); //发送从机地址,读写位为1,表示即将读取 46 Myiic_SlaveAck(); //接收应答 47 48 Data = Myiic_ReceiveByte(); 49 Myiic_HostAck(0); 50 51 Data <<= 8; 52 Data += Myiic_ReceiveByte(); 53 Myiic_HostAck(0); 54 55 Data <<= 8; 56 Data += Myiic_ReceiveByte(); 57 Myiic_HostAck(0); 58 59 Data <<= 8; 60 Data += Myiic_ReceiveByte(); 61 62 Myiic_HostAck(1); //发送应答,给从机非应答,终止从机的数据输出 63 Myiic_Stop(); //iic终止 64 65 return Data; 66 }
1 int main(void) 2 { 3 /*模块初始化*/ 4 OLED_Init(); //OLED初始化 5 Myiic_Init(); 6 OLED_Clear(); 7 float num=19.625; 8 float result; 9 EEPROM_WriteWord(0, num); 10 Delay_s(1); 11 result=EEPROM_ReadWord(0); 12 OLED_Printf(0, 0, OLED_8X16, "%05.2f",result); 13 OLED_Update(); 14 }
void EEPROM_WriteWord(uint16_t RegAddress, uint32_t Data) { Myiic_Start(); //iic起始 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte((0xff000000&Data)>>24); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte((0x00ff0000&Data)>>16); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte((0x0000ff00&Data)>>8); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte((0x000000ff&Data)); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_Stop(); //iic终止 } /** * 函 数:EEPROM读取float类型数据 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF */ uint32_t EEPROM_ReadWord(uint16_t RegAddress) { uint32_t Data; Myiic_Start(); //iic起始 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_Start(); //iic重复起始 Myiic_SendByte(EEPROM_READADDRESS); //发送从机地址,读写位为1,表示即将读取 Myiic_SlaveAck(); //接收应答 Data = Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(1); //发送应答,给从机非应答,终止从机的数据输出 Myiic_Stop(); //iic终止 return Data; }
1 int main(void) 2 { 3 // /*模块初始化*/ 4 OLED_Init(); //OLED初始化 5 Myiic_Init(); 6 OLED_Clear(); 7 8 Input_t input; 9 Output_t output; 10 input.value_i=19.625; 11 EEPROM_WriteWord(0, input.num_i); 12 13 Delay_s(1); 14 output.num_o=EEPROM_ReadWord(0); 15 OLED_Printf(0, 0, OLED_8X16, "%05.2f",output.value_o); 16 OLED_Update(); 17 }
1 void EEPROM_WriteWord(uint16_t RegAddress, uint32_t Data) 2 { 3 Myiic_Start(); //iic起始 4 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 5 Myiic_SlaveAck(); //接收应答 6 7 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 8 Myiic_SlaveAck(); //接收应答 9 10 Myiic_SendByte(RegAddress); //发送寄存器地址 11 Myiic_SlaveAck(); //接收应答 12 13 Myiic_SendByte((0xff000000&Data)>>24); //发送寄存器地址 14 Myiic_SlaveAck(); //接收应答 15 Myiic_SendByte((0x00ff0000&Data)>>16); //发送寄存器地址 16 Myiic_SlaveAck(); //接收应答 17 Myiic_SendByte((0x0000ff00&Data)>>8); //发送寄存器地址 18 Myiic_SlaveAck(); //接收应答 19 Myiic_SendByte((0x000000ff&Data)); //发送寄存器地址 20 Myiic_SlaveAck(); //接收应答 21 22 Myiic_Stop(); //iic终止 23 } 24 25 /** 26 * 函 数:EEPROM读取float类型数据 27 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 28 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF 29 */ 30 uint32_t EEPROM_ReadWord(uint16_t RegAddress) 31 { 32 uint32_t Data; 33 34 Myiic_Start(); //iic起始 35 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 36 Myiic_SlaveAck(); //接收应答 37 38 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 39 Myiic_SlaveAck(); //接收应答 40 41 Myiic_SendByte(RegAddress); //发送寄存器地址 42 Myiic_SlaveAck(); //接收应答 43 44 Myiic_Start(); //iic重复起始 45 Myiic_SendByte(EEPROM_READADDRESS); //发送从机地址,读写位为1,表示即将读取 46 Myiic_SlaveAck(); //接收应答 47 48 Data = Myiic_ReceiveByte(); 49 Myiic_HostAck(0); 50 51 Data <<= 8; 52 Data += Myiic_ReceiveByte(); 53 Myiic_HostAck(0); 54 55 Data <<= 8; 56 Data += Myiic_ReceiveByte(); 57 Myiic_HostAck(0); 58 59 Data <<= 8; 60 Data += Myiic_ReceiveByte(); 61 62 Myiic_HostAck(1); //发送应答,给从机非应答,终止从机的数据输出 63 Myiic_Stop(); //iic终止 64 65 return Data; 66 }
1 int main(void) 2 { 3 // /*模块初始化*/ 4 OLED_Init(); //OLED初始化 5 Myiic_Init(); 6 OLED_Clear(); 7 Output_t output; 8 float num=19.625; 9 EEPROM_WriteWord(0, num); 10 11 Delay_s(1); 12 output.num_o=EEPROM_ReadWord(0); 13 OLED_Printf(0, 0, OLED_8X16, "%05.2f",output.value_o); 14 OLED_Update(); 15 }
/** * 函 数:EEPROM读取float类型数据 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF */ float EEPROM_ReadWord(uint16_t RegAddress) { uint32_t Data; Myiic_Start(); //iic起始 Myiic_SendByte(EEPROM_WRITEADDRESS); //发送从机地址,读写位为0,表示即将写入 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress>>8); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_SendByte(RegAddress); //发送寄存器地址 Myiic_SlaveAck(); //接收应答 Myiic_Start(); //iic重复起始 Myiic_SendByte(EEPROM_READADDRESS); //发送从机地址,读写位为1,表示即将读取 Myiic_SlaveAck(); //接收应答 Data = Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(0); Data <<= 8; Data += Myiic_ReceiveByte(); Myiic_HostAck(1); //发送应答,给从机非应答,终止从机的数据输出 Myiic_Stop(); //iic终止 return Data; }
1 int main(void) 2 { 3 // /*模块初始化*/ 4 OLED_Init(); //OLED初始化 5 Myiic_Init(); 6 OLED_Clear(); 7 8 Input_t input; 9 float result; 10 11 input.value_i=19.625; 12 EEPROM_WriteWord(0, input.num_i); 13 14 Delay_s(1); 15 result=EEPROM_ReadWord(0); 16 OLED_Printf(0, 0, OLED_8X16, "%05.2f",result); 17 OLED_Update(); 18 }
以上用到的联合体如下:
1 typedef union Input 2 { 3 float value_i; 4 uint32_t num_i; 5 }Input_t; 6 7 typedef union Output 8 { 9 float value_o; 10 uint32_t num_o; 11 }Output_t;
为了找到问题所在模拟下上述过程
第一种情况:(其实第三种更好)
1 int main() 2 { 3 OLED_Init(); 4 OLED_Clear(); 5 float num=19.625;//write的实参 6 uint32_t Data;//write的形参 7 uint32_t Data1;//read当中的Data 8 float temp;//read中的返回时创建的临时变量 9 float value;//主函数那里用来接read返回值的变量 10 uint8_t num1; 11 uint8_t num2; 12 uint8_t num3; 13 uint8_t num4; 14 Data=num; 15 num1=(0xff000000&(Data))>>24; 16 num2=(0x00ff0000&(Data))>>16; 17 num3=(0x0000ff00&(Data))>>8; 18 num4=(0x000000ff&(Data)); 19 Data1 = num1; 20 Data1 <<= 8; 21 22 Data1 += num2; 23 Data1 <<= 8; 24 25 Data1 += num3; 26 Data1 <<= 8; 27 28 Data1 += num4; 29 temp=Data1; 30 31 value=temp; 32 33 OLED_Printf(0,0,OLED_6X8,"%05.2f",value); 34 OLED_Update(); 35 return 0; 36 }
这里应该是num传给Data那里Data将小数点后省略因此结果和输入不符合
第四种情况:
1 int main() 2 { 3 OLED_Init(); 4 OLED_Clear(); 5 Test.value=19.625;//write的实参 6 uint32_t Data;//write的形参 7 uint32_t Data1;//read当中的Data 8 float temp;//read中的返回时创建的临时变量 9 float value;//主函数那里用来接read返回值的变量 10 uint8_t num1; 11 uint8_t num2; 12 uint8_t num3; 13 uint8_t num4; 14 Data=Test.num; 15 num1=(0xff000000&(Data))>>24; 16 num2=(0x00ff0000&(Data))>>16; 17 num3=(0x0000ff00&(Data))>>8; 18 num4=(0x000000ff&(Data)); 19 Data1 = num1; 20 Data1 <<= 8; 21 22 Data1 += num2; 23 Data1 <<= 8; 24 25 Data1 += num3; 26 Data1 <<= 8; 27 28 Data1 += num4; 29 temp=Data1; 30 31 value=temp; 32 33 OLED_Printf(0,0,OLED_6X8,"%05.2f",value); 34 OLED_Update(); 35 return 0; 36 }
为了弄清楚原因我们看看Data1是什么
1 int main() 2 { 3 OLED_Init(); 4 OLED_Clear(); 5 Test.value=19.625;//write的实参 6 uint32_t Data;//write的形参 7 uint32_t Data1;//read当中的Data 8 float temp;//read中的返回时创建的临时变量 9 float value;//主函数那里用来接read返回值的变量 10 uint8_t num1; 11 uint8_t num2; 12 uint8_t num3; 13 uint8_t num4; 14 Data=Test.num; 15 num1=(0xff000000&(Data))>>24; 16 num2=(0x00ff0000&(Data))>>16; 17 num3=(0x0000ff00&(Data))>>8; 18 num4=(0x000000ff&(Data)); 19 Data1 = num1; 20 Data1 <<= 8; 21 22 Data1 += num2; 23 Data1 <<= 8; 24 25 Data1 += num3; 26 Data1 <<= 8; 27 28 Data1 += num4; 29 temp=Data1; 30 31 value=temp; 32 OLED_Printf(0,0,OLED_6X8,"%d",Data); 33 // OLED_Printf(0,0,OLED_6X8,"%05.2f",value); 34 OLED_Update(); 35 return 0; 36 }
正好是19.625对应的二进制串,因此可以推断问题出现在返回值temp身上,这涉及到float存储有空再研究吧
2023-12-28
EEPROM出厂设置问题
我想实现当我第一次使用EEPROM的时候对EEPROM初始化,但是下次再运行同样的程序时就不对EEPROM初始化了,也就好比出厂设置,为此我们可以在EEPROM每页的开头定义一个标志位,我们的数据从第二个位置开始写,下次再运行这个程序的时候就判断第一个位置是否定义了标志位,如果定义了就不再初始化
1 /** 2 * @brief 第一次使用这块内存时将EEPROM初始化为特定值,并且每页第一个4字节空间不能使用,用来存放标志位 3 */ 4 void EEPROM_Store_Init(uint16_t page,uint32_t Flag,void(*para_init)(void)) 5 { 6 //判断是不是第一次使用,不是第一次使用的话第一个半字不会是FLAGE 7 if(EEPROM_ReadWord(page*OnePageWord*4,0)!=Flag) 8 { 9 EEPROM_WriteWord(page*OnePageWord*4,0, Flag); 10 //由于每一页对应的参数类型不同(比如第一页装入环境相关参数,第二页装入环境相关的数组参数),因此定义一个函数指针,在回调函数中决定要将各值初始化为多少 11 para_init(); 12 } 13 }
2024-01-12
串口DMA的使用
将接收到的数据通过串口发送出去
我想实现的目标是将在一次传输计数器减到0之前将float数组中的所有元素全都通过串口传输到串口调试助手上,因此我们必须先将float数组中的每个元素转换成字符串放到一个字节数组中,然后让DMA把数据搬到串口数据寄存器中
1 pUSART1_TX_FLOAT=(void*)USART3_RX_DATA_BUF; 2 for(uint16_t i=0;i<2;i++) 3 { 4 sprintf(arr+i*8,"%05.3f",pUSART1_TX_FLOAT[i]); 5 arr[6+i*8]='\r'; 6 arr[7+i*8]='\n';//为了在串口中每显示一个数据换一行,我在每个数据后面加'\r','\n' 7 } 8 USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 9 SRAM_USART1_Transfer();
以上是我目前能想到的办法
寄存器到外设到底要不要使用中断
江科协野火原子在寄存器给外设发送数据的时候均使用的标志位函数,没有使用中断
1 void SRAM_USART1_Transfer(void) 2 { 3 DMA_Cmd(DMA1_Channel4,DISABLE);//失能DMA通道 4 DMA_SetCurrDataCounter(DMA1_Channel4, _count);//将传输计数器赋值 5 DMA_Cmd(DMA1_Channel4,ENABLE);//使能DMA 6 7 while(DMA_GetFlagStatus(DMA1_FLAG_TC4)==RESET);//等待DMA转运完成 8 DMA_ClearFlag(DMA1_FLAG_TC4); 9 }
这里等待转运完成仍然需要CPU等待,占用CPU,因此我想能不能才用中断,中断到的时候,也就是DMA转运完成通知CPU,不用CPU一直等待,于是我参考了如下博文:
STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)_stm32串口dma很难?-CSDN博客
1 //DMA 发送应用源码 2 void DMA_USART2_Tx_Data(u8 *buffer, u32 size) 3 { 4 while(USART2_TX_FLAG); //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据) 5 USART2_TX_FLAG=1; //USART2发送标志(启动发送) 6 DMA1_Channel7->CMAR = (uint32_t)buffer; //设置要发送的数据地址 7 DMA1_Channel7->CNDTR = size; //设置要发送的字节数目 8 DMA_Cmd(DMA1_Channel7, ENABLE); //开始DMA发送 9 } 10 11 //DMA1通道7中断 12 void DMA1_Channel7_IRQHandler(void) 13 { 14 if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET) //DMA接收完成标志 15 { 16 DMA_ClearITPendingBit(DMA1_IT_TC7); //清除中断标志 17 USART_ClearFlag(USART2,USART_FLAG_TC); //清除串口2的标志位 18 DMA_Cmd(DMA1_Channel7, DISABLE ); //关闭USART2 TX DMA1 所指示的通道 19 USART2_TX_FLAG=0; //USART2发送标志(关闭) 20 } 21 }
但是它仍然需要等待上一个数据发送完成,这样问题不但没有得到解决,反而增加了中断,因此到底要不要开启中断,有没有更好的通过中断实现的程序
后来我改了一下程序,把等待上一次发送完成那个while死循环去掉发现也可以实现
1 void SRAM_USART1_Transfer(void) 2 { 3 USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送,也可以写到初始化函数中 4 DMA_SetCurrDataCounter(DMA1_Channel4, _count);//将传输计数器赋值 5 DMA_Cmd(DMA1_Channel4,ENABLE);//使能DMA 6 USART1_DMA1_TX_Flag=1; 7 } 8 9 void DMA1_Channel4_IRQHandler(void) 10 { 11 if((DMA_GetITStatus(DMA1_IT_TC4)!= RESET)&&USART1_DMA1_TX_Flag==1) //DMA接收完成标志 12 { 13 DMA_ClearITPendingBit(DMA1_IT_TC4); //清除中断标志 14 DMA_Cmd(DMA1_Channel4, DISABLE ); //关闭USART2 TX DMA1 所指示的通道 15 USART1_DMA1_TX_Flag=0; 16 } 17 }
2024-01-19
项目的注意点
Flash不能写的太晚
if(Temperature_WriteFlashNum==256)
{
Flash_Store_Save(StoreTemperature_StartADDRESS,(void*)Flash_StoreTemperature,STORE_COUNT);
Temperature_WriteFlashNum=1;
}
否则你突然掉电,数组中的数据还没写入Flash,Flash里面什么都没有,那重新上电,Flash再重新写入数组的话,数组中也就什么数据也都没有了
也不能写入太早
这样Temperature_WriteFlashNum很快会回到1,那么可读的数据就变少了,而且重新掉电Flash里面的数据也没更新(因为这组数据还没有到更新Flash的时候),那么Flash就会把上一组的数据写入数组中,那么你读到的数据也就是上一组的了。
综上所述,掉电后再上电读出来的数据肯定会有问题,无法避免