Compare commits

...

20 Commits

Author SHA1 Message Date
King Kévin 58b6823627 application: use new DCF77 interface 2017-10-13 15:50:25 +02:00
King Kévin e598b572c2 rtc_dcf77: better decoding using signal correlation 2017-10-13 15:49:48 +02:00
King Kévin 2f67cbe979 Rakefile: check header changes 2017-10-13 14:01:23 +02:00
King Kévin 256363bd15 Rakefile: switch from clang to gcc because fot clang uint64_t is only 4 bytes long 2017-10-09 17:59:12 +02:00
King Kévin d8830f3114 global: fix typo 2017-10-09 17:58:19 +02:00
King Kévin 901e5468e8 application: fix overflow warning 2017-10-09 17:57:54 +02:00
King Kévin 937c9fd25c README: remove external RTC support 2017-10-09 17:57:24 +02:00
King Kévin b95994027f application: use macro to get ADC channel registers 2017-10-09 09:46:11 +02:00
King Kévin 5f3473257d lib/led_ws2812b: now use global macros 2017-10-09 09:36:53 +02:00
King Kévin de6a6637e6 global: add SPI DMA macros 2017-10-09 09:36:23 +02:00
King Kévin 332bd132cf lib/flash_internal: re-add used library 2017-10-08 18:59:26 +02:00
King Kévin 6bbb30ed84 remove unused libraries 2017-10-08 18:42:34 +02:00
King Kévin 4ec6691818 lib/rtc_dcf77: update library to use global macros 2017-10-08 18:36:32 +02:00
King Kévin 4994c6a77d application: better new hour animation 2017-10-08 18:35:57 +02:00
King Kévin 0cbaf714d2 application: minor fixes 2017-10-08 18:09:52 +02:00
King Kévin 2cff4f0af5 README: port LED clock README 2017-10-08 17:33:37 +02:00
King Kévin e70e430a31 application: port LED clock firmware to application 2017-10-08 17:32:06 +02:00
King Kévin e8826000fa lib/rtc_dcf77: make library compilable again 2017-10-08 17:30:22 +02:00
King Kévin de5d8d0d87 Rakefile: change library order for libm to be used correctly 2017-10-08 16:53:36 +02:00
King Kévin 409a4ffa4c Rakefile: switch to blue pill board + st-link v2 adapter used by project 2017-10-08 16:52:24 +02:00
41 changed files with 713 additions and 6646 deletions

View File

@ -1,4 +1,13 @@
This firmware template is designed for development boards based around [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
The LED clock is an add-on for round wall clocks.
The purpose is to have LEDs on the circumference of the clock to show the progress of the time using coloured light.
For that you will need:
- a WS2812B RGB LEDs strip (long enough to go around the clock)
- a development board with a STM32F103 micro-controller equipped with a 32.768 kHz oscillator for the Real Time Clock (such as the [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill))
- a coin cell battery to keep the RTC running (optional)
- a GL5528 photo-resistor to adjust the LED brightness (optional)
- a DCF77 module to set and update the time automatically (salvaged from a radio controlled digital clock)
project
=======
@ -6,23 +15,60 @@ project
summary
-------
*describe project purpose*
The time will be shown as arc progress bars, in addition to the original hands of the clock pointing at the current time.
The hours passed since the beginning of the midday are shown using blue LEDs.
The minutes passed sine the beginning of the hour are shown using green LEDs.
Whichever progress is higher will be shown on top of the other.
For example if it's 6:45, the first half of the circle will be blue, and an additional quarter will be green.
The seconds passed since the beginning of the minute are shown using a running red LED, similar to the seconds hand.
The red colour might be added on top of the blue, or green colour, then showing as violet or orange.
The (gamma corrected) brightness of the last LED shows how much of the hour, minute, or second has passed.
technology
----------
*described electronic details*
The brain of this add-on is a [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031) (based on an ARM Cortex-M3 32-bit processor).
To keep track of the time a Real Time Clock (RTC) is used.
If the board includes a 32.768 kHz oscillator (such as a [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill)) the micro-controller will use the internal RTC.
Connect a DCF77 module (e.g. salvaged from a radio controlled clock) to the micro-controller.
This will allow to automatically get precise time (at least in Europe) when booting.
Since the RTC is drifting, the time will get updated using DCF77 every hour to keep <0.5 s time precision.
Alternatively set the time using serial over the USB port (providing the CDC ACM profile) or USART port and enter "time HH:MM:SS".
Power the board using an external 5 V power supply (e.g. through the USB port).
This will power the micro-controller, and the LEDs (a single LED consumes more energy than the micro-controller).
To keep the correct time in case the main power supply gets disconnected optionally connect a 3 V coin battery on the VBAT pin for the internal RTC.
For the LEDs use a 1 meter LED strip with 60 red-green-blue WS2812B LEDs.
Tape the LED strip along the border/edge of the clock.
Ideally the wall clock has a diameter of 32 cm for a 1 m LED strip to completely fit.
Otherwise change the number of actually used LEDs in the source files.
Connect the 5 V power rail of the LED strip to the 5 V pin of the board.
Connect the DIN signal line of the LED strip to the MISO pin of the micro-controller on PA6.
SPI is used to efficiently shift out the LED colour values to the WS2812B LEDs.
A custom clock is provided for this operation using channel 3 of timer 3 on pin PB0.
Simply connect this clock to the SPI CLK input on pin PA5.
The brightness of the LEDs is dependant on the ambient luminance.
To measure the ambient luminance a GL5528 photo-resistor is used.
Connect one leg of the photo-resistor to ADC channel 1 and the other to ground.
Connect one leg of a 1 kOhm resistor to ADC channel 1 and the other to a 3.3 V pin.
This voltage divider allows to measure the photo-sensor's resistance and determine the luminance.
If you don't want to use this feature, connect PA1 to ground for the highest brightness or Vcc for the lowest brightness.
board
=====
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
The current implementation uses a [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill).
The underlying template also supports following board:
- [Maple Mini](http://leaflabs.com/docs/hardware/maple-mini.html), based on a STM32F103CBT6
- [System Board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#system_board), based on a STM32F103C8T6
- [blue pill](ihttps://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
- [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
**Which board is used is defined in the Makefile**.
@ -33,7 +79,13 @@ connections
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
- *list board to preipheral pin connections*
- USART1_TX; PA9; RX; UART RX; optional, same as over USB ACM
- USART1_RX; PA10; TX; UART TX; optional, same as over USB ACM
- ADC12_IN1; PA1; GL5528; photo-resistor + 1 kOhm to 3.3 V; without GL5528 photo-resistor connect to ground for highest brightness or Vcc for lowest brightness
- TIM3_CH3; PB0; PA5; SPI1_SCK; generated clock for WS2812B transmission
- SPI1_MISO; PA6; WS2812B DIN; DIN; WS2812B LED strip data stream
- GPIO; PA2; DCF77 PO; \#EN; DCF77 enable on low
- GPIO; PA3; DCF77 TN; DCF77; DCF77 high bit pulses
All pins are configured using `define`s in the corresponding source code.
@ -69,7 +121,7 @@ It is up to the application to advertise USB DFU support (i.e. as does the provi
The `bootlaoder` image will be flashed using SWD (Serial Wire Debug).
For that you need an SWD adapter.
The `Makefile` uses a Black Magic Probe (per default), or a ST-Link V2 along OpenOCD software.
The `Makefile` uses a Black Magic Probe, or a ST-Link V2 (per default) along OpenOCD software.
To flash the `booltoader` using SWD run `rake flash_booloader`.
Once the `bootloader` is flashed it is possible to flash the `application` over USB using the DFU protocol by running `rake flash`.

View File

@ -15,7 +15,7 @@ FIRMWARES = [BOOTLOADER, APPLICATION]
# which development board is used
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, CORE_BOARD
BOARD = ENV["BOARD"] || "CORE_BOARD"
BOARD = ENV["BOARD"] || "BLUE_PILL"
# libopencm3 definitions
LIBOPENCM3_DIR = "libopencm3"
@ -29,7 +29,8 @@ SRC_DIRS = [".", "lib"]
# cross-compiler environment
PREFIX = ENV["PREFIX"] || "arm-none-eabi"
CC = "clang -target #{PREFIX}" # use clang instead of gcc
CC = PREFIX+"-gcc"
#CC = "clang -target #{PREFIX}" # to use clang instead of gcc
LD = PREFIX+"-ld"
AR = PREFIX+"-ar"
AS = PREFIX+"-as"
@ -53,10 +54,6 @@ cflags << "-fno-common -ffunction-sections -fdata-sections"
cflags << "-fshort-enums"
# don't use system main definition (the starting point)
cflags << "-ffreestanding"
# don't use the standard library (only if you provide an alternative libc library)
#cflags << "-nostdlib -nostdinc"
# standard C library (use musl libc)
cflags << "-I /usr/lib/musl/include/"
# include own libraries
cflags += SRC_DIRS.collect {|srd_dir| "-I #{srd_dir}"}
# include libopencm3 library
@ -74,14 +71,15 @@ ldflags << "-static"
ldflags << "-nostartfiles"
# only keep used sections
ldflags << "--gc-sections"
# don't use system libraries (only if you provide an alternative libc library)
#ldflags << "-nostdlib -nostdinc"
# add standard libraries (for libc, libm, libnosys, libgcc) and libopencm3
library_paths = ["/usr/arm-none-eabi/lib/armv7-m/", "/usr/lib/gcc/arm-none-eabi/*/armv7-m/", LIBOPENCM3_LIB]
# add standard libraries (for libc, libm, libnosys, libgcc) because we don't use arm-none-eabi-gcc to locate them
library_paths = ["/usr/arm-none-eabi/lib/armv7-m/", "/usr/lib/gcc/arm-none-eabi/*/armv7-m/"]
# add libopencm3
library_paths += [LIBOPENCM3_LIB]
# include libraries in flags
ldflags += library_paths.collect {|library_path| "--library-path #{library_path}"}
ldflags *= ' '
# used libraries (gcc provides the ARM ABI)
ldlibs = [STM32F1_LIB, "c", "m", "nosys", "gcc"]
ldlibs = [STM32F1_LIB, "m", "c", "nosys", "gcc"]
ldlibs = ldlibs.collect {|library| "--library #{library}"}
ldlibs *= ' '
@ -145,7 +143,7 @@ task :doc => ["Doxyfile", "README.md"] do |t|
end
desc "compile source into object"
rule '.o' => ['.c', "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a"] do |t|
rule '.o' => ['.c', '.h', "#{LIBOPENCM3_LIB}/lib#{STM32F1_LIB}.a"] do |t|
sh "#{CC} #{cflags} #{archflags} -o #{t.name} -c #{t.prerequisites[0]}"
end
@ -187,7 +185,7 @@ end
# SWD/JTAG adapter used
# supported are : STLINKV2 (ST-Link V2), BMP (Black Magic Probe)
SWD_ADAPTER = ENV["SWD_ADAPTER"] || "BMP"
SWD_ADAPTER = ENV["SWD_ADAPTER"] || "STLINKV2"
# openOCD path to control the adapter
OOCD = ENV["OOCD"] || "openocd"
# openOCD adapted name

View File

@ -22,7 +22,7 @@
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <math.h> // mathematical utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
@ -35,12 +35,19 @@
#include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/adc.h> // ADC utilities
#include <libopencm3/stm32/rtc.h> // real time clock utilities
/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#include "usart.h" // USART utilities
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "led_ws2812b.h" // WS2812B LEDs utilities
#include "rtc_dcf77.h" // DCF77 time receiver utilities
/** use external RTC, else use internal RTC */
#define EXTERNAL_RTC false
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
@ -48,10 +55,50 @@
* @{
*/
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
volatile bool photoresistor_flag = false; /**< flag set when ambient luminosity is measured */
/** @} */
time_t time_rtc = 0; /**< time (seconds since Unix Epoch) */
struct tm* time_tm; /**< time in tm format (time zones are not handled for non-POSIX environments) */
/** @defgroup main_ticks ticks per time units
* @note these are derived from TICKS_PER_SECOND
* @note I have to use type variables because defines would be stored in signed integers, leading to an overflow it later calculations
* @{
*/
/** the number of ticks in one second (32768 divisor greater than 256*LED_WS2812B_LEDS/60) */
#define TICKS_PER_SECOND (256UL)
/** number of ticks in one second */
#define TICKS_SECOND (TICKS_PER_SECOND)
/** number of ticks in one minute */
#define TICKS_MINUTE (60*TICKS_SECOND)
/** number of ticks in one hour */
#define TICKS_HOUR (60*TICKS_MINUTE)
/** number of ticks in one midday (12 hours) */
#define TICKS_MIDDAY (12*TICKS_HOUR)
/** @} */
/** @defgroup photoresistor_adc ADC used to ambient luminosity
* @{
*/
#define PHOTORESISTOR_ADC_CHANNEL 1 /**< ADC channel */
/** @} */
/** RGB values for the WS2812B clock LEDs */
uint8_t clock_leds[LED_WS2812B_LEDS*3] = {0};
/** gamma correction lookup table (common for all colors) */
uint8_t gamma_correction_lut[256] = {0};
/** photo-resistor measurement of ambient luminosity */
volatile uint16_t photoresistor_value = 0;
/** photo-resistor voltage for the minimum brightness */
#define PHOTORESISTOR_MIN 2.7
/** photo-resistor voltage for the maximum brightness */
#define PHOTORESISTOR_MAX 1.7
/** factor to dim LED of the clock, depending on the ambient luminosity */
float clock_brightness = 1;
/** minimum LED brightness */
#define BRIGHTNESS_MIN 0.2
/** maximum LED brightness */
#define BRIGHTNESS_MAX 1.0
/** the factor to change the brightness */
#define BRIGHTNESS_FACTOR 0.1
size_t putc(char c)
{
@ -99,6 +146,8 @@ static void process_command(char* str)
if (0==strcmp(word,"h") || 0==strcmp(word,"help") || 0==strcmp(word,"?")) {
printf("available commands:\n");
printf("led [on|off|toggle]\n");
printf("time [HH:MM:SS]\n");
printf("DCF77 on|off\n");
} else if (0==strcmp(word,"l") || 0==strcmp(word,"led")) {
word = strtok(NULL,delimiter);
if (!word) {
@ -123,38 +172,25 @@ static void process_command(char* str)
} else if (0==strcmp(word,"time")) {
word = strtok(NULL,delimiter);
if (!word) {
time_rtc = rtc_get_counter_val(); // get time from internal RTC
time_tm = localtime(&time_rtc); // convert time
printf("time: %02d:%02d:%02d\n", time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
printf("time: %02U:%02U:%02U\n", rtc_get_counter_val()/TICKS_HOUR, (rtc_get_counter_val()%TICKS_HOUR)/TICKS_MINUTE, (rtc_get_counter_val()%TICKS_MINUTE)/TICKS_SECOND); // get and print time from internal RTC
} else if (strlen(word)!=8 || word[0]<'0' || word[0]>'2' || word[1]<'0' || word[1]>'9' || word[3]<'0' || word[3]>'5' || word[4]<'0' || word[4]>'9' || word[6]<'0' || word[6]>'5' || word[7]<'0' || word[7]>'9') { // time format is incorrect
goto error;
} else {
time_rtc = rtc_get_counter_val(); // get time from internal RTC
time_tm = localtime(&time_rtc); // convert time
time_tm->tm_hour = (word[0]-'0')*10+(word[1]-'0')*1; // set hours
time_tm->tm_min = (word[3]-'0')*10+(word[4]-'0')*1; // set minutes
time_tm->tm_sec = (word[6]-'0')*10+(word[7]-'0')*1; // set seconds
time_rtc = mktime(time_tm); // get back seconds
rtc_set_counter_val(time_rtc); // save time to internal RTC
rtc_set_counter_val(((word[0]-'0')*10+(word[1]-'0')*1)*TICKS_HOUR+((word[3]-'0')*10+(word[4]-'0')*1)*TICKS_MINUTE+((word[6]-'0')*10+(word[7]-'0')*1)*TICKS_SECOND); // set time in internal RTC counter
printf("time set\n");
}
} else if (0==strcmp(word,"date")) {
} else if (0==strcmp(word,"DCF77")) {
word = strtok(NULL,delimiter);
if (!word) {
time_rtc = rtc_get_counter_val(); // get time from internal RTC
time_tm = localtime(&time_rtc); // convert time
printf("date: %d-%02d-%02d\n", 1900+time_tm->tm_year, time_tm->tm_mon+1, time_tm->tm_mday);
} else if (strlen(word)!=10 || word[0]!='2' || word[1]!='0' || word[2]<'0' || word[2]>'9' || word[3]<'0' || word[3]>'9' || word[5]<'0' || word[5]>'1' || word[6]<'0' || word[6]>'9' || word[8]<'0' || word[8]>'3' || word[9]<'0' || word[9]>'9') {
goto error;
goto error;
} else if (0==strcmp(word,"on")) {
rtc_dcf77_on(); // switch DCF77 on
printf("DCF77 receiver switched on\n"); // notify user
} else if (0==strcmp(word,"off")) {
rtc_dcf77_off(); // switch DCF77 off
printf("DCF77 receiver switched off\n"); // notify user
} else {
time_rtc = rtc_get_counter_val(); // get time from internal RTC
time_tm = localtime(&time_rtc); // convert time
time_tm->tm_year = ((word[0]-'0')*1000+(word[1]-'0')*100+(word[2]-'0')*10+(word[3]-'0')*1)-1900; // set year
time_tm->tm_mon = (word[5]-'0')*10+(word[6]-'0')*1-1; // set month
time_tm->tm_mday = (word[8]-'0')*10+(word[9]-'0')*1; // set day
time_rtc = mktime(time_tm); // get back seconds
rtc_set_counter_val(time_rtc); // save time to internal RTC
printf("date set\n");
goto error;
}
} else {
goto error;
@ -166,6 +202,178 @@ error:
return;
}
/** switch off all clock LEDs
* @note LEDs need to be set separately
*/
static void clock_clear(void)
{
// set all colors of all LEDs to 0
for (uint16_t i=0; i<LENGTH(clock_leds); i++) {
clock_leds[i] = 0;
}
}
/** show time on LED clock
* @param[in] time in ticks to show
* @details show hours and minutes progress as full arcs, show second position as marker. the brightness of the LED shows the progress of the unit. hours are blue, minutes green, seconds red
* @note LEDs need to be set separately
*/
static void clock_show_time(uint32_t time)
{
uint32_t led_hour = (LED_WS2812B_LEDS*256ULL*(time%TICKS_MIDDAY))/TICKS_MIDDAY; // scale to LED brightnesses for hours
uint32_t led_minute = (LED_WS2812B_LEDS*256ULL*(time%TICKS_HOUR))/TICKS_HOUR; // scale to LED brightnesses for minutes
if (led_hour>=LED_WS2812B_LEDS*256 || led_minute>=LED_WS2812B_LEDS*256) { // a calculation error occurred
return;
}
// show hours and minutes on LEDs
if (led_hour>led_minute) {
// show hours in blue (and clear other LEDs)
for (uint16_t led=0; led<LED_WS2812B_LEDS; led++) {
clock_leds[led*3+0] = 0; // clear red (seconds)
clock_leds[led*3+1] = 0; // clear green (minutes)
if (led_hour>=0xff) { // full hours
clock_leds[led*3+2] = 0xff; // set blue (hours) to full
} else { // running hours
clock_leds[led*3+2] = led_hour; // set blue (hours) to remaining
}
led_hour -= clock_leds[led*3+2]; // subtract displayed value
}
// show minutes in green (override hours)
for (uint16_t led=0; led<LED_WS2812B_LEDS && led_minute>0; led++) {
clock_leds[led*3+0] = 0; // clear red (seconds)
if (led_minute>=0xff) { // full minutes
clock_leds[led*3+1] = 0xff; // set green (minutes) to full
} else { // running minutes
clock_leds[led*3+1] = led_minute; // set green (minutes) to remaining
}
led_minute -= clock_leds[led*3+1]; // subtract displayed value
clock_leds[led*3+2] = 0; // clear blue (hours)
}
} else {
// show minutes in green (and clear other LEDs)
for (uint16_t led=0; led<LED_WS2812B_LEDS; led++) {
clock_leds[led*3+0] = 0; // clear red (seconds)
if (led_minute>=0xff) { // full minutes
clock_leds[led*3+1] = 0xff; // set green (minutes) to full
} else { // running minutes
clock_leds[led*3+1] = led_minute; // set green (minutes) to remaining
}
led_minute -= clock_leds[led*3+1]; // subtract displayed value
clock_leds[led*3+2] = 0; // clear blue (hours)
}
// show hours in blue (override minutes)
for (uint16_t led=0; led<LED_WS2812B_LEDS && led_hour>0; led++) {
clock_leds[led*3+0] = 0; // clear red (seconds)
clock_leds[led*3+1] = 0; // clear green (minutes)
if (led_hour>=0xff) { // full hours
clock_leds[led*3+2] = 0xff; // set blue (hours) to full
} else { // running hours
clock_leds[led*3+2] = led_hour; // set blue (hours) to remaining
}
led_hour -= clock_leds[led*3+2]; // subtract displayed value
}
}
// don't show seconds on full minute (better for first time setting, barely visible else)
if (time%TICKS_MINUTE==0) {
return;
}
uint32_t led_second = (LED_WS2812B_LEDS*(256*(uint64_t)(time%TICKS_MINUTE)))/TICKS_MINUTE; // scale to LED brightnesses for seconds
uint8_t brightness_second = led_second%256; // get brightness for seconds for last LED
uint16_t second_led = (LED_WS2812B_LEDS*(time%TICKS_MINUTE))/TICKS_MINUTE; // get LED for seconds (we only use the last LED as runner instead of all LEDs as arc)
// set seconds LED
clock_leds[second_led*3+0] = brightness_second;
//clock_leds[second_led*3+1] = 0; // clear other colors (minutes/hours indication)
//clock_leds[second_led*3+2] = 0; // clear other colors (minutes/hours indication)
// set previous seconds LED
second_led = ((second_led==0) ? LED_WS2812B_LEDS-1 : second_led-1); // previous LED
clock_leds[second_led*3+0] = 0xff-brightness_second;
//clock_leds[second_led*3+1] = 0; // clear other colors (minutes/hours indication)
//clock_leds[second_led*3+2] = 0; // clear other colors (minutes/hours indication)
}
/** set the LEDs
* @details set the LED colors on WS2812B LEDs
* @note WS2812B LED color values need to be transmitted separately
*/
static void clock_leds_set(void)
{
for (uint16_t i=0; i<LENGTH(clock_leds)/3; i++) {
led_ws2812b_set_rgb(i,gamma_correction_lut[(uint8_t)(clock_leds[i*3+0]*clock_brightness)],gamma_correction_lut[(uint8_t)(clock_leds[i*3+1]*clock_brightness)],gamma_correction_lut[(uint8_t)(clock_leds[i*3+2]*clock_brightness)]); // set new value (this costs time)
}
}
/** set the time on the LEDs
* @param[in] time time to set
*/
static void clock_set_time(uint32_t time)
{
clock_show_time(time); // convert time to LED values
clock_leds_set(); // set the colors of all LEDs
led_ws2812b_transmit(); // transmit set color
}
/** incrementally set the time on the LEDs
* @details this will have an animation where time is incremented until it reaches the provided time
* @param[in] time time to set
*/
static void clock_animate_time(uint32_t time)
{
static uint32_t display_time = 0; // the time to display
while (display_time<time) {
if (display_time+TICKS_HOUR<=time) { // first set hours
display_time += TICKS_HOUR; // increment hours
} else if (display_time+TICKS_MINUTE<=time) { // second set minutes
display_time += TICKS_MINUTE; // increment minutes
} else if (display_time+TICKS_SECOND<=time) { // third set seconds
display_time += TICKS_SECOND; // increment seconds
} else { // finally set time
display_time = time;
}
clock_set_time(display_time); // set time (progress)
// delay some time for the animation
for (uint32_t i=0; i<400000; i++) {
__asm__("nop");
}
}
}
/** show animation with fading hours mark on clock LEDs
*/
static void clock_hours(void)
{
for (uint16_t i=0; i<255; i++) { // fade out
for (uint16_t led=0; led<LED_WS2812B_LEDS; led++) { // fade minutes out
if (clock_leds[led*3+1]) {
clock_leds[led*3+1] -= 1; // fade minutes out (green)
}
if (clock_leds[led*3+0]) {
clock_leds[led*3+0] -= 1; // fade seconds out (red)
}
}
clock_leds_set(); // set the colors of all LEDs
led_ws2812b_transmit(); // transmit set color
// delay some time for the animation
for (uint32_t j=0; j<40000; j++) {
__asm__("nop");
}
}
for (uint16_t i=0; i<512; i++) { // fade hour marks in and out
uint8_t brightness = (i>255 ? 512-i-1 : i); // get fade brightness
for (uint8_t hour=0; hour<12; hour++) { // set all hour colors
uint16_t led = (uint16_t)(LED_WS2812B_LEDS*hour)/12; // get LED four hour mark
clock_leds[led*3+0] = brightness; // set brightness
clock_leds[led*3+1] = brightness; // set brightness
clock_leds[led*3+2] = brightness; // set brightness
}
clock_leds_set(); // set the colors of all LEDs
led_ws2812b_transmit(); // transmit set color
// delay some time for the animation
for (uint32_t j=0; j<40000; j++) {
__asm__("nop");
}
}
}
/** program entry point
* this is the firmware function started by the micro-controller
*/
@ -174,7 +382,6 @@ void main(void)
{
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
#if DEBUG
// enable functionalities for easier debug
DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
@ -191,7 +398,7 @@ void main(void)
board_setup(); // setup board
usart_setup(); // setup USART (for printing)
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
printf("welcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
printf("welcome to the CuVoodoo LED clock\n"); // print welcome message
#if !(DEBUG)
// show watchdog information
@ -207,17 +414,68 @@ void main(void)
// setup RTC
printf("setup internal RTC: ");
rtc_auto_awake(RCC_LSE, 32768-1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
rtc_auto_awake(RCC_LSE, 32768/TICKS_SECOND-1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
printf("OK\n");
time_rtc= rtc_get_counter_val(); // get time from internal RTC
time_tm = localtime(&time_rtc); // convert time
printf("date: %d-%02d-%02d %02d:%02d:%02d\n", 1900+time_tm->tm_year, time_tm->tm_mon+1, time_tm->tm_mday, time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
// setup DCF77
printf("setup DCF77 receiver: ");
rtc_dcf77_setup(); // setup DCF77 module
printf("OK\n");
rtc_dcf77_on(); // switch DCF77 on to get correct time
printf("DCF77 receiver switched on\n"); // notify user
// setup WS2812B LEDs
printf("setup LEDs: ");
for (uint16_t i=0; i<LENGTH(gamma_correction_lut); i++) { // generate gamma correction table
gamma_correction_lut[i] = powf((float)i / (float)LENGTH(gamma_correction_lut), 2.2)*LENGTH(gamma_correction_lut); // calculate using fixed gamma value
}
led_ws2812b_setup(); // setup WS2812B LEDs
clock_clear(); // clear all LEDs
clock_leds_set(); // set the colors of all LEDs
led_ws2812b_transmit(); // transmit set color
printf("OK\n");
// setup ADC to photo-resistor voltage
printf("setup brightness sensor: ");
rcc_periph_clock_enable(RCC_ADC12_IN(PHOTORESISTOR_ADC_CHANNEL)); // enable clock for photo-resistor GPIO peripheral
gpio_set_mode(ADC12_IN_PORT(PHOTORESISTOR_ADC_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, ADC12_IN_PIN(PHOTORESISTOR_ADC_CHANNEL)); // set photo-resistor GPIO as analogue input for the ADC
rcc_periph_clock_enable(RCC_ADC1); // enable clock for ADC peripheral
adc_off(ADC1); // switch off ADC while configuring it
// configuration is correct per default
adc_set_single_conversion_mode(ADC1); // we just want one measurement
adc_set_sample_time_on_all_channels(ADC1, ADC_SMPR_SMP_28DOT5CYC); // use 28.5 cycles to sample (long enough to be stable)
adc_enable_temperature_sensor(ADC1); // enable internal voltage reference
adc_enable_discontinuous_mode_regular(ADC1, 1); // do only one conversion per sequence
adc_enable_external_trigger_regular(ADC1, ADC_CR2_EXTSEL_SWSTART); // use software trigger to start conversion
adc_power_on(ADC1); // switch on ADC
for (uint32_t i=0; i<800000; i++) { // wait t_stab for the ADC to stabilize
__asm__("nop");
}
adc_reset_calibration(ADC1); // remove previous non-calibration
adc_calibration(ADC1); // calibrate ADC for less accuracy errors
// read internal reference 1.2V
uint8_t channels[] = {ADC_CHANNEL17}; // voltages to convert
adc_set_regular_sequence(ADC1, LENGTH(channels), channels); // set channels to convert
adc_start_conversion_regular(ADC1); // start conversion to get first voltage of this group
while (!adc_eoc(ADC1)); // wait until conversion finished
uint16_t ref_value = adc_read_regular(ADC1); // read internal reference 1.2V voltage value
// now use interrupts to only measure ambient luminosity
channels[0] = ADC_CHANNEL(PHOTORESISTOR_ADC_CHANNEL); // only measure ambient luminosity
adc_set_regular_sequence(ADC1, 1, channels); // set now group
adc_enable_eoc_interrupt(ADC1); // enable interrupt for end of conversion
nvic_enable_irq(NVIC_ADC1_2_IRQ); // enable ADC interrupts
printf("OK\n");
// get date and time
uint32_t ticks_time = rtc_get_counter_val(); // get time/date from internal RTC
printf("current time: %02u:%02u:%02u\n", ticks_time/TICKS_HOUR, (ticks_time%TICKS_HOUR)/TICKS_MINUTE, (ticks_time%TICKS_MINUTE)/TICKS_SECOND); // display time
clock_animate_time(ticks_time); // set time with animation
// main loop
printf("command input: ready\n");
led_on(); // indicate everything is OK and the main loop will start
bool action = false; // if an action has been performed don't go to sleep
button_flag = false; // reset button flag
char c = '\0'; // to store received character
@ -262,17 +520,56 @@ void main(void)
}
button_flag = false; // reset flag
}
while (rtc_dcf77_time_flag) { // the DCF77 module received a new time
rtc_dcf77_time_flag = false; // reset flag
action = true; // action has been performed
if (rtc_dcf77_time.valid) { // ensure decoded time is valid
ticks_time = rtc_dcf77_time.hours*TICKS_HOUR+rtc_dcf77_time.minutes*TICKS_MINUTE+rtc_dcf77_time.seconds*TICKS_SECOND+(rtc_dcf77_time.milliseconds*TICKS_SECOND)/1000; // calculate new time
rtc_set_counter_val(ticks_time); // set new time to internal RTC
printf("DCF77 time: 20%02u-%02u-%02u %02u:%02u:%02u\n", rtc_dcf77_time.year, rtc_dcf77_time.month, rtc_dcf77_time.day, rtc_dcf77_time.hours, rtc_dcf77_time.minutes, rtc_dcf77_time.seconds); // display time
// never switch of DCF77 receiver since the signal gets better demodulated with time, we always have power, we can always get correct the time
} else {
printf("DCF77 time: error\n");
}
}
while (rtc_internal_tick_flag) { // the internal RTC ticked
rtc_internal_tick_flag = false; // reset flag
ticks_time = rtc_get_counter_val(); // copy time from internal RTC for processing
action = true; // action has been performed
#if !defined(BLUE_PILL) // on the blue pill the LED is close to the 32.768 kHz oscillator and heavily influences it
led_toggle(); // toggle LED (good to indicate if main function is stuck)
#endif
time_rtc = rtc_get_counter_val(); // get time from internal RTC (seconds since Unix Epoch)
time_tm = localtime(&time_rtc); // get time in tm format from Epoch (time zones are not handled for non-POSIX environments)
if (0==time_tm->tm_sec) { // new minute
printf("time: %02d:%02d:%02d\n", time_tm->tm_hour, time_tm->tm_min, time_tm->tm_sec);
if ((ticks_time%(TICKS_SECOND/10))==0) { // one tenth of a second passed
adc_start_conversion_regular(ADC1); // start measuring ambient luminosity
}
if ((ticks_time%TICKS_SECOND)==0) { // one second passed
//led_toggle(); // LED toggling confuses the 32.768 kHz oscillator on the blue pill
}
if ((ticks_time%TICKS_MINUTE)==0) { // one minute passed
printf("%02u:%02u:%02u\n", ticks_time/TICKS_HOUR, (ticks_time%TICKS_HOUR)/TICKS_MINUTE, (ticks_time%TICKS_MINUTE)/TICKS_SECOND); // display external time
}
if ((ticks_time%(TICKS_MINUTE*5))==0) { // five minutes passed
rtc_dcf77_on(); // ensure the module is on in case it switched off because of decoding issues
}
if ((ticks_time%TICKS_HOUR)==0) { // one hours passed
clock_hours(); // show hour markers
}
if (ticks_time>=TICKS_MIDDAY*2) { // one day passed
rtc_set_counter_val(rtc_get_counter_val()%TICKS_MIDDAY); // reset time counter
}
clock_set_time(ticks_time); // set time
}
while (photoresistor_flag) { // new photo-resistor value has been measured
photoresistor_flag = false; // reset flag
action = true; // action has been performed
float photoresistor_voltage = photoresistor_value*1.2/ref_value; // calculate voltage from value
float new_clock_brightness = 0; // to calculate new brightness
if (photoresistor_voltage<PHOTORESISTOR_MAX) { // high ambient luminosity
new_clock_brightness = BRIGHTNESS_MAX; // set highest brightness
} else if (photoresistor_voltage>PHOTORESISTOR_MIN) { // low ambient luminosity
new_clock_brightness = BRIGHTNESS_MIN; // set low brightness
} else { // intermediate ambient luminosity
new_clock_brightness = BRIGHTNESS_MIN+(BRIGHTNESS_MAX-BRIGHTNESS_MIN)*(1-(photoresistor_voltage-PHOTORESISTOR_MAX)/(PHOTORESISTOR_MIN-PHOTORESISTOR_MAX)); // set variable brightness
}
clock_brightness = clock_brightness*(1-BRIGHTNESS_FACTOR)+new_clock_brightness*BRIGHTNESS_FACTOR; // calculate new brightness based on factor
//printf("photo-resistor voltage: %f, clock brightness: %f\n", photoresistor_voltage, clock_brightness);
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
@ -288,3 +585,10 @@ void rtc_isr(void)
rtc_clear_flag(RTC_SEC); // clear flag
rtc_internal_tick_flag = true; // notify to show new time
}
/** interrupt service routine called when ADC conversion completed */
void adc1_2_isr(void)
{
photoresistor_value = adc_read_regular(ADC1); // read measured photo-resistor value (clears interrupt flag)
photoresistor_flag = true; // notify new ambient luminosity has been measured
}

View File

@ -20,6 +20,7 @@
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // memory utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
@ -31,7 +32,6 @@
#include <libopencm3/stm32/exti.h> // external interrupt defines
#include "global.h" // common methods
#include "string.h" // memory utilities
volatile bool button_flag = false;
volatile uint32_t sleep_duration = 0; /**< sleep duration count down (in SysTick interrupts) */

View File

@ -278,6 +278,26 @@
#define DMA_CHANNEL_SPI1_RX DMA_CHANNEL4 /**< SPI1 RX is on DMA channel 4 */
#define DMA_CHANNEL_SPI2_RX DMA_CHANNEL2 /**< SPI2 RX is on DMA channel 2 */
#define DMA_CHANNEL_SPI3_RX DMA_CHANNEL1 /**< SPI3 RX is on DMA channel 1 */
/** get DMA NVIC IRQ for SPI TX based on SPI identifier */
#define DMA_IRQ_SPI_TX(x) CAT3(NVIC_DMA_CHANNEL_IRQ_SPI,x,_TX)
#define NVIC_DMA_CHANNEL_IRQ_SPI1_TX NVIC_DMA1_CHANNEL3_IRQ /**< SPI1 TX is on DMA 1 channel 3 */
#define NVIC_DMA_CHANNEL_IRQ_SPI2_TX NVIC_DMA1_CHANNEL5_IRQ /**< SPI2 TX is on DMA 1 channel 5 */
#define NVIC_DMA_CHANNEL_IRQ_SPI3_TX NVIC_DMA2_CHANNEL2_IRQ /**< SPI3 TX is on DMA 2 channel 2 */
/** get DMA NVIC IRQ for SPI RX based on SPI identifier */
#define DMA_IRQ_SPI_RX(x) CAT3(NVIC_DMA_CHANNEL_IRQ_SPI,x,_RX)
#define NVIC_DMA_CHANNEL_IRQ_SPI1_RX NVIC_DMA1_CHANNEL4_IRQ /**< SPI1 RX is on DMA 1 channel 4 */
#define NVIC_DMA_CHANNEL_IRQ_SPI2_RX NVIC_DMA1_CHANNEL2_IRQ /**< SPI2 RX is on DMA 1 channel 2 */
#define NVIC_DMA_CHANNEL_IRQ_SPI3_RX NVIC_DMA2_CHANNEL1_IRQ /**< SPI3 RX is on DMA 2 channel 1 */
/** get DMA ISR for SPI TX based on SPI identifier */
#define DMA_ISR_SPI_TX(x) CAT3(DMA_CHANNEL_ISR_SPI,x,_TX)
#define DMA_CHANNEL_ISR_SPI1_TX dma1_channel3_isr /**< SPI1 TX is on DMA 1 channel 3 */
#define DMA_CHANNEL_ISR_SPI2_TX dma1_channel5_isr /**< SPI2 TX is on DMA 1 channel 5 */
#define DMA_CHANNEL_ISR_SPI3_TX dma2_channel2_isr /**< SPI3 TX is on DMA 2 channel 2 */
/** get DMA ISR for SPI RX based on SPI identifier */
#define DMA_ISR_SPI_RX(x) CAT3(DMA_CHANNEL_ISR_SPI,x,_RX)
#define DMA_CHANNEL_ISR_SPI1_RX dma1_channel4_isr /**< SPI1 RX is on DMA 1 channel 4 */
#define DMA_CHANNEL_ISR_SPI2_RX dma1_channel2_isr /**< SPI2 RX is on DMA 1 channel 2 */
#define DMA_CHANNEL_ISR_SPI3_RX dma2_channel1_isr /**< SPI3 RX is on DMA 2 channel 1 */
/** get DMA channel based on SPI identifier */
/** @} */

View File

@ -1,611 +0,0 @@
/* 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 3 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with an SD card flash memory using the SPI mode (code)
* @file flash_sdcard.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017
* @note peripherals used: SPI @ref flash_sdcard_spi
* @warning all calls are blocking
* @implements SD Specifications, Part 1, Physical Layer, Simplified Specification, Version 6.00, 10 April 10 2017
* @todo use SPI unidirectional mode, use DMA, force/wait going to idle state when initializing, filter out reserved values, check sector against size
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include "global.h" // global utilities
#include "flash_sdcard.h" // SD card header and definitions
/** @defgroup flash_sdcard_spi SPI used to communication with SD card
* @{
*/
#define FLASH_SDCARD_SPI 1 /**< SPI peripheral */
/** @} */
/** if the card has been initialized successfully */
static bool initialized = false;
/** maximum N_AC value (in 8-clock cycles) (time between the response token R1 and data block when reading data (see section 7.5.4)
* @note this is set to N_CR until we can read CSD (see section 7.2.6)
*/
static uint32_t n_ac = 8;
/** is it a Standard Capacity SD card (true), or High Capacity SD cards (false)
* @note this is indicated in the Card Capacity Status bit or OCR (set for high capacity)
* @note this is important for addressing: for standard capacity cards the address is the byte number, for high capacity cards it is the 512-byte block number
*/
static bool sdsc = false;
/** size of card in bytes */
static uint64_t sdcard_size = 0;
/** size of an erase block bytes */
static uint32_t erase_size = 0;
/** table for CRC-7 calculation for the command messages (see section 4.5)
* @note faster than calculating the CRC and doesn't cost a lot of space
* @note generated using pycrc --width=7 --poly=0x09 --reflect-in=false --reflect-out=false --xor-in=0x00 --xor-out=0x00 --generate=table
*/
static const uint8_t crc7_table[] = {
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
};
/** wait one SPI round (one SPI word)
*/
static void flash_sdcard_spi_wait(void)
{
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
}
/** read one SPI word
* @return SPI word read
*/
static uint16_t flash_sdcard_spi_read(void)
{
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
(void)SPI_DR(SPI(FLASH_SDCARD_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until Tx buffer is empty before clearing the (previous) RXNE flag
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_RXNE)); // wait for next data to be available
return SPI_DR(SPI(FLASH_SDCARD_SPI)); // return received adat
}
/** test if card is present
* @return if card has been detected
* @note this use the SD card detection mechanism (CD/CS is high card is inserted due to the internal 50 kOhm resistor)
*/
static bool flash_sdcard_card_detect(void)
{
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for NSS pin port peripheral for SD card CD signal
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS pin as input to read CD signal
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // pull pin low to avoid false positive when card in not inserted
return (0!=gpio_get(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI))); // read CD signal: is card is present the internal 50 kOhm pull-up resistor will override our 1 MOhm pull-down resistor and set the signal high (see section 6.2)
}
/** transmit command token
* @param[in] index command index
* @param[in] argument command argument
*/
static void flash_sdcard_send_command(uint8_t index, uint32_t argument)
{
uint8_t command[5] = { 0x40+(index&0x3f), argument>>24, argument>>16, argument>>8, argument>>0 }; // commands are 5 bytes long, plus 1 bytes of CRC (see section 7.3.1.1)
uint8_t crc7 = 0x00; // CRC-7 checksum for command message
// send command
for (uint8_t i=0; i<LENGTH(command); i++) {
spi_send(SPI(FLASH_SDCARD_SPI), command[i]); // send data
crc7 = (crc7_table[((crc7<<1)^command[i])])&0x7f; // update checksum
}
spi_send(SPI(FLASH_SDCARD_SPI), (crc7<<1)+0x01); // send CRC value (see section 7.3.1.1)
}
/** transmit command token and receive response token
* @param[in] index command index
* @param[in] argument command argument
* @param[out] response response data to read (if no error occurred)
* @param[in] size size of response to read
* @return response token R1 or 0xff if error occurred or card is not present
*/
static uint8_t flash_sdcard_command_response(uint8_t index, uint32_t argument, uint8_t* response, size_t size)
{
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.1.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
if (0x00==(r1&0xfe) && 0!=size && NULL!=response) { // we have to read a response
for (size_t i=0; i<size; i++) {
response[i] = flash_sdcard_spi_read(); // get byte
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r1;
}
/** read a data block
* @param[out] data data block to read (if no error occurred)
* @param[in] size size of response to read (a multiple of 2)
* @return 0 if succeeded, else control token (0xff for other errors)
*/
static uint8_t flash_sdcard_read_block(uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
uint8_t token = 0xff; // to save the control block token (see section 7.3.3)
for (uint32_t i=0; i<n_ac && token==0xff; i++) { // wait for N_AC before reading data block (see section 7.5.2.1)
token = flash_sdcard_spi_read(); // get control token (see section 7.3.3)
}
if (0==(token&0xf0)) { // data error token received (see section 7.3.3.3)
if (0==(token&0x0f)) { // unknown error
token = 0xff;
}
} else if (0xfe==token) { // start block token received (see section 7.3.3.2)
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// get block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
for (size_t i=0; i<size/2; i++) {
uint16_t word = flash_sdcard_spi_read(); // get word
data[i*2+0] = (word>>8); // save byte
data[i*2+1] = (word>>0); // save byte
}
flash_sdcard_spi_read(); // read CRC (the CRC after the data block should clear the computed CRC)
if (SPI_CRC_RXR(FLASH_SDCARD_SPI)) { // CRC is wrong
token = 0xff;
} else { // no error occurred
token = 0;
}
// switch back to 8-bit SPI frames
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
} else { // start block token not received
token = 0xff;
}
return token;
}
/** write a data block
* @param[in] data data block to write
* @param[in] size size of response to read (a multiple of 2)
* @return data response token (0xff for other errors)
*/
static uint8_t flash_sdcard_write_block(uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
spi_send(SPI(FLASH_SDCARD_SPI), 0xfe); // send start block token (see section 7.3.3.2)
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// send block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
for (size_t i=0; i<size/2; i++) {
uint16_t word = (data[i*2+0]<<8)+data[i*2+1]; // prepare SPI frame
spi_send(SPI(FLASH_SDCARD_SPI), word); // senf data frame
}
spi_set_next_tx_from_crc(SPI(FLASH_SDCARD_SPI)); // send CRC
// switch back to 8-bit SPI frames
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_next_tx_from_buffer(SPI(FLASH_SDCARD_SPI)); // don't send CRC
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
uint8_t token = 0xff;
while (0x01!=(token&0x11)) {
token = flash_sdcard_spi_read(); // get data response token (see section 7.3.3.1)
}
while (0==flash_sdcard_spi_read()); // wait N_EC while the card is busy programming the data
return token;
}
/** get card status
* @param[out] status SD status (512 bits)
* @return response token R2 or 0xffff if error occurred or card is not present
*/
static uint16_t flash_sdcard_status(uint8_t* status)
{
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
uint8_t r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
if ((r1&0xfe)) { // error occurred, not in idle state
return false;
}
// send ACMD13 command
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(13, 0); // send ACMD13 (SD_STATUS) (see table 7-4)
// get response token R2
uint16_t r2 = 0xffff; // response token R2 (see section 7.3.2.3)
for (uint8_t i=0; i<8 && r2&0x8000; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r2 = (flash_sdcard_spi_read()<<8); // get first byte of response (see section 7.3.2.1)
}
if (0==(r2&0x8000)) { // got the first byte
r2 += flash_sdcard_spi_read(); // read second byte (see 7.3.2.3)
}
// get data block
if (0==r2) { // no error
if (flash_sdcard_read_block(status, 64)) { // read 512 bits data block containing SD status
r2 |= (1<<11); // set communication error
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r2;
}
/** transmit command token, receive response token and data block
* @param[in] index command index
* @param[in] argument command argument
* @param[out] data data block to read (if no error occurred)
* @param[in] size size of data to read (a multiple of 2)
* @return response token R1 or 0xff if error occurred or card is not present
*/
static uint8_t flash_sdcard_data_read(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
// get data block
if (0x00==r1) { // we can read a data block
if (flash_sdcard_read_block(data, size)) { // read data block
r1 |= (1<<3); // set communication error
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r1;
}
/** transmit command token, receive response token and write data block
* @param[in] index command index
* @param[in] argument command argument
* @param[out] data data block to write
* @param[in] size size of data to write (a multiple of 2)
* @return data response token, or 0xff if error occurred or card is not present
* @note at the end of a write operation the SD status should be check to ensure no error occurred during programming
*/
static uint8_t flash_sdcard_data_write(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) write odd number of bytes
return 0xff;
}
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
// write data block
uint8_t drt = 0xff; // data response token (see section 7.3.3.1)
if (0x00==r1) { // we have to write the data block
drt = flash_sdcard_write_block(data, size); // write data block
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return drt;
}
bool flash_sdcard_setup(void)
{
// reset values
initialized = false;
n_ac = 8;
sdcard_size = 0;
erase_size = 0;
// check if card is present
if (!flash_sdcard_card_detect()) {
return false;
}
// configure SPI peripheral
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for clock signal
gpio_set_mode(SPI_SCK_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(FLASH_SDCARD_SPI)); // set SCK as output (clock speed will be negotiated later)
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MOSI signal
gpio_set_mode(SPI_MOSI_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(FLASH_SDCARD_SPI)); // set MOSI as output
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MISO signal
gpio_set_mode(SPI_MISO_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(FLASH_SDCARD_SPI)); // set MISO as input
gpio_set(SPI_MISO_PORT(FLASH_SDCARD_SPI), SPI_MISO_PIN(FLASH_SDCARD_SPI)); // pull pin high to detect when the card is not answering (or not present) since responses always start with MSb 0
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS (CS) as output
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(FLASH_SDCARD_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(FLASH_SDCARD_SPI)); // clear SPI values to default
spi_init_master(SPI(FLASH_SDCARD_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_256, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 256 (72E6/256=281 kHz) since maximum SD card clock frequency (fOD, see section 7.8/6.6.6) during initial card-identification mode is 400 kHz (maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (from SD card spec, polarity depends on clock phase), use 8 bits frames (as per spec), use MSb first
spi_set_full_duplex_mode(SPI(FLASH_SDCARD_SPI)); // ensure we are in full duplex mode
spi_enable_software_slave_management(SPI(FLASH_SDCARD_SPI)); // control NSS (CS) manually
spi_set_nss_high(SPI(FLASH_SDCARD_SPI)); // set NSS high (internally) so we can output
spi_disable_ss_output(SPI(FLASH_SDCARD_SPI)); // disable NSS output since we control CS manually
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// sadly we can't use the interrupts as events to sleep (WFE) since sleep disables also communication (e.g. going to sleep until Rx buffer is not empty prevents transmission)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI
// start card-identification (see section 7.2.1/4.2)
uint8_t r1 = 0;
// send CMD0 (GO_IDLE_START) to start the card identification (see section 7.2.1)
r1 = flash_sdcard_command_response(0, 0, NULL, 0); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
}
// send CMD8 (SEND_IF_COND) to inform about voltage (1: 2.7-3.6V, aa: recommended check pattern) (see section 7.2.1)
uint8_t r7[4] = {0}; // to store response toke R7 (see section 7.3.2.6)
r1 = flash_sdcard_command_response(8, 0x000001aa, r7, sizeof(r7)); // (see table 7-3)
if (0x01==r1) { // command supported, in idle state
if (!(r7[2]&0x1)) { // 2.7-3.6V not supported (see table 5-1)
return false;
} else if (0xaa!=r7[3]) { // recommended pattern not returned (see section 4.3.13)
return false;
}
} else if (0x05!=r1) { // illegal command (cards < physical spec v2.0 don't support CMD8) (see section 7.2.1)
return false;
}
// send CMD58 (READ_OCR) to read Operation Conditions Register (see section 7.2.1)
uint8_t r3[4] = {0}; // to store response token R3 (see section 7.3.2.4)
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
} else if (!(r3[1]&0x30)) { // 3.3V not supported (see table 5-1)
return false;
}
do {
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
}
// send ACMD41 (SD_SEND_OP_COND) with Host Capacity Support (0b: SDSC Only Host, 1b: SDHC or SDXC Supported) (see section 7.2.1)
r1 = flash_sdcard_command_response(41, 0x40000000, NULL, 0); // (see table 7-4)
if (r1&0xfe) { // error occurred
return false;
}
} while (0x00!=r1); // wait until card is ready (see section 7.2.1)
// send CMD58 (READ_OCR) to read Card Capacity Status (CCS) (see section 7.2.1)
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
if (r1) { // error occurred
return false;
}
// card power up status bit (bit 31) is set when power up is complete (see table 5-1)
if (0x00==(r3[0]&0x80)) {
return false;
}
sdsc = (0==(r3[0]&0x40)); // CCS is bit 30 in OCR (see table 5-1)
// now the card identification is complete and we should be in data-transfer mode (see figure 7-1)
// we can switch clock frequency to fPP (max. 25 MHz) (see section 4.3/6.6.6)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_4); // set clock speed to 18 MHz (72/4=18, < 25 MHz)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// send CMD9 (SEND_CSD) to get Card Specific Data (CSD) and calculate N_AC (see section 7.2.6)
uint8_t csd[16] = {0}; // CSD response (see chapter 7.2.6)
r1 = flash_sdcard_data_read(9, 0, csd, sizeof(csd)); // (see table 7-3)
if (r1) { // error occurred
return false;
}
// check if CSD structure version matches capacity (see section 5.3.1)
if ((sdsc && (csd[0]>>6)) || (!sdsc && 0==(csd[0]>>6))) {
return false;
}
// calculate N_AC value (we use our set minimum frequency 16 MHz to calculate time)
if (sdsc) { // calculate N_AC using TAAC and NSAC
static const float TAAC_UNITS[] = {1E-9, 10E-9, 100E-9, 1E-6, 10E-6, 100E-6, 1E-3, 10E-3}; // (see table 5-5)
static const float TAAC_VALUES[] = {10.0, 1.0, 1.2, 1.3, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0}; // (see table 5-5)
double taac = TAAC_VALUES[(csd[1]>>2)&0xf]*TAAC_UNITS[csd[1]&0x7]; // time in ns
n_ac=100*((taac*16E6)+(csd[2]*100))/8; // (see section 7.5.4)
} else { // value is fixed to 100 ms
n_ac=100E-3*16E6/8;
}
// calculate card size
if (sdsc) { // see section 5.3.2
uint16_t c_size = (((uint16_t)csd[6]&0x03)<<10)+((uint16_t)csd[7]<<2)+(csd[8]>>6);
uint8_t c_size_mutl = ((csd[9]&0x03)<<1)+((csd[10]&0x80)>>7);
uint8_t read_bl_len = (csd[5]&0x0f);
sdcard_size = ((c_size+1)*(1UL<<(c_size_mutl+2)))*(1UL<<read_bl_len);
} else { // see section 5.3.3
uint32_t c_size = ((uint32_t)(csd[7]&0x3f)<<16)+((uint16_t)csd[8]<<8)+csd[9];
sdcard_size = (c_size+1)*(512<<10);
}
// calculate erase size
if (sdsc) { // see section 5.3.2
erase_size = (((csd[10]&0x3f)<<1)+((csd[11]&0x80)>>7)+1)<<(((csd[12]&0x03)<<2)+(csd[13]>>6));
} else {
uint8_t status[64] = {0}; // SD status (see section 4.10.2)
uint16_t r2 = flash_sdcard_status(status); // get status (see table 7-4)
if (r2) { // error occurred
return false;
}
erase_size = (8192UL<<(status[10]>>4)); // calculate erase size (see table 4-44, section 4.10.2.4)
}
// ensure block length is 512 bytes for SDSC (should be per default) to we match SDHC/SDXC block size
if (sdsc) {
r1 = flash_sdcard_command_response(16, 512, NULL, 0); // set block size using CMD16 (SET_BLOCKLEN) (see table 7-3)
if (r1) { // error occurred
return false;
}
}
// try to switch to high speed mode (see section 7.2.14/4.3.10)
if (csd[4]&0x40) { // ensure CMD6 is supported by checking if command class 10 is set
uint32_t n_ac_back = n_ac; // backup N_AC
n_ac = 100E-3*16E6/8; // temporarily set timeout to 100 ms (see section 4.3.10.1)
// query access mode (group function 1) to check if high speed is supported (fPP=50MHz at 3.3V, we can be faster)
uint8_t fnc[64] = {0}; // function status response (see table 4-12)
r1 = flash_sdcard_data_read(6, 0x00fffff1, fnc, sizeof(fnc)); // check high speed function using CMD6 (SWITCH_FUNC) to check (mode 0) access mode (function group 1) (see table 7-3/4-30)
if (r1) { // error occurred
return false;
}
if (0x1==(fnc[16]&0x0f)) { // we can to access mode function 1 (see table 4-12)
r1 = flash_sdcard_data_read(6, 0x80fffff1, fnc, sizeof(fnc)); // switch to high speed function using CMD6 (SWITCH_FUNC) to switch (mode 1) access mode (function group 1) (see table 7-3/4-30)
if (r1) { // error occurred
return false;
}
if (0x1!=(fnc[16]&0x0f)) { // could not switch to high speed
return false;
}
// we can switch clock frequency to fPP (max. 50 MHz) (see section 6.6.7)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_2); // set clock speed to 36 MHz (72/2=36 < 50 MHz)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
n_ac_back /= 2; // since we go twice faster the N_AC timeout has to be halved
}
n_ac = n_ac_back; // restore N_AC
}
initialized = true;
return initialized;
}
uint64_t flash_sdcard_size(void)
{
return sdcard_size;
}
uint32_t flash_sdcard_erase_size(void