Кольцевой буфер – это метод передачи данных в котором используется оперативная память фиксированного размера. В процессе передачи данных после заполнения последней ячейки памяти, запись начинается с первой ячейки, при этом старые данные перезаписываются, чтение из буфера происходит таким же образом, по замкнутому маршруту. Чтобы лучше понять, в каких случаях можно использовать кольцевой буфер, рассмотрим два примера:
1. Требуется равномерно передавать данные от источника к приемнику, без задержек, при этом время получения единицы данных (например, байта) от источника может сильно варьироваться (присутствует асинхронность работы источника и приемника). Источник может задержать выдачу данных из-за программных/аппаратных особенностей, задержка может появиться вследствие промежуточных вычислений в процессе передачи данных и т.д. Кольцевой буфер позволит избавиться от задержек, так как в запасе всегда будет находиться некоторое количество данных полученных от приемника. Для корректной работы буфера, должно соблюдаться следующее условие: средняя скорость получение данных от источника должна быть равна или выше скорости передачи данных на приемник.
2. От источника равномерно поступают данные, которые необходимо отправить на приемник, при этом время обработки данных приемником не является постоянной величиной. Если приемник будет долго обрабатывать данные, или будут выполняться промежуточные вычисления, то данные поступающие от источника могут быть утеряны. При использовании кольцевого буфера будет исключен пропуск данных от источника, данные запишутся в свободные ячейки буфера. Данная конструкция будет работать при условии, что средняя скорость передачи данных на приемник равна или выше скорости поступления данных от источника.
Рассмотрим организацию кольцевого буфера в микроконтроллерах PIC. Память ОЗУ выделенную под кольцевой буфер, я делю на две равные части. Обе части буфера могут располагаться в одном банке ОЗУ, или в двух банках в целях увеличения объема буфера. На следующей картинке можно наглядно увидеть распределение памяти под кольцевой буфер в микроконтроллерах PIC:
Ниже представлен код программы с реализацией кольцевого буфера для варианта, где требуется равномерная передача данных от источника к приемнику:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; flag equ 78h ;регистр флагов FSR_prer equ 7Bh ;регистр хранения значения FSR для обработчика прерываний FSR_temp equ 7Dh ;регистр хранения значения FSR для основной программы W_TEMP equ 7Eh ;регистр хранения значения аккумулятора W STATUS_TEMP equ 7Fh ;регистр хранения значения STATUS bnk1_start equ 20h ;начальный адрес регистра 1-й половины буфера ОЗУ bnk1_end equ 47h ;конечный адрес регистра 1-й половины буфера ОЗУ bnk2_start equ A0h ;начальный адрес регистра 2-й половины буфера ОЗУ bnk2_end equ С7h ;конечный адрес регистра 2-й половины буфера ОЗУ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 0000h ; Начать выполнение программы с адреса 0 PC. goto Start ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Подпрограмма обработки прерываний org 0004h ;начать выполнение подпрограммы с адреса 0004h movwf W_TEMP ;сохранение значений ключевых регистров swapf STATUS,W ; clrf STATUS ; movwf STATUS_TEMP ; movf FSR,W ;сохранение значения регистра FSR, и восстановление movwf FSR_temp ;ранее сохраненного значения в предыдущем входе movf FSR_prer,W ;в обработчик прерываний movwf FSR ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; movf INDF,W ;копируем значение регистра ОЗУ в аккумулятор ; ;.................. ;Здесь выполняем какие-либо операции с полученным значением: ;.................. ;например вычисления, передача по UART, передача в ШИМ и т.д. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; btfsc flag,0 ;проверка флага занятости буферов ОЗУ goto bnk2 ;флаг=1: переход на метку bnk2 для проверки окончания ;2-й половины буфера ОЗУ ;флаг=0: проверка окончания 1-й половины буфера ОЗУ bnk1 movlw bnk1_end ;проверка не является ли текущий регистр последним в 1-й xorwf FSR,W ;половине буфера ОЗУ btfss STATUS,Z ; goto exxit ;текущий регистр не последний: переход на метку exxit ;текущий регистр последний в 1-й половине буфера ОЗУ ust_bnk2 movlw bnk2_start ;установка первого регистра 2-й половины буфера ОЗУ movwf FSR ;для косвенной адресации bsf flag,0 ;установка флага занятости: считывание из 2-й половины буфера goto exxit_1 ;переход на метку exxit_1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; bnk2 movlw bnk2_end ;проверка не является ли текущий регистр последним во 2-й xorwf FSR,W ;половине буфера ОЗУ btfss STATUS,Z ; goto exxit ;текущий регистр не последний: переход на метку exxit ;текущий регистр последний во 2-й половине буфера ОЗУ ust_bnk2 movlw bnk1_start ;установка первого регистра 1-й половины буфера ОЗУ movwf FSR ;для косвенной адресации bcf flag,0 ;сброс флага занятости: считывание из 1-й половины буфера goto exxit_1 ;переход на метку exxit_1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; exxit incf FSR,F ;инкремент регистра FSR: подготовка следующего ;регистра ОЗУ для чтения ; exxit_1 ;.................. ;Здесь выполняем настройку таймера, сброс флагов прерываний ;.................. ;запись нового значения в таймер, и его запуск ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; movf FSR,W ;сохранение значения регистра FSR, и восстановление movwf FSR_prer ;ранее сохраненного значения при входе в обработчик movf FSR_temp,W ;прерываний movwf FSR ; swapf STATUS_temp,W ;восстановление регистров STATUS и W movwf STATUS ; swapf W_temp,F ; swapf W_temp,W ; retfie ;выход из подпрограммы прерывания ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ;................... ;настройка регистров микроконтроллера ;................... ; ;................... ;настройка таймера для регулярных прерываний ;................... ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Первоначальная загрузка буферов ОЗУ us_1 movlw bnk1_start ;установка первого регистра 1-й половины буфера ОЗУ movwf FSR ;для косвенной адресации met_1 call priem_byte ;вызов подпрограммы приема байта данных movwf INDF ;запись принятого байта данных в регистр буфера ОЗУ movlw bnk1_end ;проверка не является ли текущий регистр последним в 1-й xorwf FSR,W ;половине буфера ОЗУ btfsc STATUS,Z ; goto ust_2 ;текущий регистр последний: переход на метку ust_2 incf FSR,F ;текущий регистр не последний: инкремент регистра FSR: ;подготовка следующего регистра ОЗУ для загрузки goto met_1 ;переход на метку met_1 ust_2 movlw bnk2_start ;установка первого регистра 2-й половины буфера ОЗУ movwf FSR ;для косвенной адресации met_2 call priem_byte ;вызов подпрограммы приема байта данных movwf INDF ;запись принятого байта данных в регистр буфера ОЗУ movlw bnk2_end ;проверка не является ли текущий регистр последним во 2-й xorwf FSR,W ;половине буфера ОЗУ btfsc STATUS,Z ; goto zapusk ;текущий регистр последний: переход на метку zapusk incf FSR,F ;текущий регистр не последний: инкремент регистра FSR: ;подготовка следующего регистра ОЗУ для загрузки goto met_2 ;переход на метку met_1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; zapusk movlw bnk1_start ;установка первого регистра 1-й половины буфера ОЗУ movwf FSR_prer ;для обработчика прерываний, выполняется однократно ;при запуске кругового цикла передачи данных bcf flag,0 ;сброс флага занятости 1-й половины буфера ОЗУ ;для обработчика прерывания, выполняется однократно ;при запуске кругового цикла передачи данных ;................... ;Здесь запускаем таймер для прерываний ;................... ;(Начало кругового цикла передачи данных) zagruz_1 ;.................... ;Здесь выполняем проверку кнопок, флагов и т.д. на факт появления ;.................... ;стоп сигнала, для того чтобы завершить круговой цикл передачи данных ;.................... ;и перейти к другим задачам. Если обнаружен стоп сигнал необходимо ;.................... ;остановить таймер, для прекращения вызова обработчика прерываний povt_1 btfss flag,0 ;опрос флага занятости банка goto povt_1 ;флаг=0, занята 1-я половины буфера ОЗУ: переход на метку povt_1 ;флаг=1, 1-я половина буфера ОЗУ освободилась movlw bnk1_start ;установка первого регистра 1-й половины буфера ОЗУ movwf FSR ;для загрузки данных zag_1 call priem_byte ;вызов подпрограммы приема байта данных movwf INDF ;запись принятого байта данных в регистр буфера ОЗУ movlw bnk1_end ;проверка не является ли текущий регистр последним в 1-й xorwf FSR,W ;половине буфера ОЗУ btfsc STATUS,Z ; goto zagruz_2 ;текущий регистр последний: переход на метку zagruz_2 incf FSR,F ;текущий регистр не последний: инкремент регистра FSR: ;подготовка следующего регистра ОЗУ для загрузки goto zag_1 ;переход на метку zag_1 zagruz_2 btfsc flag,0 ;опрос флага занятости банка goto zagruz_2 ;флаг=1, занята 2-я половины буфера ОЗУ: переход на метку zagruz_2 ;флаг=0, 2-я половина буфера ОЗУ освободилась movlw bnk2_start ;установка первого регистра 2-й половины буфера ОЗУ movwf FSR ;для загрузки данных zag_2 call priem_byte ;вызов подпрограммы приема байта данных movwf INDF ;запись принятого байта данных в регистр буфера ОЗУ movlw bnk2_end ;проверка не является ли текущий регистр последним во 2-й xorwf FSR,W ;половине буфера ОЗУ btfsc STATUS,Z ; goto zagruz_1 ;текущий регистр последний: переход на метку zagruz_1 incf FSR,F ;текущий регистр не последний: инкремент регистра FSR: ;подготовка следующего регистра ОЗУ для загрузки goto zag_2 ;переход на метку zag_2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; priem_byte ;................... ;Подпрограмма приема байта данных от внешних устройств ;................... ;(например из EEPROM микросхемы, карты памяти и т.д.) ;................... ; ; movf byte,W ;запись принятого байта в аккумулятор W return ;выход из подпрограммы end ;конец всей программы |
Обращение к регистрам ОЗУ выполняется с помощью косвенной адресации, в начале программы задаются начальный (bnk_start) и конечный (bnk_end) адреса для обеих частей буфера, 1-я часть буфера расположена в 0-м банке ОЗУ, 2-я часть в 1-ом банке. Напомню что если буфер затрагивает регистры 2-го и 3-го банка, то в программе потребуется дополнительная манипуляция с битом IRP регистра STATUS, для правильного выбора банков памяти. Кроме этого в начале программы перечисляются вспомогательные регистры для работы с обработчиком прерываний. В подпрограмме обработки прерываний осуществляется извлечение данных из буфера для дальнейшего процесса передачи или обработки. Для того чтобы процесс был равномерным, прерывание генерируется по переполнению таймера. В основной программе происходит загрузка буфера данными, полученными от какого-либо источника.
В начале основной программы настраиваются внутренние регистры, а также таймер для обработчика прерываний. Сначала обе части буфера заполняются данными, полученными от источника. Далее выполняется переход на метку zapusk, где в регистр FSR_prer загружается адрес 1-го регистра 1-й половины буфера, после чего сбрасывается флаг занятости буферов flag,0. Эти действия выполняются однократно перед запуском таймера. После запуска таймера начинается круговой цикл передачи данных. Далее необходимо дополнительно проверить кнопки, флаги и т.д., сигнализирующие о необходимости остановки кругового цикла, иначе без этого программа уйдет в бесконечный цикл.
При отсутствии стоп сигналов, переходим на опрос флага занятости буферов. Если флаг равен нулю, 1-я часть буфера занята обработчиком прерываний, продолжаем ожидать освобождения. Тем временем происходит регулярный переход в обработчик прерываний, где флаг занятости указывает из какой части буфера необходимо извлекать данные. Если флаг равен 0, операции выполняются с 1-й частью буфера, в противном случае с 2-й частью буфера. По мере считывания данных из 1-й части буфера выполняется проверка на факт окончания буфера, если это произошло, в регистр FSR записывается начальный адрес регистра 2-й части буфера, флаг занятости устанавливается в 1. Тем самым обработчик прерывания переключиться на 2-ю часть буфера.
В этот момент в основной программе начнется загрузка 1-й части буфера данными, получаемыми от источника с помощью подпрограммы priem_byte. Как и в случае считывания, здесь выполняется проверка на факт окончания буфера. После окончания загрузки, снова опрашивается флаг занятости буферов, если флаг равен 1, продолжаем ожидать освобождения 2-й части буфера. Как только обработчик прерываний переключится на 1-ю часть, основная программа начнет заполнять 2-ю часть буфера. Далее цикл повторится, как видно организация кольцевого буфера не так уж сложна.
Данный алгоритм реализован в музыкальном звонке, где требуется равномерная загрузка модуля ШИМ данными, считанными с карты памяти, для получения неискаженного звука.