From 3725c03a175c5b274384a80c4de02d7b11ddc9ef Mon Sep 17 00:00:00 2001 From: Terraneo Federico <fede.tft@hotmail.it> Date: Tue, 20 Aug 2013 18:22:50 +0200 Subject: [PATCH] Improving BSP for sony watch --- .../common/drivers/stm32_hardware_rng.cpp | 13 +- .../arch/common/drivers/stm32_hardware_rng.h | 7 + miosix/arch/common/drivers/stm32f2_f4_i2c.cpp | 322 ++++++++++++++++++ miosix/arch/common/drivers/stm32f2_f4_i2c.h | 77 +++++ .../interfaces-impl/bsp.cpp | 222 +++++++++++- .../interfaces-impl/bsp_impl.h | 123 ++++++- miosix/config/Makefile.inc | 1 + miosix_np_2/nbproject/configurations.xml | 20 ++ .../nbproject/private/configurations.xml | 2 + miosix_np_2/nbproject/private/private.xml | 2 +- 10 files changed, 780 insertions(+), 9 deletions(-) create mode 100644 miosix/arch/common/drivers/stm32f2_f4_i2c.cpp create mode 100644 miosix/arch/common/drivers/stm32f2_f4_i2c.h diff --git a/miosix/arch/common/drivers/stm32_hardware_rng.cpp b/miosix/arch/common/drivers/stm32_hardware_rng.cpp index 3f3773e3..9595e24d 100644 --- a/miosix/arch/common/drivers/stm32_hardware_rng.cpp +++ b/miosix/arch/common/drivers/stm32_hardware_rng.cpp @@ -29,9 +29,10 @@ #include "interfaces/delays.h" #include "stm32_hardware_rng.h" -using namespace miosix; using namespace std; +namespace miosix { + // // class HardwareRng // @@ -91,7 +92,11 @@ void HardwareRng::get(void* buf, unsigned int size) unsigned int HardwareRng::getImpl() { - for(int i=0;i<16;i++) + #ifndef __NO_EXCEPTIONS + for(int i=0;i<16;i++) //Try up to a reasonable # of times, then throw + #else + for(;;) //Can't return an error, keep retrying + #endif { int timeout=1000000; unsigned int sr; @@ -108,5 +113,9 @@ unsigned int HardwareRng::getImpl() old=result; return result; } + #ifndef __NO_EXCEPTIONS throw runtime_error("RNG Fault detected"); + #endif //__NO_EXCEPTIONS } + +} //namespace miosix diff --git a/miosix/arch/common/drivers/stm32_hardware_rng.h b/miosix/arch/common/drivers/stm32_hardware_rng.h index 536af82a..d3ea2a50 100644 --- a/miosix/arch/common/drivers/stm32_hardware_rng.h +++ b/miosix/arch/common/drivers/stm32_hardware_rng.h @@ -31,6 +31,8 @@ #include "interfaces/arch_registers.h" #include "kernel/sync.h" +namespace miosix { + /** * Class to access the hardware random number generator in Miosix * Works with the hardware RNG in stm32f2 and stm32f4 @@ -58,6 +60,9 @@ public: void get(void *buf, unsigned int size); private: + HardwareRng(const HardwareRng&); + HardwareRng& operator=(const HardwareRng&); + /** * Constructor */ @@ -87,4 +92,6 @@ private: unsigned int old; ///< Previously read value }; +} //namespace miosix + #endif //RNG_H diff --git a/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp b/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp new file mode 100644 index 00000000..11fa9991 --- /dev/null +++ b/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp @@ -0,0 +1,322 @@ + +#include "stm32f2_f4_i2c.h" +#include <miosix.h> +#include <kernel/scheduler/scheduler.h> + +using namespace miosix; + +static bool error; ///< Set to true by IRQ on error +static Thread *waiting=0; ///< Thread waiting for an operation to complete + +/** + * DMA I2C rx end of transfer + */ +void __attribute__((naked)) DMA1_Stream0_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20I2C1rxDmaHandlerImplv"); + restoreContext(); +} + +/** + * DMA I2C tx end of transfer actual implementation + */ +void __attribute__((used)) I2C1rxDmaHandlerImpl() +{ + DMA1->LIFCR=DMA_LIFCR_CTCIF0 + | DMA_LIFCR_CTEIF0 + | DMA_LIFCR_CDMEIF0 + | DMA_LIFCR_CFEIF0; + I2C1->CR1 |= I2C_CR1_STOP; + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * DMA I2C tx end of transfer + */ +void DMA1_Stream7_IRQHandler() +{ + DMA1->HIFCR=DMA_HIFCR_CTCIF7 + | DMA_HIFCR_CTEIF7 + | DMA_HIFCR_CDMEIF7 + | DMA_HIFCR_CFEIF7; + + //We can't just wake the thread because the I2C is double buffered, and this + //interrupt is fired at the same time as the second last byte is starting + //to be sent out of the bus. If we return now, the main code would send a + //stop condiotion too soon, and the last byte would never be sent. Instead, + //we change from DMA mode to IRQ mode, so when the second last byte is sent, + //that interrupt is fired and the last byte is sent out. + //Note that since no thread is awakened from this IRQ, there's no need for + //the saveContext(), restoreContext() and __attribute__((naked)) + I2C1->CR2 &= ~I2C_CR2_DMAEN; + I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN; +} + +/** + * I2C address sent interrupt + */ +void __attribute__((naked)) I2C1_EV_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15I2C1HandlerImplv"); + restoreContext(); +} + +/** + * I2C address sent interrupt actual implementation + */ +void __attribute__((used)) I2C1HandlerImpl() +{ + //When called to resolve the last byte not sent issue, clearing + //I2C_CR2_ITBUFEN prevents this interrupt being re-entered forever, as + //it does not send another byte to the I2C, so the interrupt would remain + //pending. When called after the start bit has been sent, clearing + //I2C_CR2_ITEVTEN prevents the same infinite re-enter as this interrupt + //does not start an address transmission, which is necessary to stop + //this interrupt from being pending + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN); + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * I2C error interrupt + */ +void __attribute__((naked)) I2C1_ER_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z18I2C1errHandlerImplv"); + restoreContext(); +} + +/** + * I2C error interrupt actual implementation + */ +void __attribute__((used)) I2C1errHandlerImpl() +{ + I2C1->SR1=0; //Clear error flags + error=true; + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +namespace miosix { + +// +// class I2C +// + +I2C1Driver& I2C1Driver::instance() +{ + static I2C1Driver singleton; + return singleton; +} + +void I2C1Driver::init() +{ + //I2C devices are connected to APB1, whose frequency is the system clock + //divided by a value set in the PPRE1 bits of RCC->CFGR + const int ppre1=(RCC->CFGR & RCC_CFGR_PPRE1)>>10; + const int divFactor= (ppre1 & 1<<2) ? (2<<(ppre1 & 0x3)) : 1; + const int fpclk1=SystemCoreClock/divFactor; + //iprintf("fpclk1=%d\n",fpclk1); + + { + FastInterruptDisableLock dLock; + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //Enable clock gating + } + + NVIC_SetPriority(DMA1_Stream7_IRQn,10);//Low priority for DMA + NVIC_ClearPendingIRQ(DMA1_Stream7_IRQn);//DMA1 stream 7 channel 1 = I2C1 TX + NVIC_EnableIRQ(DMA1_Stream7_IRQn); + + NVIC_SetPriority(DMA1_Stream0_IRQn,10);//Low priority for DMA + NVIC_ClearPendingIRQ(DMA1_Stream0_IRQn);//DMA1 stream 0 channel 1 = I2C1 RX + NVIC_EnableIRQ(DMA1_Stream0_IRQn); + + NVIC_SetPriority(I2C1_EV_IRQn,10);//Low priority for I2C + NVIC_ClearPendingIRQ(I2C1_EV_IRQn); + NVIC_EnableIRQ(I2C1_EV_IRQn); + + NVIC_SetPriority(I2C1_ER_IRQn,10); + NVIC_ClearPendingIRQ(I2C1_ER_IRQn); + NVIC_EnableIRQ(I2C1_ER_IRQn); + + I2C1->CR1=I2C_CR1_SWRST; + I2C1->CR1=0; + I2C1->CR2=fpclk1/1000000; //Set pclk frequency in MHz + //This sets the duration of both Thigh and Tlow (master mode)) + const int spiSpeed=100000; //100KHz + I2C1->CCR=std::max(4,fpclk1/(2*spiSpeed)); //Duty=2, standard mode (100KHz) + //Datasheet says with I2C @ 100KHz, maximum SCL rise time is 1000ns + //Need to change formula if I2C needs to run @ 400kHz + I2C1->TRISE=fpclk1/1000000+1; + I2C1->CR1=I2C_CR1_PE; //Enable peripheral +} + +bool I2C1Driver::send(unsigned char address, const void *data, int len) +{ + address &= 0xfe; //Mask bit 0, as we are writing + if(start(address)==false || (I2C1->SR2 & I2C_SR2_TRA)==0) + { + I2C1->CR1 |= I2C_CR1_STOP; + return false; + } + + error=false; + waiting=Thread::getCurrentThread(); + DMA1_Stream7->CR=0; + DMA1_Stream7->PAR=reinterpret_cast<unsigned int>(&I2C1->DR); + DMA1_Stream7->M0AR=reinterpret_cast<unsigned int>(data); + DMA1_Stream7->NDTR=len; + DMA1_Stream7->FCR=DMA_SxFCR_FEIE + | DMA_SxFCR_DMDIS; + DMA1_Stream7->CR=DMA_SxCR_CHSEL_0 //Channel 1 + | DMA_SxCR_MINC //Increment memory pointer + | DMA_SxCR_DIR_0 //Memory to peripheral + | DMA_SxCR_TCIE //Interrupt on transfer complete + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start DMA + + //Enable DMA in the I2C peripheral *after* having configured the DMA + //peripheral, or a spurious interrupt is triggered + I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_ITERREN; + + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + + DMA1_Stream7->CR=0; + + //The DMA interrupt routine changes the interrupt flags! + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); + + /* + * The main idea of this driver is to avoid having the processor spinning + * waiting on some status flag. Why? Because I2C is slow compared to a + * modern processor. A 120MHz core does 1200 clock cycles in the time it + * takes to transfer a single bit through an I2C clocked at 100KHz. + * This time could be better spent doing a context switch and letting + * another thread do useful work, or (and Miosix does it automatically if + * there are no ready threads) sleeping the processor core. However, + * I'm quite disappointed by the STM32 I2C peripheral, as it seems overly + * complicated to use. To come close to achieving this goal I had to + * orchestrate among *four* interrupt handlers, two of the DMA, and two + * of the I2C itself. And in the end, what's even more disappointing, is + * that I haven't found a way to completely avoid spinning. Why? + * There's no interrupt that's fired when the stop bit is sent! + * And what's worse, the documentation says that after you set the stop + * bit in the CR2 register you can't write to it again (for example, to send + * a start bit because two i2c api calls are made back to back) until the + * MSL bit is cleared. But there's no interrupt tied to that event! + * What's worse, is that the closest interrupt flag I've found when doing + * an I2C send is fired when the last byte is *beginning* to be sent. + * Maybe I haven't searched well enough, but the fact is I found nothing, + * so this code below spins for 8 data bits of the last byte plus the ack + * bit, plus the stop bit. That's 12000 wasted CPU cycles. Thanks, ST... + */ + I2C1->CR1 |= I2C_CR1_STOP; + while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent + return !error; +} + +bool I2C1Driver::recv(unsigned char address, void *data, int len) +{ + address |= 0x01; + if(start(address,len==1)==false || I2C1->SR2 & I2C_SR2_TRA) + { + I2C1->CR1 |= I2C_CR1_STOP; + return false; + } + + I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN; + + error=false; + waiting=Thread::getCurrentThread(); + DMA1_Stream0->CR=0; + DMA1_Stream0->PAR=reinterpret_cast<unsigned int>(&I2C1->DR); + DMA1_Stream0->M0AR=reinterpret_cast<unsigned int>(data); + DMA1_Stream0->NDTR=len; + DMA1_Stream0->FCR=DMA_SxFCR_FEIE + | DMA_SxFCR_DMDIS; + DMA1_Stream0->CR=DMA_SxCR_CHSEL_0 //Channel 1 + | DMA_SxCR_MINC //Increment memory pointer + | DMA_SxCR_TCIE //Interrupt on transfer complete + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start DMA + + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + + DMA1_Stream7->CR=0; + + I2C1->CR2 &= ~(I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN); + while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent + return !error; +} + +bool I2C1Driver::start(unsigned char address, bool immediateNak) +{ + I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK; + if(!waitStatus1()) return false; + if((I2C1->SR1 & I2C_SR1_SB)==0) return false; //Must read SR1 to clear flag + I2C1->DR=address; + if(immediateNak) I2C1->CR1 &= ~I2C_CR1_ACK; + if(!waitStatus1()) return false; + if(I2C1->SR1 & I2C_SR1_AF) return false; //Must read SR1 and SR2 + if((I2C1->SR2 & I2C_SR2_MSL)==0) return false; + return true; +} + +bool I2C1Driver::waitStatus1() +{ + error=false; + waiting=Thread::getCurrentThread(); + I2C1->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); + return !error; +} + +} //namespace miosix diff --git a/miosix/arch/common/drivers/stm32f2_f4_i2c.h b/miosix/arch/common/drivers/stm32f2_f4_i2c.h new file mode 100644 index 00000000..86d0ca4d --- /dev/null +++ b/miosix/arch/common/drivers/stm32f2_f4_i2c.h @@ -0,0 +1,77 @@ + +#include <interfaces/arch_registers.h> + +#ifndef STM32F2_I2C_H +#define STM32F2_I2C_H + +namespace miosix { + +/** + * Driver for the I2C1 peripheral in STM32F2 and STM32F4 under Miosix + */ +class I2C1Driver +{ +public: + /** + * \return an instance of this class (singleton) + */ + static I2C1Driver& instance(); + + /** + * Initializes the peripheral. The only supported mode is 100KHz, master, + * 7bit address. Note that there is no need to manually call this member + * function as the constructor already inizializes the I2C peripheral. + * The only use of this member function is to reinitialize the peripheral + * if the microcontroller clock frequency or the APB prescaler is changed. + */ + void init(); + + /** + * Send data to a device connected to the I2C bus + * \param address device address (bit 0 is forced at 0) + * \param data pointer with data to send + * \param len length of data to send + * \return true on success, false on failure + */ + bool send(unsigned char address, const void *data, int len); + + /** + * Receive data from a device connected to the I2C bus + * \param address device address (bit 0 is forced at 1) + * \param data pointer to a buffer where data will be received + * \param len length of data to receive + * \return true on success, false on failure + */ + bool recv(unsigned char address, void *data, int len); + +private: + I2C1Driver(const I2C1Driver&); + I2C1Driver& operator=(const I2C1Driver&); + + /** + * Constructor. Initializes the peripheral except the GPIOs, that must be + * set by the caller to the appropriate alternate function mode prior to + * creating an instance of this class. + * \param i2c pinter to the desired I2C peripheral, such as I2C1, I2C2, ... + */ + I2C1Driver() { init(); } + + /** + * Send a start condition + * \param address + * \param immediateNak + * \return + */ + bool start(unsigned char address, bool immediateNak=false); + + /** + * Wait until until an interrupt occurs during the send start bit and + * send address phases of the i2c communication. + * \return true if the operation was successful, false on error + */ + bool waitStatus1(); +}; + +} //namespace miosix + +#endif //STM32F2_I2C_H diff --git a/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp.cpp b/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp.cpp index 8e1132f9..e7f07ce2 100644 --- a/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp.cpp +++ b/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp.cpp @@ -37,9 +37,31 @@ #include "interfaces/portability.h" #include "interfaces/arch_registers.h" #include "config/miosix_settings.h" +#include <algorithm> + +using namespace std; namespace miosix { +FastMutex i2cMutex; + +bool i2cWriteReg(miosix::I2C1Driver& i2c, unsigned char dev, unsigned char reg, + unsigned char data) +{ + const unsigned char buffer[]={reg,data}; + return i2c.send(dev,buffer,sizeof(buffer)); +} + +bool i2cReadReg(miosix::I2C1Driver& i2c, unsigned char dev, unsigned char reg, + unsigned char& data) +{ + if(i2c.send(dev,®,1)==false) return false; + unsigned char temp; + if(i2c.recv(dev,&temp,1)==false) return false; + data=temp; + return true; +} + // // Initialization // @@ -54,16 +76,16 @@ void IRQbspInit() using namespace oled; OLED_nSS_Pin::mode(Mode::OUTPUT); OLED_nSS_Pin::high(); - OLED_nSS_Pin::speed(Speed::_100MHz); //Without changing the default speed + OLED_nSS_Pin::speed(Speed::_50MHz); //Without changing the default speed OLED_SCK_Pin::mode(Mode::ALTERNATE); //OLED does not work! OLED_SCK_Pin::alternateFunction(5); - OLED_SCK_Pin::speed(Speed::_100MHz); + OLED_SCK_Pin::speed(Speed::_50MHz); OLED_MOSI_Pin::mode(Mode::ALTERNATE); OLED_MOSI_Pin::alternateFunction(5); - OLED_MOSI_Pin::speed(Speed::_100MHz); + OLED_MOSI_Pin::speed(Speed::_50MHz); OLED_A0_Pin::mode(Mode::OUTPUT); OLED_A0_Pin::low(); - OLED_A0_Pin::speed(Speed::_100MHz); + OLED_A0_Pin::speed(Speed::_50MHz); OLED_Reset_Pin::mode(Mode::OUTPUT); OLED_Reset_Pin::low(); OLED_Reset_Pin::speed(Speed::_50MHz); @@ -166,6 +188,7 @@ void IRQbspInit() void bspInit2() { + PowerManagement::instance(); //This initializes the PMU BUZER_PWM_Pin::high(); Thread::sleep(200); BUZER_PWM_Pin::low(); @@ -201,4 +224,193 @@ void reboot() miosix_private::IRQsystemReboot(); } -};//namespace miosix +//As usual, since the PMU datasheet is unavailable (we don't even know what +//chip it is), these are taken from underverk's code +#define CHGSTATUS 0x01 + +#define CH_ACTIVE_MSK 0x08 + +#define CHGCONFIG0 0x02 + +#define VSYS_4_4V 0x40 +#define VSYS_5V 0x80 +#define ACIC_100mA_DPPM_ENABLE 0x00 +#define ACIC_500mA_DPPM_ENABLE 0x10 +#define ACIC_500mA_DPPM_DISABLE 0x20 +#define ACIC_USB_SUSPEND 0x20 +#define TH_LOOP 0x08 +#define DYN_TMR 0x04 +#define TERM_EN 0x02 +#define CH_EN 0x01 + +#define CHGCONFIG1 0x03 +#define I_PRE_05 0x00 +#define I_PRE_10 0x40 +#define I_PRE_15 0x80 +#define I_PRE_20 0xC0 + +#define DEFDCDC 0x07 +#define DCDC1_DEFAULT 0x29 +#define DCDC_DISCH 0x40 +#define HOLD_DCDC1 0x80 + +#define ISET_25 0x00 +#define ISET_50 0x10 +#define ISET_75 0x20 +#define ISET_100 0x30 + +#define I_TERM_05 0x00 +#define I_TERM_10 0x04 +#define I_TERM_15 0x08 +#define I_TERM_20 0x0C + +#define CHGCONFIG2 0x04 +#define SFTY_TMR_4h 0x0 +#define SFTY_TMR_5h 0x40 +#define SFTY_TMR_6h 0x80 +#define SFTY_TMR_8h 0xC0 + +#define PRE_TMR_30m 0x0 +#define PRE_TMR_60m 0x20 + +#define NTC_100k 0x0 +#define NTC_10k 0x8 + +#define V_DPPM_VBAT_100mV 0x0 +#define V_DPPM_4_3_V 0x04 + +#define VBAT_COMP_ENABLE 0x02 +#define VBAT_COMP_DISABLE 0x00 + +// +// class PowerManagement +// + +PowerManagement& PowerManagement::instance() +{ + static PowerManagement singleton; + return singleton; +} + +bool PowerManagement::isUsbConnected() const +{ + return usb::USB5V_Detected_Pin::value(); +} + +bool PowerManagement::isCharging() +{ + Lock<FastMutex> l(i2cMutex); + unsigned char chgstatus; + //During testing the i2c command never failed. If it does, we lie and say + //we're not charging + if(i2cReadReg(i2c,PMU_I2C_ADDRESS,CHGSTATUS,chgstatus)==false) return false; + return (chgstatus & CH_ACTIVE_MSK)!=0; +} + +int PowerManagement::getBatteryStatus() +{ + const int battCharged=4000; //4.0V + const int battDead=3000; //3V + return max(0,min(100,(getBatteryVoltage()-battDead)*100/(battCharged-battDead))); +} + +int PowerManagement::getBatteryVoltage() +{ + Lock<FastMutex> l(batteryMutex); + power::BATT_V_ON_Pin::high(); //Enable battry measure circuitry + ADC1->CR2=ADC_CR2_ADON; //Turn ADC ON + Thread::sleep(5); //Wait for voltage to stabilize + ADC1->CR2 |= ADC_CR2_SWSTART; //Start conversion + while((ADC1->SR & ADC_SR_EOC)==0) ; //Wait for conversion + int result=ADC1->DR; //Read result + ADC1->CR2=0; //Turn ADC OFF + power::BATT_V_ON_Pin::low(); //Disable battery measure circuitry + return result*4440/4095; +} + +PowerManagement::PowerManagement() : i2c(I2C1Driver::instance()), + chargingAllowed(true) +{ + { + FastInterruptDisableLock dLock; + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + } + ADC1->CR1=0; + ADC1->CR2=0; //Keep the ADC OFF to save power + ADC1->SMPR2=ADC_SMPR2_SMP2_0; //Sample for 15 cycles channel 2 (battery) + ADC1->SQR1=0; //Do only one conversion + ADC1->SQR2=0; + ADC1->SQR3=2; //Convert channel 2 (battery voltage) + + unsigned char config0=VSYS_4_4V + | ACIC_100mA_DPPM_ENABLE + | TH_LOOP + | DYN_TMR + | TERM_EN + | CH_EN; + unsigned char config1=I_TERM_10 + | ISET_100 + | I_PRE_10; + unsigned char config2=SFTY_TMR_5h + | PRE_TMR_30m + | NTC_10k + | V_DPPM_4_3_V + | VBAT_COMP_ENABLE; + unsigned char defdcdc=DCDC_DISCH + | DCDC1_DEFAULT; + Lock<FastMutex> l(i2cMutex); + bool error=false; + if(i2cWriteReg(i2c,PMU_I2C_ADDRESS,CHGCONFIG0,config0)==false) error=true; + if(i2cWriteReg(i2c,PMU_I2C_ADDRESS,CHGCONFIG1,config1)==false) error=true; + if(i2cWriteReg(i2c,PMU_I2C_ADDRESS,CHGCONFIG2,config2)==false) error=true; + if(i2cWriteReg(i2c,PMU_I2C_ADDRESS,DEFDCDC,defdcdc)==false) error=true; + if(!error) return; + //Should never happen + for(int i=0;i<10;i++) + { + BUZER_PWM_Pin::high(); + Thread::sleep(200); + BUZER_PWM_Pin::low(); + Thread::sleep(200); + } +} + +// +// class LightSensor +// + +LightSensor& LightSensor::instance() +{ + static LightSensor singleton; + return singleton; +} + +int LightSensor::read() +{ + Lock<FastMutex> l(lightMutex); + power::ENABLE_LIGHT_SENSOR_Pin::high(); //Enable battry measure circuitry + ADC2->CR2=ADC_CR2_ADON; //Turn ADC ON + Thread::sleep(5); //Wait for voltage to stabilize + ADC2->CR2 |= ADC_CR2_SWSTART; //Start conversion + while((ADC2->SR & ADC_SR_EOC)==0) ; //Wait for conversion + int result=ADC2->DR; //Read result + ADC2->CR2=0; //Turn ADC OFF + power::ENABLE_LIGHT_SENSOR_Pin::low(); //Disable battery measure circuitry + return result; +} + +LightSensor::LightSensor() +{ + { + FastInterruptDisableLock dLock; + RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; + } + ADC2->CR1=0; + ADC2->CR2=0; //Keep the ADC OFF to save power + ADC2->SMPR1=ADC_SMPR1_SMP14_0; //Sample for 15 cycles channel 14 + ADC2->SQR1=0; //Do only one conversion + ADC2->SQR2=0; + ADC2->SQR3=14; //Convert channel 14 (light sensor) +} + +} //namespace miosix diff --git a/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp_impl.h b/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp_impl.h index ee06b0db..45186f31 100644 --- a/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp_impl.h +++ b/miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp_impl.h @@ -36,6 +36,8 @@ #include "config/miosix_settings.h" #include "hwmapping.h" #include "drivers/stm32_hardware_rng.h" +#include "drivers/stm32f2_f4_i2c.h" +#include "kernel/sync.h" namespace miosix { @@ -43,6 +45,125 @@ namespace miosix { inline void ledOn() {} inline void ledOff() {} -} +/** + * You <b>must</b> lock this mutex before accessing the I2C1Driver directly + * on this board, as there are multiple threads that access the I2C for + * different purposes (touchscreen, accelerometer, PMU). If you don't do it, + * your application will crash sooner or later. + */ +extern FastMutex i2cMutex; + +enum { + PMU_I2C_ADDRESS=0x90, ///< I2C Address of the PMU + TOUCH_I2C_ADDRESS=0x0a,///< I2C Address of the touchscreen controller + ACCEL_I2C_ADDRESS=0x30 ///< I2C Address of the accelerometer +}; + +/** + * Helper function to write a register of an I2C device. Don't forget to lock + * i2cMutex before calling this. + * \param i2c the I2C driver + * \param dev device address (PMU_I2C_ADDRESS) + * \param reg register address + * \param data byte to write + * \return true on success, false on failure + */ +bool i2cWriteReg(miosix::I2C1Driver& i2c, unsigned char dev, unsigned char reg, + unsigned char data); + +/** + * Helper function to write a register of an I2C device. Don't forget to lock + * i2cMutex before calling this. + * \param i2c the I2C driver + * \param dev device address (PMU_I2C_ADDRESS) + * \param reg register address + * \param data byte to write + * \return true on success, false on failure + */ +bool i2cReadReg(miosix::I2C1Driver& i2c, unsigned char dev, unsigned char reg, + unsigned char& data); + +/** + * This class contains all what regards power management on the watch. + * The class can be safely used by multiple threads concurrently. + */ +class PowerManagement +{ +public: + /** + * \return an instance of the power management class (singleton) + */ + static PowerManagement& instance(); + + /** + * \return true if the USB cable is connected + */ + bool isUsbConnected() const; + + /** + * \return true if the battery is currently charging. For this to happen, + * charging must be allowed, the USB cable must be connected, and the + * battery must not be fully charged. + */ + bool isCharging(); + + /** + * \return the battery charge status as a number in the 0..100 range. + * The reading takes ~5ms + */ + int getBatteryStatus(); + + /** + * \return the battery voltage, in millivolts. So 3700 means 3.7V. + * The reading takes ~5ms + */ + int getBatteryVoltage(); + +private: + PowerManagement(const PowerManagement&); + PowerManagement& operator=(const PowerManagement&); + + /** + * Constructor + */ + PowerManagement(); + + I2C1Driver &i2c; + bool chargingAllowed; + FastMutex batteryMutex; +}; + +/** + * This allows to retrieve the light value + * The class can be safely used by multiple threads concurrently. + */ +class LightSensor +{ +public: + /** + * \return an instance of the power management class (singleton) + */ + static LightSensor& instance(); + + /** + * \return the light value. The reading takes ~5ms + */ + int read(); + +private: + LightSensor(const LightSensor&); + LightSensor& operator=(const LightSensor&); + + /** + * Constructor + */ + LightSensor(); + + FastMutex lightMutex; +}; + + + +} //namespace miosix #endif //BSP_IMPL_H diff --git a/miosix/config/Makefile.inc b/miosix/config/Makefile.inc index 910993d6..dce538c4 100644 --- a/miosix/config/Makefile.inc +++ b/miosix/config/Makefile.inc @@ -843,6 +843,7 @@ else ifeq ($(ARCH),cortexM3_stm32f2) ## Select architecture specific files ## These are the files in arch/<arch name>/<board name> ARCH_SRC := \ + arch/common/drivers/stm32f2_f4_i2c.cpp \ $(BOARD_INC)/interfaces-impl/console.cpp \ $(BOARD_INC)/interfaces-impl/delays.cpp \ $(BOARD_INC)/interfaces-impl/bsp.cpp diff --git a/miosix_np_2/nbproject/configurations.xml b/miosix_np_2/nbproject/configurations.xml index 1d41c781..31ed01df 100644 --- a/miosix_np_2/nbproject/configurations.xml +++ b/miosix_np_2/nbproject/configurations.xml @@ -42,6 +42,8 @@ <in>dcc.h</in> <in>stm32_hardware_rng.cpp</in> <in>stm32_hardware_rng.h</in> + <in>stm32f2_f4_i2c.cpp</in> + <in>stm32f2_f4_i2c.h</in> </df> </df> <df name="cortexM3_stm32"> @@ -157,6 +159,9 @@ <in>arch_settings.h</in> </df> <df name="stm32f205rg_sony-newman"> + <df name="core"> + <in>stage_1_boot.cpp</in> + </df> <df name="interfaces-impl"> <in>bsp.cpp</in> <in>bsp_impl.h</in> @@ -8097,6 +8102,16 @@ tool="3" flavor2="0"> </item> + <item path="../miosix/arch/common/drivers/stm32f2_f4_i2c.cpp" + ex="false" + tool="1" + flavor2="0"> + </item> + <item path="../miosix/arch/common/drivers/stm32f2_f4_i2c.h" + ex="false" + tool="3" + flavor2="0"> + </item> <item path="../miosix/arch/cortexM3_stm32/common/CMSIS/core_cm3.c" ex="false" tool="0" @@ -14869,6 +14884,11 @@ tool="3" flavor2="0"> </item> + <item path="../miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/core/stage_1_boot.cpp" + ex="false" + tool="1" + flavor2="0"> + </item> <item path="../miosix/arch/cortexM3_stm32f2/stm32f205rg_sony-newman/interfaces-impl/bsp.cpp" ex="false" tool="1" diff --git a/miosix_np_2/nbproject/private/configurations.xml b/miosix_np_2/nbproject/private/configurations.xml index 3701528d..3f209690 100644 --- a/miosix_np_2/nbproject/private/configurations.xml +++ b/miosix_np_2/nbproject/private/configurations.xml @@ -42,6 +42,8 @@ <in>dcc.h</in> <in>stm32_hardware_rng.cpp</in> <in>stm32_hardware_rng.h</in> + <in>stm32f2_f4_i2c.cpp</in> + <in>stm32f2_f4_i2c.h</in> </df> </df> <df name="cortexM3_stm32"> diff --git a/miosix_np_2/nbproject/private/private.xml b/miosix_np_2/nbproject/private/private.xml index 53489479..f1b9e16b 100644 --- a/miosix_np_2/nbproject/private/private.xml +++ b/miosix_np_2/nbproject/private/private.xml @@ -5,7 +5,7 @@ </code-assistance-data> <data xmlns="http://www.netbeans.org/ns/make-project-private/1"> <activeConfTypeElem>0</activeConfTypeElem> - <activeConfIndexElem>2</activeConfIndexElem> + <activeConfIndexElem>13</activeConfIndexElem> </data> <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/> <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/> -- GitLab