Pengembangan alat ukur IRIS

gambar

Salam komunitas Habr. Baru-baru ini, perusahaan kami meluncurkan alat pengukur dan kontrol IRIS di pasar. Sebagai programmer utama proyek ini, saya ingin memberi tahu Anda tentang perkembangan firmware perangkat (Menurut manajer proyek, firmware tidak lebih dari 30% dari total pekerjaan dari ide hingga produksi massal). Artikel ini terutama akan berguna bagi pengembang pemula dalam hal memahami biaya tenaga kerja dari proyek "nyata" dan pengguna yang ingin "melihat ke bawah tenda".



Tujuan perangkat



IRIS adalah alat ukur multifungsi. Dia tahu bagaimana mengukur arus (amperemeter), voltase (voltmeter), daya (wattmeter) dan sejumlah besaran lainnya. KIP IRIS mengingat nilai maksimumnya, menulis osilogram. Penjelasan rinci tentang perangkat dapat ditemukan di situs web perusahaan.



Sedikit statistik



Pengaturan waktu



Komitmen pertama untuk SVN: 16 Mei 2019.

Rilis: 19 Juni 2020.

* Ini adalah waktu kalender, bukan pengembangan penuh waktu selama jangka waktu tersebut. Ada gangguan untuk proyek lain, ekspektasi spesifikasi teknis, iterasi perangkat keras, dll.



Komit



Nomor dalam SVN: 928

Dari mana asalnya?

1) Saya adalah pendukung microcommitting selama pengembangan

2) Duplikat di cabang untuk perangkat keras dan emulator

3) Dokumentasi

Jadi, angka dengan muatan dalam bentuk kode baru (cabang batang) tidak lebih dari 300.

gambar



Jumlah baris kode



Statistik dikumpulkan oleh utilitas cloc dengan parameter default, tidak termasuk sumber HAL STM32 dan ESP-IDF ESP32.

gambar

Firmware STM32: 38.334 baris kode. Di antaranya:

60870-5-101: 18751

ModbusRTU: 3859

Osiloskop: 1944

Pengarsip: 955

ESP32 firmware: 1537 baris kode.



Komponen perangkat keras (periferal terlibat)



Fungsi utama perangkat diimplementasikan di firmware STM32. Firmware ESP32 bertanggung jawab untuk komunikasi Bluetooth. Komunikasi antar chip dilakukan melalui UART (lihat gambar di header).

NVIC adalah pengontrol interupsi.

IWDG - pengatur waktu pengawas untuk memulai ulang chip jika firmware macet.

Timer - Interupsi timer menjaga detak jantung proyek.

EEPROM - memori untuk menyimpan informasi produksi, pengaturan, pembacaan maksimum, koefisien kalibrasi ADC.

I2C adalah antarmuka untuk mengakses chip EEPROM.

NOR - memori untuk menyimpan bentuk gelombang.

QSPI adalah antarmuka untuk mengakses chip memori NOR.

RTC - jam waktu nyata menyediakan program waktu setelah mematikan perangkat.

ADC - ADC.

RS485 adalah antarmuka serial untuk koneksi melalui ModbusRTU dan protokol 60870-101.

DIN, DOUT - input dan output diskrit.

Tombol - tombol di panel depan perangkat untuk mengalihkan indikasi antara pengukuran.



Arsitektur perangkat lunak



Modul perangkat lunak utama



gambar



Aliran data pengukuran



gambar



sistem operasi



Mempertimbangkan batasan jumlah memori flash (OS memperkenalkan overhead) dan kesederhanaan relatif perangkat, diputuskan untuk meninggalkan penggunaan sistem operasi dan bertahan dengan interupsi. Pendekatan ini telah disorot dalam artikel di Habré lebih dari sekali, jadi saya hanya akan memberikan diagram alur tugas di dalam interupsi dengan prioritas mereka.

gambar



Kode sampel. Generasi interupsi tertunda di STM32.



//     6
  HAL_NVIC_SetPriority(CEC_IRQn, 6, 0);
  HAL_NVIC_EnableIRQ(CEC_IRQn);

//  
HAL_NVIC_SetPendingIRQ(CEC_IRQn);

// 
void CEC_IRQHandler(void) {
// user code
}




Tampilan segmen PWM 7



Perangkat ini memiliki dua baris masing-masing 4 karakter, total 8 indikator. Tampilan 7 segmen memiliki 8 garis data paralel (A, B, C, D, E, F, G, DP) dan 2 garis pemilihan warna (hijau dan merah) untuk masing-masing.

gambar



Penyimpanan bentuk gelombang



Penyimpanan diatur sesuai dengan prinsip buffer melingkar dengan 64 KB "slot" per bentuk gelombang (ukuran tetap).



Memastikan konsistensi data jika terjadi shutdown yang tidak terduga



Di EEPROM, data ditulis dalam dua salinan dengan tambahan checksum di bagian akhir. Jika pada saat pencatatan data perangkat dimatikan, maka setidaknya satu salinan data akan tetap utuh. Checksum juga ditambahkan ke setiap irisan data osiloskop (nilai terukur pada input ADC), sehingga checksum irisan yang tidak valid akan menjadi tanda akhir osilogram.



Pembuatan versi perangkat lunak secara otomatis



1) Buat file version.fmt:

#define SVN_REV ($ WCREV $)

2) Sebelum membuat proyek, tambahkan perintah (untuk System Workbanch):

SubWCRev $ {ProjDirPath} $ {ProjDirPath} /version.fmt $ {ProjDirPath} /version.h

Setelah menjalankan perintah ini, file version.h dengan nomor komit terakhir akan dibuat.



Ada utilitas serupa untuk GIT: GitWCRev. /version.fmt ./main/version.h

#define GIT_REV ($ WCLOGCOUNT $)

Ini memungkinkan Anda untuk mencocokkan komit dan versi perangkat lunak dengan jelas.



Emulator



Karena pengembangan firmware dimulai sebelum munculnya salinan pertama perangkat keras, kemudian bagian kode mulai ditulis sebagai aplikasi konsol pada PC.

gambar

Keuntungan:

- pengembangan dan debugging untuk PC lebih mudah daripada langsung pada perangkat keras.

- kemampuan untuk menghasilkan sinyal input apa pun.

- kemampuan untuk men-debug klien pada PC tanpa perangkat keras. Driver com0com diinstal pada PC, yang membuat sepasang port com. Salah satunya memulai emulator, dan yang lainnya menghubungkan klien.

- berkontribusi pada arsitektur yang indah, karena Anda harus memilih antarmuka modul yang bergantung pada perangkat keras dan menulis dua implementasi



Kode sampel. Dua implementasi membaca data dari eeprom.




uint32_t eeprom_read(uint32_t offset, uint8_t * buf, uint32_t len);
ifdef STM32H7
uint32_t eeprom_read(uint32_t offset, uint8_t * buf, uint32_t len)
{
  if (diag_isError(ERR_I2C))
    return 0;
	if (eeprom_wait_ready()) {
		HAL_StatusTypeDef status = HAL_I2C_Mem_Read(&I2C_MEM_HANDLE, I2C_MEM_DEV_ADDR, offset, I2C_MEMADD_SIZE_16BIT, buf, len, I2C_MEM_TIMEOUT_MS);
		if (status == HAL_OK)
			return len;
	}
	diag_setError(ERR_I2C, true);
  return 0;
}
#endif
#ifdef _WIN32
static FILE *fpEeprom = NULL;
#define EMUL_EEPROM_FILE "eeprom.bin"
void checkAndCreateEpromFile() {
	if (fpEeprom == NULL) {
		fopen_s(&fpEeprom, EMUL_EEPROM_FILE, "rb+");
		if (fpEeprom == NULL)
			fopen_s(&fpEeprom, EMUL_EEPROM_FILE, "wb+");
		fseek(fpEeprom, EEPROM_SIZE, SEEK_SET);
		fputc('\0', fpEeprom);
		fflush(fpEeprom);
	}
}
uint32_t eeprom_read(uint32_t offset, uint8_t * buf, uint32_t len)
{
	checkAndCreateEpromFile();
	fseek(fpEeprom, offset, SEEK_SET);
	return (uint32_t)fread(buf, len, 1, fpEeprom);
}
#endif




Percepatan transfer data (pengarsipan)



Untuk meningkatkan kecepatan mengunduh bentuk gelombang, mereka diarsipkan sebelum dikirim. Perpustakaan uzlib digunakan sebagai pengarsip . Pembongkaran format ini di C # dilakukan dalam beberapa baris kode.



Kode sampel. Pengarsipan data.




#define ARCHIVER_HASH_BITS (12)
uint8_t __RAM_288K archiver_hash_table[sizeof(uzlib_hash_entry_t) * (1 << ARCHIVER_HASH_BITS)];

bool archive(const uint8_t* src, uint32_t src_len, uint8_t* dst, uint32_t dst_len, uint32_t *archive_len)
{
	struct uzlib_comp comp = { 0 };
	comp.dict_size = 32768;
	comp.hash_bits = ARCHIVER_HASH_BITS;
	comp.hash_table = (uzlib_hash_entry_t*)&archiver_hash_table[0];
	memset((void*)comp.hash_table, 0, sizeof(archiver_hash_table));
	comp.out.outbuf = &dst[10]; // skip header 10 bytes
	comp.out.outsize = dst_len - 10 - 8; // skip header 10 bytes and tail(crc+len) 8 bytes
	comp.out.is_overflow = false;

	zlib_start_block(&comp.out);
	uzlib_compress(&comp, src, src_len);
	zlib_finish_block(&comp.out);
	if (comp.out.is_overflow)
		comp.out.outlen = 0;

	dst[0] = 0x1f;
	dst[1] = 0x8b;
	dst[2] = 0x08;
	dst[3] = 0x00; // FLG
	// mtime
	dst[4] =
		dst[5] =
		dst[6] =
		dst[7] = 0;
	dst[8] = 0x04; // XFL
	dst[9] = 0x03; // OS

	unsigned crc = ~uzlib_crc32(src, src_len, ~0);
	memcpy(&dst[10 + comp.out.outlen], &crc, sizeof(crc));
	memcpy(&dst[14 + comp.out.outlen], &src_len, sizeof(src_len));
	*archive_len = 18 + comp.out.outlen;

	if (comp.out.is_overflow)
		return false;
	return true;
}




Kode sampel. Membongkar data.



// byte[] res; //  
                        using (var msOut = new MemoryStream())
                        using (var ms = new MemoryStream(res))
                        using (var gzip = new GZipStream(ms, CompressionMode.Decompress))
                        {
                            int chunk = 4096;
                            var buffer = new byte[chunk];
                            int read;
                            do
                            {
                                read = gzip.Read(buffer, 0, chunk);
                                msOut.Write(buffer, 0, read);
                            } while (read == chunk);

                            //msOut.ToArray();//    
                        }




Tentang perubahan permanen di TK



Meme dari Internet:

- Tapi Anda menyetujui kerangka acuan!

- Tugas teknis? Kami pikir TK adalah "Sudut Pandang", dan kami memiliki beberapa di antaranya.



Kode sampel. Penanganan keyboard.




enum {
	IVA_KEY_MASK_NONE,
	IVA_KEY_MASK_ENTER = 0x1,
	IVA_KEY_MASK_ANY   = IVA_KEY_MASK_ENTER,
}IVA_KEY;
uint8_t keyboard_isKeyDown(uint8_t keyMask) {
	return ((keyMask & keyStatesMask) == keyMask);
}


Setelah melihat potongan kode seperti itu, Anda mungkin berpikir mengapa dia menumpuk semuanya, jika hanya ada satu tombol di perangkat? Di versi pertama TK ada 5 tombol dan dengan bantuannya direncanakan untuk menerapkan pengeditan pengaturan langsung di perangkat:


enum {
	IVA_KEY_MASK_NONE  = 0,
	IVA_KEY_MASK_ENTER = 0x01,
	IVA_KEY_MASK_LEFT  = 0x02,
	IVA_KEY_MASK_RIGHT = 0x04,
	IVA_KEY_MASK_UP    = 0x08,
	IVA_KEY_MASK_DOWN  = 0x10,
	IVA_KEY_MASK_ANY   = IVA_KEY_MASK_ENTER | IVA_KEY_MASK_LEFT | IVA_KEY_MASK_RIGHT | IVA_KEY_MASK_UP | IVA_KEY_MASK_DOWN,
}IVA_KEY;


Jadi, jika Anda menemukan keanehan pada kodenya, maka Anda tidak perlu langsung mengingat programmer sebelumnya dengan kata-kata kasar, mungkin pada saat itu ada alasan untuk implementasi seperti itu.



Beberapa masalah pembangunan



Flush sudah berakhir



Mikrokontroler memiliki memori flash 128 KB. Di beberapa titik, build debug melebihi volume ini. Saya harus mengaktifkan pengoptimalan berdasarkan volume -Os. Jika debugging pada perangkat keras diperlukan, maka perakitan khusus dibuat dengan menonaktifkan beberapa modul perangkat lunak (modbas, 101st).



Kesalahan data QSPI



Terkadang, saat membaca data melalui qspi, byte "ekstra" muncul. Masalahnya hilang setelah meningkatkan prioritas interupsi qspi.



Kesalahan data osiloskop



Karena data dikirim oleh DMA, prosesor mungkin tidak "melihatnya" dan membaca data lama dari cache. Anda perlu melakukan validasi cache.



Kode sampel. Validasi cache.




//           QSPI/DMA
SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)&data[0]) & 0xFFFFFFE0), dataSize + 32);
//    ADC/DMA      CPU
SCB_InvalidateDCache_by_Addr((uint32_t*)&s_pAlignedAdcBuffer[0], sizeof(s_pAlignedAdcBuffer));




Masalah ADC (bacaan berbeda dari menghidupkan ke menghidupkan)



Dari pengaktifan ke pengaktifan, offset berbeda dari pembacaan saat ini (sekitar 10-30 mA) muncul di perangkat. Solusinya dibantu oleh rekan-rekan dari Kompel dalam pribadi Vladislav Barsov dan Alexander Kvashin yang banyak berterima kasih kepada mereka.



Kode sampel. Inisialisasi ADC.



//         
HAL_ADCEx_Calibration_SetValue (&hadc1, ADC_SINGLE_ENDED, myCalibrationFactor[0]);
HAL_ADCEx_Calibration_SetValue (&hadc1, ADC_DIFFERENTIAL_ENDED, myCalibrationFactor[1]);
HAL_ADCEx_LinearCalibration_SetValue (&hadc1, &myLinearCalib_Buffer[0]);




Tampilkan lampu latar



Pada tampilan 7-segmen yang "kosong", bukannya mati total, cahaya lemah muncul. Pasalnya, di dunia nyata bentuk gelombangnya tidak ideal, dan jika Anda menjalankan kode gpio_set_level (0), bukan berarti level sinyal langsung berubah. Flare dihilangkan dengan menambahkan PWM ke jalur data.



Kesalahan Uart di HAL



Setelah error Over-Run terjadi, UART berhenti bekerja. Masalah telah diperbaiki dengan patch HAL:



Kode sampel. Patch untuk HAL.



---    if (((isrflags & USART_ISR_ORE) != 0U)
---        && (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U) ||
---            ((cr3its & (USART_CR3_RXFTIE | USART_CR3_EIE)) != 0U)))
+++    if ((isrflags & USART_ISR_ORE) != 0U)
    {
      __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF);




Mengakses data yang tidak selaras



Kesalahan memanifestasikan dirinya hanya pada perangkat keras dalam rakitan dengan tingkat pengoptimalan -Os. Alih-alih data nyata, klien modbus membaca nol.



Kode sampel. Kesalahan membaca data yang tidak selaras.



	float f_value;
	uint16_t registerValue;
	//     registerValue  0
	//registerValue = ((uint16_t*)&f_value)[(offsetInMaximeterData -
	//	offsetof(mbreg_Maximeter, primaryValue)) / 2];

	//     memcpy  
    memcpy(& registerValue, ((uint16_t*)&f_value) + (offsetInMaximeterData -
        offsetof(mbreg_Maximeter, primaryValue)) / 2, sizeof(uint16_t));




Menemukan penyebab HardFault



Salah satu alat pelokalan pengecualian yang saya gunakan adalah watchpoints. Saya menyebarkan watchpoint di sekitar kode, dan setelah pengecualian muncul, saya terhubung dengan debugger dan melihat titik mana yang telah dilewati kode.



Kode sampel. SET_DEBUG_POINT (__ LINE__).



//debug.h
#define USE_DEBUG_POINTS
#ifdef USE_DEBUG_POINTS
//     SET_DEBUG_POINT1(__LINE__)
void SET_DEBUG_POINT1(uint32_t val);
void SET_DEBUG_POINT2(uint32_t val);
#else
#define SET_DEBUG_POINT1(...)
#define SET_DEBUG_POINT2(...)
#endif

//debug.c
#ifdef USE_DEBUG_POINTS
volatile uint32_t dbg_point1 = 0;
volatile uint32_t dbg_point2 = 0;
void SET_DEBUG_POINT1(uint32_t val) {
  dbg_point1 = val;
}
void SET_DEBUG_POINT2(uint32_t val) {
  dbg_point2 = val;
}
#endif

//     :
SET_DEBUG_POINT1(__line__);




Tips untuk pemula



1) Lihat contoh kode. Untuk esp32, contoh disertakan dengan SDK. Untuk stm32 dalam penyimpanan HAL STM32CubeMX \ STM32Cube_FW_H7_V1.7.0 \ Projects \ NUCLEO-H743ZI \ Example \

2) Google: manual pemrograman <chip Anda>, manual referensi teknis <chip Anda>, catatan aplikasi <chip Anda>, lembar data <milik Anda chip>.

3) Jika Anda mengalami kesulitan teknis dan 2 poin teratas tidak membantu, maka Anda tidak boleh mengabaikan menghubungi dukungan, tetapi lebih kepada distributor yang memiliki kontak langsung dengan insinyur dari perusahaan pabrikan.

4) Bug tidak hanya ada di kode Anda, tetapi juga di HAL pabrikan.



Terima kasih atas perhatian Anda.



All Articles