mohan-bee/mini-mp3-player

Portable, battery-powered MP3 player hardware with USB charging, microSD storage, an MCU, VS10xx audio decoder, headphone output, status controls, and regulated power rails.

Version
1.2.0
License
unset
Stars
0

firmware/src/board.c

#include "board.h"
#include "ch32v20x.h"
static bool adc_ready;

static void gpio(GPIO_TypeDef *port, uint16_t pins, GPIOMode_TypeDef mode) {
    GPIO_InitTypeDef config = { .GPIO_Pin=pins, .GPIO_Speed=GPIO_Speed_50MHz, .GPIO_Mode=mode };
    GPIO_Init(port, &config);
}

uint32_t millis(void) {
    // Read the free-running 64-bit counter without a low-word rollover tear.
    volatile uint32_t *counter = (volatile uint32_t *)&SysTick->CNT;
    uint32_t high, low, check;
    do { high=counter[1]; low=counter[0]; check=counter[1]; } while (high != check);
    return (uint32_t)((((uint64_t)high << 32) | low) / (SystemCoreClock / 1000));
}
void delay_ms(uint32_t ms) { uint32_t start=millis(); while ((uint32_t)(millis()-start)<ms) {} }
void log_text(const char *s) {
    while (*s) {
        uint32_t start=millis();
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET)
            if ((uint32_t)(millis()-start)>20) return;
        USART_SendData(USART1, (uint8_t)*s++);
    }
}
void log_number(uint32_t n) {
    char text[11]; unsigned i=10; text[i]=0;
    do { text[--i]=(char)('0'+n%10); n/=10; } while(n);
    log_text(text+i);
}
void led_set(bool on) { GPIO_WriteBit(GPIOB, GPIO_Pin_11, on ? Bit_RESET : Bit_SET); }
uint8_t buttons_raw(void) {
    uint8_t pressed=0;
    if (!(GPIOA->INDR & GPIO_Pin_1)) pressed|=BUTTON_PLAY;
    if (!(GPIOA->INDR & GPIO_Pin_2)) pressed|=BUTTON_PREV;
    if (!(GPIOA->INDR & GPIO_Pin_3)) pressed|=BUTTON_NEXT;
    if (!(GPIOB->INDR & GPIO_Pin_0)) pressed|=BUTTON_MODE;
    return pressed;
}
static bool spi_byte(SPI_TypeDef *spi, uint8_t tx, uint8_t *rx) {
    uint32_t start=millis();
    while (!(spi->STATR & SPI_I2S_FLAG_TXE)) if ((uint32_t)(millis()-start)>10) return false;
    SPI_I2S_SendData(spi,tx);
    while (!(spi->STATR & SPI_I2S_FLAG_RXNE)) if ((uint32_t)(millis()-start)>10) return false;
    *rx=(uint8_t)SPI_I2S_ReceiveData(spi);
    while (spi->STATR & SPI_I2S_FLAG_BSY) if ((uint32_t)(millis()-start)>10) return false;
    return true;
}
bool sd_byte(uint8_t tx, uint8_t *rx) { return spi_byte(SPI1,tx,rx); }
bool vs_byte(uint8_t tx, uint8_t *rx) { return spi_byte(SPI2,tx,rx); }
void sd_select(bool selected) { GPIO_WriteBit(GPIOA,GPIO_Pin_4,selected ? Bit_RESET : Bit_SET); }
void vs_select(bool data, bool selected) { GPIO_WriteBit(GPIOB,data ? GPIO_Pin_9 : GPIO_Pin_12,selected ? Bit_RESET : Bit_SET); }
void vs_reset(bool asserted) { GPIO_WriteBit(GPIOB,GPIO_Pin_8,asserted ? Bit_RESET : Bit_SET); }
bool vs_ready(void) { return (GPIOA->INDR & GPIO_Pin_8)!=0; }
static void spi_speed(SPI_TypeDef *spi,uint16_t prescaler) {
    SPI_Cmd(spi,DISABLE);
    spi->CTLR1=(spi->CTLR1 & ~SPI_BaudRatePrescaler_256)|prescaler;
    SPI_Cmd(spi,ENABLE);
}
void sd_speed(bool fast) { spi_speed(SPI1,fast ? SPI_BaudRatePrescaler_8 : SPI_BaudRatePrescaler_256); }
void vs_speed(bool fast) { spi_speed(SPI2,fast ? SPI_BaudRatePrescaler_8 : SPI_BaudRatePrescaler_32); }
static bool adc_read(uint8_t channel,uint16_t *value) {
    ADC_RegularChannelConfig(ADC1,channel,1,ADC_SampleTime_239Cycles5);
    ADC_ClearFlag(ADC1,ADC_FLAG_EOC);
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);
    uint32_t start=millis();
    while (!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)) if ((uint32_t)(millis()-start)>10) return false;
    *value=ADC_GetConversionValue(ADC1); return true;
}
uint16_t battery_mv(void) {
    if (!adc_ready) return UINT16_MAX;
    uint32_t battery=0,reference=0;
    for (unsigned i=0;i<16;i++) {
        uint16_t b,r;
        if (!adc_read(ADC_Channel_0,&b) || !adc_read(ADC_Channel_Vrefint,&r)) return UINT16_MAX;
        battery+=b; reference+=r;
    }
    if (!reference) return UINT16_MAX;
    // 100k/100k divider; nominal internal bandgap. Calibrate against a DMM.
    return (uint16_t)(battery*2400u/reference);
}
void board_init(void) {
    SystemCoreClockUpdate();
    SysTick->CTLR=0; SysTick->CNT=0; SysTick->CMP=UINT64_MAX;
    SysTick->CTLR=5; // Enable, HCLK, no interrupt, no automatic reload.
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO|RCC_APB2Periph_SPI1|RCC_APB2Periph_USART1|RCC_APB2Periph_ADC1,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
    GPIO_SetBits(GPIOA,GPIO_Pin_4);
    GPIO_SetBits(GPIOB,GPIO_Pin_9|GPIO_Pin_12|GPIO_Pin_11);
    GPIO_ResetBits(GPIOB,GPIO_Pin_8);
    gpio(GPIOA,GPIO_Pin_4,GPIO_Mode_Out_PP);
    gpio(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_12|GPIO_Pin_11,GPIO_Mode_Out_PP);
    gpio(GPIOA,GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3,GPIO_Mode_IPU);
    gpio(GPIOB,GPIO_Pin_0,GPIO_Mode_IPU);
    gpio(GPIOA,GPIO_Pin_8|GPIO_Pin_6|GPIO_Pin_10,GPIO_Mode_IN_FLOATING);
    gpio(GPIOB,GPIO_Pin_14,GPIO_Mode_IN_FLOATING);
    gpio(GPIOA,GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_9,GPIO_Mode_AF_PP);
    gpio(GPIOB,GPIO_Pin_13|GPIO_Pin_15,GPIO_Mode_AF_PP);
    gpio(GPIOA,GPIO_Pin_0,GPIO_Mode_AIN);
    SPI_InitTypeDef spi;
    SPI_StructInit(&spi); spi.SPI_Mode=SPI_Mode_Master; spi.SPI_NSS=SPI_NSS_Soft;
    spi.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
    SPI_Init(SPI1,&spi); SPI_Cmd(SPI1,ENABLE);
    spi.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_32;
    SPI_Init(SPI2,&spi); SPI_Cmd(SPI2,ENABLE);
    USART_InitTypeDef uart; USART_StructInit(&uart); uart.USART_BaudRate=115200;
    USART_Init(USART1,&uart); USART_Cmd(USART1,ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    ADC_InitTypeDef adc; ADC_StructInit(&adc); adc.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
    adc.ADC_NbrOfChannel=1; ADC_Init(ADC1,&adc); ADC_BufferCmd(ADC1,DISABLE);
    ADC_TempSensorVrefintCmd(ENABLE); ADC_Cmd(ADC1,ENABLE); delay_ms(2);
    ADC_ResetCalibration(ADC1); uint32_t start=millis();
    while (ADC_GetResetCalibrationStatus(ADC1)) if ((uint32_t)(millis()-start)>100) return;
    ADC_StartCalibration(ADC1); start=millis();
    while (ADC_GetCalibrationStatus(ADC1)) if ((uint32_t)(millis()-start)>100) return;
    delay_ms(20);
    adc_ready=true;
}