Вступление
В предыдущей статье я рассказал о своем первом знакомстве с DMA. В ней мы делали связку DMA + SysTick. Статья получилась очень специфичной и сложной, ввиду неопытного кривого подхода. Набравшись опыта, в данной статье я расскажу о куда более простом и понятном способе работы с DMA.Основные аспекты.
Из предыдущей статьи мы уяснили, что для того, чтобы запустить DMA, нужно:- Подать сигнал тактирвания на модуль DMA.
- Заполнить управляющую структуру работы DMA.
- Произвести настройку DMA.
- Произвести настройку канала.
- Включить передачу.
- По окончании передачи «восстановить» (перезаписать) структуру.
Настройка структуры DMA, самого DMA и канала-связки с таймером.
Как мы помним, у каждого канала есть своя управляющая структура (в нашем случае, таковых целых две). Настроим их так, как было описано в предыдущей статье. Полученные настройки сохраним в две переменные. Из этих переменных мы потом будем восстанавливать наши структуры.Настройка структур.
Как вы могли заметить — я выделил память под 32 первичные и 32 альтернативные структуры, что в сумме будет давать килобайт памяти в ОЗУ. Я пошел на данный шаг, чтобы не мучиться со смещениями. В будущем легко будет сделать смещение и оставить 2 структуры. Далее нужно заполнить эти структуры.
//-------------------------------------------------
//Настройка структур.
//-------------------------------------------------
#define dst_src (3<<30) //Источник - 16 бит (полуслово).
#define src_inc (1<<26) //Источник смещается на 16 бит после каждой передачи.
#define src_size (1<<24) //Отправляем по 16 бит.
#define dst_size (1<<28) //Принимаем по 16 бит. Приемник и передатчик должны иметь одинаковые размерности.
#define n_minus_1 (49<<4) //50 передач (-1) DMA.
#define cycle_ctrl (3<<0) //Пинг-понг.
struct DAC_ST
{
uint32_t Destination_end_pointer; //Указатель конца данных приемника.
uint32_t Source_end_pointer; //Указатель конца данных источника
uint32_t channel_cfg; //Конфигурация канала.
uint32_t NULL; //Пустая ячейка.
}
__align(1024) DAC_ST; //Выравниваем массив структур по 1024 байта.
struct DAC_ST DAC_ST_ADC[32+32]; //Создаем массив структур для всех каналов.
//Источник/приемник = 16 бит, отправляем/принимаем = 16 бит, защиты нет, 50 передач, пинг-понг.
uint32_t DMA_DAC_InitST_PR = dst_src|src_inc|src_size|dst_size|n_minus_1|cycle_ctrl;
uint32_t DMA_DAC_InitST_ALT = dst_src|src_inc|src_size|dst_size|n_minus_1|cycle_ctrl;
Заполнение структур
Как вы могли заметить, разница у этих двух структур лишь в конечных адресах источника данных. В первичной мы указываем центр массива (передача будет осуществляться с начала до середины), а во вторичной конец (передача с середины до конца).
//Настраиваем первичную структуру.
DAC_ST_ADC[10-1].Destination_end_pointer = (uint32_t)C_4 + (sizeof(C_4))/2 - 1; //Указатель на последний элемент середины массива (C_4 - массив значений синусоидального сигнала в 100 значений).
DAC_ST_ADC[10-1].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //Указатель на последний (не меняется) адрес приемника (регистр данных DAC).
DAC_ST_ADC[10-1].channel_cfg = (uint32_t)(DMA_DAC_InitST_PR); //Структура настройки первичной структуры.
DAC_ST_ADC[10-1].NULL = (uint32_t)0; //Пустая ячейка.
//Настройка альтернативной структуры.
DAC_ST_ADC[10-1+32].Destination_end_pointer = (uint32_t)C_4 + sizeof(C_4) - 1; //Указатель на последний элемент массива (C_4 - массив значений синусоидального сигнала в 100 значений).
DAC_ST_ADC[10-1+32].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //Указатель на последний (не меняется) адрес приемника (регистр данных DAC).
DAC_ST_ADC[10-1+32].channel_cfg = (uint32_t)(DMA_DAC_InitST_ALT); //Структура настройки альтернативной структуры.
DAC_ST_ADC[10-1+32].NULL = (uint32_t)0; //Пустая ячейка.
Теперь настроим DMA.
Ну и осталось лишь настроить канал. Но какой? Мы можем обратиться к таблице, приведенной в предыдущей статье. Но. После написания предыдущей статьи мне сообщили о моей ошибке. Дело в том, что та таблица не совсем верна.
#define CFG_master_enable (1<<0)//Маска разрешает работу контроллера.
#define PCLK_EN_DMA (1<<5)//Маска включает тактирование DMA.
//Настраиваем контроллер DMA.
RST_CLK->PER_CLOCK|=PCLK_EN_DMA; //Включаем тактирование DMA.
DMA->CTRL_BASE_PTR = (uint32_t)&DAC_ST_ADC; //Указываем адрес массива структур.
DMA->CFG = CFG_master_enable; //Разрешаем работу DMA.
Более точная таблица выглядит так.
Таблицу мне дали на официальном форуме, но она есть и в официальной библиотеке. Мы будем использовать таймер 1. => наш канал — десятый.
/** @defgroup DMA_valid_channels DMA valid channels
* @{
*/
#define DMA_Channel_UART1_TX ((uint8_t)(0))
#define DMA_Channel_UART1_RX ((uint8_t)(1))
#define DMA_Channel_UART2_TX ((uint8_t)(2))
#define DMA_Channel_UART2_RX ((uint8_t)(3))
#define DMA_Channel_SSP1_TX ((uint8_t)(4))
#define DMA_Channel_SSP1_RX ((uint8_t)(5))
#define DMA_Channel_SSP2_TX ((uint8_t)(6))
#define DMA_Channel_SSP2_RX ((uint8_t)(7))
#define DMA_Channel_ADC1 ((uint8_t)(8))
#define DMA_Channel_ADC2 ((uint8_t)(9))
#define DMA_Channel_TIM1 ((uint8_t)(10))
#define DMA_Channel_TIM2 ((uint8_t)(11))
#define DMA_Channel_TIM3 ((uint8_t)(12))
#define DMA_Channel_SW1 ((uint8_t)(13))
#define DMA_Channel_SW2 ((uint8_t)(14))
#define DMA_Channel_SW3 ((uint8_t)(15))
#define DMA_Channel_SW4 ((uint8_t)(16))
#define DMA_Channel_SW5 ((uint8_t)(17))
#define DMA_Channel_SW6 ((uint8_t)(18))
#define DMA_Channel_SW7 ((uint8_t)(19))
#define DMA_Channel_SW8 ((uint8_t)(20))
#define DMA_Channel_SW9 ((uint8_t)(21))
#define DMA_Channel_SW10 ((uint8_t)(22))
#define DMA_Channel_SW11 ((uint8_t)(23))
#define DMA_Channel_SW12 ((uint8_t)(24))
#define DMA_Channel_SW13 ((uint8_t)(25))
#define DMA_Channel_SW14 ((uint8_t)(26))
#define DMA_Channel_SW15 ((uint8_t)(27))
#define DMA_Channel_SW16 ((uint8_t)(28))
#define DMA_Channel_SW17 ((uint8_t)(29))
#define DMA_Channel_SW18 ((uint8_t)(30))
#define DMA_Channel_SW19 ((uint8_t)(31))
#define IS_DMA_CHANNEL(CHANNEL) (CHANNEL <= (DMA_Channels_Number - 1))
/** @} */ /* End of group DMA_valid_channels */
//Настраиваем канал.
DMA->CHNL_ENABLE_SET = 1<<10; //Разрешаем работу 10 канала.
Мы получили следующую функцию.
//-------------------------------------------------
//Настраиваем DMA для связки с DAC.
//-------------------------------------------------
void DMA_to_DAC_and_TIM1 (void)
{
//Настраиваем первичную структуру.
DAC_ST_ADC[10-1].Destination_end_pointer = (uint32_t)C_4 + (sizeof(C_4))/2 - 1; //Указатель на последний элемент середины массива (C_4 - массив значений синусоидального сигнала в 100 значений).
DAC_ST_ADC[10-1].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //Указатель на последний (не меняется) адрес приемника (регистр данных DAC).
DAC_ST_ADC[10-1].channel_cfg = (uint32_t)(DMA_DAC_InitST_PR); //Структура настройки первичной структуры.
DAC_ST_ADC[10-1].NULL = (uint32_t)0; //Пустая ячейка.
//Настройка альтернативной структуры.
DAC_ST_ADC[10-1+32].Destination_end_pointer = (uint32_t)C_4 + sizeof(C_4) - 1; //Указатель на последний элемент массива (C_4 - массив значений синусоидального сигнала в 100 значений).
DAC_ST_ADC[10-1+32].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //Указатель на последний (не меняется) адрес приемника (регистр данных DAC).
DAC_ST_ADC[10-1+32].channel_cfg = (uint32_t)(DMA_DAC_InitST_ALT); //Структура настройки альтернативной структуры.
DAC_ST_ADC[10-1+32].NULL = (uint32_t)0; //Пустая ячейка.
//Настраиваем контроллер DMA.
RST_CLK->PER_CLOCK|=PCLK_EN_DMA; //Включаем тактирование DMA.
DMA->CTRL_BASE_PTR = (uint32_t)&DAC_ST_ADC; //Указываем адрес массива структур.
DMA->CFG = CFG_master_enable; //Разрешаем работу DMA.
//Настраиваем канал.
DMA->CHNL_ENABLE_SET = 1<<10; //Разрешаем работу 10 канала.
}
Знакомство с таймером.
Прежде всего таймер нужно включить (затактировать).
А теперь собственно, стоит начать разбираться. Все три таймера имеют одинаковые возможности. По крайней мере на первый взгляд. Каждый таймер имеет очень богатый функционал => множество регистров. Но в них очень легко разобраться. Никаких граблей мне на пути изучения работы таймеров не встретилось. Мы помним, что мы настроили DMA так, чтобы таймер мог управлять им. Для этой цели нам достаточно будет лишь подождать определенное количество времени и передать следующую порцию данных. Таймер имеет 4 канала «сравнения» и основной счетчик. Нам будет достаточно использовать основной счетчик.
#define PER_CLOCK_TIMER1_ONCLK (1<<14) //Маска разрешения тактирования таймера 1.
RST_CLK->PER_CLOCK |= PER_CLOCK_TIMER1_ONCLK; //Включаем тактирование.
Взглянем на основной регистр таймера.
Здесь нам следует разрешить работу таймера.
В качестве теста — будем считать до 0xFFFF (в последствии разберемся с временными интервалами). По умолчанию счет идет от 0.
#define CNTRL_CNT_EN (1<<0) //Маска включения таймера.
TIMER1->CNTRL |= CNTRL_CNT_EN; //Разрешаем работу таймера, событие = равенство значению.
TIMER1->ARR = 0xFFFF; //Считаем до...
Далее нужно связать наш таймер с DMA. В качества события, из-за которого будет происходить передача — выберем CNT == ARR в таймере 1. Это простое сравнение текущего значения таймера CNT с числом в регистре ARR.
Для этого есть регистр DMA_RE
Можно было бы сказать, что настройка завершена, НО. Сейчас наш таймер тактируется от частоты HCLK, которая без пред делителя = 8МГц. Наше значение 0xFFFF будет достигнуто мгновенно. И мы не сможем проследить нашу передачу. Для решения этой проблемы мы должны включить пред делитель.
Здесь нам нужно выбрать связь по достижению счетчиком нужного значения.
Для этих целей служит регистр RST_CLK->TIM_CLOCK в блоке тактирования.
Для пробы я включил самый большой делитель. Так же здесь следует и подать сигнал тактирования на таймер. Чтобы таймер начал считать от HCLK через пред делитель.
В результате мы получаем такую функцию.
Теперь, если мы включим наш пример, то мы будем наблюдать, как происходит смена значений в регистре значения напряжения в DAC2 с интервалом примерно секунда. Чтож, первый этап пройден. Правда вот процесс прервется на середине нашего массива. Дело в том, что таймер может лишь повторно «включать» передачу. Но как только мы передали половину массива — мы «израсходовали» первую структуру. Сейчас необходимо ее восстановить. Для этого мы будем использовать второй таймер. Многие спросят «Почему не прерывание по передаче от DMA или по передаче половины массива?». Дело в том, что в наш DMA может генерировать лишь прерывание по окончании передачи. Возможности вызывать прерывание по прохождении середины нет. Но и тут не все так просто. Вспомним, что мы передаем не все подряд, а по частям. Наш DMA этого не понимает. Прерывание генерируется во время простоя DMA. Иначе говоря, DMA не понимает, что передан не весь пакет данных, а лишь один элемент массива. Этот факт делает использование прерывания от DMA для нашей задачи непригодным.
#define PER_CLOCK_TIMER1_ONCLK (1<<14) //Маска разрешения тактирования таймера 1.
#define TIM_CLOCK_TIM1_CLK_EN (1<<24) //Маска включения частоты на таймере 1.
#define SHARE_HCLK_TIMER 7 //Делим тактовую частоту HCLK на... (0 = не делим, 1 = /2, 2 = /4).
#define TIM_CLOCK_TIM1_BRG (SHARE_HCLK_TIMER<<0) //Делим частоту на таймере 1 на SHARE_HCLK_TIMER.
#define CNTRL_CNT_EN (1<<0) //Маска включения таймера.
#define DMA_RE_CNT_ARR_EVENT_RE (1<<1) //Маска разрешения запроса DMA при событии CNT == ARR;
void Init_TIMER1_to_DMA_and_DAC2 (void)
{
RST_CLK->PER_CLOCK |= PER_CLOCK_TIMER1_ONCLK; //Включаем тактирование.
TIMER1->CNTRL |= CNTRL_CNT_EN; //Разрешаем работу таймера, событие = равенство значению.
TIMER1->ARR = 0xFFFF; //Считаем до...
TIMER1->DMA_RE |= DMA_RE_CNT_ARR_EVENT_RE; //Разрешаем "пинать" DMA.
RST_CLK->TIM_CLOCK |= TIM_CLOCK_TIM1_CLK_EN|TIM_CLOCK_TIM1_BRG; //Подаем тактовый сигнал на таймер.
}
Настраиваем таймеры для генерации синусоиды.
Как я уже сказал, для «восстановления» структуры мы должны настроить в следующем (втором) таймере прерывание по прохождении половины половины массива (1/4 всего массива). Почему именно одной четвертой — объясню далее. Но перед этим перенастроим первый таймер. Нам нужно определиться со скоростью «пинков» DMA. Смотрим. Контроллер тактируется с частотой 8МГц. Наш массив содержит 100 элементов. Частота ноты до первой октавы, как мы помним, 261.63 Гц. Вместимость регистра счета 0xFFFF (до этого значения может считать счетчик). Делим 8000000 Гц/261.63Гц/100 нот = раз в 305 тактов «пинать» DMA. Это много меньше максимального значения регистра сравнения таймера. Таким образом, нам не придется использовать пред делитель.Подаем тактирование на оба таймера.
После включения тактирования таймера (всего блока) иногда случается, что таймер начинают свою работу по ранее настроенным параметрам. Из-за этого, еще до их настройки, бывают различные глюки. Чтобы это предотвратить нужно отключить сигнал тактирования счетного блока (чтобы таймер по старым параметрам не успел досчитать до какого-нибудь значения, от которого вызовет, к примеру прерывание).
RST_CLK->PER_CLOCK |= PER_CLOCK_TIMER1_ONCLK|PER_CLOCK_TIMER2_ONCLK; //Включаем тактирование таймера 1 и 2.
Отключаем тактирование счетного блока.
RST_CLK->TIM_CLOCK = 0; //Отключаем тактирование таймеров.
Далее указываем период обращения к DMA и разрешаем его.
Теперь настраиваем таймер 2. Для простоты, тактировать его будем так же без пред делителя. Так же решаем, как часто вызывать прерывание. В следствие того, что мы тактируем оба таймера от одного источника, мы не можем обновлять значение структуры в тот самый момент, когда был передан последний элемент первой структуры. Потому что когда DMA сделает попытку передать элемент, следующий за последним из первой структуры — он наткнется на ее конец и автоматически перейдет к альтернативной. Таким образом, нужно дождаться, когда DMA решит, что первичная структура «исчерпана» и начнет передавать из альтернативной. Для этого достаточно дождаться передачи хотя бы одного пакета из альтернативной структуры. После чего можно будет заполнить первичную заново. Тоже самое нужно будет делать и с альтернативной. Когда в ней закончатся передачи, нужно дождаться, пока DMA передаст хоть 1 пакет из первичной, после чего можно перезаписать альтернативную. Мы передаем 100 элементов. По 50 в каждой структуре. Мы могли бы вызывать прерывание вместе с пятидесятой передачей, но по описанным выше причинам, нам нужно дождаться хотя бы 51-й передачи. Для того, чтобы не мучиться со смещениями, сделаем проверку обеих структура раз в 25 передач.
//У нас 8000000 Гц/261.63/100 = 305.
TIMER1->ARR = 305; //Считаем до...
TIMER1->DMA_RE |= DMA_RE_CNT_ARR_EVENT_RE; //Разрешаем пинать DMA.
Таким образом, период нашего таймер будет 305*25.
TIMER2->ARR = 305*25; //После передачи половины массива.
Немного о прерываниях.
Для того, чтобы случилось прерывание в момент достижения таймером нужного значения, нужно.-
Разрешить прерывание по событию: достижение счетчиком нужного значения, в регистре TIMERx->IE
#define TIMERx_IE_CNT_ARR_EVENT_IE (1<<1) //Маска: Флага разрешения прерывания по событию совпадения CNT и ARR. TIMER2->IE = TIMERx_IE_CNT_ARR_EVENT_IE; //Прерывание по достижении границы.
-
Сбросить флаг наличия каких-либо прерываний TIMERy->STATUSДело в том, что после разрешения прерывания — автоматически устанавливается его флаг. Чтобы сразу же после настройки не уйти в прерывание — нужно его сбросить.
TIMER2->STATUS=0; //Сбрасываем флаги прерываний.
-
Разрешить прерывание от таймера в контроллере прерываний NVIC.На тему этого контроллера стоит выделить отдельную статью, но в рамках данной статьи ограничимся следующей информационной базой: для того, чтобы контроллер «заметил», что таймер требует прерывания, нужно разрешить это самое прерывание. Делается это в регистре NVIC->ISER[0]. Нам нужно узнать, какой номер имеет прерывание от таймера. Для этого заходим в стартап файл и ищем вектор с нужным именем.
DCD Timer2_IRQHandler ; IRQ15
NVIC->ISER[0] = 1<<15; //Разрешили прерывание от таймера 2.
-
Нужно создать функцию-обработчик прерывания и сбросить в ней флаг прерывания (это делается вручную).
void Timer2_IRQHandler (void) //Меняем структуры. TIMER2->STATUS=0; }
Заканчиваем настройку таймеров.
После того, как мы указали параметры таймеров, нужно разрешить их работу и подать тактовый сигнал на счетчик.
#define CNTRL_CNT_EN (1<<0) //Маска включения таймера.
#define TIM_CLOCK_TIM1_CLK_EN (1<<24) //Маска включения частоты на таймере 1.
#define TIM_CLOCK_TIM2_CLK_EN (1<<25) //Маска включения частоты на таймере 2.
TIMER1->CNTRL = CNTRL_CNT_EN; //Разрешаем работу таймеров.
TIMER2->CNTRL = CNTRL_CNT_EN;
RST_CLK->TIM_CLOCK = TIM_CLOCK_TIM1_CLK_EN|TIM_CLOCK_TIM2_CLK_EN; //Подаем тактовый сигнал на таймеры.
Итогом настройки стала функция.
#define PER_CLOCK_TIMER1_ONCLK (1<<14) //Маска разрешения тактирования таймера 1.
#define PER_CLOCK_TIMER2_ONCLK (1<<15) //Маска разрешения тактирования таймера 1.
#define TIM_CLOCK_TIM1_CLK_EN (1<<24) //Маска включения частоты на таймере 1.
#define SHARE_HCLK_TIMER1 7 //Делим тактовую частоту HCLK на... (0 = не делим, 1 = /2, 2 = /4).
#define TIM_CLOCK_TIM1_BRG (SHARE_HCLK_TIMER1<<0) //Делим частоту на таймере 1 на SHARE_HCLK_TIMER.
#define CNTRL_CNT_EN (1<<0) //Маска включения таймера.
#define CNTRL_EVENT_SEL (1<<8) //Маска выбора сточник события: CNT == ARR;
#define DMA_RE_CNT_ARR_EVENT_RE (1<<1) //Маска разрешения запроса DMA при событии CNT == ARR;
#define IE_CNT_ARR_EVENT_IE (1<<1) //Маска разрешения прерывания при событии CNT == ARR;
#define TIM_CLOCK_TIM2_CLK_EN (1<<25) //Маска включения частоты на таймере 2.
#define SHARE_HCLK_TIMER2 7 //Делим тактовую частоту HCLK на... (0 = не делим, 1 = /2, 2 = /4).
#define TIM_CLOCK_TIM2_BRG (SHARE_HCLK_TIMER2<<8)//Делим частоту на таймере 1 на SHARE_HCLK_TIMER.
#define CH1_CNTRL_CAP_nPWM_Z (1<<15) //Маска: какнал в режиме "захват".
#define CH1_CNTRL_CHPSC_8 (3<<6) //Маска: предворительный делитель для канала на 8.
#define CHy_CNTRL2_CCR1_EN (1<<2) //Маска: включить канал 1.
#define TIMERx_IE_CNT_ARR_EVENT_IE (1<<1) //Маска: Флага разрешения прерывания по событию совпадения CNT и ARR.
void Init_TIMER1_to_DMA_and_DAC2 (void)
{
RST_CLK->PER_CLOCK |= PER_CLOCK_TIMER1_ONCLK|PER_CLOCK_TIMER2_ONCLK; //Включаем тактирование таймера 1 и 2.
RST_CLK->TIM_CLOCK = 0; //Отключаем тактирование таймеров.
//У нас 8000000 Гц/261.63/100 = 305.
TIMER1->ARR = 305; //Считаем до...
TIMER1->DMA_RE |= DMA_RE_CNT_ARR_EVENT_RE; //Разрешаем пинать DMA.
TIMER2->ARR = 305*25; //После передачи половины массива.
TIMER2->IE = TIMERx_IE_CNT_ARR_EVENT_IE; //Прерывание по достижении границы.
TIMER2->STATUS=0; //Сбрасываем флаги прерываний.
NVIC->ISER[0] = 1<<15; //Разрешили прерывание от таймера 2.
TIMER1->CNTRL = CNTRL_CNT_EN; //Разрешаем работу таймеров.
TIMER2->CNTRL = CNTRL_CNT_EN;
RST_CLK->TIM_CLOCK = TIM_CLOCK_TIM1_CLK_EN|TIM_CLOCK_TIM2_CLK_EN; //Подаем тактовый сигнал на таймеры.
}
Пишем функцию смены структур.
Мы описали прерывание. Осталось лишь проверить в нем: если закончилась первичная структура и был передан хотя бы 1 блок из альтернативной структуры — перезаписать первичную. Тоже самое со вторичной. Не забудем так же, что после передачи первой структуры — DMA блокирует канал. Нужно снова разрешить его работу, чтобы по «пинкам» от таймера 1 продолжала идти передача.Получившееся прерывание
Код основной функции с последнего примера изменяется лишь включением новой функции и выключения SysTick.
#define ST_Play_P (DAC_ST_ADC[10-1].channel_cfg & (1023<<4)) //Для проверки колличества оставшихся передачь в первичной сруктуре.
#define ST_Play_ALT (DAC_ST_ADC[10-1+32].channel_cfg & (1023<<4)) //Для проверки колличества оставшихся передачь в альтернативной сруктуре.
void Timer2_IRQHandler (void) //Меняем структуры.
{
if ((ST_Play_P == 0) && (ST_Play_ALT <= (48<<4))) //Если прошли первую половину и уже начата передача второй - переключиться на 2-ю.
DAC_ST_ADC[10-1].channel_cfg = (uint32_t)(DMA_DAC_InitST_PR);
if ((ST_Play_ALT == 0) && (ST_Play_P <= (48<<4)))
DAC_ST_ADC[10-1+32].channel_cfg = (uint32_t)(DMA_DAC_InitST_ALT);
DMA->CHNL_ENABLE_SET = 1<<10;
TIMER2->STATUS=0;
}
Вот он.
Запустив программу мы получим на выходе следующую картину.Скачать звук можно здесь.
int main (void)
{
HSE_Clock_ON(); //Разрешаем использование HSE генератора.
HSE_Clock_OffPLL(); //Настраиваем "путь" сигнала и включаем тактирование от HSE генератора.
Buzzer_out_DAC_init(); //Настраиваем порт для ЦАП.
DAC_Init(); //Настраиваем ЦАП.
DMA_to_DAC_and_TIM1(); //Настраиваем DMA для работы с DAC2 через таймер 1.
Init_TIMER1_to_DMA_and_DAC2(); //Настраиваем таймер 1.
while (1)
{
}
}
Об ошибках.
Долго мучился с подбором момента для смены структур. Все время выходило что-то типа этого.
Подведем итог
Нам удалось иначе взглянуть на DMA, начать пользоваться его преимуществами. В следующей статье мы закрепим наши умения и создадим подобие музыкальной шкатулки, а после — плеера.Список предыдущих статей.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий