Compare commits
17 Commits
master
...
spark_stro
Author | SHA1 | Date | |
---|---|---|---|
cfdb9f8977 | |||
c6c9c7aed9 | |||
4c2b3e0516 | |||
102e1669d9 | |||
453d35079f | |||
e78e67c0e8 | |||
ba5ea0a6b2 | |||
e9078e831e | |||
e6a545cd0f | |||
ca4d41e79f | |||
4a96073898 | |||
a7aab104ba | |||
2f42659b18 | |||
b78af16939 | |||
4f7a173e23 | |||
10c2973ee7 | |||
0bfae07c30 |
67
README.md
67
README.md
@ -1,4 +1,4 @@
|
||||
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).
|
||||
This firmware is for the spark strober, using an [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
|
||||
|
||||
project
|
||||
=======
|
||||
@ -6,40 +6,63 @@ project
|
||||
summary
|
||||
-------
|
||||
|
||||
*describe project purpose*
|
||||
The spark strober switches mains electricity.
|
||||
This allows to switch on a flood light or stroboscope for a short time to create a light pulse.
|
||||
|
||||
technology
|
||||
----------
|
||||
|
||||
*described electronic details*
|
||||
components:
|
||||
|
||||
board
|
||||
=====
|
||||
|
||||
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
|
||||
|
||||
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
|
||||
- [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**.
|
||||
This is required to map the user LED and button provided on the board
|
||||
- [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6: development board to control everything
|
||||
- HLK-PM01 power module: 100-240V AC to 5V DC power supply to power the board and other peripherals (protected with a 0.25A glass fuse)
|
||||
- VS1838b: 38 kHz infrared demodulator to receive the codes from a remote control (connected using a 3.5 mm TRS jack)
|
||||
- G3MB-202P: compact 240V AC 2A solid state relay to switch mains electricity (protected with a 2A fuse)
|
||||
- infrared remote control sending NEC codes (low power version salvages from a multimedia player, high power version from Jedi iDual)
|
||||
|
||||
connections
|
||||
===========
|
||||
|
||||
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
|
||||
Hi-Link HLK-PM01:
|
||||
|
||||
- *list board to preipheral pin connections*
|
||||
- AC: 100-240V AC mains
|
||||
- +Vo: 5V
|
||||
- -Vo: ground
|
||||
|
||||
All pins are configured using `define`s in the corresponding source code.
|
||||
Omron G3MB-202P:
|
||||
|
||||
- 1: 100-240V AC mains line input
|
||||
- 2: 100-240V AC mains line output
|
||||
- 3: 5V with 330 Ohm resistor
|
||||
- 4: blue pill, PB6
|
||||
|
||||
VS1838b:
|
||||
|
||||
- 1, OUT: 3.5 mm TRS jack plug, ring
|
||||
- 2, GND: ground
|
||||
- 3, VCC: 5V (with 100 nF decoupling capacitor)
|
||||
|
||||
3.5 mm TRS jack socket:
|
||||
|
||||
- tip: 5V
|
||||
- ring: blue pill, PB8, TIM4_CH3
|
||||
- sleeve: ground
|
||||
|
||||
blue pill:
|
||||
|
||||
- PB8, TIM4_CH3: IR demodulator OUT
|
||||
- PB6: SSR control
|
||||
- 5V: 5V
|
||||
- GND: ground
|
||||
|
||||
code
|
||||
====
|
||||
|
||||
control
|
||||
-------
|
||||
|
||||
Have a look at the `application.c` *ir_action* to changes the AC on/off switch animations and corresponding remote control code.
|
||||
|
||||
dependencies
|
||||
------------
|
||||
|
||||
@ -86,7 +109,3 @@ USB
|
||||
---
|
||||
|
||||
The firmware offers serial communication over USART1 and USB (using the CDC ACM device class).
|
||||
|
||||
You can also reset the board by setting the serial width to 5 bits over USB.
|
||||
To reset the board run `rake reset`.
|
||||
This only works if provided USB CDC ACM is running correctly and the micro-controller isn't stuck.
|
||||
|
4
Rakefile
4
Rakefile
@ -15,7 +15,7 @@ FIRMWARES = [BOOTLOADER, APPLICATION]
|
||||
|
||||
# which development board is used
|
||||
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, CORE_BOARD, BUSVOODOO
|
||||
BOARD = ENV["BOARD"] || "CORE_BOARD"
|
||||
BOARD = ENV["BOARD"] || "BLUE_PILL"
|
||||
|
||||
# libopencm3 definitions
|
||||
LIBOPENCM3_DIR = "libopencm3"
|
||||
@ -183,7 +183,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
|
||||
|
269
application.c
269
application.c
@ -12,8 +12,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/** STM32F1 application example
|
||||
* @file application.c
|
||||
/** STM32F1 application to strobe electricity
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2018
|
||||
*/
|
||||
@ -43,8 +43,9 @@
|
||||
#include "usb_cdcacm.h" // USB CDC ACM utilities
|
||||
#include "terminal.h" // handle the terminal interface
|
||||
#include "menu.h" // menu utilities
|
||||
#include "ir_nec.h" // InfraRed NEC decoding utilities
|
||||
|
||||
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
|
||||
#define WATCHDOG_PERIOD 20000 /**< watchdog period in ms */
|
||||
|
||||
/** @defgroup main_flags flag set in interrupts to be processed in main task
|
||||
* @{
|
||||
@ -52,6 +53,9 @@
|
||||
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
|
||||
/** @} */
|
||||
|
||||
/** if the strobe output should not flicker */
|
||||
static bool flicker_off = true;
|
||||
|
||||
size_t putc(char c)
|
||||
{
|
||||
size_t length = 0; // number of characters printed
|
||||
@ -235,6 +239,196 @@ static void process_command(char* str)
|
||||
}
|
||||
}
|
||||
|
||||
#define STROBE_PORT B /**< GPIO port to control strobe light */
|
||||
#define STROBE_PIN 6 /**< GPIO pin to control strobe light */
|
||||
#define STROBE_ON 0 /**< LED is on when pin is low (open-drain allows 5V on) */
|
||||
|
||||
/* strobe animations (on + off times in ms) */
|
||||
static const uint16_t strobe1[] = {100, 0};
|
||||
static const uint16_t strobe2[] = {100, 100, 100, 0};
|
||||
static const uint16_t strobe3[] = {100, 100, 100, 100, 100, 0};
|
||||
static const uint16_t strobe5[] = {50, 50, 50, 50, 50, 50, 50, 50, 50, 0};
|
||||
static const uint16_t strobe10[] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
|
||||
|
||||
/** switch strobe power on */
|
||||
static void strobe_on(void)
|
||||
{
|
||||
#if STROBE_ON
|
||||
gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#else
|
||||
gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** switch strobe power off */
|
||||
static void strobe_off(void)
|
||||
{
|
||||
#if STROBE_ON
|
||||
gpio_clear(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#else
|
||||
gpio_set(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** toggle strobe power */
|
||||
static void strobe_toggle(void)
|
||||
{
|
||||
gpio_toggle(GPIO(STROBE_PORT), GPIO(STROBE_PIN));
|
||||
}
|
||||
|
||||
/** play strobe animation
|
||||
* @param[in] animation on+off timings (in ms)
|
||||
* @param[in] length animation length
|
||||
*/
|
||||
static void strobe_play(const uint16_t* animation, uint16_t length)
|
||||
{
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
iwdg_reset(); // kick the dog
|
||||
if (0 == animation[i]) { // skip animation
|
||||
continue;
|
||||
}
|
||||
if (i % 2) { // odd index if encodes off duration
|
||||
strobe_off();
|
||||
} else {
|
||||
strobe_on();
|
||||
}
|
||||
sleep_ms(animation[i]); // wait for set duration
|
||||
}
|
||||
strobe_off(); // switch off at the end
|
||||
}
|
||||
|
||||
/** perform IR code related action
|
||||
* @warning the codes need to be adjusted to your remote
|
||||
* @param[in] code IR code
|
||||
*/
|
||||
static void ir_action(const struct ir_nec_code_t* code)
|
||||
{
|
||||
if (code->repeat) { // don't handle long button press repeating the code
|
||||
return;
|
||||
}
|
||||
if (0x00f7 == code->address) { // flat LED IR remote
|
||||
switch (code->command) { // choose animation depending on button
|
||||
case 0x00: // UP
|
||||
strobe_toggle();
|
||||
printf("toggle strobe\n");
|
||||
break;
|
||||
case 0x80: // DOWN
|
||||
printf("start flickering\n");
|
||||
flicker_off = false; // let flickering happen in main loop
|
||||
break;
|
||||
case 0xc0: // ON
|
||||
strobe_on();
|
||||
printf("light on\n");
|
||||
break;
|
||||
case 0x40: // OFF
|
||||
strobe_off();
|
||||
flicker_off = true; // stop flickering
|
||||
printf("light off\n");
|
||||
break;
|
||||
case 0x20: // red
|
||||
case 0x10: // orange
|
||||
case 0x30: // orange
|
||||
case 0x08: // orange
|
||||
case 0x28: // organe
|
||||
printf("1 strobe\n");
|
||||
strobe_play(strobe1, LENGTH(strobe1));
|
||||
break;
|
||||
case 0xa0: // green
|
||||
case 0x90: // green
|
||||
case 0xb0: // green
|
||||
case 0x88: // green
|
||||
case 0xa8: // green
|
||||
printf("2 strobes\n");
|
||||
strobe_play(strobe2, LENGTH(strobe2));
|
||||
break;
|
||||
case 0x60: // blue
|
||||
case 0x50: // blue
|
||||
case 0x70: // blue
|
||||
case 0x48: // blue
|
||||
case 0x68: // blue
|
||||
printf("3 strobes\n");
|
||||
strobe_play(strobe3, LENGTH(strobe3));
|
||||
break;
|
||||
case 0xe0: // W
|
||||
case 0xd0: // flash
|
||||
case 0xc8: // fade
|
||||
case 0xf0: // strobe
|
||||
printf("5 strobes\n");
|
||||
strobe_play(strobe5, LENGTH(strobe5));
|
||||
break;
|
||||
case 0xe8: // smooth
|
||||
printf("10 strobes\n");
|
||||
strobe_play(strobe10, LENGTH(strobe10));
|
||||
break;
|
||||
default:
|
||||
printf("unknown code\n");
|
||||
break;
|
||||
}
|
||||
} else if (0x407f == code->address) { // iDual remote
|
||||
switch (code->command) { // choose animation depending on button
|
||||
case 0x08: // brightness down
|
||||
strobe_toggle();
|
||||
printf("toggle strobe\n");
|
||||
break;
|
||||
case 0x90: // brightness up
|
||||
printf("start flickering\n");
|
||||
flicker_off = false; // let flickering happen in main loop
|
||||
break;
|
||||
case 0x80: // ON
|
||||
strobe_on();
|
||||
printf("light on\n");
|
||||
break;
|
||||
case 0x40: // OFF
|
||||
strobe_off();
|
||||
flicker_off = true; // stop flickering
|
||||
printf("light off\n");
|
||||
break;
|
||||
case 0x88: // left
|
||||
case 0x48:
|
||||
case 0xc8:
|
||||
case 0x28:
|
||||
printf("1 strobe\n");
|
||||
strobe_play(strobe1, LENGTH(strobe1));
|
||||
break;
|
||||
case 0x68: // middle-left
|
||||
case 0xe8:
|
||||
case 0x18:
|
||||
case 0x98:
|
||||
printf("2 strobes\n");
|
||||
strobe_play(strobe2, LENGTH(strobe2));
|
||||
break;
|
||||
case 0x50: // middle-right
|
||||
case 0xd0:
|
||||
case 0x30:
|
||||
case 0xb0:
|
||||
printf("3 strobes\n");
|
||||
strobe_play(strobe3, LENGTH(strobe3));
|
||||
break;
|
||||
case 0xc0: // right
|
||||
case 0x20:
|
||||
case 0xa0:
|
||||
case 0x60:
|
||||
printf("5 strobes\n");
|
||||
strobe_play(strobe5, LENGTH(strobe5));
|
||||
break;
|
||||
case 0x70: // circle
|
||||
case 0xa8:
|
||||
case 0x58:
|
||||
case 0xf0:
|
||||
case 0xe0:
|
||||
case 0x10:
|
||||
printf("10 strobes\n");
|
||||
strobe_play(strobe10, LENGTH(strobe10));
|
||||
break;
|
||||
default:
|
||||
printf("unknown code\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printf("unknown remote\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** program entry point
|
||||
* this is the firmware function started by the micro-controller
|
||||
*/
|
||||
@ -243,6 +437,27 @@ void main(void)
|
||||
{
|
||||
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
|
||||
|
||||
board_setup(); // setup board
|
||||
uart_setup(); // setup USART (for printing)
|
||||
usb_cdcacm_setup(); // setup USB CDC ACM (for printing and DFU)
|
||||
// setup strobe pin
|
||||
rcc_periph_clock_enable(RCC_GPIO(STROBE_PORT)); // enable clock for GPIO port peripheral
|
||||
#if STROBE_ON
|
||||
gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(STROBE_PIN)); // set pin to output push-pull do drive strobe signal
|
||||
#else
|
||||
gpio_set_mode(GPIO(STROBE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(STROBE_PIN)); // set pin to output open-drain do enable strobe
|
||||
#endif
|
||||
strobe_off(); // switch off strobe per defaulf
|
||||
ir_nec_setup(true); // setup ID NEC code decoder
|
||||
printf("\nwelcome to the CuVoodoo STM32F1 spark strober\n"); // print welcome message
|
||||
|
||||
// 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_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
|
||||
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
|
||||
printf("OK\n");
|
||||
|
||||
#if DEBUG
|
||||
// enable functionalities for easier debug
|
||||
DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
|
||||
@ -256,11 +471,6 @@ void main(void)
|
||||
iwdg_start(); // start independent watchdog
|
||||
#endif
|
||||
|
||||
board_setup(); // setup board
|
||||
uart_setup(); // setup USART (for printing)
|
||||
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
|
||||
printf("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
|
||||
|
||||
#if !(DEBUG)
|
||||
// show watchdog information
|
||||
printf("setup watchdog: %.2fs",WATCHDOG_PERIOD/1000.0);
|
||||
@ -273,12 +483,6 @@ void main(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
|
||||
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
|
||||
printf("OK\n");
|
||||
|
||||
// setup terminal
|
||||
terminal_prefix = ""; // set default prefix
|
||||
@ -288,15 +492,16 @@ void main(void)
|
||||
// start main loop
|
||||
bool action = false; // if an action has been performed don't go to sleep
|
||||
button_flag = false; // reset button flag
|
||||
bool flicker_on = false; // if the flicker strobe is currently on
|
||||
while (true) { // infinite loop
|
||||
iwdg_reset(); // kick the dog
|
||||
while (user_input_available) { // user input is available
|
||||
if (user_input_available) { // user input is available
|
||||
action = true; // action has been performed
|
||||
led_toggle(); // toggle LED
|
||||
char c = user_input_get(); // store receive character
|
||||
terminal_send(c); // send received character to terminal
|
||||
}
|
||||
while (button_flag) { // user pressed button
|
||||
if (button_flag) { // user pressed button
|
||||
action = true; // action has been performed
|
||||
printf("button pressed\n");
|
||||
led_toggle(); // toggle LED
|
||||
@ -305,13 +510,41 @@ void main(void)
|
||||
}
|
||||
button_flag = false; // reset flag
|
||||
}
|
||||
while (rtc_internal_tick_flag) { // the internal RTC ticked
|
||||
if (rtc_internal_tick_flag) { // the internal RTC ticked
|
||||
rtc_internal_tick_flag = false; // reset flag
|
||||
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)
|
||||
//led_toggle(); // toggle LED (good to indicate if main function is stuck)
|
||||
#endif
|
||||
}
|
||||
if (ir_nec_code_received_flag) { // IR code received
|
||||
ir_nec_code_received_flag = false; // reset flag
|
||||
led_on(); // notify user we received a code
|
||||
printf("IR NEC code received: addr=%+04x, cmd=%+02x%s\n", ir_nec_code_received.address, ir_nec_code_received.command, ir_nec_code_received.repeat ? " (repeat)" : "");
|
||||
if (!ir_nec_code_received.repeat) { // ignore repeated codes
|
||||
ir_action(&ir_nec_code_received); // handle IR code
|
||||
}
|
||||
led_off(); // notify user we received a code
|
||||
}
|
||||
if (!flicker_off) {
|
||||
action = true; // prevent going to sleep
|
||||
uint32_t time = rand();
|
||||
if (flicker_on) {
|
||||
time %= 1000;
|
||||
if (time < 100) {
|
||||
time = 100;
|
||||
}
|
||||
strobe_off();
|
||||
} else {
|
||||
time %= 100;
|
||||
if (time < 10) {
|
||||
time = 10;
|
||||
}
|
||||
strobe_on();
|
||||
}
|
||||
sleep_ms(time);
|
||||
flicker_on = !flicker_on;
|
||||
}
|
||||
if (action) { // go to sleep if nothing had to be done, else recheck for activity
|
||||
action = false;
|
||||
} else {
|
||||
|
2
global.h
2
global.h
@ -20,7 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
/** enable debugging functionalities */
|
||||
#define DEBUG true
|
||||
#define DEBUG false
|
||||
|
||||
/** get the length of an array */
|
||||
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
@ -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)
|
||||
{
|
||||
return erase_size;
|
||||
}
|
||||
|
||||
bool flash_sdcard_read_data(uint32_t block, uint8_t* data)
|
||||
{
|
||||
if (NULL==data) {
|
||||
return false;
|
||||
}
|
||||
if (sdsc) { // the address for standard capacity cards must be provided in bytes
|
||||
if (block>UINT32_MAX/512) { // check for integer overflow
|
||||
return false;
|
||||
} else {
|
||||
block *= 512; // calculate byte address from block address
|
||||
}
|
||||
}
|
||||
return (0==flash_sdcard_data_read(17, block, data, 512)); // read single data block using CMD17 (READ_SINGLE_BLOCK) (see table 7-3)
|
||||
}
|
||||
|
||||
bool flash_sdcard_write_data(uint32_t block, uint8_t* data)
|
||||
{
|
||||
if (NULL==data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sdsc) { // the address for standard capacity cards must be provided in bytes
|
||||
if (block>UINT32_MAX/512) { // check for integer overflow
|
||||
return false;
|
||||
} else {
|
||||
block *= 512; // calculate byte address from block address
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t drt = flash_sdcard_data_write(24, block, data, 512); // write single data block using CMD24 (WRITE_SINGLE_BLOCK) (see table 7-3)
|
||||
if (0x05!=(drt&0x1f)) { // write block failed
|
||||
return false;
|
||||
}
|
||||
|
||||
// get status to check if programming succeeded
|
||||
uint8_t r2[1] = {0}; // to store response token R2 (see section 7.3.2.3)
|
||||
uint8_t r1 = flash_sdcard_command_response(13, 0, r2, sizeof(r2)); // get SD status using CMD13 (SEND_STATUS) (see table 7-3)
|
||||
if (0x00!=r1) { // error occurred
|
||||
return false;
|
||||
} else if (r2[0]) { // programming error
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // programming succeeded
|
||||
}
|
@ -1,47 +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 (API)
|
||||
* @file flash_sdcard.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: SPI @ref flash_sdcard_spi
|
||||
* @warning all calls are blocking
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup communication with SD card
|
||||
* @return if card has been initialized correctly
|
||||
*/
|
||||
bool flash_sdcard_setup(void);
|
||||
/** get size of SD card flash memory
|
||||
* @return size of SD card flash memory (in bytes)
|
||||
*/
|
||||
uint64_t flash_sdcard_size(void);
|
||||
/** get size of a erase block
|
||||
* @return size of a erase block (in bytes)
|
||||
*/
|
||||
uint32_t flash_sdcard_erase_size(void);
|
||||
/** read data on flash of SD card
|
||||
* @param[in] block address of data to read (in block in 512 bytes unit)
|
||||
* @param[out] data data block to read (with a size of 512 bytes)
|
||||
* @return if read succeeded
|
||||
*/
|
||||
bool flash_sdcard_read_data(uint32_t block, uint8_t* data);
|
||||
/** write data on flash of SD card
|
||||
* @param[in] block address of data to write (in block in 512 bytes unit)
|
||||
* @param[in] data data block to write (with a size of 512 bytes)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool flash_sdcard_write_data(uint32_t block, uint8_t* data);
|
633
lib/i2c_master.c
633
lib/i2c_master.c
@ -1,633 +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 using I2C as master (code)
|
||||
* @file i2c_master.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017-2018
|
||||
* @note peripherals used: I2C
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/i2c.h> // I2C library
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // global utilities
|
||||
#include "i2c_master.h" // I2C header and definitions
|
||||
|
||||
/** get RCC for I2C based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC address for I2C peripheral
|
||||
*/
|
||||
static uint32_t RCC_I2C(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
return RCC_I2C1;
|
||||
break;
|
||||
case I2C2:
|
||||
return RCC_I2C2;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get RCC for GPIO port for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC GPIO address
|
||||
*/
|
||||
static uint32_t RCC_GPIO_PORT_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
case I2C2:
|
||||
return RCC_GPIOB;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get RCC for GPIO port for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return RCC GPIO address
|
||||
*/
|
||||
static uint32_t RCC_GPIO_PORT_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
case I2C2:
|
||||
return RCC_GPIOB;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO port for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PORT_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_BANK_I2C1_RE_SCL;
|
||||
} else {
|
||||
return GPIO_BANK_I2C1_SCL;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_BANK_I2C2_SCL;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO port for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PORT_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_BANK_I2C1_RE_SDA;
|
||||
} else {
|
||||
return GPIO_BANK_I2C1_SDA;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_BANK_I2C2_SDA;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO pin for SCL pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PIN_SCL(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_I2C1_RE_SCL;
|
||||
} else {
|
||||
return GPIO_I2C1_SCL;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_I2C2_SCL;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
/** get GPIO pin for SDA pin based on I2C identifier
|
||||
* @param[in] i2c I2C base address
|
||||
* @return GPIO address
|
||||
*/
|
||||
static uint32_t GPIO_PIN_SDA(uint32_t i2c)
|
||||
{
|
||||
switch (i2c) {
|
||||
case I2C1:
|
||||
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
|
||||
return GPIO_I2C1_RE_SDA;
|
||||
} else {
|
||||
return GPIO_I2C1_SDA;
|
||||
}
|
||||
break;
|
||||
case I2C2:
|
||||
return GPIO_I2C2_SDA;
|
||||
break;
|
||||
default:
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_master_setup(uint32_t i2c, uint16_t frequency)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// configure I2C peripheral
|
||||
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I2C I/O peripheral
|
||||
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // already put signal high to avoid small pulse
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // setup I2C I/O pins
|
||||
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I2C I/O peripheral
|
||||
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // already put signal high to avoid small pulse
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I2C I/O pins
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
|
||||
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I2C peripheral
|
||||
i2c_reset(i2c); // reset peripheral domain
|
||||
i2c_peripheral_disable(i2c); // I2C needs to be disable to be configured
|
||||
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset peripheral
|
||||
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // clear peripheral reset
|
||||
if (0==frequency) { // don't allow null frequency
|
||||
frequency = 1;
|
||||
} else if (frequency>400) { // limit frequency to 400 kHz
|
||||
frequency = 400;
|
||||
}
|
||||
i2c_set_clock_frequency(i2c, rcc_apb1_frequency/1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
|
||||
if (frequency>100) { // use fast mode for frequencies over 100 kHz
|
||||
i2c_set_fast_mode(i2c); // set fast mode (Fm)
|
||||
i2c_set_ccr(i2c, rcc_apb1_frequency/(frequency*1000*2)); // set Thigh/Tlow to generate frequency (fast duty not used)
|
||||
i2c_set_trise(i2c, (300/(1000/(rcc_apb1_frequency/1000000)))+1); // max rise time for Fm mode (< 400) kHz is 300 ns
|
||||
} else { // use fast mode for frequencies below 100 kHz
|
||||
i2c_set_standard_mode(i2c); // set standard mode (Sm)
|
||||
i2c_set_ccr(i2c, rcc_apb1_frequency/(frequency*1000*2)); // set Thigh/Tlow to generate frequency of 100 kHz
|
||||
i2c_set_trise(i2c, (1000/(1000/(rcc_apb1_frequency/1000000)))+1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
|
||||
}
|
||||
i2c_peripheral_enable(i2c); // enable I2C after configuration completed
|
||||
}
|
||||
|
||||
void i2c_master_release(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
i2c_reset(i2c); // reset I2C peripheral configuration
|
||||
i2c_peripheral_disable(i2c); // disable I2C peripheral
|
||||
rcc_periph_clock_disable(RCC_I2C(i2c)); // disable clock for I2C peripheral
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SCL(i2c)); // put I2C I/O pins back to floating
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SDA(i2c)); // put I2C I/O pins back to floating
|
||||
}
|
||||
|
||||
bool i2c_master_check_signals(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// pull SDA and SDC low to check if there are pull-up resistors
|
||||
uint32_t sda_crl = GPIO_CRL(GPIO_PORT_SDA(i2c)); // backup port configuration
|
||||
uint32_t sda_crh = GPIO_CRH(GPIO_PORT_SDA(i2c)); // backup port configuration
|
||||
uint32_t sda_bsrr = GPIO_BSRR(GPIO_PORT_SDA(i2c)); // backup port configuration
|
||||
uint32_t scl_crl = GPIO_CRL(GPIO_PORT_SCL(i2c)); // backup port configuration
|
||||
uint32_t scl_crh = GPIO_CRH(GPIO_PORT_SCL(i2c)); // backup port configuration
|
||||
uint32_t scl_bsrr = GPIO_BSRR(GPIO_PORT_SCL(i2c)); // backup port configuration
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SDA(i2c)); // configure signal as pull down
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SCL(i2c)); // configure signal as pull down
|
||||
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // pull down
|
||||
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // pull down
|
||||
bool to_return = (0!=gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) && 0!=gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // check if the signals are still pulled high by external stronger pull-up resistors
|
||||
GPIO_CRL(GPIO_PORT_SDA(i2c)) = sda_crl; // restore port configuration
|
||||
GPIO_CRH(GPIO_PORT_SDA(i2c)) = sda_crh; // restore port configuration
|
||||
GPIO_BSRR(GPIO_PORT_SDA(i2c)) = sda_bsrr; // restore port configuration
|
||||
GPIO_CRL(GPIO_PORT_SCL(i2c)) = scl_crl; // restore port configuration
|
||||
GPIO_CRH(GPIO_PORT_SCL(i2c)) = scl_crh; // restore port configuration
|
||||
GPIO_BSRR(GPIO_PORT_SCL(i2c)) = scl_bsrr; // restore port configuration
|
||||
|
||||
return to_return;
|
||||
}
|
||||
|
||||
void i2c_master_reset(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// follow procedure described in STM32F10xxC/D/E Errata sheet, Section 2.14.7
|
||||
i2c_peripheral_disable(i2c); // disable i2c peripheral
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SCL(i2c)); // put I2C I/O pins to general output
|
||||
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high
|
||||
while (!gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c))); // ensure it is high
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SDA(i2c)); // put I2C I/O pins to general output
|
||||
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high
|
||||
while (!gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // ensure it is high
|
||||
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set low (try first transition)
|
||||
while (gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // ensure it is low
|
||||
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set low (try first transition)
|
||||
while (gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c))); // ensure it is low
|
||||
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high (try second transition)
|
||||
while (!gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c))); // ensure it is high
|
||||
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high (try second transition)
|
||||
while (!gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // ensure it is high
|
||||
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // set I2C I/O pins back
|
||||
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I2C I/O pins back
|
||||
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset device
|
||||
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // reset device
|
||||
i2c_peripheral_enable(i2c); // re-enable device
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_start(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// send (re-)start condition
|
||||
if (I2C_CR1(i2c) & (I2C_CR1_START|I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
|
||||
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
|
||||
}
|
||||
i2c_send_start(i2c); // send start condition to start transaction
|
||||
while ((I2C_CR1(i2c) & I2C_CR1_START) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until start condition has been accepted and cleared
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
while (!(I2C_SR1(i2c) & I2C_SR1_SB) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until start condition is transmitted
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_MSL)) { // verify if in master mode
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool address_10bit, bool write)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I2C return codes
|
||||
if (!(I2C_SR1(i2c) & I2C_SR1_SB)) { // start condition has not been sent
|
||||
rc = i2c_master_start(i2c); // send start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_MSL)) { // I2C device is not in master mode
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
|
||||
// select slave
|
||||
if (!address_10bit) { // 7-bit address
|
||||
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
|
||||
i2c_send_7bit_address(i2c, slave, write ? I2C_WRITE : I2C_READ); // select slave, with read/write flag
|
||||
while (!(I2C_SR1(i2c) & (I2C_SR1_ADDR|I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until address is transmitted
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
}
|
||||
} else { // 10-bit address
|
||||
// send first part of address
|
||||
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
|
||||
I2C_DR(i2c) = 11110000 | (((slave>>8)&0x3)<<1); // send first header (11110xx0, where xx are 2 MSb of slave address)
|
||||
while (!(I2C_SR1(i2c) & (I2C_SR1_ADD10|I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until first part of address is transmitted
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
}
|
||||
// send second part of address
|
||||
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
|
||||
I2C_DR(i2c) = (slave&0xff); // send remaining of address
|
||||
while (!(I2C_SR1(i2c) & (I2C_SR1_ADDR|I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
}
|
||||
// go into receive mode if necessary
|
||||
if (!write) {
|
||||
rc = i2c_master_start(i2c); // send start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
// send first part of address with receive flag
|
||||
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
|
||||
I2C_DR(i2c) = 11110001 | (((slave>>8)&0x3)<<1); // send header (11110xx1, where xx are 2 MSb of slave address)
|
||||
while (!(I2C_SR1(i2c) & (I2C_SR1_ADDR|I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (write) {
|
||||
if (!((I2C_SR2(i2c) & I2C_SR2_TRA))) { // verify we are in transmit mode (and read SR2 to clear ADDR)
|
||||
return I2C_MASTER_RC_NOT_TRANSMIT;
|
||||
}
|
||||
} else {
|
||||
if ((I2C_SR2(i2c) & I2C_SR2_TRA)) { // verify we are in read mode (and read SR2 to clear ADDR)
|
||||
return I2C_MASTER_RC_NOT_RECEIVE;
|
||||
}
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (data==NULL || data_size==0) { // no data to read
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_MSL)) { // I2C device is not master
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
if ((I2C_SR2(i2c) & I2C_SR2_TRA)) { // I2C device not in receiver mode
|
||||
return I2C_MASTER_RC_NOT_RECEIVE;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // check if the previous transaction went well
|
||||
return I2C_MASTER_RC_NOT_READY;
|
||||
}
|
||||
|
||||
// read data
|
||||
for (size_t i=0; i<data_size; i++) { // read bytes
|
||||
if (i==data_size-1) { // prepare to sent NACK for last byte
|
||||
i2c_disable_ack(i2c); // NACK received to stop slave transmission
|
||||
} else {
|
||||
i2c_enable_ack(i2c); // ACK received byte to continue slave transmission
|
||||
}
|
||||
while (!(I2C_SR1(i2c) & I2C_SR1_RxNE) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until byte has been received
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
data[i] = i2c_get_data(i2c); // read received byte
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (data==NULL || data_size==0) { // no data to write
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_MSL)) { // I2C device is not master
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_TRA)) { // I2C device not in transmitter mode
|
||||
return I2C_MASTER_RC_NOT_TRANSMIT;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // check if the previous transaction went well
|
||||
return I2C_MASTER_RC_NOT_READY;
|
||||
}
|
||||
|
||||
// write data
|
||||
for (size_t i=0; i<data_size; i++) { // write bytes
|
||||
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
|
||||
i2c_send_data(i2c, data[i]); // send byte to be written in memory
|
||||
while (!(I2C_SR1(i2c) & (I2C_SR1_TxE|I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until byte has been transmitted
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_SR1(i2c) & I2C_SR1_AF) { // data has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
}
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_stop(uint32_t i2c)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (!(I2C_SR2(i2c) & I2C_SR2_BUSY)) { // release is not busy
|
||||
return I2C_MASTER_RC_NONE; // bus has probably already been released
|
||||
}
|
||||
// send stop condition
|
||||
if (I2C_CR1(i2c) & (I2C_CR1_START|I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
|
||||
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
|
||||
}
|
||||
|
||||
i2c_send_stop(i2c); // send stop to release bus
|
||||
while ((I2C_CR1(i2c) & I2C_CR1_STOP) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until stop condition is accepted and cleared
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
while ((I2C_SR2(i2c) & I2C_SR2_MSL) && !(I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO))); // wait until bus released (non master mode)
|
||||
if (I2C_SR1(i2c) & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I2C return codes
|
||||
rc = i2c_master_start(i2c); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(i2c, slave, address_10bit, false); // select slave to read
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
if (NULL!=data && data_size>0) { // only read data if needed
|
||||
rc = i2c_master_read(i2c, data, data_size);
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
i2c_master_stop(i2c); // sent stop condition
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I2C return codes
|
||||
rc = i2c_master_start(i2c); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
if (NULL!=data && data_size>0) { // write data only is some is available
|
||||
rc = i2c_master_write(i2c, data, data_size); // write data
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
i2c_master_stop(i2c); // sent stop condition
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I2C return codes
|
||||
rc = i2c_master_start(i2c); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// write address
|
||||
if (NULL!=address && address_size>0) {
|
||||
rc = i2c_master_write(i2c, address, address_size); // send memory address
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
// read data
|
||||
if (NULL!=data && data_size>0) {
|
||||
rc = i2c_master_start(i2c); // send re-start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(i2c, slave, address_10bit, false); // select slave to read
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
rc = i2c_master_read(i2c, data, data_size); // read memory
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
i2c_master_stop(i2c); // sent stop condition
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size)
|
||||
{
|
||||
// check I2C peripheral
|
||||
if (I2C1!=i2c && I2C2!=i2c) {
|
||||
while (true);
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I2C return codes
|
||||
rc = i2c_master_start(i2c); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// write address
|
||||
if (NULL!=address && address_size>0) {
|
||||
rc = i2c_master_write(i2c, address, address_size); // send memory address
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
// read data
|
||||
if (NULL!=data && data_size>0) {
|
||||
rc = i2c_master_write(i2c, data, data_size); // write memory
|
||||
if (I2C_MASTER_RC_NONE!=rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
i2c_master_stop(i2c); // sent stop condition
|
||||
return rc;
|
||||
}
|
132
lib/i2c_master.h
132
lib/i2c_master.h
@ -1,132 +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 using I2C as master (API)
|
||||
* @file i2c_master.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017-2018
|
||||
* @note peripherals used: I2C
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** I2C return codes */
|
||||
enum i2c_master_rc {
|
||||
I2C_MASTER_RC_NONE = 0, /**< no error */
|
||||
I2C_MASTER_RC_START_STOP_IN_PROGESS, /**< a start or stop condition is already in progress */
|
||||
I2C_MASTER_RC_NOT_MASTER, /**< not in master mode */
|
||||
I2C_MASTER_RC_NOT_TRANSMIT, /**< not in transmit mode */
|
||||
I2C_MASTER_RC_NOT_RECEIVE, /**< not in receive mode */
|
||||
I2C_MASTER_RC_NOT_READY, /**< slave is not read (previous operations has been nacked) */
|
||||
I2C_MASTER_RC_NAK, /**< not acknowledge received */
|
||||
I2C_MASTER_RC_BUS_ERROR, /**< an error on the I2C bus occurred */
|
||||
};
|
||||
|
||||
/** setup I2C peripheral
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] frequency frequency to use in kHz (1-400)
|
||||
* @note Standard mode (Sm) is used for frequencies up to 100 kHz, and Fast mode (Fm) is used for frequencies up to 400 kHz
|
||||
*/
|
||||
void i2c_master_setup(uint32_t i2c, uint16_t frequency);
|
||||
/** release I2C peripheral
|
||||
* @param[in] i2c I2C base address
|
||||
*/
|
||||
void i2c_master_release(uint32_t i2c);
|
||||
/** reset I2C peripheral, fixing any locked state
|
||||
* @warning the I2C peripheral needs to be re-setup
|
||||
* @note to be used after failed start or stop, and bus error
|
||||
* @param[in] i2c I2C base address
|
||||
*/
|
||||
void i2c_master_reset(uint32_t i2c);
|
||||
/** check if SDA and SCL signals are high
|
||||
* @param[in] i2c I2C base address
|
||||
* @return SDA and SCL signals are high
|
||||
*/
|
||||
bool i2c_master_check_signals(uint32_t i2c);
|
||||
/** send start condition
|
||||
* @param[in] i2c I2C base address
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_start(uint32_t i2c);
|
||||
/** select I2C slave device
|
||||
* @warning a start condition should be sent before this operation
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] slave I2C address of slave device to select
|
||||
* @param[in] address_10bit if the I2C slave address is 10 bits wide
|
||||
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool address_10bit, bool write);
|
||||
/** read data over I2C
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size);
|
||||
/** write data over I2C
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t data_size);
|
||||
/** sent stop condition
|
||||
* @param[in] i2c I2C base address
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_stop(uint32_t i2c);
|
||||
/** read data from slave device
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] slave I2C address of slave device to select
|
||||
* @param[in] address_10bit if the I2C slave address is 10 bits wide
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size);
|
||||
/** write data to slave device
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] slave I2C address of slave device to select
|
||||
* @param[in] address_10bit if the I2C slave address is 10 bits wide
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size);
|
||||
/** read data at specific address from an I2C memory slave
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] slave I2C address of slave device to select
|
||||
* @param[in] address_10bit if the I2C slave address is 10 bits wide
|
||||
* @param[in] address memory address of slave to read from
|
||||
* @param[in] address_size address size in bytes
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size);
|
||||
/** write data at specific address on an I2C memory slave
|
||||
* @param[in] i2c I2C base address
|
||||
* @param[in] slave I2C address of slave device to select
|
||||
* @param[in] address_10bit if the I2C slave address is 10 bits wide
|
||||
* @param[in] address memory address of slave to write to
|
||||
* @param[in] address_size address size in bytes
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size);
|
202
lib/ir_nec.c
Normal file
202
lib/ir_nec.c
Normal file
@ -0,0 +1,202 @@
|
||||
/* 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 decode InfraRed NEC code
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: timer channel @ref ir_nec_timer
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // standard boolean type
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer utilities
|
||||
|
||||
/* own libraries */
|
||||
#include "ir_nec.h" // own definitions
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup ir_nec_timer timer peripheral used to measure signal timing for code decoding
|
||||
* @{
|
||||
*/
|
||||
#define IR_NEC_TIMER 4 /**< timer peripheral */
|
||||
#define IR_NEC_CHANNEL 3 /**< channel used as input capture */
|
||||
#define IR_NEC_JITTER 40 /**< signal timing jitter in % tolerated in timing */
|
||||
/** @} */
|
||||
|
||||
volatile bool ir_nec_code_received_flag = false;
|
||||
struct ir_nec_code_t ir_nec_code_received;
|
||||
|
||||
/** if the extended address in the code is used
|
||||
* the extended address uses all 16-bits instead of having redundant/robust 2x8-bits address
|
||||
*/
|
||||
static bool ir_nec_extended = false;
|
||||
|
||||
void ir_nec_setup(bool extended)
|
||||
{
|
||||
ir_nec_extended = extended; // remember setting
|
||||
|
||||
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
|
||||
rcc_periph_clock_enable(RCC_TIM_CH(IR_NEC_TIMER, IR_NEC_CHANNEL)); // enable clock for GPIO peripheral
|
||||
rcc_periph_clock_enable(RCC_TIM(IR_NEC_TIMER)); // enable clock for timer peripheral
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternative functions
|
||||
gpio_set(TIM_CH_PORT(IR_NEC_TIMER, IR_NEC_CHANNEL), TIM_CH_PIN(IR_NEC_TIMER, IR_NEC_CHANNEL)); // idle is high (using pull-up resistor)
|
||||
gpio_set_mode(TIM_CH_PORT(IR_NEC_TIMER, IR_NEC_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, TIM_CH_PIN(IR_NEC_TIMER, IR_NEC_CHANNEL)); // setup GPIO pin as input
|
||||
timer_reset(TIM(IR_NEC_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(IR_NEC_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
|
||||
// codes are repeated every 110 ms, thus we need to measure at least this duration to detect repeats correctly
|
||||
// the 16-bit timer is by far precise enough to measure the smallest 560 us burst
|
||||
timer_set_prescaler(TIM(IR_NEC_TIMER), (110 * (100 + IR_NEC_JITTER) / 100 * (rcc_ahb_frequency / 1000) / (1 << 16)) + 1 - 1); // set the prescaler so this 16 bits timer allows to wait for 110 ms (+ jitter) from the start signal
|
||||
timer_ic_set_input(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_IN_TI(IR_NEC_CHANNEL)); // configure ICx to use TIn
|
||||
timer_ic_set_filter(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_CK_INT_N_8); // use small filter (noise reduction is more important than timing)
|
||||
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // capture on falling edge (IR bursts are active low on IR demodulators)
|
||||
timer_ic_set_prescaler(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
|
||||
|
||||
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF); // clear flag
|
||||
timer_update_on_overflow(TIM(IR_NEC_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(IR_NEC_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
|
||||
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_CCIF(IR_NEC_CHANNEL)); // clear input compare flag
|
||||
timer_ic_enable(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL)); // enable capture interrupt only when IR burst
|
||||
timer_enable_irq(TIM(IR_NEC_TIMER), TIM_DIER_CCIE(IR_NEC_CHANNEL)); // enable capture interrupt
|
||||
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(IR_NEC_TIMER)); // catch interrupt in service routine
|
||||
timer_enable_counter(TIM(IR_NEC_TIMER)); // enable timer
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer
|
||||
*
|
||||
* @remark normally we want to keep the ISR as short as possible, and do the processing in the main loop,
|
||||
* @remark but because the code needs to be decoded in order to detect repeat burst correctly,
|
||||
* @remark we do the decoding in the ISR and don't trust the user to run the decoding within 42.42 ms (time until the next code is sent)
|
||||
*
|
||||
* @note we don't enforce 110 ms between codes (they can be received earlier), but recognize repeat code after 110 ms
|
||||
*/
|
||||
void TIM_ISR(IR_NEC_TIMER)(void)
|
||||
{
|
||||
static uint8_t burst_count = 0; // the mark or space count
|
||||
static uint32_t burst_start = 0; // time of current mark/space start
|
||||
static uint32_t bits = 0; // the received code bits
|
||||
static struct ir_nec_code_t code; // the last code received (don't trust the user exposed ir_nec_code_received)
|
||||
static bool valid = false; // if the last IR activity is a valid code
|
||||
|
||||
if (timer_get_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF); // clear flag
|
||||
goto error; // no code or repeat code has been received in time
|
||||
} else if (timer_get_flag(TIM(IR_NEC_TIMER), TIM_SR_CCIF(IR_NEC_CHANNEL))) { // edge detected on input capture
|
||||
uint32_t time = TIM_CCR(IR_NEC_TIMER, IR_NEC_CHANNEL); // save captured bit timing (this also clears the flag)
|
||||
time = (time * (TIM_PSC(TIM(IR_NEC_TIMER)) + 1)) / (rcc_ahb_frequency / 1000000); // calculate time in us
|
||||
if (time < burst_start) { // this should not happen
|
||||
goto error;
|
||||
}
|
||||
time -= burst_start; // calculate mark/space burst time
|
||||
if (0 == burst_count) { // start of very first IR mark for the AGC burst
|
||||
timer_set_counter(TIM(IR_NEC_TIMER), 0); // reset timer counter
|
||||
burst_start = 0; // reset code timer
|
||||
time = 0; // ignore first burst
|
||||
} else if (1 == burst_count) { // end of AGC mark
|
||||
if (time > 9000 * (100 - IR_NEC_JITTER) / 100 && time < 9000 * (100 + IR_NEC_JITTER) / 100) { // AGC mark
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else if (2 == burst_count) { // end of AGC space
|
||||
if (time > 4500 * (100 - IR_NEC_JITTER) / 100 && time < 4500 * (100 + IR_NEC_JITTER) / 100) { // AGC code space
|
||||
bits = 0; // reset previously received bits
|
||||
valid = false; // invalidate previously received code (since this is not a repeat)
|
||||
} else if (time > 2250 * (100 - IR_NEC_JITTER) / 100 && time < 2250 * (100 + IR_NEC_JITTER) / 100) { // AGC repeat space
|
||||
if (valid) {
|
||||
code.repeat = true;
|
||||
ir_nec_code_received.repeat = code.repeat;
|
||||
ir_nec_code_received.address = code.address;
|
||||
ir_nec_code_received.command = code.command;
|
||||
ir_nec_code_received_flag = true;
|
||||
goto reset; // wait for next code
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
goto reset; // not the correct header
|
||||
}
|
||||
} else if (burst_count <= (1 + 32) * 2) { // the code bits
|
||||
if (burst_count % 2) { // bit mark end
|
||||
if (time > 560 * (100 - IR_NEC_JITTER) / 100 && time < 560 * (100 + IR_NEC_JITTER) / 100) { // bit mark
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else { // bit space end
|
||||
bits <<= 1;
|
||||
if (time > (2250 - 560) * (100 - IR_NEC_JITTER) / 100 && time < (2250 - 560) * (100 + IR_NEC_JITTER) / 100) { // bit 1space
|
||||
bits |= 1; // save bit
|
||||
} else if (time > (1125 - 560) * (100 - IR_NEC_JITTER) / 100 && time < (1125 - 560) * (100 + IR_NEC_JITTER) / 100) { // bit 0 space
|
||||
bits |= 0; // save bit
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if ((1 + 32) * 2 == burst_count) { // the code is complete
|
||||
uint8_t address = (bits >> 24) & 0xff; // get 8 address bits
|
||||
uint8_t naddress = (bits >> 16) & 0xff; // get negated 8 address bits
|
||||
uint8_t command = (bits >> 8) & 0xff; // get 8 command bits
|
||||
uint8_t ncommand = (bits >> 0) & 0xff; // get negate 8 commend bits
|
||||
if (!ir_nec_extended) { // the 8-bits address has its inverse
|
||||
if (0xff != (address ^ naddress)) { // the address and its inverse do not match
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (0xff != (command ^ ncommand)) { // the command and its inverse do not match
|
||||
goto error;
|
||||
}
|
||||
valid = true; // remember we have a valid signal
|
||||
code.repeat = false; // this is not a repeat code
|
||||
if (ir_nec_extended) {
|
||||
code.address = (address << 8) + naddress;
|
||||
} else {
|
||||
code.address = address; // save decoded address
|
||||
}
|
||||
code.command = command; // save decoded command
|
||||
ir_nec_code_received.repeat = code.repeat; // transfer code to user
|
||||
ir_nec_code_received.address = code.address; // transfer code to user
|
||||
ir_nec_code_received.command = code.command; // transfer code to user
|
||||
ir_nec_code_received_flag = true;
|
||||
ir_nec_code_received_flag = true; // notify user about the new code
|
||||
goto reset; // wait for next code
|
||||
}
|
||||
} else { // this should not happen
|
||||
goto error;
|
||||
}
|
||||
if (burst_count % 2) {
|
||||
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // wait for end of space
|
||||
} else {
|
||||
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_RISING); // wait for end of mark
|
||||
}
|
||||
burst_count++; // wait for next burst
|
||||
burst_start += time; // save current burst start
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
return;
|
||||
error:
|
||||
valid = false; // invalidate previously received code
|
||||
reset:
|
||||
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // wait for next IR mark burst
|
||||
burst_count = 0; // reset state
|
||||
burst_start = 0; // reset state
|
||||
}
|
39
lib/ir_nec.h
Normal file
39
lib/ir_nec.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* 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 decode InfraRed NEC code
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: timer channel @ref ir_nec_timer
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** set when an IR NEC code has been received */
|
||||
extern volatile bool ir_nec_code_received_flag;
|
||||
|
||||
/** IR NEC code */
|
||||
struct ir_nec_code_t {
|
||||
bool repeat; /**< if this is only a code repeat (received 42.42 ms after the code, 98.75 ms after a repeat) */
|
||||
uint16_t address; /**< code address (8-bit for non-extended, 16-bit for extended) */
|
||||
uint8_t command; /**< code command */
|
||||
};
|
||||
|
||||
/** last IR NEC code received */
|
||||
extern struct ir_nec_code_t ir_nec_code_received;
|
||||
|
||||
/** setup peripherals to receive IR NEC codes
|
||||
* @param[in] extended if the command address is extended (using 16 bits instead of 8, at the cost of error checking)
|
||||
*/
|
||||
void ir_nec_setup(bool extended);
|
192
lib/ir_nikon.c
Normal file
192
lib/ir_nikon.c
Normal file
@ -0,0 +1,192 @@
|
||||
/* 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 detect Nikon infrared remote control trigger using 38 kHz infrared demodulator
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: timer channel @ref ir_nikon_timer
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // standard boolean type
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer utilities
|
||||
|
||||
/* own libraries */
|
||||
#include "ir_nikon.h" // own definitions
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup ir_nikon_timer timer peripheral used to measure signal timing for signal decoding
|
||||
* @{
|
||||
*/
|
||||
#define IR_NIKON_TIMER 4 /**< timer peripheral */
|
||||
#define IR_NIKON_CHANNEL 3 /**< channel used as input capture */
|
||||
#define IR_NIKON_JITTER 20 /**< signal timing jitter in % tolerated in timing */
|
||||
#define IR_NIKON_EXTERNAL_PULLUP true /**< if an external pull-up resistor is already present on the infrared demodulator OUT signal */
|
||||
/** @} */
|
||||
|
||||
volatile bool ir_nikon_trigger_flag = false;
|
||||
|
||||
/** the mark and space durations (in us) corresponding to the Nikon IR sequence (static, measured from a remote clone) */
|
||||
const uint16_t ir_nikon_sequence[] = {2000, 28000, 400, 1580, 400, 3580, 400, 63200, 2000, 28000, 400, 1580, 400, 3580, 400}; // actually there is a 2 pulse trailer, but we skip it to have a faster trigger: 70000, 540, 7200, 580
|
||||
|
||||
void ir_nikon_setup(void);
|
||||
{
|
||||
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
|
||||
rcc_periph_clock_enable(RCC_TIM_CH(IR_NIKON_TIMER, IR_NIKON_CHANNEL)); // enable clock for GPIO peripheral
|
||||
rcc_periph_clock_enable(RCC_TIM(IR_NIKON_TIMER)); // enable clock for timer peripheral
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternative functions
|
||||
#if IR_NIKON_EXTERNAL_PULLUP
|
||||
gpio_set_mode(TIM_CH_PORT(IR_NIKON_TIMER, IR_NIKON_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(IR_NIKON_TIMER, IR_NIKON_CHANNEL)); // setup GPIO pin as input
|
||||
#else
|
||||
gpio_set(TIM_CH_PORT(IR_NIKON_TIMER, IR_NIKON_CHANNEL), TIM_CH_PIN(IR_NIKON_TIMER, IR_NIKON_CHANNEL)); // idle is high (using pull-up resistor)
|
||||
gpio_set_mode(TIM_CH_PORT(IR_NIKON_TIMER, IR_NIKON_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, TIM_CH_PIN(IR_NIKON_TIMER, IR_NIKON_CHANNEL)); // setup GPIO pin as input
|
||||
#endif
|
||||
timer_reset(TIM(IR_NIKON_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(IR_NIKON_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
|
||||
// codes are repeated every 110 ms, thus we need to measure at least this duration to detect repeats correctly
|
||||
// the 16-bit timer is by far precise enough to measure the smallest 560 us burst
|
||||
timer_set_prescaler(TIM(IR_NIKON_TIMER), (70 * (rcc_ahb_frequency / 1000) / (1 << 16)) - 1); // set the prescaler so this 16 bits timer overflow after 70 ms (to ignore the second sequence)
|
||||
timer_ic_set_input(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_IN_TI(IR_NIKON_CHANNEL)); // configure ICx to use TIn
|
||||
timer_ic_set_filter(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_CK_INT_N_8); // use small filter (noise reduction is more important than timing)
|
||||
timer_ic_set_polarity(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_FALLING); // capture on falling edge (IR bursts are active low on IR demodulators)
|
||||
timer_ic_set_prescaler(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
|
||||
|
||||
timer_clear_flag(TIM(IR_NIKON_TIMER), TIM_SR_UIF); // clear flag
|
||||
timer_update_on_overflow(TIM(IR_NIKON_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(IR_NIKON_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
|
||||
timer_clear_flag(TIM(IR_NIKON_TIMER), TIM_SR_CCIF(IR_NIKON_CHANNEL)); // clear input compare flag
|
||||
timer_ic_enable(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL)); // enable capture interrupt only when IR burst
|
||||
timer_enable_irq(TIM(IR_NIKON_TIMER), TIM_DIER_CCIE(IR_NIKON_CHANNEL)); // enable capture interrupt
|
||||
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(IR_NIKON_TIMER)); // catch interrupt in service routine
|
||||
timer_enable_counter(TIM(IR_NIKON_TIMER)); // enable timer
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer
|
||||
* @note this could be improve by ignoring short noise burst, but works good enough
|
||||
*/
|
||||
void TIM_ISR(IR_NIKON_TIMER)(void)
|
||||
{
|
||||
static uint8_t burst_count = 0; // the mark or space count
|
||||
static uint32_t burst_start = 0; // time of current mark/space start
|
||||
static uint32_t bits = 0; // the received code bits
|
||||
static struct ir_nec_code_t code; // the last code received (don't trust the user exposed ir_nec_code_received)
|
||||
static bool valid = false; // if the last IR activity is a valid code
|
||||
|
||||
if (timer_get_flag(TIM(IR_NIKON_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(IR_NIKON_TIMER), TIM_SR_UIF); // clear flag
|
||||
goto reset;
|
||||
} else if (timer_get_flag(TIM(IR_NIKON_TIMER), TIM_SR_CCIF(IR_NIKON_CHANNEL))) { // edge detected on input capture
|
||||
uint32_t time = TIM_CCR(IR_NIKON_TIMER, IR_NIKON_CHANNEL); // save captured bit timing (this also clears the flag)
|
||||
time = (time * (TIM_PSC(TIM(IR_NIKON_TIMER)) + 1)) / (rcc_ahb_frequency / 1000000); // calculate time in us
|
||||
if (time < burst_start) { // this should not happen
|
||||
goto error;
|
||||
}
|
||||
time -= burst_start; // calculate mark/space burst time
|
||||
if (0 == burst_count) { // start of very first IR mark for the AGC burst
|
||||
timer_set_counter(TIM(IR_NIKON_TIMER), 0); // reset timer counter
|
||||
burst_start = 0; // reset code timer
|
||||
time = 0; // ignore first burst
|
||||
} else if (1 == burst_count) { // end of AGC mark
|
||||
if (time > 9000 * (100 - IR_NIKON_JITTER) / 100 && time < 9000 * (100 + IR_NIKON_JITTER) / 100) { // AGC mark
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else if (2 == burst_count) { // end of AGC space
|
||||
if (time > 4500 * (100 - IR_NIKON_JITTER) / 100 && time < 4500 * (100 + IR_NIKON_JITTER) / 100) { // AGC code space
|
||||
bits = 0; // reset previously received bits
|
||||
valid = false; // invalidate previously received code (since this is not a repeat)
|
||||
} else if (time > 2250 * (100 - IR_NIKON_JITTER) / 100 && time < 2250 * (100 + IR_NIKON_JITTER) / 100) { // AGC repeat space
|
||||
if (valid) {
|
||||
code.repeat = true;
|
||||
ir_nec_code_received.repeat = code.repeat;
|
||||
ir_nec_code_received.address = code.address;
|
||||
ir_nec_code_received.command = code.command;
|
||||
ir_nec_code_received_flag = true;
|
||||
goto reset; // wait for next code
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
goto reset; // not the correct header
|
||||
}
|
||||
} else if (burst_count <= (1 + 32) * 2) { // the code bits
|
||||
if (burst_count % 2) { // bit mark end
|
||||
if (time > 560 * (100 - IR_NIKON_JITTER) / 100 && time < 560 * (100 + IR_NIKON_JITTER) / 100) { // bit mark
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else { // bit space end
|
||||
bits <<= 1;
|
||||
if (time > (2250 - 560) * (100 - IR_NIKON_JITTER) / 100 && time < (2250 - 560) * (100 + IR_NIKON_JITTER) / 100) { // bit 1space
|
||||
bits |= 1; // save bit
|
||||
} else if (time > (1125 - 560) * (100 - IR_NIKON_JITTER) / 100 && time < (1125 - 560) * (100 + IR_NIKON_JITTER) / 100) { // bit 0 space
|
||||
bits |= 0; // save bit
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if ((1 + 32) * 2 == burst_count) { // the code is complete
|
||||
uint8_t address = (bits >> 24) & 0xff; // get 8 address bits
|
||||
uint8_t naddress = (bits >> 16) & 0xff; // get negated 8 address bits
|
||||
uint8_t command = (bits >> 8) & 0xff; // get 8 command bits
|
||||
uint8_t ncommand = (bits >> 0) & 0xff; // get negate 8 commend bits
|
||||
if (0xff != (address ^ naddress)) { // the address and its negative do not match
|
||||
goto error;
|
||||
}
|
||||
if (0xff != (command ^ ncommand)) { // the command and its negative do not match
|
||||
goto error;
|
||||
}
|
||||
valid = true; // remember we have a valid signal
|
||||
code.repeat = false; // this is not a repeat code
|
||||
code.address = address; // save decoded address
|
||||
code.command = command; // save decoded command
|
||||
ir_nec_code_received.repeat = code.repeat; // transfer code to user
|
||||
ir_nec_code_received.address = code.address; // transfer code to user
|
||||
ir_nec_code_received.command = code.command; // transfer code to user
|
||||
ir_nec_code_received_flag = true;
|
||||
ir_nec_code_received_flag = true; // notify user about the new code
|
||||
goto reset; // wait for next code
|
||||
}
|
||||
} else { // this should not happen
|
||||
goto error;
|
||||
}
|
||||
if (burst_count % 2) {
|
||||
timer_ic_set_polarity(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_FALLING); // wait for end of space
|
||||
} else {
|
||||
timer_ic_set_polarity(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_RISING); // wait for end of mark
|
||||
}
|
||||
burst_count++; // wait for next burst
|
||||
burst_start += time; // save current burst start
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
return;
|
||||
error:
|
||||
valid = false; // invalidate previously received code
|
||||
reset:
|
||||
timer_ic_set_polarity(TIM(IR_NIKON_TIMER), TIM_IC(IR_NIKON_CHANNEL), TIM_IC_FALLING); // wait for next IR mark burst
|
||||
ignore = false; // don't ignore pulses anymore
|
||||
burst_count = 0; // reset state
|
||||
burst_start = 0; // reset state
|
||||
}
|
27
lib/ir_nikon.h
Normal file
27
lib/ir_nikon.h
Normal file
@ -0,0 +1,27 @@
|
||||
/* 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 detect Nikon infrared remote control trigger using 38 kHz infrared demodulator
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: timer channel @ref ir_nikon_timer
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** set when the Nikon trigger has been received */
|
||||
extern volatile bool ir_nikon_trigger_flag;
|
||||
|
||||
/** setup peripherals to receive infrared Nikon trigger */
|
||||
void ir_nikon_setup(void);
|
@ -1,267 +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 a Titan Micro MAX7219 IC attached to a 4-digit 7-segment (code)
|
||||
* @file led_max7219.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref led_max7219_gpio, timer @ref led_tm1637_timer
|
||||
* @warning all calls are blocking
|
||||
*
|
||||
* bit vs segment: 0bpabcdefg
|
||||
* +a+
|
||||
* f b
|
||||
* +g+
|
||||
* e c p
|
||||
* +d+
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
#include <string.h> // string 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 <libopencm3/stm32/timer.h> // timer library
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "led_max7219.h" // MAX7219 header and definitions
|
||||
|
||||
/** @defgroup led_max7219_gpio GPIO used to control MAX7219 IC load line
|
||||
* @{
|
||||
*/
|
||||
#define LED_MAX7219_LOAD_PORT B /**< port for load line */
|
||||
#define LED_MAX7219_LOAD_PIN 12 /**< pin for load line */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup led_max7219_spi SPI used to communication with MAX7219 IC
|
||||
* @{
|
||||
*/
|
||||
#define LED_MAX7219_SPI 2 /**< SPI to send data */
|
||||
/** @} */
|
||||
|
||||
/** ASCII characters encoded for the 7 segments digit block
|
||||
* @note starts with space
|
||||
*/
|
||||
static const uint8_t ascii_7segments[] = {
|
||||
0x00, // space
|
||||
0x06, // ! (I)
|
||||
0x22, // "
|
||||
0x1d, // # (o)
|
||||
0x5b, // $ (s)
|
||||
0x25, // % (/)
|
||||
0x5f, // & (6)
|
||||
0x02, // '
|
||||
0x4e, // ( ([)
|
||||
0x78, // )
|
||||
0x07, // *
|
||||
0x31, // +
|
||||
0x04, // ,
|
||||
0x01, // -
|
||||
0x04, // . (,)
|
||||
0x25, // /
|
||||
0x7e, // 0
|
||||
0x30, // 1
|
||||
0x6d, // 2
|
||||
0x79, // 3
|
||||
0x33, // 4
|
||||
0x5b, // 5
|
||||
0x5f, // 6
|
||||
0x70, // 7
|
||||
0x7f, // 8
|
||||
0x7b, // 9
|
||||
0x09, // : (=)
|
||||
0x09, // ; (=)
|
||||
0x0d, // <
|
||||
0x09, // =
|
||||
0x19, // >
|
||||
0x65, // ?
|
||||
0x6f, // @
|
||||
0x77, // A
|
||||
0x7f, // B
|
||||
0x4e, // C
|
||||
0x3d, // D
|
||||
0x4f, // E
|
||||
0x47, // F
|
||||
0x5e, // G
|
||||
0x37, // H
|
||||
0x06, // I
|
||||
0x3c, // J
|
||||
0x37, // K
|
||||
0x0e, // L
|
||||
0x76, // M
|
||||
0x76, // N
|
||||
0x7e, // O
|
||||
0x67, // P
|
||||
0x6b, // Q
|
||||
0x66, // R
|
||||
0x5b, // S
|
||||
0x0f, // T
|
||||
0x3e, // U
|
||||
0x3e, // V (U)
|
||||
0x3e, // W (U)
|
||||
0x37, // X (H)
|
||||
0x3b, // Y
|
||||
0x6d, // Z
|
||||
0x4e, // [
|
||||
0x13, // '\'
|
||||
0x78, // /
|
||||
0x62, // ^
|
||||
0x08, // _
|
||||
0x20, // `
|
||||
0x7d, // a
|
||||
0x1f, // b
|
||||
0x0d, // c
|
||||
0x3d, // d
|
||||
0x6f, // e
|
||||
0x47, // f
|
||||
0x7b, // g
|
||||
0x17, // h
|
||||
0x04, // i
|
||||
0x18, // j
|
||||
0x37, // k
|
||||
0x06, // l
|
||||
0x15, // m
|
||||
0x15, // n
|
||||
0x1d, // o
|
||||
0x67, // p
|
||||
0x73, // q
|
||||
0x05, // r
|
||||
0x5b, // s
|
||||
0x0f, // t
|
||||
0x1c, // u
|
||||
0x1c, // v (u)
|
||||
0x1c, // w (u)
|
||||
0x37, // x
|
||||
0x3b, // y
|
||||
0x6d, // z
|
||||
0x4e, // { ([)
|
||||
0x06, // |
|
||||
0x78, // } ([)
|
||||
0x01, // ~
|
||||
};
|
||||
|
||||
/** number of display in the chain */
|
||||
uint8_t lex_max7219_displays = 0;
|
||||
|
||||
/** write data on SPI bus and handle load signal
|
||||
* @param[in] data bytes to write
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
static void led_max7219_write(uint16_t data, uint8_t display)
|
||||
{
|
||||
if (lex_max7219_displays<=display && 0xff!=display) { // display no in chain
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_clear(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // ensure load pin is low (data is put in MAX7219 register on rising edge)
|
||||
for (uint8_t i=lex_max7219_displays; i>0; i--) { // go though all displays
|
||||
while (SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_BSY); // wait until not busy
|
||||
if (0xff==display || i==(display+1)) { // right display or broadcast message
|
||||
spi_send(SPI(LED_MAX7219_SPI), data); // send data
|
||||
} else {
|
||||
spi_send(SPI(LED_MAX7219_SPI), 0x0000); // send no-op command to shift command to correct display or out
|
||||
}
|
||||
while (!(SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_TXE)); // wait until Tx is empty (reference manual says BSY should also cover this, but it doesn't)
|
||||
while (SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
|
||||
}
|
||||
gpio_set(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // create rising edge on load pin for data to be set in MAX7219 register
|
||||
}
|
||||
|
||||
void led_max7219_setup(uint8_t displays)
|
||||
{
|
||||
// saved number of displays
|
||||
lex_max7219_displays = displays;
|
||||
|
||||
// configure GPIO for load line
|
||||
rcc_periph_clock_enable(RCC_GPIO(LED_MAX7219_LOAD_PORT)); // enable clock for GPIO peripheral
|
||||
gpio_clear(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // idle low (load on rising edge)
|
||||
gpio_set_mode(GPIO(LED_MAX7219_LOAD_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_MAX7219_LOAD_PIN)); // set as output
|
||||
|
||||
// configure SPI peripheral
|
||||
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(LED_MAX7219_SPI)); // enable clock for GPIO peripheral for clock signal
|
||||
gpio_set_mode(SPI_SCK_PORT(LED_MAX7219_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(LED_MAX7219_SPI)); // set as output (max clock speed for MAX7219 is 10 MHz)
|
||||
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(LED_MAX7219_SPI)); // enable clock for GPIO peripheral for MOSI signal
|
||||
gpio_set_mode(SPI_MOSI_PORT(LED_MAX7219_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(LED_MAX7219_SPI)); // set as output
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
|
||||
rcc_periph_clock_enable(RCC_SPI(LED_MAX7219_SPI)); // enable clock for SPI peripheral
|
||||
spi_reset(SPI(LED_MAX7219_SPI)); // clear SPI values to default
|
||||
spi_init_master(SPI(LED_MAX7219_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_8, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 8 since max MAX7219 clock is 10 MHz and max SPI PCLK clock is 72 Mhz, depending on which SPI is used, set clock polarity to idle low (as in the datasheet of the MAX7219, but not that important), set clock phase to go high when bit is set (depends on polarity) as data is stored on MAX7219 on rising edge), use 16 bits frames (as used by MAX7219), use MSB first
|
||||
spi_set_unidirectional_mode(SPI(LED_MAX7219_SPI)); // we only need to transmit data
|
||||
spi_enable(SPI(LED_MAX7219_SPI)); // enable SPI
|
||||
}
|
||||
|
||||
void led_max7219_on(uint8_t display)
|
||||
{
|
||||
led_max7219_write(0x0C01, display); // put in normal operation more (registers remain as set)
|
||||
}
|
||||
|
||||
void led_max7219_off(uint8_t display)
|
||||
{
|
||||
led_max7219_write(0x0C00, display); // put in shutdown mode (registers remain as set)
|
||||
}
|
||||
|
||||
void led_max7219_test(bool test, uint8_t display)
|
||||
{
|
||||
if (test) {
|
||||
led_max7219_write(0x0F01, display); // go into display test mode
|
||||
} else {
|
||||
led_max7219_write(0x0F00, display); // go into normal operation mode
|
||||
}
|
||||
}
|
||||
|
||||
void led_max7219_intensity(uint8_t intensity, uint8_t digits, uint8_t display)
|
||||
{
|
||||
if (intensity>15) { // intensity must be 0-15 (corresponds to (2*brightness+1)/32)
|
||||
return;
|
||||
}
|
||||
if (digits<1 || digits>8) { // scan limit must bit 0-7
|
||||
return;
|
||||
}
|
||||
led_max7219_write(0x0A00+intensity, display); // set brightness
|
||||
led_max7219_write(0x0B00+digits-1, display); // set scan limit to display digits
|
||||
}
|
||||
|
||||
bool led_max7219_text(char* text, uint8_t display)
|
||||
{
|
||||
for (uint8_t i=0; i<8; i++) { // input text should only contain printable character (8th bit is used for dots)
|
||||
if ((text[i]&0x7f)<' ' || (text[i]&0x7f)>=' '+LENGTH(ascii_7segments)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
led_max7219_write(0x0900, display); // disable BCD decoding on all 7 digits
|
||||
for (uint8_t i=0; i<8; i++) { // display text
|
||||
led_max7219_write(((i+1)<<8)+(ascii_7segments[(text[7-i]&0x7f)-' '])+(text[7-i]&0x80), display); // send digit (in reverse order)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void led_max7219_number(uint32_t number, uint8_t dots, uint8_t display)
|
||||
{
|
||||
led_max7219_write(0x09FF, display); // enable BCD decoding on all 7 digits
|
||||
for (uint8_t digit=0; digit<8; digit++) { // go through digits
|
||||
if (0==digit) { // display 0 on 0 only to first digit
|
||||
led_max7219_write(((digit+1)<<8) + (number%10) + (((dots>>digit)&0x01)<<7), display); // display digit
|
||||
} else if (0==number) { // display blank on other digits
|
||||
led_max7219_write(((digit+1)<<8) + 0x0F + (((dots>>digit)&0x01)<<7), display); // display blank
|
||||
} else {
|
||||
led_max7219_write(((digit+1)<<8) + (number%10) + (((dots>>digit)&0x01)<<7), display); // display digit
|
||||
}
|
||||
number /= 10; // get next digit
|
||||
}
|
||||
}
|
@ -1,64 +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 a Maxim MAX7219 IC attached to a 8-digit 7-segment (API)
|
||||
* @file led_max7219.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref led_max7219_gpio, SPI @ref led_max7219_spi
|
||||
* @warning all calls are blocking
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup communication with MAX7219 IC
|
||||
* @param[in] displays number of displays in the chain
|
||||
*/
|
||||
void led_max7219_setup(uint8_t displays);
|
||||
/** do nothing (no operation)
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
* @note send it to the last display in the chain to clear the previous command from the chain
|
||||
*/
|
||||
void led_max7219_nop(uint8_t display);
|
||||
/** switch display on
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
void led_max7219_on(uint8_t display);
|
||||
/** switch display off
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
void led_max7219_off(uint8_t display);
|
||||
/** switch display in test or normal operation mode
|
||||
* @param[in] test switch in test mode (else normal operation)
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
void led_max7219_test(bool test, uint8_t display);
|
||||
/** set display intensity
|
||||
* @param[in] intensity level to set (0-15)
|
||||
* @param[in] digits number of digits to display (1-8)
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
void led_max7219_intensity(uint8_t intensity, uint8_t digits, uint8_t display);
|
||||
/** display text
|
||||
* @param[in] text text to display (8 characters)
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
* @note use first bit of each character to enable dot
|
||||
* @return false if string has unsupported characters
|
||||
*/
|
||||
bool led_max7219_text(char* text, uint8_t display);
|
||||
/** display number
|
||||
* @param[in] number number to display (8 digits max)
|
||||
* @param[in] dots set bit if dot on corresponding digit should be displayed
|
||||
* @param[in] display display number in chain (0xff for all)
|
||||
*/
|
||||
void led_max7219_number(uint32_t number, uint8_t dots, uint8_t display);
|
327
lib/led_tm1637.c
327
lib/led_tm1637.c
@ -1,327 +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 a Titan Micro TM1637 IC attached to a 4-digit 7-segment (code)
|
||||
* @file led_tm1637.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer
|
||||
* @note the protocol is very similar to I2C but incompatible for the following reasons: the capacitance is too large for open-drain type output with weak pull-up resistors (push-pull needs to be used, preventing to get ACKs since no indication of the ACK timing is provided); the devices doesn't use addresses; the STM32 I2C will switch to receiver mode when the first sent byte (the I2C address) has last bit set to 1 (such as for address commands with B7=1 where B7 is transmitted last), preventing to send further bytes (the data byte after the address)
|
||||
* @warning all calls are blocking
|
||||
*
|
||||
* bit vs segment: 0bpgfedcba
|
||||
* +a+
|
||||
* f b p
|
||||
* +g+
|
||||
* e c p
|
||||
* +d+
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
#include <string.h> // string 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/timer.h> // timer library
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "led_tm1637.h" // TM1637 header and definitions
|
||||
|
||||
/** @defgroup led_tm1637_gpio GPIO used to communication with TM1637 IC
|
||||
* @{
|
||||
*/
|
||||
#define LED_TM1637_CLK_PORT B /**< port for CLK signal */
|
||||
#define LED_TM1637_CLK_PIN 6 /**< pin for CLK signal */
|
||||
#define LED_TM1637_DIO_PORT B /**< port for DIO signal */
|
||||
#define LED_TM1637_DIO_PIN 7 /**< pin for DIO signal */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup led_tm1637_timer timer used to communication with TM1637 IC
|
||||
* @{
|
||||
*/
|
||||
#define LED_TM1637_TIMER 3 /**< timer to create signal */
|
||||
/** @} */
|
||||
|
||||
/** display brightness */
|
||||
static enum led_tm1637_brightness_t display_brightness = LED_TM1637_14DIV16;
|
||||
/** if display is on */
|
||||
static bool display_on = false;
|
||||
|
||||
/** ASCII characters encoded for the 7 segments digit block
|
||||
* @note starts with space
|
||||
*/
|
||||
static const uint8_t ascii_7segments[] = {
|
||||
0x00, // 0b00000000 space
|
||||
0x30, // 0b00110000 ! (I)
|
||||
0x22, // 0b00100010 "
|
||||
0x5c, // 0b01011100 # (o)
|
||||
0x6d, // 0b01101101 $ (s)
|
||||
0x52, // 0b01010010 % (/)
|
||||
0x7d, // 0b01111101 & (6)
|
||||
0x20, // 0b00100000 '
|
||||
0x39, // 0b00111001 ( ([)
|
||||
0x0f, // 0b00001111 )
|
||||
0x70, // 0b01110000 *
|
||||
0x46, // 0b01000110 +
|
||||
0x10, // 0b00010000 ,
|
||||
0x40, // 0b01000000 -
|
||||
0x10, // 0b00010000 . (,)
|
||||
0x52, // 0b01010010 /
|
||||
0x3f, // 0b00111111 0
|
||||
0x06, // 0b00000110 1
|
||||
0x5b, // 0b01011011 2
|
||||
0x4f, // 0b01001111 3
|
||||
0x66, // 0b01100110 4
|
||||
0x6d, // 0b01101101 5
|
||||
0x7d, // 0b01111101 6
|
||||
0x07, // 0b00000111 7
|
||||
0x7f, // 0b01111111 8
|
||||
0x6f, // 0b01101111 9
|
||||
0x48, // 0b01001000 : (=)
|
||||
0x48, // 0b01001000 ; (=)
|
||||
0x58, // 0b01011000 <
|
||||
0x48, // 0b01001000 =
|
||||
0x4c, // 0b01001100 >
|
||||
0x53, // 0b01010011 ?
|
||||
0x7b, // 0b01111011 @
|
||||
0x77, // 0b01110111 A
|
||||
0x7f, // 0b01111111 B
|
||||
0x39, // 0b00111001 C
|
||||
0x5e, // 0b01011110 D
|
||||
0x79, // 0b01111001 E
|
||||
0x71, // 0b01110001 F
|
||||
0x3d, // 0b00111101 G
|
||||
0x76, // 0b01110110 H
|
||||
0x30, // 0b00110000 I
|
||||
0x1e, // 0b00011110 J
|
||||
0x76, // 0b01110110 K
|
||||
0x38, // 0b00111000 L
|
||||
0x37, // 0b00110111 M
|
||||
0x37, // 0b00110111 N
|
||||
0x3f, // 0b00111111 O
|
||||
0x73, // 0b01110011 P
|
||||
0x6b, // 0b01101011 Q
|
||||
0x33, // 0b00110011 R
|
||||
0x6d, // 0b01101101 S
|
||||
0x78, // 0b01111000 T
|
||||
0x3e, // 0b00111110 U
|
||||
0x3e, // 0b00111110 V (U)
|
||||
0x3e, // 0b00111110 W (U)
|
||||
0x76, // 0b01110110 X (H)
|
||||
0x6e, // 0b01101110 Y
|
||||
0x5b, // 0b01011011 Z
|
||||
0x39, // 0b00111001 [
|
||||
0x64, // 0b01100100 '\'
|
||||
0x0f, // 0b00001111 /
|
||||
0x23, // 0b00100011 ^
|
||||
0x08, // 0b00001000 _
|
||||
0x02, // 0b00000010 `
|
||||
0x5f, // 0b01011111 a
|
||||
0x7c, // 0b01111100 b
|
||||
0x58, // 0b01011000 c
|
||||
0x5e, // 0b01011110 d
|
||||
0x7b, // 0b01111011 e
|
||||
0x71, // 0b01110001 f
|
||||
0x6f, // 0b01101111 g
|
||||
0x74, // 0b01110100 h
|
||||
0x10, // 0b00010000 i
|
||||
0x0c, // 0b00001100 j
|
||||
0x76, // 0b01110110 k
|
||||
0x30, // 0b00110000 l
|
||||
0x54, // 0b01010100 m
|
||||
0x54, // 0b01010100 n
|
||||
0x5c, // 0b01011100 o
|
||||
0x73, // 0b01110011 p
|
||||
0x67, // 0b01100111 q
|
||||
0x50, // 0b01010000 r
|
||||
0x6d, // 0b01101101 s
|
||||
0x78, // 0b01111000 t
|
||||
0x1c, // 0b00011100 u
|
||||
0x1c, // 0b00011100 v (u)
|
||||
0x1c, // 0b00011100 w (u)
|
||||
0x76, // 0b01110110 x
|
||||
0x6e, // 0b01101110 y
|
||||
0x5b, // 0b01011011 z
|
||||
0x39, // 0b00111001 { ([)
|
||||
0x30, // 0b00110000 |
|
||||
0x0f, // 0b00001111 } ([)
|
||||
0x40, // 0b01000000 ~
|
||||
};
|
||||
|
||||
void led_tm1637_setup(void)
|
||||
{
|
||||
// configure GPIO for CLK and DIO signals
|
||||
rcc_periph_clock_enable(RCC_GPIO(LED_TM1637_CLK_PORT)); // enable clock for GPIO peripheral
|
||||
gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // idle high
|
||||
gpio_set_mode(GPIO(LED_TM1637_CLK_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_CLK_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave
|
||||
rcc_periph_clock_enable(RCC_GPIO(LED_TM1637_DIO_PORT)); // enable clock for GPIO peripheral
|
||||
gpio_set(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // idle high
|
||||
gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_DIO_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave
|
||||
// first clock then data high also stands for stop condition
|
||||
|
||||
// setup timer to create signal timing (each tick is used for a single GPIO transition)
|
||||
rcc_periph_clock_enable(RCC_TIM(LED_TM1637_TIMER)); // enable clock for timer block
|
||||
timer_reset(TIM(LED_TM1637_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(LED_TM1637_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(LED_TM1637_TIMER), 0); // don't prescale to get most precise timing ( 1/(72E6/1/(2**16))=0.91 ms > 0.5 us )
|
||||
timer_set_period(TIM(LED_TM1637_TIMER), 500); // set the clock frequency (emprical value until the signal starts to look bad)
|
||||
timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear flag
|
||||
timer_update_on_overflow(TIM(LED_TM1637_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
}
|
||||
|
||||
/** wait until clock tick (timer overflow) occurred
|
||||
*/
|
||||
static inline void led_tm1637_tick(void)
|
||||
{
|
||||
while (!timer_get_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF)); // wait until counter overflow update event happens
|
||||
timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear event flag
|
||||
}
|
||||
|
||||
/** write data on bus
|
||||
* @param[in] data bytes to write
|
||||
* @param[in] length number of bytes to write
|
||||
* @return if write succeeded
|
||||
* @note includes start and stop conditions
|
||||
*/
|
||||
static bool led_tm1637_write(const uint8_t* data, size_t length)
|
||||
{
|
||||
bool to_return = true; // return if write succeeded
|
||||
if (data==NULL || length==0) { // verify there it data to be read
|
||||
return false;
|
||||
}
|
||||
|
||||
// enable timer for signal generation
|
||||
timer_set_counter(TIM(LED_TM1637_TIMER), 0); // reset timer counter
|
||||
timer_enable_counter(TIM(LED_TM1637_TIMER)); // enable timer to generate timing
|
||||
led_tm1637_tick(); // wait to enforce minimum time since last write
|
||||
|
||||
// send start condition (DIO then CLK low)
|
||||
gpio_clear(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO low
|
||||
led_tm1637_tick(); // wait for next tick
|
||||
gpio_clear(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low
|
||||
|
||||
// send data bytes (MSb first)
|
||||
for (size_t i=0; i<length; i++) { // send all bytes
|
||||
uint8_t byte = data[i];
|
||||
for (uint8_t b=0; b<8; b++) { // send all bits
|
||||
if (byte&0x1) { // send a 1
|
||||
gpio_set(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO high
|
||||
} else {
|
||||
gpio_clear(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO low
|
||||
}
|
||||
byte >>= 1; // shift data
|
||||
led_tm1637_tick(); // wait for next tick
|
||||
gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high
|
||||
led_tm1637_tick(); // wait for next tick (no DIO transition when CLK is high)
|
||||
gpio_clear(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low
|
||||
}
|
||||
gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(LED_TM1637_DIO_PIN)); // switch DIO as input to read ACK
|
||||
led_tm1637_tick(); // wait for next tick (when the slave should ACK)
|
||||
gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high
|
||||
if (gpio_get(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN))) { // no ACK received
|
||||
to_return = false; // remember there was an error
|
||||
break; // stop sending bytes
|
||||
}
|
||||
led_tm1637_tick(); // wait for next tick
|
||||
gpio_clear(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low
|
||||
gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_DIO_PIN)); // switch DIO back to output to send next byte
|
||||
}
|
||||
|
||||
// send stop condition
|
||||
gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_DIO_PIN)); // ensure DIO is output (in case no ACK as been received
|
||||
led_tm1637_tick(); // wait for next tick
|
||||
gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high
|
||||
led_tm1637_tick(); // wait for next tick
|
||||
gpio_set(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO high
|
||||
timer_disable_counter(TIM(LED_TM1637_TIMER)); // stop timer since it's not used anymore
|
||||
|
||||
return to_return;
|
||||
}
|
||||
|
||||
bool led_tm1637_on(void)
|
||||
{
|
||||
uint8_t data[] = { 0x88+display_brightness }; // command to turn display on (use set brightness)
|
||||
bool to_return = false; // result to return
|
||||
if (led_tm1637_write(data,LENGTH(data))) { // send command
|
||||
display_on = true; // remember display is on
|
||||
to_return = true; // command succeeded
|
||||
}
|
||||
return to_return; // return result
|
||||
}
|
||||
|
||||
bool led_tm1637_off(void)
|
||||
{
|
||||
uint8_t data[] = { 0x80+display_brightness }; // command to turn display off (use set brightness)
|
||||
if (led_tm1637_write(data,LENGTH(data))) { // send command
|
||||
display_on = false; // remember display is off
|
||||
return true; // command succeeded
|
||||
}
|
||||
return false; // return result
|
||||
}
|
||||
|
||||
bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness)
|
||||
{
|
||||
display_brightness = brightness; // save brightness
|
||||
if (display_on) { // adjust brightness if display is on
|
||||
return led_tm1637_on(); // adjust brightness
|
||||
} else {
|
||||
return true; // command succeeded
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool led_tm1637_number(uint16_t number)
|
||||
{
|
||||
uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
|
||||
uint8_t data[] = { 0xc0, ascii_7segments[((number/1000)%10)+'0'-' '], ascii_7segments[((number/100)%10)+'0'-' '], ascii_7segments[((number/10)%10)+'0'-' '], ascii_7segments[((number/1)%10)+'0'-' '] }; // set address C0H and add data
|
||||
|
||||
if (led_tm1637_write(write_data,LENGTH(write_data)) && led_tm1637_write(data,LENGTH(data))) { // send commands
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool led_tm1637_time(uint8_t hours, uint8_t minutes)
|
||||
{
|
||||
uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
|
||||
uint8_t data[] = { 0xc0, ascii_7segments[((hours/10)%10)+'0'-' '], ascii_7segments[((hours/1)%10)+'0'-' ']|0x80, ascii_7segments[((minutes/10)%10)+'0'-' '], ascii_7segments[((minutes/1)%10)+'0'-' '] }; // set address C0H and add data
|
||||
|
||||
if (led_tm1637_write(write_data,LENGTH(write_data)) && led_tm1637_write(data,LENGTH(data))) { // send commands
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool led_tm1637_text(char* text)
|
||||
{
|
||||
if (strlen(text)!=4) { // input text should have exactly 4 characters
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i=0; i<4; i++) { // input text should only contain printable character (8th bit is used for dots)
|
||||
if ((text[i]&0x7f)<' ' || (text[i]&0x7f)>=' '+LENGTH(ascii_7segments)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
|
||||
uint8_t data[] = { 0xc0, ascii_7segments[(text[0]&0x7f)-' ']|(text[0]&0x80), ascii_7segments[(text[1]&0x7f)-' ']|(text[1]&0x80), ascii_7segments[(text[2]&0x7f)-' ']|(text[2]&0x80), ascii_7segments[(text[3]&0x7f)-' ']|(text[3]&0x80) }; // set address C0H and add data
|
||||
|
||||
if (led_tm1637_write(write_data,LENGTH(write_data)) && led_tm1637_write(data,LENGTH(data))) { // send commands
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,71 +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 a Titan Micro TM1637 IC attached to a 4-digit 7-segment (API)
|
||||
* @file led_tm1637.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer
|
||||
* @warning all calls are blocking
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** display brightness levels
|
||||
*/
|
||||
enum led_tm1637_brightness_t {
|
||||
LED_TM1637_1DIV16 = 0,
|
||||
LED_TM1637_2DIV16 = 1,
|
||||
LED_TM1637_4DIV16 = 2,
|
||||
LED_TM1637_10DIV16 = 3,
|
||||
LED_TM1637_11DIV16 = 4,
|
||||
LED_TM1637_12DIV16 = 5,
|
||||
LED_TM1637_13DIV16 = 6,
|
||||
LED_TM1637_14DIV16 = 7,
|
||||
};
|
||||
|
||||
/** setup communication with TM1637 IC
|
||||
*/
|
||||
void led_tm1637_setup(void);
|
||||
/** switch display on
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_on(void);
|
||||
/** switch display off
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_off(void);
|
||||
/** set display brightness
|
||||
* @param[in] brightness brightness level to set
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness);
|
||||
/** display number
|
||||
* @param[in] number number to display (0-9999)
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_number(uint16_t number);
|
||||
/** display time
|
||||
* @param[in] hours hours to display (0-99)
|
||||
* @param[in] minutes minutes to display (0-99)
|
||||
* @note display separator between hours and minutes
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_time(uint8_t hours, uint8_t minutes);
|
||||
/** display text
|
||||
* @param[in] text text to display (4 characters)
|
||||
* @note use first bit of each character to enable dot
|
||||
* @return if transmission succeeded
|
||||
*/
|
||||
bool led_tm1637_text(char* text);
|
||||
|
@ -1,181 +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 drive a WS2812B LED chain (code)
|
||||
* @file led_ws2812b.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA (for SPI MISO)
|
||||
*/
|
||||
|
||||
/* 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 <libopencm3/stm32/timer.h> // timer library
|
||||
#include <libopencm3/stm32/dma.h> // DMA library
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
|
||||
#include "led_ws2812b.h" // LED WS2812B library API
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** peripheral configuration */
|
||||
/** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs
|
||||
* @{
|
||||
*/
|
||||
#define LED_WS2812B_SPI 1 /**< SPI peripheral */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup led_ws2812b_timer timer peripheral used to generate SPI clock
|
||||
* @{
|
||||
*/
|
||||
#define LED_WS2812B_TIMER 3 /**< timer peripheral */
|
||||
#define LED_WS2812B_CLK_CH 3 /**< timer channel to output PWM (PB0), connect to SPI clock input */
|
||||
#define LED_WS2812B_TIMER_OC TIM_OC3 /**< timer output compare used to set PWM frequency */
|
||||
/** @} */
|
||||
|
||||
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
|
||||
* @details For each WS2812B bit which needs to be transfered we require to transfer 3 SPI bits.
|
||||
* The first SPI bit is the high start of the WS2812B bit frame.
|
||||
* The second SPI bit determines if the WS2812B bit is a 0 or 1.
|
||||
* The third SPI bit is the last part of the WS2812B bit frame, which is always low.
|
||||
* The binary pattern is 0b100100100100100100100100
|
||||
*/
|
||||
#define LED_WS2812B_SPI_TEMPLATE 0x924924
|
||||
|
||||
uint8_t led_ws2812b_data[LED_WS2812B_LEDS*3*3+40*3/8+1] = {0}; /**< data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
|
||||
static volatile bool transmit_flag = false; /**< flag set in software when transmission started, clear by interrupt when transmission completed */
|
||||
|
||||
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
// verify the led exists
|
||||
if (led>=LED_WS2812B_LEDS) {
|
||||
return;
|
||||
}
|
||||
// wait for transmission to complete before changing the color
|
||||
while (transmit_flag) {
|
||||
__WFI();
|
||||
}
|
||||
|
||||
const uint8_t colors[] = {green, red, blue}; // color order for the WS2812B
|
||||
const uint8_t pattern_bit[] = {0x02, 0x10, 0x80, 0x04, 0x20, 0x01, 0x08, 0x40}; // which bit to change in the pattern
|
||||
const uint8_t pattern_byte[] = {2,2,2,1,1,0,0,0}; // in which byte in the pattern to write the pattern bit
|
||||
for (uint8_t color=0; color<LENGTH(colors); color++) { // colors are encoded similarly
|
||||
// fill the middle bit (fixed is faster than calculating it)
|
||||
for (uint8_t bit=0; bit<8; bit++) { // bit from the color to set/clear
|
||||
if (colors[color]&(1<<bit)) { // setting bit
|
||||
led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] |= pattern_bit[bit]; // setting bit is pattern
|
||||
} else { // clear bit
|
||||
led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] &= ~pattern_bit[bit]; // clearing bit is pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool led_ws2812b_transmit(void)
|
||||
{
|
||||
if (transmit_flag) { // a transmission is already ongoing
|
||||
return false;
|
||||
}
|
||||
transmit_flag = true; // remember transmission started
|
||||
dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data);
|
||||
dma_set_number_of_data(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), LENGTH(led_ws2812b_data)); // set the size of the data to transmit
|
||||
dma_enable_transfer_complete_interrupt(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // warm when transfer is complete to stop transmission
|
||||
dma_enable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // enable DMA channel
|
||||
|
||||
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transfered
|
||||
|
||||
timer_set_counter(TIM(LED_WS2812B_TIMER), 0); // reset timer counter fro clean clock
|
||||
timer_enable_counter(TIM(LED_WS2812B_TIMER)); // start timer to generate clock
|
||||
return true;
|
||||
}
|
||||
|
||||
void led_ws2812b_setup(void)
|
||||
{
|
||||
// setup timer to generate clock of (using PWM): 800kHz*3
|
||||
rcc_periph_clock_enable(RCC_TIM_CH(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH)); // enable clock for GPIO peripheral
|
||||
gpio_set_mode(TIM_CH_PORT(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, TIM_CH_PIN(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH)); // set pin as output
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM)
|
||||
rcc_periph_clock_enable(RCC_TIM(LED_WS2812B_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(LED_WS2812B_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(LED_WS2812B_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(LED_WS2812B_TIMER), 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
|
||||
timer_set_period(TIM(LED_WS2812B_TIMER), rcc_ahb_frequency/800000/3-1); // set the clock frequency to 800kHz*3bit since we need to send 3 bits to output a 800kbps stream
|
||||
timer_set_oc_value(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50%
|
||||
timer_set_oc_mode(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock)
|
||||
timer_enable_oc_output(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC); // enable output to generate the clock
|
||||
|
||||
// setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812B bit
|
||||
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
|
||||
gpio_set_mode(SPI_SCK_PORT(LED_WS2812B_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(LED_WS2812B_SPI)); // set clock as input
|
||||
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
|
||||
gpio_set_mode(SPI_MISO_PORT(LED_WS2812B_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MISO_PIN(LED_WS2812B_SPI)); // set MISO as output
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
|
||||
rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral
|
||||
spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default
|
||||
spi_set_slave_mode(SPI(LED_WS2812B_SPI)); // set SPI as slave (since we use the clock as input)
|
||||
spi_set_bidirectional_transmit_only_mode(SPI(LED_WS2812B_SPI)); // we won't receive data
|
||||
spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
|
||||
spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
|
||||
spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle
|
||||
spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising)
|
||||
spi_send_msb_first(SPI(LED_WS2812B_SPI)); // send least significant bit first
|
||||
spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (since there is no master)
|
||||
spi_set_nss_low(SPI(LED_WS2812B_SPI)); // set NSS low so we can output
|
||||
spi_enable(SPI(LED_WS2812B_SPI)); // enable SPI
|
||||
// do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
|
||||
|
||||
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
|
||||
rcc_periph_clock_enable(RCC_DMA_SPI(LED_WS2812B_SPI)); // enable clock for DMA peripheral
|
||||
dma_channel_reset(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // start with fresh channel configuration
|
||||
dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data); // set bit pattern as source address
|
||||
dma_set_peripheral_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)&SPI_DR(SPI(LED_WS2812B_SPI))); // set SPI as peripheral destination address
|
||||
dma_set_read_from_memory(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // set direction from memory to peripheral
|
||||
dma_enable_memory_increment_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // go through bit pattern
|
||||
dma_set_memory_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
|
||||
dma_set_peripheral_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
|
||||
dma_set_priority(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
|
||||
nvic_enable_irq(DMA_IRQ_SPI_TX(LED_WS2812B_SPI)); // enable interrupts for this DMA channel
|
||||
|
||||
// fill buffer with bit pattern
|
||||
for (uint16_t i=0; i<LED_WS2812B_LEDS*3; i++) {
|
||||
led_ws2812b_data[i*3+0] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>16);
|
||||
led_ws2812b_data[i*3+1] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>8);
|
||||
led_ws2812b_data[i*3+2] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>0);
|
||||
}
|
||||
// fill remaining with with 0 to encode the reset code
|
||||
for (uint16_t i=LED_WS2812B_LEDS*3*3; i<LENGTH(led_ws2812b_data); i++) {
|
||||
led_ws2812b_data[i] = 0;
|
||||
}
|
||||
led_ws2812b_transmit(); // set LEDs
|
||||
}
|
||||
|
||||
/** DMA interrupt service routine to stop transmission after it finished */
|
||||
void DMA_ISR_SPI_TX(LED_WS2812B_SPI)(void)
|
||||
{
|
||||
if (dma_get_interrupt_flag(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_TCIF)) { // transfer completed
|
||||
dma_clear_interrupt_flags(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_TCIF); // clear flag
|
||||
dma_disable_transfer_complete_interrupt(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop warning transfer completed
|
||||
spi_disable_tx_dma(SPI(LED_WS2812B_SPI)); // stop SPI asking for more data
|
||||
while (SPI_SR(SPI(LED_WS2812B_SPI)) & SPI_SR_BSY); // wait for data to be shifted out
|
||||
timer_disable_counter(TIM(LED_WS2812B_TIMER)); // stop clock
|
||||
dma_disable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop using DMA
|
||||
transmit_flag = false; // transmission completed
|
||||
}
|
||||
}
|
@ -1,39 +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 drive a WS2812B LED chain (API)
|
||||
* @file led_ws2812b.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA (for SPI MISO)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** number of LEDs on the WS2812B strip */
|
||||
#define LED_WS2812B_LEDS 48
|
||||
|
||||
/** setup WS2812B LED driver */
|
||||
void led_ws2812b_setup(void);
|
||||
/** set color of a single LED
|
||||
* @param[in] led the LED number to set the color
|
||||
* @param[in] red the red color value to set on the LED
|
||||
* @param[in] green the green color value to set on the LED
|
||||
* @param[in] blue the blue color value to set on the LED
|
||||
* @note transmission needs to be done separately
|
||||
*/
|
||||
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue);
|
||||
/** transmit color values to WS2812B LEDs
|
||||
* @return true if transmission started, false if another transmission is already ongoing
|
||||
*/
|
||||
bool led_ws2812b_transmit(void);
|
@ -1,315 +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 using microwore as master (code)
|
||||
* @file microwire_master.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref microwire_master_gpio, timer @ref microwire_master_timer
|
||||
* microwire is a 3-Wire half-duplex synchronous bus. It is very similar to SPI without fixed length messages (bit-wised).
|
||||
* @note the user has to handle the slave select pin (high during operations) so to be able to handle multiple slaves.
|
||||
* @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones.
|
||||
*/
|
||||
|
||||
/* 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/timer.h> // timer utilities
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "microwire_master.h" // microwire header and definitions
|
||||
|
||||
/** @defgroup microwire_master_gpio GPIO peripheral used to communicate
|
||||
* @{
|
||||
*/
|
||||
#define MICROWIRE_MASTER_SDO_PORT A /**< SDO output signal port (to be connected on D slave signal) */
|
||||
#define MICROWIRE_MASTER_SDO_PIN 0 /**< SDO output signal pin (to be connected on D slave signal) */
|
||||
#define MICROWIRE_MASTER_SDI_PORT A /**< SDO input signal port (to be connected on Q slave signal) */
|
||||
#define MICROWIRE_MASTER_SDI_PIN 2 /**< SDO input signal pin (to be connected on Q slave signal) */
|
||||
#define MICROWIRE_MASTER_SCK_PORT A /**< SCK output signal port (to be connected on C slave signal) */
|
||||
#define MICROWIRE_MASTER_SCK_PIN 4 /**< SCK output signal pin (to be connected on C slave signal) */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup microwire_master_timer timer peripheral used to generate timing for the signal
|
||||
* @{
|
||||
*/
|
||||
#define MICROWIRE_MASTER_TIMER 4 /**< timer peripheral */
|
||||
/** @} */
|
||||
|
||||
/** address size used in operations (slave specific) */
|
||||
uint8_t mirowire_master_address_size = 0;
|
||||
/** organization used (true=x16, false=x8) */
|
||||
bool mirowire_master_organization_x16 = true;
|
||||
|
||||
void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size)
|
||||
{
|
||||
// sanity checks
|
||||
if (0==frequency || 0==address_size) {
|
||||
return;
|
||||
}
|
||||
mirowire_master_address_size = address_size; // save address size
|
||||
mirowire_master_organization_x16 = organization_x16; // save organisation
|
||||
|
||||
// setup GPIO
|
||||
rcc_periph_clock_enable(RCC_GPIO(MICROWIRE_MASTER_SDO_PORT)); // enable clock for GPIO domain for SDO signal
|
||||
gpio_set_mode(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MICROWIRE_MASTER_SDO_PIN)); // set SDO signal as output (controlled by the master)
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // SDO is idle low
|
||||
rcc_periph_clock_enable(RCC_GPIO(MICROWIRE_MASTER_SDI_PORT)); // enable clock for GPIO domain for SDI signal
|
||||
gpio_set_mode(GPIO(MICROWIRE_MASTER_SDI_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(MICROWIRE_MASTER_SDI_PIN)); // set SDI signal as output (controlled by the slave)
|
||||
rcc_periph_clock_enable(RCC_GPIO(MICROWIRE_MASTER_SCK_PORT)); // enable clock for GPIO domain for SCK signal
|
||||
gpio_set_mode(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(MICROWIRE_MASTER_SCK_PIN)); // set SCK signal as output (controlled by the master)
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // SCK is idle low
|
||||
|
||||
// setup timer to generate timing for the signal
|
||||
rcc_periph_clock_enable(RCC_TIM(MICROWIRE_MASTER_TIMER)); // enable clock for timer domain
|
||||
timer_reset(TIM(MICROWIRE_MASTER_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(MICROWIRE_MASTER_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
uint16_t prescaler = rcc_ahb_frequency/(frequency*2)/(uint32_t)(1<<16)+1; // calculate prescaler for most accurate timing for this speed
|
||||
timer_set_prescaler(TIM(MICROWIRE_MASTER_TIMER), prescaler-1); // set calculated prescaler
|
||||
uint16_t period = (rcc_ahb_frequency/prescaler)/(frequency*2); // calculate period to get most accurate timing based on the calculated prescaler
|
||||
timer_set_period(TIM(MICROWIRE_MASTER_TIMER), period-1); // set calculated period
|
||||
timer_update_on_overflow(TIM(MICROWIRE_MASTER_TIMER)); // only use counter overflow as UEV source (use overflow as timeout)
|
||||
SCB_SCR |= SCB_SCR_SEVEONPEND; // enable wake up on event (instead of using ISR)
|
||||
timer_enable_irq(TIM(MICROWIRE_MASTER_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
}
|
||||
|
||||
/** wait for clock tick used to synchronise communication */
|
||||
static void microwire_master_wait_clock(void)
|
||||
{
|
||||
while ( !timer_get_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF)) { // wait for timer overflow event for clock change
|
||||
__asm__("wfe"); // go to sleep and wait for event
|
||||
}
|
||||
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
|
||||
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
|
||||
|
||||
}
|
||||
|
||||
/** send bit over microwire
|
||||
* @param[in] bit bit to send (true = '1', false = '0')
|
||||
*/
|
||||
static void microwire_master_send_bit(bool bit)
|
||||
{
|
||||
if (bit) {
|
||||
gpio_set(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // set '1' on output
|
||||
} else {
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // set '0' on output
|
||||
}
|
||||
microwire_master_wait_clock(); // wait for clock timing
|
||||
gpio_set(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to sample
|
||||
microwire_master_wait_clock(); // keep output signal stable while clock is high
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // put clock back to idle
|
||||
}
|
||||
|
||||
/** initialize microwire communication and send header (with leading start bit '1')
|
||||
* @param[in] operation operation code to send (2 bits)
|
||||
* @param[in] address slave memory address to select
|
||||
*/
|
||||
static void microwire_master_start(uint8_t operation, uint32_t address)
|
||||
{
|
||||
// to sanity checks
|
||||
if (0==mirowire_master_address_size) { // can't send address
|
||||
return;
|
||||
}
|
||||
|
||||
// initial setup
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge)
|
||||
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER),0); // reset timer counter
|
||||
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
|
||||
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
|
||||
timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing
|
||||
|
||||
// send '1' start bit
|
||||
microwire_master_send_bit(true); // send start bit
|
||||
// send two bits operation code
|
||||
if (operation&0x2) { // send first bit (MSb first)
|
||||
microwire_master_send_bit(true); // send '1'
|
||||
} else {
|
||||
microwire_master_send_bit(false); // send '2'
|
||||
}
|
||||
if (operation&0x1) { // send second bit (LSb last)
|
||||
microwire_master_send_bit(true); // send '1'
|
||||
} else {
|
||||
microwire_master_send_bit(false); // send '2'
|
||||
}
|
||||
|
||||
// send address
|
||||
for (uint8_t bit = mirowire_master_address_size; bit > 0; bit--) {
|
||||
if ((address>>(bit-1))&0x01) {
|
||||
microwire_master_send_bit(true); // send '1' address bit
|
||||
} else {
|
||||
microwire_master_send_bit(false); // send '0' address bit
|
||||
}
|
||||
}
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low (could be floating)
|
||||
}
|
||||
|
||||
/** stop microwire communication and end all activities */
|
||||
static void microwire_master_stop(void)
|
||||
{
|
||||
timer_disable_counter(TIM(MICROWIRE_MASTER_TIMER)); // disable timer
|
||||
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER),0); // reset timer counter
|
||||
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
|
||||
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is idle low
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SDO_PORT), GPIO(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low
|
||||
}
|
||||
|
||||
|
||||
/** read bit from microwire communication
|
||||
* @return bit value (true = '1', false = '0')
|
||||
*/
|
||||
static bool microwire_master_read_bit(void)
|
||||
{
|
||||
microwire_master_wait_clock(); // wait for clock timing
|
||||
gpio_set(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to output data
|
||||
microwire_master_wait_clock(); // wait for signal to be stable
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // set clock low again
|
||||
return 0!=gpio_get(GPIO(MICROWIRE_MASTER_SDI_PORT), GPIO(MICROWIRE_MASTER_SDI_PIN)); // read input signal
|
||||
}
|
||||
|
||||
void microwire_master_read(uint32_t address, uint16_t* data, size_t length)
|
||||
{
|
||||
// to sanity checks
|
||||
if (NULL==data || 0==length || 0==mirowire_master_address_size) { // can't save data
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x02, address); // send '10' READ instruction and memory address
|
||||
|
||||
// there should already be a '0' dummy bit
|
||||
if (0!=gpio_get(GPIO(MICROWIRE_MASTER_SDI_PORT), GPIO(MICROWIRE_MASTER_SDI_PIN))) { // the dummy bit wasn't '0'
|
||||
goto clean;
|
||||
}
|
||||
|
||||
// read data
|
||||
for (size_t i=0; i<length; i++) {
|
||||
for (uint8_t b=(mirowire_master_organization_x16 ? 16 : 8); b>0; b--) {
|
||||
if (microwire_master_read_bit()) { // read bit, MSb first
|
||||
data[i] |= (1<<(b-1)); // set bit
|
||||
} else {
|
||||
data[i] &= ~(1<<(b-1)); // clear bit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clean:
|
||||
microwire_master_stop(); // stop communication and clean up
|
||||
}
|
||||
|
||||
void microwire_master_write_enable(void)
|
||||
{
|
||||
// to sanity checks
|
||||
if (mirowire_master_address_size<2) { // can't send '11...' address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x0, 0x3<<(mirowire_master_address_size-2)); // send '00' WEN operation code and '11...' address
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_write_disable(void)
|
||||
{
|
||||
// to sanity checks
|
||||
if (mirowire_master_address_size<2) { // can't send '00...' address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x0, 0); // send '00' WDS operation code and '00...' address
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_write(uint32_t address, uint16_t data)
|
||||
{
|
||||
// to sanity checks
|
||||
if (0==mirowire_master_address_size) { // can't send address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x01, address); // send '01' WRITE operation code and memory address
|
||||
|
||||
// write data (MSb first)
|
||||
for (uint8_t b=(mirowire_master_organization_x16 ? 16 : 8); b>0; b--) {
|
||||
if (data&(1<<(b-1))) { // bit is set
|
||||
microwire_master_send_bit(true); // send '1' data bit
|
||||
} else {
|
||||
microwire_master_send_bit(false); // send '0' data bit
|
||||
}
|
||||
}
|
||||
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_wait_ready(void)
|
||||
{
|
||||
|
||||
// initial setup
|
||||
gpio_clear(GPIO(MICROWIRE_MASTER_SCK_PORT), GPIO(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge)
|
||||
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER),0); // reset timer counter
|
||||
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
|
||||
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
|
||||
timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing
|
||||
|
||||
|
||||
// SDI low on busy, high on ready, clock is ignored
|
||||
while (!microwire_master_read_bit()); // wait until slave is ready
|
||||
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_erase(uint32_t address)
|
||||
{
|
||||
// sanity checks
|
||||
if (0==mirowire_master_address_size) { // can't send address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x03, address); // send '11' ERASE operation code and memory address
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_erase_all(void)
|
||||
{
|
||||
// sanity checks
|
||||
if (mirowire_master_address_size<2) { // can't send '11...' address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x00, 0x2<<(mirowire_master_address_size-2)); // send '00' ERAL operation code and '10...' address
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
||||
|
||||
void microwire_master_write_all(uint16_t data)
|
||||
{
|
||||
// sanity checks
|
||||
if (0==mirowire_master_address_size) { // can't send address
|
||||
return;
|
||||
}
|
||||
|
||||
microwire_master_start(0x00, 0x1<<(mirowire_master_address_size-2)); // send '00' WRAL operation code and '01...' address
|
||||
// write data (MSb first)
|
||||
for (uint8_t b=(mirowire_master_organization_x16 ? 16 : 8); b>0; b--) {
|
||||
if (data&(1<<(b-1))) { // bit is set
|
||||
microwire_master_send_bit(true); // send '1' data bit
|
||||
} else {
|
||||
microwire_master_send_bit(false); // send '0' data bit
|
||||
}
|
||||
}
|
||||
|
||||
microwire_master_stop(); // clean up
|
||||
}
|
@ -1,68 +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 using microwore as master (API)
|
||||
* @file microwire_master.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO @ref microwire_master_gpio, timer @ref microwire_master_timer
|
||||
* microwire is a 3-Wire half-duplex synchronous bus. It is very similar to SPI without fixed length messages (bit-wised).
|
||||
* @note the user has to handle the slave select pin (high during operations) so to be able to handle multiple slaves.
|
||||
* @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup microwire peripheral
|
||||
* @param[in] frequency clock frequency in Hz
|
||||
* @param[in] organization_x16 if x16 memory organization (16-bits) is used, or x8 (8-bits)
|
||||
* @param[in] address_size address size in bits
|
||||
* @note frequency practically limited to 500 kHz due to the software implementation nature
|
||||
*/
|
||||
void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size);
|
||||
/** read data from slave memory
|
||||
* @param[in] address memory address of data to read
|
||||
* @param[out] data array to store read data
|
||||
* @param[in] length number of data bytes/words to read
|
||||
*/
|
||||
void microwire_master_read(uint32_t address, uint16_t* data, size_t length);
|
||||
/** enable write and erase operations
|
||||
* @note on slave boot write is disable to prevent corruption
|
||||
*/
|
||||
void microwire_master_write_enable(void);
|
||||
/** disable write and erase operations
|
||||
* @note this should be done after every complete write operation to protect against corruption
|
||||
*/
|
||||
void microwire_master_write_disable(void);
|
||||
/** write data to slave memory
|
||||
* @param[in] address memory address of data to read
|
||||
* @param[in] data byte/word to write
|
||||
* @note after each write and before the next operation user should wait for the slave to be ready
|
||||
*/
|
||||
void microwire_master_write(uint32_t address, uint16_t data);
|
||||
/** wait until slave is ready after a write or erase */
|
||||
void microwire_master_wait_ready(void);
|
||||
/** erase memory
|
||||
* @param[in] address memory address of data to read
|
||||
* @note after each erase and before the next operation user should wait for the slave to be ready
|
||||
*/
|
||||
void microwire_master_erase(uint32_t address);
|
||||
/** erase all memory
|
||||
* @note after each erase and before the next operation user should wait for the slave to be ready
|
||||
*/
|
||||
void microwire_master_erase_all(void);
|
||||
/** write data to all slave memory
|
||||
* @param[in] data byte/word to write
|
||||
* @note after each write and before the next operation user should wait for the slave to be ready
|
||||
*/
|
||||
void microwire_master_write_all(uint16_t data);
|
@ -1,140 +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/>.
|
||||
*
|
||||
*/
|
||||
/** SSD1306 OLED library (code)
|
||||
* @file oled_ssd1306.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: I2C @ref oled_ssd1306_i2c
|
||||
*/
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean type
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencm3/stm32/i2c.h> // I2C library
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // global utilities
|
||||
#include "oled_ssd1306.h" // OLED definitions
|
||||
#include "i2c_master.h" // I2C header and definitions
|
||||
|
||||
/** SSD1306 OLED display I2C slave address */
|
||||
#define OLED_SSD1306_SLAVE 0x3c
|
||||
|
||||
/** @defgroup oled_ssd1306_i2c I2C peripheral to communicate with the SSD1306 OLED
|
||||
* @{
|
||||
*/
|
||||
#define OLED_SSD1306_I2C I2C1 /**< I2C peripheral */
|
||||
/** @} */
|
||||
|
||||
bool oled_ssd1306_setup(void)
|
||||
{
|
||||
if (!i2c_master_check_signals(OLED_SSD1306_I2C)) { // check if there are pull-ups to operator I2C
|
||||
return false;
|
||||
}
|
||||
i2c_master_setup(OLED_SSD1306_I2C, 400); // setup I2C bus ( SSD1306 supports an I2C cleck up to 400 kHz)
|
||||
const uint8_t oled_init[] = {
|
||||
0x00, // control byte: continuous (multiple byes), command
|
||||
0xae, // Set Display ON/OFF: OFF
|
||||
// hardware configuration
|
||||
0xa8, 0x3f, // Set Multiplex Ratio: 64
|
||||
0xd3, 0x00, // Set Display Offset: 0
|
||||
0xa1, // Set Segment Re-map: column address 0 is mapped to SEG127
|
||||
0xc8, // Set COM Output Scan Direction: normal mode (RESET) Scan from COM[N-1] to COM[0]
|
||||
0xda, 0x12, // Set COM Pins Hardware Configuration: Alternative COM pin configuration, Disable COM Left/Right remap
|
||||
0x40, // Set Display Start Line: start line register from 0
|
||||
// fundamental commands
|
||||
0x81, 0xff, // Set Contrast Control: 256
|
||||
0xa6, // Set Normal/Inverse Display: Normal display (RESET)
|
||||
// Timing & Driving Scheme Setting
|
||||
0xd5, 0xf0, // Set Display Clock Divide Ratio/Oscillator Frequency: Divide ratio=129, F_OSC=1
|
||||
0xd9, 0x22, // Set Pre-charge Period: Phase 1=2 DCLK, Phase 2=2DCLK
|
||||
0xdb, 0x20, // Set V_COMH Deselect Level: ~0.77xV_CC
|
||||
// Charge Pump
|
||||
0x8d, 0x14, // Charge Pump Setting: Enable Charge Pump
|
||||
// Addressing Setting
|
||||
0x20, 0x00 // Set Memory Addressing Mode: Horizontal Addressing Mode
|
||||
}; // command to initialize the display
|
||||
return I2C_MASTER_RC_NONE==i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_init, LENGTH(oled_init)); // send command to initialize display
|
||||
}
|
||||
|
||||
void oled_ssd1306_on(void)
|
||||
{
|
||||
const uint8_t oled_display_on[] = {
|
||||
0x80, // control byte: no continuation, command
|
||||
0xaf, // Set Display ON/OFF: ON
|
||||
}; // command to switch on display
|
||||
i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_display_on, LENGTH(oled_display_on)); // sent command to switch on display
|
||||
}
|
||||
|
||||
void oled_ssd1306_off(void)
|
||||
{
|
||||
const uint8_t oled_display_off[] = {
|
||||
0x80, // control byte: no continuation, command
|
||||
0xae, // Set Display ON/OFF: OFF
|
||||
}; // command to switch off display
|
||||
i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_display_off, LENGTH(oled_display_off)); // sent command to switch onff display
|
||||
}
|
||||
|
||||
void oled_ssd1306_test(void)
|
||||
{
|
||||
const uint8_t oled_entire_display_on[] = {
|
||||
0x80, // control byte: no continuation, command
|
||||
0xa5 // Entire Display ON: Entire display ON Output ignores RAM content
|
||||
}; // command to set entire display on
|
||||
i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_entire_display_on, LENGTH(oled_entire_display_on)); // send command to switch entire display on
|
||||
oled_ssd1306_on(); // set display on
|
||||
sleep_ms(200); // let is on for a bit (enough for the user to see it is completely on
|
||||
oled_ssd1306_off(); // set display off
|
||||
const uint8_t oled_entire_display_ram[] = {
|
||||
0x80, // control byte: no continuation, command
|
||||
0xa4 // Entire Display ON: Resume to RAM content display
|
||||
}; // command to display RAM
|
||||
i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_entire_display_ram, LENGTH(oled_entire_display_ram)); // send command to display RAM
|
||||
}
|
||||
|
||||
void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length)
|
||||
{
|
||||
// verify input
|
||||
if (0==display_length || NULL==display_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t oled_start_page[] = {
|
||||
0x00, // control byte: continuous (multiple byes), command
|
||||
0xb0, // Set Page Start Address for Page Addressing Mode: PAGE0
|
||||
0x00, // Set Lower Column Start Address for Page Addressing Mode: 0
|
||||
0x10 // Set Higher Column Start Address for Page Addressing Mode: 0
|
||||
}; // command to set addressing mode
|
||||
i2c_master_slave_write(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, oled_start_page, LENGTH(oled_start_page)); // send command to set addressing mode
|
||||
if (I2C_MASTER_RC_NONE!=i2c_master_start(OLED_SSD1306_I2C)) { // send start condition
|
||||
return;
|
||||
}
|
||||
if (I2C_MASTER_RC_NONE!=i2c_master_select_slave(OLED_SSD1306_I2C, OLED_SSD1306_SLAVE, false, true)) { // select OLED display
|
||||
return;
|
||||
}
|
||||
const uint8_t oled_data[] = {
|
||||
0x40, // control byte: continuous (multiple byes), data
|
||||
};
|
||||
if (I2C_MASTER_RC_NONE!=i2c_master_write(OLED_SSD1306_I2C, oled_data, LENGTH(oled_data))) { // send data header
|
||||
return;
|
||||
}
|
||||
if (I2C_MASTER_RC_NONE!=i2c_master_write(OLED_SSD1306_I2C, display_data, display_length)) { // send template picture to display
|
||||
return;
|
||||
}
|
||||
i2c_master_stop(OLED_SSD1306_I2C); // send stop condition
|
||||
}
|
||||
|
@ -1,36 +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/>.
|
||||
*
|
||||
*/
|
||||
/** SSD1306 OLED library (API)
|
||||
* @file oled_ssd1306.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2018
|
||||
* @note peripherals used: I2C @ref oled_ssd1306_i2c
|
||||
*/
|
||||
|
||||
/** setup OLED display
|
||||
* @return if the display setup is successful, else the display is probably not on the I2C bus
|
||||
*/
|
||||
bool oled_ssd1306_setup(void);
|
||||
/** switch OLED display on */
|
||||
void oled_ssd1306_on(void);
|
||||
/** switch OLED display off */
|
||||
void oled_ssd1306_off(void);
|
||||
/** test OLED display: switch entire screen on for a brief time */
|
||||
void oled_ssd1306_test(void);
|
||||
/** send data to display to OLED display
|
||||
* @param[in] display_data data to display (first byte is left column, MSb is top pixel, warps pages)
|
||||
* @param[in] display_length length of data to display
|
||||
*/
|
||||
void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length);
|
@ -1,513 +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 for 1-wire protocol as master
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017-2018
|
||||
* @note peripherals used: timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio
|
||||
* @note overdrive mode is not provided
|
||||
* @implements 1-Wire protocol description from Book of iButton Standards
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean type
|
||||
#include <stddef.h> // NULL definition
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer library
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // help macros
|
||||
#include "interrupt.h" // runtime interrupt table
|
||||
#include "onewire_master.h" // own definitions
|
||||
|
||||
/** @defgroup onewire_master_timer timer used to measure 1-wire signal timing
|
||||
* @{
|
||||
*/
|
||||
#define ONEWIRE_MASTER_TIMER 5 /**< timer ID */
|
||||
/** @} */
|
||||
|
||||
/** set if the timer ISR should be set in the interrupt table instead of the vector table
|
||||
* @note the vector table is faster, but doesn't allow to change the ISR
|
||||
*/
|
||||
#define ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE false
|
||||
|
||||
/** state of 1-Wire communication */
|
||||
volatile enum {
|
||||
ONEWIRE_STATE_IDLE, /**< no current communication */
|
||||
ONEWIRE_STATE_DONE, /**< communication complete */
|
||||
ONEWIRE_STATE_ERROR, /**< communication error */
|
||||
ONEWIRE_STATE_MASTER_RESET, /**< reset pulse started */
|
||||
ONEWIRE_STATE_SLAVE_PRESENCE, /**< waiting for slave response to reset pulse */
|
||||
ONEWIRE_STATE_MASTER_WRITE, /**< master is writing bits */
|
||||
ONEWIRE_STATE_MASTER_READ, /**< master is reading bits */
|
||||
ONEWIRE_MAX /** to count the number of possible states */
|
||||
} onewire_master_state = ONEWIRE_STATE_IDLE;
|
||||
|
||||
static volatile bool slave_presence = false; /**< if slaves have been detected */
|
||||
static uint8_t* buffer = NULL; /**< input/output buffer for read/write commands/functions */
|
||||
static uint32_t buffer_size = 0; /**< size of buffer in bits */
|
||||
static volatile uint32_t buffer_bit = 0; /**< number of bits read/written */
|
||||
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
|
||||
static void (*isr_backup)(void) = NULL; /**< backup for the existing timer ISR */
|
||||
static bool irq_backup = false; /**< backup for the existing timer IRQ */
|
||||
#endif
|
||||
|
||||
/** interrupt service routine called for timer */
|
||||
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
|
||||
static void onewire_master_timer_isr(void)
|
||||
#else
|
||||
void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
|
||||
#endif
|
||||
{
|
||||
if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF); // clear flag
|
||||
switch (onewire_master_state) {
|
||||
case ONEWIRE_STATE_MASTER_RESET: // reset pulse has been started
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // enable compare interrupt for presence detection
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // set signal high again for slaves to respond
|
||||
onewire_master_state = ONEWIRE_STATE_SLAVE_PRESENCE; // set new state
|
||||
break;
|
||||
case ONEWIRE_STATE_SLAVE_PRESENCE: // waiting for slave presence but none received
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // disable compare interrupt for presence detection
|
||||
onewire_master_state = ONEWIRE_STATE_DONE; // go to next state
|
||||
break;
|
||||
case ONEWIRE_STATE_MASTER_READ: // end of time slot and recovery time for reading bit
|
||||
case ONEWIRE_STATE_MASTER_WRITE: // end of time slot and recovery time for writing bit
|
||||
if (buffer_bit<buffer_size) { // check if byte to read/write are remaining
|
||||
gpio_clear(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal low to start next slot
|
||||
} else { // all bytes read/written
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // disable compare interrupt for master pull low
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt for read/write bit
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // disable compare interrupt for end of slot
|
||||
onewire_master_state = ONEWIRE_STATE_DONE; // set end state
|
||||
}
|
||||
break;
|
||||
default: // unknown state for this stage
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // disable all compare interrupt
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable all compare interrupt
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // disable all compare interrupt
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // disable all compare interrupt
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal high (idle state)
|
||||
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
|
||||
}
|
||||
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF)) { // compare event happened for master pull low end for read
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear flag
|
||||
switch (onewire_master_state) {
|
||||
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
|
||||
break;
|
||||
default: // unknown state for this stage
|
||||
break; // let the overflow handle the error if any
|
||||
}
|
||||
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF)) { // compare event happened for bit sampling/setting
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear flag
|
||||
switch (onewire_master_state) {
|
||||
case ONEWIRE_STATE_MASTER_WRITE: // master has to write a bit
|
||||
if (buffer_bit<buffer_size) { // check if byte to send are remaining
|
||||
if (buffer[buffer_bit/8]&(1<<(buffer_bit%8))) { // check bit (LSb first)
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // set signal high again to write "1"
|
||||
}
|
||||
buffer_bit++; // got to next bit
|
||||
} else {
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt
|
||||
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit set by slave
|
||||
if (buffer_bit<buffer_size) { // check if byte to send are remaining
|
||||
if (gpio_get(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN))) { // check if the slave kept it low
|
||||
buffer[buffer_bit/8] |= (1<<(buffer_bit%8)); // save bit "1"
|
||||
} else {
|
||||
buffer[buffer_bit/8] &= ~(1<<(buffer_bit%8)); // save bit "0"
|
||||
}
|
||||
buffer_bit++; // got to next bit
|
||||
} else {
|
||||
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt
|
||||
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
|
||||
}
|
||||
break;
|
||||
default: // unknown state for this stage
|
||||
break; // let the overflow handle the error if any
|
||||
}
|
||||
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF)) { // compare event happened for end to time slot
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear flag
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
|
||||
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF)) { // compare event happened for slave presence detection
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear flag
|
||||
if (gpio_get(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN))) { // check is a slave let its presence know by pulling low
|
||||
slave_presence = false; // remember no slave(s) responded
|
||||
} else {
|
||||
slave_presence = true; // remember slave(s) responded
|
||||
}
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
}
|
||||
|
||||
void onewire_master_setup(void)
|
||||
{
|
||||
// setup GPIO with external interrupt
|
||||
rcc_periph_clock_enable(RCC_GPIO(ONEWIRE_MASTER_PORT)); // enable clock for GPIO peripheral
|
||||
gpio_set(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // idle is high (using pull-up resistor)
|
||||
gpio_set_mode(GPIO(ONEWIRE_MASTER_PORT), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
|
||||
|
||||
// setup timer to generate/measure signal timing
|
||||
rcc_periph_clock_enable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(ONEWIRE_MASTER_TIMER)); // reset timer state
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to configure it
|
||||
timer_set_mode(TIM(ONEWIRE_MASTER_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(ONEWIRE_MASTER_TIMER), 1-1); // don't use prescale since this 16 bits timer allows to wait > 480 us used for the reset pulse ( 1/(72E6/1/(2**16))=910us )
|
||||
|
||||
// use comparator to time signal (without using the output), starting at slot start
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear flag
|
||||
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC1, 1*(rcc_ahb_frequency/1000000)-1); // use compare function to time master pulling low when reading (1 < Tlowr < 15)
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear flag
|
||||
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC2, 7*(rcc_ahb_frequency/1000000)-1); // use compare function to read or write 0 or 1 (1 < Trw < 15)
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear flag
|
||||
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC3, 62*(rcc_ahb_frequency/1000000)-1); // use compare function to end time slot (60 < Tslot < 120), this will be followed by a recovery time (end of timer)
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear flag
|
||||
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC4, (70-10)*(rcc_ahb_frequency/1000000)-1); // use compare function to detect slave presence (15 < Tpdh < 60 + 60 < Tpdl < 240), with hand tuning
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF); // clear update (overflow) flag
|
||||
timer_update_on_overflow(TIM(ONEWIRE_MASTER_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow
|
||||
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
|
||||
isr_backup = interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)]; // backup timer ISR
|
||||
interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)] = &onewire_master_timer_isr; // set the 1-wire timer ISR
|
||||
irq_backup = nvic_get_irq_enabled(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // backup timer IRQ setting
|
||||
#endif
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // catch interrupt in service routine
|
||||
|
||||
slave_presence = false; // reset state
|
||||
onewire_master_state = ONEWIRE_STATE_IDLE; // reset state
|
||||
}
|
||||
|
||||
void onewire_master_release(void)
|
||||
{
|
||||
// release timer
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
|
||||
timer_reset(TIM(ONEWIRE_MASTER_TIMER)); // reset timer state
|
||||
rcc_periph_clock_disable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // disable clock for timer peripheral
|
||||
|
||||
// release GPIO
|
||||
gpio_set_mode(GPIO(ONEWIRE_MASTER_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(ONEWIRE_MASTER_PIN)); // put back to input floating
|
||||
|
||||
// disable timer ISR
|
||||
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
|
||||
if (!irq_backup) { // don't disable the IRQ if there was already enabled
|
||||
nvic_disable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // stop timer IRQ
|
||||
}
|
||||
interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)] = isr_backup; // set back original timer ISR
|
||||
#else
|
||||
nvic_disable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // stop timer IRQ
|
||||
#endif
|
||||
}
|
||||
|
||||
bool onewire_master_reset(void)
|
||||
{
|
||||
// prepare timer
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
|
||||
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER),0); // reset counter
|
||||
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), 490*(rcc_ahb_frequency/1000000)-1); // set timeout to > 480 us (480 < Trst)
|
||||
|
||||
slave_presence = false; // reset state
|
||||
onewire_master_state = ONEWIRE_STATE_MASTER_RESET; // set new state
|
||||
|
||||
gpio_set_mode(GPIO(ONEWIRE_MASTER_PORT), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
|
||||
gpio_clear(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal low to start reset (it's not important if it was low in the first place since the reset pulse has no maximum time)
|
||||
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
|
||||
|
||||
while (onewire_master_state!=ONEWIRE_STATE_DONE && onewire_master_state!=ONEWIRE_STATE_ERROR) { // wait until reset procedure completed
|
||||
__WFI(); // go to sleep
|
||||
}
|
||||
if (ONEWIRE_STATE_ERROR==onewire_master_state) { // an error occurred
|
||||
return false;
|
||||
}
|
||||
|
||||
return slave_presence;
|
||||
}
|
||||
|
||||
/** write bits on 1-Wire bus
|
||||
* @warning buffer_size must be set to the number of bits to writen and buffer must contain the data to write
|
||||
* @return if write succeeded
|
||||
*/
|
||||
static bool onewire_master_write(void)
|
||||
{
|
||||
buffer_bit = 0; // reset bit index
|
||||
onewire_master_state = ONEWIRE_STATE_MASTER_WRITE; // set new state
|
||||
|
||||
// prepare timer
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
|
||||
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER), 0); // reset counter
|
||||
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), TIM_CCR3(TIM(ONEWIRE_MASTER_TIMER))+2*(rcc_ahb_frequency/1000000)); // set time for new time slot (recovery timer Trec>1, after time slot end )
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // enable compare interrupt for bit setting
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
|
||||
|
||||
// start writing
|
||||
gpio_set_mode(GPIO(ONEWIRE_MASTER_PORT), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
|
||||
gpio_clear(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
|
||||
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
|
||||
while (onewire_master_state!=ONEWIRE_STATE_DONE && onewire_master_state!=ONEWIRE_STATE_ERROR) { // wait until write procedure completed
|
||||
__WFI(); // go to sleep
|
||||
}
|
||||
if (ONEWIRE_STATE_ERROR==onewire_master_state) { // an error occurred
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** read bits on 1-Wire bus
|
||||
* @warning buffer_size must be set to the number of bits to read
|
||||
* @return if read succeeded
|
||||
*/
|
||||
static bool onewire_master_read(void)
|
||||
{
|
||||
if (0==buffer_size) { // check input
|
||||
return false;
|
||||
}
|
||||
buffer_bit = 0; // reset bit index
|
||||
onewire_master_state = ONEWIRE_STATE_MASTER_READ; // set new state
|
||||
|
||||
// prepare timer
|
||||
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
|
||||
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER), 0); // reset counter
|
||||
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), TIM_CCR3(TIM(ONEWIRE_MASTER_TIMER))+2*(rcc_ahb_frequency/1000000)); // set time for new time slot (recovery timer Trec>1, after time slot end )
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // enable compare interrupt for stop pulling low
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // enable compare interrupt for bit setting
|
||||
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear output compare flag
|
||||
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
|
||||
|
||||
// start reading
|
||||
gpio_set_mode(GPIO(ONEWIRE_MASTER_PORT), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
|
||||
gpio_clear(GPIO(ONEWIRE_MASTER_PORT),GPIO(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
|
||||
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
|
||||
while (onewire_master_state!=ONEWIRE_STATE_DONE && onewire_master_state!=ONEWIRE_STATE_ERROR) { // wait until read procedure completed
|
||||
__WFI(); // go to sleep
|
||||
}
|
||||
if (ONEWIRE_STATE_ERROR==onewire_master_state) { // an error occurred
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t onewire_master_crc(uint8_t* data, uint32_t length)
|
||||
{
|
||||
if (NULL==data || 0==length) { // check input
|
||||
return 0; // wrong input
|
||||
}
|
||||
|
||||
uint8_t crc = 0x00; // initial value
|
||||
for (uint8_t i=0; i<length; i++) { // go through every byte
|
||||
crc ^= data[i]; // XOR byte
|
||||
for (uint8_t b=0; b<8; b++) { // go through every bit
|
||||
if (crc&0x01) { // least significant bit is set (we are using the reverse way)
|
||||
crc = (crc>>1)^0x8C; // // shift to the right (for the next bit) and XOR with (reverse) polynomial
|
||||
} else {
|
||||
crc >>= 1; // just shift right (for the next bit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool onewire_master_read_byte(uint8_t* data)
|
||||
{
|
||||
if (NULL==data) { // check input
|
||||
return false; // wrong input
|
||||
}
|
||||
|
||||
// read data
|
||||
buffer_size = 8; // save number of bits to read (1 byte)
|
||||
buffer = data; // set the buffer to the data to write
|
||||
if (!onewire_master_read()) { // read bits from slave
|
||||
return false; // an error occurred
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onewire_master_write_byte(uint8_t data)
|
||||
{
|
||||
// send data byte
|
||||
buffer_size = 8; // function command is only one byte
|
||||
buffer = &data; // set the buffer to the function code
|
||||
if (!onewire_master_write()) { // send command
|
||||
return false; // an error occurred
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onewire_master_function_read(uint8_t function, uint8_t* data, uint32_t bits)
|
||||
{
|
||||
// send function command
|
||||
if (!onewire_master_write_byte(function)) {
|
||||
return false; // an error occurred
|
||||
}
|
||||
|
||||
if (NULL==data || 0==bits) { // there is no data to read
|
||||
return true; // operation completed
|
||||
}
|
||||
|
||||
// read data
|
||||
buffer_size = bits; // save number of bits to read
|
||||
buffer = data; // set the buffer to the data to write
|
||||
if (!onewire_master_read()) { // read bits from slave
|
||||
return false; // an error occurred
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onewire_master_function_write(uint8_t function, uint8_t* data, uint32_t bits)
|
||||
{
|
||||
// send function command
|
||||
if (!onewire_master_write_byte(function)) {
|
||||
return false; // an error occurred
|
||||
}
|
||||
|
||||
if (NULL==data || 0==bits) { // there is no data to read
|
||||
return true; // operation completed
|
||||
}
|
||||
|
||||
// copy data from user buffer
|
||||
buffer_size = bits; // save number of bits to write
|
||||
buffer = data; // set the buffer to the data to write
|
||||
// write data
|
||||
if (!onewire_master_write()) { // read bits from slave
|
||||
return false; // an error occurred
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t onewire_master_rom_read(void)
|
||||
{
|
||||
uint8_t rom_code[8] = {0}; // to store 64 bits ROM code
|
||||
if (!onewire_master_function_read(0x33, rom_code, 64)) { // read ROM code (I'm cheating because the ROM command isn't a function command, but it works the same way in the end)
|
||||
return 0; // an error occurred
|
||||
}
|
||||
if (onewire_master_crc(rom_code, LENGTH(rom_code))) { // verify checksum
|
||||
return 0; // checksum is wrong (not 0)
|
||||
}
|
||||
|
||||
// return ROM code
|
||||
uint64_t code = 0;
|
||||
for (uint32_t i=0; i<8; i++) {
|
||||
code += (uint64_t)rom_code[i]<<(8*i); // add byte
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
bool onewire_master_rom_search(uint64_t* code, bool alarm)
|
||||
{
|
||||
static uint8_t conflict_last = 64; // on which bit has the conflict been detected (64 means there hasn't been)
|
||||
uint8_t conflict_current = 64; // to remember on which bit the last unknown conflict has been detected
|
||||
uint8_t bits[1] = {0}; // small buffer to store the bits used to search the ROM codes
|
||||
|
||||
// send SEARCH ROM command
|
||||
uint8_t command = 0xf0; // SEARCH ROM command
|
||||
if (alarm) { // looking only for ROM codes for slaves in alarm state
|
||||
command = 0xec; // use ALARM SEARCH ROM command instead
|
||||
}
|
||||
if (!onewire_master_function_read(command, NULL, 0)) { // send SEARCH ROM command
|
||||
goto end; // an error occurred
|
||||
}
|
||||
|
||||
if (conflict_last>=64) { // no previous conflict has been detected
|
||||
*code = 0; // restart search codes
|
||||
}
|
||||
|
||||
buffer = bits; // buffer to read up to two bits
|
||||
for (uint8_t bit=0; bit<64; bit++) { // go through all 64 bits ROM code
|
||||
buffer_size = 2; // read two first bits to detect conflict
|
||||
if (!onewire_master_read()) { // read ROM ID from slave
|
||||
goto end; // an error occurred
|
||||
}
|
||||
switch (buffer[0]&0x03) { // check 2 bits received
|
||||
case 0: // collision detected
|
||||
if (bit==conflict_last) { // this conflict is known
|
||||
*code |= (((uint64_t)1)<<bit); // use 0 as next bit
|
||||
} else { // unknown conflict
|
||||
conflict_current = bit; // remember conflict
|
||||
*code &= ~(((uint64_t)1)<<bit); // use 1 as next bit
|
||||
}
|
||||
break;
|
||||
case 1: // no conflict, valid bit is 1
|
||||
*code |= (((uint64_t)1)<<bit); // remember valid bit 1
|
||||
break;
|
||||
case 2: // no conflict, valid bit is 0
|
||||
*code &= ~(((uint64_t)1)<<bit); // remember valid bit 0
|
||||
break;
|
||||
default: // two 1's indicate there is no slave
|
||||
conflict_current = 64; // remember there was no conflict because there is no slave
|
||||
goto end; // an error has occurred
|
||||
}
|
||||
buffer_size = 1; // to send next bit
|
||||
buffer[0] = ((*code)>>bit); // set bit to send
|
||||
if (!onewire_master_write()) { // send bit
|
||||
goto end; // an error has occurred
|
||||
}
|
||||
}
|
||||
// verify ROM code
|
||||
uint8_t rom_code[8] = {0}; // to store ROM code
|
||||
for (uint8_t i=0; i<LENGTH(rom_code); i++) {
|
||||
rom_code[i] = (*code)>>(8*i); // split and save last code in ROM code
|
||||
}
|
||||
if (onewire_master_crc(rom_code, LENGTH(rom_code))) { // verify checksum
|
||||
*code = 0; // return the last code found since it's valid
|
||||
}
|
||||
|
||||
end:
|
||||
conflict_last = conflict_current; // update the last seen and unknown conflict
|
||||
if (conflict_current<64) { // we have seen an unknown conflict
|
||||
return true; // tell there are more slaves
|
||||
} else { // no conflict seen
|
||||
return false; // no more slaves
|
||||
}
|
||||
}
|
||||
|
||||
bool onewire_master_rom_skip(void)
|
||||
{
|
||||
if (!onewire_master_function_write(0xcc, NULL, 0)) { // send SKIP ROM command
|
||||
return false; // an error occurred
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onewire_master_rom_match(uint64_t code)
|
||||
{
|
||||
uint8_t rom_code[8] = {0}; // to store ROM code
|
||||
for (uint8_t i=0; i<LENGTH(rom_code); i++) {
|
||||
rom_code[i] = code>>(8*i); // split and save code in ROM code
|
||||
}
|
||||
if (!onewire_master_function_write(0x55, rom_code, 64)) { // send MATCH ROM command with ROM code
|
||||
return false; // an error occurred
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,387 +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 for 1-wire protocol as master
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017-2018
|
||||
* @note peripherals used: timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio
|
||||
* @note overdrive mode is not provided
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** @defgroup onewire_master_gpio GPIO used for 1-wire signal
|
||||
* @note external pull-up resistor on pin is required (< 5 kOhm)
|
||||
* @{
|
||||
*/
|
||||
#define ONEWIRE_MASTER_PORT C /**< GPIO port */
|
||||
#define ONEWIRE_MASTER_PIN 9 /**< GPIO pin */
|
||||
/** @} */
|
||||
|
||||
/** setup 1-wire peripheral
|
||||
*/
|
||||
void onewire_master_setup(void);
|
||||
/** release 1-wire peripheral
|
||||
*/
|
||||
void onewire_master_release(void);
|
||||
/** send reset pulse
|
||||
* @return if slaves have indicated their presence
|
||||
*/
|
||||
bool onewire_master_reset(void);
|
||||
/** compute CRC for 1-Wire
|
||||
* @note this CRC-8 uses normal polynomial 0x31, reverse polynomial 0x8C, start value 0x00
|
||||
* @param[in] data bytes on which to calculate CRC checksum on
|
||||
* @param[in] length number of bytes in data
|
||||
* @return computed CRC checksum
|
||||
*/
|
||||
uint8_t onewire_master_crc(uint8_t* data, uint32_t length);
|
||||
/** send READ ROM command and read ROM code response
|
||||
* @note user needs to send reset pulse before
|
||||
* @return ROM code read
|
||||
*/
|
||||
uint64_t onewire_master_rom_read(void);
|
||||
/** send SEARCH ROM command
|
||||
* @note user needs to send reset pulse before
|
||||
* @warning undefined behaviour if a ROM code different than the last found is provided
|
||||
* @param[in,out] code use 0 to start search ROM code from scratch, or last know value to search next; writes back next ROM code found, or 0 if error occurred
|
||||
* @param[in] alarm search only for ROM codes for slaves with an alarm flag set
|
||||
* @return if an additional slave has been detected
|
||||
* @warning when the code found is 0 it very probably means that the 1-wire line is not pulled up instead of actually having found a slave with ROM code 0
|
||||
*/
|
||||
bool onewire_master_rom_search(uint64_t* code, bool alarm);
|
||||
/** send SKIP ROM command (all slaves on the bus will be selected)
|
||||
* @note user needs to send reset pulse before
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_rom_skip(void);
|
||||
/** send MATCH ROM command to select a specific slave
|
||||
* @note user needs to send reset pulse before
|
||||
* @param[in] code ROM code of slave to select
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_rom_match(uint64_t code);
|
||||
/** read data byte
|
||||
* @note it is up to the user to send the reset pulse
|
||||
* @param[out] data buffer to save data read
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_read_byte(uint8_t* data);
|
||||
/** write data byte
|
||||
* @note it is up to the user to send the reset pulse
|
||||
* @param[in] data byte to write
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_write_byte(uint8_t data);
|
||||
/** issue function and read data
|
||||
* @note user needs to send a ROM command before
|
||||
* @param[in] function function command to send
|
||||
* @param[out] data buffer to save read bits (NULL if only the function command should be sent)
|
||||
* @param[in] bits number of bits to read (0 if only the function command should be sent)
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_function_read(uint8_t function, uint8_t* data, uint32_t bits);
|
||||
/** issue function and write data
|
||||
* @note user needs to send a ROM command before
|
||||
* @param[in] function function command to send
|
||||
* @param[out] data data to write (NULL if only the function command should be sent)
|
||||
* @param[in] bits number of bits to write (0 if only the function command should be sent)
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool onewire_master_function_write(uint8_t function, uint8_t* data, uint32_t bits);
|
||||
|
||||
/** device corresponding to a family code
|
||||
*/
|
||||
struct onewire_family_code_t {
|
||||
uint8_t code; /**< ROM ID code */
|
||||
const char* device; /**< device name(s) */
|
||||
};
|
||||
|
||||
/** list of possible devices corresponding to the family code
|
||||
* sources:
|
||||
* - http://owfs.org/index.php?page=family-code-list
|
||||
* - http://owfs.sourceforge.net/family.html
|
||||
* - https://www.maximintegrated.com/en/app-notes/index.mvp/id/155
|
||||
* - https://github.com/owfs/owfs-doc/wiki/1Wire-Device-List
|
||||
* - IDs seen or reported
|
||||
*/
|
||||
static const struct onewire_family_code_t onewire_family_codes[] = {
|
||||
{
|
||||
.code = 0x01,
|
||||
.device = "DS1990R/DS2401/DS2411/DS2490A",
|
||||
},
|
||||
{
|
||||
.code = 0x02,
|
||||
.device = "DS1991/DS1425",
|
||||
},
|
||||
{
|
||||
.code = 0x04,
|
||||
.device = "DS1994/DS2404",
|
||||
},
|
||||
{
|
||||
.code = 0x05,
|
||||
.device = "DS2405",
|
||||
},
|
||||
{
|
||||
.code = 0x06,
|
||||
.device = "DS1993",
|
||||
},
|
||||
{
|
||||
.code = 0x08,
|
||||
.device = "DS1992",
|
||||
},
|
||||
{
|
||||
.code = 0x09,
|
||||
.device = "DS1982/DS2502/DS2703/DS2704",
|
||||
},
|
||||
{
|
||||
.code = 0x0a,
|
||||
.device = "DS1995",
|
||||
},
|
||||
{
|
||||
.code = 0x0b,
|
||||
.device = "DS1985/DS2505",
|
||||
},
|
||||
{
|
||||
.code = 0x0c,
|
||||
.device = "DS1996",
|
||||
},
|
||||
{
|
||||
.code = 0x0f,
|
||||
.device = "DS1986/DS2506",
|
||||
},
|
||||
{
|
||||
.code = 0x10,
|
||||
.device = "DS1920/DS18S20",
|
||||
},
|
||||
{
|
||||
.code = 0x12,
|
||||
.device = "DS2406/DS2407",
|
||||
},
|
||||
{
|
||||
.code = 0x14,
|
||||
.device = "DS1971/DS2430A",
|
||||
},
|
||||
{
|
||||
.code = 0x16,
|
||||
.device = "DS1954/DS1957",
|
||||
},
|
||||
{
|
||||
.code = 0x18,
|
||||
.device = "DS1963S/DS1962",
|
||||
},
|
||||
{
|
||||
.code = 0x1a,
|
||||
.device = "DS1963L",
|
||||
},
|
||||
{
|
||||
.code = 0x1b,
|
||||
.device = "DS2436",
|
||||
},
|
||||
{
|
||||
.code = 0x1c,
|
||||
.device = "DS28E04-100",
|
||||
},
|
||||
{
|
||||
.code = 0x1d,
|
||||
.device = "DS2423",
|
||||
},
|
||||
{
|
||||
.code = 0x1e,
|
||||
.device = "DS2437",
|
||||
},
|
||||
{
|
||||
.code = 0x1f,
|
||||
.device = "DS2409",
|
||||
},
|
||||
{
|
||||
.code = 0x20,
|
||||
.device = "DS2450",
|
||||
},
|
||||
{
|
||||
.code = 0x21,
|
||||
.device = "DS1921",
|
||||
},
|
||||
{
|
||||
.code = 0x22,
|
||||
.device = "DS1922",
|
||||
},
|
||||
{
|
||||
.code = 0x23,
|
||||
.device = "DS1973/DS2433",
|
||||
},
|
||||
{
|
||||
.code = 0x24,
|
||||
.device = "DS1904/DS2415",
|
||||
},
|
||||
{
|
||||
.code = 0x26,
|
||||
.device = "DS2438",
|
||||
},
|
||||
{
|
||||
.code = 0x27,
|
||||
.device = "DS2417",
|
||||
},
|
||||
{
|
||||
.code = 0x28,
|
||||
.device = "DS18B20",
|
||||
},
|
||||
{
|
||||
.code = 0x29,
|
||||
.device = "DS2408",
|
||||
},
|
||||
{
|
||||
.code = 0x2c,
|
||||
.device = "DS2890",
|
||||
},
|
||||
{
|
||||
.code = 0x2d,
|
||||
.device = "DS1972/DS2431",
|
||||
},
|
||||
{
|
||||
.code = 0x2e,
|
||||
.device = "DS2770",
|
||||
},
|
||||
{
|
||||
.code = 0x2f,
|
||||
.device = "DS28E01-100",
|
||||
},
|
||||
{
|
||||
.code = 0x30,
|
||||
.device = "DS2760/DS2761/DS2762",
|
||||
},
|
||||
{
|
||||
.code = 0x31,
|
||||
.device = "DS2720",
|
||||
},
|
||||
{
|
||||
.code = 0x32,
|
||||
.device = "DS2780",
|
||||
},
|
||||
{
|
||||
.code = 0x33,
|
||||
.device = "DS1961S/DS2432",
|
||||
},
|
||||
{
|
||||
.code = 0x34,
|
||||
.device = "DS2703",
|
||||
},
|
||||
{
|
||||
.code = 0x35,
|
||||
.device = "DS2755",
|
||||
},
|
||||
{
|
||||
.code = 0x36,
|
||||
.device = "DS2740",
|
||||
},
|
||||
{
|
||||
.code = 0x37,
|
||||
.device = "DS1977",
|
||||
},
|
||||
{
|
||||
.code = 0x3a,
|
||||
.device = "DS2413",
|
||||
},
|
||||
{
|
||||
.code = 0x3b,
|
||||
.device = "DS1825/MAX31826/MAX31850",
|
||||
},
|
||||
{
|
||||
.code = 0x3d,
|
||||
.device = "DS2781",
|
||||
},
|
||||
{
|
||||
.code = 0x41,
|
||||
.device = "DS1922/DS1923/DS2422",
|
||||
},
|
||||
{
|
||||
.code = 0x42,
|
||||
.device = "DS28EA00",
|
||||
},
|
||||
{
|
||||
.code = 0x43,
|
||||
.device = "DS28EC20",
|
||||
},
|
||||
{
|
||||
.code = 0x44,
|
||||
.device = "DS28E10",
|
||||
},
|
||||
{
|
||||
.code = 0x51,
|
||||
.device = "DS2751",
|
||||
},
|
||||
{
|
||||
.code = 0x7e,
|
||||
.device = "EDS00xx",
|
||||
},
|
||||
{
|
||||
.code = 0x81,
|
||||
.device = "DS1420/DS2490R/DS2490B",
|
||||
},
|
||||
{
|
||||
.code = 0x82,
|
||||
.device = "DS1425",
|
||||
},
|
||||
{
|
||||
.code = 0x84,
|
||||
.device = "DS2404S",
|
||||
},
|
||||
{
|
||||
.code = 0x89,
|
||||
.device = "DS1982U/DS2502",
|
||||
},
|
||||
{
|
||||
.code = 0x8b,
|
||||
.device = "DS1985U/DS2505",
|
||||
},
|
||||
{
|
||||
.code = 0x8f,
|
||||
.device = "DS1986U/DS2506",
|
||||
},
|
||||
{
|
||||
.code = 0xa0,
|
||||
.device = "mRS001",
|
||||
},
|
||||
{
|
||||
.code = 0xa1,
|
||||
.device = "mVM001",
|
||||
},
|
||||
{
|
||||
.code = 0xa2,
|
||||
.device = "mCM001",
|
||||
},
|
||||
{
|
||||
.code = 0xa6,
|
||||
.device = "mTS017",
|
||||
},
|
||||
{
|
||||
.code = 0xb1,
|
||||
.device = "mTC001",
|
||||
},
|
||||
{
|
||||
.code = 0xb2,
|
||||
.device = "mAM001",
|
||||
},
|
||||
{
|
||||
.code = 0xb3,
|
||||
.device = "DS2432/mTC002",
|
||||
},
|
||||
{
|
||||
.code = 0xfc,
|
||||
.device = "BAE0910/BAE0911",
|
||||
},
|
||||
{
|
||||
.code = 0xff,
|
||||
.device = "Swart LCD",
|
||||
}
|
||||
};
|
@ -1,409 +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 for 1-wire protocol as master (code)
|
||||
* @file onewire_slave.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO and timer @ref onewire_slave_timer, GPIO @ref onewire_slave_gpio
|
||||
* @note overdrive mode is not supported
|
||||
* @implements 1-Wire protocol description from Book of iButton Standards
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean type
|
||||
#include <stddef.h> // NULL definition
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer library
|
||||
#include <libopencm3/stm32/exti.h> // external interrupt library
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // help macros
|
||||
#include "onewire_slave.h" // own definitions
|
||||
|
||||
/** @defgroup onewire_slave_timer timer used to measure 1-wire signal timing
|
||||
* @{
|
||||
*/
|
||||
#define ONEWIRE_SLAVE_TIMER 2 /**< timer ID */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup onewire_slave_gpio GPIO used for 1-wire signal
|
||||
* @warning ensure no same pin number on other parts are used for external interrupts
|
||||
* @note external pull-up resistor on pin is required (< 5 kOhm), generally provided by the master
|
||||
* @{
|
||||
*/
|
||||
#define ONEWIRE_SLAVE_PORT A /**< GPIO port */
|
||||
#define ONEWIRE_SLAVE_PIN 4 /**< GPIO pin */
|
||||
/** @} */
|
||||
|
||||
/** state of 1-Wire communication */
|
||||
static volatile enum {
|
||||
ONEWIRE_STATE_IDLE, /**< no current communication */
|
||||
ONEWIRE_STATE_RESET, /**< reset pulse has been detected */
|
||||
ONEWIRE_STATE_WAIT_PRESENCE, /**< waiting before sending the presence pulse */
|
||||
ONEWIRE_STATE_PULSE_PRESENCE, /**< sending the presence pulse */
|
||||
ONEWIRE_STATE_ROM_COMMAND, /**< slave is reading ROM command bits */
|
||||
ONEWIRE_STATE_ROM_READ, /**< slave is sending ROM code in response to ROM command READ ROM */
|
||||
ONEWIRE_STATE_ROM_MATCH, /**< master is sending ROM code to select slave */
|
||||
ONEWIRE_STATE_ROM_SEARCH_TRUE, /**< master is searching ROM code, slave will send first bit (not negated) */
|
||||
ONEWIRE_STATE_ROM_SEARCH_FALSE, /**< master is searching ROM code, slave will send first bit (not negated) */
|
||||
ONEWIRE_STATE_ROM_SEARCH_SELECT, /**< master is searching ROM code, slave will read selected bit */
|
||||
ONEWIRE_STATE_FUNCTION_COMMAND, /**< slave is reading function command bits */
|
||||
ONEWIRE_STATE_FUNCTION_DATA, /**< waiting for user to provide data to transfer */
|
||||
ONEWIRE_STATE_FUNCTION_READ, /**< slave is reading bits */
|
||||
ONEWIRE_STATE_FUNCTION_WRITE, /**< slave is writing bits */
|
||||
ONEWIRE_MAX /** to count the number of possible states */
|
||||
} onewire_slave_state = ONEWIRE_STATE_IDLE;
|
||||
|
||||
static uint8_t onewire_slave_rom_code[8] = {0}; /**< slave ROM code */
|
||||
|
||||
volatile bool onewire_slave_function_code_received = false;
|
||||
volatile uint8_t onewire_slave_function_code = 0;
|
||||
volatile bool onewire_slave_transfer_complete = false;
|
||||
|
||||
static volatile uint8_t bits_buffer = 0; /**< buffer for the incoming bits (up to one byte) */
|
||||
static volatile uint32_t bits_bit = 0; /**< number of incoming bits */
|
||||
static volatile uint8_t* onewire_slave_transfer_data = NULL; /**< data to transfer (read or write) */
|
||||
static volatile uint32_t onewire_slave_transfer_bits = 0; /**< number of bits to transfer */
|
||||
|
||||
/** compute CRC for 1-Wire
|
||||
* @note this CRC-8 uses normal polynomial 0x31, reverse polynomial 0x8C, start value 0x00
|
||||
* @param[in] data bytes on which to calculate CRC checksum on
|
||||
* @param[in] length number of bytes in data
|
||||
* @return computed CRC checksum
|
||||
*/
|
||||
static uint8_t onewire_slave_crc(uint8_t* data, uint32_t length)
|
||||
{
|
||||
if (NULL==data || 0==length) { // check input
|
||||
return 0; // wrong input
|
||||
}
|
||||
|
||||
uint8_t crc = 0x00; // initial value
|
||||
for (uint8_t i=0; i<length; i++) { // go through every byte
|
||||
crc ^= data[i]; // XOR byte
|
||||
for (uint8_t b=0; b<8; b++) { // go through every bit
|
||||
if (crc&0x01) { // least significant bit is set (we are using the reverse way)
|
||||
crc = (crc>>1)^0x8C; // // shift to the right (for the next bit) and XOR with (reverse) polynomial
|
||||
} else {
|
||||
crc >>= 1; // just shift right (for the next bit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void onewire_slave_setup(uint8_t family, uint64_t serial)
|
||||
{
|
||||
// save ROM code (LSB first)
|
||||
onewire_slave_rom_code[0] = family;
|
||||
onewire_slave_rom_code[1] = serial >> 40;
|
||||
onewire_slave_rom_code[2] = serial >> 32;
|
||||
onewire_slave_rom_code[3] = serial >> 24;
|
||||
onewire_slave_rom_code[4] = serial >> 16;
|
||||
onewire_slave_rom_code[5] = serial >> 8;
|
||||
onewire_slave_rom_code[6] = serial >> 0;
|
||||
onewire_slave_rom_code[7] = onewire_slave_crc(onewire_slave_rom_code, 7); // calculate CRC
|
||||
|
||||
// setup timer to generate/measure signal timing
|
||||
rcc_periph_clock_enable(RCC_TIM(ONEWIRE_SLAVE_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(ONEWIRE_SLAVE_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(ONEWIRE_SLAVE_TIMER), 1-1); // don't use prescale since this 16 bits timer allows to wait > 480 us used for the reset pulse ( 1/(72E6/1/(2**16))=910us )
|
||||
|
||||
// use comparator to time signal (without using the output), starting at slot start
|
||||
timer_set_period(TIM(ONEWIRE_SLAVE_TIMER), 480*(rcc_ahb_frequency/1000000)-1-1300); // minimum time needed for a reset pulse (480 < Trst), plus hand tuning
|
||||
timer_set_oc_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC1, TIM_OCM_FROZEN);
|
||||
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC1, 16*(rcc_ahb_frequency/1000000)-1); // time to wait before sending the presence pulse, after the rising edge of the reset pulse (15 < Tpdh < 60)
|
||||
timer_set_oc_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC2, TIM_OCM_FROZEN);
|
||||
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC2, 45*(rcc_ahb_frequency/1000000)-1-350); // time to sample the bit after being set (1 < Tlow1 < 15, 60 < Tslot < 120), or stop sending the bit use compare function to detect slave presence (15 = Trdv + 0 < Trelease < 45), plus hand tuning
|
||||
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC3, 90*(rcc_ahb_frequency/1000000)-1); // time to stop the presence pulse (60 < Tpdl < 120)
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all interrupt flags
|
||||
timer_update_on_overflow(TIM(ONEWIRE_SLAVE_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(ONEWIRE_SLAVE_TIMER)); // catch interrupt in service routine
|
||||
|
||||
onewire_slave_function_code_received = false; // reset state
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // reset state
|
||||
onewire_slave_transfer_complete = false; // reset state
|
||||
|
||||
// setup GPIO with external interrupt
|
||||
rcc_periph_clock_enable(RCC_GPIO(ONEWIRE_SLAVE_PORT)); // enable clock for GPIO peripheral
|
||||
gpio_set(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // idle is high (using pull-up resistor)
|
||||
gpio_set_mode(GPIO(ONEWIRE_SLAVE_PORT), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(ONEWIRE_SLAVE_PIN)); // control output using open drain (this mode also allows to read the input signal)
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
|
||||
exti_select_source(EXTI(ONEWIRE_SLAVE_PIN), GPIO(ONEWIRE_SLAVE_PORT)); // mask external interrupt of this pin only for this port
|
||||
exti_set_trigger(EXTI(ONEWIRE_SLAVE_PIN), EXTI_TRIGGER_BOTH); // trigger on signal change
|
||||
exti_enable_request(EXTI(ONEWIRE_SLAVE_PIN)); // enable external interrupt
|
||||
nvic_enable_irq(NVIC_EXTI_IRQ(ONEWIRE_SLAVE_PIN)); // enable interrupt
|
||||
}
|
||||
|
||||
bool onewire_slave_function_read(uint8_t* data, size_t size)
|
||||
{
|
||||
if (NULL==data || 0==size) { // verify input
|
||||
return false;
|
||||
}
|
||||
if (UINT32_MAX/8<size) { // too many bits to transfer
|
||||
return false;
|
||||
}
|
||||
if (onewire_slave_state!=ONEWIRE_STATE_FUNCTION_DATA) { // not in the right state to transfer data
|
||||
return false;
|
||||
}
|
||||
onewire_slave_transfer_data = data; // save buffer to write to
|
||||
onewire_slave_transfer_bits = size*8; // number of bits to read
|
||||
onewire_slave_transfer_complete = false; // reset state
|
||||
bits_bit = 0; // reset number of bits read
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_READ; // read data
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onewire_slave_function_write(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (NULL==data || 0==size) { // verify input
|
||||
return false;
|
||||
}
|
||||
if (onewire_slave_state!=ONEWIRE_STATE_FUNCTION_DATA) { // not in the right state to transfer data
|
||||
return false;
|
||||
}
|
||||
if (UINT32_MAX/8<size) { // too many bits to transfer
|
||||
return false;
|
||||
}
|
||||
onewire_slave_transfer_data = (uint8_t*)data; // save buffer to read from
|
||||
onewire_slave_transfer_bits = size*8; // number of bits to write
|
||||
onewire_slave_transfer_complete = false; // reset state
|
||||
bits_bit = 0; // reset number of bits written
|
||||
bits_buffer = onewire_slave_transfer_data[0]; // prepare byte to write
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_WRITE; // write data
|
||||
return true;
|
||||
}
|
||||
|
||||
/** interrupt service routine called when 1-Wire signal changes */
|
||||
void EXTI_ISR(ONEWIRE_SLAVE_PIN)(void)
|
||||
{
|
||||
exti_reset_request(EXTI(ONEWIRE_SLAVE_PIN)); // reset interrupt
|
||||
if (gpio_get(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN))) { // it's a rising edge
|
||||
switch (onewire_slave_state) {
|
||||
case ONEWIRE_STATE_RESET: // reset pulse has ended
|
||||
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer for reconfiguration
|
||||
timer_set_counter(TIM(ONEWIRE_SLAVE_TIMER), 0); // reset timer counter
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF); // clear flag
|
||||
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE); // enable compare interrupt for presence pulse
|
||||
timer_enable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // start timer to generate timing
|
||||
onewire_slave_state = ONEWIRE_STATE_WAIT_PRESENCE; // set new stated
|
||||
break;
|
||||
case ONEWIRE_STATE_PULSE_PRESENCE: // we stopped sending the presence pulse
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_COMMAND; // we now expect a ROM command
|
||||
bits_bit = 0; // reset buffer bit count
|
||||
break; // no need to stop the time, the reset will be checked correctly
|
||||
default: // rising edge is not important is the other cases
|
||||
break; // nothing to do
|
||||
}
|
||||
} else { // it's a falling edge, the beginning of a new signal
|
||||
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer for reconfiguration
|
||||
timer_set_counter(TIM(ONEWIRE_SLAVE_TIMER), 0); // reset timer counter
|
||||
timer_disable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE); // disable all timers
|
||||
switch (onewire_slave_state) {
|
||||
case ONEWIRE_STATE_PULSE_PRESENCE: // we started sending the presence pulse
|
||||
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC3IE); // enable timer for end of pulse
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command bits
|
||||
case ONEWIRE_STATE_ROM_MATCH: // read ROM code bits
|
||||
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command bits
|
||||
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // read selected ROM code bit
|
||||
case ONEWIRE_STATE_FUNCTION_READ: // read function data bit
|
||||
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC2IE); // enable timer for reading bit
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_READ: // send ROM code bit
|
||||
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // send ROM code bit while searching ROM, not negated
|
||||
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // send ROM code bit while searching ROM, already negated
|
||||
case ONEWIRE_STATE_FUNCTION_WRITE: // write function data bit
|
||||
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC2IE); // enable timer for reading bit
|
||||
if (0==(bits_buffer&(1<<(bits_bit%8)))) { // need to send a 0 bit
|
||||
gpio_clear(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // hold low to send 0 bit
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_IDLE: // we only expect a reset
|
||||
default: // we don't expect any falling edge in other states
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // unexpected signal, reset to idle state
|
||||
break; // the timer overflow will confirm detect reset pulses
|
||||
}
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all flags
|
||||
timer_enable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // start timer to measure the configured timeouts
|
||||
}
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer */
|
||||
void TIM_ISR(ONEWIRE_SLAVE_TIMER)(void)
|
||||
{
|
||||
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF)) { // reset timer triggered, verify if it's a reset
|
||||
if (0==gpio_get(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN))) { // signal it still low, thus it must be a reset
|
||||
onewire_slave_state = ONEWIRE_STATE_RESET; // update state
|
||||
}
|
||||
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer since there is nothing more to measure
|
||||
timer_disable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE); // disable all timers
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all flag (I have no idea why the others are get too, even when the interrupt is not enabled)
|
||||
}
|
||||
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF)) { // wait for presence pulse timer triggered
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF); // clear flag
|
||||
if (ONEWIRE_STATE_WAIT_PRESENCE==onewire_slave_state) { // we can now send the pulse
|
||||
onewire_slave_state = ONEWIRE_STATE_PULSE_PRESENCE; // save new state
|
||||
gpio_clear(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // send presence pulse (will also trigger the timer start)
|
||||
}
|
||||
}
|
||||
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC2IF)) { // time to read the bit, or stop writing it
|
||||
// read/write bit depending on bit
|
||||
switch (onewire_slave_state) {
|
||||
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command code bit
|
||||
case ONEWIRE_STATE_ROM_MATCH: // read ROM code
|
||||
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command code bit
|
||||
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // read selected ROM code bit
|
||||
case ONEWIRE_STATE_FUNCTION_READ: // read function data bit
|
||||
if (gpio_get(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN))) { // bit is set to 1
|
||||
bits_buffer |= (1<<(bits_bit%8)); // set bit
|
||||
} else { // bit is set to 0
|
||||
bits_buffer &= ~(1<<(bits_bit%8)); // clear bit
|
||||
}
|
||||
bits_bit++; // go to next bit
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_READ: // write ROM code
|
||||
case ONEWIRE_STATE_FUNCTION_WRITE: // write function data bit
|
||||
gpio_set(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // stop sending bit
|
||||
bits_bit++; // go to next bit
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // ROM code bit is sent
|
||||
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // ROM code bit is sent
|
||||
gpio_set(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // stop sending bit
|
||||
break;
|
||||
default: // these states don't need read/write
|
||||
break;
|
||||
}
|
||||
static uint8_t rom_code_byte; // which byte of the ROM code is processed
|
||||
// act on bit count
|
||||
switch (onewire_slave_state) {
|
||||
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command
|
||||
if (bits_bit>7) { // complete ROM command code received
|
||||
bits_bit = 0; // reset buffer
|
||||
rom_code_byte = 0; // reset ROM code byte index
|
||||
switch (bits_buffer) { // act depending on ROM command code
|
||||
case 0x33: // READ ROM
|
||||
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare to send the first byte
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_READ; // write ROM code
|
||||
break;
|
||||
case 0xcc: // SKIP ROM
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
|
||||
break;
|
||||
case 0x55: // MATCH ROM
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_MATCH; // read ROM code
|
||||
break;
|
||||
case 0xf0: // SEARCH ROM
|
||||
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare to search code
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_TRUE; // prepare to start sending first new bit
|
||||
break;
|
||||
default: // unknown ROM code
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to default idle state
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_READ: // send ROM code
|
||||
if (bits_bit>7) { // complete byte transmitted
|
||||
rom_code_byte++; // go to next ROM code byte
|
||||
if (rom_code_byte>LENGTH(onewire_slave_rom_code)) { // complete ROM code send
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to default idle state
|
||||
} else {
|
||||
bits_bit = 0; // reset buffer
|
||||
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // send next ROM code byte
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_MATCH: // compare ROM code
|
||||
if (bits_bit>7) { // complete byte received
|
||||
if (bits_buffer==onewire_slave_rom_code[rom_code_byte]) { // ROM code byte matches
|
||||
bits_bit = 0; // reset buffer
|
||||
rom_code_byte++; // go to next ROM code byte
|
||||
if (rom_code_byte>=LENGTH(onewire_slave_rom_code)) { // complete ROM code matches
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
|
||||
}
|
||||
} else { // ROM code does not match
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // stop comparing and go back to idle
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // ROM code bit is send, prepare to send negated version
|
||||
bits_buffer ^= (1<<bits_bit); // negate bit
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_FALSE; // send negated version
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // negated ROM code bit is send, prepare to read selected bit
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_SELECT; // read selected
|
||||
break;
|
||||
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // check if we are selected
|
||||
if ((bits_buffer&(1<<(bits_bit-1)))==(onewire_slave_rom_code[rom_code_byte]&(1<<(bits_bit-1)))) { // we have been selected
|
||||
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_TRUE; // prepare to compare next bit
|
||||
} else { // we are no selected
|
||||
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to idle
|
||||
}
|
||||
if (bits_bit>7) { // complete byte searched
|
||||
bits_bit = 0; // reset buffer
|
||||
rom_code_byte++; // go to next ROM code byte
|
||||
if (rom_code_byte>=LENGTH(onewire_slave_rom_code)) { // complete ROM code search
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
|
||||
} else {
|
||||
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare next ROM code byte
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command
|
||||
if (bits_bit>7) { // complete function command code received
|
||||
onewire_slave_function_code = bits_buffer; // save function command code to user buffer
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer data
|
||||
onewire_slave_function_code_received = true; // notify user
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_FUNCTION_READ: // save function data bit
|
||||
if (0==bits_bit%8) { // complete byte received
|
||||
onewire_slave_transfer_data[(bits_bit-1)/8] = bits_buffer; // save received bytes
|
||||
}
|
||||
if (bits_bit>=onewire_slave_transfer_bits) { // read transfer complete
|
||||
onewire_slave_transfer_data[(bits_bit-1)/8] = bits_buffer; // save last bits
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer more data
|
||||
onewire_slave_transfer_complete = true; // notify user
|
||||
}
|
||||
break;
|
||||
case ONEWIRE_STATE_FUNCTION_WRITE: // update function data bit to write
|
||||
if (0==bits_bit%8) { // complete byte transfer
|
||||
bits_buffer = onewire_slave_transfer_data[bits_bit/8]; // prepare next byte to write
|
||||
}
|
||||
if (bits_bit>=onewire_slave_transfer_bits) { // write transfer complete
|
||||
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer more data
|
||||
onewire_slave_transfer_complete = true; // notify user
|
||||
}
|
||||
break;
|
||||
default: // no action needed
|
||||
break;
|
||||
}
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC2IF); // clear flag
|
||||
}
|
||||
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC3IF)) { // end of presence pulse timer triggered
|
||||
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC3IF); // clear flag
|
||||
if (ONEWIRE_STATE_PULSE_PRESENCE==onewire_slave_state) {
|
||||
gpio_set(GPIO(ONEWIRE_SLAVE_PORT), GPIO(ONEWIRE_SLAVE_PIN)); // stop sending presence pulse
|
||||
// if the pin stays low the reset timer will catch it
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +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 for 1-wire protocol as slave (API)
|
||||
* @file onewire_slave.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: timer @ref onewire_slave_timer, GPIO @ref onewire_slave_gpio
|
||||
* @note overdrive mode is not supported
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** set when a function command code has been received
|
||||
* @note needs to be cleared by user
|
||||
*/
|
||||
extern volatile bool onewire_slave_function_code_received;
|
||||
/** last function command code received */
|
||||
extern volatile uint8_t onewire_slave_function_code;
|
||||
/** set when data read/write transfer has been completed
|
||||
* @note needs to be cleared by user
|
||||
*/
|
||||
extern volatile bool onewire_slave_transfer_complete;
|
||||
|
||||
/** setup 1-wire peripheral
|
||||
* @param[in] family family code for slave ROM code (8 bits)
|
||||
* @param[in] serial serial number for slave ROM code (48 bits)
|
||||
*/
|
||||
void onewire_slave_setup(uint8_t family, uint64_t serial);
|
||||
/** read data from master
|
||||
* @param[out] data buffer to save read bits
|
||||
* @param[in] size number of bytes to read
|
||||
* @return if transfer initialization succeeded
|
||||
* @note onewire_slave_transfer_complete is set when transfer is completed
|
||||
*/
|
||||
bool onewire_slave_function_read(uint8_t* data, size_t size);
|
||||
/** write data to master
|
||||
* @param[in] data data to write
|
||||
* @param[in] size number of bytes to write
|
||||
* @return if transfer initialization succeeded
|
||||
* @note onewire_slave_transfer_complete is set when transfer is completed
|
||||
*/
|
||||
bool onewire_slave_function_write(const uint8_t* data, size_t size);
|
@ -1,175 +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 send data using ESP8266 WiFi SoC (code)
|
||||
* @file radio_esp8266.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: USART @ref radio_esp8266_usart
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
#include <string.h> // string and memory utilities
|
||||
#include <stdio.h> // string utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
|
||||
#include "radio_esp8266.h" // radio header and definitions
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup radio_esp8266_usart USART peripheral used for communication with radio
|
||||
* @{
|
||||
*/
|
||||
#define RADIO_ESP8266_USART 2 /**< USART peripheral */
|
||||
/** @} */
|
||||
|
||||
/* input and output buffers and used memory */
|
||||
static uint8_t rx_buffer[24] = {0}; /**< buffer for received data (we only expect AT responses) */
|
||||
static volatile uint16_t rx_used = 0; /**< number of byte in receive buffer */
|
||||
static uint8_t tx_buffer[256] = {0}; /**< buffer for data to transmit */
|
||||
static volatile uint16_t tx_used = 0; /**< number of bytes used in transmit buffer */
|
||||
|
||||
volatile bool radio_esp8266_activity = false;
|
||||
volatile bool radio_esp8266_success = false;
|
||||
|
||||
/** transmit data to radio
|
||||
* @param[in] data data to transmit
|
||||
* @param[in] length length of data to transmit
|
||||
*/
|
||||
static void radio_esp8266_transmit(uint8_t* data, uint8_t length) {
|
||||
while (tx_used || !usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // wait until ongoing transmission completed
|
||||
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable transmit interrupt
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // ensure transmit interrupt is disable to prevent index corruption (the ISR should already have done it)
|
||||
radio_esp8266_activity = false; // reset status because of new activity
|
||||
for (tx_used=0; tx_used<length && tx_used<LENGTH(tx_buffer); tx_used++) { // copy data
|
||||
tx_buffer[tx_used] = data[length-1-tx_used]; // put character in buffer (in reverse order)
|
||||
}
|
||||
if (tx_used) {
|
||||
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable interrupt to send bytes
|
||||
}
|
||||
}
|
||||
|
||||
void radio_esp8266_setup(void)
|
||||
{
|
||||
/* enable USART I/O peripheral */
|
||||
rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (USART)
|
||||
rcc_periph_clock_enable(USART_PORT_RCC(RADIO_ESP8266_USART)); // enable clock for USART port peripheral
|
||||
rcc_periph_clock_enable(USART_RCC(RADIO_ESP8266_USART)); // enable clock for USART peripheral
|
||||
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_PIN_TX(RADIO_ESP8266_USART)); // setup GPIO pin USART transmit
|
||||
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_PIN_RX(RADIO_ESP8266_USART)); // setup GPIO pin USART receive
|
||||
gpio_set(USART_PORT(RADIO_ESP8266_USART), USART_PIN_RX(RADIO_ESP8266_USART)); // pull up to avoid noise when not connected
|
||||
|
||||
/* setup USART parameters for ESP8266 AT firmware */
|
||||
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200); // AT firmware 0.51 (SDK 1.5.0) uses 115200 bps
|
||||
usart_set_databits(USART(RADIO_ESP8266_USART), 8);
|
||||
usart_set_stopbits(USART(RADIO_ESP8266_USART), USART_STOPBITS_1);
|
||||
usart_set_mode(USART(RADIO_ESP8266_USART), USART_MODE_TX_RX);
|
||||
usart_set_parity(USART(RADIO_ESP8266_USART), USART_PARITY_NONE);
|
||||
usart_set_flow_control(USART(RADIO_ESP8266_USART), USART_FLOWCONTROL_NONE);
|
||||
|
||||
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the USART interrupt
|
||||
usart_enable_rx_interrupt(USART(RADIO_ESP8266_USART)); // enable receive interrupt
|
||||
usart_enable(USART(RADIO_ESP8266_USART)); // enable USART
|
||||
|
||||
/* reset buffer states */
|
||||
rx_used = 0;
|
||||
tx_used = 0;
|
||||
radio_esp8266_activity = false;
|
||||
radio_esp8266_success = false;
|
||||
|
||||
radio_esp8266_transmit((uint8_t*)"AT\r\n",4); // verify if module is present
|
||||
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n",8); // reset module
|
||||
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
while(rx_used<13 || memcmp((char*)&rx_buffer[rx_used-13], "WIFI GOT IP\r\n", 13)!=0) { // wait to have IP
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
radio_esp8266_transmit((uint8_t*)"ATE0\r\n",6); // disable echoing
|
||||
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
}
|
||||
|
||||
void radio_esp8266_tcp_open(char* host, uint16_t port)
|
||||
{
|
||||
char command[256] = {0}; // string to create command
|
||||
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"TCP\",\"%s\",%u\r\n", host, port); // create AT command to establish a TCP connection
|
||||
if (length>0) {
|
||||
radio_esp8266_transmit((uint8_t*)command, length);
|
||||
}
|
||||
}
|
||||
|
||||
void radio_esp8266_send(uint8_t* data, uint8_t length)
|
||||
{
|
||||
char command[16+1] = {0}; // string to create command
|
||||
int command_length = snprintf(command, LENGTH(command), "AT+CIPSEND=%u\r\n", length); // create AT command to send data
|
||||
if (command_length>0) {
|
||||
radio_esp8266_transmit((uint8_t*)command, command_length); // transmit AT command
|
||||
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
|
||||
__WFI(); // sleep until something happened
|
||||
}
|
||||
if (!radio_esp8266_success) { // send AT command did not succeed
|
||||
return; // don't transmit data
|
||||
}
|
||||
radio_esp8266_transmit(data, length); // transmit data
|
||||
}
|
||||
}
|
||||
|
||||
void radio_esp8266_close(void)
|
||||
{
|
||||
radio_esp8266_transmit((uint8_t*)"AT+CIPCLOSE\r\n", 13); // send AT command to close established connection
|
||||
}
|
||||
|
||||
/** USART interrupt service routine called when data has been transmitted or received */
|
||||
void USART_ISR(RADIO_ESP8266_USART)(void)
|
||||
{
|
||||
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
|
||||
if (tx_used) { // there is still data in the buffer to transmit
|
||||
usart_send(USART(RADIO_ESP8266_USART),tx_buffer[tx_used-1]); // put data in transmit register
|
||||
tx_used--; // update used size
|
||||
} else { // no data in the buffer to transmit
|
||||
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // disable transmit interrupt
|
||||
}
|
||||
}
|
||||
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
|
||||
while (rx_used>=LENGTH(rx_buffer)) { // if buffer is full
|
||||
memmove(rx_buffer,&rx_buffer[1],LENGTH(rx_buffer)-1); // drop old data to make space (ring buffer are more efficient but harder to handle)
|
||||
rx_used--; // update used buffer information
|
||||
}
|
||||
rx_buffer[rx_used++] = usart_recv(USART(RADIO_ESP8266_USART)); // put character in buffer
|
||||
// if the used send a packet with these strings during the commands detection the AT command response will break (AT commands are hard to handle perfectly)
|
||||
if (rx_used>=4 && memcmp((char*)&rx_buffer[rx_used-4], "OK\r\n", 4)==0) { // OK received
|
||||
radio_esp8266_activity = true; // response received
|
||||
radio_esp8266_success = true; // command succeeded
|
||||
rx_used = 0; // reset buffer
|
||||
} else if (rx_used>=7 && memcmp((char*)&rx_buffer[rx_used-7], "ERROR\r\n", 7)==0) { // ERROR received
|
||||
radio_esp8266_activity = true; // response received
|
||||
radio_esp8266_success = false; // command failed
|
||||
rx_used = 0; // reset buffer
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +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 send data using ESP8266 WiFi SoC (API)
|
||||
* @file radio_esp8266.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: USART @ref radio_esp8266_usart
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** a response has been returned by the radio */
|
||||
extern volatile bool radio_esp8266_activity;
|
||||
/** the last command has succeeded */
|
||||
extern volatile bool radio_esp8266_success;
|
||||
|
||||
/** setup peripherals to communicate with radio
|
||||
* @note this is blocking to ensure we are connected to the WiFi network
|
||||
*/
|
||||
void radio_esp8266_setup(void);
|
||||
/** establish TCP connection
|
||||
* @param[in] host host to connect to
|
||||
* @param[in] port TCP port to connect to
|
||||
* @note wait for activity to get success status
|
||||
*/
|
||||
void radio_esp8266_tcp_open(char* host, uint16_t port);
|
||||
/** send data (requires established connection)
|
||||
* @param[in] data data to send
|
||||
* @param[in] length size of data to send
|
||||
* @note wait for activity to get success status
|
||||
*/
|
||||
void radio_esp8266_send(uint8_t* data, uint8_t length);
|
||||
/** close established connection
|
||||
* @note wait for activity to get success status
|
||||
*/
|
||||
void radio_esp8266_close(void);
|
286
lib/rtc_dcf77.c
286
lib/rtc_dcf77.c
@ -1,286 +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 get time from a DCF77 module (code)
|
||||
* @file rtc_dcf77.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
|
||||
*/
|
||||
|
||||
/* 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/cm3/nvic.h> // interrupt handler
|
||||
#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 <libopencm3/stm32/timer.h> // timer library
|
||||
|
||||
#include "rtc_dcf77.h" // RTC DCF77 library API
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup rtc_dcf77_gpio output to enable DCF module and input to capture DCF signal
|
||||
* @{
|
||||
*/
|
||||
#define RTC_DCF77_ENABLE_PORT A /**< GPIO port to enable the module */
|
||||
#define RTC_DCF77_ENABLE_PIN 2 /**< GPIO pinto enable the module */
|
||||
#define RTC_DCF77_SIGNAL_PORT A /**< GPIO port to capture the DCF signal */
|
||||
#define RTC_DCF77_SIGNAL_PIN 3 /**< GPIO pin to capture the DCF signal */
|
||||
/** @} */
|
||||
|
||||
/** @defgroup rtc_dcf77_timer timer to sample DCF77 signal
|
||||
* @{
|
||||
*/
|
||||
#define RTC_DCF77_TIMER 4 /**< timer peripheral */
|
||||
/** @} */
|
||||
|
||||
volatile bool rtc_dcf77_time_flag = false;
|
||||
struct rtc_dcf77_time_t rtc_dcf77_time = {false, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/**< the received DCF77 frame bits */
|
||||
static volatile uint64_t rtc_dcf77_frame = 0;
|
||||
/** values of the DCF77 signal over 10 ms for 1 s (how many times it is high) */
|
||||
static uint8_t rtc_dcf77_bins[100] = {0};
|
||||
/** the bin shift for the bit phase */
|
||||
static uint8_t rtc_dcf77_phase = 0;
|
||||
/** the maximum phase value */
|
||||
static int16_t rtc_dcf77_phase_max = INT16_MIN;
|
||||
/** if the current phase has been verified */
|
||||
static bool rtc_dcf77_phase_locked = false;
|
||||
/** number of invalid decoding */
|
||||
static uint8_t rtc_dcf77_invalid = 0;
|
||||
/** maximum number of invalid decoding before switching off */
|
||||
#define RTC_DCF77_INVALID_MAX 5
|
||||
|
||||
void rtc_dcf77_setup(void)
|
||||
{
|
||||
// setup enable output
|
||||
rcc_periph_clock_enable(RCC_GPIO(RTC_DCF77_ENABLE_PORT)); // enable clock GPIO peripheral
|
||||
gpio_set_mode(GPIO(RTC_DCF77_ENABLE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(RTC_DCF77_ENABLE_PIN)); // set pin to output push-pull to be able to enable the module
|
||||
rtc_dcf77_off(); // disable module at start
|
||||
|
||||
// setup signal input
|
||||
rcc_periph_clock_enable(RCC_GPIO(RTC_DCF77_SIGNAL_PORT)); // enable clock for signal input peripheral
|
||||
gpio_set_mode(GPIO(RTC_DCF77_SIGNAL_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(RTC_DCF77_SIGNAL_PIN)); // set signal pin to input
|
||||
|
||||
// setup timer to sample signal at 1kHz
|
||||
rcc_periph_clock_enable(RCC_TIM(RTC_DCF77_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(RTC_DCF77_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(RTC_DCF77_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(RTC_DCF77_TIMER), 1); // set prescaler to divide frequency by two, to be able to have a period of 1 kHz (72MHz/2/2^16=549Hz<1kHz)
|
||||
timer_set_period(TIM(RTC_DCF77_TIMER), rcc_ahb_frequency/2/1000-1); // set period to 1kHz (plus hand tuning)
|
||||
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear update event flag
|
||||
timer_update_on_overflow(TIM(RTC_DCF77_TIMER)); // only use counter overflow as UEV source
|
||||
timer_enable_irq(TIM(RTC_DCF77_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(RTC_DCF77_TIMER)); // catch interrupt in service routine
|
||||
}
|
||||
|
||||
void rtc_dcf77_on(void)
|
||||
{
|
||||
if (!gpio_get(GPIO(RTC_DCF77_ENABLE_PORT), GPIO(RTC_DCF77_ENABLE_PIN))) { // receiver is already turned on
|
||||
return; // do nothing
|
||||
}
|
||||
rtc_dcf77_frame = 0; // reset frame
|
||||
rtc_dcf77_phase_locked = false; // reset phase lock
|
||||
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
|
||||
rtc_dcf77_invalid = 0; // reset invalid count
|
||||
gpio_clear(GPIO(RTC_DCF77_ENABLE_PORT), GPIO(RTC_DCF77_ENABLE_PIN)); // enable module by pulling pin low
|
||||
timer_set_counter(TIM(RTC_DCF77_TIMER), 0); // reset timer counter
|
||||
timer_enable_counter(TIM(RTC_DCF77_TIMER)); // start timer to sample signal
|
||||
}
|
||||
|
||||
void rtc_dcf77_off(void)
|
||||
{
|
||||
gpio_set(GPIO(RTC_DCF77_ENABLE_PORT), GPIO(RTC_DCF77_ENABLE_PIN)); // disable module by pull pin high
|
||||
timer_disable_counter(TIM(RTC_DCF77_TIMER)); // stop timer since we don't need to sample anymore
|
||||
}
|
||||
|
||||
/** decode rtc_dcf77_frame DCF77 frame into rtc_dcf77_time DCF77 time
|
||||
* @note check valid for validity
|
||||
*/
|
||||
static void rtc_dcf77_decode(void)
|
||||
{
|
||||
rtc_dcf77_time.valid = false; // reset validity
|
||||
|
||||
if (rtc_dcf77_frame==0) { // no time received yet
|
||||
return;
|
||||
}
|
||||
if (!(rtc_dcf77_frame&((uint64_t)1<<20))) { // start of encoded time should always be 1
|
||||
return;
|
||||
}
|
||||
|
||||
// check minute parity
|
||||
uint8_t parity = 0; // to check parity
|
||||
for (uint8_t bit=21; bit<=28; bit++) {
|
||||
if (rtc_dcf77_frame&((uint64_t)1<<bit)) {
|
||||
parity++; // count the set bits
|
||||
}
|
||||
}
|
||||
if (parity%2) { // parity should be even
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.minutes = 1*((rtc_dcf77_frame>>21)&(0x1))+2*((rtc_dcf77_frame>>22)&(0x1))+4*((rtc_dcf77_frame>>23)&(0x1))+8*((rtc_dcf77_frame>>24)&(0x1))+10*((rtc_dcf77_frame>>25)&(0x1))+20*((rtc_dcf77_frame>>26)&(0x1))+40*((rtc_dcf77_frame>>27)&(0x1)); // read minute (00-59)
|
||||
if (rtc_dcf77_time.minutes>59) { // minutes should not be more than 59
|
||||
return;
|
||||
}
|
||||
|
||||
// check hour parity
|
||||
parity = 0;
|
||||
for (uint8_t bit=29; bit<=35; bit++) {
|
||||
if (rtc_dcf77_frame&((uint64_t)1<<bit)) {
|
||||
parity++; // count the set bits
|
||||
}
|
||||
}
|
||||
if (parity%2) { // parity should be even
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.hours = 1*((rtc_dcf77_frame>>29)&(0x1))+2*((rtc_dcf77_frame>>30)&(0x1))+4*((rtc_dcf77_frame>>31)&(0x1))+8*((rtc_dcf77_frame>>32)&(0x1))+10*((rtc_dcf77_frame>>33)&(0x1))+20*((rtc_dcf77_frame>>34)&(0x1)); // read hour (00-23)
|
||||
if (rtc_dcf77_time.hours>23) { // hours should not be more than 23
|
||||
return;
|
||||
}
|
||||
|
||||
// check date parity
|
||||
parity = 0;
|
||||
for (uint8_t bit=36; bit<=58; bit++) {
|
||||
if (rtc_dcf77_frame&((uint64_t)1<<bit)) {
|
||||
parity++; // count the set bits
|
||||
}
|
||||
}
|
||||
if (parity%2) { // parity should be even
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.day = 1*((rtc_dcf77_frame>>36)&(0x1))+2*((rtc_dcf77_frame>>37)&(0x1))+4*((rtc_dcf77_frame>>38)&(0x1))+8*((rtc_dcf77_frame>>39)&(0x1))+10*((rtc_dcf77_frame>>40)&(0x1))+20*((rtc_dcf77_frame>>41)&(0x1)); // read day of the month (01-31)
|
||||
if (rtc_dcf77_time.day==0 || rtc_dcf77_time.day>31) { // day of the month should be 1-31
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.weekday = 1*((rtc_dcf77_frame>>42)&(0x1))+2*((rtc_dcf77_frame>>43)&(0x1))+4*((rtc_dcf77_frame>>44)&(0x1)); // read day of the week (1=Monday - 7=Sunday)
|
||||
if (rtc_dcf77_time.weekday==0 || rtc_dcf77_time.weekday>7) { // day of the week should be 1-7
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.month = 1*((rtc_dcf77_frame>>45)&(0x1))+2*((rtc_dcf77_frame>>46)&(0x1))+4*((rtc_dcf77_frame>>47)&(0x1))+8*((rtc_dcf77_frame>>48)&(0x1))+10*((rtc_dcf77_frame>>49)&(0x1)); // read month of the year (01-12)
|
||||
if (rtc_dcf77_time.month==0 || rtc_dcf77_time.month>12) { // month of the year should be 1-12
|
||||
return;
|
||||
}
|
||||
rtc_dcf77_time.year = 1*((rtc_dcf77_frame>>50)&(0x1))+2*((rtc_dcf77_frame>>51)&(0x1))+4*((rtc_dcf77_frame>>52)&(0x1))+8*((rtc_dcf77_frame>>53)&(0x1))+10*((rtc_dcf77_frame>>54)&(0x1))+20*((rtc_dcf77_frame>>55)&(0x1))+40*((rtc_dcf77_frame>>56)&(0x1))+80*((rtc_dcf77_frame>>57)&(0x1)); // read year of the century (00-99)
|
||||
if (rtc_dcf77_time.year>99) { // year should be <100
|
||||
return;
|
||||
}
|
||||
|
||||
rtc_dcf77_time.valid = true; // if we managed it until here the decoding is successful
|
||||
}
|
||||
|
||||
/** find phase of 1 seconds DCF77 signal in the bins
|
||||
* searches the complete second for the highest correlation if the phase it not locked
|
||||
* searches only around the last phase if locked
|
||||
* saves the new phase with the highest correlation
|
||||
*/
|
||||
static void rtc_dcf77_phase_detector(void) {
|
||||
uint8_t integral_i = 0; // which bin has the highest integral/correlation
|
||||
int16_t integral_max = 0; // maximum integral value found
|
||||
|
||||
for (uint8_t start=0; start<(rtc_dcf77_phase_locked ? 10 : LENGTH(rtc_dcf77_bins)); start++) { // which bin has been used to start the convolution (only use +/- 15 bits of previous phase if locked)
|
||||
int16_t integral = 0; // value of the integral
|
||||
for (uint8_t bin=0; bin<LENGTH(rtc_dcf77_bins); bin++) { // go through bins to calculate correlation
|
||||
int8_t dfc77_signal = -1; // the signal of the reference DCF77 signal
|
||||
if (bin<10) { // the signal is always high for the first 100 ms
|
||||
dfc77_signal = 1; // use highest values
|
||||
} else if (bin<20) { // the signal has 50% chance of being high for the next 100 ms (encoding the bit)
|
||||
dfc77_signal = 0; // use middle value
|
||||
}
|
||||
// the rest of the time the signal is low (keep lowest value)
|
||||
integral += rtc_dcf77_bins[(start+bin+rtc_dcf77_phase+LENGTH(rtc_dcf77_bins)-5)%LENGTH(rtc_dcf77_bins)]*dfc77_signal; // calculate the correlation at this point and integrate it (start with previous phase - 15 bins)
|
||||
}
|
||||
if (integral>integral_max) { // we found a better correlation result
|
||||
integral_max = integral; // save new best result
|
||||
integral_i = (start+rtc_dcf77_phase+LENGTH(rtc_dcf77_bins)-5)%LENGTH(rtc_dcf77_bins); // start new best phase start
|
||||
}
|
||||
}
|
||||
|
||||
if ((int16_t)(integral_max+40)>rtc_dcf77_phase_max) { // only save new phase if it is better than the last one, with some margin to compensate for the drift (perfect correlation = 100, worst correlation = -800)
|
||||
rtc_dcf77_phase_max = integral_max; // save best phase value
|
||||
rtc_dcf77_phase = integral_i; // save bin index corresponding to start of the phase
|
||||
}
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer */
|
||||
void TIM_ISR(RTC_DCF77_TIMER)(void)
|
||||
{
|
||||
static uint8_t bin_state = 0; // how many samples have been stored in the bin
|
||||
static uint8_t bin_i = 0; // current bin filled
|
||||
static uint8_t bit_i = 0; // current bit in the DCF77 minute frame
|
||||
if (timer_get_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear flag
|
||||
// fill bin with current sample state
|
||||
if (gpio_get(GPIO(RTC_DCF77_SIGNAL_PORT), GPIO(RTC_DCF77_SIGNAL_PIN))) {
|
||||
rtc_dcf77_bins[bin_i]++; // only need to increase if the signal is high
|
||||
}
|
||||
bin_state++; // remember we filled the bin
|
||||
|
||||
if (bin_state>=10) { // bin has 10x1 ms samples, it is now full
|
||||
bin_i = (bin_i+1)%LENGTH(rtc_dcf77_bins); // go to next bin
|
||||
rtc_dcf77_bins[bin_i] = 0; // restart bin
|
||||
bin_state = 0; // restart collecting
|
||||
}
|
||||
|
||||
if (0==bin_i && 0==bin_state) { // we have 1 s of samples
|
||||
if (bit_i<59) {
|
||||
rtc_dcf77_phase_detector(); // detect phase in signal
|
||||
// check modulation of first 100 ms
|
||||
uint16_t modulation = 0;
|
||||
for (uint8_t bin=0; bin<10; bin++) {
|
||||
modulation += rtc_dcf77_bins[(rtc_dcf77_phase+bin)%LENGTH(rtc_dcf77_bins)];
|
||||
}
|
||||
if (modulation<50) { // signal is not modulated, it might be the 60th pause bit
|
||||
bit_i = 0; // restart frame
|
||||
rtc_dcf77_frame = 0; // restart frame
|
||||
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
|
||||
rtc_dcf77_phase_locked = false; // unlock phase since the decoding seems wrong
|
||||
} else { // modulation detected
|
||||
// check modulation of next 100 ms
|
||||
modulation = 0;
|
||||
for (uint8_t bin=10; bin<20; bin++) {
|
||||
modulation += rtc_dcf77_bins[(rtc_dcf77_phase+bin)%LENGTH(rtc_dcf77_bins)];
|
||||
}
|
||||
if (modulation<50) { // it's a 0
|
||||
// bit is already cleared
|
||||
} else { // it's a 1
|
||||
rtc_dcf77_frame |= (1ULL<<bit_i); // set bit
|
||||
}
|
||||
bit_i++; // go to next bit
|
||||
}
|
||||
} else { // complete DCF77 frame received
|
||||
rtc_dcf77_decode(); // decode frame
|
||||
if (rtc_dcf77_time.valid) { // decoded time is valid
|
||||
rtc_dcf77_time.milliseconds = rtc_dcf77_phase*10; // save milliseconds corresponding to phase
|
||||
rtc_dcf77_phase_locked = true; // lock phase since decoding succeeded
|
||||
rtc_dcf77_invalid = 0; // remember we had an valid decoding
|
||||
rtc_dcf77_time_flag = true; // notify user we have time
|
||||
} else {
|
||||
rtc_dcf77_invalid++; // remember we had an invalid decoding
|
||||
}
|
||||
if (rtc_dcf77_invalid>=RTC_DCF77_INVALID_MAX) { // too many invalid decoding
|
||||
rtc_dcf77_off(); // switch off receiver so it can re-tune
|
||||
}
|
||||
bit_i = 0; // restart frame
|
||||
rtc_dcf77_frame = 0; // restart frame
|
||||
}
|
||||
}
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
}
|
@ -1,46 +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 get time from a DCF77 module (API)
|
||||
* @file rtc_dcf77.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** set when time information has been received */
|
||||
extern volatile bool rtc_dcf77_time_flag;
|
||||
|
||||
/** decoded DCF77 time received */
|
||||
extern struct rtc_dcf77_time_t {
|
||||
bool valid; /**< if the time is valid */
|
||||
uint8_t milliseconds; /**< milliseconds (00-99) */
|
||||
uint8_t seconds; /**< seconds (always 0) */
|
||||
uint8_t minutes; /**< minutes (00-49) */
|
||||
uint8_t hours; /**< hours (00-23) */
|
||||
uint8_t day; /**< day of the month (01-31) */
|
||||
uint8_t weekday; /**< day of the week (1-7=Monday-Sunday) */
|
||||
uint8_t month; /**< month (01-12) */
|
||||
uint8_t year; /**< year within century (00-99) */
|
||||
} rtc_dcf77_time;
|
||||
|
||||
/** setup DCF77 time receiver module */
|
||||
void rtc_dcf77_setup(void);
|
||||
/** switch on DCF77 time receiver module
|
||||
* @note it switches back off after too many invalid decoding attempts
|
||||
*/
|
||||
void rtc_dcf77_on(void);
|
||||
/** switch off DCF77 time receiver module */
|
||||
void rtc_dcf77_off(void);
|
397
lib/rtc_ds1307.c
397
lib/rtc_ds1307.c
@ -1,397 +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 the Maxim DS1307 I2C RTC IC (code)
|
||||
* @file rtc_ds1307.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: I2C @ref i2c_master_i2c
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdio.h> // standard I/O facilities
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/i2c.h> // I2C library
|
||||
|
||||
#include "global.h" // global utilities
|
||||
#include "rtc_ds1307.h" // RTC header and definitions
|
||||
#include "i2c_master.h" // i2c utilities
|
||||
|
||||
#define RTC_DS1307_I2C_ADDR 0x68 /**< DS1307 I2C address (fixed to 0b1101000) */
|
||||
|
||||
void rtc_ds1307_setup(void)
|
||||
{
|
||||
// configure I2C peripheral
|
||||
i2c_master_setup(false); // DS1307 only supports normal mode (up to 100 kHz)
|
||||
}
|
||||
|
||||
bool rtc_ds1307_oscillator_disabled(void)
|
||||
{
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing CH value
|
||||
return false;
|
||||
}
|
||||
return data[0]&0x80; // return CH bit value to indicate if oscillator is disabled
|
||||
}
|
||||
|
||||
uint16_t rtc_ds1307_read_square_wave(void)
|
||||
{
|
||||
uint16_t to_return = 0; // square wave frequency to return (in Hz)
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint16_t rtc_ds1307_rs[] = {1, 4096, 8192, 32768}; // RS1/RS0 values
|
||||
const uint8_t address[] = {0x07}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing control register
|
||||
return 0xffff; // error occurred
|
||||
}
|
||||
if (data[0]&0x10) { // verify if the square wave is enabled (SQWE)
|
||||
to_return = rtc_ds1307_rs[data[0]&0x03]; // read RS1/RS0 and get value
|
||||
} else {
|
||||
to_return = 0; // square wave output is disabled
|
||||
}
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_seconds(void)
|
||||
{
|
||||
uint8_t to_return = 0; // seconds to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
to_return = ((data[0]&0x70)>>4)*10+(data[0]&0x0f); // convert BCD coding into seconds
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_minutes(void)
|
||||
{
|
||||
uint8_t to_return = 0; // minutes to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x01}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
to_return = (data[0]>>4)*10+(data[0]&0x0f); // convert BCD coding into minutes
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_hours(void)
|
||||
{
|
||||
uint8_t to_return = 0; // hours to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x02}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
if (data[0]&0x40) { // 12 hour mode
|
||||
if (data[0]&0x02) { // PM
|
||||
to_return += 12; // add the 12 hours
|
||||
}
|
||||
to_return += ((data[0]&0x10)>>4)*10; // convert BCD coding into hours (first digit)
|
||||
} else {
|
||||
to_return = ((data[0]&0x30)>>4)*10; // convert BCD coding into hours (first digit)
|
||||
}
|
||||
to_return += (data[0]&0x0f); // convert BCD coding into hours (second digit)
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_day(void)
|
||||
{
|
||||
uint8_t to_return = 0; // day to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x03}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
to_return = (data[0]&0x07); // convert BCD coding into days
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_date(void)
|
||||
{
|
||||
uint8_t to_return = 0; // date to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x04}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
to_return = ((data[0]&0x30)>>4)*10+(data[0]&0x0f); // convert BCD coding into date
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_month(void)
|
||||
{
|
||||
uint8_t to_return = 0; // month to return
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x05}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
to_return = ((data[0]&0x10)>>4)*10+(data[0]&0x0f); // convert BCD coding into month
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t rtc_ds1307_read_year(void)
|
||||
{
|
||||
uint8_t data[1] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x06}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
|
||||
return 0xff;
|
||||
}
|
||||
uint8_t to_return = ((data[0]&0xf0)>>4)*10+(data[0]&0x0f); // convert BCD coding into year
|
||||
return to_return;
|
||||
}
|
||||
|
||||
uint8_t* rtc_ds1307_read_time(void)
|
||||
{
|
||||
static uint8_t time[7] = {0}; // store time {seconds, minutes, hours, day, date, month, year}
|
||||
uint8_t data[7] = {0}; // to read data over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read all time bytes
|
||||
return NULL; // error occurred
|
||||
}
|
||||
time[0] = ((data[0]&0x70)>>4)*10+(data[0]&0x0f); // convert seconds from BCD
|
||||
time[1] = (data[1]>>4)*10+(data[1]&0x0f); // convert minutes from BCD
|
||||
time[2] = 0; // re-initialize hours
|
||||
if (data[2]&0x40) { // 12 hour mode
|
||||
if (data[2]&0x02) { // PM
|
||||
time[2] += 12; // add the 12 hours
|
||||
}
|
||||
time[2] += ((data[2]&0x10)>>4)*10; // convert BCD coding into hours (first digit)
|
||||
} else {
|
||||
time[2] = ((data[2]&0x30)>>4)*10; // convert BCD coding into hours (first digit)
|
||||
}
|
||||
time[2] += (data[2]&0x0f); // convert BCD coding into hours (second digit)
|
||||
time[3] = (data[3]&0x07); // convert BCD coding into days
|
||||
time[4] = ((data[4]&0x30)>>4)*10+(data[4]&0x0f); // convert BCD coding into date
|
||||
time[5] = ((data[5]&0x10)>>4)*10+(data[5]&0x0f); // convert BCD coding into month
|
||||
time[6] = ((data[6]&0xf0)>>4)*10+(data[6]&0x0f); // convert BCD coding into year
|
||||
return time;
|
||||
}
|
||||
|
||||
bool rtc_ds1307_read_ram(uint8_t* data, uint8_t start, uint8_t length)
|
||||
{
|
||||
// sanity checks
|
||||
if (data==NULL || length==0) { // nothing to read
|
||||
return false;
|
||||
}
|
||||
if (start>55 || start+length>56) { // out of bounds RAM
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t address[] = {0x08+start}; // memory address for data
|
||||
return i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // read RAM (starting at 0x08)
|
||||
}
|
||||
|
||||
bool rtc_ds1307_oscillator_disable(void)
|
||||
{
|
||||
uint8_t data[1] = {0}; // to write CH value data over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
|
||||
return false;
|
||||
}
|
||||
data[0] |= 0x80; // set CH to disable oscillator
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
|
||||
}
|
||||
|
||||
bool rtc_ds1307_oscillator_enable(void)
|
||||
{
|
||||
uint8_t data[1] = {0}; // to write CH value data over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
|
||||
return false;
|
||||
}
|
||||
data[0] &= 0x7f; // clear CH to enable oscillator
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_square_wave(uint16_t frequency)
|
||||
{
|
||||
uint8_t data[1] = {0}; // to write control register value data over I2C
|
||||
switch (frequency) { // set RS1/RS0 based on frequency
|
||||
case 0:
|
||||
data[0] = 0;
|
||||
break;
|
||||
case 1:
|
||||
data[0] = 0|(1<<4);
|
||||
break;
|
||||
case 4096:
|
||||
data[0] = 1|(1<<4);
|
||||
break;
|
||||
case 8192:
|
||||
data[0] = 2|(1<<4);
|
||||
break;
|
||||
case 32768:
|
||||
data[0] = 3|(1<<4);
|
||||
break;
|
||||
default: // unspecified frequency
|
||||
return false;
|
||||
}
|
||||
const uint8_t address[] = {0x07}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_seconds(uint8_t seconds)
|
||||
{
|
||||
if (seconds>59) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to read CH value data and write seconds value over I2C
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
|
||||
return false;
|
||||
}
|
||||
data[0] &= 0x80; // only keep CH flag
|
||||
data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format
|
||||
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with previous CH value
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_minutes(uint8_t minutes)
|
||||
{
|
||||
if (minutes>59) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format
|
||||
|
||||
const uint8_t address[] = {0x01}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_hours(uint8_t hours)
|
||||
{
|
||||
if (hours>24) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format
|
||||
|
||||
const uint8_t address[] = {0x02}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_day(uint8_t day)
|
||||
{
|
||||
if (day<1 || day>7) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (day%8); // encode day in BCD format
|
||||
|
||||
const uint8_t address[] = {0x03}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_date(uint8_t date)
|
||||
{
|
||||
if (date<1 || date>31) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format
|
||||
|
||||
const uint8_t address[] = {0x04}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_month(uint8_t month)
|
||||
{
|
||||
if (month<1 || month>12) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format
|
||||
|
||||
const uint8_t address[] = {0x05}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_year(uint8_t year)
|
||||
{
|
||||
if (year>99) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[1] = {0}; // to write time value
|
||||
data[0] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format
|
||||
|
||||
const uint8_t address[] = {0x06}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_time(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t day, uint8_t date, uint8_t month, uint8_t year)
|
||||
{
|
||||
uint8_t data[7] = {0}; // to write all time values
|
||||
const uint8_t address[] = {0x00}; // memory address for data
|
||||
// seconds
|
||||
if (seconds>59) {
|
||||
return false;
|
||||
}
|
||||
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, 1)) { // read seconds with CH value
|
||||
return false;
|
||||
}
|
||||
data[0] &= 0x80; // only keep CH flag
|
||||
data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format
|
||||
// minutes
|
||||
if (minutes>59) {
|
||||
return false;
|
||||
}
|
||||
data[1] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format
|
||||
// hours
|
||||
if (hours>24) {
|
||||
return false;
|
||||
}
|
||||
data[2] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format
|
||||
// day
|
||||
if (day<1 || day>7) {
|
||||
return false;
|
||||
}
|
||||
data[3] = (day%8); // encode day in BCD format
|
||||
// date
|
||||
if (date<1 || date>31) {
|
||||
return false;
|
||||
}
|
||||
data[4] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format
|
||||
// month
|
||||
if (month<1 || month>12) {
|
||||
return false;
|
||||
}
|
||||
data[5] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format
|
||||
// year
|
||||
if (year>99) {
|
||||
return false;
|
||||
}
|
||||
data[6] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format
|
||||
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
|
||||
}
|
||||
|
||||
bool rtc_ds1307_write_ram(uint8_t* data, uint8_t start, uint8_t length)
|
||||
{
|
||||
// sanity checks
|
||||
if (data==NULL || length==0) { // nothing to read
|
||||
return false;
|
||||
}
|
||||
if (start>55 || start+length>56) { // out of bounds RAM
|
||||
return false;
|
||||
}
|
||||
const uint8_t address[] = {0x08+start}; // memory address for data
|
||||
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write RAM (starting at 0x08)
|
||||
}
|
140
lib/rtc_ds1307.h
140
lib/rtc_ds1307.h
@ -1,140 +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 the Maxim DS1307 I2C RTC IC (API)
|
||||
* @file rtc_ds1307.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016-2017
|
||||
* @note peripherals used: I2C @ref i2c_master_i2c
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup communication with RTC IC
|
||||
* configure the I2C port defined in the sources
|
||||
*/
|
||||
void rtc_ds1307_setup(void);
|
||||
/** verify if oscillator is disabled
|
||||
* @return if oscillator is disabled (or if communication error occurred)
|
||||
*/
|
||||
bool rtc_ds1307_oscillator_disabled(void);
|
||||
/** read square wave output frequency (in Hz)
|
||||
* @return square wave output frequency in Hz, 0 if disabled (0xffff if communication error occurred)
|
||||
*/
|
||||
uint16_t rtc_ds1307_read_square_wave(void);
|
||||
/** read seconds from RTC IC
|
||||
* @return number of seconds (0-59) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_seconds(void);
|
||||
/** read minutes from RTC IC
|
||||
* @return number of minutes (0-59) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_minutes(void);
|
||||
/** read hours from RTC IC
|
||||
* @return number of hours (0-23) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_hours(void);
|
||||
/** read day from RTC IC
|
||||
* @return day of the week (1-7, 1 is Sunday) of the current time, 1 being Sunday (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_day(void);
|
||||
/** read date from RTC IC
|
||||
* @return day of the month (1-31) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_date(void);
|
||||
/** read month from RTC IC
|
||||
* @return month of the year (1-12) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_month(void);
|
||||
/** read year from RTC IC
|
||||
* @return year of the century (00-99) of the current time (0xff if communication error occurred)
|
||||
*/
|
||||
uint8_t rtc_ds1307_read_year(void);
|
||||
/** read time from RTC IC
|
||||
* @return array of {seconds, minutes, hours, day, date, month, year} as defined above (NULL if communication error occurred)
|
||||
*/
|
||||
uint8_t* rtc_ds1307_read_time(void);
|
||||
/** read user RAM from RTC IC
|
||||
* @param[out] data array to store the RAM read
|
||||
* @param[in] start start of the user RAM to read (0-55)
|
||||
* @param[in] length number of user RAM bytes to read (0-55)
|
||||
* @return if read succeeded
|
||||
*/
|
||||
bool rtc_ds1307_read_ram(uint8_t* data, uint8_t start, uint8_t length);
|
||||
/** disable RTC IC oscillator
|
||||
* @return if disabling oscillator succeeded
|
||||
*/
|
||||
bool rtc_ds1307_oscillator_disable(void);
|
||||
/** enable RTC IC oscillator
|
||||
* @return if enabling oscillator succeeded
|
||||
*/
|
||||
bool rtc_ds1307_oscillator_enable(void);
|
||||
/** write square wave output frequency (in Hz)
|
||||
* @param[in] frequency square wave output frequency in Hz (0 to disable, 1, 4096, 8192, 32768)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_square_wave(uint16_t frequency);
|
||||
/** write seconds into RTC IC
|
||||
* @param[in] seconds number of seconds (0-59)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_seconds(uint8_t seconds);
|
||||
/** write minutes into RTC IC
|
||||
* @param[in] minutes number of minutes (0-59)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_minutes(uint8_t minutes);
|
||||
/** write hours into RTC IC
|
||||
* @param[in] hours number of hours (0-23)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_hours(uint8_t hours);
|
||||
/** write day into RTC IC
|
||||
* @param[in] day day of the week (1-7, 1 is Sunday)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_day(uint8_t day);
|
||||
/** write date into RTC IC
|
||||
* @param[in] date day of the month (1-31)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_date(uint8_t date);
|
||||
/** write month into RTC IC
|
||||
* @param[in] month month of the year (1-12)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_month(uint8_t month);
|
||||
/** write year into RTC IC
|
||||
* @param[in] year year of the century (00-99)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_year(uint8_t year);
|
||||
/** write time into RTC IC
|
||||
* @param[in] seconds number of seconds (0-59)
|
||||
* @param[in] minutes number of minutes (0-59)
|
||||
* @param[in] hours number of hours (0-23)
|
||||
* @param[in] day day of the week (1-7, 1 is Sunday)
|
||||
* @param[in] date day of the month (1-31)
|
||||
* @param[in] month month of the year (1-12)
|
||||
* @param[in] year year of the century (00-99)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_time(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t day, uint8_t date, uint8_t month, uint8_t year);
|
||||
/** write user RAM from RTC IC
|
||||
* @param[in] data array of byte to write in RAM
|
||||
* @param[in] start start of the user RAM to write (0-55)
|
||||
* @param[in] length number of user RAM bytes to write (0-55)
|
||||
* @return if write succeeded
|
||||
*/
|
||||
bool rtc_ds1307_write_ram(uint8_t* data, uint8_t start, uint8_t length);
|
||||
|
@ -1,190 +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 query measurements from Aosong DHT11 temperature and relative humidity sensor (code)
|
||||
* @file sensor_dht11.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: timer channel @ref sensor_dht11_timer
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer utilities
|
||||
|
||||
/* own libraries */
|
||||
#include "sensor_dht11.h" // PZEM electricity meter header and definitions
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup sensor_dht11_timer timer peripheral used to measure signal timing for bit decoding
|
||||
* @{
|
||||
*/
|
||||
#define SENSOR_DHT11_TIMER 3 /**< timer peripheral */
|
||||
#define SENSOR_DHT11_CHANNEL 1 /**< channel used as input capture */
|
||||
#define SENSOR_DHT11_JITTER 0.1 /**< signal timing jitter tolerated in timing */
|
||||
/** @} */
|
||||
|
||||
volatile bool sensor_dht11_measurement_received = false;
|
||||
|
||||
/** communication states */
|
||||
volatile enum sensor_dht11_state_t {
|
||||
SENSOR_DHT11_OFF, // no request has started
|
||||
SENSOR_DHT11_HOST_START, // host starts request (and waits >18ms)
|
||||
SENSOR_DHT11_HOST_STARTED, // host started request and waits for slave answer
|
||||
SENSOR_DHT11_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
|
||||
SENSOR_DHT11_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
|
||||
SENSOR_DHT11_MAX
|
||||
} sensor_dht11_state = SENSOR_DHT11_OFF; /**< current communication state */
|
||||
|
||||
/** the bit number being sent (MSb first), up to 40 */
|
||||
volatile uint8_t sensor_dht11_bit = 0;
|
||||
|
||||
/** the 40 bits (5 bytes) being sent by the device */
|
||||
volatile uint8_t sensor_dht11_bits[5] = {0};
|
||||
|
||||
/** reset all states */
|
||||
static void sensor_dht11_reset(void)
|
||||
{
|
||||
// reset states
|
||||
sensor_dht11_state = SENSOR_DHT11_OFF;
|
||||
sensor_dht11_bit = 0;
|
||||
sensor_dht11_measurement_received = false;
|
||||
gpio_set(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
|
||||
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
|
||||
timer_ic_disable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
|
||||
timer_disable_counter(TIM(SENSOR_DHT11_TIMER)); // disable timer
|
||||
}
|
||||
|
||||
void sensor_dht11_setup(void)
|
||||
{
|
||||
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
|
||||
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // enable clock for GPIO peripheral
|
||||
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT11_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(SENSOR_DHT11_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(SENSOR_DHT11_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(SENSOR_DHT11_TIMER), 20-1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/20/(2**16))=18.20ms )
|
||||
timer_ic_set_input(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT11_CHANNEL)); // configure ICx to use TIn
|
||||
timer_ic_set_filter(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
|
||||
timer_ic_set_polarity(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_FALLING); // capture on rising edge
|
||||
timer_ic_set_prescaler(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
|
||||
|
||||
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
|
||||
timer_update_on_overflow(TIM(SENSOR_DHT11_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
|
||||
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL)); // clear input compare flag
|
||||
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_CCIE(SENSOR_DHT11_CHANNEL)); // enable capture interrupt
|
||||
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT11_TIMER)); // catch interrupt in service routine
|
||||
|
||||
sensor_dht11_reset(); // reset state
|
||||
}
|
||||
|
||||
bool sensor_dht11_measurement_request(void)
|
||||
{
|
||||
if (sensor_dht11_state!=SENSOR_DHT11_OFF) { // not the right state to start (wait up until timeout to reset state)
|
||||
return false;
|
||||
}
|
||||
if (gpio_get(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL))==0) { // signal should be high per default
|
||||
return false;
|
||||
}
|
||||
if (TIM_CR1(TIM(SENSOR_DHT11_TIMER))&(TIM_CR1_CEN)) { // timer should be off
|
||||
return false;
|
||||
}
|
||||
sensor_dht11_reset(); // reset states
|
||||
|
||||
// send start signal (pull low for > 18 ms)
|
||||
gpio_clear(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // set signal to low
|
||||
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
|
||||
timer_enable_counter(TIM(SENSOR_DHT11_TIMER)); // enable timer to wait for 18 ms until overflow
|
||||
sensor_dht11_state = SENSOR_DHT11_HOST_START; // remember we started sending signal
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void)
|
||||
{
|
||||
struct sensor_dht11_measurement_t measurement = { 0xff, 0xff }; // measurement to return
|
||||
if (sensor_dht11_bit<40) { // not enough bits received
|
||||
return measurement;
|
||||
}
|
||||
if ((uint8_t)(sensor_dht11_bits[0]+sensor_dht11_bits[1]+sensor_dht11_bits[2]+sensor_dht11_bits[3])!=sensor_dht11_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
|
||||
return measurement;
|
||||
}
|
||||
// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
|
||||
measurement.humidity = sensor_dht11_bits[0];
|
||||
measurement.temperature = sensor_dht11_bits[2];
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer */
|
||||
void TIM_ISR(SENSOR_DHT11_TIMER)(void)
|
||||
{
|
||||
if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
|
||||
if (sensor_dht11_state==SENSOR_DHT11_HOST_START) { // start signal sent
|
||||
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
|
||||
sensor_dht11_state = SENSOR_DHT11_HOST_STARTED; // switch to next state
|
||||
timer_ic_enable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
|
||||
} else { // timeout occurred
|
||||
sensor_dht11_reset(); // reset states
|
||||
}
|
||||
} else if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL))) { // edge detected on input capture
|
||||
uint16_t time = TIM_CCR(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL); // save captured bit timing (this clear also the flag)
|
||||
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
|
||||
time = (time*1E6)/(rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_DHT11_TIMER))+1)); // calculate time in us
|
||||
switch (sensor_dht11_state) {
|
||||
case (SENSOR_DHT11_HOST_STARTED): // the host query data and the slave is responding
|
||||
sensor_dht11_state = SENSOR_DHT11_SLAVE_START; // set new state
|
||||
break;
|
||||
case (SENSOR_DHT11_SLAVE_START): // the slave sent the start signal
|
||||
if (time >= ((80+80)*(1-SENSOR_DHT11_JITTER)) && time <= ((80+80)*(1+SENSOR_DHT11_JITTER))) { // response time should be 80 us low and 80 us high
|
||||
sensor_dht11_state = SENSOR_DHT11_SLAVE_BIT; // set new state
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case (SENSOR_DHT11_SLAVE_BIT): // the slave sent a bit
|
||||
if (sensor_dht11_bit>=40) { // no bits should be received after 40 bits
|
||||
goto error;
|
||||
}
|
||||
if (time >= ((50+26)*(1-SENSOR_DHT11_JITTER)) && time <= ((50+28)*(1+SENSOR_DHT11_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
|
||||
sensor_dht11_bits[sensor_dht11_bit/8] &= ~(1<<(7-(sensor_dht11_bit%8))); // clear bit
|
||||
} else if (time >= ((50+70)*(1-SENSOR_DHT11_JITTER)) && time <= ((50+70)*(1+SENSOR_DHT11_JITTER))) { // bit 1 time should be 50 us low and 70 us high
|
||||
sensor_dht11_bits[sensor_dht11_bit/8] |= (1<<(7-(sensor_dht11_bit%8))); // set bit
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
sensor_dht11_bit++;
|
||||
if (sensor_dht11_bit>=40) { // all bits received
|
||||
sensor_dht11_reset(); // reset states
|
||||
sensor_dht11_bit = 40; // signal decoder all bits have been received
|
||||
sensor_dht11_measurement_received = true; // signal user all bits have been received
|
||||
}
|
||||
break;
|
||||
default: // unexpected state
|
||||
error:
|
||||
sensor_dht11_reset(); // reset states
|
||||
}
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
}
|
@ -1,41 +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 query measurements from Aosong DHT11 temperature and relative humidity sensor (API)
|
||||
* @file sensor_dht11.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: timer channel @ref sensor_dht11_timer (add external pull-up resistor)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** a measurement response has been received */
|
||||
extern volatile bool sensor_dht11_measurement_received;
|
||||
|
||||
/** measurement returned by sensor */
|
||||
struct sensor_dht11_measurement_t {
|
||||
uint8_t humidity; /**< relative humidity in %RH (20-95) */
|
||||
uint8_t temperature; /**< temperature in °C (0-50) */
|
||||
};
|
||||
|
||||
/** setup peripherals to communicate with sensor */
|
||||
void sensor_dht11_setup(void);
|
||||
/** request measurement from sensor
|
||||
* @return request started successfully
|
||||
*/
|
||||
bool sensor_dht11_measurement_request(void);
|
||||
/** decode received measurement
|
||||
* @return decoded measurement (0xff,0xff if invalid)
|
||||
*/
|
||||
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void);
|
@ -1,194 +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 query measurements from Aosong DHT22 temperature and relative humidity sensor (code)
|
||||
* @file sensor_dht22.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: GPIO and timer @ref sensor_dht22_timer
|
||||
* @note the DHT22 protocol is very similar but nit completely compatible with the DHT22 protocol: only 1 ms initial host pull low is required (vs. 18 ms), the data is encoded as int16_t (vs. uint8_t), and the signal has more jitter
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <math.h> // maths utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/timer.h> // timer utilities
|
||||
|
||||
/* own libraries */
|
||||
#include "sensor_dht22.h" // PZEM electricity meter header and definitions
|
||||
#include "global.h" // common methods
|
||||
|
||||
/** @defgroup sensor_dht22_timer timer peripheral used to measure signal timing for bit decoding
|
||||
* @{
|
||||
*/
|
||||
#define SENSOR_DHT22_TIMER 4 /**< timer peripheral */
|
||||
#define SENSOR_DHT22_CHANNEL 3 /**< channel used as input capture */
|
||||
#define SENSOR_DHT22_JITTER 0.2 /**< signal timing jitter tolerated in timing */
|
||||
/** @} */
|
||||
|
||||
volatile bool sensor_dht22_measurement_received = false;
|
||||
|
||||
/** communication states */
|
||||
volatile enum sensor_dht22_state_t {
|
||||
SENSOR_DHT22_OFF, // no request has started
|
||||
SENSOR_DHT22_HOST_START, // host starts request (and waits >18ms)
|
||||
SENSOR_DHT22_HOST_STARTED, // host started request and waits for slave answer
|
||||
SENSOR_DHT22_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
|
||||
SENSOR_DHT22_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
|
||||
SENSOR_DHT22_MAX
|
||||
} sensor_dht22_state = SENSOR_DHT22_OFF; /**< current communication state */
|
||||
|
||||
/** the bit number being sent (MSb first), up to 40 */
|
||||
volatile uint8_t sensor_dht22_bit = 0;
|
||||
|
||||
/** the 40 bits (5 bytes) being sent by the device */
|
||||
volatile uint8_t sensor_dht22_bits[5] = {0};
|
||||
|
||||
/** reset all states */
|
||||
static void sensor_dht22_reset(void)
|
||||
{
|
||||
// reset states
|
||||
sensor_dht22_state = SENSOR_DHT22_OFF;
|
||||
sensor_dht22_bit = 0;
|
||||
sensor_dht22_measurement_received = false;
|
||||
|
||||
gpio_set(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
|
||||
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
|
||||
|
||||
timer_ic_disable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
|
||||
timer_disable_counter(TIM(SENSOR_DHT22_TIMER)); // disable timer
|
||||
}
|
||||
|
||||
void sensor_dht22_setup(void)
|
||||
{
|
||||
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
|
||||
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // enable clock for GPIO peripheral
|
||||
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT22_TIMER)); // enable clock for timer peripheral
|
||||
timer_reset(TIM(SENSOR_DHT22_TIMER)); // reset timer state
|
||||
timer_set_mode(TIM(SENSOR_DHT22_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
|
||||
timer_set_prescaler(TIM(SENSOR_DHT22_TIMER), 2-1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/2/(2**16))=1.820ms )
|
||||
timer_ic_set_input(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT22_CHANNEL)); // configure ICx to use TIn
|
||||
timer_ic_set_filter(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
|
||||
timer_ic_set_polarity(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_FALLING); // capture on rising edge
|
||||
timer_ic_set_prescaler(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
|
||||
|
||||
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
|
||||
timer_update_on_overflow(TIM(SENSOR_DHT22_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
||||
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
||||
|
||||
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL)); // clear input compare flag
|
||||
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_CCIE(SENSOR_DHT22_CHANNEL)); // enable capture interrupt
|
||||
|
||||
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT22_TIMER)); // catch interrupt in service routine
|
||||
|
||||
sensor_dht22_reset(); // reset state
|
||||
}
|
||||
|
||||
bool sensor_dht22_measurement_request(void)
|
||||
{
|
||||
if (sensor_dht22_state!=SENSOR_DHT22_OFF) { // not the right state to start (wait up until timeout to reset state)
|
||||
return false;
|
||||
}
|
||||
if (gpio_get(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL))==0) { // signal should be high per default
|
||||
return false;
|
||||
}
|
||||
if (TIM_CR1(TIM(SENSOR_DHT22_TIMER))&(TIM_CR1_CEN)) { // timer should be off
|
||||
return false;
|
||||
}
|
||||
sensor_dht22_reset(); // reset states
|
||||
|
||||
// send start signal (pull low for > 1 ms)
|
||||
gpio_clear(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // set signal to low
|
||||
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
|
||||
timer_enable_counter(TIM(SENSOR_DHT22_TIMER)); // enable timer to wait for 1.8 ms until overflow
|
||||
sensor_dht22_state = SENSOR_DHT22_HOST_START; // remember we started sending signal
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void)
|
||||
{
|
||||
struct sensor_dht22_measurement_t measurement = { NAN, NAN }; // measurement to return
|
||||
if (sensor_dht22_bit<40) { // not enough bits received
|
||||
return measurement;
|
||||
}
|
||||
if ((uint8_t)(sensor_dht22_bits[0]+sensor_dht22_bits[1]+sensor_dht22_bits[2]+sensor_dht22_bits[3])!=sensor_dht22_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
|
||||
return measurement;
|
||||
}
|
||||
// calculate measured values (stored as uint16_t deci-value)
|
||||
measurement.humidity = (int16_t)((sensor_dht22_bits[0]<<8)+sensor_dht22_bits[1])/10.0;
|
||||
measurement.temperature = (int16_t)((sensor_dht22_bits[2]<<8)+sensor_dht22_bits[3])/10.0;
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
/** interrupt service routine called for timer */
|
||||
void TIM_ISR(SENSOR_DHT22_TIMER)(void)
|
||||
{
|
||||
if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
||||
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
|
||||
if (sensor_dht22_state==SENSOR_DHT22_HOST_START) { // start signal sent
|
||||
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
|
||||
sensor_dht22_state = SENSOR_DHT22_HOST_STARTED; // switch to next state
|
||||
timer_ic_enable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
|
||||
} else { // timeout occurred
|
||||
sensor_dht22_reset(); // reset states
|
||||
}
|
||||
} else if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL))) { // edge detected on input capture
|
||||
uint16_t time = TIM_CCR(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL); // save captured bit timing (this clear also the flag)
|
||||
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
|
||||
time = (time*1E6)/(rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_DHT22_TIMER))+1)); // calculate time in us
|
||||
switch (sensor_dht22_state) {
|
||||
case (SENSOR_DHT22_HOST_STARTED): // the host query data and the slave is responding
|
||||
sensor_dht22_state = SENSOR_DHT22_SLAVE_START; // set new state
|
||||
break;
|
||||
case (SENSOR_DHT22_SLAVE_START): // the slave sent the start signal
|
||||
if (time >= ((80+80)*(1-SENSOR_DHT22_JITTER)) && time <= ((80+80)*(1+SENSOR_DHT22_JITTER))) { // response time should be 80 us low and 80 us high
|
||||
sensor_dht22_state = SENSOR_DHT22_SLAVE_BIT; // set new state
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case (SENSOR_DHT22_SLAVE_BIT): // the slave sent a bit
|
||||
if (sensor_dht22_bit>=40) { // no bits should be received after 40 bits
|
||||
goto error;
|
||||
}
|
||||
if (time >= ((50+26)*(1-SENSOR_DHT22_JITTER)) && time <= ((50+28)*(1+SENSOR_DHT22_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
|
||||
sensor_dht22_bits[sensor_dht22_bit/8] &= ~(1<<(7-(sensor_dht22_bit%8))); // clear bit
|
||||
} else if (time >= ((50+70)*(1-SENSOR_DHT22_JITTER)) && time <= ((50+70)*(1+SENSOR_DHT22_JITTER))) { // bit 1 time should be 50 us low and 70 us high
|
||||
sensor_dht22_bits[sensor_dht22_bit/8] |= (1<<(7-(sensor_dht22_bit%8))); // set bit
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
sensor_dht22_bit++;
|
||||
if (sensor_dht22_bit>=40) { // all bits received
|
||||
sensor_dht22_reset(); // reset states
|
||||
sensor_dht22_bit = 40; // signal decoder all bits have been received
|
||||
sensor_dht22_measurement_received = true; // signal user all bits have been received
|
||||
}
|
||||
break;
|
||||
default: // unexpected state
|
||||
error:
|
||||
sensor_dht22_reset(); // reset states
|
||||
}
|
||||
} else { // no other interrupt should occur
|
||||
while (true); // unhandled exception: wait for the watchdog to bite
|
||||
}
|
||||
}
|
@ -1,41 +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 query measurements from Aosong DHT22 (aka. AM2302) temperature and relative humidity sensor (API)
|
||||
* @file sensor_dht22.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: timer channel @ref sensor_dht22_timer (add external pull-up resistor)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** a measurement response has been received */
|
||||
extern volatile bool sensor_dht22_measurement_received;
|
||||
|
||||
/** measurement returned by sensor */
|
||||
struct sensor_dht22_measurement_t {
|
||||
float humidity; /**< relative humidity in %RH (0-100) */
|
||||
float temperature; /**< temperature in °C (-40-80) */
|
||||
};
|
||||
|
||||
/** setup peripherals to communicate with sensor */
|
||||
void sensor_dht22_setup(void);
|
||||
/** request measurement from sensor
|
||||
* @return request started successfully
|
||||
*/
|
||||
bool sensor_dht22_measurement_request(void);
|
||||
/** decode received measurement
|
||||
* @return decoded measurement (0xff,0xff if invalid)
|
||||
*/
|
||||
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void);
|
@ -1,196 +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 for Maxim DS18B20 digital temperature sensor (using 1-Wire protocol) (code)
|
||||
* @file sensor_ds18b20.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: 1-Wire (timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio)
|
||||
* @warning this library does not support parasite power mode and alarms
|
||||
*/
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean type
|
||||
#include <stdlib.h> // size_t definition
|
||||
#include <math.h> // NAN definition
|
||||
|
||||
/* own libraries */
|
||||
#include "global.h" // help macros
|
||||
#include "onewire_master.h" // 1-Wire utilities
|
||||
#include "sensor_ds18b20.h" // own definitions
|
||||
|
||||
/** remember number of DS18B20 sensors on 1-Wire bus for certain functions */
|
||||
uint64_t sensors = 0;
|
||||
/** remember if only DS18B20 sensors on 1-Wire bus for certain functions */
|
||||
bool only = false;
|
||||
/** remember code of last sensor **/
|
||||
uint64_t last = 0;
|
||||
|
||||
void sensor_ds18b20_setup(void)
|
||||
{
|
||||
onewire_master_setup(); // setup 1-Wire peripheral to communicate with sensors on bus
|
||||
sensor_ds18b20_number(); // scan for sensor (remembers sensor number and exclusivity)
|
||||
}
|
||||
|
||||
uint64_t sensor_ds18b20_number(void)
|
||||
{
|
||||
sensors = 0; // reset number
|
||||
only = true; // reset state
|
||||
uint64_t code = 0; // ROM code found (use 0 to start from scratch)
|
||||
bool more = true; // save if other additional slaves exist
|
||||
|
||||
while (more) { // scan for all 1-Wire slaves
|
||||
if (!onewire_master_reset()) { // send reset to start communication
|
||||
return 0; // no slave presence detected
|
||||
}
|
||||
more = onewire_master_rom_search(&code, false); // get next slave ROM code (without alarm)
|
||||
if (0==code) { // error occurred
|
||||
return 0;
|
||||
}
|
||||
if (0x28==(code&0xff)) { // family code (8-LSb) for DS18B20 sensors is 0x28
|
||||
last = code; // save last found code
|
||||
sensors++; // we found an additional sensor
|
||||
} else {
|
||||
only = false; // we found a slave which is not a sensor
|
||||
}
|
||||
}
|
||||
|
||||
return sensors;
|
||||
}
|
||||
|
||||
bool sensor_ds18b20_only(void)
|
||||
{
|
||||
sensor_ds18b20_number(); // this also checks for exclusivity
|
||||
return only;
|
||||
}
|
||||
|
||||
bool sensor_ds18b20_list(uint64_t* code)
|
||||
{
|
||||
if (!onewire_master_reset()) { // send reset to start communication
|
||||
return false; // no slave presence detected
|
||||
}
|
||||
onewire_master_rom_search(code, false); // get next code
|
||||
return (last!=*code); // verify if the last has been found
|
||||
}
|
||||
|
||||
bool sensor_ds18b20_convert(uint64_t code)
|
||||
{
|
||||
if (0==code && !only) { // asked for broadcast but there are different slaves on bus
|
||||
return false; // broadcast not possible when there are also different slaves on bus
|
||||
}
|
||||
|
||||
// send reset pulse
|
||||
if (!onewire_master_reset()) { // send reset to start communication
|
||||
return false; // no slave presence detected
|
||||
}
|
||||
|
||||
// send ROM command to select slave(s)
|
||||
if (0==code) { // broadcast convert
|
||||
if (!onewire_master_rom_skip()) { // select all slaves
|
||||
return false; // ROM command failed
|
||||
}
|
||||
} else {
|
||||
if (!onewire_master_rom_match(code)) { // select specific slave
|
||||
return false; // ROM command failed
|
||||
}
|
||||
}
|
||||
|
||||
// send convert T function command
|
||||
return onewire_master_function_read(0x44, NULL, 0);
|
||||
}
|
||||
|
||||
float sensor_ds18b20_temperature(uint64_t code)
|
||||
{
|
||||
if (0==code && (sensors>1 || !only)) { // broadcast read requested
|
||||
return NAN; // this function is not possible when several sensors or other devices are present
|
||||
}
|
||||
|
||||
// send reset pulse
|
||||
if (!onewire_master_reset()) { // send reset to start communication
|
||||
return NAN; // no slave presence detected
|
||||
}
|
||||
|
||||
// send ROM command to select slave
|
||||
if (!onewire_master_rom_match(code)) { // select specific slave
|
||||
return NAN; // ROM command failed
|
||||
}
|
||||
|
||||
// read scratchpad to get temperature (on byte 0 and 1)
|
||||
uint8_t scratchpad[9] = {0}; // to store read scratchpad
|
||||
if (!onewire_master_function_read(0xbe, scratchpad, sizeof(scratchpad)*8)) { // read complete scratchpad
|
||||
return NAN; // error occurred during read
|
||||
}
|
||||
|
||||
// verify if data is valid
|
||||
if (onewire_master_crc(scratchpad, sizeof(scratchpad))) { // check CRC checksum
|
||||
return NAN; // data corrupted
|
||||
}
|
||||
|
||||
// calculate temperature (stored as int16_t but on 0.125 C steps)
|
||||
return ((int16_t)(scratchpad[1]<<8)+scratchpad[0])/16.0; // get temperature (on < 16 precision the last bits are undefined, but that doesn't matter for the end result since the lower precision is still provided)
|
||||
}
|
||||
|
||||
bool sensor_ds18b20_precision(uint64_t code, uint8_t precision)
|
||||
{
|
||||
if (precision<9 || precision>12) { // check input
|
||||
return false; // wrong precision value
|
||||
}
|
||||
|
||||
if (0==code && !only) { // asked for broadcast but there are different slaves on bus
|
||||
return false; // broadcast not possible when there are also different slaves on bus
|
||||
}
|
||||
|
||||
// send reset pulse
|
||||
if (!onewire_master_reset()) { // send reset to start communication
|
||||
return false; // no slave presence detected
|
||||
}
|
||||
|
||||
// send ROM command to select slave(s)
|
||||
if (0==code) { // broadcast convert
|
||||
if (!onewire_master_rom_skip()) { // select all slaves
|
||||
return false; // ROM command failed
|
||||
}
|
||||
} else {
|
||||
if (!onewire_master_rom_match(code)) { // select specific slave
|
||||
return false; // ROM command failed
|
||||
}
|
||||
}
|
||||
|
||||
// read scratchpad to get alarm values (on byte 2 and 3)
|
||||
uint8_t scratchpad[9] = {0}; // to store read scratchpad
|
||||
if (!onewire_master_function_read(0xbe, scratchpad, sizeof(scratchpad)*8)) { // read complete scratchpad
|
||||
return false; // error occurred during read
|
||||
}
|
||||
|
||||
// verify if data is valid
|
||||
if (onewire_master_crc(scratchpad, sizeof(scratchpad))) { // check CRC checksum
|
||||
return false; // data corrupted
|
||||
}
|
||||
|
||||
// send new configuration (and keep the alarm values)
|
||||
uint8_t configuration[3] = {0}; // to store T_HIGH, T_LOW, and configuration
|
||||
configuration[0] = scratchpad[2]; // keep T_HIGH
|
||||
configuration[1] = scratchpad[3]; // keep T_LOW
|
||||
configuration[2] = 0x1f+((precision-9)<<5); // set precision bit (R1-R0 on bit 6-5)
|
||||
if (!onewire_master_function_write(0x4e, configuration, sizeof(configuration)*8)) { // write scratchpad with new configuration (all three bytes must be written)
|
||||
return false; // error occurred during write
|
||||
}
|
||||
|
||||
// store new configuration into sensor's EEPROM for retrieval on next power up
|
||||
if (!onewire_master_function_read(0x48, NULL, 0)) { // copy scratchpad (to EEPROM)
|
||||
return false; // error during copy
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,56 +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 for Maxim DS18B20 digital temperature sensor (using 1-Wire protocol) (API)
|
||||
* @file sensor_ds18b20.h
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2017
|
||||
* @note peripherals used: 1-Wire (timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio)
|
||||
* @warning this library does not support parasite power mode and alarms
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** setup 1-Wire peripheral to communicate with sensors on bus */
|
||||
void sensor_ds18b20_setup(void);
|
||||
/** get number of DS18B20 sensors on bus
|
||||
* @return number of DS18B20 sensors on bus
|
||||
*/
|
||||
uint64_t sensor_ds18b20_number(void);
|
||||
/** verify if only DS18B20 sensors are on the bus
|
||||
* @return if only DS18B20 sensors are on the bus
|
||||
*/
|
||||
bool sensor_ds18b20_only(void);
|
||||
/** send all DS18B20 slaves on the bus
|
||||
* @param[out] code ROM code for sensor (0 if error occurred)
|
||||
* @return if an additional sensors have been detected
|
||||
*/
|
||||
bool sensor_ds18b20_list(uint64_t* code);
|
||||
/** start converting (e.g. measuring) temperature
|
||||
* @warning conversion time to wait before reading temperature depends on the resolution set (9 bits: 93.75ms, 10 bits: 187.5ms, 11 bits: 375ms, 12 bits: 950ms)
|
||||
* @param[in] code ROM code of sensor to start conversion on (0 for all, if only DS18B20 sensors are on the bus)
|
||||
* @return if conversion started
|
||||
*/
|
||||
bool sensor_ds18b20_convert(uint64_t code);
|
||||
/** get converted temperature
|
||||
* @note 85.0 C is the default temperature when no conversion has been performed
|
||||
* @param[in] code ROM code of sensor
|
||||
* @return temperature (NaN if error)
|
||||
*/
|
||||
float sensor_ds18b20_temperature(uint64_t code);
|
||||
/** set conversion precision
|
||||
* @param[in] code ROM code of sensor to start conversion on (0 for all, if only DS18B20 sensors are on the bus)
|
||||
* @param[in] precision precision in bits (9-12)
|
||||
* @return if operation succeeded
|
||||
*/
|
||||
bool sensor_ds18b20_precision(uint64_t code, uint8_t precision);
|
@ -1,214 +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 query measurements from peacefair PZEM-004 and PZEM-004T electricity meter (code)
|
||||
* @file sensor_pzem.c
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @date 2016
|
||||
* @note peripherals used: USART @ref sensor_pzem_usart, timer @ref sensor_pzem_timer
|
||||
*/
|
||||
|
||||
/* 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/cm3/nvic.h> // interrupt handler
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
|
||||
#include <libopencm3/stm32/timer.h> // timer utilities
|
||||