Изюминкой STM32, ИМХО, является возможность связывать таймера между собой и широкие возможности DMA. Здесь возможности практически безграничны, что позволяет делать многие вещи по сути хардварными.
Процесс этот, порой, мозговыносящий и без хороших знаний работы модулей МК, логического анализатора и других средств отладки не сулит ничего хорошего. Однако результаты того стоят.
Самый простой пример - подключение 4-х разрядного LED-семисегментника с общим анодом (максимум до 8 разрядов). Сегменты подключены к PA0-PA7 через резисторы 200 Ом, аноды - к PA8-PA11 непосредственно, без ключей.
Всё это подключалось к STM32F0Discovery, МК STM32F051. Особенностью такого подключения является разнояркость разрядов, для компенсации которой был тоже задействован DMA. Итого задействовано - 1 таймер и 2 канала DMA (при подключении ключей в цепях анодов достаточно 1 таймера и 1 канала DMA). Результат - нет прерываний, обновление информации и коррекция разнояркости по сути хардварное.
Как это работает:
В общем случае - таймер периодически пинает DMA, DMA загружает данные из буфера в порт, в данном случае это GPIOA. Т.к. сегменты и разряды подключены к одному порту и их коммутация происходит одновременно, то никакого гашения/включения разрядов на время смены информации на сегментах не требуется, подсветка нерабочих сегментов отсутствует.
В данном случае, с коррекцией разнояркости - сначала таймер пинает один канал DMA для загрузки TIM_ARR, чем и определяется яркость разряда, потом пинает другой канал DMA, который и загружает порт данными о текущем разряде и состоянии сегментов.
Примерный код:
Здесь всё стандартно и ничего интересного.
Код:
|
#define LOW_BYTE(var) (*(__IO uint8_t *)((uint32_t)&(var)))
#define DIGITS 0x04 // количество разрядов LED
#define LED_PORT GPIOA
#define DIGIT_1 GPIO_ODR_11
#define DIGIT_2 GPIO_ODR_8
#define DIGIT_3 GPIO_ODR_9
#define DIGIT_4 GPIO_ODR_10
#define DIGIT_5 GPIO_ODR_12
#define DIGIT_6 GPIO_ODR_13
#define DIGIT_7 GPIO_ODR_14
#define DIGIT_8 GPIO_ODR_15
#define SEG_A GPIO_ODR_0
#define SEG_B GPIO_ODR_2
#define SEG_C GPIO_ODR_6
#define SEG_D GPIO_ODR_4
#define SEG_E GPIO_ODR_3
#define SEG_F GPIO_ODR_1
#define SEG_G GPIO_ODR_7
#define SEG_H GPIO_ODR_5
const uint8_t ten2led[] =
{
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // "0"
SEG_B | SEG_C, // "1"
SEG_A | SEG_B | SEG_G | SEG_E | SEG_D, // "2"
SEG_A | SEG_B | SEG_G | SEG_C | SEG_D, // "3"
SEG_F | SEG_G | SEG_B | SEG_C, // "4"
SEG_A | SEG_F | SEG_G | SEG_D | SEG_C, // "5"
SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, // "6"
SEG_A | SEG_B | SEG_C, // "7"
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, // "8"
SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, // "9"
SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G, // "A"
SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, // "b"
SEG_A | SEG_D | SEG_E | SEG_F, // "C"
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // "d"
SEG_A | SEG_D | SEG_E | SEG_F | SEG_G, // "E"
SEG_A | SEG_E | SEG_F | SEG_G, // "F"
0x00 // " "
};
uint32_t tmp = 0;
uint32_t ttmp = 0;
uint8_t Ress[11]; |
Здесь содержится информация о разрядах и сегментах, которые DMA загружает в порт. Старшие байты DIGIT_Х содержат информацию о включённом разряде (в данном случае "1" соответствует включённому разряду) и
при выполнении программы меняться не должны!
Младшие байты содержат информацию о включённых сегментах. Изначально содержат 0xFF, т.е. все сегменты выключены.
Код:
|
uint16_t Data_Buffer[DIGITS]=
{
DIGIT_1 | 0xFF, DIGIT_2 | 0xFF, DIGIT_3 | 0xFF, DIGIT_4 | 0xFF
}; |
Здесь содержатся значения яркости для каждого разряда которые DMA заносит в таймер. Изначально содержатся базовые значения яркости.
Код:
|
uint16_t TIM_Buffer[DIGITS]=
{
1000, 1000, 1000, 1000
}; |
Здесь содержатся значения яркости для различных высвечиваемых символов. 1000 - наибольшая яркость, 770 - наименьшая.
В данном случае значений всего три, что вполне достаточно. В других случаях можно задать для каждого символа свои значения.
Код:
|
const uint16_t TIM_ARR_val[] =
{
1000, // "0"
770, // "1"
920, // "2"
920, // "3"
920, // "4"
920, // "5"
1000, // "6"
770, // "7"
1000, // "8"
1000, // "9"
1000, // "A"
920, // "b"
920, // "C"
920, // "d"
920, // "E"
920, // "F"
770, // " "
}; |
Настройки DMA, как по учебнику, ничего особенного.
Код:
|
void Init_DMA(void)
{
RCC-›AHBENR |= RCC_AHBENR_DMA1EN;
DMA1_Channel3-›CPAR = (uint32_t)&GPIOA-›ODR; // DMA channel x peripheral address register
DMA1_Channel3-›CMAR = (uint32_t)Data_Buffer; // DMA channel x memory address register
DMA1_Channel3-›CNDTR = DIGITS; // DMA channel x number of data register
DMA1_Channel3-›CCR |= DMA_CCR_MSIZE_0; // Memory size 16 bit
DMA1_Channel3-›CCR |= DMA_CCR_PSIZE_0; // Peripheral size 16 bit
DMA1_Channel3-›CCR |= DMA_CCR_PL_1; // Channel Priority level High
DMA1_Channel3-›CCR |= DMA_CCR_MINC; // Memory increment mode
DMA1_Channel3-›CCR |= DMA_CCR_CIRC; // Circular mode
DMA1_Channel3-›CCR |= DMA_CCR_DIR; // Data transfer direction Memory -› Peripheral
DMA1_Channel3-›CCR |= DMA_CCR_EN; // Channel enable
//---------------------------
DMA1_Channel4-›CPAR = (uint32_t)&TIM3-›ARR; // DMA channel x peripheral address register
DMA1_Channel4-›CMAR = (uint32_t)TIM_Buffer; // DMA channel x memory address register
DMA1_Channel4-›CNDTR = DIGITS; // DMA channel x number of data register
DMA1_Channel4-›CCR |= DMA_CCR_MSIZE_0; // Memory size 16 bit
DMA1_Channel4-›CCR |= DMA_CCR_PSIZE_0; // Peripheral size 16 bit
DMA1_Channel4-›CCR |= DMA_CCR_PL; // Channel Priority level Very High
DMA1_Channel4-›CCR |= DMA_CCR_MINC; // Memory increment mode
DMA1_Channel4-›CCR |= DMA_CCR_CIRC; // Circular mode
DMA1_Channel4-›CCR |= DMA_CCR_DIR; // Data transfer direction Memory -› Peripheral
DMA1_Channel4-›CCR |= DMA_CCR_EN; // Channel enable
} |
Настройка таймера.
Настройка предделителя: (SystemCoreClock/(DIGITS * 50 * 1000)) - 1, где 50 значение в герцах на один разряд, 1000 - базовая константа яркости.
TIM3-›ARR = 1000 - 1, где 1000 - базовая константа яркости на время настройки таймера. При работе изменяется через DMA.
TIM3-›CCR1 = 25, где 25 - абстрактная константа, определяет промежуток времени между загрузкой значения яркости для разряда и последующим обновлением GPIOA.
Код:
|
void Init_TIM3(void)
{
RCC-›APB1ENR |= RCC_APB1ENR_TIM3EN; // TIM3 clock enable
TIM3-›PSC = (SystemCoreClock/(DIGITS * 50 * 1000)) - 1; //240
TIM3-›ARR = 1000 - 1;
TIM3-›CCR1 = 25;
TIM3-›DIER |= TIM_DIER_UDE; // Upload DMA Enable
TIM3-›DIER |= TIM_DIER_CC1DE;
TIM3-›CR1 |= TIM_CR1_CEN | TIM_CR1_ARPE; // Counter Enable
} |
Bin2BCD, в коментах не нуждается.
Код:
|
uint32_t Bin2BCD(uint32_t value, uint8_t *Res)
{
uint8_t *n = Res;
while (value › 0)
{
*Res++ = value % 10;
value /= 10;
}
return (uint32_t)(Res - n);
} |
Примерный майн - инкрементальный счётчик с инкрементом значения через ~100 миллисекунд.
Код:
|
int main(void)
{
Init_GPIO();
Init_DMA();
Init_TIM3();
while(1)
{
Bin2BCD(tmp, Ress);
ttmp = DIGITS;
while(ttmp--)
{
TIM_Buffer[ttmp] = TIM_ARR_val[Ress[ttmp]]; // Загружается значение яркости в соответствии с высвечиваемым
// символом для соответствующего разряда
LOW_BYTE(Data_Buffer[ttmp]) = ~ten2led[Ress[ttmp]]; // Загружаются данные по сегментам для соответствующего разряда
}
TIM_Buffer[1] += 100; // Для децимальной точки увеличиваем яркость
LOW_BYTE(Data_Buffer[1]) -= SEG_H; // Засвечиваем точку в соответствующем разряде
tmp++;
Delay_mS(100); // delay 100ms
}
while(1);
} |