tag:blogger.com,1999:blog-62454133463752181882024-03-15T05:42:04.578-07:00Nerd Ralphscience and technology stuffRalph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.comBlogger152125tag:blogger.com,1999:blog-6245413346375218188.post-91130301821565351712024-01-21T14:50:00.000-08:002024-01-21T14:50:13.087-08:00Hyundai Level 2 EV Charging Efficiency about 88%<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaUG2vnFP8T_Wx-2z_j9V1336GpvikEjp_OUvKOU8mmRpT91-Q4W6cV28VUmqJwn8QshB3W2juzqxLeL7hsG0HmFn3AkslyaqP-gpLADcr9HSDH5ICdvM5DdsEvEfhehGpU2AR5DD9FFtRXfSDrv07RN1k5TNqHFZhSGasx2t8bVYDsaw-cWmsmcBdxVE/s1221/Hyundai-OBC.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="726" data-original-width="1221" height="381" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaUG2vnFP8T_Wx-2z_j9V1336GpvikEjp_OUvKOU8mmRpT91-Q4W6cV28VUmqJwn8QshB3W2juzqxLeL7hsG0HmFn3AkslyaqP-gpLADcr9HSDH5ICdvM5DdsEvEfhehGpU2AR5DD9FFtRXfSDrv07RN1k5TNqHFZhSGasx2t8bVYDsaw-cWmsmcBdxVE/w640-h381/Hyundai-OBC.jpg" width="640" /></a></div><p></p><p>Unlike PV inverters, EV on-board chargers usually don't have efficiency specifications published by manufacturers. <a href="https://ieeexplore.ieee.org/document/7046253">Studies done on charger efficiency</a> are limited in the number of vehicles that can be tested. I decided to test the charging efficiency of a Canadian 2023 model Kia Sportage PHEV.</p><p>I performed the test with the Kyungshin EVSE that was provided with the vehicle, set to 12 amps. The EVSE was <a href="https://nerdralph.blogspot.com/2023/11/240v-ev-charging-for-5.html">plugged into a 240V outlet</a> via a 18m, 10 AWG extension cord. The L14-30R outlet is wired to the electrical panel with 6/3 aluminum cable. Measurements were made at the electrical panel using a <a href=" http://en.peacefair.cn/products/601.html">Peacefair PZEM-016</a>, logged with <a href="https://github.com/nerdralph/solis-us/blob/master/pzem016.py">a python program I wrote</a>. I initially took measurements with a clamp-style meter, but the readings were too variable, and could not account for power factor.</p><p>Considering the connectors and <a href="http://nerdralph.blogspot.com/2023/10/calculating-copper-wire-characteristics.html">wire resistance</a>, I estimate the losses between the electrical panel and the EVSE to be about 1%. For a typical home level 2 30-amp charger installation, the losses would be much higher, likely over 2%.</p><p>The Sportage PHEV battery capacity is 13.8 kWh. To charge from 20% to 100%, the PZEM-016 recorded 12.55 kWh of energy. The energy stored in the battery was 80% of 13.8, or 11.04 kWh. The efficiency is therefore 11.04/12.55 = 0.8797, or about 88%.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-27013892536316021842023-11-26T14:20:00.000-08:002023-11-26T14:20:43.902-08:00Level 2 EV Charging Deep Dive<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBKh_0XaWrM3cZOn1y_fW5mv59eushQVBIDADGGRKTyeG1qKsarX31rWYfqjBT9qGzmJ_rOcWolDDjg18ap88WICQJkZQ0JxHrAvvOqfiTE3ZfS_2YI34BoWaxaHRB7XGvPCCGJSyFcMQup4j0hVGSA8pqH3CyDI7EsKhVzVBTCmM2vE7ho6TXZ-IqrwQ/s1221/Hyundai-OBC.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="726" data-original-width="1221" height="381" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBKh_0XaWrM3cZOn1y_fW5mv59eushQVBIDADGGRKTyeG1qKsarX31rWYfqjBT9qGzmJ_rOcWolDDjg18ap88WICQJkZQ0JxHrAvvOqfiTE3ZfS_2YI34BoWaxaHRB7XGvPCCGJSyFcMQup4j0hVGSA8pqH3CyDI7EsKhVzVBTCmM2vE7ho6TXZ-IqrwQ/w640-h381/Hyundai-OBC.jpg" width="640" /></a></div><br /><p></p><p>Electric vehicles have an on-board charger, which converts an AC input voltage to DC to charge the batteries. These chargers are designed to accept 120 volt input for level 1 charging, and 208/240 volt input for level 2 charging. The specs for the Hyundai OBC shown above indicate it can accept a wide range of 70 to 285 Vac, allowing it to work with almost any power grid in the world. Note that it is the OBC, not the EVSE, that rectifies and boosts the voltage to charge the battery. That's why <a href="http://nerdralph.blogspot.com/2023/11/240v-ev-charging-for-5.html">an EVSE with a 120V 5-15R plug can be connected to a 240V source</a>.</p><p>Although the Hyundai OBC is rated for 7.2 kW of output power, getting much more than 6 kW of input power has been difficult. The first reason is that power to commercial buildings is usually 3-phase 120/208V, so charging stations usually provide 208V. At the maximum input of 32 amps, that's 6656 watts. Having used both <a href="https://www.chargepoint.com/drivers">ChargePoint</a> and <a href="https://www.flo.com/en-ca/flo-network/">Flo</a> charging stations, I've noticed the majority of them are limited to 30 amps for level 2 charging. Those stations rated for 30 amps use 10 AWG flexible cord, which is limited to 30 amps according to table 12 of the <a href="https://www.csagroup.org/store/product/CSA%20C22.1:21/">Canadian Electrical Code</a>. 30 amps at 208 volts is 6240 watts. Charging at more than 30 amps requires a more expensive larger cable.</p><p>I've also found the OBC doesn't seem to pull the full amperage advertized by the EVSE. The signalling used for <a href="https://www.electronicdesign.com/markets/automotive/article/21267879/electronic-design-an-introduction-to-the-sae-j1772-and-ccs-ev-charging-interfaces">J1772 charging</a> doesn't communicate a precise amperage available to the OBC. It transmits a sequence of pulses, and the duty cycle timing of the pulses indicates the available amperage. I think the OBC reduces the current by a safety margin to allow for imprecise timing of the control signal pulses. When connected to a Kyungshin IC-CPD set to 12 amps, the on-board charger draws about 11 amps.</p><p>Lastly, some charging stations don't always provide the power that they advertize. Several of the ChargePoint chargers I've encountered are <a href="https://www.leviton.com/en/products/dual-head-bollard-evr-green-4000">Leviton 4000 units</a>. They support 16 amps per head, or charging from a single head at 30 amps. These stations are usually listed on the ChargePoint network as 6.6 kW, but with a 208V supply, you'll never see more than 6.2 kW. When both heads are being used, your vehicle will charge at no more than 3.3 kW.</p><p>I think home charging makes a lot of sense, but I see limited value in public level 2 chargers. I am not aware of any public chargers in Nova Scotia that accept payment by credit card. They require users to first set up an account and install an app in order to active chargers. When you do get a charger working, at a charging rate of 6 kW, you can't get much of a charge while you shop at a store or eat at a restaurant. I've seen a few businesses that offer free charging for customers, but after the novelty factor of free charging wears off, I wonder how much use they will get. Since charging at home costs 18.5 c/kWh including GST, getting free charging while you shop for a half hour only saves you 50c.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-79083520291384716532023-11-08T12:53:00.004-08:002023-11-09T05:17:51.447-08:00240V EV Charging for $5<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV_BLCIkh0BM1m9p8KG0fsL4tGvVgwxqvq3BcVlKTjg8yXoWS7Gejn6phUmgJA8xoX9-gB_DBoo47BYq2e2KBcgWYeQqXBk_pSxoz9wRByzmJWP7m7HztzSoq3UMOn-yShe1bp7H8WSxDUeCUzieaYMB70uKNjargZRafSUWt4pdX0HBdHlC6Alr1FNRw/s1139/5-15to14-30.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1074" data-original-width="1139" height="378" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV_BLCIkh0BM1m9p8KG0fsL4tGvVgwxqvq3BcVlKTjg8yXoWS7Gejn6phUmgJA8xoX9-gB_DBoo47BYq2e2KBcgWYeQqXBk_pSxoz9wRByzmJWP7m7HztzSoq3UMOn-yShe1bp7H8WSxDUeCUzieaYMB70uKNjargZRafSUWt4pdX0HBdHlC6Alr1FNRw/w400-h378/5-15to14-30.jpg" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div>We recently purchased a PHEV which came with a portable home charger/EVSE. It plugs into a NEMA 5-15R outlet, and supports a maximum charging rate of 12 amps. At 120 volts, the maximum charge rate is 1440 watts, and the charge rate reported by the vehicle is usually 1.3 kW. The vehicle supports level 2 charging at up to 7.2 kW, but I didn't want to spend $400 to $500 for a good quality 30 amp level 2 EVSE.<p></p><p>The label on the portable EVSE listed an input of 12 amps and 120 volts, however I suspected 240 volts would be fine. The EVSE just passes through the AC power, generating a PWM signal on a control wire to indicate the amount of current the vehicle's on-board charger can draw. Of course, it's possible some home EVSEs for the North American market are built as cheaply as possible, and may not handle 240V. I am confident our Kyungshin IC-CPD is built to accept 240V. On the vehicle side, I checked the on-board charger label and saw that it has a wide input voltage, with a rating of 70-285Vac. I have a 14-30R 240 volt outlet in my garage, which is the same type of outlet an electric dryer uses, giving me an available source for 240V power.</p><p>To make an adapter for the portable EVSE, I used the cord I cut off a broken dryer, and a 5-15R connector. I used <a href="https://www.eaton.com/ca/en-gb/skuPage.4887-BOX.html">an Eaton 4887</a>, which costs about $5 at local electrical suppliers. <a href="https://www.leviton.com/en/products/515cv">The Leviton 515CV</a> is another option. The specs for the 4887 lists an input wire size of 12 to 18 AWG, however the 10 AWG stranded copper wires on the dryer cord were just able to fit.</p><p>With my portable EVSE adapter hack, the vehicle now charges twice as fast. It's probably more efficient too. The output of the on-board charger is 240-430 Vdc, and boost converter efficiency increases with a smaller difference between the input and output voltages.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-61274072468077415812023-10-13T07:41:00.001-07:002023-10-13T07:41:38.471-07:00Calculating Copper Wire Characteristics<p>For the early of my life I've relied on tables or similar references to look up things like copper ampacities and resistance. Now I just remember a few constants, and can calculate what I need to know.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7ytyC5Tkc9dSucMBWSURRs9bL6bAHFM3pfKHLupzA9CM2_CmwPA4b4Hwc2_n2ShCIKlpSCD9BqmsgRqFWhJrTIO7XYdhBMKJbJo6XM-rf2kvfIv5gDLbbZ2sLKXC5vLGOvNR21KzWXBPyXzAEpCLesZDuiD-7nOcTl1AxhrTl2WQ-0-GJY_KI1h8Owcw/s2080/12AWGwire.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1213" data-original-width="2080" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7ytyC5Tkc9dSucMBWSURRs9bL6bAHFM3pfKHLupzA9CM2_CmwPA4b4Hwc2_n2ShCIKlpSCD9BqmsgRqFWhJrTIO7XYdhBMKJbJo6XM-rf2kvfIv5gDLbbZ2sLKXC5vLGOvNR21KzWXBPyXzAEpCLesZDuiD-7nOcTl1AxhrTl2WQ-0-GJY_KI1h8Owcw/w400-h234/12AWGwire.jpg" width="400" /></a></div><p></p><p>The photo above is a 12 AWG copper wire, commonly used in building wiring in the US and Canada. It has a diameter of .0808 inches or 2.052 mm. When used for building wire, it is typically limited to carrying 20 amps of current by breakers or fuses. For circuits carrying more current, 10 AWG wire with a diameter of 2.587 mm can be used. For a change of 2 AWG in wire size, the change in diameter is always 1.26, and therefore the change in cross-sectional area is 1.26^2.</p><p>The cube of 1.26 is 2.0, so an increase in size of 3 AWG will double the cross-sectional area of the wire, and reduce the linear resistance by half. The resistance of 10 AWG wire is 1 ohm per thousand feet (304.8 m), so the resistance of 16 AWG wire, often used in extension cords, is 4 ohms per thousand feet at room temperature. The amount of heat generated in a wire is calculated with the formula P=I^2xR. If 10 amps is flowing through 1000 feet of 16 AWG wire, the power dissipated will be 10^2 x 4 or 400 watts. If the wire is 10 AWG, and the current is 20 amps, the power dissipated will be the same 400 watts. However voltage drop will be lower, since V = I x R. The resistance of 10 AWG wire is a quarter of 16 AWG, so the voltage drop in the 10 AWG wire with 20 amps will be half of the voltage drop of the 16 AWG wire with 10 amps.</p><p>The resistance of copper increases with temperature, by 0.393% per degree C, so increasing the temperature by 25 C will increase the resistance by almost 10%. Calculating temperature increase due to power dissipation is quite complicated, so it is common in electrical codes to consider an ambient temperature of 30 C, and a temperature rise in the wire of no more than 30 C. Because of that, most wire sold in Canada that is <a href="http://www.csagroup.org">CSA</a> certified will use insulation rated for at least 60 C. The most common category of wire used in residential construction, NMD90, has an insulation temperature rating of 90 C.</p><p>On a final note, <a href="http://nerdralph.blogspot.com/2016/06/when-does-18-26-when-buying-cheap-cables.html">a wire labeled 16 AWG might not really be 16 AWG</a>. I generally trust the electrical distributors like Rexel and Wesco, however before using some battery wire from a discount hardware store I'd inspect it carefully first.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-70631129109458020672023-05-13T14:37:00.002-07:002023-05-13T14:37:50.445-07:00MODBUS communication with Solis 4G-US inverters<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2qXzRb7hWTUeyY9HaJ_jeqfqEEm7Pas0EOx0kmqw_J3Z0Ku83sC8NB1s__6iHeDpaf1_9FFB8zuDIox2tjzomC7UNiE8nRQYCqzuEftjVcoYEfQwOfT0WfY9QP3WcwyheMYRRXy1Cv7CtsnWbfrbK508Yvlc0APE7oDvvks3zUP-5vd9Geur2CtkT/s600/logger.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="600" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2qXzRb7hWTUeyY9HaJ_jeqfqEEm7Pas0EOx0kmqw_J3Z0Ku83sC8NB1s__6iHeDpaf1_9FFB8zuDIox2tjzomC7UNiE8nRQYCqzuEftjVcoYEfQwOfT0WfY9QP3WcwyheMYRRXy1Cv7CtsnWbfrbK508Yvlc0APE7oDvvks3zUP-5vd9Geur2CtkT/w400-h266/logger.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: left;"><a href="https://www.ginlong.com/1p_inverter3/1022.html">Solis single-phase inverters</a> have <a href="https://ginlongsolis.freshdesk.com/support/solutions/articles/36000012141-rs-485-wiring-with-circular-connector">a circular RS485 connector</a> supporting <a href="https://www.modbus.org/">MODBUS</a> communication. <a href="http://www.exceedconn.com/e_products_detail.asp?id=76">The connectors</a> can be difficult to find for sale outside China, so using <a href="https://www.solarmanpv.com/products/data-logger/stick-logger/">a wifi data logger stick</a> is a more straightforward way of communicating with the inverters. IGEN Tech is the OEM for the Solis wifi data loggers, which IGEN also sells under the SOLARMAN brand. While the same circular connector is used by many other inverter manufacturers such as Solax, RENAC, and <a href="https://nerdralph.blogspot.com/2023/04/kstar-single-phase-string-inverters.html">KSTAR</a>, the logger firmware is customized to read and report the MODBUS registers for a particular manufacturer.</div><p>The LSW-3 series of wifi loggers allow external programs to perform MODBUS queries via a TCP connection port 8899. I believe this is a variant of the <a href="https://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf">MODBUS/TCP protocol</a> that is assigned TCP port 502.</p><p>To perform MODBUS queries, I used <a href="https://github.com/jmccrohan/pysolarmanv5">pysolarmanv5</a>. To connect to the logger pysolarmanv5 requires the logger serial number and IP address. Initially I read the serial number off the label of the logger, and looked up the IP address from the admin page of my router. Later I noticed <a href="http://solarman_scan.py">solarman_scan.py</a>, which sends a broadcast UDP packet which the data logger replies to. I sometimes had to run it more than once before the logger responded to the scan packet.</p><p>The Solis 4G-US series inverters are <a href="https://sunspec.org/sunspec-modbus-specifications/">Sunspec MODBUS</a> certified, and have the well-known 32-bit ‘SunS’ identifier (0x53756e53) at address 40001. This means it should be possible to read the registers by decoding the SunSpec information models and inverter device IDs. SunSpec <a href="https://github.com/sunspec">shares some example code</a>, however I haven't been able to figure it all out.</p><p>I couldn't figure out the Solis registers using SunSpec, but I was able to find the <a href="https://ginlongsolis.freshdesk.com/support/solutions/articles/36000340158-modbus-communication-for-solis-inverters">register documentation from</a> Ginlong. The AC output power and DC input power are 32-bit registers at address 3005 using MODBUS function code 4 (input registers). I wrote a python program to read the output and input power and calculate the efficiency. It also reads the inverter temperature, and outputs the data every 5 minutes. I also wrote a small AWK program to calculate the weighted average of multiple samples. The code can be found <a href="https://github.com/nerdralph/solis-us">in my github repo</a>.</p><p>Over multiple days of output in the spring of 2023 including sunny and cloudy days, I observed an overall efficiency of 94% for a Solis 1P4K-4G-US. For a Solis 1P6K-4G-US I observed an average efficiency of 95%. This compares to respective advertised CEC weighted efficiencies of 97.5% and 97.0%.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-51321841363960011332023-04-10T06:43:00.003-07:002023-04-10T06:43:53.362-07:00KSTAR Single Phase String Inverters<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWVFncNK5xN2W9VzvPZ2PXP8em6xeWt_bTAEU0jyC38tsUuxVaF4sMPDChK2kvAFZFiKjVAqb6rNUa0FZHS1HVCGkWbN3U8PGOuh5euCjmk5wAMw9cQNbOm-fT1OmWUgQaXQ_pkpOnAhRVcRuyqWZCgOE8PfWQd0xrDAxJ0f8Js1oNutbqiKDVPUDb/s800/5000D.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="800" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWVFncNK5xN2W9VzvPZ2PXP8em6xeWt_bTAEU0jyC38tsUuxVaF4sMPDChK2kvAFZFiKjVAqb6rNUa0FZHS1HVCGkWbN3U8PGOuh5euCjmk5wAMw9cQNbOm-fT1OmWUgQaXQ_pkpOnAhRVcRuyqWZCgOE8PfWQd0xrDAxJ0f8Js1oNutbqiKDVPUDb/w400-h300/5000D.jpg" width="400" /></a></div> <p></p><p><a href="https://www.kstar.com/">KSTAR New Energy</a> makes single phase grid-tied inverters ranging from 1 kW to 10 kW. I tested a 3000S, a 5000D, and a 6000D that were produced in KSTAR's factory outside of Shenzhen. Their single phase inverters are marketed for locations with a 230 V line to neutral (L-N) grid. They also work with the split phase 240 V line to line grid that is typical in the US and Canada. They do not have UL 1741 certification, so they would require special engineering approval to be used for permanent installations with most US and Canada power utilities.<br /></p><p>Residential inverters used in the US and Canada usually have an attached junction box with terminal connections for DC and AC wiring. In the rest of the world, inverters usually have MC4 connectors for the DC string input, and a watertight three-pin plug connection for the AC output. It is much more convenient having the plug connections when testing inverters and PV panels. It also avoids potential electrical code concerns when DC wiring up to 600 V and 240 Vac are in the same junction box.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKNX4atpxMGBTuDQGngzYRfXRW8tv_32Ul7w_4ITp8OQWKTrfqkQNEbdfKwlb_WXn0So9AZ0qOSSmA5j_nvdNtfZSwCXaNbq2svvxU9gwYysdeTT9DfTQrhUXDeVRLlEj-hVxm5sTSdnX1AWgdGeY5QsLJla8j_uUh-yAusY2s7gMAjOxT7buk7iUn/s1632/AC_output.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1224" data-original-width="1632" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKNX4atpxMGBTuDQGngzYRfXRW8tv_32Ul7w_4ITp8OQWKTrfqkQNEbdfKwlb_WXn0So9AZ0qOSSmA5j_nvdNtfZSwCXaNbq2svvxU9gwYysdeTT9DfTQrhUXDeVRLlEj-hVxm5sTSdnX1AWgdGeY5QsLJla8j_uUh-yAusY2s7gMAjOxT7buk7iUn/w200-h150/AC_output.jpg" width="200" /></a></div> <p></p><p>The KSTAR inverters all included MC4 crimp connectors for terminating the DC strings. The AC connector will accept SOOW or SJOW cable with a outside diameter of up to 16 mm. I used 3-wire 12 AWG SOOW cable that is rated for up to 25 Amps.<br /></p><p>The 3000S has a single string input, and a "nominal" output power of 3 kW. It is a light inverter, with a stated weight of 8 kg. Out of the box, the measured weight was 7.3 kg. The light weight makes it very easy for a single person to install. When hooked up to a test string of 10 72-cell panels, the efficiency was 85-86%. This is much lower than the spec efficiency of 97% or the 96% efficiency at nominal 380 V listed on the inspection and test sheet that was included with the inverter. With input power of 3070 W and input voltage of 367.7 V, the output power was 2620 W, for an efficiency of 85.3%. KSTAR sales and engineering were unable to explain the low efficiency.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-nQVB8ojBcbHoQGWuqcIiB_bjDq0tsqcQo22J2Q6wuQL_Ov7m1rDnYg52Qsw1PKXH73RqmeMs-L2XJtTFl54oKASh13alDv39ohGw3hSB_ntJuiUgr913l4FKFy9fx_33I8cbe8JqjwluzVIEUHUcW98i1aq-ub6fF9tizkDdSS5eMbvexH72X-M9/s1103/3000Spower.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="411" data-original-width="1103" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-nQVB8ojBcbHoQGWuqcIiB_bjDq0tsqcQo22J2Q6wuQL_Ov7m1rDnYg52Qsw1PKXH73RqmeMs-L2XJtTFl54oKASh13alDv39ohGw3hSB_ntJuiUgr913l4FKFy9fx_33I8cbe8JqjwluzVIEUHUcW98i1aq-ub6fF9tizkDdSS5eMbvexH72X-M9/w640-h238/3000Spower.png" width="640" /></a></div><br /><p>The 5000D and 6000D have the same external dimensions and connections on the bottom. The weight of the 5000D is 11.74 kg, while the 6000D weighs 12.48 kg. This suggests the 6000D has different internal circuitry, likely larger inductors and capacitors, to support the higher power rating.</p><p>The efficiency of the 5000D and 6000D inverters ranged between 89 and 91%. The screenshot of monitor data below shows a total input power of 6240 W with AC output power of 5570 W, for an efficiency of 89.3%. This test was done with a large difference between the PV1 and PV2 voltages to represent typical residential PV installations which are not optimized for the inverter's 380 nominal string voltage.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFTaUE2evNnZdVvf5KVPAc117tV2pd7CLC7UZd6pABG-ZG3vzGwjymUnKnQZWz1eEOxSkL-VeHMXzfCI0hEQJfqBQfOzQbsByPxd2xhLQWEvRY26pewtsaYUomPEfb_cnWH9URA-7ViAA6zJ2NCSwvcqBklIF92AzCN58dMxa6XmPK8rT8qU0riQvd/s1125/6000Dpower.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="369" data-original-width="1125" height="210" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFTaUE2evNnZdVvf5KVPAc117tV2pd7CLC7UZd6pABG-ZG3vzGwjymUnKnQZWz1eEOxSkL-VeHMXzfCI0hEQJfqBQfOzQbsByPxd2xhLQWEvRY26pewtsaYUomPEfb_cnWH9URA-7ViAA6zJ2NCSwvcqBklIF92AzCN58dMxa6XmPK8rT8qU0riQvd/w640-h210/6000Dpower.png" width="640" /></a></div><br /><p></p><p></p><p>The KStar inverters are reasonably priced and easy to install, but the low efficiency makes them unattractive compared to Growatt and Ginlong Solis inverters.<br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-32412043128593740232022-10-05T11:39:00.000-07:002022-10-05T11:39:13.119-07:00DC Wiring Losses in String and Microinverter Solar PV Arrays<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcgrG2kS1Om9N17RfBTNMKAplWJkX20lDCBqHarOsnUWL8-lBV56Y2Qa3OZt_M9O9RBxmtNFVVztsf31bQ6P44qt1_4f5UyNBZK_ewCYsD8A_bHO--TamivsEmjJ-4Ylq3VZZP1Bd0RzOU9gv-2C63GPgh1W0de6KYxQ-AMWPfcuQQCyrqxTlokxUi/s401/micro-string-inverters.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="263" data-original-width="401" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcgrG2kS1Om9N17RfBTNMKAplWJkX20lDCBqHarOsnUWL8-lBV56Y2Qa3OZt_M9O9RBxmtNFVVztsf31bQ6P44qt1_4f5UyNBZK_ewCYsD8A_bHO--TamivsEmjJ-4Ylq3VZZP1Bd0RzOU9gv-2C63GPgh1W0de6KYxQ-AMWPfcuQQCyrqxTlokxUi/s16000/micro-string-inverters.jpg" /></a></div><p>There are two common ways of wiring solar PV arrays. Each panel can be connected to a microinverter, with each microinverter connected in parallel to an AC bus. Alternatively, panels can be connected in series, with one or more DC strings connected to an inverter. Although there is debate over which design is best, at <a href="http://solarsi.ca">Solar Si</a>, we prefer string inverters. This is an analysis of DC wiring losses with an array of 8 <a href="https://www.longi.com/en/products/modules/hi-mo-4m/">72-cell LONGi PV modules</a> of about 450 Watts each.</p><p>There are two sources of wiring resistance in the array. The first is from the wire itself, and the second is from the connectors. The 12 AWG wire used for the panel output cables has a resistance of 5.2 mOhm/m. The MC4 connectors are specified to have a contact resistance of less than 0.5 mOhm. While this may be the resistance when tested in a clean and dry factory, test results in warm and humid conditions show a much higher resistance. <a href="https://www.osti.gov/servlets/purl/1083087">Reliability Model Development for Photovoltaic Connector Lifetime Prediction Capabilities</a> indicate resistance in the field is likely to be around 2.5 mOhm.</p><p>For the string array, the panels are arranged in the portrait configuration, with the inverter situated 1m from the array. The panels are about 1.06 m wide, making the length of the array 8.5 m. Each panel has a 20cm and a 40cm negative and positive output cable. Unlike the 12 AWG wire used for the PV panel output cables, in Canada, field wiring for PV strings is almost always done with 10 AWG RPVU wire. This has a resistance of 3.28 mOhm/m, and a total of 10.5 m are used for the array.</p><p>With 8 panels, there are 7 connections between panels, plus two connections at the ends mating with the RPVU wire. The DC connections on the inverter are usually not MC4, but for simplicity the resistance is assumed to be the same. Adding the positive and negative connections connections to the inverter, the total comes to 11. Here's the calculations for the total resistance:</p><p><span style="font-family: courier;">10.5 m * 3.28 mOhm/m = 34.4 mOhm<br />12 AWG 0.6 m panel cables * 8 = 4.8m, * 5.2 = 25 mOhm<br />11 contacts/string * 2.5 mOhm = 27.5 mOhm<br />total: 86.9 mOhm</span></p><p style="text-align: left;">For the microinverter array, the optional 1.4 m PV panel output cables will be needed in order for the cables to reach the corresponding microinverter. This increased the total length of 12 AWG wire to 22.4 m. Here's the calculations for the total resistance:</p><p><span style="font-family: courier;">12 AWG 2.8 m panel cables * 8 = 22.4 m, * 5.2 = 116 mOhm<br />16 contacts * 2.5 mOhm = 40 mOhm<br />total: 156 mOhm</span></p><p>Although the microinverter configuration higher resistance losses, they are not significant. During peak power output, DC current is about 10 Amps. Using P = I^2 * R, power losses are around 0.5%. Most of the time the array output current is much less than 10 Amps, so the average power loss is much lower. There are additional losses from the AC bus connectors, which are also not significant.</p><p>In conclusion, power losses are higher with microinverters than string inverters, but they are not significant. The justification for choosing string inverters lies more with the cost savings in material and labor. For an array with 16 panels, the cost of a 6 kW inverter with 2 string inputs is less than half the cost of 16 <a href="https://enphase.com/store/microinverters/iq7-series/iq7a-microinverter">Enphase IQ7A microinverters</a>.</p><p><br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-72155445816759521752021-04-16T16:47:00.003-07:002021-04-16T16:47:56.643-07:00Pi ethernet gadget with reverse SSH proxy<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikcyH6D1WDgGpT98NsciftHMu_KPaD9OGuduCu96a2LDqTdK4e6zxcTUBTIteGg8Nr4LikxBmIsld03fF3d2XNO2PEKZ1XyV-mb1_Lp9pRkKDslf-_Gt_AjfoGLPg6TcsRUWpigg-QG9Y/s501/piGadget.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="51" data-original-width="501" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikcyH6D1WDgGpT98NsciftHMu_KPaD9OGuduCu96a2LDqTdK4e6zxcTUBTIteGg8Nr4LikxBmIsld03fF3d2XNO2PEKZ1XyV-mb1_Lp9pRkKDslf-_Gt_AjfoGLPg6TcsRUWpigg-QG9Y/s16000/piGadget.png" /></a></div><p>I love my Pi Zeros. I think every hacker should have one in their toolbox. When I got my firs Pi Zero several years ago, I used <a href="http://nerdralph.blogspot.com/2018/04/ttl-usb-dongles-hackers-duct-tape.html">a USB-TTL serial adapter</a> to connect to the console UART on pins 8 and 10 of the Pi header. Once I learned <a href="http://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget">how to setup the Zero as an ethernet gadget</a>, things were a bit easier. However updating software was still a cumbersome process of downloading files to the host computer and then using scp to transfer them to the Pi. This blog post documents how to setup the Pi to use a SSH reverse proxy so utilities like git and apt work.</p><p>When I got my first Pi Zero, I chose the Pi OS Lite image. I decided to update to the March 4, 2021 release, and this time I used the Pi OS with desktop because it includes development tools like git. I followed the <a href="https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget">ethernet gadget setup instructions</a>, modifying config.txt, cmdline.txt, and creating an empty file called "ssh". The next step is to configure the multicast DNS component of <a href="http://www.zeroconf.org/">Zeroconf</a>. As mentioned in the Adafruit instructions, if you are using Windows, the easiest way to do this is installing Apple's <a href="https://support.apple.com/kb/DL999?locale=en_CA">Bonjour</a> service.</p><p>To use a reverse proxy over ssh, Windows users can't use putty as that feature is not supported. <a href="https://www.openssh.com/">OpenSSH</a> supports reverse socks5 proxies as of version 7.6. For connecting from Windows, I installed <a href="https://www.msys2.org/">MSYS2</a>, including OpenSSH 8.4. On Windows 10, <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">WSL</a> is probably the easiest option. To connect to the Pi and enable a reverse socks5 proxy on port 1080, enter, "ssh -R 1080 pi@raspberrypi.local".</p><p>Once connected to the Pi, set "http_proxy" to "socks5h://localhost:1080". The "h" at the end is important as it means the client will do hostname (DNS) resolution through the proxy. I added the following line to .profile to set it every time I login:</p><p><span style="color: #93c47d; font-family: courier;"><b style="background-color: black;">export http_proxy="socks5h://localhost:1080"</b></span></p><p>Programs such as git and curl will automatically use the socks proxy when the http_proxy environment variable is set. Note that github defaults to showing https URLs for repositories, which need to be changed to "http://" for the proxy to work.</p><p>The last configuration I recommend is setting the current date, since the Pi does not have a battery-backed RTC. I normally use ntpdate from <a href="https://github.com/ntp-project/ntp">the ntp project</a> for manually setting the date and time on Linux, but it does not work with a socks proxy. After some searching I found a suggestion of using the HTTP Date: field from a reliable internet server. The command I use is:</p><p><span style="color: #93c47d; font-family: courier;"><b style="background-color: black;">date -s "`curl -sI google.com | grep "^Date:" | cut -d' ' -f3-7`"</b></span></p><p>Once the Pi Zero is configured and has the proper date and time set, I recommend running "apt update". If everything is working properly, it will use the socks5 reverse proxy to connect to the raspbian servers and update the local apt repository cache.</p><p><br /></p><p></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com1tag:blogger.com,1999:blog-6245413346375218188.post-10658869394953652272021-04-03T15:16:00.002-07:002021-04-05T04:11:22.452-07:00Honey, I shrunk the Arduino core!<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYUoj6t0-D6GOzeEuQsvvWA66HOhzT1hqQ_wOQvONBo2ho9Y2-ZKDBWgWpsYYNBNOUwZiqhDsPcpRKrSKKs-61Pm9Pe748crSetk3bPNMD2PXk_EkC9TbCvHZt3LzX9D6wbFJcNFDIabI/s683/ArduinoShrink.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="201" data-original-width="683" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYUoj6t0-D6GOzeEuQsvvWA66HOhzT1hqQ_wOQvONBo2ho9Y2-ZKDBWgWpsYYNBNOUwZiqhDsPcpRKrSKKs-61Pm9Pe748crSetk3bPNMD2PXk_EkC9TbCvHZt3LzX9D6wbFJcNFDIabI/s16000/ArduinoShrink.png" /></a></div><br /><p></p><p>One of my gripes about <a href="https://github.com/arduino/ArduinoCore-avr">the Arduino AVR core</a> is that it is not an example of efficient embedded programming. One of the <a href="https://www.stroustrup.com/ETAPS-corrected-draft.pdf">foundations of C++ (PDF)</a> is zero-overhead abstractions, yet the Arduino core has a very significant overhead. The Arduino basic blink example compiles to almost 1kB, with most of that space taken up by code that is never used. Rewriting the AVR core is a task I'm not ready to tackle, but after writing <a href="https://github.com/nerdralph/picoCore">picoCore</a>, I realized I could use many of the same optimization techniques in an Arduino library. The result is <a href="https://github.com/nerdralph/ArduinoShrink">ArduinoShrink</a>, a library that can dramatically reduce the compiled size of Arduino projects. In this post I'll explain some of the techniques I used to achieve the coding trifecta of faster, better, and smaller.</p><p>The Arduino core is actually a static library that is linked with the project code. As Eli explains in <a href="https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking">this post on static linking</a>, libraries like libc usually have only one function per .o in order to avoid linking in unnecessary code. The Arduino doesn't use that kind of modular approach, however by making use of gcc's "-ffunction-sections" option, it does mitigate the amount of code bloat due to the non-modular approach.</p><p>With <a href="https://github.com/nerdralph/ArduinoShrink">ArduinoShrink</a>, I wrote more modular, self-contained code. For example, the <a href="https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c#L106">Arduino delay()</a> function calls micros(), which relies on the 32-bit timer0 interrupt overflow counter. I simplified the delay function so that it only needs the 8-bit timer value. If the user code never calls micros() or millis(), the timer0 ISR code never gets linked in. By using a more efficient algorithm and writing the code in AVR assembler, I reduced the size of <a href="https://github.com/nerdralph/ArduinoShrink/blob/master/src/delay.S">the delay function</a> to 12 instructions taking 24 bytes of flash.</p><p>In order to minimize code size and maximize speed, almost half of the code is in AVR assembler. Despite <a href="http://nerdralph.blogspot.com/2014/04/gcc-link-time-optimization-can-fix-bad.html">improvements in compiler optimization techniques</a> over the past decades, on architectures like the AVR I can almost always write better assembler code than what the compiler generates. That's especially true for interrupt service routines, such as the timer0 interrupt used to maintain the counters for millis() and micros(). My assembler version of the interrupt uses only 56 bytes of flash, and is faster than the Arduino ISR written in C.</p><p>One part that is still written in C is the digitalWrite() function. The Arduino core uses a set of tables in flash to map a given pin number to an IO port and bit, making for a lot of code to have digitalWrite(13, LOW) clear PORTB5. Making use of <a href="https://github.com/WestfW/Duino-hacks/blob/master/fastdigitalIO/fastdigitalIO.h">Bill's discovery that these flash memory table lookups can be resolved at compile time</a>, digitalWrite(13, LOW) compiles to a single instruction: "cbi PORTB, 5".</p><p><a href="https://github.com/nerdralph/ArduinoShrink">ArduinoShrink</a> is also designed to significantly reduce interrupt latency. The original timer0 interrupt takes around 5us to run, during which time any other interrupts are delayed. The first instruction in my ISR is 'sei', which allows other interrupts to run, reducing the latency impact to a few cycles more than <a href="http://nerdralph.blogspot.com/2020/04/measuring-avr-interrupt-latency.html">the hardware minimum</a>. The official Arduino core disables interrupts in several places, such as when reading the millis counter. My solution is to <a href="https://github.com/nerdralph/ArduinoShrink/blob/master/src/millis.S#L19">detect if the millis counter has been updated</a> and re-read it, thereby avoiding any interrupt latency impact.</p><p>The only limitation compared to the official AVR core is that the compiler must be able to resolve the pin number for the digital IO functions at compile time. Although the pin may hard-coded, even with LTO enabled, avr-gcc is not always able to recognize the pin is a compile-time constant. Since <a href="https://www.avrfreaks.net/forum/avr-gcc-and-avr-g-are-deprecated-now">AVR is not a priority target for GCC</a> optimizations, I can't rely on compiler improvements to resolve this limitation. Therefore I plan to write a version of digitalWrite that is much smaller and faster, even when avr-gcc can't figure out the pin at compile time.</p><p>Although <a href="https://github.com/nerdralph/ArduinoShrink">ArduinoShrink</a> should be compatible with any Arduino sketch, given some of the compiler tricks I've used it's not unlikely I've missed a potential error. If you do find what you think is a bug, open an <a href="https://github.com/nerdralph/ArduinoShrink/issues">issue</a> in the github repository.</p><p><br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com10tag:blogger.com,1999:blog-6245413346375218188.post-63587684742814893672021-03-02T18:53:00.002-08:002021-06-01T00:07:02.590-07:00Writing USB firmware on the CH55x MCUs<p>Over the last several months, I've been <a href="http://nerdralph.blogspot.com/2020/07/getting-started-with-wch-ch551-and-ch552.html">familiarizing myself with the CH552 and CH551 MCUs</a>. Most recently, I've been learning how to program the USB serial interface engine on these devices. The USB interface is powerful and flexible enough to implement many different kinds of USB devices, from HID to CDC serial. The highlights are:<br /></p><ul style="text-align: left;"><li>support for endpoints 0 through 4, both IN and OUT</li><li>64-byte maximum packet size</li><li>DMA to/from xram only</li><li>multiple USB interrupt triggers</li></ul><div>One of the first requirements for writing USB firmware is writing the <a href="https://www.beyondlogic.org/usbnutshell/usb5.shtml">descriptors</a>. The <a href="https://github.com/nerdralph/ch554_sdcc/blob/master/examples/usb_spi_flash/main.c">examples</a> from WCH are difficult to use as a template due to the descriptors being uint8_t arrays instead of structures. There are USB structure and constant definitions in <a href="https://github.com/nerdralph/ch554_sdcc/blob/master/include/ch554_usb.h">ch554_usb.h</a>, which I recommend using instead of arrays. For instance, I changed the <a href="https://github.com/nerdralph/ch554_sdcc/blob/master/examples/usb_device_cdc/main.c">CDC serial example</a> from :</div><p></p><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC34"><span class="pl-c"><span style="font-family: courier;"><b>__code uint8_t DevDesc[] = {0x12,0x01,0x10,0x01,0x02,0x00,0x00,DEFAULT_ENDP0_SIZE,</b></span></span></td>
</tr>
<tr>
</tr></tbody></table><div><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC35"><span class="pl-c"><span style="font-family: courier;"><b> 0x86,0x1a,0x22,0x57,0x00,0x01,0x01,0x02,</b></span></span></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC36"><span class="pl-c"><span style="font-family: courier;"><b> 0x03,0x01</b></span></span></td>
</tr>
<tr>
</tr></tbody></table><span class="pl-c"><span style="font-family: courier;"><b> };</b></span></span></div><div><br /></div><div>to:</div><div><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC42"><b><span style="font-family: courier;">__code USB_DEV_DESCR DevDesc = {</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC43"><b><span style="font-family: courier;"> .<span class="pl-smi">bLength</span> = <span class="pl-c1">18</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC44"><b><span style="font-family: courier;"> .<span class="pl-smi">bDescriptorType</span> = USB_DESCR_TYP_DEVICE,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC45"><b><span style="font-family: courier;"> .<span class="pl-smi">bcdUSBH</span> = <span class="pl-c1">0x01</span>, .<span class="pl-smi">bcdUSBL</span> = <span class="pl-c1">0x10</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC46"><b><span style="font-family: courier;"> .<span class="pl-smi">bDeviceClass</span> = USB_DEV_CLASS_COMMUNIC,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC47"><b><span style="font-family: courier;"> .<span class="pl-smi">bDeviceSubClass</span> = <span class="pl-c1">0</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC48"><b><span style="font-family: courier;"> .<span class="pl-smi">bDeviceProtocol</span> = <span class="pl-c1">0</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC49"><b><span style="font-family: courier;"> .<span class="pl-smi">bMaxPacketSize0</span> = DEFAULT_ENDP0_SIZE,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC50"><b><span style="font-family: courier;"> .<span class="pl-smi">idVendorH</span> = <span class="pl-c1">0x1a</span>, .<span class="pl-smi">idVendorL</span> = <span class="pl-c1">0x86</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC51"><b><span style="font-family: courier;"> .<span class="pl-smi">idProductH</span> = <span class="pl-c1">0x57</span>, .<span class="pl-smi">idProductL</span> = <span class="pl-c1">0x22</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC52"><b><span style="font-family: courier;"> .<span class="pl-smi">bcdDeviceH</span> = <span class="pl-c1">0x01</span>, .<span class="pl-smi">bcdDeviceL</span> = <span class="pl-c1">0x00</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC53"><b><span style="font-family: courier;"> .<span class="pl-smi">iManufacturer</span> = <span class="pl-c1">1</span>, <span class="pl-c"><span class="pl-c">//</span> string descriptors</span></span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC54"><b><span style="font-family: courier;"> .<span class="pl-smi">iProduct</span> = <span class="pl-c1">2</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC55"><b><span style="font-family: courier;"> .<span class="pl-smi">iSerialNumber</span> = <span class="pl-c1">0</span>,</span></b></td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC56"><b><span style="font-family: courier;"> .<span class="pl-smi">bNumConfigurations</span> = <span class="pl-c1">1</span></span></b></td>
</tr>
<tr>
</tr></tbody></table><b><span style="font-family: courier;">};</span></b></div><div><br /></div><div>Once the descriptors are written, the code to handle device enumeration is mostly boilerplate and can be copied from one of the examples. During the firmware development stage, I recommend adding a call to disconnectUSB() near the start of main(). It's a function I added to <a href="https://github.com/nerdralph/ch554_sdcc/blob/master/include/debug.h">debug.h</a> which forces the host to re-enumerate the device. This way I don't have to unplug and re-connect the USB module after flashing new firmware.</div><div><br /></div><div>Setting up the DMA buffer pointers requires special attention when multiple IN and OUT endpoints are used. Even though five endpoints are supported, there are only four DMA buffer pointer registers: UEP[0-3]_DMA. When the bits bUEP4_RX_EN and bUEP4_TX_EN are set in the UEP4_1_MOD SFR, the EP4 OUT buffer is UEP0_DMA + 64, and the EP4 IN buffer is UEP0_DMA + 128. Endpoints 1-3 have even more complex buffer configurations, with optional double-buffering for IN and OUT using 256 bytes for four buffers starting from the UEPn_DMA pointer.</div><div><br /></div><div>When I first started writing USB firmware for the CH551 and CH552, I was concerned that it may be difficult to meet the tight timing requirements, particularly for control and bulk packets that can have multiple in a single 1ms frame. For example, with small data packets, the time between the end of one OUT transfer and the end of the next OUT transfer can be less than 20uS. If the USB interrupt handler is too slow, the 2nd OUT transfer could overwrite the DMA buffer before processing of the first has completed. This situation is avoided by setting bUC_INT_BUSY in the USB_CTRL SFR. When this bit is set, the SIE will NAK any packets while the UIF_TRANSFER flag is set. Therefore I recommend setting bUC_INT_BUSY, and clear UIF_TRANSFER at the end of the interrupt handler.</div><div><br /></div><div>I am currently working on the <a href="https://github.com/nerdralph/ch554_sdcc/tree/master/examples/CMSIS_DAP">CMSIS_DAP</a> example. It implements the DAPv1 (HID) protocol supporting SWD transfers, and works well with <a href="http://openocd.org/">OpenOCD</a> and <a href="https://github.com/pyocd/pyOCD">pyOCD</a>. I'm working on adding CDC/ACM for serial UART communication. The first step is creating the descriptors for the composite CDC + HID device. The second step will be integrating the <a href="https://github.com/nerdralph/ch554_sdcc/tree/master/examples/usb_device_cdc">usb_device_cdc</a> code. The final step, although not absolutely necessary, will be optimizing the CDC code for baud rates up to 1mbps. The current code uses transmit and receive ring buffers with data copied to and from the IN and OUT DMA buffers. With double-buffering, the transmit and receive ring buffers can be omitted. The UART interrupt will copy directly between SBUF and the appropriate USB DMA buffer.</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com2tag:blogger.com,1999:blog-6245413346375218188.post-57626538591417299342021-01-26T06:49:00.002-08:002021-01-26T06:49:34.406-08:00Quirks of the CH55x MCUs<p>Over the past several months, I've been been learning to use <a href="http://nerdralph.blogspot.com/2020/07/getting-started-with-wch-ch551-and-ch552.html">the CH551 and CH552</a> MCUs. Learning generic 8051 programming was the easy part, as there is lots of old documentation available, with <a href="https://www.keil.com/dd/docs/datashts/philips/p51_pg.pdf">Philips having written some of the best</a>. The learning curve for WCH's additions to the MCS-51 architecture has been steeper, requiring careful reading of the datasheets, and reading <a href="https://github.com/nerdralph/ch554_sdcc">the SDK headers and examples</a>. I've found that the CH55x chips have some quirks that I've never encountered on any other MCUs.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUEiPl5Rl0Y7i37OSiZXxI1nzCBlcAKCrcW3XUmsL3W77eO0wx5JKywKqdKBYff6SnYXOR5CVfrlOpPtswq_rgotBDMTxwLAamzKCrL7XFP3CMyq2b__i2-ERnbnXcqo1p00z5fPxMELk/s569/CH55xIO.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="288" data-original-width="569" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUEiPl5Rl0Y7i37OSiZXxI1nzCBlcAKCrcW3XUmsL3W77eO0wx5JKywKqdKBYff6SnYXOR5CVfrlOpPtswq_rgotBDMTxwLAamzKCrL7XFP3CMyq2b__i2-ERnbnXcqo1p00z5fPxMELk/s16000/CH55xIO.png" /></a></div><p></p><p><br /></p><p>The GPIO modes are controlled by two registers: MOD_OC and DIR_PU. The register values are explained in the datasheet and in <a href="https://github.com/nerdralph/ch554_sdcc/blob/master/include/ch554.h#L204">ch554.h in the SDK</a>. Figure 10.2.1 in the datasheet shows a schematic diagram for the GPIO. Modes 0, 1, and 2 are for high-Z input, push-pull, and open-drain respectively. Mode 3, "standard 8051 mode" is the most complicated. It's an open drain mode with internal pullup, but with the output driven high for two cycles when the GPIO changes from a 0 to a 1. This ensures a fast signal rise time. The part that took me the longest to figure out was the operation of the pullup. The GPIO diagram shows 70k and 10k, but section 10 of the datasheet does not explain their operation. Therefore I've highlighted a part of the schematic in green. When the pin input schmitt trigger output is 1, the inverter in the top right of the diagram will output a low signal to turn on the pFET activating the 10k pullup. When port input value is 0, only the weak 70k pullup is active.</p><p>The pullups aren't actually implemented as resistors on the IC. They are specially-designed FETs with a high drain-source resistance (RDS). Since RDS varies with gate-source voltage (Vgs), the pullup resistance will vary inversely with Vcc. Using a 5V supply, the pullup resistance will be close to the 70k shown in the schematic. Using a 3.3V supply, the pullup resistance is close to 125k. Although it is not obvious, this information can be found in section 18 of the datasheet, with the specifications for IUP5 and IUP3. These numbers are the amount of current a grounded pin will source when the pullup is enabled.</p><p>The reset pin has an internal pulldown, which seems to be weak like the GPIO pullups. At times when working with a CH552 running at 3V3, the chip reset when I inadvertently touched the RST pin with my finger. This was easily solved by keeping the RST pin shorted to ground.</p><p>The last issue I encountered is more of a documentation issue than a quirk. The maximum reliable clock speed of an IC is depended on the supply voltage. All of the AVR MCUs I've worked with have a graph in the datasheet showing the voltage required to ensure safe operation at a given speed. For the CH55x MCUs, there is a subtle difference in the electrical specs at section 18 of the datasheet. At 5V, total supply current at 24MHz is specified, whereas the specs for 3.3V specify total operating current at 16Mhz. When I tried running a CH552T at 24MHz with a 3.3V supply, it never worked. The same part worked perfectly at 16MHz.</p><p>Despite the quirks, I think the CH55x MCUs are still a good value. Current quantity 10 pricing at <a href="http://lcsc.com">LCSC</a> is 36c for the CH552T, and 26c for the CH551G. I recently purchased a small tube of the CH552T, and have plans to test the touch, ADC, PWM, and SPI peripherals.</p><p><br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com1tag:blogger.com,1999:blog-6245413346375218188.post-80136589377493752342021-01-19T12:09:00.002-08:002021-01-19T12:09:57.942-08:00GD32E230: a better STM32F0?<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQm__Chpdorj2MV7_msdavuBigIFeraiMyJ2z8VOeuQikQKftGEnyclo2ce6zrELEuL3Gr3nWO_MwgoyviPeFZgXrhIvQnTIgf3Tm2ZkLHdwAgjKHtNBdFgIjiorpcB-Ahyphenhyphenw6QmovDk74/s487/GD32E230Kx.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="361" data-original-width="487" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQm__Chpdorj2MV7_msdavuBigIFeraiMyJ2z8VOeuQikQKftGEnyclo2ce6zrELEuL3Gr3nWO_MwgoyviPeFZgXrhIvQnTIgf3Tm2ZkLHdwAgjKHtNBdFgIjiorpcB-Ahyphenhyphenw6QmovDk74/s16000/GD32E230Kx.png" /></a></div>On my last LCSC order, I bought a few <a href="https://www.gigadevice.com/press-release/gigadevice-launches-low-cost-gd32e230-mcu-series-featuring-the-cortex-m23-generation-core/">GD32E230</a> chips, specifically the GD32E230K8T6. I chose the LQFP parts since I have lots of QFP32 breakout boards that <a href="http://nerdralph.blogspot.com/2014/08/breaking-out-qfp-attiny88-avr.html">I've used for other QFP32 parts</a>. Gigadevice is much better than many other Chinese MCU manufacturers when it comes to providing English documents. After my past endeavors trying to understand datasheets from <a href="http://www.wch-ic.com/">WCH</a> and CHK, going through the Gigadevice documentation was rather pleasant.<p></p><p>Although Gigadevice makes no mention of any STM32 compatibility, but the first clue is the matching pinouts of the STM32F030 and GD32E230. To prepare for testing, I tinned the pads on a couple of breakout boards, applied some flux, and laid the chips on the pads. I laid the modules on a cast-iron skillet, and heated it up to about 240C. The solder reflowed well, however I noticed some browning of the white silkscreen. Next time I'll limit the temperature to 220C. After testing for continuity and fixing a solder bridge, I was ready to try SWD. I connected 3.3V power and the SWD lines, and ran "pyocd cmd -v":</p><p><span style="color: #010201;"><span style="font-family: courier;"><b>0000710:INFO:board:Target type is cortex_m<br /></b></span><b style="font-family: courier;">0000734:INFO:dap:DP IDR = 0x0bf11477 (v1 MINDP rev0)<br /></b><b style="font-family: courier;">0000759:INFO:ap:AHB5-AP#0 IDR = 0x04770025 (AHB5-AP var2 rev0)<br /></b><b style="font-family: courier;">0000799:INFO:rom_table:AHB5-AP#0 Class 0x1 ROM table #0 @ 0xe00ff000 (designer=4 3b part=4cb)<br /></b><b style="font-family: courier;">0000812:INFO:rom_table:[0]<e000e000:SCS-M23 class=9 designer=43b part=d20 devtyp e=00 archid=2a04 devid=0:0:0><br /></b><b style="font-family: courier;">0000823:INFO:rom_table:[1]<e0001000:DWT class=9 designer=43b part=d20 devtype=00 archid=1a02 devid=0:0:0><br /></b><b style="font-family: courier;">0000841:INFO:rom_table:[2]<e0002000:BPU class=9 designer=43b part=d20 devtype=00 archid=1a03 devid=0:0:0><br /></b><b style="font-family: courier;">0000848:INFO:cortex_m_v8m:CPU core #0 is Cortex-M23 r1p0<br /></b><b style="font-family: courier;">0000859:INFO:dwt:2 hardware watchpoints<br /></b><b style="font-family: courier;">0000866:INFO:fpb:4 hardware breakpoints, 0 literal comparators</b></span></p><div>I did little probing around the chip memory. The <a href="https://www.gigadevice.com/manual/gd32e230xxxx-user-manual/">GD32E23x user manual</a> shows SRAM at 0x20000000, like STM32 parts. The contents looked like random values, which I could overwrite using the pyocd "ww' command. Writing to 0x20002000 resulted in a memory fault, indicating the part does not have any "bonus" RAM beyond 8kB.</div><div><br /></div><div>Next, I tried using the built-in serial bootloader. After connecting BOOT0 to VDD and connecting power, PA9 and PA10 were pulled high, indicative of the UART being activated. However my first attempt at using <a href="https://sourceforge.net/projects/stm32flash/">stm32flash</a> was not successful:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigyAYDTE56DECoNdtkZZ5sgahz4P8sEpEonM6YNJbvYo8LHD-v4PxoUouJC-hMAI8WsCc2Kz1Fg3ZOKWzvNmSPpV7xiRU-yEEOjFREyDj-bdooCmlGY4DJjFteyYKr6l9Z4bTmFkVqQhA/s383/stm32flash-GD32E230.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="108" data-original-width="383" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigyAYDTE56DECoNdtkZZ5sgahz4P8sEpEonM6YNJbvYo8LHD-v4PxoUouJC-hMAI8WsCc2Kz1Fg3ZOKWzvNmSPpV7xiRU-yEEOjFREyDj-bdooCmlGY4DJjFteyYKr6l9Z4bTmFkVqQhA/s16000/stm32flash-GD32E230.png" /></a></div><div><br /></div><div>After attaching my oscilloscope, and writing a small bootloader protocol test program, I was able to determine that the responses did seem to conform to the <a href="https://www.st.com/content/ccc/resource/technical/document/application_note/51/5f/03/1e/bd/9b/45/be/CD00264342.pdf/files/CD00264342.pdf/jcr:content/translations/en.CD00264342.pdf">STM32 bootloader protocol</a>. I did notice that the baud rate from the GD32E230 was only 110kbps, so it wasn't perfectly matching the 115.2kbps speed of the 0x7F byte sent for baud rate detection. To avoid the potential for data corruption, I switched to 57.6kbps. Before resorting to debugging the source for stm32flash, my test of<a href="https://github.com/florisla/stm32loader"> stm32loader</a> gave better results:</div><div><div><span style="color: #010201; font-family: courier;"><b>$ stm32loader -V -p com39</b></span></div><div><span style="color: #010201; font-family: courier;"><b>Open port com39, baud 115200</b></span></div><div><span style="color: #010201; font-family: courier;"><b>Activating bootloader (select UART)</b></span></div><div><span style="color: #010201; font-family: courier;"><b>*** Command: Get</b></span></div><div><span style="color: #010201; font-family: courier;"><b> Bootloader version: 0x10</b></span></div><div><span style="color: #010201;"><span style="font-family: courier;"><b> Available commands: 0x0, 0x2, 0x11, 0x21, 0x31, 0x43, 0x63, 0x73, 0x82, 0x92</b></span><b style="font-family: courier;">, 0x6</b></span></div><div><span style="color: #010201; font-family: courier;"><b>Bootloader version: 0x10</b></span></div><div><span style="color: #010201; font-family: courier;"><b>*** Command: Get ID</b></span></div><div><span style="color: #010201; font-family: courier;"><b>Chip id: 0x440 (STM32F030x8)</b></span></div><div><span style="color: #010201; font-family: courier;"><b>Supply -f [family] to see flash size and device UID, e.g: -f F1</b></span></div></div><div><br /></div><div>Next, I was ready to try flashing a basic program. I first checked for GD32E support in <a href="https://github.com/libopencm3/libopencm3">libopencm3</a>. No luck. Then as I read through the user manual, I noticed GPIOA starts at 0x4800 0000 on AHB2, the same as STM32F0 devices. The register names didn't match the STM32, but the function and offsets were the same. For example on the GD32E, the register to clear individual GPIOA bits is called GPIOA_BC, rather than GPIOA_BRR as it is called on the STM32. The clock control registers, called RCU on the GD32E, also matched the STM32 RCC registers. Since it was looking STM32F0 compatible, I tried flashing <a href="https://github.com/nerdralph/stm32f0-blink">my blink example</a> with stm32loader, and it worked!</div><div><br /></div><div>The LED was flashing faster than it did with the STM32F030. A little searching revealed that the ARM Cortex-M23, like the M0+, has a 2-stage pipeline. The STM32F030 with it's M0 core has a 3-stage pipeline. My <a href="https://github.com/nerdralph/stm32f0-blink">delay busy loop</a> needs to be four cycles per iteration, and on the M23, the bne instruction only takes two cycles. My solution is adding a nop instruction based on an optional compile flag.</div><div><br /></div><div>One problem I have yet to resolve with the GD32E is support for the bootloader Go/0x21 command. With the STM32F0, I left BOOT0 high, and used DTR to toggle nRST before uploading new code. The stm32flash "-g 0" option made the target run the uploaded code after flashing was complete. I went back to debugging stm32flash, and discovered that it is hard-coded to use the "Get Version"/0x01 command, and silently fails if the bootloader responds with a NAK. After a few mods to the source, I was able to build a version that works with the GD32E230, however the Go command still doesn't work. Perhaps a task for a later date will be to hook up a debug probe to see what the E230 is doing when it gets the Go command.</div><div><br /></div><div>Overall, I'm quite happy with the GD32E230K8T6. They cost less than half the equivalent STM32 parts, and are even cheaper than other Chinese STM32 clones I've seen. They are lower power and their maximum clock speed is 50% faster than the STM32F0. In addition to the shorter 2-stage pipeline, the GD32E devices support single-cycle IO, making them faster for bit-banged communications than the STM32F0 which takes 2 cycles to write to a GPIO pin. The GD32E230 also has some new features, which might be worth discussing in a future blog post.</div><div><br /></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com3tag:blogger.com,1999:blog-6245413346375218188.post-33032039731177552912021-01-02T16:51:00.000-08:002021-01-02T16:51:17.320-08:00Trying to test a "ten cent" tiny ARM-M0 MCU part 2<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlQtOIIMpM7g9bQgZy_w-0wwlNil84HNVGj5A84jYrowBPs7oov1lijYSxm5yvJxCD7SIswhIigARxRXoufj8sYSQckMEo8pKIECwJJDO9hwvEYC7z0Qi-bAYc2ZPSEI1jzjTnv94Lbh4/s642/HK32pyocd.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="157" data-original-width="642" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlQtOIIMpM7g9bQgZy_w-0wwlNil84HNVGj5A84jYrowBPs7oov1lijYSxm5yvJxCD7SIswhIigARxRXoufj8sYSQckMEo8pKIECwJJDO9hwvEYC7z0Qi-bAYc2ZPSEI1jzjTnv94Lbh4/s16000/HK32pyocd.png" /></a></div><p>After <a href="http://nerdralph.blogspot.com/2020/12/trying-to-test-ten-cent-tiny-arm-m0-mcu.html">my first look at the HK32F030MF4P6</a>, I wondered if the HK part, unlike the STM32F030 it is modeled after, does not have 5V tolerant IO. I changed the solder jumpers to 3V3 on <a href="https://www.electrodragon.com/product/ch552-ch554-mini-dev-board-ch55x-series/">the CH552 module</a> I'm using as a CMSIS-DAP adapter, which caused it to stop working. This was because the CH552 requires a 5V supply in order to run reliably at 24Mhz. After re-flashing the CMSIS-DAP firmware <a href="https://github.com/DeqingSun/ch55xduino/issues/28">set to run at 16MHz</a>, the module worked, and I was finally able to talk to the HK MCU via SWD.</p><p>In the screen shot above, I chose the stm32f051 target because pyocd does not have the HK MCU nor the STM32F030 among it's builtin targets. For basic SWD communications, the target option is not even necessary. With the target specified, it's possible to specify peripheral registers by name, rather than having to specify a memory address to read or write.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm7HNW_PyUEcIwLaZH9uM8z__NmK_d73X01nPBwDsyb9bgCx2s2IU7TfTf12bTw1-ypKk-Ppj5_RKDspDptG3gkjp8jt6uJqR6XT__JH4QFgqiE0UBXw0H_Sw_PX9aR8j5m-74AxaZ3DE/s437/pyOCD-reg.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="133" data-original-width="437" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm7HNW_PyUEcIwLaZH9uM8z__NmK_d73X01nPBwDsyb9bgCx2s2IU7TfTf12bTw1-ypKk-Ppj5_RKDspDptG3gkjp8jt6uJqR6XT__JH4QFgqiE0UBXw0H_Sw_PX9aR8j5m-74AxaZ3DE/s16000/pyOCD-reg.png" /></a></div><p>In the screen shot above, I'm using the "connect_mode" option to bring the nRST line low on the target device when entering debug mode. Usually this is not necessary for SWD, however some of the probing I did would cause the MCU to crash. This required a power cycle or reset to restore communications via SWD.</p><p>The first tests I did with the HK MCU were to probe the flash and RAM. The HK datasheet shows the flash at address 0. In the STM32F0, the flash is at address 0x8000000, and is mapped to address 0 when the boot0 pin is low. Although the HK MCU doesn't have a boot0 pin, data at address 0x8000000 is mirrored at address 0 as well. What was most unusal about the HK MCU is that the flash was not erased to all 0xFF as is typical with other flash-based MCUs. Most of the flash contents was zeros, except for some data at address 0x400, which was the same on the 2 MCUs I checked:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxRFHCum_MrMlaPJqtKeLfcrngbq6kANoc0zkjg4uxXQUeDvWANYyRjWb-cTjWiu4AhE48XnhSrGQTFWV7dP-aYS8gRz0HuVbkGdLX9jX755NvTbq5_bwrNd3pByw1gyuoFRZifmazyr8/s548/HK320x400.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="74" data-original-width="548" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxRFHCum_MrMlaPJqtKeLfcrngbq6kANoc0zkjg4uxXQUeDvWANYyRjWb-cTjWiu4AhE48XnhSrGQTFWV7dP-aYS8gRz0HuVbkGdLX9jX755NvTbq5_bwrNd3pByw1gyuoFRZifmazyr8/s16000/HK320x400.png" /></a></div><p>By writing to memory starting at 0x20000000 using the 'ww' command, I discovered that the MCUs I received have 4kB or RAM, rather than the 2kB specified in the datasheet. Writing to 0x20001000 (beyond 4kB) results in a crash.</p><p>For writing and erasing the flash, I initially tried using the pyOCD 'erase' and 'flash' commands. Since the MCU flash interface is not part of Cortex-M specification, the flash interface peripheral will vary from one MCU vendor to the next. The flash interface on the STM32F051 is almost identical to the flash interface on the STM32F030, however the 'erase' and 'flash' commands caused the HK MCU to crash when I ran them. Testing on a genuine STM32F030 crashed as well, and after some debugging and reading through the pyOCD code, I realized the STM32F051 flash routines need 8kB of RAM. Even after downloading and installing the STM32F0 <a href="https://www.keil.com/dd2/pack/">device pack</a>, I could not erase or flash the HK MCU.</p><p>Next I reviewed the <a href="https://www.st.com/resource/en/programming_manual/dm00051352-stm32f0xxx-cortexm0-programming-manual-stmicroelectronics.pdf">STM32F030 programming manual</a>, and tried to access the flash peripheral registers directly. This was when I found <a href="https://github.com/pyocd/pyOCD/issues/1034">a pyOCD bug with the wreg command</a>. I was able to unlock the flash by writing the magic sequence of 0x45670123 followed by 0xCDEF89AB to flash.keyr. I tried erasing the first page at address 0, and although flash.sr and flash.cr updated as expected, the memory contents did not change. What did work was erasing the page at address 0x8000000, which cleared the contents at address 0 as well. I still find it strange that the erase operation sets all bits to 0 instead of 1. The HK datasheet says a flash page is 128 bytes, and erasing a page resulted in 128 bytes set to all zero.</p><p>I was only partially successful in writing data to the flash. Writing to 0x8000000 did not work, however writing a 16-bits to address 0 using the 'wh' command was successful. Trying to write 16-bits to address 2 updated the flash.ar and flash.sr as expected, but did not change the data. Writing to any 4-byte aligned address in the erased page worked, but writing to addresses that were only 2-byte aligned left all 16 bits at zero. I tried writing bytes with 'wb' and full words with 'ww', both of which crashed the MCU, likely from a hard fault interrrupt. I even made sure there isn't a bug with the 'wh' command by writing 16-bits at a time to RAM.</p><p>While searching the CHK website for more documentation, I found <a href="http://www.hsxp-hk.com/companyfile/107.html">a page with IAR device packs</a>. Although pyOCD uses Kiel device packs, I downloaded the HK32F0 pack, which is a self-extracting RAR file, which saves the uncompressed files in AppData\Local\Temp\RarSFX0.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCdA-6p8MLaxdCzfvQBo6_HJXPh4NFIq1o2Y5PChsX754_IWAhiV3TxZqDvSVuJT9VjWGkEZ66qWGF3nPTvc10g9lC3Qu2F6fKMgAY0tqK-dr2T1hWk6_-ncfL7ePUoS-FHYCMdx_gZxY/s537/HK32pack.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="374" data-original-width="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCdA-6p8MLaxdCzfvQBo6_HJXPh4NFIq1o2Y5PChsX754_IWAhiV3TxZqDvSVuJT9VjWGkEZ66qWGF3nPTvc10g9lC3Qu2F6fKMgAY0tqK-dr2T1hWk6_-ncfL7ePUoS-FHYCMdx_gZxY/s16000/HK32pack.png" /></a></div><p>Since .pack files are just zip files with a different extension, I zipped the files back up as a .pack file. However pyOCD couldn't read it: "0000731:CRITICAL:__main__:CMSIS-Pack './HK32F0.pack' is missing a .pdsc file". Manually examining the files confirmed some of my earlier discoveries, such as flash at address 0x8000000, remapped to address zero. I found a file named HK32F030M.svd, which contains XML definitions of the peripheral registers. pyOCD's builtin devices appear to use svd files, so it may be possible to add HKD32F0 support to pyOCD.</p><p>Copies of the IAR support pack, datasheet, and pyocd page erase sequence can be found <a href="https://github.com/nerdralph/HK32F030M">in my github repository</a>.</p><p><br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-30167966487284893192020-12-13T16:24:00.007-08:002021-01-07T13:57:47.417-08:00Trying to test a "ten cent" tiny ARM-M0 MCU<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyt7Nt6BahbtHVh99M3QlgNo2Jn_xxr7Th3zSfpqi3FNqz3WjCoZKxZquWqQax5tw6kQMNQMjZUygE4uknY9Q8m6nUO8SyRpCPNhT-qF4MZ_1Mjd2D8jJrRCvzLnYj3QvkK8frz_6zRqE/s694/HK32F030MF4P6-LCSC.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="396" data-original-width="694" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyt7Nt6BahbtHVh99M3QlgNo2Jn_xxr7Th3zSfpqi3FNqz3WjCoZKxZquWqQax5tw6kQMNQMjZUygE4uknY9Q8m6nUO8SyRpCPNhT-qF4MZ_1Mjd2D8jJrRCvzLnYj3QvkK8frz_6zRqE/s16000/HK32F030MF4P6-LCSC.jpg" /></a></div>A few months ago, while browsing <a href="http://lcsc.com">LCSC</a>, I found <a href="https://lcsc.com/product-detail/Other-Processors-and-Microcontrollers-MCUs_HK-HK32F030MF4P6_C707444.html">a surprisingly cheap ARM M0 MCU</a>. At the time it was 16.6c in single-unit quantities, with no higher-volume pricing listed. From <a href="https://datasheet.lcsc.com/szlcsc/2007291735_HK-HK32F030MF4P6_C707444.pdf">the datasheet</a> LCSC has posted, there was enough information in English to tell that it has 2kB RAM, 16kB flash, and runs up to 32MHz with a 1.8V to 3.6V power supply. Although the part number suggests it may be a clone or is compatible with the <a href="http://nerdralph.blogspot.com/2020/12/stm32-starting-small.html">STM32F030</a>, it's not. The part number for the STM32F030 clone is HK32F030F4P6.<p></p><p>Some additional searching brought me to some Chinese web sites that advertised the chip as a 32-bit replacement for the STM8S003. The pinout matches the STM8S003F3P6, so in theory it is a drop-in replacement for the 8S003. Unlike the STM32F0, it has no serial bootloader, so programming has to be done via SWD. And with no bootloader support, there's no need to be able to remap the flash from 0x0800000 to 0x0000000 like the STM32. A small change to the linker script should be all it takes to handle that difference. Even though I wasn't sure how or if I'd be able to program the chips, I went ahead and ordered a few of them. I already had some TSSOP20 breakout boards, so the challenge would be in the software, and the programming hardware.</p><p>Since I'm cheap, I didn't want to buy a dedicated <a href="https://github.com/ARMmbed/DAPLink">DAPlink</a> programmer. I have a STM32F103 "blue pill", so I considered <a href="https://github.com/koendv/blackmagic-bluepill">converting it to a black magic probe</a>. But since I've been playing with <a href="http://nerdralph.blogspot.com/2020/07/getting-started-with-wch-ch551-and-ch552.html">the CH554 series of chips</a>, I decided to try running <a href="https://github.com/DeqingSun/ch55xduino/tree/playground/ch55xduino/ch55x/libraries/Generic_Examples/examples/05.USB/CMSIS_DAP">CMSIS-DAP firmware</a> on a CH552. If you're not familiar with CMSIS-DAP and SWD, I recommend <a href="https://interrupt.memfault.com/blog/a-deep-dive-into-arm-cortex-m-debug-interfaces">Chris Coleman's blog post</a>. Before I tried it with with the HK32F030MF4P6, I needed to try it with a known good target. Since I had recently been working with a <a href="http://nerdralph.blogspot.com/2020/12/stm32-starting-small.html">STM32F030</a>, that's what I chose to try first.</p><p>The two main alternatives for open-source CMSIS-DAP software for downloading, running, and debugging target firmware are <a href="http://openocd.org/">OpenOCD</a> and <a href="https://github.com/pyocd/pyOCD">pyOCD</a>. pyOCD is much simpler to use than OpenOCD; after installing it with <a href="https://pypi.org/project/pip/">pip</a>, 'pyocd list' found my CH552 CMSIS-DAP:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwmKUaNjO-z3eBFtjnJqyJtOvLtGIPGcR7lqqWJzmJneowJFgH61qdTjfgO7nlDalvMvDAcn1i4ii__joxlcrKYMonCqoW9ppjzl1x8w6HvmvnL4Rw2x5JfuLjHaAIv6Et8zEUurRdd88/s343/pyOCD-list.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="59" data-original-width="343" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwmKUaNjO-z3eBFtjnJqyJtOvLtGIPGcR7lqqWJzmJneowJFgH61qdTjfgO7nlDalvMvDAcn1i4ii__joxlcrKYMonCqoW9ppjzl1x8w6HvmvnL4Rw2x5JfuLjHaAIv6Et8zEUurRdd88/s16000/pyOCD-list.png" /></a></div><p>However that's as far as I could get with pyOCD. There seems to be <a href="https://github.com/ARMmbed/DAPLink/issues/748">a bug in the CMSIS-DAP firmware or pyOCD</a> around the handling of the DAP_INFO message. Fixing the bug may be a project for another day, but for the time being I decided to figure out how to use OpenOCD.</p><p>To use OpenOCD, you need to create a configuration file with information about your debug adapter and target. <a href="http://openocd.org/doc-release/html/index.html">It's all documented</a>, however it's very complicated given that OpenOCD does a whole lot more than pyOCD. It's also complicated by the fact that since the release of v0.10.0, there have been updates that have made material changes to the configuration file syntax. I had a working configuration file on Windows that wouldn't work on Linux. On Linux I was running OpenOCD v0.10.0-4, but on windows I was running v0.10.0-15. After installing the <a href="https://github.com/xpack-dev-tools/openocd-xpack/releases">xPack project OpenOCD build</a> on Linux, the same config file worked on both Linux and Windows, which I named "cmsis-dap.cfg":</p><p><b style="font-family: courier;">adapter driver cmsis-dap</b></p><p><b style="font-family: courier;">transport select swd<br /></b><b style="font-family: courier;">adapter speed 100</b></p><p><b style="font-family: courier;">swd newdap chip cpu -enable<br /></b><b style="font-family: courier;">dap create chip.dap -chain-position chip.cpu<br /></b><b style="font-family: courier;">target create chip.cpu cortex_m -dap chip.dap</b></p><p><b style="font-family: courier;">init<br /></b><b style="font-family: courier;">dap info</b></p><p>With dupont jumpers connecting SWCLK, SWDIO, VDD, and VSS on my STM32F030 breakout board, <a href="https://gist.github.com/nerdralph/c63e6ce06473660ab5adc95c0683bd12">here's the output</a> from openocd.</p><p>After making the same connections (factoring the different pinout) to the HK32F030MF4P6, I was getting no response from the MCU. Before connecting, I had done the usual checks for shorts and continuity, making sure all my solder connections were good. Next I tried just connecting VDD and VSS, while I probed each pin. Pin 2, SWDIO, was pulled high to 3V3, as was nRST. All other pins were low, close to 0V. The STM32F030 pulls SWDIO and nRST high too. I tried reconnecting SWDIO and SWCLK, and connecting a line to control nRST. I added "reset_config trst_and_srst" to my config file, and still didn't get a response. Looking at the debug output from openocd (-d flag) shows the target isn't responding to SWD commands:</p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; color: black; font-size: 11pt; font-style: normal; font-variant: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span style="font-family: courier;"><b>Debug: 179 99 cmsis_dap_usb.c:728 cmsis_dap_swd_read_process(): SWD ack not OK @ 0 JUNK
</b></span></span><span style="font-family: courier; font-size: 11pt; white-space: pre-wrap;"><b>Debug: 180 99 command.c:626 run_command(): Command 'dap init' failed with error code -4</b></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><br /></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">Since the datasheet says that after reset, pin 2 functions as SWDIO, and pin 11 functions as SWCLK, I'm at a bit of an impasse. I'll try hooking up my oscilloscope to the SWDIO and SWCLK lines to make sure the signals are clean. I've read that in some ARM MCUs, DAP works while the device is in reset, so I'll peruse the openocd docs to figure out how to hold nRST low while communicating with the target. And of course, suggestions are welcome.</p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><br /></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">Before I finish this post, I wanted to explain the reference to a "ten cent" MCU. LCSC does not list volume pricing for the part, but when I searched for the manufacturer's name, "Shenzhen Hangshun Chip Technology Development", I found <a href="https://ee-paper.com/incubation-100-mcu-factory-why-is-this-local-mcu-so-domineering/">an article about the company</a>. In the article, the company president, Liu Jiping, refers to the 10c ($0.1) price. I suspect that pricing is for quantities over 1000. Assuming these chips can actually be programmed with a basic SWD adapter, then even paying 20c for a 20-pin, 32MHz M0 MCU looks like a good deal to me.</p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><br /></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">Read <a href="http://nerdralph.blogspot.com/2021/01/trying-to-test-ten-cent-tiny-arm-m0-mcu.html">part 2</a> to find out how I got SWD working.</p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><br /></p><h3 class="e_title col d_titleN p_titleN_1">
</h3>
<div class="e_title col d_titleW p_titleW_38_1">
<div autocut="true" autosize="3" class="font js_dataCut js-fixLed" style="overflow-wrap: break-word;" title="16"><i class="iconfont"></i></div></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-23829768223549879562020-12-07T14:40:00.001-08:002020-12-07T14:48:41.108-08:00STM32 Starting Small<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP6-qjqDNQt2I0x8yNDmHmId-l5Cc8webyI2V07B8ru2M5QjJq144FfeKgoLj-zoPBO_y0W0kXWciy0K1j6bJfUZ47QDN-6-FfFG4RWpudUBF349965KdVBSWXygRc1cZY9dEBZBUqXf8/s775/STM32F030breakout.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="483" data-original-width="775" height="398" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP6-qjqDNQt2I0x8yNDmHmId-l5Cc8webyI2V07B8ru2M5QjJq144FfeKgoLj-zoPBO_y0W0kXWciy0K1j6bJfUZ47QDN-6-FfFG4RWpudUBF349965KdVBSWXygRc1cZY9dEBZBUqXf8/w640-h398/STM32F030breakout.jpg" width="640" /></a></div><p></p><p>For software development, I often prefer to work close to the hardware. Libraries that abstract away the hardware not only use up limited flash memory, they add to the potential sources of bugs in your code. For a basic test of STM32 library bloat, I compiled the buttons example from <a href="https://github.com/nerdralph/TM1638NR">my TM1638NR library</a> in the Arduino 1.8.13 IDE using <a href="https://github.com/stm32duino/Arduino_Core_STM32">stm32duino</a> for a STM32F030 target. The flash required was just over 8kB, or slightly more than half of the 16kB of flash specification on the STM32F030F4P6 MCU. While I wasn't ready to write my own tiny Arduino core for the STM32F, I was determined to find a more efficient way of programming small ARM Cortex-M devices.</p><p>After a bit of searching, looking at Bill Westfield's <a href="https://github.com/WestfW/Minimal-ARM">Miimalist ARM project</a>, <a href="https://github.com/libopencm3/libopencm3">libopencm3</a>, and other projects, I found most of what I was looking for in <a href="https://vivonomicon.com/2018/04/02/bare-metal-stm32-programming-part-1-hello-arm/">a series of STM32 bare metal programming</a> posts by William Ransohoff. However instead of using an <a href="https://www.st.com/en/development-tools/st-link-v2.html">ST-Link programmer</a>, I decided to use a standard USB-TTL serial dongle to communicate with the ROM bootloader on the STM32.</p><p>To enable the bootloader, the STM32 boot0 pin must be pulled high during power-up. then the bootloader will wait for communication over the USART Tx and Rx lines. On the STM32F030F4P6, the Tx line is PA9, and the Rx line is PA10. In order reset the chip before flashing, I also connected the DTR line from my serial module to NRST (pin 4) on the MCU as shown in the following wiring diagram:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiblQ7HsF300xREXNYJSiqsDPGVP1ZVkUfCDgvFpfbPlBgdP6gVZMSbgRVYQE2nX0LpOsG9Culm6YVvBn95vDhrkQLefpK4iY92XtFva8MlmxyQsqOabGxH3nuKYqx3c-ojN9jTTi6G-_w/s549/STM32F030wiring.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="248" data-original-width="549" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiblQ7HsF300xREXNYJSiqsDPGVP1ZVkUfCDgvFpfbPlBgdP6gVZMSbgRVYQE2nX0LpOsG9Culm6YVvBn95vDhrkQLefpK4iY92XtFva8MlmxyQsqOabGxH3nuKYqx3c-ojN9jTTi6G-_w/s16000/STM32F030wiring.png" /></a></div><p>For flashing the MCU, I decided on <a href="https://sourceforge.net/p/stm32flash/code/ci/master/tree/">stm32flash</a>. While installation on Debian Linux is as simple as, "apt install stm32flash", I had some difficulty finding a recent Windows build. So I ended up <a href="https://github.com/nerdralph/stm32flash/releases/download/v0.5.3/stm32flash.exe">building it myself</a>. Although my build defaults to 115.2kbps, I found 230.4kbps completely reliable. At 460.8kbps and 500kbps, I encountered intermittent errors, so I stuck with 230.4kbps. After making the necessary connections, and before flashing any code to the MCU, do a test to confirm the MCU is detected.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKPbW2W647OomPCrGbjc8WeUaVzbaCj0E9_TpRnXJY8pe-hUP6fXCbtuLYJ4hu3MCGrrN6F-sXCXXoTf0WLz-DCyij1bDsOX0fm2eBy61U0dZm-aZIthxRzIm53XAlO6pwOvP1m986ibk/s468/stm32flash.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="181" data-original-width="468" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKPbW2W647OomPCrGbjc8WeUaVzbaCj0E9_TpRnXJY8pe-hUP6fXCbtuLYJ4hu3MCGrrN6F-sXCXXoTf0WLz-DCyij1bDsOX0fm2eBy61U0dZm-aZIthxRzIm53XAlO6pwOvP1m986ibk/s16000/stm32flash.png" /></a></div><p>One thing to note about stm32flash is that it does not detect the amount of flash and RAM on the target MCU. The numbers come from a hard-coded table based on the device ID reported. The official flash size in kB is stored in the system ROM at address 0x1FFFF7CC. On my STM32F030F4P6, the value read from that address is 0x0010, reflecting the spec of 16kB flash for the chip. My testing revealed that it actually has 32kB of usable flash.</p><p>I used William's <a href="https://github.com/WRansohoff/STM32F0_minimal_GPIO">STM32F0 GPIO example</a> as a template to create <a href="https://github.com/nerdralph/stm32f0-blink">a tiny blinky example</a> that uses less than 300 bytes of flash. Most of that is for the <a href="https://github.com/nerdralph/stm32f0-blink/blob/master/src/vector_table.S">vector table</a>, which on the Cortex-M0 has 48 entries of 4 bytes each. To save space, I embedded the reset handler in an unused part of the vector table. Since the blinky example doesn't use any interrupts, all but the initial stack pointer at vector 0 and the reset handler at vector 1 could technically be omitted. I plan to re-use the vector table code for other projects, so I did not prune it down to the minimum.</p><p>The blinky example will toggle PA9 at a frequency of 1Hz. That is the UART Tx pin on the MCU, which is connected to the Rx pin on the USB-TTL dongle. This means when the example runs, the Rx LED on the USB-TTL dongle will flash on and off.</p><p>I think my next step in Cortex-M development will be to experiment with <a href="https://github.com/libopencm3/libopencm3/wiki">libopencm3</a>. It appears to have a reasonably lightweight abstraction of GPIO and some peripherals, so it should be easier to write code that is portable across multiple different ARM MCUs.</p><p><br /></p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com2tag:blogger.com,1999:blog-6245413346375218188.post-88551710686158006762020-10-05T07:18:00.001-07:002020-10-06T15:04:40.772-07:00LGT8F328P EDMINI board<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMyZw7Ll8iaRlwXjj0lmkUnpRhRmlspVMGFyBduIgJArnYWEPI3ajaqJfWIZVTXrM8FFbPAmr15T18Bq6e6hkvCWLZTKI-CrRgVVt2zP7KH6LN9OSLM9TWSJIf4w9RXJIdwLnrjJbUMHE/s693/EDMINI.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="310" data-original-width="693" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMyZw7Ll8iaRlwXjj0lmkUnpRhRmlspVMGFyBduIgJArnYWEPI3ajaqJfWIZVTXrM8FFbPAmr15T18Bq6e6hkvCWLZTKI-CrRgVVt2zP7KH6LN9OSLM9TWSJIf4w9RXJIdwLnrjJbUMHE/w400-h179/EDMINI.jpg" width="400" /></a></div><br /><div>Earlier this year I purchased a <a href="https://www.electrodragon.com/product/edmini-dev-board-arduino-compatible/">EDMINI board from Electrodragon</a>. It uses a LGT8F328P chip, which supports the AVR instruction set. The instruction set timings and peripheral registers vary slightly from the ATmega328P, so it is not 99% compatible as claimed by Electrodragon. I bought one to see just how compatible it is, and possibly to port some of my AVR libraries to the LGT MCU.</div><div><br /></div><div>The module arrived in an anti-static bag, inside a padded envelope. After connecting 5V power to the board, the D13 LED blinked on and off every second, suggesting that it comes with the Arduino blink sketch pre-loaded. I then hooked up a USB-TTL adapter, installed the LGT board file in the Arduino IDE, and tried flashing a modified blink sketch to the board. The upload failed, and after some debugging I found that the reset was not working on the MCU. Neither pressing and holding the reset button nor grounding RST would reset the board. After contacting Electrodragon, Chao agreed replace the board, with two new boards. He told me that they see a higher than average failure rate with the LGT8F328P chips.</div><div><br /></div><div>In addition to Chao's frank comment about reliability, another concern I had about the LGT parts was the lack of markings on the chip. I suspect LGT sells the parts without markings so vendors can label them with their own brand. This also makes it easier for more nefarious manufacturers to label them as an ATmega328p. </div><div><br /></div><div>When the new boards arrived, the first thing I did was make sure the reset button worked. After pressing reset the LED flashes quickly three times for the bootloader, and then flashes on and off every second. However when I tried uploading sketch using the Arduino IDE, the upload still failed. After some more debugging, I found I could upload if I pressed the reset button just before uploading. This meant the bootloader was working, but auto-reset (toggling the DTR line) was not. These boards use the same auto-reset circuit as an Arduino Pro Mini:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe60xVtY4FZDttMOIAzatwGF4papLL3q4gGgwSQjnTfBNhQzT6bq2mRofnoxc6V_bb_ubYSK5XParx2jw32pY4UDtUJ1T0o9gVME0olXV9f-hCj-5OLko-GTAqVZimSU5Z3yS14CGvn3k/s406/Arduino-auto-reset.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="406" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe60xVtY4FZDttMOIAzatwGF4papLL3q4gGgwSQjnTfBNhQzT6bq2mRofnoxc6V_bb_ubYSK5XParx2jw32pY4UDtUJ1T0o9gVME0olXV9f-hCj-5OLko-GTAqVZimSU5Z3yS14CGvn3k/s16000/Arduino-auto-reset.png" /></a></div><br /><div>A negative pulse on DTR will cause a voltage drop on RST, which is supposed to reset the target. When the target power is 5V and 3V3 TTL signals are used, toggling DTR will cause RST to drop from 5V to about 1.7V (5 - 3.3). With the ATmega328P and most other AVR MCUs, 2V is low enough to reset the chip. The LGT8F328P, however requires a lower voltage to reset. In some situations this can be a good thing, as it means the LGT MCU is less likely to reset due to electromagnetic interference.</div><div><br /></div><div>The EDMINI board has a 3V3 regulator which can be selected by a solder jumper. This is mentioned on the Electrodragon site, but it is not clearly documented which pads need to be shorted to switch from 5V to 3V3. After a bit of debugging I was able to run the board at 3V3, and was able to use the auto-reset feature.</div><div><br /></div><div>I do most of my AVR development using command line tools, not the Arduino IDE. I compiled <a href="https://github.com/nerdralph/nerdralph/blob/master/avr/blink.c">a small program that toggles every pin on PORTB</a> using avr-gcc 5.4.0, and flashed it to the EDMINI board using avrdude. Nothing happened. Since the Arduino blink sketch worked, I know that the LED on PB5 was working. My conclusion is that the LGT Arduino core must do some setup to enable PORTB. This is common on modern MCUs such as the ARM Cortex, but on AVRs like the ATmega328p, writing 255 to the PORTB and DDRB registers is all it takes to drive every pin on port B high.</div><div><br /></div><div>I won't be doing any development work with the LGT MCUs. Although they are cheaper and can run a bit faster than authentic AVR parts, their compatibility is rather limited. Any code that relies on the standard AVR instruction set timing, such as <a href="http://nerdralph.blogspot.com/2020/02/building-better-bit-bang-uart-picouart.html">my picoUART library</a>, will not work. The 8F328P cannot be programed with a <a href="https://github.com/nerdralph/usbasp">USBasp</a>, as the native programming interface is SWD, not Atmel's SPI-based protocol. For a cheap and powerful MCU, <a href="http://nerdralph.blogspot.com/2020/07/getting-started-with-wch-ch551-and-ch552.html">the CH551</a> looks much more interesting.</div><div><br /></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com4tag:blogger.com,1999:blog-6245413346375218188.post-48856869737572976742020-09-17T11:09:00.000-07:002020-09-17T11:09:34.765-07:00Recording the Reset Pin<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6YZAC5MhSjVQNlcgpKC7gGJeVjnfu0Pas7taR9Y01d3HoQbxvpV9cLUtCmQ_wgxhIRoN40UpBAvaLAsFvbeeEJcRMOruhGsPrQQMYHLQCp3rUiKf6FKet_mAgF2exNv14cAyA86Ecw3o/s1088/PiggyProg-t13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="459" data-original-width="1088" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6YZAC5MhSjVQNlcgpKC7gGJeVjnfu0Pas7taR9Y01d3HoQbxvpV9cLUtCmQ_wgxhIRoN40UpBAvaLAsFvbeeEJcRMOruhGsPrQQMYHLQCp3rUiKf6FKet_mAgF2exNv14cAyA86Ecw3o/w640-h270/PiggyProg-t13.png" width="640" /></a></div><br /><p></p><p>The AVR reset pin has many functions. In addition to being used as an external reset signal, it can be used for <a href="http://nerdralph.blogspot.com/2018/04/debugging-debugwire.html">debugWire</a>, and it is used for SPI and for <a href="http://nerdralph.blogspot.com/2018/05/piggyfuse-hvsp-avr-fuse-programmer.html">high-voltage programming</a>. Other than for when it is used as an external reset signal, the datasheet specifications are somewhat ambiguous. I recently started working on <a href="https://github.com/nerdralph/usbasp">an updated firmware for the USBasp</a>, and wanted to find out more details about the SPI programming mode. The image above is one of many recordings I made from programming tests of AVR MCUs.</p><p>When I first started capturing the programming signals, I observed seemingly random patterns on the MISO line before programming was enabled. Although the datasheet lists the target MISO line as being an output, it only switches to output mode after the first two bytes of the "Programming Enable" instruction, 0xAC 0x53, are received and recognized. Prior to that the pin floats, and the seemingly random patterns I observed were caused by the signals on the MOSI and SCK lines inducing a voltage on the MISO line. I enabled the pullup resistor on the programmer side in order to keep the MISO line high until the PE instruction was recognized by the target.</p><p>One of the steps in the datasheet's serial programming alorithm that doesn't make sense to me is step 2, which says, "Wait for at least 20 ms and enable Serial Programming by sending the Programming Enable serial instruction to pin MOSI." It's clear from the capture image above that a wait time of less than 100 us worked in this case. I did a number of experiments with different targets (t13, t85, m8a) with and without the CKDIV8 fuse set, and found a delay of 64 us was always sufficient. Nevertheless, I still used <a href="https://github.com/nerdralph/usbasp/blob/master/firmware/isp.c#L187">a 20 ms delay in the USBasp firmware</a>.</p><p>Another observation I made was of a repeatable delay between the 8th rising edge of the SCK signal on the second byte and MISO going low. After multiple tests, I found that delay is between 2 and 3 of the target clock cyles. A close-up of the 0x53 byte shows this clearly:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDwdnJM3SbNwa98BeeCjU4sidJljfkeLUOi7jg4_qhrnjkZs8rFnUT0nmnqgtDQOUlJq9UwwJ5sjd-f8JH2f3q6RPQkICu4FT9L3wWPNzYj_RAMT6VRNRJpMw_Y2WCL6EU-DDGrMrC9yI/s1088/MISOdelay.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="459" data-original-width="1088" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDwdnJM3SbNwa98BeeCjU4sidJljfkeLUOi7jg4_qhrnjkZs8rFnUT0nmnqgtDQOUlJq9UwwJ5sjd-f8JH2f3q6RPQkICu4FT9L3wWPNzYj_RAMT6VRNRJpMw_Y2WCL6EU-DDGrMrC9yI/w640-h270/MISOdelay.png" width="640" /></a></div><br /><p>The 2-3 clock ccyle delay seems to correspond with the datasheet's specification of the minimum low and high periods for the SCK signal of 2 clock cycles when the target is running at less than 12Mhz. However I found I couldn't consistently get a target running at 8MHz to enter programming mode with a SCK clock of 1.5MHz. Additional logs of the programming sequence revealed something interesting when multiple PE instructions are sent at less than 1/8th of the target clock rate, with a positive pulse on RST for synchronization. In those sequences, the delay was smaller between the 8th rising edge of the SCK signal on the second byte and MISO going low for the second and subsequent times the PE instruction is sent. It seems you need to use a slower SCK frequency to get the target into programming mode, but after that, the frequency can be increased to 1/4 of the target clock.</p><p>Using what I learned, I have implemented automatic SCK speed negotiation and a higher default SCK clock speed. The speed negotiation starts with 1.5MHz for SCK, and makes 3 attempts to enter programming mode. If that fails, the next slower speed (750kHz) is tried three times, and so on until a speed is found where the target responds. For subsequent communications with the target, the speed is doubled, since the slowest speed is only needed the first time the PE command is received after power-up. The firmware also supports a maximum SCK frequency of 3MHz, vs 1.5MHz for the original firmware.</p><p>The higher speeds don't make a large difference in flash/verify times since the overhead of the vUSB code tends to dominate beyond a SCK frequency of 750kHz or so. Reading the 8kB of flash on an ATtiny85 takes around 3 seconds. By optimizing the low-speed USB code, such as <a href="https://github.com/cpldcpu/u-wire">was done by Tim with u-wire</a>, it should be possible to double that speed.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-54211639950706052282020-09-06T10:26:00.003-07:002020-09-08T04:57:33.995-07:00Flashing AVRs at high speed<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDdLif6VmQI8O4eJkXQ-4OC532fRWR8IxPzhkc-uJ0IWWWjICGyBDge0rjPBDX1FdaHHZd-Adkddl-T3BbNEudcPQlvhZdgRICjj2Y6Wv9EX1Uw_k4XH7meWgov4KQbDT_e1ZJBhklhhY/s706/t88osccal.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="439" data-original-width="706" height="311" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDdLif6VmQI8O4eJkXQ-4OC532fRWR8IxPzhkc-uJ0IWWWjICGyBDge0rjPBDX1FdaHHZd-Adkddl-T3BbNEudcPQlvhZdgRICjj2Y6Wv9EX1Uw_k4XH7meWgov4KQbDT_e1ZJBhklhhY/w500-h311/t88osccal.png" width="500" /></a></div>I've written a few<a href="https://github.com/nerdralph/picoboot-lib"> bootloaders</a> for AVR MCUs, which necessarily need to modify the flash while running. The typical 4ms to write or erase a page depends on the speed of the internal RC oscillator. Here's a quote from section 6.6.1 of the ATtiny88 datasheet:<p></p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p style="text-align: left;"><b>Note that this oscillator is used to time EEPROM and Flash write accesses, and the write times will be affected accordingly. If the EEPROM or Flash are written, do not calibrate to more than 8.8 MHz. Otherwise, the EEPROM or Flash write may fail.</b></p></blockquote><p>I wondered how running the RC oscillator well above 8.8MHz would impact erasing and writing flash In the past I read about tests showing the endurance of AVR flash and EEPROM is many times more than the spec, but I couldn't find any tests done while running the AVR at high speed. I did come across a post from an old grouch on AVRfreaks warning not to do it, so now I had to try.</p><p>The result is <a href="https://github.com/nerdralph/nerdralph/blob/master/avr/flashabuse.S">a program I called flashabuse</a>, which you'll see later is a bit of a misnomer. What the program does is set OSCCAL to 255, then repeatedly erase, verify, write, and verify a page of flash. I chose to test just one page of flash for a couple reasons. First, testing all 128 pages of flash on an ATtiny88 would take much more time. The second is that I would only risk damaging one page, and an ATtiny88 with 127 good pages of flash is still useful.</p><p>The results were very positive. My little program was completing about 192 cycles per second, taking 2.6ms for each page erase or page write. I let it run for an hour and a half, so it successfully completed 1 million cycles. Not bad considering Atmel's design specification is a minimum of 10,000 cycles.</p><p>So why does the flash work fine at high speed? I think it has to do with how floating-gate flash memory works. Erasing and writing the flash requires removing and adding a charge to the floating gate using high voltages. Atmel likely uses timing margins well in excess of the 10% indicated in the datasheet, so even half the typical 4ms is more than enough to ensure error-free operation. I even think writing at high speed puts less wear on the flash because it exposes the gate to high voltages for a shorter period of time.</p><h3 style="text-align: left;">Addendum</h3><div>I received some feedback questioning whether the faster write time may reduce retention due to reduced charge on the floating gate. As I mentioned above, Atmel likely used a very large timing margin when designing the flash memory. Chris Lamont, <a href="http://hypnocube.com/2014/11/flash-endurance-testing/">who tested flash retention on a PIC32</a>, stated that retention failure is "extremely unlikely".</div><div><br /></div><div>The retention specs for the ATtiny88 are, "20 years at 85°C / 100 years at 25°C". As <a href="https://www.micron.com/-/media/client/global/documents/products/technical-note/nor-flash/tn1230_nor_flash_cycling_endurance_data_retention.pdf">this Micron technical note (PDF) </a>shows, retention specs are based on models, not actual testing. Micron's JESD47I PCHTDR testing is done at 125C for 1000 hours, and requires 0 failures. TEKMOS states, "<a href="https://www.tekmos.com/about/blog/high-temperature-flash-memories">As a very rough rule of thumb, the data retention time halves for every 10C rise in temperature</a>." Extrapolating from a 100-year retention at 25C, retention at 255C, a typical reflow soldering peak temperature, would be only 6 minutes.</div><div><br /></div><div>In an attempt to show that retention is not impacted by repeated fast flashing, I performed two additional tests. For the first test, I baked the subject MCU for 12 hours at 150C, then performed 100,000 fast write/erase cycles. Next, 0x55 was written to the test page, and repeatedly verified for 2 hours. This test passed with no errors. For the second test, I filled the 8kB of flash with zeros to put a charge on the floating gate for every bit. I then baked the subject MCU for 12 hours at 150C, then verified that all bits remained at zero. This test passed with all 65,536 bits reading zero. I did, however have a failure of one solder joint, likely due to the stress of thermal cycling.</div><div><br /></div><div>For those who are <strike>particularly concerned</strike> paranoid about flash retention, one solution is refereshing the flash. For an AVR MCU, it would be simple to refesh the flash on every bootup with a small segment of code in .init1. The code would copy each page into the page buffer, then perform a write on the page. This would refresh all the 0 bits, and extend the retention life for another 20 to 100 years.</div><div><br /></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-80940421802910690922020-08-27T08:04:00.003-07:002021-01-21T06:04:00.381-08:00Hacker's Intro to USB hardware<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://en.wikipedia.org/wiki/USB#/media/File:USB.svg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1097" data-original-width="1920" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_LVev3YP7S3qMqMLQGCQnHZn1GOvF8IAFPaNDQa9JzhO-7aUecO5GiteeMkJCZTgLzFjnayTQrB8HhqFJCgrCN5cONrfZHSu6Nk_Aa1zTNZ7GknvmdRjju1QXaZRnXFlFa0h-WbTfNJM/s640/USB.svg.png" width="640" /></a></div><br /><p></p><p>Low-speed 1.5Mbps and full-speed 12Mbps USB, while more complicated than <a href="http://nerdralph.blogspot.com/2020/02/building-better-bit-bang-uart-picouart.html">a UART</a>, are still hacker-friendly. As the standard approaches 25 years old, I've decided to document some of the more useful highlights I've learned.</p><p>While some USB devices will have accessible PCB pads where you can probe signals, it's best to have some <a href="https://www.sparkfun.com/products/12700">breakouts</a> and pass-thru cables with test points. I've found broken micro-USB cables to be a cheap option. I cut the micro-b end off, strip the wires, and solder them to some protoboard with 4 pin headers for the ground 5V, D+ and D- connections. A crude USB voltage tester can be made with a couple silicon diodes and white or blue LED in series, powered by the 5V line. In the 20mA range, <a href="https://2n3904blog.com/1n4148-diode-forward-biased-i-v-curve/">a 1N4148 has a vF of about 0.8V</a>, so a 3.4V LED will be brightly lit if 5V is present. I've also made a custom USB-A extension cable with a section of the D+ and D- wires exposed for easy attachment of alligator clips.</p><p>Although USB power is 5V, typically at up to 500mA, the signalling is 3.3V. At the host, the data pins are pulled to ground with a resistance between 15k and 22k, so a typical host will use 18.5k Ohms. At the device, the D+(full-speed) or D-(low-speed) pin is pulled up to 3.3V to signal to the host that a device is attached. <a href="http://sdphca.ucsd.edu/lab_equip_manuals/usb_20.pdf">The spec (pdf)</a> shows this being done with a 1.5k pullup to 3.6V, creates a 18.5k/20k divider, resulting in 3.6V * 0.925 or 3.33V. I've found a 10k pullup to 5V works just fine, and many devices use a 1.5k pullup to 3.3V. since the spec requires a minimum of 2.7V for detection to work. For a connected low-speed device (like a mouse), D+ will be near 0V, and D- will be near 3.3V. For a full-speed device, the polarity will be reversed. High-speed devices use low-swing 400mV signalling with both D+ and D- at 0V when idle.</p><p>The frequency counter on a multimeter can be used to tell if a device is alive, or if the host has failed to recognize it. For a device that has been enumerated by a host, the host will send a keepalive signal to the device. For a low-speed device, this is a single-ended 0 (SE0) where D- is pulled low for 1.3us every ms. Therefore, a frequency of at least 1kHz will be detected on the D- line.</p><p>You can get a USB device to reconnect without unplugging it by forcing a bus reset. This can be done by shorting the D+(full-speed) or D-(low-speed). To avoid releasing the magic smoke by accidentally shorting the wrong connection, I suggest using 100-150 ohm resistor, which is still more than sufficient to reset the bus.</p>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-29336173298647056392020-07-02T08:11:00.004-07:002020-12-11T08:28:07.757-08:00Getting started with the WCH CH551 and CH552<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMYVZe5TzO9gilzQwqITTgi_5IdLjtHlAJ8hyphenhyphenSwHzlfqVdGT8ajDzHwj8xB_u66dbqIB4-E7wtAi5OxCTXN77TgbkjB9C-YrA75PyeNoE2YWuGMX0DJ2rYIHIE1JDgoSycLGt58Jx8to0/s1600/CH551G-Mini-DEV-Board.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="453" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMYVZe5TzO9gilzQwqITTgi_5IdLjtHlAJ8hyphenhyphenSwHzlfqVdGT8ajDzHwj8xB_u66dbqIB4-E7wtAi5OxCTXN77TgbkjB9C-YrA75PyeNoE2YWuGMX0DJ2rYIHIE1JDgoSycLGt58Jx8to0/s320/CH551G-Mini-DEV-Board.jpg" width="320" /></a></div>
When I first <a href="https://hackaday.com/2018/11/30/new-part-day-the-twenty-five-cent-usb-microcontroller-with-a-toolchain/">read about the CH554 series of MCUs</a>, I thought it would be interesting to test out some day. Part of the attraction is that it's based on the <a href="https://en.wikipedia.org/wiki/Intel_MCS-51">8051</a>, which is a well-documented an widely used architecture. The first assembly language I learned almost 40 years ago was for the 6502, so learning to program the 8-bit CISC should be relatively easy.<br />
<br />
Instead of purchasing <a href="https://lcsc.com/product-detail/WCH_WCH-Jiangsu-Qin-Heng-CH551G_C108958.html">the bare chips for pennies at LCSC</a> and putting together a breakout board, I bought a couple <a href="https://www.electrodragon.com/product/ch551-mini-dev-board-ch55x-series/">modules from Electrodragon</a>. I had learned that <a href="https://richis-lab.de/CH55x.htm">the CH551, CH552, and CH554 all used the same die</a>. I bought the CH551 and CH552 modules with the intention of eventually trying to hack them into working as a CH554.<br />
<br />
For testing the modules, in addition to the <a href="https://github.com/Blinkinlabs/ch554_sdcc">CH554 SDK for SDCC</a> on Linux, I've used <a href="https://github.com/DeqingSun/ch55xduino">Ch55xduino</a> on Windows. One thing not in the Ch55xduino documentation is driver setup. The windoze version I'm using is 7E, and when I first inserted the CH551 module, I got a driver error.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlYIib5W2hwZNo3sNtLhJta6REsHuFQzZWsteuLMoKvfJGE7J69almDUPyJJo0noluIKJ4Hl9hEDe_LeiiaDRbMdMbikS_CwNmEqdtA7_ppBsW62mnCr-zc14rdZgyPgxXxCSqd-Smw8/s1600/CH551error.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="271" data-original-width="536" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlYIib5W2hwZNo3sNtLhJta6REsHuFQzZWsteuLMoKvfJGE7J69almDUPyJJo0noluIKJ4Hl9hEDe_LeiiaDRbMdMbikS_CwNmEqdtA7_ppBsW62mnCr-zc14rdZgyPgxXxCSqd-Smw8/s1600/CH551error.png" /></a></div>
<br />
Using <a href="https://zadig.akeo.ie/">Zadig</a> to set the driver to libusb-win32 solved the problem.<br />
<br />
The CH55xduino documenation also lacks pinout documentation for anything other than the reverence board. To help, I've copied the pinouts from the CH552 datahseet.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhemkpuTUw4ulRrXSgQDP5GUM78bBt0jMcR9hvtEm_SOb61YCZTvxBsZOnycvuOPp6GkBsTaevk7er2tJFBoWMNTs0TSEdWvTkK0B5HTxm6QvJ_V-8tAIfvgwN4DnEYqUbIKKIlNKma5oI/s1600/CH551G+pinout.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="476" data-original-width="805" height="378" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhemkpuTUw4ulRrXSgQDP5GUM78bBt0jMcR9hvtEm_SOb61YCZTvxBsZOnycvuOPp6GkBsTaevk7er2tJFBoWMNTs0TSEdWvTkK0B5HTxm6QvJ_V-8tAIfvgwN4DnEYqUbIKKIlNKma5oI/s640/CH551G+pinout.png" width="640" /></a></div>
<br />
<br />
The CH55x bootloader supports <a href="https://blogs.mentor.com/colinwalls/blog/2013/06/10/device-firmware-upgrade-through-usb/">DFU</a>, which is what the CH55xduino uploader uses the first time code is uploaded to the module. Once the first sketch is uploaded, the CH55xduino core includes a CDC serial stack. With my CH551 module no longer appearing as a DFU device, I had to use Zadig again to change the CDC Serial device to use the USB Serial (CDC) driver. After that, the module appears as a COM port.<br />
<br />
With the COM port selected in the Arduino IDE, subsequent uploads enter the bootloader by switching the baud rate to 1200bps. If no COM port is selected, the upload tool looks for a CH55x device in DFU bootloader mode. To enter the bootloader, it is necessary to pull the USB D+ pin up to 3.3V when power is applied. The Electrodragon boards have a pinout for an upload jumper, which when shorted will connect the D+ pin (P3.6/UDP)to 3.3V through a 10k resistor. On one of my modules I soldered pin headers and use a jumper to force it into upload mode. On the other, I just used a low-value (270Ohm) through-hole resistor pushed into the holes.<br />
<br />
Currently CH55xduino is not optimized for size, with a basic blink sketch requiring 5333 bytes of flash. Officially, the CH551 is only supposed to have 10kB of available flash, so the CH55xduino overhead means less than 5kB is left for user code. The CH551 actually seems to have 12kB available for flashing user code, which I think will be plenty if the CH55xduino core gets some optimization work. Since I like to do low-level embedded coding, I'll be using SDCC from the command line most of the time. The blink example in the CH554 SDK for SDCC compiles to 700 bytes, and <a href="https://github.com/Blinkinlabs/ch554_sdcc/issues/17">I was able to get that down to 232 bytes after leaving out the UART initialization in debug.c</a>. With a bit more optimization I think I can get the blink example down to 100 bytes or so.<br />
<br />
One small surprise I found during my testing is that the Electrodragon CH551 and CH552 modules use different pins for the user LED. On the CH551, use P3.0, working in open-drain mode so the LED light up when P3.0 is low. On the CH552, drive P1.4 high to light the LED. This is documented on the Electrodragon web site, but it is easy to forget when switching between the two modules.<br />
<br />
I've already started to learn how to configure the standard MCS-51 UART, and have figured out how to directly manipulate the ports using the SFRs (Special Function Registers). Once I've mastered how to program these cheap little devices, I'll follow up with another blog post revealing the details.<div><br /><h4 style="text-align: left;">Postscript</h4><div>I recently found out that these modules do not fit well in a solderless breadboard. The row spacing for the 0.1" headers on the CH551 is about 0.47", so the pins have to bend out slightly to plug into the breadboard. On the CH552 module, the row spacing is about 0.52", so the pins have to bend out slightly to fit.</div><div>
<br /></div></div>Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com13tag:blogger.com,1999:blog-6245413346375218188.post-63662715991199463212020-06-11T21:09:00.000-07:002020-06-12T11:26:56.023-07:00A full-duplex tiny AVR software UARTI've written a few software UARTs for AVR MCUs. All of them have bit-banged the output, using cycle-counted assembler busy loops to time the output of each bit. The code requires interrupts to be disabled to ensure accurate timing between bits. This makes it impossible to receive data at the same time as it is being transmitted, and therefore the bit-banged implementations have been half-duplex. By using the waveform generator of the timer/counter in many AVR MCUs, I've found a way to implement a full-duplex UART, which can simultaneously send and receive at up to 115kbps when the MCU is clocked at 8Mhz.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZSS_TaHjrj_F_lze-nPkzIZZ1znCHSRwVaoOFF39jr04LMscQ4yTRAXUZWTFnhoGK11RxyWGqFtQ8prwnAzPq2ovz7RGFkFSrfJOgyFDZ6WkCAPtm0HeBqbYYiVSbDGiLhsLUi-Ms0zg/s1600/AVR-timer-counter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="723" height="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZSS_TaHjrj_F_lze-nPkzIZZ1znCHSRwVaoOFF39jr04LMscQ4yTRAXUZWTFnhoGK11RxyWGqFtQ8prwnAzPq2ovz7RGFkFSrfJOgyFDZ6WkCAPtm0HeBqbYYiVSbDGiLhsLUi-Ms0zg/s640/AVR-timer-counter.png" width="640" /></a></div>
<br />
I expect most AVR developers are familiar with using PWM, where the output pin is toggled at a given duty cycle, independent of the code execution. The technique behind my full-duplex UART is using the waveform generation mode so the timer/counter hardware sets the OC0A pin at the appropriate time for each bit to be transmitted. TIM0_COMPA interrupt runs after each bit is output. The ISR determines if the next bit is a 0 or a 1. For a 1 bit, TCCR0A is configured to set OC0A on compare match. For a 0 bit, TCCR0A is configured to clear OC0A on compare match. The ISR also updates OCR0A with the appropriate timer count for the next bit. To allow for simultaneous receiving, the TIM0_COMPA transmit ISR is made interruptible (the first instruction is "sei").<br />
<br />
The receiving is handled by PCINT0, which triggers on the received start bit, and TIM0_COMPB interrupt which runs for each received bit. I wrote this ISR in assembler in order to ensure the received bit is read at the correct time, taking into consideration <a href="http://nerdralph.blogspot.com/2020/04/measuring-avr-interrupt-latency.html">interrupt latency</a>. If any other interrupts are enabled, they must be interruptible (ISR_NOBLOCK if written in C). I've implemented a two-level receive FIFO, which can be queried with the rx_data_ready() function. A byte can be read from the FIFO with rx_read().<br />
<br />
The code is written to work with the ATtiny13, ATtiny85, and ATtiny84. Only PCINT0 is supported, which on the t84 means that the receive pin must be on PORTA. With a few modifications to the code, PCINT1 could be used for receiving on PORTB with the t84. The total time required for both the transmit and the receive ISRs is 52 cycles. Adding an average interrupt overhead of 7 cycles for each ISR means that there must be at least 66 cycles between bits. At 8Mhz this means the maximum baud rate is 8,000,000/66 = 121kbps. The lowest standard baud rate that can be used with an 8Mhz clock is 9600bps.<br />
<br />
The wgmuart application implements an example echo program running at the default baud rate of 57.6kbps. In addition to echoing back each character received, it prints out a period '.' every second along with toggling an LED.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheem8KDjjXQ7Lq5VyC6YaO0ksDSUQ1rs4caG_EG1R-dfr0WX103ckn0f01LruS1LezNL9E0MEF0zkblPGQfkinoyePJBAeQg78TjbDF2X03vZU5C4dfCO42JycitXP8WOG7UIhs5Y83Hs/s1600/wgmuart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="50" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheem8KDjjXQ7Lq5VyC6YaO0ksDSUQ1rs4caG_EG1R-dfr0WX103ckn0f01LruS1LezNL9E0MEF0zkblPGQfkinoyePJBAeQg78TjbDF2X03vZU5C4dfCO42JycitXP8WOG7UIhs5Y83Hs/s1600/wgmuart.png" /></a></div>
<br />
I've published <a href="https://github.com/nerdralph/nerdralph/tree/master/WGMUART">the code on github</a>.<br />
<br />Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com4tag:blogger.com,1999:blog-6245413346375218188.post-52086913019577473832020-04-27T14:38:00.000-07:002020-04-27T14:38:27.677-07:00Measuring AVR interrupt latencyOne thing I like about AVR MCUs is that their datasheets are relatively short and simple. It's also one of the things I don't like, because the datasheets often lack important details. Understanding external interrupt latency is one things that is lacking complete and clear details. I decided to investigate the interrupt latency of the ATtiny13 and the ATtiny85. The datasheet's description of interrupt response time and external interrupts is identical for both parts.<br />
<h4>
Interrupt Response Time</h4>
<div>
The ATtiny13 datasheet section 4.7.1, under the heading "Interrupt Response Time", says, "The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock cycles the Program Vector address for the actual interrupt handling routine is executed. [...] The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles. [...] If an interrupt occurs when the MCU is in sleep mode, the interrupt execution response time is increased by four clock cycles."</div>
<div>
<br /></div>
<div>
While section 4.7.1 is reasonably detailed, it has one significant error, and another important omission. The error is the sentence, "The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles". All AVRs with less than 8KB of flash, like the ATtiny13, have no jump instruction. They only have a relative jump "rjmp", which takes two clock cycles. This is obviously a copy/paste error from the datasheet of an AVR with more than 8KB of flash. Anyone familiar with the AVR instruction set would likely catch this simple error. The omission from section 4.7.1 is much harder to recognize until you carefully examine section 9.2 and figure 9-1 in the datasheet.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bR0ntCDxBEJdtS06osxN4osflbFrnfSMF3xxAckzut3Dfcww5NJ3ptTlSiiPTrJwWouk91452VHbDTWxZiEaSwnO2PFxQ0JWCSWAHqphz1zKXHV2qHx5HRLNfQZ86tDWVJOhyU0kHL4/s1600/pcint.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="136" data-original-width="779" height="110" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bR0ntCDxBEJdtS06osxN4osflbFrnfSMF3xxAckzut3Dfcww5NJ3ptTlSiiPTrJwWouk91452VHbDTWxZiEaSwnO2PFxQ0JWCSWAHqphz1zKXHV2qHx5HRLNfQZ86tDWVJOhyU0kHL4/s640/pcint.png" width="640" /></a></div>
<div>
<br /></div>
<div>
Figure 9-1 shows a circuit which appears to add a latency of two clock cycles to pin change interrupts. There is no written description for the circuit, and the external interrupt details in section 9.2 of the datasheet state, "Pin change interrupts on PCINT[5:0] are detected asynchronously." Since pin change interrupts can be used to wake the part from power-down sleep mode when all clocks are disabled, they must be detected asynchronously during power-down sleep. To determine when they are detected synchronously requires testing.</div>
<div>
<br /></div>
<div>
To test the interrupt latency I wrote <a href="https://github.com/nerdralph/nerdralph/blob/master/avr/isrtime.S">a program in assembler</a> that can generate low pulses of different lengths using PWM. I chose not to write the program in C because I want to be able to measure the interrupt latency down to a single cycle. On the t13, PB1 is the pin for INT0, PCINT1, and OC0B. By using OC0B to generate a low pulse on PB1, I'll be able to trigger INT0 and PCINT1 without any external connections. When the interrupt is triggered, it should take four cycles to execute the code at the interrupt vector. That code is an rjmp to the interrupt function, and that rjmp takes two additional clock cycles. For the best-case latency, the first instruction in the interrupt function will execute six cycles after the interrupt is triggered.</div>
<div>
<br /></div>
<div>
<a href="https://github.com/nerdralph/nerdralph/blob/master/avr/isrtime.S#L19">The first instruction of the interrupt function</a> checks the state of the pin that triggered the interrupt (the "sbic" instruction). If the pin is low, it skips the next instruction, then goes into an infinite loop. If the pin is high, it toggles the LED pin. Since the PWM is configured to generate a low pulse, if the pulse has ended before the sbic, the LED will light up to indicate the interrupt response time was too slow. The length of the pulse is one cycle longer than the value stored in OCR0B, <a href="https://github.com/nerdralph/nerdralph/blob/master/avr/isrtime.S#L28">which is done at lines 28 and 29</a>. My testing consisted mainly of modifying the OCR0B value, then building and flashing the modified code to the AVR.</div>
<h4>
Results</h4>
<div>
As expected INT0 latency is 4 clock cycles from the end of the currently executing instruction. This means that if the interrupt occurs during the first cycle of a call instruction which takes 3 cycles, the interrupt response time will be 6 cycles. For pin change interrupts, the latency is 6 cycles, indicating the synchronizer circuit adds 2 cycles of latency. In idle sleep mode, both INT0 and PCINT latency is 8 cycles, indicating pin change interrupts operate asynchronously when the CPU clock is not running.</div>
<div>
<br /></div>
Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com0tag:blogger.com,1999:blog-6245413346375218188.post-2092807590759830932020-04-08T12:11:00.000-07:002020-04-08T12:11:03.746-07:00Better asserts in C with link-time optimizationI've been <a href="http://nerdralph.blogspot.com/2014/04/gcc-link-time-optimization-can-fix-bad.html">a fan of link-time optimization for several years</a>. I've been a fan of efficient programming for even longer. I was an early fan of C++ because features like function overloading made it easier to move decisions done at run-time in C to compile-time with C++. As C++ has become more complex over the decades, I've become less of a C++ fan, and appreciate the simplicity of C.<br />
<br />
For small embedded systems like 8-bit AVRs and ARM M0, run-time error checking with assert() has minimal usefulness compared to UNIX, where a a core dump will help pinpoint the error location and the state of the program at the time of the error. Even if the usability problems were solved, real-time embedded systems may not be able to afford the performance costs of run-time error checking.<br />
<br />
Both C++ and C support static assertions. Anyone who has tried to use static_assert likely has encountered "expression in static assertion is not constant" errors for anything but the simplest of checks. The limitations of static_assert is well documented elsewhere, so I will not go into further details in this post.<br />
<br />
I had long understood that LTO allowed the compiler to evaluate expressions in code at build time, I never realized it's potential for static error checking. The idea came to me when looking at <a href="https://github.com/WestfW/Duino-hacks/blob/master/fastdigitalIO/fastdigitalIO.h">a fellow embedded developer's code for fast Arduino digital IO</a>. In particular, Bill's code introduced me to the gcc error function attribute. <a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html">The documentation</a> describes the attribute as follows:<br />
<br />
<ul>
<li>If the <code>error</code> or <code>warning</code> attribute
is used on a function declaration and a call to such a function
is not eliminated through dead code elimination or other optimizations,
an error or warning (respectively) that includes <var>message</var> is diagnosed. This is useful
for compile-time checking ...</li>
</ul>
Despite the fact that it seems the error attribute was introduced to address some of the limitations of static asserts, it doesn't seem to be commonly used. After some experimentation, I came up with a basic example.<br />
<b><span style="font-family: Courier New, Courier, monospace;">pll.c:</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">__attribute((error("")))</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">void constraint_error(char * details);</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"><br /></span></b>
<b><span style="font-family: Courier New, Courier, monospace;">volatile unsigned pll_mult;</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"><br /></span></b>
<b><span style="font-family: Courier New, Courier, monospace;"><br /></span></b>
<b><span style="font-family: Courier New, Courier, monospace;">void set_pll_mult(unsigned multiplier)</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">{</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"> if (multiplier > 8) constraint_error("multlier out of range");</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"> pll_mult = multiplier;</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">}</span></b><br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><b>main.c:</b></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b>extern void set_pll_mult(unsigned multiplier);</b></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b><br /></b></span>
<span style="font-family: Courier New, Courier, monospace;"><b>int main()</b></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b>{</b></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b> set_pll_mult(9);</b></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b>}</b></span><br />
<br />
<b><span style="font-family: Courier New, Courier, monospace;">$ gcc -Os -flto -o main *.c</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">In function 'set_pll_mult.constprop',</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"> inlined from 'main' at main.c:6:5:</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;">pll.c:9:25: error: call to 'constraint_error' declared with attribute error:</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"> if (multiplier > 8) constraint_error("multlier out of range");</span></b><br />
<b><span style="font-family: Courier New, Courier, monospace;"> ^</span></b><br />
When set_pll_mult() is called with an argument greater than 8, a compile error occurs. When it is compiled with a valid multiplier, the "if (multiplier > 8)" statement is eliminated by the optimizer. One drawback to the technique is that the caller (main.c in this case) is not identified when the called function is not inlined. Increasing the optimization level to O3 may help to get the function inlined.<br />
<br />Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com2tag:blogger.com,1999:blog-6245413346375218188.post-31800570853226532852020-02-12T18:05:00.001-08:002020-02-19T10:54:49.746-08:00Building a better bit-bang UART - picoUARTOver the past years, one of my most popular blog posts has been <a href="http://nerdralph.blogspot.com/2014/01/avr-half-duplex-software-uart.html">a soft UART for AVR MCUs</a>. I've seen variations of my soft UART code used in other projects. When <a href="https://github.com/MCUdude/MicroCore">MicroCore</a> recently integrated a modified version of my old bit-bang UART code, it got me thinking about how I could improve it.<br />
<br />
There were a few limitations to my earlier UART code. One was that it didn't support baud rates below 19.2kbps at 8Mhz or baud rates below 38.4kbps at 16Mhz. It was also problematic for people that tried to integrate it into C/C++ libraries, as the code was written in AVR assembler. Another problem that was recently brought to my attention by <a href="https://github.com/sleemanj/">James Sleeman</a>, was that the UART receive didn't work well at moderately high baud rates such as 57.6kbps. Since my AVR skills had improved over time, I was confident I could make tangible improvements to the code I wrote in 2014.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG0SqSfaL-BS0IZt3zHiRV8K_y4f1w9XGjhmAwGUJNjxB8XUZtSyg8uUgj1qZ5np438nLe7Fv6hSrWSUsTkaKd-8vJFwJAxWpXTXTkHvNAIJjkDbbYJO9LN-3sNWtywys8utOfyWhm_Q8/s1600/DS1Z_QuickPrint23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="800" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG0SqSfaL-BS0IZt3zHiRV8K_y4f1w9XGjhmAwGUJNjxB8XUZtSyg8uUgj1qZ5np438nLe7Fv6hSrWSUsTkaKd-8vJFwJAxWpXTXTkHvNAIJjkDbbYJO9LN-3sNWtywys8utOfyWhm_Q8/s640/DS1Z_QuickPrint23.png" width="640" /></a></div>
<br />
The screen shot above is from <a href="https://github.com/nerdralph/picoUART">picoUART</a> running on an ATtiny13, at a baud rate of 230.4kbps. The new UART has several improvements over my old code. To understand the improvements, it helps to understand how an asynchronous serial TTL UART works first.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyzdxRN686-bq5SAjn4F2RY5l20E1tEkXpFHTJxi1dhMC65Ho5Z9VGQ9HrXhN2rOC7p_fWzUvsZYztf99lVG1qJ4YPbN7lWE87l-evz334gQOR7FnmTcuKpyD9Nol7_bcnCWgjj_3lhJE/s1600/ttl-timing.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="272" data-original-width="1200" height="144" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyzdxRN686-bq5SAjn4F2RY5l20E1tEkXpFHTJxi1dhMC65Ho5Z9VGQ9HrXhN2rOC7p_fWzUvsZYztf99lVG1qJ4YPbN7lWE87l-evz334gQOR7FnmTcuKpyD9Nol7_bcnCWgjj_3lhJE/s640/ttl-timing.PNG" width="640" /></a></div>
<br />
Most embedded systems use 81N communication, which means 8 data bits, 1 stop bit, and no parity. Each frame begins with a low start bit, so the total frame is 1 start bit + 8 data bits + 1 stop bit for a total of 10 bits. Frames can be sent back-to-back with no idle time between them. The data is sent at a fixed baud rate, and when either the receiver or transmitter varies from the chosen baud rate, errors can occur.<br />
<br />
When it comes to the timing error tolerance of asynchronous serial communications, I've often read that somewhere between 2% and 3.5% timing error is acceptable. I've also read many "experts" claim that a micro-controller needs an accurate external crystal oscillator in order to avoid UART timing errors. The truth is that UART timing can be off by a total of over 5% without encountering errors. By total, I mean the sum of the errors for both ends, so if a transmitter is 2% fast, and the receiver is 2% slow, the 81N data frames can still be received error-free. The timing on a <a href="http://nerdralph.blogspot.com/2013/09/clone-pl-2303hx-usb-to-ttl-serial.html">USB-TTL UART adapter</a> is usually accurate to within 0.1%, so if I am sending data from an AVR that is running 3% slow, my PL2303HX adapter still receives it error-free.<br />
<br />
If a frame is being transmitted at 57.6kbps, each bit needs to last 1000/57.6 = 17.36us. That means 17.36us after bringing the line low for the start bit, the least significant bit needs to be sent. A receiver will wait for the start bit to begin, wait another 17.36, and then wait for the middle of the first bit to sample the line. If the line is high, the bit is a 1, and it it is low, the bit is a zero. So the receiver will sample the first bit 1.5 * 17.36 = 26.04us after the line goes low to signal the start bit. The last(8th) bit will be sampled after 8.5 *17.36 = 147.56us. If the transmitter is to slow, and is still transmitting the 7th bit, it will cause a communication error, as the receiver will interpret the 7th bit as actually being the 8th bit. If the transmitter is still sending the 7th bit after 147.56us, then it is sending at 8/8.5 or 0.941 * 57.6 = 54.2kbps. Since many UARTs check for a valid stop bit, the maximum timing error is usually 9/9.5 or 94.7% of the baud rate.<br />
<br />
The transmit timing of my earlier soft UART implementations is accurate to within 3 clock cycles. This was each iteration of <a href="https://github.com/nerdralph/nerdralph/blob/master/avr/libs/bbuart/BBUart.S#L36">the delay loop</a> takes 3 clock cycles - one for decrement and two for the branch:<br />
<table class="highlight tab-size js-file-line-container" data-tab-size="8"><tbody>
<tr><td class="blob-code blob-code-inner js-file-line" id="LC35"><b><span style="font-family: "courier new" , "courier" , monospace;"> ldi delayArg, TXDELAY</span></b></td>
</tr>
<tr>
</tr>
</tbody></table>
<table class="highlight tab-size js-file-line-container" data-tab-size="8"><tbody>
<tr><td class="blob-code blob-code-inner js-file-line" id="LC36"><span class="pl-en"><b><span style="font-family: "courier new" , "courier" , monospace;">TxDelay:</span></b></span></td>
</tr>
<tr>
</tr>
</tbody></table>
<table class="highlight tab-size js-file-line-container" data-tab-size="8"><tbody>
<tr><td class="blob-code blob-code-inner js-file-line" id="LC37"><b><span style="font-family: "courier new" , "courier" , monospace;"> dec delayArg</span></b></td>
</tr>
<tr>
</tr>
</tbody></table>
<b><span style="font-family: "courier new" , "courier" , monospace;"> brne TxDelay</span></b><br />
<br />
And since delayArg is an 8-bit register, the maximum delay added to the transmission of each bit is 2^8 * 3 = 768 cycles. On a MCU running at 8Mhz, that limited the lowest baud rate to around 8000/768 or 10.4kbps. To allow for lower bit rates, picoUART needed to support longer delays. I also wanted to support more accurate timing, so picoUART uses <a href="https://gcc.gnu.org/onlinedocs/gcc/AVR-Built-in-Functions.html">__builtin_avr_delay_cycles</a> during the transmission of each bit. The exact number of cycles to wait is calculated by some inline functions, <a href="https://gcc.gnu.org/onlinedocs/gcc/Inline.html">which is a better way</a> of doing the calculations than the macros I had used before. Writing picoUART in C made the timing calculations more difficult, since compiler has some flexibility in how the code is compiled to AVR machine instructions. In order to get avr-gcc to generate the exact sequence of instructions that I wanted, I had to use <a href="https://github.com/nerdralph/picoUART/blob/master/picoUART.c#L112">one inline asm statement</a>. When I used a C "while" loop instead of the asm goto "brne" instruction, the loop was one cycle longer due to a superfluous compare instruction. Future versions of the compiler may have improved optimization and omit the compare, which would slightly impact the timing.<br />
<br />
As with the transmit code, picoUART's receive code is accurate to within one cycle. Unlike my earlier UART code, picoUART returns after reading the 8th bit instead of waiting for the stop bit. Because of this change, picoUART begins by waiting for the line to be high before waiting for the start bit. Without the initial wait for high, back-to-back calls to purx() could lead an error when the 8th bit of one frame is 0(low) and gets interpreted as the start bit of the next frame. This change approximately triples the amount of time for the AVR to process each byte in a continuous stream of data.<br />
<br />
My earlier UART code had two incompatible versions. One version used open-drain communication, where the transmit line is pulled high by an external resistor, and pulled low by the AVR. This version supported using a single wire for both receive and transmit. While it also worked with separate pins, some users found it inconvenient to add the pull-up resistor. Instead they would choose the "push-pull" version, where the AVR drives the line high and pulls it low. With picoUART a single version works for both use cases, because it works in "push-pull" mode only during transmit. When not actively transmitting, the IO pin is set to input mode with the internal pull-up activated.<br />
<br />
I've tried to help both the noobs and experienced AVR developers. The noob can download <a href="https://github.com/nerdralph/picoUART/releases">a release zip file</a> to add as an Arduino library. If you are an old AVR developer like me that prefers a keyboard over a mouse, you'll find a basic Makefile with <a href="https://github.com/nerdralph/picoUART/tree/master/examples/echo">the echo example</a>. The default baud rate is 115.2kbps, although it is capable of accurate timing at much higher speeds such as 1mbps for an AVR running at 8Mhz. The default transmit is on PB0, with PB1 for receive. The defaults can be changed in pu_config.h, or with build flags like "-DPU_BAUD_RATE=230400L".<br />
<br />Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com13tag:blogger.com,1999:blog-6245413346375218188.post-29539166667566891612020-01-11T16:33:00.000-08:002020-01-11T16:33:53.496-08:00Picoboot v3 with autobaud and timeoutToday I released v3 beta2 of <a href="https://github.com/nerdralph/picoboot/tree/master/arduino">picoboot</a>. Like <a href="http://nerdralph.blogspot.com/2018/05/picoboot-adruino-with-autobaud.html">the last release of picoboot</a>, it takes up only 256 bytes, which is the minimum bootloader size supported on the ATmega88 and ATmega168. This means picoboot will free up 256 bytes of flash if you currently use <a href="https://github.com/optiboot">Optiboot</a>. Without any potential benefit from reduced size, this release focused on robustness and speed.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvaiUtqXNlRdVzExPMW36sB-k3nbrBw1OtiKBmK9kQP_FemU3mpape34A6dNXMDxELfc9UOAAqkSco147COfeas2F_vIwUkS3BH_A4r-mD9XB3oKKR4FgRI8HBzXJsLWTcDbzGozcZvo4/s1600/picobootV3b2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="641" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvaiUtqXNlRdVzExPMW36sB-k3nbrBw1OtiKBmK9kQP_FemU3mpape34A6dNXMDxELfc9UOAAqkSco147COfeas2F_vIwUkS3BH_A4r-mD9XB3oKKR4FgRI8HBzXJsLWTcDbzGozcZvo4/s1600/picobootV3b2.png" /></a></div>
<br />
The above screen shot shows reading the 16kB flash memory of an ATmega168 in 1.32 seconds. Using 500kbps instead of 250 will read the flash in under one second, and will read 32kB of flash from an ATmega328 in two seconds. Not only is it fast, it is reliable, with no errors using CH340G and CP2102 adapters under Windows, and PL2303HX adapters under Linux. So as long as your serial driver supports 250 or 500kbps and doesn't round them down to 230,400 and 460,800, you can have reliable and high speed uploading and verify of code on ATmega MCUs.<br />
<br />
Earlier versions of picoboot supported a bootloader toggle mode, where resetting the MCU once entered the bootloader, and resetting again ran the application code. I designed this with boards that don't support the auto-reset functionality of the Arduino bootloader. However this turned out to be problematic with some boards that do have auto-reset, where picoboot could sometimes toggle out of bootloader mode when it was supposed to enter bootloader mode. With v3, picoboot now implements a timeout where it will wait for a few seconds and if no communication is received from avrdude, the bootloader will exit.<br />
<br />
Like the previous versions, picoboot does not use the watchdog timer, and will not impact application code that uses the watchdog reset. To make picoboot useful for use with a standalone AVR on a breadboard, it does not rely on a user LED on PB5 to indicate bootloader activity. Instead, when the bootloader starts, it lowers the TXD line (PD1). This will light the RX LED on the attached serial adapter. If the bootloader times out, PD1 will be left floating before the bootloader exits.<br />
<br />
My recommended baud rate for picoboot is 250kbps. This baud rate results in 0 timing error with the AVR USART when used with the common clock rates of 8, 12, and 16Mhz, as well as the less-common 20Mhz. The faster 500kbps also results in 0 timing error with the USART, however poor design of some serial adapters makes the higher speeds more susceptible to noise, particularly when long wires are used to connect to the AVR. I didn't encounter problems at 500kbps, but I was a bit surprised by how much noise I saw on my oscilloscope when testing a CP2102.<br />
<br />
If you are using the Arduino IDE rather than the command line, I explained how to change the boards.txt file in <a href="http://nerdralph.blogspot.com/2015/06/picobootstk500-v1-release.html">my blog post about picoboot v1</a>.<br />
<br />
I plan to test v3 beta 2 for about a month, so expect the final v3 in early February. In addition to further testing on the mega168 and mega328, I'll test the mega88. If there is enough interest in a build for the mega8, I'll look into supporting it too.<br />
<br />
<br />
<br />Ralph Doncasterhttp://www.blogger.com/profile/00037504544742962130noreply@blogger.com7