 |
#include <stm32f10x.h>
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; // 數碼管0~9段碼表 uint16_t segbuf[8][16]; // 共有8個數碼管, 點亮每個數碼管需要傳送16位數據
void delay(void) { uint32_t i; for (i = 0; i < 200000; i++); }
// 設置數碼管顯示的數字 // 向74HC595發送的數據: SerIn(seg8[n]); SerIn(1 << i); ParOut(); (先段選後位選) // 段選為低電平, 位選為高電平時點亮相應的筆畫(共陽) void seg_set(uint32_t num) { uint8_t i, j; uint16_t data, temp; for (i = 0; i < 8; i++) // 從低位掃描到高位 { data = (seg8[num % 10] << 8) | (1 << i); // 要向74HC595發送的數據 for (j = 0; j < 16; j++) // 每一份CCER寄存器的值決定一位數據 { temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E; if (j == 0) temp |= TIM_CCER_CC3P; // RCLK=1, 顯示上次(第i-1次或第7次)點亮的數碼管 if (data & 0x8000) temp |= TIM_CCER_CC2P; // DIO=1 segbuf[i][j] = temp; data <<= 1; } num /= 10; } }
// 數碼管熄滅 void seg_clr(void) { uint8_t i, j; uint16_t temp; for (i = 0; i < 8; i++) { for (j = 0; j < 16; j++) { temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E; if (j == 0) temp |= TIM_CCER_CC3P; if (j < 8) temp |= TIM_CCER_CC2P; // 相當於上面的data=0xff00 segbuf[i][j] = temp; } } }
// 數碼管動態掃描初始化 void seg_init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; TIM4->ARR = 624; // 定時時間: 625*0.25us=156.25us, 每個數碼管點亮的時間: 16*156.25us=2.5ms TIM4->PSC = 17; // 基準: 72MHz/18=4MHz->0.25us TIM4->CR1 = TIM_CR1_URS; // EGR_UG=1不置位SR_UIF TIM4->EGR = TIM_EGR_UG; // 保存上述設置 TIM4->CCMR1 = TIM_CCMR1_OC2M_2; // 通道2為強制輸出模式, 輸出的電平由CC2P決定 TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M; // 通道3也是強制輸出模式; 通道4為PWM模式2(用於產生SCLK), 先低電平後高電平 TIM4->CCR4 = 600; // SCLK上升沿的出現時間(相對於TIM4->ARR) TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; // 傳送的寄存器為CCER, 每次只傳送一個寄存器(DBL=0) TIM4->DIER = TIM_DIER_UDE; // 當定時器溢出時產生DMA請求, 對應的DMA通道是DMA1_Channel7 // 配置時DMA必須處於關閉狀態 RCC->AHBENR |= RCC_AHBENR_DMA1EN; DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR; DMA1_Channel7->CMAR = (uint32_t)segbuf; DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t); DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR; DMA1_Channel7->CCR |= DMA_CCR7_EN; // 開DMA TIM4->CR1 |= TIM_CR1_CEN; // 開始動態掃描 }
int main(void) { uint32_t num = 12345678; // 數碼管動態掃描管腳配置(復用推挽50MHz輸出) RCC->APB2ENR = RCC_APB2ENR_IOPBEN; GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2) GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4) seg_init(); while (1) { // 主循環里只負責改變數字, 不負責掃描數碼管 // 數碼管的掃描任務完全交給DMA處理 if (num % 100 < 20) seg_clr(); // 當最後兩位小於20時熄滅數碼管 else seg_set(num); delay(); num++; } }
|
 |
【原理說明】 通道4通過PWM模式2產生時鐘信號SCLK,當計數值CNT<CCR4時輸出低電平, 否則輸出高電平 因此SCLK的上升沿出現在CNT=CCR4的瞬間,此時DIN中的數據移入74HC595
定時器每溢出一次,就向74HC595傳輸一位數據。每傳輸完16位數據, 在傳送下一位時將RCLK置1,使74HC595刷新輸出端, 點亮一位數碼管。 通道2(連接數據端DIO)和通道3(連接輸出鎖存時鐘端RCLK)都配置為強制輸出模式,輸出的電平僅由CCER寄存器中的CCxP決定。 而每次定時器溢出觸發的DMA傳輸更新的就是CCER寄存器(TIM4->CCER=segbuf[i][j])。
【可靠性分析】 若在某一時刻需要熄滅數碼管,則調用seg_clr函數。假設熄滅數碼管時DMA剛好正在發送百位數碼管段選的第6位,該位數碼管顯示的是0xa4。 也就是:10010[1]00 本來點亮該位數碼管時發送的是: 10010100 00000100 但由於seg_clr函數將segbuf[2]_CC2P置成了:11111111 00000000 因此DMA實際發送的是: 10010111 00000000,由於位選為0,所以最終沒有數碼管被點亮, 不會出現閃爍。 更新數字時調用seg_set函數,因為segbuf變量中存儲的低八位(位選代碼)始終是固定的,所以不會出現篡位顯示的情況。
|
 |
【程序的運行效果】

|
 |
【注意】 如果使用高級定時器TIM1/TIM8,則必須要將BDTR寄存器中的MOE置1(開總輸出),否則4個輸出通道將只能輸出低電平: TIM1->BDTR = TIM_BDTR_MOE; // 開總輸出
|
 |
【三個數碼管的動態掃描】 #include <stm32f10x.h>
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; uint16_t segbuf[3][16];
void delay(void) { uint32_t i; for (i = 0; i < 2000000; i++); }
void seg_set(uint32_t num) { uint8_t i, j; uint16_t data, temp; for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++) { data = (seg8[num % 10] << 8) | (1 << i); for (j = 0; j < 16; j++) { temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E; if (j == 0) temp |= TIM_CCER_CC3P; if (data & 0x8000) temp |= TIM_CCER_CC2P; segbuf[i][j] = temp; data <<= 1; } num /= 10; } }
void seg_init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; TIM4->ARR = 624; TIM4->PSC = 17; TIM4->CR1 = TIM_CR1_URS; TIM4->EGR = TIM_EGR_UG; TIM4->CCMR1 = TIM_CCMR1_OC2M_2; TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M; TIM4->CCR4 = 600; TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; TIM4->DIER = TIM_DIER_UDE; RCC->AHBENR |= RCC_AHBENR_DMA1EN; DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR; DMA1_Channel7->CMAR = (uint32_t)segbuf; DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t); DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR; DMA1_Channel7->CCR |= DMA_CCR7_EN; TIM4->CR1 |= TIM_CR1_CEN; }
int main(void) { uint8_t num = 0; RCC->APB2ENR = RCC_APB2ENR_IOPBEN; GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2) GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4) seg_init(); while (1) { seg_set(num); delay(); num++; } }
|
 |
因為每次傳輸只涉及一個寄存器,所以還可以直接將CPAR指向CCER: //TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; // 不再需要這一步 DMA1_Channel7->CPAR = (uint32_t)&TIM4->CCER;
|
 |
如果把CPAR設為GPIOx->ODR的地址,同時設置好CCR寄存器中的MSIZE和PSIZE(8位或16位),那麼就可以操作更多的I/O口,甚至可以脫離74HC595這樣的I/O擴展晶片,直接驅動數碼管。
|
 |
用下面的方法可以消除復位時數碼管閃現的隨機字符: seg_init(); seg_clr(); while ((DMA1->ISR & DMA_ISR_TCIF7) == 0); // 等待清屏完畢 DMA1->IFCR |= DMA_IFCR_CTCIF7; // 清除標誌 並將seg_clr函數中的if (j == 0)改為:if (i != 0 && j == 0) 但是這種方法不能消除上電時的隨機字符,因為DMA的第一個請求是在定時器第一次溢出後才發出,連續發送16個DMA請求後隨機字符才能被消除,這需要消耗(16+1)*156.25us=2.65625ms的時間,這還沒有將DMA和定時器初始化的時間計算在內。
|
 |
【消除復位時的隨機字符】 #include <stm32f10x.h>
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; uint16_t segbuf[3][16];
void delay(void) { uint32_t i; for (i = 0; i < 2000000; i++); }
void seg_set(uint32_t num) { uint8_t i, j; uint16_t data, temp; for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++) { data = (seg8[num % 10] << 8) | (1 << i); for (j = 0; j < 16; j++) { temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E; if (j == 0) temp |= TIM_CCER_CC3P; if (data & 0x8000) temp |= TIM_CCER_CC2P; segbuf[i][j] = temp; data <<= 1; } num /= 10; } }
void seg_clr(void) { uint8_t i, j; uint16_t temp; for (i = 0; i < sizeof(segbuf) / sizeof(segbuf[0]); i++) { for (j = 0; j < 16; j++) { temp = TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E; if (i != 0 && j == 0) // 添加i!=0 temp |= TIM_CCER_CC3P; if (j < 8) temp |= TIM_CCER_CC2P; segbuf[i][j] = temp; } } }
void seg_init(void) { seg_clr(); RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; TIM4->CCMR1 = TIM_CCMR1_OC2M_2; TIM4->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC4M; TIM4->DCR = (&TIM4->CCER - &TIM4->CR1) / 2; TIM4->DIER = TIM_DIER_UDE; RCC->AHBENR |= RCC_AHBENR_DMA1EN; DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_CIRC | DMA_CCR7_DIR; DMA1_Channel7->CMAR = (uint32_t)segbuf; DMA1_Channel7->CNDTR = sizeof(segbuf) / sizeof(uint16_t); DMA1_Channel7->CPAR = (uint32_t)&TIM4->DMAR; DMA1_Channel7->CCR |= DMA_CCR7_EN; TIM4->ARR = 624; TIM4->CCR4 = 600; //TIM4->PSC = 0; // 以較快的速度刷除復位時的隨機字符 TIM4->EGR = TIM_EGR_UG; TIM4->CR1 |= TIM_CR1_CEN; // 不能消除上電時產生的隨機字符 (只能夠通過手動控制74HC595的清零端或輸出允許端消除) while ((DMA1->ISR & DMA_ISR_TCIF7) == 0); DMA1->IFCR |= DMA_IFCR_CTCIF7; TIM4->CR1 |= TIM_CR1_URS; // UG不再將UIF置位 TIM4->PSC = 17; // 減慢掃描速度 }
int main(void) { uint8_t num = 0; RCC->APB2ENR = RCC_APB2ENR_IOPBEN; GPIOB->CRL = 0xb0000000; // PB7->DIO(TIM4_CH2) GPIOB->CRH = 0x000000bb; // PB8->RCLK(TIM4_CH3), PB9->SCLK(TIM4_CH4) seg_init(); while (1) { /*if (num % 4 == 0) seg_clr(); else */seg_set(num); delay(); num++; } }
|
 |
10樓
巨大八爪鱼
2024-4-10 13:40
頂🆙
|