Давайте рассмотрим достаточно типовую на сегодняшний день задачу: есть ПК, есть какое-то внешнее «железо» на базе микроконтроллера с USB, необходимо наладить обмен данными. Рассмотрим решение этой задачи, несколько уточнив требования:
- работать будем через virtual COM port на базе USB;
- среда программирования Visual Studio;
- язык C#,
причем требования к аппаратной составляющей и вопросы программирования микроконтроллера STM32F102 освещены в статье-симбионте «Использование USB в STM32 на примере Virtual COM port», а здесь мы рассмотрим только разработку ПО для ПК.
Итак, с точки зрения программы для ПК, у вас в системе при подключении USB-устройства просто появляется новый COM-порт (как это сделать, смотрите в вышеназванной статье). На том конце COM-порта — электронное устройство, которому необходимо передать массив данных (например, пользовательские настройки или калибровочные данные). Кроме того, необходимо эти данные еще и читать (например, для сверки двух наборов калибровочных данных — записанных в устройство и заданных через пользовательский интерфейс), и хранить в виде файла на жестком диске ПК (дабы не вбивать данные через пользовательский интерфейс при каждом перезапуске программы).
Чтобы не опираться на совсем уж невидимые абстракции, давайте создадим минимальный интерфейс пользователя, для, скажем, самодельной GSM-сигнализации. Внешний вид программы показан ниже.
Давайте попробуем написать немного кода. Для начала создадим структуру данных для хранения всей той информации, которую пользователь может ввести через визуальный интерфейс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | SystemPack SystemPacket = new SystemPack(); // Текущий пакет данных со стороны ПК [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] public unsafe struct SystemPack { // Перед отправкой пакета контроллеру сюда надо записать общую длину пакета public ushort PackLength; // Вкладка "Телефоны" [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber1; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber2; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber3; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber4; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber5; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TELEPHONELENGTH)] public byte[] TelephoneNumber6; public byte SMSInfo1; public byte SMSInfo2; public byte SMSInfo3; public byte SMSInfo4; public byte SMSInfo5; public byte SMSInfo6; public byte SMSManagement1; public byte SMSManagement2; public byte SMSManagement3; public byte SMSManagement4; public byte SMSManagement5; public byte SMSManagement6; public byte CallAttempt; // Вкладка "Входы" [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] InputState; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6 * 6)] public byte[,] InputDoCall; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6 * 6)] public byte[,] InputDoSMS; // Вкладка "Выходы" [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] OutputState; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] AlarmOn; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] AlarmReaction; // Контрольная сумма пакета public ushort PackCheckSum; } |
После этого уточняем формат полей, инициализированных как public byte[,], место под которые компилятором выделено, но подробности реализации пока не известны:
1 2 3 4 5 6 7 8 9 | public MainForm() { InitializeComponent(); ... SystemPacket.InputDoCall = new byte[6, 6]; SystemPacket.InputDoSMS = new byte[6, 6]; } |
Предположим, пользователь ввел данные в необходимые поля и хочет отправить пакет данных через COM-порт. Памятуя о том, что кроме передачи внешнему контроллеру, у нас должна быть еще запись введенной информации на жесткий диск, лучше выделить операцию преобразования в отдельную функцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | unsafe void VisualToStruct() { // Вкладка "Телефоны" SystemPacket.TelephoneNumber1 = StringToASCIIArray(TelephoneNumber1.Text, TELEPHONELENGTH); SystemPacket.SMSInfo1 = Convert.ToByte(SMSInfo1.Checked); SystemPacket.SMSManagement1 = Convert.ToByte(SMSManagement1.Checked); try { SystemPacket.CallAttempt = Convert.ToByte(CallAttempt.Text); } catch (System.FormatException ex) { SystemPacket.CallAttempt = 1; } } |
Обратная операция будет выглядеть примерно так:
1 2 3 4 5 6 7 8 | unsafe void StructToVisual() { // Вкладка "Телефоны" TelephoneNumber1.Text = Encoding.ASCII.GetString(SystemPacket.TelephoneNumber1); SMSInfo1.Checked = Convert.ToBoolean(SystemPacket.SMSInfo1); SMSManagement1.Checked = Convert.ToBoolean(SystemPacket.SMSManagement1); CallAttempt.Text = Convert.ToString(SystemPacket.CallAttempt); } |
Операции записи на диск и чтения с диска будут, соответственно, выглядеть как:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | private void SaveToolStripMenuItem_Click(object sender, EventArgs e) { SaveStructFileDialog.ShowDialog(); if (SaveStructFileDialog.FileName != "") { VisualToStruct(); // Текущие значения визуальных компонентов будут преобразованы в структуру // Структура с текущими данными будет записана в файл try { byte[] buffer = new byte[System.Runtime.InteropServices.Marshal.SizeOf(SystemPacket)]; GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); Marshal.StructureToPtr(SystemPacket, h.AddrOfPinnedObject(), false); h.Free(); BinaryWriter bw = new BinaryWriter(File.Open(SaveStructFileDialog.FileName, FileMode.Create)); bw.Write(buffer); bw.Close(); bw = null; } catch (Exception ex) { throw ex; } } } private void OpenToolStripMenuItem_Click(object sender, EventArgs e) { OpenStructFileDialog.ShowDialog(); if (OpenStructFileDialog.FileName != "") { // Текущие данные будут считаны из файла byte[] buffer = new byte[Marshal.SizeOf(SystemPacket) + 1]; try { buffer = File.ReadAllBytes(OpenStructFileDialog.FileName); GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); SystemPacket = (SystemPack)Marshal.PtrToStructure(h.AddrOfPinnedObject(), typeof(SystemPack)); h.Free(); byte[,] a = new byte[6, 6]; // Превращаем InputDoCall и InputDoSMS снова в двухмерные массивы, ибо их после PtrToStructure расплющило в одномерные System.Buffer.BlockCopy(SystemPacket.InputDoCall, 0, a, 0, 36); SystemPacket.InputDoCall = new byte[6, 6]; Array.Copy(a, SystemPacket.InputDoCall, 36); System.Buffer.BlockCopy(SystemPacket.InputDoSMS, 0, a, 0, 36); SystemPacket.InputDoSMS = new byte[6, 6]; Array.Copy(a, SystemPacket.InputDoSMS, 36); StructToVisual(); // Текущие значения визуальных компонентов будут считаны из структуры } catch (Exception ex) { MessageBox.Show("Файл архива с данными отсутствует", "Предупреждение"); } } } |
Ну и наконец, обмен с внешним «железом» через COM-порт:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | private async void DataReceviedHandler(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = (SerialPort)sender; string ExpectedResponse = ""; int packsize = 0; byte[] packbuf, expected; switch (DataTransferState) { case DATAWRITEREQUEST: ExpectedResponse = "HardwareWriteResponseOK"; packsize = ExpectedResponse.Length; packbuf = new byte[packsize]; sp.Read(packbuf, 0, packsize); expected = StringToASCIIArray(ExpectedResponse, packsize); if (ByteArraysComparer(packbuf, expected, packsize)) DataTransferState = DATAWRITERESPONSE; break; case DATAREADREQUEST: ExpectedResponse = "HardwareReadResponseOK"; packsize = ExpectedResponse.Length; packbuf = new byte[packsize]; sp.Read(packbuf, 0, packsize); expected = StringToASCIIArray(ExpectedResponse, packsize); if (ByteArraysComparer(packbuf, expected, packsize)) { DataTransferState = DATAREADRESPONSE; int backpacksize = 0; try { byte[] f = { 1, 2 }; // Передаем незначащие данные, чтобы запустить прерывание USB контроллера и инициировать обратную передачу пакета DeviceSerialPort.Write(f, 0, 2); await Task.Delay(200); // Даем время контроллеру переварить полученный запрос packsize = Marshal.SizeOf(SystemPacket); packbuf = new byte[packsize]; backpacksize = sp.Read(packbuf, 0, packsize); } catch (TimeoutException ex) { MessageBox.Show("Контроллер подключен, но в настоящее время собирает актуальную информацию о GSM-сети. Повторите запрос позже.", "Предупреждение"); } if (backpacksize > 0) { bool crccheck = false; ushort ReceiveCRC16 = ComputeByteArrayCRC16(packbuf, 0, packsize - 2); // При расчете контрольной суммы не учитываем два последних байта, содержащих собственно контрольную сумму ushort a = packbuf[packsize - 2]; int b = packbuf[packsize - 1] << 8; int ExpectedCRC16 = a + b; if (ReceiveCRC16 == ExpectedCRC16) crccheck = true; if (DataPurpose == READ) { if (crccheck) { IntPtr ptr = Marshal.AllocHGlobal(packsize); // Превращаем принятый массив в структуру Marshal.Copy(packbuf, 0, ptr, packsize); SystemPacket = (SystemPack)Marshal.PtrToStructure(ptr, SystemPacket.GetType()); Marshal.FreeHGlobal(ptr); this.Invoke((MethodInvoker)delegate { StructToVisual(); // Отображаем структуру в UI потоке }); MessageBox.Show("С контроллера считан пакет данных. Ожидаемый размер: " + packsize + " байт, фактический размер " + backpacksize + " байт. Контрольная сумма верна.", "Сообщение"); } else MessageBox.Show("С контроллера считан пакет данных. Ожидаемый размер: " + packsize + " байт, фактический размер " + backpacksize + " байт. Контрольная сумма не верна.", "Ошибка"); } else if (DataPurpose == COMPARE) { SystemPacket.PackLength = (ushort)packsize; // Зажимаем длину в два байта и записываем ее в заголовок пакета expected = new byte[packsize]; GCHandle h = GCHandle.Alloc(expected, GCHandleType.Pinned); Marshal.StructureToPtr(SystemPacket, h.AddrOfPinnedObject(), false); h.Free(); if (backpacksize != packsize) MessageBox.Show("С контроллера считан пакет данных неверного размера. Ожидаемый размер: " + packsize + " байт, фактический размер " + backpacksize + " байт.", "Ошибка"); else if (ByteArraysComparer(packbuf, expected, packsize - 2)) MessageBox.Show("Данные совпадают", "Сообщение"); else MessageBox.Show("Данные не совпадают", "Сообщение"); } } } break; } } |