(10/09/2009) ตัวอย่างการ เขียนโปรแกรมแบบ Multitask เพื่อแก้ปัญหาการติด loop เมื่อใช้งาน Keymatrix หรือ Keypad
ปล. ขออภัยด้วยครับที่ code ไม่สวยเพราะเดิมทีไม่ตั้งใจจะ opensource (^_^!)
/*************************************************************************** * Status : Complete !! (เสร็จเมื่อ 15/4/2552 (ป่วยการเมืองหลายวัน ไม่ได้กิน ไม่ได้หลับ ไม่ได้นอน)) * Date Start : 9/4/2552 8:34 * Project : Ultimate KeyMatrix (debounce without delay and multiple keypress support !!) * Project Code : KMAT (สำหรับทุก function และ global variable) * Compiler : Arduino - 0015
* Board : DuinoThumb V1, DuinoThumb V2, Pico * MCU : ATMEGA168 * : ATMEGA8 (Arduino SingleBoard àÅ×Í¡ Arduino NG or older w/ ATmega8) * Clock : 12Mhz, 16Mhz * Programmer : Ksemvis Vitoonrach * Email : Manager@DuinoThumb.com * Objective : * - keymatrix แบบไม่รอการหน่วงเวลา (delay) ใช้เป็น polling เพราะบางงานไม่สามารถ delay ได้ * - ใช้ internal r-pullup ไม่ต้องต่อภายนอก * - ตั้งได้ว่าจะให้กดค้างได้ หรือจะให้กดได้ครั้งเดียว * - ใช้กดแบบปุ่มเดียว หรือหลายคีย์พร้อมกันได้ (พร้อมลำดับการกดก่อนหลัง) * - พัฒนาให้เหมาะแก่การ portable * - ง่ายต่อการ reuse ระดับ source code คือ copy ไปรวมกับ code งานเดิมที่มีอยู่ได้ทันที ไม่ต้องแก้ไข * * * หลักการ * - ใช้วิธีตรวจสอบการกดปุ่ม ถ้ามีการกดปุ่มแล้ว จะเริ่มการนับเวลา และจะวนรอบนับเวลาไปเรื่อยๆ * จนกว่าการนับเวลา จะเท่ากับเวลาที่ตั้งเป็น debounce (ใช้การนับเวลา ไม่ใช้ delay ในการตรวจสอบ) จึงจะถือว่ากดปุ่มจริง ! * จากนั้นจะนำค่าปุ่มที่ได้ ไปเก็บลง buffer การทำแบบนี้จึงไม่ติด loop จึงสามารถทำงานได้หลายๆ ปุ่มพร้อมๆ กัน และใช้งานควบคู่ไปกับ * watch dog หรือ งานอื่นๆ ใน code โดยไม่ต้องกังวลว่าจะไปติดลูปตายที่ไหน * * * Update : 10/4/2552 22:46 * - แก้ bug ที่ปุ่ม 1 กับ 24 มีช่วงเวลาไม่เท่ากัน (ใช้ตรวจก่อนว่าครบ 1ms หรือยัง ถ้าครบแล้วค่อยบวก เพราะ MCU ทำงานไวเกินไป) * - ปรับปรุงเงื่อนไขการวนลูบให้ดีขึ้น ใช้บวก loop ที่บรรทัดสุดท้าย เพราะเมื่อหลุดจาก loop จะไม่ต้องเริ่มขาเดิมอีกครั้ง * - รองรับการกดหลายปุ่มพร้อมกัน ในคนละ Rows ได้ แต่ columns เดียวกันไม่ได้ (ปัญหาวงจร ที่เป็นแบบ matrix แก้ไม่ได้) * * * สิ่งที่ต้องพัฒนาเพิ่ม * x จำลองเป็น Slave I2C ได้ เพื่อสามารถสั่งจาก Master MCU * -> ยกเลิก เนื่องจากเห็นว่ามันนิ่งแล้ว ส่วนเรื่อง I2C จะแยกไปเขียนเป็นอีกไฟล์หนึ่งเป็น i2c Master & Slave implement * เวลาใช้งาน จะใช้วิธี copy source (implement) แล้วแก้ไข จะได้ไม่สับสน * * แนะนำ * - เวลาทดลองให้เปิดใน hyperterminal ถ้าเปิดใน Arduino Serialport ดูเหมือนว่าจะ ส่งข้อมูลผิดพลาดเยอะ * แต่เปิดใน hyperterminal เปิดได้ 2 ชม. ไม่มี error * * * BUG * - ยังไม่ค้นพบ * ********************************************************************** * * [วิธีการใช้งาน] (step by step) * ********************************************************************** * 1. copy ตั้งแต่ต้น ไปจนสิ้นสุดเพื่อนำไปใช้ (เว้นส่วน setup() และ loop() ไว้) * 2. เปลี่ยนค่า KMAT_ALL_ROWS, KMAT_ALL_COLS ตามต้องการ * 3. กำหนดค่าขาให้ตรงแล้วเรียกใช้ KMAT_setup() * * [หมายเหตุการใช้งาน] * - จงค้นหาคำว่า "(แก้ไข)" หมายถึง ให้แก้ไขค่าให้ตรงกับความเป็นจริง * - จงค้นหาคำว่า "(แก้ไขก็ได้)" หมายถึง ค่าที่สามารถ แก้ไข หรือจะปล่อยผ่านก็ได้ * ********************************************************************** * * [ข้อจำกัด] * - จอง array เป็นแบบตายตัวสำหรับ columns และ rows ถ้าอยากค่าอื่นต้องแก้ constant * * [รูปแบบการต่อลายวงจร] * * Row N ----[B N]-----[B N]------[B N]-------[B N]---- * | | | | * | | | | * Row 1 ----[B 5]-----[B 6]-------[B 7]-------[B 8]---- * | | | | * | | | | * Row 0 ----[B 1]-----[B 2]-------[B 3]-------[B 4]---- * ^ | | | | * | | | | | * (0) - > Col 0 Col 1 Col 2 Col N * * อธิบาย : * - Row0 และ Col0 จะเริ่มที่มุมซ้าย B คือค่าที่จะ return กลับเป็น byte * - ค่า index ของ array ที่ใช้นับเวลา debounce สามารถคำนวนได้จากสูตร * (RowX * Column_size) + ColumnX * - ค่า return สามารถคำนวนได้จากสูตร * (RowX * Column_size) + ColumnX + 1 * - ถ้าไม่มีการกด key จะคืนค่า 0 * - ขา Rows จะเป็น output , ขา Cols จะเป็น input */
/*************************** Define and Constant ***************************/ #define KMAT_DEBUG true // เปิด(true)/ปิด(false) ระบบ debug (แก้ไข) // ถ้า true จะส่ง output มาที่ UART 19200 const byte KMAT_ALL_ROWS = 4; // จำนวน แถวทั้งหมด (แก้ไข) const byte KMAT_ALL_COLS = 6; // จำนวน คอลัมน์ทั้งหมด (แก้ไข) const byte KMAT_ALL_KEYS = KMAT_ALL_ROWS * KMAT_ALL_COLS; // จำนวนคีย์ทั้งหมด const byte KMAT_KEYBUFFER_MAX = 6; // กด key พร้อมกันได้สูงสุด 6 ปุ่ม เก็บลง buffer (แก้ไข)
/*************************** Global Define *********************************/ byte KMAT_leg_rows[KMAT_ALL_ROWS]; // เก็บขาที่ใช้เป็นแถว byte KMAT_leg_cols[KMAT_ALL_COLS]; // เก็บขาที่ใช้เป็น columns byte KMAT_timer_counter[KMAT_ALL_KEYS]; // ตัวนับเวลาที่ผ่านไปหลังการกด key (0=ไม่ได้กด, 1-250=เวลา debounce, >250=สงวนไว้สถานะอื่น byte KMAT_debounce_ms; // เวลาที่จะใช้ตรวจการ debounce (milliseconds) มีค่าได้ 1-250 unsigned long KMAT_global_ms; // เก็บค่าเวลาที่ผ่านไปของระบบ byte KMAT_keybuffer[KMAT_KEYBUFFER_MAX]; // keyboard buffer byte KMAT_keybuffer_index; // บอกว่าตอนนี้มี key ในหน่วยความจำกี่ตัวแล้ว
// เมื่อทำงานอีกครั้งจะเริ่มนับปุ่มต่อไป ใช้แก้ปัญหาว่าเมื่อได้ค่าแล้วเด้ง return ทันที จะทำให้กดได้ปุ่มเดียว กดหลายปุ่มพร้อมกันไม่ได้ // ที่เป็นเช่นนี้เพราะเราจะอ่านค่าที่ละ pin ไม่ได้อ่านทีละ port // ถ้าเป็น Global มันจะเริ่มตรวจที่ขาถัดไปในรอบใหม่ เมื่อครบทุก rows+cols แล้วจะเริ่มใหม่ เสมือนว่ากดหลายปุ่มพร้อมกันได้ byte KMAT_currentRows = 0; // ใช้ใน KMAT_readkey(); byte KMAT_currentCols = 0; // ใช้ใน KMAT_readkey();
/*************************** Global Function *******************************/
// อ่านค่า key ทีละปุ่ม ถ้ามีการกดก็ return, ไม่มีก็ 0 // ถ้า pressOnce = true หมายถึง กดได้ครั้งเดียว จะติดอีกครั้งเมื่อปล่อยมือแล้วกดใหม่ // ถ้า pressOnce = false หมายถึง กดได้กดค้างได้ สถานะจะยังคงส่งออกมาต่อเนื่อง byte KMAT_readkey(byte pressOnce = false) { // ตรวจสอบว่าค่า KMAT_currentRows ครบจำนวน ขาที่มีหรือยัง ถ้าวนรอบครบแล้ว ก็เริ่มใหม่ if (KMAT_currentRows >= KMAT_ALL_ROWS) KMAT_currentRows = 0; // เริ่มการอ่านค่า วนลูปส่งค่า 0 for ( ;KMAT_currentRows < KMAT_ALL_ROWS; ) // บวก rows ไปไว้ล่างสุดของ loop { digitalWrite(KMAT_leg_rows[KMAT_currentRows], LOW);
// ตรวจสอบว่าค่า KMAT_currentCols ครบจำนวน ขาที่มีหรือยัง ถ้าวนรอบครบแล้ว ก็เริ่มใหม่ (เหมือน KMAT_currentRows) if (KMAT_currentCols >= KMAT_ALL_COLS) KMAT_currentCols = 0; // เริ่มอ่านค่าการกดจากฝั่ง Cols for ( ;KMAT_currentCols < KMAT_ALL_COLS; ) // บวก cols ไปไว้ล่างสุดของ loop { // ตำแหน่ง array index ที่จะใช้ตรวจสอบเก็บค่า debounce จากการกดปุ่ม byte index = (KMAT_currentRows * KMAT_ALL_COLS) + KMAT_currentCols; // ตรวจได้ว่ามีการกดปุ่ม if (digitalRead(KMAT_leg_cols[KMAT_currentCols]) == LOW) { // // จัดการส่วน debounce // // ตรวจว่าก่อนหน้านี้มีการเริ่มนับเวลาไปแล้ว(จนเสร็จแล้ว) และเป็นกรณี pressOnce // คือได้มีการ debounce มาก่อนหน้านี้จนครบเวลาแล้ว แต่ยังไม่ยอมยกมือ if (KMAT_timer_counter[index] >= KMAT_debounce_ms && pressOnce) { // ไม่ต้องทำอะไร เมื่อยกมือจะเคลียร์ค่าเอง } // พบว่ามีการกดปุ่มแล้ว แต่ยังไม่ครบเวลา จึงตรวจว่าก่อนหน้านี้มีการเริ่มนับเวลาหรือยัง แบบปกติ else if (KMAT_timer_counter[index] > 0) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ก่อนใช้เวลาระบบ ต้องตรวจ millis ว่าเกิน 49 วัน 9 ชม.หรือไม่ เพราะกรณีเกินกว่า มันจะเริ่ม 0 ใหม่ (ค่าจะน้อยกว่า KMAT_global_ms) if (KMAT_global_ms > millis()) KMAT_global_ms = millis(); // ปรับเวลาใหม่ // สาเหตุที่นำมาไว้ตรงนี้ เพราะว่าถ้าไว้นอกลูปมันจะตรวจเกือบตลอดเวลา ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // เอาเวลาที่ผ่านไป มาบวก , ถ้าเริ่มนับไปแล้ว ตรวจว่าเวลามากเท่า debounce หรือยัง // โดยเอาเวลาปัจจุบัน + กับเวลาที่กด = เวลาที่ผ่านไปมา if (millis() - KMAT_global_ms > 1) { KMAT_timer_counter[index] += (byte)(millis() - KMAT_global_ms); // บันทึกเวลาล่าสุดที่ทำงาน KMAT_global_ms = millis(); } // ตรวจเวลาครบ debounce แล้ว มั่นใจว่ากดจริง ก็ให้ return ค่า if (KMAT_timer_counter[index] >= KMAT_debounce_ms) { // clear ค่าเวลา กรณี pressOnce = false จะกดค้างได้ การทำให้เป็น 0 เมื่อรอบหน้าจะเข้าloop เดิม จึงถือเป็นการกดค้าง // (*** หากไม่เคลียร์ค่า สามารถเอาไปตรวจการที่ยังไม่ยกมือขึ้นได้) if (!pressOnce) KMAT_timer_counter[index] = 0; // คืนค่า port มิเช่นนั้นจะ กดตลอดกาล digitalWrite(KMAT_leg_rows[KMAT_currentRows], HIGH); // เพิ่ม cols อีก 1 (แต่ไม่ต้องไปเพิ่มตรง for..loop) มิเช่นนั้น ถ้าหลุดออกไปจะกลับมาซ้ำรอบเดิมอีกหนก่อนบวก KMAT_currentCols++; return index + 1; } } else // ยังไม่ได้เริ่มนับ ก็สั่งให้เริ่มนับ { KMAT_timer_counter[index] = 1; // บันทึกเวลาล่าสุดที่ทำงาน KMAT_global_ms = millis(); } } else // ตรงนี้ตรวจแล้ว ไม่ใช่การกดปุ่ม (ถือว่าเป็นการ unpress) { KMAT_timer_counter[index] = 0; } // เพิ่ม cols อีก 1 (แต่ไม่ต้องไปเพิ่มตรง for..loop) มิเช่นนั้น ถ้าหลุดออกไปจะกลับมาซ้ำรอบเดิมอีกหนก่อนบวก KMAT_currentCols++; } // ครบรอบ Row 1 แถว ก็ให้คืนค่า port Row มิเช่นนั้นจะ ถือว่า "กด" ตลอดกาล digitalWrite(KMAT_leg_rows[KMAT_currentRows], HIGH); // เพิ่ม rows อีกแถว (แต่ไม่ต้องไปเพิ่มตรง for..loop) มิเช่นนั้น ถ้าหลุดออกไปจะกลับมาซ้ำรอบเดิมอีกหนก่อนบวก KMAT_currentRows++; } // ทำงานจนเสร็จครบทุกรอบ รอบนี้ถือว่าไม่มีการกดปุ่มใดๆ return 0 return 0; }
// ใช้แสดงผลข้อมูลใน buffer // ถ้า KMAT_DEBUG == true จะเปิดใช้ UART // ถ้า KMAT_DEBUG == false function นี้เอาออกไปได้เลย เพื่อประหยัด Flash #ifdef KMAT_DEBUG #if KMAT_DEBUG void printkeybuff() // for test { for(byte count = 0; count < KMAT_KEYBUFFER_MAX; count++) { Serial.print( KMAT_keybuffer[count],HEX ); Serial.print(","); } Serial.println(); } #endif #endif
// ลบค่าใน keybuffer void KMAT_clear_keybuffer() { for(byte count = 0; count < KMAT_KEYBUFFER_MAX; count++) KMAT_keybuffer[count] = 0; // เริ่มตัวชี้ index ของ keyboard buffer KMAT_keybuffer_index = 0; }
// ลบข้อมูล keybuffer array ตาม index ที่ระบุ แล้วเลื่อนลำดับต่อไปขึ้นมา void KMAT_remove_keybuffer(byte index) { for (byte count = index; count < KMAT_keybuffer_index; count++) KMAT_keybuffer[count] = KMAT_keybuffer[count+1]; // เมื่อสลับจนสิ้นสุดแล้ว ข้อมูลสุดท้ายเป็น 0 และ ลดค่า index อีก 1 KMAT_keybuffer[--KMAT_keybuffer_index] = 0; }
// ค้นหาค่า key ว่ามีค่าที่กำหนดใน buffer หรือไม่ // (เริ่มต้นที่ 1..n) // return เป็นตำแหน่ง index ของ array, ถ้าได้น้อยกว่า 0 คือไม่มี char KMAT_findKeyInBuff(byte keyValue) { for(byte index = 0; index < KMAT_KEYBUFFER_MAX; index++) if (KMAT_keybuffer[index] == keyValue) return index; return -1; }
// add ค่า key เก็บไว้ใน buffer (function ย่อยของ KMAT_multiple_readkey()) // keyadd = ค่าเลขปุ่มที่ต้องการเก็บ 1 เป็นค่าแรก (ถ้าจะใช้เป็น index ของ array ต้องลบ 1) void KMAT_addkey_to_buffer( byte keyadd) { // ถ้าปุ่มมากกว่า KMAT_ALL_KEYS หรือ น้อยกว่า หรือ = 0 ให้ return ไม่ต้อง save if ( (keyadd > KMAT_ALL_KEYS) || (keyadd <= 0) ) return; // ถ้าเกินกว่าจะรับได้ if (KMAT_keybuffer_index >= KMAT_KEYBUFFER_MAX) return; // วนลูปตรวจว่ามีข้อมูลซ้ำหรือไม่ ถ้ามีก็ไม่ add for (byte loopChkExist = 0; loopChkExist < KMAT_KEYBUFFER_MAX; loopChkExist++) if (KMAT_keybuffer[loopChkExist] == keyadd) return; // ถ้าไม่ซ้ำเลย จึง add key KMAT_keybuffer[KMAT_keybuffer_index++] = keyadd; }
// อ่านค่าแบบหลายปุ่ม output จะข้อมูลเก็บไว้ที่ KMAT_keybuffer[] void KMAT_multiple_readkey() { KMAT_readkey(true); // เมื่อครบ 1 รอบก็วน loop จนครบทุกปุ่ม เพื่อจะดูว่ามีปุ่มใดยกมืออออกไปแล้วบ้าง for (byte index = 0; index < KMAT_ALL_KEYS; index++) { // ดูว่าปุ่มใดกดจน debounce เสร็จแล้ว ก็ให้ add key if (KMAT_timer_counter[index] >= KMAT_debounce_ms) { KMAT_addkey_to_buffer( index+1 ); } // ดูว่าปุ่มใดยกมือออกมาแล้ว else if (KMAT_timer_counter[index] == 0) { // ดูว่ามีข้อมูลเก่าใน buffer หรือไม่ ถ้ามีก็เอาออกไป for (byte y = 0; y < KMAT_KEYBUFFER_MAX; y++) { // ค้นหาข้อมูลเก่าที่ไม่ได้กดปุ่มแล้ว ให้เอาข้อมูลออกไป if (KMAT_keybuffer[y] == index+1) // เพราะค่า index ของ array น้อยกว่าค่าปุ่มอยู่ 1 KMAT_remove_keybuffer(y); } } } }
// กำหนดค่าให้ keymatrix // arrRows = array เก็บเลขขา rows แบบเรียงลำดับ // arrCols = array เก็บเลขขา cols แบบเรียงลำดับ // debounce_ms = ระยะเวลา (ms) สำหรับ debounce void KMAT_setup(byte arrRows[], byte arrCols[], byte debounce_ms) { byte count = 0; // กำหนดค่า ขา rows for (count = 0; count < KMAT_ALL_ROWS; count++) { KMAT_leg_rows[count] = arrRows[count]; pinMode(arrRows[count],OUTPUT); digitalWrite(arrRows[count], HIGH); } // กำหนดค่า ขา columns for (count = 0; count < KMAT_ALL_COLS; count++) { KMAT_leg_cols[count] = arrCols[count]; // เปิด internal r-pull up pinMode(arrCols[count],OUTPUT); digitalWrite(arrCols[count],HIGH); // เปลี่ยน mode pinMode(arrCols[count],INPUT); } // init var KMAT_debounce_ms = debounce_ms > 250 ? 250 : debounce_ms; KMAT_global_ms = millis(); // clear keybuffer KMAT_clear_keybuffer(); }
/*************************** End Keymatrix Part ****************************/
/*************************** Example ***************************************/ #ifdef KMAT_DEBUG #if KMAT_DEBUG == false void setup(){}; void loop(){}; #endif #endif #ifdef KMAT_DEBUG #if KMAT_DEBUG
const byte PIN_JUMPER = 12;
void setup() { // ตัวอย่างการกำหนดขาต่างๆ byte rowslegs[] = {2,3,4,5}; // กำหนดขา Rows byte colslegs[] = {6,7,8,9,10,11}; // กำหนดขา Cols KMAT_setup(rowslegs, colslegs, 30); // เรียก setup Serial.begin(19200); // เรียกใช้ Serial port ถ้าไม่ใช้ไม่ต้องใส่เพื่อประหยัด flash // // ถ้า short ขา 12 กับ 13 เข้าด้วยกันจะเป็นการอนุญาติให้กดค้างได้ // // เปิด r-pull up และทำหน้าที่แทน jumper pinMode(PIN_JUMPER, OUTPUT); digitalWrite(PIN_JUMPER, HIGH); pinMode(PIN_JUMPER,INPUT); // ทำ output pinMode(PIN_JUMPER+1, OUTPUT); digitalWrite(PIN_JUMPER+1, LOW); }
void loop() { // example 1 : ทดสอบการทำงานของ function ต่างๆ Serial.println("start..."); printkeybuff(); // for test // ทดสอบการจำลองการส่งค่าปุ่มไปเก็บใน buffer KMAT_addkey_to_buffer(1); KMAT_addkey_to_buffer(3); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(7); Serial.println("add 1357"); printkeybuff(); // for test // ทดสอบ clear buffer Serial.println("clear.."); KMAT_clear_keybuffer(); printkeybuff(); // for test
KMAT_addkey_to_buffer(1); KMAT_addkey_to_buffer(3); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(7); Serial.println("add 1357 again"); printkeybuff(); // for test // ลบ buffer บางตำแหน่ง KMAT_remove_keybuffer(2); Serial.println("remove index 2"); printkeybuff(); // for test KMAT_remove_keybuffer(1); Serial.println("remove index 1"); printkeybuff(); // for test KMAT_addkey_to_buffer(8); KMAT_addkey_to_buffer(9); KMAT_addkey_to_buffer(9); Serial.println("add 899 (add only first 9 becuase can't add exist key)"); printkeybuff(); // for test KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(5); KMAT_addkey_to_buffer(5); Serial.println("add many more (555555)"); printkeybuff(); // for test KMAT_addkey_to_buffer(99); Serial.println("add many more than max_key (99)"); printkeybuff(); // for test KMAT_addkey_to_buffer(13); KMAT_addkey_to_buffer(14); KMAT_addkey_to_buffer(15); Serial.println("test add until max buffer by ad 13,14,15"); printkeybuff(); // for test // ส่วนของการตรวจสอบการกด byte readkey = 0; Serial.println("---------------------------------" ); while(true) { KMAT_multiple_readkey(); if(KMAT_keybuffer[0] > 0) printkeybuff(); }
; /* // ---------------------------------------------------------------------------------------------------------------------- // example 2 : ทดสอบการทำงานของปุ่มกด และเป็นตัวอย่างการเรียกใช้ byte readkey = 0;
Serial.println("KeyMatrix without debounce"); Serial.println("--------------------------"); while(true) { // // ถ้า short ขา 12 กับ 13 เข้าด้วยกันจะเป็นการอนุญาติให้กดค้างได้ // if (digitalRead(PIN_JUMPER) == HIGH) readkey = KMAT_readkey(true); else readkey = KMAT_readkey(false); if (readkey != 0) { Serial.print(readkey + 0); // +0 เพื่อให้เปลี่ยนเป็นตัวอักษร Serial.print(","); } } ; */ } #endif #endif
|
|