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. Точность момента считывания/выставления сигнала обеспечивается ожиданием перепада уровня на ноге, что означает начало тайм слота.
В третьей части цикла расскажем как создать свой класс устройств для библиотеки OWFS с собственным списком параметров.