開始

靜態數碼管與矩陣按鍵的聯合測試

單擊,雙擊,長按

要達到的效果是16個按鍵單擊雙擊長按都能在數碼管上顯示相應的位置碼0-F.
這裏使用的是一位的共陽數碼管只需要關心段選拉低就行。
驅動

//驅動函數給1位的共陽靜態數碼管,所以不用管位選
//PB8-PB15
void led_write(UINT8 dat)
{
    LED_PORT->ODR = (dat<<8);
}

顯示函數
test函數20ms調用一次

/*
函數功能:按鍵單擊雙擊長按的測試
參數説明:
	參數1:按鍵單擊雙擊長按標誌位(keyF,keyDF,keyLF)
返回值:void
*/
void test(void *pMsg){
	UINT8 i;
	KEY_TYPE  keyF=(*(KEY_TYPE *)pMsg);
	for(i=0;i<KEY_NUM;i++){
		if(keyF&(1<<i)){
			led_write(LedChars[i]);
		}
	}
	(*(KEY_TYPE *)pMsg)=0x00;
}

這裏是任務列表

//10ms
const TASK_S  taskGroup0[]=
{
	   {keyMain,&Key},
     {NULL,NULL},
};
//20ms
const TASK_S  taskGroup1[]=
{
	   {test,&Key.keyLF},//這裏切換檢測模式單擊雙擊長按
		 {NULL,NULL},		 
};

任務參數可以是以下標誌位:單擊Key.keyF,雙擊Key.keyDF,長按Key.keyLF
然後在keyMain中 不斷調用對應的檢測函數

void keyMain(void *pMsg)
{
	KEY_S *p = pMsg;
	keyScan(p,p->pf());
#if(D_CLICK_EN)
	keyDScan(p);
#endif
	keyLScan(p);
}
組合鍵

從keyMain函數開始分析整體的流程很簡單,按鍵按下產生按鍵標誌位,檢查標誌位把按鍵事件進行分類包裝成消息,消息寫入按鍵對象的消息隊列,添加一個20毫秒的消息解析函數,解析消息。

1,檢測標誌位

void keyMain(void *pMsg)
{
	KEY_S *p = pMsg;
	keyScan(p,p->pf());
#if(D_CLICK_EN)
	keyDScan(p);
#endif
	keyLScan(p);
	
	//keyF 按鍵單擊標誌位
	if(p->keyF){//按鍵單擊事件
		creatKeyMsg(p,&p->keyF,KEY_CHAR_EVENT);
	}
	if(p->keyDF){//按鍵雙擊事件
		creatKeyMsg(p,&p->keyDF,KEY_D_EVENT);
	}
	
	if(p->keyLF){//按鍵長按事件
		creatKeyMsg(p,&p->keyLF,KEY_L_EVENT);
	}
}

2,包裝消息寫入隊列

右側映射到左側,Alt,Shift,Ctrl,Enter四個按鍵是控制按鍵

嵌入式設計實驗三:Tasket與工作隊列_tasket add_#單片機

UINT8 const KeyMap[16] = 
{
    '1', '2', '3', VK_ENTER,
	  '4', '5', '6', VK_CONTROL,
	  '7', '8', '9', VK_SHIFT,
	  '*', '0', '#', VK_ALT
};
//按鍵碼高效獲取
UINT8 getKeyNum(KEY_TYPE *pKey)
{
    UINT8  y,x,num;
	  KEY_TYPE key = *pKey;
	  x = (key & 0xff)? 0 : 1;
    y = pro[(UINT8)((key) >> (x << 3))];
	  num = y + x*8;
	  *pKey &= ~(1 << num);//注意打開
	  return num;
}
/*
函數功能:生成按鍵消息
參數説明:
	參數1:按鍵對象
	餐數2:按鍵碼
	參數3:按鍵事件
返回值:void

*/
void creatKeyMsg(KEY_S* p,KEY_TYPE *pKeyF,UINT8 event){

	MSG_S msg;
	//pKeyF每一位都表示一個按鍵是否按下,為了應對多個按鍵一起按下需要循環檢測
	while(*pKeyF && (isMsgQueueFull(&p->msgQueue)==FALSE)){
		//獲取按鍵碼在進行映射ASCII
		msg.code=KeyMap[getKeyNum(pKeyF)];
		//記錄按鍵按下時間
		msg.time=TimeStamp;
		//判斷按鍵事件類型,雙擊長按
		if(event == KEY_D_EVENT || event ==KEY_L_EVENT){
			msg.event=event;
		}else{
			if((msg.code>=32)&&(msg.code<=126)){
				msg.event=KEY_CHAR_EVENT;//字符事件
			}else{
				msg.event=KEY_DOWN_EVENT;//控制事件
			}		
		}
		saveMsgQueue(&p->msgQueue,&msg);		
	}
}

3,解析消息並顯示

/*
函數功能:組合鍵測試
參數説明:
	參數1:按鍵對象指針


返回值:void
*/
void combinationKey(void *pMsg){

	KEY_S * p=(KEY_S *)pMsg;
	MSG_S msg;
	if(msgQueueNum(&p->msgQueue)){//有消息
		getMsgQueue(&p->msgQueue,&msg);
		if(msg.event ==KEY_CHAR_EVENT){
			if(p->keyDown &KEY_CONTROL){//ctrl鍵按下 1顯示a,2顯示b,3顯示c
				//這裏需要對輸入做一下限定,因為還有*  #鍵
				if(msg.code >='0'&& msg.code<='9'){
					//LedChars偏移
					led_write(LedChars[msg.code-'0'+9]);
				}
			}else{//ctrl鍵未按下正常顯示1234567890  * #不檢測組合
				if(msg.code >='0'&& msg.code<='9'){
					led_write(LedChars[msg.code-'0']);
				}
			}	
		}
	}
}

4,將消息解析函數添加到任務組

//20ms
const TASK_S  taskGroup1[]=
{
	   //{test,&Key.keyLF},
		 {combinationKey,&Key},
		// {producer,&P2},
		 {NULL,NULL},		 
};

5,最終效果
Ctrl+123456789分別顯示’a’‘b’‘c’‘d’…

按鍵轉義

按鍵轉義是在組合鍵的升級,加入了shift,alt兩個鍵。

1,定義映射表相關宏定義

#define VK_ENTER      0x0d
#define VK_CONTROL    0x11
#define VK_SHIFT      0x10
#define VK_ALT        0x12
#define VK_UP         24
#define VK_DOWN       25
#define VK_LEFT       27
#define VK_RIGHT      26

#define KEY_ENTER     0x08
#define KEY_CONTROL   0x80
#define KEY_SHIFT     0x0800
#define KEY_ALT       0x8000
UINT8 const ConbCtrl[16] = 
{
    'a', 'b', 'c', VK_ENTER,
	  'd', 'e', 'f', VK_CONTROL,
	  'g', 'h', 'i', VK_SHIFT,
	  'j', 'k', 'l', VK_ALT   
};
UINT8 const ConbShift[16] = 
{
    'm', 'n', 'o', VK_ENTER,
	  'p', 'q', 'r', VK_CONTROL,
	  's', 't', 'u', VK_SHIFT,
	  'v', 'w', 'x', VK_ALT   
};
UINT8 const ConbAlt[16] = 
{
    'y', 'x', ',', VK_ENTER,
	  '.', '?', '!', VK_CONTROL,
	  '+', '-', VK_UP, VK_SHIFT,
	  VK_DOWN, VK_LEFT, VK_RIGHT, VK_ALT   
};

2,keyMain函數沒有修改正常檢測三種標誌位並調用消息生成函數
3,消息生成函數加入shift,alt的長按功能

/*
函數功能:生成按鍵消息(按鍵轉義shift,ctrl,alt)
參數説明:
	參數1:按鍵對象
	餐數2:按鍵碼
	參數3:按鍵事件
返回值:void
*/

void creatKeyMsg(KEY_S * p,KEY_TYPE *pKeyF,UINT8 event){
	MSG_S msg;
	UINT8 num;
	//有按鍵按下並且消息隊列有空間存儲
	//這裏每次只產生一個消息並清零標誌位
	if(*pKeyF && (isMsgQueueFull(&p->msgQueue)) ==FALSE){
		num=getKeyNum(pKeyF);//獲取物理按鍵碼
		msg.code=KeyMap[num];//物理按鍵碼映射到ASCII
		msg.time=TimeStamp;
		
		//雙擊長按事件
		if(event ==KEY_D_EVENT || event == KEY_L_EVENT){
			msg.event= event;
		}else{
			//先把enter ,ctrl,shift,alt四個按鍵刨除
			if(num!=0x07 && num !=0x0b && num !=0x0f && num !=0x03){
			
				switch(p->keyDown & 0x8880){
					case KEY_CONTROL://ctrl
						msg.code=ConbCtrl[num];
						break;
					case KEY_SHIFT://SHIFT
						msg.code=ConbShift[num];
						break;
					case KEY_ALT://ALT
						msg.code=ConbAlt[num];
						break;
					default :
						break;			
				}
				if(msg.code >=32 && msg.code <=126){
					msg.event =KEY_CHAR_EVENT;
				}else{
					msg.event =KEY_DOWN_EVENT;
				}				
			}			
		}
		saveMsgQueue(&p->msgQueue,&msg);
		*pKeyF &= ~(1 << num);//清零對應標誌位			
	}

}

4,消息解析顯示

void combinationKey(void *pMsg){

	KEY_S * p=(KEY_S *)pMsg;
	MSG_S msg;
	if(msgQueueNum(&p->msgQueue)){//有消息
		getMsgQueue(&p->msgQueue,&msg);
		if(msg.event ==KEY_CHAR_EVENT){
			if(p->keyDown &KEY_CONTROL || p->keyDown & KEY_SHIFT || p->keyDown & KEY_ALT){//ctrl鍵按下 1顯示a,2顯示b,3顯示c
				led_write(LedChars[toIdx(msg.code)]);
				
			}else{//ctrl鍵未按下正常顯示123。。。
				if(msg.code >='0'&& msg.code<='9'){
					led_write(LedChars[msg.code-'0']);
				}
			}	
		}
	}
}

命令式編程與動態數碼管的測試

動態數碼管的驅動
//段選
#define LED_PORT 			 GPIOB   
#define LED_PIN 			(GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15)
#define LED_PORT_RCC		RCC_APB2Periph_GPIOB
//片選
#define LED_CHIP_PORT      GPIOB
#define LED_CHIP_PIN        (GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_CHIP_PORT_RCC		RCC_APB2Periph_GPIOB

//驅動
void ledDrv(UINT8 dat,UINT8 chip)
{
	   UINT16 temp;
	   GPIOB->BRR = 0xff00;//所有段選拉低,因為是共陰數碼管,消影
		 temp = GPIOB->ODR;//讀取輸出寄存器值,防止修改低八位
	   temp &= 0x001f;//PB5-PB15清零
	   temp |= chip << 5;//寫入位選值
		 temp |= (~dat) << 8;//字模是共陽的所以需要取反
	   GPIOB->ODR = temp;	 //寫入輸出寄存器。
}
各種命令的測試
//================轉換命令===========================
#define LED_TEXT	0X00 //普通文本顯示
#define LED_FLASH	0x01 //閃爍顯示
#define LED_FLASH_N 0x02	//多次閃爍
#define LED_DEC		0x03	//倒計時
#define LED_MOVE	0x04	//滾動
#define LED_PIC 	0x05	//圖片顯示
#define LED_PASSWORD 0x06	//顯示**

流程説明:
後台軟件入口ledMain每100ms調用一次,ledMain調用ledcmd,ledcmd在根據命令類型調用對應的函數

void ledMain(void *pMsg){
	UINT8 i;
	LED_S *pLed=(LED_S *)pMsg;
	for(i=0;i<pLed->cnt;i++){
		ledCmd(pLed,i);
	}
}
void ledCmd(LED_S *pLed,UINT8 winNum)
{
    switch(pLed->pWin[winNum].cmd)
    {
			case LED_FLASH:
      {
				  disLedFlash(pLed,winNum);
			    break;
			}
			case LED_FLASH_N:
      {
				  disLedFlashN(pLed,winNum);
			    break;
			}
			case LED_DEC:
      {
				  ledDec(pLed,winNum);
			    break;
			}
			case LED_MOVE:
      {
				  ledMove(pLed,winNum);
			    break;
			}
			case LED_PIC:
      {
			    disLedGif(pLed,winNum);
			    break;
			}
			case LED_PASSWORD:
      {
				  disLedPassword(pLed,winNum);
			    break;
			}
			default:
      {
			    disLedText(pLed,winNum);
			    break;
			}
		}
}

圖片顯示測試步驟:
1,添加任務到任務列表

//後台軟件100ms調用一次
const TASK_S  taskGroup2[]=
{
	   //{consumer,&C1},
	{ledMain,&Dleds},
     {NULL,NULL},
};
//驅動函數3ms調用一次
const TASK_S  taskGroup3[]=
{
	   //{consumer,&C2},
	{updataLed,&Dleds},
     {NULL,NULL},
};

const TASK_GROUP_S TaskList[]=
{
     {10, taskGroup0},
	 {20,taskGroup1},
	 {100,taskGroup2},
	 {3,taskGroup3},
};

2,設置窗口

setWinNum(&Dleds,1);//只有一個窗口
setWinSize(&Dleds,0,0,8);//對窗口0設置顯存初始地址為0,長度為8
setWinCmd(&Dleds,0,LED_PIC,0,1,0,(void*)MyGif);//圖片顯示,cnt=0,reload=1,attr=0;
仿真

1,閃爍顯示仿真

int main(void)
{

	bspInit();
	initKey(&Key,50,200,arrayKeyCode);
	SysTick_Init(72);
	initQueue(&Queue);
	initMultiTask();
	
	setWinNum(&Dleds,1);//只有一個窗口
	setWinSize(&Dleds,0,0,8);//對窗口0設置顯存初始地址為0,長度為8
	setWinCmd(&Dleds,0,LED_FLASH_N,0,1,3,NULL);//閃爍顯示3次,reload 1秒,
	writeLed(&Dleds,0,"%s","123");
	while (1)
	{
		ledCmd(&Dleds,0);
		//task_exec();

	}
}

可以看到顯存中前三位交替變化三次

嵌入式設計實驗三:Tasket與工作隊列_tasket add_#單片機_02


嵌入式設計實驗三:Tasket與工作隊列_tasket add_#嵌入式硬件_03


2,倒計時顯示仿真

setWinNum(&Dleds,1);//只有一個窗口
	setWinSize(&Dleds,0,0,2);//對窗口0設置顯存初始地址為0,長度為2,即使用兩位數碼管,顯存起始地址的對應每一位數碼管
	setWinCmd(&Dleds,0,LED_DEC,0,1,3,NULL);//倒計時顯示,cnt=0,reload=1,attr=3即從3開始倒計時每秒一次遞減
void ledDec(LED_S *pLed,UINT8 winNum){
	if(pLed->pWin[winNum].attr !=0xfe){//結束條件
		if(ledDelay(pLed,winNum)==TRUE){//延遲時間到
			UINT8 fmt[4]="% d";
			fmt[1]=pLed->pWin[winNum].len +'0';
			if(pLed->pWin[winNum].attr ==0xff || pLed->pWin[winNum].attr ==0xfe){
				writeLed(pLed,winNum,(const char *)fmt,0);
			} else{
				writeLed(pLed,winNum,(const char *)fmt,pLed->pWin[winNum].attr);
			}
			disLedText(pLed,winNum);
			pLed->pWin[winNum].attr--;
		}	
	}

}

buf[1]依次寫入0x0d,0x25,0x9f,0x03,最終結束後attr值為0xfe

嵌入式設計實驗三:Tasket與工作隊列_tasket add_#stm32_04


3,移動顯示仿真

setWinNum(&Dleds,1);//只有一個窗口
	setWinSize(&Dleds,0,0,8);//對窗口0設置顯存初始地址為0,長度為8
	setWinCmd(&Dleds,0,LED_MOVE,0,1,3,NULL);//3表示移動字符有三個
	writeLed(&Dleds,0,"%s","123");