首页 > Python资料 博客日记
单片机裸机程序——程序架构
2024-10-13 18:00:12Python资料围观30次
程序架构等同于思想体系
建一栋楼房,地基要先设计好,而不是马上砌砖,地基和布局都合理,房子就住得舒服,也不会闹心。
写一段程序也一样,程序构架要想好,而不是一边写一边调整构架,想到这个功能就先写这个功能,多个功能放在一起后无法协调,又要改构架,效率非常低。
把构架用图形的形式设计好,再像添砖加瓦一样去编码,实现功能。
一、前后台顺序法
公司都有负责接待来宾的前台,当有人来访或有电话来,前台就会通知其他负责人,正在有序工作的负责人必须马上停止手中工作,接待或接听结束后再回到原来的事情上继续。有人来访或有电话比做前台,正在有序工作比做后台。
前后台顺序执行是入门开发者常用的程序架构,逻辑简单,复杂度低,代码量少,最直观了,从上往下执行,全部任务都在一个循环里执行,不考虑每个函数执行所需要的时间。但遇到延时就要等待,因此会造成其他函数间隔执行时间的不同,尽管能够通过定时器中断的方式,但前提是中断服务函数执行的时间必须短。
顺序执行就是在你前面的执行完了,才到你执行,好比上体育课列队报数一样,必须按顺序来,你前面相邻的同学报完数,你才能报数。
还有就是有中断发生,就优先执行中断要执行的事情,好比体育课,点到谁的名字就优先得去跑两圈。
未加入中断的轮询系统
int main(void)
{
uint8 keyValue;
InitSys(); // 系统初始化
while (1)
{
TaskDisplayClock();//显示系统时钟
keyValue = TaskKeySan();//扫描键盘
switch (keyValue)
{
case x: TaskDispStatus(); break;
...
default: break;
}
}
}
在while(1)死循环中从第一行代码往下执行到最后一行,然后返回到第一行,如此反复执行。
在轮询系统基础上加入中断处理
int flag1 = 0,flag2= 0,flag3 = 0;
int main(){
hardwareInit();//硬件初始化
while(1){
if(flag1){
doSomething1();//处理事件1
flag1=0;//清除标志位
}
if(flag2){
doSomething2();//处理事件2
flag2=0;//清除标志位
}
if(flag3){
doSomething3();//处理事件3
flag3=0;//清除标志位
}
}
}
//中断处理程序1
void ISR1(void ){
flag1=1//置位标志位
}
//中断处理程序2
void ISR2(void ){
flag2=1//置位标志位
}
//中断处理程序3
void ISR3(void ){
flag3=1//置位标志位
}
外部事件的响应(改变一些标志位,确保所用时间非常短)在中断里面完成,事件的处理还是要回到轮询系统中完成。
1、如果中断服务程序执行时间太长,可能导致中断嵌套非常深,容易导致栈溢出,也容易出现异常,比如死机。
2、中断只处理紧急事务!紧急事务要用中断处理!比如串口数据的接收和发送,速度太快,放在循环里处理太慢了。然而对于数据的处理,就不用着急了,可以在中断里接收完成后设置标志位,然后在主循环里根据标志位完成处理,之后手动开启相应中断去发送。
中断在这里称为前台,main()函数中的主循环称为后台。
二、时间片轮询法
敢言:时间片轮询法是万能的裸机程序构架
时间片法是一种多任务执行法,它通过为每个任务分配一定的执行时间片,使得所有任务都能够按照一定的时间间隔交替执行。任务执行的时间片是固定的,当一个任务的时间片用完后,系统切换到下一个任务执行。这种方式使得多个任务能够并行执行,提高了系统的资源利用率和效率。
时间片法适用于中等复杂度的嵌入式系统,可以满足对实时性要求较高的场景。它需要合理设置任务的优先级和时间片大小,以确保重要任务优先执行,并且每个任务都能在适当的时间内完成。
使用定时器设定不同的时间片,定时到了某个时间节点,就去执行对应时间片里的代码,代码执行时间不能超过时间片时间,时间片时间应该根据所执行任务的耗时来设定。比如,按键扫描,通常都需要软件防抖,顺序法是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理很多其他事情,时间片轮询法就能很好利用CPU资源。
世界万事万物都是基于时间这条线而存在而运作的,谁都摆脱不了与时间的关联。
时间片轮询方式也容易理解,你设定的时间到了,你就执行,没到就继续等待,各个时间片之间没有优先级。
时间片轮询法系统的实现思路(只需要一个任意的定时器)
1、在系统初始化里初始化一个定时器,假设定时中断为1ms(也可以改成10ms,根据实际需求,但需要注意,中断过于频繁效率就低,中断太长,实时性差)。
2、定义多个任务变量及标志位
3、定义任务函数
4、把任务调度添加到主循环中
5、把任务切换逻辑控制添加到中断服务中
1、初始化定时器,以51单片机为例
//定时器初始化,定时1ms中断一次
void Timer1_Init(){
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x30; //设置定时初值
TH1 = 0xF8; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器中断
EA = 1;
}
//定时器中断服务函数
void TM1_Isr() interrupt 3
{
TL1 = 0x30; //设置定时初值
TH1 = 0xF8; //设置定时初值
}
2、定义任务相关
//任务结构
typedef struct _TASK_COMPONENTS
{
unsigned int Run; //程序运行标记: 0-不运行,1-运行
unsigned int Timer; //计时器
unsigned int ItvTime; //任务运行时间间隔
void (*TaskHook)(void); //要运行的任务函数
}TASK_COMPONENTS;//任务定义,TASK_COMPONENTS是_TASK_COMPONENTS的别名
static TASK_COMPONENTS TaskComps[]= //时间节拍为1ms,由定时器产生
{
{0,10,10,TaskRfCheck}, //处理RF数据
{0,20,20,TaskUart2Check}, //每次只处理串口2的一个字节数据
{0,8,8,TaskRGBdata},//RGB数据处理 刷新频率不能低于30HZ
{0,30,30,TaskPWMout}, //刷新PWM数据
{0,1000,1000,TaskTimers}, //计时器
};
//任务清单
typedef enum _TASK_LIST
{
TASK_RF_CHECK,
TASK_UART2_CHECK,
TASK_RGB_DATA,
TASK_UART1_PRINTF,
TASK_TIMERS,
TASKS_MAX, //总共可分配的定时任务数目
}TASK_LIST;
3、定义任务函数
void TaskRfCheck() //识别遥控器按键
{
//在此处添加代码
}
void TaskUart2Check() //解析串口2数据
{
//在此处添加代码
}
void TaskRGBdata() //处理RGBW数据
{
//在此处添加代码
}
void TaskPWMout() //刷新PWM数据
{
//在此处添加代码
}
void TaskTimers() //灯带独立计时器
{
//在此处添加代码
}
4、定义任务调度函数并添加到主循环中
void TaskProcess()
{
static unsigned char i=0; //必须是static类型
for(i=0;i<TASKS_MAX;i++) //逐个任务时间处理
{
if(TaskComps[i].Run) //任务可以运行
{
TaskComps[i].TaskHook(); //调用任务函数
TaskComps[i].Run = 0; //标志清0
}
}
}
void SysInit_all()
{
Timer1_Init(); //定时器1初始化,用于多个任务调度
WDT_CONTR=0x35;//看门狗设置,超时2秒则复位
}
//主函数
int main()
{
SysInit_all();//系统初始化
while(1)
{
TaskProcess(); //任务处理
WDT_CONTR |= 0x10; //喂看门狗,防止系统复位
}
return 1;
}
5、把任务切换逻辑控制添加到中断服务中
void TaskRemarks(void)
{
static unsigned char i=0; //必须是static类型
for(i=0;i<TASKS_MAX;i++) //逐个任务处理
{
if(TaskComps[i].Timer) //时间不为0
{
TaskComps[i].Timer--; //减去一个节拍
if(TaskComps[i].Timer<=0)//时间减完了
{
TaskComps[i].Timer = TaskComps[i].ItvTime; //恢复计时器值,重新下一次
TaskComps[i].Run = 1; //任务可以运行
}
}
}
}
//每1ms就执行一次TaskRemarks()
void TM1_Isr() interrupt 3
{
TL1 = 0x30; //设置定时初值
TH1 = 0xF8; //设置定时初值
TaskRemarks(); //任务调度
}
时间片轮询法的流程图大致为:
在程序中,只要把时间节点控制成有规律性的,有周期性的,那么就非常好添加代码或调试信息了。
比如在时间片为1秒的任务里,加入调试信息打印语句,那么在程序运行时每秒就会打印一次打印信息。
比如每秒打印一次字符串信息,task_1s_function函数每秒就被调用一次:
void task_1s_function()
{
printf("1 sec is up\r\n");
}
比如每5秒打印一次字符串信息:
void task_1s_function()
{
static unsigned int count = 0;
if(++count > 5)
{
count = 0;
printf("5 sec is up\r\n");
}
}
比如事件A发生3秒后,执行一次事件B:
static unsigned int count_down = 0;
void A_event_occured()
{
printf("Event A occurs\r\n");
count_down = 3;
}
void B_event_occured()
{
printf("Event B occurs\r\n");
}
void task_1s_function()
{
if(count_down)
{
count_down--;
if(count_down == 0)
{
B_event_occured();
}
}
}
当A事件被触发时,执行一次B事件:
bool A_occured_flag = false;
A事件被触发,启用标志位 A_occured_flag
A_occured_flag = true;
周期地(比如10ms计时一到,F_Timer_10MS被设为true)循环执行那边判断标志位,如果被启用了,就执行语句体,否则不执行。只要限制条件满足,相关处理逻辑就会执行一次。
while(1)
{
if(F_Timer_10MS)
{
F_Timer_10MS = false;
if(A_occured_flag )
{
if(限制条件)
{
相关处理逻辑
A_occured_flag = false;
}
}
}
}
当一个状态发生反转时,打印一次信息
if(R_loadON != R_loadON_last){
R_loadON_last = R_loadON;
if(R_loadON) LOG_STR("R_loadON == 1\r\n");
else LOG_STR("R_loadON == 0\r\n");
}
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Could not build wheels for llama-cpp-python, which is required to install pyproject.toml-based proj