Quantcast
Channel: Periphery – Mare & Gal Electronics
Viewing all 24 articles
Browse latest View live

RGB LED Badge

$
0
0

This is heart shaped four RGB led badge:

The PCB and schematic is in this PDF file: S54MTB_led-srcek-v1r2

The Software is based on STM32F0 HAL drivers. Here is ZIP with keil uVision project. Please keep the folder structure! Source code has comments and is quite simple. One timer intterupt is driving all leds. The PWM is 1:300 for each color and there are few functions for setting the PWM or the intensity. The function for converting Hue/Saturation/Intensity id also provided in the led.c module. The result of the demo “blinky” is shown in the video above.

The badge has one LiPo cell with battery protection circuit and simple USB charger, so there should be no trouble with the empty batteries.


Simple WEB server hosted on NXP/Freescale FRDM K64F demo board

$
0
0

Kinetis K64F demo board is excellent little board for experimenting with ethernet. There is no quick step-by-step tutorial for building simple web server on this board. The following instructions help bringing working HTTP server on FRDM K64F with one index page and second page with simple CGI script to print out some random numbers. This is absolute minimum for developing any web based gadget with this board.

frdm-k64f120m-board-with-bluetooth-and-nrf24l01

(image source: https://mcuoneclipse.com/2014/04/09/ftf-frdm-k64f-kinetis-design-studio-and-kinetis-sdk/ )

What you will need:

  • Compiling tool: Keil development tools
  • Hardware:
  • Good to read: HTTP server demo application
  • LAN cable and spare port on your switch or router

First start new project, select MK64FN1M0VLL12  and keep default settings except in the tab Utilities, Configure Image File Processing (FCARM). More about this later.

Next, select all required packs for the web server application:

CMSIS, CMSIS Driver for ethernet MAC and PHY:

packs1

Select Kinetis SDK Framework packs:

packs2

Select Kinetis SDK HAL drivers:

packs3

Select additional SDK packs:

packs4

Finally, select Network packs:

packs5

In project explorer add two groups: “SRC” and “Web files”:

project1

Configure RTOS in group CMSIS, file RTX_Conf_CM.C:

rtxconf

In the group Device edit RTE_Device.h, enable MAC and select RMII mode and OSCERCLK for clock source:

eth1

Edit file hardware_init.c and put following code in this file:

#include "board.h"
#include "pin_mux.h"
#include "fsl_clock_manager.h"
#include "fsl_debug_console.h"
 
void hardware_init(void) {
  /* Disable Memory Protection Unit */
  MPU->CESR &= ~MPU_CESR_VLD_MASK;
 
  /* enable clock for PORTs */
  CLOCK_SYS_EnablePortClock(PORTA_IDX);
  CLOCK_SYS_EnablePortClock(PORTB_IDX);
  CLOCK_SYS_EnablePortClock(PORTC_IDX);
  CLOCK_SYS_EnablePortClock(PORTD_IDX);
  CLOCK_SYS_EnablePortClock(PORTE_IDX);
 
  configure_enet_pins (0);
 
  /* Setup board clock source. */
  g_xtal0ClkFreq = 50000000U;
  g_xtalRtcClkFreq = 32768U;
 
  /* Init board clock */
  BOARD_ClockInit();
  dbg_uart_init();
}

In the group Network edit file Net_Config.c. Enter Local host name, memory pool size, enable system services and adjust core thread stack size:
netconfig
 

Edit ethernet configuration in file Net_Config_Eth_0.h with the addresses suitable for your LAN for IP v4 and v6:

ethconfig

Enable all legacy callbacks in Net_Config_Legacy.h This is required to enable backward compatibility with legacy API.

Configure TCP layer in file Net_Config_TCP.h:

tcpconfig

Adjust UDP sockets to 5 in file Net_Config_UDP.h

Edit HTTP server configuration in file Net_Config_HTTP_Server.h :

httpconf

Final settings is in the project settings, tab Utilities, Configure Image File Processing (FCARM): enter web.c for output file and select file group where to place the generated source file. Define the root folder for image files. The “image” files are all files with the web page content. The FCARM will automatically translate those files to array in target flash memory.

fcarm

 


Now it’s time to add some code.

Add main.c to group SRC:

#include "cmsis_os.h"                   /* CMSIS RTOS definitions             */
#include "rl_net.h"                     /* Network definitions                */
#include "board.h"                      /* BSP definitions                    */
 
/*----------------------------------------------------------------------------
  Main Thread 'main': Run Network
 *---------------------------------------------------------------------------*/
int main (void) {
 
  hardware_init();
 
  net_initialize     ();
 
  while(1) {
    net_main ();
    osThreadYield ();
  }
}

 

 

Add main web page in file index.htm and add it to group “Web files”:

<head>
<title>S54MTB Testing Web server</title>
</head>
 
<body bgColor=#ffffff >
<div align=center>
 The web server is working :)
 <br>
</div></body>
</html>

 

Now it’s time to compile and make the project. At first it will take a bit longer. If everything is OK, the project will compile to about 70k of code, 4,8k RO data for web content, requiring about 38k of data.

 

Next step is to add some dynamic content.

 

Edit index file and add one additional link to the file test.cgi:

<head>
<title>S54MTB Testing Web server</title>
</head>
 
<body bgColor=#ffffff >
<div align=center>
 The web server is working :)
 <br>
 <hr>
 <a href="/test.cgi">Test - dynamic content</a>
</div></body>
</html>

 

add file test.cgi to group “Web files”:

t <html><head><title>Test CGI</title>
t <meta http-equiv="refresh" content="5"></head>
t <h2 align=center><br>Test CGI --- print some RND numbers</h2>
t <center>
c r
c a
t </center>
t </html>
. End of script must be closed with period.

 

Few words about Scripting Language for the Common Gateway Interface (CGI) – from keil documentation:

CGI is a standard method used to generate dynamic content on web pages. CGI stands for Common Gateway Interface and provides an interface between the HTTP server and programs generating web content. These programs are better known as CGI scripts. They are written in a scripting language. The Network Component provides such a scripting language. The HTTP server processes the script source file line by line and calls the CGI functions as needed. The output from a CGI function is sent to the web client (browser) as a part of the web page.

Each script line starts with a command character which specifies a command for the script interpreter. The scripting language itself is simple and works as follows:

i The script interpreter include a file from the file system and sends the content to the web browser.
t The line of text that follows will be sent to the browser.
c Calls the function netCGI_Script from the HTTP_Server_CGI.c file. It may be followed by a line of text which is passed to netCGI_Script as a pointer to an environment variable.
# This is a comment line and is ignored by the interpreter.
. Denotes the end of the script.
Note
The script files need to use the reserved filename extension of cgi, for the HTTP server’s script interpreter to recognize and process the files accordingly.
The script line length is limited to 120 characters.

Our example above will call function uint32_t cgi_script (const char *env, char *buf, uint32_t buflen, uint32_t *pcgi) ;
We should add interpreter for the CGI calls by rewriting this function. Add file http_cgi.c to group SRC with following code:

#include <stdio.h>
#include "rl_net.h"
#include "rl_net_lib.h"
#include <stdlib.h> // rand()
 
 
// Generate dynamic web data from a script line.
uint32_t cgi_script (const char *env, char *buf, uint32_t buflen, uint32_t *pcgi) {
 uint32_t len = 0;
 
switch (env[0]) {
 
case 'r':
 // testing ... write random number
 len = sprintf (buf, "Tralala Random number between 0 and 50 is %d !!!<br>", rand() % 50);
 break;
 
case 'a':
 // Write some text
 len = sprintf (buf, " Hopsasa Random number between 50 and 100 is %d !!!<br>", 50 + rand() % 50);
 break;
 }
 
return (len);
}

 

 

Our new little web server will now respond with the following two pages:

web1    web2

 

This simple web server project for FRDM K64F will be soon available on GIT repository.

 

SiLabs Si7013 humidity and temperature sensor

$
0
0

I got samples of Si7013.

The Si7013 I2C Humidity and 2-Zone Temperature Sensor is a monolithic CMOS IC integrating humidity and temperature sensor elements, an analog-to-digital
converter, signal processing, calibration data, and an I2C Interface. The patented use of industry-standard, low-K polymeric dielectrics for sensing humidity enables the construction of low-power, monolithic CMOS Sensor ICs with low drift and hysteresis, and excellent long term stability. The humidity and temperature sensors are factory-calibrated and the calibration data is stored in the on-chip non-volatile memory. This ensures that the sensors are fully interchangeable, with no recalibration or software changes required. An auxiliary sensor input with power management can be tied directly to an external thermistor network or other voltage-output sensor. On-board logic performs calibration/linearization of the external input using user-programmable coefficients. The least-significant bit of the Si7013’s I2C address is programmable, allowing two devices to share the same bus. The Si7013 is available in a 3×3 mm DFN package and is reflow solderable. The
optional factory-installed cover offers a low profile, convenient means of protecting the sensor during assembly (e.g., reflow soldering) and throughout the life of the product, excluding liquids (hydrophobic/oleophobic) and particulates. The Si7013 offers an accurate, low-power, factory-calibrated digital solution ideal for measuring humidity, dew-point, and temperature, in applications ranging from HVAC/R and asset tracking to industrial and consumer platforms.

I prepared prototyping board with STM32F0 and line RS485 driver:

rht  pcbrht0

 

Next, I populated the PCB with the components:

pcbrht

The sensor schematic is from Si7013 datasheet, typical application:

sch

The microcontroller pinout is:

pinout

The microcontroller UART has hardware support for DE in RS-485 Half Duplex interface, which is nice feature for reliable RS485 operation.

Finally, I prepared driver for Si7013. I put the code to GitHUB, because it will hopefully evolve to some usable final application. First “alpha” testing provide reading and writing of “user registers” for chip configuration and readout of the humidity, temperature and external thermistor voltage.

si7013 driver: https://github.com/s54mtb/Si7013

And some preliminary testing:

test

Adding RS485 to FRDM-K64F

$
0
0

FRDM-K64F is nice piece of development board. Unfortunately, it is “bonded” to MBED development platform, which is a nightmare! Adding RS485 “out of the box” to the FRDM-K64F in MBED is NoGo. But it is very easy.

Here is image from my scope sending single char via UART4 with enabled RTS as DE for RS485 in Half Duplex mode:

scope_0

There is “bluetooth” connector J199 on the board. It is connected to UART4 pins PTC14 (UART4_Rx) and PTC15 (UART4_Tx). The signal UART_RTS is available on connector J6, pin 3:

k64f-rs485

 

Initialization for UAR4 is simple when using UART Periph Driver:

#include "fsl_uart_driver.h"   // UART periph driver
 
int main (void) {
    uint8_t txchar = 'A';
 
    // Initialize variable uartState of type uart_state_t
    uart_state_t uartState;
 
    // Uart config data
    uart_user_config_t uartConfig = {
        .bitCountPerChar = kUart8BitsPerChar,
        .parityMode      = kUartParityDisabled,
        .stopBitCount    = kUartOneStopBit,
        .baudRate        = 9600
    };
 
  hardware_init();
  
  // Init UART4          
  UART_DRV_Init(UART4_IDX, &uartState, &uartConfig);
  // Config RTS
  PORTC_PCR12 |= PORT_PCR_MUX(3);   // PC13 ... Alt3 = UART4_RTS (RS485 DE)
  UART4_MODEM |= UART_MODEM_TXRTSE_MASK;  // enable rts
  
  while(1) {
    UART4_D = txchar++;
  }
}

USB to RS485

$
0
0

Testing of sensors with RS485 using PC without proper interface is not possible. Since RS232 interfaces are very rare, the interface should be hooked to USB. The interface between USB and RS485 can be soldered with one of the many FTDI interfaces with added RS485 driver, or bought as assembled module. There is always the third option. I made it from scratch.

I took smallest STM32F0 with USB and UART interface. The best thing with UART in the STM32F0 is that signal for driver enable is provided within hardware. The complete pinout of the microcontroller is:

pinout

The hardware part is easy. The complete PCB is one-sided. I kept it as minimalistic as possible. There are three active components: voltage regulator, microcontroller and RS485 transceiver. The complete schematic:

sch

The PCB is single sided:

pcb pcb3d

Here is complete altium project: USB-RS485

The documentation in PDF file: USB2RS485-doc

For all who want to produce the PCB with the toner transfer technology is here the PCB, mirrored and with 1:1 scale: USB2RS232-tonertransfer

And the most useful sheet of documentation for assembling:

assembly

After soldering it’s time to put the module to life:

pcb-assembled

The project, generated from the MX cube is almost all we need. There are few things to add:

  • retransmit bytes received from USB to UART
  • forward bytes received via UART to USB
  • add serial line coding interpretation and reflect it in the hardware UART

The code for sending bytes from UART to USB is within main endless loop:

if (HAL_UART_Receive_IT(&huart2, &aRxBuffer, 1) == HAL_OK) 
   CDC_Transmit_FS(&aRxBuffer,1)

Code to send data from USB to UART is within CDC_Receive_FS() function:

static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
 /* USER CODE BEGIN 6 */
 USBD_CDC_SetRxBuffer(hUsbDevice_0, &Buf[0]);
 USBD_CDC_ReceivePacket(hUsbDevice_0);
 HAL_UART_Transmit(&huart2, Buf, *Len, 100);
 return (USBD_OK);
 /* USER CODE END 6 */ 
}

And finally, the code to set the UART line parameters are within USB CDC_Control_FS() function:

  /*******************************************************************************/
 /* Line Coding Structure */
 /*-----------------------------------------------------------------------------*/
 /* Offset | Field | Size | Value | Description */
 /* 0 | dwDTERate | 4 | Number |Data terminal rate, in bits per second*/
 /* 4 | bCharFormat | 1 | Number | Stop bits */
 /* 0 - 1 Stop bit */
 /* 1 - 1.5 Stop bits */
 /* 2 - 2 Stop bits */
 /* 5 | bParityType | 1 | Number | Parity */
 /* 0 - None */
 /* 1 - Odd */ 
 /* 2 - Even */
 /* 3 - Mark */
 /* 4 - Space */
 /* 6 | bDataBits | 1 | Number Data bits (5, 6, 7, 8 or 16). */
 /*******************************************************************************/
 case CDC_SET_LINE_CODING: 
 linecoding.bitrate = (uint32_t)(pbuf[0] | (pbuf[1] << 8) | (pbuf[2] << 16) | (pbuf[3] << 24));
 linecoding.format = pbuf[4];
 linecoding.paritytype = pbuf[5];
 linecoding.datatype = pbuf[6];
 huart2.Init.BaudRate = linecoding.bitrate;
 switch (linecoding.format)
 {
 case 0 : huart2.Init.StopBits = UART_STOPBITS_1; break;
 case 1 : huart2.Init.StopBits = UART_STOPBITS_1_5; break;
 case 2 : huart2.Init.StopBits = UART_STOPBITS_2; break;
 }
 switch (linecoding.paritytype)
 {
 case 0 : huart2.Init.Parity = UART_PARITY_NONE; break;
 case 1 : huart2.Init.Parity = UART_PARITY_ODD; break;
 case 2 : huart2.Init.Parity = UART_PARITY_EVEN; break;
 }
 //UART_WORDLENGTH_7B
 switch (linecoding.datatype)
 {
 case 7 : huart2.Init.WordLength = UART_WORDLENGTH_7B; break;
 case 8 : huart2.Init.WordLength = UART_WORDLENGTH_8B; break;
 }
 HAL_RS485Ex_Init(&huart2, UART_DE_POLARITY_HIGH, 0, 0);
 
 break;

The complete project is hosted on GitHub>>> https://github.com/s54mtb/USB2RS485

The driver for the USB/CDC device class can be downloaded from: http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-utilities/stsw-stm32102.html  or local copy: VCP_V1.4.0_Setup

I will use this small module for testing sensors, which I have under development for my vineyard weather station: humidity, air pressure, temperature, wind, illumination, soil parameters, etc…

Please send comments or questions via form below this post.

HDLC-Like data link via RS485

$
0
0

Communication between microcontrollers is always interesting topic. I was looking for simple, efficient and reliable protocol between several microcontrollers. First I have to choose the physical layer. One of the most commonly used multipoint buses is RS485. Due to simplicity I choose half-duplex RS485. Only two wires is all it needs to establish link between two devices:

rs485

Next problem was finding good data link on top of RS485 physical layer. High-Level Data Link Control or HDLC is data link layer protocol developed by the International Organization for Standardization (ISO) with several standards for frame structure and protocol procedures. Since I am not developing HDLC-compliant hardware I decided to take only part of it (frame structure) and write ultra-simplified “mini” HDLC-like protocol or mHDLC.

The contents of an  mHDLC frame are shown in the following table:

Flag Source address Destination address Control Information FCS Flag
8 bits 8 bits 8 bits 8 bits Variable length, n * 8 bits 16  bits 8 bits
  • The frame boundary octet is 01111110, (0x7E in hexadecimal notation).
  • Since RS485 is limited with the number of the devices on same bus it is enough to use 8-bit addressing. There are two addresses: Source and destination. The sender send first it’s own address followed by address of the addressed device.
  • Control octet is taken from HDLC frame structure. Currently onlu unnumbered information frame is implemented in my code. I put this in the frame structure for future expansion.
  • Information is the sequence of one or more bytes
  • The frame check sequence (FCS) is a 16-bit CRC-CCITT

 

Escape sequences

A “control escape octet”, has the bit sequence ‘01111101’, (7D hexadecimal).
If either frame boundary octet or escape octet appears in the transmitted data, an escape octet is sent, followed by the original data octet with bit 5 inverted. For example, the data sequence “01111110” (7E hex) would be transmitted as “01111101 01011110” (“7D 5E” hex).

 

UART

RS485 is basically asynchronous serial port, which is present in almost every microcontroller. The only difference is that it requires transciever for balanced lines and when bus half-duplex it means the two wires are shared between all devices on the bus. The consequence is that only one device can “talk” at once. This requires additional signal line for driving the data to the bus. The operation is shown in this RS485 Half Duplex mode oscillogram:

scope_0

The implementation of the driver enable and  initialiyation of the UART peripheral device is vendor- and family- specific for each microcontroller.

 

CODE
Code for the mHDLC has two functions which are not implemented and should be provided by application:

void uart_putchar(char ch);
int16_t payload_processor(hdlc_t *payload);

First function is straightforward. Second is callback when information is received for specific device. The example is at the end of this page.  When the mHDLC is started, the function hdlc_init() initializes the whole machinery. And finally, when byte is received via UART/RS485 it should be passed to the function hdlc_process_rx_byte().

 

hdlc.c

/**
  ******************************************************************************
  * File Name          : hdlc.c
  * Description        : HDLC frame parser
  ******************************************************************************
  *
  * Copyright (c) 2016 S54MTB
  * Licensed under Apache License 2.0 
  * http://www.apache.org/licenses/LICENSE-2.0.html
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "hdlc.h"                                // header for this HDLC implementation
#include <string.h>                            // memcpy()
 
 
// Basic setup constants, define for debugging and skip CRC checking, too...
#include "setup.h"
 
 
__weak void uart_putchar(char ch)
{
    // implement function to send character via UART
}
 
//extern void uart_putchar(char ch);
__weak int16_t payload_processor(hdlc_t *hdlc)
{
    // implement function to do something with the received payload
    // when this function return > 0 ... the HDLC frame processor will 
    // initiate sending of the payload back to the device with source address
    return 0;
}
 
static hdlc_t        hdlc;
 
// Static buffer allocations
static uint8_t  _hdlc_rx_frame[HDLC_MRU];   // rx frame buffer allocation
static uint8_t  _hdlc_tx_frame[HDLC_MRU];   // tx frame buffer allocation
static uint8_t  _hdlc_payload[HDLC_MRU];    // payload buffer allocation
 
 
/** Private functions to send bytes via UART */
/* Send a byte via uart_putchar() function */
static void hdlc_tx_byte(uint8_t byte)
{
  uart_putchar((char)byte);
}
 
/* Check and send a byte with hdlc ESC sequence via UART */
static void hdlc_esc_tx_byte(uint8_t byte)
{
    if((byte == HDLC_CONTROL_ESCAPE) || (byte == HDLC_FLAG_SOF))
    {
        hdlc_tx_byte(HDLC_CONTROL_ESCAPE);
        byte ^= HDLC_ESCAPE_BIT;
        hdlc_tx_byte(byte);
    }
    else 
        hdlc_tx_byte(byte);
}
 
 
/* initialiyatiuon of the HDLC state machine, buffer pointers and status variables */
void hdlc_init(void)
{
  hdlc.rx_frame_index = 0;
  hdlc.rx_frame_fcs   = HDLC_CRC_INIT_VAL;
    hdlc.p_rx_frame       = _hdlc_rx_frame;
    memset(hdlc.p_rx_frame, 0, HDLC_MRU);
    hdlc.p_tx_frame       = _hdlc_tx_frame;
    memset(hdlc.p_tx_frame, 0, HDLC_MRU);
    hdlc.p_payload         = _hdlc_payload;
    memset(hdlc.p_payload, 0, HDLC_MRU);
    hdlc.state                    = HDLC_SOF_WAIT;
    hdlc.own_addr                = SETUP_OWNADDRESS;
}
 
 
/* This function should be called when new character is received via UART */
void hdlc_process_rx_byte(uint8_t rx_byte)
{
    switch (hdlc.state)
    {
        case HDLC_SOF_WAIT:   /// Waiting for SOF flag
            if (rx_byte == HDLC_FLAG_SOF) 
            {
                hdlc_init();
                hdlc.state = HDLC_DATARX;
            }
        break;
            
        case HDLC_DATARX:     /// Data reception process running
            if (rx_byte == HDLC_CONTROL_ESCAPE)  // is esc received ?
            {
                hdlc.state = HDLC_PROC_ESC;  // handle ESCaped byte
                break;
            } 
            // not ESC, check for next sof
            if (rx_byte == HDLC_FLAG_SOF) // sof received ... process frame
            {
                if (hdlc.rx_frame_index == 0) // sof after sof ... drop and continue
                    break;
                if (hdlc.rx_frame_index > 5) // at least addresses + crc
                  hdlc_process_rx_frame(hdlc.p_rx_frame, hdlc.rx_frame_index);
                hdlc_init();
                hdlc.state = HDLC_DATARX;
            } else // "normal" - not ESCaped byte
            {
                if (hdlc.rx_frame_index < HDLC_MRU) 
                {
                    hdlc.p_rx_frame[hdlc.rx_frame_index] = rx_byte;
                    hdlc.rx_frame_index++;                    
                } else // frame overrun
                {
                    hdlc_init();   // drop frame and start over
                }
            }
        break;
            
        case HDLC_PROC_ESC:  /// process ESCaped byte
            hdlc.state = HDLC_DATARX; // return to normal reception after this
          if (hdlc.rx_frame_index < HDLC_MRU) // check for overrun
            {
                rx_byte ^= HDLC_ESCAPE_BIT;  // XOR with ESC bit
                hdlc.p_rx_frame[hdlc.rx_frame_index] = rx_byte;  
                hdlc.rx_frame_index++;
            } else // frame overrun
            {
                hdlc_init();  // drop frame and start over
            }
        break;
            
            
    }
}
 
/** Process received frame buf with length len
  Frame structure:
      [Source Address]                // Address of the data source
        [Destination address]        // Address of the data destination
        [HDLC Ctrl byte]                // only UI - Unnumbered Information with payload are processed
        [payload]                                // 1 or more bytes of payload data
        [crc16-H]                                // MSB of CRC16
        [crc16-L]                                // LSB of CRC16
*/
void hdlc_process_rx_frame(uint8_t *buf, uint16_t len)
{
    if (len>=5)               // 5 bytes overhead (2xaddr+ctrl+crc)
    {
        hdlc.src_addr = buf[0];        // source address --- nedded for sending reply
        hdlc.dest_addr = buf[1];  // destination address --- check for match with own address
        hdlc.ctrl = buf[2];                // HDLC Ctrl byte
        
        // Is the received packet for this device and has proper ctrl ?
        if ((hdlc.dest_addr == SETUP_OWNADDRESS) & (hdlc.ctrl == (HDLC_UI_CMD | HDLC_POLL_FLAG)))
        {
          // process only frame where destination address matches own address
            hdlc.rx_frame_fcs = (uint16_t)(buf[len-2]<<8) | (uint16_t)(buf[len-1]);
            if (len>5)
            {  // copy payload
                memcpy(hdlc.p_payload,hdlc.p_rx_frame+3,len-5);
            }
            #ifndef __SKIPCRC__
            if (crc16(buf, len-2) == hdlc.rx_frame_fcs)
            #endif
            {
                // process received payload
                len = payload_processor(&hdlc);
                if (len > 0)
                {
                    hdlc_tx_frame(hdlc.p_payload, len);
                }
            }
        }        
    }
}
 
// calculate crc16 CCITT
uint16_t crc16(const uint8_t *data_p, uint8_t length)
{
    uint8_t x;
    uint16_t crc = 0xFFFF;
 
    while (length--){
            x = crc >> 8 ^ *data_p++;
            x ^= x>>4;
            crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
    }
    return crc;
}
 
 
// Transmit HDLC UI frame 
void hdlc_tx_frame(const uint8_t *txbuffer, uint8_t len)
{
    //uint8_t  byte;
    uint16_t crc, i;
 
    // Prepare Tx buffer
    hdlc.p_tx_frame[0] = hdlc.own_addr;
    hdlc.p_tx_frame[1] = hdlc.src_addr;
    hdlc.p_tx_frame[2] = HDLC_UI_CMD | HDLC_FINAL_FLAG;
    
    for (i=0; i<len; i++)
    {
        hdlc.p_tx_frame[3+i] = *txbuffer++;
    }
    
    // Calculate CRC
    crc = crc16(hdlc.p_tx_frame, len+3);
    
    // Send/escaped buffer
    for (i=0; i<len+3; i++)
    {
        hdlc_esc_tx_byte(hdlc.p_tx_frame[i]);        // Send byte with esc checking
    }
        
    hdlc_esc_tx_byte((uint8_t)((crc>>8)&0xff));  // Send CRC MSB with esc check
    hdlc_esc_tx_byte((uint8_t)(crc&0xff));      // Send CRC LSB with esc check
    hdlc_tx_byte(HDLC_FLAG_SOF);         // Send flag - stop frame
}
 
 
// Transmit "RAW" HDLC UI frame --- just added SOF and CRC
void hdlc_tx_raw_frame(const uint8_t *txbuffer, uint8_t len)
{
    uint8_t  byte;
    uint16_t crc = crc16(txbuffer, len);
    
    hdlc_tx_byte(HDLC_FLAG_SOF);             // Send flag - indicate start of frame
    while(len)
    {
        byte = *txbuffer++;     // Get next byte from buffer
        hdlc_esc_tx_byte(byte);        // Send byte with esc checking
        len--;
    }
    
    hdlc_esc_tx_byte((uint8_t)((crc>>8)&0xff));  // Send CRC MSB with esc check
    hdlc_esc_tx_byte((uint8_t)(crc&0xff));      // Send CRC LSB with esc check
    hdlc_tx_byte(HDLC_FLAG_SOF);         // Send flag - stop frame
}
 
/* Copyright (c) 2016 S54MTB            ********* End Of File   ********/

 

hdlc.h

 

/**
  ******************************************************************************
  * File Name          : hdlc.h
  * Description        : HDLC implementation
  ******************************************************************************
  *
  * Copyright (c) 2016 S54MTB
  * Licensed under Apache License 2.0 
  * http://www.apache.org/licenses/LICENSE-2.0.html
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#ifndef __HDLC_H__
#define __HDLC_H__
 
#define HDLC_MRU    256
// HDLC constants --- RFC 1662 
#define HDLC_FLAG_SOF				  0x7e   // Flag
#define HDLC_CONTROL_ESCAPE 	0x7d   // Control Escape octet
#define HDLC_ESCAPE_BIT     	0x20   // Transparency modifier octet (XOR bit)
#define HDLC_CRC_INIT_VAL   	0xffff
#define HDLC_CRC_MAGIC_VAL   	0xf0b8
#define HDLC_CRC_POLYNOMIAL  	0x8408
#define HDLC_UI_CMD						0x03     // Unnumbered Information with payload
#define HDLC_FINAL_FLAG       0x10     // F flag
#define HDLC_POLL_FLAG       0x10      // P flag
 
typedef enum
{
	HDLC_SOF_WAIT,
	HDLC_DATARX,
	HDLC_PROC_ESC,
} hdlc_state_t;
 
typedef struct 
{
	uint8_t				own_addr;
	uint8_t				src_addr;
	uint8_t  			dest_addr;
	uint8_t 			ctrl;
	uint8_t				*p_tx_frame;			// tx frame buffer
	uint8_t 			*p_rx_frame;			// rx frame buffer
	uint8_t				*p_payload;				// payload pointer
	uint16_t   		rx_frame_index;
	uint16_t			rx_frame_fcs;
	hdlc_state_t	state;
} hdlc_t;
 
void hdlc_init(void);
void hdlc_process_rx_byte(uint8_t rx_byte);
void hdlc_process_rx_frame(uint8_t *buf, uint16_t len);
void hdlc_tx_frame(const uint8_t *txbuffer, uint8_t len);
uint16_t hdlc_crc_update(uint16_t crc, uint8_t dat);
uint16_t crc16(const uint8_t *data_p, uint8_t length);
void hdlc_tx_raw_frame(const uint8_t *txbuffer, uint8_t len);
#endif

 

example of payload processor:

 

#include "payload_processor.h"
#include "si7013.h"
#include "setup.h"
#include "hdlc.h"
 
enum
{
	CMD_Temperature = 0x30,
	CMD_Humidity,
	CMD_Thermistor,
	CMD_ID,
};
 
 
extern I2C_HandleTypeDef hi2c1;
extern si7013_userReg_t si7013_userRegs;
extern UART_HandleTypeDef huart2;
 
 
int16_t payload_processor(hdlc_t *hdlc/*uint8_t *payload*/)
{	
	uint8_t response[6],i;
  int16_t len=0;
	uint32_t uid = UNIQUE_ID;
 
	switch (hdlc->p_payload[0])
	{
		case CMD_Temperature :
			si7013_measure_intemperature(&hi2c1,(int32_t *)response);
			len=5;
		break;
 
		case CMD_Humidity :
			si7013_measure_humidity(&hi2c1,(int32_t *)response);
			len=5;			
		break;
 
		case CMD_Thermistor:
			si7013_userRegs.reg2.b.vout = 1; // connect the thermistor to vdd
			si7013_userRegs.reg2.b.vrefp = 1; // connect vref to vdd	
			si7013_write_regs(&hi2c1, &si7013_userRegs);
			si7013_measure_thermistor(&hi2c1, (int16_t *)response);
		  len = 3;
		break;
 
		case CMD_ID:
			si7013_get_device_id(&hi2c1,response);
		  memcpy(&response[1],&uid,4);
			len=6;						
		break;		
	}
 
	for (i=0; i<len; i++) hdlc->p_payload[i+1]=response[i];
 
	return len;
}

Here is example of the testing setup…

The sensor (slave) is Humidity/Temperature sensor , the master is WEB server based on FRDM K64 which requires RS485 transciever, there is also “sniffer” for RS485 based on this RS485/USB design.

debugiranje0

1 – web server, 2 – RS485 for FRDM KE64 board, 3 – Humidity and temperature sensor, 4 – USB/RS485 interface.

 

debugiranje

Debug session (left:web server/top and sensor/bot) with RS485 sniffer at work and final result, web page with the measurements read via mHDLC.

Keyboard for a CNC – recycling a programmable keyboard

$
0
0
Final look

Final look

This keyboard is more than 10 years old. I got it for free but it had its problems. First one was a PS/2 interface. Not ideal for a keyboard used in 2016. Second problem was software for programming this keyboard. It came on not one, but two diskettes with DOS and Windows versions of the software.

That’s where I decided to take it apart and replace electronics inside. New controller is based on KeyWarrior IC which is capable of scanning 16×8 matrix.

This keyboard merges a standard keyboard with a jog controller for a CNC machine. Upper half are “normal” keys and bottom half is for controlling my CNC machine. The shortcut keys will save me a lot of time and trouble when working with the machine.

Keyboard’s original software came on two diskettes with software for DOS and windows

Two diskettes containing software

Two diskettes containing software

 

Switches are made by Cherry Americas. They are mounted using face plate mount technique. Each keycap has another cap made from clear plastic. A key label goes between those two caps.

Switch made by Cherry Americas

Switch made by Cherry Americas

 

Changing electronics

When I first opened it I was surprised how the matrix is arranged. It was done so to prevent phantom keys without using diodes. Board had one Motorola microcontroller and two 1/8 multiplexers. horizontal lines of the matrix were connected directly to the Motorola chip while other twelve went through two multiplexers to get the number of required inputs down.

Main board of the keyboard

Main board of the keyboard

 

My USB keyboard controller

It is based on a KeyWarrior IC with external EEPROM for storing key tables. Device requires 6 MHz crystal to operate (6 MHz / 4 = 1.5 MHz). TVS diode array on USB protects the device from potential electrostatic discharges and other inconveniences that could occur on the USB lines. Additional PNP transistors extend the maximum current of lock key indicators so it is suitable for driving more powerful LEDs. Matrix connectors are placed on sides.

KeyWarrior based USB controller

KeyWarrior based USB controller

 


Gerbers and design files are available here: http://gal.pavlin.si/key/keyboard-controller.zip


 

Here’s my keyboard controller mounted where the old one used to be. Mount was made with a piece of PMMA plastic.

My USB controller

My USB controller

I like devices to have connectors instead of fixed cables because it makes them more practical. My controller has standard mini USB connector that is commonly found in cameras.

Mini USB connector

Mini USB connector

 

It wasn’t difficult to find which pins belong to matrix. All I had to do was open the datasheet of the multiplexer and check which pins are inputs. Finding the horizontal 8 was even easier because they all had 10 kOhm pulldown resistors.

All wired to the board

All wired to the board

It all worked perfectly.

 

Key labels

Next thing I did was print key labels. I still had to figure out the most suitable layout of the keys so some experimenting was present. It took me a good hour.

Key labels

Keyboard with key labels next to it

 

Key mapping

Key mapping is done via USB and it’s rather simple and straightforward. Chip I used supports 31 character long macros for each key on 3 levels. This means you can easily map “www.” or “.com” on a single key.

Table Editor

Table Editor

It took some time to figure out the matrix because it had no visible succession.

Keyboard ready to serve its purpose

Keyboard ready to serve its purpose

 

I hope I won’t have to use the red corner buttons too much. They are emergency stop keys.

Pressure, temperature and humidity sensor based on MS5637 HDC1080

$
0
0

This is anothe small module to measure air pressure, temperature and humidity. Two sensors are on-board: MS5637 and HDC1080. Microcontroller is small cortex M0 in TSSOP-20 housing from STM: STM32F070CxP. The SN65HVD72DGKR provides RS485 interface functionality with half duplex mode. Voltage regulator, reverse polarity protection and some LED indicators are provided on-board. Complete module is 10x55mm, produced on single-sided PCB, easily producible in every home lab with proto-PCB capability.

RH, T and air pressure sensor with RS485

RH, T and air pressure sensor with RS485

Firmware for the sensor is on the GitHub in my MS5637_HDC1080 repository.

Sensor has default HDLC address 0x31 and unique ID 0x0d000011.

Initial version has  following HDLC commands

enum
{
    CMD_Temperature = 0x40, /// Temperature readout from hdc1080
    CMD_Humidity,           /// Humidity readout from hdc1080
    CMD_Bat,                /// battery readout from hdc1080
    CMD_Pressure,           /// Air pressure in hPa or mbar abs
    CMD_pTemperature,       /// Temperature readout from pressure sensor
    CMD_pCAL,               /// Calibration coefficients from pressure sensor
    CMD_pD1,                /// Raw pressure readout from pressure sensor
    CMD_pD2,                /// Raw temperature from pressure sensor
    CMD_ID,                 /// Identification
};

Drivers for both sensors are:

https://github.com/s54mtb/MS5637_HDC1080/blob/master/src/hdc1080.c
https://github.com/s54mtb/MS5637_HDC1080/blob/master/src/MS5637.c


Other project resources:

rhtp-pcb

Single sided PCB

 

rhtp-sch

Schematic diagram

“Toner transfer” artwork for single sided PCB: RHTP

Here is complete documentation for the PCB, schematic and BOM:

RHTP-projectPCB-doc


Source code generator for Command Line Interpreter (CLI)

$
0
0

Microcontroller with serial bus or USB CDC (virtual COM port) is usually connected to some terminal. User then type commands and firmware in the microcontroller interprets entered commands.

Developing simple interpreter is not very scientific task, more like PITA with repeating chunks of the code. After couple of successful projects with such interpreter I can say my code is somehow tested and proven in the practice. Now I prepared one application which can shorten development process and make my life easier when I start application in new device.

The source code generator generates only command line interpreter. It provides all identifiers for selected commands, provides function prototypes for each command and text template for help. Finally only the functions for executing specific commands should be then implemented.

The final application can be either multi-threaded with RTOS, or flat single thread. In both cases, there should be some function or ISR checking for new char and feeding this char to the command line editor. When new line “arrives”, the function cmd_proc() is called and then the magic happens.

cli1

General structure of the caller thread or application

Click here for the application for generating CLI source code<<<<   —-    DOWNLOAD

Application requires maximum length of the command (including optional parameters) and list of commands. The commands are listed in the text box on the left:

cli2

When all commands are listed, hit the button “Generate code” and check the source code in the right text box. Button “Copy all” put the whole text in clipboard.

 

Here is example of using the code…

In main part of the microcontroller application (function main() or one thread in RTOS), put something like this:

int main(void)
{
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
///////// further initializations....
 
 
 
  /* Infinite loop */
  while (1)
  {
      if (is_line_received())
        {
//     Process received line
            cmd_proc(get_line_buffer());
        }  // if
  }  // while
}

 

Then provide three functions:

  1. process_rx_char(char rx_char)

    —   for command line editor and another

  2. unsigned char is_line_received(void)

    —   for indicating new line received

  3. char *get_line_buffer(void)

    —   which only returns pointer to the newly received line

Example source code:

/**
 * Command line edit chars
 */
#define CNTLQ      0x11
#define CNTLS      0x13
#define DEL        0x7F
#define BACKSPACE  0x08
#define CR         0x0D
#define LF         0x0A
 
static unsigned char line_flag; 			// Flag to indicate new received line
char line_buf[256];
int line_idx = 0;
extern void abort_autorun(void);
 
/**
 * Process received char, check if LF or CR received
 * Set flag when line is done
 */
void process_rx_char(char rx_char)
{
		abort_autorun();
 
    if (rx_char == CR)  rx_char = LF;   
    if (rx_char == BACKSPACE  ||  rx_char == DEL) 
    {    // process backspace
      	if (line_idx != 0)  
      	{            
        	line_idx--;                      // decrement index
        	#ifdef LOCALECHO
        		putchar (BACKSPACE);               // echo backspace
        		putchar (' ');
        		putchar (BACKSPACE);
        	#endif
      	}
    }
    else 
    {
      	#ifdef LOCALECHO
      		putchar (rx_char);                   // echo 
      	#endif 
      	line_buf[line_idx++] = rx_char; 	   // store character and increment index
    }
 
    // check limit and end line feed
  	if ((line_idx == 0xff)  ||  (rx_char == LF))
  	{
  		line_buf[line_idx-1] = 0;                  // mark end of string
  		line_idx = 0;
			line_flag = 1;
  	}
}
 
/**
 * Indicate new line received via UART. 
 * Return 1 when new line received. Status flag is cleared to 0.
 * Return 0 if new line is not received yet
 */
unsigned char is_line_received(void)
{
	if (line_flag == 1)
	{
		line_flag = 0;
		return 1;
	}
	else
	{
		return 0;
	}
}
 
char *get_line_buffer(void)
{
	return line_buf;
}

 

The commands in the autogenerated code are at the end of the source code:

}/**
   * CMD_COMMAND1
   * @brief 
   * @param Arguments string from AUD command
   * @param None
   * @retval None
   */
void cmd_COMMAND1(char *argstr_buf)
 
{
 
  // add code here --------
 
 
 
}

 

 

 

 

UV index sensor with RS485 interface

LED shades

$
0
0

Unfinished content – work in progress.

 

Light Emitting Dance shades – general purpose party accessory.

 

About this project

I believe many young adults like to party and with the rapidly growing EDM community a lot of people attend parties with electronic music. I am one of them and you can probably tell where I got the idea to make the LED shades. Projects like this one don’t just keep me occupied in my free time but are also designed to broaden my knowledge in electronics and programming.

I want to share what I have learned working on this project and that’s why I’m making it open-source. I hope this would encourage someone to follow my steps in building the LED shades themselves while exposing design flaws and advantages. This would push the project further and eventually make the shades better for all of us.

Lens from wayfarer style sunglasses are taken out and replaced by circuit boards with 69 LEDs on each. Every LED can be separately controlled much like pixels on 5×7 display. Both left and right circuit boards have a STM32F0 micro-controller for driving the LEDs wired in a matrix. Among with the LEDs there are also other peripherals such as:

  • I2C bus which enables both sides of the shades to communicate with each other,
  • MEMS motion sensor (accelerometer) to track head movements,
  • microphone to make glasses react to music,
  • light sensor to make glasses react to light,
  • two buttons for changing modes/effects currently displayed ,
  • LiPo/LiIon battery charger,
  • mini USB connector for charging and connectivity with a windows based application,
  • extra ADC input for experimenting, status LED, battery voltage monitor, USART connector for bluetooth to enable connectivity with an android based phone.

 

Schematics

Click the picture to see the schematics in PDF form.

led matrika preview

Schematics – master

led matrika mirror preview

Schematics – slave

LED matrix

Driving 69 LEDs separately would require at least 69 pins which is inefficient. To reduce pin number and thus minimize MCU’s footprint and cost LEDs have to be wired in a matrix and constantly scanned. This is a good article on how exactly LEDs in matrix work written by Make Magazine.

LEDs

I used standard single color 0603 LEDs I had at home. I checked the datasheet and calculated the current limiting resistor value with the 80% of the rated absolute maximum current that can flow through the diode. I did that in order to make LEDs as bright as possible. Because the matrix is constantly being scanned a single LED is turned on just 1/8 of the time (matrix is scanned vertically and has 8 rows). Effective current  through a LED is in fact much lower (about 6 mA).

LEDs connected in a matrix

LEDs connected in a matrix

Switching

Dual P channel (dual just to reduce footprint area) MOSFETs are switching rows (horizontal) and dual N channel MOSFETs are switching columns (vertical). I used PMDT670UPE and counterpart PMDT290UNE transistors manufactured by NXP. They are cheap, have low drain to source resistance while open and most importantly they have a very small footprint (SOT666 1.7 x 1.3 mm)

Dual P channel MOSFET

Dual P channel MOSFET

 

MCU

I decided to go with STM32F0 series because of cost and support for crystal-less USB. After some research I chose STM32F042G6 in UFQFPN28 case with 4×4 mm footprint. It has 12 bit ADC which is suitable for audio processing. I managed to use all pins on both controllers for either driving the matrix or connectivity with the peripherals.

3-to-8 line decoder/demultiplexer

At first I thought the MCU would have enough pins but at about 60% into drawing the schematics I realized it won’t. I solved this issue with an external 74HC138BQ 3-8 line decoder. It is very cheap and takes only 3.5 x 2.5 mm of area in QFN package. Outputs are inverting (LOW active) what makes it perfect for driving P channel FETs.

3 to 8 line decoder

3 to 8 line decoder

Programming connector

Because of lack of space programming is done with TAG connect. I think it’s convenient because it takes less space than other solutions. It also does not require any soldering. You can get TAG connect footprint for Altium designer here.

TAG connect – source: www.tag-connect.com

Power supply and management

Supply

Power is supplied from an external LiPo or LiIon batter-y/ies with integrated protection circuit. I’m using two 200 mAh LiPo batteries each mounted on one side of the glasses. They should last for about two hours of use.

Management

Voltage delivered by battery varies with the charge in the battery so some kind of management and regulation had to be implemented.

Vcc line regulator

TPS63031 buck/boost switching regulator with fixed output of 3.3V regulates the input voltage. It is rated for 1.2A and makes a perfect choice with 2.5 x 2.5 mm footprint. It requires an external inductor and input and output capacitors.

Buck/boost fixed 3.3V switching regulator

Buck/boost fixed 3.3V switching regulator

Single push button on/off

The input of TPS63031  is constantly connected to the battery and in shutdown mode when the device is not being used. Shutdown current is only few uA and I calculated it would take around 10 years to completely discharge the battery. Take a look at the bottom picture.

Single push button on/off.

Single push button on/off.

Let’s assume that the device is in shutdown mode. POWER net is connected to Enable pin of the switching regulator. Pull-down resistor prevents the device to power on. When someone pushed the S1 button the battery potential minus the schottky diode voltage drop (0.3V) is forced on the POWER net what causes the regulator to turn on and MCU to boot. Once the MCU is booted it sets the PA15 pin to high. The button can now be released. When the device needs to be shut down another press on the button once again forces the battery potential on the POWER net, however, it also forces it on the PB6 pin and since the MCU is operating (it wasn’t before) it detects that input pin PB6 is high and sets PA15 pin to low. Device is still functioning as long as the button is pushed down. Once the button is released pull-down does the trick and pull POWER net (connected to enable pin of TPS63031 ) to ground what makes the device to shut down.

Battery charging

MCP73811, a 500 mA charger IC is on board. It has both CC and CV modes and manages them automatically. It is powered from USB’s Vbus (5V) and works as linear drop-down regulator. It produces some heat and requires some copper around it on the PCB to ensure good thermal conductivity and dissipation. Like any linear regulator this one too requires external capacitors. IC is in SOT23-5 housing and has a status pin. It is used to either monitor the charge status with a gpio pin on the MCU or with a LED which stays on during the charge and turns off when the battery is charged.

Lipo/LiIon 5V charger

Lipo/LiIon 5V charger

 

Sensors

Sensors make the LED shades react to movement, light and sound.

Movement

The shades can track head movement with LIS3DH MEMS motion sensor (accelerometer). It does not only output data on I2C but also have two interrupt pins that can be programmed to trigger at various events. I chose this sensor because it’s simple and cheap. I also have around 100 of them at home. In addition the sensor features three 12 bit ADCs of which I only used 2. One for temperature sensor (with NTC thermistor) and one for measuring battery voltage.

LIS3DH motion sensor

LIS3DH motion sensor

Measuring battery voltage

Because the voltage of the battery can be grater than the supply of the ADC in the motion sensor a divider has to be used. However, just a resistor divider isn’t enough because it would slowly but surely drain the battery. That can be solved with either using extremely large resistors with a great impact on measurement accuracy or with a transistor that switches the divider on just when it is needed. A single N channel MOSFET is there to enable the divider. POWER net is the same net that enables the Vcc rail regulator.

Voltage divider with enable

Voltage divider with enable – note: the resistor values are symbolic and are yet to be determined.

 

Light

Active element for light sensing isTEMT6200FX01, a phototransistor commonly used in LCDs with automatic brightness. It is cheap, simple and with 0805 footprint. Gain is set with collector resistor. Emitter resistor is there to compensate for thermal drift of the transistor.

Phototransistor as light sensor

Phototransistor as light sensor

 

Sound

Sound pickup is done with a 4 mm electret microphone capsule. The most standard audio pre-amplifier MAX4466 is used to produce usable signals. Because only low frequency sounds are needed to extract the ‘beat’ or tempo from music passive elements around the amplifier are calculated to work as a light low pass filter. Output of the amplifier is directly connected to ADC where it is sampled and processed.

Electrec microphone and amplifier with MAX4466

Electrec microphone and amplifier with MAX4466

PCB design

Due to small size the layout and routing was quite complex. Small holes enable the user to actually see through the glasses. Boards have 2 layers and are routed with 6 mil clearance and 6 mil minimum trace width. There are components on the front and on the back side. The master side (left) is more dense with components since it has got power management.

Click the pictures to see prints in PDF form.

PCB - master

PCB – master

 

PCB - slave

PCB – slave

 

 

 

 

 

LED shades

Standard sunglasses lenses replaced with printed circuit boards.

 

Prototypes

Software

….

 

Licence

Everything (text, schematics in any form, PCB design and prints in any form, source code in any form) published in this post (http://e.pavlin.si/2016/08/14/led-shades/) is protected by Creative commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) licence. Read more about the licence here: https://creativecommons.org/licenses/by-nc/4.0/

You are free to:

  • Share — copy and redistribute the material in any medium or format
  • Adapt — remix, transform, and build upon the material

Under the following terms:

  • Attribution — You must give appropriate credit, provide a link to the license, andindicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
  • NonCommercial — You may not use the material for commercial purposes.

USB/RS485 converter on a double sided PCB

$
0
0

USB/RS485 module on a single sided board worked successfully for several months. The code is now stable and I decided to order small batch of double sided PCBs for this project. Here is documentation:

RS485/USB interface on a double sided board

RS485/USB interface on a double sided board

The complete documentation for USB/RS485 doublesided

Screen capture for assembly:

PCB - Top side

PCB – Top side

 

 

PCB - Bot side

PCB – Bot side

Protected: RotoLED

$
0
0

This content is password protected. To view it please enter your password below:

Affordable pH meter

$
0
0

Our home vineyard delivers about 1000 liters of wine every year. It takes some effort to keep good quality and pleasure for all friends tasting the must, new wine and “senior” wine reserves. One of the most important tasks in wine quality management is controlling the biological and chemical state of the wine. The wine can be monitored in special laboratory or in home lab. Of course home lab become rational when quantity of the wine is high enough to justify expenses in special equipment.  There is some low cost equipment available for wine analysis, mostly with titration techniques, but chemical lab equipment is rather expensive and many hobby wineries avoid buying such equipment. I hope this post can contribute a bit to change that. I will describe the development of simple pH meter in detail.

IMG_6366.jpg    IMG_0855.jpg

Hardware

I will not go much into the details of the pH measurement principle. The topic is explained elsewhere quite deeply:

Complete instrument has only few building blocks: pH electrode, analog frontend, AD converter, microcontroller, power supply and user interface. These are usual building blocks for many similar applications and I would like to describe the circuit in every detail to help others building similar projects based on this one. First I will describe the hardware part, followed by software description. The circuit block diagram is shown in following image:

pH Meter Block Diagram

pH Meter Block Diagram

The sensor: pH electrode

I didn’t want to experiment with some expensive pH probe, because those sensors are very sensitive devices. The can degrade very quickly when not properly connected. Simple short circuit at the electrode can destroy it or at least requires not so easy process of electrode regeneration. So, when experimenting with the pH electrodes: Be careful!

Great source for such electrodes is eBay. Just search for “pH Electrode BNC”.

Analog frontend (AFE)

The pH meter is based on TI’s LMP91200 sensor analog frontend. The LMP91200 is designed for 2-electrode sensors and provides all of the functionality needed to detect changes based on a delta voltage at the sensor. It works over a wide voltage range of 1.8 V to 5.5V. The most important feature for pH electrodes is LMP91200 extremely low input bias current, which is also true in absence of supply voltage. This will reduce degradation of the pH probe and provide longer electrode life.

LMP91200

The interesting thing about LMP91200 is its development cycle. I had about two year old samples from TI sampling system. The LMP91200 AFE datasheet from year 2012 has all pins describedand defined. New revision of the same datasheet has all pins which are normally not used omitted from the description with only recommended connections to disable all extended functionality. I don’t know if the internal circuit was really changed or they just skipped the functions which were not operating as expected. In any case, here is old datasheet, which is not available anywhere and may be useful as source for undocumented features of the LMP91200. The difference is as follows:

New LPM91200 without pins described

 

Old LMP91200 with all pins

A/D Converter

I found nice stock of AD7715 in my old component storage. It is a Σ-Δ ADC which I used in past for some sensor projects with great success. It has differential input and internal PGA, both not really needed for this pH meter, but may be useful for other projects based on this one. It has few on-chip control registers allowing adjustment of the PGA gain, ADC configuration, filter cutoff and output update rate. Depending on the PGA gain it can handle unipolar input signal ranges of 0 mV to 20 mV, 0 mV to 80 mV, 0 V to 1.25 V and 0 V to 2.5 V. The part contains self-calibration and system calibration options to eliminate gain and offset errors on the part itself or in the system. The package is “hobby friendly” SO-16-Wide.

The AD converter can be as good as the voltage reference used for the conversion process. I decided to invest a bit more into the voltage reference to avoid accuracy and long term stability issues in future with such instrument. It is a bit overkill for the pH application, because the pH measurement is calibrated as the whole system, but for any other instrumentation applications it can save few gray hairs.

LMP91200 + ADC with 2,5V refere3nce

LMP91200 + ADC with 2,5V reference

Microcontroller

Microcontroller for this instrument can be low on resources. Good choice is the one with good development support. Combination of low cost and excellent development environment is the STM32F0 from ST with free Keil MDK edition for STM32F0/L0. The price is below 1 EUR and the plastic housing is also “hobby friendly” – TSSOP20. It has USB interface, which can be used instead of user interface to connect with tablet, mobile phone or PC. In presented project, we are dealing with conventional user interface: alphanumeric LCD and rotary encoder.

Microcontroller, encoder and 2x16 LCD

Microcontroller, encoder and 2×16 LCD

User interface

The most common user interface for simple microcontroller projects can be alphanumeric LCD and simple rotary encoder. Both can provide simple and efficient control over the instrument features, calibration, settings, etc… The connection diagram is shown above.

 

Now it’s time to solder everything together and start programming.

 

Software

Software development is based on free Keil MDK Version 5, the MDK for STM32F0 and STM32L0 edition, which includes the ARM C/C++ Compiler, the CMSIS-RTOS RTX Kernel, and the µVision IDE/Debugge free of charge. The source code is Unlicensed.

AD7715

Functional block diagram of the AD7715 reveals requirement for SPI interface. We will not use DRDY signal. It can be polled from the status bit in the internal register of the AD7715.

AD7715 Functional block diagram

AD7715 Functional block diagram

 

AD7715 is connected to Cortex M0 via four pins: MISO, MOSI, SCK and CS. It is also possible to use shared MISO/MOSI. I decided to prepare the driver with bit-banging serial interface, which can be ported to any other microcontroller without hardware SPI interface. The communication is very simple and can be implemented on smallest MCUs. Here is description for the STM32F0.

First we have to define pins for communication. In our case, the pinout is CLK: PA5, MOSI: PA7, MISO: PA6 and CS: PF1:

 

/* CLK: PA5 */
#define AD7715_CLKPORT        GPIOA
#define AD7715_CLKPINn        5
 
/* MOSI: PA7 */
#define AD7715_MOSIPORT        GPIOA
#define AD7715_MOSIPINn   7
 
/* MISO: PA6 */
#define AD7715_MISOPORT        GPIOA
#define AD7715_MISOPINn     6
 
/* CS: PF1 */
#define AD7715_CSPORT          GPIOF
#define AD7715_CSPINn            1

 

Now define the bit masks for the pins:

#define AD7715_MOSIPIN        ((uint16_t)(1U<<AD7715_MOSIPINn))
#define AD7715_MISOPIN        ((uint16_t)(1U<<AD7715_MISOPINn))
#define AD7715_CSPIN            ((uint16_t)(1U<<AD7715_CSPINn))
#define AD7715_CLKPIN            ((uint16_t)(1U<<AD7715_CLKPINn))

 

When pinout is defined, it’s time to init the pins. The internal register banks and hardware blocks must be connected to clock source first in order to operate properly, including programming the registers. Our pins are connected to GPIOA ind GPIOF blocks. They are both on AHB bus. The AHB peripheral clock enable register is within RCC, the Reset and clock control block. The register definitions and bit masks for accessing bits within the registers are all defined in the device include file. Our microcontroller is STM32F070C6, the 20 pin “small beast” has all the peripheral register’s definitions, bits definitions and memory mapping in file stm32f070x6.h.

GPIO Clock enable

AHB peripheral clock enable register (RCC_AHBENR)

AHB peripheral clock enable register (RCC_AHBENR)

  RCC->AHBENR |= RCC_AHBENR_GPIOAEN;  /* Enable GPIOA clock         */
  RCC->AHBENR |= RCC_AHBENR_GPIOFEN;  /* Enable GPIOF clock         */

GPIO Output configuration

Basic structure of the IO pin is shown in the following figure:

Basic structure of an I/O port bit

Basic structure of an I/O port bit

For each GPIO pin we have to define 4 different configurations: pin mode, output type, speed and pullup/down with the registers  MODER, OTYPER, OSPEEDR and PUPDR, respectively. The pins MOSI, CLK and CS will be Medium speed, Push-Pull outputs without pullup/down. Each of 16 GPIO pinse per port has two bits in each of the  32-bit MODER, OSPEEDR and PUPDR registers. The access to the bits is therefore double pin number, e.g. for PA10, the bit fields in the GPIOA->MODER register are 10*2: bits 20 and 21. The register OTYPER configure the I/O output type.

Each two bits per port in MODER register can configure corresponding port to following four possible configurations:

00: Input mode (reset state)
01: General purpose output mode
10: Alternate function mode
11: Analog mode

The bits  written in OTYPER configure the I/O output type:

0: Output push-pull (reset state)
1: Output open-drain

Three possible speed configurations cen be selected by writing the bits to OSPEEDR:

x0: Low speed
01: Medium speed
11: High speed

And finally, the bits are written to PUPDR to configure the I/O pull-up or pull-down:

00: No pull-up, pull-down
01: Pull-up
10: Pull-down
11: Reserved

In our case, we need medium speed, no pullop/down, pushpull output. With the help by the port definitions above we can initialize port pins as follows:

    /* CLK push-pull, no pullup */
    AD7715_CLKPORT->MODER   &= ~(3ul << 2*AD7715_CLKPINn);
  AD7715_CLKPORT->MODER   |=  (1ul << 2*AD7715_CLKPINn);
  AD7715_CLKPORT->OTYPER  &= ~(1ul <<   AD7715_CLKPINn);
  AD7715_CLKPORT->OSPEEDR &= ~(3ul << 2*AD7715_CLKPINn);
  AD7715_CLKPORT->OSPEEDR |=  (1ul << 2*AD7715_CLKPINn);
  AD7715_CLKPORT->PUPDR   &= ~(3ul << 2*AD7715_CLKPINn);
 
    /* MOSI push-pull, no pullup */
    AD7715_MOSIPORT->MODER   &= ~(3ul << 2*AD7715_MOSIPINn);
  AD7715_MOSIPORT->MODER   |=  (1ul << 2*AD7715_MOSIPINn);
  AD7715_MOSIPORT->OTYPER  &= ~(1ul <<   AD7715_MOSIPINn);
  AD7715_MOSIPORT->OSPEEDR &= ~(3ul << 2*AD7715_MOSIPINn);
  AD7715_MOSIPORT->OSPEEDR |=  (1ul << 2*AD7715_MOSIPINn);
  AD7715_MOSIPORT->PUPDR   &= ~(3ul << 2*AD7715_MOSIPINn);
 
    /* CS push-pull, no pullup */
    AD7715_CSPORT->MODER   &= ~(3ul << 2*AD7715_CSPINn);
  AD7715_CSPORT->MODER   |=  (1ul << 2*AD7715_CSPINn);
  AD7715_CSPORT->OTYPER  &= ~(1ul <<   AD7715_CSPINn);
  AD7715_CSPORT->OSPEEDR &= ~(3ul << 2*AD7715_CSPINn);
  AD7715_CSPORT->OSPEEDR |=  (1ul << 2*AD7715_CSPINn);
  AD7715_CSPORT->PUPDR   &= ~(3ul << 2*AD7715_CSPINn);

GPIO Input configuration

Similar to above outputs, now we can configure pin MISO as input with the pullup:

    /* MISO Input, pullup */
  AD7715_MISOPORT->MODER   &= ~(3ul << 2*AD7715_MISOPINn);
  AD7715_MISOPORT->OSPEEDR &= ~(3ul << 2*AD7715_MISOPINn);
  AD7715_MISOPORT->OSPEEDR |=  (1ul << 2*AD7715_MISOPINn);
  AD7715_MISOPORT->PUPDR   &= ~(3ul << 2*AD7715_MISOPINn);
  AD7715_MISOPORT->PUPDR   |=  (1ul << 2*AD7715_MISOPINn);

Write and read GPIO Pins

Output pins are set or reset by writing to GPIO port bit set/reset register. Lower 16 bits are used to set, upper 16 bit are for reset the output pins. The operation can be done by accessing the single register and shifting the bits 16 places when we want to set the pins to 0. The procedure for set or reset the MOSI line is then:

void AD7715_SetMOSI(int state)
{
    if (state) 
        AD7715_MOSIPORT->BSRR = AD7715_MOSIPIN;
    else
        AD7715_MOSIPORT->BSRR = AD7715_MOSIPIN << 16;    
}

and similar for CS and CLK. The content of the output state can be read or set by accessing the GPIO port output data register. When pin is configured as input, the state of the pin is reflected in GPIO port input data register. The state of the MISO pin can be read with the following function:

uint8_t AD7715_ReadMISO(void)
{
  uint8_t rv = 0;    
    
    if ((AD7715_MISOPORT->IDR & (AD7715_MISOPIN)) != 0) 
    {
    rv = 1;
  }
 
    return rv;
}

 

Transfer a byte

The function to simultaneously transmit and receive a byte on the AD7715 with such “software defined SPI” is shown below. It returns the received byte.

uint8_t AD7715_transferbyte(uint8_t byte_out)
{
    uint8_t byte_in = 0;
    uint8_t bit;
 
    for (bit = 0x80; bit; bit >>= 1) 
    {
        AD7715_SetCLK(0);
        /* Shift-out a bit to the MOSI line */
        AD7715_SetMOSI((byte_out & bit) ? 1 : 0);
        /* Pull the clock line high */
        AD7715_SetCLK(1);
        /* Shift-in a bit from the MISO line */
        if (AD7715_ReadMISO() > 0)
            byte_in |= bit;
    }
    return byte_in;
}

Now it’s time to define AD7715 registers. One option is to use bit fields. It’s not the best solution, because sometimes the compilers can behave very wierd dealing with the bit fields. For such embedded application it is always advisable to check if the proper values are generated from the bitfield type definitions. To prepare the register definitions we must open the AD7715 datasheet. Registers are described in chapter “ON-CHIP REGISTERS”, page 12 of the AD7715 datasheet.

There are several registers. Communication sequence starts by writing to Communication register:

AD7715 communications register

AD7715 communications register

The communications register is an eight-bit register from which data can either be read or to which data can be written. All communications to the part must start with a write operation to the communications register. The data written to the communications register determines whether the next operation is a read or write operation and to which register this operation takes place. Once the subsequent read or write operation to the selected register is complete, the interface returns to where it expects a write operation to the communications register. This is the default state of the interface, and on power-up or after a reset, the AD7715 is in this default state waiting for a write operation to the communications register. In situations where the interface sequence is lost, if a write operation to the device of sufficient duration (containing at least 32 serial clock cycles) takes place with DIN high, the AD7715 returns to this default state.
The above bit designations for the communications register are written as bitfield typedef as follows:
typedef union
{
    struct
    {
        uint8_t Gain        :2;            /*!< bits 1:0   : Gain Select */
        uint8_t STBY        :1;            /*!< bit  2     : Standby */
        uint8_t RW            :1;            /*!< bit  3     : Read/Write Select */
        uint8_t RS            :2;            /*!< bits 5:4   : Register Selection */ 
        uint8_t Zero         :1;          /*!< bit  6       : must be zero! */
        uint8_t DRDY        :1;          /*!< bit  7       : DRDY bit */
    } b;
    uint8_t B;
} AD7715_CommReg_t;

Similar is for setup register:

AD7715 setup register

AD7715 setup register

The setup register is an eight-bit register from which data can either be read or to which data can be written. This register controls thesetup that the device is to operate in such as the calibration mode, and output rate, unipolar/bipolar operation etc.

/** \brief  Union type for the structure of SETUP REGISTER
*/
typedef union
{
struct
{
uint8_t FSYNC        :1;          /*!< bit  0       : filter synchronization */
uint8_t BUF        :1;          /*!< bit  1       : buffer control */
uint8_t BU            :1;            /*!< bit  2     : bipolar/unipolar  */
uint8_t FS            :2;            /*!< bits 4:3   : output update rate */
uint8_t CLK          :1;            /*!< bit  5     : master clock selection */
uint8_t MD           :2;            /*!< bits 7:6   : Mode select */
} b;
uint8_t B;
} AD7715_SetupReg_t;

 

We can then define some self-explanatory symbols for easier source code readability:

/** \brief AD7715 Operating Modes 
 MD1  MD0        Operating Mode
    0        0            Normal mode
    0        1            Self-calibration
    1        0            Zero-scale system calibration
    1        1            Full-scale system calibration
*/
#define AD7715_MODE_NORMAL            0
#define AD7715_MODE_SELFCAL            1
#define AD7715_MODE_ZEROCAL            2
#define AD7715_MODE_FSCAL                3
 
 
/** \brief AD7715 Operating frequency select */
#define AD7715_CLK_1MHZ                    0
#define AD7715_CLK_2_4576MHZ        1
 
 
/** \brief AD7715 Update rate 
    Note: the rate depends on CLK bit ! */
        /** 1MHz clock */
#define AD7715_FS_20HZ                    0
#define AD7715_FS_25HZ                    1
#define AD7715_FS_100HZ                    2
#define AD7715_FS_200HZ                    3
        /** 2.4576MHz clock */
#define AD7715_FS_50HZ                    0
#define AD7715_FS_60HZ                    1
#define AD7715_FS_250HZ                    2
#define AD7715_FS_500HZ                    3
 
 
/** \brief AD7715 Polarity select */
#define AD7715_BU_UNIPOLAR                1
#define AD7715_BU_BIPOLAR                    0
 
/** \brief AD7715 Buffer bypass */
#define AD7715_BUF_ACTIVE                1
#define AD7715_BUF_BYPASSED            0

 

To setup the AD7715 we must transfer two bytes: communication register followed by setup register. The transfer is “wrapped” within one CS cycle. It is good if we setup the AD7715 for self calibration after powerup:

/** AD7715 Register variables */
    AD7715_CommReg_t CommReg; 
    AD7715_SetupReg_t SetupReg; 
    
    /* Init Pins */
    AD7715_InitPins();
    
    /* Reset AD7715 */
    AD7715_Reset();
    
    /** Write to setup register */
    CommReg.b.DRDY = 0;
    CommReg.b.Zero = 0;
    CommReg.b.RS = AD7715_REG_SETUP;
    CommReg.b.RW = AD7715_RW_WRITE;    
    CommReg.b.STBY = AD7715_STBY_POWERUP;
    CommReg.b.Gain = AD7715_GAIN_1; 
 
  /** Setup register */
  SetupReg.b.BU = AD7715_BU_BIPOLAR;
    SetupReg.b.BUF = AD7715_BUF_BYPASSED;
    SetupReg.b.CLK = AD7715_CLK_2_4576MHZ;
    SetupReg.b.FS = AD7715_FS_50HZ;
    SetupReg.b.FSYNC = 0;
    SetupReg.b.MD = AD7715_MODE_SELFCAL;
  
    AD7715_SetCS(0);
    AD7715_transferbyte(CommReg.B);
    AD7715_transferbyte(SetupReg.B);
    AD7715_SetCS(1);

The normal operation is initiated by changing the operation mode from self calibration to normal operation mode:

  /* Set normal operation */
     SetupReg.b.MD = AD7715_MODE_NORMAL;
    
    AD7715_SetCS(0);
    AD7715_transferbyte(CommReg.B);
    AD7715_transferbyte(SetupReg.B);
    AD7715_SetCS(1);

 

The AD7715 readout cycle starts by reading from communication register and checking the DRDY flag. If the DRDY is low it means that new conversion is finished and readout data is ready in the 16 bit result register. Depending on type of the conversion (bipolar on unipolar, the result is either signed or unsigned 16 bit integer type):

  while (1) {
        
        /** Read from comm register, poll DRDY */
        CommReg.b.DRDY = 0;
        CommReg.b.Zero = 0;
        CommReg.b.RS = AD7715_REG_COMM;
        CommReg.b.RW = AD7715_RW_READ;    
        CommReg.b.STBY = AD7715_STBY_POWERUP;
        CommReg.b.Gain = AD7715_GAIN_1; 
        
        AD7715_SetCS(0);
        AD7715_transferbyte(CommReg.B);
        CommReg.B = AD7715_transferbyte(0xff);
        AD7715_SetCS(1);
        
        if ((CommReg.b.DRDY) == 0) 
        {
            // read data
            CommReg.b.DRDY = 0;
            CommReg.b.Zero = 0;
            CommReg.b.RS = AD7715_REG_DATA;
            CommReg.b.RW = AD7715_RW_READ;    
            CommReg.b.STBY = AD7715_STBY_POWERUP;
            CommReg.b.Gain = AD7715_GAIN_1;
 
            AD7715_SetCS(0);
            AD7715_transferbyte(CommReg.B);
            adcbuf[1] = AD7715_transferbyte(0xff);
            adcbuf[0] = AD7715_transferbyte(0xff);
            AD7715_SetCS(1);
            memcpy(&adcreadout, adcbuf, 2);
            
        }

This finalizes this simple AD7715 driver.

Now let’s move to the rotary encoder and LCD. Start first with the rotary encoder.

User interface: Encoder

I took Alps EC11 series rotary encoder. This device has two switches making and breaking contact between A, B and common pin which are 90 degrees out of phase with each other. The number of pulses or steps generated per complete turn varies, usually from 9 to 18 (12, 15 also possible). One possible logic for encoder readout is to trigger on one edge of signal A and capture level of signal B at the same time. The polarity of B signal will tell the CW or CCW direction of the rotation:

 

Rotary encoder signals

Rotary encoder signals (photo from: 
electro-labs.com)

The encoder switches will produce less debouncing pulses when bypassed with 100nF capacitors:

Rotary encoder connection diagram

Rotary encoder connection diagram

All pins of the encoder have internal pullup resistors. The basic idea is to trigger external interrupt EXTI with rising edge of the signal A. Within the EXTI interrupt service routine we will read the level of signal B and send message. Another external interrupt will be triggered by the pushbutton of the encoder. The rotary encoder shaft can be pushed down to close the key contact (marked with net name “Tipka-1” in the above schematic.

The encoder pinout is:

Signal A — PA10
Signal B — PA9
Key — PF0

STM32F0 External interrupt tutorial

I mentioned external interrupts. Let’s take a deeper look into the external interrupts. In order to trigger the EXTI ISR by transition on the external pin we will have to configure following blocks of the Cortex M0:

  • Initialize GPIO pins
  • Map EXTI line to GPIO pins
  • Set EXTI triggers
  • Unmask EXTI interrupt lines
  • Assign EXTI interrupt priority
  • Enable EXTI interrupts in nested interrupt vector controller (NVIC)

 

Let’s first define the pins:

//A - Pin for encoder "A" pin ---> triggers EXTI
#define ENCODER_APORT      GPIOA
#define ENCODER_APINn      10
 
//B - Pin for encoder "B" pin 
#define ENCODER_BPORT      GPIOA
#define ENCODER_BPINn      9
 
//A - Pin for encoder "K" pin ---> KEy, triggers EXTI
#define ENCODER_KPORT      GPIOF
#define ENCODER_KPINn      0
 
#define ENCODER_APIN        ((uint16_t)(1U<<ENCODER_APINn))
#define ENCODER_BPIN        ((uint16_t)(1U<<ENCODER_BPINn))
#define ENCODER_KPIN        ((uint16_t)(1U<<ENCODER_KPINn))

Now we have to enable GPIO clock to enable GPIO configuration. Then we have to configure the pins as inputs with pullups. I will not repeat everything again. Please refer to the ADC section above for more details about GPIO configuration. All three GPIO pins are configured with:

    /** Enable GPIO clocks for GPIOA and GPIOF */
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    RCC->AHBENR |= RCC_AHBENR_GPIOFEN;
 
  /* Init GPIO Pins */
    /* Encoder Pin A */
  ENCODER_APORT->MODER   &= ~(3ul << 2*ENCODER_APINn);
  ENCODER_APORT->OSPEEDR &= ~(3ul << 2*ENCODER_APINn);
  ENCODER_APORT->OSPEEDR |=  (1ul << 2*ENCODER_APINn);
  ENCODER_APORT->PUPDR   &= ~(3ul << 2*ENCODER_APINn);
  ENCODER_APORT->PUPDR   |=  (1ul << 2*ENCODER_APINn);
 
    /* Encoder Pin B */
  ENCODER_BPORT->MODER   &= ~(3ul << 2*ENCODER_BPINn);
  ENCODER_BPORT->OSPEEDR &= ~(3ul << 2*ENCODER_BPINn);
  ENCODER_BPORT->OSPEEDR |=  (1ul << 2*ENCODER_BPINn);
  ENCODER_BPORT->PUPDR   &= ~(3ul << 2*ENCODER_BPINn);
  ENCODER_BPORT->PUPDR   |=  (1ul << 2*ENCODER_BPINn);
    
    /* Encoder Key pin */
  ENCODER_KPORT->MODER   &= ~(3ul << 2*ENCODER_KPINn);
  ENCODER_KPORT->OSPEEDR &= ~(3ul << 2*ENCODER_KPINn);
  ENCODER_KPORT->OSPEEDR |=  (1ul << 2*ENCODER_KPINn);
  ENCODER_KPORT->PUPDR   &= ~(3ul << 2*ENCODER_KPINn);
  ENCODER_KPORT->PUPDR   |=  (1ul << 2*ENCODER_KPINn);

 

In the Cortex M0 the external interrupt lines connection to the GPIOs are managed with the System configuration controller (SYSCFG). There are four SYSCFG external interrupt configuration registers: SYSCFG->EXTICR[0] to SYSCFG->EXTICR[3]. In each SYSCFG->EXTICR[n] register, there are four bits for each external interrupt line EXTI0 to EXTI15 selecting which pin is mapped to what EXTI line:

External interrupt/event GPIO mapping

External interrupt/event GPIO mapping

The registers SYSCFG->EXTICR[0] to SYSCFG->EXTICR[3] have following structure (example for ):

SYSCFG external interrupt configuration register 1

SYSCFG external interrupt configuration register1 SYSCFG->EXTICR[0]

If we want to map PF0 to EXTI0 we have to program bits EXTI0[3:0] to 0101. The four bit mapping configuration is:

x000: PA[x] pin
x001: PB[x] pin
x010: PC[x] pin
x011: PD[x] pin
x100: Reserved
x101: PF[x] pin
other configurations: reserved

Similar is register SYSCFG->EXTICR[1] for GPIO pins 4 to 7, SYSCFG->EXTICR[2] for GPIO pinf 8 to 11 and SYSCFG->EXTICR[3] for GPIO pins 12 to 15.

Don’t forget to enable clock for SYSCFG block before any configuration changes. Now we can map PA10 to GPIO External interrupt line 10 and PF0 to line 0:

    /* Enable SYSCFG Clock */
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
 
  /* Map EXTI10 line to PA10 */
    SYSCFG->EXTICR[3] &= (uint16_t)~SYSCFG_EXTICR3_EXTI10_PA;
    SYSCFG->EXTICR[3] |= (uint16_t)SYSCFG_EXTICR3_EXTI10_PA;
    
  /* Map EXTI0 line to PF0 */
    SYSCFG->EXTICR[0] &= (uint16_t)~SYSCFG_EXTICR1_EXTI0_PF;
    SYSCFG->EXTICR[0] |= (uint16_t)SYSCFG_EXTICR1_EXTI0_PF;

Next step is to configure the Extended interrupts and events controller (EXTI).

In general, the extended interrupts and events controller (EXTI) manages the external and internal asynchronous events/interrupts and generates the event request to the CPU/Interrupt Controller and a wake-up request to the Power Manager. The EXTI allows the management of up to 28 external/internal event line (21 external event lines and 7 internal event lines).

For the external interrupt lines, to generate the interrupt, the interrupt line should be configured and enabled. This is done by programming the two trigger registers with the desired edge detection and by enabling the interrupt request by writing a ‘1’ to the corresponding bit in the interrupt mask register. When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set. This request is reset by writing a ‘1’ in the pending register. This is usually managed within the ISR.

Each line has one bit to enable rising edge trigger and one for falling edge trigger. When both are enabled, the EXTI line will trigger on both edges: rising and falling. In our case for encoder we need only one edge: falling (remember, we have pullups, the switch in the encoder connects pin to GND). EXTI lines 0 and 10 are configured to trigger on rising edge by writing 1 to corresponding bits in the EXTI->FTSR register:

/* EXTI0 line interrupts: set falling-edge trigger */
    EXTI->FTSR |= EXTI_FTSR_TR0;
  /* EXTI0 line interrupts: clear rising-edge trigger */
    EXTI->RTSR &= ~EXTI_RTSR_TR0;
 
    /* EXTI10 line interrupts: set falling-edge trigger */
    EXTI->FTSR |= EXTI_FTSR_TR10;
  /* EXTI10 line interrupts: clear rising-edge trigger */
    EXTI->RTSR &= ~EXTI_RTSR_TR10;

The register EXTI->RTSR has reset value 0, but it doesnt hurt if we explicitly set the proper bits to 0 during setup. The register bit definitions are provided in the devide include file, so we can have more friendly and readable code.

The final step before enabling external interrupts is to unmask the EXTI lines. The Interrupt mask register EXTI->IMR has Bits 31:0 Interrupt mask on external/internal line x. Remeber: to unmask, the mask bit must be set to 1! Again we can use predefined symbols for unmasking the specific EXTI lines:

/* Unmask interrupts from EXTI0 line */
    EXTI->IMR |= EXTI_IMR_MR0;
 
  /* Unmask interrupts from EXTI10 line */
    EXTI->IMR |= EXTI_IMR_MR10;

Now we can set the intreeupt priority and enable interrupts. The functions for handling the NVIC is provided by CMSIS Cortex-M0 Core Peripheral Access Layer. This is standardised for all Cortex-M0 microcontrollers. We have to spend few words about Nested vectored interrupt controller (NVIC). Not every EXTI line has own vector in the NVIC. For the Cortex M0, the EXTI lines are gathered into three groups. This means that several lines can trigger same interrupt. There are 32 interrupt channels in Cortex M0 and three channels are dedicated to EXTI triggers. Channel (or NVIC position) number 5 is for EXTI lines 0 and 1. The channel number 6 is for EXTI 2 and 3 and channel number 7 is for EXTI 4 to 15. The detailed description of NVIC programming is given in the Core peripherals chapter of PM0215 programming manual.

It is important to set Interrupt priority befor enabling the interrupt:

    /* Assign EXTI interrupt priority = 2 in NVIC */
    NVIC_SetPriority(EXTI4_15_IRQn, 2);
    NVIC_SetPriority(EXTI0_1_IRQn, 2);
 
 
    /* Enable EXTI interrupts in NVIC */
    NVIC_EnableIRQ(EXTI4_15_IRQn);            /** PA10 */
    NVIC_EnableIRQ(EXTI0_1_IRQn);              /** PF0 */

 

The GPIO external interrupt are finally concluded with proper Interrupt Service Routines. Here is the bare minimum, which don’t do anything except acknowledge the interrupts by clearing the interrupt pending flag. This is done by writing 1 to the flag in the EXTI pending register EXTI->PR:

// Encoder
void EXTI4_15_IRQHandler(void)
{
    if ((EXTI->PR & EXTI_PR_PR10) != 0)
    {
        // Clear EXTI interrupt pending flag (EXTI->PR).
        EXTI->PR |= EXTI_PR_PR10;
    }
}
 
 
// Key
void EXTI0_1_IRQHandler(void)
{
    if ((EXTI->PR & EXTI_PR_PR0) != 0)
    {
        // Clear EXTI interrupt pending flag (EXTI->PR).
        EXTI->PR |= EXTI_PR_PR0 ;
    }
}

 

User interface: 2×16 alphanumeric LCD

The final part is LCD to display readouts and provide access to the instrument settings.

2x16 Alphanumeric LCD

2×16 Alphanumeric LCD

The LCD is connected to microcontroller with 6 signals. All signals are outputs at the microcontroller side. There is nothing dramatic about the 2×16 alphanumeric LCD. I took excellent library from Tilen Majerle and rewritten it to off-burden from  HAL or any other libraries.

The pins are defined with same logic as already described above:

/// LCD pinout definitions
//RS - Register select pin
#define HD44780_RSPORT     GPIOB
#define HD44780_RSPINn     1
//E - Enable pin
#define HD44780_EPORT      GPIOA
#define HD44780_EPINn      4
//D4 - Data 4 pin
#define HD44780_D4PORT     GPIOA
#define HD44780_D4PINn     3
//D5 - Data 5 pin
#define HD44780_D5PORT     GPIOA
#define HD44780_D5PINn     2
//D6 - Data 6 pin
#define HD44780_D6PORT     GPIOA
#define HD44780_D6PINn     1
//D7 - Data 7 pin
#define HD44780_D7PORT     GPIOA
#define HD44780_D7PINn     0
 
#define HD44780_RSPIN        ((uint16_t)(1U<<HD44780_RSPINn))
#define HD44780_EPIN        ((uint16_t)(1U<<HD44780_EPINn))
#define HD44780_D4PIN        ((uint16_t)(1U<<HD44780_D4PINn))
#define HD44780_D5PIN        ((uint16_t)(1U<<HD44780_D5PINn))
#define HD44780_D6PIN        ((uint16_t)(1U<<HD44780_D6PINn))
#define HD44780_D7PIN        ((uint16_t)(1U<<HD44780_D7PINn))

 

The pin handling is done with few macro definitions:

#define HD44780_RS_LOW              HD44780_RSPORT->BSRR = HD44780_RSPIN<<16
#define HD44780_RS_HIGH             HD44780_RSPORT->BSRR = HD44780_RSPIN
#define HD44780_E_LOW               HD44780_EPORT->BSRR = HD44780_EPIN<<16
#define HD44780_E_HIGH              HD44780_EPORT->BSRR = HD44780_EPIN
#define HD44780_E_BLINK             HD44780_E_HIGH; HD44780_Delay(20); HD44780_E_LOW; HD44780_Delay(20)

 

The LCD pins init procedure is:

static void HD44780_InitPins(void)
{
  RCC->AHBENR |= RCC_AHBENR_GPIOAEN;  /* Enable GPIOA clock         */
  RCC->AHBENR |= RCC_AHBENR_GPIOBEN;  /* Enable GPIOB clock         */
 
    /* All pins push-pull, no pullup */
 
    HD44780_RSPORT->MODER   &= ~(3ul << 2*HD44780_RSPINn);
  HD44780_RSPORT->MODER   |=  (1ul << 2*HD44780_RSPINn);
  HD44780_RSPORT->OTYPER  &= ~(1ul <<   HD44780_RSPINn);
  HD44780_RSPORT->OSPEEDR &= ~(3ul << 2*HD44780_RSPINn);
  HD44780_RSPORT->OSPEEDR |=  (1ul << 2*HD44780_RSPINn);
  HD44780_RSPORT->PUPDR   &= ~(3ul << 2*HD44780_RSPINn);
 
    HD44780_EPORT->MODER   &= ~(3ul << 2*HD44780_EPINn);
  HD44780_EPORT->MODER   |=  (1ul << 2*HD44780_EPINn);
  HD44780_EPORT->OTYPER  &= ~(1ul <<   HD44780_EPINn);
  HD44780_EPORT->OSPEEDR &= ~(3ul << 2*HD44780_EPINn);
  HD44780_EPORT->OSPEEDR |=  (1ul << 2*HD44780_EPINn);
  HD44780_EPORT->PUPDR   &= ~(3ul << 2*HD44780_EPINn);
 
    HD44780_D4PORT->MODER   &= ~(3ul << 2*HD44780_D4PINn);
  HD44780_D4PORT->MODER   |=  (1ul << 2*HD44780_D4PINn);
  HD44780_D4PORT->OTYPER  &= ~(1ul <<   HD44780_D4PINn);
  HD44780_D4PORT->OSPEEDR &= ~(3ul << 2*HD44780_D4PINn);
  HD44780_D4PORT->OSPEEDR |=  (1ul << 2*HD44780_D4PINn);
  HD44780_D4PORT->PUPDR   &= ~(3ul << 2*HD44780_D4PINn);
 
    HD44780_D5PORT->MODER   &= ~(3ul << 2*HD44780_D5PINn);
  HD44780_D5PORT->MODER   |=  (1ul << 2*HD44780_D5PINn);
  HD44780_D5PORT->OTYPER  &= ~(1ul <<   HD44780_D5PINn);
  HD44780_D5PORT->OSPEEDR &= ~(3ul << 2*HD44780_D5PINn);
  HD44780_D5PORT->OSPEEDR |=  (1ul << 2*HD44780_D5PINn);
  HD44780_D5PORT->PUPDR   &= ~(3ul << 2*HD44780_D5PINn);
 
    HD44780_D6PORT->MODER   &= ~(3ul << 2*HD44780_D6PINn);
  HD44780_D6PORT->MODER   |=  (1ul << 2*HD44780_D6PINn);
  HD44780_D6PORT->OTYPER  &= ~(1ul <<   HD44780_D6PINn);
  HD44780_D6PORT->OSPEEDR &= ~(3ul << 2*HD44780_D6PINn);
  HD44780_D6PORT->OSPEEDR |=  (1ul << 2*HD44780_D6PINn);
  HD44780_D6PORT->PUPDR   &= ~(3ul << 2*HD44780_D6PINn);
 
    HD44780_D7PORT->MODER   &= ~(3ul << 2*HD44780_D7PINn);
  HD44780_D7PORT->MODER   |=  (1ul << 2*HD44780_D7PINn);
  HD44780_D7PORT->OTYPER  &= ~(1ul <<   HD44780_D7PINn);
  HD44780_D7PORT->OSPEEDR &= ~(3ul << 2*HD44780_D7PINn);
  HD44780_D7PORT->OSPEEDR |=  (1ul << 2*HD44780_D7PINn);
  HD44780_D7PORT->PUPDR   &= ~(3ul << 2*HD44780_D7PINn);
 
}

Function to send data via 4 bit interface:

static void HD44780_Cmd4bit(uint8_t cmd) {
    /* Set output port */
    
    HD44780_D7PORT->BSRR = HD44780_D7PIN<<((cmd & 0x08) ? 0 : 16); 
    HD44780_D6PORT->BSRR = HD44780_D6PIN<<((cmd & 0x04) ? 0 : 16); 
    HD44780_D5PORT->BSRR = HD44780_D5PIN<<((cmd & 0x02) ? 0 : 16); 
    HD44780_D4PORT->BSRR = HD44780_D4PIN<<((cmd & 0x01) ? 0 : 16); 
    HD44780_E_BLINK;
}
 
 
 
The simple microsecond delay function is:
static void HD44780_Delay(uint32_t us)
{
    us *= 48; 
    
    while (us--) 
        __nop();
    
}

 

And finally, the LCD init and clear:

void HD44780_Init(uint8_t cols, uint8_t rows) {
    
    /* Init pinout */
    HD44780_InitPins();
    
    /* At least 40ms */
    osDelay(45);
    
    /* Set LCD width and height */
    HD44780_Opts.Rows = rows;
    HD44780_Opts.Cols = cols;
    
    /* Set cursor pointer to beginning for LCD */
    HD44780_Opts.currentX = 0;
    HD44780_Opts.currentY = 0;
    
    HD44780_Opts.DisplayFunction = HD44780_4BITMODE | HD44780_5x8DOTS | HD44780_1LINE;
    if (rows > 1) {
        HD44780_Opts.DisplayFunction |= HD44780_2LINE;
    }
    
    /* Try to set 4bit mode */
    HD44780_Cmd4bit(0x03);
    osDelay(5);
    
    /* Second try */
    HD44780_Cmd4bit(0x03);
        osDelay(5);
    
    /* Third goo! */
    HD44780_Cmd4bit(0x03);
        osDelay(5);
    
    /* Set 4-bit interface */
    HD44780_Cmd4bit(0x02);
    osDelay(1);
    
    /* Set # lines, font size, etc. */
    HD44780_Cmd(HD44780_FUNCTIONSET | HD44780_Opts.DisplayFunction);
 
    /* Turn the display on with no cursor or blinking default */
    HD44780_Opts.DisplayControl = HD44780_DISPLAYON;
    HD44780_DisplayOn();
 
    /* Clear lcd */
    HD44780_Clear();
 
    /* Default font directions */
    HD44780_Opts.DisplayMode = HD44780_ENTRYLEFT | HD44780_ENTRYSHIFTDECREMENT;
    HD44780_Cmd(HD44780_ENTRYMODESET | HD44780_Opts.DisplayMode);
 
    /* Delay */
        osDelay(5);
}
 
void HD44780_Clear(void) {
    HD44780_Cmd(HD44780_CLEARDISPLAY);
    //HD44780_Delay(3000);
    osDelay(3);
}

 

 

Complete source code is available in GitHub repository s54mtb/pHmeter.

 

This is work in progress. It is meant as learning example for Cortex M0 and interfacing precision analog signals to the microcontroller.

APA102C LED strip at 28 MHz clock

$
0
0

I recently received 1m of APA102C led strip with 144 leds per meter. I want to use the strip for rotary POV display so I cut the strip in half and tested the performance with a STM32L476RGT nucleo board. The results of the tests are surprising and very promising for my application.

The APA102C LEDs are very similar to WS2812 (or “NeoPixels”). They are RGB, individually addressable and take an 8-bit value for setting each color. However, the key difference between said two is the additional CLOCK line on the APA102C strip. The clock line makes setting values much easier, more flexible and also much faster. Instead of trying to write a code to match the timing for WS2812 LEDs, clock on APA102C LEDs can be set to almost anything.

Data protocol

A diagram explaining how to transmit data is shown bellow.

APA102C data protocol

APA102C data protocol

The LEDs take 8 bit values for PWM dimming for each color and a 5 bit value for analog dimming of all the colors at once (setting the LED drive current). There were some miseries what’s the purpose of end bytes and they were solved on this blog. It turned out that each LED in a chain inverts the clock and thus delays it for a half of a period. This enables the shift-register in the LEDs to work correctly. The downside of this simple solution is that the end of the transmission requires some additional clock cycles (number of LEDs in a string divided by 2).

APA102C strip

LEDs have data/clock input and output. The image below shows how the LEDs are wired in a chain.

APA102C strip

APA102C strip

Electrical connection of the strip

Two APA102C LED strips

Two APA102C LED strips

The strips take 5 V and about 8 A/m maximum current (for the 144 LEDS/m). I placed some decoupling capacitors next to strip connectors because there are none on the strip. 10 uF and 100 nF combo should decouple any high frequency noise nicely and keep the 5 V rail clean for other components. Data and clock are directly connected to the MCU.

Test

At first I generated source code with STM32CubeMX and then I wrote a short code that generates an array of bytes to be sent. The total transmission with 72 LEDs has 296 bytes.

I tested with various clocks. The problems started occurring at 30 MHz when the last LEDs started flickering. 28 MHz seemed like a limit for 72 leds in a chain. At 40 MHz clock only the first ten lighted up, later ten flickered and the rest was turned off. I couldn’t test much further since the STM32 I was using ran on 84 MHz maximum internal clock. With 28 MHz clock on LEDs and 296 * 8 = 2368 bits to send the maximum theoretically possible refresh rate is astonishing 11.8 kHz. Almost twice as my design requirement for the POV project.

 

Last LED (72th) flickering at 30 MHz

LEDs flickering at 40 MHz

I also tested the maximum refresh rate I could get from my STM32 Nucleo board. Each loop, the MCU generated a new data array with a slightly different color setting for all LEDs. The result was a color fading effect that ran at once again astonishing 6.5 kHz. Also note that DMA could be implemented to make computing data arrays and transmitting data to the LEDs run parallel. With such implementation the refresh rate could come very near the absolute theoretically possible value (11.8 kHz).


LED decoration

$
0
0

This LED decoration is for beginners in microcontroller applications. It has 8 LEDs, audio generator and possibility to interconnect with other modules. The PCB is simplified to certain level in order to be manufacturable on a single sided substrate with minimum effort. There are only two wire bridges and one 0 ohm resistor in addition to other components. The LED decoration can be used in school projects or for new-years fun. Of course it’s not limited to X-mas tree. It can be used during Bodhi Day, Hanukkah, Id al-Adha, Winter Solstice celebration, Saturnalia, Yule, Kwanza, Omisoka or any other occasion which might come at the end of the year, when day is short and some LED blinking device might rise your mood.

The concept of this LED decoration:

LED decoration concept

LED decoration concept

Each module has 20 pin STM32F0 microcontroller with USB and UART connection, low drop voltage regulator, 1W audio amplifier and 8 LEDs. The complete schematic diagram:

LED decoration schematic

LED decoration schematic

Here is PDF version.

 

The PCB has 8 LEDs around:

LED decoration PCB with 8 LEDs

LED decoration PCB with 8 LEDs

The 3D view shows how are LEDs placed. The speaker is mounted at the reverse side. It is important to know there is no need to assemble all components on all modules when they are connected in a chain. There is no need to play songs with all modules (although it could produce interesting sound effects).

LED decoration - 3D view on PCB

LED decoration – 3D view on PCB

As I mentioned, the PCB can be produced with “toner transfer” method or any other prototyping technology. Here are the necessary files: SINGLE PDF, 12-times PDF, GERBER.

The first prototypes were etched:

Preparation for etching -- transferring toner to the pcb

Preparation for etching — transferring toner to the pcb

The toner has been transferred

The toner has been transferred

 

Etched PCBs

Etched PCBs

Now it’s time to prepare some serious quantity of LEDs:

1000 pcs of LEDs

1000 pcs of LEDs

Assembly of the module is simple. Just follow the assembly drawing and schematic. Here is complete documentation in a single PDF file.

First three modules assembled and working:

First three prototypes assembled and running

First three prototypes assembled and running

 

One of the ideas was to place a straw on top of each LED

One of the ideas was to place a straw on top of each LED

 

The software for this project is under development. Just follow this site for updates.

 

Quadruple BDC driver for robotics

$
0
0

Once finished (deadline is end of January) this project will become open-source software and hardware.

My quadruple BDC driver for supports up to 4 brushed motors with encoders. Each motor is controlled by its individual closed loop and individual PID gain settings. Additionally, motor speed can also be individually addressed. Analog part of the driver is based on DRV8701P predriver and is controlled by STM32F4 microcontroller.

Specifications:

  • 28 V max input voltage (recommended 24V)
  • 12.5 A max motor current – not yet tested (recommended 10A)
  • 50 kHz max encoder pulse frequency (recommended 30kHz)
  • Serial communication
  • ROS compatible with available library
  • XT-60 power connector
  • XT-30 motor connectors
  • Adjustable current protection (0.1A – 13A)
  • Adjustable current alert (LED or on serial)
  • Temperature protection
  • Hardware emergency stop
  • Extra 24V power switch for external 24V load
BDC driver supports up to 4 motors and simultaneously controls them with PID closed loop

BDC driver supports up to 4 motors and simultaneously controls them with PID closed loop

Schematics

The circuit is divided in two parts. The analog power part and the digital part with peripherals. Let’s talk about the power side firstly.

Power side

Everything is based on DRV8701P integrated circuit that takes care of dead times, gate currents and ensures higher voltage for opening high side n-mosfets. The DRV has four control inputs. One is sleep (it is used for emergency stop) and two are PWM signals for forward or reverse drive. The DRV also features braking (though not regenerative) with both PWM signals on at the same time. The fourth control input is reference voltage for limiting the current.

The DRV has shunt amplifier built-in with fixed gain of 20. This is ideal for current sensing with a 10 mOhm shunt resistor. Once the voltage on the output of the shunt amplifier exceeds the voltage reference set on the control pin the driver enters so called current chopping mode in which it effectively limits the motor current to set level.

The driver measures voltage drop across transistors and immediately enters fault mode when the drop is too large. It also features internal temperature protection.

Power side of the BDC driver

(1) Power side of the BDC driver

The schematic (1) is only for 1 channel. The actual circuit board has 4 identical ones.

 

Digital side and peripherals

Digital side is based on STM32F4 microcontroller. I used one in QFN housing to make it more immune to possible mechanical vibrations. The microcontroller is generating 8 PWM signals at 21 kHz with 1000 sections (10bit). The encoders are read by interrupt active pins. All four motors work in PID closed loop. Some additional features are motor sensing motor current, input voltage, used energy and temperature sensing.

Three LED indicators are present on board to provide basic information about the driver state.

  • Sleep LED indicates whether the driver (as a unit) is in sleep mode and motors drives are disabled.
  • Fault LED blinks with a fault code and indicates a possible problem.
  • Brake LED indicates whether one of the channels is breaking.

The board has got a connector for remote emergency stop switch. A relay or npn/n-channel transistor can also be connected on this input to work as some kind of emergeny stop with external watchdog module.

To ensue proper and correct measurements of input voltage, temperature and motor current a 2.5 V reference is present on-board for the ADC.

Small 0.5 A buck switching regulator takes care of making 3.3V from the input supply. All digital side and peripherals are powered by this IC.

Additionally another N-channel mosfet switch is implemented. It can switch currents up to 20A.

The communication with the driver is based on UART. User will have multiple write registers (to set motor speed, PID gains, etc.) and multiple read register that will contain information about temperature, used energy, encoder ticks, etc. This is not yet implemented in software.

Digital side of the driver with peripherals and power management

Digital side of the driver with peripherals and power management

Circuit board

First prototype

BDC driver circuit board

BDC driver circuit board

The first prototype was thoroughly tested and initial mistakes were corrected. Some problems were as follows:

  • Wrong interrupt pins (joined interrupt lines in the mcu)
  • Wrong 3.3V switcher layout
  • Huge button bounce (solved with a capacitor)

Some other minor mistakes that were solved with second revision

  • Added 2.5 V reference for ADC
  • Added forward and reverse PWM signals of a single driver to a single MCU timer
  • Added a resistor in parallel with NTC to make it more linear
  • Improved board layout

The power side of the circuit worked perfectly in the first revision and no correction were made there.

Prototype PCBs were manufactured by PCBWay. Their rapid prototype service is fast (5 days from gerbers to PCBs in my hands), reliable and the quality of the PCBs is completely comparable with a local manufacturer. Not to mention 20 boards with 6mil clearance were just 30$ including shipping. I’ll definitely order again.

Click on the logo to visit PCBWay.com

Click on the logo to visit PCBWay.com

 

Revision 0.2a

To be continued…

 

Software

Most of the software has already been written.

What can the driver do:

  • Control the motors in open loop with duty cycle in both directions
  • Read the motor speed from encoders
  • Control the motors in closed loop in both directions
  • Read temperature
  • Read motor current
  • Read input voltage
  • Calculate used energy
  • Transmit data over serial port

To do:

  • Serial receive
  • Register mapping
  • Startup routine
  • Active breaking

Pressure, temperature and humidity sensor based on MS5637 HDC1080 Rev.2

$
0
0

Gal ordered some PCBs from PCB-Way a while ago. It was multiproject panel with several PCBs. One of the modules was double sided revision of the Pressure, temperature and humidity sensor based on MS5637 HDC1080 which was posted while ago.

Pressure, temperature and humidity sensor based on MS5637 HDC1080

Top row, middle PCB

First of all, I have to admit. The PCB-Way did excellent job once again. With the panel We ordered stencil and both, the panel and the stencil were top quality and very competitive price.

Schematic diagram is slightly different compared to original Rev. 1.0. There are three bits to set the address of the module:

Schematic Rev. 2

The PCB documentation is here.

RHTP Sensor – Assembly drawing

And here is photostrip of the assembly process (with the PBC-Way stencil):

PCB assembly

And finally, the PCB 3D model with the 3D printable housing is here in my GrabCAD project folder.

PCB and the housing

 

3D printed housings for this and some other sensors

Simple Rh, T and p sensor with UART communication

$
0
0

The Pressure, temperature and humidity sensor based on MS5637 HDC1080 originally (Rev. 2) operates via RS485 interface and multidrop HDLC-like protocol.  I decided to simplify this for use with Raspberry Pi, arduion or any other mass platforms. First, I took away the RS485 transceiver and second, I simplified the communication.

First adaptation was easy:

Remove RS485 driver and connect Rx/Tx to A/B

With above change, the line A operates as UART Tx and line B as UART Rx.

The software part is on my GitHub repository.

Commands

The communication is simple. Attach some serial port to A and B lines and type  commands with the following syntax:

@<addr>:<device> <readcmd>

where….

  • <addr> is 0 to 7 and represents binary combination of the three address bits which are set with the resistors on lines PA6, PA7 and PB1.
  • <device> is HDC1080 or MS5637 and the
  • <readcmd> depends on the device, see below.

 

The MS5637 read commands:

  • p for pressure
  • t for temperature
  • c for cal[0]
  • d for cal[0]
  • e for cal[0]
  • f for cal[0]
  • g for cal[0]
  • h for cal[0]
  • i for cal[0]
  • j for cal[0]
  • x for raw ADC1
  • y for raw ADC2

Commands for HDC1080 are:

 

  • h for humidity
  • t for temperature
  • v for battery status bit

 

The letters for readout command can be repeated in any order. Maximum length for readout string is 32.

Example to readout temperature and humidity from hdc1080 on module with address 7:

 

@7:hdc1080 th

Air pressure and aitr temperature from MS5637 from module with same address can be read by:

@7:MS5637 pt

The return value is in the following format:

#%d:%s

where integer %d is the device address and string %s is readout. Pressure, temperature and humidity readouts are floating point with format:

%c=%3.2f

where char %c is either p, t or h for pressure, temperature or humidity, respectively.

Calibration coefficients have format %c=%04X where char %c is equal to char in <readcmd>.

The raw ADC readouts have format %c=%08X, where char %c is either x or y.

 

Example:

@7:hdc1080 h

will return

#7:h=25.76

 

Use with the Raspberry pi

The simplest way is to hook the Rx/Tx to the UART pins on R.Pi and try with the following python script (save it to the file rhtp.py):

import serial
import time

port = serial.Serial("/dev/ttyAMA0", baudrate=9600, timeout=1.0)
port.write("@7:hdc1080 h\n")
time.sleep(0.5)   #Pause time in seconds
x=port.readline()
print x

The response from above is:

pi:~/serialtest $ python rhtp.py
#7:h=25.61

pi:~/serialtest $

 

More advanced script with some run time parameters could be (save it to demo.py):

 

import serial, sys


if len(sys.argv) == 4:  # 1 + 3
  (cmd, adr, dev, par) = sys.argv
  port = serial.Serial("/dev/ttyAMA0", baudrate=9600, timeout=3.0)
  port.write("@"+adr+":"+dev+" "+par+"\n")
  rcv = port.read(10)
  (x,t) = rcv.split("=")
  print (t)
else:
  print ("Error")

 

 

and run with

 python demo.py 7 hdc1080 h

 

output from this is only the number:

26.01

 

 

Raspberry Pi breakout board with ESD, PoE and Cortex M0

$
0
0

This is Raspberry Pi GPIO connector breakout board with some additional features:

  • “Real” or “passive” PoE with 12V output
  • 5V DC/DC module
  • Additional 3,3V regulator
  • ESD protection on all GPIO pins
  • two additional pins for each pin on R.Pi 40 pin GPIO connector
  • Separate UART and I2C headers
  • 3,3V Supply for periphery is selectable: from R.Pi or from external regulator
  • STM32F070 or similar in TSSOP-20 housing for controlling additional sensor or any other hardware
  • Jumper for Cortex-M0 / R.Pi UART connection

Top view of the PCB with R.Pi contour

This is revision 1.0 – work in progress and the PCB boards are not yet produced (the order is out). For now I will briefly explain the schematics.

Power supply

Power supply section

The power supply is based on Silvertel Ag9700 PoE module. It is IEEE 802.3af compliant module with 12, 5 or 3V isolated output (1500V barrier) delivering 12W of power when proper PoE injector is used. Input voltage has wide operating range. I bought samples via distribution and the price was about 8 EUR per module.

Ag9700 Block diagram (drawing from Silvertel web page)

If this price is too high, there is “Ghetto” option for “passive” PoE. It simply connects unused twisted pairs to the input of the 5V DC/DC. Unfortunately with such solution there is no galvanic isolation. Two 0 ohm jumpers are provided for this: R3 and R4. They can be replaced with fuses as well.

Ethernet

Few words about ethernet connection:

The cable from the outside is connected directly to the heaqder without the RJ45 cable connector. I did this way for simpler installation in the IP65 plastic or metal housings. I put one RJ45 female plug to connect the R.Pi ethernet through short (5-10cm) patch cable.

Cortex M0

Next section is microcontroller. It has SWD debug header and all pins connected to 100 mils headers. The UART PA2/PA3 can be connected to R.Pi UART GPIO14/GPIO15 via two 0 ohm jumpers R1 and R2:

Microcontroller section

Headers

And finally, there are headers for I2C0, I2C1, 1 wire, UART0 and selecting header for 3,3V supply:

IO port headers and power supply selection

 

The PCB is 56x68mm and slightly covers the display connector of the R.Pi. Other connectors are free.

There are additional pads with +3,3V, +5V and +12V as well. Please check the complete schematic for further details.

Stay tuned for further details about progress (when PCBs arrive).

Viewing all 24 articles
Browse latest View live