gd32f103单片机-裸机开发1main.c-如何考虑调试问题

因工作需要,最近写了几段32的单片机程序。就来总结一下呗。

今天的主题:

1.在没有硬件的情况下,如何布局单片机程序。

2.在拿到硬件的情况下,如何快速调试自己的单片机程序

3.在单片机出现故障时,如何快速定位问题,并排除问题?

三个小问题呢,其实就是一个问题,就是如何为调试布局。(我的思路也不一定是最优解。请多多指教)

参考代码:

https://github.com/zhaozhi0810/gd32f103ret6-2022-04/blob/main/src/main.c

本节主要是main.c



/*
宏定义功能控制,定义在includes.h文件中
// 1. lcd 亮度pwm控制,调整液晶屏亮度
// 2. lcd 加热pwm控制,没有定义时就是直接加热或关闭,不能降低功率
// 3. lcd 加热控制,用于控制lcd加热功能和18b20温度读取功能,关闭后,不再读取18b20温度,液晶屏加热停止
本软件用于GD32f103ret6,主要功能包含如下: 8M晶振输入
1. 按键检测(PA0,低电平有效),控制另一个引脚(PA1)输出高低电平,用于控制另一个cpu的开机关机操作
	短按开机(输出高电平),长按(大于5s)关机(串口发出关机命令),上电默认自启动(高),
2. 串口2用于与cpu通信,应用通信协议待定。115200,8N1 (PA2,PA3)
3. 串口1用于调试输出,115200,8N1 (PA9,PA10)
4. 调试led 低电平点亮  PA8 
5. 风扇控制。高开启,低关闭 PA12 
6. 预留风扇PWM控制,默认不使用PWM功能 PA11
7. 18b20温度传感器。(PB0,PB1) -60~+100(高低温实验)
8. 液晶屏加热使能(PB3 高电平)
9. 液晶屏背光pwm(PB4,使用定时器)
10.液晶屏背光使能(PB5)
11.iic1(PB6,PB7)[1.sm2900-11 4路电压采集,2.sm2900-01 两路温度采集]
12.看门狗芯片使能(PB8)
13.看门狗喂狗信号(PB9)
14.iic2(PB10,PB11) [1.28v电源的电流上报]
15.io输入(PB12,PB13,PB14,PB15)
16.cpu复位信号(PC0,输入)
17.复位控制(PC1,输出)
18.7A桥复位信号1(PC2,输出)
19.7A桥复位信号2(PC3,输出)
20.与cpu的io通信引脚(PC4-PC6,PC10,PC11) 
21.LVDS转换使能引脚(PC12,输出)
22.硬盘销毁引脚(PC13,输出),!!!!已更改
新原理图的修改
23. 4个状态的读取,PA4,PA5,PA6,PA7
24. 硬盘销毁引脚 PA15,可能需要对调试端口的引脚进行设置。
25. 液晶屏供电使能 PC15,输出高电平有效
26. 转换使能高有效 PC14,输出高电平有效
27. 与FPGA通信预留引脚,PC7,PC8,PC9
*/



#include "includes.h"


const char* g_build_time_str = "Buildtime :"__DATE__" "__TIME__;   //获得编译时间
uint16_t g_task_id;   //每一个位对应一个任务,为1表示需要启动任务,在任务中清零该位






800ms 看门狗
static void iwdog_init(void)
{
	fwdgt_write_enable();
	fwdgt_config(0xfff,FWDGT_PSC_DIV8);    //设置分配系数,最长819ms
	
	fwdgt_enable(); //使能看门狗
}


static  void iwdog_feed(void)
{
//	if(mcu_reboot)  //设置mcu重启,不喂狗。2021-12-17增加
//		return ;
	fwdgt_counter_reload();
}




static void BoardInit(void)
{
	//0. 中断分组初始化
	//NVIC_SetPriorityGrouping(4);  //均为4个等级
	nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
	
	//0.1 复用功能模块通电
    rcu_periph_clock_enable(RCU_AF);
	
	//1.串口初始化
	//#define DEBUG_COM_NUM 0   //调试串口号
	//#define TOCPU_COM_NUM 1   //与cpu通信的串口
	gd_eval_com_init(DEBUG_COM_NUM);  //用于调试
	gd_eval_com_init(TOCPU_COM_NUM);  //用于与cpu数据通信,改到cpu上电后再初始化
	
	//2.systick 初始化
	SystickConfig();

	//3.lcd加热引脚控制初始化,默认输出低
	LcdHeat_Init();  //没有加热功能时,也要保证初始化后引脚为低
#ifdef LCD_HEAT_ENABLE		
	//4.18B20初始化
	DS18B20_Init(ds18b20_ind1);
	DS18B20_Init(ds18b20_ind2);
#endif	
	//5. 电源按键和cpu电源控制
	PowerBtn_Control_Init();
	
	//6. cpu运行状态监控引脚初始化
	Cpu_Run_Pins_Init();
	
	//7.散热风扇初始化
	Fan_Control_Init();
	
	//8. 4路di输入的引脚配置
	Di_4Ttl_Pins_Init();
	
	//9. lcd控制引脚初始化
	lcd_pwm_init(70);    //亮度默认为70% ,此时显示屏不开启!!!!
		
	//10. 初始化外部硬件看门狗,默认不开启
	Hard_WatchDog_Pins_Init();
	
	// 11.led初始化
	Led_Show_Work_init();
	
	//12. 电压电流,温度,iic的初始化
	Vol_Temp_Api_Init();
		
	//13. 销毁
	Msata_Destroy_Pin_Init();   //默认输出高
			
	//14. 启动系统,默认上电时是启动系统的。
	Enable_Cpu_Poweron();
	
	//15. 启动单片机内部看门狗
	iwdog_init();
	
}





int main(void)
{
	uint8_t i;
	const task_t task[TASK_MAX]={Btn_Power_Change_Scan    //任务1,上电按钮扫描								
					//		,[1] = task1_btn_scan       		//任务2,无
							,[2] = Task_Check_CPU_Run_Status    //任务3,运行状态检测,关机重启控制,这个优先级可以低一点
							,[3] = Task_Get_Temp_Vol       //任务4,温湿度,电压监控读取任务,2000ms调用一次
					//		,[4] = Task_ReportTo_Cpu_status  //任务5,定时向cpu汇报,500ms一次 //2022-04-21不再主动发送	
					//		,[5] = com3_frame_handle    //无需要
							,[14] = iwdog_feed         //最后一个任务喂狗
						,[15]=Task_Led_Show_Work       //任务16,最后一个任务,让工作led灯闪烁,1s调用一次
					//因为工作灯不能正常使用,所以删除该任务。2021-12-01
	};
	
	
	//1. 初始化
	BoardInit();

	printf("%s\r\n", g_build_time_str);
	printf("BoardInit done! 2022-04-18\r\n");
	
	while(1)
	{
		for(i=0;i<TASK_MAX && g_task_id;i++){
			if(g_task_id & (1<<i))   //定时时间到,要执行
			{
				g_task_id &= ~(1<<i);  //对应的位置被清零,等待定时器设置
			
				if(task[i])  //指针不能为空
				{	
					task[i](); //执行该任务
					break;    //一次只执行一个任务,任务靠前的优先级高,任务靠后的优先级低
				}				
			}
		}//end for		
	}
}

这个文件的内容不会很多,主要分成两个主要部分,一个是初始化,一个是主循环。

1. 初始化部分

考虑到程序写成时,并不能上硬件调试,所以需要考虑的是各个模块的独立初始化,我可以注释任意模块的初始化部分,而其他并不受到影响。

那这个部分主要考虑的就是每个模块独立初始化,并且不相互包含。如果一定要相互包含,请注意包含关系,并且尽量在依赖没有初始化的时候,考虑自身初始化的过程(是阻止自身初始化(可能就是直接返回了)?还是自己先部分初始化?)

       这里我想到了一个小插曲,就是调试18B20的时候,参考了网上的代码,在进行通信阶段是关闭了中断的,而18B20出现通信错误的时候,是直接返回,而没有开中断。

       平时正常使用的时候没啥问题,而有一次同事为了调试某个功能,把18b20给断开了,然后单片机就傻逼了(因为我的程序需要依赖中断才能运行)。然后就去调试,发现程序跑一会(大概几秒)中断就没了,但是启动的时候是有中断的,就觉得很莫名其妙。

      当时没有想到18B20拆掉后造成的影响,花了很多功夫,才看到中断关闭而没有重新开启的问题。真是太。。。。汗颜了。

第二的话,我觉得要考虑部分模块的初始化的先后顺序,包括中断优先级,串口初始化等,这些就可以优先初始化,为后面的初始化做准备。

这两点就是我在初始化的阶段考虑的问题,我相信大家也都会这么去考虑。

毕竟即使单片机程序编译通过,真正上机测试的时候还是会出现很多意想不到的问题,这个时候就需要注释一些初始化的部分,来排除问题,初始化的部分不受其他影响,那我任意注释,问题都不会大。这样能加速我在初始化阶段问题的定位。

2. 主循环部分

初始化问题,其实是比较好解决的,毕竟程序只运行一次,要么成功,要么失败,失败就去定位是什么原因引发的失败。

而主循环的问题估计就不好解决,程序要运行多次,每一次运行的时候条件可能又会有一些差异,这样就会导致问题可能会出现,或者不出现。

一个模块出现问题还是比较好排除,反正程序跑到那个点,就会有问题。

然而,上机之前,所有的模块都没有真的跑过,这时候问题可能就比较复杂了,可能是各个模块之间的干扰,或者依赖关系,或者逻辑问题,那真是比较繁杂。

所以大家看到的贴出的代码,我用的是函数指针的方式,每一个函数指针基本就是某个模块循环采集或者控制。

我在主循环中,循环去调用这些函数指针,这样就能任意控制哪些模块的循环函数可能被调用,那如果出现问题,我可以把部分或者全部的函数指针注释,然后单独跑某一个模块的循环函数,这样我就可以逐步排除是哪个模块出现的问题,或者追踪到是哪个模块加入后引发的逻辑问题。

2.1 我考虑了每个模块的循环函数的周期的差异,所以使用的是用定时器(systick,1ms周期)控制,定时器不同的时间来设置某个标志位,只有这个位置1的时候,任务才有可能被执行。考虑到单片机程序不要跑飞了,判断一下函数的指针不为0,则执行该函数。

2.2 我设计的所有的周期函数,要考虑的是执行时间短,不延时的方式,如果需要延时,如何处理?请期待我后期的按键扫描识别程序的讲解(或者直接参考代码吧)。

2.3 循环函数也要考虑对其他模块的依赖情况,万一其他模块没有运行时的处理机制。

3.总结

这是我对如何进行问题排除的一个简单思路,并且这些方法已经用于多个项目,程序经过多轮实验测试,证明稳定可靠,暂时没有出现什么问题。

这是讲解的第一个部分,后续还会把代码中其他的关键部分再列举出来,如果大家有跟高明的方法,也希望能与大家多多交流学习。

如有需要,可以私聊我的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大智兄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值