#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// ======== 圖案資料區 ========
// 初始箭頭圖案(向右箭頭)
const uint8_t arrow_right[8] = {
0b00001000,
0b00001100,
0b00001110,
0b01111111,
0b01111111,
0b00001110,
0b00001100,
0b00001000
};
// 可變圖案工作區 (pattern[])
volatile uint8_t pattern[8];
// ======== 全域變數區 ========
volatile uint8_t current_row = 0; // 掃描行 (0~7)
volatile uint16_t shiftCounter = 0; // 跑馬燈移動速度計數
volatile uint16_t shiftLimit = 300; // 跑馬燈移動速度設定
volatile uint8_t direction = 1; // 0=LEFT, 1=RIGHT, 2=UP, 3=DOWN
volatile uint8_t speed = 0;
volatile bool button_pressed = false; // 外部中斷旗標,記錄是否按鈕被按下
volatile uint16_t adc_result[3] = {512, 512, 0}; // Joycon X/Y 軸 ADC 結果 以及可變
volatile uint8_t adc_channel = 2; // ADC 通道切換
volatile bool shiftFlag = false; // **跑馬燈移動旗標**(ISR 設 flag,主程式處理)
volatile int count = 0;
// ======== SPI 初始化 ========
void SPI_init() {
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5); // SS, MOSI, SCK → 輸出
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); // 啟用 SPI 主模式, F_CPU/16
}
// ======== SPI 傳送 1 byte ========
void SPI_send(uint8_t data) {
SPDR = data;
while (!(SPSR & (1 << SPIF)));
}
// ======== Latch 更新 ========
void latch() {
PORTB &= ~(1 << PB2);
PORTB |= (1 << PB2);
}
// ======== 更新 8x8 矩陣掃描行 ========
void updateMatrix() {
uint8_t row_data = ~(1 << current_row); // 共陰極 → 行需反相
uint8_t col_data = pattern[current_row]; // 共陰極 → 列直接輸出
PORTB &= ~(1 << PB2); // SS = LOW,開始傳輸
//SPCR &= ~(1 << DORD); // MSB first
SPI_send(row_data); // 傳送行資料
//SPCR |= (1 << DORD); // LSB first
SPI_send(col_data); // 傳送列資料
PORTB |= (1 << PB2); // SS = HIGH,鎖存資料
current_row = (current_row + 1) % 8; // 換下一列
}
// ======== 圖案移動邏輯 (跑馬燈效果) ========
void shiftPattern() {
if (direction == 0) { // LEFT
for (uint8_t i = 0; i < 8; i++) {
pattern[i] = (pattern[i] << 1) | (pattern[i] >> 7);
}
} else if (direction == 1) { // RIGHT
for (uint8_t i = 0; i < 8; i++) {
pattern[i] = (pattern[i] >> 1) | (pattern[i] << 7);
}
} else if (direction == 2) { // UP
uint8_t temp = pattern[0];
for (uint8_t i = 0; i < 7; i++) {
pattern[i] = pattern[i + 1];
}
pattern[7] = temp;
} else if (direction == 3) { // DOWN
uint8_t temp = pattern[7];
for (int8_t i = 7; i > 0; i--) {
pattern[i] = pattern[i - 1];
}
pattern[0] = temp;
}
}
// ======== Timer1 初始化 (CTC 模式) ========
void Timer1_init() {
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS12); // CTC 模式 + 預除頻 256
OCR1A = 50; // 掃描速度
TIMSK1 = (1 << OCIE1A); // 啟用比較中斷
}
// ======== ADC 初始化 ========
void ADC_init() {
ADMUX = (1 << REFS0); // 參考電壓 AVCC
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1); // 分頻 64, 啟用中斷
}
// ======== ADC 啟動轉換 ========
void ADC_start() {
ADMUX = (ADMUX & 0xF0) | (adc_channel & 0x0F); // 選通道
ADCSRA |= (1 << ADSC); // 開始轉換
}
// ======== Timer1 中斷 ISR ========
ISR(TIMER1_COMPA_vect) {
updateMatrix(); // 每次中斷掃描 1 行
// 只設 shiftFlag,不直接做 shiftPattern()
shiftCounter++;
if (shiftCounter >= shiftLimit) {
shiftCounter = 0;
shiftFlag = true; // 設置 flag,主程式處理
}
ADC_start(); // 啟動下一次 ADC
}
// ======== ADC 完成 ISR ========
ISR(ADC_vect) {
adc_result[adc_channel] = ADC;
adc_channel = (adc_channel + 1) % 3;
OCR2B = adc_result[2] >> 2; // 0~1023 → 0~255,更新 PWM duty
}
// ======== 方向判斷 (Joycon X/Y 軸) ========
void directionControl() {
if (adc_result[0] < 300) {
direction = 0; // LEFT
} else if (adc_result[0] > 700) {
direction = 1; // RIGHT
} else if (adc_result[1] > 700) {
direction = 2; // UP
} else if (adc_result[1] < 300) {
direction = 3; // DOWN
}
}
// ======== 速度調整 (Joycon X 軸) ========
void speedControl() {
if(speed == 0){
shiftLimit = 50;
}
if(speed == 1){
shiftLimit = 250;
}
if(speed == 2){
shiftLimit = 500;
}
if (button_pressed) {
button_pressed = false;
speed = ( speed + 1 ) % 3;
}
}
// ======== 跑馬燈移動控制 (loop 裡呼叫) ========
void shiftPatternControl() {
if (shiftFlag) {
shiftFlag = false; // 清除 flag
shiftPattern(); // 執行圖案移動
}
}
// 外部中斷 INT0:記錄按鈕被按下的事件
ISR(INT0_vect) {
_delay_ms(20); // 防彈跳延遲
button_pressed = true;
}
// ======================== Timer2 PWM 亮度控制 ========================
void Timer2_PWM_init() {
DDRD |= (1 << PD3); // OC2B (PD3) 為輸出腳,接第二顆 595 的 /OE
TCCR2A = (1 << WGM20) | (1 << WGM21) | (1 << COM2B1); // Fast PWM, 非反向
TCCR2B = (1 << CS22); // 預除頻 64(~976Hz)
OCR2B = 127; // 初始亮度 50%
}
// INT0 初始化:設定下降緣觸發中斷,開啟內部上拉電阻
void INT0_init() {
EICRA |= (1 << ISC01);
EIMSK |= (1 << INT0);
DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2);
}
// ======== setup 初始化 ========
void setup() {
SPI_init();
Timer1_init();
ADC_init();
INT0_init();
Timer2_PWM_init();
// 初始化圖案 → 複製 arrow_right 到 pattern[]
for (uint8_t i = 0; i < 8; i++) {
pattern[i] = arrow_right[i];
}
sei(); // 啟用全域中斷
}
// ======== 主程式 loop ========
void loop() {
directionControl(); // 更新方向
shiftPatternControl(); // 處理跑馬燈移動
speedControl(); // 更新速度
}