Avr จับเวลาขัดจังหวะโดยบังเอิญ หลักสูตรการฝึกอบรม AVR จับเวลา - เคาน์เตอร์ T0 ลงทะเบียน ส่วนที่ 1. ตัวจับเวลาสามารถทำอะไรได้บ้าง?

มันค่อนข้างง่าย หากคุณขี้เกียจอ่านเกินไป เพียงดาวน์โหลดตัวอย่างที่แนบมาแล้วดูในขณะที่ฉันอ่านต่อ

นี่มีไว้เพื่ออะไร?

การนับเวลาโดยทางโปรแกรมในส่วนเนื้อหาของโปรแกรมหลักไม่ใช่วิธีที่ดีที่สุด ในการนับวินาที โปรแกรมจะไม่ทำอะไรเลยนอกจากนับวินาทีนั้น

ตลอดเวลานี้อุปกรณ์ของคุณจะไม่ตอบสนองต่อปุ่มต่างๆ จะไม่แสดงข้อมูลบนตัวบ่งชี้ และจะไม่ทำอะไรเลยเพื่อไม่ให้สูญเสียการนับ แต่คุณต้องนับเวลาและต้องการความแม่นยำมากขึ้น

ทุกครั้งที่นับอย่างน้อยหนึ่งมิลลิวินาที ดูเหมือนว่าโปรแกรมจะหยุดทำงานในครั้งนี้ – เครื่องจะค้างนานถึงหนึ่งเดือนหรือไม่?จะทำอย่างไร?

เป็นการดีที่สุดที่จะมอบความไว้วางใจในการนับช่วงเวลาขั้นต่ำให้กับตัวจับเวลาที่มีตัวเปรียบเทียบ นั่นคือเครื่องจับเวลาพร้อมตัวเลขที่ตั้งไว้ล่วงหน้าสำหรับการนับ

สร้างโปรเจ็กต์ใน MPLAB IDE ด้วยตัวจับเวลา TMR2

มาเลือกคอนโทรลเลอร์ 16F628A กันดีกว่า ดาวน์โหลดเอกสารข้อมูลลงไป มีตัวจับเวลาพร้อมตัวเปรียบเทียบ TMR2 มาสร้างโปรเจ็กต์ใน MPLAB กันดีกว่า คุณสามารถค้นหาเทมเพลตบนดิสก์ระบบของคุณได้ เช่น:

C:\Program Files\Microchip\MPASM Suite\Template\Code\16F628ATEMP.ASM

คุณสามารถลบความคิดเห็นที่ไม่จำเป็นในเทมเพลตและเพิ่มความคิดเห็นของคุณเองได้

หากต้องการสร้างโครงการ ควรใช้ Project Wizard ในเมนู MPLAB / Project / Project Wizard...

ลองรวบรวมสิ่งที่เราได้รับ

MPLAB สาบานกับเทมเพลตของตัวเอง!!!??? เรามาลองดูในไฟล์ P16F628A.INC ว่าควรจะเป็นอย่างไร

คุณสามารถค้นหาได้ที่นั่นในแค็ตตาล็อก Microchip:

C:\Program Files\Microchip\MPASM Suite\ P16F628A.INC

คุณสามารถคัดลอกไฟล์นี้ได้ เผื่อว่าไฟล์นี้จะมีประโยชน์สำหรับโปรเจ็กต์ของคุณ

มาดูกันว่ามันเขียนอย่างไรและแก้ไขในเทมเพลต:

กำหนดค่า _CP_OFF ​​​​& _DATA_CP_OFF

บน

กำหนดค่า _CP_OFF ​​& DATA_CP_OFF

ความแตกต่างมีขนาดเล็ก แต่มีนัยสำคัญ ตอนนี้ทุกอย่างเรียบร้อยดีก็คอมไพล์

ไม่มีมโนสาเร่ในการเขียนโปรแกรม ดังนั้นอย่าเชื่อทุกสิ่งที่พวกเขาเขียน ตรวจสอบ :-)

มาเปิดเมนู Debugger / Select Tool / MPLAB SIM Simulator

ใน Debugger / Settings... เลือกความถี่คริสตัล 4 MHz

ใน Debugger เราใช้นาฬิกาจับเวลาแบบ Stopwatch

ในตอนต้นของโปรแกรมหลัก เราจะกำหนดค่าคอนโทรลเลอร์

ก่อนจับเวลา ให้ตั้งค่าพรีสเกลเลอร์: 4 และตัวเลข .249 ในรีจิสเตอร์ที่ตั้งไว้ล่วงหน้า

ตอนนี้เรามี: 4 Mhz / 4 = 1 รอบของเครื่อง = 1 ไมโครวินาที

เราคูณด้วยพรีสเกลเลอร์ 4 และหมายเลขที่ตั้งไว้ล่วงหน้า 250 (จาก 0 ถึง 249) เราจะได้ = 1 มิลลิวินาที

ที่จุดเริ่มต้นของรูทีนย่อยการขัดจังหวะที่เริ่มต้นด้วย ORG 0x004 เราจะเพิ่มการตรวจสอบการขัดจังหวะจาก TMR2 เพื่อไม่ให้สับสนกับการขัดจังหวะอื่นๆ เรายังไม่มีการหยุดชะงักอื่นใด แต่บางทีสิ่งเหล่านั้นอาจปรากฏขึ้นในภายหลัง ดังนั้นจึงเป็นการดีกว่าที่จะทำทันที:

Bcf PIR1,TMR2IF ; การรีเซ็ตการขัดจังหวะตัวจับเวลา TMR2

- และทำเครื่องหมายทันทีโดยดับเบิลคลิกที่บรรทัดที่มีการรีเซ็ตอินเทอร์รัปต์จาก TMR2:

เรารวบรวมโปรแกรมและรันโปรแกรมจำลอง โปรแกรมหยุดอยู่ที่เครื่องหมายของเรา

รีเซ็ตนาฬิกาจับเวลาและเริ่มโปรแกรมจำลองอีกครั้ง

คราวนี้นาฬิกาจับเวลาแสดง 1,000 MC (รอบเครื่องจักร) และต่ำกว่า 1 มิลลิวินาที

สิ่งที่เราต้องการ การขัดจังหวะเกิดขึ้นในช่วงเวลาปกติอย่างแม่นยำที่ 1 มิลลิวินาที

สิ่งที่เรามี

มีเหตุการณ์เกิดขึ้น ตัวควบคุมนับ 1 มิลลิวินาที เรามาตั้งชื่อบรรทัดเหล่านี้กันดีกว่า:

เหตุการณ์_เวลา_1ms

Btfss PIR1,TMR2IF ; ตรวจสอบการขัดจังหวะจาก TMR2

ไปที่ other_interrupts ; มิฉะนั้นเราจะดำเนินการตรวจสอบการขัดจังหวะอื่นๆ

Bcf PIR1,TMR2IF ; รีเซ็ตตัวจับเวลาขัดจังหวะ

ทุกอย่างด้านล่างจะเกิดขึ้นที่ความถี่ 1 มิลลิวินาที

ที่นี่ คุณสามารถยกแฟล็กที่ความถี่นี้สำหรับรูทีนย่อยที่ต้องการแฟล็กดังกล่าว

กิจวัตรเหล่านี้จะสามารถดำเนินการทุก ๆ มิลลิวินาทีหรือนับถอยหลังที่จำเป็น

จำนวนมิลลิวินาที พวกเขาสามารถดำเนินการในเวลาที่หารด้วยหนึ่งมิลลิวินาที ตัวอย่างเช่น,

ทุกๆ 17 มิลลิวินาที

จะสะดวกกว่าถ้าวางตัวนับในการขัดจังหวะที่เป็นผลคูณของช่วงเวลามาตรฐานหรือช่วงเวลาที่สะดวกบางช่วง

เวลา. ตัวอย่างเช่น 10 ms, 0.1 วินาที, 1 วินาที, 1 นาที, 1 ชั่วโมง เป็นต้น

อย่างไรก็ตาม หากต้องการ คุณสามารถเพิ่มตัวนับสำหรับ 17 มิลลิวินาทีเดียวกันตรงนั้นในช่วงขัดจังหวะได้

การเพิ่มตัวนับเวลา

ในแต่ละช่วงเวลา คุณจะต้องมีทะเบียนการนับ:

Reg_Time_10ms ; การลงทะเบียนตัวนับเวลา

Reg_Time_01วินาที

Reg_Time_1วินาที

Reg_Time_1นาที

Reg_Time_1ชั่วโมง

Reg_Time_1วัน

เราจะโหลดค่าคงที่ลงในรีจิสเตอร์เหล่านี้ ลองตั้งชื่อตามนั้น

#กำหนด TIME_10ms .10 ; ค่าคงที่สำหรับการลงทะเบียนตัวนับเวลา

#กำหนด TIME_01sec .10 ;

#กำหนด TIME_1 วินาที .10 ;

#กำหนด TIME_1 นาที .60 ;

#กำหนด TIME_1ชั่วโมง .60 ;

#กำหนด TIME_1day .24 ;

ค่าคงที่ทั้งหมดนี้จะต้องโหลดลงในรีจิสเตอร์ที่เหมาะสมเมื่อเริ่มต้นโปรแกรม

เราจำกัดตัวเองไว้แค่วันๆ เพราะว่า... พวกมันเหมือนกันหมด คนละ 24 ชั่วโมง สัปดาห์ก็เหมือนกันแต่ไม่ค่อยนับเป็นสัปดาห์

ยกเว้นการตั้งครรภ์ :-)

ในแต่ละเดือนจะซับซ้อนมากขึ้น จำนวนวันแตกต่างกัน การนับจะซับซ้อนมากขึ้นและไม่เหมาะกับตัวอย่าง ต่อไป

ใช้ชิปเรียลไทม์เช่น PCF8583 ฯลฯ ได้ง่ายกว่า

ตัวนับสามารถมีช่วงเวลาอื่นได้ ไม่ใช่ 10 ms แต่ 100 ms เป็นต้น มันเป็นสิ่งที่เหมาะกับคุณ

ดูไฟล์แนบ Time_intervals1.asm

มาเขียนตัวนับในรูปแบบนี้:

เหตุการณ์_เวลา_10ms

Decfsz reg_Time_10ms,F ; ลบ 1 ถ้าไม่ใช่ศูนย์ ให้ข้ามคำสั่งถัดไป

ไปที่ other_interrupts ; มิฉะนั้นเราจะดำเนินการตรวจสอบการขัดจังหวะอื่นๆ

เคลื่อนที่ TIME_10ms ; กำลังโหลดค่าคงที่

Movwf reg_Time_10ms ; กลับไปลงทะเบียน

เคาน์เตอร์อื่นๆ ทั้งหมดจะเหมือนกัน

ตอนนี้ หากคุณตั้งค่าเบรกพอยต์ที่ส่วนท้ายของตัวนับใดๆ (ตั้งค่าเพียงครั้งละหนึ่งเบรกพอยต์)

โปรแกรมจะหยุดอยู่แค่นั้น

ด้วยความถี่ที่เหมาะสม

จะใช้ตัวนับซอฟต์แวร์ได้อย่างไร?

มาสร้างแบบจำลองด้วย LED ใน Proteus แล้วกระพริบตากัน ดูไฟล์ Time_intervals1.DSN

แน่นอนคุณสามารถสลับพินพอร์ตได้โดยตรงในการขัดจังหวะ แต่เราจะทำแตกต่างออกไป

ลองเลือกการลงทะเบียนอื่นสำหรับตัวบ่งชี้และเรียกมันว่าตัวบ่งชี้

ตัวบ่งชี้จะเปลี่ยนเมื่อจำเป็นเท่านั้น

เราเขียนรูทีนย่อย LED_Indicator

ที่จุดเริ่มต้นของรูทีนย่อย จะมีการตรวจสอบแฟล็กเหตุการณ์ EV_indicator และรูทีนย่อยจะดำเนินการต่อไป

ก็ต่อเมื่อเหตุการณ์นี้เกิดขึ้นและมีการชักธงเท่านั้น

เราจัดระเบียบสวิตช์ LED หนึ่งครั้งต่อวินาที เมื่อต้องการทำเช่นนี้ ให้ตั้งค่าสถานะ LED_1sec หลังจากนั้น

การหยุดชะงักใน 1 วินาที

เหตุการณ์นี้จะต้องได้รับการประมวลผล มาเขียนโปรแกรมอีกตัวหนึ่ง Switch_ LED_1sec

เช่นเดียวกับรูทีนก่อนหน้านี้ มันจะตรวจสอบแฟล็กเหตุการณ์ EV_LED_1 วินาที

สลับ LED บนตัวบ่งชี้ด้วยมาสก์ LED_1sec

เคลื่อนที่ LED_1 วินาที ; สลับ LED_1 วินาที

ตัวบ่งชี้ Xorwf,F ; บนตัวบ่งชี้

ในตอนท้ายของรูทีนย่อย เราจะเพิ่มแฟล็กเหตุการณ์สำหรับตัวบ่งชี้ EV_indicator

จะเกิดอะไรขึ้นถ้าจำเป็นต้องเปลี่ยน LED ไม่ใช่ทุกวินาที แต่มีความถี่ 1 วินาทีนั่นคือ สลับทุกๆ 0.5 วินาที? เราเลือกรีจิสเตอร์และทำการแทรกสำหรับเคาน์เตอร์แยกต่างหาก ความถี่สัญญาณนาฬิกาสามารถเลือกได้เป็น 0.1 วินาที คูณด้วย 5 หรือ 0.01 วินาที คูณด้วย 50 แล้วเราจะเอามันไป ค่าคงที่ที่เราได้รับคือ 50 เราวางตัวนับไว้ที่ตำแหน่งที่ยกธงเป็นเวลา 10 มิลลิวินาที ตรงท้ายเคาน์เตอร์เราก็ชูธงเช่นเคย ใช่ และอย่าลืมค่าที่ตั้งไว้ล่วงหน้าเมื่อเริ่มต้นโปรแกรม

เหตุการณ์_เวลา_05วินาที

Decfsz reg_Time_05sec,F ; ลบ 1 ถ้าไม่ใช่ศูนย์ ให้ข้ามคำสั่งถัดไป

ไปที่เหตุการณ์_เวลา_01วินาที ; ไม่เช่นนั้นเราจะไปยังเคาน์เตอร์ถัดไป

เคลื่อนที่ TIME_05 วินาที

Movwf reg_Time_05วินาที

เหตุการณ์ Bsf, EV_LED_05 วินาที ; เรายกธงกิจกรรมนี้ทุกๆ 0.5 วินาที

เรามาเชื่อมต่อ LED อื่นและเพิ่มรูทีนย่อยที่สวิตช์มัน

เหตุใดจึงมีรูทีนย่อยแยกต่างหากสำหรับ LED แต่ละตัว นี่เป็นเพียงตัวอย่างเท่านั้น กิจกรรมของเราแตกต่างออกไป และไม่สามารถเชื่อมต่อ LED กับพินนี้ได้ แต่สามารถเชื่อมต่อกับปั๊มหรือพัดลม สัญญาณเตือน หรือเครื่องทำความร้อนได้ และทุกอย่างในโปรแกรมจะเชื่อมต่อแยกกันและทำงานแยกกันโดยไม่รบกวนซึ่งกันและกัน ไม่มีสิ่งใดขัดขวางไม่ให้คุณเชื่อมต่อบล็อก LED และเลือกรูทีนย่อยเพียงอันเดียวสำหรับบล็อกนี้ บล็อกนี้สามารถประกอบด้วย LED หลายร้อยดวง และจะถูกควบคุมผ่านสายไฟ 2-3 เส้นโดยรูทีนย่อยเดียว ซึ่งจะถูกเรียกด้วยแฟล็กเดียวกัน ฉันยังไม่ได้บอกวิธีทำให้ล่าช้าเลยเหรอ? คล้ายกัน. คุณสามารถเลือกหนึ่งรายการขึ้นไป ขึ้นอยู่กับช่วงเวลาที่คุณต้องการนับและความแม่นยำเท่าใด หากอินพุตตัวนับมีรอบสัญญาณนาฬิกา 1 ms ความแม่นยำก็จะเหมาะสม หากคุณต้องการความแม่นยำมากขึ้น ให้นับรอบของเครื่องจักร เรารู้วิธีสร้างส่วนแทรกแล้ว และตัวนับเริ่มต้นอย่างเรียบง่าย โหลดค่าคงที่ลงในตัวนับและรีเซ็ตแฟล็ก เมื่อสิ้นสุดการนับธงจะชูขึ้น

สรุป: ดูเหมือนว่ามีโค้ดมากเกินไป จริงๆแล้วสิ่งนี้ไม่เป็นความจริง บางบรรทัดเขียนสงวนไว้เพื่อนับเศษส่วนของวินาที เป็นนาที ชั่วโมง และวัน ยังไม่ได้ใช้และคุณสามารถลบออกหรือใช้ในโปรแกรมของคุณได้ สิ่งเดียวกับที่ใช้นั้นจะดำเนินการในเวลาที่แน่นอนและรวดเร็วมาก ตัวอย่างเช่น สวิตช์ LED จะทำงานเพียงครั้งเดียวต่อวินาที รูทีนย่อยตัวบ่งชี้ยังทำงานตามความจำเป็นอีกด้วย เช่น ผมสร้างโปรแกรมอื่นขึ้นมา (ดูในโฟลเดอร์ Flasher101) มีตัวจับเวลา 8 ตัวสลับไฟ LED 8 ดวง ไฟ LED ดวงแรกจะกะพริบหนึ่งครั้งต่อวินาที และไฟ LED ถัดไปแต่ละดวงจะกะพริบนานขึ้น 1% นั่นคือหลังจากเปิดใช้งาน 1% x 100 พวกเขาจะกะพริบพร้อมกันอีกครั้ง ผลลัพธ์ที่ได้คือเอฟเฟกต์ภาพที่น่าสนใจ และตัวจับเวลาอื่นจะปิดไฟกะพริบทั้งหมดนี้หลังจากผ่านไป 5 นาที มันกลายเป็นเรื่องง่าย แม่นยำ และมีประสิทธิภาพ

ด้วยความล่าช้าของโปรแกรมปกติ การทำเช่นนี้อาจทำได้ยาก คุณจะสับสนได้ และโดยทั่วไปแล้วจะเป็นไปไม่ได้เลยที่จะเพิ่มสิ่งอื่นใดลงในโปรแกรมดังกล่าวโดยไม่เปลี่ยนอัลกอริธึมก่อนหน้า นอกจากนี้ รูทีนย่อยตัวบ่งชี้ไม่สามารถเรียกได้จากรูทีนย่อยอื่น แต่ตามเวลา เช่น ทุก 10 ms หรือบ่อยกว่านั้น นี่อาจจำเป็นสำหรับการแสดงผลแบบไดนามิก หรือในทางกลับกันสามารถเรียกได้วินาทีละครั้งเพื่อให้การอ่านไม่สั่นไหวและอ่านได้สะดวก นี่อาจจำเป็นเมื่อพารามิเตอร์ที่แสดงมีการเปลี่ยนแปลงอย่างรวดเร็ว คุณยังสามารถรวมการแสดงผลแบบไดนามิกและการเปลี่ยนแปลงการอ่านปัจจุบันได้หนึ่งครั้งต่อวินาที นั่นคือเรามีช่วงเวลาในการรวมกันใดๆ และไม่เพียงแต่สำหรับการแสดงผลเท่านั้น แต่ยังสำหรับการสแกนแป้นพิมพ์ ตัวเข้ารหัส การทำงานกับเซ็นเซอร์ ฯลฯ และทั้งหมดนี้ คอนโทรลเลอร์จะมีเวลาคอมพิวเตอร์เหลืออีกมากสำหรับความต้องการและการคำนวณอื่นๆ และรูทีนย่อยที่ได้รับการปรับแต่งอย่างระมัดระวังเพื่อทำงานกับแฟล็กจะถูกโอนไปยังโปรเจ็กต์อื่นได้อย่างง่ายดาย

โชคดีทุกคน! สามารถใช้ไฟล์ต้นฉบับ โครงการใน Proteus และเอกสารอื่นๆ สำหรับบทความนี้ได้




ATMega16 MCU มีไทเมอร์/เคาน์เตอร์สามตัว - 8 บิตสองตัว (Timer/Counter0, Timer/Counter2) และ 16 บิตหนึ่งตัว (Timer/Counter1) แต่ละรายการมีรีจิสเตอร์พิเศษ ซึ่งหนึ่งในนั้นคือรีจิสเตอร์การนับ TCNn (n คือตัวเลข 0, 1 หรือ 2) แต่ละครั้งที่โปรเซสเซอร์ดำเนินการหนึ่งคำสั่ง เนื้อหาของรีจิสเตอร์นี้จะเพิ่มขึ้นทีละหนึ่ง (ทุกๆ 8, 64, 256 หรือ 1,024 รอบสัญญาณนาฬิกา) จึงเรียกว่าการนับ นอกจากนั้นยังมีการลงทะเบียนการเปรียบเทียบ OCRn (Output Compare Register) ซึ่งเราสามารถเขียนตัวเลขใดก็ได้ด้วยตัวเอง ตัวนับ 8 บิตมีรีจิสเตอร์ 8 บิต ขณะที่โปรแกรมทำงาน เนื้อหาของ TCNN จะเพิ่มขึ้น และเมื่อถึงจุดหนึ่งก็จะตรงกับเนื้อหาของ OCRn จากนั้น (หากระบุพารามิเตอร์พิเศษ) ในการลงทะเบียนแฟล็กขัดจังหวะ TIFR (ตัวจับเวลา/ตัวนับค่าสถานะขัดจังหวะตัวนับ) หนึ่งในบิตจะเท่ากับหนึ่งและตัวประมวลผลเมื่อเห็นคำขอขัดจังหวะ จะแยกออกจากการดำเนินการวนซ้ำไม่มีที่สิ้นสุดทันทีและไป เพื่อให้บริการตัวจับเวลาขัดจังหวะ หลังจากนั้นให้ทำซ้ำขั้นตอนนี้

ด้านล่างนี้คือแผนภาพเวลาของโหมด CTC (Clear Timer on Compare) ในโหมดนี้ รีจิสเตอร์การนับจะถูกล้างเมื่อเนื้อหาของ TCNn และ OCRn ตรงกัน และระยะเวลาการโทรขัดจังหวะจะเปลี่ยนไปตามนั้น

นี่ยังห่างไกลจากโหมดการทำงานเพียงโหมดเดียวของตัวจับเวลา/ตัวนับ คุณไม่จำเป็นต้องล้างการลงทะเบียนการนับในขณะที่มีการแข่งขัน จากนั้นนี่จะเป็นโหมดการสร้างการมอดูเลตความกว้างพัลส์ ซึ่งเราจะพิจารณาในบทความถัดไป คุณสามารถเปลี่ยนทิศทางการนับได้ เช่น เนื้อหาของรีจิสเตอร์การนับจะลดลงเมื่อโปรแกรมทำงาน นอกจากนี้ยังสามารถนับไม่ได้ด้วยจำนวนคำสั่งที่ดำเนินการโดยโปรเซสเซอร์ แต่ด้วยจำนวนการเปลี่ยนแปลงระดับแรงดันไฟฟ้าบน "ขา" T0 หรือ T1 (โหมดตัวนับ) คุณสามารถทำได้โดยอัตโนมัติโดยไม่ต้องมีส่วนร่วมของโปรเซสเซอร์ , เปลี่ยนสถานะของขา OCn ขึ้นอยู่กับสถานะของตัวจับเวลา Timer/Counter1 สามารถเปรียบเทียบสองช่องสัญญาณพร้อมกัน - A หรือ B

ในการเริ่มจับเวลาคุณต้องตั้งค่าบิตที่เกี่ยวข้องในรีจิสเตอร์ควบคุมตัวจับเวลา TCCRn (Timer/Counter Control Register) หลังจากนั้นจะเริ่มทำงานทันที

เราจะพิจารณาเฉพาะโหมดการทำงานของตัวจับเวลาบางโหมดเท่านั้น หากคุณต้องการทำงานในโหมดอื่นให้อ่านเอกสารข้อมูลสำหรับ ATMega16 - ทุกอย่างเขียนไว้อย่างละเอียดเป็นภาษาอังกฤษแม้กระทั่งตัวอย่างของโปรแกรมใน C และแอสเซมเบลอร์ก็มีให้ (ไม่ใช่เพื่ออะไรที่จะพิมพ์ถึง 357 หน้า ข้อความ!).

ตอนนี้เรามาดูปุ่มต่างๆ กัน

หากเราจะใช้ปุ่มจำนวนเล็กน้อย (มากถึง 9 ชิ้น) ก็ควรเชื่อมต่อปุ่มเหล่านั้นระหว่างกราวด์กับพินของพอร์ตไมโครคอนโทรลเลอร์ ในกรณีนี้ คุณควรสร้างพินอินพุตโดยการตั้งค่าบิตที่สอดคล้องกันในรีจิสเตอร์ DDRx และเปิดตัวต้านทานแบบดึงขึ้นภายในโดยการตั้งค่าบิตในรีจิสเตอร์ PORTx ในกรณีนี้แรงดันไฟฟ้าบน "ขา" เหล่านี้จะเป็น 5 V เมื่อกดปุ่มอินพุต MK จะถูกปิดไปที่ GND และแรงดันไฟฟ้าบนนั้นจะลดลงเหลือศูนย์ (หรืออาจเป็นอย่างอื่น - เอาต์พุต MK สั้นลงสู่พื้นดินในสภาวะหดหู่) สิ่งนี้จะเปลี่ยนการลงทะเบียน PINx ซึ่งจะจัดเก็บสถานะปัจจุบันของพอร์ต (ต่างจาก PORTx ซึ่งจะตั้งค่าสถานะของพอร์ตเมื่อไม่มีโหลด กล่าวคือ ก่อนที่จะกดปุ่มใด ๆ ) ด้วยการอ่านสถานะ PINx เป็นระยะ คุณสามารถระบุได้ว่ามีการกดปุ่มใดปุ่มหนึ่ง

ความสนใจ!หากบิตที่เกี่ยวข้องในการลงทะเบียน DDRx ถูกตั้งค่าเป็น 1 สำหรับปุ่มของคุณ การกดปุ่มอย่างดีอาจทำให้เกิดเอฟเฟกต์พลุไฟเล็กน้อย - ลักษณะของควันรอบ MCU แน่นอนว่า MK จะต้องถูกทิ้งลงถังขยะหลังจากนี้...

เรามาดูส่วนที่ใช้งานได้จริงกันดีกว่า สร้างพื้นที่ทำงานใหม่และโปรเจ็กต์ใหม่ใน IAR ด้วยชื่อเช่น TimerButton ตั้งค่าตัวเลือกโครงการตามที่อธิบายไว้ในบทความก่อนหน้านี้ ตอนนี้เรามาพิมพ์โค้ดเล็กๆ ต่อไปนี้

#รวม"iom16.h" เป็นโมฆะ init_timer0( เป็นโมฆะ) //เริ่มต้นตัวจับเวลา/counter0(OCR0 = 255; //เนื้อหาของการลงทะเบียนการเปรียบเทียบ // ตั้งค่าโหมดการทำงานของตัวจับเวลา TCCR0 = (1 โมฆะ init_timer2( เป็นโมฆะ) //เริ่มต้นตัวจับเวลา/ตัวนับ2( OCR2 = 255; TCCR2 = (1 // ตั้งค่าการขัดจังหวะการจับคู่) เป็นโมฆะหลัก( เป็นโมฆะ) (DDRB = 255; init_timer0(); init_timer2(); ในขณะที่(1) { } } #แพรมาเวกเตอร์ = TIMER2_COMP_vect //ตัวจับเวลาขัดจังหวะ 2 __ขัดจังหวะเป็นโมฆะกระพริบ() ( ถ้า((PORTB & 3) == 1) ( PORTB &= (0xFF // ปิดการใช้งานพิน PB0, PB1 PORTB |= 2; // เปิดใช้งาน PB1 } อื่น( PORTB &= (0xFF // ปิดการใช้งานพิน PB0, PB1 PORTB |= 1; // เปิดใช้งาน PB0 } }

มาดูกันว่ามันทำงานอย่างไร ฟังก์ชัน init_timern จะตั้งค่าบิตในรีจิสเตอร์ TCCRn, OCRn และ TIMSK และวิธีการนี้อาจดูแปลกหรือไม่คุ้นเคยสำหรับบางคน เราจะต้องอธิบายก่อนว่ารายการอะไร “(1

โดยที่ a คือตัวเลขที่ต้องเลื่อนการแสดงไบนารี่ และ b ระบุจำนวนบิตที่ต้องเลื่อน ในกรณีนี้ การสูญเสียค่าที่เก็บไว้ใน a เป็นไปได้ (เช่น ไม่สามารถกู้คืนจาก C สิ่งที่อยู่ใน a ได้เสมอไป) ลองดูตัวอย่าง:

สิ่งที่จะจบลงใน C หลังจากดำเนินการบรรทัด C = (22

2 ในรหัสไบนารี่จะมีลักษณะดังนี้ 00010110 และหลังจากเลื่อนไปทางซ้าย 3 บิต เราจะได้ C = 10110000

ในทำนองเดียวกันมีการเลื่อนไปทางขวา ตัวอย่างอื่น:

ถ่านค; ... C = ((0xFF > 2);

ขั้นแรกจะดำเนินการในวงเล็บด้านใน (0xFF คือ 255 ในรหัสฐานสิบหก) จาก 11111111 ผลลัพธ์จะเป็น 11111100 จากนั้นจะเกิดการเลื่อนไปทางขวาและเราจะได้ C = 00111111 ดังที่เราเห็นที่นี่สอง การดำเนินการผกผันซึ่งกันและกันนำไปสู่ตัวเลขที่แตกต่างกัน เนื่องจากเราสูญเสียบิตไปสองบิต สิ่งนี้จะไม่เกิดขึ้นหากตัวแปร C เป็นประเภท int เนื่องจาก int ครอบครอง 16 บิต

ตอนนี้เรามาดูตัวดำเนินการบิตอีกสองตัวที่ใช้กันอย่างแพร่หลายในการเขียนโปรแกรม MK เหล่านี้คือตัวดำเนินการระดับบิตและ (&) และระดับบิตหรือ (|) ฉันคิดว่ามันทำงานอย่างไรจะชัดเจนจากตัวอย่าง:

การกระทำ: ผลลัพธ์ (เป็นไบนารี่): C = 0; // C = 00000000 C = (1 // C = 00100101 C |= (1 // C = 00101101 C &= (0xF0 >> 2); // ค = 00101100ค = (ค & 4) | 3; // ค = 00000111

ฉันเกือบลืม! นอกจากนี้ยังมี “ระดับบิตพิเศษหรือ” (^) โดยจะเปรียบเทียบบิตที่สอดคล้องกันในตัวเลข และหากบิตเหล่านั้นเท่ากัน จะส่งคืนค่า 0 หรือมิฉะนั้นจะเป็นหนึ่ง

กลับมาที่โปรแกรมของเรากันดีกว่า มันบอกว่า "(1

/* ตัวจับเวลา/ตัวนับ 0 รีจิสเตอร์ควบคุม */ #กำหนด FOC0 7 #กำหนด WGM00 6 #กำหนดคอม015 #กำหนดคอม004 #กำหนด WGM01 3 #กำหนดซีเอส02 2 #กำหนดซีเอส01 1 #กำหนดซีเอส00 0

เมื่อคอมไพล์โปรแกรม รายการ WGM01 จะถูกแทนที่ด้วยหมายเลข 3 และผลลัพธ์จะเป็นรายการที่ถูกต้อง WGM01 เรียกว่ามาโคร และแตกต่างจากตัวแปรตรงที่ไม่ใช้พื้นที่ในหน่วยความจำ (ยกเว้นในหน่วยความจำของโปรแกรมเมอร์ :-)

หากคุณดูที่ Datasheet ในตอนนี้ คุณจะเห็นว่า WGM01 เป็นชื่อของบิตที่สามในรีจิสเตอร์ TCCR0 เช่นเดียวกับบิตที่เหลือของรีจิสเตอร์นี้ ความบังเอิญนี้ไม่ใช่เรื่องบังเอิญและใช้ได้กับการลงทะเบียน MK ทั้งหมด (หรือเกือบทั้งหมด) กล่าวคือ โดยเขียนว่า “(1

รวม, เส้น

หมายความว่าโหมด CTC เปิดอยู่เมื่อเรียกใช้ตัวจับเวลา 0 สถานะของ OS0 "ขา" (หรือที่รู้จักในชื่อ PB3) จะเปลี่ยนไปเนื้อหาของตัวนับจะเพิ่มขึ้นทุก ๆ 1,024 รอบนาฬิกา

ในทำนองเดียวกันสำหรับตัวจับเวลา 2: TCCR2 = (1

รีจิสเตอร์ TIMSK (รีจิสเตอร์ตัวจับเวลา/ตัวนับอินเทอร์รัปต์ MaSK) ตั้งค่าโหมดอินเตอร์รัปต์ พวกเราเขียน

ซึ่งหมายถึงการขัดจังหวะตัวจับเวลา 2 เมื่อ TCNT2 และ OCR2 ตรงกัน ฟังก์ชันสุดท้ายคือฟังก์ชัน Timer2 Match Interrupt ที่เกิดขึ้นจริง มีการประกาศการขัดจังหวะดังต่อไปนี้:

#เวกเตอร์แพรกมา= เวกเตอร์ __ขัดจังหวะประเภทชื่อ()

โดยที่ VECTOR คือมาโครเวกเตอร์ขัดจังหวะ (หมายถึงเพียงตัวเลขที่แสดงลักษณะของการขัดจังหวะนี้) มาโครเหล่านี้แสดงรายการตามลำดับความสำคัญที่ลดลงในไฟล์ iom16.h TYPE คือประเภทของค่าที่ส่งคืนโดยฟังก์ชัน ในกรณีของเราเป็นโมฆะ (ไม่มีเลย) NAME – ชื่อที่กำหนดเองสำหรับฟังก์ชันนี้ หากเกิดการหยุดชะงัก เราจะยังมีเวลาดำเนินการแก้ไขในอนาคต

เมื่อทำหน้าที่ของเรา ไฟ LED ที่เชื่อมต่อกับ PB0 และ PB1 ควรกะพริบตามลำดับ เห็นได้ชัดว่าความถี่คือ 11059200/(256*1024) = 42 Hz รวดเร็วแต่จะสังเกตได้ด้วยตาเปล่า อย่างไรก็ตามการใช้ตัวจับเวลาทำให้สามารถนับช่วงเวลาที่แม่นยำซึ่งไม่ขึ้นอยู่กับความซับซ้อนของโปรแกรมของคุณและลำดับที่ดำเนินการ (แต่หากคุณไม่มีการขัดจังหวะมากกว่าหนึ่งครั้ง)

ดังนั้นให้บันทึกไฟล์เป็น "TimerDebug.c" เพิ่มลงในโปรเจ็กต์ คอมไพล์มัน แฟลช MK เราเห็นอะไร? ไฟ LED ที่เชื่อมต่อกับพิน PB3 จะกระพริบอย่างแข็งขัน แต่จะไม่มีการเปลี่ยนแปลงที่ PB0 และ PB1 เกิดอะไรขึ้น? มีอะไรผิดปกติจริงๆเหรอ?

หากต้องการทราบ เราจะต้องทำการดีบักโปรแกรมของเรา เนื่องจาก IAR ไม่มี Debugger คุณจะต้องใช้ AVR Studio สามารถดาวน์โหลดสภาพแวดล้อมการพัฒนานี้ได้จากเว็บไซต์ของผู้ผลิต http://atmel.com ฉันไม่คิดว่าจะมีปัญหาใดๆ กับการติดตั้ง ก่อนที่จะเริ่ม AVR Studio ให้เลือกโหมด Debug ใน IAR และสร้างไฟล์ cof การดีบัก (ต้องตั้งค่าตัวเลือกโปรเจ็กต์ทั้งหมดตามที่อธิบายไว้ในบทความก่อนหน้า)

เมื่อเปิด AVR Studio แล้ว เราจะเห็นหน้าต่างต้อนรับซึ่งเราจะเลือก "เปิด" ตอนนี้เราเข้าไปในโฟลเดอร์ที่มีโปรเจ็กต์ ที่นั่นใน Debug\Exe เลือก “TimerDebug.cof” ที่นั่น สร้างโปรเจ็กต์ที่พวกเขาแนะนำ เลือกอุปกรณ์ ATMega16 และโหมดดีบัก Simulator หลังจากนี้ หากทุกอย่างถูกต้อง กระบวนการดีบักจะเริ่มต้นขึ้นทันที

สภาพแวดล้อมการดีบักที่นี่สะดวกมาก เพราะ... ช่วยให้คุณดูเนื้อหาของการลงทะเบียน MK ทั้งหมดรวมทั้งตั้งค่าด้วยตนเองด้วยการคลิกเมาส์ ตัวอย่างเช่น หากคุณตั้งค่าสถานะการขัดจังหวะในการลงทะเบียน TIFR ในบิต 7 (ใต้สี่เหลี่ยมสีดำใน TIMSK) ดังนั้นขั้นตอนถัดไปของโปรแกรม (การกด F10 หรือ F11) ควรเป็นการประมวลผลการขัดจังหวะ (การตั้งค่าสถานะจะถูกตั้งค่าโดยอัตโนมัติหาก TCNT2 และ OCR2 ลงทะเบียนตรงกัน) แต่ที่น่าประหลาดใจก็คือจะไม่มีการหยุดชะงัก!

คำถามเกิดขึ้น: ทำไม?

มาเปิดการลงทะเบียน CPU SREG กัน การลงทะเบียนนี้กำหนดการทำงานของโปรเซสเซอร์และโดยเฉพาะบิตที่เจ็ด (I-bit, บิตขัดจังหวะ) มีหน้าที่ในการประมวลผลการขัดจังหวะทั้งหมดใน MK เราไม่มีมันติดตั้ง ทันทีที่คุณตั้งค่าไว้ การขัดจังหวะจะถูกดำเนินการทันที (หากบิตที่เจ็ดใน TIFR ถูกตั้งค่าพร้อมกัน)

คุณสามารถสังเกตเห็นคุณสมบัติที่น่าสนใจอย่างหนึ่ง: ทันทีที่โปรเซสเซอร์เข้าสู่การประมวลผลแบบขัดจังหวะ บิตนี้ (แฟล็กการเปิดใช้งานการประมวลผลแบบขัดจังหวะ) จะถูกล้าง และเมื่อออกจากฟังก์ชันขัดจังหวะ มันจะถูกตั้งค่าอีกครั้งโดยอัตโนมัติ สิ่งนี้ไม่อนุญาตให้โปรเซสเซอร์คว้าตัวอื่นโดยไม่ต้องดำเนินการขัดจังหวะหนึ่งครั้ง (ท้ายที่สุดแล้วมันจะนำทางโปรแกรมในลักษณะนี้ - โดยแฟล็ก)

ซึ่งหมายความว่าคุณต้องเพิ่มบรรทัดโค้ดเพื่อตั้งค่าบิตนี้เป็นหนึ่ง เราจะเพิ่มมันเข้าไปในฟังก์ชัน init_timer2 คุณจะได้รับสิ่งต่อไปนี้:

เป็นโมฆะ init_timer2( เป็นโมฆะ) ( SREG |= (1 // เพิ่มบรรทัดนี้ OCR2 = 255; TCCR2 = (1

ตอนนี้เมื่อเลือกการกำหนดค่า Release และแฟลช MK โดยกด F7 แล้วเปิด AVReal32.exe เรายินดีที่จะเห็นว่าทุกอย่างทำงานได้ตามปกติ

ความคิดเห็น:เมื่อทำการดีบั๊กโปรแกรม คุณควรลดช่วงเวลาของตัวจับเวลาลงหากมันยาวเกินไป เพราะในระหว่างการดีบั๊กใน AVR Studio โปรแกรมจะทำงานช้ากว่าใน MK หลายพันเท่า และคุณจะไม่รอให้ตัวจับเวลาเริ่มทำงาน โดยทั่วไป การดีบักจะคล้ายคลึงกับในระบบการเขียนโปรแกรมอื่นๆ เช่น Visual C++ โดยสิ้นเชิง

ตอนนี้หลังจากเรียนรู้วิธีแก้ไขข้อบกพร่องของโปรแกรมแล้ว เรามาสร้างไฟล์ใหม่ใน IAR (และบันทึกไฟล์เก่าและลบออกจากโครงการ) แล้วพิมพ์รหัสต่อไปนี้:

#รวม"iom16.h" int ที่ไม่ได้ลงนามมานานตัวนับ = 0; //ตัวนับสำหรับการสร้างช่วงเวลา ถ่านที่ไม่ได้ลงนาม B0กด = 0; // สถานะของปุ่ม 0 ถูกเก็บไว้ที่นี่ (0 - ไม่ได้กด, 1 - กด) ถ่านที่ไม่ได้ลงนาม B1กด = 0; // สถานะของ button1 ถูกเก็บไว้ที่นี่ (0 - ไม่ได้กด, 1 - กด) //เริ่มต้นตัวจับเวลา2 //จำเป็นต้องเพิ่มตัวนับทุกๆ 11,059 รอบสัญญาณนาฬิกา (1 ms) เราได้รับมันทุกๆ 1.001175 มิลลิวินาที เป็นโมฆะ init_timer2() ( OCR2 = 173; TCCR2 = (1 // การเริ่มต้นพอร์ต I/O init_io_ports () ( DDRA = (1 // สร้างความล่าช้าใน Pause_ms มิลลิวินาที เป็นโมฆะล่าช้า( int ที่ไม่ได้ลงนามมานาน Pause_ms) ( ตัวนับ = 0; ในขณะที่(เคาน์เตอร์เป็นโมฆะ main() ( SREG |= (1 //เปิดใช้งานการขัดจังหวะ init_timer2(); //เปิดเครื่องจับเวลา2 ทุกๆ 64 ขีด นับได้ถึง 173 init_io_ports(); //เปิดใช้งานพอร์ต I/O ในขณะที่(1) { // ปุ่มประมวลผล 0 ถ้า(B0กด == 1) { //เพิ่ม PORTB รอการเปิดตัวพอร์ตบี++; B0กด = 0; ในขณะที่((PINC & (อีก 1 รายการ ( ถ้า((PINC & (1 // แก้ไขการกด ( ล่าช้า (50); ถ้า((PINC & (1 // ตรวจสอบการกด ( B0Pressed = 1; } } } // ปุ่มประมวลผล 1 ถ้า(B1กด == 1) //หากคลิกปุ่ม { // ลด PORTB รอการเปิดตัวพอร์ตบี--; B1กด = 0; ในขณะที่((PINC & (อีก 1 รายการ ( ถ้า((PINC & (1 // แก้ไขการกด ( ล่าช้า (200); // กำจัด "การตีกลับคีย์" ถ้า((PINC & (1 // ตรวจสอบการกด ( B1Pressed = 1; //ตั้งค่าสถานะ "กดปุ่ม" } } } } } //ขัดจังหวะด้วยตัวจับเวลา 2 ดังนั้นตัวนับจึงเพิ่มขึ้น #เวกเตอร์แพรกมา= TIMER2_COMP_vect __ขัดจังหวะเป็นโมฆะ inc_delay_counter() ( เคาน์เตอร์++; )

ก่อนอื่นฉันขอแนะนำให้นำไฟล์เฟิร์มแวร์สำเร็จรูป (ไฟล์สำหรับบทความ, โฟลเดอร์ Release, ไฟล์ TimerButton.hex หรือรวบรวมข้อความนี้) แล้วเขียนลงใน MK จากนั้นถอดสายเฟิร์มแวร์ออก เชื่อมต่อปุ่มต่างๆ เข้ากับ PC0 และ PC1 แล้วลองกด เราจะเห็นว่าเมื่อคุณกดปุ่มใดปุ่มหนึ่งการลงทะเบียน PORTB จะเพิ่มขึ้น (ไฟ LED จะสว่างขึ้น) และเมื่อคุณกดอีกปุ่มหนึ่งจะลดลง หากไม่ได้ผล ให้ลองกดปุ่มหนึ่งในขณะที่อีกปุ่มค้างไว้ - มันจะได้ผล ความจริงก็คือฉันเชื่อมต่อปุ่มต่าง ๆ ด้วยวิธีต่อไปนี้: เมื่อคุณกดปุ่ม MK เอาท์พุตจะ "ห้อย" ในอากาศ และเมื่อปล่อยปุ่มก็จะสั้นลงกับพื้น หากคุณเชื่อมต่อปุ่มต่าง ๆ คุณจะต้องปรับปรุงโปรแกรมให้ทันสมัยเพียงเล็กน้อยเท่านั้น

มาดูโค้ดกัน ที่นี่การทำงานกับตัวจับเวลานั้นมีการจัดระเบียบแตกต่างออกไปบ้าง โดยจะเริ่มทำงานทุกๆ 11072 รอบสัญญาณนาฬิกา (นั่นคือ ทุกๆ 1.001175 ms) และเพิ่มตัวแปรตัวนับ นอกจากนี้ยังมีการหน่วงเวลาของฟังก์ชัน (int Pause_ms ที่ไม่ได้ลงนามแบบยาว) ซึ่งใช้จำนวนมิลลิวินาที Pause_ms เป็นพารามิเตอร์ รีเซ็ตตัวนับและรอให้ตัวนับถึงค่า Pause_ms หลังจากนั้น MK ยังคงทำงานต่อไป ดังนั้นโดยการเขียนดีเลย์ (1500) เราจะสร้างดีเลย์ในโปรแกรม 1.5 วินาที สะดวกมากสำหรับการสร้างช่วงเวลา

ทุกอย่างดูเหมือนจะชัดเจนด้วยการจับเวลา แต่มันใช้ทำอะไรล่ะ? พิจารณาการวนซ้ำไม่สิ้นสุดในขณะที่ (1) อยู่ใน main() การวนซ้ำนี้จะตรวจสอบสถานะของปุ่มโดยการวิเคราะห์เนื้อหาของการลงทะเบียน PINB เหตุใดจึงมีความล่าช้า 50 ms ที่นั่น นี่คือการกำจัดสิ่งที่เรียกว่า "เรื่องสำคัญ" ความจริงก็คือเมื่อคุณกดปุ่ม ผู้ติดต่อรายหนึ่งจะโดนอีกรายหนึ่ง และเนื่องจากผู้ติดต่อนั้นเป็นโลหะ ผลกระทบนี้จึงยืดหยุ่น หน้าสัมผัสเด้งปิดและเปิดหลายครั้งแม้ว่านิ้วจะกดเพียงครั้งเดียวก็ตาม สิ่งนี้นำไปสู่การบันทึก MK หลายครั้ง ลองดูกราฟของแรงดันไฟฟ้าที่เอาต์พุต PC0 เทียบกับเวลา อาจมีลักษณะเช่นนี้:

จุด A คือช่วงเวลาที่กดปุ่ม MK สามารถแก้ไขได้ จากนั้นมีการลัดวงจรและวงจรเปิดหลายจุด (อาจไม่มีหรืออาจมี 12 วงจร - ปรากฏการณ์นี้ถือได้ว่าเป็นแบบสุ่ม) ณ จุด B ผู้ติดต่อได้รับการแก้ไขอย่างปลอดภัยแล้ว ระหว่าง A และ B มีค่าเฉลี่ยประมาณ 10 ms ในที่สุด ณ จุด D จะมีการเปิดเกิดขึ้น จะกำจัดปรากฏการณ์อันไม่พึงประสงค์นี้ได้อย่างไร? ปรากฎว่ามันง่ายมาก คุณต้องบันทึกช่วงเวลาที่กดปุ่ม (จุด A) หลังจากนั้นสักครู่ เช่น 50 ms (จุด C) ตรวจสอบว่ากดปุ่มจริงแล้ว ดำเนินการตามปุ่มนี้แล้วรอสักครู่ ถูกปล่อยออกมา (จุด D) นั่นคือคุณต้องหยุดชั่วคราวจาก A ถึง C เพื่อให้ "กะพริบ" ทั้งหมดอยู่ภายในการหยุดชั่วคราวนี้ ตอนนี้ให้ลองลบบรรทัดที่สร้างความล่าช้า คอมไพล์โปรแกรมและต่อเข้ากับ MK เพียงกดปุ่มคุณสามารถตรวจสอบได้อย่างง่ายดายว่า "ความทรมาน" ทั้งหมดนี้ไม่ได้ไร้ประโยชน์

แต่จะทำอย่างไรถ้าคุณต้องการเชื่อมต่อเช่น 40 ปุ่มเข้ากับ MK? ท้ายที่สุดมันมีเพียง 32 พินเท่านั้น ดูเหมือนว่าจะไม่มีทาง มันเป็นไปได้จริงๆ ในกรณีนี้ จะใช้อัลกอริธึมที่เรียกว่า gating ในการทำเช่นนี้คุณจะต้องเชื่อมต่อปุ่มต่างๆ ในรูปแบบของเมทริกซ์ดังแสดงในรูป (รูปนี้นำมาจากหนังสือของ Morton เรื่อง "MK AVR หลักสูตรเบื้องต้น" ซึ่งเขียนเกี่ยวกับการเขียนโปรแกรม AVR ในแอสเซมเบลอร์)

เมื่อนำไปใช้กับเอาต์พุตบันทึก PB0 1 (+5V) และสำหรับปักหมุดบันทึก PB1 และ PB2 0 อนุญาตให้ประมวลผลปุ่ม 1, 4 และ 7 หลังจากนั้นสามารถตรวจสอบสถานะของแต่ละปุ่มได้โดยการตรวจสอบแรงดันไฟฟ้าที่พิน PB3..PB5 อันใดอันหนึ่ง ดังนั้น การใช้ตามลำดับกับบันทึกพิน PB0..PB2 1 สามารถกำหนดสถานะของปุ่มทั้งหมดได้ เป็นที่ชัดเจนว่าพิน PB0..PB2 ควรเป็นเอาต์พุต และอินพุต PB0..PB2 เพื่อกำหนดจำนวนพินที่จำเป็นสำหรับอาร์เรย์ของปุ่ม X คุณต้องค้นหาคู่ของปัจจัย X ที่ผลรวมน้อยที่สุด (สำหรับกรณีของเราที่มี 40 ปุ่ม จะเป็นตัวเลข 5 และ 8) ซึ่งหมายความว่าสามารถเชื่อมต่อปุ่มได้สูงสุด 256 ปุ่มกับ MK หนึ่งตัว (และยิ่งกว่านั้นด้วยการใช้ตัวถอดรหัส แต่จะเพิ่มเติมเกี่ยวกับตัวถอดรหัสในภายหลัง) จะดีกว่าถ้าสร้างพินเอาท์พุตน้อยลงและมีพินอินพุทมากขึ้น ในกรณีนี้ การสแกนแถวทั้งหมดของเมทริกซ์จะใช้เวลาน้อยลง วิธีการเชื่อมต่อ (แฟลช) นี้ไม่เฉพาะกับปุ่มต่างๆ ที่นั่นคุณสามารถเชื่อมต่ออุปกรณ์ได้หลากหลาย ตั้งแต่เมทริกซ์ LED ไปจนถึงชิปหน่วยความจำแฟลช

© คิเซเลฟ โรมัน
มิถุนายน 2550

โดยพื้นฐานแล้ว ตัวจับเวลาไมโครคอนโทรลเลอร์เป็นตัวนับดิจิทัลที่ "ซับซ้อน" เท่านั้น สัญญาณนาฬิกาจะถูกส่งไปยังอินพุตตัวนับโดยขึ้นอยู่กับการหยดที่ตัวนับจะเพิ่มค่า เมื่อเหตุการณ์เกิดขึ้น - ตัวนับโอเวอร์โฟลว์หรือค่าของมันตรงกับค่าที่กำหนด - คำขอขัดจังหวะจะถูกสร้างขึ้น

มาดูวิธีใช้ตัวจับเวลา T0 ในโหมดปกติกัน ในโหมดนี้ ตัวจับเวลาจะนับจากค่าเริ่มต้นของรีจิสเตอร์การนับไปจนถึงค่าสูงสุดที่เป็นไปได้ (สูงสุด 255 หรือ 0xFF) เมื่อตัวจับเวลา T0 นับเป็นค่าสูงสุด จากนั้นในรอบนาฬิกาถัดไป การลงทะเบียนการนับ TCNT0 ล้น - จะถูกรีเซ็ตและตั้งค่าสถานะ TOV0 หากโปรแกรมอนุญาตให้มีการขัดจังหวะทั่วโลก (แฟล็ก I ของรีจิสเตอร์ SREG) และการขัดจังหวะโอเวอร์โฟลว์ของตัวจับเวลา T0 (แฟล็ก TOIE0 ของรีจิสเตอร์ TIMSK) ไมโครคอนโทรลเลอร์จะเรียกตัวจัดการที่เกี่ยวข้อง หากค่าของรีจิสเตอร์การนับตรงกับรีจิสเตอร์การเปรียบเทียบ OCR0 ดังนั้นแฟล็ก OCF0 จะถูกตั้งค่า และหากเปิดใช้งานการขัดจังหวะเหตุการณ์การแข่งขัน ตัวจัดการจะเริ่มทำงาน

ตัวจับเวลา T0 ในโหมดปกติ

ลองพิจารณาปัญหาในทางปฏิบัติ - เราต้องสำรวจปุ่มทุกๆ 20 มิลลิวินาที ความถี่ไมโครคอนโทรลเลอร์ 8 MHz, ไมโครคอนโทรลเลอร์ ATmega16

สิ่งแรกที่ต้องทำคือตัดสินใจเลือกค่าสัมประสิทธิ์พรีสเกลเลอร์ของตัวจับเวลา และคำนวณค่าเริ่มต้นสำหรับรีจิสเตอร์ตัวนับ TCNT0

ตัวจับเวลา T0 สามารถโอเวอร์คล็อกได้จากสัญญาณนาฬิกาภายในของไมโครคอนโทรลเลอร์หรือจากสัญญาณภายนอกซึ่งจ่ายให้กับพิน T0 เมื่อทำงานจากสัญญาณนาฬิกาภายใน ผู้ใช้สามารถเลือกอัตราส่วนการแบ่งความถี่ของสัญญาณนี้ได้ ตัวจับเวลา T0 มีตัวเลือกสัมประสิทธิ์พรีสเกลเลอร์ที่เป็นไปได้ห้าตัวเลือก - 1, 8, 64, 256, 1024

เพื่อแก้ไขปัญหานี้ ผมให้เหตุผลดังนี้ หากตัวจับเวลา T0 หนึ่งขีดมีระยะเวลา 1 ms มันก็จะเหมาะกับฉัน 20 รอบนาฬิกาให้เวลา 20 ms ค่าสัมประสิทธิ์พรีสเกลเลอร์ตัวจับเวลาใดที่จะช่วยให้คุณได้ช่วงเวลานาฬิกาใกล้ 1 มิลลิวินาที คุณสามารถนับได้

ความถี่สัญญาณนาฬิกาของไมโครคอนโทรลเลอร์ Fcpu = 8000000 Hz
ช่วงเวลานาฬิกาของไมโครคอนโทรลเลอร์ Tcpu = 1/Fcpu
คาบนาฬิกาของตัวจับเวลา T0 เท่ากับ Tt0 = (1/Fcpu)/k = k/Fcpu

ที่ k = 1,024 ระยะเวลานาฬิกาของตัวจับเวลา T0 จะเท่ากับ Tt0 = 1024/8000000 = 0.128 ms

นี่คือช่วงนาฬิกาจับเวลาสูงสุดที่เราสามารถรับได้ภายใต้เงื่อนไขของเรา (Fcpu = 8 MHz) ด้วยอัตราต่อรองที่ต่ำกว่า ระยะเวลาจะสั้นลงอีก

ให้รอบนาฬิกาของตัวจับเวลาหนึ่งรอบเป็น 0.128 ms รีจิสเตอร์การนับกว้างพอที่จะนับช่วงเวลานี้หรือไม่ และต้องใช้กี่รอบนาฬิกา เราหารช่วงเวลาที่ต้องการ (20 ms) ด้วยระยะเวลาของเครื่องหมายตัวจับเวลาหนึ่งครั้งและรับคำตอบ

n = t/Tto = 20 ms/ 0.128 ms = 156.25

เมื่อปัดเศษเป็นจำนวนเต็มที่ใกล้ที่สุด เราจะได้ 156 รอบสัญญาณนาฬิกา ซึ่งน้อยกว่า 255 (ค่าสูงสุดของรีจิสเตอร์การนับ) ซึ่งหมายความว่ารีจิสเตอร์การนับ TCNT0 ก็เพียงพอแล้ว

ค่าเริ่มต้นสำหรับรีจิสเตอร์การนับ TCNT0 คำนวณเป็นผลต่างระหว่างจำนวนรอบสัญญาณนาฬิกาสูงสุดของตัวจับเวลา T0 และค่าที่ต้องการ นั่นคือ 256 - 156 = 100 (256 คือจำนวนรอบเวลาสูงสุดที่ 8- บิตจับเวลาสามารถนับได้)

ฉันคิดว่าตอนนี้วิธีคำนวณค่าเริ่มต้นของ TCNT0 สำหรับโหมดปกติก็ชัดเจนแล้ว:

เราคำนวณระยะเวลาหนึ่งรอบของตัวจับเวลา Tt0 = k/Fcpu
- คำนวณจำนวนรอบสัญญาณนาฬิกาที่ต้องการสำหรับช่วงเวลาที่กำหนด n = t/Tto
- คำนวณค่าเริ่มต้นสำหรับการลงทะเบียนการนับ TCNT0 = 256 - n

คุณสามารถทำให้ขั้นตอนนี้เป็นแบบอัตโนมัติโดยใช้มาโคร ตัวอย่างเช่นเช่นนี้:

#กำหนด F_CPU 8000000UL
#define TIME_MS(เวลา, k) (256L - ((เวลา)*(F_CPU))/(1000L*(k)))

แต่ด้วยมาโครคุณต้องระวัง ข้อผิดพลาดอาจเกิดขึ้นที่ค่าเวลาและ k

ตอนนี้เรามาดูโค้ดกันดีกว่า หากต้องการใช้ตัวจับเวลา T0 (และอื่นๆ ด้วย) คุณต้องกำหนดค่า (เริ่มต้น) และอธิบายตัวจัดการการขัดจังหวะ (หากใช้)

การเริ่มต้นตัวจับเวลาประกอบด้วยขั้นตอนต่อไปนี้:

หยุดตัวจับเวลา
- การตั้งค่าโหมดปกติใน TCCR0 โดยไม่ต้องเริ่ม
- ตั้งค่าเริ่มต้น TCNT0
- การรีเซ็ตค่าสถานะในการลงทะเบียน TIFR
- เปิดใช้งานการขัดจังหวะโอเวอร์โฟลว์ใน TIMSK
- การตั้งค่าพรีสเกลเลอร์ใน TCCR0 นั่นคือเริ่มจับเวลา

ลำดับนี้สามารถเปลี่ยนแปลงได้

สำหรับงานของเรา รหัสเริ่มต้นจะมีลักษณะดังนี้:


/*ค่าลงทะเบียนการนับ*/
#กำหนด T_POLL 100

TCCR0 = 0;
TCCR0 = (0<TCNT0 = T_POLL;
TIFR = (1<ทิมสค์ |= (1<TCCR0 |= (1<

บรรทัดการเริ่มต้นที่สองนั้นไร้ประโยชน์โดยพื้นฐานแล้วมันถูกเพิ่มเพื่อความชัดเจน เพื่อให้เห็นได้ชัดเจนว่ากำลังตั้งค่าโหมดตัวจับเวลาใดอยู่

การรีเซ็ตแฟล็กอินเทอร์รัปต์ในรีจิสเตอร์ TIFR ทำได้โดยการเขียน 1 ไปยังบิตที่เกี่ยวข้องการดำเนินการนี้จะต้องดำเนินการโดยการเขียนทับการลงทะเบียน และไม่ใช้ระดับบิตหรือ และนั่นคือเหตุผล

สมมติว่ามีการตั้งค่าสถานะขัดจังหวะสองรายการในรีจิสเตอร์ TIFR - TOV1 และ TOV0 TOV0 เราต้องรีเซ็ต เมื่อตั้งค่าหลักที่ต้องการโดยใช้ ORสิ่งต่อไปนี้เกิดขึ้น


//TIFR มีค่า 0b00000101
//ตั้งค่าสถานะแล้ว TOV1 และ TOV0
//โค้ดถูกดำเนินการ TIFR |= (1<
//TIFR ถูกคัดลอกไปที่ R16
ใน R16, 0x38

// ใน R16 บิต TOV0 จะถูกตั้งค่า
//ถึงแม้จะติดตั้งไปแล้วก็ตาม
โอริ R16, 0x02

//R16 เท่ากับ 0b00000101 ถูกเขียนไปยังการลงทะเบียน TIFR
ออก 0x38, R16

ผลก็คือ ธงทั้งสองถูกรีเซ็ต แต่เราต้องการรีเซ็ตธงหนึ่งธง

มาต่อกัน

ไวยากรณ์สำหรับการอธิบายตัวจัดการการขัดจังหวะจะแตกต่างกันเล็กน้อยสำหรับคอมไพเลอร์ที่ต่างกัน สำหรับ IAR'a ตัวจัดการการขัดจังหวะตัวจับเวลา T0 สำหรับเหตุการณ์โอเวอร์โฟลว์จะมีลักษณะดังนี้:



{
TCNT0 = T_POLL;

/*ควรมีปุ่มสำรวจความคิดเห็นที่นี่*/

TIMER0_OVF_vect คือที่อยู่ของเวกเตอร์ขัดจังหวะโอเวอร์โฟลว์ นำมาจากไฟล์ส่วนหัวบนไมโครคอนโทรลเลอร์ ในกรณีนี้ ฉันเอามาจากไฟล์ iom16.h

บรรทัดแรกของตัวจัดการ (TCNT0 = T_POLL;) จะเขียนทับรีจิสเตอร์การนับและตั้งค่าเริ่มต้น หากยังไม่เสร็จสิ้น ตัวจับเวลาจะนับต่อจาก 0 การเขียนรีจิสเตอร์การนับใหม่จะต้องดำเนินการที่จุดเริ่มต้นของตัวจัดการการขัดจังหวะ

โค้ดทั้งหมดสำหรับงานของเราจะมีลักษณะดังนี้ (รหัสจะแสดงสำหรับ IAR สำหรับคอมไพเลอร์อื่น ๆ คุณต้องเปลี่ยนไฟล์ส่วนหัวและตัวจัดการการขัดจังหวะ)

#รวม
#รวม
#รวม

#กำหนด T_POLL 100

int main (เป็นโมฆะ)
{
/*การเริ่มต้นจับเวลา*/

TCCR0 = 0;
TCCR0 = (0<TCNT0 = T_POLL;
TIFR |= (1<ทิมสค์ |= (1<TCCR0 |= (1<

/*เริ่มต้นอุปกรณ์ต่อพ่วงที่เหลือ*/
DDRB |= (1<

Enable_ขัดจังหวะ();
ในขณะที่(1);

/*ตัวจัดการขัดจังหวะ T0
ตามเหตุการณ์ล้น*/
#pragma เวกเตอร์ = TIMER0_OVF_vect
__ ขัดจังหวะโมฆะ TimerT0Ovf (เป็นโมฆะ)
{
/*เขียนทับทะเบียนการนับ*/
TCNT0 = T_POLL;

/*ปุ่มโพลล์*/

/*การผกผันของ PB0 สำหรับการดีบัก*/
พอร์ตบี ^= (1<

การควบคุมเอาต์พุต OC0

ในโหมดปกติ ตัวจับเวลา T0 สามารถเปลี่ยนสถานะของพิน OC0 ได้เมื่อรีจิสเตอร์การนับและรีจิสเตอร์การเปรียบเทียบตรงกัน และแม้จะไม่มีการหยุดชะงักก็ตาม ตัวเลือกการควบคุมถูกกำหนดโดยบิต COM01 และ COM00 ของรีจิสเตอร์ TCCR0

นี่คือตัวอย่างโปรแกรมที่สร้างคลื่นสี่เหลี่ยมที่ขา OC0

#รวม
#รวม

int main (เป็นโมฆะ)
{
/*เริ่มต้นตัวจับเวลา T0*/

TCCR0 = 0;
TCCR0 = (0<TCNT0 = 0;
โอซีอาร์0 = 0;
ทิมสค์ = 0;
TCCR0 |= (1<

/*เริ่มต้น OC0*/
DDRB |= (1<

ในขณะที่(1);
กลับ 0;
}

พิน OS0 จะเปลี่ยนสถานะเป็นตรงกันข้ามเมื่อรีจิสเตอร์การนับเป็นศูนย์

เกร็ดเล็กๆ น้อยๆ เกี่ยวกับการใช้ตัวจับเวลา

ตัวจัดการการขัดจังหวะตัวจับเวลา (และอุปกรณ์ต่อพ่วงอื่น ๆ ) ควรทำให้สั้นที่สุดเท่าที่จะทำได้

ถ้าค่าที่คำนวณได้สำหรับการลงทะเบียนการนับ (หรือการลงทะเบียนการเปรียบเทียบ) ถูกปัดเศษ ช่วงเวลาจะถูกนับโดยตัวจับเวลาที่มีข้อผิดพลาด

และสิ่งสุดท้ายอย่างหนึ่ง อาจเกิดขึ้นได้ว่าการประมวลผลการขัดจังหวะตัวจับเวลาเกิดความล่าช้า (เช่น เนื่องจากข้อผิดพลาดของตัวจัดการอื่น) และรีจิสเตอร์ TCNT0 จะนับรอบสัญญาณนาฬิกาหลายรอบแล้ว หากคุณเพียงแค่เขียนทับค่าของ TCNT0 การขัดจังหวะครั้งถัดไปจะถูกเรียกช้ากว่าที่จำเป็น ปรากฎว่าการขัดจังหวะก่อนหน้า (ล่าช้า) และใหม่จะไม่ทนต่อช่วงเวลาที่ต้องการ

สถานการณ์นี้สามารถคลี่คลายได้หากคุณเขียนทับทะเบียนการตรวจนับดังนี้:

TCNT0 = TCNT0 + มูลค่าเริ่มต้น;

การเพิ่มค่าปัจจุบันของรีจิสเตอร์การนับด้วยค่าเริ่มต้นจะคำนึงถึงรอบสัญญาณนาฬิกาพิเศษเหล่านี้ด้วยมีหนึ่งจริงๆ แต่! ถ้า startValue มีขนาดใหญ่ การดำเนินการเพิ่มเติมอาจทำให้การลงทะเบียนตัวนับล้น

ตัวอย่างเช่น startValue = 250 และตัวจับเวลาจัดการให้นับถึง 10 จากนั้นการดำเนินการเพิ่มเติมจะนำไปสู่ผลลัพธ์ต่อไปนี้:

10 + 250 = 260

เราใช้ 8 หลักจาก 260 และรับ 4 4 เขียนเป็น TCNT0

ข้อดีอย่างหนึ่งของไมโครคอนโทรลเลอร์ ATmega8 คือการขัดจังหวะที่หลากหลาย

ขัดจังหวะเป็นเหตุการณ์ที่เกิดขึ้นเมื่อมีการหยุดการทำงานของโปรแกรมหลักและมีการเรียกใช้ฟังก์ชันที่จัดการการขัดจังหวะบางประเภท

การขัดจังหวะแบ่งออกเป็นภายในและภายนอก แหล่งที่มาของการขัดจังหวะภายในรวมถึงโมดูลไมโครคอนโทรลเลอร์ในตัว (ตัวจับเวลา, ตัวรับส่งสัญญาณ USART ฯลฯ ) การขัดจังหวะภายนอกเกิดขึ้นเมื่อสัญญาณภายนอกมาถึงที่พินไมโครคอนโทรลเลอร์ (เช่น สัญญาณที่พิน RESET และ INT) ลักษณะของสัญญาณที่นำไปสู่การขัดจังหวะถูกตั้งค่าไว้ในรีจิสเตอร์ควบคุม มจรโดยเฉพาะอย่างยิ่งในบิต - ISC00 (บิต 0) และ ISC01 (บิต 1) สำหรับอินพุต INT 0 ISC10 (bit2) และ ISC11 (bit3) สำหรับอินพุต INT1

ในไมโครคอนโทรลเลอร์ ATmega8 แต่ละอินเทอร์รัปต์จะมีของตัวเอง เวกเตอร์ขัดจังหวะ(ที่อยู่ที่จุดเริ่มต้นของพื้นที่หน่วยความจำโปรแกรมซึ่งเก็บคำสั่งสำหรับการข้ามไปยังรูทีนขัดจังหวะที่ระบุ) ใน mega8 การขัดจังหวะทั้งหมดจะมีลำดับความสำคัญเท่ากัน หากมีการขัดจังหวะหลายครั้งเกิดขึ้นพร้อมกัน การขัดจังหวะที่มีจำนวนเวกเตอร์ต่ำกว่าจะถูกประมวลผลก่อน

เวกเตอร์ขัดจังหวะใน Atmega8

ที่อยู่ แหล่งที่มาขัดจังหวะ คำอธิบาย
0x0000 รีเซ็ต รีเซ็ตสัญญาณ
0x0001 INT0 คำขอขัดจังหวะภายนอกที่อินพุต INT0
0x0002 INT1 คำขอขัดจังหวะภายนอกที่อินพุต INT1
0x0003 ที/ซี1 จับเวลาจับ T/C1
0x0004 ที/ซี1 จับคู่ตัวจับเวลา T/C1 เปรียบเทียบรีจิสเตอร์ A
0x0005 ที/ซี1 จับคู่กับรีจิสเตอร์เปรียบเทียบ B ของไทม์เมอร์ T/C1
0x0006 ที/ซี1 T/C1 เคาน์เตอร์ล้น
0x0007 ที/ซี0 T/C0 เคาน์เตอร์ล้น
0x0008 เอสพีไอ การถ่ายโอนข้อมูล SPI เสร็จสมบูรณ์
0x0009 ยูอาร์ที ตัวรับส่งสัญญาณ UART ได้รับข้อมูลเรียบร้อยแล้ว
0x000A ยูอาร์ที การลงทะเบียนข้อมูล UART ว่างเปล่า
0x000B ยูอาร์ที การส่งข้อมูลด้วยตัวรับส่งสัญญาณ UART เสร็จสิ้น
0x000C ANA_COMP ขัดจังหวะจากตัวเปรียบเทียบแบบอะนาล็อก

การจัดการขัดจังหวะ

4 รีจิสเตอร์มีหน้าที่จัดการการขัดจังหวะใน ATmega8:

กิมสค์(หรือที่เรียกว่า GICR) - ห้าม/เปิดใช้งานการหยุดชะงักตามสัญญาณที่อินพุต INT0, INT1

GIFR- การจัดการการขัดจังหวะภายนอกทั้งหมด

ทิมสค์, ทีไอเอฟอาร์- การจัดการการหยุดชะงักจากตัวจับเวลา/ตัวนับ

ลงทะเบียน กิมสค์(GICR)

INTFx=1: เกิดการขัดจังหวะที่อินพุต INTx เมื่อเข้าสู่รูทีนการจัดการการขัดจังหวะ INTFx จะถูกรีเซ็ตเป็นสถานะบันทึกโดยอัตโนมัติ 0

ลงทะเบียน ทิมสค์

7 6 5 4 3 2 1 0
โทอิเอะ1
โอซีอี1เอ
โอซีอี1บี
-
ทิซี่
-
TOIE0
-

TOIE1=1: เปิดใช้งานการขัดจังหวะโอเวอร์โฟลว์ T/C1

โอซีอี1เอ=1: ขัดจังหวะเมื่อรีจิสเตอร์การเปรียบเทียบ A ตรงกับเนื้อหาของตัวนับ T/C1 ที่เปิดใช้งาน

โอซีอี1B=1: ขัดจังหวะเมื่อรีจิสเตอร์การเปรียบเทียบ B ตรงกับเนื้อหาของตัวนับ T/C1 ที่เปิดใช้งาน

TICIE=1: เปิดใช้งานการขัดจังหวะเมื่อตรงตามเงื่อนไขการจับ

TOIE0=1: เปิดใช้งานการขัดจังหวะโอเวอร์โฟลว์ T/C0

ลงทะเบียน ทีไอเอฟอาร์

7 6 5 4 3 2 1 0
ทีโอวี1
โอซีเอฟ1เอ
โอซีเอฟ1บี
-
ไอซีเอฟ1
-
ทีโอวี0
-

ทีโอวี1=1: เกิดการโอเวอร์โฟลว์ของ T/C1

โอซีเอฟ1เอ=1: การลงทะเบียนการเปรียบเทียบ A ตรงกับเนื้อหาของตัวนับ T/C1 ที่อนุญาต

โอซีเอฟ1B=1: การลงทะเบียนการเปรียบเทียบ B ตรงกับเนื้อหาของตัวนับ T/C1 ที่อนุญาต

ไอซีเอฟ=1: ตรงตามเงื่อนไขการจับ

TOV0=1: เกิดการโอเวอร์โฟลว์ของ T/C0

เมื่อเข้าสู่รูทีนย่อยการจัดการการขัดจังหวะ แฟล็กการลงทะเบียน TIFR ที่สอดคล้องกับการขัดจังหวะจะถูกรีเซ็ตเป็นสถานะบันทึกโดยอัตโนมัติ

การขัดจังหวะจะทำงานเฉพาะเมื่อมีการเปิดใช้งานการขัดจังหวะทั่วไปในการลงทะเบียนสถานะ SREG (บิต 7 = 1) เมื่อเกิดการขัดจังหวะ บิตนี้จะถูกรีเซ็ตเป็น 0 โดยอัตโนมัติ และปิดใช้งานการขัดจังหวะที่ตามมา

ในตัวอย่างนี้ พิน INT0 ถูกเปิดใช้งานในโหมดอินพุตแบบดึงขึ้น เมื่อพินลัดวงจรลงกราวด์โดยใช้ปุ่ม ลอจิก 0 จะถูกตั้งค่าไว้ (ขอบของสัญญาณจะลดลงจากแรงดันไฟฟ้าไปที่ 0) และตัวจัดการอินเทอร์รัปต์จะถูกทริกเกอร์ โดยเปิดหลอดไฟที่เชื่อมต่อกับพินศูนย์ของพอร์ต บี

โมฆะ lampON()
{
พอร์ตบี.0=1;
DDRB.0=1;
}

ขัดจังหวะเป็นโมฆะ ext_int0_isr (เป็นโมฆะ)
{
หลอดON();
}

DDRD.2=0;
พอร์ตดี.2=1;

SREG|= (1 ในขณะที่(1) (

ตัวอย่างข้างต้นยังแสดงวิธีการตั้งค่าเวกเตอร์อินเทอร์รัปต์ใน Code Vision AVR (interrupt void ext_int0_isr(void)) เวกเตอร์อินเทอร์รัปต์มีการตั้งค่าคล้ายกันสำหรับกรณีอื่นๆ:

EXT_INT0 2
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC11
USART_RXC 12
USART_DRE 13
USART_TXC14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
ทวิ 18
SPM_พร้อม 19


คำอธิบายการทำงานของตัวจับเวลา/ตัวนับ 1.
การขัดจังหวะจาก TC1

Timer/Counter 1 (TC1) เป็นโมดูล 16 บิตที่ประกอบด้วยรีจิสเตอร์ 8 บิต 10 ตัว จริงๆ แล้วรีจิสเตอร์เหล่านี้เป็นชุดของรีจิสเตอร์ 16 บิตจำนวน 5 ชุด การนับเกิดขึ้นในรีจิสเตอร์ TCNT1H (ตัวนับจับเวลา 1 ไบต์สูง) และรีจิสเตอร์ TCNT1L (ไบต์ต่ำ) ซึ่งรวมกันเป็นรีจิสเตอร์ TCNT1 16 บิต ความสนใจ! หากคุณใช้การอ่านโดยตรงของรีจิสเตอร์ 8 บิต TCNT1H และ TCNT1L คุณจะไม่สามารถแน่ใจได้ว่ารีจิสเตอร์เหล่านี้ถูกอ่านในเวลาเดียวกัน สถานการณ์ต่อไปนี้อาจเกิดขึ้น: ตัวนับมีค่า $01FF คุณนับ TCNT1H (มีค่า 01 ในบางตัวแปร) ในช่วงเวลานี้ เกิดแรงกระตุ้นในการนับ และเนื้อหาของ TCNT1L เท่ากับ $00 และค่า $02 ถูกเขียนไปยัง TCNT1H ตอนนี้คุณอ่านค่าของ TCNT1L ไปยังตัวแปรอื่นแล้ว คุณจะได้รับค่า $00 ในตัวแปรนี้ (ท้ายที่สุด ตัวนับเวลาได้นับแล้ว) ค่า 16 บิตของตัวแปรเหล่านี้กลายเป็น $0100 แต่ในขณะที่อ่านไบต์สูง เนื้อหาของตัวนับคือ $01FF และไบต์ต่ำของคุณควรอ่านเป็น FF เพื่อป้องกันสถานการณ์ดังกล่าว จึงมีการใช้รีจิสเตอร์ชั่วคราวที่อยู่ในบล็อกตัวนับเวลาและตัวนับ การลงทะเบียนนี้มีความโปร่งใสเช่น ทำงานโดยอัตโนมัติ เมื่ออ่านค่าของรีจิสเตอร์ TCNT1L ลงในตัวแปร เนื้อหาของ TCNT1H จะจบลงในรีจิสเตอร์นี้ จากนั้นเมื่ออ่านไบต์สูงลงในตัวแปร ค่าของรีจิสเตอร์ชั่วคราวจะถูกอ่าน การลงทะเบียนชั่วคราวมีความโปร่งใสต่อผู้ใช้ แต่เพื่อให้ทำงานได้อย่างถูกต้องจะต้องปฏิบัติตามลำดับการดำเนินการต่อไปนี้:
สำหรับการดำเนินการเขียนแบบ 16 บิต จะต้องเขียนไบต์ที่สำคัญที่สุดก่อน น้องคนสุดท้องเป็นอันดับสอง
สำหรับการดำเนินการอ่านแบบ 16 บิต จะต้องอ่านไบต์ต่ำก่อน และเนื้อหาของไบต์สูงจะต้องอ่านเป็นลำดับที่สอง
ลงทะเบียน TCCR1Aทำหน้าที่กำหนดโหมดการทำงานของตัวจับเวลา/ตัวนับ 1:

บิต COM1A1, COM1A0, COM1B1 และ COM1B0 - ควบคุมการทำงานของพิน OC1A และ OC1B
บิต FOC1A, FOC1B, WGM11 และ WGM10 ใช้เพื่อระบุการทำงานของ TC1 เป็นตัวปรับความกว้างพัลส์
สามารถตั้งค่าความเร็วในการนับของ TC1 ได้ในรีจิสเตอร์ TCCR1B:

โดยที่บิต ICNC1, ICES1, WGM13 และ WGM12 ยังใช้สำหรับ PWM และ CS12, CS11 และ CS10 จะปรับอัตราการนับดังนี้:


หากเขียนค่า 000 ลงในบิตเหล่านี้ TC0 จะหยุดทำงาน หากมีการเขียน 001 ความถี่สัญญาณนาฬิกาของโปรเซสเซอร์จะถูกส่งผ่านวงจรตัวแบ่งโดยไม่มีการเปลี่ยนแปลง และสำหรับแต่ละรอบสัญญาณนาฬิกาของโปรเซสเซอร์ TC1 จะเพิ่มค่าในการลงทะเบียน TCNT1 ดังนั้น หากเขียน 101 ไปยัง CSxx ค่าใน TCNT1 จะเพิ่มขึ้นทุกๆ รอบของตัวประมวลผลที่ 1024

รีจิสเตอร์ 16 บิต OCR1Aและ OCR1Bทำหน้าที่ตั้งค่า เมื่อถึงโหมดการนับ TS1 จะสร้างการขัดจังหวะที่สอดคล้องกัน

การจัดการขัดจังหวะจาก TC1

เมื่อค่า TCNT1 โอเวอร์โฟลว์ TC1 จะส่งสัญญาณโอเวอร์โฟลว์ของตัวจับเวลา/ตัวนับ 1 ไปยังโปรเซสเซอร์ นอกจากนี้ สัญญาณ Timer/Counter 1 A หรือ B Compare Match จะถูกส่งไปยังโปรเซสเซอร์เมื่อค่าในการลงทะเบียน TCNT1 และ OCR1A และ OCR1B ตรงกันตามลำดับ ปฏิกิริยาของโปรเซสเซอร์ต่อสัญญาณเหล่านี้ (การเรียกการขัดจังหวะที่สอดคล้องกัน) ขึ้นอยู่กับค่าของรีจิสเตอร์ ทิมสค์และตั้งค่าสถานะ I ในการลงทะเบียนสถานะของโปรเซสเซอร์
ในการตั้งค่าการตอบสนองต่อเหตุการณ์ TC1 จะใช้สี่บิตในการลงทะเบียน TIMSK:

บิต 2 - TOIE1 - เมื่อบิตนี้ถูกตั้งค่าเป็น 1 และเปิดใช้งานการขัดจังหวะ โปรเซสเซอร์จะตอบสนองต่อสัญญาณโอเวอร์โฟลว์ TC1 และทำให้เวกเตอร์ขัดจังหวะ $010 (OVF1addr)
บิต 3 - OCIE1B - เมื่อบิตนี้ถูกตั้งค่าเป็น 1 และเปิดใช้งานการขัดจังหวะ โปรเซสเซอร์จะตอบสนองโดยการเรียกเวกเตอร์ขัดจังหวะ $00E (OC1Baddr) ไปยังเหตุการณ์ที่การนับตรงกับค่าคงที่ในรีจิสเตอร์ OCR1B บิต 4 - OCIE1A - เมื่อบิตนี้ถูกตั้งค่าเป็น 1 และเปิดใช้งานการขัดจังหวะ โปรเซสเซอร์จะตอบสนองโดยการเรียกเวกเตอร์ขัดจังหวะ $00C (OC1Aaddr) ไปยังเหตุการณ์ที่การนับตรงกับค่าคงที่ในรีจิสเตอร์ OCR1A บิต 5 - TICIE1 - หากบิตนี้ถูกตั้งค่าและเปิดใช้งานการขัดจังหวะ การขัดจังหวะการจับ TC1 ซึ่งอยู่บนเวกเตอร์ $00A (ICP1addr) จะถูกเปิดใช้งาน