Преобразование RSSI в расстояние.

Будем конвертировать RSSI в расстояние.
За основу взято Design Note DN018 Range Measurements in an Open Field Environment



Введения
В статье будет использоваться SoC CC2510 скорость 250k.

Всего для определения расстояния можно использовать два метода:
— ToF (Time of Flight);
— с помощью RSSI.

ToF это один из самых лучших и удобных методов. Сложен в реализации только на аппаратном уровне. Реализуется на CC2510 c пол пинка. Там есть таймер который можно привязать к событиям трансивера. Точность получиться +- 6 метров, причем улучшить ее никак нельзя(ограничения таймера и трансивера).

Измеряя RSSI можно получить точность +- 1 сантиметр (проверенно на личном опыте). Но есть большой недостаток, точность зависит от расстояния. Также этот метод идеально работает только на открытой местности. Преграды и отражающие поверхности заметно снижают точность. Зато этот метод очень легко реализовать на аппаратном уровне, проблемы будут только при расчете расстояния на сервере.

Ti рекомендует использовать одновременно два метода если требуется измерять расстояния от 0.01 и более 20 метров.

В моем случае требуется максимальная дальность 15 метров.

Аппаратный уровень

Тут у нас все просто. Маячок должен периодически пинговать базовую станцию и все. А на базовой станции уже сохраняется ID, RSSI и время прихода пакета(важно для отслеживания маршрута). В последствии эта информация должна быть передана на сервер, где и произойдет все веселье.

Как видно сделать такое очень легко. Фактически ничего и писать не требуется.

И сразу немного граблей.



Из этого графика выходит, что мы можем использовать только диапазон -20dBm… -95dBm.

Также значение RSSI не ведет себя стабильно, даже если маячок не двигается. Оно немного дрожит. Это происходит из-за шума. Уже выходит, что нам нужно отправлять не один пакет а периодически отправлять серии пакетов и усреднять на базовой станции значение RSSI. Я отправляю по 5ть пакетов каждые 5ть секунд. Между пакетами должны быть небольшие задержки, чтобы вся серия не пришлась на пик помехи.



Задержки между пакетами зависят от максимальной возможно скорости движения объекта. Если выбрать задержки слишком большие, то скорость существенно исказит значение RSSI. В моем случае объект это человек и я взял скорость рекордсмена спринтера.

Во время усреднения на базовой станции я тут-же словил еще один булыжник. Так как я банально усреднял необработанное значение RSSI и в районе -71dBm у меня оно сходило с ума. Оказалось нужно усреднять не RSSI а значение dBi получаемое из RSSI.

Конвертирование делается по формуле.
1)Если RSSI >= 128, то 2*dBm = 2* ((RSSI — 256)/2 — RSSI_offset)
2)Если RSSI <128, то 2*dBm = 2* (RSSI/2 — RSSI_offset)

Умножая на два мы не потеряем точность и не придется иметь дело с float. На сервере результат нужно будет соответственно разделить на два.

Сервер

Итак, данные попали на сервер. Теперь предстоит их преобразовать в расстояние. Вот это уже куда сложнее.

Сначала я заметил, что иногда значение RSSI резко скачет. Тут уже оказывает влияние низкочастотная помеха.



Иногда выходит так, что вся серия пакетов попадает на пик большой помехи. И это существенно может исказить результат. Имеет смысл проводить усреднения и на сервере. Забегая на перед скажу, что фильтр Калмана отлично игнорит такую помеху.

И вот самое интересное. Определяем расстояние.

Можно тупо провести замеры и составить график зависимости. Но так мы получим зависимость только для конкретной местности. Такой подход мне сразу не подошел. Благо Ti предоставило очень круйтой апнот DN018.

В начале я попытался схитрить и решил использовать формулу передачи Фрииса с учетом коэффициента затухания. Формула простая (очень легко вывести значение d) и Ti уже рассчитала все необходимые коэффициенты затухания. Всего-то требовалось каждой базовой станции присвоить нужный коэффициент затухания и дело в шляпе.

Но во время испытаний оказалось, что эта формула на близких расстояниях работает очень плохо. И вот почему



Синяя линия это Фриис а зелена и красная это Фриис с учетом рефлектора. Видно, что различия существенны на близких расстояниях. Догадайтесь какая зависимость наиболее приближена к реальности? Правда если в вашей реальности у вас под ногами есть твердая поверхность по которой можно ходить.



Вот Ti показывает нам результаты опытных испытаний. Видно, что формула хорошо работает на всем диапазоне (проверенна на себе. Это чудо формула).

Делать было нечего. Нужно было все переделать под самую сложную и точную формулу. Два дня я пытался вывести необходимые коэффициенты d и h2. Ни в какую. Думал капец настал.



H1 — высота расположения базовой станции(известно).
H2 — высота расположения маячка(требуется найти).
d — расстояние, не путать с расстоянием прямой видимости (требуется найти).
Direct Dist — определяется легко sqrt(d^2 + (H1-H2)^2). Это еще Пифагор умел.

И тут меня осенило. А зачем вообще выводить эти значения? Проще не насиловать мозг а составить таблицу для всех возможных значений а уже из нее брать нужное для заданного значения dBm.

Быстренько накидал следующий код

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections;

namespace InGoLan
{
    public struct Position
    {
        public double dBm;
        public double h;
        public double d;
    }

    class BaseStation
    {
        public Int64 IEEE { get; private set; }

        private ArrayList arrFriisList = new ArrayList();

        private double PrevRSSI = -50;

        public class StructComparer : IComparer
        {
            int IComparer.Compare(Object x, Object y)
            {
                if (!(x is Position) || !(y is Position)) return 0;
                Position a = (Position)x;
                Position b = (Position)y;

                return a.dBm.CompareTo(b.dBm);
            }
        }

        //constructor
        public BaseStation( Int64 address,           //base station IEEE address
                            double BaseHeight,       //height , Base station
                            double CeilingHeight,    //max height, ceiling 
                            double HeightStep,       //height step   
                            double Distance,         //max distance  
                            double DistanceStep,     //distance step 
                            double RxFrequency,      //Signal frequency in Hz
                            double er,               //Relative permittivity of ground.
                            double GainTX,           //Antenna Gain transmitting antenna.     
                            double GainRX)           //Antenna Gain receiving antenna.
        {

            IEEE = address;

            arrFriisList.Clear();

            double c = 299972458.0;  //Speed of light in vaccum [m/s]
            double Pt = 0.001;      //Energy to the transmitting antenna [Watt]

            double lambda = c / RxFrequency;  //m

            double phi;             //phi incident angle to ground.
            double direct_wave;     //Distance, traveled direct wave
            double refl_wave;       //Distance, traveled reflected wave

            double gamma;           //vertical polarization reflection coefficient

            double length_diff;
            double cos_phase_diff;

            double Direct_energy;
            double reflected_energy;

            double Total_received_energy;
            double Total_received_energy_dBm;

            //Distance thresholds
            double Dist_threshold = DistanceStep * 2;
           

            //height thresholds
            double h_upper_th;          //upper threshold 
            double h_low_th;            //lower threshold
            double h1_threshold = HeightStep * 2;

            if ((BaseHeight - h1_threshold) <= 0)
            {
                h_low_th = 0.01;
            }
            else
            {
                h_low_th = (BaseHeight - h1_threshold);
            }


            if ((BaseHeight + h1_threshold) > CeilingHeight)
            {
                h_upper_th = CeilingHeight;
            }
            else
            {
                h_upper_th = BaseHeight + h1_threshold;
            }


            Position pos = new Position();

            double d_step = 0.01;
            for (double d = d_step; d < Distance; d += d_step)
            {

                double h_step = HeightStep;
                bool first_h = false; 
                for (double h2 = h_step; h2 < CeilingHeight; h2 += h_step)
                {
                    phi = Math.Atan((BaseHeight + h2) / d);
                    direct_wave = Math.Sqrt(Math.Pow(Math.Abs(BaseHeight - h2), 2) + Math.Pow(d, 2));
                    refl_wave = Math.Sqrt(Math.Pow(d, 2) + Math.Pow(BaseHeight + h2, 2));

                    //vertical polarization reflection coefficient
                    gamma = (er * Math.Sin(phi) - Math.Sqrt(er - Math.Pow(Math.Cos(phi), 2))) / (er * Math.Sin(phi) + Math.Sqrt(er - Math.Pow(Math.Cos(phi), 2)));

                    length_diff = refl_wave - direct_wave;
                    cos_phase_diff = Math.Cos((length_diff * 2 * Math.PI) / lambda) * Math.Sign(gamma);

                    Direct_energy = (Pt * GainTX * GainRX * Math.Pow(lambda, 2)) / Math.Pow((4 * Math.PI * direct_wave), 2);
                    reflected_energy = ((Pt * GainTX * GainRX * Math.Pow(lambda, 2)) / Math.Pow((4 * Math.PI * refl_wave), 2)) * Math.Abs(gamma);

                    Total_received_energy = Direct_energy + cos_phase_diff * reflected_energy;
                    Total_received_energy_dBm = 10 * Math.Log10(Total_received_energy * 1000);

                    //save new data                    
                    pos.d = d;
                    pos.h = h2;

                    //optimize list, set dBi step 0.5 
                    pos.dBm = Math.Round(Total_received_energy_dBm * (1 / 0.5)) / (1 / 0.5);

                    arrFriisList.Add(pos);

                    //determine the next height step 
                    if ((h2 >= h_low_th) && (h2 < BaseHeight))
                    {
                        if (h_step != 0.01)
                        {
                            h_step *= 0.5;

                            if (h_step <= 0.01)
                            {
                                h_step = 0.01;
                            }
                        }
                    }
                    else if ((h2 > BaseHeight) && (h2 < h_upper_th))
                    {
                        if (first_h == false)
                        {
                            h_step = 0.01;
                            first_h = true;
                        }
                        else
                        {
                            h_step *= 1.5;

                            if (h_step >HeightStep)
                            {
                                h_step = HeightStep;
                            }
                        }
                    }
                    else
                    {
                        h_step = HeightStep;
                    }
                }

                //determine the next distance step
                if (d_step < Dist_threshold)
                {
                    d_step *= 1.5;
                }
                else
                {
                    d_step = DistanceStep;
                }
            }

            //sort arrFriisList
            arrFriisList.Sort(new StructComparer());

        }


        public ArrayList GetPosition(double RSSI)
        {
            ArrayList arrPos = new ArrayList();
            double localRSSI;

            if (Math.Abs(RSSI) > 95)
            {
                localRSSI = PrevRSSI;
            }
            else
            {
               // if (Math.Abs(PrevRSSI - RSSI) > 10)
               // {
               //     localRSSI = (PrevRSSI + RSSI) / 2.0;
               // }
               // else
               // {
                //    localRSSI = RSSI;
               // }

                PrevRSSI = localRSSI;
            }

            //serch all position
            Position ps = new Position();
            ps.dBm = localRSSI;
            int j = arrFriisList.BinarySearch(ps, new StructComparer());

            if (j < 0)
            {
                //not found
                j = ~j;       //invert

                //check neighbors
                double lo_dBi, hi_dBi;

                //low
                if ((j - 1) > 0)
                {
                    ps = (Position)arrFriisList[(j - 1)];
                }
                else
                {
                    ps = (Position)arrFriisList[0];
                }

                lo_dBi = ps.dBm;

                //hight
                if ((j + 1) < arrFriisList.Count)
                {
                    ps = (Position)arrFriisList[(j + 1)];
                }
                else
                {
                    ps = (Position)arrFriisList[j - 1];
                }

                hi_dBi = ps.dBm;


                double len_lo = Math.Abs(lo_dBi - localRSSI);
                double len_hi = Math.Abs(hi_dBi - localRSSI);

                if (len_lo > len_hi)
                {
                    //hi neighbors better
                    if ((j + 1) < arrFriisList.Count)
                    {
                        j++;
                    }
                }
                else
                {
                    //lo neighbors better
                    if ((j - 1) > 0)
                    {
                        j--;
                    }
                }

            }//if

            //update local_rssi
            ps = (Position)arrFriisList[j];
            double local_rssi = ps.dBm;

            //fix the index
            for (; j > 0; j--)
            {
                ps = (Position)arrFriisList[j];

                if (local_rssi != ps.dBm)
                {
                    j++;
                    break;
                }
            }

            //fix j
            if (j == arrFriisList.Count)
            {
                j--;
            }


            //all position with the specified rssi
            for (; j < arrFriisList.Count; j++)
            {
                ps = (Position)arrFriisList[j];

                if (local_rssi != ps.dBm)
                {
                    break;
                }
                else
                {
                    arrPos.Add(ps);
                }
            }

            return arrPos;
        }

    }
}


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

Работает все простенько. Генерируется таблица всех возможных результатов для конкретной базовой станции. При этом вертикальный и горизонтальный шаг зависит от близости к базовой станции. Так можно получить маленькую таблицу и при этом сохранится высокая точность.
Когда нужно возвращаем список ответов для заданного значения dBm. Именно список а не конкретный ответ. На графиках видно, что одному значению dBi может соответствовать множество ответов. Кстати примечательно, что используя эту формулы мы будем знать высоту расположения маячка.

Также довольно просто добавить дополнительные рефлекторы. Например потолок(его учесть очень легко и размер таблицы не изменится). Но с увеличением количества рефлекторов увеличивается и размер таблицы, зависимость нелинейная.

То, что возвращается список это не страшно. У всего этого списка, почти всегда, прямое расстояние идентично. После получив данные со множества базовых станций и определив координаты уже можно выбрать из списка возможных ответов правильный.

Фильтр Калмана

Расстояние уже определили. Осталось только его отфильтровать, да и сгладить. Информация поступает каждые пять секунд и расстояние будет меняться рывками а это не есть гуд. Можно повысить частоту пингования, но это повысит расход энергии и забьет эфир для других маячков. Ничего, этот недостаток можно пофиксить с помощью Калмана. Ведь он умеет еще и предсказывать :).

Фильтр я брал вот тут.

Он не только повысит точность, но и существенно сгладит движение.

Заключение

На близких расстояниях мне удалось добиться точности +-1 см. С увеличением расстояния точность падает до +-1 м.
А как-же насчет затухания из-за преград и прочее, ведь они не учитываются? Имея данные от множества базовых станций можно скомпенсировать его программно. Это я еще не допилил, но уже вижу что это еще как возможно.

PS: Используемые антенны должны быть стандартизированы. Также нужно использовать высокоточные кварцевые резонаторы, CC2510 очень брезглив и с чем попало не работает.
  • +4
  • 05 ноября 2012, 03:06
  • a9d

Комментарии (25)

RSS свернуть / развернуть
А что такое RSSI?
0
  • avatar
  • Vga
  • 05 ноября 2012, 04:33
Сразу видно, что не трушный радиолюбитель :)
RSSI (англ. Received Signal Strength Indication) — в телекоммуникации, устройство для измерения уровня мощности принимаемого сигнала.
0
Верно. Я программист-электронщик. Самим же радио интересуюсь постольку-поскольку.
0
мне кажется, вы немного оговорились, сказав, что это *устройство*
0
В обиходе это просто уровень радиосигнала.
0
Можно пару замечаний по изложению информации?
Во-первых было бы не плохо в начало статьи (а может кое что и в заголовок) вынести начальные условия. То есть то, что речь идёт об измерении расстояния в помещении (а то я почти до самого конца статьи не мог понять как это так всё хорошо и гладко получается) и применительно к конкретным радиомодулям.
Во-вторых всё-таки стоило бы вставить исходные формулы из апнотов и оговорить, какие параметры придётся крутить при переходе на другие радиомодули.
В-третьих, хорошо бы добавить пару слов о причинах происходящего на первом графике. Какая чувствительность по Rx у данного чипа? Какой бюджет линка?
И да, про булыжник с усреднением можно по-подробнее? Что-то я так и не понял в чём прикол, видимо какая-то специфичная для данного модуля фича?
+3
1)О помещении не сказано, потому что о нем не сказано. Система работает везде. Меняется только количество рефлекторов.
2)Применимо ко всем модулям вообще. Формуле Фрииса подчиняется все. К примеру и звук.
3)В шапке приведен апнот и предполагается, что он будет открыт первым. Я покрайней мере так полагал
4)При переходе на другие изменить нужно только мощность трансивера (указанно в даташите). Поляризацию антенн(в даташите). Коэффициенты усиления и частоту.
5) Максимальная чувствительность не имеет смысла. Максимум один фиг можно использовать -95dBi, в моем случае.
6) Булыжник простой. -71dBi с условием дрожание это RSSI = 0 или 255. Усреднив эти числа в место -71dBi выйдет -120dBi.
0
<zanood mode = on>
1) Ну тут можно поспорить. Нигде не упоминается о том, что стоит задача определения координат — задача сформулирована как пересчёт RSSI в расстояние, то есть и о числе рефлекторов говорить не совсем корректно — он один в данной постановке. Далее переносимся из комнаты с ровным полом, ровным потолком и расстояниями в десятки метров на пересечённую местность с рощами, реками, болотами и, главное, изменяющимися климатическими условиями — картинка переотражения и затухания будет совсем иная, сильно зависящая от погодных условий и времени года. Вот тут уже всё не так просто.
3) Читатели бывают разные — бывают те, кому припёрло в данной теме разобраться и реализовать и те, кто читает ради расширения кругозора, на будущее. Вторые вряд ли сразу полезут углубляться в заумные апноты на буржуйском. Да и уровень подготовки у всех разный — вон товарищ выше не знает, что такое RSSI, но ему тоже интересно. Так что для упрощения формулы лучше показать. Ну или, как минимум, дать ссылки на номера формул, о которых идёт речь.
5) Да в том-то и вопрос — почему нельзя, откуда такая картинка? Я так понял это графики из даташита на модуль, но далеко не в каждом дш. можно такое найти. Хочется понять причины и как это адаптировать к другим модулям, для которых картинки нету.
6) Что ж там за хитрая формула пересчёта? У них регистр RSSI 8 бит?
0
Разве в статье вообще о координатах говориться? Это уже будет в совсем другой статье.
О количестве рефлектором говорить еще как коректно. От них зависит результат.

Далее переносимся из комнаты с ровным полом, ровным потолком и расстояниями в десятки метров на пересечённую местность с рощами, реками, болотами и, главное, изменяющимися климатическими условиями — картинка переотражения и затухания будет совсем иная, сильно зависящая от погодных условий и времени года. Вот тут уже всё не так просто

Ничего подобного. Будет один рефлектор. Его электрическая проницаемость известна. Максимальное расстояние между базовыми станциями не меняется. Ибо на другое и не рассчитана система. Температура и заряд батареи у меня учитывается, этого нет к статье ибо зависит от конкретного чипа.

А насчет других модулей. Тут уже никак помочь не могу. Если не дано графика, то нужно находить экспериментально либо с помощью спец оборудования. Кстати у Ti есть апнот на тему как построить такой график с помощью спектрального анализатора.

Яж писал в статье
Конвертирование делается по формуле.
1)Если RSSI >= 128, то 2*dBi = 2* ((RSSI — 256)/2 — RSSI_offset)
2)Если RSSI <128, то 2*dBi = 2* (RSSI/2 — RSSI_offset)

0 и 255 соответствуют 71 dBi. Числа разные а вот мощность идентичная. Эта формула тоже зависит от чипа.
0
вон товарищ выше не знает, что такое RSSI
Но догадывается по контексту. И настолько ленивый, что и правда статью чисто для кругозора прочитал, а в аппнот не полез.
0
А есть примеры по одновременному измерению с ToF (то есть сразу два метода использовать)?
0
  • avatar
  • x893
  • 05 ноября 2012, 14:40
У меня не используется ToF. В моем случае расстояние между базовыми станциями не превышает 15 метров.
0
Я понял что не используется. Просто может добавить (если не сложно конечно) и проверить на больших расстояниях, или точность возрастет.
0
Добавить в систему в которой погрешность не превышает метра переменную с погрешностью 6ть метров и возрастет точность??
0
Можно добавить простой акселерометр и приделать 3D фильтр
www.codeproject.com/Articles/469458/Kalman-Filtering-Part-2
0
Можно сделать много чего. Но это влияет на цену и энергозатраты + размер. Также с добавлением акселерометра будет 2х мернй фильтр.
0
Это понятно. Просто спросил.
0
А что ты вообще делаешь? Для чего тебе нужно определение расстояния?
0
  • avatar
  • Vga
  • 05 ноября 2012, 20:33
А это новая моднявая фича в европе. Социальная сеть фейсбук в реале. Ходишь в развлекательном комплексе с маячком и все твои контакты, передвижения, встречи сейвятся в сети.
0
жжуть.
осталось только на каждого по камере и дополнять реальность раскраской народа типа свой-чужой.
+1
я тоже думаю, что это идиотизм. Но пипл хавает и платит за это.
0
та да — любой каприз за ваши деньги.
плюсом — задачка интересная.
0
не путайте dBi (dB isotropic — децибел относительно изотропного излучателя) c dBm (dB mW — децибел относительно 1мВт мощности)!
0
исправил
0
reflected_energy=Pt*Gt*Gr*lambda^2./((4*pi*refl_wave).^2).*abs(gamma); 


Кто шарит в matlab. abs(gamma) находится в числителе или знаменатели? По правилам должно находиться в числителе.
0
  • avatar
  • a9d
  • 06 ноября 2012, 17:09
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.