Close
0%
0%

NTP Clock Based on STM32H735 Discovery Kit

This is an SNTP clock based on the STM32H735 Discovery Kit.

Similar projects worth following
NTP display clock based on the STM32H735I Discovery Kit

I'm building an SNTP display device based on the STM32H735 Discovery Kit, an $85 development board that includes Ethernet and a touch screen display.  The software is written in C, and I'm using ST's IDE, the STM32Cube IDE, which is based on Eclipse.  The purpose is to have a clock displaying the correct time in UTC, on my workbench.  It's the second such clock I've designed;  I built a clock on the old #Twatch platform built by Dangerous Prototypes.

  • 1 × ST Microelectronics STM32H735-DK Discovery Kit A development board with LCD, Ethernet and Arduino support based on the STM32H735 single-core ARM Cortex-M7 processor.

  • My NTP clock: What have I learned?

    dmoisan04/08/2021 at 16:09 0 comments

    I started this project because my first NTP clock, the one I adapted from the Twatch, was a great success.  I had the clock, and I used it in service for 11 years.  (I'm a ham radio operator.  We use UTC clocks all the time.)  I also wanted to experiment with a new display.  I didn't have experience with ST Microelectronics, so when I saw the Digi-Key ad for the board, it seemed to be ideal.  It had everything I needed.  I've abandoned many projects where I couldn't find the parts--unobtanium--or where the parts were expensive--expensivum, or where I had interesting display parts (like a VFD DVD display) but no good or cheap way to integrate them into a project.  The Discovery Kit has all the components most people would need, plus an Arduino interface besides.

    So how did it go?

    ST Microelectronics has a reputation for having a steep learning curve.  I think there's much truth to that.  During the course of development, I just had things "happen" to my code.  At one point--just a few days ago as I write this--my clock firmware was totally non-functional, after I'd tried to make some simple changes in the layout of the display.  I tried some code I had archived previously, and this is what I got:

    The firmware was failing on the rand() function, which is used in the LwIP stack during initialization, and DHCP operations.  I had no idea why it failed.  There are build-time parameters that set heap memory size for FreeRTOS, and I tweaked those.

    My firmware would crash randomly.  That's fine, that's the nature of debugging.  But when I asked the community at ST (who are all good people, no snark), I was asked, "Did you write a HardFault debugger?  Did you do this?  Did you do that?".  I've been in these conversations on both sides, and there's not much worse than your being told, "Well, you need to do X, but X is really hard, so I can't help you..."

    (For fun, look up "Cortex-M FreeRTOS SVC 0 fault".  Don't thank me.)

    Again, I can't be mad, it is the nature of development.  But ST Micro's stuff just keeps presenting pothole after pothole.  New things you need to check.  And I'm often no smarter than I was before, there's no new thing that makes me go, "Aha, that's why it did that!"  All I can do is think, "I have an idea...no wait, that broke something.  Screw it."

    The problem mysteriously resolved itself.  After trying to build the project on two different machines, I went back to the original workstation I use for development, and recopied the code to a different workspace in STM32CubeIDE.  It worked.  I resolved never to try to change the code again.

    (I wrote a compiler project in University, so I've always been interested in reverse engineering.  I was just about disassembling the GNU rand() library code to determine why it wanted to fail an assert.  Then the code started working again...  I still don't know why.)

    STM32CubeIDE has its problems as well.  It is Eclipse-based.  I'm indifferent when I chose development tools:  I've used many, many text editors and IDEs in my career.  I can use more or less anything I can set up.

    This IDE has problems, though.  Eclipse has two different ways of organizing the many files in a project.  Most projects beyond "Hello World!" (blinking LED's) will have hundreds or thousands of files for a small project like my NTP Clock.

    Eclipse has a "file system", which is your OS's native file system, Windows, Mac or Linux.  Then it has "workspaces", a file system within a file system.  STM32CubeIDE doesn't show you every file.  You can have what looks like a very empty "Hello, World"-style project.  The iceberg with all the libraries in it--and your code--is invisible!

    Good luck finding all your files,...

    Read more »

  • The Fount, or Font, of All Good Things

    dmoisan04/07/2021 at 03:38 0 comments

    When your project involves a bitmapped display, you will need fonts.  When you've gone beyond the old, reliable 2-line LCD (courtesy of the Hitachi HD44780 that powered my Twatch), you need fonts.  When you want something that looks good, you need fonts!

    The ST Micro firmware library has a graphics library with a font importer.  However, the HTTP app I adapted for my clock doesn't use the library.  I'm not sure what ST used to create the fonts in the original app, but I found a good app to create fonts.  The Dot Factory  is a small utility that is used to generate C source from TrueType and bitmapped fonts, and it can also be used to convert bitmaps to C source.

    I mentioned previously that I decided to use Droid Sans Mono as a display font, due to its aesthetics, and also due to its permissive open-source licensing.  To use the app, you specify the font, the size and the style you want, and then tweak the code generation settings to match how your library expects to use your font data.

    You can accept most of the defaults.  You only need to pay attention to the byte ordering--it is Row Major, MSB First--and the space character, which is always done. Droid Sans Mono is a monospaced font;  this is the best choice for a clock and a good choice for nearly all applications.  The app can also create a separate descriptor array, which provides a fast index into font arrays that might have a few hundred glyphs in them.  Descriptors aren't used in my program.  After configuring the options, you're ready to generate the code.

    And there is our font code!  I chose to include all the glyphs of Droid Sans Mono--there's plenty of ROM in the Discovery Kit--a megabyte's worth!  My clock has two different font sizes.  Just copy and paste the code into your text editor.

    There are just a few more things to do.  We need to copy the font code file into the project directory, but first we need to put in an include to "fonts.h" and create a struct object that describes the font to the graphics API.

    Excerpt of fonts.h:

    ...
    typedef struct _tFont
    {
      const uint8_t *table;
      uint16_t Width;
      uint16_t Height;
    } sFONT;
    ...
    
    extern sFONT DroidSansMono28;
    extern sFONT DroidSansMono20;
    ...

    And DroidSansMono28:

    /* Character bitmaps for Droid Sans Mono 28pt */
    const uint8_t droidSansMono_28ptBitmaps[] = 
    {
    ...
    	/* @111 '!' (22 pixels wide) */
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x30, 0x00, //           ##          
    	0x00, 0x30, 0x00, //           ##          
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x7C, 0x00, //          #####        
    	0x00, 0x78, 0x00, //          ####         
    	0x00, 0x38, 0x00, //           ###         
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    	0x00, 0x00, 0x00, //                       
    ...
    };
    
    /* Font information for Droid Sans Mono 28pt */
    
    sFONT DroidSansMono28 = {
    		droidSansMono_28ptBitmaps,
      22, /* Width */
      37, /* Height */
    };
    

    The only annoying part was counting the width and height in pixels for the sFONT struct.  That's not done automatically.  If you get the height wrong (easy to do), the display will resemble an odometer, or a slot machine as the digits go out of alignment every time they're drawn!

    I'm about caught up.  I've done everything I needed to do, and I now have a usable clock, even if it isn't perfectly rendered.  I've tested my code...

    Read more »

  • Displaying the Time: LCD Operations

    dmoisan04/04/2021 at 16:57 0 comments

    The STM32H735G Discovery Kit has an LCD touchscreen display of 480x272 pixels, smaller than VGA, but bigger than the displays on most other project boards.  I bought the board for the display.  Here's the code.

    For the user, the LCD code starts in main.c, in function BSP_Config:

    static void BSP_Config(void)
    {
    
      /* Initialize the LCD */
      BSP_LCD_Init(0, LCD_ORIENTATION_LANDSCAPE);
      UTIL_LCD_SetFuncDriver(&LCD_Driver);
      UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK);
      UTIL_LCD_SetBackColor(UTIL_LCD_COLOR_BLACK);
      UTIL_LCD_SetTextColor(UTIL_LCD_COLOR_WHITE);
    //  UTIL_LCD_SetFont(&DroidSansMono20);
    //  UTIL_LCD_DisplayStringAt(10,60, (uint8_t *)"Initializing network...", CENTER_MODE);
    }

    This code first sets the orientation of the LCD to landscape, setting certain internal counters and position references, and then it references a list of driver function primitives--there are eleven basic functions that must be implemented.  Then it clears the LCD screen, sets the background color (black) and the text color (white).

    I've commented out the next lines, which would have displayed a message on the screen.  More on those as we get further into the code.

    These are the eleven primitive LCD functions:

    /** @defgroup STM32H735G_DISCO_LCD_Private_TypesDefinitions LCD Private TypesDefinitions
      * @{
      */
    const LCD_UTILS_Drv_t LCD_Driver =
    {
      BSP_LCD_DrawBitmap,
      BSP_LCD_FillRGBRect,
      BSP_LCD_DrawHLine,
      BSP_LCD_DrawVLine,
      BSP_LCD_FillRect,
      BSP_LCD_ReadPixel,
      BSP_LCD_WritePixel,
      BSP_LCD_GetXSize,
      BSP_LCD_GetYSize,
      BSP_LCD_SetActiveLayer,
      BSP_LCD_GetPixelFormat
    };
    

    All other drawing functions are derived from these.  The graphics library supports two drawing layers, but I only use one of them.

    We convert the time data in hours, minutes and seconds, to glyphs to display on the screen.  I use macro definitions for the X and Y coordinates so I can easily tweak the position of the elements.

        // Display code
    
        //
        // Text positioning definitions
        //
    #define LINE1XBASELINE    140
    #define LINE1YBASELINE    70
    
        //
        // Clear time display
        //
    
        UTIL_LCD_SetFont(&DroidSansMono28);
    //    UTIL_LCD_SetFont(&DroidSansMono72);
        UTIL_LCD_ClearStringLine(LINE1YBASELINE);
    
        // Line 1 Hours, Minutes, Seconds
        static char textstring[25];
    
        TimeToStr(textstring, hour);
        UTIL_LCD_DisplayStringAt(LINE1XBASELINE,LINE1YBASELINE, (uint8_t *)textstring, LEFT_MODE);
        UTIL_LCD_DisplayStringAt(LINE1XBASELINE+42,LINE1YBASELINE, (uint8_t *)":", LEFT_MODE);
    
        TimeToStr(textstring, minute);
        UTIL_LCD_DisplayStringAt(LINE1XBASELINE+64,LINE1YBASELINE,(uint8_t *)textstring, LEFT_MODE);
    //    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+120,LINE1YBASELINE,(uint8_t *)":", LEFT_MODE); -- Don't display colon
    
        TimeToStr(textstring, second);
        UTIL_LCD_SetFont(&DroidSansMono20);
        UTIL_LCD_DisplayStringAt(LINE1XBASELINE+116,LINE1YBASELINE,(uint8_t *)textstring, LEFT_MODE);
    // original offset = 220
        UTIL_LCD_DisplayStringAt(LINE1XBASELINE+162,LINE1YBASELINE,(uint8_t *)TimeZoneName, LEFT_MODE);
    
    

    I use the open source font Droid Sans Mono for display.  I've found that the biggest size I can use is 28;  the lower-level LCD HAL routines (what the LCD primitive functions call) display garbage on larger bitmap sizes.  There's obviously a limit on the size of a bitmap.   TimeToStr is a homemade function that converts time values (hours, minutes and seconds) to bitmaps.  I don't like using the standard sprintf function in this routine, because the latency of string handling is unpredictable.  TimeToStr will take the same time and the same resources to execute, which it must do, 60 times a minute.  (There are sprintf functions designed specifically for embedded systems, which I hope to try out later on.)

    This is how it looks so far:

    Now to display the month, day and year in North American English format.

        //
        // Text position definitions for Line 2
        //
    
    #define LINE2XBASELINE    80
    #define LINE2YBASELINE    190
    
        //
    // Display the day of...
    Read more »

  • SNTP and its integration

    dmoisan04/04/2021 at 00:23 0 comments

    Now we come to SNTP:  Simple Network Time Protocol is a well-known TCP application that retrieves a timestamp from an NTP server.  It is the simpler cousin to NTPd, the distributed time server application that runs everywhere on the Internet.  (I run an NTP server for my home network on a Beaglebone, with my own custom cape, with GPS, an RTC and an OLED display.)

    LwIP comes with its own SNTP client, which is included in the  ST Microelectronics library.  As far as I can tell, it's unchanged from the official code on LwIP's Git page.

    How does it work with our code?

    First, let's go back to main.c, where the SNTP client is set up.

      /* Initialize the LwIP stack */
      Netif_Config();
    
    
      //
      // Look up server address
      //
      dns_init();
      ip_addr_t defaultdns;
    
      IP4_ADDR(&defaultdns,8,8,8,8); // Google DNS
    
      dns_setserver(0,&defaultdns);
    
    //SNTPDNSResult = dns_gethostbyname(sntp_server_name,&sntp_server_address, NULL, NULL);
    //  const TickType_t xDelay = 20000 / portTICK_PERIOD_MS; // 20 second delay
    //  vTaskDelay(xDelay);
    
      IP4_ADDR(&sntp_server_address,108,61,73,244);
      sntp_setoperatingmode(SNTP_OPMODE_POLL);
      sntp_setserver(0,&sntp_server_address);
      sntp_init();
    
    

     DNS has to be enabled in opts.h.  I ran into trouble getting SNTP to enable DNS lookups, so this DNS code ultimately didn't work.  I hard-coded a public SNTP server, 0.us.pool.ntp.org, with one of its IP addresses, 108.61.73.244.  (The pool.ntp.org servers use round-robin DNS, so that 0.us.pool.ntp.org will present several other IP's besides 108.61.73.244.)

    Moving on, we set the operating mode to SNTP_OPMODE_POLL, set the client to use the IP address we just defined as a server.  Then sntp_init kicks it off.  But, we need to change the code in sntp.c itself to include a callback to our code that SNTP can refer to when it retrieves a timestamp.  This is where I had a bad time.

    In LwIP, and as in every library, one needs to tweak parameters, and enable or disable features as needed for their project.  In C, we use the preprocessor #define directive:

    # main.h
    #define TURNONFEATURE     1
    #define MYFUNCTIONCALLBACK  MyCallback(int functionthings);
    ...
    ...
    In main.c:
    #ifdef TURNONFEATURE
    ...some enabling code...
    #endif
    ...
    ...
    MYFUNCTIONCALLBACK

    At least, this was the mental model I had in my head.  I put my defines in sntpopts.h, and declared sntpopts.h in every place I could find where my code would work with SNTP.

    Didn't work.  I searched online, and there were just a few people using the code.  One person said the code was not well documented.  It's the story of our lives, at least as I can see as a IT professional. 

    I did something ugly, and altered my copy of sntp.c directly.  It felt wrong.  It felt like writing in a library book.

    //
    // External function prototypes
    //
    
    extern void SNTPSetTicks(uint64_t sec);
    extern void SNTPGetTicks(uint64_t sec, int us);
    
    ...
    ...
    
    
    //  SNTP_SET_SYSTEM_TIME_NTP(sec, frac); // line 332 in sntp.c, original
      SNTPSetTicks((u32_t)sec); //line 332 in sntp.c, my change
     
    

    Around line 332, is our callback function, SNTPSetTicks.  (SNTPGetTicks is defined, but not used.  It's there to allow SNTP to compute a roundtrip time to the server.  For my application, I didn't need this extra complexity.)

    The actual callback is a very short function in ntpdisplaytasks.c:

    extern void SNTPSetTicks(uint64_t sec, uint64_t us) {
        // This is a callback function that is defined by a macro in sntp_opts.h
        // Update the SecondsCounter
    
        SecondsCounter = sec;
        }
    

    All this function does is step our seconds counter with the seconds count in the timestamp.  We don't use the microseconds (us) variable.  We don't discipline our clock timer because this is just a display clock and I don't know how to do this like ntpd does.   My Twatch clock worked the same way.

    That's it for the...

    Read more »

  • Calculating Time

    dmoisan04/01/2021 at 22:13 0 comments

    We have a seconds counter in our NTP clock, but now we need to convert this into human-readable information on the LCD display.   The function DisplayClock does this, and it's (mostly) neatly divided between the computation part and the display part.  I'm only going to cover the computation aspect in this project entry.

    I got the algorithm for time computation from APPLICATION NOTE 517 DS1371/DS1372/DS1374 32-BIT BINARY COUNTER TIME CONVERSION, from Maxim Integrated.  It's the same code I used in the Twatch. This is the algorithm, starting with a timestamp in seconds:

    1. Divide the timestamp by 60 to get minutes.
    2. Take the modulus of 60 of the timestamp to get seconds.
    3. Divide the timestamp by 3600 to get hours.
    4. Divide hours by 24 to get days.
    5. Adjust the days to include 1969 and 1968 (reference days to a leap year, 1968).
    6. Determine the number of leap year periods since 1968.
    7. If the current date is a leap year, and it is past February 28th, add another leap year period.
    8. Compute the number of years since 1968.
    9. Find the current month using the number of days that have passed for the current year.
    10. Determine the day of the current month.
    11. Determine the current day of the week.
    12. Add the epoch year (1900 for Unix, 1830 for NTP) to get the current year.

    Here is the time computation code from DisplayClock:

    extern void DisplayClock(int SecondsCount, char* TimeZoneName, int timeZoneOffset, int DSTFlag) {
    
    	static unsigned int DaysToMonth[13] = {
    	   		0,31,59,90,120,151,181,212,243,273,304,334,365
    			};
    
    	static unsigned int hour, day, minute, second, month, year;
    
    	static unsigned int whole_minutes, whole_hours, whole_days;
    	static unsigned long whole_days_since_1968;
    	static unsigned long leap_year_periods, days_since_current_lyear, whole_years;
    
    	static unsigned int days_since_first_of_year;
    	static unsigned int days_to_month;
    	static unsigned int day_of_week;
    
    	whole_minutes = SecondsCounter / 60;
    	// second = SecondsCounter - (60 * whole_minutes);		    // leftover seconds
    	second = (SecondsCounter % 60);
    	whole_hours  = whole_minutes / 60;
    	minute = whole_minutes - (60 * whole_hours);            // leftover minutes
    	whole_days   = whole_hours / 24;
    	hour         = whole_hours - (24 * whole_days);         // leftover hours
    
    	whole_days_since_1968 = whole_days + 365 + 366;
    	leap_year_periods = whole_days_since_1968 / ((4 * 365) + 1);
    
    	days_since_current_lyear = whole_days_since_1968 % ((4 * 365) + 1);
    
    	// if days are after a current leap year then add a leap year period
    
    	if ((days_since_current_lyear >= (31 + 29))) {
    		leap_year_periods++;
    		}
    	whole_years = (whole_days_since_1968 - leap_year_periods) / 365;
    	// Fixed to display day of year properly so January 1st displays as "Day 1"
    	days_since_first_of_year = whole_days_since_1968 - (whole_years * 365) - leap_year_periods + 1;
    
    	if ((days_since_current_lyear <= 365) && (days_since_current_lyear >= 60)) {
    		days_since_first_of_year++;
    		}
    	year = whole_years + 68;
    
    	// 	setup for a search for what month it is based on how many days have passed
    	//  within the current year
    
    	month = 13;
    	days_to_month = 366;
    //		Fixed to eliminate "Feb 0" problem where the last day of a month was displayed as the "0th" day of the next month
    //		Changed comparison to be less than or equal
    	while (days_since_first_of_year <= days_to_month) {
    		month--;
    		days_to_month = DaysToMonth[month-1];
    		if ((month > 2) && ((year % 4) == 0)) {
    		   days_to_month++;
    			}
    		}
    
    	day = days_since_first_of_year - days_to_month;
    
    
    	day_of_week = (whole_days  + 1) % 7; // was (whole_days+4) % 7
    
    	year = year + 1830;
    
    

    Note the last line.  The epoch year is 1830 to match the SNTP timestamp provided by that function.  Unix timestamps use 1900. 

    I need to talk about SNTP.  LwIP has an SNTP module that must be integrated into your code.  I had a lot of trouble with this, and so did others.  I'll describe that next time.

  • Counting Ticks, Making Time

    dmoisan04/01/2021 at 21:31 0 comments

    If you're just joining in, this is a project log about making an NTP clock from ST Microelectronics' STM32H735G Discovery Kit. In our last entry, I created a timer that calls a function once a second.  This function would update a seconds counter and display the time.  Here it is, in ntpdisplaytasks.c

    extern void DisplayUpdate() {
    	// Turn on LED
    	BSP_LED_On(LED1);
    	DisplayClock(SecondsCounter, TimeZoneName, TimeZoneOffset, DSTFlag);
    
    	//
    	// Increment seconds counter.
    	//
    
    	SecondsCounter++;
    
    	//
    	// Turn off LED1
    	//
    	const TickType_t xDelay = 50 / portTICK_PERIOD_MS; // 50 ms delay before turning the LED of
    	vTaskDelay(xDelay);
    
    	BSP_LED_Off(LED1);
    

     Remember that the Discovery Kit has two LED's available, one green, one red.  I used the red one during startup to indicate problems and lockups.  The green LED is our time tick.  It blinks once a second,  It's turned on before we call DisplayClock, and turned off afterwards.  Before the LED is extinguished, though, I insert a 50 millisecond delay using vTaskDelay, which is a FreeRTOS function.  The actual blink period is a useful indication of how long it takes DisplayClock to run.  DisplayClock does all of the display work, and I'm worried about efficiency.

    We also increment SecondsCounter.  This is the main counter by which we keep time.  The DisplayClock function also has provisions for specifying the timezone name and offset, and includes a daylight-savings/summer-time flag.  Presently, I don't implement these features;  the clock displays UTC only for the moment, just like my original Twatch clock.

    My next entry will cover the calculation of time elements, hours, minutes, and seconds, from SecondsCounter.  This will be a long project entry, so it gets its own post.  Catch up with me then! 

  • The Beating Heart: FreeRTOS

    dmoisan03/30/2021 at 15:58 0 comments

    My clock is built around FreeRTOS.  FreeRTOS is an open-source real-time operating system for embedded devices.  It provides timers and synchronization functions, amongst many other capabilities, and it is a fully preemptive multitasking operating system, like its big brother, Linux.Clocks are timers (and timers are clocks!)  That is perhaps stupidly obvious, but that's what our clock, and many other projects, needs.  The TCP/IP stack (Microchip's) in my last clock (the Twatch derivative) didn't use an OS;  it used cooperative multitasking, where the stack would do some work, and then pause for the main function (my clock code) to do something, and my code would pause to let the stack work.  LwIP will work this way, but it works better with an RTOS, and that's what we're using. 

    Here's some code from my main.c.  I'll explain it as I go along.

    int main(void)
    {
    
      /* Configure the MPU attributes as Device memory for ETH DMA descriptors */
      MPU_Config();
    
      /* Enable the CPU Cache */
      CPU_CACHE_Enable();
    
      /* STM32H7xx HAL library initialization:
           - Configure the TIM6 to generate an interrupt each 1 msec
           - Set NVIC Group Priority to 4
           - Low Level Initialization
         */
      HAL_Init();
    
     //
     // Code to redirect trace to ST-LINK SWM Port
     //
    
    /* Configure the system clock to 520 MHz */
      SystemClock_Config();
    
      /* Configure the LCD ...*/
      BSP_Config();
    
      // Initialize LED's.
      BSP_LED_Init(LED1);
      BSP_LED_Init(LED2);
    
      // Turn on red ("warning/error") LED
      // It will be turned off when the timers are started
      //
    
      BSP_LED_On(LED2);
    
      /* Init thread */
      osKernelInitialize();
      
      attr.name = "Start";
      attr.stack_size = 4 * configMINIMAL_STACK_SIZE;
      attr.priority = osPriorityNormal;
      StartHandle = osThreadNew(StartThread, NULL, &attr);
    
      /* Start scheduler */
      osKernelStart();
    
      /* We should never get here as control is now taken by the scheduler */
      for( ;; );
    }
    

    The first part of this code through BSP_Config just sets up the hardware.  We then initialize the LCD (covered later on) and then the LED's.  LED1 is green, and LED2 is red.  I use the red LED to indicate initialization.  When the clock is working normally, LED2 will be turned on during initialization, and then turned off just before the clock starts running with its main code.  Normally, LED2 will briefly blink.  If, however, LED2 remains lit, it means the device locked up or crashed and I need to do something.  LED1, the green one, is used to blink once a second and I will talk about that later.

    The following code is boilerplate.  FreeRTOS is based on tasks--there is no code running outside of FreeRTOS in the device.  The code initializes the FreeRTOS kernel (osKernelInitialize) and sets up an initial task, the function StartThread.  Then the kernel is started with osKernelStart, and the code continues in an infinite while(1) loop.  This is a very common idiom in FreeRTOS coding, and embedded coding in general.  (After all, you're responsible for everything and there is no exit() function to take you back!)

    Now, let's see StartThread.  This is the first task that FreeRTOS executes, and it kicks off the rest of the code, including my clock code.

    void StartThread(void* argument)
    {
      /* Reset seconds counter */
      ResetCounters();
    
      /* Create tcp_ip stack thread */
    
      tcpip_init(NULL, NULL);
    
      /* Initialize the LwIP stack */
      Netif_Config();
    
    ...
    [Some code omitted for clarity...]
    ...
    
      //
      // Create TickUpdate timer
      //
    
      // Display update interval is 1 second
    
      xTimerHandle DisplayUpdateHandle = xTimerCreate(
            "DispUpd", /* name */
            pdMS_TO_TICKS(1000), /* period/time 1 second */
            pdTRUE, /* auto reload */
            (void*)0, /* timer ID */
            (TimerCallbackFunction_t )DisplayUpdate); /* callback */
    
      if (DisplayUpdateHandle==NULL) {
        for(;;); /* failure! */
      }
    
      //
      // Start DisplayUpdate Timer
      //
    
      if (xTimerStart(DisplayUpdateHandle, 0)!=pdPASS)...
    Read more »

  • Code Demolition

    dmoisan03/30/2021 at 15:01 0 comments

    OK, now for code demolition!

    I work at a local public-access TV facility.  I've been there since it opened, and I have overseen three major renovations of our technical spaces (and will see a fourth renovation, after the pandemic!)  We had an executive director who was very enthusiastic about the project, like an eager DIY'er.  One day, we're filling up a dumpster and this guy decides it would be fun to jump up and down in the dumpster to compact all the cardboard, wood and who knew what else.  It's great fun to clown around on the jobsite, right?

    He threw his back out.  Cost him his vacation.  Damn.

    I shouldn't have to worry about this. I'm working behind a keyboard!

    I mentioned in a previous installment that I would be adapting one of the sample projects in the firmware/library package, specifically the HTTP server demo.  It makes a good foundation for my clock.  But I had to make some changes.  Most experienced hackers can skim over this part, but it's worth mentioning how and why I adapted the code. 

    First, I removed the references to "httpserver_socket" everywhere in the project.  It's only referenced in main.c, so that's trivial.

    I don't use the macro USE_LCD in my code--I always use the LCD, so I'll never need an ifdef. Since we're removing httpserver_socket.c, we have to remove httpserver_socket.h.  I'll mention lcd_trace in a moment. I removed those three lines.

     I then remove the references in StartThread.  This thread is invoked by FreeRTOS, and essentially kicks off the main function of the program.  I'll discuss this in a future installment. 

    A word about lcd_trace.  It's an interesting and useful function provided by ST.  It implements a small terminal window on the LCD upon which you can write status messages, printf statements, and so forth.  It's very useful.  But we can't use this, and use the LCD for its intended purpose, so we have to remove it to go on.

    We need to remove every line with UTIL_LCD_TRACE_ in it, and the wrapper function LCD_UsrTrace, which is not shown here. These are scattered across the Ethernet and DHCP functions as well.

    Note the BSP_LED_Init functions.  Those are staying in the code.  We'll get to them later.

    Finally, we delete httpserver_socket.c:

    We've spent time deleting code.  Now, we can start to write the code that will make our clock work. Next time, we'll look at FreeRTOS, the literally beating heart of our code.

  • What is the STM32H735G Discovery Kit?

    dmoisan03/29/2021 at 22:49 0 comments

    In my previous log entries, I forgot to describe the board I'm working on.  This is a relatively new board, and not many people are developing for it.

    I'll copypaste this from the ST website:

    The STM32H735G-DK Discovery kit is a complete demonstration and development platform for Arm® Cortex®-M7 core-based STM32H735IGK6U microcontroller, with 1 Mbyte of Flash memory and 564 Kbytes of SRAM. STM32H735G-DK board photoThe STM32H735G-DK Discovery kit is used as a reference design for user application development before porting to the final product, thus simplifying the application development. The full range of hardware features available on the board helps users to enhance their application development by an evaluation of all the peripherals (such as USB OTG FS, Ethernet, microSD™ card, USART, CAN FD, SAI audio DAC stereo with audio jack input and output, MEMS digital microphone, HyperRAM™, Octo-SPI Flash memory, RGB interface LCD with capacitive touch panel, and others). ARDUINO® Uno V3, Pmod™ and STMod+ connectors provide easy connection to extension shields or daughterboards for specific applications. STLINK-V3E is integrated into the board, as the embedded in-circuit debugger and programmer for the STM32 MCU and USB Virtual COM port bridge. The STM32H735G-DK board comes with the STM32CubeH7 MCU Package, which provides an STM32 comprehensive software HAL library as well as various software examples.

    The header of this project blog has a picture of the dev kit, but here are some more pictures.

    The packaging:

    Another view of the front of the board:

    The back of the board:

    The bright green LED is the power LED.  Next to it is a smaller, green LED.  This is actually an RGB led and it is used to show the status of the ST-LINK, ST's debugging interface chipset which is helpfully included with the board.

    By the way, the board will power up on a regular bare USB connection, but get the ST-LINK drivers. 

    The daughtercard that connects to the Arduino:

    ST has a precompiled module for the MXCHIP AT3080 WiFi adapter, but I didn't use it for this project.  I am using the built-in Ethernet interface, which the sample HTTP server uses.  The touchscreen interface also has a binary "blob" driver.  I haven't yet touched that.  In all, in addition to the Ethernet interface, I'll be using the LCD display and the two LED's.  I will probably use a cheap food storage container to hold the board, just as I do on my innumerable Raspberry Pi systems.  I wanted to find a nice industrial enclosure, the half-clear ones used for smart meters.  I used one of these for my first NTP clock, but I couldn't find anything I liked from the usual vendors I buy from.

    If you're interested in buying this board, DigiKey sells them. It cost me (as an American) about $100 including the tariff that's been in effect for the past few years.

    My next installment will be about code demolition, and a first look at FreeRTOS, which provides the beat that my clock relies on.

  • Renaming a project in STM32CubeIDE (and other Eclipse derivatives)

    dmoisan03/29/2021 at 21:27 1 comment

    I had the project imported, but the first thing I wanted to do was to rename it.  I have the habit of looking at library code as if it were a real library book:  I hate to mark it up!  I have to rename it and file the serial numbers off of it before I'm comfortable working on it.  The only problem.  You can't do it.

    If you highlight your project and right click, there is a rename option.  It fails.

    A kind fellow user on the STM32 community forums gave me this workaround, which I'll describe as follows.

    In your system's file explorer, find your workspace and open your project folder.  Find the folder named "STM32CubeIDE" and open that.  There should be a file named ".project".  With a separate text editor, open that file.

    In this XML file, find the <name> tag. It's right at the head of the file, line number 3.  Inside the tag is the original project's name.  Change it to the new name as needed.  This is the only change you need to make.  Save the file and close the editor.

    Now, while you still have your file explorer open, click on the .project file you just edited. CubeIDE will create a new project and copy all the files over from the original project.

    (You might want to have a separate text editor besides CubeIDE, if you aren't already using one.  I use Visual Studio Code.)

    This tip might be applicable to other Eclipse derivatives.  If your toolchain uses Eclipse (it's usually not a secret if it does), you might want to try it.  (This could have come from an Eclipse user group for all I know.)

    I just remembered that I haven't really described the hardware, specifically the STM32H735G Discovery Kit.  When I bought the board, I searched for projects using the board, and I only came up with projects from ST Micro themselves.  The board doesn't seem to have many users out in the field--or rather, not many users in the hobby electronics or hacking communities.  I'll fix this and describe the board next time.

View all 12 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates