Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • avn/swd/miosix-kernel
  • emilio.corigliano/miosix-kernel
2 results
Select Git revision
Show changes
Showing
with 521 additions and 455 deletions
......@@ -25,8 +25,6 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "board_settings.h"
#define PRESERVE __attribute__((section(".preserve")))
namespace miosix {
......@@ -84,8 +82,6 @@ private:
SGM();
void readResetRegister();
void clearResetFlag();
};
}
/***************************************************************************
* Copyright (C) 2013 by Terraneo Federico and Silvano Seva *
* Copyright (C) 2013-2022 by Terraneo Federico and Silvano Seva *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -32,22 +32,8 @@
using namespace miosix;
static volatile bool error; ///< Set to true by IRQ on error
static Thread *waiting=0; ///< Thread waiting for an operation to complete
static Thread *waiting=nullptr; ///< Thread waiting for an operation to complete
/* In non-DMA mode the variables below are used to
* handle the reception of 2 or more bytes through
* an interrupt, avoiding the thread that calls recv
* to be locked in polling
*/
#ifndef I2C_WITH_DMA
static uint8_t *rxBuf = 0;
static unsigned int rxBufCnt = 0;
static unsigned int rxBufSize = 0;
#endif
#ifdef I2C_WITH_DMA
/**
* DMA I2C rx end of transfer
*/
......@@ -67,12 +53,11 @@ void __attribute__((used)) I2C1rxDmaHandlerImpl()
| DMA_LIFCR_CTEIF0
| DMA_LIFCR_CDMEIF0
| DMA_LIFCR_CFEIF0;
I2C1->CR1 |= I2C_CR1_STOP;
if(waiting==0) return;
if(waiting==nullptr) return;
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=0;
waiting=nullptr;
}
/**
......@@ -84,7 +69,6 @@ void DMA1_Stream7_IRQHandler()
| 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
......@@ -97,8 +81,6 @@ void DMA1_Stream7_IRQHandler()
I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN;
}
#endif
/**
* I2C address sent interrupt
*/
......@@ -114,7 +96,6 @@ void __attribute__((naked)) I2C1_EV_IRQHandler()
*/
void __attribute__((used)) I2C1HandlerImpl()
{
#ifdef I2C_WITH_DMA
//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
......@@ -122,37 +103,12 @@ void __attribute__((used)) I2C1HandlerImpl()
//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;
#else
bool rxFinished = false;
/* If rxBuf is equal to zero means that we are sending the slave
address and this ISR is used to manage the address sent interrupt */
if(rxBuf == 0)
{
I2C1->CR2 &= ~I2C_CR2_ITEVTEN;
rxFinished = true;
}
if(I2C1->SR1 & I2C_SR1_RXNE)
{
rxBuf[rxBufCnt++] = I2C1->DR;
if(rxBufCnt >= rxBufSize)
{
I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
rxFinished = true;
}
}
if(waiting==0 || !rxFinished) return;
#endif
I2C1->CR2 &= ~(I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN);
if(waiting==nullptr) return;
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=0;
waiting=nullptr;
}
/**
......@@ -172,11 +128,11 @@ void __attribute__((used)) I2C1errHandlerImpl()
{
I2C1->SR1=0; //Clear error flags
error=true;
if(waiting==0) return;
if(waiting==nullptr) return;
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=0;
waiting=nullptr;
}
namespace miosix {
......@@ -185,14 +141,11 @@ namespace miosix {
// class I2C
//
I2C1Driver& I2C1Driver::instance()
I2C1Master::I2C1Master(GpioPin sda, GpioPin scl, int frequency)
{
static I2C1Driver singleton;
return singleton;
}
if(checkMultipleInstances) errorHandler(UNEXPECTED);
checkMultipleInstances=true;
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;
......@@ -202,16 +155,18 @@ void I2C1Driver::init()
{
FastInterruptDisableLock dLock;
#ifdef I2C_WITH_DMA
// NOTE: GPIOs need to be configured before enabling the peripheral or
// the first read/write call blocks forever. Smells like a hw bug
// NOTE: ALTERNATE_OD as the I2C peripheral doesn't enforce open drain
sda.alternateFunction(4);
sda.mode(Mode::ALTERNATE_OD);
scl.alternateFunction(4);
scl.mode(Mode::ALTERNATE_OD);
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
RCC_SYNC();
#endif
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //Enable clock gating
RCC_SYNC();
}
#ifdef I2C_WITH_DMA
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);
......@@ -219,7 +174,6 @@ void I2C1Driver::init()
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);
#endif
NVIC_SetPriority(I2C1_EV_IRQn,10);//Low priority for I2C
NVIC_ClearPendingIRQ(I2C1_EV_IRQn);
......@@ -232,46 +186,66 @@ void I2C1Driver::init()
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 i2cSpeed=100000; //100KHz
I2C1->CCR=std::max(4,fpclk1/(2*i2cSpeed)); //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
//Clamp to a reasonable range, but only 100 and 400 are officially supported
frequency=std::max(10,std::min(1000,frequency));
if(frequency>100)
{
I2C1->CCR=std::max(4,fpclk1/(3000*frequency)) | I2C_CCR_FS;
/*
* TRISE sets the maximum SCL rise time. Reading the I2C specs:
* 400KHz (2.5us) I2C has maximum rise time 300ns, ratio 8.333
* 1MHz (1us) I2C has maximum rise time 120ns, ratio 8.333
* Although higher frequencies than 400kHz are not officially supported,
* to allow some overclocking, we'll set TRISE with the "8.333 rule".
* I2Period[s] = 1 / I2CFrequency[Hz]
* RISETIME[s] = I2CPeriod[s] / 8.333
* K = 1 / RISETIME
* TRISE = (fpclk1/K)+1
* Putting it all together,
* K = I2CFrequency[Hz] * 8.333 = I2CFrequency[kHz] * 8333
*/
I2C1->TRISE=fpclk1/(frequency*8333)+1;
} else {
// With full speed mode disabled, we need to divide by 2, not 3
I2C1->CCR=std::max(4,fpclk1/(2000*frequency));
// 100kHz I2C has 1000ns rise time, that does not follow the 8.333 rule
I2C1->TRISE=fpclk1/1000000+1;
}
I2C1->CR1=I2C_CR1_PE; //Enable peripheral
}
bool I2C1Driver::send(unsigned char address,
const void *data, int len, bool sendStop)
bool I2C1Master::recv(unsigned char address, void *data, int len)
{
address &= 0xfe; //Mask bit 0, as we are writing
if(start(address)==false || (I2C1->SR2 & I2C_SR2_TRA)==0)
if(len<=0 || len>0xffff) return false;
address |= 0x01;
if(startWorkaround(address,len)==false || I2C1->SR2 & I2C_SR2_TRA)
{
I2C1->CR1 |= I2C_CR1_STOP;
fastEnableInterrupts(); //HACK Workaround critical section end
stop();
return false;
}
error=false;
waiting=Thread::IRQgetCurrentThread();
#ifdef I2C_WITH_DMA
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
I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN;
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_Stream7->CR=DMA_SxCR_CHSEL_0 //Channel 1
DMA1_Stream0->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;
fastEnableInterrupts(); //HACK Workaround critical section end
{
FastInterruptDisableLock dLock;
......@@ -287,87 +261,43 @@ bool I2C1Driver::send(unsigned char address,
DMA1_Stream7->CR=0;
//The DMA interrupt routine changes the interrupt flags!
I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN);
#else
I2C1->CR2 |= I2C_CR2_ITERREN;
const uint8_t *txData = reinterpret_cast<const uint8_t*>(data);
for(int i=0; i<len && !error; i++)
{
I2C1->DR = txData[i];
while(!(I2C1->SR1 & I2C_SR1_TXE)) ;
}
I2C1->CR2 &= ~I2C_CR2_ITERREN;
#endif
/*
* 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->CR2 &= ~(I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN);
if(sendStop)
{
I2C1->CR1 |= I2C_CR1_STOP;
while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent
} else {
// Dummy write, is the only way to clear
// the TxE flag if stop bit is not sent...
I2C1->DR = 0x00;
}
stop();
return !error;
}
bool I2C1Driver::recv(unsigned char address, void *data, int len)
bool I2C1Master::send(unsigned char address, const void *data, int len, bool sendStop)
{
address |= 0x01;
if(start(address,len==1)==false || I2C1->SR2 & I2C_SR2_TRA)
if(len<=0 || len>0xffff) return false;
address &= 0xfe; //Mask bit 0, as we are writing
if(start(address)==false || (I2C1->SR2 & I2C_SR2_TRA)==0)
{
I2C1->CR1 |= I2C_CR1_STOP;
stop();
return false;
}
error=false;
waiting=Thread::getCurrentThread();
#ifdef I2C_WITH_DMA
I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN;
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
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_Stream0->CR=DMA_SxCR_CHSEL_0 //Channel 1
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)
......@@ -382,78 +312,91 @@ bool I2C1Driver::recv(unsigned char address, void *data, int len)
DMA1_Stream7->CR=0;
I2C1->CR2 &= ~(I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN);
#else
/* Since i2c data reception is a bit tricky (see ST's reference manual for
* further details), the thread that calls recv is yelded and reception is
* handled using interrupts only if the number of bytes to be received is
* greater than one.
*/
//The DMA interrupt routine changes the interrupt flags!
I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN);
rxBuf = reinterpret_cast<uint8_t*>(data);
if(sendStop) stop();
return !error;
}
if(len > 1)
I2C1Master::~I2C1Master()
{
I2C1->CR2 |= I2C_CR2_ITERREN | I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN;
I2C1->CR1=I2C_CR1_SWRST;
I2C1->CR1=0;
rxBufCnt = 0;
rxBufSize = len-2;
NVIC_DisableIRQ(DMA1_Stream7_IRQn);
NVIC_DisableIRQ(DMA1_Stream0_IRQn);
NVIC_DisableIRQ(I2C1_EV_IRQn);
NVIC_DisableIRQ(I2C1_ER_IRQn);
{
FastInterruptDisableLock dLock;
while(waiting)
{
waiting->IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
}
RCC->APB1ENR &= ~RCC_APB1ENR_I2C1EN;
RCC_SYNC();
}
I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
checkMultipleInstances=false;
}
I2C1->CR1 &= ~I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_STOP;
while(!(I2C1->SR1 & I2C_SR1_RXNE)) ;
rxBuf[len-1] = I2C1->DR;
//set pointer to rx buffer to zero after having used it, see i2c event ISR
rxBuf = 0;
I2C1->CR2 &= ~I2C_CR2_ITERREN;
#endif
while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent
return !error;
bool I2C1Master::start(unsigned char address)
{
I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
if(!waitStatus1()) return false;
//EV5: Must read SR1 to clear SB
if((I2C1->SR1 & I2C_SR1_SB)==0) return false;
I2C1->DR=address;
if(!waitStatus1()) return false;
//EV6: Must read SR1 and SR2 to clear ADDR
bool result=true;
if(I2C1->SR1 & I2C_SR1_AF) result=false;
if((I2C1->SR2 & I2C_SR2_MSL)==0) result=false;
return result;
}
bool I2C1Driver::start(unsigned char address, bool immediateNak)
bool I2C1Master::startWorkaround(unsigned char address, int len)
{
/* Because the only way to send a restart is having the send function not
* sending a stop condition after the data transfer, here we have to manage
* a couple of things in SR1:
* - the BTF flag is set, cleared by a dummy read of DR
* - The Berr flag is set, this because the I2C harware detects the start
* condition sent without a stop before it as a misplaced start and
* rises an error
/*
* This function should't exist if this peripheral had been designed in a
* sane way. Unfortunately, reading from the I2C hides a quirk that made
* code using this driver deadlock, sometimes. How? When doing an I2C read,
* as soon as the condition called EV6 in the manual is cleared (a cryptic
* name for "clear the interrupt flag signaling that the address has been
* sent"), the peripheral starts reading from the bus. On its own! And the
* software has to keep up servicing events and setting when to send a NACK
* because the last byte has been sent, otherwise three bad things happen:
* 1) The peripheral may read more data from the bus than requested
* 2) It may be too late to send a NACK at the last byte, and an ACK is sent
* 3) The peripheral may not send the required interrupts anymore
* deadlocking the software.
* But there's the DMA, you may say, shouldn't it automate all this?
* Now, problem was if after clearing EV6 and before configuring the DMA the
* OS decided to do a context switch, then the three bad things happened.
* A clean software solution to cope with this bad hardware design would be
* to create some sort of interrupt driven FSM that would react to events
* and prepare for the next one, relying on the atomicity of interrupts.
* Maybe this solution could even work with the notoriously buggy STM32F1
* I2C peripheral. But for now, we'll just create a critical section by
* disabling interrupts.
* Oh, and another necessary quirk this function implements, when receiving
* a single byte unless you do clear I2C_CR1_ACK before EV6, everything
* locks up. Fragile hardware it is...
*/
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
//EV5: Must read SR1 to clear SB
if((I2C1->SR1 & I2C_SR1_SB)==0) return false;
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;
//If the transfer is single byte, then ACK must be reset before EV6 cleared
if(len==1) I2C1->CR1 &= ~I2C_CR1_ACK;
fastDisableInterrupts(); //HACK Workaround critical section start
bool result=true;
//EV6: Must read SR1 and SR2 to clear ADDR
if(I2C1->SR1 & I2C_SR1_AF) result=false;
if((I2C1->SR2 & I2C_SR2_MSL)==0) result=false;
return result;
}
bool I2C1Driver::waitStatus1()
bool I2C1Master::waitStatus1()
{
error=false;
waiting=Thread::getCurrentThread();
......@@ -473,4 +416,36 @@ bool I2C1Driver::waitStatus1()
return !error;
}
void I2C1Master::stop()
{
/*
* 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
}
bool I2C1Master::checkMultipleInstances=false;
} //namespace miosix
/***************************************************************************
* Copyright (C) 2013 by Terraneo Federico and Silvano Seva *
* Copyright (C) 2013-2022 by Terraneo Federico and Silvano Seva *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -25,74 +25,123 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef STM32F2_I2C_H
#define STM32F2_I2C_H
#pragma once
#include <interfaces/arch_registers.h>
#include "board_settings.h"
#include <interfaces/gpio.h>
namespace miosix {
/**
* Driver for the I2C1 peripheral in STM32F2 and STM32F4 under Miosix
*/
class I2C1Driver
class I2C1Master
{
public:
/**
* \return an instance of this class (singleton)
* Constructor. This driver only supports master mode with 7bit address.
* NOTE: do not create multiple instances of this class.
* \param sda SDA GPIO pin, the constructor configures the pin
* \param scl SCL GPIO pin, the constructor configures the pin
* \param frequency I2C bus frequency, in kHz. Officially only 100kHz and
* 400kHz are supported, but some overclocking may be possible.
*/
static I2C1Driver& instance();
I2C1Master(GpioPin sda, GpioPin scl, int frequency=100);
/**
* 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)
* Send data
* - send START condition
* - send address
* - send data
* - send STOP condition
*
* \param address device address, stored in bits 7 to 1. Bit 0 is ignored
* \param data pointer with data to send
* \param len length of data to send
* \param sendStop if set to false disables the sending of a stop condition
* after data transmission has finished
* \return true on success, false on failure
*/
bool send(unsigned char address,
const void *data, int len, bool sendStop = true);
bool send(unsigned char address, const void *data, int len)
{
return send(address,data,len,true);
}
/**
* Receive data from a device connected to the I2C bus
* \param address device address (bit 0 is forced at 1)
* Purely receive data
* - send START condition
* - send address
* - receive data
* - send STOP condition
*
* \param address device address, stored in bits 7 to 1. Bit 0 is ignored
* \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);
/**
* Send and receive data, with a repeated START betwwen send and receive
* - send START condition
* - send address
* - send data
* - send repeated START
* - send address
* - receive data
* - send STOP condition
*
* \param address device address, stored in bits 7 to 1. Bit 0 is ignored
* \param txData data to transmit, set to nullptr if none
* \param txLen number of bytes to transmit, set to 0 if none
* \param rxData data to receive, set to nullptr if none
* \param rxLen number of bytes to receive, set to 0 if none
*/
bool sendRecv(unsigned char address, const void *txData, int txLen,
void *rxData, int rxLen)
{
if(send(address,txData,txLen,false)==false) return false;
return recv(address,rxData,rxLen);
}
/**
* Probe if a device is on the bus
* - send START condition
* - send address
* - send STOP condition
* \return true if the address was acknowledged on the bus
*/
bool probe(unsigned char address)
{
bool result=start(address & 0xfe);
stop();
return result;
}
/**
* Destructor
*/
~I2C1Master();
private:
I2C1Driver(const I2C1Driver&);
I2C1Driver& operator=(const I2C1Driver&);
I2C1Master(const I2C1Master&);
I2C1Master& operator=(const I2C1Master&);
/**
* 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, ...
* Internal version of send, able to omit the final STOP
* \param address device address (bit 0 is forced at 0)
* \param data pointer with data to send
* \param len length of data to send
* \param sendStop if set to false disables the sending of a STOP condition
* after data transmission has finished
* \return true on success, false on failure
*/
I2C1Driver() { init(); }
bool send(unsigned char address, const void *data, int len, bool sendStop);
/**
* Send a start condition
* \param address
* \param immediateNak
* \return
* \param address device address (includes r/w bit)
* \return true if successful
*/
bool start(unsigned char address, bool immediateNak=false);
bool start(unsigned char address);
bool startWorkaround(unsigned char address, int len);
/**
* Wait until until an interrupt occurs during the send start bit and
......@@ -100,8 +149,13 @@ private:
* \return true if the operation was successful, false on error
*/
bool waitStatus1();
/**
* Send a stop condition, waiting for its completion
*/
void stop();
static bool checkMultipleInstances;
};
} //namespace miosix
#endif //STM32F2_I2C_H
# This is an STM32F0 discovery board with a single STM32F051R8T6 chip.
# http://www.st.com/internet/evalboard/product/253215.jsp
# source [find interface/stlink-v2.cfg]
source [find interface/cmsis-dap.cfg]
# transport select hla_swd
# set WORKAREASIZE 0x2000
source [find target/stm32f0x.cfg]
# reset_config srst_only
\ No newline at end of file
/***************************************************************************
* Copyright (C) 2010 by Terraneo Federico *
* Copyright (C) 2010-2021 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -25,8 +25,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef ARCH_SETTINGS_H
#define ARCH_SETTINGS_H
#pragma once
namespace miosix {
......@@ -55,5 +54,3 @@ const unsigned int CTXSAVE_STACK_ALIGNMENT=8;
*/
} //namespace miosix
#endif /* ARCH_SETTINGS_H */
/***************************************************************************
* Copyright (C) 2010 by Terraneo Federico *
* Copyright (C) 2023 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -31,14 +31,8 @@ namespace miosix {
void delayMs(unsigned int mseconds)
{
#ifdef SYSCLK_FREQ_48MHz
register const unsigned int count=9600;
#elif SYSCLK_FREQ_36MHz
register const unsigned int count=7200;
#elif SYSCLK_FREQ_24MHz
register const unsigned int count=6000;
#else
register const unsigned int count=2016;
#ifdef SYSCLK_FREQ_32MHz
register const unsigned int count=3205;
#endif
for(unsigned int i=0;i<mseconds;i++)
......@@ -46,11 +40,11 @@ void delayMs(unsigned int mseconds)
// This delay has been calibrated to take 1 millisecond
// It is written in assembler to be independent on compiler optimizations
asm volatile(" mov r1, #0 \n"
"___loop_m: cmp r1, %0 \n"
" bge __loop_m_exit\n"
"1: cmp r1, %0 \n"
" bge 2f \n"
" add r1, r1, #1 \n"
" b ___loop_m \n"
"__loop_m_exit: \n"::"r"(count):"r1");
" b 1b \n"
"2: \n"::"r"(count):"r1");
}
}
......@@ -58,23 +52,16 @@ void delayUs(unsigned int useconds)
{
// This delay has been calibrated to take x microseconds
// It is written in assembler to be independent on compiler optimizations
#ifdef SYSCLK_FREQ_48MHz
#error "delayUs not implemented"
#elif SYSCLK_FREQ_36MHz
#error "delayUs not implemented"
#elif SYSCLK_FREQ_24MHz
#error "delayUs not implemented"
#else
asm volatile(" mov r1, #2 \n"
#ifdef SYSCLK_FREQ_32MHz
asm volatile(" mov r1, #4 \n"
" mul r1, %0, r1 \n"
" mov r2, #0 \n"
"___loop_u: cmp r2, r1 \n"
" bge __loop_u_exit\n"
" add r2, r2, #1 \n"
" b ___loop_u \n"
"__loop_u_exit: \n"::"r"(useconds):"r1","r2");
" sub r1, r1, #1 \n"
" .align 2 \n" // <- important!
"1: sub r1, r1, #1 \n"
" cmp r1, #0 \n" //No subs instruction in cortex m0
" bpl 1b \n"::"r"(useconds):"r1");
#else
#error "delayUs not implemented"
#endif
}
......
......@@ -57,14 +57,22 @@ public:
*/
enum Mode_
{
INPUT = 0, ///Floating Input (MODE=00 TYPE=0 PUP=00)
INPUT_PULL_UP = 1, ///Pullup Input (MODE=00 TYPE=0 PUP=01)
INPUT_PULL_DOWN = 2, ///Pulldown Input (MODE=00 TYPE=0 PUP=10)
INPUT_ANALOG = 24, ///Analog Input (MODE=11 TYPE=0 PUP=00)
OUTPUT = 8, ///Push Pull Output (MODE=01 TYPE=0 PUP=00)
OPEN_DRAIN = 12, ///Open Drain Output (MODE=01 TYPE=1 PUP=00)
ALTERNATE = 16, ///Alternate function (MODE=10 TYPE=0 PUP=00)
ALTERNATE_OD = 20, ///Alternate Open Drain (MODE=10 TYPE=1 PUP=00)
INPUT_ANALOG = 0b11000, ///Input Analog (MODE=11 TYPE=0 PUP=00)
INPUT = 0b00000, ///Input Floating (MODE=00 TYPE=0 PUP=00)
INPUT_PULL_UP = 0b00001, ///Input PullUp (MODE=00 TYPE=0 PUP=01)
INPUT_PULL_DOWN = 0b00010, ///Input PullDown (MODE=00 TYPE=0 PUP=10)
OUTPUT = 0b01000, ///Push Pull Output (MODE=01 TYPE=0 PUP=00)
OUTPUT_PULL_UP = 0b01001, ///Push Pull Output PU (MODE=01 TYPE=0 PUP=01)
OUTPUT_PULL_DOWN = 0b01010, ///Push Pull Output PD (MODE=01 TYPE=0 PUP=10)
OPEN_DRAIN = 0b01100, ///Open Drain Output (MODE=01 TYPE=1 PUP=00)
OPEN_DRAIN_PULL_UP = 0b01101, ///Open Drain Output PU (MODE=01 TYPE=1 PUP=01)
OPEN_DRAIN_PULL_DOWN = 0b01110, ///Open Drain Output PD (MODE=01 TYPE=1 PUP=10)
ALTERNATE = 0b10000, ///Alternate function (MODE=10 TYPE=0 PUP=00)
ALTERNATE_PULL_UP = 0b10001, ///Alternate function PU (MODE=10 TYPE=0 PUP=01)
ALTERNATE_PULL_DOWN = 0b10010, ///Alternate function PD (MODE=10 TYPE=0 PUP=10)
ALTERNATE_OD = 0b10100, ///Alternate Open Drain (MODE=10 TYPE=1 PUP=00)
ALTERNATE_OD_PULL_UP = 0b10101, ///Alternate Open Drain PU (MODE=10 TYPE=1 PUP=01)
ALTERNATE_OD_PULL_DOWN = 0b10110, ///Alternate Open Drain PD (MODE=10 TYPE=1 PUP=10)
};
private:
Mode(); //Just a wrapper class, disallow creating instances
......
/***************************************************************************
* Copyright (C) 2010, 2011, 2012 by Terraneo Federico *
* Copyright (C) 2010-2021 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -24,32 +24,14 @@
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
//Miosix kernel
#include "interfaces/portability.h"
#include "kernel/kernel.h"
#include "kernel/error.h"
#include "interfaces/bsp.h"
#include "kernel/scheduler/scheduler.h"
#include "kernel/scheduler/tick_interrupt.h"
#include <algorithm>
/**
* \internal
* timer interrupt routine.
* Since inside naked functions only assembler code is allowed, this function
* only calls the ctxsave/ctxrestore macros (which are in assembler), and calls
* the implementation code in ISR_preempt()
*/
void SysTick_Handler() __attribute__((naked));
void SysTick_Handler()
{
saveContext();
//Call ISR_preempt(). Name is a C++ mangled name.
asm volatile("bl _ZN14miosix_private11ISR_preemptEv");
restoreContext();
}
/**
* \internal
* software interrupt routine.
......@@ -66,41 +48,8 @@ void SVC_Handler()
restoreContext();
}
#ifdef SCHED_TYPE_CONTROL_BASED
/**
* \internal
* Auxiliary timer interupt routine.
* Used for variable lenght bursts in control based scheduler.
* Since inside naked functions only assembler code is allowed, this function
* only calls the ctxsave/ctxrestore macros (which are in assembler), and calls
* the implementation code in ISR_yield()
*/
void XXX_IRQHandler() __attribute__((naked));
void XXX_IRQHandler()
{
saveContext();
//Call ISR_auxTimer(). Name is a C++ mangled name.
asm volatile("bl _ZN14miosix_private12ISR_auxTimerEv");
restoreContext();
}
#endif //SCHED_TYPE_CONTROL_BASED
namespace miosix_private {
/**
* \internal
* Called by the timer interrupt, preempt to next thread
* Declared noinline to avoid the compiler trying to inline it into the caller,
* which would violate the requirement on naked functions. Function is not
* static because otherwise the compiler optimizes it out...
*/
void ISR_preempt() __attribute__((noinline));
void ISR_preempt()
{
IRQstackOverflowCheck();
miosix::IRQtickInterrupt();
}
/**
* \internal
* Called by the software interrupt, yield to next thread
......@@ -111,39 +60,10 @@ void ISR_preempt()
void ISR_yield() __attribute__((noinline));
void ISR_yield()
{
IRQstackOverflowCheck();
miosix::Thread::IRQstackOverflowCheck();
miosix::Scheduler::IRQfindNextThread();
}
#ifdef SCHED_TYPE_CONTROL_BASED
/**
* \internal
* Auxiliary timer interupt routine.
* Used for variable lenght bursts in control based scheduler.
*/
void ISR_auxTimer() __attribute__((noinline));
void ISR_auxTimer()
{
IRQstackOverflowCheck();
miosix::Scheduler::IRQfindNextThread();//If the kernel is running, preempt
if(miosix::kernel_running!=0) miosix::tick_skew=true;
TIM2->SR=0;
}
#endif //SCHED_TYPE_CONTROL_BASED
void IRQstackOverflowCheck()
{
const unsigned int watermarkSize=miosix::WATERMARK_LEN/sizeof(unsigned int);
for(unsigned int i=0;i<watermarkSize;i++)
{
if(miosix::cur->watermark[i]!=miosix::WATERMARK_FILL)
miosix::errorHandler(miosix::STACK_OVERFLOW);
}
if(miosix::cur->ctxsave[0] < reinterpret_cast<unsigned int>(
miosix::cur->watermark+watermarkSize))
miosix::errorHandler(miosix::STACK_OVERFLOW);
}
void IRQsystemReboot()
{
NVIC_SystemReset();
......@@ -171,15 +91,6 @@ void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp,
void IRQportableStartKernel()
{
NVIC_SetPriority(SVC_IRQn,3);//High priority for SVC (Max=0, min=15)
NVIC_SetPriority(SysTick_IRQn,3);//High priority for SysTick (Max=0, min=15)
SysTick->LOAD=SystemCoreClock/miosix::TICK_FREQ;
//Start SysTick, set to generate interrupts
SysTick->CTRL=SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_CLKSOURCE_Msk;
#ifdef SCHED_TYPE_CONTROL_BASED
AuxiliaryTimer::IRQinit();
#endif //SCHED_TYPE_CONTROL_BASED
//create a temporary space to save current registers. This data is useless
//since there's no way to stop the sheduler, but we need to save it anyway.
......@@ -197,22 +108,4 @@ void sleepCpu()
__WFI();
}
#ifdef SCHED_TYPE_CONTROL_BASED
#error "AUX_TIMER not yet implemented"
void AuxiliaryTimer::IRQinit()
{
}
int AuxiliaryTimer::IRQgetValue()
{
}
void AuxiliaryTimer::IRQsetValue(int x)
{
}
#endif //SCHED_TYPE_CONTROL_BASED
} //namespace miosix_private
......@@ -55,6 +55,7 @@
extern "C" {
extern volatile unsigned int *ctxsave;
}
const int stackPtrOffsetInCtxsave=0; ///< Allows to locate the stack pointer
/**
* \internal
......@@ -79,12 +80,12 @@ extern volatile unsigned int *ctxsave;
"mrs r1, psp \n\t" /*get PROCESS stack pointer*/ \
"ldr r0, =ctxsave \n\t" /*get current context*/ \
"ldr r0, [r0] \n\t" \
"stmia r0, {r1,r4-r7} \n\t" /*save PROCESS sp + r4-r7*/ \
"stmia r0!, {r1,r4-r7} \n\t" /*save PROCESS sp + r4-r7*/ \
"mov r4, r8 \n\t" \
"mov r5, r9 \n\t" \
"mov r6, r10 \n\t" \
"mov r7, r11 \n\t" \
"stmia r0, {r4-r7} \n\t" \
"stmia r0!, {r4-r7} \n\t" \
"dmb \n\t" \
); \
}
......@@ -100,12 +101,12 @@ extern volatile unsigned int *ctxsave;
{ \
asm volatile("ldr r0, =ctxsave \n\t" /*get current context*/ \
"ldr r0, [r0] \n\t" \
"ldmia r0, {r1,r4-r7} \n\t" /*pop r8-r11 saving in r4-r7*/ \
"ldmia r0!, {r1,r4-r7} \n\t" /*pop r8-r11 saving in r4-r7*/ \
"msr psp, r1 \n\t" /*restore PROCESS sp*/ \
"ldmia r0!, {r0-r3} \n\t" \
"mov r9, r0 \n\t" \
"mov r10, r1 \n\t" \
"mov r11, r2 \n\t" \
"ldmia r0, {r0-r3} \n\t" \
"mov r8, r0 \n\t" \
"mov r9, r1 \n\t" \
"mov r10, r2 \n\t" \
"mov r11, r3 \n\t" \
"pop {pc} \n\t" /*return*/ \
); \
......
......@@ -95,7 +95,7 @@ void /*__attribute__((weak))*/ NMI_Handler(); //weak because they are
void /*__attribute__((weak))*/ HardFault_Handler(); //surely defined by Miosix
void /*__attribute__((weak))*/ SVC_Handler();
void /*__attribute__((weak))*/ PendSV_Handler();
void /*__attribute__((weak))*/ SysTick_Handler();
void __attribute__((weak)) SysTick_Handler();
// These system handlers are present in Miosix but not supported by the
// architecture, so are defined as weak
......@@ -197,6 +197,7 @@ void (* const __Vectors[])() __attribute__ ((section(".isr_vector"))) =
USB_IRQHandler /* USB */
};
#pragma weak SysTick_Handler = Default_Handler
#pragma weak WWDG_IRQHandler = Default_Handler
#pragma weak PVD_VDDIO2_IRQHandler = Default_Handler
#pragma weak RTC_IRQHandler = Default_Handler
......@@ -224,8 +225,8 @@ void (* const __Vectors[])() __attribute__ ((section(".isr_vector"))) =
#pragma weak I2C2_IRQHandler = Default_Handler
#pragma weak SPI1_IRQHandler = Default_Handler
#pragma weak SPI2_IRQHandler = Default_Handler
#pragma weak USART1_IRQHandler = Default_Handler
#pragma weak USART2_IRQHandler = Default_Handler
#pragma weak USART3_4_IRQHandler = Default_Handler
// #pragma weak USART1_IRQHandler = Default_Handler
// #pragma weak USART2_IRQHandler = Default_Handler
// #pragma weak USART3_4_IRQHandler = Default_Handler
#pragma weak CEC_CAN_IRQHandler = Default_Handler
#pragma weak USB_IRQHandler = Default_Handler
......@@ -15,4 +15,14 @@
telnet_port 4444
gdb_port 3333
source [find arch/cortexM0_stm32/stm32f072rb_stm32f0discovery/stm32f0discovery.cfg]
# This is an STM32F0 discovery board with a single STM32F051R8T6 chip.
# http://www.st.com/internet/evalboard/product/253215.jsp
source [find interface/stlink.cfg]
transport select hla_swd
# set WORKAREASIZE 0x2000
source [find target/stm32f0x.cfg]
# reset_config srst_only
/***************************************************************************
* Copyright (C) 2015 by Terraneo Federico *
* Copyright (C) 2015-2021 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -25,8 +25,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef ARCH_SETTINGS_H
#define ARCH_SETTINGS_H
#pragma once
namespace miosix {
......@@ -55,5 +54,3 @@ const unsigned int CTXSAVE_STACK_ALIGNMENT=8;
*/
} //namespace miosix
#endif /* ARCH_SETTINGS_H */
/***************************************************************************
* Copyright (C) 2023 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* As a special exception, if other files instantiate templates or use *
* macros or inline functions from this file, or you compile this file *
* and link it with other works to produce a work based on this file, *
* this file does not by itself cause the resulting work to be covered *
* by the GNU General Public License. However the source code for this *
* file must still be made available in accordance with the GNU General *
* Public License. This exception does not invalidate any other reasons *
* why a work based on this file might be covered by the GNU General *
* Public License. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "adc.h"
#include "miosix.h"
Adc& Adc::instance()
{
static Adc singleton;
return singleton;
}
void Adc::powerMode(AdcPowerMode mode)
{
ADC0->CTRL &= ~ 0b11; //Clear WARMUPMODE field
//NOTE: from an implementation perspective Off is the same as OnDemand
//but from a logical perspective Off is used to turn the ADC off after it
//has been turned on, while OnDemand is used to pay the startup cost at
//every conversion
if(mode==On)
{
ADC0->CTRL |= ADC_CTRL_WARMUPMODE_KEEPADCWARM;
while((ADC0->STATUS & ADC_STATUS_WARM)==0) ;
}
}
unsigned short Adc::readChannel(int channel)
{
auto temp = ADC0->SINGLECTRL;
temp &= ~(0xf<<8); //Clear INPUTSEL field
temp |= (channel & 0xf)<<8;
ADC0->SINGLECTRL = temp;
ADC0->CMD=ADC_CMD_SINGLESTART;
while((ADC0->STATUS & ADC_STATUS_SINGLEDV)==0) ;
return ADC0->SINGLEDATA;
}
Adc::Adc()
{
{
miosix::FastInterruptDisableLock dLock;
CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_ADC0;
}
ADC0->CTRL = 0b11111<<16 //TIMEBASE ??
| (4-1)<<8 //PRESC 48/4=12MHz < 13MHz OK
| ADC_CTRL_LPFMODE_RCFILT
| ADC_CTRL_WARMUPMODE_NORMAL;
//TODO: expose more options
ADC0->SINGLECTRL = ADC_SINGLECTRL_AT_256CYCLES
| ADC_SINGLECTRL_REF_VDD
| ADC_SINGLECTRL_RES_12BIT
| ADC_SINGLECTRL_ADJ_RIGHT;
}
/***************************************************************************
* Copyright (C) 2023 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* As a special exception, if other files instantiate templates or use *
* macros or inline functions from this file, or you compile this file *
* and link it with other works to produce a work based on this file, *
* this file does not by itself cause the resulting work to be covered *
* by the GNU General Public License. However the source code for this *
* file must still be made available in accordance with the GNU General *
* Public License. This exception does not invalidate any other reasons *
* why a work based on this file might be covered by the GNU General *
* Public License. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#pragma once
/**
* Efm32 ADC driver
*
* Example code
* \code
* {
* FastInterruptDisableLock dLock;
* //GPIO digital logic must be disabled to use as ADC input
* expansion::gpio0::mode(Mode::DISABLED); //GPIO0 is PD3 or ADC0_CH3
* expansion::gpio1::mode(Mode::OUTPUT_HIGH);
* }
* auto& adc=Adc::instance();
* adc.powerMode(Adc::OnDemand);
* for(;;)
* {
* iprintf("%d\n",adc.readChannel(3));
* Thread::sleep(500);
* }
* \endcode
*/
class Adc
{
public:
/**
* Possible ADC power modes
*/
enum AdcPowerMode
{
Off, On, OnDemand
};
/**
* \return an instance to the ADC (singleton)
*/
static Adc& instance();
/**
* Change the ADC power mode
* \param mode power mode
*/
void powerMode(AdcPowerMode mode);
/**
* Read an ADC channel
* \param channel channel to read
* \return ADC sample, in the 0..4095 range
*/
unsigned short readChannel(int channel);
public:
Adc();
};
......@@ -30,10 +30,11 @@
namespace miosix {
#warning "FIXME: EFM32_HFXO_FREQ is not the right value as the core clock can be prescaled"
//TODO: trying to put SystemCoreClock instead of EFM32_HFXO_FREQ results in the
//inner loop taking one more asm instruction. Investigate why and whether it
//may depend on compiler optimizations
//TODO: in theory the core clock can be prescaled so EFM32_HFXO_FREQ could not
//reflect the actual CPU speed, but no use case for doing it was found. Should
//prescaling be implemented, this code needs to be modified, but trying to put
//SystemCoreClock instead of EFM32_HFXO_FREQ results in the inner loop taking
//one more asm instruction, skewing delays.
void delayMs(unsigned int mseconds)
{
......