实用的MCU非阻塞式框架---外设部分
常用的单片机裸机非阻塞式框架
仅作参考
ADC采样滤波
0.约1024次AD后采样获取一次温度值
1.支持双电阻高低温区采样
2.一阶滤波、限幅滤波、温差比较 ---- 冒泡排序后取平均值
3.二分法获取查表获取温度
4.添加补偿温度
/****************************************************************************************************
Function Name :void NTC_GetTpTask(void)
Description :
Author :MECHT
Version :V1.0
****************************************************************************************************/
#define ADC_GROUP 8
#define NTC_GROUP_NUM 16
void NTC_GetTpTask(void)
{
static uint xdata Su16TempBuf1[ADC_GROUP] = {0};
static uchar xdata Su8BufIndex1 = 0; //单个通道ADC值数组下标
static uint xdata Su16TempBuf2[ADC_GROUP] = {0};
static uchar xdata Su8BufIndex2 = 0; //单通道平均值数组下标
static signed int xdata Su16TempBuf3[NTC_GROUP_NUM] = {0};
static uchar xdata Su8BufIndex3 = 0;
static signed int xdata OldNtcValue = 0;
static uint16_t Tempd = 0;
static uint16_t OldAdcValue = 0;
uint16_t AdcTempValue = 0; //临时值
uint16_t AdcTempAverage = 0; //临时平均值
// 此部分根据单片机不同更换,目标都是获取AD值
ADCCON &= ~(0X20); //清零
ADCCON |= 0X40; //Start ADC conversion
while(!(ADCCON & 0X20));
AdcTempValue = (ADCVH<<4)+(ADCVL>>4);
//======================================================
//======================================================
//1.一阶滤波,需根据实际情况微调
if(Su8BufIndex1 == 0)
OldAdcValue = AdcTempValue;
Su16TempBuf1[Su8BufIndex1] = 7*OldAdcValue/10 + 3*AdcTempValue/10;
if(++Su8BufIndex1 >= ADC_GROUP)
{
Su8BufIndex1 = 0;
AdcTempAverage = AdcAverageHandle(Su16TempBuf1,ADC_GROUP,2); //得到ADC平均值
Su16TempBuf2[Su8BufIndex2] = AdcTempAverage;
if(++Su8BufIndex2 >= ADC_GROUP)
{
Su8BufIndex2 = 0;
NtcInfo.ADValue = AdcAverageHandle(Su16TempBuf2,ADC_GROUP,2);
//限幅滤波
NtcInfo.ADValue = ADC_Fliter(NtcInfo.ADValue );
//==========================================================================================
#if 1 //单通道 二分法查询温度
Tempd = Find_temperaturePos(NtcInfo.ADValue,Cu16TempTable1,TEMP_Table_SIZE1);
Su16TempBuf3[Su8BufIndex3] = -400+Tempd*5;
if(++Su8BufIndex3 >= NTC_GROUP_NUM)
{
Su8BufIndex3 = 0;
NtcInfo.NtcTp = MyAverageHandle(Su16TempBuf3,NTC_GROUP_NUM,6);
//若跟上一次相差0.5℃则不更新
if((OldNtcValue - NtcInfo.NtcTp) == 5 || (NtcInfo.NtcTp - OldNtcValue)==5)
{
NtcInfo.NtcTp = OldNtcValue;
}
else
{
OldNtcValue = NtcInfo.NtcTp;
}
//根据不同模式提供不同的补偿温度
switch(WorkInfo.Function)
{
case FUN_O:
NtcInfo.NtcTp += WorkInfo.OffsetTp1*10;
case FUN_W:
case FUN_C:
case FUN_F:
NtcInfo.NtcTp += WorkInfo.OffsetTp2*10; //补偿温度2
break;
case FUN_H:
case FUN_A:
NtcInfo.NtcTp += WorkInfo.OffsetTp3*10; //补偿温度3
break;
}
if(NtcInfo.NtcTp >= 850)
WorkInfo.NtcTemperature[1] = 850;
else if(NtcInfo.NtcTp <= -300)
WorkInfo.NtcTemperature[1] = -300;
else
WorkInfo.NtcTemperature[1] = NtcInfo.NtcTp;
WorkInfo.NtcTemperature[0] = TemperUnitChange(0,WorkInfo.NtcTemperature[1]);
}
#else //双通道切换
//选择分压电阻
switch(NtcInfo.DoubleRes)
{
case 0:
NtcInfo.NtcTp = Find_temperaturePos(NtcInfo.ADValue,Cu16TempTable1,TEMP_Table_SIZE1);
if(NtcInfo.NtcTp >= 180)
{
NtcInfo.DoubleRes = 1;
//Pin_key_ntc = 1; //单电阻
}
break;
case 1:
NtcInfo.NtcTp = Find_temperaturePos(NtcInfo.ADValue,Cu16TempTable2,TEMP_Table_SIZE2);
NtcInfo.NtcTp += 170; //根据情况调整
if(NtcInfo.NtcTp <= 170)
{
NtcInfo.DoubleRes = 0;
// Pin_key_ntc = 0; //开双电阻
}
break;
}
//切换PIN脚
if(NtcInfo.DoubleRes)
{
Pin_key_ntc = 0;
}
else
{
Pin_key_ntc = 1; //单电阻
}
#endif
NtcInfo.GetOnceTpFlag = 1;
}
}
}
串口无阻塞发送–循环队列(应用层不仅仅适用于串口)
1.需要结合循环队列,非阻塞发送
2.单片机资源紧张,串口发送时无需判断是否发送成功,1ms或者2ms发送一字节数据即可
typedef struct Queue
{
unsigned char MaxSize; //最大个数
unsigned char NowCount; //参考RTOS 增加当前个数,节省一行数据
unsigned char head; //队头
unsigned char fail; //队尾
// unsigned char (*DataBuf)[]; //队列缓冲区,节省空间后面的直接用数组
}QueueObject_t; //简易队列
unsigned char QueueEmpty(QueueObject_t *Queue)//队空
{
//return (Queue->fail==Queue->head)?1:0; //减少运算
return (Queue->NowCount==0)?1:0;
}
unsigned char QueueFull(QueueObject_t *Queue) //队满
{
//return (((Queue->fail+1)%Queue->MaxSize)== Queue->head)?1:0;
return (Queue->NowCount >= Queue->MaxSize)?1:0;
}
void QueueAddTail(QueueObject_t *Queue,unsigned char info[],unsigned char length) //入队 尾插
{
if((QueueFull(Queue))) //队列满退出
{
return;
}
//MyMemoryCpy(Queue->DataBuf[Queue->fail],info,length);
MyMemoryCpy(SendQueueBuf[Queue->fail],info,length); //由队列转换为数组
Queue->fail = (Queue->fail+1)%Queue->MaxSize; //队尾+1,指向+1,此时指向的位置是空的,无数据
Queue->NowCount++; //总量+1
}
void QueueDelete(QueueObject_t *Queue)//出队,头节点+1
{
//if((QueueEmpty(Queue))) //队列满退出,根据需求选择覆盖or退出
// {
// return;
// }
Queue->head = (Queue->head+1)%Queue->MaxSize;
Queue->NowCount--;
}
void MyLog_Task(void)
{
static unsigned char Su8UartSendTimes = 0; //发送次数,1次
static unsigned char Su8SendIntervalCnt = 0; //每次发送间隔时间
static unsigned char Su8SendSuccessIntervalCnt = 0; //一类数据发送完后的发送间隔时间
if(GsUartInfo.Txing) //发送数据ing
{
//SFR 串口
//UART_SFR_REG = GsUartInfo.QueueInfo_Tx->DataBuf[GsUartInfo.QueueInfo_Tx->head][GsUartInfo.TxBufIndex++]; //为节省空间直接将队列指针改为数组
UART_SFR_REG = UartSendQueueBuf[GsUartSendQueueInfo.head][GsUartInfo.TxBufIndex++]; //队列改数组
if(GsUartInfo.TxBufIndex >= UartSendQueueBuf[GsUartSendQueueInfo.head][1])
{
GsUartInfo.TxBufIndex = 0;
GsUartInfo.Txing = 0;
}
return;
}
if(Su8SendSuccessIntervalCnt != 0) //间隔发送,若时间少可去除,比如100ms
{
Su8SendSuccessIntervalCnt--;
return;
}
if(QueueEmpty(&GsUartSendQueueInfo)) //队列无数据时不发送
{
return;
}
//===========================================
switch(GsUartInfo.TxStep) //发送步骤,间隔时间由轮询位置和case 2 共同决定
{
case 0:
GsUartInfo.TxStep++;
Su8UartSendTimes = 0;
Su8SendIntervalCnt = UART_SAME_DATA_INTERVAL_TIME; //第一次不间隔
break;
case 1: //发送队列中的数据
if(Su8UartSendTimes < GsUartInfo.TxTimes) //固定发送次数模式时
{
if(Su8SendIntervalCnt++ >= UART_SAME_DATA_INTERVAL_TIME)//在1ms扫描时,间隔30ms发送以一次
{
Su8SendIntervalCnt = 0;
GsUartInfo.Txing = 1; //启动发送
GsUartInfo.TxBufIndex = 0; //重新发送
Su8UartSendTimes++;
}
}
else //实际发送多一次间隔时间1ms
{
GsUartInfo.TxStep++; //等待ACK,有ACK的情况则继续往下执行,否则会重发队头数据,不出队 //实际上到case 2 需要一段时间
//加一个比较数据 --- 可用来应对MODBUS协议/或者直接使用队列头数据比较
}
break;
case 2: //发送完成后或者 帧错误(未判断)出队
Su8UartSendTimes = 0;
GsUartInfo.TxStep = 0;
QueueDelete(&GsUartSendQueueInfo); //出队
Su8SendSuccessIntervalCnt = UART_SEND_FINISH_INTERVAL_TIME; //间隔
break;
default:
break;
}
}
软件定时器
1.设置1ms定时器中断,设置变量在定时器中断函数中++/–
2.跑裸机都可参考此框架的非阻塞式延时。
3.注意MCU的工作频率和机器周期。
unsigned int Cnt1 = 0;
unsigned int Cnt2 = 0;
void main()
{
while(1)
{
if(Cnt1) //1ms执行一次
{
Cnt1 = 0;
task_1ms(); //在此任务函数中,可以继续计时
}
if(Cnt2 >= 10) //确定时间,10ms执行一次
{
Cnt2 = 0;
task_10ms();
}
}
}
void timer0() interrupt 1
{
TL0 = 0x1C; //与计算值会有一定偏差,需做补偿
TH0 = 0xFC; //定时器重赋值
Cnt1 = 1; //1ms定时器
Cnt2++; //不定
}
按键扫描
1.机械按键按下会产生抖动,通过非阻塞式的延时消抖,轻触按键也可以通过计数消抖。
static void KeyScan(void)
{
// if(0) //开门、故障、某特定情况不需要按键时
// return;
//1.获取按键按位值
GsKeyInfo.CurrentValue = Drv_Key_New_Scan();
//2.按下判断和组合判断
if(0 != GsKeyInfo.CurrentValue)//按下
{
if(GsKeyInfo.PreValue != GsKeyInfo.CurrentValue)
{
if(GsKeyInfo.Pressing == 0)
{
GsKeyInfo.PreValue = GsKeyInfo.CurrentValue;
GsKeyInfo.PressCnt = 0;
}
else
{
/*
此部分不固定,根据需求决定做法
若按下的情况下切换按键,根据实际情况添加功能
1.双键轻触 --- 在脱手处判断时间
2.组合键 --- 单变双、三变双,这两个按键需要脱手触发或者按下触发但不是短触
3.无功能 ---- 单边多/多变单都不响应
4.
*/
// if(GsKeyInfo.PreValue == KeyCombine) //上一次是组合按键值
// {
// if(GsKeyInfo.CurrentValue == One_of_Combine) //释放了一个属于组合按键值之一的键
// {
//
// }
// else
// {
// GsKeyInfo.PreValue = GsKeyInfo.CurrentValue;
// GsKeyInfo.PressCnt = 0;
// }
// }
// else
GsKeyInfo.PreValue = GsKeyInfo.CurrentValue;
}
}
GsKeyInfo.Pressing = 1;
GsKeyInfo.PressCnt++;
if(GsKeyInfo.PressCnt >= KEY_MAX_TIME)
{
GsKeyInfo.PressCnt = KEY_MAX_TIME;
}
// == 1.按下过程的功能
WorkInfo.BackLightCnt = 50; //按下背光就打开倒计时
//=== 2.长按、连续
if(WorkInfo.WorkMode == MODE_WORK)
{
#if 1
switch(GsKeyInfo.PreValue)
{
case KEY_Mode:
if(GsKeyInfo.PressCnt == 1000) //长按10S功能
{
GsKeyInfo.Type = KEY_TYPE_LONG;
GsKeyInfo.PressOk = 1;
GsKeyInfo.ActionValue = GsKeyInfo.PreValue;
}
break;
}
#endif
}
}
else//松手,进行按键选择
{
GsKeyInfo.Pressing = 0;
if(GsKeyInfo.PressCnt >= KEY_SHORT_PRESS_MIN && GsKeyInfo.PressCnt <= KEY_SHORT_PRESS_MAX)
{
switch(GsKeyInfo.PreValue)
{
case C_KEY_BitMap_KEY1:
case C_KEY_BitMap_KEY2:
case C_KEY_BitMap_KEY3:
case C_KEY_BitMap_KEY4:
GsKeyInfo.Type = KEY_TYPE_SHORT; //短按
GsKeyInfo.PressOk = 1;
GsKeyInfo.ActionValue = GsKeyInfo.PreValue;
break;
default:
break;
}
}
GsKeyInfo.PressCnt = 0; //松手清零
}
}
static void Key_Task(void)
{
if(GsKeyInfo.PressOk)
{
GsKeyInfo.PressOk = 0;
KeyFunction(GsKeyInfo.ActionValue,GsKeyInfo.Type); //根据长、短类型响应功能
#if 0 //按键调试串口打印
KeyValuebuf[0] = GsKeyInfo.ActionValue >> 16;
KeyValuebuf[1] = GsKeyInfo.ActionValue >> 8;
UartSendDataHandle(KeyValuebuf,2,UART_KEYVALUE,UartSendBuf);
GsComInfo.TxSendFun(UartSendBuf);
#endif
GsKeyInfo.Type = KEY_TYPE_NONE; //默认类型
}
}
static void KeyFunction(uint16_t ActionValue,uint8_t Type)
{
switch(ActionValue)
{
case 0:
break;
case KEY_FanSpeed:
if(Type == KEY_TYPE_SHORT)
{
Key1_Short_Fun();
}
else if(Type == KEY_TYPE_LONG)
{
Key1_Long_Fun();
}
break;
case KEY_Mode:
if(Type == KEY_TYPE_SHORT)
{
Key2_Short_Fun();
}
else if(Type == KEY_TYPE_LONG)
{
Key2_Long_Fun();
}
break;
}
}
数码管显示(改为扫描COM可实现调节亮度)
1.假设是共阳双八数码管。
2.假设用按键设置时,数码管闪烁显示。
3.假设是单片机的P2口连接数码管引脚。
void DisplayScan(void) //1ms定时器中扫描
{
static uchar Su8ScanStep = 0;//动态扫描步骤
if(GbWorkStatus)
{
if(GbLedEnableFlag )
{
//消隐
P2 = (unsigned char)0xFF; //共阳,给高电平不亮
LED_COM1_CLOSE; //第一个8片选关闭
LED_COM2_CLOSE; //第二个8片选关闭
switch(Su8ScanStep) //扫描步骤
{
case 0: //first
P2 = Gu8LedDigTableBuf[Gu8LedFirstShowData]; //在外部确定Gu8LedFirstShowData的值是多少,将0-9和部分字母的编码列举在Gu8LedDigTableBuf[]数组中
LED_COM1_OPEN; //选中第一个数码管
LED_COM2_CLOSE;
Su8ScanStep++; //下一次显示第二个数码管
break;
case 1: //second
P2 = Gu8LedDigTableBuf[Gu8LedSecondShowData];//第二位数据
P27 = !GbLedThirdShowEnable; //是否带小数点
LED_COM1_CLOSE;
LED_COM2_OPEN;
//=============================以下为闪烁部分程序,不需要闪烁时,删除下面case 1程序,添加Su8ScanStep = 0;即可
if(!GbLedFlickerFlag) //闪烁标志位,没有要求闪烁的话跳回到第一个数码管的显示
{
Gu16LedOffFlickerCnt = 0;
Su8ScanStep = 0;
}
else if(++Gu16LedOffFlickerCnt >= LED_ON_FLICKER_TIME)//要求闪烁时,闪烁时期亮的时间
{
Gu16LedOffFlickerCnt = 0;
Su8ScanStep++;
}
else
Su8ScanStep = 0;
break;
case 2://
LED_COM1_CLOSE;
LED_COM2_CLOSE;
if(++Gu16LedOnFlickerCnt >= LED_OFF_FLICKER_TIME) //闪烁时期灭的时间
{
Gu16LedOnFlickerCnt = 0;
Su8ScanStep = 0;
}
break;
}
}
else
{
LED_COM1_CLOSE;
LED_COM2_CLOSE;
}
}
}
无源蜂鸣器
1.通电后需要一定震荡频率蜂鸣器才会响。
void BuzTask(void)
{
if(xBuzInfo.Times) //次数
{
if(xBuzInfo.Cmd)
{
if(xBuzInfo.SoftCnt-- == 0)
{
if(xBuzInfo.IntervalTime)
{
xBuzInfo.SoftCnt = xBuzInfo.IntervalTime; //间隔
}
xBuzInfo.Cmd = 0;
xBuzInfo.Times--; //次数
}
}
else
{
if(--xBuzInfo.SoftCnt == 0)
{
xBuzInfo.SoftCnt = xBuzInfo.OpenTime;
xBuzInfo.Cmd = 1;
}
}
}
else
{
}
xBuzInfo.BuzDrv();
}
static void BuzDrv(void)
{
static uint8_t xdata Su8oldcmd = 0xFF;
if(Su8oldcmd != xBuzInfo.Cmd)
{
Su8oldcmd = xBuzInfo.Cmd;
if(xBuzInfo.Cmd)
{
BuzPwm_Enable;
}
else
{
BuzPwm_Disable;
}
}
}
##IO口模拟PWM
void IoToPwmScan(void) //放在1ms定时器中
{
if(Enable)
{
if(++Cnt <= 100) //100ms低电平
PIN = 0;
else if(Cnt <= 200) //100ms高电平
PIN = 1;
else
Cnt = 0;
}
else
Cnt = 0;
}
工业开关
1.分层扫描,高低都延时,随意更改开关个数
static void Switch_Scan(void)
{
static uint8_t xdata Su8SwitchScanStep = 0;
switch(Su8SwitchScanStep)
{
case 0:
Su8SwitchScanStep = 1;
break;
case 1:
F_SwitchPinValue1 = PIN_DIAL_SWITCH1;
F_SwitchPinValue2 = PIN_DIAL_SWITCH2;
F_SwitchPinValue3 = PIN_DIAL_SWITCH3;
F_SwitchPinValue4 = PIN_DIAL_SWITCH4;
F_SwitchPinValue5 = PIN_DIAL_SWITCH5;
F_SwitchPinValue6 = PIN_DIAL_SWITCH6;
F_SwitchPinValue7 = PIN_DIAL_SWITCH7;
F_SwitchPinValue8 = PIN_DIAL_SWITCH8;
Su8SwitchScanStep = 0; //修改此处
break;
// case 2:
// P3CON |= 0x80;
// SWITCH_COM2 = 0; //
// P3CON &= ~0x40;
//
// Su8SwitchScanStep = 3;
// break;
// case 3:
// F_SwitchPinValue5 = PIN_DIAL_SWITCH4;
// F_SwitchPinValue6 = PIN_DIAL_SWITCH3;
// F_SwitchPinValue7 = PIN_DIAL_SWITCH2;
// F_SwitchPinValue8 = PIN_DIAL_SWITCH1;
// Su8SwitchScanStep = 4;
// break;
// case 4:
// Su8SwitchScanStep = 0;
// break;
default:
Su8SwitchScanStep = 0;
break;
}
}
static uint8_t GetSwitchValue(unsigned char Pin)
{
uint8_t PinValue = 0;
switch(Pin)
{
case 0:
PinValue = F_SwitchPinValue1;
break;
case 1:
PinValue = F_SwitchPinValue2;
break;
case 2:
PinValue = F_SwitchPinValue3;
break;
case 3:
PinValue = F_SwitchPinValue4;
break;
case 4:
PinValue = F_SwitchPinValue5;
break;
case 5:
PinValue = F_SwitchPinValue6;
break;
case 6:
PinValue = F_SwitchPinValue7;
break;
case 7:
PinValue = F_SwitchPinValue8;
break;
}
return PinValue;
}
//最后通过200ms滤波获取值
void Drv_DialSwitch(void)
{
uint8_t i = 0; //max == 255
uint8_t PinValue = 0;
static uint8_t xdata Su8LowFilterCnt[Dial_Switch_NUM] = {0};
static uint8_t xdata Su8HighFilterCnt[Dial_Switch_NUM] = {0};
Switch_Scan(); //底层扫描
for(i = 0;i < Dial_Switch_NUM;i++) //应用层获取
{
PinValue = GetSwitchValue(i); //中间件
if(!PinValue) //低电平是ON
{
Su8LowFilterCnt[i] = 0;
if(Su8HighFilterCnt[i] < DialSWICHT_FILTER_TIME)
{
Su8HighFilterCnt[i]++;
if(Su8HighFilterCnt[i] >= DialSWICHT_FILTER_TIME)
{
Gu8SwitchStateFlag_Ass |= 0x01 << i;
}
}
}
else
{
Su8HighFilterCnt[i] = 0;
if(Su8LowFilterCnt[i] < DialSWICHT_FILTER_TIME)
{
Su8LowFilterCnt[i]++;
if(Su8LowFilterCnt[i] >= DialSWICHT_FILTER_TIME)
{
Gu8SwitchStateFlag_Ass &= ~(0x01 << i);
}
}
}
}
}
最后:
为避免全局变量满天飞,用结构体管理全局变量,若MCU内存不够,最后可解耦;
typedef struct
{
/*
MenuParameterSet: 为当前设置对象,在进行 +- 时无需判断模式
MenuNub: 菜单编号,为功能之下的子菜单,初始化为1
*/
unsigned char Versions; // 版本号
unsigned char BoardVersions; // 软件版本
unsigned char ID;
unsigned char Unit:1; // 温度单位 0:F 1:C
unsigned char TpType:1; // 当前温度类型
unsigned char ReinstatedFactoryFlag:1; //恢复出场设置标志
unsigned char BuzEn:1; //蜂鸣器使能控制
unsigned char OffsetTp1; //补偿温度1
unsigned char OffsetTp2; //补偿温度2
unsigned char WifiFlag:1; // WIFI连接情况
unsigned char WorkSwitch1:1;
unsigned char WorkSwitch2:1;
unsigned char WorkSwitch3:1;
unsigned char WorkSwitch4:1;
unsigned char CommSuccess:1; // 通讯完成标志
unsigned int SetTemperature[2]; // 设置温度 0:F 1:C
signed int NtcTemperature[2]; // 环境温度 0:F 1:C
unsigned char WorkMode; // 工作状态: 自检、自检模式、关机、待机、设置、工作
unsigned char OldWorkMode; // 上一次工作模式
unsigned char Function; // 功能
unsigned char OldFunction;
FunctionObject_t *FunObject; // 当前功能对应参数
unsigned char BackLightCnt; // 背光倒计时 默认5S
unsigned char InitStep; // 初始化步骤
unsigned int InitCnt; // 初始化计时器
unsigned char NTC_State; // NTC状态
//======================= 故障
unsigned char ErrorFlag:1; // 有故障标志
unsigned char ErrorShowCnt; // 故障显示计时器
unsigned int ErrorNub; // 故障编号 //或起来显示的故障点
unsigned int ErrorNubBackup; // 故障编号备份
unsigned char BatVoltage; //电池电压
//=========================================================
unsigned int SpecialFunCnt; //特殊功能计时器
unsigned char LowVolFlag:1; // 低压信号
unsigned int LowHighVolStayCnt; // 低压保持时间
unsigned char CheckStep; // 测试步骤
unsigned char CheckCnt; // 测试时间
//======================= 预留 ==================
unsigned int WorkTimeS;
unsigned int WorkTimeUpCnt;
unsigned int WorkTestCnt;
}WorkManageObject_t;
extern WorkManageObject_t xdata WorkInfo;