1-Wire slave на МК. Часть 2: Реализация в коде

от автора


Первая статья цикла: 1-Wire slave на МК. Часть 1: Железо

Что есть в инетрнете по 1-Wire slave

В интернете про реализацию 1-Wire мастера можно найти множество информации, статей, application notes для микроконтроллеров на любой вкус и цвет.
А вот для реализации со стороны Slave материалов немного. Исходных кодов и того меньше. В итоге был найден один исходник для PIC, с ассемблерными вставками и ошибками. Недавно появилась статья на хабре для MSP430 от resetnow. Под катом наш вариант реализации задачи.

Немного о целевом контроллере

ATSAMD20G16 (ссылка на интернет магазин) это микроконтроллер от Atmel с ядром Cortex-M0+. Его основные характеристики:

  • ОЗУ 8 кбайт,
  • Flash 64 кбайт,
  • максимальная тактовая 48 МГц.

Периферия:

  • порты ввода/вывода 38,
  • SERCOM — универсальный модуль, который может быть сконфигурирован как uart, spi или i2c, 6 модулей,
  • таймеры, 8 бит, 16 бит, всего 8,
  • АЦП 12 бит, 20 каналов,
  • ЦАП 10 бит,
  • контроллер сенсорных кнопок,
  • система событий (8 каналов),
  • часы реального времени,
  • аналоговые компараторы,

Реализация

Для разработки ПО для МК была выбрана Atmel Studio 6.2 + ASF.
Для корректной работы необходимо реализовать команды:

  • Search rom
  • Read uid
  • Match rom + собственная система команд (обеспечивает управление пользовательскими светодиодами и реле)

Для отслеживания изменения уровня на шине OW использовалось внешнее прерывание. Для измерения временных интервалов – таймер (отслеживание сигнала reset) и системная частота контроллера (для всего остального).
Таким образом, использованные ресурсы МК: общее тактирование, таймер, внешние прерывание, порты ввода вывода.
Тактирование МК 8 МГц, внутренний RC-генератор.

Основной алгоритм:

При создании нового проекта в Atmel Studio есть возможность выбрать пустой проект для соответствующей отладочной платы. Этот проект уже содержит необходимую инициализацию тактовой частоты, портов ввода вывода и другой периферии при необходимости.
Поэтому рассмотрим фрагменты кода, которые непосредственно отвечают за логику работы.

Внешнее прерывание

Для его работы необходимо сначала инициализировать его, а потом написать процедуру обработки прерывания (callback).
Инициализация всех модулей в ASF происходит по одному сценарию: в структуру, отвечающую за параметры модуля, считываются значения по умолчанию. Затем вносятся необходимые изменения в эти значения, и они устанавливаются. Далее, при необходимости, регистрируются те прерывания, которые будут использоваться в этом модуле (их может быть не одно). И в конце разрешается выполнение обработчиков прерываний (callback).

Код инициализации внешнего прерывания:

void configure_ext_int(void) {     //----------------------channel 2-----------------------------------------       extint_chan_get_config_defaults(&eic_conf_2); 	  eic_conf_2.gpio_pin           = OW_IN_PIN_INT; 	  eic_conf_2.gpio_pin_mux       = OW_IN_PIN_MUX; 	  eic_conf_2.gpio_pin_pull      = EXTINT_PULL_NONE; 	  eic_conf_2.detection_criteria = EXTINT_DETECT_RISING;       eic_conf_2.filter_input_signal = true; 	  extint_chan_set_config( OW_IN_INT_CHANNEL, &eic_conf_2);      	  // Register and enable the callback function  	  extint_register_callback(extint_user_callback_2, OW_IN_INT_CHANNEL,EXTINT_CALLBACK_TYPE_DETECT); 	  extint_chan_enable_callback(OW_IN_INT_CHANNEL,EXTINT_CALLBACK_TYPE_DETECT);      } 

Из кода видно, что отличие от настроек по умолчанию состоит в выборе порта ввода/вывода, прерывание на котором отслеживается, отсутствие подтяжки, и событие, по которому происходит прерывание (по нарастающему фронту).

Таймер

Код инициализации таймера:

void configure_tc0(void) { 	struct tc_config config_tc; 	tc_get_config_defaults(&config_tc);  	config_tc.counter_size    = TC_COUNTER_SIZE_16BIT; 	config_tc.wave_generation = TC_WAVE_GENERATION_MATCH_FREQ; 	config_tc.counter_16_bit.compare_capture_channel[0] = 350;      config_tc.clock_prescaler=TC_CLOCK_PRESCALER_DIV8;     config_tc.clock_source=GCLK_GENERATOR_0;     config_tc.reload_action=TC_RELOAD_ACTION_RESYNC;  	tc_init(&tc_instance_tc0, TC0, &config_tc); 	tc_enable(&tc_instance_tc0);         tc_stop_counter(&tc_instance_tc0); } 

Из кода видно, что таймер переводится в режим 16 битного счетчика, работа по совпадению частоты, считаем до 350 тиков, предделитель 8, тактируется от основного тактового сигнала (8 Мгц).
Особый интерес представляет функция задержки на определенное число микросекунд. Она необходима для «взаимопонимания» между устройствами на шине. Так как библиотека ASF достаточна «тяжелая», то генерация небольших временных задержек (порядка нескольких микросекунд) потребовала применения встроенной функции, а также оптимизации O3.

Код:

inline void my_delay_us(uint32_t usec) {    volatile uint32_t ctr_reg_shdw ;        ctr_reg_shdw = SysTick->CTRL; // Clear count flag by reading reg    SysTick->LOAD = (usec) * (7880000 / 1000000);     SysTick->VAL = 0;    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; // 0x5    // wait for flag, do not need interrupts here    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); } 

Код обработчика внешнего прерывания по нарастающему фронту:

void extint_user_callback_2(void) {      if(reset_flag==0)    {       reset_flag=1; 	  tc_set_count_value(&tc_instance_tc0, 0);       tc_start_counter(&tc_instance_tc0); 	  port_pin_set_output_level(LED_0_PIN, 0);    }    else    {        tc_set_count_value(&tc_instance_tc0, 0);       reset_flag=0;     }    } 

Код обработчика прерывания от таймера

void tc0_callback(struct tc_module *const module_inst) {      volatile unsigned char tmp=0;    if(port_pin_get_input_level(PIN_PA04)!=0)    {       while((OW_check_in_level())!=0);       if(reset_flag==1)       {      		             reset_flag=2;                  SendPresense();                  tmp=0;          tmp=get_byte();          if (tmp==0xf0)          {               search_rom();               mute_on=0;                        reset_flag=0;          }          else          {             // comeend "read UID"             if(tmp==0x33)             {                //send UID                for(unsigned char i=0;i<8;i++) send_byte(ownuid[i]);                   }             // match rom             if(tmp==0x55)             {                 // wait for own UID+ command+address                // potential problem: if less than 10 bytes received program hangs here                for(unsigned char i=0;i<10;i++) in_buffer[i]=get_byte();                   // if own UID                if((in_buffer[0]==ownuid[0])&&(in_buffer[1]==ownuid[1])&&(in_buffer[2]==ownuid[2])                   &&(in_buffer[3]==ownuid[3])&&(in_buffer[4]==ownuid[4])&&(in_buffer[5]==ownuid[5])                    &&(in_buffer[6]==ownuid[6])&&(in_buffer[7]==ownuid[7]))                {                                // if command 0xA0 (reading)                   if(in_buffer[8]==0xa0)                   {                       // my_delay_us(3);                      // what address to read from?                      if(in_buffer[9]==0)                      {                                             // count crc                         registers[0]=0;                         registers[0] = OWI_ComputeCRC8(registers[1], registers[0]);                          // send value and crc                         send_byte(registers[1]);                         send_byte(registers[0]);                      } 			 …..                   }                    // if command 0xA1 (write)                   if(in_buffer[8]==0xa1)                   {                        write_flag=1;                      in_buffer[10]=get_byte();                      //my_delay_us(3);                      // what address to write to                      if((in_buffer[9]==0)||(in_buffer[9]==1)||(in_buffer[9]==2))                      {                        //nothing to do, as they are read only                      }                      if(in_buffer[9]==3)                      {                         registers[4]=in_buffer[10];                      } ….                   }                }// end of if my_uid             }//end of  if(tmp==0x55)          }      }//end of if(reset_flag==1)    } //end of if((PORTD.IN&0x01)==0)    else reset_flag=0; } 

Основными сложностями при реализации устройства были подбор и точность таймингов, а также времени считывания/выставления сигнала.
С таймингами проблема была решена использованием inline+systick. Точность момента считывания/выставления сигнала обеспечивается ожиданием перепада уровня на ноге, что означает начало тайм слота.

ссылка на оригинал статьи http://geektimes.ru/post/262156/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *