ESP32 เป็นไมโครคอนโทรลเลอร์รุ่นใหม่ที่รองรับ WiFi และ Bluetooth เป็นผู้สืบทอดความนิยมอย่างมากของ Espressif ในเซี่ยงไฮ้และสำหรับผู้ชมงานอดิเรก - ไมโครคอนโทรลเลอร์ ESP8266 .
ลักษณะเฉพาะของไมโครคอนโทรลเลอร์ข้อกำหนดของ ESP32 มีทุกอย่างยกเว้นอ่างล้างจาน เป็นผลิตภัณฑ์ระบบบนชิป (SoC) และต้องใช้ระบบปฏิบัติการเพื่อใช้ประโยชน์จากคุณสมบัติทั้งหมด
บทช่วยสอน ESP32 นี้จะอธิบายและแก้ปัญหาเฉพาะในการสุ่มตัวอย่างตัวแปลงอนาล็อกเป็นดิจิตอล (ADC) จากการขัดจังหวะตัวจับเวลา เราจะใช้ Arduino IDE แม้ว่าจะเป็นหนึ่งใน IDE ที่แย่ที่สุดในแง่ของชุดคุณสมบัติ แต่อย่างน้อย Arduino IDE ก็ติดตั้งและใช้งานสำหรับการพัฒนา ESP32 ได้ง่ายและมีไลบรารีที่ใหญ่ที่สุดสำหรับโมดูลฮาร์ดแวร์ทั่วไปที่หลากหลาย อย่างไรก็ตามเราจะใช้ ESP-IDF API ดั้งเดิมหลายตัวแทน Arduino ด้วยเหตุผลด้านประสิทธิภาพ
ESP32 ประกอบด้วยตัวจับเวลาฮาร์ดแวร์สี่ตัวแบ่งออกเป็นสองกลุ่ม ตัวจับเวลาทั้งหมดจะเหมือนกันโดยมีพรีสคัลเลอร์ 16 บิตและตัวนับ 64 บิต ค่า Prescale ใช้เพื่อ จำกัด สัญญาณนาฬิกาฮาร์ดแวร์ซึ่งมาจากนาฬิกา 80 MHz ภายในที่จะเข้าสู่ตัวจับเวลาไปยังทุกขีด Nth ค่า prescale ต่ำสุดคือ 2 ซึ่งหมายความว่าอินเทอร์รัปต์สามารถยิงอย่างเป็นทางการได้สูงสุดที่ 40 MHz สิ่งนี้ไม่เลวเพราะหมายความว่าที่ความละเอียดของตัวจับเวลาสูงสุดโค้ดตัวจัดการจะต้องทำงานได้สูงสุด 6 รอบสัญญาณนาฬิกา (คอร์ 240 MHz / 40 MHz) ตัวจับเวลามีคุณสมบัติที่เกี่ยวข้องหลายประการ:
divider
- ค่าความถี่ล่วงหน้าcounter_en
- ไม่ว่าจะเปิดใช้งานตัวนับ 64 บิตที่เกี่ยวข้องของตัวจับเวลาหรือไม่ (โดยปกติจะเป็นจริง)counter_dir
- ไม่ว่าตัวนับจะเพิ่มขึ้นหรือลดลงalarm_en
- ไม่ว่าจะเปิดใช้งาน 'การปลุก' หรือการดำเนินการตอบโต้auto_reload
- ตัวนับจะรีเซ็ตเมื่อมีการปลุกหรือไม่โหมดจับเวลาที่สำคัญบางโหมด ได้แก่ :
ตัวนับของตัวจับเวลาสามารถอ่านได้โดยใช้รหัสที่กำหนดเอง แต่ในกรณีส่วนใหญ่เราสนใจที่จะทำบางสิ่งบางอย่างเป็นระยะและนั่นหมายความว่าเราจะกำหนดค่าฮาร์ดแวร์ตัวจับเวลาเพื่อสร้างการขัดจังหวะและเราจะเขียนโค้ดเพื่อจัดการกับมัน
ฟังก์ชันตัวจัดการขัดจังหวะจะต้องเสร็จสิ้นก่อนที่จะมีการสร้างอินเทอร์รัปต์ถัดไปซึ่งทำให้เรามีขีด จำกัด สูงสุดอย่างชัดเจนเกี่ยวกับความซับซ้อนของฟังก์ชัน โดยทั่วไปตัวจัดการขัดจังหวะควรทำงานให้น้อยที่สุดเท่าที่จะทำได้
องค์ประกอบของหลักการออกแบบ
เพื่อให้บรรลุสิ่งที่ซับซ้อนจากระยะไกลควรตั้งค่าสถานะที่ตรวจสอบโดยรหัสที่ไม่ขัดจังหวะแทน I / O ชนิดใดที่ซับซ้อนกว่าการอ่านหรือการตั้งค่าพินเดียวเป็นค่าเดียวมักจะถูกถ่ายโอนไปยังตัวจัดการแยกต่างหากได้ดีกว่า
ในสภาพแวดล้อม ESP-IDF ฟังก์ชัน FreeRTOS vTaskNotifyGiveFromISR()
สามารถใช้เพื่อแจ้งงานที่ตัวจัดการขัดจังหวะ (เรียกอีกอย่างว่า Interrupt Service Routine หรือ ISR) มีอะไรให้ทำ รหัสมีลักษณะดังนี้:
portMUX_TYPE DRAM_ATTR timerMux = portMUX_INITIALIZER_UNLOCKED; TaskHandle_t complexHandlerTask; hw_timer_t * adcTimer = NULL; // our timer void complexHandler(void *param) { while (true) { // Sleep until the ISR gives us something to do, or for 1 second uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)); if (check_for_work) { // Do something complex and CPU-intensive } } } void IRAM_ATTR onTimer() { // A mutex protects the handler from reentry (which shouldn't happen, but just in case) portENTER_CRITICAL_ISR(&timerMux); // Do something, e.g. read a pin. if (some_condition) { // Notify complexHandlerTask that the buffer is full. BaseType_t xHigherPriorityTaskWoken = pdFALSE; vTaskNotifyGiveFromISR(complexHandlerTask, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken) { portYIELD_FROM_ISR(); } } portEXIT_CRITICAL_ISR(&timerMux); } void setup() { xTaskCreate(complexHandler, 'Handler Task', 8192, NULL, 1, &complexHandlerTask); adcTimer = timerBegin(3, 80, true); // 80 MHz / 80 = 1 MHz hardware clock for easy figuring timerAttachInterrupt(adcTimer, &onTimer, true); // Attaches the handler function to the timer timerAlarmWrite(adcTimer, 45, true); // Interrupts when counter == 45, i.e. 22.222 times a second timerAlarmEnable(adcTimer); }
หมายเหตุ: ฟังก์ชันที่ใช้ในโค้ดตลอดบทความนี้มีเอกสาร ESP-IDF API และที่ โครงการ ESP32 Arduino core GitHub .
สิ่งที่สำคัญมากที่ต้องสังเกตคือ IRAM_ATTR
อนุประโยคในความหมายของ onTimer()
ตัวจัดการขัดจังหวะ เหตุผลก็คือแกน CPU สามารถดำเนินการตามคำสั่ง (และเข้าถึงข้อมูล) จาก RAM ในตัวเท่านั้นไม่ใช่จากหน่วยความจำแฟลชที่เก็บรหัสโปรแกรมและข้อมูลตามปกติ ในการหลีกเลี่ยงปัญหานี้ส่วนหนึ่งของ RAM ทั้งหมด 520 KiB นั้นทุ่มเทให้กับ IRAM ซึ่งเป็นแคช 128 KiB ที่ใช้ในการโหลดโค้ดจากแฟลชจัดเก็บ ESP32 ใช้บัสแยกต่างหากสำหรับรหัสและข้อมูล ( “ สถาปัตยกรรมฮาร์วาร์ด” ) ดังนั้นพวกเขาจึงได้รับการจัดการแยกกันเป็นอย่างมากและขยายไปถึงคุณสมบัติของหน่วยความจำ: IRAM มีความพิเศษและสามารถเข้าถึงได้ที่ขอบเขตที่อยู่ 32 บิตเท่านั้น
ในความเป็นจริงหน่วยความจำ ESP32 นั้นไม่สม่ำเสมอกันมาก ภูมิภาคต่างๆมีไว้เพื่อวัตถุประสงค์ที่แตกต่างกัน: พื้นที่ต่อเนื่องสูงสุดมีขนาดประมาณ 160 KiB และหน่วยความจำ 'ปกติ' ทั้งหมดที่โปรแกรมผู้ใช้สามารถเข้าถึงได้มีเพียงประมาณ 316 KiB
การโหลดข้อมูลจากแฟลชจัดเก็บช้าและอาจต้องใช้การเข้าถึงบัส SPI ดังนั้นโค้ดใด ๆ ที่ต้องอาศัยความเร็วจะต้องดูแลให้พอดีกับแคช IRAM และมักจะมีขนาดเล็กกว่ามาก (น้อยกว่า 100 KiB) เนื่องจากส่วนหนึ่งถูกใช้โดย ระบบปฏิบัติการ. โดยเฉพาะอย่างยิ่งระบบจะสร้างข้อยกเว้นหากไม่โหลดโค้ดตัวจัดการขัดจังหวะลงในแคชเมื่อเกิดการขัดจังหวะ มันจะช้ามากและเป็นฝันร้ายด้านลอจิสติกส์ในการโหลดบางสิ่งจากที่เก็บข้อมูลแฟลชเช่นเดียวกับการขัดจังหวะที่เกิดขึ้น IRAM_ATTR
ตัวระบุบน onTimer()
ตัวจัดการจะบอกให้คอมไพลเลอร์และตัวเชื่อมโยงทำเครื่องหมายรหัสนี้ว่าเป็นแบบพิเศษซึ่งจะถูกวางไว้ใน IRAM แบบคงที่และไม่ถูกสลับออก
อย่างไรก็ตาม IRAM_ATTR
ใช้กับฟังก์ชันที่ระบุไว้เท่านั้นฟังก์ชันใด ๆ ที่เรียกจากฟังก์ชันนั้นจะไม่ได้รับผลกระทบ
วิธีการสุ่มตัวอย่างสัญญาณเสียงจากอินเทอร์รัปต์มักจะเกี่ยวข้องกับการรักษาบัฟเฟอร์หน่วยความจำของตัวอย่างกรอกข้อมูลด้วยข้อมูลตัวอย่างจากนั้นแจ้งงานตัวจัดการว่ามีข้อมูลพร้อมใช้งาน
ESP-IDF จัดทำเอกสาร adc1_get_raw()
ฟังก์ชั่นที่วัดข้อมูลบนช่อง ADC เฉพาะบนอุปกรณ์ต่อพ่วง ADC แรก (อันที่สองใช้โดย WiFi) อย่างไรก็ตามการใช้รหัสนี้ในรหัสตัวจัดการตัวจับเวลาจะทำให้โปรแกรมไม่เสถียรเนื่องจากเป็นฟังก์ชันที่ซับซ้อนซึ่งเรียกฟังก์ชัน IDF อื่น ๆ ที่ไม่สำคัญโดยเฉพาะอย่างยิ่งฟังก์ชันที่จัดการกับการล็อกและไม่มี adc1_get_raw()
หรือฟังก์ชันที่เรียกใช้จะถูกทำเครื่องหมายด้วย IRAM_ATTR
ตัวจัดการการขัดจังหวะจะหยุดทำงานทันทีที่มีการเรียกใช้โค้ดที่มีขนาดใหญ่พอที่จะทำให้ฟังก์ชัน ADC ถูกสลับออกจาก IRAM และนี่อาจเป็นสแต็ก WiFi-TCP / IP-HTTP หรือไลบรารีระบบไฟล์ SPIFFS หรืออย่างอื่น
ไปที่กลยุทธ์การตลาดสำหรับสตาร์ทอัพ
หมายเหตุ: ฟังก์ชัน IDF บางฟังก์ชันได้รับการออกแบบมาเป็นพิเศษ (และมีเครื่องหมาย IRAM_ATTR
) เพื่อให้สามารถเรียกใช้จากตัวจัดการขัดจังหวะได้ vTaskNotifyGiveFromISR()
ฟังก์ชันจากตัวอย่างด้านบนเป็นฟังก์ชันหนึ่ง
วิธีที่เป็นมิตรกับ IDF มากที่สุดในการแก้ไขปัญหานี้คือสำหรับตัวจัดการขัดจังหวะเพื่อแจ้งงานเมื่อต้องใช้ตัวอย่าง ADC และให้งานนี้ทำการสุ่มตัวอย่างและการจัดการบัฟเฟอร์โดยอาจใช้งานอื่นสำหรับการวิเคราะห์ข้อมูล (หรือ การบีบอัดหรือการส่งผ่านหรือไม่ว่าในกรณีใดก็ตาม) น่าเสียดายที่สิ่งนี้ไม่มีประสิทธิภาพอย่างยิ่ง ทั้งฝั่งตัวจัดการ (ซึ่งแจ้งงานว่ามีงานต้องทำ) และฝั่งงาน (ซึ่งรับงานที่ต้องทำ) เกี่ยวข้องกับการโต้ตอบกับระบบปฏิบัติการและคำสั่งหลายพันคำที่กำลังดำเนินการ วิธีนี้ในขณะที่ถูกต้องตามหลักวิชาสามารถทำให้ CPU ติดขัดได้มากจนทำให้เหลือพลังงาน CPU เพียงเล็กน้อยสำหรับงานอื่น ๆ
การสุ่มตัวอย่างข้อมูลจาก ADC มักจะเป็นงานง่ายๆดังนั้นกลยุทธ์ต่อไปคือการดูว่า IDF ทำอย่างไรและทำซ้ำในโค้ดของเราโดยตรงโดยไม่ต้องเรียก API ที่ให้มา adc1_get_raw()
มีการใช้งานฟังก์ชันในไฟล์ rtc_module.c
ไฟล์ของ IDF และจากแปดสิ่งที่ทำมีเพียงไฟล์เดียวเท่านั้นที่สุ่มตัวอย่าง ADC ซึ่งทำได้โดยการเรียกไปที่ adc_convert()
โชคดีที่ adc_convert()
เป็นฟังก์ชันง่ายๆที่เก็บตัวอย่าง ADC โดยจัดการกับการลงทะเบียนฮาร์ดแวร์อุปกรณ์ต่อพ่วงผ่านโครงสร้างส่วนกลางที่ชื่อ SENS
การปรับรหัสนี้ให้ทำงานในโปรแกรมของเรา (และเพื่อเลียนแบบพฤติกรรมของ adc1_get_raw()
) นั้นทำได้ง่าย ดูเหมือนว่า:
int IRAM_ATTR local_adc1_read(int channel) { uint16_t adc_value; SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected while (SENS.sar_slave_addr1.meas_status != 0); SENS.sar_meas_start1.meas1_start_sar = 0; SENS.sar_meas_start1.meas1_start_sar = 1; while (SENS.sar_meas_start1.meas1_done_sar == 0); adc_value = SENS.sar_meas_start1.meas1_data_sar; return adc_value; }
ขั้นตอนต่อไปคือการรวมส่วนหัวที่เกี่ยวข้องดังนั้น SENS
ตัวแปรพร้อมใช้งาน:
#include #include
ในที่สุดตั้งแต่ adc1_get_raw()
ดำเนินการขั้นตอนการกำหนดค่าบางอย่างก่อนสุ่มตัวอย่าง ADC ควรเรียกใช้โดยตรงหลังจากตั้งค่า ADC แล้ว ด้วยวิธีนี้สามารถดำเนินการกำหนดค่าที่เกี่ยวข้องได้ก่อนเริ่มจับเวลา
ข้อเสียของวิธีนี้คือไม่ได้ผลดีกับฟังก์ชัน IDF อื่น ๆ ทันทีที่มีการเรียกอุปกรณ์ต่อพ่วงไดรเวอร์หรือโค้ดแบบสุ่มอื่น ๆ ซึ่งรีเซ็ตการกำหนดค่า ADC ฟังก์ชันที่กำหนดเองของเราจะทำงานไม่ถูกต้องอีกต่อไป อย่างน้อย WiFi, PWM, I2C และ SPI จะไม่มีผลต่อการกำหนดค่า ADC ในกรณีที่มีบางสิ่งที่มีอิทธิพลต่อสิ่งนั้นให้เรียกไปที่ adc1_get_raw()
จะกำหนดค่า ADC ให้เหมาะสมอีกครั้ง
ทำไม json ถึงดีกว่า xml
ด้วย local_adc_read()
ฟังก์ชั่นในตำแหน่งรหัสตัวจัดการตัวจับเวลาของเรามีลักษณะดังนี้
#define ADC_SAMPLES_COUNT 1000 int16_t abuf[ADC_SAMPLES_COUNT]; int16_t abufPos = 0; void IRAM_ATTR onTimer() { portENTER_CRITICAL_ISR(&timerMux); abuf[abufPos++] = local_adc1_read(ADC1_CHANNEL_0); if (abufPos >= ADC_SAMPLES_COUNT) { abufPos = 0; // Notify adcTask that the buffer is full. BaseType_t xHigherPriorityTaskWoken = pdFALSE; vTaskNotifyGiveFromISR(adcTaskHandle, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken) { portYIELD_FROM_ISR(); } } portEXIT_CRITICAL_ISR(&timerMux); }
ที่นี่ adcTaskHandle
เป็นงาน FreeRTOS ที่จะนำไปใช้เพื่อประมวลผลบัฟเฟอร์ตามโครงสร้างของ complexHandler
ฟังก์ชันในข้อมูลโค้ดแรก มันจะทำสำเนาบัฟเฟอร์เสียงในเครื่องจากนั้นสามารถประมวลผลได้ตามอัธยาศัย ตัวอย่างเช่นอาจเรียกใช้อัลกอริทึม FFT บนบัฟเฟอร์หรืออาจบีบอัดและส่งผ่าน WiFi
ในทางตรงกันข้ามการใช้ Arduino API แทน ESP-IDF API (เช่น analogRead()
แทน adc1_get_raw()
) จะทำงานได้เนื่องจากฟังก์ชัน Arduino ถูกทำเครื่องหมายด้วย IRAM_ATTR
อย่างไรก็ตามพวกเขาช้ากว่า ESP-IDF มากเนื่องจากมีระดับนามธรรมที่สูงกว่า เมื่อพูดถึงประสิทธิภาพแล้วฟังก์ชันการอ่าน ADC ที่กำหนดเองของเรานั้นเร็วกว่า ESP-IDF ประมาณสองเท่า
ฉันเป็น s corp หรือ c corp
สิ่งที่เราทำที่นี่ - การนำ API ของระบบปฏิบัติการมาใช้ใหม่เพื่อแก้ไขปัญหาบางอย่างซึ่งจะไม่เกิดขึ้นหากเราไม่ได้ใช้ระบบปฏิบัติการ - เป็นภาพประกอบที่ดีของข้อดีข้อเสียของการใช้ระบบปฏิบัติการใน ที่แรก.
ไมโครคอนโทรลเลอร์ที่มีขนาดเล็กกว่าจะได้รับการตั้งโปรแกรมโดยตรงบางครั้งก็เป็นโค้ดแอสเซมเบลอร์และนักพัฒนาสามารถควบคุมการทำงานของโปรแกรมได้อย่างสมบูรณ์ทุกคำสั่ง CPU เดียวและสถานะทั้งหมดของอุปกรณ์ต่อพ่วงทั้งหมดบนชิป สิ่งนี้อาจกลายเป็นเรื่องน่าเบื่อโดยธรรมชาติเมื่อโปรแกรมมีขนาดใหญ่ขึ้นและเมื่อใช้ฮาร์ดแวร์มากขึ้นเรื่อย ๆ ไมโครคอนโทรลเลอร์ที่ซับซ้อนเช่น ESP32 พร้อมชุดอุปกรณ์ต่อพ่วงขนาดใหญ่แกน CPU สองแกนและรูปแบบหน่วยความจำที่ซับซ้อนและไม่สม่ำเสมอจะเป็นเรื่องท้าทายและลำบากในการเขียนโปรแกรมตั้งแต่เริ่มต้น
ในขณะที่ระบบปฏิบัติการทุกระบบมีข้อ จำกัด และข้อกำหนดบางประการเกี่ยวกับโค้ดที่ใช้บริการของมัน แต่ผลประโยชน์มักจะคุ้มค่านั่นคือการพัฒนาที่เร็วขึ้นและง่ายขึ้น อย่างไรก็ตามบางครั้งเราสามารถทำได้และในพื้นที่ฝังมักควรหลีกเลี่ยง
ที่เกี่ยวข้อง: ฉันจะสร้าง Arduino Weather Station ที่ใช้งานได้เต็มรูปแบบได้อย่างไรESP32 เป็นไมโครคอนโทรลเลอร์ที่มี WiFi และบลูทู ธ ที่ใช้สร้างผลิตภัณฑ์ IoT เป็นอุปกรณ์ที่ทรงพลังพร้อมซีพียูแบบดูอัลคอร์และชุดคุณสมบัติมากมายรวมถึงการถ่ายโอนข้อมูลการเข้ารหัสฮาร์ดแวร์แรม 520 KiB และ ADC 12 บิต ใช้ในผลิตภัณฑ์ที่ซับซ้อนซึ่งชุดคุณลักษณะทำให้การพัฒนามีประสิทธิภาพมากขึ้น
Espressif เป็น บริษัท ที่มีผลิตภัณฑ์ที่เป็นที่รู้จักมากที่สุด ได้แก่ ไมโครคอนโทรลเลอร์ที่รองรับ ESP8266 และ ESP32 WiFi ผลิตภัณฑ์ดังกล่าวใช้ในอุปกรณ์ IoT ซึ่งจะได้รับประโยชน์จากชุดคุณลักษณะขนาดใหญ่ของพวกเขา
ตัวจับเวลาคืออุปกรณ์ต่อพ่วงไมโครคอนโทรลเลอร์ (โมดูลภายใน) จับคู่กับสัญญาณนาฬิกาภายในหรือภายนอกซึ่งจะเพิ่มหรือลดค่าในทุกขีดของนาฬิกา ตัวจับเวลาสามารถสร้างอินเทอร์รัปต์ได้หลังจากนับจำนวนที่กำหนดซึ่งทำให้โค้ดถูกเรียกใช้งาน
ESP32 เป็นไมโครคอนโทรลเลอร์ที่รองรับ WiFi และ Bluetooth ที่ใช้สร้างผลิตภัณฑ์ IoT เป็นอุปกรณ์ที่ทรงพลังพร้อมซีพียูแบบดูอัลคอร์และคุณสมบัติชุดใหญ่ การเขียนเฟิร์มแวร์ ESP32 มักจะอาศัยกรอบการพัฒนาที่ผู้จัดจำหน่ายชื่อ ESP-IDF ซึ่งติดตั้งบน FreeRTOS
System-on-a-chip เป็นแนวทางในการสร้างฮาร์ดแวร์ซึ่งรวมถึงส่วนประกอบจำนวนมาก (หรือทั้งหมด) ที่ใช้ในการสร้างคอมพิวเตอร์ที่ใช้งานได้บนแพ็คเกจชิปตัวเดียว ข้อมูลจำเพาะที่แน่นอนแตกต่างกันไปและอาจรวมถึงหน่วยความจำระบบโปรเซสเซอร์กราฟิกคอนโทรลเลอร์ I / O คอนโทรลเลอร์เครือข่ายหน่วยความจำแฟลชและอื่น ๆ