#pragma once #include enum class CyfralReaderError : uint8_t { NO_ERROR = 0, UNABLE_TO_DETECT = 1, RAW_DATA_SIZE_ERROR = 2, UNKNOWN_NIBBLE_VALUE = 3, NO_START_NIBBLE = 4, }; class CyfralReader { private: ADC_HandleTypeDef adc_config; ADC_TypeDef* adc_instance; uint32_t adc_channel; void get_line_minmax(uint16_t times, uint32_t* min_level, uint32_t* max_level); void capture_data(bool* data, uint16_t capture_size, uint32_t line_min, uint32_t line_max); bool parse_data(bool* raw_data, uint16_t capture_size, uint8_t* data, uint8_t count); uint32_t search_array_in_array( const bool* haystack, const uint32_t haystack_size, const bool* needle, const uint32_t needle_size); // key is 9 nibbles static const uint16_t bits_in_nibble = 4; static const uint16_t key_length = 9; static const uint32_t capture_size = key_length * bits_in_nibble * 2; CyfralReaderError error; public: CyfralReader(ADC_TypeDef* adc, uint32_t Channel); ~CyfralReader(); void start(void); void stop(void); bool read(uint8_t* data, uint8_t count); }; void CyfralReader::get_line_minmax(uint16_t times, uint32_t* min_level, uint32_t* max_level) { uint32_t in = 0; uint32_t min = UINT_MAX; uint32_t max = 0; for(uint32_t i = 0; i < 256; i++) { HAL_ADC_Start(&adc_config); HAL_ADC_PollForConversion(&adc_config, 100); in = HAL_ADC_GetValue(&adc_config); if(in < min) min = in; if(in > max) max = in; } *min_level = min; *max_level = max; } void CyfralReader::capture_data( bool* data, uint16_t capture_size, uint32_t line_min, uint32_t line_max) { uint32_t input_value = 0; bool last_input_value = 0; uint32_t diff = line_max - line_min; uint32_t mid = line_min + diff / 2; uint32_t low_threshold = mid - (diff / 4); uint32_t high_threshold = mid - (diff / 4); uint16_t capture_position = 0; uint32_t instructions_per_us = (SystemCoreClock / 1000000.0f); uint32_t time_threshold = 75 * instructions_per_us; uint32_t capture_max_time = 140 * (capture_size * 2) * instructions_per_us; uint32_t start = DWT->CYCCNT; uint32_t end = DWT->CYCCNT; memset(data, 0, capture_size); osKernelLock(); uint32_t capture_start = DWT->CYCCNT; while((capture_position < capture_size) && ((DWT->CYCCNT - capture_start) < capture_max_time)) { // read adc HAL_ADC_Start(&adc_config); HAL_ADC_PollForConversion(&adc_config, 100); input_value = HAL_ADC_GetValue(&adc_config); // low to high transition if((input_value > high_threshold) && last_input_value == 0) { last_input_value = 1; start = DWT->CYCCNT; } // high to low transition if((input_value < low_threshold) && last_input_value == 1) { last_input_value = 0; end = DWT->CYCCNT; // check transition time if(end - start < time_threshold) { data[capture_position] = 1; capture_position++; } else { data[capture_position] = 0; capture_position++; } } } osKernelUnlock(); } uint32_t CyfralReader::search_array_in_array( const bool* haystack, const uint32_t haystack_size, const bool* needle, const uint32_t needle_size) { uint32_t haystack_index = 0, needle_index = 0; while(haystack_index < haystack_size && needle_index < needle_size) { if(haystack[haystack_index] == needle[needle_index]) { haystack_index++; needle_index++; if(needle_index == needle_size) { return (haystack_index - needle_size); }; } else { haystack_index = haystack_index - needle_index + 1; needle_index = 0; } } return haystack_index; } bool CyfralReader::parse_data(bool* raw_data, uint16_t capture_size, uint8_t* data, uint8_t count) { const bool start_nibble[bits_in_nibble] = {1, 1, 1, 0}; uint32_t start_position = search_array_in_array(raw_data, capture_size, start_nibble, bits_in_nibble); uint32_t end_position = 0; memset(data, 0, count); if(start_position < capture_size) { start_position = start_position + bits_in_nibble; end_position = start_position + count * 2 * bits_in_nibble; if(end_position >= capture_size) { error = CyfralReaderError::RAW_DATA_SIZE_ERROR; return false; } bool first_nibble = true; uint8_t data_position = 0; uint8_t nibble_value = 0; while(data_position < count) { nibble_value = !raw_data[start_position] << 3 | !raw_data[start_position + 1] << 2 | !raw_data[start_position + 2] << 1 | !raw_data[start_position + 3]; switch(nibble_value) { case(0x7): case(0xB): case(0xD): case(0xE): break; default: error = CyfralReaderError::UNKNOWN_NIBBLE_VALUE; return false; break; } if(first_nibble) { data[data_position] |= nibble_value << 4; } else { data[data_position] |= nibble_value; } first_nibble = !first_nibble; if(first_nibble) { data_position++; } start_position = start_position + bits_in_nibble; } error = CyfralReaderError::NO_ERROR; return true; } error = CyfralReaderError::NO_START_NIBBLE; return false; } CyfralReader::CyfralReader(ADC_TypeDef* adc, uint32_t channel) { adc_instance = adc; adc_channel = channel; } CyfralReader::~CyfralReader() { } void CyfralReader::start(void) { ADC_ChannelConfTypeDef sConfig = {0}; // init ADC adc_config.Instance = adc_instance; adc_config.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; adc_config.Init.Resolution = ADC_RESOLUTION_12B; adc_config.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc_config.Init.ScanConvMode = ADC_SCAN_DISABLE; adc_config.Init.EOCSelection = ADC_EOC_SINGLE_CONV; adc_config.Init.LowPowerAutoWait = DISABLE; adc_config.Init.ContinuousConvMode = DISABLE; adc_config.Init.NbrOfConversion = 1; adc_config.Init.DiscontinuousConvMode = DISABLE; adc_config.Init.ExternalTrigConv = ADC_SOFTWARE_START; adc_config.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; adc_config.Init.DMAContinuousRequests = DISABLE; adc_config.Init.Overrun = ADC_OVR_DATA_PRESERVED; adc_config.Init.OversamplingMode = DISABLE; if(HAL_ADC_Init(&adc_config) != HAL_OK) { Error_Handler(); } // init channel sConfig.Channel = adc_channel; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; if(HAL_ADC_ConfigChannel(&adc_config, &sConfig) != HAL_OK) { Error_Handler(); } } void CyfralReader::stop(void) { HAL_ADC_DeInit(&adc_config); } bool CyfralReader::read(uint8_t* data, uint8_t count) { uint32_t line_level_min, line_level_max; bool raw_data[capture_size]; bool result = false; error = CyfralReaderError::NO_ERROR; // calibrate get_line_minmax(256, &line_level_min, &line_level_max); // TODO think about other detection method // key not on line if(line_level_max > 2000) { error = CyfralReaderError::UNABLE_TO_DETECT; return false; } // capturing raw data consisting of bits capture_data(raw_data, capture_size, line_level_min, line_level_max); // parse captured data if(parse_data(raw_data, capture_size, data, count)) { result = true; } return result; }