# отследим формирование ответа с отправки Общий принцип такой: - MESS_01 (default state) - отправляем массив State_Data обычнфм блокирующим способом - MESS_02 (Transmith packet) - Формируем ответ в массиве Long_Data, отправляем (**что?**) через USART_TX_DMA - MESS_03://Transmith saved packet - обновляем данные в UART_DATA, - запускаeм DMA отправку (**чего?**) # отследим путь информации с ADC hadc1.Init.DMAContinuousRequests = DISABLE; hadc3.Init.DMAContinuousRequests = DISABLE; ### static uint16_t Get_ADC(uint8_t num): - num == 0 => запуск ADC1 - num == 1 => возвращает данные ADC1 - num == 2 => stop ADC1 - num == 3 => запуск ADC3 - num == 4 => возвращает данные ADC3 - num == 5 => stop ADC3 ## USART_TX(State_Data,2) (main.c:414) Срабатывает, если UART_transmission_request == Default state (MESS_1) # при получении 0x7777 1. Decode _uart (main.c:1506) 2. Command копируется в temp2 3. Устанавливаются параметры Curr_setup 1. WORK_EN 2. U5V1_EN 3. U5V2_EN 4. LD1_EN 5. REF1_EN 6. REF2_EN 7. TEC1_EN 8. TEC2_EN 9. TS1_EN 10. TS2_EN 11. SD_EN 12. PI1_RD 13. PI2_RD 4. Парсится заголовок: 5. Eсли 0x1111: 6. Если 0x7777 -- Устанавливаются параметры task: 1. task_type 2. min_param 3. current_param 4. max_param 5. delta_param 6. dt 7. tau 8. sec_param 9. curr 10. temp 11. p_coef_1 12. i_coef_1 13. p_coef_2 14. i_coef_2 Температурные коэффициенты записываются в соотвестсвующие поля LD1_curr_setup и 7. # hardware: MCU настроен так, что не имеет выходов DAC и TIM. Следовательно ток лазеров задается по цифровому интерфейсу. - SDMMC1 -- карта памяти - **SPI2 -- TX** - SPI4 -- RX - SPI5 -- RX - **SPI6 -- TX** - USART1 -- связь с компом main.c:1652: //LL_SPI_Enable(SPI6);//Enable SPI for Laser2 DAC -- *Догадка верна!* Предыдущая строка (main.c:1651): `HAL_GPIO_WritePin(LD2_EN_GPIO_Port, LD2_EN_Pin, GPIO_PIN_SET); Аналогично в(ы)ключается LD1 (main.c:1638-1647) Что такое TEC2? `LL_SPI_Disable(SPI2);//Disable SPI for Laser1 DAC & TEC1` main.c:1739`LL_SPI_TransmitData16(SPI6, DATA);//Transmit word to Laser1 DAC --строка из функции void Set_LTEC(uint8_t num, uint16_t DATA). Функция вызывается: 1. (main.c:260) `temp16=PID_Controller_Temp(&LD1_curr_setup, &LD1_param, 1); `Set_LTEC(3, temp16);//Drive Laser TEC 1 2. (main.c:262) `Set_LTEC(4, temp16);//Drive Laser TEC 2` -- аналогично предыдущему 3. (main.c:277) `Set_LTEC(1,LD1_curr_setup.CURRENT[Work_counter]);//Drive Laser diode 1` -- установка тока лазера 4. main.c:1717 -- definition Set_LTEC -- LL_SPI_TransmitData16(SPI2, DATA);//Transmit word to Laser1 DAC, нужное устройство выбирается интерфейсом SPI (2 или 6) и сигналом EN через GPIO. LD1_curr_setup -- структура с полями: - P_coeff_temp - I_coeff_temp - CURRENT -- массив Заполнение LD1_curr_setup: -- **происходит при парсинге команды 0x1111** `for (uint8_t i=0; iCURRENT[i] = (uint16_t)(*temp2); `temp2++; `}` (main.c:1579-1583) temp2 -- сдвинутый указатель на массив Command. Многократное увеличение temp2 на единицу -- видимо для расшифровки разных параметров. # Структура команд ## 0x1111 | Номер | Название | Код | Расшифровка | | ----- | ---------- | ------------------- | ------------------------------------ | | 0 | Header | 0x1111 | Заголовок | | 1 | Setup | 0xXXXX | Биты настроек | | 2 | LD1_temp | 16bit float | Температура лазера 1 | | 3 | LD2_temp | 16bit float | Температура лазера 1 | | 4 | Reserved | 0x0000 | Зарезервировано | | 5 | Reserved | 0x0000 | Зарезервировано | | 6 | Reserved | 0x0000 | Зарезервировано | | 7 | P_temp1 | 16bit float | Пропорциональный коэффициент для ЛМ1 | | 8 | I_temp1 | 16bit float | Интегальный коэффициент для ЛМ1 | | 9 | P_temp2 | 16bit float | Пропорциональный коэффициент для ЛМ2 | | 10 | I_temp2 | 16bit float | Интегальный коэффициент для ЛМ2 | | 11 | Message_ID | 16bit uint | Номер сообщения | | 12 | LD1_curr | 16bit float***100** | Ток лазера 1 | | 13 | LD2_curr | 16bit float***100** | Ток лазера 2 | | 14 | CRC | 16bit uint | Контрольная сумма | Итого 213 16-битовых слов. ## 0x7777 | Номер | Название | Код | Расшифровка | | | | | ----- | ----------- | ----------- | ------------------------------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------- | | 0 | Header | 0x7777 | Заголовок | поле в структуре | действие | ref | | 1 | Setup | 0xXXXX | Биты настроек (как в 0х1111) | | | | | 2 | TaskType | 16bit uint | 0х1 -- меняется ток лазера 1 | task.task_type | | | | | | | 0х2 -- меняется ток лазера 2 | | | | | | | | 0х3 -- меняется температура лазера 1 | | | | | | | | 0х4 -- меняется температура лазера 2 | | | | | 3 | chngd_p_min | 16bit uint | Минимальное значение изменяемого параметра (mA или C) | task.min_param = task.current_param | записывается в железный драйвер по SPI `Set_LTEC(task.task_type, task.current_param)` | main.c:373-377. И далее (main.c:384)... | | 4 | chngd_p_max | 16bit uint | Максимальное значение изменяемого параметра (mA или C) | task.max_param | | | | 5 | delta_p | 16bit uint | Шаг дискретизации изменяемого параметра (mA или C) | task.delta_param | | | | 6 | delta_t | 16bit uint | Дискретный шаг по времени (мкс) | task.dt | период TIM10 | main.c:360 | | 7 | fixed_param | 16bit uint | Значение другого параметра лазера с изменяемым параметром | task.tau | | | | 8 | I_fixed | 16bit uint | Значение тока для лазера с фиксированными параметрами | task.sec_param | | | | 9 | T_fixed | 16bit uint | Значение температуры для лазера с фиксированными параметрами | task.curr | | | | 10 | Tau | 16bit uint | Значение тау (время релаксации, миллисекунды) | task.temp | | | | 11 | P_temp1 | 16bit float | Пропорциональный коэффициент для ЛМ1 | task.p_coef_1 | | | | 12 | I_temp1 | 16bit float | Интегальный коэффициент для ЛМ1 | task.i_coef_1 | | | | 13 | P_temp2 | 16bit float | Пропорциональный коэффициент для ЛМ2 | task.p_coef_2 | | | | 14 | I_temp2 | 16bit float | Интегальный коэффициент для ЛМ2 | task.i_coef_2 | | | | 15 | CRC | 16bit uint | Контрольная сумма | | | | # Общий принцип работы: С частотой 1кГц: - Установили ток current\[Work_counter] - Померяли температуры - увеличили Work_counter на 1 # получение температуры Есть 2 подозрительные функции: Get_ADC и MPhD_T. Что делает PID_Controller_Temp? -- Реализация ПИ контроллера. ### Get_ADC В строках main.c:286:311 -- вызывается с параметрами 0-5, результат кладется в Long_Data\[CURRL_16\*2+5 -- CURRL_16\*2+10]. CURRL_16 -- макрос для 100. Вероятно, это `//Prepare DATA of internals ADCs` (main.c:281) определение Get_ADC(uint8_t num): num == 0 => HAL_ADC_Start(&hadc1); // Power on num == 1 => ADC1 poll for conversion, return value num == 2 => HAL_ADC_Stop(&hadc1); // Power off num == 3 => HAL_ADC_Start(&hadc3); // Power on num == 4 => ADC3 poll for conversion, return value num == 5 => HAL_ADC_Stop(&hadc3); // Power off => заполнение массива Long_Data\[CURRL_16\*2 +5:9] == результат измерения ADC1. Затем ADC1 выключается. Long_Data\[CURRL_16\*2+10] == единичное измерение ADC3. ADC3 -- включен всего один канал. ###### Измеряется один и тот же канал или разные? -- ADC1 -- Разные; ADC3 -- один (единственный включенный) ADC1 подключено к 5 каналам: IN2, IN8-11. Конфигурация ADC1: **Mode: independent** **Scan conversion mode: enabled** -- после каждого измерения ADC переключается на следующий канал. **Continuous conversion mode: disabled** -- In continuous conversion mode, the ADC starts a new conversion as soon as it finishes one. -- FALSE **Discontinuous conversion mode: disabled** -- По триггеру запускается последовательность из нескольких (ADC_CR1 -> DISCNUM\[2:0]) измерений включенных каналов (ADC_SQRx). **DMA Continuous Requests: Disabled** **End of conversion selection: EOC flag at the end of single channel conversion.** ADC1 и ADC3 -- сконфигурированы одинаково (кроме числа каналов.) ### MPhD_T(uint8_t num) -> uint16_t P 1. медленно дрыгаем ногами SPI(4:5)\_CNV_Pin для //Prepare conversion и //Stop acqusition & start conversion 2. Включаем SPI, ждем получения значения, возвращаем его. 3. Различия между параметрам num(1:4) -- дрыгаем разными ногами и слушаем разные SPI(4 или 5) 4. Ноги: (по 1_1_Report_generator_heterodyne_20240124 1.pdf)^[![[1_1_Report_generator_heterodyne_20240124 1.pdf#page=29]]] | название | на железе (по .ioc) | на схеме из report | окончание | | | ----------------- | ------------------- | ------------------ | ------------------------------------------------------------------------------- | -------------------------------------------- | | ADC_ThrLD1_CS_Pin | PE11 | SPI4_NSS | [AD7686CRMZ](https://www.analog.com/en/products/ad7686.html#part-details) (ADC) | меряет цепь ThrLD1 -- **подтянутую к 2V5_3** | | ADC_MPD1_CS_Pin | PE10 | SPI4_IO1 | [AD7686CRMZ](https://www.analog.com/en/products/ad7686.html#part-details) (ADC) | меряет цепь `100 mV` -- 1/50 от 5VA2 | [Даташит на AD7686](AD7686.pdf) 6. Читаются: 1. num == 1 => MPD1 2. num == 2 => MPD2 3. num == 3 => ThrLD1 4. num == 4 => ThrLD2 7. Зачем-то MPhD_T() в коде всегда вызывается 2 раза подряд с одним и тем же аргументом, причем результат первого вызова отбрасывается. Что такое ThrLD1? -- **Temperature** LaserDiode? ThorlabsLaserDiode? Что такое MPD1? -- Monitor PhotoDiode1? ### Set_LTEC(uint8_t num, uint16_t DATA) - num == 1 => запись DATA в DAC_LD1 - num == 2 => запись DATA в DAC_LD2 - num == 3 => запись DATA в DAC_TEC1 - num == 4 => запись DATA в DAC_TEC2 Куда идут значения MPhD_T(uint8_t num) -> uint16_t adc_val? # В основном рабочем цикле WORK_ENABLE (main.c:243), каждую 1 мс: TO7 -- счетчик TIM7_overflow -- происходят каждую 1 мс(? -- согласно комментарию в коде. Можно проверить, найдя источник тактирования TIM7 и зная настройки таймера) - LD1_param.POWER = MPhD_T(1) (MPD1);//Get Data from monitor photodiode of LD1 **измерили мощность** - LD1_param.LD_CURR_TEMP = MPhD_T(3); **измерили температуру** - temp16=PID_Controller_Temp(&LD1_curr_setup, &LD1_param, 1); **рассчитали новую мощность нагревателя лазера - Set_LTEC(3, temp16);//Drive Laser TEC 1 **отправили новую мощность в DAC нагревателя - LD1_param.LD_CURR_TEMP -- средняя температура, реализовано через накопитель T1_temp. - Корректировка нагревателя происходит непрерывно, каждый рабочий цикл (раз в 1 мс) - Также непрерывно в Long_Data записывается LDx_param.POWER. LD1 -- в позиции от 0 до CURRL_16-1 (\==99), LD2 -- от 100 до 199. - В DAC драйвера лазера записывается новая мощность из LD1_curr_setup.CURRENT\[WorkCounter] - - WorkCounter -- счетчик рабочих циклов. Меняется от 0 до 99, затем сбрасывается. При определенных значениях происходит: - WorkCounter == 0 => старт нового цикла, - WorkCounter == CURRL_16-8 (=92) -- запись значений внутренних ADC в конец Long_Data\[CURRL_16\*2 + 5:10]. (main.c:284) - WorkCounter == CURRL_16-1 (main.c:317) - В LDx_param.LD_CURR_TEMP записывается усредненная за весь цикл (WorkCounter от 0 до 99) температура - В Long_Data\[CURRL_16\*2+1:2] записывается время с дискретностью 10 мс (т.е. единица времени -- 10 мс) - В Long_Data\[CURRL_16\*2+3] записывается LD1_param.LD_CURR_TEMP; (main.c:337) - В Long_Data\[CURRL_16\*2+4] записывается LD2_param.LD_CURR_TEMP; (main.c:340) ## Структура Long_Data | Long_Data индекс | значение | кто пишет | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 0 : CURRL_16-1 (0:99) | Измеренная мониторным диодом мощность лазера 1 | LD1_param.POWER (main.c:269, 274), измеренное MPhD_T(1) (main.c:250) | | CURR_L16 : CURRL_16\*2-1 (100:199) | Измеренная мониторным диодом мощность лазера 2 | LD2_param.POWER (main.c:270, 275), измеренное MPhD_T(2) (main.c:252) | | CURR_L16\*2 +1:2 | Время в десятках мс | TO6_stop = TO6; и следующие 2 строки (main.c:332:334) (TO6 -- счетчик переполнения таймера TIM6, происходящего каждые 10 мс) | | Long_Data\[CURRL_16\*2+3] | Усредненная за цикл варьирования мощности температура лазера 1 | LD1_param.LD_CURR_TEMP (main.c:337) Усреднение -- main.c:323 и main.c:264. Среднее рассчитывается прямо перед записью в массив. До этого в LD1_param.LD_CURR_TEMP хранится последняя измеренная температура | | Long_Data\[CURRL_16\*2+4] | Усредненная за цикл варьирования мощности температура лазера 2 | Аналогично предыдущему | | Long_Data\[CURRL_16\*2 + 5:10] | Значения внутренних ADC. Каналы и пины MCU соответственно: \[ADC1_IN2 - PA2, ADC1_IN8 - PB0, ADC1_IN9 - PB1, ADC1_IN10 - PC0, ADC1_IN11 - PC1, ADC2_IN15 - PF5] | Запись происходит когда WorkCounter == CURRL_16-8. main.c:284:311 | | Long_Data\[CURRL_16\*2 + 11] | //Message ID | main.c:1577 | | Long_Data\[DL_16-1] (212) | Checksum | main.c:342:348 | Long_Data сохраняется на SD карточку (main.c:342:348) Long_Data может быть прочитана с SDcard (main.c:216:218) Long_Data отправляется по UART через DMA (main.c:434:442) или (main.c:420:433), взависимости от UART_transmission_request (если равна MESS_02 или MESS_03) Long_Data (uint16_t) преобразуется в UART_DATA (uint8_t), затем вызывается функция USART_TX_DMA(data_length), запускающая отправку через DMA. DMA контроллер сконфигурирован брать данные из памяти, начиная с указателя UART_DATA (main.c:1437), отправлять в регистр LL_USART_DMA_REG_DATA_TRANSMIT, после чего инкрементировать адрес (main.c:1177), откуда берутся данные. И так data_length раз. #### Где устанавливается флаг UART_transmission_request? MESS_03 -- если Long_Data прочитан с SDcard MESS_02 -- если CPU_state == TRANS_ENABLE Установка флага CPU_state в состояние TRANS_ENABLE: Если UART принял команду 0x4444. # Обработка команды 0x4444 1. по UART пришло сообщение. Когда прием сообщения окончился -- вызывается обработчик UART_RxCpltCallback (stm32f7xx_it.c:376) 2. Обработчик читает заголовок сообщения. Если это 0x4444 -- CPU_state устанавливается в TRANS_ENABLE. 3. В основном цикле (main.c:154) проверяется CPU_state. Если это TRANS_ENABLE -- UART_transmission_request устанавливается в MESS_02, CPU_state устанавливается в предыдущее состояние. 4. В том же цикле проверяется состояние UART_transmission_request. Если это MESS_02: 1. Рассчитывается контрольная сумма массива Long_Data 2. Long_Data отправляется через DMA на передачу UART # Режим TASK_ENABLE 0. TIM10 -- установлен на генерацию прерывания и обновление TO10. Предделитель дает один отсчет таймера в 1 мкс. 1. Установили период TIM10 в dt микросекунд. (обновляем TO10) (main.c:357:363) 2. Каждое обновление TO10 через записываем Set_LTEC в DAC, увеличиваем параметр (task.current_param) на task.delta_param, пока не достигнем максимального значения (task.max_param) (main.c:373:379) 3. Останавливаем TIM10, обнуляем счетчики (TO10, TO10_before), task.current_param = min_param; 4. # почему при установке min, max тока одного лазера ток другого устанавливается == min первого? В For_stm32_2023_12_08 — в main.c:339 — при варьировании тока диода 1 — ток диода 2 устанавливается в мин. значение тока диода 1. В main.c:378 — наоборот. Зачем так сделано — непонятно. В For_stm32_cubeide -- всё хорошо, токи варьируются независимо.