Compare commits

...

17 Commits

Author SHA1 Message Date
cfdb9f8977 removed unused files 2019-08-13 23:28:42 +02:00
c6c9c7aed9 fix typo 2019-08-13 23:26:04 +02:00
4c2b3e0516 ir_nikon: add library to receive IR code or Nikon shutter remote 2019-08-13 21:32:16 +02:00
102e1669d9 minor: disable debug 2018-10-28 22:28:28 +01:00
453d35079f minor: update description 2018-10-28 22:28:13 +01:00
e78e67c0e8 application: set keymap of 2 remotes for animations 2018-10-28 22:26:52 +01:00
ba5ea0a6b2 application: add flicker animations 2018-10-28 22:25:56 +01:00
e9078e831e application: add strobe animations 2018-10-28 22:25:40 +01:00
e6a545cd0f application: minor fix 2018-10-28 22:25:06 +01:00
ca4d41e79f application: move RTC initialisation before watchdog in case the locking needs more time 2018-10-28 22:24:19 +01:00
4a96073898 application: use ir_nec extended mode 2018-10-28 22:22:40 +01:00
a7aab104ba ir_nec: add support for extended command address 2018-10-28 22:20:37 +01:00
2f42659b18 add library to decode IR NEC codes (missing header) 2018-10-15 22:07:53 +02:00
b78af16939 document project 2018-10-15 22:06:48 +02:00
4f7a173e23 add NEC code controlled power output animations 2018-10-15 22:06:29 +02:00
10c2973ee7 use blue pill as devboard and stlinkv2 as flasher 2018-10-15 22:05:46 +02:00
0bfae07c30 add library to decode IR NEC codes 2018-10-15 22:05:06 +02:00
48 changed files with 757 additions and 8146 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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]))

View File

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

View File

@ -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);

View File

@ -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;
}

View File

@ -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
View 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
View 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
View 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
View 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);

View File

@ -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
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);

View File

@ -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
}

View File

@ -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);

View File

@ -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
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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",
}
};

View File

@ -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
}
}
}

View File

@ -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);

View File

@ -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
}
}
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);

View File

@ -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)
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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